Compare commits
5 Commits
0816546550
...
f88f4c16a0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f88f4c16a0 | ||
|
|
8567d86cee | ||
|
|
480e1d5364 | ||
|
|
b84970eddd | ||
|
|
414ded7e09 |
46
demos/2DDungeon/CMakeLists.txt
Normal file
46
demos/2DDungeon/CMakeLists.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(2d_dungeon_demo CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Add the main project as a subdirectory to get the nd-wfc library
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../src ${CMAKE_CURRENT_BINARY_DIR}/nd-wfc)
|
||||
|
||||
# Add compiler warnings
|
||||
if(MSVC)
|
||||
add_compile_options(/W4)
|
||||
else()
|
||||
add_compile_options(-Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
|
||||
# Create demo executable
|
||||
add_executable(dungeon_demo main.cpp)
|
||||
|
||||
# Link to nd-wfc library
|
||||
target_link_libraries(dungeon_demo PRIVATE nd-wfc)
|
||||
|
||||
# Include directories
|
||||
target_include_directories(dungeon_demo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Set output directory and properties
|
||||
set_target_properties(dungeon_demo PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
)
|
||||
|
||||
# Optional: Enable optimizations for release builds
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
if(MSVC)
|
||||
target_compile_options(dungeon_demo PRIVATE /O2)
|
||||
else()
|
||||
target_compile_options(dungeon_demo PRIVATE -O3 -march=native)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Ensure consistent runtime library settings for MSVC
|
||||
if(MSVC)
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
target_compile_options(dungeon_demo PRIVATE $<$<CONFIG:Debug>:/MDd> $<$<CONFIG:Release>:/MD>)
|
||||
endif()
|
||||
18
demos/2DDungeon/Makefile
Normal file
18
demos/2DDungeon/Makefile
Normal file
@@ -0,0 +1,18 @@
|
||||
BUILD_DIR := build
|
||||
BUILD_TYPE ?= Debug
|
||||
|
||||
.PHONY: all clean run configure build
|
||||
|
||||
all: build
|
||||
|
||||
configure:
|
||||
cmake -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE)
|
||||
|
||||
build: configure
|
||||
cmake --build $(BUILD_DIR)
|
||||
|
||||
run: build
|
||||
./$(BUILD_DIR)/bin/dungeon_demo
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
89
demos/2DDungeon/main.cpp
Normal file
89
demos/2DDungeon/main.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include <nd-wfc/wfc.hpp>
|
||||
#include <nd-wfc/wfc_builder.hpp>
|
||||
#include <nd-wfc/worlds.hpp>
|
||||
#include <iostream>
|
||||
|
||||
// Tile types for the dungeon
|
||||
// Values start at 1 so default-initialized cells (0) don't match any tile
|
||||
enum class Tile : int {
|
||||
Empty = 1,
|
||||
Wall = 2,
|
||||
Floor = 3
|
||||
};
|
||||
|
||||
constexpr size_t DungeonWidth = 16;
|
||||
constexpr size_t DungeonHeight = 16;
|
||||
|
||||
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) {
|
||||
for (size_t y = 0; y < DungeonHeight; ++y) {
|
||||
for (size_t x = 0; x < DungeonWidth; ++x) {
|
||||
switch (world.at(x, y)) {
|
||||
case Tile::Floor: std::cout << '.'; break;
|
||||
case Tile::Wall: std::cout << '#'; break;
|
||||
case Tile::Empty: std::cout << ' '; break;
|
||||
}
|
||||
}
|
||||
std::cout << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "2D Dungeon WFC Demo\n";
|
||||
std::cout << "Dungeon size: " << DungeonWidth << "x" << DungeonHeight << "\n\n";
|
||||
|
||||
using DungeonBuilder = WFC::Builder<World>
|
||||
::DefineIDs<Tile::Floor, Tile::Wall, Tile::Empty>
|
||||
::Variable<Tile::Floor>
|
||||
::Constrain<decltype([](World& world, size_t index, WFC::WorldValue<Tile> val, ConstrainerT& constrainer) {
|
||||
|
||||
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&) 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)}));
|
||||
}
|
||||
// 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, std::random_device{}());
|
||||
if (!success) {
|
||||
std::cout << "WFC solver failed!\n";
|
||||
}
|
||||
printDungeon(world);
|
||||
|
||||
return 0;
|
||||
}
|
||||
32
demos/sudoku/Makefile
Normal file
32
demos/sudoku/Makefile
Normal file
@@ -0,0 +1,32 @@
|
||||
BUILD_DIR := build
|
||||
BUILD_TYPE ?= Release
|
||||
|
||||
.PHONY: all clean run test benchmark configure build
|
||||
|
||||
all: build
|
||||
|
||||
configure:
|
||||
cmake -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE)
|
||||
|
||||
build: configure
|
||||
cmake --build $(BUILD_DIR) -j$(shell nproc)
|
||||
|
||||
run: build
|
||||
./$(BUILD_DIR)/bin/sudoku_wfc_demo
|
||||
|
||||
test: build
|
||||
@if [ -f ./$(BUILD_DIR)/bin/sudoku_tests ]; then \
|
||||
./$(BUILD_DIR)/bin/sudoku_tests; \
|
||||
else \
|
||||
echo "Tests not available (Google Test not found)"; \
|
||||
fi
|
||||
|
||||
benchmark: build
|
||||
@if [ -f ./$(BUILD_DIR)/bin/sudoku_benchmarks ]; then \
|
||||
./$(BUILD_DIR)/bin/sudoku_benchmarks; \
|
||||
else \
|
||||
echo "Benchmarks not available (Google Benchmark not found)"; \
|
||||
fi
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
@@ -1,93 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Simple build script for the Sudoku demo
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
echo "Building Fast Sudoku Loader and Solution Validator..."
|
||||
echo "======================================================"
|
||||
|
||||
# Create build directory if it doesn't exist
|
||||
if [ ! -d "build" ]; then
|
||||
echo "Creating build directory..."
|
||||
mkdir build
|
||||
fi
|
||||
|
||||
cd build
|
||||
|
||||
# Configure with CMake
|
||||
echo "Configuring with CMake..."
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
# Build the project
|
||||
echo "Building project..."
|
||||
make -j$(nproc)
|
||||
|
||||
echo ""
|
||||
echo "Build completed successfully!"
|
||||
echo "Run the demo with: ./bin/sudoku_demo"
|
||||
|
||||
# Check what executables were built
|
||||
available_executables=""
|
||||
if [ -f "./bin/sudoku_tests" ]; then
|
||||
echo "Run tests with: ./bin/sudoku_tests"
|
||||
available_executables="$available_executables test"
|
||||
fi
|
||||
|
||||
if [ -f "./bin/sudoku_benchmarks" ]; then
|
||||
echo "Run benchmarks with: ./bin/sudoku_benchmarks"
|
||||
available_executables="$available_executables benchmark"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Handle different run modes
|
||||
case "$1" in
|
||||
"run")
|
||||
echo "Running demo..."
|
||||
./bin/sudoku_demo
|
||||
;;
|
||||
"test")
|
||||
if [ -f "./bin/sudoku_tests" ]; then
|
||||
echo "Running tests..."
|
||||
./bin/sudoku_tests
|
||||
else
|
||||
echo "Tests not available (Google Test not found)"
|
||||
fi
|
||||
;;
|
||||
"benchmark")
|
||||
if [ -f "./bin/sudoku_benchmarks" ]; then
|
||||
echo "Running benchmarks..."
|
||||
./bin/sudoku_benchmarks
|
||||
else
|
||||
echo "Benchmarks not available (Google Benchmark not found)"
|
||||
fi
|
||||
;;
|
||||
"all")
|
||||
echo "Running demo..."
|
||||
./bin/sudoku_demo
|
||||
echo ""
|
||||
|
||||
if [ -f "./bin/sudoku_tests" ]; then
|
||||
echo "Running tests..."
|
||||
./bin/sudoku_tests
|
||||
echo ""
|
||||
else
|
||||
echo "Tests not available (Google Test not found)"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ -f "./bin/sudoku_benchmarks" ]; then
|
||||
echo "Running benchmarks..."
|
||||
./bin/sudoku_benchmarks
|
||||
else
|
||||
echo "Benchmarks not available (Google Benchmark not found)"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if [ -n "$1" ]; then
|
||||
echo "Unknown option: $1"
|
||||
echo "Usage: $0 [run|test|benchmark|all]"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
@@ -64,7 +64,7 @@ int main()
|
||||
|
||||
Sudoku sudokuWorld = Sudoku{ "6......3.......7....7463....7.8...2.4...9...1.9...7.8....9851....6.......1......9" };
|
||||
|
||||
bool success = SudokuSolverCallback::Run(sudokuWorld, true);
|
||||
bool success = WFC::Run<SudokuSolverCallback>(sudokuWorld, true);
|
||||
|
||||
bool solved = sudokuWorld.isSolved();
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ protected:
|
||||
|
||||
// Helper function to solve a puzzle using WFC
|
||||
void solvePuzzle(Sudoku& sudoku) {
|
||||
SudokuSolver::Run(sudoku, true);
|
||||
WFC::Run<SudokuSolver>(sudoku, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -286,7 +286,7 @@ void testPuzzleSolving(const std::string& difficulty, const std::string& filenam
|
||||
Sudoku& sudoku = puzzles[i];
|
||||
EXPECT_TRUE(sudoku.isValid()) << difficulty << " puzzle " << i << " is not valid";
|
||||
|
||||
SudokuSolver::Run(sudoku);
|
||||
WFC::Run<SudokuSolver>(sudoku);
|
||||
|
||||
EXPECT_TRUE(sudoku.isSolved()) << difficulty << " puzzle " << i << " was not solved. Puzzle string: " << sudoku.toString();
|
||||
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
|
||||
namespace WFC {
|
||||
|
||||
struct EmptyInitialState {};
|
||||
|
||||
|
||||
template<typename T>
|
||||
concept WorldType = requires(T world, typename T::ValueType value) {
|
||||
{ world.size() } -> std::integral;
|
||||
@@ -44,265 +47,297 @@ concept HasConstexprSize = requires {
|
||||
{ []() constexpr -> std::size_t { return WorldT{}.size(); }() };
|
||||
};
|
||||
|
||||
template<typename WorldT, typename VarT,
|
||||
typename VariableIDMapT = VariableIDMap<VarT>,
|
||||
typename ConstrainerFunctionMapT = ConstrainerFunctionMap<void*>,
|
||||
typename CallbacksT = Callbacks<WorldT>,
|
||||
typename RandomSelectorT = DefaultRandomSelector<VarT>
|
||||
>
|
||||
class WFC {
|
||||
public:
|
||||
// Standalone SolverState struct
|
||||
template <typename WorldT, typename RandomSelectorT = DefaultRandomSelector<typename WorldT::ValueType>>
|
||||
struct SolverState {
|
||||
using WorldType = WorldT;
|
||||
using WorldSizeT = decltype(WorldT{}.size());
|
||||
static constexpr WorldSizeT WorldSize = HasConstexprSize<WorldT> ? WorldT{}.size() : 0;
|
||||
using PropagationQueueType = WFCQueue<WorldSize, WorldSizeT>;
|
||||
|
||||
WorldT& m_world;
|
||||
PropagationQueueType m_propagationQueue{};
|
||||
RandomSelectorT m_randomSelector{};
|
||||
WFCStackAllocator m_allocator{};
|
||||
size_t m_iterations{};
|
||||
|
||||
SolverState(WorldT& world, uint32_t seed)
|
||||
: m_world(world)
|
||||
, m_propagationQueue{ WorldSize ? WorldSize : static_cast<WorldSizeT>(world.size()) }
|
||||
, m_randomSelector(seed)
|
||||
{}
|
||||
|
||||
SolverState(const SolverState& other) = default;
|
||||
};
|
||||
|
||||
// Types-only config struct produced by Builder
|
||||
template <typename WorldT, typename VarT, typename VariableIDMapT,
|
||||
typename ConstrainerFunctionMapT, typename CallbacksT, typename RandomSelectorT,
|
||||
typename InitialStateFunctionT = EmptyInitialState>
|
||||
struct WFCConfig {
|
||||
static_assert(WorldType<WorldT>, "WorldT must satisfy World type requirements");
|
||||
|
||||
using WorldSizeT = decltype(WorldT{}.size());
|
||||
|
||||
// Try getting the world size, which is only available if the world type has a constexpr size() method
|
||||
constexpr static WorldSizeT WorldSize = HasConstexprSize<WorldT> ? WorldT{}.size() : 0;
|
||||
|
||||
static constexpr WorldSizeT WorldSize = HasConstexprSize<WorldT> ? WorldT{}.size() : 0;
|
||||
using SolverStateType = SolverState<WorldT, RandomSelectorT>;
|
||||
using WaveType = Wave<VariableIDMapT, WorldSize>;
|
||||
using PropagationQueueType = WFCQueue<WorldSize, WorldSizeT>;
|
||||
using ConstrainerType = Constrainer<WaveType, PropagationQueueType>;
|
||||
using MaskType = typename WaveType::ElementT;
|
||||
using VariableIDT = typename WaveType::VariableIDT;
|
||||
|
||||
public:
|
||||
struct SolverState
|
||||
{
|
||||
WorldT& m_world;
|
||||
PropagationQueueType m_propagationQueue{};
|
||||
RandomSelectorT m_randomSelector{};
|
||||
WFCStackAllocator m_allocator{};
|
||||
size_t m_iterations{};
|
||||
|
||||
SolverState(WorldT& world, uint32_t seed)
|
||||
: m_world(world)
|
||||
, m_propagationQueue{ WorldSize ? WorldSize : static_cast<WorldSizeT>(world.size()) }
|
||||
, m_randomSelector(seed)
|
||||
{}
|
||||
|
||||
SolverState(const SolverState& other) = default;
|
||||
};
|
||||
|
||||
public:
|
||||
WFC() = delete; // dont make an instance of this class, only use the static methods.
|
||||
|
||||
public:
|
||||
|
||||
static bool Run(WorldT& world, uint32_t seed = std::random_device{}())
|
||||
{
|
||||
SolverState state{ world, seed };
|
||||
bool result = Run(state);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run the WFC algorithm to generate a solution
|
||||
* @return true if a solution was found, false if contradiction occurred
|
||||
*/
|
||||
static bool Run(SolverState& state)
|
||||
{
|
||||
WaveType wave{ WorldSize, VariableIDMapT::size(), state.m_allocator };
|
||||
|
||||
PropogateInitialValues(state, wave);
|
||||
|
||||
if (RunLoop(state, wave)) {
|
||||
|
||||
PopulateWorld(state, wave);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool RunLoop(SolverState& state, WaveType& wave)
|
||||
{
|
||||
static constexpr size_t MaxIterations = 1024 * 8;
|
||||
for (; state.m_iterations < MaxIterations; ++state.m_iterations)
|
||||
{
|
||||
if (!Propagate(state, wave))
|
||||
return false;
|
||||
|
||||
if (wave.HasContradiction())
|
||||
{
|
||||
if constexpr (CallbacksT::HasContradictionCallback())
|
||||
{
|
||||
PopulateWorld(state, wave);
|
||||
typename CallbacksT::ContradictionCallback{}(state.m_world);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wave.IsFullyCollapsed())
|
||||
return true;
|
||||
|
||||
if constexpr (CallbacksT::HasBranchCallback())
|
||||
{
|
||||
PopulateWorld(state, wave);
|
||||
typename CallbacksT::BranchCallback{}(state.m_world);
|
||||
}
|
||||
|
||||
if (Branch(state, wave))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the value at a specific cell
|
||||
* @param cellId The cell ID
|
||||
* @return The value if collapsed, std::nullopt otherwise
|
||||
*/
|
||||
static std::optional<VarT> GetValue(WaveType& wave, int cellId) {
|
||||
if (wave.IsCollapsed(cellId)) {
|
||||
auto variableId = wave.GetVariableID(cellId);
|
||||
return VariableIDMapT::GetValue(variableId);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get all possible values for a cell
|
||||
* @param cellId The cell ID
|
||||
* @return Set of possible values
|
||||
*/
|
||||
static const std::vector<VarT> GetPossibleValues(WaveType& wave, int cellId)
|
||||
{
|
||||
std::vector<VarT> possibleValues;
|
||||
MaskType mask = wave.GetMask(cellId);
|
||||
for (size_t i = 0; i < ConstrainerFunctionMapT::size(); ++i) {
|
||||
if (mask & (1 << i)) possibleValues.push_back(VariableIDMapT::GetValue(i));
|
||||
}
|
||||
return possibleValues;
|
||||
}
|
||||
|
||||
private:
|
||||
static void CollapseCell(SolverState& state, WaveType& wave, WorldSizeT cellId, VariableIDT value)
|
||||
{
|
||||
constexpr_assert(!wave.IsCollapsed(cellId) || wave.GetMask(cellId) == (MaskType(1) << value));
|
||||
wave.Collapse(cellId, 1 << value);
|
||||
constexpr_assert(wave.IsCollapsed(cellId));
|
||||
|
||||
if constexpr (CallbacksT::HasCellCollapsedCallback())
|
||||
{
|
||||
PopulateWorld(state, wave);
|
||||
typename CallbacksT::CellCollapsedCallback{}(state.m_world);
|
||||
}
|
||||
}
|
||||
|
||||
static bool Branch(SolverState& state, WaveType& wave)
|
||||
{
|
||||
constexpr_assert(state.m_propagationQueue.empty());
|
||||
|
||||
// Find cell with minimum entropy > 1
|
||||
WorldSizeT minEntropyCell = static_cast<WorldSizeT>(-1);
|
||||
size_t minEntropy = static_cast<size_t>(-1);
|
||||
|
||||
for (WorldSizeT i = 0; i < wave.size(); ++i) {
|
||||
size_t entropy = wave.Entropy(i);
|
||||
if (entropy > 1 && entropy < minEntropy) {
|
||||
minEntropy = entropy;
|
||||
minEntropyCell = i;
|
||||
}
|
||||
}
|
||||
if (minEntropyCell == static_cast<WorldSizeT>(-1)) return false;
|
||||
|
||||
constexpr_assert(!wave.IsCollapsed(minEntropyCell));
|
||||
|
||||
// create a list of possible values
|
||||
VariableIDT availableValues = static_cast<VariableIDT>(wave.Entropy(minEntropyCell));
|
||||
std::array<VariableIDT, VariableIDMapT::size()> possibleValues{};
|
||||
MaskType mask = wave.GetMask(minEntropyCell);
|
||||
for (size_t i = 0; i < availableValues; ++i)
|
||||
{
|
||||
VariableIDT index = static_cast<VariableIDT>(std::countr_zero(mask)); // get the index of the lowest set bit
|
||||
constexpr_assert(index < VariableIDMapT::size(), "Possible value went outside bounds");
|
||||
|
||||
possibleValues[i] = index;
|
||||
constexpr_assert(((mask & (MaskType(1) << index)) != 0), "Possible value was not set");
|
||||
|
||||
mask = mask & (mask - 1); // turn off lowest set bit
|
||||
}
|
||||
|
||||
// randomly select a value from possible values
|
||||
while (availableValues)
|
||||
{
|
||||
size_t randomIndex = state.m_randomSelector.rng(availableValues);
|
||||
VariableIDT selectedValue = possibleValues[randomIndex];
|
||||
|
||||
{
|
||||
// copy the state and branch out
|
||||
auto stackFrame = state.m_allocator.createFrame();
|
||||
auto queueFrame = state.m_propagationQueue.createBranchPoint();
|
||||
|
||||
auto newWave = wave;
|
||||
CollapseCell(state, newWave, minEntropyCell, selectedValue);
|
||||
state.m_propagationQueue.push(minEntropyCell);
|
||||
|
||||
if (RunLoop(state, newWave))
|
||||
{
|
||||
// move the solution to the original state
|
||||
wave = newWave;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// remove the failure state from the wave
|
||||
constexpr_assert((wave.GetMask(minEntropyCell) & (MaskType(1) << selectedValue)) != 0, "Possible value was not set");
|
||||
wave.Collapse(minEntropyCell, ~(1 << selectedValue));
|
||||
constexpr_assert((wave.GetMask(minEntropyCell) & (MaskType(1) << selectedValue)) == 0, "Wave was not collapsed correctly");
|
||||
|
||||
// swap replacement value with the last value
|
||||
std::swap(possibleValues[randomIndex], possibleValues[--availableValues]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool Propagate(SolverState& state, WaveType& wave)
|
||||
{
|
||||
while (!state.m_propagationQueue.empty())
|
||||
{
|
||||
WorldSizeT cellId = state.m_propagationQueue.pop();
|
||||
|
||||
if (wave.IsContradicted(cellId)) return false;
|
||||
|
||||
constexpr_assert(wave.IsCollapsed(cellId), "Cell was not collapsed");
|
||||
|
||||
VariableIDT variableID = wave.GetVariableID(cellId);
|
||||
ConstrainerType constrainer(wave, state.m_propagationQueue);
|
||||
|
||||
using ConstrainerFunctionPtrT = void(*)(WorldT&, size_t, WorldValue<VarT>, ConstrainerType&);
|
||||
|
||||
ConstrainerFunctionMapT::template GetFunction<ConstrainerFunctionPtrT>(variableID)(state.m_world, cellId, WorldValue<VarT>{VariableIDMapT::GetValue(variableID), variableID}, constrainer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void PopulateWorld(SolverState& state, WaveType& wave)
|
||||
{
|
||||
for (size_t i = 0; i < wave.size(); ++i)
|
||||
{
|
||||
if (wave.IsCollapsed(i))
|
||||
state.m_world.setValue(i, VariableIDMapT::GetValue(wave.GetVariableID(i)));
|
||||
}
|
||||
}
|
||||
|
||||
static void PropogateInitialValues(SolverState& state, WaveType& wave)
|
||||
{
|
||||
for (size_t i = 0; i < wave.size(); ++i)
|
||||
{
|
||||
for (size_t j = 0; j < VariableIDMapT::size(); ++j)
|
||||
{
|
||||
if (state.m_world.getValue(i) == VariableIDMapT::GetValue(j))
|
||||
{
|
||||
CollapseCell(state, wave, static_cast<WorldSizeT>(i), static_cast<VariableIDT>(j));
|
||||
state.m_propagationQueue.push(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using CallbacksType = CallbacksT;
|
||||
using ConstrainerFunctionMapType = ConstrainerFunctionMapT;
|
||||
using InitialStateFunctionType = InitialStateFunctionT;
|
||||
static consteval bool HasInitialState() { return !std::is_same_v<InitialStateFunctionT, EmptyInitialState>; }
|
||||
};
|
||||
|
||||
// Forward declarations for mutually recursive functions
|
||||
template <typename CallbacksT, typename ConstrainerFunctionMapT, typename StateT, typename WaveT>
|
||||
bool RunLoop(StateT& state, WaveT& wave);
|
||||
|
||||
template <typename CallbacksT, typename ConstrainerFunctionMapT, typename StateT, typename WaveT>
|
||||
bool Branch(StateT& state, WaveT& wave);
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename StateT, typename WaveT>
|
||||
void PopulateWorld(StateT& state, WaveT& wave)
|
||||
{
|
||||
using VariableIDMapT = typename WaveT::IDMapT;
|
||||
for (size_t i = 0; i < wave.size(); ++i)
|
||||
{
|
||||
if (wave.IsCollapsed(i))
|
||||
state.m_world.setValue(i, VariableIDMapT::GetValue(wave.GetVariableID(i)));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CallbacksT, typename StateT, typename WaveT>
|
||||
void CollapseCell(StateT& state, WaveT& wave, typename StateT::WorldSizeT cellId, typename WaveT::VariableIDT value)
|
||||
{
|
||||
using MaskType = typename WaveT::ElementT;
|
||||
constexpr_assert(!wave.IsCollapsed(cellId) || wave.GetMask(cellId) == (MaskType(1) << value));
|
||||
wave.Collapse(cellId, 1 << value);
|
||||
constexpr_assert(wave.IsCollapsed(cellId));
|
||||
|
||||
if constexpr (CallbacksT::HasCellCollapsedCallback())
|
||||
{
|
||||
PopulateWorld(state, wave);
|
||||
typename CallbacksT::CellCollapsedCallback{}(state.m_world);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CallbacksT, typename StateT, typename WaveT>
|
||||
void PropogateInitialValues(StateT& state, WaveT& wave)
|
||||
{
|
||||
using VariableIDMapT = typename WaveT::IDMapT;
|
||||
using WorldSizeT = typename StateT::WorldSizeT;
|
||||
using VariableIDT = typename WaveT::VariableIDT;
|
||||
for (size_t i = 0; i < wave.size(); ++i)
|
||||
{
|
||||
for (size_t j = 0; j < VariableIDMapT::size(); ++j)
|
||||
{
|
||||
if (state.m_world.getValue(i) == VariableIDMapT::GetValue(j))
|
||||
{
|
||||
CollapseCell<CallbacksT>(state, wave, static_cast<WorldSizeT>(i), static_cast<VariableIDT>(j));
|
||||
state.m_propagationQueue.push(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ConstrainerFunctionMapT, typename StateT, typename WaveT>
|
||||
bool Propagate(StateT& state, WaveT& wave)
|
||||
{
|
||||
using VariableIDMapT = typename WaveT::IDMapT;
|
||||
using VarT = typename VariableIDMapT::Type;
|
||||
using WorldSizeT = typename StateT::WorldSizeT;
|
||||
using VariableIDT = typename WaveT::VariableIDT;
|
||||
using PropagationQueueType = typename StateT::PropagationQueueType;
|
||||
using ConstrainerType = Constrainer<WaveT, PropagationQueueType>;
|
||||
|
||||
while (!state.m_propagationQueue.empty())
|
||||
{
|
||||
WorldSizeT cellId = state.m_propagationQueue.pop();
|
||||
|
||||
if (wave.IsContradicted(cellId)) return false;
|
||||
|
||||
constexpr_assert(wave.IsCollapsed(cellId), "Cell was not collapsed");
|
||||
|
||||
VariableIDT variableID = wave.GetVariableID(cellId);
|
||||
ConstrainerType constrainer(wave, state.m_propagationQueue);
|
||||
|
||||
using WorldT = typename StateT::WorldType;
|
||||
using ConstrainerFunctionPtrT = void(*)(WorldT&, size_t, WorldValue<VarT>, ConstrainerType&);
|
||||
|
||||
ConstrainerFunctionMapT::template GetFunction<ConstrainerFunctionPtrT>(variableID)(state.m_world, cellId, WorldValue<VarT>{VariableIDMapT::GetValue(variableID), variableID}, constrainer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename CallbacksT, typename ConstrainerFunctionMapT, typename StateT, typename WaveT>
|
||||
bool Branch(StateT& state, WaveT& wave)
|
||||
{
|
||||
using VariableIDMapT = typename WaveT::IDMapT;
|
||||
using MaskType = typename WaveT::ElementT;
|
||||
using WorldSizeT = typename StateT::WorldSizeT;
|
||||
using VariableIDT = typename WaveT::VariableIDT;
|
||||
|
||||
constexpr_assert(state.m_propagationQueue.empty());
|
||||
|
||||
// Find cell with minimum entropy > 1
|
||||
WorldSizeT minEntropyCell = static_cast<WorldSizeT>(-1);
|
||||
size_t minEntropy = static_cast<size_t>(-1);
|
||||
|
||||
for (WorldSizeT i = 0; i < wave.size(); ++i) {
|
||||
size_t entropy = wave.Entropy(i);
|
||||
if (entropy > 1 && entropy < minEntropy) {
|
||||
minEntropy = entropy;
|
||||
minEntropyCell = i;
|
||||
}
|
||||
}
|
||||
if (minEntropyCell == static_cast<WorldSizeT>(-1)) return false;
|
||||
|
||||
constexpr_assert(!wave.IsCollapsed(minEntropyCell));
|
||||
|
||||
// create a list of possible values
|
||||
VariableIDT availableValues = static_cast<VariableIDT>(wave.Entropy(minEntropyCell));
|
||||
std::array<VariableIDT, VariableIDMapT::size()> possibleValues{};
|
||||
MaskType mask = wave.GetMask(minEntropyCell);
|
||||
for (size_t i = 0; i < availableValues; ++i)
|
||||
{
|
||||
VariableIDT index = static_cast<VariableIDT>(std::countr_zero(mask)); // get the index of the lowest set bit
|
||||
constexpr_assert(index < VariableIDMapT::size(), "Possible value went outside bounds");
|
||||
|
||||
possibleValues[i] = index;
|
||||
constexpr_assert(((mask & (MaskType(1) << index)) != 0), "Possible value was not set");
|
||||
|
||||
mask = mask & (mask - 1); // turn off lowest set bit
|
||||
}
|
||||
|
||||
// randomly select a value from possible values
|
||||
while (availableValues)
|
||||
{
|
||||
size_t randomIndex = state.m_randomSelector.rng(availableValues);
|
||||
VariableIDT selectedValue = possibleValues[randomIndex];
|
||||
|
||||
{
|
||||
// copy the state and branch out
|
||||
auto stackFrame = state.m_allocator.createFrame();
|
||||
auto queueFrame = state.m_propagationQueue.createBranchPoint();
|
||||
|
||||
auto newWave = wave;
|
||||
detail::CollapseCell<CallbacksT>(state, newWave, minEntropyCell, selectedValue);
|
||||
state.m_propagationQueue.push(minEntropyCell);
|
||||
|
||||
if (RunLoop<CallbacksT, ConstrainerFunctionMapT>(state, newWave))
|
||||
{
|
||||
// move the solution to the original state
|
||||
wave = newWave;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// remove the failure state from the wave
|
||||
constexpr_assert((wave.GetMask(minEntropyCell) & (MaskType(1) << selectedValue)) != 0, "Possible value was not set");
|
||||
wave.Collapse(minEntropyCell, ~(1 << selectedValue));
|
||||
constexpr_assert((wave.GetMask(minEntropyCell) & (MaskType(1) << selectedValue)) == 0, "Wave was not collapsed correctly");
|
||||
|
||||
// swap replacement value with the last value
|
||||
std::swap(possibleValues[randomIndex], possibleValues[--availableValues]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename CallbacksT, typename ConstrainerFunctionMapT, typename StateT, typename WaveT>
|
||||
bool RunLoop(StateT& state, WaveT& wave)
|
||||
{
|
||||
static constexpr size_t MaxIterations = 1024 * 8;
|
||||
for (; state.m_iterations < MaxIterations; ++state.m_iterations)
|
||||
{
|
||||
if (!detail::Propagate<ConstrainerFunctionMapT>(state, wave))
|
||||
return false;
|
||||
|
||||
if (wave.HasContradiction())
|
||||
{
|
||||
if constexpr (CallbacksT::HasContradictionCallback())
|
||||
{
|
||||
detail::PopulateWorld(state, wave);
|
||||
typename CallbacksT::ContradictionCallback{}(state.m_world);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wave.IsFullyCollapsed())
|
||||
return true;
|
||||
|
||||
if constexpr (CallbacksT::HasBranchCallback())
|
||||
{
|
||||
detail::PopulateWorld(state, wave);
|
||||
typename CallbacksT::BranchCallback{}(state.m_world);
|
||||
}
|
||||
|
||||
if (Branch<CallbacksT, ConstrainerFunctionMapT>(state, wave))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename ConfigT>
|
||||
bool Run(typename ConfigT::SolverStateType& state)
|
||||
{
|
||||
using CallbacksT = typename ConfigT::CallbacksType;
|
||||
using ConstrainerFunctionMapT = typename ConfigT::ConstrainerFunctionMapType;
|
||||
using WaveType = typename ConfigT::WaveType;
|
||||
using VariableIDMapT = typename WaveType::IDMapT;
|
||||
|
||||
WaveType wave{ ConfigT::WorldSize, VariableIDMapT::size(), state.m_allocator };
|
||||
|
||||
detail::PropogateInitialValues<CallbacksT>(state, wave);
|
||||
|
||||
if constexpr (ConfigT::HasInitialState())
|
||||
{
|
||||
using ConstrainerType = Constrainer<WaveType, typename ConfigT::SolverStateType::PropagationQueueType>;
|
||||
ConstrainerType constrainer(wave, state.m_propagationQueue);
|
||||
typename ConfigT::InitialStateFunctionType{}(state.m_world, constrainer, state.m_randomSelector);
|
||||
}
|
||||
|
||||
if (RunLoop<CallbacksT, ConstrainerFunctionMapT>(state, wave)) {
|
||||
detail::PopulateWorld(state, wave);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename ConfigT, typename WorldT>
|
||||
bool Run(WorldT& world, uint32_t seed = std::random_device{}())
|
||||
{
|
||||
typename ConfigT::SolverStateType state{ world, seed };
|
||||
return Run<ConfigT>(state);
|
||||
}
|
||||
|
||||
template <typename WaveT>
|
||||
std::optional<typename WaveT::IDMapT::Type> GetValue(WaveT& wave, int cellId) {
|
||||
using VariableIDMapT = typename WaveT::IDMapT;
|
||||
if (wave.IsCollapsed(cellId)) {
|
||||
auto variableId = wave.GetVariableID(cellId);
|
||||
return VariableIDMapT::GetValue(variableId);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <typename ConstrainerFunctionMapT, typename WaveT>
|
||||
const std::vector<typename WaveT::IDMapT::Type> GetPossibleValues(WaveT& wave, int cellId)
|
||||
{
|
||||
using VariableIDMapT = typename WaveT::IDMapT;
|
||||
using VarT = typename VariableIDMapT::Type;
|
||||
using MaskType = typename WaveT::ElementT;
|
||||
std::vector<VarT> possibleValues;
|
||||
MaskType mask = wave.GetMask(cellId);
|
||||
for (size_t i = 0; i < ConstrainerFunctionMapT::size(); ++i) {
|
||||
if (mask & (1 << i)) possibleValues.push_back(VariableIDMapT::GetValue(i));
|
||||
}
|
||||
return possibleValues;
|
||||
}
|
||||
|
||||
} // namespace WFC
|
||||
|
||||
@@ -142,28 +142,31 @@ public:
|
||||
public: // Sub byte
|
||||
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 SetValue(uint8_t val) { Clear(); return Data |= ((val & Mask) << Shift); }
|
||||
constexpr void Clear() { Data &= ~Mask; }
|
||||
constexpr uint8_t SetValue(uint8_t val) { Clear(); Data |= ((val & Mask) << Shift); return GetValue(); }
|
||||
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 SubTypeAccess& operator&=(uint8_t other) { return SetValue(GetValue() & other); }
|
||||
constexpr SubTypeAccess& operator|=(uint8_t other) { return SetValue(GetValue() | other); }
|
||||
constexpr SubTypeAccess& operator^=(uint8_t other) { return SetValue(GetValue() ^ other); }
|
||||
constexpr SubTypeAccess& operator<<=(uint8_t other) { return SetValue(GetValue() << other); }
|
||||
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) { SetValue(GetValue() | other); return *this; }
|
||||
constexpr SubTypeAccess& operator^=(uint8_t other) { SetValue(GetValue() ^ other); return *this; }
|
||||
constexpr SubTypeAccess& operator<<=(uint8_t other) { SetValue(GetValue() << other); return *this; }
|
||||
constexpr SubTypeAccess& operator>>=(uint8_t other) { SetValue(GetValue() >> other); return *this; }
|
||||
|
||||
uint8_t& Data;
|
||||
uint8_t Shift;
|
||||
};
|
||||
|
||||
constexpr const SubTypeAccess operator[](size_t index) const requires(IsSubByte) { return SubTypeAccess{data()[index / ElementsPerByte], index & ElementsPerByte }; }
|
||||
constexpr SubTypeAccess operator[](size_t index) requires(IsSubByte) { return SubTypeAccess{data()[index / ElementsPerByte], index & ElementsPerByte }; }
|
||||
constexpr StorageType operator[](size_t index) const requires(IsSubByte) {
|
||||
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
|
||||
constexpr const StorageType& operator[](size_t index) const requires(!IsSubByte) { return data()[index]; }
|
||||
|
||||
@@ -13,13 +13,14 @@ namespace WFC {
|
||||
* @brief Builder class for creating WFC instances
|
||||
*/
|
||||
template<
|
||||
typename WorldT,
|
||||
typename VarT = typename WorldT::ValueType,
|
||||
typename VariableIDMapT = VariableIDMap<VarT>,
|
||||
typename ConstrainerFunctionMapT = ConstrainerFunctionMap<void*>,
|
||||
typename CallbacksT = Callbacks<WorldT>,
|
||||
typename WorldT,
|
||||
typename VarT = typename WorldT::ValueType,
|
||||
typename VariableIDMapT = VariableIDMap<VarT>,
|
||||
typename ConstrainerFunctionMapT = ConstrainerFunctionMap<void*>,
|
||||
typename CallbacksT = Callbacks<WorldT>,
|
||||
typename RandomSelectorT = DefaultRandomSelector<VarT>,
|
||||
typename SelectedValueT = void>
|
||||
typename SelectedValueT = void,
|
||||
typename InitialStateFunctionT = EmptyInitialState>
|
||||
class Builder {
|
||||
public:
|
||||
using WorldSizeT = decltype(WorldT{}.size());
|
||||
@@ -31,22 +32,22 @@ public:
|
||||
|
||||
|
||||
template <VarT ... Values>
|
||||
using DefineIDs = Builder<WorldT, VarT, VariableIDMap<VarT, Values...>, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, VariableIDMap<VarT, Values...>>;
|
||||
using DefineIDs = Builder<WorldT, VarT, VariableIDMap<VarT, Values...>, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, VariableIDMap<VarT, Values...>, InitialStateFunctionT>;
|
||||
|
||||
template <size_t RangeStart, size_t RangeEnd>
|
||||
using DefineRange = Builder<WorldT, VarT, VariableIDRange<VarT, RangeStart, RangeEnd>, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, VariableIDRange<VarT, RangeStart, RangeEnd>>;
|
||||
using DefineRange = Builder<WorldT, VarT, VariableIDRange<VarT, RangeStart, RangeEnd>, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, VariableIDRange<VarT, RangeStart, RangeEnd>, InitialStateFunctionT>;
|
||||
|
||||
template <size_t RangeEnd>
|
||||
using DefineRange0 = Builder<WorldT, VarT, VariableIDRange<VarT, 0, RangeEnd>, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, VariableIDRange<VarT, 0, RangeEnd>>;
|
||||
using DefineRange0 = Builder<WorldT, VarT, VariableIDRange<VarT, 0, RangeEnd>, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, VariableIDRange<VarT, 0, RangeEnd>, InitialStateFunctionT>;
|
||||
|
||||
|
||||
template <VarT ... Values>
|
||||
using Variable = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, VariableIDMap<VarT, Values...>>;
|
||||
using Variable = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, VariableIDMap<VarT, Values...>, InitialStateFunctionT>;
|
||||
|
||||
template <size_t RangeStart, size_t RangeEnd>
|
||||
using VariableRange = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, VariableIDRange<VarT, RangeStart, RangeEnd>>;
|
||||
using VariableRange = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, VariableIDRange<VarT, RangeStart, RangeEnd>, InitialStateFunctionT>;
|
||||
|
||||
|
||||
|
||||
using EmptyConstrainerFunctionT = EmptyConstrainerFunction<WorldT, WorldSizeT, VarT, ConstrainerType>;
|
||||
|
||||
template <typename ConstrainerFunctionT>
|
||||
@@ -58,7 +59,7 @@ public:
|
||||
ConstrainerFunctionT,
|
||||
SelectedValueT,
|
||||
EmptyConstrainerFunctionT
|
||||
>, CallbacksT, RandomSelectorT, SelectedValueT
|
||||
>, CallbacksT, RandomSelectorT, SelectedValueT, InitialStateFunctionT
|
||||
>;
|
||||
|
||||
template <typename ConstrainerFunctionT>
|
||||
@@ -70,22 +71,25 @@ public:
|
||||
ConstrainerFunctionT,
|
||||
VariableIDMapT,
|
||||
EmptyConstrainerFunctionT
|
||||
>, CallbacksT, RandomSelectorT
|
||||
>, CallbacksT, RandomSelectorT, SelectedValueT, InitialStateFunctionT
|
||||
>;
|
||||
|
||||
|
||||
|
||||
template <typename NewCellCollapsedCallbackT>
|
||||
using SetCellCollapsedCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetCellCollapsedCallbackT<NewCellCollapsedCallbackT>, RandomSelectorT>;
|
||||
using SetCellCollapsedCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetCellCollapsedCallbackT<NewCellCollapsedCallbackT>, RandomSelectorT, SelectedValueT, InitialStateFunctionT>;
|
||||
template <typename NewContradictionCallbackT>
|
||||
using SetContradictionCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetContradictionCallbackT<NewContradictionCallbackT>, RandomSelectorT>;
|
||||
using SetContradictionCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetContradictionCallbackT<NewContradictionCallbackT>, RandomSelectorT, SelectedValueT, InitialStateFunctionT>;
|
||||
template <typename NewBranchCallbackT>
|
||||
using SetBranchCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetBranchCallbackT<NewBranchCallbackT>, RandomSelectorT>;
|
||||
using SetBranchCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetBranchCallbackT<NewBranchCallbackT>, RandomSelectorT, SelectedValueT, InitialStateFunctionT>;
|
||||
|
||||
|
||||
template <typename NewRandomSelectorT>
|
||||
using SetRandomSelector = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, NewRandomSelectorT>;
|
||||
using SetRandomSelector = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, NewRandomSelectorT, SelectedValueT, InitialStateFunctionT>;
|
||||
|
||||
using Build = WFC<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT>;
|
||||
template <typename NewInitialStateFunctionT>
|
||||
using SetInitialState = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, SelectedValueT, NewInitialStateFunctionT>;
|
||||
|
||||
using Build = WFCConfig<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, InitialStateFunctionT>;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,11 @@ namespace WFC {
|
||||
template <typename WorldT, typename WorldSizeT, typename VarT, typename ConstainerType>
|
||||
struct EmptyConstrainerFunction
|
||||
{
|
||||
static void invoke(WorldT&, WorldSizeT, WorldValue<VarT>, ConstainerType&) {}
|
||||
void operator()(WorldT&, WorldSizeT, WorldValue<VarT>, ConstainerType&) const {}
|
||||
|
||||
using FuncPtrType = void(*)(WorldT&, WorldSizeT, WorldValue<VarT>, ConstainerType&);
|
||||
operator FuncPtrType() const { return &invoke; }
|
||||
};
|
||||
|
||||
template <typename ... ConstrainerFunctions>
|
||||
@@ -113,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);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace WFC {
|
||||
|
||||
/**
|
||||
@@ -27,14 +29,14 @@ 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);
|
||||
return dist(m_rng);
|
||||
uint32_t rng(uint32_t max) const {
|
||||
std::uniform_int_distribution<uint32_t> dist(0, max - 1);
|
||||
return dist(m_rng);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ using VariableIDType = std::conditional_t<VariablesAmount <= std::numeric_limits
|
||||
template <typename VarT, VarT ... Values>
|
||||
class VariableIDMap {
|
||||
public:
|
||||
using Type = VarT;
|
||||
|
||||
template <VarT ... AdditionalValues>
|
||||
using Merge = VariableIDMap<VarT, Values..., AdditionalValues...>;
|
||||
@@ -60,7 +61,8 @@ public:
|
||||
|
||||
static constexpr VarT GetValue(size_t index) {
|
||||
constexpr_assert(index < size());
|
||||
return GetAllValues()[index];
|
||||
constexpr VarT arr[] = {Values...};
|
||||
return arr[index];
|
||||
}
|
||||
|
||||
static consteval size_t size() { return sizeof...(Values); }
|
||||
|
||||
@@ -18,20 +18,29 @@ public:
|
||||
|
||||
public:
|
||||
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;
|
||||
|
||||
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; }
|
||||
bool IsFullyCollapsed() const { return std::all_of(m_data.begin(), m_data.end(), [](ElementT value) { return std::popcount(value) == 1; }); }
|
||||
bool HasContradiction() const { return std::any_of(m_data.begin(), m_data.end(), [](ElementT value) { return value == 0; }); }
|
||||
bool IsFullyCollapsed() const {
|
||||
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; }
|
||||
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]; }
|
||||
|
||||
@@ -9,25 +9,25 @@ namespace WFC {
|
||||
/**
|
||||
* @brief 2D Array World implementation
|
||||
*/
|
||||
template<typename T, size_t Width, size_t Height>
|
||||
template<typename T, size_t Width, size_t Height, bool Looping = false>
|
||||
class Array2D {
|
||||
public:
|
||||
using ValueType = T;
|
||||
using CoordType = std::tuple<int, int>;
|
||||
|
||||
Array2D() = default;
|
||||
constexpr Array2D() = default;
|
||||
|
||||
/**
|
||||
* @brief Get the total size of the world
|
||||
*/
|
||||
size_t size() const {
|
||||
constexpr size_t size() const {
|
||||
return Width * Height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert coordinates to cell ID
|
||||
*/
|
||||
int getId(CoordType coord) const {
|
||||
constexpr int getId(CoordType coord) const {
|
||||
auto [x, y] = coord;
|
||||
return y * Width + x;
|
||||
}
|
||||
@@ -35,66 +35,86 @@ public:
|
||||
/**
|
||||
* @brief Convert cell ID to coordinates
|
||||
*/
|
||||
CoordType getCoord(int id) const {
|
||||
constexpr CoordType getCoord(int id) const {
|
||||
int x = id % Width;
|
||||
int y = id / Width;
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
constexpr size_t GetCoordOffsetX(int x, int dx) const {
|
||||
if constexpr (Looping) {
|
||||
return (x + dx + Width) % Width;
|
||||
} else {
|
||||
return static_cast<size_t>(std::clamp(x + dx, 0, static_cast<int>(Width) - 1));
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t GetCoordOffsetY(int y, int dy) const {
|
||||
if constexpr (Looping) {
|
||||
return (y + dy + Width) % Width;
|
||||
} else {
|
||||
return static_cast<size_t>(std::clamp(y + dy, 0, static_cast<int>(Height) - 1));
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t getCoordOffset(int x, int y, int dx, int dy) const {
|
||||
return getId({GetCoordOffsetX(x, dx), GetCoordOffsetY(y, dy)});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get width of the array
|
||||
*/
|
||||
size_t width() const { return Width; }
|
||||
constexpr size_t width() const { return Width; }
|
||||
|
||||
/**
|
||||
* @brief Get height of the array
|
||||
*/
|
||||
size_t height() const { return Height; }
|
||||
constexpr size_t height() const { return Height; }
|
||||
|
||||
/**
|
||||
* @brief Access element at coordinates
|
||||
*/
|
||||
T& at(int x, int y) {
|
||||
constexpr T& at(int x, int y) {
|
||||
return data_[y * Width + x];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access element at coordinates (const)
|
||||
*/
|
||||
const T& at(int x, int y) const {
|
||||
constexpr const T& at(int x, int y) const {
|
||||
return data_[y * Width + x];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access element by ID
|
||||
*/
|
||||
T& operator[](int id) {
|
||||
constexpr T& operator[](int id) {
|
||||
return data_[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access element by ID (const)
|
||||
*/
|
||||
const T& operator[](int id) const {
|
||||
constexpr const T& operator[](int id) const {
|
||||
return data_[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set value at specific index (required by WFC)
|
||||
*/
|
||||
void setValue(size_t index, T value) {
|
||||
constexpr void setValue(size_t index, T value) {
|
||||
data_[index] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get value at specific index
|
||||
*/
|
||||
T getValue(size_t index) const {
|
||||
constexpr T getValue(size_t index) const {
|
||||
return data_[index];
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<T, Width * Height> data_;
|
||||
std::array<T, Width * Height> data_{};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -106,19 +126,19 @@ public:
|
||||
using ValueType = T;
|
||||
using CoordType = std::tuple<int, int, int>;
|
||||
|
||||
Array3D() = default;
|
||||
constexpr Array3D() = default;
|
||||
|
||||
/**
|
||||
* @brief Get the total size of the world
|
||||
*/
|
||||
size_t size() const {
|
||||
constexpr size_t size() const {
|
||||
return Width * Height * Depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert coordinates to cell ID
|
||||
*/
|
||||
int getId(CoordType coord) const {
|
||||
constexpr int getId(CoordType coord) const {
|
||||
auto [x, y, z] = coord;
|
||||
return z * (Width * Height) + y * Width + x;
|
||||
}
|
||||
@@ -126,7 +146,7 @@ public:
|
||||
/**
|
||||
* @brief Convert cell ID to coordinates
|
||||
*/
|
||||
CoordType getCoord(int id) const {
|
||||
constexpr CoordType getCoord(int id) const {
|
||||
int x = id % Width;
|
||||
int y = (id / Width) % Height;
|
||||
int z = id / (Width * Height);
|
||||
@@ -136,48 +156,47 @@ public:
|
||||
/**
|
||||
* @brief Access element at coordinates
|
||||
*/
|
||||
T& at(int x, int y, int z) {
|
||||
constexpr T& at(int x, int y, int z) {
|
||||
return data_[z * (Width * Height) + y * Width + x];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access element at coordinates (const)
|
||||
*/
|
||||
const T& at(int x, int y, int z) const {
|
||||
constexpr const T& at(int x, int y, int z) const {
|
||||
return data_[z * (Width * Height) + y * Width + x];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access element by ID
|
||||
*/
|
||||
T& operator[](int id) {
|
||||
constexpr T& operator[](int id) {
|
||||
return data_[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access element by ID (const)
|
||||
*/
|
||||
const T& operator[](int id) const {
|
||||
constexpr const T& operator[](int id) const {
|
||||
return data_[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set value at specific index (required by WFC)
|
||||
*/
|
||||
void setValue(size_t index, T value) {
|
||||
constexpr void setValue(size_t index, T value) {
|
||||
data_[index] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get value at specific index
|
||||
*/
|
||||
T getValue(size_t index) const {
|
||||
constexpr T getValue(size_t index) const {
|
||||
return data_[index];
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<T, Width * Height * Depth> data_;
|
||||
std::array<T, Width * Height * Depth> data_{};
|
||||
};
|
||||
|
||||
} // namespace WFC
|
||||
|
||||
|
||||
Reference in New Issue
Block a user