dungeon generator v0.1
This commit is contained in:
@@ -4,10 +4,11 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
// Tile types for the dungeon
|
// Tile types for the dungeon
|
||||||
|
// Values start at 1 so default-initialized cells (0) don't match any tile
|
||||||
enum class Tile : int {
|
enum class Tile : int {
|
||||||
Empty = 0,
|
Empty = 1,
|
||||||
Wall = 1,
|
Wall = 2,
|
||||||
Floor = 2
|
Floor = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr size_t DungeonWidth = 16;
|
constexpr size_t DungeonWidth = 16;
|
||||||
@@ -15,6 +16,13 @@ constexpr size_t DungeonHeight = 16;
|
|||||||
|
|
||||||
using World = WFC::Array2D<Tile, DungeonWidth, DungeonHeight>;
|
using World = WFC::Array2D<Tile, DungeonWidth, DungeonHeight>;
|
||||||
|
|
||||||
|
// Concrete types needed for constrainer lambda signatures
|
||||||
|
using IDMap = WFC::VariableIDMap<Tile, Tile::Floor, Tile::Wall, Tile::Empty>;
|
||||||
|
constexpr size_t WorldSize = DungeonWidth * DungeonHeight;
|
||||||
|
using WaveT = WFC::Wave<IDMap, WorldSize>;
|
||||||
|
using QueueT = WFC::WFCQueue<WorldSize, size_t>;
|
||||||
|
using ConstrainerT = WFC::Constrainer<WaveT, QueueT>;
|
||||||
|
|
||||||
void printDungeon(const World& world) {
|
void printDungeon(const World& world) {
|
||||||
for (size_t y = 0; y < DungeonHeight; ++y) {
|
for (size_t y = 0; y < DungeonHeight; ++y) {
|
||||||
for (size_t x = 0; x < DungeonWidth; ++x) {
|
for (size_t x = 0; x < DungeonWidth; ++x) {
|
||||||
@@ -33,33 +41,36 @@ int main() {
|
|||||||
std::cout << "Dungeon size: " << DungeonWidth << "x" << DungeonHeight << "\n\n";
|
std::cout << "Dungeon size: " << DungeonWidth << "x" << DungeonHeight << "\n\n";
|
||||||
|
|
||||||
using DungeonBuilder = WFC::Builder<World>
|
using DungeonBuilder = WFC::Builder<World>
|
||||||
::DefineIDs<Tile::Floor>
|
::DefineIDs<Tile::Floor, Tile::Wall, Tile::Empty>
|
||||||
::Constrain<decltype([](const World& world, size_t index, WFC::WorldValue<Tile> val, auto& constrainer) constexpr {
|
::Variable<Tile::Floor>
|
||||||
|
::Constrain<decltype([](World& world, size_t index, WFC::WorldValue<Tile> val, ConstrainerT& constrainer) {
|
||||||
|
|
||||||
auto [x, y] = world.getCoord(index);
|
auto [x, y] = world.getCoord(index);
|
||||||
|
|
||||||
// floor cannot be adjacent to empty space
|
// floor cannot be adjacent to empty space
|
||||||
constrainer.Exclude(Tile::Empty, world.getCoordOffset(x, y, -1, 0)); // Left
|
constrainer.template Exclude<Tile::Empty>(world.getCoordOffset(x, y, -1, 0)); // Left
|
||||||
constrainer.Exclude(Tile::Empty, world.getCoordOffset(x, y, 1, 0)); // Right
|
constrainer.template Exclude<Tile::Empty>(world.getCoordOffset(x, y, 1, 0)); // Right
|
||||||
constrainer.Exclude(Tile::Empty, world.getCoordOffset(x, y, 0, -1)); // Up
|
constrainer.template Exclude<Tile::Empty>(world.getCoordOffset(x, y, 0, -1)); // Up
|
||||||
constrainer.Exclude(Tile::Empty, world.getCoordOffset(x, y, 0, 1)); // Down
|
constrainer.template Exclude<Tile::Empty>(world.getCoordOffset(x, y, 0, 1)); // Down
|
||||||
})>
|
})>
|
||||||
::DefineIDs<Tile::Wall, Tile::Empty>
|
|
||||||
::SetInitialState<decltype([](World& world, auto& constrainer, auto& rng) constexpr {
|
::SetInitialState<decltype([](World& world, auto& constrainer, auto& rng) constexpr {
|
||||||
// make it impossible for the edge to be floor
|
// make it impossible for the edge to be floor
|
||||||
for (size_t x = 0; x < world.width(); ++x) {
|
for (size_t x = 0; x < world.width(); ++x) {
|
||||||
constrainer.Exclude(world.getId({x, 0}), Tile::Floor);
|
constrainer.template Exclude<Tile::Floor>(world.getId({static_cast<int>(x), 0}));
|
||||||
constrainer.Exclude(world.getId({x, world.height() - 1}), Tile::Floor);
|
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) {
|
for (size_t y = 0; y < world.height(); ++y) {
|
||||||
constrainer.Exclude(world.getId({0, y}), Tile::Wall);
|
constrainer.template Exclude<Tile::Wall>(world.getId({0, static_cast<int>(y)}));
|
||||||
constrainer.Exclude(world.getId({world.width() - 1, y}), Tile::Wall);
|
constrainer.template Exclude<Tile::Wall>(world.getId({static_cast<int>(world.width() - 1), static_cast<int>(y)}));
|
||||||
}
|
}
|
||||||
})>
|
})>
|
||||||
::Build;
|
::Build;
|
||||||
|
|
||||||
World world{};
|
World world{};
|
||||||
DungeonBuilder::Solve(world, 0xDEADBEEF);
|
bool success = WFC::Run<DungeonBuilder>(world, 0xDEADBEEF);
|
||||||
|
if (!success) {
|
||||||
|
std::cout << "WFC solver failed!\n";
|
||||||
|
}
|
||||||
printDungeon(world);
|
printDungeon(world);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -142,28 +142,31 @@ public:
|
|||||||
public: // Sub byte
|
public: // Sub byte
|
||||||
struct SubTypeAccess
|
struct SubTypeAccess
|
||||||
{
|
{
|
||||||
constexpr SubTypeAccess(uint8_t& data, uint8_t subIndex) : Data{ data }, Shift{ StorageBits * subIndex } {};
|
constexpr SubTypeAccess(uint8_t& data, uint8_t subIndex) : Data{ data }, Shift{ static_cast<uint8_t>(StorageBits * subIndex) } {};
|
||||||
|
|
||||||
constexpr uint8_t GetValue() const { return ((Data >> Shift) & Mask); }
|
constexpr uint8_t GetValue() const { return ((Data >> Shift) & Mask); }
|
||||||
constexpr uint8_t SetValue(uint8_t val) { Clear(); return Data |= ((val & Mask) << Shift); }
|
constexpr uint8_t SetValue(uint8_t val) { Clear(); Data |= ((val & Mask) << Shift); return GetValue(); }
|
||||||
constexpr void Clear() { Data &= ~Mask; }
|
constexpr void Clear() { Data &= ~(static_cast<uint8_t>(Mask) << Shift); }
|
||||||
|
|
||||||
|
|
||||||
constexpr SubTypeAccess& operator=(uint8_t other) { return SetValue(other); }
|
constexpr SubTypeAccess& operator=(uint8_t other) { SetValue(other); return *this; }
|
||||||
constexpr operator uint8_t() const { return GetValue(); }
|
constexpr operator uint8_t() const { return GetValue(); }
|
||||||
|
|
||||||
constexpr SubTypeAccess& operator&=(uint8_t other) { return SetValue(GetValue() & other); }
|
constexpr SubTypeAccess& operator&=(uint8_t other) { SetValue(GetValue() & other); return *this; }
|
||||||
constexpr SubTypeAccess& operator|=(uint8_t other) { return SetValue(GetValue() | other); }
|
constexpr SubTypeAccess& operator|=(uint8_t other) { SetValue(GetValue() | other); return *this; }
|
||||||
constexpr SubTypeAccess& operator^=(uint8_t other) { return SetValue(GetValue() ^ other); }
|
constexpr SubTypeAccess& operator^=(uint8_t other) { SetValue(GetValue() ^ other); return *this; }
|
||||||
constexpr SubTypeAccess& operator<<=(uint8_t other) { return SetValue(GetValue() << other); }
|
constexpr SubTypeAccess& operator<<=(uint8_t other) { SetValue(GetValue() << other); return *this; }
|
||||||
constexpr SubTypeAccess& operator>>=(uint8_t other) { return SetValue(GetValue() >> other); }
|
constexpr SubTypeAccess& operator>>=(uint8_t other) { SetValue(GetValue() >> other); return *this; }
|
||||||
|
|
||||||
uint8_t& Data;
|
uint8_t& Data;
|
||||||
uint8_t Shift;
|
uint8_t Shift;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr const SubTypeAccess operator[](size_t index) const requires(IsSubByte) { return SubTypeAccess{data()[index / ElementsPerByte], index & ElementsPerByte }; }
|
constexpr StorageType operator[](size_t index) const requires(IsSubByte) {
|
||||||
constexpr SubTypeAccess operator[](size_t index) requires(IsSubByte) { return SubTypeAccess{data()[index / ElementsPerByte], index & ElementsPerByte }; }
|
uint8_t shift = static_cast<uint8_t>(StorageBits * (index % ElementsPerByte));
|
||||||
|
return (data()[index / ElementsPerByte] >> shift) & static_cast<StorageType>(Mask);
|
||||||
|
}
|
||||||
|
constexpr SubTypeAccess operator[](size_t index) requires(IsSubByte) { return SubTypeAccess{data()[index / ElementsPerByte], static_cast<uint8_t>(index % ElementsPerByte) }; }
|
||||||
|
|
||||||
public: // default
|
public: // default
|
||||||
constexpr const StorageType& operator[](size_t index) const requires(!IsSubByte) { return data()[index]; }
|
constexpr const StorageType& operator[](size_t index) const requires(!IsSubByte) { return data()[index]; }
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ namespace WFC {
|
|||||||
template <typename WorldT, typename WorldSizeT, typename VarT, typename ConstainerType>
|
template <typename WorldT, typename WorldSizeT, typename VarT, typename ConstainerType>
|
||||||
struct EmptyConstrainerFunction
|
struct EmptyConstrainerFunction
|
||||||
{
|
{
|
||||||
|
static void invoke(WorldT&, WorldSizeT, WorldValue<VarT>, ConstainerType&) {}
|
||||||
void operator()(WorldT&, WorldSizeT, WorldValue<VarT>, ConstainerType&) const {}
|
void operator()(WorldT&, WorldSizeT, WorldValue<VarT>, ConstainerType&) const {}
|
||||||
|
|
||||||
|
using FuncPtrType = void(*)(WorldT&, WorldSizeT, WorldValue<VarT>, ConstainerType&);
|
||||||
|
operator FuncPtrType() const { return &invoke; }
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename ... ConstrainerFunctions>
|
template <typename ... ConstrainerFunctions>
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ public:
|
|||||||
|
|
||||||
static constexpr VarT GetValue(size_t index) {
|
static constexpr VarT GetValue(size_t index) {
|
||||||
constexpr_assert(index < size());
|
constexpr_assert(index < size());
|
||||||
return GetAllValues()[index];
|
constexpr VarT arr[] = {Values...};
|
||||||
|
return arr[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
static consteval size_t size() { return sizeof...(Values); }
|
static consteval size_t size() { return sizeof...(Values); }
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ public:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
Wave() = default;
|
Wave() = default;
|
||||||
Wave(size_t size, size_t variableAmount, WFCStackAllocator& allocator) : m_data(size, allocator)
|
Wave(size_t size, size_t variableAmount, WFCStackAllocator& allocator) : m_data(size, allocator)
|
||||||
{
|
{
|
||||||
for (auto& wave : m_data) wave = (1 << variableAmount) - 1;
|
for (size_t i = 0; i < m_data.size(); ++i) m_data[i] = (1 << variableAmount) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Wave(const Wave& other) = default;
|
Wave(const Wave& other) = default;
|
||||||
@@ -30,8 +30,16 @@ public:
|
|||||||
size_t size() const { return m_data.size(); }
|
size_t size() const { return m_data.size(); }
|
||||||
size_t Entropy(size_t index) const { return std::popcount(m_data[index]); }
|
size_t Entropy(size_t index) const { return std::popcount(m_data[index]); }
|
||||||
bool IsCollapsed(size_t index) const { return Entropy(index) == 1; }
|
bool IsCollapsed(size_t index) const { return Entropy(index) == 1; }
|
||||||
bool IsFullyCollapsed() const { return std::all_of(m_data.begin(), m_data.end(), [](ElementT value) { return std::popcount(value) == 1; }); }
|
bool IsFullyCollapsed() const {
|
||||||
bool HasContradiction() const { return std::any_of(m_data.begin(), m_data.end(), [](ElementT value) { return value == 0; }); }
|
for (size_t i = 0; i < m_data.size(); ++i)
|
||||||
|
if (std::popcount(static_cast<ElementT>(m_data[i])) != 1) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool HasContradiction() const {
|
||||||
|
for (size_t i = 0; i < m_data.size(); ++i)
|
||||||
|
if (static_cast<ElementT>(m_data[i]) == 0) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
bool IsContradicted(size_t index) const { return m_data[index] == 0; }
|
bool IsContradicted(size_t index) const { return m_data[index] == 0; }
|
||||||
uint16_t GetVariableID(size_t index) const { return static_cast<uint16_t>(std::countr_zero(m_data[index])); }
|
uint16_t GetVariableID(size_t index) const { return static_cast<uint16_t>(std::countr_zero(m_data[index])); }
|
||||||
ElementT GetMask(size_t index) const { return m_data[index]; }
|
ElementT GetMask(size_t index) const { return m_data[index]; }
|
||||||
|
|||||||
Reference in New Issue
Block a user