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
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.
September 11, 2007 at 2:38 pm |
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?
September 11, 2007 at 5:03 pm |
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
September 19, 2007 at 7:32 am |
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?
September 20, 2007 at 2:07 pm |
Hi!
Nice code there. ,However, I have problem to run it. Do we need to put any initialization?
September 20, 2007 at 9:30 pm |
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))
September 22, 2007 at 6:16 pm |
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??
September 22, 2007 at 9:35 pm |
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
September 23, 2007 at 1:27 am |
farah, go ahead and do what you’d like with the code. Good luck!
September 23, 2007 at 5:11 pm |
how the code will be execute?..and what will be the output?
November 4, 2007 at 10:35 pm |
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
December 9, 2007 at 10:03 pm |
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
December 10, 2007 at 7:32 am |
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
December 12, 2007 at 11:34 pm |
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
December 15, 2007 at 1:19 am |
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…
December 17, 2007 at 9:35 pm |
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.
December 17, 2007 at 9:44 pm |
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
December 17, 2007 at 9:47 pm |
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)))
January 30, 2008 at 8:07 pm |
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.
June 20, 2008 at 12:43 am |
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
October 2, 2008 at 12:02 pm |
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!