Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.
Beide Seiten, vorherige Überarbeitung Vorherige Überarbeitung Nächste Überarbeitung | Vorherige Überarbeitung | ||
ksk_ef:web:apps:server [2024-08-11 10:48] – [Server-Side TicTacToe] hof | ksk_ef:web:apps:server [2024-08-11 11:20] (aktuell) – hof | ||
---|---|---|---|
Zeile 59: | Zeile 59: | ||
}) | }) | ||
</ | </ | ||
+ | |||
+ | ### 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 '/ | ||
+ | // ' | ||
+ | app.use(express.static(' | ||
+ | </ | ||
+ | |||
+ | Ausprobieren: | ||
+ | |||
### Server-Side TicTacToe | ### Server-Side TicTacToe | ||
Zeile 87: | Zeile 100: | ||
} | } | ||
</ | </ | ||
+ | |||
+ | In `app.js` erstellen wir ein Spiel, z.B. mit `let game = TicTacToe()`. Damit das funktioniert, | ||
+ | |||
+ | <code javascript tictactoe.js> | ||
+ | export class TicTacToe { | ||
+ | ... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <code javascript app.js> | ||
+ | import {TicTacToe} from ' | ||
+ | |||
+ | let the_game = new TicTacToe(); | ||
+ | |||
+ | app.get('/ | ||
+ | return the_game.toJson(); | ||
+ | }) | ||
+ | |||
+ | app.get('/ | ||
+ | the_game.set(req.params[' | ||
+ | res.json(the_game.toJson()) | ||
+ | }) | ||
+ | </ | ||
+ | |||
+ | Nun sollte unser Server ein einziges Game betreiben. Der Zustand sollte unter http:// | ||
+ | |||
+ | 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 { | ||
+ | | ||
+ | /** | ||
+ | * Fetches the game state, updates the UI, and installs click handlers. | ||
+ | */ | ||
+ | async init() { | ||
+ | let i = 0; | ||
+ | for (const button of this.view.grid.getElementsByTagName(" | ||
+ | const cell = i; | ||
+ | button.addEventListener(' | ||
+ | this.handleJsonUrl(`/ | ||
+ | }); | ||
+ | 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(" | ||
+ | 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(" | ||
+ | const cellText = json.grid[i]; | ||
+ | // Set the data-state attribute which drives CSS formatting. | ||
+ | button.setAttribute(' | ||
+ | // Set the text contents of the cell. | ||
+ | button.textContent = cellText; | ||
+ | i++; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ### Sicherheit, User Ids & Cookies | ||
+ | Um sicherzustellen, | ||
+ | |||
+ | <code javascript app.js> | ||
+ | import express from ' | ||
+ | import cookieParser from ' | ||
+ | import {TicTacToe, GameState} from ' | ||
+ | |||
+ | const app = express() | ||
+ | app.use(cookieParser()) | ||
+ | const port = 3000 | ||
+ | |||
+ | function getUserId(req, | ||
+ | let userid = req.cookies.userid | ||
+ | if (userid == undefined) { | ||
+ | userid = crypto.randomUUID() | ||
+ | res.cookie(' | ||
+ | } | ||
+ | return userid | ||
+ | } | ||
+ | |||
+ | app.get('/ | ||
+ | const userid = getUserId(req, | ||
+ | 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)) | ||
+ | }) | ||
+ | </ | ||
+ | |||
+ | ### Hinweise | ||
+ | |||
+ | * Die ganze Web-App mit JS & Node: https:// | ||
+ | * Alternative mit Python & Flask auf der Serverseite: |