Sokoban
Kisten in einem Lagerhaus auf die Zielfelder schieben — sechs Ebenen, Schrittzähler und Rückgängig-Funktion.
Ihr seid ein Lagerarbeiter und müsst Kisten auf die markierten Zielfelder schieben. Klingt einfach — und ist es in den ersten Ebenen auch. Die Tücke steckt in einer einzigen Regel: Kisten lassen sich nur schieben, niemals ziehen. Wer eine Kiste in eine Ecke schiebt, bekommt sie dort nie wieder heraus.
Sokoban wurde 1981 von Hiroyuki Imabayashi entworfen und ist bis heute das Musterbeispiel eines Spiels, das ohne Zeitdruck, ohne Gegner und ohne Zufall auskommt und trotzdem beliebig schwer werden kann. Für Informatiker ist es mehr als ein Spiel: Sokoban gehört zu den nachweislich sehr harten Planungsproblemen — Ebenen mit wenigen Kisten können Computer stundenlang beschäftigen.
Gesteuert wird mit den Pfeiltasten oder den Knöpfen darunter. Klickt vorher einmal auf das Spielfeld, damit die Tasten ankommen. Mit „Zurück" nehmt ihr Schritte zurück, so weit ihr wollt — ohne diese Möglichkeit wäre das Spiel eine Geduldsprobe, denn ein einziger falscher Schub kann eine Ebene unlösbar machen.
Die sechs Ebenen sind eigens für diese Seite entworfen und werden von Ebene zu Ebene etwas verwickelter. Unter dem Feld stehen Schritte und Schübe getrennt gezählt — unter Sokoban-Spielern ist es üblich, Lösungen nach beiden Zahlen zu vergleichen.
Verwandtes im Archiv: Die Türme von Hanoi ist das andere klassische Denkspiel ohne Zufall, Lights Out und Sudoku verlangen ebenfalls reines Nachdenken.
Kisten lassen sich nur schieben. Eine Kiste in einer Ecke bleibt dort für immer.
So funktioniert das Script
Sokoban ist ein gutes Beispiel dafür, wie wenig Code ein vollständiges Spiel braucht, wenn man den Zustand richtig trennt — und wie eine Rückgängig-Funktion praktisch von selbst entsteht.
const ebenen = [
[
"#######",
"# #",
"#.$@ #",
…
],
…
];
Die Ebenen stehen als Text da, in einer Schreibweise, die seit den achtziger Jahren üblich ist: # für Wand, Punkt für Ziel, $ für Kiste, @ für den Arbeiter. Eine Ebene zu entwerfen heißt damit, ein paar Zeilen zu tippen — kein Editor, kein Datenformat, keine Bilder.
reihe.push(zeichen === "#" ? "#" : (zeichen === "." || …) ? "." : " ");
if (zeichen === "$" || zeichen === "*") kisten.push({ x: x, y: y });
if (zeichen === "@" || zeichen === "+") arbeiter = { x: x, y: y };
Beim Laden wird der Text in zwei getrennte Teile zerlegt: den Plan mit Wänden und Zielfeldern, der sich nie ändert, und den Zustand aus Kistenpositionen und Arbeiterposition, der sich bei jedem Zug ändert. Diese Trennung ist die wichtigste Entwurfsentscheidung des ganzen Scripts. Ohne sie müsste man beim Verschieben einer Kiste daran denken, das darunterliegende Zielfeld wiederherzustellen — eine klassische Fehlerquelle.
if (istWand(hinterX, hinterY) || kisteAn(hinterX, hinterY) !== -1) return;
Das ist die gesamte Spielregel. Läuft man gegen eine Kiste, wird geprüft, ob das Feld dahinter frei ist — weder Wand noch zweite Kiste. Ist es das nicht, passiert gar nichts. Aus dieser einen Bedingung folgt alles, was Sokoban schwer macht: Man kann nicht ziehen, man kann nicht zwei Kisten auf einmal schieben, und eine Kiste in der Ecke ist verloren.
verlauf.push({
arbeiter: { x: arbeiter.x, y: arbeiter.y },
kiste: k,
von: { x: kisten[k].x, y: kisten[k].y },
schuebe: schuebe
});
Vor jedem Zug wird notiert, was sich gleich ändern wird: wo der Arbeiter stand, welche Kiste bewegt wird und wo sie herkam. Zum Zurücknehmen genügt es, den obersten Eintrag zu holen und alles zurückzusetzen. Weil bei einem Zug höchstens eine Kiste bewegt wird, sind das ein paar Zahlen je Schritt — man könnte tausende Züge zurücknehmen, ohne dass es auffiele.
Der Merker schuebe wird ebenfalls gesichert und nicht etwa heruntergezählt. Das ist bequemer und sicherer: Man muss sich nicht merken, ob der zurückgenommene Zug ein Schub war.
return kisten.every(function (kiste) {
return plan[kiste.y][kiste.x] === ".";
});
Gewonnen ist, wenn jede Kiste auf einem Zielfeld steht. every prüft genau das und liefert true, wenn die Bedingung für alle Elemente gilt. Beachtet, dass nicht geprüft wird, ob jedes Ziel belegt ist — das ist gleichbedeutend, solange es genauso viele Kisten wie Ziele gibt, und diese Formulierung kommt ohne zweite Schleife aus.
if (plan[y] === undefined || plan[y][x] === undefined || plan[y][x] === "#")
Die Wandprüfung behandelt auch den Rand: Was außerhalb des Plans liegt, gilt als Wand. Ohne diese beiden zusätzlichen Abfragen würde ein Schritt über den Rand hinaus zu einem Absturz führen. Alle Ebenen sind zwar von Wänden umschlossen — sich darauf zu verlassen wäre aber genau die Art von Annahme, die beim Entwerfen der siebten Ebene zum Fehler wird.
Zum Anpassen: Eigene Ebenen sind ein weiterer Eintrag im Array. Achtet darauf, dass die Zeilen nicht gleich lang sein müssen — das Script kommt damit zurecht, weil es jede Zeile einzeln durchläuft. Wollt ihr Ebenen aus einer der großen Sammlungen im Netz übernehmen, prüft vorher die Nutzungsbedingungen; viele Sokoban-Ebenen stehen unter eigenen Lizenzen.
Script für die eigene Homepage
Kopiert euch den kompletten Code und fügt ihn an der Stelle eurer Seite ein, an der das Script erscheinen soll. Er läuft ohne weitere Dateien und ohne fremde Server.
Mehr aus der Kategorie Spiele
-
ASCII Minesweeper
-
Asteroids
-
Ausweichen
-
Ballspiel
-
Bennys Quiz
-
Blackjack
-
Blu Mania
-
BreakOut
-
Buchstaben zählen
-
Buchstabenpuzzle
-
Checkbox Spiel
-
Concentration
-
Der längste Drücker
-
Die Türme von Hanoi
-
Einmaleins Trainer
-
Escape
-
Galgenmännchen
-
Galgenmännchen 2
-
Game Over Game
-
Glücks Klick
-
Glücksbuttons
-
Ich sehe was
-
Kniffel
-
Labyrinth
-
Liebesrechner
-
Lights Out
-
Lights Out 2
-
Mastermind
-
Minesweeper
-
Mühle