Felix Riesterer: Neues Anfänger-Tutorial für JavaScript: Tic-Tac-Toe

Beitrag lesen

problematische Seite

Lieber Gunnar,

Hab ich dann mal gemacht.

na, dann schauen wir einmal in das Beispiel, um zu sehen, inwiefern es sich als Alternative für ein Anfänger-Tutorial eignet.

und vergleiche anschließend die Komplexität des Codes.

Das überlasse ich gern jemand anderem.

Warum? Traust Du Dir das nicht zu? Oder befürchtest Du, dass Dein Code tatsächlich nicht mehr anfängergerecht ist, jetzt, da Du ihn fertiggestellt hast?

Dann können wir darüber fachsimpeln.

Da bin ich dann wieder dabei. ;-)

Ich fürchte, Deine Erwartungen sind andere, als der Kern dieser Diskussion...

Vergleichen wir einmal die Komplexität des Markups, welches im Artikel verwendet wird, und welches Du dagegen stellst:

<!-- Wiki-Artikel -->
<table class="tic-tac-toe">
    <tbody>
        <tr>
            <td></td>
            <td></td>
            <td></td>
        <tr>
...
    </tbody>
</table>

<!-- Dein Ansatz -->
<table id="tic-tac-toe">
    <tbody>
        <tr>
            <td>
                <label for="top-left">top left field</label>
                <select id="top-left">
                    <option></option>
                    <option value="x" aria-label="x"></option>
                    <option value="o" aria-label="o"></option>
                </select>
            </td>
...
        </tr>
...
    </tbody>
</table>

Ein Anfänger wird sicherlich meinen Ansatz schneller begreifen, da er wesentlich(!) simpler ist. Eine rein mit CSS gestaltete leere Tabellenzelle ist zwar eine Hürde für sehbehinderte (kann eine Braille-Zeile generated content darstellen?), aber um zu verstehen, wie man mit JavaScript zumindest einmal anfangen kann, ist das Beispiel wesentlich schneller erfassbar. Die ganzen Formular-Elemente muss ein Anfänger bei Dir erst einmal lernen und ihren Sinn und Zweck außerhalb eines Formulars verstanden haben, bevor er im Bereich CSS und JavaScript den Zusammenhang zum Markup herstellen kann.

Dazu kommt noch die Verwendung von id- anstelle von class-Attributen. Mehrere Spiele auf einer Seite (warum auch immer das nützlich oder sinnvoll sein mag) ist damit nicht gegeben. Eine später stärkere Objektorientierung ist dann weniger schön dadurch demonstrierbar, dass man einfach beliebig viele solcher Spiele auf einer Seite instanziiert.

Gehen wir zum CSS-Teil:


#tic-tac-toe
{
    border-collapse: collapse;
}

#tic-tac-toe td
{
    padding: 1em;
}

.js #tic-tac-toe td
{
    width: 30vmin;
    height: 30vmin;
    padding: 0;
    border: 1px solid black;
    color: transparent;
}

#tic-tac-toe .piece-x
{
    background-image: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201%201%22%3E%3Cline%20x1%3D%220.1%22%20y1%3D%220.1%22%20x2%3D%220.9%22%20y2%3D%220.9%22%20stroke-width%3D%220.1%22%20stroke%3D%22red%22%2F%3E%3Cline%20x1%3D%220.1%22%20y1%3D%220.9%22%20x2%3D%220.9%22%20y2%3D%220.1%22%20stroke-width%3D%220.1%22%20stroke%3D%22red%22%2F%3E%3C%2Fsvg%3E');
}

#tic-tac-toe .piece-o
{
    background-image: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201%201%22%3E%3Ccircle%20cx%3D%220.5%22%20cy%3D%220.5%22%20r%3D%220.4%22%20fill%3D%22none%22%20stroke-width%3D%220.1%22%20stroke%3D%22blue%22%2F%3E%3C%2Fsvg%3E');
}

#tic-tac-toe label,
#tic-tac-toe select,
#tic-tac-toe button
{
    display: block;
}

#tic-tac-toe button
{
    border: none;    
    width: 100%;
    height: 100%;
    background: transparent;
    color: transparent;
    cursor: pointer;
}

#tic-tac-toe button::-moz-focus-inner
{
    border: none;
}

#tic-tac-toe button:focus
{
    background: silver;
}

