2026-02-06 12:36:10 +09:00
2025-08-24 17:15:09 +09:00
2025-08-21 12:17:21 +09:00
2026-02-06 12:36:10 +09:00
2026-02-06 12:34:16 +09:00
2025-08-24 17:15:09 +09:00
2025-09-12 12:01:39 +09:00
2025-08-31 17:42:06 +09:00
2025-08-25 13:08:32 +09:00
2025-08-24 17:15:09 +09:00
2025-09-04 07:53:38 +09:00
2026-02-06 12:36:10 +09:00

N-Dimensional Wave Function Collapse (WFC) Library

A templated C++20 Wave Function Collapse engine that can work with 2D grids, 3D grids, Sudoku, 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
  • Custom Random Selectors: Pluggable random selection strategies for cell collapse, including compile-time compatible options

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

using MyWFC = WFC::Builder<MyWorld, int, VariableMap>
    ::SetRandomSelector<WFC::DefaultRandomSelector<int>>
    ::Build;

MyWorld world(size);
MyWFC::Run(world, seed);

Advanced Random Selector

using MyWFC = WFC::Builder<MyWorld, int, VariableMap>
    ::SetRandomSelector<WFC::AdvancedRandomSelector<int>>
    ::Build;

MyWorld world(size);
MyWFC::Run(world, seed);

Custom Selector Implementation

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

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

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 function
  • std::unique_ptr<WFC<WorldT>> build(WorldT world) - Build the WFC instance

WFC

  • bool run() - Run the WFC algorithm
  • std::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 values
  • void only(int cellId, int value) - Constrain to single value
  • void 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/Land
  • sudoku_example.cpp - Full 9x9 Sudoku solver
  • simple_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:

  1. World Types: Define the problem space and coordinate systems
  2. Builder Pattern: Fluent interface for setting up variables and constraints
  3. Constraint System: Lambda-based constraint definitions
  4. Wave Function Collapse Algorithm: Core WFC implementation with observation and propagation
  5. Propagation Queue: Efficient constraint propagation system

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Ensure all tests pass
  6. Submit a pull request

License

This project is licensed under the MIT License - see the LICENSE file for details.

References

Description
No description provided
Readme 33 MiB
Languages
C++ 94.4%
CMake 5.2%
C 0.4%