Files

717 lines
31 KiB
C

#include "maze32_maze.h"
#include <limits.h>
#include <string.h>
typedef struct {
uint32_t state;
} maze32_rng_t;
static uint16_t cell_count(const maze32_maze_t* maze) {
return (uint16_t)(maze->config.width * maze->config.height);
}
static uint32_t next_random(maze32_rng_t* random) {
uint32_t value = random->state;
value ^= value << 13U;
value ^= value >> 17U;
value ^= value << 5U;
random->state = value;
return value;
}
static uint32_t bounded_random(maze32_rng_t* random, uint32_t limit) {
return limit == 0U ? 0U : next_random(random) % limit;
}
static int config_is_valid(const maze32_maze_config_t* config) {
if (config == NULL || config->width < 8U || config->height < 8U ||
config->width > MAZE32_MAZE_MAX_WIDTH || config->height > MAZE32_MAZE_MAX_HEIGHT ||
(config->width & 1U) != 0U || (config->height & 1U) != 0U ||
config->cross_half_pairs < 2U || config->cross_half_pairs > config->height / 2U ||
config->minimum_exit_separation > config->width + config->height - 2U ||
config->landmark_pairs > MAZE32_MAZE_MAX_LANDMARK_PAIRS || config->artifact_pairs == 0U ||
config->artifact_pairs > MAZE32_MAZE_MAX_ARTIFACT_PAIRS || config->maximum_attempts == 0U) {
return 0;
}
return (uint32_t)config->width * config->height <= MAZE32_MAZE_MAX_CELLS;
}
static int cells_equal(maze32_cell_t first, maze32_cell_t second) {
return first.x == second.x && first.y == second.y;
}
static maze32_cell_t cell_from_index(const maze32_maze_t* maze, uint16_t index) {
maze32_cell_t cell = {
.x = (uint16_t)(index % maze->config.width),
.y = (uint16_t)(index / maze->config.width),
};
return cell;
}
uint16_t maze32_maze_cell_index(const maze32_maze_t* maze, maze32_cell_t cell) {
if (maze == NULL || cell.x >= maze->config.width || cell.y >= maze->config.height) {
return UINT16_MAX;
}
return (uint16_t)(cell.y * maze->config.width + cell.x);
}
maze32_cell_t maze32_maze_rotate(const maze32_maze_t* maze, maze32_cell_t cell) {
maze32_cell_t rotated = {UINT16_MAX, UINT16_MAX};
if (maze != NULL && cell.x < maze->config.width && cell.y < maze->config.height) {
rotated.x = (uint16_t)(maze->config.width - 1U - cell.x);
rotated.y = (uint16_t)(maze->config.height - 1U - cell.y);
}
return rotated;
}
static maze32_passage_t opposite_passage(maze32_passage_t passage) {
switch (passage) {
case MAZE32_PASSAGE_NORTH:
return MAZE32_PASSAGE_SOUTH;
case MAZE32_PASSAGE_EAST:
return MAZE32_PASSAGE_WEST;
case MAZE32_PASSAGE_SOUTH:
return MAZE32_PASSAGE_NORTH;
case MAZE32_PASSAGE_WEST:
return MAZE32_PASSAGE_EAST;
default:
return 0;
}
}
static int neighbor_for(const maze32_maze_t* maze, uint16_t index, maze32_passage_t passage,
uint16_t* neighbor) {
maze32_cell_t cell = cell_from_index(maze, index);
if (passage == MAZE32_PASSAGE_NORTH && cell.y > 0U) {
cell.y--;
} else if (passage == MAZE32_PASSAGE_EAST && cell.x + 1U < maze->config.width) {
cell.x++;
} else if (passage == MAZE32_PASSAGE_SOUTH && cell.y + 1U < maze->config.height) {
cell.y++;
} else if (passage == MAZE32_PASSAGE_WEST && cell.x > 0U) {
cell.x--;
} else {
return 0;
}
*neighbor = maze32_maze_cell_index(maze, cell);
return 1;
}
static void open_edge(maze32_maze_t* maze, uint16_t first, uint16_t second,
maze32_passage_t passage) {
maze->passages[first] |= (uint8_t)passage;
maze->passages[second] |= (uint8_t)opposite_passage(passage);
}
static void open_symmetric_edge(maze32_maze_t* maze, uint16_t first, uint16_t second,
maze32_passage_t passage) {
open_edge(maze, first, second, passage);
maze32_cell_t rotated_first = maze32_maze_rotate(maze, cell_from_index(maze, first));
maze32_cell_t rotated_second = maze32_maze_rotate(maze, cell_from_index(maze, second));
open_edge(maze, maze32_maze_cell_index(maze, rotated_first),
maze32_maze_cell_index(maze, rotated_second), opposite_passage(passage));
}
static void clear_visited(maze32_maze_workspace_t* workspace) {
memset(workspace->visited, 0, sizeof(workspace->visited));
}
static int is_visited(const maze32_maze_workspace_t* workspace, uint16_t index) {
return (workspace->visited[index / 8U] & (uint8_t)(1U << (index % 8U))) != 0U;
}
static void mark_visited(maze32_maze_workspace_t* workspace, uint16_t index) {
workspace->visited[index / 8U] |= (uint8_t)(1U << (index % 8U));
}
static int build_half_tree(maze32_maze_t* maze, maze32_maze_workspace_t* workspace,
maze32_rng_t* random) {
static const maze32_passage_t DIRECTIONS[] = {
MAZE32_PASSAGE_NORTH,
MAZE32_PASSAGE_EAST,
MAZE32_PASSAGE_SOUTH,
MAZE32_PASSAGE_WEST,
};
uint16_t half_width = maze->config.width / 2U;
uint16_t target_cells = (uint16_t)(half_width * maze->config.height);
uint16_t visited_cells = 1U;
size_t stack_size = 1U;
clear_visited(workspace);
workspace->traversal[0] = 0U;
mark_visited(workspace, 0U);
while (stack_size > 0U) {
uint16_t current = workspace->traversal[stack_size - 1U];
maze32_passage_t choices[4];
uint16_t neighbors[4];
size_t choice_count = 0U;
for (size_t direction_index = 0U; direction_index < 4U; ++direction_index) {
uint16_t neighbor = 0U;
if (neighbor_for(maze, current, DIRECTIONS[direction_index], &neighbor)) {
maze32_cell_t neighbor_cell = cell_from_index(maze, neighbor);
if (neighbor_cell.x < half_width && !is_visited(workspace, neighbor)) {
choices[choice_count] = DIRECTIONS[direction_index];
neighbors[choice_count] = neighbor;
choice_count++;
}
}
}
if (choice_count == 0U) {
stack_size--;
continue;
}
size_t selected = bounded_random(random, (uint32_t)choice_count);
open_symmetric_edge(maze, current, neighbors[selected], choices[selected]);
mark_visited(workspace, neighbors[selected]);
workspace->traversal[stack_size++] = neighbors[selected];
visited_cells++;
}
return visited_cells == target_cells;
}
static int add_cycle_pairs(maze32_maze_t* maze, maze32_rng_t* random) {
uint16_t half_width = maze->config.width / 2U;
uint16_t added = 0U;
uint32_t attempt_limit = (uint32_t)maze->config.extra_cycle_pairs * 64U + 256U;
for (uint32_t attempt = 0U; attempt < attempt_limit && added < maze->config.extra_cycle_pairs;
++attempt) {
maze32_cell_t cell = {
.x = (uint16_t)bounded_random(random, half_width),
.y = (uint16_t)bounded_random(random, maze->config.height),
};
maze32_passage_t passage =
bounded_random(random, 2U) == 0U ? MAZE32_PASSAGE_EAST : MAZE32_PASSAGE_SOUTH;
uint16_t first = maze32_maze_cell_index(maze, cell);
uint16_t second = 0U;
if (!neighbor_for(maze, first, passage, &second) ||
cell_from_index(maze, second).x >= half_width ||
(maze->passages[first] & passage) != 0U) {
continue;
}
open_symmetric_edge(maze, first, second, passage);
added++;
}
return added == maze->config.extra_cycle_pairs;
}
static int add_cross_half_connections(maze32_maze_t* maze, maze32_maze_workspace_t* workspace,
maze32_rng_t* random) {
clear_visited(workspace);
uint16_t added_pairs = 0U;
uint16_t half_height = maze->config.height / 2U;
while (added_pairs < maze->config.cross_half_pairs) {
uint16_t y = (uint16_t)bounded_random(random, half_height);
if (is_visited(workspace, y)) {
continue;
}
mark_visited(workspace, y);
maze32_cell_t left = {(uint16_t)(maze->config.width / 2U - 1U), y};
uint16_t first = maze32_maze_cell_index(maze, left);
uint16_t second = 0U;
if (!neighbor_for(maze, first, MAZE32_PASSAGE_EAST, &second)) {
return 0;
}
open_symmetric_edge(maze, first, second, MAZE32_PASSAGE_EAST);
added_pairs++;
}
return 1;
}
static uint16_t calculate_distances(const maze32_maze_t* maze, maze32_maze_workspace_t* workspace,
maze32_cell_t start) {
uint16_t count = cell_count(maze);
for (uint16_t index = 0U; index < count; ++index) {
workspace->distances[index] = UINT16_MAX;
}
uint16_t start_index = maze32_maze_cell_index(maze, start);
size_t head = 0U;
size_t tail = 1U;
workspace->traversal[0] = start_index;
workspace->distances[start_index] = 0U;
while (head < tail) {
uint16_t current = workspace->traversal[head++];
const maze32_passage_t directions[] = {MAZE32_PASSAGE_NORTH, MAZE32_PASSAGE_EAST,
MAZE32_PASSAGE_SOUTH, MAZE32_PASSAGE_WEST};
for (size_t direction_index = 0U; direction_index < 4U; ++direction_index) {
maze32_passage_t passage = directions[direction_index];
uint16_t neighbor = 0U;
if ((maze->passages[current] & passage) != 0U &&
neighbor_for(maze, current, passage, &neighbor) &&
workspace->distances[neighbor] == UINT16_MAX) {
workspace->distances[neighbor] = (uint16_t)(workspace->distances[current] + 1U);
workspace->traversal[tail++] = neighbor;
}
}
}
return (uint16_t)tail;
}
static int required_cell_occupied(const maze32_maze_t* maze, maze32_cell_t cell,
unsigned int placement_stage) {
unsigned int pair_counts[] = {2U, 2U, 2U, 2U};
const maze32_cell_t* required[] = {maze->starts, maze->keys, maze->exits, maze->compasses};
for (unsigned int group = 0U; group < placement_stage && group < 4U; ++group) {
for (unsigned int index = 0U; index < pair_counts[group]; ++index) {
if (cells_equal(cell, required[group][index])) {
return 1;
}
}
}
unsigned int placed_landmarks = placement_stage > 4U ? placement_stage - 4U : 0U;
if (placed_landmarks > maze->config.landmark_pairs) {
placed_landmarks = maze->config.landmark_pairs;
}
for (unsigned int pair = 0U; pair < placed_landmarks; ++pair) {
if (cells_equal(cell, maze->landmarks[pair].cells[0]) ||
cells_equal(cell, maze->landmarks[pair].cells[1])) {
return 1;
}
}
unsigned int artifact_stage = 4U + maze->config.landmark_pairs;
unsigned int placed_artifacts =
placement_stage > artifact_stage ? placement_stage - artifact_stage : 0U;
if (placed_artifacts > maze->config.artifact_pairs) {
placed_artifacts = maze->config.artifact_pairs;
}
for (unsigned int pair = 0U; pair < placed_artifacts; ++pair) {
if (cells_equal(cell, maze->artifacts[pair][0]) ||
cells_equal(cell, maze->artifacts[pair][1])) {
return 1;
}
}
return 0;
}
static int pair_is_available(const maze32_maze_t* maze, maze32_cell_t first,
unsigned int placement_stage) {
maze32_cell_t second = maze32_maze_rotate(maze, first);
return !cells_equal(first, second) && !required_cell_occupied(maze, first, placement_stage) &&
!required_cell_occupied(maze, second, placement_stage);
}
static int choose_farthest(const maze32_maze_t* maze, maze32_maze_workspace_t* workspace,
unsigned int placement_stage, int boundary_only,
uint16_t maximum_distance, const maze32_cell_t* separated_from,
uint16_t minimum_separation, maze32_cell_t* selected) {
uint16_t best_distance = 0U;
int found = 0;
uint16_t count = cell_count(maze);
for (uint16_t index = 0U; index < count; ++index) {
maze32_cell_t cell = cell_from_index(maze, index);
int on_boundary = cell.x == 0U || cell.y == 0U || cell.x + 1U == maze->config.width ||
cell.y + 1U == maze->config.height;
uint16_t separation = 0U;
if (separated_from != NULL) {
separation = (uint16_t)((cell.x > separated_from->x ? cell.x - separated_from->x
: separated_from->x - cell.x) +
(cell.y > separated_from->y ? cell.y - separated_from->y
: separated_from->y - cell.y));
}
uint16_t distance = workspace->distances[index];
if ((!boundary_only || on_boundary) && distance != UINT16_MAX &&
distance <= maximum_distance && separation >= minimum_separation &&
pair_is_available(maze, cell, placement_stage) &&
(!found || distance > best_distance)) {
*selected = cell;
best_distance = distance;
found = 1;
}
}
return found;
}
static int choose_random_pair(const maze32_maze_t* maze, maze32_rng_t* random,
unsigned int placement_stage, maze32_cell_t* selected) {
uint16_t count = cell_count(maze);
for (uint32_t attempt = 0U; attempt < (uint32_t)count * 2U; ++attempt) {
maze32_cell_t candidate = cell_from_index(maze, (uint16_t)bounded_random(random, count));
if (pair_is_available(maze, candidate, placement_stage)) {
*selected = candidate;
return 1;
}
}
for (uint16_t index = 0U; index < count; ++index) {
maze32_cell_t candidate = cell_from_index(maze, index);
if (pair_is_available(maze, candidate, placement_stage)) {
*selected = candidate;
return 1;
}
}
return 0;
}
static int place_objects(maze32_maze_t* maze, maze32_maze_workspace_t* workspace,
maze32_rng_t* random) {
maze->starts[0].x = 0U;
maze->starts[0].y = (uint16_t)bounded_random(random, maze->config.height);
maze->starts[1] = maze32_maze_rotate(maze, maze->starts[0]);
calculate_distances(maze, workspace, maze->starts[0]);
if (!choose_farthest(maze, workspace, 1U, 0, UINT16_MAX, NULL, 0U, &maze->keys[0])) {
return 0;
}
maze->keys[1] = maze32_maze_rotate(maze, maze->keys[0]);
uint16_t start_key_distance = workspace->distances[maze32_maze_cell_index(maze, maze->keys[0])];
calculate_distances(maze, workspace, maze->keys[0]);
if (!choose_farthest(maze, workspace, 2U, 1, start_key_distance, &maze->starts[0],
maze->config.minimum_exit_separation, &maze->exits[0])) {
return 0;
}
maze->exits[1] = maze32_maze_rotate(maze, maze->exits[0]);
if (!choose_random_pair(maze, random, 3U, &maze->compasses[0])) {
return 0;
}
maze->compasses[1] = maze32_maze_rotate(maze, maze->compasses[0]);
for (unsigned int pair = 0U; pair < maze->config.landmark_pairs; ++pair) {
if (!choose_random_pair(maze, random, 4U + pair, &maze->landmarks[pair].cells[0])) {
return 0;
}
maze->landmarks[pair].cells[1] = maze32_maze_rotate(maze, maze->landmarks[pair].cells[0]);
maze->landmarks[pair].kind = (uint8_t)(pair % 4U + 1U);
}
for (unsigned int pair = 0U; pair < maze->config.artifact_pairs; ++pair) {
if (!choose_random_pair(maze, random, 4U + maze->config.landmark_pairs + pair,
&maze->artifacts[pair][0])) {
return 0;
}
maze->artifacts[pair][1] = maze32_maze_rotate(maze, maze->artifacts[pair][0]);
}
return 1;
}
static maze32_maze_result_t generate_candidate(maze32_maze_t* maze,
maze32_maze_workspace_t* workspace,
const maze32_maze_config_t* config,
uint32_t requested_seed, uint32_t generation_seed,
uint8_t attempt) {
memset(maze, 0, sizeof(*maze));
maze->config = *config;
maze->requested_seed = requested_seed;
maze->generation_seed = generation_seed;
maze->generation_attempts = attempt;
maze32_rng_t random = {.state = generation_seed == 0U ? 0x6d2b79f5U : generation_seed};
if (!build_half_tree(maze, workspace, &random) || !add_cycle_pairs(maze, &random) ||
!add_cross_half_connections(maze, workspace, &random) ||
!place_objects(maze, workspace, &random)) {
return MAZE32_MAZE_PLACEMENT_COLLISION;
}
return MAZE32_MAZE_OK;
}
static int validate_passages(const maze32_maze_t* maze, maze32_maze_metrics_t* metrics) {
uint16_t count = cell_count(maze);
for (uint16_t index = 0U; index < count; ++index) {
if ((maze->passages[index] & 0xf0U) != 0U) {
return 0;
}
unsigned int degree = 0U;
const maze32_passage_t directions[] = {MAZE32_PASSAGE_NORTH, MAZE32_PASSAGE_EAST,
MAZE32_PASSAGE_SOUTH, MAZE32_PASSAGE_WEST};
for (size_t direction_index = 0U; direction_index < 4U; ++direction_index) {
maze32_passage_t passage = directions[direction_index];
uint16_t neighbor = 0U;
if ((maze->passages[index] & passage) == 0U) {
continue;
}
if (!neighbor_for(maze, index, passage, &neighbor) ||
(maze->passages[neighbor] & opposite_passage(passage)) == 0U) {
return 0;
}
degree++;
if (passage == MAZE32_PASSAGE_EAST || passage == MAZE32_PASSAGE_SOUTH) {
metrics->passage_edges++;
}
}
if (degree == 1U) {
metrics->dead_end_count++;
} else if (degree >= 3U) {
metrics->branching_cells++;
}
maze32_cell_t cell = cell_from_index(maze, index);
maze32_cell_t rotated = maze32_maze_rotate(maze, cell);
uint16_t rotated_index = maze32_maze_cell_index(maze, rotated);
uint8_t expected = 0U;
if ((maze->passages[index] & MAZE32_PASSAGE_NORTH) != 0U) {
expected |= MAZE32_PASSAGE_SOUTH;
}
if ((maze->passages[index] & MAZE32_PASSAGE_EAST) != 0U) {
expected |= MAZE32_PASSAGE_WEST;
}
if ((maze->passages[index] & MAZE32_PASSAGE_SOUTH) != 0U) {
expected |= MAZE32_PASSAGE_NORTH;
}
if ((maze->passages[index] & MAZE32_PASSAGE_WEST) != 0U) {
expected |= MAZE32_PASSAGE_EAST;
}
if (maze->passages[rotated_index] != expected) {
return 0;
}
}
return 1;
}
static int object_pair_is_valid(const maze32_maze_t* maze, const maze32_cell_t pair[2]) {
return maze32_maze_cell_index(maze, pair[0]) != UINT16_MAX &&
maze32_maze_cell_index(maze, pair[1]) != UINT16_MAX &&
cells_equal(maze32_maze_rotate(maze, pair[0]), pair[1]);
}
static maze32_maze_result_t validate_objects(const maze32_maze_t* maze,
maze32_maze_workspace_t* workspace,
maze32_maze_metrics_t* metrics) {
maze32_cell_t
occupied[8U + MAZE32_MAZE_MAX_LANDMARK_PAIRS * 2U + MAZE32_MAZE_MAX_ARTIFACT_PAIRS * 2U];
size_t occupied_count = 0U;
const maze32_cell_t* required[] = {maze->starts, maze->keys, maze->exits, maze->compasses};
for (size_t group = 0U; group < 4U; ++group) {
if (!object_pair_is_valid(maze, required[group])) {
return MAZE32_MAZE_UNFAIR_PLACEMENT;
}
for (size_t index = 0U; index < 2U; ++index) {
for (size_t previous = 0U; previous < occupied_count; ++previous) {
if (cells_equal(required[group][index], occupied[previous])) {
return MAZE32_MAZE_PLACEMENT_COLLISION;
}
}
occupied[occupied_count++] = required[group][index];
}
}
for (size_t pair = 0U; pair < maze->config.landmark_pairs; ++pair) {
if (!object_pair_is_valid(maze, maze->landmarks[pair].cells)) {
return MAZE32_MAZE_UNFAIR_PLACEMENT;
}
for (size_t index = 0U; index < 2U; ++index) {
for (size_t previous = 0U; previous < occupied_count; ++previous) {
if (cells_equal(maze->landmarks[pair].cells[index], occupied[previous])) {
return MAZE32_MAZE_PLACEMENT_COLLISION;
}
}
occupied[occupied_count++] = maze->landmarks[pair].cells[index];
}
}
for (size_t pair = 0U; pair < maze->config.artifact_pairs; ++pair) {
if (!object_pair_is_valid(maze, maze->artifacts[pair])) {
return MAZE32_MAZE_UNFAIR_PLACEMENT;
}
for (size_t index = 0U; index < 2U; ++index) {
for (size_t previous = 0U; previous < occupied_count; ++previous) {
if (cells_equal(maze->artifacts[pair][index], occupied[previous])) {
return MAZE32_MAZE_PLACEMENT_COLLISION;
}
}
occupied[occupied_count++] = maze->artifacts[pair][index];
}
}
for (size_t player = 0U; player < 2U; ++player) {
maze32_cell_t start = maze->starts[player];
maze32_cell_t exit = maze->exits[player];
int start_on_boundary = start.x == 0U || start.y == 0U ||
start.x + 1U == maze->config.width ||
start.y + 1U == maze->config.height;
int exit_on_boundary = exit.x == 0U || exit.y == 0U || exit.x + 1U == maze->config.width ||
exit.y + 1U == maze->config.height;
uint16_t exit_separation =
(uint16_t)((start.x > exit.x ? start.x - exit.x : exit.x - start.x) +
(start.y > exit.y ? start.y - exit.y : exit.y - start.y));
if (!start_on_boundary || !exit_on_boundary ||
exit_separation < maze->config.minimum_exit_separation) {
return MAZE32_MAZE_UNFAIR_PLACEMENT;
}
}
metrics->reachable_cells = calculate_distances(maze, workspace, maze->starts[0]);
if (metrics->reachable_cells != cell_count(maze)) {
return MAZE32_MAZE_UNREACHABLE;
}
metrics->start_to_key[0] = workspace->distances[maze32_maze_cell_index(maze, maze->keys[0])];
metrics->start_to_exit[0] = workspace->distances[maze32_maze_cell_index(maze, maze->exits[0])];
metrics->start_to_compass[0] =
workspace->distances[maze32_maze_cell_index(maze, maze->compasses[0])];
for (size_t pair = 0U; pair < maze->config.landmark_pairs; ++pair) {
metrics->landmark_distance_total[0] +=
workspace->distances[maze32_maze_cell_index(maze, maze->landmarks[pair].cells[0])];
}
for (size_t pair = 0U; pair < maze->config.artifact_pairs; ++pair) {
metrics->artifact_distance_total[0] +=
workspace->distances[maze32_maze_cell_index(maze, maze->artifacts[pair][0])];
}
for (size_t player = 0U; player < 2U; ++player) {
int x_distance = maze->starts[player].x > maze->keys[player].x
? maze->starts[player].x - maze->keys[player].x
: maze->keys[player].x - maze->starts[player].x;
int y_distance = maze->starts[player].y > maze->keys[player].y
? maze->starts[player].y - maze->keys[player].y
: maze->keys[player].y - maze->starts[player].y;
if (x_distance <= maze->config.initial_view_radius &&
y_distance <= maze->config.initial_view_radius) {
return MAZE32_MAZE_KEY_IN_INITIAL_VIEW;
}
}
calculate_distances(maze, workspace, maze->keys[0]);
metrics->key_to_exit[0] = workspace->distances[maze32_maze_cell_index(maze, maze->exits[0])];
metrics->start_to_key[1] = metrics->start_to_key[0];
metrics->key_to_exit[1] = metrics->key_to_exit[0];
metrics->start_to_exit[1] = metrics->start_to_exit[0];
metrics->start_to_compass[1] = metrics->start_to_compass[0];
metrics->landmark_distance_total[1] = metrics->landmark_distance_total[0];
metrics->artifact_distance_total[1] = metrics->artifact_distance_total[0];
if (metrics->start_to_key[0] < metrics->key_to_exit[0]) {
return MAZE32_MAZE_UNFAIR_PLACEMENT;
}
return MAZE32_MAZE_OK;
}
maze32_maze_config_t maze32_maze_default_config(void) {
maze32_maze_config_t config = {
.width = MAZE32_MAZE_DEFAULT_WIDTH,
.height = MAZE32_MAZE_DEFAULT_HEIGHT,
.initial_view_radius = 3U,
.cross_half_pairs = 2U,
.extra_cycle_pairs = 8U,
.minimum_exit_separation = 22U,
.landmark_pairs = 2U,
.artifact_pairs = 1U,
.maximum_attempts = 8U,
};
return config;
}
maze32_maze_result_t maze32_maze_validate(const maze32_maze_t* maze,
maze32_maze_workspace_t* workspace,
maze32_maze_metrics_t* metrics) {
if (maze == NULL || workspace == NULL || metrics == NULL) {
return MAZE32_MAZE_INVALID_ARGUMENT;
}
memset(metrics, 0, sizeof(*metrics));
if (!config_is_valid(&maze->config)) {
return MAZE32_MAZE_INVALID_CONFIG;
}
if (!validate_passages(maze, metrics)) {
return MAZE32_MAZE_ASYMMETRIC;
}
maze32_maze_result_t object_result = validate_objects(maze, workspace, metrics);
if (object_result != MAZE32_MAZE_OK) {
return object_result;
}
metrics->cycle_count = (uint16_t)(metrics->passage_edges - metrics->reachable_cells + 1U);
if (metrics->cycle_count == 0U || metrics->dead_end_count == 0U) {
return MAZE32_MAZE_INSUFFICIENT_CYCLES;
}
uint16_t seam_x = maze->config.width / 2U - 1U;
for (uint16_t y = 0U; y < maze->config.height; ++y) {
maze32_cell_t cell = {seam_x, y};
if (maze32_maze_has_passage(maze, cell, MAZE32_PASSAGE_EAST)) {
metrics->cross_half_connections++;
}
}
if (metrics->cross_half_connections < 4U) {
return MAZE32_MAZE_INSUFFICIENT_CROSSINGS;
}
return MAZE32_MAZE_OK;
}
maze32_maze_result_t maze32_maze_generate(maze32_maze_t* maze, maze32_maze_workspace_t* workspace,
const maze32_maze_config_t* config, uint32_t seed,
maze32_maze_report_t* report) {
if (maze == NULL || workspace == NULL || config == NULL || report == NULL) {
return MAZE32_MAZE_INVALID_ARGUMENT;
}
memset(report, 0, sizeof(*report));
if (!config_is_valid(config)) {
report->result = MAZE32_MAZE_INVALID_CONFIG;
return report->result;
}
maze32_maze_result_t last_result = MAZE32_MAZE_GENERATION_EXHAUSTED;
uint32_t generation_seed = seed;
for (uint8_t attempt = 1U; attempt <= config->maximum_attempts; ++attempt) {
generation_seed += 0x9e3779b9U;
last_result = generate_candidate(maze, workspace, config, seed, generation_seed, attempt);
if (last_result == MAZE32_MAZE_OK) {
last_result = maze32_maze_validate(maze, workspace, &report->metrics);
}
report->attempts = attempt;
report->last_validation_result = last_result;
if (last_result == MAZE32_MAZE_OK) {
report->result = MAZE32_MAZE_OK;
return MAZE32_MAZE_OK;
}
}
report->result = MAZE32_MAZE_GENERATION_EXHAUSTED;
return report->result;
}
int maze32_maze_has_passage(const maze32_maze_t* maze, maze32_cell_t cell,
maze32_passage_t passage) {
uint16_t index = maze32_maze_cell_index(maze, cell);
return index != UINT16_MAX && (maze->passages[index] & passage) != 0U;
}
static void hash_bytes(uint64_t* hash, const void* data, size_t size) {
const uint8_t* bytes = data;
for (size_t index = 0U; index < size; ++index) {
*hash ^= bytes[index];
*hash *= UINT64_C(1099511628211);
}
}
uint64_t maze32_maze_hash(const maze32_maze_t* maze) {
if (maze == NULL || !config_is_valid(&maze->config)) {
return 0U;
}
uint64_t hash = UINT64_C(1469598103934665603);
hash_bytes(&hash, &maze->config.width, sizeof(maze->config.width));
hash_bytes(&hash, &maze->config.height, sizeof(maze->config.height));
hash_bytes(&hash, &maze->config.initial_view_radius, sizeof(maze->config.initial_view_radius));
hash_bytes(&hash, &maze->config.cross_half_pairs, sizeof(maze->config.cross_half_pairs));
hash_bytes(&hash, &maze->config.extra_cycle_pairs, sizeof(maze->config.extra_cycle_pairs));
hash_bytes(&hash, &maze->config.minimum_exit_separation,
sizeof(maze->config.minimum_exit_separation));
hash_bytes(&hash, &maze->config.landmark_pairs, sizeof(maze->config.landmark_pairs));
hash_bytes(&hash, &maze->config.artifact_pairs, sizeof(maze->config.artifact_pairs));
hash_bytes(&hash, &maze->config.maximum_attempts, sizeof(maze->config.maximum_attempts));
hash_bytes(&hash, &maze->requested_seed, sizeof(maze->requested_seed));
hash_bytes(&hash, &maze->generation_seed, sizeof(maze->generation_seed));
hash_bytes(&hash, maze->passages, cell_count(maze));
hash_bytes(&hash, maze->starts, sizeof(maze->starts));
hash_bytes(&hash, maze->keys, sizeof(maze->keys));
hash_bytes(&hash, maze->exits, sizeof(maze->exits));
hash_bytes(&hash, maze->compasses, sizeof(maze->compasses));
hash_bytes(&hash, maze->landmarks, maze->config.landmark_pairs * sizeof(maze->landmarks[0]));
hash_bytes(&hash, maze->artifacts, maze->config.artifact_pairs * sizeof(maze->artifacts[0]));
return hash;
}
const char* maze32_maze_result_name(maze32_maze_result_t result) {
switch (result) {
case MAZE32_MAZE_OK:
return "ok";
case MAZE32_MAZE_INVALID_ARGUMENT:
return "invalid_argument";
case MAZE32_MAZE_INVALID_CONFIG:
return "invalid_config";
case MAZE32_MAZE_UNREACHABLE:
return "unreachable";
case MAZE32_MAZE_ASYMMETRIC:
return "asymmetric";
case MAZE32_MAZE_INSUFFICIENT_CYCLES:
return "insufficient_cycles";
case MAZE32_MAZE_INSUFFICIENT_CROSSINGS:
return "insufficient_crossings";
case MAZE32_MAZE_PLACEMENT_COLLISION:
return "placement_collision";
case MAZE32_MAZE_UNFAIR_PLACEMENT:
return "unfair_placement";
case MAZE32_MAZE_KEY_IN_INITIAL_VIEW:
return "key_in_initial_view";
case MAZE32_MAZE_GENERATION_EXHAUSTED:
return "generation_exhausted";
default:
return "unknown";
}
}