The magazine of the Melbourne PC User Group

Solving Sudoku
Ken Holmes
 

Ken Holmes has written another of his fantastic programs —this time he shows us how to solve the popular game Sudoku

Are you feeling out of the mainstream because you haven't taken to Sudoku with enthusiasm? Well, l have tried only a couple. There may be more sophisticated approaches, but the obvious way would seem to be to try to see the weak spots and concentrate on, say, a row, using a soft pencil to jot, in each cell, the possible values which don't clash with the set values in that cell's row, column or 3 x 3 square. Then, hopefully, you will see that some value appears only in one cell of the row and it can therefore be boldly written in. Now, switching to the column through that cell or the 3 x 3 square, repeat the process. I imagine it would get progressively easier as the cells fill up but I haven't gone that far, since I consider there are more exciting things to do in life.

However, the opportunity to write a program to solve it was irresistible. I would not, for a moment, suggest using the program to solve the daily puzzle (that would be pointless), but it might be useful to the composers to check that there was only one solution. I imagine they may have such a program already.

This is an obvious case for a recursive procedure. Such a procedure uses a single piece of code which can call up another copy of the same code, within itself, and wait for it to do its job and return, to then go out of existence. The second copy can also call up a third copy and so on. In this program, for example, there are 81 copies running simultaneously when it reaches the final cell, ie. one for each cell. In the meantime, it will have run up numerous dead ends and backed out, creating and deleting numerous copies of the procedure.

The Program

We use Visual Basic 6 since many use it and it is similar in commands, punctuation and structure to the DOS-based Basics such as QuickBasic. It is not the Visual Basic 2005 Express, as distributed on the March, 2006, monthly DVD; that is object-oriented and markedly dissimilar.

As may be seen in the code and in the screen captures, there is some initial housekeeping with a "Draw Grid" Menu to draw the grids, one for the
original puzzle and one for the working solution. There are two arrays, cvorig (x, y) for the puzzle and c (x, y) for the working grid. Clicking the "Enter Puzzle" Menu enables the mouse to alternately select a cell and then allot it a value; the value is entered in both grids. Figure 1 shows a puzzle fully entered, with the "Select Cell" invitation showing; it is ignored if ready to solve.

Clicking the "Solve Puzzle" Menu initiates the recurse() Sub for the top/ left cell (column 1, row 1). Unless its value has already been allotted, each value from 1 to 9 will be checked to see if it clashes with the same value in the row or column or 3 x 3 square. When a valid value is found it is allotted and recurse() is called for column 2, row 1, with recurse(1, 1) remaining in existence. This process continues down the grid; if no valid value is found for the next cell, the recurse() ends and it reverts to the previous cell for a fresh try. The location travels down and up the grid with many, many unsuccessful forays until it manages to reach the bottom right cell. Bingo! A solution. A message Box pops up to advise this and the solution is displayed in the solution grid. In Figure 2 the Message Box shows that it is the first solution, found after 6852 recursions taking 550 milliseconds.
 



Figure 1. Puzzle fully entered



Figure 2. First solution found

There may be more than one solution, so clicking away the Message Box lets it back off up the grid, exploring the untried values in all the other cells. Each time it reaches the final cell we have another solution and we count them. Eventually, it will get back to the first cell and when it has done that enough times to exhaust all the initial possibilities for that cell, recurse(1, 1) will finish [in mnusolvepuzzle()] and a Message Box will advise the total number of solutions. We also have a counter in recurse() so that we can record the total number of times that a recursion is opened; also, each Message Box gives elapsed time. In Figure 3 we see there is still only one solution after 14,287 recusions taking 5460 milliseconds. These features will be useful when solving the five-grid Sudoku, as discussed later.

Investigations

Now that we have the donkey work available to solve a Sudoku, in a second or so after it is entered, we can explore some interesting areas.

When it finally returns to cell(1, 1), the solution grid will be back in its original state ie. identical to the puzzle grid (Figure 3). We can modify it and solve it again. Presumably, all published Sudokus have a single solution but, for example, I took one and solved it; then I deleted clue values, separately one at a time, and determined the number of solutions. The results were 38, 10, 43, 213, 45, 21 and 2. This is enough to give the general picture. Next, I filled in an extra clue in the puzzle. Using the value for the cell from the solution, there was still one solution, as you would expect since it is the same solution. Entering a different number, which, of course, must not conflict with other clues in its row, column or 3 x 3 square, there was no solution – again as you would expect. The cut-off is sharp. Counting the number of clues in ten Sudokus gave seven at 28, one at 26, one at 29 and one at 30; so this is not a reliable indicator for a single solution.

The Five-Grid Sudoku

