tiramisu-one/public/blog/2023/04/sudoku.html

236 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Writing a Sudoku Solver: Displaying the Grid</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<div class="container">
<h1 class="page-title">Writing a Sudoku Solver: Displaying the
Grid</h1>
<div class="date">Published 2023-04-10</div>
<p>I previously dabbled with writing a <a
href="https://gitlab.com/dgalbraith33/sudoku-solver">sudoku
solver</a> but got carried away early on by making crazy speed
improvements rather than actually improving the solving ability.
What I did enjoy about the project is making logical deductions
rather than the guess and backcheck method commonly employed.</p>
<p>I want to take another crack at doing this except this time focus
on the question “Given the current state what are any of the next
possible deductions” without focusing on speed. Eventually Id also
like to take a crack at solving other types of sudokus (Chess
Sudoku, Killer Sudoku, etc).</p>
<p>One of the issues I had last time as I was debugging was
displaying the current board state in a clean way so I could see
what had gone wrong. So this time around Im planning on writing a
quick HTML/javascript board state display to be able to visualize
the board. For now I dont intend it to be interactive, however Ill
likely add that in the future.</p>
<h2 id="creating-a-sudoku-grid">Creating a sudoku grid</h2>
<p>Im no front end dev so this may take some time. I literally just
want to create an outline for the puzzle with nothing in it.</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode html"><code class="sourceCode html"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">&lt;div</span> <span class="er">class</span><span class="ot">=</span><span class="st">&quot;container&quot;</span><span class="kw">&gt;</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">&lt;h1&gt;</span>Sudoku<span class="kw">&lt;/h1&gt;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">&lt;div</span> <span class="er">class</span><span class="ot">=</span><span class="st">&quot;puzzle&quot;</span><span class="kw">&gt;</span>nothing<span class="kw">&lt;/div&gt;</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="kw">&lt;/div&gt;</span></span></code></pre></div>
<div class="sourceCode" id="cb2"><pre
class="sourceCode css"><code class="sourceCode css"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="fu">.container</span> {</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">max-width</span>: <span class="dv">800</span><span class="dt">px</span><span class="op">;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">margin</span>: <span class="bu">auto</span><span class="op">;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="fu">.puzzle</span> {</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">border</span>: <span class="dv">1</span><span class="dt">px</span> <span class="dv">solid</span> <span class="cn">black</span><span class="op">;</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<figure>
<img src="images/sudoku-1.png" class="center-img"
alt="A box with nothing in it." />
<figcaption aria-hidden="true">A box with nothing in
it.</figcaption>
</figure>
<p>Im not joking when I say Im going to have to do baby steps
here.</p>
<p>Next Ill try to actually make a square grid. The best way to do
this is probably with flexbox or something but Im just gonna hard
code some widths and heights.</p>
<p>Ok lets make this thing a width divisible by 9 so we can divide
it into equal portions. I chose 630 quite honestly because it was
the first number below 800px that popped into my head divisible by
9.</p>
<p>Lets focus on the 9 main boxes in the grid now before worrying
about the cells. Ill make each box 210 pixels tall and wide. And
slap a big ole border on there. Lets make it 2px because we will
want a narrower one for the cells.</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode css"><code class="sourceCode css"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="fu">.puzzle</span> {</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">width</span>: <span class="dv">630</span><span class="dt">px</span><span class="op">;</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">height</span>: <span class="dv">630</span><span class="dt">px</span><span class="op">;</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="fu">.box</span> {</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">border</span>: <span class="dv">2</span><span class="dt">px</span> <span class="dv">solid</span> <span class="cn">black</span><span class="op">;</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">width</span>: <span class="dv">210</span><span class="dt">px</span><span class="op">;</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">height</span>: <span class="dv">210</span><span class="dt">px</span><span class="op">;</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p><img src="images/sudoku-2.png" class="center-img" /></p>
<p>Reload that and… right divs auto linebreak after them.</p>
<p>I think we can “float: left” these bad boys and…</p>
<p><img src="images/sudoku-3.png" class="center-img" /></p>
<p>Right, now Im pretty sure the boxes are 210 + 4 pixels wide
because the border isnt included. While Im tempted to just math my
way out of this I recall that you can specify the border-box sizing
to avoid this.</p>
<p>Now this works! Now the astute of you may have noticed that there
were 10 not 9 boxes in the screenshot with the 2 columns. That
became even more obvious in the full grid.</p>
<figure>
<img src="images/sudoku-4.png" class="center-img"
alt="Off by one errors…" />
<figcaption aria-hidden="true">Off by one errors…</figcaption>
</figure>
<p>Ok now we can just recreate all of this with the cells and should
be good to go right?</p>
<p>Nope! The internal size of the boxes are now only 206x206 because
of the border-box attribute. But I now realize I can just get rid of
the puzzle sizing all together and go back to regular sizing on the
boxes. This happens to work because 4 214px boxes wont fit in the
800px wide container. (Again just use flexbox).</p>
<p>Finally this works!</p>
<figure>
<img src="images/sudoku-5.png" class="center-img"
alt="We have a grid!" />
<figcaption aria-hidden="true">We have a grid!</figcaption>
</figure>
<h2 id="displaying-a-puzzle">Displaying a puzzle</h2>
<p>Next up is to actually get some numbers in this bad boy. Now this
is where I relent and use flexboxes because <a
href="https://stackoverflow.com/questions/2939914/how-do-i-vertically-align-text-in-a-div/13515693">this</a>
StackOverflow answer convinced me.</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode css"><code class="sourceCode css"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="fu">.cell</span> {</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> <span class="fu">...</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">font-size</span>: <span class="dv">40</span><span class="dt">px</span><span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">font-weight</span>: <span class="dv">bold</span><span class="op">;</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">display</span>: <span class="dv">flex</span><span class="op">;</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">align-items</span>: <span class="dv">center</span><span class="op">;</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">justify-content</span>: <span class="dv">center</span><span class="op">;</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p><img src="images/sudoku-6.png" class="center-img" /></p>
<h3 id="taking-the-puzzle-from-the-url">Taking the puzzle from the
URL</h3>
<p>Now to make it so I can get the page to display any puzzle I want
easily from the solver, Ill allow specifying it as a parameter in
the URL. For now in a row-major string of 81 characters using a
period to denote blank spaces.</p>
<p>Ill can just get all of the cells by class name and iterate over
them in the same order as the puzzle string and it will display
relatively easily.</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode js"><code class="sourceCode javascript"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="bu">window</span><span class="op">.</span><span class="at">onload</span> <span class="op">=</span> (<span class="bu">event</span>) <span class="kw">=&gt;</span> {</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">var</span> params <span class="op">=</span> <span class="kw">new</span> <span class="fu">URLSearchParams</span>(<span class="bu">window</span><span class="op">.</span><span class="at">location</span><span class="op">.</span><span class="at">search</span>)<span class="op">;</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">var</span> puzzle <span class="op">=</span> params<span class="op">.</span><span class="fu">get</span>(<span class="st">&quot;p&quot;</span>)<span class="op">;</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (puzzle <span class="op">===</span> <span class="kw">null</span>) {</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span><span class="op">;</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (puzzle<span class="op">.</span><span class="at">length</span> <span class="op">!=</span> <span class="dv">81</span>) {</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a> <span class="bu">console</span><span class="op">.</span><span class="fu">log</span>(<span class="st">&quot;Failure: puzzle url len is &quot;</span> <span class="op">+</span> puzzle<span class="op">.</span><span class="at">length</span>)<span class="op">;</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span><span class="op">;</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">var</span> cells <span class="op">=</span> <span class="bu">document</span><span class="op">.</span><span class="fu">getElementsByClassName</span>(<span class="st">&quot;cell&quot;</span>)<span class="op">;</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (cells<span class="op">.</span><span class="at">length</span> <span class="op">!=</span> <span class="dv">81</span>) {</span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a> <span class="bu">console</span><span class="op">.</span><span class="fu">log</span>(<span class="st">&quot;Failure: wrong number of cells: &quot;</span> <span class="op">+</span> cells<span class="op">.</span><span class="at">length</span>)<span class="op">;</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span><span class="op">;</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> (i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> <span class="dv">81</span><span class="op">;</span> i<span class="op">++</span>) {</span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (puzzle[i] <span class="op">!=</span> <span class="st">&#39;.&#39;</span>) {</span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a> cells[i]<span class="op">.</span><span class="at">innerText</span> <span class="op">=</span> puzzle[i]</span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>And hooray it works super well!</p>
<figure>
<img src="images/sudoku-7.png" class="center-img"
alt="This isnt quite right." />
<figcaption aria-hidden="true">This isnt quite right.</figcaption>
</figure>
<p>Oh wait… I didnt think about the fact that the elements in the
HTMLCollection from the document.getElementsByClassName call
wouldnt be in the row order of the puzzle (all of the cells in box
1 come first).</p>
<p>You can see the effect of this as there are 2 9s in column 2 of
the puzzle. Oops.</p>
<p>Im going to just do the old fashioned brute force way and give
each cell an id from 1-81 and insert those manually. Im sure there
is a better way but hey this works.</p>
<p>Then with a quick update to the code.</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode js"><code class="sourceCode javascript"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> (i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> <span class="dv">81</span><span class="op">;</span> i<span class="op">++</span>) {</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (puzzle[i] <span class="op">!=</span> <span class="st">&#39;.&#39;</span>) {</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a> <span class="bu">document</span><span class="op">.</span><span class="fu">getElementById</span>(i<span class="op">+</span><span class="dv">1</span>)<span class="op">.</span><span class="at">innerText</span> <span class="op">=</span> puzzle[i]</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>It works!</p>
<figure>
<img src="images/sudoku-8.png" class="center-img"
alt="We have a puzzle!" />
<figcaption aria-hidden="true">We have a puzzle!</figcaption>
</figure>
<p>Still some goofiness like the border on the outside being thinner
than the interiors but Im pretty happy with this for now.</p>
<h2 id="showing-pencil-marks">Showing pencil marks</h2>
<p>Now to really visualize the solvers state, well also need to
see which pencil marks it has. These could be styled nicely but for
now I just went with a span inside the cell div with the following
style.</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode css"><code class="sourceCode css"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="fu">.cell</span> <span class="op">&gt;</span> span {</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">font-size</span>: <span class="dv">10</span><span class="dt">px</span><span class="op">;</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">font-weight</span>: <span class="dv">normal</span><span class="op">;</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">text-align</span>: <span class="dv">center</span><span class="op">;</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">letter-spacing</span>: <span class="dv">6</span><span class="dt">px</span><span class="op">;</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">word-wrap</span>: anywhere<span class="op">;</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>The letter-spacing and wordwrap attributes let us just jam all of
the pencil marks in as a single string and let the browser break
them up into multiple lines for us.</p>
<p>This comes out quite nicely:</p>
<p><img src="images/sudoku-9.png" class="center-img" /></p>
<h3 id="reading-the-pencil-marks-from-the-url">Reading the pencil
marks from the url</h3>
<p>For this url trick we will add the pencil marks using a comma
separated array like so:</p>
<p>123,,,,456,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,</p>
<p>Using the string above we can use the following javascript code
to parse and insert them:</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode js"><code class="sourceCode javascript"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a> <span class="kw">var</span> marks_param <span class="op">=</span> params<span class="op">.</span><span class="fu">get</span>(<span class="st">&quot;m&quot;</span>)<span class="op">;</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (marks_param <span class="op">===</span> <span class="kw">null</span>) {</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span><span class="op">;</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">var</span> marks <span class="op">=</span> marks_param<span class="op">.</span><span class="fu">split</span>(<span class="st">&quot;,&quot;</span>)<span class="op">;</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (marks<span class="op">.</span><span class="at">length</span> <span class="op">!=</span> <span class="dv">81</span>) {</span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a> <span class="bu">console</span><span class="op">.</span><span class="fu">log</span>(<span class="st">&quot;Failure: marks url len is &quot;</span> <span class="op">+</span> marks<span class="op">.</span><span class="at">length</span>)<span class="op">;</span></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span><span class="op">;</span></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> (i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> <span class="dv">81</span><span class="op">;</span> i<span class="op">++</span>) {</span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (marks[i]<span class="op">.</span><span class="at">length</span> <span class="op">&gt;</span> <span class="dv">0</span>) {</span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">var</span> cell <span class="op">=</span> <span class="bu">document</span><span class="op">.</span><span class="fu">getElementById</span>(i<span class="op">+</span><span class="dv">1</span>)<span class="op">;</span></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (cell<span class="op">.</span><span class="at">innerHTML</span><span class="op">.</span><span class="fu">trim</span>()<span class="op">.</span><span class="at">length</span> <span class="op">&gt;</span> <span class="dv">0</span>) {</span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a> <span class="bu">console</span><span class="op">.</span><span class="fu">log</span>(<span class="st">&quot;Pencil marks in cell with number: &quot;</span> <span class="op">+</span> (i<span class="op">+</span><span class="dv">1</span>))<span class="op">;</span></span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a> } <span class="cf">else</span> {</span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a> cell<span class="op">.</span><span class="at">innerHTML</span> <span class="op">=</span> <span class="st">&quot;&lt;span&gt;&quot;</span> <span class="op">+</span> marks[i] <span class="op">+</span> <span class="st">&quot;&lt;/span&gt;&quot;</span><span class="op">;</span></span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a> }</span></code></pre></div>
<p>Which also comes out nicely:</p>
<p><img src="images/sudoku-10.png" class="center-img" /></p>
</div>
</body>
</html>