The locked lab 2: puzzles and the way out
Problem
All the doors are open
In the first lesson the lab is locked only in words: the player goes wherever they want. The key and the note just sit there and are no use. The game has no end.
An escape game needs:
- a locked door that opens with an item;
- a code lock, whose code you put together from clues in different rooms;
- an end: the player is outside, with a move count to brag about;
- a save code: the game is long, and the player wants to stop and go on later.
The save code is a short piece of text. The game prints it, and the player types it back. If the text is broken, the game has to say so, not crash.
The idea
A locked door
The room gets a lock. The secret is the name of the item that opens it, or the code:
private String secret = null;private boolean codeLock = false;private boolean locked = false;
public void lockWithKey(String keyName) { secret = keyName; codeLock = false; locked = true;}
public void lockWithCode(String code) { secret = code; codeLock = true; locked = true;}
public boolean isLocked() { return locked;}
public boolean isCodeLock() { return codeLock;}
public boolean opensWith(String attempt) { return locked && secret.equals(attempt);}
public void unlock() { locked = false;}private string secret = null;
public bool IsLocked { get; private set; } = false;public bool IsCodeLock { get; private set; } = false;
public void LockWithKey(string keyName){ secret = keyName; IsCodeLock = false; IsLocked = true;}
public void LockWithCode(string code){ secret = code; IsCodeLock = true; IsLocked = true;}
public bool OpensWith(string attempt){ return IsLocked && secret == attempt;}
public void Unlock(){ IsLocked = false;}def __init__(self, name, description): self._name = name self._description = description self._exits = {} self._items = [] self._secret = None self._code_lock = False self._locked = False
def lock_with_key(self, key_name): self._secret = key_name self._code_lock = False self._locked = True
def lock_with_code(self, code): self._secret = code self._code_lock = True self._locked = True
@propertydef is_locked(self): return self._locked
@propertydef is_code_lock(self): return self._code_lock
def opens_with(self, attempt): return self._locked and self._secret == attempt
def unlock(self): self._locked = False#secret = null;#codeLock = false;#locked = false;
lockWithKey(keyName) { this.#secret = keyName; this.#codeLock = false; this.#locked = true;}
lockWithCode(code) { this.#secret = code; this.#codeLock = true; this.#locked = true;}
get isLocked() { return this.#locked;}
get isCodeLock() { return this.#codeLock;}
opensWith(attempt) { return this.#locked && this.#secret === attempt;}
unlock() { this.#locked = false;}"go" now stops in front of a locked door:
class GoCommand implements Command { @Override public String execute(Game game, String argument) { Room next = game.getCurrent().getExit(argument); if (next == null) { return "There is no exit that way."; } if (next.isLocked()) { return "The door to the " + next.getName() + " is locked."; } game.setCurrent(next); return next.describe(); }}class GoCommand : ICommand{ public string Execute(Game game, string argument) { Room next = game.Current.GetExit(argument); if (next == null) { return "There is no exit that way."; } if (next.IsLocked) { return "The door to the " + next.Name + " is locked."; } game.Current = next; return next.Describe(); }}class GoCommand(Command): def execute(self, game, argument): next_room = game.current.get_exit(argument) if next_room is None: return "There is no exit that way." if next_room.is_locked: return f"The door to the {next_room.name} is locked." game.current = next_room return next_room.describe()class GoCommand { execute(game, argument) { const next = game.current.getExit(argument); if (next === undefined) { return "There is no exit that way."; } if (next.isLocked) { return `The door to the ${next.name} is locked.`; } game.current = next; return next.describe(); }}In the world, the office is locked with the key from the storeroom, and the new lobby with a code:
Room foyer = new Room("lobby", "The glass door to the street is open.");corridor.addExit("north", foyer);foyer.addExit("south", corridor);office.lockWithKey("key");foyer.lockWithCode("4719");Room foyer = new Room("lobby", "The glass door to the street is open.");corridor.AddExit("north", foyer);foyer.AddExit("south", corridor);office.LockWithKey("key");foyer.LockWithCode("4719");foyer = Room("lobby", "The glass door to the street is open.")corridor.add_exit("north", foyer)foyer.add_exit("south", corridor)office.lock_with_key("key")foyer.lock_with_code("4719")const foyer = new Room("lobby", "The glass door to the street is open.");corridor.addExit("north", foyer);foyer.addExit("south", corridor);office.lockWithKey("key");foyer.lockWithCode("4719");The idea
The key unlocks
"use key" checks the backpack, then looks for a locked neighboring door that opens with this item:
class UseCommand implements Command { @Override public String execute(Game game, String argument) { if (!game.has(argument)) { return "You have no " + argument + "."; } for (Room room : game.getCurrent().getNeighbours()) { if (!room.isCodeLock() && room.opensWith(argument)) { game.unlock(room); return "You unlock the door to the " + room.getName() + "."; } } return "The " + argument + " does not help here."; }}class UseCommand : ICommand{ public string Execute(Game game, string argument) { if (!game.Has(argument)) { return "You have no " + argument + "."; } foreach (Room room in game.Current.GetNeighbours()) { if (!room.IsCodeLock && room.OpensWith(argument)) { game.Unlock(room); return "You unlock the door to the " + room.Name + "."; } } return "The " + argument + " does not help here."; }}class UseCommand(Command): def execute(self, game, argument): if not game.has(argument): return f"You have no {argument}." for room in game.current.get_neighbours(): if not room.is_code_lock and room.opens_with(argument): game.unlock(room) return f"You unlock the door to the {room.name}." return f"The {argument} does not help here."class UseCommand { execute(game, argument) { if (!game.has(argument)) { return `You have no ${argument}.`; } for (const room of game.current.getNeighbours()) { if (!room.isCodeLock && room.opensWith(argument)) { game.unlock(room); return `You unlock the door to the ${room.name}.`; } } return `The ${argument} does not help here.`; }}Unlocking goes through the game, not straight through the room. That way the game remembers which doors are open. You will need that for the save code.
Try it
Which key, which door?
The player types these commands from the start of the game. What does the game answer to the three "use key" commands, in order?
go northuse keygo easttake keyuse keygo westuse keyThe first time, the key is still in the storeroom. The second time, it is in the backpack, but you cannot see the office from the storeroom: the only neighbor is the corridor. The third time, the player is in the corridor, and the door to the west is locked with exactly this key.
The idea
A code lock from clues
The lobby is locked with four digits. The game does not say them outright:
- the note in the office: "Lobby code: 47 and the stocktaking day.";
- the calendar in the storeroom: "Stocktaking on October 19".
The code is 4719. The command "enter" checks it against the code lock of a neighboring door:
class CodeCommand implements Command { @Override public String execute(Game game, String argument) { for (Room room : game.getCurrent().getNeighbours()) { if (room.isCodeLock() && room.isLocked()) { if (room.opensWith(argument)) { game.unlock(room); return "The lock clicks. The door to the " + room.getName() + " is open."; } return "Wrong code. The lock flashes red."; } } return "There is no code lock here."; }}class CodeCommand : ICommand{ public string Execute(Game game, string argument) { foreach (Room room in game.Current.GetNeighbours()) { if (room.IsCodeLock && room.IsLocked) { if (room.OpensWith(argument)) { game.Unlock(room); return "The lock clicks. The door to the " + room.Name + " is open."; } return "Wrong code. The lock flashes red."; } } return "There is no code lock here."; }}class CodeCommand(Command): def execute(self, game, argument): for room in game.current.get_neighbours(): if room.is_code_lock and room.is_locked: if room.opens_with(argument): game.unlock(room) return f"The lock clicks. The door to the {room.name} is open." return "Wrong code. The lock flashes red." return "There is no code lock here."class CodeCommand { execute(game, argument) { for (const room of game.current.getNeighbours()) { if (room.isCodeLock && room.isLocked) { if (room.opensWith(argument)) { game.unlock(room); return `The lock clicks. The door to the ${room.name} is open.`; } return "Wrong code. The lock flashes red."; } } return "There is no code lock here."; }}The puzzle is in the data, not in the code: a different code and different clues make a different game with the same classes.
See it
Outside
The game is won when the player reaches the lobby:
public boolean isWon() { return current.getName().equals("lobby");}public bool IsWon(){ return Current.Name == "lobby";}def is_won(self): return self._current.name == "lobby"isWon() { return this.#current.name === "lobby";}Handle counts every command it understands as a move. The loop in Main stops when you win:
Game game = new Game();System.out.println(game.getCurrent().describe());Scanner in = new Scanner(System.in);while (!game.isWon() && in.hasNextLine()) { String line = in.nextLine(); System.out.println("> " + line); System.out.println(game.handle(line));}if (game.isWon()) { System.out.println("You got out in " + game.getMoves() + " moves.");}[lab] The tables are full of flasks. The door behind you locked by itself.You see: flashlightExits: north> go north[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go westThe door to the office is locked.> go east[storeroom] Shelves of boxes up to the ceiling. A calendar on the wall says: Stocktaking on October 19.You see: keyExits: west> take keyYou take the key. A small brass key. Its tag says office.> go west[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> use keyYou unlock the door to the office.> go west[office] A desk, a chair and a cabinet full of folders.You see: noteExits: east> take noteYou take the note. Lobby code: 47 and the stocktaking day.> go east[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go northThe door to the lobby is locked.> enter 1234Wrong code. The lock flashes red.> enter 4719The lock clicks. The door to the lobby is open.> go north[lobby] The glass door to the street is open.Exits: southYou got out in 13 moves.Game game = new Game();Console.WriteLine(game.Current.Describe());string line = Console.ReadLine();while (!game.IsWon() && line != null){ Console.WriteLine("> " + line); Console.WriteLine(game.Handle(line)); line = Console.ReadLine();}if (game.IsWon()){ Console.WriteLine("You got out in " + game.Moves + " moves.");}[lab] The tables are full of flasks. The door behind you locked by itself.You see: flashlightExits: north> go north[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go westThe door to the office is locked.> go east[storeroom] Shelves of boxes up to the ceiling. A calendar on the wall says: Stocktaking on October 19.You see: keyExits: west> take keyYou take the key. A small brass key. Its tag says office.> go west[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> use keyYou unlock the door to the office.> go west[office] A desk, a chair and a cabinet full of folders.You see: noteExits: east> take noteYou take the note. Lobby code: 47 and the stocktaking day.> go east[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go northThe door to the lobby is locked.> enter 1234Wrong code. The lock flashes red.> enter 4719The lock clicks. The door to the lobby is open.> go north[lobby] The glass door to the street is open.Exits: southYou got out in 13 moves.game = Game()print(game.current.describe())for line in sys.stdin: line = line.rstrip("\n") print(f"> {line}") print(game.handle(line)) if game.is_won(): breakif game.is_won(): print(f"You got out in {game.moves} moves.")[lab] The tables are full of flasks. The door behind you locked by itself.You see: flashlightExits: north> go north[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go westThe door to the office is locked.> go east[storeroom] Shelves of boxes up to the ceiling. A calendar on the wall says: Stocktaking on October 19.You see: keyExits: west> take keyYou take the key. A small brass key. Its tag says office.> go west[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> use keyYou unlock the door to the office.> go west[office] A desk, a chair and a cabinet full of folders.You see: noteExits: east> take noteYou take the note. Lobby code: 47 and the stocktaking day.> go east[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go northThe door to the lobby is locked.> enter 1234Wrong code. The lock flashes red.> enter 4719The lock clicks. The door to the lobby is open.> go north[lobby] The glass door to the street is open.Exits: southYou got out in 13 moves.const game = new Game();console.log(game.current.describe());let line = await ask("");while (!game.isWon() && line !== undefined) { console.log(`> ${line}`); console.log(game.handle(line)); line = await ask("");}rl.close();if (game.isWon()) { console.log(`You got out in ${game.moves} moves.`);}[lab] The tables are full of flasks. The door behind you locked by itself.You see: flashlightExits: north> go north[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go westThe door to the office is locked.> go east[storeroom] Shelves of boxes up to the ceiling. A calendar on the wall says: Stocktaking on October 19.You see: keyExits: west> take keyYou take the key. A small brass key. Its tag says office.> go west[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> use keyYou unlock the door to the office.> go west[office] A desk, a chair and a cabinet full of folders.You see: noteExits: east> take noteYou take the note. Lobby code: 47 and the stocktaking day.> go east[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go northThe door to the lobby is locked.> enter 1234Wrong code. The lock flashes red.> enter 4719The lock clicks. The door to the lobby is open.> go north[lobby] The glass door to the street is open.Exits: southYou got out in 13 moves.With this input:
go northgo westgo easttake keygo westuse keygo westtake notego eastgo northenter 1234enter 4719go northGame game = new Game();Console.WriteLine(game.Current.Describe());string line = Console.ReadLine();while (!game.IsWon() && line != null){ Console.WriteLine("> " + line); Console.WriteLine(game.Handle(line)); line = Console.ReadLine();}if (game.IsWon()){ Console.WriteLine("You got out in " + game.Moves + " moves.");}Step by step, in words
- You start in the lab. The door to the office opens with the key, and the door to the lobby with a four-digit code.
- Move 1: "go north". You enter the corridor.
- Move 2: "go west". The door to the office is locked: "go" stops before it moves you.
- Move 3: "go east". You enter the storeroom. On the wall: "Stocktaking on October 19".
- Move 4: "take key". You take the key. It unlocks the office.
- Move 5: "go west". You enter the corridor.
- Move 6: "use key". The key is in the backpack and opens the locked neighboring door to the office.
- Move 7: "go west". You enter the office.
- Move 8: "take note". You take the note: "Lobby code: 47 and the stocktaking day."
- Move 9: "go east". You enter the corridor.
- Move 10: "go north". The door to the lobby is locked: "go" stops before it moves you.
- Move 11: "enter 1234". 1234 is not the code. The lock flashes red.
- Move 12: "enter 4719". 47 from the note and 19 from the calendar: 4719. The lobby lock clicks.
- Move 13: "go north". You enter the lobby.
- You reach the lobby: IsWon() is true, and the loop stops. You get out in 13 moves.
The idea
A save code
The whole state of the game is four things: where the player is, what they carry, which doors they opened and how many moves they played. They go into a short piece of text, separated by ;:
public String saveCode() { List<String> names = new ArrayList<>(); for (Item item : inventory) { names.add(item.getName()); } String items = names.isEmpty() ? "-" : String.join(",", names); String doors = unlocked.isEmpty() ? "-" : String.join(",", unlocked); return current.getName() + ";" + items + ";" + doors + ";" + moves;}public string SaveCode(){ List<string> names = new List<string>(); foreach (Item item in Inventory) { names.Add(item.Name); } string items = names.Count == 0 ? "-" : string.Join(",", names); string doors = unlocked.Count == 0 ? "-" : string.Join(",", unlocked); return Current.Name + ";" + items + ";" + doors + ";" + Moves;}def save_code(self): names = [] for item in self._inventory: names.append(item.name) items = ",".join(names) if names else "-" doors = ",".join(self._unlocked) if self._unlocked else "-" return f"{self._current.name};{items};{doors};{self._moves}"saveCode() { const names = []; for (const item of this.#inventory) { names.push(item.name); } const items = names.length === 0 ? "-" : names.join(","); const doors = this.#unlocked.length === 0 ? "-" : this.#unlocked.join(","); return `${this.#current.name};${items};${doors};${this.#moves}`;}For example office;key,note;office;9. The lists use commas, and an empty list is -, so there is never an empty spot between two ; signs.
The game prints the code, and the player types it back with "load". You do not need a file: the code can be copied onto paper or sent to a friend.
More precisely, if you are curious
The C# course has no lesson on files, so the game keeps the state only as text. Writing it to a file is one line with File.WriteAllText, when you get there.
The idea
A broken code
The player may get a letter wrong or try to cheat: appear in the locked office. Then loading throws its own exception:
class InvalidSaveCodeException extends Exception { public InvalidSaveCodeException(String message) { super(message); }}class InvalidSaveCodeException : Exception{ public InvalidSaveCodeException(string message) : base(message) { }}class InvalidSaveCodeError(Exception): passclass InvalidSaveCodeError extends Error { constructor(message) { super(message); this.name = "InvalidSaveCodeError"; }}Load builds a new world and checks the code part by part on it. Only when everything has passed does the new world become the current one:
public void load(String code) throws InvalidSaveCodeException { String[] parts = code.split(";"); if (parts.length != 4) { throw new InvalidSaveCodeException("it needs 4 parts separated by ;"); } Map<String, Room> world = buildWorld(); Room room = world.get(parts[0]); if (room == null) { throw new InvalidSaveCodeException("there is no room " + parts[0]); } List<Item> items = new ArrayList<>(); if (!parts[1].equals("-")) { for (String name : parts[1].split(",")) { Item found = takeFrom(world, name); if (found == null) { throw new InvalidSaveCodeException("there is no item " + name); } items.add(found); } } List<String> doors = new ArrayList<>(); if (!parts[2].equals("-")) { for (String name : parts[2].split(",")) { Room door = world.get(name); if (door == null || !door.isLocked()) { throw new InvalidSaveCodeException("there is no locked door to " + name); } door.unlock(); doors.add(name); } } if (room.isLocked()) { throw new InvalidSaveCodeException("the room " + room.getName() + " is locked"); } int savedMoves; try { savedMoves = Integer.parseInt(parts[3]); } catch (NumberFormatException e) { throw new InvalidSaveCodeException("the moves are not a number: " + parts[3]); } rooms = world; current = room; inventory = items; unlocked = doors; moves = savedMoves;}
private Item takeFrom(Map<String, Room> world, String name) { for (Room room : world.values()) { Item found = room.takeItem(name); if (found != null) { return found; } } return null;}public void Load(string code){ string[] parts = code.Split(';'); if (parts.Length != 4) { throw new InvalidSaveCodeException("it needs 4 parts separated by ;"); } Dictionary<string, Room> world = BuildWorld(); if (!world.ContainsKey(parts[0])) { throw new InvalidSaveCodeException("there is no room " + parts[0]); } Room room = world[parts[0]]; List<Item> items = new List<Item>(); if (parts[1] != "-") { foreach (string name in parts[1].Split(',')) { Item found = TakeFrom(world, name); if (found == null) { throw new InvalidSaveCodeException("there is no item " + name); } items.Add(found); } } List<string> doors = new List<string>(); if (parts[2] != "-") { foreach (string name in parts[2].Split(',')) { if (!world.ContainsKey(name) || !world[name].IsLocked) { throw new InvalidSaveCodeException("there is no locked door to " + name); } world[name].Unlock(); doors.Add(name); } } if (room.IsLocked) { throw new InvalidSaveCodeException("the room " + room.Name + " is locked"); } int savedMoves; try { savedMoves = int.Parse(parts[3]); } catch (FormatException) { throw new InvalidSaveCodeException("the moves are not a number: " + parts[3]); } rooms = world; Current = room; Inventory = items; unlocked = doors; Moves = savedMoves;}
private Item TakeFrom(Dictionary<string, Room> world, string name){ foreach (Room room in world.Values) { Item found = room.TakeItem(name); if (found != null) { return found; } } return null;}def load(self, code): parts = code.split(";") if len(parts) != 4: raise InvalidSaveCodeError("it needs 4 parts separated by ;") world = self._build_world() room = world.get(parts[0]) if room is None: raise InvalidSaveCodeError(f"there is no room {parts[0]}") items = [] if parts[1] != "-": for name in parts[1].split(","): found = self._take_from(world, name) if found is None: raise InvalidSaveCodeError(f"there is no item {name}") items.append(found) doors = [] if parts[2] != "-": for name in parts[2].split(","): door = world.get(name) if door is None or not door.is_locked: raise InvalidSaveCodeError(f"there is no locked door to {name}") door.unlock() doors.append(name) if room.is_locked: raise InvalidSaveCodeError(f"the room {room.name} is locked") try: saved_moves = int(parts[3]) except ValueError: raise InvalidSaveCodeError(f"the moves are not a number: {parts[3]}") self._rooms = world self._current = room self._inventory = items self._unlocked = doors self._moves = saved_moves
def _take_from(self, world, name): for room in world.values(): found = room.take_item(name) if found is not None: return found return Noneload(code) { const parts = code.split(";"); if (parts.length !== 4) { throw new InvalidSaveCodeError("it needs 4 parts separated by ;"); } const world = this.#buildWorld(); const room = world.get(parts[0]); if (room === undefined) { throw new InvalidSaveCodeError(`there is no room ${parts[0]}`); } const items = []; if (parts[1] !== "-") { for (const name of parts[1].split(",")) { const found = this.#takeFrom(world, name); if (found === null) { throw new InvalidSaveCodeError(`there is no item ${name}`); } items.push(found); } } const doors = []; if (parts[2] !== "-") { for (const name of parts[2].split(",")) { const door = world.get(name); if (door === undefined || !door.isLocked) { throw new InvalidSaveCodeError(`there is no locked door to ${name}`); } door.unlock(); doors.push(name); } } if (room.isLocked) { throw new InvalidSaveCodeError(`the room ${room.name} is locked`); } if (!/^\d+$/.test(parts[3])) { throw new InvalidSaveCodeError(`the moves are not a number: ${parts[3]}`); } this.#rooms = world; this.#current = room; this.#inventory = items; this.#unlocked = doors; this.#moves = Number(parts[3]);}
#takeFrom(world, name) { for (const room of world.values()) { const found = room.takeItem(name); if (found !== null) { return found; } } return null;}The "load" command catches the exception and tells the player what is wrong:
class LoadCommand implements Command { @Override public String execute(Game game, String argument) { try { game.load(argument); return "You pick up where you left off.\n" + game.getCurrent().describe(); } catch (InvalidSaveCodeException e) { return "The code does not work: " + e.getMessage() + "."; } }}class LoadCommand : ICommand{ public string Execute(Game game, string argument) { try { game.Load(argument); return "You pick up where you left off.\n" + game.Current.Describe(); } catch (InvalidSaveCodeException e) { return "The code does not work: " + e.Message + "."; } }}class LoadCommand(Command): def execute(self, game, argument): try: game.load(argument) return "You pick up where you left off.\n" + game.current.describe() except InvalidSaveCodeError as e: return f"The code does not work: {e}."class LoadCommand { execute(game, argument) { try { game.load(argument); return `You pick up where you left off.\n${game.current.describe()}`; } catch (e) { if (!(e instanceof InvalidSaveCodeError)) { throw e; } return `The code does not work: ${e.message}.`; } }}More precisely, if you are curious
-
int.ParsethrowsFormatException, andLoadturns it into its own exception. That way "load" catches only one exception, and the message is in the language of the game.
- The code is no protection against cheating: anyone who figures out the format can write "lobby". The check that the room is not locked guards only against the obvious. Real games add a checksum: a number computed from the whole text, which does not match if someone has touched it.
Try it
Find the bug
The player types load office;hammer;-;3. The game says "The code does not work: there is no item hammer.", but after "look" the player is in the office, which is locked. On which line is the bug?
Pick the line with the bug.
The player is moved before the code has been checked all the way. Then the item check throws, but the move has already happened. The line has to be at the very bottom, with the other changes, after all the checks.
See it
Save and continue
The player takes the key and saves. Then they try three broken codes and finally one that works:
go northgo easttake keysaveload storeroom;hammer;-;4load corridor;key;office;fourload office;key;-;7load office;key;office;7go east[lab] The tables are full of flasks. The door behind you locked by itself.You see: flashlightExits: north> go north[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go east[storeroom] Shelves of boxes up to the ceiling. A calendar on the wall says: Stocktaking on October 19.You see: keyExits: west> take keyYou take the key. A small brass key. Its tag says office.> saveSave code: storeroom;key;-;4> load storeroom;hammer;-;4The code does not work: there is no item hammer.> load corridor;key;office;fourThe code does not work: the moves are not a number: four.> load office;key;-;7The code does not work: the room office is locked.> load office;key;office;7You pick up where you left off.[office] A desk, a chair and a cabinet full of folders.You see: noteExits: east> go east[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, westThe last code was written by hand, but it is valid: the office is unlocked, and the key is in the backpack. The game does not know whether it printed the code itself. It only knows that it is a possible state.
Exercise
More puzzles
Solve the tasks in your editor, with the classes from the lesson.
1. The dark cellar. South of the lab there is a cellar ("A low cellar with pipes along the ceiling."), and in it a spare fuse. You can go in only with a flashlight in the backpack. Hint: a command that extends GoCommand checks for the cellar and calls the parent (Execute in GoCommand has to be virtual). Register it with game.AddCommand in place of "go".
go southtake flashlightgo southtake fuse[lab] The tables are full of flasks. The door behind you locked by itself.You see: flashlightExits: north, south> go southIt is too dark. Without light you will not find anything.> take flashlightYou take the flashlight. It works, and the battery is almost full.> go south[cellar] A low cellar with pipes along the ceiling.You see: fuseExits: north> take fuseYou take the fuse. A spare fuse for the fuse box.2. A timer. The guards come after 12 moves. If the player is not outside by then, the game is over.
go northgo easttake keygo westgo westuse keygo westtake notego eastgo eastgo westenter 4719go north[lab] The tables are full of flasks. The door behind you locked by itself.You see: flashlightExits: north> go north[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go east[storeroom] Shelves of boxes up to the ceiling. A calendar on the wall says: Stocktaking on October 19.You see: keyExits: west> take keyYou take the key. A small brass key. Its tag says office.> go west[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go westThe door to the office is locked.> use keyYou unlock the door to the office.> go west[office] A desk, a chair and a cabinet full of folders.You see: noteExits: east> take noteYou take the note. Lobby code: 47 and the stocktaking day.> go east[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go east[storeroom] Shelves of boxes up to the ceiling. A calendar on the wall says: Stocktaking on October 19.Exits: west> go west[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> enter 4719The lock clicks. The door to the lobby is open.The guards came. They caught you after 12 moves.3. A hint for a move. A command "hint" says the next step based on the state: the key, the office, the note, the calendar. It costs a move, like every command.
hintgo northgo easttake keyhintgo westgo westuse keyhint[lab] The tables are full of flasks. The door behind you locked by itself.You see: flashlightExits: north> hintHint: look around the storeroom.> go north[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go east[storeroom] Shelves of boxes up to the ceiling. A calendar on the wall says: Stocktaking on October 19.You see: keyExits: west> take keyYou take the key. A small brass key. Its tag says office.> hintHint: the key is for the office.> go west[corridor] A long corridor. The ceiling light flickers.Exits: north, south, east, west> go westThe door to the office is locked.> use keyYou unlock the door to the office.> hintHint: there is something to read in the office.More tasks
- The fuse. The code lock does not work until the fuse from the cellar has been "used" in the corridor.
- Moves in the code. Make the timer work after "load" too: the code already carries the moves.
- A checksum. Add a fifth part: the sum of the lengths of the first four. A code with a wrong sum is broken.
Test