Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,301 +1,507 @@
|
|
| 1 |
-
# app.py -
|
| 2 |
import gradio as gr
|
| 3 |
import time
|
| 4 |
import math
|
| 5 |
-
from PIL import Image, ImageDraw
|
| 6 |
import random
|
|
|
|
| 7 |
from game_engine import GauchoCharacter, PhysicsEngine, WorldGenerator, Vector2D
|
| 8 |
|
| 9 |
-
class
|
| 10 |
-
"""Renderer épico para un gaucho que se respete"""
|
| 11 |
def __init__(self):
|
| 12 |
-
self.
|
| 13 |
-
self.
|
| 14 |
-
self.
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
"""Genera estrellas sin usar numpy.choice con tuplas"""
|
| 18 |
-
star_colors = [(255, 255, 255), (200, 200, 255), (255, 255, 200), (255, 200, 255)]
|
| 19 |
-
return [(
|
| 20 |
-
random.randint(0, 800),
|
| 21 |
-
random.randint(0, 600),
|
| 22 |
-
random.choice([1, 2, 3]),
|
| 23 |
-
random.choice(star_colors) # Usamos random de Python, no numpy
|
| 24 |
-
) for _ in range(count)]
|
| 25 |
-
|
| 26 |
-
def _create_epic_tiles(self):
|
| 27 |
-
"""Crea texturas épicas para cada tipo de terreno"""
|
| 28 |
-
tiles = {}
|
| 29 |
|
| 30 |
-
#
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
#
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
color = (20, 40 + intensity//3, 80 + intensity//2)
|
| 39 |
-
draw.rectangle([i, j, i+18, j+18], fill=color, outline=(0, 255, 150), width=1)
|
| 40 |
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
-
def
|
| 45 |
-
"""
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
-
#
|
| 60 |
-
|
| 61 |
-
(x-30, y-10), (x-20, y-35), (x+20, y-35), (x+30, y-10),
|
| 62 |
-
(x+25, y+15), (x-25, y+15)
|
| 63 |
-
]
|
| 64 |
-
draw.polygon(poncho_points, fill=(0, 100, 200), outline=(0, 150, 255), width=2)
|
| 65 |
|
| 66 |
-
#
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
-
#
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
-
#
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
draw.ellipse([x+5, y-6, x+6, y-5], fill=(0, 0, 0))
|
| 86 |
-
|
| 87 |
-
class CosmicGauchoEpic:
|
| 88 |
-
def __init__(self):
|
| 89 |
-
self.player = GauchoCharacter()
|
| 90 |
-
self.player.position = Vector2D(400, 300) # Centro de la pantalla
|
| 91 |
-
self.physics = PhysicsEngine()
|
| 92 |
-
self.renderer = EpicGauchoRenderer()
|
| 93 |
-
self.camera_offset = Vector2D(0, 0)
|
| 94 |
-
self.crystals = self._spawn_crystals()
|
| 95 |
-
self.enemies = []
|
| 96 |
-
self.particles = []
|
| 97 |
-
self.game_time = 0
|
| 98 |
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
})
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
-
def
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
-
|
| 116 |
-
|
| 117 |
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
elif movement == "right":
|
| 121 |
-
acceleration.x = speed
|
| 122 |
-
elif movement == "up":
|
| 123 |
-
acceleration.y = -speed * 0.8 # Gravedad simulada
|
| 124 |
-
elif movement == "down":
|
| 125 |
-
acceleration.y = speed
|
| 126 |
|
| 127 |
-
#
|
| 128 |
-
self.
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
)
|
| 132 |
|
| 133 |
-
|
|
|
|
| 134 |
|
| 135 |
-
#
|
| 136 |
-
self.
|
| 137 |
-
self.player.position.y = max(50, min(self.player.position.y, 1200))
|
| 138 |
|
| 139 |
-
#
|
| 140 |
-
self.
|
| 141 |
-
self.camera_offset.y = self.player.position.y - 300
|
| 142 |
|
| 143 |
-
#
|
| 144 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
-
def
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
|
| 155 |
-
def
|
| 156 |
-
|
| 157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
scroll_y = int(star_y - self.camera_offset.y * 0.3)
|
| 164 |
-
if 0 <= scroll_x <= 800 and 0 <= scroll_y <= 600:
|
| 165 |
-
draw.ellipse([scroll_x-size, scroll_y-size, scroll_x+size, scroll_y+size], fill=color)
|
| 166 |
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
# Campo energético
|
| 175 |
-
alpha = int(50 + 30 * math.sin(self.game_time + x/100))
|
| 176 |
-
color = (0, 50 + alpha//2, 100 + alpha)
|
| 177 |
-
draw.rectangle([screen_x, screen_y, screen_x+90, screen_y+90],
|
| 178 |
-
fill=color, outline=(0, 150, 255), width=1)
|
| 179 |
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
for crystal in self.crystals:
|
| 182 |
if not crystal['collected']:
|
| 183 |
-
|
| 184 |
-
|
| 185 |
|
| 186 |
-
if
|
| 187 |
-
#
|
| 188 |
-
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
|
| 191 |
-
#
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
crystal_color = crystal_colors.get(crystal['type'], (255, 255, 255))
|
| 205 |
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
|
| 220 |
-
def
|
| 221 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
return {
|
| 223 |
-
"🗺️
|
| 224 |
-
"⚡
|
| 225 |
-
"💎 Cristales": f"{
|
| 226 |
-
"
|
| 227 |
-
"
|
|
|
|
| 228 |
}
|
| 229 |
|
| 230 |
-
def
|
| 231 |
-
game =
|
| 232 |
|
| 233 |
with gr.Blocks(
|
| 234 |
-
title="
|
| 235 |
-
theme=gr.themes.
|
| 236 |
) as interface:
|
| 237 |
|
| 238 |
gr.HTML("""
|
| 239 |
-
<div style="text-align: center;
|
| 240 |
-
|
| 241 |
-
<
|
| 242 |
-
|
| 243 |
-
</
|
|
|
|
|
|
|
|
|
|
| 244 |
</div>
|
| 245 |
""")
|
| 246 |
|
| 247 |
with gr.Row():
|
| 248 |
-
with gr.Column(scale=
|
| 249 |
game_display = gr.Image(
|
| 250 |
-
label="🌌
|
| 251 |
interactive=False,
|
| 252 |
width=800,
|
| 253 |
height=600
|
| 254 |
)
|
| 255 |
|
|
|
|
| 256 |
with gr.Row():
|
| 257 |
-
left_btn = gr.Button("⬅️ IZQUIERDA", variant="primary"
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
with gr.Column(scale=1):
|
| 263 |
-
gr.Markdown("###
|
| 264 |
-
status_display = gr.JSON(
|
|
|
|
|
|
|
|
|
|
| 265 |
|
| 266 |
-
gr.Markdown("###
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
"
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
|
|
|
|
|
|
| 279 |
)
|
| 280 |
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
right_btn.click(fn=lambda: epic_move_and_update("right"), outputs=[game_display, status_display])
|
| 288 |
-
up_btn.click(fn=lambda: epic_move_and_update("up"), outputs=[game_display, status_display])
|
| 289 |
-
down_btn.click(fn=lambda: epic_move_and_update("down"), outputs=[game_display, status_display])
|
| 290 |
|
| 291 |
-
#
|
| 292 |
interface.load(
|
| 293 |
-
fn=lambda: (game.render_epic_frame(), game.
|
| 294 |
outputs=[game_display, status_display]
|
| 295 |
)
|
| 296 |
|
| 297 |
return interface
|
| 298 |
|
| 299 |
if __name__ == "__main__":
|
| 300 |
-
interface =
|
| 301 |
-
interface.launch(share=True, show_error=True
|
|
|
|
| 1 |
+
# app.py - EL GAUCHO CÓSMICO ÉPICO VERSION 2.0
|
| 2 |
import gradio as gr
|
| 3 |
import time
|
| 4 |
import math
|
|
|
|
| 5 |
import random
|
| 6 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 7 |
from game_engine import GauchoCharacter, PhysicsEngine, WorldGenerator, Vector2D
|
| 8 |
|
| 9 |
+
class EpicGameEngine:
|
|
|
|
| 10 |
def __init__(self):
|
| 11 |
+
self.width = 800
|
| 12 |
+
self.height = 600
|
| 13 |
+
self.player = GauchoCharacter()
|
| 14 |
+
self.player.position = Vector2D(100, 400)
|
| 15 |
+
self.physics = PhysicsEngine()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
+
# Sistema de juego épico
|
| 18 |
+
self.scroll_x = 0
|
| 19 |
+
self.enemies = []
|
| 20 |
+
self.projectiles = []
|
| 21 |
+
self.crystals = []
|
| 22 |
+
self.particles = []
|
| 23 |
+
self.combo_meter = 0
|
| 24 |
+
self.cultural_power = 100
|
| 25 |
|
| 26 |
+
# Estados de juego
|
| 27 |
+
self.game_state = "playing" # playing, dialogue, victory
|
| 28 |
+
self.current_level = 1
|
| 29 |
+
self.boss_fight = False
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
# Inicializar mundo épico
|
| 32 |
+
self._spawn_initial_world()
|
| 33 |
+
self._spawn_enemies()
|
| 34 |
+
self._spawn_crystals()
|
| 35 |
+
|
| 36 |
+
def _spawn_initial_world(self):
|
| 37 |
+
"""Crea un mundo side-scrolling épico"""
|
| 38 |
+
self.terrain = []
|
| 39 |
+
for x in range(0, 2000, 50):
|
| 40 |
+
height = 450 + 50 * math.sin(x / 100)
|
| 41 |
+
self.terrain.append((x, int(height)))
|
| 42 |
|
| 43 |
+
def _spawn_enemies(self):
|
| 44 |
+
"""Genera enemigos algorítmicos épicos"""
|
| 45 |
+
enemy_types = ['homogenizer', 'cultural_eraser', 'identity_fragmenter']
|
| 46 |
+
for i in range(5):
|
| 47 |
+
self.enemies.append({
|
| 48 |
+
'type': random.choice(enemy_types),
|
| 49 |
+
'position': Vector2D(300 + i * 200, 350),
|
| 50 |
+
'health': 3,
|
| 51 |
+
'pattern': 'patrol',
|
| 52 |
+
'attack_timer': 0,
|
| 53 |
+
'animation_frame': 0
|
| 54 |
+
})
|
| 55 |
+
|
| 56 |
+
def _spawn_crystals(self):
|
| 57 |
+
"""Cristales de valores argentinos distribuidos épicamente"""
|
| 58 |
+
values = ['Solidaridad', 'Familia', 'Amistad', 'Respeto', 'Creatividad']
|
| 59 |
+
colors = [(255, 100, 100), (100, 255, 100), (100, 100, 255), (255, 255, 100), (255, 100, 255)]
|
| 60 |
|
| 61 |
+
for i, (value, color) in enumerate(zip(values, colors)):
|
| 62 |
+
self.crystals.append({
|
| 63 |
+
'type': value,
|
| 64 |
+
'position': Vector2D(200 + i * 300, 200 + random.randint(-100, 100)),
|
| 65 |
+
'color': color,
|
| 66 |
+
'collected': False,
|
| 67 |
+
'pulse': 0,
|
| 68 |
+
'floating_offset': random.random() * math.pi
|
| 69 |
+
})
|
| 70 |
+
|
| 71 |
+
def update_game(self, action):
|
| 72 |
+
"""Motor de juego principal"""
|
| 73 |
+
dt = 0.016 # 60 FPS target
|
| 74 |
|
| 75 |
+
# Actualizar jugador
|
| 76 |
+
self._update_player(action, dt)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
+
# Actualizar mundo
|
| 79 |
+
self._update_scroll()
|
| 80 |
+
self._update_enemies(dt)
|
| 81 |
+
self._update_projectiles(dt)
|
| 82 |
+
self._update_particles(dt)
|
| 83 |
+
self._check_collisions()
|
| 84 |
|
| 85 |
+
# Sistemas de juego
|
| 86 |
+
self._update_combo_system()
|
| 87 |
+
self._check_victory_condition()
|
| 88 |
+
|
| 89 |
+
def _update_player(self, action, dt):
|
| 90 |
+
"""Sistema de movimiento fluido del gaucho"""
|
| 91 |
+
acceleration = Vector2D(0, 0.5) # Gravedad
|
| 92 |
|
| 93 |
+
if action == "left":
|
| 94 |
+
acceleration.x = -8
|
| 95 |
+
self.player.facing = "left"
|
| 96 |
+
elif action == "right":
|
| 97 |
+
acceleration.x = 8
|
| 98 |
+
self.player.facing = "right"
|
| 99 |
+
elif action == "jump":
|
| 100 |
+
if self.player.on_ground:
|
| 101 |
+
self.player.velocity.y = -12
|
| 102 |
+
self.player.on_ground = False
|
| 103 |
+
self._create_particles(self.player.position, "jump", 5)
|
| 104 |
+
elif action == "attack":
|
| 105 |
+
self._throw_boleadoras()
|
| 106 |
+
elif action == "special":
|
| 107 |
+
self._use_mate_power()
|
| 108 |
|
| 109 |
+
# Aplicar física fluida
|
| 110 |
+
self.player.velocity = self.physics.apply_momentum(
|
| 111 |
+
self.player.velocity, acceleration, dt
|
| 112 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
|
| 114 |
+
# Movimiento suave
|
| 115 |
+
self.player.position += self.player.velocity * dt * 60
|
| 116 |
+
|
| 117 |
+
# Colisión con terreno
|
| 118 |
+
self._handle_terrain_collision()
|
| 119 |
+
|
| 120 |
+
def _update_scroll(self):
|
| 121 |
+
"""Cámara que sigue al jugador dinámicamente"""
|
| 122 |
+
target_scroll = self.player.position.x - 200
|
| 123 |
+
self.scroll_x += (target_scroll - self.scroll_x) * 0.1 # Smooth following
|
| 124 |
+
|
| 125 |
+
def _throw_boleadoras(self):
|
| 126 |
+
"""Sistema de combate con boleadoras cuánticas"""
|
| 127 |
+
if len(self.projectiles) < 3: # Máximo 3 boleadoras activas
|
| 128 |
+
direction = 1 if self.player.facing == "right" else -1
|
| 129 |
+
self.projectiles.append({
|
| 130 |
+
'position': Vector2D(self.player.position.x, self.player.position.y - 20),
|
| 131 |
+
'velocity': Vector2D(direction * 15, -2),
|
| 132 |
+
'type': 'boleadoras',
|
| 133 |
+
'lifetime': 2.0,
|
| 134 |
+
'trail': []
|
| 135 |
})
|
| 136 |
+
self._create_particles(self.player.position, "boleadoras", 3)
|
| 137 |
+
|
| 138 |
+
def _use_mate_power(self):
|
| 139 |
+
"""Poder especial del mate tecnológico"""
|
| 140 |
+
if self.cultural_power >= 50:
|
| 141 |
+
self.cultural_power -= 50
|
| 142 |
+
self.combo_meter += 20
|
| 143 |
+
|
| 144 |
+
# Efecto de área que afecta enemigos cercanos
|
| 145 |
+
for enemy in self.enemies:
|
| 146 |
+
distance = ((self.player.position.x - enemy['position'].x) ** 2 +
|
| 147 |
+
(self.player.position.y - enemy['position'].y) ** 2) ** 0.5
|
| 148 |
+
if distance < 150:
|
| 149 |
+
enemy['health'] -= 2
|
| 150 |
+
self._create_particles(enemy['position'], "mate_effect", 8)
|
| 151 |
|
| 152 |
+
def _create_particles(self, position, particle_type, count):
|
| 153 |
+
"""Sistema de partículas épico"""
|
| 154 |
+
for _ in range(count):
|
| 155 |
+
self.particles.append({
|
| 156 |
+
'position': Vector2D(position.x + random.randint(-10, 10),
|
| 157 |
+
position.y + random.randint(-10, 10)),
|
| 158 |
+
'velocity': Vector2D(random.randint(-3, 3), random.randint(-5, 0)),
|
| 159 |
+
'color': self._get_particle_color(particle_type),
|
| 160 |
+
'lifetime': random.uniform(0.5, 1.5),
|
| 161 |
+
'type': particle_type
|
| 162 |
+
})
|
| 163 |
+
|
| 164 |
+
def _get_particle_color(self, particle_type):
|
| 165 |
+
colors = {
|
| 166 |
+
'jump': (100, 200, 255),
|
| 167 |
+
'boleadoras': (255, 200, 100),
|
| 168 |
+
'mate_effect': (0, 255, 100),
|
| 169 |
+
'explosion': (255, 100, 100),
|
| 170 |
+
'crystal': (255, 255, 255)
|
| 171 |
+
}
|
| 172 |
+
return colors.get(particle_type, (255, 255, 255))
|
| 173 |
+
|
| 174 |
+
def render_epic_frame(self) -> Image.Image:
|
| 175 |
+
"""Renderizado épico con múltiples capas"""
|
| 176 |
+
img = Image.new('RGB', (self.width, self.height), (10, 15, 35))
|
| 177 |
+
draw = ImageDraw.Draw(img)
|
| 178 |
|
| 179 |
+
# Capa 1: Fondo estrellado con parallax
|
| 180 |
+
self._draw_starfield(draw)
|
| 181 |
|
| 182 |
+
# Capa 2: Montañas distantes
|
| 183 |
+
self._draw_mountains(draw)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
|
| 185 |
+
# Capa 3: Terreno principal
|
| 186 |
+
self._draw_terrain(draw)
|
| 187 |
+
|
| 188 |
+
# Capa 4: Cristales con efectos
|
| 189 |
+
self._draw_crystals(draw)
|
| 190 |
|
| 191 |
+
# Capa 5: Enemigos animados
|
| 192 |
+
self._draw_enemies(draw)
|
| 193 |
|
| 194 |
+
# Capa 6: Proyectiles con trails
|
| 195 |
+
self._draw_projectiles(draw)
|
|
|
|
| 196 |
|
| 197 |
+
# Capa 7: Gaucho protagonista
|
| 198 |
+
self._draw_epic_gaucho(draw)
|
|
|
|
| 199 |
|
| 200 |
+
# Capa 8: Partículas
|
| 201 |
+
self._draw_particles(draw)
|
| 202 |
+
|
| 203 |
+
# Capa 9: UI épica
|
| 204 |
+
self._draw_ui(draw)
|
| 205 |
+
|
| 206 |
+
return img
|
| 207 |
|
| 208 |
+
def _draw_starfield(self, draw):
|
| 209 |
+
"""Fondo estrellado con parallax scrolling"""
|
| 210 |
+
for i in range(100):
|
| 211 |
+
star_x = (i * 47 + self.scroll_x * 0.2) % self.width
|
| 212 |
+
star_y = (i * 31) % self.height
|
| 213 |
+
brightness = 100 + (i % 155)
|
| 214 |
+
draw.ellipse([star_x-1, star_y-1, star_x+1, star_y+1],
|
| 215 |
+
fill=(brightness, brightness, brightness))
|
| 216 |
|
| 217 |
+
def _draw_mountains(self, draw):
|
| 218 |
+
"""Montañas de fondo con profundidad"""
|
| 219 |
+
for i in range(0, self.width + 100, 100):
|
| 220 |
+
mountain_x = i - (self.scroll_x * 0.5) % 100
|
| 221 |
+
mountain_height = 200 + 50 * math.sin((mountain_x + self.scroll_x) / 150)
|
| 222 |
+
draw.polygon([
|
| 223 |
+
(mountain_x, self.height),
|
| 224 |
+
(mountain_x + 50, mountain_height),
|
| 225 |
+
(mountain_x + 100, self.height)
|
| 226 |
+
], fill=(30, 40, 80), outline=(50, 60, 120))
|
| 227 |
+
|
| 228 |
+
def _draw_terrain(self, draw):
|
| 229 |
+
"""Terreno orgánico y fluido"""
|
| 230 |
+
terrain_points = [(0, self.height)]
|
| 231 |
|
| 232 |
+
for i in range(0, self.width, 20):
|
| 233 |
+
world_x = i + self.scroll_x
|
| 234 |
+
height = 450 + 30 * math.sin(world_x / 80) + 20 * math.cos(world_x / 120)
|
| 235 |
+
terrain_points.append((i, height))
|
|
|
|
|
|
|
|
|
|
| 236 |
|
| 237 |
+
terrain_points.append((self.width, self.height))
|
| 238 |
+
draw.polygon(terrain_points, fill=(20, 60, 40), outline=(40, 120, 80))
|
| 239 |
+
|
| 240 |
+
def _draw_epic_gaucho(self, draw):
|
| 241 |
+
"""Gaucho protagonista con animaciones fluidas"""
|
| 242 |
+
screen_x = int(self.player.position.x - self.scroll_x)
|
| 243 |
+
screen_y = int(self.player.position.y)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
|
| 245 |
+
if 0 <= screen_x <= self.width:
|
| 246 |
+
# Sombra dinámica
|
| 247 |
+
draw.ellipse([screen_x-15, screen_y+20, screen_x+15, screen_y+25],
|
| 248 |
+
fill=(0, 0, 0, 100))
|
| 249 |
+
|
| 250 |
+
# Cuerpo principal
|
| 251 |
+
body_color = (139, 69, 19)
|
| 252 |
+
draw.ellipse([screen_x-20, screen_y-30, screen_x+20, screen_y+10],
|
| 253 |
+
fill=body_color, outline=(101, 67, 33), width=2)
|
| 254 |
+
|
| 255 |
+
# Poncho tecnológico animado
|
| 256 |
+
poncho_wave = math.sin(time.time() * 5) * 3
|
| 257 |
+
poncho_points = [
|
| 258 |
+
(screen_x-25, screen_y-15 + poncho_wave),
|
| 259 |
+
(screen_x-15, screen_y-35),
|
| 260 |
+
(screen_x+15, screen_y-35),
|
| 261 |
+
(screen_x+25, screen_y-15 - poncho_wave),
|
| 262 |
+
(screen_x+20, screen_y+5),
|
| 263 |
+
(screen_x-20, screen_y+5)
|
| 264 |
+
]
|
| 265 |
+
draw.polygon(poncho_points, fill=(0, 120, 200), outline=(0, 180, 255), width=2)
|
| 266 |
+
|
| 267 |
+
# Líneas energéticas del poncho
|
| 268 |
+
for i in range(-15, 20, 8):
|
| 269 |
+
line_alpha = int(255 * (0.5 + 0.5 * math.sin(time.time() * 3 + i)))
|
| 270 |
+
draw.line([(screen_x+i, screen_y-30), (screen_x+i, screen_y+5)],
|
| 271 |
+
fill=(255, 255, 100), width=1)
|
| 272 |
+
|
| 273 |
+
# Sombrero gaucho épico
|
| 274 |
+
hat_tilt = 2 if self.player.facing == "right" else -2
|
| 275 |
+
draw.ellipse([screen_x-18+hat_tilt, screen_y-45, screen_x+18+hat_tilt, screen_y-35],
|
| 276 |
+
fill=(50, 25, 0), outline=(100, 50, 0), width=2)
|
| 277 |
+
draw.ellipse([screen_x-30+hat_tilt, screen_y-40, screen_x+30+hat_tilt, screen_y-38],
|
| 278 |
+
fill=(40, 20, 0), outline=(80, 40, 0), width=1)
|
| 279 |
+
|
| 280 |
+
# Mate tecnológico brillante
|
| 281 |
+
mate_x = screen_x + (12 if self.player.facing == "right" else -12)
|
| 282 |
+
mate_glow = int(100 + 50 * math.sin(time.time() * 4))
|
| 283 |
+
draw.ellipse([mate_x-6, screen_y-10, mate_x+6, screen_y+2],
|
| 284 |
+
fill=(150, 75, 0), outline=(255, mate_glow, 0), width=2)
|
| 285 |
+
|
| 286 |
+
# Bombilla láser
|
| 287 |
+
laser_intensity = int(255 * (0.7 + 0.3 * math.sin(time.time() * 8)))
|
| 288 |
+
draw.line([(mate_x, screen_y-10), (mate_x, screen_y-18)],
|
| 289 |
+
fill=(0, laser_intensity, 0), width=3)
|
| 290 |
+
|
| 291 |
+
# Ojos expresivos
|
| 292 |
+
eye_offset = 2 if self.player.facing == "right" else -2
|
| 293 |
+
draw.ellipse([screen_x-8+eye_offset, screen_y-25, screen_x-3+eye_offset, screen_y-20],
|
| 294 |
+
fill=(255, 255, 255))
|
| 295 |
+
draw.ellipse([screen_x+3+eye_offset, screen_y-25, screen_x+8+eye_offset, screen_y-20],
|
| 296 |
+
fill=(255, 255, 255))
|
| 297 |
+
draw.ellipse([screen_x-6+eye_offset, screen_y-23, screen_x-5+eye_offset, screen_y-22],
|
| 298 |
+
fill=(0, 0, 0))
|
| 299 |
+
draw.ellipse([screen_x+5+eye_offset, screen_y-23, screen_x+6+eye_offset, screen_y-22],
|
| 300 |
+
fill=(0, 0, 0))
|
| 301 |
+
|
| 302 |
+
def _draw_crystals(self, draw):
|
| 303 |
+
"""Cristales con efectos mágicos"""
|
| 304 |
for crystal in self.crystals:
|
| 305 |
if not crystal['collected']:
|
| 306 |
+
screen_x = int(crystal['position'].x - self.scroll_x)
|
| 307 |
+
screen_y = int(crystal['position'].y + 10 * math.sin(time.time() * 2 + crystal['floating_offset']))
|
| 308 |
|
| 309 |
+
if -50 <= screen_x <= self.width + 50:
|
| 310 |
+
# Aura brillante
|
| 311 |
+
aura_size = 25 + 8 * math.sin(time.time() * 3 + crystal['pulse'])
|
| 312 |
+
for i in range(3):
|
| 313 |
+
alpha = 50 - i * 15
|
| 314 |
+
draw.ellipse([screen_x-aura_size+i*5, screen_y-aura_size+i*5,
|
| 315 |
+
screen_x+aura_size-i*5, screen_y+aura_size-i*5],
|
| 316 |
+
outline=crystal['color'])
|
| 317 |
|
| 318 |
+
# Cristal central
|
| 319 |
+
crystal_size = 15 + 5 * math.sin(time.time() * 4 + crystal['pulse'])
|
| 320 |
+
draw.polygon([
|
| 321 |
+
(screen_x, screen_y-crystal_size),
|
| 322 |
+
(screen_x-crystal_size*0.7, screen_y-crystal_size*0.3),
|
| 323 |
+
(screen_x-crystal_size*0.7, screen_y+crystal_size*0.3),
|
| 324 |
+
(screen_x, screen_y+crystal_size),
|
| 325 |
+
(screen_x+crystal_size*0.7, screen_y+crystal_size*0.3),
|
| 326 |
+
(screen_x+crystal_size*0.7, screen_y-crystal_size*0.3)
|
| 327 |
+
], fill=crystal['color'], outline=(255, 255, 255), width=2)
|
| 328 |
+
|
| 329 |
+
def _draw_enemies(self, draw):
|
| 330 |
+
"""Enemigos algorítmicos con IA visual"""
|
| 331 |
+
for enemy in self.enemies:
|
| 332 |
+
if enemy['health'] > 0:
|
| 333 |
+
screen_x = int(enemy['position'].x - self.scroll_x)
|
| 334 |
+
screen_y = int(enemy['position'].y)
|
| 335 |
+
|
| 336 |
+
if -50 <= screen_x <= self.width + 50:
|
| 337 |
+
# Forma algorítmica hostil
|
| 338 |
+
if enemy['type'] == 'homogenizer':
|
| 339 |
+
# Cubo gris hostil
|
| 340 |
+
draw.rectangle([screen_x-15, screen_y-15, screen_x+15, screen_y+15],
|
| 341 |
+
fill=(80, 80, 80), outline=(120, 120, 120), width=2)
|
| 342 |
+
# Patrón de homogenización
|
| 343 |
+
for i in range(-10, 15, 5):
|
| 344 |
+
draw.line([(screen_x+i, screen_y-15), (screen_x+i, screen_y+15)],
|
| 345 |
+
fill=(150, 150, 150), width=1)
|
| 346 |
|
| 347 |
+
elif enemy['type'] == 'cultural_eraser':
|
| 348 |
+
# Forma que "borra" la cultura
|
| 349 |
+
draw.polygon([
|
| 350 |
+
(screen_x, screen_y-20),
|
| 351 |
+
(screen_x-15, screen_y),
|
| 352 |
+
(screen_x, screen_y+20),
|
| 353 |
+
(screen_x+15, screen_y)
|
| 354 |
+
], fill=(100, 50, 50), outline=(150, 100, 100), width=2)
|
|
|
|
| 355 |
|
| 356 |
+
# Barras de vida
|
| 357 |
+
health_width = (enemy['health'] / 3) * 30
|
| 358 |
+
draw.rectangle([screen_x-15, screen_y-25, screen_x+15, screen_y-22],
|
| 359 |
+
fill=(50, 50, 50))
|
| 360 |
+
draw.rectangle([screen_x-15, screen_y-25, screen_x-15+health_width, screen_y-22],
|
| 361 |
+
fill=(255, 100, 100))
|
| 362 |
+
|
| 363 |
+
def _draw_particles(self, draw):
|
| 364 |
+
"""Sistema de partículas dinámico"""
|
| 365 |
+
for particle in self.particles:
|
| 366 |
+
if particle['lifetime'] > 0:
|
| 367 |
+
screen_x = int(particle['position'].x - self.scroll_x)
|
| 368 |
+
screen_y = int(particle['position'].y)
|
| 369 |
+
|
| 370 |
+
if 0 <= screen_x <= self.width and 0 <= screen_y <= self.height:
|
| 371 |
+
alpha = int(255 * (particle['lifetime'] / 1.5))
|
| 372 |
+
size = max(1, int(3 * particle['lifetime']))
|
| 373 |
+
draw.ellipse([screen_x-size, screen_y-size, screen_x+size, screen_y+size],
|
| 374 |
+
fill=particle['color'])
|
| 375 |
+
|
| 376 |
+
def _draw_ui(self, draw):
|
| 377 |
+
"""Interfaz épica superpuesta"""
|
| 378 |
+
# Barra de poder cultural
|
| 379 |
+
draw.rectangle([20, 20, 220, 40], fill=(50, 50, 50), outline=(150, 150, 150), width=2)
|
| 380 |
+
cultural_width = int((self.cultural_power / 100) * 200)
|
| 381 |
+
draw.rectangle([20, 20, 20 + cultural_width, 40], fill=(0, 200, 100))
|
| 382 |
|
| 383 |
+
# Combo meter
|
| 384 |
+
if self.combo_meter > 0:
|
| 385 |
+
combo_height = min(100, self.combo_meter * 2)
|
| 386 |
+
draw.rectangle([self.width - 50, self.height - 120, self.width - 30, self.height - 20],
|
| 387 |
+
fill=(50, 50, 50), outline=(200, 200, 200), width=2)
|
| 388 |
+
draw.rectangle([self.width - 50, self.height - 20 - combo_height,
|
| 389 |
+
self.width - 30, self.height - 20], fill=(255, 200, 0))
|
| 390 |
+
|
| 391 |
+
class GauchoCosmicGame:
|
| 392 |
+
def __init__(self):
|
| 393 |
+
self.engine = EpicGameEngine()
|
| 394 |
+
self.last_action = "none"
|
| 395 |
+
self.game_stats = {
|
| 396 |
+
'crystals_collected': 0,
|
| 397 |
+
'enemies_defeated': 0,
|
| 398 |
+
'cultural_combos': 0,
|
| 399 |
+
'epic_level': 'MÁXIMO'
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
def process_input(self, action):
|
| 403 |
+
"""Procesa input del jugador"""
|
| 404 |
+
self.last_action = action
|
| 405 |
+
self.engine.update_game(action)
|
| 406 |
+
self._update_stats()
|
| 407 |
+
return self.engine.render_epic_frame(), self.get_status()
|
| 408 |
|
| 409 |
+
def _update_stats(self):
|
| 410 |
+
"""Actualiza estadísticas del juego"""
|
| 411 |
+
self.game_stats['crystals_collected'] = sum(
|
| 412 |
+
1 for crystal in self.engine.crystals if crystal['collected']
|
| 413 |
+
)
|
| 414 |
+
self.game_stats['cultural_combos'] = self.engine.combo_meter // 10
|
| 415 |
+
|
| 416 |
+
def get_status(self):
|
| 417 |
+
"""Estado épico del juego"""
|
| 418 |
return {
|
| 419 |
+
"🗺️ Posición": f"X: {self.engine.player.position.x:.0f}",
|
| 420 |
+
"⚡ Velocidad": f"{abs(self.engine.player.velocity.x):.1f} km/h",
|
| 421 |
+
"💎 Cristales": f"{self.game_stats['crystals_collected']}/5",
|
| 422 |
+
"🔥 Poder Cultural": f"{self.engine.cultural_power:.0f}%",
|
| 423 |
+
"🎯 Combos": self.game_stats['cultural_combos'],
|
| 424 |
+
"🚀 Épica": self.game_stats['epic_level']
|
| 425 |
}
|
| 426 |
|
| 427 |
+
def create_ultimate_interface():
|
| 428 |
+
game = GauchoCosmicGame()
|
| 429 |
|
| 430 |
with gr.Blocks(
|
| 431 |
+
title="🎮 EL GAUCHO CÓSMICO - ULTIMATE EDITION 🇦🇷",
|
| 432 |
+
theme=gr.themes.Glass()
|
| 433 |
) as interface:
|
| 434 |
|
| 435 |
gr.HTML("""
|
| 436 |
+
<div style="text-align: center; background: linear-gradient(45deg, #FF6B6B, #4ECDC4);
|
| 437 |
+
color: white; padding: 20px; border-radius: 10px; margin: 10px;">
|
| 438 |
+
<h1 style="font-size: 3em; margin: 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.5);">
|
| 439 |
+
🌟 EL GAUCHO CÓSMICO 🌟
|
| 440 |
+
</h1>
|
| 441 |
+
<p style="font-size: 1.2em; margin: 10px 0;">
|
| 442 |
+
AVENTURA SIDE-SCROLLING DE IDENTIDAD CULTURAL ARGENTINA
|
| 443 |
+
</p>
|
| 444 |
</div>
|
| 445 |
""")
|
| 446 |
|
| 447 |
with gr.Row():
|
| 448 |
+
with gr.Column(scale=3):
|
| 449 |
game_display = gr.Image(
|
| 450 |
+
label="🌌 PAMPA CÓSMICA ÉPICA",
|
| 451 |
interactive=False,
|
| 452 |
width=800,
|
| 453 |
height=600
|
| 454 |
)
|
| 455 |
|
| 456 |
+
gr.Markdown("### 🎮 CONTROLES ÉPICOS")
|
| 457 |
with gr.Row():
|
| 458 |
+
left_btn = gr.Button("⬅️ IZQUIERDA", variant="primary")
|
| 459 |
+
right_btn = gr.Button("➡️ DERECHA", variant="primary")
|
| 460 |
+
jump_btn = gr.Button("⬆️ SALTAR", variant="secondary")
|
| 461 |
+
|
| 462 |
+
with gr.Row():
|
| 463 |
+
attack_btn = gr.Button("🪃 BOLEADORAS", variant="stop")
|
| 464 |
+
special_btn = gr.Button("🧉 MATE POWER", variant="stop")
|
| 465 |
|
| 466 |
with gr.Column(scale=1):
|
| 467 |
+
gr.Markdown("### 📊 ESTADO ÉPICO")
|
| 468 |
+
status_display = gr.JSON(
|
| 469 |
+
label="🎯 MÉTRICAS DE MARTÍN 'EL MATE'",
|
| 470 |
+
value=game.get_status()
|
| 471 |
+
)
|
| 472 |
|
| 473 |
+
gr.Markdown("### 🎵 SOUNDTRACK")
|
| 474 |
+
gr.HTML("""
|
| 475 |
+
<div style="background: #2C3E50; padding: 15px; border-radius: 8px; color: white;">
|
| 476 |
+
<p>🎼 <strong>Ahora sonando:</strong></p>
|
| 477 |
+
<p style="font-style: italic;">"Pampa Cósmica - Tema Principal"</p>
|
| 478 |
+
<p>🎸 Género: Folklectrónica Argentina</p>
|
| 479 |
+
</div>
|
| 480 |
+
""")
|
| 481 |
|
| 482 |
+
gr.Markdown("### 💬 DIÁLOGOS ÉPICOS")
|
| 483 |
+
dialogue_box = gr.Textbox(
|
| 484 |
+
value="¡Che! ¿Viste esos algoritmos tratando de borrar nuestra cultura? ¡Ni en pedo se la llevo!",
|
| 485 |
+
label="🗨️ Martín 'El Mate' habla",
|
| 486 |
+
interactive=False,
|
| 487 |
+
max_lines=3
|
| 488 |
)
|
| 489 |
|
| 490 |
+
# Conectar controles
|
| 491 |
+
left_btn.click(fn=lambda: game.process_input("left"), outputs=[game_display, status_display])
|
| 492 |
+
right_btn.click(fn=lambda: game.process_input("right"), outputs=[game_display, status_display])
|
| 493 |
+
jump_btn.click(fn=lambda: game.process_input("jump"), outputs=[game_display, status_display])
|
| 494 |
+
attack_btn.click(fn=lambda: game.process_input("attack"), outputs=[game_display, status_display])
|
| 495 |
+
special_btn.click(fn=lambda: game.process_input("special"), outputs=[game_display, status_display])
|
|
|
|
|
|
|
|
|
|
| 496 |
|
| 497 |
+
# Inicialización
|
| 498 |
interface.load(
|
| 499 |
+
fn=lambda: (game.engine.render_epic_frame(), game.get_status()),
|
| 500 |
outputs=[game_display, status_display]
|
| 501 |
)
|
| 502 |
|
| 503 |
return interface
|
| 504 |
|
| 505 |
if __name__ == "__main__":
|
| 506 |
+
interface = create_ultimate_interface()
|
| 507 |
+
interface.launch(share=True, show_error=True)
|