r4726: *** empty log message ***
[reversi.git] / original / othello-orig.lisp
diff --git a/original/othello-orig.lisp b/original/othello-orig.lisp
deleted file mode 100644 (file)
index d964c83..0000000
+++ /dev/null
@@ -1,451 +0,0 @@
-;;;; -*- Mode: Lisp; Syntax: Common-Lisp -*-
-;;;; Code from Paradigms of AI Programming
-;;;; Copyright (c) 1991 Peter Norvig
-
-;;;; File othello.lisp: An othello monitor, with all strategies
-;;;; up to and including section 18.8
-
-;;; One bug fix by Alberto Segre, segre@cs.cornell.edu, March 1993.
-
-(defun mappend (fn list)
-  "Append the results of calling fn on each element of list.
-  Like mapcon, but uses append instead of nconc."
-  (apply #'append (mapcar fn list)))
-
-(defun random-elt (seq) 
-  "Pick a random element out of a sequence."
-  (elt seq (random (length seq))))
-
-(defun concat-symbol (&rest args)
-  "Concatenate symbols or strings to form an interned symbol"
-  (intern (format nil "~{~a~}" args)))
-
-(defun cross-product (fn xlist ylist)
-  "Return a list of all (fn x y) values."
-  (mappend #'(lambda (y)
-               (mapcar #'(lambda (x) (funcall fn x y))
-                       xlist))
-           ylist))
-
-(defconstant all-directions '(-11 -10 -9 -1 1 9 10 11))
-
-(defconstant empty 0 "An empty square")
-(defconstant black 1 "A black piece")
-(defconstant white 2 "A white piece")
-(defconstant outer 3 "Marks squares outside the 8x8 board")
-
-(deftype piece () `(integer ,empty ,outer))
-
-(defun name-of (piece) (char ".@O?" piece))
-
-(defun opponent (player) (if (eql player black) white black))
-
-(deftype board () '(simple-array piece (100)))
-
-(defun bref (board square) (aref board square))
-(defsetf bref (board square) (val) 
-  `(setf (aref ,board ,square) ,val))
-
-(defun copy-board (board)
-  (copy-seq board))
-
-(defconstant all-squares
-  (loop for i from 11 to 88 when (<= 1 (mod i 10) 8) collect i))
-
-(defun initial-board ()
-  "Return a board, empty except for four pieces in the middle."
-  ;; Boards are 100-element vectors, with elements 11-88 used,
-  ;; and the others marked with the sentinel OUTER.  Initially
-  ;; the 4 center squares are taken, the others empty.
-  (let ((board (make-array 100 :element-type 'piece
-                           :initial-element outer)))
-    (dolist (square all-squares)
-      (setf (bref board square) empty))
-    (setf (bref board 44) white   (bref board 45) black
-          (bref board 54) black   (bref board 55) white)
-    board))
-
-(defun count-difference (player board)
-  "Count player's pieces minus opponent's pieces."
-  (- (count player board)
-     (count (opponent player) board)))
-
-(defun valid-p (move)
-  "Valid moves are numbers in the range 11-88 that end in 1-8."
-  (and (integerp move) (<= 11 move 88) (<= 1 (mod move 10) 8)))
-
-(defun legal-p (move player board)
-  "A Legal move must be into an empty square, and it must
-  flip at least one opponent piece."
-  (and (eql (bref board move) empty)
-       (some #'(lambda (dir) (would-flip? move player board dir))
-             all-directions)))
-
-(defun make-move (move player board)
-  "Update board to reflect move by player"
-  ;; First make the move, then make any flips
-  (setf (bref board move) player)
-  (dolist (dir all-directions)
-    (make-flips move player board dir))
-  board)
-
-(defun make-flips (move player board dir)
-  "Make any flips in the given direction."
-  (let ((bracketer (would-flip? move player board dir)))
-    (when bracketer
-      (loop for c from (+ move dir) by dir until (eql c bracketer)
-            do (setf (bref board c) player)))))
-
-(defun would-flip? (move player board dir)
-  "Would this move result in any flips in this direction?
-  If so, return the square number of the bracketing piece."
-  ;; A flip occurs if, starting at the adjacent square, c, there
-  ;; is a string of at least one opponent pieces, bracketed by 
-  ;; one of player's pieces
-  (let ((c (+ move dir)))
-    (and (eql (bref board c) (opponent player))
-         (find-bracketing-piece (+ c dir) player board dir))))
-
-(defun find-bracketing-piece (square player board dir)
-  "Return the square number of the bracketing piece."
-  (cond ((eql (bref board square) player) square)
-        ((eql (bref board square) (opponent player))
-         (find-bracketing-piece (+ square dir) player board dir))
-        (t nil)))
-
-(defun next-to-play (board previous-player print)
-  "Compute the player to move next, or NIL if nobody can move."
-  (let ((opp (opponent previous-player)))
-    (cond ((any-legal-move? opp board) opp)
-          ((any-legal-move? previous-player board) 
-           (when print
-             (format t "~&~c has no moves and must pass."
-                     (name-of opp)))
-           previous-player)
-          (t nil))))
-
-(defun any-legal-move? (player board)
-  "Does player have any legal moves in this position?"
-  (some #'(lambda (move) (legal-p move player board))
-        all-squares))
-
-(defun random-strategy (player board)
-  "Make any legal move."
-  (random-elt (legal-moves player board)))
-
-(defun legal-moves (player board)
-  "Returns a list of legal moves for player"
-  ;;*** fix, segre, 3/30/93.  Was remove-if, which can share with all-squares.
-  (loop for move in all-squares
-       when (legal-p move player board) collect move))
-
-(defun maximize-difference (player board)
-  "A strategy that maximizes the difference in pieces."
-  (funcall (maximizer #'count-difference) player board))
-
-(defun maximizer (eval-fn)
-  "Return a strategy that will consider every legal move,
-  apply EVAL-FN to each resulting board, and choose 
-  the move for which EVAL-FN returns the best score.
-  FN takes two arguments: the player-to-move and board"
-  #'(lambda (player board)
-      (let* ((moves (legal-moves player board))
-             (scores (mapcar #'(lambda (move)
-                                (funcall
-                                 eval-fn
-                                 player
-                                 (make-move move player
-                                            (copy-board board))))
-                             moves))
-             (best  (apply #'max scores)))
-        (elt moves (position best scores)))))
-
-(defparameter *weights*
-  '#(0   0   0  0  0  0  0   0   0 0
-     0 120 -20 20  5  5 20 -20 120 0
-     0 -20 -40 -5 -5 -5 -5 -40 -20 0
-     0  20  -5 15  3  3 15  -5  20 0
-     0   5  -5  3  3  3  3  -5   5 0
-     0   5  -5  3  3  3  3  -5   5 0
-     0  20  -5 15  3  3 15  -5  20 0
-     0 -20 -40 -5 -5 -5 -5 -40 -20 0
-     0 120 -20 20  5  5 20 -20 120 0
-     0   0   0  0  0  0  0   0   0 0))
-
-(defun weighted-squares (player board)
-  "Sum of the weights of player's squares minus opponent's."
-  (let ((opp (opponent player)))
-    (loop for i in all-squares
-          when (eql (bref board i) player) 
-          sum (aref *weights* i)
-          when (eql (bref board i) opp)
-          sum (- (aref *weights* i)))))
-
-(defconstant winning-value most-positive-fixnum)
-(defconstant losing-value  most-negative-fixnum)
-
-(defun final-value (player board)
-  "Is this a win, loss, or draw for player?"
-  (case (signum (count-difference player board))
-    (-1 losing-value)
-    ( 0 0)
-    (+1 winning-value)))
-
-(defun minimax (player board ply eval-fn)
-  "Find the best move, for PLAYER, according to EVAL-FN,
-  searching PLY levels deep and backing up values."
-  (if (= ply 0)
-      (funcall eval-fn player board)
-      (let ((moves (legal-moves player board)))
-        (if (null moves)
-            (if (any-legal-move? (opponent player) board)
-                (- (minimax (opponent player) board
-                            (- ply 1) eval-fn))
-                (final-value player board))
-            (let ((best-move nil)
-                  (best-val nil))
-              (dolist (move moves)
-                (let* ((board2 (make-move move player
-                                          (copy-board board)))
-                       (val (- (minimax
-                                 (opponent player) board2
-                                 (- ply 1) eval-fn))))
-                  (when (or (null best-val)
-                            (> val best-val))
-                    (setf best-val val)
-                    (setf best-move move))))
-              (values best-val best-move))))))
-
-(defun minimax-searcher (ply eval-fn)
-  "A strategy that searches PLY levels and then uses EVAL-FN."
-  #'(lambda (player board)
-      (multiple-value-bind (value move)
-          (minimax player board ply eval-fn) 
-        (declare (ignore value))
-        move)))
-
-(defun alpha-beta (player board achievable cutoff ply eval-fn)
-  "Find the best move, for PLAYER, according to EVAL-FN,
-  searching PLY levels deep and backing up values,
-  using cutoffs whenever possible."
-  (if (= ply 0)
-      (funcall eval-fn player board)
-      (let ((moves (legal-moves player board)))
-        (if (null moves)
-            (if (any-legal-move? (opponent player) board)
-                (- (alpha-beta (opponent player) board
-                               (- cutoff) (- achievable)
-                               (- ply 1) eval-fn))
-                (final-value player board))
-            (let ((best-move (first moves)))
-              (loop for move in moves do
-                (let* ((board2 (make-move move player
-                                          (copy-board board)))
-                       (val (- (alpha-beta
-                                 (opponent player) board2
-                                 (- cutoff) (- achievable)
-                                 (- ply 1) eval-fn))))
-                  (when (> val achievable)
-                    (setf achievable val)
-                    (setf best-move move)))
-                until (>= achievable cutoff))
-              (values achievable best-move))))))
-
-(defun alpha-beta-searcher (depth eval-fn)
-  "A strategy that searches to DEPTH and then uses EVAL-FN."
-  #'(lambda (player board)
-      (multiple-value-bind (value move)
-          (alpha-beta player board losing-value winning-value
-                      depth eval-fn) 
-        (declare (ignore value))
-        move)))
-
-(defun modified-weighted-squares (player board)
-  "Like WEIGHTED-SQUARES, but don't take off for moving
-  near an occupied corner."
-  (let ((w (weighted-squares player board)))
-    (dolist (corner '(11 18 81 88))
-      (when (not (eql (bref board corner) empty))
-        (dolist (c (neighbors corner))
-          (when (not (eql (bref board c) empty))
-            (incf w (* (- 5 (aref *weights* c))
-                       (if (eql (bref board c) player)
-                           +1 -1)))))))
-    w))
-
-(let ((neighbor-table (make-array 100 :initial-element nil)))
-  ;; Initialize the neighbor table
-  (dolist (square all-squares)
-    (dolist (dir all-directions)
-      (if (valid-p (+ square dir))
-          (push (+ square dir)
-                (aref neighbor-table square)))))
-
-  (defun neighbors (square)
-    "Return a list of all squares adjacent to a square."
-    (aref neighbor-table square)))
-
-(let ((square-names 
-        (cross-product #'concat-symbol
-                       '(? a b c d e f g h ?)
-                       '(? 1 2 3 4 5 6 7 8 ?))))
-
-  (defun h8->88 (str)
-    "Convert from alphanumeric to numeric square notation."
-    (or (position (string str) square-names :test #'string-equal)
-        str))
-
-  (defun 88->h8 (num)
-    "Convert from numeric to alphanumeric square notation."
-    (if (valid-p num)
-        (elt square-names num)
-        num)))
-
-(defun human (player board)
-  "A human player for the game of Othello"
-  (format t "~&~c to move ~a: " (name-of player)
-          (mapcar #'88->h8 (legal-moves player board)))
-  (h8->88 (read)))
-
-(defvar *move-number* 1 "The number of the move to be played")
-
-(defun othello (bl-strategy wh-strategy 
-                &optional (print t) (minutes 30))
-  "Play a game of othello.  Return the score, where a positive
-  difference means black, the first player, wins."
-  (let ((board (initial-board))
-        (clock (make-array (+ 1 (max black white))
-                           :initial-element 
-                           (* minutes 60 
-                              internal-time-units-per-second))))
-    (catch 'game-over
-      (loop for *move-number* from 1
-            for player = black then (next-to-play board player print)
-            for strategy = (if (eql player black) 
-                               bl-strategy
-                               wh-strategy)
-            until (null player)
-            do (get-move strategy player board print clock))
-      (when print
-        (format t "~&The game is over.  Final result:")
-        (print-board board clock))
-      (count-difference black board))))
-
-(defvar *clock* (make-array 3) "A copy of the game clock")
-(defvar *board* (initial-board) "A copy of the game board")
-
-(defun get-move (strategy player board print clock)
-  "Call the player's strategy function to get a move.
-  Keep calling until a legal move is made."
-  ;; Note we don't pass the strategy function the REAL board.
-  ;; If we did, it could cheat by changing the pieces on the board.
-  (when print (print-board board clock))
-  (replace *clock* clock)
-  (let* ((t0 (get-internal-real-time))
-         (move (funcall strategy player (replace *board* board)))
-         (t1 (get-internal-real-time)))
-    (decf (elt clock player) (- t1 t0))
-    (cond
-      ((< (elt clock player) 0)
-       (format t "~&~c has no time left and forfeits."
-               (name-of player))
-       (THROW 'game-over (if (eql player black) -64 64)))
-      ((eq move 'resign)
-       (THROW 'game-over (if (eql player black) -64 64)))
-      ((and (valid-p move) (legal-p move player board))
-       (when print
-         (format t "~&~c moves to ~a." 
-                 (name-of player) (88->h8 move)))
-       (make-move move player board))
-      (t (warn "Illegal move: ~a" (88->h8 move))
-         (get-move strategy player board print clock)))))
-
-(defun print-board (&optional (board *board*) clock)
-  "Print a board, along with some statistics."
-  ;; First print the header and the current score
-  (format t "~2&    a b c d e f g h   [~c=~2a ~c=~2a (~@d)]"
-          (name-of black) (count black board)
-          (name-of white) (count white board)
-          (count-difference black board))
-  ;; Print the board itself
-  (loop for row from 1 to 8 do
-        (format t "~&  ~d " row)
-        (loop for col from 1 to 8
-              for piece = (bref board (+ col (* 10 row)))
-              do (format t "~c " (name-of piece))))
-  ;; Finally print the time remaining for each player
-  (when clock
-    (format t "  [~c=~a ~c=~a]~2&"
-            (name-of black) (time-string (elt clock black))
-            (name-of white) (time-string (elt clock white)))))
-
-(defun time-string (time)
-  "Return a string representing this internal time in min:secs."
-  (multiple-value-bind (min sec)
-      (floor (round time internal-time-units-per-second) 60)
-    (format nil "~2d:~2,'0d" min sec)))
-
-(defun random-othello-series (strategy1 strategy2 
-                              n-pairs &optional (n-random 10))
-  "Play a series of 2*n games, starting from a random position."
-  (othello-series
-    (switch-strategies #'random-strategy n-random strategy1)
-    (switch-strategies #'random-strategy n-random strategy2)
-    n-pairs))
-
-(defun switch-strategies (strategy1 m strategy2)
-  "Make a new strategy that plays strategy1 for m moves,
-  then plays according to strategy2."
-  #'(lambda (player board)
-      (funcall (if (<= *move-number* m) strategy1 strategy2)
-               player board)))
-
-(defun othello-series (strategy1 strategy2 n-pairs)
-  "Play a series of 2*n-pairs games, swapping sides."
-  (let ((scores
-          (loop repeat n-pairs
-             for random-state = (make-random-state)
-             collect (othello strategy1 strategy2 nil)
-             do (setf *random-state* random-state)
-             collect (- (othello strategy2 strategy1 nil)))))
-    ;; Return the number of wins (1/2 for a tie),
-    ;; the total of the point differences, and the
-    ;; scores themselves, all from strategy1's point of view.
-    (values (+ (count-if #'plusp scores)
-               (/ (count-if #'zerop scores) 2))
-            (apply #'+ scores)
-            scores)))
-
-(defun round-robin (strategies n-pairs &optional
-                    (n-random 10) (names strategies))
-  "Play a tournament among the strategies.
-  N-PAIRS = games each strategy plays as each color against
-  each opponent.  So with N strategies, a total of
-  N*(N-1)*N-PAIRS games are played."
-  (let* ((N (length strategies))
-         (totals (make-array N :initial-element 0))
-         (scores (make-array (list N N)
-                             :initial-element 0)))
-    ;; Play the games
-    (dotimes (i N)
-      (loop for j from (+ i 1) to (- N 1) do 
-          (let* ((wins (random-othello-series
-                         (elt strategies i)
-                         (elt strategies j)
-                         n-pairs n-random))
-                 (losses (- (* 2 n-pairs) wins)))
-            (incf (aref scores i j) wins)
-            (incf (aref scores j i) losses)
-            (incf (aref totals i) wins)
-            (incf (aref totals j) losses))))
-    ;; Print the results
-    (dotimes (i N)
-      (format t "~&~a~20T ~4f: " (elt names i) (elt totals i))
-      (dotimes (j N)
-        (format t "~4f " (if (= i j) '---
-                             (aref scores i j)))))))
-
-(defun mobility (player board)
-  "The number of moves a player has."
-  (length (legal-moves player board)))
-