fixed tests

This commit is contained in:
2025-09-01 09:15:35 +09:00
parent 8dc7bcd618
commit e9a298e9ef
19 changed files with 228 additions and 1853 deletions

View File

@@ -1,24 +0,0 @@
# Console Renderer Demo
add_executable(sudoku_demo_renderer
sudoku_demo_renderer.cpp
console_renderer.cpp
sudoku/sudoku.cpp
)
target_include_directories(sudoku_demo_renderer PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)
# Link threading library for std::thread (platform-specific)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
if(Threads_FOUND)
target_link_libraries(sudoku_demo_renderer Threads::Threads)
endif()
# Set C++17 standard
set_target_properties(sudoku_demo_renderer PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)

View File

@@ -1,267 +0,0 @@
# Console Rendering System for Puzzle Demos
This directory contains a comprehensive console rendering system designed for real-time puzzle visualization without adding new lines to the console. The system is perfect for showing solving animations and interactive demos.
## Features
- **Real-time Updates**: Render puzzle states without scrolling the console
- **Space Pre-allocation**: Reserve console space before rendering to prevent layout issues
- **Animation Support**: Smooth animations for solving steps and cell highlights
- **Cross-platform**: Works on Linux, macOS, and Windows
- **Multiple Puzzle Types**: Support for Sudoku and Nonogram puzzles
## Quick Start
### Basic Sudoku Rendering
```cpp
#include "console_renderer.h"
#include "sudoku/sudoku.h"
int main() {
// 1. Create a Sudoku puzzle
std::string puzzle = "530070000600195000098000060800060003400803001700020006060000280000419005000080079";
Sudoku sudoku(puzzle);
// 2. Create renderer
SudokuRenderer renderer(sudoku);
// 3. IMPORTANT: Allocate space first!
renderer.allocateSpace();
// 4. Render initial state
renderer.render();
// 5. Update puzzle and re-render (updates in place!)
sudoku.set(0, 0, 5);
renderer.render();
return 0;
}
```
### Animation Example
```cpp
// Animate a cell change with highlighting
renderer.animateCell(row, col, new_value, 200); // 200ms delay
// Show solving progress
renderer.showSolvingProgress("Solving step 1 of 10");
// Highlight specific cells
renderer.renderWithHighlight(highlight_row, highlight_col);
```
## API Reference
### SudokuRenderer
The `SudokuRenderer` class provides a clean API for rendering Sudoku puzzles with real-time updates.
#### Constructor
```cpp
SudokuRenderer(const Sudoku& sudoku)
```
#### Core Methods
```cpp
void allocateSpace() // Reserve console space (call first!)
void render() // Render current puzzle state
void clear() // Clear allocated space
```
#### Animation Methods
```cpp
void renderWithHighlight(int row, int col) // Highlight specific cell
void showSolvingProgress(const std::string& status) // Update status line
void animateCell(int row, int col, uint8_t value, int delay_ms = 100) // Animate cell change
```
### NonogramRenderer
The `NonogramRenderer` class handles Nonogram puzzle visualization with hints and solution grid.
#### Constructor
```cpp
NonogramRenderer(const Nonogram& nonogram)
```
#### Core Methods
```cpp
void allocateSpace() // Reserve console space
void render() // Render puzzle with hints
void clear() // Clear allocated space
```
#### Nonogram-specific Methods
```cpp
void renderWithState(const std::vector<std::vector<int>>& state) // Render with current solving state
void showSolvingProgress(const std::string& status) // Update status
```
### ConsoleRenderer (Base Class)
Provides low-level console control utilities.
#### Static Utilities
```cpp
static void moveCursorUp(int lines)
static void moveCursorDown(int lines)
static void moveCursorToPosition(int row, int col)
static void clearLine()
static void clearScreen()
static void hideCursor()
static void showCursor()
static void sleep(int milliseconds)
static std::string repeatChar(char c, int count)
static std::string centerText(const std::string& text, int width)
```
### DemoAnimator
Helper class for creating smooth demo animations.
```cpp
static void typewriterEffect(const std::string& text, int delay_ms = 50)
static void fadeIn(const std::vector<std::string>& lines, int delay_ms = 100)
static void progressBar(const std::string& label, int current, int total, int width = 30)
```
## Important Usage Notes
### 1. Always Allocate Space First
**Critical**: Call `allocateSpace()` before any rendering operations:
```cpp
SudokuRenderer renderer(sudoku);
renderer.allocateSpace(); // This MUST be called first!
renderer.render();
```
This reserves the necessary console lines and prevents the display from being corrupted by new output.
### 2. Real-time Updates
After calling `allocateSpace()`, all subsequent `render()` calls will update the display in place:
```cpp
renderer.allocateSpace(); // Once at the start
renderer.render(); // Initial display
// Update puzzle state
sudoku.set(0, 0, 5);
renderer.render(); // Updates in place - no new lines!
sudoku.set(1, 1, 3);
renderer.render(); // Updates in place again
```
### 3. Animation Best Practices
For smooth animations, use the provided animation methods:
```cpp
// Good: Use built-in animation
renderer.animateCell(row, col, value, 200);
// Also good: Manual animation with proper timing
for (int i = 0; i < steps.size(); ++i) {
renderer.showSolvingProgress("Step " + std::to_string(i + 1));
// Update state
renderer.render();
ConsoleRenderer::sleep(300);
}
```
### 4. Error Handling
```cpp
try {
ConsoleRenderer::hideCursor(); // For cleaner display
SudokuRenderer renderer(sudoku);
renderer.allocateSpace();
renderer.render();
// ... your demo logic ...
ConsoleRenderer::showCursor(); // Always restore cursor
} catch (const std::exception& e) {
ConsoleRenderer::showCursor(); // Restore cursor on error
std::cerr << "Error: " << e.what() << std::endl;
}
```
## Building
### Using CMake
```bash
cd /testbed/demos
mkdir build && cd build
cmake ..
make sudoku_demo_renderer
./sudoku_demo_renderer
```
### Manual Compilation
```bash
cd /testbed/demos
g++ -std=c++17 -I. -I.. -pthread \
sudoku_demo_renderer.cpp console_renderer.cpp sudoku/sudoku.cpp \
-o sudoku_demo_renderer
./sudoku_demo_renderer
```
## Demo Programs
### sudoku_demo_renderer
A comprehensive demonstration showing:
- Basic Sudoku rendering
- Real-time puzzle updates
- Solving animations
- API usage examples
```bash
./sudoku_demo_renderer
```
### test_renderer
Simple test program to verify the renderer functionality:
```bash
./test_renderer
```
## Technical Details
### Console Control
The system uses ANSI escape sequences for cursor control:
- `\033[nA` - Move cursor up n lines
- `\033[nB` - Move cursor down n lines
- `\033[2K` - Clear current line
- `\033[?25l` - Hide cursor
- `\033[?25h` - Show cursor
### Memory Efficiency
- Sudoku puzzles use only 41 bytes of storage
- Nonogram puzzles use bit-packed storage for solutions
- Renderers don't store puzzle state - they reference existing objects
### Cross-platform Support
The renderer works on:
- Linux (tested)
- macOS (ANSI escape sequences supported)
- Windows (with ANSI support enabled)
## Examples
See the demo programs for complete working examples:
- `sudoku_demo_renderer.cpp` - Full-featured Sudoku demo
- `test_renderer.cpp` - Basic functionality test
The console rendering system provides a powerful foundation for creating engaging puzzle demos and interactive solving visualizations!

View File

