class GtpConnection(): def __init__(self, go_engine, debug_mode = False): """ Play Go over a GTP connection Parameters ---------- go_engine: GoPlayer a program that is capable of playing go by reading GTP commands komi : float komi used for the current game board: GoBoard SIZExSIZE array representing the current board state """ self.stdout = sys.stdout self._debug_mode = debug_mode sys.stdout = self self.go_engine = go_engine self.go_engine.komi = 6.5 self.go_engine.selfatari = 1 self.go_engine.pattern = 1 self.board = GoBoard(7) self.mm_file_name = "features_mm_training.dat" self.init_mm_file = False self.num_game = 0 self.skip_counter = 0 self.param_options = { "selfatari" : self.go_engine.selfatari, "pattern" : self.go_engine.pattern } self.commands = { "protocol_version": self.protocol_version_cmd, "quit": self.quit_cmd, "name": self.name_cmd, "boardsize": self.boardsize_cmd, "showboard": self.showboard_cmd, "clear_board": self.clear_board_cmd, "komi": self.komi_cmd, "version": self.version_cmd, "known_command": self.known_command_cmd, "set_free_handicap": self.set_free_handicap, "genmove": self.genmove_cmd, "list_commands": self.list_commands_cmd, "play": self.play_cmd, "final_score": self.final_score_cmd, "legal_moves": self.legal_moves_cmd, "policy_moves": self.policy_moves_cmd, "random_moves": self.random_moves_cmd, "go_param": self.go_param_cmd, "gogui-analyze_commands": self.gogui_analyze_cmd, "num_sim": self.num_sim_cmd, "showoptions": self.showoptions_cmd, "feature_move": self.feature_move_cmd, "features_mm_file": self.feature_mm_cmd } # used for argument checking # values: (required number or arguments, error message on argnum failure) self.argmap = { "boardsize": (1, 'Usage: boardsize INT'), "komi": (1, 'Usage: komi FLOAT'), "known_command": (1, 'Usage: known_command CMD_NAME'), "set_free_handicap": (1, 'Usage: set_free_handicap MOVE (e.g. A4)'), "genmove": (1, 'Usage: genmove {w, b}'), "play": (2, 'Usage: play {b, w} MOVE'), "legal_moves": (0, 'Usage: legal_moves does not have arguments'), "go_param": (2,'Usage: goparam {{{0}}} {{0,1}}'.format(' '.join(list(self.param_options.keys())))), "num_sim":(1,'Usage: num_sim #(e.g. num_sim 100 )'), "showoptions":(0,'Usage: showoptions does not have arguments'), "feature_move":(1,'Usage: feature_move move') } def __del__(self): sys.stdout = self.stdout def write(self, data): self.stdout.write(data) def flush(self): self.stdout.flush() def start_connection(self): """ start a GTP connection. This function is what continuously monitors the user's input of commands. """ self.debug_msg("Start up successful...\n\n") line = sys.stdin.readline() while line: self.get_cmd(line) line = sys.stdin.readline() def get_cmd(self, command): """ parse the command and execute it Arguments --------- command : str the raw command to parse/execute """ if len(command.strip(' \r\t')) == 0: return if command[0] == '#': return # Strip leading numbers from regression tests if command[0].isdigit(): command = re.sub("^\d+", "", command).lstrip() elements = command.split() if not elements: return command_name = elements[0]; args = elements[1:] if self.arg_error(command_name, len(args)): return if command_name in self.commands: try: self.commands[command_name](args) except Exception as e: self.debug_msg("Error executing command {}\n".format(str(e))) self.debug_msg("Stack Trace:\n{}\n".format(traceback.format_exc())) traceback.print_exc(file=sys.stdout) raise e else: self.debug_msg("Unknown command: {}\n".format(command_name)) self.error('Unknown command') sys.stdout.flush() def arg_error(self, cmd, argnum): """ checker function for the number of arguments given to a command Arguments --------- cmd : str the command name argnum : int number of parsed argument Returns ------- True if there was an argument error False otherwise """ if cmd in self.argmap and self.argmap[cmd][0] != argnum: self.error(self.argmap[cmd][1]) return True return False def debug_msg(self, msg = ''): """ Write a msg to the debug stream """ if self._debug_mode: sys.stderr.write(msg); sys.stderr.flush() def error(self, error_msg = ''): """ Send error msg to stdout and through the GTP connection. """ sys.stdout.write('? {}\n\n'.format(error_msg)); sys.stdout.flush() def respond(self, response = ''): """ Send msg to stdout """ sys.stdout.write('= {}\n\n'.format(response)); sys.stdout.flush() def reset(self, size): """ Resets the state of the GTP to a starting board Arguments --------- size : int the boardsize to reinitialize the state to """ self.board.reset(size) self.go_engine.reset() def protocol_version_cmd(self, args): """ Return the GTP protocol version being used (always 2) """ self.respond('2') def quit_cmd(self, args): """ Quit game and exit the GTP interface """ self.respond() exit() def name_cmd(self, args): """ Return the name of the player """ self.respond(self.go_engine.name) def version_cmd(self, args): """ Return the version of the player """ self.respond(self.go_engine.version) def clear_board_cmd(self, args): """ clear the board """ self.reset(self.board.size) self.respond() def boardsize_cmd(self, args): """ Reset the game and initialize with a new boardsize Arguments --------- args[0] : int size of reinitialized board """ self.reset(int(args[0])) self.respond() def showboard_cmd(self, args): self.respond('\n' + str(self.board.get_twoD_board())) def showoptions_cmd(self,args): options = dict() options['komi'] = self.go_engine.komi options['pattern'] = self.go_engine.pattern options['selfatari'] = self.go_engine.selfatari options['num_sim'] = self.go_engine.num_simulation self.respond(options) def komi_cmd(self, args): """ Set the komi for the game Arguments --------- args[0] : float komi value """ self.go_engine.komi = float(args[0]) self.respond() def known_command_cmd(self, args): """ Check if a command is known to the GTP interface Arguments --------- args[0] : str the command name to check for """ if args[0] in self.commands: self.respond("true") else: self.respond("false") def list_commands_cmd(self, args): """ list all supported GTP commands """ self.respond(' '.join(list(self.commands.keys()))) def set_free_handicap(self, args): """ clear the board and set free handicap for the game Arguments --------- args[0] : str the move to handicap (e.g. B2) """ self.board.reset(self.board.size) for point in args: move = GoBoardUtil.move_to_coord(point, self.board.size) point = self.board._coord_to_point(*move) if not self.board.move(point, BLACK): self.debug_msg("Illegal Move: {}\nBoard:\n{}\n".format(move, str(self.board.get_twoD_board()))) self.respond() def legal_moves_cmd(self, args): """ list legal moves for current player """ color = self.board.current_player legal_moves = GoBoardUtil.generate_legal_moves(self.board, color) self.respond(GoBoardUtil.sorted_point_string(legal_moves, self.board.NS)) def num_sim_cmd(self, args): self.go_engine.num_simulation = int(args[0]) self.respond() def go_param_cmd(self, args): valid_values = [0,1] valid_params = ['selfatari','pattern'] param = args[0] param_value = int(args[1]) if param not in valid_params: self.error('Unkown parameters: {}'.format(param)) if param_value not in valid_values: self.error('Argument 2 ({}) must be of type bool'.format(param_value)) if param ==valid_params[1]: self.go_engine.pattern = param_value elif param == valid_params[0]: self.go_engine.selfatari = param_value self.param_options[param] = param_value self.respond() def policy_moves_cmd(self, args): """ Return list of policy moves for the current_player of the board """ # Assignment4 - policy moves # ========================================= move_prob_tuples = self.get_move_prob() response = ' '.join([' '.join([t[0], '{:.5f}'.format(t[1])]) for t in move_prob_tuples]) self.respond(response) # ========================================= def random_moves_cmd(self, args): """ Return list of random moves (legal, but not eye-filling) """ moves = GoBoardUtil.generate_random_moves(self.board) if len(moves) == 0: self.respond("Pass") else: self.respond(GoBoardUtil.sorted_point_string(moves, self.board.NS)) def feature_move_cmd(self, args): move = None if args[0]=='PASS': move = 'PASS' else: move = GoBoardUtil.move_to_coord(args[0], self.board.size) if move: move = self.board._coord_to_point(move[0], move[1]) else: self.error("Error in executing the move %s, check given move: %s"%(move,args[1])) return assert move != None response = [] features = Feature.find_move_feature(self.board, move) if features == None: self.respond(response) return for f in features: fn = Feature.find_feature_name(f) if fn != None: response.append(Feature.find_feature_name(f)) else: response.append(self.board.neighborhood_33_pattern_shape(move)) r = '\n'.join(response) self.respond(r) def init_mm_file_header(self): with open(self.mm_file_name ,'w') as header_writer: header_writer.write('! 1080\n') header_writer.write('8\n') header_writer.write('2 Feature_Pass\n') header_writer.write('1 Feature_Capture\n') header_writer.write('2 Feature_Atari\n') header_writer.write('1 Feature_SelfAtari\n') header_writer.write('3 Feature_DistanceLine\n') header_writer.write('8 Feature_DistancePrev\n') header_writer.write('9 Feature_DistancePrevOwn\n') header_writer.write('1054 Feature_Pattern\n') header_writer.write('!\n') header_writer.close() self.init_mm_file = True def feature_mm_cmd(self, args): if self.init_mm_file == False: self.init_mm_file_header() assert self.init_mm_file == True if len(self.board.moves) == 0: with open('game_num.txt', 'a') as file: self.num_game = self.num_game + 1 file.write('{}\n'.format(self.num_game)) file.close() self.respond() self.skip_counter = 0 return # skip the first five moves in the game records, since they are randomly selected if self.skip_counter <= 5: self.skip_counter += 1 self.respond() return chosenMove = self.board.last_move bd = self.board.copy() if chosenMove != -1: if chosenMove == None: chosenMove = 'PASS' bd.partial_undo_move() Feature.write_mm_file(bd, chosenMove, self.mm_file_name) if chosenMove == 'PASS': bd.move(None, bd.current_player) else: bd.move(chosenMove, bd.current_player) self.respond() def gogui_analyze_cmd(self, args): try: self.respond("pstring/Legal Moves/legal_moves\n" "pstring/Policy Moves/policy_moves\n" "pstring/Random Moves/random_moves\n" "none/Feature Move/feature_move %p\n" "none/Features MM File/features_mm_file\n" ) except Exception as e: self.respond('Error: {}'.format(str(e))) def play_cmd(self, args): """ play a move as the given color Arguments --------- args[0] : {'b','w'} the color to play the move as it gets converted to Black --> 1 White --> 2 color : {0,1} board_color : {'b','w'} args[1] : str the move to play (e.g. A5) """ try: board_color = args[0].lower() board_move = args[1] color= GoBoardUtil.color_to_int(board_color) if args[1].lower()=='pass': self.debug_msg("Player {} is passing\n".format(args[0])) self.go_engine.update('pass') #self.board.current_player = GoBoardUtil.opponent(color) self.board.move(None, color) self.respond() return move = GoBoardUtil.move_to_coord(args[1], self.board.size) if move: move = self.board._coord_to_point(move[0], move[1]) else: self.error("Error in executing the move %s, check given move: %s"%(move,args[1])) return if not self.board.move(move, color): self.respond("Illegal Move: {}".format(board_move)) return else: self.debug_msg("Move: {}\nBoard:\n{}\n".format(board_move, str(self.board.get_twoD_board()))) self.go_engine.update(move) self.respond() except Exception as e: self.respond("illegal move: {} {} {}".format(board_color, board_move, str(e))) def final_score_cmd(self, args): self.respond(self.board.final_score(self.go_engine.komi)) def genmove_cmd(self, args): """ generate a move for the specified color Arguments --------- args[0] : {'b','w'} the color to generate a move for it gets converted to Black --> 1 White --> 2 color : {0,1} board_color : {'b','w'} """ try: board_color = args[0].lower() color = GoBoardUtil.color_to_int(board_color) self.debug_msg("Board:\n{}\nko: {}\n".format(str(self.board.get_twoD_board()), self.board.ko_constraint)) # Assignment4 - policy probabilistic player # ========================================= if color != self.board.current_player: self.respond("Opponent's turn") return move_prob_tuples = self.get_move_prob() if move_prob_tuples[0][0] == 'pass': self.respond("pass") self.go_engine.update('pass') self.board.move(None, color) return # based on # https://docs.python.org/3.5/library/random.html population = [val for val, cnt in move_prob_tuples for _ in range(int(cnt*1e5))] move = random.choice(population) move = GoBoardUtil.move_to_coord(move, self.board.size) move = self.board._coord_to_point(move[0], move[1]) # ========================================= if not self.board.check_legal(move, color): move = self.board._point_to_coord(move) board_move = GoBoardUtil.format_point(move) self.respond("Illegal move: {}".format(board_move)) traceback.print_exc(file=sys.stdout) self.respond('Error: {}'.format(str(e))) raise RuntimeError("Illegal move given by engine") # move is legal; play it self.board.move(move, color) self.debug_msg("Move: {}\nBoard: \n{}\n".format(move, str(self.board.get_twoD_board()))) move = self.board._point_to_coord(move) board_move = GoBoardUtil.format_point(move) self.respond(board_move) except Exception as e: self.respond('Error: {}'.format(str(e))) # Assignment4 # ========================================= def get_move_prob(self): moves = GoBoardUtil.generate_random_moves(self.board) # legal and not eye-filling features = Feature.find_all_features(self.board) gammas_sum = 0 move_gammas = dict() if len(Features_weight) != 0: for move in moves: move_gammas[move] = Feature.compute_move_gamma(Features_weight, features[move]) gammas_sum += move_gammas[move] # normalize to get probability if gammas_sum: for move in move_gammas.keys(): move_gammas[move] /= gammas_sum if move_gammas and gammas_sum: move_prob_tuples = [(self.board.point_to_string(k), v) for (k, v) in move_gammas.items()] # sort list by probability and alphabetic order # based on # http://stackoverflow.com/questions/5212870/sorting-a-python-list-by-two-criteria # answered by jaap on Stack Overflow http://stackoverflow.com/users/1186954/jaap move_prob_tuples = sorted(move_prob_tuples, key=lambda k:(-k[1], k[0][0], k[0][1])) else: move_prob_tuples = list() move_prob_tuples.append(('pass', 1)) return move_prob_tuples
#!/usr/bin/python3 from board import GoBoard from board_util import GoBoardUtil, BLACK, WHITE, EMPTY, BORDER, FLOODFILL import numpy as np from Go5 import Go5Player import time board = GoBoard(4) player = Go5Player(num_simulation=200, limit=100, exploration=np.sqrt(2)) player.MCTS.komi = 6.5 player.num_nodes = 5 cboard = board.copy() print("\nrunning playout 200 times\n") player.run(cboard, BLACK, print_info=True) #time.sleep(30) # sleeping player.num_simulation = 300 print("\nrunning it 300 more times\n") cboard = board.copy() player.run(cboard, BLACK, print_info=True) #time.sleep(30) print("\nrunning it 300 more times\n") cboard = board.copy() player.run(cboard, BLACK, print_info=True) #time.sleep(30) print("\nrunning it 300 more times\n") cboard = board.copy() player.run(cboard, BLACK, print_info=True)
class GtpConnection(): def __init__(self, go_engine, debug_mode=False): """ object that plays Go using GTP Parameters ---------- go_engine: GoPlayer a program that is capable of playing go by reading GTP commands debug_mode: prints debug messages """ self.stdout = sys.stdout sys.stdout = self self._debug_mode = debug_mode self.go_engine = go_engine self.komi = 0 self.board = GoBoard(7) # Assignment2 - 1.timelimit self.timelimit = 1 signal.signal(signal.SIGALRM, self.timeout) self.boarddic = {} for d in range(0, 49): self.boarddic[d] = {} self.commands = { "protocol_version": self.protocol_version_cmd, "quit": self.quit_cmd, "name": self.name_cmd, "boardsize": self.boardsize_cmd, "showboard": self.showboard_cmd, "clear_board": self.clear_board_cmd, "komi": self.komi_cmd, "version": self.version_cmd, "known_command": self.known_command_cmd, "set_free_handicap": self.set_free_handicap, "genmove": self.genmove_cmd, "list_commands": self.list_commands_cmd, "play": self.play_cmd, "final_score": self.final_score_cmd, "legal_moves": self.legal_moves_cmd, # Assignment2 - 1.timelimit "timelimit": self.timelimit_cmd, # Assignment2 - 2.solve "solve": self.solve_cmd, } # used for argument checking # values: (required number or arguments, error message on argnum failure) self.argmap = { "boardsize": (1, 'Usage: boardsize INT'), "komi": (1, 'Usage: komi FLOAT'), "known_command": (1, 'Usage: known_command CMD_NAME'), "set_free_handicap": (1, 'Usage: set_free_handicap MOVE (e.g. A4)'), "genmove": (1, 'Usage: genmove {w, b}'), "play": (2, 'Usage: play {b, w} MOVE'), "legal_moves": (1, 'Usage: legal_moves {w, b}'), # Assignment2 - 1.timelimit "timelimit": (1, "Usage: timelimit 1<=INT<=100"), } def __del__(self): sys.stdout = self.stdout def write(self, data): self.stdout.write(data) def flush(self): self.stdout.flush() def start_connection(self): """ start a GTP connection. This function is what continuously monitors the user's input of commands. """ self.debug_msg("Start up successful...\n\n") line = sys.stdin.readline() while line: self.get_cmd(line) line = sys.stdin.readline() def get_cmd(self, command): """ parse the command and execute it Arguments --------- command : str the raw command to parse/execute """ if len(command.strip(' \r\t')) == 0: return if command[0] == '#': return # Strip leading numbers from regression tests if command[0].isdigit(): command = re.sub("^\d+", "", command).lstrip() elements = command.split() if not elements: return command_name = elements[0] args = elements[1:] if command_name == "play" and self.argmap[command_name][0] != len( args): self.respond('illegal move: {} wrong number of arguments'.format( args[0])) return if self.arg_error(command_name, len(args)): return if command_name in self.commands: try: self.commands[command_name](args) except Exception as e: self.debug_msg("Error executing command {}\n".format(str(e))) self.debug_msg("Stack Trace:\n{}\n".format( traceback.format_exc())) raise e else: self.debug_msg("Unknown command: {}\n".format(command_name)) self.error('Unknown command') sys.stdout.flush() def arg_error(self, cmd, argnum): """ checker funciton for the number of arguments given to a command Arguments --------- cmd : str the command name argnum : int number of parsed argument Returns ------- True if there was an argument error False otherwise """ if cmd in self.argmap and self.argmap[cmd][0] > argnum: self.error(self.argmap[cmd][1]) return True return False def debug_msg(self, msg=''): """ Write a msg to the debug stream """ if self._debug_mode: sys.stderr.write(msg) sys.stderr.flush() def error(self, error_msg=''): """ Send error msg to stdout and through the GTP connection. """ sys.stdout.write('? {}\n\n'.format(error_msg)) sys.stdout.flush() def respond(self, response=''): """ Send msg to stdout """ sys.stdout.write('= {}\n\n'.format(response)) sys.stdout.flush() def reset(self, size): """ Resets the state of the GTP to a starting board Arguments --------- size : int the boardsize to reinitialize the state to """ if size != self.board.size: self.boarddic = {} for depth in range(0, size**2): self.boarddic[depth] = {} self.board.reset(size) def protocol_version_cmd(self, args): """ Return the GTP protocol version being used (always 2) """ self.respond('2') def quit_cmd(self, args): """ Quit game and exit the GTP interface """ self.respond() exit() def name_cmd(self, args): """ Return the name of the player """ self.respond(self.go_engine.name) def version_cmd(self, args): """ Return the version of the player """ self.respond(self.go_engine.version) def clear_board_cmd(self, args): """ clear the board """ self.reset(self.board.size) self.respond() def boardsize_cmd(self, args): """ Reset the game and initialize with a new boardsize Arguments --------- args[0] : int size of reinitialized board """ self.reset(int(args[0])) self.respond() def showboard_cmd(self, args): self.respond('\n' + str(self.board.get_twoD_board())) def komi_cmd(self, args): """ Set the komi for the game Arguments --------- args[0] : float komi value """ self.komi = float(args[0]) self.respond() def known_command_cmd(self, args): """ Check if a command is known to the GTP interface Arguments --------- args[0] : str the command name to check for """ if args[0] in self.commands: self.respond("true") else: self.respond("false") def list_commands_cmd(self, args): """ list all supported GTP commands """ self.respond(' '.join(list(self.commands.keys()))) def set_free_handicap(self, args): """ clear the board and set free handicap for the game Arguments --------- args[0] : str the move to handicap (e.g. B2) """ self.board.reset(self.board.size) for point in args: move = GoBoardUtil.move_to_coord(point, self.board.size) point = self.board._coord_to_point(*move) if not self.board.move(point, BLACK): self.debug_msg("Illegal Move: {}\nBoard:\n{}\n".format( move, str(self.board.get_twoD_board()))) self.respond() def legal_moves_cmd(self, args): """ list legal moves for the given color Arguments --------- args[0] : {'b','w'} the color to play the move as it gets converted to Black --> 1 White --> 2 color : {0,1} board_color : {'b','w'} """ try: board_color = args[0].lower() color = GoBoardUtil.color_to_int(board_color) moves = GoBoardUtil.generate_legal_moves(self.board, color) self.respond(moves) except Exception as e: self.respond('Error: {}'.format(str(e))) def play_cmd(self, args): """ play a move as the given color Arguments --------- args[0] : {'b','w'} the color to play the move as it gets converted to Black --> 1 White --> 2 color : {0,1} board_color : {'b','w'} args[1] : str the move to play (e.g. A5) """ try: board_color = args[0].lower() board_move = args[1] color = GoBoardUtil.color_to_int(board_color) move = GoBoardUtil.move_to_coord(args[1], self.board.size) if move: move = self.board._coord_to_point(move[0], move[1]) else: return if not self.board.move(move, color): return self.respond() except Exception as e: self.respond("illegal move: {} {} {}".format( board_color, board_move, str(e))) def final_score_cmd(self, args): self.respond(self.board.final_score(self.komi)) """ Assignment2 - 3.genmove """ def genmove_cmd(self, args): """ try to generate a perfect move for the specified color Arguments --------- args[0] : {'b','w'} the color to generate a move for it gets converted to Black --> 1 White --> 2 color : {0,1} board_color : {'b','w'} """ try: board_color = args[0].lower() color = GoBoardUtil.color_to_int(board_color) _, move = self.solve() if not move: move = self.go_engine.get_move(self.board, color) if move is None: self.respond("resign") return if not self.board.check_legal(move, color): move = self.board._point_to_coord(move) board_move = GoBoardUtil.format_point(move) self.respond("Illegal move: {}".format(board_move)) raise RuntimeError("Illegal move given by engine") # move is legal; play it self.board.move(move, color) self.debug_msg("Move: {}\nBoard: \n{}\n".format( move, str(self.board.get_twoD_board()))) move = self.board._point_to_coord(move) board_move = GoBoardUtil.format_point(move) self.respond(board_move) except Exception as e: self.respond('Error: {}'.format(str(e))) # try: # board_color = args[0].lower() # color = GoBoardUtil.color_to_int(board_color) # move = self.go_engine.get_move(self.board, color) # if move is None: # self.respond("pass") # return # # if not self.board.check_legal(move, color): # move = self.board._point_to_coord(move) # board_move = GoBoardUtil.format_point(move) # self.respond("Illegal move: {}".format(board_move)) # raise RuntimeError("Illegal move given by engine") # # # move is legal; play it # self.board.move(move, color) # self.debug_msg("Move: {}\nBoard: \n{}\n".format(move, str(self.board.get_twoD_board()))) # move = self.board._point_to_coord(move) # board_move = GoBoardUtil.format_point(move) # self.respond(board_move) # except Exception as e: # self.respond('Error: {}'.format(str(e))) """ Assignment2 - 1.timelimit """ def timelimit_cmd(self, args): """ set the maximum time to use for all following genmove or solve commands the default value is 1 Arguments --------- args[0] : int timelimit """ try: t = int(args[0]) if t in range(1, 101): self.timelimit = t self.respond() else: self.error(self.argmap["timelimit"][1]) except Exception as e: self.respond("Error: {}".format(str(e))) def timeout(self, signum, frame): raise Exception("Timeout") """ Assignment2 - 2.solve """ def solve_cmd(self, args): try: winner, move = self.solve() if move: move = self.board._point_to_coord(move) move = GoBoardUtil.format_point(move) self.respond("{} {}".format(winner, move)) except Exception as e: self.respond("Error: {}".format(str(e))) """ Helper function """ def solve(self): board = self.board.copy() depth = self.board.depth try: signal.alarm(self.timelimit) #start = time.process_time() result = self.negamaxBoolean(board, depth) #self.respond("Time: {}".format(str(time.process_time() - start))) signal.alarm(0) except Exception as e: if str(e) == "Timeout": winner, move = "unknown", "" else: raise e else: if result[0]: winner, move = GoBoardUtil.int_to_color( self.board.to_play), result[1] else: winner, move = GoBoardUtil.int_to_color( GoBoardUtil.opponent(self.board.to_play)), "" return winner, move # Based on https://webdocs.cs.ualberta.ca/~mmueller/courses/496-general/python/code/tic_tac_toe_solve.py def negamaxBoolean(self, board, depth): # optimize board2D = board.get_twoD_board().tostring() if board2D in self.boarddic[depth]: if self.boarddic[depth][board2D][0] == board.to_play: return True, self.boarddic[depth][board2D][1] else: return False, None if board.get_winner(): return False, None for m in GoBoardUtil.generate_legal_moves_fast(board, board.to_play): backup = board.copy() board.move(m, board.to_play) success = not self.negamaxBoolean(board, depth + 1)[0] board = backup if success: self.boardData(depth, board, m) return True, m return False, None def boardData(self, depth, board, m): # Store the {depth:{state: (winner, move), ...}, ...} self.boarddic[depth][board.get_twoD_board().tostring()] = ( board.to_play, m) # optimize 2 boardarr = board.get_twoD_board() blist = [] blist.append(boardarr.tostring()) for i in range(0, 7): emptyboard = GoBoard(board.size) emptyboard.move(m, 1) if i == 0: b = np.rot90(boardarr) if b.tostring() in blist: continue empty_move = np.rot90(emptyboard.get_twoD_board()) elif i == 1: b = np.rot90(boardarr, 2) if b.tostring() in blist: continue empty_move = np.rot90(emptyboard.get_twoD_board(), 2) elif i == 2: b = np.rot90(boardarr, 3) if b.tostring() in blist: continue empty_move = np.rot90(emptyboard.get_twoD_board(), 3) elif i == 3: b = np.fliplr(boardarr) if b.tostring() in blist: continue empty_move = np.fliplr(emptyboard.get_twoD_board()) elif i == 4: b = np.flipud(boardarr) if b.tostring() in blist: continue empty_move = np.flipud(emptyboard.get_twoD_board()) elif i == 5: b = np.fliplr(np.rot90(boardarr)) if b.tostring() in blist: continue empty_move = np.fliplr(np.rot90(emptyboard.get_twoD_board())) else: b = np.flipud(np.rot90(boardarr)) if b.tostring() in blist: continue empty_move = np.flipud(np.rot90(emptyboard.get_twoD_board())) blist.append(b.tostring()) index = np.where(empty_move == 1) r, c = index[0][0] + 1, index[1][0] + 1 new_move = board._coord_to_point(r, c) self.boarddic[depth][b.tostring()] = (board.to_play, new_move)