<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P020-text-adventure/ | updated: 2026-10-03 -->

# P020 Text adventure

Explore an old house of eight rooms on a grid - go, take, drop and use things, light a lamp for the dark cellar, unlock a door with the key found there - and carry the crown out to the garden to win; a map shows the rooms you know, and a path command finds a route through them by backtracking.

EML-P project `projects/text-adventure` in the EML language repo: 4 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/text-adventure` runs it and `eml project verify projects/text-adventure` replays every session under CPython (two hash seeds) and in the interpreter; the site build replays every session in the interpreter again and publishes a session only if its screen matches.

Built on verified corpus cases: maze-solver-backtracking (https://efficientnewlanguage.org/cases/116-maze-solver-backtracking/), grid-coordinate-map (https://efficientnewlanguage.org/cases/172-grid-coordinate-map/).

## Sessions

### bad-input - interpreter: byte-equal to the golden

Input:

```text
dance

go
go up
n
north east
take
take sword
drop lamp
use key
i
path cellar
path attic
path hall
path
s
n
w
take lamp
drop lamp
s
take key
use lamp
n
take lamp
use lamp
use lamp
s
drop lamp
take key
use key
n
map
quit
```

Screen:

```text
An old house. Somewhere inside is a golden crown: carry it out to the garden to win.
Type help for the commands.

-- Hall --
A wide hall with a stone floor. Doors lead off in every direction.
Exits: north (locked), east, south and west.

> dance
I do not understand that. Type help for the commands.

> 
Type a command, or help.

> go
Go where? North, east, south or west.

> go up
Go where? North, east, south or west.

> n
The door is locked.

> north east
I do not understand that. Type help for the commands.

> take
What do you want to take?

> take sword
There is no sword here.

> drop lamp
You do not have the lamp.

> use key
You do not have the key.

> i
You are carrying nothing.

> path cellar
You have not been to the Cellar yet.

> path attic
There is no room called attic.

> path hall
You are already in the Hall.

> path
A path to which room?

> s

-- Garden --
An overgrown garden behind the house, with a gate to the road.
Exits: north.

> n

-- Hall --
A wide hall with a stone floor. Doors lead off in every direction.
Exits: north (locked), east, south and west.

> w

-- Kitchen --
A cold kitchen. Pots hang from hooks above the stove.
You see an oil lamp.
Exits: north, east and south.

> take lamp
You take an oil lamp.

> drop lamp
You drop an oil lamp.

> s

-- Cellar --
It is pitch dark. You cannot see a thing.
Exits: north.

> take key
It is too dark to see anything here.

> use lamp
You do not have the lamp.

> n

-- Kitchen --
A cold kitchen. Pots hang from hooks above the stove.
You see an oil lamp.
Exits: north, east and south.

> take lamp
You take an oil lamp.

> use lamp
You light the lamp.

> use lamp
The lamp is already lit.

> s

-- Cellar --
A low cellar that smells of earth.
You see an iron key.
Exits: north.

> drop lamp
You drop an oil lamp.

> take key
You take an iron key.

> use key
There is nothing here to unlock.

> n

-- Kitchen --
A cold kitchen. Pots hang from hooks above the stove.
Exits: north, east and south.

> map
  ?         ?         ?
  Kitchen*  Hall      ?
  Cellar    Garden
(* is where you are; ? is a room you have not seen.)

> quit
You leave the house without the crown.
Bye.
```

### basic - interpreter: byte-equal to the golden

Input:

```text
look
map
w
take lamp
n
take book
use book
s
s
take key
use lamp
take key
i
path hall
n
e
n
use key
n
take crown
s
e
n
map
s
w
s
```

Screen:

```text
An old house. Somewhere inside is a golden crown: carry it out to the garden to win.
Type help for the commands.

-- Hall --
A wide hall with a stone floor. Doors lead off in every direction.
Exits: north (locked), east, south and west.

> look

-- Hall --
A wide hall with a stone floor. Doors lead off in every direction.
Exits: north (locked), east, south and west.

> map
  ?         ?         ?
  ?         Hall*     ?
  ?         ?
(* is where you are; ? is a room you have not seen.)

> w

-- Kitchen --
A cold kitchen. Pots hang from hooks above the stove.
You see an oil lamp.
Exits: north, east and south.

> take lamp
You take an oil lamp.

> n

-- Library --
Shelves of dusty books reach up to the ceiling.
You see an old book.
Exits: south.

