forked from pablooliveira/SimpleTronBot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tron.py
285 lines (216 loc) · 7.62 KB
/
tron.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
273
274
275
276
277
278
279
280
281
282
283
284
285
# If you want to use this file, make sure to include it in your
# submission. You may modify it and submit the modified copy, or you
# may discard it and roll your own.
"""
Provided code for the Python starter package
See the example bots randbot.py and wallbot.py to get started.
"""
import sys, os
def invalid_input(message):
"""You do not need to call this function directly."""
print >>sys.stderr, "Invalid input: %s" % message
sys.exit(1)
def readline(buf):
"""You do not need to call this function directly."""
while not '\n' in buf:
tmp = os.read(0, 1024)
if not tmp:
break
buf += tmp
if not buf.strip():
return None, buf
if not '\n' in buf:
invalid_input('unexpected EOF after "%s"' % buf)
index = buf.find('\n')
line = buf[0:index]
rest = buf[index + 1:]
return line, rest
class Board(object):
"""The Tron Board.
The recommended way to use this class is as follows:
def which_move(board):
# figure this part out yourself
return tron.NORTH
for board in tron.Board.generate():
tron.move(which_move(board))
Feel free to add stuff to this class.
"""
def __init__(self, width, height, board):
"""You do not need to call this method directly."""
self.board = board
self.height = height
self.width = width
self._me = None
self._them = None
self.win = 0
@staticmethod
def read(buf):
"""You do not need to call this method directly."""
meta, buf = readline(buf)
if not meta:
return None, buf
dim = meta.split(' ')
if len(dim) != 2:
invalid_input("expected dimensions on first line")
try:
width, height = int(dim[0]), int(dim[1])
except ValueError:
invalid_input("malformed dimensions on first line")
lines = []
while len(lines) != height:
line, buf = readline(buf)
if not line:
invalid_input("unexpected EOF reading board")
lines.append(line)
board = [line[:width] for line in lines]
if len(board) != height or any(len(board[y]) != width for y in xrange(height)):
invalid_input("malformed board")
return Board(width, height, board), buf
@staticmethod
def generate():
"""Generate board objects, once per turn.
This method returns a generator which you may iterate over.
Make sure to call tron.move() exactly once for every board
generated, or your bot will not work.
"""
buf = ''
while True:
board, buf = Board.read(buf)
if not board:
break
yield board
if buf.strip():
invalid_input("garbage after last board: %s" % buf)
def __getitem__(self, coords):
"""Retrieve the object at the specified coordinates.
Use it like this:
if board[3, 2] == tron.THEM:
# oh no, the other player is at (3,2)
run_away()
Coordinate System:
The coordinate (y, x) corresponds to row y, column x.
The top left is (0, 0) and the bottom right is
(board.height - 1, board.width - 1). Out-of-range
coordinates are always considered walls.
Items on the board:
tron.FLOOR - an empty square
tron.WALL - a wall or trail of a bot
tron.ME - your bot
tron.THEM - the enemy bot
"""
y, x = coords
if not 0 <= x < self.width or not 0 <= y < self.height:
return WALL
return self.board[y][x]
def __setitem__(self, coords, v):
y, x = coords
# not very efficient, but good enough ...
new = self.board[y][:x] + v + self.board[y][x+1:]
self.board[y] = new
def move_forth(self,move,player=1):
origin = self.origin(player)
to = self.rel(move,origin)
self[origin] = WALL
if player == 1:
self[to] = ME
self._me = to
else:
self[to] = THEM
self._them = to
def move_back(self,move,player=1):
origin = self.origin(player)
to = self.rel(MIRROR[move],origin)
self[origin] = FLOOR
if player == 1:
self[to] = ME
self._me = to
else:
self[to] = THEM
self._them = to
def me(self):
"""Finds your position on the board.
It is always true that board[board.me()] == tron.ME.
"""
if not self._me:
self._me = self.find(ME)
return self._me
def them(self):
"""Finds the other player's position on the board.
It is always true that board[board.them()] == tron.THEM.
"""
if not self._them:
self._them = self.find(THEM)
return self._them
def find(self, obj):
"""You do not need to call this method directly."""
for y in xrange(self.height):
for x in xrange(self.width):
if self[y, x] == obj:
return y, x
raise KeyError("object '%s' is not in the board" % obj)
def passable(self, coords):
"""Determine if a position in the board is passable.
You can only safely move onto passable tiles, and only
floor tiles are passable.
"""
return (self[coords] == FLOOR)
def rel(self, direction, origin=None):
"""Calculate which tile is in the given direction from origin.
The default origin is you. Therefore, board.rel(tron.NORTH))
is the tile north of your current position. Similarly,
board.rel(tron.SOUTH, board.them()) is the tile south of
the other bot's position.
"""
if not origin:
origin = self.me()
y, x = origin
if direction == NORTH:
return y - 1, x
elif direction == SOUTH:
return y + 1, x
elif direction == EAST:
return y, x + 1
elif direction == WEST:
return y, x - 1
else:
raise KeyError("not a valid direction: %s" % direction)
def adjacent(self, origin):
"""Calculate the four tiles that are adjacent to origin.
Particularly, board.adjacent(board.me()) returns the four
tiles to which you can move to this turn. This does not
return tiles diagonally adjacent to origin.
"""
return [self.rel(dir, origin) for dir in DIRECTIONS]
def origin(self, player=1):
if player == 1:
return self.me()
else:
return self.them()
def moves(self, player=1):
"""Calculate which moves are safe to make this turn.
Any move in the returned list is a valid move. There
are two ways moving to one of these tiles could end
the game:
1. At the beginning of the following turn,
there are no valid moves off this tile.
2. The other player also moves onto this tile,
and you collide.
"""
origin = self.origin(player)
possible = dict((dir, self.rel(dir,origin)) for dir in DIRECTIONS)
passable = [dir for dir in possible if self.passable(possible[dir])]
return passable
NORTH = 1
EAST = 2
SOUTH = 3
WEST = 4
FLOOR = ' '
WALL = '#'
ME = '1'
THEM = '2'
DIRECTIONS = (NORTH, EAST, SOUTH, WEST)
MIRROR = {NORTH:SOUTH, EAST:WEST, SOUTH:NORTH, WEST:EAST}
NAMES= {NORTH:"n", EAST:"e", SOUTH:"s", WEST:"w"}
def move(direction):
print direction
sys.stdout.flush()