First pass at a sudoku solver: Puzzle Import

Imports a puzzle from a string and displays the restricted puzzle.
This commit is contained in:
Drew Galbraith 2023-04-17 16:31:47 -07:00
commit bcc5b5097e
8 changed files with 179 additions and 0 deletions

1
.clang-format Normal file
View File

@ -0,0 +1 @@
BasedOnStyle: Google

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build/
builddir/

14
CMakeLists.txt Normal file
View File

@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.26)
project(Sudoku)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(SOURCE_FILES
solver/cell.cpp
solver/puzzle.cpp
solver.cpp
)
add_executable(solver ${SOURCE_FILES})

11
solver.cpp Normal file
View File

@ -0,0 +1,11 @@
#include <iostream>
#include "solver/puzzle.h"
int main(int argc, char** argv) {
Puzzle puzzle = Puzzle::FromString(
"3..4.162.1...8.4....5.2.83..578........7..5.3..29.4..748.53..1.2.3.9...."
".7...6.9.");
std::cout << "https://tiramisu.one/sudoku.html?p=" << puzzle.CurrentState()
<< "&m=" << puzzle.PencilMarkState() << std::endl;
}

17
solver/cell.cpp Normal file
View File

@ -0,0 +1,17 @@
#include "cell.h"
#include <cassert>
Cell::Cell()
: state_(Unsolved), value_(0), possibilities_{true, true, true, true, true,
true, true, true, true} {}
Cell::Cell(uint8_t value)
: state_(Solved), value_(value), possibilities_{false, false, false,
false, false, false,
false, false, false} {}
void Cell::Restrict(uint8_t value) {
assert(value >= 1 && value <= 9);
possibilities_[value - 1] = false;
}

26
solver/cell.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <stdint.h>
#include <array>
class Cell {
public:
enum State {
Unsolved,
Solved,
};
Cell();
explicit Cell(uint8_t value);
void Restrict(uint8_t value);
bool IsSolved() const { return state_ == Solved; }
uint8_t value() const { return value_; }
bool IsPossible(uint8_t v) const { return possibilities_[v - 1]; }
private:
State state_;
uint8_t value_;
std::array<bool, 9> possibilities_;
};

88
solver/puzzle.cpp Normal file
View File

@ -0,0 +1,88 @@
#include "puzzle.h"
#include <cassert>
#include <iostream>
#include <sstream>
namespace {
uint8_t RowStart(uint8_t id) { return (id / 9) * 9; }
uint8_t ColStart(uint8_t id) { return id % 9; }
uint8_t BoxStart(uint8_t id) {
uint8_t row = (RowStart(id) / 27) * 27;
uint8_t col = (ColStart(id) / 3) * 3;
return row + col;
}
constexpr std::array<uint8_t, 9> kBoxOffsets{0, 1, 2, 9, 10, 11, 18, 19, 20};
} // namespace
Puzzle Puzzle::FromString(std::string puzzle) {
assert(puzzle.length() == 81);
Puzzle p;
for (int i = 0; i < 81; i++) {
if (puzzle[i] == '.' || puzzle[i] == '0') {
continue;
}
int diff = puzzle[i] - '0';
if (diff < 1 || diff > 9) {
assert(false && "Invalid input character");
}
p.AssignSquare(i, diff);
}
return p;
}
std::string Puzzle::CurrentState() {
std::ostringstream str;
for (const Cell& c : cells_) {
if (c.IsSolved()) {
str << (int)c.value();
} else {
str << '.';
}
}
return str.str();
}
std::string Puzzle::PencilMarkState() {
std::ostringstream str;
for (const Cell& c : cells_) {
for (uint8_t i = 1; i <= 9; i++) {
if (c.IsPossible(i)) {
str << (int)i;
}
}
str << ",";
}
// Erase the trailing ",".
std::string temp = str.str();
temp.erase(temp.end() - 1);
return temp;
}
void Puzzle::AssignSquare(uint8_t id, uint8_t value) {
assert(id < 81);
assert(value >= 0 && value <= 9);
cells_[id] = Cell(value);
const uint8_t row = RowStart(id);
for (uint8_t i = row; i < row + 9; i++) {
cells_[i].Restrict(value);
}
const uint8_t col = ColStart(id);
for (uint8_t i = col; i < 81; i += 9) {
cells_[i].Restrict(value);
}
uint8_t box = BoxStart(id);
for (uint8_t offset : kBoxOffsets) {
cells_[box + offset].Restrict(value);
}
}
Puzzle::Puzzle() {}

20
solver/puzzle.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <optional>
#include <string>
#include "cell.h"
class Puzzle {
public:
static Puzzle FromString(std::string puzzle);
std::string CurrentState();
std::string PencilMarkState();
void AssignSquare(uint8_t id, uint8_t value);
private:
Puzzle();
std::array<Cell, 81> cells_;
};