Files
nd-wfc/demos/sudoku/sudoku.cpp
2025-08-25 13:08:32 +09:00

329 lines
8.8 KiB
C++

#include "sudoku.h"
#include <iostream>
#include <fstream>
#include <filesystem>
#include <algorithm>
#include <cctype>
#include <bitset>
Sudoku::Sudoku() {
clear();
}
Sudoku::Sudoku(const std::string& puzzle_str) {
clear();
loadFromString(puzzle_str);
}
bool Sudoku::loadFromString(const std::string& puzzle_str) {
if (puzzle_str.length() != 81) {
return false;
}
clear();
for (int i = 0; i < 81; ++i) {
char c = puzzle_str[i];
if (c >= '1' && c <= '9') {
int row = i / 9;
int col = i % 9;
uint8_t value = c - '0';
if (!set(row, col, value)) {
return false; // Invalid move
}
} else if (c != '0' && c != '.') {
return false; // Invalid character
}
}
return true;
}
bool Sudoku::loadFromFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
return false;
}
std::string line;
std::string puzzle_str;
while (std::getline(file, line)) {
// Remove whitespace and comments
line.erase(std::remove_if(line.begin(), line.end(),
[](char c) { return std::isspace(c) || c == '#'; }), line.end());
if (line.empty()) continue;
puzzle_str += line;
}
return loadFromString(puzzle_str);
}
void Sudoku::clear() {
board_.clear();
}
bool Sudoku::isValid() const {
// Check rows for duplicates using bitset for efficiency
for (int row = 0; row < 9; ++row) {
std::bitset<10> seen; // bits 1-9 track values 1-9
for (int col = 0; col < 9; ++col) {
uint8_t value = get(row, col);
if (value != 0) {
if (seen[value]) return false; // Duplicate found
seen.set(value);
}
}
}
// Check columns for duplicates
for (int col = 0; col < 9; ++col) {
std::bitset<10> seen;
for (int row = 0; row < 9; ++row) {
uint8_t value = get(row, col);
if (value != 0) {
if (seen[value]) return false; // Duplicate found
seen.set(value);
}
}
}
// Check boxes for duplicates
for (int box = 0; box < 9; ++box) {
std::bitset<10> seen;
int startRow = (box / 3) * 3;
int startCol = (box % 3) * 3;
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
uint8_t value = get(startRow + row, startCol + col);
if (value != 0) {
if (seen[value]) return false; // Duplicate found
seen.set(value);
}
}
}
}
return true;
}
bool Sudoku::isSolved() const {
for (int i = 0; i < 81; ++i) {
if (board_.get(i) == 0) return false;
}
return isValid();
}
void Sudoku::print() const {
for (int row = 0; row < 9; ++row) {
for (int col = 0; col < 9; ++col) {
uint8_t value = get(row, col);
std::cout << (value == 0 ? '.' : static_cast<char>('0' + value));
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::string Sudoku::toString() const {
std::string result;
result.reserve(81);
for (int i = 0; i < 81; ++i) {
uint8_t cell = board_.get(i);
result += (cell == 0 ? '.' : static_cast<char>('0' + cell));
}
return result;
}
std::array<uint8_t, 81> Sudoku::getBoard() const {
std::array<uint8_t, 81> result;
for (int i = 0; i < 81; ++i) {
result[i] = board_.get(i);
}
return result;
}
// SudokuValidator implementation
bool SudokuValidator::isValidSolution(const std::array<uint8_t, 81>& board) {
// Check if all cells are filled and valid
for (int i = 0; i < 81; ++i) {
if (board[i] == 0) return false;
}
return isValidPartial(board);
}
bool SudokuValidator::isValidPartial(const std::array<uint8_t, 81>& board) {
return !hasConflicts(board);
}
bool SudokuValidator::hasConflicts(const std::array<uint8_t, 81>& board) {
// Check rows
for (int row = 0; row < 9; ++row) {
std::bitset<10> seen;
for (int col = 0; col < 9; ++col) {
uint8_t value = board[row * 9 + col];
if (value != 0) {
if (seen[value]) return true;
seen.set(value);
}
}
}
// Check columns
for (int col = 0; col < 9; ++col) {
std::bitset<10> seen;
for (int row = 0; row < 9; ++row) {
uint8_t value = board[row * 9 + col];
if (value != 0) {
if (seen[value]) return true;
seen.set(value);
}
}
}
// Check boxes
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 = board[(startRow + row) * 9 + (startCol + col)];
if (value != 0) {
if (seen[value]) return true;
seen.set(value);
}
}
}
}
return false;
}
std::vector<std::pair<int, int>> SudokuValidator::findConflicts(const std::array<uint8_t, 81>& board) {
std::vector<std::pair<int, int>> conflicts;
// Check rows
for (int row = 0; row < 9; ++row) {
std::bitset<10> seen;
for (int col = 0; col < 9; ++col) {
uint8_t value = board[row * 9 + col];
if (value != 0) {
if (seen[value]) {
conflicts.emplace_back(row, col);
} else {
seen.set(value);
}
}
}
}
// Check columns
for (int col = 0; col < 9; ++col) {
std::bitset<10> seen;
for (int row = 0; row < 9; ++row) {
uint8_t value = board[row * 9 + col];
if (value != 0) {
if (seen[value]) {
conflicts.emplace_back(row, col);
} else {
seen.set(value);
}
}
}
}
// Check boxes
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) {
int r = startRow + row;
int c = startCol + col;
uint8_t value = board[r * 9 + c];
if (value != 0) {
if (seen[value]) {
conflicts.emplace_back(r, c);
} else {
seen.set(value);
}
}
}
}
}
return conflicts;
}
// SudokuLoader implementation
std::optional<Sudoku> SudokuLoader::fromString(const std::string& puzzle_str) {
Sudoku sudoku;
if (sudoku.loadFromString(puzzle_str)) {
return sudoku;
}
return std::nullopt;
}
std::optional<Sudoku> SudokuLoader::fromFile(const std::string& filename) {
Sudoku sudoku;
if (sudoku.loadFromFile(filename)) {
return sudoku;
}
return std::nullopt;
}
std::vector<Sudoku> SudokuLoader::fromDirectory(const std::string& dirname, const std::string& extension) {
std::vector<Sudoku> puzzles;
try {
for (const auto& entry : std::filesystem::directory_iterator(dirname)) {
if (entry.is_regular_file()) {
std::string filename = entry.path().string();
if (filename.size() >= extension.size() &&
filename.substr(filename.size() - extension.size()) == extension) {
if (auto sudoku = fromFile(filename)) {
puzzles.push_back(std::move(*sudoku));
}
}
}
}
} catch (const std::filesystem::filesystem_error&) {
// Directory doesn't exist or can't be read
}
return puzzles;
}
bool SudokuLoader::parseLine(const std::string& line, std::array<uint8_t, 81>& board) {
std::string cleaned = line;
cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(),
[](char c) { return std::isspace(c); }), cleaned.end());
if (cleaned.length() != 81) return false;
for (int i = 0; i < 81; ++i) {
char c = cleaned[i];
if (c >= '1' && c <= '9') {
board[i] = c - '0';
} else if (c == '0' || c == '.') {
board[i] = 0;
} else {
return false;
}
}
return true;
}