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 Funktion draw, 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 onClick und 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.G ist der Spielzustand, den du längst kennst — bei Tic-Tac-Toe ein Objekt mit dem Array cells und seinen 9 Feldern. Aus state.G.cells malst du das Brett.
  • state.ctx.gameover ist 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 state kommt jetzt von boardgame.io — die Spieldaten liegen in state.G;
  • du rufst draw nicht selbst auf — die Umgebung ruft nach jedem Zug automatisch draw(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 über textAlign/textBaseline), mit Linien aus moveTo/lineTo, einem Kreis aus arc oder sogar einem drawPicture. 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:

📌 onClick trifft clickCell — Mit onClick(x, y, breite, höhe, handler) legst du wie gewohnt einen klickbaren Bereich über eine Zelle. Im handler rufst du clickCell(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, aktualisiert G, wechselt mit deiner turn-Regel den Spieler und prüft mit endIf, ob das Spiel vorbei ist. Danach ruft es draw(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 onClick hier in einer Schleife anlegst, deklariere die Zählvariable mit let direkt in der Schleife (oder nimm for ... 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.gameover null — dann zeichnest du das Brett ganz normal.
  • Ist das Spiel vorbei, steht in gameover das Ergebnis: gameover.winner ist der Gewinner ("0" oder "1"), und bei einem Unentschieden ist gameover.draw gleich true.

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:

  1. Das 3×3-Gitter ist zu sehen.
  2. Die Markierungen kommen aus state.G.cells (richtiger Spieler in der richtigen Zelle).
  3. Jede Zelle ist anklickbar und ruft clickCell mit dem richtigen Index auf.
  4. Das Spielende wird angezeigt (Gewinner oder Unentschieden), sobald state.ctx.gameover gesetzt 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 (strokeRect mit denselben vier Zahlen wie beim onClick) — 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, onClickclickCell, 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. 🚀

Zurück zur Übersicht