Inhaltsverzeichnis

Dynamische JSON-APIs

Mit unserem Wissen über Web-APIs und JSON wollen wir nun die Vier-Gewinnt Web-App weiterentwickeln, so dass zwei Spieler:innen übers Web gegeneinander spielen können.

Dazu muss Folgendes geschehen:

Dynamische Routes

Wir haben express bereits so konfiguriert, dass er unsere bestehenden Dateien statisch ausliefert. Das bedeutet, dass die Dateien 1:1 so zum Client übertragen werden, so wie sie der Server in seinem static Ordner antrifft.

Im Gegensatz zu statischen Dateien wird der Inhalt eines dynamischen Pfads (Route) erst zur Laufzeit bestimmt. Wir schreiben also Javascript-Code, um den Inhalt der HTTP-Response zusammenzusetzen. Für unsere Routes bietet sich an, JSON als Austauschformat zu verwenden.

Wir schauen nochmals in unsere index.js Datei und fügen eine JSON-Route hinzu:

index.js
import express from 'express'
 
const app = express()
const port = 3001
 
app.get('/get_info', (req, res) => {
  // ein JS Object (wie ein Dictionary in Python)
  let content = {
    message: 'Hello',
    name: 'World'
  }
  // Response verwendet JSON
  res.json(content)
})
 
app.use('/:gameid/', express.static('static'))
 
// Listen on the given port
const server = app.listen(port, () => {
    console.log(`Example app listening on http://localhost:${server.address().port}/`)
})

Mit dem Kommandozeilenaufruf node index.js wird der Node-Server gestartet. Auf der Adresse http://localhost:3001/get_info können wir auf die oben definierte API zugreifen. Natürlich kann der Server den zurückgegebenen Inhalt beliebig verändern.

Server-Side Vier-Gewinnt

Ab jetzt soll der Server das Spiel verwalten. Die Clients können über den Aufruf der gewünschten URLs den Spielzustand verändern. Danach erhalten sie den neuen Zustand im JSON-Format und stellen ihn im HTML dar.

Dazu ist es nötig, unsere bisherige Spiel-JS-Datei (in static) in zwei Teile aufzuteilen:

Erstelle eine Kopie deiner static/connect4.js Datei als connect4_server.js im gleichen Verzeichnis wie index.js.

Was muss sich ändern?

In index.js erstellen wir ein Spiel, z.B. mit let game = newGame(). Damit das funktioniert, müssen wir alle Funktionen oder Klassen in connect4_server.js mit export markieren und in app.js importieren (newGame und dropPiece):

connect4_server.js
/* Returns a fresh game. */
export function newGame(id = -1) {
  return {
    "id": id,
    "state": "waiting",  // or "waiting" or "won" or "tie"
    "board": [
      0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0,
    ],
    "next": 1,  // 1 or 2, the player whose turn it is, the winner if state is "won"
    "player1": undefined,
    "player2": undefined,
  }
}
 
/* Drops a piece into given column. */
export function dropPiece(game, column) {
  ...
}
index.js
import {newGame, dropPiece} from './connect4.js'
 
// FIXME: support multiple games in the future!
let the_game = newGame()
 
app.get('/:gameid/game', (req, res) => {
    // FIXME: find the game for the given gameid.
    res.json(the_game)
})
 
app.get('/:gameid/set/:column', (req, res) => {
    let column = parseInt(req.params['cell'])
    dropPiece(the_game, column)
    res.json(the_game)
})

Nun sollte unser Server ein einziges Game betreiben. Der Zustand sollte unter http://localhost:3001/0/game zurückgeliefert werden. Ein Spielzug wird über das set API gemacht, z.B. sollte die mittlere Spalte mit http://localhost:3001/0/set/4 gesetzt werden können. Kannst du (ganz ohne Grafik, nur mit den obigen URLs) ein Spiel spielen?

Mehrere Games

Wir möchten mehrere Games unterstützen. Statt the_game unterhalten wir ein Dictionary von gameid auf den zugehörigen Gamestate:

index.js
/** All games by gameid. */
let games = {}
 
app.get('/:gameid/game', (req, res) => {
    const gameid = parseInt(req.params['gameid'])
    let game = games[gameid]
    if (game == undefined) {
        // Create a new game if it does not exist.
        game = newGame(gameid)
        games[gameid] = game
    }
    res.json(game)
})
 
app.get('/:gameid/set/:column', (req, res) => {
    const game = games[parseInt(req.params['gameid'])]
    if (game == undefined) {
        // Send error code
        res.status(404).json("no such game")
    } else {
        let column = parseInt(req.params['column'])
        dropPiece(game, column)
        res.json(game)
    }
})

 Weiter

Was gibt es noch zu lösen?

Weiter gehts mit Client-Side Javascript!