Callback System

This commit is contained in:
2025-08-31 17:42:06 +09:00
parent bd7b27eb18
commit 8dc7bcd618
9 changed files with 276 additions and 84 deletions

View File

@@ -17,14 +17,15 @@ else()
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# Find Google Test (optional)
find_package(GTest)
if(GTest_FOUND)
set(HAS_GTEST TRUE)
else()
set(HAS_GTEST FALSE)
message(WARNING "Google Test not found. Tests will not be built.")
endif()
# Use FetchContent to get Google Test for consistent builds
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)
FetchContent_MakeAvailable(googletest)
set(HAS_GTEST TRUE)
message(STATUS "Using Google Test via FetchContent")
# Find Google Benchmark (optional)
find_package(benchmark)
@@ -39,6 +40,14 @@ endif()
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads)
# Ensure consistent runtime library settings for MSVC
if(MSVC)
# Force all targets to use the same runtime library
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
# Ensure Google Test uses the same runtime library
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
endif()
# Create the main executable
add_executable(sudoku_demo
main.cpp
@@ -69,6 +78,14 @@ target_link_libraries(sudoku_wfc_demo PRIVATE nd-wfc)
target_link_libraries(analyze_failing_puzzles PRIVATE nd-wfc)
target_link_libraries(debug_failing_puzzles PRIVATE nd-wfc)
# Ensure consistent runtime library settings for all executables
if(MSVC)
target_compile_options(sudoku_demo PRIVATE $<$<CONFIG:Debug>:/MDd> $<$<CONFIG:Release>:/MD>)
target_compile_options(sudoku_wfc_demo PRIVATE $<$<CONFIG:Debug>:/MDd> $<$<CONFIG:Release>:/MD>)
target_compile_options(analyze_failing_puzzles PRIVATE $<$<CONFIG:Debug>:/MDd> $<$<CONFIG:Release>:/MD>)
target_compile_options(debug_failing_puzzles PRIVATE $<$<CONFIG:Debug>:/MDd> $<$<CONFIG:Release>:/MD>)
endif()
# Set output directory for sudoku_demo
set_target_properties(sudoku_demo PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
@@ -123,7 +140,16 @@ if(HAS_GTEST)
test_sudoku.cpp
)
target_link_libraries(sudoku_tests GTest::gtest GTest::gtest_main nd-wfc)
target_link_libraries(sudoku_tests gtest gtest_main nd-wfc)
# Ensure consistent runtime library settings for test executable
if(MSVC)
target_compile_options(sudoku_tests PRIVATE $<$<CONFIG:Debug>:/MDd> $<$<CONFIG:Release>:/MD>)
# Ensure Google Test targets use consistent runtime library
set_target_properties(gtest gtest_main gmock gmock_main PROPERTIES
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL"
)
endif()
if(Threads_FOUND)
target_link_libraries(sudoku_tests Threads::Threads)
endif()

View File

@@ -6,7 +6,7 @@ int main()
std::cout << "Sudoku Demo" << std::endl;
// Create a simple sudoku puzzle
Sudoku sudoku("530070000600195000098000060800060003400803001700020006060000280000419005000080079");
Sudoku sudoku("140000050700200000000300204200080400080090020006050001809001000000006007050000069");
if (sudoku.isValid()) {
std::cout << "Loaded valid sudoku puzzle:" << std::endl;

View File

@@ -200,11 +200,11 @@ public: // WFC Support
using ValueType = uint8_t;
ValueType getValue(size_t index) const {
return board_.get(index);
return board_.get(static_cast<int>(index));
}
void setValue(size_t index, ValueType value) {
board_.set(index, value);
board_.set(static_cast<int>(index), value);
}
constexpr size_t size() const {
@@ -235,35 +235,37 @@ private:
static bool parseLine(const std::string& line, std::array<uint8_t, 81>& board);
};
using SudokuSolver = WFC::Builder<Sudoku>
::DefineIDs<1, 2, 3, 4, 5, 6, 7, 8, 9>
::DefineConstrainer<decltype([](Sudoku&, size_t index, WFC::WorldValue<uint8_t> val, auto& constrainer) {
size_t x = index % 9;
size_t y = index / 9;
// Add row constraints (same row, different columns)
for (size_t i = 0; i < 9; ++i) {
if (i != x) constrainer.Exclude(val, i + y * 9);
}
using SudokuSolverBuilder = WFC::Builder<Sudoku>
::DefineIDs<1, 2, 3, 4, 5, 6, 7, 8, 9>
::DefineConstrainer<decltype([](Sudoku&, size_t index, WFC::WorldValue<uint8_t> val, auto& constrainer) {
size_t x = index % 9;
size_t y = index / 9;
// Add column constraints (same column, different rows)
for (size_t i = 0; i < 9; ++i) {
if (i != y) constrainer.Exclude(val,x + i * 9);
}
// Add row constraints (same row, different columns)
for (size_t i = 0; i < 9; ++i) {
if (i != x) constrainer.Exclude(val, i + y * 9);
}
// Add box constraints (3x3 box)
size_t box_x = (x / 3) * 3;
size_t box_y = (y / 3) * 3;
for (size_t j = 0; j < 3; ++j) {
for (size_t k = 0; k < 3; ++k) {
size_t cell_x = box_x + j;
size_t cell_y = box_y + k;
size_t cell_index = cell_x + cell_y * 9;
if (cell_index != index) {
constrainer.Exclude(val, cell_index);
}
// Add column constraints (same column, different rows)
for (size_t i = 0; i < 9; ++i) {
if (i != y) constrainer.Exclude(val,x + i * 9);
}
// Add box constraints (3x3 box)
size_t box_x = (x / 3) * 3;
size_t box_y = (y / 3) * 3;
for (size_t j = 0; j < 3; ++j) {
for (size_t k = 0; k < 3; ++k) {
size_t cell_x = box_x + j;
size_t cell_y = box_y + k;
size_t cell_index = cell_x + cell_y * 9;
if (cell_index != index) {
constrainer.Exclude(val, cell_index);
}
}
}), 1, 2, 3, 4, 5, 6, 7, 8, 9>
::Build;
}
}), 1, 2, 3, 4, 5, 6, 7, 8, 9>;
using SudokuSolver = SudokuSolverBuilder::Build;

View File

@@ -1,28 +1,88 @@
#include <nd-wfc/wfc.hpp>
#include "sudoku.h"
#include <iostream>
#include <fstream>
int main()
{
std::cout << "Running Sudoku WFC" << std::endl;
// Helper function to load multiple puzzles from a file (one puzzle per line)
std::vector<Sudoku> loadPuzzlesFromFile(const std::string& filename) {
std::vector<Sudoku> puzzles;
std::ifstream file(filename);
Sudoku sudokuWorld;
sudokuWorld.setValue(0, 5);
sudokuWorld.setValue(80, 1);
bool success = SudokuSolver::Run(sudokuWorld, true);
if (!file.is_open()) {
return puzzles;
}
if (success) {
std::cout << "Sudoku solved successfully!" << std::endl;
// Print the solved sudoku
std::string line;
while (std::getline(file, line)) {
// Remove whitespace
line.erase(std::remove_if(line.begin(), line.end(),
[](char c) { return std::isspace(c); }), line.end());
if (line.empty()) continue;
Sudoku sudoku;
if (sudoku.loadFromString(line)) {
puzzles.push_back(std::move(sudoku));
}
}
return puzzles;
}
using SudokuSolverCallback = SudokuSolverBuilder::SetCellCollapsedCallback<decltype([](Sudoku& sudoku)
{
static Sudoku LastSudoku{};
static int counter = 0;
for (size_t y = 0; y < 9; ++y) {
for (size_t x = 0; x < 9; ++x) {
std::cout << static_cast<int>(sudokuWorld.getValue(x + y * 9)) << " ";
int current = static_cast<int>(sudoku.getValue(x + y * 9));
int last = static_cast<int>(LastSudoku.getValue(x + y * 9));
if (current != last) {
std::cout << "\033[31m" << current << "\033[0m ";
} else {
std::cout << current << " ";
}
if (x == 2 || x == 5) std::cout << "| ";
}
std::cout << std::endl;
if (y == 2 || y == 5) std::cout << "------+-------+------" << std::endl;
}
LastSudoku = sudoku;
std::cout << "Iteration: " << counter << std::endl;
counter++;
// std::cout << std::endl;
// std::cout << "Press Enter to continue..." << std::endl;
// std::cin.get();
})>
::Build;
int main()
{
std::cout << "Running Sudoku WFC" << std::endl;
Sudoku sudokuWorld{ "040280030010006007609070008000092000900000004000740000500020803400800010070035090" };
bool success = SudokuSolverCallback::Run(sudokuWorld, true);
bool solved = sudokuWorld.isSolved();
if (success && solved) {
std::cout << "Sudoku solved successfully!" << std::endl;
} else {
std::cout << "Failed to solve sudoku!" << std::endl;
}
// Print the solved sudoku
for (size_t y = 0; y < 9; ++y) {
for (size_t x = 0; x < 9; ++x) {
std::cout << static_cast<int>(sudokuWorld.getValue(x + y * 9)) << " ";
if (x == 2 || x == 5) std::cout << "| ";
}
std::cout << std::endl;
if (y == 2 || y == 5) std::cout << "------+-------+------" << std::endl;
}
}

View File

@@ -282,7 +282,7 @@ TEST_F(SudokuTest, WFCIntegration)
// Tests loading and solving puzzles from data files
TEST_F(SudokuTest, LoadAndSolveEasyPuzzles)
{
std::vector<Sudoku> easyPuzzles = loadPuzzlesFromFile("/home/connor/repos/nd-wfc/demos/sudoku/data/Sudoku_easy.txt");
std::vector<Sudoku> easyPuzzles = loadPuzzlesFromFile("../data/Sudoku_easy.txt");
ASSERT_GT(easyPuzzles.size(), 0) << "No easy puzzles loaded";
@@ -320,7 +320,7 @@ TEST_F(SudokuTest, LoadAndSolveEasyPuzzles)
TEST_F(SudokuTest, LoadAndSolveMediumPuzzles)
{
std::vector<Sudoku> mediumPuzzles = loadPuzzlesFromFile("/home/connor/repos/nd-wfc/demos/sudoku/data/Sudoku_medium.txt");
std::vector<Sudoku> mediumPuzzles = loadPuzzlesFromFile("../data/Sudoku_medium.txt");
ASSERT_GT(mediumPuzzles.size(), 0) << "No medium puzzles loaded";
@@ -358,7 +358,7 @@ TEST_F(SudokuTest, LoadAndSolveMediumPuzzles)
TEST_F(SudokuTest, LoadAndSolveHardPuzzles)
{
std::vector<Sudoku> hardPuzzles = loadPuzzlesFromFile("/home/connor/repos/nd-wfc/demos/sudoku/data/Sudoku_hard.txt");
std::vector<Sudoku> hardPuzzles = loadPuzzlesFromFile("../data/Sudoku_hard.txt");
ASSERT_GT(hardPuzzles.size(), 0) << "No hard puzzles loaded";
@@ -396,7 +396,7 @@ TEST_F(SudokuTest, LoadAndSolveHardPuzzles)
TEST_F(SudokuTest, LoadAndSolveDiabolicalPuzzles)
{
std::vector<Sudoku> diabolicalPuzzles = loadPuzzlesFromFile("/home/connor/repos/nd-wfc/demos/sudoku/data/Sudoku_diabolical.txt");
std::vector<Sudoku> diabolicalPuzzles = loadPuzzlesFromFile("../data/Sudoku_diabolical.txt");
ASSERT_GT(diabolicalPuzzles.size(), 0) << "No diabolical puzzles loaded";
@@ -435,7 +435,7 @@ TEST_F(SudokuTest, LoadAndSolveDiabolicalPuzzles)
// Test loading a single puzzle from each difficulty file
TEST_F(SudokuTest, LoadAndSolveFirstPuzzleFromEachFile)
{
const std::string dataPath = "/home/connor/repos/nd-wfc/demos/sudoku/data";
const std::string dataPath = "../data";
const std::vector<std::string> files = {"Sudoku_easy.txt", "Sudoku_medium.txt", "Sudoku_hard.txt", "Sudoku_diabolical.txt"};
for (const auto& filename : files) {