X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=game.js;h=f1f764fa1968425d597e4e2ab47e4b8d916650a8;hb=f27cdf8b57b41a91dd7ab67be407d803dcaad1a3;hp=028c0393f9be02ac027922fa6b9244d70380608e;hpb=de78893aec1d5a4a0ae84610f2f01f78e9455f18;p=empires-server diff --git a/game.js b/game.js index 028c039..f1f764f 100644 --- a/game.js +++ b/game.js @@ -1,8 +1,87 @@ /* Base class providing common code for game engine implementations. */ class Game { - constructor(name) { - this.name = name; + constructor(id) { + this.id = id; + this.clients = []; + this.next_client_id = 1; } + + /* Suport for game meta-data. + * + * What we want here is an effectively static field that is + * accessible through either the class name (SomeGame.meta) or an + * instance (some_game.meta). To pull this off we do keep two copies + * of the data. But the game classes can just set SomeGame.meta once + * and then reference it either way. + */ + static set meta(data) { + /* This allows class access (SomeGame.meta) via the get method below. */ + this._meta = data; + + /* While this allows access via an instance (some_game.meta). */ + this.prototype.meta = data; + } + + static get meta() { + return this._meta; + } + + 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`); + } + + 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); + + /* Add this new client to our list of clients. */ + const id = this.add_client(response); + + /* And queue up cleanup to be triggered on client close. */ + request.on('close', () => { + this.remove_client(id); + }); + + /* Finally, if this game class has a "state" property, stream that + * current state to the client. */ + if (this.state) { + const state_json = JSON.stringify(this.state); + response.write(`event: game-state\ndata: ${state_json}\n\n`); + } + } + + broadcast_move(move) { + this.broadcast_event("move", move); + } + } module.exports = Game;