From bcc5b5097ef1c5f042986640483c8a8515a43bf2 Mon Sep 17 00:00:00 2001 From: Drew Galbraith Date: Mon, 17 Apr 2023 16:31:47 -0700 Subject: [PATCH] First pass at a sudoku solver: Puzzle Import Imports a puzzle from a string and displays the restricted puzzle. --- .clang-format | 1 + .gitignore | 2 ++ CMakeLists.txt | 14 ++++++++ solver.cpp | 11 ++++++ solver/cell.cpp | 17 +++++++++ solver/cell.h | 26 ++++++++++++++ solver/puzzle.cpp | 88 +++++++++++++++++++++++++++++++++++++++++++++++ solver/puzzle.h | 20 +++++++++++ 8 files changed, 179 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 solver.cpp create mode 100644 solver/cell.cpp create mode 100644 solver/cell.h create mode 100644 solver/puzzle.cpp create mode 100644 solver/puzzle.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f6cb8ad --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: Google diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3392a0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +builddir/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f315f6c --- /dev/null +++ b/CMakeLists.txt @@ -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}) diff --git a/solver.cpp b/solver.cpp new file mode 100644 index 0000000..b71947e --- /dev/null +++ b/solver.cpp @@ -0,0 +1,11 @@ +#include + +#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; +} diff --git a/solver/cell.cpp b/solver/cell.cpp new file mode 100644 index 0000000..c777c10 --- /dev/null +++ b/solver/cell.cpp @@ -0,0 +1,17 @@ +#include "cell.h" + +#include + +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; +} diff --git a/solver/cell.h b/solver/cell.h new file mode 100644 index 0000000..7396577 --- /dev/null +++ b/solver/cell.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include + +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 possibilities_; +}; diff --git a/solver/puzzle.cpp b/solver/puzzle.cpp new file mode 100644 index 0000000..808f8eb --- /dev/null +++ b/solver/puzzle.cpp @@ -0,0 +1,88 @@ +#include "puzzle.h" + +#include +#include +#include + +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 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() {} diff --git a/solver/puzzle.h b/solver/puzzle.h new file mode 100644 index 0000000..c67c2f8 --- /dev/null +++ b/solver/puzzle.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#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 cells_; +};