Lukeetah commited on
Commit
a32b0b9
·
verified ·
1 Parent(s): 1cb2dab

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +447 -668
app.py CHANGED
@@ -1,811 +1,590 @@
1
- # app.py - SIMCIUDAD ARGENTINA: REVOLUCIÓN GRÁFICA
2
  import gradio as gr
3
  import time
4
  import math
5
  import random
6
  import os
7
- from PIL import Image, ImageDraw, ImageFont
8
  import json
9
 
 
10
  try:
11
  from groq import Groq
12
  GROQ_AVAILABLE = True
 
13
  except ImportError:
14
  GROQ_AVAILABLE = False
 
15
 
16
- class ArgentineCityBuilder:
17
- """SimCity Argentino con mecánicas revolucionarias"""
18
  def __init__(self):
19
- self.width, self.height = 1000, 700
20
- self.grid_size = 20
21
- self.city_grid = {} # Coordenadas -> tipo de edificio
22
- self.population = 100
23
- self.happiness = 75
24
- self.economy = 1000
25
- self.inflation = 1.0
26
- self.cultural_identity = 90
27
 
28
- # Sistemas dinámicos
29
- self.neighborhoods = {}
30
- self.transport_network = {}
31
- self.cultural_centers = {}
32
- self.businesses = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- # Recursos complejos
35
  self.resources = {
36
- 'presupuesto': 50000,
37
  'energia': 100,
38
  'agua': 100,
39
  'seguridad': 70,
40
  'educacion': 60,
41
  'salud': 65,
42
  'cultura': 80,
43
- 'empleo': 75,
44
- 'vivienda': 50
45
  }
46
 
47
- # Edificios disponibles
48
  self.building_types = {
49
- 'casa': {'cost': 1000, 'pop': 4, 'happiness': 2},
50
- 'villa': {'cost': 500, 'pop': 8, 'happiness': -2, 'solidarity': 15},
51
- 'edificio': {'cost': 5000, 'pop': 20, 'happiness': 1},
52
- 'escuela': {'cost': 8000, 'education': 20, 'happiness': 10},
53
- 'hospital': {'cost': 15000, 'health': 30, 'happiness': 15},
54
- 'cancha': {'cost': 3000, 'happiness': 25, 'culture': 10},
55
- 'parrilla': {'cost': 2000, 'happiness': 20, 'culture': 15},
56
- 'centro_cultural': {'cost': 10000, 'culture': 40, 'education': 10},
57
- 'fabrica': {'cost': 12000, 'employment': 30, 'pollution': -10},
58
- 'plaza': {'cost': 1500, 'happiness': 15, 'culture': 5},
59
- 'subte': {'cost': 20000, 'transport': 50, 'happiness': 10},
60
- 'comisaria': {'cost': 7000, 'security': 25, 'happiness': -5},
61
- 'kiosco': {'cost': 800, 'happiness': 5, 'employment': 2}
62
  }
63
 
64
- # Inicializar ciudad base
65
- self._initialize_base_city()
66
-
67
- def _initialize_base_city(self):
68
- """Crea la ciudad inicial con algunos edificios"""
69
- # Plaza central
70
- self.city_grid[(25, 17)] = 'plaza'
71
-
72
- # Algunas casas iniciales
73
- initial_houses = [(20, 15), (30, 15), (25, 12), (25, 22)]
74
- for pos in initial_houses:
75
- self.city_grid[pos] = 'casa'
 
76
 
77
- # Una escuela inicial
78
- self.city_grid[(15, 20)] = 'escuela'
79
 
80
- class AdvancedCityRenderer:
81
- """Renderizador avanzado con gráficos isométricos"""
82
  def __init__(self):
 
83
  self.animation_time = 0
84
- self.tile_size = 20
85
 
86
- def render_city_frame(self, city_builder) -> Image.Image:
87
- """Renderiza la ciudad con vista isométrica mejorada"""
88
- img = Image.new('RGB', (city_builder.width, city_builder.height), (20, 40, 20))
 
89
  draw = ImageDraw.Draw(img)
90
 
91
  self.animation_time += 0.1
92
 
93
- # Fondo: Cielo dinámico según felicidad
94
- self._draw_dynamic_sky(draw, city_builder)
95
 
96
- # Grid base de la ciudad
97
- self._draw_city_grid(draw, city_builder)
98
 
99
- # Edificios con perspectiva isométrica
100
- self._draw_isometric_buildings(draw, city_builder)
101
 
102
- # Sistemas dinámicos (tráfico, personas, etc.)
103
- self._draw_city_life(draw, city_builder)
104
-
105
- # UI avanzada
106
- self._draw_advanced_ui(draw, city_builder)
107
-
108
- # Efectos especiales
109
- self._draw_special_effects(draw, city_builder)
110
 
111
  return img
112
 
113
- def _draw_dynamic_sky(self, draw, city):
114
- """Cielo que cambia según el estado de la ciudad"""
115
  happiness = city.happiness
116
 
117
- # Color base del cielo
118
  if happiness > 80:
119
- sky_color = (135, 206, 250) # Azul cielo feliz
 
120
  elif happiness > 60:
121
- sky_color = (176, 196, 222) # Azul grisáceo
122
- elif happiness > 40:
123
- sky_color = (119, 136, 153) # Gris claro
124
  else:
125
- sky_color = (105, 105, 105) # Gris oscuro
 
126
 
127
  # Gradiente de cielo
128
  for y in range(200):
129
- alpha = 1.0 - (y / 400)
130
- color = tuple(int(c * alpha + 20 * (1-alpha)) for c in sky_color)
131
- draw.rectangle([0, y, city.width, y+2], fill=color)
 
132
 
133
- # Nubes dinámicas
134
- cloud_offset = int(self.animation_time * 20) % city.width
135
- for i in range(3):
136
- x = (i * 300 + cloud_offset) % (city.width + 100)
137
- y = 50 + i * 30
138
- self._draw_cloud(draw, x, y, happiness)
139
 
140
- def _draw_cloud(self, draw, x, y, happiness):
141
- """Dibuja nube con forma según felicidad"""
142
- cloud_color = (255, 255, 255, 180) if happiness > 50 else (200, 200, 200, 150)
143
 
