X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=server.js;h=7ea999aa71372a6be41c927e24cd4542b5a6eb9d;hb=1e652af1e541675c021201743bfe1873f591944e;hp=4957f321f1f64628fcffa0800401a296e4e08504;hpb=c9c80e294186a8b2ef742806559a0655d624c05f;p=lmno-server diff --git a/server.js b/server.js index 4957f32..7ea999a 100644 --- a/server.js +++ b/server.js @@ -1,40 +1,188 @@ -const express = require("express") +const express = require("express"); +const cors = require("cors"); const body_parser = require("body-parser"); const app = express(); +app.use(cors()); class Game { constructor() { - this.players = []; + this._players = []; this.next_player_id = 1; + this.clients = []; + this.next_client_id = 1; + + /* Send a comment to every connected client every 15 seconds. */ + setInterval(() => {this.broadcast_string(":");}, 15000); } add_player(name, character) { - this.players.push({id: this.next_player_id, + const new_player = {id: this.next_player_id, name: name, - character: character}) + character: character, + captures: [], + }; + this._players.push(new_player); this.next_player_id++; + /* The syntax here is using an anonymous function to create a new + object from new_player with just the subset of fields that we + want. */ + const player_string = JSON.stringify((({id, name}) => ({id, name}))(new_player)); + this.broadcast_event("player-register", player_string); + } + + remove_player(id) { + const index = this._players.findIndex(player => player.id === id); + this._players.splice(index, 1); + + this.broadcast_event("player-deregister", `{"id": ${id}}`); + } + + remove_all_players() { + this._players = []; + this.next_player_id = 1; + } + + capture(captor_id, captee_id) { + /* TODO: Fix to fail on already-captured players (or to move the + * captured player from an old captor to a new—need to clarify in + * the API specification which we want here. */ + let captor = this._players.find(player => player.id === captor_id); + captor.captures.push(captee_id); + + this.broadcast_event("capture", `{"captor": ${captor_id}, "captee": ${captee_id}}`); + } + + liberate(captee_id) { + let captor = this._players.find(player => player.captures.includes(captee_id)); + captor.captures.splice(captor.captures.indexOf(captee_id), 1); + } + + restart() { + for (const player of this._players) { + player.captures = []; + } + } + + get characters() { + return this._players.map(player => player.character); + } + + get empires() { + return this._players.map(player => ({id: player.id, captures: player.captures})); + } + + get players() { + return this._players.map(player => ({id: player.id, name: player.name })); + } + + 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`); } } -game = new Game(); +const game = new Game(); app.use(body_parser.urlencoded({ extended: false })); app.use(body_parser.json()); -app.get('/', function (requses, response) { +function handle_events(request, response) { + /* 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); + + /* Now that a client has connected, first we need to stream all of + * the existing players (if any). */ + if (game._players.length > 0) { + const players_json = JSON.stringify(game.players); + const players_data = `event: players\ndata: ${players_json}\n\n`; + response.write(players_data); + } + + /* 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); + }); +} + +app.get('/', (request, response) => { response.send('Hello World!'); }); -app.get('/players', function (request, response) { - response.send(game.players); +app.post('/register', (request, response) => { + game.add_player(request.body.name, request.body.character); + response.send(); }); -app.post('/register', function (request, response) { - game.add_player(request.body.name, request.body.character); +app.post('/deregister/:id', (request, response) => { + game.remove_player(parseInt(request.params.id)); response.send(); }); +app.post('/reset', (request, response) => { + game.remove_all_players(); + response.send(); +}); + +app.post('/capture/:captor/:captee', (request, response) => { + game.capture(parseInt(request.params.captor), parseInt(request.params.captee)); + response.send(); +}); + +app.post('/liberate/:id', (request, response) => { + game.liberate(parseInt(request.params.id)); + response.send(); +}); + +app.post('/restart', (request, response) => { + game.restart(parseInt(request.params.id)); + response.send(); +}); + +app.get('/characters', (request, response) => { + response.send(game.characters); +}); + +app.get('/empires', (request, response) => { + response.send(game.empires); +}); + +app.get('/players', (request, response) => { + response.send(game.players); +}); + +app.get('/events', handle_events); + app.listen(3000, function () { - console.log('Example app listening on port 3000!'); + console.log('Empires server listening on localhost:3000'); });