Merge commit 'f9371ce0b567d73c59cece5556edd3aa5d35fc34' into prompt/3-nonogram
This commit is contained in:
151
demos/nonogram/README.md
Normal file
151
demos/nonogram/README.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Nonogram Loader
|
||||
|
||||
A memory-efficient C++ nonogram loader that stores nonogram solution and instructions in a separate class, using minimal memory while only storing important information.
|
||||
|
||||
## Features
|
||||
|
||||
- **Memory Efficient**: Uses compact storage for hints (uint8_t) and solutions (1 bit per cell)
|
||||
- **Standard Format Support**: Reads `.non` format files as specified in the format specification
|
||||
- **Ignores Unnecessary Data**: Only stores essential puzzle data, ignores author names and other metadata
|
||||
- **Multiple Loading Methods**: Load from files, strings, or entire directories
|
||||
- **Solution Support**: Optional solution storage when present in the file
|
||||
|
||||
## Memory Usage
|
||||
|
||||
The loader is designed to use minimal memory:
|
||||
|
||||
- **Hints**: Stored as `uint8_t` arrays with compact indexing
|
||||
- **Solutions**: Stored as bit arrays (1 bit per cell)
|
||||
- **Dimensions**: Stored as `uint16_t` for puzzles up to 65535x65535
|
||||
|
||||
For a 10x10 puzzle:
|
||||
- Row hints: ~10 bytes
|
||||
- Column hints: ~10 bytes
|
||||
- Solution: ~13 bytes (10x10 bits = 100 bits = 13 bytes)
|
||||
- Total: ~33 bytes + overhead
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```cpp
|
||||
#include "nonogram.h"
|
||||
|
||||
// Load a single nonogram from file
|
||||
auto nonogram = NonogramLoader::fromFile("puzzle.non");
|
||||
if (nonogram) {
|
||||
std::cout << "Loaded: " << nonogram->getWidth() << "x" << nonogram->getHeight() << std::endl;
|
||||
|
||||
// Access row hints
|
||||
for (size_t i = 0; i < nonogram->getRowCount(); ++i) {
|
||||
auto hints = nonogram->getRowHints(i);
|
||||
// Process hints...
|
||||
}
|
||||
|
||||
// Access column hints
|
||||
for (size_t i = 0; i < nonogram->getColumnCount(); ++i) {
|
||||
auto hints = nonogram->getColumnHints(i);
|
||||
// Process hints...
|
||||
}
|
||||
|
||||
// Access solution if available
|
||||
if (nonogram->hasSolution()) {
|
||||
for (size_t row = 0; row < nonogram->getHeight(); ++row) {
|
||||
for (size_t col = 0; col < nonogram->getWidth(); ++col) {
|
||||
bool filled = nonogram->getSolutionCell(row, col);
|
||||
// Process cell...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Load from Directory
|
||||
|
||||
```cpp
|
||||
// Load all nonograms from a directory
|
||||
auto puzzles = NonogramLoader::fromDirectory("/path/to/nonograms");
|
||||
for (const auto& puzzle : puzzles) {
|
||||
// Process each puzzle...
|
||||
}
|
||||
```
|
||||
|
||||
### Load from String
|
||||
|
||||
```cpp
|
||||
std::string content = R"(
|
||||
width 5
|
||||
height 5
|
||||
|
||||
rows
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
|
||||
columns
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
|
||||
goal 1000010000100001000010000
|
||||
)";
|
||||
|
||||
auto nonogram = NonogramLoader::fromString(content);
|
||||
```
|
||||
|
||||
## Classes
|
||||
|
||||
### Nonogram
|
||||
Main class storing puzzle data:
|
||||
- `getWidth()` / `getHeight()`: Puzzle dimensions
|
||||
- `getRowHints(row)`: Get hints for a specific row
|
||||
- `getColumnHints(col)`: Get hints for a specific column
|
||||
- `hasSolution()`: Check if solution is available
|
||||
- `getSolutionCell(row, col)`: Get solution cell value
|
||||
|
||||
### NonogramLoader
|
||||
Static utility class for loading:
|
||||
- `fromFile(filename)`: Load from file
|
||||
- `fromString(content)`: Load from string
|
||||
- `fromDirectory(dirname)`: Load all .non files from directory
|
||||
|
||||
## Storage Details
|
||||
|
||||
### Hints Storage
|
||||
- Uses `NonogramHintsStorage` class
|
||||
- Each hint is a `uint8_t` (0-255)
|
||||
- Compact storage with offset arrays
|
||||
- Handles comma-separated hint sequences
|
||||
|
||||
### Solution Storage
|
||||
- Uses `NonogramSolutionStorage` class
|
||||
- 1 bit per cell (filled/empty)
|
||||
- Packed into bytes for efficiency
|
||||
- Supports loading from goal strings with or without quotes
|
||||
|
||||
## Supported Format
|
||||
|
||||
The loader supports the standard `.non` format:
|
||||
- Width/height dimensions
|
||||
- Row and column hints (comma-separated numbers)
|
||||
- Optional solution/goal string
|
||||
- Ignores metadata (author, title, copyright, etc.)
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
# Compile with C++17
|
||||
g++ -std=c++17 -o myprogram main.cpp nonogram.cpp
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
See `example.cpp` for a complete usage example that demonstrates:
|
||||
- Loading from file
|
||||
- Accessing hints and solutions
|
||||
- Loading from directory
|
||||
- Displaying puzzle information
|
||||
62
demos/nonogram/example.cpp
Normal file
62
demos/nonogram/example.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "nonogram.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
// Load a nonogram from file
|
||||
auto nonogram = NonogramLoader::fromFile("/home/connor/repos/nd-wfc/demos/nonogram/data/db/webpbn/1.non");
|
||||
|
||||
if (!nonogram) {
|
||||
std::cout << "Failed to load nonogram!" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Loaded nonogram: " << nonogram->getWidth() << "x" << nonogram->getHeight() << std::endl;
|
||||
|
||||
// Display row hints
|
||||
std::cout << "\nRow hints:" << std::endl;
|
||||
for (size_t i = 0; i < nonogram->getRowCount(); ++i) {
|
||||
const auto& hints = nonogram->getRowHints(i);
|
||||
std::cout << "Row " << i << ":";
|
||||
for (uint8_t hint : hints) {
|
||||
std::cout << " " << static_cast<int>(hint);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// Display column hints
|
||||
std::cout << "\nColumn hints:" << std::endl;
|
||||
for (size_t i = 0; i < nonogram->getColumnCount(); ++i) {
|
||||
const auto& hints = nonogram->getColumnHints(i);
|
||||
std::cout << "Col " << i << ":";
|
||||
for (uint8_t hint : hints) {
|
||||
std::cout << " " << static_cast<int>(hint);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// Display solution if available
|
||||
if (nonogram->hasSolution()) {
|
||||
std::cout << "\nSolution:" << std::endl;
|
||||
for (size_t row = 0; row < nonogram->getHeight(); ++row) {
|
||||
for (size_t col = 0; col < nonogram->getWidth(); ++col) {
|
||||
std::cout << (nonogram->getSolutionCell(row, col) ? "1" : "0");
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Load all nonograms from a directory
|
||||
std::cout << "\nLoading all nonograms from directory..." << std::endl;
|
||||
auto puzzles = NonogramLoader::fromDirectory("/home/connor/repos/nd-wfc/demos/nonogram/data/db/webpbn");
|
||||
|
||||
std::cout << "Loaded " << puzzles.size() << " puzzles:" << std::endl;
|
||||
for (size_t i = 0; i < puzzles.size(); ++i) {
|
||||
std::cout << " Puzzle " << i << ": " << puzzles[i].getWidth() << "x" << puzzles[i].getHeight();
|
||||
if (puzzles[i].hasSolution()) {
|
||||
std::cout << " (with solution)";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
302
demos/nonogram/nonogram.cpp
Normal file
302
demos/nonogram/nonogram.cpp
Normal file
@@ -0,0 +1,302 @@
|
||||
#include "nonogram.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <sstream>
|
||||
|
||||
Nonogram::Nonogram() : width_(0), height_(0) {}
|
||||
|
||||
Nonogram::Nonogram(uint16_t w, uint16_t h) : width_(w), height_(h) {}
|
||||
|
||||
void Nonogram::clear() {
|
||||
width_ = 0;
|
||||
height_ = 0;
|
||||
row_hints_.clear();
|
||||
col_hints_.clear();
|
||||
solution_.reset();
|
||||
}
|
||||
|
||||
bool Nonogram::loadFromFile(const std::string& filename) {
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
return loadFromString(content);
|
||||
}
|
||||
|
||||
bool Nonogram::loadFromString(const std::string& content) {
|
||||
clear();
|
||||
|
||||
std::istringstream iss(content);
|
||||
std::string line;
|
||||
int section = 0; // 0: header, 1: rows, 2: columns
|
||||
|
||||
while (std::getline(iss, line)) {
|
||||
if (!parseLine(line, section)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that we have all required data
|
||||
if (width_ == 0 || height_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (row_hints_.size() != height_ || col_hints_.size() != width_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Nonogram::parseLine(const std::string& line, int& section) {
|
||||
std::string trimmed = trim(line);
|
||||
|
||||
// Skip empty lines and comments
|
||||
if (trimmed.empty() || trimmed[0] == '#') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip lines we don't recognize (as per format spec)
|
||||
if (startsWith(trimmed, "catalogue") ||
|
||||
startsWith(trimmed, "title") ||
|
||||
startsWith(trimmed, "by") ||
|
||||
startsWith(trimmed, "copyright") ||
|
||||
startsWith(trimmed, "license") ||
|
||||
startsWith(trimmed, "color")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (startsWith(trimmed, "width")) {
|
||||
return parseDimensions(trimmed);
|
||||
}
|
||||
|
||||
if (startsWith(trimmed, "height")) {
|
||||
return parseDimensions(trimmed);
|
||||
}
|
||||
|
||||
if (startsWith(trimmed, "rows")) {
|
||||
section = 1; // Enter rows section
|
||||
return true;
|
||||
}
|
||||
|
||||
if (startsWith(trimmed, "columns")) {
|
||||
section = 2; // Enter columns section
|
||||
return true;
|
||||
}
|
||||
|
||||
if (startsWith(trimmed, "goal")) {
|
||||
return parseGoalLine(trimmed);
|
||||
}
|
||||
|
||||
// Check if this is a hints line (contains numbers)
|
||||
if (std::any_of(trimmed.begin(), trimmed.end(), ::isdigit)) {
|
||||
if (section == 1) {
|
||||
return parseHintsLine(trimmed, true); // Parse as row hints
|
||||
} else if (section == 2) {
|
||||
return parseHintsLine(trimmed, false); // Parse as column hints
|
||||
}
|
||||
}
|
||||
|
||||
return true; // Ignore unrecognized lines
|
||||
}
|
||||
|
||||
bool Nonogram::parseDimensions(const std::string& line) {
|
||||
size_t space_pos = line.find(' ');
|
||||
if (space_pos == std::string::npos) return false;
|
||||
|
||||
std::string key = line.substr(0, space_pos);
|
||||
std::string value = line.substr(space_pos + 1);
|
||||
|
||||
try {
|
||||
int dim = std::stoi(value);
|
||||
if (dim <= 0 || dim > 65535) return false;
|
||||
|
||||
if (key == "width") {
|
||||
width_ = static_cast<uint16_t>(dim);
|
||||
} else if (key == "height") {
|
||||
height_ = static_cast<uint16_t>(dim);
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Nonogram::parseHintsLine(const std::string& line, bool is_rows) {
|
||||
std::vector<uint8_t> hints = parseHintSequence(line);
|
||||
if (hints.empty()) return true; // Empty line is OK
|
||||
|
||||
if (is_rows) {
|
||||
row_hints_.addHints(hints);
|
||||
return true;
|
||||
} else {
|
||||
col_hints_.addHints(hints);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Nonogram::parseGoalLine(const std::string& line) {
|
||||
size_t space_pos = line.find(' ');
|
||||
if (space_pos == std::string::npos) return false;
|
||||
|
||||
std::string goal_str = line.substr(space_pos + 1);
|
||||
goal_str = trim(goal_str);
|
||||
|
||||
// Remove quotes if present
|
||||
if (goal_str.length() >= 2 && goal_str[0] == '"' && goal_str[goal_str.length() - 1] == '"') {
|
||||
goal_str = goal_str.substr(1, goal_str.length() - 2);
|
||||
}
|
||||
|
||||
if (goal_str.empty()) return false;
|
||||
|
||||
solution_ = std::make_unique<NonogramSolutionStorage>();
|
||||
return solution_->loadFromGoalString(goal_str, width_, height_);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Nonogram::parseHintSequence(const std::string& hint_str) {
|
||||
std::vector<uint8_t> result;
|
||||
std::string cleaned = hint_str;
|
||||
|
||||
// Remove spaces and handle comma-separated values
|
||||
cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), ::isspace), cleaned.end());
|
||||
|
||||
std::istringstream iss(cleaned);
|
||||
std::string token;
|
||||
|
||||
while (std::getline(iss, token, ',')) {
|
||||
// Skip empty tokens
|
||||
if (token.empty()) continue;
|
||||
|
||||
// Remove any non-digit characters from the end (like color codes)
|
||||
size_t first_non_digit = 0;
|
||||
for (; first_non_digit < token.size() && std::isdigit(token[first_non_digit]); ++first_non_digit) {}
|
||||
token = token.substr(0, first_non_digit);
|
||||
|
||||
if (token.empty() || !std::isdigit(token[0])) continue;
|
||||
|
||||
try {
|
||||
int num = std::stoi(token);
|
||||
if (num > 0 && num <= 255) { // Max hint value
|
||||
result.push_back(static_cast<uint8_t>(num));
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
// Skip invalid numbers
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// NonogramLoader implementation
|
||||
std::optional<Nonogram> NonogramLoader::fromFile(const std::string& filename) {
|
||||
Nonogram nonogram;
|
||||
if (nonogram.loadFromFile(filename)) {
|
||||
return nonogram;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Nonogram> NonogramLoader::fromString(const std::string& content) {
|
||||
Nonogram nonogram;
|
||||
if (nonogram.loadFromString(content)) {
|
||||
return nonogram;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<Nonogram> NonogramLoader::fromDirectory(const std::string& dirname) {
|
||||
std::vector<Nonogram> puzzles;
|
||||
|
||||
try {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dirname)) {
|
||||
if (entry.is_regular_file()) {
|
||||
std::string filename = entry.path().string();
|
||||
std::string extension = ".non";
|
||||
if (filename.size() >= extension.size() &&
|
||||
filename.substr(filename.size() - extension.size()) == extension) {
|
||||
if (auto nonogram = fromFile(filename)) {
|
||||
puzzles.push_back(std::move(*nonogram));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::filesystem::filesystem_error&) {
|
||||
// Directory doesn't exist or can't be read
|
||||
}
|
||||
|
||||
return puzzles;
|
||||
}
|
||||
|
||||
std::vector<std::string> NonogramLoader::splitContent(const std::string& content) {
|
||||
std::vector<std::string> puzzles;
|
||||
std::istringstream iss(content);
|
||||
std::string line;
|
||||
std::string current_puzzle;
|
||||
|
||||
while (std::getline(iss, line)) {
|
||||
if (trim(line) == "====") {
|
||||
if (!current_puzzle.empty()) {
|
||||
puzzles.push_back(current_puzzle);
|
||||
current_puzzle.clear();
|
||||
}
|
||||
} else {
|
||||
current_puzzle += line + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!current_puzzle.empty()) {
|
||||
puzzles.push_back(current_puzzle);
|
||||
}
|
||||
|
||||
return puzzles;
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
std::string trim(const std::string& str) {
|
||||
size_t first = str.find_first_not_of(" \t\r\n");
|
||||
if (first == std::string::npos) return "";
|
||||
size_t last = str.find_last_not_of(" \t\r\n");
|
||||
return str.substr(first, last - first + 1);
|
||||
}
|
||||
|
||||
bool startsWith(const std::string& str, const std::string& prefix) {
|
||||
return str.substr(0, prefix.length()) == prefix;
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string& str, char delimiter) {
|
||||
std::vector<std::string> result;
|
||||
std::istringstream iss(str);
|
||||
std::string token;
|
||||
|
||||
while (std::getline(iss, token, delimiter)) {
|
||||
result.push_back(trim(token));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> parseNumberSequence(const std::string& str, char delimiter) {
|
||||
std::vector<uint8_t> result;
|
||||
std::vector<std::string> tokens = split(str, delimiter);
|
||||
|
||||
for (const std::string& token : tokens) {
|
||||
if (!token.empty()) {
|
||||
try {
|
||||
int num = std::stoi(token);
|
||||
if (num > 0 && num <= 255) {
|
||||
result.push_back(static_cast<uint8_t>(num));
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
// Skip invalid numbers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
180
demos/nonogram/nonogram.h
Normal file
180
demos/nonogram/nonogram.h
Normal file
@@ -0,0 +1,180 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
// Forward declarations
|
||||
struct NonogramHints;
|
||||
struct NonogramSolution;
|
||||
|
||||
// Compact storage for nonogram hints
|
||||
// Stores each hint as a uint8_t (max hint value 255)
|
||||
class NonogramHintsStorage {
|
||||
public:
|
||||
// Each hint group is stored as: [count, hint1, hint2, ..., hintN]
|
||||
std::vector<uint8_t> data;
|
||||
std::vector<uint16_t> offsets; // Start position of each row/column in data
|
||||
|
||||
void clear() {
|
||||
data.clear();
|
||||
offsets.clear();
|
||||
}
|
||||
|
||||
// Add a new row/column's hints
|
||||
void addHints(const std::vector<uint8_t>& hints) {
|
||||
offsets.push_back(static_cast<uint16_t>(data.size()));
|
||||
data.push_back(static_cast<uint8_t>(hints.size())); // Store count first
|
||||
for (uint8_t hint : hints) {
|
||||
data.push_back(hint);
|
||||
}
|
||||
}
|
||||
|
||||
// Get hints for a specific row/column
|
||||
std::vector<uint8_t> getHints(size_t index) const {
|
||||
if (index >= offsets.size()) return {};
|
||||
|
||||
size_t start = offsets[index];
|
||||
if (start >= data.size()) return {};
|
||||
|
||||
uint8_t count = data[start];
|
||||
std::vector<uint8_t> result(count);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
result[i] = data[start + 1 + i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t size() const { return offsets.size(); }
|
||||
};
|
||||
|
||||
// Compact bit storage for nonogram solution
|
||||
// Each cell is 1 bit (filled=1, empty=0)
|
||||
class NonogramSolutionStorage {
|
||||
public:
|
||||
std::vector<uint8_t> data; // 8 bits per byte
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
|
||||
void resize(uint16_t w, uint16_t h) {
|
||||
width = w;
|
||||
height = h;
|
||||
size_t total_bits = static_cast<size_t>(w) * h;
|
||||
size_t total_bytes = (total_bits + 7) / 8; // Ceiling division
|
||||
data.resize(total_bytes, 0);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
data.clear();
|
||||
width = 0;
|
||||
height = 0;
|
||||
}
|
||||
|
||||
// Get cell value at (row, col) - true if filled, false if empty
|
||||
bool get(size_t row, size_t col) const {
|
||||
if (row >= height || col >= width) return false;
|
||||
size_t bit_index = row * width + col;
|
||||
size_t byte_index = bit_index / 8;
|
||||
size_t bit_offset = bit_index % 8;
|
||||
return (data[byte_index] & (1 << (7 - bit_offset))) != 0;
|
||||
}
|
||||
|
||||
// Set cell value at (row, col)
|
||||
void set(size_t row, size_t col, bool filled) {
|
||||
if (row >= height || col >= width) return;
|
||||
size_t bit_index = row * width + col;
|
||||
size_t byte_index = bit_index / 8;
|
||||
size_t bit_offset = bit_index % 8;
|
||||
if (filled) {
|
||||
data[byte_index] |= (1 << (7 - bit_offset));
|
||||
} else {
|
||||
data[byte_index] &= ~(1 << (7 - bit_offset));
|
||||
}
|
||||
}
|
||||
|
||||
// Load from goal string (sequence of 0s and 1s)
|
||||
bool loadFromGoalString(const std::string& goal, uint16_t w, uint16_t h) {
|
||||
size_t expected_size = static_cast<size_t>(w) * h;
|
||||
if (goal.length() != expected_size) return false;
|
||||
|
||||
resize(w, h);
|
||||
for (size_t i = 0; i < expected_size; ++i) {
|
||||
if (goal[i] == '1') {
|
||||
size_t row = i / w;
|
||||
size_t col = i % w;
|
||||
set(row, col, true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Memory-efficient nonogram class
|
||||
class Nonogram {
|
||||
public:
|
||||
Nonogram();
|
||||
explicit Nonogram(uint16_t w, uint16_t h);
|
||||
|
||||
// Dimensions
|
||||
uint16_t getWidth() const { return width_; }
|
||||
uint16_t getHeight() const { return height_; }
|
||||
|
||||
// Hints access
|
||||
std::vector<uint8_t> getRowHints(size_t row) const {
|
||||
return row_hints_.getHints(row);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> getColumnHints(size_t col) const {
|
||||
return col_hints_.getHints(col);
|
||||
}
|
||||
|
||||
size_t getRowCount() const { return row_hints_.size(); }
|
||||
size_t getColumnCount() const { return col_hints_.size(); }
|
||||
|
||||
// Solution access (if available)
|
||||
bool hasSolution() const { return solution_ != nullptr; }
|
||||
bool getSolutionCell(size_t row, size_t col) const {
|
||||
return solution_ ? solution_->get(row, col) : false;
|
||||
}
|
||||
|
||||
// Load from various formats
|
||||
bool loadFromFile(const std::string& filename);
|
||||
bool loadFromString(const std::string& content);
|
||||
|
||||
void clear();
|
||||
|
||||
private:
|
||||
uint16_t width_ = 0;
|
||||
uint16_t height_ = 0;
|
||||
NonogramHintsStorage row_hints_;
|
||||
NonogramHintsStorage col_hints_;
|
||||
std::unique_ptr<NonogramSolutionStorage> solution_;
|
||||
|
||||
// Parser helpers
|
||||
bool parseLine(const std::string& line, int& section);
|
||||
bool parseHintsLine(const std::string& line, bool is_rows);
|
||||
bool parseGoalLine(const std::string& line);
|
||||
bool parseDimensions(const std::string& line);
|
||||
std::vector<uint8_t> parseHintSequence(const std::string& hint_str);
|
||||
};
|
||||
|
||||
// Fast nonogram loader
|
||||
class NonogramLoader {
|
||||
public:
|
||||
static std::optional<Nonogram> fromFile(const std::string& filename);
|
||||
static std::optional<Nonogram> fromString(const std::string& content);
|
||||
static std::vector<Nonogram> fromDirectory(const std::string& dirname);
|
||||
|
||||
private:
|
||||
static std::vector<std::string> splitContent(const std::string& content);
|
||||
};
|
||||
|
||||
// Utility functions
|
||||
std::string trim(const std::string& str);
|
||||
bool startsWith(const std::string& str, const std::string& prefix);
|
||||
std::vector<std::string> split(const std::string& str, char delimiter);
|
||||
std::vector<uint8_t> parseNumberSequence(const std::string& str, char delimiter);
|
||||
118
demos/nonogram/test_nonogram.cpp
Normal file
118
demos/nonogram/test_nonogram.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "nonogram.h"
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
|
||||
void printNonogram(const Nonogram& nonogram) {
|
||||
std::cout << "Nonogram: " << nonogram.getWidth() << "x" << nonogram.getHeight() << std::endl;
|
||||
|
||||
std::cout << "Row hints:" << std::endl;
|
||||
for (size_t i = 0; i < nonogram.getRowCount(); ++i) {
|
||||
const auto& hints = nonogram.getRowHints(i);
|
||||
std::cout << " Row " << i << ":";
|
||||
for (uint8_t hint : hints) {
|
||||
std::cout << " " << static_cast<int>(hint);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "Column hints:" << std::endl;
|
||||
for (size_t i = 0; i < nonogram.getColumnCount(); ++i) {
|
||||
const auto& hints = nonogram.getColumnHints(i);
|
||||
std::cout << " Col " << i << ":";
|
||||
for (uint8_t hint : hints) {
|
||||
std::cout << " " << static_cast<int>(hint);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
if (nonogram.hasSolution()) {
|
||||
std::cout << "Solution:" << std::endl;
|
||||
for (size_t row = 0; row < nonogram.getHeight(); ++row) {
|
||||
for (size_t col = 0; col < nonogram.getWidth(); ++col) {
|
||||
std::cout << (nonogram.getSolutionCell(row, col) ? "1" : "0");
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Testing Nonogram Loader..." << std::endl;
|
||||
|
||||
// Test loading from file
|
||||
std::string filepath = "/home/connor/repos/nd-wfc/demos/nonogram/data/db/webpbn/1.non";
|
||||
std::cout << "Attempting to load from: " << filepath << std::endl;
|
||||
|
||||
std::ifstream test_file(filepath);
|
||||
if (test_file.is_open()) {
|
||||
std::cout << "File exists and can be opened." << std::endl;
|
||||
std::string content((std::istreambuf_iterator<char>(test_file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
std::cout << "File content length: " << content.length() << std::endl;
|
||||
std::cout << "File content:\n" << content << std::endl;
|
||||
test_file.close();
|
||||
|
||||
// Try to load from this content
|
||||
auto nonogram = NonogramLoader::fromString(content);
|
||||
if (nonogram) {
|
||||
std::cout << "Successfully loaded nonogram from file content!" << std::endl;
|
||||
printNonogram(*nonogram);
|
||||
} else {
|
||||
std::cout << "Failed to load nonogram from file content." << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "Cannot open file!" << std::endl;
|
||||
}
|
||||
|
||||
auto nonogram = NonogramLoader::fromFile(filepath);
|
||||
if (nonogram) {
|
||||
std::cout << "Successfully loaded nonogram from file!" << std::endl;
|
||||
printNonogram(*nonogram);
|
||||
} else {
|
||||
std::cout << "Failed to load nonogram from file." << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "\nTesting with sample data..." << std::endl;
|
||||
|
||||
// Test with sample data
|
||||
std::string sample_data =
|
||||
"width 5\n"
|
||||
"height 10\n"
|
||||
"\n"
|
||||
"rows\n"
|
||||
"2\n"
|
||||
"2,1\n"
|
||||
"1,1\n"
|
||||
"3\n"
|
||||
"1,1\n"
|
||||
"1,1\n"
|
||||
"2\n"
|
||||
"1,1\n"
|
||||
"1,2\n"
|
||||
"2\n"
|
||||
"\n"
|
||||
"columns\n"
|
||||
"2,1\n"
|
||||
"2,1,3\n"
|
||||
"7\n"
|
||||
"1,3\n"
|
||||
"2,1\n"
|
||||
"\n"
|
||||
"goal 01100011010010101110101001010000110010100101111000\n";
|
||||
|
||||
auto sample_nonogram = NonogramLoader::fromString(sample_data);
|
||||
if (sample_nonogram) {
|
||||
std::cout << "Successfully loaded nonogram from string!" << std::endl;
|
||||
printNonogram(*sample_nonogram);
|
||||
} else {
|
||||
std::cout << "Failed to load nonogram from string." << std::endl;
|
||||
}
|
||||
|
||||
// Test loading from directory
|
||||
std::cout << "\nTesting directory loading..." << std::endl;
|
||||
auto puzzles = NonogramLoader::fromDirectory("/home/connor/repos/nd-wfc/demos/nonogram/data/db/webpbn");
|
||||
std::cout << "Loaded " << puzzles.size() << " puzzles from directory." << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user