X-Git-Url: https://git.cworth.org/git?p=empires-server;a=blobdiff_plain;f=tictactoe.js;h=82c5f7e839d770cb4b4bb33f6f040ae9f079c6aa;hp=18fe3428806bee30c2c533182b15e02fee36993b;hb=90941051a42263fed5bc16d0624510e4bd171810;hpb=ab53a3a530ad30db6cf52b8507fa1cbddb28eecf diff --git a/tictactoe.js b/tictactoe.js index 18fe342..82c5f7e 100644 --- a/tictactoe.js +++ b/tictactoe.js @@ -14,6 +14,58 @@ nunjucks.configure("templates", { express: app }); +class TicTacToe { + constructor() { + this.moves = []; + this.board = Array(9).fill(null); + this.clients = []; + this.next_client_id = 1; + } + + /* Returns Boolean indicating whether move was legal and added. */ + add_move(square) { + /* Cannot move to an occupied square. */ + if (this.board[square]) + return false; + + this.board[square] = 'X'; + this.moves.push(square); + + return true; + } + + add_client(response) { + const id = this.next_client_id; + this.clients.push({id: id, + response: response}); + this.next_client_id++; + + return id; + } + + remove_client(id) { + this.clients = this.clients.filter(client => client.id !== id); + } + + /* Send a string to all clients */ + broadcast_string(str) { + this.clients.forEach(client => client.response.write(str + '\n')); + } + + /* Send an event to all clients. + * + * An event has both a declared type and a separate data block. + * It also ends with two newlines (to mark the end of the event). + */ + broadcast_event(type, data) { + this.broadcast_string(`event: ${type}\ndata: ${data}\n`); + } + + broadcast_move(square) { + this.broadcast_event("move", square); + } +} + app.get('/', (request, response) => { const game = request.game; @@ -23,4 +75,42 @@ app.get('/', (request, response) => { response.render('tictactoe-game.html'); }); +app.post('/move', (request, response) => { + const game = request.game; + const square = request.body.square; + + const legal = game.add_move(square); + + /* Inform this client whether the move was legal. */ + response.send(JSON.stringify(legal)); + + /* And only if legal, inform all clients. */ + if (! legal) + return; + + game.broadcast_move(square); +}); + +app.get('/events', (request, response) => { + const game = request.game; + + /* These headers will keep the connection open so we can stream events. */ + const headers = { + "Content-type": "text/event-stream", + "Connection": "keep-alive", + "Cache-Control": "no-cache" + }; + response.writeHead(200, headers); + + /* Add this new client to our list of clients. */ + const id = game.add_client(response); + + /* And queue up cleanup to be triggered on client close. */ + request.on('close', () => { + game.remove_client(id); + }); +}); + exports.app = app; +exports.name = "tictactoe"; +exports.Game = TicTacToe;