Spaces:
Sleeping
Sleeping
| """ | |
| Devil's Dozen - Pig Engine | |
| Simple single-die push-your-luck game. Roll a D6: 2-6 adds face value | |
| to turn score, rolling 1 = bust (lose all turn points). Bank anytime. | |
| First to target wins. | |
| All methods are stateless class methods operating on immutable data. | |
| """ | |
| import random | |
| from src.engine.base import DiceRoll, DiceType, ScoringResult, ScoringBreakdown, ScoringCategory | |
| class PigEngine: | |
| """ | |
| Stateless engine for the Pig game mode. | |
| All methods are class methods operating on immutable data. | |
| State is passed in and returned, never stored. | |
| """ | |
| NUM_DICE = 1 | |
| DICE_TYPE = DiceType.D6 | |
| def roll_dice(cls) -> DiceRoll: | |
| """Roll a single D6. | |
| Returns: | |
| DiceRoll with one random value (1-6) | |
| """ | |
| value = random.randint(1, 6) | |
| return DiceRoll(values=(value,), dice_type=cls.DICE_TYPE) | |
| def is_bust(cls, dice: DiceRoll | tuple[int, ...]) -> bool: | |
| """Check if a roll is a bust (rolled a 1). | |
| Args: | |
| dice: A DiceRoll or tuple of dice values | |
| Returns: | |
| True if the die shows 1 | |
| """ | |
| values = dice.values if isinstance(dice, DiceRoll) else dice | |
| return values[0] == 1 | |
| def calculate_score(cls, dice: DiceRoll | tuple[int, ...]) -> ScoringResult: | |
| """Calculate score for a single die roll. | |
| Rolling 1 = bust (0 points). Rolling 2-6 = face value. | |
| Args: | |
| dice: A DiceRoll or tuple of dice values | |
| Returns: | |
| ScoringResult with points and bust status | |
| """ | |
| values = dice.values if isinstance(dice, DiceRoll) else tuple(dice) | |
| value = values[0] | |
| if value == 1: | |
| return ScoringResult( | |
| points=0, | |
| breakdown=(), | |
| scoring_dice_indices=frozenset(), | |
| is_bust=True, | |
| ) | |
| return ScoringResult( | |
| points=value, | |
| breakdown=( | |
| ScoringBreakdown( | |
| category=ScoringCategory.SINGLE_FIVE, # Reuse; Pig has no unique category | |
| dice_values=(value,), | |
| points=value, | |
| description=f"Rolled {value}", | |
| ), | |
| ), | |
| scoring_dice_indices=frozenset({0}), | |
| is_bust=False, | |
| ) | |
| def process_roll( | |
| cls, | |
| turn_score: int, | |
| roll: DiceRoll | None = None, | |
| ) -> tuple[int, DiceRoll, bool]: | |
| """Process a complete roll: roll dice, check bust, update turn score. | |
| Args: | |
| turn_score: Current accumulated turn score | |
| roll: Optional pre-determined roll (for testing) | |
| Returns: | |
| Tuple of (new_turn_score, dice_roll, is_bust) | |
| """ | |
| if roll is None: | |
| roll = cls.roll_dice() | |
| if cls.is_bust(roll): | |
| return (0, roll, True) | |
| new_score = turn_score + roll.values[0] | |
| return (new_score, roll, False) | |