;
; Bulls and Cows game v2 - 6th March 2020 - see http://forum.ulisp.com/t/bulls-cows-game-using-assembler/501
;
(defvar *spectrum* nil)
; Calculate number of bulls and cows between two numbers
(defun bullcow (digits guess code)
(let ((score 0))
(dotimes (i 16) (setf (nth i *spectrum*) 0))
(dotimes (d digits)
(let ((da (mod guess 16))
(db (mod code 16)))
(cond
((= da db) (incf score 16))
(t
(when (<= (incf (nth da *spectrum*)) 0) (incf score))
(when (>= (decf (nth db *spectrum*)) 0) (incf score))))
(setq guess (truncate guess 16))
(setq code (truncate code 16))))
score))
; Convert a decimal number to bcd: 123 -> #x123
(defun bcd (n)
(cond
((zerop n) 0)
(t (+ (mod n 10) (* 16 (bcd (truncate n 10)))))))
; Print n digits with leading zeros
(defun printn (n d)
(cond
((= n 0) nil)
(t (printn (1- n) (truncate d 16)) (princ (mod d 16)))))
; Next valid bcd number
(defun incbcd (n)
(cond
((< (logand n #xf) 9) (1+ n))
(t (ash (incbcd (ash n -4)) 4))))
; Return t only if every call of fun a b succeeds
(defun every* (fun a b)
(cond
((or (null a) (null b)) t)
((null (funcall fun (car a) (car b))) nil)
(t (every* fun (cdr a) (cdr b)))))
; Find guess compatible with all my guesses and your replies
(defun computer-choose (digits start try guesses replies)
(let ((s (ash 1 (* digits 4))))
(loop
(setq try (mod (incbcd try) s))
(when (= try start) (return nil))
(when
(every*
(lambda (guess reply) (= (bullcow digits try guess) reply))
guesses replies)
(return try)))))
; Invite player to guess my code
(defun player-guess (digits i-playing mycode)
(princ " Your guess: ")
(let* ((your-guess (bcd (read)))
(result (bullcow digits your-guess mycode))
(you-playing (if (= result (* digits #x10)) nil result)))
(princ " Score: ") (printn 2 result)
(unless you-playing
(terpri)
(if i-playing (princ "You win!") (princ "You got it!"))
(terpri) (princ " "))
you-playing))
; Let me have a guess
(defun computer-guess (digits you-playing try)
(princ " My guess: ") (printn digits try) (princ " Score: ")
(let* ((your-reply (bcd (read)))
(i-playing (if (= your-reply (* digits #x10)) nil your-reply)))
(unless i-playing
(terpri)
(if you-playing (princ "I win!") (princ "I got it!")))
i-playing))
; Play the game
(defun play (digits)
(setq *spectrum* (let (lst) (dotimes (i 16 lst) (push 0 lst))))
(let* ((s (expt 10 digits))
(mycode (bcd (random s)))
(start (bcd (random s)))
(try start) (turn 0)
(i-playing 0) (you-playing 0) guesses replies)
(loop
(terpri) (princ "[") (princ (incf turn)) (princ "]")
(when you-playing (setq you-playing (player-guess digits i-playing mycode)))
(when i-playing (setq i-playing (computer-guess digits you-playing try))
(when i-playing
(push try guesses)
(push i-playing replies)
(setq try (computer-choose digits start try guesses replies))
(unless try
(terpri)
(princ "I think you gave an incorrect answer")
(setq i-playing nil))))
(when (and (not you-playing) (not i-playing)) (return)))))