fixed tests
This commit is contained in:
@@ -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
|
|
||||||
)
|
|
||||||
267
demos/README.md
267
demos/README.md
@@ -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!
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
};
|
|
||||||
@@ -48,11 +48,7 @@ if(MSVC)
|
|||||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Create the main executable
|
|
||||||
add_executable(sudoku_demo
|
|
||||||
main.cpp
|
|
||||||
sudoku.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create WFC demo executable
|
# Create WFC demo executable
|
||||||
add_executable(sudoku_wfc_demo
|
add_executable(sudoku_wfc_demo
|
||||||
@@ -60,36 +56,19 @@ 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
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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(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)
|
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(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()
|
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 output directory and properties for sudoku_wfc_demo
|
||||||
set_target_properties(sudoku_wfc_demo PROPERTIES
|
set_target_properties(sudoku_wfc_demo PROPERTIES
|
||||||
@@ -98,38 +77,19 @@ 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 (current source directory for local headers)
|
# 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(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
|
# 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_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_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()
|
||||||
|
|
||||||
@@ -193,9 +153,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 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)
|
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()
|
else()
|
||||||
install(TARGETS sudoku_demo sudoku_wfc_demo analyze_failing_puzzles debug_failing_puzzles DESTINATION bin)
|
install(TARGETS sudoku_wfc_demo DESTINATION bin)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -6,39 +6,6 @@
|
|||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <bitset>
|
#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) {
|
bool Sudoku::loadFromFile(const std::string& filename) {
|
||||||
std::ifstream file(filename);
|
std::ifstream file(filename);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
@@ -60,68 +27,11 @@ bool Sudoku::loadFromFile(const std::string& filename) {
|
|||||||
return loadFromString(puzzle_str);
|
return loadFromString(puzzle_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Sudoku::print(char emptyVal) const {
|
||||||
|
|
||||||
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 {
|
|
||||||
for (int row = 0; row < 9; ++row) {
|
for (int row = 0; row < 9; ++row) {
|
||||||
for (int col = 0; col < 9; ++col) {
|
for (int col = 0; col < 9; ++col) {
|
||||||
uint8_t value = get(row, 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 < 8) std::cout << " ";
|
||||||
if (col == 2 || col == 5) 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;
|
std::string result;
|
||||||
result.reserve(81);
|
result.reserve(81);
|
||||||
for (int i = 0; i < 81; ++i) {
|
for (int i = 0; i < 81; ++i) {
|
||||||
uint8_t cell = board_.get(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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,12 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cassert>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <bitset>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
#include <nd-wfc/wfc.hpp>
|
#include <nd-wfc/wfc.hpp>
|
||||||
|
|
||||||
// 4-bit packed Sudoku board storage - optimal packing
|
// 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)
|
// 81 cells / 2 = 40.5 bytes → 41 bytes total (with 4 bits unused)
|
||||||
class SudokuBoardStorage {
|
class SudokuBoardStorage {
|
||||||
public:
|
public:
|
||||||
std::array<uint8_t, 41> data;
|
std::array<uint8_t, 41> data {};
|
||||||
|
|
||||||
// Get 4-bit value at position (0-80)
|
// Get 4-bit value at position (0-80)
|
||||||
// Each byte contains 2 cells: [cell0(4bits)][cell1(4bits)]
|
// Each byte contains 2 cells: [cell0(4bits)][cell1(4bits)]
|
||||||
// Ultra-fast: only bitwise operations, no divide/modulo!
|
// Ultra-fast: only bitwise operations, no divide/modulo!
|
||||||
// Optimization: pos >> 1 instead of pos / 2
|
// Optimization: pos >> 1 instead of pos / 2
|
||||||
// Optimization: (pos & 1) << 2 instead of (pos % 2) * 4
|
// 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
|
int byteIndex = pos >> 1; // pos / 2 using right shift
|
||||||
|
|
||||||
// Precomputed shift amounts: 4 for even positions, 0 for odd positions
|
// Precomputed shift amounts: 4 for even positions, 0 for odd positions
|
||||||
@@ -34,7 +38,7 @@ public:
|
|||||||
uint8_t result = (data[byteIndex] >> shiftAmount) & 0xF;
|
uint8_t result = (data[byteIndex] >> shiftAmount) & 0xF;
|
||||||
|
|
||||||
// Debug assertion: ensure result is in valid range
|
// 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;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -43,9 +47,9 @@ public:
|
|||||||
// Ultra-fast: only bitwise operations, no divide/modulo!
|
// Ultra-fast: only bitwise operations, no divide/modulo!
|
||||||
// Optimization: pos >> 1 instead of pos / 2
|
// Optimization: pos >> 1 instead of pos / 2
|
||||||
// Optimization: (pos & 1) << 2 instead of (pos % 2) * 4
|
// 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 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
|
int byteIndex = pos >> 1; // pos / 2 using right shift
|
||||||
|
|
||||||
@@ -59,7 +63,7 @@ public:
|
|||||||
data[byteIndex] = (data[byteIndex] & mask) | (value << shiftAmount);
|
data[byteIndex] = (data[byteIndex] & mask) | (value << shiftAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
constexpr void clear() {
|
||||||
data.fill(0);
|
data.fill(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -67,23 +71,49 @@ public:
|
|||||||
// Ultra-memory-efficient Sudoku class: exactly 41 bytes
|
// Ultra-memory-efficient Sudoku class: exactly 41 bytes
|
||||||
class Sudoku {
|
class Sudoku {
|
||||||
public:
|
public:
|
||||||
Sudoku();
|
constexpr Sudoku() = default;
|
||||||
explicit Sudoku(const std::string& puzzle_str);
|
constexpr explicit Sudoku(std::string_view puzzle_str)
|
||||||
|
{
|
||||||
|
loadFromString(puzzle_str);
|
||||||
|
}
|
||||||
|
|
||||||
// Load from various formats
|
// 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);
|
bool loadFromFile(const std::string& filename);
|
||||||
|
|
||||||
// Board access (inlined for performance)
|
// Board access (inlined for performance)
|
||||||
inline uint8_t get(int row, int col) const {
|
constexpr inline uint8_t get(int row, int col) const {
|
||||||
assert((row >= 0 && row < 9 && col >= 0 && col < 9) &&
|
WFC::constexpr_assert((row >= 0 && row < 9 && col >= 0 && col < 9),
|
||||||
"Sudoku::get() called with invalid position - row and col must be 0-8");
|
"Sudoku::get() called with invalid position - row and col must be 0-8");
|
||||||
int linearIndex = getLinearIndex(row, col);
|
int linearIndex = getLinearIndex(row, col);
|
||||||
return board_.get(linearIndex);
|
return board_.get(linearIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool set(int row, int col, uint8_t value) {
|
constexpr inline bool set(int row, int col, uint8_t value) {
|
||||||
assert((row >= 0 && row < 9 && col >= 0 && col < 9) &&
|
WFC::constexpr_assert((row >= 0 && row < 9 && col >= 0 && col < 9),
|
||||||
"Sudoku::set() called with invalid position - row and col must be 0-8");
|
"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
|
// Keep value validation as runtime check since it's about valid Sudoku numbers
|
||||||
@@ -104,14 +134,64 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear();
|
constexpr void clear() {
|
||||||
|
board_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
bool isValid() const;
|
constexpr bool isValid() const {
|
||||||
bool isSolved() 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())
|
// 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;
|
if (value == 0 || value > 9) return false;
|
||||||
return !hasRowConflictExcluding(row, col, value) &&
|
return !hasRowConflictExcluding(row, col, value) &&
|
||||||
!hasColConflictExcluding(col, row, value) &&
|
!hasColConflictExcluding(col, row, value) &&
|
||||||
@@ -119,8 +199,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Utility
|
// Utility
|
||||||
void print() const;
|
void print(char emptyVal = '.') const;
|
||||||
std::string toString() const;
|
std::string toString(char emptyVal = '.') const;
|
||||||
|
|
||||||
// Convert to standard board format for external use
|
// Convert to standard board format for external use
|
||||||
std::array<uint8_t, 81> getBoard() const;
|
std::array<uint8_t, 81> getBoard() const;
|
||||||
@@ -129,35 +209,35 @@ private:
|
|||||||
SudokuBoardStorage board_;
|
SudokuBoardStorage board_;
|
||||||
|
|
||||||
// Helper functions (inlined for performance)
|
// 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;
|
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);
|
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;
|
return row >= 0 && row < 9 && col >= 0 && col < 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation helpers (inlined for performance)
|
// Validation helpers (inlined for performance)
|
||||||
// Uses std::bitset<10> for efficient duplicate detection instead of arrays
|
// 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) {
|
for (int col = 0; col < 9; ++col) {
|
||||||
if (get(row, col) == value) return true;
|
if (get(row, col) == value) return true;
|
||||||
}
|
}
|
||||||
return false;
|
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) {
|
for (int row = 0; row < 9; ++row) {
|
||||||
if (get(row, col) == value) return true;
|
if (get(row, col) == value) return true;
|
||||||
}
|
}
|
||||||
return false;
|
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 startRow = (box / 3) * 3;
|
||||||
int startCol = (box % 3) * 3;
|
int startCol = (box % 3) * 3;
|
||||||
for (int row = 0; row < 3; ++row) {
|
for (int row = 0; row < 3; ++row) {
|
||||||
@@ -169,21 +249,21 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validation helpers that exclude current position (for move validation)
|
// 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) {
|
for (int col = 0; col < 9; ++col) {
|
||||||
if (col != excludeCol && get(row, col) == value) return true;
|
if (col != excludeCol && get(row, col) == value) return true;
|
||||||
}
|
}
|
||||||
return false;
|
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) {
|
for (int row = 0; row < 9; ++row) {
|
||||||
if (row != excludeRow && get(row, col) == value) return true;
|
if (row != excludeRow && get(row, col) == value) return true;
|
||||||
}
|
}
|
||||||
return false;
|
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 startRow = (box / 3) * 3;
|
||||||
int startCol = (box % 3) * 3;
|
int startCol = (box % 3) * 3;
|
||||||
for (int row = 0; row < 3; ++row) {
|
for (int row = 0; row < 3; ++row) {
|
||||||
@@ -199,15 +279,15 @@ private:
|
|||||||
public: // WFC Support
|
public: // WFC Support
|
||||||
using ValueType = uint8_t;
|
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));
|
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);
|
board_.set(static_cast<int>(index), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr size_t size() const {
|
constexpr inline size_t size() const {
|
||||||
return 81;
|
return 81;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -234,8 +314,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
static bool parseLine(const std::string& line, std::array<uint8_t, 81>& board);
|
static bool parseLine(const std::string& line, std::array<uint8_t, 81>& board);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
using SudokuSolverBuilder = WFC::Builder<Sudoku>
|
using SudokuSolverBuilder = WFC::Builder<Sudoku>
|
||||||
::DefineIDs<1, 2, 3, 4, 5, 6, 7, 8, 9>
|
::DefineIDs<1, 2, 3, 4, 5, 6, 7, 8, 9>
|
||||||
::DefineConstrainer<decltype([](Sudoku&, size_t index, WFC::WorldValue<uint8_t> val, auto& constrainer) {
|
::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>;
|
}), 1, 2, 3, 4, 5, 6, 7, 8, 9>;
|
||||||
|
|
||||||
using SudokuSolver = SudokuSolverBuilder::Build;
|
using SudokuSolver = SudokuSolverBuilder::Build;
|
||||||
|
|
||||||
|
|||||||
@@ -58,11 +58,16 @@ using SudokuSolverCallback = SudokuSolverBuilder::SetCellCollapsedCallback<declt
|
|||||||
})>
|
})>
|
||||||
::Build;
|
::Build;
|
||||||
|
|
||||||
|
Sudoku GetWorldConsteval()
|
||||||
|
{
|
||||||
|
return Sudoku{ "6......3.......7....7463....7.8...2.4...9...1.9...7.8....9851....6.......1......9" };
|
||||||
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
std::cout << "Running Sudoku WFC" << std::endl;
|
std::cout << "Running Sudoku WFC" << std::endl;
|
||||||
|
|
||||||
Sudoku sudokuWorld{ "040280030010006007609070008000092000900000004000740000500020803400800010070035090" };
|
Sudoku sudokuWorld = GetWorldConsteval();
|
||||||
|
|
||||||
bool success = SudokuSolverCallback::Run(sudokuWorld, true);
|
bool success = SudokuSolverCallback::Run(sudokuWorld, true);
|
||||||
|
|
||||||
@@ -84,5 +89,5 @@ int main()
|
|||||||
if (y == 2 || y == 5) std::cout << "------+-------+------" << std::endl;
|
if (y == 2 || y == 5) std::cout << "------+-------+------" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (success && solved) ? 0 : 1;
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
@@ -4,22 +4,17 @@
|
|||||||
#include <nd-wfc/wfc.hpp>
|
#include <nd-wfc/wfc.hpp>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <algorithm>
|
#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);
|
std::vector<Sudoku> loadPuzzlesFromFile(const std::string& filename);
|
||||||
|
void testPuzzleSolving(const std::string& difficulty, const std::string& filename);
|
||||||
|
|
||||||
// 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 {
|
|
||||||
// Common test setup
|
|
||||||
}
|
|
||||||
|
|
||||||
void TearDown() override {
|
|
||||||
// Common test cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to create a solved Sudoku
|
// Helper function to create a solved Sudoku
|
||||||
Sudoku createSolvedSudoku() {
|
Sudoku createSolvedSudoku() {
|
||||||
Sudoku sudoku;
|
Sudoku sudoku;
|
||||||
@@ -36,9 +31,9 @@ protected:
|
|||||||
return sudoku;
|
return sudoku;
|
||||||
}
|
}
|
||||||
|
|
||||||
Sudoku SolvePuzzle(Sudoku& sudoku) {
|
// Helper function to solve a puzzle using WFC
|
||||||
|
void solvePuzzle(Sudoku& sudoku) {
|
||||||
SudokuSolver::Run(sudoku, true);
|
SudokuSolver::Run(sudoku, true);
|
||||||
return sudoku;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -246,7 +241,7 @@ TEST_F(SudokuTest, PerformanceSetOperations) {
|
|||||||
int row = i % 9;
|
int row = i % 9;
|
||||||
int col = (i / 9) % 9;
|
int col = (i / 9) % 9;
|
||||||
int value = (i % 9) + 1;
|
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();
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
@@ -272,173 +267,82 @@ TEST_F(SudokuTest, EdgeCases) {
|
|||||||
EXPECT_EQ(sudoku.get(8, 8), 4);
|
EXPECT_EQ(sudoku.get(8, 8), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SudokuTest, WFCIntegration)
|
TEST_F(SudokuTest, WFCIntegration) {
|
||||||
{
|
Sudoku sudoku = createEasyPuzzle();
|
||||||
auto sudoku = createEasyPuzzle();
|
solvePuzzle(sudoku);
|
||||||
SudokuSolver::Run(sudoku, true);
|
|
||||||
EXPECT_TRUE(sudoku.isSolved());
|
EXPECT_TRUE(sudoku.isSolved());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests loading and solving puzzles from data files
|
// Unified function to test puzzle solving for different difficulty levels
|
||||||
TEST_F(SudokuTest, LoadAndSolveEasyPuzzles)
|
void testPuzzleSolving(const std::string& difficulty, const std::string& filename) {
|
||||||
{
|
std::vector<Sudoku> puzzles = loadPuzzlesFromFile(filename);
|
||||||
std::vector<Sudoku> easyPuzzles = loadPuzzlesFromFile("../data/Sudoku_easy.txt");
|
|
||||||
|
|
||||||
ASSERT_GT(easyPuzzles.size(), 0) << "No easy puzzles loaded";
|
ASSERT_GT(puzzles.size(), 0) << "No " << difficulty << " puzzles loaded";
|
||||||
|
|
||||||
int solvedCount = 0;
|
int solvedCount = 0;
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
for (size_t i = 0; i < easyPuzzles.size(); ++i) {
|
WFC::WFCStackAllocator allocator{};
|
||||||
auto& sudoku = easyPuzzles[i];
|
|
||||||
EXPECT_TRUE(sudoku.isValid()) << "Puzzle " << i << " is not valid";
|
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();
|
auto puzzleStart = std::chrono::high_resolution_clock::now();
|
||||||
SudokuSolver::Run(sudoku, true);
|
SudokuSolver::Run(sudoku, allocator);
|
||||||
auto puzzleEnd = std::chrono::high_resolution_clock::now();
|
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()) {
|
if (sudoku.isSolved()) {
|
||||||
solvedCount++;
|
solvedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto puzzleDuration = std::chrono::duration_cast<std::chrono::milliseconds>(puzzleEnd - puzzleStart);
|
// give progress every max(100, puzzles.size() / 100) puzzles and output percentage complete
|
||||||
if (i < 5) { // Only print timing for first 5 puzzles to avoid spam
|
if (i % std::max<size_t>(100, puzzles.size() / 100) == 0) {
|
||||||
std::cout << "Easy puzzle " << i << " solved in " << puzzleDuration.count() << "ms" << std::endl;
|
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 end = std::chrono::high_resolution_clock::now();
|
||||||
auto totalDuration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
|
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;
|
<< " 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)
|
// Tests loading and solving puzzles from data files using the unified function
|
||||||
{
|
TEST_F(SudokuTest, LoadAndSolveEasyPuzzles) {
|
||||||
std::vector<Sudoku> mediumPuzzles = loadPuzzlesFromFile("../data/Sudoku_medium.txt");
|
testPuzzleSolving("Easy", "../data/Sudoku_easy.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";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SudokuTest, LoadAndSolveHardPuzzles)
|
TEST_F(SudokuTest, LoadAndSolveMediumPuzzles) {
|
||||||
{
|
testPuzzleSolving("Medium", "../data/Sudoku_medium.txt");
|
||||||
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, LoadAndSolveDiabolicalPuzzles)
|
TEST_F(SudokuTest, LoadAndSolveHardPuzzles) {
|
||||||
{
|
testPuzzleSolving("Hard", "../data/Sudoku_hard.txt");
|
||||||
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 loading a single puzzle from each difficulty file
|
TEST_F(SudokuTest, LoadAndSolveDiabolicalPuzzles) {
|
||||||
TEST_F(SudokuTest, LoadAndSolveFirstPuzzleFromEachFile)
|
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::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::string filepath = dataPath + "/" + filename;
|
||||||
std::ifstream file(filepath);
|
std::ifstream file(filepath);
|
||||||
|
|
||||||
@@ -459,13 +363,13 @@ TEST_F(SudokuTest, LoadAndSolveFirstPuzzleFromEachFile)
|
|||||||
EXPECT_TRUE(puzzle.isValid()) << "Loaded puzzle from " << filename << " is not valid";
|
EXPECT_TRUE(puzzle.isValid()) << "Loaded puzzle from " << filename << " is not valid";
|
||||||
|
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
SudokuSolver::Run(puzzle, true);
|
solvePuzzle(puzzle);
|
||||||
auto end = std::chrono::high_resolution_clock::now();
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
EXPECT_TRUE(puzzle.isSolved()) << "Failed to solve first puzzle from " << filename;
|
EXPECT_TRUE(puzzle.isSolved()) << "Failed to solve first puzzle from " << filename;
|
||||||
|
|
||||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
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.
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
@@ -18,8 +18,12 @@
|
|||||||
|
|
||||||
namespace WFC {
|
namespace WFC {
|
||||||
|
|
||||||
|
inline constexpr void constexpr_assert(bool condition, const char* message = "") {
|
||||||
|
if (!condition) throw message;
|
||||||
|
}
|
||||||
|
|
||||||
inline int FindNthSetBit(size_t num, int n) {
|
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;
|
int bitCount = 0;
|
||||||
while (num) {
|
while (num) {
|
||||||
if (bitCount == n) {
|
if (bitCount == n) {
|
||||||
@@ -111,7 +115,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static VarT GetValue(size_t index) {
|
static VarT GetValue(size_t index) {
|
||||||
assert(index < ValuesRegisteredAmount);
|
constexpr_assert(index < ValuesRegisteredAmount);
|
||||||
return GetAllValues()[index];
|
return GetAllValues()[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,34 +378,40 @@ public:
|
|||||||
WFC() = delete; // dont make an instance of this class, only use the static methods.
|
WFC() = delete; // dont make an instance of this class, only use the static methods.
|
||||||
|
|
||||||
public:
|
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 };
|
WFCStackAllocator allocator{};
|
||||||
std::mt19937 random{ std::random_device{}() };
|
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;
|
size_t iterations = 0;
|
||||||
if (!allocator)
|
auto random = std::mt19937{ seed };
|
||||||
|
SolverState state
|
||||||
{
|
{
|
||||||
WFCStackAllocator newAllocator{};
|
world,
|
||||||
SolverState state(world, ConstrainerFunctionMapT::size(), random, newAllocator, iterations);
|
ConstrainerFunctionMapT::size(),
|
||||||
return Run(state, propagateInitialValues);
|
random,
|
||||||
}
|
allocator,
|
||||||
else
|
iterations
|
||||||
{
|
};
|
||||||
SolverState state(world, ConstrainerFunctionMapT::size(), random, *allocator, iterations);
|
return Run(state);
|
||||||
return Run(state, propagateInitialValues);
|
|
||||||
}
|
allocator.reset();
|
||||||
|
constexpr_assert(allocator.getUsed() == 0, "Allocator must be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Run the WFC algorithm to generate a solution
|
* @brief Run the WFC algorithm to generate a solution
|
||||||
* @return true if a solution was found, false if contradiction occurred
|
* @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)) {
|
if (RunLoop(state)) {
|
||||||
|
|
||||||
@@ -413,7 +423,7 @@ public:
|
|||||||
|
|
||||||
static bool RunLoop(SolverState& state)
|
static bool RunLoop(SolverState& state)
|
||||||
{
|
{
|
||||||
for (; state.iterations < 1024; ++state.iterations)
|
for (; state.iterations < 1024 * 8; ++state.iterations)
|
||||||
{
|
{
|
||||||
if (!Propagate(state))
|
if (!Propagate(state))
|
||||||
return false;
|
return false;
|
||||||
@@ -474,9 +484,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
static void CollapseCell(SolverState& state, size_t cellId, uint16_t value)
|
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);
|
state.wave.Collapse(cellId, 1 << value);
|
||||||
assert(state.wave.IsCollapsed(cellId));
|
constexpr_assert(state.wave.IsCollapsed(cellId));
|
||||||
|
|
||||||
if constexpr (CallbacksT::HasCellCollapsedCallback())
|
if constexpr (CallbacksT::HasCellCollapsedCallback())
|
||||||
{
|
{
|
||||||
@@ -487,7 +497,7 @@ private:
|
|||||||
|
|
||||||
static bool Branch(SolverState& state)
|
static bool Branch(SolverState& state)
|
||||||
{
|
{
|
||||||
assert(state.propagationQueue.empty());
|
constexpr_assert(state.propagationQueue.empty());
|
||||||
|
|
||||||
// Find cell with minimum entropy > 1
|
// Find cell with minimum entropy > 1
|
||||||
size_t minEntropyCell = static_cast<size_t>(-1);
|
size_t minEntropyCell = static_cast<size_t>(-1);
|
||||||
@@ -502,7 +512,7 @@ private:
|
|||||||
}
|
}
|
||||||
if (minEntropyCell == static_cast<size_t>(-1)) return false;
|
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
|
// create a list of possible values
|
||||||
uint16_t availableValues = static_cast<uint16_t>(state.wave.Entropy(minEntropyCell));
|
uint16_t availableValues = static_cast<uint16_t>(state.wave.Entropy(minEntropyCell));
|
||||||
@@ -511,10 +521,10 @@ private:
|
|||||||
for (size_t i = 0; i < availableValues; ++i)
|
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
|
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;
|
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
|
mask = mask & (mask - 1); // turn off lowest set bit
|
||||||
}
|
}
|
||||||
@@ -543,9 +553,9 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove the failure state from the wave
|
// 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));
|
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
|
// swap replacement value with the last value
|
||||||
std::swap(possibleValues[randomIndex], possibleValues[--availableValues]);
|
std::swap(possibleValues[randomIndex], possibleValues[--availableValues]);
|
||||||
@@ -563,7 +573,7 @@ private:
|
|||||||
|
|
||||||
if (state.wave.IsContradicted(cellId)) return false;
|
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);
|
uint16_t variableID = state.wave.GetVariableID(cellId);
|
||||||
Constrainer<VariableIDMapT> constrainer(state.wave, state.propagationQueue);
|
Constrainer<VariableIDMapT> constrainer(state.wave, state.propagationQueue);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ target_link_libraries(nd-wfc-tests
|
|||||||
|
|
||||||
# Set C++ standard
|
# Set C++ standard
|
||||||
set_target_properties(nd-wfc-tests PROPERTIES
|
set_target_properties(nd-wfc-tests PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 20
|
||||||
CXX_STANDARD_REQUIRED ON
|
CXX_STANDARD_REQUIRED ON
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user