diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt new file mode 100644 index 0000000..9986095 --- /dev/null +++ b/demos/CMakeLists.txt @@ -0,0 +1,21 @@ +# 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 pthread for std::thread +find_package(Threads REQUIRED) +target_link_libraries(sudoku_demo_renderer Threads::Threads) + +# Set C++17 standard +set_target_properties(sudoku_demo_renderer PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON +) diff --git a/demos/README.md b/demos/README.md new file mode 100644 index 0000000..e89050e --- /dev/null +++ b/demos/README.md @@ -0,0 +1,267 @@ +# 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>& 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& 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! diff --git a/demos/console_renderer.cpp b/demos/console_renderer.cpp new file mode 100644 index 0000000..54ada16 --- /dev/null +++ b/demos/console_renderer.cpp @@ -0,0 +1,509 @@ +#include "console_renderer.h" +#include "sudoku/sudoku.h" +#include "nonogram/nonogram.h" +#include +#include + +// ANSI escape codes for cursor control +#ifdef _WIN32 + #include + #include +#else + #include + #include +#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(text.length()) >= width) return text; + int padding = (width - static_cast(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(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(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('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(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>& 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(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>* 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(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(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(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(hints.size())); + + if (hint_index >= 0 && hint_index < static_cast(hints.size())) { + oss << static_cast(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(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(hints.size())); + } + + return 2 + max_col_hints + 1 + static_cast(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& 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(current) / total; + int filled = static_cast(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(percentage * 100) << "%"; + std::cout.flush(); +} diff --git a/demos/console_renderer.h b/demos/console_renderer.h new file mode 100644 index 0000000..d3c2262 --- /dev/null +++ b/demos/console_renderer.h @@ -0,0 +1,130 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +/** + * 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>& state); + void showSolvingProgress(const std::string& status = ""); + +private: + const class Nonogram& nonogram_; + + // Rendering helpers + std::string formatNonogramLine(int row, const std::vector>* 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& lines, int delay_ms = 100); + static void progressBar(const std::string& label, int current, int total, int width = 30); +}; diff --git a/demos/sudoku_demo_renderer b/demos/sudoku_demo_renderer new file mode 100755 index 0000000..015743c Binary files /dev/null and b/demos/sudoku_demo_renderer differ diff --git a/demos/sudoku_demo_renderer.cpp b/demos/sudoku_demo_renderer.cpp new file mode 100644 index 0000000..e08575a --- /dev/null +++ b/demos/sudoku_demo_renderer.cpp @@ -0,0 +1,188 @@ +#include "console_renderer.h" +#include "sudoku/sudoku.h" +#include +#include +#include +#include + +/** + * 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> 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> 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; +}