14
\$\begingroup\$

Text adventure games have a pretty set formula; there's a world composed of a series of rooms / spaces, the player can move around these rooms, and there are some items in the rooms. Items can be picked up by the player, put down, used to access other rooms (e.g. keys), and combined with other items to make new items.

Challenge

Your challenge is to write a text adventure runtime in the fewest bytes (code golf). To keep things simple, all you need to do is output a truthy or falsey value depending on whether a given series of commands would win a given game or not (no interactivity, no human friendly output, etc.)

Game Rules

  • The world is always composed of a corridor with 10 connected rooms. Each room requires a key to enter, but can be exited at any time without a key (so it's some kind of latch lock I guess);
  • The player begins in room 0, and wins if they ever enter room 9 (once they reach room 9 they can do whatever they like, including go to another room, and they will still have won);
  • Each room can contain any number of items;
  • There are up to 26 items, named A-Z, and no item will appear more than once in the world;
  • The player can pick up items from the current room and place them in their inventory (they can also drop items from their inventory into the current room);
  • The player's maximum inventory size is finite, and will be provided with the level details;
  • At the beginning of the game, the player's inventory is always empty;
  • There is no limit to the maximum number of items in a room (though the implicit limit would be 26, since that is the total number of items);
  • Items A-J are keys which can be used to enter rooms 0-9 (i.e. the player can move to room 0 if they have item A, to room 1 if they have B, etc. note that keys are not required to leave a room, and the player begins in room 0, so the key "A" is only required if the player wants to return to room 0);
  • Items in the player's inventory can be combined to create new items (which will be created in the player's inventory) — the permitted combinations will be provided with the level details;
  • Combining items consumes the original items (i.e. if one of the items was a key, it will no-longer be possible to use that key);
  • If the player tries to do something impossible (e.g. pick up an item which is not in the current room / drop an item they don't have / combine items they don't have / go to a room they don't have the key for), nothing happens and they can continue;
  • The player will never give a nonsense command (e.g. go to room 11).

So a simple game might look like this:

  v
+---+---+---+---+---+---+---+---+---+---+
| C |   | J |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
|               CORRIDOR                |
+---------------------------------------+
Inventory capacity: 99

Room 0 contains item "C" (which is the key to room 2). Room 2 contains item "J" (which is the key to room 9). The player can win the game by picking up C, moving to room 2, picking up J, then moving to room 9.

A more complex game might be:

  v
+---+---+---+---+---+---+---+---+---+---+
| C |   | X |YZ |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
|               CORRIDOR                |
+---------------------------------------+
Inventory capacity: 10
C+X => D
Y+Z => J

Now the player can win by picking up C, moving to room 2, picking up X, combining C with X to create D, then moving to room 3. They can now pick up and combine Y and Z to get J, allowing them to go to room 9.


Input Format

There's a fair bit of input to handle, and that's a pretty boring task, so the input format is very flexible. You will get the following data, and how it should be sent to your program is largely up to you:

  • The initial contents of each room (list of 0 or more items for each room);
  • A collection of permitted item combinations (each contains 2 input items and their output item — note that the input items are unordered);
  • The maximum inventory size (integer, 0 <= size <= 26);
  • The list of commands the player attempted.

The player's commands can be:

  • [P]ick up <item> - picks up an item from the room and puts it into the player's inventory (if there is space)
  • [D]rop <item> - drops an item from the player's inventory into the current room
  • [C]ombine <item1> <item2> - combines 2 items in the player's inventory to produce a new item
  • [G]o to <room> - travels to the chosen room if the player has the required key

For example, the input format I used for testing was simple program arguments:

./adventure YZ '' '' '' '' '' '' '' '' ''  1 YZJ         2          PY PZ CYZ G9
#           r0 r1 r2 r3 r4 r5 r6 r7 r8 r9  combinations  inv. size  commands...
# means:
#  room 0 starts with items Y & Z, all other rooms start empty
#  1 combination is possible: Y+Z => J
#  max inventory size is 2
#  player commands are [P]ick up Y, [P]ick up Z, [C]ombine Y and Z, [G]o to room 9
#  (in this example, the player wins)

But if some other format makes it easier, that's fine (e.g. special delimiter characters / multiple lines / different ordering / serialised to JSON / etc.)

Output Format

Your program should return some truthy output if the player's commands cause them to win the game, and some falsey output otherwise. This could be a recognisable message to stdout, a program return code, or whatever your language of choice provides. All other output will be ignored.

Test Cases

The following bash script provides a test harness which will check most situations. It has been written to use the format described above, but modifying it to use a different format is just a case of adding a conversion in the invoke function.

#!/bin/sh

PROG="$1";

if [[ -z "$PROG" ]]; then
    echo "Usage: $0 <program-to-test>";
    exit 1;
fi;

function invoke {
    "$PROG" "$@"
}

RED="\033[1;31m";
GREEN="\033[1;32m";
RESET="\033[m";
FAILURES="0";

function pass {
    if ! invoke "$@" >/dev/null 2>&1; then
        echo "${RED}Expected pass, got fail:${RESET} $*" >&2;
        (( FAILURES = "$FAILURES" + 1 ));
        invoke "$@" 2>&1;
    fi;
}

function fail {
    if invoke "$@" >/dev/null 2>&1; then
        echo "${RED}Expected fail, got pass:${RESET} $*" >&2;
        (( FAILURES = "$FAILURES" + 1 ));
        invoke "$@" 2>&1;
    fi;
}

echo "Running tests...";

#    R0  R1  R2  R3  R4  R5  R6  R7  R8  R9  C      I  Cmd...
pass J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ G9;
fail ''  J   ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ G9;
pass J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ PJ G9;
fail J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ;
fail J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  G9;
pass J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  G9 PJ G9;
pass J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      1  PJ G9;
fail J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      0  PJ G9;
fail J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ DJ G9;
fail J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ PJ DJ G9;
pass J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ DJ PJ G9;
pass J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ DJ PJ G9;
pass B   CJ  ''  ''  ''  ''  ''  ''  ''  ''  0      2  PB G1 DB PC PJ G9;
fail B   CJ  ''  ''  ''  ''  ''  ''  ''  ''  0      2  PB G1 DB PB PC PJ G9;
pass AJ  ''  ''  ''  ''  ''  ''  ''  ''  ''  0      2  PA PJ G9;
pass B   D   ''  J   ''  ''  ''  ''  ''  ''  0      2  PB G1 PD G3 DB PJ G9;
fail B   D   ''  J   ''  ''  ''  ''  ''  ''  0      2  PB G1 PD G2 DB PJ G9;
fail B   D   ''  J   ''  ''  ''  ''  ''  ''  0      2  PB G1 PD G3 PJ G9;
fail B   D   J   C   ''  ''  ''  ''  ''  ''  0      2  PB G1 PD G3 PJ G9;
pass AJ  ''  ''  ''  ''  ''  ''  ''  ''  ''  0      2  PA PJ G9 G0;
fail ADJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      3  PA PD PJ G3 DJ G0 PJ G9;
pass ADJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      3  PA PD PJ G3 DJ G0 G3 PJ G9;
fail ADJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      3  PA PD PJ G3 DJ G0 DD G3 PJ G9;
pass ADJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      3  PA PD PJ DD G3 DJ G0 DD G3 PJ G9;
fail ADJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      1  PA DA DA PD PJ G9;
pass ADJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      1  PA DA DA PJ G9;
fail ABCDEFGHIKLMNOPQRSTUVWXYZ  J  '' '' '' '' '' '' '' '' 0 26 PA PB PC PD PE PF PG PH PI PJ PK PL PM PN PO PP PQ PR PS PT PU PV PW PX PY PZ G9;
pass ABCDEFGHIJKLMNOPQRSTUVWXYZ '' '' '' '' '' '' '' '' '' 0 26 PA PB PC PD PE PF PG PH PI PJ PK PL PM PN PO PP PQ PR PS PT PU PV PW PX PY PZ G9;
fail YZJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      2  PY PZ CYZ PJ G9;
pass YZJ ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZW  2  PY PZ CYZ PJ G9;
pass YZJ ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZW  2  PY PZ CYZ PJ CWJ G9;
fail XYZJ '' ''  ''  ''  ''  ''  ''  ''  ''  1 YZW  2  PY PZ CYZ PX PJ G9;
fail XYZJ '' ''  ''  ''  ''  ''  ''  ''  ''  1 YZW  2  PY PZ CYZ PX DY DZ PJ G9;
pass XYZJ '' ''  ''  ''  ''  ''  ''  ''  ''  1 YZW  2  PY PZ CYZ PX DW PJ G9;
pass YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PY PZ CYZ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  CYZ G9;
pass YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PY PZ CYZ CYZ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PY PZ CYZ DJ CYZ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PY PZ CYZ DJ PY PZ CYZ G9;
fail WZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PW PZ CYZ G9;
fail WZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PY PZ CYZ G9;
pass YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PY PZ CZY G9;
pass YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 ZYJ  2  PY PZ CYZ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  1  PY PZ CYZ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  1  PY PZ CYZ PY PZ CYZ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  1  PY PZ CYZ PJ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  1  PJ G9;
pass BW  UV  ''  ''  ''  ''  ''  ''  ''  ''  3 BUR WVS RSJ  2  PB PW G1 DW PU CBU DR PW PV CVW PR CRS G9;
fail BW  AUV ''  ''  ''  ''  ''  ''  ''  ''  3 BUR WVS RSJ  2  PB G1 PU CBU DR PA PB G0 DA PW G1 PV CVW PR CRS G9;
pass BCW AUV ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU DR PC PA PB G0 DA PW G1 DB PV CVW PR CRS G9;
fail BCW UV  ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU DR PC PA PB G0 DA PW G1 DB PV CVW PR CRS G9;
fail BCW AUV ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU PA PB G0 DA PW G1 DB PV CVW PR CRS G9;
fail BCW AUV ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU DR PA G0 DA PW G1 DB PV CVW PR CRS G9;
fail BCW AUV ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU DR PB G0 DA PW G1 DB PV CVW PR CRS G9;
fail BCW AUV ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU DR PA PB G0 DA G1 DB PV CVW PR CRS G9;
fail BCW AUV ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU DR PA PB G0 DA PW G1 DB CVW PR CRS G9;
pass BFK LG  M   N   O   CDE PQR U   W   ''  10 BPT CQS TSH HUI IWV VFA GRX MXZ ANY YZJ  5 \
  PB PF PK G1 PL PG G6 DB DK DL G5 PC PD PE G6 DF G2 PM G6 DM DC G3 PN G4 PO G6 DN DO DD DE \
  PB PP CBP PC PQ CCQ CTS G7 PU CUH G8 PW CWI G6 PF CVF PR PM PN CGR CMX CAN CYZ G9
fail BFK LG  M   N   O   CDE PQR U   W   ''  10 BPT CQS TSH HUI IWV VFA GRX MXZ ANY YZJ  5 \
  PB PF PK G1 PL PG G6 DB DK DL G5 PC PD PE G6 DF G6 DM DC G3 PN G4 PO PM G6 DN DO DD DE \
  PB PP CBP PC PQ CCQ CTS G7 PU CUH G8 PW CWI G6 PF CVF PR PM PN CGR CMX CAN CYZ G9

if (( "$FAILURES" == "0" )); then
    echo "${GREEN}All tests passed${RESET}";
else
    echo "${RED}Total failures: $FAILURES${RESET}";
fi;

Winning

Standard code golf: shortest code (in bytes) wins. Entries must follow the game rules, which in practice means they must pass all test cases (more tests may be added if necessary).

\$\endgroup\$
  • \$\begingroup\$ You might not believe me but I thought of a challenge that is pretty much the same thing as this one a few days ago... \$\endgroup\$ – acrolith Aug 6 '16 at 16:15
  • \$\begingroup\$ I like this challenge. However, I'd definitely include test cases outside of your test script. \$\endgroup\$ – Nathan Merrill Aug 6 '16 at 16:18
  • \$\begingroup\$ @NathanMerrill can do, what format would you prefer? (the test cases inside the script are already quite easy to parse so I wasn't sure what to do to make a test table without simply repeating the same lines!) \$\endgroup\$ – Dave Aug 6 '16 at 16:25
  • \$\begingroup\$ @daHugLenny I got the idea a few days ago too. I guess it's possible we were both inspired by some challenge posted last week, or another question on the network. I can't remember where I got the idea from. \$\endgroup\$ – Dave Aug 6 '16 at 16:26
  • \$\begingroup\$ Dropping an item the user does not have. Is it impossible (no op) or nonsense (will not happen). And dropping a not existing item? \$\endgroup\$ – edc65 Aug 6 '16 at 16:42
5
\$\begingroup\$

JavaScript (ES6), 244 249 267 280

Edit Saved 18 (!) bytes thx @Neil

A function with input:

  • r = room content (array of 10 strings)
  • k = combinations (array of string of 3 chars - source1, source2, result)
  • s = inventory max size (number)
  • c = commands (array of strings)

Returns true or false

(r,k,s,c,p=0,j={})=>c.some(([c,a,b])=>c<'D'?j[a]>0&j[b]>0&&!k.map(([t,u,v])=>u+t==a+b|u+t==b+a?j[j[a]=j[b]=v]=++s:0):c<'G'?j[a]>0&&!(j[++s,a]=~p):c>'G'?s&&j[a]==~p&&!(j[a]=s--):j['ABCDEFGHIJ'[a]]>0&&(p=a)>8,r.map((o,n)=>[...o].map(c=>j[c]=~n)))

See the test snippet below for a newline separated version

Test

Exec=
(r,k,s,c,p=0,j={})=>
c.some(
  ([c,a,b])=>
   c<'D'?j[a]>0&j[b]>0&&!k.map(([t,u,v])=>u+t==a+b|u+t==b+a?j[j[a]=j[b]=v]=++s:0)
   :c<'G'?j[a]>0&&!(j[++s,a]=~p)
   :c>'G'?s&&j[a]==~p&&!(j[a]=s--)
   :j['ABCDEFGHIJ'[a]]>0&&(p=a)>8
  ,r.map((o,n)=>[...o].map(c=>j[c]=~n))
)

console.log = (...x) => O.textContent += x + '\n';

;`pass J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ G9;
fail ''  J   ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ G9;
pass J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ PJ G9;
fail J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ;
fail J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  G9;
pass J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  G9 PJ G9;
pass J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      1  PJ G9;
fail J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      0  PJ G9;
fail J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ DJ G9;
fail J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ PJ DJ G9;
pass J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ DJ PJ G9;
pass J   ''  ''  ''  ''  ''  ''  ''  ''  ''  0      9  PJ DJ PJ G9;
pass B   CJ  ''  ''  ''  ''  ''  ''  ''  ''  0      2  PB G1 DB PC PJ G9;
fail B   CJ  ''  ''  ''  ''  ''  ''  ''  ''  0      2  PB G1 DB PB PC PJ G9;
pass AJ  ''  ''  ''  ''  ''  ''  ''  ''  ''  0      2  PA PJ G9;
pass B   D   ''  J   ''  ''  ''  ''  ''  ''  0      2  PB G1 PD G3 DB PJ G9;
fail B   D   ''  J   ''  ''  ''  ''  ''  ''  0      2  PB G1 PD G2 DB PJ G9;
fail B   D   ''  J   ''  ''  ''  ''  ''  ''  0      2  PB G1 PD G3 PJ G9;
fail B   D   J   C   ''  ''  ''  ''  ''  ''  0      2  PB G1 PD G3 PJ G9;
pass AJ  ''  ''  ''  ''  ''  ''  ''  ''  ''  0      2  PA PJ G9 G0;
fail ADJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      3  PA PD PJ G3 DJ G0 PJ G9;
pass ADJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      3  PA PD PJ G3 DJ G0 G3 PJ G9;
fail ADJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      3  PA PD PJ G3 DJ G0 DD G3 PJ G9;
pass ADJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      3  PA PD PJ DD G3 DJ G0 DD G3 PJ G9;
fail ADJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      1  PA DA DA PD PJ G9;
pass ADJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      1  PA DA DA PJ G9;
fail ABCDEFGHIKLMNOPQRSTUVWXYZ  J  '' '' '' '' '' '' '' '' 0 26 PA PB PC PD PE PF PG PH PI PJ PK PL PM PN PO PP PQ PR PS PT PU PV PW PX PY PZ G9;
pass ABCDEFGHIJKLMNOPQRSTUVWXYZ '' '' '' '' '' '' '' '' '' 0 26 PA PB PC PD PE PF PG PH PI PJ PK PL PM PN PO PP PQ PR PS PT PU PV PW PX PY PZ G9;
fail YZJ ''  ''  ''  ''  ''  ''  ''  ''  ''  0      2  PY PZ CYZ PJ G9;
pass YZJ ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZW  2  PY PZ CYZ PJ G9;
pass YZJ ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZW  2  PY PZ CYZ PJ CWJ G9;
fail XYZJ '' ''  ''  ''  ''  ''  ''  ''  ''  1 YZW  2  PY PZ CYZ PX PJ G9;
fail XYZJ '' ''  ''  ''  ''  ''  ''  ''  ''  1 YZW  2  PY PZ CYZ PX DY DZ PJ G9;
pass XYZJ '' ''  ''  ''  ''  ''  ''  ''  ''  1 YZW  2  PY PZ CYZ PX DW PJ G9;
pass YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PY PZ CYZ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  CYZ G9;
pass YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PY PZ CYZ CYZ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PY PZ CYZ DJ CYZ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PY PZ CYZ DJ PY PZ CYZ G9;
fail WZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PW PZ CYZ G9;
fail WZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PY PZ CYZ G9;
pass YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  2  PY PZ CZY G9;
pass YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 ZYJ  2  PY PZ CYZ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  1  PY PZ CYZ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  1  PY PZ CYZ PY PZ CYZ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  1  PY PZ CYZ PJ G9;
fail YZ  ''  ''  ''  ''  ''  ''  ''  ''  ''  1 YZJ  1  PJ G9;
pass BW  UV  ''  ''  ''  ''  ''  ''  ''  ''  3 BUR WVS RSJ  2  PB PW G1 DW PU CBU DR PW PV CVW PR CRS G9;
fail BW  AUV ''  ''  ''  ''  ''  ''  ''  ''  3 BUR WVS RSJ  2  PB G1 PU CBU DR PA PB G0 DA PW G1 PV CVW PR CRS G9;
pass BCW AUV ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU DR PC PA PB G0 DA PW G1 DB PV CVW PR CRS G9;
fail BCW UV  ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU DR PC PA PB G0 DA PW G1 DB PV CVW PR CRS G9;
fail BCW AUV ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU PA PB G0 DA PW G1 DB PV CVW PR CRS G9;
fail BCW AUV ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU DR PA G0 DA PW G1 DB PV CVW PR CRS G9;
fail BCW AUV ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU DR PB G0 DA PW G1 DB PV CVW PR CRS G9;
fail BCW AUV ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU DR PA PB G0 DA G1 DB PV CVW PR CRS G9;
fail BCW AUV ''  ''  ''  ''  ''  ''  ''  ''  3 CUR WVS RSJ  2  PB PC G1 DB PU CCU DR PA PB G0 DA PW G1 DB CVW PR CRS G9;
pass BFK LG  M   N   O   CDE PQR U   W   ''  10 BPT CQS TSH HUI IWV VFA GRX MXZ ANY YZJ 5 PB PF PK G1 PL PG G6 DB DK DL G5 PC PD PE G6 DF G2 PM G6 DM DC G3 PN G4 PO G6 DN DO DD DE PB PP CBP PC PQ CCQ CTS G7 PU CUH G8 PW CWI G6 PF CVF PR PM PN CGR CMX CAN CYZ G9
fail BFK LG  M   N   O   CDE PQR U   W   ''  10 BPT CQS TSH HUI IWV VFA GRX MXZ ANY YZJ 5 PB PF PK G1 PL PG G6 DB DK DL G5 PC PD PE G6 DF G6 DM DC G3 PN G4 PO PM G6 DN DO DD DE PB PP CBP PC PQ CCQ CTS G7 PU CUH G8 PW CWI G6 PF CVF PR PM PN CGR CMX CAN CYZ G9`
.split(/;*\n/).map((srow,i)=>{
  var row=srow.split(/\s+/),
  rooms=row.slice(1,11).map(x=>x=="''"?"":x),
  ncomb=+row[11],
  comb=row.slice(12,12+ncomb),
  isize=+row[12+ncomb],
  commands=row.slice(13+ncomb),
  start='['+rooms+'] ['+comb+'] '+isize+' ['+commands+']';
  var result=Exec(rooms,comb,isize,commands),
     ok = row[0] == ['fail','pass'][~~result]
  console.log(i, ok ? 'ok':'ko', start, row[0], result)
})
<pre id=O></pre>

\$\endgroup\$
  • \$\begingroup\$ Nice (ab)use of .map! \$\endgroup\$ – Dave Aug 7 '16 at 10:42
  • \$\begingroup\$ Why is a>8 inside ()s? Can j[--s,a]=1 become j[a]=s--? Also, String.fromCharCode is far too long, why not just index into "ABCDEFGHIJ"? \$\endgroup\$ – Neil Aug 7 '16 at 16:31
  • \$\begingroup\$ @Neil thanks for the hints, i'll check them all. Obviously this is a result of a series of changes (at some point I need j[] to be exactly 1, but now probably they can be any positive value) \$\endgroup\$ – edc65 Aug 7 '16 at 16:50
3
\$\begingroup\$

C, 338 bytes

I had a go at minifying my own test program. I think it went quite well, even if it is the longest answer so far!

#define P c[(i<j?i:j)*25+i+j]
#define F for(q=atoi(*++v);r
#define X c[r*91+i]
r,i,j,c[2405];main(q,v)char**v;{for(r=10;--r;)for(++v;i=**v;++*v)++X;++v;F<q;++r)i=**++v,j=1[*v],P=2[*v];r=9;F&&*++v;c[i=1[*v]]&&j==68?c[i]=!++X,++q:j>79&&q*X?c[i]=!--X,--q:j==71&&c[i+17]?r=57-i:j<68&&c[i]*c[j=2[*v]]&&P?c[i]=c[j]=0,c[P]=++q:0)j=**v;return r;}

