├── .gitattributes ├── .gitignore ├── LICENSE.md ├── Puzzles ├── Regular │ ├── 4109.txt │ ├── 4112.txt │ ├── 4319.txt │ ├── 4322.txt │ ├── Unnamed 1.txt │ ├── Unnamed 2.txt │ ├── Unnamed 3.txt │ ├── Unnamed 4.txt │ ├── Unnamed 5.txt │ └── Unnamed 6.txt ├── Tough │ ├── 116.txt │ ├── 117.txt │ ├── Unsolved - Super Tough.txt │ ├── Unsolved - Unnamed 7.txt │ ├── Unsolved - s2.txt │ └── Unsolved - s3.txt └── Training │ ├── Avoidable Rectangle 1.txt │ ├── Avoidable Rectangle 2.txt │ ├── Hidden Quad.txt │ ├── Hidden Rectangle (1).txt │ ├── Hidden Rectangle (2).txt │ ├── Hidden Triple.txt │ ├── Jellyfish.txt │ ├── Naked Quad.txt │ ├── Naked Triple.txt │ ├── Pointing Tuples.txt │ ├── Unique Rectangle 1.txt │ ├── Unique Rectangle 2.txt │ ├── Unique Rectangle 3 (1).txt │ ├── Unique Rectangle 3 (2).txt │ ├── Unique Rectangle 4.txt │ ├── Unique Rectangle 5.txt │ ├── Unique Rectangle 6.txt │ ├── Unsolved - Hidden Rectangle (3).txt │ ├── Unsolved - Sue-De-Coq.txt │ ├── Unsolved - XYZ-Wing.txt │ ├── XY-Chain (1).txt │ ├── XY-Chain (2).txt │ ├── XY-Chain (3).txt │ └── Y-Wing.txt ├── README.md ├── Showcase ├── Fail.png └── Success.gif ├── SudokuSolver.sln ├── SudokuSolver ├── Candidates.cs ├── Cell.cs ├── CellSnapshot.cs ├── Puzzle.cs ├── PuzzleSnapshot.cs ├── Region.cs ├── SPoint.cs ├── Solver.cs ├── SolverTechniques │ ├── Solver_AvoidableRectangle.cs │ ├── Solver_Fish.cs │ ├── Solver_HiddenRectangle.cs │ ├── Solver_HiddenSingle.cs │ ├── Solver_HiddenTuple.cs │ ├── Solver_LockedCandidate.cs │ ├── Solver_NakedTuple.cs │ ├── Solver_PointingTuple.cs │ ├── Solver_UniqueRectangle.cs │ ├── Solver_XYChain.cs │ ├── Solver_XYZWing.cs │ └── Solver_YWing.cs ├── Solver_Techniques.cs ├── SudokuSolver.csproj └── Utils.cs ├── SudokuSolverTests ├── SudokuSolverTests.csproj ├── TestSolverTechniques.cs └── TestUtils.cs └── SudokuSolverWinForms ├── MainWindow.Designer.cs ├── MainWindow.cs ├── MainWindow.resx ├── Program.cs ├── Properties ├── Icon.ico └── Icon.png ├── SudokuBoard.cs └── SudokuSolverWinForms.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | Build/ 25 | 26 | # Visual Studio 2015 cache/options directory 27 | .vs/ 28 | # Uncomment if you have tasks that create the project's static files in wwwroot 29 | #wwwroot/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | project.fragment.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opendb 82 | *.opensdf 83 | *.sdf 84 | *.cachefile 85 | *.VC.db 86 | *.VC.VC.opendb 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | *.sap 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding add-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | _NCrunch_* 116 | .*crunch*.local.xml 117 | nCrunchTemp_* 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | # TODO: Comment the next line if you want to checkin your web deploy settings 146 | # but database connection strings (with potential passwords) will be unencrypted 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directories and files 176 | AppPackages/ 177 | BundleArtifacts/ 178 | Package.StoreAssociation.xml 179 | _pkginfo.txt 180 | 181 | # Visual Studio cache files 182 | # files ending in .cache can be ignored 183 | *.[Cc]ache 184 | # but keep track of directories ending in .cache 185 | !*.[Cc]ache/ 186 | 187 | # Others 188 | ClientBin/ 189 | ~$* 190 | *~ 191 | *.dbmdl 192 | *.dbproj.schemaview 193 | *.jfm 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The zlib/libpng License 2 | ======================= 3 | 4 | Copyright (c) `<2020>` `` 5 | 6 | This software is provided 'as-is', without any express or implied warranty. In 7 | no event will the authors be held liable for any damages arising from the use of 8 | this software. 9 | 10 | Permission is granted to anyone to use this software for any purpose, including 11 | commercial applications, and to alter it and redistribute it freely, subject to 12 | the following restrictions: 13 | 14 | 1. The origin of this software must not be misrepresented; you must not claim 15 | that you wrote the original software. If you use this software in a product, 16 | an acknowledgment in the product documentation would be appreciated but is 17 | not required. 18 | 19 | 2. Altered source versions must be plainly marked as such, and must not be 20 | misrepresented as being the original software. 21 | 22 | 3. This notice may not be removed or altered from any source distribution. 23 | -------------------------------------------------------------------------------- /Puzzles/Regular/4109.txt: -------------------------------------------------------------------------------- 1 | ---72---3 2 | 7-5-6---2 3 | ---8-1-9- 4 | -7---9--- 5 | 9---1---4 6 | ---4---1- 7 | -4-2-3--- 8 | 8---4-3-9 9 | 2---97--- -------------------------------------------------------------------------------- /Puzzles/Regular/4112.txt: -------------------------------------------------------------------------------- 1 | ---14--2- 2 | 4-----3-- 3 | --13--7-- 4 | -2--9--3- 5 | ---8-5--- 6 | -8--7--1- 7 | --8--65-- 8 | --7-----4 9 | -6--39--- -------------------------------------------------------------------------------- /Puzzles/Regular/4319.txt: -------------------------------------------------------------------------------- 1 | -5-9---2- 2 | ---8-3--- 3 | -2-5--413 4 | -64--52-- 5 | 28--4--65 6 | --36--17- 7 | 648--1-3- 8 | ---7-6--- 9 | -3---8-5- -------------------------------------------------------------------------------- /Puzzles/Regular/4322.txt: -------------------------------------------------------------------------------- 1 | 6--9----- 2 | -3-4-5-1- 3 | 7-8-1-5-- 4 | 917-6-4-- 5 | ----4---- 6 | --4-8-932 7 | --5-2-8-6 8 | -7-1-4-2- 9 | -----6--1 -------------------------------------------------------------------------------- /Puzzles/Regular/Unnamed 1.txt: -------------------------------------------------------------------------------- 1 | 3---59--8 2 | 2---8--63 3 | -----4-9- 4 | 1-3----7- 5 | --9-7-2-- 6 | -6----3-9 7 | -5-9----- 8 | 93--6---7 9 | 6--21---5 -------------------------------------------------------------------------------- /Puzzles/Regular/Unnamed 2.txt: -------------------------------------------------------------------------------- 1 | -2---9-85 2 | -5--48--6 3 | -8--5692- 4 | ----3-812 5 | -3-----4- 6 | 148-2---- 7 | -1576--9- 8 | 8--59--6- 9 | 49-8---7- -------------------------------------------------------------------------------- /Puzzles/Regular/Unnamed 3.txt: -------------------------------------------------------------------------------- 1 | ---5-43-- 2 | 9-127---- 3 | -5--8---6 4 | 3---4--7- 5 | 5-------2 6 | -7--9---1 7 | 2---5--6- 8 | ----274-8 9 | --41-9--- -------------------------------------------------------------------------------- /Puzzles/Regular/Unnamed 4.txt: -------------------------------------------------------------------------------- 1 | --2-34-1- 2 | --37928-- 3 | --4-85-2- 4 | -4-3----- 5 | 7---4---1 6 | -----7-9- 7 | -3-42-1-- 8 | --58139-- 9 | -8-57-4-- -------------------------------------------------------------------------------- /Puzzles/Regular/Unnamed 5.txt: -------------------------------------------------------------------------------- 1 | --7-2---- 2 | -2--156-3 3 | 5---3-89- 4 | -547----- 5 | --------- 6 | -----945- 7 | -36-5---4 8 | 2-819--3- 9 | ----7-9-- -------------------------------------------------------------------------------- /Puzzles/Regular/Unnamed 6.txt: -------------------------------------------------------------------------------- 1 | ----7--4- 2 | ---3----9 3 | 5-6-8---- 4 | -2--4--7- 5 | --82671-- 6 | -1--3--8- 7 | ----2-7-4 8 | 3----6--- 9 | -6--5---- -------------------------------------------------------------------------------- /Puzzles/Tough/116.txt: -------------------------------------------------------------------------------- 1 | --17----2 2 | -4---2--- 3 | ----5-7-6 4 | 7---6--5- 5 | -59---46- 6 | -6--2---8 7 | 9-3-8---- 8 | ---9---8- 9 | 4----51-- -------------------------------------------------------------------------------- /Puzzles/Tough/117.txt: -------------------------------------------------------------------------------- 1 | ---4-12-- 2 | -6----3-- 3 | -8---6-4- 4 | 5-2-3---- 5 | --9---6-- 6 | ----7-4-5 7 | -2-1---8- 8 | --6----2- 9 | --58-2--- -------------------------------------------------------------------------------- /Puzzles/Tough/Unsolved - Super Tough.txt: -------------------------------------------------------------------------------- 1 | 5-7-84--- 2 | --8----7- 3 | ---1----- 4 | ----4---2 5 | --------- 6 | 9---2---- 7 | -----1--- 8 | -7----2-- 9 | ---35-7-8 -------------------------------------------------------------------------------- /Puzzles/Tough/Unsolved - Unnamed 7.txt: -------------------------------------------------------------------------------- 1 | -4---3-8- 2 | -3--9---- 3 | 5-74--9-- 4 | ----2---- 5 | -23---56- 6 | 4---5---- 7 | --6--47-8 8 | -5--1--4- 9 | -9-8---1- -------------------------------------------------------------------------------- /Puzzles/Tough/Unsolved - s2.txt: -------------------------------------------------------------------------------- 1 | 13-7--295 2 | 5-69----- 3 | --983---1 4 | ----2---- 5 | ----6---- 6 | ----7---- 7 | 3---548-- 8 | -----74-6 9 | 964--1-37 -------------------------------------------------------------------------------- /Puzzles/Tough/Unsolved - s3.txt: -------------------------------------------------------------------------------- 1 | 6--925--- 2 | -3247-85- 3 | 4----3-7- 4 | -1--8---- 5 | --------- 6 | ----3--2- 7 | -9-3----8 8 | -24-9153- 9 | ---758--4 -------------------------------------------------------------------------------- /Puzzles/Training/Avoidable Rectangle 1.txt: -------------------------------------------------------------------------------- 1 | -5------- 2 | -6-5-42-- 3 | --8-71--- 4 | 4----36-8 5 | --------- 6 | 89-1--7-- 7 | 3-------- 8 | ---2-7-1- 9 | -72-3--9- 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Avoidable Rectangle 2.txt: -------------------------------------------------------------------------------- 1 | 9-----6-- 2 | --1--6-38 3 | 7-6-8---- 4 | -------76 5 | --41----- 6 | -5-947-2- 7 | 2-------5 8 | ---25---- 9 | ----9---1 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Hidden Quad.txt: -------------------------------------------------------------------------------- 1 | -3-----1- 2 | --8-9---- 3 | 4--6-8--- 4 | ---57694- 5 | ---98352- 6 | ---124--- 7 | 276--519- 8 | ---7-9--- 9 | -95---47- -------------------------------------------------------------------------------- /Puzzles/Training/Hidden Rectangle (1).txt: -------------------------------------------------------------------------------- 1 | -9---21-- 2 | ---73--2- 3 | 52------8 4 | 1-3-47--- 5 | ----6--3- 6 | 8-------5 7 | 7-68-4--- 8 | 9-------- 9 | --1-----4 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Hidden Rectangle (2).txt: -------------------------------------------------------------------------------- 1 | ----5-2-- 2 | 3--7----- 3 | 7---9--1- 4 | 9--1--746 5 | --------- 6 | ---5----9 7 | -29---4-- 8 | -6---4--- 9 | -8---23-1 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Hidden Triple.txt: -------------------------------------------------------------------------------- 1 | 4-7-----5 2 | ---2--7-- 3 | 2-1-7-6-8 4 | --91-23-- 5 | 3-2-97--- 6 | 17--6---- 7 | 72-851--6 8 | 986734--- 9 | 51-629--- -------------------------------------------------------------------------------- /Puzzles/Training/Jellyfish.txt: -------------------------------------------------------------------------------- 1 | 2-------3 2 | -8--3--5- 3 | --34-21-- 4 | --12-54-- 5 | ----9---- 6 | --93-86-- 7 | --25-69-- 8 | -9--2--7- 9 | 4-------1 -------------------------------------------------------------------------------- /Puzzles/Training/Naked Quad.txt: -------------------------------------------------------------------------------- 1 | ----3--86 2 | ----2---- 3 | -----85-- 4 | 371----94 5 | 9-------5 6 | 4----76-- 7 | 2--7--8-- 8 | -3---5--- 9 | 7----4-3- -------------------------------------------------------------------------------- /Puzzles/Training/Naked Triple.txt: -------------------------------------------------------------------------------- 1 | 891--576- 2 | 53769-2-8 3 | 462-----5 4 | 24351-8-6 5 | 156---4-- 6 | 978-465-- 7 | 319-5-687 8 | 684----52 9 | 72586-3-- -------------------------------------------------------------------------------- /Puzzles/Training/Pointing Tuples.txt: -------------------------------------------------------------------------------- 1 | -32--61-- 2 | 41------- 3 | ---9-1--- 4 | 5---9---4 5 | -6-----7- 6 | 3---2---5 7 | ---5-8--- 8 | -------19 9 | --7---86- 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Unique Rectangle 1.txt: -------------------------------------------------------------------------------- 1 | -----896- 2 | 1--7----- 3 | -675--3-- 4 | 21---78-- 5 | --489---3 6 | 7----4--5 7 | -219----4 8 | --------- 9 | -------26 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Unique Rectangle 2.txt: -------------------------------------------------------------------------------- 1 | -6-5--2-1 2 | 1-------- 3 | -239----6 4 | 64------- 5 | ----27-9- 6 | -52----8- 7 | -------6- 8 | --1--59-- 9 | 5---7-1-- 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Unique Rectangle 3 (1).txt: -------------------------------------------------------------------------------- 1 | 5-8---12- 2 | --------7 3 | 2----58-6 4 | 4----2--- 5 | -8-9----- 6 | -----697- 7 | ---2----- 8 | ----7-419 9 | -3-1592-- 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Unique Rectangle 3 (2).txt: -------------------------------------------------------------------------------- 1 | -8--3-1-- 2 | -----23-- 3 | --64---75 4 | 3-9-2---- 5 | -2-5-1-8- 6 | --7-4---2 7 | 7--6----- 8 | ------8-- 9 | 54------7 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Unique Rectangle 4.txt: -------------------------------------------------------------------------------- 1 | ---76--9- 2 | --6-3-4-1 3 | ----8--3- 4 | --------- 5 | ---47---2 6 | 2351---4- 7 | 493-26--- 8 | 82------3 9 | --------- 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Unique Rectangle 5.txt: -------------------------------------------------------------------------------- 1 | -9----25- 2 | ---2----- 3 | -6-----8- 4 | --4-2---3 5 | 7264----1 6 | 5----7--8 7 | ----84-7- 8 | 158-7---- 9 | ---15---- 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Unique Rectangle 6.txt: -------------------------------------------------------------------------------- 1 | -395----8 2 | 5-------2 3 | ----2-4-- 4 | -419-5--- 5 | 9---7--3- 6 | -----857- 7 | --8--67-- 8 | --------- 9 | 19------- 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Unsolved - Hidden Rectangle (3).txt: -------------------------------------------------------------------------------- 1 | ----41--- 2 | -7----5-- 3 | -------6- 4 | 5--7--68- 5 | -9-3----- 6 | -------4- 7 | 4-1-8---- 8 | ---2--7-- 9 | --------- 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Unsolved - Sue-De-Coq.txt: -------------------------------------------------------------------------------- 1 | -1-9-8--2 2 | --6-47-1- 3 | 5-------- 4 | --7-6--3- 5 | 6-------5 6 | -4--9-2-- 7 | --------4 8 | -9-51-7-- 9 | 3--2-4-5- 10 | -------------------------------------------------------------------------------- /Puzzles/Training/Unsolved - XYZ-Wing.txt: -------------------------------------------------------------------------------- 1 | -92--175- 2 | 5--2----8 3 | ----3-2-- 4 | -75--496- 5 | 2---6--75 6 | -697---3- 7 | --8-9--2- 8 | 7----3-89 9 | 9-38---4- -------------------------------------------------------------------------------- /Puzzles/Training/XY-Chain (1).txt: -------------------------------------------------------------------------------- 1 | --3--1--- 2 | 8-------- 3 | -51--9-6- 4 | -8----29- 5 | ---7---8- 6 | 2---4-5-3 7 | 6--9----- 8 | --2-84--- 9 | 41--5-6-- 10 | -------------------------------------------------------------------------------- /Puzzles/Training/XY-Chain (2).txt: -------------------------------------------------------------------------------- 1 | 1549----- 2 | -----41-- 3 | -6-75---9 4 | -1---8--- 5 | 69---7-4- 6 | --5----37 7 | -8-----1- 8 | ----3--68 9 | ------5-- 10 | -------------------------------------------------------------------------------- /Puzzles/Training/XY-Chain (3).txt: -------------------------------------------------------------------------------- 1 | 174832596 2 | 593461278 3 | 682957--1 4 | -675--9-- 5 | -197-36-5 6 | 435-968-7 7 | 3-16--759 8 | 9-8-75-6- 9 | 7563-9-82 -------------------------------------------------------------------------------- /Puzzles/Training/Y-Wing.txt: -------------------------------------------------------------------------------- 1 | 9---4---- 2 | ---6---31 3 | -2-----9- 4 | ---7---2- 5 | --29356-- 6 | -7---2--- 7 | -6-----73 8 | 51---9--- 9 | ----8---9 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SudokuSolver 2 | 3 | A program that works out the solution to a Sudoku puzzle by using human techniques and proofs. 4 | Because it logs the human techniques it uses, you can learn how to get past obstacles you found yourself in. 5 | 6 | ![Success](Showcase/Success.gif) 7 | 8 | It is designed with speed in mind, but there are still many improvements to be had (span etc). 9 | The program has been updated to .NET 8.0, but I'm not actively working on the program. 10 | The goal was to write each technique before optimizing further. 11 | 12 | The form draws the puzzle and its changes. 13 | If it gets stuck, the candidates for each cell will be shown for debugging: 14 | 15 | ![Fail](Showcase/Fail.png) 16 | 17 | Once design is done, of course, there will be no candidates showing, as every cell will be filled (assuming the input puzzle is valid and has human-solvable steps). 18 | 19 | Big thanks to http://hodoku.sourceforge.net and http://www.sudokuwiki.org for providing a lot of information on tough Sudoku techniques. 20 | 21 | ## To Do 22 | 23 | * A way to toggle techniques and logging with the UI 24 | * Remaining techniques -------------------------------------------------------------------------------- /Showcase/Fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/SudokuSolver/5f30aaa6da883b6a2aad02a754736a19df057f9d/Showcase/Fail.png -------------------------------------------------------------------------------- /Showcase/Success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/SudokuSolver/5f30aaa6da883b6a2aad02a754736a19df057f9d/Showcase/Success.gif -------------------------------------------------------------------------------- /SudokuSolver.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33110.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SudokuSolver", "SudokuSolver\SudokuSolver.csproj", "{C3556DE7-3124-4995-8BBB-FD9DFF460193}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SudokuSolverTests", "SudokuSolverTests\SudokuSolverTests.csproj", "{80009691-BE65-4F91-91DB-DA244CBA5527}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBD}") = "SudokuSolverWinForms", "SudokuSolverWinForms\SudokuSolverWinForms.csproj", "{9B84A03B-038F-4FE9-AFFA-DD64D749C86B}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {C3556DE7-3124-4995-8BBB-FD9DFF460193}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {C3556DE7-3124-4995-8BBB-FD9DFF460193}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {C3556DE7-3124-4995-8BBB-FD9DFF460193}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {C3556DE7-3124-4995-8BBB-FD9DFF460193}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {80009691-BE65-4F91-91DB-DA244CBA5527}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {80009691-BE65-4F91-91DB-DA244CBA5527}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {80009691-BE65-4F91-91DB-DA244CBA5527}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {80009691-BE65-4F91-91DB-DA244CBA5527}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {9B84A03B-038F-4FE9-AFFA-DD64D749C86B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {9B84A03B-038F-4FE9-AFFA-DD64D749C86B}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {9B84A03B-038F-4FE9-AFFA-DD64D749C86B}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {9B84A03B-038F-4FE9-AFFA-DD64D749C86B}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {80455E13-5CFE-4045-BADA-AD2B435FC912} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /SudokuSolver/Candidates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Kermalis.SudokuSolver; 6 | 7 | public struct Candidates : IEnumerable 8 | { 9 | // First 9 bits are flags for valid candidates. Next 4 bits are for the count [0, 9]. Last 3 bits unused 10 | private const ushort ALL_POSSIBLE = 0b1_1111_1111 | (9 << 9); 11 | private const ushort NONE_POSSIBLE = 0; 12 | 13 | private ushort _data; 14 | 15 | public readonly int Count => _data >> 9; 16 | 17 | internal Candidates(bool possible) 18 | { 19 | _data = possible ? ALL_POSSIBLE : NONE_POSSIBLE; 20 | } 21 | 22 | public readonly bool IsCandidate(int digit) 23 | { 24 | if (digit is < 1 or > 9) 25 | { 26 | throw new ArgumentOutOfRangeException(nameof(digit), digit, null); 27 | } 28 | 29 | return IsCandidate_Fast(digit - 1); 30 | } 31 | private readonly bool IsCandidate_Fast(int index) 32 | { 33 | int bit = 1 << index; 34 | return (_data & bit) != 0; 35 | } 36 | 37 | private void SetCount(int count) 38 | { 39 | _data &= 0b1110_0001_1111_1111; 40 | _data |= (ushort)(count << 9); 41 | } 42 | 43 | /// Returns true if there was a change. 44 | internal bool Set(int digit, bool newState) 45 | { 46 | int index = digit - 1; 47 | bool changed = IsCandidate_Fast(index) != newState; 48 | if (changed) 49 | { 50 | _data ^= (ushort)(1 << index); 51 | if (newState) 52 | { 53 | SetCount(Count + 1); 54 | } 55 | else 56 | { 57 | SetCount(Count - 1); 58 | } 59 | } 60 | 61 | return changed; 62 | } 63 | /// Returns true if there was a change. 64 | internal bool Set(ReadOnlySpan digit, bool newState) 65 | { 66 | bool changed = false; 67 | foreach (int can in digit) 68 | { 69 | if (Set(can, newState)) 70 | { 71 | changed = true; 72 | } 73 | } 74 | return changed; 75 | } 76 | /// Returns true if there was a change. 77 | internal bool Set(IEnumerable digit, bool newState) 78 | { 79 | bool changed = false; 80 | foreach (int can in digit) 81 | { 82 | if (Set(can, newState)) 83 | { 84 | changed = true; 85 | } 86 | } 87 | return changed; 88 | } 89 | /// Returns true if there was a change. 90 | internal static bool Set(ReadOnlySpan cells, int digit, bool newState) 91 | { 92 | bool changed = false; 93 | foreach (Cell cell in cells) 94 | { 95 | if (cell.CandI.Set(digit, newState)) 96 | { 97 | changed = true; 98 | } 99 | } 100 | return changed; 101 | } 102 | /// Returns true if there was a change. 103 | internal static bool Set(ReadOnlySpan cells, ReadOnlySpan digit, bool newState) 104 | { 105 | bool changed = false; 106 | foreach (Cell cell in cells) 107 | { 108 | foreach (int cand in digit) 109 | { 110 | if (cell.CandI.Set(cand, newState)) 111 | { 112 | changed = true; 113 | } 114 | } 115 | } 116 | return changed; 117 | } 118 | 119 | /// Result length is [0,9] 120 | internal readonly Span Intersect(Candidates other, Span cache) 121 | { 122 | int retLength = 0; 123 | for (int i = 0; i < 9; i++) 124 | { 125 | if (IsCandidate_Fast(i) && other.IsCandidate_Fast(i)) 126 | { 127 | cache[retLength++] = i + 1; 128 | } 129 | } 130 | return cache.Slice(0, retLength); 131 | } 132 | /// Result length is [0,9] 133 | internal readonly Span Intersect(Candidates otherA, Candidates otherB, Span cache) 134 | { 135 | int retLength = 0; 136 | for (int i = 0; i < 9; i++) 137 | { 138 | if (IsCandidate_Fast(i) && otherA.IsCandidate_Fast(i) && otherB.IsCandidate_Fast(i)) 139 | { 140 | cache[retLength++] = i + 1; 141 | } 142 | } 143 | return cache.Slice(0, retLength); 144 | } 145 | /// Result length is [0,9] 146 | internal readonly Span Intersect(ReadOnlySpan other, Span cache) 147 | { 148 | int retLength = 0; 149 | for (int digit = 1; digit <= 9; digit++) 150 | { 151 | if (IsCandidate_Fast(digit - 1) && other.SimpleIndexOf(digit) != -1) 152 | { 153 | cache[retLength++] = digit; 154 | } 155 | } 156 | return cache.Slice(0, retLength); 157 | } 158 | /// Returns all candidates except for the ones in . 159 | /// Result length is [0,8] 160 | internal readonly Span Except(Candidates other, Span cache) 161 | { 162 | int retLength = 0; 163 | for (int i = 0; i < 9; i++) 164 | { 165 | if (IsCandidate_Fast(i) && !other.IsCandidate_Fast(i)) 166 | { 167 | cache[retLength++] = i + 1; 168 | } 169 | } 170 | return cache.Slice(0, retLength); 171 | } 172 | /// Returns all candidates except for the ones in . 173 | /// Result length is [0,8] 174 | internal readonly Span Except(ReadOnlySpan other, Span cache) 175 | { 176 | int retLength = 0; 177 | for (int digit = 1; digit <= 9; digit++) 178 | { 179 | if (IsCandidate_Fast(digit - 1) && other.SimpleIndexOf(digit) == -1) 180 | { 181 | cache[retLength++] = digit; 182 | } 183 | } 184 | return cache.Slice(0, retLength); 185 | } 186 | /// Returns all candidates except for . 187 | /// Result length is [0,8] 188 | internal readonly Span Except(int other, Span cache) 189 | { 190 | int retLength = 0; 191 | for (int digit = 1; digit <= 9; digit++) 192 | { 193 | if (digit != other && IsCandidate_Fast(digit - 1)) 194 | { 195 | cache[retLength++] = digit; 196 | } 197 | } 198 | return cache.Slice(0, retLength); 199 | } 200 | /// Result length is [0,9] 201 | internal static Span Union(ReadOnlySpan cells, Span cache) 202 | { 203 | int retLength = 0; 204 | for (int i = 0; i < 9; i++) 205 | { 206 | foreach (Cell cell in cells) 207 | { 208 | if (cell.CandI.IsCandidate_Fast(i)) 209 | { 210 | cache[retLength++] = i + 1; 211 | break; 212 | } 213 | } 214 | } 215 | return cache.Slice(0, retLength); 216 | } 217 | 218 | /// Returns these candidates as ints in the range [1,9]. 219 | /// Result length is [0,9] 220 | internal readonly Span AsInt(Span cache) 221 | { 222 | int retLength = 0; 223 | for (int i = 0; i < 9; i++) 224 | { 225 | if (IsCandidate_Fast(i)) 226 | { 227 | cache[retLength++] = i + 1; 228 | } 229 | } 230 | return cache.Slice(0, retLength); 231 | } 232 | 233 | /// Returns the cells from that have . 234 | /// Result length is [0, cells.Length] 235 | internal static Span GetCellsWithCandidate(ReadOnlySpan cells, int digit, Span cache) 236 | { 237 | int retLength = 0; 238 | for (int i = 0; i < cells.Length; i++) 239 | { 240 | Cell cell = cells[i]; 241 | if (cell.CandI.IsCandidate_Fast(digit - 1)) 242 | { 243 | cache[retLength++] = cell; 244 | } 245 | } 246 | return cache.Slice(0, retLength); 247 | } 248 | 249 | public readonly string Print() 250 | { 251 | Span span = stackalloc int[9]; 252 | return Utils.PrintCandidates(AsInt(span)); 253 | } 254 | /// Returns true if has the same candidates as this 255 | public readonly bool SetEquals(Candidates other) 256 | { 257 | return _data == other._data; 258 | } 259 | 260 | /// Returns true if is 1, and sets to the candidate. 261 | internal readonly bool TryGetCount1(out int can1) 262 | { 263 | if (Count != 1) 264 | { 265 | can1 = -1; 266 | return false; 267 | } 268 | 269 | for (int i = 0; i < 9; i++) 270 | { 271 | if (IsCandidate_Fast(i)) 272 | { 273 | can1 = i + 1; 274 | return true; 275 | } 276 | } 277 | throw new InvalidOperationException(); 278 | } 279 | /// Returns true if is 2, and sets and to the candidates. 280 | internal readonly bool TryGetCount2(out int can1, out int can2) 281 | { 282 | can1 = -1; 283 | can2 = -1; 284 | 285 | if (Count != 2) 286 | { 287 | return false; 288 | } 289 | 290 | int counter = 0; 291 | for (int i = 0; i < 9; i++) 292 | { 293 | if (!IsCandidate_Fast(i)) 294 | { 295 | continue; 296 | } 297 | 298 | if (counter++ == 0) 299 | { 300 | can1 = i + 1; 301 | } 302 | else 303 | { 304 | can2 = i + 1; 305 | break; 306 | } 307 | } 308 | return true; 309 | } 310 | /// Gets the first two candidates. Assumes is 2. 311 | internal readonly void GetCount2(out int can1, out int can2) 312 | { 313 | can1 = -1; 314 | can2 = -1; 315 | 316 | int counter = 0; 317 | for (int i = 0; i < 9; i++) 318 | { 319 | if (!IsCandidate_Fast(i)) 320 | { 321 | continue; 322 | } 323 | 324 | if (counter++ == 0) 325 | { 326 | can1 = i + 1; 327 | } 328 | else 329 | { 330 | can2 = i + 1; 331 | break; 332 | } 333 | } 334 | } 335 | 336 | internal readonly bool HasBoth(int can1, int can2) 337 | { 338 | return IsCandidate_Fast(can1 - 1) && IsCandidate_Fast(can2 - 1); 339 | } 340 | 341 | // TODO: Eventually remove..? 342 | public readonly IEnumerator GetEnumerator() 343 | { 344 | for (int i = 0; i < 9; i++) 345 | { 346 | if (IsCandidate_Fast(i)) 347 | { 348 | yield return i + 1; 349 | } 350 | } 351 | } 352 | readonly IEnumerator IEnumerable.GetEnumerator() 353 | { 354 | return GetEnumerator(); 355 | } 356 | } -------------------------------------------------------------------------------- /SudokuSolver/Cell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | #if DEBUG 5 | using System.Diagnostics; 6 | #endif 7 | 8 | namespace Kermalis.SudokuSolver; 9 | 10 | #if DEBUG 11 | [DebuggerDisplay("{DebugString()}", Name = "{ToString()}")] 12 | #endif 13 | public sealed class Cell 14 | { 15 | public const int EMPTY_VALUE = 0; 16 | /// 6 in Column, 6 in Row, 8 in Block 17 | public const int NUM_VISIBLE_CELLS = 6 + 6 + 8; 18 | 19 | internal readonly Puzzle Puzzle; 20 | public int OriginalValue { get; private set; } 21 | public SPoint Point { get; } 22 | public Region Block { get; private set; } 23 | public Region Column { get; private set; } 24 | public Region Row { get; private set; } 25 | internal readonly Cell[] VisibleI; 26 | 27 | public int Value { get; private set; } 28 | internal Candidates CandI; 29 | 30 | public Candidates Candidates => CandI; 31 | /// The cells this cell is grouped with. Block, Row, Column 32 | public ReadOnlyCollection VisibleCells { get; } 33 | 34 | internal Cell(Puzzle puzzle, int value, SPoint point) 35 | { 36 | Puzzle = puzzle; 37 | 38 | OriginalValue = value; 39 | Value = value; 40 | Point = point; 41 | 42 | CandI = new Candidates(true); 43 | VisibleI = new Cell[NUM_VISIBLE_CELLS]; // Will be init in InitVisibleCells 44 | VisibleCells = new ReadOnlyCollection(VisibleI); 45 | 46 | // Will be set in InitRegions 47 | Block = null!; 48 | Column = null!; 49 | Row = null!; 50 | } 51 | internal void InitRegions() 52 | { 53 | Block = Puzzle.BlocksI[Point.BlockIndex]; 54 | Column = Puzzle.ColumnsI[Point.Column]; 55 | Row = Puzzle.RowsI[Point.Row]; 56 | } 57 | internal void InitVisibleCells() 58 | { 59 | int counter = 0; 60 | for (int i = 0; i < 9; i++) 61 | { 62 | // Add 8 neighbors from block 63 | Cell other = Block[i]; 64 | if (other != this) 65 | { 66 | VisibleI[counter++] = other; 67 | } 68 | 69 | // Add 6 neighbors from row 70 | other = Row[i]; 71 | if (other.Block != Block) 72 | { 73 | VisibleI[counter++] = other; 74 | } 75 | 76 | // Add 6 neighbors from column 77 | other = Column[i]; 78 | if (other.Block != Block) 79 | { 80 | VisibleI[counter++] = other; 81 | } 82 | } 83 | } 84 | 85 | // TODO: Remove 86 | internal bool ChangeCandidates(IEnumerable digits, bool remove = true) 87 | { 88 | bool changed = false; 89 | foreach (int digit in digits) 90 | { 91 | if (CandI.Set(digit, !remove)) 92 | { 93 | changed = true; 94 | } 95 | } 96 | return changed; 97 | } 98 | internal static bool ChangeCandidates(IEnumerable cells, int digit, bool remove = true) 99 | { 100 | bool changed = false; 101 | foreach (Cell cell in cells) 102 | { 103 | if (cell.CandI.Set(digit, !remove)) 104 | { 105 | changed = true; 106 | } 107 | } 108 | return changed; 109 | } 110 | internal static bool ChangeCandidates(IEnumerable cells, IEnumerable digits, bool remove = true) 111 | { 112 | bool changed = false; 113 | foreach (Cell cell in cells) 114 | { 115 | foreach (int digit in digits) 116 | { 117 | if (cell.CandI.Set(digit, !remove)) 118 | { 119 | changed = true; 120 | } 121 | } 122 | } 123 | return changed; 124 | } 125 | 126 | /// Changes the current value to . is updated. 127 | /// If is true, the entire puzzle's candidates are refreshed. 128 | internal void SetValue(int newValue, bool refreshOtherCellCandidates = false) 129 | { 130 | int oldValue = Value; 131 | Value = newValue; 132 | 133 | if (newValue == EMPTY_VALUE) 134 | { 135 | for (int i = 1; i <= 9; i++) 136 | { 137 | CandI.Set(i, true); 138 | } 139 | Candidates.Set(VisibleI, oldValue, true); 140 | } 141 | else 142 | { 143 | CandI = new Candidates(false); 144 | Candidates.Set(VisibleI, newValue, false); 145 | } 146 | 147 | if (refreshOtherCellCandidates) 148 | { 149 | Puzzle.RefreshCandidates(); 150 | } 151 | } 152 | public void ChangeOriginalValue(int value) 153 | { 154 | if (value is < EMPTY_VALUE or > 9) 155 | { 156 | throw new ArgumentOutOfRangeException(nameof(value), value, null); 157 | } 158 | 159 | OriginalValue = value; 160 | SetValue(value, refreshOtherCellCandidates: true); 161 | } 162 | 163 | public override int GetHashCode() 164 | { 165 | return Point.GetHashCode(); 166 | } 167 | public override bool Equals(object? obj) 168 | { 169 | if (obj is Cell other) 170 | { 171 | return other.Point.Equals(Point); 172 | } 173 | return false; 174 | } 175 | public override string ToString() 176 | { 177 | return Point.ToString(); 178 | } 179 | #if DEBUG 180 | public string DebugString() 181 | { 182 | string s = Point.ToString() + " "; 183 | if (Value == EMPTY_VALUE) 184 | { 185 | s += "has candidates: " + CandI.Print(); 186 | } 187 | else 188 | { 189 | s += "- " + Value.ToString(); 190 | } 191 | return s; 192 | } 193 | #endif 194 | 195 | /// Returns the visible cells that this cell and share. 196 | /// Result length is 2 (1 row + 1 column) or 7 (7 row/column) or 13 (7 block + 6 row/column) 197 | internal Span IntersectVisibleCells(Cell other, Span cache) 198 | { 199 | int counter = 0; 200 | for (int i = 0; i < VisibleI.Length; i++) 201 | { 202 | Cell cell = VisibleI[i]; 203 | if (Array.IndexOf(other.VisibleI, cell) != -1) 204 | { 205 | cache[counter++] = cell; 206 | } 207 | } 208 | return cache.Slice(0, counter); 209 | } 210 | /*/// Returns the visible cells that this cell, , and all share. 211 | /// Result length is 0 or 1 (1 row/column) or 6 (6 block/row/column) or 12 (6 block + 6 row/column) 212 | internal Span IntersectVisibleCells(Cell otherA, Cell otherB, Span cache) 213 | { 214 | int counter = 0; 215 | for (int i = 0; i < NUM_VISIBLE_CELLS; i++) 216 | { 217 | Cell cell = VisibleCells[i]; 218 | if (otherA.VisibleCells.Contains(cell) && otherB.VisibleCells.Contains(cell)) 219 | { 220 | cache[counter++] = cell; 221 | } 222 | } 223 | return cache.Slice(0, counter); 224 | }*/ 225 | 226 | /*/// Result length is 12 or 14 227 | internal Span VisibleCellsExceptRegion(Region except, Span cache) 228 | { 229 | int counter = 0; 230 | for (int i = 0; i < 8 + 6 + 6; i++) 231 | { 232 | Cell cell = VisibleCells[i]; 233 | if (except.IndexOf(cell) == -1) 234 | { 235 | // Add if "except" region does not contain the visible cell 236 | cache[counter++] = cell; 237 | } 238 | } 239 | return cache.Slice(0, counter); 240 | }*/ 241 | } 242 | -------------------------------------------------------------------------------- /SudokuSolver/CellSnapshot.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.SudokuSolver; 2 | 3 | public readonly struct CellSnapshot 4 | { 5 | public Candidates Candidates { get; } 6 | /// First 4 bits for [0, 9]. Next bit for . Next bit for . Last 2 bits unused. 7 | private readonly byte _data; 8 | 9 | public int Value => _data & 0b0000_1111; 10 | public bool IsCulprit => (_data & 0b0001_0000) != 0; 11 | public bool IsSemiCulprit => (_data & 0b0010_0000) != 0; 12 | 13 | internal CellSnapshot(Cell cell, bool isCulprit, bool isSemiCulprit) 14 | { 15 | Candidates = cell.Candidates; 16 | _data = (byte)cell.Value; 17 | _data |= (byte)((isCulprit ? 1 : 0) << 4); 18 | _data |= (byte)((isSemiCulprit ? 1 : 0) << 5); 19 | } 20 | } -------------------------------------------------------------------------------- /SudokuSolver/Puzzle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace Kermalis.SudokuSolver; 7 | 8 | public sealed class Puzzle 9 | { 10 | public ReadOnlyCollection Rows { get; } 11 | public ReadOnlyCollection Columns { get; } 12 | public ReadOnlyCollection Blocks { get; } 13 | public ReadOnlyCollection> Regions { get; } 14 | 15 | public bool IsCustom { get; } 16 | private readonly Cell[] _board; 17 | internal readonly Region[] RowsI; 18 | internal readonly Region[] ColumnsI; 19 | internal readonly Region[] BlocksI; 20 | internal readonly Region[][] RegionsI; 21 | 22 | public Cell this[int col, int row] => _board[Utils.CellIndex(col, row)]; 23 | 24 | private Puzzle(int[][] board, bool isCustom) 25 | { 26 | IsCustom = isCustom; 27 | 28 | _board = new Cell[81]; 29 | for (int col = 0; col < 9; col++) 30 | { 31 | for (int row = 0; row < 9; row++) 32 | { 33 | _board[Utils.CellIndex(col, row)] = new Cell(this, board[col][row], new SPoint(col, row)); 34 | } 35 | } 36 | 37 | RowsI = new Region[9]; 38 | Rows = new ReadOnlyCollection(RowsI); 39 | ColumnsI = new Region[9]; 40 | Columns = new ReadOnlyCollection(ColumnsI); 41 | BlocksI = new Region[9]; 42 | Blocks = new ReadOnlyCollection(BlocksI); 43 | RegionsI = [RowsI, ColumnsI, BlocksI]; 44 | Regions = new ReadOnlyCollection>([Rows, Columns, Blocks]); 45 | 46 | var cellsCache = new Cell[9]; 47 | for (int i = 0; i < 9; i++) 48 | { 49 | int j; 50 | for (j = 0; j < 9; j++) 51 | { 52 | cellsCache[j] = _board[Utils.CellIndex(j, i)]; 53 | } 54 | RowsI[i] = new Region(cellsCache); 55 | 56 | for (j = 0; j < 9; j++) 57 | { 58 | cellsCache[j] = _board[Utils.CellIndex(i, j)]; 59 | } 60 | ColumnsI[i] = new Region(cellsCache); 61 | 62 | j = 0; 63 | int x = i % 3 * 3; 64 | int y = i / 3 * 3; 65 | for (int col = x; col < x + 3; col++) 66 | { 67 | for (int row = y; row < y + 3; row++) 68 | { 69 | cellsCache[j++] = _board[Utils.CellIndex(col, row)]; 70 | } 71 | } 72 | BlocksI[i] = new Region(cellsCache); 73 | } 74 | 75 | for (int i = 0; i < 81; i++) 76 | { 77 | _board[i].InitRegions(); 78 | } 79 | for (int i = 0; i < 81; i++) 80 | { 81 | _board[i].InitVisibleCells(); 82 | } 83 | } 84 | 85 | internal void RefreshCandidates() 86 | { 87 | for (int i = 0; i < 81; i++) 88 | { 89 | Cell cell = _board[i]; 90 | for (int digit = 1; digit <= 9; digit++) 91 | { 92 | cell.CandI.Set(digit, true); 93 | } 94 | } 95 | for (int i = 0; i < 81; i++) 96 | { 97 | Cell cell = _board[i]; 98 | if (cell.Value != Cell.EMPTY_VALUE) 99 | { 100 | cell.SetValue(cell.Value); 101 | } 102 | } 103 | } 104 | 105 | public static Puzzle CreateCustom() 106 | { 107 | int[][] board = new int[9][]; 108 | for (int col = 0; col < 9; col++) 109 | { 110 | board[col] = new int[9]; 111 | } 112 | return new Puzzle(board, true); 113 | } 114 | public static Puzzle Parse(ReadOnlySpan inRows) 115 | { 116 | if (inRows.Length != 9) 117 | { 118 | throw new InvalidDataException("Puzzle must have 9 rows."); 119 | } 120 | 121 | int[][] board = new int[9][]; 122 | for (int col = 0; col < 9; col++) 123 | { 124 | board[col] = new int[9]; 125 | } 126 | 127 | for (int row = 0; row < 9; row++) 128 | { 129 | string line = inRows[row]; 130 | if (line.Length != 9) 131 | { 132 | throw new InvalidDataException($"Row {row} must have 9 values."); 133 | } 134 | 135 | for (int col = 0; col < 9; col++) 136 | { 137 | if (int.TryParse(line[col].ToString(), out int value) && value is >= 1 and <= 9) 138 | { 139 | board[col][row] = value; 140 | } 141 | else 142 | { 143 | board[col][row] = Cell.EMPTY_VALUE; // Anything else can represent Cell.EMPTY_VALUE 144 | } 145 | } 146 | } 147 | 148 | return new Puzzle(board, false); 149 | } 150 | 151 | public void Reset() 152 | { 153 | for (int i = 0; i < 81; i++) 154 | { 155 | Cell cell = _board[i]; 156 | if (cell.Value != cell.OriginalValue) 157 | { 158 | cell.SetValue(Cell.EMPTY_VALUE); 159 | } 160 | } 161 | } 162 | /// Returns true if any digit is repeated. Can be called even if the puzzle isn't solved yet. 163 | public bool CheckForErrors() 164 | { 165 | for (int digit = 1; digit <= 9; digit++) 166 | { 167 | for (int i = 0; i < 9; i++) 168 | { 169 | if (BlocksI[i].CheckForDuplicateValue(digit) 170 | || RowsI[i].CheckForDuplicateValue(digit) 171 | || ColumnsI[i].CheckForDuplicateValue(digit)) 172 | { 173 | return true; 174 | } 175 | } 176 | } 177 | return false; 178 | } 179 | 180 | public override string ToString() 181 | { 182 | var sb = new StringBuilder(); 183 | for (int row = 0; row < 9; row++) 184 | { 185 | for (int col = 0; col < 9; col++) 186 | { 187 | Cell cell = _board[Utils.CellIndex(col, row)]; 188 | if (cell.OriginalValue == Cell.EMPTY_VALUE) 189 | { 190 | sb.Append('-'); 191 | } 192 | else 193 | { 194 | sb.Append(cell.OriginalValue); 195 | } 196 | } 197 | if (row != 8) 198 | { 199 | sb.AppendLine(); 200 | } 201 | } 202 | return sb.ToString(); 203 | } 204 | public string ToStringFancy() 205 | { 206 | var sb = new StringBuilder(); 207 | for (int row = 0; row < 9; row++) 208 | { 209 | if (row % 3 == 0) 210 | { 211 | for (int col = 0; col < 13; col++) 212 | { 213 | sb.Append('—'); 214 | } 215 | sb.AppendLine(); 216 | } 217 | for (int col = 0; col < 9; col++) 218 | { 219 | if (col % 3 == 0) 220 | { 221 | sb.Append('┃'); 222 | } 223 | 224 | Cell cell = _board[Utils.CellIndex(col, row)]; 225 | if (cell.Value == Cell.EMPTY_VALUE) 226 | { 227 | sb.Append(' '); 228 | } 229 | else 230 | { 231 | sb.Append(cell.Value); 232 | } 233 | 234 | if (col == 8) 235 | { 236 | sb.Append('┃'); 237 | } 238 | } 239 | sb.AppendLine(); 240 | if (row == 8) 241 | { 242 | for (int col = 0; col < 13; col++) 243 | { 244 | sb.Append('—'); 245 | } 246 | } 247 | } 248 | return sb.ToString(); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /SudokuSolver/PuzzleSnapshot.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.SudokuSolver; 2 | 3 | public sealed class PuzzleSnapshot 4 | { 5 | public string Action { get; } 6 | /// Stored as x,y (col,row) 7 | private readonly CellSnapshot[] _board; 8 | 9 | public CellSnapshot this[int col, int row] => _board[Utils.CellIndex(col, row)]; 10 | 11 | internal PuzzleSnapshot(string action, CellSnapshot[] board) 12 | { 13 | Action = action; 14 | _board = board; 15 | } 16 | 17 | public override string ToString() 18 | { 19 | return Action; 20 | } 21 | } -------------------------------------------------------------------------------- /SudokuSolver/Region.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Kermalis.SudokuSolver; 7 | 8 | public sealed class Region : IEnumerable 9 | { 10 | private readonly Cell[] _cells; 11 | 12 | public Cell this[int index] => _cells[index]; 13 | 14 | internal Region(ReadOnlySpan cells) 15 | { 16 | _cells = cells.ToArray(); 17 | } 18 | 19 | public IEnumerator GetEnumerator() 20 | { 21 | return ((IEnumerable)_cells).GetEnumerator(); 22 | } 23 | IEnumerator IEnumerable.GetEnumerator() 24 | { 25 | return _cells.GetEnumerator(); 26 | } 27 | 28 | public int IndexOf(Cell cell) 29 | { 30 | for (int i = 0; i < 9; i++) 31 | { 32 | if (_cells[i] == cell) 33 | { 34 | return i; 35 | } 36 | } 37 | return -1; 38 | } 39 | 40 | /// Result length is [0,9] 41 | internal Span GetCellsWithCandidate(int digit, Span cache) 42 | { 43 | return Candidates.GetCellsWithCandidate(_cells, digit, cache); 44 | } 45 | public IEnumerable GetCellsWithCandidate(int candidate) 46 | { 47 | return _cells.Where(c => c.CandI.Contains(candidate)); 48 | } 49 | 50 | /// Result length is [0,9] 51 | internal Span GetCellsWithCandidateCount(int numCandidates, Span cache) 52 | { 53 | int counter = 0; 54 | for (int i = 0; i < 9; i++) 55 | { 56 | Cell cell = _cells[i]; 57 | if (cell.Candidates.Count == numCandidates) 58 | { 59 | cache[counter++] = cell; 60 | } 61 | } 62 | return cache.Slice(0, counter); 63 | } 64 | internal int CountCellsWithCandidates() 65 | { 66 | int counter = 0; 67 | for (int i = 0; i < 9; i++) 68 | { 69 | Cell cell = _cells[i]; 70 | if (cell.CandI.Count != 0) 71 | { 72 | counter++; 73 | } 74 | } 75 | return counter; 76 | } 77 | 78 | /// Returns all cells except for the ones in . 79 | /// Result length is [0,9] 80 | internal Span Except(ReadOnlySpan other, Span cache) 81 | { 82 | int retLength = 0; 83 | for (int i = 0; i < 9; i++) 84 | { 85 | Cell c = _cells[i]; 86 | if (other.SimpleIndexOf(c) == -1) 87 | { 88 | cache[retLength++] = c; 89 | } 90 | } 91 | return cache.Slice(0, retLength); 92 | } 93 | /// Returns all cells except for the ones in . 94 | /// Result length is [0,9] 95 | internal Span Except(Region other, Span cache) 96 | { 97 | return Except(other._cells, cache); 98 | } 99 | 100 | /// Returns true if this region has more than one cell with the value of . 101 | internal bool CheckForDuplicateValue(int digit) 102 | { 103 | bool foundValueAlready = false; 104 | for (int i = 0; i < 9; i++) 105 | { 106 | if (_cells[i].Value == digit) 107 | { 108 | if (foundValueAlready) 109 | { 110 | return true; 111 | } 112 | foundValueAlready = true; 113 | } 114 | } 115 | return false; 116 | } 117 | } -------------------------------------------------------------------------------- /SudokuSolver/SPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Kermalis.SudokuSolver; 4 | 5 | public readonly struct SPoint 6 | { 7 | public int Column { get; } 8 | public int Row { get; } 9 | public int BlockIndex { get; } 10 | 11 | internal SPoint(int col, int row) 12 | { 13 | Column = col; 14 | Row = row; 15 | BlockIndex = (col / 3) + (3 * (row / 3)); 16 | } 17 | 18 | public bool Equals(int col, int row) 19 | { 20 | return Column == col && Row == row; 21 | } 22 | public static string RowLetter(int row) 23 | { 24 | return ((char)(row + 'A')).ToString(); 25 | } 26 | public static string ColumnLetter(int col) 27 | { 28 | return (col + 1).ToString(); 29 | } 30 | 31 | public static bool operator ==(SPoint left, SPoint right) 32 | { 33 | return left.Equals(right); 34 | } 35 | public static bool operator !=(SPoint left, SPoint right) 36 | { 37 | return !(left == right); 38 | } 39 | public override bool Equals(object? obj) 40 | { 41 | if (obj is SPoint other) 42 | { 43 | return other.Column == Column && other.Row == Row; 44 | } 45 | return false; 46 | } 47 | public override int GetHashCode() 48 | { 49 | return HashCode.Combine(Column, Row); 50 | } 51 | public override string ToString() 52 | { 53 | return RowLetter(Row) + ColumnLetter(Column); 54 | } 55 | } -------------------------------------------------------------------------------- /SudokuSolver/Solver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Threading; 6 | 7 | namespace Kermalis.SudokuSolver; 8 | 9 | public sealed partial class Solver 10 | { 11 | public Puzzle Puzzle { get; } 12 | public BindingList Actions { get; } 13 | 14 | public Solver(Puzzle puzzle) 15 | { 16 | Puzzle = puzzle; 17 | Actions = []; 18 | 19 | _techniques = InitSolverTechniques(); 20 | } 21 | public static Solver CreateCustomPuzzle() 22 | { 23 | var s = new Solver(Puzzle.CreateCustom()); 24 | s.LogAction("Custom puzzle created"); 25 | return s; 26 | } 27 | 28 | public void SetOriginalCellValue(Cell cell, int value) 29 | { 30 | if (cell.Puzzle != Puzzle) 31 | { 32 | throw new ArgumentOutOfRangeException(nameof(cell), cell, "Cell belongs to another puzzle"); 33 | } 34 | 35 | cell.ChangeOriginalValue(value); 36 | LogAction(TechniqueFormat("Changed cell", cell.ToString()), 37 | cell); 38 | } 39 | 40 | public bool TrySolve() 41 | { 42 | Puzzle.RefreshCandidates(); 43 | LogAction("Begin"); 44 | 45 | do 46 | { 47 | if (CheckForNakedSinglesOrCompletion(out bool changed)) 48 | { 49 | LogAction("Solver completed the puzzle"); 50 | return true; 51 | } 52 | if (changed) 53 | { 54 | continue; 55 | } 56 | if (!RunTechnique()) 57 | { 58 | LogAction("Solver failed the puzzle"); 59 | return false; 60 | } 61 | } while (true); 62 | } 63 | public bool TrySolveAsync(CancellationToken ct) 64 | { 65 | Puzzle.RefreshCandidates(); 66 | LogAction("Begin"); 67 | 68 | do 69 | { 70 | if (CheckForNakedSinglesOrCompletion(out bool changed)) 71 | { 72 | LogAction("Solver completed the puzzle"); 73 | return true; 74 | } 75 | if (ct.IsCancellationRequested) 76 | { 77 | break; 78 | } 79 | if (changed) 80 | { 81 | continue; 82 | } 83 | if (!RunTechnique()) 84 | { 85 | LogAction("Solver failed the puzzle"); 86 | return false; 87 | } 88 | if (ct.IsCancellationRequested) 89 | { 90 | break; 91 | } 92 | } while (true); 93 | 94 | LogAction("Solver cancelled"); 95 | throw new OperationCanceledException(ct); 96 | } 97 | private bool CheckForNakedSinglesOrCompletion(out bool changed) 98 | { 99 | changed = false; 100 | bool solved = true; 101 | again: 102 | for (int col = 0; col < 9; col++) 103 | { 104 | for (int row = 0; row < 9; row++) 105 | { 106 | Cell cell = Puzzle[col, row]; 107 | if (cell.Value != Cell.EMPTY_VALUE) 108 | { 109 | continue; 110 | } 111 | 112 | // Empty cell... check for naked single 113 | solved = false; 114 | if (cell.CandI.TryGetCount1(out int nakedSingle)) 115 | { 116 | cell.SetValue(nakedSingle); 117 | 118 | LogAction(TechniqueFormat("Naked single", 119 | "{0}: {1}", 120 | cell, nakedSingle), 121 | cell); 122 | 123 | changed = true; 124 | goto again; // Restart the search for naked singles since we have the potential to create new ones 125 | } 126 | } 127 | } 128 | return solved; 129 | } 130 | 131 | private bool RunTechnique() 132 | { 133 | foreach (SolverTechnique t in _techniques) 134 | { 135 | if (t.Function.Invoke()) 136 | { 137 | return true; 138 | } 139 | } 140 | return false; 141 | } 142 | 143 | private static string TechniqueFormat(string technique, string format, params object[] args) 144 | { 145 | return string.Format(string.Format("{0,-20}", technique) + format, args); 146 | } 147 | private void LogAction(string action) 148 | { 149 | var sBoard = new CellSnapshot[81]; 150 | for (int col = 0; col < 9; col++) 151 | { 152 | for (int row = 0; row < 9; row++) 153 | { 154 | Cell cell = Puzzle[col, row]; 155 | 156 | sBoard[Utils.CellIndex(col, row)] = new CellSnapshot(cell, false, false); 157 | } 158 | } 159 | Actions.Add(new PuzzleSnapshot(action, sBoard)); 160 | } 161 | private void LogAction(string action, Cell culprit) 162 | { 163 | var sBoard = new CellSnapshot[81]; 164 | for (int col = 0; col < 9; col++) 165 | { 166 | for (int row = 0; row < 9; row++) 167 | { 168 | Cell cell = Puzzle[col, row]; 169 | 170 | sBoard[Utils.CellIndex(col, row)] = new CellSnapshot(cell, culprit == cell, false); 171 | } 172 | } 173 | Actions.Add(new PuzzleSnapshot(action, sBoard)); 174 | } 175 | private void LogAction(string action, Cell culprit, Cell semiCulprit) 176 | { 177 | var sBoard = new CellSnapshot[81]; 178 | for (int col = 0; col < 9; col++) 179 | { 180 | for (int row = 0; row < 9; row++) 181 | { 182 | Cell cell = Puzzle[col, row]; 183 | 184 | sBoard[Utils.CellIndex(col, row)] = new CellSnapshot(cell, culprit == cell, semiCulprit == cell); 185 | } 186 | } 187 | Actions.Add(new PuzzleSnapshot(action, sBoard)); 188 | } 189 | private void LogAction(string action, ReadOnlySpan culprits) 190 | { 191 | var sBoard = new CellSnapshot[81]; 192 | for (int col = 0; col < 9; col++) 193 | { 194 | for (int row = 0; row < 9; row++) 195 | { 196 | Cell cell = Puzzle[col, row]; 197 | 198 | sBoard[Utils.CellIndex(col, row)] = new CellSnapshot(cell, culprits.SimpleIndexOf(cell) != -1, false); 199 | } 200 | } 201 | Actions.Add(new PuzzleSnapshot(action, sBoard)); 202 | } 203 | private void LogAction(string action, ReadOnlySpan culprits, Cell semiCulprit) 204 | { 205 | var sBoard = new CellSnapshot[81]; 206 | for (int col = 0; col < 9; col++) 207 | { 208 | for (int row = 0; row < 9; row++) 209 | { 210 | Cell cell = Puzzle[col, row]; 211 | 212 | sBoard[Utils.CellIndex(col, row)] = new CellSnapshot(cell, culprits.SimpleIndexOf(cell) != -1, semiCulprit == cell); 213 | } 214 | } 215 | Actions.Add(new PuzzleSnapshot(action, sBoard)); 216 | } 217 | private void LogAction(string action, Cell culprit, ReadOnlySpan semiCulprits) 218 | { 219 | var sBoard = new CellSnapshot[81]; 220 | for (int col = 0; col < 9; col++) 221 | { 222 | for (int row = 0; row < 9; row++) 223 | { 224 | Cell cell = Puzzle[col, row]; 225 | 226 | sBoard[Utils.CellIndex(col, row)] = new CellSnapshot(cell, culprit == cell, semiCulprits.SimpleIndexOf(cell) != -1); 227 | } 228 | } 229 | Actions.Add(new PuzzleSnapshot(action, sBoard)); 230 | } 231 | private void LogAction(string action, ReadOnlySpan culprits, ReadOnlySpan semiCulprits) 232 | { 233 | var sBoard = new CellSnapshot[81]; 234 | for (int col = 0; col < 9; col++) 235 | { 236 | for (int row = 0; row < 9; row++) 237 | { 238 | Cell cell = Puzzle[col, row]; 239 | 240 | sBoard[Utils.CellIndex(col, row)] = new CellSnapshot(cell, culprits.SimpleIndexOf(cell) != -1, semiCulprits.SimpleIndexOf(cell) != -1); 241 | } 242 | } 243 | Actions.Add(new PuzzleSnapshot(action, sBoard)); 244 | } 245 | public void LogAction(string action, IEnumerable culprits) 246 | { 247 | var sBoard = new CellSnapshot[81]; 248 | for (int col = 0; col < 9; col++) 249 | { 250 | for (int row = 0; row < 9; row++) 251 | { 252 | Cell cell = Puzzle[col, row]; 253 | 254 | sBoard[Utils.CellIndex(col, row)] = new CellSnapshot(cell, culprits.Contains(cell), false); 255 | } 256 | } 257 | Actions.Add(new PuzzleSnapshot(action, sBoard)); 258 | } 259 | public void LogAction(string action, IEnumerable culprits, IEnumerable semiCulprits) 260 | { 261 | var sBoard = new CellSnapshot[81]; 262 | for (int col = 0; col < 9; col++) 263 | { 264 | for (int row = 0; row < 9; row++) 265 | { 266 | Cell cell = Puzzle[col, row]; 267 | 268 | sBoard[Utils.CellIndex(col, row)] = new CellSnapshot(cell, culprits.Contains(cell), semiCulprits.Contains(cell)); 269 | } 270 | } 271 | Actions.Add(new PuzzleSnapshot(action, sBoard)); 272 | } 273 | } -------------------------------------------------------------------------------- /SudokuSolver/SolverTechniques/Solver_AvoidableRectangle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Kermalis.SudokuSolver; 6 | 7 | partial class Solver 8 | { 9 | private bool AvoidableRectangle() 10 | { 11 | for (int type = 1; type <= 2; type++) 12 | { 13 | for (int x1 = 0; x1 < 9; x1++) 14 | { 15 | Region c1 = Puzzle.ColumnsI[x1]; 16 | for (int x2 = x1 + 1; x2 < 9; x2++) 17 | { 18 | Region c2 = Puzzle.ColumnsI[x2]; 19 | for (int y1 = 0; y1 < 9; y1++) 20 | { 21 | for (int y2 = y1 + 1; y2 < 9; y2++) 22 | { 23 | for (int value1 = 1; value1 <= 9; value1++) 24 | { 25 | for (int value2 = value1 + 1; value2 <= 9; value2++) 26 | { 27 | int[] candidates = [value1, value2]; 28 | Cell[] cells = [c1[y1], c1[y2], c2[y1], c2[y2]]; 29 | if (cells.Any(c => c.OriginalValue != Cell.EMPTY_VALUE)) 30 | { 31 | continue; 32 | } 33 | 34 | IEnumerable alreadySet = cells.Where(c => c.Value != Cell.EMPTY_VALUE); 35 | IEnumerable notSet = cells.Where(c => c.Value == Cell.EMPTY_VALUE); 36 | 37 | switch (type) 38 | { 39 | case 1: 40 | { 41 | if (alreadySet.Count() != 3) 42 | { 43 | continue; 44 | } 45 | break; 46 | } 47 | case 2: 48 | { 49 | if (alreadySet.Count() != 2) 50 | { 51 | continue; 52 | } 53 | break; 54 | } 55 | } 56 | Cell[][] pairs = 57 | [ 58 | [cells[0], cells[3]], 59 | [cells[1], cells[2]] 60 | ]; 61 | foreach (Cell[] pair in pairs) 62 | { 63 | Cell[] otherPair = pair == pairs[0] ? pairs[1] : pairs[0]; 64 | foreach (int i in candidates) 65 | { 66 | int otherVal = candidates.Single(ca => ca != i); 67 | if (((pair[0].Value == i && pair[1].Value == Cell.EMPTY_VALUE && pair[1].CandI.Count == 2 && pair[1].CandI.Contains(i)) 68 | || (pair[1].Value == i && pair[0].Value == Cell.EMPTY_VALUE && pair[0].CandI.Count == 2 && pair[0].CandI.Contains(i))) 69 | && otherPair.All(c => c.Value == otherVal || (c.CandI.Count == 2 && c.CandI.Contains(otherVal)))) 70 | { 71 | goto breakpairs; 72 | } 73 | } 74 | } 75 | continue; // Did not find 76 | breakpairs: 77 | bool changed = false; 78 | switch (type) 79 | { 80 | case 1: 81 | { 82 | Cell cell = notSet.ElementAt(0); 83 | if (cell.CandI.Count == 2) 84 | { 85 | cell.SetValue(cell.CandI.Except(candidates).ElementAt(0)); 86 | } 87 | else 88 | { 89 | cell.CandI.Set(cell.CandI.Intersect(candidates), false); 90 | } 91 | changed = true; 92 | break; 93 | } 94 | case 2: 95 | { 96 | IEnumerable commonCandidates = notSet.Select(c => c.CandI.Except(candidates)).IntersectAll(); 97 | if (commonCandidates.Any() 98 | && Cell.ChangeCandidates(notSet.Select(c => c.VisibleCells).IntersectAll(), commonCandidates)) 99 | { 100 | changed = true; 101 | } 102 | break; 103 | } 104 | } 105 | 106 | if (changed) 107 | { 108 | LogAction(TechniqueFormat("Avoidable rectangle", 109 | "{0}: {1}", 110 | Utils.PrintCells(cells), Utils.PrintCandidates(candidates)), 111 | (ReadOnlySpan)cells); 112 | return true; 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } 120 | } 121 | return false; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /SudokuSolver/SolverTechniques/Solver_Fish.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Kermalis.SudokuSolver; 6 | 7 | partial class Solver 8 | { 9 | private static ReadOnlySpan FishStr => new string[5] { string.Empty, string.Empty, "X-Wing", "Swordfish", "Jellyfish" }; 10 | 11 | private bool Jellyfish() 12 | { 13 | return Fish_Find(4); 14 | } 15 | 16 | private bool Swordfish() 17 | { 18 | return Fish_Find(3); 19 | } 20 | 21 | private bool XWing() 22 | { 23 | return Fish_Find(2); 24 | } 25 | 26 | 27 | private bool Fish_Find(int amount) 28 | { 29 | for (int candidate = 1; candidate <= 9; candidate++) 30 | { 31 | if (Fish_Recurse(amount, candidate, 0, new int[amount])) 32 | { 33 | return true; 34 | } 35 | } 36 | return false; 37 | } 38 | private bool Fish_Recurse(int amount, int candidate, int loop, int[] indexes) 39 | { 40 | if (loop == amount) 41 | { 42 | IEnumerable> rowCells = indexes.Select(i => Puzzle.RowsI[i].GetCellsWithCandidate(candidate)); 43 | IEnumerable> colCells = indexes.Select(i => Puzzle.ColumnsI[i].GetCellsWithCandidate(candidate)); 44 | 45 | IEnumerable rowLengths = rowCells.Select(cells => cells.Count()); 46 | IEnumerable colLengths = colCells.Select(parr => parr.Count()); 47 | 48 | if (rowLengths.Max() == amount && rowLengths.Min() > 0 && rowCells.Select(cells => cells.Select(c => c.Point.Column)).UniteAll().Count() <= amount) 49 | { 50 | IEnumerable row2D = rowCells.UniteAll(); 51 | if (Cell.ChangeCandidates(row2D.Select(c => Puzzle.ColumnsI[c.Point.Column]).UniteAll().Except(row2D), candidate)) 52 | { 53 | LogAction(TechniqueFormat(FishStr[amount], "{0}: {1}", row2D.Print(), candidate), row2D); 54 | return true; 55 | } 56 | } 57 | if (colLengths.Max() == amount && colLengths.Min() > 0 && colCells.Select(cells => cells.Select(c => c.Point.Row)).UniteAll().Count() <= amount) 58 | { 59 | IEnumerable col2D = colCells.UniteAll(); 60 | if (Cell.ChangeCandidates(col2D.Select(c => Puzzle.RowsI[c.Point.Row]).UniteAll().Except(col2D), candidate)) 61 | { 62 | LogAction(TechniqueFormat(FishStr[amount], "{0}: {1}", col2D.Print(), candidate), col2D); 63 | return true; 64 | } 65 | } 66 | } 67 | else 68 | { 69 | for (int i = loop == 0 ? 0 : indexes[loop - 1] + 1; i < 9; i++) 70 | { 71 | indexes[loop] = i; 72 | if (Fish_Recurse(amount, candidate, loop + 1, indexes)) 73 | { 74 | return true; 75 | } 76 | } 77 | } 78 | return false; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /SudokuSolver/SolverTechniques/Solver_HiddenRectangle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Kermalis.SudokuSolver; 6 | 7 | partial class Solver 8 | { 9 | private bool HiddenRectangle() 10 | { 11 | for (int x1 = 0; x1 < 9; x1++) 12 | { 13 | Region c1 = Puzzle.ColumnsI[x1]; 14 | for (int x2 = x1 + 1; x2 < 9; x2++) 15 | { 16 | Region c2 = Puzzle.ColumnsI[x2]; 17 | for (int y1 = 0; y1 < 9; y1++) 18 | { 19 | for (int y2 = y1 + 1; y2 < 9; y2++) 20 | { 21 | for (int value1 = 1; value1 <= 9; value1++) 22 | { 23 | for (int value2 = value1 + 1; value2 <= 9; value2++) 24 | { 25 | int[] candidates = [value1, value2]; 26 | Cell[] cells = [c1[y1], c1[y2], c2[y1], c2[y2]]; 27 | if (cells.Any(c => !c.CandI.ContainsAll(candidates))) 28 | { 29 | continue; 30 | } 31 | ILookup l = cells.ToLookup(c => c.CandI.Count); 32 | IEnumerable gtTwo = l.Where(g => g.Key > 2).SelectMany(g => g); 33 | int gtTwoCount = gtTwo.Count(); 34 | if (gtTwoCount < 2 || gtTwoCount > 3) 35 | { 36 | continue; 37 | } 38 | 39 | bool changed = false; 40 | foreach (Cell c in l[2]) 41 | { 42 | int eks = c.Point.Column == x1 ? x2 : x1; 43 | int why = c.Point.Row == y1 ? y2 : y1; 44 | foreach (int i in candidates) 45 | { 46 | if (!Puzzle.RowsI[why].GetCellsWithCandidate(i).Except(cells).Any() // "i" only appears in our UR 47 | && !Puzzle.ColumnsI[eks].GetCellsWithCandidate(i).Except(cells).Any()) 48 | { 49 | Cell diag = Puzzle[eks, why]; 50 | if (diag.CandI.Count == 2) 51 | { 52 | diag.SetValue(i); 53 | } 54 | else 55 | { 56 | diag.CandI.Set(i == value1 ? value2 : value1, false); 57 | } 58 | changed = true; 59 | } 60 | } 61 | } 62 | if (changed) 63 | { 64 | LogAction(TechniqueFormat("Hidden rectangle", 65 | "{0}: {1}", 66 | Utils.PrintCells(cells), Utils.PrintCandidates(candidates)), 67 | (ReadOnlySpan)cells); 68 | return true; 69 | } 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } 76 | return false; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /SudokuSolver/SolverTechniques/Solver_HiddenSingle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Kermalis.SudokuSolver; 4 | 5 | partial class Solver 6 | { 7 | private bool HiddenSingle() 8 | { 9 | bool changed = false; 10 | bool restartSearch; 11 | 12 | do 13 | { 14 | restartSearch = false; 15 | 16 | foreach (Region[] regions in Puzzle.RegionsI) 17 | { 18 | foreach (Region region in regions) 19 | { 20 | for (int digit = 1; digit <= 9; digit++) 21 | { 22 | Span c = region.GetCellsWithCandidate(digit, _cellCache); 23 | if (c.Length == 1) 24 | { 25 | c[0].SetValue(digit); 26 | LogAction(TechniqueFormat("Hidden single", 27 | "{0}: {1}", 28 | c[0], digit), 29 | c[0]); 30 | changed = true; 31 | restartSearch = true; 32 | } 33 | } 34 | } 35 | } 36 | 37 | } while (restartSearch); 38 | 39 | return changed; 40 | } 41 | } -------------------------------------------------------------------------------- /SudokuSolver/SolverTechniques/Solver_HiddenTuple.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Kermalis.SudokuSolver; 6 | 7 | partial class Solver 8 | { 9 | private bool HiddenQuadruple() 10 | { 11 | for (int i = 0; i < 9; i++) 12 | { 13 | if (HiddenTuple_Find(Puzzle.BlocksI[i], 4) 14 | || HiddenTuple_Find(Puzzle.RowsI[i], 4) 15 | || HiddenTuple_Find(Puzzle.ColumnsI[i], 4)) 16 | { 17 | return true; 18 | } 19 | } 20 | return false; 21 | } 22 | 23 | private bool HiddenTriple() 24 | { 25 | for (int i = 0; i < 9; i++) 26 | { 27 | if (HiddenTuple_Find(Puzzle.BlocksI[i], 3) 28 | || HiddenTuple_Find(Puzzle.RowsI[i], 3) 29 | || HiddenTuple_Find(Puzzle.ColumnsI[i], 3)) 30 | { 31 | return true; 32 | } 33 | } 34 | return false; 35 | } 36 | 37 | private bool HiddenPair() 38 | { 39 | for (int i = 0; i < 9; i++) 40 | { 41 | if (HiddenTuple_Find(Puzzle.BlocksI[i], 2) 42 | || HiddenTuple_Find(Puzzle.RowsI[i], 2) 43 | || HiddenTuple_Find(Puzzle.ColumnsI[i], 2)) 44 | { 45 | return true; 46 | } 47 | } 48 | return false; 49 | } 50 | 51 | private bool HiddenTuple_Find(Region region, int amount) 52 | { 53 | // If there are only "amount" cells with candidates, we don't have to waste our time 54 | if (region.CountCellsWithCandidates() == amount) 55 | { 56 | return false; 57 | } 58 | 59 | return HiddenTuple_Recurse(region, amount, 0, new int[amount]); 60 | } 61 | private bool HiddenTuple_Recurse(Region region, int amount, int loop, int[] candidates) 62 | { 63 | if (loop == amount) 64 | { 65 | IEnumerable cells = candidates.Select(region.GetCellsWithCandidate).UniteAll(); 66 | IEnumerable cands = cells.Select(c => (IEnumerable)c.CandI).UniteAll(); 67 | 68 | if (cells.Count() != amount // There aren't "amount" cells for our tuple to be in 69 | || cands.Count() == amount // We already know it's a tuple (might be faster to skip this check, idk) 70 | || !cands.ContainsAll(candidates)) 71 | { 72 | return false; // If a number in our combo doesn't actually show up in any of our cells 73 | } 74 | 75 | if (Cell.ChangeCandidates(cells, Utils.OneToNine.Except(candidates))) 76 | { 77 | LogAction(TechniqueFormat("Hidden " + TupleStr[amount], 78 | "{0}: {1}", 79 | cells.Print(), Utils.PrintCandidates(candidates)), 80 | cells); 81 | return true; 82 | } 83 | } 84 | else 85 | { 86 | for (int i = candidates[loop == 0 ? loop : loop - 1] + 1; i <= 9; i++) 87 | { 88 | candidates[loop] = i; 89 | if (HiddenTuple_Recurse(region, amount, loop + 1, candidates)) 90 | { 91 | return true; 92 | } 93 | } 94 | } 95 | return false; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /SudokuSolver/SolverTechniques/Solver_LockedCandidate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Kermalis.SudokuSolver; 4 | 5 | partial class Solver 6 | { 7 | private bool LockedCandidate() 8 | { 9 | // A locked candidate is when a row or column has a candidate that can only appear in one block. 10 | // For example, if a row has 3 cells with "5" as a candidate, and all 3 of those cells are in the same block, then that block can only have "5" in that row. 11 | // Other cells in that block cannot have "5", so we clear candidates that way. 12 | 13 | // Our search will iterate each row and column and check for these conditions on each digit. 14 | 15 | for (int i = 0; i < 9; i++) 16 | { 17 | for (int candidate = 1; candidate <= 9; candidate++) 18 | { 19 | if (LockedCandidate_Find(i, candidate, true) 20 | || LockedCandidate_Find(i, candidate, false)) 21 | { 22 | return true; 23 | } 24 | } 25 | } 26 | return false; 27 | } 28 | 29 | private bool LockedCandidate_Find(int i, int candidate, bool doRows) 30 | { 31 | // Grab the row or column we will scan. 32 | Region region = (doRows ? Puzzle.RowsI : Puzzle.ColumnsI)[i]; 33 | 34 | // Grab the cells in this row/col that still have the candidate we're looking for. 35 | Span cellsWithCandidates = _cellCache.AsSpan(0, 9); 36 | cellsWithCandidates = region.GetCellsWithCandidate(candidate, cellsWithCandidates); 37 | 38 | // If there are 2 or 3 cells with the candidate, we will check to see if they share a block. 39 | if (cellsWithCandidates.Length is not 2 and not 3) 40 | { 41 | return false; 42 | } 43 | 44 | // Check which blocks the cells belong to. 45 | Span blockIndices = stackalloc int[3]; 46 | blockIndices = Utils.GetDistinctBlockIndices(cellsWithCandidates, blockIndices); 47 | 48 | // If they are in the same block, we can remove the candidate from other cells in the block. 49 | if (blockIndices.Length != 1) 50 | { 51 | return false; 52 | } 53 | 54 | // Grab the block they're in. 55 | int blockIndex = blockIndices[0]; 56 | Region block = Puzzle.BlocksI[blockIndex]; 57 | 58 | // Grab the cells in the block that don't belong to the col/row we scanned. They cannot have this candidate. 59 | // Up to 6 cells can have candidates changed. 60 | Span cellsToChange = _cellCache.AsSpan(3, 6); 61 | cellsToChange = block.Except(region, cellsToChange); 62 | 63 | if (Candidates.Set(cellsToChange, candidate, false)) 64 | { 65 | LogAction(TechniqueFormat("Locked candidate", 66 | "{4} {0} locks within block {1}: {2}: {3}", 67 | doRows ? SPoint.RowLetter(i) : SPoint.ColumnLetter(i), blockIndex + 1, Utils.PrintCells(cellsWithCandidates), candidate, doRows ? "Row" : "Column"), 68 | cellsWithCandidates); 69 | return true; 70 | } 71 | return false; 72 | } 73 | } -------------------------------------------------------------------------------- /SudokuSolver/SolverTechniques/Solver_NakedTuple.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Kermalis.SudokuSolver; 6 | 7 | partial class Solver 8 | { 9 | private bool NakedQuadruple() 10 | { 11 | for (int i = 0; i < 9; i++) 12 | { 13 | if (NakedTuple_Find(Puzzle.BlocksI[i], 4) 14 | || NakedTuple_Find(Puzzle.RowsI[i], 4) 15 | || NakedTuple_Find(Puzzle.ColumnsI[i], 4)) 16 | { 17 | return true; 18 | } 19 | } 20 | return false; 21 | } 22 | 23 | private bool NakedTriple() 24 | { 25 | for (int i = 0; i < 9; i++) 26 | { 27 | if (NakedTuple_Find(Puzzle.BlocksI[i], 3) 28 | || NakedTuple_Find(Puzzle.RowsI[i], 3) 29 | || NakedTuple_Find(Puzzle.ColumnsI[i], 3)) 30 | { 31 | return true; 32 | } 33 | } 34 | return false; 35 | } 36 | 37 | private bool NakedPair() 38 | { 39 | for (int i = 0; i < 9; i++) 40 | { 41 | if (NakedTuple_Find(Puzzle.BlocksI[i], 2) 42 | || NakedTuple_Find(Puzzle.RowsI[i], 2) 43 | || NakedTuple_Find(Puzzle.ColumnsI[i], 2)) 44 | { 45 | return true; 46 | } 47 | } 48 | return false; 49 | } 50 | 51 | private bool NakedTuple_Find(Region region, int amount) 52 | { 53 | return NakedTuple_Recurse(region, amount, 0, new Cell[amount], new int[amount]); 54 | } 55 | private bool NakedTuple_Recurse(Region region, int amount, int loop, Cell[] cells, int[] indexes) 56 | { 57 | if (loop == amount) 58 | { 59 | IEnumerable combo = cells.Select(c => (IEnumerable)c.CandI).UniteAll(); 60 | 61 | if (combo.Count() == amount) 62 | { 63 | if (Cell.ChangeCandidates(indexes.Select(i => region[i].VisibleCells).IntersectAll(), combo)) 64 | { 65 | LogAction(TechniqueFormat("Naked " + TupleStr[amount], 66 | "{0}: {1}", 67 | Utils.PrintCells(cells), combo.Print()), 68 | (ReadOnlySpan)cells); 69 | return true; 70 | } 71 | } 72 | } 73 | else 74 | { 75 | for (int i = loop == 0 ? 0 : indexes[loop - 1] + 1; i < 9; i++) 76 | { 77 | Cell c = region[i]; 78 | if (c.CandI.Count == 0) 79 | { 80 | continue; 81 | } 82 | 83 | cells[loop] = c; 84 | indexes[loop] = i; 85 | 86 | if (NakedTuple_Recurse(region, amount, loop + 1, cells, indexes)) 87 | { 88 | return true; 89 | } 90 | } 91 | } 92 | return false; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /SudokuSolver/SolverTechniques/Solver_PointingTuple.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Kermalis.SudokuSolver; 6 | 7 | partial class Solver 8 | { 9 | private static ReadOnlySpan OrdinalStr => new string[4] { string.Empty, "1st", "2nd", "3rd" }; 10 | 11 | private bool PointingTuple() 12 | { 13 | var blockrow = new Cell[3][]; 14 | var blockcol = new Cell[3][]; 15 | for (int i = 0; i < 3; i++) 16 | { 17 | for (int r = 0; r < 3; r++) 18 | { 19 | blockrow[r] = [.. Puzzle.BlocksI[r + (i * 3)]]; 20 | blockcol[r] = [.. Puzzle.BlocksI[i + (r * 3)]]; 21 | } 22 | 23 | for (int r = 0; r < 3; r++) // 3 blocks in a blockrow/blockcolumn 24 | { 25 | int[][] rowCandidates = new int[3][]; 26 | int[][] colCand = new int[3][]; 27 | for (int j = 0; j < 3; j++) // 3 rows/columns in block 28 | { 29 | // The 3 cells' candidates in a block's row/column 30 | rowCandidates[j] = blockrow[r].GetRowInBlock(j).Select(c => (IEnumerable)c.CandI).UniteAll().ToArray(); 31 | colCand[j] = blockcol[r].GetColumnInBlock(j).Select(c => (IEnumerable)c.CandI).UniteAll().ToArray(); 32 | } 33 | 34 | bool RemovePointingTuple(bool doRows, int rcIndex, IEnumerable candidates) 35 | { 36 | bool changed = false; 37 | for (int j = 0; j < 3; j++) 38 | { 39 | if (j == r) 40 | { 41 | continue; 42 | } 43 | 44 | Cell[] rcs = doRows ? blockrow[j].GetRowInBlock(rcIndex) : blockcol[j].GetColumnInBlock(rcIndex); 45 | if (Cell.ChangeCandidates(rcs, candidates)) 46 | { 47 | changed = true; 48 | } 49 | } 50 | 51 | if (changed) 52 | { 53 | ReadOnlySpan culprits = doRows ? blockrow[r].GetRowInBlock(rcIndex) : blockcol[r].GetColumnInBlock(rcIndex); 54 | LogAction(TechniqueFormat("Pointing tuple", 55 | "Starting in block{0} {1}'s {2} block, {3} {0}: {4}", 56 | doRows ? "row" : "column", i + 1, OrdinalStr[r + 1], OrdinalStr[rcIndex + 1], candidates.SingleOrMultiToString()), 57 | culprits); 58 | } 59 | return changed; 60 | } 61 | 62 | // Now check if a row has a distinct candidate 63 | IEnumerable zero_distinct = rowCandidates[0].Except(rowCandidates[1]).Except(rowCandidates[2]); 64 | if (zero_distinct.Any()) 65 | { 66 | if (RemovePointingTuple(true, 0, zero_distinct)) 67 | { 68 | return true; 69 | } 70 | } 71 | IEnumerable one_distinct = rowCandidates[1].Except(rowCandidates[0]).Except(rowCandidates[2]); 72 | if (one_distinct.Any()) 73 | { 74 | if (RemovePointingTuple(true, 1, one_distinct)) 75 | { 76 | return true; 77 | } 78 | } 79 | IEnumerable two_distinct = rowCandidates[2].Except(rowCandidates[0]).Except(rowCandidates[1]); 80 | if (two_distinct.Any()) 81 | { 82 | if (RemovePointingTuple(true, 2, two_distinct)) 83 | { 84 | return true; 85 | } 86 | } 87 | 88 | // Now check if a column has a distinct candidate 89 | zero_distinct = colCand[0].Except(colCand[1]).Except(colCand[2]); 90 | if (zero_distinct.Any()) 91 | { 92 | if (RemovePointingTuple(false, 0, zero_distinct)) 93 | { 94 | return true; 95 | } 96 | } 97 | one_distinct = colCand[1].Except(colCand[0]).Except(colCand[2]); 98 | if (one_distinct.Any()) 99 | { 100 | if (RemovePointingTuple(false, 1, one_distinct)) 101 | { 102 | return true; 103 | } 104 | } 105 | two_distinct = colCand[2].Except(colCand[0]).Except(colCand[1]); 106 | if (two_distinct.Any()) 107 | { 108 | if (RemovePointingTuple(false, 2, two_distinct)) 109 | { 110 | return true; 111 | } 112 | } 113 | } 114 | } 115 | return false; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /SudokuSolver/SolverTechniques/Solver_UniqueRectangle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Kermalis.SudokuSolver; 6 | 7 | partial class Solver 8 | { 9 | private bool UniqueRectangle() 10 | { 11 | for (int type = 1; type <= 6; type++) // Type 12 | { 13 | for (int x1 = 0; x1 < 9; x1++) 14 | { 15 | Region c1 = Puzzle.ColumnsI[x1]; 16 | for (int x2 = x1 + 1; x2 < 9; x2++) 17 | { 18 | Region c2 = Puzzle.ColumnsI[x2]; 19 | for (int y1 = 0; y1 < 9; y1++) 20 | { 21 | for (int y2 = y1 + 1; y2 < 9; y2++) 22 | { 23 | for (int value1 = 1; value1 <= 9; value1++) 24 | { 25 | for (int value2 = value1 + 1; value2 <= 9; value2++) 26 | { 27 | int[] candidates = [value1, value2]; 28 | Cell[] cells = [c1[y1], c1[y2], c2[y1], c2[y2]]; 29 | if (cells.Any(c => !c.CandI.ContainsAll(candidates))) 30 | { 31 | continue; 32 | } 33 | 34 | ILookup l = cells.ToLookup(c => c.CandI.Count); 35 | Cell[] gtTwo = l.Where(g => g.Key > 2).SelectMany(g => g).ToArray(), 36 | two = l[2].ToArray(), three = l[3].ToArray(), four = l[4].ToArray(); 37 | 38 | switch (type) // Check for candidate counts 39 | { 40 | case 1: 41 | { 42 | if (two.Length != 3 || gtTwo.Length != 1) 43 | { 44 | continue; 45 | } 46 | break; 47 | } 48 | case 2: 49 | case 6: 50 | { 51 | if (two.Length != 2 || three.Length != 2) 52 | { 53 | continue; 54 | } 55 | break; 56 | } 57 | case 3: 58 | { 59 | if (two.Length != 2 || gtTwo.Length != 2) 60 | { 61 | continue; 62 | } 63 | break; 64 | } 65 | case 4: 66 | { 67 | if (two.Length != 2 || three.Length != 1 || four.Length != 1) 68 | { 69 | continue; 70 | } 71 | break; 72 | } 73 | case 5: 74 | { 75 | if (two.Length != 1 || three.Length != 3) 76 | { 77 | continue; 78 | } 79 | break; 80 | } 81 | } 82 | 83 | switch (type) // Check for extra rules 84 | { 85 | case 1: 86 | { 87 | if (gtTwo[0].CandI.Count == 3) 88 | { 89 | gtTwo[0].SetValue(gtTwo[0].CandI.Single(c => !candidates.Contains(c))); 90 | } 91 | else 92 | { 93 | Cell.ChangeCandidates(gtTwo, candidates); 94 | } 95 | break; 96 | } 97 | case 2: 98 | { 99 | if (!three[0].CandI.SetEquals(three[1].CandI)) 100 | { 101 | continue; 102 | } 103 | if (!Cell.ChangeCandidates(three[0].VisibleCells.Intersect(three[1].VisibleCells), three[0].CandI.Except(candidates))) 104 | { 105 | continue; 106 | } 107 | break; 108 | } 109 | case 3: 110 | { 111 | if (gtTwo[0].Point.Column != gtTwo[1].Point.Column && gtTwo[0].Point.Row != gtTwo[1].Point.Row) 112 | { 113 | continue; // Must be non-diagonal 114 | } 115 | IEnumerable others = gtTwo[0].CandI.Except(candidates).Union(gtTwo[1].CandI.Except(candidates)); 116 | if (others.Count() > 4 || others.Count() < 2) 117 | { 118 | continue; 119 | } 120 | IEnumerable nSubset = ((gtTwo[0].Point.Row == gtTwo[1].Point.Row) ? // Same row 121 | Puzzle.RowsI[gtTwo[0].Point.Row] : Puzzle.ColumnsI[gtTwo[0].Point.Column]) 122 | .Where(c => c.CandI.ContainsAny(others) && !c.CandI.ContainsAny(Utils.OneToNine.Except(others))); 123 | if (nSubset.Count() != others.Count() - 1) 124 | { 125 | continue; 126 | } 127 | if (!Cell.ChangeCandidates(nSubset.Union(gtTwo).Select(c => c.VisibleCells).IntersectAll(), others)) 128 | { 129 | continue; 130 | } 131 | break; 132 | } 133 | case 4: 134 | { 135 | int[] remove = new int[1]; 136 | if (four[0].Point.BlockIndex == three[0].Point.BlockIndex) 137 | { 138 | if (Puzzle.BlocksI[four[0].Point.BlockIndex].GetCellsWithCandidate(value1).Count() == 2) 139 | { 140 | remove[0] = value2; 141 | } 142 | else if (Puzzle.BlocksI[four[0].Point.BlockIndex].GetCellsWithCandidate(value2).Count() == 2) 143 | { 144 | remove[0] = value1; 145 | } 146 | } 147 | if (remove[0] != 0) // They share the same row/column but not the same block 148 | { 149 | if (three[0].Point.Column == three[0].Point.Column) 150 | { 151 | if (Puzzle.ColumnsI[four[0].Point.Column].GetCellsWithCandidate(value1).Count() == 2) 152 | { 153 | remove[0] = value2; 154 | } 155 | else if (Puzzle.ColumnsI[four[0].Point.Column].GetCellsWithCandidate(value2).Count() == 2) 156 | { 157 | remove[0] = value1; 158 | } 159 | } 160 | else 161 | { 162 | if (Puzzle.RowsI[four[0].Point.Row].GetCellsWithCandidate(value1).Count() == 2) 163 | { 164 | remove[0] = value2; 165 | } 166 | else if (Puzzle.RowsI[four[0].Point.Row].GetCellsWithCandidate(value2).Count() == 2) 167 | { 168 | remove[0] = value1; 169 | } 170 | } 171 | } 172 | else 173 | { 174 | continue; 175 | } 176 | Cell.ChangeCandidates(cells.Except(l[2]), remove); 177 | break; 178 | } 179 | case 5: 180 | { 181 | if (!three[0].CandI.SetEquals(three[1].CandI) || !three[1].CandI.SetEquals(three[2].CandI)) 182 | { 183 | continue; 184 | } 185 | if (!Cell.ChangeCandidates(three.Select(c => c.VisibleCells).IntersectAll(), three[0].CandI.Except(candidates))) 186 | { 187 | continue; 188 | } 189 | break; 190 | } 191 | case 6: 192 | { 193 | if (three[0].Point.Column == three[1].Point.Column) 194 | { 195 | continue; 196 | } 197 | int set; 198 | if (c1.GetCellsWithCandidate(value1).Count() == 2 && c2.GetCellsWithCandidate(value1).Count() == 2 // Check if "v" only appears in the UR 199 | && Puzzle.RowsI[two[0].Point.Row].GetCellsWithCandidate(value1).Count() == 2 200 | && Puzzle.RowsI[two[1].Point.Row].GetCellsWithCandidate(value1).Count() == 2) 201 | { 202 | set = value1; 203 | } 204 | else if (c1.GetCellsWithCandidate(value2).Count() == 2 && c2.GetCellsWithCandidate(value2).Count() == 2 205 | && Puzzle.RowsI[two[0].Point.Row].GetCellsWithCandidate(value2).Count() == 2 206 | && Puzzle.RowsI[two[1].Point.Row].GetCellsWithCandidate(value2).Count() == 2) 207 | { 208 | set = value2; 209 | } 210 | else 211 | { 212 | continue; 213 | } 214 | two[0].SetValue(set); 215 | two[1].SetValue(set); 216 | break; 217 | } 218 | } 219 | 220 | LogAction(TechniqueFormat("Unique rectangle", 221 | "{0}: {1}", 222 | Utils.PrintCells(cells), Utils.PrintCandidates(candidates)), 223 | (ReadOnlySpan)cells); 224 | return true; 225 | } 226 | } 227 | } 228 | } 229 | } 230 | } 231 | } 232 | return false; 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /SudokuSolver/SolverTechniques/Solver_XYChain.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Kermalis.SudokuSolver; 5 | 6 | partial class Solver 7 | { 8 | private bool XYChain() 9 | { 10 | var ignore = new List(); // TODO: Alloc 11 | 12 | for (int col = 0; col < 9; col++) 13 | { 14 | for (int row = 0; row < 9; row++) 15 | { 16 | Cell cell = Puzzle[col, row]; 17 | if (!cell.CandI.TryGetCount2(out int start1, out int start2)) 18 | { 19 | continue; // Must have two candidates 20 | } 21 | 22 | ignore.Clear(); 23 | if (XYChain_Recursion(cell, ignore, cell, start1, start2) 24 | || XYChain_Recursion(cell, ignore, cell, start2, start1)) 25 | { 26 | return true; 27 | } 28 | } 29 | } 30 | return false; 31 | } 32 | 33 | private bool XYChain_Recursion(Cell startCell, List ignore, Cell currentCell, int theOneThatWillEndItAllBaybee, int mustFind) 34 | { 35 | ignore.Add(currentCell); 36 | IEnumerable visible = currentCell.VisibleCells.Except(ignore); 37 | foreach (Cell cell in visible) 38 | { 39 | if (!cell.CandI.TryGetCount2(out int can1, out int can2)) 40 | { 41 | continue; // Must have two candidates 42 | } 43 | if (can1 != mustFind && can2 != mustFind) 44 | { 45 | continue; // Must have "mustFind" 46 | } 47 | 48 | int otherCandidate = mustFind == can1 ? can2 : can1; 49 | // Check end condition 50 | if (otherCandidate == theOneThatWillEndItAllBaybee && startCell != currentCell) 51 | { 52 | Cell[] commonVisibleWithStartCell = cell.VisibleCells.Intersect(startCell.VisibleCells).ToArray(); 53 | if (commonVisibleWithStartCell.Length > 0) 54 | { 55 | IEnumerable commonWithEndingCandidate = commonVisibleWithStartCell.Where(c => c.CandI.Contains(theOneThatWillEndItAllBaybee)); 56 | if (Cell.ChangeCandidates(commonWithEndingCandidate, theOneThatWillEndItAllBaybee)) 57 | { 58 | ignore.Remove(startCell); // Remove here because we're now using "ignore" as "semiCulprits" and exiting 59 | Cell[] culprits = [startCell, cell]; 60 | LogAction(TechniqueFormat("XY-Chain", 61 | "{0}-{1}: {2}", 62 | Utils.PrintCells(culprits), ignore.SingleOrMultiToString(), theOneThatWillEndItAllBaybee), 63 | culprits, ignore); 64 | return true; 65 | } 66 | } 67 | } 68 | // Loop again 69 | if (XYChain_Recursion(startCell, ignore, cell, theOneThatWillEndItAllBaybee, otherCandidate)) 70 | { 71 | return true; 72 | } 73 | } 74 | ignore.Remove(currentCell); 75 | return false; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /SudokuSolver/SolverTechniques/Solver_XYZWing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Kermalis.SudokuSolver; 6 | 7 | partial class Solver 8 | { 9 | private bool XYZWing() 10 | { 11 | for (int i = 0; i < 9; i++) 12 | { 13 | if (XYZWing_Find(Puzzle.RowsI[i]) || XYZWing_Find(Puzzle.ColumnsI[i])) 14 | { 15 | return true; 16 | } 17 | } 18 | return false; 19 | } 20 | 21 | private bool XYZWing_Find(Region region) 22 | { 23 | Cell[] cells2 = region.Where(c => c.CandI.Count == 2).ToArray(); 24 | if (cells2.Length == 0) 25 | { 26 | return false; 27 | } 28 | 29 | Cell[] cells3 = region.Where(c => c.CandI.Count == 3).ToArray(); 30 | if (cells3.Length == 0) 31 | { 32 | return false; 33 | } 34 | 35 | bool changed = false; 36 | foreach (Cell c2 in cells2) 37 | { 38 | foreach (Cell c3 in cells3) 39 | { 40 | if (c2.CandI.Intersect(c3.CandI).Count() != 2) 41 | { 42 | continue; 43 | } 44 | 45 | IEnumerable c3Sees = c3.VisibleCells.Except(region) 46 | .Where(c => c.CandI.Count == 2 // If it has 2 candidates 47 | && c.CandI.Intersect(c3.CandI).Count() == 2 // Shares them both with c3 48 | && c.CandI.Intersect(c2.CandI).Count() == 1); // And shares one with c2 49 | foreach (Cell c2_2 in c3Sees) 50 | { 51 | IEnumerable allSee = c2.VisibleCells.Intersect(c3.VisibleCells).Intersect(c2_2.VisibleCells); 52 | int allHave = c2.CandI.Intersect(c3.CandI).Intersect(c2_2.CandI).Single(); // Will be 1 Length 53 | 54 | if (Cell.ChangeCandidates(allSee, allHave)) 55 | { 56 | Span culprits = _cellCache.AsSpan(0, 3); 57 | culprits[0] = c2; 58 | culprits[1] = c3; 59 | culprits[2] = c2_2; 60 | 61 | LogAction(TechniqueFormat("XYZ-Wing", 62 | "{0}: {1}", 63 | Utils.PrintCells(culprits), allHave), 64 | culprits); 65 | changed = true; 66 | } 67 | } 68 | } 69 | } 70 | return changed; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SudokuSolver/SolverTechniques/Solver_YWing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace Kermalis.SudokuSolver; 5 | 6 | partial class Solver 7 | { 8 | // TODO: Comments 9 | private bool YWing() 10 | { 11 | for (int i = 0; i < 9; i++) 12 | { 13 | if (YWing_Find(Puzzle.RowsI[i]) || YWing_Find(Puzzle.ColumnsI[i])) 14 | { 15 | return true; 16 | } 17 | } 18 | return false; 19 | } 20 | 21 | private bool YWing_Find(Region region) 22 | { 23 | Span cellsWith2Cand = _cellCache.AsSpan(0, 9); 24 | cellsWith2Cand = region.GetCellsWithCandidateCount(2, cellsWith2Cand); 25 | if (cellsWith2Cand.Length < 2) 26 | { 27 | return false; 28 | } 29 | 30 | for (int i = 0; i < cellsWith2Cand.Length; i++) 31 | { 32 | Cell c1 = cellsWith2Cand[i]; 33 | 34 | for (int j = i + 1; j < cellsWith2Cand.Length; j++) 35 | { 36 | Cell c2 = cellsWith2Cand[j]; 37 | 38 | if (!YWing_GetSingleSharedCandidate(c1.CandI, c2.CandI, out int other1, out int other2)) 39 | { 40 | continue; 41 | } 42 | 43 | if (YWing_Match(cellsWith2Cand, other1, other2, c1, c2)) 44 | { 45 | return true; 46 | } 47 | if (YWing_Match(cellsWith2Cand, other1, other2, c2, c1)) 48 | { 49 | return true; 50 | } 51 | } 52 | } 53 | 54 | return false; 55 | } 56 | private static bool YWing_GetSingleSharedCandidate(Candidates a, Candidates b, out int other1, out int other2) 57 | { 58 | // These two cells only have 2 candidates each. 59 | // For example: 60 | // 4 and 5 61 | // 3 and 5 62 | // ...1 shared candidate. 63 | 64 | a.GetCount2(out int a1, out int a2); 65 | b.GetCount2(out int b1, out int b2); 66 | 67 | int sharedCount = 0; 68 | int sharedCandidate = -1; 69 | 70 | if (a1 == b1) 71 | { 72 | sharedCandidate = a1; 73 | sharedCount++; 74 | } 75 | if (a1 == b2) 76 | { 77 | sharedCandidate = a1; 78 | sharedCount++; 79 | } 80 | if (a2 == b1) 81 | { 82 | sharedCandidate = a2; 83 | sharedCount++; 84 | } 85 | if (a2 == b2) 86 | { 87 | sharedCandidate = a2; 88 | sharedCount++; 89 | } 90 | 91 | if (sharedCount != 1) 92 | { 93 | other1 = -1; 94 | other2 = -1; 95 | return false; 96 | } 97 | 98 | other1 = sharedCandidate == a1 ? a2 : a1; 99 | other2 = sharedCandidate == b1 ? b2 : b1; 100 | return true; 101 | } 102 | private bool YWing_Match(ReadOnlySpan cellsWith2Cand, int other1, int other2, Cell cell, Cell cOther) 103 | { 104 | // Example: c1 and c3 see each other, so remove similarities from c2 and c3 105 | if (!YWing_FindSingleMatch(cell, cellsWith2Cand, other1, other2, out Cell? c3)) 106 | { 107 | return false; 108 | } 109 | 110 | Span commonCells = _cellCache.AsSpan(9, 7); 111 | commonCells = c3.IntersectVisibleCells(cOther, commonCells); 112 | 113 | Span commonCandidates = stackalloc int[1]; 114 | commonCandidates = c3.CandI.Intersect(cOther.CandI, commonCandidates); // Will just be 1 candidate 115 | int candidate = commonCandidates[0]; 116 | if (Candidates.Set(commonCells, candidate, false)) 117 | { 118 | Span culprits = _cellCache.AsSpan(0, 3); 119 | culprits[0] = cell; 120 | culprits[1] = cOther; 121 | culprits[2] = c3; 122 | 123 | LogAction(TechniqueFormat("Y-Wing", 124 | "{0}: {1}", 125 | Utils.PrintCells(culprits), candidate), 126 | culprits); 127 | return true; 128 | } 129 | return false; 130 | } 131 | private static bool YWing_FindSingleMatch(Cell cell, ReadOnlySpan cellsWith2Cand, int other1, int other2, [NotNullWhen(true)] out Cell? c3) 132 | { 133 | // Desperately need comments! 134 | c3 = null; 135 | 136 | foreach (Cell c in cell.VisibleI) 137 | { 138 | if (cellsWith2Cand.SimpleIndexOf(c) != -1) 139 | { 140 | continue; 141 | } 142 | 143 | if (c.CandI.Count == 2 && c.CandI.HasBoth(other1, other2)) 144 | { 145 | if (c3 is not null) 146 | { 147 | return false; // More than 1 match 148 | } 149 | c3 = c; // Our current match 150 | } 151 | } 152 | 153 | return c3 is not null; 154 | } 155 | } -------------------------------------------------------------------------------- /SudokuSolver/Solver_Techniques.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Kermalis.SudokuSolver; 4 | 5 | partial class Solver 6 | { 7 | private sealed class SolverTechnique 8 | { 9 | public Func Function { get; } 10 | /// Currently unused. 11 | public string Url { get; } 12 | 13 | public SolverTechnique(Func function, string url) 14 | { 15 | Function = function; 16 | Url = url; 17 | } 18 | } 19 | 20 | private static ReadOnlySpan TupleStr => new string[5] { string.Empty, "single", "pair", "triple", "quadruple" }; 21 | 22 | private readonly SolverTechnique[] _techniques; 23 | 24 | private readonly Cell[] _cellCache = new Cell[20]; 25 | private readonly int[] _intCache = new int[20]; 26 | 27 | private SolverTechnique[] InitSolverTechniques() 28 | { 29 | return [ 30 | new SolverTechnique(HiddenSingle, "Hidden single"), 31 | new SolverTechnique(NakedPair, "https://hodoku.sourceforge.net/en/tech_naked.php#n2"), 32 | new SolverTechnique(HiddenPair, "https://hodoku.sourceforge.net/en/tech_hidden.php#h2"), 33 | new SolverTechnique(LockedCandidate, "https://hodoku.sourceforge.net/en/tech_intersections.php#lc2"), 34 | new SolverTechnique(PointingTuple, "https://hodoku.sourceforge.net/en/tech_intersections.php#lc1"), 35 | new SolverTechnique(NakedTriple, "https://hodoku.sourceforge.net/en/tech_naked.php#n3"), 36 | new SolverTechnique(HiddenTriple, "https://hodoku.sourceforge.net/en/tech_hidden.php#h3"), 37 | new SolverTechnique(XWing, "https://hodoku.sourceforge.net/en/tech_fishb.php#bf2"), 38 | new SolverTechnique(Swordfish, "https://hodoku.sourceforge.net/en/tech_fishb.php#bf3"), 39 | new SolverTechnique(YWing, "https://www.sudokuwiki.org/Y_Wing_Strategy"), 40 | new SolverTechnique(XYZWing, "https://www.sudokuwiki.org/XYZ_Wing"), 41 | new SolverTechnique(XYChain, "https://www.sudokuwiki.org/XY_Chains"), 42 | new SolverTechnique(NakedQuadruple, "https://hodoku.sourceforge.net/en/tech_naked.php#n4"), 43 | new SolverTechnique(HiddenQuadruple, "https://hodoku.sourceforge.net/en/tech_hidden.php#h4"), 44 | new SolverTechnique(Jellyfish, "https://hodoku.sourceforge.net/en/tech_fishb.php#bf4"), 45 | new SolverTechnique(UniqueRectangle, "https://hodoku.sourceforge.net/en/tech_ur.php"), 46 | new SolverTechnique(HiddenRectangle, "https://hodoku.sourceforge.net/en/tech_ur.php#hr"), 47 | new SolverTechnique(AvoidableRectangle, "https://hodoku.sourceforge.net/en/tech_ur.php#ar"), 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /SudokuSolver/SudokuSolver.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | Library 6 | latest 7 | Kermalis.SudokuSolver 8 | enable 9 | 10 | 11 | -------------------------------------------------------------------------------- /SudokuSolver/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Kermalis.SudokuSolver; 8 | 9 | internal static class Utils 10 | { 11 | public static ReadOnlyCollection OneToNine { get; } = new ReadOnlyCollection(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); 12 | public static ReadOnlySpan OneToNineSpan => new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 13 | private static readonly StringBuilder _sb = new(); 14 | 15 | public static int CellIndex(int col, int row) 16 | { 17 | return (row * 9) + col; 18 | } 19 | 20 | /// outCol must be 3 length 21 | public static void GetColumnInBlock(this Cell[] block, int x, Span outCol) 22 | { 23 | for (int col = 0; col < 3; col++) 24 | { 25 | outCol[col] = block[(x * 3) + col]; 26 | } 27 | } 28 | /// outCol must be 3 length 29 | public static void GetRowInBlock(this Cell[] block, int y, Span outRow) 30 | { 31 | for (int row = 0; row < 3; row++) 32 | { 33 | outRow[row] = block[(row * 3) + y]; 34 | } 35 | } 36 | public static Cell[] GetColumnInBlock(this Cell[] block, int x) 37 | { 38 | var column = new Cell[3]; 39 | for (int i = 0; i < 3; i++) 40 | { 41 | column[i] = block[(x * 3) + i]; 42 | } 43 | return column; 44 | } 45 | public static Cell[] GetRowInBlock(this Cell[] block, int y) 46 | { 47 | var row = new Cell[3]; 48 | for (int i = 0; i < 3; i++) 49 | { 50 | row[i] = block[(i * 3) + y]; 51 | } 52 | return row; 53 | } 54 | 55 | public static bool ContainsAny(this IEnumerable source, IEnumerable values) 56 | { 57 | foreach (T o in values) 58 | { 59 | if (source.Contains(o)) 60 | { 61 | return true; 62 | } 63 | } 64 | return false; 65 | } 66 | public static bool ContainsAll(this IEnumerable source, IEnumerable values) 67 | { 68 | foreach (T o in values) 69 | { 70 | if (!source.Contains(o)) 71 | { 72 | return false; 73 | } 74 | } 75 | return true; 76 | } 77 | public static IEnumerable IntersectAll(this IEnumerable> source) 78 | { 79 | if (!source.Any()) 80 | { 81 | return Array.Empty(); 82 | } 83 | 84 | IEnumerable[] inp = source.ToArray(); 85 | IEnumerable output = inp[0]; 86 | for (int i = 1; i < inp.Length; i++) 87 | { 88 | output = output.Intersect(inp[i]); 89 | } 90 | return output; 91 | } 92 | public static IEnumerable UniteAll(this IEnumerable> source) 93 | { 94 | IEnumerable output = Array.Empty(); 95 | foreach (IEnumerable i in source) 96 | { 97 | output = output.Union(i); 98 | } 99 | return output; 100 | } 101 | 102 | public static string SingleOrMultiToString(this IEnumerable source) 103 | { 104 | int i = 0; 105 | foreach (T o in source) 106 | { 107 | if (++i > 1) 108 | { 109 | return source.Print(); 110 | } 111 | } 112 | if (i == 0) 113 | { 114 | throw new ArgumentOutOfRangeException(nameof(source), "No elements in source"); 115 | } 116 | return source.ElementAt(0)!.ToString()!; 117 | } 118 | public static string Print(this IEnumerable source) 119 | { 120 | return _sb.Clear() 121 | .Append("( ") 122 | .AppendJoin(", ", source) 123 | .Append(" )") 124 | .ToString(); 125 | } 126 | public static string PrintCells(ReadOnlySpan cells) 127 | { 128 | _sb.Clear() 129 | .Append("( "); 130 | 131 | for (int i = 0; i < cells.Length; i++) 132 | { 133 | if (i != 0) 134 | { 135 | _sb.Append(", "); 136 | } 137 | _sb.Append(cells[i].ToString()); 138 | } 139 | 140 | return _sb.Append(" )") 141 | .ToString(); 142 | } 143 | public static string PrintCandidates(ReadOnlySpan candidates) 144 | { 145 | _sb.Clear() 146 | .Append("( "); 147 | 148 | for (int i = 0; i < candidates.Length; i++) 149 | { 150 | if (i != 0) 151 | { 152 | _sb.Append(", "); 153 | } 154 | _sb.Append(candidates[i]); 155 | } 156 | 157 | return _sb.Append(" )") 158 | .ToString(); 159 | } 160 | 161 | public static int SimpleIndexOf(this ReadOnlySpan cells, Cell cell) 162 | { 163 | for (int i = 0; i < cells.Length; i++) 164 | { 165 | if (cells[i].Point == cell.Point) 166 | { 167 | return i; 168 | } 169 | } 170 | return -1; 171 | } 172 | public static int SimpleIndexOf(this ReadOnlySpan source, int value) 173 | { 174 | for (int i = 0; i < source.Length; i++) 175 | { 176 | if (source[i] == value) 177 | { 178 | return i; 179 | } 180 | } 181 | return -1; 182 | } 183 | 184 | /// Result length is [1, 9]. If there are only 2 cells for example, then it's [1, 2] 185 | public static Span GetDistinctBlockIndices(ReadOnlySpan cells, Span cache) 186 | { 187 | int retLength = 0; 188 | foreach (Cell c in cells) 189 | { 190 | int blockIndex = c.Point.BlockIndex; 191 | if (retLength == 0 || SimpleIndexOf(cache.Slice(0, retLength), blockIndex) == -1) 192 | { 193 | cache[retLength++] = blockIndex; 194 | } 195 | } 196 | return cache.Slice(0, retLength); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /SudokuSolverTests/SudokuSolverTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | latest 6 | Kermalis.SudokuSolver.Tests 7 | enable 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /SudokuSolverTests/TestSolverTechniques.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | using Xunit.Abstractions; 4 | 5 | namespace Kermalis.SudokuSolver.Tests; 6 | 7 | [Collection(TestUtilsCollection.DEF)] 8 | public sealed class TestSolverTechniques 9 | { 10 | private readonly TestUtils _utils; 11 | 12 | public TestSolverTechniques(TestUtils utils, ITestOutputHelper output) 13 | { 14 | _utils = utils; 15 | _utils.SetOutputHelper(output); 16 | } 17 | 18 | private void SolveBasic(string technique, ReadOnlySpan puzzleText) 19 | { 20 | Solver solver = _utils.CreateSolver(puzzleText); 21 | _utils.AssertSolvedCorrectly(solver, technique); 22 | } 23 | 24 | // TODO: Avoidable Rectangle, Hidden Pair, Locked Candidate, Naked Quadruple, Naked Pair, Swordfish, Unique Rectangle (all kinds), X-Wing, XYZ-Wing 25 | 26 | [Fact] 27 | public void HiddenRectangle() 28 | { 29 | SolveBasic("Hidden rectangle", [ 30 | "----5-2--", 31 | "3--7-----", 32 | "7---9--1-", 33 | "9--1--746", 34 | "---------", 35 | "---5----9", 36 | "-29---4--", 37 | "-6---4---", 38 | "-8---23-1" 39 | ]); 40 | } 41 | 42 | [Fact] 43 | public void HiddenQuadruple() 44 | { 45 | SolveBasic("Hidden quadruple", [ 46 | "-3-----1-", 47 | "--8-9----", 48 | "4--6-8---", 49 | "---57694-", 50 | "---98352-", 51 | "---124---", 52 | "276--519-", 53 | "---7-9---", 54 | "-95---47-" 55 | ]); 56 | } 57 | [Fact] 58 | public void HiddenTriple() 59 | { 60 | SolveBasic("Hidden triple", [ 61 | "4-7-----5", 62 | "---2--7--", 63 | "2-1-7-6-8", 64 | "--91-23--", 65 | "3-2-97---", 66 | "17--6----", 67 | "72-851--6", 68 | "986734---", 69 | "51-629---" 70 | ]); 71 | } 72 | 73 | [Fact] 74 | public void Jellyfish() 75 | { 76 | SolveBasic("Jellyfish", [ 77 | "2-------3", 78 | "-8--3--5-", 79 | "--34-21--", 80 | "--12-54--", 81 | "----9----", 82 | "--93-86--", 83 | "--25-69--", 84 | "-9--2--7-", 85 | "4-------1" 86 | ]); 87 | } 88 | 89 | [Fact] 90 | public void NakedTriple() 91 | { 92 | SolveBasic("Naked triple", [ 93 | "891--576-", 94 | "53769-2-8", 95 | "462-----5", 96 | "24351-8-6", 97 | "156---4--", 98 | "978-465--", 99 | "319-5-687", 100 | "684----52", 101 | "72586-3--" 102 | ]); 103 | } 104 | 105 | [Fact] 106 | public void PointingTuple() 107 | { 108 | SolveBasic("Pointing tuple", [ 109 | "-32--61--", 110 | "41-------", 111 | "---9-1---", 112 | "5---9---4", 113 | "-6-----7-", 114 | "3---2---5", 115 | "---5-8---", 116 | "-------19", 117 | "--7---86-" 118 | ]); 119 | } 120 | 121 | [Fact] 122 | public void XYChain() 123 | { 124 | SolveBasic("XY-Chain", [ 125 | "--3--1---", 126 | "8--------", 127 | "-51--9-6-", 128 | "-8----29-", 129 | "---7---8-", 130 | "2---4-5-3", 131 | "6--9-----", 132 | "--2-84---", 133 | "41--5-6--" 134 | ]); 135 | } 136 | 137 | [Fact] 138 | public void YWing() 139 | { 140 | SolveBasic("Y-Wing", [ 141 | "9---4----", 142 | "---6---31", 143 | "-2-----9-", 144 | "---7---2-", 145 | "--29356--", 146 | "-7---2---", 147 | "-6-----73", 148 | "51---9---", 149 | "----8---9" 150 | ]); 151 | } 152 | } -------------------------------------------------------------------------------- /SudokuSolverTests/TestUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using Xunit; 4 | using Xunit.Abstractions; 5 | 6 | namespace Kermalis.SudokuSolver.Tests; 7 | 8 | [CollectionDefinition(DEF)] 9 | public sealed class TestUtilsCollection : ICollectionFixture 10 | { 11 | internal const string DEF = "Utils"; 12 | } 13 | 14 | public sealed class TestUtils 15 | { 16 | private ITestOutputHelper _output = null!; 17 | 18 | public void SetOutputHelper(ITestOutputHelper output) 19 | { 20 | _output = output; 21 | } 22 | 23 | public Solver CreateSolver(ReadOnlySpan puzzleText) 24 | { 25 | var solver = new Solver(Puzzle.Parse(puzzleText)); 26 | _output.WriteLine(solver.Puzzle.ToStringFancy()); 27 | _output.WriteLine(string.Empty); 28 | solver.Actions.ListChanged += Actions_ListChanged; 29 | return solver; 30 | } 31 | public void AssertSolvedCorrectly(Solver solver, string technique) 32 | { 33 | Assert.True(solver.TrySolve()); 34 | Assert.False(solver.Puzzle.CheckForErrors()); 35 | Assert.Contains(solver.Actions, a => a.Action.StartsWith(technique, StringComparison.OrdinalIgnoreCase)); 36 | 37 | _output.WriteLine(string.Empty); 38 | _output.WriteLine(solver.Puzzle.ToStringFancy()); 39 | } 40 | 41 | private void Actions_ListChanged(object? sender, ListChangedEventArgs e) 42 | { 43 | if (e.ListChangedType == ListChangedType.ItemAdded) 44 | { 45 | var list = (BindingList)sender!; 46 | _output.WriteLine(list[e.NewIndex].Action); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /SudokuSolverWinForms/MainWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.SudokuSolver.UI 2 | { 3 | internal sealed partial class MainWindow 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer _components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (_components != null)) 17 | { 18 | _components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainWindow)); 32 | this._splitContainer1 = new System.Windows.Forms.SplitContainer(); 33 | this._sudokuBoard = new Kermalis.SudokuSolver.UI.SudokuBoard(); 34 | this._logList = new System.Windows.Forms.ListBox(); 35 | this._solveButton = new System.Windows.Forms.Button(); 36 | this._menuStrip1 = new System.Windows.Forms.MenuStrip(); 37 | this._fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 38 | this._newToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 39 | this._openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 40 | this._saveAsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 41 | this._exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 42 | this._statusStrip1 = new System.Windows.Forms.StatusStrip(); 43 | this._puzzleLabel = new System.Windows.Forms.ToolStripStatusLabel(); 44 | this._statusLabel = new System.Windows.Forms.ToolStripStatusLabel(); 45 | ((System.ComponentModel.ISupportInitialize)(this._splitContainer1)).BeginInit(); 46 | this._splitContainer1.Panel1.SuspendLayout(); 47 | this._splitContainer1.Panel2.SuspendLayout(); 48 | this._splitContainer1.SuspendLayout(); 49 | this._menuStrip1.SuspendLayout(); 50 | this._statusStrip1.SuspendLayout(); 51 | this.SuspendLayout(); 52 | // 53 | // splitContainer1 54 | // 55 | this._splitContainer1.IsSplitterFixed = true; 56 | this._splitContainer1.Location = new System.Drawing.Point(35, 53); 57 | this._splitContainer1.Name = "splitContainer1"; 58 | // 59 | // splitContainer1.Panel1 60 | // 61 | this._splitContainer1.Panel1.Controls.Add(this._sudokuBoard); 62 | // 63 | // splitContainer1.Panel2 64 | // 65 | this._splitContainer1.Panel2.Controls.Add(this._logList); 66 | this._splitContainer1.Size = new System.Drawing.Size(941, 470); 67 | this._splitContainer1.SplitterDistance = 470; 68 | this._splitContainer1.SplitterWidth = 1; 69 | this._splitContainer1.TabIndex = 2; 70 | // 71 | // sudokuBoard 72 | // 73 | this._sudokuBoard.Cursor = System.Windows.Forms.Cursors.Hand; 74 | this._sudokuBoard.Dock = System.Windows.Forms.DockStyle.Fill; 75 | this._sudokuBoard.Font = new System.Drawing.Font("Leelawadee", 20.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 76 | this._sudokuBoard.Location = new System.Drawing.Point(0, 0); 77 | this._sudokuBoard.Margin = new System.Windows.Forms.Padding(0); 78 | this._sudokuBoard.Name = "sudokuBoard"; 79 | this._sudokuBoard.Size = new System.Drawing.Size(470, 470); 80 | this._sudokuBoard.TabIndex = 0; 81 | // 82 | // logList 83 | // 84 | this._logList.Dock = System.Windows.Forms.DockStyle.Bottom; 85 | this._logList.Font = new System.Drawing.Font("Meiryo", 8.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 86 | this._logList.FormattingEnabled = true; 87 | this._logList.HorizontalScrollbar = true; 88 | this._logList.ItemHeight = 17; 89 | this._logList.Location = new System.Drawing.Point(0, 7); 90 | this._logList.Name = "logList"; 91 | this._logList.Size = new System.Drawing.Size(470, 463); 92 | this._logList.TabIndex = 0; 93 | // 94 | // solveButton 95 | // 96 | this._solveButton.Enabled = false; 97 | this._solveButton.Location = new System.Drawing.Point(448, 27); 98 | this._solveButton.Name = "solveButton"; 99 | this._solveButton.Size = new System.Drawing.Size(75, 23); 100 | this._solveButton.TabIndex = 1; 101 | this._solveButton.Text = "Solve"; 102 | this._solveButton.UseVisualStyleBackColor = true; 103 | this._solveButton.Click += new System.EventHandler(this.SolvePuzzle); 104 | // 105 | // menuStrip1 106 | // 107 | this._menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 108 | this._fileToolStripMenuItem}); 109 | this._menuStrip1.Location = new System.Drawing.Point(0, 0); 110 | this._menuStrip1.Name = "menuStrip1"; 111 | this._menuStrip1.Size = new System.Drawing.Size(1011, 24); 112 | this._menuStrip1.TabIndex = 3; 113 | this._menuStrip1.Text = "menuStrip1"; 114 | // 115 | // fileToolStripMenuItem 116 | // 117 | this._fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 118 | this._newToolStripMenuItem, 119 | this._openToolStripMenuItem, 120 | this._saveAsToolStripMenuItem, 121 | this._exitToolStripMenuItem}); 122 | this._fileToolStripMenuItem.Name = "fileToolStripMenuItem"; 123 | this._fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); 124 | this._fileToolStripMenuItem.Text = "File"; 125 | // 126 | // newToolStripMenuItem 127 | // 128 | this._newToolStripMenuItem.Name = "newToolStripMenuItem"; 129 | this._newToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.N))); 130 | this._newToolStripMenuItem.Size = new System.Drawing.Size(154, 22); 131 | this._newToolStripMenuItem.Text = "New"; 132 | this._newToolStripMenuItem.Click += new System.EventHandler(this.NewPuzzle); 133 | // 134 | // openToolStripMenuItem 135 | // 136 | this._openToolStripMenuItem.Name = "openToolStripMenuItem"; 137 | this._openToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); 138 | this._openToolStripMenuItem.Size = new System.Drawing.Size(154, 22); 139 | this._openToolStripMenuItem.Text = "Open"; 140 | this._openToolStripMenuItem.Click += new System.EventHandler(this.OpenPuzzle); 141 | // 142 | // saveAsToolStripMenuItem 143 | // 144 | this._saveAsToolStripMenuItem.Enabled = false; 145 | this._saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem"; 146 | this._saveAsToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.S))); 147 | this._saveAsToolStripMenuItem.Size = new System.Drawing.Size(154, 22); 148 | this._saveAsToolStripMenuItem.Text = "Save As"; 149 | this._saveAsToolStripMenuItem.Click += new System.EventHandler(this.SavePuzzle); 150 | // 151 | // exitToolStripMenuItem 152 | // 153 | this._exitToolStripMenuItem.Name = "exitToolStripMenuItem"; 154 | this._exitToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.W))); 155 | this._exitToolStripMenuItem.Size = new System.Drawing.Size(154, 22); 156 | this._exitToolStripMenuItem.Text = "Exit"; 157 | this._exitToolStripMenuItem.Click += new System.EventHandler(this.Exit); 158 | // 159 | // statusStrip1 160 | // 161 | this._statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 162 | this._puzzleLabel, 163 | this._statusLabel}); 164 | this._statusStrip1.Location = new System.Drawing.Point(0, 552); 165 | this._statusStrip1.Name = "statusStrip1"; 166 | this._statusStrip1.Size = new System.Drawing.Size(1011, 22); 167 | this._statusStrip1.TabIndex = 0; 168 | this._statusStrip1.Text = "statusStrip1"; 169 | // 170 | // puzzleLabel 171 | // 172 | this._puzzleLabel.Name = "puzzleLabel"; 173 | this._puzzleLabel.Size = new System.Drawing.Size(68, 17); 174 | this._puzzleLabel.Text = "puzzleLabel"; 175 | // 176 | // statusLabel 177 | // 178 | this._statusLabel.Name = "statusLabel"; 179 | this._statusLabel.Size = new System.Drawing.Size(66, 17); 180 | this._statusLabel.Text = "statusLabel"; 181 | // 182 | // MainWindow 183 | // 184 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 185 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 186 | this.ClientSize = new System.Drawing.Size(1011, 574); 187 | this.Controls.Add(this._statusStrip1); 188 | this.Controls.Add(this._solveButton); 189 | this.Controls.Add(this._splitContainer1); 190 | this.Controls.Add(this._menuStrip1); 191 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 192 | this.MainMenuStrip = this._menuStrip1; 193 | this.Name = "MainWindow"; 194 | this.Text = "Sudoku Solver"; 195 | this._splitContainer1.Panel1.ResumeLayout(false); 196 | this._splitContainer1.Panel2.ResumeLayout(false); 197 | ((System.ComponentModel.ISupportInitialize)(this._splitContainer1)).EndInit(); 198 | this._splitContainer1.ResumeLayout(false); 199 | this._menuStrip1.ResumeLayout(false); 200 | this._menuStrip1.PerformLayout(); 201 | this._statusStrip1.ResumeLayout(false); 202 | this._statusStrip1.PerformLayout(); 203 | this.ResumeLayout(false); 204 | this.PerformLayout(); 205 | 206 | } 207 | 208 | #endregion 209 | 210 | private System.Windows.Forms.SplitContainer _splitContainer1; 211 | private SudokuSolver.UI.SudokuBoard _sudokuBoard; 212 | private System.Windows.Forms.Button _solveButton; 213 | private System.Windows.Forms.MenuStrip _menuStrip1; 214 | private System.Windows.Forms.ToolStripMenuItem _fileToolStripMenuItem; 215 | private System.Windows.Forms.ToolStripMenuItem _openToolStripMenuItem; 216 | private System.Windows.Forms.StatusStrip _statusStrip1; 217 | private System.Windows.Forms.ToolStripStatusLabel _statusLabel; 218 | private System.Windows.Forms.ListBox _logList; 219 | private System.Windows.Forms.ToolStripStatusLabel _puzzleLabel; 220 | private System.Windows.Forms.ToolStripMenuItem _newToolStripMenuItem; 221 | private System.Windows.Forms.ToolStripMenuItem _saveAsToolStripMenuItem; 222 | private System.Windows.Forms.ToolStripMenuItem _exitToolStripMenuItem; 223 | } 224 | } 225 | 226 | -------------------------------------------------------------------------------- /SudokuSolverWinForms/MainWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Windows.Forms; 5 | 6 | namespace Kermalis.SudokuSolver.UI; 7 | 8 | internal sealed partial class MainWindow : Form // TODO: Alloc, fix custom puzzle crash 9 | { 10 | private Solver? _solver; 11 | 12 | public MainWindow() 13 | { 14 | InitializeComponent(); 15 | 16 | _puzzleLabel.Text = string.Empty; 17 | _statusLabel.Text = string.Empty; 18 | _logList.SelectedIndexChanged += LogList_SelectedIndexChanged; 19 | _sudokuBoard.CellChanged += (cell) => ChangeState(true, true); 20 | } 21 | 22 | private void LogList_SelectedIndexChanged(object? sender, EventArgs e) 23 | { 24 | _sudokuBoard.ReDraw(true, _logList.SelectedIndex); 25 | } 26 | 27 | private void ChangeState(bool solveButtonState, bool saveState) 28 | { 29 | _solveButton.Enabled = solveButtonState; 30 | _saveAsToolStripMenuItem.Enabled = saveState; 31 | } 32 | 33 | private void ChangePuzzle(Solver newSolver, string puzzleName, bool solveButtonState) 34 | { 35 | _solver = newSolver; 36 | ChangeState(solveButtonState, false); 37 | _puzzleLabel.Text = puzzleName + " Puzzle"; 38 | _statusLabel.Text = string.Empty; 39 | _logList.DataSource = _solver.Actions; 40 | _sudokuBoard.SetSolver(_solver); 41 | } 42 | 43 | private void NewPuzzle(object? sender, EventArgs e) 44 | { 45 | ChangePuzzle(Solver.CreateCustomPuzzle(), "Custom", false); 46 | MessageBox.Show("A custom puzzle has been created. Click cells to type in values.", Text); 47 | } 48 | 49 | private void OpenPuzzle(object? sender, EventArgs e) 50 | { 51 | var d = new OpenFileDialog 52 | { 53 | Title = "Open Sudoku Puzzle", 54 | Filter = "TXT files|*.txt", 55 | InitialDirectory = Path.GetFullPath(Directory.GetCurrentDirectory() + @"\..\Puzzles") 56 | }; 57 | 58 | if (d.ShowDialog() == DialogResult.OK) 59 | { 60 | Puzzle puzzle; 61 | try 62 | { 63 | puzzle = Puzzle.Parse(File.ReadAllLines(d.FileName)); 64 | } 65 | catch (InvalidDataException) 66 | { 67 | MessageBox.Show("Invalid puzzle data."); 68 | return; 69 | } 70 | catch 71 | { 72 | MessageBox.Show("Error loading puzzle."); 73 | return; 74 | } 75 | 76 | ChangePuzzle(new Solver(puzzle), Path.GetFileNameWithoutExtension(d.FileName), true); 77 | } 78 | } 79 | 80 | private void SavePuzzle(object? sender, EventArgs e) 81 | { 82 | var d = new SaveFileDialog 83 | { 84 | Title = "Save Sudoku Puzzle", 85 | Filter = "TXT files|*.txt", 86 | InitialDirectory = Path.GetFullPath(Directory.GetCurrentDirectory() + @"\..\Puzzles") 87 | }; 88 | 89 | if (d.ShowDialog() == DialogResult.OK) 90 | { 91 | File.WriteAllText(d.FileName, _solver!.Puzzle.ToString()); 92 | MessageBox.Show("Puzzle saved.", Text); 93 | } 94 | } 95 | 96 | private void SolvePuzzle(object? sender, EventArgs e) 97 | { 98 | ChangeState(false, _saveAsToolStripMenuItem.Enabled); 99 | // Clear solver's guesses on a custom puzzle 100 | if (_solver!.Puzzle.IsCustom) 101 | { 102 | _solver.Puzzle.Reset(); 103 | } 104 | 105 | var sw = new Stopwatch(); 106 | sw.Start(); 107 | _solver.TrySolve(); 108 | sw.Stop(); 109 | _statusLabel.Text = string.Format("Solver finished in {0} seconds.", sw.Elapsed.TotalSeconds); 110 | _logList.SelectedIndex = _solver.Actions.Count - 1; 111 | _logList.Select(); 112 | } 113 | 114 | private void Exit(object? sender, EventArgs e) 115 | { 116 | Close(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /SudokuSolverWinForms/MainWindow.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | 124 | 132, 17 125 | 126 | 127 | 128 | 129 | AAABAAUAEBAAAAEAIABoBAAAVgAAABgYAAABACAAiAkAAL4EAAAgIAAAAQAgAKgQAABGDgAAMDAAAAEA 130 | IACoJQAA7h4AADIyEAABAAQAcAcAAJZEAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAADCDgAAwg4AAAAA 131 | AAAAAAAAjoqF/8C7tf++ubP/vrmz/765s/++ubP/vrmz/765s/++ubP/vrmz/765s/++ubP/vrmz/765 132 | s//Au7X/joqF/767tf///fX///vz///78///+/P///vz///78///+/P///vz///78///+/P///vz///7 133 | 8///+/P///31/767tf++ubP///vz///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 134 | 8P//+PD///jw///78/++ubP/vrmz///78///+PD///jw///48P//+PD///jw///58f//+/P///jx///4 135 | 8P//+PD///jw///48P//+/P/vrmz/765s///+/P///jw///48P//+PD///jw///58f/i4d//z8S9//zy 136 | 5///+PH///jw///48P//+PD///vz/765s/++ubP///vz///48P//+PD///jw///48P///PT/jJuu/0Mp 137 | KP/z4c3///ny///48P//+PD///jw///78/++ubP/vrmz///78///+PD///jw///48P//+PD///z0/4mY 138 | rP89IiL/8uDM///68v//+PD///jw///48P//+/P/vrmz/765s///+/P///jw///48P//+PD///jw///9 139 | 9f+Jmaz/PSIi//LgzP//+vL///jw///48P//+PD///vz/765s/++ubP///vz///48P//+PD///jw//74 140 | 8P/w6+T/hZOk/z0iIv/y4Mz///ry///48P//+PD///jw///78/++ubP/vrmz///78///+PD///jw///4 141 | 8P/6+PL/foeU/yguMv8+Ix7/8uDM///68v//+PD///jw///48P//+/P/vrmz/765s///+/P///jw///4 142 | 8P//+PD///nx/9/g3v96ho3/ZlBM//Tk0v//+fL///jw///48P//+PD///vz/765s/++ubP///vz///4 143 | 8P//+PD///jw///48P//+vH//Pny//Xt5v/+9u7///jw///48P//+PD///jw///78/++ubP/vrmz///7 144 | 8///+PD///jw///48P//+PD///jw///48P//+fH///jw///48P//+PD///jw///48P//+/P/vrmz/765 145 | s///+/P///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///vz/765 146 | s/++u7X///31///78///+/P///vz///78///+/P///vz///78///+/P///vz///78///+/P///vz///9 147 | 9f++u7X/joqF/8C7tf++ubP/vrmz/765s/++ubP/vrmz/765s/++ubP/vrmz/765s/++ubP/vrmz/765 148 | s//Au7X/joqF/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 149 | AAAAAAAAAAAAAAAAAAAoAAAAGAAAADAAAAABACAAAAAAAAAJAADCDgAAwg4AAAAAAAAAAAAAV1RR/5aS 150 | jf+VkIz/lZCM/5WQjP+VkIz/lZCM/5WQjP+VkIz/lZCM/5WQjP+VkIz/lZCM/5WQjP+VkIz/lZCM/5WQ 151 | jP+VkIz/lZCM/5WQjP+VkIz/lZCM/5aSjf9XVFH/lZKN///+9v//+/P///vz///78///+/P///vz///7 152 | 8///+/P///vz///78///+/P///vz///78///+/P///vz///78///+/P///vz///78///+/P///vz///+ 153 | 9v+Vko3/lZCM///78///+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 154 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///78/+VkIz/lZCM///78///+PD///jw///4 155 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 156 | 8P//+PD///jw///78/+VkIz/lZCM///78///+PD///jw///48P//+PD///jw///48P//+PD///jw///4 157 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///78/+VkIz/lZCM///7 158 | 8///+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 159 | 8P//+PD///jw///48P//+PD///jw///78/+VkIz/lZCM///78///+PD///jw///48P//+PD///jw///4 160 | 8P//+PD///jw///48P/79e7/+fLq//317f//+PD///jw///48P//+PD///jw///48P//+PD///jw///7 161 | 8/+VkIz/lZCM///78///+PD///jw///48P//+PD///jw///48P//+PD///jw//v58/+ElKv/RDU6/8ik 162 | jP///PD///jw///48P//+PD///jw///48P//+PD///jw///78/+VkIz/lZCM///78///+PD///jw///4 163 | 8P//+PD///jw///48P//+PD///jw//n59P9cdJT/CQAA/7aKbf///e////jw///48P//+PD///jw///4 164 | 8P//+PD///jw///78/+VkIz/lZCM///78///+PD///jw///48P//+PD///jw///48P//+PD///jw//n5 165 | 9P9ddZX/CQAB/7eKbf///e////jw///48P//+PD///jw///48P//+PD///jw///78/+VkIz/lZCM///7 166 | 8///+PD///jw///48P//+PD///jw///48P//+PD///jw//n59P9ddZX/CQAB/7eKbf///e////jw///4 167 | 8P//+PD///jw///48P//+PD///jw///78/+VkIz/lZCM///78///+PD///jw///48P//+PD///jw///4 168 | 8P//+PD///jw//n59P9ddZX/CQAB/7eKbf///e////jw///48P//+PD///jw///48P//+PD///jw///7 169 | 8/+VkIz/lZCM///78///+PD///jw///48P//+PD///jw///48P//+PD///rx//n99/9ddpf/CQAB/7eK 170 | bf///e////jw///48P//+PD///jw///48P//+PD///jw///78/+VkIz/lZCM///78///+PD///jw///4 171 | 8P//+PD///jw///48P/++fL/sbjE/7GlmP9UaIL/CgAC/7eKbf///e////jw///48P//+PD///jw///4 172 | 8P//+PD///jw///78/+VkIz/lZCM///78///+PD///jw///48P//+PD///jw///48P/9+vP/gZew/w8S 173 | G/8NEA//DQAA/7eKbf///e////jw///48P//+PD///jw///48P//+PD///jw///78/+VkIz/lZCM///7 174 | 8///+PD///jw///48P//+PD///jw///48P//+PD/7+/r/6Kttf8zSlf/DwAF/7eJbf///e////jw///4 175 | 8P//+PD///jw///48P//+PD///jw///78/+VkIz/lZCM///78///+PD///jw///48P//+PD///jw///4 176 | 8P//+PD///nw///88//j6Oj/sqqt/+bSw///+vD///jw///48P//+PD///jw///48P//+PD///jw///7 177 | 8/+VkIz/lZCM///78///+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+fH///z0///6 178 | 8///+PD///jw///48P//+PD///jw///48P//+PD///jw///78/+VkIz/lZCM///78///+PD///jw///4 179 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 180 | 8P//+PD///jw///78/+VkIz/lZCM///78///+PD///jw///48P//+PD///jw///48P//+PD///jw///4 181 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///78/+VkIz/lZCM///7 182 | 8///+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 183 | 8P//+PD///jw///48P//+PD///jw///78/+VkIz/lZCM///78///+PD///jw///48P//+PD///jw///4 184 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///7 185 | 8/+VkIz/lZKN///+9v//+/P///vz///78///+/P///vz///78///+/P///vz///78///+/P///vz///7 186 | 8///+/P///vz///78///+/P///vz///78///+/P///vz///+9v+Vko3/V1RR/5aSjf+VkIz/lZCM/5WQ 187 | jP+VkIz/lZCM/5WQjP+VkIz/lZCM/5WQjP+VkIz/lZCM/5WQjP+VkIz/lZCM/5WQjP+VkIz/lZCM/5WQ 188 | jP+VkIz/lZCM/5aSjf9XVFH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 189 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAACAA 190 | AABAAAAAAQAgAAAAAAAAEAAAwg4AAMIOAAAAAAAAAAAAACsqKP9pZmP/aWZi/2lmYv9pZmL/aWZi/2lm 191 | Yv9pZmL/aWZi/2lmYv9pZmL/aWZi/2lmYv9pZmL/aWZi/2lmYv9pZmL/aWZi/2lmYv9pZmL/aWZi/2lm 192 | Yv9pZmL/aWZi/2lmYv9pZmL/aWZi/2lmYv9pZmL/aWZi/2lmY/8rKij/aWZj///78///+vH///rx///6 193 | 8f//+vH///rx///68f//+vH///rx///68f//+vH///rx///68f//+vH///rx///68f//+vH///rx///6 194 | 8f//+vH///rx///68f//+vH///rx///68f//+vH///rx///68f//+vH///vz/2lmY/9pZmL///rx///4 195 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 196 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+vH/aWZi/2lm 197 | Yv//+vH///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 198 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///6 199 | 8f9pZmL/aWZi///68f//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 200 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 201 | 8P//+PD///rx/2lmYv9pZmL///rx///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 202 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 203 | 8P//+PD///jw///48P//+vH/aWZi/2lmYv//+vH///jw///48P//+PD///jw///48P//+PD///jw///4 204 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 205 | 8P//+PD///jw///48P//+PD///jw///68f9pZmL/aWZi///68f//+PD///jw///48P//+PD///jw///4 206 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 207 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///rx/2lmYv9pZmL///rx///48P//+PD///jw///4 208 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P///PP///72///99f//+PH///jw///4 209 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+vH/aWZi/2lmYv//+vH///jw///4 210 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9PXx/6+0wf+VkI//waKY//3y 211 | 3v//+PH///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///68f9pZmL/aWZi///6 212 | 8f//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///58P/j8fL/OVB7/wAA 213 | AP9lJBn/+urE///48f//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///rx/2lm 214 | Yv9pZmL///rx///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///nw/+Px 215 | 8v86UH3/AAAB/2YkGf/66sX///jx///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 216 | 8P//+vH/aWZi/2lmYv//+vH///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 217 | 8P//+fD/4/Hy/zpQff8AAAH/ZiQZ//rqxf//+PH///jw///48P//+PD///jw///48P//+PD///jw///4 218 | 8P//+PD///jw///68f9pZmL/aWZi///68f//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 219 | 8P//+PD///jw///58P/j8fL/OlB9/wAAAf9mJBn/+urF///48f//+PD///jw///48P//+PD///jw///4 220 | 8P//+PD///jw///48P//+PD///rx/2lmYv9pZmL///rx///48P//+PD///jw///48P//+PD///jw///4 221 | 8P//+PD///jw///48P//+PD///nw/+Px8v86UH3/AAAB/2YkGf/66sX///jx///48P//+PD///jw///4 222 | 8P//+PD///jw///48P//+PD///jw///48P//+vH/aWZi/2lmYv//+vH///jw///48P//+PD///jw///4 223 | 8P//+PD///jw///48P//+PD///jw///48P//+fD/4/Hy/zpQff8AAAH/ZiQZ//rqxf//+PH///jw///4 224 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///68f9pZmL/aWZi///68f//+PD///jw///4 225 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///nw///78v/j8fP/OlB8/wAAAf9mJBn/+urF///4 226 | 8f//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///rx/2lmYv9pZmL///rx///4 227 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P/u7er/693T/+Py7/86Un//AAAB/2Yk 228 | Gf/66sX///jx///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+vH/aWZi/2lm 229 | Yv//+vH///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///vz/4+vyv88Ji//g4Nr/yw5 230 | Vv8AAAP/ZiQZ//rqxf//+PH///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///6 231 | 8f9pZmL/aWZi///68f//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+vL/orzR/xQd 232 | Pf8EBAL/BgQI/wAAAP9mJBn/+urF///48f//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 233 | 8P//+PD///rx/2lmYv9pZmL///rx///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 234 | 8P/7+PD/tMTQ/11wfv8FGyf/AAAA/2QkGf/56sT///jx///48P//+PD///jw///48P//+PD///jw///4 235 | 8P//+PD///jw///48P//+vH/aWZi/2lmYv//+vH///jw///48P//+PD///jw///48P//+PD///jw///4 236 | 8P//+PD///jw///48P//+/L/8/by/6e+y/9KS1//jlxR//vu0P//+PH///jw///48P//+PD///jw///4 237 | 8P//+PD///jw///48P//+PD///jw///68f9pZmL/aWZi///68f//+PD///jw///48P//+PD///jw///4 238 | 8P//+PD///jw///48P//+PD///jw///48P//+fD///ry//738P/+9+////jw///48P//+PD///jw///4 239 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///rx/2lmYv9pZmL///rx///48P//+PD///jw///4 240 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 241 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+vH/aWZi/2lmYv//+vH///jw///4 242 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 243 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///68f9pZmL/aWZi///6 244 | 8f//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 245 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///rx/2lm 246 | Yv9pZmL///rx///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 247 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 248 | 8P//+vH/aWZi/2lmYv//+vH///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 249 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 250 | 8P//+PD///jw///68f9pZmL/aWZi///68f//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 251 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 252 | 8P//+PD///jw///48P//+PD///rx/2lmYv9pZmL///rx///48P//+PD///jw///48P//+PD///jw///4 253 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 254 | 8P//+PD///jw///48P//+PD///jw///48P//+vH/aWZi/2lmY///+/P///rx///68f//+vH///rx///6 255 | 8f//+vH///rx///68f//+vH///rx///68f//+vH///rx///68f//+vH///rx///68f//+vH///rx///6 256 | 8f//+vH///rx///68f//+vH///rx///68f//+vH///rx///78/9pZmP/Kyoo/2lmY/9pZmL/aWZi/2lm 257 | Yv9pZmL/aWZi/2lmYv9pZmL/aWZi/2lmYv9pZmL/aWZi/2lmYv9pZmL/aWZi/2lmYv9pZmL/aWZi/2lm 258 | Yv9pZmL/aWZi/2lmYv9pZmL/aWZi/2lmYv9pZmL/aWZi/2lmYv9pZmL/aWZj/ysqKP8AAAAAAAAAAAAA 259 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 260 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgA 261 | AAAwAAAAYAAAAAEAIAAAAAAAACQAAMIOAADCDgAAAAAAAAAAAAACAgL/FhUV/xcWFv8XFhb/FxYW/xcW 262 | Fv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcW 263 | Fv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcW 264 | Fv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FhUV/wICAv8WFRX/6ePb//Tt 265 | 5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt 266 | 5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt 267 | 5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/6ePb/xYV 268 | Ff8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 269 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 270 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 271 | 8P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 272 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 273 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 274 | 8P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///4 275 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 276 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 277 | 8P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///4 278 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 279 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 280 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///4 281 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 282 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 283 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcW 284 | Fv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 285 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 286 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 287 | 8P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 288 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 289 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 290 | 8P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///4 291 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 292 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 293 | 8P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///4 294 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 295 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 296 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///4 297 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 298 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 299 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcW 300 | Fv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 301 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 302 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 303 | 8P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 304 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///nw////9v////j////4///+ 305 | 9///+PL///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 306 | 8P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///4 307 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P/99/D/xeLr/2xt 308 | jv9kYmH/bGFe/7R2bf/+9tD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 309 | 8P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///4 310 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 311 | 8P/89/D/ntLo/w0SSf8AAAT/CwAA/4EiGP/99br///jw///48P//+PD///jw///48P//+PD///jw///4 312 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///4 313 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 314 | 8P//+PD///jw///48P/89/D/oNPo/w0STf8AAAT/DAAA/4QiGP/99bz///jw///48P//+PD///jw///4 315 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcW 316 | Fv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 317 | 8P//+PD///jw///48P//+PD///jw///48P/89/D/oNPo/w0STf8AAAT/DAAA/4QiGP/99bz///jw///4 318 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 319 | 8P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 320 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P/89/D/oNPo/w0STf8AAAT/DAAA/4Qi 321 | GP/99bz///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 322 | 8P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///4 323 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P/89/D/oNPo/w0S 324 | Tf8AAAT/DAAA/4QiGP/99bz///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 325 | 8P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///4 326 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 327 | 8P/89/D/oNPo/w0STf8AAAT/DAAA/4QiGP/99bz///jw///48P//+PD///jw///48P//+PD///jw///4 328 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///4 329 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 330 | 8P//+PD///jw///48P/89/D/oNPo/w0STf8AAAT/DAAA/4QiGP/99bz///jw///48P//+PD///jw///4 331 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcW 332 | Fv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 333 | 8P//+PD///jw///48P//+PD///jw///48P/89/D/oNPo/w0STf8AAAT/DAAA/4QiGP/99bz///jw///4 334 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 335 | 8P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 336 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P/89/D/oNPo/w0STf8AAAT/DAAA/4Qi 337 | GP/99bz///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 338 | 8P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///4 339 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P/89/D/oNPo/w0S 340 | Tf8AAAT/DAAA/4QiGP/99bz///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 341 | 8P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///4 342 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///nx///5 343 | 8f/89/D/oNPo/w0STf8AAAT/DAAA/4QiGP/99bz///jw///48P//+PD///jw///48P//+PD///jw///4 344 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///4 345 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 346 | 8P/19fD/4Nzd/+/e0f/79ur/odTq/w0STv8AAAT/DAAA/4QiGP/99bz///jw///48P//+PD///jw///4 347 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcW 348 | Fv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 349 | 8P//+PD///jw///58P+24/L/IS5o/20yD//Iupj/kLvA/wwQS/8AAAT/DAAA/4QiGP/99bz///jw///4 350 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 351 | 8P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 352 | 8P//+PD///jw///48P//+PD///jw///58P+04vL/Hitk/wAACf8CAgD/MS8D/wUFLv8AAAP/DAAA/4Qi 353 | GP/99bz///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 354 | 8P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///4 355 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P/H5/H/TVyH/wcTLP8AAQT/AAAA/wAA 356 | AP8AAAD/DAAA/4QiGP/99bz///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 357 | 8P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///4 358 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/8Pvy/2KN 359 | uf9DSVD/CB8v/wACBP8AAAD/DAAA/4QiGP/99bz///jw///48P//+PD///jw///48P//+PD///jw///4 360 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///4 361 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 362 | 8P//+PD///jw///88v/6+/T/YqXK/y5GUv8DBBP/DAAA/4EiGP/99bv///jw///48P//+PD///jw///4 363 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcW 364 | Fv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 365 | 8P//+PD///jw///48P//+PD///jw///48P//+PD//Pvy/8Lj7/9gYYb/YldW/69uZP/+9s7///jw///4 366 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 367 | 8P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 368 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///58P////X////4///+ 369 | 9///+PL///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 370 | 8P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///4 371 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 372 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 373 | 8P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///4 374 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 375 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 376 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///4 377 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 378 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 379 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcW 380 | Fv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 381 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 382 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 383 | 8P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 384 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 385 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 386 | 8P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///4 387 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 388 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 389 | 8P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///4 390 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 391 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 392 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///4 393 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 394 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 395 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcW 396 | Fv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 397 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 398 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 399 | 8P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 400 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 401 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 402 | 8P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///48P//+PD///jw///4 403 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 404 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 405 | 8P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8XFhb/9O3l///48P//+PD///jw///4 406 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 407 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD///jw///4 408 | 8P//+PD///jw///48P//+PD///jw///48P//+PD///jw///48P//+PD/9O3l/xcWFv8WFRX/6ePb//Tt 409 | 5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt 410 | 5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt 411 | 5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/9O3l//Tt5f/07eX/6ePb/xYV 412 | Ff8CAgL/FhUV/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcW 413 | Fv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcW 414 | Fv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcWFv8XFhb/FxYW/xcW 415 | Fv8XFhb/FhUV/wICAv8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 416 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 417 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 418 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 419 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 420 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 421 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAMgAAAGQAAAABAAQAAAAAAHgF 422 | AADCDgAAwg4AABAAAAAQAAAAAAAAAP/48ACc2fAAAABDAHQAAAD/+LQAAHG0AOD48AAARZIASEUAAJxF 423 | AAD/+NMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABERERERERERER 424 | EREREREREREREREREREQAAAAAREREREREREREREREREREREREREREREREAAAAAERERERERERERERERER 425 | ERERERERERERERAAAAABEREREREREREREREREREREREREREREREQAAAAARERERERERERERERERERERER 426 | EREREREREAAAAAERERERERERERERERERERERERERERERERAAAAABERERERERERERERERERERERERERER 427 | EREQAAAAAREREREREREREREREREREREREREREREREAAAAAERERERERERERERERERERERERERERERERAA 428 | AAABEREREREREREREREREREREREREREREREQAAAAAREREREREREREREREREREREREREREREREAAAAAER 429 | ERERERERERERERERERERERERERERERAAAAABEREREREREREREREREREREREREREREREQAAAAARERERER 430 | EREREREREREREREREREREREREAAAAAEREREREREREREREjAEURERERERERERERAAAAABERERERERERER 431 | ERIwBFEREREREREREREQAAAAARERERERERERERESMARREREREREREREREAAAAAEREREREREREREREjAE 432 | URERERERERERERAAAAABERERERERERERERIwBFEREREREREREREQAAAAARERERERERERERESMARRERER 433 | EREREREREAAAAAEREREREREREREREjAEURERERERERERERAAAAABERERERERERERERIwBFERERERERER 434 | EREQAAAAARERERERERERERESMARREREREREREREREAAAAAEREREREREREREREjAEURERERERERERERAA 435 | AAABERERERERERERERIwBFEREREREREREREQAAAAARERERERERERERESMARREREREREREREREAAAAAER 436 | EREREREREREREjAEURERERERERERERAAAAABERERERERERESOrIwBFEREREREREREREQAAAAARERERER 437 | EREREjAJMARREREREREREREREAAAAAERERERERERERIwAAAEURERERERERERERAAAAABERERERERERER 438 | eAAABFEREREREREREREQAAAAAREREREREREREREWAARREREREREREREREAAAAAERERERERERERERESME 439 | URERERERERERERAAAAABEREREREREREREREREREREREREREREREQAAAAARERERERERERERERERERERER 440 | EREREREREAAAAAERERERERERERERERERERERERERERERERAAAAABERERERERERERERERERERERERERER 441 | EREQAAAAAREREREREREREREREREREREREREREREREAAAAAERERERERERERERERERERERERERERERERAA 442 | AAABEREREREREREREREREREREREREREREREQAAAAAREREREREREREREREREREREREREREREREAAAAAER 443 | ERERERERERERERERERERERERERERERAAAAABEREREREREREREREREREREREREREREREQAAAAARERERER 444 | EREREREREREREREREREREREREAAAAAERERERERERERERERERERERERERERERERAAAAABERERERERERER 445 | EREREREREREREREREREQAAAAAREREREREREREREREREREREREREREREREAAAAAERERERERERERERERER 446 | ERERERERERERERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 447 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 448 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 449 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 450 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 451 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 452 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 453 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== 454 | 455 | 456 | -------------------------------------------------------------------------------- /SudokuSolverWinForms/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace Kermalis.SudokuSolver.UI; 5 | 6 | internal static class Program 7 | { 8 | [STAThread] 9 | private static void Main() 10 | { 11 | Application.EnableVisualStyles(); 12 | Application.SetCompatibleTextRenderingDefault(false); 13 | Application.Run(new MainWindow()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SudokuSolverWinForms/Properties/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/SudokuSolver/5f30aaa6da883b6a2aad02a754736a19df057f9d/SudokuSolverWinForms/Properties/Icon.ico -------------------------------------------------------------------------------- /SudokuSolverWinForms/Properties/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/SudokuSolver/5f30aaa6da883b6a2aad02a754736a19df057f9d/SudokuSolverWinForms/Properties/Icon.png -------------------------------------------------------------------------------- /SudokuSolverWinForms/SudokuBoard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | 5 | namespace Kermalis.SudokuSolver.UI; 6 | 7 | internal sealed class SudokuBoard : UserControl 8 | { 9 | private const int SPACE_BEFORE_GRID = 20; 10 | private const int NO_SNAPSHOT = -1; 11 | 12 | private static readonly Brush _changedText = Brushes.DodgerBlue; 13 | private static readonly Brush _candidateText = Brushes.Crimson; 14 | private static readonly Brush _culpritChangedHighlight = Brushes.Plum; 15 | private static readonly Brush _culpritHighlight = Brushes.Pink; 16 | private static readonly Brush _semiCulpritChangedHighlight = Brushes.CornflowerBlue; 17 | private static readonly Brush _semiCulpritHighlight = Brushes.Aquamarine; 18 | private static readonly Brush _changedHighlight = Brushes.Cornsilk; 19 | 20 | public delegate void CellChangedEventHandler(Cell cell); 21 | public event CellChangedEventHandler? CellChanged; 22 | 23 | private Cell? _selectedCell; 24 | private Solver? _solver; 25 | private bool _showCandidates; 26 | private int _snapshotIndex; 27 | 28 | public SudokuBoard() 29 | { 30 | _snapshotIndex = NO_SNAPSHOT; 31 | 32 | SuspendLayout(); 33 | AutoScaleMode = AutoScaleMode.Font; 34 | DoubleBuffered = true; 35 | Name = "SudokuBoard"; 36 | Size = new Size(450 + SPACE_BEFORE_GRID, 450 + SPACE_BEFORE_GRID); 37 | Paint += SudokuBoard_Paint; 38 | MouseMove += SudokuBoard_MouseMove; 39 | MouseClick += SudokuBoard_Click; 40 | KeyPress += SudokuBoard_KeyPress; 41 | LostFocus += SudokuBoard_LostFocus; 42 | Resize += SudokuBoard_Resize; 43 | ResumeLayout(false); 44 | } 45 | 46 | private void SudokuBoard_Paint(object? sender, PaintEventArgs e) 47 | { 48 | Font f = Font; 49 | var fMini = new Font(f.FontFamily, f.Size / 1.75f); 50 | float rWidth = Width - SPACE_BEFORE_GRID; 51 | float rHeight = Height - SPACE_BEFORE_GRID; 52 | 53 | e.Graphics.DrawRectangle(Pens.Black, SPACE_BEFORE_GRID, SPACE_BEFORE_GRID, rWidth - 1, rHeight - 1); 54 | 55 | float w = rWidth / 3f; 56 | float h = rHeight / 3f; 57 | bool b = true; 58 | for (int x = 0; x < 3; x++) 59 | { 60 | for (int y = 0; y < 3; y++) 61 | { 62 | var rect = new Rectangle((int)(w * x + SPACE_BEFORE_GRID + 1), (int)(h * y + SPACE_BEFORE_GRID + 1), (int)(w - 2), (int)(h - 2)); 63 | e.Graphics.FillRectangle((b = !b) ? Brushes.AliceBlue : Brushes.GhostWhite, rect); 64 | e.Graphics.DrawRectangle(Pens.Black, rect); 65 | } 66 | } 67 | 68 | w = rWidth / 9f; 69 | h = rHeight / 9f; 70 | for (int x = 0; x < 9; x++) 71 | { 72 | float xoff = w * x; 73 | e.Graphics.DrawString(SPoint.ColumnLetter(x), fMini, Brushes.Black, xoff + w / 1.3f, 0); 74 | e.Graphics.DrawString(SPoint.RowLetter(x), fMini, Brushes.Black, 0, h * x + h / 1.4f); 75 | for (int y = 0; y < 9; y++) 76 | { 77 | float yoff = h * y; 78 | e.Graphics.DrawRectangle(Pens.Black, xoff + SPACE_BEFORE_GRID, yoff + SPACE_BEFORE_GRID, w, h); 79 | if (_solver is null) 80 | { 81 | continue; 82 | } 83 | 84 | Cell cell = _solver.Puzzle[x, y]; 85 | int val; 86 | Candidates candidates; 87 | 88 | if (_snapshotIndex == NO_SNAPSHOT || _snapshotIndex >= _solver.Actions.Count) 89 | { 90 | val = cell.Value; 91 | candidates = cell.Candidates; 92 | } 93 | else 94 | { 95 | CellSnapshot s = GetCellSnapshot(_solver, _snapshotIndex, cell); 96 | val = s.Value; 97 | candidates = s.Candidates; 98 | int xxoff = x % 3 == 0 ? 1 : 0, yyoff = y % 3 == 0 ? 1 : 0; // MATH 99 | int exoff = x % 3 == 2 ? 1 : 0, eyoff = y % 3 == 2 ? 1 : 0; 100 | var rect = new RectangleF(xoff + SPACE_BEFORE_GRID + 1 + xxoff, yoff + SPACE_BEFORE_GRID + 1 + yyoff, w - 1 - xxoff - exoff, h - 1 - yyoff - eyoff); 101 | bool changed = _snapshotIndex - 1 >= 0 && !candidates.SetEquals(GetCellSnapshot(_solver, _snapshotIndex - 1, cell).Candidates); 102 | Brush? brush = null; 103 | if (changed) 104 | { 105 | if (s.IsCulprit) 106 | { 107 | brush = _culpritChangedHighlight; 108 | } 109 | else if (s.IsSemiCulprit) 110 | { 111 | brush = _semiCulpritChangedHighlight; 112 | } 113 | else 114 | { 115 | brush = _changedHighlight; 116 | } 117 | } 118 | else if (s.IsCulprit) 119 | { 120 | brush = _culpritHighlight; 121 | } 122 | else if (s.IsSemiCulprit) 123 | { 124 | brush = _semiCulpritHighlight; 125 | } 126 | if (brush is not null) 127 | { 128 | e.Graphics.FillRectangle(brush, rect); 129 | } 130 | } 131 | 132 | var point = new PointF(xoff + f.Size / 1.5f + SPACE_BEFORE_GRID, yoff + f.Size / 2.25f + SPACE_BEFORE_GRID); 133 | if (_selectedCell is not null && _selectedCell.Point.Equals(x, y)) 134 | { 135 | e.Graphics.DrawString("_", f, Brushes.Crimson, point); 136 | } 137 | if (val != Cell.EMPTY_VALUE) 138 | { 139 | e.Graphics.DrawString(val.ToString(), f, val == cell.OriginalValue ? Brushes.Black : _changedText, point); 140 | } 141 | else if (_showCandidates) 142 | { 143 | for (int c = 1; c <= 9; c++) 144 | { 145 | if (!candidates.IsCandidate(c)) 146 | { 147 | continue; 148 | } 149 | 150 | float stringX = xoff + fMini.Size / 4 + ((c - 1) % 3 * (w / 3)) + SPACE_BEFORE_GRID; 151 | float stringY = yoff + ((c - 1) / 3 * (h / 3)) + SPACE_BEFORE_GRID; 152 | e.Graphics.DrawString(c.ToString(), fMini, _candidateText, stringX, stringY); 153 | } 154 | } 155 | } 156 | } 157 | } 158 | 159 | private static CellSnapshot GetCellSnapshot(Solver solver, int snapshotIdx, Cell cell) 160 | { 161 | return solver.Actions[snapshotIdx][cell.Point.Column, cell.Point.Row]; 162 | } 163 | 164 | private Cell? GetCellFromMouseLocation(Point location) 165 | { 166 | // TODO: There is a crash on the right side of the board. Should have a better solution 167 | if (_solver is null || !_solver.Puzzle.IsCustom || location.X < SPACE_BEFORE_GRID || location.Y < SPACE_BEFORE_GRID) 168 | { 169 | return null; 170 | } 171 | 172 | int col = (location.X - SPACE_BEFORE_GRID) / ((Width - SPACE_BEFORE_GRID) / 9); 173 | int row = (location.Y - SPACE_BEFORE_GRID) / ((Height - SPACE_BEFORE_GRID) / 9); 174 | return _solver.Puzzle[col, row]; 175 | } 176 | 177 | private void SudokuBoard_MouseMove(object? sender, MouseEventArgs e) 178 | { 179 | Cursor = GetCellFromMouseLocation(e.Location) is null ? Cursors.Default : Cursors.Hand; 180 | } 181 | 182 | private void SudokuBoard_Click(object? sender, MouseEventArgs e) 183 | { 184 | _selectedCell = GetCellFromMouseLocation(e.Location); 185 | ReDraw(false); 186 | } 187 | 188 | private void SudokuBoard_KeyPress(object? sender, KeyPressEventArgs e) 189 | { 190 | if (_selectedCell is null) 191 | { 192 | return; 193 | } 194 | 195 | if ((e.KeyChar == '0' && _selectedCell.Value != Cell.EMPTY_VALUE) || (e.KeyChar > '0' && e.KeyChar <= '9')) 196 | { 197 | _solver!.SetOriginalCellValue(_selectedCell, e.KeyChar - '0'); 198 | CellChanged?.Invoke(_selectedCell); 199 | } 200 | 201 | _selectedCell = null; 202 | ReDraw(false); 203 | } 204 | private void SudokuBoard_LostFocus(object? sender, EventArgs e) 205 | { 206 | if (_selectedCell is not null) 207 | { 208 | _selectedCell = null; 209 | ReDraw(false); 210 | } 211 | } 212 | 213 | private void SudokuBoard_Resize(object? sender, EventArgs e) 214 | { 215 | ReDraw(_showCandidates); 216 | } 217 | public void ReDraw(bool showCandidates, int snapshot = NO_SNAPSHOT) 218 | { 219 | _showCandidates = showCandidates; 220 | _snapshotIndex = snapshot; 221 | Invalidate(); 222 | } 223 | 224 | public void SetSolver(Solver newSolver) 225 | { 226 | _solver = newSolver; 227 | ReDraw(false); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /SudokuSolverWinForms/SudokuSolverWinForms.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0-windows 5 | WinExe 6 | latest 7 | Kermalis.SudokuSolver.UI 8 | Kermalis.SudokuSolver.UI.Program 9 | enable 10 | true 11 | ..\Build 12 | 13 | Kermalis 14 | Kermalis 15 | SudokuSolver 16 | SudokuSolver 17 | Properties\Icon.ico 18 | False 19 | 20 | 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------