;;; Mastermind solver

(defparameter *default-colors*
  '(red green blue yellow cyan magenta))

(defvar *colors*)
(defvar *constraints*)
(defvar *places*)

(defun generate-all (&optional (places *places*))
  (if (= places 0)
      '(nil)
      (iter (with rest = (generate-all (1- places)))
            (for c in *colors*)
            (appending (mapcar (lambda (s) (cons c s)) rest)))))

(defun init-game (colors places)
  (setf *colors* colors)
  (setf *places* places)
  (setf *constraints* (generate-all)))

(defun score (secret guess)
  (iter (for color in *colors*)
        (for s-color = (funcall #'count color secret))
        (for g-color = (funcall #'count color guess))
        (for same = (iter (for s in secret)
                          (for g in guess)
                          (count (and (eq s color) (eq s g)))))
        (sum (- (min s-color g-color) same) into white)
        (sum same into black)
        (finally (return (list black white)))))

(defun analyze (guess score)
  (setf *constraints*
        (remove-if-not (lambda (x) (equal (score x guess) score))
                       *constraints*)))

(defun guess ()
  (elt *constraints* (random (length *constraints*))))

(defun read-number (str)
  (format t (concatenate 'string "~&" str " "))
  (read))

(defun start-game (&key (colors *default-colors*) (places 4))
  (format t "There are ~a colors:~%~
             ~{~a~^, ~}~%~
             Choose ~a (you can select a color twice).~%~%~
             Now I will try to guess...~%~%"
          (length colors) colors places)
  (init-game colors places)
  (iter (for guess = (guess))
        (format t "My guess: ~a~%" guess)
        (for guesses upfrom 1)
        (for black = (read-number "How many colors were matched exactly?"))
        (while (< black places))
        (for white = (read-number "How many were on the wrong position?"))
        (analyze guess (list black white))
        (finally (format t "I have guessed it in ~d steps.~%" guesses))))

(defun automatic-game (&key (colors *default-colors*) (places 4) quiet)
  (init-game colors places)
  (let ((secret (guess)))
    (unless quiet
      (format t "There are ~a colors:~%~
             ~{~a~^, ~}~%~
             I have chosen the colors: ~a~%~%~
             Now I will try to guess...~%~%"
              (length colors) colors secret))
    (iter (for guess = (guess))
          (for (black white) = (score secret guess))
          (unless quiet
            (format t "My guess: ~a [~d / ~d]~%" guess black white))
          (for guesses upfrom 1)
          (while (< black places))
          (analyze guess (list black white))
          (finally
           (if quiet
               (return guesses)
               (format t "I have guessed it in ~d steps.~%" guesses))))))

;;; Gives an average of ~4.6
(defun test-average (games)
  (+ (/ (iter (repeat games)
              (sum (automatic-game :quiet t)))
        games)
     0.0d0))