This uses a few tricks to save space:

  • Rooms are loaded in reverse so that checking for room 9 becomes checking for room 0, which is cheaper
  • Room 9's contents never matter, so it is skipped when reading the input and used to store the inventory instead
  • The room contents and item combinations are stored in the same array. Because items are stored at their ascii values, they never overlap.

Breakdown:

#define P c[(i<j?i:j)*25+i+j]       // item combination lookup (input: i,j)
#define X c[r*91+i]                 // room item lookup (input: r,i)
r,i,j,c[2405];                      // globals default to 0
main(q,v)char**v;{                  // K&R syntax to save bytes
    for(r=10;--r;)                  // Load rooms 0-8, store as 9-1
        for(++v;i=**v;++*v)
            ++X;
    ++v;                            // Skip room 9
    for(q=atoi(*++v);r<q;++r)       // For each combination
        i=**++v,
        j=1[*v],                    // Use index[array] syntax to avoid (brackets)
        P=2[*v];                    // Record combination
    r=9;                            // Begin in room 0 (9 in memory)
    for(q=atoi(*++v);               // Load inventory size
                     r              // While not in room 9 (0 in memory)...
                      &&*++v;       // ...and still have user commands
                                    // (ISO C promises a NULL at the end of argv)
        c[i=1[*v]]&&j==68?          // If 'D'rop, and we have the item:
            c[i]=!++X,              //  Drop it
            ++q:                    //  Increase our inventory capacity
        j>79&&                      // If 'P'ick up, and...
              q                     // ...we have capacity in our inventory and...
               *X?                  // ...the item is in the room:
            c[i]=!--X,              //  Pick it up
            --q:                    //  Decrease our inventory capacity
        j==71&&c[i+17]?             // If 'G'o to room, and we have the key:
            r=57-i:                 //  Go to the room
        j<68&&                      // If 'C'ombine, and...
              c[i]*c[j=2[*v]]       // ...we have the items and...
                             &&P?   // ...they can be combined
            c[i]=c[j]=0,            //  Remove the items
            c[P]=++q:               //  Add the combined item and increase capacity
        0                           // Unrecognised or invalid command
    )
        j=**v;                      // 'j' is the command letter (happens first)
    return r;                       // Return the final room (0 = truthy in shell)
}

