25WfcPreset makeDungeonPreset() {
35 auto allow = [&](
int a,
int b) {
36 p.tiles[size_t(
a)].compat |= (1ull <<
b);
37 p.tiles[size_t(
b)].compat |= (1ull <<
a);
51WfcPreset makeCavePreset() {
58 auto allow = [&](
int a,
int b) {
59 p.tiles[size_t(
a)].compat |= (1ull <<
b);
60 p.tiles[size_t(
b)].compat |= (1ull <<
a);
68WfcPreset makeTerrainPreset() {
77 auto allow = [&](
int a,
int b) {
78 p.tiles[size_t(
a)].compat |= (1ull <<
b);
79 p.tiles[size_t(
b)].compat |= (1ull <<
a);
81 for (
int i = 0; i < 6; ++i) {
83 if (i + 1 < 6) allow(i, i + 1);
88const WfcPreset &presetByName(
const std::string &
name) {
89 static const WfcPreset dungeon = makeDungeonPreset();
90 static const WfcPreset cave = makeCavePreset();
91 static const WfcPreset terrain = makeTerrainPreset();
92 if (
name ==
"cave")
return cave;
93 if (
name ==
"terrain")
return terrain;
97bool genWfcSimple(
const Params ¶ms, Grid2D &out, std::string &
error) {
98 const int w = params.getWidth();
99 const int h = params.getHeight();
100 if (
w < 4 ||
h < 4) {
101 error =
"wfc.simple: size must be at least 4x4";
104 if (
w > 256 ||
h > 256) {
105 error =
"wfc.simple: size capped at 256x256";
109 const std::string presetName = params.getString(
"preset",
"dungeon");
110 if (presetName !=
"dungeon" && presetName !=
"cave" && presetName !=
"terrain") {
111 error =
"wfc.simple: unknown preset '" + presetName +
"' (use dungeon|cave|terrain)";
114 const WfcPreset &preset = presetByName(presetName);
115 const int tileCount = int(preset.tiles.size());
116 if (tileCount <= 0 || tileCount > 63) {
117 error =
"wfc.simple: invalid tile set";
121 const int maxAttempts = std::max(1, params.getInt(
"maxAttempts", 32));
122 const uint64_t fullMask = (tileCount >= 63) ? ~0ull : ((1ull << tileCount) - 1ull);
124 std::mt19937 rng(params.getSeed());
126 auto trySolve = [&](Grid2D &grid) ->
bool {
127 std::vector<uint64_t> wave(
size_t(
w *
h), fullMask);
128 auto idx = [&](
int x,
int y) {
return size_t(
y *
w +
x); };
129 auto countBits = [](uint64_t
m) {
138 auto pickTile = [&](uint64_t mask) ->
int {
141 for (
int t = 0; t < tileCount; ++t) {
142 if (mask & (1ull << t)) options[
n++] = t;
144 if (
n <= 0)
return -1;
145 std::uniform_int_distribution<int> dist(0,
n - 1);
146 return options[dist(rng)];
150 if (presetName !=
"terrain") {
151 for (
int x = 0;
x <
w; ++
x) {
152 wave[
idx(
x, 0)] = 1ull << 0;
153 wave[
idx(
x,
h - 1)] = 1ull << 0;
155 for (
int y = 0;
y <
h; ++
y) {
156 wave[
idx(0,
y)] = 1ull << 0;
157 wave[
idx(
w - 1,
y)] = 1ull << 0;
161 auto propagate = [&](
int sx,
int sy) ->
bool {
162 std::vector<std::pair<int, int>> stack;
163 stack.emplace_back(sx, sy);
164 while (!stack.empty()) {
165 const auto [
cx,
cy] = stack.back();
167 const uint64_t self = wave[
idx(
cx,
cy)];
168 if (self == 0)
return false;
170 uint64_t allowedNeighbor = 0;
171 for (
int t = 0; t < tileCount; ++t) {
172 if (self & (1ull << t)) allowedNeighbor |= preset.tiles[size_t(t)].compat;
175 const int dirs[4][2] = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}};
176 for (
const auto &
d : dirs) {
177 const int nx =
cx +
d[0];
178 const int ny =
cy +
d[1];
179 if (nx < 0 || ny < 0 || nx >=
w || ny >=
h)
continue;
180 uint64_t &nb = wave[
idx(nx, ny)];
181 const uint64_t before = nb;
182 nb &= allowedNeighbor;
183 if (nb == 0)
return false;
184 if (nb != before) stack.emplace_back(nx, ny);
191 if (presetName !=
"terrain") {
192 for (
int x = 0;
x <
w; ++
x) {
193 if (!propagate(
x, 0) || !propagate(
x,
h - 1))
return false;
195 for (
int y = 0;
y <
h; ++
y) {
196 if (!propagate(0,
y) || !propagate(
w - 1,
y))
return false;
201 int bestX = -1, bestY = -1;
202 int bestEntropy = std::numeric_limits<int>::max();
203 bool anyUncollapsed =
false;
204 for (
int y = 0;
y <
h; ++
y) {
205 for (
int x = 0;
x <
w; ++
x) {
206 const int e = countBits(wave[
idx(
x,
y)]);
207 if (e == 0)
return false;
208 if (e == 1)
continue;
209 anyUncollapsed =
true;
211 const int salted = e * 1000 + int(rng() % 1000u);
212 if (salted < bestEntropy) {
213 bestEntropy = salted;
219 if (!anyUncollapsed)
break;
220 if (bestX < 0)
return false;
222 const int chosen = pickTile(wave[
idx(bestX, bestY)]);
223 if (chosen < 0)
return false;
224 wave[
idx(bestX, bestY)] = 1ull << chosen;
225 if (!propagate(bestX, bestY))
return false;
229 for (
int y = 0;
y <
h; ++
y) {
230 for (
int x = 0;
x <
w; ++
x) {
231 const uint64_t
m = wave[
idx(
x,
y)];
233 for (
int t = 0; t < tileCount; ++t) {
234 if (
m & (1ull << t)) {
239 grid.setCell(
x,
y,
int(preset.tiles[
size_t(tile)].semantic));
246 for (
int attempt = 0; attempt < maxAttempts; ++attempt) {
248 if (attempt > 0) (void)rng();
250 if (trySolve(candidate)) {
251 out = std::move(candidate);
257 error =
"wfc.simple: failed to collapse (try different seed/preset or raise maxAttempts)";
261 out.setMeta(
"algorithm",
"wfc.simple");
262 out.setMeta(
"preset", presetName);
265 std::vector<std::pair<int, int>> walkable;
266 for (
int y = 0;
y <
h; ++
y) {
267 for (
int x = 0;
x <
w; ++
x) {
268 const uint32_t
c = uint32_t(out.getCell(
x,
y));
271 walkable.emplace_back(
x,
y);
275 if (!walkable.empty()) {
276 std::uniform_int_distribution<size_t> pick(0, walkable.size() - 1);
277 auto a = walkable[pick(rng)];
278 auto b = walkable[pick(rng)];
279 if (
a ==
b && walkable.size() > 1)
b = walkable[(pick(rng) + 1) % walkable.size()];
281 out.addObjectAt(
"spawn",
"spawn",
float(
a.first), float(
a.second));
282 out.addObjectAt(
"stairs",
"stairs",
float(
b.first), float(
b.second));
std::vector< WfcTile > tiles
void registerAlgorithm(const std::string &id, GeneratorFn fn)
constexpr uint32_t Corridor
void registerWfcSimple(GeneratorRegistry ®istry)