Zurück zur Übersicht

Tic-Tac-Toe

In dieser Schritt-für-Schritt Anleitung programmieren wir eine einfache Variante von Tic-Tac-Toe (auch bekannt als "Drei gewinnt").

Screenshot

Wie spielt man das?

2 Spieler besetzen ein 3x3 Felder großes Spielfeld, indem sie ihre Marker O oder X abwächselnd auf eines der Felder plazieren. Gewonnen hat derjenige, der als erster schafft seine Marker in einer horizontale, vertikale oder diagonale Reihe zu platzieren.

Siehe auch Wikipedia.

Ein einfaches Spielkonzelt, was wir im Folgenden in Programmcode umsetzen wollen. Als Programmiersprache dient uns JavaScript. Vorausetzung ist ein installierter WebBrowser und ein beliebiger Editor, beispielsweise Geany, Visual Studio Code Nodepad++ oder Android Studio.

Für unseren Code nehmen wir zunächst Geany.

Basics

Beginnen wir damit einen Ordner anzulegen, damit der Code und die Grafiken darin Platz finden. Den Ordner nennen wir tic-tac-toe. Er befindet sich diekt auf unserem Schreibtisch (Desktop).

Tipp: Vermeide bei Ordner- und Dateinamen Leerzeichen und beschränke dich auf Kleinschreibung. Das erleichtert uns später die Verwaltung von Verknüpfungen.

In Geany beginnen wir mit Datei -> Neu eine neue Datei, die wir sofort unter game.html speichern.

Unsere Verzeichnisstruktur sieht nun so aus:

Desktop/
  tic-tac-toe/
    game.html

Nun füllen wir die game.html mit Inhalten. HTML steht für Hyper Text Modeling Language. Das erfordert, dass wir in die Datei Tags umgeben von spitzen Klammern setzen.

<html>
</html>

Dabei hat nahezu jedes Tag einen Start und ein Ende. Während der Start-Tag nur < und > umschließen, hat das End-Tag oft auch noch ein / an zweiter Stelle.

Die HTML Datei besitzt einen Bereich, in dem die Sturktur abgebildet wird. Den Bereich nennt man body. Darüber ist der Bereich head, dessen Betandteile nie sichtbar werden. Head und Body haben einen Start- und End-Tag.

<html>
  <head></head>
  <body></body>
</html>

Tipp: Konsequentes Einrücken um 2 oder 4 Leerzeichen oder einen Tabulator der eingeschlossenen Tags erleichtert die Orientierung enorm und lässt dich schneller Fehler finden.

Innerhalb des Bodys plazieren wir eine sichtbaren Text in Form einer Überschrift h1. h1 steht für Headline.

<html>
  <head></head>
  <body>
    <h1>Tic-Tac-Toe</h1>
  </body>
</html>

Unterhalb der Überschrift platzieren wir unser Spielfeld. Da das Spielfeld aus 9 Kacheln besteht, platzieren wir entsprechend viele Bilder vom Typ img. Das besondere am Tag img ist, dass es einen besonderen Abschluss beistzt und einwenig aneres aussieht wie etwa body oder h1:

Achtung: Zur besseren Übersicht blende ich an jetzt Teile des Codes aus. Das bedeutet nicht, dass er nicht existiert. Es bedeutet nur, dass er uns im Tutorial nicht stören soll

<body>
  <h1>Tic-Tac-Toe</h1>
  <img />
  <img />
  <img />
  <img />
  <img />
  <img />
  <img />
  <img />
  <img />
</body>

Obwohl wir im Editor die Kacheln untereinander platziert haben, wird der WebBrowser sie in eine Reihe setzen. Das macht er, weil er unseren Umbruch als sogenannten Whitespace uminterpretiert. Es hat für den WebBrowser eine ander Bedeutung als für uns. Die Kacheln sind nun also einer Reihe hintereinander angeordnet. Wir brauchen aber ein 3x3 Raster. Um 3 Reihen mit je 3 Kacheln zu erzeugen, können wir beispielsweise an zwei passenden Stellen einen künstlichen Umbruch mit Hilfe des Tags br erzeugen. Dieser wird auch wie img mit einem abschließendem /> beschrieben.

