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 0000000..ff68380 Binary files /dev/null and b/img-versiones/version_4.0.png differ