]> git.cworth.org Git - empires-server/blob - tictactoe.js
Use an express Router for each of the game-engine-specific sub-apps
[empires-server] / tictactoe.js
1 const express = require("express");
2
3 const app = express.Router();
4
5 class TicTacToe {
6   constructor() {
7     this.moves = [];
8     this.board = Array(9).fill(null);
9     this.clients = [];
10     this.next_client_id = 1;
11   }
12
13   /* Returns Boolean indicating whether move was legal and added. */
14   add_move(square) {
15     /* Cannot move to an occupied square. */
16     if (this.board[square])
17       return false;
18
19     this.board[square] = 'X';
20     this.moves.push(square);
21
22     return true;
23   }
24
25   add_client(response) {
26     const id = this.next_client_id;
27     this.clients.push({id: id,
28                        response: response});
29     this.next_client_id++;
30
31     return id;
32   }
33
34   remove_client(id) {
35     this.clients = this.clients.filter(client => client.id !== id);
36   }
37
38   /* Send a string to all clients */
39   broadcast_string(str) {
40     this.clients.forEach(client => client.response.write(str + '\n'));
41   }
42
43   /* Send an event to all clients.
44    *
45    * An event has both a declared type and a separate data block.
46    * It also ends with two newlines (to mark the end of the event).
47    */
48   broadcast_event(type, data) {
49     this.broadcast_string(`event: ${type}\ndata: ${data}\n`);
50   }
51
52   broadcast_move(square) {
53     this.broadcast_event("move", square);
54   }
55 }
56
57 app.get('/', (request, response) => {
58   const game = request.game;
59
60   if (! request.session.nickname)
61     response.render('choose-nickname.html', { game_name: "Tic Tac Toe" });
62   else
63     response.render('tictactoe-game.html');
64 });
65
66 app.post('/move', (request, response) => {
67   const game = request.game;
68   const square = request.body.square;
69
70   const legal = game.add_move(square);
71
72   /* Inform this client whether the move was legal. */
73   response.send(JSON.stringify(legal));
74
75   /* And only if legal, inform all clients. */
76   if (! legal)
77     return;
78
79   game.broadcast_move(square);
80 });
81
82 app.get('/events', (request, response) => {
83   const game = request.game;
84
85   /* These headers will keep the connection open so we can stream events. */
86   const headers = {
87     "Content-type": "text/event-stream",
88     "Connection": "keep-alive",
89     "Cache-Control": "no-cache"
90   };
91   response.writeHead(200, headers);
92
93   /* Add this new client to our list of clients. */
94   const id = game.add_client(response);
95
96   /* And queue up cleanup to be triggered on client close. */
97   request.on('close', () => {
98     game.remove_client(id);
99   });
100 });
101
102 exports.app = app;
103 exports.name = "tictactoe";
104 exports.Game = TicTacToe;