Skip to content

Latest commit

 

History

History
150 lines (129 loc) · 25.3 KB

File metadata and controls

150 lines (129 loc) · 25.3 KB

Übersicht

Das ChessDestroyer-Projekt ist eine Webanwendung, die es einem Nutzer ermöglicht gegen eine Stockfish Chess Engine mit verschiedenen Schwierigkeitsstufen zu spielen. Gewinnt ein Spieler in einer Runde, wird er zudem noch mit einem Score bewertet und in ein persistentes Scoreboard eingetragen.

Struktur des Projektes

Die Aufgaben innerhalb der Webanwendung werden strikt zwischen dem Backend und dem Frontend aufgeteilt und wobei das Backend alle Angaben des Frontends nach dem Zero-Trust Prinzip validiert.

Frontend

Technologien

Das Frontend bassiert auf zwei Javascript frameworks:

  • Chessboard.js v2
    • Ist für das bereitstellen des Schachbrettes verantwortlich und generiert sowohl das Brett an sich als auch die Figuren.
    • Chessboard.js stellt schnittstellen bereit, um eine Aufstellung aus einem String zu laden und die einzelnen Figuren zu bewegen.
    • Auch Callbacks für Nutzereingaben werden Bereitgestellt, so dass das Auslesen von angeklickten Feldern einfacher wird.
    • Chessboard.js enthält keinerlei Spiellogik, auf dem Brett könnte im grunde alles angezeigt werden.
  • Chess.js
    • Benutzen wir um im Frontend bereits die Schachlogik bereitstellen zu können.
    • Wir benutzen Chess.js hauptsächlich um Züge zu validieren oder um zu erkennen, ob und wie ein Spiel beendet wurde (Schachmatt oder Unentschieden).

Unterseiten

Das komplette Projekt besteht lediglich aus 4 Unterseiten (3 Seiten + Impressum). Da die verknüpfungen der Seiten schwierig textuell zu beschreiben sind, haben wir dieses Diagramm erstellt.

flowchart  LR

A("welcome_page.html")
B("settings.html")
C("imprint.html")
D("game.html")

A -- "Play Now!" --> B
A -- "Imprint" --> C
B -- "Play!" --> D
D -- "Play again" ----> B
Loading

Der Nutzer Startet auf der welcome_page.html. Hier befinden sich allgemeine Informationen, was den Nutzer auf den Nächsten Seiten erwartet und worauf er genau geklickt hat. Von welcome_page.html aus kann der Nutzer über das Menü und den Button "Imprint" zu imprint.html gelangen. Alternativ kann er ausgehend von der welcome_page.html mit dem Button "Play Now!" auch auf die settings.html gelangen. Die settings.html Unterseite gibt dem Nutzer die Möglichkeit, seinen Nutzernamen (Der später auch auf dem Scoreboard landet), die Schwierigkeit des Computergegners und die Figurenfarbe (Schwarz / Weis / Zufällig) anzupassen. Über den Button "Play!" gelangt der Nutzer schlussendlich auf die game.html Seite. Hier findet das eigentliche Spiel statt. Nach einem beendeten Spiel kann mit dem Button "Play again" zurück zur settings.html navigiert werden, um ein neues Spiel zu starten.

Frontend Design

welcome_page.html

Die Landing Page für die Webanwendung "Chessdestroyer 4000" bietet eine kurze Einführung in das Produkt, hebt seine Hauptmerkmale hervor und bietet einfache Navigationselemente sowie eine Schaltfläche zum Starten des Spiels.\

Wichtige Features

  • Durch das Meta-Viewport-Tag und die externe CSS-Datei ist die Seite auf verschiedene Bildschirmgrößen angepasst.
  • Ein Formular mit einer Schaltfläche zum Starten des Spiels, das den Benutzer zur Seite "settings.html" führt.
  • Multimedia: Ein Hintergrundvideo, das der Seite ein dynamisches und ansprechendes Aussehen verleiht.

settings.html

Die Einstellungsseite ermöglicht es Benutzern, ihre Spielerinformationen und -präferenzen festzulegen, bevor sie ein neues Spiel starten.

  • Verstecktes Feld: Enthält ein verstecktes Eingabefeld (hidden), um eine neue Sitzung zu kennzeichnen.
  • Benutzernamen-Feld: Ein Eingabefeld für den Benutzernamen (username), das erforderlich ist und auf 20 Zeichen begrenzt ist.
  • Schwierigkeitsgrad: Ein Schieberegler (range) für die Auswahl des Schwierigkeitsgrads mit einem Bereich von 1 bis 3, wobei der aktuelle Wert angezeigt wird.
  • Farbauswahl: Radiobuttons für die Auswahl der Spielerfarbe (Weiß, Zufällig, Schwarz).
  • Absenden: Eine Schaltfläche zum Absenden des Formulars und Starten des Spiels.

