doasku

Human-like solver for sudoku
git clone git://git.dimitrijedobrota.com/doasku.git
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING

commit 9bc87891964a37a0dfc09358e27a3d2f4ce111a1
parent 3754de91370eff1f2b330dc9ea2eb2e63f95dd6d
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Fri, 14 Jun 2024 22:25:42 +0200

Proper CMake project

Diffstat:
A .gitignore | ++
A CMakeLists.txt | ++++++++++++++++++++
A include/cord.hpp | +++++++++++++++++++++++++++++++++++++++++++++
A include/grid.hpp | ++++++++++++++++++++++++++++++++++++++++++++
A include/ref.hpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D main.cpp | ---------------------------------------------------------------------------------
A src/CMakeLists.txt | ++++++++
A src/grid.cpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A src/main.cpp | +++++++++++++++
A src/ref.cpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

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;
}