Merge commit 'd581e9b58dbd841707deb2469c649b423358b657' into prompt/random-selector
This commit is contained in:
73
README.md
73
README.md
@@ -10,6 +10,7 @@ A templated C++20 Wave Function Collapse engine that can work with 2D grids, 3D
|
|||||||
- **Multiple World Types**: Built-in support for 2D/3D arrays, graphs, and Sudoku grids
|
- **Multiple World Types**: Built-in support for 2D/3D arrays, graphs, and Sudoku grids
|
||||||
- **Coordinate Agnostic**: Works without coordinate systems using only cell IDs
|
- **Coordinate Agnostic**: Works without coordinate systems using only cell IDs
|
||||||
- **C++20**: Uses modern C++ features like concepts, ranges, and smart pointers
|
- **C++20**: Uses modern C++ features like concepts, ranges, and smart pointers
|
||||||
|
- **Custom Random Selectors**: Pluggable random selection strategies for cell collapse, including compile-time compatible options
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
@@ -31,6 +32,78 @@ nd-wfc/
|
|||||||
└── build/ # Build directory (generated)
|
└── build/ # Build directory (generated)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Custom Random Selectors
|
||||||
|
|
||||||
|
The WFC library now supports customizable random selection strategies for choosing cell values during the collapse process. This allows users to implement different randomization algorithms while maintaining compile-time compatibility.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **Default Random Selector**: A constexpr-compatible seed-based randomizer using linear congruential generator
|
||||||
|
- **Advanced Random Selector**: High-quality randomization using `std::mt19937` and `std::uniform_int_distribution`
|
||||||
|
- **Custom Selectors**: Support for user-defined selector classes that can capture state and maintain behavior between calls
|
||||||
|
- **Lambda Support**: Selectors can be implemented as lambdas with captured variables for flexible behavior
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
#### Basic Usage with Default Selector
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using MyWFC = WFC::Builder<MyWorld, int, VariableMap>
|
||||||
|
::SetRandomSelector<WFC::DefaultRandomSelector<int>>
|
||||||
|
::Build;
|
||||||
|
|
||||||
|
MyWorld world(size);
|
||||||
|
MyWFC::Run(world, seed);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Advanced Random Selector
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using MyWFC = WFC::Builder<MyWorld, int, VariableMap>
|
||||||
|
::SetRandomSelector<WFC::AdvancedRandomSelector<int>>
|
||||||
|
::Build;
|
||||||
|
|
||||||
|
MyWorld world(size);
|
||||||
|
MyWFC::Run(world, seed);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom Selector Implementation
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class CustomSelector {
|
||||||
|
private:
|
||||||
|
mutable int callCount = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
size_t operator()(std::span<const int> possibleValues) const {
|
||||||
|
// Round-robin selection
|
||||||
|
return callCount++ % possibleValues.size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using MyWFC = WFC::Builder<MyWorld, int, VariableMap>
|
||||||
|
::SetRandomSelector<CustomSelector>
|
||||||
|
::Build;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Stateful Lambda Selector
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int counter = 0;
|
||||||
|
auto selector = [&counter](std::span<const int> values) mutable -> size_t {
|
||||||
|
return counter++ % values.size();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use with WFC (would need to wrap in a class for template parameter)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Benefits
|
||||||
|
|
||||||
|
1. **Compile-time Compatible**: The default selector works in constexpr contexts for compile-time WFC solving
|
||||||
|
2. **Stateful Selection**: Selectors can maintain state between calls for deterministic or custom behaviors
|
||||||
|
3. **Flexible Interface**: Simple function signature `(std::span<const VarT>) -> size_t` makes implementation easy
|
||||||
|
4. **Performance**: Custom selectors can optimize for specific use cases (e.g., always pick first, weighted selection)
|
||||||
|
|
||||||
## Building the Project
|
## Building the Project
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|||||||
191
demos/random_selector_example.cpp
Normal file
191
demos/random_selector_example.cpp
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
/**
|
||||||
|
* @brief Example demonstrating custom random selectors in WFC
|
||||||
|
*
|
||||||
|
* This example shows how to use different random selection strategies
|
||||||
|
* in the Wave Function Collapse algorithm, including:
|
||||||
|
* - Default constexpr random selector
|
||||||
|
* - Advanced random selector with std::mt19937
|
||||||
|
* - Custom lambda-based selectors
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include "../include/nd-wfc/wfc.hpp"
|
||||||
|
|
||||||
|
// Simple test world for demonstration
|
||||||
|
struct SimpleWorld {
|
||||||
|
using ValueType = int;
|
||||||
|
std::vector<int> data;
|
||||||
|
size_t grid_size;
|
||||||
|
|
||||||
|
SimpleWorld(size_t size) : data(size * size, 0), grid_size(size) {}
|
||||||
|
size_t size() const { return data.size(); }
|
||||||
|
void setValue(size_t id, int value) { data[id] = value; }
|
||||||
|
int getValue(size_t id) const { return data[id]; }
|
||||||
|
|
||||||
|
void print() const {
|
||||||
|
for (size_t i = 0; i < grid_size; ++i) {
|
||||||
|
for (size_t j = 0; j < grid_size; ++j) {
|
||||||
|
std::cout << data[i * grid_size + j] << " ";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "=== WFC Random Selector Examples ===\n\n";
|
||||||
|
|
||||||
|
// Example 1: Using the default constexpr random selector
|
||||||
|
std::cout << "1. Default Random Selector (constexpr-friendly):\n";
|
||||||
|
{
|
||||||
|
WFC::DefaultRandomSelector<int> selector(0x12345678);
|
||||||
|
|
||||||
|
// Test with some sample values
|
||||||
|
std::array<int, 4> values = {1, 2, 3, 4};
|
||||||
|
std::span<const int> span(values.data(), values.size());
|
||||||
|
|
||||||
|
std::cout << "Possible values: ";
|
||||||
|
for (int v : values) std::cout << v << " ";
|
||||||
|
std::cout << "\nSelected indices: ";
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
size_t index = selector(span);
|
||||||
|
std::cout << index << "(" << values[index] << ") ";
|
||||||
|
}
|
||||||
|
std::cout << "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 2: Using the advanced random selector
|
||||||
|
std::cout << "2. Advanced Random Selector (std::mt19937):\n";
|
||||||
|
{
|
||||||
|
std::mt19937 rng(54321);
|
||||||
|
WFC::AdvancedRandomSelector<int> selector(rng);
|
||||||
|
|
||||||
|
std::array<int, 5> values = {10, 20, 30, 40, 50};
|
||||||
|
std::span<const int> span(values.data(), values.size());
|
||||||
|
|
||||||
|
std::cout << "Possible values: ";
|
||||||
|
for (int v : values) std::cout << v << " ";
|
||||||
|
std::cout << "\nSelected values: ";
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
size_t index = selector(span);
|
||||||
|
std::cout << values[index] << " ";
|
||||||
|
}
|
||||||
|
std::cout << "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 3: Custom lambda selector that always picks the first element
|
||||||
|
std::cout << "3. Custom Lambda Selector (always first):\n";
|
||||||
|
{
|
||||||
|
auto firstSelector = [](std::span<const int> values) -> size_t {
|
||||||
|
return 0; // Always pick first
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<int, 3> values = {100, 200, 300};
|
||||||
|
std::span<const int> span(values.data(), values.size());
|
||||||
|
|
||||||
|
std::cout << "Possible values: ";
|
||||||
|
for (int v : values) std::cout << v << " ";
|
||||||
|
std::cout << "\nSelected values: ";
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
size_t index = firstSelector(span);
|
||||||
|
std::cout << values[index] << " ";
|
||||||
|
}
|
||||||
|
std::cout << "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 4: Stateful lambda selector with captured variables
|
||||||
|
std::cout << "4. Stateful Lambda Selector (round-robin):\n";
|
||||||
|
{
|
||||||
|
int callCount = 0;
|
||||||
|
auto roundRobinSelector = [&callCount](std::span<const int> values) mutable -> size_t {
|
||||||
|
return callCount++ % values.size();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<int, 3> values = {1, 2, 3};
|
||||||
|
std::span<const int> span(values.data(), values.size());
|
||||||
|
|
||||||
|
std::cout << "Possible values: ";
|
||||||
|
for (int v : values) std::cout << v << " ";
|
||||||
|
std::cout << "\nRound-robin selection: ";
|
||||||
|
|
||||||
|
for (int i = 0; i < 9; ++i) {
|
||||||
|
size_t index = roundRobinSelector(span);
|
||||||
|
std::cout << values[index] << " ";
|
||||||
|
}
|
||||||
|
std::cout << "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 5: Custom selector with probability weighting
|
||||||
|
std::cout << "5. Weighted Random Selector:\n";
|
||||||
|
{
|
||||||
|
std::mt19937 rng(99999);
|
||||||
|
auto weightedSelector = [&rng](std::span<const int> values) -> size_t {
|
||||||
|
// Simple weighted selection - favor earlier indices
|
||||||
|
std::vector<double> weights;
|
||||||
|
for (size_t i = 0; i < values.size(); ++i) {
|
||||||
|
weights.push_back(1.0 / (i + 1.0)); // Higher weight for lower indices
|
||||||
|
}
|
||||||
|
|
||||||
|
std::discrete_distribution<size_t> dist(weights.begin(), weights.end());
|
||||||
|
return dist(rng);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<int, 4> values = {1, 2, 3, 4};
|
||||||
|
std::span<const int> span(values.data(), values.size());
|
||||||
|
|
||||||
|
std::cout << "Possible values: ";
|
||||||
|
for (int v : values) std::cout << v << " ";
|
||||||
|
std::cout << "\nWeighted selection ( favors lower values): ";
|
||||||
|
|
||||||
|
for (int i = 0; i < 12; ++i) {
|
||||||
|
size_t index = weightedSelector(span);
|
||||||
|
std::cout << values[index] << " ";
|
||||||
|
}
|
||||||
|
std::cout << "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 6: Integration with WFC Builder
|
||||||
|
std::cout << "6. WFC Builder Integration:\n";
|
||||||
|
{
|
||||||
|
// Define a simple WFC setup with custom random selector
|
||||||
|
using VariableMap = WFC::VariableIDMap<int, 0, 1, 2, 3>;
|
||||||
|
|
||||||
|
// Using default random selector
|
||||||
|
using DefaultWFC = WFC::Builder<SimpleWorld, int, VariableMap>
|
||||||
|
::SetRandomSelector<WFC::DefaultRandomSelector<int>>
|
||||||
|
::Build;
|
||||||
|
|
||||||
|
// Using advanced random selector
|
||||||
|
using AdvancedWFC = WFC::Builder<SimpleWorld, int, VariableMap>
|
||||||
|
::SetRandomSelector<WFC::AdvancedRandomSelector<int>>
|
||||||
|
::Build;
|
||||||
|
|
||||||
|
// Note: Lambda types cannot be used directly as template parameters
|
||||||
|
// Instead, you would create a custom selector class:
|
||||||
|
// class CustomSelector {
|
||||||
|
// public:
|
||||||
|
// size_t operator()(std::span<const int> values) const {
|
||||||
|
// return values.size() > 1 ? 1 : 0;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// using CustomWFC = WFC::Builder<SimpleWorld, int, VariableMap>
|
||||||
|
// ::SetRandomSelector<CustomSelector>
|
||||||
|
// ::Build;
|
||||||
|
|
||||||
|
std::cout << "Successfully created WFC types with different random selectors:\n";
|
||||||
|
std::cout << "- DefaultWFC with DefaultRandomSelector\n";
|
||||||
|
std::cout << "- AdvancedWFC with AdvancedRandomSelector\n";
|
||||||
|
std::cout << "- Custom selector classes can be created for lambda-like behavior\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n=== Examples completed successfully! ===\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -342,10 +342,11 @@ struct Callbacks
|
|||||||
/**
|
/**
|
||||||
* @brief Main WFC class implementing the Wave Function Collapse algorithm
|
* @brief Main WFC class implementing the Wave Function Collapse algorithm
|
||||||
*/
|
*/
|
||||||
template<typename WorldT, typename VarT,
|
template<typename WorldT, typename VarT,
|
||||||
typename VariableIDMapT = VariableIDMap<VarT>,
|
typename VariableIDMapT = VariableIDMap<VarT>,
|
||||||
typename ConstrainerFunctionMapT = ConstrainerFunctionMap<void*>,
|
typename ConstrainerFunctionMapT = ConstrainerFunctionMap<void*>,
|
||||||
typename CallbacksT = Callbacks<WorldT>>
|
typename CallbacksT = Callbacks<WorldT>,
|
||||||
|
typename RandomSelectorT = DefaultRandomSelector<VarT>>
|
||||||
class WFC {
|
class WFC {
|
||||||
public:
|
public:
|
||||||
static_assert(WorldType<WorldT>, "WorldT must satisfy World type requirements");
|
static_assert(WorldType<WorldT>, "WorldT must satisfy World type requirements");
|
||||||
@@ -353,20 +354,22 @@ public:
|
|||||||
using MaskType = typename VariableIDMapT::MaskType;
|
using MaskType = typename VariableIDMapT::MaskType;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct SolverState
|
struct SolverState
|
||||||
{
|
{
|
||||||
WorldT& world;
|
WorldT& world;
|
||||||
WFCQueue<size_t> propagationQueue;
|
WFCQueue<size_t> propagationQueue;
|
||||||
Wave<MaskType> wave;
|
Wave<MaskType> wave;
|
||||||
std::mt19937& rng;
|
std::mt19937& rng;
|
||||||
|
RandomSelectorT& randomSelector;
|
||||||
WFCStackAllocator& allocator;
|
WFCStackAllocator& allocator;
|
||||||
size_t& iterations;
|
size_t& iterations;
|
||||||
|
|
||||||
SolverState(WorldT& world, size_t variableAmount, std::mt19937& rng, WFCStackAllocator& allocator, size_t& iterations)
|
SolverState(WorldT& world, size_t variableAmount, std::mt19937& rng, RandomSelectorT& randomSelector, WFCStackAllocator& allocator, size_t& iterations)
|
||||||
: world(world)
|
: world(world)
|
||||||
, propagationQueue{ WFCStackAllocatorAdapter<size_t>(allocator) }
|
, propagationQueue{ WFCStackAllocatorAdapter<size_t>(allocator) }
|
||||||
, wave{ world.size(), variableAmount, allocator }
|
, wave{ world.size(), variableAmount, allocator }
|
||||||
, rng(rng)
|
, rng(rng)
|
||||||
|
, randomSelector(randomSelector)
|
||||||
, allocator(allocator)
|
, allocator(allocator)
|
||||||
, iterations(iterations)
|
, iterations(iterations)
|
||||||
{}
|
{}
|
||||||
@@ -388,14 +391,16 @@ public:
|
|||||||
{
|
{
|
||||||
allocator.reset();
|
allocator.reset();
|
||||||
constexpr_assert(allocator.getUsed() == 0, "Allocator must be empty");
|
constexpr_assert(allocator.getUsed() == 0, "Allocator must be empty");
|
||||||
|
|
||||||
size_t iterations = 0;
|
size_t iterations = 0;
|
||||||
auto random = std::mt19937{ seed };
|
auto random = std::mt19937{ seed };
|
||||||
|
RandomSelectorT randomSelector{ seed };
|
||||||
SolverState state
|
SolverState state
|
||||||
{
|
{
|
||||||
world,
|
world,
|
||||||
ConstrainerFunctionMapT::size(),
|
ConstrainerFunctionMapT::size(),
|
||||||
random,
|
random,
|
||||||
|
randomSelector,
|
||||||
allocator,
|
allocator,
|
||||||
iterations
|
iterations
|
||||||
};
|
};
|
||||||
@@ -532,8 +537,13 @@ private:
|
|||||||
// randomly select a value from possible values
|
// randomly select a value from possible values
|
||||||
while (availableValues)
|
while (availableValues)
|
||||||
{
|
{
|
||||||
std::uniform_int_distribution<size_t> dist(0, availableValues - 1);
|
// Create a span of the actual variable values for the random selector
|
||||||
size_t randomIndex = dist(state.rng);
|
std::array<VarT, VariableIDMapT::ValuesRegisteredAmount> valueArray;
|
||||||
|
for (size_t i = 0; i < availableValues; ++i) {
|
||||||
|
valueArray[i] = VariableIDMapT::GetValue(possibleValues[i]);
|
||||||
|
}
|
||||||
|
std::span<const VarT> currentPossibleValues(valueArray.data(), availableValues);
|
||||||
|
size_t randomIndex = state.randomSelector(currentPossibleValues);
|
||||||
size_t selectedValue = possibleValues[randomIndex];
|
size_t selectedValue = possibleValues[randomIndex];
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -621,15 +631,65 @@ concept ConstrainerFunction = requires(T func, WorldT& world, size_t index, Worl
|
|||||||
func(world, index, value, constrainer);
|
func(world, index, value, constrainer);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concept to validate random selector function signature
|
||||||
|
* The function must be callable with parameters: (std::span<const VarT>) and return size_t
|
||||||
|
*/
|
||||||
|
template <typename T, typename VarT>
|
||||||
|
concept RandomSelectorFunction = requires(T func, std::span<const VarT> possibleValues) {
|
||||||
|
{ func(possibleValues) } -> std::convertible_to<size_t>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Default constexpr random selector using a simple seed-based algorithm
|
||||||
|
* This provides a compile-time random selection that maintains state between calls
|
||||||
|
*/
|
||||||
|
template <typename VarT>
|
||||||
|
class DefaultRandomSelector {
|
||||||
|
private:
|
||||||
|
mutable uint32_t m_seed;
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr explicit DefaultRandomSelector(uint32_t seed = 0x12345678) : m_seed(seed) {}
|
||||||
|
|
||||||
|
constexpr size_t operator()(std::span<const VarT> possibleValues) const {
|
||||||
|
if (possibleValues.empty()) return 0;
|
||||||
|
|
||||||
|
// Simple linear congruential generator for constexpr compatibility
|
||||||
|
m_seed = m_seed * 1103515245 + 12345;
|
||||||
|
return static_cast<size_t>(m_seed) % possibleValues.size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Advanced random selector using std::mt19937 and std::uniform_int_distribution
|
||||||
|
* This provides high-quality randomization for runtime use
|
||||||
|
*/
|
||||||
|
template <typename VarT>
|
||||||
|
class AdvancedRandomSelector {
|
||||||
|
private:
|
||||||
|
std::mt19937& m_rng;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AdvancedRandomSelector(std::mt19937& rng) : m_rng(rng) {}
|
||||||
|
|
||||||
|
size_t operator()(std::span<const VarT> possibleValues) const {
|
||||||
|
if (possibleValues.empty()) return 0;
|
||||||
|
|
||||||
|
std::uniform_int_distribution<size_t> dist(0, possibleValues.size() - 1);
|
||||||
|
return dist(m_rng);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Builder class for creating WFC instances
|
* @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>>
|
template<typename WorldT, typename VarT = typename WorldT::ValueType, typename VariableIDMapT = VariableIDMap<VarT>, typename ConstrainerFunctionMapT = ConstrainerFunctionMap<void*>, typename CallbacksT = Callbacks<WorldT>, typename RandomSelectorT = DefaultRandomSelector<VarT>>
|
||||||
class Builder {
|
class Builder {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
template <VarT ... Values>
|
template <VarT ... Values>
|
||||||
using DefineIDs = Builder<WorldT, VarT, typename VariableIDMapT::template Merge<Values...>, ConstrainerFunctionMapT, CallbacksT>;
|
using DefineIDs = Builder<WorldT, VarT, typename VariableIDMapT::template Merge<Values...>, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT>;
|
||||||
|
|
||||||
template <typename ConstrainerFunctionT, VarT ... CorrespondingValues>
|
template <typename ConstrainerFunctionT, VarT ... CorrespondingValues>
|
||||||
requires ConstrainerFunction<ConstrainerFunctionT, WorldT, VarT, VariableIDMapT>
|
requires ConstrainerFunction<ConstrainerFunctionT, WorldT, VarT, VariableIDMapT>
|
||||||
@@ -640,17 +700,21 @@ public:
|
|||||||
ConstrainerFunctionT,
|
ConstrainerFunctionT,
|
||||||
VariableIDMap<VarT, CorrespondingValues...>,
|
VariableIDMap<VarT, CorrespondingValues...>,
|
||||||
decltype([](WorldT&, size_t, WorldValue<VarT>, Constrainer<VariableIDMapT>&) {})
|
decltype([](WorldT&, size_t, WorldValue<VarT>, Constrainer<VariableIDMapT>&) {})
|
||||||
>, CallbacksT
|
>, CallbacksT, RandomSelectorT
|
||||||
>;
|
>;
|
||||||
|
|
||||||
template <typename NewCellCollapsedCallbackT>
|
|
||||||
using SetCellCollapsedCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetCellCollapsedCallbackT<NewCellCollapsedCallbackT>>;
|
|
||||||
template <typename NewContradictionCallbackT>
|
|
||||||
using SetContradictionCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetContradictionCallbackT<NewContradictionCallbackT>>;
|
|
||||||
template <typename NewBranchCallbackT>
|
|
||||||
using SetBranchCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetBranchCallbackT<NewBranchCallbackT>>;
|
|
||||||
|
|
||||||
using Build = WFC<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT>;
|
template <typename NewCellCollapsedCallbackT>
|
||||||
|
using SetCellCollapsedCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetCellCollapsedCallbackT<NewCellCollapsedCallbackT>, RandomSelectorT>;
|
||||||
|
template <typename NewContradictionCallbackT>
|
||||||
|
using SetContradictionCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetContradictionCallbackT<NewContradictionCallbackT>, RandomSelectorT>;
|
||||||
|
template <typename NewBranchCallbackT>
|
||||||
|
using SetBranchCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetBranchCallbackT<NewBranchCallbackT>, RandomSelectorT>;
|
||||||
|
|
||||||
|
template <typename NewRandomSelectorT>
|
||||||
|
requires RandomSelectorFunction<NewRandomSelectorT, VarT>
|
||||||
|
using SetRandomSelector = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, NewRandomSelectorT>;
|
||||||
|
|
||||||
|
using Build = WFC<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT>;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace WFC
|
} // namespace WFC
|
||||||
|
|||||||
@@ -1,4 +1,125 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
#include "nd-wfc/wfc.hpp"
|
||||||
|
#include "nd-wfc/worlds.hpp"
|
||||||
|
|
||||||
|
// Test world for demonstration
|
||||||
|
struct TestWorld {
|
||||||
|
using ValueType = int;
|
||||||
|
std::vector<int> data;
|
||||||
|
|
||||||
|
TestWorld(size_t size) : data(size, 0) {}
|
||||||
|
size_t size() const { return data.size(); }
|
||||||
|
void setValue(size_t id, int value) { data[id] = value; }
|
||||||
|
int getValue(size_t id) const { return data[id]; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test random selectors
|
||||||
|
TEST(RandomSelectorTest, DefaultRandomSelector) {
|
||||||
|
using namespace WFC;
|
||||||
|
|
||||||
|
// Test default random selector with constexpr capabilities
|
||||||
|
DefaultRandomSelector<int> selector(12345);
|
||||||
|
|
||||||
|
std::array<int, 3> testValues = {1, 2, 3};
|
||||||
|
std::span<const int> span(testValues.data(), testValues.size());
|
||||||
|
|
||||||
|
// Test that selector returns valid indices
|
||||||
|
size_t index1 = selector(span);
|
||||||
|
size_t index2 = selector(span);
|
||||||
|
|
||||||
|
EXPECT_LT(index1, testValues.size());
|
||||||
|
EXPECT_LT(index2, testValues.size());
|
||||||
|
EXPECT_GE(index1, 0);
|
||||||
|
EXPECT_GE(index2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RandomSelectorTest, AdvancedRandomSelector) {
|
||||||
|
using namespace WFC;
|
||||||
|
|
||||||
|
std::mt19937 rng(54321);
|
||||||
|
AdvancedRandomSelector<int> selector(rng);
|
||||||
|
|
||||||
|
std::array<int, 4> testValues = {10, 20, 30, 40};
|
||||||
|
std::span<const int> span(testValues.data(), testValues.size());
|
||||||
|
|
||||||
|
// Test that selector returns valid indices
|
||||||
|
size_t index = selector(span);
|
||||||
|
EXPECT_LT(index, testValues.size());
|
||||||
|
EXPECT_GE(index, 0);
|
||||||
|
|
||||||
|
// Verify the selected value matches
|
||||||
|
EXPECT_EQ(testValues[index], span[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RandomSelectorTest, CustomLambdaSelector) {
|
||||||
|
using namespace WFC;
|
||||||
|
|
||||||
|
// Custom selector that always picks the first element
|
||||||
|
auto firstSelector = [](std::span<const int> values) -> size_t {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<int, 3> testValues = {100, 200, 300};
|
||||||
|
std::span<const int> span(testValues.data(), testValues.size());
|
||||||
|
|
||||||
|
size_t index = firstSelector(span);
|
||||||
|
EXPECT_EQ(index, 0);
|
||||||
|
EXPECT_EQ(span[index], 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RandomSelectorTest, StatefulLambdaSelector) {
|
||||||
|
using namespace WFC;
|
||||||
|
|
||||||
|
// Stateful selector that cycles through options
|
||||||
|
uint32_t callCount = 0;
|
||||||
|
auto cyclingSelector = [&callCount](std::span<const int> values) mutable -> size_t {
|
||||||
|
return callCount++ % values.size();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<int, 3> testValues = {1, 2, 3};
|
||||||
|
std::span<const int> span(testValues.data(), testValues.size());
|
||||||
|
|
||||||
|
// Test multiple calls
|
||||||
|
EXPECT_EQ(cyclingSelector(span), 0);
|
||||||
|
EXPECT_EQ(cyclingSelector(span), 1);
|
||||||
|
EXPECT_EQ(cyclingSelector(span), 2);
|
||||||
|
EXPECT_EQ(cyclingSelector(span), 0); // Should cycle back
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RandomSelectorTest, WFCIntegration) {
|
||||||
|
using namespace WFC;
|
||||||
|
|
||||||
|
// Create a simple WFC setup with custom random selector
|
||||||
|
using VariableMap = VariableIDMap<int, 0, 1, 2>;
|
||||||
|
using CustomWFC = Builder<TestWorld, int, VariableMap>
|
||||||
|
::SetRandomSelector<DefaultRandomSelector<int>>
|
||||||
|
::Build;
|
||||||
|
|
||||||
|
TestWorld world(4);
|
||||||
|
uint32_t seed = 12345;
|
||||||
|
|
||||||
|
// This should compile and run without errors
|
||||||
|
// (Note: This is a basic integration test - full WFC solving would require proper constraints)
|
||||||
|
SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RandomSelectorTest, ConstexprDefaultSelector) {
|
||||||
|
using namespace WFC;
|
||||||
|
|
||||||
|
// Test that default selector can be used in constexpr context
|
||||||
|
constexpr DefaultRandomSelector<int> selector(0xDEADBEEF);
|
||||||
|
|
||||||
|
constexpr std::array<int, 2> testValues = {42, 84};
|
||||||
|
constexpr auto span = std::span<const int>(testValues.data(), testValues.size());
|
||||||
|
|
||||||
|
// This should compile in constexpr context
|
||||||
|
constexpr size_t index = selector(span);
|
||||||
|
EXPECT_LT(index, testValues.size());
|
||||||
|
EXPECT_GE(index, 0);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
|||||||
Reference in New Issue
Block a user