commit 9bc87891964a37a0dfc09358e27a3d2f4ce111a1
parent 3754de91370eff1f2b330dc9ea2eb2e63f95dd6d
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date: Sat, 15 Jun 2024 00:25:42 +0200
Proper CMake project
Diffstat:
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;
+}