|
| 1 | +from typing import Optional |
| 2 | +from schnapsen.game import Bot, PlayerPerspective, Move, GameState, GamePlayEngine |
| 3 | +import random |
| 4 | + |
| 5 | +from .rand import RandBot |
| 6 | + |
| 7 | + |
| 8 | +class RdeepCheaterBot(Bot): |
| 9 | + """ |
| 10 | + Rdeep bot is a bot which performs many random rollouts of the game to decide which move to play. |
| 11 | + """ |
| 12 | + def __init__(self, num_samples: int, depth: int, rand: random.Random, name: Optional[str] = None) -> None: |
| 13 | + """ |
| 14 | + Create a new rdeep bot. |
| 15 | +
|
| 16 | + :param num_samples: how many samples to take per move |
| 17 | + :param depth: how deep to sample |
| 18 | + :param rand: the source of randomness for this Bot |
| 19 | + :param name: the name of this Bot |
| 20 | + """ |
| 21 | + super().__init__(name) |
| 22 | + assert num_samples >= 1, f"we cannot work with less than one sample, got {num_samples}" |
| 23 | + assert depth >= 1, f"it does not make sense to use a dept <1. got {depth}" |
| 24 | + self.__num_samples = num_samples |
| 25 | + self.__depth = depth |
| 26 | + self.__rand = rand |
| 27 | + |
| 28 | + def get_move(self, perspective: PlayerPerspective, leader_move: Optional[Move]) -> Move: |
| 29 | + # get the list of valid moves, and shuffle it such |
| 30 | + # that we get a random move of the highest scoring |
| 31 | + # ones if there are multiple highest scoring moves. |
| 32 | + moves = perspective.valid_moves() |
| 33 | + self.__rand.shuffle(moves) |
| 34 | + |
| 35 | + best_score = float('-inf') |
| 36 | + best_move = None |
| 37 | + for move in moves: |
| 38 | + sum_of_scores = 0.0 |
| 39 | + for _ in range(self.__num_samples): |
| 40 | + gamestate = perspective.make_cheating_assumption(leader_move=leader_move, rand=self.__rand) |
| 41 | + score = self.__evaluate(gamestate, perspective.get_engine(), leader_move, move) |
| 42 | + sum_of_scores += score |
| 43 | + average_score = sum_of_scores / self.__num_samples |
| 44 | + if average_score > best_score: |
| 45 | + best_score = average_score |
| 46 | + best_move = move |
| 47 | + assert best_move is not None |
| 48 | + return best_move |
| 49 | + |
| 50 | + def __evaluate(self, gamestate: GameState, engine: GamePlayEngine, leader_move: Optional[Move], my_move: Move) -> float: |
| 51 | + """ |
| 52 | + Evaluates the value of the given state for the given player |
| 53 | + :param state: The state to evaluate |
| 54 | + :param player: The player for whom to evaluate this state (1 or 2) |
| 55 | + :return: A float representing the value of this state for the given player. The higher the value, the better the |
| 56 | + state is for the player. |
| 57 | + """ |
| 58 | + me: Bot |
| 59 | + leader_bot: Bot |
| 60 | + follower_bot: Bot |
| 61 | + |
| 62 | + if leader_move: |
| 63 | + # we know what the other bot played |
| 64 | + leader_bot = FirstFixedMoveThenBaseBot(RandBot(rand=self.__rand), leader_move) |
| 65 | + # I am the follower |
| 66 | + me = follower_bot = FirstFixedMoveThenBaseBot(RandBot(rand=self.__rand), my_move) |
| 67 | + else: |
| 68 | + # I am the leader bot |
| 69 | + me = leader_bot = FirstFixedMoveThenBaseBot(RandBot(rand=self.__rand), my_move) |
| 70 | + # We assume the other bot just random |
| 71 | + follower_bot = RandBot(self.__rand) |
| 72 | + |
| 73 | + new_game_state, _ = engine.play_at_most_n_tricks(game_state=gamestate, new_leader=leader_bot, new_follower=follower_bot, n=self.__depth) |
| 74 | + |
| 75 | + if new_game_state.leader.implementation is me: |
| 76 | + my_score = new_game_state.leader.score.direct_points |
| 77 | + opponent_score = new_game_state.follower.score.direct_points |
| 78 | + else: |
| 79 | + my_score = new_game_state.follower.score.direct_points |
| 80 | + opponent_score = new_game_state.leader.score.direct_points |
| 81 | + |
| 82 | + heuristic = my_score / (my_score + opponent_score) |
| 83 | + return heuristic |
| 84 | + |
| 85 | + |
| 86 | +class FirstFixedMoveThenBaseBot(Bot): |
| 87 | + def __init__(self, base_bot: Bot, first_move: Move) -> None: |
| 88 | + self.first_move = first_move |
| 89 | + self.first_move_played = False |
| 90 | + self.base_bot = base_bot |
| 91 | + |
| 92 | + def get_move(self, perspective: PlayerPerspective, leader_move: Optional[Move]) -> Move: |
| 93 | + if not self.first_move_played: |
| 94 | + self.first_move_played = True |
| 95 | + return self.first_move |
| 96 | + return self.base_bot.get_move(perspective=perspective, leader_move=leader_move) |
0 commit comments