]> git.cworth.org Git - empires-server/commitdiff
game: Allow either player to play the first move
authorCarl Worth <cworth@cworth.org>
Fri, 5 Jun 2020 23:18:57 +0000 (16:18 -0700)
committerCarl Worth <cworth@cworth.org>
Fri, 5 Jun 2020 23:18:57 +0000 (16:18 -0700)
When players originally join the game they do not belong on any
team. And while we could require the players to divide themselves up
and join separate teams, it's much easier for them if they aren't
required to coordinate on that.

So, with this commit we allow any player, (not yet in a team), to make
the first move. At that point, we assign that player the appropriate
team for playing first, and assign at least one player (if there are
others in the game) to each successive team value in the order they
originally joined the game.

This means that in the common case of a 2-player game, if both players
are in the game, and one player makes a move, then the two players
will be assigned to separate teams.

game.js
lmno.js
test

diff --git a/game.js b/game.js
index 72c3c7f16fd1c8fd0b24b96935ad588228cdfbf0..24e36adbc4106db5df835ddcfcc3ea403f575988 100644 (file)
--- a/game.js
+++ b/game.js
@@ -50,6 +50,7 @@ class Game {
     this.state = {
       team_to_play: ""
     };
+    this.first_move = true;
 
     /* Send a comment to every connected client every 15 seconds. */
     setInterval(() => {this.broadcast_string(":");}, 15000);
@@ -80,23 +81,71 @@ class Game {
    * to override this (and call super.add_move early!) to implement
    * the actual logic for a move. */
   add_move(player, move) {
-    /* Cannot move if you are not on a team. */
-    if (player.team === "")
-    {
-      return { legal: false,
-               message: "You must be on a team to take a turn" };
-    }
 
-    /* Cannot move if it's not this player's team's turn. */
-    if (player.team !== this.state.team_to_play)
-    {
-      return { legal: false,
-               message: "It's not your turn to move" };
+    /* The checks here don't apply on the first move. */
+    if (! this.first_move) {
+
+      /* Discard any move asserting to be the first move if it's no
+       * longer the first move. This resolves the race condition if
+       * multiple players attempt to make the first move. */
+      if (move.assert_first_move) {
+        return { legal: false,
+                 message: "Your opponent beat you to the first move" };
+      }
+
+      /* Cannot move if you are not on a team. */
+      if (player.team === "")
+      {
+        return { legal: false,
+                 message: "You must be on a team to take a turn" };
+      }
+
+      /* Cannot move if it's not this player's team's turn. */
+      if (player.team !== this.state.team_to_play)
+      {
+        return { legal: false,
+                 message: "It's not your turn to move" };
+      }
     }
 
     return { legal: true };
   }
 
+  /* Assign team only if player is unassigned.
+   * Return true if assignment made, false otherwise. */
+  assign_player_to_team_perhaps(player, team)
+  {
+    if (player.team !== "")
+      return false;
+
+    player.team = team;
+    this.broadcast_event("player-update", player.info_json());
+
+    return true;
+  }
+
+  /* This function is called after the child add_move has returned
+   * 'result' so that any generic processing can happen.
+   *
+   * In particular, we assign teams for a two-player game where a
+   * player assumed a team by making the first move. */
+  post_move(player, result)
+  {
+    if (this.first_move && result.legal) {
+      this.first_move = false;
+
+      this.assign_player_to_team_perhaps(player, this.teams[0]);
+
+      /* Yes, start at 1 to skip teams[0] which we just assigned. */
+      for (let i = 1; i < this.teams.length; i++) {
+        const other = this.players.find(p => p !== player && p.team === "");
+        if (!other)
+          return;
+        this.assign_player_to_team_perhaps(other, this.teams[i]);
+      }
+    }
+  }
+
   add_player(session, connection) {
     /* First see if we already have a player object for this session. */
     const existing = this.players_by_session[session.id];
diff --git a/lmno.js b/lmno.js
index c254046ecb7ae7054a7a4f0b617acd96f46c936a..b85041ec74d13be48abcbca9a5f7e3e8b900ba4e 100644 (file)
--- a/lmno.js
+++ b/lmno.js
@@ -324,6 +324,9 @@ for (let key in engines) {
 
       const result = game.add_move(player, move);
 
+      /* Take care of any generic post-move work. */
+      game.post_move(player, result);
+
       /* Feed move response back to the client. */
       response.json(result);
 
diff --git a/test b/test
index b480b5fb3409957334e030541468559b3d170c3e..d8a46ec970edf2bc40e4b382c48e1979a5ea1501 100755 (executable)
--- a/test
+++ b/test
@@ -372,25 +372,30 @@ TEST_END
 
 TEST_SUBSECTION "Tic Tac Toe /move"
 
-TEST "Illegal to move when not on a team"
+TEST "First move doesn't require a team"
+result=$(tictactoe_move 0)
+test "$result" = '{"legal":true}'
+TEST_END
+
+TEST "Second move does require a team"
 result=$(tictactoe_move 4)
-test "$result" = '{"legal":false,"message":"You must be on a team to take a turn"}'
+test "$result" = '{"legal":false,"message":"It'"'"'s not your turn to move"}'
 TEST_END
 
 TEST "Illegal to move when it's not your turn"
-tictactoe_player_team O
+tictactoe_player_team X
 result=$(tictactoe_move 4)
 test "$result" = '{"legal":false,"message":"It'"'"'s not your turn to move"}'
 TEST_END
 
 TEST "Legal move to center square"
-tictactoe_player_team X
+tictactoe_player_team O
 result=$(tictactoe_move 4)
 test "$result" = '{"legal":true}'
 TEST_END
 
 TEST "Move to center square again is now illegal"
-tictactoe_player_team O
+tictactoe_player_team X
 result=$(tictactoe_move 4)
 test "$result" = '{"legal":false,"message":"Square is already occupied"}'
 TEST_END