doasku

Sudoku solver
git clone git://git.dimitrijedobrota.com/doasku.git
Log | Files | Refs | README | LICENSE

commit 9bc87891964a37a0dfc09358e27a3d2f4ce111a1
parent 3754de91370eff1f2b330dc9ea2eb2e63f95dd6d
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Sat, 15 Jun 2024 00:25:42 +0200

Proper CMake project

Diffstat:
A.gitignore | 2++
ACMakeLists.txt | 20++++++++++++++++++++
Ainclude/cord.hpp | 45+++++++++++++++++++++++++++++++++++++++++++++
Ainclude/grid.hpp | 44++++++++++++++++++++++++++++++++++++++++++++
Ainclude/ref.hpp | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dmain.cpp | 508-------------------------------------------------------------------------------
Asrc/CMakeLists.txt | 8++++++++
Asrc/grid.cpp | 240+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main.cpp | 15+++++++++++++++
Asrc/ref.cpp | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 593 insertions(+), 508 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +build +.cache diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.25.2) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +project( + doasku + VERSION 0.0.1 + DESCRIPTION "Sudoku madness" + HOMEPAGE_URL https://git.dimitrijedobrota.com/doasku.git + LANGUAGES CXX +) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) +endif() + +add_subdirectory(src) diff --git a/include/cord.hpp b/include/cord.hpp @@ -0,0 +1,45 @@ +#ifndef DOASKO_CORD_HPP +#define DOASKO_CORD_HPP + +#include <cassert> +#include <cinttypes> +#include <utility> + +struct cord_t { + cord_t(uint8_t value) : value(value) { assert(value < 9); } + cord_t(uint8_t row, uint8_t col) : value(row * 3 + col) { + assert(row < 3 && col < 3); + } + + operator uint8_t() const { return value; } + + uint8_t row() const { return value / 3; } + uint8_t col() const { return value % 3; } + + uint8_t value; +}; + +class acord_t { + public: + acord_t(cord_t subgrid, cord_t field) + : subgrid_i(subgrid), field_i(field) {} + + acord_t(uint8_t row, uint8_t col) + : subgrid_i(row / 3, col / 3), field_i(row % 3, col % 3) {} + + cord_t subgrid() const { return subgrid_i; } + cord_t field() const { return field_i; } + + uint8_t row() const { return subgrid_i.row() * 3 + field_i.row(); } + uint8_t col() const { return subgrid_i.col() * 3 + field_i.col(); } + + std::tuple<cord_t, cord_t> relative() const { + return {subgrid_i, field_i}; + } + + private: + cord_t subgrid_i; + cord_t field_i; +}; + +#endif diff --git a/include/grid.hpp b/include/grid.hpp @@ -0,0 +1,44 @@ +#ifndef DOASKU_GRID_HPP +#define DOASKU_GRID_HPP + +#include <string> +#include <utility> + +#include "cord.hpp" +#include "ref.hpp" + +class Grid { + public: + Grid(const std::string &s); + + bool solve(); + void print() const; + + private: + using operation_t = std::tuple<acord_t, uint16_t>; + + void op_set(operation_t op); + void op_mask(operation_t op); + void op_clear(operation_t op); + + void op_clear_row(operation_t op); + void op_clear_col(operation_t op); + + void op_clear_row_rel(operation_t op); + void op_clear_col_rel(operation_t op); + + void _set(acord_t ab, uint8_t value); + void _mask(acord_t ab, uint16_t mask); + void _clear(acord_t ab, uint8_t value); + + void _clear_row(cord_t sg, uint8_t row, uint8_t value); + void _clear_col(cord_t sg, uint8_t col, uint8_t value); + + bool _is_finished(uint8_t subgrid); + bool _is_finished(); + + Ref subgrids[9], rows[9], cols[9]; + bool changed = false; +}; + +#endif diff --git a/include/ref.hpp b/include/ref.hpp @@ -0,0 +1,63 @@ +#ifndef DOASKU_REF_HPP +#define DOASKU_REF_HPP + +#include <cstdint> +#include <utility> +#include <vector> + +class Ref { + public: + using change_t = std::tuple<uint8_t, uint16_t>; + using changes_t = std::vector<change_t>; + + uint16_t get(uint8_t field) const { return value[field]; } + uint16_t get_ref(uint8_t value) const { return ref[value]; } + uint16_t get_value(uint8_t field) const { return res[field]; } + + void clear(uint8_t field, uint8_t value); + void set(uint8_t field, uint8_t value); + + changes_t get_hidden_singles() const; + changes_t get_naked_singles() const; + + auto get_hidden_pairs() const { return get_hidden(2); } + auto get_hidden_triplets() const { return get_hidden(3); } + auto get_hidden_quads() const { return get_hidden(4); } + + auto get_naked_pairs() const { return get_naked(2); } + auto get_naked_triplets() const { return get_naked(3); } + auto get_naked_quads() const { return get_naked(4); } + + auto get_pointing_row() const { return get_pointing(0x7, 0); } + auto get_pointing_col() const { return get_pointing(0x49, 1); } + + private: + changes_t get_naked(int number) const; + changes_t get_hidden(int number) const; + changes_t get_pointing(uint16_t mask, bool seen) const; + + bool get_hidden(changes_t &res, uint8_t og, uint8_t number, uint8_t first, + uint16_t val, uint16_t mask) const; + + bool get_naked(changes_t &res, uint8_t og, uint8_t number, uint8_t first, + uint16_t val) const; + + static constexpr const std::int64_t mask_field = (1 << 9) - 1; + static constexpr const std::int64_t mask_value = 0x201008040201; + + uint16_t value[9] = {mask_field, mask_field, mask_field, + mask_field, mask_field, mask_field, + mask_field, mask_field, mask_field}; + + uint16_t ref[9] = {mask_field, mask_field, mask_field, + mask_field, mask_field, mask_field, + mask_field, mask_field, mask_field}; + + uint16_t res[9] = {0}; + + mutable uint16_t seen_hidden[4] = {0}; + mutable uint16_t seen_naked[4] = {0}; + mutable uint16_t seen_point[2] = {0}; +}; + +#endif diff --git a/main.cpp b/main.cpp @@ -1,508 +0,0 @@ -#include <algorithm> -#include <array> -#include <bit> -#include <bitset> -#include <cassert> -#include <cinttypes> -#include <cstring> -#include <format> -#include <iomanip> -#include <iostream> -#include <vector> - -template <class Input, class UnaryFunc> -UnaryFunc for_each(Input input, UnaryFunc f) { - return std::for_each(begin(input), end(input), f); -} - -struct cord_t { - cord_t(uint8_t value) : value(value) { assert(value < 9); } - cord_t(uint8_t row, uint8_t col) : value(row * 3 + col) { - assert(row < 3 && col < 3); - } - - operator uint8_t() const { return value; } - - uint8_t row() const { return value / 3; } - uint8_t col() const { return value % 3; } - - friend std::ostream &operator<<(std::ostream &os, cord_t cord) { - return os << std::format("({}, {})", cord.row(), cord.col()); - } - - uint8_t value; -}; - -class acord_t { - public: - acord_t(cord_t subgrid, cord_t field) - : subgrid_i(subgrid), field_i(field) {} - - acord_t(uint8_t row, uint8_t col) - : subgrid_i(row / 3, col / 3), field_i(row % 3, col % 3) {} - - cord_t subgrid() const { return subgrid_i; } - cord_t field() const { return field_i; } - - uint8_t row() const { return subgrid_i.row() * 3 + field_i.row(); } - uint8_t col() const { return subgrid_i.col() * 3 + field_i.col(); } - - std::tuple<cord_t, cord_t> relative() const { - return {subgrid_i, field_i}; - } - - friend std::ostream &operator<<(std::ostream &os, acord_t acord) { - return os << std::format("(({}, {}))", acord.row(), acord.col()); - } - - private: - cord_t subgrid_i; - cord_t field_i; -}; - -static std::tuple<uint8_t, uint8_t> other_subgrid_row(uint8_t subgrid) { - static std::tuple<uint8_t, uint8_t> mapping[9] = {{1, 2}, {0, 2}, {0, 1}, - {4, 5}, {3, 5}, {3, 4}, - {7, 8}, {6, 8}, {6, 7}}; - return mapping[subgrid]; -} - -static std::tuple<uint8_t, uint8_t> other_subgrid_col(uint8_t subgrid) { - static std::tuple<uint8_t, uint8_t> mapping[9] = {{3, 6}, {4, 7}, {5, 8}, - {0, 6}, {1, 7}, {2, 8}, - {0, 3}, {1, 4}, {2, 5}}; - return mapping[subgrid]; -} - -class Ref { - public: - using change_t = std::tuple<uint8_t, uint16_t>; - using changes_t = std::vector<change_t>; - - public: - uint16_t get(uint8_t field) const { return value[field]; } - uint16_t get_ref(uint8_t value) const { return ref[value]; } - uint16_t get_value(uint8_t field) const { return res[field]; } - - void clear(uint8_t field, uint8_t value) { - this->value[field] &= ~(1 << value); - ref[value] &= ~(1 << field); - } - - void set(uint8_t field, uint8_t value) { - for (uint8_t i = 0; i < 9; i++) { - ref[i] &= ~(1 << field); - } - - this->value[field] = ref[value] = 0; - res[field] = value + 1; - } - - changes_t get_hidden_singles() const { - changes_t res; - - for (uint8_t candidate = 0; candidate < 9; candidate++) { - if (std::popcount(ref[candidate]) != 1) continue; - res.emplace_back(std::countr_zero(ref[candidate]), candidate); - } - - return res; - } - - changes_t get_naked_singles() const { - changes_t res; - - for (uint8_t i = 0; i < 9; i++) { - const auto values = get(i); - if (std::popcount(values) != 1) continue; - const auto value = std::countr_zero(values); - if (!ref[value]) continue; - res.emplace_back(i, value); - } - - return res; - } - - auto get_hidden_pairs() const { return get_hidden(2); } - auto get_hidden_triplets() const { return get_hidden(3); } - auto get_hidden_quads() const { return get_hidden(4); } - - auto get_naked_pairs() const { return get_naked(2); } - auto get_naked_triplets() const { return get_naked(3); } - auto get_naked_quads() const { return get_naked(4); } - - auto get_pointing_row() const { return get_pointing(0x7, 0); } - auto get_pointing_col() const { return get_pointing(0x49, 1); } - - private: - changes_t get_hidden(int number) const { - assert(number > 1 && number <= 4); - - changes_t res; - get_hidden(res, number, number, 0, 0, 0); - return res; - } - - bool get_hidden(changes_t &res, uint8_t og, uint8_t number, uint8_t first, - uint16_t val, uint16_t mask) const { - if (number != 0) { - for (uint8_t i = first; i < 9; i++) { - if (std::popcount(ref[i]) < 2) continue; - if (seen_hidden[og] & (1ul << i)) continue; - - bool used = get_hidden(res, og, number - 1, i + 1, - val | ref[i], mask | (1 << i)); - if (!used) continue; - - seen_hidden[og] |= 1ul << i; - if (number != og) return true; - } - - return false; - } - - if (std::popcount(val) != og) return false; - - static uint8_t fields[9]; - uint8_t size = 0; - - while (val) { - const uint8_t idx = std::countr_zero(val); - fields[size++] = idx; - val ^= 1ull << idx; - } - - for (uint8_t i = 0; i < og; i++) { - const uint16_t change = - value[fields[i]] & ~(value[fields[i]] & mask); - if (!change) continue; - res.emplace_back(fields[i], change); - } - - return true; - } - - changes_t get_naked(int number) const { - assert(number > 1 && number <= 4); - - changes_t res; - get_naked(res, number, number, 0, 0); - return res; - } - - bool get_naked(changes_t &res, uint8_t og, uint8_t number, uint8_t first, - uint16_t val) const { - static uint8_t seen[4] = {0}; - - if (number != 0) { - for (uint8_t i = first; i < 9; i++) { - if (this->res[i]) continue; - if (number == og && seen_naked[og] & (1ul << i)) continue; - - seen[og - number] = i; - bool used = - get_naked(res, og, number - 1, i + 1, val | value[i]); - if (!used) continue; - - if (number == og) seen_naked[og] |= 1ul << i; - if (number != og) return true; - } - - return false; - } - - if (std::popcount(val) != og) return false; - - while (val) { - const uint8_t idx = std::countr_zero(val); - for (uint8_t pos = 0, i; pos < 9; pos++) { - if ((value[pos] & idx) == 0) continue; - for (i = 0; i < og; i++) { - if (pos == seen[i]) break; - } - if (i == og) res.emplace_back(pos, idx); - } - val ^= 1ull << idx; - } - - return true; - } - - changes_t get_pointing(uint16_t mask, bool seen) const { - changes_t res; - - for (uint8_t i = 0; i < 9; i++) { - const uint8_t popcnt = std::popcount(ref[i]); - if (popcnt < 2 || popcnt > 3) continue; - - if ((seen_point[seen] & (1 << i)) == 0) { - uint16_t cmask = mask; - for (uint8_t k = 0; k < 3; k++, cmask <<= 3) { - if (std::popcount(uint16_t(cmask & ref[i])) != popcnt) - continue; - - seen_point[seen] |= 1 << i; - res.emplace_back(k, i); - break; - } - } - } - - return res; - } - - static constexpr const std::int64_t mask_field = (1 << 9) - 1; - static constexpr const std::int64_t mask_value = 0x201008040201; - - uint16_t value[9] = {mask_field, mask_field, mask_field, - mask_field, mask_field, mask_field, - mask_field, mask_field, mask_field}; - - uint16_t ref[9] = {mask_field, mask_field, mask_field, - mask_field, mask_field, mask_field, - mask_field, mask_field, mask_field}; - - uint16_t res[9] = {0}; - - mutable uint16_t seen_hidden[4] = {0}; - mutable uint16_t seen_naked[4] = {0}; - mutable uint16_t seen_point[2] = {0}; -}; - -class Grid { - public: - Grid(const std::string &s) { - int idx = 0; - for (uint8_t i = 0; i < 9; i++) { - for (uint8_t j = 0; j < 9; j++, idx++) { - if (s[idx] == '0') continue; - _set({i, j}, s[idx] - '1'); - } - } - } - - bool solve() { - // clang-format off - static const auto sub_op = - [this](void (Grid::*op)(operation_t), Ref::changes_t (Ref::*f)() const) { - for(uint8_t subgrid = 0; subgrid < 9; subgrid++) { - for_each((subgrids[subgrid].*(f))(), [this, subgrid, op](const Ref::change_t ch) { - (this->*(op))(operation_t({cord_t(subgrid), cord_t(std::get<0>(ch))}, std::get<1>(ch))); - }); - } - return changed; - }; - - static const auto row_op = - [this](void (Grid::*op)(operation_t), Ref::changes_t (Ref::*f)() const) { - for(uint8_t row = 0; row < 9; row++) { - for_each((rows[row].*(f))(), [this, row, op](const Ref::change_t ch) { - (this->*(op))(operation_t({row, std::get<0>(ch)}, std::get<1>(ch))); - }); - } - return changed; - }; - - static const auto col_op = - [this](void (Grid::*op)(operation_t), Ref::changes_t (Ref::*f)() const) { - for(uint8_t col = 0; col < 9; col++) { - for_each((cols[col].*(f))(), [this, col, op](const Ref::change_t ch) { - (this->*(op))(operation_t({std::get<0>(ch), col}, std::get<1>(ch))); - }); - } - return changed; - }; - - static const auto all_op = - [this](void (Grid::*op)(operation_t), Ref::changes_t (Ref::*f)() const) { - sub_op(op, f), row_op(op, f), col_op(op, f); - return changed; - }; - // clang-format on - - changed = true; - while (changed) { - changed = false; - - if (sub_op(&Grid::op_set, &Ref::get_naked_singles)) continue; - if (all_op(&Grid::op_set, &Ref::get_hidden_singles)) continue; - if (sub_op(&Grid::op_clear_row, &Ref::get_pointing_row)) continue; - if (sub_op(&Grid::op_clear_col, &Ref::get_pointing_col)) continue; - if (all_op(&Grid::op_mask, &Ref::get_hidden_pairs)) continue; - if (all_op(&Grid::op_mask, &Ref::get_hidden_triplets)) continue; - if (all_op(&Grid::op_mask, &Ref::get_hidden_quads)) continue; - if (all_op(&Grid::op_clear, &Ref::get_naked_pairs)) continue; - if (all_op(&Grid::op_clear, &Ref::get_naked_triplets)) continue; - if (all_op(&Grid::op_clear, &Ref::get_naked_quads)) continue; - - row_op(&Grid::op_clear_row_rel, &Ref::get_pointing_row); - col_op(&Grid::op_clear_col_rel, &Ref::get_pointing_row); - } - - return _is_finished(); - } - - void print() const { - // clang-format off - static const auto print_i = [this](const Ref refs[9], uint16_t (Ref::*f)(uint8_t) const, bool bits) { - for (uint8_t i = 0; i < 9; i++) { - for (uint8_t j = 0; j < 9; j++) { - const cord_t subgrid = {uint8_t(i / 3), uint8_t(j / 3)}; - const cord_t field = {uint8_t(i % 3), uint8_t(j % 3)}; - - uint16_t value = (refs[subgrid].*(f))((uint8_t)field); - if (bits) std::cout << std::bitset<9>(value) << " "; - else std::cout << value << " "; - - if (j % 3 == 2) std::cout << " "; - } - - std::cout << std::endl; - if (i % 3 == 2) std::cout << std::endl; - } - }; - // clang-format on - - std::cout << "Field: " << std::endl; - print_i(subgrids, &Ref::get, true); - - std::cout << "Refs: " << std::endl; - print_i(subgrids, &Ref::get_ref, true); - - std::cout << "Board: " << std::endl; - print_i(subgrids, &Ref::get_value, false); - } - - private: - using operation_t = std::tuple<acord_t, uint16_t>; - - void op_set(operation_t op) { - _set(std::get<0>(op), std::get<1>(op)); - changed = true; - } - - void op_mask(operation_t op) { - _mask(std::get<0>(op), std::get<1>(op)); - changed = true; - } - - void op_clear(operation_t op) { - _clear(std::get<0>(op), std::get<1>(op)); - changed = true; - } - - void op_clear_row(operation_t op) { - const auto [ab, val] = op; - - const auto [r1, r2] = other_subgrid_row(ab.subgrid()); - _clear_row(r1, ab.field(), val); - _clear_row(r2, ab.field(), val); - - changed = true; - } - - void op_clear_col(operation_t op) { - const auto [ab, val] = op; - - const auto [c1, c2] = other_subgrid_col(ab.subgrid()); - _clear_col(c1, ab.field(), val); - _clear_col(c2, ab.field(), val); - - changed = true; - } - - void op_clear_row_rel(operation_t op) { - const auto [ab, val] = op; - - const auto [r1, r2] = other_subgrid_row(ab.row()); - _clear_row((r1 / 3) * 3 + ab.col(), r1 % 3, val); - _clear_row((r2 / 3) * 3 + ab.col(), r2 % 3, val); - - changed = true; - } - - void op_clear_col_rel(operation_t op) { - const auto [ab, val] = op; - - const auto [c1, c2] = other_subgrid_row(ab.col()); - _clear_col(ab.row() * 3 + c1 / 3, c1 % 3, val); - _clear_col(ab.row() * 3 + c2 / 3, c2 % 3, val); - - changed = true; - } - - void _set(acord_t ab, uint8_t value) { - rows[ab.row()].set(ab.col(), value); - cols[ab.col()].set(ab.row(), value); - subgrids[ab.subgrid()].set(ab.field(), value); - - _clear_row(ab.subgrid(), 0, value); - _clear_row(ab.subgrid(), 1, value); - _clear_row(ab.subgrid(), 2, value); - - const auto [r1, r2] = other_subgrid_row(ab.subgrid()); - _clear_row(r1, ab.field().row(), value); - _clear_row(r2, ab.field().row(), value); - - const auto [c1, c2] = other_subgrid_col(ab.subgrid()); - _clear_col(c1, ab.field().col(), value); - _clear_col(c2, ab.field().col(), value); - } - - void _mask(acord_t ab, uint16_t mask) { - while (mask) { - const uint8_t idx = std::countr_zero(mask); - _clear(ab, idx); - mask ^= 1ull << idx; - } - } - - void _clear(acord_t ab, uint8_t value) { - subgrids[ab.subgrid()].clear(ab.field(), value); - - rows[ab.row()].clear(ab.col(), value); - cols[ab.col()].clear(ab.row(), value); - } - - void _clear_row(cord_t sg, uint8_t row, uint8_t value) { - for (uint8_t i = 0; i < 3; i++) { - _clear({sg, {row, i}}, value); - } - } - - void _clear_col(cord_t sg, uint8_t col, uint8_t value) { - for (uint8_t i = 0; i < 3; i++) { - _clear({sg, {i, col}}, value); - } - } - - bool _is_finished(uint8_t subgrid) { - for (uint8_t i = 0; i < 9; i++) { - if (!subgrids[subgrid].get_value(i)) return false; - } - return true; - } - - bool _is_finished() { - for (uint8_t i = 0; i < 9; i++) { - if (!_is_finished(i)) return false; - } - return true; - } - - Ref subgrids[9], rows[9], cols[9]; - bool changed = false; -}; - -int main(const int argc, const char *argv[]) { - - for (int i = 1; i < argc; i++) { - Grid g(argv[i]); - - g.print(); - std::cout << (g.solve() ? "solved" : "unable to solve") << std::endl; - g.print(); - } - - return 0; -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(doasku main.cpp ref.cpp grid.cpp) +target_include_directories(doasku PRIVATE ../include) + +set_target_properties(doasku PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) diff --git a/src/grid.cpp b/src/grid.cpp @@ -0,0 +1,240 @@ +#include <algorithm> +#include <bitset> +#include <iostream> + +#include "grid.hpp" + +template <class Input, class UnaryFunc> +UnaryFunc for_each(Input input, UnaryFunc f) { + return std::for_each(begin(input), end(input), f); +} + +static std::tuple<uint8_t, uint8_t> other_subgrid_row(uint8_t subgrid) { + static std::tuple<uint8_t, uint8_t> mapping[9] = {{1, 2}, {0, 2}, {0, 1}, + {4, 5}, {3, 5}, {3, 4}, + {7, 8}, {6, 8}, {6, 7}}; + return mapping[subgrid]; +} + +static std::tuple<uint8_t, uint8_t> other_subgrid_col(uint8_t subgrid) { + static std::tuple<uint8_t, uint8_t> mapping[9] = {{3, 6}, {4, 7}, {5, 8}, + {0, 6}, {1, 7}, {2, 8}, + {0, 3}, {1, 4}, {2, 5}}; + return mapping[subgrid]; +} + +Grid::Grid(const std::string &s) { + int idx = 0; + for (uint8_t i = 0; i < 9; i++) { + for (uint8_t j = 0; j < 9; j++, idx++) { + if (s[idx] == '0') continue; + _set({i, j}, s[idx] - '1'); + } + } +} + +bool Grid::solve() { + // clang-format off + static const auto sub_op = + [this](void (Grid::*op)(operation_t), Ref::changes_t (Ref::*f)() const) { + for(uint8_t subgrid = 0; subgrid < 9; subgrid++) { + for_each((subgrids[subgrid].*(f))(), [this, subgrid, op](const Ref::change_t ch) { + (this->*(op))(operation_t({cord_t(subgrid), cord_t(std::get<0>(ch))}, std::get<1>(ch))); + }); + } + return changed; + }; + + static const auto row_op = + [this](void (Grid::*op)(operation_t), Ref::changes_t (Ref::*f)() const) { + for(uint8_t row = 0; row < 9; row++) { + for_each((rows[row].*(f))(), [this, row, op](const Ref::change_t ch) { + (this->*(op))(operation_t({row, std::get<0>(ch)}, std::get<1>(ch))); + }); + } + return changed; + }; + + static const auto col_op = + [this](void (Grid::*op)(operation_t), Ref::changes_t (Ref::*f)() const) { + for(uint8_t col = 0; col < 9; col++) { + for_each((cols[col].*(f))(), [this, col, op](const Ref::change_t ch) { + (this->*(op))(operation_t({std::get<0>(ch), col}, std::get<1>(ch))); + }); + } + return changed; + }; + + static const auto all_op = + [this](void (Grid::*op)(operation_t), Ref::changes_t (Ref::*f)() const) { + sub_op(op, f), row_op(op, f), col_op(op, f); + return changed; + }; + // clang-format on + + changed = true; + while (changed) { + changed = false; + + if (sub_op(&Grid::op_set, &Ref::get_naked_singles)) continue; + if (all_op(&Grid::op_set, &Ref::get_hidden_singles)) continue; + if (sub_op(&Grid::op_clear_row, &Ref::get_pointing_row)) continue; + if (sub_op(&Grid::op_clear_col, &Ref::get_pointing_col)) continue; + if (all_op(&Grid::op_mask, &Ref::get_hidden_pairs)) continue; + if (all_op(&Grid::op_mask, &Ref::get_hidden_triplets)) continue; + if (all_op(&Grid::op_mask, &Ref::get_hidden_quads)) continue; + if (all_op(&Grid::op_clear, &Ref::get_naked_pairs)) continue; + if (all_op(&Grid::op_clear, &Ref::get_naked_triplets)) continue; + if (all_op(&Grid::op_clear, &Ref::get_naked_quads)) continue; + + row_op(&Grid::op_clear_row_rel, &Ref::get_pointing_row); + col_op(&Grid::op_clear_col_rel, &Ref::get_pointing_row); + } + + return _is_finished(); +} + +void Grid::print() const { + // clang-format off + static const auto print_i = [this](const Ref refs[9], uint16_t (Ref::*f)(uint8_t) const, bool bits) { + for (uint8_t i = 0; i < 9; i++) { + for (uint8_t j = 0; j < 9; j++) { + const cord_t subgrid = {uint8_t(i / 3), uint8_t(j / 3)}; + const cord_t field = {uint8_t(i % 3), uint8_t(j % 3)}; + + uint16_t value = (refs[subgrid].*(f))((uint8_t)field); + if (bits) std::cout << std::bitset<9>(value) << " "; + else std::cout << value << " "; + + if (j % 3 == 2) std::cout << " "; + } + + std::cout << std::endl; + if (i % 3 == 2) std::cout << std::endl; + } + }; + // clang-format on + + std::cout << "Field: " << std::endl; + print_i(subgrids, &Ref::get, true); + + std::cout << "Refs: " << std::endl; + print_i(subgrids, &Ref::get_ref, true); + + std::cout << "Board: " << std::endl; + print_i(subgrids, &Ref::get_value, false); +} + +void Grid::op_set(operation_t op) { + _set(std::get<0>(op), std::get<1>(op)); + changed = true; +} + +void Grid::op_mask(operation_t op) { + _mask(std::get<0>(op), std::get<1>(op)); + changed = true; +} + +void Grid::op_clear(operation_t op) { + _clear(std::get<0>(op), std::get<1>(op)); + changed = true; +} + +void Grid::op_clear_row(operation_t op) { + const auto [ab, val] = op; + + const auto [r1, r2] = other_subgrid_row(ab.subgrid()); + _clear_row(r1, ab.field(), val); + _clear_row(r2, ab.field(), val); + + changed = true; +} + +void Grid::op_clear_col(operation_t op) { + const auto [ab, val] = op; + + const auto [c1, c2] = other_subgrid_col(ab.subgrid()); + _clear_col(c1, ab.field(), val); + _clear_col(c2, ab.field(), val); + + changed = true; +} + +void Grid::op_clear_row_rel(operation_t op) { + const auto [ab, val] = op; + + const auto [r1, r2] = other_subgrid_row(ab.row()); + _clear_row((r1 / 3) * 3 + ab.col(), r1 % 3, val); + _clear_row((r2 / 3) * 3 + ab.col(), r2 % 3, val); + + changed = true; +} + +void Grid::op_clear_col_rel(operation_t op) { + const auto [ab, val] = op; + + const auto [c1, c2] = other_subgrid_row(ab.col()); + _clear_col(ab.row() * 3 + c1 / 3, c1 % 3, val); + _clear_col(ab.row() * 3 + c2 / 3, c2 % 3, val); + + changed = true; +} + +void Grid::_set(acord_t ab, uint8_t value) { + rows[ab.row()].set(ab.col(), value); + cols[ab.col()].set(ab.row(), value); + subgrids[ab.subgrid()].set(ab.field(), value); + + _clear_row(ab.subgrid(), 0, value); + _clear_row(ab.subgrid(), 1, value); + _clear_row(ab.subgrid(), 2, value); + + const auto [r1, r2] = other_subgrid_row(ab.subgrid()); + _clear_row(r1, ab.field().row(), value); + _clear_row(r2, ab.field().row(), value); + + const auto [c1, c2] = other_subgrid_col(ab.subgrid()); + _clear_col(c1, ab.field().col(), value); + _clear_col(c2, ab.field().col(), value); +} + +void Grid::_mask(acord_t ab, uint16_t mask) { + while (mask) { + const uint8_t idx = std::countr_zero(mask); + _clear(ab, idx); + mask ^= 1ull << idx; + } +} + +void Grid::_clear(acord_t ab, uint8_t value) { + subgrids[ab.subgrid()].clear(ab.field(), value); + + rows[ab.row()].clear(ab.col(), value); + cols[ab.col()].clear(ab.row(), value); +} + +void Grid::_clear_row(cord_t sg, uint8_t row, uint8_t value) { + for (uint8_t i = 0; i < 3; i++) { + _clear({sg, {row, i}}, value); + } +} + +void Grid::_clear_col(cord_t sg, uint8_t col, uint8_t value) { + for (uint8_t i = 0; i < 3; i++) { + _clear({sg, {i, col}}, value); + } +} + +bool Grid::_is_finished(uint8_t subgrid) { + for (uint8_t i = 0; i < 9; i++) { + if (!subgrids[subgrid].get_value(i)) return false; + } + return true; +} + +bool Grid::_is_finished() { + for (uint8_t i = 0; i < 9; i++) { + if (!_is_finished(i)) return false; + } + return true; +} diff --git a/src/main.cpp b/src/main.cpp @@ -0,0 +1,15 @@ +#include <iostream> + +#include "grid.hpp" + +int main(const int argc, const char *argv[]) { + for (int i = 1; i < argc; i++) { + Grid g(argv[i]); + + g.print(); + std::cout << (g.solve() ? "solved" : "unable to solve") << std::endl; + g.print(); + } + + return 0; +} diff --git a/src/ref.cpp b/src/ref.cpp @@ -0,0 +1,156 @@ +#include <cassert> + +#include "ref.hpp" + +void Ref::clear(uint8_t field, uint8_t value) { + this->value[field] &= ~(1 << value); + ref[value] &= ~(1 << field); +} + +void Ref::set(uint8_t field, uint8_t value) { + for (uint8_t i = 0; i < 9; i++) { + ref[i] &= ~(1 << field); + } + + this->value[field] = ref[value] = 0; + res[field] = value + 1; +} + +Ref::changes_t Ref::get_hidden_singles() const { + changes_t res; + + for (uint8_t candidate = 0; candidate < 9; candidate++) { + if (std::popcount(ref[candidate]) != 1) continue; + res.emplace_back(std::countr_zero(ref[candidate]), candidate); + } + + return res; +} + +Ref::changes_t Ref::get_naked_singles() const { + changes_t res; + + for (uint8_t i = 0; i < 9; i++) { + const auto values = get(i); + if (std::popcount(values) != 1) continue; + const auto value = std::countr_zero(values); + if (!ref[value]) continue; + res.emplace_back(i, value); + } + + return res; +} + +Ref::changes_t Ref::get_hidden(int number) const { + assert(number > 1 && number <= 4); + + changes_t res; + get_hidden(res, number, number, 0, 0, 0); + return res; +} + +bool Ref::get_hidden(changes_t &res, uint8_t og, uint8_t number, uint8_t first, + uint16_t val, uint16_t mask) const { + if (number != 0) { + for (uint8_t i = first; i < 9; i++) { + if (std::popcount(ref[i]) < 2) continue; + if (seen_hidden[og] & (1ul << i)) continue; + + bool used = get_hidden(res, og, number - 1, i + 1, val | ref[i], + mask | (1 << i)); + if (!used) continue; + + seen_hidden[og] |= 1ul << i; + if (number != og) return true; + } + + return false; + } + + if (std::popcount(val) != og) return false; + + static uint8_t fields[9]; + uint8_t size = 0; + + while (val) { + const uint8_t idx = std::countr_zero(val); + fields[size++] = idx; + val ^= 1ull << idx; + } + + for (uint8_t i = 0; i < og; i++) { + const uint16_t change = value[fields[i]] & ~(value[fields[i]] & mask); + if (!change) continue; + res.emplace_back(fields[i], change); + } + + return true; +} + +Ref::changes_t Ref::get_naked(int number) const { + assert(number > 1 && number <= 4); + + changes_t res; + get_naked(res, number, number, 0, 0); + return res; +} + +bool Ref::get_naked(changes_t &res, uint8_t og, uint8_t number, uint8_t first, + uint16_t val) const { + static uint8_t seen[4] = {0}; + + if (number != 0) { + for (uint8_t i = first; i < 9; i++) { + if (this->res[i]) continue; + if (number == og && seen_naked[og] & (1ul << i)) continue; + + seen[og - number] = i; + bool used = get_naked(res, og, number - 1, i + 1, val | value[i]); + if (!used) continue; + + if (number == og) seen_naked[og] |= 1ul << i; + if (number != og) return true; + } + + return false; + } + + if (std::popcount(val) != og) return false; + + while (val) { + const uint8_t idx = std::countr_zero(val); + for (uint8_t pos = 0, i; pos < 9; pos++) { + if ((value[pos] & idx) == 0) continue; + for (i = 0; i < og; i++) { + if (pos == seen[i]) break; + } + if (i == og) res.emplace_back(pos, idx); + } + val ^= 1ull << idx; + } + + return true; +} + +Ref::changes_t Ref::get_pointing(uint16_t mask, bool seen) const { + changes_t res; + + for (uint8_t i = 0; i < 9; i++) { + const uint8_t popcnt = std::popcount(ref[i]); + if (popcnt < 2 || popcnt > 3) continue; + + if ((seen_point[seen] & (1 << i)) == 0) { + uint16_t cmask = mask; + for (uint8_t k = 0; k < 3; k++, cmask <<= 3) { + if (std::popcount(uint16_t(cmask & ref[i])) != popcnt) + continue; + + seen_point[seen] |= 1 << i; + res.emplace_back(k, i); + break; + } + } + } + + return res; +}