Skip to content

go-counting: Implement the exercise "go-counting" #767

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 7, 2017
13 changes: 13 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,19 @@
"loops"
]
},
{
"uuid": "d4ddeb18-ac22-11e7-abc4-cec278b6b50a",
"slug": "go-counting",
"core": false,
"unlocked_by": null,
"difficulty": 4,
"topics": [
"parsing",
"tuples",
"optional_values",
"classes"
]
},
{
"uuid": "7f4d5743-7ab8-42ca-b393-767f7e9a4e97",
"slug": "complex-numbers",
Expand Down
44 changes: 44 additions & 0 deletions exercises/go-counting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Go Counting

Count the scored points on a Go board.

In the game of go (also known as baduk, igo, cờ vây and wéiqí) points
are gained by completely encircling empty intersections with your
stones. The encircled intersections of a player are known as its
territory.

Write a function that determines the territory of each player. You may
assume that any stones that have been stranded in enemy territory have
already been taken off the board.

Multiple empty intersections may be encircled at once and for encircling
only horizontal and vertical neighbours count. In the following diagram
the stones which matter are marked "O" and the stones that don't are
marked "I" (ignored). Empty spaces represent empty intersections.

```
+----+
|IOOI|
|O O|
|O OI|
|IOI |
+----+
```

To be more precise an empty intersection is part of a player's territory
if all of its neighbours are either stones of that player or empty
intersections that are part of that player's territory.

For more information see
[wikipedia](https://en.wikipedia.org/wiki/Go_%28game%29) or [Sensei's
Library](http://senseis.xmp.net/).

## Submitting Exercises

Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.

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`.

## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the
exercise.
62 changes: 62 additions & 0 deletions exercises/go-counting/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@

BLACK = "B"
WHITE = "W"
NONE = ""
STONES = [BLACK, WHITE]
DIRECTIONS = [(0, 1), (0, -1), (1, 0), (-1, 0)]


class Board:
def __init__(self, board):
self.board = board.splitlines()
self.width = len(self.board[0])
self.height = len(self.board)

def valid(self, x, y):
return x >= 0 and x < self.width and y >= 0 and y < self.height

def walk(self, x, y,
visited_territory=[],
visited_coords=[],
visited_stones=[]):
if not (x, y) in visited_coords and self.valid(x, y):
s = self.board[y][x]
if s in STONES:
if s not in visited_stones:
return (visited_territory, visited_stones + [s])
else: # s is empty
for d in DIRECTIONS:
visited = self.walk(x + d[0], y + d[1],
visited_territory + [(x, y)],
visited_coords + [(x, y)],
visited_stones)
visited_territory = visited[0]
visited_stones = visited[1]

return (visited_territory, visited_stones)

def territoryFor(self, coord):
assert len(coord) == 2
x, y = coord[0], coord[1]
if not self.valid(x, y) or self.board[y][x] in STONES:
return (NONE, set())

visited_territory, visited_stones = self.walk(x, y)
result = set(visited_territory)

if len(visited_stones) == 1:
return (visited_stones[0], result)
return (NONE, result)

def territories(self):
owners = STONES + [NONE]
result = dict([(owner, set()) for owner in owners])
visited = set()
for y in range(self.height):
for x in range(self.width):
if not (x, y) in visited:
owner, owned_territories = self.territoryFor((x, y))
result[owner].update(owned_territories)
visited.update(owned_territories)

return result
38 changes: 38 additions & 0 deletions exercises/go-counting/go_counting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

class Board:
"""Count territories of each player in a Go game

Args:
board (list[str]): A two-dimensional Go board
"""

def __init__(self, board):
pass

def territoryFor(self, coord):
"""Find the owner and the territories given a coordinate on
the board

Args:
coord ((int,int)): Coordinate on the board

Returns:
(str, set): A tuple, the first element being the owner
of that area. One of "W", "B", "". The
second being a set of coordinates, representing
the owner's territories.
"""
pass

def territories(self):
"""Find the owners and the territories of the whole board

Args:
none

Returns:
dict(str, set): A dictionary whose key being the owner
, i.e. "W", "B", "". The value being a set
of coordinates owned by the owner.
"""
pass
92 changes: 92 additions & 0 deletions exercises/go-counting/go_counting_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import unittest
import gocounting


# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0

board5x5 = "\n".join([
" B ",
" B B ",
"B W B",
" W W ",
" W "
])

board9x9 = "\n".join([
" B B ",
"B B B",
"WBBBWBBBW",
"W W W W W",
" ",
" W W W W ",
"B B B B",
" W BBB W ",
" B B "
])


class GoCountingTest(unittest.TestCase):
def test_5x5_for_black(self):
board = gocounting.Board(board5x5)
stone, territory = board.territoryFor((0, 1))
self.assertEqual(stone, gocounting.BLACK)
self.assertEqual(territory, set([(0, 0), (0, 1), (1, 0)]))

def test_5x5_for_white(self):
board = gocounting.Board(board5x5)
stone, territory = board.territoryFor((2, 3))
self.assertEqual(stone, gocounting.WHITE)
self.assertEqual(territory, set([(2, 3)]))

def test_5x5_for_open_territory(self):
board = gocounting.Board(board5x5)
stone, territory = board.territoryFor((1, 4))
self.assertEqual(stone, gocounting.NONE)
self.assertEqual(territory, set([(0, 3), (0, 4), (1, 4)]))

def test_5x5_for_non_territory(self):
board = gocounting.Board(board5x5)
stone, territory = board.territoryFor((1, 1))
self.assertEqual(stone, gocounting.NONE)
self.assertEqual(territory, set())

def test_5x5_for_valid_coordinate(self):
board = gocounting.Board(board5x5)
stone, territory = board.territoryFor((-1, 1))
self.assertEqual(stone, gocounting.NONE)
self.assertEqual(territory, set())

def test_5x5_for_valid_coordinate2(self):
board = gocounting.Board(board5x5)
stone, territory = board.territoryFor((1, 5))
self.assertEqual(stone, gocounting.NONE)
self.assertEqual(territory, set())

def test_one_territory_whole_board(self):
board = gocounting.Board(" ")
territories = board.territories()
self.assertEqual(territories[gocounting.BLACK], set())
self.assertEqual(territories[gocounting.WHITE], set())
self.assertEqual(territories[gocounting.NONE], set([(0, 0)]))

def test_two_territories_rectangular_board(self):
input_board = "\n".join([
" BW ",
" BW "
])
board = gocounting.Board(input_board)
territories = board.territories()
self.assertEqual(territories[gocounting.BLACK], set([(0, 0), (0, 1)]))
self.assertEqual(territories[gocounting.WHITE], set([(3, 0), (3, 1)]))
self.assertEqual(territories[gocounting.NONE], set())

def test_9x9_for_open_territory(self):
board = gocounting.Board(board9x9)
stone, territory = board.territoryFor((0, 8))
self.assertEqual(stone, gocounting.NONE)
self.assertEqual(territory,
set([(2, 7), (2, 8), (1, 8), (0, 8), (0, 7)]))


if __name__ == '__main__':
unittest.main()