Skip to content

Commit

Permalink
chunky: Add boss room filter, enemy types and breakable walls
Browse files Browse the repository at this point in the history
The enemy archetypes are used for room placements.
  • Loading branch information
perim committed Aug 27, 2023
1 parent e82b35c commit 29d80af
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 28 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ function, like this:
seed s = seed_random();
```

And then you can roll values like this:

```c++
int r = s.roll(1, 6); // simulate a 6-sided dice roll
```

Note that if you copy the `seed` class by value instead of passing it by
reference, updates to the internal generator state will not be made in the
original. This is sometimes a useful feature, and sometimes not what you
Expand Down Expand Up @@ -135,7 +141,7 @@ Linear series
This is another variant of the linear roll table described above, but it
uses a constant, small amount memory for any size of roll table and all
operations are O(1) when your table is one less than power of two sized.
When it is not this ize, it will generate a number of rerolls each roll
When it is not this size, it will generate a number of rerolls each roll
depending on how far its size is to the next power of two. If far off, the
above roll table is slightly faster, if close this one is faster. It
supports reserving a portion of its range and changing this reservation on
Expand All @@ -147,7 +153,7 @@ Example:
linear_series ls4k(s, 4096-1); // storing a 4k roll table in just 40 bytes
int result1 = ls4k.roll(); // and get the result in O(1) time
int result2 = ls4k.roll(); // guaranteed to be different than result1
ls4k.reset(); // generate another random shuffle including the above results
ls4k.reset(); // generate another random shuffle in O(1) with all results
```

Luck
Expand Down
137 changes: 128 additions & 9 deletions chunky.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,21 +315,55 @@ void chunk_filter_connect_exits(chunk& c)
CHUNK_ASSERT(c, c.rooms.size() > 0);
}

#define DBLACK "\e[0;30m"
#define DRED "\e[0;31m"
#define DGREEN "\e[0;32m"
#define DYELLOW "\e[0;33m"
#define DBLUE "\e[0;34m"
#define DPINK "\e[0;35m"
#define DCYAN "\e[0;36m"
#define DGRAY "\e[0;37m"
#define LGRAY "\e[0;90m"
#define LRED "\e[0;91m"
#define LGREEN "\e[0;92m"
#define LYELLOW "\e[0;93m"
#define LBLUE "\e[0;94m"
#define LPINK "\e[0;95m"
#define LCYAN "\e[0;96m"
#define LWHITE "\e[0;97m"
static inline void print_tile(uint8_t t)
{
switch (t)
{
case TILE_ROCK: printf(" "); break;
case TILE_WALL: printf("#"); break;
case TILE_DOOR: printf("+"); break;
case TILE_ONE_WAY_TOP: printf("^"); break;
case TILE_ONE_WAY_BOTTOM: printf("_"); break;
case TILE_ONE_WAY_RIGHT: printf("]"); break;
case TILE_ONE_WAY_LEFT: printf("["); break;
case TILE_DOOR: printf(DCYAN"+"); break;
case TILE_ONE_WAY_TOP: printf(DCYAN"^"); break;
case TILE_ONE_WAY_BOTTOM: printf(DCYAN"_"); break;
case TILE_ONE_WAY_RIGHT: printf(DCYAN"]"); break;
case TILE_ONE_WAY_LEFT: printf(DCYAN"["); break;
case TILE_EMPTY: printf("."); break;
case TILE_DEBRIS: printf("*"); break;
case TILE_WALL_DAMAGED: printf(LGRAY"#"); break;
case TILE_DEBRIS: printf(LGRAY"*"); break;
case TILE_RAIL: printf(DBLUE"."); break;
case TILE_SENTINEL: printf(LRED"§"); break;
case TILE_TURRET: printf(LRED"¤"); break;
case TILE_TOTEM: printf(LRED"I"); break;
case TILE_TRAP: printf(LRED"~"); break;
case TILE_ALTAR: printf(LBLUE"A"); break;
case TILE_SHRINE: printf(LBLUE"S"); break;
case TILE_HIDDEN_GROVE: printf(LBLUE"G"); break;
case TILE_SHRUB: printf(DGREEN"t"); break;

case ENTITY_BOSS: printf(LRED"B"); break;
case ENTITY_LEADER: printf(LRED"L"); break;
case ENTITY_SUPPORT: printf(LGREEN"s"); break;
case ENTITY_TANK: printf(LBLUE"T"); break;
case ENTITY_DAMAGE: printf("d"); break;
case ENTITY_SPECIALIST: printf(LCYAN"S"); break;
default: assert(false); break;
}
printf("\x1b[0m");
}

