liars-dice/engine.py

136 lines
3.7 KiB
Python
Raw Permalink Normal View History

2022-08-03 15:39:53 +00:00
import random
from collections import Counter
from dataclasses import dataclass
@dataclass
class Bid:
die_face: int
num_dice: int
class EngineHandler:
def on_game_started(self, game_state: "LiarsDiceEngine"):
pass
def on_new_round(self):
pass
def on_bid_placed(self, bid):
pass
def on_challenge_bid(self):
pass
def on_player_eliminated(self, player_index):
pass
def get_players_turn(self) -> Bid | None:
pass
@dataclass
class LiarsDiceEngine:
NUM_INITIAL_DICE = 5
WILD_DIE_FACE = 1
listener: EngineHandler
num_players: int
round_number: int
wild_ones_variant: bool
active_player_index: int
current_bid: Bid | None
dice: list[Counter[int]]
@staticmethod
def new_game(
num_players: int, wild_ones_variant: bool, listener: EngineHandler
) -> "LiarsDiceEngine":
return LiarsDiceEngine(
num_players=num_players,
active_player_index=random.randint(0, num_players - 1),
wild_ones_variant=wild_ones_variant,
current_bid=None,
dice=[
Counter([random.randint(1, 6) for _ in range(LiarsDiceEngine.NUM_INITIAL_DICE)])
for _ in range(num_players)
],
round_number=0,
listener=listener,
)
def start_game(self) -> None:
self.listener.on_game_started(self)
while self.num_players > 1:
self.new_round()
while True:
bid = self.listener.get_players_turn()
if bid:
self.place_bid(bid)
else:
self.challenge_current_bid()
break
def new_round(self):
self.current_bid = None
self.active_player_index = self.active_player_index % self.num_players
self.round_number += 1
self.listener.on_new_round()
def place_bid(self, bid: Bid):
self.current_bid = bid
self.listener.on_bid_placed(bid)
self.active_player_index = (self.active_player_index + 1) % self.num_players
def challenge_current_bid(self):
self.listener.on_challenge_bid()
losing_player_index = (
self.active_player_index
if self.is_current_bid_successful()
else self.active_player_index - 1
)
num_dice_per_player = [c.total() for c in self.dice]
if num_dice_per_player[losing_player_index] > 1:
num_dice_per_player[losing_player_index] -= 1
else:
self.listener.on_player_eliminated(losing_player_index)
self.num_players -= 1
num_dice_per_player = [
d for (i, d) in enumerate(num_dice_per_player) if i != losing_player_index
]
self.active_player_index = losing_player_index
self.dice = [
Counter([random.randint(1, 6) for _ in range(num_dice_per_player[p])])
for p in range(self.num_players)
]
def is_current_bid_successful(self) -> bool:
num_wild_dice = (
self.num_all_dice_with_value(self.WILD_DIE_FACE) if self.wild_ones_variant else 0
)
return (
self.num_all_dice_with_value(self.current_bid.die_face) + num_wild_dice
>= self.current_bid.num_dice
)
@property
def num_opponent_dice(self) -> int:
return sum(c.total() for (i, c) in enumerate(self.dice) if i != self.active_player_index)
@property
def num_all_dice(self) -> int:
return sum(c.total() for c in self.dice)
def num_all_dice_with_value(self, die_face: int) -> int:
return sum(c[die_face] for c in self.dice)