lispbacktrackingknights-tour

Knights tour backtracking Lisp


I am having some problems generating the correct output for this program. My output is almost correct, but missing of some steps. My code is as follows:

(defun kt (x y m n)                       ;set the board
  (setq totalmoves (* m n))     ;find the total moves
  (setq steps 1)                         ;count steps
  (setq lst (list (list x y)))  ;list visited with initial points
  (findPath x y totalmoves steps m n lst)       ;start tour with initial points
)

(defun findPath (x y totalMoves steps m n lst)
  (cond
  ((null lst) NIL)
  ((= steps totalMoves) lst)                ;if steps equals than total moves, then solution is complete
  ;try to solve the rest recursively
  ;1- down and right
  ((canMove (+ x 2) (+ y 1) m n lst)
    (findPath (+ x 2) (+ y 1) totalMoves (+ steps 1) m n (appendList (+ x 2)        (+ y 1) lst))

)
;2- right and down
((canMove (+ x 1) (+ y 2) m n lst)
     (findPath (+ x 1) (+ y 2) totalMoves (+ steps 1) m n (appendList (+ x 1) (+ y 2) lst))

)
;3- right ups
((canMove (- x 1) (+ y 2) m n lst)
     (findPath (- x 1) (+ y 2) totalMoves (+ steps 1) m n (appendList(- x 1) (+ y 2) lst))

)
;4- up and right
((canMove(- x 2) (+ y 1) m n lst)
     (findPath(- x 2) (+ y 1) totalMoves (+ steps 1) m n (appendList(- x 2) (+ y 1) lst))
)
;5 - up and left
((canMove(- x 2) (- y 1) m n lst)
     (findPath(- x 2) (- y 1) totalMoves (+ steps 1) m n (appendList(- x 2) (- y 1) lst))
)
;6- left and up 
((canMove(- x 1) (- y 2) m n lst)
     (findPath(- x 1) (- y 2) totalMoves (+ steps 1) m n (appendList(- x 1) (- y 2) lst))
)
;7- left and down
((canMove(+ x 1) (- y 2) m n lst)
     (findPath(+ x 1) (- y 2) totalMoves (+ steps 1) m n (appendList(+ x 1) (- y 2) lst))
)
;8- down and left
((canMove(+ x 2) (- y 1) m n lst)
     (findPath(+ x 2) (- y 1) totalMoves (+ steps 1) m n (appendList(+ x 2) (- y 1) lst))
)
(t 
 (findPath (car(car(reverse lst))) (car(cdr(car(reverse lst)))) totalMoves steps m n (reverse(cdr (reverse lst))))
  )
)
)

(defun appendList (x y lst)
  (setq lst (reverse(append (list (list x y)) (reverse lst))))
)

(defun check-visited (x y lst)
  (cond
    ((null lst) 1)              ;if nth else to check in list
    ((equal (list x y) (car lst)) NIL)    ;check if curr is visited
    ((check-visited x y (cdr lst)))        ;recurse
  )
)

(defun canMove (x y m n lst)
  (if (and (<= 1 x)         ;for the correct state, all conditions below must be met          
       (<= x m)    ;height is more than or equal to x
       (<= 1 y)
       (<= y n)     ;width is more than or equal to y
       (equal (check-visited x y lst) 1)
  ) 
  1 NIL ;if all above conds are true, return true else return nil
)
)

The test code is

kt 1 1 5 5

The output is ((1 1) (3 2) (5 3) (4 5) (2 4) (1 2) (3 3) (5 4) (3 5) (1 4) (2 2) (4 3) (5 5) (3 4) (1 5) (2 3) (4 4) (2 5) (1 3) (2 1) (4 2))

There is 21 steps listed here, but it is supposed to have 25.


Solution

  • Your approach is incorrect, since the function findPath does not try all the possible moves in a certain position, but, using cond, tries only the first possible move (in a cond, the first non nil branch is executed and the statement is terminated by returning the value corresponding findPath call). So, your function produces only the longest tour without backtracking, which is of exactly 21 moves.

    In order to have the correct solution, you have to try all the possible moves, returning the first one that, through the recursive call to findPath, produces the right number of moves. In Common Lisp this can be done by using the or and and operators:

    1. or, with n operands, returns the value of the first operand which is not nil, if exists, otherwise it returns nil: so if we arrange to put in a or all the recursive calls of findPath, if one of them returns the correct final value the or expression terminates returning that value;

    2. and returns nil if any of its operands is nil, otherwise, if all of them are not nil, it returns the value of the last operand. So we can use it by first checking if a move is possible, and, if this is true, executing the move by calling findPath recursively. If the call returns nil then that move is not useful, otherwise we have found a correct tour.

    Here is the new function:

    (defun findPath (x y totalMoves steps m n lst)
      (if (= steps totalMoves)
          lst           ; if the steps are equal to total moves, then a solution is found
                        ; else try recursively all the possible moves from x y
                        ; 1- down and right
          (or (and (canMove (+ x 2) (+ y 1) m n lst)
                   (findPath (+ x 2) (+ y 1) totalMoves (+ steps 1) m n (appendList (+ x 2) (+ y 1) lst)))
                        ; 2- right and down
              (and (canMove (+ x 1) (+ y 2) m n lst)
                   (findPath (+ x 1) (+ y 2) totalMoves (+ steps 1) m n (appendList (+ x 1) (+ y 2) lst)))
                        ; 3- right ups
              (and (canMove (- x 1) (+ y 2) m n lst)
                   (findPath (- x 1) (+ y 2) totalMoves (+ steps 1) m n (appendList (- x 1) (+ y 2) lst)))
                        ; 4- up and right
              (and (canMove (- x 2) (+ y 1) m n lst)
                   (findPath (- x 2) (+ y 1) totalMoves (+ steps 1) m n (appendList (- x 2) (+ y 1) lst)))
                        ; 5 - up and left
              (and (canMove (- x 2) (- y 1) m n lst)
                   (findPath (- x 2) (- y 1) totalMoves (+ steps 1) m n (appendList (- x 2) (- y 1) lst)))
                        ; 6- left and up 
              (and (canMove (- x 1) (- y 2) m n lst)
                   (findPath (- x 1) (- y 2) totalMoves (+ steps 1) m n (appendList (- x 1) (- y 2) lst)))
                        ; 7- left and down
              (and (canMove (+ x 1) (- y 2) m n lst)
                   (findPath (+ x 1) (- y 2) totalMoves (+ steps 1) m n (appendList (+ x 1) (- y 2) lst)))
                        ; 8- down and left
              (and (canMove (+ x 2) (- y 1) m n lst)
                   (findPath (+ x 2) (- y 1) totalMoves (+ steps 1) m n (appendList (+ x 2) (- y 1) lst))))))
    

    Finally, a few notes about the code.

    Don't use setq to initialize variables not previously declared

    let can be used to declare and initialize local variables, so that the function kt could be defined in this way:

    (defun kt (x y m n)                            ; set the board
      (let ((totalmoves (* m n))                   ; find the total moves
            (steps 1)                              ; count steps
            (lst (list (list x y))))               ; list visited with initial points
        (findPath x y totalmoves steps m n lst)))  ; start tour with initial points
    

    Try to keep your code simple

    The function appendList is defined by appending a one-element list to the reverse of another, and the reversing the result. This is equivalent to simply append the first list to the second one, that is:

    (defun appendList (x y lst)
      (append lst (list (list x y))))
    

    Use generalized booleans to simplify conditions

    For instance, the function check-visited and canMove can be integrated in a single, more simple function:

    (defun canMove (x y m n lst)
      (and (<= 1 x m)      ;for the correct state, all conditions below must be met          
           (<= 1 y n)
           (not (member (list x y) lst :test 'equal))))
    

    Try to factorize your code, or don't repeat similar code when not necessary

    The function findPath has a lot of repetitions, that could be eliminated with the use of the loop (thereis is the equivalent of the or in a loop):

    (defun findPath (x y totalMoves steps m n lst)
      (if (= steps totalMoves)
          lst 
          (loop for (x-inc y-inc) in '((+2 +1) (+1 +2) (-1 +2) (-2 +1) (-2 -1) (-1 -2) (+1 -2) (+2 -1))
            for x1 = (+ x x-inc)
            for y1 = (+ y y-inc)
            thereis (and (canMove x1 y1 m n lst)
                         (findPath x1 y1 totalMoves (1+ steps) m n (appendList x1 y1 lst))))))
    

    Use conventions typical for the language in use

    In Common Lisp, camelCase is avoided, preferring to it the classical notation of names and verbs separated by dash, like find-path instead of findPath, or can-move instead of canMove, etc.