imprint.html

Die Seite stellt eine rechtskonforme Impressumsseite für die Chessdestroyer 4000 Webanwendung bereit, die den Nutzern alle erforderlichen rechtlichen Informationen und Kontaktmöglichkeiten bietet.

game.html

Um das Spiel Layout einfacher auf mobilgeräte anpassen zu können, wird hier ein Gridlayout mit einem 2X3 Grid verwendet. Das Spielbrett nimmt ein 2X2 Felder ein, die Beschreibung von Spieler und Gegner jeweils 1 Feld. Um Das Spiel effektiv Centern zu können, besitzt das Grid zwei Parent divs. Neben dem Spielfeld besteht die Website noch aus dem game-over-container. Dieser ist beim laden der Seite verborgen und wird erst später durch javascript sichtbar gesetzt. Auch im game-over-container befindet sich die gleiche struktur um den inhalt zu Centern. Des weiteren enthält der game-over-container noch den "Game Over" Schriftzug, einen link Button um zurück zur settings.html seite gelangen zu können und zwei Platzhalter, einen für die over-description und einen für das Scoreboard, welches ebenfalls durch Javascript eingefügt wird.

Frontend Logik

Die gesamte Logik des Frontends spielt sich in der game.html.hbs datei ab, hier ist das eigentliche Schach Spiel implementiert. Somit ist der Nutzer die meiste Zeit auf der game.html.hbs seite.

Handlebars

Beim Rendern der Handlebars setzt da Backend bereits drei Variablen in das Frontend ein:

Name Nutzen
username Durch das einfügen des Nutzernamens ist sichergestellt, dass das Frontend und Backend den gleichen Nutzernamen führen. Dies ist für die Korrektheit des Scoreboard wichtig.
difficulty Die Schwierigkeit wird im Backend rein durch Zahlen abgebildet. Dass der Nutzer trotzdem auch während des Spiels die Möglichkeit hat, die Schwierigkeit einzustufen, wird ihm im Frontend eine Lesbare Schwierigkeit angezeigt.
color Da wir nach dem Zero-Trust Prinzip vorgehen, wird bei auswahl einer Zufälligen Farbe, eben diese Farbe im Backend generiert. Da die spiel logik des Frontends allerdings auch eine Spieler farbe benötigt, wird diese per handelbar in ein hidden input Feld gerendert, wo sie per javascript später ausgelesen werden kann.

Funktionen

Im Wesentlichen besteht die Logik des Frontend aus den folgenden Funktionen:

Fuktionsname Beschreibung
isPiecePlayerColor (piece) Überprüft mittels REGEX ob die Farbe der Figur (pice) der Farbe des spielers entspricht
onDragStart (dragStartEvt) Wird von Chessboard.js aufgerufen, sobald ein Spieler versucht eine Figur zu bewegen.
1. Zuerst wird die Funktion isPiecePlayerColor() aufgerufen, danach Chess.js konsultiert, ob der Spieler am Zug ist.
2. Nur wenn beide Abfragen erfolgreich sind, darf der Spieler die Figur bewegen.
3. Außerdem werden mithilfe von Chess.js alle legalen züge der Figur generiert.
4. Über die Erlaubten züge wird iteriert und auf jedes Zielfeld eines Zuges ein Kreis gesetzt.
onDrop (dropEvt) Wird von Chessboard.js aufgerufen, sobald ein Spieler versucht eine Figur auf ein neues Feld gezogen und losgelassen hat.
1. Der Zug wird an Chess.js weitergereicht, das Ergebnis des Zuges wird für später gespeichert.
2. Alle Punkte (welche zuvor durch onDragStart gesetzt wurden, werden gelöscht.
3. Nun wird das zuvor gespeicherte Ergebnis von Chess.js überprüft.
4a. Falls der Zug nicht erlaubt war, wird die Figur auf das Ursprüngliche Feld zurückgesetzt.
4b. Falls der Zug erlaubt war, wird das Schachbrett auf die Aktuelle Position gesetzt.
5b. Die Funktion opponentMove(dropEvt.source, dropEvt.target); wird aufgerufen.
6b. Die Funktion checkGameOver();
fetchBoard(src, dest) Schickt die src und dest Felder and das Backend.
checkGameOver() Ist Relativ selbsterklärend, Chess.jswird Konsultiert, ob das Spiel beendet wurde.
Wenn laut Chess.js das Spiel Beendet wurde, wird das Backend konsultiert, ob das Spiel auch im backend beendet ist. Dieses Verfahren ist teil unseres Zero-Trust Prinzips
Falls das Spiel im Backend nicht beendet ist, wird der aktuelle Spielstand mit dem Stand aus dem Backend überschrieben.
Wenn sich das Backend und Chess.js einig sind, wird der "game-over-container" sichtbar gesetzt und sperrt somit das Spielfeld. Außerdem wird der Gewinner in die Beschreibung des Game Over Popup eingefügt.
Bei Jedem Aufruf der checkGameOver() Funktion wird auch die highlightTurn() Funktion aufgerufen.
opponentMove(src, dest) Stellt zuerst sicher, dass wirklich der Gegner am Zug ist. Dann wird:
1. Die Funktion fetchBoard wird aufgerufen, der Zug des Backends mit Chess.js synchronisiert und das Board auf die aktuelle Position gesetzt.
2. Die Funktion checkGameOver() wird ausgeführt.
highlightTurn() Setzt die Deckkraft des Spieler namens, welcher aktuell am Zug ist auf 100% während die Deckkraft des nicht aktiven Spieler namens auf 50% Reduziert wird.
Außerdem wird von Chess.jsberechnet, ob aktuell ein Spieler im Schach steht. Sollte dies der Fall sein, wird das Feld auf dem der Betroffene König steht Rot eingefärbt.
firstMove() Zuerst wird die Funktion highlightTurn() aufgerufen.
Falls die Farbe des Menschlichen Spielers Schwarz ist, wird dann ein erste Zug aus dem Backend angefordert mit dem Aufruf `opponentMove(0, 0).

Ablauf

Ablauf der Frontend Logik, nachdem die Website fertig geladen ist (Dies passiert ohne Interaktion):

  1. Einlesen der color, welche zuvor mit Handlebars gesetzt wurde.
  2. Erstellen einer Instanz von Chess.js in Startaufstellung.
  3. Erstellen des Schachboards mit Chessboard.js, die Ausrichtung wird von der zuvor eingelesenen color angegeben und die Aufstellung aus der Chess.js Instanz übernommen.
  4. Die Funktion firstMove() wird ausgeführt:
    1. Die FunktionhighlightTurn() wird ausgeführt und highlightet im UI den Spieler der aktuell am Zug ist.
    2. Falls der Spieler die Figurenfarbe Schwarz hat, wird die Funktion opponentMove("0", "0") aufgerufen.

Backend

Der Quellcode des Backends befindet sich im src Verzeichnis des Projekts. Der Einstiegspunkt ist hierbei die main.rs mit der Funktion rocket. Die Cargo.toml im Root-Verzeichnis gibt die Abhängigkeiten des Projektes an und wird von cargo (dem Package-Manager von Rust) benötigt.

Verwendete Technologien

Für das Backend der Webanwendung wurde Rust in Kombination mit dem Rocket Framework ausgewählt. Diese Entscheidung resultierte aus den spezifischen Anforderungen des Projekts, in dem die Anwendung äußerst robust sein und eine ausgezeichnete Fehlerbehandlung bieten sollte. Zudem war es wichtig, zu verhindern, dass bei fehlerhaften Serveranfragen Laufzeitfehler entstehen.
Rust, als typensichere Programmiersprache mit hervorragender Fehlerbehandlung, wurde als ideal geeignet betrachtet. Das Rocket Framework überzeugte durch seine einfache Syntax, die eine schnelle Implementierung eines Webservers ermöglicht. Darüber hinaus unterstützt es nativ die asynchrone Anfragenverarbeitung und bietet eine sehr gute Laufzeitperformance. Das Konzept der mountable applications erleichtert die Unterteilung der Anwendung in Komponenten, was die Integration neuer Funktionen, wie zum Beispiel des dynamisches HTML-Rendering, erheblich vereinfacht und entsprechend effizient umsetzt.
Für die Verwaltung und Durchführung des Schachspiels wurde die Shakmaty Bibliothek eingesetzt. Diese Bibliothek übernimmt die gesamte Spielverwaltung und sorgt für eine reibungslose Handhabung. Ein wichtiges Merkmal dieser Bibliothek ist die Kompatibilität mit dem UCI (Universal Chess Interface) und der FEN (Forsyth-Edwards Notation), was eine einfache Kommunikation mit der Stockfish-Instanz und eine minimale und zugleich genormte Darstellung des Schachbrettzustandes ermöglicht.
Für die persistente Speicherung des Scoreboards und das gleichzeitige Durchführen von Änderungen aus einem asynchronen Kontext ohne das Auftreten von race conditions wurde die Rusqlite Bibliothek verwendet. Diese basiert auf SQLite und ermöglicht eine effiziente Fehlerbehandlung, wobei die Vorteile einer SQLite-basierten Datenbank voll zum Tragen kommen.

Routen und Aufbau

Aufbau

Im Rahmen der Instanziierung des Webservers mit der Funktion rocket, werden zunächst mehrere Objekte generiert, die als Zustände in das Rocket Framework übergeben werden. Dabei wird ein SessionHandler erstellt, welcher Game Instanzen speichert und diese einem Client durch später platzierte SessionId-Cookies zuordnet. Zudem wird ein DB Objekt, als Verbindung zur persistenten rusqlite Datenbank, generiert und ebenfalls an das Rocket Framework übergeben.

Routen

Der Webserver unterstützt fünf explizit definierte Routen sowie eine Route für die statische Dateibereitstellung. Die Funktionen der einzelnen Routen werden im Folgenden genauer erläutert:

  1. GET /
    Diese Route wird normalerweise als erste aufgerufen und leitet die Anfrage des Clients permanent an die welcome_page.html weiter.

  2. POST /game
    Diese Route wird üblicherweise über das Formular der settings.html aufgerufen und dient der Erstellung eines Spieles.
    Sie muss folgende Parameter beinhalten:

    • new_session: Option
      Ein optionaler Parameter, der angibt, ob eine möglicherweise laufende Sitzung überschrieben werden soll.
    • username: String
      Der Nutzername des Spielers, der unter anderem für den Scoreboard-Eintrag genutzt wird.
    • difficulty: Integer $\in{1,2,3}$
      Eine Zahl die die vom Spieler gewählte Schwierigkeitsstufe angibt.
    • color: Character $\in {w,b,r}$
      Ein Textzeichen, welches die vom Spieler gewählte Farbe oder $r$ für eine zufällige Farbe angibt.

    Anfangs überprüft die Route mit find_session, ob der Client bereits eine Sitzung hat. Existiert diese und ist der new_session Parameter gesetzt, wird diese gelöscht.
    Dann wird ein neues Spiel mit den vorgegebenen Parametern generiert. Dabei wird über die Game-Instanz unter anderem ein Unterprozess der Stockfish-Instanz erstellt.
    Ist das Spiel erfolgreich erstellt worden, wird anschließend eine neue Sitzung, die mit der Game-Instanz verknüpft ist, erstellt und im SessionHandler gespeichert.
    Schließlich wird die game.html über Handlebars generiert und als Antwort zurückgegeben, wobei wichtige Informationen, wie die Spielerfarbe und der Nutzername, in das HTML eingefügt werden.

  3. POST /move
    Diese Route wird während eines Spiels wiederholt von der game.html aufgerufen, um einen vom Spieler ausgeführten Zug zu validieren und den entsprechenden Gegenzug der Stockfish-Engine zu ermitteln.
    Die Anfrage muss folgenden Body haben:

    • move: String Ein vierstelliger String, der den Zug beschreibt. Die ersten zwei Zeichen bezeichnen das Quellfeld des Zuges, die letzten beiden das Zielfeld

    Zunächst wird überprüft, ob der anfordernde Client überhaupt eine Session besitzt. Ist dies der Fall, wird die zugehörige Spielsitzung geladen und der Zug des Spielers validiert und ausgeführt.
    Sollte der Zug ungültig sein, wird der letzte Schachbrettzustand als FEN mit einem 406-Statuscode zurück an den Client gesendet, um eine Fortsetzung des Spiels mit einem validierten Schachbrett zu ermöglichen.
    Im weiteren Verlauf berechnet und vollzieht die Stockfish-Engine, die der Game-Instanz zugeordnet ist, den Gegenzug. Der neue Schachbrettzustand wird dann als FEN mit einem positiven Statuscode zurückgegeben.

  4. GET /game_end
    Der Aufruf dieser Route erfolgt, wenn der Client auf der game.html vermutet, dass das Spiel beendet ist.
    Die Route überprüft, ob das Spiel tatsächlich beendet ist. Im Fall, dass das Spiel beendet ist, wird ein 200er-Statuscode zurückgegeben. Andernfalls wird mit dem Statuscode 406 der letzte Spielzustand als FEN zurückgesendet.

  5. GET /scoreboard
    Der Aufruf dieser Route erfolgt, sobald ein Spiel beendet ist und das Scoreboard geladen werden soll.
    Sie muss folgenden Parameter beinhalten:

    • count: int
      Ein optionaler Parameter, der die Anzahl der Scoreboard-Einträge, die geladen werden sollen, angibt.

    Diese Route öffnet über den DB Zustand eine Verbindung zur Datenbank und gibt die entsprechenden Einträge, sofern vorhanden, aus. Sie werden als Liste im JSON-Format an den Client übermittelt.

  6. GET /<document_name>
    Der Aufruf dieser Route dient dazu, alle statischen Ressourcen bereitzustellen, die im Ordner static vorliegen. Sie wird über den FileServer instanziiert.
    Über diese Route werden insbesondere die welcome_page.html, die settings.html, die impressum.html sowie die zugehörigen CSS- und JS-Dateien bereitgestellt.