<body>
  <h1>Tic-Tac-Toe</h1>
  <img />
  <img />
  <img />
  <br />
  <img />
  <img />
  <img />
  <br />
  <img />
  <img />
  <img />
</body>

Damit wir das Spielfeld einwenig einfacher gestalten können, umschließen wir die Bilder mitsamt der Umbrüche mit einem Rahmen. Dieser wird mit Hilfe des Tags div erstellt und ist zunächst unscheinbar.

<body>
  <h1>Tic-Tac-Toe</h1>
  <div>
    <img />
    <img />
    <img />
    <br />
    <img />
    <img />
    <img />
    <br />
    <img />
    <img />
    <img />
  </div>
</body>

Weil es viele div Tags gaben kann auf einer solchen Seite, geben wir ihm einen eigenen Namen. Dies machen wir mit einem Attribut id. id steht für Identifiationsummer; in HTML genauer sogar für Identifikations-name.

Attribute sind sogenannte Schlüssel-Wert-Paare, die mit einem Gleichzeichen = von einander getrennt werden. Den Wert muss man mit doppelten Anführungsstrichen " umschließen. Das bedeutet, dass der gesamte Ausdruck wie folgt beschrieben wird:

<div id="spielfeld">
  ...
</div>

Tipp: In der Programmierung hat sich das durchgesetzt, dass die IDs mit einem kleinen Buchstaben beginnen. In unserem Fall ist deshalb "Spielfeld" klein geschrieben.

Um das Spielfeld mit Abbildern von X und O zu befüllen, benötigen wir folgende Bilder.

X
O
leer

Laden wir diese Bilder in unseren Ordner tic-tac-toe herunter. Für besseren Überblick erzeugen wir einen neuen Ordner theme und darin standard. Die Bilder platzieren wir im letzten Ordner.

Desktop/
  tic-tac-toe/
    game.html
    theme/
      standard/
        x.png
        o.png
        leer.png

Zur Demonstration laden wir in die Kacheln beliebige Bilder aus unserem Beispiel-Ordner. Um dem Tag img ein Bild zu entlocken, benötigen wir das Attribut src. Da die Dateien in den Ordnern theme und darin unter standard liegen, müssen wir diesen Pfad auch entsprechend in dem src-Attribut ablegen. Lasst uns die 3 Bilder in 3 Zeilen abbilden.

<div id="spielfeld">
  <img src="theme/standard/x.png" />
  <img src="theme/standard/x.png" />
  <img src="theme/standard/x.png" />
  <br />
  <img src="theme/standard/o.png" />
  <img src="theme/standard/o.png" />
  <img src="theme/standard/o.png" />
  <br />
  <img src="theme/standard/leer.png" />
  <img src="theme/standard/leer.png" />
  <img src="theme/standard/leer.png" />
</div>

Nun haben wir eine Reihe mit Xen eine mit Os und eine mit leeren Flächen. Wenn wir das Spiel später starten, sollen natärlich alle Felder leer sein. Das können wir vorbereiten, indem wir alle Dateipfade in den Feldern zu leer.png ändern.

<div id="spielfeld">
  <img src="theme/standard/leer.png" />
  <img src="theme/standard/leer.png" />
  <img src="theme/standard/leer.png" />
  <br />
  <img src="theme/standard/leer.png" />
  <img src="theme/standard/leer.png" />
  <img src="theme/standard/leer.png" />
  <br />
  <img src="theme/standard/leer.png" />
  <img src="theme/standard/leer.png" />
  <img src="theme/standard/leer.png" />
</div>

Das Spiel ist nun fast bereit für die erste Interaktion. Für bessere Orientierung auf dem Spielfeld geben wir ihm einwenig mehr Schliff.

Die Gestaltung der Tags nennt man Style (zu deutsch "Stil"). Passend gibt es dafür einen Tag style. Dieser ist selbst nicht sichtbar. Er hilft lediglich dabei andere Tags zu gestalten. Darum wirt der style Tag selbst im head des Dokuments platziert.

<head>
  <style>
  </style>
</head>

