Skip to content

Commit

Permalink
Add go-counting exercise to resolve exercism#748
Browse files Browse the repository at this point in the history
  • Loading branch information
yunchih authored and smalley committed Nov 12, 2017
1 parent ca8ce11 commit b0a86c6
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 0 deletions.
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()

0 comments on commit b0a86c6

Please sign in to comment.