Tough Problem 4 (VBA)

Thank you for the explanation. It confirms that I had correctly figured out what you did. I've learned a lot about multi-dimension arrays by picking apart your macro. I love the backtracking feature, and the fact that it remembers where it left off at the previous square. The sequential move generator is a big improvement over my random choice approach. While dissecting your work I added some lines among yours to make it spit out some numbers: to arrive at the final sequence of moves, the ValidateMove function got called over 67 million times. This generated almost 40 million legal moves, most of which turn out to be dead ends on the way to a valid 64 move sequence. My "random move" attempt was iterating only 65,000 times per shot, and I saw the same failed sequences repeating (due to the random move selection); I was afraid to let it run open ended until it found a solution, and seeing what you did, I don't think it would have ever gotten there!


PGC01: As long as we are talking chess, is the Eight Queens puzzle up next?
 

Excel Facts

How to find 2nd largest value in a column?
MAX finds the largest value. =LARGE(A:A,2) will find the second largest. =SMALL(A:A,3) will find the third smallest
This problem really interested me because I played a bit of chess when I was younger and I remember sitting over a board trying to solve the Knight's Tour. The rule of thumb is, when you have a choice of squares, to pick the one which has the fewest number of legal moves. This invariably means that the knight is moved towards the empty edges and corners of the board in this puzzle. It also has relevance to a game of chess because it also tells us that knights are generally more powerful when they are closer to the middle of the board (since they command more squares from there) and, in the endgame, a flank pawn can be a handy tool against an opponent's knight.

This is my solution. To run it, copy into a standard code module and execute the sub called Main(). I've added randomising factors so it will produce a different Knight's Tour each time it is run and you can experiment with changing the dimensions of the board by changing the values of the mlROW_COUNT and mlCOL_COUNT constants. On my PC it produces a Knight's Tour on an 8x8 board at the first attempt in under a second.

Code:
Option Explicit
 
'grid rows and columns representing dimensions of the chess board
'change dimensions to suit (a traditional board is 8x8):
Const mlROW_COUNT As Long = 8
Const mlCOL_COUNT As Long = 8
 
'the maximum number of permissable attempts to solve the puzzle
'change max number of attempts to suit:
Const mlMAX_ATTEMPTS As Long = 10
 
'we will use C3 as the top left cell so that no moves can
'take us off the worksheet
Const msTOP_LEFT_CELL As String = "C3"
 
'a range representing a chess board
Dim mrngChessBoard As Range
 
 
 
 
'the main sub to run to try to solve the puzzle
Public Sub Main()
 
    Dim lAttempts As Long  'a counter to monitor how many tries the algorithm has had
    Dim sMsg As String        'a report message
 
    'get a reference to a range which represents our chess board
    Set mrngChessBoard = Worksheets.Add.Range(msTOP_LEFT_CELL).Resize(mlROW_COUNT, mlCOL_COUNT)
 
    'try to solve the puzzle
    Do
        'we must start with an empty board
        mrngChessBoard.Clear
        lAttempts = lAttempts + 1
    Loop While KnightMoves <> (mlROW_COUNT * mlCOL_COUNT) And lAttempts < mlMAX_ATTEMPTS
 
    'tidy up the chess board presentation however you need to....
    mrngChessBoard.Columns.AutoFit
 
    'create a report message for the end user
    sMsg = "solved after " & lAttempts & " attempt": If lAttempts > 1 Then sMsg = sMsg & "s"
 
    'did we solve it? if not then adjust the report message....
    If lAttempts = mlMAX_ATTEMPTS Then sMsg = "NOT " & sMsg
 
    MsgBox sMsg
 
    'clean up
    Set mrngChessBoard = Nothing
 
End Sub
 
 
 
 
 
'a function which moves the knight around the board and tracks the number of moves
Private Function KnightMoves() As Double
    Dim rngCurrent As Range     'a range representing the current position of the knight
 
    'let's take a random corner of the board as our starting point
    Set rngCurrent = GetStartingSquare
    KnightMoves = 1
 
    With rngCurrent
        .Value = KnightMoves
        .Interior.Color = vbYellow  'mark the starting square yellow
    End With
 
    'move the knight around the board until we run out of available squares
    Do
 
        Set rngCurrent = GetNextMove(rngCurrent)
 
        If Not rngCurrent Is Nothing Then
            KnightMoves = KnightMoves + 1
            rngCurrent.Value = KnightMoves
        Else
            Exit Do
        End If
 
    Loop
 
End Function
 
 
'use Warnsdorff's algorithm to determine the next move:
'--> we move to the next square which itself has the
'least number of possible legal moves
Private Function GetNextMove(ByRef Square As Range) As Range
    Const bytMAXMOVES As Byte = 8 'the largest number of squares a knight can move to is 8
 
    Dim rngMove1 As Range   'looking one move ahead
    Dim rngMove2 As Range   'looking two moves ahead
    Dim rngSquare As Range  'a cell counter
    Dim bytNextMoveCounter As Byte 'track the least number of subsequent legal moves
 
 
    Set rngMove1 = GetLegalMoves(Square)
 
    'if there are no legal first moves then do nothing
    If rngMove1 Is Nothing Then
    'if there is only one legal move then we have to move there
    ElseIf rngMove1.Areas.Count = 1 Then
        Set GetNextMove = rngMove1
 
    'if there are multiple legal moves we have to determine
    'which is the best
    Else
 
        bytNextMoveCounter = bytMAXMOVES
 
        'loop through all the squares which are legal 1st moves
        For Each rngSquare In rngMove1.Areas
 
            'determine all the possible legal 2nd moves from the 1st move
            Set rngMove2 = GetLegalMoves(rngSquare)
 
            If Not rngMove2 Is Nothing Then
                With rngMove2.Areas
 
                    'if there are less 2nd moves from that square than
                    'any others we have checked then we want it
                    If .Count < bytNextMoveCounter Then
                        bytNextMoveCounter = .Count
                        Set GetNextMove = rngSquare
 
                    ElseIf .Count = bytNextMoveCounter Then
 
                        'if two squares are equally viable let's randomly pick one
                        'so that our algorithm produces different results each time
                        Randomize
                        If Rnd >= 0.5 Then Set GetNextMove = rngSquare
 
                    End If
                End With
            End If
        Next rngSquare
 
    End If
 
 
