diff --git a/highway_vector.py b/highway_vector.py index 5623c98..ce2de08 100644 --- a/highway_vector.py +++ b/highway_vector.py @@ -1,11 +1,11 @@ -import sys import math +import os +import sys import pygame -# Inicialización de Pygame pygame.init() -# Configuración de pantalla +# Configuración de Pantalla SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 FPS = 60 @@ -16,106 +16,148 @@ TILE_HEIGHT = 32 GRID_SIZE_X = 8 GRID_SIZE_Y = 120 -# Colores de apoyo +# Colores de Respaldo / Suelo COLOR_BG = (15, 15, 25) COLOR_ROAD = (50, 50, 65) COLOR_GRID = (30, 30, 45) def grid_to_iso(gx, gy, gz=0): - """Convierte coordenadas 3D de rejilla en coordenadas 2D en pantalla.""" + """Convierte coordenadas de la cuadrícula a coordenadas isométricas de pantalla.""" iso_x = (gx - gy) * (TILE_WIDTH // 2) iso_y = (gx + gy) * (TILE_HEIGHT // 2) - (gz * 16) return iso_x, iso_y -# ============================================================================== -# GENERADOR DE SPRITES AUTOMÁTICO (O Carga desde PNG si existen) -# ============================================================================== -def load_or_create_sprite(filename, create_func): - """Intenta cargar una imagen PNG. Si no existe, la crea dinámicamente.""" - try: - image = pygame.image.load(filename).convert_alpha() - return image - except (pygame.error, FileNotFoundError): - return create_func() +def create_placeholder_sprites(): + """Genera sprites vectoriales en memoria si no existen archivos PNG locales.""" + sprites = {} + + # 1. Jugador (Lasertron) + surf_player = pygame.Surface((64, 64), pygame.SRCALPHA) + pygame.draw.polygon( + surf_player, + (255, 200, 0), + [(32, 8), (56, 24), (32, 56), (8, 24)], + ) + pygame.draw.polygon( + surf_player, + (255, 255, 255), + [(32, 8), (56, 24), (32, 56), (8, 24)], + 2, + ) + pygame.draw.circle(surf_player, (0, 255, 255), (32, 28), 6) + sprites["player"] = surf_player + + # 2. VORTEX + surf_vortex = pygame.Surface((64, 64), pygame.SRCALPHA) + pygame.draw.polygon( + surf_vortex, + (220, 50, 50), + [(32, 4), (60, 20), (52, 52), (12, 52)], + ) + pygame.draw.polygon( + surf_vortex, + (255, 255, 255), + [(32, 4), (60, 20), (52, 52), (12, 52)], + 2, + ) + pygame.draw.circle(surf_vortex, (255, 255, 0), (32, 28), 10) + sprites["vortex"] = surf_vortex + + # 3. Bloque empujable + surf_block = pygame.Surface((64, 64), pygame.SRCALPHA) + pygame.draw.polygon( + surf_block, + (180, 70, 40), + [(32, 12), (58, 26), (32, 40), (6, 26)], + ) + pygame.draw.polygon( + surf_block, + (120, 40, 20), + [(6, 26), (32, 40), (32, 58), (6, 44)], + ) + pygame.draw.polygon( + surf_block, + (150, 50, 30), + [(58, 26), (32, 40), (32, 58), (58, 44)], + ) + sprites["block"] = surf_block + + # 4. Enemigo Alienígena (Drone) + surf_enemy = pygame.Surface((64, 64), pygame.SRCALPHA) + pygame.draw.polygon( + surf_enemy, + (50, 200, 100), + [(32, 10), (54, 28), (32, 46), (10, 28)], + ) + pygame.draw.polygon( + surf_enemy, + (255, 255, 255), + [(32, 10), (54, 28), (32, 46), (10, 28)], + 2, + ) + pygame.draw.circle(surf_enemy, (255, 0, 0), (32, 28), 5) # Ojo rojo + sprites["enemy"] = surf_enemy + + # 5. Bala + surf_bullet = pygame.Surface((32, 32), pygame.SRCALPHA) + pygame.draw.circle(surf_bullet, (0, 255, 255), (16, 16), 5) + pygame.draw.circle(surf_bullet, (255, 255, 255), (16, 16), 2) + sprites["bullet"] = surf_bullet + + return sprites -def create_player_sprite(): - surf = pygame.Surface((64, 64), pygame.SRCALPHA) - # Cuerpo principal Lasertron - pygame.draw.ellipse(surf, (255, 200, 0), (16, 20, 32, 28)) - pygame.draw.ellipse(surf, (255, 255, 200), (20, 12, 24, 20)) - pygame.draw.circle(surf, (0, 200, 255), (32, 20), 6) # Visor - # Cañón de disparo - pygame.draw.rect(surf, (150, 150, 150), (30, 6, 4, 10)) - return surf +class SpriteManager: + + def __init__(self): + self.placeholders = create_placeholder_sprites() + self.cache = {} + + def get_sprite(self, name, filename): + if name in self.cache: + return self.cache[name] + + if os.path.exists(filename): + try: + img = pygame.image.load(filename).convert_alpha() + self.cache[name] = img + return img + except Exception as e: + print( + f"Error al cargar {filename}, usando respaldo vectorial: {e}" + ) + + self.cache[name] = self.placeholders[name] + return self.cache[name] -def create_vortex_sprite(): - surf = pygame.Surface((64, 64), pygame.SRCALPHA) - # Base pesada VORTEX - pygame.draw.ellipse(surf, (180, 40, 40), (8, 24, 48, 30)) - pygame.draw.ellipse(surf, (220, 60, 60), (14, 16, 36, 22)) - pygame.draw.circle(surf, (255, 255, 255), (32, 24), 8) - pygame.draw.circle(surf, (255, 0, 0), (32, 24), 4) - return surf - - -def create_block_sprite(): - surf = pygame.Surface((64, 64), pygame.SRCALPHA) - # Cara superior - pts_top = [(32, 8), (56, 20), (32, 32), (8, 20)] - pygame.draw.polygon(surf, (210, 90, 50), pts_top) - # Cara izquierda - pts_left = [(8, 20), (32, 32), (32, 52), (8, 40)] - pygame.draw.polygon(surf, (150, 60, 30), pts_left) - # Cara derecha - pts_right = [(32, 32), (56, 20), (56, 40), (32, 52)] - pygame.draw.polygon(surf, (180, 75, 40), pts_right) - # Bordes - pygame.draw.polygon(surf, (255, 255, 255), pts_top, 1) - return surf - - -def create_enemy_sprite(): - surf = pygame.Surface((64, 64), pygame.SRCALPHA) - # Dron Alienígena - pygame.draw.ellipse(surf, (50, 220, 100), (12, 16, 40, 32)) - pygame.draw.circle(surf, (255, 0, 100), (32, 30), 8) - pygame.draw.line(surf, (50, 220, 100), (20, 48), (12, 58), 3) - pygame.draw.line(surf, (50, 220, 100), (44, 48), (52, 58), 3) - return surf - - -def create_bullet_sprite(): - surf = pygame.Surface((32, 32), pygame.SRCALPHA) - pygame.draw.circle(surf, (0, 255, 255), (16, 16), 6) - pygame.draw.circle(surf, (255, 255, 255), (16, 16), 3) - return surf - - -# ============================================================================== -# CLASES DEL JUEGO -# ============================================================================== class Entity: - def __init__(self, gx, gy, sprite, is_solid=True): + + def __init__(self, gx, gy, sprite_name, filename, is_solid=True): self.gx = float(gx) self.gy = float(gy) self.gz = 0 - self.sprite = sprite + self.sprite_name = sprite_name + self.filename = filename self.is_solid = is_solid - def draw(self, surface, camera_offset_x, camera_offset_y): + def draw(self, surface, camera_offset_x, camera_offset_y, sprite_mgr): screen_x, screen_y = grid_to_iso(self.gx, self.gy, self.gz) - draw_x = screen_x + camera_offset_x - (self.sprite.get_width() // 2) - draw_y = screen_y + camera_offset_y - (self.sprite.get_height() // 2) - surface.blit(self.sprite, (draw_x, draw_y)) + sprite = sprite_mgr.get_sprite(self.sprite_name, self.filename) + + # Centrar la imagen con respecto a la casilla isométrica + draw_x = screen_x + camera_offset_x - (sprite.get_width() // 2) + draw_y = screen_y + camera_offset_y - (sprite.get_height() // 2) + + surface.blit(sprite, (draw_x, draw_y)) class Bullet(Entity): - def __init__(self, gx, gy, dir_x, dir_y, sprite): - super().__init__(gx, gy, sprite, is_solid=False) + + def __init__(self, gx, gy, dir_x, dir_y): + super().__init__(gx, gy, "bullet", "bullet.png", is_solid=False) self.dir_x = dir_x self.dir_y = dir_y self.speed = 0.25 @@ -131,7 +173,10 @@ class Bullet(Entity): for entity in entities: if entity != self and entity.is_solid: - if abs(entity.gx - self.gx) < 0.5 and abs(entity.gy - self.gy) < 0.5: + if ( + abs(entity.gx - self.gx) < 0.5 + and abs(entity.gy - self.gy) < 0.5 + ): self.alive = False if isinstance(entity, (PushBlock, Enemy)): entity.health -= 1 @@ -139,8 +184,9 @@ class Bullet(Entity): class PushBlock(Entity): - def __init__(self, gx, gy, sprite): - super().__init__(gx, gy, sprite, is_solid=True) + + def __init__(self, gx, gy): + super().__init__(gx, gy, "block", "block.png", is_solid=True) self.health = 2 def push(self, dx, dy, entities): @@ -152,7 +198,10 @@ class PushBlock(Entity): for entity in entities: if entity != self and entity.is_solid: - if abs(entity.gx - new_gx) < 0.5 and abs(entity.gy - new_gy) < 0.5: + if ( + abs(entity.gx - new_gx) < 0.5 + and abs(entity.gy - new_gy) < 0.5 + ): return False self.gx = new_gx @@ -161,15 +210,18 @@ class PushBlock(Entity): class Enemy(Entity): - def __init__(self, gx, gy, sprite): - super().__init__(gx, gy, sprite, is_solid=True) - self.speed = 0.03 + + def __init__(self, gx, gy): + super().__init__(gx, gy, "enemy", "enemy.png", is_solid=True) + self.speed = 0.04 self.direction = 1 self.health = 1 def update(self, entities): + # Movimiento de patrulla horizontal target_gx = self.gx + (self.speed * self.direction) + # Rebotar en los bordes de la carretera if target_gx <= 0.5 or target_gx >= GRID_SIZE_X - 0.5: self.direction *= -1 target_gx = self.gx + (self.speed * self.direction) @@ -178,9 +230,9 @@ class Enemy(Entity): class Player(Entity): - def __init__(self, gx, gy, sprite, bullet_sprite): - super().__init__(gx, gy, sprite, is_solid=True) - self.bullet_sprite = bullet_sprite + + def __init__(self, gx, gy): + super().__init__(gx, gy, "player", "player.png", is_solid=True) self.speed = 0.09 self.facing_x = 0.0 self.facing_y = -1.0 @@ -209,10 +261,17 @@ class Player(Entity): blocked = False for entity in entities: if entity != self and entity.is_solid: - if abs(entity.gx - target_gx) < 0.5 and abs(entity.gy - target_gy) < 0.5: + if ( + abs(entity.gx - target_gx) < 0.5 + and abs(entity.gy - target_gy) < 0.5 + ): if isinstance(entity, PushBlock): - push_dx = 0.15 if dx > 0 else (-0.15 if dx < 0 else 0) - push_dy = 0.15 if dy > 0 else (-0.15 if dy < 0 else 0) + push_dx = ( + 0.15 if dx > 0 else (-0.15 if dx < 0 else 0) + ) + push_dy = ( + 0.15 if dy > 0 else (-0.15 if dy < 0 else 0) + ) if not entity.push(push_dx, push_dy, entities): blocked = True else: @@ -227,13 +286,16 @@ class Player(Entity): if keys[pygame.K_SPACE] and self.shoot_cooldown == 0: spawn_x = self.gx + (self.facing_x * 0.7) spawn_y = self.gy + (self.facing_y * 0.7) - bullets.append(Bullet(spawn_x, spawn_y, self.facing_x, self.facing_y, self.bullet_sprite)) + bullets.append( + Bullet(spawn_x, spawn_y, self.facing_x, self.facing_y) + ) self.shoot_cooldown = 15 class Vortex(Entity): - def __init__(self, gx, gy, sprite): - super().__init__(gx, gy, sprite, is_solid=True) + + def __init__(self, gx, gy): + super().__init__(gx, gy, "vortex", "vortex.png", is_solid=True) self.speed = 0.012 def update(self, entities): @@ -242,7 +304,10 @@ class Vortex(Entity): blocked = False for entity in entities: if entity != self and entity.is_solid: - if abs(entity.gx - self.gx) < 0.6 and abs(entity.gy - target_gy) < 0.6: + if ( + abs(entity.gx - self.gx) < 0.6 + and abs(entity.gy - target_gy) < 0.6 + ): blocked = True break @@ -251,53 +316,65 @@ class Vortex(Entity): class Game: + def __init__(self): self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) - pygame.display.set_caption("Highway Encounter - PNG Sprites Version") + pygame.display.set_caption("Highway Encounter - Full Sprites & Enemies") self.clock = pygame.time.Clock() self.font = pygame.font.SysFont("monospace", 18, bold=True) - - # Carga o Generación de Sprites - self.spr_player = load_or_create_sprite("player.png", create_player_sprite) - self.spr_vortex = load_or_create_sprite("vortex.png", create_vortex_sprite) - self.spr_block = load_or_create_sprite("block.png", create_block_sprite) - self.spr_enemy = load_or_create_sprite("enemy.png", create_enemy_sprite) - self.spr_bullet = load_or_create_sprite("bullet.png", create_bullet_sprite) + self.sprite_mgr = SpriteManager() self.lives = 4 self.score = 0 + self.reset_level() def reset_level(self): - self.vortex = Vortex(3.5, GRID_SIZE_Y - 5, self.spr_vortex) - self.player = Player(3.5, GRID_SIZE_Y - 8, self.spr_player, self.spr_bullet) + self.vortex = Vortex(3.5, GRID_SIZE_Y - 5) + self.player = Player(3.5, GRID_SIZE_Y - 8) self.entities = [self.vortex, self.player] self.bullets = [] + # Escuadrón de reserva (vidas) self.spares = [] for i in range(self.lives - 1): - spare = Entity(3.5, GRID_SIZE_Y - 3 + (i * 1.2), self.spr_player, is_solid=False) + spare = Entity( + 3.5, + GRID_SIZE_Y - 3 + (i * 1.2), + "player", + "player.png", + is_solid=False, + ) self.spares.append(spare) - for y in range(10, GRID_SIZE_Y - 15, 6): - self.entities.append(PushBlock(y % GRID_SIZE_X, y, self.spr_block)) - if y % 12 == 0: - self.entities.append(Enemy(3.5, y - 2, self.spr_enemy)) + # Generar bloques y enemigos alternados a lo largo de la autopista + for y in range(10, GRID_SIZE_Y - 15, 5): + # Agregar bloques + self.entities.append(PushBlock(y % GRID_SIZE_X, y)) + + # Agregar enemigos cada 10 casillas + if y % 10 == 0: + self.entities.append(Enemy(3.5, y - 2)) def handle_collisions(self): + # Colisión con enemigos for entity in self.entities: if isinstance(entity, Enemy): - if abs(entity.gx - self.player.gx) < 0.5 and abs(entity.gy - self.player.gy) < 0.5: + if ( + abs(entity.gx - self.player.gx) < 0.5 + and abs(entity.gy - self.player.gy) < 0.5 + ): self.lives -= 1 if self.lives > 0: self.reset_level() return + # Limpiar entidades destruidas (bloques o enemigos con salud <= 0) for entity in self.entities[:]: if hasattr(entity, "health") and entity.health <= 0: self.entities.remove(entity) - self.score += 100 + self.score += 150 def draw_hud(self): zone = int((GRID_SIZE_Y - self.vortex.gy) / 4) @@ -317,8 +394,12 @@ class Game: if self.lives <= 0: self.screen.fill((0, 0, 0)) - msg = self.font.render("GAME OVER - Press R to Restart", True, (255, 50, 50)) - self.screen.blit(msg, (SCREEN_WIDTH // 2 - 150, SCREEN_HEIGHT // 2)) + msg = self.font.render( + "GAME OVER - Press R to Restart", True, (255, 50, 50) + ) + self.screen.blit( + msg, (SCREEN_WIDTH // 2 - 150, SCREEN_HEIGHT // 2) + ) pygame.display.flip() keys = pygame.key.get_pressed() @@ -328,6 +409,7 @@ class Game: self.reset_level() continue + # Actualización keys = pygame.key.get_pressed() self.player.update(keys, self.entities, self.bullets) self.vortex.update(self.entities) @@ -343,32 +425,49 @@ class Game: self.handle_collisions() + # Desplazamiento de cámara centrado en el jugador iso_x, iso_y = grid_to_iso(self.player.gx, self.player.gy) camera_offset_x = SCREEN_WIDTH // 2 - iso_x camera_offset_y = SCREEN_HEIGHT // 2 - iso_y + 100 + # Renderizado self.screen.fill(COLOR_BG) + # Dibujar mosaico de la carretera for gy in range(0, GRID_SIZE_Y): for gx in range(0, GRID_SIZE_X): sx, sy = grid_to_iso(gx, gy) draw_x = sx + camera_offset_x draw_y = sy + camera_offset_y - if -100 < draw_x < SCREEN_WIDTH + 100 and -100 < draw_y < SCREEN_HEIGHT + 100: + if ( + -100 < draw_x < SCREEN_WIDTH + 100 + and -100 < draw_y < SCREEN_HEIGHT + 100 + ): pts = [ (draw_x, draw_y), - (draw_x + TILE_WIDTH // 2, draw_y + TILE_HEIGHT // 2), + ( + draw_x + TILE_WIDTH // 2, + draw_y + TILE_HEIGHT // 2, + ), (draw_x, draw_y + TILE_HEIGHT), - (draw_x - TILE_WIDTH // 2, draw_y + TILE_HEIGHT // 2), + ( + draw_x - TILE_WIDTH // 2, + draw_y + TILE_HEIGHT // 2, + ), ] pygame.draw.polygon(self.screen, COLOR_ROAD, pts) pygame.draw.polygon(self.screen, COLOR_GRID, pts, 1) + # Dibujar elementos ordenados por profundidad (Y descendente) all_drawables = self.entities + self.bullets + self.spares - sorted_entities = sorted(all_drawables, key=lambda e: e.gy, reverse=True) + sorted_entities = sorted( + all_drawables, key=lambda e: e.gy, reverse=True + ) for entity in sorted_entities: - entity.draw(self.screen, camera_offset_x, camera_offset_y) + entity.draw( + self.screen, camera_offset_x, camera_offset_y, self.sprite_mgr + ) self.draw_hud() pygame.display.flip() @@ -378,4 +477,4 @@ class Game: if __name__ == "__main__": - Game().run() \ No newline at end of file + Game().run() diff --git a/img-versiones/version_1.1.png b/img-versiones/version_1.1.png new file mode 100644 index 0000000..cb6ccd5 Binary files /dev/null and b/img-versiones/version_1.1.png differ