def test_check_end_state_row(): board = cm.initialize_game_state() board[0, 0] = cm.PLAYER1 board[0, 1] = cm.PLAYER1 board[0, 2] = cm.PLAYER1 board[0, 3] = cm.PLAYER1 assert cm.GameState.IS_WIN == cm.check_end_state(board, cm.PLAYER1) assert not cm.GameState.IS_WIN == cm.check_end_state(board, cm.PLAYER2)
def test_check_end_state_column(): board = cm.initialize_game_state() board[0, 0] = cm.PLAYER1 board[1, 0] = cm.PLAYER1 board[2, 0] = cm.PLAYER1 board[3, 0] = cm.PLAYER1 assert cm.GameState.IS_WIN == cm.check_end_state(board, cm.PLAYER1) assert not cm.GameState.IS_WIN == cm.check_end_state(board, cm.PLAYER2)
def test_check_end_state_left_diagonal(): board = cm.initialize_game_state() board[0, 1] = cm.PLAYER1 board[1, 1] = cm.PLAYER1 board[2, 1] = cm.PLAYER1 board[3, 1] = cm.PLAYER2 board[0, 2] = cm.PLAYER2 board[1, 2] = cm.PLAYER2 board[2, 2] = cm.PLAYER2 board[0, 3] = cm.PLAYER1 board[1, 3] = cm.PLAYER2 board[0, 4] = cm.PLAYER2 print(cm.pretty_print_board(board)) assert cm.GameState.IS_WIN == cm.check_end_state(board, cm.PLAYER2) assert not cm.GameState.IS_WIN == cm.check_end_state(board, cm.PLAYER1)
def get_open_moves(self) -> np.ndarray: """ Getter function to obtain possible open columns :return: An array of column numbers """ if common.check_end_state(self.state, self.player) == common.GameState.IS_WIN: return np.array([]) else: return np.array(common.get_free_columns(self.state))
def human_vs_agent( generate_move_1: GenMove, generate_move_2: GenMove = user_move, player_1: str = "Player 1", player_2: str = "Player 2", args_1: tuple = (), args_2: tuple = (), init_1: Callable = lambda board, player: None, init_2: Callable = lambda board, player: None, ): players = (PLAYER1, PLAYER2) for play_first in (1, -1): for init, player in zip((init_1, init_2)[::play_first], players): init(initialize_game_state(), player) saved_state = {PLAYER1: None, PLAYER2: None} board = initialize_game_state() gen_moves = (generate_move_1, generate_move_2)[::play_first] player_names = (player_1, player_2)[::play_first] gen_args = (args_1, args_2)[::play_first] playing = True end_state = GameState.STILL_PLAYING while playing: for player, player_name, gen_move, args in zip( players, player_names, gen_moves, gen_args, ): t0 = time.time() print(pretty_print_board(board)) action, saved_state[player] = gen_move(board.copy(), player, saved_state[player], *args) print('{} \'s action is {}'.format(player_name, action)) print(f"Move time: {time.time() - t0:.3f}s") board, r_board = apply_player_action(board, action, player, True) end_state = check_end_state(board, player) if end_state != GameState.STILL_PLAYING: print(pretty_print_board(board)) if end_state == GameState.IS_DRAW: print("Game ended in draw") else: print(f'{player_name} won playing \ {"X" if player == PLAYER1 else "O"}') playing = False break
def minimax(board: np.ndarray, depth: int, alpha: int, beta: int, maximizing_player: bool) -> tuple: """ Applies the minimax algorithm on the board to give a suggested column number and maximising/minimising score :parameter board: the playing board of type np.ndarray :parameter depth: the depth till which to evaluate the board with the algorithm :parameter alpha: the minimum score the maximising player is assured of :parameter beta: the maximum score the minimising player is assured of :parameter maximizing_player: boolean flag suggesting if the algorithm needs to maximise or minimise :return: The column and computed maximum/minimum score """ # if depth is 0 or node is terminal, return heuristic value of the node # if terminal check if it is win , loss or draw if cn.connected_four(board, AGENT): return None, 10000 if cn.connected_four(board, HUMAN): return None, -10000 # Check if there is a draw if cn.check_end_state(board, AGENT) == cn.GameState.IS_DRAW: return None, 0 # if depth is 0, calculate heuristic scoring if depth == 0: return None, heuristic_scoring(board, AGENT) # get a list of columns open for placement col_list = cn.get_free_columns(board) if maximizing_player: # value = -infinity value = -math.inf column = np.random.choice(col_list) # for child node (interpreted it as available columns) for col in col_list: # apply a random column to check the minimax b_copy, ori_board = cn.apply_player_action(board, col, AGENT, True) # get the score only new_score = minimax(b_copy, depth - 1, alpha, beta, False)[1] # personal debugging if depth == GLOBAL_DEPTH: print('For col {}, the score is {}'.format(col, new_score)) # use the new score if higher if new_score > value: value = new_score column = col # prune in the maximising node alpha = max(alpha, value) if alpha >= beta: break return column, value # minimising branch else: # value = +infinity value = math.inf column = np.random.choice(col_list) for col in col_list: b_copy, ori_board = cn.apply_player_action(board, col, HUMAN, True) new_score = minimax(b_copy, depth - 1, alpha, beta, True)[1] # get the minimizing score if new_score < value: value = new_score column = col # do beta pruning check beta = min(beta, value) if alpha >= beta: break return column, value
def test_end_state_draw(): board = 5 * np.ones((6, 7), dtype=BoardPiece) assert cm.GameState.IS_DRAW == cm.check_end_state(board, cm.PLAYER1)