Der Browser beherrscht so genannte "Cascading Style Sheets" (css). Diese "Sprache" unterscheidet sich gänzlich von HTML. Um unser Spielfeld einen Rahmen zu verpassen, müssen wir folgendes in den style Tag übernehmen. Dabei ist #spielfeld ein Hinweis an den WebBrowser, dass ein Tag mit der id="spielfeld" gemeint ist. Dies ist eine Erleichterung beim Gestalten: # vpr einem Namen bedeutet immer, dass der WebBrowser nach IDs suchen muss.

<style>
  #spielfeld {
    border: 10px solid green;
  }
</style>

Tipp: Stile besitzen Attribute, die durch einen Doppelpunkt : getrennt sind. Außerdem haben sie am Ende immer ein Semikolon ;

Damit erhält unser Spielfeld einen knall-grünen, durchgezogenen, 10 Pixel dicken Rahmen. Der Rahmen ist standardmäßig auf die gesammte Seite ausgedehnt. Der Grund hierfür ist, dass div zunächst einmal 100% des sichtbaren Bereich beansprucht. Damit wir das Spielfeld auf die Kacheln bechränken, müssen wir dessen Erscheinung ändern. Hierzu hilft das Attribut display.

<style>
  #spielfeld {
    border: 10px solid green;
    display: inline-block;
  }
</style>

Unsere Kacheln sollen besser sichtbar werden. Hierfür gibt es die Stil-Attribut background-color das den Kachelhintergrund umfärbt. Dann gibt es margin das dazu dient, dass ein Abstand zwischen die Kacheln setzt und so für weniger Gedränge auf dem Spielfeld sorgt. Da nur die Kacheln innerhalb des Spielfeldes umgestaltet werden sollen, müssen wir ein besonderes CSS gestalten.

<style>
  #spielfeld {
    border: 10px solid green;
    display: inline-block;
  }
  #spielfeld img {
    background-color: silver;
    margin: 10px;
  }
</style>

Mit #spielfeld img wird ausgedrückt, dass nur Bilder img unterhalb des Spielfelds #spielfeld berücksichtigt werden.

Sehr schön. Wir haben nun ein buntes Spielfeld und können mit dem ersten Script für Action sorgen!

Im Kopf der HTML Datei setzen wir nun ein script Tag ein. Dabei spielt es für uns keine Rolle, ob das script Tag über oder unter dem style Tag sitzt.

<head>
  <style>
    ...
  </style>
  <script>
  </script>
</head>

Wir beginnen unser JavaScript mit der Funktion wechsleKachel(), denn wir wollen ja bei einem Click das gedrückte Feld entweder X oder O anzeigen.

<script>
  function wechsleKachel(){
    alert(123)
  }
</script>

Damit diese Funktion auch tatsächlich verwendet wird, müssen wir allen Kacheln ein enstprechendes Ereignis (Event) registrieren. Für den Mausklick nutzen wir onclick.

<div id="spielfeld">
  <img src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <img src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <img src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <br />
  <img src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <img src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <img src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <br />
  <img src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <img src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <img src="theme/standard/leer.png" onclick="wechsleKachel();" />
</div>

Rufen wir die game.html im WebBrowser auf und klicken auf eine Kachel, so erscheint ein Hinweis mit dem Text 123.

Nur wissen wir zu dieser Zeit noch nicht welche Kachel gwerade geklickt wurde. Um die aktive, gedrückte Kachel in der Funktion wechsleKachel() zu erfahren, müssen wir sie in dem Event-Aufruf onclick="wechsleKachel();" bekannt machen, indem wir this zwischen die Klammern platzieren.

Im Ergebnis sehen alle img Tags dann wie folgt aus:

<img src="theme/standard/leer.png" onclick="wechsleFeld(this);" />

Damit das geklickte Element auch in der Funktion wechsleKachel() bekannt wird, muss ein Parameter zur Funktion beigestellt werden.

<script>
  function wechsleKachel(kachel){
    alert(123)
  }
</script>

Nun erhalten wir bei jedem Klick die Kachel als Paramater, mit der wir weiterarebiten können. So könen wir nun das Attribut src der aktiven Kachel wechseln, z.B. zu x.png.

<script>
  function wechsleKachel(aktiveKachel){
    aktiveKachel.src = "theme/standard/x.png";
  }
