Solving The Knight’s Tour

I really enjoy solving computer programming puzzles. They are great for exercising the brain and learning new programming languages. One of my all time favorites is The Knight’s Tour, which was given to me as an assignment in college back in ‘93.

The basic idea is this: Place a knight on a standard chessboard in a random starting position. Move the knight using normal L-shaped knight moves to every position on the board without moving to any position twice. There is more than one solution for a given starting point. The smallest solvable board size is 6×6. If you are solving this by hand then it is best to mark the traveled positions with a number. For example, here’s a solved puzzle:

1 |16|31|56|3 |18|21|50
--+--+--+--+--+--+--+--
30|55|2 |17|48|51|4 |19
--+--+--+--+--+--+--+--
15|32|57|54|45|20|49|22
--+--+--+--+--+--+--+--
58|29|44|47|52|63|42|5
--+--+--+--+--+--+--+--
33|14|53|64|43|46|23|40
--+--+--+--+--+--+--+--
28|59|34|37|62|41|6 |9
--+--+--+--+--+--+--+--
13|36|61|26|11|8 |39|24
--+--+--+--+--+--+--+--
60|27|12|35|38|25|10|7

Back in college I wrote two solutions: a brute force solution and what I call the “look ahead” solution. On an 8×8 chessboard the brute force solution, written in C++, took 14 days to solve on a 486 DX2. The “look ahead” method can solve it in microseconds. The brute force method travels to every possible position until it finds a solution while the look-ahead method chooses the best move from the next possible moves. The best move is determined by “looking ahead” at the next 2 moves and choosing the move with the least number possible moves. Don’t ask me why it works, it just does!

I’ve written Knight’s Tour solutions in just about every language I’ve learned since ‘93 including Java, Python, Ruby and Common Lisp. The Common Lisp solution I wrote is by far the most elegant, not necessarily because its written in Common Lisp. It is mostly because I’m now more experienced as a developer and at solving this particular problem :-P

My CL solution is 63 lines of code with comments. If you find a bug you win a free one year subscription to my blog!

#|
The Knight's Tour
Author: Anthony Fairchild
|#

(defun make-chessboard (size-xy)
  "creates a two dimensional array representing a chessboard"
  (make-array (list size-xy size-xy)))

(defun is-valid-move (board pos-x pos-y)
  "Returns true if the given move is valid.  A move is valid if it is
   within the board dimensions and it is not taken by another position."
  (and (< pos-x (array-dimension board 0))
       (< pos-y (array-dimension board 1))
       (>= pos-x 0)(>= pos-y 0)
       (zerop (aref board pos-x pos-y))))

(defun next-valid-moves (board curx cury)
  "returns next 8 possible valid moves"
  (loop for pair in '((-1 2)(-2 1)(1 -2)(2 -1)(-1 -2)(-2 -1)(1 2)(2 1))
        for newx = (+ (first pair) curx)
        for newy = (+ (second pair) cury)
        when (is-valid-move board newx newy)
        collect (list newx newy)))

(defun next-optimized-moves (board curx cury)
  "Sorts the next valid moves with the best moves first.  The best move is
   determined by looking ahead at the next 2 moves and choosing the move
   with the least number of possible moves"
  (sort (next-valid-moves board curx cury)
        #'(lambda (move-a move-b)
            (< (length (next-valid-moves board (first move-a) (second move-a)))
               (length (next-valid-moves board (first move-b) (second move-b)))))))

(defun solve-knights-tour (board posx posy move-num next-moves-fun)
  "Solves the knight's tour recursively. Uses next-move-fun function
to determine next move. Both brute force and optimized methods use
this function as a base."
  (setf (aref board posx posy) move-num)
  (cond ((= move-num (* (array-dimension board 0)
                        (array-dimension board 1))) board)
        ((loop for move in (funcall next-moves-fun board posx posy)
               when (solve-knights-tour board (first move)
                                        (second move) (1+ move-num)
                                        next-moves-fun)
               return t) board)
        (t (setf (aref board posx posy) 0) nil)))

