this.state = {
team_to_play: ""
};
+ this.first_move = true;
/* Send a comment to every connected client every 15 seconds. */
setInterval(() => {this.broadcast_string(":");}, 15000);
* 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];
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