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 |

commit4595a3d8cf36dcc69ae1d659a6c4ff48c5ea22b9
parent8f55dc256a616127fe139c92c22c6ceffc8bdd70
authorDimitrije Dobrota <mail@dimitrijedobrota.com>
dateFri, 21 Jun 2024 17:32:56 +0200

Use cmake-init and modernize codebase

Diffstat:
M.clang-format|+++++++++++++++++++++++++++++++++++-----------------------------------------------
A.clang-tidy|+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.codespellrc|++++++
M.gitignore|+++++++++++--
ABUILDING.md|++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MCMakeLists.txt|++++++++++++++++++++++++++++++++----------
ACMakePresets.json|+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACODE_OF_CONDUCT.md|+++++
ACONTRIBUTING.md|++++++++++++++
AHACKING.md|+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ALICENSE.md|+++++++++++++++++++++
AREADME.md|+++++++++++++++
Acmake/coverage.cmake|+++++++++++++++++++++++++++++++++
Acmake/dev-mode.cmake|++++++++++++++++++
Acmake/folders.cmake|+++++++++++++++++++++
Acmake/install-rules.cmake|++++++++
Acmake/lint-targets.cmake|+++++++++++++++++++++++++++++++++
Acmake/lint.cmake|+++++++++++++++++++++++++++++++++++++++++++++++++++
Acmake/prelude.cmake|++++++++++
Acmake/project-is-top-level.cmake|++++++
Acmake/spell-targets.cmake|++++++++++++++++++++++
Acmake/spell.cmake|+++++++++++++++++++++++++++++
Acmake/variables.cmake|++++++++++++++++++++++++++++
Dinclude/cord.hpp|---------------------------------------------
Dinclude/grid.hpp|--------------------------------------------
Dinclude/ref.hpp|---------------------------------------------------------------
Asource/cord.hpp|+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/grid.cpp|+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/grid.hpp|++++++++++++++++++++++++++++++++++++++++++++++
Asource/main.cpp|+++++++++++++++++++
Asource/ref.cpp|+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ref.hpp|+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/CMakeLists.txt|--------
Dsrc/grid.cpp|---------------------------------------------------------------------------------
Dsrc/main.cpp|---------------
Dsrc/ref.cpp|---------------------------------------------------------------------------------

36 files changed, 1681 insertions(+), 730 deletions(-)


diff --git a/.clang-format b/.clang-format

@@ -1,216 +1,178 @@

---
Language: Cpp
# BasedOnStyle: Microsoft
Language: Cpp
# BasedOnStyle: Chromium
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments: true
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveBitFields: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: DontAlign
AlignOperands: DontAlign
AlignTrailingComments: false
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: true
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: MultiLine
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: true
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
# BreakBeforeInheritanceComma: true
BreakInheritanceList: BeforeComma
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakConstructorInitializersBeforeComma: true
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: true
BreakStringLiterals: true
ColumnLimit: 79
CommentPragmas: '^ IWYU pragma:'
QualifierAlignment: Leave
ColumnLimit: 79
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DeriveLineEnding: false
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
PackConstructorInitializers: BinPack
BasedOnStyle: ''
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowAllConstructorInitializersOnNextLine: true
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
# Standard library headers come before anything else
- Regex: '^<[a-z_]+>'
Priority: -1
- Regex: '^<.+\.h(pp)?>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: ''
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseLabels: false
IndentCaseLabels: true
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequiresClause: true
IndentWidth: 4
IndentPPDirectives: AfterHash
IndentExternBlock: NoIndent
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertBraces: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
InsertTrailingCommas: Wrapped
JavaScriptQuotes: Double
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 1000
PenaltyIndentedWhitespace: 0
PointerAlignment: Right
PPIndentWidth: -1
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RequiresClausePosition: OwnLine
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
- ParseTestProto
- ParsePartialTestProto
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeParens: ControlStatementsExceptForEachMacros
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
Standard: Auto
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseCRLF: false
UseTab: Never
TabWidth: 8
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...

diff --git a/.clang-tidy b/.clang-tidy

@@ -0,0 +1,165 @@

