def parse(boardstr): '''Parses a board into a gamestate, and returns the location of any moves marked with anything other than 'B', 'X', '#', 'W', 'O', or '.' Rows are separated by '|', spaces are ignored. ''' boardstr = boardstr.replace(' ', '') board_size = max(boardstr.index('|'), boardstr.count('|')) st = GameState(size=board_size) moves = {} for row, rowstr in enumerate(boardstr.split('|')): for col, c in enumerate(rowstr): if c == '.': continue # ignore empty spaces elif c in 'BX#': st.do_move((row, col), color=BLACK) elif c in 'WO': st.do_move((row, col), color=WHITE) else: # move reference assert c not in moves, "{} already used as a move marker".format(c) moves[c] = (row, col) return st, moves
def capture_board(): gs = GameState(size=7) # another small board, this one with imminent captures # # X # 0 1 2 3 4 5 6 # . . B B . . . 0 # . B W W B . . 1 # . B W . . . . 2 # Y . . B . . . . 3 # . . . . W B . 4 # . . . W . W B 5 # . . . . W B . 6 # # current_player = black black = [(2, 0), (3, 0), (1, 1), (4, 1), (1, 2), (2, 3), (5, 4), (6, 5), (5, 6)] white = [(2, 1), (3, 1), (2, 2), (4, 4), (3, 5), (5, 5), (4, 6)] for B in black: gs.do_move(B, go.BLACK) for W in white: gs.do_move(W, go.WHITE) gs.set_current_player(go.BLACK) return gs
class play_match(object): """Interface to handle play between two players.""" def __init__(self, player1, player2, save_dir=None, size=19): # super(ClassName, self).__init__() self.player1 = player1 self.player2 = player2 self.state = GameState(size=size) # I Propose that GameState should take a top-level save directory, # then automatically generate the specific file name def _play(self, player): move = player.get_move(self.state) # TODO: Fix is_eye? self.state.do_move(move) # Return max prob sensible legal move # self.state.write_to_disk() if len(self.state.history) > 1: if self.state.history[-1] is None and self.state.history[-2] is None \ and self.state.current_player == -1: end_of_game = True else: end_of_game = False else: end_of_game = False return end_of_game def play(self): """Play one turn, update game state, save to disk""" end_of_game = self._play(self.player1) # This is incorrect. return end_of_game
class play_match(object): """Interface to handle play between two players.""" def __init__(self, player1, player2, save_dir=None, size=19): # super(ClassName, self).__init__() self.player1 = player1 self.player2 = player2 self.state = GameState(size=size) # I Propose that GameState should take a top-level save directory, # then automatically generate the specific file name def _play(self, player): move = player.get_move(self.state) # TODO: Fix is_eye? self.state.do_move(move) # Return max prob sensible legal move # self.state.write_to_disk() if len(self.state.history) > 1: if self.state.history[-1] is None and self.state.history[-2] is None and self.state.current_player == -1: end_of_game = True else: end_of_game = False else: end_of_game = False return end_of_game def play(self): """Play one turn, update game state, save to disk""" end_of_game = self._play(self.player1) # This is incorrect. return end_of_game
class GTPGameConnector(object): """A class implementing the functions of a 'game' object required by the GTP Engine by wrapping a GameState and Player instance """ def __init__(self, player): self._state = GameState(enforce_superko=True) self._player = player self._komi = 0 def clear(self): self._state = GameState() def make_move(self, color, vertex): # vertex in GTP language is 1-indexed, whereas GameState's are zero-indexed if color == gtp.BLACK: color = go.BLACK else: color = go.WHITE try: if vertex == gtp.PASS: self._state.do_move(go.PASS) else: (x, y) = vertex self._state.do_move((x - 1, y - 1), color) return True except go.IllegalMove: return False def set_size(self, n): self._state = GameState(size=n, enforce_superko=True) def set_komi(self, k): self._komi = k def get_move(self, color): if color == gtp.BLACK: color = go.BLACK else: color = go.WHITE self._state.set_current_player(color) move = self._player.get_move(self._state) if move == go.PASS: return gtp.PASS else: (x, y) = move return (x + 1, y + 1) def get_current_state_as_sgf(self): from tempfile import NamedTemporaryFile temp_file = NamedTemporaryFile(delete=False) save_gamestate_to_sgf(self._state, '', temp_file.name) return temp_file.name def place_handicaps(self, vertices): actions = [] for vertex in vertices: (x, y) = vertex actions.append((x - 1, y - 1)) self._state.place_handicaps(actions)
def test_probabilistic_player(self): gs = GameState() policy = CNNPolicy(["board", "ones", "turns_since"]) player = ProbabilisticPolicyPlayer(policy) for i in range(20): move = player.get_move(gs) self.assertIsNotNone(move) gs.do_move(move)
def test_greedy_player(self): gs = GameState() policy = CNNPolicy(["board", "ones", "turns_since"]) player = GreedyPolicyPlayer(policy) for _ in range(20): move = player.get_move(gs) self.assertNotEqual(move, go.PASS) gs.do_move(move)
def test_eye_recursion(self): # a checkerboard pattern of black is 'technically' all true eyes # mutually supporting each other gs = GameState(7) for x in range(gs.size): for y in range(gs.size): if (x + y) % 2 == 1: gs.do_move((x, y), go.BLACK) self.assertTrue(gs.is_eye((0, 0), go.BLACK))
def test_probabilistic_player(self): gs = GameState(size=9) value = CNNValue(["board", "ones", "turns_since"], board=9) player = ValuePlayer(value) for i in range(10): move = player.get_move(gs) self.assertNotEqual(move, go.PASS) gs.do_move(move)
def test_sensible_greedy(self): gs = GameState() policy = CNNPolicy(["board", "ones", "turns_since"]) player = GreedyPolicyPlayer(policy) empty = (10, 10) for x in range(19): for y in range(19): if (x, y) != empty: gs.do_move((x, y), go.BLACK) gs.current_player = go.BLACK self.assertIsNone(player.get_move(gs))
def test_sensible_probabilistic(self): gs = GameState() policy = CNNPolicy(["board", "ones", "turns_since"]) player = ProbabilisticPolicyPlayer(policy) empty = (10, 10) for x in range(19): for y in range(19): if (x, y) != empty: gs.do_move((x, y), go.BLACK) gs.set_current_player(go.BLACK) self.assertEqual(player.get_move(gs), go.PASS)
def test_copy_maintains_shared_sets(self): gs = GameState(7) gs.do_move((4, 4), go.BLACK) gs.do_move((4, 5), go.BLACK) # assert that gs has *the same object* referenced by group/liberty sets self.assertTrue(gs.group_sets[4][5] is gs.group_sets[4][4]) self.assertTrue(gs.liberty_sets[4][5] is gs.liberty_sets[4][4]) gs_copy = gs.copy() self.assertTrue(gs_copy.group_sets[4][5] is gs_copy.group_sets[4][4]) self.assertTrue( gs_copy.liberty_sets[4][5] is gs_copy.liberty_sets[4][4])
def test_sensible_greedy(self): gs = GameState() value = CNNValue(["board", "ones", "turns_since"]) player = ValuePlayer(value, greedy_start=0) empty = (10, 10) for x in range(19): for y in range(19): if (x, y) != empty: gs.do_move((x, y), go.BLACK) gs.set_current_player(go.BLACK) self.assertEqual(player.get_move(gs), go.PASS)
def test_simple_eye(self): # create a black eye in top left (1,1), white in bottom right (5,5) gs = GameState(size=7) gs.do_move((1, 0)) # B gs.do_move((5, 4)) # W gs.do_move((2, 1)) # B gs.do_move((6, 5)) # W gs.do_move((1, 2)) # B gs.do_move((5, 6)) # W gs.do_move((0, 1)) # B gs.do_move((4, 5)) # W # test black eye top left self.assertTrue(gs.is_eye((1, 1), go.BLACK)) self.assertFalse(gs.is_eye((1, 1), go.WHITE)) # test white eye bottom right self.assertTrue(gs.is_eye((5, 5), go.WHITE)) self.assertFalse(gs.is_eye((5, 5), go.BLACK)) # test no eye in other random positions self.assertFalse(gs.is_eye((1, 0), go.BLACK)) self.assertFalse(gs.is_eye((1, 0), go.WHITE)) self.assertFalse(gs.is_eye((2, 2), go.BLACK)) self.assertFalse(gs.is_eye((2, 2), go.WHITE))
class TestLiberties(unittest.TestCase): def setUp(self): self.s = GameState() self.s.do_move((4,5)) self.s.do_move((5,5)) self.s.do_move((5,6)) self.s.do_move((10,10)) self.s.do_move((4,6)) self.syms = self.s.symmetries() def test_lib_count(self): self.assertEqual(self.s.liberty_count(5,5), 2) print("liberty_count checked") def test_lib_pos(self): self.assertEqual(self.s.liberty_pos(5,5), ((6,5), (5,4))) print("liberty_pos checked") def test_curr_liberties(self): self.assertEqual(self.s.update_current_liberties()[5][5], 2) self.assertEqual(self.s.update_current_liberties()[4][5], 6) self.assertEqual(self.s.update_current_liberties()[5][6], 6) print("curr_liberties checked") def test_future_liberties(self): self.assertEqual(self.s.update_future_liberties((6,5))[6][5], 4) self.assertEqual(self.s.update_future_liberties((5,4))[5][4], 4) self.assertEqual(self.s.update_future_liberties((6,6))[5][6], 5) print("future_liberties checked")
def test_positional_superko(self): move_list = [(0, 3), (0, 4), (1, 3), (1, 4), (2, 3), (2, 4), (2, 2), (3, 4), (2, 1), (3, 3), (3, 1), (3, 2), (3, 0), (4, 2), (1, 1), (4, 1), (8, 0), (4, 0), (8, 1), (0, 2), (8, 2), (0, 1), (8, 3), (1, 0), (8, 4), (2, 0), (0, 0)] gs = GameState(size=9) for move in move_list: gs.do_move(move) self.assertTrue(gs.is_legal((1, 0))) gs = GameState(size=9, enforce_superko=True) for move in move_list: gs.do_move(move) self.assertFalse(gs.is_legal((1, 0)))
def test_snapback_is_not_ko(self): gs = GameState(size=5) # B o W B . # W W B . . # . . . . . # . . . . . # . . . . . # here, imagine black plays at 'o' capturing # the white stone at (2,0). White may play # again at (2,0) to capture the black stones # at (0,0), (1,0). this is 'snapback' not 'ko' # since it doesn't return the game to a # previous position B = [(0, 0), (2, 1), (3, 0)] W = [(0, 1), (1, 1), (2, 0)] for (b, w) in zip(B, W): gs.do_move(b) gs.do_move(w) # do the capture of the single white stone gs.do_move((1, 0)) # there should be no ko self.assertIsNone(gs.ko) self.assertTrue(gs.is_legal((2, 0))) # now play the snapback gs.do_move((2, 0)) # check that the numbers worked out self.assertEqual(gs.num_black_prisoners, 2) self.assertEqual(gs.num_white_prisoners, 1)
def test_hash_update_matches_actual_hash(self): gs = GameState(size=7) gs, moves = parseboard.parse("a x b . . . .|" "z c d . . . .|" ". . . . . . .|" ". . . y . . .|" ". . . . . . .|" ". . . . . . .|" ". . . . . . .|") # a,b,c,d are black, x,y,z,x are white move_order = ['a', 'x', 'b', 'y', 'c', 'z', 'd', 'x'] for m in move_order: move_1d = flatten_idx(moves[m], gs.get_size()) # 'Try' move and get hash with gs.try_stone(move_1d): hash1 = gs.get_hash() # Actually do move and get hash gs.do_move(moves[m]) hash2 = gs.get_hash() self.assertEqual(hash1, hash2)
def test_neighbors_edge_cases(self): st = GameState() st.do_move((0, 0)) # B B . . . . . st.do_move((5, 5)) # B W . . . . . st.do_move((0, 1)) # . . . . . . . st.do_move((6, 6)) # . . . . . . . st.do_move((1, 0)) # . . . . . W . st.do_move((1, 1)) # . . . . . . W # get_group in the corner self.assertEqual(len(st.get_group((0, 0))), 3, "group size in corner") # get_group of an empty space self.assertEqual(len(st.get_group((4, 4))), 0, "group size of empty space") # get_group of a single piece self.assertEqual(len(st.get_group((5, 5))), 1, "group size of single piece")
def test_true_eye(self): gs = GameState(size=7) gs.do_move((1, 0), go.BLACK) gs.do_move((0, 1), go.BLACK) # false eye at 0,0 self.assertTrue(gs.is_eyeish((0, 0), go.BLACK)) self.assertFalse(gs.is_eye((0, 0), go.BLACK)) # make it a true eye by turning the corner (1,1) into an eye itself gs.do_move((1, 2), go.BLACK) gs.do_move((2, 1), go.BLACK) gs.do_move((2, 2), go.BLACK) gs.do_move((0, 2), go.BLACK) self.assertTrue(gs.is_eyeish((0, 0), go.BLACK)) self.assertTrue(gs.is_eye((0, 0), go.BLACK)) self.assertTrue(gs.is_eye((1, 1), go.BLACK))
def test_true_eye(self): gs = GameState(size=7) gs.do_move((1, 0), go.BLACK) gs.do_move((0, 1), go.BLACK) # false eye at 0, 0 self.assertTrue(gs.is_eyeish((0, 0), go.BLACK)) self.assertFalse(gs.is_eye((0, 0), go.BLACK)) # make it a true eye by turning the corner (1, 1) into an eye itself gs.do_move((1, 2), go.BLACK) gs.do_move((2, 1), go.BLACK) gs.do_move((2, 2), go.BLACK) gs.do_move((0, 2), go.BLACK) self.assertTrue(gs.is_eyeish((0, 0), go.BLACK)) self.assertTrue(gs.is_eye((0, 0), go.BLACK)) self.assertTrue(gs.is_eye((1, 1), go.BLACK))
class TestLiberties(unittest.TestCase): def setUp(self): self.s = GameState() self.s.do_move((4, 5)) self.s.do_move((5, 5)) self.s.do_move((5, 6)) self.s.do_move((10, 10)) self.s.do_move((4, 6)) self.s.do_move((10, 11)) self.s.do_move((6, 6)) self.s.do_move((9, 10)) def test_curr_liberties(self): self.assertEqual(self.s.liberty_counts[5][5], 2) self.assertEqual(self.s.liberty_counts[4][5], 8) self.assertEqual(self.s.liberty_counts[5][6], 8) def test_neighbors_edge_cases(self): st = GameState() st.do_move((0, 0)) # B B . . . . . st.do_move((5, 5)) # B W . . . . . st.do_move((0, 1)) # . . . . . . . st.do_move((6, 6)) # . . . . . . . st.do_move((1, 0)) # . . . . . W . st.do_move((1, 1)) # . . . . . . W # get_group in the corner self.assertEqual(len(st.get_group((0, 0))), 3, "group size in corner") # get_group of an empty space self.assertEqual(len(st.get_group((4, 4))), 0, "group size of empty space") # get_group of a single piece self.assertEqual(len(st.get_group((5, 5))), 1, "group size of single piece")
class TestSymmetries(unittest.TestCase): def setUp(self): self.s = GameState() self.s.do_move((4, 5)) self.s.do_move((5, 5)) self.s.do_move((5, 6)) self.syms = self.s.symmetries() def test_num_syms(self): # make sure we got exactly 8 back self.assertEqual(len(self.syms), 8) def test_copy_fields(self): # make sure each copy has the correct non-board fields for copy in self.syms: self.assertEqual(self.s.size, copy.size) self.assertEqual(self.s.turns_played, copy.turns_played) self.assertEqual(self.s.current_player, copy.current_player) def test_sym_boards(self): # construct by hand the 8 boards we expect to see expectations = [GameState() for i in range(8)] descriptions = [ "noop", "rot90", "rot180", "rot270", "mirror LR", "mirror UD", "mirror \\", "mirror /" ] # copy of self.s expectations[0].do_move((4, 5)) expectations[0].do_move((5, 5)) expectations[0].do_move((5, 6)) # rotate 90 CCW expectations[1].do_move((13, 4)) expectations[1].do_move((13, 5)) expectations[1].do_move((12, 5)) # rotate 180 expectations[2].do_move((14, 13)) expectations[2].do_move((13, 13)) expectations[2].do_move((13, 12)) # rotate CCW 270 expectations[3].do_move((5, 14)) expectations[3].do_move((5, 13)) expectations[3].do_move((6, 13)) # mirror left-right expectations[4].do_move((4, 13)) expectations[4].do_move((5, 13)) expectations[4].do_move((5, 12)) # mirror up-down expectations[5].do_move((14, 5)) expectations[5].do_move((13, 5)) expectations[5].do_move((13, 6)) # mirror \ diagonal expectations[6].do_move((5, 4)) expectations[6].do_move((5, 5)) expectations[6].do_move((6, 5)) # mirror / diagonal (equivalently: rotate 90 CCW then flip LR) expectations[7].do_move((13, 14)) expectations[7].do_move((13, 13)) expectations[7].do_move((12, 13)) for i in range(8): self.assertTrue( np.array_equal(expectations[i].board, self.syms[i].board), descriptions[i])
class TestMCTS(unittest.TestCase): def setUp(self): self.gs = GameState() self.mcts = MCTS(dummy_value, dummy_policy, dummy_rollout, n_playout=2) def _count_expansions(self): """Helper function to count the number of expansions past the root using the dummy policy """ node = self.mcts._root expansions = 0 # Loop over actions in decreasing probability. for action, _ in sorted(dummy_policy(self.gs), key=itemgetter(1), reverse=True): if action in node._children: expansions += 1 node = node._children[action] else: break return expansions def test_playout(self): self.mcts._playout(self.gs.copy(), 8) # Assert that the most likely child was visited (according to the dummy policy below). self.assertEqual(1, self.mcts._root._children[(18, 18)]._n_visits) # Assert that the search depth expanded nodes 8 times. self.assertEqual(8, self._count_expansions()) def test_playout_with_pass(self): # Test that playout handles the end of the game (i.e. passing/no moves). Mock this by # creating a policy that returns nothing after 4 moves. def stop_early_policy(state): if len(state.history) <= 4: return dummy_policy(state) else: return [] self.mcts = MCTS(dummy_value, stop_early_policy, stop_early_policy, n_playout=2) self.mcts._playout(self.gs.copy(), 8) # Assert that (18, 18) and (18, 17) are still only visited once. self.assertEqual(1, self.mcts._root._children[(18, 18)]._n_visits) # Assert that no expansions happened after reaching the "end" in 4 moves. self.assertEqual(5, self._count_expansions()) def test_get_move(self): move = self.mcts.get_move(self.gs) self.mcts.update_with_move(move) # success if no errors def test_update_with_move(self): move = self.mcts.get_move(self.gs) self.gs.do_move(move) self.mcts.update_with_move(move) # Assert that the new root still has children. self.assertTrue(len(self.mcts._root._children) > 0) # Assert that the new root has no parent (the rest of the tree will be garbage collected). self.assertIsNone(self.mcts._root._parent) # Assert that the next best move according to the root is (18, 17), according to the # dummy policy below. self.assertEqual((18, 17), self.mcts._root.select()[0])
def test_liberties_after_capture(self): # creates 3x3 black group in the middle, that is then all captured # ...then an assertion is made that the resulting liberties after # capture are the same as if the group had never been there gs_capture = GameState(7) gs_reference = GameState(7) # add in 3x3 black stones for x in range(2, 5): for y in range(2, 5): gs_capture.do_move((x, y), go.BLACK) # surround the black group with white stones # and set the same white stones in gs_reference for x in range(2, 5): gs_capture.do_move((x, 1), go.WHITE) gs_capture.do_move((x, 5), go.WHITE) gs_reference.do_move((x, 1), go.WHITE) gs_reference.do_move((x, 5), go.WHITE) gs_capture.do_move((1, 1), go.WHITE) gs_reference.do_move((1, 1), go.WHITE) for y in range(2, 5): gs_capture.do_move((1, y), go.WHITE) gs_capture.do_move((5, y), go.WHITE) gs_reference.do_move((1, y), go.WHITE) gs_reference.do_move((5, y), go.WHITE) # board configuration and liberties of gs_capture and of gs_reference should be identical self.assertTrue(np.all(gs_reference.board == gs_capture.board)) self.assertTrue(np.all(gs_reference.liberty_counts == gs_capture.liberty_counts))
def test_liberties_after_capture(self): # creates 3x3 black group in the middle, that is then all captured # ...then an assertion is made that the resulting liberties after # capture are the same as if the group had never been there gs_capture = GameState(7) gs_reference = GameState(7) # add in 3x3 black stones for x in range(2, 5): for y in range(2, 5): gs_capture.do_move((x, y), go.BLACK) # surround the black group with white stones # and set the same white stones in gs_reference for x in range(2, 5): gs_capture.do_move((x, 1), go.WHITE) gs_capture.do_move((x, 5), go.WHITE) gs_reference.do_move((x, 1), go.WHITE) gs_reference.do_move((x, 5), go.WHITE) gs_capture.do_move((1, 1), go.WHITE) gs_reference.do_move((1, 1), go.WHITE) for y in range(2, 5): gs_capture.do_move((1, y), go.WHITE) gs_capture.do_move((5, y), go.WHITE) gs_reference.do_move((1, y), go.WHITE) gs_reference.do_move((5, y), go.WHITE) # board configuration and liberties of gs_capture and of gs_reference should be identical self.assertTrue(np.all(gs_reference.board == gs_capture.board)) self.assertTrue( np.all(gs_reference.liberty_counts == gs_capture.liberty_counts))
class TestLiberties(unittest.TestCase): def setUp(self): self.s = GameState() self.s.do_move((4,5)) self.s.do_move((5,5)) self.s.do_move((5,6)) self.s.do_move((10,10)) self.s.do_move((4,6)) self.s.do_move((10,11)) self.s.do_move((6,6)) self.s.do_move((9, 10)) self.syms = self.s.symmetries() def test_lib_count(self): self.assertEqual(self.s.liberty_count((5,5)), 2) print("liberty_count checked") def test_lib_pos(self): self.assertEqual(self.s.liberty_pos((5,5)), [(6,5), (5,4)]) print("liberty_pos checked") def test_curr_liberties(self): self.assertEqual(self.s.update_current_liberties()[5][5], 2) self.assertEqual(self.s.update_current_liberties()[4][5], 8) self.assertEqual(self.s.update_current_liberties()[5][6], 8) print("curr_liberties checked") def test_future_liberties(self): print(self.s.update_future_liberties((4,4))) self.assertEqual(self.s.update_future_liberties((6,5))[6][5], 9) self.assertEqual(self.s.update_future_liberties((5,4))[5][4], 3) self.assertEqual(self.s.update_future_liberties((4,4))[4][4], 10) print("future_liberties checked") def test_neighbors_edge_cases(self): st = GameState() st.do_move((0,0)) # B B . . . . . st.do_move((5,5)) # B W . . . . . st.do_move((0,1)) # . . . . . . . st.do_move((6,6)) # . . . . . . . st.do_move((1,0)) # . . . . . W . st.do_move((1,1)) # . . . . . . W # visit_neighbor in the corner self.assertEqual(len(st.visit_neighbor((0,0))), 3, "group size in corner") # visit_neighbor of an empty space self.assertEqual(len(st.visit_neighbor((4,4))), 0, "group size of empty space") # visit_neighbor of a single piece self.assertEqual(len(st.visit_neighbor((5,5))), 1, "group size of single piece")
def test_standard_ko(self): gs = GameState(size=9) gs.do_move((1, 0)) # B gs.do_move((2, 0)) # W gs.do_move((0, 1)) # B gs.do_move((3, 1)) # W gs.do_move((1, 2)) # B gs.do_move((2, 2)) # W gs.do_move((2, 1)) # B gs.do_move((1, 1)) # W trigger capture and ko self.assertEqual(gs.num_black_prisoners, 1) self.assertEqual(gs.num_white_prisoners, 0) self.assertFalse(gs.is_legal((2, 1))) gs.do_move((5, 5)) gs.do_move((5, 6)) self.assertTrue(gs.is_legal((2, 1)))
def self_atari_board(): gs = GameState(size=7) # another tiny board for testing self-atari specifically. # positions marked with 'a' are self-atari for black # # X # 0 1 2 3 4 5 6 # a W . . . W B 0 # . . . . . . . 1 # . . . . . . . 2 # Y . . W . W . . 3 # . W B a B W . 4 # . . W W W . . 5 # . . . . . . . 6 # # current_player = black gs.do_move((2, 4), go.BLACK) gs.do_move((4, 4), go.BLACK) gs.do_move((6, 0), go.BLACK) gs.do_move((1, 0), go.WHITE) gs.do_move((5, 0), go.WHITE) gs.do_move((2, 3), go.WHITE) gs.do_move((4, 3), go.WHITE) gs.do_move((1, 4), go.WHITE) gs.do_move((5, 4), go.WHITE) gs.do_move((2, 5), go.WHITE) gs.do_move((3, 5), go.WHITE) gs.do_move((4, 5), go.WHITE) return gs
def simple_board(): gs = GameState(size=7) # make a tiny board for the sake of testing and hand-coding expected results # # X # 0 1 2 3 4 5 6 # B W . . . . . 0 # B W . . . . . 1 # B . . . B . . 2 # Y . . . B k B . 3 # . . . W B W . 4 # . . . . W . . 5 # . . . . . . . 6 # # where k is a ko position (white was just captured) # ladder-looking thing in the top-left gs.do_move((0, 0)) # B gs.do_move((1, 0)) # W gs.do_move((0, 1)) # B gs.do_move((1, 1)) # W gs.do_move((0, 2)) # B # ko position in the middle gs.do_move((3, 4)) # W gs.do_move((3, 3)) # B gs.do_move((4, 5)) # W gs.do_move((4, 2)) # B gs.do_move((5, 4)) # W gs.do_move((5, 3)) # B gs.do_move((4, 3)) # W - the ko position gs.do_move((4, 4)) # B - does the capture return gs
class TestLiberties(unittest.TestCase): def setUp(self): self.s = GameState() self.s.do_move((4, 5)) self.s.do_move((5, 5)) self.s.do_move((5, 6)) self.s.do_move((10, 10)) self.s.do_move((4, 6)) self.s.do_move((10, 11)) self.s.do_move((6, 6)) self.s.do_move((9, 10)) self.syms = self.s.symmetries() def test_lib_count(self): self.assertEqual(self.s.liberty_count((5, 5)), 2) print("liberty_count checked") def test_lib_pos(self): self.assertEqual(self.s.liberty_pos((5, 5)), [(6, 5), (5, 4)]) print("liberty_pos checked") def test_curr_liberties(self): self.assertEqual(self.s.update_current_liberties()[5][5], 2) self.assertEqual(self.s.update_current_liberties()[4][5], 8) self.assertEqual(self.s.update_current_liberties()[5][6], 8) print("curr_liberties checked") def test_future_liberties(self): print(self.s.update_future_liberties((4, 4))) self.assertEqual(self.s.update_future_liberties((6, 5))[6][5], 9) self.assertEqual(self.s.update_future_liberties((5, 4))[5][4], 3) self.assertEqual(self.s.update_future_liberties((4, 4))[4][4], 10) print("future_liberties checked") def test_neighbors_edge_cases(self): st = GameState() st.do_move((0, 0)) # B B . . . . . st.do_move((5, 5)) # B W . . . . . st.do_move((0, 1)) # . . . . . . . st.do_move((6, 6)) # . . . . . . . st.do_move((1, 0)) # . . . . . W . st.do_move((1, 1)) # . . . . . . W # visit_neighbor in the corner self.assertEqual(len(st.visit_neighbor((0, 0))), 3, "group size in corner") # visit_neighbor of an empty space self.assertEqual(len(st.visit_neighbor((4, 4))), 0, "group size of empty space") # visit_neighbor of a single piece self.assertEqual(len(st.visit_neighbor((5, 5))), 1, "group size of single piece")
class TestSymmetries(unittest.TestCase): def setUp(self): self.s = GameState() self.s.do_move((4,5)) self.s.do_move((5,5)) self.s.do_move((5,6)) self.syms = self.s.symmetries() def test_num_syms(self): # make sure we got exactly 8 back self.assertEqual(len(self.syms), 8) def test_copy_fields(self): # make sure each copy has the correct non-board fields for copy in self.syms: self.assertEqual(self.s.size, copy.size) self.assertEqual(self.s.turns_played, copy.turns_played) self.assertEqual(self.s.current_player, copy.current_player) def test_sym_boards(self): # construct by hand the 8 boards we expect to see expectations = [GameState() for i in range(8)] descriptions = ["noop", "rot90", "rot180", "rot270", "mirror LR", "mirror UD", "mirror \\", "mirror /"] # copy of self.s expectations[0].do_move((4,5)) expectations[0].do_move((5,5)) expectations[0].do_move((5,6)) # rotate 90 CCW expectations[1].do_move((13,4)) expectations[1].do_move((13,5)) expectations[1].do_move((12,5)) # rotate 180 expectations[2].do_move((14,13)) expectations[2].do_move((13,13)) expectations[2].do_move((13,12)) # rotate CCW 270 expectations[3].do_move((5,14)) expectations[3].do_move((5,13)) expectations[3].do_move((6,13)) # mirror left-right expectations[4].do_move((4,13)) expectations[4].do_move((5,13)) expectations[4].do_move((5,12)) # mirror up-down expectations[5].do_move((14,5)) expectations[5].do_move((13,5)) expectations[5].do_move((13,6)) # mirror \ diagonal expectations[6].do_move((5,4)) expectations[6].do_move((5,5)) expectations[6].do_move((6,5)) # mirror / diagonal (equivalently: rotate 90 CCW then flip LR) expectations[7].do_move((13,14)) expectations[7].do_move((13,13)) expectations[7].do_move((12,13)) for i in range(8): self.assertTrue(np.array_equal(expectations[i].board, self.syms[i].board), descriptions[i])