Ich habe kein Problem damit, dass Dein CSS Bilddaten als Data-URI benutzt. Wer in Sachen Gestaltung weiter ausholen möchte, darf diesen Teil gerne beliebig ausweiten. Mein Tutorial stellt Anfängern auch eine mehr oder minder knapp erklärte fertige Lösung vor, um "das GUI" schnell und wirksam umzusetzen. Selbst mit dem Selektor .js #tic-tac-toe td habe ich kein Problem, auch wenn er weit über Anfänger-Niveau liegt (was die von mir verwendeten ::before und ::after zweifellos auch sind).

Also nähern wir uns dem Kern, der JavaScript-Logik:

if (document.querySelector)
{
    document.documentElement.classList.add('js');
    
    var ticTacToeElement = document.querySelector('#tic-tac-toe');
    var rowElements = ticTacToeElement.rows;
    
    for (var i = 0, cellElements; i < rowElements.length; i++)
    {
        cellElements = rowElements[i].cells;
        
        for (var j = 0, labelElement; j < cellElements.length; j++)
        {
            labelElement = cellElements[j].querySelector('label');
            cellElements[j].innerHTML = '<button data-row="' + i + '" data-column="' + j + '">' + labelElement.innerHTML + '</button>';
        }
    }
    
    var isPlayerXMoving = true;
    var board = [
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]
    ];

    ticTacToeElement.addEventListener('click', ticTacToeClickHandler);
}

function ticTacToeClickHandler(event)
{
    var targetElement = event.target;
    var parentElement = targetElement.parentElement;
    var row, column;
    
    if (targetElement.nodeName == 'BUTTON')
    {
        row = parseInt(targetElement.dataset.row);
        column = parseInt(targetElement.dataset.column);
        
        board[row][column] = isPlayerXMoving ? 1 : -1;
        
        parentElement.innerHTML = isPlayerXMoving ? 'x' : 'o';
        parentElement.classList.add(isPlayerXMoving ? 'piece-x' : 'piece-o');
        
        if (gameOver())
        {
            alert('Game over. ' + (isPlayerXMoving ? '❌' : '⭕') + ' wins.');
        }
        else
        {
            isPlayerXMoving = !isPlayerXMoving;
        }
    }
}

function gameOver()
{
    var sum;
    
    for (var i = 0; i < 3; i++)
    {
        sum = 0;
        
        for (var j = 0; j < 3; j++)
        {
            sum += board[i][j];
        }
        
        if (Math.abs(sum) == 3)
        {
            return true;
        }
    }
    
    for (var j = 0; j < 3; j++)
    {
        sum = 0;
        
        for (var i = 0; i < 3; i++)
        {
            sum += board[i][j];
        }
        
        if (Math.abs(sum) == 3)
        {
            return true;
        }
    }

    sum = 0;

    for (var i = 0; i < 3; i++)
    {
        sum += board[i][i];
    }

    if (Math.abs(sum) == 3)
    {
        return true;
    }

    sum = 0;

    for (var i = 0; i < 3; i++)
    {
        sum += board[i][2 - i];
    }

    if (Math.abs(sum) == 3)
    {
        return true;
    }

    return false;
}

Wenn man den von mir verwendeten JavaScript-Code um alle Leerzeilen und Kommentarzeilen bereinigt, bleiben 61 Zeilen übrig. Das Ganze verpackt in eine anonyme Funktion, die sofort ausgeführt wird.

Dein Code umfasst abzüglich aller Leerzeilen (Kommentare sind keine vorhanden) 91 Zeilen, also 50% mehr! Damit hast Du schon quantitativ bewiesen, dass Dein Ansatz nicht minimalistisch ist, für ein Anfänger-Tutorial also eher weniger gut geeignet. Außerdem ist er nicht in eine Funktion verpackt, sondern liegt im globalen Scope herum. Sowohl die ausschließlich zu dem Spiel gehörenden Funktionen ticTacToeClickHandler und gameOver, als auch die Variablen ticTacToeElement, rowElements, i, cellElements, isPlayerXMoving und board sind Methoden bzw. Eigenschaften von window. Das sollte man Anfängern gleich von vornherein aber abempfehlen, auch wenn man das dahinter steckende Geheimnisprinzip noch nicht offen als solches thematisiert.

