]> git.cworth.org Git - empires-server/commitdiff
Implement the /reveal,/start endpoints as well as the "game-state" event
authorCarl Worth <cworth@cworth.org>
Sun, 10 May 2020 22:45:46 +0000 (15:45 -0700)
committerCarl Worth <cworth@cworth.org>
Sun, 10 May 2020 22:45:46 +0000 (15:45 -0700)
This brings us up to protocol version 0.5 and should be a pretty
playable server at this point. Upon receiving the "/reveal" API
request the server will transition to the "reveal" game-state and from
there will broadcast the names of each character to all clients, one
name every three seconds, (and sending an empty name at the end to
clear off the last name). The "/reveal" API request can be sent
additional times if the players want to see the names again.

server.js

index ca7b844c5296b893ca114bc68433b8dd299a0ea5..b7a99a594267232fc2fcd457517dc2e914fc66f2 100644 (file)
--- a/server.js
+++ b/server.js
@@ -5,12 +5,42 @@ const body_parser = require("body-parser");
 const app = express();
 app.use(cors());
 
+const GameState = {
+  JOIN:    1,
+  REVEAL:  2,
+  CAPTURE: 3,
+  properties: {
+    1: {name: "join"},
+    2: {name: "reveal"},
+    3: {name: "capture"}
+  }
+};
+
+/**
+ * Shuffles array in place.
+ * @param {Array} a items An array containing the items.
+ */
+function shuffle(a) {
+  if (a === undefined)
+    return;
+
+  var j, x, i;
+  for (i = a.length - 1; i > 0; i--) {
+    j = Math.floor(Math.random() * (i + 1));
+    x = a[i];
+    a[i] = a[j];
+    a[j] = x;
+  }
+}
+
 class Game {
   constructor() {
     this._players = [];
+    this.characters_to_reveal = null;
     this.next_player_id = 1;
     this.clients = [];
     this.next_client_id = 1;
+    this.state = GameState.JOIN;
 
     /* Send a comment to every connected client every 15 seconds. */
     setInterval(() => {this.broadcast_string(":");}, 15000);
@@ -43,6 +73,39 @@ class Game {
     this.next_player_id = 1;
   }
 
+  reveal_next() {
+    if (this.reveal_index >= this.characters_to_reveal.length) {
+      clearInterval(this.reveal_interval);
+      this.broadcast_event("character-reveal", '{"character":""}');
+      return;
+    }
+    const character = this.characters_to_reveal[this.reveal_index];
+    this.reveal_index++;
+    const character_data = JSON.stringify({"character":character});
+    this.broadcast_event("character-reveal", character_data);
+  }
+
+  reveal() {
+    this.change_state(GameState.REVEAL);
+
+    if (this.characters_to_reveal === null) {
+      this.characters_to_reveal = [];
+      this.characters_to_reveal = this._players.reduce((characters, player) => {
+        characters.push(player.character);
+        return characters;
+      }, []);
+      shuffle(this.characters_to_reveal);
+    }
+
+    this.reveal_index = 0;
+
+    this.reveal_interval = setInterval(this.reveal_next.bind(this), 3000);
+  }
+
+  start() {
+    this.change_state(GameState.CAPTURE);
+  }
+
   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
@@ -102,6 +165,34 @@ class Game {
   broadcast_event(type, data) {
     this.broadcast_string(`event: ${type}\ndata: ${data}\n`);
   }
+
+  game_state_event_data() {
+    var old_state_name;
+    if (this.old_state)
+      old_state_name = GameState.properties[this.old_state].name;
+    else
+      old_state_name = "none";
+    const new_state_name = GameState.properties[this.state].name;
+
+    return `{"old_state":"${old_state_name}","new_state":"${new_state_name}"}`;
+  }
+
+  /* Inform clients about a state change. */
+  broadcast_state_change() {
+    this.broadcast_event("game-state", this.game_state_event_data());
+  }
+
+  /* Change game state and broadcast the change to all clients. */
+  change_state(state) {
+    /* Do nothing if there is no actual change happening. */
+    if (state === this.state)
+      return;
+
+    this.old_state = this.state;
+    this.state = state;
+
+    this.broadcast_state_change();
+  }
 }
 
 const game = new Game();
@@ -126,6 +217,9 @@ function handle_events(request, response) {
     response.write(players_data);
   }
 
+  /* And we need to inform the client of the current game state. */
+  response.write("event: game-state\n" + "data: " + game.game_state_event_data() + "\n\n");
+
   /* Add this new client to our list of clients. */
   const id = game.add_client(response);
 
@@ -149,6 +243,16 @@ app.post('/deregister/:id', (request, response) => {
   response.send();
 });
 
+app.post('/reveal', (request, response) => {
+  game.reveal();
+  response.send();
+});
+
+app.post('/start', (request, response) => {
+  game.start();
+  response.send();
+});
+
 app.post('/reset', (request, response) => {
   game.remove_all_players();
   response.send();