@@ -1,509 +0,0 @@
#include "console_renderer.h"
#include "sudoku/sudoku.h"
#include "nonogram/nonogram.h"
#include <iostream>
#include <iomanip>
// ANSI escape codes for cursor control
#ifdef _WIN32
#include <windows.h>
#include <conio.h>
#else
#include <unistd.h>
#include <termios.h>
#endif
// =============================================================================
// ConsoleRenderer Implementation
// =============================================================================
void ConsoleRenderer::moveCursorUp(int lines) {
if (lines > 0) {
std::cout << "\033[" << lines << "A";
std::cout.flush();
}
}
void ConsoleRenderer::moveCursorDown(int lines) {
if (lines > 0) {
std::cout << "\033[" << lines << "B";
std::cout.flush();
}
}
void ConsoleRenderer::moveCursorToColumn(int col) {
std::cout << "\033[" << col << "G";
std::cout.flush();
}
void ConsoleRenderer::moveCursorToPosition(int row, int col) {
std::cout << "\033[" << row << ";" << col << "H";
std::cout.flush();
}
void ConsoleRenderer::clearLine() {
std::cout << "\033[2K";
std::cout.flush();
}
void ConsoleRenderer::clearScreen() {
std::cout << "\033[2J\033[H";
std::cout.flush();
}
void ConsoleRenderer::hideCursor() {
std::cout << "\033[?25l";
std::cout.flush();
}
void ConsoleRenderer::showCursor() {
std::cout << "\033[?25h";
std::cout.flush();
}
void ConsoleRenderer::sleep(int milliseconds) {
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}
std::string ConsoleRenderer::repeatChar(char c, int count) {
return std::string(count, c);
}
std::string ConsoleRenderer::centerText(const std::string& text, int width) {
if (static_cast<int>(text.length()) >= width) return text;
int padding = (width - static_cast<int>(text.length())) / 2;
return repeatChar(' ', padding) + text;
}
// =============================================================================
// SudokuRenderer Implementation
// =============================================================================
SudokuRenderer::SudokuRenderer(const Sudoku& sudoku) : sudoku_(sudoku) {}
void SudokuRenderer::allocateSpace() {
if (space_allocated_) return;
// Print empty lines to reserve space
for (int i = 0; i < TOTAL_HEIGHT; ++i) {
std::cout << std::endl;
}
allocated_lines_ = TOTAL_HEIGHT;
space_allocated_ = true;
// Move cursor back to start of allocated space
moveCursorUp(TOTAL_HEIGHT);
}
void SudokuRenderer::render() {
if (!space_allocated_) {
allocateSpace();
}
// Save cursor position
std::cout << "\033[s";
// Render title
std::string title = "SUDOKU PUZZLE";
std::cout << centerText(title, GRID_WIDTH) << std::endl;
std::cout << centerText(repeatChar('=', title.length()), GRID_WIDTH) << std::endl;
std::cout << std::endl;
// Render grid
for (int row = 0; row < 9; ++row) {
std::cout << formatSudokuLine(row) << std::endl;
// Add separator lines after rows 2 and 5
if (row == 2 || row == 5) {
std::cout << getSeparatorLine() << std::endl;
}
}
std::cout << std::endl;
// Render status
std::string status = sudoku_.isSolved() ? "SOLVED!" :
sudoku_.isValid() ? "Valid puzzle" : "Invalid puzzle";
std::cout << centerText(status, GRID_WIDTH) << std::endl;
// Restore cursor position
std::cout << "\033[u";
std::cout.flush();
}
void SudokuRenderer::clear() {
if (!space_allocated_) return;
// Move to start of allocated space
std::cout << "\033[s";
// Clear all lines
for (int i = 0; i < allocated_lines_; ++i) {
clearLine();
if (i < allocated_lines_ - 1) {
moveCursorDown(1);
}
}
// Restore cursor position
std::cout << "\033[u";
std::cout.flush();
}
void SudokuRenderer::renderWithHighlight(int highlight_row, int highlight_col) {
if (!space_allocated_) {
allocateSpace();
}
// Save cursor position
std::cout << "\033[s";
// Render title
std::string title = "SUDOKU PUZZLE";
std::cout << centerText(title, GRID_WIDTH) << std::endl;
std::cout << centerText(repeatChar('=', title.length()), GRID_WIDTH) << std::endl;
std::cout << std::endl;
// Render grid with highlighting
for (int row = 0; row < 9; ++row) {
std::string line = formatSudokuLine(row);
// Apply highlighting if this is the target row
if (row == highlight_row && highlight_col >= 0 && highlight_col < 9) {
// Calculate position of highlighted cell in the line
int pos = highlight_col * 2; // Each cell takes 2 chars ("X ")
if (highlight_col >= 3) pos += 2; // Account for " | " separator
if (highlight_col >= 6) pos += 2; // Account for second " | " separator
// Insert highlighting safely
std::string highlighted = line;
if (pos >= 0 && pos < static_cast<int>(highlighted.length())) {
// Insert reverse video before the character
highlighted.insert(pos, "\033[7m");
// Insert reset after the character (accounting for the inserted escape sequence)
if (pos + 5 < static_cast<int>(highlighted.length())) {
highlighted.insert(pos + 5, "\033[0m");
}
}
line = highlighted;
}
std::cout << line << std::endl;
// Add separator lines after rows 2 and 5
if (row == 2 || row == 5) {
std::cout << getSeparatorLine() << std::endl;
}
}
std::cout << std::endl;
// Render status
std::string status = sudoku_.isSolved() ? "SOLVED!" :
sudoku_.isValid() ? "Valid puzzle" : "Invalid puzzle";
std::cout << centerText(status, GRID_WIDTH) << std::endl;
// Restore cursor position
std::cout << "\033[u";
std::cout.flush();
}
void SudokuRenderer::showSolvingProgress(const std::string& status) {
if (!space_allocated_) return;
// Move to status line (last line of allocated space)
std::cout << "\033[s";
moveCursorDown(allocated_lines_ - 1);
clearLine();
std::string display_status = status.empty() ? "Solving..." : status;
std::cout << centerText(display_status, GRID_WIDTH);
std::cout << "\033[u";
std::cout.flush();
}
void SudokuRenderer::animateCell(int row, int col, uint8_t value, int delay_ms) {
// Highlight the cell
renderWithHighlight(row, col);
sleep(delay_ms);
// Render normally
render();
}
std::string SudokuRenderer::formatSudokuLine(int row) const {
std::ostringstream oss;
for (int col = 0; col < 9; ++col) {
uint8_t value = sudoku_.get(row, col);
oss << getCellChar(value);
if (col < 8) {
oss << " ";
// Add vertical separator after columns 2 and 5
if (col == 2 || col == 5) {
oss << "| ";
}
}
}
return oss.str();
}
std::string SudokuRenderer::getSeparatorLine() const {
return "------+-------+------";
}
char SudokuRenderer::getCellChar(uint8_t value) const {
return (value == 0) ? '.' : static_cast<char>('0' + value);
}
// =============================================================================
// NonogramRenderer Implementation
// =============================================================================
NonogramRenderer::NonogramRenderer(const Nonogram& nonogram) : nonogram_(nonogram) {}
void NonogramRenderer::allocateSpace() {
if (space_allocated_) return;
int height = calculateHeight();
// Print empty lines to reserve space
for (int i = 0; i < height; ++i) {
std::cout << std::endl;
}
allocated_lines_ = height;
space_allocated_ = true;
// Move cursor back to start of allocated space
moveCursorUp(height);
}
void NonogramRenderer::render() {
if (!space_allocated_) {
allocateSpace();
}
// Save cursor position
std::cout << "\033[s";
// Render title
std::string title = "NONOGRAM PUZZLE";
int width = calculateWidth();
std::cout << centerText(title, width) << std::endl;
std::cout << centerText(repeatChar('=', title.length()), width) << std::endl;
std::cout << std::endl;
// Render column hints
std::cout << formatColumnHints() << std::endl;
std::cout << std::endl;
// Render grid with row hints
for (size_t row = 0; row < nonogram_.getHeight(); ++row) {
std::cout << formatNonogramLine(static_cast<int>(row)) << std::endl;
}
std::cout << std::endl;
// Render status
std::string status = "Nonogram loaded";
std::cout << centerText(status, width) << std::endl;
// Restore cursor position
std::cout << "\033[u";
std::cout.flush();
}
void NonogramRenderer::clear() {
if (!space_allocated_) return;
// Move to start of allocated space
std::cout << "\033[s";
// Clear all lines
for (int i = 0; i < allocated_lines_; ++i) {
clearLine();
if (i < allocated_lines_ - 1) {
moveCursorDown(1);
}
}
// Restore cursor position
std::cout << "\033[u";
std::cout.flush();
}
void NonogramRenderer::renderWithState(const std::vector<std::vector<int>>& state) {
if (!space_allocated_) {
allocateSpace();
}
// Save cursor position
std::cout << "\033[s";
// Render title
std::string title = "NONOGRAM PUZZLE";
int width = calculateWidth();
std::cout << centerText(title, width) << std::endl;
std::cout << centerText(repeatChar('=', title.length()), width) << std::endl;
std::cout << std::endl;
// Render column hints
std::cout << formatColumnHints() << std::endl;
std::cout << std::endl;
// Render grid with current state
for (size_t row = 0; row < nonogram_.getHeight(); ++row) {
std::cout << formatNonogramLine(static_cast<int>(row), &state) << std::endl;
}
std::cout << std::endl;
// Render status
std::string status = "Solving...";
std::cout << centerText(status, width) << std::endl;
// Restore cursor position
std::cout << "\033[u";
std::cout.flush();
}
void NonogramRenderer::showSolvingProgress(const std::string& status) {
if (!space_allocated_) return;
// Move to status line
std::cout << "\033[s";
moveCursorDown(allocated_lines_ - 1);
clearLine();
std::string display_status = status.empty() ? "Solving..." : status;
std::cout << centerText(display_status, calculateWidth());
std::cout << "\033[u";
std::cout.flush();
}
std::string NonogramRenderer::formatNonogramLine(int row, const std::vector<std::vector<int>>* state) const {
std::ostringstream oss;
// Add row hints (right-aligned in a fixed width)
auto hints = nonogram_.getRowHints(row);
std::ostringstream hints_oss;
for (size_t i = 0; i < hints.size(); ++i) {
if (i > 0) hints_oss << " ";
hints_oss << static_cast<int>(hints[i]);
}
std::string hints_str = hints_oss.str();
// Right-align hints in 8 character field
oss << std::setw(8) << hints_str << " | ";
// Add grid cells
for (size_t col = 0; col < nonogram_.getWidth(); ++col) {
char cell_char = '?';
if (state && row < static_cast<int>(state->size()) && col < state->at(row).size()) {
int cell_state = state->at(row)[col];
if (cell_state == 1) cell_char = '#';
else if (cell_state == 0) cell_char = '.';
} else if (nonogram_.hasSolution()) {
cell_char = nonogram_.getSolutionCell(row, col) ? '#' : '.';
}
oss << cell_char;
if (col < nonogram_.getWidth() - 1) oss << " ";
}
return oss.str();
}
std::string NonogramRenderer::formatColumnHints() const {
std::ostringstream oss;
// Find max hint height for columns
int max_hint_height = 0;
for (size_t col = 0; col < nonogram_.getColumnCount(); ++col) {
auto hints = nonogram_.getColumnHints(col);
max_hint_height = std::max(max_hint_height, static_cast<int>(hints.size()));
}
// Render column hints from top to bottom
for (int hint_row = 0; hint_row < max_hint_height; ++hint_row) {
oss << std::setw(8) << " " << " | "; // Space for row hints
for (size_t col = 0; col < nonogram_.getColumnCount(); ++col) {
auto hints = nonogram_.getColumnHints(col);
// Calculate which hint to show (from top)
int hint_index = hint_row - (max_hint_height - static_cast<int>(hints.size()));
if (hint_index >= 0 && hint_index < static_cast<int>(hints.size())) {
oss << static_cast<int>(hints[hint_index]);
} else {
oss << " ";
}
if (col < nonogram_.getColumnCount() - 1) oss << " ";
}
if (hint_row < max_hint_height - 1) oss << std::endl;
}
return oss.str();
}
int NonogramRenderer::calculateWidth() const {
return 10 + static_cast<int>(nonogram_.getWidth()) * 2; // hints + grid
}
int NonogramRenderer::calculateHeight() const {
// Title (2) + column hints (variable) + grid + status (3)
int max_col_hints = 0;
for (size_t col = 0; col < nonogram_.getColumnCount(); ++col) {
auto hints = nonogram_.getColumnHints(col);
max_col_hints = std::max(max_col_hints, static_cast<int>(hints.size()));
}
return 2 + max_col_hints + 1 + static_cast<int>(nonogram_.getHeight()) + 3;
}
// =============================================================================
// DemoAnimator Implementation
// =============================================================================
void DemoAnimator::typewriterEffect(const std::string& text, int delay_ms) {
for (char c : text) {
std::cout << c;
std::cout.flush();
ConsoleRenderer::sleep(delay_ms);
}
}
void DemoAnimator::fadeIn(const std::vector<std::string>& lines, int delay_ms) {
for (const auto& line : lines) {
std::cout << line << std::endl;
ConsoleRenderer::sleep(delay_ms);
}
}
void DemoAnimator::progressBar(const std::string& label, int current, int total, int width) {
double percentage = static_cast<double>(current) / total;
int filled = static_cast<int>(percentage * width);
std::cout << "\r" << label << " [";
for (int i = 0; i < width; ++i) {
if (i < filled) {
std::cout << "=";
} else if (i == filled) {
std::cout << ">";
} else {
std::cout << " ";
}
}
std::cout << "] " << static_cast<int>(percentage * 100) << "%";
std::cout.flush();
}

