859e4f51a492e4aea8b5c8d262b25f40ebdaf835
N-Dimensional Wave Function Collapse (WFC) Library
A templated C++20 Wave Function Collapse engine that can work with 2D grids, 3D grids, Sudoku, Picross, and any other constraint satisfaction problem. The engine is coordinate-system agnostic and can work with any graph-like structure.
Features
- Templated Design: Works with any World type that satisfies the
WorldConcept - Builder Pattern: Easy to use fluent interface for setting up WFC systems
- Constraint System: Flexible constraint definition using lambda functions
- Multiple World Types: Built-in support for 2D/3D arrays, graphs, and Sudoku grids
- Coordinate Agnostic: Works without coordinate systems using only cell IDs
- C++20: Uses modern C++ features like concepts, ranges, and smart pointers
Project Structure
nd-wfc/
├── CMakeLists.txt # Main CMake configuration
├── include/nd-wfc/ # Public headers
│ ├── wfc.h # Main include file
│ ├── wfc.hpp # Core WFC classes and builder pattern
│ └── worlds.hpp # Built-in World type implementations
├── src/ # Implementation files
│ ├── CMakeLists.txt
│ └── main.cpp # Basic test program
├── examples/ # Example programs
│ ├── CMakeLists.txt
│ ├── tilemap_example.cpp # 2D tilemap generation
│ ├── sudoku_example.cpp # Full 9x9 Sudoku solver
│ └── simple_sudoku_example.cpp # Simple 2x2 Sudoku for testing
└── build/ # Build directory (generated)
Building the Project
Prerequisites
- CMake 3.16 or higher
- C++20 compatible compiler (GCC 10+, Clang 10+, MSVC 2019+)
- Git
Build Instructions
# Clone the repository
git clone <repository-url>
cd nd-wfc
# Create build directory
mkdir build
cd build
# Configure with CMake
cmake ..
# Build the project
make
# Run basic test
./src/nd-wfc-main
# Run examples
./examples/tilemap_example
./examples/simple_sudoku_example
Usage
Basic Example (2x2 Grid)
#include <nd-wfc/wfc.h>
#include <iostream>
int main() {
enum SimpleTiles { A = 1, B = 2 };
auto wfc = WFC::Builder<WFC::Array2D<uint8_t, 2, 2>>()
.variable(A, [](WFC::Array2D<uint8_t, 2, 2>& world, int worldID,
WFC::Constrainer& constrainer, uint8_t var) {
auto [x, y] = world.getCoord(worldID);
if (y > 0) constrainer.only((y-1) * 2 + x, B);
if (y < 1) constrainer.only((y+1) * 2 + x, B);
if (x > 0) constrainer.only(y * 2 + (x-1), B);
if (x < 1) constrainer.only(y * 2 + (x+1), B);
})
.variable(B, [](WFC::Array2D<uint8_t, 2, 2>& world, int worldID,
WFC::Constrainer& constrainer, uint8_t var) {
auto [x, y] = world.getCoord(worldID);
if (y > 0) constrainer.only((y-1) * 2 + x, A);
if (y < 1) constrainer.only((y+1) * 2 + x, A);
if (x > 0) constrainer.only(y * 2 + (x-1), A);
if (x < 1) constrainer.only(y * 2 + (x+1), A);
})
.build(WFC::Array2D<uint8_t, 2, 2>());
if (wfc->run()) {
std::cout << "Solution found!" << std::endl;
// Print solution...
}
return 0;
}
2D Tilemap Example
enum TileType { SEA = 1, BEACH = 2, LAND = 3 };
auto wfc = WFC::Builder<WFC::Array2D<uint8_t, 100, 100>>()
.variable(SEA, [](WFC::Array2D<uint8_t, 100, 100>& world, int worldID,
WFC::Constrainer& constrainer, uint8_t var) {
auto [x, y] = world.getCoord(worldID);
// Adjacent cells should be SEA or BEACH (no direct LAND next to SEA)
if (y > 0) constrainer.only((y-1) * 100 + x, SEA, BEACH);
if (y < 99) constrainer.only((y+1) * 100 + x, SEA, BEACH);
if (x > 0) constrainer.only(y * 100 + (x-1), SEA, BEACH);
if (x < 99) constrainer.only(y * 100 + (x+1), SEA, BEACH);
})
.variable(BEACH, [](WFC::Array2D<uint8_t, 100, 100>& world, int worldID,
WFC::Constrainer& constrainer, uint8_t var) {
auto [x, y] = world.getCoord(worldID);
// Adjacent cells should be SEA or LAND (BEACH is transition between them)
if (y > 0) constrainer.only((y-1) * 100 + x, SEA, LAND);
if (y < 99) constrainer.only((y+1) * 100 + x, SEA, LAND);
if (x > 0) constrainer.only(y * 100 + (x-1), SEA, LAND);
if (x < 99) constrainer.only(y * 100 + (x+1), SEA, LAND);
})
.variable(LAND, [](WFC::Array2D<uint8_t, 100, 100>& world, int worldID,
WFC::Constrainer& constrainer, uint8_t var) {
auto [x, y] = world.getCoord(worldID);
// Adjacent cells should be LAND or BEACH (no direct SEA next to LAND)
if (y > 0) constrainer.only((y-1) * 100 + x, LAND, BEACH);
if (y < 99) constrainer.only((y+1) * 100 + x, LAND, BEACH);
if (x > 0) constrainer.only(y * 100 + (x-1), LAND, BEACH);
if (x < 99) constrainer.only(y * 100 + (x+1), LAND, BEACH);
})
.build(WFC::Array2D<uint8_t, 100, 100>());
World Types
The library provides several built-in World types:
Array2D<T, Width, Height>
2D array with compile-time dimensions.
WFC::Array2D<uint8_t, 100, 100> world;
Array3D<T, Width, Height, Depth>
3D array with compile-time dimensions.
WFC::Array3D<uint8_t, 10, 10, 10> world;
GraphWorld
Graph-based world for non-grid structures.
WFC::GraphWorld<uint8_t> world(100); // 100 nodes
world.addEdge(0, 1); // Connect nodes
SudokuWorld
Specialized 9x9 grid with Sudoku helper functions.
WFC::SudokuWorld world;
auto rowCells = world.getRowCells(cellId);
auto colCells = world.getColumnCells(cellId);
auto boxCells = world.getBoxCells(cellId);
Custom World Types
To create a custom World type, implement these requirements:
struct MyWorld {
using ValueType = MyValueType;
using CoordType = MyCoordType;
size_t size() const;
int getId(CoordType coord) const;
CoordType getCoord(int id) const;
};
API Reference
Builder
Builder& variable(VarT value, ConstraintFunc func)- Add a variable with constraint functionstd::unique_ptr<WFC<WorldT>> build(WorldT world)- Build the WFC instance
WFC
bool run()- Run the WFC algorithmstd::optional<VarT> getValue(int cellId)- Get value at cell (if collapsed)const std::unordered_set<int>& getPossibleValues(int cellId)- Get possible values for cell
Constrainer
void constrain(int cellId, const std::unordered_set<int>& allowedValues)- Constrain to specific valuesvoid only(int cellId, int value)- Constrain to single valuevoid only(int cellId, int value1, int value2)- Constrain to two values
Examples
The examples/ directory contains:
tilemap_example.cpp- 2D tilemap generation with Sea/Beach/Landsudoku_example.cpp- Full 9x9 Sudoku solversimple_sudoku_example.cpp- Simple 2x2 Sudoku for testing
Running Examples
cd build/examples
./tilemap_example # Generate a 100x100 tilemap
./simple_sudoku_example # Solve a simple 2x2 Sudoku
Architecture
The WFC engine consists of:
- World Types: Define the problem space and coordinate systems
- Builder Pattern: Fluent interface for setting up variables and constraints
- Constraint System: Lambda-based constraint definitions
- Wave Function Collapse Algorithm: Core WFC implementation with observation and propagation
- Propagation Queue: Efficient constraint propagation system
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
This project is licensed under the MIT License - see the LICENSE file for details.
References
- Original WFC Algorithm: https://github.com/mxgmn/WaveFunctionCollapse
- Constraint satisfaction and procedural generation concepts
Description
Languages
C++
94.4%
CMake
5.2%
C
0.4%