
Task

Play a game of tic-tac-toe.

Ensure that legal moves are played and that a winning position is notified.


Tic-tac-toe   is also known as:

   naughts and crosses 
   tic tac toe 
   tick tack toe 
   three in a row 
   tres en rayo       and
   Xs  and  Os


See also
   MathWorld™, Tic-Tac-Toe game.
   Wikipedia tic-tac-toe.


 
(defun generate-board ()
  (loop repeat 9 collect nil))
 
(defparameter *straights* '((1 2 3) (4 5 6) (7 8 9) (1 4 7) (2 5 8) (3 6 9) (1 5 9) (3 5 7)))
(defparameter *current-player* 'x)
 
(defun get-board-elt (n board)
  (nth (1- n) board))
 
(defun legal-p (n board)
  (null (get-board-elt n board)))
 
(defun set-board-elt (n board symbol)
  (if (legal-p n board)
      (setf (nth (1- n) board) symbol)
      (progn (format t "Illegal move. Try again.~&")
	     (set-board-elt (read) board symbol))))
 
(defun list-legal-moves (board)
  (loop for i from 1 to (length board)
     when (legal-p i board)
     collect i))
 
(defun get-random-element (lst)
  (nth (random (length lst)) lst))
 
(defun multi-non-nil-eq (lst)
  (and (notany #'null lst)
       (notany #'null (mapcar #'(lambda (x) (eq (car lst) x)) lst))
       (car lst)))
 
(defun elements-of-straights (board)
  (loop for i in *straights*
     collect (loop for j from 0 to 2
	   collect (get-board-elt (nth j i) board))))
 
(defun find-winner (board)
  (car (remove-if #'null (mapcar #'multi-non-nil-eq (elements-of-straights board)))))
 
(defun set-player (mark)
  (format t "Shall a computer play as ~a? (y/n)~&" mark)
  (let ((response (read)))
    (cond ((equalp response 'y) t)
	  ((equalp response 'n) nil)
	  (t (format t "Come again?~&")
	     (set-player mark)))))
 
(defun player-move (board symbol)
  (format t "~%Player ~a, please input your move.~&" symbol)
  (set-board-elt (read) board symbol)
  (format t "~%"))
 
(defun computer-move (board symbol)
  (let ((move (get-random-element (list-legal-moves board))))
    (set-board-elt move board symbol)
    (format t "~%computer selects ~a~%~%" move)))
 
(defun computer-move-p (current-player autoplay-x-p autoplay-o-p)
  (if (eq current-player 'x)
      autoplay-x-p
      autoplay-o-p))
 
(defun perform-turn (current-player board autoplay-x-p autoplay-o-p)
  (if (computer-move-p current-player autoplay-x-p autoplay-o-p)
      (computer-move board current-player)
      (player-move board current-player)))
 
(defun switch-player ()
  (if (eq *current-player* 'x) 
      (setf *current-player* 'o)
      (setf *current-player* 'x)))
 
(defun display-board (board)
  (loop for i downfrom 2 to 0
     do (loop for j from 1 to 3
	   initially (format t "|")
	   do (format t "~a|" (or (get-board-elt (+ (* 3 i) j) board) (+ (* 3 i) j)))
	   finally (format t "~&"))))
 
(defun tic-tac-toe ()
  (setf *current-player* 'x)
  (let ((board (generate-board))
	(autoplay-x-p (set-player 'x))
	(autoplay-o-p (set-player 'o)))
    (format t "~%")
    (loop until (or (find-winner board) (null (list-legal-moves board)))
       do (display-board board)      
       do (perform-turn *current-player* board autoplay-x-p autoplay-o-p)
       do (switch-player)
       finally (if (find-winner board)
		   (format t "The winner is ~a!" (find-winner board))
		   (format t "It's a tie.")))))
 