feat(game): complete authoritative gameplay mechanics
This commit is contained in:
+171
-3
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user