Home > Netzwerke, Programmierung, SW-Technik > Schachspiel über WebSockets

Schachspiel über WebSockets

9. November 2014

Hier ein kleines Beispiel, was mit dem HTML 5 WebSocket-Protkoll möglich ist, das von fast allen modernen Web-Browsern unterstützt wird.

Eine WebSocket ist eine bidirektionale Verbindung zwischen einem Browser und einem Server. Es ist möglich, Text-Nachrichten in beide Richtungen in “fast Echtzeit” zu empfangen und zu senden. Mit “fast Echtzeit” meine ich, dass die menschlichen Sinne bei einer schnellen und stabilen Netzwerkverbindung keine signifikanten Verzögerungen feststellen können. WebSockets sind geeignet für Web-Anwendungen, die eine hohe Interaktivität und kleine Datenvolumen haben, z.B. ein Chat-Programm oder ein Computerspiel.

Dieses Beispiel zeigt ein Schachprogramm, das auf meinem Server läuft und meine fehlerhafte und unvollständige Schach-Engine benutzt, die ich vor ein paar Jahren geschrieben habe.

Auf Clientseite ist ein JavaScript Programm, das die Züge des Anwenders zum Server schickt und Züge des Computers empfängt. Der Client ist sehr dumm und kennt keine Schachregeln. Die Validierung wird komplett auf dem Server durchgeführt. Mit jeder Nachricht schickt der Server die komplette (neue) Position, die dann vom Client auf dem Brett aktualisiert wird.

Das Nachrichtenformat ist JSON, weil es auf Clientseite einfach von/zu JavaScript-Objekten serialisiert/deserialisiert werden kann.

Diese Applikation nutzt drei Typen von Nachrichten:

  • Anfrage (Request): Der Client sendet ein Request zum Logger, wie “Ich möchte ein neues Spiel starten” und “Ich möchte einen Zug machen”
  • Antwort (Response): Als Reaktion einer Client Anfrage sendet der Server eine Antwort. Ich denke, dass es für die meisten Anwendungen vernünftig ist, für alle Anfragen des Clients auch eine Antwort zu schicken und wenn es eine bloße Notiz ist, dass die Anfrage eingetroffen ist. Hier sendet der Server Antworten wie “Neues Spiel wurde gestartet”, “Dein Zug wurde akzeptiert”, “Dein Zug wurde abgelehnt” (Ungültiger Zug, nicht am Zug, …) und so weiter.
  • Ereignis (Event): Eine asynchrone Nachricht, die vom Server geschickt wird, wenn ein Ereignis (auf Serverseite) eintrifft. In diesem Beispiel: “Das Schachprogramm hat einen Zug gemacht”

Der “Request/Response” Mechanismus ist genau was das traditionelle Client/Server-Modell tut und ist einfach mit HTTP allein möglich.
Der Vorteil der WebSockets sind, dass sich Nachrichten vom Typ “Ereignis” ziemlich einfach realisieren lassen.

Der zweite große Vorteil einer WebSocket-Verbindung ist, dass sie zustandsbehaftet sind, im Vergleich zur Zustandslosigkeit von HTTP. Es gibt viele intelligente Lösungen, um die Zustandslosigkeit von HTTP zu umgehen. Aber tatsächlich sollte man überlegen, ob nicht ein Protokoll verwendet, das vom Entwurf her Zustände unterstützt. In unserem Beispiel ist der Zustand der Sitzung die aktuelle Position auf dem Schachbrett. Das wird vom Server gespeichert, wo es auch hingehört (meiner Meinung nach). Jetzt muss der Client nur die Züge des Anwenders an den Server weiterleiten und die aktuelle Position anzeigen.
Bei einem zustandslosen Server müsste der Client jedes Mal anfragen “Berechne mir einen Zug für diese Position” und der Client würde sich die aktuelle Position merken. Das wäre möglich, aber fühlt sich für mich unnatürlich an.
Auf der anderen Seite ist der Vorteil eines zustandslosen Web-Servers seine starke Skalierbarkeit. Man kann jederzeit einen neuen Server hinzufügen oder ersetzen.

Der WebSocket Client verwendet require.js, jQuery, die großartige chessboard.js Bibliothek und – natürlich – Websockets. So schrumpft der Client Code auf ein paar Zeilen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
define(['jquery', 'chessboard'], function() {
    var url = "ws://" + location.hostname + ":" + location.port + location.pathname + "endpoint";
    var ws = new WebSocket(url);
    var cfg = {
        draggable: true,
        position: 'start',
        onDrop: function(source, target, piece, newPos, oldPos, orientation) {
            if (source === target) {
                return;
            }
            ws.send(JSON.stringify({
                message: 'set move',
                type: 'request',
                from: source,
                to: target}));
        }
    };
    var board = new ChessBoard('board', cfg);
    board.start();
 
 
    ws.onopen = function()
    {
        ws.send(JSON.stringify(
                {message: 'new game',
                    type: 'request'}));
    };
    ws.onmessage = function(evt)
    {
        var message = JSON.parse(evt.data);
        if (message.message === 'new game' && message.type === 'response') {
            board.position(message.position);
        } else if (message.message === 'set move' || message.message === 'new move') {
            board.position(message.position);
        }
    };
    ws.onclose = function()
    {
        console.log("Connection is closed...");
    };
});
Kommentare sind geschlossen