feat(game): add persistent bush-wall breakthroughs

This commit is contained in:
2026-09-10 23:12:42 +03:00
parent bb5bd73ebd
commit e10ad3acb8
12 changed files with 354 additions and 39 deletions
+85 -4
View File
@@ -20,6 +20,19 @@ static maze32_passage_t direction_passage(maze32_game_direction_t direction) {
return PASSAGES[direction];
}
static maze32_passage_t opposite_passage(maze32_passage_t passage) {
if (passage == MAZE32_PASSAGE_NORTH) {
return MAZE32_PASSAGE_SOUTH;
}
if (passage == MAZE32_PASSAGE_EAST) {
return MAZE32_PASSAGE_WEST;
}
if (passage == MAZE32_PASSAGE_SOUTH) {
return MAZE32_PASSAGE_NORTH;
}
return MAZE32_PASSAGE_EAST;
}
static maze32_cell_t moved_cell(maze32_cell_t cell, maze32_game_direction_t direction) {
if (direction == MAZE32_GAME_DIRECTION_NORTH) {
cell.y--;
@@ -45,6 +58,43 @@ static int cells_equal(maze32_cell_t first, maze32_cell_t second) {
return first.x == second.x && first.y == second.y;
}
static uint8_t game_passages(const maze32_game_t* game, maze32_cell_t cell) {
return (uint8_t)(game->maze->passages[cell_index(game->maze, cell)] |
game->opened_passages[cell_index(game->maze, cell)]);
}
static int game_has_passage(const maze32_game_t* game, maze32_cell_t cell,
maze32_passage_t passage) {
return (game_passages(game, cell) & passage) != 0U;
}
static void reset_perseverance(maze32_game_player_t* player) {
player->perseverance_hits = 0U;
player->perseverance_last_impact_ms = 0U;
}
static uint8_t record_wall_impact(maze32_game_player_t* player, maze32_cell_t cell,
maze32_game_direction_t direction, uint64_t submitted_ms) {
if (player->perseverance_hits == 0U || !cells_equal(player->perseverance_cell, cell) ||
player->perseverance_direction != direction ||
submitted_ms < player->perseverance_last_impact_ms ||
submitted_ms - player->perseverance_last_impact_ms >= MAZE32_GAME_BREAKTHROUGH_TIMEOUT_MS) {
player->perseverance_cell = cell;
player->perseverance_direction = direction;
player->perseverance_hits = 1U;
} else {
player->perseverance_hits++;
}
player->perseverance_last_impact_ms = submitted_ms;
return player->perseverance_hits;
}
static void reset_all_perseverance(maze32_game_t* game) {
for (uint8_t player = 0U; player < MAZE32_GAME_PLAYER_COUNT; ++player) {
reset_perseverance(&game->players[player]);
}
}
static int is_visible(maze32_cell_t center, maze32_cell_t cell, uint8_t radius) {
uint16_t dx = center.x > cell.x ? center.x - cell.x : cell.x - center.x;
uint16_t dy = center.y > cell.y ? center.y - cell.y : cell.y - center.y;
@@ -95,6 +145,9 @@ maze32_game_result_t maze32_game_init(maze32_game_t* game, const maze32_maze_t*
void maze32_game_set_phase(maze32_game_t* game, maze32_game_phase_t phase) {
if (game != NULL && phase >= MAZE32_GAME_PHASE_COUNTDOWN &&
phase <= MAZE32_GAME_PHASE_FINISHED) {
if (phase != MAZE32_GAME_PHASE_ACTIVE) {
reset_all_perseverance(game);
}
if (phase == MAZE32_GAME_PHASE_ACTIVE &&
(!game->players[0].connected || !game->players[1].connected)) {
game->phase_before_pause = MAZE32_GAME_PHASE_ACTIVE;
@@ -159,6 +212,7 @@ int maze32_game_advance_time(maze32_game_t* game, uint64_t now_ms) {
game->paused_started_ms = 0U;
game->command_head = 0U;
game->command_count = 0U;
reset_all_perseverance(game);
game->state_sequence++;
return 1;
}
@@ -176,6 +230,7 @@ int maze32_game_advance_time(maze32_game_t* game, uint64_t now_ms) {
game->phase = MAZE32_GAME_PHASE_FINISHED;
game->final_result = game->first_exited_player == 0U ? MAZE32_GAME_FINAL_PLAYER_ONE
: MAZE32_GAME_FINAL_PLAYER_TWO;
reset_all_perseverance(game);
game->state_sequence++;
return 1;
}
@@ -219,6 +274,7 @@ maze32_game_result_t maze32_game_set_player_connected(maze32_game_t* game, uint8
}
player->connected = 0U;
reset_perseverance(player);
player->reconnect_deadline_ms = now_ms > UINT64_MAX - MAZE32_GAME_RECONNECT_TIMEOUT_MS
? UINT64_MAX
: now_ms + MAZE32_GAME_RECONNECT_TIMEOUT_MS;
@@ -229,6 +285,7 @@ maze32_game_result_t maze32_game_set_player_connected(maze32_game_t* game, uint8
game->paused_started_ms = now_ms;
game->command_head = 0U;
game->command_count = 0U;
reset_all_perseverance(game);
}
game->state_sequence++;
return MAZE32_GAME_RESULT_ACCEPTED;
@@ -373,6 +430,7 @@ static void finish_outward_move(maze32_game_t* game, uint8_t player_index, uint6
} else {
game->phase = MAZE32_GAME_PHASE_FINISHED;
game->final_result = MAZE32_GAME_FINAL_DRAW;
reset_all_perseverance(game);
processed->finished = 1U;
}
}
@@ -452,6 +510,7 @@ maze32_game_command_result_t maze32_game_process_next(maze32_game_t* game) {
return processed;
}
if (direction_leaves_map(game->maze, player->position, command.direction)) {
reset_perseverance(player);
if (!cells_equal(player->position, game->maze->exits[player_index]) ||
!game->exits[player_index].open) {
processed.result = MAZE32_GAME_RESULT_BLOCKED;
@@ -460,7 +519,27 @@ maze32_game_command_result_t maze32_game_process_next(maze32_game_t* game) {
finish_outward_move(game, player_index, command.submitted_ms, &processed);
} else {
maze32_passage_t passage = direction_passage(command.direction);
if (!maze32_maze_has_passage(game->maze, player->position, passage)) {
if (!game_has_passage(game, player->position, passage)) {
processed.breakthrough_hits = record_wall_impact(
player, player->position, command.direction, command.submitted_ms);
if (processed.breakthrough_hits < MAZE32_GAME_BREAKTHROUGH_HITS) {
processed.result = MAZE32_GAME_RESULT_BLOCKED;
return processed;
}
maze32_cell_t destination = moved_cell(player->position, command.direction);
if (!valid_cell(game->maze, destination)) {
reset_perseverance(player);
processed.result = MAZE32_GAME_RESULT_BLOCKED;
return processed;
}
game->opened_passages[cell_index(game->maze, player->position)] |= passage;
game->opened_passages[cell_index(game->maze, destination)] |= opposite_passage(passage);
processed.breakthrough_opened = 1U;
reset_perseverance(player);
}
if (game_has_passage(game, player->position, passage)) {
reset_perseverance(player);
} else {
processed.result = MAZE32_GAME_RESULT_BLOCKED;
return processed;
}
@@ -589,7 +668,8 @@ maze32_game_result_t maze32_game_project_view(const maze32_game_t* game, maze32_
if (reveal_all || is_visible(center, cell, radius)) {
uint16_t index = cell_index(game->maze, cell);
view->cells[view->cell_count].cell_index = index;
view->cells[view->cell_count].passages = game->maze->passages[index];
view->cells[view->cell_count].passages = game_passages(game, cell);
view->cells[view->cell_count].opened_passages = game->opened_passages[index];
view->cell_count++;
}
}
@@ -781,8 +861,9 @@ maze32_game_result_t maze32_game_serialize_view_chunk(const maze32_view_t* view,
}
for (size_t index = first; index < end; ++index) {
const maze32_view_cell_t* cell = &view->cells[index];
if (!append_text(output, output_size, &offset, "%s[%u,%u]", index == first ? "" : ",",
cell->cell_index, cell->passages)) {
if (!append_text(output, output_size, &offset, "%s[%u,%u,%u]",
index == first ? "" : ",", cell->cell_index, cell->passages,
cell->opened_passages)) {
return MAZE32_GAME_RESULT_BUFFER_TOO_SMALL;
}
}