(defun solve-optimized (board posx posy move-num)
  "Solves the knight's tour intelligently by finding the most likely
good position to move to."
  (solve-knights-tour board posx posy move-num #'next-optimized-moves))  

(defun solve-brute-force (board posx posy move-num)
  "Solves the knight's tour using brute force.  This means travel to every
possible position."
  (solve-knights-tour board posx posy move-num #'next-valid-moves))

(defun knights-tour (&key (optimized t)(size-xy 8)(start-x (random size-xy))(start-y (random size-xy)))
  "Return a solution for the knight's tour or nil if no solution could be found"
  (if optimized
      (solve-optimized (make-chessboard size-xy) start-x start-y 1)
      (solve-brute-force (make-chessboard size-xy) start-x start-y 1)))

CL-USER> (time (print-table (knights-tour)))
9 |6 |11|44|27|4 |29|34
--+--+--+--+--+--+--+--
12|43|8 |5 |46|33|26|3
--+--+--+--+--+--+--+--
7 |10|45|48|55|28|35|30
--+--+--+--+--+--+--+--
42|13|54|63|32|47|2 |25
--+--+--+--+--+--+--+--
53|64|49|56|1 |58|31|36
--+--+--+--+--+--+--+--
14|41|62|59|50|19|24|21
--+--+--+--+--+--+--+--
61|52|39|16|57|22|37|18
--+--+--+--+--+--+--+--
40|15|60|51|38|17|20|23
Evaluation took:
  0.006 seconds of real time
  0.004001 seconds of user run time
  0.0 seconds of system run time
  0 page faults and
  1,043,200 bytes consed.

20 Responses to “Solving The Knight’s Tour”

  1. taha Says:

    Hi,
    Recently i compiled this program, and there was no syntax error there, but when i tried to run it with the command (time (print-table (knights-tour))), it gives me an error, that there is no defined print-table there?
    so what should i do now?
    can you help please?

  2. anthonyf Says:

    Taha, try this instead:

    (time (pprint (knights-tour)))

    PRINT-TABLE is another utility of mine to print formatted tables. The code for it is in this blog entry: http://anthonyf.wordpress.com/2006/06/21/pretty-printing-tables/

    Good luck!

    Anthony

  3. HNZ Says:

    Hello,

    I try to run your program but it has an error said ” `NIL’ is not of the expected type `NUMBER’ “. Why is it so?

  4. Homer Says:

    Hi!

    Nice code there. ,However, I have problem to run it. Do we need to put any initialization?

  5. anthonyf Says:

    I think I see the problem you all are having… try putting :initial-element 0 when creating the board array:

    (defun make-chessboard (size-xy)
    “creates a two dimensional array representing a chessboard”
    (make-array (list size-xy size-xy) :initial-element 0))

  6. farah Says:

    hi
    i am searching for similar code but i want the code start of one position randomly and show all possoble squres which knight can move to there ,can i modify this code??

  7. morteza Says:

    for me it take 4 or 5 hours to write c++ code but anyway i knew the algorithm…
    your code is great in lisp its several days I’m trying to write this program but I’m newbie in lisp it really needs time to learn lisp
    i want to use your code for my assignment if you don’t mind

  8. anthonyf Says:

    farah, go ahead and do what you’d like with the code. Good luck!

  9. may Says:

    how the code will be execute?..and what will be the output?

  10. ben Says:

    Thanks a lot for this, works like a charm! i was looking to speed up my brute-force approach to the same problem written in Prolog, and your code helped me a lot.

    cheers,
    -Ben

  11. fredy Says:

    hi I’m mexican and I have a question about it

    que compilador usaste ????

    What compiler did you use???????????? and where dowload it

    I have many probles with clisp

  12. anthonyf Says:

    Hi Fredy,

    I used Lispworks. You can download the personal edition of the compiler here:

    http://www.lispworks.com/downloads/index.html

    Good luck!

    Anthony

  13. fredy Says:

    ok now I’m using lispworks 5.0 personal but I have problems

    when I type (time (print-table (knights-tour))) says:

    CL-USER 1 > (time (print-table (knights-tour)))
    Timing the evaluation of (PRINT-TABLE (KNIGHTS-TOUR))

    Error: Undefined operator PRINT-TABLE in form (PRINT-TABLE (KNIGHTS-TOUR)).
    1 (continue) Try invoking PRINT-TABLE again.
    2 Return some values from the form (PRINT-TABLE (KNIGHTS-TOUR)).
    3 Try invoking something other than PRINT-TABLE with the same arguments.
    4 Set the symbol-function of PRINT-TABLE to another function.
    5 Set the macro-function of PRINT-TABLE to another function.
    6 (abort) Return to level 0.
    7 Return to top loop level 0.

    Type :b for backtrace, :c to proceed, or :? for other options

    THEN I tried with (time (pprint (knights-tour))) and says:

    CL-USER 1 > (time (pprint (knights-tour)))
    Timing the evaluation of (PPRINT (KNIGHTS-TOUR))

    Error: In ZEROP of (NIL) arguments should be of type NUMBER.
    1 (continue) Return a value to use.
    2 Supply a new argument.
    3 (abort) Return to level 0.
    4 Return to top loop level 0.

    Type :b for backtrace, :c to proceed, or :? for other options

    then y changed this code:
    (defun make-chessboard (size-xy)
    “creates a two dimensional array representing a chessboard”
    (make-array (list size-xy size-xy) :initial-element 0))
    and wowwwwwwww listooooooooooooooooo :p

    and ready,,, thanks a lot for your help anthony

    ahhh una cosa mas

    could you document your code line for line I don’t understand some lines I’m new in lisp thanks… good luck

  14. triplex Says:

    hi ,,,,anthony

    I am searching for similar code but I want the code start of one position gives for the user
    could you help me please, what changes can I do in your code for it????? and

    how run it?? please help me…

  15. fredy Says:

    hi,,

    I think I find a bug in your code

    your code no “Sorts the next valid moves with the best moves first. The best move is
    determined by looking ahead at the next 2 moves and choosing the move
    with the least number of possible moves”
    also put two times to curx and cury and never go to second move.

  16. snake Says:

    your code is greatttt””’

    but very hard I don’t understand this lines :

    (sort (next-valid-moves board curx cury)
    #’(lambda (move-a move-b)
    (< (length (next-valid-moves board (first move-a) (second move-a)))
    (length (next-valid-moves board (first move-b) (second move-b)))))))

    could you explain me please

  17. mario Says:

    I don’t understand this lines

    anthony how does work this lines????

    (setf (aref board posx posy) move-num)
    (cond ((= move-num (* (array-dimension board 0)
    (array-dimension board 1))) board)
    ((loop for move in (funcall next-moves-fun board posx posy)
    when (solve-knights-tour board (first move)
    (second move) (1+ move-num)
    next-moves-fun)
    return t) board)
    (t (setf (aref board posx posy) 0) nil)))

  18. Javier Says:

    I don’t know why and what it is but i have many errors while i compile the code.

    *** – READ from #:
    After #\# is #\` an undefined dispatch macro character

    I don’t know if the problem is the compiler or the version or something. Can you solve this please?

    I have the common lisp. And my compiler is cygwin.

    Thanks. If you want any more information only question me.

  19. M R Says:

    hi there,
    It seems you said the smallest solvable board is 6 by 6.
    what did you mean by that? Did you mean that it is solvable from every starting square?, not from only certain starting squares?
    M R

  20. ray Says:

    can you help me with the codes?or could you share me the code?cause i think i can use it on my review..thanks a lot!

Leave a Reply