random generator bugfix

This commit is contained in:
Connor
2026-02-09 23:54:49 +09:00
parent 8567d86cee
commit f88f4c16a0
4 changed files with 43 additions and 11 deletions

View File

@@ -47,27 +47,39 @@ int main() {
auto [x, y] = world.getCoord(index);
// enable walls in 3x3 area around floor (without center)
// must come before Exclude to avoid collapsing then un-collapsing cells
for (int dy = -1; dy <= 1; ++dy) {
for (int dx = -1; dx <= 1; ++dx) {
if (dx == 0 && dy == 0) continue;
constrainer.template Include<Tile::Wall>(world.getCoordOffset(x, y, dx, dy));
}
}
// floor cannot be adjacent to empty space
constrainer.template Exclude<Tile::Empty>(world.getCoordOffset(x, y, -1, 0)); // Left
constrainer.template Exclude<Tile::Empty>(world.getCoordOffset(x, y, 1, 0)); // Right
constrainer.template Exclude<Tile::Empty>(world.getCoordOffset(x, y, 0, -1)); // Up
constrainer.template Exclude<Tile::Empty>(world.getCoordOffset(x, y, 0, 1)); // Down
})>
::SetInitialState<decltype([](World& world, auto& constrainer, auto& rng) constexpr {
::SetInitialState<decltype([](World& world, auto& constrainer, auto&) constexpr {
// disable walls everywhere by default
for (size_t i = 0; i < world.size(); ++i) {
constrainer.template Exclude<Tile::Wall>(i);
}
// make it impossible for the edge to be floor
for (size_t x = 0; x < world.width(); ++x) {
constrainer.template Exclude<Tile::Floor>(world.getId({static_cast<int>(x), 0}));
constrainer.template Exclude<Tile::Floor>(world.getId({static_cast<int>(x), static_cast<int>(world.height() - 1)}));
}
for (size_t y = 0; y < world.height(); ++y) {
constrainer.template Exclude<Tile::Wall>(world.getId({0, static_cast<int>(y)}));
constrainer.template Exclude<Tile::Wall>(world.getId({static_cast<int>(world.width() - 1), static_cast<int>(y)}));
}
// seed floor tiles to kick-start dungeon generation
constrainer.template Only<Tile::Floor>(world.getId({2, 2}));
})>
::SetRandomSelector<WFC::AdvancedRandomSelector<Tile>>
::Build;
World world{};
bool success = WFC::Run<DungeonBuilder>(world, 0xDEADBEEF);
bool success = WFC::Run<DungeonBuilder>(world, std::random_device{}());
if (!success) {
std::cout << "WFC solver failed!\n";
}

View File

@@ -117,6 +117,23 @@ public:
ApplyMask(cellId, 1 << value.InternalIndex);
}
/**
* @brief Re-enable specific values for a cell (OR bits back in)
* @param cellId The ID of the cell to modify
*/
template <typename IDMapT::Type ... IncludedValues>
void Include(size_t cellId) {
static_assert(sizeof...(IncludedValues) > 0, "At least one included value must be provided");
if (m_wave.IsCollapsed(cellId)) return; // don't un-collapse decided cells
auto indices = IDMapT::template ValuesToIndices<IncludedValues...>();
m_wave.Enable(cellId, BitContainerT::GetMask(indices));
}
void Include(WorldValue<typename IDMapT::Type> value, size_t cellId) {
if (m_wave.IsCollapsed(cellId)) return;
m_wave.Enable(cellId, 1 << value.InternalIndex);
}
private:
void ApplyMask(size_t cellId, MaskType mask) {
bool wasCollapsed = m_wave.IsCollapsed(cellId);

View File

@@ -1,5 +1,7 @@
#pragma once
#include <random>
namespace WFC {
/**
@@ -27,13 +29,13 @@ public:
template <typename VarT>
class AdvancedRandomSelector {
private:
std::mt19937& m_rng;
mutable std::mt19937 m_rng;
public:
explicit AdvancedRandomSelector(std::mt19937& rng) : m_rng(rng) {}
explicit AdvancedRandomSelector(uint32_t seed = 0x12345678) : m_rng(seed) {}
uint32_t rng(uint32_t max) const {
std::uniform_int_distribution<uint32_t> dist(0, max);
std::uniform_int_distribution<uint32_t> dist(0, max - 1);
return dist(m_rng);
}
};

View File

@@ -27,6 +27,7 @@ public:
public:
void Collapse(size_t index, ElementT mask) { m_data[index] &= mask; }
void Enable(size_t index, ElementT mask) { m_data[index] |= mask; }
size_t size() const { return m_data.size(); }
size_t Entropy(size_t index) const { return std::popcount(m_data[index]); }
bool IsCollapsed(size_t index) const { return Entropy(index) == 1; }