r3182: *** empty log message ***
[reversi.git] / strategies.lisp
1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10; Package: reversi -*-
2 ;;;;***************************************************************************
3 ;;;;
4 ;;;; FILE IDENTIFICATION
5 ;;;; 
6 ;;;;  Name:           strategies.lisp
7 ;;;;  Purpose:        Strategy routines for reversi
8 ;;;;  Programer:      Kevin Rosenberg based on code by Peter Norvig
9 ;;;;  Date Started:   1 Nov 2001
10 ;;;;
11 ;;;; $Id: strategies.lisp,v 1.2 2002/10/25 09:23:39 kevin Exp $
12 ;;;;
13 ;;;; This file is Copyright (c) 2001-2002 by Kevin M. Rosenberg 
14 ;;;; and Copyright (c) 1998-2002 Peter Norvig
15 ;;;;
16 ;;;; Reversi users are granted the rights to distribute and use this software
17 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
18 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
19 ;;;;***************************************************************************
20
21 (in-package :reversi)
22 (declaim (optimize (safety 1) (debug 3) (speed 3) (compilation-speed 0)))
23
24
25 (defun random-strategy (player board)
26   "Make any legal move."
27   (declare (type player player)
28            (type board board))
29   (random-elt (legal-moves player board)))
30
31
32 (defun maximize-difference (player board)
33   "A strategy that maximizes the difference in pieces."
34   (declare (type player player)
35            (type board board))
36   (funcall (maximizer #'count-difference) player board))
37
38 (defun maximizer (eval-fn)
39   "Return a strategy that will consider every legal move,
40   apply EVAL-FN to each resulting board, and choose 
41   the move for which EVAL-FN returns the best score.
42   FN takes two arguments: the player-to-move and board"
43   #'(lambda (player board)
44       (declare (type player player)
45                (type board board))
46       (let* ((moves (legal-moves player board))
47              (scores (mapcar #'(lambda (move)
48                                  (funcall
49                                   eval-fn
50                                   player
51                                   (make-move move player
52                                              (copy-board board))))
53                              moves))
54              (best  (apply #'max scores)))
55         (declare (fixnum moves best))
56         (elt moves (position best scores)))))
57
58 (eval-when (:compile-toplevel :load-toplevel :execute)
59   (defparameter *weights*
60       (make-array 100 :element-type 'fixnum 
61                   :fill-pointer nil :adjustable nil
62                   :initial-contents
63                   '(0   0   0  0  0  0  0   0  0 0
64                     0 120 -20 20  5  5 20 -20 120 0
65                     0 -20 -40 -5 -5 -5 -5 -40 -20 0
66                     0  20  -5 15  3  3 15  -5  20 0
67                     0   5  -5  3  3  3  3  -5   5 0
68                     0   5  -5  3  3  3  3  -5   5 0
69                     0  20  -5 15  3  3 15  -5  20 0
70                     0 -20 -40 -5 -5 -5 -5 -40 -20 0
71                     0 120 -20 20  5  5 20 -20 120 0
72                     0   0   0  0  0  0  0   0   0 0)))
73   (declaim (type (simple-array fixnum (100)) *weights*))
74 )
75
76 (eval-when (:compile-toplevel :load-toplevel :execute)
77   (setq all-squares 
78     (sort (loop for i from 11 to 88 
79               when (<= 1 (mod i 10) 8) collect i)
80           #'> :key #'(lambda (sq) (elt *weights* sq)))))
81
82
83 (defun weighted-squares (player board)
84   "Sum of the weights of player's squares minus opponent's."
85   (declare (type player player)
86            (type board board))
87   (let ((opp (opponent player)))
88     (loop for i in all-squares
89           when (= (bref board i) player) 
90           sum (aref *weights* i)
91           when (= (bref board i) opp)
92           sum (- (aref *weights* i)))))
93
94 (defconstant winning-value (- most-positive-fixnum 70))
95 (defconstant losing-value  (+ most-negative-fixnum 70))
96
97 (defun final-value (player board)
98   "Is this a win, loss, or draw for player?"
99   (declare (type player player)
100            (type board board))
101   (case (signum (count-difference player board))
102     (-1 losing-value)
103     ( 0 0)
104     (+1 winning-value)))
105
106 (defun final-value-weighted (player board)
107   "Is this a win, loss, or draw for player?"
108   (declare (type player player)
109            (type board board))
110   (let ((diff (count-difference player board)))
111     (case (signum diff)
112       (-1 (+ losing-value diff))
113       ( 0 0)
114       (+1 (+ winning-value diff)))))
115
116 (defun minimax (player board ply eval-fn)
117   "Find the best move, for PLAYER, according to EVAL-FN,
118   searching PLY levels deep and backing up values."
119   (declare (type player player)
120            (type board board)
121            (fixnum ply))
122   (if (= ply 0)
123       (funcall eval-fn player board)
124       (let ((moves (legal-moves player board)))
125         (if (null moves)
126             (if (any-legal-move? (opponent player) board)
127                 (- (minimax (opponent player) board
128                             (- ply 1) eval-fn))
129                 (final-value player board))
130             (let ((best-move nil)
131                   (best-val nil))
132               (dolist (move moves)
133                 (let* ((board2 (make-move move player
134                                           (copy-board board)))
135                        (val (- (minimax
136                                  (opponent player) board2
137                                  (- ply 1) eval-fn))))
138                   (when (or (null best-val)
139                             (> val best-val))
140                     (setf best-val val)
141                     (setf best-move move))))
142               (values best-val best-move))))))
143
144 (defun minimax-searcher (ply eval-fn)
145   "A strategy that searches PLY levels and then uses EVAL-FN."
146   #'(lambda (player board)
147       (declare (type player player)
148                (type board board))
149       (multiple-value-bind (value move)
150           (minimax player board ply eval-fn) 
151         (declare (ignore value))
152         move)))
153
154 (defun alpha-beta (player board achievable cutoff ply eval-fn)
155   "Find the best move, for PLAYER, according to EVAL-FN,
156   searching PLY levels deep and backing up values,
157   using cutoffs whenever possible."
158   (declare (type player player)
159            (type board board)
160            (fixnum achievable cutoff ply))
161   (if (= ply 0)
162       (funcall eval-fn player board)
163       (let ((moves (legal-moves player board)))
164         (if (null moves)
165             (if (any-legal-move? (opponent player) board)
166                 (- (alpha-beta (opponent player) board
167                                (- cutoff) (- achievable)
168                                (- ply 1) eval-fn))
169                 (final-value player board))
170           (let ((best-move (first moves)))
171             (declare (type move best-move))
172             (loop for move in moves do
173                   (let* ((board2 (make-move move player
174                                             (copy-board board)))
175                          (val (- (alpha-beta
176                                  (opponent player) board2
177                                  (- cutoff) (- achievable)
178                                  (- ply 1) eval-fn))))
179                   (when (> val achievable)
180                     (setf achievable val)
181                     (setf best-move move)))
182                 until (>= achievable cutoff))
183               (values achievable best-move))))))
184
185 (defun alpha-beta-searcher (depth eval-fn)
186   "A strategy that searches to DEPTH and then uses EVAL-FN."
187   (declare (fixnum depth))
188   #'(lambda (player board)
189       (declare (type board board)
190                (type player player))
191       (multiple-value-bind (value move)
192           (alpha-beta player board losing-value winning-value
193                       depth eval-fn) 
194         (declare (ignore value))
195         move)))
196
197 (defun modified-weighted-squares (player board)
198   "Like WEIGHTED-SQUARES, but don't take off for moving
199   near an occupied corner."
200   (declare (type player player)
201            (type board board))
202   (let ((w (weighted-squares player board)))
203     (declare (fixnum w))
204     (dolist (corner '(11 18 81 88))
205       (declare (type square corner))
206       (when (not (= (bref board corner) empty))
207         (dolist (c (neighbors corner))
208           (declare (type square c))
209           (when (not (= (bref board c) empty))
210             (incf w (* (- 5 (aref *weights* c))
211                        (if (= (bref board c) player)
212                            +1 -1)))))))
213     w))
214
215 (eval-when (:compile-toplevel :load-toplevel :execute)
216 (let ((neighbor-table (make-array 100 :initial-element nil)))
217   ;; Initialize the neighbor table
218   (dolist (square all-squares)
219     (declare (type square square))
220     (dolist (dir +all-directions+)
221       (declare (type dir dir))
222       (if (valid-p (+ square dir))
223           (push (+ square dir)
224                 (aref neighbor-table square)))))
225
226   (defun neighbors (square)
227     "Return a list of all squares adjacent to a square."
228     (aref neighbor-table square))))
229
230
231 (defun mobility-simple (player board)
232   "The number of moves a player has."
233   (length (legal-moves player board)))
234
235
236
237 (defstruct (node) 
238   (square nil :type square)
239   (board nil :type board)
240   (value nil :type integer))
241
242 (defun alpha-beta-searcher2 (depth eval-fn)
243   "Return a strategy that does A-B search with sorted moves."
244   #'(lambda (player board)
245       (declare (type player player)
246                (type board board))
247       (multiple-value-bind (value node)
248           (alpha-beta2
249             player (make-node :board board
250                               :value (funcall eval-fn player board))
251             losing-value winning-value depth eval-fn)
252         (declare (ignore value))
253         (node-square node))))
254
255 (defun alpha-beta2 (player node achievable cutoff ply eval-fn)
256   "A-B search, sorting moves by eval-fn"
257   ;; Returns two values: achievable-value and move-to-make
258   (if (= ply 0)
259       (values (node-value node) node)
260       (let* ((board (node-board node))
261              (nodes (legal-nodes player board eval-fn)))
262         (if (null nodes)
263             (if (any-legal-move? (opponent player) board)
264                 (values (- (alpha-beta2 (opponent player)
265                                         (negate-value node)
266                                         (- cutoff) (- achievable)
267                                         (- ply 1) eval-fn))
268                         nil)
269                 (values (final-value player board) nil))
270           (let ((best-node (first nodes)))
271               (loop for move in nodes
272                     for val = (- (alpha-beta2
273                                    (opponent player)
274                                    (negate-value move)
275                                    (- cutoff) (- achievable)
276                                    (- ply 1) eval-fn))
277                     do (when (> val achievable)
278                          (setf achievable val)
279                          (setf best-node move))
280                     until (>= achievable cutoff))
281               (values achievable best-node))))))
282
283 (defun negate-value (node)
284   "Set the value of a node to its negative."
285   (setf (node-value node) (- (node-value node)))
286   node)
287
288 (defun legal-nodes (player board eval-fn)
289   "Return a list of legal moves, each one packed into a node."
290   (let ((moves (legal-moves player board)))
291     (sort (map-into
292             moves
293             #'(lambda (move)
294                 (let ((new-board (make-move move player
295                                             (copy-board board))))
296                   (make-node
297                     :square move :board new-board
298                     :value (funcall eval-fn player new-board))))
299             moves)
300           #'> :key #'node-value)))
301
302 (defun alpha-beta3 (player board achievable cutoff ply eval-fn
303                     killer)
304   (declare (type board board)
305            (type player player)
306            (type fixnum achievable cutoff ply))
307   "A-B search, putting killer move first."
308   (if (= ply 0)
309       (funcall eval-fn player board)
310       (let ((moves (put-first killer (legal-moves player board))))
311         (if (null moves)
312             (if (any-legal-move? (opponent player) board)
313                 (- (alpha-beta3 (opponent player) board
314                                 (- cutoff) (- achievable)
315                                 (- ply 1) eval-fn nil))
316                 (final-value player board))
317             (let ((best-move (first moves))
318                   (new-board (svref *ply-boards* ply))
319                   (killer2 nil)
320                   (killer2-val winning-value))
321               (declare (type move best-move)
322                        (type board new-board)
323                        (type fixnum killer2-val))
324               (loop for move in moves
325                   do (multiple-value-bind (val reply)
326                        (alpha-beta3
327                         (opponent player)
328                         (make-move move player
329                                    (replace-board new-board board))
330                         (- cutoff) (- achievable)
331                         (- ply 1) eval-fn killer2)
332                        (setf val (- val))
333                        (when (> val achievable)
334                          (setq achievable val)
335                          (setq best-move move))
336                        (when (and reply (< val killer2-val))
337                          (setq killer2 reply)
338                          (setq killer2-val val)))
339                   until (>= achievable cutoff))
340               (values achievable best-move))))))
341
342 (defun alpha-beta3w (player board achievable cutoff ply eval-fn
343                     killer)
344   (declare (type board board)
345            (type player player)
346            (type fixnum achievable cutoff ply)
347            (type move killer))
348   "A-B search, putting killer move first."
349   (if (= ply 0)
350       (funcall eval-fn player board)
351       (let ((moves (put-first killer (legal-moves player board))))
352         (if (null moves)
353             (if (any-legal-move? (opponent player) board)
354                 (- (alpha-beta3 (opponent player) board
355                                 (- cutoff) (- achievable)
356                                 (- ply 1) eval-fn nil))
357                 (final-value-weighted player board))
358             (let ((best-move (first moves))
359                   (new-board (svref *ply-boards* ply))
360                   (killer2 nil)
361                   (killer2-val winning-value))
362               (declare (type move best-move)
363                        (type board new-board)
364                        (type fixnum killer2-val))
365               (loop for move in moves
366                   do (multiple-value-bind (val reply)
367                        (alpha-beta3
368                         (opponent player)
369                         (make-move move player
370                                    (replace-board new-board board))
371                         (- cutoff) (- achievable)
372                         (- ply 1) eval-fn killer2)
373                        (setf val (- val))
374                        (when (> val achievable)
375                          (setq achievable val)
376                          (setq best-move move))
377                        (when (and reply (< val killer2-val))
378                          (setq killer2 reply)
379                          (setq killer2-val val)))
380                   until (>= achievable cutoff))
381               (values achievable best-move))))))
382
383
384 (defun alpha-beta-searcher3 (depth eval-fn)
385   "Return a strategy that does A-B search with killer moves."
386   #'(lambda (player board)
387       (declare (type board board)
388                (type player player))
389       (multiple-value-bind (value move)
390           (alpha-beta3 player board losing-value winning-value
391                        depth eval-fn nil)
392         (declare (ignore value))
393         move)))
394
395 (defun alpha-beta-searcher3w (depth eval-fn)
396   "Return a strategy that does A-B search with killer moves."
397   #'(lambda (player board)
398       (multiple-value-bind (value move)
399           (alpha-beta3w player board losing-value winning-value
400                        depth eval-fn nil)
401         (declare (ignore value))
402         move)))
403
404 (defun put-first (killer moves)
405   "Move the killer move to the front of moves,
406   if the killer move is in fact a legal move."
407   (if (member killer moves)
408       (cons killer (delete killer moves))
409       moves))
410
411 (defun mobility (player board)
412   "Current Mobility is the number of legal moves.
413   Potential mobility is the number of blank squares
414   adjacent to an opponent that are not legal moves.
415   Returns current and potential mobility for player."
416   (declare (type board board)
417            (type player player)
418            (optimize (speed 3) (safety 0 )))
419   (let ((opp (opponent player))
420         (current 0)    ; player's current mobility
421         (potential 0))                  ; player's potential mobility
422     (declare (type player opp)
423              (type fixnum current potential))
424     (dolist (square all-squares)
425       (declare (type square square))
426       (when (= (bref board square) empty)
427         (cond ((legal-p square player board)
428                (incf current))
429               ((some-neighbors board opp (neighbors square))
430                (incf potential))
431               )))
432     (values current (the fixnum (+ current potential)))))
433
434
435 (defun some-neighbors (board opp neighbors)
436   (declare (type board board)
437            (type player opp)
438            (type cons neighbors)
439            (optimize (speed 3) (safety 0)))
440   (block search
441     (dolist (sq neighbors)
442       (declare (type square sq))
443       (when (= (bref board sq) opp)
444         (return-from search t)))
445     (return-from search nil)))
446
447 (defun edge-stability (player board)
448   "Total edge evaluation for player to move on board."
449   (declare (type board board)
450            (type player player))
451   (loop for edge-list in *edge-and-x-lists*
452         sum (aref *edge-table*
453                   (edge-index player board edge-list))))
454
455 (defun iago-eval (player board)
456   "Combine edge-stability, current mobility and
457   potential mobility to arrive at an evaluation."
458   ;; The three factors are multiplied by coefficients
459   ;; that vary by move number:
460   (declare (type board board)
461            (type player player))
462   (let ((c-edg  (+ 312000 (* 6240 *move-number*)))
463         (c-cur (if (< *move-number* 25)
464                    (+ 50000 (* 2000 *move-number*))
465                  (+ 75000 (* 1000 *move-number*))))
466         (c-pot 20000))
467     (declare (fixnum c-edg c-cur c-pot))
468     (multiple-value-bind (p-cur p-pot)
469         (mobility player board)
470       (multiple-value-bind (o-cur o-pot)
471           (mobility (opponent player) board)
472         ;; Combine the three factors into one sum:
473         (+ (round (* c-edg (edge-stability player board))
474                   32000)
475            (round (* c-cur (- p-cur o-cur))
476                   (+ p-cur o-cur 2))
477            (round (* c-pot (- p-pot o-pot))
478                   (+ p-pot o-pot 2)))))))
479
480
481 ;; Strategy Functions
482
483 (defun iago (depth)
484   "Use an approximation of Iago's evaluation function."
485   (alpha-beta-searcher3 depth #'iago-eval))
486
487 ;; Maximizer (1-ply)
488 (defun mx-df ()
489   (maximizer #'count-difference))
490
491 (defun mx-wt ()
492   (maximizer #'weighted-squares))
493
494 (defun mx-md-wt ()
495   (maximizer #'modified-weighted-squares))
496
497 ;; Minimax-searcher
498
499 (defun mm-df (ply)
500   (minimax-searcher ply #'count-difference))
501
502 (defun mm-wt (ply)
503   (minimax-searcher ply #'weighted-squares))
504
505 (defun mm-md-wt (ply)
506   (minimax-searcher ply #'modified-weighted-squares))
507
508 ;; Alpha-beta3 searcher
509 (defun ab3-df (ply)
510   (alpha-beta-searcher3 ply #'count-difference))
511
512 (defun ab3-wt (ply)
513   (alpha-beta-searcher3 ply #'weighted-squares))
514
515 (defun ab3-md-wt (ply)
516   (alpha-beta-searcher3 ply #'modified-weighted-squares))
517
518
519 (defun ab3w-df (ply)
520   (alpha-beta-searcher3w ply #'count-difference))
521
522 (defun ab3w-wt (ply)
523   (alpha-beta-searcher3w ply #'weighted-squares))
524
525 (defun ab3w-md-wt (ply)
526   (alpha-beta-searcher3w ply #'modified-weighted-squares))
527
528
529 (defun rr (ply n-pairs)
530   (round-robin 
531    (list #'random-strategy (ab3-df ply) (ab3-wt ply) (ab3-md-wt ply) (iago 3)) 
532    n-pairs 
533    10
534    '(random ab3-df ab3-wt ab3-md-wt iago)))
535
536   
537
538                                           
539