Was die tatsächliche Komplexität angeht, so wird in meinem Beispiel lediglich ein spezieller Attributwert von einer Sorte HTML-Element überprüft, bei Dir dagegen muss die ganze Menge an Eingabe-Elementen (select, option und button) abgeklappert werden, z.T. auch noch die Verschachtelung der Elemente im DOM berücksichtigt werden, um dann Elementeigenschaften wie parentElement, dataset, innerHTML und Methoden wie classList.add zu nutzen, um die Spiellogik zu implementieren. Für einen Anfänger ist das eine happige Menge an Objekteigenschaften und Methoden, bzw. Unterobjektmethoden! Letztlich verstößt Du damit gegen das KISS-Prinzip, weil Du es eben nicht "simple stupid" belässt.

Der Inhalt meiner Eventhandlerfunktion beläuft sich auf sechs Zeilen. Das ist für einen Anfänger sehr übersichtlich und verständlich: Eine Variablendeklaration, eine Verzweigung mit einer AND-verknüpften Bedingung, zwei Wertezuweisungen und einem Funktionsaufruf.

Der Inhalt Deiner Eventhandlerfunktion beläuft sich auf fünfzehn Zeilen, benötigt vier verschiedene Variablen für vier verschiedene Dinge, mit dementsprechend vielen Wertezuweisungen, und hat verschachtelte Verzweigungen. Das ist definitiv wesentlich komplexer!

Die Lösung mit einem alert-Aufruf ist in meinen Augen die schlechtestmögliche Lösung eines Problems, das mein Ansatz definitiv unaufdringlicher gelöst hat. Du kennst mit Sicherheit die Probleme, die ein alert in anderen Browsertabs/-fenstern verursacht. Wozu also Anfängern diese Unsitte beibriungen?

Was Dein Beispiel in meinen Augen wesentlich weniger konsequent trennt, ist der Zusammenhang von Markup und JavaScript-Logik. Du hantierst mit Elementen, deren Vorhandensein in genau der Weise gegeben sein muss. Wenn dem nicht so ist, funktioniert Dein Code nicht mehr wie gewünscht. Du abstrahierst überhaupt nicht, auf welchen Wegen Deine Eingaben ins Spiel gelangen, denn in der Eventhandlerfunktion hangelst Du Dich durch's DOM, um an die passenden (Eltern-)Elemente zu gelangen, damit Du Werte von ihnen abfragen oder an sie zuweisen kannst. Es gibt keine Zwischenschicht, die Eingaben und Darstellung mit der Spielelogik verbindet. Aber hattest Du nicht genau das gefordert und in Deinem Beispiel umzusetzen versucht?

Mein Ansatz verlangt genau neun <td>, ist dabei aber nicht auf ihre 3x3-Anordnung festgelegt und könnte in einer Setup-Funktion das Vorhandensein sicherstellen. Den Rest des GUI macht konsequent CSS - bis hin zur Ausgabe des Spielergebnisses. Besser kann man die Trennung zwischen Applikationslogik und Darstellung bei einem so minimalistischen Beispiel kaum machen! Dass bei der Auswertung wieder die neun <td> als Datenspeicher abgefragt werden, anstatt ein abstrahiertes Modell wie Dein board-Array zu verwenden, ist zwar im Vergleich zu Deinem Ansatz wirklich weniger schön, aber eben der Einfachheit des für Anfänger konzipierten Tutorials geopfert.

Liebe Grüße,

Felix Riesterer.

3 205

Neues Anfänger-Tutorial für JavaScript: Tic-Tac-Toe

