First draft for a blog post about the sudoku display tool
|
@ -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
|
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 136 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 18 KiB |