feat(game): add persistent bush-wall breakthroughs
This commit is contained in:
@@ -10,6 +10,8 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#define MAZE32_GAME_PLAYER_COUNT 2U
|
||||
#define MAZE32_GAME_BREAKTHROUGH_HITS 5U
|
||||
#define MAZE32_GAME_BREAKTHROUGH_TIMEOUT_MS 3000U
|
||||
#define MAZE32_GAME_COMMAND_CAPACITY 16U
|
||||
#define MAZE32_GAME_KEY_COUNT 2U
|
||||
#define MAZE32_GAME_NO_KEY UINT8_MAX
|
||||
@@ -101,6 +103,10 @@ typedef struct {
|
||||
uint8_t exited;
|
||||
uint8_t connected;
|
||||
uint64_t reconnect_deadline_ms;
|
||||
maze32_cell_t perseverance_cell;
|
||||
maze32_game_direction_t perseverance_direction;
|
||||
uint64_t perseverance_last_impact_ms;
|
||||
uint8_t perseverance_hits;
|
||||
} maze32_game_player_t;
|
||||
|
||||
typedef enum {
|
||||
@@ -139,6 +145,8 @@ typedef struct {
|
||||
uint8_t activated_artifact;
|
||||
uint8_t opened_exit;
|
||||
uint8_t outward_exit;
|
||||
uint8_t breakthrough_hits;
|
||||
uint8_t breakthrough_opened;
|
||||
uint8_t finished;
|
||||
uint8_t key;
|
||||
uint8_t artifact;
|
||||
@@ -154,6 +162,7 @@ typedef struct {
|
||||
maze32_game_key_t keys[MAZE32_GAME_KEY_COUNT];
|
||||
maze32_game_exit_t exits[MAZE32_GAME_PLAYER_COUNT];
|
||||
uint8_t consumed_artifacts[MAZE32_GAME_MAX_ARTIFACTS];
|
||||
uint8_t opened_passages[MAZE32_MAZE_MAX_CELLS];
|
||||
maze32_game_command_t commands[MAZE32_GAME_COMMAND_CAPACITY];
|
||||
uint8_t command_head;
|
||||
uint8_t command_count;
|
||||
@@ -179,6 +188,7 @@ typedef enum {
|
||||
typedef struct {
|
||||
uint16_t cell_index;
|
||||
uint8_t passages;
|
||||
uint8_t opened_passages;
|
||||
} maze32_view_cell_t;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -722,6 +722,20 @@ static void send_game_error(maze32_gateway_t* gateway, int socket_fd, const char
|
||||
send_error(gateway, socket_fd, request_id, maze32_game_result_name(result), message);
|
||||
}
|
||||
|
||||
static void send_breakthrough_impact(maze32_gateway_t* gateway, int socket_fd,
|
||||
const char* request_id, uint8_t hits) {
|
||||
char response[MAZE32_GATEWAY_OUTGOING_LIMIT] = "";
|
||||
int written = snprintf(response, sizeof(response),
|
||||
"{\"v\":1,\"type\":\"error\",\"seq\":%" PRIu32
|
||||
",\"requestId\":\"%s\",\"payload\":{\"code\":\"blocked\","
|
||||
"\"message\":\"Там стена. Очень убедительная\","
|
||||
"\"breakthroughHits\":%u}}",
|
||||
next_server_sequence(gateway), request_id, (unsigned int)hits);
|
||||
if (written >= 0 && (size_t)written < sizeof(response)) {
|
||||
enqueue(gateway, socket_fd, response);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_game_command(maze32_gateway_t* gateway, const maze32_session_t* session,
|
||||
const maze32_client_message_t* message) {
|
||||
if (session->role == MAZE32_ROLE_OBSERVER) {
|
||||
@@ -753,7 +767,12 @@ static void handle_game_command(maze32_gateway_t* gateway, const maze32_session_
|
||||
if (finished_now) {
|
||||
complete_match(gateway);
|
||||
}
|
||||
send_game_error(gateway, session->connection_id, message->request_id, result);
|
||||
if (result == MAZE32_GAME_RESULT_BLOCKED && processed.breakthrough_hits > 0U) {
|
||||
send_breakthrough_impact(gateway, session->connection_id, message->request_id,
|
||||
processed.breakthrough_hits);
|
||||
} else {
|
||||
send_game_error(gateway, session->connection_id, message->request_id, result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
char acknowledgement[160] = "";
|
||||
|
||||
Reference in New Issue
Block a user