def test_obvious_first_movement(self, clean_tree_search_caches_before_tests): # Here we are going to set up a game where player 1 is about to win the game, except if # player 2 (who goes first) prevents it by placing a piece on the square player 1 needs to use # to win game = Game() game.starting_player = game.player_2 five = game.player_1.get_piece_by_type(PieceType.five) five.set_movement_direction(Direction.east) game.board.get_tile(1, 1).place_piece(five) four = game.player_1.get_piece_by_type(PieceType.four) four.set_movement_direction(Direction.west) game.board.get_tile(3, 2).place_piece(four) opponent_four = game.player_2.get_piece_by_type(PieceType.four) opponent_four.set_movement_direction(Direction.west) game.board.get_tile(1, 3).place_piece(opponent_four) best_moves, _, eval_type = get_best_moves(game.player_2, game.player_1, is_first_move=True, depth=1) assert len(best_moves) == 1 assert eval_type == EvaluationType.exact # Player 2 can only avoid losing this turn by playing anything on tile (2, 4) best_move = get_best_move(game.player_2, game.player_1, is_first_move=True, depth=1) assert best_move.x == 2 assert best_move.y == 4 # Should be true for any depth level (using 2 for test speed reasons) best_move_depth_2 = get_best_move(game.player_2, game.player_1, is_first_move=True, depth=2) assert best_move_depth_2.x == 2 assert best_move_depth_2.y == 4
def test_using_inverted_alpha_beta_cutoffs( self, clean_tree_search_caches_before_tests): game = Game() # Player 1 pieces one_p1 = game.player_1.get_piece_by_type(PieceType.one) one_p1.set_movement_direction(Direction.east) game.board.get_tile(1, 3).place_piece(one_p1) four_p1 = game.player_1.get_piece_by_type(PieceType.four) four_p1.set_movement_direction(Direction.south) game.board.get_tile(3, 2).place_piece(four_p1) five_p1 = game.player_1.get_piece_by_type(PieceType.five) five_p1.set_movement_direction(Direction.south) game.board.get_tile(2, 2).place_piece(five_p1) # Player 2 pieces two_p2 = game.player_2.get_piece_by_type(PieceType.two) two_p2.set_movement_direction(Direction.west) game.board.get_tile(3, 1).place_piece(two_p2) three_p2 = game.player_2.get_piece_by_type(PieceType.three) three_p2.set_movement_direction(Direction.east) game.board.get_tile(2, 3).place_piece(three_p2) four_p2 = game.player_2.get_piece_by_type(PieceType.four) four_p2.set_movement_direction(Direction.east) game.board.get_tile(2, 1).place_piece(four_p2) five_p2 = game.player_2.get_piece_by_type(PieceType.five) five_p2.set_movement_direction(Direction.east) game.board.get_tile(1, 2).place_piece(five_p2) _, score, eval_type = get_best_moves(game.player_1, game.player_2, is_first_move=True, depth=2) assert eval_type == EvaluationType.exact
def test_transposition_does_not_use_wrong_alphabeta_cutoffs( self, clean_tree_search_caches_before_tests): game = Game() game.starting_player = game.player_2 game.first_move_executed = True one_p1 = game.player_1.get_piece_by_type(PieceType.one) one_p1.set_movement_direction(Direction.south) game.board.get_tile(2, 3).place_piece(one_p1) one_p2 = game.player_2.get_piece_by_type(PieceType.one) one_p2.set_movement_direction(Direction.west) game.board.get_tile(3, 3).place_piece(one_p2) five_p2 = game.player_2.get_piece_by_type(PieceType.five) five_p2.set_movement_direction(Direction.west) game.board.get_tile(4, 2).place_piece(five_p2) best_move_p1_r1 = get_best_move(game.player_1, game.player_2, is_first_move=False, depth=2) assert best_move_p1_r1.piece_type != PieceType.four or best_move_p1_r1.x != 0 or best_move_p1_r1.y != 3 # We ignore the correct movement and play a losing move (4 piece to (0, 3)) four_p1 = game.player_1.get_piece_by_type(PieceType.four) four_p1.set_movement_direction(Direction.east) game.board.get_tile(0, 3).place_piece(four_p1) game.board.execute_board_movements(game.player_2.id) game.switch_starting_player() _, score, eval_type = get_best_moves(game.player_1, game.player_2, is_first_move=True, depth=2) assert score == -WIN_CONDITION_SCORE + DEPTH_PENALTY assert eval_type == EvaluationType.exact
def test_game_is_lost_on_depth_2_alternative(self, clean_tree_search_caches_before_tests): game = Game() game.starting_player = game.player_2 three = game.player_1.get_piece_by_type(PieceType.three) three.set_movement_direction(Direction.east) game.board.get_tile(2, 1).place_piece(three) four = game.player_1.get_piece_by_type(PieceType.four) four.set_movement_direction(Direction.south) game.board.get_tile(2, 3).place_piece(four) five = game.player_1.get_piece_by_type(PieceType.five) five.set_movement_direction(Direction.south) game.board.get_tile(1, 2).place_piece(five) four_p2 = game.player_2.get_piece_by_type(PieceType.four) four_p2.set_movement_direction(Direction.west) game.board.get_tile(3, 2).place_piece(four_p2) five_p2 = game.player_2.get_piece_by_type(PieceType.five) five_p2.set_movement_direction(Direction.west) game.board.get_tile(2, 2).place_piece(five_p2) # At this position, player 2 is losing at depth 2. He can avoid losing this round by playing either: # 1) The 1 piece at (0, 3) or (1, 4) # 2) the 2 or 3 pieces at (3, 0) or (4, 1), # but next round he will be unable to block both squares where P1 can mate, and will not have the 4 in hand best_moves_p2_t1, score, eval_type = get_best_moves(game.player_2, game.player_1, is_first_move=True, depth=2) assert 6 >= len(best_moves_p2_t1) >= 1 assert eval_type == EvaluationType.exact best_move_p2 = get_best_move(game.player_2, game.player_1, is_first_move=True, depth=2) assert (best_move_p2.x == 0 and best_move_p2.y == 3 and best_move_p2.piece_type == PieceType.one) or \ (best_move_p2.x == 1 and best_move_p2.y == 4 and best_move_p2.piece_type == PieceType.one) or \ (best_move_p2.x == 3 and best_move_p2.y == 0 and best_move_p2.piece_type in [PieceType.two, PieceType.three]) or \ (best_move_p2.x == 4 and best_move_p2.y == 1 and best_move_p2.piece_type in [PieceType.two, PieceType.three]) assert best_move_p2.score == -WIN_CONDITION_SCORE + DEPTH_PENALTY * 2
def test_game_is_lost_on_depth_2(self, clean_tree_search_caches_before_tests): game = Game() game.starting_player = game.player_1 two = game.player_1.get_piece_by_type(PieceType.two) two.set_movement_direction(Direction.south) game.board.get_tile(2, 3).place_piece(two) three = game.player_1.get_piece_by_type(PieceType.three) three.set_movement_direction(Direction.east) game.board.get_tile(1, 2).place_piece(three) four = game.player_1.get_piece_by_type(PieceType.four) four.set_movement_direction(Direction.east) game.board.get_tile(0, 2).place_piece(four) one_p2 = game.player_2.get_piece_by_type(PieceType.one) one_p2.set_movement_direction(Direction.north) game.board.get_tile(2, 2).place_piece(one_p2) two_p2 = game.player_2.get_piece_by_type(PieceType.two) two_p2.set_movement_direction(Direction.west) game.board.get_tile(3, 2).place_piece(two_p2) # At this position, player 2 is completely losing at depth 2 # He can avoid losing this round by playing the 4 piece at (2, 0), but next round he will # be unable to block both squares where P1 can mate, and will not have the 4 in hand best_moves_p2_t1, score_p2_t1, eval_type_p2_t1 = get_best_moves(game.player_2, game.player_1, is_first_move=False, depth=2) assert len(best_moves_p2_t1) == 1 assert eval_type_p2_t1 == EvaluationType.exact best_move_p2 = get_best_move(game.player_2, game.player_1, is_first_move=False, depth=2) assert best_move_p2.x == 2 and best_move_p2.y == 0 assert best_move_p2.piece_type == PieceType.four assert best_move_p2.score == score_p2_t1 == -WIN_CONDITION_SCORE + DEPTH_PENALTY * 2
def test_black_widow(self, clean_tree_search_caches_before_tests): # The following situation is hard to see for a human, but completely winning for player 2 # He only has piece 5 in hand, and as long as he keeps it for the next move (makes it not enter the board) # He will be able to use it next round to win the game, no reply from player 1 possible. game = Game() game.starting_player = game.player_1 # Player 1 pieces player_1_one = game.player_1.get_piece_by_type(PieceType.one) player_1_one.set_movement_direction(Direction.east) game.board.get_tile(2, 3).place_piece(player_1_one) player_1_two = game.player_1.get_piece_by_type(PieceType.two) player_1_two.set_movement_direction(Direction.west) game.board.get_tile(3, 2).place_piece(player_1_two) player_1_four = game.player_1.get_piece_by_type(PieceType.four) player_1_four.set_movement_direction(Direction.east) game.board.get_tile(1, 3).place_piece(player_1_four) player_1_five = game.player_1.get_piece_by_type(PieceType.five) player_1_five.set_movement_direction(Direction.east) game.board.get_tile(1, 1).place_piece(player_1_five) player_1_three = game.player_1.get_piece_by_type(PieceType.three) player_1_three.set_movement_direction(Direction.east) game.board.get_tile(0, 3).place_piece(player_1_three) # Player 2 pieces player_2_one = game.player_2.get_piece_by_type(PieceType.one) player_2_one.set_movement_direction(Direction.north) game.board.get_tile(2, 1).place_piece(player_2_one) player_2_two = game.player_2.get_piece_by_type(PieceType.two) player_2_two.set_movement_direction(Direction.west) game.board.get_tile(3, 1).place_piece(player_2_two) player_2_three = game.player_2.get_piece_by_type(PieceType.three) player_2_three.set_movement_direction(Direction.west) game.board.get_tile(3, 3).place_piece(player_2_three) player_2_four = game.player_2.get_piece_by_type(PieceType.four) player_2_four.set_movement_direction(Direction.east) game.board.get_tile(2, 2).place_piece(player_2_four) best_moves_player_2_round_1, player_2_round_1_score, _ = get_best_moves(game.player_2, game.player_1, is_first_move=False, depth=2) for candidate_move in best_moves_player_2_round_1: clone_game = game.clone() candidate_move.execute(clone_game.player_2) clone_game.board.execute_board_movements(starting_player_id=clone_game.player_1.id) clone_game.switch_starting_player() # Play the second round clone_move_p2_r2 = get_best_move(clone_game.player_2, clone_game.player_1, is_first_move=True, depth=2) clone_move_p2_r2.execute(clone_game.player_2) clone_move_p1_r2 = get_best_move(clone_game.player_1, clone_game.player_2, is_first_move=False, depth=2) clone_move_p1_r2.execute(clone_game.player_1) clone_game.board.execute_board_movements(starting_player_id=clone_game.player_2.id) assert clone_game.board.get_game_result(clone_game.player_2.id, clone_game.player_1.id) == GameResult.win assert clone_move_p1_r2.score == -WIN_CONDITION_SCORE + DEPTH_PENALTY assert clone_move_p2_r2.score == WIN_CONDITION_SCORE - DEPTH_PENALTY # Play the first round best_move_player_2_round_1 = get_best_move(game.player_2, game.player_1, is_first_move=False, depth=2) best_move_player_2_round_1.execute(game.player_2) game.board.execute_board_movements(starting_player_id=game.player_1.id) game.switch_starting_player() best_moves_player_2_round_2, _, _ = get_best_moves(game.player_2, game.player_1, is_first_move=True, depth=2) # Play the second round best_move_player_2_round_2 = get_best_move(game.player_2, game.player_1, is_first_move=True, depth=2) best_move_player_2_round_2.execute(game.player_2) best_move_player_1_round_2 = get_best_move(game.player_1, game.player_2, is_first_move=False, depth=2) best_move_player_1_round_2.execute(game.player_1) game.board.execute_board_movements(starting_player_id=game.player_2.id) # On the first round, player 2 must play the 5 piece (forced, only piece in hand) on any tile where it cannot # enter the board, as he needs it to mate in the next round. tiles_where_5_would_enter = [(0, 2), (1, 4), (0, 3), (2, 0)] for candidate_move in best_moves_player_2_round_1: assert candidate_move.piece_type == PieceType.five assert (candidate_move.x, candidate_move.y) not in tiles_where_5_would_enter assert best_move_player_2_round_1.piece_type == PieceType.five assert (best_move_player_2_round_1.x, best_move_player_2_round_1.y) not in tiles_where_5_would_enter # On the second round, player 2 should play the 5 on (2, 4), as its the winning move assert len(best_moves_player_2_round_2) == 1 assert best_move_player_2_round_2.piece_type == PieceType.five assert best_move_player_2_round_2.x == 2 and best_move_player_2_round_2.y == 4 # Both players should know at any moment they have already won/lost assert best_move_player_2_round_1.score == player_2_round_1_score == WIN_CONDITION_SCORE - DEPTH_PENALTY * 2 assert best_move_player_1_round_2.score == -WIN_CONDITION_SCORE + DEPTH_PENALTY assert best_move_player_2_round_2.score == WIN_CONDITION_SCORE - DEPTH_PENALTY assert game.board.get_game_result(game.player_2.id, game.player_1.id) == GameResult.win