---
# Enable ALL the things! Except not really
# misc-non-private-member-variables-in-classes: the options don't do anything
# modernize-use-nodiscard: too aggressive, attribute is situationally useful
Checks: "*,\
-altera-*,\
-fuchsia-*,\
-llvmlibc-*,\
-*-braces-around-statements,\
-bugprone-argument-comment,\
-bugprone-easily-swappable-parameters,\
-cppcoreguidelines-avoid-magic-numbers,\
-hicpp-signed-bitwise,\
-llvm-header-guard,\
-llvm-include-order,\
-modernize-use-nodiscard,\
-modernize-use-trailing-return-type,\
-readability-function-cognitive-complexity,\
-readability-magic-numbers,\
fuchsia-multiple-inheritance,\
-misc-no-recursion,\
-misc-non-private-member-variables-in-classes"
WarningsAsErrors: ''
CheckOptions:
- key: 'bugprone-argument-comment.StrictMode'
value: 'true'
# Prefer using enum classes with 2 values for parameters instead of bools
- key: 'bugprone-argument-comment.CommentBoolLiterals'
value: 'true'
- key: 'bugprone-misplaced-widening-cast.CheckImplicitCasts'
value: 'true'
- key: 'bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression'
value: 'true'
- key: 'bugprone-suspicious-string-compare.WarnOnLogicalNotComparison'
value: 'true'
- key: 'readability-simplify-boolean-expr.ChainedConditionalReturn'
value: 'true'
- key: 'readability-simplify-boolean-expr.ChainedConditionalAssignment'
value: 'true'
- key: 'readability-uniqueptr-delete-release.PreferResetCall'
value: 'true'
- key: 'cppcoreguidelines-init-variables.MathHeader'
value: '<cmath>'
- key: 'cppcoreguidelines-narrowing-conversions.PedanticMode'
value: 'true'
- key: 'readability-else-after-return.WarnOnUnfixable'
value: 'true'
- key: 'readability-else-after-return.WarnOnConditionVariables'
value: 'true'
- key: 'readability-inconsistent-declaration-parameter-name.Strict'
value: 'true'
- key: 'readability-qualified-auto.AddConstToQualified'
value: 'true'
- key: 'readability-redundant-access-specifiers.CheckFirstDeclaration'
value: 'true'
# These seem to be the most common identifier styles
- key: 'readability-identifier-naming.AbstractClassCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstantMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstantParameterCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstantPointerParameterCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstexprFunctionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstexprMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstexprVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.EnumCase'
value: 'lower_case'
- key: 'readability-identifier-naming.EnumConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.FunctionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalConstantPointerCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalFunctionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalPointerCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.InlineNamespaceCase'
value: 'lower_case'
- key: 'readability-identifier-naming.LocalConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.LocalConstantPointerCase'
value: 'lower_case'
- key: 'readability-identifier-naming.LocalPointerCase'
value: 'lower_case'
- key: 'readability-identifier-naming.LocalVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.MacroDefinitionCase'
value: 'UPPER_CASE'
- key: 'readability-identifier-naming.MemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.MethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.NamespaceCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ParameterCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ParameterPackCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PointerParameterCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PrivateMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PrivateMemberPrefix'
value: 'm_'
- key: 'readability-identifier-naming.PrivateMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ProtectedMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ProtectedMemberPrefix'
value: 'm_'
- key: 'readability-identifier-naming.ProtectedMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PublicMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PublicMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ScopedEnumConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.StaticConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.StaticVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.StructCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.TemplateTemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.TypeAliasCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TypedefCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TypeTemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.TypeTemplateParameterIgnoredRegexp',
value: 'expr-type'
- key: 'readability-identifier-naming.UnionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ValueTemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.VariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.VirtualMethodCase'
value: 'lower_case'
...

diff --git a/.codespellrc b/.codespellrc

@@ -0,0 +1,6 @@

[codespell]
builtin = clear,rare,en-GB_to_en-US,names,informal,code
check-filenames =
check-hidden =
skip = */.git,*/build,*/prefix
quiet-level = 2

diff --git a/.gitignore b/.gitignore

@@ -1,2 +1,11 @@

build
.cache
**/.DS_Store
.idea/
.vs/
.vscode/
build/
cmake-build-*/
prefix/
.clangd
CMakeLists.txt.user
CMakeUserPresets.json
compile_commands.json

diff --git a/BUILDING.md b/BUILDING.md

@@ -0,0 +1,60 @@

# Building with CMake
## Build
This project doesn't require any special command-line flags to build to keep
things simple.
Here are the steps for building in release mode with a single-configuration
generator, like the Unix Makefiles one:
```sh
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release
cmake --build build
```
Here are the steps for building in release mode with a multi-configuration
generator, like the Visual Studio ones:
```sh
cmake -S . -B build
cmake --build build --config Release
```
### Building with MSVC
Note that MSVC by default is not standards compliant and you need to pass some
flags to make it behave properly. See the `flags-msvc` preset in the
[CMakePresets.json](CMakePresets.json) file for the flags and with what
variable to provide them to CMake during configuration.
### Building on Apple Silicon
CMake supports building on Apple Silicon properly since 3.20.1. Make sure you
have the [latest version][1] installed.
## Install
This project doesn't require any special command-line flags to install to keep
things simple. As a prerequisite, the project has to be built with the above
commands already.
The below commands require at least CMake 3.15 to run, because that is the
version in which [Install a Project][2] was added.
Here is the command for installing the release mode artifacts with a
single-configuration generator, like the Unix Makefiles one:
```sh
cmake --install build
```
Here is the command for installing the release mode artifacts with a
multi-configuration generator, like the Visual Studio ones:
```sh
cmake --install build --config Release
```
[1]: https://cmake.org/download/
[2]: https://cmake.org/cmake/help/latest/manual/cmake.1.html#install-a-project

diff --git a/CMakeLists.txt b/CMakeLists.txt

@@ -1,20 +1,42 @@

cmake_minimum_required(VERSION 3.25.2)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
cmake_minimum_required(VERSION 3.14)
include(cmake/prelude.cmake)
project(
doasku
VERSION 0.0.2
VERSION 0.0.3
DESCRIPTION "Sudoku madness"
HOMEPAGE_URL https://git.dimitrijedobrota.com/doasku.git
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)
include(cmake/project-is-top-level.cmake)
include(cmake/variables.cmake)
# ---- Declare executable ----
add_executable(doasku_exe source/main.cpp source/grid.cpp source/ref.cpp)
add_executable(doasku::exe ALIAS doasku_exe)
set_property(TARGET doasku_exe PROPERTY OUTPUT_NAME doasku)
target_compile_features(doasku_exe PRIVATE cxx_std_20)
# ---- Install rules ----
if(NOT CMAKE_SKIP_INSTALL_RULES)
include(cmake/install-rules.cmake)
endif()
# ---- Developer mode ----
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
if(NOT doasku_DEVELOPER_MODE)
return()
elseif(NOT PROJECT_IS_TOP_LEVEL)
message(
AUTHOR_WARNING
"Developer mode is intended for developers of doasku"
)
endif()
add_subdirectory(src)
include(cmake/dev-mode.cmake)

diff --git a/CMakePresets.json b/CMakePresets.json

@@ -0,0 +1,159 @@