Felix Riesterer
  • meinung
  • seitenbewertung
  • selfhtml-wiki
  1. 0
    Matthias Scharwies
    1. 0
      Felix Riesterer
      1. 0
        Matthias Apsel
        1. 0
          Felix Riesterer
          1. 0
            Der Martin
            1. 0
              Gunnar Bittersmann
              1. 0
                Der Martin
                1. 0
                  Gunnar Bittersmann
                  1. 0
                    Matthias Apsel
                    1. 0
                      Gunnar Bittersmann
                      1. 0
                        Der Martin
                        1. 0
                          Gunnar Bittersmann
                2. 0
                  Felix Riesterer
                  1. 0
                    Camping_RIDER
                    1. 0
                      Gunnar Bittersmann
                      1. 0
                        Camping_RIDER
          2. 0
            Gunnar Bittersmann
            1. 3
              Felix Riesterer
              1. 1
                Gunnar Bittersmann
                1. 0
                  Felix Riesterer
                  1. 0
                    Gunnar Bittersmann
                    1. 1
                      Matthias Apsel
                      1. 0
                        Gunnar Bittersmann
                        1. 0
                          Matthias Apsel
                          1. 0
                            Gunnar Bittersmann
                            1. 0
                              Camping_RIDER
                              1. 0
                                Gunnar Bittersmann
                                1. 0
                                  JürgenB
                                  1. 0
                                    Gunnar Bittersmann
                                    1. 0
                                      JürgenB
                                      1. 0
                                        Gunnar Bittersmann
                                2. 4
                                  Camping_RIDER
                                  1. 0
                                    Gunnar Bittersmann
                                    1. 0
                                      Camping_RIDER
                                      1. 0
                                        Gunnar Bittersmann
                                        1. 0
                                          Felix Riesterer
                                          1. 0
                                            Gunnar Bittersmann
                                            1. 0
                                              Camping_RIDER
                                              1. 0
                                                Gunnar Bittersmann
                                                1. 1

                                                  Kommunikation

                                                  Camping_RIDER
                                                  • meinung
                                                  • menschelei
              2. 0
                Richard Rüfenacht
                1. 0
                  Gunnar Bittersmann
                  1. 0
                    Richard Rüfenacht
                  2. 0
                    Auge
                    • meinung
                    • selfhtml-wiki
                    1. 0
                      Gunnar Bittersmann
                2. 0
                  Auge
                  • meinung
                  • selfhtml-wiki
    2. 0
      Gunnar Bittersmann
      1. 0
        Felix Riesterer
        1. 0

          Großes ẞ

          Gunnar Bittersmann
          • typografie
          1. 0
            Camping_RIDER
            1. 0
              Gunnar Bittersmann
              1. 0
                Camping_RIDER
                1. 0
                  Gunnar Bittersmann
                  1. 0
                    Camping_RIDER
                    1. 0
                      Der Martin
                      1. 0
                        Christian Kruse
                        1. 0
                          Der Martin
                        2. 0
                          Gunnar Bittersmann
                          1. 0
                            Christian Kruse
                            1. 0
                              Camping_RIDER
                            2. 0
                              Christian Kruse
                          2. 0
                            Der Martin
                            1. 0
                              Gunnar Bittersmann
                              • menschelei
                    2. 0
                      Matthias Apsel
            2. 0
              MudGuard
              1. 0
                Christian Kruse
              2. 0
                Gunnar Bittersmann
                1. 0
                  Felix Riesterer
              3. 0
                Gunnar Bittersmann
                1. 0
                  Matthias Apsel
          2. 2

            Großes ẞ - der Vollständigkeit halber

            Bai Se Wey
            1. 0
              Gunnar Bittersmann
              1. 1

                Großes ẞ - der Kleinlichkeit halber

                Bai Se Wey
      2. 0
        Klawischnigg
        1. 0
          Camping_RIDER
          • menschelei
  2. 0
    Gunnar Bittersmann
    1. 0
      JürgenB
      1. 0
        Matthias Apsel
        1. 0
          Christian Kruse
          1. 0
            JürgenB
            1. 0
              Tabellenkalk
          2. 0
            Camping_RIDER
      2. 0
        Gunnar Bittersmann
        1. 0
          Felix Riesterer
          1. 0
            Gunnar Bittersmann
            • selfhtml-wiki
            • svg
            1. 0
              Felix Riesterer
              1. 0
                Camping_RIDER
                1. 0
                  Gunnar Bittersmann
                  • selfhtml-wiki
                  • typografie
                  1. 0
                    Auge
                    1. 0
                      Gunnar Bittersmann
                      1. 0
                        Christian Kruse
                        1. 0
                          Gunnar Bittersmann
                          1. 3
                            Christian Kruse
              2. 0
                Gunnar Bittersmann
                1. 1
                  Camping_RIDER
                  1. 0
                    Gunnar Bittersmann
    2. 0
      Felix Riesterer
  3. 0
    JürgenB
    1. 0
      Felix Riesterer
      1. 0
        Gunnar Bittersmann
        1. 0
          Matthias Apsel
        2. 0
          Felix Riesterer
        3. 0
          Camping_RIDER
          1. 0
            Gunnar Bittersmann
            1. 0
              JürgenB
              1. 0
                Auge
              2. 0
                Camping_RIDER
              3. 0
                Gunnar Bittersmann
                1. 0
                  Gunnar Bittersmann
      2. 0
        JürgenB
  4. 4
    dedlfix
    1. 0
      Matthias Scharwies
      1. 0
        dedlfix
        1. 0
          Matthias Scharwies
          1. 0
            dedlfix
            1. 0
              Matthias Scharwies
              1. 0
                dedlfix
          2. 0
            Matthias Apsel
            1. 0
              Matthias Scharwies
            2. 0
              Camping_RIDER
    2. 0
      Felix Riesterer
      1. 0
        Gunnar Bittersmann
      2. 0
        dedlfix
  5. 0
    pl
    1. 0
      Felix Riesterer
      1. 0
        pl
        1. 0
          Gunnar Bittersmann
  6. 0
    Msass
  7. 5
    Gunnar Bittersmann
    1. 1
      dedlfix
      1. 0
        Gunnar Bittersmann
        1. 2
          Felix Riesterer
          1. 1
            Gunnar Bittersmann
            1. 1
              Felix Riesterer
              1. 0
                Gunnar Bittersmann
                1. 0
                  Gunnar Bittersmann
                  1. 0
                    Felix Riesterer
                    • meinung
                    • selfhtml-wiki
                2. 0
                  Felix Riesterer
                  • meinung
                  • selfhtml-wiki
            2. 3
              Matthias Apsel
    2. 0
      Matthias Apsel
      1. 0
        Matthias Apsel
        1. 2
          dedlfix
      2. 0
        Gunnar Bittersmann
        1. 0
          Felix Riesterer
          1. 0
            Gunnar Bittersmann
            1. 0
              Camping_RIDER
              1. 0
                Gunnar Bittersmann
                1. 1
                  Felix Riesterer
                  1. 0
                    Gunnar Bittersmann
                    1. 0
                      Felix Riesterer
            2. 0
              Felix Riesterer
              1. 0
                Gunnar Bittersmann
    3. 0
      Felix Riesterer
      1. 1
        Gunnar Bittersmann
        1. 0
          Gunnar Bittersmann
    4. 0

      Wer hat gewonnen?

      Matthias Apsel
      • javascript
      1. 0
        Gunnar Bittersmann
    5. 0
      Matthias Apsel
    6. 0
      Matthias Apsel
      1. 0
        Matthias Apsel
    7. 6
      Felix Riesterer
      1. 1
        Der Martin
        1. 1
          Felix Riesterer
          1. 0
            Matthias Scharwies
            1. 0
              Felix Riesterer
              1. 0
                Gunnar Bittersmann
          2. 1
            Gunnar Bittersmann
            1. 0
              Felix Riesterer
              1. 0
                Gunnar Bittersmann
                1. 0
                  Felix Riesterer
                  1. 0
                    Gunnar Bittersmann
                    1. 0
                      Matthias Apsel
                2. 1

                  Roter-Faden-Artikel in Wikis

                  Camping_RIDER
              2. 0
                Gunnar Bittersmann
      2. 1
        Gunnar Bittersmann
  8. 0
    Gunnar Bittersmann
    1. 0
      Felix Riesterer
      • meinung
      • selfhtml
      • selfhtml-wiki
    2. 0
      Camping_RIDER
  9. 1
    Gunnar Bittersmann
    1. 0
      Felix Riesterer
      1. 0
        Gunnar Bittersmann
        1. 0
          Felix Riesterer
          1. 0
            Gunnar Bittersmann
            1. 2
              Mitleser
              1. 0
                Gunnar Bittersmann
              2. 0
                Matthias Apsel
                1. 0
                  Gunnar Bittersmann
                  1. 1
                    Camping_RIDER
                    1. 1
                      Gunnar Bittersmann
                      1. 1
                        Felix Riesterer
                        • meinung
                        • selfhtml-wiki
                        1. 0
                          Gunnar Bittersmann
                          1. 1
                            Camping_RIDER
                            1. 0
                              Gunnar Bittersmann
                              1. 1
                                Matthias Apsel
                        2. 0
                          Gunnar Bittersmann
                          1. 2
                            Tabellenkalk
                    2. 1
                      Gunnar Bittersmann
                      1. 0
                        Camping_RIDER
                        1. 1
                          Der Martin
                      2. 0
                        Matthias Apsel
          2. 1
            Gunnar Bittersmann
    2. 0
      Camping_RIDER
      1. 0
        Gunnar Bittersmann
        1. 1
          Camping_RIDER