Guest

Untitled

By: a guest on Jan 27th, 2011  |  syntax: Lisp  |  size: 11.55 KB  |  hits: 90  |  expires: Never
download  |  raw  |  embed  |  report abuse
This paste has a previous version, view the difference. Copied
  1. ;; From http://github.com/redline6561/Tic-Tac-Toe/
  2. ;; Meant to be run by sbcl --script tic-tac-toe.lisp
  3.  
  4. ; Who wants to run a script and see compiler style notes?
  5. (declaim (sb-ext:muffle-conditions style-warning))
  6.  
  7. (defpackage :tic-tac-toe
  8.   (:use :cl))
  9.  
  10. (in-package :tic-tac-toe)
  11.  
  12. (defconstant +win+ (expt 2 28)
  13.   "A semi-arbitrary numeric value denoting the best outcome.")
  14. (defconstant +lose+ (- (expt 2 28))
  15.   "A semi-arbitrary numeric value denoting the worst outcome.")
  16.  
  17. (defclass tic-tac-toe ()
  18.   ((score-human :initform 0
  19.                 :accessor score-human)
  20.    (score-ai :initform 0
  21.              :accessor score-ai)
  22.    (players :initform '()
  23.             :accessor players)
  24.    (board :initform (make-array '(3 3) :initial-element #\Space
  25.                                 :element-type 'standard-char)
  26.           :accessor board)))
  27.  
  28. (defparameter *game-session* (make-instance 'tic-tac-toe)
  29.   "A global variable holding all our lovely game state.")
  30.  
  31. (defparameter *win-conditions* '((0 1 2)
  32.                                  (3 4 5)
  33.                                  (6 7 8)
  34.                                  (0 3 6)
  35.                                  (1 4 7)
  36.                                  (2 5 8)
  37.                                  (0 4 8)
  38.                                  (2 4 6))
  39.   "This is an enumeration of all win conditions.
  40. Specifically, A list of lists each specifying a row
  41. of three Xs or Os constituting a win.")
  42.  
  43. (defun valid-moves (board)
  44.   "Iterate across the board finding all blank spaces
  45. (i.e. valid moves) and returning them as a numbered list of array indices."
  46.   (let ((valid-moves nil)
  47.         (move-count 0))
  48.     ; Note that we could generalize to an N-by-N board by
  49.     ; using a destructuring-bind on the array-dimensions.
  50.     ; See: http://quotenil.com/git/?p=micmac.git;a=summary
  51.     ; especially test/test-game-theory.lisp
  52.     (dotimes (x 3)
  53.       (dotimes (y 3)
  54.         (when (char= #\Space (aref board x y))
  55.           (push (list (incf move-count) x y) valid-moves))))
  56.     valid-moves))
  57.  
  58. (defun print-board (board &key moves)
  59.   "Print each row of the board inside square brackets.
  60. If MOVES is T, blank spaces (i.e. available moves) will
  61. be numbered starting from 1."
  62.   (let ((move-count 0))
  63.     (flet ((print-row (row-num &key moves)
  64.              (with-output-to-string (result)
  65.                (loop for i in '(0 1 2) do
  66.                     (if (and moves
  67.                              (char= #\Space (aref board row-num i)))
  68.                         (format result " ~A" (incf move-count))
  69.                         (format result " ~A" (aref board row-num i)))))))
  70.       (when moves
  71.         (format t "Your potential moves are:~%"))
  72.       (format t "[~A ]~%[~A ]~%[~A ]~%"
  73.               (print-row 0 :moves moves)
  74.               (print-row 1 :moves moves)
  75.               (print-row 2 :moves moves)))))
  76.  
  77. (defun print-help ()
  78.   "Display instructions for playing Tic-Tac-Toe."
  79.   (format t "~%Welcome to the glorious world of Tic-Tac-Toe.
  80. If you've never tic'd or tac'd before the rules are simple:
  81. There is a 3 by 3 game board and each player takes turns
  82. filling the 9 empty spaces with their sign, an X or an O.
  83. Whoever gets 3 in a row (vertical, horizontal or diagonal)
  84. first wins! Lectures on Game Trees and Combinatorics
  85. will follow with milk and cookies.~%~%")
  86.   (format t "This is the board with the potential moves numbered...~%")
  87.   (print-board (board *game-session*) :moves t))
  88.  
  89. (defun make-move (board move letter &key pure)
  90.   "Given a BOARD, MOVE and LETTER, return a BOARD with the specified location
  91. set to LETTER. If PURE is T, ensure that the original board is not modified."
  92.   (if pure
  93.       (let ((arr (make-array '(3 3) :element-type 'standard-char)))
  94.         (loop for i from 0 upto 8 do
  95.              (setf (row-major-aref arr i)
  96.                    (row-major-aref board i)))
  97.         (setf (aref arr (second move) (third move)) letter)
  98.         arr)
  99.       (setf (aref board (second move) (third move)) letter)))
  100.  
  101. ;; Rather than explicitly defining generic functions for all of these,
  102. ;; I'll have this handler-bind muffle the compiler notes for clean terminal
  103. ;; output. I also violate traditional indentation rules here.
  104. ;; The handler-bind form is closed just below the last defmethod.
  105. ;;
  106. ;; For references, see:
  107. ;; http://bugs.launchpad.net/sbcl/+bug/671523 (esp. Attila Lendvai's comment)
  108. ;; http://sbcl.sourceforge.net/manual/Controlling-Verbosity.html
  109. (handler-bind ((sb-ext:implicit-generic-function-warning #'muffle-warning))
  110.  
  111. (defmethod print-score ((game tic-tac-toe))
  112.   "Print the score of the computer and player in GAME."
  113.   (format t "The score is... Scary Robots: ~A   Puny Humans: ~A~%"
  114.           (score-ai game) (score-human game)))
  115.  
  116. (defmethod reset-board ((game tic-tac-toe))
  117.   "Reset the board for a new game."
  118.   (setf (board game) (make-array '(3 3) :initial-element #\Space
  119.                                  :element-type 'standard-char)))
  120.  
  121. (defmethod take-turns ((game tic-tac-toe))
  122.   "Ask the player if they would like to go first. Whoever goes first gets
  123. Xs and the other player gets Os. Once a decision is made, loop back and
  124. forth between the competitors until the game is over."
  125.   (let ((human-p (yes-or-no-p "X moves first. Would you like to play X?")))
  126.     (if human-p
  127.         (setf (players game) '(:human :ai))
  128.         (setf (players game) '(:ai :human)))
  129.     (catch 'game-over
  130.       (loop
  131.          (take-turn game #\X human-p) ; X goes first...
  132.          (take-turn game #\O (not human-p))))))
  133.  
  134. (defmethod take-turn ((game tic-tac-toe) letter human-p)
  135.   "If it is the computer's turn, compute the \"best\" move with SELECT-NEGAMAX,
  136. make the move and inform the user. Otherwise, print the options for the player
  137. and get their selection, then set that location to LETTER. If the game is ended
  138. by this move, display the results of the game and return from TAKE-TURNS."
  139.   (let* ((board (board game))
  140.          (moves (valid-moves board))
  141.          (players (players game)))
  142.     (if human-p
  143.         (let ((limit (length moves))
  144.               (input nil))
  145.           (print-board board :moves t)
  146.           (setf input (get-numeric-input "Please select a move" limit))
  147.           (make-move board (find-if (lambda (x)
  148.                                       (= x input)) moves :key #'car)
  149.                      letter))
  150.         (let ((move (nth-value 1 (select-negamax board letter players
  151.                                                  +lose+ +win+ 1))))
  152.           (format t "Computer moves:~%")
  153.           (make-move board move letter)
  154.           (print-board board)))
  155.     (let ((results (game-over-p (board game) letter players)))
  156.       (when results
  157.         (display-results results game)
  158.         (throw 'game-over nil)))))
  159. ) ; Closes the handler-bind muffling implicit-generic warnings...
  160.  
  161. (defun select-negamax (board letter players alpha beta color)
  162.   "Check to see if the game is over, if so return a value based on who the
  163. winner is. Otherwise, for each valid move for BOARD, run SELECT-NEGAMAX on
  164. a new board where that move has been made, returning both the highest ALPHA
  165. found and the corresponding move."
  166.   ; Largely adapted from http://en.wikipedia.org/wiki/Negamax
  167.   (let* ((opponent (opponent letter))
  168.          (moves (valid-moves board))
  169.          (winner-p (game-over-p board (if (null moves) opponent letter) players))
  170.          (best-move nil))
  171.     (if winner-p
  172.         (setf alpha (* color (board-value winner-p)))
  173.         (dolist (move moves)
  174.           (let* ((board* (make-move board move letter :pure t))
  175.                  (val (- (select-negamax board* opponent players
  176.                                          (- beta) (- alpha) (- color)))))
  177.             (when (> val alpha)
  178.               (setf best-move move
  179.                     alpha val)))))
  180.     (values alpha best-move)))
  181.  
  182. (defun board-value (winner)
  183.   "Given a WINNER compute the value of the board."
  184.   (ecase winner
  185.     (:draw 0)
  186.     (:ai +win+)
  187.     (:human +lose+)))
  188.  
  189. (defun get-numeric-input (prompt upper-limit)
  190.   "Get numeric input from the user, reprompting them if they
  191. provide junk input which contains non-numerics or is below 1
  192. or above UPPER-LIMIT."
  193.   (let ((input nil)
  194.         (range-str
  195.          (format nil "You must enter a number between 1 and ~A" upper-limit)))
  196.     (flet ((get-input (message)
  197.              (format t "~A: " message)
  198.              (force-output)
  199.              (setf input (parse-integer (read-line) :junk-allowed t))))
  200.       (get-input prompt)
  201.       (loop until (and input
  202.                        (<= input upper-limit)
  203.                        (> input 0))
  204.          do (get-input range-str))
  205.       input)))
  206.  
  207. (defun opponent (letter)
  208.   "Return the opponent of LETTER."
  209.   (if (char= #\X letter)
  210.       #\O
  211.       #\X))
  212.  
  213. (defun game-over-p (board letter players)
  214.   "Check the game BOARD to see if a winner has emerged by
  215. seeing if the board is full and then iterating through the
  216. known *win-conditions*. Return NIL if the game isn't over,
  217. otherwise return the winner. Note that people might expect
  218. a *-p function to return only T or NIL...so don't export it."
  219.   (let ((player (if (char= #\X letter)
  220.                     (first players)
  221.                     (second players))))
  222.     (loop for condition in *win-conditions* do
  223.          (when (three-in-a-row-p letter condition board)
  224.            (return-from game-over-p player)))
  225.     (when (full-board-p board)
  226.       (return-from game-over-p :draw))))
  227.  
  228. (defun three-in-a-row-p (letter condition board &optional possible-p)
  229.   "Check if LETTER occurs three times in a row on BOARD as specified
  230. by CONDITION or, if POSSIBLE-P is T, whether LETTER is blocked from
  231. achieving the CONDITION. Returns T or NIL."
  232.   (let ((opponent (opponent letter)))
  233.     (if possible-p
  234.         (loop for index in condition
  235.            never (char= opponent (row-major-aref board index)))
  236.         (loop for index in condition
  237.            always (char= letter (row-major-aref board index))))))
  238.  
  239. (defun full-board-p (board)
  240.   "Check if any blank spaces remain on BOARD.
  241. If so, return NIL, otherwise return T."
  242.   (loop for index from 0 upto 8
  243.         never (char= #\Space (row-major-aref board index))))
  244.  
  245. (defun display-results (winner game)
  246.   "Increment the score for the winning player or
  247. do nothing in the case of a draw and inform the user
  248. of the game's outcome."
  249.   (ecase winner
  250.     (:human
  251.      (incf (score-human game))
  252.      (format t "The human wins!~%"))
  253.     (:ai
  254.      (incf (score-ai game))
  255.      (format t "The AI wins!~%"))
  256.     (:draw
  257.      (format t "No winner!~%"))))
  258.  
  259. (defun main ()
  260.   "Print the instructions for playing Tic-Tac-Toe.
  261. Afterwards, continually prompt the player to play and
  262. start a new game each time they respond affirmatively."
  263.   (print-help)
  264.   (flet ((new-game? ()
  265.            (reset-board *game-session*)
  266.            (yes-or-no-p "Would you like to play Tic-Tac-Toe?")))
  267.     (loop until (not (new-game?)) do
  268.          (take-turns *game-session*)
  269.          (print-score *game-session*)))
  270.   (format t "~%Thanks for playing!~%~%")
  271.   (sb-ext:quit))
  272.  
  273. (defun the-kris-bugs ()
  274.   (let ((arr #2A((#\Space #\X #\X) (#\X #\O #\O) (#\Space #\O #\X)))
  275.         (players '(:human :ai)))
  276.     (multiple-value-bind (value move)
  277.         (select-negamax arr #\O players +lose+ +win+ 1)
  278.       (format t "Value: ~A~%Move: ~A~%" value move)
  279.       (make-move arr '(2 2 0) #\O)
  280.       (make-move arr '(1 0 0) #\X)
  281.       (format t "The Kris Bugs:~%Right Move? ~A~%Right Winner? ~A~%"
  282.               (equal '(1 0 0) move)
  283.               (eql  (game-over-p arr #\X players) :human))
  284.       (print-board arr))))
  285.  
  286. ;(trace select-negamax game-over-p)
  287.  
  288. (main)
  289. ;(the-kris-bugs)