diff --git a/blog/2023-04-10-sudoku.md b/blog/2023-04-10-sudoku.md new file mode 100644 index 0000000..101da96 --- /dev/null +++ b/blog/2023-04-10-sudoku.md @@ -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 +
+

Sudoku

+
nothing
+
+``` + +```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 = "" + marks[i] + ""; + } + } + } +``` + +Which also comes out nicely: + +-- Screenshot: sudoku-10.png diff --git a/blog/sudoku-1.png b/blog/sudoku-1.png new file mode 100644 index 0000000..81ea383 Binary files /dev/null and b/blog/sudoku-1.png differ diff --git a/blog/sudoku-10.png b/blog/sudoku-10.png new file mode 100644 index 0000000..f92110a Binary files /dev/null and b/blog/sudoku-10.png differ diff --git a/blog/sudoku-2.png b/blog/sudoku-2.png new file mode 100644 index 0000000..df09597 Binary files /dev/null and b/blog/sudoku-2.png differ diff --git a/blog/sudoku-3.png b/blog/sudoku-3.png new file mode 100644 index 0000000..7f4d02d Binary files /dev/null and b/blog/sudoku-3.png differ diff --git a/blog/sudoku-4.png b/blog/sudoku-4.png new file mode 100644 index 0000000..85b7659 Binary files /dev/null and b/blog/sudoku-4.png differ diff --git a/blog/sudoku-5.png b/blog/sudoku-5.png new file mode 100644 index 0000000..837dcba Binary files /dev/null and b/blog/sudoku-5.png differ diff --git a/blog/sudoku-6.png b/blog/sudoku-6.png new file mode 100644 index 0000000..9fe2174 Binary files /dev/null and b/blog/sudoku-6.png differ diff --git a/blog/sudoku-7.png b/blog/sudoku-7.png new file mode 100644 index 0000000..7fd59d2 Binary files /dev/null and b/blog/sudoku-7.png differ diff --git a/blog/sudoku-8.png b/blog/sudoku-8.png new file mode 100644 index 0000000..c98a3b6 Binary files /dev/null and b/blog/sudoku-8.png differ diff --git a/blog/sudoku-9.png b/blog/sudoku-9.png new file mode 100644 index 0000000..a67c63f Binary files /dev/null and b/blog/sudoku-9.png differ