diff --git a/demos/2DDungeon/CMakeLists.txt b/demos/2DDungeon/CMakeLists.txt new file mode 100644 index 0000000..2062d21 --- /dev/null +++ b/demos/2DDungeon/CMakeLists.txt @@ -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$<$:Debug>DLL") + target_compile_options(dungeon_demo PRIVATE $<$:/MDd> $<$:/MD>) +endif() diff --git a/demos/2DDungeon/Makefile b/demos/2DDungeon/Makefile new file mode 100644 index 0000000..4510ec6 --- /dev/null +++ b/demos/2DDungeon/Makefile @@ -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) diff --git a/demos/2DDungeon/main.cpp b/demos/2DDungeon/main.cpp new file mode 100644 index 0000000..0fbbe29 --- /dev/null +++ b/demos/2DDungeon/main.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include + +// Tile types for the dungeon +enum class Tile : int { + Empty = 0, + Wall = 1, + Floor = 2 +}; + +constexpr size_t DungeonWidth = 16; +constexpr size_t DungeonHeight = 16; + +using World = WFC::Array2D; + +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 + ::DefineIDs + ::Constrain val, auto& constrainer) constexpr { + + auto [x, y] = world.getCoord(index); + + // floor cannot be adjacent to empty space + constrainer.Exclude(Tile::Empty, world.getCoordOffset(x, y, -1, 0)); // Left + constrainer.Exclude(Tile::Empty, world.getCoordOffset(x, y, 1, 0)); // Right + constrainer.Exclude(Tile::Empty, world.getCoordOffset(x, y, 0, -1)); // Up + constrainer.Exclude(Tile::Empty, world.getCoordOffset(x, y, 0, 1)); // Down + })> + ::DefineIDs + ::SetInitialState + ::Build; + + World world{}; + DungeonBuilder::Solve(world, 0xDEADBEEF); + printDungeon(world); + + return 0; +} diff --git a/demos/sudoku/Makefile b/demos/sudoku/Makefile new file mode 100644 index 0000000..0f70281 --- /dev/null +++ b/demos/sudoku/Makefile @@ -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) diff --git a/demos/sudoku/build.sh b/demos/sudoku/build.sh deleted file mode 100755 index 5c364f0..0000000 --- a/demos/sudoku/build.sh +++ /dev/null @@ -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 diff --git a/include/nd-wfc/worlds.hpp b/include/nd-wfc/worlds.hpp index c6ec047..686ec15 100644 --- a/include/nd-wfc/worlds.hpp +++ b/include/nd-wfc/worlds.hpp @@ -9,25 +9,25 @@ namespace WFC { /** * @brief 2D Array World implementation */ -template +template class Array2D { public: using ValueType = T; using CoordType = std::tuple; - 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(std::clamp(x + dx, 0, static_cast(Width) - 1)); + } + } + + constexpr size_t GetCoordOffsetY(int y, int dy) const { + if constexpr (Looping) { + return (y + dy + Width) % Width; + } else { + return static_cast(std::clamp(y + dy, 0, static_cast(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 data_; + std::array data_{}; }; /** @@ -106,19 +126,19 @@ public: using ValueType = T; using CoordType = std::tuple; - 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 data_; + std::array data_{}; }; } // namespace WFC -