Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.

Link zu der Vergleichsansicht

Beide Seiten, vorherige Überarbeitung Vorherige Überarbeitung
Nächste Überarbeitung
Vorherige Überarbeitung
ksk_ef:web:apps:server [2024-08-11 10:26] – [App definieren] hofksk_ef:web:apps:server [2024-08-11 11:20] (aktuell) hof
Zeile 59: Zeile 59:
 }) })
 </code> </code>
 +
 +### Statische Dateien
 +Die HTML und CSS Dateien bleiben unverändert - der Server soll diese bitte direkt zum Browser übertragen. Das erreichen wir, indem wir einen Ordner `static` anlegen, und die Dateien dorthin verschieben. Node wird instruiert, diese Dateien unverändert auszuliefern:
 +
 +<code javascript app.js>
 +// Serve all files from static/ as is.
 +// For example, a request for '/tictactoe.html' will be served from
 +// 'static/tictactoe.html'
 +app.use(express.static('static'))
 +</code>
 +
 +Ausprobieren: Node neu starten und den URL http://localhost:3000/tictactoe.html laden - unser HTML + CSS sollte korrekt angezeigt werden!
 +
 +### Server-Side TicTacToe
 +
 +Erstelle eine Kopie deiner `tictactoe.js` Datei, um auf dem Server ein Spiel laufen zu lassen. Idealerweise ist deine Datei bereits objektorientiert, damit der Server eine Vielzahl von Spielen parallell betreuen kann. 
 +
 +Was muss sich ändern?
 +  * Der Spielzustand kann nicht mehr im HTML gespeichert werden. Es bietet sich an, ein Array von Buchstaben zu verwenden `let grid = Array(9).fill(' ')`.
 +  * Der Spielzustand muss als JSON vom Server zu den Clients gesendet werden. Schreibe eine Funktion `toJson()`, die Code ähnlich wie den folgenden produziert:
 +
 +<code javascript>
 +{
 +  id: 0,           // game id
 +  state: "ended",  // "waiting", "playing", "ended"
 +  grid: [          // the nine game grid cells
 +    "X",
 +    " ",
 +    " ",
 +    "X",
 +    "O",
 +    " ",
 +    "X",
 +    "O",
 +    " "
 +  ],
 +  winner: "X",     // the player who won, if there is one.
 +  player: "X",     // the user's color
 +  next: "O"        // the color of the player next in turn
 +}
 +</code>
 +
 +In `app.js` erstellen wir ein Spiel, z.B. mit `let game = TicTacToe()`. Damit das funktioniert, müssen wir alle Funktionen oder Klassen in `tictactoe.js` mit `export` markieren und in `app.js` importieren:
 +
 +<code javascript tictactoe.js>
 +export class TicTacToe {
 +  ...
 +}
 +</code>
 +
 +<code javascript app.js>
 +import {TicTacToe} from './tictactoe.js'
 +
 +let the_game = new TicTacToe();
 +
 +app.get('/game/:gameid', (req, res) => {
 +    return the_game.toJson();
 +})
 +
 +app.get('/set/:gameid/:cell', (req, res) => {
 +    the_game.set(req.params['cell'])
 +    res.json(the_game.toJson())
 +})
 +</code>
 +
 +Nun sollte unser Server ein einziges Game betreiben. Der Zustand sollte unter http://localhost:3000/game/0 zurückgeliefert werden. Ein Spielzug wird über das `set` API gemacht, z.B. sollte das mittlere Feld mit http://localhost:3000/set/0/4 gesetzt werden können. Kannst du (ganz ohne Grafik, nur mit den obigen URLs) ein Spiel spielen?
 +
 +Was gibt es noch zu lösen?
 +  * Der Browser benötigt auch Javascript. Der Click-Handler jedes Buttons soll das `set` API aufrufen und anschliessend den Inhalt des HTMLs an den Spielzustand anpassen.
 +  * Wie weiss der Browser, ob er `X` oder `O` ist?
 +  * Wie können mehrere Spiele betreut werden (Hinweis: schau dir die `gameid` oben an...!)
 +
 +### Client-Side Javascript
 +Der Browser benötigt auch Javascript. Der Click-Handler jedes Buttons soll das `set` API aufrufen und anschliessend den Inhalt des HTMLs an den Spielzustand anpassen.
 +
 +<code javascript tictactoe_client.js>
 +class Tictactoe_Client {
 +   game_id = 0;  // TODO set when joining game
 +   /**
 +     * Fetches the game state, updates the UI, and installs click handlers.
 +     */
 +    async init() {
 +        let i = 0;
 +        for (const button of this.view.grid.getElementsByTagName("button")) {
 +            const cell = i;
 +            button.addEventListener('click', () => {
 +                this.handleJsonUrl(`/set/${this.game_id}/${cell}`);
 +            });
 +            i++;
 +        }
 +    }
 +
 +    /**
 +     * Fetches the given URL and updates the internal state from the parsed JSON.
 +     
 +     * Keeps polling for updates if the updated state could change remotely.
 +     
 +     * @param {string} url the URL to fetch that will return tictactoe JSON.
 +     */
 +    async handleJsonUrl(url) {
 +        let response = await fetch(url);
 +        if (!response.ok) {
 +            let error =  await response.text();
 +            console.log("Error: " + error);
 +            return;
 +        }
 +        const json = await response.json();
 +        this.updateHtml(json);
 +    }
 +    
 +    /** Update the HTML based on JSON game state. */
 +    updateHtml(json) {
 +        let i = 0;  
 +
 +        for (const button of this.grid.getElementsByTagName("button")) {
 +            const cellText = json.grid[i];
 +            // Set the data-state attribute which drives CSS formatting.
 +            button.setAttribute('data-state', cellText);
 +            // Set the text contents of the cell.
 +            button.textContent = cellText;
 +            i++;
 +        }
 +    }
 +}
 +</code>
 +
 +### Sicherheit, User Ids & Cookies
 +Um sicherzustellen, dass jede:r Benutzer:in nur in einem Spiel ist, und kein böswilliger Akteur einen falschen Spielzug ausführen kann, wollen wir eine Benutzer-Id für jede Verbindung erstellen. Dafür nützen wir Cookies. Das Cookie wird gesetzt wenn zum ersten Mal einem Spiel beigetreten wird:
 +
 +<code javascript app.js>
 +import express from 'express'
 +import cookieParser from 'cookie-parser'
 +import {TicTacToe, GameState} from './tictactoe.js'
 +
 +const app = express()
 +app.use(cookieParser())
 +const port = 3000
 +
 +function getUserId(req, res) {
 +    let userid = req.cookies.userid
 +    if (userid == undefined) {
 +        userid = crypto.randomUUID()
 +        res.cookie('userid', userid)
 +    }
 +    return userid
 +}
 +
 +app.get('/join', (req, res) => {
 +    const userid = getUserId(req, res)
 +    for (let game of Object.values(games)) {
 +        if (game.isWaiting()) {
 +            game.join(userid)
 +            res.json(game.toJson(userid))
 +            return
 +        }
 +    }
 +    // No waiting game found - create a new one
 +    let game = new TicTacToe(nextGameId)
 +    games[nextGameId] = game
 +    nextGameId += 1
 +    
 +    game.join(userid)
 +    res.json(game.toJson(userid))
 +})
 +</code>
 +
 +### Hinweise
 +
 +  * Die ganze Web-App mit JS & Node: https://github.com/tkilla77/ksr_tictactoe/tree/server_side_js
 +  * Alternative mit Python & Flask auf der Serverseite: https://github.com/tkilla77/ksr_tictactoe/tree/security
  • ksk_ef/web/apps/server.1723371960.txt.gz
  • Zuletzt geändert: 2024-08-11 10:26
  • von hof