> take book
You take an old book.

> use book
You open the book. A note in the margin says: the crown is kept in the study, and the study key is kept below the kitchen.

> s

-- Kitchen --
A cold kitchen. Pots hang from hooks above the stove.
Exits: north, east and south.

> s

-- Cellar --
It is pitch dark. You cannot see a thing.
Exits: north.

> take key
It is too dark to see anything here.

> use lamp
You light the lamp.

-- Cellar --
A low cellar that smells of earth.
You see an iron key.
Exits: north.

> take key
You take an iron key.

> i
You are carrying an oil lamp, an iron key and an old book.

> path hall
From the Cellar to the Hall: north, then east.

> n

-- Kitchen --
A cold kitchen. Pots hang from hooks above the stove.
Exits: north, east and south.

> e

-- Hall --
A wide hall with a stone floor. Doors lead off in every direction.
Exits: north (locked), east, south and west.

> n
The door is locked.

> use key
You unlock the study door with the iron key.

> n

-- Study --
A quiet study with a heavy oak desk.
You see a golden crown.
Exits: south.

> take crown
You take a golden crown.

> s

-- Hall --
A wide hall with a stone floor. Doors lead off in every direction.
Exits: north, east, south and west.

> e

-- Gallery --
Old portraits watch you from the walls.
Exits: north and west.

> n

-- Tower --
A narrow room at the top of the tower. From its window you can see the whole house.
Exits: south.

> map
  Library   Study     Tower*
  Kitchen   Hall      Gallery
  Cellar    Garden
(* is where you are; ? is a room you have not seen.)

> s

-- Gallery --
Old portraits watch you from the walls.
Exits: north and west.

> w

-- Hall --
A wide hall with a stone floor. Doors lead off in every direction.
Exits: north, east, south and west.

> s

-- Garden --
An overgrown garden behind the house, with a gate to the road.
Exits: north.

You carry the crown out into the garden. You have won, in 13 moves!
Bye.
```

## Modules

### main.eml

```eml
# P020 text adventure: explore an old house, find the crown and carry it out
# to the garden. Commands are typed as words - go north (or just n), take
# lamp, use key - and the game keeps what you carry, where each thing lies,
# which doors are locked and which rooms you know.
import world
import route
import text

def can_see(here, where, lamp_lit):
    # A dark room can be seen only with the lit lamp in it.
    if not world.rooms[here][4]:
        return True
    world.thing_named("lamp") => lamp
    return lamp_lit and (where[lamp] == 0 - 1 or where[lamp] == here)

def describe(here, where, locked, lamp_lit):
    "" ^0
    ("-- " + world.rooms[here][0] + " --") ^0
    if can_see(here, where, lamp_lit):
        world.rooms[here][3] ^0
        [] => seen
        for i in [0:len(world.things) - 1]:
            if where[i] == here:
                seen + [world.things[i][1]] => seen
        if len(seen) > 0:
            ("You see " + text.listed(seen) + ".") ^0
    else:
        "It is pitch dark. You cannot see a thing." ^0
    [] => ways
    for d in world.directions:
        world.door_from(here, d) => door
        if door != None:
            if locked[door[0]]:
                ways + [d + " (locked)"] => ways
            else:
                ways + [d] => ways
    ("Exits: " + text.listed(ways) + ".") ^0

def direction(word):
    # The full direction for north/n, east/e, south/s, west/w, or "".
    for d in world.directions:
        if word == d or word == d[0:1]:
            return d
    return ""

def plural(n, word):
    if n == 1:
        return "1 " + word
    return str(n) + " " + word + "s"

world.start_room => here
[] => where
for t in world.things:
    where + [t[2]] => where
[] => locked
for d in world.doors:
    locked + [False] => locked
True => locked[world.study_door]
[] => visited
[] => known
for r in world.rooms:
    visited + [False] => visited
    known + [False] => known
True => visited[here]
True => known[here]
False => lamp_lit
0 => moves
False => won
True => playing

