Das verschlossene Labor 2: Rätsel und der Ausweg
Kurz gesagt: Das Ende des Fluchtspiels: eine verschlossene Tür und ein Schlüssel, ein Zahlenschloss aus Hinweisen, der Sieg mit einer Zugzahl und ein Speichercode, den das Spiel ausgibt und wieder einliest.
Problem
Alle Türen sind offen
In der ersten Lektion ist das Labor nur in Worten verschlossen: Der Spieler geht, wohin er will. Der Schlüssel und der Zettel liegen nur herum und nützen nichts. Das Spiel hat kein Ende.
Ein Fluchtspiel braucht:
- eine verschlossene Tür, die sich mit einem Gegenstand öffnet;
- ein Zahlenschloss, dessen Code du aus Hinweisen in verschiedenen Räumen zusammensetzt;
- ein Ende: Der Spieler ist draußen, mit einer Zugzahl zum Angeben;
- einen Speichercode: Das Spiel ist lang, und der Spieler will aufhören und später weitermachen.
Der Speichercode ist ein kurzer Text. Das Spiel gibt ihn aus, und der Spieler tippt ihn wieder ein. Ist der Text kaputt, muss das Spiel das sagen und nicht abstürzen.
Die Idee
Eine verschlossene Tür
Der Raum bekommt ein Schloss. Das Geheimnis ist der Name des Gegenstands, der es öffnet, oder der 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;}„geh“ hält jetzt vor einer verschlossenen Tür an:
class GoCommand implements Command { @Override public String execute(Game game, String argument) { Room next = game.getCurrent().getExit(argument); if (next == null) { return "In diese Richtung gibt es keinen Ausgang."; } if (next.isLocked()) { return "Die Tür zum " + next.getName() + " ist verschlossen."; } 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 "In diese Richtung gibt es keinen Ausgang."; } if (next.IsLocked) { return "Die Tür zum " + next.Name + " ist verschlossen."; } 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 "In diese Richtung gibt es keinen Ausgang." if next_room.is_locked: return f"Die Tür zum {next_room.name} ist verschlossen." game.current = next_room return next_room.describe()class GoCommand { execute(game, argument) { const next = game.current.getExit(argument); if (next === undefined) { return "In diese Richtung gibt es keinen Ausgang."; } if (next.isLocked) { return `Die Tür zum ${next.name} ist verschlossen.`; } game.current = next; return next.describe(); }}In der Welt ist das Büro mit dem Schlüssel aus dem Lager verschlossen und das neue Foyer mit einem Code:
Room foyer = new Room("foyer", "Die Glastür zur Straße ist offen.");corridor.addExit("norden", foyer);foyer.addExit("süden", corridor);office.lockWithKey("schlüssel");foyer.lockWithCode("4719");Room foyer = new Room("foyer", "Die Glastür zur Straße ist offen.");corridor.AddExit("norden", foyer);foyer.AddExit("süden", corridor);office.LockWithKey("schlüssel");foyer.LockWithCode("4719");foyer = Room("foyer", "Die Glastür zur Straße ist offen.")corridor.add_exit("norden", foyer)foyer.add_exit("süden", corridor)office.lock_with_key("schlüssel")foyer.lock_with_code("4719")const foyer = new Room("foyer", "Die Glastür zur Straße ist offen.");corridor.addExit("norden", foyer);foyer.addExit("süden", corridor);office.lockWithKey("schlüssel");foyer.lockWithCode("4719");Die Idee
Der Schlüssel schließt auf
„benutze schlüssel“ prüft den Rucksack und sucht dann eine verschlossene Nachbartür, die sich mit diesem Gegenstand öffnet:
class UseCommand implements Command { @Override public String execute(Game game, String argument) { if (!game.has(argument)) { return "Du hast keinen Gegenstand „" + argument + "“."; } for (Room room : game.getCurrent().getNeighbours()) { if (!room.isCodeLock() && room.opensWith(argument)) { game.unlock(room); return "Du schließt die Tür zum " + room.getName() + " auf."; } } return "„" + argument + "“ hilft hier nicht."; }}class UseCommand : ICommand{ public string Execute(Game game, string argument) { if (!game.Has(argument)) { return "Du hast keinen Gegenstand „" + argument + "“."; } foreach (Room room in game.Current.GetNeighbours()) { if (!room.IsCodeLock && room.OpensWith(argument)) { game.Unlock(room); return "Du schließt die Tür zum " + room.Name + " auf."; } } return "„" + argument + "“ hilft hier nicht."; }}class UseCommand(Command): def execute(self, game, argument): if not game.has(argument): return f"Du hast keinen Gegenstand „{argument}“." for room in game.current.get_neighbours(): if not room.is_code_lock and room.opens_with(argument): game.unlock(room) return f"Du schließt die Tür zum {room.name} auf." return f"„{argument}“ hilft hier nicht."class UseCommand { execute(game, argument) { if (!game.has(argument)) { return `Du hast keinen Gegenstand „${argument}“.`; } for (const room of game.current.getNeighbours()) { if (!room.isCodeLock && room.opensWith(argument)) { game.unlock(room); return `Du schließt die Tür zum ${room.name} auf.`; } } return `„${argument}“ hilft hier nicht.`; }}Das Aufschließen läuft über das Spiel, nicht direkt über den Raum. So merkt sich das Spiel, welche Türen offen sind. Das brauchst du für den Speichercode.
Probier es aus
Welcher Schlüssel, welche Tür?
Der Spieler tippt diese Befehle ab dem Start des Spiels. Was antwortet das Spiel der Reihe nach auf die drei „benutze schlüssel“-Befehle?
geh nordenbenutze schlüsselgeh ostennimm schlüsselbenutze schlüsselgeh westenbenutze schlüsselBeim ersten Mal liegt der Schlüssel noch im Lager. Beim zweiten Mal ist er im Rucksack, aber vom Lager aus siehst du das Büro nicht: Der einzige Nachbar ist der Flur. Beim dritten Mal steht der Spieler im Flur, und die Tür im Westen ist genau mit diesem Schlüssel verschlossen.
Die Idee
Ein Zahlenschloss aus Hinweisen
Das Foyer ist mit vier Ziffern verschlossen. Das Spiel sagt sie nicht direkt:
- der Zettel im Büro: „Foyer-Code: 47 und der Tag der Inventur.“;
- der Kalender im Lager: „Inventur am 19. Oktober“.
Der Code ist 4719. Der Befehl „tippe“ vergleicht ihn mit dem Zahlenschloss einer Nachbartür:
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 "Das Schloss klickt. Die Tür zum " + room.getName() + " ist offen."; } return "Falscher Code. Das Schloss blinkt rot."; } } return "Hier gibt es kein Zahlenschloss."; }}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 "Das Schloss klickt. Die Tür zum " + room.Name + " ist offen."; } return "Falscher Code. Das Schloss blinkt rot."; } } return "Hier gibt es kein Zahlenschloss."; }}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"Das Schloss klickt. Die Tür zum {room.name} ist offen." return "Falscher Code. Das Schloss blinkt rot." return "Hier gibt es kein Zahlenschloss."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 `Das Schloss klickt. Die Tür zum ${room.name} ist offen.`; } return "Falscher Code. Das Schloss blinkt rot."; } } return "Hier gibt es kein Zahlenschloss."; }}Das Rätsel steckt in den Daten, nicht im Code: Ein anderer Code und andere Hinweise ergeben ein anderes Spiel mit denselben Klassen.
Sieh es dir an
Draußen
Das Spiel ist gewonnen, wenn der Spieler das Foyer erreicht:
public boolean isWon() { return current.getName().equals("foyer");}public bool IsWon(){ return Current.Name == "foyer";}def is_won(self): return self._current.name == "foyer"isWon() { return this.#current.name === "foyer";}handle zählt jeden Befehl, den es versteht, als Zug. Die Schleife im Programm endet, wenn du gewinnst:
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("Du bist nach " + game.getMoves() + " Zügen draußen.");}[labor] Die Tische sind voller Kolben. Die Tür hinter dir hat sich von selbst verriegelt.Hier siehst du: taschenlampeAusgänge: norden> geh norden[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh westenDie Tür zum büro ist verschlossen.> geh osten[lager] Regale mit Kartons bis zur Decke. An der Wand hängt ein Kalender: „Inventur am 19. Oktober“.Hier siehst du: schlüsselAusgänge: westen> nimm schlüsselDu nimmst: schlüssel. Ein kleiner Messingschlüssel. Auf dem Anhänger steht „Büro“.> geh westen[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> benutze schlüsselDu schließt die Tür zum büro auf.> geh westen[büro] Ein Schreibtisch, ein Stuhl und ein Schrank voller Ordner.Hier siehst du: zettelAusgänge: osten> nimm zettelDu nimmst: zettel. „Foyer-Code: 47 und der Tag der Inventur.“> geh osten[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh nordenDie Tür zum foyer ist verschlossen.> tippe 1234Falscher Code. Das Schloss blinkt rot.> tippe 4719Das Schloss klickt. Die Tür zum foyer ist offen.> geh norden[foyer] Die Glastür zur Straße ist offen.Ausgänge: südenDu bist nach 13 Zügen draußen.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("Du bist nach " + game.Moves + " Zügen draußen.");}[labor] Die Tische sind voller Kolben. Die Tür hinter dir hat sich von selbst verriegelt.Hier siehst du: taschenlampeAusgänge: norden> geh norden[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh westenDie Tür zum büro ist verschlossen.> geh osten[lager] Regale mit Kartons bis zur Decke. An der Wand hängt ein Kalender: „Inventur am 19. Oktober“.Hier siehst du: schlüsselAusgänge: westen> nimm schlüsselDu nimmst: schlüssel. Ein kleiner Messingschlüssel. Auf dem Anhänger steht „Büro“.> geh westen[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> benutze schlüsselDu schließt die Tür zum büro auf.> geh westen[büro] Ein Schreibtisch, ein Stuhl und ein Schrank voller Ordner.Hier siehst du: zettelAusgänge: osten> nimm zettelDu nimmst: zettel. „Foyer-Code: 47 und der Tag der Inventur.“> geh osten[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh nordenDie Tür zum foyer ist verschlossen.> tippe 1234Falscher Code. Das Schloss blinkt rot.> tippe 4719Das Schloss klickt. Die Tür zum foyer ist offen.> geh norden[foyer] Die Glastür zur Straße ist offen.Ausgänge: südenDu bist nach 13 Zügen draußen.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"Du bist nach {game.moves} Zügen draußen.")[labor] Die Tische sind voller Kolben. Die Tür hinter dir hat sich von selbst verriegelt.Hier siehst du: taschenlampeAusgänge: norden> geh norden[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh westenDie Tür zum büro ist verschlossen.> geh osten[lager] Regale mit Kartons bis zur Decke. An der Wand hängt ein Kalender: „Inventur am 19. Oktober“.Hier siehst du: schlüsselAusgänge: westen> nimm schlüsselDu nimmst: schlüssel. Ein kleiner Messingschlüssel. Auf dem Anhänger steht „Büro“.> geh westen[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> benutze schlüsselDu schließt die Tür zum büro auf.> geh westen[büro] Ein Schreibtisch, ein Stuhl und ein Schrank voller Ordner.Hier siehst du: zettelAusgänge: osten> nimm zettelDu nimmst: zettel. „Foyer-Code: 47 und der Tag der Inventur.“> geh osten[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh nordenDie Tür zum foyer ist verschlossen.> tippe 1234Falscher Code. Das Schloss blinkt rot.> tippe 4719Das Schloss klickt. Die Tür zum foyer ist offen.> geh norden[foyer] Die Glastür zur Straße ist offen.Ausgänge: südenDu bist nach 13 Zügen draußen.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(`Du bist nach ${game.moves} Zügen draußen.`);}[labor] Die Tische sind voller Kolben. Die Tür hinter dir hat sich von selbst verriegelt.Hier siehst du: taschenlampeAusgänge: norden> geh norden[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh westenDie Tür zum büro ist verschlossen.> geh osten[lager] Regale mit Kartons bis zur Decke. An der Wand hängt ein Kalender: „Inventur am 19. Oktober“.Hier siehst du: schlüsselAusgänge: westen> nimm schlüsselDu nimmst: schlüssel. Ein kleiner Messingschlüssel. Auf dem Anhänger steht „Büro“.> geh westen[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> benutze schlüsselDu schließt die Tür zum büro auf.> geh westen[büro] Ein Schreibtisch, ein Stuhl und ein Schrank voller Ordner.Hier siehst du: zettelAusgänge: osten> nimm zettelDu nimmst: zettel. „Foyer-Code: 47 und der Tag der Inventur.“> geh osten[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh nordenDie Tür zum foyer ist verschlossen.> tippe 1234Falscher Code. Das Schloss blinkt rot.> tippe 4719Das Schloss klickt. Die Tür zum foyer ist offen.> geh norden[foyer] Die Glastür zur Straße ist offen.Ausgänge: südenDu bist nach 13 Zügen draußen.Mit dieser Eingabe:
geh nordengeh westengeh ostennimm schlüsselgeh westenbenutze schlüsselgeh westennimm zettelgeh ostengeh nordentippe 1234tippe 4719geh nordenconst 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(`Du bist nach ${game.moves} Zügen draußen.`);}Schritt für Schritt, in Worten
- Du startest im Raum „labor“. Die Tür zum Raum „büro“ geht mit „schlüssel“ auf, die Tür zum Raum „foyer“ mit einem vierstelligen Code.
- Zug 1: „geh norden“. Du betrittst „flur“.
- Zug 2: „geh westen“. Die Tür zum Raum „büro“ ist verschlossen: „geh“ hält davor an, bevor es dich bewegt.
- Zug 3: „geh osten“. Du betrittst „lager“. An der Wand hängt ein Kalender: „Inventur am 19. Oktober“.
- Zug 4: „nimm schlüssel“. Du nimmst „schlüssel“. Damit geht „büro“ auf.
- Zug 5: „geh westen“. Du betrittst „flur“.
- Zug 6: „benutze schlüssel“. „schlüssel“ ist im Rucksack und öffnet die verschlossene Nachbartür zu „büro“.
- Zug 7: „geh westen“. Du betrittst „büro“.
- Zug 8: „nimm zettel“. Du nimmst „zettel“: „Foyer-Code: 47 und der Tag der Inventur.“
- Zug 9: „geh osten“. Du betrittst „flur“.
- Zug 10: „geh norden“. Die Tür zum Raum „foyer“ ist verschlossen: „geh“ hält davor an, bevor es dich bewegt.
- Zug 11: „tippe 1234“. 1234 ist nicht der Code. Das Schloss blinkt rot.
- Zug 12: „tippe 4719“. 47 vom Zettel und 19 vom Kalender: 4719. Das Schloss von „foyer“ klickt.
- Zug 13: „geh norden“. Du betrittst „foyer“.
- Du erreichst „foyer“: isWon() ist true, und die Schleife endet. Du bist nach 13 Zügen draußen.
Die Idee
Ein Speichercode
Der ganze Zustand des Spiels besteht aus vier Dingen: wo der Spieler ist, was er trägt, welche Türen er geöffnet hat und wie viele Züge er gespielt hat. Sie kommen in einen kurzen Text, getrennt durch ;:
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}`;}Zum Beispiel büro;schlüssel,zettel;büro;9. Die Listen sind mit Kommas getrennt, und eine leere Liste ist -, damit nie eine leere Stelle zwischen zwei ; steht.
Das Spiel gibt den Code aus, und der Spieler tippt ihn mit „lade“ wieder ein. Du brauchst dafür keine Datei: Den Code kannst du auf Papier abschreiben oder einem Freund schicken.
Genauer, für Neugierige
Du kannst denselben Text auch in eine Datei schreiben, mit dem, was du im Modul „Text und Dateien“ gelernt hast:
import { readFileSync, writeFileSync } from "node:fs";
writeFileSync("save.txt", game.saveCode());...later.load(readFileSync("save.txt", "utf8"));Die Idee
Ein kaputter Code
Der Spieler vertippt sich vielleicht oder will schummeln: im verschlossenen Büro auftauchen. Dann wirft das Laden eine eigene 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 baut eine neue Welt und prüft den Code Teil für Teil an ihr. Erst wenn alles durch ist, wird die neue Welt zur aktuellen:
public void load(String code) throws InvalidSaveCodeException { String[] parts = code.split(";"); if (parts.length != 4) { throw new InvalidSaveCodeException("es braucht 4 Teile, getrennt durch ;"); } Map<String, Room> world = buildWorld(); Room room = world.get(parts[0]); if (room == null) { throw new InvalidSaveCodeException("es gibt keinen Raum " + 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("es gibt keinen Gegenstand " + 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("es gibt keine verschlossene Tür zu " + name); } door.unlock(); doors.add(name); } } if (room.isLocked()) { throw new InvalidSaveCodeException("der Raum " + room.getName() + " ist verschlossen"); } int savedMoves; try { savedMoves = Integer.parseInt(parts[3]); } catch (NumberFormatException e) { throw new InvalidSaveCodeException("die Züge sind keine Zahl (" + 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("es braucht 4 Teile, getrennt durch ;"); } Dictionary<string, Room> world = BuildWorld(); if (!world.ContainsKey(parts[0])) { throw new InvalidSaveCodeException("es gibt keinen Raum " + 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("es gibt keinen Gegenstand " + 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("es gibt keine verschlossene Tür zu " + name); } world[name].Unlock(); doors.Add(name); } } if (room.IsLocked) { throw new InvalidSaveCodeException("der Raum " + room.Name + " ist verschlossen"); } int savedMoves; try { savedMoves = int.Parse(parts[3]); } catch (FormatException) { throw new InvalidSaveCodeException("die Züge sind keine Zahl (" + 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("es braucht 4 Teile, getrennt durch ;") world = self._build_world() room = world.get(parts[0]) if room is None: raise InvalidSaveCodeError(f"es gibt keinen Raum {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"es gibt keinen Gegenstand {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"es gibt keine verschlossene Tür zu {name}") door.unlock() doors.append(name) if room.is_locked: raise InvalidSaveCodeError(f"der Raum {room.name} ist verschlossen") try: saved_moves = int(parts[3]) except ValueError: raise InvalidSaveCodeError(f"die Züge sind keine Zahl ({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("es braucht 4 Teile, getrennt durch ;"); } const world = this.#buildWorld(); const room = world.get(parts[0]); if (room === undefined) { throw new InvalidSaveCodeError(`es gibt keinen Raum ${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(`es gibt keinen Gegenstand ${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(`es gibt keine verschlossene Tür zu ${name}`); } door.unlock(); doors.push(name); } } if (room.isLocked) { throw new InvalidSaveCodeError(`der Raum ${room.name} ist verschlossen`); } if (!/^\d+$/.test(parts[3])) { throw new InvalidSaveCodeError(`die Züge sind keine Zahl (${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;}Der Befehl „lade“ fängt die Exception und sagt dem Spieler, was nicht stimmt:
class LoadCommand implements Command { @Override public String execute(Game game, String argument) { try { game.load(argument); return "Du machst dort weiter, wo du aufgehört hast.\n" + game.getCurrent().describe(); } catch (InvalidSaveCodeException e) { return "Der Code funktioniert nicht: " + e.getMessage() + "."; } }}class LoadCommand : ICommand{ public string Execute(Game game, string argument) { try { game.Load(argument); return "Du machst dort weiter, wo du aufgehört hast.\n" + game.Current.Describe(); } catch (InvalidSaveCodeException e) { return "Der Code funktioniert nicht: " + e.Message + "."; } }}class LoadCommand(Command): def execute(self, game, argument): try: game.load(argument) return "Du machst dort weiter, wo du aufgehört hast.\n" + game.current.describe() except InvalidSaveCodeError as e: return f"Der Code funktioniert nicht: {e}."class LoadCommand { execute(game, argument) { try { game.load(argument); return `Du machst dort weiter, wo du aufgehört hast.\n${game.current.describe()}`; } catch (e) { if (!(e instanceof InvalidSaveCodeError)) { throw e; } return `Der Code funktioniert nicht: ${e.message}.`; } }}Genauer, für Neugierige
-
Numberwirft nicht: Eine kaputte Zahl istNaN, und leerer Text ist0. Darum prüftloaddie Züge mit dem regulären Ausdruck/^\d+$/(nur Ziffern) und wirft selbst eine eigene Exception. So fängt „lade“ nur eine einzige Exception, und die Meldung ist in der Sprache des Spiels.
console.log(Number("vier"));console.log(Number(""));console.log(Number("7"));console.log(/^\d+$/.test("vier"), /^\d+$/.test(""), /^\d+$/.test("7"));NaN07false false true- Der Code ist kein Schutz gegen Schummeln: Wer das Format herausfindet, kann „foyer“ hineinschreiben. Die Prüfung, dass der Raum nicht verschlossen ist, schützt nur vor dem Offensichtlichen. Echte Spiele fügen eine Prüfsumme hinzu: eine Zahl, die aus dem ganzen Text berechnet wird und nicht mehr stimmt, wenn jemand ihn angefasst hat.
Probier es aus
Finde den Fehler
Der Spieler tippt lade büro;hammer;-;3. Das Spiel sagt „Der Code funktioniert nicht: es gibt keinen Gegenstand hammer.“, aber nach „schau“ steht der Spieler im Büro, das verschlossen ist. In welcher Zeile steckt der Fehler?
Wähle die Zeile mit dem Fehler.
Der Spieler wird verschoben, bevor der Code ganz geprüft ist. Danach wirft die Prüfung der Gegenstände, aber die Verschiebung ist schon passiert. Die Zeile muss ganz unten stehen, bei den anderen Änderungen, nach allen Prüfungen.
Sieh es dir an
Speichern und weitermachen
Der Spieler nimmt den Schlüssel und speichert. Dann probiert er drei kaputte Codes und zuletzt einen, der funktioniert:
geh nordengeh ostennimm schlüsselspeicherelade lager;hammer;-;4lade flur;schlüssel;büro;vierlade büro;schlüssel;-;7lade büro;schlüssel;büro;7geh osten[labor] Die Tische sind voller Kolben. Die Tür hinter dir hat sich von selbst verriegelt.Hier siehst du: taschenlampeAusgänge: norden> geh norden[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh osten[lager] Regale mit Kartons bis zur Decke. An der Wand hängt ein Kalender: „Inventur am 19. Oktober“.Hier siehst du: schlüsselAusgänge: westen> nimm schlüsselDu nimmst: schlüssel. Ein kleiner Messingschlüssel. Auf dem Anhänger steht „Büro“.> speichereSpeichercode: lager;schlüssel;-;4> lade lager;hammer;-;4Der Code funktioniert nicht: es gibt keinen Gegenstand hammer.> lade flur;schlüssel;büro;vierDer Code funktioniert nicht: die Züge sind keine Zahl (vier).> lade büro;schlüssel;-;7Der Code funktioniert nicht: der Raum büro ist verschlossen.> lade büro;schlüssel;büro;7Du machst dort weiter, wo du aufgehört hast.[büro] Ein Schreibtisch, ein Stuhl und ein Schrank voller Ordner.Hier siehst du: zettelAusgänge: osten> geh osten[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westenDer letzte Code wurde von Hand geschrieben, ist aber gültig: Das Büro ist offen, und der Schlüssel liegt im Rucksack. Das Spiel weiß nicht, ob es den Code selbst ausgegeben hat. Es weiß nur, dass es ein möglicher Zustand ist.
Übung
Mehr Rätsel
Löse die Aufgaben in deinem Editor, mit den Klassen aus der Lektion.
1. Der dunkle Keller. Südlich vom Labor liegt ein Keller („Ein niedriger Keller mit Rohren an der Decke.“), und darin eine Ersatzsicherung. Hinein kommst du nur mit einer Taschenlampe im Rucksack. Tipp: Ein Befehl, der GoCommand erweitert, prüft auf den Keller und ruft die Basisklasse auf mit super.execute(...). Registriere ihn mit game.addCommand anstelle von „geh“.
geh südennimm taschenlampegeh südennimm sicherung[labor] Die Tische sind voller Kolben. Die Tür hinter dir hat sich von selbst verriegelt.Hier siehst du: taschenlampeAusgänge: norden, süden> geh südenEs ist zu dunkel. Ohne Licht findest du nichts.> nimm taschenlampeDu nimmst: taschenlampe. Sie funktioniert, und der Akku ist fast voll.> geh süden[keller] Ein niedriger Keller mit Rohren an der Decke.Hier siehst du: sicherungAusgänge: norden> nimm sicherungDu nimmst: sicherung. Eine Ersatzsicherung für den Sicherungskasten.2. Ein Timer. Die Wachen kommen nach 12 Zügen. Ist der Spieler bis dahin nicht draußen, ist das Spiel vorbei.
geh nordengeh ostennimm schlüsselgeh westengeh westenbenutze schlüsselgeh westennimm zettelgeh ostengeh ostengeh westentippe 4719geh norden[labor] Die Tische sind voller Kolben. Die Tür hinter dir hat sich von selbst verriegelt.Hier siehst du: taschenlampeAusgänge: norden> geh norden[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh osten[lager] Regale mit Kartons bis zur Decke. An der Wand hängt ein Kalender: „Inventur am 19. Oktober“.Hier siehst du: schlüsselAusgänge: westen> nimm schlüsselDu nimmst: schlüssel. Ein kleiner Messingschlüssel. Auf dem Anhänger steht „Büro“.> geh westen[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh westenDie Tür zum büro ist verschlossen.> benutze schlüsselDu schließt die Tür zum büro auf.> geh westen[büro] Ein Schreibtisch, ein Stuhl und ein Schrank voller Ordner.Hier siehst du: zettelAusgänge: osten> nimm zettelDu nimmst: zettel. „Foyer-Code: 47 und der Tag der Inventur.“> geh osten[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh osten[lager] Regale mit Kartons bis zur Decke. An der Wand hängt ein Kalender: „Inventur am 19. Oktober“.Ausgänge: westen> geh westen[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> tippe 4719Das Schloss klickt. Die Tür zum foyer ist offen.Die Wachen kamen. Sie haben dich nach 12 Zügen erwischt.3. Ein Hinweis für einen Zug. Ein Befehl „hinweis“ nennt den nächsten Schritt je nach Zustand: der Schlüssel, das Büro, der Zettel, der Kalender. Er kostet einen Zug, wie jeder Befehl.
hinweisgeh nordengeh ostennimm schlüsselhinweisgeh westengeh westenbenutze schlüsselhinweis[labor] Die Tische sind voller Kolben. Die Tür hinter dir hat sich von selbst verriegelt.Hier siehst du: taschenlampeAusgänge: norden> hinweisHinweis: Sieh dich im Lager um.> geh norden[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh osten[lager] Regale mit Kartons bis zur Decke. An der Wand hängt ein Kalender: „Inventur am 19. Oktober“.Hier siehst du: schlüsselAusgänge: westen> nimm schlüsselDu nimmst: schlüssel. Ein kleiner Messingschlüssel. Auf dem Anhänger steht „Büro“.> hinweisHinweis: Der Schlüssel gehört zum Büro.> geh westen[flur] Ein langer Flur. Die Deckenlampe flackert.Ausgänge: norden, süden, osten, westen> geh westenDie Tür zum büro ist verschlossen.> benutze schlüsselDu schließt die Tür zum büro auf.> hinweisHinweis: Im Büro gibt es etwas zu lesen.Weitere Aufgaben
- Die Sicherung. Das Zahlenschloss funktioniert erst, wenn die Sicherung aus dem Keller im Flur „benutzt“ wurde.
- Züge im Code. Lass den Timer auch nach „lade“ funktionieren: Der Code trägt die Züge schon in sich.
- Eine Prüfsumme. Füge einen fünften Teil hinzu: die Summe der Längen der ersten vier. Ein Code mit falscher Summe ist kaputt.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Reguläre Ausdrücke · Dateien · Arrays: push, splice und sort · Map und Set
- Aufgaben zum Thema: Wir bauen ein Spiel