</script>

Attribute des Objekts aktiveKachel können unter JavaScript auf ähnliche Weise angesprochen werden wie die Attribute des HTML Tags selbst. In unserem Fall wird das img als aktiveKachel bekannt gemacht.

Beide Zeilen führen zu dem selben Ergebnis:

<img src="theme/standard/leer.png" ... />

aktiveKachel.src = "theme/standard/leer.png";

Nun soll ja aber nicht in jeder Kachel ein X auftauchen, sondern mal ein X mal ein O - und das abwechselnd. Dafür brauchen wir eine Abfrage, welcher Spieler an der Reihe ist. Auch das müssen wir irgendwo vermerken.

Wir merken uns die Aktivität des Spielers X. Ist der Spieler X am Zug, merken wir uns in einer Variable xSpieler den Wert true, also ja. Die Prüfung, ob der Spieler X gerade aktiv ist machen wir mit einer wenn-dann-sonst-Abfrage, also wenn xSpieler dann ... sonst .... In JavaScript lautet der Aufruf if (xSpieler) ... else .... Damit ... auch komplizierten Code beinhalten kann, ersetzen wir ... gegen einen Block { }. Der Code sieht dann so aus: if (xSpieler) { } else { }.

In den beiden Blöcken { } setzen wir den Wechsel des Attributs src ein:

aktiveKachel.src = "theme/standard/x.png";

bzw.

aktiveKachel.src = "theme/standard/o.png";

Nachdem die Kachel passend zum Spieler X oder O angezeigt wird, müssen wir zum Abschluss der Funktion den Spielerwechsel herbeiführen.

Da xSpieler eine Variable des Typs Boolean ist, was mit var xSpieler = true; herbeigeführt wurde, können wir den Wert zu nein, also false ändern, indem wir damit rechnen. Wir können den Wert von xSpieler umkehren, indem wir der Variable xSpieler einen neuen Wert, den negierten Wert zuweisen. Das erledigt für uns das Symbol !.

xSpieler = !xSpieler;

Unser kompletter Code sieht nun wie folgt aus:

<script>
  var xSpieler = true;

  function wechsleKachel(aktiveKachel){

    if (xSpieler){
      aktiveKachel.src = "theme/standard/x.png";
    } else {
      aktiveKachel.src = "theme/standard/o.png";
    }

    xSpieler = !xSpieler;
  }
</script>

Sehr schön. Starten wir das Spiel im WebBrowser, können wir es nach bekannten Regeln spielen... und nach unbekannten Regeln über den Haufen werfen, denn a) wir können bereits besetzte Kacheln überschreiben und b) wir können ewig spielen. Das müssen wir ändern. Das Spiel soll etwas smarter werden und uns Fehler erst gar nicht machen lassen.

Bevor wir den Code aber erneut überarbeiten, sollten wir ihn aber in andere Dateien verteilen. Der Inhalt des script Tags soll dafür in eine eigene Datei game.js wandern. Passend dazu soll der Inhalt des style Tags in eine Datei mit dem Namen game.css verschoben werden. Beide neuen Dateien liegen neben der bisherigen Datei game.html.

Desktop/
  tic-tac-toe/
    game.html
    game.css
    game.js
    theme/
      standard/
        x.png
        o.png
        leer.png

Beide Dateien können zunächst leer angelegt werden und danach mit dem Inhalt aus den oben beschriebenen Tags gefüllt werden.

game.css:

#spielfeld {
  border: 10px solid green;
  display: inline-block;
}
#spielfeld img {
  background-color: silver;
  margin: 10px;
}

game.js:

var xSpieler = true;
function wechsleKachel(aktiveKachel){
  if (xSpieler){
    aktiveKachel.src = "theme/standard/x.png";
  } else {
    aktiveKachel.src = "theme/standard/o.png";
  }
  xSpieler = !xSpieler;
}

Weil nun der Code für den Stil in einer anderen Datei existiert und das style Tag leer ist, müssen wir das es komplett gegen einen Hinweis auf die neue Datei ersetzen. Aus <style>...</style> wird ein link Tag:

<head>
  <link rel="stylesheet" href="game.css" />
  <script>
  ...
  </script>
