#include "maze32_game.h" #include #include #include #define TEST_WIDTH 8U #define TEST_HEIGHT 8U static uint16_t index_of(uint16_t x, uint16_t y) { return (uint16_t)(y * TEST_WIDTH + x); } static void open_test_grid(maze32_maze_t* maze) { memset(maze, 0, sizeof(*maze)); maze->config.width = TEST_WIDTH; maze->config.height = TEST_HEIGHT; maze->config.artifact_pairs = 1U; for (uint16_t y = 0U; y < TEST_HEIGHT; ++y) { for (uint16_t x = 0U; x < TEST_WIDTH; ++x) { uint8_t passages = 0U; if (y > 0U) { passages |= MAZE32_PASSAGE_NORTH; } if (x + 1U < TEST_WIDTH) { passages |= MAZE32_PASSAGE_EAST; } if (y + 1U < TEST_HEIGHT) { passages |= MAZE32_PASSAGE_SOUTH; } if (x > 0U) { passages |= MAZE32_PASSAGE_WEST; } maze->passages[index_of(x, y)] = passages; } } maze->starts[0] = (maze32_cell_t){0U, 1U}; maze->starts[1] = (maze32_cell_t){7U, 6U}; maze->keys[0] = (maze32_cell_t){2U, 1U}; maze->keys[1] = (maze32_cell_t){1U, 1U}; maze->exits[0] = (maze32_cell_t){0U, 1U}; maze->exits[1] = (maze32_cell_t){7U, 6U}; maze->compasses[0] = (maze32_cell_t){1U, 1U}; maze->compasses[1] = (maze32_cell_t){6U, 6U}; maze->artifacts[0][0] = (maze32_cell_t){1U, 2U}; maze->artifacts[0][1] = (maze32_cell_t){7U, 7U}; } static maze32_game_t active_game(maze32_maze_t* maze) { maze32_game_t game; maze32_game_config_t config = maze32_game_default_config(); config.movement_cooldown_ms = 0U; assert(maze32_game_init(&game, maze, &config) == MAZE32_GAME_RESULT_ACCEPTED); maze32_game_set_phase(&game, MAZE32_GAME_PHASE_ACTIVE); return game; } static maze32_game_command_result_t move_player(maze32_game_t* game, maze32_game_role_t role, maze32_game_direction_t direction, uint64_t submitted_ms) { uint32_t sequence = 0U; assert(maze32_game_enqueue_move(game, role, direction, submitted_ms, &sequence) == MAZE32_GAME_RESULT_ACCEPTED); maze32_game_command_result_t result = maze32_game_process_next(game); assert(result.command_sequence == sequence); return result; } static maze32_game_command_result_t drop_key(maze32_game_t* game, maze32_game_role_t role, uint64_t submitted_ms) { uint32_t sequence = 0U; assert(maze32_game_enqueue_drop_key(game, role, submitted_ms, &sequence) == MAZE32_GAME_RESULT_ACCEPTED); maze32_game_command_result_t result = maze32_game_process_next(game); assert(result.command_sequence == sequence); return result; } static void give_key(maze32_game_t* game, uint8_t carrier, uint8_t key) { game->players[carrier].carried_key = key; game->keys[key].location = MAZE32_GAME_KEY_CARRIED; game->keys[key].carrier = carrier; } static void assert_key_conservation(const maze32_game_t* game) { uint8_t references[MAZE32_GAME_KEY_COUNT] = {0U, 0U}; for (uint8_t key = 0U; key < MAZE32_GAME_KEY_COUNT; ++key) { if (game->keys[key].location == MAZE32_GAME_KEY_GROUND) { references[key]++; assert(game->keys[key].carrier == UINT8_MAX); } else { assert(game->keys[key].carrier < MAZE32_GAME_PLAYER_COUNT); } } for (uint8_t player = 0U; player < MAZE32_GAME_PLAYER_COUNT; ++player) { uint8_t key = game->players[player].carried_key; if (key != MAZE32_GAME_NO_KEY) { assert(key < MAZE32_GAME_KEY_COUNT); assert(game->keys[key].location == MAZE32_GAME_KEY_CARRIED); assert(game->keys[key].carrier == player); references[key]++; } } assert(references[0] == 1U && references[1] == 1U); } static void test_inventory_drop_and_sabotage(void) { maze32_maze_t maze; open_test_grid(&maze); maze32_game_t game = active_game(&maze); maze32_game_command_result_t result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST, 1U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && result.picked_key); assert(result.key == 1U && game.players[0].carried_key == 1U); assert_key_conservation(&game); result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST, 2U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && !result.picked_key); assert(game.players[0].carried_key == 1U); assert(game.keys[0].location == MAZE32_GAME_KEY_GROUND); assert_key_conservation(&game); result = drop_key(&game, MAZE32_GAME_ROLE_PLAYER_ONE, 3U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && result.dropped_key && result.key == 1U); assert(game.players[0].carried_key == MAZE32_GAME_NO_KEY); assert(game.keys[0].location == MAZE32_GAME_KEY_GROUND && game.keys[1].location == MAZE32_GAME_KEY_GROUND); assert(game.keys[0].ground_position.x == 2U && game.keys[1].ground_position.x == 2U); assert_key_conservation(&game); assert(move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_WEST, 4U).result == MAZE32_GAME_RESULT_ACCEPTED); result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST, 5U); assert(result.picked_key && result.key == 0U); assert(game.players[0].carried_key == 0U); assert(game.keys[1].location == MAZE32_GAME_KEY_GROUND); assert_key_conservation(&game); uint32_t state_sequence = game.state_sequence; game.players[0].carried_key = MAZE32_GAME_NO_KEY; game.keys[0].location = MAZE32_GAME_KEY_GROUND; game.keys[0].ground_position = game.players[0].position; game.keys[0].carrier = UINT8_MAX; result = drop_key(&game, MAZE32_GAME_ROLE_PLAYER_ONE, 6U); assert(result.result == MAZE32_GAME_RESULT_NO_KEY && game.state_sequence == state_sequence); assert_key_conservation(&game); printf("game key inventory, drop, and sabotage: PASS\n"); } static void test_atomic_meeting_return(void) { maze32_maze_t maze; open_test_grid(&maze); maze32_game_t game = active_game(&maze); game.players[0].position = (maze32_cell_t){2U, 2U}; game.players[1].position = (maze32_cell_t){4U, 2U}; give_key(&game, 0U, 1U); give_key(&game, 1U, 0U); assert_key_conservation(&game); maze32_game_command_result_t result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_TWO, MAZE32_GAME_DIRECTION_WEST, 10U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && !result.meeting); result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST, 10U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && result.meeting); assert(result.returned_keys == 3U); assert(game.players[0].carried_key == 0U && game.players[1].carried_key == 1U); assert(game.keys[0].carrier == 0U && game.keys[1].carrier == 1U); assert_key_conservation(&game); printf("game atomic meeting key return: PASS (opponent keys swapped to owners)\n"); } static void test_owner_exit_restrictions(void) { maze32_maze_t maze; open_test_grid(&maze); maze32_game_t game = active_game(&maze); game.players[0].position = (maze32_cell_t){1U, 1U}; give_key(&game, 0U, 1U); maze32_game_command_result_t result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_WEST, 20U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && !result.opened_exit); assert(!game.exits[0].open); result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_WEST, 21U); assert(result.result == MAZE32_GAME_RESULT_BLOCKED && game.phase == MAZE32_GAME_PHASE_ACTIVE); open_test_grid(&maze); game = active_game(&maze); game.players[0].position = (maze32_cell_t){6U, 6U}; give_key(&game, 0U, 1U); result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST, 30U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && !result.opened_exit); assert(!game.exits[1].open); game.exits[1].open = 1U; result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST, 31U); assert(result.result == MAZE32_GAME_RESULT_BLOCKED && !game.players[0].exited); open_test_grid(&maze); game = active_game(&maze); game.players[0].position = (maze32_cell_t){1U, 1U}; give_key(&game, 0U, 0U); result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_WEST, 40U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && result.opened_exit); assert(game.exits[0].open && game.phase == MAZE32_GAME_PHASE_ACTIVE); assert(game.players[0].carried_key == 0U); result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_WEST, 41U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && result.outward_exit); assert(game.phase == MAZE32_GAME_PHASE_FINISH_PENDING && game.final_result == MAZE32_GAME_FINAL_NONE); printf("game owner-only exit opening and outward move: PASS\n"); } static void test_vision_artifact_duration(void) { maze32_maze_t maze; open_test_grid(&maze); maze.starts[0] = (maze32_cell_t){0U, 2U}; maze32_game_t game = active_game(&maze); maze32_game_command_result_t result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST, 100U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && result.activated_artifact); assert(result.artifact == 0U && game.consumed_artifacts[0]); assert(game.players[0].vision_moves_remaining == MAZE32_GAME_VISION_MOVES); assert(maze32_game_player_view_radius(&game, 0U) == 6U); maze.passages[index_of(1U, 2U)] &= (uint8_t)~MAZE32_PASSAGE_NORTH; uint32_t accepted_moves = game.players[0].accepted_moves; result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_NORTH, 101U); assert(result.result == MAZE32_GAME_RESULT_BLOCKED); assert(game.players[0].vision_moves_remaining == 10U); assert(game.players[0].accepted_moves == accepted_moves); for (uint8_t move = 0U; move < MAZE32_GAME_VISION_MOVES; ++move) { maze32_game_direction_t direction = move % 2U == 0U ? MAZE32_GAME_DIRECTION_WEST : MAZE32_GAME_DIRECTION_EAST; result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, direction, 102U + move); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && !result.activated_artifact); assert(game.players[0].vision_moves_remaining == MAZE32_GAME_VISION_MOVES - move - 1U); if (move < MAZE32_GAME_VISION_MOVES - 1U) { assert(maze32_game_player_view_radius(&game, 0U) == 6U); } } assert(maze32_game_player_view_radius(&game, 0U) == 3U); printf("game one-use vision duration: PASS (10 successful moves, failed move ignored)\n"); } static void test_compass_and_role_projections(void) { maze32_maze_t maze; open_test_grid(&maze); maze.exits[0] = (maze32_cell_t){7U, 0U}; maze32_game_t game = active_game(&maze); give_key(&game, 0U, 1U); maze32_game_command_result_t result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST, 200U); assert(result.compass_hint == MAZE32_GAME_COMPASS_NORTH_EAST); assert(game.players[0].compass_hint == MAZE32_GAME_COMPASS_NORTH_EAST); maze32_view_t view; assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_PLAYER_ONE, &view) == MAZE32_GAME_RESULT_ACCEPTED); assert(view.self_carried_key == 1U && view.self_compass_hint == MAZE32_GAME_COMPASS_NORTH_EAST); char payload[MAZE32_GAME_VIEW_CHUNK_BYTES]; size_t written = 0U; assert(maze32_game_serialize_view_chunk(&view, 0U, payload, sizeof(payload), &written) == MAZE32_GAME_RESULT_ACCEPTED); assert(strstr(payload, "\"hint\":\"ne\"") != NULL); assert(strstr(payload, "\"inventory\":1") != NULL); assert(strstr(payload, "\"exitTimes\"") == NULL); game.keys[0].location = MAZE32_GAME_KEY_CARRIED; game.keys[0].carrier = 1U; game.players[1].carried_key = 0U; game.players[1].position = (maze32_cell_t){2U, 1U}; assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_PLAYER_ONE, &view) == MAZE32_GAME_RESULT_ACCEPTED); assert(view.opponent_visible && view.opponent_carried_key == 0U); assert(maze32_game_serialize_view_chunk(&view, 0U, payload, sizeof(payload), &written) == MAZE32_GAME_RESULT_ACCEPTED); assert(strstr(payload, "\"opponentKey\":0") != NULL); game.players[1].position = (maze32_cell_t){7U, 7U}; assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_PLAYER_TWO, &view) == MAZE32_GAME_RESULT_ACCEPTED); assert(!view.opponent_visible); assert(maze32_game_serialize_view_chunk(&view, 0U, payload, sizeof(payload), &written) == MAZE32_GAME_RESULT_ACCEPTED); assert(strstr(payload, "\"hint\":\"ne\"") == NULL); assert(strstr(payload, "\"opponentKey\"") == NULL); assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_OBSERVER, &view) == MAZE32_GAME_RESULT_ACCEPTED); assert(view.observer_carried_keys[0] == 1U); assert(maze32_game_serialize_view_chunk(&view, 0U, payload, sizeof(payload), &written) == MAZE32_GAME_RESULT_ACCEPTED); assert(strstr(payload, "\"players\":[") != NULL); assert(strstr(payload, "\"key\":1") != NULL); assert(strstr(payload, "\"exitTimes\":[null,null]") != NULL); maze.passages[index_of(1U, 1U)] &= (uint8_t)~MAZE32_PASSAGE_NORTH; result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_NORTH, 201U); assert(result.result == MAZE32_GAME_RESULT_BLOCKED); assert(game.players[0].compass_hint == MAZE32_GAME_COMPASS_NORTH_EAST); result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_WEST, 202U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED); assert(game.players[0].compass_hint == MAZE32_GAME_COMPASS_NONE); printf("game compass bearing and role-specific projections: PASS\n"); } static void prepare_open_exits(maze32_game_t* game) { game->players[0].position = game->maze->exits[0]; game->players[1].position = game->maze->exits[1]; game->exits[0].open = 1U; game->exits[1].open = 1U; } static void test_victory_and_draw_window(void) { maze32_maze_t maze; open_test_grid(&maze); maze32_game_t game = active_game(&maze); prepare_open_exits(&game); maze32_game_command_result_t result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_WEST, 1000U); assert(result.outward_exit && !result.finished); assert(game.phase == MAZE32_GAME_PHASE_FINISH_PENDING); assert(!maze32_game_advance_time(&game, 1200U)); result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_TWO, MAZE32_GAME_DIRECTION_EAST, 1200U); assert(result.outward_exit && result.finished); assert(game.phase == MAZE32_GAME_PHASE_FINISHED && game.final_result == MAZE32_GAME_FINAL_DRAW); open_test_grid(&maze); game = active_game(&maze); prepare_open_exits(&game); result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_WEST, 2000U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED); result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_TWO, MAZE32_GAME_DIRECTION_EAST, 2201U); assert(result.result == MAZE32_GAME_RESULT_MATCH_FINISHED && result.finished); assert(!game.players[1].exited && game.final_result == MAZE32_GAME_FINAL_PLAYER_ONE); open_test_grid(&maze); game = active_game(&maze); prepare_open_exits(&game); result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_TWO, MAZE32_GAME_DIRECTION_EAST, 3000U); assert(result.outward_exit && game.final_result == MAZE32_GAME_FINAL_NONE); assert(!maze32_game_advance_time(&game, 3200U)); assert(maze32_game_advance_time(&game, 3201U)); assert(game.final_result == MAZE32_GAME_FINAL_PLAYER_TWO); printf("game outward result window: PASS (200 ms draw boundary, 201 ms winner)\n"); } static void test_persistent_bush_wall_breakthrough(void) { maze32_maze_t maze; open_test_grid(&maze); maze.passages[index_of(2U, 2U)] &= (uint8_t)~MAZE32_PASSAGE_EAST; maze.passages[index_of(3U, 2U)] &= (uint8_t)~MAZE32_PASSAGE_WEST; maze32_game_t game = active_game(&maze); game.players[0].position = (maze32_cell_t){2U, 2U}; for (uint8_t hit = 1U; hit < MAZE32_GAME_BREAKTHROUGH_HITS; ++hit) { maze32_game_command_result_t result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST, hit * 500U); assert(result.result == MAZE32_GAME_RESULT_BLOCKED); assert(result.breakthrough_hits == hit); assert(!result.breakthrough_opened); assert(game.players[0].position.x == 2U); } maze32_game_command_result_t result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST, 2500U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && result.breakthrough_opened); assert(result.breakthrough_hits == MAZE32_GAME_BREAKTHROUGH_HITS); assert(game.players[0].position.x == 3U); assert(game.opened_passages[index_of(2U, 2U)] & MAZE32_PASSAGE_EAST); assert(game.opened_passages[index_of(3U, 2U)] & MAZE32_PASSAGE_WEST); game.players[1].position = (maze32_cell_t){3U, 2U}; result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_TWO, MAZE32_GAME_DIRECTION_WEST, 2600U); assert(result.result == MAZE32_GAME_RESULT_ACCEPTED); assert(game.players[1].position.x == 2U); printf("game persistent bush-wall breakthrough: PASS (five impacts, shared passage)\n"); } int main(void) { test_inventory_drop_and_sabotage(); test_atomic_meeting_return(); test_owner_exit_restrictions(); test_vision_artifact_duration(); test_compass_and_role_projections(); test_victory_and_draw_window(); test_persistent_bush_wall_breakthrough(); puts("game mechanics tests: PASS"); return 0; }