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) | |
} |