#include "game_lifecycle.h" #include #include static lifecycle_result_t session_result(session_result_t result) { switch (result) { case SESSION_RESULT_OK: return LIFECYCLE_RESULT_OK; case SESSION_RESULT_INVALID_NAME: return LIFECYCLE_RESULT_INVALID_NAME; case SESSION_RESULT_INVALID_ROLE: return LIFECYCLE_RESULT_INVALID_ROLE; case SESSION_RESULT_NO_PLAYER_SLOT: return LIFECYCLE_RESULT_NO_PLAYER_SLOT; case SESSION_RESULT_NO_SPECTATOR_SLOT: return LIFECYCLE_RESULT_NO_SPECTATOR_SLOT; case SESSION_RESULT_WRONG_PHASE: return LIFECYCLE_RESULT_WRONG_PHASE; default: return LIFECYCLE_RESULT_UNAUTHORIZED; } } static lifecycle_result_t game_result(game_result_t result) { switch (result) { case GAME_RESULT_OK: return LIFECYCLE_RESULT_OK; case GAME_RESULT_INVALID_COORDINATE: return LIFECYCLE_RESULT_INVALID_COORDINATE; case GAME_RESULT_WRONG_PHASE: return LIFECYCLE_RESULT_WRONG_PHASE; case GAME_RESULT_NOT_YOUR_TURN: return LIFECYCLE_RESULT_NOT_YOUR_TURN; case GAME_RESULT_CELL_ALREADY_SHOT: return LIFECYCLE_RESULT_CELL_ALREADY_SHOT; default: return LIFECYCLE_RESULT_GENERATION_FAILED; } } static bool is_player(const game_lifecycle_t *lifecycle, uint8_t session_index, uint8_t *player) { if (lifecycle == NULL || session_index >= kPlayerCapacity || !lifecycle->sessions.entries[session_index].occupied) return false; if (lifecycle->sessions.entries[session_index].role != session_index) return false; if (player != NULL) *player = session_index; return true; } static bool game_id_matches(const game_lifecycle_t *lifecycle, uint32_t game_id) { return lifecycle != NULL && lifecycle->game.state.game_id == game_id; } static void record_cumulative(game_lifecycle_t *lifecycle) { for (uint8_t player = 0; player < kPlayerCapacity; ++player) { cumulative_statistics_t *total = &lifecycle->cumulative[player]; const match_statistics_t *match = &lifecycle->game.state.statistics[player]; ++total->games; total->shots += match->shots; total->hits += match->hits; total->misses += match->misses; total->ships_sunk += match->ships_sunk; if (lifecycle->game.state.winner == player) ++total->wins; else ++total->losses; } } static lifecycle_result_t start_current_game(game_lifecycle_t *lifecycle) { const uint32_t game_id = lifecycle->game.state.game_id; const game_mode_t mode = lifecycle->game.state.mode; const uint32_t version = lifecycle->game.state.version; game_engine_init(&lifecycle->game); lifecycle->game.state.game_id = game_id; lifecycle->game.state.version = version; const lifecycle_result_t result = game_result(game_engine_start(&lifecycle->game, game_id, mode, &(fleet_generator_t){.random = lifecycle->random})); if (result != LIFECYCLE_RESULT_OK || mode != MODE_BOT) return result; bot_player_init(&lifecycle->bot, lifecycle->random, lifecycle->bot_scheduler); if (lifecycle->game.state.current_player == 1U && lifecycle->bot_scheduler.schedule_after_ms != NULL) { (void)bot_player_schedule_turn(&lifecycle->bot); } return LIFECYCLE_RESULT_OK; } static void return_to_lobby(game_lifecycle_t *lifecycle) { const uint32_t version = lifecycle->game.state.version + 1U; const uint32_t game_id = lifecycle->next_game_id++; game_engine_init(&lifecycle->game); lifecycle->game.state.game_id = game_id; lifecycle->game.state.version = version; lifecycle->bot_reserved = false; lifecycle->rematch_confirmed[0] = false; lifecycle->rematch_confirmed[1] = false; bot_player_cancel_turn(&lifecycle->bot); } void game_lifecycle_init(game_lifecycle_t *lifecycle, random_source_t random) { if (lifecycle == NULL) return; memset(lifecycle, 0, sizeof(*lifecycle)); lifecycle->random = random; lifecycle->next_game_id = 2U; lifecycle->recovery_generation = 1U; game_engine_init(&lifecycle->game); lifecycle->game.state.game_id = 1U; session_manager_init(&lifecycle->sessions); } void game_lifecycle_set_bot_scheduler(game_lifecycle_t *lifecycle, scheduler_t scheduler) { if (lifecycle == NULL) return; lifecycle->bot_scheduler = scheduler; } lifecycle_result_t game_lifecycle_join(game_lifecycle_t *lifecycle, role_t requested_role, const char *name, uint8_t *session_index) { if (lifecycle == NULL) return LIFECYCLE_RESULT_UNAUTHORIZED; if ((requested_role == ROLE_PLAYER_1 || requested_role == ROLE_PLAYER_2 || requested_role == ROLE_AUTO_PLAYER) && lifecycle->game.state.phase != PHASE_LOBBY) { return LIFECYCLE_RESULT_NO_PLAYER_SLOT; } if (requested_role == ROLE_PLAYER_2 && !session_manager_player_present(&lifecycle->sessions, 0U)) { return LIFECYCLE_RESULT_NO_PLAYER_SLOT; } if ((requested_role == ROLE_PLAYER_2 || requested_role == ROLE_AUTO_PLAYER) && (lifecycle->game.state.mode == MODE_BOT || lifecycle->bot_reserved)) { return LIFECYCLE_RESULT_NO_PLAYER_SLOT; } const lifecycle_result_t result = session_result(session_manager_join(&lifecycle->sessions, requested_role, name, lifecycle->random, session_index)); if (result == LIFECYCLE_RESULT_OK) ++lifecycle->game.state.version; return result; } lifecycle_result_t game_lifecycle_resume(game_lifecycle_t *lifecycle, const uint8_t token[kSessionTokenBytes], uint8_t *session_index) { return lifecycle == NULL ? LIFECYCLE_RESULT_UNAUTHORIZED : session_result(session_manager_resume(&lifecycle->sessions, token, session_index)); } lifecycle_result_t game_lifecycle_disconnect(game_lifecycle_t *lifecycle, uint8_t session_index) { return lifecycle != NULL && session_manager_disconnect(&lifecycle->sessions, session_index) ? LIFECYCLE_RESULT_OK : LIFECYCLE_RESULT_UNAUTHORIZED; } lifecycle_result_t game_lifecycle_leave(game_lifecycle_t *lifecycle, uint8_t session_index) { if (lifecycle == NULL || session_index >= kSessionCapacity || !lifecycle->sessions.entries[session_index].occupied) return LIFECYCLE_RESULT_UNAUTHORIZED; const bool player = session_index < kPlayerCapacity && lifecycle->sessions.entries[session_index].role == (role_t)session_index; if (player && lifecycle->game.state.phase != PHASE_LOBBY) return_to_lobby(lifecycle); else ++lifecycle->game.state.version; memset(&lifecycle->sessions.entries[session_index], 0, sizeof(session_t)); ++lifecycle->recovery_generation; lifecycle->recovery_reason = RECOVERY_REASON_SESSION_LEFT; return LIFECYCLE_RESULT_OK; } lifecycle_result_t game_lifecycle_reset(game_lifecycle_t *lifecycle, uint8_t session_index, uint32_t game_id) { uint8_t player = 0; if (!is_player(lifecycle, session_index, &player)) return LIFECYCLE_RESULT_FORBIDDEN_ROLE; if (!game_id_matches(lifecycle, game_id)) return LIFECYCLE_RESULT_STALE_GAME; const uint32_t version = lifecycle->game.state.version + 1U; const uint32_t next_game_id = lifecycle->next_game_id++; const random_source_t random = lifecycle->random; const scheduler_t scheduler = lifecycle->bot_scheduler; bot_player_cancel_turn(&lifecycle->bot); memset(lifecycle, 0, sizeof(*lifecycle)); lifecycle->random = random; lifecycle->bot_scheduler = scheduler; lifecycle->next_game_id = next_game_id + 1U; lifecycle->recovery_generation = next_game_id; lifecycle->recovery_reason = RECOVERY_REASON_GAME_RESET; game_engine_init(&lifecycle->game); lifecycle->game.state.game_id = next_game_id; lifecycle->game.state.version = version; return LIFECYCLE_RESULT_OK; } void game_lifecycle_set_recovery_reason(game_lifecycle_t *lifecycle, recovery_reason_t reason) { if (lifecycle != NULL) lifecycle->recovery_reason = reason; } lifecycle_result_t game_lifecycle_configure(game_lifecycle_t *lifecycle, uint8_t session_index, uint32_t game_id, game_mode_t mode) { uint8_t player = 0; if (!is_player(lifecycle, session_index, &player)) return LIFECYCLE_RESULT_FORBIDDEN_ROLE; if (player != 0U) return LIFECYCLE_RESULT_FORBIDDEN_ROLE; if (!game_id_matches(lifecycle, game_id)) return LIFECYCLE_RESULT_STALE_GAME; if (lifecycle->game.state.phase != PHASE_LOBBY) return LIFECYCLE_RESULT_WRONG_PHASE; if (mode != MODE_HUMAN && mode != MODE_BOT) return LIFECYCLE_RESULT_INVALID_MODE; if (mode == MODE_BOT && session_manager_player_present(&lifecycle->sessions, 1U)) return LIFECYCLE_RESULT_NO_PLAYER_SLOT; lifecycle->game.state.mode = mode; lifecycle->bot_reserved = mode == MODE_BOT; ++lifecycle->game.state.version; return LIFECYCLE_RESULT_OK; } lifecycle_result_t game_lifecycle_start(game_lifecycle_t *lifecycle, uint8_t session_index, uint32_t game_id) { uint8_t player = 0; if (!is_player(lifecycle, session_index, &player)) return LIFECYCLE_RESULT_FORBIDDEN_ROLE; if (player != 0U) return LIFECYCLE_RESULT_FORBIDDEN_ROLE; if (!game_id_matches(lifecycle, game_id)) return LIFECYCLE_RESULT_STALE_GAME; if (lifecycle->game.state.phase != PHASE_LOBBY) return LIFECYCLE_RESULT_WRONG_PHASE; if (lifecycle->game.state.mode == MODE_HUMAN && !session_manager_player_present(&lifecycle->sessions, 1U)) return LIFECYCLE_RESULT_WRONG_PHASE; if (lifecycle->game.state.mode == MODE_BOT && !lifecycle->bot_reserved) return LIFECYCLE_RESULT_WRONG_PHASE; return start_current_game(lifecycle); } lifecycle_result_t game_lifecycle_shot(game_lifecycle_t *lifecycle, uint8_t session_index, uint32_t game_id, coordinate_t coordinate, shot_result_t *result) { uint8_t player = 0; if (!is_player(lifecycle, session_index, &player)) return LIFECYCLE_RESULT_FORBIDDEN_ROLE; if (!game_id_matches(lifecycle, game_id)) return LIFECYCLE_RESULT_STALE_GAME; const lifecycle_result_t outcome = game_result(game_engine_shot(&lifecycle->game, player, coordinate, result)); if (outcome == LIFECYCLE_RESULT_OK && lifecycle->game.state.phase == PHASE_FINISHED) record_cumulative(lifecycle); else if (outcome == LIFECYCLE_RESULT_OK && lifecycle->game.state.mode == MODE_BOT && lifecycle->game.state.current_player == 1U && lifecycle->bot_scheduler.schedule_after_ms != NULL) (void)bot_player_schedule_turn(&lifecycle->bot); return outcome; } lifecycle_result_t game_lifecycle_rematch(game_lifecycle_t *lifecycle, uint8_t session_index, uint32_t game_id) { uint8_t player = 0; if (!is_player(lifecycle, session_index, &player)) return LIFECYCLE_RESULT_FORBIDDEN_ROLE; if (!game_id_matches(lifecycle, game_id)) return LIFECYCLE_RESULT_STALE_GAME; if (lifecycle->game.state.phase != PHASE_FINISHED && lifecycle->game.state.phase != PHASE_REMATCH_WAIT) return LIFECYCLE_RESULT_WRONG_PHASE; lifecycle->rematch_confirmed[player] = true; const bool ready = lifecycle->game.state.mode == MODE_BOT ? lifecycle->rematch_confirmed[0] : lifecycle->rematch_confirmed[0] && lifecycle->rematch_confirmed[1]; if (!ready) { if (lifecycle->game.state.phase == PHASE_FINISHED) { lifecycle->game.state.phase = PHASE_REMATCH_WAIT; ++lifecycle->game.state.version; } return LIFECYCLE_RESULT_OK; } lifecycle->rematch_confirmed[0] = false; lifecycle->rematch_confirmed[1] = false; lifecycle->game.state.game_id = lifecycle->next_game_id++; return start_current_game(lifecycle); } lifecycle_result_t game_lifecycle_abort(game_lifecycle_t *lifecycle, uint8_t session_index, uint32_t game_id) { uint8_t player = 0; if (!is_player(lifecycle, session_index, &player) || player != 0U) return LIFECYCLE_RESULT_FORBIDDEN_ROLE; if (!game_id_matches(lifecycle, game_id)) return LIFECYCLE_RESULT_STALE_GAME; if (lifecycle->game.state.phase != PHASE_IN_PROGRESS || lifecycle->game.state.mode != MODE_HUMAN || lifecycle->sessions.entries[1].connected) return LIFECYCLE_RESULT_WRONG_PHASE; const uint32_t version = lifecycle->game.state.version + 1U; const uint32_t next_game_id = lifecycle->next_game_id++; game_engine_init(&lifecycle->game); lifecycle->game.state.game_id = next_game_id; lifecycle->game.state.version = version; lifecycle->bot_reserved = false; bot_player_cancel_turn(&lifecycle->bot); return LIFECYCLE_RESULT_OK; } bool game_lifecycle_bot_take_turn(game_lifecycle_t *lifecycle) { if (lifecycle == NULL || lifecycle->game.state.phase != PHASE_IN_PROGRESS || lifecycle->game.state.mode != MODE_BOT || lifecycle->game.state.current_player != 1U) return false; coordinate_t coordinate = {0}; shot_result_t result = {0}; if (!bot_player_next_shot(&lifecycle->bot, &coordinate) || game_engine_shot(&lifecycle->game, 1U, coordinate, &result) != GAME_RESULT_OK) return false; bot_player_record_result(&lifecycle->bot, coordinate, &(bot_shot_result_t){.hit = result.hit, .sunk = result.sunk}); if (lifecycle->game.state.phase == PHASE_FINISHED) record_cumulative(lifecycle); else if (lifecycle->game.state.current_player == 1U) (void)bot_player_schedule_turn(&lifecycle->bot); return true; }