June 20, 2019
Sudoku in Reagent
Here's the live code - the game is rendered below.
We'll start with a bunch of sudokus:
(require '[reagent.core :refer [atom]])
(def sudokus
[{:puzzle [0 0 4 3 0 0 2 0 9 0 0 5 0 0 9 0 0 1 0 7 0 0 6 0 0 4 3 0 0 6 0 0 2 0 8 7 1 9 0 0 0 7 4 0 0 0 5 0 0 8 3 0 0 0 6 0 0 0 0 0 1 0 5 0 0 3 5 0 8 6 9 0 0 4 2 9 1 0 3 0 0]
:answer [8 6 4 3 7 1 2 5 9 3 2 5 8 4 9 7 6 1 9 7 1 2 6 5 8 4 3 4 3 6 1 9 2 5 8 7 1 9 8 6 5 7 4 3 2 2 5 7 4 8 3 9 1 6 6 8 9 7 3 4 1 2 5 7 1 3 5 2 8 6 9 4 5 4 2 9 1 6 3 7 8]}
{:puzzle [0 4 0 1 0 0 0 5 0 1 0 7 0 0 3 9 6 0 5 2 0 0 0 8 0 0 0 0 0 0 0 0 0 0 1 7 0 0 0 9 0 6 8 0 0 8 0 3 0 5 0 6 2 0 0 9 0 0 6 0 5 4 3 6 0 0 0 8 0 7 0 0 2 5 0 0 9 7 1 0 0]
:answer [3 4 6 1 7 9 2 5 8 1 8 7 5 2 3 9 6 4 5 2 9 6 4 8 3 7 1 9 6 5 8 3 2 4 1 7 4 7 2 9 1 6 8 3 5 8 1 3 7 5 4 6 2 9 7 9 8 2 6 1 5 4 3 6 3 1 4 8 5 7 9 2 2 5 4 3 9 7 1 8 6]}
{:puzzle [6 0 0 1 2 0 3 8 4 0 0 8 4 5 9 0 7 2 0 0 0 0 0 6 0 0 5 0 0 0 2 6 4 0 3 0 0 7 0 0 8 0 0 0 6 9 4 0 0 0 3 0 0 0 3 1 0 0 0 0 0 5 0 0 8 9 7 0 0 0 0 0 5 0 2 0 0 0 1 9 0]
:answer [6 9 5 1 2 7 3 8 4 1 3 8 4 5 9 6 7 2 7 2 4 8 3 6 9 1 5 8 5 1 2 6 4 7 3 9 2 7 3 9 8 1 5 4 6 9 4 6 5 7 3 8 2 1 3 1 7 6 9 2 4 5 8 4 8 9 7 1 5 2 6 3 5 6 2 3 4 8 1 9 7]}
{:puzzle [4 9 7 2 0 0 0 0 0 1 0 0 4 0 0 0 0 5 0 0 0 0 1 6 0 9 8 6 2 0 3 0 0 0 4 0 3 0 0 9 0 0 0 0 0 0 0 1 0 7 2 6 0 0 0 0 2 0 0 5 8 7 0 0 0 0 6 0 0 0 0 4 5 3 0 0 9 7 0 6 1]
:answer [4 9 7 2 5 8 3 1 6 1 8 6 4 3 9 7 2 5 2 5 3 7 1 6 4 9 8 6 2 9 3 8 1 5 4 7 3 7 5 9 6 4 1 8 2 8 4 1 5 7 2 6 3 9 9 6 2 1 4 5 8 7 3 7 1 8 6 2 3 9 5 4 5 3 4 8 9 7 2 6 1]}
{:puzzle [0 0 5 9 1 0 3 0 8 0 0 9 4 0 3 0 6 0 0 2 7 5 0 0 1 0 0 0 3 0 0 0 0 2 0 1 0 0 0 8 2 0 0 0 7 0 0 6 0 0 7 0 0 4 0 0 0 0 8 0 0 0 0 6 4 0 1 5 0 7 0 0 8 9 0 0 0 0 4 2 0]
:answer [4 6 5 9 1 2 3 7 8 1 8 9 4 7 3 5 6 2 3 2 7 5 6 8 1 4 9 7 3 8 6 4 5 2 9 1 9 5 4 8 2 1 6 3 7 2 1 6 3 9 7 8 5 4 5 7 3 2 8 4 9 1 6 6 4 2 1 5 9 7 8 3 8 9 1 7 3 6 4 2 5]}
{:puzzle [1 0 0 0 0 5 0 0 7 3 8 0 9 0 0 0 0 0 6 0 0 0 0 0 4 8 0 8 2 0 0 0 1 0 7 5 0 4 0 7 6 0 0 2 0 0 6 9 0 0 2 0 0 1 0 0 5 0 3 9 0 0 4 0 0 0 0 2 0 1 0 0 0 0 0 0 4 6 3 5 2]
:answer [1 9 4 6 8 5 2 3 7 3 8 2 9 7 4 5 1 6 6 5 7 2 1 3 4 8 9 8 2 3 4 9 1 6 7 5 5 4 1 7 6 8 9 2 3 7 6 9 3 5 2 8 4 1 2 1 5 8 3 9 7 6 4 4 3 6 5 2 7 1 9 8 9 7 8 1 4 6 3 5 2]}
{:puzzle [0 0 9 0 6 5 4 3 0 0 0 7 0 0 0 8 0 0 6 0 0 1 0 8 0 2 0 0 0 3 0 9 0 0 0 2 5 0 1 4 0 3 9 6 0 8 0 4 0 0 0 1 0 0 0 3 0 5 0 9 0 0 7 0 5 6 0 8 0 0 0 0 0 7 0 2 4 0 0 9 0]
:answer [2 8 9 7 6 5 4 3 1 3 1 7 9 2 4 8 5 6 6 4 5 1 3 8 7 2 9 7 6 3 8 9 1 5 4 2 5 2 1 4 7 3 9 6 8 8 9 4 6 5 2 1 7 3 4 3 2 5 1 9 6 8 7 9 5 6 3 8 7 2 1 4 1 7 8 2 4 6 3 9 5]}
{:puzzle [0 0 0 0 0 0 6 5 7 7 0 2 4 0 0 1 0 0 3 5 0 0 0 6 0 0 0 5 0 0 0 2 0 0 0 9 2 1 0 3 0 0 5 0 0 0 4 7 1 0 9 0 0 8 0 0 8 7 6 0 0 9 0 9 0 0 5 0 2 0 3 0 0 3 0 0 1 8 2 0 6]
:answer [8 9 4 2 3 1 6 5 7 7 6 2 4 9 5 1 8 3 3 5 1 8 7 6 9 4 2 5 8 3 6 2 4 7 1 9 2 1 9 3 8 7 5 6 4 6 4 7 1 5 9 3 2 8 1 2 8 7 6 3 4 9 5 9 7 6 5 4 2 8 3 1 4 3 5 9 1 8 2 7 6]}
{:puzzle [5 0 3 0 7 0 1 9 0 0 0 0 0 0 6 7 5 0 0 4 7 1 9 0 6 0 0 4 0 0 0 3 8 0 0 0 9 5 0 2 0 0 3 0 0 0 0 0 0 1 0 0 7 2 0 0 0 8 0 4 0 0 1 3 0 0 0 0 1 8 6 0 0 8 6 7 2 0 0 0 5]
:answer [5 6 3 4 7 2 1 9 8 2 1 9 3 8 6 7 5 4 8 4 7 1 9 5 6 2 3 4 7 2 6 3 8 5 1 9 9 5 1 2 4 7 3 8 6 6 3 8 5 1 9 4 7 2 7 9 5 8 6 4 2 3 1 3 2 4 9 5 1 8 6 7 1 8 6 7 2 3 9 4 5]}
{:puzzle [0 6 0 7 2 0 9 0 8 0 8 4 0 0 3 0 0 1 7 0 0 1 0 0 0 6 5 9 0 0 0 0 8 0 0 0 0 7 1 0 6 0 0 0 0 0 0 2 0 1 0 0 3 4 0 0 0 2 0 0 7 0 6 0 3 0 0 4 9 8 0 0 2 1 5 0 0 0 0 9 0]
:answer [1 6 3 7 2 5 9 4 8 5 8 4 6 9 3 2 7 1 7 2 9 1 8 4 3 6 5 9 4 6 3 5 8 1 2 7 3 7 1 4 6 2 5 8 9 8 5 2 9 1 7 6 3 4 4 9 8 2 3 1 7 5 6 6 3 7 5 4 9 8 1 2 2 1 5 8 7 6 4 9 3]}
{:puzzle [1 4 0 0 6 0 8 0 0 0 8 5 0 1 0 0 4 0 9 0 7 4 0 0 2 5 0 0 3 0 0 7 0 4 0 0 2 0 9 0 0 0 3 0 7 0 0 8 9 0 0 0 6 0 0 0 0 7 4 0 0 1 0 6 0 1 3 0 5 0 9 0 7 0 0 0 0 2 6 0 0]
:answer [1 4 2 5 6 9 8 7 3 3 8 5 2 1 7 9 4 6 9 6 7 4 3 8 2 5 1 5 3 6 8 7 1 4 2 9 2 1 9 6 5 4 3 8 7 4 7 8 9 2 3 1 6 5 8 9 3 7 4 6 5 1 2 6 2 1 3 8 5 7 9 4 7 5 4 1 9 2 6 3 8]}
{:puzzle [5 9 0 0 0 0 1 4 7 0 0 0 9 0 0 0 0 8 0 7 2 0 0 0 0 3 0 7 0 0 0 4 0 2 9 0 0 2 0 0 3 0 8 0 6 8 0 0 1 7 0 0 5 0 0 0 5 7 6 4 0 0 9 0 3 6 0 0 5 0 0 0 1 0 0 8 0 0 0 0 2]
:answer [5 9 8 3 2 6 1 4 7 3 1 4 9 5 7 6 2 8 6 7 2 4 8 1 9 3 5 7 5 3 6 4 8 2 9 1 4 2 1 5 3 9 8 7 6 8 6 9 1 7 2 4 5 3 2 8 5 7 6 4 3 1 9 9 3 6 2 1 5 7 8 4 1 4 7 8 9 3 5 6 2]}
{:puzzle [1 0 0 0 0 0 0 9 0 2 0 8 9 7 0 6 0 5 0 0 0 5 3 2 0 0 0 0 0 6 0 5 0 4 0 0 7 0 0 8 0 6 0 0 2 0 8 3 7 0 0 0 1 0 6 0 4 0 8 0 1 2 0 8 9 0 6 0 0 0 5 0 0 1 5 0 4 0 0 0 7]
:answer [1 5 7 4 6 8 2 9 3 2 3 8 9 7 1 6 4 5 4 6 9 5 3 2 7 8 1 9 2 6 1 5 3 4 7 8 7 4 1 8 9 6 5 3 2 5 8 3 7 2 4 9 1 6 6 7 4 3 8 5 1 2 9 8 9 2 6 1 7 3 5 4 3 1 5 2 4 9 8 6 7]}
{:puzzle [9 0 0 0 8 4 0 6 0 6 0 4 0 0 5 2 0 7 0 3 0 0 7 0 0 8 0 7 6 0 0 0 1 5 0 0 0 5 3 0 0 0 0 0 1 0 0 0 4 0 9 6 0 3 1 0 5 0 2 6 0 9 0 0 0 2 0 4 0 0 0 0 8 0 0 0 0 3 7 1 0]
:answer [9 2 7 3 8 4 1 6 5 6 8 4 9 1 5 2 3 7 5 3 1 6 7 2 4 8 9 7 6 9 2 3 1 5 4 8 4 5 3 7 6 8 9 2 1 2 1 8 4 5 9 6 7 3 1 7 5 8 2 6 3 9 4 3 9 2 1 4 7 8 5 6 8 4 6 5 9 3 7 1 2]}
{:puzzle [3 0 8 0 5 6 0 0 7 0 0 6 9 0 0 2 5 3 0 1 2 0 4 0 0 0 0 0 0 0 0 0 0 3 2 0 9 0 4 8 0 0 0 0 0 7 6 0 1 0 9 8 0 5 0 0 0 0 0 1 9 0 4 8 3 1 0 0 0 5 0 6 0 4 0 0 0 7 0 3 0]
:answer [3 9 8 2 5 6 4 1 7 4 7 6 9 1 8 2 5 3 5 1 2 7 4 3 6 9 8 1 8 5 6 7 4 3 2 9 9 2 4 8 3 5 7 6 1 7 6 3 1 2 9 8 4 5 2 5 7 3 6 1 9 8 4 8 3 1 4 9 2 5 7 6 6 4 9 5 8 7 1 3 2]}
{:puzzle [1 7 0 3 0 0 0 0 9 0 0 8 0 4 0 6 0 0 0 0 0 0 6 0 0 3 0 6 0 0 8 0 0 0 0 1 9 2 4 6 0 0 3 0 0 3 0 0 9 0 2 5 0 0 0 1 0 2 0 0 0 4 0 7 0 9 5 0 3 0 1 6 0 0 5 0 0 7 8 0 0]
:answer [1 7 6 3 2 8 4 5 9 5 3 8 1 4 9 6 7 2 4 9 2 7 6 5 1 3 8 6 5 7 8 3 4 9 2 1 9 2 4 6 5 1 3 8 7 3 8 1 9 7 2 5 6 4 8 1 3 2 9 6 7 4 5 7 4 9 5 8 3 2 1 6 2 6 5 4 1 7 8 9 3]}
{:puzzle [0 0 4 0 3 0 0 2 1 0 7 0 0 0 5 0 0 9 3 8 0 6 9 0 0 0 0 0 3 0 0 0 0 0 0 0 6 0 2 1 0 0 4 5 0 0 1 0 9 0 7 0 0 3 0 0 0 8 4 6 7 0 0 5 6 0 0 0 1 2 4 0 0 0 8 2 5 0 0 3 0]
:answer [9 5 4 7 3 8 6 2 1 2 7 6 4 1 5 3 8 9 3 8 1 6 9 2 5 7 4 8 3 7 5 6 4 9 1 2 6 9 2 1 8 3 4 5 7 4 1 5 9 2 7 8 6 3 1 2 3 8 4 6 7 9 5 5 6 9 3 7 1 2 4 8 7 4 8 2 5 9 1 3 6]}
{:puzzle [0 8 3 2 0 0 0 9 6 2 0 0 0 3 0 7 0 4 0 0 7 9 1 5 0 0 0 4 0 2 3 9 0 0 0 8 0 1 0 0 0 4 0 6 0 0 6 9 8 7 0 0 0 0 0 0 0 4 0 0 0 0 7 5 0 0 0 6 0 2 8 0 0 7 0 0 5 0 9 0 0]
:answer [1 8 3 2 4 7 5 9 6 2 9 5 6 3 8 7 1 4 6 4 7 9 1 5 8 3 2 4 5 2 3 9 6 1 7 8 7 1 8 5 2 4 3 6 9 3 6 9 8 7 1 4 2 5 9 2 1 4 8 3 6 5 7 5 3 4 7 6 9 2 8 1 8 7 6 1 5 2 9 4 3]}
{:puzzle [8 0 3 0 0 0 2 7 0 4 0 9 0 0 8 0 0 0 7 0 0 0 2 4 0 9 6 0 0 0 0 0 6 9 1 5 0 0 1 8 0 2 0 0 0 0 3 0 7 5 0 0 0 0 0 5 4 0 0 0 0 6 0 6 0 8 1 0 0 0 0 3 3 7 2 0 0 9 1 4 0]
:answer [8 6 3 9 1 5 2 7 4 4 2 9 6 7 8 3 5 1 7 1 5 3 2 4 8 9 6 2 8 7 4 3 6 9 1 5 5 4 1 8 9 2 6 3 7 9 3 6 7 5 1 4 8 2 1 5 4 2 8 3 7 6 9 6 9 8 1 4 7 5 2 3 3 7 2 5 6 9 1 4 8]}
{:puzzle [0 7 0 4 9 0 1 0 3 0 0 3 0 7 0 5 9 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 1 1 0 0 7 4 9 0 2 0 0 2 4 3 0 6 0 0 8 6 0 0 9 8 0 7 0 0 0 1 2 6 0 0 0 0 0 4 8 0 0 0 7 0 5 2]
:answer [2 7 6 4 9 5 1 8 3 8 4 3 2 7 1 5 9 6 9 5 1 8 6 3 2 4 7 3 9 7 5 2 8 4 6 1 1 6 8 7 4 9 3 2 5 5 2 4 3 1 6 9 7 8 6 3 5 9 8 2 7 1 4 7 1 2 6 5 4 8 3 9 4 8 9 1 3 7 6 5 2]}
{:puzzle [8 3 0 0 4 0 0 9 6 0 2 0 0 1 0 0 0 8 9 0 4 7 0 0 0 3 0 4 0 9 0 0 2 0 6 5 3 0 8 0 0 1 0 7 0 0 0 0 6 0 3 8 0 0 5 0 7 0 3 0 0 2 0 0 0 0 5 0 6 4 0 0 0 0 2 0 8 0 1 0 0]
:answer [8 3 1 2 4 5 7 9 6 7 2 6 3 1 9 5 4 8 9 5 4 7 6 8 2 3 1 4 1 9 8 7 2 3 6 5 3 6 8 4 5 1 9 7 2 2 7 5 6 9 3 8 1 4 5 8 7 1 3 4 6 2 9 1 9 3 5 2 6 4 8 7 6 4 2 9 8 7 1 5 3]}
{:puzzle [0 6 0 2 5 0 0 0 0 7 9 2 0 0 6 1 0 0 0 0 0 0 8 1 6 0 0 0 0 9 0 0 0 5 0 0 4 1 0 0 0 9 7 8 0 2 0 7 3 0 0 0 0 4 0 0 0 7 6 3 0 1 0 3 0 0 5 4 0 2 9 0 8 0 0 0 0 0 0 4 0]
:answer [1 6 8 2 5 7 4 3 9 7 9 2 4 3 6 1 5 8 5 4 3 9 8 1 6 7 2 6 3 9 8 7 4 5 2 1 4 1 5 6 2 9 7 8 3 2 8 7 3 1 5 9 6 4 9 2 4 7 6 3 8 1 5 3 7 1 5 4 8 2 9 6 8 5 6 1 9 2 3 4 7]}
{:puzzle [0 5 0 4 0 0 6 8 0 0 9 0 1 0 0 0 0 0 0 0 8 0 5 9 3 0 2 0 0 7 2 0 3 0 0 0 0 0 0 6 0 0 2 0 8 6 0 4 0 8 0 0 0 5 0 3 6 0 0 4 1 9 0 1 0 0 0 0 7 0 0 0 0 7 2 8 0 0 0 5 0]
:answer [7 5 1 4 3 2 6 8 9 2 9 3 1 6 8 5 7 4 4 6 8 7 5 9 3 1 2 5 8 7 2 4 3 9 6 1 3 1 9 6 7 5 2 4 8 6 2 4 9 8 1 7 3 5 8 3 6 5 2 4 1 9 7 1 4 5 3 9 7 8 2 6 9 7 2 8 1 6 4 5 3]}
{:puzzle [0 6 1 4 9 0 0 2 0 2 8 0 0 0 7 0 5 0 0 0 3 1 0 8 0 0 7 6 0 0 7 0 4 0 3 1 0 0 0 2 5 0 0 7 4 0 9 0 6 0 0 0 0 0 0 0 0 0 1 0 0 0 8 5 7 0 0 0 0 2 0 6 8 0 0 9 0 6 0 0 0]
:answer [7 6 1 4 9 5 8 2 3 2 8 4 3 6 7 1 5 9 9 5 3 1 2 8 4 6 7 6 2 5 7 8 4 9 3 1 1 3 8 2 5 9 6 7 4 4 9 7 6 3 1 5 8 2 3 4 6 5 1 2 7 9 8 5 7 9 8 4 3 2 1 6 8 1 2 9 7 6 3 4 5]}
{:puzzle [6 0 8 9 0 0 0 5 0 0 0 0 3 2 0 1 9 0 0 1 0 0 0 0 3 0 0 4 0 0 0 7 3 6 0 0 5 7 0 2 6 0 0 0 0 0 0 3 1 0 5 0 2 0 0 8 0 0 0 0 0 6 4 0 2 0 0 9 0 5 0 7 0 4 7 0 0 8 0 0 1]
:answer [6 3 8 9 1 7 4 5 2 7 5 4 3 2 6 1 9 8 2 1 9 5 8 4 3 7 6 4 9 2 8 7 3 6 1 5 5 7 1 2 6 9 8 4 3 8 6 3 1 4 5 7 2 9 1 8 5 7 3 2 9 6 4 3 2 6 4 9 1 5 8 7 9 4 7 6 5 8 2 3 1]}])
Now we'll shuffle them and pick one to be the current-puzzle
.
(def current-puzzle (atom (first (shuffle sudokus))))
Assign a set of coordinates and a puzzle digit to each square and call it a board
:
(defn positions []
(for [i (range 9) j (range 9)]
[j i]))
(defn init-matrix []
(into {}
(map vector
(positions)
(:puzzle @current-puzzle))))
(def board (atom (init-matrix)))
A cell, when clicked becomes the selected-cell
, and is displayed in a different color.
(def selected-cell (atom nil))
(def mouse-over-cell (atom nil))
(defn rect-cell [[x y]]
[:rect.cell
{:x (+ 0 x) :width 1
:y (+ 0 y) :height 1
:fill (cond
(= [x y] @selected-cell) "magenta"
(= [x y] @mouse-over-cell) "violet"
(and (< 2 x 6) (< 2 y 6)) "lightpink"
(or (< 2 x 6) (< 2 y 6)) "cyan"
:else "lightpink")
:stroke-width (if (or (= [x y] @selected-cell)
(= [x y] @mouse-over-cell))
0.05 0.025)
:stroke "black"
:on-click
(fn click-square [e]
(reset! selected-cell [x y]))
:on-mouse-over
(fn mouse-over-square [e]
(reset! mouse-over-cell [x y]))}])
(defn text-cell [app-state [x y]]
[:text
{:x (+ 0.5 x) :width 1
:y (+ 0.72 y) :height 1
:text-anchor "middle"
:font-size 0.6
:on-click
(fn click-square [e]
(reset! selected-cell [x y]))}
(if (< 0 (get app-state [x y]))
(get app-state [x y]))])
(defn render-board [app-state]
(into
[:svg.board
{:view-box "0 0 9 9"
:shape-rendering "auto"
:style {:max-height "500px"}}]
(for [[pos val] app-state]
[:g
[rect-cell pos]
[text-cell app-state pos]])))
The number-button
will set the selected-cell
in the board
to its value.
(defn number-button [x]
(fn []
[:button
{:style {:color "white"
:padding "20 20px"
:background-color "violet"
:background-image "linear-gradient(to top left,
rgba(0, 0, 0, .2),
rgba(0, 0, 0, .2) 30%,
rgba(0, 0, 0, 0))"
:font-size "28px"
:text-shadow "1px 1px 1px #000"
:border-radius "10px"
:box-shadow "inset 2px 2px 3px rgba(255, 255, 255, .6),
inset -2px -2px 3px rgba(0, 0, 0, .6)"}
:on-click
(fn new-game-click [e]
(swap! board assoc @selected-cell x))}
x]))
To figure out if we've won, we compare the current digits with the answer:
(defn attempt [state]
(into [] (for [i (range 9) j (range 9)]
(get state [j i]))))
(defn win? [state]
(= (:answer @current-puzzle)
(attempt state)))
(defn num-row []
(for [i (range 1 10)]
[number-button i]))
(defn sudoku []
[:center
[:h2 "Sudoku - Reagent"]
[:div (num-row)]
[:div [render-board @board]]
[:h3 (str "Win? = " (win? @board))]
[:button
{:style {:color "white"
:padding "50 50px"
:background-color "violet"
:background-image "linear-gradient(to top left,
rgba(0, 0, 0, .2),
rgba(0, 0, 0, .2) 30%,
rgba(0, 0, 0, 0))"
:font-size "28px"
:text-shadow "1px 1px 1px #000"
:border-radius "10px"
:box-shadow "inset 2px 2px 3px rgba(255, 255, 255, .6),
inset -2px -2px 3px rgba(0, 0, 0, .6)"}
:on-click
(fn new-game-click [e]
(reset! current-puzzle (first (shuffle sudokus)))
(reset! board (init-matrix)))}
"New puzzle"]])
[sudoku]