World generator
Source: src/dungeon_generator.c · 499 lines · 24 functions.
Module responsibility
Builds the three procedural floors, rooms, passages, stairs, events and treasures, then validates the result.
Module entry points
These functions are exposed to the rest of the Dungeon system. Open one to follow its decisions and helper calls.
How to read these diagrams
Each function has an expandable structural diagram derived from the current C source. Decisions split into True and False, loop bodies are indented, and arrows show statement order.
These are source diagrams before preprocessing, not runtime traces. Callback execution, task scheduling and macro expansion are not inferred.
Functions in this file
ix() · source line 11
static uint16_t ix(uint8_t x, uint8_t y)
- Exit this function
return (uint16_t)y * DUNGEON_WIDTH + x;
Read original function source
static uint16_t ix(uint8_t x, uint8_t y) {
return (uint16_t)y * DUNGEON_WIDTH + x;
}cx() · source line 14
static uint8_t cx(uint16_t n)
- Exit this function
return (uint8_t)(n % DUNGEON_WIDTH);
Read original function source
static uint8_t cx(uint16_t n) { return (uint8_t)(n % DUNGEON_WIDTH); }cy() · source line 15
static uint8_t cy(uint16_t n)
- Exit this function
return (uint8_t)(n / DUNGEON_WIDTH);
Read original function source
static uint8_t cy(uint16_t n) { return (uint8_t)(n / DUNGEON_WIDTH); }ex() · source line 16
static uint8_t ex(uint8_t d)
- Execute
static const uint8_t v[] = {1U, 2U, 4U, 8U}; - Exit this function
return v[d];
Read original function source
static uint8_t ex(uint8_t d) {
static const uint8_t v[] = {1U, 2U, 4U, 8U};
return v[d];
}op() · source line 20
static uint8_t op(uint8_t d)
- Execute
static const uint8_t v[] = {4U, 8U, 1U, 2U}; - Exit this function
return v[d];
Read original function source
static uint8_t op(uint8_t d) {
static const uint8_t v[] = {4U, 8U, 1U, 2U};
return v[d];
}ec() · source line 24
static uint8_t ec(uint8_t e)
- Execute
uint8_t n = 0U; - FOR loop
uint8_t i = 0U; i < 4U; ++iRepeat body while the loop condition permits; then continue below.
- Execute
n += (e >> i) & 1U;
- Exit this function
return n;
Read original function source
static uint8_t ec(uint8_t e) {
uint8_t n = 0U;
for (uint8_t i = 0U; i < 4U; ++i)
n += (e >> i) & 1U;
return n;
}next() · source line 30
static bool next(uint16_t c, uint8_t d, uint16_t *n)
Calls into this documentation:
- Execute
uint8_t x = cx(c), y = cy(c); - Decision
d == 0U && y > 0UTrue- Execute
*n = ix(x, y - 1U);
False- Decision
d == 1U && x + 1U < DUNGEON_WIDTHTrue- Execute
*n = ix(x + 1U, y);
False- Decision
d == 2U && y + 1U < DUNGEON_HEIGHTTrue- Execute
*n = ix(x, y + 1U);
False- Decision
d == 3U && x > 0UTrue- Execute
*n = ix(x - 1U, y);
False- Exit this function
return false;
- Exit this function
return true;
Read original function source
static bool next(uint16_t c, uint8_t d, uint16_t *n) {
uint8_t x = cx(c), y = cy(c);
if (d == 0U && y > 0U)
*n = ix(x, y - 1U);
else if (d == 1U && x + 1U < DUNGEON_WIDTH)
*n = ix(x + 1U, y);
else if (d == 2U && y + 1U < DUNGEON_HEIGHT)
*n = ix(x, y + 1U);
else if (d == 3U && x > 0U)
*n = ix(x - 1U, y);
else
return false;
return true;
}room() · source line 44
static bool room(uint8_t t)
- Exit this function
return t == DUNGEON_TILE_DEAD_END_ROOM_ROUND || t == DUNGEON_TILE_DEAD_END_ROOM_SQUARE || t == DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
Read original function source
static bool room(uint8_t t) {
return t == DUNGEON_TILE_DEAD_END_ROOM_ROUND ||
t == DUNGEON_TILE_DEAD_END_ROOM_SQUARE ||
t == DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
}shuffle() · source line 49
static void shuffle(uint16_t n)
- FOR loop
; n > 1U; --nRepeat body while the loop condition permits; then continue below.
- Execute
uint16_t s = (uint16_t)(esp_random() % n), t = candidates[n - 1U]; - Execute
candidates[n - 1U] = candidates[s]; - Execute
candidates[s] = t;
Read original function source
static void shuffle(uint16_t n) {
for (; n > 1U; --n) {
uint16_t s = (uint16_t)(esp_random() % n), t = candidates[n - 1U];
candidates[n - 1U] = candidates[s];
candidates[s] = t;
}
}validate_floor() · source line 57
static DungeonGenerationError validate_floor(const DungeonFloor *f,
uint16_t *reachable)Calls into this documentation:
- FOR loop
uint8_t y = 0; y < DUNGEON_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t x = 0; x < DUNGEON_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
uint16_t c = ix(x, y); - FOR loop
uint8_t d = 0; d < 4; ++dRepeat body while the loop condition permits; then continue below.
- Decision
f->cells[y][x].exits & ex(d)True- Execute
uint16_t n; - Decision
!next(c, d, &n)True- Exit this function
return DUNGEON_GENERATION_OUTSIDE_EXIT;
FalseContinue after this decision.
- Decision
!(f->cells[cy(n)][cx(n)].exits & op(d))True- Exit this function
return DUNGEON_GENERATION_EXIT_MISMATCH;
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
memset(visited, 0, sizeof(visited)); - Execute
uint16_t a = 0, b = 0; - Execute
queue[b++] = 0; - Execute
visited[0] = 1U; - WHILE loop
a < bRepeat body while the loop condition permits; then continue below.
- Execute
uint16_t c = queue[a++]; - FOR loop
uint8_t d = 0; d < 4; ++dRepeat body while the loop condition permits; then continue below.
- Execute
uint16_t n; - Decision
(f->cells[cy(c)][cx(c)].exits & ex(d)) && next(c, d, &n) && !visited[n]True- Execute
visited[n] = 1U; - Execute
queue[b++] = n;
FalseContinue after this decision.
- Execute
*reachable = b; - Exit this function
return b == CELLS ? DUNGEON_GENERATION_OK : DUNGEON_GENERATION_UNREACHABLE_CELL;
Read original function source
static DungeonGenerationError validate_floor(const DungeonFloor *f,
uint16_t *reachable) {
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
uint16_t c = ix(x, y);
for (uint8_t d = 0; d < 4; ++d)
if (f->cells[y][x].exits & ex(d)) {
uint16_t n;
if (!next(c, d, &n))
return DUNGEON_GENERATION_OUTSIDE_EXIT;
if (!(f->cells[cy(n)][cx(n)].exits & op(d)))
return DUNGEON_GENERATION_EXIT_MISMATCH;
}
}
memset(visited, 0, sizeof(visited));
uint16_t a = 0, b = 0;
queue[b++] = 0;
visited[0] = 1U;
while (a < b) {
uint16_t c = queue[a++];
for (uint8_t d = 0; d < 4; ++d) {
uint16_t n;
if ((f->cells[cy(c)][cx(c)].exits & ex(d)) && next(c, d, &n) &&
!visited[n]) {
visited[n] = 1U;
queue[b++] = n;
}
}
}
*reachable = b;
return b == CELLS ? DUNGEON_GENERATION_OK
: DUNGEON_GENERATION_UNREACHABLE_CELL;
}classify() · source line 90
static void classify(DungeonFloor *f, DungeonGenerationStats *s)
Calls into this documentation:
- Execute
uint16_t count = 0; - Execute
uint32_t exits = 0; - FOR loop
uint8_t y = 0; y < DUNGEON_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t x = 0; x < DUNGEON_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
DungeonCell *c = &f->cells[y][x]; - Execute
uint8_t n = ec(c->exits); - Execute
exits += n; - Execute
c->tileType = n == 1U ? DUNGEON_TILE_DEAD_END_PASSAGE : DUNGEON_TILE_PASSAGE; - Decision
n == 1UTrue- Execute
dead_ends[count++] = ix(x, y);
FalseContinue after this decision.
- Execute
uint16_t rooms = count < getDungeonConfig()->roomsPerFloor ? count : getDungeonConfig()->roomsPerFloor; - FOR loop
uint16_t i = 0; i < rooms; ++iRepeat body while the loop condition permits; then continue below.
- Execute
uint16_t p = i + (uint16_t)(esp_random() % (count - i)), t = dead_ends[i]; - Execute
dead_ends[i] = dead_ends[p]; - Execute
dead_ends[p] = t; - Execute
f->cells[cy(dead_ends[i])][cx(dead_ends[i])].tileType = i % 3U == 0U ? DUNGEON_TILE_DEAD_END_ROOM_ROUND : i % 3U == 1U ? DUNGEON_TILE_DEAD_END_ROOM_SQUARE : DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
- Execute
s->rooms = rooms; - Execute
s->deadEnds = count; - Execute
s->connections = (uint16_t)(exits / 2U); - Execute
s->loops = (uint16_t)(s->connections - CELLS + 1U);
Read original function source
static void classify(DungeonFloor *f, DungeonGenerationStats *s) {
uint16_t count = 0;
uint32_t exits = 0;
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
DungeonCell *c = &f->cells[y][x];
uint8_t n = ec(c->exits);
exits += n;
c->tileType =
n == 1U ? DUNGEON_TILE_DEAD_END_PASSAGE : DUNGEON_TILE_PASSAGE;
if (n == 1U)
dead_ends[count++] = ix(x, y);
}
uint16_t rooms = count < getDungeonConfig()->roomsPerFloor
? count
: getDungeonConfig()->roomsPerFloor;
for (uint16_t i = 0; i < rooms; ++i) {
uint16_t p = i + (uint16_t)(esp_random() % (count - i)), t = dead_ends[i];
dead_ends[i] = dead_ends[p];
dead_ends[p] = t;
f->cells[cy(dead_ends[i])][cx(dead_ends[i])].tileType =
i % 3U == 0U ? DUNGEON_TILE_DEAD_END_ROOM_ROUND
: i % 3U == 1U ? DUNGEON_TILE_DEAD_END_ROOM_SQUARE
: DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
}
s->rooms = rooms;
s->deadEnds = count;
s->connections = (uint16_t)(exits / 2U);
s->loops = (uint16_t)(s->connections - CELLS + 1U);
}generateDungeonFloor() · source line 120
bool generateDungeonFloor(uint8_t floor, DungeonGenerationStats *s)
Calls into this documentation:
- Decision
!sTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
memset(s, 0, sizeof(*s)); - Decision
floor >= DUNGEON_FLOOR_COUNTTrue- Execute
s->error = DUNGEON_GENERATION_INVALID_FLOOR; - Exit this function
return false;
FalseContinue after this decision.
- Execute
DungeonFloor *f = &getDungeonWorld()->floors[floor]; - Execute
memset(f, 0, sizeof(*f)); - Execute
memset(visited, 0, sizeof(visited)); - Execute
uint16_t depth = 0, start = ix((uint8_t)(esp_random() % DUNGEON_WIDTH), (uint8_t)(esp_random() % DUNGEON_HEIGHT)); - Execute
stack[depth++] = start; - Execute
visited[start] = 1U; - WHILE loop
depthRepeat body while the loop condition permits; then continue below.
- Execute
uint16_t c = stack[depth - 1U], n; - Execute
uint8_t choices[4], count = 0; - FOR loop
uint8_t d = 0; d < 4; ++dRepeat body while the loop condition permits; then continue below.
- Decision
next(c, d, &n) && !visited[n]True- Execute
choices[count++] = d;
FalseContinue after this decision.
- Decision
!countTrue- Execute
--depth; - Next loop iteration
continue;
FalseContinue after this decision.
- Execute
uint8_t d = choices[esp_random() % count]; - Execute
(void)next(c, d, &n); - Execute
f->cells[cy(c)][cx(c)].exits |= ex(d); - Execute
f->cells[cy(n)][cx(n)].exits |= op(d); - Execute
visited[n] = 1U; - Execute
stack[depth++] = n;
- Execute
uint16_t target = (uint16_t)(((uint32_t)CELLS * getDungeonConfig()->extraConnectionPercent + 99U) / 100U), added = 0; - FOR loop
uint32_t tries = 0; added < target && tries < (uint32_t)CELLS * 10U; ++triesRepeat body while the loop condition permits; then continue below.
- Execute
uint16_t c = (uint16_t)(esp_random() % CELLS), n; - Execute
uint8_t d = (uint8_t)(esp_random() % 4U); - Decision
!next(c, d, &n) || (f->cells[cy(c)][cx(c)].exits & ex(d))True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
f->cells[cy(c)][cx(c)].exits |= ex(d); - Execute
f->cells[cy(n)][cx(n)].exits |= op(d); - Execute
++added;
- Execute
classify(f, s); - Execute
s->error = validate_floor(f, &s->reachableCells); - Execute
s->valid = s->error == DUNGEON_GENERATION_OK; - Exit this function
return s->valid;
Read original function source
bool generateDungeonFloor(uint8_t floor, DungeonGenerationStats *s) {
if (!s)
return false;
memset(s, 0, sizeof(*s));
if (floor >= DUNGEON_FLOOR_COUNT) {
s->error = DUNGEON_GENERATION_INVALID_FLOOR;
return false;
}
DungeonFloor *f = &getDungeonWorld()->floors[floor];
memset(f, 0, sizeof(*f));
memset(visited, 0, sizeof(visited));
uint16_t depth = 0, start = ix((uint8_t)(esp_random() % DUNGEON_WIDTH),
(uint8_t)(esp_random() % DUNGEON_HEIGHT));
stack[depth++] = start;
visited[start] = 1U;
while (depth) {
uint16_t c = stack[depth - 1U], n;
uint8_t choices[4], count = 0;
for (uint8_t d = 0; d < 4; ++d)
if (next(c, d, &n) && !visited[n])
choices[count++] = d;
if (!count) {
--depth;
continue;
}
uint8_t d = choices[esp_random() % count];
(void)next(c, d, &n);
f->cells[cy(c)][cx(c)].exits |= ex(d);
f->cells[cy(n)][cx(n)].exits |= op(d);
visited[n] = 1U;
stack[depth++] = n;
}
uint16_t target = (uint16_t)(((uint32_t)CELLS *
getDungeonConfig()->extraConnectionPercent +
99U) /
100U),
added = 0;
for (uint32_t tries = 0; added < target && tries < (uint32_t)CELLS * 10U;
++tries) {
uint16_t c = (uint16_t)(esp_random() % CELLS), n;
uint8_t d = (uint8_t)(esp_random() % 4U);
if (!next(c, d, &n) || (f->cells[cy(c)][cx(c)].exits & ex(d)))
continue;
f->cells[cy(c)][cx(c)].exits |= ex(d);
f->cells[cy(n)][cx(n)].exits |= op(d);
++added;
}
classify(f, s);
s->error = validate_floor(f, &s->reachableCells);
s->valid = s->error == DUNGEON_GENERATION_OK;
return s->valid;
}choose_dead() · source line 172
static bool choose_dead(uint8_t floor, DungeonPosition *p)
Calls into this documentation:
- Execute
uint16_t n = 0; - Execute
DungeonFloor *f = &getDungeonWorld()->floors[floor]; - FOR loop
uint8_t y = 0; y < DUNGEON_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t x = 0; x < DUNGEON_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Decision
f->cells[y][x].tileType == DUNGEON_TILE_DEAD_END_PASSAGETrue- Execute
candidates[n++] = ix(x, y);
FalseContinue after this decision.
- Decision
!nTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
uint16_t c = candidates[esp_random() % n]; - Execute
*p = (DungeonPosition){floor, cx(c), cy(c)}; - Exit this function
return true;
Read original function source
static bool choose_dead(uint8_t floor, DungeonPosition *p) {
uint16_t n = 0;
DungeonFloor *f = &getDungeonWorld()->floors[floor];
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x)
if (f->cells[y][x].tileType == DUNGEON_TILE_DEAD_END_PASSAGE)
candidates[n++] = ix(x, y);
if (!n)
return false;
uint16_t c = candidates[esp_random() % n];
*p = (DungeonPosition){floor, cx(c), cy(c)};
return true;
}choose_straight() · source line 185
static bool choose_straight(uint8_t floor, DungeonPosition *p,
const DungeonPosition *skip)Calls into this documentation:
- Execute
uint16_t n = 0; - Execute
DungeonFloor *f = &getDungeonWorld()->floors[floor]; - FOR loop
uint8_t y = 0; y < DUNGEON_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t x = 0; x < DUNGEON_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
DungeonCell *c = &f->cells[y][x]; - Decision
c->tileType == DUNGEON_TILE_PASSAGE && (c->exits == (DUNGEON_EXIT_NORTH | DUNGEON_EXIT_SOUTH) || c->exits == (DUNGEON_EXIT_EAST | DUNGEON_EXIT_WEST)) && !(skip && skip->floor == floor && skip->x == x && skip->y == y)True- Execute
candidates[n++] = ix(x, y);
FalseContinue after this decision.
- Decision
!nTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
uint16_t c = candidates[esp_random() % n]; - Execute
*p = (DungeonPosition){floor, cx(c), cy(c)}; - Exit this function
return true;
Read original function source
static bool choose_straight(uint8_t floor, DungeonPosition *p,
const DungeonPosition *skip) {
uint16_t n = 0;
DungeonFloor *f = &getDungeonWorld()->floors[floor];
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
DungeonCell *c = &f->cells[y][x];
if (c->tileType == DUNGEON_TILE_PASSAGE &&
(c->exits == (DUNGEON_EXIT_NORTH | DUNGEON_EXIT_SOUTH) ||
c->exits == (DUNGEON_EXIT_EAST | DUNGEON_EXIT_WEST)) &&
!(skip && skip->floor == floor && skip->x == x && skip->y == y))
candidates[n++] = ix(x, y);
}
if (!n)
return false;
uint16_t c = candidates[esp_random() % n];
*p = (DungeonPosition){floor, cx(c), cy(c)};
return true;
}erange() · source line 204
static void erange(uint8_t f, uint8_t *lo, uint8_t *hi)
Calls into this documentation:
- Execute
const DungeonConfig *c = getDungeonConfig(); - Execute
*lo = f == 0U ? c->floor1EnemyMinLevel : f == 1U ? c->floor2EnemyMinLevel : c->floor3EnemyMinLevel; - Execute
*hi = f == 0U ? c->floor1EnemyMaxLevel : f == 1U ? c->floor2EnemyMaxLevel : c->floor3EnemyMaxLevel;
Read original function source
static void erange(uint8_t f, uint8_t *lo, uint8_t *hi) {
const DungeonConfig *c = getDungeonConfig();
*lo = f == 0U ? c->floor1EnemyMinLevel
: f == 1U ? c->floor2EnemyMinLevel
: c->floor3EnemyMinLevel;
*hi = f == 0U ? c->floor1EnemyMaxLevel
: f == 1U ? c->floor2EnemyMaxLevel
: c->floor3EnemyMaxLevel;
}event() · source line 213
static void event(DungeonCell *c, uint16_t flags)
- Execute
c->eventFlags = flags; - Execute
c->stateFlags = DUNGEON_STATE_NONE; - Execute
c->variant = 1U;
Read original function source
static void event(DungeonCell *c, uint16_t flags) {
c->eventFlags = flags;
c->stateFlags = DUNGEON_STATE_NONE;
c->variant = 1U;
}treasure() · source line 218
static uint8_t treasure(void)
Calls into this documentation:
- Execute
const DungeonConfig *c = getDungeonConfig(); - Decision
c->rareTreasurePercent && esp_random() % 100U < c->rareTreasurePercentTrue- Execute
uint32_t t = (uint32_t)c->rareLifeRatio + c->rareAttackRatio + c->rareDefenceRatio, r = t ? esp_random() % t : 0U; - Decision
r < c->rareLifeRatioTrue- Exit this function
return DUNGEON_TREASURE_LIFE;
FalseContinue after this decision.
- Execute
r -= c->rareLifeRatio; - Exit this function
return r < c->rareAttackRatio ? DUNGEON_TREASURE_ATTACK : DUNGEON_TREASURE_DEFENCE;
FalseContinue after this decision.
- Execute
uint32_t t = (uint32_t)c->normalGoldRatio + c->normalHealthRatio; - Exit this function
return !t || esp_random() % t < c->normalGoldRatio ? DUNGEON_TREASURE_GOLD : DUNGEON_TREASURE_HEALTH;
Read original function source
static uint8_t treasure(void) {
const DungeonConfig *c = getDungeonConfig();
if (c->rareTreasurePercent && esp_random() % 100U < c->rareTreasurePercent) {
uint32_t t = (uint32_t)c->rareLifeRatio + c->rareAttackRatio +
c->rareDefenceRatio,
r = t ? esp_random() % t : 0U;
if (r < c->rareLifeRatio)
return DUNGEON_TREASURE_LIFE;
r -= c->rareLifeRatio;
return r < c->rareAttackRatio ? DUNGEON_TREASURE_ATTACK
: DUNGEON_TREASURE_DEFENCE;
}
uint32_t t = (uint32_t)c->normalGoldRatio + c->normalHealthRatio;
return !t || esp_random() % t < c->normalGoldRatio ? DUNGEON_TREASURE_GOLD
: DUNGEON_TREASURE_HEALTH;
}key_room() · source line 234
static bool key_room(DungeonPosition *p)
Calls into this documentation:
- Execute
uint16_t n = 0; - Execute
DungeonWorld *w = getDungeonWorld(); - FOR loop
uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++fRepeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t y = 0; y < DUNGEON_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t x = 0; x < DUNGEON_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Decision
room(w->floors[f].cells[y][x].tileType)True- Execute
++n; - Decision
esp_random() % n == 0UTrue- Execute
*p = (DungeonPosition){f, x, y};
FalseContinue after this decision.
FalseContinue after this decision.
- Exit this function
return n > 0;
Read original function source
static bool key_room(DungeonPosition *p) {
uint16_t n = 0;
DungeonWorld *w = getDungeonWorld();
for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f)
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x)
if (room(w->floors[f].cells[y][x].tileType)) {
++n;
if (esp_random() % n == 0U)
*p = (DungeonPosition){f, x, y};
}
return n > 0;
}room_counts() · source line 247
static void room_counts(uint16_t n, uint16_t out[3])
Calls into this documentation:
- Execute
const DungeonConfig *c = getDungeonConfig(); - Execute
uint16_t w[3] = {c->enemyRoomRatio, c->treasureRoomRatio, c->emptyRoomRatio}; - Execute
uint32_t total = (uint32_t)w[0] + w[1] + w[2], rem[3]; - Execute
uint16_t done = 0; - Decision
!totalTrue- Execute
out[2] = n; - Exit this function
return;
FalseContinue after this decision.
- FOR loop
uint8_t i = 0; i < 3; ++iRepeat body while the loop condition permits; then continue below.
- Execute
uint32_t q = (uint32_t)n * w[i]; - Execute
out[i] = (uint16_t)(q / total); - Execute
rem[i] = q % total; - Execute
done += out[i];
- WHILE loop
done < nRepeat body while the loop condition permits; then continue below.
- Execute
uint32_t high = 0; - Execute
uint8_t ties[3], nt = 0; - FOR loop
uint8_t i = 0; i < 3; ++iRepeat body while the loop condition permits; then continue below.
- Decision
rem[i] > highTrue- Execute
high = rem[i]; - Execute
ties[0] = i; - Execute
nt = 1;
False- Decision
rem[i] == highTrue- Execute
ties[nt++] = i;
FalseContinue after this decision.
- Execute
uint8_t k = ties[esp_random() % nt]; - Execute
++out[k]; - Execute
rem[k] = 0; - Execute
++done;
Read original function source
static void room_counts(uint16_t n, uint16_t out[3]) {
const DungeonConfig *c = getDungeonConfig();
uint16_t w[3] = {c->enemyRoomRatio, c->treasureRoomRatio, c->emptyRoomRatio};
uint32_t total = (uint32_t)w[0] + w[1] + w[2], rem[3];
uint16_t done = 0;
if (!total) {
out[2] = n;
return;
}
for (uint8_t i = 0; i < 3; ++i) {
uint32_t q = (uint32_t)n * w[i];
out[i] = (uint16_t)(q / total);
rem[i] = q % total;
done += out[i];
}
while (done < n) {
uint32_t high = 0;
uint8_t ties[3], nt = 0;
for (uint8_t i = 0; i < 3; ++i)
if (rem[i] > high) {
high = rem[i];
ties[0] = i;
nt = 1;
} else if (rem[i] == high)
ties[nt++] = i;
uint8_t k = ties[esp_random() % nt];
++out[k];
rem[k] = 0;
++done;
}
}events() · source line 278
static void events(const DungeonPosition *key)
Calls into this documentation:
- Execute
DungeonWorld *w = getDungeonWorld(); - FOR loop
uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++fRepeat body while the loop condition permits; then continue below.
- Execute
uint16_t n = 0; - FOR loop
uint8_t y = 0; y < DUNGEON_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t x = 0; x < DUNGEON_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Decision
room(w->floors[f].cells[y][x].tileType) && !(key->floor == f && key->x == x && key->y == y)True- Execute
candidates[n++] = ix(x, y);
FalseContinue after this decision.
- Execute
shuffle(n); - Execute
uint16_t types[3] = {0, 0, 0}; - Execute
room_counts(n, types); - Execute
uint8_t lo, hi; - Execute
erange(f, &lo, &hi); - Execute
uint16_t i = 0; - FOR loop
; i < types[0]; ++iRepeat body while the loop condition permits; then continue below.
- Execute
DungeonCell *c = &w->floors[f].cells[cy(candidates[i])][cx(candidates[i])]; - Execute
event(c, DUNGEON_EVENT_ENCOUNTER | ((uint16_t)(lo + esp_random() % (hi - lo + 1U)) << DUNGEON_ENEMY_DETAIL_SHIFT));
- FOR loop
; i < (uint16_t)(types[0] + types[1]); ++iRepeat body while the loop condition permits; then continue below.
- Execute
event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])], DUNGEON_EVENT_TREASURE | ((uint16_t)treasure() << DUNGEON_TREASURE_DETAIL_SHIFT));
Read original function source
static void events(const DungeonPosition *key) {
DungeonWorld *w = getDungeonWorld();
for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f) {
uint16_t n = 0;
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x)
if (room(w->floors[f].cells[y][x].tileType) &&
!(key->floor == f && key->x == x && key->y == y))
candidates[n++] = ix(x, y);
shuffle(n);
uint16_t types[3] = {0, 0, 0};
room_counts(n, types);
uint8_t lo, hi;
erange(f, &lo, &hi);
uint16_t i = 0;
for (; i < types[0]; ++i) {
DungeonCell *c =
&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])];
event(c, DUNGEON_EVENT_ENCOUNTER |
((uint16_t)(lo + esp_random() % (hi - lo + 1U))
<< DUNGEON_ENEMY_DETAIL_SHIFT));
}
for (; i < (uint16_t)(types[0] + types[1]); ++i)
event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])],
DUNGEON_EVENT_TREASURE |
((uint16_t)treasure() << DUNGEON_TREASURE_DETAIL_SHIFT));
}
}corridors() · source line 306
static void corridors(void)
Calls into this documentation:
- Execute
DungeonWorld *w = getDungeonWorld(); - Execute
const DungeonConfig *c = getDungeonConfig(); - FOR loop
uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++fRepeat body while the loop condition permits; then continue below.
- Execute
uint16_t r = 0, n = 0; - FOR loop
uint8_t y = 0; y < DUNGEON_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t x = 0; x < DUNGEON_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
uint8_t t = w->floors[f].cells[y][x].tileType; - Decision
room(t)True- Execute
++r;
FalseContinue after this decision.
- Decision
t == DUNGEON_TILE_PASSAGE || t == DUNGEON_TILE_DEAD_END_PASSAGETrue- Execute
candidates[n++] = ix(x, y);
FalseContinue after this decision.
- Execute
uint16_t traps = (uint16_t)(((uint32_t)r * c->trapsPer100Rooms + 99U) / 100U), tr = (uint16_t)(((uint32_t)r * c->corridorTreasuresPer100Rooms + 99U) / 100U); - Decision
traps > nTrue- Execute
traps = n;
FalseContinue after this decision.
- Decision
tr > n - trapsTrue- Execute
tr = n - traps;
FalseContinue after this decision.
- Execute
shuffle(n); - FOR loop
uint16_t i = 0; i < traps; ++iRepeat body while the loop condition permits; then continue below.
- Execute
event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])], DUNGEON_EVENT_TRAP);
- FOR loop
uint16_t i = traps; i < (uint16_t)(traps + tr); ++iRepeat body while the loop condition permits; then continue below.
- Execute
event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])], DUNGEON_EVENT_TREASURE | ((uint16_t)treasure() << DUNGEON_TREASURE_DETAIL_SHIFT));
Read original function source
static void corridors(void) {
DungeonWorld *w = getDungeonWorld();
const DungeonConfig *c = getDungeonConfig();
for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f) {
uint16_t r = 0, n = 0;
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
uint8_t t = w->floors[f].cells[y][x].tileType;
if (room(t))
++r;
if (t == DUNGEON_TILE_PASSAGE || t == DUNGEON_TILE_DEAD_END_PASSAGE)
candidates[n++] = ix(x, y);
}
uint16_t traps =
(uint16_t)(((uint32_t)r * c->trapsPer100Rooms + 99U) / 100U),
tr = (uint16_t)(((uint32_t)r * c->corridorTreasuresPer100Rooms +
99U) /
100U);
if (traps > n)
traps = n;
if (tr > n - traps)
tr = n - traps;
shuffle(n);
for (uint16_t i = 0; i < traps; ++i)
event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])],
DUNGEON_EVENT_TRAP);
for (uint16_t i = traps; i < (uint16_t)(traps + tr); ++i)
event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])],
DUNGEON_EVENT_TREASURE |
((uint16_t)treasure() << DUNGEON_TREASURE_DETAIL_SHIFT));
}
}validateDungeonWorld() · source line 338
bool validateDungeonWorld(DungeonWorldGenerationStats *s)
Calls into this documentation:
- Decision
!sTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
memset(s, 0, sizeof(*s)); - Execute
DungeonWorld *w = getDungeonWorld(); - FOR loop
uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++fRepeat body while the loop condition permits; then continue below.
- Execute
DungeonGenerationStats *fs = &s->floors[f]; - Execute
fs->error = validate_floor(&w->floors[f], &fs->reachableCells); - Decision
fs->error != DUNGEON_GENERATION_OKTrue- Execute
s->error = fs->error; - Exit this function
return false;
FalseContinue after this decision.
- FOR loop
uint8_t y = 0; y < DUNGEON_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t x = 0; x < DUNGEON_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
DungeonCell *c = &w->floors[f].cells[y][x]; - Decision
c->tileType > DUNGEON_TILE_STAIRS_BOTHTrue- Execute
s->error = DUNGEON_GENERATION_INVALID_TILE; - Exit this function
return false;
FalseContinue after this decision.
- Decision
ec(c->exits) == 1UTrue- Execute
++fs->deadEnds;
FalseContinue after this decision.
- Decision
room(c->tileType)True- Execute
++fs->rooms; - Decision
c->eventFlags & DUNGEON_EVENT_ENCOUNTERTrue- Execute
++fs->enemyRooms;
False- Decision
c->eventFlags & DUNGEON_EVENT_TREASURETrue- Execute
++fs->treasureRooms;
False- Execute
++fs->emptyRooms;
FalseContinue after this decision.
- Decision
c->eventFlags & DUNGEON_EVENT_TRAPTrue- Execute
++fs->traps;
FalseContinue after this decision.
- Decision
c->eventFlags & DUNGEON_EVENT_KEYTrue- Execute
s->keyPlaced = true;
FalseContinue after this decision.
- Decision
(c->eventFlags & DUNGEON_EVENT_ENCOUNTER) && ((c->eventFlags & DUNGEON_ENEMY_DETAIL_MASK) >> DUNGEON_ENEMY_DETAIL_SHIFT) == DUNGEON_ENEMY_DRAGONTrue- Execute
s->dragonPlaced = true;
FalseContinue after this decision.
- Execute
fs->valid = true; - Execute
s->reachableCells += fs->reachableCells; - Execute
s->rooms += fs->rooms; - Execute
s->deadEnds += fs->deadEnds; - Execute
s->enemyRooms += fs->enemyRooms; - Execute
s->treasureRooms += fs->treasureRooms; - Execute
s->emptyRooms += fs->emptyRooms; - Execute
s->traps += fs->traps;
- Decision
w->entrance.floor != 0U || w->entrance.x >= DUNGEON_WIDTH || w->entrance.y >= DUNGEON_HEIGHT || w->floors[w->entrance.floor] .cells[w->entrance.y][w->entrance.x] .tileType != DUNGEON_TILE_ENTRANCETrue- Execute
s->error = DUNGEON_GENERATION_NO_ENTRANCE; - Exit this function
return false;
FalseContinue after this decision.
- Decision
w->exit.floor != DUNGEON_FLOOR_COUNT - 1U || w->exit.x >= DUNGEON_WIDTH || w->exit.y >= DUNGEON_HEIGHT || w->floors[w->exit.floor].cells[w->exit.y][w->exit.x].tileType != DUNGEON_TILE_EXITTrue- Execute
s->error = DUNGEON_GENERATION_NO_EXIT; - Exit this function
return false;
FalseContinue after this decision.
- FOR loop
uint8_t i = 0; i < DUNGEON_FLOOR_COUNT - 1U; ++iRepeat body while the loop condition permits; then continue below.
- Execute
DungeonStairLink *l = &w->stairLinks[i]; - Decision
l->upper.floor >= DUNGEON_FLOOR_COUNT || l->lower.floor >= DUNGEON_FLOOR_COUNT || l->upper.x >= DUNGEON_WIDTH || l->lower.x >= DUNGEON_WIDTH || l->upper.y >= DUNGEON_HEIGHT || l->lower.y >= DUNGEON_HEIGHT || l->upper.floor + 1U != l->lower.floor || w->floors[l->upper.floor].cells[l->upper.y][l->upper.x].tileType != DUNGEON_TILE_STAIRS_DOWN || w->floors[l->lower.floor].cells[l->lower.y][l->lower.x].tileType != DUNGEON_TILE_STAIRS_UPTrue- Execute
s->error = DUNGEON_GENERATION_INVALID_STAIR_LINK; - Exit this function
return false;
FalseContinue after this decision.
- Execute
s->stairLinks = DUNGEON_FLOOR_COUNT - 1U; - Execute
s->valid = true; - Execute
s->error = DUNGEON_GENERATION_OK; - Exit this function
return true;
Read original function source
bool validateDungeonWorld(DungeonWorldGenerationStats *s) {
if (!s)
return false;
memset(s, 0, sizeof(*s));
DungeonWorld *w = getDungeonWorld();
for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f) {
DungeonGenerationStats *fs = &s->floors[f];
fs->error = validate_floor(&w->floors[f], &fs->reachableCells);
if (fs->error != DUNGEON_GENERATION_OK) {
s->error = fs->error;
return false;
}
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
DungeonCell *c = &w->floors[f].cells[y][x];
if (c->tileType > DUNGEON_TILE_STAIRS_BOTH) {
s->error = DUNGEON_GENERATION_INVALID_TILE;
return false;
}
if (ec(c->exits) == 1U)
++fs->deadEnds;
if (room(c->tileType)) {
++fs->rooms;
if (c->eventFlags & DUNGEON_EVENT_ENCOUNTER)
++fs->enemyRooms;
else if (c->eventFlags & DUNGEON_EVENT_TREASURE)
++fs->treasureRooms;
else
++fs->emptyRooms;
}
if (c->eventFlags & DUNGEON_EVENT_TRAP)
++fs->traps;
if (c->eventFlags & DUNGEON_EVENT_KEY)
s->keyPlaced = true;
if ((c->eventFlags & DUNGEON_EVENT_ENCOUNTER) &&
((c->eventFlags & DUNGEON_ENEMY_DETAIL_MASK) >>
DUNGEON_ENEMY_DETAIL_SHIFT) == DUNGEON_ENEMY_DRAGON)
s->dragonPlaced = true;
}
fs->valid = true;
s->reachableCells += fs->reachableCells;
s->rooms += fs->rooms;
s->deadEnds += fs->deadEnds;
s->enemyRooms += fs->enemyRooms;
s->treasureRooms += fs->treasureRooms;
s->emptyRooms += fs->emptyRooms;
s->traps += fs->traps;
}
if (w->entrance.floor != 0U || w->entrance.x >= DUNGEON_WIDTH ||
w->entrance.y >= DUNGEON_HEIGHT ||
w->floors[w->entrance.floor]
.cells[w->entrance.y][w->entrance.x]
.tileType != DUNGEON_TILE_ENTRANCE) {
s->error = DUNGEON_GENERATION_NO_ENTRANCE;
return false;
}
if (w->exit.floor != DUNGEON_FLOOR_COUNT - 1U || w->exit.x >= DUNGEON_WIDTH ||
w->exit.y >= DUNGEON_HEIGHT ||
w->floors[w->exit.floor].cells[w->exit.y][w->exit.x].tileType !=
DUNGEON_TILE_EXIT) {
s->error = DUNGEON_GENERATION_NO_EXIT;
return false;
}
for (uint8_t i = 0; i < DUNGEON_FLOOR_COUNT - 1U; ++i) {
DungeonStairLink *l = &w->stairLinks[i];
if (l->upper.floor >= DUNGEON_FLOOR_COUNT ||
l->lower.floor >= DUNGEON_FLOOR_COUNT || l->upper.x >= DUNGEON_WIDTH ||
l->lower.x >= DUNGEON_WIDTH || l->upper.y >= DUNGEON_HEIGHT ||
l->lower.y >= DUNGEON_HEIGHT || l->upper.floor + 1U != l->lower.floor ||
w->floors[l->upper.floor].cells[l->upper.y][l->upper.x].tileType !=
DUNGEON_TILE_STAIRS_DOWN ||
w->floors[l->lower.floor].cells[l->lower.y][l->lower.x].tileType !=
DUNGEON_TILE_STAIRS_UP) {
s->error = DUNGEON_GENERATION_INVALID_STAIR_LINK;
return false;
}
}
s->stairLinks = DUNGEON_FLOOR_COUNT - 1U;
s->valid = true;
s->error = DUNGEON_GENERATION_OK;
return true;
}generateDungeonWorld() · source line 420
bool generateDungeonWorld(DungeonWorldGenerationStats *s)
Calls into this documentation:
- Decision
!sTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
memset(s, 0, sizeof(*s)); - Execute
initDungeonMap(); - FOR loop
uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++fRepeat body while the loop condition permits; then continue below.
- Decision
!generateDungeonFloor(f, &s->floors[f])True- Execute
s->error = s->floors[f].error; - Exit this function
return false;
FalseContinue after this decision.
- Execute
DungeonWorld *w = getDungeonWorld(); - Decision
!choose_dead(0U, &w->entrance)True- Execute
s->error = DUNGEON_GENERATION_NO_ENTRANCE; - Exit this function
return false;
FalseContinue after this decision.
- Execute
w->floors[w->entrance.floor].cells[w->entrance.y][w->entrance.x].tileType = DUNGEON_TILE_ENTRANCE; - Decision
!choose_dead(DUNGEON_FLOOR_COUNT - 1U, &w->exit)True- Execute
s->error = DUNGEON_GENERATION_NO_EXIT; - Exit this function
return false;
FalseContinue after this decision.
- Execute
w->floors[w->exit.floor].cells[w->exit.y][w->exit.x].tileType = DUNGEON_TILE_EXIT; - Execute
DungeonPosition prior = {255U, 0U, 0U}; - FOR loop
uint8_t i = 0; i < DUNGEON_FLOOR_COUNT - 1U; ++iRepeat body while the loop condition permits; then continue below.
- Execute
DungeonStairLink *l = &w->stairLinks[i]; - Decision
!choose_straight(i, &l->upper, i == 1U ? &prior : NULL) || !choose_straight((uint8_t)(i + 1U), &l->lower, NULL)True- Execute
s->error = DUNGEON_GENERATION_NO_STAIRS; - Exit this function
return false;
FalseContinue after this decision.
- Execute
w->floors[l->upper.floor].cells[l->upper.y][l->upper.x].tileType = DUNGEON_TILE_STAIRS_DOWN; - Execute
w->floors[l->lower.floor].cells[l->lower.y][l->lower.x].tileType = DUNGEON_TILE_STAIRS_UP; - Decision
i == 0UTrue- Execute
prior = l->lower;
FalseContinue after this decision.
- Execute
DungeonPosition key; - Decision
!key_room(&key)True- Execute
s->error = DUNGEON_GENERATION_NO_KEY_ROOM; - Exit this function
return false;
FalseContinue after this decision.
- Execute
event(&w->floors[key.floor].cells[key.y][key.x], DUNGEON_EVENT_KEY); - Execute
events(&key); - Execute
corridors(); - Execute
event(&w->floors[w->exit.floor].cells[w->exit.y][w->exit.x], DUNGEON_EVENT_ENCOUNTER | DUNGEON_EVENT_REQUIRES_KEY | DUNGEON_EVENT_MESSAGE | DUNGEON_EVENT_DOOR | ((uint16_t)DUNGEON_ENEMY_DRAGON << DUNGEON_ENEMY_DETAIL_SHIFT)); - Exit this function
return validateDungeonWorld(s);
Read original function source
bool generateDungeonWorld(DungeonWorldGenerationStats *s) {
if (!s)
return false;
memset(s, 0, sizeof(*s));
initDungeonMap();
for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f)
if (!generateDungeonFloor(f, &s->floors[f])) {
s->error = s->floors[f].error;
return false;
}
DungeonWorld *w = getDungeonWorld();
if (!choose_dead(0U, &w->entrance)) {
s->error = DUNGEON_GENERATION_NO_ENTRANCE;
return false;
}
w->floors[w->entrance.floor].cells[w->entrance.y][w->entrance.x].tileType =
DUNGEON_TILE_ENTRANCE;
if (!choose_dead(DUNGEON_FLOOR_COUNT - 1U, &w->exit)) {
s->error = DUNGEON_GENERATION_NO_EXIT;
return false;
}
w->floors[w->exit.floor].cells[w->exit.y][w->exit.x].tileType =
DUNGEON_TILE_EXIT;
DungeonPosition prior = {255U, 0U, 0U};
for (uint8_t i = 0; i < DUNGEON_FLOOR_COUNT - 1U; ++i) {
DungeonStairLink *l = &w->stairLinks[i];
if (!choose_straight(i, &l->upper, i == 1U ? &prior : NULL) ||
!choose_straight((uint8_t)(i + 1U), &l->lower, NULL)) {
s->error = DUNGEON_GENERATION_NO_STAIRS;
return false;
}
w->floors[l->upper.floor].cells[l->upper.y][l->upper.x].tileType =
DUNGEON_TILE_STAIRS_DOWN;
w->floors[l->lower.floor].cells[l->lower.y][l->lower.x].tileType =
DUNGEON_TILE_STAIRS_UP;
if (i == 0U)
prior = l->lower;
}
DungeonPosition key;
if (!key_room(&key)) {
s->error = DUNGEON_GENERATION_NO_KEY_ROOM;
return false;
}
event(&w->floors[key.floor].cells[key.y][key.x], DUNGEON_EVENT_KEY);
events(&key);
corridors();
event(&w->floors[w->exit.floor].cells[w->exit.y][w->exit.x],
DUNGEON_EVENT_ENCOUNTER | DUNGEON_EVENT_REQUIRES_KEY |
DUNGEON_EVENT_MESSAGE | DUNGEON_EVENT_DOOR |
((uint16_t)DUNGEON_ENEMY_DRAGON << DUNGEON_ENEMY_DETAIL_SHIFT));
return validateDungeonWorld(s);
}dungeonGenerationErrorText() · source line 472
const char *dungeonGenerationErrorText(DungeonGenerationError e)
- Select a case
eEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case DUNGEON_GENERATION_OK: - Exit this function
return "OK"; - Case / label
case DUNGEON_GENERATION_INVALID_FLOOR: - Exit this function
return "Invalid floor"; - Case / label
case DUNGEON_GENERATION_OUTSIDE_EXIT: - Exit this function
return "Exit outside floor"; - Case / label
case DUNGEON_GENERATION_EXIT_MISMATCH: - Exit this function
return "Exit mismatch"; - Case / label
case DUNGEON_GENERATION_UNREACHABLE_CELL: - Exit this function
return "Unreachable cell"; - Case / label
case DUNGEON_GENERATION_NO_ENTRANCE: - Exit this function
return "No entrance cell"; - Case / label
case DUNGEON_GENERATION_NO_EXIT: - Exit this function
return "No exit cell"; - Case / label
case DUNGEON_GENERATION_NO_STAIRS: - Exit this function
return "No stair cell"; - Case / label
case DUNGEON_GENERATION_INVALID_STAIR_LINK: - Exit this function
return "Invalid stair link"; - Case / label
case DUNGEON_GENERATION_INVALID_TILE: - Exit this function
return "Invalid tile"; - Case / label
case DUNGEON_GENERATION_NO_KEY_ROOM: - Exit this function
return "No key room"; - Case / label
default: - Exit this function
return "Unknown error";
Read original function source
const char *dungeonGenerationErrorText(DungeonGenerationError e) {
switch (e) {
case DUNGEON_GENERATION_OK:
return "OK";
case DUNGEON_GENERATION_INVALID_FLOOR:
return "Invalid floor";
case DUNGEON_GENERATION_OUTSIDE_EXIT:
return "Exit outside floor";
case DUNGEON_GENERATION_EXIT_MISMATCH:
return "Exit mismatch";
case DUNGEON_GENERATION_UNREACHABLE_CELL:
return "Unreachable cell";
case DUNGEON_GENERATION_NO_ENTRANCE:
return "No entrance cell";
case DUNGEON_GENERATION_NO_EXIT:
return "No exit cell";
case DUNGEON_GENERATION_NO_STAIRS:
return "No stair cell";
case DUNGEON_GENERATION_INVALID_STAIR_LINK:
return "Invalid stair link";
case DUNGEON_GENERATION_INVALID_TILE:
return "Invalid tile";
case DUNGEON_GENERATION_NO_KEY_ROOM:
return "No key room";
default:
return "Unknown error";
}
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "dungeon_generator.h"
#include "dungeon_config.h"
#include "dungeon_map.h"
#include "esp_attr.h"
#include "esp_random.h"
#include <string.h>
#define CELLS (DUNGEON_WIDTH * DUNGEON_HEIGHT)
EXT_RAM_BSS_ATTR static uint8_t visited[CELLS];
EXT_RAM_BSS_ATTR static uint16_t stack[CELLS], queue[CELLS], candidates[CELLS], dead_ends[CELLS];
static uint16_t ix(uint8_t x, uint8_t y) {
return (uint16_t)y * DUNGEON_WIDTH + x;
}
static uint8_t cx(uint16_t n) { return (uint8_t)(n % DUNGEON_WIDTH); }
static uint8_t cy(uint16_t n) { return (uint8_t)(n / DUNGEON_WIDTH); }
static uint8_t ex(uint8_t d) {
static const uint8_t v[] = {1U, 2U, 4U, 8U};
return v[d];
}
static uint8_t op(uint8_t d) {
static const uint8_t v[] = {4U, 8U, 1U, 2U};
return v[d];
}
static uint8_t ec(uint8_t e) {
uint8_t n = 0U;
for (uint8_t i = 0U; i < 4U; ++i)
n += (e >> i) & 1U;
return n;
}
static bool next(uint16_t c, uint8_t d, uint16_t *n) {
uint8_t x = cx(c), y = cy(c);
if (d == 0U && y > 0U)
*n = ix(x, y - 1U);
else if (d == 1U && x + 1U < DUNGEON_WIDTH)
*n = ix(x + 1U, y);
else if (d == 2U && y + 1U < DUNGEON_HEIGHT)
*n = ix(x, y + 1U);
else if (d == 3U && x > 0U)
*n = ix(x - 1U, y);
else
return false;
return true;
}
static bool room(uint8_t t) {
return t == DUNGEON_TILE_DEAD_END_ROOM_ROUND ||
t == DUNGEON_TILE_DEAD_END_ROOM_SQUARE ||
t == DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
}
static void shuffle(uint16_t n) {
for (; n > 1U; --n) {
uint16_t s = (uint16_t)(esp_random() % n), t = candidates[n - 1U];
candidates[n - 1U] = candidates[s];
candidates[s] = t;
}
}
static DungeonGenerationError validate_floor(const DungeonFloor *f,
uint16_t *reachable) {
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
uint16_t c = ix(x, y);
for (uint8_t d = 0; d < 4; ++d)
if (f->cells[y][x].exits & ex(d)) {
uint16_t n;
if (!next(c, d, &n))
return DUNGEON_GENERATION_OUTSIDE_EXIT;
if (!(f->cells[cy(n)][cx(n)].exits & op(d)))
return DUNGEON_GENERATION_EXIT_MISMATCH;
}
}
memset(visited, 0, sizeof(visited));
uint16_t a = 0, b = 0;
queue[b++] = 0;
visited[0] = 1U;
while (a < b) {
uint16_t c = queue[a++];
for (uint8_t d = 0; d < 4; ++d) {
uint16_t n;
if ((f->cells[cy(c)][cx(c)].exits & ex(d)) && next(c, d, &n) &&
!visited[n]) {
visited[n] = 1U;
queue[b++] = n;
}
}
}
*reachable = b;
return b == CELLS ? DUNGEON_GENERATION_OK
: DUNGEON_GENERATION_UNREACHABLE_CELL;
}
static void classify(DungeonFloor *f, DungeonGenerationStats *s) {
uint16_t count = 0;
uint32_t exits = 0;
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
DungeonCell *c = &f->cells[y][x];
uint8_t n = ec(c->exits);
exits += n;
c->tileType =
n == 1U ? DUNGEON_TILE_DEAD_END_PASSAGE : DUNGEON_TILE_PASSAGE;
if (n == 1U)
dead_ends[count++] = ix(x, y);
}
uint16_t rooms = count < getDungeonConfig()->roomsPerFloor
? count
: getDungeonConfig()->roomsPerFloor;
for (uint16_t i = 0; i < rooms; ++i) {
uint16_t p = i + (uint16_t)(esp_random() % (count - i)), t = dead_ends[i];
dead_ends[i] = dead_ends[p];
dead_ends[p] = t;
f->cells[cy(dead_ends[i])][cx(dead_ends[i])].tileType =
i % 3U == 0U ? DUNGEON_TILE_DEAD_END_ROOM_ROUND
: i % 3U == 1U ? DUNGEON_TILE_DEAD_END_ROOM_SQUARE
: DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
}
s->rooms = rooms;
s->deadEnds = count;
s->connections = (uint16_t)(exits / 2U);
s->loops = (uint16_t)(s->connections - CELLS + 1U);
}
bool generateDungeonFloor(uint8_t floor, DungeonGenerationStats *s) {
if (!s)
return false;
memset(s, 0, sizeof(*s));
if (floor >= DUNGEON_FLOOR_COUNT) {
s->error = DUNGEON_GENERATION_INVALID_FLOOR;
return false;
}
DungeonFloor *f = &getDungeonWorld()->floors[floor];
memset(f, 0, sizeof(*f));
memset(visited, 0, sizeof(visited));
uint16_t depth = 0, start = ix((uint8_t)(esp_random() % DUNGEON_WIDTH),
(uint8_t)(esp_random() % DUNGEON_HEIGHT));
stack[depth++] = start;
visited[start] = 1U;
while (depth) {
uint16_t c = stack[depth - 1U], n;
uint8_t choices[4], count = 0;
for (uint8_t d = 0; d < 4; ++d)
if (next(c, d, &n) && !visited[n])
choices[count++] = d;
if (!count) {
--depth;
continue;
}
uint8_t d = choices[esp_random() % count];
(void)next(c, d, &n);
f->cells[cy(c)][cx(c)].exits |= ex(d);
f->cells[cy(n)][cx(n)].exits |= op(d);
visited[n] = 1U;
stack[depth++] = n;
}
uint16_t target = (uint16_t)(((uint32_t)CELLS *
getDungeonConfig()->extraConnectionPercent +
99U) /
100U),
added = 0;
for (uint32_t tries = 0; added < target && tries < (uint32_t)CELLS * 10U;
++tries) {
uint16_t c = (uint16_t)(esp_random() % CELLS), n;
uint8_t d = (uint8_t)(esp_random() % 4U);
if (!next(c, d, &n) || (f->cells[cy(c)][cx(c)].exits & ex(d)))
continue;
f->cells[cy(c)][cx(c)].exits |= ex(d);
f->cells[cy(n)][cx(n)].exits |= op(d);
++added;
}
classify(f, s);
s->error = validate_floor(f, &s->reachableCells);
s->valid = s->error == DUNGEON_GENERATION_OK;
return s->valid;
}
static bool choose_dead(uint8_t floor, DungeonPosition *p) {
uint16_t n = 0;
DungeonFloor *f = &getDungeonWorld()->floors[floor];
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x)
if (f->cells[y][x].tileType == DUNGEON_TILE_DEAD_END_PASSAGE)
candidates[n++] = ix(x, y);
if (!n)
return false;
uint16_t c = candidates[esp_random() % n];
*p = (DungeonPosition){floor, cx(c), cy(c)};
return true;
}
static bool choose_straight(uint8_t floor, DungeonPosition *p,
const DungeonPosition *skip) {
uint16_t n = 0;
DungeonFloor *f = &getDungeonWorld()->floors[floor];
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
DungeonCell *c = &f->cells[y][x];
if (c->tileType == DUNGEON_TILE_PASSAGE &&
(c->exits == (DUNGEON_EXIT_NORTH | DUNGEON_EXIT_SOUTH) ||
c->exits == (DUNGEON_EXIT_EAST | DUNGEON_EXIT_WEST)) &&
!(skip && skip->floor == floor && skip->x == x && skip->y == y))
candidates[n++] = ix(x, y);
}
if (!n)
return false;
uint16_t c = candidates[esp_random() % n];
*p = (DungeonPosition){floor, cx(c), cy(c)};
return true;
}
static void erange(uint8_t f, uint8_t *lo, uint8_t *hi) {
const DungeonConfig *c = getDungeonConfig();
*lo = f == 0U ? c->floor1EnemyMinLevel
: f == 1U ? c->floor2EnemyMinLevel
: c->floor3EnemyMinLevel;
*hi = f == 0U ? c->floor1EnemyMaxLevel
: f == 1U ? c->floor2EnemyMaxLevel
: c->floor3EnemyMaxLevel;
}
static void event(DungeonCell *c, uint16_t flags) {
c->eventFlags = flags;
c->stateFlags = DUNGEON_STATE_NONE;
c->variant = 1U;
}
static uint8_t treasure(void) {
const DungeonConfig *c = getDungeonConfig();
if (c->rareTreasurePercent && esp_random() % 100U < c->rareTreasurePercent) {
uint32_t t = (uint32_t)c->rareLifeRatio + c->rareAttackRatio +
c->rareDefenceRatio,
r = t ? esp_random() % t : 0U;
if (r < c->rareLifeRatio)
return DUNGEON_TREASURE_LIFE;
r -= c->rareLifeRatio;
return r < c->rareAttackRatio ? DUNGEON_TREASURE_ATTACK
: DUNGEON_TREASURE_DEFENCE;
}
uint32_t t = (uint32_t)c->normalGoldRatio + c->normalHealthRatio;
return !t || esp_random() % t < c->normalGoldRatio ? DUNGEON_TREASURE_GOLD
: DUNGEON_TREASURE_HEALTH;
}
static bool key_room(DungeonPosition *p) {
uint16_t n = 0;
DungeonWorld *w = getDungeonWorld();
for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f)
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x)
if (room(w->floors[f].cells[y][x].tileType)) {
++n;
if (esp_random() % n == 0U)
*p = (DungeonPosition){f, x, y};
}
return n > 0;
}
static void room_counts(uint16_t n, uint16_t out[3]) {
const DungeonConfig *c = getDungeonConfig();
uint16_t w[3] = {c->enemyRoomRatio, c->treasureRoomRatio, c->emptyRoomRatio};
uint32_t total = (uint32_t)w[0] + w[1] + w[2], rem[3];
uint16_t done = 0;
if (!total) {
out[2] = n;
return;
}
for (uint8_t i = 0; i < 3; ++i) {
uint32_t q = (uint32_t)n * w[i];
out[i] = (uint16_t)(q / total);
rem[i] = q % total;
done += out[i];
}
while (done < n) {
uint32_t high = 0;
uint8_t ties[3], nt = 0;
for (uint8_t i = 0; i < 3; ++i)
if (rem[i] > high) {
high = rem[i];
ties[0] = i;
nt = 1;
} else if (rem[i] == high)
ties[nt++] = i;
uint8_t k = ties[esp_random() % nt];
++out[k];
rem[k] = 0;
++done;
}
}
static void events(const DungeonPosition *key) {
DungeonWorld *w = getDungeonWorld();
for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f) {
uint16_t n = 0;
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x)
if (room(w->floors[f].cells[y][x].tileType) &&
!(key->floor == f && key->x == x && key->y == y))
candidates[n++] = ix(x, y);
shuffle(n);
uint16_t types[3] = {0, 0, 0};
room_counts(n, types);
uint8_t lo, hi;
erange(f, &lo, &hi);
uint16_t i = 0;
for (; i < types[0]; ++i) {
DungeonCell *c =
&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])];
event(c, DUNGEON_EVENT_ENCOUNTER |
((uint16_t)(lo + esp_random() % (hi - lo + 1U))
<< DUNGEON_ENEMY_DETAIL_SHIFT));
}
for (; i < (uint16_t)(types[0] + types[1]); ++i)
event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])],
DUNGEON_EVENT_TREASURE |
((uint16_t)treasure() << DUNGEON_TREASURE_DETAIL_SHIFT));
}
}
static void corridors(void) {
DungeonWorld *w = getDungeonWorld();
const DungeonConfig *c = getDungeonConfig();
for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f) {
uint16_t r = 0, n = 0;
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
uint8_t t = w->floors[f].cells[y][x].tileType;
if (room(t))
++r;
if (t == DUNGEON_TILE_PASSAGE || t == DUNGEON_TILE_DEAD_END_PASSAGE)
candidates[n++] = ix(x, y);
}
uint16_t traps =
(uint16_t)(((uint32_t)r * c->trapsPer100Rooms + 99U) / 100U),
tr = (uint16_t)(((uint32_t)r * c->corridorTreasuresPer100Rooms +
99U) /
100U);
if (traps > n)
traps = n;
if (tr > n - traps)
tr = n - traps;
shuffle(n);
for (uint16_t i = 0; i < traps; ++i)
event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])],
DUNGEON_EVENT_TRAP);
for (uint16_t i = traps; i < (uint16_t)(traps + tr); ++i)
event(&w->floors[f].cells[cy(candidates[i])][cx(candidates[i])],
DUNGEON_EVENT_TREASURE |
((uint16_t)treasure() << DUNGEON_TREASURE_DETAIL_SHIFT));
}
}
bool validateDungeonWorld(DungeonWorldGenerationStats *s) {
if (!s)
return false;
memset(s, 0, sizeof(*s));
DungeonWorld *w = getDungeonWorld();
for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f) {
DungeonGenerationStats *fs = &s->floors[f];
fs->error = validate_floor(&w->floors[f], &fs->reachableCells);
if (fs->error != DUNGEON_GENERATION_OK) {
s->error = fs->error;
return false;
}
for (uint8_t y = 0; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0; x < DUNGEON_WIDTH; ++x) {
DungeonCell *c = &w->floors[f].cells[y][x];
if (c->tileType > DUNGEON_TILE_STAIRS_BOTH) {
s->error = DUNGEON_GENERATION_INVALID_TILE;
return false;
}
if (ec(c->exits) == 1U)
++fs->deadEnds;
if (room(c->tileType)) {
++fs->rooms;
if (c->eventFlags & DUNGEON_EVENT_ENCOUNTER)
++fs->enemyRooms;
else if (c->eventFlags & DUNGEON_EVENT_TREASURE)
++fs->treasureRooms;
else
++fs->emptyRooms;
}
if (c->eventFlags & DUNGEON_EVENT_TRAP)
++fs->traps;
if (c->eventFlags & DUNGEON_EVENT_KEY)
s->keyPlaced = true;
if ((c->eventFlags & DUNGEON_EVENT_ENCOUNTER) &&
((c->eventFlags & DUNGEON_ENEMY_DETAIL_MASK) >>
DUNGEON_ENEMY_DETAIL_SHIFT) == DUNGEON_ENEMY_DRAGON)
s->dragonPlaced = true;
}
fs->valid = true;
s->reachableCells += fs->reachableCells;
s->rooms += fs->rooms;
s->deadEnds += fs->deadEnds;
s->enemyRooms += fs->enemyRooms;
s->treasureRooms += fs->treasureRooms;
s->emptyRooms += fs->emptyRooms;
s->traps += fs->traps;
}
if (w->entrance.floor != 0U || w->entrance.x >= DUNGEON_WIDTH ||
w->entrance.y >= DUNGEON_HEIGHT ||
w->floors[w->entrance.floor]
.cells[w->entrance.y][w->entrance.x]
.tileType != DUNGEON_TILE_ENTRANCE) {
s->error = DUNGEON_GENERATION_NO_ENTRANCE;
return false;
}
if (w->exit.floor != DUNGEON_FLOOR_COUNT - 1U || w->exit.x >= DUNGEON_WIDTH ||
w->exit.y >= DUNGEON_HEIGHT ||
w->floors[w->exit.floor].cells[w->exit.y][w->exit.x].tileType !=
DUNGEON_TILE_EXIT) {
s->error = DUNGEON_GENERATION_NO_EXIT;
return false;
}
for (uint8_t i = 0; i < DUNGEON_FLOOR_COUNT - 1U; ++i) {
DungeonStairLink *l = &w->stairLinks[i];
if (l->upper.floor >= DUNGEON_FLOOR_COUNT ||
l->lower.floor >= DUNGEON_FLOOR_COUNT || l->upper.x >= DUNGEON_WIDTH ||
l->lower.x >= DUNGEON_WIDTH || l->upper.y >= DUNGEON_HEIGHT ||
l->lower.y >= DUNGEON_HEIGHT || l->upper.floor + 1U != l->lower.floor ||
w->floors[l->upper.floor].cells[l->upper.y][l->upper.x].tileType !=
DUNGEON_TILE_STAIRS_DOWN ||
w->floors[l->lower.floor].cells[l->lower.y][l->lower.x].tileType !=
DUNGEON_TILE_STAIRS_UP) {
s->error = DUNGEON_GENERATION_INVALID_STAIR_LINK;
return false;
}
}
s->stairLinks = DUNGEON_FLOOR_COUNT - 1U;
s->valid = true;
s->error = DUNGEON_GENERATION_OK;
return true;
}
bool generateDungeonWorld(DungeonWorldGenerationStats *s) {
if (!s)
return false;
memset(s, 0, sizeof(*s));
initDungeonMap();
for (uint8_t f = 0; f < DUNGEON_FLOOR_COUNT; ++f)
if (!generateDungeonFloor(f, &s->floors[f])) {
s->error = s->floors[f].error;
return false;
}
DungeonWorld *w = getDungeonWorld();
if (!choose_dead(0U, &w->entrance)) {
s->error = DUNGEON_GENERATION_NO_ENTRANCE;
return false;
}
w->floors[w->entrance.floor].cells[w->entrance.y][w->entrance.x].tileType =
DUNGEON_TILE_ENTRANCE;
if (!choose_dead(DUNGEON_FLOOR_COUNT - 1U, &w->exit)) {
s->error = DUNGEON_GENERATION_NO_EXIT;
return false;
}
w->floors[w->exit.floor].cells[w->exit.y][w->exit.x].tileType =
DUNGEON_TILE_EXIT;
DungeonPosition prior = {255U, 0U, 0U};
for (uint8_t i = 0; i < DUNGEON_FLOOR_COUNT - 1U; ++i) {
DungeonStairLink *l = &w->stairLinks[i];
if (!choose_straight(i, &l->upper, i == 1U ? &prior : NULL) ||
!choose_straight((uint8_t)(i + 1U), &l->lower, NULL)) {
s->error = DUNGEON_GENERATION_NO_STAIRS;
return false;
}
w->floors[l->upper.floor].cells[l->upper.y][l->upper.x].tileType =
DUNGEON_TILE_STAIRS_DOWN;
w->floors[l->lower.floor].cells[l->lower.y][l->lower.x].tileType =
DUNGEON_TILE_STAIRS_UP;
if (i == 0U)
prior = l->lower;
}
DungeonPosition key;
if (!key_room(&key)) {
s->error = DUNGEON_GENERATION_NO_KEY_ROOM;
return false;
}
event(&w->floors[key.floor].cells[key.y][key.x], DUNGEON_EVENT_KEY);
events(&key);
corridors();
event(&w->floors[w->exit.floor].cells[w->exit.y][w->exit.x],
DUNGEON_EVENT_ENCOUNTER | DUNGEON_EVENT_REQUIRES_KEY |
DUNGEON_EVENT_MESSAGE | DUNGEON_EVENT_DOOR |
((uint16_t)DUNGEON_ENEMY_DRAGON << DUNGEON_ENEMY_DETAIL_SHIFT));
return validateDungeonWorld(s);
}
const char *dungeonGenerationErrorText(DungeonGenerationError e) {
switch (e) {
case DUNGEON_GENERATION_OK:
return "OK";
case DUNGEON_GENERATION_INVALID_FLOOR:
return "Invalid floor";
case DUNGEON_GENERATION_OUTSIDE_EXIT:
return "Exit outside floor";
case DUNGEON_GENERATION_EXIT_MISMATCH:
return "Exit mismatch";
case DUNGEON_GENERATION_UNREACHABLE_CELL:
return "Unreachable cell";
case DUNGEON_GENERATION_NO_ENTRANCE:
return "No entrance cell";
case DUNGEON_GENERATION_NO_EXIT:
return "No exit cell";
case DUNGEON_GENERATION_NO_STAIRS:
return "No stair cell";
case DUNGEON_GENERATION_INVALID_STAIR_LINK:
return "Invalid stair link";
case DUNGEON_GENERATION_INVALID_TILE:
return "Invalid tile";
case DUNGEON_GENERATION_NO_KEY_ROOM:
return "No key room";
default:
return "Unknown error";
}
}