Highway_Vector/highway_vector.py

535 lines
17 KiB
Python

import math
import os
import sys
import pygame
pygame.init()
# Configuración de Pantalla
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FPS = 60
# Isometría y Cuadrícula
TILE_WIDTH = 64
TILE_HEIGHT = 32
GRID_SIZE_X = 8
GRID_SIZE_Y = 120
# Colores del Entorno
COLOR_BG = (15, 15, 25)
COLOR_ROAD = (50, 50, 65)
COLOR_GRID = (30, 30, 45)
# Paleta de Colores de Entidades
COLOR_YELLOW = (255, 215, 0)
COLOR_WHITE = (240, 240, 240)
COLOR_RED = (220, 40, 40)
COLOR_BROWN = (139, 69, 19)
COLOR_ORANGE = (255, 140, 0)
def grid_to_iso(gx, gy, gz=0):
"""Convierte coordenadas 3D de rejilla a coordenadas 2D de pantalla."""
iso_x = (gx - gy) * (TILE_WIDTH // 2)
iso_y = (gx + gy) * (TILE_HEIGHT // 2) - (gz * 16)
return iso_x, iso_y
def draw_isometric_prism(surface, color, width=40, height=35):
"""Genera una superficie isométrica con forma de prisma o cubo."""
surf = pygame.Surface((64, 64), pygame.SRCALPHA)
cx, cy = 32, 32
w2 = width // 2
h2 = width // 4
# Cara Superior
top_pts = [
(cx, cy - height),
(cx + w2, cy - height + h2),
(cx, cy - height + 2 * h2),
(cx - w2, cy - height + h2),
]
# Cara Izquierda
left_pts = [
(cx - w2, cy - height + h2),
(cx, cy - height + 2 * h2),
(cx, cy + 2 * h2),
(cx - w2, cy + h2),
]
# Cara Derecha
right_pts = [
(cx, cy - height + 2 * h2),
(cx + w2, cy - height + h2),
(cx + w2, cy + h2),
(cx, cy + 2 * h2),
]
c_top = color
c_left = tuple(max(0, c - 40) for c in color)
c_right = tuple(max(0, c - 70) for c in color)
pygame.draw.polygon(surf, c_top, top_pts)
pygame.draw.polygon(surf, c_left, left_pts)
pygame.draw.polygon(surf, c_right, right_pts)
pygame.draw.polygon(surf, (20, 20, 20), top_pts, 1)
pygame.draw.polygon(surf, (20, 20, 20), left_pts, 1)
pygame.draw.polygon(surf, (20, 20, 20), right_pts, 1)
return surf
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)
pygame.draw.polygon(
surf_player, (200, 160, 0), [base_pts[3], base_pts[0], apex]
)
pygame.draw.polygon(
surf_player, (255, 215, 0), [base_pts[0], base_pts[1], apex]
)
pygame.draw.polygon(surf_player, (255, 235, 100), base_pts)
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)
# 4. Enemigo Indestructible: Prisma Blanco
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
)
sprites["enemy_brown"] = draw_isometric_prism(
None, COLOR_BROWN, width=32, height=35
)
sprites["enemy_orange"] = draw_isometric_prism(
None, COLOR_ORANGE, 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
return sprites
class SpriteManager:
def __init__(self):
self.placeholders = create_placeholder_sprites()
self.cache = {}
def get_sprite(self, name, filename=None):
if name in self.cache:
return self.cache[name]
if filename and 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 cargando {filename}: {e}")
self.cache[name] = self.placeholders[name]
return self.cache[name]
class Entity:
def __init__(self, gx, gy, sprite_name, filename=None, is_solid=True):
self.gx = float(gx)
self.gy = float(gy)
self.gz = 0
self.sprite_name = sprite_name
self.filename = filename
self.is_solid = is_solid
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)
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)
)
surface.blit(sprite, (draw_x, draw_y))
class Bullet(Entity):
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
self.alive = True
def update(self, entities):
self.gx += self.dir_x * self.speed
self.gy += self.dir_y * self.speed
if not (0 <= self.gx < GRID_SIZE_X and 0 <= self.gy < GRID_SIZE_Y):
self.alive = False
return
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
):
self.alive = False
if isinstance(entity, PushBlock):
entity.health -= 1
elif isinstance(entity, Enemy) and entity.destructible:
entity.take_damage()
break
class PushBlock(Entity):
def __init__(self, gx, gy):
super().__init__(gx, gy, "block", "block.png", is_solid=True)
self.health = 2
def push(self, dx, dy, entities):
new_gx = self.gx + dx
new_gy = self.gy + dy
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.5
and abs(entity.gy - new_gy) < 0.5
):
return False
self.gx = new_gx
self.gy = new_gy
return True
class Enemy(Entity):
def __init__(self, gx, gy, destructible=True):
sprite = "enemy_red" if destructible else "enemy_indestructible"
super().__init__(gx, gy, sprite, "enemy.png", is_solid=True)
self.speed = 0.04
self.direction = 1
self.destructible = destructible
self.health = 3 if destructible else 999
def take_damage(self):
if not self.destructible:
return
self.health -= 1
if self.health == 2:
self.sprite_name = "enemy_brown"
elif self.health == 1:
self.sprite_name = "enemy_orange"
def update(self, entities):
target_gx = self.gx + (self.speed * self.direction)
if target_gx <= 0.5 or target_gx >= GRID_SIZE_X - 0.5:
self.direction *= -1
return
for entity in entities:
if entity != self and entity.is_solid:
if (
abs(entity.gx - target_gx) < 0.6
and abs(entity.gy - self.gy) < 0.6
):
self.direction *= -1
return
self.gx = target_gx
class Player(Entity):
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
self.shoot_cooldown = 0
def update(self, keys, entities, bullets):
dx, dy = 0, 0
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
dx -= self.speed
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
dx += self.speed
if keys[pygame.K_UP] or keys[pygame.K_w]:
dy -= self.speed
if keys[pygame.K_DOWN] or keys[pygame.K_s]:
dy += self.speed
if dx != 0 or dy != 0:
norm = math.sqrt(dx * dx + dy * dy)
self.facing_x = dx / norm
self.facing_y = dy / norm
target_gx = self.gx + dx
target_gy = self.gy + dy
if 0 <= target_gx < GRID_SIZE_X and 0 <= target_gy < GRID_SIZE_Y:
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 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)
)
if not entity.push(push_dx, push_dy, entities):
blocked = True
else:
blocked = True
if not blocked:
self.gx = target_gx
self.gy = target_gy
if self.shoot_cooldown > 0:
self.shoot_cooldown -= 1
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.shoot_cooldown = 15
class Vortex(Entity):
def __init__(self, gx, gy):
super().__init__(gx, gy, "vortex", "vortex.png", is_solid=True)
self.speed = 0.012
def update(self, entities):
target_gy = self.gy - self.speed
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
):
blocked = True
break
if not blocked and target_gy >= 0:
self.gy = target_gy
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")
self.clock = pygame.time.Clock()
self.font = pygame.font.SysFont("monospace", 18, bold=True)
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.player = Player(3.5, GRID_SIZE_Y - 8)
self.entities = [self.vortex, self.player]
self.bullets = []
self.spares = []
for i in range(self.lives - 1):
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 - 12):
if y % 6 == 0:
lane = (y * 3) % GRID_SIZE_X
self.entities.append(PushBlock(lane, y))
if y % 14 == 0:
self.entities.append(Enemy(2.0, y, destructible=True))
if y % 20 == 0:
self.entities.append(Enemy(5.0, y, destructible=False))
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 = (
abs(entity.gx - self.player.gx) < 0.6
and abs(entity.gy - self.player.gy) < 0.6
)
vortex_hit = (
abs(entity.gx - self.vortex.gx) < 0.7
and abs(entity.gy - self.vortex.gy) < 0.7
)
if player_hit or vortex_hit:
return True # Devuelve True si hubo un impacto directo
# Limpiar bloques destruidos y enemigos muertos
for entity in self.entities[:]:
if hasattr(entity, "health") and entity.health <= 0:
self.entities.remove(entity)
self.score += 200
return False
def draw_hud(self):
zone = int((GRID_SIZE_Y - self.vortex.gy) / 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))
self.screen.blit(text_surface, (20, 10))
def run(self):
running = True
while running:
self.clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
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)
)
pygame.display.flip()
keys = pygame.key.get_pressed()
if keys[pygame.K_r]:
self.lives = 4
self.score = 0
self.reset_level()
continue
keys = pygame.key.get_pressed()
self.player.update(keys, self.entities, self.bullets)
self.vortex.update(self.entities)
for entity in self.entities:
if isinstance(entity, Enemy):
entity.update(self.entities)
for bullet in self.bullets[:]:
bullet.update(self.entities)
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
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
self.screen.fill(COLOR_BG)
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
):
pts = [
(draw_x, draw_y),
(
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,
),
]
pygame.draw.polygon(self.screen, COLOR_ROAD, pts)
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
)
for entity in sorted_entities:
entity.draw(
self.screen, camera_offset_x, camera_offset_y, self.sprite_mgr
)
self.draw_hud()
pygame.display.flip()
pygame.quit()
sys.exit()
if __name__ == "__main__":
Game().run()