Skip to content
wannacode

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;
}

"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();
}
}

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");

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.";
}
}

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 north
use key
go east
take key
use key
go west
use key
Answer

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.";
}
}

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");
}

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.");
}
Output
[lab] The tables are full of flasks. The door behind you locked by itself.
You see: flashlight
Exits: north
> go north
[corridor] A long corridor. The ceiling light flickers.
Exits: north, south, east, west
> go west
The 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: key
Exits: west
> take key
You 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 key
You unlock the door to the office.
> go west
[office] A desk, a chair and a cabinet full of folders.
You see: note
Exits: east
> take note
You 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 north
The door to the lobby is locked.
> enter 1234
Wrong code. The lock flashes red.
> enter 4719
The lock clicks. The door to the lobby is open.
> go north
[lobby] The glass door to the street is open.
Exits: south
You got out in 13 moves.

With this input:

Input
go north
go west
go east
take key
go west
use key
go west
take note
go east
go north
enter 1234
enter 4719
go north

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.");
}
Step by step, in words
  1. 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.
  2. Move 1: "go north". You enter the corridor.
  3. Move 2: "go west". The door to the office is locked: "go" stops before it moves you.
  4. Move 3: "go east". You enter the storeroom. On the wall: "Stocktaking on October 19".
  5. Move 4: "take key". You take the key. It unlocks the office.
  6. Move 5: "go west". You enter the corridor.
  7. Move 6: "use key". The key is in the backpack and opens the locked neighboring door to the office.
  8. Move 7: "go west". You enter the office.
  9. Move 8: "take note". You take the note: "Lobby code: 47 and the stocktaking day."
  10. Move 9: "go east". You enter the corridor.
  11. Move 10: "go north". The door to the lobby is locked: "go" stops before it moves you.
  12. Move 11: "enter 1234". 1234 is not the code. The lock flashes red.
  13. Move 12: "enter 4719". 47 from the note and 19 from the calendar: 4719. The lobby lock clicks.
  14. Move 13: "go north". You enter the lobby.
  15. 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;
}

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);
}
}

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;
}

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() + ".";
}
}
}
More precisely, if you are curious
  • int.Parse throws FormatException, and Load turns 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.

See it

Save and continue

The player takes the key and saves. Then they try three broken codes and finally one that works:

Input
go north
go east
take key
save
load storeroom;hammer;-;4
load corridor;key;office;four
load office;key;-;7
load office;key;office;7
go east
Output
[lab] The tables are full of flasks. The door behind you locked by itself.
You see: flashlight
Exits: 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: key
Exits: west
> take key
You take the key. A small brass key. Its tag says office.
> save
Save code: storeroom;key;-;4
> load storeroom;hammer;-;4
The code does not work: there is no item hammer.
> load corridor;key;office;four
The code does not work: the moves are not a number: four.
> load office;key;-;7
The code does not work: the room office is locked.
> load office;key;office;7
You pick up where you left off.
[office] A desk, a chair and a cabinet full of folders.
You see: note
Exits: east
> go east
[corridor] A long corridor. The ceiling light flickers.
Exits: north, south, east, west

The 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".

Input
go south
take flashlight
go south
take fuse
Output
[lab] The tables are full of flasks. The door behind you locked by itself.
You see: flashlight
Exits: north, south
> go south
It is too dark. Without light you will not find anything.
> take flashlight
You take the flashlight. It works, and the battery is almost full.
> go south
[cellar] A low cellar with pipes along the ceiling.
You see: fuse
Exits: north
> take fuse
You 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.

Input
go north
go east
take key
go west
go west
use key
go west
take note
go east
go east
go west
enter 4719
go north
Output
[lab] The tables are full of flasks. The door behind you locked by itself.
You see: flashlight
Exits: 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: key
Exits: west
> take key
You 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 west
The door to the office is locked.
> use key
You unlock the door to the office.
> go west
[office] A desk, a chair and a cabinet full of folders.
You see: note
Exits: east
> take note
You 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 4719
The 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.

Input
hint
go north
go east
take key
hint
go west
go west
use key
hint
Output
[lab] The tables are full of flasks. The door behind you locked by itself.
You see: flashlight
Exits: north
> hint
Hint: 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: key
Exits: west
> take key
You take the key. A small brass key. Its tag says office.
> hint
Hint: the key is for the office.
> go west
[corridor] A long corridor. The ceiling light flickers.
Exits: north, south, east, west
> go west
The door to the office is locked.
> use key
You unlock the door to the office.
> hint
Hint: 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

Check yourself

1. The player is in the storeroom and types "use key". The key is in the backpack. What happens?
2. The save code is "corridor;key,note;office;9". What does the third part say?
3. Why does Load check everything on a new world and only at the end replace the current game?
4. Why does the move count go up after Find and not before it?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·