"An old house. Somewhere inside is a golden crown: carry it out to the garden to win." ^0
"Type help for the commands." ^0
describe(here, where, locked, lamp_lit)
while playing:
    "" ^0
    text.words(text.lower(input("> "))) => ws
    "" => verb
    "" => rest
    if len(ws) > 0:
        ws[0] => verb
        text.joined(ws[1:len(ws)]) => rest
    if direction(verb) != "" and rest == "":
        "go" => verb
        direction(ws[0]) => rest
    if verb == "":
        "Type a command, or help." ^0
    elif verb == "go":
        direction(rest) => d
        if d == "":
            "Go where? North, east, south or west." ^0
        elif world.door_from(here, d) == None:
            ("You cannot go " + d + " from here.") ^0
        elif locked[world.door_from(here, d)[0]]:
            "The door is locked." ^0
        else:
            world.door_from(here, d)[1] => here
            moves + 1 => moves
            True => visited[here]
            True => known[here]
            if world.rooms[here][0] == "Tower":
                for i in [0:len(known) - 1]:
                    True => known[i]
            describe(here, where, locked, lamp_lit)
            if here == world.goal_room and where[world.thing_named("crown")] == 0 - 1:
                ""^0
                ("You carry the crown out into the garden. You have won, in " + plural(moves, "move") + "!") ^0
                True => won
                False => playing
    elif verb == "look" or verb == "l":
        describe(here, where, locked, lamp_lit)
    elif verb == "take" or verb == "get" or verb == "drop" or verb == "use":
        0 - 1 => t
        if rest != "":
            text.words(rest) => names
            world.thing_named(names[len(names) - 1]) => t
        if rest == "":
            ("What do you want to " + verb + "?") ^0
        elif verb == "take" or verb == "get":
            if not can_see(here, where, lamp_lit):
                "It is too dark to see anything here." ^0
            elif t == 0 - 1 or where[t] != here:
                ("There is no " + rest + " here.") ^0
            else:
                0 - 1 => where[t]
                ("You take " + world.things[t][1] + ".") ^0
        elif t == 0 - 1 or where[t] != 0 - 1:
            ("You do not have the " + rest + ".") ^0
        elif verb == "drop":
            here => where[t]
            ("You drop " + world.things[t][1] + ".") ^0
        elif world.things[t][0] == "lamp":
            if lamp_lit:
                "The lamp is already lit." ^0
            else:
                True => lamp_lit
                "You light the lamp." ^0
                if world.rooms[here][4]:
                    describe(here, where, locked, lamp_lit)
        elif world.things[t][0] == "key":
            if world.doors[world.study_door][0] != here:
                "There is nothing here to unlock." ^0
            elif locked[world.study_door]:
                False => locked[world.study_door]
                "You unlock the study door with the iron key." ^0
            else:
                "The study door is already unlocked." ^0
        elif world.things[t][0] == "book":
            "You open the book. A note in the margin says: the crown is kept in the study, and the study key is kept below the kitchen." ^0
        else:
            "It is too precious to wear. Carry it out to the garden." ^0
    elif verb == "inventory" or verb == "i":
        [] => mine
        for i in [0:len(world.things) - 1]:
            if where[i] == 0 - 1:
                mine + [world.things[i][1]] => mine
        if len(mine) == 0:
            "You are carrying nothing." ^0
        else:
            ("You are carrying " + text.listed(mine) + ".") ^0
    elif verb == "map":
        for line in route.map_lines(here, known):
            line ^0
        "(* is where you are; ? is a room you have not seen.)" ^0
    elif verb == "path":
        0 - 1 => goal
        for i in [0:len(world.rooms) - 1]:
            if text.lower(world.rooms[i][0]) == rest:
                i => goal
        if rest == "":
            "A path to which room?" ^0
        elif goal == 0 - 1:
            ("There is no room called " + rest + ".") ^0
        elif goal == here:
            ("You are already in the " + world.rooms[here][0] + ".") ^0
        elif not visited[goal]:
            ("You have not been to the " + world.rooms[goal][0] + " yet.") ^0
        else:
            route.route(here, goal, locked, visited) => steps
            if steps == None:
                "There is no way there through the rooms you have been in." ^0
            else:
                "" => line
                for i in [0:len(steps) - 1]:
                    if i > 0:
                        line + ", then " => line
                    line + steps[i] => line
                ("From the " + world.rooms[here][0] + " to the " + world.rooms[goal][0] + ": " + line + ".") ^0
    elif verb == "help":
        "Commands: look, go north/east/south/west (or n, e, s, w), take, drop and use a thing," ^0
        "inventory (i), map, path to a room you have been in (path kitchen), quit." ^0
    elif verb == "quit":
        "You leave the house without the crown." ^0
        False => playing
    else:
        "I do not understand that. Type help for the commands." ^0
