5. Das große Finale: dein Spiel in einer Datei 🎮
Jetzt fließt alles zusammen. In den boardgame.io-Kapiteln hast du die komplette
Spiellogik geschrieben — setup, moves, turn, endIf und sogar einen
Bot. Im Canvas-Kurs hast du gelernt, zu zeichnen und auf Klicks zu
reagieren. Hier kommt beides in einer einzigen Datei zusammen — und dann
läuft dein Spiel vollständig auf deinem eigenen Brett.
📌 Alles in einer Datei. Rechts im Editor siehst du diesmal die ganze Datei: oben deine Spiellogik (
TicTacToe) aus den vorigen Kapiteln, unten die neue Funktiondraw, die das Brett zeichnet. So hast du beide Hälften deines Spiels an einem Ort vor Augen.Läuft deine Logik aus den Kapiteln 1–4 schon, steht hier dein Code. Hast du sie noch nicht fertig, springt eine funktionierende Reserve-Version ein, damit du dich hier ganz aufs Zeichnen konzentrieren kannst.
💡 Du brauchst die Canvas-Kapitel als Grundlage: Rechtecke, Text, und vor allem Klicks mit
onClickund den Moves. Wir bauen hier genau auf diesem Muster auf.
Die draw(state)-Funktion
Ganz unten in der Datei steht das Gerüst für die Funktion, die das Spielfeld
malt: draw. Sie wird exportiert und bekommt ein Ding — den ganzen
Spielzustand state, so wie boardgame.io ihn führt. Deine Aufgabe ist,
ihren Körper zu füllen.
Zwei Sachen liest du daraus:
state.Gist der Spielzustand, den du längst kennst — bei Tic-Tac-Toe ein Objekt mit dem Arraycellsund seinen 9 Feldern. Ausstate.G.cellsmalst du das Brett.state.ctx.gameoverist die Spielende-Info:null, solange gespielt wird, sonst das Ergebnis.
Im Gerüst holen wir beide gleich in eigene Variablen (const G = state.G und
const gameover = state.ctx.gameover), damit der Rest kurz bleibt.
📌 So spielen die Konzepte zusammen — In der Canvas-Klicks-Übung hast du schon
draw(state)geschrieben, den Zustand gelesen und Moves aufgerufen. Hier ist es dasselbe Muster, nur:
- der Zustand
statekommt jetzt von boardgame.io — die Spieldaten liegen instate.G;- du rufst
drawnicht selbst auf — die Umgebung ruft nach jedem Zug automatischdraw(state)mit dem neuen Zustand auf.
Schritt 1: Das Gitter
Fang mit dem 3×3-Gitter an. Jede Zelle ist feld (= 100) Pixel groß, das Brett
also 300 × 300.
Für jede Zelle zeichnest du dann mit strokeRect einen quadratischen Umriss der Größe feld.
Das kennst du genau so aus der Canvas-Klick-Übung mit dem Raster.
Drück ▶ Ausführen — oben in der Spiel-Sektion sollte ein leeres Gitter erscheinen.
Schritt 2: Die Markierungen aus G.cells
Jetzt malst du, was im Zustand steht. Erinnerst du dich an die Nummerierung der Felder aus dem Siegbedingungs-Kapitel? Genau die brauchst du wieder:
0 | 1 | 2
---------
3 | 4 | 5
---------
6 | 7 | 8
In G.cells steht an jeder Stelle entweder null (leer), "0" (Spieler ✕) oder
"1" (Spieler ◯).
💡 Wie genau du ✕ und ◯ zeichnest, ist dir überlassen: mit
fillText(denk an zentrierte Ausrichtung übertextAlign/textBaseline), mit Linien ausmoveTo/lineTo, einem Kreis ausarcoder sogar einemdrawPicture. Hauptsache, man erkennt, welcher Spieler in welcher Zelle ist.
Schritt 3: Klicks sind Spielzüge
Jetzt machst du das Brett spielbar. Dafür gibt es eine Brücke zwischen den beiden Welten:
📌
onClicktrifftclickCell— MitonClick(x, y, breite, höhe, handler)legst du wie gewohnt einen klickbaren Bereich über eine Zelle. Imhandlerrufst duclickCell(index)auf — und das ist genau der Move, den du in Kapitel 1 geschrieben hast! Klickt jemand auf die Zelle, führt boardgame.io den Move aus, aktualisiertG, wechselt mit deinerturn-Regel den Spieler und prüft mitendIf, ob das Spiel vorbei ist. Danach ruft esdraw(state)neu auf — und dein Bild aktualisiert sich von selbst.
Im Startcode ist die Zelle oben links (Index 0) schon verdrahtet: ein
onClick-Bereich über dieser Zelle ruft im Handler clickCell(0) auf.
Probier’s aus: Drück ▶ Ausführen und klick im Brett oben links — dort wird eine
Markierung gesetzt, der Rest bleibt tot. Deine Aufgabe: Leg für alle Zellen
einen onClick-Bereich an, dessen Handler clickCell mit dem passenden Index
aufruft. Am elegantesten geht das in derselben Schleife, in der du auch die
Markierungen zeichnest — die obere linke Ecke und den Index der Zelle hast du dort ja
schon ausgerechnet.
💡 Weil du
onClickhier in einer Schleife anlegst, deklariere die Zählvariable mitletdirekt in der Schleife (oder nimmfor ... of) — sonst klicken am Ende alle Zellen denselben Index.
Schritt 4: Das Spielende anzeigen 🏆
Dein endIf aus dem Siegbedingungs-Kapitel entscheidet, wann
Schluss ist. Sein Ergebnis bekommt draw in state.ctx.gameover:
- Solange gespielt wird, ist
state.ctx.gameovernull— dann zeichnest du das Brett ganz normal. - Ist das Spiel vorbei, steht in
gameoverdas Ergebnis:gameover.winnerist der Gewinner ("0"oder"1"), und bei einem Unentschieden istgameover.drawgleichtrue.
Deine Aufgabe: Zeig das Ergebnis auf dem Brett an, sobald gameover gesetzt
ist — zum Beispiel mit einem großen fillText über dem Spielfeld. Wie du es
gestaltest, ist dir überlassen; Hauptsache, man erkennt, wer gewonnen hat (oder
dass es unentschieden steht).
Was dahinter passiert — die Verdrahtung 🔌
Eine Frage bleibt: Woher weiß dein draw, wann es laufen soll? Und wie wird aus
einem Klick aufs Canvas ein echter Spielzug? Das erledigt ein kleines Stück
Verdrahtung, das wir dir abnehmen. Du schreibst es nicht selbst — aber es lohnt
sich, einmal zu sehen, wie deine beiden Hälften zusammengesteckt werden:
// 1) boardgame.io bekommt deine Spiellogik und verwaltet ab jetzt den Zustand.
const client = Client({ game: TicTacToe })
client.start()
// 2) Nach jeder Änderung räumen wir die alten Klick-Bereiche weg und
// zeichnen dein Brett neu -- mit dem AKTUELLEN Zustand.
function render() {
resetOnClicks()
draw(client.getState()) // du bekommst den ganzen Zustand herein
}
client.subscribe(render) // "ruf render auf, sobald sich etwas ändert"
// 3) Ein echter Klick aufs Canvas sucht den passenden onClick-Bereich
// und ruft dessen handler auf.
canvas.addEventListener("click", (event) => {
// (Maus-Position in Canvas-Koordinaten umrechnen ...)
for (const bereich of klickBereiche) {
if (liegtInnerhalb(event, bereich)) {
bereich.handler()
}
}
})
// 4) clickCell ist die Brücke: Es löst deinen Move in boardgame.io aus.
// Der Move ändert G, endIf prüft auf Sieg -- und durch (2) zeichnet
// sich dein Brett von selbst neu.
function clickCell(i) {
client.moves.clickCell(i)
}
💡 Das ist derselbe Kreislauf wie in der Klicks-Übung — „Zustand ändern, dann neu zeichnen" — nur dass der Zustand jetzt in boardgame.io lebt und das Neuzeichnen automatisch nach jedem Zug passiert.
Übung 🎯 — Dein eigenes Spielbrett
Vervollständige draw(state) in deiner Datei, bis du ein vollständiges,
spielbares Tic-Tac-Toe auf deinem eigenen Brett hast. Die Auswertung (in der
Spiel-Sektion oben) prüft automatisch:
- Das 3×3-Gitter ist zu sehen.
- Die Markierungen kommen aus
state.G.cells(richtiger Spieler in der richtigen Zelle). - Jede Zelle ist anklickbar und ruft
clickCellmit dem richtigen Index auf. - Das Spielende wird angezeigt (Gewinner oder Unentschieden), sobald
state.ctx.gameovergesetzt ist.
Wenn alles passt, spiel ein paar Runden gegen dich selbst — und denk dran: dahinter läuft die Spiellogik, die du selbst geschrieben hast.
💡 Klickt ein Zug ins Leere, obwohl du den Bereich angelegt hast? Mal den klickbaren Bereich zur Kontrolle kurz mit einer auffälligen Farbe als Umriss (
strokeRectmit denselben vier Zahlen wie beimonClick) — genau wie im Klick-Tipp aus dem Canvas-Kapitel. Liegt der Umriss genau auf der Zelle, passen die Zahlen.
Geschafft — du hast das ganze Spiel gebaut! 🎉
Halt kurz inne, was hier gerade passiert ist: Jede Zeile dieses Spiels stammt von dir.
- Die Spiellogik (
setup,moves,turn,endIf,ai) — aus den boardgame.io-Kapiteln. - Das Zeichnen und die Interaktion (
draw, das Gitter, die Markierungen,onClick→clickCell, das Spielende) — aus dem Canvas-Kurs, hier zusammengeführt.
Damit hast du genau das gebaut, was im echten boardgame.io-Projekt entsteht: ein Spiel, bei dem die Logik vom Framework verwaltet und das Brett von dir gezeichnet wird. Dieselbe Idee — Zustand zeichnen, Klicks in Züge übersetzen — trägt dich durch jedes Brettspiel, das du als Nächstes bauen willst. 🚀