Spaces:
Runtime error
Runtime error
🥚 Initial DigiPal deployment to HuggingFace Spaces🤖 Generated with [Claude Code](https://claude.ai/code)Co-Authored-By: Claude <noreply@anthropic.com>
4399e64
| """ | |
| EvolutionController for DigiPal - Manages life stage progression and generational inheritance. | |
| """ | |
| from typing import Dict, List, Optional, Tuple, Any | |
| import random | |
| from datetime import datetime, timedelta | |
| from dataclasses import dataclass, field | |
| from .models import DigiPal, AttributeModifier | |
| from .enums import LifeStage, EggType, AttributeType | |
| class EvolutionRequirement: | |
| """Represents requirements for evolution to a specific life stage.""" | |
| min_attributes: Dict[AttributeType, int] = field(default_factory=dict) | |
| max_attributes: Dict[AttributeType, int] = field(default_factory=dict) | |
| max_care_mistakes: int = 999 | |
| min_age_hours: float = 0.0 | |
| max_age_hours: float = float('inf') | |
| required_actions: List[str] = field(default_factory=list) | |
| happiness_threshold: int = 0 | |
| discipline_range: Tuple[int, int] = (0, 100) | |
| weight_range: Tuple[int, int] = (1, 99) | |
| def to_dict(self) -> Dict[str, Any]: | |
| """Convert EvolutionRequirement to dictionary.""" | |
| return { | |
| 'min_attributes': {attr.value: val for attr, val in self.min_attributes.items()}, | |
| 'max_attributes': {attr.value: val for attr, val in self.max_attributes.items()}, | |
| 'max_care_mistakes': self.max_care_mistakes, | |
| 'min_age_hours': self.min_age_hours, | |
| 'max_age_hours': self.max_age_hours, | |
| 'required_actions': self.required_actions, | |
| 'happiness_threshold': self.happiness_threshold, | |
| 'discipline_range': self.discipline_range, | |
| 'weight_range': self.weight_range | |
| } | |
| def from_dict(cls, data: Dict[str, Any]) -> 'EvolutionRequirement': | |
| """Create EvolutionRequirement from dictionary.""" | |
| data['min_attributes'] = {AttributeType(attr): val for attr, val in data.get('min_attributes', {}).items()} | |
| data['max_attributes'] = {AttributeType(attr): val for attr, val in data.get('max_attributes', {}).items()} | |
| return cls(**data) | |
| class EvolutionResult: | |
| """Result of an evolution attempt.""" | |
| success: bool | |
| old_stage: LifeStage | |
| new_stage: LifeStage | |
| attribute_changes: Dict[str, int] = field(default_factory=dict) | |
| message: str = "" | |
| requirements_met: Dict[str, bool] = field(default_factory=dict) | |
| def to_dict(self) -> Dict[str, Any]: | |
| """Convert EvolutionResult to dictionary.""" | |
| return { | |
| 'success': self.success, | |
| 'old_stage': self.old_stage.value, | |
| 'new_stage': self.new_stage.value, | |
| 'attribute_changes': self.attribute_changes, | |
| 'message': self.message, | |
| 'requirements_met': self.requirements_met | |
| } | |
| class DNAInheritance: | |
| """Represents DNA inheritance data for generational passing.""" | |
| parent_attributes: Dict[AttributeType, int] = field(default_factory=dict) | |
| parent_care_quality: str = "fair" | |
| parent_final_stage: LifeStage = LifeStage.ADULT | |
| parent_egg_type: EggType = EggType.RED | |
| generation: int = 1 | |
| inheritance_bonuses: Dict[AttributeType, int] = field(default_factory=dict) | |
| def to_dict(self) -> Dict[str, Any]: | |
| """Convert DNAInheritance to dictionary.""" | |
| return { | |
| 'parent_attributes': {attr.value: val for attr, val in self.parent_attributes.items()}, | |
| 'parent_care_quality': self.parent_care_quality, | |
| 'parent_final_stage': self.parent_final_stage.value, | |
| 'parent_egg_type': self.parent_egg_type.value, | |
| 'generation': self.generation, | |
| 'inheritance_bonuses': {attr.value: val for attr, val in self.inheritance_bonuses.items()} | |
| } | |
| def from_dict(cls, data: Dict[str, Any]) -> 'DNAInheritance': | |
| """Create DNAInheritance from dictionary.""" | |
| data['parent_attributes'] = {AttributeType(attr): val for attr, val in data.get('parent_attributes', {}).items()} | |
| data['parent_final_stage'] = LifeStage(data['parent_final_stage']) | |
| data['parent_egg_type'] = EggType(data['parent_egg_type']) | |
| data['inheritance_bonuses'] = {AttributeType(attr): val for attr, val in data.get('inheritance_bonuses', {}).items()} | |
| return cls(**data) | |
| class EvolutionController: | |
| """ | |
| Manages DigiPal evolution through life stages and generational inheritance. | |
| Implements time-based evolution triggers and DNA-based attribute passing. | |
| """ | |
| # Evolution timing configuration (hours) | |
| EVOLUTION_TIMINGS = { | |
| LifeStage.EGG: 0.5, # 30 minutes to hatch | |
| LifeStage.BABY: 24.0, # 1 day as baby | |
| LifeStage.CHILD: 72.0, # 3 days as child | |
| LifeStage.TEEN: 120.0, # 5 days as teen | |
| LifeStage.YOUNG_ADULT: 168.0, # 7 days as young adult | |
| LifeStage.ADULT: 240.0, # 10 days as adult | |
| LifeStage.ELDERLY: 72.0 # 3 days as elderly before death | |
| } | |
| def __init__(self): | |
| """Initialize the EvolutionController.""" | |
| self.evolution_requirements = self._initialize_evolution_requirements() | |
| def _initialize_evolution_requirements(self) -> Dict[LifeStage, EvolutionRequirement]: | |
| """Initialize evolution requirements for each life stage transition.""" | |
| requirements = {} | |
| # EGG -> BABY: Triggered by first speech interaction | |
| requirements[LifeStage.BABY] = EvolutionRequirement( | |
| min_age_hours=0.0, | |
| max_care_mistakes=0, | |
| happiness_threshold=0 | |
| ) | |
| # BABY -> CHILD: Basic care requirements | |
| requirements[LifeStage.CHILD] = EvolutionRequirement( | |
| min_age_hours=20.0, | |
| max_care_mistakes=5, | |
| happiness_threshold=30, | |
| min_attributes={ | |
| AttributeType.HP: 80, | |
| AttributeType.ENERGY: 20 | |
| } | |
| ) | |
| # CHILD -> TEEN: Balanced development | |
| requirements[LifeStage.TEEN] = EvolutionRequirement( | |
| min_age_hours=60.0, | |
| max_care_mistakes=10, | |
| happiness_threshold=40, | |
| discipline_range=(20, 80), | |
| weight_range=(15, 50), | |
| min_attributes={ | |
| AttributeType.HP: 120, | |
| AttributeType.OFFENSE: 15, | |
| AttributeType.DEFENSE: 15 | |
| } | |
| ) | |
| # TEEN -> YOUNG_ADULT: Specialized development paths | |
| requirements[LifeStage.YOUNG_ADULT] = EvolutionRequirement( | |
| min_age_hours=100.0, | |
| max_care_mistakes=15, | |
| happiness_threshold=50, | |
| discipline_range=(30, 70), | |
| weight_range=(15, 40), | |
| min_attributes={ | |
| AttributeType.HP: 150, | |
| AttributeType.OFFENSE: 25, | |
| AttributeType.DEFENSE: 25, | |
| AttributeType.SPEED: 20, | |
| AttributeType.BRAINS: 20 | |
| } | |
| ) | |
| # YOUNG_ADULT -> ADULT: Peak performance requirements | |
| requirements[LifeStage.ADULT] = EvolutionRequirement( | |
| min_age_hours=150.0, | |
| max_care_mistakes=20, | |
| happiness_threshold=60, | |
| discipline_range=(40, 60), | |
| weight_range=(20, 35), | |
| min_attributes={ | |
| AttributeType.HP: 200, | |
| AttributeType.OFFENSE: 40, | |
| AttributeType.DEFENSE: 40, | |
| AttributeType.SPEED: 35, | |
| AttributeType.BRAINS: 35 | |
| } | |
| ) | |
| # ADULT -> ELDERLY: Automatic after time limit | |
| requirements[LifeStage.ELDERLY] = EvolutionRequirement( | |
| min_age_hours=200.0, | |
| max_care_mistakes=999, # No care mistake limit for elderly | |
| happiness_threshold=0 | |
| ) | |
| return requirements | |
| def check_evolution_eligibility(self, pet: DigiPal) -> Tuple[bool, LifeStage, Dict[str, bool]]: | |
| """ | |
| Check if a DigiPal is eligible for evolution to the next stage. | |
| Args: | |
| pet: The DigiPal to check | |
| Returns: | |
| Tuple of (eligible, next_stage, requirements_status) | |
| """ | |
| current_stage = pet.life_stage | |
| next_stage = self._get_next_life_stage(current_stage) | |
| if next_stage is None: | |
| return False, current_stage, {} | |
| requirements = self.evolution_requirements.get(next_stage) | |
| if not requirements: | |
| return False, current_stage, {} | |
| requirements_status = self._evaluate_evolution_requirements(pet, requirements) | |
| eligible = all(requirements_status.values()) | |
| return eligible, next_stage, requirements_status | |
| def _get_next_life_stage(self, current_stage: LifeStage) -> Optional[LifeStage]: | |
| """Get the next life stage in the progression.""" | |
| stage_progression = [ | |
| LifeStage.EGG, | |
| LifeStage.BABY, | |
| LifeStage.CHILD, | |
| LifeStage.TEEN, | |
| LifeStage.YOUNG_ADULT, | |
| LifeStage.ADULT, | |
| LifeStage.ELDERLY | |
| ] | |
| try: | |
| current_index = stage_progression.index(current_stage) | |
| if current_index < len(stage_progression) - 1: | |
| return stage_progression[current_index + 1] | |
| except ValueError: | |
| pass | |
| return None | |
| def _evaluate_evolution_requirements(self, pet: DigiPal, requirements: EvolutionRequirement) -> Dict[str, bool]: | |
| """Evaluate all evolution requirements for a pet.""" | |
| status = {} | |
| # Check age requirements | |
| age_hours = pet.get_age_hours() | |
| status['min_age'] = age_hours >= requirements.min_age_hours | |
| status['max_age'] = age_hours <= requirements.max_age_hours | |
| # Check care mistakes | |
| status['care_mistakes'] = pet.care_mistakes <= requirements.max_care_mistakes | |
| # Check happiness threshold | |
| status['happiness'] = pet.happiness >= requirements.happiness_threshold | |
| # Check discipline range | |
| discipline_min, discipline_max = requirements.discipline_range | |
| status['discipline'] = discipline_min <= pet.discipline <= discipline_max | |
| # Check weight range | |
| weight_min, weight_max = requirements.weight_range | |
| status['weight'] = weight_min <= pet.weight <= weight_max | |
| # Check minimum attributes | |
| for attr, min_val in requirements.min_attributes.items(): | |
| current_val = pet.get_attribute(attr) | |
| status[f'min_{attr.value}'] = current_val >= min_val | |
| # Check maximum attributes | |
| for attr, max_val in requirements.max_attributes.items(): | |
| current_val = pet.get_attribute(attr) | |
| status[f'max_{attr.value}'] = current_val <= max_val | |
| # Check required actions (placeholder for future implementation) | |
| if requirements.required_actions: | |
| status['required_actions'] = True # Simplified for now | |
| return status | |
| def trigger_evolution(self, pet: DigiPal, force: bool = False) -> EvolutionResult: | |
| """ | |
| Trigger evolution for a DigiPal if eligible. | |
| Args: | |
| pet: The DigiPal to evolve | |
| force: Force evolution regardless of requirements (for testing) | |
| Returns: | |
| EvolutionResult with evolution outcome | |
| """ | |
| old_stage = pet.life_stage | |
| if not force: | |
| eligible, next_stage, requirements_status = self.check_evolution_eligibility(pet) | |
| if not eligible: | |
| return EvolutionResult( | |
| success=False, | |
| old_stage=old_stage, | |
| new_stage=old_stage, | |
| message="Evolution requirements not met", | |
| requirements_met=requirements_status | |
| ) | |
| else: | |
| next_stage = self._get_next_life_stage(old_stage) | |
| if next_stage is None: | |
| return EvolutionResult( | |
| success=False, | |
| old_stage=old_stage, | |
| new_stage=old_stage, | |
| message="No next evolution stage available" | |
| ) | |
| requirements_status = {} | |
| # Perform evolution | |
| attribute_changes = self._apply_evolution_changes(pet, old_stage, next_stage) | |
| pet.life_stage = next_stage | |
| pet.evolution_timer = 0.0 # Reset evolution timer | |
| # Update learned commands based on new stage | |
| self._update_learned_commands(pet, next_stage) | |
| # Generate evolution message | |
| message = f"Evolution successful! {old_stage.value.title()} -> {next_stage.value.title()}" | |
| return EvolutionResult( | |
| success=True, | |
| old_stage=old_stage, | |
| new_stage=next_stage, | |
| attribute_changes=attribute_changes, | |
| message=message, | |
| requirements_met=requirements_status | |
| ) | |
| def _apply_evolution_changes(self, pet: DigiPal, old_stage: LifeStage, new_stage: LifeStage) -> Dict[str, int]: | |
| """Apply attribute changes during evolution.""" | |
| changes = {} | |
| # Base evolution bonuses by stage | |
| evolution_bonuses = { | |
| LifeStage.BABY: { | |
| AttributeType.HP: 20, | |
| AttributeType.MP: 10, | |
| AttributeType.HAPPINESS: 10 | |
| }, | |
| LifeStage.CHILD: { | |
| AttributeType.HP: 30, | |
| AttributeType.MP: 15, | |
| AttributeType.OFFENSE: 5, | |
| AttributeType.DEFENSE: 5, | |
| AttributeType.SPEED: 5, | |
| AttributeType.BRAINS: 5 | |
| }, | |
| LifeStage.TEEN: { | |
| AttributeType.HP: 40, | |
| AttributeType.MP: 20, | |
| AttributeType.OFFENSE: 10, | |
| AttributeType.DEFENSE: 10, | |
| AttributeType.SPEED: 10, | |
| AttributeType.BRAINS: 10 | |
| }, | |
| LifeStage.YOUNG_ADULT: { | |
| AttributeType.HP: 50, | |
| AttributeType.MP: 25, | |
| AttributeType.OFFENSE: 15, | |
| AttributeType.DEFENSE: 15, | |
| AttributeType.SPEED: 15, | |
| AttributeType.BRAINS: 15 | |
| }, | |
| LifeStage.ADULT: { | |
| AttributeType.HP: 60, | |
| AttributeType.MP: 30, | |
| AttributeType.OFFENSE: 20, | |
| AttributeType.DEFENSE: 20, | |
| AttributeType.SPEED: 20, | |
| AttributeType.BRAINS: 20 | |
| }, | |
| LifeStage.ELDERLY: { | |
| AttributeType.HP: -20, # Elderly lose some physical attributes | |
| AttributeType.OFFENSE: -10, | |
| AttributeType.DEFENSE: -5, | |
| AttributeType.SPEED: -15, | |
| AttributeType.BRAINS: 10, # But gain wisdom | |
| AttributeType.MP: 20 | |
| } | |
| } | |
| bonuses = evolution_bonuses.get(new_stage, {}) | |
| for attr, bonus in bonuses.items(): | |
| old_value = pet.get_attribute(attr) | |
| pet.modify_attribute(attr, bonus) | |
| new_value = pet.get_attribute(attr) | |
| changes[attr.value] = new_value - old_value | |
| # Egg type specific bonuses | |
| if new_stage in [LifeStage.CHILD, LifeStage.TEEN, LifeStage.YOUNG_ADULT]: | |
| egg_bonuses = self._get_egg_type_evolution_bonus(pet.egg_type, new_stage) | |
| for attr, bonus in egg_bonuses.items(): | |
| old_value = pet.get_attribute(attr) | |
| pet.modify_attribute(attr, bonus) | |
| new_value = pet.get_attribute(attr) | |
| if attr.value in changes: | |
| changes[attr.value] += new_value - old_value | |
| else: | |
| changes[attr.value] = new_value - old_value | |
| return changes | |
| def _get_egg_type_evolution_bonus(self, egg_type: EggType, stage: LifeStage) -> Dict[AttributeType, int]: | |
| """Get egg type specific evolution bonuses.""" | |
| bonuses = {} | |
| if egg_type == EggType.RED: # Fire-oriented | |
| bonuses = { | |
| AttributeType.OFFENSE: 5, | |
| AttributeType.SPEED: 3, | |
| AttributeType.HP: 2 | |
| } | |
| elif egg_type == EggType.BLUE: # Water-oriented | |
| bonuses = { | |
| AttributeType.DEFENSE: 5, | |
| AttributeType.MP: 5, | |
| AttributeType.BRAINS: 3 | |
| } | |
| elif egg_type == EggType.GREEN: # Earth-oriented | |
| bonuses = { | |
| AttributeType.HP: 8, | |
| AttributeType.DEFENSE: 3, | |
| AttributeType.BRAINS: 2 | |
| } | |
| # Scale bonuses by stage | |
| stage_multipliers = { | |
| LifeStage.CHILD: 1.0, | |
| LifeStage.TEEN: 1.5, | |
| LifeStage.YOUNG_ADULT: 2.0 | |
| } | |
| multiplier = stage_multipliers.get(stage, 1.0) | |
| return {attr: int(bonus * multiplier) for attr, bonus in bonuses.items()} | |
| def _update_learned_commands(self, pet: DigiPal, new_stage: LifeStage): | |
| """Update learned commands based on new life stage.""" | |
| stage_commands = { | |
| LifeStage.EGG: set(), | |
| LifeStage.BABY: {"eat", "sleep", "good", "bad"}, | |
| LifeStage.CHILD: {"eat", "sleep", "good", "bad", "play", "train"}, | |
| LifeStage.TEEN: {"eat", "sleep", "good", "bad", "play", "train", "status", "talk"}, | |
| LifeStage.YOUNG_ADULT: {"eat", "sleep", "good", "bad", "play", "train", "status", "talk", "battle"}, | |
| LifeStage.ADULT: {"eat", "sleep", "good", "bad", "play", "train", "status", "talk", "battle", "teach"}, | |
| LifeStage.ELDERLY: {"eat", "sleep", "good", "bad", "play", "train", "status", "talk", "battle", "teach", "wisdom"} | |
| } | |
| new_commands = stage_commands.get(new_stage, set()) | |
| pet.learned_commands.update(new_commands) | |
| def check_time_based_evolution(self, pet: DigiPal) -> bool: | |
| """ | |
| Check if enough time has passed for automatic evolution. | |
| Args: | |
| pet: The DigiPal to check | |
| Returns: | |
| True if time-based evolution should be triggered | |
| """ | |
| current_stage = pet.life_stage | |
| required_time = self.EVOLUTION_TIMINGS.get(current_stage, float('inf')) | |
| age_hours = pet.get_age_hours() | |
| return age_hours >= required_time | |
| def update_evolution_timer(self, pet: DigiPal, hours_passed: float): | |
| """ | |
| Update the evolution timer for a DigiPal. | |
| Args: | |
| pet: The DigiPal to update | |
| hours_passed: Number of hours that have passed | |
| """ | |
| pet.evolution_timer += hours_passed | |
| def create_inheritance_dna(self, parent: DigiPal, care_quality: str) -> DNAInheritance: | |
| """ | |
| Create DNA inheritance data from a parent DigiPal. | |
| Args: | |
| parent: The parent DigiPal | |
| care_quality: Quality of care the parent received | |
| Returns: | |
| DNAInheritance object with inheritance data | |
| """ | |
| # Capture parent's final attributes | |
| parent_attributes = { | |
| AttributeType.HP: parent.hp, | |
| AttributeType.MP: parent.mp, | |
| AttributeType.OFFENSE: parent.offense, | |
| AttributeType.DEFENSE: parent.defense, | |
| AttributeType.SPEED: parent.speed, | |
| AttributeType.BRAINS: parent.brains | |
| } | |
| # Calculate inheritance bonuses based on parent's attributes and care quality | |
| inheritance_bonuses = self._calculate_inheritance_bonuses(parent, care_quality) | |
| return DNAInheritance( | |
| parent_attributes=parent_attributes, | |
| parent_care_quality=care_quality, | |
| parent_final_stage=parent.life_stage, | |
| parent_egg_type=parent.egg_type, | |
| generation=parent.generation + 1, | |
| inheritance_bonuses=inheritance_bonuses | |
| ) | |
| def _calculate_inheritance_bonuses(self, parent: DigiPal, care_quality: str) -> Dict[AttributeType, int]: | |
| """Calculate attribute bonuses for inheritance based on parent stats and care quality.""" | |
| bonuses = {} | |
| # Base inheritance percentages by care quality | |
| inheritance_rates = { | |
| "perfect": 0.25, | |
| "excellent": 0.20, | |
| "good": 0.15, | |
| "fair": 0.10, | |
| "poor": 0.05 | |
| } | |
| rate = inheritance_rates.get(care_quality, 0.10) | |
| # Calculate bonuses based on parent's final attributes | |
| primary_attributes = [ | |
| AttributeType.HP, AttributeType.MP, AttributeType.OFFENSE, | |
| AttributeType.DEFENSE, AttributeType.SPEED, AttributeType.BRAINS | |
| ] | |
| for attr in primary_attributes: | |
| parent_value = parent.get_attribute(attr) | |
| # Higher parent attributes provide better inheritance bonuses | |
| if parent_value > 100: # Above average | |
| bonus = int((parent_value - 100) * rate) | |
| bonuses[attr] = max(1, bonus) # Minimum bonus of 1 | |
| # Special bonuses for exceptional care | |
| if care_quality == "perfect": | |
| # Perfect care provides additional random bonuses | |
| bonus_attr = random.choice(primary_attributes) | |
| bonuses[bonus_attr] = bonuses.get(bonus_attr, 0) + random.randint(5, 15) | |
| return bonuses | |
| def apply_inheritance(self, offspring: DigiPal, dna: DNAInheritance): | |
| """ | |
| Apply DNA inheritance to a new DigiPal. | |
| Args: | |
| offspring: The new DigiPal to apply inheritance to | |
| dna: The DNA inheritance data | |
| """ | |
| offspring.generation = dna.generation | |
| # Apply inheritance bonuses | |
| for attr, bonus in dna.inheritance_bonuses.items(): | |
| offspring.modify_attribute(attr, bonus) | |
| # Add some randomization to prevent identical offspring | |
| primary_attributes = [ | |
| AttributeType.HP, AttributeType.MP, AttributeType.OFFENSE, | |
| AttributeType.DEFENSE, AttributeType.SPEED, AttributeType.BRAINS | |
| ] | |
| for attr in primary_attributes: | |
| # Small random variation (-2 to +2) | |
| variation = random.randint(-2, 2) | |
| offspring.modify_attribute(attr, variation) | |
| # Inherit some personality traits (placeholder for future implementation) | |
| offspring.personality_traits["inherited"] = True | |
| offspring.personality_traits["parent_care_quality"] = dna.parent_care_quality | |
| def is_death_time(self, pet: DigiPal) -> bool: | |
| """ | |
| Check if a DigiPal should die (elderly stage time limit reached). | |
| Args: | |
| pet: The DigiPal to check | |
| Returns: | |
| True if the DigiPal should die | |
| """ | |
| if pet.life_stage != LifeStage.ELDERLY: | |
| return False | |
| # Calculate total age and time limits for all previous stages | |
| age_hours = pet.get_age_hours() | |
| elderly_time_limit = self.EVOLUTION_TIMINGS.get(LifeStage.ELDERLY, 72.0) | |
| # Calculate minimum time to reach elderly stage | |
| min_time_to_elderly = sum( | |
| self.EVOLUTION_TIMINGS.get(stage, 0) | |
| for stage in [LifeStage.EGG, LifeStage.BABY, LifeStage.CHILD, | |
| LifeStage.TEEN, LifeStage.YOUNG_ADULT, LifeStage.ADULT] | |
| ) | |
| # If pet is old enough to have been elderly for the full elderly duration | |
| total_max_lifetime = min_time_to_elderly + elderly_time_limit | |
| return age_hours >= total_max_lifetime | |
| def get_evolution_requirements(self, stage: LifeStage) -> Optional[EvolutionRequirement]: | |
| """Get evolution requirements for a specific stage.""" | |
| return self.evolution_requirements.get(stage) | |
| def get_all_evolution_requirements(self) -> Dict[LifeStage, EvolutionRequirement]: | |
| """Get all evolution requirements.""" | |
| return self.evolution_requirements.copy() | |
| def get_evolution_timing(self, stage: LifeStage) -> float: | |
| """Get the evolution timing for a specific stage.""" | |
| return self.EVOLUTION_TIMINGS.get(stage, float('inf')) | |
| def get_all_evolution_timings(self) -> Dict[LifeStage, float]: | |
| """Get all evolution timings.""" | |
| return self.EVOLUTION_TIMINGS.copy() |