]> git.cworth.org Git - empires-server/commitdiff
empires: Implement support for spectators
authorCarl Worth <cworth@cworth.org>
Sun, 24 May 2020 16:19:50 +0000 (09:19 -0700)
committerCarl Worth <cworth@cworth.org>
Sun, 24 May 2020 20:48:51 +0000 (13:48 -0700)
These are much like players, but without a selected character, and
they are displayed separately in the game UI.

This brings us up to version 0.7 of the empires protocol in
lmno-api:empires.txt.

empires.js
templates/empires-game.html

index e6b7e77979736a6269a27250899e3420c89fe178..19c57d7dba2b3cc11d4c84c56c4898dbaea6d50f 100644 (file)
@@ -44,9 +44,11 @@ function shuffle(a) {
 
 class Game {
   constructor() {
+    this._spectators = [];
+    this.next_spectator_id = 1;
     this._players = [];
-    this.characters_to_reveal = null;
     this.next_player_id = 1;
+    this.characters_to_reveal = null;
     this.clients = [];
     this.next_client_id = 1;
     this.state = GameState.JOIN;
@@ -55,6 +57,31 @@ class Game {
     setInterval(() => {this.broadcast_string(":");}, 15000);
   }
 
+  add_spectator(name, session_id) {
+    /* Don't add another spectator that matches an existing session. */
+    const existing = this._spectators.findIndex(
+      spectator => spectator.session_id === session_id);
+    if (existing >= 0)
+      return existing.id;
+
+    const new_spectator = {id: this.next_spectator_id,
+                           name: name,
+                           session_id: session_id
+                          };
+    this._spectators.push(new_spectator);
+    this.next_spectator_id++;
+    this.broadcast_event("spectator-join", JSON.stringify(new_spectator));
+
+    return new_spectator.id;
+  }
+
+  remove_spectator(id) {
+    const index = this._spectators.findIndex(spectator => spectator.id === id);
+    this._spectators.splice(index, 1);
+
+    this.broadcast_event("spectator-leave", `{"id": ${id}}`);
+  }
+
   add_player(name, character) {
     const new_player = {id: this.next_player_id,
                        name: name,
@@ -84,6 +111,7 @@ class Game {
 
     this.change_state(GameState.JOIN);
 
+    this.broadcast_event("spectators", "{}");
     this.broadcast_event("players", "{}");
   }
 
@@ -155,6 +183,14 @@ class Game {
     return this._players.map(player => ({id: player.id, captures: player.captures}));
   }
 
+  get spectators() {
+    /* We return only "id" and "name" here (specifically not session_id!). */
+    return this._spectators.map(spectator => ({
+      id: spectator.id,
+      name: spectator.name
+    }));
+  }
+
   get players() {
     return this._players.map(player => ({id: player.id, name: player.name }));
   }
@@ -227,7 +263,13 @@ function handle_events(request, response) {
   response.writeHead(200, headers);
 
   /* Now that a client has connected, first we need to stream all of
-   * the existing players (if any). */
+   * the existing spectators and players (if any). */
+  if (game._spectators.length > 0) {
+    const spectators_json = JSON.stringify(game.spectators);
+    const spectators_data = `event: spectators\ndata: ${spectators_json}\n\n`;
+    response.write(spectators_data);
+  }
+
   if (game._players.length > 0) {
     const players_json = JSON.stringify(game.players);
     const players_data = `event: players\ndata: ${players_json}\n\n`;
@@ -262,6 +304,24 @@ app.get('/', (request, response) => {
     response.render('empires-game.html');
 });
 
+app.post('/spectator', (request, response) => {
+  const game = request.game;
+  var name = request.session.nickname;
+
+  /* If the request includes a name, that overrides the session nickname. */
+  if (request.body.name)
+    name = request.body.name;
+
+  const id = game.add_spectator(name, request.session.id);
+  response.send(JSON.stringify(id));
+});
+
+app.delete('/spectator/:id', (request, response) => {
+  const game = request.game;
+  game.remove_spectator(parseInt(request.params.id));
+  response.send();
+});
+
 app.post('/register', (request, response) => {
   const game = request.game;
   var name = request.session.nickname;;
@@ -326,6 +386,11 @@ app.get('/empires', (request, response) => {
   response.send(game.empires);
 });
 
+app.get('/spectators', (request, response) => {
+  const game = request.game;
+  response.send(game.spectators);
+});
+
 app.get('/players', (request, response) => {
   const game = request.game;
   response.send(game.players);
index a3175e5ae254dc17db512f403bff9b618c841a72..9c860ca60d9d303b8baaf8addb5b8d17ec915212 100644 (file)
   <span id="character-reveal"></span>
 </div>
 
+<div class="hide-state-reveal" id="spectators-div">
+  <h1>Spectators</h1>
+
+  <ul id="spectators">
+  </ul>
+</div>
+
 <div class="hide-state-reveal" id="players-div">
-  <h1>Players in the game</h1>
+  <h1>Players</h1>
 
   <ul id="players">
   </ul>
 </div>
 {% endblock %}
-