doasku

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

commit 4595a3d8cf36dcc69ae1d659a6c4ff48c5ea22b9
parent 8f55dc256a616127fe139c92c22c6ceffc8bdd70
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Fri, 21 Jun 2024 19:32:56 +0200

Use cmake-init and modernize codebase

Diffstat:
M.clang-format | 254++++++++++++++++++++++++++++++++++---------------------------------------------
A.clang-tidy | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.codespellrc | 6++++++
M.gitignore | 13+++++++++++--
ABUILDING.md | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MCMakeLists.txt | 42++++++++++++++++++++++++++++++++----------
ACMakePresets.json | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACODE_OF_CONDUCT.md | 5+++++
ACONTRIBUTING.md | 14++++++++++++++
AHACKING.md | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ALICENSE.md | 21+++++++++++++++++++++
AREADME.md | 15+++++++++++++++
Acmake/coverage.cmake | 33+++++++++++++++++++++++++++++++++
Acmake/dev-mode.cmake | 18++++++++++++++++++
Acmake/folders.cmake | 21+++++++++++++++++++++
Acmake/install-rules.cmake | 8++++++++
Acmake/lint-targets.cmake | 33+++++++++++++++++++++++++++++++++
Acmake/lint.cmake | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Acmake/prelude.cmake | 10++++++++++
Acmake/project-is-top-level.cmake | 6++++++
Acmake/spell-targets.cmake | 22++++++++++++++++++++++
Acmake/spell.cmake | 29+++++++++++++++++++++++++++++
Acmake/variables.cmake | 28++++++++++++++++++++++++++++
Dinclude/cord.hpp | 45---------------------------------------------
Dinclude/grid.hpp | 44--------------------------------------------
Dinclude/ref.hpp | 63---------------------------------------------------------------
Asource/cord.hpp | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/grid.cpp | 281+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/grid.hpp | 46++++++++++++++++++++++++++++++++++++++++++++++
Asource/main.cpp | 19+++++++++++++++++++
Asource/ref.cpp | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ref.hpp | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/CMakeLists.txt | 8--------
Dsrc/grid.cpp | 241-------------------------------------------------------------------------------
Dsrc/main.cpp | 15---------------
Dsrc/ref.cpp | 156-------------------------------------------------------------------------------
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; -}