#include #include #include #include "fleet_generator.h" #include "game_engine.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 fleet_generator_t generator_for(test_random_t *random) { return (fleet_generator_t){.random = {.next_u32 = next_random, .context = random}}; } static void start_game(game_engine_t *engine, test_random_t *random) { const fleet_generator_t generator = generator_for(random); game_engine_init(engine); assert(game_engine_start(engine, 1, MODE_HUMAN, &generator) == GAME_RESULT_OK); assert(engine->state.phase == PHASE_IN_PROGRESS); assert(engine->state.version == 1U); assert(fleet_generator_validate(&engine->state.boards[0])); assert(fleet_generator_validate(&engine->state.boards[1])); } static coordinate_t first_cell(const board_t *board, cell_t wanted) { for (uint8_t y = 0; y < kBoardHeight; ++y) { for (uint8_t x = 0; x < kBoardWidth; ++x) { if (board->cells[y * kBoardWidth + x] == wanted) return (coordinate_t){x, y}; } } assert(false); return (coordinate_t){0, 0}; } static void assert_rejected_unchanged(game_engine_t *engine, uint8_t player, coordinate_t coordinate, game_result_t expected) { game_engine_t before = *engine; assert(game_engine_shot(engine, player, coordinate, NULL) == expected); assert(memcmp(&before, engine, sizeof(before)) == 0); } static void test_many_fleets(void) { for (uint32_t seed = 0; seed < 10000U; ++seed) { test_random_t random = {.value = seed}; const fleet_generator_t generator = generator_for(&random); board_t board = {0}; assert(fleet_generator_generate(&generator, &board)); assert(fleet_generator_validate(&board)); } } static void test_rules_and_rejection(void) { test_random_t random = {.value = 7}; game_engine_t engine; start_game(&engine, &random); const uint8_t player = engine.state.current_player; game_engine_t before_start = engine; const fleet_generator_t generator = generator_for(&random); assert(game_engine_start(&engine, 2, MODE_HUMAN, &generator) == GAME_RESULT_WRONG_PHASE); assert(memcmp(&before_start, &engine, sizeof(engine)) == 0); assert_rejected_unchanged(&engine, player, (coordinate_t){10, 0}, GAME_RESULT_INVALID_COORDINATE); assert_rejected_unchanged(&engine, player ^ 1U, (coordinate_t){0, 0}, GAME_RESULT_NOT_YOUR_TURN); const coordinate_t water = first_cell(&engine.state.boards[player ^ 1U], CELL_WATER); shot_result_t result = {0}; const uint32_t version = engine.state.version; assert(game_engine_shot(&engine, player, water, &result) == GAME_RESULT_OK); assert(!result.hit && !result.sunk && !result.finished); assert(engine.state.current_player == (uint8_t)(player ^ 1U)); assert(engine.state.version == version + 1U); assert(engine.state.statistics[player].shots == 1U && engine.state.statistics[player].misses == 1U); assert_rejected_unchanged(&engine, player, water, GAME_RESULT_NOT_YOUR_TURN); const uint8_t hitter = engine.state.current_player; const coordinate_t ship_cell = first_cell(&engine.state.boards[hitter ^ 1U], CELL_SHIP); assert(game_engine_shot(&engine, hitter, ship_cell, &result) == GAME_RESULT_OK); assert(result.hit && !result.finished); assert(engine.state.current_player == hitter); assert(engine.state.statistics[hitter].hits == 1U); assert_rejected_unchanged(&engine, hitter, ship_cell, GAME_RESULT_CELL_ALREADY_SHOT); } static void test_sinking_and_victory(void) { test_random_t random = {.value = 11}; game_engine_t engine; start_game(&engine, &random); const uint8_t player = engine.state.current_player; board_t *target = &engine.state.boards[player ^ 1U]; const ship_t ship = target->ships[9]; assert(ship.length == 1U); const coordinate_t coordinate = {ship.x, ship.y}; shot_result_t result = {0}; assert(game_engine_shot(&engine, player, coordinate, &result) == GAME_RESULT_OK); assert(result.hit && result.sunk && !result.finished); assert(target->ships_alive == kFleetShipCount - 1U); assert(engine.state.statistics[player].ships_sunk == 1U); for (int8_t y = -1; y <= 1; ++y) { for (int8_t x = -1; x <= 1; ++x) { const int16_t neighbour_x = (int16_t)coordinate.x + x; const int16_t neighbour_y = (int16_t)coordinate.y + y; if (neighbour_x < 0 || neighbour_y < 0 || neighbour_x >= kBoardWidth || neighbour_y >= kBoardHeight) continue; const uint8_t index = (uint8_t)(neighbour_y * kBoardWidth + neighbour_x); if (x != 0 || y != 0) assert(target->cells[index] == CELL_MISS); } } for (uint8_t ship_index = 0; ship_index < kFleetShipCount; ++ship_index) { const ship_t remaining = target->ships[ship_index]; for (uint8_t offset = 0; offset < remaining.length; ++offset) { const coordinate_t hit = {remaining.horizontal ? (uint8_t)(remaining.x + offset) : remaining.x, remaining.horizontal ? remaining.y : (uint8_t)(remaining.y + offset)}; if (target->cells[hit.y * kBoardWidth + hit.x] == CELL_SHIP) assert(game_engine_shot(&engine, player, hit, &result) == GAME_RESULT_OK); } } assert(result.finished); assert(engine.state.phase == PHASE_FINISHED && engine.state.winner == player); assert_rejected_unchanged(&engine, player, (coordinate_t){0, 0}, GAME_RESULT_WRONG_PHASE); } static void test_simulated_games(void) { for (uint32_t seed = 100; seed < 10100U; ++seed) { test_random_t random = {.value = seed}; game_engine_t engine; start_game(&engine, &random); for (uint16_t step = 0; step < 200U && engine.state.phase == PHASE_IN_PROGRESS; ++step) { const uint8_t player = engine.state.current_player; const board_t *target = &engine.state.boards[player ^ 1U]; coordinate_t choice = {0, 0}; bool found = false; for (uint8_t index = 0; index < kBoardCellCount; ++index) { if (target->cells[index] == CELL_WATER || target->cells[index] == CELL_SHIP) { choice = (coordinate_t){(uint8_t)(index % kBoardWidth), (uint8_t)(index / kBoardWidth)}; found = true; break; } } assert(found); assert(game_engine_shot(&engine, player, choice, NULL) == GAME_RESULT_OK); } assert(engine.state.phase == PHASE_FINISHED); assert(engine.state.winner < kPlayerCapacity); for (uint8_t player = 0; player < kPlayerCapacity; ++player) { const match_statistics_t *statistics = &engine.state.statistics[player]; assert(statistics->shots == statistics->hits + statistics->misses); assert(statistics->ships_sunk <= kFleetShipCount); } } } int main(void) { test_many_fleets(); test_rules_and_rejection(); test_sinking_and_victory(); test_simulated_games(); puts("game domain tests passed"); return 0; }