</head>

So etwas Ähnliches passiert mit dem script Tag auch, nachdem dessen Inhalt verschoben wurde. Doch statt es gegen ein neues Tag zu ersetzen, verpassen wir ihm ein src Attribut, welches als Quelle für die Daten gilt. Das script Ende können wir nun auch, der Kürze wegen, in eine Zeile schreiben.

<head>
  <link rel="stylesheet" href="game.css" />
  <script src="game.js"></script>
</head>

Unser Code ist nun sauber verteilt auf ein paar Dateien, mit denen der WebBrowser und unser Editor besser umgehen kann. Wir konzentrieren uns beim Programmieren nur auf einen bestimmten Aspekt, z.b. Style und ein Editor wie Genay hilft uns dabei, indem es den Code für uns hübsch bunt hervorhebt - Syntax highlighting nennt man das dann.

Beginnen wir damit den offensichlichsten Fehler zu enfernen: Kein Spieler soll die Kachel eines anderen Spielers ändern dürfen. Um das zu erreichen benötigen wir eine Funktion, die prüft ob die angeklickte Kachel noch unbesetzt ist, bevor sie zum X oder O wird. Nenn wir diese Funktion istKachelLeer(), und platzieren wir sie ans Ende der Scriptdatei game.js.

function istKachelLeer(kachel){

}

Die Funktion soll uns mitteilen, ob die Kachel, die wir ihr nennen bzw. übergeben, das Bild theme/standard/leer.png entspricht. Ist das der Fall, ist die Kachel "leer". Damit wir der Funktion eine Antwort auf unsere Frage entlocken können, benötigt sie eine Rückgabeanweisung return.

function istKachelLeer(kachel){
  return
}

Die passende Frage, die geklärt werden muss lautet Kachel-Bild entspricht "theme/standard/leer.png" bzw in JavaScript kachel.src == "theme/standard/leer.png"

function istKachelLeer(kachel){
  return kachel.src == "theme/standard/leer.png"
}

Nun haben wir aber ein Problem. Der Pfad zur Datei leer.png ist nicht so wie gedacht. Auf manchen Computersystmen setzt der WebBrowser den Rest vom Pfad an die Datei, die wir nennen. also irgendetwas wie file:///bla/bla/theme/standard/leer.png. Das ist aber "nicht" der selbe Pfad, den wir überprüfen. Statt also auf Gleichheit zu prüfen, sollten wir lediglich testen, ob leer.png im Pfad existiert. Um das zu erreichen suchen wir mit Hilfe von indexOf() nach leer.png im gegebenen Pfad von src von kachel. Da indexOf() mit einer Zahl antwortet, die bei Fund 0 oder mehr ist, müssen wir nach Antworten von mehr oder gleich 0 prüfen. Also so:

function istKachelLeer(kachel){
  return kachel.src.indexOf("leer.png") >= 0;
}

Diese Funktion benötigen wir nun an einer Stelle, an der die Entscheidung über den Wechsel stattfindet, also in der Funktion wechsleKachel().

function wechsleKachel(aktiveKachel){
  if (istKachelLeer(aktiveKachel)){
    if (xSpieler){
      aktiveKachel.src = "theme/standard/x.png";
    } else {
      aktiveKachel.src = "theme/standard/o.png";
    }
    xSpieler = !xSpieler;
  }
}

Fertig. Ab sofort kann kein Spieler Kacheln besetzen, die nicht leer sind.

Es wird Zeit für die Gewinnerermittlung.

Um einen eventuellen Sieger zu ermittlen bauen wir uns eine Funktion prüfeGewinner() und platzieren sie erneut am Ende der Datei game.js.

function prüfeGewinner(){

}

Unser Konzept ist wie folgt. Klickt der User auf eine Kachel, womit die Kachel gewechselt wird, soll die Gewinnerermittlung stattfinden. Dabei prüfen wir alle möglichen Reihen, Spalten und die Diagonalen nach gleichen Markierungen mit Hilfe der Funktion prüfeReihe(). Ist eine Reihe vorhanden, leiten wir die Seite game.html um auf eine ähnliche mit Glückwünschen zum Gewinner, also x-gewonnen.html und o-gewonnen.html.