{
"version": 2,
"cmakeMinimumRequired": {
"major": 3,
"minor": 14,
"patch": 0
},
"configurePresets": [
{
"name": "cmake-pedantic",
"hidden": true,
"warnings": {
"dev": true,
"deprecated": true,
"uninitialized": true,
"unusedCli": true,
"systemVars": false
},
"errors": {
"dev": true,
"deprecated": true
}
},
{
"name": "dev-mode",
"hidden": true,
"inherits": "cmake-pedantic",
"cacheVariables": {
"doasku_DEVELOPER_MODE": "ON"
}
},
{
"name": "cppcheck",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_CPPCHECK": "cppcheck;--inline-suppr"
}
},
{
"name": "clang-tidy",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=^${sourceDir}/"
}
},
{
"name": "ci-std",
"description": "This preset makes sure the project actually builds with at least the specified standard",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_EXTENSIONS": "OFF",
"CMAKE_CXX_STANDARD": "20",
"CMAKE_CXX_STANDARD_REQUIRED": "ON"
}
},
{
"name": "flags-gcc-clang",
"description": "These flags are supported by both GCC and Clang",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS=1 -fstack-protector-strong -fcf-protection=full -fstack-clash-protection -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast",
"CMAKE_EXE_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now,-z,nodlopen",
"CMAKE_SHARED_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now,-z,nodlopen"
}
},
{
"name": "flags-appleclang",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-fstack-protector-strong -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast"
}
},
{
"name": "flags-msvc",
"description": "Note that all the flags after /W4 are required for MSVC to conform to the language standard",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "/sdl /guard:cf /utf-8 /diagnostics:caret /w14165 /w44242 /w44254 /w44263 /w34265 /w34287 /w44296 /w44365 /w44388 /w44464 /w14545 /w14546 /w14547 /w14549 /w14555 /w34619 /w34640 /w24826 /w14905 /w14906 /w14928 /w45038 /W4 /permissive- /volatile:iso /Zc:inline /Zc:preprocessor /Zc:enumTypes /Zc:lambda /Zc:__cplusplus /Zc:externConstexpr /Zc:throwingNew /EHsc",
"CMAKE_EXE_LINKER_FLAGS": "/machine:x64 /guard:cf"
}
},
{
"name": "ci-linux",
"inherits": ["flags-gcc-clang", "ci-std"],
"generator": "Unix Makefiles",
"hidden": true,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "ci-darwin",
"inherits": ["flags-appleclang", "ci-std"],
"generator": "Xcode",
"hidden": true
},
{
"name": "ci-win64",
"inherits": ["flags-msvc", "ci-std"],
"generator": "Visual Studio 17 2022",
"architecture": "x64",
"hidden": true
},
{
"name": "coverage-linux",
"binaryDir": "${sourceDir}/build/coverage",
"inherits": "ci-linux",
"hidden": true,
"cacheVariables": {
"ENABLE_COVERAGE": "ON",
"CMAKE_BUILD_TYPE": "Coverage",
"CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions",
"CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage",
"CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage"
}
},
{
"name": "ci-coverage",
"inherits": ["coverage-linux", "dev-mode"],
"cacheVariables": {
"COVERAGE_HTML_COMMAND": ""
}
},
{
"name": "ci-sanitize",
"binaryDir": "${sourceDir}/build/sanitize",
"inherits": ["ci-linux", "dev-mode"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Sanitize",
"CMAKE_CXX_FLAGS_SANITIZE": "-U_FORTIFY_SOURCE -O2 -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-common"
}
},
{
"name": "ci-build",
"binaryDir": "${sourceDir}/build",
"hidden": true
},
{
"name": "ci-multi-config",
"description": "Speed up multi-config generators by generating only one configuration instead of the defaults",
"hidden": true,
"cacheVariables": {
"CMAKE_CONFIGURATION_TYPES": "Release"
}
},
{
"name": "ci-macos",
"inherits": ["ci-build", "ci-darwin", "dev-mode", "ci-multi-config"]
},
{
"name": "ci-ubuntu",
"inherits": ["ci-build", "ci-linux", "clang-tidy", "cppcheck", "dev-mode"]
},
{
"name": "ci-windows",
"inherits": ["ci-build", "ci-win64", "dev-mode", "ci-multi-config"]
}
]
}

diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md

@@ -0,0 +1,5 @@

# Code of Conduct
* You will be judged by your contributions first, and your sense of humor
second.
* Nobody owes you anything.

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md

@@ -0,0 +1,14 @@

# Contributing
## Code of Conduct
Please see the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) document.
## Getting started
Helpful notes for developers can be found in the [`HACKING.md`](HACKING.md)
document.
In addition to he above, if you use the presets file as instructed, then you
should NOT check it into source control, just as the CMake documentation
suggests.

diff --git a/HACKING.md b/HACKING.md

@@ -0,0 +1,149 @@