"Bye." ^0
```

Python projection of main.eml:

```python
import world
import route
import text

def can_see(here, where, lamp_lit):
    if not world.rooms[here][4]:
        return True
    lamp = world.thing_named("lamp")
    return lamp_lit and (where[lamp] == 0 - 1 or where[lamp] == here)

def describe(here, where, locked, lamp_lit):
    print("")
    print("-- " + world.rooms[here][0] + " --")
    if can_see(here, where, lamp_lit):
        print(world.rooms[here][3])
        seen = []
        for i in range(0, len(world.things)):
            if where[i] == here:
                seen = seen + [world.things[i][1]]
        if len(seen) > 0:
            print("You see " + text.listed(seen) + ".")
    else:
        print("It is pitch dark. You cannot see a thing.")
    ways = []
    for d in world.directions:
        door = world.door_from(here, d)
        if door != None:
            if locked[door[0]]:
                ways = ways + [d + " (locked)"]
            else:
                ways = ways + [d]
    print("Exits: " + text.listed(ways) + ".")

def direction(word):
    for d in world.directions:
        if word == d or word == d[0:1]:
            return d
    return ""

def plural(n, word):
    if n == 1:
        return "1 " + word
    return str(n) + " " + word + "s"

here = world.start_room
where = []
for t in world.things:
    where = where + [t[2]]
locked = []
for d in world.doors:
    locked = locked + [False]
locked[world.study_door] = True
visited = []
known = []
for r in world.rooms:
    visited = visited + [False]
    known = known + [False]
visited[here] = True
known[here] = True
lamp_lit = False
moves = 0
won = False
playing = True
print("An old house. Somewhere inside is a golden crown: carry it out to the garden to win.")
print("Type help for the commands.")
describe(here, where, locked, lamp_lit)
while playing:
    print("")
    ws = text.words(text.lower(input("> ")))
    verb = ""
    rest = ""
    if len(ws) > 0:
        verb = ws[0]
        rest = text.joined(ws[1:len(ws)])
    if direction(verb) != "" and rest == "":
        verb = "go"
        rest = direction(ws[0])
    if verb == "":
        print("Type a command, or help.")
    elif verb == "go":
        d = direction(rest)
        if d == "":
            print("Go where? North, east, south or west.")
        elif world.door_from(here, d) == None:
            print("You cannot go " + d + " from here.")
        elif locked[world.door_from(here, d)[0]]:
            print("The door is locked.")
        else:
            here = world.door_from(here, d)[1]
            moves = moves + 1
            visited[here] = True
            known[here] = True
            if world.rooms[here][0] == "Tower":
                for i in range(0, len(known)):
                    known[i] = True
            describe(here, where, locked, lamp_lit)
            if here == world.goal_room and where[world.thing_named("crown")] == 0 - 1:
                print("")
                print("You carry the crown out into the garden. You have won, in " + plural(moves, "move") + "!")
                won = True
                playing = False
    elif verb == "look" or verb == "l":
        describe(here, where, locked, lamp_lit)
    elif verb == "take" or verb == "get" or verb == "drop" or verb == "use":
        t = 0 - 1
        if rest != "":
            names = text.words(rest)
            t = world.thing_named(names[len(names) - 1])
        if rest == "":
            print("What do you want to " + verb + "?")
        elif verb == "take" or verb == "get":
            if not can_see(here, where, lamp_lit):
                print("It is too dark to see anything here.")
            elif t == 0 - 1 or where[t] != here:
                print("There is no " + rest + " here.")
            else:
                where[t] = 0 - 1
                print("You take " + world.things[t][1] + ".")
        elif t == 0 - 1 or where[t] != 0 - 1:
            print("You do not have the " + rest + ".")
        elif verb == "drop":
            where[t] = here
            print("You drop " + world.things[t][1] + ".")
        elif world.things[t][0] == "lamp":
            if lamp_lit:
                print("The lamp is already lit.")
            else:
                lamp_lit = True
                print("You light the lamp.")
                if world.rooms[here][4]:
                    describe(here, where, locked, lamp_lit)
        elif world.things[t][0] == "key":
            if world.doors[world.study_door][0] != here:
                print("There is nothing here to unlock.")
            elif locked[world.study_door]:
                locked[world.study_door] = False
                print("You unlock the study door with the iron key.")
            else:
                print("The study door is already unlocked.")
        elif world.things[t][0] == "book":
            print("You open the book. A note in the margin says: the crown is kept in the study, and the study key is kept below the kitchen.")
        else:
            print("It is too precious to wear. Carry it out to the garden.")
    elif verb == "inventory" or verb == "i":
        mine = []
        for i in range(0, len(world.things)):
            if where[i] == 0 - 1:
                mine = mine + [world.things[i][1]]
        if len(mine) == 0:
            print("You are carrying nothing.")
        else:
            print("You are carrying " + text.listed(mine) + ".")
    elif verb == "map":
        for line in route.map_lines(here, known):
            print(line)
        print("(* is where you are; ? is a room you have not seen.)")
    elif verb == "path":
        goal = 0 - 1
        for i in range(0, len(world.rooms)):
            if text.lower(world.rooms[i][0]) == rest:
                goal = i
        if rest == "":
            print("A path to which room?")
        elif goal == 0 - 1:
            print("There is no room called " + rest + ".")
        elif goal == here:
            print("You are already in the " + world.rooms[here][0] + ".")
        elif not visited[goal]:
            print("You have not been to the " + world.rooms[goal][0] + " yet.")
        else:
            steps = route.route(here, goal, locked, visited)
            if steps == None:
                print("There is no way there through the rooms you have been in.")
            else:
                line = ""
                for i in range(0, len(steps)):
                    if i > 0:
                        line = line + ", then "
                    line = line + steps[i]
                print("From the " + world.rooms[here][0] + " to the " + world.rooms[goal][0] + ": " + line + ".")
    elif verb == "help":
        print("Commands: look, go north/east/south/west (or n, e, s, w), take, drop and use a thing,")
        print("inventory (i), map, path to a room you have been in (path kitchen), quit.")
    elif verb == "quit":
        print("You leave the house without the crown.")
        playing = False
    else:
        print("I do not understand that. Type help for the commands.")