These have a central Sudoku with another at each corner, sharing a common 3 x 3 square. We are told they need to be solved together, implying that they are interdependent and can't be solved one at a time. Our program proves useful in solving these. For a start we can count the number of solutions for each of the five, as a stand-alone; for example, in one case we found that the central Sudoku had 2 solutions and the corners 1, 1, 12 and 50. The obvious next step was to solve a single-solution corner one, giving, in this case, six extra clues in the 3 x 3 square common with the central one and, thus, giving it a single-solution. In turn, this ensured that the others were solvable. When solving manually you do not, of course, know which is the single-solution one.
 



Figure 3. Solution completed

The initial numbers of clues were 29 for the centre and 26, 25, 26 and 26 for the corners; the total recursions were 5041 for the centre and 1360, 5101, 18356 and 132715 respectively for the corners. There is here a vague correlation of the number of recursions with the number of solutions but not with the number of clues.

Another five-gridder gave more interesting results. The central one had only 21 clues and gave 37934 solutions after 13,230,000 recursions! Obviously the extra blank spaces rapidly multiply the optional pathways to try. The corner ones had 24, 24, 24 and 24 clues and gave 127, 348, 294 and 1 solutions after 114,771, 46,929, 2,714,683 and 1,257,533 recursions respectively. Thankfully, the single-solution gave the starting point to solve it all. But there seems to be little correlation; obviously it depends very much on the particular pattern of numbers in each puzzle.
I assume it might be possible to encounter a five-gridder with no single-solution element. In that case it would be a simple modification to the program to put in extra Message Box pauses to allow copying more solutions for the starting Sudoku.

Conclusion

Although a computer program adopts a straight-forward trial-and-error approach it gets a complete, reliable result and with alacrity. Most single Sudokus are solved in a second or less. The 13,230,000 recursions mentioned above took about 16 minutes; again, the program could be changed to end after, say, 10 solutions.
 

 Listing 1  [ Download text version here ]
 
 Visual Basic Code to Solve Sudoku Puzzles

Option Explicit: DefInt A-Z 'All integers except where specified
Dim cvorig(1 To 9, 1 To 9), c(1 To 9, 1 To 9) 'Original and ...
Dim i, j, x, y, xp, yp, mx, my                '... working values.
Dim entering, solno!, cnt!, Start!, TTime As Long

Private Sub mnuexit_Click()
 End
End Sub

Private Sub mnudrawgrid_Click()            'Draw both grids
 For i = 1000 To 5500 Step 500             'Vertical lines
  For j = 1000 To 5500 Step 500            'Horizontal lines
   Line (1000, j)-(5500, j)                'Puzzle grid
   Line (i, 1000)-(i, 5500)
   Line (7000, j)-(11500, j)               'Solution grid
   Line (i + 6000, 1000)-(i + 6000, 5500)
 Next: Next                                'Now for doubled lines
 For i = 1020 To 5520 Step 1500            'Vertical lines
  For j = 1020 To 5520 Step 1500           'Horizontal lines
   Line (1000, j)-(5500, j)                'Puzzle grid
   Line (i, 1000)-(i, 5500)
   Line (7000, j)-(11500, j)               'Solution grid
   Line (i + 6000, 1000)-(i + 6000, 5500)
 Next: Next                                'Draw cell values grid
 For i = 500 To 5500 Step 500              'Vertical lines
  For j = 6500 To 7000 Step 500            'Horizontal lines
   Line (i, 6500)-(i, 7000)
   Line (500, j)-(5500, j)
 Next: Next
 For i = 0 To 9                            'Enter values in grid
 CurrentX = 600 + i * 500: CurrentY = 6600: Print Str(i)
 Next                                      'Now print grid titles
 CurrentX = 2000: CurrentY = 100: Print "ORIGINAL PUZZLE"
 CurrentX = 8500: CurrentY = 100: Print "SOLUTION"
 CurrentX = 2400: CurrentY = 5600: Print "CELL VALUES"
End Sub

Public Sub PrintCell(xc, yc, v, grid)      'grid=0 for puzzle cell
 Dim xp, yp                                'grid=1 for solution cell
 xp = xc * 500 + 600 + grid * 6000: yp = yc * 500 + 600
 Line (xp, yp)-Step(220, 250), &H80000005, BF 'Blank & print value
 CurrentX = xp: CurrentY = yp: If v > 0 Then Print Str(v)
End Sub                                    'Note. Don't print zero

Public Sub Message()
 TTime = (Timer - Start) * 1000 'convert to millisecs (Long Integer)
 MsgBox "Solution " + Str(solno) + Chr(13) + Chr(10)
            + "Recursions " + Str(cnt) + Chr(13) + Chr(10)
 + "Time " + Str(TTime)         'Note. MsgBox must be all on one line
End Sub

Private Sub mnuenterp_Click()
 entering = 1                   'enables entering of puzzle
 CurrentX = 2400: CurrentY = 600: Print "SELECT CELL"
End Sub

