-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtemplate.py
More file actions
336 lines (275 loc) · 11.4 KB
/
template.py
File metadata and controls
336 lines (275 loc) · 11.4 KB
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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
"""
This module contains the example solution for finding marking
the territory and counting the score and a class representing
a stone group.
The game Model needs to inherit from Terr_Template if it does not
implement those methods itself.
"""
class Terr_Template(object):
"""This class does not work on it's own but can be inherited from by
the Model."""
def find_territory(self):
"""Tries to automatically claim territory for the proper players.
Current algorithm:
It just claims empty areas that are completely surrounded
by one color.
Therefore it will not recognise prisoners or dead groups.
Attributes updated by this function:
self.score
self.territory
"""
# Keep track of the checked fields
area = []
for y in range(self.size):
for x in range(self.size):
if (x, y) in area:
continue
# Only look at empty fields
if self.board[y][x] is None:
# Find all adjacent empty fields
# Count contains the number of adjacent stones
# of each color.
_a, count = self._find_empty(x, y, area=area)
area += _a
# Claim the territory if one color has no
# stones adjacent
if count[BLACK] == 0 and count[WHITE] > 0:
self._claim_empty(x, y, WHITE)
elif count[WHITE] == 0 and count[BLACK] > 0:
self._claim_empty(x, y, BLACK)
# Recompute the score
self._compute_score()
def mark_territory(self, x, y):
"""Function that can be evoked by user to claim territory for
one player.
For empty fields it will also mark all adjacent empty fields,
for fields that contain a stone it will mark the entire stone
group and all adjacent empty spaces.
Arguments:
x, y (int): coordinates of the field
Attributes updated by this function:
self.score
self.territory
"""
# Only valid when the game is over
if not self.game_over:
return
# Claim an empty field
if self.board[y][x] is None:
# Cycle through the colours depending on how the
# field is currently marked
col_dict = {None:BLACK, BLACK:WHITE, WHITE:None}
color = col_dict[self.territory[y][x]]
# Start recursively claim the fields
self._claim_empty(x, y, color)
# Claim a group
else:
# Choose whether to mark or unmark the group
if self.territory[y][x] is None:
color = not self.board[y][x].color
else:
color = None
# Start recursively claim the fields
self._claim_group(x, y, color)
# Recompute the score
self._compute_score()
def _claim_empty(self, x, y, color, area=None):
"""First part of the recursive claim function, which claims
an empty field and then tries to claim all adjacent fields, too.
Arguments:
x, y (int) : coordinates
color (bool/None): The color which the territory should
be claimed for.
area (list) : For recursion to keep track of visited fields.
Attributes updated by this function:
self.territory
"""
# Don't do that in the function definition, because then you get
# a weird behaviour (mutable obj) and area will not be empty
# when you expect it to be.
if area is None:
area = []
# Stop recursion
if self.board[y][x] is not None or (x, y) in area:
return
# Claim field
area.append((x, y))
self.territory[y][x] = color
# Go recursively over all adjacent fields
for (u, v) in [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]:
# Not on the board
if u < 0 or v < 0 or u >= self.size or v >= self.size:
continue
# Only claim adjacent empty fields
if (u, v) not in area and self.board[v][u] is None:
self._claim_empty(u, v, color, area=area)
def _claim_empty(self, x, y, color, area=None):
"""First part of the recursive claim function, which claims
an empty field and then tries to claim all adjacent fields, too.
Arguments:
x, y (int) : coordinates
color (bool/None): The color which the territory should
be claimed for.
area (list) : For recursion to keep track of visited fields.
Attributes updated by this function:
self.territory
"""
# Don't do that in the function definition, because then you get
# a weird behaviour (mutable obj) and area will not be empty
# when you expect it to be.
if area is None:
area = []
# Stop recursion
if self.board[y][x] is not None or (x, y) in area:
return
# Claim field
area.append((x, y))
self.territory[y][x] = color
# Go recursively over all adjacent fields
for (u, v) in [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]:
# Not on the board
if u < 0 or v < 0 or u >= self.size or v >= self.size:
continue
# Only claim adjacent empty fields
if (u, v) not in area and self.board[v][u] is None:
self._claim_empty(u, v, color, area=area)
def _claim_group(self, x, y, color, area=None):
"""Second part of the recursive claim function, which claims
a group for the enemies team (dead stones) and the also
claims all adjacent empty spaces.
Arguments:
x, y (int) : coordinates
color (bool/None): The color which the territory should
be claimed for.
area (list) : For recursion to keep track of visited
fields.
Attributes updated by this function:
self.territory
"""
if area is None:
area = []
# Claim the group
for u, v in self.board[y][x].stones:
if (u, v) not in area:
area.append((u, v))
self.territory[v][u] = color
# Go recursively over all adjacent fields
# (in the border of the group)
for u, v in self.board[y][x].border:
# Claim all empty adjacent fields
if self.board[v][u] is None and (u, v) not in area:
self._claim_empty(u, v, color, area=area)
def _compute_score(self):
"""Computes the score of the players from the current
territory and the player's prisoners.
Set:
self.score (list): sum of prisoners and number of coloured
territory fields
"""
self.score = [0, 0]
for j in range(self.size):
for i in range(self.size):
# Count black territory
if self.territory[j][i] == BLACK:
self.score[BLACK] += 1
# Additional point for dead stones inside the territory
if self.board[j][i] is not None:
self.score[BLACK] += 1
# Count white territory
elif self.territory[j][i] == WHITE:
self.score[WHITE] += 1
# Additional point for dead stones inside the territory
if self.board[j][i] is not None:
self.score[WHITE] += 1
def _find_empty(self, x, y, area=None, count=None):
""" Finds all connected empty fields starting at (x, y) and
counts the number of adjacent stones of each color
Returns:
area (list) : List of empty fields
count (list): Number of adjacent stones to the[black, white]
Known bug:
stones are counted several times if more than one
empty field is adjacent to them
"""
if area is None:
area = []
if count is None:
count = [0, 0]
# Stop recursion
if self.board[y][x] is not None or (x, y) in area:
return area, count
area.append((x, y))
# Look at al neighbours
for (u, v) in [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]:
# Not on the board
if u < 0 or v < 0 or u >= self.size or v >= self.size:
continue
# Go recursively over empty fields and add adjacent stones
if (u, v) not in area:
if self.board[v][u] is None:
self._find_empty(u, v, area=area, count=count)
else:
count[self.board[v][u].color] += 1
return area, count
class Grp_Template(object):
def _kill(self, grp):
"""Removes a group of stones from the game and increases the
counter of captured stones.
Arguments:
grp (Group): The group that should be removed
Attributes updated by this function:
self.board
self.captured
self.group
"""
self.captured[not grp.color] += grp.size
self._remove(grp)
def _liberties(self, grp):
"""Counts the number of empty fields adjacent to the group.
Arguments:
grp (Group): A group of stones.
Returns:
(int): nr. of liberties of that group
"""
return sum([1 for u, v in grp.border if self.board[v][u] is None])
class Group(object):
"""Represents a group of connected stones on the board.
Attributes:
stones (set): list of all coordinates where the group has a stone
border (set): list of all fields that are adjacent to the group
For a new group empty fields must be added manually
since the group does not know about the field size
color (bool): color of the group
Property:
size (int): equal to len(self.stones), the number of stones in
the group.
"""
def __init__(self, stones=None, color=None):
"""
Initialise group
"""
if stones is not None:
self.stones = set(stones)
else:
self.stones = set()
self.border = set()
self.color = color
def __add__(self, other):
"""To add two groups of the same color
The new group contains all the stones of the previous groups and
the border will be updated correctly.
Raises:
TypeError: The colours of the groups do not match
"""
if self.color != other.color:
raise ValueError('Only groups of same colour can be added!')
grp = Group(stones=self.stones.union(other.stones))
grp.color = self.color
grp.border = self.border.union(other.border).difference(grp.stones)
return grp
@property
def size(self):
"""Size of the group"""
return len(self.stones)