import random from scipy.stats import binom from engine import LiarsDiceEngine, Bid class BotAi: def ai_turn(self, game_state: LiarsDiceEngine) -> Bid | None: bid = game_state.current_bid or Bid(die_face=1, num_dice=0) # Challenge a bid if we deem it sufficiently unlikely, based on the statistical odds of the bid being true. Do # not hardcode a value to avoid the AI always reacting in a predetermined manner. if ( self.calculate_odds(game_state, bid.die_face, bid.num_dice) < random.uniform(0.1, 0.5) ): return None max_confidence = 0 max_confidence_bids = [] for d in range(bid.die_face, 7): for n in range(1, game_state.num_all_dice): if (d == bid.die_face and n > bid.num_dice) or d > bid.die_face: confidence = self.calculate_odds(game_state, d, n) if confidence == max_confidence: max_confidence_bids.append(Bid(d, n)) elif confidence > max_confidence: max_confidence = confidence max_confidence_bids = [Bid(d, n)] return random.choice(max_confidence_bids) if max_confidence_bids else None @staticmethod def calculate_odds(game_state: LiarsDiceEngine, die_face: int, num_values: int) -> float: num_minimum_needed_values = ( num_values - game_state.dice[game_state.active_player_index][die_face] - 1 ) die_face_probability = 2.0 / 6 if game_state.wild_ones_variant else 1.0 / 6 return binom.sf( k=num_minimum_needed_values, n=game_state.num_opponent_dice, p=die_face_probability, )