failure analysis framework
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -73,3 +73,4 @@ thirdparty/*/install/
|
|||||||
# Temporary files
|
# Temporary files
|
||||||
*.tmp
|
*.tmp
|
||||||
*.temp
|
*.temp
|
||||||
|
demos/sudoku/data/Sudoku_failing.txt
|
||||||
|
|||||||
@@ -45,6 +45,18 @@ add_executable(sudoku_wfc_demo
|
|||||||
sudoku.cpp
|
sudoku.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Create failing puzzles analyzer executable
|
||||||
|
add_executable(analyze_failing_puzzles
|
||||||
|
analyze_failing_puzzles.cpp
|
||||||
|
sudoku.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create debug failing puzzles executable
|
||||||
|
add_executable(debug_failing_puzzles
|
||||||
|
debug_failing_puzzles.cpp
|
||||||
|
sudoku.cpp
|
||||||
|
)
|
||||||
|
|
||||||
# Set output directory for sudoku_demo
|
# Set output directory for sudoku_demo
|
||||||
set_target_properties(sudoku_demo PROPERTIES
|
set_target_properties(sudoku_demo PROPERTIES
|
||||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||||
@@ -57,21 +69,47 @@ set_target_properties(sudoku_wfc_demo PROPERTIES
|
|||||||
CXX_STANDARD_REQUIRED ON
|
CXX_STANDARD_REQUIRED ON
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Set output directory and properties for analyze_failing_puzzles
|
||||||
|
set_target_properties(analyze_failing_puzzles PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||||
|
CXX_STANDARD 20
|
||||||
|
CXX_STANDARD_REQUIRED ON
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set output directory and properties for debug_failing_puzzles
|
||||||
|
set_target_properties(debug_failing_puzzles PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||||
|
CXX_STANDARD 20
|
||||||
|
CXX_STANDARD_REQUIRED ON
|
||||||
|
)
|
||||||
|
|
||||||
# Include directories
|
# Include directories
|
||||||
target_include_directories(sudoku_demo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(sudoku_demo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
target_include_directories(sudoku_wfc_demo PRIVATE
|
target_include_directories(sudoku_wfc_demo PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../include
|
${CMAKE_CURRENT_SOURCE_DIR}/../../include
|
||||||
)
|
)
|
||||||
|
target_include_directories(analyze_failing_puzzles PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../include
|
||||||
|
)
|
||||||
|
target_include_directories(debug_failing_puzzles PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../include
|
||||||
|
)
|
||||||
|
|
||||||
# Optional: Enable optimizations for release builds
|
# Optional: Enable optimizations for release builds
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
target_compile_options(sudoku_demo PRIVATE /O2)
|
target_compile_options(sudoku_demo PRIVATE /O2)
|
||||||
target_compile_options(sudoku_wfc_demo PRIVATE /O2)
|
target_compile_options(sudoku_wfc_demo PRIVATE /O2)
|
||||||
|
target_compile_options(analyze_failing_puzzles PRIVATE /O2)
|
||||||
|
target_compile_options(debug_failing_puzzles PRIVATE /O2)
|
||||||
else()
|
else()
|
||||||
target_compile_options(sudoku_demo PRIVATE -O3 -march=native)
|
target_compile_options(sudoku_demo PRIVATE -O3 -march=native)
|
||||||
target_compile_options(sudoku_wfc_demo PRIVATE -O3 -march=native)
|
target_compile_options(sudoku_wfc_demo PRIVATE -O3 -march=native)
|
||||||
|
target_compile_options(analyze_failing_puzzles PRIVATE -O3 -march=native)
|
||||||
|
target_compile_options(debug_failing_puzzles PRIVATE -O3 -march=native)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -126,9 +164,9 @@ endif()
|
|||||||
|
|
||||||
# Installation (optional)
|
# Installation (optional)
|
||||||
if(HAS_GTEST AND HAS_BENCHMARK)
|
if(HAS_GTEST AND HAS_BENCHMARK)
|
||||||
install(TARGETS sudoku_demo sudoku_wfc_demo sudoku_tests sudoku_benchmarks DESTINATION bin)
|
install(TARGETS sudoku_demo sudoku_wfc_demo analyze_failing_puzzles debug_failing_puzzles sudoku_tests sudoku_benchmarks DESTINATION bin)
|
||||||
elseif(HAS_GTEST)
|
elseif(HAS_GTEST)
|
||||||
install(TARGETS sudoku_demo sudoku_wfc_demo sudoku_tests DESTINATION bin)
|
install(TARGETS sudoku_demo sudoku_wfc_demo analyze_failing_puzzles debug_failing_puzzles sudoku_tests DESTINATION bin)
|
||||||
else()
|
else()
|
||||||
install(TARGETS sudoku_demo sudoku_wfc_demo DESTINATION bin)
|
install(TARGETS sudoku_demo sudoku_wfc_demo analyze_failing_puzzles debug_failing_puzzles DESTINATION bin)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
190
demos/sudoku/analyze_failing_puzzles.cpp
Normal file
190
demos/sudoku/analyze_failing_puzzles.cpp
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
#include "sudoku.h"
|
||||||
|
#include <nd-wfc/wfc.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "Failed to open file: " << filename << std::endl;
|
||||||
|
return puzzles;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to save failing puzzles to a file
|
||||||
|
void saveFailingPuzzles(const std::vector<std::string>& failingPuzzles, const std::string& outputFile) {
|
||||||
|
std::ofstream file(outputFile);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "Failed to open output file: " << outputFile << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& puzzle : failingPuzzles) {
|
||||||
|
file << puzzle << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Saved " << failingPuzzles.size() << " failing puzzles to " << outputFile << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Create WFC solver
|
||||||
|
auto sudokuSolver = WFC::Builder<Sudoku, uint8_t>()
|
||||||
|
.DefineIDs<1, 2, 3, 4, 5, 6, 7, 8, 9>()
|
||||||
|
.Variable<1, 2, 3, 4, 5, 6, 7, 8, 9>([](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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// File paths
|
||||||
|
const std::string dataPath = "/home/connor/repos/nd-wfc/demos/sudoku/data";
|
||||||
|
const std::vector<std::string> inputFiles = {
|
||||||
|
dataPath + "/Sudoku_easy.txt",
|
||||||
|
dataPath + "/Sudoku_medium.txt",
|
||||||
|
dataPath + "/Sudoku_hard.txt",
|
||||||
|
dataPath + "/Sudoku_diabolical.txt"
|
||||||
|
};
|
||||||
|
const std::string outputFile = dataPath + "/Sudoku_failing.txt";
|
||||||
|
|
||||||
|
// Collect all failing puzzles
|
||||||
|
std::vector<std::string> allFailingPuzzles;
|
||||||
|
std::vector<std::pair<std::string, int>> failureStats; // filename -> count
|
||||||
|
|
||||||
|
std::cout << "Analyzing Sudoku puzzles for solver failures..." << std::endl;
|
||||||
|
std::cout << "=================================================" << std::endl;
|
||||||
|
|
||||||
|
for (const auto& inputFile : inputFiles) {
|
||||||
|
std::cout << "\nProcessing " << inputFile << "..." << std::endl;
|
||||||
|
|
||||||
|
// Load puzzles from file
|
||||||
|
auto puzzles = loadPuzzlesFromFile(inputFile);
|
||||||
|
if (puzzles.empty()) {
|
||||||
|
std::cout << "No puzzles loaded from " << inputFile << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Loaded " << puzzles.size() << " puzzles" << std::endl;
|
||||||
|
|
||||||
|
// Test each puzzle
|
||||||
|
int solvedCount = 0;
|
||||||
|
int failedCount = 0;
|
||||||
|
std::vector<std::string> failingPuzzles;
|
||||||
|
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < puzzles.size(); ++i) {
|
||||||
|
auto& sudoku = puzzles[i];
|
||||||
|
|
||||||
|
// Validate puzzle before solving
|
||||||
|
if (!sudoku.isValid()) {
|
||||||
|
std::cout << "Puzzle " << i << " is invalid, skipping" << std::endl;
|
||||||
|
failedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to solve
|
||||||
|
auto puzzleStart = std::chrono::high_resolution_clock::now();
|
||||||
|
sudokuSolver.Run(sudoku, false); // false = disable verbose output for speed
|
||||||
|
auto puzzleEnd = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
bool solved = sudoku.isSolved();
|
||||||
|
|
||||||
|
if (solved) {
|
||||||
|
solvedCount++;
|
||||||
|
} else {
|
||||||
|
failedCount++;
|
||||||
|
// Get the original puzzle string for saving
|
||||||
|
std::string puzzleStr = sudoku.toString();
|
||||||
|
failingPuzzles.push_back(puzzleStr);
|
||||||
|
|
||||||
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(puzzleEnd - puzzleStart);
|
||||||
|
std::cout << "Puzzle " << i << " failed to solve in " << duration.count() << "ms" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress indicator for large files
|
||||||
|
if ((i + 1) % 1000 == 0) {
|
||||||
|
std::cout << "Progress: " << (i + 1) << "/" << puzzles.size()
|
||||||
|
<< " (" << solvedCount << " solved, " << failedCount << " failed)" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
auto totalDuration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
|
||||||
|
|
||||||
|
std::cout << "\nResults for " << inputFile << ":" << std::endl;
|
||||||
|
std::cout << " Total puzzles in file: " << puzzles.size() << std::endl;
|
||||||
|
std::cout << " Solved: " << solvedCount << std::endl;
|
||||||
|
std::cout << " Failed: " << failedCount << std::endl;
|
||||||
|
std::cout << " Success rate: " << (puzzles.size() > 0 ? (solvedCount * 100.0 / puzzles.size()) : 0) << "%" << std::endl;
|
||||||
|
std::cout << " Total time: " << totalDuration.count() << " seconds" << std::endl;
|
||||||
|
|
||||||
|
// Add failing puzzles to the global list
|
||||||
|
allFailingPuzzles.insert(allFailingPuzzles.end(), failingPuzzles.begin(), failingPuzzles.end());
|
||||||
|
failureStats.push_back({inputFile, failedCount});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save all failing puzzles to output file
|
||||||
|
std::cout << "\n=================================================" << std::endl;
|
||||||
|
std::cout << "Analysis complete!" << std::endl;
|
||||||
|
std::cout << "Total failing puzzles across all files: " << allFailingPuzzles.size() << std::endl;
|
||||||
|
|
||||||
|
if (!allFailingPuzzles.empty()) {
|
||||||
|
saveFailingPuzzles(allFailingPuzzles, outputFile);
|
||||||
|
|
||||||
|
std::cout << "\nFailure statistics by file:" << std::endl;
|
||||||
|
for (const auto& stat : failureStats) {
|
||||||
|
std::cout << " " << stat.first << ": " << stat.second << " failures" << std::endl;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cout << "No failing puzzles found!" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
196
demos/sudoku/debug_failing_puzzles.cpp
Normal file
196
demos/sudoku/debug_failing_puzzles.cpp
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#include "sudoku.h"
|
||||||
|
#include <nd-wfc/wfc.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
// Helper function to load puzzles from a file (one puzzle per line)
|
||||||
|
std::vector<Sudoku> loadPuzzlesFromFile(const std::string& filename) {
|
||||||
|
std::vector<Sudoku> puzzles;
|
||||||
|
std::ifstream file(filename);
|
||||||
|
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "Failed to open file: " << filename << std::endl;
|
||||||
|
return puzzles;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to print a puzzle in a nice format
|
||||||
|
void printPuzzle(const Sudoku& sudoku, const std::string& title = "") {
|
||||||
|
if (!title.empty()) {
|
||||||
|
std::cout << title << std::endl;
|
||||||
|
std::cout << std::string(title.length(), '=') << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int row = 0; row < 9; ++row) {
|
||||||
|
for (int col = 0; col < 9; ++col) {
|
||||||
|
uint8_t value = sudoku.get(row, col);
|
||||||
|
std::cout << (value == 0 ? '.' : static_cast<char>('0' + value));
|
||||||
|
if (col < 8) std::cout << " ";
|
||||||
|
if (col == 2 || col == 5) std::cout << "| ";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
if (row == 2 || row == 5) {
|
||||||
|
std::cout << "------+-------+------" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to count filled cells in a puzzle
|
||||||
|
int countFilledCells(const Sudoku& sudoku) {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < 81; ++i) {
|
||||||
|
if (sudoku.get(i / 9, i % 9) != 0) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyze a single failing puzzle in detail
|
||||||
|
void analyzeFailingPuzzle(const Sudoku& originalPuzzle, int puzzleIndex) {
|
||||||
|
std::cout << "\n" << std::string(60, '=') << std::endl;
|
||||||
|
std::cout << "ANALYZING FAILING PUZZLE #" << puzzleIndex << std::endl;
|
||||||
|
std::cout << std::string(60, '=') << std::endl;
|
||||||
|
|
||||||
|
// Show original puzzle
|
||||||
|
printPuzzle(originalPuzzle, "Original Puzzle");
|
||||||
|
|
||||||
|
// Show statistics
|
||||||
|
int filledCells = countFilledCells(originalPuzzle);
|
||||||
|
std::cout << "Statistics:" << std::endl;
|
||||||
|
std::cout << " Filled cells: " << filledCells << "/81 (" << std::fixed << std::setprecision(1)
|
||||||
|
<< (filledCells * 100.0 / 81) << "%)" << std::endl;
|
||||||
|
std::cout << " Empty cells: " << (81 - filledCells) << std::endl;
|
||||||
|
std::cout << " Is valid: " << (originalPuzzle.isValid() ? "Yes" : "No") << std::endl;
|
||||||
|
std::cout << " Is solved: " << (originalPuzzle.isSolved() ? "Yes" : "No") << std::endl;
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
// Try different solving approaches
|
||||||
|
auto sudokuSolver = WFC::Builder<Sudoku, uint8_t>()
|
||||||
|
.DefineIDs<1, 2, 3, 4, 5, 6, 7, 8, 9>()
|
||||||
|
.Variable<1, 2, 3, 4, 5, 6, 7, 8, 9>([](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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Test 1: Try solving with different configurations
|
||||||
|
std::cout << "Testing different solving approaches:" << std::endl;
|
||||||
|
|
||||||
|
// Test with verbose output to see what happens
|
||||||
|
Sudoku testPuzzle = originalPuzzle;
|
||||||
|
std::cout << "\nTest 1: Solving with verbose output..." << std::endl;
|
||||||
|
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
sudokuSolver.Run(testPuzzle, true); // Enable verbose output
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||||
|
|
||||||
|
bool solved = testPuzzle.isSolved();
|
||||||
|
std::cout << "Result: " << (solved ? "SOLVED" : "FAILED") << std::endl;
|
||||||
|
std::cout << "Time taken: " << duration.count() << "ms" << std::endl;
|
||||||
|
|
||||||
|
if (solved) {
|
||||||
|
printPuzzle(testPuzzle, "Solved Puzzle");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Multiple attempts
|
||||||
|
if (!solved) {
|
||||||
|
std::cout << "\nTest 2: Multiple solving attempts..." << std::endl;
|
||||||
|
|
||||||
|
int attempts = 3;
|
||||||
|
for (int attempt = 1; attempt <= attempts; ++attempt) {
|
||||||
|
Sudoku attemptPuzzle = originalPuzzle;
|
||||||
|
std::cout << "Attempt " << attempt << ": ";
|
||||||
|
|
||||||
|
auto attemptStart = std::chrono::high_resolution_clock::now();
|
||||||
|
sudokuSolver.Run(attemptPuzzle, false); // No verbose output
|
||||||
|
auto attemptEnd = std::chrono::high_resolution_clock::now();
|
||||||
|
auto attemptDuration = std::chrono::duration_cast<std::chrono::milliseconds>(attemptEnd - attemptStart);
|
||||||
|
|
||||||
|
bool attemptSolved = attemptPuzzle.isSolved();
|
||||||
|
std::cout << (attemptSolved ? "SOLVED" : "FAILED")
|
||||||
|
<< " (" << attemptDuration.count() << "ms)" << std::endl;
|
||||||
|
|
||||||
|
if (attemptSolved) {
|
||||||
|
std::cout << "SUCCESS on attempt " << attempt << "!" << std::endl;
|
||||||
|
printPuzzle(attemptPuzzle, "Successfully Solved Puzzle");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
const std::string failingPuzzlesFile = "/home/connor/repos/nd-wfc/demos/sudoku/data/Sudoku_failing.txt";
|
||||||
|
|
||||||
|
std::cout << "Loading failing puzzles from: " << failingPuzzlesFile << std::endl;
|
||||||
|
|
||||||
|
// Load failing puzzles
|
||||||
|
auto failingPuzzles = loadPuzzlesFromFile(failingPuzzlesFile);
|
||||||
|
|
||||||
|
if (failingPuzzles.empty()) {
|
||||||
|
std::cout << "No failing puzzles found!" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Found " << failingPuzzles.size() << " failing puzzles" << std::endl;
|
||||||
|
|
||||||
|
// Analyze each failing puzzle
|
||||||
|
for (size_t i = 0; i < failingPuzzles.size(); ++i) {
|
||||||
|
analyzeFailingPuzzle(failingPuzzles[i], i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n" << std::string(60, '=') << std::endl;
|
||||||
|
std::cout << "ANALYSIS COMPLETE" << std::endl;
|
||||||
|
std::cout << "Total failing puzzles analyzed: " << failingPuzzles.size() << std::endl;
|
||||||
|
std::cout << std::string(60, '=') << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -326,4 +326,4 @@ bool SudokuLoader::parseLine(const std::string& line, std::array<uint8_t, 81>& b
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -209,31 +209,10 @@ public: // WFC Support
|
|||||||
constexpr size_t size() const {
|
constexpr size_t size() const {
|
||||||
return 81;
|
return 81;
|
||||||
}
|
}
|
||||||
|
|
||||||
public: // Solver Interface
|
|
||||||
// Solve the puzzle using WFC algorithm
|
|
||||||
bool solve();
|
|
||||||
|
|
||||||
// Solve with custom initial constraints (for testing)
|
|
||||||
bool solveWithConstraints(const std::vector<std::pair<int, int>>& constraints);
|
|
||||||
|
|
||||||
// Get the number of attempts made during solving
|
|
||||||
int getSolveAttempts() const { return solve_attempts_; }
|
|
||||||
|
|
||||||
// Get the time taken for the last solve operation (in microseconds)
|
|
||||||
long long getSolveTimeMicroseconds() const { return solve_time_us_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
mutable int solve_attempts_ = 0;
|
|
||||||
mutable long long solve_time_us_ = 0;
|
|
||||||
|
|
||||||
// Helper method for backtracking solver
|
|
||||||
bool solveBacktracking(size_t index);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Static assert to ensure correct size (now 56 bytes with solver additions)
|
// Static assert to ensure correct size (now 56 bytes with solver additions)
|
||||||
static_assert(sizeof(Sudoku) == 56, "Sudoku class must be exactly 56 bytes");
|
static_assert(sizeof(Sudoku) == 41, "Sudoku class must be exactly 41 bytes");
|
||||||
|
|
||||||
// Fast solution validator (stateless)
|
// Fast solution validator (stateless)
|
||||||
class SudokuValidator {
|
class SudokuValidator {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include <nd-wfc/wfc.hpp>
|
#include <nd-wfc/wfc.hpp>
|
||||||
#include <nd-wfc/worlds.hpp>
|
|
||||||
#include "sudoku.h"
|
#include "sudoku.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,49 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include "sudoku.h"
|
#include "sudoku.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <nd-wfc/wfc.hpp>
|
||||||
|
#include <fstream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
// Forward declaration for helper function
|
||||||
|
std::vector<Sudoku> loadPuzzlesFromFile(const std::string& filename);
|
||||||
|
|
||||||
|
static auto sudokuTestSolver = WFC::Builder<Sudoku, uint8_t>()
|
||||||
|
.DefineIDs<1, 2, 3, 4, 5, 6, 7, 8, 9>()
|
||||||
|
.Variable<1, 2, 3, 4, 5, 6, 7, 8, 9>([](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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
// Test fixture for Sudoku tests
|
// Test fixture for Sudoku tests
|
||||||
class SudokuTest : public ::testing::Test {
|
class SudokuTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
// Common test setup
|
// Common test setup
|
||||||
}
|
}
|
||||||
@@ -28,6 +67,11 @@ protected:
|
|||||||
sudoku.loadFromString(easy);
|
sudoku.loadFromString(easy);
|
||||||
return sudoku;
|
return sudoku;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Sudoku SolvePuzzle(Sudoku& sudoku) {
|
||||||
|
sudokuTestSolver.Run(sudoku, true);
|
||||||
|
return sudoku;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Basic functionality tests
|
// Basic functionality tests
|
||||||
@@ -97,7 +141,7 @@ TEST_F(SudokuTest, Clear) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SudokuTest, MemorySize) {
|
TEST_F(SudokuTest, MemorySize) {
|
||||||
EXPECT_EQ(sizeof(Sudoku), 56); // Updated to include solver members
|
EXPECT_EQ(sizeof(Sudoku), 41);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SudokuTest, SetInvalidValue) {
|
TEST_F(SudokuTest, SetInvalidValue) {
|
||||||
@@ -260,6 +304,229 @@ TEST_F(SudokuTest, EdgeCases) {
|
|||||||
EXPECT_EQ(sudoku.get(8, 8), 4);
|
EXPECT_EQ(sudoku.get(8, 8), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SudokuTest, WFCIntegration)
|
||||||
|
{
|
||||||
|
auto sudoku = createEasyPuzzle();
|
||||||
|
sudokuTestSolver.Run(sudoku, true);
|
||||||
|
EXPECT_TRUE(sudoku.isSolved());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
|
||||||
|
ASSERT_GT(easyPuzzles.size(), 0) << "No easy puzzles loaded";
|
||||||
|
|
||||||
|
int solvedCount = 0;
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < easyPuzzles.size(); ++i) {
|
||||||
|
auto& sudoku = easyPuzzles[i];
|
||||||
|
EXPECT_TRUE(sudoku.isValid()) << "Puzzle " << i << " is not valid";
|
||||||
|
|
||||||
|
auto puzzleStart = std::chrono::high_resolution_clock::now();
|
||||||
|
sudokuTestSolver.Run(sudoku, true);
|
||||||
|
auto puzzleEnd = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
EXPECT_TRUE(sudoku.isSolved()) << "Puzzle " << i << " was not solved";
|
||||||
|
|
||||||
|
if (sudoku.isSolved()) {
|
||||||
|
solvedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto puzzleDuration = std::chrono::duration_cast<std::chrono::milliseconds>(puzzleEnd - puzzleStart);
|
||||||
|
if (i < 5) { // Only print timing for first 5 puzzles to avoid spam
|
||||||
|
std::cout << "Easy puzzle " << i << " solved in " << puzzleDuration.count() << "ms" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
auto totalDuration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
|
||||||
|
|
||||||
|
std::cout << "Easy puzzles: solved " << solvedCount << "/" << easyPuzzles.size()
|
||||||
|
<< " in " << totalDuration.count() << " seconds" << std::endl;
|
||||||
|
|
||||||
|
EXPECT_EQ(solvedCount, easyPuzzles.size()) << "Not all easy puzzles were solved";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SudokuTest, LoadAndSolveMediumPuzzles)
|
||||||
|
{
|
||||||
|
std::vector<Sudoku> mediumPuzzles = loadPuzzlesFromFile("/home/connor/repos/nd-wfc/demos/sudoku/data/Sudoku_medium.txt");
|
||||||
|
|
||||||
|
ASSERT_GT(mediumPuzzles.size(), 0) << "No medium puzzles loaded";
|
||||||
|
|
||||||
|
int solvedCount = 0;
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < mediumPuzzles.size(); ++i) {
|
||||||
|
auto& sudoku = mediumPuzzles[i];
|
||||||
|
EXPECT_TRUE(sudoku.isValid()) << "Puzzle " << i << " is not valid";
|
||||||
|
|
||||||
|
auto puzzleStart = std::chrono::high_resolution_clock::now();
|
||||||
|
sudokuTestSolver.Run(sudoku, true);
|
||||||
|
auto puzzleEnd = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
EXPECT_TRUE(sudoku.isSolved()) << "Puzzle " << i << " was not solved";
|
||||||
|
|
||||||
|
if (sudoku.isSolved()) {
|
||||||
|
solvedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto puzzleDuration = std::chrono::duration_cast<std::chrono::milliseconds>(puzzleEnd - puzzleStart);
|
||||||
|
if (i < 5) { // Only print timing for first 5 puzzles to avoid spam
|
||||||
|
std::cout << "Medium puzzle " << i << " solved in " << puzzleDuration.count() << "ms" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
auto totalDuration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
|
||||||
|
|
||||||
|
std::cout << "Medium puzzles: solved " << solvedCount << "/" << mediumPuzzles.size()
|
||||||
|
<< " in " << totalDuration.count() << " seconds" << std::endl;
|
||||||
|
|
||||||
|
EXPECT_EQ(solvedCount, mediumPuzzles.size()) << "Not all medium puzzles were solved";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SudokuTest, LoadAndSolveHardPuzzles)
|
||||||
|
{
|
||||||
|
std::vector<Sudoku> hardPuzzles = loadPuzzlesFromFile("/home/connor/repos/nd-wfc/demos/sudoku/data/Sudoku_hard.txt");
|
||||||
|
|
||||||
|
ASSERT_GT(hardPuzzles.size(), 0) << "No hard puzzles loaded";
|
||||||
|
|
||||||
|
int solvedCount = 0;
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < hardPuzzles.size(); ++i) {
|
||||||
|
auto& sudoku = hardPuzzles[i];
|
||||||
|
EXPECT_TRUE(sudoku.isValid()) << "Puzzle " << i << " is not valid";
|
||||||
|
|
||||||
|
auto puzzleStart = std::chrono::high_resolution_clock::now();
|
||||||
|
sudokuTestSolver.Run(sudoku, true);
|
||||||
|
auto puzzleEnd = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
EXPECT_TRUE(sudoku.isSolved()) << "Puzzle " << i << " was not solved";
|
||||||
|
|
||||||
|
if (sudoku.isSolved()) {
|
||||||
|
solvedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto puzzleDuration = std::chrono::duration_cast<std::chrono::milliseconds>(puzzleEnd - puzzleStart);
|
||||||
|
if (i < 5) { // Only print timing for first 5 puzzles to avoid spam
|
||||||
|
std::cout << "Hard puzzle " << i << " solved in " << puzzleDuration.count() << "ms" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
auto totalDuration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
|
||||||
|
|
||||||
|
std::cout << "Hard puzzles: solved " << solvedCount << "/" << hardPuzzles.size()
|
||||||
|
<< " in " << totalDuration.count() << " seconds" << std::endl;
|
||||||
|
|
||||||
|
EXPECT_EQ(solvedCount, hardPuzzles.size()) << "Not all hard puzzles were solved";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SudokuTest, LoadAndSolveDiabolicalPuzzles)
|
||||||
|
{
|
||||||
|
std::vector<Sudoku> diabolicalPuzzles = loadPuzzlesFromFile("/home/connor/repos/nd-wfc/demos/sudoku/data/Sudoku_diabolical.txt");
|
||||||
|
|
||||||
|
ASSERT_GT(diabolicalPuzzles.size(), 0) << "No diabolical puzzles loaded";
|
||||||
|
|
||||||
|
int solvedCount = 0;
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < diabolicalPuzzles.size(); ++i) {
|
||||||
|
auto& sudoku = diabolicalPuzzles[i];
|
||||||
|
EXPECT_TRUE(sudoku.isValid()) << "Puzzle " << i << " is not valid";
|
||||||
|
|
||||||
|
auto puzzleStart = std::chrono::high_resolution_clock::now();
|
||||||
|
sudokuTestSolver.Run(sudoku, true);
|
||||||
|
auto puzzleEnd = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
EXPECT_TRUE(sudoku.isSolved()) << "Puzzle " << i << " was not solved";
|
||||||
|
|
||||||
|
if (sudoku.isSolved()) {
|
||||||
|
solvedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto puzzleDuration = std::chrono::duration_cast<std::chrono::milliseconds>(puzzleEnd - puzzleStart);
|
||||||
|
if (i < 5) { // Only print timing for first 5 puzzles to avoid spam
|
||||||
|
std::cout << "Diabolical puzzle " << i << " solved in " << puzzleDuration.count() << "ms" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
auto totalDuration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
|
||||||
|
|
||||||
|
std::cout << "Diabolical puzzles: solved " << solvedCount << "/" << diabolicalPuzzles.size()
|
||||||
|
<< " in " << totalDuration.count() << " seconds" << std::endl;
|
||||||
|
|
||||||
|
EXPECT_EQ(solvedCount, diabolicalPuzzles.size()) << "Not all diabolical puzzles were solved";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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::vector<std::string> files = {"Sudoku_easy.txt", "Sudoku_medium.txt", "Sudoku_hard.txt", "Sudoku_diabolical.txt"};
|
||||||
|
|
||||||
|
for (const auto& filename : files) {
|
||||||
|
std::string filepath = dataPath + "/" + filename;
|
||||||
|
std::ifstream file(filepath);
|
||||||
|
|
||||||
|
ASSERT_TRUE(file.is_open()) << "Failed to open file " << filename;
|
||||||
|
|
||||||
|
std::string firstLine;
|
||||||
|
std::getline(file, firstLine);
|
||||||
|
|
||||||
|
// Remove whitespace
|
||||||
|
firstLine.erase(std::remove_if(firstLine.begin(), firstLine.end(),
|
||||||
|
[](char c) { return std::isspace(c); }), firstLine.end());
|
||||||
|
|
||||||
|
ASSERT_FALSE(firstLine.empty()) << "No puzzle data found in " << filename;
|
||||||
|
|
||||||
|
Sudoku puzzle;
|
||||||
|
ASSERT_TRUE(puzzle.loadFromString(firstLine)) << "Failed to load puzzle from first line of " << filename;
|
||||||
|
|
||||||
|
EXPECT_TRUE(puzzle.isValid()) << "Loaded puzzle from " << filename << " is not valid";
|
||||||
|
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
sudokuTestSolver.Run(puzzle, true);
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
EXPECT_TRUE(puzzle.isSolved()) << "Failed to solve first puzzle from " << filename;
|
||||||
|
|
||||||
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||||
|
std::cout << "First puzzle from " << filename << " solved in " << duration.count() << "ms" << 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);
|
||||||
|
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return puzzles;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
return RUN_ALL_TESTS();
|
return RUN_ALL_TESTS();
|
||||||
|
|||||||
@@ -260,13 +260,15 @@ public:
|
|||||||
Wave<MaskType> wave;
|
Wave<MaskType> wave;
|
||||||
std::mt19937& rng;
|
std::mt19937& rng;
|
||||||
WFCStackAllocator& allocator;
|
WFCStackAllocator& allocator;
|
||||||
|
size_t& iterations;
|
||||||
|
|
||||||
SolverState(WorldT& world, size_t variableAmount, std::mt19937& rng, WFCStackAllocator& allocator)
|
SolverState(WorldT& world, size_t variableAmount, std::mt19937& rng, WFCStackAllocator& allocator, size_t& iterations)
|
||||||
: world(world)
|
: world(world)
|
||||||
, propagationQueue{ WFCStackAllocatorAdapter<size_t>(allocator) }
|
, propagationQueue{ WFCStackAllocatorAdapter<size_t>(allocator) }
|
||||||
, wave{ world.size(), variableAmount, allocator }
|
, wave{ world.size(), variableAmount, allocator }
|
||||||
, rng(rng)
|
, rng(rng)
|
||||||
, allocator(allocator)
|
, allocator(allocator)
|
||||||
|
, iterations(iterations)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
SolverState(const SolverState& other) = default;
|
SolverState(const SolverState& other) = default;
|
||||||
@@ -282,7 +284,8 @@ public:
|
|||||||
{
|
{
|
||||||
WFCStackAllocator allocator{};
|
WFCStackAllocator allocator{};
|
||||||
std::mt19937 random{ std::random_device{}() };
|
std::mt19937 random{ std::random_device{}() };
|
||||||
SolverState state(world, m_variables.size(), random, allocator);
|
size_t iterations = 0;
|
||||||
|
SolverState state(world, m_variables.size(), random, allocator, iterations);
|
||||||
return Run(state, propagateInitialValues);
|
return Run(state, propagateInitialValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,8 +310,7 @@ public:
|
|||||||
|
|
||||||
bool RunLoop(SolverState& state)
|
bool RunLoop(SolverState& state)
|
||||||
{
|
{
|
||||||
constexpr size_t maxIterations = 1024;
|
for (; state.iterations < 256; ++state.iterations)
|
||||||
for (size_t i = 0; i < maxIterations; ++i)
|
|
||||||
{
|
{
|
||||||
if (!Propagate(state))
|
if (!Propagate(state))
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user