47 lines
1.7 KiB
Python
47 lines
1.7 KiB
Python
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,
|
|
)
|