Skip to content

Commit 9785ac6

Browse files
yunchihilya-khadykin
authored andcommitted
Add go-counting exercise to resolve #748
1 parent a693317 commit 9785ac6

File tree

5 files changed

+249
-0
lines changed

5 files changed

+249
-0
lines changed

config.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,19 @@
10421042
"loops"
10431043
]
10441044
},
1045+
{
1046+
"uuid": "d4ddeb18-ac22-11e7-abc4-cec278b6b50a",
1047+
"slug": "go-counting",
1048+
"core": false,
1049+
"unlocked_by": null,
1050+
"difficulty": 4,
1051+
"topics": [
1052+
"parsing",
1053+
"tuples",
1054+
"optional_values",
1055+
"classes"
1056+
]
1057+
},
10451058
{
10461059
"uuid": "7f4d5743-7ab8-42ca-b393-767f7e9a4e97",
10471060
"slug": "complex-numbers",

exercises/go-counting/README.md

Lines changed: 44 additions & 0 deletions
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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
2+
BLACK = "B"
3+
WHITE = "W"
4+
NONE = ""
5+
STONES = [BLACK, WHITE]
6+
DIRECTIONS = [(0, 1), (0, -1), (1, 0), (-1, 0)]
7+
8+
9+
class Board:
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(self, x, y,
19+
visited_territory=[],
20+
visited_coords=[],
21+
visited_stones=[]):
22+
if not (x, y) in visited_coords and self.valid(x, y):
23+
s = self.board[y][x]
24+
if s in STONES:
25+
if s not in visited_stones:
26+
return (visited_territory, visited_stones + [s])
27+
else: # s is empty
28+
for d in DIRECTIONS:
29+
visited = self.walk(x + d[0], y + d[1],
30+
visited_territory + [(x, y)],
31+
visited_coords + [(x, y)],
32+
visited_stones)
33+
visited_territory = visited[0]
34+
visited_stones = visited[1]
35+
36+
return (visited_territory, visited_stones)
37+
38+
def territoryFor(self, coord):
39+
assert len(coord) == 2
40+
x, y = coord[0], coord[1]
41+
if not self.valid(x, y) or self.board[y][x] in STONES:
42+
return (NONE, set())
43+
44+
visited_territory, visited_stones = self.walk(x, y)
45+
result = set(visited_territory)
46+
47+
if len(visited_stones) == 1:
48+
return (visited_stones[0], result)
49+
return (NONE, result)
50+
51+
def territories(self):
52+
owners = STONES + [NONE]
53+
result = dict([(owner, set()) for owner in owners])
54+
visited = set()
55+
for y in range(self.height):
56+
for x in range(self.width):
57+
if not (x, y) in visited:
58+
owner, owned_territories = self.territoryFor((x, y))
59+
result[owner].update(owned_territories)
60+
visited.update(owned_territories)
61+
62+
return result

exercises/go-counting/go_counting.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
2+
class Board:
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
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import unittest
2+
import gocounting
3+
4+
5+
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0
6+
7+
board5x5 = "\n".join([
8+
" B ",
9+
" B B ",
10+
"B W B",
11+
" W W ",
12+
" W "
13+
])
14+
15+
board9x9 = "\n".join([
16+
" B B ",
17+
"B B B",
18+
"WBBBWBBBW",
19+
"W W W W W",
20+
" ",
21+
" W W W W ",
22+
"B B B B",
23+
" W BBB W ",
24+
" B B "
25+
])
26+
27+
28+
class GoCountingTest(unittest.TestCase):
29+
def test_5x5_for_black(self):
30+
board = gocounting.Board(board5x5)
31+
stone, territory = board.territoryFor((0, 1))
32+
self.assertEqual(stone, gocounting.BLACK)
33+
self.assertEqual(territory, set([(0, 0), (0, 1), (1, 0)]))
34+
35+
def test_5x5_for_white(self):
36+
board = gocounting.Board(board5x5)
37+
stone, territory = board.territoryFor((2, 3))
38+
self.assertEqual(stone, gocounting.WHITE)
39+
self.assertEqual(territory, set([(2, 3)]))
40+
41+
def test_5x5_for_open_territory(self):
42+
board = gocounting.Board(board5x5)
43+
stone, territory = board.territoryFor((1, 4))
44+
self.assertEqual(stone, gocounting.NONE)
45+
self.assertEqual(territory, set([(0, 3), (0, 4), (1, 4)]))
46+
47+
def test_5x5_for_non_territory(self):
48+
board = gocounting.Board(board5x5)
49+
stone, territory = board.territoryFor((1, 1))
50+
self.assertEqual(stone, gocounting.NONE)
51+
self.assertEqual(territory, set())
52+
53+
def test_5x5_for_valid_coordinate(self):
54+
board = gocounting.Board(board5x5)
55+
stone, territory = board.territoryFor((-1, 1))
56+
self.assertEqual(stone, gocounting.NONE)
57+
self.assertEqual(territory, set())
58+
59+
def test_5x5_for_valid_coordinate2(self):
60+
board = gocounting.Board(board5x5)
61+
stone, territory = board.territoryFor((1, 5))
62+
self.assertEqual(stone, gocounting.NONE)
63+
self.assertEqual(territory, set())
64+
65+
def test_one_territory_whole_board(self):
66+
board = gocounting.Board(" ")
67+
territories = board.territories()
68+
self.assertEqual(territories[gocounting.BLACK], set())
69+
self.assertEqual(territories[gocounting.WHITE], set())
70+
self.assertEqual(territories[gocounting.NONE], set([(0, 0)]))
71+
72+
def test_two_territories_rectangular_board(self):
73+
input_board = "\n".join([
74+
" BW ",
75+
" BW "
76+
])
77+
board = gocounting.Board(input_board)
78+
territories = board.territories()
79+
self.assertEqual(territories[gocounting.BLACK], set([(0, 0), (0, 1)]))
80+
self.assertEqual(territories[gocounting.WHITE], set([(3, 0), (3, 1)]))
81+
self.assertEqual(territories[gocounting.NONE], set())
82+
83+
def test_9x9_for_open_territory(self):
84+
board = gocounting.Board(board9x9)
85+
stone, territory = board.territoryFor((0, 8))
86+
self.assertEqual(stone, gocounting.NONE)
87+
self.assertEqual(territory,
88+
set([(2, 7), (2, 8), (1, 8), (0, 8), (0, 7)]))
89+
90+
91+
if __name__ == '__main__':
92+
unittest.main()

0 commit comments

Comments
 (0)