Private Sub Form_MouseDown(Button%, Shift%, x!, y!)
 'Mouse is only used for menues, entering puzzle and message boxes.
 If entering = 1 Then     'Selecting cell by clicking in "Puzzle" area
  If x > 1000 And x < 5500 And y > 1000 And y < 5500 Then
   mx = x \ 500 - 1: my = y \ 500 - 1: entering = 2   'Cell identified
   CurrentX = 2400: CurrentY = 6100: Print "ALLOT VALUE"
   Line (2400, 600)-(4200, 900), &H80000005, BF 'Blanks "SELECT CELL"
  End If                  'Next mouse click must be to allot value.
 ElseIf entering = 2 Then 'Allotting value by clicking in "Allot" area
  If x > 500 And x < 5500 And y > 6500 And y < 7000 Then
   cvorig(mx, my) = x \ 500 - 1: c(mx, my) = cvorig(mx, my)
   Call PrintCell(mx, my, cvorig(mx, my), 0)  'Print in Puzzle grid
   Call PrintCell(mx, my, c(mx, my), 1)       'Print in Solution grid
   entering = 1: CurrentX = 2400: CurrentY = 600: Print "SELECT CELL"
   Line (2400, 6100)-(4200, 6400), &H80000005, BF 'Blank "ALLOT VALUE"
  End If  'Next mouse click must be to select cell (or start solving).
 End If
End Sub

Private Sub mnusolvepuzzle_Click() 'Initiate solution
 Line (2400, 600)-(4200, 900), &H80000005, BF   'Delete instructions
 Line (2400, 6100)-(4200, 6400), &H80000005, BF: entering = 0
 CurrentX = 8600: CurrentY = 600: Print "SOLVING": solno = 0: cnt = 0
 Start = Timer
 Call recurse(1, 1)                'start the recursion at cell(1, 1)
 Call Message
End Sub

Public Sub recurse(cx%, cy%)       'Recursive procedure. Called for
 'each of 81 cells in turn. If meets dead end, Sub ends and it reverts
 'to previous Sub to try another value there. Solution is when cell
 '(9,9) reached and OK. First solution printed (Usually only
 'one) and, if allowed to continue, it ends back at cell(1, 1), having
 'exhausted all paths and with puzzle in original presentation.
 Dim nx%, ny%, try%, k%, l%, sx%, sy%
 Dim valid As Boolean
 cnt = cnt + 1                    'Count number of recursions opened
 nx = cx + 1: ny = cy             'Determine next cell along row.
 If nx = 10 Then                  'If at end of row, ...
  nx = 1: ny = ny + 1             '... move to start of next row.
 End If                           'See later, ny = 10 will finish.
 If cvorig(cx, cy) > 0 Then       'Bypass if value fixed by puzzle
  If ny < 10 Then                 'Provided not on last cell, ...
   Call recurse(nx, ny)           '... move to next cell
  Else                            'If on last cell, ...
   solno = solno + 1
   If solno = 1 Then Call Message 'For first solution only
  End If                          'Solution found
 Else                             'Value NOT set in puzzle (ie. =0)
  For try = 1 To 9                'Check all possible values
   valid = True: c(cx, cy) = 0    'Assume number valid and then ...
   For k = 1 To 9                 '... check if clashes with rules
    If try = c(k, cy) Then valid = False 'check along row for clashes
    If valid = False Then Exit For
   Next
   If valid = False Then GoTo done 'Already false so bypass next checks
   For k = 1 To 9
    If try = c(cx, k) Then valid = False'Check down column for clashes
    If valid = False Then Exit For
   Next
   If valid = False Then GoTo done  'Find top/left cell of 3 X 3 square
   sx = ((cx - 1) \ 3) * 3 + 1: sy = ((cy - 1) \ 3) * 3 + 1
   For k = 0 To 2: For l = 0 To 2   'Check in small square for clashes
    If try = c(sx + k, sy + l) Then valid = False
    If valid = False Then Exit For  'get out of inner loop
   Next
    If valid = False Then Exit For  'get out of outer loop
   Next
done:               'Check completed. Now, if number valid, ...
   If valid = True Then '...go on to next cell. If NOT test next 'try'.
    c(cx, cy) = try                         'Put into array
    Call PrintCell(cx, cy, try, 1)  'Print in Solution grid
    If ny < 10 Then                 'Not reached final cell
     Call recurse(nx, ny)           'Move to next cell
    Else                            'Reached final cell. Solution found
     solno = solno + 1              'Increment solution count
     If solno = 1 Then Call Message 'For first solution only
    End If
   End If
  Next
 End If
 'If recurse() reaches here, there are no further values to try,
 'we need to restore original value. Sub will end and it will
 'revert to the previous cell which will try further possibilities.
 c(cx, cy) = cvorig(cx, cy)            'Restore original value and
 Call PrintCell(cx, cy, cvorig(cx, cy), 1)  'Print in Solution grid
End Sub

Reprinted from the May 2006 issue of PC Update, the magazine of Melbourne PC User Group, Australia

[ About Melbourne PC User Group ]