print("Bye.")
```

### world.eml

```eml
# P020 text adventure - the house: rooms on a grid, the doors between them,
# and the things in it at the start. A room is [name, column, row,
# description, dark]; a door is [room, direction, room] and works both ways;
# a thing is [name, description, starting room].

[
    ["Library", 0, 0, "Shelves of dusty books reach up to the ceiling.", False],
    ["Study", 1, 0, "A quiet study with a heavy oak desk.", False],
    ["Tower", 2, 0, "A narrow room at the top of the tower. From its window you can see the whole house.", False],
    ["Kitchen", 0, 1, "A cold kitchen. Pots hang from hooks above the stove.", False],
    ["Hall", 1, 1, "A wide hall with a stone floor. Doors lead off in every direction.", False],
    ["Gallery", 2, 1, "Old portraits watch you from the walls.", False],
    ["Cellar", 0, 2, "A low cellar that smells of earth.", True],
    ["Garden", 1, 2, "An overgrown garden behind the house, with a gate to the road.", False],
] => rooms

["north", "east", "south", "west"] => directions

[
    [4, "north", 1],
    [4, "west", 3],
    [4, "east", 5],
    [4, "south", 7],
    [3, "north", 0],
    [3, "south", 6],
    [5, "north", 2],
] => doors

# The door from the hall to the study (door 0) is locked at the start.
0 => study_door

[
    ["lamp", "an oil lamp", 3],
    ["key", "an iron key", 6],
    ["crown", "a golden crown", 1],
    ["book", "an old book", 0],
] => things

4 => start_room
7 => goal_room

def opposite(d):
    if d == "north":
        return "south"
    if d == "south":
        return "north"
    if d == "east":
        return "west"
    return "east"

def door_from(room, d):
    # [door number, the room it leads to] for the door going d from room, or
    # None if there is no door that way.
    for k in [0:len(doors) - 1]:
        if doors[k][0] == room and doors[k][1] == d:
            return [k, doors[k][2]]
        if doors[k][2] == room and opposite(doors[k][1]) == d:
            return [k, doors[k][0]]
    return None

def room_at(column, row):
    # The room at a grid position, or -1 if there is none.
    for i in [0:len(rooms) - 1]:
        if rooms[i][1] == column and rooms[i][2] == row:
            return i
    return 0 - 1

def thing_named(name):
    # The thing called name, or -1.
    for i in [0:len(things) - 1]:
        if things[i][0] == name:
            return i
    return 0 - 1