View File

@@ -1,130 +0,0 @@
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <chrono>
#include <thread>
/**
* Base Console Renderer
*
* Provides utilities for console-based rendering with cursor control,
* allowing for real-time updates without adding new lines.
*
* Key features:
* - Allocate screen space before rendering
* - Move cursor to update specific areas
* - Clear and redraw content in place
* - Cross-platform cursor control
*/
class ConsoleRenderer {
public:
ConsoleRenderer() = default;
virtual ~ConsoleRenderer() = default;
// Core rendering interface
virtual void allocateSpace() = 0;
virtual void render() = 0;
virtual void clear() = 0;
// Cursor control utilities
static void moveCursorUp(int lines);
static void moveCursorDown(int lines);
static void moveCursorToColumn(int col);
static void moveCursorToPosition(int row, int col);
static void clearLine();
static void clearScreen();
static void hideCursor();
static void showCursor();
// Helper functions
static void sleep(int milliseconds);
static std::string repeatChar(char c, int count);
static std::string centerText(const std::string& text, int width);
protected:
int allocated_lines_ = 0;
bool space_allocated_ = false;
};
/**
* Sudoku Console Renderer
*
* Renders Sudoku puzzles with proper grid formatting and supports
* real-time updates for solving animations.
*
* Usage:
* SudokuRenderer renderer(sudoku);
* renderer.allocateSpace(); // Reserve console space
* renderer.render(); // Initial render
*
* // Update sudoku state and re-render
* sudoku.set(0, 0, 5);
* renderer.render(); // Updates in place
*/
class SudokuRenderer : public ConsoleRenderer {
public:
explicit SudokuRenderer(const class Sudoku& sudoku);
void allocateSpace() override;
void render() override;
void clear() override;
// Sudoku-specific methods
void renderWithHighlight(int highlight_row = -1, int highlight_col = -1);
void showSolvingProgress(const std::string& status = "");
// Animation support
void animateCell(int row, int col, uint8_t value, int delay_ms = 100);
private:
const class Sudoku& sudoku_;
// Rendering helpers
std::string formatSudokuLine(int row) const;
std::string getSeparatorLine() const;
char getCellChar(uint8_t value) const;
// Constants for formatting
static constexpr int GRID_WIDTH = 21; // "1 2 3 | 4 5 6 | 7 8 9"
static constexpr int GRID_HEIGHT = 11; // 9 rows + 2 separators
static constexpr int TOTAL_HEIGHT = 15; // Grid + title + status
};
/**
* Nonogram Console Renderer
*
* Renders Nonogram puzzles with hints and solution grid.
* Supports real-time updates for solving animations.
*/
class NonogramRenderer : public ConsoleRenderer {
public:
explicit NonogramRenderer(const class Nonogram& nonogram);
void allocateSpace() override;
void render() override;
void clear() override;
// Nonogram-specific methods
void renderWithState(const std::vector<std::vector<int>>& state);
void showSolvingProgress(const std::string& status = "");
private:
const class Nonogram& nonogram_;
// Rendering helpers
std::string formatNonogramLine(int row, const std::vector<std::vector<int>>* state = nullptr) const;
std::string formatColumnHints() const;
int calculateWidth() const;
int calculateHeight() const;
};
// Utility class for demo animations
class DemoAnimator {
public:
static void typewriterEffect(const std::string& text, int delay_ms = 50);
static void fadeIn(const std::vector<std::string>& lines, int delay_ms = 100);
static void progressBar(const std::string& label, int current, int total, int width = 30);
};

