Bulls and Cows


Task

Create a four digit random number from the digits   1   to   9,   without duplication.

The program should:

   ask for guesses to this number
   reject guesses that are malformed
   print the score for the guess


The score is computed as:

 The player wins if the guess is the same as the randomly chosen number, and the program ends.
 A score of one bull is accumulated for each digit in the guess that equals the corresponding digit in the randomly chosen initial number.
 A score of one cow is accumulated for each digit in the guess that also appears in the randomly chosen number, but in the wrong position.


Related tasks

   Bulls and cows/Player
   Guess the number
   Guess the number/With Feedback
   Mastermind


(defun get-number ()
  (do ((digits '()))
      ((>= (length digits) 4) digits)
    (pushnew (1+ (random 9)) digits)))
 
(defun compute-score (guess number)
  (let ((cows  0)
        (bulls 0))
    (map nil (lambda (guess-digit number-digit)
               (cond ((= guess-digit number-digit) (incf bulls))
                     ((member guess-digit number)  (incf cows))))
         guess number)
    (values cows bulls)))
 
(defun number->guess (number)
  (when (integerp number)
    (do ((digits '()))
        ((zerop number) digits)
      (multiple-value-bind (quotient remainder) (floor number 10)
        (push remainder digits)
        (setf number quotient)))))
 
(defun valid-guess-p (guess)
  (and (= 4 (length guess))
       (every (lambda (digit) (<= 1 digit 9)) guess)
       (equal guess (remove-duplicates guess))))
 
(defun play-game (&optional (stream *query-io*))
  (do ((number (get-number))
       (cows   0)
       (bulls  0))
      ((= 4 bulls))
    (format stream "~&Guess a 4-digit number: ")
    (let ((guess (number->guess (read stream))))
      (cond ((not (valid-guess-p guess))
             (format stream "~&Malformed guess."))
            (t
             (setf (values cows bulls) (compute-score guess number))
             (if (= 4 bulls)
               (format stream "~&Correct, you win!")
               (format stream "~&Score: ~a cows, ~a bulls."
                       cows bulls)))))))