def update_available_placement(self, action): # to update the available actions in the placement phase we just need to read in the action made # remove this entry from the dictionary # add the entries of any eliminated positions in the dictionary elim = [] eliminated_pieces = self.board.eliminated_pieces_last_move( self.board.phase, self.board.move_counter, pop=False) #print("ELIMINATED: ",end='') #print(eliminated_pieces) #print("AVAILABLE: ",end='') #print(self.available_actions) for colour in (constant.WHITE_PIECE, constant.BLACK_PIECE): if Board.within_starting_area(action, colour): # remove the action from the entry of the dictionary if action in self.available_actions[colour]: self.available_actions[colour].pop(action) # add all the eliminated pieces to the available moves of the dictionary for piece in eliminated_pieces[colour]: elim.append(piece) for colour in (constant.WHITE_PIECE, constant.BLACK_PIECE): for piece in elim: if Board.within_starting_area(piece, colour): update_entry = {piece: constant.PLACEMENT_PHASE} self.available_actions[colour].update(update_entry)
def start_available_actions_placement(self): # get rid of all pieces that exist on the board for colour in (constant.BLACK_PIECE, constant.WHITE_PIECE): for piece in self.board.piece_pos[colour]: if piece in self.available_actions[constant.WHITE_PIECE]: if Board.within_starting_area(piece, constant.WHITE_PIECE): self.available_actions[constant.WHITE_PIECE].pop(piece) if Board.within_starting_area(piece, constant.BLACK_PIECE): self.available_actions[constant.BLACK_PIECE].pop(piece)
class Player: def __init__(self, colour): if colour == 'white': self.colour = constant.WHITE_PIECE elif colour == 'black': self.colour = constant.BLACK_PIECE self.available_moves = [] # each players internal board representation self.board = Board() # TODO -- need to see if this works correctly self.strategy = MonteCarloTreeSearch(self.board, self.colour) self.opponent = self.board.get_opp_piece_type(self.colour) def update(self, action): # update the board based on the action of the opponent if self.board.phase == constant.PLACEMENT_PHASE: # update board also returns the pieces of the board that will be eliminated self.board.update_board(action, self.opponent) # self.board.eliminated_pieces[self.opponent] elif self.board.phase == constant.MOVING_PHASE: if isinstance(action[0], tuple) is False: print("ERROR: action is not a tuple") return move_type = self.board.convert_coord_to_move_type( action[0], action[1]) # update the player board representation with the action self.board.update_board((action[0], move_type), self.opponent) def action(self, turns): self.strategy.num_nodes = 0 self.strategy.update_board(self.board) if turns == 0 and self.board.phase == constant.MOVING_PHASE: self.board.move_counter = 0 self.board.phase = constant.MOVING_PHASE best_move = self.strategy.MCTS() # print("NUM NODE IN THIS TREE: " + str(self.strategy.num_nodes)) # once we have found the best move we must apply it to the board representation if self.board.phase == constant.PLACEMENT_PHASE: self.board.update_board(best_move, self.colour) return best_move else: new_pos = Board.convert_move_type_to_coord(best_move[0], best_move[1]) self.board.update_board(best_move, self.colour) return best_move[0], new_pos
def __init__(self, colour): if colour == 'white': self.colour = constant.WHITE_PIECE elif colour == 'black': self.colour = constant.BLACK_PIECE self.available_moves = [] # each players internal board representation self.board = Board() # initialise the available moves self.init_start_moves() self.opponent = self.board.get_opp_piece_type(self.colour)
def apply_horizontal_reflection(board_state): temp = '' for index in range(constant.BOARD_SIZE**2): temp += constant.FREE_SPACE temp = bytearray(temp, 'utf-8') for row in range(constant.BOARD_SIZE): for col in range(constant.BOARD_SIZE): Board.set_array_char( temp, 7 - row, 7 - col, Board.get_array_element(board_state, row, col)) # print(temp) # print(board_state) return temp
def undo_available_placement(self): # we just need to pop each piece from the undo_moves effected pieces while len(self.undo_effected) > 0: action = self.undo_effected.pop() #print("POP") #print(action) loc = action[0] #print(loc) colour = action[1] undo_type = action[2] opponent = Board.get_opp_piece_type(colour) if undo_type == constant.ELIMINATED_PIECE: # this piece was eliminated before the undo move, now we have placed it back on the board with undo if loc in self.available_actions[colour]: # remove the action from the dictionary of the corresponding colour self.available_actions[colour].pop(loc) if loc in self.available_actions[opponent]: self.available_actions[opponent].pop(loc) elif undo_type == constant.PLACE_LOC: # a piece was was placed at this location at prior to calling undo move # therefore to reestablish the original available moves list, then we need to add # this piece to the corresponding dict if loc not in self.available_actions[colour] and loc not in\ self.available_actions[opponent]: # if we can place a piece at this location again -- then this piece corresponds to a free space if self.board.within_starting_area(loc, colour): temp = {loc: constant.PLACEMENT_PHASE} self.available_actions[colour].update(temp) if self.board.within_starting_area(loc, opponent): temp = {loc: constant.PLACEMENT_PHASE} self.available_actions[opponent].update(temp)
def __init__(self, colour): if colour == 'white': self.colour = constant.WHITE_PIECE elif colour == 'black': self.colour = constant.BLACK_PIECE self.available_moves = [] # each players internal board representation self.board = Board() # TODO -- need to see if this works correctly self.strategy = MonteCarloTreeSearch(self.board, self.colour) self.opponent = self.board.get_opp_piece_type(self.colour)
def action(self, turns): self.minimax.update_board(self.board) # if action is called first the board representation move counter will be zero # this indicates that this player is the first one to move # if update is called before action the board representation counter will be 1, # this indicates that the player is the second to move if turns == 0 and self.board.phase == constant.MOVING_PHASE: self.board.move_counter = 0 self.board.phase = constant.MOVING_PHASE # create the node to search on root = self.minimax.create_node(self.colour, None) # update the board representation and the available moves self.minimax.update_minimax_board(None, root, start_node=True) # print(self.minimax.available_actions) best_move = self.minimax.alpha_beta_minimax(3, root) # do an alpha beta search on this node # once we have found the best move we must apply it to the board representation if self.board.phase == constant.PLACEMENT_PHASE: # print(best_move) self.board.update_board(best_move, self.colour) self.minimax.update_board(self.board) return best_move else: # (best_move is None) # print(best_move[0],best_move[1]) new_pos = Board.convert_move_type_to_coord(best_move[0], best_move[1]) self.board.update_board(best_move, self.colour) self.minimax.update_board(self.board) return best_move[0], new_pos
def action(self, turns): if turns == 0 and self.board.phase == constant.PLACEMENT_PHASE: self.board.set_player_to_move(self.colour) if turns == 24 and self.board.phase == constant.PLACEMENT_PHASE: self.board.move_counter = 0 self.board.phase = constant.MOVING_PHASE root = self.minimax.create_node(self.colour, None) self.minimax.update_minimax_board(None, root) # self.minimax.update_available_actions(None) # best_move = self.minimax.alpha_beta_minimax(2,root) # best_move = self.minimax.iterative_deepening_alpha_beta(root) best_move = self.minimax.alpha_beta_minimax(3, root) # do an alpha beta search on this node if self.board.phase == constant.PLACEMENT_PHASE: # print(best_move) self.board.update_board(best_move, self.colour) self.minimax.update_board(self.board) return best_move else: # (best_move is None) # print(best_move[0],best_move[1]) new_pos = Board.convert_move_type_to_coord(best_move[0], best_move[1]) self.board.update_board(best_move, self.colour) self.minimax.update_board(self.board) return best_move[0], new_pos
def __init__(self, colour): if colour == 'white': self.colour = constant.WHITE_PIECE elif colour == 'black': self.colour = constant.BLACK_PIECE self.available_moves = [] # each players internal board representation self.board = Board() # initialise the available moves self.init_start_moves() # TODO -- need to see if this works correctly self.minimax = MinimaxABUndo(self.board) self.opponent = self.board.get_opp_piece_type(self.colour)
def init_available_placement_actions(self): # initialise the dictionary with the available placements on the board for row in range(constant.BOARD_SIZE): for col in range(constant.BOARD_SIZE): piece = col, row # print(col,row) for colour in (constant.WHITE_PIECE, constant.BLACK_PIECE): if Board.within_starting_area(piece, colour): temp = {piece: constant.PLACEMENT_PHASE} # print(temp) self.available_actions[colour].update(temp)
def __init__(self, colour): if colour == 'white': self.colour = constant.WHITE_PIECE elif colour == 'black': self.colour = constant.BLACK_PIECE self.available_moves = [] # each players internal board representation self.board = Board() # TODO -- need to see if this works correctly self.minimax = Negamax(self.board, self.colour) self.opponent = self.board.get_opp_piece_type(self.colour) # self.search_algorithm = Minimax(self.board,self.available_moves,self.colour) # print(self.opponent) self.depth_eval = 0 self.minimax_val = 0 self.policy_vector = 0
def update_surrounding_pieces(self, center_pos): for move_type in range(constant.MAX_MOVETYPE): potential_piece = Board.convert_move_type_to_coord( center_pos, move_type) # check if the potential piece is a piece if potential_piece in self.available_actions[constant.WHITE_PIECE]: # then it is a piece on the board # update this piece self.update_actions_dict_entry(potential_piece, constant.WHITE_PIECE) elif potential_piece in self.available_actions[ constant.BLACK_PIECE]: self.update_actions_dict_entry(potential_piece, constant.BLACK_PIECE)
def __init__(self, board, colour): # we want to create a node self.tt = TranspositionTable() # only use this board to complete the search # save memory self.board = deepcopy(board) # for alpha beta search -- instead of passing it into the function calls we can use this self.alpha = -inf self.beta = inf # defines the colours of min and max self.player = colour self.opponent = Board.get_opp_piece_type(self.player) # default depth self.depth = inf # default move ordering with iterative deepening self.actions_evaluated = [] self.actions_leftover = [] # data structures for machine learning self.eval_depth = 0 self.minimax_val = 0 self.policy_vector = [] # dictionary storing the available moves of the board self.available_actions = { constant.WHITE_PIECE: {}, constant.BLACK_PIECE: {} } # generate the actions for the start of the game # self.generate_actions() self.undo_effected = [] self.time_alloc = 0 self.time_rem = 0 self.time_start = 0 self.time_end = 0 self.evaluation = Evaluation("./XML", "/eval_weights")
def action(self, turns): self.strategy.num_nodes = 0 self.strategy.update_board(self.board) if turns == 0 and self.board.phase == constant.MOVING_PHASE: self.board.move_counter = 0 self.board.phase = constant.MOVING_PHASE best_move = self.strategy.MCTS() # print("NUM NODE IN THIS TREE: " + str(self.strategy.num_nodes)) # once we have found the best move we must apply it to the board representation if self.board.phase == constant.PLACEMENT_PHASE: self.board.update_board(best_move, self.colour) return best_move else: new_pos = Board.convert_move_type_to_coord(best_move[0], best_move[1]) self.board.update_board(best_move, self.colour) return best_move[0], new_pos
def action(self, turns): self.minimax.update_board(self.board) # print(self.board.piece_pos) # if action is called first the board representation move counter will be zero # this indicates that this player is the first one to move # if update is called before action the board representation counter will be 1, # this indicates that the player is the second to move if turns == 0 and self.board.phase == constant.MOVING_PHASE: self.board.move_counter = 0 self.board.phase = constant.MOVING_PHASE # create the node to search on # update the board representation and the available moves # print(self.minimax.available_actions) # best_move = self.minimax.alpha_beta_minimax(3) best_move = self.minimax.itr_negamax() # best_move = self.minimax.alpha_beta(3) self.depth_eval = self.minimax.eval_depth self.minimax_val = self.minimax.minimax_val # do an alpha beta search on this node # once we have found the best move we must apply it to the board representation if self.board.phase == constant.PLACEMENT_PHASE: # print(best_move) self.board.update_board(best_move, self.colour) self.minimax.update_board(self.board) return best_move else: if best_move is None: return None # (best_move is None) # print(best_move[0],best_move[1]) new_pos = Board.convert_move_type_to_coord(best_move[0], best_move[1]) self.board.update_board(best_move, self.colour) self.minimax.update_board(self.board) return best_move[0], new_pos
def playgame(self, first_player=True): game = Board() state_index = 0 if first_player: # this player makes the move first self.game_state.append(deepcopy(game)) while game.is_terminal() is False: turns = game.move_counter action = self.player.action(turns) # append the depth at which the action was evaluated at -- this is so we can find the best child node later # we require r(s_i^l,w) -- now is s_i^l the minimax value that evaluated? self.eval_depths.append(self.player.depth_eval) self.minimax_eval.append(self.player.minimax_val) self.policy_vectors.append(self.player.policy_vector) # update the board game.update_board(action, self.player.colour) self.opponent.update(action) turns = game.move_counter # opponent makes a move action = self.opponent.action(turns) game.update_board(action, self.opponent.colour) self.player.update(action) # add the game state to the game state array self.game_state.append(deepcopy(game)) # return the outcome of the game if game.winner == constant.WHITE_PIECE: return 1 elif game.winner == constant.BLACK_PIECE: return -1 elif game.winner is None: return 0
class Player: def __init__(self, colour): if colour == 'white': self.colour = constant.WHITE_PIECE elif colour == 'black': self.colour = constant.BLACK_PIECE self.available_moves = [] # each players internal board representation self.board = Board() # initialise the available moves self.init_start_moves() # TODO -- need to see if this works correctly self.minimax = MinimaxABUndo(self.board) self.opponent = self.board.get_opp_piece_type(self.colour) # self.search_algorithm = Minimax(self.board,self.available_moves,self.colour) # print(self.opponent) # set up the board for the first time def init_start_moves(self): # set the initial board parameters # no pieces on the board # available moves is the entire starting zone for each player if self.colour == constant.WHITE_PIECE: # set the white pieces available moves for row in range(0, constant.BOARD_SIZE - 2): for col in range(constant.BOARD_SIZE): if (row, col) not in self.board.corner_pos: self.available_moves.append((col, row)) else: # set the black piece available moves for row in range(2, constant.BOARD_SIZE): for col in range(constant.BOARD_SIZE): if (row, col) not in self.board.corner_pos: # append the available move in the list in the form col, row self.available_moves.append((col, row)) def update(self, action): # print("UPDATING THIS ACTION : " + str(action)) if self.board.move_counter == 0: # then the opponent is the first person to move self.board.set_player_to_move(self.opponent) # update the board based on the action of the opponent # get move type if self.board.phase == constant.PLACEMENT_PHASE: # update board also returns the pieces of the board that will be eliminated self.board.update_board(action, self.opponent) # self.board.eliminated_pieces[self.opponent] self.minimax.update_board(self.board) # remove the opponent piece from the available moves list elif self.board.phase == constant.MOVING_PHASE: if isinstance(action[0], tuple) is False: print("asdfasf") return move_type = self.board.convert_coord_to_move_type( action[0], action[1]) # print("MOVETYPE: " + str(move_type)) # print(action[0]) self.board.update_board((action[0], move_type), self.opponent) #self.minimax.update_available_actions(action,self.opponent) def action(self, turns): if turns == 0 and self.board.phase == constant.PLACEMENT_PHASE: self.board.set_player_to_move(self.colour) if turns == 24 and self.board.phase == constant.PLACEMENT_PHASE: self.board.move_counter = 0 self.board.phase = constant.MOVING_PHASE root = self.minimax.create_node(self.colour, None) self.minimax.update_minimax_board(None, root) # self.minimax.update_available_actions(None) # best_move = self.minimax.alpha_beta_minimax(2,root) # best_move = self.minimax.iterative_deepening_alpha_beta(root) best_move = self.minimax.alpha_beta_minimax(3, root) # do an alpha beta search on this node if self.board.phase == constant.PLACEMENT_PHASE: # print(best_move) self.board.update_board(best_move, self.colour) self.minimax.update_board(self.board) return best_move else: # (best_move is None) # print(best_move[0],best_move[1]) new_pos = Board.convert_move_type_to_coord(best_move[0], best_move[1]) self.board.update_board(best_move, self.colour) self.minimax.update_board(self.board) return best_move[0], new_pos
def negascout(self, depth, colour): # Timeout handling self.time_end = self.curr_millisecond_time() if self.time_end - self.time_start > self.time_rem: raise TimeOut opponent = Board.get_opp_piece_type(colour) alpha = -inf original_alpha = alpha beta = inf dic = {self.player: 1, self.opponent: -1} move_to_try = None # check if the current board state is in the transposition table board_str = self.board.board_state.decode("utf-8") key = self.tt.contains(board_str, colour, phase=self.board.phase) if key is not None: board_str = key[0] entry = self.tt.get_entry(board_str, colour) tt_value = entry[0] tt_type = entry[1] tt_best_move = entry[2] tt_depth = entry[3] # if we have found an entry in the transposition table, then the move # we should try first is this best move move_to_try = tt_best_move # print(move_to_try) # print("FOUND ENTRY IN TT") if tt_depth >= depth: if tt_type == constant.TT_EXACT: # print("FOUND PV") return tt_best_move elif tt_type == constant.TT_LOWER: if tt_value > alpha: # print("FOUND FAIL SOFT") alpha = tt_value elif tt_type == constant.TT_UPPER: if tt_value < beta: # print("FOUND FAIL HARD") beta = tt_value if alpha >= beta: return tt_best_move actions_1 = self.board.update_actions(self.board, colour) # actions = actions_1 actions = self.board.sort_actions(actions_1, colour) # actions = actions_1 # terminal test -- default case if self.cutoff_test(depth): val = self.evaluate_state(self.board, self.player, actions_1) * dic[colour] return val, None # do the minimax search best_val = -inf best_action = None if move_to_try is not None and move_to_try in actions: # print("MOVE ORDERING") # put the move to try at the first position -- therefore it will be searched first actions = [move_to_try] + actions i = 0 if len(actions) <= 20: favourable = actions else: favourable = actions[:20] # print(len(actions)) # start negascout here for i, action in enumerate(favourable): # skip over the best action in the tt table if action == move_to_try and i > 0: continue self.board.update_board(action, colour) ''' if i == 0: # do a full search on the best move found so far score, _ = self.negascout(depth-1,-beta,-alpha, opponent) score = -score else: # assume that score, _ = self.negascout(depth-1,-alpha-1,-alpha,opponent) score = -score if alpha < score < beta: score, _ = self.negascout(depth-1,-beta,-score,opponent) score = -score ''' score = -self.negascout_value(depth - 1, -beta, -alpha, opponent) if alpha < score < beta and i > 0: score = -self.negascout_value(depth - 1, -beta, -alpha, opponent) if best_val < score: best_val = score best_action = action if alpha < score: alpha = score self.undo_move() if alpha >= beta: break beta = alpha + 1 # store the values in the transposition table if best_val <= original_alpha: # then this is an upperbound -FAILHARD tt_type = constant.TT_UPPER elif best_val >= beta: tt_type = constant.TT_LOWER # print("LOWER") else: tt_type = constant.TT_EXACT # print("EXACT") # add the entry to the transposition table self.tt.add_entry(self.board.board_state, colour, best_val, tt_type, best_action, depth) return best_action
def negamax(self, depth, alpha, beta, colour): # Timeout handling self.time_end = self.curr_millisecond_time() if self.time_end - self.time_start > self.time_rem: raise TimeOut opponent = Board.get_opp_piece_type(colour) dic = {self.player: 1, self.opponent: -1} # generate legal actions actions_1 = self.board.update_actions(self.board, colour) # print(len(actions)) actions = self.board.sort_actions(actions_1, colour) # terminal test -- default case if self.cutoff_test(depth): val = self.evaluate_state(self.board, self.player, actions) * dic[colour] return val, None # do the minimax search best_val = -inf best_action = None # generate legal actions #actions = self.board.update_actions(self.board, colour) # split the actions into favourable an unfavourable # if the length of actions is greater than X, then we can just choose to look through the first # 5 'favourable' actions that we see right now # if the length of actions is less than X, then we can just evaluate all possible actions we have # THIS IS A GREEDY APPROACH TO MINIMAX THAT LIMITS OUR BRANCHING FACTOR OF THE GAME if len(actions) > 8: favourable = actions[:8] else: favourable = actions # got here #print("got here") # depth reduction R = 2 #print(favourable) #self.board.print_board() for action in favourable: self.board.update_board(action, colour) if action in favourable: score, temp = self.negamax(depth - 1, -beta, -alpha, opponent) else: score, temp = self.negamax(depth - 1 - R, -beta, -alpha, opponent) score = -score if score > best_val: best_val = score best_action = action if score > alpha: alpha = score self.undo_move() if alpha >= beta: break return best_val, best_action
from Agents.Negamax import Negamax from DepreciatedBoard.Board import Board from Constants import constant # create a new board board = Board() colour = constant.WHITE_PIECE #agent = MonteCarloTreeSearch(board,colour) agent2 = Negamax(board,colour) actions = agent2.board.update_actions(agent2.board,agent2.player) print(actions) print(agent2.board.sort_actions(actions,agent2.player)) #node = agent.create_node(board,colour,None,None) #agent.simulate(node) # agent2.itr_negamax() ''' while(board.is_terminal() is False): agent = MonteCarloTreeSearch(board, colour) move = agent.MCTS() print(move) board.update_board(move,colour) board.print_board() colour = DepreciatedBoard.get_opp_piece_type(colour) '''
class Player: def __init__(self, colour): if colour == 'white': self.colour = constant.WHITE_PIECE elif colour == 'black': self.colour = constant.BLACK_PIECE self.available_moves = [] # each players internal board representation self.board = Board() # TODO -- need to see if this works correctly self.minimax = Negamax(self.board, self.colour) self.opponent = self.board.get_opp_piece_type(self.colour) # self.search_algorithm = Minimax(self.board,self.available_moves,self.colour) # print(self.opponent) self.depth_eval = 0 self.minimax_val = 0 self.policy_vector = 0 def update(self, action): # update the board based on the action of the opponent if self.board.phase == constant.PLACEMENT_PHASE: # update board also returns the pieces of the board that will be eliminated self.board.update_board(action, self.opponent) # self.board.eliminated_pieces[self.opponent] self.minimax.update_board(self.board) elif self.board.phase == constant.MOVING_PHASE: if isinstance(action[0], tuple) is False: print("ERROR: action is not a tuple") return move_type = self.board.convert_coord_to_move_type(action[0], action[1]) # update the player board representation with the action self.board.update_board((action[0], move_type), self.opponent) self.minimax.update_board(self.board) def action(self, turns): self.minimax.update_board(self.board) # print(self.board.piece_pos) # if action is called first the board representation move counter will be zero # this indicates that this player is the first one to move # if update is called before action the board representation counter will be 1, # this indicates that the player is the second to move if turns == 0 and self.board.phase == constant.MOVING_PHASE: self.board.move_counter = 0 self.board.phase = constant.MOVING_PHASE # create the node to search on # update the board representation and the available moves # print(self.minimax.available_actions) # best_move = self.minimax.alpha_beta_minimax(3) best_move = self.minimax.itr_negamax() # best_move = self.minimax.alpha_beta(3) self.depth_eval = self.minimax.eval_depth self.minimax_val = self.minimax.minimax_val # do an alpha beta search on this node # once we have found the best move we must apply it to the board representation if self.board.phase == constant.PLACEMENT_PHASE: # print(best_move) self.board.update_board(best_move, self.colour) self.minimax.update_board(self.board) return best_move else: if best_move is None: return None # (best_move is None) # print(best_move[0],best_move[1]) new_pos = Board.convert_move_type_to_coord(best_move[0], best_move[1]) self.board.update_board(best_move, self.colour) self.minimax.update_board(self.board) return best_move[0], new_pos
class Player: def __init__(self, colour): if colour == 'white': self.colour = constant.WHITE_PIECE elif colour == 'black': self.colour = constant.BLACK_PIECE self.available_moves = [] # each players internal board representation self.board = Board() # initialise the available moves self.init_start_moves() self.opponent = self.board.get_opp_piece_type(self.colour) # print(self.opponent) # set up the board for the first time def init_start_moves(self): # set the initial board parameters # no pieces on the board # available moves is the entire starting zone for each player if self.colour == constant.WHITE_PIECE: # set the white pieces available moves for row in range(0, constant.BOARD_SIZE - 2): for col in range(constant.BOARD_SIZE): if (row, col) not in self.board.corner_pos: self.available_moves.append((col, row)) else: # set the black piece available moves for row in range(2, constant.BOARD_SIZE): for col in range(constant.BOARD_SIZE): if (row, col) not in self.board.corner_pos: # append the available move in the list in the form col, row self.available_moves.append((col, row)) def update(self, action): # print("UPDATING THIS ACTION : " + str(action)) if self.board.move_counter == 0: # then the opponent is the first person to move self.board.set_player_to_move(self.opponent) # update the board based on the action of the opponent # get move type if self.board.phase == constant.PLACEMENT_PHASE: # update board also returns the pieces of the board that will be eliminated self.board.update_board(action, self.opponent) eliminated_pieces = self.board.eliminated_pieces[self.opponent] # remove the eliminated pieces from the available moves of this player for piece in eliminated_pieces: if piece in self.available_moves and Player.within_starting_area( piece, self.colour): # self.available_moves.remove(piece) self.available_moves.append(piece) # remove the opponent piece from the available moves list if action in self.available_moves: self.available_moves.remove(action) # print(self.available_moves) elif self.board.phase == constant.MOVING_PHASE: if isinstance(action[0], tuple) is False: # print("WHYYYYYYYY") return move_type = self.board.convert_coord_to_move_type( action[0], action[1]) # print("MOVETYPE: " + str(move_type)) # print(action[0]) self.board.update_board((action[0], move_type), self.opponent) def action(self, turns): # print("TURNS SO FAR ---------- " + str(turns)) # print("ACTION CALLED: BOARD REPRESENTATION COUNTER: " + str(self.board.move_counter)) if turns == 0 and self.board.phase == constant.PLACEMENT_PHASE: # then we are first person to move self.board.set_player_to_move(self.colour) if turns < 24 and self.board.phase == constant.PLACEMENT_PHASE: # then we pick the best move to make based on a search algorithm search_algorithm = Random(len(self.available_moves)) next_move = self.available_moves[search_algorithm.choose_move()] # making moves during the placement phase self.board.update_board(next_move, self.colour) eliminated_pieces = self.board.eliminated_pieces[self.colour] # remove the move made from the available moves self.available_moves.remove(next_move) if len(eliminated_pieces) != 0: for piece in eliminated_pieces: if piece in self.available_moves: self.available_moves.remove(piece) return next_move elif self.board.phase == constant.MOVING_PHASE: if turns == 0 or turns == 1: # if the turns is 0 or 1 and the board is in moving phase then the # all players have placed their pieces on the board, we can call update_available_moves to update the # available moves available to this player # clear the list self.available_moves = [] # update the lists available moves -- now in the form ((col,row),move_type) # self.update_available_moves() # print(self.available_moves) # we are making a move in the moving phase #print(self.available_moves) self.update_available_moves() # if there are no available moves to be made we can return None: if len(self.available_moves) == 0: return None # print("AVAILABLE MOVES: " + str(self.colour) + " " + str(self.available_moves)) # if there is a move to be made we can return the best move # TODO : THIS IS WHERE WE CARRY OUT OUR SEARCH ALGORITHM # then we pick the best move to make based on a search algorithm search_algorithm = Random(len(self.available_moves)) next_move = self.available_moves[search_algorithm.choose_move()] self.board.update_board(next_move, self.colour) new_pos = self.board.convert_move_type_to_coord( next_move[0], next_move[1]) # print(self.colour + " " + str(self.board.piece_pos)) # TODO - need to double check if this update_available_moves is necessary self.update_available_moves() #print(getsizeof(self.board.piece_pos)) #print(getsizeof(self.board.board_state)) #print(getsizeof(self.available_moves)) return next_move[0], new_pos # updates the available moves a piece can make after it has been moved # this way we don;t need to calculate all the available moves on the board # as pieces that have been eliminated also get rid of those associated available moves def update_available_moves(self): # clear the available moves available_moves = [] self.available_moves = [] # recalculate the moves a piece can make based on the available pieces on the board # print(self.colour) # print("-"*20) # self.board.print_board() # print("-"*20) # print("THIS PLAYERS CURRENT PIECES: " + str(self.colour) + str(self.board.piece_pos[self.colour])) for piece in self.board.piece_pos[self.colour]: for move_type in range(constant.MAX_MOVETYPE): if self.board.is_legal_move(piece, move_type): available_moves.append((piece, move_type)) self.available_moves = available_moves @staticmethod def within_starting_area(move, colour): if colour == constant.WHITE_PIECE: # update the starting rows based off the player colour if colour == constant.WHITE_PIECE: min_row = 0 max_row = 6 elif colour == constant.BLACK_PIECE: min_row = 2 max_row = 8 col, row = move if min_row <= row <= max_row: return True else: return False
from DepreciatedBoard.Board import Board from Constants import constant from Agents.Minimax import Minimax # create a new board game board_game = Board() board_game.print_board() print() print() # create the starting node -- this node is black node = Minimax.create_node(board_game, constant.BLACK_PIECE, None) node.board.print_board() print(node.available_moves) print(node.colour) print() # to create a child node we apply one of the move from black to the next node child = Minimax.create_node(node.board, node.colour, (3, 2)) child.board.print_board() print(child.available_moves) print(child.board.move_counter) print(child.board.piece_pos) print(child.board.eliminated_pieces) child = Minimax.create_node(child.board, Board.get_opp_piece_type(child.colour), (3, 3)) child.board.print_board() print(child.available_moves) print(child.board.move_counter)
def set_player_colour(self, colour): self.player = colour self.opponent = Board.get_opp_piece_type(colour)
from Agents.Minimax import MinimaxABUndo from DepreciatedBoard.Board import Board from Constants import constant board = Board() colour = constant.WHITE_PIECE board.print_board() agent = MinimaxABUndo(board) root = agent.create_node(colour, None) agent.update_minimax_board(None, root, start_node=True) action = agent.alpha_beta_minimax(3, root) print(action)
def negamax(self, depth, alpha, beta, colour): # Timeout handling self.time_end = self.curr_millisecond_time() if self.time_end - self.time_start > self.time_rem: raise TimeOut opponent = Board.get_opp_piece_type(colour) original_alpha = alpha dic = {self.player: 1, self.opponent: -1} ''' move_to_try = None # check if the current board state is in the transposition table board_str = self.board.board_state.decode("utf-8") key = self.tt.contains(board_str,colour,phase=self.board.phase) if key is not None: board_str = key[0] entry = self.tt.get_entry(board_str,colour) tt_value = entry[0] tt_type = entry[1] tt_best_move = entry[2] tt_depth = entry[3] # if we have found an entry in the transposition table, then the move # we should try first is this best move move_to_try = tt_best_move #print("FOUND ENTRY IN TT") if tt_depth >= depth: if tt_type == constant.TT_EXACT: #print("FOUND PV") return tt_value, tt_best_move elif tt_type == constant.TT_LOWER: if tt_value > alpha: #print("FOUND FAIL SOFT") alpha = tt_value elif tt_type == constant.TT_UPPER: if tt_value < beta: #print("FOUND FAIL HARD") beta = tt_value if alpha >= beta: return tt_value, None ''' # terminal test -- default case if self.cutoff_test(depth): val = self.evaluate_state(self.board, self.player) #*dic[colour] return val, None # do the minimax search best_val = -inf best_action = None actions = self.board.update_actions(self.board, colour) ''' if move_to_try is not None and move_to_try in actions: #print("MOVE ORDERING") # put the move to try at the first position -- therefore it will be searched first actions = [move_to_try] + actions i = 0 ''' # get the favourable moves of the board actions = self.get_favourable_actions(self.available_actions) # if there are no favourable actions to iterate on - raise if len(actions) < 0: raise ReturnUnfavourableMove for action in actions: # skip over the best action in the tt table ''' if action == move_to_try and i!= 0: continue i+=1 ''' self.board.update_board(action, colour) score, temp = self.negamax(depth - 1, -beta, -alpha, opponent) score = -score if score > best_val: best_val = score best_action = action if score > alpha: alpha = score self.undo_move() if alpha >= beta: break ''' # store the values in the transposition table if best_val <= original_alpha: # then this is an upperbound -FAILHARD tt_type = constant.TT_UPPER elif best_val >= beta: tt_type = constant.TT_LOWER # print("LOWER") else: tt_type = constant.TT_EXACT # print("EXACT") ''' # add the entry to the transposition table # self.tt.add_entry(self.board.board_state,colour,best_val,tt_type,best_action, depth) return best_val, best_action
def update_available_moves(self, action, colour): # if there were any eliminated pieces last move retrieve them from the stack -- but make sure not to pop them # off the stack completely eliminated_pieces = self.board.eliminated_pieces_last_move( self.board.phase, self.board.move_counter, pop=False) # action is in the form (position, movetype) # -- i,e. we are moving the piece at position by the movetype # -- when an action is called we have move that piece already and we need to change # -- the entries in the dictionary according to that move # colour is the colour of the piece we have moved # read in the pieces on the board -- if they already exist in the dictionary # then we dont need to do anything -- if they don't exist in the dictionary # need to look at all the eliminated pieces on the board # -- look for pieces in the vicinity of that space # -- delete keys associated with those eliminated pieces as these are pieces on the board # -- that do not exists anymore, therefore there are no associated moves with this piece # -- update the available moves of the pieces that can move into that square # need to update the available moves of the piece at its new location # delete entry in the dictionary that corresponds to the old position old_pos = action[0] #print(old_pos) #print(action) new_pos = Board.convert_move_type_to_coord(old_pos, action[1]) # first we need to update the dictionary by removing the old piece from the # dictionary -- as this is not an available move anymore if old_pos in self.available_actions[colour]: #print("old") self.available_actions[colour].pop(old_pos) else: pass # need to raise an error saying # then add an entry into the dictionary corresponding to the new location of the piece # after the move has been applied if new_pos not in self.available_actions[colour]: self.update_actions_dict_entry(new_pos, colour) else: pass # need to raise an error # remove all eliminated pieces from the dictionary for piece_type in (constant.WHITE_PIECE, constant.BLACK_PIECE): for piece in eliminated_pieces[piece_type]: if piece in self.available_actions[piece_type]: self.available_actions[piece_type].pop(piece) else: pass # need to raise an error # update any piece that is surrounding the old position but also any eliminated pieces and update # their available moves by adding the corresponding move type to that list # this old position is now a free space on the board and therefore pieces are able to now move into it # need to test all positions surround this newly freed space and update their available actions for move_type in range(constant.MAX_MOVETYPE): # iterate through all the possible moves at the old location, checking # whether or not there is a piece there # if there is a piece at that location we can update that piece's available moves piece = Board.convert_move_type_to_coord(old_pos, move_type) for piece_colour in (constant.WHITE_PIECE, constant.BLACK_PIECE): if piece in self.available_actions[piece_colour]: if move_type < 4: self.update_actions_dict_entry(piece, piece_colour) else: if self.board.can_jump_into_position( old_pos, move_type): self.update_actions_dict_entry(piece, piece_colour) # update the pieces around any eliminated pieces for piece_colour in (constant.WHITE_PIECE, constant.BLACK_PIECE): # iterate through all the eliminated pieces on the board for elim_piece in eliminated_pieces[piece_colour]: # for each eliminated piece we apply a move (move_type to it), checking if there is a piece # at this position on the board, we do this by checking the available moves dictionary # if there is a piece associated with that position on the board then if it is a one step move # we just need to update that pieces available moves, if it is a jump, then we need to test if there # is an adjacent piece between the jump and the free space -- do this by calling # can_jump_into_position -- for a given space, if we apply a move_type corresponding to a # two piece move, can we jump into this free spot # if we can then we just need to update this pieces available actions piece = Board.convert_move_type_to_coord( elim_piece, move_type) ''' # if this piece corresponds to an entry in the dictionary, then there is a piece at this location if piece in self.available_actions[piece_colour]: # one step moves if move_type < 4: self.update_actions_dict_entry(piece,piece_colour) else: # need to check if a jump is available into the free space # if the piece at the jump location is in the available_action dict if self.board.can_jump_into_position(elim_piece,move_type): self.update_actions_dict_entry(piece,piece_colour) ''' self.update_surrounding_pieces(piece) # update the available moves of the pieces that surround where the # new position of the piece is -- this is no longer an occupied space therefore pieces surrounding # it cannot move into this space anymore piece = Board.convert_move_type_to_coord(new_pos, move_type) for piece_colour in (constant.WHITE_PIECE, constant.BLACK_PIECE): if piece in self.available_actions[piece_colour]: ''' if move_type < 4: self.update_actions_dict_entry(piece,piece_colour) else: # treat this old position as a free space -- if there are pieces # that can jump into this piece we have to update these pieces available # actions because this space is no longer free if self.board.can_jump_into_position(new_pos,move_type): self.update_actions_dict_entry(piece,piece_colour) ''' self.update_surrounding_pieces(piece)
from DepreciatedBoard.Board import Board from Constants import constant from Agents.Minimax import Minimax # create a new board game board_game = Board() board_game.print_board() # create the starting node -- this node is black node = Minimax.create_node(board_game, constant.BLACK_PIECE, None) node.board.print_board() print(node.available_moves) print(node.colour) print() # to create a child node we apply one of the move from black to the next node child = Minimax.create_node(node.board,Board.get_opp_piece_type(node.colour),node.available_moves[2]) child.board.print_board() print(len(child.available_moves)) print(child.colour) print() print("UNDO MOVE") child.board.undo_move() child.board.print_board() print(child.board.eliminated_pieces) print(child.board.piece_pos) child = Minimax.create_node(child.board,Board.get_opp_piece_type(child.colour),child.available_moves[3]) child.board.print_board()
def action(self, turns): # print(self.board.piece_pos) # print(self.board.eliminated_pieces) # p rint("THIS BOARD REPRESENTATION") # self.board.print_board() # print("TURNS SO FAR ---------- " + str(turns)) # print("ACTION CALLED: BOARD REPRESENTATION COUNTER: " + str(self.board.move_counter)) if turns == 0 and self.board.phase == constant.PLACEMENT_PHASE: # then we are first person to move self.board.set_player_to_move(self.colour) if turns < 24 and self.board.phase == constant.PLACEMENT_PHASE: for colour in (constant.BLACK_PIECE, constant.WHITE_PIECE): for piece in self.board.eliminated_pieces[colour]: if (piece not in self.available_moves) and ( Board.within_starting_area(piece, self.colour)): self.available_moves.append(piece) # print("ACTION: ",end='') # print(piece) # then we pick the best move to make based on a search algorithm self.available_moves.sort() for i in range(len(self.available_moves)): print(str(i) + " : " + str(self.available_moves[i])) index = int(input("Enter move: ")) next_move = self.available_moves[index] # making moves during the placement phase self.board.update_board(next_move, self.colour) # print(self.board.eliminated_pieces) # print("EEEEEE") # print("BOARDS TURNS NOW ---------- " + str(self.board.move_counter)) for colour in (constant.BLACK_PIECE, constant.WHITE_PIECE): for piece in self.board.eliminated_pieces[colour]: if (piece not in self.available_moves) and ( Board.within_starting_area(piece, self.colour)): self.available_moves.append(piece) # print("ACTION: ",end='') # print(piece) print(self.available_moves) # remove the move made from the available moves self.available_moves.remove(next_move) ''' if len(eliminated_pieces) != 0: for piece in eliminated_pieces: print(piece) if piece in self.available_moves: self.available_moves.remove(piece) ''' return next_move elif self.board.phase == constant.MOVING_PHASE: if turns == 0 or turns == 1: # if the turns is 0 or 1 and the board is in moving phase then the # all players have placed their pieces on the board, we can call update_available_moves to update the # available moves available to this player # clear the list self.available_moves = [] # update the lists available moves -- now in the form ((col,row),move_type) # self.update_available_moves() # print(self.available_moves) # we are making a move in the moving phase #print(self.available_moves) self.update_available_moves() # if there are no available moves to be made we can return None: if len(self.available_moves) == 0: return None # print("AVAILABLE MOVES: " + str(self.colour) + " " + str(self.available_moves)) # if there is a move to be made we can return the best move # TODO : THIS IS WHERE WE CARRY OUT OUR SEARCH ALGORITHM # then we pick the best move to make based on a search algorithm self.available_moves.sort() for i in range(len(self.available_moves)): print(str(i) + " : " + str(self.available_moves[i])) index = int(input("Enter move: ")) next_move = self.available_moves[index] self.board.update_board(next_move, self.colour) new_pos = self.board.convert_move_type_to_coord( next_move[0], next_move[1]) # print(self.colour + " " + str(self.board.piece_pos)) # TODO - need to double check if this update_available_moves is necessary self.update_available_moves() #print(getsizeof(self.board.piece_pos)) #print(getsizeof(self.board.board_state)) #print(getsizeof(self.available_moves)) return next_move[0], new_pos