View File

@@ -48,11 +48,7 @@ if(MSVC)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
endif()
# Create the main executable
add_executable(sudoku_demo
main.cpp
sudoku.cpp
)
# Create WFC demo executable
add_executable(sudoku_wfc_demo
@@ -60,36 +56,19 @@ add_executable(sudoku_wfc_demo
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
)
# Link all executables to the nd-wfc library
target_link_libraries(sudoku_demo PRIVATE nd-wfc)
# Link executable to the nd-wfc library
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
# Ensure consistent runtime library settings for executable
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
)
# Set output directory and properties for sudoku_wfc_demo
set_target_properties(sudoku_wfc_demo PROPERTIES
@@ -98,38 +77,19 @@ set_target_properties(sudoku_wfc_demo PROPERTIES
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 (current source directory for local headers)
target_include_directories(sudoku_demo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(sudoku_wfc_demo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(analyze_failing_puzzles PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(debug_failing_puzzles PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
# Optional: Enable optimizations for release builds
if(CMAKE_BUILD_TYPE STREQUAL "Release")
if(MSVC)
target_compile_options(sudoku_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()
target_compile_options(sudoku_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()
@@ -193,9 +153,9 @@ endif()
# Installation (optional)
if(HAS_GTEST AND HAS_BENCHMARK)
install(TARGETS sudoku_demo sudoku_wfc_demo analyze_failing_puzzles debug_failing_puzzles sudoku_tests sudoku_benchmarks DESTINATION bin)
install(TARGETS sudoku_wfc_demo sudoku_tests sudoku_benchmarks DESTINATION bin)
elseif(HAS_GTEST)
install(TARGETS sudoku_demo sudoku_wfc_demo analyze_failing_puzzles debug_failing_puzzles sudoku_tests DESTINATION bin)
install(TARGETS sudoku_wfc_demo sudoku_tests DESTINATION bin)
else()
install(TARGETS sudoku_demo sudoku_wfc_demo analyze_failing_puzzles debug_failing_puzzles DESTINATION bin)
install(TARGETS sudoku_wfc_demo DESTINATION bin)
endif()

View File

@@ -1,158 +0,0 @@
#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() {
// 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;
}

View File

@@ -1,165 +0,0 @@
#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
// 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;
}

View File

@@ -1,19 +0,0 @@
#include "sudoku.h"
#include <iostream>
int main()
{
std::cout << "Sudoku Demo" << std::endl;
// Create a simple sudoku puzzle
Sudoku sudoku("140000050700200000000300204200080400080090020006050001809001000000006007050000069");
if (sudoku.isValid()) {
std::cout << "Loaded valid sudoku puzzle:" << std::endl;
sudoku.print();
} else {
std::cout << "Invalid sudoku puzzle!" << std::endl;
}
return 0;
}

View File

@@ -6,39 +6,6 @@
#include <cctype>
#include <bitset>
Sudoku::Sudoku() {
clear();
}
Sudoku::Sudoku(const std::string& puzzle_str) {
clear();
loadFromString(puzzle_str);
}
bool Sudoku::loadFromString(const std::string& puzzle_str) {
if (puzzle_str.length() != 81) {
return false;
}
clear();
for (int i = 0; i < 81; ++i) {
char c = puzzle_str[i];
if (c >= '1' && c <= '9') {
int row = i / 9;
int col = i % 9;
uint8_t value = c - '0';
if (!set(row, col, value)) {
return false; // Invalid move
}
} else if (c != '0' && c != '.') {
return false; // Invalid character
}
}
return true;
}
bool Sudoku::loadFromFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
@@ -60,68 +27,11 @@ bool Sudoku::loadFromFile(const std::string& filename) {
return loadFromString(puzzle_str);
}
void Sudoku::clear() {
board_.clear();
}
bool Sudoku::isValid() const {
// Check rows for duplicates using bitset for efficiency
for (int row = 0; row < 9; ++row) {
std::bitset<10> seen; // bits 1-9 track values 1-9
for (int col = 0; col < 9; ++col) {
uint8_t value = get(row, col);
if (value != 0) {
if (seen[value]) return false; // Duplicate found
seen.set(value);
}
}
}
// Check columns for duplicates
for (int col = 0; col < 9; ++col) {
std::bitset<10> seen;
for (int row = 0; row < 9; ++row) {
uint8_t value = get(row, col);
if (value != 0) {
if (seen[value]) return false; // Duplicate found
seen.set(value);
}
}
}
// Check boxes for duplicates
for (int box = 0; box < 9; ++box) {
std::bitset<10> seen;
int startRow = (box / 3) * 3;
int startCol = (box % 3) * 3;
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
uint8_t value = get(startRow + row, startCol + col);
if (value != 0) {
if (seen[value]) return false; // Duplicate found
seen.set(value);
}
}
}
}
return true;
}
bool Sudoku::isSolved() const {
for (int i = 0; i < 81; ++i) {
if (board_.get(i) == 0) return false;
}
return isValid();
}
void Sudoku::print() const {
void Sudoku::print(char emptyVal) const {
for (int row = 0; row < 9; ++row) {
for (int col = 0; col < 9; ++col) {
uint8_t value = get(row, col);
std::cout << (value == 0 ? '.' : static_cast<char>('0' + value));
std::cout << (value == 0 ? emptyVal : static_cast<char>('0' + value));
if (col < 8) std::cout << " ";
if (col == 2 || col == 5) std::cout << "| ";
}
@@ -132,12 +42,12 @@ void Sudoku::print() const {
}
}
std::string Sudoku::toString() const {
std::string Sudoku::toString(char emptyVal) const {
std::string result;
result.reserve(81);
for (int i = 0; i < 81; ++i) {
uint8_t cell = board_.get(i);
result += (cell == 0 ? '.' : static_cast<char>('0' + cell));
result += (cell == 0 ? emptyVal : static_cast<char>('0' + cell));
}
return result;
}

View File

@@ -5,8 +5,12 @@
#include <optional>
#include <cstdint>
#include <array>
#include <cassert>
#include <chrono>
#include <bitset>
#include <algorithm>
#include <cctype>
#include <string_view>
#include <nd-wfc/wfc.hpp>
// 4-bit packed Sudoku board storage - optimal packing
@@ -15,14 +19,14 @@
// 81 cells / 2 = 40.5 bytes → 41 bytes total (with 4 bits unused)
class SudokuBoardStorage {
public:
std::array<uint8_t, 41> data;
std::array<uint8_t, 41> data {};
// Get 4-bit value at position (0-80)
// Each byte contains 2 cells: [cell0(4bits)][cell1(4bits)]
// Ultra-fast: only bitwise operations, no divide/modulo!
// Optimization: pos >> 1 instead of pos / 2
// Optimization: (pos & 1) << 2 instead of (pos % 2) * 4
uint8_t get(int pos) const {
constexpr inline uint8_t get(int pos) const {
int byteIndex = pos >> 1; // pos / 2 using right shift
// Precomputed shift amounts: 4 for even positions, 0 for odd positions
@@ -34,7 +38,7 @@ public:
uint8_t result = (data[byteIndex] >> shiftAmount) & 0xF;
// Debug assertion: ensure result is in valid range
assert(result >= 0 && result <= 9 && "Sudoku cell value must be between 0-9");
WFC::constexpr_assert(result >= 0 && result <= 9, "Sudoku cell value must be between 0-9");
return result;
}
@@ -43,9 +47,9 @@ public:
// Ultra-fast: only bitwise operations, no divide/modulo!
// Optimization: pos >> 1 instead of pos / 2
// Optimization: (pos & 1) << 2 instead of (pos % 2) * 4
void set(int pos, uint8_t value) {
constexpr inline void set(int pos, uint8_t value) {
// Assert that value is in valid Sudoku range (0-9)
assert(value >= 0 && value <= 9 && "Sudoku cell value must be between 0-9");
WFC::constexpr_assert(value >= 0 && value <= 9, "Sudoku cell value must be between 0-9");
int byteIndex = pos >> 1; // pos / 2 using right shift
@@ -59,7 +63,7 @@ public:
data[byteIndex] = (data[byteIndex] & mask) | (value << shiftAmount);
}
void clear() {
constexpr void clear() {
data.fill(0);
}
};
@@ -67,23 +71,49 @@ public:
// Ultra-memory-efficient Sudoku class: exactly 41 bytes
class Sudoku {
public:
Sudoku();
explicit Sudoku(const std::string& puzzle_str);
constexpr Sudoku() = default;
constexpr explicit Sudoku(std::string_view puzzle_str)
{
loadFromString(puzzle_str);
}
// Load from various formats
bool loadFromString(const std::string& puzzle_str);
constexpr bool loadFromString(std::string_view puzzle_str)
{
if (puzzle_str.length() != 81) {
return false;
}
clear();
for (int i = 0; i < 81; ++i) {
char c = puzzle_str[i];
if (c >= '1' && c <= '9') {
int row = i / 9;
int col = i % 9;
uint8_t value = c - '0';
if (!set(row, col, value)) {
return false; // Invalid move
}
} else if (c != '0' && c != '.') {
return false; // Invalid character
}
}
return true;
}
bool loadFromFile(const std::string& filename);
// Board access (inlined for performance)
inline uint8_t get(int row, int col) const {
assert((row >= 0 && row < 9 && col >= 0 && col < 9) &&
constexpr inline uint8_t get(int row, int col) const {
WFC::constexpr_assert((row >= 0 && row < 9 && col >= 0 && col < 9),
"Sudoku::get() called with invalid position - row and col must be 0-8");
int linearIndex = getLinearIndex(row, col);
return board_.get(linearIndex);
}
inline bool set(int row, int col, uint8_t value) {
assert((row >= 0 && row < 9 && col >= 0 && col < 9) &&
constexpr inline bool set(int row, int col, uint8_t value) {
WFC::constexpr_assert((row >= 0 && row < 9 && col >= 0 && col < 9),
"Sudoku::set() called with invalid position - row and col must be 0-8");
// Keep value validation as runtime check since it's about valid Sudoku numbers
@@ -104,14 +134,64 @@ public:
return true;
}
void clear();
constexpr void clear() {
board_.clear();
}
// Validation
bool isValid() const;
bool isSolved() const;
constexpr bool isValid() const {
// Check rows for duplicates using bitset for efficiency
for (int row = 0; row < 9; ++row) {
std::bitset<10> seen; // bits 1-9 track values 1-9
for (int col = 0; col < 9; ++col) {
uint8_t value = get(row, col);
if (value != 0) {
if (seen[value]) return false; // Duplicate found
seen.set(value);
}
}
}
// Check columns for duplicates
for (int col = 0; col < 9; ++col) {
std::bitset<10> seen;
for (int row = 0; row < 9; ++row) {
uint8_t value = get(row, col);
if (value != 0) {
if (seen[value]) return false; // Duplicate found
seen.set(value);
}
}
}
// Check boxes for duplicates
for (int box = 0; box < 9; ++box) {
std::bitset<10> seen;
int startRow = (box / 3) * 3;
int startCol = (box % 3) * 3;
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
uint8_t value = get(startRow + row, startCol + col);
if (value != 0) {
if (seen[value]) return false; // Duplicate found
seen.set(value);
}
}
}
}
return true;
}
constexpr bool isSolved() const {
for (int i = 0; i < 81; ++i) {
if (board_.get(i) == 0) return false;
}
return isValid();
}
// Inlined validation (called frequently from set())
inline bool isValidMove(int row, int col, uint8_t value) const {
constexpr inline bool isValidMove(int row, int col, uint8_t value) const {
if (value == 0 || value > 9) return false;
return !hasRowConflictExcluding(row, col, value) &&
!hasColConflictExcluding(col, row, value) &&
@@ -119,8 +199,8 @@ public:
}
// Utility
void print() const;
std::string toString() const;
void print(char emptyVal = '.') const;
std::string toString(char emptyVal = '.') const;
// Convert to standard board format for external use
std::array<uint8_t, 81> getBoard() const;
@@ -129,35 +209,35 @@ private:
SudokuBoardStorage board_;
// Helper functions (inlined for performance)
inline int getLinearIndex(int row, int col) const {
constexpr inline int getLinearIndex(int row, int col) const {
return row * 9 + col;
}
inline int getBoxIndex(int row, int col) const {
constexpr inline int getBoxIndex(int row, int col) const {
return (row / 3) * 3 + (col / 3);
}
inline bool isValidPosition(int row, int col) const {
constexpr inline bool isValidPosition(int row, int col) const {
return row >= 0 && row < 9 && col >= 0 && col < 9;
}
// Validation helpers (inlined for performance)
// Uses std::bitset<10> for efficient duplicate detection instead of arrays
inline bool hasRowConflict(int row, uint8_t value) const {
constexpr inline bool hasRowConflict(int row, uint8_t value) const {
for (int col = 0; col < 9; ++col) {
if (get(row, col) == value) return true;
}
return false;
}
inline bool hasColConflict(int col, uint8_t value) const {
constexpr inline bool hasColConflict(int col, uint8_t value) const {
for (int row = 0; row < 9; ++row) {
if (get(row, col) == value) return true;
}
return false;
}
inline bool hasBoxConflict(int box, uint8_t value) const {
constexpr inline bool hasBoxConflict(int box, uint8_t value) const {
int startRow = (box / 3) * 3;
int startCol = (box % 3) * 3;
for (int row = 0; row < 3; ++row) {
@@ -169,21 +249,21 @@ private:
}
// Validation helpers that exclude current position (for move validation)
inline bool hasRowConflictExcluding(int row, int excludeCol, uint8_t value) const {
constexpr inline bool hasRowConflictExcluding(int row, int excludeCol, uint8_t value) const {
for (int col = 0; col < 9; ++col) {
if (col != excludeCol && get(row, col) == value) return true;
}
return false;
}
inline bool hasColConflictExcluding(int col, int excludeRow, uint8_t value) const {
constexpr inline bool hasColConflictExcluding(int col, int excludeRow, uint8_t value) const {
for (int row = 0; row < 9; ++row) {
if (row != excludeRow && get(row, col) == value) return true;
}
return false;
}
inline bool hasBoxConflictExcluding(int box, int excludeRow, int excludeCol, uint8_t value) const {
constexpr inline bool hasBoxConflictExcluding(int box, int excludeRow, int excludeCol, uint8_t value) const {
int startRow = (box / 3) * 3;
int startCol = (box % 3) * 3;
for (int row = 0; row < 3; ++row) {
@@ -199,15 +279,15 @@ private:
public: // WFC Support
using ValueType = uint8_t;
ValueType getValue(size_t index) const {
constexpr inline ValueType getValue(size_t index) const {
return board_.get(static_cast<int>(index));
}
void setValue(size_t index, ValueType value) {
constexpr inline void setValue(size_t index, ValueType value) {
board_.set(static_cast<int>(index), value);
}
constexpr size_t size() const {
constexpr inline size_t size() const {
return 81;
}
};
@@ -234,8 +314,6 @@ public:
private:
static bool parseLine(const std::string& line, std::array<uint8_t, 81>& board);
};
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) {
@@ -268,4 +346,5 @@ using SudokuSolverBuilder = WFC::Builder<Sudoku>
}), 1, 2, 3, 4, 5, 6, 7, 8, 9>;
using SudokuSolver = SudokuSolverBuilder::Build;
using SudokuSolver = SudokuSolverBuilder::Build;

View File

@@ -58,11 +58,16 @@ using SudokuSolverCallback = SudokuSolverBuilder::SetCellCollapsedCallback<declt
})>
::Build;
Sudoku GetWorldConsteval()
{
return Sudoku{ "6......3.......7....7463....7.8...2.4...9...1.9...7.8....9851....6.......1......9" };
}
int main()
{
std::cout << "Running Sudoku WFC" << std::endl;
Sudoku sudokuWorld{ "040280030010006007609070008000092000900000004000740000500020803400800010070035090" };
Sudoku sudokuWorld = GetWorldConsteval();
bool success = SudokuSolverCallback::Run(sudokuWorld, true);
@@ -84,5 +89,5 @@ int main()
if (y == 2 || y == 5) std::cout << "------+-------+------" << std::endl;
}
return (success && solved) ? 0 : 1;
}

Binary file not shown.

Binary file not shown.

View File

@@ -4,22 +4,17 @@
#include <nd-wfc/wfc.hpp>
#include <fstream>
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
// Forward declaration for helper function
// Forward declarations for helper functions
std::vector<Sudoku> loadPuzzlesFromFile(const std::string& filename);
void testPuzzleSolving(const std::string& difficulty, const std::string& filename);
// Test fixture for Sudoku tests
class SudokuTest : public ::testing::Test {
protected:
void SetUp() override {
// Common test setup
}
void TearDown() override {
// Common test cleanup
}
// Helper function to create a solved Sudoku
Sudoku createSolvedSudoku() {
Sudoku sudoku;
@@ -36,9 +31,9 @@ protected:
return sudoku;
}
Sudoku SolvePuzzle(Sudoku& sudoku) {
// Helper function to solve a puzzle using WFC
void solvePuzzle(Sudoku& sudoku) {
SudokuSolver::Run(sudoku, true);
return sudoku;
}
};
@@ -246,7 +241,7 @@ TEST_F(SudokuTest, PerformanceSetOperations) {
int row = i % 9;
int col = (i / 9) % 9;
int value = (i % 9) + 1;
sudoku.set(row, col, value);
sudoku.set(row, col, static_cast<uint8_t>(value));
}
auto end = std::chrono::high_resolution_clock::now();
@@ -272,173 +267,82 @@ TEST_F(SudokuTest, EdgeCases) {
EXPECT_EQ(sudoku.get(8, 8), 4);
}
TEST_F(SudokuTest, WFCIntegration)
{
auto sudoku = createEasyPuzzle();
SudokuSolver::Run(sudoku, true);
TEST_F(SudokuTest, WFCIntegration) {
Sudoku sudoku = createEasyPuzzle();
solvePuzzle(sudoku);
EXPECT_TRUE(sudoku.isSolved());
}
// Tests loading and solving puzzles from data files
TEST_F(SudokuTest, LoadAndSolveEasyPuzzles)
{
std::vector<Sudoku> easyPuzzles = loadPuzzlesFromFile("../data/Sudoku_easy.txt");
// Unified function to test puzzle solving for different difficulty levels
void testPuzzleSolving(const std::string& difficulty, const std::string& filename) {
std::vector<Sudoku> puzzles = loadPuzzlesFromFile(filename);
ASSERT_GT(easyPuzzles.size(), 0) << "No easy puzzles loaded";
ASSERT_GT(puzzles.size(), 0) << "No " << difficulty << " 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";
WFC::WFCStackAllocator allocator{};
for (size_t i = 0; i < puzzles.size(); ++i) {
Sudoku& sudoku = puzzles[i];
EXPECT_TRUE(sudoku.isValid()) << difficulty << " puzzle " << i << " is not valid";
auto puzzleStart = std::chrono::high_resolution_clock::now();
SudokuSolver::Run(sudoku, true);
SudokuSolver::Run(sudoku, allocator);
auto puzzleEnd = std::chrono::high_resolution_clock::now();
EXPECT_TRUE(sudoku.isSolved()) << "Puzzle " << i << " was not solved";
EXPECT_TRUE(sudoku.isSolved()) << difficulty << " puzzle " << i << " was not solved. Puzzle string: " << sudoku.toString();
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;
// give progress every max(100, puzzles.size() / 100) puzzles and output percentage complete
if (i % std::max<size_t>(100, puzzles.size() / 100) == 0) {
std::cout << difficulty << " puzzles: solved " << solvedCount << "/" << puzzles.size()
<< " in " << std::chrono::duration_cast<std::chrono::seconds>(std::chrono::high_resolution_clock::now() - start).count() << " seconds" << std::endl;
std::cout << "Percentage complete: " << static_cast<int>(static_cast<double>(i) / puzzles.size() * 100.0) << "%" << 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()
std::cout << difficulty << " puzzles: solved " << solvedCount << "/" << puzzles.size()
<< " in " << totalDuration.count() << " seconds" << std::endl;
EXPECT_EQ(solvedCount, easyPuzzles.size()) << "Not all easy puzzles were solved";
EXPECT_EQ(solvedCount, puzzles.size()) << "Not all " << difficulty << " puzzles were solved";
}
TEST_F(SudokuTest, LoadAndSolveMediumPuzzles)
{
std::vector<Sudoku> mediumPuzzles = loadPuzzlesFromFile("../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();
SudokuSolver::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";
// Tests loading and solving puzzles from data files using the unified function
TEST_F(SudokuTest, LoadAndSolveEasyPuzzles) {
testPuzzleSolving("Easy", "../data/Sudoku_easy.txt");
}
TEST_F(SudokuTest, LoadAndSolveHardPuzzles)
{
std::vector<Sudoku> hardPuzzles = loadPuzzlesFromFile("../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();
SudokuSolver::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, LoadAndSolveMediumPuzzles) {
testPuzzleSolving("Medium", "../data/Sudoku_medium.txt");
}
TEST_F(SudokuTest, LoadAndSolveDiabolicalPuzzles)
{
std::vector<Sudoku> diabolicalPuzzles = loadPuzzlesFromFile("../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();
SudokuSolver::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_F(SudokuTest, LoadAndSolveHardPuzzles) {
testPuzzleSolving("Hard", "../data/Sudoku_hard.txt");
}
// Test loading a single puzzle from each difficulty file
TEST_F(SudokuTest, LoadAndSolveFirstPuzzleFromEachFile)
{
TEST_F(SudokuTest, LoadAndSolveDiabolicalPuzzles) {
testPuzzleSolving("Diabolical", "../data/Sudoku_diabolical.txt");
}
// Test loading and solving the first puzzle from each difficulty file
TEST_F(SudokuTest, LoadAndSolveFirstPuzzleFromEachFile) {
const std::string dataPath = "../data";
const std::vector<std::string> files = {"Sudoku_easy.txt", "Sudoku_medium.txt", "Sudoku_hard.txt", "Sudoku_diabolical.txt"};
const std::vector<std::pair<std::string, std::string>> fileTests = {
{"Sudoku_easy.txt", "Easy"},
{"Sudoku_medium.txt", "Medium"},
{"Sudoku_hard.txt", "Hard"},
{"Sudoku_diabolical.txt", "Diabolical"}
};
for (const auto& filename : files) {
for (const auto& [filename, difficulty] : fileTests) {
std::string filepath = dataPath + "/" + filename;
std::ifstream file(filepath);
@@ -459,13 +363,13 @@ TEST_F(SudokuTest, LoadAndSolveFirstPuzzleFromEachFile)
EXPECT_TRUE(puzzle.isValid()) << "Loaded puzzle from " << filename << " is not valid";
auto start = std::chrono::high_resolution_clock::now();
SudokuSolver::Run(puzzle, true);
solvePuzzle(puzzle);
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;
std::cout << "First " << difficulty << " puzzle solved in " << duration.count() << "ms" << std::endl;
}
}

Binary file not shown.

View File

@@ -1,188 +0,0 @@
#include "console_renderer.h"
#include "sudoku/sudoku.h"
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
/**
* Sudoku Demo with Real-time Console Rendering
*
* This demo shows how to use the SudokuRenderer to display and animate
* a Sudoku puzzle being solved in real-time without adding new lines.
*/
void demonstrateSudokuRendering() {
std::cout << "=== SUDOKU CONSOLE RENDERING DEMO ===" << std::endl;
std::cout << "This demo will show a Sudoku puzzle and simulate solving it." << std::endl;
std::cout << "Press Enter to continue..." << std::endl;
std::cin.get();
// Clear screen for clean demo
ConsoleRenderer::clearScreen();
// Create a sample Sudoku puzzle
std::string puzzle = "530070000600195000098000060800060003400803001700020006060000280000419005000080079";
Sudoku sudoku(puzzle);
// Create renderer
SudokuRenderer renderer(sudoku);
// Allocate space first (this is crucial!)
std::cout << "Allocating console space..." << std::endl;
ConsoleRenderer::sleep(1000);
renderer.allocateSpace();
// Initial render
std::cout << "Rendering initial puzzle..." << std::endl;
ConsoleRenderer::sleep(500);
renderer.render();
// Wait a moment
ConsoleRenderer::sleep(2000);
// Simulate solving some cells with animation
std::vector<std::tuple<int, int, uint8_t>> moves = {
{0, 2, 1}, {0, 5, 8}, {0, 7, 4}, {0, 8, 2},
{1, 0, 2}, {1, 3, 4}, {1, 6, 7}, {1, 8, 3},
{2, 2, 7}, {2, 4, 3}, {2, 5, 2}, {2, 7, 6},
{3, 1, 9}, {3, 3, 5}, {3, 6, 2}, {3, 8, 7},
{4, 0, 5}, {4, 2, 6}, {4, 6, 9}, {4, 8, 8}
};
renderer.showSolvingProgress("Starting to solve...");
ConsoleRenderer::sleep(1000);
for (size_t i = 0; i < moves.size(); ++i) {
auto [row, col, value] = moves[i];
// Update status
std::string status = "Solving step " + std::to_string(i + 1) + " of " + std::to_string(moves.size());
renderer.showSolvingProgress(status);
// Set the value
sudoku.set(row, col, value);
// Animate the change
renderer.animateCell(row, col, value, 200);
// Small delay between moves
ConsoleRenderer::sleep(300);
}
// Final render
renderer.showSolvingProgress("Partial solution completed!");
ConsoleRenderer::sleep(2000);
// Show final state
if (sudoku.isSolved()) {
renderer.showSolvingProgress("PUZZLE SOLVED!");
} else {
renderer.showSolvingProgress("Partial solution (demo complete)");
}
ConsoleRenderer::sleep(2000);
}
void demonstrateRealTimeUpdates() {
std::cout << "\n=== REAL-TIME UPDATE DEMO ===" << std::endl;
std::cout << "This shows how to update the puzzle without adding lines." << std::endl;
std::cout << "Press Enter to continue..." << std::endl;
std::cin.get();
ConsoleRenderer::clearScreen();
// Create empty sudoku
Sudoku sudoku;
SudokuRenderer renderer(sudoku);
// Allocate and render
renderer.allocateSpace();
renderer.render();
ConsoleRenderer::sleep(1000);
// Add numbers one by one
std::vector<std::tuple<int, int, uint8_t>> sequence = {
{0, 0, 5}, {0, 1, 3}, {0, 4, 7},
{1, 0, 6}, {1, 3, 1}, {1, 4, 9}, {1, 5, 5},
{2, 1, 9}, {2, 2, 8}, {2, 7, 6},
{3, 0, 8}, {3, 4, 6}, {3, 8, 3},
{4, 0, 4}, {4, 3, 8}, {4, 5, 3}, {4, 8, 1}
};
for (const auto& [row, col, value] : sequence) {
renderer.showSolvingProgress("Adding number " + std::to_string(value) +
" at (" + std::to_string(row + 1) + "," + std::to_string(col + 1) + ")");
sudoku.set(row, col, value);
renderer.renderWithHighlight(row, col);
ConsoleRenderer::sleep(800);
renderer.render();
ConsoleRenderer::sleep(200);
}
renderer.showSolvingProgress("Real-time update demo complete!");
ConsoleRenderer::sleep(2000);
}
void showAPIUsage() {
std::cout << "\n=== API USAGE EXAMPLE ===" << std::endl;
std::cout << "Here's how to use the SudokuRenderer API:" << std::endl;
std::cout << std::endl;
std::cout << "// 1. Create a Sudoku puzzle" << std::endl;
std::cout << "Sudoku sudoku(\"530070000600195000...\");" << std::endl;
std::cout << std::endl;
std::cout << "// 2. Create renderer" << std::endl;
std::cout << "SudokuRenderer renderer(sudoku);" << std::endl;
std::cout << std::endl;
std::cout << "// 3. IMPORTANT: Allocate space first!" << std::endl;
std::cout << "renderer.allocateSpace();" << std::endl;
std::cout << std::endl;
std::cout << "// 4. Render initial state" << std::endl;
std::cout << "renderer.render();" << std::endl;
std::cout << std::endl;
std::cout << "// 5. Update puzzle and re-render" << std::endl;
std::cout << "sudoku.set(0, 0, 5);" << std::endl;
std::cout << "renderer.render(); // Updates in place!" << std::endl;
std::cout << std::endl;
std::cout << "// 6. Optional: Use animations" << std::endl;
std::cout << "renderer.animateCell(row, col, value);" << std::endl;
std::cout << "renderer.showSolvingProgress(\"Status\");" << std::endl;
std::cout << std::endl;
std::cout << "Press Enter to continue..." << std::endl;
std::cin.get();
}
int main() {
try {
// Hide cursor for cleaner display
ConsoleRenderer::hideCursor();
// Run demonstrations
showAPIUsage();
demonstrateSudokuRendering();
demonstrateRealTimeUpdates();
// Show cursor again
ConsoleRenderer::showCursor();
std::cout << "\n=== DEMO COMPLETE ===" << std::endl;
std::cout << "The SudokuRenderer provides a clean API for real-time puzzle rendering!" << std::endl;
} catch (const std::exception& e) {
ConsoleRenderer::showCursor();
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}

View File

@@ -1,33 +0,0 @@
set(EXAMPLE_SOURCES
)
# Create executables for each example
foreach(EXAMPLE_SOURCE ${EXAMPLE_SOURCES})
get_filename_component(EXAMPLE_NAME ${EXAMPLE_SOURCE} NAME_WE)
add_executable(${EXAMPLE_NAME} ${EXAMPLE_SOURCE})
target_link_libraries(${EXAMPLE_NAME}
PRIVATE
nd-wfc
# SDL3::SDL3 # Will be enabled when SDL3 is needed
)
target_include_directories(${EXAMPLE_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../include
)
set_target_properties(${EXAMPLE_NAME} PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/examples
)
endforeach()
# Copy resources if they exist
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/resources)
add_custom_target(copy-example-resources
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_BINARY_DIR}/examples/resources
)
# Dependencies for resource copying would go here if needed
endif()

View File

@@ -18,8 +18,12 @@
namespace WFC {
inline constexpr void constexpr_assert(bool condition, const char* message = "") {
if (!condition) throw message;
}
inline int FindNthSetBit(size_t num, int n) {
assert(n < std::popcount(num) && "index is out of range");
constexpr_assert(n < std::popcount(num), "index is out of range");
int bitCount = 0;
while (num) {
if (bitCount == n) {
@@ -111,7 +115,7 @@ public:
}
static VarT GetValue(size_t index) {
assert(index < ValuesRegisteredAmount);
constexpr_assert(index < ValuesRegisteredAmount);
return GetAllValues()[index];
}
@@ -374,34 +378,40 @@ public:
WFC() = delete; // dont make an instance of this class, only use the static methods.
public:
static bool Run(WorldT& world, bool propagateInitialValues = true, WFCStackAllocator* allocator = nullptr)
static bool Run(WorldT& world, uint32_t seed = std::random_device{}())
{
//std::mt19937 random{ 212 };
std::mt19937 random{ std::random_device{}() };
WFCStackAllocator allocator{};
return Run(world, allocator, seed);
}
static bool Run(WorldT& world, WFCStackAllocator& allocator, uint32_t seed = std::random_device{}())
{
allocator.reset();
constexpr_assert(allocator.getUsed() == 0, "Allocator must be empty");
size_t iterations = 0;
if (!allocator)
auto random = std::mt19937{ seed };
SolverState state
{
WFCStackAllocator newAllocator{};
SolverState state(world, ConstrainerFunctionMapT::size(), random, newAllocator, iterations);
return Run(state, propagateInitialValues);
}
else
{
SolverState state(world, ConstrainerFunctionMapT::size(), random, *allocator, iterations);
return Run(state, propagateInitialValues);
}
world,
ConstrainerFunctionMapT::size(),
random,
allocator,
iterations
};
return Run(state);
allocator.reset();
constexpr_assert(allocator.getUsed() == 0, "Allocator must be empty");
}
/**
* @brief Run the WFC algorithm to generate a solution
* @return true if a solution was found, false if contradiction occurred
*/
static bool Run(SolverState& state, bool propagateInitialValues = true)
static bool Run(SolverState& state)
{
if (propagateInitialValues)
{
PropogateInitialValues(state);
}
PropogateInitialValues(state);
if (RunLoop(state)) {
@@ -413,7 +423,7 @@ public:
static bool RunLoop(SolverState& state)
{
for (; state.iterations < 1024; ++state.iterations)
for (; state.iterations < 1024 * 8; ++state.iterations)
{
if (!Propagate(state))
return false;
@@ -474,9 +484,9 @@ public:
private:
static void CollapseCell(SolverState& state, size_t cellId, uint16_t value)
{
assert(!state.wave.IsCollapsed(cellId) || state.wave.GetMask(cellId) == (1 << value));
constexpr_assert(!state.wave.IsCollapsed(cellId) || state.wave.GetMask(cellId) == (1 << value));
state.wave.Collapse(cellId, 1 << value);
assert(state.wave.IsCollapsed(cellId));
constexpr_assert(state.wave.IsCollapsed(cellId));
if constexpr (CallbacksT::HasCellCollapsedCallback())
{
@@ -487,7 +497,7 @@ private:
static bool Branch(SolverState& state)
{
assert(state.propagationQueue.empty());
constexpr_assert(state.propagationQueue.empty());
// Find cell with minimum entropy > 1
size_t minEntropyCell = static_cast<size_t>(-1);
@@ -502,7 +512,7 @@ private:
}
if (minEntropyCell == static_cast<size_t>(-1)) return false;
assert(!state.wave.IsCollapsed(minEntropyCell));
constexpr_assert(!state.wave.IsCollapsed(minEntropyCell));
// create a list of possible values
uint16_t availableValues = static_cast<uint16_t>(state.wave.Entropy(minEntropyCell));
@@ -511,10 +521,10 @@ private:
for (size_t i = 0; i < availableValues; ++i)
{
uint16_t index = static_cast<uint16_t>(std::countr_zero(mask)); // get the index of the lowest set bit
assert(index < VariableIDMapT::ValuesRegisteredAmount && "Possible value went outside bounds");
constexpr_assert(index < VariableIDMapT::ValuesRegisteredAmount, "Possible value went outside bounds");
possibleValues[i] = index;
assert(((mask & (1 << index)) != 0) && "Possible value was not set");
constexpr_assert(((mask & (1 << index)) != 0), "Possible value was not set");
mask = mask & (mask - 1); // turn off lowest set bit
}
@@ -543,9 +553,9 @@ private:
}
// remove the failure state from the wave
assert((state.wave.GetMask(minEntropyCell) & (1 << selectedValue)) != 0 && "Possible value was not set");
constexpr_assert((state.wave.GetMask(minEntropyCell) & (1 << selectedValue)) != 0, "Possible value was not set");
state.wave.Collapse(minEntropyCell, ~(1 << selectedValue));
assert((state.wave.GetMask(minEntropyCell) & (1 << selectedValue)) == 0 && "Wave was not collapsed correctly");
constexpr_assert((state.wave.GetMask(minEntropyCell) & (1 << selectedValue)) == 0, "Wave was not collapsed correctly");
// swap replacement value with the last value
std::swap(possibleValues[randomIndex], possibleValues[--availableValues]);
@@ -563,7 +573,7 @@ private:
if (state.wave.IsContradicted(cellId)) return false;
assert(state.wave.IsCollapsed(cellId) && "Cell was not collapsed");
constexpr_assert(state.wave.IsCollapsed(cellId), "Cell was not collapsed");
uint16_t variableID = state.wave.GetVariableID(cellId);
Constrainer<VariableIDMapT> constrainer(state.wave, state.propagationQueue);

View File

@@ -31,7 +31,7 @@ target_link_libraries(nd-wfc-tests
# Set C++ standard
set_target_properties(nd-wfc-tests PROPERTIES
CXX_STANDARD 17
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
)