Callback System
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user