feat: implement sessions, lobby, roles, and rematch lifecycle
This commit is contained in:
+6
-2
@@ -1,7 +1,7 @@
|
||||
CC ?= cc
|
||||
CFLAGS ?= -std=c11 -Wall -Wextra -Werror -I../../include
|
||||
|
||||
all: test_command_queue test_game_domain test_bot_player
|
||||
all: test_command_queue test_game_domain test_bot_player test_game_lifecycle
|
||||
|
||||
test_command_queue: test_command_queue.c ../../src/command_queue.c ../../src/application.c
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
@@ -10,6 +10,7 @@ run: all
|
||||
./test_command_queue
|
||||
./test_game_domain
|
||||
./test_bot_player
|
||||
./test_game_lifecycle
|
||||
|
||||
test_game_domain: test_game_domain.c ../../src/fleet_generator.c ../../src/game_engine.c
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
@@ -17,5 +18,8 @@ test_game_domain: test_game_domain.c ../../src/fleet_generator.c ../../src/game_
|
||||
test_bot_player: test_bot_player.c ../../src/bot_player.c ../../src/fleet_generator.c ../../src/game_engine.c
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
|
||||
test_game_lifecycle: test_game_lifecycle.c ../../src/session_manager.c ../../src/game_lifecycle.c ../../src/fleet_generator.c ../../src/game_engine.c
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
|
||||
clean:
|
||||
rm -f test_command_queue test_game_domain test_bot_player
|
||||
rm -f test_command_queue test_game_domain test_bot_player test_game_lifecycle
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "game_lifecycle.h"
|
||||
|
||||
typedef struct { uint32_t value; } test_random_t;
|
||||
|
||||
static uint32_t next_random(void *context) {
|
||||
test_random_t *random = context;
|
||||
random->value = random->value * 1664525U + 1013904223U;
|
||||
return random->value;
|
||||
}
|
||||
|
||||
static game_lifecycle_t new_lifecycle(test_random_t *random) {
|
||||
game_lifecycle_t lifecycle;
|
||||
game_lifecycle_init(&lifecycle, (random_source_t){.next_u32 = next_random, .context = random});
|
||||
return lifecycle;
|
||||
}
|
||||
|
||||
static void join_players(game_lifecycle_t *lifecycle, uint8_t *player_1, uint8_t *player_2) {
|
||||
assert(game_lifecycle_join(lifecycle, ROLE_PLAYER_1, "Alice", player_1) == LIFECYCLE_RESULT_OK);
|
||||
assert(game_lifecycle_join(lifecycle, ROLE_PLAYER_2, "Bob", player_2) == LIFECYCLE_RESULT_OK);
|
||||
assert(*player_1 == 0U && *player_2 == 1U);
|
||||
}
|
||||
|
||||
static void finish_for_player_1(game_lifecycle_t *lifecycle, uint8_t player_1) {
|
||||
game_state_t *state = &lifecycle->game.state;
|
||||
state->current_player = 0U;
|
||||
memset(&state->boards[1], 0, sizeof(state->boards[1]));
|
||||
state->boards[1].ships_alive = 1U;
|
||||
state->boards[1].ships[0] = (ship_t){.x = 0, .y = 0, .length = 1, .horizontal = true};
|
||||
state->boards[1].cells[0] = CELL_SHIP;
|
||||
shot_result_t result = {0};
|
||||
assert(game_lifecycle_shot(lifecycle, player_1, state->game_id, (coordinate_t){0, 0}, &result) == LIFECYCLE_RESULT_OK);
|
||||
assert(result.finished && state->phase == PHASE_FINISHED && state->winner == 0U);
|
||||
}
|
||||
|
||||
static void test_capacity_sanitize_and_resume(void) {
|
||||
test_random_t random = {.value = 1};
|
||||
game_lifecycle_t lifecycle = new_lifecycle(&random);
|
||||
uint8_t player_1 = 0;
|
||||
uint8_t player_2 = 0;
|
||||
assert(game_lifecycle_join(&lifecycle, ROLE_PLAYER_2, "Bob", &player_2) == LIFECYCLE_RESULT_NO_PLAYER_SLOT);
|
||||
join_players(&lifecycle, &player_1, &player_2);
|
||||
assert(game_lifecycle_join(&lifecycle, ROLE_PLAYER_1, "Other", &player_1) == LIFECYCLE_RESULT_NO_PLAYER_SLOT);
|
||||
assert(game_lifecycle_join(&lifecycle, ROLE_SPECTATOR, "<bad>", &player_1) == LIFECYCLE_RESULT_INVALID_NAME);
|
||||
for (uint8_t index = 0; index < kSpectatorCapacity; ++index) {
|
||||
assert(game_lifecycle_join(&lifecycle, ROLE_SPECTATOR, "Watch", &player_1) == LIFECYCLE_RESULT_OK);
|
||||
}
|
||||
assert(game_lifecycle_join(&lifecycle, ROLE_SPECTATOR, "Extra", &player_1) == LIFECYCLE_RESULT_NO_SPECTATOR_SLOT);
|
||||
const uint8_t token[kSessionTokenBytes] = {0};
|
||||
uint8_t saved_token[kSessionTokenBytes] = {0};
|
||||
memcpy(saved_token, lifecycle.sessions.entries[player_2].token, sizeof(saved_token));
|
||||
assert(memcmp(token, saved_token, sizeof(token)) != 0);
|
||||
assert(game_lifecycle_disconnect(&lifecycle, player_2) == LIFECYCLE_RESULT_OK);
|
||||
assert(!lifecycle.sessions.entries[player_2].connected);
|
||||
uint8_t resumed = kSessionCapacity;
|
||||
assert(game_lifecycle_resume(&lifecycle, saved_token, &resumed) == LIFECYCLE_RESULT_OK);
|
||||
assert(resumed == player_2 && lifecycle.sessions.entries[resumed].role == ROLE_PLAYER_2);
|
||||
}
|
||||
|
||||
static void test_human_lifecycle_and_guards(void) {
|
||||
test_random_t random = {.value = 9};
|
||||
game_lifecycle_t lifecycle = new_lifecycle(&random);
|
||||
uint8_t player_1 = 0;
|
||||
uint8_t player_2 = 0;
|
||||
join_players(&lifecycle, &player_1, &player_2);
|
||||
const uint32_t game_id = lifecycle.game.state.game_id;
|
||||
game_lifecycle_t before = lifecycle;
|
||||
assert(game_lifecycle_configure(&lifecycle, player_2, game_id, MODE_BOT) == LIFECYCLE_RESULT_FORBIDDEN_ROLE);
|
||||
assert(memcmp(&before, &lifecycle, sizeof(lifecycle)) == 0);
|
||||
assert(game_lifecycle_start(&lifecycle, player_1, game_id + 1U) == LIFECYCLE_RESULT_STALE_GAME);
|
||||
assert(game_lifecycle_start(&lifecycle, player_1, game_id) == LIFECYCLE_RESULT_OK);
|
||||
assert(lifecycle.game.state.phase == PHASE_IN_PROGRESS);
|
||||
finish_for_player_1(&lifecycle, player_1);
|
||||
assert(lifecycle.cumulative[0].games == 1U && lifecycle.cumulative[0].wins == 1U);
|
||||
assert(lifecycle.cumulative[1].games == 1U && lifecycle.cumulative[1].losses == 1U);
|
||||
assert(game_lifecycle_rematch(&lifecycle, player_1, game_id) == LIFECYCLE_RESULT_OK);
|
||||
assert(lifecycle.game.state.phase == PHASE_REMATCH_WAIT);
|
||||
assert(game_lifecycle_rematch(&lifecycle, player_2, game_id) == LIFECYCLE_RESULT_OK);
|
||||
assert(lifecycle.game.state.phase == PHASE_IN_PROGRESS && lifecycle.game.state.game_id != game_id);
|
||||
assert(lifecycle.cumulative[0].wins == 1U);
|
||||
assert(game_lifecycle_disconnect(&lifecycle, player_2) == LIFECYCLE_RESULT_OK);
|
||||
assert(game_lifecycle_abort(&lifecycle, player_1, lifecycle.game.state.game_id) == LIFECYCLE_RESULT_OK);
|
||||
assert(lifecycle.game.state.phase == PHASE_LOBBY);
|
||||
}
|
||||
|
||||
static void test_bot_lifecycle_and_stale_actions(void) {
|
||||
test_random_t random = {.value = 17};
|
||||
game_lifecycle_t lifecycle = new_lifecycle(&random);
|
||||
uint8_t player_1 = 0;
|
||||
assert(game_lifecycle_join(&lifecycle, ROLE_PLAYER_1, "Alice", &player_1) == LIFECYCLE_RESULT_OK);
|
||||
const uint32_t game_id = lifecycle.game.state.game_id;
|
||||
assert(game_lifecycle_configure(&lifecycle, player_1, game_id, MODE_BOT) == LIFECYCLE_RESULT_OK);
|
||||
assert(lifecycle.bot_reserved);
|
||||
assert(game_lifecycle_join(&lifecycle, ROLE_PLAYER_2, "Bob", &player_1) == LIFECYCLE_RESULT_NO_PLAYER_SLOT);
|
||||
assert(game_lifecycle_start(&lifecycle, player_1, game_id) == LIFECYCLE_RESULT_OK);
|
||||
finish_for_player_1(&lifecycle, player_1);
|
||||
assert(game_lifecycle_rematch(&lifecycle, player_1, game_id) == LIFECYCLE_RESULT_OK);
|
||||
assert(lifecycle.game.state.phase == PHASE_IN_PROGRESS && lifecycle.game.state.game_id != game_id);
|
||||
const game_lifecycle_t before = lifecycle;
|
||||
assert(game_lifecycle_shot(&lifecycle, player_1, game_id, (coordinate_t){0, 0}, NULL) == LIFECYCLE_RESULT_STALE_GAME);
|
||||
assert(memcmp(&before, &lifecycle, sizeof(lifecycle)) == 0);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_capacity_sanitize_and_resume();
|
||||
test_human_lifecycle_and_guards();
|
||||
test_bot_lifecycle_and_stale_actions();
|
||||
puts("game lifecycle tests passed");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user