End Function
 
'A function to get all the legal moves the knight can make
Private Function GetLegalMoves(ByRef Square As Range) As Range
    Dim rngCell As Range
 
    For Each rngCell In GetAllMoves(Square)
        If IsMoveLegal(rngCell) Then
 
            If GetLegalMoves Is Nothing Then
                Set GetLegalMoves = rngCell
            Else
                Set GetLegalMoves = Union(GetLegalMoves, rngCell)
            End If
 
        End If
    Next rngCell
 
End Function
 
'A function to determine if a move to a square is allowed
Private Function IsMoveLegal(ByRef Square As Range) As Boolean
    'has the square been visited yet?
    If IsEmpty(Square) Then
 
            'is the square on the chess board?
            If Not Intersect(Square, mrngChessBoard) Is Nothing Then IsMoveLegal = True
 
    End If
 
End Function
 
'A function to determine all the possible moves (legal or not) a knight could make
Private Function GetAllMoves(ByRef Square As Range) As Range
    With Square
        Set GetAllMoves = Union( _
                            .Offset(-2, -1), _
                            .Offset(-2, 1), _
                            .Offset(-1, -2), _
                            .Offset(-1, 2), _
                            .Offset(1, -2), _
                            .Offset(1, 2), _
                            .Offset(2, -1), _
                            .Offset(2, 1) _
                                )
    End With
End Function
 
'a function to determine a corner of the chess board to start from
Private Function GetStartingSquare() As Range
    Dim sngRandomNumber As Single
    Randomize
    sngRandomNumber = Rnd
 
    With mrngChessBoard
        If sngRandomNumber >= 0.75 Then
            Set GetStartingSquare = .Cells(1, 1)
 
        ElseIf sngRandomNumber >= 0.5 Then
            Set GetStartingSquare = .Cells(1, mlCOL_COUNT)
 
        ElseIf sngRandomNumber >= 0.25 Then
            Set GetStartingSquare = .Cells(mlROW_COUNT, 1)
 
        Else
            Set GetStartingSquare = .Cells(mlROW_COUNT, mlCOL_COUNT)
        End If
    End With
End Function

Because this is proof of concept I haven't added any error handling and I haven't concerned myself with the application object's screenupdating, calculation and enableevents properties.

Colin
 
Hi Colin
Nice non-brute force method. Why do you select the option with the least number of possible next moves? I'm not following the relevance of this.....
Cheers
Andrew

Late Edit : Is this an attempt to fill in the outer squares first? If so, then has 'Warnsdorff's algorithm' been optimised purely for solving the Knights tour problem? If so, does it have application elsewhere?
 
Last edited:
Hi Andrew,

It's a rule of thumb I learnt at school; the logic being that we move to a square with the least number of valid exit points (we move to it while it still has some) so we are less likely to come to a dead end before the puzzle is complete. On an 'empty' board a knight in a corner square has only 2 valid moves whereas a knight in the centre has 8 valid moves so, all else being equal, the move will tend to be towards the sides and corners. I didn't know it was called Warnsdorff's rule until I googled it yesterday when I was writing this code. I'm not familiar with the other concepts on that good link you provided such as counting adjacent squares.

Cheers
Colin
 
Last edited:
I can solve the problem on the website, in 63 steps, but my vba skills don't seem to be sufficient to translate what I am doing by hand into code :-(

Stephan, learning is always an ongoing process, if you can't solve it this time, study the solutions posted and you'll be able to post your own solution next time! :)

Cheers
 
Hi Andrew, Colin

Thank you for your great solutions.

Andrew, your solution is similar to mine, using recursion. I think it's worth for anywone that tried to solve this problem to examine this solution to learn about recursion. Recursion comes very handy in this type of problems.

Your link seems also very promising. I didn't have time yet to read it carefully, but I will.


Colin, your solution is great and incredibly fast. You followed another path, your method is not a simple try until succeed, the method you use is very efficient. I didn't know it, I'm sure it's worth understanding and studying, which is what I will do.

Cheers
Pedro
 
PGC01: As long as we are talking chess, is the Eight Queens puzzle up next?

Hi gardnertoo

That's a very good idea. I'll not use it right now, I'd like to vary the themes of the problems, but I'll keep it for future use. Like the Knight Tour that's also a problem that may appeal to many of us. I haven't tried it yet, and it may be a good challenge.

Cheers
 
I'm happy to announce that next week's Tough Problem (vba) will be posted by Andrew Fergus!


This doesn't mean at all that this problem is closed. I still hope to get other answers, maybe with different approaches.

For ex.: the simplest approach, no recursion, just some loops to try to find the desired path.
 

Forum statistics

Threads
1,222,647
Messages
6,167,322
Members
452,110
Latest member
eui

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top