void chunk::print_chunk() const
Expand All @@ -353,10 +387,8 @@ void print_room(const chunk& c, const room& r)
for (int x = 0; x < c.width; x++)
{
int t = c.at(x, y);
if (x >= r.x1 && x <= r.x2 && y >= r.y1 && y <= r.y2) printf("\e[0;33m");
else if (t >= TILE_DOOR && t <= TILE_ONE_WAY_RIGHT) printf("\e[0;36m");
if (t == TILE_EMPTY && x >= r.x1 && x <= r.x2 && y >= r.y1 && y <= r.y2) printf(DYELLOW);
print_tile(t);
printf("\x1b[0m");
}
if (y == 0) printf("\tSize: %d", r.size());
else if (y == 1) printf("\tIsolation: %d", r.isolation);
Expand Down Expand Up @@ -551,6 +583,24 @@ void chunk_filter_room_in_room(chunk& c)
}
}

void breakdown_wall_horizontally(chunk& c, int x1, int x2, int y, seed& s)
{
for (int i = x1 + 1; i < x2; i++)
{
if (c.at(i, y) == TILE_WALL && s.roll(1, 6) <= c.config.brokenness) c.build(i, y, TILE_WALL_DAMAGED);
if (c.at(i, y) == TILE_WALL_DAMAGED && s.roll(1, 8) <= c.config.brokenness) c.build(i, y, TILE_EMPTY);
}
}

void breakdown_wall_vertically(chunk& c, int x, int y1, int y2, seed& s)
{
for (int i = y1 + 1; i < y2; i++)
{
if (c.at(x, i) == TILE_WALL && s.roll(1, 6) <= c.config.brokenness) c.build(x, i, TILE_WALL_DAMAGED);
if (c.at(x, i) == TILE_WALL_DAMAGED && s.roll(1, 8) <= c.config.brokenness) c.build(x, i, TILE_EMPTY);
}
}

bool chunk_room_in_room(chunk& c, room& r, int space)
{
assert(space >= 1);
Expand All @@ -567,6 +617,11 @@ bool chunk_room_in_room(chunk& c, room& r, int space)
default: abort();
}
c.add_room(r2);
seed s = c.config.state; // make sure we don't clobber our random state with the below
breakdown_wall_horizontally(c, r2.x1 - 1, r2.x2 + 1, r2.y1 - 1, s);
breakdown_wall_horizontally(c, r2.x1 - 1, r2.x2 + 1, r2.y2 + 1, s);
breakdown_wall_vertically(c, r2.x1 - 1, r2.y1 - 1, r2.y2 + 1, s);
breakdown_wall_vertically(c, r2.x2 + 1, r2.y1 - 1, r2.y2 + 1, s);
r2.self_test();
c.rooms.push_back(r2);
return true;
Expand Down Expand Up @@ -645,3 +700,67 @@ bool chunk_room_corners(chunk& c, room& r, int corners, int min)
if (retval) r.flags |= ROOM_FLAG_FURNISHED;
return retval;
}

bool find_location(chunk& c, room& r, int& x, int& y)
{
seed s = c.config.state; // make sure we don't clobber our random state with the below
int count = 0;
while (count < 100)
{
x = s.roll(r.x1, r.x2);
y = s.roll(r.y1, r.y2);
if (c.empty(x, y)) return true;
count++;
}
return false;
}

static int room_exit_guards(chunk& c, room& r, tile_type entity)
{
int spent = 0;
if (r.top != -1) { c.build(r.top - 1, r.y1, ENTITY_DAMAGE); c.build(r.top + 1, r.y1, entity); spent += 2; }
if (r.bottom != -1) { c.build(r.bottom - 1, r.y2, ENTITY_DAMAGE); c.build(r.bottom + 1, r.y2, entity); spent += 2; }
if (r.left != -1) { c.build(r.x1, r.left - 1, ENTITY_DAMAGE); c.build(r.x1, r.left + 1, entity); spent += 2; }
if (r.right != -1) { c.build(r.x2, r.right - 1, ENTITY_DAMAGE); c.build(r.x2, r.right + 1, entity); spent += 2; }
return spent;
}

