From 249cdaa516c833e8583569e13e50e4326959191d Mon Sep 17 00:00:00 2001 From: Yunchih Chen Date: Sun, 8 Oct 2017 11:14:03 +0800 Subject: [PATCH 1/8] go-counting: Implement the exercise "go-counting" See #748 --- config.json | 12 ++++ exercises/go-counting/README.md | 44 +++++++++++++ exercises/go-counting/example.py | 57 ++++++++++++++++ exercises/go-counting/gocounting.py | 38 +++++++++++ exercises/go-counting/gocountingtest.py | 87 +++++++++++++++++++++++++ 5 files changed, 238 insertions(+) create mode 100644 exercises/go-counting/README.md create mode 100644 exercises/go-counting/example.py create mode 100644 exercises/go-counting/gocounting.py create mode 100644 exercises/go-counting/gocountingtest.py diff --git a/config.json b/config.json index 1d22c21285..5b990b0821 100644 --- a/config.json +++ b/config.json @@ -823,6 +823,18 @@ "control-flow (loops)" ] }, + { + "uuid": "d4ddeb18-ac22-11e7-abc4-cec278b6b50a", + "slug": "go-counting", + "core": false, + "unlocked_by": null, + "difficulty": 1, + "topics": [ + "parsing", + "tuples", + "optional values" + ] + }, { "uuid": "7f4d5743-7ab8-42ca-b393-767f7e9a4e97", "slug": "complex-numbers", diff --git a/exercises/go-counting/README.md b/exercises/go-counting/README.md new file mode 100644 index 0000000000..43d6903196 --- /dev/null +++ b/exercises/go-counting/README.md @@ -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/` directory. + +For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit /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. diff --git a/exercises/go-counting/example.py b/exercises/go-counting/example.py new file mode 100644 index 0000000000..b99832f064 --- /dev/null +++ b/exercises/go-counting/example.py @@ -0,0 +1,57 @@ + +class GoCounting: + + none = "" + white = "W" + black = "B" + stones = [black, white] + directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] + + 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_board(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 self.stones: + if s not in visited_stones: + return (visited_territory, visited_stones + [s]) + else: # s is empty + for d in self.directions: + visited_territory, visited_stones = self.walk_board(x + d[0], y + d[1], + visited_territory + [(x, y)], + visited_coords + [(x, y)], + visited_stones) + + 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 self.stones: + return (self.none, set()) + + visited_territory, visited_stones = self.walk_board(x, y) + result = set(visited_territory) + + if len(visited_stones) == 1: + return (visited_stones[0], result) + return (self.none, result) + + def territories(self): + owners = self.stones + [self.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 diff --git a/exercises/go-counting/gocounting.py b/exercises/go-counting/gocounting.py new file mode 100644 index 0000000000..dd223a8dc3 --- /dev/null +++ b/exercises/go-counting/gocounting.py @@ -0,0 +1,38 @@ + +class GoCounting: + """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 diff --git a/exercises/go-counting/gocountingtest.py b/exercises/go-counting/gocountingtest.py new file mode 100644 index 0000000000..e53227be5d --- /dev/null +++ b/exercises/go-counting/gocountingtest.py @@ -0,0 +1,87 @@ +import unittest +import gocounting + +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.GoCounting(board5x5) + stone, territory = board.territoryFor((0, 1)) + self.assertEqual(stone, board.black) + self.assertEqual(territory, set([(0, 0), (0, 1), (1, 0)])) + + def test_5x5_for_white(self): + board = gocounting.GoCounting(board5x5) + stone, territory = board.territoryFor((2, 3)) + self.assertEqual(stone, board.white) + self.assertEqual(territory, set([(2, 3)])) + + def test_5x5_for_open_territory(self): + board = gocounting.GoCounting(board5x5) + stone, territory = board.territoryFor((1, 4)) + self.assertEqual(stone, board.none) + self.assertEqual(territory, set([(0, 3), (0, 4), (1, 4)])) + + def test_5x5_for_non_territory(self): + board = gocounting.GoCounting(board5x5) + stone, territory = board.territoryFor((1, 1)) + self.assertEqual(stone, board.none) + self.assertEqual(territory, set()) + + def test_5x5_for_valid_coordinate(self): + board = gocounting.GoCounting(board5x5) + stone, territory = board.territoryFor((-1, 1)) + self.assertEqual(stone, board.none) + self.assertEqual(territory, set()) + + def test_5x5_for_valid_coordinate2(self): + board = gocounting.GoCounting(board5x5) + stone, territory = board.territoryFor((1, 5)) + self.assertEqual(stone, board.none) + self.assertEqual(territory, set()) + + def test_one_territory_whole_board(self): + board = gocounting.GoCounting(" ") + territories = board.territories() + self.assertEqual(territories[board.black], set()) + self.assertEqual(territories[board.white], set()) + self.assertEqual(territories[board.none], set([(0, 0)])) + + def test_two_territories_rectangular_board(self): + input_board = "\n".join([ + " BW ", + " BW " + ]) + board = gocounting.GoCounting(input_board) + territories = board.territories() + self.assertEqual(territories[board.black], set([(0, 0), (0, 1)])) + self.assertEqual(territories[board.white], set([(3, 0), (3, 1)])) + self.assertEqual(territories[board.none], set()) + + def test_9x9_for_open_territory(self): + board = gocounting.GoCounting(board9x9) + stone, territory = board.territoryFor((0, 8)) + self.assertEqual(stone, board.none) + self.assertEqual(territory, set([(2, 7), (2, 8), (1, 8), (0, 8), (0, 7)])) + + +if __name__ == '__main__': + unittest.main() From 4101f91e432f2297a554db5b0636672c46a7e261 Mon Sep 17 00:00:00 2001 From: Yunchih Chen Date: Mon, 9 Oct 2017 08:40:57 +0800 Subject: [PATCH 2/8] go-counting: Rename files to follow snake_case convention --- config.json | 2 +- exercises/go-counting/{gocounting.py => go_counting.py} | 0 .../go-counting/{gocountingtest.py => go_counting_test.py} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename exercises/go-counting/{gocounting.py => go_counting.py} (100%) rename exercises/go-counting/{gocountingtest.py => go_counting_test.py} (100%) diff --git a/config.json b/config.json index 5b990b0821..53accb1d17 100644 --- a/config.json +++ b/config.json @@ -832,7 +832,7 @@ "topics": [ "parsing", "tuples", - "optional values" + "optional_values" ] }, { diff --git a/exercises/go-counting/gocounting.py b/exercises/go-counting/go_counting.py similarity index 100% rename from exercises/go-counting/gocounting.py rename to exercises/go-counting/go_counting.py diff --git a/exercises/go-counting/gocountingtest.py b/exercises/go-counting/go_counting_test.py similarity index 100% rename from exercises/go-counting/gocountingtest.py rename to exercises/go-counting/go_counting_test.py From 08552ed9fde5fb50716d6c3931aeabf9ccbccffe Mon Sep 17 00:00:00 2001 From: Yunchih Chen Date: Mon, 9 Oct 2017 08:43:08 +0800 Subject: [PATCH 3/8] go-counting: Increase difficulty --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 53accb1d17..4ae8d56cfc 100644 --- a/config.json +++ b/config.json @@ -828,7 +828,7 @@ "slug": "go-counting", "core": false, "unlocked_by": null, - "difficulty": 1, + "difficulty": 4, "topics": [ "parsing", "tuples", From dd454c917d00e80587dbc546a6d7fa6dfa09b64d Mon Sep 17 00:00:00 2001 From: Yunchih Chen Date: Mon, 9 Oct 2017 08:45:43 +0800 Subject: [PATCH 4/8] go-counting: Add topic --- config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index 4ae8d56cfc..f61615d45b 100644 --- a/config.json +++ b/config.json @@ -832,7 +832,8 @@ "topics": [ "parsing", "tuples", - "optional_values" + "optional_values", + "classes" ] }, { From d61e6d34af0c0ce43fca5653ebee1efc191447b3 Mon Sep 17 00:00:00 2001 From: Yunchih Chen Date: Mon, 9 Oct 2017 08:55:35 +0800 Subject: [PATCH 5/8] go-counting: Follow flake8 --- exercises/go-counting/example.py | 19 ++++++++++++------- exercises/go-counting/go_counting_test.py | 4 +++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/exercises/go-counting/example.py b/exercises/go-counting/example.py index b99832f064..0a4f2b0316 100644 --- a/exercises/go-counting/example.py +++ b/exercises/go-counting/example.py @@ -15,23 +15,28 @@ def __init__(self, board): def valid(self, x, y): return x >= 0 and x < self.width and y >= 0 and y < self.height - def walk_board(self, x, y, visited_territory=[], visited_coords=[], visited_stones=[]): + def walk_board(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 self.stones: if s not in visited_stones: return (visited_territory, visited_stones + [s]) - else: # s is empty + else: # s is empty for d in self.directions: - visited_territory, visited_stones = self.walk_board(x + d[0], y + d[1], - visited_territory + [(x, y)], - visited_coords + [(x, y)], - visited_stones) + visited = self.walk_board(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) + assert len(coord) == 2 x, y = coord[0], coord[1] if not self.valid(x, y) or self.board[y][x] in self.stones: return (self.none, set()) diff --git a/exercises/go-counting/go_counting_test.py b/exercises/go-counting/go_counting_test.py index e53227be5d..7475aa72b5 100644 --- a/exercises/go-counting/go_counting_test.py +++ b/exercises/go-counting/go_counting_test.py @@ -21,6 +21,7 @@ " B B " ]) + class GoCountingTest(unittest.TestCase): def test_5x5_for_black(self): board = gocounting.GoCounting(board5x5) @@ -80,7 +81,8 @@ def test_9x9_for_open_territory(self): board = gocounting.GoCounting(board9x9) stone, territory = board.territoryFor((0, 8)) self.assertEqual(stone, board.none) - self.assertEqual(territory, set([(2, 7), (2, 8), (1, 8), (0, 8), (0, 7)])) + self.assertEqual(territory, + set([(2, 7), (2, 8), (1, 8), (0, 8), (0, 7)])) if __name__ == '__main__': From 7453fc5c0cb247a26fa3192eb53934eb4be13d7c Mon Sep 17 00:00:00 2001 From: Yunchih Chen Date: Mon, 30 Oct 2017 22:43:33 +0800 Subject: [PATCH 6/8] go-counting: Add testfile version string --- exercises/go-counting/go_counting_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exercises/go-counting/go_counting_test.py b/exercises/go-counting/go_counting_test.py index 7475aa72b5..a71be8773f 100644 --- a/exercises/go-counting/go_counting_test.py +++ b/exercises/go-counting/go_counting_test.py @@ -1,6 +1,8 @@ import unittest import gocounting +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0 + board5x5 = "\n".join([ " B ", " B B ", From 97e6d7f1e500df8b8d0428d895be72e13fa806ab Mon Sep 17 00:00:00 2001 From: Yunchih Chen Date: Wed, 1 Nov 2017 22:17:15 +0800 Subject: [PATCH 7/8] go-counting: Fix README.md wrong format --- exercises/go-counting/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/go-counting/README.md b/exercises/go-counting/README.md index 43d6903196..0d21809476 100644 --- a/exercises/go-counting/README.md +++ b/exercises/go-counting/README.md @@ -33,7 +33,7 @@ For more information see [wikipedia](https://en.wikipedia.org/wiki/Go_%28game%29) or [Sensei's Library](http://senseis.xmp.net/). -### Submitting Exercises +## Submitting Exercises Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/` directory. From 8f4059c713596355c44478972cd9c84f47e1f77c Mon Sep 17 00:00:00 2001 From: Yunchih Chen Date: Wed, 1 Nov 2017 22:18:20 +0800 Subject: [PATCH 8/8] go-counting: Rename class name & move constants --- exercises/go-counting/example.py | 42 ++++++++++----------- exercises/go-counting/go_counting.py | 2 +- exercises/go-counting/go_counting_test.py | 45 ++++++++++++----------- 3 files changed, 45 insertions(+), 44 deletions(-) diff --git a/exercises/go-counting/example.py b/exercises/go-counting/example.py index 0a4f2b0316..b0ba0ee0ba 100644 --- a/exercises/go-counting/example.py +++ b/exercises/go-counting/example.py @@ -1,12 +1,12 @@ -class GoCounting: +BLACK = "B" +WHITE = "W" +NONE = "" +STONES = [BLACK, WHITE] +DIRECTIONS = [(0, 1), (0, -1), (1, 0), (-1, 0)] - none = "" - white = "W" - black = "B" - 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]) @@ -15,21 +15,21 @@ def __init__(self, board): def valid(self, x, y): return x >= 0 and x < self.width and y >= 0 and y < self.height - def walk_board(self, x, y, - visited_territory=[], - visited_coords=[], - visited_stones=[]): + 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 self.stones: + if s in STONES: if s not in visited_stones: return (visited_territory, visited_stones + [s]) else: # s is empty - for d in self.directions: - visited = self.walk_board(x + d[0], y + d[1], - visited_territory + [(x, y)], - visited_coords + [(x, y)], - visited_stones) + 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] @@ -38,18 +38,18 @@ def walk_board(self, x, y, 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 self.stones: - return (self.none, set()) + if not self.valid(x, y) or self.board[y][x] in STONES: + return (NONE, set()) - visited_territory, visited_stones = self.walk_board(x, y) + visited_territory, visited_stones = self.walk(x, y) result = set(visited_territory) if len(visited_stones) == 1: return (visited_stones[0], result) - return (self.none, result) + return (NONE, result) def territories(self): - owners = self.stones + [self.none] + owners = STONES + [NONE] result = dict([(owner, set()) for owner in owners]) visited = set() for y in range(self.height): diff --git a/exercises/go-counting/go_counting.py b/exercises/go-counting/go_counting.py index dd223a8dc3..a35bf6f7b3 100644 --- a/exercises/go-counting/go_counting.py +++ b/exercises/go-counting/go_counting.py @@ -1,5 +1,5 @@ -class GoCounting: +class Board: """Count territories of each player in a Go game Args: diff --git a/exercises/go-counting/go_counting_test.py b/exercises/go-counting/go_counting_test.py index a71be8773f..207eeaf407 100644 --- a/exercises/go-counting/go_counting_test.py +++ b/exercises/go-counting/go_counting_test.py @@ -1,6 +1,7 @@ import unittest import gocounting + # Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0 board5x5 = "\n".join([ @@ -26,63 +27,63 @@ class GoCountingTest(unittest.TestCase): def test_5x5_for_black(self): - board = gocounting.GoCounting(board5x5) + board = gocounting.Board(board5x5) stone, territory = board.territoryFor((0, 1)) - self.assertEqual(stone, board.black) + self.assertEqual(stone, gocounting.BLACK) self.assertEqual(territory, set([(0, 0), (0, 1), (1, 0)])) def test_5x5_for_white(self): - board = gocounting.GoCounting(board5x5) + board = gocounting.Board(board5x5) stone, territory = board.territoryFor((2, 3)) - self.assertEqual(stone, board.white) + self.assertEqual(stone, gocounting.WHITE) self.assertEqual(territory, set([(2, 3)])) def test_5x5_for_open_territory(self): - board = gocounting.GoCounting(board5x5) + board = gocounting.Board(board5x5) stone, territory = board.territoryFor((1, 4)) - self.assertEqual(stone, board.none) + self.assertEqual(stone, gocounting.NONE) self.assertEqual(territory, set([(0, 3), (0, 4), (1, 4)])) def test_5x5_for_non_territory(self): - board = gocounting.GoCounting(board5x5) + board = gocounting.Board(board5x5) stone, territory = board.territoryFor((1, 1)) - self.assertEqual(stone, board.none) + self.assertEqual(stone, gocounting.NONE) self.assertEqual(territory, set()) def test_5x5_for_valid_coordinate(self): - board = gocounting.GoCounting(board5x5) + board = gocounting.Board(board5x5) stone, territory = board.territoryFor((-1, 1)) - self.assertEqual(stone, board.none) + self.assertEqual(stone, gocounting.NONE) self.assertEqual(territory, set()) def test_5x5_for_valid_coordinate2(self): - board = gocounting.GoCounting(board5x5) + board = gocounting.Board(board5x5) stone, territory = board.territoryFor((1, 5)) - self.assertEqual(stone, board.none) + self.assertEqual(stone, gocounting.NONE) self.assertEqual(territory, set()) def test_one_territory_whole_board(self): - board = gocounting.GoCounting(" ") + board = gocounting.Board(" ") territories = board.territories() - self.assertEqual(territories[board.black], set()) - self.assertEqual(territories[board.white], set()) - self.assertEqual(territories[board.none], set([(0, 0)])) + 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.GoCounting(input_board) + board = gocounting.Board(input_board) territories = board.territories() - self.assertEqual(territories[board.black], set([(0, 0), (0, 1)])) - self.assertEqual(territories[board.white], set([(3, 0), (3, 1)])) - self.assertEqual(territories[board.none], set()) + 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.GoCounting(board9x9) + board = gocounting.Board(board9x9) stone, territory = board.territoryFor((0, 8)) - self.assertEqual(stone, board.none) + self.assertEqual(stone, gocounting.NONE) self.assertEqual(territory, set([(2, 7), (2, 8), (1, 8), (0, 8), (0, 7)]))