Files
maze32/components/maze32_game/maze32_game.c
T

725 lines
31 KiB
C

#include "maze32_game.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#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];
}