From af1192232046d33efd97085be73095aefa9b6d0d Mon Sep 17 00:00:00 2001 From: luisgulo Date: Sun, 4 Oct 2026 15:28:22 +0200 Subject: [PATCH] 3er tipo enemigo y bloque morado solo se mueve con disparo --- highway_vector.py | 178 ++++++++++++++++++++++++++++------ img-versiones/version_4.0.png | Bin 0 -> 18823 bytes 2 files changed, 148 insertions(+), 30 deletions(-) create mode 100644 img-versiones/version_4.0.png diff --git a/highway_vector.py b/highway_vector.py index 29c6a02..8f0ad6b 100644 --- a/highway_vector.py +++ b/highway_vector.py @@ -21,12 +21,14 @@ COLOR_BG = (15, 15, 25) COLOR_ROAD = (50, 50, 65) COLOR_GRID = (30, 30, 45) -# Paleta de Colores de Entidades +# Paleta de Colores COLOR_YELLOW = (255, 215, 0) COLOR_WHITE = (240, 240, 240) +COLOR_PURPLE = (100, 30, 140) COLOR_RED = (220, 40, 40) COLOR_BROWN = (139, 69, 19) COLOR_ORANGE = (255, 140, 0) +COLOR_CYAN = (0, 200, 255) def grid_to_iso(gx, gy, gz=0): @@ -44,7 +46,6 @@ def draw_isometric_prism(surface, color, width=40, height=35): w2 = width // 2 h2 = width // 4 - # Cara Superior top_pts = [ (cx, cy - height), (cx + w2, cy - height + h2), @@ -52,7 +53,6 @@ def draw_isometric_prism(surface, color, width=40, height=35): (cx - w2, cy - height + h2), ] - # Cara Izquierda left_pts = [ (cx - w2, cy - height + h2), (cx, cy - height + 2 * h2), @@ -60,7 +60,6 @@ def draw_isometric_prism(surface, color, width=40, height=35): (cx - w2, cy + h2), ] - # Cara Derecha right_pts = [ (cx, cy - height + 2 * h2), (cx + w2, cy - height + h2), @@ -84,10 +83,8 @@ def draw_isometric_prism(surface, color, width=40, height=35): def create_placeholder_sprites(): - """Crea las formas geométricas vectoriales solicitadas.""" sprites = {} - # 1. Jugador: Pirámide Amarilla surf_player = pygame.Surface((64, 64), pygame.SRCALPHA) base_pts = [(32, 48), (52, 38), (32, 28), (12, 38)] apex = (32, 8) @@ -102,21 +99,23 @@ def create_placeholder_sprites(): pygame.draw.line(surf_player, (50, 50, 0), apex, base_pts[0], 2) sprites["player"] = surf_player - # 2. VORTEX: Cubo amarillo con bola encima surf_vortex = draw_isometric_prism(None, COLOR_YELLOW, width=38, height=25) pygame.draw.circle(surf_vortex, (255, 255, 200), (32, 12), 8) pygame.draw.circle(surf_vortex, (0, 0, 0), (32, 12), 8, 1) sprites["vortex"] = surf_vortex - # 3. Bloque Empujable: Cubo Blanco - sprites["block"] = draw_isometric_prism(None, COLOR_WHITE, width=40, height=25) + # Bloques + sprites["block_white"] = draw_isometric_prism( + None, COLOR_WHITE, width=40, height=25 + ) + sprites["block_purple"] = draw_isometric_prism( + None, COLOR_PURPLE, width=40, height=25 + ) - # 4. Enemigo Indestructible: Prisma Blanco + # Enemigos sprites["enemy_indestructible"] = draw_isometric_prism( None, COLOR_WHITE, width=32, height=35 ) - - # 5. Enemigos Destructibles sprites["enemy_red"] = draw_isometric_prism( None, COLOR_RED, width=32, height=35 ) @@ -126,8 +125,10 @@ def create_placeholder_sprites(): sprites["enemy_orange"] = draw_isometric_prism( None, COLOR_ORANGE, width=32, height=35 ) + sprites["enemy_diagonal"] = draw_isometric_prism( + None, COLOR_CYAN, width=32, height=35 + ) - # 6. Proyectil surf_bullet = pygame.Surface((32, 32), pygame.SRCALPHA) pygame.draw.circle(surf_bullet, (0, 255, 255), (16, 16), 4) sprites["bullet"] = surf_bullet @@ -171,9 +172,7 @@ class Entity: screen_x, screen_y = grid_to_iso(self.gx, self.gy, self.gz) sprite = sprite_mgr.get_sprite(self.sprite_name, self.filename) - # Centrar horizontalmente respecto a la casilla draw_x = screen_x + camera_offset_x - (sprite.get_width() // 2) - # Anclar en la BASE de la casilla (no en el centro) para que no se "hunda" draw_y = ( screen_y + camera_offset_y - sprite.get_height() + (TILE_HEIGHT // 2) ) @@ -205,17 +204,22 @@ class Bullet(Entity): and abs(entity.gy - self.gy) < 0.5 ): self.alive = False - if isinstance(entity, PushBlock): + if isinstance(entity, WhiteBlock): entity.health -= 1 + elif isinstance(entity, PurpleBlock): + # El bloque morado es empujado por el disparo + entity.push_by_bullet(self.dir_x, self.dir_y, entities) elif isinstance(entity, Enemy) and entity.destructible: entity.take_damage() break -class PushBlock(Entity): +class WhiteBlock(Entity): def __init__(self, gx, gy): - super().__init__(gx, gy, "block", "block.png", is_solid=True) + super().__init__( + gx, gy, "block_white", "block_white.png", is_solid=True + ) self.health = 2 def push(self, dx, dy, entities): @@ -238,6 +242,37 @@ class PushBlock(Entity): return True +class PurpleBlock(Entity): + + def __init__(self, gx, gy): + super().__init__( + gx, gy, "block_purple", "block_purple.png", is_solid=True + ) + + def push_by_bullet(self, dir_x, dir_y, entities): + """Se desplaza 1 casilla entera en la dirección del impacto del disparo.""" + step_x = 1 if dir_x > 0.3 else (-1 if dir_x < -0.3 else 0) + step_y = 1 if dir_y > 0.3 else (-1 if dir_y < -0.3 else 0) + + new_gx = self.gx + step_x + new_gy = self.gy + step_y + + if not (0 <= new_gx < GRID_SIZE_X and 0 <= new_gy < GRID_SIZE_Y): + return False + + for entity in entities: + if entity != self and entity.is_solid: + if ( + abs(entity.gx - new_gx) < 0.6 + and abs(entity.gy - new_gy) < 0.6 + ): + return False + + self.gx = new_gx + self.gy = new_gy + return True + + class Enemy(Entity): def __init__(self, gx, gy, destructible=True): @@ -277,6 +312,44 @@ class Enemy(Entity): self.gx = target_gx +class DiagonalEnemy(Enemy): + + def __init__(self, gx, gy): + super().__init__(gx, gy, destructible=False) + self.sprite_name = "enemy_diagonal" + self.speed_x = 0.035 + self.speed_y = 0.035 + self.dir_x = 1 + self.dir_y = 1 + self.min_y = gy - 6 + self.max_y = gy + 6 + + def update(self, entities): + target_gx = self.gx + (self.speed_x * self.dir_x) + target_gy = self.gy + (self.speed_y * self.dir_y) + + if target_gx <= 0.5 or target_gx >= GRID_SIZE_X - 0.5: + self.dir_x *= -1 + target_gx = self.gx + (self.speed_x * self.dir_x) + + if target_gy <= self.min_y or target_gy >= self.max_y: + self.dir_y *= -1 + target_gy = self.gy + (self.speed_y * self.dir_y) + + for entity in entities: + if entity != self and entity.is_solid: + if ( + abs(entity.gx - target_gx) < 0.6 + and abs(entity.gy - target_gy) < 0.6 + ): + self.dir_x *= -1 + self.dir_y *= -1 + return + + self.gx = target_gx + self.gy = target_gy + + class Player(Entity): def __init__(self, gx, gy): @@ -313,7 +386,8 @@ class Player(Entity): abs(entity.gx - target_gx) < 0.5 and abs(entity.gy - target_gy) < 0.5 ): - if isinstance(entity, PushBlock): + # Bloque Blanco: Se empuja caminando + if isinstance(entity, WhiteBlock): push_dx = ( 0.15 if dx > 0 else (-0.15 if dx < 0 else 0) ) @@ -322,6 +396,7 @@ class Player(Entity): ) if not entity.push(push_dx, push_dy, entities): blocked = True + # Bloque Morado y otros elementos: Bloquean el movimiento del jugador else: blocked = True if not blocked: @@ -367,13 +442,15 @@ class Game: def __init__(self): self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) - pygame.display.set_caption("Highway Encounter - Fixed Collision & Life System") + pygame.display.set_caption("Highway Encounter - Isometric Edition") self.clock = pygame.time.Clock() self.font = pygame.font.SysFont("monospace", 18, bold=True) + self.big_font = pygame.font.SysFont("monospace", 32, bold=True) self.sprite_mgr = SpriteManager() self.lives = 4 self.score = 0 + self.won = False self.reset_level() @@ -395,10 +472,15 @@ class Game: ) self.spares.append(spare) + # Generar mapa balanceado con bloques blancos y morados for y in range(10, GRID_SIZE_Y - 12): + # Alternar entre bloques Blancos (empujables) y Morados (desplazados por disparos) if y % 6 == 0: lane = (y * 3) % GRID_SIZE_X - self.entities.append(PushBlock(lane, y)) + if (y // 6) % 2 == 0: + self.entities.append(WhiteBlock(lane, y)) + else: + self.entities.append(PurpleBlock(lane, y)) if y % 14 == 0: self.entities.append(Enemy(2.0, y, destructible=True)) @@ -406,8 +488,10 @@ class Game: if y % 20 == 0: self.entities.append(Enemy(5.0, y, destructible=False)) + if y % 25 == 0: + self.entities.append(DiagonalEnemy(3.5, y)) + def handle_collisions(self): - # Detección precisa de colisión entre enemigos, jugador y VORTEX for entity in self.entities: if isinstance(entity, Enemy): player_hit = ( @@ -420,9 +504,9 @@ class Game: ) if player_hit or vortex_hit: - return True # Devuelve True si hubo un impacto directo + return True - # Limpiar bloques destruidos y enemigos muertos + # Eliminar bloques blancos destruidos y enemigos abatidos for entity in self.entities[:]: if hasattr(entity, "health") and entity.health <= 0: self.entities.remove(entity) @@ -431,7 +515,7 @@ class Game: return False def draw_hud(self): - zone = int((GRID_SIZE_Y - self.vortex.gy) / 4) + zone = max(0, int((self.vortex.gy - 10) / 4)) hud_text = f"ZONE: {zone:02d} | LIVES: {self.lives} | SCORE: {self.score:06d}" text_surface = self.font.render(hud_text, True, (255, 255, 255)) pygame.draw.rect(self.screen, (0, 0, 0), (0, 0, SCREEN_WIDTH, 40)) @@ -446,6 +530,37 @@ class Game: if event.type == pygame.QUIT: running = False + if self.won: + self.screen.fill((10, 30, 20)) + title = self.big_font.render( + "¡MISIÓN CUMPLIDA!", True, (100, 255, 100) + ) + subtitle = self.font.render( + "El VORTEX ha llegado con éxito a la base.", + True, + (220, 255, 220), + ) + score_msg = self.font.render( + f"Puntuación Final: {self.score:06d}", True, (255, 215, 0) + ) + restart_msg = self.font.render( + "Pulsa R para volver a jugar", True, (200, 200, 200) + ) + + self.screen.blit(title, (SCREEN_WIDTH // 2 - 170, 200)) + self.screen.blit(subtitle, (SCREEN_WIDTH // 2 - 210, 260)) + self.screen.blit(score_msg, (SCREEN_WIDTH // 2 - 130, 310)) + self.screen.blit(restart_msg, (SCREEN_WIDTH // 2 - 150, 380)) + pygame.display.flip() + + keys = pygame.key.get_pressed() + if keys[pygame.K_r]: + self.lives = 4 + self.score = 0 + self.won = False + self.reset_level() + continue + if self.lives <= 0: self.screen.fill((0, 0, 0)) msg = self.font.render( @@ -460,6 +575,7 @@ class Game: if keys[pygame.K_r]: self.lives = 4 self.score = 0 + self.won = False self.reset_level() continue @@ -467,6 +583,11 @@ class Game: self.player.update(keys, self.entities, self.bullets) self.vortex.update(self.entities) + if self.vortex.gy <= 10: + self.won = True + self.score += 5000 + continue + for entity in self.entities: if isinstance(entity, Enemy): entity.update(self.entities) @@ -476,12 +597,11 @@ class Game: if not bullet.alive: self.bullets.remove(bullet) - # Verificar colisión y procesar pérdida de vida if self.handle_collisions(): self.lives -= 1 if self.lives > 0: self.reset_level() - continue # Reinicia el ciclo sin renderizar el estado fallido + continue iso_x, iso_y = grid_to_iso(self.player.gx, self.player.gy) camera_offset_x = SCREEN_WIDTH // 2 - iso_x @@ -515,9 +635,7 @@ class Game: pygame.draw.polygon(self.screen, COLOR_GRID, pts, 1) all_drawables = self.entities + self.bullets + self.spares - sorted_entities = sorted( - all_drawables, key=lambda e: e.gy + e.gx - ) + sorted_entities = sorted(all_drawables, key=lambda e: e.gy + e.gx) for entity in sorted_entities: entity.draw( self.screen, camera_offset_x, camera_offset_y, self.sprite_mgr diff --git a/img-versiones/version_4.0.png b/img-versiones/version_4.0.png new file mode 100644 index 0000000000000000000000000000000000000000..ff6838030e1eb855a6db7dfca487709b82acc8b1 GIT binary patch literal 18823 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