# Hacking
Here is some wisdom to help you build and test this project as a developer and
potential contributor.
If you plan to contribute, please read the [CONTRIBUTING](CONTRIBUTING.md)
guide.
## Developer mode
Build system targets that are only useful for developers of this project are
hidden if the `doasku_DEVELOPER_MODE` option is disabled. Enabling this
option makes tests and other developer targets and options available. Not
enabling this option means that you are a consumer of this project and thus you
have no need for these targets and options.
Developer mode is always set to on in CI workflows.
### Presets
This project makes use of [presets][1] to simplify the process of configuring
the project. As a developer, you are recommended to always have the [latest
CMake version][2] installed to make use of the latest Quality-of-Life
additions.
You have a few options to pass `doasku_DEVELOPER_MODE` to the configure
command, but this project prefers to use presets.
As a developer, you should create a `CMakeUserPresets.json` file at the root of
the project:
```json
{
"version": 2,
"cmakeMinimumRequired": {
"major": 3,
"minor": 14,
"patch": 0
},
"configurePresets": [
{
"name": "dev",
"binaryDir": "${sourceDir}/build/dev",
"inherits": ["dev-mode", "ci-<os>"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}
],
"buildPresets": [
{
"name": "dev",
"configurePreset": "dev",
"configuration": "Debug"
}
],
"testPresets": [
{
"name": "dev",
"configurePreset": "dev",
"configuration": "Debug",
"output": {
"outputOnFailure": true
}
}
]
}
```
You should replace `<os>` in your newly created presets file with the name of
the operating system you have, which may be `win64`, `linux` or `darwin`. You
can see what these correspond to in the
[`CMakePresets.json`](CMakePresets.json) file.
`CMakeUserPresets.json` is also the perfect place in which you can put all
sorts of things that you would otherwise want to pass to the configure command
in the terminal.
> **Note**
> Some editors are pretty greedy with how they open projects with presets.
> Some just randomly pick a preset and start configuring without your consent,
> which can be confusing. Make sure that your editor configures when you
> actually want it to, for example in CLion you have to make sure only the
> `dev-dev preset` has `Enable profile` ticked in
> `File > Settings... > Build, Execution, Deployment > CMake` and in Visual
> Studio you have to set the option `Never run configure step automatically`
> in `Tools > Options > CMake` **prior to opening the project**, after which
> you can manually configure using `Project > Configure Cache`.
### Configure, build and test
If you followed the above instructions, then you can configure, build and test
the project respectively with the following commands from the project root on
any operating system with any build system:
```sh
cmake --preset=dev
cmake --build --preset=dev
ctest --preset=dev
```
If you are using a compatible editor (e.g. VSCode) or IDE (e.g. CLion, VS), you
will also be able to select the above created user presets for automatic
integration.
Please note that both the build and test commands accept a `-j` flag to specify
the number of jobs to use, which should ideally be specified to the number of
threads your CPU has. You may also want to add that to your preset using the
`jobs` property, see the [presets documentation][1] for more details.
### Developer mode targets
These are targets you may invoke using the build command from above, with an
additional `-t <target>` flag:
#### `coverage`
Available if `ENABLE_COVERAGE` is enabled. This target processes the output of
the previously run tests when built with coverage configuration. The commands
this target runs can be found in the `COVERAGE_TRACE_COMMAND` and
`COVERAGE_HTML_COMMAND` cache variables. The trace command produces an info
file by default, which can be submitted to services with CI integration. The
HTML command uses the trace command's output to generate an HTML document to
`<binary-dir>/coverage_html` by default.
#### `docs`
Available if `BUILD_MCSS_DOCS` is enabled. Builds to documentation using
Doxygen and m.css. The output will go to `<binary-dir>/docs` by default
(customizable using `DOXYGEN_OUTPUT_DIRECTORY`).
#### `format-check` and `format-fix`
These targets run the clang-format tool on the codebase to check errors and to
fix them respectively. Customization available using the `FORMAT_PATTERNS` and
`FORMAT_COMMAND` cache variables.
#### `run-exe`
Runs the executable target `doasku_exe`.
#### `spell-check` and `spell-fix`
These targets run the codespell tool on the codebase to check errors and to fix
them respectively. Customization available using the `SPELL_COMMAND` cache
variable.
[1]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html
[2]: https://cmake.org/download/

diff --git a/LICENSE.md b/LICENSE.md

@@ -0,0 +1,21 @@

MIT License
Copyright (c) 2024 Dimitrije Dobrota
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

diff --git a/README.md b/README.md

@@ -0,0 +1,15 @@

# doasku
This is the doasku project.
# Building and installing
See the [BUILDING](BUILDING.md) document.
# Contributing
See the [CONTRIBUTING](CONTRIBUTING.md) document.
# Licensing
This project is licensed under the MIT License - see the LICENSE.md file for details

diff --git a/cmake/coverage.cmake b/cmake/coverage.cmake

@@ -0,0 +1,33 @@

# ---- Variables ----
# We use variables separate from what CTest uses, because those have
# customization issues
set(
COVERAGE_TRACE_COMMAND
lcov -c -q
-o "${PROJECT_BINARY_DIR}/coverage.info"
-d "${PROJECT_BINARY_DIR}"
--include "${PROJECT_SOURCE_DIR}/*"
CACHE STRING
"; separated command to generate a trace for the 'coverage' target"
)
set(
COVERAGE_HTML_COMMAND
genhtml --legend -f -q
"${PROJECT_BINARY_DIR}/coverage.info"
-p "${PROJECT_SOURCE_DIR}"
-o "${PROJECT_BINARY_DIR}/coverage_html"
CACHE STRING
"; separated command to generate an HTML report for the 'coverage' target"
)
# ---- Coverage target ----
add_custom_target(
coverage
COMMAND ${COVERAGE_TRACE_COMMAND}
COMMAND ${COVERAGE_HTML_COMMAND}
COMMENT "Generating coverage report"
VERBATIM
)

diff --git a/cmake/dev-mode.cmake b/cmake/dev-mode.cmake

@@ -0,0 +1,18 @@

include(cmake/folders.cmake)
add_custom_target(
run-exe
COMMAND doasku_exe
VERBATIM
)
add_dependencies(run-exe doasku_exe)
option(ENABLE_COVERAGE "Enable coverage support separate from CTest's" OFF)
if(ENABLE_COVERAGE)
include(cmake/coverage.cmake)
endif()
include(cmake/lint-targets.cmake)
include(cmake/spell-targets.cmake)
add_folders(Project)

diff --git a/cmake/folders.cmake b/cmake/folders.cmake

@@ -0,0 +1,21 @@

