{"id":"text-adventure","title":"A text adventure","summary":"A small game you play by typing. Walk through the rooms of an old clockmaker's house, pick things up, open a locked door and start the great clock again, with the whole world read from a file into dictionaries.","after":"reading-files","intro":"In this project you build a **text adventure**: a game that tells you where you are, reads what you want to do (`go north`, `take key`), and answers in text. The world isn't written into the program. It comes from a file, `world.txt`, so the same program can play any world that the file describes.\n\nWhen it's finished, this file is a whole world: the house of an old clockmaker, with five rooms, three things to find, a locked door and a goal.\n\n```text\nroom;Workshop;Half-built clocks tick on every shelf.\nroom;Hallway;A long hallway. The grandfather clock here has stopped.\nroom;Kitchen;A kettle sits on a cold stove.\nroom;Attic;Dusty boxes stand under a round window.\nroom;Clock Tower;Huge wheels fill the tower. One gear is missing.\nexit;Workshop;north;Hallway\nexit;Hallway;south;Workshop\nexit;Hallway;east;Kitchen\nexit;Hallway;up;Attic\nexit;Kitchen;west;Hallway\nexit;Attic;down;Hallway\nexit;Attic;up;Clock Tower\nexit;Clock Tower;down;Attic\nitem;Workshop;key\nitem;Kitchen;gear\nitem;Attic;oil\nlock;Attic;up;key\ngoal;Clock Tower;gear;You fit the gear into place, and the great clock starts ticking again.\n```\n\nA player who types these commands, one per line:\n\n```text\ntake key\ngo north\ngo east\ntake gear\ngo west\ngo up\ngo up\n```\n\nsees this, with the game's prompt `> ` in front of each command:\n\n```text\n== Workshop ==\nHalf-built clocks tick on every shelf.\nExits: north\nYou see: key\n> take key\nYou take the key.\n> go north\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: south, east, up\n> go east\n== Kitchen ==\nA kettle sits on a cold stove.\nExits: west\nYou see: gear\n> take gear\nYou take the gear.\n> go west\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: south, east, up\n> go up\n== Attic ==\nDusty boxes stand under a round window.\nExits: down, up\nYou see: oil\n> go up\nYou unlock the way up with the key.\n== Clock Tower ==\nHuge wheels fill the tower. One gear is missing.\nExits: down\nYou fit the gear into place, and the great clock starts ticking again.\nYou won in 5 moves.\n```\n\n### What you need\n\nEverything up to module 23: functions that return values (modules 13 and 14), lists (module 15), the string methods `split`, `join` and `strip` (module 18), dictionaries, also dictionaries inside a dictionary (module 21), a function that returns two values as a tuple (module 22), and reading a file line by line with `with open(...)` (module 23).\n\n### How you build it\n\nEvery line of `world.txt` starts with its kind (`room`, `exit`, `item`, `lock` or `goal`), then the name of the room it's about, and then the rest, all separated by `;`. The room lines come first in the file. Each milestone teaches the program one more kind of line, with the commands that go with it:\n\n1. **Rooms:** the rooms from the file, and a loop that reads commands.\n2. **Exits:** `go north` takes you to the next room.\n3. **Things to carry:** `take`, `drop` and `inventory`.\n4. **A locked door:** an exit that opens only for a player who carries the right thing.\n5. **The goal:** the game ends when you reach it, and counts your moves.\n\nThe program skips the lines of a kind it doesn't know yet, so a world file written for a later milestone still works with an earlier program.\n","milestones":[{"id":"rooms","title":"Rooms","task":"The starting code opens `world.txt` and splits each of its lines at `;`. A line such as `room;Workshop;Half-built clocks tick on every shelf.` is a room: the word `room`, the room's name and its description.\n\n1. In `read_world`, add each room to the dictionary `rooms`. The key is the room's name, and the value is a dictionary of its own, for everything the game knows about that room. For now that's only the description: `{\"description\": \"Half-built clocks tick on every shelf.\"}`. The game starts in the first room of the file, so `start` is the name of that room. A line of another kind (the next milestones read those) is simply skipped.\n2. Write `show_room(rooms, name)`, which prints the room's name between `==` signs, and its description under it:\n\n   ```text\n   == Workshop ==\n   Half-built clocks tick on every shelf.\n   ```\n\n3. In `main`, show the room where the game starts. Then read commands with the prompt `> `, one at a time, until the player types `quit`:\n   - `look` shows the room again.\n   - `quit` prints `Goodbye!` and ends the game.\n   - Anything else prints `I don't understand that.`\n\nWith this `world.txt`\n\n```text\nroom;Workshop;Half-built clocks tick on every shelf.\nroom;Hallway;A long hallway. The grandfather clock here has stopped.\n```\n\nand the commands `look`, `dance` and `quit`, the program prints:\n\n```\n== Workshop ==\nHalf-built clocks tick on every shelf.\n> look\n== Workshop ==\nHalf-built clocks tick on every shelf.\n> dance\nI don't understand that.\n> quit\nGoodbye!\n```\n","kind":"code","seed":"# A text adventure. The world is read from the file world.txt.\n\n\ndef read_world(filename):\n    rooms = {}\n    start = \"\"\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            # Each line starts with its kind. A room line is room;name;description\n\n    return rooms, start\n\n\ndef main():\n    rooms, start = read_world(\"world.txt\")\n    location = start\n    # Show the room where the game starts, then read commands until quit.\n\n\nif __name__ == \"__main__\":\n    main()\n","solution":"# A text adventure. The world is read from the file world.txt.\n\n\ndef read_world(filename):\n    rooms = {}\n    start = \"\"\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"room\":\n                rooms[parts[1]] = {\"description\": parts[2]}\n                if start == \"\":\n                    start = parts[1]\n    return rooms, start\n\n\ndef show_room(rooms, name):\n    print(f\"== {name} ==\")\n    print(rooms[name][\"description\"])\n\n\ndef main():\n    rooms, start = read_world(\"world.txt\")\n    location = start\n    show_room(rooms, location)\n    while True:\n        command = input(\"> \")\n        if command == \"quit\":\n            print(\"Goodbye!\")\n            break\n        if command == \"look\":\n            show_room(rooms, location)\n        else:\n            print(\"I don't understand that.\")\n\n\nif __name__ == \"__main__\":\n    main()\n","hints":["In the loop of `read_world`, `if parts[0] == \"room\":` picks the room lines. Then `parts[1]` is the name and `parts[2]` the description: `rooms[parts[1]] = {\"description\": parts[2]}`.","`start` begins as `\"\"`, so the first room line is the one that finds it still empty: `if start == \"\":` then `start = parts[1]`.","`show_room` prints `f\"== {name} ==\"` and then `rooms[name][\"description\"]`. In `main`, a `while True:` loop reads `command = input(\"> \")` and leaves with `break` after `Goodbye!`."],"tests":[{"name":"Look around","stdin":"look\ndance\nquit\n","files":{"world.txt":"room;Workshop;Half-built clocks tick on every shelf.\nroom;Hallway;A long hallway. The grandfather clock here has stopped.\n"},"expect":"== Workshop ==\nHalf-built clocks tick on every shelf.\n> look\n== Workshop ==\nHalf-built clocks tick on every shelf.\n> dance\nI don't understand that.\n> quit\nGoodbye!","hidden":false},{"name":"Reading the rooms","stdin":"","call":"rooms, start = read_world(\"world.txt\")\nprint(start)\nprint(len(rooms))\nprint(rooms[\"Hallway\"][\"description\"])\n","files":{"world.txt":"room;Workshop;Half-built clocks tick on every shelf.\nroom;Hallway;A long hallway. The grandfather clock here has stopped.\n"},"expect":"Workshop\n2\nA long hallway. The grandfather clock here has stopped.","hidden":false},{"name":"Another world, with lines to skip","stdin":"Look\nlook\ngo down\nquit\n","files":{"world.txt":"room;Deck;Wet planks creak under your feet.\nroom;Cabin;A hammock swings from the ceiling.\nroom;Galley;Pots and pans rattle with every wave.\nexit;Deck;down;Cabin\nexit;Cabin;up;Deck\nitem;Galley;biscuit\n"},"expect":"== Deck ==\nWet planks creak under your feet.\n> Look\nI don't understand that.\n> look\n== Deck ==\nWet planks creak under your feet.\n> go down\nI don't understand that.\n> quit\nGoodbye!","hidden":true},{"name":"The rooms as dictionaries","stdin":"","call":"rooms, start = read_world(\"world.txt\")\nprint(start)\nprint(rooms)\nshow_room(rooms, \"Garden\")\n","files":{"world.txt":"room;Cellar;Shelves of jam jars line the walls.\nexit;Cellar;up;Garden\nroom;Garden;Bees buzz between the roses.\n"},"expect":"Cellar\n{'Cellar': {'description': 'Shelves of jam jars line the walls.'}, 'Garden': {'description': 'Bees buzz between the roses.'}}\n== Garden ==\nBees buzz between the roses.","hidden":true},{"name":"Quit at once","stdin":"quit\n","files":{"world.txt":"room;Shed;Rakes and spades hang on the wall.\n"},"expect":"== Shed ==\nRakes and spades hang on the wall.\n> quit\nGoodbye!","hidden":true}],"require":[],"forbid":[]},{"id":"exits","title":"Exits","task":"Now the rooms get exits. A line such as `exit;Workshop;north;Hallway` means: from the Workshop, the way north leads to the Hallway. The room lines come first in the file, so every room that an exit line names already exists when the line is read.\n\n1. Give each room's dictionary the key `\"exits\"`, with an empty dictionary as its value when the room is created. For each exit line, `read_world` adds the direction as a key to that dictionary, with the name of the room it leads to as the value.\n2. When a room has exits, `show_room` prints one more line after the description, with their directions in the order of the file, separated by a comma and a space: `Exits: south, east`. A room without exits prints no such line.\n3. A new command: `go` and a direction, such as `go north`, takes the player through that exit and shows the new room. When the room has no exit that way, it prints `You can't go west from here.` (with the direction the player typed). Only a command of exactly these two words is a move; `go` on its own is still a command the game doesn't understand.\n\nWith this `world.txt`\n\n```text\nroom;Workshop;Half-built clocks tick on every shelf.\nroom;Hallway;A long hallway. The grandfather clock here has stopped.\nroom;Kitchen;A kettle sits on a cold stove.\nexit;Workshop;north;Hallway\nexit;Hallway;south;Workshop\nexit;Hallway;east;Kitchen\nexit;Kitchen;west;Hallway\n```\n\nand the commands `go north`, `go up`, `go east` and `quit`, the program prints:\n\n```\n== Workshop ==\nHalf-built clocks tick on every shelf.\nExits: north\n> go north\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: south, east\n> go up\nYou can't go up from here.\n> go east\n== Kitchen ==\nA kettle sits on a cold stove.\nExits: west\n> quit\nGoodbye!\n```\n","kind":"code","seed":"# A text adventure. The world is read from the file world.txt.\n\n\ndef read_world(filename):\n    rooms = {}\n    start = \"\"\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"room\":\n                rooms[parts[1]] = {\"description\": parts[2]}\n                if start == \"\":\n                    start = parts[1]\n    return rooms, start\n\n\ndef show_room(rooms, name):\n    print(f\"== {name} ==\")\n    print(rooms[name][\"description\"])\n\n\ndef main():\n    rooms, start = read_world(\"world.txt\")\n    location = start\n    show_room(rooms, location)\n    while True:\n        command = input(\"> \")\n        if command == \"quit\":\n            print(\"Goodbye!\")\n            break\n        if command == \"look\":\n            show_room(rooms, location)\n        else:\n            print(\"I don't understand that.\")\n\n\nif __name__ == \"__main__\":\n    main()\n","solution":"# A text adventure. The world is read from the file world.txt.\n\n\ndef read_world(filename):\n    rooms = {}\n    start = \"\"\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"room\":\n                rooms[parts[1]] = {\"description\": parts[2], \"exits\": {}}\n                if start == \"\":\n                    start = parts[1]\n            elif parts[0] == \"exit\":\n                rooms[parts[1]][\"exits\"][parts[2]] = parts[3]\n    return rooms, start\n\n\ndef show_room(rooms, name):\n    room = rooms[name]\n    print(f\"== {name} ==\")\n    print(room[\"description\"])\n    if len(room[\"exits\"]) > 0:\n        print(\"Exits: \" + \", \".join(room[\"exits\"]))\n\n\ndef move(rooms, location, direction):\n    room = rooms[location]\n    if direction not in room[\"exits\"]:\n        print(f\"You can't go {direction} from here.\")\n        return location\n    return room[\"exits\"][direction]\n\n\ndef main():\n    rooms, start = read_world(\"world.txt\")\n    location = start\n    show_room(rooms, location)\n    while True:\n        command = input(\"> \")\n        words = command.split()\n        if command == \"quit\":\n            print(\"Goodbye!\")\n            break\n        if command == \"look\":\n            show_room(rooms, location)\n        elif len(words) == 2 and words[0] == \"go\":\n            new_location = move(rooms, location, words[1])\n            if new_location != location:\n                location = new_location\n                show_room(rooms, location)\n        else:\n            print(\"I don't understand that.\")\n\n\nif __name__ == \"__main__\":\n    main()\n","hints":["A room is now created as `{\"description\": parts[2], \"exits\": {}}`. For an exit line, `rooms[parts[1]][\"exits\"][parts[2]] = parts[3]`: the room, then its exits, then the direction.","`join` from module 18 puts the comma and the space between the directions. Going through a dictionary gives its keys in order, and so does joining one: `\", \".join(room[\"exits\"])` joins the directions.","Split the command into words with `words = command.split()`. It's a move when `len(words) == 2 and words[0] == \"go\"`, and `words[1]` is the direction: `if direction in room[\"exits\"]:` tells whether there's a way."],"tests":[{"name":"Walk north and east","stdin":"go north\ngo up\ngo east\nquit\n","files":{"world.txt":"room;Workshop;Half-built clocks tick on every shelf.\nroom;Hallway;A long hallway. The grandfather clock here has stopped.\nroom;Kitchen;A kettle sits on a cold stove.\nexit;Workshop;north;Hallway\nexit;Hallway;south;Workshop\nexit;Hallway;east;Kitchen\nexit;Kitchen;west;Hallway\n"},"expect":"== Workshop ==\nHalf-built clocks tick on every shelf.\nExits: north\n> go north\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: south, east\n> go up\nYou can't go up from here.\n> go east\n== Kitchen ==\nA kettle sits on a cold stove.\nExits: west\n> quit\nGoodbye!","hidden":false},{"name":"Exits of a room","stdin":"","call":"rooms, start = read_world(\"world.txt\")\nshow_room(rooms, \"Hallway\")\nshow_room(rooms, \"Kitchen\")\n","files":{"world.txt":"room;Workshop;Half-built clocks tick on every shelf.\nroom;Hallway;A long hallway. The grandfather clock here has stopped.\nroom;Kitchen;A kettle sits on a cold stove.\nexit;Hallway;south;Workshop\nexit;Hallway;east;Kitchen\nexit;Hallway;up;Attic\n"},"expect":"== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: south, east, up\n== Kitchen ==\nA kettle sits on a cold stove.","hidden":false},{"name":"Up and down the ship","stdin":"go down\ngo down\ngo up\nlook\ngo\ngo up now\ngo down\ngo up\ngo up\nquit\n","files":{"world.txt":"room;Deck;Wet planks creak under your feet.\nroom;Cabin;A hammock swings from the ceiling.\nroom;Hold;Barrels are stacked in the dark.\nroom;Crow's Nest;You can see for miles.\nexit;Deck;down;Cabin\nexit;Deck;up;Crow's Nest\nexit;Cabin;up;Deck\nexit;Cabin;down;Hold\nexit;Crow's Nest;down;Deck\n"},"expect":"== Deck ==\nWet planks creak under your feet.\nExits: down, up\n> go down\n== Cabin ==\nA hammock swings from the ceiling.\nExits: up, down\n> go down\n== Hold ==\nBarrels are stacked in the dark.\n> go up\nYou can't go up from here.\n> look\n== Hold ==\nBarrels are stacked in the dark.\n> go\nI don't understand that.\n> go up now\nI don't understand that.\n> go down\nYou can't go down from here.\n> go up\nYou can't go up from here.\n> go up\nYou can't go up from here.\n> quit\nGoodbye!","hidden":true},{"name":"A room without exits","stdin":"go north\nlook\nquit\n","files":{"world.txt":"room;Cell;Four stone walls and a narrow window.\nitem;Cell;spoon\n"},"expect":"== Cell ==\nFour stone walls and a narrow window.\n> go north\nYou can't go north from here.\n> look\n== Cell ==\nFour stone walls and a narrow window.\n> quit\nGoodbye!","hidden":true},{"name":"The exits as a dictionary","stdin":"","call":"rooms, start = read_world(\"world.txt\")\nprint(rooms[\"Garden\"][\"exits\"])\nprint(rooms[\"Cellar\"][\"exits\"][\"up\"])\nshow_room(rooms, \"Garden\")\n","files":{"world.txt":"room;Cellar;Shelves of jam jars line the walls.\nroom;Garden;Bees buzz between the roses.\nexit;Cellar;up;Garden\nexit;Garden;down;Cellar\nexit;Garden;west;Cellar\n"},"expect":"{'down': 'Cellar', 'west': 'Cellar'}\nGarden\n== Garden ==\nBees buzz between the roses.\nExits: down, west","hidden":true}],"require":[],"forbid":[]},{"id":"items","title":"Things to carry","task":"An adventure needs things to find. A line such as `item;Kitchen;gear` means that a gear lies in the Kitchen. Item names are single words.\n\n1. Give each room's dictionary the key `\"items\"`, with an empty list when the room is created. For each item line, `read_world` appends the item to that room's list.\n2. When a room has items, `show_room` prints one more line after the exits: `You see: gear, spoon`, in the order of the list.\n3. The player carries an **inventory**, a list that starts empty, and three new commands use it:\n   - `take gear`: when the gear is in the room, it moves from the room's list to the end of the inventory, and the game prints `You take the gear.` Otherwise it prints `There is no gear here.`\n   - `drop gear`: when the player carries the gear, it moves from the inventory to the end of the room's list, with `You drop the gear.` Otherwise `You don't have the gear.`\n   - `inventory` prints `You carry: key, gear`, in the order of the list, or `You carry nothing.`\n\nWith the world of the last milestone and three item lines at the end of it\n\n```text\nitem;Workshop;key\nitem;Kitchen;gear\nitem;Kitchen;spoon\n```\n\nand the commands `take key`, `take gear`, `go north`, `go east`, `take gear`, `drop key`, `inventory`, `look` and `quit`, the program prints:\n\n```\n== Workshop ==\nHalf-built clocks tick on every shelf.\nExits: north\nYou see: key\n> take key\nYou take the key.\n> take gear\nThere is no gear here.\n> go north\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: south, east\n> go east\n== Kitchen ==\nA kettle sits on a cold stove.\nExits: west\nYou see: gear, spoon\n> take gear\nYou take the gear.\n> drop key\nYou drop the key.\n> inventory\nYou carry: gear\n> look\n== Kitchen ==\nA kettle sits on a cold stove.\nExits: west\nYou see: spoon, key\n> quit\nGoodbye!\n```\n","kind":"code","seed":"# A text adventure. The world is read from the file world.txt.\n\n\ndef read_world(filename):\n    rooms = {}\n    start = \"\"\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"room\":\n                rooms[parts[1]] = {\"description\": parts[2], \"exits\": {}}\n                if start == \"\":\n                    start = parts[1]\n            elif parts[0] == \"exit\":\n                rooms[parts[1]][\"exits\"][parts[2]] = parts[3]\n    return rooms, start\n\n\ndef show_room(rooms, name):\n    room = rooms[name]\n    print(f\"== {name} ==\")\n    print(room[\"description\"])\n    if len(room[\"exits\"]) > 0:\n        print(\"Exits: \" + \", \".join(room[\"exits\"]))\n\n\ndef move(rooms, location, direction):\n    room = rooms[location]\n    if direction not in room[\"exits\"]:\n        print(f\"You can't go {direction} from here.\")\n        return location\n    return room[\"exits\"][direction]\n\n\ndef main():\n    rooms, start = read_world(\"world.txt\")\n    location = start\n    show_room(rooms, location)\n    while True:\n        command = input(\"> \")\n        words = command.split()\n        if command == \"quit\":\n            print(\"Goodbye!\")\n            break\n        if command == \"look\":\n            show_room(rooms, location)\n        elif len(words) == 2 and words[0] == \"go\":\n            new_location = move(rooms, location, words[1])\n            if new_location != location:\n                location = new_location\n                show_room(rooms, location)\n        else:\n            print(\"I don't understand that.\")\n\n\nif __name__ == \"__main__\":\n    main()\n","solution":"# A text adventure. The world is read from the file world.txt.\n\n\ndef read_world(filename):\n    rooms = {}\n    start = \"\"\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"room\":\n                rooms[parts[1]] = {\"description\": parts[2], \"exits\": {}, \"items\": []}\n                if start == \"\":\n                    start = parts[1]\n            elif parts[0] == \"exit\":\n                rooms[parts[1]][\"exits\"][parts[2]] = parts[3]\n            elif parts[0] == \"item\":\n                rooms[parts[1]][\"items\"].append(parts[2])\n    return rooms, start\n\n\ndef show_room(rooms, name):\n    room = rooms[name]\n    print(f\"== {name} ==\")\n    print(room[\"description\"])\n    if len(room[\"exits\"]) > 0:\n        print(\"Exits: \" + \", \".join(room[\"exits\"]))\n    if len(room[\"items\"]) > 0:\n        print(\"You see: \" + \", \".join(room[\"items\"]))\n\n\ndef move(rooms, location, direction):\n    room = rooms[location]\n    if direction not in room[\"exits\"]:\n        print(f\"You can't go {direction} from here.\")\n        return location\n    return room[\"exits\"][direction]\n\n\ndef take(room, item, inventory):\n    if item in room[\"items\"]:\n        room[\"items\"].remove(item)\n        inventory.append(item)\n        print(f\"You take the {item}.\")\n    else:\n        print(f\"There is no {item} here.\")\n\n\ndef drop(room, item, inventory):\n    if item in inventory:\n        inventory.remove(item)\n        room[\"items\"].append(item)\n        print(f\"You drop the {item}.\")\n    else:\n        print(f\"You don't have the {item}.\")\n\n\ndef main():\n    rooms, start = read_world(\"world.txt\")\n    location = start\n    inventory = []\n    show_room(rooms, location)\n    while True:\n        command = input(\"> \")\n        words = command.split()\n        if command == \"quit\":\n            print(\"Goodbye!\")\n            break\n        if command == \"look\":\n            show_room(rooms, location)\n        elif command == \"inventory\":\n            if len(inventory) == 0:\n                print(\"You carry nothing.\")\n            else:\n                print(\"You carry: \" + \", \".join(inventory))\n        elif len(words) == 2 and words[0] == \"go\":\n            new_location = move(rooms, location, words[1])\n            if new_location != location:\n                location = new_location\n                show_room(rooms, location)\n        elif len(words) == 2 and words[0] == \"take\":\n            take(rooms[location], words[1], inventory)\n        elif len(words) == 2 and words[0] == \"drop\":\n            drop(rooms[location], words[1], inventory)\n        else:\n            print(\"I don't understand that.\")\n\n\nif __name__ == \"__main__\":\n    main()\n","hints":["A room is now created with `\"items\": []` as well, and an item line does `rooms[parts[1]][\"items\"].append(parts[2])`. `join` works for the list of items as well.","In `main`, `inventory = []` goes before the loop. A take is `len(words) == 2 and words[0] == \"take\"`, like a move. `rooms[location][\"items\"]` is the list of the room the player is in.","Moving an item is two steps: `remove` it from one list and `append` it to the other. A function such as `take(room, item, inventory)` keeps `main` short: a list passed to a function is the same list, so the function's changes show in `main` too."],"tests":[{"name":"Take and drop","stdin":"take key\ntake gear\ngo north\ngo east\ntake gear\ndrop key\ninventory\nlook\nquit\n","files":{"world.txt":"room;Workshop;Half-built clocks tick on every shelf.\nroom;Hallway;A long hallway. The grandfather clock here has stopped.\nroom;Kitchen;A kettle sits on a cold stove.\nexit;Workshop;north;Hallway\nexit;Hallway;south;Workshop\nexit;Hallway;east;Kitchen\nexit;Kitchen;west;Hallway\nitem;Workshop;key\nitem;Kitchen;gear\nitem;Kitchen;spoon\n"},"expect":"== Workshop ==\nHalf-built clocks tick on every shelf.\nExits: north\nYou see: key\n> take key\nYou take the key.\n> take gear\nThere is no gear here.\n> go north\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: south, east\n> go east\n== Kitchen ==\nA kettle sits on a cold stove.\nExits: west\nYou see: gear, spoon\n> take gear\nYou take the gear.\n> drop key\nYou drop the key.\n> inventory\nYou carry: gear\n> look\n== Kitchen ==\nA kettle sits on a cold stove.\nExits: west\nYou see: spoon, key\n> quit\nGoodbye!","hidden":false},{"name":"Carry things to another room","stdin":"inventory\ndrop rope\ntake map\ntake map\ntake rope\ninventory\ngo down\ndrop map\ntake lantern\ninventory\ngo up\ntake\ntake two ropes\nlook\nquit\n","files":{"world.txt":"room;Deck;Wet planks creak under your feet.\nroom;Cabin;A hammock swings from the ceiling.\nexit;Deck;down;Cabin\nexit;Cabin;up;Deck\nitem;Deck;rope\nitem;Deck;map\nitem;Cabin;lantern\n"},"expect":"== Deck ==\nWet planks creak under your feet.\nExits: down\nYou see: rope, map\n> inventory\nYou carry nothing.\n> drop rope\nYou don't have the rope.\n> take map\nYou take the map.\n> take map\nThere is no map here.\n> take rope\nYou take the rope.\n> inventory\nYou carry: map, rope\n> go down\n== Cabin ==\nA hammock swings from the ceiling.\nExits: up\nYou see: lantern\n> drop map\nYou drop the map.\n> take lantern\nYou take the lantern.\n> inventory\nYou carry: rope, lantern\n> go up\n== Deck ==\nWet planks creak under your feet.\nExits: down\n> take\nI don't understand that.\n> take two ropes\nI don't understand that.\n> look\n== Deck ==\nWet planks creak under your feet.\nExits: down\n> quit\nGoodbye!","hidden":true},{"name":"Items as lists","stdin":"","call":"rooms, start = read_world(\"world.txt\")\nprint(rooms[\"Garden\"][\"items\"])\nprint(rooms[\"Cellar\"][\"items\"])\nshow_room(rooms, \"Garden\")\n","files":{"world.txt":"room;Cellar;Shelves of jam jars line the walls.\nroom;Garden;Bees buzz between the roses.\nexit;Cellar;up;Garden\nitem;Garden;rake\nitem;Cellar;jar\nitem;Garden;seeds\n"},"expect":"['rake', 'seeds']\n['jar']\n== Garden ==\nBees buzz between the roses.\nYou see: rake, seeds","hidden":true}],"require":[],"forbid":[]},{"id":"locks","title":"A locked door","task":"Some ways are locked. A line such as `lock;Attic;up;key` means that the exit `up` from the Attic is locked, and that the key opens it. The exit itself has its own exit line, as before.\n\n1. Give each room's dictionary the key `\"locks\"`, with an empty dictionary when the room is created. For each lock line, `read_world` adds the direction as a key, with the item that opens it as the value.\n2. When the player tries to go through a locked exit:\n   - If they carry the item that opens it, the game prints `You unlock the way up with the key.`, the lock is gone for good, and the player goes through as usual.\n   - Otherwise the game prints `The way up is locked.` and the player stays where they are.\n\nA locked exit is listed among the exits as usual: the player only finds out it's locked when they try it.\n\nWith this `world.txt`\n\n```text\nroom;Hallway;A long hallway. The grandfather clock here has stopped.\nroom;Attic;Dusty boxes stand under a round window.\nroom;Clock Tower;Huge wheels fill the tower. One gear is missing.\nroom;Workshop;Half-built clocks tick on every shelf.\nexit;Hallway;up;Attic\nexit;Hallway;south;Workshop\nexit;Attic;down;Hallway\nexit;Attic;up;Clock Tower\nexit;Clock Tower;down;Attic\nexit;Workshop;north;Hallway\nitem;Workshop;key\nlock;Attic;up;key\n```\n\nand the commands `go up`, `go up`, `go down`, `go south`, `take key`, `go north`, `go up`, `go up`, `go down`, `go up` and `quit`, the program prints:\n\n```\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: up, south\n> go up\n== Attic ==\nDusty boxes stand under a round window.\nExits: down, up\n> go up\nThe way up is locked.\n> go down\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: up, south\n> go south\n== Workshop ==\nHalf-built clocks tick on every shelf.\nExits: north\nYou see: key\n> take key\nYou take the key.\n> go north\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: up, south\n> go up\n== Attic ==\nDusty boxes stand under a round window.\nExits: down, up\n> go up\nYou unlock the way up with the key.\n== Clock Tower ==\nHuge wheels fill the tower. One gear is missing.\nExits: down\n> go down\n== Attic ==\nDusty boxes stand under a round window.\nExits: down, up\n> go up\n== Clock Tower ==\nHuge wheels fill the tower. One gear is missing.\nExits: down\n> quit\nGoodbye!\n```\n\nThe last `go up` passes without a word: the lock was opened before.\n","kind":"code","seed":"# A text adventure. The world is read from the file world.txt.\n\n\ndef read_world(filename):\n    rooms = {}\n    start = \"\"\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"room\":\n                rooms[parts[1]] = {\"description\": parts[2], \"exits\": {}, \"items\": []}\n                if start == \"\":\n                    start = parts[1]\n            elif parts[0] == \"exit\":\n                rooms[parts[1]][\"exits\"][parts[2]] = parts[3]\n            elif parts[0] == \"item\":\n                rooms[parts[1]][\"items\"].append(parts[2])\n    return rooms, start\n\n\ndef show_room(rooms, name):\n    room = rooms[name]\n    print(f\"== {name} ==\")\n    print(room[\"description\"])\n    if len(room[\"exits\"]) > 0:\n        print(\"Exits: \" + \", \".join(room[\"exits\"]))\n    if len(room[\"items\"]) > 0:\n        print(\"You see: \" + \", \".join(room[\"items\"]))\n\n\ndef move(rooms, location, direction):\n    room = rooms[location]\n    if direction not in room[\"exits\"]:\n        print(f\"You can't go {direction} from here.\")\n        return location\n    return room[\"exits\"][direction]\n\n\ndef take(room, item, inventory):\n    if item in room[\"items\"]:\n        room[\"items\"].remove(item)\n        inventory.append(item)\n        print(f\"You take the {item}.\")\n    else:\n        print(f\"There is no {item} here.\")\n\n\ndef drop(room, item, inventory):\n    if item in inventory:\n        inventory.remove(item)\n        room[\"items\"].append(item)\n        print(f\"You drop the {item}.\")\n    else:\n        print(f\"You don't have the {item}.\")\n\n\ndef main():\n    rooms, start = read_world(\"world.txt\")\n    location = start\n    inventory = []\n    show_room(rooms, location)\n    while True:\n        command = input(\"> \")\n        words = command.split()\n        if command == \"quit\":\n            print(\"Goodbye!\")\n            break\n        if command == \"look\":\n            show_room(rooms, location)\n        elif command == \"inventory\":\n            if len(inventory) == 0:\n                print(\"You carry nothing.\")\n            else:\n                print(\"You carry: \" + \", \".join(inventory))\n        elif len(words) == 2 and words[0] == \"go\":\n            new_location = move(rooms, location, words[1])\n            if new_location != location:\n                location = new_location\n                show_room(rooms, location)\n        elif len(words) == 2 and words[0] == \"take\":\n            take(rooms[location], words[1], inventory)\n        elif len(words) == 2 and words[0] == \"drop\":\n            drop(rooms[location], words[1], inventory)\n        else:\n            print(\"I don't understand that.\")\n\n\nif __name__ == \"__main__\":\n    main()\n","solution":"# A text adventure. The world is read from the file world.txt.\n\n\ndef read_world(filename):\n    rooms = {}\n    start = \"\"\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"room\":\n                rooms[parts[1]] = {\"description\": parts[2], \"exits\": {}, \"items\": [], \"locks\": {}}\n                if start == \"\":\n                    start = parts[1]\n            elif parts[0] == \"exit\":\n                rooms[parts[1]][\"exits\"][parts[2]] = parts[3]\n            elif parts[0] == \"item\":\n                rooms[parts[1]][\"items\"].append(parts[2])\n            elif parts[0] == \"lock\":\n                rooms[parts[1]][\"locks\"][parts[2]] = parts[3]\n    return rooms, start\n\n\ndef show_room(rooms, name):\n    room = rooms[name]\n    print(f\"== {name} ==\")\n    print(room[\"description\"])\n    if len(room[\"exits\"]) > 0:\n        print(\"Exits: \" + \", \".join(room[\"exits\"]))\n    if len(room[\"items\"]) > 0:\n        print(\"You see: \" + \", \".join(room[\"items\"]))\n\n\ndef move(rooms, location, direction, inventory):\n    room = rooms[location]\n    if direction not in room[\"exits\"]:\n        print(f\"You can't go {direction} from here.\")\n        return location\n    if direction in room[\"locks\"]:\n        item = room[\"locks\"][direction]\n        if item not in inventory:\n            print(f\"The way {direction} is locked.\")\n            return location\n        print(f\"You unlock the way {direction} with the {item}.\")\n        del room[\"locks\"][direction]\n    return room[\"exits\"][direction]\n\n\ndef take(room, item, inventory):\n    if item in room[\"items\"]:\n        room[\"items\"].remove(item)\n        inventory.append(item)\n        print(f\"You take the {item}.\")\n    else:\n        print(f\"There is no {item} here.\")\n\n\ndef drop(room, item, inventory):\n    if item in inventory:\n        inventory.remove(item)\n        room[\"items\"].append(item)\n        print(f\"You drop the {item}.\")\n    else:\n        print(f\"You don't have the {item}.\")\n\n\ndef main():\n    rooms, start = read_world(\"world.txt\")\n    location = start\n    inventory = []\n    show_room(rooms, location)\n    while True:\n        command = input(\"> \")\n        words = command.split()\n        if command == \"quit\":\n            print(\"Goodbye!\")\n            break\n        if command == \"look\":\n            show_room(rooms, location)\n        elif command == \"inventory\":\n            if len(inventory) == 0:\n                print(\"You carry nothing.\")\n            else:\n                print(\"You carry: \" + \", \".join(inventory))\n        elif len(words) == 2 and words[0] == \"go\":\n            new_location = move(rooms, location, words[1], inventory)\n            if new_location != location:\n                location = new_location\n                show_room(rooms, location)\n        elif len(words) == 2 and words[0] == \"take\":\n            take(rooms[location], words[1], inventory)\n        elif len(words) == 2 and words[0] == \"drop\":\n            drop(rooms[location], words[1], inventory)\n        else:\n            print(\"I don't understand that.\")\n\n\nif __name__ == \"__main__\":\n    main()\n","hints":["A room is now created with `\"locks\": {}` too, and a lock line does `rooms[parts[1]][\"locks\"][parts[2]] = parts[3]`.","Whoever handles `go` now needs the inventory as well: give your move function one more parameter. After finding that the exit exists, check `if direction in room[\"locks\"]:`, and look up the item that opens it.","`del room[\"locks\"][direction]` removes the lock for good, because the room's dictionary is the same one that `rooms` keeps."],"tests":[{"name":"Find the key and open the door","stdin":"go up\ngo up\ngo down\ngo south\ntake key\ngo north\ngo up\ngo up\ngo down\ngo up\nquit\n","files":{"world.txt":"room;Hallway;A long hallway. The grandfather clock here has stopped.\nroom;Attic;Dusty boxes stand under a round window.\nroom;Clock Tower;Huge wheels fill the tower. One gear is missing.\nroom;Workshop;Half-built clocks tick on every shelf.\nexit;Hallway;up;Attic\nexit;Hallway;south;Workshop\nexit;Attic;down;Hallway\nexit;Attic;up;Clock Tower\nexit;Clock Tower;down;Attic\nexit;Workshop;north;Hallway\nitem;Workshop;key\nlock;Attic;up;key\n"},"expect":"== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: up, south\n> go up\n== Attic ==\nDusty boxes stand under a round window.\nExits: down, up\n> go up\nThe way up is locked.\n> go down\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: up, south\n> go south\n== Workshop ==\nHalf-built clocks tick on every shelf.\nExits: north\nYou see: key\n> take key\nYou take the key.\n> go north\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: up, south\n> go up\n== Attic ==\nDusty boxes stand under a round window.\nExits: down, up\n> go up\nYou unlock the way up with the key.\n== Clock Tower ==\nHuge wheels fill the tower. One gear is missing.\nExits: down\n> go down\n== Attic ==\nDusty boxes stand under a round window.\nExits: down, up\n> go up\n== Clock Tower ==\nHuge wheels fill the tower. One gear is missing.\nExits: down\n> quit\nGoodbye!","hidden":false},{"name":"Two locks and the wrong item","stdin":"go down\ntake rope\ngo down\ngo down\ndrop rope\ngo up\ngo west\ntake lantern\ngo down\ninventory\nquit\n","files":{"world.txt":"room;Deck;Wet planks creak under your feet.\nroom;Cabin;A hammock swings from the ceiling.\nroom;Hold;Barrels are stacked in the dark.\nexit;Deck;down;Cabin\nexit;Cabin;up;Deck\nexit;Cabin;down;Hold\nexit;Hold;up;Cabin\nitem;Deck;rope\nitem;Cabin;lantern\nlock;Deck;down;rope\nlock;Cabin;down;lantern\n"},"expect":"== Deck ==\nWet planks creak under your feet.\nExits: down\nYou see: rope\n> go down\nThe way down is locked.\n> take rope\nYou take the rope.\n> go down\nYou unlock the way down with the rope.\n== Cabin ==\nA hammock swings from the ceiling.\nExits: up, down\nYou see: lantern\n> go down\nThe way down is locked.\n> drop rope\nYou drop the rope.\n> go up\n== Deck ==\nWet planks creak under your feet.\nExits: down\n> go west\nYou can't go west from here.\n> take lantern\nThere is no lantern here.\n> go down\n== Cabin ==\nA hammock swings from the ceiling.\nExits: up, down\nYou see: lantern, rope\n> inventory\nYou carry nothing.\n> quit\nGoodbye!","hidden":true},{"name":"Dropping the key after unlocking","stdin":"take key\ngo in\ndrop key\ngo out\ngo in\nquit\n","files":{"world.txt":"room;Gate;An iron gate in a stone wall.\nroom;Yard;Weeds grow between the cobbles.\nexit;Gate;in;Yard\nexit;Yard;out;Gate\nitem;Gate;key\nlock;Gate;in;key\n"},"expect":"== Gate ==\nAn iron gate in a stone wall.\nExits: in\nYou see: key\n> take key\nYou take the key.\n> go in\nYou unlock the way in with the key.\n== Yard ==\nWeeds grow between the cobbles.\nExits: out\n> drop key\nYou drop the key.\n> go out\n== Gate ==\nAn iron gate in a stone wall.\nExits: in\n> go in\n== Yard ==\nWeeds grow between the cobbles.\nExits: out\nYou see: key\n> quit\nGoodbye!","hidden":true},{"name":"Locks as a dictionary","stdin":"","call":"rooms, start = read_world(\"world.txt\")\nprint(rooms[\"Cellar\"][\"locks\"])\nprint(rooms[\"Garden\"][\"locks\"])\n","files":{"world.txt":"room;Cellar;Shelves of jam jars line the walls.\nroom;Garden;Bees buzz between the roses.\nexit;Cellar;up;Garden\nexit;Garden;down;Cellar\nlock;Cellar;up;ladder\n"},"expect":"{'up': 'ladder'}\n{}","hidden":true}],"require":[],"forbid":[]},{"id":"goal","title":"The goal","task":"Last, the game gets a goal. A line such as `goal;Clock Tower;gear;You fit the gear into place, and the great clock starts ticking again.` means: the player wins by being in the Clock Tower with the gear in their inventory, and then the game prints that message.\n\n1. For a goal line, `read_world` gives that room's dictionary the key `\"goal\"`, with a **tuple** of the item and the message as its value: `(\"gear\", \"You fit the gear ...\")`. Rooms without a goal don't get the key at all.\n2. The game counts the player's **moves**: each `go` that takes the player to another room is one move. A way that is closed, or locked, is no move.\n3. After every command, the game checks whether the player has won. If so, it prints the goal's message and then `You won in 5 moves.` (with the number of moves; for exactly one move, `You won in 1 move.`), and the game ends. It doesn't matter how the player got there: walking in with the item, or picking it up in that room.\n\nA world without a goal line is played as before, until `quit`.\n\nWith the world and the commands at the top of this page, the program prints:\n\n```\n== Workshop ==\nHalf-built clocks tick on every shelf.\nExits: north\nYou see: key\n> take key\nYou take the key.\n> go north\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: south, east, up\n> go east\n== Kitchen ==\nA kettle sits on a cold stove.\nExits: west\nYou see: gear\n> take gear\nYou take the gear.\n> go west\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: south, east, up\n> go up\n== Attic ==\nDusty boxes stand under a round window.\nExits: down, up\nYou see: oil\n> go up\nYou unlock the way up with the key.\n== Clock Tower ==\nHuge wheels fill the tower. One gear is missing.\nExits: down\nYou fit the gear into place, and the great clock starts ticking again.\nYou won in 5 moves.\n```\n","kind":"code","seed":"# A text adventure. The world is read from the file world.txt.\n\n\ndef read_world(filename):\n    rooms = {}\n    start = \"\"\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"room\":\n                rooms[parts[1]] = {\"description\": parts[2], \"exits\": {}, \"items\": [], \"locks\": {}}\n                if start == \"\":\n                    start = parts[1]\n            elif parts[0] == \"exit\":\n                rooms[parts[1]][\"exits\"][parts[2]] = parts[3]\n            elif parts[0] == \"item\":\n                rooms[parts[1]][\"items\"].append(parts[2])\n            elif parts[0] == \"lock\":\n                rooms[parts[1]][\"locks\"][parts[2]] = parts[3]\n    return rooms, start\n\n\ndef show_room(rooms, name):\n    room = rooms[name]\n    print(f\"== {name} ==\")\n    print(room[\"description\"])\n    if len(room[\"exits\"]) > 0:\n        print(\"Exits: \" + \", \".join(room[\"exits\"]))\n    if len(room[\"items\"]) > 0:\n        print(\"You see: \" + \", \".join(room[\"items\"]))\n\n\ndef move(rooms, location, direction, inventory):\n    room = rooms[location]\n    if direction not in room[\"exits\"]:\n        print(f\"You can't go {direction} from here.\")\n        return location\n    if direction in room[\"locks\"]:\n        item = room[\"locks\"][direction]\n        if item not in inventory:\n            print(f\"The way {direction} is locked.\")\n            return location\n        print(f\"You unlock the way {direction} with the {item}.\")\n        del room[\"locks\"][direction]\n    return room[\"exits\"][direction]\n\n\ndef take(room, item, inventory):\n    if item in room[\"items\"]:\n        room[\"items\"].remove(item)\n        inventory.append(item)\n        print(f\"You take the {item}.\")\n    else:\n        print(f\"There is no {item} here.\")\n\n\ndef drop(room, item, inventory):\n    if item in inventory:\n        inventory.remove(item)\n        room[\"items\"].append(item)\n        print(f\"You drop the {item}.\")\n    else:\n        print(f\"You don't have the {item}.\")\n\n\ndef main():\n    rooms, start = read_world(\"world.txt\")\n    location = start\n    inventory = []\n    show_room(rooms, location)\n    while True:\n        command = input(\"> \")\n        words = command.split()\n        if command == \"quit\":\n            print(\"Goodbye!\")\n            break\n        if command == \"look\":\n            show_room(rooms, location)\n        elif command == \"inventory\":\n            if len(inventory) == 0:\n                print(\"You carry nothing.\")\n            else:\n                print(\"You carry: \" + \", \".join(inventory))\n        elif len(words) == 2 and words[0] == \"go\":\n            new_location = move(rooms, location, words[1], inventory)\n            if new_location != location:\n                location = new_location\n                show_room(rooms, location)\n        elif len(words) == 2 and words[0] == \"take\":\n            take(rooms[location], words[1], inventory)\n        elif len(words) == 2 and words[0] == \"drop\":\n            drop(rooms[location], words[1], inventory)\n        else:\n            print(\"I don't understand that.\")\n\n\nif __name__ == \"__main__\":\n    main()\n","solution":"# A text adventure. The world is read from the file world.txt.\n\n\ndef read_world(filename):\n    rooms = {}\n    start = \"\"\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"room\":\n                rooms[parts[1]] = {\"description\": parts[2], \"exits\": {}, \"items\": [], \"locks\": {}}\n                if start == \"\":\n                    start = parts[1]\n            elif parts[0] == \"exit\":\n                rooms[parts[1]][\"exits\"][parts[2]] = parts[3]\n            elif parts[0] == \"item\":\n                rooms[parts[1]][\"items\"].append(parts[2])\n            elif parts[0] == \"lock\":\n                rooms[parts[1]][\"locks\"][parts[2]] = parts[3]\n            elif parts[0] == \"goal\":\n                rooms[parts[1]][\"goal\"] = (parts[2], parts[3])\n    return rooms, start\n\n\ndef show_room(rooms, name):\n    room = rooms[name]\n    print(f\"== {name} ==\")\n    print(room[\"description\"])\n    if len(room[\"exits\"]) > 0:\n        print(\"Exits: \" + \", \".join(room[\"exits\"]))\n    if len(room[\"items\"]) > 0:\n        print(\"You see: \" + \", \".join(room[\"items\"]))\n\n\ndef move(rooms, location, direction, inventory):\n    room = rooms[location]\n    if direction not in room[\"exits\"]:\n        print(f\"You can't go {direction} from here.\")\n        return location\n    if direction in room[\"locks\"]:\n        item = room[\"locks\"][direction]\n        if item not in inventory:\n            print(f\"The way {direction} is locked.\")\n            return location\n        print(f\"You unlock the way {direction} with the {item}.\")\n        del room[\"locks\"][direction]\n    return room[\"exits\"][direction]\n\n\ndef take(room, item, inventory):\n    if item in room[\"items\"]:\n        room[\"items\"].remove(item)\n        inventory.append(item)\n        print(f\"You take the {item}.\")\n    else:\n        print(f\"There is no {item} here.\")\n\n\ndef drop(room, item, inventory):\n    if item in inventory:\n        inventory.remove(item)\n        room[\"items\"].append(item)\n        print(f\"You drop the {item}.\")\n    else:\n        print(f\"You don't have the {item}.\")\n\n\ndef main():\n    rooms, start = read_world(\"world.txt\")\n    location = start\n    inventory = []\n    moves = 0\n    show_room(rooms, location)\n    while True:\n        command = input(\"> \")\n        words = command.split()\n        if command == \"quit\":\n            print(\"Goodbye!\")\n            break\n        if command == \"look\":\n            show_room(rooms, location)\n        elif command == \"inventory\":\n            if len(inventory) == 0:\n                print(\"You carry nothing.\")\n            else:\n                print(\"You carry: \" + \", \".join(inventory))\n        elif len(words) == 2 and words[0] == \"go\":\n            new_location = move(rooms, location, words[1], inventory)\n            if new_location != location:\n                location = new_location\n                moves += 1\n                show_room(rooms, location)\n        elif len(words) == 2 and words[0] == \"take\":\n            take(rooms[location], words[1], inventory)\n        elif len(words) == 2 and words[0] == \"drop\":\n            drop(rooms[location], words[1], inventory)\n        else:\n            print(\"I don't understand that.\")\n        room = rooms[location]\n        if \"goal\" in room and room[\"goal\"][0] in inventory:\n            print(room[\"goal\"][1])\n            if moves == 1:\n                print(\"You won in 1 move.\")\n            else:\n                print(f\"You won in {moves} moves.\")\n            break\n\n\nif __name__ == \"__main__\":\n    main()\n","hints":["A goal line does `rooms[parts[1]][\"goal\"] = (parts[2], parts[3])`. Then `room[\"goal\"][0]` is the item and `room[\"goal\"][1]` the message.","Keep `moves = 0` before the loop, and add one where the player arrives in a new room, next to the call that shows it.","At the end of each round of the loop, `room = rooms[location]` and `if \"goal\" in room and room[\"goal\"][0] in inventory:` tell you the player has won. Print the two lines there and `break`."],"tests":[{"name":"Start the great clock","stdin":"take key\ngo north\ngo east\ntake gear\ngo west\ngo up\ngo up\n","files":{"world.txt":"room;Workshop;Half-built clocks tick on every shelf.\nroom;Hallway;A long hallway. The grandfather clock here has stopped.\nroom;Kitchen;A kettle sits on a cold stove.\nroom;Attic;Dusty boxes stand under a round window.\nroom;Clock Tower;Huge wheels fill the tower. One gear is missing.\nexit;Workshop;north;Hallway\nexit;Hallway;south;Workshop\nexit;Hallway;east;Kitchen\nexit;Hallway;up;Attic\nexit;Kitchen;west;Hallway\nexit;Attic;down;Hallway\nexit;Attic;up;Clock Tower\nexit;Clock Tower;down;Attic\nitem;Workshop;key\nitem;Kitchen;gear\nitem;Attic;oil\nlock;Attic;up;key\ngoal;Clock Tower;gear;You fit the gear into place, and the great clock starts ticking again.\n"},"expect":"== Workshop ==\nHalf-built clocks tick on every shelf.\nExits: north\nYou see: key\n> take key\nYou take the key.\n> go north\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: south, east, up\n> go east\n== Kitchen ==\nA kettle sits on a cold stove.\nExits: west\nYou see: gear\n> take gear\nYou take the gear.\n> go west\n== Hallway ==\nA long hallway. The grandfather clock here has stopped.\nExits: south, east, up\n> go up\n== Attic ==\nDusty boxes stand under a round window.\nExits: down, up\nYou see: oil\n> go up\nYou unlock the way up with the key.\n== Clock Tower ==\nHuge wheels fill the tower. One gear is missing.\nExits: down\nYou fit the gear into place, and the great clock starts ticking again.\nYou won in 5 moves.","hidden":false},{"name":"Arrive first, then fetch the item","stdin":"go up\ngo down\ngo down\ngo down\ntake compass\ngo up\ngo up\n","files":{"world.txt":"room;Deck;Wet planks creak under your feet.\nroom;Cabin;A hammock swings from the ceiling.\nroom;Hold;Barrels are stacked in the dark.\nexit;Deck;down;Cabin\nexit;Cabin;up;Deck\nexit;Cabin;down;Hold\nexit;Hold;up;Cabin\nitem;Hold;compass\ngoal;Deck;compass;With the compass in hand, you set a course for home.\n"},"expect":"== Deck ==\nWet planks creak under your feet.\nExits: down\n> go up\nYou can't go up from here.\n> go down\n== Cabin ==\nA hammock swings from the ceiling.\nExits: up, down\n> go down\n== Hold ==\nBarrels are stacked in the dark.\nExits: up\nYou see: compass\n> go down\nYou can't go down from here.\n> take compass\nYou take the compass.\n> go up\n== Cabin ==\nA hammock swings from the ceiling.\nExits: up, down\n> go up\n== Deck ==\nWet planks creak under your feet.\nExits: down\nWith the compass in hand, you set a course for home.\nYou won in 4 moves.","hidden":true},{"name":"Win in one move","stdin":"take flag\ngo in\n","files":{"world.txt":"room;Gate;An iron gate in a stone wall.\nroom;Yard;Weeds grow between the cobbles.\nexit;Gate;in;Yard\nitem;Gate;flag\ngoal;Yard;flag;You plant the flag in the middle of the yard.\n"},"expect":"== Gate ==\nAn iron gate in a stone wall.\nExits: in\nYou see: flag\n> take flag\nYou take the flag.\n> go in\n== Yard ==\nWeeds grow between the cobbles.\nYou plant the flag in the middle of the yard.\nYou won in 1 move.","hidden":true},{"name":"Win by taking the item","stdin":"look\ntake honey\n","files":{"world.txt":"room;Garden;Bees buzz between the roses.\nitem;Garden;honey\ngoal;Garden;honey;You found the honey!\n"},"expect":"== Garden ==\nBees buzz between the roses.\nYou see: honey\n> look\n== Garden ==\nBees buzz between the roses.\nYou see: honey\n> take honey\nYou take the honey.\nYou found the honey!\nYou won in 0 moves.","hidden":true},{"name":"No goal in this world","stdin":"take jar\ngo up\ndrop jar\ninventory\nquit\n","files":{"world.txt":"room;Cellar;Shelves of jam jars line the walls.\nroom;Garden;Bees buzz between the roses.\nexit;Cellar;up;Garden\nexit;Garden;down;Cellar\nitem;Cellar;jar\n"},"expect":"== Cellar ==\nShelves of jam jars line the walls.\nExits: up\nYou see: jar\n> take jar\nYou take the jar.\n> go up\n== Garden ==\nBees buzz between the roses.\nExits: down\n> drop jar\nYou drop the jar.\n> inventory\nYou carry nothing.\n> quit\nGoodbye!","hidden":true},{"name":"A goal as a tuple","stdin":"","call":"rooms, start = read_world(\"world.txt\")\nprint(rooms[\"Pond\"][\"goal\"])\nprint(\"goal\" in rooms[\"Garden\"])\n","files":{"world.txt":"room;Garden;Bees buzz between the roses.\nroom;Pond;Frogs sit on the lily pads.\nexit;Garden;east;Pond\ngoal;Pond;net;You catch a frog. Well, almost.\n"},"expect":"('net', 'You catch a frog. Well, almost.')\nFalse","hidden":true}],"require":[],"forbid":[]}]}