35constexpr int kDecorBase = 100;
36constexpr int kFloorMaxVariant = 32;
39constexpr int kE = 1 << 0;
40constexpr int kS = 1 << 1;
41constexpr int kW = 1 << 2;
42constexpr int kN = 1 << 3;
43constexpr int kSE = 1 << 4;
44constexpr int kSW = 1 << 5;
45constexpr int kNW = 1 << 6;
46constexpr int kNE = 1 << 7;
48bool isWalkable(uint32_t
c) {
59int clampInt(
int v,
int lo,
int hi) {
return std::max(lo, std::min(hi,
v)); }
62void autotileWalls(Grid2D &out) {
63 const int w = out.getWidth();
64 const int h = out.getHeight();
65 for (
int y = 0;
y <
h; ++
y) {
66 for (
int x = 0;
x <
w; ++
x) {
69 if (
x + 1 <
w && isWalkable(uint32_t(out.getCell(
x + 1,
y)))) mask |= kE;
70 if (
y + 1 <
h && isWalkable(uint32_t(out.getCell(
x,
y + 1)))) mask |= kS;
71 if (
x - 1 >= 0 && isWalkable(uint32_t(out.getCell(
x - 1,
y)))) mask |= kW;
72 if (
y - 1 >= 0 && isWalkable(uint32_t(out.getCell(
x,
y - 1)))) mask |= kN;
73 if (
x + 1 <
w &&
y + 1 <
h && isWalkable(uint32_t(out.getCell(
x + 1,
y + 1))))
75 if (
x - 1 >= 0 &&
y + 1 <
h && isWalkable(uint32_t(out.getCell(
x - 1,
y + 1))))
77 if (
x - 1 >= 0 &&
y - 1 >= 0 && isWalkable(uint32_t(out.getCell(
x - 1,
y - 1))))
79 if (
x + 1 <
w &&
y - 1 >= 0 && isWalkable(uint32_t(out.getCell(
x + 1,
y - 1))))
81 out.setDetail(
x,
y, mask);
87int floorVariant(
int x,
int y,
const std::string &pattern,
int variants, uint32_t salt) {
88 const int v = std::max(1, variants);
91 return 1 + ((
x +
y) %
v);
95 return 1 + (int((
x * 73856093u +
y * 19349663u + salt) & 0x7FFFFFFFu) %
v);
99void carveRect(Grid2D &out,
const Rect &r, uint32_t
semantic) {
100 for (
int y = r.y;
y < r.y + r.h; ++
y)
101 for (
int x = r.x;
x < r.x + r.w; ++
x) out.setCell(
x,
y,
int(
semantic));
104bool rectsOverlap(
const Rect &
a,
const Rect &
b,
int pad) {
105 return a.x <
b.x +
b.w + pad &&
b.x <
a.x +
a.w + pad &&
a.y <
b.y +
b.h + pad &&
106 b.y <
a.y +
a.h + pad;
109void carveCorridor(Grid2D &out,
int ax,
int ay,
int bx,
int by,
int width,
110 const std::string &style, std::mt19937 &rng) {
111 if (style ==
"diagonal") {
114 while (
x != bx ||
y != by) {
115 for (
int oy = 0; oy <
width; ++oy)
116 for (
int ox = 0; ox <
width; ++ox)
118 if (
x != bx &&
y != by && (rng() & 1u)) {
119 x += (bx >
x) ? 1 : -1;
120 }
else if (
x != bx) {
121 x += (bx >
x) ? 1 : -1;
123 y += (by >
y) ? 1 : -1;
126 for (
int oy = 0; oy <
width; ++oy)
134 if (style ==
"straight") {
136 }
else if (rng() & 1u) {
141 const int x0 = std::min(ax, cornerX);
142 const int x1 = std::max(ax, cornerX);
143 for (
int x = x0;
x <= x1; ++
x)
144 for (
int oy = 0; oy <
width; ++oy)
147 const int y0 = std::min(cornerY, by);
148 const int y1 = std::max(cornerY, by);
149 for (
int y = y0;
y <= y1; ++
y)
150 for (
int ox = 0; ox <
width; ++ox)
154bool genRoguelike(
const Params ¶ms, Grid2D &out, std::string &
error) {
155 const int w = params.getWidth();
156 const int h = params.getHeight();
157 if (
w < 9 ||
h < 9) {
158 error =
"level.roguelike: size must be at least 9x9";
162 const uint32_t
seed = params.getSeed();
163 const int roomCount = clampInt(params.getInt(
"roomCount", 9), 1, 256);
164 const int roomMin = clampInt(params.getInt(
"roomMin", 4), 2,
w);
165 const int roomMax = clampInt(params.getInt(
"roomMax", 8), roomMin,
w);
166 const int padding = clampInt(params.getInt(
"padding", 1), 0, 4);
167 const int spacing = clampInt(params.getInt(
"spacing", 2), 0, 8);
168 const int corridorW = clampInt(params.getInt(
"corridorWidth", 1), 1, 3);
169 const std::string style = params.getString(
"corridorStyle",
"l");
170 const std::string pattern = params.getString(
"floorPattern",
"brick");
171 const int variants = clampInt(params.getInt(
"floorVariants", 4), 1, kFloorMaxVariant);
172 const float decorDensity = std::clamp(params.getFloat(
"decorDensity", 0.05f), 0.f, 1.f);
173 const std::string decorSet = params.getString(
"decorSet",
"mixed");
174 const bool doAutotile = params.getInt(
"autotile", 1) != 0;
179 std::mt19937 rng(
seed);
184 const int cols = std::max(1,
int(std::sqrt(
double(roomCount))));
185 const int rows = std::max(1, (roomCount + cols - 1) / cols);
186 std::vector<Rect> rooms;
187 std::uniform_int_distribution<int> dim(roomMin, roomMax);
188 std::uniform_int_distribution<int> jx(0, 0), jy(0, 0);
190 for (
int r = 0; r < rows; ++r) {
191 for (
int c = 0;
c < cols; ++
c) {
192 if (
int(rooms.size()) >= roomCount)
break;
194 const int sx = padding + int((
long(
c) * (
w - 2 * padding)) / cols);
195 const int ex = padding + int((
long(
c + 1) * (
w - 2 * padding)) / cols);
196 const int sy = padding + int((
long(r) * (
h - 2 * padding)) / rows);
197 const int ey = padding + int((
long(r + 1) * (
h - 2 * padding)) / rows);
198 const int slotW = std::max(roomMin, ex - sx);
199 const int slotH = std::max(roomMin, ey - sy);
200 if (slotW < roomMin || slotH < roomMin)
continue;
203 for (
int attempt = 0; attempt < 24; ++attempt) {
204 const int rw = std::min(dim(rng), slotW);
205 const int rh = std::min(dim(rng), slotH);
206 const int maxX = sx + (slotW - rw);
207 const int maxY = sy + (slotH - rh);
208 std::uniform_int_distribution<int> ox(sx, maxX);
209 std::uniform_int_distribution<int> oy(sy, maxY);
210 const Rect cand{ox(rng), oy(rng), rw, rh};
213 for (
const Rect &other : rooms) {
214 if (rectsOverlap(cand, other,
spacing)) {
ok =
false;
break; }
217 rooms.push_back(cand);
225 const int rw = std::max(roomMin,
w / 3);
226 const int rh = std::max(roomMin,
h / 3);
227 rooms.push_back({(
w - rw) / 2, (
h - rh) / 2, rw, rh});
231 for (
const Rect &r : rooms) carveRect(out, r, Semantic::
Floor);
234 for (
size_t i = 1; i < rooms.size(); ++i) {
235 const int ax = rooms[i - 1].x + rooms[i - 1].w / 2;
236 const int ay = rooms[i - 1].y + rooms[i - 1].h / 2;
237 const int bx = rooms[i].x + rooms[i].w / 2;
238 const int by = rooms[i].y + rooms[i].h / 2;
239 carveCorridor(out, ax, ay, bx, by, corridorW, style, rng);
243 if (doAutotile) autotileWalls(out);
246 const uint32_t salt =
seed * 2654435761u;
247 for (
int y = 0;
y <
h; ++
y) {
248 for (
int x = 0;
x <
w; ++
x) {
249 const uint32_t
c = uint32_t(out.getCell(
x,
y));
251 out.setDetail(
x,
y, floorVariant(
x,
y, pattern, variants, salt));
259 std::vector<std::pair<int, int>> floorCells;
260 floorCells.reserve(
size_t(
w *
h));
261 for (
int y = 1;
y <
h - 1; ++
y)
262 for (
int x = 1;
x <
w - 1; ++
x)
263 if (isWalkable(uint32_t(out.getCell(
x,
y)))) floorCells.emplace_back(
x,
y);
265 std::vector<std::pair<int, int>> decorTiles;
266 if (!floorCells.empty() && decorDensity > 0.f) {
267 const size_t count = size_t(
float(floorCells.size()) * decorDensity);
268 std::uniform_int_distribution<size_t> pick(0, floorCells.size() - 1);
269 std::uniform_int_distribution<int>
kind(0, 2);
270 for (
size_t i = 0; i < count; ++i) {
271 const auto [dx, dy] = floorCells[pick(rng)];
272 if (out.getDetail(dx, dy) >= kDecorBase)
continue;
274 bool nearMarker =
false;
275 for (
int k = 0; k < out.getObjectCount(); ++k) {
276 if (
int(out.getObjectX(k)) == dx &&
int(out.getObjectY(k)) == dy) {
281 if (nearMarker)
continue;
282 out.setDetail(dx, dy, kDecorBase +
kind(rng));
283 decorTiles.emplace_back(dx, dy);
288 auto propCount = [&](
int base) {
289 return decorSet ==
"none" ? 0 : std::max(0,
int(
float(base) * 0.35f));
291 std::uniform_int_distribution<int> yroll(0, 2);
292 if (decorSet ==
"pillars" || decorSet ==
"mixed") {
293 const int n = propCount(roomCount);
294 for (
int i = 0; i <
n; ++i) {
295 if (rooms.empty())
break;
296 const Rect &r = rooms[size_t(i) % rooms.size()];
297 if (r.w < 4 || r.h < 4)
continue;
298 const int px = r.x + r.w / 2 + (yroll(rng) - 1);
299 const int py = r.y + r.h / 2 + (yroll(rng) - 1);
300 out.addObject(
"pillar" + std::to_string(i),
"pillar", float(
px), float(py), 1.f, 1.f,
304 if (decorSet ==
"treasure" || decorSet ==
"mixed") {
305 const int n = propCount(roomCount);
306 for (
int i = 0; i <
n; ++i) {
307 if (rooms.empty())
break;
308 const Rect &r = rooms[size_t(i) % rooms.size()];
309 const int px = r.x + r.w / 2 + (yroll(rng) - 1);
310 const int py = r.y + r.h / 2 + (yroll(rng) - 1);
311 out.addObject(
"chest" + std::to_string(i),
"chest", float(
px), float(py), 1.f, 1.f, 0);
316 std::vector<std::pair<int, int>> walkable;
317 for (
int y = 0;
y <
h; ++
y)
318 for (
int x = 0;
x <
w; ++
x)
319 if (isWalkable(uint32_t(out.getCell(
x,
y)))) walkable.emplace_back(
x,
y);
320 if (!walkable.empty()) {
321 auto pick = [&](uint32_t
s) {
322 return walkable[(
seed * 1664525u +
s * 1013904223u) % walkable.size()];
326 if (
a ==
b && walkable.size() > 1)
b = walkable[(size_t(
seed) + 1) % walkable.size()];
327 out.addObjectAt(
"spawn",
"spawn",
float(
a.first), float(
a.second));
328 out.addObjectAt(
"stairs",
"stairs",
float(
b.first), float(
b.second));
332 out.setMeta(
"algorithm",
"level.roguelike");
333 out.setMeta(
"seed", std::to_string(
seed));
334 out.setMeta(
"rooms", std::to_string(rooms.size()));
335 out.setMeta(
"floorPattern", pattern);
336 out.setMeta(
"decorTiles", std::to_string(decorTiles.size()));
337 out.setMeta(
"corridorStyle", style);
353 std::random_device rd;
354 const uint32_t
v = rd();
355 return v == 0 ? 1u :
v;
void registerAlgorithm(const std::string &id, GeneratorFn fn)
Intermediate 2D generation result. cells store semantic ids (see Semantic.h), not tile GIDs — convert...
constexpr uint32_t Corridor
uint32_t randomSeedValue()
Produce a fresh seed suitable for regenerating a level (never 0).
void registerRoguelikeGenerator(GeneratorRegistry ®istry)
Register the "level.roguelike" algorithm (idempotent).
bool autotileGridInPlace(Grid2D &grid)
Post-process any generated Grid2D: fill each wall cell's detail with an 8-bit neighbour mask describi...