Die Funktion prüfeReihe() füttern wir mit den Nummern der Kacheln von links oben nach rechts unten, also drei aus neun. Das wiederholen wir mit allen Varianten.

function prüfeGewinner(){
  // Reihen
  prüfeReihe(1,2,3)
  prüfeReihe(4,5,6)
  prüfeReihe(7,8,9)

  // Spalten
  prüfeReihe(1,4,7)
  prüfeReihe(2,5,8)
  prüfeReihe(3,6,9)

  // Diagonalen
  prüfeReihe(1,5,9)
  prüfeReihe(3,5,7)
}

Die Funktion prüfeReihe() erhält also drei Kachelnummern, die wir als Parameter wahrnehmen. Die Namen der Parameter können wir frei bestimmen. Da es sich um Nummern handelt wäre es sinnvoll die Parameter nummer1, nummer2, nummer3 zu nennen.

function prüfeReihe(nummer1, nummer2, nummer3){
}

Aus diesen Nummern müssen wird die tatsächlichen Kachel-Objekte ermittlen, damit wir mit ihnen weiterarbeiten können. Es gibt unterschiedliche Möglichkeiten an die Kacheln zu kommen. Schauen wir uns zwei an.

Beide Methoden basieren auf einer weiteren Funktion, die wir findeKachel() nennen. Sie soll die Kachel anhand der Nummer suchen.

function findeKachel(nummer){
}

Die erste Variante durchsucht die HTML Seite document mit Hilfe der Methode getElementsByTagName() nach Tags mit dem Namen img. Als Ergebnis bekommen wir eine Liste mit Kacheln, die wir in der Variablen kacheln ablegen. Eine solche Liste nennt man auch Array.

function findeKachel(nummer){
  var kacheln = document.getElementsByTagName("img")
}

Leider steht die erste Kachel an der Position 0 statt 1, also müssen wir einwenig rechnen. Die Position in der Liste nennt man auch Index. Ein Element aus einer Liste erreichen wir mit eckigen Klammern, z.B. kacheln[0].

function findeKachel(nummer){
  var kacheln = document.getElementsByTagName("img");
  var index = nummer - 1;
  return kacheln[index];
}

Damit können wir die drei Kacheln finden, die in der Funktion prüfeReihe() benötigt werden. Die jeweiligen Funde speichern wir in passend benannten Variablen.

function prüfeReihe(nummer1, nummer2, nummer3){
  var kachel1 = findeKachel(nummer1);
  var kachel2 = findeKachel(nummer2);
  var kachel3 = findeKachel(nummer3);
}

Eine Alternative Vorgehensweise bei der Suche nach Kacheln ist es die Kacheln anhand ihrer ID zu suchen. Diese ID könnte zb. kachel1 heißen, sodass wir nicht mehr umrechnen müssten. Dafür müssen wir alle Kacheln der Reihe nach benennen.

 <div id="spielfeld">
  <img id="kachel1" src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <img id="kachel2" src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <img id="kachel3" src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <br />
  <img id="kachel4" src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <img id="kachel5" src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <img id="kachel6" src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <br />
  <img id="kachel7" src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <img id="kachel8" src="theme/standard/leer.png" onclick="wechsleKachel();" />
  <img id="kachel9" src="theme/standard/leer.png" onclick="wechsleKachel();" />
</div>

Jetzt können wir die Kacheln im document anhand der Methode getElementById() suchen. Dafür mpssen wir den namen "kachel" mit der Nummer kombinieren. Das wiederum geschicht wieder mit einem +. Aber anstatt, dass der WebBrowser versicht "kachel" und "1" zusammenzurechnen, klebt er einfach die Zahl ans Ende des Text "kachel" zu "kachel1", wenn wir eine 1 übergeben oder "kachel2", wenn wir eine 2 übergeben und so weiter. Das Ergebnis von getElementById() künnen wir ohne Umwege an die Rückgabe unserer Funktion findeKachel() übergeben.

function findeKachel(nummer){
  var id = "kachel" + nummer;
  return document.getElementById(id);
}

