Files
maze32/test/host/test_game_core.c
T

336 lines
15 KiB
C

#include "maze32_game.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
static uint16_t index_of(uint16_t x, uint16_t y) {
return (uint16_t)(y * 8U + x);
}
static void open_test_grid(maze32_maze_t* maze) {
memset(maze, 0, sizeof(*maze));
maze->config.width = 8U;
maze->config.height = 8U;
maze->config.landmark_pairs = 1U;
maze->config.artifact_pairs = 1U;
for (uint16_t y = 0U; y < 8U; ++y) {
for (uint16_t x = 0U; x < 8U; ++x) {
uint8_t passages = 0U;
if (y > 0U) {
passages |= MAZE32_PASSAGE_NORTH;
}
if (x < 7U) {
passages |= MAZE32_PASSAGE_EAST;
}
if (y < 7U) {
passages |= MAZE32_PASSAGE_SOUTH;
}
if (x > 0U) {
passages |= MAZE32_PASSAGE_WEST;
}
maze->passages[index_of(x, y)] = passages;
}
}
maze->starts[0] = (maze32_cell_t){1U, 1U};
maze->starts[1] = (maze32_cell_t){6U, 6U};
maze->keys[0] = (maze32_cell_t){2U, 1U};
maze->keys[1] = (maze32_cell_t){7U, 7U};
maze->exits[0] = (maze32_cell_t){0U, 0U};
maze->exits[1] = (maze32_cell_t){7U, 6U};
maze->compasses[0] = (maze32_cell_t){1U, 0U};
maze->compasses[1] = (maze32_cell_t){6U, 7U};
maze->landmarks[0].cells[0] = (maze32_cell_t){0U, 1U};
maze->landmarks[0].cells[1] = (maze32_cell_t){7U, 6U};
maze->landmarks[0].kind = 4U;
maze->artifacts[0][0] = (maze32_cell_t){2U, 2U};
maze->artifacts[0][1] = (maze32_cell_t){6U, 6U};
}
static maze32_game_t initialized_game(maze32_maze_t* maze) {
maze32_game_t game;
maze32_game_config_t config = maze32_game_default_config();
assert(config.movement_cooldown_ms == 150U);
assert(maze32_game_init(&game, maze, &config) == MAZE32_GAME_RESULT_ACCEPTED);
return game;
}
static void assert_position(const maze32_game_t* game, uint8_t player, uint16_t x, uint16_t y) {
assert(game->players[player].position.x == x);
assert(game->players[player].position.y == y);
}
static void test_movement_cooldown_and_ordering(void) {
maze32_maze_t maze;
open_test_grid(&maze);
maze32_game_t game = initialized_game(&maze);
uint32_t sequence = 0U;
assert(maze32_game_enqueue_move(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST,
100U, &sequence) == MAZE32_GAME_RESULT_ACCEPTED);
assert(sequence == 1U);
maze32_game_command_result_t result = maze32_game_process_next(&game);
assert(result.result == MAZE32_GAME_RESULT_PREMATURE);
assert(result.command_sequence == 1U);
assert(game.state_sequence == 0U);
assert_position(&game, 0U, 1U, 1U);
maze32_game_set_phase(&game, MAZE32_GAME_PHASE_ACTIVE);
assert(maze32_game_enqueue_move(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST,
100U, &sequence) == MAZE32_GAME_RESULT_ACCEPTED);
assert(sequence == 2U);
assert(maze32_game_enqueue_move(&game, MAZE32_GAME_ROLE_PLAYER_TWO, MAZE32_GAME_DIRECTION_WEST,
100U, &sequence) == MAZE32_GAME_RESULT_ACCEPTED);
assert(sequence == 3U);
result = maze32_game_process_next(&game);
assert(result.result == MAZE32_GAME_RESULT_ACCEPTED);
assert(result.command_sequence == 2U && result.state_sequence == 1U);
assert_position(&game, 0U, 2U, 1U);
assert_position(&game, 1U, 6U, 6U);
result = maze32_game_process_next(&game);
assert(result.result == MAZE32_GAME_RESULT_ACCEPTED);
assert(result.command_sequence == 3U && result.state_sequence == 2U);
assert_position(&game, 1U, 5U, 6U);
assert(maze32_game_enqueue_move(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_SOUTH,
200U, &sequence) == MAZE32_GAME_RESULT_ACCEPTED);
result = maze32_game_process_next(&game);
assert(result.result == MAZE32_GAME_RESULT_RATE_LIMITED);
assert(game.state_sequence == 2U && game.players[0].last_move_ms == 100U);
assert_position(&game, 0U, 2U, 1U);
assert(maze32_game_enqueue_move(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_SOUTH,
250U, &sequence) == MAZE32_GAME_RESULT_ACCEPTED);
result = maze32_game_process_next(&game);
assert(result.result == MAZE32_GAME_RESULT_ACCEPTED);
assert_position(&game, 0U, 2U, 2U);
assert(game.players[0].accepted_moves == 2U);
uint16_t current = index_of(2U, 2U);
maze.passages[current] &= (uint8_t)~MAZE32_PASSAGE_EAST;
assert(maze32_game_enqueue_move(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST,
400U, &sequence) == MAZE32_GAME_RESULT_ACCEPTED);
result = maze32_game_process_next(&game);
assert(result.result == MAZE32_GAME_RESULT_BLOCKED);
assert(game.state_sequence == 3U && game.players[0].last_move_ms == 250U);
assert_position(&game, 0U, 2U, 2U);
assert(maze32_game_enqueue_move(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_WEST,
200U, &sequence) == MAZE32_GAME_RESULT_ACCEPTED);
result = maze32_game_process_next(&game);
assert(result.result == MAZE32_GAME_RESULT_RATE_LIMITED);
assert_position(&game, 0U, 2U, 2U);
assert(maze32_game_enqueue_move(&game, MAZE32_GAME_ROLE_OBSERVER, MAZE32_GAME_DIRECTION_EAST,
500U, &sequence) == MAZE32_GAME_RESULT_READ_ONLY);
assert(maze32_game_enqueue_move(&game, MAZE32_GAME_ROLE_PLAYER_ONE, (maze32_game_direction_t)99,
500U, &sequence) == MAZE32_GAME_RESULT_INVALID_ARGUMENT);
assert(game.state_sequence == 3U);
printf("game movement, cooldown, and FIFO ordering: PASS (commands 1-7, states 0-3)\n");
}
static void test_bounded_queue(void) {
maze32_maze_t maze;
open_test_grid(&maze);
maze32_game_t game = initialized_game(&maze);
uint32_t sequence = 0U;
for (uint32_t index = 0U; index < MAZE32_GAME_COMMAND_CAPACITY; ++index) {
assert(maze32_game_enqueue_move(&game, MAZE32_GAME_ROLE_PLAYER_ONE,
MAZE32_GAME_DIRECTION_EAST, index,
&sequence) == MAZE32_GAME_RESULT_ACCEPTED);
assert(sequence == index + 1U);
}
assert(maze32_game_enqueue_move(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST,
20U, &sequence) == MAZE32_GAME_RESULT_QUEUE_FULL);
assert(game.next_command_sequence == MAZE32_GAME_COMMAND_CAPACITY + 1U);
assert(game.state_sequence == 0U);
printf("game bounded command queue: PASS (%u entries)\n", MAZE32_GAME_COMMAND_CAPACITY);
}
static void test_shared_cell_meeting(void) {
maze32_maze_t maze;
open_test_grid(&maze);
maze.starts[1] = (maze32_cell_t){2U, 1U};
maze32_game_t game = initialized_game(&maze);
maze32_game_set_phase(&game, MAZE32_GAME_PHASE_ACTIVE);
uint32_t sequence = 0U;
assert(maze32_game_enqueue_move(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST,
10U, &sequence) == MAZE32_GAME_RESULT_ACCEPTED);
maze32_game_command_result_t result = maze32_game_process_next(&game);
assert(result.result == MAZE32_GAME_RESULT_ACCEPTED);
assert(result.meeting == 1U && result.meeting_sequence == 1U);
assert_position(&game, 0U, 2U, 1U);
assert_position(&game, 1U, 2U, 1U);
result = maze32_game_process_next(&game);
assert(result.result == MAZE32_GAME_RESULT_QUEUE_EMPTY);
assert(result.meeting == 0U && result.meeting_sequence == 1U);
printf("game shared-cell meeting event: PASS (one event)\n");
}
static int view_has_cell(const maze32_view_t* view, uint16_t cell_index) {
for (uint16_t index = 0U; index < view->cell_count; ++index) {
if (view->cells[index].cell_index == cell_index) {
return 1;
}
}
return 0;
}
static int view_has_object(const maze32_view_t* view, maze32_view_object_kind_t kind,
uint16_t cell_index) {
for (uint8_t index = 0U; index < view->object_count; ++index) {
if (view->objects[index].kind == kind && view->objects[index].cell_index == cell_index) {
return 1;
}
}
return 0;
}
static void serialize_and_inspect(const maze32_view_t* view, int expect_hidden) {
char payload[MAZE32_GAME_VIEW_CHUNK_BYTES];
size_t chunks = maze32_game_view_chunk_count(view);
int saw_last_cell = 0;
int saw_hidden_key = 0;
int saw_players = 0;
for (size_t chunk = 0U; chunk < chunks; ++chunk) {
size_t written = 0U;
assert(maze32_game_serialize_view_chunk(view, chunk, payload, sizeof(payload), &written) ==
MAZE32_GAME_RESULT_ACCEPTED);
assert(written < sizeof(payload));
assert(payload[written] == '\0');
assert(strstr(payload, "{\"v\":1,\"type\":\"view\",\"seq\":") == payload);
assert(strstr(payload, "\"phase\":\"countdown\"") != NULL);
assert(strstr(payload, "\"width\":8,\"height\":8") != NULL);
saw_last_cell |= strstr(payload, "[63,") != NULL;
saw_hidden_key |= strstr(payload, "[\"key\",1,0,63]") != NULL;
saw_players |= strstr(payload, "\"players\":[") != NULL;
if (expect_hidden) {
assert(strstr(payload, "\"opponent\":63") == NULL);
}
}
if (expect_hidden) {
assert(!saw_last_cell && !saw_hidden_key && !saw_players);
} else {
assert(saw_last_cell && saw_hidden_key && saw_players);
}
}
static void test_visibility_and_serialization(void) {
maze32_maze_t maze;
open_test_grid(&maze);
maze32_game_t game = initialized_game(&maze);
maze32_view_t view;
game.players[0].position = (maze32_cell_t){3U, 3U};
game.players[1].position = (maze32_cell_t){4U, 4U};
maze.passages[index_of(3U, 3U)] = 0U;
assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_PLAYER_ONE, &view) ==
MAZE32_GAME_RESULT_ACCEPTED);
assert(view.cell_count == 49U);
assert(view_has_cell(&view, index_of(6U, 6U)));
assert(view.opponent_visible && view.opponent_cell_index == index_of(4U, 4U));
game.players[0].position = (maze32_cell_t){0U, 0U};
game.players[1].position = (maze32_cell_t){7U, 7U};
assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_PLAYER_ONE, &view) ==
MAZE32_GAME_RESULT_ACCEPTED);
assert(view.cell_count == 16U);
assert(!view.opponent_visible && view.opponent_cell_index == UINT16_MAX);
assert(view_has_object(&view, MAZE32_VIEW_OBJECT_KEY, index_of(2U, 1U)));
assert(!view_has_object(&view, MAZE32_VIEW_OBJECT_KEY, index_of(7U, 7U)));
serialize_and_inspect(&view, 1);
assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_PLAYER_TWO, &view) ==
MAZE32_GAME_RESULT_ACCEPTED);
assert(view.cell_count == 16U && view.self_cell_index == index_of(7U, 7U));
assert(!view.opponent_visible && !view_has_cell(&view, index_of(0U, 0U)));
assert(view_has_object(&view, MAZE32_VIEW_OBJECT_KEY, index_of(7U, 7U)));
assert(!view_has_object(&view, MAZE32_VIEW_OBJECT_KEY, index_of(2U, 1U)));
char player_two_payload[MAZE32_GAME_VIEW_CHUNK_BYTES];
for (size_t chunk = 0U; chunk < maze32_game_view_chunk_count(&view); ++chunk) {
size_t written = 0U;
assert(maze32_game_serialize_view_chunk(&view, chunk, player_two_payload,
sizeof(player_two_payload),
&written) == MAZE32_GAME_RESULT_ACCEPTED);
assert(strstr(player_two_payload, "\"role\":\"player2\"") != NULL);
assert(strstr(player_two_payload, "\"opponent\":0") == NULL);
assert(strstr(player_two_payload, "[\"key\",0,0,10]") == NULL);
}
game.players[0].vision_moves_remaining = 1U;
assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_PLAYER_ONE, &view) ==
MAZE32_GAME_RESULT_ACCEPTED);
assert(view.cell_count == 49U);
assert(!view_has_cell(&view, index_of(7U, 7U)));
game.players[0].position = (maze32_cell_t){3U, 3U};
assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_PLAYER_ONE, &view) ==
MAZE32_GAME_RESULT_ACCEPTED);
assert(view.cell_count == 64U);
assert(view.opponent_visible);
assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_OBSERVER, &view) ==
MAZE32_GAME_RESULT_ACCEPTED);
assert(view.cell_count == 64U);
assert(view.object_count == 10U);
assert(view.observer_player_cell_indices[0] == index_of(3U, 3U));
assert(view.observer_player_cell_indices[1] == index_of(7U, 7U));
assert(view.phase == MAZE32_GAME_PHASE_COUNTDOWN);
serialize_and_inspect(&view, 0);
printf("game visibility and payload security: PASS (r3=49/16, r6=64/49, observer=64)\n");
}
static void test_default_map_chunk_bounds(void) {
maze32_maze_t maze;
maze32_maze_workspace_t workspace;
maze32_maze_config_t maze_config = maze32_maze_default_config();
maze32_maze_report_t report;
assert(maze32_maze_generate(&maze, &workspace, &maze_config, UINT32_C(0x5a320006), &report) ==
MAZE32_MAZE_OK);
maze32_game_t game = initialized_game(&maze);
game.phase = MAZE32_GAME_PHASE_FINISHED;
game.final_result = MAZE32_GAME_FINAL_DRAW;
game.state_sequence = UINT32_MAX;
game.meeting_sequence = UINT32_MAX;
for (uint8_t player = 0U; player < MAZE32_GAME_PLAYER_COUNT; ++player) {
game.players[player].vision_moves_remaining = MAZE32_GAME_VISION_MOVES;
game.players[player].compass_hint = MAZE32_GAME_COMPASS_NORTH_WEST;
game.players[player].exited = 1U;
game.exits[player].open = 1U;
game.has_exit_time[player] = 1U;
game.exit_times_ms[player] = UINT64_MAX - player;
}
maze32_view_t view;
assert(maze32_game_project_view(&game, MAZE32_GAME_ROLE_OBSERVER, &view) ==
MAZE32_GAME_RESULT_ACCEPTED);
assert(view.cell_count == 8192U && view.object_count == 30U);
size_t chunks = maze32_game_view_chunk_count(&view);
assert(chunks == 688U);
char payload[MAZE32_GAME_VIEW_CHUNK_BYTES];
size_t largest_chunk = 0U;
for (size_t chunk = 0U; chunk < chunks; ++chunk) {
size_t written = 0U;
assert(maze32_game_serialize_view_chunk(&view, chunk, payload, sizeof(payload), &written) ==
MAZE32_GAME_RESULT_ACCEPTED);
if (chunk == 0U) {
assert(strstr(payload, "\"connected\":1") != NULL);
}
if (written > largest_chunk) {
largest_chunk = written;
}
}
assert(largest_chunk < MAZE32_GAME_VIEW_CHUNK_BYTES);
printf("game default observer serialization: PASS (%u chunks, maximum %u bytes)\n",
(unsigned int)chunks, (unsigned int)largest_chunk);
}
int main(void) {
test_movement_cooldown_and_ordering();
test_bounded_queue();
test_shared_cell_meeting();
test_visibility_and_serialization();
test_default_map_chunk_bounds();
puts("game core tests: PASS");
return 0;
}