Spaces:
Runtime error
Runtime error
| # app.py - CRISIS Y CORAJE CON IA NARRATIVA | |
| import gradio as gr | |
| import time | |
| import math | |
| import random | |
| import os | |
| from PIL import Image, ImageDraw | |
| from groq import Groq | |
| import json | |
| class AIDialogueSystem: | |
| """Sistema de diálogos con IA usando Groq""" | |
| def __init__(self): | |
| try: | |
| self.client = Groq(api_key=os.getenv("GROQ_API_KEY")) | |
| self.context_memory = [] | |
| self.character_personalities = self._load_character_personalities() | |
| self.ai_enabled = True | |
| except Exception as e: | |
| print(f"Groq no disponible: {e}") | |
| self.ai_enabled = False | |
| def _load_character_personalities(self): | |
| return { | |
| 'narrator': { | |
| 'role': 'Narrador porteño con experiencia en crisis argentinas', | |
| 'style': 'Realista, empático, usa lunfardo y jerga argentina', | |
| 'knowledge': 'Historia argentina reciente, crisis económicas, cultura popular' | |
| }, | |
| 'dona_rosa': { | |
| 'role': 'Vecina mayor, sabia del barrio', | |
| 'style': 'Maternal, directa, llena de consejos prácticos', | |
| 'knowledge': 'Supervivencia en crisis, redes vecinales, cocina criolla' | |
| }, | |
| 'el_checo': { | |
| 'role': 'Parrillero del barrio, organizador social', | |
| 'style': 'Carismático, solidario, siempre con una anécdota', | |
| 'knowledge': 'Asados, organización comunitaria, fútbol' | |
| }, | |
| 'comerciante': { | |
| 'role': 'Dueño del almacén, conoce la economía barrial', | |
| 'style': 'Pragmático, conoce precios y mercado, observador', | |
| 'knowledge': 'Economía local, precios, crédito informal' | |
| } | |
| } | |
| def generate_contextual_dialogue(self, character, situation, player_action, game_state): | |
| """Genera diálogo contextual usando IA""" | |
| if not self.ai_enabled: | |
| return self._fallback_dialogue(character, situation) | |
| try: | |
| personality = self.character_personalities.get(character, self.character_personalities['narrator']) | |
| prompt = f""" | |
| Sos {personality['role']} en un juego sobre supervivencia en Argentina durante crisis económicas. | |
| Estilo: {personality['style']} | |
| Conocimiento: {personality['knowledge']} | |
| CONTEXTO DEL JUEGO: | |
| - Día: {game_state.get('day', 1)} | |
| - Barrio: {game_state.get('current_neighborhood', 'villa_31')} | |
| - Recursos del jugador: {json.dumps(game_state.get('resources', {}), indent=2)} | |
| - Inflación actual: {game_state.get('inflation_rate', 1.0):.2f}x | |
| - Último evento: {game_state.get('last_event', 'Inicio del juego')} | |
| SITUACIÓN ACTUAL: {situation} | |
| ACCIÓN DEL JUGADOR: {player_action} | |
| Recordá que estás en Argentina, usá expresiones auténticas. Sé empático pero realista sobre las crisis. | |
| Mencioná detalles específicos argentinos cuando sea relevante. | |
| Máximo 2-3 oraciones, directo y conversacional. | |
| Respuesta como {personality['role']}: | |
| """ | |
| response = self.client.chat.completions.create( | |
| messages=[ | |
| {"role": "system", "content": "Sos un personaje argentino en un juego de supervivencia ambientado en Argentina. Usá lenguaje auténtico porteno y mantené el tono realista pero esperanzador."}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| model="llama-3.1-8b-instant", | |
| temperature=0.8, | |
| max_tokens=150 | |
| ) | |
| dialogue = response.choices[0].message.content.strip() | |
| # Guardar en memoria para contexto futuro | |
| self.context_memory.append({ | |
| 'character': character, | |
| 'situation': situation, | |
| 'dialogue': dialogue, | |
| 'game_day': game_state.get('day', 1) | |
| }) | |
| # Mantener solo los últimos 10 diálogos | |
| if len(self.context_memory) > 10: | |
| self.context_memory.pop(0) | |
| return dialogue | |
| except Exception as e: | |
| print(f"Error en IA: {e}") | |
| return self._fallback_dialogue(character, situation) | |
| def generate_neighborhood_news(self, game_state): | |
| """Genera noticias dinámicas del barrio usando IA""" | |
| if not self.ai_enabled: | |
| return "El barrio sigue adelante con solidaridad y creatividad." | |
| try: | |
| prompt = f""" | |
| Sos un periodista local que cubre noticias del barrio en Argentina. | |
| Generá una noticia breve (1-2 líneas) basada en: | |
| Estado actual: | |
| - Día {game_state.get('day', 1)} | |
| - Barrio: {game_state.get('current_neighborhood', 'villa_31')} | |
| - Nivel de crisis: {game_state.get('crisis_level', 1)}/5 | |
| - Solidaridad comunitaria: {game_state.get('resources', {}).get('solidaridad', 50)}% | |
| Hacé que sea realista pero no deprimente. Enfocate en la resistencia y creatividad argentina. | |
| Incluí detalles específicos del barrio si es relevante. | |
| Noticia breve: | |
| """ | |
| response = self.client.chat.completions.create( | |
| messages=[ | |
| {"role": "system", "content": "Sos un periodista barrial argentino que ve el lado humano de las crisis."}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| model="llama-3.1-8b-instant", | |
| temperature=0.7, | |
| max_tokens=100 | |
| ) | |
| return response.choices[0].message.content.strip() | |
| except Exception as e: | |
| return "Los vecinos siguen organizándose para enfrentar juntos los desafíos." | |
| def generate_crisis_commentary(self, crisis_level, resources): | |
| """Genera comentario sobre la situación económica""" | |
| if not self.ai_enabled: | |
| return "La situación económica sigue desafiante pero manejable." | |
| try: | |
| prompt = f""" | |
| Sos un analista económico argentino con sensibilidad social. | |
| Comentá brevemente la situación basada en: | |
| - Nivel de crisis: {crisis_level}/5 | |
| - Recursos familiares: pesos {resources.get('pesos', 0)}, dólares {resources.get('dolares', 0)} | |
| - Nivel de esperanza: {resources.get('esperanza', 50)}% | |
| Sé realista pero no catastrófico. Mencioná estrategias argentinas típicas. | |
| Máximo 2 líneas. | |
| Análisis: | |
| """ | |
| response = self.client.chat.completions.create( | |
| messages=[ | |
| {"role": "system", "content": "Sos un economista argentino que entiende las estrategias populares de supervivencia."}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| model="llama-3.1-8b-instant", | |
| temperature=0.6, | |
| max_tokens=80 | |
| ) | |
| return response.choices[0].message.content.strip() | |
| except Exception as e: | |
| return "El rebusque y la solidaridad siguen siendo clave en estos momentos." | |
| def _fallback_dialogue(self, character, situation): | |
| """Diálogos de respaldo si la IA no está disponible""" | |
| fallbacks = { | |
| 'narrator': [ | |
| "La vida en Argentina nunca es fácil, pero siempre hay una manera de salir adelante.", | |
| "En el barrio todos se conocen, y eso marca la diferencia en tiempos difíciles.", | |
| "El rebusque argentino nunca falla, che." | |
| ], | |
| 'dona_rosa': [ | |
| "Mirá, pibe, en mis años he visto de todo. Esto también se supera.", | |
| "¿Viste? Siempre es mejor cuando nos ayudamos entre vecinos.", | |
| "Con un buen guiso y buena onda, todo se arregla." | |
| ], | |
| 'el_checo': [ | |
| "¿Vamos haciendo un asadito para levantar el ánimo?", | |
| "En la cancha y en la vida, lo que importa es el equipo.", | |
| "Acá en el barrio nunca falta una mano amiga." | |
| ] | |
| } | |
| character_lines = fallbacks.get(character, fallbacks['narrator']) | |
| return random.choice(character_lines) | |
| class EnhancedArgentinaGame: | |
| """Versión mejorada del juego con IA narrativa""" | |
| def __init__(self): | |
| # Sistema base | |
| self.engine = ArgentinaGameEngine() | |
| self.renderer = GameRenderer() | |
| self.ai_dialogue = AIDialogueSystem() | |
| # Estado expandido del juego | |
| self.game_state = { | |
| 'resources': self.engine.resources.copy(), | |
| 'day': 1, | |
| 'current_neighborhood': 'villa_31', | |
| 'crisis_level': 1, | |
| 'inflation_rate': 1.0, | |
| 'last_event': 'Inicio del juego', | |
| 'current_character': None, | |
| 'story_progress': 0 | |
| } | |
| # Sistema de eventos narrativos | |
| self.story_events = [] | |
| self.character_interactions = 0 | |
| self.neighborhood_reputation = 50 | |
| def process_enhanced_action(self, action): | |
| """Procesa acciones con narrativa IA mejorada""" | |
| # Acción base | |
| old_resources = self.game_state['resources'].copy() | |
| message = "" | |
| character_dialogue = "" | |
| if action == "trabajar": | |
| income = self._calculate_work_income() | |
| self.game_state['resources']['pesos'] += income | |
| self.game_state['resources']['trabajo'] = min(100, self.game_state['resources']['trabajo'] + 10) | |
| # Generar diálogo contextual | |
| situation = f"El jugador fue a trabajar y ganó ${income} pesos en un día de crisis nivel {self.game_state['crisis_level']}" | |
| character_dialogue = self.ai_dialogue.generate_contextual_dialogue( | |
| 'narrator', situation, 'trabajar', self.game_state | |
| ) | |
| message = f"Trabajaste y ganaste ${income} pesos." | |
| elif action == "ayudar_vecinos": | |
| solidarity_gain = random.randint(15, 25) | |
| self.game_state['resources']['solidaridad'] += solidarity_gain | |
| self.game_state['resources']['esperanza'] += 10 | |
| self.game_state['resources']['pesos'] -= 200 | |
| self.character_interactions += 1 | |
| # Elegir personaje aleatorio para interactuar | |
| characters = ['dona_rosa', 'el_checo', 'comerciante'] | |
| chosen_character = random.choice(characters) | |
| self.game_state['current_character'] = chosen_character | |
| situation = f"El jugador ayudó a los vecinos, gastando $200 pero ganando {solidarity_gain} puntos de solidaridad" | |
| character_dialogue = self.ai_dialogue.generate_contextual_dialogue( | |
| chosen_character, situation, 'ayudar_vecinos', self.game_state | |
| ) | |
| message = f"Ayudaste a tus vecinos. +{solidarity_gain} solidaridad" | |
| elif action == "buscar_rebusque": | |
| rebusque_success = random.random() < 0.7 | |
| if rebusque_success: | |
| income = random.randint(800, 2000) | |
| self.game_state['resources']['pesos'] += income | |
| situation = f"El rebusque del jugador fue exitoso, ganando ${income} pesos" | |
| message = f"¡El rebusque funcionó! Ganaste ${income}" | |
| else: | |
| self.game_state['resources']['esperanza'] -= 8 | |
| situation = f"El rebusque del jugador no funcionó, perdiendo esperanza" | |
| message = "El rebusque no salió bien hoy" | |
| character_dialogue = self.ai_dialogue.generate_contextual_dialogue( | |
| 'narrator', situation, 'buscar_rebusque', self.game_state | |
| ) | |
| elif action == "hacer_asado": | |
| asado_cost = 1500 | |
| if self.game_state['resources']['pesos'] >= asado_cost: | |
| self.game_state['resources']['pesos'] -= asado_cost | |
| self.game_state['resources']['solidaridad'] += 25 | |
| self.game_state['resources']['esperanza'] += 20 | |
| self.neighborhood_reputation += 15 | |
| situation = f"El jugador organizó un asado que costó ${asado_cost}, mejorando la moral del barrio" | |
| character_dialogue = self.ai_dialogue.generate_contextual_dialogue( | |
| 'el_checo', situation, 'hacer_asado', self.game_state | |
| ) | |
| message = f"¡Asado bárbaro! Toda la cuadra vino." | |
| else: | |
| situation = "El jugador quiso hacer asado pero no tenía suficiente plata" | |
| character_dialogue = self.ai_dialogue.generate_contextual_dialogue( | |
| 'dona_rosa', situation, 'hacer_asado', self.game_state | |
| ) | |
| message = "No tenés suficiente plata para el asado, pibe." | |
| elif action == "ahorrar_dolares": | |
| peso_cost = 1000 | |
| if self.game_state['resources']['pesos'] >= peso_cost: | |
| self.game_state['resources']['pesos'] -= peso_cost | |
| self.game_state['resources']['dolares'] += 1 | |
| situation = f"El jugador compró US$1 por ${peso_cost} pesos como protección contra inflación" | |
| character_dialogue = self.ai_dialogue.generate_contextual_dialogue( | |
| 'comerciante', situation, 'ahorrar_dolares', self.game_state | |
| ) | |
| message = "Compraste US$1. Protección contra inflación." | |
| else: | |
| message = "No tenés suficientes pesos para comprar dólares." | |
| character_dialogue = "Los dólares están carísimos, pero hay que cuidarse de la inflación." | |
| # Sistema de eventos especiales basados en progreso | |
| if self.game_state['day'] % 7 == 0: | |
| self._trigger_weekly_event() | |
| # Avanzar día con narrativa mejorada | |
| self._advance_enhanced_day() | |
| # Generar frame y status | |
| frame = self.renderer.render_argentina_frame(self.game_state) | |
| status = self.get_enhanced_status() | |
| # Combinar mensaje de acción con diálogo de personaje | |
| full_message = f"{message}\n\n💬 {character_dialogue}" if character_dialogue else message | |
| return frame, status, full_message | |
| def _calculate_work_income(self): | |
| """Calcula ingresos de trabajo según crisis y profesión""" | |
| base_income = random.randint(1500, 3500) | |
| crisis_modifier = max(0.5, 1.5 - (self.game_state['crisis_level'] * 0.2)) | |
| return int(base_income * crisis_modifier) | |
| def _advance_enhanced_day(self): | |
| """Avanza el día con eventos narrativos""" | |
| self.game_state['day'] += 1 | |
| # Inflación dinámica | |
| inflation_increase = random.uniform(0.01, 0.04) * self.game_state['crisis_level'] | |
| self.game_state['inflation_rate'] += inflation_increase | |
| # Gastos diarios ajustados por inflación | |
| daily_food_cost = random.randint(5, 12) * self.game_state['inflation_rate'] | |
| daily_living_cost = random.randint(200, 500) * self.game_state['inflation_rate'] | |
| self.game_state['resources']['comida'] -= daily_food_cost | |
| self.game_state['resources']['pesos'] -= daily_living_cost | |
| # Mantener recursos en rango válido | |
| for resource in self.game_state['resources']: | |
| self.game_state['resources'][resource] = max(0, self.game_state['resources'][resource]) | |
| # Eventos aleatorios con narrativa IA | |
| if random.random() < 0.2: | |
| self._trigger_ai_event() | |
| # Actualizar nivel de crisis basado en condiciones | |
| self._update_crisis_level() | |
| def _trigger_ai_event(self): | |
| """Eventos aleatorios con narrativa generada por IA""" | |
| base_events = [ | |
| ("Se cortó la luz", "solidaridad", 15, "Los vecinos se organizaron para afrontar el corte"), | |
| ("Llegó mercadería al almacén", "comida", 25, "Doña Rosa te guardó algunos productos"), | |
| ("Paro de transporte", "trabajo", -20, "No pudiste llegar al trabajo por el paro"), | |
| ("Feria en la plaza", "pesos", 600, "Vendiste algunas cosas en la feria del barrio"), | |
| ("Reunión vecinal", "solidaridad", 20, "Se organizó una asamblea para mejorar el barrio") | |
| ] | |
| event_name, resource, change, context = random.choice(base_events) | |
| self.game_state['resources'][resource] += change | |
| self.game_state['last_event'] = event_name | |
| # Generar comentario IA sobre el evento | |
| situation = f"Ocurrió el evento: {event_name}. {context}. Cambió {resource} en {change} puntos." | |
| ai_comment = self.ai_dialogue.generate_contextual_dialogue( | |
| 'narrator', situation, 'evento_aleatorio', self.game_state | |
| ) | |
| self.story_events.append({ | |
| 'day': self.game_state['day'], | |
| 'event': event_name, | |
| 'comment': ai_comment | |
| }) | |
| def _trigger_weekly_event(self): | |
| """Eventos semanales especiales con narrativa extendida""" | |
| weekly_events = [ | |
| "Asamblea vecinal extraordinaria", | |
| "Llegada de ayuda municipal", | |
| "Feria gastronómica del barrio", | |
| "Torneo de fútbol inter-barrios", | |
| "Jornada de trabajo comunitario" | |
| ] | |
| event = random.choice(weekly_events) | |
| self.game_state['last_event'] = f"Evento Semanal: {event}" | |
| # Efectos especiales de eventos semanales | |
| if "Asamblea" in event: | |
| self.game_state['resources']['solidaridad'] += 30 | |
| self.game_state['resources']['esperanza'] += 15 | |
| elif "ayuda municipal" in event: | |
| self.game_state['resources']['comida'] += 40 | |
| self.game_state['resources']['pesos'] += 1000 | |
| elif "Feria gastronómica" in event: | |
| self.game_state['resources']['pesos'] += random.randint(800, 2000) | |
| self.game_state['resources']['solidaridad'] += 20 | |
| elif "Torneo" in event: | |
| self.game_state['resources']['esperanza'] += 25 | |
| self.neighborhood_reputation += 10 | |
| elif "trabajo comunitario" in event: | |
| self.game_state['resources']['solidaridad'] += 35 | |
| self.game_state['resources']['trabajo'] += 20 | |
| def _update_crisis_level(self): | |
| """Actualiza nivel de crisis basado en condiciones""" | |
| # Crisis basada en recursos e inflación | |
| avg_resources = sum(self.game_state['resources'].values()) / len(self.game_state['resources']) | |
| inflation_factor = min(5, self.game_state['inflation_rate']) | |
| if avg_resources < 30 or inflation_factor > 3: | |
| self.game_state['crisis_level'] = min(5, self.game_state['crisis_level'] + 1) | |
| elif avg_resources > 70 and inflation_factor < 2: | |
| self.game_state['crisis_level'] = max(1, self.game_state['crisis_level'] - 1) | |
| def get_enhanced_status(self): | |
| """Estado del juego con información narrativa""" | |
| # Generar análisis económico IA | |
| economic_analysis = self.ai_dialogue.generate_crisis_commentary( | |
| self.game_state['crisis_level'], | |
| self.game_state['resources'] | |
| ) | |
| return { | |
| "📅 Día": self.game_state['day'], | |
| "🏠 Barrio": self.game_state['current_neighborhood'].replace('_', ' ').title(), | |
| "💰 Pesos": f"${self.game_state['resources']['pesos']:.0f}", | |
| "💵 Dólares": f"US${self.game_state['resources']['dolares']:.0f}", | |
| "🍖 Comida": f"{self.game_state['resources']['comida']:.0f}%", | |
| "💪 Esperanza": f"{self.game_state['resources']['esperanza']:.0f}%", | |
| "🤝 Solidaridad": f"{self.game_state['resources']['solidaridad']:.0f}%", | |
| "📈 Inflación": f"{self.game_state['inflation_rate']:.2f}x", | |
| "🏆 Reputación": f"{self.neighborhood_reputation}/100", | |
| "📊 Análisis IA": economic_analysis | |
| } | |
| def get_neighborhood_news(self): | |
| """Genera noticias del barrio con IA""" | |
| return self.ai_dialogue.generate_neighborhood_news(self.game_state) | |
| # Continúo con las otras clases necesarias... | |
| class ArgentinaGameEngine: | |
| """Motor base del juego""" | |
| def __init__(self): | |
| self.resources = { | |
| 'pesos': 1200, | |
| 'dolares': 30, | |
| 'comida': 75, | |
| 'esperanza': 85, | |
| 'solidaridad': 80, | |
| 'trabajo': 65 | |
| } | |
| class GameRenderer: | |
| """Renderizador mejorado""" | |
| def __init__(self): | |
| self.animation_frame = 0 | |
| def render_argentina_frame(self, game_state) -> Image.Image: | |
| """Renderiza con detalles mejorados""" | |
| img = Image.new('RGB', (900, 600), (15, 25, 50)) | |
| draw = ImageDraw.Draw(img) | |
| # Skyline dinámico según hora del día | |
| self._draw_dynamic_skyline(draw, game_state) | |
| # Barrio con actividad según solidaridad | |
| self._draw_active_neighborhood(draw, game_state) | |
| # Personaje con animaciones | |
| self._draw_animated_character(draw, game_state) | |
| # UI mejorada con gradientes | |
| self._draw_enhanced_ui(draw, game_state) | |
| return img | |
| def _draw_dynamic_skyline(self, draw, game_state): | |
| """Skyline que cambia según el estado del juego""" | |
| # Color del cielo según esperanza | |
| hope_level = game_state.get('resources', {}).get('esperanza', 50) | |
| sky_blue = min(255, int(60 + hope_level * 1.6)) | |
| # Gradiente de cielo | |
| for y in range(200): | |
| color_intensity = int(sky_blue * (1 - y/400)) | |
| draw.rectangle([0, y, 900, y+1], fill=(15, 25, color_intensity)) | |
| # Edificios con luces según recursos | |
| prosperity = sum(game_state.get('resources', {}).values()) / 500 | |
| buildings = [ | |
| (100, 120, 140, 400), (160, 100, 200, 400), (220, 140, 260, 400), | |
| (700, 80, 750, 350), (770, 110, 820, 350) | |
| ] | |
| for building in buildings: | |
| draw.rectangle(building, fill=(30, 30, 50), outline=(60, 60, 80)) | |
| # Ventanas encendidas según prosperidad | |
| for window_y in range(building[1] + 20, building[3], 25): | |
| for window_x in range(building[0] + 8, building[2], 18): | |
| if random.random() < prosperity: | |
| draw.rectangle([window_x, window_y, window_x+10, window_y+10], | |
| fill=(255, 255, 180)) | |
| def _draw_active_neighborhood(self, draw, game_state): | |
| """Barrio con actividad dinámica""" | |
| solidarity = game_state.get('resources', {}).get('solidaridad', 50) | |
| # Base del neighborhood | |
| draw.rectangle([0, 400, 900, 600], fill=(40, 60, 30)) | |
| # Casas con colores según solidaridad | |
| house_colors = [(200, 100, 100), (100, 200, 100), (100, 100, 200), (200, 200, 100)] | |
| brightness_multiplier = 0.6 + (solidarity / 200) | |
| for i in range(6): | |
| x = 60 + i * 130 | |
| y = 350 + random.randint(-15, 15) | |
| base_color = random.choice(house_colors) | |
| bright_color = tuple(int(c * brightness_multiplier) for c in base_color) | |
| # Casa principal | |
| draw.rectangle([x, y, x+70, y+70], fill=bright_color, outline=(0, 0, 0), width=2) | |
| # Techo | |
| draw.polygon([(x-5, y), (x+35, y-12), (x+75, y)], fill=(120, 120, 120)) | |
| # Detalles según solidaridad | |
| if solidarity > 70: | |
| # Jardincito al frente | |
| draw.rectangle([x-10, y+70, x+80, y+80], fill=(80, 120, 40)) | |
| # Flores | |
| for flower_x in range(x, x+70, 15): | |
| draw.ellipse([flower_x-2, y+72, flower_x+2, y+76], fill=(255, 100, 150)) | |
| # Actividad comunitaria según solidaridad | |
| if solidarity > 60: | |
| self._draw_mate_circle(draw, 180, 520) | |
| if solidarity > 80: | |
| self._draw_community_garden(draw, 600, 480) | |
| def _draw_mate_circle(self, draw, center_x, center_y): | |
| """Dibuja ronda de mate""" | |
| for i in range(5): | |
| angle = i * (math.pi * 2 / 5) | |
| person_x = center_x + 35 * math.cos(angle) | |
| person_y = center_y + 35 * math.sin(angle) | |
| # Persona | |
| draw.ellipse([person_x-6, person_y-12, person_x+6, person_y+6], | |
| fill=(140, 110, 80), outline=(100, 80, 60)) | |
| # Mate en el centro | |
| if i == 0: | |
| draw.ellipse([center_x-4, center_y-4, center_x+4, center_y+4], fill=(80, 40, 0)) | |
| def _draw_community_garden(self, draw, x, y): | |
| """Jardín comunitario""" | |
| # Base del jardín | |
| draw.rectangle([x, y, x+80, y+60], fill=(60, 100, 40)) | |
| # Plantas | |
| for plant_x in range(x+10, x+70, 20): | |
| for plant_y in range(y+10, y+50, 15): | |
| draw.ellipse([plant_x-3, plant_y-3, plant_x+3, plant_y+3], fill=(100, 180, 60)) | |
| def _draw_animated_character(self, draw, game_state): | |
| """Personaje con animaciones""" | |
| x, y = 420, 450 | |
| # Animación de respiración | |
| breath_offset = math.sin(time.time() * 2) * 2 | |
| # Sombra | |
| draw.ellipse([x-10, y+15, x+10, y+18], fill=(0, 0, 0, 120)) | |
| # Cuerpo con animación | |
| body_y = y + breath_offset | |
| draw.ellipse([x-12, body_y-18, x+12, body_y+12], fill=(130, 100, 70), outline=(90, 70, 50), width=2) | |
| # Remera argentina | |
| draw.rectangle([x-10, body_y-15, x+10, body_y+5], fill=(100, 150, 255), outline=(255, 255, 255)) | |
| # Cabeza | |
| head_y = body_y - 25 + breath_offset * 0.5 | |
| draw.ellipse([x-8, head_y-15, x+8, head_y+5], fill=(160, 130, 90), outline=(120, 100, 70)) | |
| # Expresión según esperanza | |
| hope = game_state.get('resources', {}).get('esperanza', 50) | |
| if hope > 70: | |
| # Sonrisa | |
| draw.arc([x-4, head_y-5, x+4, head_y+2], 0, 180, fill=(0, 0, 0), width=2) | |
| elif hope < 30: | |
| # Preocupación | |
| draw.arc([x-4, head_y+2, x+4, head_y-5], 0, 180, fill=(0, 0, 0), width=2) | |
| # Ojos | |
| draw.ellipse([x-5, head_y-8, x-2, head_y-5], fill=(255, 255, 255)) | |
| draw.ellipse([x+2, head_y-8, x+5, head_y-5], fill=(255, 255, 255)) | |
| draw.ellipse([x-4, head_y-7, x-3, head_y-6], fill=(0, 0, 0)) | |
| draw.ellipse([x+3, head_y-7, x+4, head_y-6], fill=(0, 0, 0)) | |
| # Mate en la mano con brillo | |
| mate_x = x + 10 | |
| mate_glow = int(150 + 50 * math.sin(time.time() * 3)) | |
| draw.ellipse([mate_x-3, body_y-8, mate_x+3, body_y-2], | |
| fill=(100, 50, 0), outline=(mate_glow, mate_glow//2, 0), width=2) | |
| def _draw_enhanced_ui(self, draw, game_state): | |
| """UI mejorada con efectos visuales""" | |
| resources = game_state.get('resources', {}) | |
| # Panel principal con gradiente | |
| for i in range(170): | |
| alpha = int(180 * (1 - i/170)) | |
| draw.rectangle([10, 10+i, 350, 11+i], fill=(0, 0, 0, alpha)) | |
| draw.rectangle([10, 10, 350, 180], outline=(100, 150, 200), width=2) | |
| # Título | |
| crisis_level = game_state.get('crisis_level', 1) | |
| crisis_colors = [(100, 255, 100), (150, 255, 100), (255, 255, 100), (255, 150, 100), (255, 100, 100)] | |
| title_color = crisis_colors[min(4, crisis_level-1)] | |
| # Recursos con barras animadas | |
| y_pos = 35 | |
| for resource, value in resources.items(): | |
| # Barra de fondo con gradiente | |
| draw.rectangle([25, y_pos, 220, y_pos+12], fill=(30, 30, 30)) | |
| # Barra de valor con animación | |
| bar_width = int((max(0, min(value, 100)) / 100) * 195) | |
| bar_color = self._get_animated_resource_color(resource, value) | |
| if bar_width > 0: | |
| # Efecto de brillo en la barra | |
| for i in range(bar_width): | |
| brightness = int(255 * (0.7 + 0.3 * math.sin(time.time() * 2 + i/10))) | |
| color_with_glow = tuple(min(255, c + brightness//4) for c in bar_color) | |
| draw.rectangle([25+i, y_pos, 26+i, y_pos+12], fill=color_with_glow) | |
| # Iconos de recursos | |
| resource_icons = { | |
| 'pesos': '💰', 'dolares': '💵', 'comida': '🍖', | |
| 'esperanza': '💪', 'solidaridad': '🤝', 'trabajo': '🔨' | |
| } | |
| # Valor numérico | |
| y_pos += 18 | |
| # Indicador de inflación animado | |
| inflation = game_state.get('inflation_rate', 1.0) | |
| inflation_intensity = min(255, int(100 + inflation * 30)) | |
| inflation_color = (255, 255-inflation_intensity//2, 255-inflation_intensity) | |
| # Pulso según inflación | |
| pulse = math.sin(time.time() * (1 + inflation)) * 10 | |
| draw.rectangle([230, 35, 340, 60], fill=inflation_color, outline=(255, 255, 255), width=2) | |
| # Crisis meter con efectos | |
| crisis_width = int((crisis_level / 5) * 100) | |
| crisis_color = crisis_colors[min(4, crisis_level-1)] | |
| draw.rectangle([230, 70, 340, 85], fill=(50, 50, 50)) | |
| draw.rectangle([230, 70, 230 + crisis_width, 85], fill=crisis_color) | |
| def _get_animated_resource_color(self, resource, value): | |
| """Color animado según el recurso y valor""" | |
| base_colors = { | |
| 'pesos': (255, 215, 0), # Dorado | |
| 'dolares': (100, 255, 100), # Verde dólar | |
| 'comida': (255, 140, 0), # Naranja | |
| 'esperanza': (100, 149, 237), # Azul esperanza | |
| 'solidaridad': (255, 105, 180), # Rosa solidario | |
| 'trabajo': (139, 69, 19) # Marrón trabajo | |
| } | |
| base_color = base_colors.get(resource, (255, 255, 255)) | |
| # Intensidad según valor | |
| if value > 70: | |
| multiplier = 1.0 | |
| elif value > 30: | |
| multiplier = 0.8 | |
| else: | |
| multiplier = 0.6 | |
| # Efecto pulsante en valores críticos | |
| if value < 20: | |
| pulse_effect = 0.8 + 0.4 * math.sin(time.time() * 4) | |
| multiplier *= pulse_effect | |
| return tuple(int(c * multiplier) for c in base_color) | |
| def create_ai_enhanced_interface(): | |
| """Interfaz mejorada con IA""" | |
| game = EnhancedArgentinaGame() | |
| with gr.Blocks( | |
| title="🇦🇷 CRISIS Y CORAJE - Con IA Narrativa", | |
| theme=gr.themes.Soft(), | |
| css=""" | |
| .enhanced-header { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| border-radius: 15px; | |
| padding: 25px; | |
| color: white; | |
| text-align: center; | |
| margin: 15px 0; | |
| } | |
| .action-button { | |
| font-size: 1.1em; | |
| padding: 12px; | |
| margin: 5px; | |
| } | |
| """ | |
| ) as interface: | |
| gr.HTML(""" | |
| <div class="enhanced-header"> | |
| <h1 style="font-size: 2.8em; margin: 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.7);"> | |
| 🇦🇷 CRISIS Y CORAJE | |
| </h1> | |
| <h2 style="font-size: 1.4em; margin: 10px 0; opacity: 0.9;"> | |
| Con Inteligencia Artificial Narrativa | |
| </h2> | |
| <p style="font-size: 1.1em; margin: 5px 0; opacity: 0.8;"> | |
| Supervivencia, Solidaridad y Cultura Argentina • Powered by Groq AI | |
| </p> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| game_display = gr.Image( | |
| label="🌆 Tu Barrio Argentino", | |
| width=900, | |
| height=600 | |
| ) | |
| gr.Markdown("### 🎮 ACCIONES DE SUPERVIVENCIA") | |
| with gr.Row(): | |
| work_btn = gr.Button("💼 TRABAJAR", variant="primary", elem_classes="action-button") | |
| help_btn = gr.Button("🤝 AYUDAR VECINOS", variant="secondary", elem_classes="action-button") | |
| rebusque_btn = gr.Button("💡 REBUSQUE", variant="stop", elem_classes="action-button") | |
| with gr.Row(): | |
| save_btn = gr.Button("💵 COMPRAR DÓLARES", variant="primary", elem_classes="action-button") | |
| asado_btn = gr.Button("🔥 HACER ASADO", variant="stop", elem_classes="action-button") | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 📊 ESTADO DE SUPERVIVENCIA") | |
| status_display = gr.JSON( | |
| label="📈 Recursos y Análisis IA", | |
| value=game.get_enhanced_status() | |
| ) | |
| gr.Markdown("### 🗞️ NOTICIAS DEL BARRIO") | |
| news_display = gr.Textbox( | |
| value="🎮 ¡Bienvenido a Crisis y Coraje con IA! Los diálogos son generados dinámicamente por inteligencia artificial para crear una experiencia narrativa única.", | |
| label="📺 Canal Barrial + IA", | |
| lines=4, | |
| interactive=False | |
| ) | |
| gr.Markdown("### 🤖 SISTEMA DE IA") | |
| ai_status = gr.HTML(f""" | |
| <div style="background: #2d3436; color: white; padding: 15px; border-radius: 10px; margin: 10px 0;"> | |
| <p><strong>🧠 IA Narrativa:</strong> {'🟢 Activa' if game.ai_dialogue.ai_enabled else '🔴 Inactiva'}</p> | |
| <p><strong>🎭 Personajes:</strong> Doña Rosa, El Checo, Comerciante</p> | |
| <p><strong>🎯 Características:</strong></p> | |
| <ul style="margin: 5px 0; padding-left: 20px; font-size: 0.9em;"> | |
| <li>Diálogos contextuales dinámicos</li> | |
| <li>Análisis económico inteligente</li> | |
| <li>Noticias del barrio generadas</li> | |
| <li>Narrativa adaptativa al progreso</li> | |
| </ul> | |
| </div> | |
| """) | |
| # Botón para generar noticias frescas | |
| news_btn = gr.Button("📰 GENERAR NOTICIAS IA", variant="secondary") | |
| # Conectar acciones principales | |
| work_btn.click( | |
| fn=lambda: game.process_enhanced_action("trabajar"), | |
| outputs=[game_display, status_display, news_display] | |
| ) | |
| help_btn.click( | |
| fn=lambda: game.process_enhanced_action("ayudar_vecinos"), | |
| outputs=[game_display, status_display, news_display] | |
| ) | |
| rebusque_btn.click( | |
| fn=lambda: game.process_enhanced_action("buscar_rebusque"), | |
| outputs=[game_display, status_display, news_display] | |
| ) | |
| save_btn.click( | |
| fn=lambda: game.process_enhanced_action("ahorrar_dolares"), | |
| outputs=[game_display, status_display, news_display] | |
| ) | |
| asado_btn.click( | |
| fn=lambda: game.process_enhanced_action("hacer_asado"), | |
| outputs=[game_display, status_display, news_display] | |
| ) | |
| # Botón de noticias IA | |
| news_btn.click( | |
| fn=lambda: game.get_neighborhood_news(), | |
| outputs=news_display | |
| ) | |
| # Estado inicial | |
| interface.load( | |
| fn=lambda: ( | |
| game.renderer.render_argentina_frame(game.game_state), | |
| game.get_enhanced_status(), | |
| "🎮 ¡Crisis y Coraje con IA activado! Cada acción generará diálogos únicos y contextuales. Los personajes reaccionarán de manera inteligente a tus decisiones." | |
| ), | |
| outputs=[game_display, status_display, news_display] | |
| ) | |
| return interface | |
| if __name__ == "__main__": | |
| interface = create_ai_enhanced_interface() | |
| interface.launch(share=True, show_error=True, show_api=False) | |