Partially inspired by @edc65's answer.


I was so close to getting ;*++*v; and c[P][c] into the code for ultimate confusion, but unfortunately other options turned out to be shorter :(

\$\endgroup\$
2
\$\begingroup\$

Haskell, 354 325 323 bytes

(#)=elem
(#)=elem
(%)=filter.(/=)
m!s=g""0.zip[0..]where g _ p _[]=p>8;g i p r(c:d)|p>8=1<2|'P':[k]<-c,k#x,length i<s=g(k:i)p((p,k%x):r)d|'D':[k]<-c,k#i=g(k%i)p((p,k:x):r)d|'C':q@[k,l]<-c,k#i,l#i,[y]<-[f|[d,e,f]<-m,q==[d,e]||q==[e,d]]=g(y:k%(l%i))p r d|'G':n<-c,y<-read n,['A'..]!!y#i=g i y r d|1<2=g i p r d where Just x=lookup p r

Defines a function ! which takes in order

  • the possible combinations as a list of 3 letter strings
  • the maximum size of the inventory
  • the rooms as a list of 9 strings
  • the player commands as a list of strings

Returns True or False. Example call:

*Main> (!) ["YZW"] 2 ["YZJ","","","","","","","","",""] ["PY","PZ","CYZ","PJ","CWJ","G9"]
True

All test cases.

Many bytes are spent in carrying the game state around. Haskell cannot destructively update data structures like the rooms and inventory.

The work is done by function g which takes 4 parameters: the inventory (String), the current room (Integer), the rooms (Assoc-List, with key: room number and value: items) and the commands left (List of String).

g _ p _[] = p>8                     -- base case. if no commands left, return
                                    -- True if we are in room 9
g i p r(c:d)
  | p>8 =   1<2                     -- reached room 9
  | 'P':[k]<-c,                     -- 'Pickup', if
        k#x,                        --   key is in room and
        length i<s                  --   inventory not full
        = g(k:i)p((p,k%x):r)d       --   adjust inventory and room
  | 'D':[k]<-c,                     -- 'Drop', if
        k#i                         --   key is in inventory
        = g(k%i)p((p,k:x):r)d       --   adjust inventory and room
  | 'C':q@[k,l]<-c,                 -- 'Combine', if
        k#i,l#i,                    --   both keys are in inventory and
        [y]<-[f|[d,e,f]<-m,q==[d,e]||q==[e,d]]
                                    --   combination is possible
        = g(y:k%(l%i))p r d         --   adjust inventory
  | 'G':n<-c, y<-read[n],           -- 'Goto', convert digit to integer  
        ['A'..]!!y#i                --   key for room is in inventory
        = g i y r t                 --   go to room
  | 1<2                             -- impossible command
        = g i p r d                 --   ignore it

Maybe the following things can save some bytes

  • State Monad for the game state
  • A single Assoc-List for the keys (key: letter of key, value: room number with -1 for inventory) instead of rooms/inventory No, constructing such an initial Assoc-List and the check for max inventory size costs more than it saves dealing with one less parameter.
\$\endgroup\$
  • \$\begingroup\$ Nice. I don't know enough Haskell to check, but you might be able to save some bytes by incrementing/decrementing s as your inventory use changes, to avoid having to check length i (you could check s against 0 instead) \$\endgroup\$ – Dave Aug 9 '16 at 19:30
  • \$\begingroup\$ @Dave: I don't think it pays off, because changing s would make it the fifth parameter of g and it would have to be passed around. I have 5 recursive calls to g, an additional parameter costs at least 6 bytes. \$\endgroup\$ – nimi Aug 9 '16 at 22:42
1
\$\begingroup\$

Python 3, 321 311 bytes

-10, thanks Dave

S,F=set,frozenset
def f(r,c,i,m):
 w,v,r,c=0,S(),list(map(S,r)),{F(k):S(x)for*k,x in c}
 for a,*q in m:
  p=F(q)
  if a<'D'and p in c and p<=v:v-=p;v|=c[p]
  elif a=='D'and p<=v:r[w]|=p;v-=p
  elif a=='G'and F(chr(65+int(q[0])))<=v:w=int(q[0])
  elif a>'G'and p<=r[w]and len(v)<i:r[w]-=p;v|=p
  if w==9:return 1

Rooms (r), combinations (c) and moves (m) are all lists of strings. Max inventory (i) is an int.

Fairly straight forward implementation. Used set()s for the room contents and inventory to make updating easy. Used frozensets to key a dictionary of the combinations, so that the order of the 2 input items is irrelevant.

\$\endgroup\$
  • \$\begingroup\$ Cool. Verified it on the test set with this: import sys;r=sys.argv[1:11];nc=int(sys.argv[11]);c=sys.argv[12:12+nc];i=int(sys.argv[12+nc]);m=sys.argv[13+nc:];exit(not f(r,c,i,m)) (semicolons -> newlines). By the way, looks like you left ,dbg=False in there; you can save 10 bytes by removing it! \$\endgroup\$ – Dave Aug 13 '16 at 11:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.