set_property(GLOBAL PROPERTY USE_FOLDERS YES)
# Call this function at the end of a directory scope to assign a folder to
# targets created in that directory. Utility targets will be assigned to the
# UtilityTargets folder, otherwise to the ${name}Targets folder. If a target
# already has a folder assigned, then that target will be skipped.
function(add_folders name)
get_property(targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
foreach(target IN LISTS targets)
get_property(folder TARGET "${target}" PROPERTY FOLDER)
if(DEFINED folder)
continue()
endif()
set(folder Utility)
get_property(type TARGET "${target}" PROPERTY TYPE)
if(NOT type STREQUAL "UTILITY")
set(folder "${name}")
endif()
set_property(TARGET "${target}" PROPERTY FOLDER "${folder}Targets")
endforeach()
endfunction()

diff --git a/cmake/install-rules.cmake b/cmake/install-rules.cmake

@@ -0,0 +1,8 @@

install(
TARGETS doasku_exe
RUNTIME COMPONENT doasku_Runtime
)
if(PROJECT_IS_TOP_LEVEL)
include(CPack)
endif()

diff --git a/cmake/lint-targets.cmake b/cmake/lint-targets.cmake

@@ -0,0 +1,33 @@

set(
FORMAT_PATTERNS
source/*.cpp source/*.hpp
include/*.hpp
test/*.cpp test/*.hpp
CACHE STRING
"; separated patterns relative to the project source dir to format"
)
set(FORMAT_COMMAND clang-format CACHE STRING "Formatter to use")
add_custom_target(
format-check
COMMAND "${CMAKE_COMMAND}"
-D "FORMAT_COMMAND=${FORMAT_COMMAND}"
-D "PATTERNS=${FORMAT_PATTERNS}"
-P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Linting the code"
VERBATIM
)
add_custom_target(
format-fix
COMMAND "${CMAKE_COMMAND}"
-D "FORMAT_COMMAND=${FORMAT_COMMAND}"
-D "PATTERNS=${FORMAT_PATTERNS}"
-D FIX=YES
-P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Fixing the code"
VERBATIM
)

diff --git a/cmake/lint.cmake b/cmake/lint.cmake

@@ -0,0 +1,51 @@

cmake_minimum_required(VERSION 3.14)
macro(default name)
if(NOT DEFINED "${name}")
set("${name}" "${ARGN}")
endif()
endmacro()
default(FORMAT_COMMAND clang-format)
default(
PATTERNS
source/*.cpp source/*.hpp
include/*.hpp
test/*.cpp test/*.hpp
)
default(FIX NO)
set(flag --output-replacements-xml)
set(args OUTPUT_VARIABLE output)
if(FIX)
set(flag -i)
set(args "")
endif()
file(GLOB_RECURSE files ${PATTERNS})
set(badly_formatted "")
set(output "")
string(LENGTH "${CMAKE_SOURCE_DIR}/" path_prefix_length)
foreach(file IN LISTS files)
execute_process(
COMMAND "${FORMAT_COMMAND}" --style=file "${flag}" "${file}"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
RESULT_VARIABLE result
${args}
)
if(NOT result EQUAL "0")
message(FATAL_ERROR "'${file}': formatter returned with ${result}")
endif()
if(NOT FIX AND output MATCHES "\n<replacement offset")
string(SUBSTRING "${file}" "${path_prefix_length}" -1 relative_file)
list(APPEND badly_formatted "${relative_file}")
endif()
set(output "")
endforeach()
if(NOT badly_formatted STREQUAL "")
list(JOIN badly_formatted "\n" bad_list)
message("The following files are badly formatted:\n\n${bad_list}\n")
message(FATAL_ERROR "Run again with FIX=YES to fix these files.")
endif()

diff --git a/cmake/prelude.cmake b/cmake/prelude.cmake

@@ -0,0 +1,10 @@

# ---- In-source guard ----
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(
FATAL_ERROR
"In-source builds are not supported. "
"Please read the BUILDING document before trying to build this project. "
"You may need to delete 'CMakeCache.txt' and 'CMakeFiles/' first."
)
endif()

diff --git a/cmake/project-is-top-level.cmake b/cmake/project-is-top-level.cmake

@@ -0,0 +1,6 @@

# This variable is set by project() in CMake 3.21+
string(
COMPARE EQUAL
"${CMAKE_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}"
PROJECT_IS_TOP_LEVEL
)

diff --git a/cmake/spell-targets.cmake b/cmake/spell-targets.cmake

@@ -0,0 +1,22 @@

set(SPELL_COMMAND codespell CACHE STRING "Spell checker to use")
add_custom_target(
spell-check
COMMAND "${CMAKE_COMMAND}"
-D "SPELL_COMMAND=${SPELL_COMMAND}"
-P "${PROJECT_SOURCE_DIR}/cmake/spell.cmake"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Checking spelling"
VERBATIM
)
add_custom_target(
spell-fix
COMMAND "${CMAKE_COMMAND}"
-D "SPELL_COMMAND=${SPELL_COMMAND}"
-D FIX=YES
-P "${PROJECT_SOURCE_DIR}/cmake/spell.cmake"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Fixing spelling errors"
VERBATIM
)

diff --git a/cmake/spell.cmake b/cmake/spell.cmake

@@ -0,0 +1,29 @@

cmake_minimum_required(VERSION 3.14)
macro(default name)
if(NOT DEFINED "${name}")
set("${name}" "${ARGN}")
endif()
endmacro()
default(SPELL_COMMAND codespell)
default(FIX NO)
set(flag "")
if(FIX)
set(flag -w)
endif()
execute_process(
COMMAND "${SPELL_COMMAND}" ${flag}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
RESULT_VARIABLE result
)
if(result EQUAL "65")
message(FATAL_ERROR "Run again with FIX=YES to fix these errors.")
elseif(result EQUAL "64")
message(FATAL_ERROR "Spell checker printed the usage info. Bad arguments?")
elseif(NOT result EQUAL "0")
message(FATAL_ERROR "Spell checker returned with ${result}")
endif()

diff --git a/cmake/variables.cmake b/cmake/variables.cmake

@@ -0,0 +1,28 @@

# ---- Developer mode ----
# Developer mode enables targets and code paths in the CMake scripts that are
# only relevant for the developer(s) of doasku
# Targets necessary to build the project must be provided unconditionally, so
# consumers can trivially build and package the project
if(PROJECT_IS_TOP_LEVEL)
option(doasku_DEVELOPER_MODE "Enable developer mode" OFF)
endif()
# ---- Warning guard ----
# target_include_directories with the SYSTEM modifier will request the compiler
# to omit warnings from the provided paths, if the compiler supports that
# This is to provide a user experience similar to find_package when
# add_subdirectory or FetchContent is used to consume this project
set(warning_guard "")
if(NOT PROJECT_IS_TOP_LEVEL)
option(
doasku_INCLUDES_WITH_SYSTEM
"Use SYSTEM modifier for doasku's includes, disabling warnings"
ON
)
mark_as_advanced(doasku_INCLUDES_WITH_SYSTEM)
if(doasku_INCLUDES_WITH_SYSTEM)
set(warning_guard SYSTEM)
endif()
endif()

diff --git a/include/cord.hpp b/include/cord.hpp

@@ -1,45 +0,0 @@

#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

@@ -1,44 +0,0 @@

#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

@@ -1,63 +0,0 @@

#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/source/cord.hpp b/source/cord.hpp

@@ -0,0 +1,65 @@

#ifndef DOASKO_CORD_HPP
#define DOASKO_CORD_HPP
#include <cassert>
#include <cinttypes>
#include <utility>
struct cord_t
{
explicit cord_t(uint8_t value)
: m_value(value)
{
assert(value < 9);
}
cord_t(uint8_t row, uint8_t col)
: m_value(static_cast<uint8_t>(row * 3 + col))
{
assert(row < 3 && col < 3);
}
// NOLINTNEXTLINE
operator uint8_t() const { return m_value; }
uint8_t row() const { return m_value / 3; }
uint8_t col() const { return m_value % 3; }
uint8_t m_value;
};
class acord_t
{
public:
acord_t(cord_t subgrid, cord_t field)
: m_subgrid(subgrid)
, m_field(field)
{
}
acord_t(uint8_t row, uint8_t col)
: m_subgrid(row / 3, col / 3)
, m_field(row % 3, col % 3)
{
}
cord_t subgrid() const { return m_subgrid; }
cord_t field() const { return m_field; }
uint8_t row() const
{
return static_cast<uint8_t>(m_subgrid.row() * 3 + m_field.row());
}
uint8_t col() const
{
return static_cast<uint8_t>(m_subgrid.col() * 3 + m_field.col());
}
std::tuple<cord_t, cord_t> relative() const { return {m_subgrid, m_field}; }
private:
cord_t m_subgrid;
cord_t m_field;
};
#endif

diff --git a/source/grid.cpp b/source/grid.cpp

@@ -0,0 +1,281 @@

#include <algorithm>
#include <bitset>
#include <functional>
#include <iostream>
#include "grid.hpp"
template<class Input, class UnaryFunc>
UnaryFunc for_each(Input input, UnaryFunc func)
{
return std::for_each(begin(input), end(input), func);
}
using other_t = std::tuple<uint8_t, uint8_t>;
other_t other_subgrid_row(uint8_t subgrid)
{
static const auto mapping = std::to_array<other_t>({{1, 2},
{0, 2},
{0, 1},
{4, 5},
{3, 5},
{3, 4},
{7, 8},
{6, 8},
{6, 7}});
return mapping.at(subgrid);
}
other_t other_subgrid_col(uint8_t subgrid)
{
static const auto mapping = std::to_array<other_t>({{3, 6},
{4, 7},
{5, 8},
{0, 6},
{1, 7},
{2, 8},
{0, 3},
{1, 4},
{2, 5}});
return mapping.at(subgrid);
}
grid::grid(const std::string& str)
{
std::size_t idx = 0;
for (uint8_t i = 0; i < 9; i++) {
for (uint8_t j = 0; j < 9; j++, idx++) {
if (str[idx] == '0') continue;
set({i, j}, static_cast<uint8_t>(str[idx] - '1'));
}
}
}
bool grid::solve()
{
// clang-format off
static const auto sub_op =
[this](auto opr, const auto func) {
for(uint8_t subgrid = 0; subgrid < 9; subgrid++) {
for_each(std::invoke(func, m_subgrids.at(subgrid)), [&](auto& chng) {
std::invoke(opr, this, operation_t({cord_t(subgrid), cord_t(std::get<0>(chng))}, std::get<1>(chng)));
});
}
return m_changed;
};
static const auto row_op =
[this](auto opr, const auto func) {
for(uint8_t row = 0; row < 9; row++) {
for_each(std::invoke(func, m_rows.at(row)), [&](auto& chng) {
std::invoke(opr, this, operation_t({row, std::get<0>(chng)}, std::get<1>(chng)));
});
}
return m_changed;
};
static const auto col_op =
[this](auto opr, const auto func) {
for(uint8_t col = 0; col < 9; col++) {
for_each(std::invoke(func, m_cols.at(col)), [&](auto &chng) {
std::invoke(opr, this, operation_t({std::get<0>(chng), col}, std::get<1>(chng)));
});
}
return m_changed;
};
static const auto all_op =
[this](auto opr, const auto func) {
sub_op(opr, func), row_op(opr, func), col_op(opr, func);
return m_changed;
};
// clang-format on
m_changed = true;
while (m_changed) {
m_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 = [](const auto& refs, uint16_t (ref::*func)(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 = {static_cast<uint8_t>(i / 3), static_cast<uint8_t>(j / 3)};
const cord_t field = {static_cast<uint8_t>(i % 3), static_cast<uint8_t>(j % 3)};
const uint16_t value = std::invoke(func, refs.at(subgrid), field);
const std::bitset<9>bts(value);
if (bits) std::cout << bts << " ";
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(m_subgrids, &ref::get, true);
std::cout << "Refs: " << std::endl;
print_i(m_subgrids, &ref::get_ref, true);
std::cout << "Board: " << std::endl;
print_i(m_subgrids, &ref::get_value, false);
}
void grid::op_set(operation_t opr)
{
set(std::get<0>(opr), static_cast<uint8_t>(std::get<1>(opr)));
m_changed = true;
}
void grid::op_mask(operation_t opr)
{
mask(std::get<0>(opr), std::get<1>(opr));
m_changed = true;
}
void grid::op_clear(operation_t opr)
{
clear(std::get<0>(opr), static_cast<uint8_t>(std::get<1>(opr)));
m_changed = true;
}
void grid::op_clear_row(operation_t opr)
{
const auto val = static_cast<uint8_t>(std::get<1>(opr));
const auto abs = std::get<0>(opr);
const auto [r1, r2] = other_subgrid_row(abs.subgrid());
clear_row(cord_t(r1), abs.field(), val);
clear_row(cord_t(r2), abs.field(), val);
m_changed = true;
}
void grid::op_clear_col(operation_t opr)
{
const auto val = static_cast<uint8_t>(std::get<1>(opr));
const auto abs = std::get<0>(opr);
const auto [c1, c2] = other_subgrid_col(abs.subgrid());
clear_col(cord_t(c1), abs.field(), val);
clear_col(cord_t(c2), abs.field(), val);
m_changed = true;
}
void grid::op_clear_row_rel(operation_t opr)
{
const auto val = static_cast<uint8_t>(std::get<1>(opr));
const auto abs = std::get<0>(opr);
const auto [r1, r2] = other_subgrid_row(abs.row());
clear_row(cord_t((r1 / 3), abs.col()), r1 % 3, val);
clear_row(cord_t((r2 / 3), abs.col()), r2 % 3, val);
m_changed = true;
}
void grid::op_clear_col_rel(operation_t opr)
{
const auto val = static_cast<uint8_t>(std::get<1>(opr));
const auto abs = std::get<0>(opr);
const auto [c1, c2] = other_subgrid_row(abs.col());
clear_col(cord_t(abs.row(), c1 / 3), c1 % 3, val);
clear_col(cord_t(abs.row(), c2 / 3), c2 % 3, val);
m_changed = true;
}
void grid::set(acord_t abs, uint8_t value)
{
m_rows.at(abs.row()).set(abs.col(), value);
m_cols.at(abs.col()).set(abs.row(), value);
m_subgrids.at(abs.subgrid()).set(abs.field(), value);
clear_row(abs.subgrid(), 0, value);
clear_row(abs.subgrid(), 1, value);
clear_row(abs.subgrid(), 2, value);
const auto [r1, r2] = other_subgrid_row(abs.subgrid());
clear_row(cord_t(r1), abs.field().row(), value);
clear_row(cord_t(r2), abs.field().row(), value);
const auto [c1, c2] = other_subgrid_col(abs.subgrid());
clear_col(cord_t(c1), abs.field().col(), value);
clear_col(cord_t(c2), abs.field().col(), value);
}
void grid::mask(acord_t abs, uint16_t mask)
{
while (mask != 0) {
const auto idx = static_cast<uint8_t>(std::countr_zero(mask));
clear(abs, idx);
mask ^= 1ULL << idx;
}
}
void grid::clear(acord_t abs, uint8_t value)
{
m_subgrids.at(abs.subgrid()).clear(abs.field(), value);
m_rows.at(abs.row()).clear(abs.col(), value);
m_cols.at(abs.col()).clear(abs.row(), value);
}
void grid::clear_row(cord_t sbgrd, uint8_t row, uint8_t value)
{
for (uint8_t i = 0; i < 3; i++) {
clear({sbgrd, {row, i}}, value);
}
}
void grid::clear_col(cord_t sbgrd, uint8_t col, uint8_t value)
{
for (uint8_t i = 0; i < 3; i++) {
clear({sbgrd, {i, col}}, value);
}
}
bool grid::is_finished(uint8_t subgrid)
{
for (uint8_t i = 0; i < 9; i++) {
if (m_subgrids.at(subgrid).get_value(i) == 0) 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/source/grid.hpp b/source/grid.hpp

@@ -0,0 +1,46 @@

#ifndef DOASKU_GRID_HPP
#define DOASKU_GRID_HPP
#include <array>
#include <string>
#include <utility>
#include "cord.hpp"
#include "ref.hpp"
class grid
{
public:
explicit grid(const std::string& str);
bool solve();
void print() const;
private:
using operation_t = std::tuple<acord_t, uint16_t>;
void op_set(operation_t opr);
void op_mask(operation_t opr);
void op_clear(operation_t opr);
void op_clear_row(operation_t opr);
void op_clear_col(operation_t opr);
void op_clear_row_rel(operation_t opr);
void op_clear_col_rel(operation_t opr);
void set(acord_t abs, uint8_t value);
void mask(acord_t abs, uint16_t mask);
void clear(acord_t abs, uint8_t value);
void clear_row(cord_t sbgrd, uint8_t row, uint8_t value);
void clear_col(cord_t sbgrd, uint8_t col, uint8_t value);
bool is_finished(uint8_t subgrid);
bool is_finished();
std::array<ref, 9> m_subgrids, m_rows, m_cols;
bool m_changed = false;
};
#endif

diff --git a/source/main.cpp b/source/main.cpp

@@ -0,0 +1,19 @@

#include <iostream>
#include <span>
#include "grid.hpp"
int main(const int argc, const char* argv[])
{
const std::span args(argv, static_cast<size_t>(argc));
for (const auto& arg : args.subspan(1)) {
grid sudoku(arg);
sudoku.print();
std::cout << (sudoku.solve() ? "solved" : "unable to solve") << std::endl;
sudoku.print();
}
return 0;
}

diff --git a/source/ref.cpp b/source/ref.cpp

@@ -0,0 +1,182 @@

#include <cassert>
#include <stdexcept>
#include "ref.hpp"
void ref::clear(uint8_t field, uint8_t value)
{
m_value.at(field) &= static_cast<uint16_t>(~(1U << value));
m_ref.at(value) &= static_cast<uint16_t>(~(1U << field));
}
void ref::set(uint8_t field, uint8_t value)
{
for (uint8_t i = 0; i < 9; i++) {
m_ref.at(i) &= static_cast<uint16_t>(~(1U << field));
}
m_ref.at(value) = 0;
m_value.at(field) = 0;
m_res.at(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(m_ref.at(candidate)) != 1) continue;
res.emplace_back(std::countr_zero(m_ref.at(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 = static_cast<size_t>(std::countr_zero(values));
if (m_ref.at(value) == 0) continue;
res.emplace_back(i, value);
}
return res;
}
ref::changes_t ref::get_hidden(uint8_t 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 orig,
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(m_ref.at(i)) < 2) continue;
if ((m_seen_hidden.at(orig - 1) & (1UL << i)) != 0) continue;
const bool used = get_hidden(res,
orig,
number - 1,
i + 1,
val | m_ref.at(i),
mask | static_cast<uint16_t>(1U << i));
if (!used) continue;
m_seen_hidden.at(orig - 1) |= 1UL << i;
if (number != orig) return true;
}
return false;
}
if (std::popcount(val) != orig) return false;
static std::array<uint8_t, 9> fields;
uint8_t size = 0;
while (val != 0) {
const auto idx = static_cast<uint8_t>(std::countr_zero(val));
fields.at(size++) = idx;
val ^= 1ULL << idx;
}
for (uint8_t i = 0; i < orig; i++) {
const uint16_t change =
m_value.at(fields.at(i)) & ~(m_value.at(fields.at(i)) & mask);
if (change == 0) continue;
res.emplace_back(fields.at(i), change);
}
return true;
}
ref::changes_t ref::get_naked(uint8_t 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 orig,
uint8_t number,
uint8_t first,
uint16_t val) const
{
static std::array<uint8_t, 4> seen = {0};
if (number != 0) {
for (uint8_t i = first; i < 9; i++) {
if (m_res.at(i) != 0) continue;
if (number == orig && (m_seen_naked.at(orig - 1) & (1UL << i)) != 0)
continue;
seen.at(orig - number) = i;
const bool used =
get_naked(res, orig, number - 1, i + 1, val | m_value.at(i));
if (!used) continue;
if (number == orig) m_seen_naked.at(orig - 1) |= 1UL << i;
if (number != orig) return true;
}
return false;
}
if (std::popcount(val) != orig) return false;
while (val != 0) {
const auto idx = static_cast<uint8_t>(std::countr_zero(val));
for (uint8_t pos = 0, i = 0; pos < 9; pos++) {
if ((m_value.at(pos) & idx) == 0) continue;
for (i = 0; i < orig; i++) {
if (pos == seen.at(i)) break;
}
if (i == orig) res.emplace_back(pos, idx);
}
val ^= 1ULL << idx;
}
return true;
}
ref::changes_t ref::get_pointing(uint16_t mask, std::size_t seen) const
{
changes_t res;
for (uint8_t i = 0; i < 9; i++) {
const auto popcnt = std::popcount(m_ref.at(i));
if (popcnt < 2 || popcnt > 3) continue;
if ((m_seen_point.at(seen) & (1UL << i)) == 0) {
uint16_t cmask = mask;
for (uint8_t k = 0; k < 3; k++, cmask <<= 3U) {
const uint16_t lmask = cmask & m_ref.at(i);
const auto cpopcnt = std::popcount(lmask);
if (cpopcnt != popcnt) continue;
m_seen_point.at(seen) |= 1U << i;
res.emplace_back(k, i);
break;
}
}
}
return res;
}

diff --git a/source/ref.hpp b/source/ref.hpp

@@ -0,0 +1,84 @@

#ifndef DOASKU_REF_HPP
#define DOASKU_REF_HPP
#include <array>
#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 m_value.at(field); }
uint16_t get_ref(uint8_t value) const { return m_ref.at(value); }
uint16_t get_value(uint8_t field) const { return m_res.at(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(uint8_t number) const;
changes_t get_hidden(uint8_t number) const;
changes_t get_pointing(uint16_t mask, size_t seen) const;
bool get_hidden(changes_t& res,
uint8_t orig,
uint8_t number,
uint8_t first,
uint16_t val,
uint16_t mask) const;
bool get_naked(changes_t& res,
uint8_t orig,
uint8_t number,
uint8_t first,
uint16_t val) const;
static constexpr const std::int64_t mask_field = (1U << 9U) - 1;
static constexpr const std::int64_t mask_value = 0x201008040201;
std::array<uint16_t, 9> m_value = {mask_field,
mask_field,
mask_field,
mask_field,
mask_field,
mask_field,
mask_field,
mask_field,
mask_field};
std::array<uint16_t, 9> m_ref = {mask_field,
mask_field,
mask_field,
mask_field,
mask_field,
mask_field,
mask_field,
mask_field,
mask_field};
std::array<uint16_t, 9> m_res = {0};
mutable std::array<uint16_t, 4> m_seen_hidden = {0};
mutable std::array<uint16_t, 4> m_seen_naked = {0};
mutable std::array<uint16_t, 2> m_seen_point = {0};
};
#endif

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt

@@ -1,8 +0,0 @@

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

@@ -1,241 +0,0 @@

#include <algorithm>
#include <bitset>
#include <functional>
#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](auto op, const auto f) {
for(uint8_t subgrid = 0; subgrid < 9; subgrid++) {
for_each(std::invoke(f, subgrids[subgrid]), [&](auto& ch) {
std::invoke(op, this, operation_t({cord_t(subgrid), cord_t(std::get<0>(ch))}, std::get<1>(ch)));
});
}
return changed;
};
static const auto row_op =
[this](auto op, const auto f) {
for(uint8_t row = 0; row < 9; row++) {
for_each(std::invoke(f, rows[row]), [&](auto& ch) {
std::invoke(op, this, operation_t({row, std::get<0>(ch)}, std::get<1>(ch)));
});
}
return changed;
};
static const auto col_op =
[this](auto op, const auto f) {
for(uint8_t col = 0; col < 9; col++) {
for_each(std::invoke(f, cols[col]), [&](auto &ch) {
std::invoke(op, this, operation_t({std::get<0>(ch), col}, std::get<1>(ch)));
});
}
return changed;
};
static const auto all_op =
[this](auto op, const auto f) {
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

@@ -1,15 +0,0 @@

#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

@@ -1,156 +0,0 @@

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