room& chunk_filter_boss_placement(chunk& c, int flags)
{
room& rr = c.rooms[0];
for (room& r : c.rooms)
{
if (r.size() + r.isolation * 5 > rr.size() + rr.isolation * 5 && !(r.flags & ROOM_FLAG_FURNISHED)) rr = r;
}
c.build(rr.x1 + rr.width() / 2, rr.y1 + rr.height() / 2, ENTITY_BOSS);
rr.flags |= ROOM_FLAG_FURNISHED;
seed s = c.config.state; // make sure we don't clobber our random state with the below
int x;
int y;
int minions = s.roll(2, 4);
int fodder = s.roll(4, 7) - minions;

if (s.roll(0, 2) + c.config.chaos < 2)
{
x = rr.x1 + rr.width() / 2;
y = rr.y1 + rr.height() / 2;
int dist = s.roll(2, 3);
if (minions >= 4 || (minions > 0 && s.roll(0, 1) == 0)) { c.build(x + dist, y, ENTITY_SUPPORT); minions--; } // right
if (minions >= 3 || (minions > 0 && s.roll(0, 1) == 0)) { c.build(x - dist, y, ENTITY_SUPPORT); minions--; } // left
if (minions >= 2 || (minions > 0 && s.roll(0, 1) == 0)) { c.build(x, y + dist, ENTITY_SUPPORT); minions--; } // top
if (minions >= 1) { c.build(x, y - dist, ENTITY_SUPPORT); minions--; } // bottom
fodder -= room_exit_guards(c, rr, ENTITY_DAMAGE);
}

for (int i = 0; i < fodder; i++) { if (find_location(c, rr, x, y)) c.build(x, y, ENTITY_DAMAGE); }
for (int i = 0; i < minions; i++) { if (find_location(c, rr, x, y)) c.build(x, y, ENTITY_SUPPORT); }

int v = s.roll(0, 2);
for (int i = 0; i < v; i++) { if (find_location(c, rr, x, y)) c.build(x, y, TILE_TURRET); }
int w = std::max(0, s.roll(0, 2) - v);
for (int i = 0; i < w; i++) { if (find_location(c, rr, x, y)) c.build(x, y, TILE_TOTEM); }
int q = std::max(0, 4 - (v + w));
for (int i = 0; i < q; i++) { if (find_location(c, rr, x, y)) c.build(x, y, TILE_TRAP); }

return rr;
}
51 changes: 38 additions & 13 deletions chunky.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
#define DIR_LEFT 0b0001
#define DIR_CROSS 0b1111

enum
enum corner_type
{
CHUNK_TOP_LEFT = 1,
CHUNK_TOP_RIGHT = 2,
Expand All @@ -44,15 +44,32 @@ enum

enum tile_type
{
TILE_ROCK = 0,
TILE_EMPTY = 1,
TILE_DOOR = 2,
TILE_ONE_WAY_TOP = 3,
TILE_ONE_WAY_BOTTOM = 4,
TILE_ONE_WAY_LEFT = 5,
TILE_ONE_WAY_RIGHT = 6,
TILE_WALL = 7,
TILE_DEBRIS = 8,
TILE_ROCK,
TILE_EMPTY,
TILE_DOOR,
TILE_ONE_WAY_TOP,
TILE_ONE_WAY_BOTTOM,
TILE_ONE_WAY_LEFT,
TILE_ONE_WAY_RIGHT,
TILE_WALL,
TILE_WALL_DAMAGED,
TILE_DEBRIS,
TILE_RAIL,
TILE_SENTINEL,
TILE_TURRET,
TILE_TOTEM,
TILE_TRAP,
TILE_ALTAR,
TILE_SHRINE,
TILE_HIDDEN_GROVE,
TILE_SHRUB,

ENTITY_BOSS,
ENTITY_LEADER,
ENTITY_SUPPORT,
ENTITY_TANK,
ENTITY_DAMAGE,
ENTITY_SPECIALIST,
};

struct chunkconfig
Expand All @@ -64,6 +81,8 @@ struct chunkconfig
int openness = 2;
/// The higher this value, the more chaotic the design.
int chaos = 1;
/// The higher this value, the more broken down everything will be. Make sure changing this does not alter the layout.
int brokenness = 1;
seed state;

// Our place in the world
Expand All @@ -90,9 +109,11 @@ struct room
int8_t flags = 0;

void self_test() const;
//void self_test(const chunk& c) const;
int size() const { return (x2 - x1 + 1) * (y2 - y1 + 1); }
bool valid() const { return (x2 >= x1 && y2 >= y1 && x2 >= 0 && y2 >= 0 && y1 >= 0 && x1 >= 0 && (top <= x2 || top == -1) && (top >= x1 || top == -1) && (bottom <= x2 || bottom == -1) && (bottom >= x1 || bottom == -1) && (left >= y1 || left == -1) && (left <= y2 || left == -1) && (right >= y1 || right == -1) && (right <= y2 || right == -1)); }
inline int width() const { return x2 - x1; }
inline int height() const { return y2 - y1; }
inline int door_count() const { return (top != -1) + (bottom != -1) + (left != -1) + (right != -1); }

room(int _x1, int _y1, int _x2, int _y2, int _isolation = 0, int _flags = 0) : x1(_x1), y1(_y1), x2(_x2), y2(_y2), isolation(_isolation), flags(_flags) {}
};
Expand Down Expand Up @@ -146,14 +167,15 @@ struct chunk
}