Beide Varianten sind möglich. Die erste ist weniger Abhängig von IDs im Dokument, listet daür alle Bilder der Seite auf, also auch die, die nicht im Spielfeld liegen. Die Zweite Variante benötigt zwar konkrete IDs im Spielfeld, aber dafür ist die Suche ohne Umwege über einen Array möglich und sie ist zudem sehr schnell.

Wir nehmen die zweite Variante.

Jetzt, wo wir die Kachel-Objekte haben, können wir mit der Prüfung fortfahren. Die Funktion prüfeReihe() benötigt nun konkrete Anweisungen, um alle drei kacheln auf Gleichheit zu prüfen. Wir nutzen erneut eine if-Anweisung, in der wir die src-Attribute unserer Kachel-Objekte vergleichen. Ist der Vergleich gelungen, sind also alle src-Attribute identisch, leiten wir die Beglückwünschung mit der Funktion glückwunsch() ein.

function prüfeReihe(nummer1, nummer2, nummer3){
  var kachel1 = findeKachel(nummer1);
  var kachel2 = findeKachel(nummer2);
  var kachel3 = findeKachel(nummer3);

  if (kachel1.src == kachel2.src && kachel2.src == kachel3.src){
    glückwunsch();
  }
}

Die Funktion glückwunsch() wiederum wechselt game.html zu einer der beiden Seiten x-gewonnen.html oder o-gewonnen.html, je nachdem welcher Spieler gerade am Zug ist.

function glückwunsch(){
  if (xSpieler){
    window.location.href = "x-gewonnen.html";
  } else {
    window.location.href = "o-gewonnen.html";
  }
}

Rufen wir nun das Spiel im WebBrowser auf, passiert nichts. Das Spiel ist defekt. Was wir nicht berücksichtigt haben, ist, dass wir Sonderzeichen in unseren Funktionen verwenden, von denen JavaScript aber zunächst nichts weiß. Um das zu ändern, müssen wir dem WebBrowser mitteilen, dass wir einen Zeichensatz vrwenden, der Sonderzeichen beinhaltet. Dafür platzieren wir im head der game.html Datei ein neues Tag meta. Der restliche Inhalt bleibt unberührt.

<head>
  <meta charset="UTF-8"/>
  ...
</head>

Nun können wir zwar das Spiel starten, aber sofort nach dem ersten Klick erhalten wir einen Fehler, weil die Seite x-gewinner.html aufgerufen werden soll. Erstens gibt es diese Seite noch nicht und zweitens die Gewinnermittlung findet zu früh statt. Warum eigentlich? Wir prüfen auf Gleichheit der Kachelbilder, doch wir haben bisher nur eine einzige Kachel markiert.

Der Grund für das Fehlverhalten liegt darin, dass die Prüfung der drei Kachelbilder auch für leer.png gilt. Der scheinbare Gewinner X wird ermittelt, weil Reihe "2,3,4" mit lauter leeren Kacheln durchaus valide ist. Nur entspricht das nicht den allgemeinen Spielregeln.

Um das Problem zu behandeln, müssen wir bei dem Vergleich der drei Kacheln diejenigen ignorieren, die leer sind.

function prüfeReihe(nummer1, nummer2, nummer3){
  var kachel1 = findeKachel(nummer1);
  var kachel2 = findeKachel(nummer2);
  var kachel3 = findeKachel(nummer3);

  if (istKachelLeer(kachel1)){
    return
  }

  if ( kachel1.src == kachel2.src && kachel2.src == kachel3.src){
    glückwunsch();
  }
}

Wir können den Code auch etwas kompakter schreiben, was aber weniger leserlich wird. Wählt selbst.

function prüfeReihe(nummer1, nummer2, nummer3){
  var kachel1 = findeKachel(nummer1);
  var kachel2 = findeKachel(nummer2);
  var kachel3 = findeKachel(nummer3);

  if (istKachelLeer(kachel1) &&
    (kachel1.src == kachel2.src && kachel2.src == kachel3.src)){
    glückwunsch();
  }
}

Bevor wir das Spiel erneut testen, müsseen die Glückwunschseiten für X und O erzeugt werden. So entstehen leere Dateien mit dem Namen x-gewonnen.html und o-gewonnen.html.

