-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathavalam.py
272 lines (222 loc) · 8.78 KB
/
avalam.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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# -*- coding: utf-8 -*-
"""
Common definitions for the Avalam players.
Copyright (C) 2010 - Vianney le Clément, UCLouvain
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
"""
PLAYER1 = 1
PLAYER2 = -1
class InvalidAction(Exception):
"""Raised when an invalid action is played."""
def __init__(self, action=None):
self.action = action
class Board:
"""Representation of an Avalam Board.
self.m is a self.rows by self.columns bi-dimensional array representing the
board. The absolute value of a cell is the height of the tower. The sign
is the color of the top-most counter (negative for red, positive for
yellow).
"""
# standard avalam
max_height = 5
initial_board = [ [ 0, 0, 1, -1, 0, 0, 0, 0, 0],
[ 0, 1, -1, 1, -1, 0, 0, 0, 0],
[ 0, -1, 1, -1, 1, -1, 1, 0, 0],
[ 0, 1, -1, 1, -1, 1, -1, 1, -1],
[ 1, -1, 1, -1, 0, -1, 1, -1, 1],
[-1, 1, -1, 1, -1, 1, -1, 1, 0],
[ 0, 0, 1, -1, 1, -1, 1, -1, 0],
[ 0, 0, 0, 0, -1, 1, -1, 1, 0],
[ 0, 0, 0, 0, 0, -1, 1, 0, 0] ]
def __init__(self, percepts=initial_board, max_height=max_height,
invert=False):
"""Initialize the board.
Arguments:
percepts -- matrix representing the board
invert -- whether to invert the sign of all values, inverting the
players
max_height -- maximum height of a tower
"""
self.m = percepts
self.rows = len(self.m)
self.columns = len(self.m[0])
self.max_height = max_height
self.m = self.get_percepts(invert) # make a copy of the percepts
def __str__(self):
def str_cell(i, j):
x = self.m[i][j]
if x:
return "%+2d" % x
else:
return " ."
return "\n".join(" ".join(str_cell(i, j) for j in range(self.columns))
for i in range(self.rows))
def clone(self):
"""Return a clone of this object."""
return Board(self.m)
def get_percepts(self, invert=False):
"""Return the percepts corresponding to the current state.
If invert is True, the sign of all values is inverted to get the view
of the other player.
"""
mul = 1
if invert:
mul = -1
return [[mul * self.m[i][j] for j in range(self.columns)]
for i in range(self.rows)]
def get_towers(self):
"""Yield all towers.
Yield the towers as triplets (i, j, h):
i -- row number of the tower
j -- column number of the tower
h -- height of the tower (absolute value) and owner (sign)
"""
for i in range(self.rows):
for j in range(self.columns):
if self.m[i][j]:
yield (i, j, self.m[i][j])
def is_action_valid(self, action):
"""Return whether action is a valid action."""
try:
i1, j1, i2, j2 = action
if i1 < 0 or j1 < 0 or i2 < 0 or j2 < 0 or \
i1 >= self.rows or j1 >= self.columns or \
i2 >= self.rows or j2 >= self.columns or \
(i1 == i2 and j1 == j2) or (abs(i1-i2) > 1) or (abs(j1-j2) > 1):
return False
h1 = abs(self.m[i1][j1])
h2 = abs(self.m[i2][j2])
if h1 <= 0 or h1 >= self.max_height or h2 <= 0 or \
h2 >= self.max_height or h1+h2 > self.max_height:
return False
return True
except (TypeError, ValueError):
return False
def get_tower_actions(self, i, j):
"""Yield all actions with moving tower (i,j)"""
h = abs(self.m[i][j])
if h > 0 and h < self.max_height:
for di in (-1, 0, 1):
for dj in (-1, 0, 1):
action = (i, j, i+di, j+dj)
if self.is_action_valid(action):
yield action
def is_tower_movable(self, i, j):
"""Return wether tower (i,j) is movable"""
for action in self.get_tower_actions(i, j):
return True
return False
def get_actions(self):
"""Yield all valid actions on this board."""
for i, j, h in self.get_towers():
for action in self.get_tower_actions(i, j):
yield action
def play_action(self, action):
"""Play an action if it is valid.
An action is a 4-uple containing the row and column of the tower to
move and the row and column of the tower to gobble. If the action is
invalid, raise an InvalidAction exception. Return self.
"""
if not self.is_action_valid(action):
raise InvalidAction(action)
i1, j1, i2, j2 = action
h1 = abs(self.m[i1][j1])
h2 = abs(self.m[i2][j2])
if self.m[i1][j1] < 0:
self.m[i2][j2] = -(h1 + h2)
else:
self.m[i2][j2] = h1 + h2
self.m[i1][j1] = 0
return self
def is_finished(self):
"""Return whether no more moves can be made (i.e., game finished)."""
for action in self.get_actions():
return False
return True
def get_score(self):
"""Return a score for this board.
The score is the difference between the number of towers of each
player. In case of ties, it is the difference between the maximal
height towers of each player. If self.is_finished() returns True,
this score represents the winner (<0: red, >0: yellow, 0: draw).
"""
score = 0
for i in range(self.rows):
for j in range(self.columns):
if self.m[i][j] < 0:
score -= 1
elif self.m[i][j] > 0:
score += 1
if score == 0:
for i in range(self.rows):
for j in range(self.columns):
if self.m[i][j] == -self.max_height:
score -= 1
elif self.m[i][j] == self.max_height:
score += 1
return score
def load_percepts(filename):
"""Load percepts from a CSV file."""
f = None
try:
f = open(filename, "r")
import csv
percepts = []
for row in csv.reader(f):
if not row:
continue
row = [int(c.strip()) for c in row]
if percepts:
assert len(row) == len(percepts[0]), \
"rows must have the same length"
percepts.append(row)
return percepts
finally:
if f is not None:
f.close()
class Agent:
"""Interface for a Zombies agent"""
def initialize(self, percepts, players, time_left):
"""Begin a new game.
The computation done here also counts in the time credit.
Arguments:
percepts -- the initial board in a form that can be fed to the Board
constructor.
players -- sequence of players this agent controls
time_left -- a float giving the number of seconds left from the time
credit for this agent (all players taken together). If the game is
not time-limited, time_left is None.
"""
pass
def play(self, percepts, player, step, time_left):
"""Play and return an action.
Arguments:
percepts -- the current board in a form that can be fed to the Board
constructor.
player -- the player to control in this step
step -- the current step number, starting from 1
time_left -- a float giving the number of seconds left from the time
credit. If the game is not time-limited, time_left is None.
"""
pass
def agent_main(agent, args_cb=None, setup_cb=None):
"""Launch agent server depending on arguments.
Arguments:
agent -- an Agent instance
args_cb -- function taking two arguments: the agent and an
ArgumentParser. It can add custom options to the parser.
(None to disable)
setup_cb -- function taking three arguments: the agent, the
ArgumentParser and the options dictionary. It can be used to
configure the agent based on the custom options. (None to
disable)
"""
pass