[HOME]

Tic Tac Toe

;;;;
;;;; Written: John Thingstad 27/01-2007
;;;; Algorithm for computer by Nils Holm
;;;;

(defconstant +element-state+ '(#\- #\O #\X))
(defconstant +player-state+ '(#\X #\O))

(defun element-state-p (element) 
  (member element +element-state+))

(defun player-state-p (player)
  (member player +player-state+))

(defun board-coordinate-p (list)
  (and (= (length list) 2)
       (every (lambda (element) 
                (typep element '(integer 0 2)))
                list)))

(defun tictactoe-board-p (board)
  (and (= (array-rank board) 2)
       (equal (array-dimensions board) '(3 3))
       (eq (array-element-type board) 'base-char)))

(deftype element-state () `(and base-char (satisfies element-state-p)))
(deftype player-state () `(and base-char (satisfies player-state-p)))
(deftype board-coordinate () `(and list (satisfies board-coordinate-p)))
(deftype tictactoe-board () `(and simple-array (satisfies tictactoe-board-p)))

(defun make-board ()
  (make-array '(3 3) 
              :element-type 'element-state
              :initial-contents '((#\- #\- #\-) 
                                  (#\- #\- #\-)
                                  (#\- #\- #\-))))

(defun print-board (board)
  (check-type board tictactoe-board)
  (fresh-line)
  (dotimes (j 3)
    (dotimes (i 3)
      (write-char (aref board j i))
      (write-char #\Space))
    (terpri))
  (terpri)
  (values))

(defun set-board (board coord state)
  (check-type board tictactoe-board)
  (check-type coord board-coordinate)
  (check-type state player-state)
  (destructuring-bind (row col) coord
    (setf (aref board row col) state)))

(defun get-board (board coord)
  (check-type board tictactoe-board)
  (check-type coord board-coordinate)
  (destructuring-bind (row col) coord
    (aref board row col)))

(defconstant +rule-list+
  '(((#\- #\O #\O)  (#\O #\O #\O)) 
    ((#\O #\- #\O)  (#\O #\O #\O)) 
    ((#\O #\O #\-)  (#\O #\O #\O)) 
    ((#\- #\X #\X)  (#\O #\X #\X)) 
    ((#\X #\- #\X)  (#\X #\O #\X)) 
    ((#\X #\X #\-)  (#\X #\X #\O)) 
    ((#\- #\X #\-)  (#\O #\X #\-)) 
    ((#\- #\- #\X)  (#\- #\O #\X)) 
    ((#\X #\- #\-)  (#\X #\O #\-)) 
    ((#\- #\X #\O)  (#\O #\X #\O)) 
    ((#\- #\O #\X)  (#\O #\O #\X)) 
    ((#\X #\- #\O)  (#\X #\O #\O)) 
    ((#\X #\O #\-)  (#\X #\O #\O)) 
    ((#\O #\- #\X)  (#\O #\O #\X)) 
    ((#\O #\X #\-)  (#\O #\X #\O)) 
    ((#\- #\- #\O)  (#\O #\- #\O)) 
    ((#\- #\O #\-)  (#\O #\O #\-)) 
    ((#\O #\- #\-)  (#\O #\- #\O)) 
    ((#\- #\- #\-)  (#\- #\O #\-))))

(defun match-row (board f1 f2 f3 rule)
  (destructuring-bind (pattern substitution) rule
    (destructuring-bind (v1 v2 v3) pattern
      (if (and (char= (row-major-aref board f1) v1)
               (char= (row-major-aref board f2) v2)
               (char= (row-major-aref board f3) v3))
          (destructuring-bind (s1 s2 s3) substitution
            (setf (row-major-aref board f1) s1)
            (setf (row-major-aref board f2) s2)
            (setf (row-major-aref board f3) s3)
            substitution)
        nil))))

(defun try-moves (board rules)
  (dolist (rule rules)
    (let ((move (or (match-row board 0 4 8 rule)
                    (match-row board 2 4 6 rule)
                    (match-row board 0 1 2 rule)
                    (match-row board 3 4 5 rule)
                    (match-row board 6 7 8 rule) 
                    (match-row board 0 3 6 rule)
                    (match-row board 1 4 7 rule)
                    (match-row board 2 5 8 rule))))
      (when move (return-from try-moves move)))))

(defun play-move (board)
  (check-type board tictactoe-board)
  (if (char= (get-board board '(1 1)) #\-)
      (set-board board '(1 1) #\O)
    (try-moves board +rule-list+)))

(defun three-row (board f1 f2 f3)
  (if (char= (row-major-aref board f1)
             (row-major-aref board f2)
             (row-major-aref board f3))
      (if (not (char= (row-major-aref board f1) #\-))
          (row-major-aref board f1)
        nil)
    nil))

(defun game-won (board)
  (or
   (three-row board 0 4 8)
   (three-row board 2 4 6)
   (three-row board 0 1 2)
   (three-row board 3 4 5)
   (three-row board 6 7 8) 
   (three-row board 0 3 6) 
   (three-row board 1 4 7) 
   (three-row board 2 5 8)))

(defun intro ()
  (fresh-line)
  (write-line "Welcome to the game Tic Tac Toe")
  (write-line "To play a move select a number on the grid")
  (terpri)
  (write-line "    1 2 3 ")
  (write-line " A  - - - ")
  (write-line " B  - - - ")
  (write-line " C  - - - ")
  (terpri))

(defun available-squares (board)
  (let (squares)
    (dotimes (j 3)
      (dotimes (i 3)
        (when (char= (aref board j i) #\-)
          (push (list j  i) squares))))
    squares))

(defun calculate-coordinate (string)
  (let ((row (- (char-code (aref string 0)) (char-code #\A)))
        (col (- (char-code (aref string 1)) (char-code #\0) 1)))
    (list row col)))
   
(defun prompt-move(positions &optional (prompt "Enter move: "))
  (fresh-line)
  (write-string prompt)
  (force-output)
  (clear-input)
  (let ((string (read-line)))
    (unless (and (= (length string) 2)
                 (member (aref string 0) '(#\A #\B #\C))
                 (member (aref string 1) '(#\1 #\2 #\3)))
      (return-from prompt-move 
        (prompt-move positions "Error! [A-C][1-3]: ")))
    (unless (member (calculate-coordinate string) positions :test #'equal)
      (return-from prompt-move 
        (prompt-move positions "Occupied! Enter move: ")))
    (terpri)
    (calculate-coordinate string)))

(defun print-result (result)
  (cond 
   ((null result) 
    (write-line "It's a draw!"))
   ((char= result #\X)
    (write-line "You won!"))
   ((char= result #\O)
    (write-line "I Won!"))))

(defun tic-tac-toe ()
  (let ((board (make-board)))
    (intro)
    (loop
     (set-board board (prompt-move (available-squares board)) #\X)
     (when (or (game-won board) (null (available-squares board)))
       (print-board board)
       (print-result (game-won board))
       (return-from tic-tac-toe (values)))
     (play-move board)
     (print-board board)
     (when (or (game-won board) (null (available-squares board)))
       (print-result (game-won board))
       (return-from tic-tac-toe (values))))))