1
- """Games, or Adversarial Search. (Chapter 6)
2
-
1
+ """Games, or Adversarial Search. (Chapter 5)
3
2
"""
4
- # (Written for the second edition of AIMA; expect some discrepanciecs
5
- # from the third edition until this gets reviewed.)
6
3
7
4
from utils import *
8
5
import random
12
9
13
10
def minimax_decision (state , game ):
14
11
"""Given a state in a game, calculate the best move by searching
15
- forward all the way to the terminal states. [Fig. 6.4 ]"""
12
+ forward all the way to the terminal states. [Fig. 5.3 ]"""
16
13
17
14
player = game .to_move (state )
18
15
19
16
def max_value (state ):
20
17
if game .terminal_test (state ):
21
18
return game .utility (state , player )
22
19
v = - infinity
23
- for ( a , s ) in game .successors (state ):
24
- v = max (v , min_value (s ))
20
+ for a in game .actions (state ):
21
+ v = max (v , min_value (game . result ( state , a ) ))
25
22
return v
26
23
27
24
def min_value (state ):
28
25
if game .terminal_test (state ):
29
26
return game .utility (state , player )
30
27
v = infinity
31
- for ( a , s ) in game .successors (state ):
32
- v = min (v , max_value (s ))
28
+ for a in game .actions (state ):
29
+ v = min (v , max_value (game . result ( state , a ) ))
33
30
return v
34
31
35
- # Body of minimax_decision starts here:
36
- action , state = argmax (game .successors (state ),
37
- lambda ((a , s )): min_value (s ))
38
- return action
39
-
32
+ # Body of minimax_decision:
33
+ return argmax (game .actions (state ),
34
+ lambda a : min_value (game .result (state , a )))
40
35
41
36
#______________________________________________________________________________
42
37
43
38
def alphabeta_full_search (state , game ):
44
39
"""Search game to determine best action; use alpha-beta pruning.
45
- As in [Fig. 6 .7], this version searches all the way to the leaves."""
40
+ As in [Fig. 5 .7], this version searches all the way to the leaves."""
46
41
47
42
player = game .to_move (state )
48
43
49
44
def max_value (state , alpha , beta ):
50
45
if game .terminal_test (state ):
51
46
return game .utility (state , player )
52
47
v = - infinity
53
- for ( a , s ) in game .successors (state ):
54
- v = max (v , min_value (s , alpha , beta ))
48
+ for a in game .actions (state ):
49
+ v = max (v , min_value (game . result ( state , a ) , alpha , beta ))
55
50
if v >= beta :
56
51
return v
57
52
alpha = max (alpha , v )
@@ -61,17 +56,17 @@ def min_value(state, alpha, beta):
61
56
if game .terminal_test (state ):
62
57
return game .utility (state , player )
63
58
v = infinity
64
- for ( a , s ) in game .successors (state ):
65
- v = min (v , max_value (s , alpha , beta ))
59
+ for a in game .actions (state ):
60
+ v = min (v , max_value (game . result ( state , a ) , alpha , beta ))
66
61
if v <= alpha :
67
62
return v
68
63
beta = min (beta , v )
69
64
return v
70
65
71
- # Body of alphabeta_search starts here :
72
- action , state = argmax (game .successors (state ),
73
- lambda (( a , s )) : min_value (s , - infinity , infinity ))
74
- return action
66
+ # Body of alphabeta_search:
67
+ return argmax (game .actions (state ),
68
+ lambda a : min_value (game . result ( state , a ),
69
+ - infinity , infinity ))
75
70
76
71
def alphabeta_search (state , game , d = 4 , cutoff_test = None , eval_fn = None ):
77
72
"""Search game to determine best action; use alpha-beta pruning.
@@ -83,8 +78,9 @@ def max_value(state, alpha, beta, depth):
83
78
if cutoff_test (state , depth ):
84
79
return eval_fn (state )
85
80
v = - infinity
86
- for (a , s ) in game .successors (state ):
87
- v = max (v , min_value (s , alpha , beta , depth + 1 ))
81
+ for a in game .actions (state ):
82
+ v = max (v , min_value (game .result (state , a ),
83
+ alpha , beta , depth + 1 ))
88
84
if v >= beta :
89
85
return v
90
86
alpha = max (alpha , v )
@@ -94,8 +90,9 @@ def min_value(state, alpha, beta, depth):
94
90
if cutoff_test (state , depth ):
95
91
return eval_fn (state )
96
92
v = infinity
97
- for (a , s ) in game .successors (state ):
98
- v = min (v , max_value (s , alpha , beta , depth + 1 ))
93
+ for a in game .actions (state ):
94
+ v = min (v , max_value (game .result (state , a ),
95
+ alpha , beta , depth + 1 ))
99
96
if v <= alpha :
100
97
return v
101
98
beta = min (beta , v )
@@ -106,9 +103,9 @@ def min_value(state, alpha, beta, depth):
106
103
cutoff_test = (cutoff_test or
107
104
(lambda state ,depth : depth > d or game .terminal_test (state )))
108
105
eval_fn = eval_fn or (lambda state : game .utility (state , player ))
109
- action , state = argmax (game .successors (state ),
110
- lambda ( a , s ) : min_value (s , - infinity , infinity , 0 ))
111
- return action
106
+ return argmax (game .actions (state ),
107
+ lambda a : min_value (game . result ( state , a ),
108
+ - infinity , infinity , 0 ))
112
109
113
110
#______________________________________________________________________________
114
111
# Players for Games
@@ -120,21 +117,21 @@ def query_player(game, state):
120
117
121
118
def random_player (game , state ):
122
119
"A player that chooses a legal move at random."
123
- return random .choice (game .legal_moves (state ))
120
+ return random .choice (game .actions (state ))
124
121
125
122
def alphabeta_player (game , state ):
126
123
return alphabeta_search (state , game )
127
124
128
125
def play_game (game , * players ):
129
126
"""Play an n-person, move-alternating game.
130
- >>> play_game(Fig62Game (), alphabeta_player, alphabeta_player)
127
+ >>> play_game(Fig52Game (), alphabeta_player, alphabeta_player)
131
128
3
132
129
"""
133
130
state = game .initial
134
131
while True :
135
132
for player in players :
136
133
move = player (game , state )
137
- state = game .make_move ( move , state )
134
+ state = game .result ( state , move )
138
135
if game .terminal_test (state ):
139
136
return game .utility (state , game .to_move (game .initial ))
140
137
@@ -144,17 +141,17 @@ def play_game(game, *players):
144
141
class Game :
145
142
"""A game is similar to a problem, but it has a utility for each
146
143
state and a terminal test instead of a path cost and a goal
147
- test. To create a game, subclass this class and implement
148
- legal_moves, make_move, utility, and terminal_test. You may
149
- override display and successors or you can inherit their default
150
- methods. You will also need to set the .initial attribute to the
151
- initial state; this can be done in the constructor."""
144
+ test. To create a game, subclass this class and implement actions,
145
+ result, utility, and terminal_test. You may override display and
146
+ successors or you can inherit their default methods. You will also
147
+ need to set the .initial attribute to the initial state; this can
148
+ be done in the constructor."""
152
149
153
- def legal_moves (self , state ):
150
+ def actions (self , state ):
154
151
"Return a list of the allowable moves at this point."
155
152
abstract
156
153
157
- def make_move (self , move , state ):
154
+ def result (self , state , move ):
158
155
"Return the state that results from making a move from a state."
159
156
abstract
160
157
@@ -164,7 +161,7 @@ def utility(self, state, player):
164
161
165
162
def terminal_test (self , state ):
166
163
"Return True if this is a final state for the game."
167
- return not self .legal_moves (state )
164
+ return not self .actions (state )
168
165
169
166
def to_move (self , state ):
170
167
"Return the player whose move it is in this state."
@@ -174,17 +171,12 @@ def display(self, state):
174
171
"Print or otherwise display the state."
175
172
print state
176
173
177
- def successors (self , state ):
178
- "Return a list of legal (move, state) pairs."
179
- return [(move , self .make_move (move , state ))
180
- for move in self .legal_moves (state )]
181
-
182
174
def __repr__ (self ):
183
175
return '<%s>' % self .__class__ .__name__
184
176
185
- class Fig62Game (Game ):
186
- """The game represented in [Fig. 6 .2]. Serves as a simple test case.
187
- >>> g = Fig62Game ()
177
+ class Fig52Game (Game ):
178
+ """The game represented in [Fig. 5 .2]. Serves as a simple test case.
179
+ >>> g = Fig52Game ()
188
180
>>> minimax_decision('A', g)
189
181
'a1'
190
182
>>> alphabeta_full_search('A', g)
@@ -199,15 +191,12 @@ class Fig62Game(Game):
199
191
utils = Dict (B1 = 3 , B2 = 12 , B3 = 8 , C1 = 2 , C2 = 4 , C3 = 6 , D1 = 14 , D2 = 5 , D3 = 2 )
200
192
initial = 'A'
201
193
202
- def legal_moves (self , state ):
203
- return [ move for ( move , next ) in self . successors ( state )]
194
+ def actions (self , state ):
195
+ return self . succs . get ( state , {}). keys ()
204
196
205
- def make_move (self , move , state ):
197
+ def result (self , state , move ):
206
198
return self .succs [state ][move ]
207
199
208
- def successors (self , state ):
209
- return self .succs .get (state , {}).items ()
210
-
211
200
def utility (self , state , player ):
212
201
if player == 'MAX' :
213
202
return self .utils [state ]
@@ -231,11 +220,11 @@ def __init__(self, h=3, v=3, k=3):
231
220
for y in range (1 , v + 1 )]
232
221
self .initial = Struct (to_move = 'X' , utility = 0 , board = {}, moves = moves )
233
222
234
- def legal_moves (self , state ):
223
+ def actions (self , state ):
235
224
"Legal moves are any square not yet taken."
236
225
return state .moves
237
226
238
- def make_move (self , move , state ):
227
+ def result (self , state , move ):
239
228
if move not in state .moves :
240
229
return state # Illegal move has no effect
241
230
board = state .board .copy (); board [move ] = state .to_move
@@ -291,12 +280,12 @@ class ConnectFour(TicTacToe):
291
280
def __init__ (self , h = 7 , v = 6 , k = 4 ):
292
281
TicTacToe .__init__ (self , h , v , k )
293
282
294
- def legal_moves (self , state ):
283
+ def actions (self , state ):
295
284
return [(x , y ) for (x , y ) in state .moves
296
285
if y == 0 or (x , y - 1 ) in state .board ]
297
286
298
287
__doc__ += random_tests ("""
299
- >>> play_game(Fig62Game (), random_player, random_player)
288
+ >>> play_game(Fig52Game (), random_player, random_player)
300
289
6
301
290
>>> play_game(TicTacToe(), random_player, random_player)
302
291
0
0 commit comments