def determineWin(self, board: np.ndarray, player: BoardPiece) -> bool: """ # Case 1: player == 1, and wins -> return true # Case 2: player == 2, and wins -> return false """ if check_end_state(board, player) == GameState.IS_WIN: return True elif check_end_state(board, opponent(player)) == GameState.IS_WIN: return False else: # draw return None
def rollout(self, board: np.ndarray, player: BoardPiece) -> BoardPiece: """ Recursive call with opponent, determineWin() check for and return win :param board: :param player: :return: player if won, otherwise continue the rollout with recursive call """ if self.isTerminal(board, player): if self.determineWin(board, player): # if win return player elif self.determineWin(board, opponent(player)): # if loss return opponent(player) else: # if draw return BoardPiece(0) # None else: # generate a random move, create a new board state, apply random move random_move, saved_state = generate_move_random(board, player) random_board = apply_player_action(board, random_move, player, True) return self.rollout(random_board, opponent( player)) # recursive call, passing in new board & opponent
def create_child(self, avail_cols: list): """ Create a new child node during Expansion at a random column within the available moves (columns) :param avail_cols: :return: the new child node """ col = np.random.choice(avail_cols) board_copy = apply_player_action(self.board, col, self.nodePlayer, True) new_node = Node(board_copy, opponent(self.nodePlayer)) new_node.parent = self self.children.append(new_node) return new_node
def mcts(tree: Tree): """ Run many iterations of the MCTS tree and build out its statistics :param tree: :return None """ for _ in range(3000): node = tree.select(tree.root) test_node, test_player = tree.expand(node, node.nodePlayer) winner = test_node.rollout( test_node.board, opponent(test_player)) # rollout on opponent tree.update_Tree(test_node, winner) return
def minimax( board: np.ndarray, player: BoardPiece, saved_state: Optional[SavedState] = None ) -> Tuple[int, Optional[SavedState]]: """ This function returns the best position for the agent to play by returning the appropriate column index. The agent checks to see if there is a win in any of the available columns, and if so, makes that move. If not, it iterates through every column, makes a move there, and checks whether the opposing player can win subsequently. If the opposing player can win given the current player's first move, the current player chooses not to make that move in the first place. Keyword arguments: board: the board that the player is playing and trying to win player: current player saved_state: Optional Saved State Returns: Tuple: consisting of the location of the column of the best move, and the Optional Saved State """ danger_col = [] columns = [0, 1, 2, 3, 4, 5, 6] score = 0 other_player = opponent(player) for i in available_columns(board): board_i = apply_player_action(board, i, player, True) if connected_four(board_i, player) == True: return i, saved_state else: danger_col = [] for j in available_columns(board_i): board_i_j = apply_player_action(board_i, j, other_player, True) if connected_four(board_i_j, other_player) == True: danger_col.append( j ) # these columns will lead to the opposing player's win if len(danger_col) != 0: columns.remove( i ) # don't use columns i in random.choice if they will lead to an other_player win cols = np.array(columns) action = np.random.choice( cols) # randomly choose a column that will avoid a loss in the # opposing player's next move return action, saved_state
def isTerminal(self, board: np.ndarray, player: BoardPiece) -> bool: """ Take in a board, and a player, and make a determination if the board is terminal or not. Check if no available columns (moves) or if either player has won. :param self: :param board: :param player: :return: True or False """ if available_columns(board) == [] or self.determineWin( board, player) or self.determineWin(board, opponent(player)): return True else: return False
def expand(self, node: Node, player: BoardPiece) -> Tuple[Node, BoardPiece]: # insert node """ Create one child node of the given node. If no more children left to create, return self. :param: parent node :return: True """ avail_cols = available_columns(node.board) if avail_cols is None: # no available moves print('board is full') return node, player # return self else: new_node = Node.create_child(node, avail_cols) return new_node, opponent( player) # child node has board with opponent's turn to play