Desktop/
  tic-tac-toe/
    game.html
    game.css
    game.js
    o-gewonnen.html
    x-gewonnen.html
    theme/
      standard/
        x.png
        o.png
        leer.png

Den Inhalt beider Seiten befüllen wir mit den bekannten Strukturen aus game.html, außer, dass wir den Inhalt von body Tag auslassen.

<html>
  <head>
    <meta charset="UTF-8"/>
    <link rel="stylesheet" href="game.css" />
    <script src="game.js"></script>
  </head>
  <body>
  </body>
</html>

Im body Tag können wir nun nach belieben einen Glückwunsch-Text formulieren darstellen - je einen für X und O.

<body>
  Herzlichen Glückwunsch, Spieler X.
</body>

Bzw.

<body>
  Herzlichen Glückwunsch, Spieler O.
</body>

Wird diese Seite aufgerufen, verschwindet das Spielfeld und stattdessen wird der Gewinner-Text angezeigt. Um zurück ins Spiel zu kommen platzieren wir einen Link a unterhalb des Glückwunsch-Textes und geben ihn einer passenden Anweisung "Zurück zum Spiel".

<body>
  Herzlichen Glückwunsch, Spieler X.
  <a href="game.html">Zurück zum Spiel</a>
</body>

Beim Click auf die Anweisung wird die Seite game.html aufgerufen und das Spiel beginnt von vorn. Den Link setzen wir natürlich auch in o-gewinner.html ein.

So weit, so schön. Aber noch sind wir nicht fertig. Es gibt noch eine Ausnahme, die wir berücksichtigen müssen, um das Spiel rund zu machen: die Patt-Situation.

Nach der Prüfung aller Reihen müssen wir zum Abschluss die Patt-Situation prüfen. Es kann ja sein, dass keiner der Spieler eine Reihe aufbauen konnte.

function prüfeGewinner(){
  // Reihen
  prüfeReihe(1,2,3)
  prüfeReihe(4,5,6)
  prüfeReihe(7,8,9)

  // Spalten
  prüfeReihe(1,4,7)
  prüfeReihe(2,5,8)
  prüfeReihe(3,6,9)

  // Diagonalen
  prüfeReihe(1,5,9)
  prüfeReihe(3,5,7)

  // Patt
  prüfePatt()
}

Die Funktion prüfePatt() kloppft dabei alle Felder zwischen Nummer 1 und 9 ab, ob sie besetzt sind.

function prüfePatt(){
  for (var nummer = 1; nummer <= 9; nummer++ ){
    var kachel = findeKachel(nummer);
  }
}

Ist das nicht der Fall, und es wird eine leere Kachel gefunden, ist das Spiel noch nicht vorüber und die Funktion prüfePatt() muss mit return verlassen werden.

function prüfePatt(){
  for (var nummer = 1; nummer <= 9; nummer++ ){
    var kachel = findeKachel(nummer);
    if ( istKachelLeer( kachel ) ) {
      return;
    }
  }
}

Falls die for-Schleife alle Kacheln prüft, aber keine leere findet, ist der letzte Scrhitt die Umleitung auf die patt.html, denn es gibt nun keine weitere Zugmöglichkeit. Wegen der besseren Lesbarkeit führen wir dafür eine Funktion patt() ein.

function prüfePatt(){
  for (var nummer = 1; nummer <= 9; nummer++ ){
    var kachel = findeKachel(nummer);
    if ( istKachelLeer( kachel ) ) {
      return;
    }
  }
  return patt();
}

function patt(){
  window.location.href = "patt.html";
}

Um die Situation nun zu beschreiben, kopieren wir uns eine der Gewinner-Seiten und nenn die Kopie patt.html.

Desktop/
  tic-tac-toe/
    game.html
    game.css
    game.js
    o-gewonnen.html
    x-gewonnen.html
    patt.html
    theme/
      standard/
        x.png
        o.png
        leer.png

In der patt.html ändern wir den Text zu "Niemand hat gewonnen."

<body>
  Niemand hat gewonnen.
  <a href="game.html">Zurück zum Spiel</a>
</body>

Fertig.

Das Spiel ist nun komplett und nimmt sehr smart Rücksicht auf besondere Spiel-Situationen.