144
- # Forma de nube
145
- for i in range(5):
146
- offset_x = i * 15 + random.randint(-5, 5)
147
- offset_y = random.randint(-8, 8)
148
- size = 20 + random.randint(-5, 10)
149
- draw.ellipse([x + offset_x - size//2, y + offset_y - size//2,
150
- x + offset_x + size//2, y + offset_y + size//2],
151
- fill=cloud_color[:3])
152
-
153
- def _draw_city_grid(self, draw, city):
154
- """Grid isométrico de la ciudad"""
155
- grid_color = (60, 80, 60) if city.cultural_identity > 50 else (80, 80, 80)
156
 
157
- # Grid horizontal
158
- for y in range(0, city.height, self.tile_size):
159
- draw.line([0, y, city.width, y], fill=grid_color, width=1)
160
 
161
- # Grid vertical
162
- for x in range(0, city.width, self.tile_size):
163
- draw.line([x, 0, x, city.height], fill=grid_color, width=1)
 
 
 
 
164
 
165
- def _draw_isometric_buildings(self, draw, city):
166
- """Edificios con perspectiva isométrica"""
167
- for (grid_x, grid_y), building_type in city.city_grid.items():
168
  screen_x = grid_x * self.tile_size
169
- screen_y = grid_y * self.tile_size
170
 
171
- if building_type == 'casa':
172
- self._draw_house_isometric(draw, screen_x, screen_y, city.happiness)
173
- elif building_type == 'villa':
174
- self._draw_villa_house(draw, screen_x, screen_y, city.cultural_identity)
175
- elif building_type == 'edificio':
176
- self._draw_apartment_building(draw, screen_x, screen_y, city.economy)
177
- elif building_type == 'escuela':
178
- self._draw_school(draw, screen_x, screen_y, city.resources['educacion'])
179
- elif building_type == 'hospital':
180
- self._draw_hospital(draw, screen_x, screen_y, city.resources['salud'])
181
- elif building_type == 'cancha':
182
- self._draw_soccer_field(draw, screen_x, screen_y, city.happiness)
183
- elif building_type == 'parrilla':
184
- self._draw_parrilla(draw, screen_x, screen_y, city.cultural_identity)
185
- elif building_type == 'plaza':
186
- self._draw_plaza(draw, screen_x, screen_y, city.happiness)
187
- elif building_type == 'fabrica':
188
- self._draw_factory(draw, screen_x, screen_y, city.resources['empleo'])
189
- elif building_type == 'centro_cultural':
190
- self._draw_cultural_center(draw, screen_x, screen_y, city.resources['cultura'])
191
- elif building_type == 'kiosco':
192
- self._draw_kiosco(draw, screen_x, screen_y, city.economy)
193
 
194
- def _draw_house_isometric(self, draw, x, y, happiness):
195
- """Casa con perspectiva isométrica"""
196
- # Base de la casa
197
- house_color = (139, 69, 19) if happiness > 60 else (101, 67, 33)
198
-
199
- # Paredes (vista isométrica)
200
- wall_points = [
201
- (x + 2, y + 15), (x + 18, y + 15),
202
- (x + 18, y + 2), (x + 2, y + 2)
203
- ]
204
- draw.polygon(wall_points, fill=house_color, outline=(80, 40, 0), width=1)
205
-
206
- # Techo isométrico
207
- roof_color = (160, 82, 45) if happiness > 70 else (120, 60, 30)
208
- roof_points = [
209
- (x, y), (x + 10, y - 5), (x + 20, y), (x + 10, y + 5)
210
- ]
211
- draw.polygon(roof_points, fill=roof_color, outline=(100, 50, 25))
212
-
 
 
 
 
 
 
 
 
 
 
 
 
213
  # Puerta
214
- draw.rectangle([x + 8, y + 10, x + 12, y + 15], fill=(101, 67, 33))
215
-
216
- # Ventanas con luz según felicidad
217
- window_color = (255, 255, 200) if happiness > 50 else (150, 150, 150)
218
- draw.rectangle([x + 4, y + 8, x + 7, y + 11], fill=window_color, outline=(0, 0, 0))
219
- draw.rectangle([x + 13, y + 8, x + 16, y + 11], fill=window_color, outline=(0, 0, 0))
220
-
221
- # Jardincito si hay alta felicidad
222
- if happiness > 80:
223
- for i in range(3):
224
- flower_x = x + 2 + i * 4
225
- flower_y = y + 16
226
- draw.ellipse([flower_x, flower_y, flower_x + 2, flower_y + 2],
227
- fill=(255, 100, 150))
228
 
229
- def _draw_villa_house(self, draw, x, y, cultural_identity):
230
- """Casa de villa con colores vibrantes"""
231
- villa_colors = [(255, 100, 100), (100, 255, 100), (100, 100, 255),
232
- (255, 255, 100), (255, 100, 255)]
233
- color = random.choice(villa_colors)
234
-
235
- # Casa principal más grande
236
- draw.rectangle([x, y + 5, x + 20, y + 18], fill=color, outline=(0, 0, 0), width=2)
237
-
238
  # Techo de chapa
239
- draw.polygon([(x-2, y+5), (x+10, y-2), (x+22, y+5)], fill=(150, 150, 150))
240
-
241
- # Extensión autoconstruida
242
- if cultural_identity > 60:
243
- draw.rectangle([x + 16, y + 12, x + 25, y + 18], fill=color, outline=(0, 0, 0))
244
-
245
- # Antena parabólica
246
- draw.ellipse([x + 15, y - 1, x + 18, y + 2], outline=(200, 200, 200), width=2)
247
-
248
- # Tendedero con ropa
249
- draw.line([(x + 2, y + 3), (x + 18, y + 3)], fill=(100, 100, 100), width=1)
250
- for i in range(3):
251
- cloth_x = x + 4 + i * 5
252
- draw.rectangle([cloth_x, y + 3, cloth_x + 3, y + 8],
253
- fill=random.choice([(255, 0, 0), (0, 255, 0), (0, 0, 255)]))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
- def _draw_soccer_field(self, draw, x, y, happiness):
256
- """Cancha de fútbol con actividad"""
 
 
 
 
 
 
 
 
 
 
 
 
257
  # Césped
258
- grass_color = (50, 150, 50) if happiness > 60 else (80, 120, 80)
259
- draw.rectangle([x, y, x + 40, y + 25], fill=grass_color, outline=(255, 255, 255), width=2)
260
-
261
- # Líneas de la cancha
262
- draw.line([(x + 20, y), (x + 20, y + 25)], fill=(255, 255, 255), width=2)
263
- draw.ellipse([x + 15, y + 10, x + 25, y + 15], outline=(255, 255, 255), width=2)
264
-
265
  # Arcos
266
- draw.rectangle([x, y + 8, x + 3, y + 17], outline=(255, 255, 255), width=2)
267
- draw.rectangle([x + 37, y + 8, x + 40, y + 17], outline=(255, 255, 255), width=2)
268
-
269
- # Jugadores animados
270
- if happiness > 70:
271
- for i in range(6):
272
- player_x = x + 5 + i * 6 + int(3 * math.sin(self.animation_time + i))
273
- player_y = y + 8 + int(2 * math.cos(self.animation_time + i * 1.5))
274
- draw.ellipse([player_x, player_y, player_x + 3, player_y + 5],
275
- fill=(180, 140, 100))
276
-
277
  # Pelota animada
278
- ball_x = x + 20 + int(10 * math.sin(self.animation_time * 2))
279
- ball_y = y + 12 + int(5 * math.cos(self.animation_time * 2))
280
- draw.ellipse([ball_x, ball_y, ball_x + 3, ball_y + 3], fill=(255, 255, 255), outline=(0, 0, 0))
281
 
282
- def _draw_parrilla(self, draw, x, y, cultural_identity):
283
- """Parrilla argentina con humo"""
284
  # Base de la parrilla
285
- draw.rectangle([x + 5, y + 10, x + 15, y + 18], fill=(50, 50, 50), outline=(100, 100, 100))
286
-
287
- # Parrilla
288
- for i in range(3):
289
- draw.line([(x + 6 + i * 3, y + 12), (x + 6 + i * 3, y + 16)],
290
- fill=(150, 150, 150), width=2)
291
-
292
- # Carne en la parrilla
293
- if cultural_identity > 70:
294
- for i in range(2):
295
- meat_x = x + 7 + i * 4
296
- meat_y = y + 13
297
- draw.ellipse([meat_x, meat_y, meat_x + 3, meat_y + 2], fill=(139, 69, 19))
298
-
299
  # Humo animado
300
- for i in range(4):
301
- smoke_x = x + 10 + random.randint(-3, 3)
302
- smoke_y = y + 8 - i * 4 + int(2 * math.sin(self.animation_time * 2 + i))
303
- size = 3 + i
304
- draw.ellipse([smoke_x - size//2, smoke_y - size//2,
305
- smoke_x + size//2, smoke_y + size//2],
306
- fill=(200, 200, 200, 150 - i * 30))
307
-
308
- # Gente alrededor
309
- if cultural_identity > 60:
310
- for i in range(3):
311
- person_x = x + 2 + i * 8
312
- person_y = y + 15
313
- draw.ellipse([person_x, person_y, person_x + 4, person_y + 8],
314
- fill=(180, 140, 100))
315
 
316
- def _draw_plaza(self, draw, x, y, happiness):
317
- """Plaza con actividades"""
318
- # Base verde de la plaza
319
- plaza_color = (100, 180, 100) if happiness > 50 else (120, 140, 120)
320
- draw.rectangle([x, y, x + 30, y + 30], fill=plaza_color, outline=(80, 120, 80))
321
-
322
  # Senderos
323
- draw.line([(x, y + 15), (x + 30, y + 15)], fill=(160, 140, 120), width=3)
324
- draw.line([(x + 15, y), (x + 15, y + 30)], fill=(160, 140, 120), width=3)
325
-
326
- # Monumento central
327
- draw.rectangle([x + 12, y + 12, x + 18, y + 18], fill=(180, 180, 180), outline=(120, 120, 120))
328
-
329
  # Árboles
330
- for tree_pos in [(x + 5, y + 5), (x + 25, y + 5), (x + 5, y + 25), (x + 25, y + 25)]:
 
331
  # Tronco
332
- draw.rectangle([tree_pos[0], tree_pos[1] + 5, tree_pos[0] + 2, tree_pos[1] + 10],
333
- fill=(101, 67, 33))
334
  # Copa
335
- draw.ellipse([tree_pos[0] - 3, tree_pos[1], tree_pos[0] + 5, tree_pos[1] + 8],
336
- fill=(50, 150, 50))
337
-
338
- # Actividades según felicidad
339
- if happiness > 70:
340
- # Ronda de mate
341
- for i in range(4):
342
- angle = i * (math.pi / 2)
343
- person_x = x + 15 + int(8 * math.cos(angle))
344
- person_y = y + 15 + int(8 * math.sin(angle))
345
- draw.ellipse([person_x, person_y, person_x + 3, person_y + 5],
346
- fill=(180, 140, 100))
347
 
348
- def _draw_city_life(self, draw, city):
349
- """Sistemas dinámicos de vida urbana"""
350
- # Tráfico animado en calles principales
351
- if city.resources['transport'] > 30:
352
- self._draw_animated_traffic(draw, city)
353
-
354
- # Personas caminando
355
- if city.happiness > 40:
356
- self._draw_pedestrians(draw, city)
357
-
358
- # Efectos de contaminación
359
- if city.resources.get('pollution', 0) < -20:
360
- self._draw_pollution_effects(draw, city)
 
361
 
362
- def _draw_animated_traffic(self, draw, city):
363
- """Tráfico animado en las calles"""
364
- # Autobuses y colectivos
365
- for i in range(3):
366
- bus_x = (100 + i * 300 + int(self.animation_time * 50)) % city.width
367
- bus_y = 200 + i * 100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
369
- # Colectivo argentino
370
- draw.rectangle([bus_x, bus_y, bus_x + 25, bus_y + 12],
371
- fill=(255, 200, 0), outline=(0, 0, 0), width=2)
372
- # Ventanas
373
- for window in range(3):
374
- draw.rectangle([bus_x + 3 + window * 6, bus_y + 2,
375
- bus_x + 8 + window * 6, bus_y + 7],
376
- fill=(150, 200, 255))
377
- # Ruedas
378
- draw.ellipse([bus_x + 2, bus_y + 10, bus_x + 6, bus_y + 14], fill=(50, 50, 50))
379
- draw.ellipse([bus_x + 19, bus_y + 10, bus_x + 23, bus_y + 14], fill=(50, 50, 50))
380
-
381
- def _draw_pedestrians(self, draw, city):
382
- """Peatones animados"""
383
- for i in range(8):
384
- person_x = (50 + i * 120 + int(self.animation_time * 30 + i * 50)) % city.width
385
- person_y = 300 + i * 40 + int(5 * math.sin(self.animation_time + i))
386
 
387
- # Persona simple
388
- draw.ellipse([person_x, person_y, person_x + 4, person_y + 8],
389
- fill=(180, 140, 100))
390
 
391
- # Mate en la mano si alta cultura
392
- if city.cultural_identity > 70 and i % 3 == 0:
393
- draw.ellipse([person_x + 3, person_y + 2, person_x + 6, person_y + 5],
394
- fill=(100, 50, 0))
395
-
396
- def _draw_advanced_ui(self, draw, city):
397
- """UI avanzada con gráficos informativos"""
398
- # Panel principal con transparencia
399
- panel_width = 300
400
- panel_height = 200
401
-
402
- # Panel de fondo con gradiente
403
- for i in range(panel_height):
404
- alpha = int(200 * (1 - i/panel_height))
405
- draw.rectangle([10, 10 + i, 10 + panel_width, 11 + i],
406
- fill=(0, 0, 0, alpha))
407
-
408
- # Borde elegante
409
- draw.rectangle([10, 10, 10 + panel_width, 10 + panel_height],
410
- outline=(100, 150, 255), width=3)
411
-
412
- # Información de la ciudad
413
- info_y = 25
414
- city_info = [
415
- f"🏙️ Población: {city.population:,}",
416
- f"😊 Felicidad: {city.happiness}%",
417
- f"💰 Economía: ${city.economy:,}",
418
- f"🇦🇷 Identidad: {city.cultural_identity}%",
419
- f"📈 Inflación: {city.inflation:.1f}x"
420
- ]
421
-
422
- for info in city_info:
423
- # Sombra del texto
424
- info_y += 20
425
-
426
- # Barras de recursos en el lado derecho
427
- self._draw_resource_bars(draw, city, city.width - 250, 20)
428
-
429
- # Mini mapa en esquina inferior derecha
430
- self._draw_mini_map(draw, city, city.width - 150, city.height - 120)
431
-
432
- def _draw_resource_bars(self, draw, city, start_x, start_y):
433
- """Barras de recursos con efectos visuales"""
434
- resources = city.resources
435
- bar_width = 200
436
- bar_height = 15
437
-
438
- resource_colors = {
439
- 'presupuesto': (255, 215, 0), 'energia': (255, 255, 0),
440
- 'agua': (0, 191, 255), 'seguridad': (255, 99, 71),
441
- 'educacion': (147, 112, 219), 'salud': (255, 20, 147),
442
- 'cultura': (255, 140, 0), 'empleo': (50, 205, 50),
443
- 'vivienda': (139, 69, 19)
444
- }
445
-
446
- y_offset = start_y
447
- for resource, value in resources.items():
448
- if resource == 'presupuesto':
449
- # Presupuesto se muestra diferente
450
- budget_text = f"💰 ${value:,}"
451
- y_offset += 25
452
- continue
453
 
454
- # Normalizar valor para barra (0-100)
455
- normalized_value = max(0, min(100, value)) if isinstance(value, (int, float)) else 50
 
456
 
457
- # Barra de fondo
458
- draw.rectangle([start_x, y_offset, start_x + bar_width, y_offset + bar_height],
459
- fill=(40, 40, 40), outline=(100, 100, 100))
 
 
 
 
 
 
460
 
461
- # Barra de valor con gradiente
462
- fill_width = int((normalized_value / 100) * (bar_width - 4))
463
- color = resource_colors.get(resource, (150, 150, 150))
 
 
 
 
464
 
465
- # Efecto de brillo
466
- for i in range(fill_width):
467
- brightness = 0.7 + 0.3 * math.sin(self.animation_time + i/10)
468
- bright_color = tuple(int(c * brightness) for c in color)
469
- draw.rectangle([start_x + 2 + i, y_offset + 2,
470
- start_x + 3 + i, y_offset + bar_height - 2],
471
- fill=bright_color)
472
 
473
- # Etiqueta del recurso
474
- resource_names = {
475
- 'energia': '⚡ Energía', 'agua': '💧 Agua', 'seguridad': '🛡️ Seguridad',
476
- 'educacion': '📚 Educación', 'salud': '🏥 Salud', 'cultura': '🎭 Cultura',
477
- 'empleo': '💼 Empleo', 'vivienda': '🏠 Vivienda'
478
- }
479
 
480
- y_offset += 22
 
481
 
482
- def _draw_mini_map(self, draw, city, start_x, start_y):
483
- """Mini mapa de la ciudad"""
484
- map_width, map_height = 120, 100
485
-
486
- # Fondo del mini mapa
487
- draw.rectangle([start_x, start_y, start_x + map_width, start_y + map_height],
488
- fill=(20, 20, 20), outline=(150, 150, 150), width=2)
489
-
490
- # Edificios en el mini mapa
491
- scale = 2
492
- for (grid_x, grid_y), building_type in city.city_grid.items():
493
- mini_x = start_x + (grid_x * scale) % map_width
494
- mini_y = start_y + (grid_y * scale) % map_height
495
 
496
- building_colors = {
497
- 'casa': (100, 150, 100), 'villa': (255, 150, 100),
498
- 'edificio': (150, 150, 200), 'escuela': (200, 200, 100),
499
- 'hospital': (255, 100, 100), 'cancha': (100, 255, 100),
500
- 'plaza': (150, 255, 150), 'fabrica': (150, 100, 100)
501
- }
502
 
503
- color = building_colors.get(building_type, (100, 100, 100))
504
- draw.rectangle([mini_x, mini_y, mini_x + scale, mini_y + scale], fill=color)
505
-
506
- class EnhancedCityGame:
507
- """Juego de ciudad argentino mejorado"""
508
- def __init__(self):
509
- self.city = ArgentineCityBuilder()
510
- self.renderer = AdvancedCityRenderer()
511
- self.selected_building = None
512
- self.game_speed = 1
513
- self.events_log = []
514
-
515
- def place_building(self, building_type, grid_x, grid_y):
516
- """Coloca un edificio en la ciudad"""
517
- if building_type not in self.city.building_types:
518
- return "Tipo de edificio inválido"
519
-
520
- building_info = self.city.building_types[building_type]
521
- cost = building_info['cost']
522
-
523
- # Verificar si se puede pagar
524
- if self.city.resources['presupuesto'] < cost:
525
- return f"No tenés suficiente presupuesto. Necesitás ${cost:,}"
526
-
527
- # Verificar si la posición está libre
528
- if (grid_x, grid_y) in self.city.city_grid:
529
- return "Ya hay un edificio en esa posición"
530
-
531
- # Construir edificio
532
- self.city.resources['presupuesto'] -= cost
533
- self.city.city_grid[(grid_x, grid_y)] = building_type
534
-
535
- # Aplicar efectos del edificio
536
- self._apply_building_effects(building_type, building_info)
537
-
538
- # Actualizar estadísticas de la ciudad
539
- self._update_city_stats()
540
-
541
- return f"✅ {building_type.replace('_', ' ').title()} construido por ${cost:,}"
542
-
543
- def _apply_building_effects(self, building_type, building_info):
544
- """Aplica los efectos de un edificio a la ciudad"""
545
- for effect, value in building_info.items():
546
- if effect == 'cost':
547
- continue
548
- elif effect == 'pop':
549
- self.city.population += value
550
- elif effect == 'happiness':
551
- self.city.happiness = min(100, max(0, self.city.happiness + value))
552
- elif effect == 'solidarity':
553
- self.city.cultural_identity = min(100, max(0, self.city.cultural_identity + value))
554
- elif effect in self.city.resources:
555
- current_value = self.city.resources[effect]
556
- self.city.resources[effect] = min(100, max(0, current_value + value))
557
-
558
- def _update_city_stats(self):
559
- """Actualiza estadísticas generales de la ciudad"""
560
- # Calcular economía basada en edificios
561
- economy_buildings = ['fabrica', 'kiosco', 'centro_cultural']
562
- economy_bonus = sum(10000 for pos, building in self.city.city_grid.items()
563
- if building in economy_buildings)
564
- self.city.economy = 1000 + economy_bonus + self.city.population * 50
565
-
566
- # Inflación aumenta con el tiempo y población
567
- self.city.inflation += 0.01 + (len(self.city.city_grid) * 0.001)
568
-
569
- # Felicidad general
570
- happiness_factors = [
571
- self.city.resources.get('cultura', 50),
572
- self.city.resources.get('seguridad', 50),
573
- self.city.resources.get('empleo', 50),
574
- self.city.cultural_identity
575
- ]
576
- self.city.happiness = sum(happiness_factors) // len(happiness_factors)
577
-
578
- def simulate_day(self):
579
- """Simula un día en la ciudad"""
580
- # Ingresos diarios
581
- daily_income = self.city.population * 10 + self.city.economy * 0.01
582
- self.city.resources['presupuesto'] += int(daily_income)
583
-
584
- # Gastos de mantenimiento
585
- maintenance_cost = len(self.city.city_grid) * 100
586
- self.city.resources['presupuesto'] -= maintenance_cost
587
-
588
- # Efectos de la inflación
589
- self.city.resources['presupuesto'] = int(self.city.resources['presupuesto'] / self.city.inflation)
590
-
591
- # Eventos aleatorios
592
- if random.random() < 0.2:
593
- self._trigger_city_event()
594
-
595
- # Actualizar todo
596
- self._update_city_stats()
597
-
598
- return f"💰 Ingresos: ${daily_income:,.0f} | 💸 Gastos: ${maintenance_cost:,} | 📈 Inflación: {self.city.inflation:.2f}x"
599
-
600
- def _trigger_city_event(self):
601
- """Eventos aleatorios de la ciudad"""
602
- events = [
603
- ("🎉 Festival de Tango", "cultura", 15, "happiness", 20),
604
- ("⚽ River vs Boca", "happiness", 30, "cultura", 10),
605
- ("🏭 Nueva fábrica privada", "empleo", 20, "presupuesto", 5000),
606
- ("🌧️ Lluvia torrencial", "agua", 25, "energia", -10),
607
- ("📚 Programa de alfabetización", "educacion", 15, "cultura", 10),
608
- ("🏥 Campaña de vacunación", "salud", 20, "happiness", 15),
609
- ("💸 Crisis económica menor", "presupuesto", -3000, "happiness", -10),
610
- ("🎭 Nuevo centro cultural", "cultura", 25, "educacion", 10)
611
- ]
612
-
613
- event_name, res1, val1, res2, val2 = random.choice(events)
614
-
615
- # Aplicar efectos
616
- if res1 == 'presupuesto':
617
- self.city.resources[res1] += val1
618
- else:
619
- self.city.resources[res1] = min(100, max(0, self.city.resources[res1] + val1))
620
-
621
- if res2 == 'presupuesto':
622
- self.city.resources[res2] += val2
623
- elif res2 == 'happiness':
624
- self.city.happiness = min(100, max(0, self.city.happiness + val2))
625
- else:
626
- self.city.resources[res2] = min(100, max(0, self.city.resources[res2] + val2))
627
-
628
- self.events_log.append(event_name)
629
- return event_name
630
 
631
- def get_city_status(self):
632
- """Status completo de la ciudad"""
633
- return {
634
- "🏙️ Población": f"{self.city.population:,} habitantes",
635
- "😊 Felicidad": f"{self.city.happiness}%",
636
- "💰 Presupuesto": f"${self.city.resources['presupuesto']:,}",
637
- "📊 Economía": f"${self.city.economy:,}",
638
- "🇦🇷 Identidad": f"{self.city.cultural_identity}%",
639
- "📈 Inflación": f"{self.city.inflation:.2f}x",
640
- "🏗️ Edificios": f"{len(self.city.city_grid)} construidos",
641
- "⚡ Recursos": f"Energía {self.city.resources['energia']}% | Agua {self.city.resources['agua']}%"
642
- }
 
 
 
 
643
 
644
  def create_simcity_interface():
645
- """Interfaz del SimCity Argentino"""
646
- game = EnhancedCityGame()
647
 
648
  with gr.Blocks(
649
- title="🇦🇷 SIMCIUDAD ARGENTINA - Construcción Épica",
650
- theme=gr.themes.Glass(),
651
- css="""
652
- .building-btn { font-size: 1.1em; padding: 10px; margin: 3px; }
653
- .city-header { background: linear-gradient(45deg, #74b9ff, #0984e3, #00b894); }
654
- """
655
  ) as interface:
656
 
657
  gr.HTML("""
658
- <div class="city-header" style="padding: 25px; text-align: center; border-radius: 15px; margin: 15px;">
659
- <h1 style="color: white; font-size: 3em; margin: 0; text-shadow: 3px 3px 6px rgba(0,0,0,0.7);">
 
660
  🏙️ SIMCIUDAD ARGENTINA 🇦🇷
661
  </h1>
662
- <h2 style="color: #ddd; font-size: 1.3em; margin: 10px 0;">
663
- Construí la Ciudad Argentina de tus Sueños
664
- </h2>
665
- <p style="color: #f1f2f6; font-size: 1.1em;">
666
- Gráficos Isométricos • Mecánicas Realistas • Cultura Auténtica
667
  </p>
668
  </div>
669
  """)
670
 
671
  with gr.Row():
672
  with gr.Column(scale=2):
 
673
  city_display = gr.Image(
674
  label="🌆 Tu Ciudad Argentina",
675
  width=1000,
676
- height=700
 
677
  )
678
 
679
  gr.Markdown("### 🏗️ HERRAMIENTAS DE CONSTRUCCIÓN")
 
680
  with gr.Row():
681
- # Coordenadas para construcción
682
- grid_x_input = gr.Number(label="Coordenada X", value=20, minimum=0, maximum=50)
683
- grid_y_input = gr.Number(label="Coordenada Y", value=20, minimum=0, maximum=35)
684
 
 
685
  with gr.Row():
686
- house_btn = gr.Button("🏠 CASA", variant="secondary", elem_classes="building-btn")
687
- villa_btn = gr.Button("🏘️ VILLA", variant="secondary", elem_classes="building-btn")
688
- building_btn = gr.Button("🏢 EDIFICIO", variant="primary", elem_classes="building-btn")
689
 
690
  with gr.Row():
691
- school_btn = gr.Button("🏫 ESCUELA", variant="primary", elem_classes="building-btn")
692
- hospital_btn = gr.Button("🏥 HOSPITAL", variant="stop", elem_classes="building-btn")
693
- field_btn = gr.Button("⚽ CANCHA", variant="secondary", elem_classes="building-btn")
694
 
695
  with gr.Row():
696
- grill_btn = gr.Button("🔥 PARRILLA", variant="stop", elem_classes="building-btn")
697
- plaza_btn = gr.Button("🌳 PLAZA", variant="secondary", elem_classes="building-btn")
698
- factory_btn = gr.Button("🏭 FÁBRICA", variant="primary", elem_classes="building-btn")
699
 
 
700
  with gr.Row():
701
- simulate_btn = gr.Button("⏭️ SIMULAR DÍA", variant="primary", size="lg")
702
 
703
  with gr.Column(scale=1):
 
704
  gr.Markdown("### 📊 ESTADO DE LA CIUDAD")
705
- city_status = gr.JSON(
706
  label="📈 Estadísticas",
707
- value=game.get_city_status()
708
  )
709
 
 
710
  gr.Markdown("### 📰 EVENTOS RECIENTES")
711
- events_display = gr.Textbox(
712
- value="🎮 ¡Bienvenido al SimCity Argentino! Construí tu ciudad con identidad nacional.",
713
  label="📺 Noticias Urbanas",
 
714
  lines=5,
715
  interactive=False
716
  )
717
 
718
- gr.Markdown("### 💰 COSTOS DE CONSTRUCCIÓN")
 
719
  gr.HTML("""
720
- <div style="background: #2d3436; color: white; padding: 15px; border-radius: 10px;">
721
- <p><strong>💵 Precios:</strong></p>
722
- <ul style="font-size: 0.9em; margin: 5px 0; padding-left: 15px;">
723
- <li>🏠 Casa: $1,000</li>
724
- <li>🏘️ Villa: $500</li>
725
- <li>🏢 Edificio: $5,000</li>
726
- <li>🏫 Escuela: $8,000</li>
727
- <li>🏥 Hospital: $15,000</li>
728
- <li>⚽ Cancha: $3,000</li>
729
- <li>🔥 Parrilla: $2,000</li>
730
- <li>🌳 Plaza: $1,500</li>
731
- <li>🏭 Fábrica: $12,000</li>
732
  </ul>
733
  </div>
734
  """)
735
 
736
- # Funciones de construcción
737
  def build_structure(building_type, x, y):
738
- message = game.place_building(building_type, int(x), int(y))
739
- frame = game.renderer.render_city_frame(game.city)
740
- status = game.get_city_status()
741
- return frame, status, message
742
 
743
  # Conectar botones
744
- house_btn.click(
745
- fn=lambda x, y: build_structure("casa", x, y),
746
- inputs=[grid_x_input, grid_y_input],
747
- outputs=[city_display, city_status, events_display]
748
- )
749
- villa_btn.click(
750
- fn=lambda x, y: build_structure("villa", x, y),
751
- inputs=[grid_x_input, grid_y_input],
752
- outputs=[city_display, city_status, events_display]
753
- )
754
- building_btn.click(
755
- fn=lambda x, y: build_structure("edificio", x, y),
756
- inputs=[grid_x_input, grid_y_input],
757
- outputs=[city_display, city_status, events_display]
758
- )
759
- school_btn.click(
760
- fn=lambda x, y: build_structure("escuela", x, y),
761
- inputs=[grid_x_input, grid_y_input],
762
- outputs=[city_display, city_status, events_display]
763
- )
764
- hospital_btn.click(
765
- fn=lambda x, y: build_structure("hospital", x, y),
766
- inputs=[grid_x_input, grid_y_input],
767
- outputs=[city_display, city_status, events_display]
768
- )
769
- field_btn.click(
770
- fn=lambda x, y: build_structure("cancha", x, y),
771
- inputs=[grid_x_input, grid_y_input],
772
- outputs=[city_display, city_status, events_display]
773
- )
774
- grill_btn.click(
775
- fn=lambda x, y: build_structure("parrilla", x, y),
776
- inputs=[grid_x_input, grid_y_input],
777
- outputs=[city_display, city_status, events_display]
778
- )
779
- plaza_btn.click(
780
- fn=lambda x, y: build_structure("plaza", x, y),
781
- inputs=[grid_x_input, grid_y_input],
782
- outputs=[city_display, city_status, events_display]
783
- )
784
- factory_btn.click(
785
- fn=lambda x, y: build_structure("fabrica", x, y),
786
- inputs=[grid_x_input, grid_y_input],
787
- outputs=[city_display, city_status, events_display]
788
- )
789
-
790
- # Simulación de día
791
- simulate_btn.click(
792
- fn=lambda: (
793
- game.renderer.render_city_frame(game.city),
794
- game.get_city_status(),
795
- game.simulate_day()
796
- ),
797
- outputs=[city_display, city_status, events_display]
798
- )
799
-
800
- # Estado inicial
801
- interface.load(
802
- fn=lambda: (
803
- game.renderer.render_city_frame(game.city),
804
- game.get_city_status(),
805
- "🎮 ¡SimCiudad Argentina iniciado! Empezá construyendo casas y escuelas."
806
- ),
807
- outputs=[city_display, city_status, events_display]
808
- )
809
 
810
  return interface
811
 
 
1
+ # app.py - SIMCIUDAD ARGENTINA FUNCIONAL Y ÉPICO
2
  import gradio as gr
3
  import time
4
  import math
5
  import random
6
  import os
7
+ from PIL import Image, ImageDraw
8
  import json
9
 
10
+ # Groq AI Integration
11
  try:
12
  from groq import Groq
13
  GROQ_AVAILABLE = True
14
+ groq_client = Groq(api_key=os.getenv("GROQ_API_KEY"))
15
  except ImportError:
16
  GROQ_AVAILABLE = False
17
+ groq_client = None
18
 
19
+ class AIUrbanPlanner:
20
+ """IA para planificación urbana argentina"""
21
  def __init__(self):
22
+ self.enabled = GROQ_AVAILABLE and groq_client is not None
23
+
24
+ def generate_city_advice(self, city_state):
25
+ """Genera consejos de planificación urbana"""
26
+ if not self.enabled:
27
+ return "Construí más plazas para aumentar la felicidad de los vecinos."
 
 
28
 
29
+ try:
30
+ prompt = f"""
31
+ Sos un urbanista argentino experto. Analizá esta ciudad y dá consejos breves:
32
+
33
+ Población: {city_state.get('population', 0)}
34
+ Felicidad: {city_state.get('happiness', 0)}%
35
+ Presupuesto: ${city_state.get('budget', 0)}
36
+ Edificios: {city_state.get('buildings', 0)}
37
+
38
+ Dá UN consejo específico y argentino en 1-2 líneas máximo.
39
+ """
40
+
41
+ response = groq_client.chat.completions.create(
42
+ messages=[{"role": "user", "content": prompt}],
43
+ model="llama-3.1-8b-instant",
44
+ temperature=0.7,
45
+ max_tokens=80
46
+ )
47
+
48
+ return response.choices[0].message.content.strip()
49
+ except:
50
+ return "La ciudad necesita más espacios verdes para el bienestar."
51
+
52
+ class ArgentineCityEngine:
53
+ """Motor de la ciudad argentina"""
54
+ def __init__(self):
55
+ # Estado básico de la ciudad
56
+ self.population = 150
57
+ self.happiness = 75
58
+ self.budget = 25000
59
+ self.buildings = {}
60
+ self.grid_size = 30
61
 
62
+ # Recursos
63
  self.resources = {
 
64
  'energia': 100,
65
  'agua': 100,
66
  'seguridad': 70,
67
  'educacion': 60,
68
  'salud': 65,
69
  'cultura': 80,
70
+ 'empleo': 75
 
71
  }
72
 
73
+ # Tipos de edificios
74
  self.building_types = {
75
+ 'casa': {'cost': 1000, 'color': (139, 69, 19), 'pop': 4, 'happiness': 2},
76
+ 'villa': {'cost': 500, 'color': (255, 150, 100), 'pop': 8, 'happiness': -2},
77
+ 'edificio': {'cost': 5000, 'color': (150, 150, 200), 'pop': 20, 'happiness': 1},
78
+ 'escuela': {'cost': 8000, 'color': (255, 255, 100), 'educacion': 20, 'happiness': 10},
79
+ 'hospital': {'cost': 15000, 'color': (255, 100, 100), 'salud': 30, 'happiness': 15},
80
+ 'cancha': {'cost': 3000, 'color': (100, 255, 100), 'happiness': 25, 'cultura': 10},
81
+ 'parrilla': {'cost': 2000, 'color': (200, 100, 50), 'happiness': 20, 'cultura': 15},
82
+ 'plaza': {'cost': 1500, 'color': (150, 255, 150), 'happiness': 15, 'cultura': 5},
83
+ 'fabrica': {'cost': 12000, 'color': (150, 100, 100), 'empleo': 30, 'happiness': -5}
 
 
 
 
84
  }
85
 
86
+ # Inicializar con algunos edificios
87
+ self._initialize_city()
88
+
89
+ def _initialize_city(self):
90
+ """Inicializa la ciudad con edificios básicos"""
91
+ initial_buildings = [
92
+ (15, 15, 'plaza'),
93
+ (12, 12, 'casa'),
94
+ (18, 12, 'casa'),
95
+ (12, 18, 'casa'),
96
+ (18, 18, 'casa'),
97
+ (10, 15, 'escuela')
98
+ ]
99
 
100
+ for x, y, building_type in initial_buildings:
101
+ self.buildings[(x, y)] = building_type
102
 
103
+ class CityRenderer:
104
+ """Renderizador de la ciudad"""
105
  def __init__(self):
106
+ self.tile_size = 25
107
  self.animation_time = 0
 
108
 
109
+ def render_city(self, city_engine) -> Image.Image:
110
+ """Renderiza la ciudad completa"""
111
+ width, height = 1000, 700
112
+ img = Image.new('RGB', (width, height), (30, 50, 30))
113
  draw = ImageDraw.Draw(img)
114
 
115
  self.animation_time += 0.1
116
 
117
+ # Fondo de la ciudad
118
+ self._draw_background(draw, width, height, city_engine)
119
 
120
+ # Grid de calles
121
+ self._draw_streets(draw, width, height)
122
 
123
+ # Edificios
124
+ self._draw_buildings(draw, city_engine)
125
 
126
+ # Efectos dinámicos
127
+ self._draw_city_effects(draw, city_engine, width, height)
 
 
 
 
 
 
128
 
129
  return img
130
 
131
+ def _draw_background(self, draw, width, height, city):
132
+ """Fondo dinámico según felicidad"""
133
  happiness = city.happiness
134
 
135
+ # Color del cielo según felicidad
136
  if happiness > 80:
137
+ sky_start = (135, 206, 250)
138
+ sky_end = (100, 149, 237)
139
  elif happiness > 60:
140
+ sky_start = (119, 136, 153)
141
+ sky_end = (105, 105, 105)
 
142
  else:
143
+ sky_start = (70, 70, 70)
144
+ sky_end = (50, 50, 50)
145
 
146
  # Gradiente de cielo
147
  for y in range(200):
148
+ ratio = y / 200
149
+ color = tuple(int(start * (1 - ratio) + end * ratio)
150
+ for start, end in zip(sky_start, sky_end))
151
+ draw.rectangle([0, y, width, y + 1], fill=color)
152
 
153
+ # Base verde
154
+ draw.rectangle([0, 200, width, height], fill=(40, 80, 40))
 
 
 
 
155
 
156
+ def _draw_streets(self, draw, width, height):
157
+ """Calles de la ciudad"""
158
+ street_color = (80, 80, 80)
159
 
160
+ # Calles horizontales
161
+ for y in range(200, height, self.tile_size * 3):
162
+ draw.rectangle([0, y, width, y + self.tile_size], fill=street_color)
 
 
 
 
 
 
 
 
 
163
 
164
+ # Calles verticales
165
+ for x in range(0, width, self.tile_size * 3):
166
+ draw.rectangle([x, 200, x + self.tile_size, height], fill=street_color)
167
 
168
+ # Líneas divisorias
169
+ line_color = (255, 255, 255)
170
+ for y in range(200, height, self.tile_size):
171
+ draw.line([(0, y), (width, y)], fill=(60, 80, 60), width=1)
172
+
173
+ for x in range(0, width, self.tile_size):
174
+ draw.line([(x, 200), (x, height)], fill=(60, 80, 60), width=1)
175
 
176
+ def _draw_buildings(self, draw, city):
177
+ """Dibuja todos los edificios"""
178
+ for (grid_x, grid_y), building_type in city.buildings.items():
179
  screen_x = grid_x * self.tile_size
180
+ screen_y = 200 + grid_y * self.tile_size
181
 
182
+ if building_type in city.building_types:
183
+ self._draw_single_building(draw, screen_x, screen_y, building_type, city)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
+ def _draw_single_building(self, draw, x, y, building_type, city):
186
+ """Dibuja un edificio específico"""
187
+ building_info = city.building_types[building_type]
188
+ color = building_info['color']
189
+
190
+ if building_type == 'casa':
191
+ self._draw_house(draw, x, y, color)
192
+ elif building_type == 'villa':
193
+ self._draw_villa(draw, x, y, color)
194
+ elif building_type == 'edificio':
195
+ self._draw_apartment(draw, x, y, color)
196
+ elif building_type == 'escuela':
197
+ self._draw_school(draw, x, y, color)
198
+ elif building_type == 'hospital':
199
+ self._draw_hospital(draw, x, y, color)
200
+ elif building_type == 'cancha':
201
+ self._draw_field(draw, x, y, color)
202
+ elif building_type == 'parrilla':
203
+ self._draw_grill(draw, x, y, color)
204
+ elif building_type == 'plaza':
205
+ self._draw_plaza(draw, x, y, color)
206
+ elif building_type == 'fabrica':
207
+ self._draw_factory(draw, x, y, color)
208
+
209
+ def _draw_house(self, draw, x, y, color):
210
+ """Casa argentina típica"""
211
+ # Base
212
+ draw.rectangle([x + 2, y + 8, x + 22, y + 23], fill=color, outline=(0, 0, 0), width=1)
213
+ # Techo
214
+ draw.polygon([(x + 1, y + 8), (x + 12, y + 2), (x + 23, y + 8)],
215
+ fill=(160, 82, 45), outline=(100, 50, 25))
216
  # Puerta
217
+ draw.rectangle([x + 10, y + 15, x + 14, y + 23], fill=(101, 67, 33))
218
+ # Ventanas
219
+ draw.rectangle([x + 5, y + 12, x + 8, y + 15], fill=(200, 200, 255), outline=(0, 0, 0))
220
+ draw.rectangle([x + 16, y + 12, x + 19, y + 15], fill=(200, 200, 255), outline=(0, 0, 0))
 
 
 
 
 
 
 
 
 
 
221
 
222
+ def _draw_villa(self, draw, x, y, color):
223
+ """Casa de villa colorida"""
224
+ # Casa principal
225
+ draw.rectangle([x + 1, y + 10, x + 23, y + 23], fill=color, outline=(0, 0, 0), width=2)
 
 
 
 
 
226
  # Techo de chapa
227
+ draw.polygon([(x, y + 10), (x + 12, y + 5), (x + 24, y + 10)], fill=(150, 150, 150))
228
+ # Extensión
229
+ draw.rectangle([x + 18, y + 15, x + 24, y + 23], fill=color, outline=(0, 0, 0))
230
+ # Antena
231
+ draw.line([(x + 20, y + 5), (x + 20, y + 1)], fill=(200, 200, 200), width=2)
232
+ draw.ellipse([x + 18, y + 1, x + 22, y + 3], outline=(200, 200, 200))
233
+
234
+ def _draw_apartment(self, draw, x, y, color):
235
+ """Edificio de departamentos"""
236
+ # Estructura principal más alta
237
+ draw.rectangle([x + 1, y + 0, x + 23, y + 23], fill=color, outline=(0, 0, 0), width=1)
238
+ # Ventanas en pisos
239
+ for floor in range(3):
240
+ floor_y = y + 5 + floor * 6
241
+ for window in range(3):
242
+ window_x = x + 4 + window * 6
243
+ draw.rectangle([window_x, floor_y, window_x + 3, floor_y + 3],
244
+ fill=(255, 255, 200), outline=(0, 0, 0))
245
+
246
+ def _draw_school(self, draw, x, y, color):
247
+ """Escuela con bandera argentina"""
248
+ # Edificio principal
249
+ draw.rectangle([x + 1, y + 8, x + 23, y + 23], fill=color, outline=(0, 0, 0), width=2)
250
+ # Bandera argentina
251
+ draw.rectangle([x + 20, y + 2, x + 22, y + 8], fill=(100, 150, 255)) # Azul
252
+ draw.rectangle([x + 20, y + 4, x + 22, y + 6], fill=(255, 255, 255)) # Blanco
253
+ # Asta
254
+ draw.line([(x + 22, y + 8), (x + 22, y + 2)], fill=(139, 69, 19), width=2)
255
+ # Patio
256
+ draw.rectangle([x + 5, y + 12, x + 19, y + 20], fill=(200, 200, 200), outline=(150, 150, 150))
257
 
258
+ def _draw_hospital(self, draw, x, y, color):
259
+ """Hospital con cruz roja"""
260
+ # Edificio
261
+ draw.rectangle([x + 1, y + 5, x + 23, y + 23], fill=color, outline=(0, 0, 0), width=2)
262
+ # Cruz roja
263
+ draw.rectangle([x + 10, y + 8, x + 14, y + 18], fill=(255, 0, 0))
264
+ draw.rectangle([x + 7, y + 11, x + 17, y + 15], fill=(255, 0, 0))
265
+ # Ambulancia
266
+ draw.rectangle([x + 2, y + 19, x + 10, y + 23], fill=(255, 255, 255), outline=(0, 0, 0))
267
+ draw.ellipse([x + 3, y + 21, x + 5, y + 23], fill=(0, 0, 0))
268
+ draw.ellipse([x + 7, y + 21, x + 9, y + 23], fill=(0, 0, 0))
269
+
270
+ def _draw_field(self, draw, x, y, color):
271
+ """Cancha de fútbol"""
272
  # Césped
273
+ draw.rectangle([x, y + 5, x + 24, y + 23], fill=color, outline=(255, 255, 255), width=2)
274
+ # Línea del medio
275
+ draw.line([(x + 12, y + 5), (x + 12, y + 23)], fill=(255, 255, 255), width=2)
276
+ # Círculo central
277
+ draw.ellipse([x + 8, y + 11, x + 16, y + 17], outline=(255, 255, 255), width=2)
 
 
278
  # Arcos
279
+ draw.rectangle([x, y + 10, x + 3, y + 18], outline=(255, 255, 255), width=2)
280
+ draw.rectangle([x + 21, y + 10, x + 24, y + 18], outline=(255, 255, 255), width=2)
 
 
 
 
 
 
 
 
 
281
  # Pelota animada
282
+ ball_x = x + 12 + int(5 * math.sin(self.animation_time))
283
+ ball_y = y + 14
284
+ draw.ellipse([ball_x, ball_y, ball_x + 2, ball_y + 2], fill=(255, 255, 255), outline=(0, 0, 0))
285
 
286
+ def _draw_grill(self, draw, x, y, color):
287
+ """Parrilla con humo"""
288
  # Base de la parrilla
289
+ draw.rectangle([x + 8, y + 15, x + 16, y + 23], fill=(50, 50, 50), outline=(100, 100, 100))
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  # Humo animado
291
+ for i in range(3):
292
+ smoke_x = x + 12 + random.randint(-2, 2)
293
+ smoke_y = y + 15 - i * 5 + int(2 * math.sin(self.animation_time + i))
294
+ size = 2 + i
295
+ draw.ellipse([smoke_x - size, smoke_y - size, smoke_x + size, smoke_y + size],
296
+ fill=(180, 180, 180))
297
+ # Personas alrededor
298
+ for person_pos in [(x + 4, y + 18), (x + 18, y + 18)]:
299
+ draw.ellipse([person_pos[0], person_pos[1], person_pos[0] + 3, person_pos[1] + 5],
300
+ fill=(180, 140, 100))
 
 
 
 
 
301
 
302
+ def _draw_plaza(self, draw, x, y, color):
303
+ """Plaza con árboles"""
304
+ # Base verde
305
+ draw.rectangle([x + 1, y + 8, x + 23, y + 23], fill=color, outline=(100, 150, 100))
 
 
306
  # Senderos
307
+ draw.line([(x + 1, y + 15), (x + 23, y + 15)], fill=(160, 140, 120), width=3)
308
+ draw.line([(x + 12, y + 8), (x + 12, y + 23)], fill=(160, 140, 120), width=3)
 
 
 
 
309
  # Árboles
310
+ tree_positions = [(x + 5, y + 11), (x + 19, y + 11), (x + 5, y + 19), (x + 19, y + 19)]
311
+ for tree_x, tree_y in tree_positions:
312
  # Tronco
313
+ draw.rectangle([tree_x, tree_y + 3, tree_x + 2, tree_y + 8], fill=(101, 67, 33))
 
314
  # Copa
315
+ draw.ellipse([tree_x - 2, tree_y, tree_x + 4, tree_y + 6], fill=(50, 150, 50))
 
 
 
 
 
 
 
 
 
 
 
316
 
317
+ def _draw_factory(self, draw, x, y, color):
318
+ """Fábrica con chimeneas"""
319
+ # Edificio principal
320
+ draw.rectangle([x + 1, y + 8, x + 23, y + 23], fill=color, outline=(0, 0, 0), width=2)
321
+ # Chimeneas
322
+ for chimney_x in [x + 6, x + 12, x + 18]:
323
+ draw.rectangle([chimney_x, y + 2, chimney_x + 3, chimney_x + 8], fill=(80, 80, 80))
324
+ # Humo
325
+ smoke_y = y + 2 - int(3 * math.sin(self.animation_time))
326
+ draw.ellipse([chimney_x, smoke_y - 2, chimney_x + 3, smoke_y + 1], fill=(150, 150, 150))
327
+ # Ventanas industriales
328
+ for window_x in range(x + 4, x + 20, 4):
329
+ draw.rectangle([window_x, y + 12, window_x + 2, window_x + 15],
330
+ fill=(255, 200, 100), outline=(0, 0, 0))
331
 
332
+ def _draw_city_effects(self, draw, city, width, height):
333
+ """Efectos dinámicos de la ciudad"""
334
+ # Tráfico en las calles
335
+ if city.happiness > 50:
336
+ for i in range(3):
337
+ car_x = (100 + i * 200 + int(self.animation_time * 30)) % width
338
+ car_y = 220 + i * 75
339
+ # Auto simple
340
+ draw.rectangle([car_x, car_y, car_x + 15, car_y + 8],
341
+ fill=(200, 100, 100), outline=(0, 0, 0))
342
+ # Ruedas
343
+ draw.ellipse([car_x + 2, car_y + 6, car_x + 5, car_y + 9], fill=(50, 50, 50))
344
+ draw.ellipse([car_x + 10, car_y + 6, car_x + 13, car_y + 9], fill=(50, 50, 50))
345
+
346
+ class SimCityGame:
347
+ """Juego principal"""
348
+ def __init__(self):
349
+ self.city = ArgentineCityEngine()
350
+ self.renderer = CityRenderer()
351
+ self.ai_planner = AIUrbanPlanner()
352
+ self.day = 1
353
+
354
+ def place_building(self, building_type, grid_x, grid_y):
355
+ """Coloca un edificio"""
356
+ try:
357
+ grid_x, grid_y = int(grid_x), int(grid_y)
358
 
359
+ if building_type not in self.city.building_types:
360
+ return "Tipo de edificio inválido", self.get_status(), "Error en tipo de edificio"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
 
362
+ if (grid_x, grid_y) in self.city.buildings:
363
+ return "Ya hay un edificio ahí", self.get_status(), "Posición ocupada"
 
364
 
365
+ cost = self.city.building_types[building_type]['cost']
366
+ if self.city.budget < cost:
367
+ return f"No tenés suficiente presupuesto (${cost:,})", self.get_status(), "Sin presupuesto"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
369
+ # Construir
370
+ self.city.budget -= cost
371
+ self.city.buildings[(grid_x, grid_y)] = building_type
372
 
373
+ # Aplicar efectos
374
+ building_info = self.city.building_types[building_type]
375
+ for effect, value in building_info.items():
376
+ if effect == 'pop':
377
+ self.city.population += value
378
+ elif effect == 'happiness':
379
+ self.city.happiness = max(0, min(100, self.city.happiness + value))
380
+ elif effect in self.city.resources:
381
+ self.city.resources[effect] = max(0, min(100, self.city.resources[effect] + value))
382
 
383
+ # Generar consejo IA
384
+ advice = self.ai_planner.generate_city_advice({
385
+ 'population': self.city.population,
386
+ 'happiness': self.city.happiness,
387
+ 'budget': self.city.budget,
388
+ 'buildings': len(self.city.buildings)
389
+ })
390
 
391
+ frame = self.renderer.render_city(self.city)
392
+ status = self.get_status()
393
+ message = f"✅ {building_type.title()} construido por ${cost:,}"
 
 
 
 
394
 
395
+ return frame, status, f"{message}\n\n🤖 IA: {advice}"
 
 
 
 
 
396
 
397
+ except Exception as e:
398
+ return "Error en construcción", self.get_status(), f"Error: {str(e)}"
399
 
400
+ def simulate_day(self):
401
+ """Simula un día"""
402
+ try:
403
+ self.day += 1
 
 
 
 
 
 
 
 
 
404
 
405
+ # Ingresos
406
+ daily_income = self.city.population * 15 + len(self.city.buildings) * 200
407
+ self.city.budget += daily_income
 
 
 
408
 
409
+ # Gastos de mantenimiento
410
+ maintenance = len(self.city.buildings) * 50
411
+ self.city.budget -= maintenance
412
+
413
+ # Eventos aleatorios
414
+ event_message = ""
415
+ if random.random() < 0.3:
416
+ events = [
417
+ ("🎉 Festival de Tango", "cultura", 15, "happiness", 10),
418
+ ("⚽ Clásico River-Boca", "happiness", 20, "cultura", 5),
419
+ ("🌧️ Lluvia beneficiosa", "agua", 20, "happiness", 5),
420
+ ("💼 Nueva inversión", "budget", 5000, "empleo", 10),
421
+ ("📚 Programa educativo", "educacion", 15, "happiness", 8)
422
+ ]
423
+
424
+ event_name, res1, val1, res2, val2 = random.choice(events)
425
+ event_message = f"📰 {event_name}"
426
+
427
+ # Aplicar efectos
428
+ if res1 == 'budget':
429
+ self.city.budget += val1
430
+ elif res1 == 'happiness':
431
+ self.city.happiness = max(0, min(100, self.city.happiness + val1))
432
+ elif res1 in self.city.resources:
433
+ self.city.resources[res1] = max(0, min(100, self.city.resources[res1] + val1))
434
+
435
+ if res2 == 'happiness':
436
+ self.city.happiness = max(0, min(100, self.city.happiness + val2))
437
+ elif res2 in self.city.resources:
438
+ self.city.resources[res2] = max(0, min(100, self.city.resources[res2] + val2))
439
+
440
+ frame = self.renderer.render_city(self.city)
441
+ status = self.get_status()
442
+
443
+ message = f"📅 Día {self.day} simulado\n💰 Ingresos: ${daily_income:,}\n💸 Gastos: ${maintenance:,}"
444
+ if event_message:
445
+ message += f"\n{event_message}"
446
+
447
+ return frame, status, message
448
+
449
+ except Exception as e:
450
+ return "Error en simulación", self.get_status(), f"Error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
 
452
+ def get_status(self):
453
+ """Estado actual de la ciudad"""
454
+ try:
455
+ building_count = len(self.city.buildings)
456
+ return {
457
+ "🏙️ Población": f"{self.city.population:,} habitantes",
458
+ "😊 Felicidad": f"{self.city.happiness}%",
459
+ "💰 Presupuesto": f"${self.city.budget:,}",
460
+ "🏗️ Edificios": f"{building_count} construidos",
461
+ "📅 Día": self.day,
462
+ "⚡ Energía": f"{self.city.resources['energia']}%",
463
+ "💧 Agua": f"{self.city.resources['agua']}%",
464
+ "🎓 Educación": f"{self.city.resources['educacion']}%"
465
+ }
466
+ except Exception as e:
467
+ return {"Error": str(e)}
468
 
469
  def create_simcity_interface():
470
+ """Interfaz principal del juego"""
471
+ game = SimCityGame()
472
 
473
  with gr.Blocks(
474
+ title="🇦🇷 SIMCIUDAD ARGENTINA",
475
+ theme=gr.themes.Soft()
 
 
 
 
476
  ) as interface:
477
 
478
  gr.HTML("""
479
+ <div style="background: linear-gradient(135deg, #74b9ff 0%, #0984e3 50%, #00b894 100%);
480
+ padding: 25px; text-align: center; border-radius: 15px; margin: 15px;">
481
+ <h1 style="color: white; font-size: 2.5em; margin: 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.7);">
482
  🏙️ SIMCIUDAD ARGENTINA 🇦🇷
483
  </h1>
484
+ <p style="color: #f1f2f6; font-size: 1.1em; margin: 10px 0;">
485
+ Construí la Ciudad Argentina de tus Sueños • Gráficos Isométricos • Mecánicas Realistas • Cultura Auténtica
 
 
 
486
  </p>
487
  </div>
488
  """)
489
 
490
  with gr.Row():
491
  with gr.Column(scale=2):
492
+ # Pantalla principal del juego
493
  city_display = gr.Image(
494
  label="🌆 Tu Ciudad Argentina",
495
  width=1000,
496
+ height=700,
497
+ value=game.renderer.render_city(game.city)
498
  )
499
 
500
  gr.Markdown("### 🏗️ HERRAMIENTAS DE CONSTRUCCIÓN")
501
+
502
  with gr.Row():
503
+ grid_x = gr.Number(label="Coordenada X", value=10, minimum=0, maximum=30)
504
+ grid_y = gr.Number(label="Coordenada Y", value=10, minimum=0, maximum=20)
 
505
 
506
+ # Botones de construcción
507
  with gr.Row():
508
+ casa_btn = gr.Button("🏠 CASA ($1,000)", variant="secondary")
509
+ villa_btn = gr.Button("🏘️ VILLA ($500)", variant="secondary")
510
+ edificio_btn = gr.Button("🏢 EDIFICIO ($5,000)", variant="primary")
511
 
512
  with gr.Row():
513
+ escuela_btn = gr.Button("🏫 ESCUELA ($8,000)", variant="primary")
514
+ hospital_btn = gr.Button("🏥 HOSPITAL ($15,000)", variant="stop")
515
+ cancha_btn = gr.Button("⚽ CANCHA ($3,000)", variant="secondary")
516
 
517
  with gr.Row():
518
+ parrilla_btn = gr.Button("🔥 PARRILLA ($2,000)", variant="stop")
519
+ plaza_btn = gr.Button("🌳 PLAZA ($1,500)", variant="secondary")
520
+ fabrica_btn = gr.Button("🏭 FÁBRICA ($12,000)", variant="primary")
521
 
522
+ # Botón de simulación
523
  with gr.Row():
524
+ simular_btn = gr.Button("⏭️ SIMULAR DÍA", variant="primary", size="lg")
525
 
526
  with gr.Column(scale=1):
527
+ # Estado de la ciudad
528
  gr.Markdown("### 📊 ESTADO DE LA CIUDAD")
529
+ status_display = gr.JSON(
530
  label="📈 Estadísticas",
531
+ value=game.get_status()
532
  )
533
 
534
+ # Eventos y noticias
535
  gr.Markdown("### 📰 EVENTOS RECIENTES")
536
+ news_display = gr.Textbox(
 
537
  label="📺 Noticias Urbanas",
538
+ value="🎮 ¡Bienvenido al SimCity Argentino! Empezá construyendo casas y servicios para tu comunidad.",
539
  lines=5,
540
  interactive=False
541
  )
542
 
543
+ # Info de construcción
544
+ gr.Markdown("### 💰 GUÍA DE CONSTRUCCIÓN")
545
  gr.HTML("""
546
+ <div style="background: #2d3436; color: white; padding: 15px; border-radius: 10px; font-size: 0.9em;">
547
+ <p><strong>🏗️ Efectos de Edificios:</strong></p>
548
+ <ul style="margin: 5px 0; padding-left: 15px;">
549
+ <li>🏠 Casa: +4 población, +2 felicidad</li>
550
+ <li>🏘️ Villa: +8 población, -2 felicidad</li>
551
+ <li>🏢 Edificio: +20 población, +1 felicidad</li>
552
+ <li>🏫 Escuela: +20 educación, +10 felicidad</li>
553
+ <li>🏥 Hospital: +30 salud, +15 felicidad</li>
554
+ <li>⚽ Cancha: +25 felicidad, +10 cultura</li>
555
+ <li>🔥 Parrilla: +20 felicidad, +15 cultura</li>
556
+ <li>🌳 Plaza: +15 felicidad, +5 cultura</li>
557
+ <li>🏭 Fábrica: +30 empleo, -5 felicidad</li>
558
  </ul>
559
  </div>
560
  """)
561
 
562
+ # Conectar funciones
563
  def build_structure(building_type, x, y):
564
+ return game.place_building(building_type, x, y)
 
 
 
565
 
566
  # Conectar botones
567
+ casa_btn.click(fn=lambda x, y: build_structure("casa", x, y),
568
+ inputs=[grid_x, grid_y], outputs=[city_display, status_display, news_display])
569
+ villa_btn.click(fn=lambda x, y: build_structure("villa", x, y),
570
+ inputs=[grid_x, grid_y], outputs=[city_display, status_display, news_display])
571
+ edificio_btn.click(fn=lambda x, y: build_structure("edificio", x, y),
572
+ inputs=[grid_x, grid_y], outputs=[city_display, status_display, news_display])
573
+ escuela_btn.click(fn=lambda x, y: build_structure("escuela", x, y),
574
+ inputs=[grid_x, grid_y], outputs=[city_display, status_display, news_display])
575
+ hospital_btn.click(fn=lambda x, y: build_structure("hospital", x, y),
576
+ inputs=[grid_x, grid_y], outputs=[city_display, status_display, news_display])
577
+ cancha_btn.click(fn=lambda x, y: build_structure("cancha", x, y),
578
+ inputs=[grid_x, grid_y], outputs=[city_display, status_display, news_display])
579
+ parrilla_btn.click(fn=lambda x, y: build_structure("parrilla", x, y),
580
+ inputs=[grid_x, grid_y], outputs=[city_display, status_display, news_display])
581
+ plaza_btn.click(fn=lambda x, y: build_structure("plaza", x, y),
582
+ inputs=[grid_x, grid_y], outputs=[city_display, status_display, news_display])
583
+ fabrica_btn.click(fn=lambda x, y: build_structure("fabrica", x, y),
584
+ inputs=[grid_x, grid_y], outputs=[city_display, status_display, news_display])
585
+
586
+ # Simulación
587
+ simular_btn.click(fn=game.simulate_day, outputs=[city_display, status_display, news_display])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
 
589
  return interface
590