RetroGame / app.py
Lukeetah's picture
Update app.py
8aaec69 verified
# app.py - SIMCIUDAD ARGENTINA FUNCIONAL COMPLETA
import gradio as gr
import time
import math
import random
import os
from PIL import Image, ImageDraw
import json
# Groq AI Integration (mejorada)
try:
from groq import Groq
GROQ_AVAILABLE = True
groq_client = Groq(api_key=os.getenv("GROQ_API_KEY")) if os.getenv("GROQ_API_KEY") else None
except ImportError:
GROQ_AVAILABLE = False
groq_client = None
class AIUrbanPlanner:
"""IA para planificación urbana argentina con Groq"""
def __init__(self):
self.enabled = GROQ_AVAILABLE and groq_client is not None
def generate_city_advice(self, city_state):
"""Genera consejos de planificación urbana inteligentes"""
if not self.enabled:
fallback_advice = [
"Construí más plazas para aumentar la felicidad vecinal.",
"Las parrillas mejoran la cultura y la moral del barrio.",
"Balanceá viviendas con servicios públicos.",
"Las canchas de fútbol son clave para la identidad argentina."
]
return random.choice(fallback_advice)
try:
prompt = f"""
Sos un urbanista argentino experto. Analizá esta ciudad y dá consejos breves:
Población: {city_state.get('population', 0)} habitantes
Felicidad: {city_state.get('happiness', 0)}%
Presupuesto: ${city_state.get('budget', 0):,}
Edificios: {city_state.get('buildings', 0)}
Identidad Cultural: {city_state.get('culture', 80)}%
Dá UN consejo específico y argentino en máximo 2 líneas.
Usá jerga porteña cuando sea apropiado.
"""
response = groq_client.chat.completions.create(
messages=[{"role": "user", "content": prompt}],
model="llama-3.1-8b-instant",
temperature=0.8,
max_tokens=100
)
return response.choices[0].message.content.strip()
except Exception as e:
print(f"Error Groq: {e}")
return "La ciudad necesita más espacios verdes y culturales para prosperar."
class ArgentineCityBuilder:
"""SimCity Argentino con mecánicas revolucionarias"""
def __init__(self):
self.width, self.height = 1000, 700
self.grid_size = 20
self.city_grid = {} # Coordenadas -> tipo de edificio
self.population = 100
self.happiness = 75
self.economy = 1000
self.inflation = 1.0
self.cultural_identity = 90
# Sistemas dinámicos
self.neighborhoods = {}
self.transport_network = {}
self.cultural_centers = {}
self.businesses = {}
# Recursos complejos (CORREGIDO - todos los recursos necesarios)
self.resources = {
'presupuesto': 50000,
'energia': 100,
'agua': 100,
'seguridad': 70,
'educacion': 60,
'salud': 65,
'cultura': 80,
'empleo': 75,
'vivienda': 50,
'transport': 60, # AGREGADO
'pollution': 0 # AGREGADO
}
# Edificios disponibles (CORREGIDOS - todos los efectos)
self.building_types = {
'casa': {'cost': 1000, 'pop': 4, 'happiness': 2, 'vivienda': 5},
'villa': {'cost': 500, 'pop': 8, 'happiness': -2, 'solidarity': 15, 'vivienda': 8},
'edificio': {'cost': 5000, 'pop': 20, 'happiness': 1, 'vivienda': 15},
'escuela': {'cost': 8000, 'educacion': 20, 'happiness': 10},
'hospital': {'cost': 15000, 'salud': 30, 'happiness': 15},
'cancha': {'cost': 3000, 'happiness': 25, 'cultura': 10},
'parrilla': {'cost': 2000, 'happiness': 20, 'cultura': 15},
'centro_cultural': {'cost': 10000, 'cultura': 40, 'educacion': 10},
'fabrica': {'cost': 12000, 'empleo': 30, 'pollution': -10, 'happiness': -5},
'plaza': {'cost': 1500, 'happiness': 15, 'cultura': 5},
'subte': {'cost': 20000, 'transport': 50, 'happiness': 10},
'comisaria': {'cost': 7000, 'seguridad': 25, 'happiness': -5},
'kiosco': {'cost': 800, 'happiness': 5, 'empleo': 2}
}
# Inicializar ciudad base
self._initialize_base_city()
def _initialize_base_city(self):
"""Crea la ciudad inicial con algunos edificios"""
# Plaza central
self.city_grid[(25, 17)] = 'plaza'
# Algunas casas iniciales
initial_houses = [(20, 15), (30, 15), (25, 12), (25, 22)]
for pos in initial_houses:
self.city_grid[pos] = 'casa'
# Una escuela inicial
self.city_grid[(15, 20)] = 'escuela'
class AdvancedCityRenderer:
"""Renderizador avanzado con gráficos isométricos COMPLETO"""
def __init__(self):
self.animation_time = 0
self.tile_size = 20
def render_city_frame(self, city_builder) -> Image.Image:
"""Renderiza la ciudad con vista isométrica mejorada"""
img = Image.new('RGB', (city_builder.width, city_builder.height), (20, 40, 20))
draw = ImageDraw.Draw(img)
self.animation_time += 0.1
# Fondo: Cielo dinámico según felicidad
self._draw_dynamic_sky(draw, city_builder)
# Grid base de la ciudad
self._draw_city_grid(draw, city_builder)
# Edificios con perspectiva isométrica
self._draw_isometric_buildings(draw, city_builder)
# Sistemas dinámicos (tráfico, personas, etc.)
self._draw_city_life(draw, city_builder)
# UI avanzada
self._draw_advanced_ui(draw, city_builder)
# Efectos especiales
self._draw_special_effects(draw, city_builder)
return img
def _draw_dynamic_sky(self, draw, city):
"""Cielo que cambia según el estado de la ciudad"""
happiness = city.happiness
# Color base del cielo
if happiness > 80:
sky_color = (135, 206, 250) # Azul cielo feliz
elif happiness > 60:
sky_color = (176, 196, 222) # Azul grisáceo
elif happiness > 40:
sky_color = (119, 136, 153) # Gris claro
else:
sky_color = (105, 105, 105) # Gris oscuro
# Gradiente de cielo
for y in range(200):
alpha = 1.0 - (y / 400)
color = tuple(int(c * alpha + 20 * (1-alpha)) for c in sky_color)
draw.rectangle([0, y, city.width, y+2], fill=color)
# Nubes dinámicas
cloud_offset = int(self.animation_time * 20) % city.width
for i in range(3):
x = (i * 300 + cloud_offset) % (city.width + 100)
y = 50 + i * 30
self._draw_cloud(draw, x, y, happiness)
def _draw_cloud(self, draw, x, y, happiness):
"""Dibuja nube con forma según felicidad"""
cloud_color = (255, 255, 255) if happiness > 50 else (200, 200, 200)
# Forma de nube
for i in range(5):
offset_x = i * 15 + random.randint(-5, 5)
offset_y = random.randint(-8, 8)
size = 20 + random.randint(-5, 10)
draw.ellipse([x + offset_x - size//2, y + offset_y - size//2,
x + offset_x + size//2, y + offset_y + size//2],
fill=cloud_color)
def _draw_city_grid(self, draw, city):
"""Grid isométrico de la ciudad"""
grid_color = (60, 80, 60) if city.cultural_identity > 50 else (80, 80, 80)
# Grid horizontal
for y in range(0, city.height, self.tile_size):
draw.line([0, y, city.width, y], fill=grid_color, width=1)
# Grid vertical
for x in range(0, city.width, self.tile_size):
draw.line([x, 0, x, city.height], fill=grid_color, width=1)
def _draw_isometric_buildings(self, draw, city):
"""Edificios con perspectiva isométrica - TODOS LOS TIPOS"""
for (grid_x, grid_y), building_type in city.city_grid.items():
screen_x = grid_x * self.tile_size
screen_y = grid_y * self.tile_size
# TODOS los tipos de edificios implementados
if building_type == 'casa':
self._draw_house_isometric(draw, screen_x, screen_y, city.happiness)
elif building_type == 'villa':
self._draw_villa_house(draw, screen_x, screen_y, city.cultural_identity)
elif building_type == 'edificio':
self._draw_apartment_building(draw, screen_x, screen_y, city.economy)
elif building_type == 'escuela':
self._draw_school(draw, screen_x, screen_y, city.resources['educacion'])
elif building_type == 'hospital':
self._draw_hospital(draw, screen_x, screen_y, city.resources['salud'])
elif building_type == 'cancha':
self._draw_soccer_field(draw, screen_x, screen_y, city.happiness)
elif building_type == 'parrilla':
self._draw_parrilla(draw, screen_x, screen_y, city.cultural_identity)
elif building_type == 'plaza':
self._draw_plaza(draw, screen_x, screen_y, city.happiness)
elif building_type == 'fabrica':
self._draw_factory(draw, screen_x, screen_y, city.resources['empleo'])
elif building_type == 'centro_cultural':
self._draw_cultural_center(draw, screen_x, screen_y, city.resources['cultura'])
elif building_type == 'kiosco':
self._draw_kiosco(draw, screen_x, screen_y, city.economy)
elif building_type == 'subte':
self._draw_subway(draw, screen_x, screen_y, city.resources['transport'])
elif building_type == 'comisaria':
self._draw_police_station(draw, screen_x, screen_y, city.resources['seguridad'])
# TODOS los métodos de dibujo de edificios (implementados completamente)
def _draw_house_isometric(self, draw, x, y, happiness):
"""Casa con perspectiva isométrica"""
house_color = (139, 69, 19) if happiness > 60 else (101, 67, 33)
# Paredes
wall_points = [(x + 2, y + 15), (x + 18, y + 15), (x + 18, y + 2), (x + 2, y + 2)]
draw.polygon(wall_points, fill=house_color, outline=(80, 40, 0), width=1)
# Techo isométrico
roof_color = (160, 82, 45) if happiness > 70 else (120, 60, 30)
roof_points = [(x, y), (x + 10, y - 5), (x + 20, y), (x + 10, y + 5)]
draw.polygon(roof_points, fill=roof_color, outline=(100, 50, 25))
# Puerta
draw.rectangle([x + 8, y + 10, x + 12, y + 15], fill=(101, 67, 33))
# Ventanas con luz según felicidad
window_color = (255, 255, 200) if happiness > 50 else (150, 150, 150)
draw.rectangle([x + 4, y + 8, x + 7, y + 11], fill=window_color, outline=(0, 0, 0))
draw.rectangle([x + 13, y + 8, x + 16, y + 11], fill=window_color, outline=(0, 0, 0))
# Jardincito si hay alta felicidad
if happiness > 80:
for i in range(3):
flower_x = x + 2 + i * 4
flower_y = y + 16
draw.ellipse([flower_x, flower_y, flower_x + 2, flower_y + 2], fill=(255, 100, 150))
def _draw_villa_house(self, draw, x, y, cultural_identity):
"""Casa de villa con colores vibrantes"""
villa_colors = [(255, 100, 100), (100, 255, 100), (100, 100, 255), (255, 255, 100), (255, 100, 255)]
color = random.choice(villa_colors)
# Casa principal
draw.rectangle([x, y + 5, x + 20, y + 18], fill=color, outline=(0, 0, 0), width=2)
# Techo de chapa
draw.polygon([(x-2, y+5), (x+10, y-2), (x+22, y+5)], fill=(150, 150, 150))
# Extensión autoconstruida
if cultural_identity > 60:
draw.rectangle([x + 16, y + 12, x + 25, y + 18], fill=color, outline=(0, 0, 0))
# Antena parabólica
draw.ellipse([x + 15, y - 1, x + 18, y + 2], outline=(200, 200, 200), width=2)
# Tendedero con ropa
draw.line([(x + 2, y + 3), (x + 18, y + 3)], fill=(100, 100, 100), width=1)
for i in range(3):
cloth_x = x + 4 + i * 5
draw.rectangle([cloth_x, y + 3, cloth_x + 3, y + 8],
fill=random.choice([(255, 0, 0), (0, 255, 0), (0, 0, 255)]))
def _draw_apartment_building(self, draw, x, y, economy):
"""Edificio de departamentos"""
building_color = (120, 120, 160) if economy > 5000 else (100, 100, 140)
# Estructura principal más alta
draw.rectangle([x + 1, y - 10, x + 23, y + 18], fill=building_color, outline=(0, 0, 0), width=1)
# Ventanas en pisos
for floor in range(4):
floor_y = y - 5 + floor * 6
for window in range(3):
window_x = x + 4 + window * 6
light_on = economy > 3000 and random.random() < 0.7
window_color = (255, 255, 200) if light_on else (100, 100, 120)
draw.rectangle([window_x, floor_y, window_x + 3, floor_y + 3],
fill=window_color, outline=(0, 0, 0))
def _draw_school(self, draw, x, y, education_level):
"""Escuela con bandera argentina"""
school_color = (255, 255, 100) if education_level > 70 else (200, 200, 80)
# Edificio principal
draw.rectangle([x + 1, y + 8, x + 23, y + 18], fill=school_color, outline=(0, 0, 0), width=2)
# Bandera argentina
draw.rectangle([x + 20, y + 2, x + 22, y + 8], fill=(100, 150, 255)) # Azul
draw.rectangle([x + 20, y + 4, x + 22, y + 6], fill=(255, 255, 255)) # Blanco
# Asta
draw.line([(x + 22, y + 8), (x + 22, y + 2)], fill=(139, 69, 19), width=2)
# Patio escolar
draw.rectangle([x + 5, y + 12, x + 19, y + 16], fill=(200, 200, 200), outline=(150, 150, 150))
# Niños en el patio si alta educación
if education_level > 60:
for i in range(3):
child_x = x + 7 + i * 4
child_y = y + 13
draw.ellipse([child_x, child_y, child_x + 2, child_y + 3], fill=(180, 140, 100))
def _draw_hospital(self, draw, x, y, health_level):
"""Hospital con cruz roja"""
hospital_color = (255, 100, 100) if health_level > 70 else (200, 80, 80)
# Edificio
draw.rectangle([x + 1, y + 5, x + 23, y + 18], fill=hospital_color, outline=(0, 0, 0), width=2)
# Cruz roja
draw.rectangle([x + 10, y + 8, x + 14, y + 15], fill=(255, 0, 0))
draw.rectangle([x + 7, y + 10, x + 17, y + 13], fill=(255, 0, 0))
# Ambulancia si hospital activo
if health_level > 50:
draw.rectangle([x + 2, y + 15, x + 10, y + 18], fill=(255, 255, 255), outline=(0, 0, 0))
draw.ellipse([x + 3, y + 16, x + 5, y + 18], fill=(0, 0, 0))
draw.ellipse([x + 7, y + 16, x + 9, y + 18], fill=(0, 0, 0))
def _draw_soccer_field(self, draw, x, y, happiness):
"""Cancha de fútbol con actividad"""
# Césped
grass_color = (50, 150, 50) if happiness > 60 else (80, 120, 80)
draw.rectangle([x, y, x + 40, y + 25], fill=grass_color, outline=(255, 255, 255), width=2)
# Líneas de la cancha
draw.line([(x + 20, y), (x + 20, y + 25)], fill=(255, 255, 255), width=2)
draw.ellipse([x + 15, y + 10, x + 25, y + 15], outline=(255, 255, 255), width=2)
# Arcos
draw.rectangle([x, y + 8, x + 3, y + 17], outline=(255, 255, 255), width=2)
draw.rectangle([x + 37, y + 8, x + 40, y + 17], outline=(255, 255, 255), width=2)
# Jugadores animados
if happiness > 70:
for i in range(6):
player_x = x + 5 + i * 6 + int(3 * math.sin(self.animation_time + i))
player_y = y + 8 + int(2 * math.cos(self.animation_time + i * 1.5))
draw.ellipse([player_x, player_y, player_x + 3, player_y + 5], fill=(180, 140, 100))
# Pelota animada
ball_x = x + 20 + int(10 * math.sin(self.animation_time * 2))
ball_y = y + 12 + int(5 * math.cos(self.animation_time * 2))
draw.ellipse([ball_x, ball_y, ball_x + 3, ball_y + 3], fill=(255, 255, 255), outline=(0, 0, 0))
def _draw_parrilla(self, draw, x, y, cultural_identity):
"""Parrilla argentina con humo"""
# Base de la parrilla
draw.rectangle([x + 5, y + 10, x + 15, y + 18], fill=(50, 50, 50), outline=(100, 100, 100))
# Parrilla
for i in range(3):
draw.line([(x + 6 + i * 3, y + 12), (x + 6 + i * 3, y + 16)], fill=(150, 150, 150), width=2)
# Carne en la parrilla
if cultural_identity > 70:
for i in range(2):
meat_x = x + 7 + i * 4
meat_y = y + 13
draw.ellipse([meat_x, meat_y, meat_x + 3, meat_y + 2], fill=(139, 69, 19))
# Humo animado
for i in range(4):
smoke_x = x + 10 + random.randint(-3, 3)
smoke_y = y + 8 - i * 4 + int(2 * math.sin(self.animation_time * 2 + i))
size = 3 + i
draw.ellipse([smoke_x - size//2, smoke_y - size//2, smoke_x + size//2, smoke_y + size//2],
fill=(200, 200, 200))
# Gente alrededor
if cultural_identity > 60:
for i in range(3):
person_x = x + 2 + i * 8
person_y = y + 15
draw.ellipse([person_x, person_y, person_x + 4, person_y + 8], fill=(180, 140, 100))
def _draw_plaza(self, draw, x, y, happiness):
"""Plaza con actividades"""
# Base verde de la plaza
plaza_color = (100, 180, 100) if happiness > 50 else (120, 140, 120)
draw.rectangle([x, y, x + 30, y + 30], fill=plaza_color, outline=(80, 120, 80))
# Senderos
draw.line([(x, y + 15), (x + 30, y + 15)], fill=(160, 140, 120), width=3)
draw.line([(x + 15, y), (x + 15, y + 30)], fill=(160, 140, 120), width=3)
# Monumento central
draw.rectangle([x + 12, y + 12, x + 18, y + 18], fill=(180, 180, 180), outline=(120, 120, 120))
# Árboles
tree_positions = [(x + 5, y + 5), (x + 25, y + 5), (x + 5, y + 25), (x + 25, y + 25)]
for tree_x, tree_y in tree_positions:
# Tronco
draw.rectangle([tree_x, tree_y + 3, tree_x + 2, tree_y + 8], fill=(101, 67, 33))
# Copa
draw.ellipse([tree_x - 2, tree_y, tree_x + 4, tree_y + 6], fill=(50, 150, 50))
# Actividades según felicidad
if happiness > 70:
# Ronda de mate
for i in range(4):
angle = i * (math.pi / 2)
person_x = x + 15 + int(8 * math.cos(angle))
person_y = y + 15 + int(8 * math.sin(angle))
draw.ellipse([person_x, person_y, person_x + 3, person_y + 5], fill=(180, 140, 100))
def _draw_factory(self, draw, x, y, employment_level):
"""Fábrica con chimeneas"""
factory_color = (150, 100, 100) if employment_level > 60 else (120, 80, 80)
# Edificio principal
draw.rectangle([x + 1, y + 8, x + 23, y + 18], fill=factory_color, outline=(0, 0, 0), width=2)
# Chimeneas
for chimney_x in [x + 6, x + 12, x + 18]:
draw.rectangle([chimney_x, y + 2, chimney_x + 3, chimney_x + 8], fill=(80, 80, 80))
# Humo
smoke_y = y + 2 - int(3 * math.sin(self.animation_time))
draw.ellipse([chimney_x, smoke_y - 2, chimney_x + 3, smoke_y + 1], fill=(150, 150, 150))
# Ventanas industriales
for window_x in range(x + 4, x + 20, 4):
draw.rectangle([window_x, y + 12, window_x + 2, window_x + 15],
fill=(255, 200, 100), outline=(0, 0, 0))
def _draw_cultural_center(self, draw, x, y, culture_level):
"""Centro cultural"""
center_color = (255, 140, 0) if culture_level > 70 else (200, 100, 0)
# Edificio principal
draw.rectangle([x + 1, y + 5, x + 23, y + 18], fill=center_color, outline=(0, 0, 0), width=2)
# Cartelera cultural
draw.rectangle([x + 5, y + 8, x + 19, y + 15], fill=(255, 255, 255), outline=(0, 0, 0))
# Máscaras de teatro
draw.ellipse([x + 7, y + 10, x + 11, y + 13], fill=(255, 255, 255), outline=(0, 0, 0))
draw.ellipse([x + 13, y + 10, x + 17, y + 13], fill=(255, 255, 255), outline=(0, 0, 0))
def _draw_kiosco(self, draw, x, y, economy):
"""Kiosco de barrio"""
kiosco_color = (200, 150, 100) if economy > 2000 else (150, 100, 80)
# Estructura pequeña
draw.rectangle([x + 5, y + 10, x + 15, y + 18], fill=kiosco_color, outline=(0, 0, 0), width=2)
# Cartel
draw.rectangle([x + 7, y + 8, x + 13, y + 10], fill=(255, 0, 0))
# Productos en ventana
for i in range(3):
product_x = x + 6 + i * 3
draw.rectangle([product_x, y + 12, product_x + 2, y + 15],
fill=random.choice([(255, 0, 0), (0, 255, 0), (0, 0, 255)]))
def _draw_subway(self, draw, x, y, transport_level):
"""Estación de subte"""
# Entrada subterránea
draw.rectangle([x + 5, y + 10, x + 15, y + 18], fill=(100, 100, 100), outline=(0, 0, 0), width=2)
# Cartel de subte
draw.rectangle([x + 7, y + 5, x + 13, y + 8], fill=(0, 100, 200))
# Escaleras
for i in range(3):
step_y = y + 12 + i * 2
draw.line([(x + 6, step_y), (x + 14, step_y)], fill=(150, 150, 150), width=1)
def _draw_police_station(self, draw, x, y, security_level):
"""Comisaría"""
station_color = (100, 100, 150) if security_level > 70 else (80, 80, 120)
# Edificio
draw.rectangle([x + 1, y + 5, x + 23, y + 18], fill=station_color, outline=(0, 0, 0), width=2)
# Escudo policial
draw.ellipse([x + 10, y + 8, x + 14, y + 12], fill=(255, 255, 255), outline=(0, 0, 0))
# Patrullero
if security_level > 50:
draw.rectangle([x + 16, y + 15, x + 22, y + 18], fill=(255, 255, 255), outline=(0, 0, 0))
draw.ellipse([x + 17, y + 17, x + 19, y + 19], fill=(0, 0, 0))
draw.ellipse([x + 20, y + 17, x + 22, y + 19], fill=(0, 0, 0))
def _draw_city_life(self, draw, city):
"""Sistemas dinámicos de vida urbana"""
# Tráfico animado en calles principales
if city.resources['transport'] > 30:
self._draw_animated_traffic(draw, city)
# Personas caminando
if city.happiness > 40:
self._draw_pedestrians(draw, city)
# Efectos de contaminación
if city.resources.get('pollution', 0) < -20:
self._draw_pollution_effects(draw, city)
def _draw_animated_traffic(self, draw, city):
"""Tráfico animado en las calles"""
# Colectivos argentinos
for i in range(3):
bus_x = (100 + i * 300 + int(self.animation_time * 50)) % city.width
bus_y = 200 + i * 100
# Colectivo
draw.rectangle([bus_x, bus_y, bus_x + 25, bus_y + 12],
fill=(255, 200, 0), outline=(0, 0, 0), width=2)
# Ventanas
for window in range(3):
draw.rectangle([bus_x + 3 + window * 6, bus_y + 2,
bus_x + 8 + window * 6, bus_y + 7],
fill=(150, 200, 255))
# Ruedas
draw.ellipse([bus_x + 2, bus_y + 10, bus_x + 6, bus_y + 14], fill=(50, 50, 50))
draw.ellipse([bus_x + 19, bus_y + 10, bus_x + 23, bus_y + 14], fill=(50, 50, 50))
def _draw_pedestrians(self, draw, city):
"""Peatones animados"""
for i in range(8):
person_x = (50 + i * 120 + int(self.animation_time * 30 + i * 50)) % city.width
person_y = 300 + i * 40 + int(5 * math.sin(self.animation_time + i))
# Persona simple
draw.ellipse([person_x, person_y, person_x + 4, person_y + 8], fill=(180, 140, 100))
# Mate en la mano si alta cultura
if city.cultural_identity > 70 and i % 3 == 0:
draw.ellipse([person_x + 3, person_y + 2, person_x + 6, person_y + 5], fill=(100, 50, 0))
def _draw_pollution_effects(self, draw, city):
"""Efectos de contaminación"""
pollution_level = abs(city.resources.get('pollution', 0))
if pollution_level > 20:
# Smog gris
for i in range(int(pollution_level / 5)):
smog_x = random.randint(0, city.width)
smog_y = random.randint(100, 300)
size = random.randint(10, 30)
draw.ellipse([smog_x - size, smog_y - size, smog_x + size, smog_y + size],
fill=(120, 120, 120))
def _draw_advanced_ui(self, draw, city):
"""UI avanzada con gráficos informativos"""
# Panel principal con transparencia
panel_width = 300
panel_height = 200
# Panel de fondo con gradiente
for i in range(panel_height):
alpha = int(180 - i)
color = (0, 0, 0) if alpha > 0 else (20, 20, 20)
draw.rectangle([10, 10 + i, 10 + panel_width, 11 + i], fill=color)
# Borde elegante
draw.rectangle([10, 10, 10 + panel_width, 10 + panel_height],
outline=(100, 150, 255), width=3)
# Información de la ciudad (texto simplificado por limitaciones de PIL)
info_y = 25
city_info = [
f"Población: {city.population:,}",
f"Felicidad: {city.happiness}%",
f"Economía: ${city.economy:,}",
f"Identidad: {city.cultural_identity}%",
f"Inflación: {city.inflation:.1f}x"
]
# Barras de recursos en el lado derecho
self._draw_resource_bars(draw, city, city.width - 250, 20)
# Mini mapa en esquina inferior derecha
self._draw_mini_map(draw, city, city.width - 150, city.height - 120)
def _draw_resource_bars(self, draw, city, start_x, start_y):
"""Barras de recursos con efectos visuales"""
resources = city.resources
bar_width = 200
bar_height = 15
resource_colors = {
'presupuesto': (255, 215, 0), 'energia': (255, 255, 0),
'agua': (0, 191, 255), 'seguridad': (255, 99, 71),
'educacion': (147, 112, 219), 'salud': (255, 20, 147),
'cultura': (255, 140, 0), 'empleo': (50, 205, 50),
'vivienda': (139, 69, 19), 'transport': (100, 149, 237),
'pollution': (120, 120, 120)
}
y_offset = start_y
for resource, value in resources.items():
if resource == 'presupuesto':
# Presupuesto se muestra diferente
y_offset += 25
continue
# Normalizar valor para barra (0-100)
if resource == 'pollution':
normalized_value = max(0, min(100, abs(value)))
else:
normalized_value = max(0, min(100, value)) if isinstance(value, (int, float)) else 50
# Barra de fondo
draw.rectangle([start_x, y_offset, start_x + bar_width, y_offset + bar_height],
fill=(40, 40, 40), outline=(100, 100, 100))
# Barra de valor con gradiente
fill_width = int((normalized_value / 100) * (bar_width - 4))
color = resource_colors.get(resource, (150, 150, 150))
# Efecto de brillo
for i in range(fill_width):
brightness = 0.7 + 0.3 * math.sin(self.animation_time + i/10)
bright_color = tuple(int(c * brightness) for c in color)
draw.rectangle([start_x + 2 + i, y_offset + 2,
start_x + 3 + i, y_offset + bar_height - 2],
fill=bright_color)
y_offset += 22
def _draw_mini_map(self, draw, city, start_x, start_y):
"""Mini mapa de la ciudad"""
map_width, map_height = 120, 100
# Fondo del mini mapa
draw.rectangle([start_x, start_y, start_x + map_width, start_y + map_height],
fill=(20, 20, 20), outline=(150, 150, 150), width=2)
# Edificios en el mini mapa
scale = 2
for (grid_x, grid_y), building_type in city.city_grid.items():
mini_x = start_x + (grid_x * scale) % map_width
mini_y = start_y + (grid_y * scale) % map_height
building_colors = {
'casa': (100, 150, 100), 'villa': (255, 150, 100),
'edificio': (150, 150, 200), 'escuela': (200, 200, 100),
'hospital': (255, 100, 100), 'cancha': (100, 255, 100),
'plaza': (150, 255, 150), 'fabrica': (150, 100, 100),
'centro_cultural': (255, 140, 0), 'kiosco': (200, 150, 100),
'subte': (100, 149, 237), 'comisaria': (100, 100, 150),
'parrilla': (200, 100, 50)
}
color = building_colors.get(building_type, (100, 100, 100))
draw.rectangle([mini_x, mini_y, mini_x + scale, mini_y + scale], fill=color)
def _draw_special_effects(self, draw, city):
"""Efectos especiales adicionales"""
# Efectos de festivales si alta cultura
if city.resources['cultura'] > 80:
for i in range(5):
firework_x = random.randint(100, city.width - 100)
firework_y = random.randint(50, 150)
size = random.randint(3, 8)
color = random.choice([(255, 100, 100), (100, 255, 100), (100, 100, 255), (255, 255, 100)])
draw.ellipse([firework_x - size, firework_y - size,
firework_x + size, firework_y + size], fill=color)
class EnhancedCityGame:
"""Juego de ciudad argentino mejorado COMPLETO"""
def __init__(self):
self.city = ArgentineCityBuilder()
self.renderer = AdvancedCityRenderer()
self.ai_planner = AIUrbanPlanner()
self.selected_building = None
self.game_speed = 1
self.events_log = []
self.day = 1
def place_building(self, building_type, grid_x, grid_y):
"""Coloca un edificio en la ciudad - CORREGIDO"""
try:
grid_x, grid_y = int(grid_x), int(grid_y)
if building_type not in self.city.building_types:
return "Tipo de edificio inválido", self.get_city_status(), "Error: Tipo de edificio no válido"
if (grid_x, grid_y) in self.city.city_grid:
return "Ya hay un edificio ahí", self.get_city_status(), "Error: Posición ocupada"
building_info = self.city.building_types[building_type]
cost = building_info['cost']
if self.city.resources['presupuesto'] < cost:
return f"No tenés suficiente presupuesto (${cost:,})", self.get_city_status(), "Error: Sin presupuesto"
# Construir edificio
self.city.resources['presupuesto'] -= cost
self.city.city_grid[(grid_x, grid_y)] = building_type
# Aplicar efectos CORREGIDOS
self._apply_building_effects(building_type, building_info)
# Actualizar estadísticas
self._update_city_stats()
# Generar consejo IA
advice = self.ai_planner.generate_city_advice({
'population': self.city.population,
'happiness': self.city.happiness,
'budget': self.city.resources['presupuesto'],
'buildings': len(self.city.city_grid),
'culture': self.city.cultural_identity
})
frame = self.renderer.render_city_frame(self.city)
status = self.get_city_status()
message = f"✅ {building_type.replace('_', ' ').title()} construido por ${cost:,}"
return frame, status, f"{message}\n\n🤖 IA Urbana: {advice}"
except Exception as e:
print(f"Error en construcción: {e}")
return self.renderer.render_city_frame(self.city), self.get_city_status(), f"Error: {str(e)}"
def _apply_building_effects(self, building_type, building_info):
"""Aplica los efectos de un edificio a la ciudad - CORREGIDO"""
for effect, value in building_info.items():
if effect == 'cost':
continue
elif effect == 'pop':
self.city.population += value
elif effect == 'happiness':
self.city.happiness = min(100, max(0, self.city.happiness + value))
elif effect == 'solidarity':
self.city.cultural_identity = min(100, max(0, self.city.cultural_identity + value))
elif effect in self.city.resources:
if effect == 'pollution':
# Pollution es negativa, así que sumamos el valor negativo
self.city.resources[effect] += value
else:
current_value = self.city.resources[effect]
self.city.resources[effect] = min(100, max(0, current_value + value))
def _update_city_stats(self):
"""Actualiza estadísticas generales de la ciudad"""
# Calcular economía basada en edificios
economy_buildings = ['fabrica', 'kiosco', 'centro_cultural', 'subte']
economy_bonus = sum(5000 for pos, building in self.city.city_grid.items()
if building in economy_buildings)
self.city.economy = 1000 + economy_bonus + self.city.population * 30
# Inflación aumenta con el tiempo y población
self.city.inflation += 0.005 + (len(self.city.city_grid) * 0.001)
# Felicidad general basada en múltiples factores
happiness_factors = [
self.city.resources.get('cultura', 50),
self.city.resources.get('seguridad', 50),
self.city.resources.get('empleo', 50),
self.city.resources.get('salud', 50),
self.city.resources.get('educacion', 50),
self.city.cultural_identity
]
self.city.happiness = sum(happiness_factors) // len(happiness_factors)
# Limitar felicidad
self.city.happiness = max(0, min(100, self.city.happiness))
def simulate_day(self):
"""Simula un día en la ciudad"""
try:
self.day += 1
# Ingresos diarios
base_income = self.city.population * 12
economy_bonus = self.city.economy * 0.008
daily_income = int(base_income + economy_bonus)
self.city.resources['presupuesto'] += daily_income
# Gastos de mantenimiento
maintenance_cost = len(self.city.city_grid) * 80
self.city.resources['presupuesto'] -= maintenance_cost
# Efectos de la inflación (más realista)
inflation_impact = int(self.city.resources['presupuesto'] * 0.01)
self.city.resources['presupuesto'] -= inflation_impact
# Eventos aleatorios
event_message = ""
if random.random() < 0.25:
event_message = self._trigger_city_event()
# Actualizar todo
self._update_city_stats()
# Generar análisis IA del día
ai_analysis = self.ai_planner.generate_city_advice({
'population': self.city.population,
'happiness': self.city.happiness,
'budget': self.city.resources['presupuesto'],
'buildings': len(self.city.city_grid),
'culture': self.city.cultural_identity
})
frame = self.renderer.render_city_frame(self.city)
status = self.get_city_status()
message = f"📅 Día {self.day} completado\n💰 Ingresos: ${daily_income:,}\n💸 Gastos: ${maintenance_cost:,}"
if event_message:
message += f"\n📰 {event_message}"
message += f"\n\n🤖 Análisis IA: {ai_analysis}"
return frame, status, message
except Exception as e:
print(f"Error en simulación: {e}")
return self.renderer.render_city_frame(self.city), self.get_city_status(), f"Error: {str(e)}"
def _trigger_city_event(self):
"""Eventos aleatorios de la ciudad - MEJORADOS"""
events = [
("🎉 Festival de Tango en San Telmo", "cultura", 15, "happiness", 20),
("⚽ Superclásico River vs Boca", "happiness", 30, "cultura", 10),
("🏭 Inversión extranjera en fábrica", "empleo", 20, "presupuesto", 8000),
("🌧️ Lluvia beneficiosa para la ciudad", "agua", 25, "energia", -5),
("📚 Programa nacional de alfabetización", "educacion", 18, "cultura", 12),
("🏥 Campaña de vacunación masiva", "salud", 25, "happiness", 15),
("💸 Crisis económica menor", "presupuesto", -5000, "happiness", -8),
("🎭 Inauguración de centro cultural", "cultura", 30, "educacion", 15),
("🚌 Mejoras en transporte público", "transport", 20, "happiness", 12),
("🛡️ Operativo de seguridad exitoso", "seguridad", 15, "happiness", 8),
("🏠 Programa de vivienda social", "vivienda", 25, "happiness", 18),
("🔥 Festival del Asado Argentino", "cultura", 20, "happiness", 25)
]
event_name, res1, val1, res2, val2 = random.choice(events)
# Aplicar efectos
if res1 == 'presupuesto':
self.city.resources[res1] += val1
elif res1 == 'happiness':
self.city.happiness = min(100, max(0, self.city.happiness + val1))
elif res1 in self.city.resources:
self.city.resources[res1] = min(100, max(0, self.city.resources[res1] + val1))
if res2 == 'presupuesto':
self.city.resources[res2] += val2
elif res2 == 'happiness':
self.city.happiness = min(100, max(0, self.city.happiness + val2))
elif res2 in self.city.resources:
self.city.resources[res2] = min(100, max(0, self.city.resources[res2] + val2))
self.events_log.append(event_name)
return event_name
def get_city_status(self):
"""Status completo de la ciudad"""
try:
building_count = len(self.city.city_grid)
return {
"🏙️ Población": f"{self.city.population:,} habitantes",
"😊 Felicidad": f"{self.city.happiness}%",
"💰 Presupuesto": f"${self.city.resources['presupuesto']:,}",
"📊 Economía": f"${self.city.economy:,}",
"🇦🇷 Identidad": f"{self.city.cultural_identity}%",
"📈 Inflación": f"{self.city.inflation:.2f}x",
"🏗️ Edificios": f"{building_count} construidos",
"📅 Día": self.day,
"⚡ Energía": f"{self.city.resources['energia']}%",
"💧 Agua": f"{self.city.resources['agua']}%",
"🎓 Educación": f"{self.city.resources['educacion']}%",
"🏥 Salud": f"{self.city.resources['salud']}%",
"🎭 Cultura": f"{self.city.resources['cultura']}%",
"💼 Empleo": f"{self.city.resources['empleo']}%"
}
except Exception as e:
return {"Error": str(e)}
def create_simcity_interface():
"""Interfaz del SimCity Argentino COMPLETA"""
game = EnhancedCityGame()
with gr.Blocks(
title="🇦🇷 SIMCIUDAD ARGENTINA - Construcción Épica",
theme=gr.themes.Glass(),
css="""
.building-btn { font-size: 1.0em; padding: 8px; margin: 2px; }
.city-header { background: linear-gradient(45deg, #74b9ff, #0984e3, #00b894); }
.status-panel { background: #2d3436; border-radius: 10px; }
"""
) as interface:
gr.HTML("""
<div class="city-header" style="padding: 25px; text-align: center; border-radius: 15px; margin: 15px;">
<h1 style="color: white; font-size: 2.8em; margin: 0; text-shadow: 3px 3px 6px rgba(0,0,0,0.7);">
🏙️ SIMCIUDAD ARGENTINA 🇦🇷
</h1>
<h2 style="color: #ddd; font-size: 1.2em; margin: 10px 0;">
Construí la Ciudad Argentina de tus Sueños con IA
</h2>
<p style="color: #f1f2f6; font-size: 1.0em;">
Gráficos Isométricos • Mecánicas Realistas • Cultura Auténtica • Powered by Groq AI
</p>
</div>
""")
with gr.Row():
with gr.Column(scale=2):
city_display = gr.Image(
label="🌆 Tu Ciudad Argentina",
width=1000,
height=700,
value=game.renderer.render_city_frame(game.city)
)
gr.Markdown("### 🏗️ HERRAMIENTAS DE CONSTRUCCIÓN")
with gr.Row():
grid_x_input = gr.Number(label="Coordenada X", value=20, minimum=0, maximum=45)
grid_y_input = gr.Number(label="Coordenada Y", value=20, minimum=0, maximum=30)
# Edificios residenciales
gr.Markdown("#### 🏠 RESIDENCIALES")
with gr.Row():
house_btn = gr.Button("🏠 CASA ($1K)", variant="secondary", elem_classes="building-btn")
villa_btn = gr.Button("🏘️ VILLA ($500)", variant="secondary", elem_classes="building-btn")
building_btn = gr.Button("🏢 EDIFICIO ($5K)", variant="primary", elem_classes="building-btn")
kiosco_btn = gr.Button("🏪 KIOSCO ($800)", variant="secondary", elem_classes="building-btn")
# Servicios públicos
gr.Markdown("#### 🏛️ SERVICIOS PÚBLICOS")
with gr.Row():
school_btn = gr.Button("🏫 ESCUELA ($8K)", variant="primary", elem_classes="building-btn")
hospital_btn = gr.Button("🏥 HOSPITAL ($15K)", variant="stop", elem_classes="building-btn")
police_btn = gr.Button("🛡️ COMISARÍA ($7K)", variant="stop", elem_classes="building-btn")
subway_btn = gr.Button("🚇 SUBTE ($20K)", variant="primary", elem_classes="building-btn")
# Cultura y entretenimiento
gr.Markdown("#### 🎭 CULTURA Y ENTRETENIMIENTO")
with gr.Row():
field_btn = gr.Button("⚽ CANCHA ($3K)", variant="secondary", elem_classes="building-btn")
grill_btn = gr.Button("🔥 PARRILLA ($2K)", variant="stop", elem_classes="building-btn")
cultural_btn = gr.Button("🎭 C.CULTURAL ($10K)", variant="primary", elem_classes="building-btn")
plaza_btn = gr.Button("🌳 PLAZA ($1.5K)", variant="secondary", elem_classes="building-btn")
# Industria
gr.Markdown("#### 🏭 INDUSTRIA")
with gr.Row():
factory_btn = gr.Button("🏭 FÁBRICA ($12K)", variant="primary", elem_classes="building-btn")
# Simulación
with gr.Row():
simulate_btn = gr.Button("⏭️ SIMULAR DÍA", variant="primary", size="lg")
with gr.Column(scale=1):
gr.Markdown("### 📊 ESTADO DE LA CIUDAD")
city_status = gr.JSON(
label="📈 Estadísticas Completas",
value=game.get_city_status()
)
gr.Markdown("### 📰 NOTICIAS Y EVENTOS")
events_display = gr.Textbox(
value="🎮 ¡Bienvenido al SimCity Argentino con IA! Construí tu ciudad con identidad nacional. La IA te dará consejos de planificación urbana en tiempo real.",
label="📺 Canal de Noticias Urbanas",
lines=6,
interactive=False
)
gr.Markdown("### 💰 GUÍA DE CONSTRUCCIÓN")
gr.HTML("""
<div style="background: #2d3436; color: white; padding: 15px; border-radius: 10px; font-size: 0.85em;">
<p><strong>💵 Precios y Efectos:</strong></p>
<ul style="margin: 5px 0; padding-left: 15px;">
<li>🏠 Casa: $1,000 (+4 pop, +2 felicidad)</li>
<li>🏘️ Villa: $500 (+8 pop, -2 felicidad, +15 solidaridad)</li>
<li>🏢 Edificio: $5,000 (+20 pop, +1 felicidad)</li>
<li>🏫 Escuela: $8,000 (+20 educación)</li>
<li>🏥 Hospital: $15,000 (+30 salud)</li>
<li>⚽ Cancha: $3,000 (+25 felicidad, +10 cultura)</li>
<li>🔥 Parrilla: $2,000 (+20 felicidad, +15 cultura)</li>
<li>🌳 Plaza: $1,500 (+15 felicidad, +5 cultura)</li>
<li>🏭 Fábrica: $12,000 (+30 empleo, -10 pollution)</li>
<li>🎭 C.Cultural: $10,000 (+40 cultura)</li>
<li>🛡️ Comisaría: $7,000 (+25 seguridad)</li>
<li>🚇 Subte: $20,000 (+50 transporte)</li>
<li>🏪 Kiosco: $800 (+5 felicidad, +2 empleo)</li>
</ul>
</div>
""")
# Funciones de construcción mejoradas
def build_structure(building_type, x, y):
return game.place_building(building_type, x, y)
# Conectar TODOS los botones
house_btn.click(fn=lambda x, y: build_structure("casa", x, y),
inputs=[grid_x_input, grid_y_input],
)