#include "maze32_game.h" #include #include #include #define MAZE32_GAME_NO_OWNER UINT8_MAX static int role_is_player(maze32_game_role_t role) { return role == MAZE32_GAME_ROLE_PLAYER_ONE || role == MAZE32_GAME_ROLE_PLAYER_TWO; } static int direction_is_valid(maze32_game_direction_t direction) { return direction >= MAZE32_GAME_DIRECTION_NORTH && direction <= MAZE32_GAME_DIRECTION_WEST; } static maze32_passage_t direction_passage(maze32_game_direction_t direction) { static const maze32_passage_t PASSAGES[] = {MAZE32_PASSAGE_NORTH, MAZE32_PASSAGE_EAST, MAZE32_PASSAGE_SOUTH, MAZE32_PASSAGE_WEST}; return PASSAGES[direction]; } static maze32_cell_t moved_cell(maze32_cell_t cell, maze32_game_direction_t direction) { if (direction == MAZE32_GAME_DIRECTION_NORTH) { cell.y--; } else if (direction == MAZE32_GAME_DIRECTION_EAST) { cell.x++; } else if (direction == MAZE32_GAME_DIRECTION_SOUTH) { cell.y++; } else { cell.x--; } return cell; } static uint16_t cell_index(const maze32_maze_t* maze, maze32_cell_t cell) { return (uint16_t)(cell.y * maze->config.width + cell.x); } static int valid_cell(const maze32_maze_t* maze, maze32_cell_t cell) { return cell.x < maze->config.width && cell.y < maze->config.height; } static int cells_equal(maze32_cell_t first, maze32_cell_t second) { return first.x == second.x && first.y == second.y; } 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; return dx <= radius && dy <= radius; } static int direction_leaves_map(const maze32_maze_t* maze, maze32_cell_t cell, maze32_game_direction_t direction) { return (direction == MAZE32_GAME_DIRECTION_NORTH && cell.y == 0U) || (direction == MAZE32_GAME_DIRECTION_EAST && cell.x + 1U == maze->config.width) || (direction == MAZE32_GAME_DIRECTION_SOUTH && cell.y + 1U == maze->config.height) || (direction == MAZE32_GAME_DIRECTION_WEST && cell.x == 0U); } maze32_game_config_t maze32_game_default_config(void) { const maze32_game_config_t config = {.movement_cooldown_ms = 150U}; return config; } maze32_game_result_t maze32_game_init(maze32_game_t* game, const maze32_maze_t* maze, const maze32_game_config_t* config) { if (game == NULL || maze == NULL || config == NULL || maze->config.width == 0U || maze->config.height == 0U || maze->config.width > MAZE32_MAZE_MAX_WIDTH || maze->config.height > MAZE32_MAZE_MAX_HEIGHT || maze->config.landmark_pairs > MAZE32_MAZE_MAX_LANDMARK_PAIRS || maze->config.artifact_pairs > MAZE32_MAZE_MAX_ARTIFACT_PAIRS || !valid_cell(maze, maze->starts[0]) || !valid_cell(maze, maze->starts[1])) { return MAZE32_GAME_RESULT_INVALID_ARGUMENT; } memset(game, 0, sizeof(*game)); game->maze = maze; game->config = *config; game->phase = MAZE32_GAME_PHASE_COUNTDOWN; game->next_command_sequence = 1U; game->first_exited_player = MAZE32_GAME_NO_OWNER; for (uint8_t player = 0U; player < MAZE32_GAME_PLAYER_COUNT; ++player) { game->players[player].position = maze->starts[player]; game->players[player].carried_key = MAZE32_GAME_NO_KEY; game->keys[player].location = MAZE32_GAME_KEY_GROUND; game->keys[player].ground_position = maze->keys[player]; game->keys[player].carrier = MAZE32_GAME_NO_OWNER; } return MAZE32_GAME_RESULT_ACCEPTED; } 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) { game->phase = phase; } } static maze32_game_result_t enqueue_command(maze32_game_t* game, maze32_game_role_t role, maze32_game_command_type_t type, maze32_game_direction_t direction, uint64_t submitted_ms, uint32_t* command_sequence) { if (game == NULL || command_sequence == NULL || (type == MAZE32_GAME_COMMAND_MOVE && !direction_is_valid(direction))) { return MAZE32_GAME_RESULT_INVALID_ARGUMENT; } if (!role_is_player(role)) { return MAZE32_GAME_RESULT_READ_ONLY; } if (game->command_count >= MAZE32_GAME_COMMAND_CAPACITY) { return MAZE32_GAME_RESULT_QUEUE_FULL; } uint8_t tail = (uint8_t)((game->command_head + game->command_count) % MAZE32_GAME_COMMAND_CAPACITY); maze32_game_command_t* command = &game->commands[tail]; command->command_sequence = game->next_command_sequence++; command->type = type; command->role = role; command->direction = direction; command->submitted_ms = submitted_ms; game->command_count++; *command_sequence = command->command_sequence; return MAZE32_GAME_RESULT_ACCEPTED; } maze32_game_result_t maze32_game_enqueue_move(maze32_game_t* game, maze32_game_role_t role, maze32_game_direction_t direction, uint64_t submitted_ms, uint32_t* command_sequence) { return enqueue_command(game, role, MAZE32_GAME_COMMAND_MOVE, direction, submitted_ms, command_sequence); } maze32_game_result_t maze32_game_enqueue_drop_key(maze32_game_t* game, maze32_game_role_t role, uint64_t submitted_ms, uint32_t* command_sequence) { return enqueue_command(game, role, MAZE32_GAME_COMMAND_DROP_KEY, MAZE32_GAME_DIRECTION_NORTH, submitted_ms, command_sequence); } int maze32_game_advance_time(maze32_game_t* game, uint64_t now_ms) { if (game == NULL || game->phase != MAZE32_GAME_PHASE_FINISH_PENDING || game->first_exited_player >= MAZE32_GAME_PLAYER_COUNT) { return 0; } uint64_t first_exit_ms = game->exit_times_ms[game->first_exited_player]; if (now_ms <= first_exit_ms || now_ms - first_exit_ms <= MAZE32_GAME_DRAW_WINDOW_MS) { return 0; } game->phase = MAZE32_GAME_PHASE_FINISHED; game->final_result = game->first_exited_player == 0U ? MAZE32_GAME_FINAL_PLAYER_ONE : MAZE32_GAME_FINAL_PLAYER_TWO; game->state_sequence++; return 1; } static void accept_player_move(maze32_game_player_t* player, uint64_t submitted_ms) { player->last_move_ms = submitted_ms; player->accepted_moves++; player->has_moved = 1U; player->compass_hint = MAZE32_GAME_COMPASS_NONE; if (player->vision_moves_remaining > 0U) { player->vision_moves_remaining--; } } static uint8_t artifact_at_player(const maze32_game_t* game, maze32_cell_t position) { for (uint8_t pair = 0U; pair < game->maze->config.artifact_pairs; ++pair) { for (uint8_t side = 0U; side < 2U; ++side) { uint8_t artifact = (uint8_t)(pair * 2U + side); if (!game->consumed_artifacts[artifact] && cells_equal(game->maze->artifacts[pair][side], position)) { return artifact; } } } return UINT8_MAX; } static uint8_t ground_key_at_player(const maze32_game_t* game, maze32_cell_t position) { for (uint8_t key = 0U; key < MAZE32_GAME_KEY_COUNT; ++key) { if (game->keys[key].location == MAZE32_GAME_KEY_GROUND && cells_equal(game->keys[key].ground_position, position)) { return key; } } return MAZE32_GAME_NO_KEY; } static uint8_t return_keys_to_owners(maze32_game_t* game) { uint8_t carried[MAZE32_GAME_PLAYER_COUNT]; uint8_t returned = 0U; for (uint8_t player = 0U; player < MAZE32_GAME_PLAYER_COUNT; ++player) { carried[player] = game->players[player].carried_key; if (carried[player] != MAZE32_GAME_NO_KEY) { game->players[player].carried_key = MAZE32_GAME_NO_KEY; } } for (uint8_t player = 0U; player < MAZE32_GAME_PLAYER_COUNT; ++player) { uint8_t key = carried[player]; if (key != MAZE32_GAME_NO_KEY) { game->players[key].carried_key = key; game->keys[key].location = MAZE32_GAME_KEY_CARRIED; game->keys[key].carrier = key; returned |= (uint8_t)(1U << key); } } return returned; } static maze32_game_compass_direction_t compass_direction(maze32_cell_t from, maze32_cell_t to) { int horizontal = (to.x > from.x) - (to.x < from.x); int vertical = (to.y > from.y) - (to.y < from.y); if (vertical < 0 && horizontal == 0) { return MAZE32_GAME_COMPASS_NORTH; } if (vertical < 0 && horizontal > 0) { return MAZE32_GAME_COMPASS_NORTH_EAST; } if (vertical == 0 && horizontal > 0) { return MAZE32_GAME_COMPASS_EAST; } if (vertical > 0 && horizontal > 0) { return MAZE32_GAME_COMPASS_SOUTH_EAST; } if (vertical > 0 && horizontal == 0) { return MAZE32_GAME_COMPASS_SOUTH; } if (vertical > 0 && horizontal < 0) { return MAZE32_GAME_COMPASS_SOUTH_WEST; } if (vertical == 0 && horizontal < 0) { return MAZE32_GAME_COMPASS_WEST; } if (vertical < 0 && horizontal < 0) { return MAZE32_GAME_COMPASS_NORTH_WEST; } return MAZE32_GAME_COMPASS_NONE; } static void resolve_cell_entry(maze32_game_t* game, uint8_t player_index, maze32_game_command_result_t* processed) { maze32_game_player_t* player = &game->players[player_index]; uint8_t artifact = artifact_at_player(game, player->position); if (artifact != UINT8_MAX) { game->consumed_artifacts[artifact] = 1U; player->vision_moves_remaining = MAZE32_GAME_VISION_MOVES; processed->activated_artifact = 1U; processed->artifact = artifact; } if (player->carried_key == MAZE32_GAME_NO_KEY) { uint8_t key = ground_key_at_player(game, player->position); if (key != MAZE32_GAME_NO_KEY) { player->carried_key = key; game->keys[key].location = MAZE32_GAME_KEY_CARRIED; game->keys[key].carrier = player_index; processed->picked_key = 1U; processed->key = key; } } uint8_t opponent_index = (uint8_t)(1U - player_index); if (cells_equal(player->position, game->players[opponent_index].position)) { game->meeting_sequence++; processed->meeting = 1U; processed->meeting_sequence = game->meeting_sequence; processed->returned_keys = return_keys_to_owners(game); } if (cells_equal(player->position, game->maze->compasses[player_index])) { player->compass_hint = compass_direction(player->position, game->maze->exits[player_index]); processed->compass_hint = player->compass_hint; } if (!game->exits[player_index].open && cells_equal(player->position, game->maze->exits[player_index]) && player->carried_key == player_index) { game->exits[player_index].open = 1U; processed->opened_exit = 1U; } } static void finish_outward_move(maze32_game_t* game, uint8_t player_index, uint64_t submitted_ms, maze32_game_command_result_t* processed) { maze32_game_player_t* player = &game->players[player_index]; accept_player_move(player, submitted_ms); player->exited = 1U; game->has_exit_time[player_index] = 1U; game->exit_times_ms[player_index] = submitted_ms; processed->moved = 1U; processed->outward_exit = 1U; if (game->first_exited_player == MAZE32_GAME_NO_OWNER) { game->first_exited_player = player_index; game->phase = MAZE32_GAME_PHASE_FINISH_PENDING; } else { game->phase = MAZE32_GAME_PHASE_FINISHED; game->final_result = MAZE32_GAME_FINAL_DRAW; processed->finished = 1U; } } static void process_drop_key(maze32_game_t* game, uint8_t player_index, maze32_game_command_result_t* processed) { maze32_game_player_t* player = &game->players[player_index]; if (player->carried_key == MAZE32_GAME_NO_KEY) { processed->result = MAZE32_GAME_RESULT_NO_KEY; return; } uint8_t key = player->carried_key; player->carried_key = MAZE32_GAME_NO_KEY; game->keys[key].location = MAZE32_GAME_KEY_GROUND; game->keys[key].ground_position = player->position; game->keys[key].carrier = MAZE32_GAME_NO_OWNER; game->state_sequence++; processed->result = MAZE32_GAME_RESULT_ACCEPTED; processed->state_sequence = game->state_sequence; processed->dropped_key = 1U; processed->key = key; } maze32_game_command_result_t maze32_game_process_next(maze32_game_t* game) { maze32_game_command_result_t processed = { .result = MAZE32_GAME_RESULT_INVALID_ARGUMENT, .key = MAZE32_GAME_NO_KEY, .artifact = UINT8_MAX, }; if (game == NULL) { return processed; } processed.state_sequence = game->state_sequence; processed.meeting_sequence = game->meeting_sequence; processed.final_result = game->final_result; if (game->command_count == 0U) { processed.result = MAZE32_GAME_RESULT_QUEUE_EMPTY; return processed; } maze32_game_command_t command = game->commands[game->command_head]; game->command_head = (uint8_t)((game->command_head + 1U) % MAZE32_GAME_COMMAND_CAPACITY); game->command_count--; processed.command_sequence = command.command_sequence; if (maze32_game_advance_time(game, command.submitted_ms)) { processed.result = MAZE32_GAME_RESULT_MATCH_FINISHED; processed.state_sequence = game->state_sequence; processed.final_result = game->final_result; processed.finished = 1U; return processed; } if (game->phase == MAZE32_GAME_PHASE_FINISHED) { processed.result = MAZE32_GAME_RESULT_MATCH_FINISHED; processed.final_result = game->final_result; return processed; } if (game->phase != MAZE32_GAME_PHASE_ACTIVE && game->phase != MAZE32_GAME_PHASE_FINISH_PENDING) { processed.result = MAZE32_GAME_RESULT_PREMATURE; return processed; } uint8_t player_index = (uint8_t)command.role; maze32_game_player_t* player = &game->players[player_index]; if (player->exited) { processed.result = MAZE32_GAME_RESULT_PLAYER_EXITED; return processed; } if (command.type == MAZE32_GAME_COMMAND_DROP_KEY) { process_drop_key(game, player_index, &processed); return processed; } if (player->has_moved && (command.submitted_ms < player->last_move_ms || command.submitted_ms - player->last_move_ms < game->config.movement_cooldown_ms)) { processed.result = MAZE32_GAME_RESULT_RATE_LIMITED; return processed; } if (direction_leaves_map(game->maze, player->position, command.direction)) { if (!cells_equal(player->position, game->maze->exits[player_index]) || !game->exits[player_index].open) { processed.result = MAZE32_GAME_RESULT_BLOCKED; return processed; } 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)) { processed.result = MAZE32_GAME_RESULT_BLOCKED; return processed; } player->position = moved_cell(player->position, command.direction); accept_player_move(player, command.submitted_ms); processed.moved = 1U; resolve_cell_entry(game, player_index, &processed); } game->state_sequence++; processed.result = MAZE32_GAME_RESULT_ACCEPTED; processed.state_sequence = game->state_sequence; processed.meeting_sequence = game->meeting_sequence; processed.final_result = game->final_result; return processed; } uint8_t maze32_game_player_view_radius(const maze32_game_t* game, uint8_t player_index) { if (game == NULL || player_index >= MAZE32_GAME_PLAYER_COUNT) { return 0U; } return game->players[player_index].vision_moves_remaining > 0U ? 6U : 3U; } static void add_object(const maze32_game_t* game, maze32_view_t* view, maze32_cell_t cell, maze32_view_object_kind_t kind, uint8_t owner, uint8_t variant, maze32_cell_t center, uint8_t radius, int reveal_all) { if (view->object_count >= MAZE32_GAME_MAX_OBJECTS || (!reveal_all && !is_visible(center, cell, radius))) { return; } maze32_view_object_t* object = &view->objects[view->object_count++]; object->cell_index = cell_index(game->maze, cell); object->kind = kind; object->owner = owner; object->variant = variant; } static void project_objects(const maze32_game_t* game, maze32_view_t* view, maze32_cell_t center, uint8_t radius, int reveal_all) { for (uint8_t owner = 0U; owner < MAZE32_GAME_PLAYER_COUNT; ++owner) { if (game->keys[owner].location == MAZE32_GAME_KEY_GROUND) { add_object(game, view, game->keys[owner].ground_position, MAZE32_VIEW_OBJECT_KEY, owner, 0U, center, radius, reveal_all); } add_object(game, view, game->maze->exits[owner], MAZE32_VIEW_OBJECT_EXIT, owner, game->exits[owner].open, center, radius, reveal_all); add_object(game, view, game->maze->compasses[owner], MAZE32_VIEW_OBJECT_COMPASS, owner, 0U, center, radius, reveal_all); } for (uint8_t pair = 0U; pair < game->maze->config.landmark_pairs; ++pair) { for (uint8_t side = 0U; side < 2U; ++side) { add_object(game, view, game->maze->landmarks[pair].cells[side], MAZE32_VIEW_OBJECT_LANDMARK, MAZE32_GAME_NO_OWNER, game->maze->landmarks[pair].kind, center, radius, reveal_all); } } for (uint8_t pair = 0U; pair < game->maze->config.artifact_pairs; ++pair) { for (uint8_t side = 0U; side < 2U; ++side) { uint8_t artifact = (uint8_t)(pair * 2U + side); if (!game->consumed_artifacts[artifact]) { add_object(game, view, game->maze->artifacts[pair][side], MAZE32_VIEW_OBJECT_VISION_ARTIFACT, MAZE32_GAME_NO_OWNER, pair, center, radius, reveal_all); } } } } maze32_game_result_t maze32_game_project_view(const maze32_game_t* game, maze32_game_role_t role, maze32_view_t* view) { if (game == NULL || game->maze == NULL || view == NULL || (!role_is_player(role) && role != MAZE32_GAME_ROLE_OBSERVER)) { return MAZE32_GAME_RESULT_INVALID_ARGUMENT; } memset(view, 0, sizeof(*view)); view->role = role; view->phase = game->phase; view->state_sequence = game->state_sequence; view->meeting_sequence = game->meeting_sequence; view->final_result = game->final_result; view->self_carried_key = MAZE32_GAME_NO_KEY; view->opponent_carried_key = MAZE32_GAME_NO_KEY; view->opponent_cell_index = UINT16_MAX; int reveal_all = role == MAZE32_GAME_ROLE_OBSERVER; uint8_t radius = 0U; maze32_cell_t center = {0U, 0U}; if (role_is_player(role)) { uint8_t player_index = (uint8_t)role; maze32_game_player_t player = game->players[player_index]; center = player.position; radius = maze32_game_player_view_radius(game, player_index); view->self_cell_index = cell_index(game->maze, center); view->self_carried_key = player.carried_key; view->self_vision_moves_remaining = player.vision_moves_remaining; view->self_compass_hint = player.compass_hint; view->self_exit_open = game->exits[player_index].open; view->self_exited = player.exited; maze32_game_player_t opponent = game->players[1U - player_index]; if (!opponent.exited && is_visible(center, opponent.position, radius)) { view->opponent_visible = 1U; view->opponent_cell_index = cell_index(game->maze, opponent.position); view->opponent_carried_key = opponent.carried_key; } } else { view->self_cell_index = UINT16_MAX; for (uint8_t player = 0U; player < MAZE32_GAME_PLAYER_COUNT; ++player) { view->observer_player_cell_indices[player] = cell_index(game->maze, game->players[player].position); view->observer_carried_keys[player] = game->players[player].carried_key; view->observer_vision_moves_remaining[player] = game->players[player].vision_moves_remaining; view->observer_compass_hints[player] = game->players[player].compass_hint; view->observer_exited[player] = game->players[player].exited; view->observer_exit_open[player] = game->exits[player].open; view->observer_has_exit_time[player] = game->has_exit_time[player]; view->observer_exit_times_ms[player] = game->exit_times_ms[player]; } } for (uint16_t y = 0U; y < game->maze->config.height; ++y) { for (uint16_t x = 0U; x < game->maze->config.width; ++x) { maze32_cell_t cell = {x, y}; 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->cell_count++; } } } project_objects(game, view, center, radius, reveal_all); return MAZE32_GAME_RESULT_ACCEPTED; } size_t maze32_game_view_chunk_count(const maze32_view_t* view) { if (view == NULL) { return 0U; } size_t cell_chunks = (view->cell_count + MAZE32_GAME_VIEW_CELLS_PER_CHUNK - 1U) / MAZE32_GAME_VIEW_CELLS_PER_CHUNK; size_t object_chunks = (view->object_count + MAZE32_GAME_VIEW_OBJECTS_PER_CHUNK - 1U) / MAZE32_GAME_VIEW_OBJECTS_PER_CHUNK; return cell_chunks + object_chunks; } static int append_text(char* output, size_t output_size, size_t* offset, const char* format, ...) { va_list arguments; va_start(arguments, format); int count = vsnprintf(output + *offset, output_size - *offset, format, arguments); va_end(arguments); if (count < 0 || (size_t)count >= output_size - *offset) { return 0; } *offset += (size_t)count; return 1; } static const char* role_name(maze32_game_role_t role) { if (role == MAZE32_GAME_ROLE_PLAYER_ONE) { return "player1"; } if (role == MAZE32_GAME_ROLE_PLAYER_TWO) { return "player2"; } return "observer"; } static const char* object_name(maze32_view_object_kind_t kind) { static const char* const NAMES[] = {"key", "exit", "compass", "landmark", "vision"}; return NAMES[kind]; } static const char* phase_name(maze32_game_phase_t phase) { static const char* const NAMES[] = {"countdown", "active", "finish_pending", "finished"}; return NAMES[phase]; } static const char* final_result_name(maze32_game_final_result_t result) { static const char* const NAMES[] = {"none", "player1", "player2", "draw"}; return NAMES[result]; } static const char* compass_name(maze32_game_compass_direction_t direction) { static const char* const NAMES[] = {"none", "n", "ne", "e", "se", "s", "sw", "w", "nw"}; return NAMES[direction]; } static int append_nullable_key(char* output, size_t output_size, size_t* offset, const char* name, uint8_t key) { if (key == MAZE32_GAME_NO_KEY) { return append_text(output, output_size, offset, ",\"%s\":null", name); } return append_text(output, output_size, offset, ",\"%s\":%u", name, key); } static int append_player_status(const maze32_view_t* view, char* output, size_t output_size, size_t* offset) { if (!append_text(output, output_size, offset, ",\"self\":%u,\"visionMoves\":%u,\"hint\":\"%s\",\"ownExitOpen\":%u," "\"exited\":%u", view->self_cell_index, view->self_vision_moves_remaining, compass_name(view->self_compass_hint), view->self_exit_open, view->self_exited) || !append_nullable_key(output, output_size, offset, "inventory", view->self_carried_key)) { return 0; } if (view->opponent_visible && (!append_text(output, output_size, offset, ",\"opponent\":%u", view->opponent_cell_index) || !append_nullable_key(output, output_size, offset, "opponentKey", view->opponent_carried_key))) { return 0; } return 1; } static int append_observer_status(const maze32_view_t* view, char* output, size_t output_size, size_t* offset) { if (!append_text(output, output_size, offset, ",\"players\":[")) { return 0; } for (uint8_t player = 0U; player < MAZE32_GAME_PLAYER_COUNT; ++player) { if (!append_text(output, output_size, offset, "%s{\"cell\":%u,\"visionMoves\":%u,\"hint\":\"%s\",\"exited\":%u", player == 0U ? "" : ",", view->observer_player_cell_indices[player], view->observer_vision_moves_remaining[player], compass_name(view->observer_compass_hints[player]), view->observer_exited[player]) || !append_nullable_key(output, output_size, offset, "key", view->observer_carried_keys[player]) || !append_text(output, output_size, offset, "}")) { return 0; } } if (!append_text(output, output_size, offset, "],\"exits\":[%u,%u],\"exitTimes\":[", view->observer_exit_open[0], view->observer_exit_open[1])) { return 0; } for (uint8_t player = 0U; player < MAZE32_GAME_PLAYER_COUNT; ++player) { if (view->observer_has_exit_time[player]) { if (!append_text(output, output_size, offset, "%s%llu", player == 0U ? "" : ",", (unsigned long long)view->observer_exit_times_ms[player])) { return 0; } } else if (!append_text(output, output_size, offset, "%snull", player == 0U ? "" : ",")) { return 0; } } return append_text(output, output_size, offset, "]"); } maze32_game_result_t maze32_game_serialize_view_chunk(const maze32_view_t* view, size_t chunk_index, char* output, size_t output_size, size_t* written) { if (view == NULL || output == NULL || written == NULL || output_size == 0U || view->cell_count > MAZE32_MAZE_MAX_CELLS || view->object_count > MAZE32_GAME_MAX_OBJECTS || view->role > MAZE32_GAME_ROLE_OBSERVER || view->phase > MAZE32_GAME_PHASE_FINISHED || view->final_result > MAZE32_GAME_FINAL_DRAW || view->self_compass_hint > MAZE32_GAME_COMPASS_NORTH_WEST) { return MAZE32_GAME_RESULT_INVALID_ARGUMENT; } size_t total_chunks = maze32_game_view_chunk_count(view); if (chunk_index >= total_chunks) { return MAZE32_GAME_RESULT_INVALID_ARGUMENT; } size_t offset = 0U; if (!append_text(output, output_size, &offset, "{\"v\":1,\"type\":\"view\",\"seq\":%lu,\"payload\":{\"role\":\"%s\"," "\"phase\":\"%s\",\"result\":\"%s\",\"meetingSequence\":%lu," "\"chunk\":%u,\"chunks\":%u", (unsigned long)view->state_sequence, role_name(view->role), phase_name(view->phase), final_result_name(view->final_result), (unsigned long)view->meeting_sequence, (unsigned int)chunk_index, (unsigned int)total_chunks)) { return MAZE32_GAME_RESULT_BUFFER_TOO_SMALL; } int status_appended = 1; if (chunk_index == 0U) { status_appended = view->role == MAZE32_GAME_ROLE_OBSERVER ? append_observer_status(view, output, output_size, &offset) : append_player_status(view, output, output_size, &offset); } if (!status_appended || !append_text(output, output_size, &offset, ",\"cells\":[")) { return MAZE32_GAME_RESULT_BUFFER_TOO_SMALL; } size_t cell_chunks = (view->cell_count + MAZE32_GAME_VIEW_CELLS_PER_CHUNK - 1U) / MAZE32_GAME_VIEW_CELLS_PER_CHUNK; if (chunk_index < cell_chunks) { size_t first = chunk_index * MAZE32_GAME_VIEW_CELLS_PER_CHUNK; size_t end = first + MAZE32_GAME_VIEW_CELLS_PER_CHUNK; if (end > view->cell_count) { end = view->cell_count; } 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)) { return MAZE32_GAME_RESULT_BUFFER_TOO_SMALL; } } } if (!append_text(output, output_size, &offset, "],\"objects\":[")) { return MAZE32_GAME_RESULT_BUFFER_TOO_SMALL; } if (chunk_index >= cell_chunks) { size_t object_chunk = chunk_index - cell_chunks; size_t first = object_chunk * MAZE32_GAME_VIEW_OBJECTS_PER_CHUNK; size_t end = first + MAZE32_GAME_VIEW_OBJECTS_PER_CHUNK; if (end > view->object_count) { end = view->object_count; } for (size_t index = first; index < end; ++index) { const maze32_view_object_t* object = &view->objects[index]; if (object->kind > MAZE32_VIEW_OBJECT_VISION_ARTIFACT) { return MAZE32_GAME_RESULT_INVALID_ARGUMENT; } if (!append_text(output, output_size, &offset, "%s[\"%s\",%u,%u,%u]", index == first ? "" : ",", object_name(object->kind), object->owner, object->variant, object->cell_index)) { return MAZE32_GAME_RESULT_BUFFER_TOO_SMALL; } } } if (!append_text(output, output_size, &offset, "]}}")) { return MAZE32_GAME_RESULT_BUFFER_TOO_SMALL; } *written = offset; return MAZE32_GAME_RESULT_ACCEPTED; } const char* maze32_game_result_name(maze32_game_result_t result) { static const char* const NAMES[] = { "accepted", "invalid_argument", "queue_full", "queue_empty", "read_only", "premature", "rate_limited", "blocked", "buffer_too_small", "no_key", "player_exited", "match_finished", }; if (result < MAZE32_GAME_RESULT_ACCEPTED || result > MAZE32_GAME_RESULT_MATCH_FINISHED) { return "unknown"; } return NAMES[result]; }