Spaces:
Paused
Paused
| import json | |
| import random | |
| from datetime import datetime, timedelta | |
| from typing import Dict, List, Any, Optional | |
| from dataclasses import dataclass, asdict | |
| import numpy as np | |
| from PIL import Image | |
| import os | |
| from pathlib import Path | |
| class Monster: | |
| """Monster data class""" | |
| name: str | |
| species: str | |
| stage: str # rookie, champion, ultimate, mega | |
| stats: Dict[str, int] | |
| care_state: Dict[str, float] | |
| personality: Dict[str, Any] | |
| birth_time: datetime | |
| evolution_time: Optional[datetime] = None | |
| training_count: int = 0 | |
| battle_count: int = 0 | |
| happiness_events: List[str] = None | |
| image_path: Optional[str] = None | |
| model_3d_path: Optional[str] = None | |
| def __post_init__(self): | |
| if self.happiness_events is None: | |
| self.happiness_events = [] | |
| def to_dict(self) -> Dict: | |
| """Convert monster to dictionary for storage""" | |
| data = asdict(self) | |
| data['birth_time'] = self.birth_time.isoformat() | |
| if self.evolution_time: | |
| data['evolution_time'] = self.evolution_time.isoformat() | |
| return data | |
| def from_dict(cls, data: Dict) -> 'Monster': | |
| """Create monster from dictionary""" | |
| data['birth_time'] = datetime.fromisoformat(data['birth_time']) | |
| if data.get('evolution_time'): | |
| data['evolution_time'] = datetime.fromisoformat(data['evolution_time']) | |
| return cls(**data) | |
| def get_stats_display(self) -> Dict[str, Any]: | |
| """Get formatted stats for display""" | |
| return { | |
| "name": self.name, | |
| "species": self.species, | |
| "stage": self.stage, | |
| "level": self._calculate_level(), | |
| "stats": { | |
| "HP": f"{self.stats['hp']}/999", | |
| "ATK": f"{self.stats['attack']}/500", | |
| "DEF": f"{self.stats['defense']}/500", | |
| "SPD": f"{self.stats['speed']}/500", | |
| "SPC": f"{self.stats['special']}/500" | |
| }, | |
| "care": { | |
| "Hunger": f"{self.care_state['hunger']:.0f}%", | |
| "Happiness": f"{self.care_state['happiness']:.0f}%", | |
| "Fatigue": f"{self.care_state['fatigue']:.0f}%", | |
| "Health": f"{self.care_state['health']:.0f}%" | |
| }, | |
| "age": self._calculate_age() | |
| } | |
| def _calculate_level(self) -> int: | |
| """Calculate monster level based on stats and experience""" | |
| total_stats = sum(self.stats.values()) | |
| base_level = total_stats // 50 | |
| exp_bonus = (self.training_count + self.battle_count) // 10 | |
| return min(99, base_level + exp_bonus + 1) | |
| def _calculate_age(self) -> str: | |
| """Calculate monster age""" | |
| age = datetime.now() - self.birth_time | |
| if age.days > 0: | |
| return f"{age.days} days" | |
| elif age.seconds > 3600: | |
| return f"{age.seconds // 3600} hours" | |
| else: | |
| return f"{age.seconds // 60} minutes" | |
| class GameMechanics: | |
| """Core game mechanics inspired by Digimon World 1""" | |
| def __init__(self): | |
| # Stat ranges and limits | |
| self.stat_limits = { | |
| 'hp': (10, 999), | |
| 'attack': (5, 500), | |
| 'defense': (5, 500), | |
| 'speed': (5, 500), | |
| 'special': (5, 500) | |
| } | |
| # Care thresholds | |
| self.care_thresholds = { | |
| 'hunger': {'critical': 20, 'low': 40, 'good': 70}, | |
| 'happiness': {'critical': 20, 'low': 40, 'good': 70}, | |
| 'fatigue': {'good': 30, 'tired': 60, 'exhausted': 80}, | |
| 'health': {'critical': 30, 'low': 50, 'good': 80} | |
| } | |
| # Training effectiveness modifiers | |
| self.training_modifiers = { | |
| 'strength': {'attack': 1.5, 'defense': 0.8, 'speed': 0.7}, | |
| 'defense': {'attack': 0.7, 'defense': 1.5, 'hp': 1.2}, | |
| 'speed': {'speed': 1.5, 'attack': 0.9, 'special': 0.8}, | |
| 'intelligence': {'special': 1.5, 'defense': 0.9, 'hp': 0.8}, | |
| 'balanced': {'attack': 1.0, 'defense': 1.0, 'speed': 1.0, 'special': 1.0} | |
| } | |
| # Evolution requirements (simplified) | |
| self.evolution_requirements = { | |
| 'champion': { | |
| 'min_stats': 150, # Total stats | |
| 'min_care': 60, # Average care percentage | |
| 'min_age': 1, # Days | |
| 'training_count': 10 | |
| }, | |
| 'ultimate': { | |
| 'min_stats': 300, | |
| 'min_care': 70, | |
| 'min_age': 3, | |
| 'training_count': 30 | |
| }, | |
| 'mega': { | |
| 'min_stats': 500, | |
| 'min_care': 80, | |
| 'min_age': 7, | |
| 'training_count': 50 | |
| } | |
| } | |
| def create_monster(self, generation_result: Dict[str, Any], user_preferences: Dict = None, user_id: str = "unknown") -> Monster: | |
| """Create a new monster from AI generation results""" | |
| traits = generation_result.get('traits', {}) | |
| preferences = user_preferences or {} | |
| # Generate base stats based on traits and preferences | |
| base_stats = self._generate_base_stats(traits, preferences.get('training_focus', 'balanced')) | |
| # Initialize care state | |
| care_state = { | |
| 'hunger': 80.0, | |
| 'happiness': 90.0, | |
| 'fatigue': 10.0, | |
| 'health': 100.0 | |
| } | |
| # Determine personality from traits | |
| personality = self._determine_personality(traits) | |
| # Create monster name | |
| name = traits.get('name', self._generate_name(traits)) | |
| # Convert PIL Images to file paths | |
| image_path = self._convert_image_to_path(generation_result.get('image'), user_id, name) | |
| model_3d_path = self._convert_image_to_path(generation_result.get('model_3d'), user_id, name) | |
| # Create monster instance | |
| monster = Monster( | |
| name=name, | |
| species=traits.get('species', 'DigiPal'), | |
| stage='rookie', | |
| stats=base_stats, | |
| care_state=care_state, | |
| personality=personality, | |
| birth_time=datetime.now(), | |
| image_path=image_path, | |
| model_3d_path=model_3d_path | |
| ) | |
| return monster | |
| def _convert_image_to_path(self, image_data, user_id: str, monster_name: str) -> Optional[str]: | |
| """Convert PIL Image to file path for storage""" | |
| if image_data is None: | |
| return None | |
| # If it's already a string path, return it | |
| if isinstance(image_data, str): | |
| return image_data | |
| # If it's a PIL Image, save it to a file | |
| if hasattr(image_data, 'save'): # PIL Image check | |
| try: | |
| # Create user-specific monsters directory | |
| monsters_dir = Path("./data/monsters") / user_id | |
| monsters_dir.mkdir(parents=True, exist_ok=True) | |
| # Generate unique filename | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| image_path = monsters_dir / f"{monster_name}_{timestamp}.png" | |
| # Save the image | |
| image_data.save(image_path) | |
| return str(image_path) | |
| except Exception as e: | |
| print(f"Error saving image: {e}") | |
| return None | |
| return None | |
| def _generate_base_stats(self, traits: Dict, focus: str) -> Dict[str, int]: | |
| """Generate base stats based on traits and focus""" | |
| # Base values | |
| base = { | |
| 'hp': random.randint(50, 100), | |
| 'attack': random.randint(15, 35), | |
| 'defense': random.randint(15, 35), | |
| 'speed': random.randint(15, 35), | |
| 'special': random.randint(15, 35) | |
| } | |
| # Apply focus modifiers | |
| if focus in self.training_modifiers: | |
| for stat, modifier in self.training_modifiers[focus].items(): | |
| if stat in base: | |
| base[stat] = int(base[stat] * modifier) | |
| # Apply trait-based modifiers | |
| if traits.get('element') == 'fire': | |
| base['attack'] += 10 | |
| base['special'] += 5 | |
| elif traits.get('element') == 'water': | |
| base['defense'] += 10 | |
| base['hp'] += 20 | |
| elif traits.get('element') == 'earth': | |
| base['defense'] += 15 | |
| base['hp'] += 10 | |
| elif traits.get('element') == 'wind': | |
| base['speed'] += 15 | |
| base['special'] += 5 | |
| # Ensure stats are within limits | |
| for stat in base: | |
| base[stat] = max(self.stat_limits[stat][0], | |
| min(self.stat_limits[stat][1], base[stat])) | |
| return base | |
| def _determine_personality(self, traits: Dict) -> Dict[str, Any]: | |
| """Determine monster personality from traits""" | |
| personality_traits = [ | |
| 'brave', 'timid', 'aggressive', 'gentle', | |
| 'playful', 'serious', 'loyal', 'independent' | |
| ] | |
| # Select primary trait | |
| primary = traits.get('personality', random.choice(personality_traits)) | |
| # Generate personality profile | |
| return { | |
| 'primary': primary, | |
| 'likes': self._generate_likes(primary), | |
| 'dislikes': self._generate_dislikes(primary), | |
| 'training_preference': self._get_training_preference(primary), | |
| 'battle_style': self._get_battle_style(primary) | |
| } | |
| def _generate_name(self, traits: Dict) -> str: | |
| """Generate a name if not provided""" | |
| prefixes = ['Digi', 'Cyber', 'Tech', 'Neo', 'Alpha', 'Beta'] | |
| suffixes = ['mon', 'pal', 'byte', 'bit', 'tron', 'x'] | |
| prefix = random.choice(prefixes) | |
| suffix = random.choice(suffixes) | |
| return f"{prefix}{suffix}" | |
| def _generate_likes(self, personality: str) -> List[str]: | |
| """Generate things the monster likes based on personality""" | |
| likes_map = { | |
| 'brave': ['battles', 'challenges', 'meat'], | |
| 'timid': ['quiet places', 'vegetables', 'praise'], | |
| 'aggressive': ['training', 'meat', 'battles'], | |
| 'gentle': ['praise', 'vegetables', 'playing'], | |
| 'playful': ['games', 'treats', 'attention'], | |
| 'serious': ['training', 'discipline', 'fish'], | |
| 'loyal': ['praise', 'companionship', 'meat'], | |
| 'independent': ['exploration', 'variety', 'fish'] | |
| } | |
| return likes_map.get(personality, ['food', 'play', 'rest']) | |
| def _generate_dislikes(self, personality: str) -> List[str]: | |
| """Generate things the monster dislikes based on personality""" | |
| dislikes_map = { | |
| 'brave': ['running away', 'vegetables', 'rest'], | |
| 'timid': ['battles', 'loud noises', 'scolding'], | |
| 'aggressive': ['vegetables', 'rest', 'gentle training'], | |
| 'gentle': ['battles', 'scolding', 'meat'], | |
| 'playful': ['discipline', 'vegetables', 'being ignored'], | |
| 'serious': ['games', 'treats', 'slacking'], | |
| 'loyal': ['being alone', 'scolding', 'betrayal'], | |
| 'independent': ['clingy behavior', 'routine', 'vegetables'] | |
| } | |
| return dislikes_map.get(personality, ['scolding', 'hunger', 'fatigue']) | |
| def _get_training_preference(self, personality: str) -> str: | |
| """Get preferred training type based on personality""" | |
| preferences = { | |
| 'brave': 'strength', | |
| 'timid': 'defense', | |
| 'aggressive': 'strength', | |
| 'gentle': 'intelligence', | |
| 'playful': 'speed', | |
| 'serious': 'balanced', | |
| 'loyal': 'defense', | |
| 'independent': 'speed' | |
| } | |
| return preferences.get(personality, 'balanced') | |
| def _get_battle_style(self, personality: str) -> str: | |
| """Get battle style based on personality""" | |
| styles = { | |
| 'brave': 'offensive', | |
| 'timid': 'defensive', | |
| 'aggressive': 'berserker', | |
| 'gentle': 'support', | |
| 'playful': 'trickster', | |
| 'serious': 'tactical', | |
| 'loyal': 'guardian', | |
| 'independent': 'adaptive' | |
| } | |
| return styles.get(personality, 'balanced') | |
| def train_monster(self, monster: Monster, training_type: str, intensity: int) -> Dict[str, Any]: | |
| """Train the monster to improve stats""" | |
| # Check if monster can train | |
| if monster.care_state['fatigue'] > self.care_thresholds['fatigue']['exhausted']: | |
| return { | |
| 'success': False, | |
| 'message': f"{monster.name} is too tired to train! π΄π€", | |
| 'stat_changes': {} | |
| } | |
| if monster.care_state['hunger'] < self.care_thresholds['hunger']['low']: | |
| return { | |
| 'success': False, | |
| 'message': f"{monster.name} is too hungry to train! πβ", | |
| 'stat_changes': {} | |
| } | |
| # Calculate stat gains | |
| base_gain = intensity * 2 | |
| stat_gains = {} | |
| # Apply training type modifiers | |
| training_type = training_type.lower() | |
| if training_type in self.training_modifiers: | |
| for stat, modifier in self.training_modifiers[training_type].items(): | |
| if stat in monster.stats: | |
| gain = int(base_gain * modifier * random.uniform(0.8, 1.2)) | |
| # Personality bonus | |
| if training_type == monster.personality['training_preference']: | |
| gain = int(gain * 1.2) | |
| # Apply gain with stat limits | |
| old_value = monster.stats[stat] | |
| new_value = min(self.stat_limits[stat][1], old_value + gain) | |
| actual_gain = new_value - old_value | |
| if actual_gain > 0: | |
| monster.stats[stat] = new_value | |
| stat_gains[stat] = actual_gain | |
| # Update care state | |
| fatigue_gain = intensity * 5 + random.randint(0, 10) | |
| happiness_gain = 5 if training_type == monster.personality['training_preference'] else 2 | |
| monster.care_state['fatigue'] = min(100, monster.care_state['fatigue'] + fatigue_gain) | |
| monster.care_state['happiness'] = min(100, monster.care_state['happiness'] + happiness_gain) | |
| monster.care_state['hunger'] = max(0, monster.care_state['hunger'] - intensity * 2) | |
| # Update training count | |
| monster.training_count += 1 | |
| # Check for evolution | |
| evolution_check = self.check_evolution(monster) | |
| # Generate response message | |
| if stat_gains: | |
| gains_text = ", ".join([f"{stat.upper()} +{gain}" for stat, gain in stat_gains.items()]) | |
| message = f"πͺ Training complete! {gains_text}" | |
| else: | |
| message = f"π {monster.name} has reached stat limits in this area!" | |
| return { | |
| 'success': True, | |
| 'message': message, | |
| 'stat_changes': stat_gains, | |
| 'fatigue_gained': fatigue_gain, | |
| 'evolution_check': evolution_check | |
| } | |
| def check_evolution(self, monster: Monster) -> Optional[Dict[str, Any]]: | |
| """Check if monster meets evolution requirements""" | |
| current_stage = monster.stage | |
| next_stage = None | |
| if current_stage == 'rookie': | |
| next_stage = 'champion' | |
| elif current_stage == 'champion': | |
| next_stage = 'ultimate' | |
| elif current_stage == 'ultimate': | |
| next_stage = 'mega' | |
| else: | |
| return None | |
| requirements = self.evolution_requirements.get(next_stage) | |
| if not requirements: | |
| return None | |
| # Check requirements | |
| total_stats = sum(monster.stats.values()) | |
| avg_care = sum(monster.care_state.values()) / len(monster.care_state) | |
| age_days = (datetime.now() - monster.birth_time).days | |
| meets_requirements = ( | |
| total_stats >= requirements['min_stats'] and | |
| avg_care >= requirements['min_care'] and | |
| age_days >= requirements['min_age'] and | |
| monster.training_count >= requirements['training_count'] | |
| ) | |
| if meets_requirements: | |
| return { | |
| 'can_evolve': True, | |
| 'next_stage': next_stage, | |
| 'message': f"β¨ {monster.name} is ready to evolve to {next_stage}!" | |
| } | |
| else: | |
| return { | |
| 'can_evolve': False, | |
| 'next_stage': next_stage, | |
| 'progress': { | |
| 'stats': f"{total_stats}/{requirements['min_stats']}", | |
| 'care': f"{avg_care:.0f}%/{requirements['min_care']}%", | |
| 'age': f"{age_days}/{requirements['min_age']} days", | |
| 'training': f"{monster.training_count}/{requirements['training_count']}" | |
| } | |
| } | |
| def feed_monster(self, monster: Monster, food_type: str) -> Dict[str, Any]: | |
| """Feed the monster""" | |
| food_effects = { | |
| 'meat': {'hunger': 40, 'happiness': 10, 'health': 5}, | |
| 'fish': {'hunger': 35, 'happiness': 15, 'health': 10}, | |
| 'vegetable': {'hunger': 30, 'happiness': 5, 'health': 15}, | |
| 'treat': {'hunger': 20, 'happiness': 30, 'health': 0}, | |
| 'medicine': {'hunger': 0, 'happiness': -10, 'health': 50} | |
| } | |
| effects = food_effects.get(food_type.lower(), food_effects['meat']) | |
| # Apply personality preferences | |
| likes_food = food_type.lower() in [like.lower() for like in monster.personality.get('likes', [])] | |
| dislikes_food = food_type.lower() in [dislike.lower() for dislike in monster.personality.get('dislikes', [])] | |
| if likes_food: | |
| effects['happiness'] *= 2 | |
| elif dislikes_food: | |
| effects['happiness'] = -abs(effects['happiness']) | |
| # Update care state | |
| old_hunger = monster.care_state['hunger'] | |
| monster.care_state['hunger'] = min(100, monster.care_state['hunger'] + effects['hunger']) | |
| monster.care_state['happiness'] = max(0, min(100, monster.care_state['happiness'] + effects['happiness'])) | |
| monster.care_state['health'] = min(100, monster.care_state['health'] + effects['health']) | |
| # Generate response | |
| if likes_food: | |
| message = f"π {monster.name} loves {food_type}! π" | |
| elif dislikes_food: | |
| message = f"π {monster.name} doesn't like {food_type}... π" | |
| elif old_hunger < 30: | |
| message = f"π½οΈ {monster.name} was very hungry! Much better now! π" | |
| else: | |
| message = f"π΄ {monster.name} enjoyed the {food_type}! π" | |
| return { | |
| 'success': True, | |
| 'message': message, | |
| 'effects': effects, | |
| 'current_state': monster.care_state | |
| } | |
| def update_care_state(self, monster: Monster, time_passed: timedelta) -> Dict[str, Any]: | |
| """Update monster care state based on time passed""" | |
| # Calculate hours passed | |
| hours = time_passed.total_seconds() / 3600 | |
| # Decrease hunger and happiness over time | |
| monster.care_state['hunger'] = max(0, monster.care_state['hunger'] - hours * 5) | |
| monster.care_state['happiness'] = max(0, monster.care_state['happiness'] - hours * 2) | |
| # Decrease fatigue over time (rest) | |
| monster.care_state['fatigue'] = max(0, monster.care_state['fatigue'] - hours * 10) | |
| # Health changes based on other stats | |
| if monster.care_state['hunger'] < 20: | |
| monster.care_state['health'] = max(0, monster.care_state['health'] - hours * 3) | |
| elif monster.care_state['happiness'] < 20: | |
| monster.care_state['health'] = max(0, monster.care_state['health'] - hours * 1) | |
| # Check for critical states | |
| alerts = [] | |
| if monster.care_state['hunger'] < self.care_thresholds['hunger']['critical']: | |
| alerts.append("π Your monster is starving!") | |
| if monster.care_state['happiness'] < self.care_thresholds['happiness']['critical']: | |
| alerts.append("π’ Your monster is very unhappy!") | |
| if monster.care_state['health'] < self.care_thresholds['health']['critical']: | |
| alerts.append("π₯ Your monster needs medical attention!") | |
| return { | |
| 'updated_state': monster.care_state, | |
| 'alerts': alerts, | |
| 'time_since_update': str(time_passed) | |
| } |