Skip to content

Commit fc53246

Browse files
committed
go-counting: Implement the exercise "go-counting"
See #748
1 parent e4f4fd8 commit fc53246

File tree

4 files changed

+226
-0
lines changed

4 files changed

+226
-0
lines changed

exercises/go-counting/README.md

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Go Counting
2+
3+
Count the scored points on a Go board.
4+
5+
In the game of go (also known as baduk, igo, cờ vây and wéiqí) points
6+
are gained by completely encircling empty intersections with your
7+
stones. The encircled intersections of a player are known as its
8+
territory.
9+
10+
Write a function that determines the territory of each player. You may
11+
assume that any stones that have been stranded in enemy territory have
12+
already been taken off the board.
13+
14+
Multiple empty intersections may be encircled at once and for encircling
15+
only horizontal and vertical neighbours count. In the following diagram
16+
the stones which matter are marked "O" and the stones that don't are
17+
marked "I" (ignored). Empty spaces represent empty intersections.
18+
19+
```
20+
+----+
21+
|IOOI|
22+
|O O|
23+
|O OI|
24+
|IOI |
25+
+----+
26+
```
27+
28+
To be more precise an empty intersection is part of a player's territory
29+
if all of its neighbours are either stones of that player or empty
30+
intersections that are part of that player's territory.
31+
32+
For more information see
33+
[wikipedia](https://en.wikipedia.org/wiki/Go_%28game%29) or [Sensei's
34+
Library](http://senseis.xmp.net/).
35+
36+
### Submitting Exercises
37+
38+
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
39+
40+
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
41+
42+
## Submitting Incomplete Solutions
43+
It's possible to submit an incomplete solution so you can see how others have completed the
44+
exercise.

exercises/go-counting/example.py

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
2+
class GoCounting:
3+
4+
none = ""
5+
white = "W"
6+
black = "B"
7+
stones = [black, white]
8+
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
9+
10+
def __init__(self, board):
11+
self.board = board.splitlines()
12+
self.width = len(self.board[0])
13+
self.height = len(self.board)
14+
15+
def valid(self, x, y):
16+
return x >= 0 and x < self.width and y >= 0 and y < self.height
17+
18+
def walk_board(self, x, y, visited_territory=[], visited_coords=[], visited_stones=[]):
19+
if not (x, y) in visited_coords and self.valid(x, y):
20+
s = self.board[y][x]
21+
if s in self.stones:
22+
if s not in visited_stones:
23+
return (visited_territory, visited_stones + [s])
24+
else: # s is empty
25+
for d in self.directions:
26+
visited_territory, visited_stones = self.walk_board(x + d[0], y + d[1],
27+
visited_territory + [(x, y)],
28+
visited_coords + [(x, y)],
29+
visited_stones)
30+
31+
return (visited_territory, visited_stones)
32+
33+
def territoryFor(self, coord):
34+
assert(len(coord) == 2)
35+
x, y = coord[0], coord[1]
36+
if not self.valid(x, y) or self.board[y][x] in self.stones:
37+
return (self.none, set())
38+
39+
visited_territory, visited_stones = self.walk_board(x, y)
40+
result = set(visited_territory)
41+
42+
if len(visited_stones) == 1:
43+
return (visited_stones[0], result)
44+
return (self.none, result)
45+
46+
def territories(self):
47+
owners = self.stones + [self.none]
48+
result = dict([(owner, set()) for owner in owners])
49+
visited = set()
50+
for y in range(self.height):
51+
for x in range(self.width):
52+
if not (x, y) in visited:
53+
owner, owned_territories = self.territoryFor((x, y))
54+
result[owner].update(owned_territories)
55+
visited.update(owned_territories)
56+
57+
return result

exercises/go-counting/gocounting.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
2+
class GoCounting:
3+
"""Count territories of each player in a Go game
4+
5+
Args:
6+
board (list[str]): A two-dimensional Go board
7+
"""
8+
9+
def __init__(self, board):
10+
pass
11+
12+
def territoryFor(self, coord):
13+
"""Find the owner and the territories given a coordinate on
14+
the board
15+
16+
Args:
17+
coord ((int,int)): Coordinate on the board
18+
19+
Returns:
20+
(str, set): A tuple, the first element being the owner
21+
of that area. One of "W", "B", "". The
22+
second being a set of coordinates, representing
23+
the owner's territories.
24+
"""
25+
pass
26+
27+
def territories(self):
28+
"""Find the owners and the territories of the whole board
29+
30+
Args:
31+
none
32+
33+
Returns:
34+
dict(str, set): A dictionary whose key being the owner
35+
, i.e. "W", "B", "". The value being a set
36+
of coordinates owned by the owner.
37+
"""
38+
pass
+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import unittest
2+
import gocounting
3+
4+
board5x5 = "\n".join([
5+
" B ",
6+
" B B ",
7+
"B W B",
8+
" W W ",
9+
" W "
10+
])
11+
12+
board9x9 = "\n".join([
13+
" B B ",
14+
"B B B",
15+
"WBBBWBBBW",
16+
"W W W W W",
17+
" ",
18+
" W W W W ",
19+
"B B B B",
20+
" W BBB W ",
21+
" B B "
22+
])
23+
24+
class GoCountingTest(unittest.TestCase):
25+
def test_5x5_for_black(self):
26+
board = gocounting.GoCounting(board5x5)
27+
stone, territory = board.territoryFor((0, 1))
28+
self.assertEqual(stone, board.black)
29+
self.assertEqual(territory, set([(0, 0), (0, 1), (1, 0)]))
30+
31+
def test_5x5_for_white(self):
32+
board = gocounting.GoCounting(board5x5)
33+
stone, territory = board.territoryFor((2, 3))
34+
self.assertEqual(stone, board.white)
35+
self.assertEqual(territory, set([(2, 3)]))
36+
37+
def test_5x5_for_open_territory(self):
38+
board = gocounting.GoCounting(board5x5)
39+
stone, territory = board.territoryFor((1, 4))
40+
self.assertEqual(stone, board.none)
41+
self.assertEqual(territory, set([(0, 3), (0, 4), (1, 4)]))
42+
43+
def test_5x5_for_non_territory(self):
44+
board = gocounting.GoCounting(board5x5)
45+
stone, territory = board.territoryFor((1, 1))
46+
self.assertEqual(stone, board.none)
47+
self.assertEqual(territory, set())
48+
49+
def test_5x5_for_valid_coordinate(self):
50+
board = gocounting.GoCounting(board5x5)
51+
stone, territory = board.territoryFor((-1, 1))
52+
self.assertEqual(stone, board.none)
53+
self.assertEqual(territory, set())
54+
55+
def test_5x5_for_valid_coordinate2(self):
56+
board = gocounting.GoCounting(board5x5)
57+
stone, territory = board.territoryFor((1, 5))
58+
self.assertEqual(stone, board.none)
59+
self.assertEqual(territory, set())
60+
61+
def test_one_territory_whole_board(self):
62+
board = gocounting.GoCounting(" ")
63+
territories = board.territories()
64+
self.assertEqual(territories[board.black], set())
65+
self.assertEqual(territories[board.white], set())
66+
self.assertEqual(territories[board.none], set([(0, 0)]))
67+
68+
def test_two_territories_rectangular_board(self):
69+
input_board = "\n".join([
70+
" BW ",
71+
" BW "
72+
])
73+
board = gocounting.GoCounting(input_board)
74+
territories = board.territories()
75+
self.assertEqual(territories[board.black], set([(0, 0), (0, 1)]))
76+
self.assertEqual(territories[board.white], set([(3, 0), (3, 1)]))
77+
self.assertEqual(territories[board.none], set())
78+
79+
def test_9x9_for_open_territory(self):
80+
board = gocounting.GoCounting(board9x9)
81+
stone, territory = board.territoryFor((0, 8))
82+
self.assertEqual(stone, board.none)
83+
self.assertEqual(territory, set([(2, 7), (2, 8), (1, 8), (0, 8), (0, 7)]))
84+
85+
86+
if __name__ == '__main__':
87+
unittest.main()

0 commit comments

Comments
 (0)