def test_chinese_handicap_handling(self): intermediate_board = utils_test.load_board(''' ......... ......... ......X.. ......... ......... ......... ......... ......... ......... ''') intermediate_position = go.Position( utils_test.BOARD_SIZE, intermediate_board, n=1, komi=5.5, caps=(0, 0), recent=(go.PlayerMove(go.BLACK, coords.from_kgs( utils_test.BOARD_SIZE, 'G7')),), to_play=go.BLACK, ) final_board = utils_test.load_board(''' ....OX... .O.OOX... O.O.X.X.. .OXXX.... OX...XX.. .X.XXO... X.XOOXXX. XXXO.OOX. .XOOX.O.. ''') final_position = go.Position( utils_test.BOARD_SIZE, final_board, n=50, komi=5.5, caps=(7, 2), ko=None, recent=( go.PlayerMove( go.WHITE, coords.from_kgs(utils_test.BOARD_SIZE, 'E9')), go.PlayerMove( go.BLACK, coords.from_kgs(utils_test.BOARD_SIZE, 'F9')),), to_play=go.WHITE ) positions_w_context = list(replay_sgf( utils_test.BOARD_SIZE, CHINESE_HANDICAP_SGF)) self.assertEqualPositions( intermediate_position, positions_w_context[1].position) self.assertEqual( positions_w_context[1].next_move, coords.from_kgs( utils_test.BOARD_SIZE, 'C3')) final_replayed_position = positions_w_context[-1].position.play_move( positions_w_context[-1].next_move) self.assertEqualPositions(final_position, final_replayed_position)
def test_japanese_handicap_handling(self): intermediate_board = utils_test.load_board(''' ......... ......... ......X.. ......... ....O.... ......... ..X...... ......... ......... ''') intermediate_position = go.Position( utils_test.BOARD_SIZE, intermediate_board, n=1, komi=5.5, caps=(0, 0), recent=(go.PlayerMove(go.WHITE, coords.from_kgs( utils_test.BOARD_SIZE, 'E5')),), to_play=go.BLACK, ) final_board = utils_test.load_board(''' ......... ......... ......X.. ......... ....O.... ......... ..XX..... ......... ......... ''') final_position = go.Position( utils_test.BOARD_SIZE, final_board, n=2, komi=5.5, caps=(0, 0), recent=( go.PlayerMove(go.WHITE, coords.from_kgs( utils_test.BOARD_SIZE, 'E5')), go.PlayerMove(go.BLACK, coords.from_kgs( utils_test.BOARD_SIZE, 'D3')),), to_play=go.WHITE, ) positions_w_context = list(replay_sgf( utils_test.BOARD_SIZE, JAPANESE_HANDICAP_SGF)) self.assertEqualPositions( intermediate_position, positions_w_context[1].position) final_replayed_position = positions_w_context[-1].position.play_move( positions_w_context[-1].next_move) self.assertEqualPositions(final_position, final_replayed_position)
def test_inference(self): with tempfile.TemporaryDirectory() as working_dir, \ tempfile.TemporaryDirectory() as export_dir: dualnet.bootstrap(working_dir, model_params.DummyMiniGoParams()) exported_model = os.path.join(export_dir, 'bootstrap-model') dualnet.export_model(working_dir, exported_model) n1 = dualnet.DualNetRunner( exported_model, model_params.DummyMiniGoParams()) n1.run(go.Position(utils_test.BOARD_SIZE)) n2 = dualnet.DualNetRunner( exported_model, model_params.DummyMiniGoParams()) n2.run(go.Position(utils_test.BOARD_SIZE))
def test_add_child(self): root = MCTSNode(utils_test.BOARD_SIZE, go.Position(utils_test.BOARD_SIZE)) child = root.maybe_add_child(17) self.assertIn(17, root.children) self.assertEqual(child.parent, root) self.assertEqual(child.fmove, 17)
def test_only_check_game_end_once(self): # When presented with a situation where the last move was a pass, # and we have to decide whether to pass, it should be the first thing # we check, but not more than that. white_passed_pos = go.Position(utils_test.BOARD_SIZE, ).play_move( (3, 3) # b plays ).play_move( (3, 4) # w plays ).play_move( (4, 3) # b plays ).pass_move() # w passes - if B passes too, B would lose by komi. player = MCTSPlayerMixin(utils_test.BOARD_SIZE, DummyNet()) player.initialize_game(white_passed_pos) # initialize the root player.tree_search() # explore a child - should be a pass move. player.tree_search() pass_move = utils_test.BOARD_SIZE * utils_test.BOARD_SIZE self.assertEqual(player.root.children[pass_move].N, 1) self.assertEqual(player.root.child_N[pass_move], 1) player.tree_search() # check that we didn't visit the pass node any more times. self.assertEqual(player.root.child_N[pass_move], 1)
def test_is_game_over(self): root = go.Position(utils_test.BOARD_SIZE) self.assertFalse(root.is_game_over()) first_pass = root.play_move(None) self.assertFalse(first_pass.is_game_over()) second_pass = first_pass.play_move(None) self.assertTrue(second_pass.is_game_over())
def test_add_child_idempotency(self): root = MCTSNode(utils_test.BOARD_SIZE, go.Position(utils_test.BOARD_SIZE)) child = root.maybe_add_child(17) current_children = copy.copy(root.children) child2 = root.maybe_add_child(17) self.assertEqual(child, child2) self.assertEqual(current_children, root.children)
def initialize_game(self, position=None): if position is None: position = go.Position(self.board_size) self.root = MCTSNode(self.board_size, position) self.result = 0 self.result_string = None self.comments = [] self.searches_pi = [] self.qs = []
def test_action_flipping(self): np.random.seed(1) probs = np.array([.02] * (utils_test.BOARD_SIZE * utils_test.BOARD_SIZE + 1)) probs += np.random.random( [utils_test.BOARD_SIZE * utils_test.BOARD_SIZE + 1]) * 0.001 black_root = MCTSNode(utils_test.BOARD_SIZE, go.Position(utils_test.BOARD_SIZE)) white_root = MCTSNode( utils_test.BOARD_SIZE, go.Position(utils_test.BOARD_SIZE, to_play=go.WHITE)) black_root.select_leaf().incorporate_results(probs, 0, black_root) white_root.select_leaf().incorporate_results(probs, 0, white_root) # No matter who is to play, when we know nothing else, the priors # should be respected, and the same move should be picked black_leaf = black_root.select_leaf() white_leaf = white_root.select_leaf() self.assertEqual(black_leaf.fmove, white_leaf.fmove) self.assertEqualNPArray(black_root.child_action_score, white_root.child_action_score)
def test_tree_search_failsafe(self): # Test that the failsafe works correctly. It can trigger if the MCTS # repeatedly visits a finished game state. probs = np.array([.001] * (utils_test.BOARD_SIZE * utils_test.BOARD_SIZE + 1)) probs[-1] = 1 # Make the dummy net always want to pass player = MCTSPlayerMixin(utils_test.BOARD_SIZE, DummyNet(fake_priors=probs)) pass_position = go.Position(utils_test.BOARD_SIZE).pass_move() player.initialize_game(pass_position) player.tree_search(num_parallel=1) self.assertNoPendingVirtualLosses(player.root)
def clear(self): if self.position and len(self.position.recent) > 1: try: sgf = self.to_sgf() with open(datetime.datetime.now().strftime( '%Y-%m-%d-%H:%M.sgf'), 'w') as f: f.write(sgf) except NotImplementedError: pass except: print('Error saving sgf', file=sys.stderr, flush=True) self.position = go.Position(komi=self.komi) self.initialize_game(self.position)
def test_dont_pick_unexpanded_child(self): probs = np.array([0.001] * (utils_test.BOARD_SIZE * utils_test.BOARD_SIZE + 1)) # make one move really likely so that tree search goes down that path twice # even with a virtual loss probs[17] = 0.999 root = MCTSNode(utils_test.BOARD_SIZE, go.Position(utils_test.BOARD_SIZE)) root.incorporate_results(probs, 0, root) leaf1 = root.select_leaf() self.assertEqual(leaf1.fmove, 17) leaf1.add_virtual_loss(up_to=root) # the second select_leaf pick should return the same thing, since the child # hasn't yet been sent to neural net for eval + result incorporation leaf2 = root.select_leaf() self.assertIs(leaf1, leaf2)
def test_do_not_explore_past_finish(self): probs = np.array([0.02] * (utils_test.BOARD_SIZE * utils_test.BOARD_SIZE + 1), dtype=np.float32) root = MCTSNode(utils_test.BOARD_SIZE, go.Position(utils_test.BOARD_SIZE)) root.select_leaf().incorporate_results(probs, 0, root) first_pass = root.maybe_add_child( coords.to_flat(utils_test.BOARD_SIZE, None)) first_pass.incorporate_results(probs, 0, root) second_pass = first_pass.maybe_add_child( coords.to_flat(utils_test.BOARD_SIZE, None)) with self.assertRaises(AssertionError): second_pass.incorporate_results(probs, 0, root) node_to_explore = second_pass.select_leaf() # should just stop exploring at the end position. self.assertEqual(node_to_explore, second_pass)
def test_long_game_tree_search(self): player = MCTSPlayerMixin(utils_test.BOARD_SIZE, DummyNet()) endgame = go.Position(utils_test.BOARD_SIZE, board=TT_FTW_BOARD, n=MAX_DEPTH - 2, komi=2.5, ko=None, recent=(go.PlayerMove(go.BLACK, (0, 1)), go.PlayerMove(go.WHITE, (0, 8))), to_play=go.BLACK) player.initialize_game(endgame) # Test that an almost complete game for i in range(10): player.tree_search(num_parallel=8) self.assertNoPendingVirtualLosses(player.root) self.assertGreater(player.root.Q, 0)
def test_replay_position(self): sgf_positions = list(sgf_wrapper.replay_sgf( utils_test.BOARD_SIZE, NO_HANDICAP_SGF)) initial = sgf_positions[0] self.assertEqual(initial.result, go.WHITE) final = sgf_positions[-1].position.play_move( sgf_positions[-1].next_move) # sanity check to ensure we're working with the right position final_board = utils_test.load_board(''' .OXX..... O.OX.X... .OOX..... OOOOXXXXX XOXXOXOOO XOOXOO.O. XOXXXOOXO XXX.XOXXO X..XOO.O. ''') expected_final_position = go.Position( utils_test.BOARD_SIZE, final_board, n=62, komi=6.5, caps=(3, 2), ko=None, recent=tuple(), to_play=go.BLACK ) self.assertEqualPositions(expected_final_position, final) self.assertEqual(final.n, len(final.recent)) replayed_positions = list(go.replay_position( utils_test.BOARD_SIZE, final, 1)) for sgf_pos, replay_pos in zip(sgf_positions, replayed_positions): self.assertEqualPositions(sgf_pos.position, replay_pos.position)
def test_make_dataset_from_sgf(self): with tempfile.NamedTemporaryFile() as sgf_file, \ tempfile.NamedTemporaryFile() as record_file: sgf_file.write(TEST_SGF.encode('utf8')) sgf_file.seek(0) preprocessing.make_dataset_from_sgf(utils_test.BOARD_SIZE, sgf_file.name, record_file.name) recovered_data = self.extract_data(record_file.name) start_pos = go.Position(utils_test.BOARD_SIZE) first_move = coords.from_sgf('fd') next_pos = start_pos.play_move(first_move) second_move = coords.from_sgf('cf') expected_data = [ (features.extract_features(utils_test.BOARD_SIZE, start_pos), preprocessing._one_hot( utils_test.BOARD_SIZE, coords.to_flat(utils_test.BOARD_SIZE, first_move)), -1), (features.extract_features(utils_test.BOARD_SIZE, next_pos), preprocessing._one_hot( utils_test.BOARD_SIZE, coords.to_flat(utils_test.BOARD_SIZE, second_move)), -1) ] self.assertEqualData(expected_data, recovered_data)
tf.logging.set_verbosity(tf.logging.ERROR) EMPTY_ROW = '.' * utils_test.BOARD_SIZE + '\n' TEST_BOARD = utils_test.load_board(''' .X.....OO X........ XXXXXXXXX ''' + EMPTY_ROW * 6) TEST_POSITION = go.Position( utils_test.BOARD_SIZE, board=TEST_BOARD, n=3, komi=6.5, caps=(1, 2), ko=None, recent=(go.PlayerMove(go.BLACK, (0, 1)), go.PlayerMove(go.WHITE, (0, 8)), go.PlayerMove(go.BLACK, (1, 0))), to_play=go.BLACK, ) TEST_BOARD2 = utils_test.load_board(''' .XOXXOO.. XO.OXOX.. XXO..X... ''' + EMPTY_ROW * 6) TEST_POSITION2 = go.Position( utils_test.BOARD_SIZE, board=TEST_BOARD2,
.XXOOOOOO X.XOO...O .XXOO...O X.XOO...O .XXOO..OO X.XOOOOOO .XXOOOOOO X.XXXXXXX XXXXXXXXX ''') SEND_TWO_RETURN_ONE = go.Position(utils_test.BOARD_SIZE, board=ALMOST_DONE_BOARD, n=70, komi=2.5, caps=(1, 4), ko=None, recent=(go.PlayerMove(go.BLACK, (0, 1)), go.PlayerMove(go.WHITE, (0, 8))), to_play=go.BLACK) # 505 moves for 19x19, 113 for 9x9 MAX_DEPTH = (utils_test.BOARD_SIZE**2) * 1.4 class DummyNet(): def __init__(self, fake_priors=None, fake_value=0): if fake_priors is None: fake_priors = np.ones((utils_test.BOARD_SIZE**2) + 1) / (utils_test.BOARD_SIZE**2 + 1) self.fake_priors = fake_priors