Die Türme von Hanoi
Der Klassiker unter den Knobelspielen: Bringt den Scheibenstapel auf einen anderen Pfahl, ohne je eine große Scheibe auf eine kleine zu legen.
Ziel des Spiels ist es, den kompletten Stapel Scheiben von seinem Pfahl auf einen anderen zu bringen. Dabei gilt nur eine einzige Regel, die es aber in sich hat: Es darf immer nur die oberste Scheibe eines Pfahls bewegt werden, und sie darf niemals auf einer kleineren Scheibe landen. Kleine Scheiben dürfen auf großen liegen — umgekehrt nie.
Die Bedienung ist mit zwei Klicks erledigt: Klickt zuerst auf die oberste Scheibe des Pfahls, von dem ihr nehmen wollt — sie wird hervorgehoben. Klickt dann auf den Pfahl, auf dem sie landen soll. Ein zweiter Klick auf dieselbe Scheibe hebt die Auswahl wieder auf. Über die Auswahlliste stellt ihr ein, mit wie vielen Scheiben ihr spielen wollt; „Start" beginnt eine neue Partie, und wenn ihr nicht weiterkommt, löst „Lösen" das Rätsel Zug um Zug vor euren Augen.
Zum Spiel selbst: Der französische Mathematiker Édouard Lucas hat es 1883 als Spielzeug herausgebracht und dazu die Legende vom Turm zu Benares erfunden, an dem Mönche mit 64 goldenen Scheiben arbeiten. Das Rätsel ist mit einer festen Zahl von Zügen lösbar: Für n Scheiben braucht man mindestens 2n − 1 Züge, bei drei Scheiben also 7, bei sieben Scheiben schon 127. Bei den 64 Scheiben der Legende wären es über 18 Trillionen — daher die Pointe der Geschichte, dass die Welt bis dahin längst untergegangen sei. Weil sich das Rätsel für n Scheiben immer auf dasselbe Rätsel mit n − 1 Scheiben zurückführen lässt, ist es das Lehrbuchbeispiel für Rekursion — genau so arbeitet auch die „Lösen"-Funktion in diesem Script.
Wenn ihr das Spiel auf eure eigene Homepage stellen wollt, braucht ihr neben dem Code auch die 16 Bilder der Scheiben und Pfähle: Download der Bilder. Sie gehören in dasselbe Verzeichnis wie das Script, sonst müsst ihr die Variable imgdir im Script anpassen. Geschrieben hat das Script 1998 Adam L. Stock, der es ausdrücklich zur Weiterverwendung freigegeben hat — sein Copyright-Hinweis muss dafür im Code stehen bleiben.
Wer noch mehr knobeln will, findet bei Mastermind, Lights Out und dem Buchstabenpuzzle weitere Denkaufgaben im Archiv.
So funktioniert das Script
Das Script ist mit gut 200 Zeilen eines der längeren im Archiv, aber es zerfällt in vier gut trennbare Teile: die Datenhaltung des Spielbretts in einem verschachtelten Array, das Zeichnen der Pfähle aus Bildern, die Klicklogik mit den Spielregeln und eine kleine Animationswarteschlange, die Bildwechsel nacheinander abarbeitet. Wir schauen uns von jedem Teil ein Stück an.
const board = new Array(maxposts);
board[0] = new Array(maxdisks + 1);
board[1] = new Array(maxdisks + 1);
board[2] = new Array(maxdisks + 1);
Das Spielbrett ist ein Array aus drei Arrays — für jeden Pfahl eine Liste mit acht Plätzen, von oben nach unten. In jedem Platz steht eine Zahl: 0 heißt „hier ist nur der nackte Pfahl", jede andere Zahl ist die Nummer einer Scheibe. Je höher die Nummer, desto kleiner die Scheibe. Das ganze Spiel besteht letztlich darin, diese Zahlen zwischen den drei Listen zu verschieben — die Bilder sind nur die Sichtbarmachung davon.
function topmost(num) {
for (let i = 0; i < board[num].length; i++) {
if (board[num][i] !== 0) return i;
}
return -1;
}
topmost ist die meistgebrauchte Hilfsfunktion: Sie läuft einen Pfahl von oben nach unten ab und gibt den Platz der ersten Scheibe zurück, auf die sie stößt — also die oberste Scheibe, die einzige, die bewegt werden darf. Findet sie keine, liefert sie −1. Aus dieser einen Funktion ergeben sich fast alle Regeln.
function legalmove(j) {
if (isempty(j)) return true;
return (board[j][topmost(j)] < board[selectedc][selectedr]);
}
Und das ist die Spielregel in zwei Zeilen. Auf einen leeren Pfahl darf immer gelegt werden. Sonst muss die dort oben liegende Scheibe eine höhere Nummer haben als die in der Hand — und höhere Nummer heißt in diesem Script kleinere Scheibe. Kurz: Die liegende Scheibe muss kleiner sein als die, die daraufkommt. Fällt die Prüfung durch, erscheint die Meldung „Illegaler Zug" und nichts bewegt sich.
html += '<a href="#" class="hanoi-klick" data-i="' + i + '" data-j="' + j +
'"><img src="' + imgdir + getName(board[j][i]) +
'" id="hanoi-pos' + j + i + '" alt=""></a>';
So entsteht das Brett: Für jeden der 3 × 8 Plätze wird ein Bild erzeugt und in einen anklickbaren Link gepackt. Jedes Bild bekommt eine eindeutige id nach dem Muster hanoi-pos + Pfahl + Platz, also etwa hanoi-pos13. Damit findet das Script später jedes einzelne Bild mit document.getElementById wieder, um es auszutauschen. In den data--Attributen merkt sich der Link, welcher Platz er ist — solche selbst erfundenen data--Attribute sind der übliche Weg, kleine Zusatzinfos am HTML-Element abzulegen.
brett.addEventListener("click", function (ereignis) {
const ziel = ereignis.target.closest(".hanoi-klick");
if (!ziel) return;
ereignis.preventDefault();
clicked(Number(ziel.dataset.i), Number(ziel.dataset.j));
});
Statt 27 einzelner Klickbehandlungen hängt nur eine am umschließenden Kasten. Das nennt man Ereignisdelegation: Ein Klick auf ein Bild wandert im Dokumentbaum nach oben, und closest(".hanoi-klick") fragt zurück, in welchem Link er begonnen hat. Das ist sparsamer und funktioniert auch für Elemente, die es beim Laden der Seite noch gar nicht gab. preventDefault() unterdrückt, dass der Browser dem Link # folgt und an den Seitenanfang springt.
function hanoi(no_of_disks, start_post, goal_post) {
if (no_of_disks > 0) {
const free_post = all_posts - start_post - goal_post;
hanoi(no_of_disks - 1, start_post, free_post);
toggle(start_post);
move(goal_post);
hanoi(no_of_disks - 1, free_post, goal_post);
}
}
Der „Lösen"-Knopf ist das Prunkstück des Scripts — eine Funktion, die sich selbst aufruft. Solche Rekursion wirkt beim ersten Lesen wie ein Taschenspielertrick, ist hier aber die wörtliche Übersetzung des Lösungsgedankens: Um n Scheiben ans Ziel zu bringen, schaffe ich zuerst die oberen n − 1 auf den freien Pfahl, lege dann die große Scheibe ans Ziel und hole die n − 1 wieder obendrauf. Der freie Pfahl ergibt sich dabei aus einer hübschen Rechnung: Weil die Pfähle 0, 1 und 2 heißen und zusammen 3 ergeben, ist der übrige Pfahl immer 3 − start − ziel. Beendet wird die Kette durch no_of_disks > 0 — bei null Scheiben gibt es nichts mehr zu tun.
function addframe(num, src) {
const theIndex = theAnim.imageNum.length;
theAnim.imageSrc[theIndex] = src;
theAnim.imageNum[theIndex] = num;
theAnim.startAnimation();
}
Weil beim automatischen Lösen hunderte Bildwechsel in Sekundenbruchteilen anfallen würden, sammelt das Script sie erst in zwei Listen — die Ids der Bilder und die neuen Bildquellen — und arbeitet sie dann alle 30 Millisekunden einzeln ab. Das ist eine simple Warteschlange, und sie ist der Grund, warum man dem Rechner beim Lösen zusehen kann, statt nur das fertige Ergebnis zu sehen.
Zum Anpassen: Die Meldungen stecken alle als Klartext in den message(…)-Aufrufen und lassen sich gefahrlos umformulieren. Das Tempo der Vorführung regeln die beiden Zahlen in den setTimeout-Aufrufen der Animation (5 und 30 Millisekunden) sowie die 300 Millisekunden Vorlauf beim „Lösen"-Knopf. Und wer eigene Scheibenbilder zeichnet, muss nur imgdir umbiegen — die Dateinamen disk1.gif bis disk7.gif und die zugehörigen Hervorhebungen mit h im Namen müssen dabei erhalten bleiben.
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
-
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