First draft for a blog post about the sudoku display tool

This commit is contained in:
Drew Galbraith 2023-04-10 19:32:25 -07:00
parent 100a4f5922
commit 786c0cb49e
11 changed files with 238 additions and 0 deletions

238
blog/2023-04-10-sudoku.md Normal file
View File

@ -0,0 +1,238 @@
# Writing a Sudoku Solver
I previously dabbled with writing a [sudoku solver](https://gitlab.com/dgalbraith33/sudoku-solver)
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.
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 I'd also
like to take a crack at solving other types of sudokus (Chess Sudoku, Killer Sudoku, etc).
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 I'm planning on writing a quick
HTML/javascript board state display to be able to visualize the board. For now I don't intend it to
be interactive, however I'll likely add that in the future.
## Creating a sudoku grid
I'm 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.
```html
<div class="container">
<h1>Sudoku</h1>
<div class="puzzle">nothing</div>
</div>
```
```css
.container {
max-width: 800px;
margin: auto;
}
.puzzle {
border: 1px solid black;
}
```
-- Screenshot: sudoku-1.png
I'm not joking when I say I'm going to have to do baby steps here.
Next I'll try to actually make a square grid. The best way to do this is probably with flexbox or
something but I'm just gonna hard code some widths and heights.
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.
Let's focus on the 9 main boxes in the grid now before worrying about the cells. I'll make each box
210 pixels tall and wide. And slap a big ole border on there. Let's make it 2px because we will
want a narrower one for the cells.
```
.puzzle {
width: 630px;
height: 630px;
}
.box {
border: 2px solid black;
width: 210px;
height: 210px;
}
```
-- Screenshot: sudoku-2.png
Reload that and... right div's auto linebreak after them.
I think we can "float: left" these bad boys and...
-- Screenshot: sudoku-3.png
Right, now I'm pretty sure the boxes are 210 + 4 pixels wide because the border isn't included.
While I'm tempted to just math my way out of this I recall that you can specify the border-box
sizing to avoid this.
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.
-- Screenshot: sudoku-4.png???
Ok now we can just recreate all of this with the cells and should be good to go right?
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 won't fit in the 800px wide container. (Again
just use flexbox).
Finally this works!
-- Screenshot: sudoku-5.png
## Displaying a puzzle
Next up is to actually get some numbers in this bad boy. Now this is where I relent and use
flexboxes because
[this](https://stackoverflow.com/questions/2939914/how-do-i-vertically-align-text-in-a-div/13515693)
StackOverflow answer convinced me.
```
.cell {
...
font-size: 40px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
```
-- Screenshot: sudoku-6.png
### Taking the puzzle from the URL
Now to make it so I can get the page to display any puzzle I want easily from the solver, I'll 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.
I'll 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.
```
window.onload = (event) => {
var params = new URLSearchParams(window.location.search);
var puzzle = params.get("p");
if (puzzle === null) {
return;
}
if (puzzle.length != 81) {
console.log("Failure: puzzle url len is " + puzzle.length);
return;
}
var cells = document.getElementsByClassName("cell");
if (cells.length != 81) {
console.log("Failure: wrong number of cells: " + cells.length);
return;
}
for (i = 0; i < 81; i++) {
if (puzzle[i] != '.') {
cells[i].innerText = puzzle[i]
}
}
}
```
And hooray it works super well!
-- Screenshot: sudoku-7.png
Oh wait... I didn't think about the fact that the elements in the HTMLCollection from the
document.getElementsByClassName call wouldn't be in the row order of the puzzle (all of the cells
in box 1 come first).
You can see the effect of this as there are 2 9s in column 2 of the puzzle. Oops.
I'm going to just do the old fashioned brute force way and give each cell an id from 1-81 and insert
those manually. I'm sure there is a better way but hey this works.
Then with a quick update to the code.
```
for (i = 0; i < 81; i++) {
if (puzzle[i] != '.') {
document.getElementById(i+1).innerText = puzzle[i]
}
}
```
It works!
-- Screenshot: sudoku-8.png
Still some goofiness like the border on the outside being thinner than the interiors but I'm pretty
happy with this for now.
## Showing pencil marks
Now to really visualize the solver's state, we'll 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.
```
.cell > span {
font-size: 10px;
font-weight: normal;
text-align: center;
letter-spacing: 6px;
word-wrap: anywhere;
}
```
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.
This comes out quite nicely:
-- Screenshot: sudoku-9.png
### Reading the pencil marks from the url
For this url trick we will add the pencil marks using a comma separated array like so:
123,,,,456,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Using the string above we can use the following javascript code to parse and insert them:
```
var marks_param = params.get("m");
if (marks_param === null) {
return;
}
var marks = marks_param.split(",");
if (marks.length != 81) {
console.log("Failure: marks url len is " + marks.length);
return;
}
for (i = 0; i < 81; i++) {
if (marks[i].length > 0) {
var cell = document.getElementById(i+1);
if (cell.innerHTML.trim().length > 0) {
console.log("Pencil marks in cell with number: " + (i+1));
} else {
cell.innerHTML = "<span>" + marks[i] + "</span>";
}
}
}
```
Which also comes out nicely:
-- Screenshot: sudoku-10.png

BIN
blog/sudoku-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
blog/sudoku-10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
blog/sudoku-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
blog/sudoku-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
blog/sudoku-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
blog/sudoku-5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
blog/sudoku-6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
blog/sudoku-7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
blog/sudoku-8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
blog/sudoku-9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB