;; Knight's tour using Warnsdorff's rule.
;; https://en.wikipedia.org/wiki/Knight%27s_tour

(defconstant +board-sz+ 8)

(defparameter *board* (make-array `(,+board-sz+ ,+board-sz+) :initial-element 0))

(defconstant +knight-moves+
  '(#(1 -2) #(2 -1) #(2 1) #(1 2) #(-1 2) #(-2 1) #(-2 -1) #(-1 -2)))

(defun board-at (x y)
  (aref *board* y x))

(defun board-set (x y val)
  (setf (aref *board* y x) val))

(defun print-board ()
  (dotimes (y +board-sz+)
    (dotimes (x +board-sz+)
      (format t "[~d] " (board-at x y))) (terpri)))

(defun is-valid-sq (x y)
  (and (>= x 0) (>= y 0) (< x +board-sz+) (< y +board-sz+) (zerop (board-at x y))))

(defun get-moves (x y)
  (remove-if-not (lambda (lst)
                   (apply #'is-valid-sq lst))
                 (mapcar (lambda (mvmt)
                           (let ((new-x (+ x (aref mvmt 0)))
                                 (new-y (+ y (aref mvmt 1))))
                             (list new-x new-y))) +knight-moves+)))

(defun get-min-move (moves)
  (if (null moves)
      nil
      (apply #'min (mapcar (lambda (move)
                             (length (get-moves (car move) (second move)))) moves))))

(defun next-sq (x y)
  (let* ((moves (get-moves x y))
         (min-move (get-min-move moves)))
    (when (null moves)
      (return-from next-sq nil))
    (dolist (move moves)
      (when (= min-move (length (get-moves (car move) (second move))))
        (return-from next-sq move)))))

(defun sq-name (x y)
  (format nil "~C~D" (code-char (+ (char-int #\a) x)) (abs (- y +board-sz+))))

(defun begin-tour (x y)
  (format t "Moved to ~A~%" (sq-name x y))
  (let ((next (next-sq x y)))
    (board-set x y 1)
    (print-board)
    (when (null next)
      (format t "No more moves!~%") (quit))
    (apply #'begin-tour next)))

(begin-tour 0 0)