// Low-level functions
inline bool rock(int x, int y) const { const int i = map.at((y << bits) + x); return i == TILE_ROCK || i == TILE_WALL; }
inline bool rock(int x, int y) const { const int i = map.at((y << bits) + x); return i == TILE_ROCK || i == TILE_WALL || i == TILE_WALL_DAMAGED; }
inline bool empty(int x, int y) const { return (map[(y << bits) + x] == TILE_EMPTY); }
inline bool wall(int x, int y) const { return (map[(y << bits) + x] == TILE_WALL); }
inline bool wall(int x, int y) const { const int i = map[(y << bits) + x]; return i == TILE_WALL || i == TILE_WALL_DAMAGED; }
inline void fill(int x, int y) { map[(y << bits) + x] = TILE_ROCK; }
inline void makewall(int x, int y) { map[(y << bits) + x] = TILE_WALL; }
inline uint8_t at(int x, int y) const { return map.at((y << bits) + x); }
inline bool border(int x, int y) const { return (x == 0 || y == 0 || x == width - 1 || y == height -1); }
inline void build(int x, int y, tile_type t) { map[(y << bits) + x] = t; }
inline bool try_build(int x, int y, tile_type t) { if (empty(x, y)) { map[(y << bits) + x] = t; return true; } else return false; }
inline void dig(int x, int y) { map[(y << bits) + x] = TILE_EMPTY; for (int i = std::max(0, x - 1); i <= std::min(width - 1, x + 1); i++) for (int j = std::max(0, y - 1); j <= std::min(height - 1, y + 1); j++) if (rock(i, j)) map[(j << bits) + i] = TILE_WALL; }
inline int roll(int low, int high) { return config.state.roll(low, high); } // convenience function
inline void horizontal_corridor(int x1, int x2, int y) { for (int i = x1; i <= x2; i++) { dig(i, y); } rooms.emplace_back(x1, y, x2, y, ROOM_FLAG_CORRIDOR); }
Expand Down Expand Up @@ -202,6 +224,9 @@ void chunk_filter_one_way_doors(chunk& c, int threshold = 3);
/// and neat flag if you want rooms to align more neatly.
void chunk_filter_room_expand(chunk& c, int min = 2, int max = 6);

/// Find the best room in the chunk for a boss room, and place a boss token in it.
room& chunk_filter_boss_placement(chunk& c, int flags);

// -- Room utility functions --

/// Fill a room.
Expand Down
2 changes: 1 addition & 1 deletion dmath.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ __attribute__((const)) static inline uint32_t next_pow2(uint32_t v) { v; v--; v
uint32_t lfsr_tap(uint32_t size) __attribute__((const));

/// Flexible linear feedback shift register. Get the magic tap constant for your bit length with lsfr_tap(). Your value will be stored in 'state'.
static inline constexpr void lfsr_next(uint64_t& state, uint32_t tap) { const uint_fast32_t lsb = state & 1; state >>= 1; if (lsb) state ^= tap; }
static inline constexpr void lfsr_next(uint64_t& state, uint32_t tap) { const uint_fast32_t lsb = state & 1; state >>= 1; state ^= (-lsb) & tap; }

/// Generate valid LFSR input state. Call lfsr_next() to get your first value.
static inline constexpr __attribute__((const)) uint64_t lfsr_init(uint64_t state, uint32_t bits) { return fastrange(splitmix64(state), 1, 1 << bits); }
Expand Down
1 change: 1 addition & 0 deletions tests/chunkytest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ static void stress_test(seed s, bool debug)
c.self_test();
chunk_filter_one_way_doors(c, s.roll(0, 4));
c.beautify();
chunk_filter_boss_placement(c, 0);
if (debug) print_room(c, s.roll(0, c.rooms.size() - 1));
}

Expand Down
3 changes: 0 additions & 3 deletions tests/test1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ static void test_linear_series_1()
assert(v1 != v2);
assert(v1 == 0 || v1 == 1);
assert(v2 == 0 || v2 == 1);
printf("%u and %u\n", v1, v2);
}
}
static void test_linear_series_2()
Expand All @@ -201,7 +200,6 @@ static void test_linear_series_2()
assert(v1 == 0 || v2 == 0 || v3 == 0);
assert(v1 == 1 || v2 == 1 || v3 == 1);
assert(v1 == 2 || v2 == 2 || v3 == 2);
printf("%u, %u, %u\n", v1, v2, v3);
}
}
static void test_linear_series_3()
Expand Down Expand Up @@ -230,7 +228,6 @@ static void test_linear_series_3()
assert(v1 == 1 || v2 == 1 || v3 == 1 || v4 == 1);
assert(v1 == 2 || v2 == 2 || v3 == 2 || v4 == 2);
assert(v1 == 3 || v2 == 3 || v3 == 3 || v4 == 3);
printf("%u, %u, %u, %u\n", v1, v2, v3, v4);
}
}

Expand Down

0 comments on commit 29d80af

Please sign in to comment.