def __init__(self, config: dict, exp_dir: str, engine, board_extractor: GomradeExtractor, board_classifier, visualizer): """ The main class of the project. It uses objects to analyse crop board, classify it, and, considering defined logic, communicate with GTP engine. :param config: .yml as in example config :param exp_dir: :param engine: GTP engine :param board_extractor: :param board_classifier: :param visualizer: Object that will visualize the current interpretation and understanding of the board """ self.buffer_size = config['buffer_size'] self.ai_color = config['ai_color'] self.exp_dir = exp_dir self.sgf_file_root = exp_dir self.engine = engine self.visualizer = visualizer self.board_extractor = board_extractor self.board_classifier = board_classifier self.interpreter = TimeBoardStateInterpreter(config=config) self.sgf_translator = SgfTranslator(config['board_size'], komi=config['komi'], root_path=self.sgf_file_root) self.sleep = config['sleep_between_frames'] self.move = Move(first_move=config['ai_color']) self.image_ind = 0
def test_double_undo_to_main_sequence(): sgf_file_root = '../gomrade_tests/' sgf_translator = SgfTranslator(board_size=9, komi=0.5, root_path=sgf_file_root) sgf_translator.create_empty() for i in range(13): stones_state = list(toy_game[i].replace(' ', '')) task = sgf_translator.parse(stones_state) stones_state = list(toy_game[6].replace(' ', '')) task = sgf_translator.parse(stones_state) assert task == Task.UNDO stones_state = '. . . . . . . . .' \ '. . . B . . . . .' \ '. . . . . . B . .' \ '. . . . . . . . .' \ '. . W B B . . . .' \ '. . . W . . . . .' \ '. . . . . . W . .' \ '. . . . . . . . .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) assert task == Task.REGULAR stones_state = list(toy_game[5].replace(' ', '')) task = sgf_translator.parse(stones_state) assert task == Task.UNDO stones_state = '. . . . . . . . .' \ '. . . B . . . . .' \ '. . . . . . B . .' \ '. . . . . . . . .' \ '. . . B . . W . .' \ '. . . W . . . . .' \ '. . . . . . W . .' \ '. . . . . . . . .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) assert task == Task.REGULAR stones_state = '. . . . . . . . .' \ '. . . B . . . . .' \ '. . . . . . B . .' \ '. . . . . . . B .' \ '. . . B . . W . .' \ '. . . W . . . . .' \ '. . . . . . W . .' \ '. . . . . . . . .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) assert task == Task.REGULAR
def test_regular_game(): sgf_file_root = '../gomrade_tests/' sgf_translator = SgfTranslator(board_size=9, komi=0.5, root_path=sgf_file_root) sgf_translator.create_empty() for i in range(13): stones_state = list(toy_game[i].replace(' ', '')) task = sgf_translator.parse(stones_state) with open('../gomrade_tests/game.sgf') as f: game_str = f.read() assert 'AW' or 'AB' not in game_str
def test_error_game(): sgf_file_root = '../gomrade_tests/' sgf_translator = SgfTranslator(board_size=9, komi=0.5, root_path=sgf_file_root) sgf_translator.create_empty() for i in range(13): stones_state = list(toy_game[i].replace(' ', '')) task = sgf_translator.parse(stones_state) stones_state = 'B B B . . . . . .' \ '. . . B . . . . .' \ '. . . . . . B . .' \ '. . W W . . . . .' \ '. . W B . . . . .' \ '. . . W B . . . .' \ '. . . B . . W . .' \ '. . . . . W W W .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) with open('../gomrade_tests/game.sgf') as f: game_str = f.read() assert 'AW' or 'AB' in game_str stones_state = 'B B B . . . . . .' \ '. . . B . . . . .' \ '. . . . W . B . .' \ '. . W W . . . . .' \ '. . W B . . . . .' \ '. . . W B . . . .' \ '. . . B . . W . .' \ '. . . . . W W W .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) stones_state = 'B B B . . . . . .' \ '. . . B . . . . .' \ '. . . . W . B . .' \ '. . W W . . B . .' \ '. . W B . . . . .' \ '. . . W B . . . .' \ '. . . B . . W . .' \ '. . . . . W W W .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) with open('../gomrade_tests/game.sgf') as f: game_str = f.read() assert 'AW' or 'AB' in game_str assert ';W' or ';B' in game_str
def test_undo(): sgf_file_root = '../gomrade_tests/' sgf_translator = SgfTranslator(board_size=9, komi=0.5, root_path=sgf_file_root) sgf_translator.create_empty() for i in range(13): stones_state = list(toy_game[i].replace(' ', '')) task = sgf_translator.parse(stones_state) stones_state = list(toy_game[6].replace(' ', '')) task = sgf_translator.parse(stones_state) assert task == Task.UNDO stones_state = '. . . . . . . . .' \ '. . . B . . . . .' \ '. . . . . . B . .' \ '. . . . . . . . .' \ '. . W B B . . . .' \ '. . . W . . . . .' \ '. . . . . . W . .' \ '. . . . . . . . .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) assert task == Task.REGULAR with open('../gomrade_tests/game.sgf') as f: game_str = f.read() assert ';W' or ';B' in game_str counter = Counter(game_str) assert counter['('] == 3 assert counter[')'] == 3
def test_undo_to_previoius_tree(): sgf_file_root = '../gomrade_tests/' sgf_translator = SgfTranslator(board_size=9, komi=0.5, root_path=sgf_file_root) sgf_translator.create_empty() for i in range(13): stones_state = list(toy_game[i].replace(' ', '')) task = sgf_translator.parse(stones_state) # Back to white move stones_state = list(toy_game[6].replace(' ', '')) task = sgf_translator.parse(stones_state) assert task == Task.UNDO # Different blacks move stones_state = '. . . . . . . . .' \ '. . . B . . . . .' \ '. . . . . . B . .' \ '. . . . . . . . .' \ '. . W B B . . . .' \ '. . . W . . . . .' \ '. . . . . . W . .' \ '. . . . . . . . .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) assert task == Task.REGULAR stones_state = '. . . . . . . . .' \ '. . . B . . . . .' \ '. . . . . . B . .' \ '. . . . . . . . .' \ '. . W B B . W . .' \ '. . . W . . . . .' \ '. . . . . . W . .' \ '. . . . . . . . .' \ '. . . . . . . . .' # white response task = sgf_translator.parse(list(stones_state.replace(' ', ''))) assert task == Task.REGULAR # black move again stones_state = '. . . . . . . . .' \ '. . . B . . . . .' \ '. . . . . . B . .' \ '. . . . . . . . .' \ '. . W B B . W . .' \ '. . . W B . . . .' \ '. . . . . . W . .' \ '. . . . . . . . .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) assert task == Task.REGULAR # White response again stones_state = '. . . . . . . . .' \ '. . . B . . . . .' \ '. . . . . . B . .' \ '. . . . . . . . .' \ '. . W B B . W . .' \ '. . W W B . . . .' \ '. . . . . . W . .' \ '. . . . . . . . .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) assert task == Task.REGULAR # Undo last black decision stones_state = '. . . . . . . . .' \ '. . . B . . . . .' \ '. . . . . . B . .' \ '. . . . . . . . .' \ '. . W B B . W . .' \ '. . . W . . . . .' \ '. . . . . . W . .' \ '. . . . . . . . .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) assert task == Task.UNDO # Blacks better move stones_state = '. . . . . . . . .' \ '. . . B . . . . .' \ '. . . . . . B . .' \ '. . . . . . . . .' \ '. . W B B . W . .' \ '. . B W . . . . .' \ '. . . . . . W . .' \ '. . . . . . . . .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) assert task == Task.REGULAR # White answers stones_state = '. . . . . . . . .' \ '. . . B . . . . .' \ '. . . . . . B . .' \ '. . . . . . . . .' \ '. . W B B . W . .' \ '. . B W . . . . .' \ '. . W . . . W . .' \ '. . . . . . . . .' \ '. . . . . . . . .' task = sgf_translator.parse(list(stones_state.replace(' ', ''))) assert task == Task.REGULAR with open('../gomrade_tests/game.sgf') as f: game_str = f.read() assert ';W' or ';B' in game_str assert 'AW' or 'AB' in game_str counter = Counter(game_str) assert counter['('] == 5 assert counter[')'] == 5
class GomradeGame: def __init__(self, config: dict, exp_dir: str, engine, board_extractor: GomradeExtractor, board_classifier, visualizer): """ The main class of the project. It uses objects to analyse crop board, classify it, and, considering defined logic, communicate with GTP engine. :param config: .yml as in example config :param exp_dir: :param engine: GTP engine :param board_extractor: :param board_classifier: :param visualizer: Object that will visualize the current interpretation and understanding of the board """ self.buffer_size = config['buffer_size'] self.ai_color = config['ai_color'] self.exp_dir = exp_dir self.sgf_file_root = exp_dir self.engine = engine self.visualizer = visualizer self.board_extractor = board_extractor self.board_classifier = board_classifier self.interpreter = TimeBoardStateInterpreter(config=config) self.sgf_translator = SgfTranslator(config['board_size'], komi=config['komi'], root_path=self.sgf_file_root) self.sleep = config['sleep_between_frames'] self.move = Move(first_move=config['ai_color']) self.image_ind = 0 def _execute(self, engine, stones_state): task = self.sgf_translator.parse(stones_state) if task == Task.OTHER_ERROR: play_wav('error') if task == Task.UNDO: play_wav('undo') if task == Task.DISAPPEAR: play_wav('disappeared') if task != Task.UNDO and task != Task.REGULAR and task != Task.KILL: play_wav('reset') engine.clear_board() engine.load_sgf(path=self.sgf_translator.path) engine.showboard() def _execute_move(self, engine, stones_state): if self.move.c == self.ai_color: logging.info('Asking for a move') self._execute(engine, stones_state) vertex = engine.genmove(self.move.c) play_wav(vertex) else: logging.info('Doing nothing') play_wav('move') self._execute(engine, stones_state) def run(self, cap, debug=True): buf_i = 0 # fill images buffer buf = fill_buffer(cap, self.buffer_size) global_start = time.time() while True: time.sleep(self.sleep) start = time.time() # Capture frame-by-frame ret, frame = cap.read() # Average images for homogeneous colors buf[buf_i] = frame buf_i = (buf_i + 1) % self.buffer_size frame = avg_images_in_buffer(buf) # Crop image to board and perform classification res, x_grid, y_grid = self.board_extractor.read_board(frame, debug=debug) stones_state, res = self.board_classifier.read_board(res, x_grid, y_grid, debug=debug) if debug: self.visualizer.show_cam(res) # Decide what to do with the understood board is_move = self.interpreter.interpret(stones_state, self.move.c) if is_move: save_game_state(frame, stones_state, exp_dir=self.exp_dir, name=self.image_ind) self.image_ind += 1 self.move.switch() self._execute_move(self.engine, stones_state) # print(time.time() - start) if debug: if time.time() - global_start > 100: break # todo unreachable code.. cap.release() cv2.destroyAllWindows()