-
Notifications
You must be signed in to change notification settings - Fork 0
/
greedy-agent-5.py
239 lines (213 loc) · 9.45 KB
/
greedy-agent-5.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
import board as bm
from random import randrange
from copy import deepcopy
import math
class Board2(bm.Board):
# additional methods
def value_board(self, team):
""" determines how favourable this board state is
:param team: the team who last moved
:return: integer value
(Higher is better for team. Lower is better for enemy)
"""
enemy = Board2.get_opponent_team(team) # enemy of team
counts = {team: 0, enemy: 0}
for piece in self.pieces:
if piece is not None:
# live pieces are worth points
# also gives weight to distance of piece from middle
piece_value = 30 - Board2.distance_of_pos_to_mid(piece['pos'])
counts[piece['team']] += piece_value
# During placing phase, consider if this piece is threatened
# to be removed on its enemy's next action
enemy_of_piece = Board2.get_opponent_team(piece['team'])
if self.phase == bm.PLACING_PHASE \
and self.placing_pos_threatened(piece['pos'], enemy_of_piece):
# is worse if your own piece is threatened after your action
if piece['team'] == team:
# can subtract less here if want to play aggressive
counts[team] -= piece_value
else: # enemy piece threatened. They could move it though
counts[enemy] -= piece_value // 3
# symmetry in calculation
return counts[team] - counts[enemy]
@staticmethod
def distance_of_pos_to_mid(pos):
""" :returns Manhattan distance of pos to middle four squares """
# middle indexes are: 3, 4
x_diff = min(abs(3 - pos[0]), abs(4 - pos[0]))
y_diff = min(abs(3 - pos[1]), abs(4 - pos[1]))
return x_diff + y_diff
def placing_pos_threatened(self, pos, enemy):
""" Checks whether or not a pos is threatened during placing phase
A piece is threatened by placing if there exists a square such that if
an enemy is placed there, it causes the piece to be removed.
:param pos: containing a piece
:param enemy: enemy team of piece
:return: whether or not piece is threatened
"""
corners = self.get_corners()
for x_off, y_off in ((1, 0), (-1, 0), (0, 1), (0, -1)):
side = (pos[0] - x_off, pos[1] - y_off)
if self.has_piece_of_team(side, enemy) or side in corners:
# one side is threatened. What about other?
other_side = (pos[0] + x_off, pos[1] + y_off)
if self.__pos_can_be_placed_in(other_side, enemy):
return True
return False
# TODO consider edge case of final place
def __pos_can_be_placed_in(self, pos, team):
""" :return whether or not pos is within team's placing boundary during
Placing Phase and is available to be placed in"""
if self.pos_empty(pos): # is the position in bounds and empty?
if team == bm.WHITE: # is the position within team's starting zone?
return pos[1] < 6
elif team == bm.BLACK:
return pos[1] > 1
return False
def get_best_value_for_state(self, team, enemy, depth, a, b):
""" If this board is current state and it's team's turn,
return the best value they can get.
Does Minimax with alpha beta pruning.
:param team: to move
:param enemy: other team
:param depth: depth of recursion
:param a: Alpha
:param b: Beta
:return: highest value for this team
"""
if depth == 0: # base case: Go no deeper. Return valuation of board
return self.value_board(enemy)
# calculate all possible actions for team in this board state
actions = self.get_all_actions(team)
# Edge case: no possible actions. Need to pass turn
if len(actions) == 0:
board = deepcopy(self)
board.do_action(None, team) # pass turn
if depth > 1:
return board.get_best_value_for_state(enemy, team, depth - 1, a, b)
else:
return board.value_board(team)
if depth % 2 == 1: # maximising
best_value = -math.inf
for action in actions:
board = deepcopy(self)
board.do_action(action, team)
best_value = max(best_value, board.get_best_value_for_state(enemy, team, depth - 1, a, b))
a = max(a, best_value)
if b < a:
# print(b, "<", a, "D cut off: depth", depth)
break # beta cut-off
return best_value
else: # minimising
best_value = math.inf
for action in actions:
board = deepcopy(self)
board.do_action(action, team)
best_value = min(best_value, board.get_best_value_for_state(enemy, team, depth - 1, a, b))
b = min(b, best_value)
if b < a:
# print(b, "<", a, "C cut off: depth", depth)
break # alpha cut-off
return best_value
def get_best_actions_from_state(self, team, enemy, depth=1, a=-math.inf, b=math.inf):
""" Does minimax to get list of good actions for team
:param team: to move
:param enemy: other team
:param depth: depth of recursion
:param a: Alpha
:param b: Beta
:return: list of actions giving best value for team
"""
assert depth > 0 # only the other function should deal with base case
actions = self.get_all_actions(team)
# Edge case: no possible actions
if len(actions) == 0:
return []
# calculate how favourable the state is after applying each action
next_values = [0] * len(actions)
if depth % 2 == 1: # maximising
best_value = -math.inf
for i in range(len(next_values)):
board = deepcopy(self)
board.do_action(actions[i], team)
next_values[i] = \
board.get_best_value_for_state(enemy, team, depth - 1, a, b)
best_value = max(best_value, next_values[i])
a = max(a, best_value)
if b < a:
break # beta cut-off (shouldn't happen)
else: # minimising
best_value = math.inf
for i in range(len(next_values)):
board = deepcopy(self)
board.do_action(actions[i], team)
next_values[i] = \
board.get_best_value_for_state(enemy, team, depth - 1, a, b)
best_value = min(best_value, next_values[i])
b = min(b, best_value)
if b < a:
break # alpha cut-off (shouldn't happen)
# filter out less-favourable states
best_actions = []
for i in range(len(next_values)):
if next_values[i] == best_value:
best_actions.append(actions[i])
# TODO remove test printing
print(" {} best_value {}, best_actions {}".format(team,
best_value,
best_actions))
return best_actions
class Player(object):
"""
An agent that can play Watch Your Back.
This agent looks at the next state caused by each action to pick an action
Like greedy-agent-4 but does alpha-beta pruning
"""
def __init__(self, colour):
"""
:param colour: either 'white' or 'black'
"""
# set up a new board
self.board = Board2()
# set up team allegiances
self.team = bm.BLACK
if colour == 'white':
self.team = bm.WHITE
elif colour == 'black':
self.team = bm.BLACK
else:
raise ValueError("colour must be 'white' or 'black'")
self.enemy_team = bm.Board.get_opponent_team(self.team)
def action(self, turns):
"""
called by the referee to request an action from the player
:param turns: number of turns that have taken place
since start of current game phase
:return: next action
"""
# Opening Book
if self.board.phase == bm.PLACING_PHASE and turns == 0:
# These are good first moves for WHITE
best_actions = [(3, 4), (4, 4)]
else:
# different Minimax search depth depending on game phase
depth = 2 + max(0, self.board.phase - 1)
best_actions = self.board.get_best_actions_from_state(
self.team, self.enemy_team, depth)
print(" {} Minimax depth: {}".format(self.team, depth))
our_action = None # will forfeit turn if no actions
if len(best_actions) > 0:
# choose random action among our best actions
our_action = best_actions[randrange(0, len(best_actions))]
# Update the board with our action
self.board.do_action(our_action, self.team)
return our_action
def update(self, action):
"""
Inform player about opponent's most recent move
:param action: opponent's action
:return: Nothing
"""
# Update our board with the opponent's action
self.board.do_action(action, self.enemy_team)