```

Python projection of world.eml:

```python
rooms = [["Library", 0, 0, "Shelves of dusty books reach up to the ceiling.", False], ["Study", 1, 0, "A quiet study with a heavy oak desk.", False], ["Tower", 2, 0, "A narrow room at the top of the tower. From its window you can see the whole house.", False], ["Kitchen", 0, 1, "A cold kitchen. Pots hang from hooks above the stove.", False], ["Hall", 1, 1, "A wide hall with a stone floor. Doors lead off in every direction.", False], ["Gallery", 2, 1, "Old portraits watch you from the walls.", False], ["Cellar", 0, 2, "A low cellar that smells of earth.", True], ["Garden", 1, 2, "An overgrown garden behind the house, with a gate to the road.", False]]
directions = ["north", "east", "south", "west"]
doors = [[4, "north", 1], [4, "west", 3], [4, "east", 5], [4, "south", 7], [3, "north", 0], [3, "south", 6], [5, "north", 2]]
study_door = 0
things = [["lamp", "an oil lamp", 3], ["key", "an iron key", 6], ["crown", "a golden crown", 1], ["book", "an old book", 0]]
start_room = 4
goal_room = 7

def opposite(d):
    if d == "north":
        return "south"
    if d == "south":
        return "north"
    if d == "east":
        return "west"
    return "east"

def door_from(room, d):
    for k in range(0, len(doors)):
        if doors[k][0] == room and doors[k][1] == d:
            return [k, doors[k][2]]
        if doors[k][2] == room and opposite(doors[k][1]) == d:
            return [k, doors[k][0]]
    return None

def room_at(column, row):
    for i in range(0, len(rooms)):
        if rooms[i][1] == column and rooms[i][2] == row:
            return i
    return 0 - 1

def thing_named(name):
    for i in range(0, len(things)):
        if things[i][0] == name:
            return i
    return 0 - 1
```

### route.eml

```eml
# P020 text adventure - the map of the rooms you know, and a route between
# rooms by backtracking: try each open door in turn (north, east, south,
# west) into a room you have been in and not yet on this route, go deeper,
# and step back when a way leads nowhere. The house has no loops, so the
# route found is the only one.
import world

def search(here, goal, on_route, locked, visited):
    # The directions from here to goal, or None if there is no way.
    if here == goal:
        return []
    True => on_route[here]
    for d in world.directions:
        world.door_from(here, d) => door
        if door != None and not locked[door[0]] and visited[door[1]] and not on_route[door[1]]:
            search(door[1], goal, on_route, locked, visited) => rest
            if rest != None:
                return [d] + rest
    False => on_route[here]
    return None

def route(here, goal, locked, visited):
    [] => on_route
    for r in world.rooms:
        on_route + [False] => on_route
    return search(here, goal, on_route, locked, visited)

def map_lines(here, known):
    # The house as a grid: a room you know by name (* where you are), one you
    # do not as ?, and nothing where there is no room.
    [] => lines
    for row in [0:2]:
        "  " => line
        for column in [0:2]:
            world.room_at(column, row) => r
            "" => cell
            if r != 0 - 1 and known[r]:
                world.rooms[r][0] => cell
                if r == here:
                    cell + "*" => cell
            elif r != 0 - 1:
                "?" => cell
            line + ("%-10s" % cell) => line
        len(line) => j
        while j > 0 and line[j - 1] == " ":
            j - 1 => j
        lines + [line[0:j]] => lines
    return lines
```

Python projection of route.eml:

```python
import world

def search(here, goal, on_route, locked, visited):
    if here == goal:
        return []
    on_route[here] = True
    for d in world.directions:
        door = world.door_from(here, d)
        if door != None and not locked[door[0]] and visited[door[1]] and not on_route[door[1]]:
            rest = search(door[1], goal, on_route, locked, visited)
            if rest != None:
                return [d] + rest
    on_route[here] = False
    return None

def route(here, goal, locked, visited):
    on_route = []
    for r in world.rooms:
        on_route = on_route + [False]
    return search(here, goal, on_route, locked, visited)

def map_lines(here, known):
    lines = []
    for row in range(0, 3):
        line = "  "
        for column in range(0, 3):
            r = world.room_at(column, row)
            cell = ""
            if r != 0 - 1 and known[r]:
                cell = world.rooms[r][0]
                if r == here:
                    cell = cell + "*"
            elif r != 0 - 1:
                cell = "?"
            line = line + "%-10s" % cell
        j = len(line)
        while j > 0 and line[j - 1] == " ":
            j = j - 1
        lines = lines + [line[0:j]]
    return lines
