#include "nonogram.h" #include #include #include #include #include #include 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(file)), std::istreambuf_iterator()); 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(dim); } else if (key == "height") { height_ = static_cast(dim); } return true; } catch (const std::exception&) { return false; } } bool Nonogram::parseHintsLine(const std::string& line, bool is_rows) { std::vector 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(); return solution_->loadFromGoalString(goal_str, width_, height_); } std::vector Nonogram::parseHintSequence(const std::string& hint_str) { std::vector 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(num)); } } catch (const std::exception&) { // Skip invalid numbers } } return result; } // NonogramLoader implementation std::optional NonogramLoader::fromFile(const std::string& filename) { Nonogram nonogram; if (nonogram.loadFromFile(filename)) { return nonogram; } return std::nullopt; } std::optional NonogramLoader::fromString(const std::string& content) { Nonogram nonogram; if (nonogram.loadFromString(content)) { return nonogram; } return std::nullopt; } std::vector NonogramLoader::fromDirectory(const std::string& dirname) { std::vector 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 NonogramLoader::splitContent(const std::string& content) { std::vector 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 split(const std::string& str, char delimiter) { std::vector result; std::istringstream iss(str); std::string token; while (std::getline(iss, token, delimiter)) { result.push_back(trim(token)); } return result; } std::vector parseNumberSequence(const std::string& str, char delimiter) { std::vector result; std::vector 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(num)); } } catch (const std::exception&) { // Skip invalid numbers } } } return result; }