feat(game): complete authoritative gameplay mechanics

This commit is contained in:
2026-09-04 12:25:37 +03:00
parent 115a3bd0a1
commit 9339578dc9
13 changed files with 8795 additions and 109 deletions
+171 -3
View File
@@ -26,6 +26,167 @@ static httpd_handle_t web_server;
static maze32_network_t network;
static maze32_gateway_t gateway;
#ifdef MAZE32_MILESTONE_007_DEVICE_TEST
static maze32_maze_t mechanics_test_maze;
static maze32_maze_workspace_t mechanics_test_workspace;
static maze32_game_t mechanics_test_game;
static maze32_view_t mechanics_test_view;
static char mechanics_test_payload[MAZE32_GAME_VIEW_CHUNK_BYTES];
static int entry_move_for(const maze32_maze_t* maze, maze32_cell_t target,
maze32_cell_t* starting_position, maze32_game_direction_t* direction) {
if (target.y > 0U && maze32_maze_has_passage(maze, target, MAZE32_PASSAGE_NORTH)) {
*starting_position = (maze32_cell_t){target.x, (uint16_t)(target.y - 1U)};
*direction = MAZE32_GAME_DIRECTION_SOUTH;
return 1;
}
if (target.x + 1U < maze->config.width &&
maze32_maze_has_passage(maze, target, MAZE32_PASSAGE_EAST)) {
*starting_position = (maze32_cell_t){(uint16_t)(target.x + 1U), target.y};
*direction = MAZE32_GAME_DIRECTION_WEST;
return 1;
}
if (target.y + 1U < maze->config.height &&
maze32_maze_has_passage(maze, target, MAZE32_PASSAGE_SOUTH)) {
*starting_position = (maze32_cell_t){target.x, (uint16_t)(target.y + 1U)};
*direction = MAZE32_GAME_DIRECTION_NORTH;
return 1;
}
if (target.x > 0U && maze32_maze_has_passage(maze, target, MAZE32_PASSAGE_WEST)) {
*starting_position = (maze32_cell_t){(uint16_t)(target.x - 1U), target.y};
*direction = MAZE32_GAME_DIRECTION_EAST;
return 1;
}
return 0;
}
static maze32_game_direction_t outward_direction(const maze32_maze_t* maze,
maze32_cell_t exit_cell) {
if (exit_cell.x == 0U) {
return MAZE32_GAME_DIRECTION_WEST;
}
if (exit_cell.x + 1U == maze->config.width) {
return MAZE32_GAME_DIRECTION_EAST;
}
if (exit_cell.y == 0U) {
return MAZE32_GAME_DIRECTION_NORTH;
}
return MAZE32_GAME_DIRECTION_SOUTH;
}
static esp_err_t run_mechanics_device_test(void) {
maze32_maze_config_t maze_config = maze32_maze_default_config();
maze32_maze_report_t maze_report;
if (maze32_maze_generate(&mechanics_test_maze, &mechanics_test_workspace, &maze_config,
UINT32_C(0x5a320007), &maze_report) != MAZE32_MAZE_OK) {
ESP_LOGE(TAG, "M007 physical mechanics test could not generate its maze");
return ESP_FAIL;
}
maze32_game_config_t game_config = maze32_game_default_config();
game_config.movement_cooldown_ms = 0U;
size_t free_before = heap_caps_get_free_size(MALLOC_CAP_8BIT);
int64_t started_us = esp_timer_get_time();
if (maze32_game_init(&mechanics_test_game, &mechanics_test_maze, &game_config) !=
MAZE32_GAME_RESULT_ACCEPTED) {
return ESP_FAIL;
}
maze32_game_set_phase(&mechanics_test_game, MAZE32_GAME_PHASE_ACTIVE);
maze32_cell_t entry_position;
maze32_game_direction_t entry_direction;
uint32_t command_sequence = 0U;
if (!entry_move_for(&mechanics_test_maze, mechanics_test_maze.artifacts[0][0], &entry_position,
&entry_direction)) {
return ESP_FAIL;
}
mechanics_test_game.players[0].position = entry_position;
maze32_game_enqueue_move(&mechanics_test_game, MAZE32_GAME_ROLE_PLAYER_ONE, entry_direction,
100U, &command_sequence);
maze32_game_command_result_t artifact_result = maze32_game_process_next(&mechanics_test_game);
if (!entry_move_for(&mechanics_test_maze, mechanics_test_maze.keys[0], &entry_position,
&entry_direction)) {
return ESP_FAIL;
}
mechanics_test_game.players[0].position = entry_position;
maze32_game_enqueue_move(&mechanics_test_game, MAZE32_GAME_ROLE_PLAYER_ONE, entry_direction,
300U, &command_sequence);
maze32_game_command_result_t key_result = maze32_game_process_next(&mechanics_test_game);
if (!entry_move_for(&mechanics_test_maze, mechanics_test_maze.compasses[0], &entry_position,
&entry_direction)) {
return ESP_FAIL;
}
mechanics_test_game.players[0].position = entry_position;
maze32_game_enqueue_move(&mechanics_test_game, MAZE32_GAME_ROLE_PLAYER_ONE, entry_direction,
500U, &command_sequence);
maze32_game_command_result_t compass_result = maze32_game_process_next(&mechanics_test_game);
if (!entry_move_for(&mechanics_test_maze, mechanics_test_maze.exits[0], &entry_position,
&entry_direction)) {
return ESP_FAIL;
}
mechanics_test_game.players[0].position = entry_position;
maze32_game_enqueue_move(&mechanics_test_game, MAZE32_GAME_ROLE_PLAYER_ONE, entry_direction,
700U, &command_sequence);
maze32_game_command_result_t exit_result = maze32_game_process_next(&mechanics_test_game);
maze32_game_enqueue_move(&mechanics_test_game, MAZE32_GAME_ROLE_PLAYER_ONE,
outward_direction(&mechanics_test_maze, mechanics_test_maze.exits[0]),
900U, &command_sequence);
maze32_game_command_result_t outward_result = maze32_game_process_next(&mechanics_test_game);
int finalized = maze32_game_advance_time(&mechanics_test_game, 1101U);
if (artifact_result.result != MAZE32_GAME_RESULT_ACCEPTED ||
!artifact_result.activated_artifact || key_result.result != MAZE32_GAME_RESULT_ACCEPTED ||
!key_result.picked_key || key_result.key != 0U ||
compass_result.compass_hint == MAZE32_GAME_COMPASS_NONE || !exit_result.opened_exit ||
!outward_result.outward_exit || !finalized ||
mechanics_test_game.final_result != MAZE32_GAME_FINAL_PLAYER_ONE ||
maze32_game_project_view(&mechanics_test_game, MAZE32_GAME_ROLE_PLAYER_ONE,
&mechanics_test_view) != MAZE32_GAME_RESULT_ACCEPTED) {
ESP_LOGE(TAG, "M007 physical mechanics transitions failed");
return ESP_FAIL;
}
size_t player_chunks = maze32_game_view_chunk_count(&mechanics_test_view);
size_t payload_bytes = 0U;
if (maze32_game_serialize_view_chunk(&mechanics_test_view, 0U, mechanics_test_payload,
sizeof(mechanics_test_payload),
&payload_bytes) != MAZE32_GAME_RESULT_ACCEPTED ||
maze32_game_project_view(&mechanics_test_game, MAZE32_GAME_ROLE_OBSERVER,
&mechanics_test_view) != MAZE32_GAME_RESULT_ACCEPTED) {
return ESP_FAIL;
}
uint64_t elapsed_us = (uint64_t)(esp_timer_get_time() - started_us);
size_t free_after = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t fixed_bytes =
sizeof(mechanics_test_game) + sizeof(mechanics_test_view) + sizeof(mechanics_test_payload);
ESP_LOGI(TAG,
"M007 events: artifact=%u, key=%u, hint=%u, opened=%u, outward=%u, result=%u, "
"sequence=%" PRIu32,
artifact_result.artifact, key_result.key, compass_result.compass_hint,
exit_result.opened_exit, outward_result.outward_exit, mechanics_test_game.final_result,
mechanics_test_game.state_sequence);
ESP_LOGI(TAG,
"M007 views: player_chunks=%u, first_chunk=%u bytes, observer_cells=%u, "
"observer_objects=%u, observer_chunks=%u",
(unsigned int)player_chunks, (unsigned int)payload_bytes,
mechanics_test_view.cell_count, mechanics_test_view.object_count,
(unsigned int)maze32_game_view_chunk_count(&mechanics_test_view));
ESP_LOGI(TAG, "M007 budget: fixed=%u/%u bytes, runtime=%" PRIu64 "/%u ms, heap_delta=%d",
(unsigned int)fixed_bytes, (unsigned int)MAZE32_GAME_RAM_BUDGET_BYTES,
elapsed_us / 1000U, (unsigned int)MAZE32_GAME_RUNTIME_BUDGET_MS,
(int)(free_before - free_after));
if (fixed_bytes > MAZE32_GAME_RAM_BUDGET_BYTES ||
elapsed_us > (uint64_t)MAZE32_GAME_RUNTIME_BUDGET_MS * 1000U || free_before != free_after) {
ESP_LOGE(TAG, "M007 physical mechanics budget failed");
return ESP_FAIL;
}
ESP_LOGI(TAG, "M007 physical mechanics test: PASS");
return ESP_OK;
}
#endif
#ifdef MAZE32_MILESTONE_006_DEVICE_TEST
static maze32_maze_t game_test_maze;
static maze32_maze_workspace_t game_test_workspace;
@@ -82,7 +243,7 @@ static esp_err_t run_game_device_test(void) {
maze32_game_command_result_t command_result = maze32_game_process_next(&device_test_game);
if (command_result.result != MAZE32_GAME_RESULT_ACCEPTED ||
command_result.state_sequence != 1U ||
maze32_game_project_view(&device_test_game, MAZE32_GAME_ROLE_PLAYER_ONE, 3U,
maze32_game_project_view(&device_test_game, MAZE32_GAME_ROLE_PLAYER_ONE,
&device_test_view) != MAZE32_GAME_RESULT_ACCEPTED) {
return ESP_FAIL;
}
@@ -95,8 +256,8 @@ static esp_err_t run_game_device_test(void) {
&payload_bytes) != MAZE32_GAME_RESULT_ACCEPTED) {
return ESP_FAIL;
}
if (maze32_game_project_view(&device_test_game, MAZE32_GAME_ROLE_OBSERVER, 0U,
&device_test_view) != MAZE32_GAME_RESULT_ACCEPTED) {
if (maze32_game_project_view(&device_test_game, MAZE32_GAME_ROLE_OBSERVER, &device_test_view) !=
MAZE32_GAME_RESULT_ACCEPTED) {
return ESP_FAIL;
}
uint64_t elapsed_us = (uint64_t)(esp_timer_get_time() - started_us);
@@ -270,6 +431,13 @@ void app_main(void) {
}
#endif
#ifdef MAZE32_MILESTONE_007_DEVICE_TEST
if (run_mechanics_device_test() != ESP_OK) {
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)ESP_FAIL);
return;
}
#endif
vTaskDelay(pdMS_TO_TICKS(1500));
ESP_LOGI(TAG, "maze32 firmware version %s starting", application->version);