```

### text.eml

```eml
# P020 text adventure - reading commands. The interpreter that checks every
# session does not run string methods yet, so the text handling is written
# out here.

def lower(s):
    # s with A-Z turned into a-z; everything else as it was.
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" => upper_letters
    "abcdefghijklmnopqrstuvwxyz" => lower_letters
    "" => out
    for c in s:
        c => d
        for i in [0:25]:
            if upper_letters[i] == c:
                lower_letters[i] => d
        out + d => out
    return out

def words(s):
    # The words of s, split at spaces.
    [] => out
    "" => word
    for c in s + " ":
        if c == " ":
            if word != "":
                out + [word] => out
            "" => word
        else:
            word + c => word
    return out

def joined(ws):
    # Words put back together with single spaces.
    "" => out
    for w in ws:
        if out != "":
            out + " " => out
        out + w => out
    return out

def listed(items):
    # "a lamp", "a lamp and a key", "a lamp, a key and a book".
    if len(items) == 1:
        return items[0]
    "" => out
    for i in [0:len(items) - 2]:
        if i > 0:
            out + ", " => out
        out + items[i] => out
    return out + " and " + items[len(items) - 1]
```

Python projection of text.eml:

```python
def lower(s):
    upper_letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    lower_letters = "abcdefghijklmnopqrstuvwxyz"
    out = ""
    for c in s:
        d = c
        for i in range(0, 26):
            if upper_letters[i] == c:
                d = lower_letters[i]
        out = out + d
    return out

def words(s):
    out = []
    word = ""
    for c in s + " ":
        if c == " ":
            if word != "":
                out = out + [word]
            word = ""
        else:
            word = word + c
    return out

def joined(ws):
    out = ""
    for w in ws:
        if out != "":
            out = out + " "
        out = out + w
    return out

def listed(items):
    if len(items) == 1:
        return items[0]
    out = ""
    for i in range(0, len(items) - 2+1):
        if i > 0:
            out = out + ", "
        out = out + items[i]
    return out + " and " + items[len(items) - 1]
```

## README

# P020 - Text adventure

A small text adventure in an old house of eight rooms. Somewhere inside is a
golden crown; carry it out to the garden to win. Commands are typed as words
- go north (or just n), take lamp, use key - and the game keeps what you
carry, where each thing lies, which doors are locked and which rooms you
know.

- `main.eml` - reading commands, the game rules, and the descriptions
- `world.eml` - the rooms on a grid, the doors between them, and the things
  at the start
- `route.eml` - the map of the rooms you know, and finding a route between
  rooms by backtracking
- `text.eml` - lower case, words, and lists like "a lamp, a key and a book"

The house is a three-by-three grid of positions holding eight rooms, each
with a column and a row. The way to the crown takes four things in order:
the oil lamp from the kitchen, lit, for the cellar, which is pitch dark
without it (a lit lamp lights a room whether you carry it or put it down);
the iron key the cellar hides; the key used in the hall to unlock the study
door; and the crown, carried out to the garden. The book in the library gives
a hint. The game counts your moves.

`map` draws the grid with the rooms you have been in by name (* marks where
you are) and the others as ?; from the tower you can see the whole house, so
the map shows every room after you climb it. `path` finds a way to a room you
have been in, through rooms you have been in and doors that are open: it
tries each door in turn - north, east, south, west - goes deeper, and steps
back when a way leads nowhere. The house has no loops, so the way it finds
is the only one, and so the shortest.

Commands: look, go north/east/south/west (or n, e, s, w), take (or get),
drop and use a thing, inventory (i), map, path to a room, help, quit.
Anything else is answered with a hint to type help.

Sessions: `sessions/basic.in` plays the whole game - the lamp and the book,
the dark cellar lit up, the key, a path back to the hall, the locked study
opened, the crown, the tower and its full map - and wins in 13 moves;
`sessions/bad-input.in` types an unknown command, nothing, a direction that
is not one, a locked door, two directions at once, things that are not there
or not carried, paths to rooms that do not exist or are not known yet, walks
into the garden without the crown, leaves the lamp behind before the cellar,
lights it, puts it down inside the cellar and still sees, tries the key
where there is no lock, and quits.

Built on the verified corpus cases `maze-solver-backtracking` (a way through
a grid found by trying each direction and stepping back) and
`grid-coordinate-map` (places kept by their grid coordinates).
