import sys import math import random import pygame from pygame.locals import * from OpenGL.GL import * from OpenGL.GLU import * # ========================================== # CONFIGURACIÓN Y CONSTANTES # ========================================== WIDTH, HEIGHT = 800, 600 # Colores (R, G, B) COLOR_BG = (0.12, 0.15, 0.12) # Fondo ambiente COLOR_WATER = (0.0, 0.5, 0.85) # Agua animada (Cian/Azul) COLOR_LAND = (0.45, 0.32, 0.15) # Tierra / Relieve COLOR_LAND_RIVER = (0.40, 0.28, 0.12) # Tierra en riachuelos COLOR_ISLAND = (0.5, 0.38, 0.18) # Islas COLOR_REED = (0.15, 0.85, 0.25) # Juncos verdes COLOR_CATTAIL = (0.35, 0.18, 0.08) # Espigas marrones densas COLOR_DRAGONFLY = (0.0, 1.0, 1.0) # Libélula alámbrica (Cian) COLOR_MOSQUITO = (1.0, 0.9, 0.0) # Mosquitos (Amarillo) COLOR_FLY = (0.8, 0.25, 0.15) # Moscas (Rojo-Marrón) COLOR_FROG = (0.15, 0.85, 0.35) # Rana verde detallada COLOR_HUD = (1.0, 1.0, 1.0) # Retícula HUD (Blanco) # Dimensiones del Mundo POND_RADIUS = 1800.0 STREAM_LEN = 900.0 STREAM_MIN_Y = -(POND_RADIUS + STREAM_LEN) STREAM_MAX_Y = (POND_RADIUS + STREAM_LEN) WORLD_LEN = STREAM_MAX_Y - STREAM_MIN_Y # Cantidades de insectos MOSQUITO_MIN, MOSQUITO_MAX = 17, 27 FLY_MIN, FLY_MAX = 9, 14 # ========================================== # ELEMENTOS DEL MAPA # ========================================== REEDS = [ (-250, 350, 0, 20), (-160, -500, 0, 22), (500, 200, 0, 24), (-700, 200, 0, 21), (200, -800, 0, 23), (-900, -300, 0, 25), (760, -600, 0, 22), (900, 500, 0, 26), (-500, 900, 0, 23), (600, -1000, 0, 22), (-1200, 400, 0, 24), (1300, -300, 0, 20), (-1400, -700, 0, 25), (1100, 1200, 0, 23), (-360, -1300, 0, 22), (-90, -1950, 0, 20), (-110, -2150, 0, 23), (-80, -2350, 0, 19), (-100, -2550, 0, 22), (90, 1950, 0, 22), (110, 2150, 0, 20), (85, 2350, 0, 24), (105, 2550, 0, 21) ] ISLANDS = [ (-250, 350, 90), (-160, -500, 85), (500, 200, 100), (-700, 200, 110), (200, -800, 95), (-900, -300, 120), (760, -600, 90), (900, 500, 105), (-500, 900, 115), (600, -1000, 100) ] def get_river_center(y): if y < -POND_RADIUS: dy = y + POND_RADIUS return -50 + 100 * math.sin(dy * 0.008) elif y > POND_RADIUS: dy = y - POND_RADIUS return 50 + 100 * math.sin(dy * 0.008) else: return (y / POND_RADIUS) * 50.0 def is_over_water(x, y): dist_center = math.hypot(x, y) if dist_center < POND_RADIUS - 50: return True if abs(y) >= POND_RADIUS: cx = get_river_center(y) if abs(x - cx) < 80: return True return False def get_random_water_position(): while True: if random.random() < 0.7: rad = random.uniform(0, math.pi * 2) r = random.uniform(100, POND_RADIUS - 150) x = r * math.cos(rad) y = r * math.sin(rad) else: y = random.choice([random.uniform(POND_RADIUS, STREAM_MAX_Y), random.uniform(STREAM_MIN_Y, -POND_RADIUS)]) cx = get_river_center(y) x = cx + random.uniform(-60, 60) if is_over_water(x, y): z = random.uniform(5.0, 35.0) return x, y, z # ========================================== # CLASES DE INSECTOS Y RANA DECORATIVA # ========================================== class Insect: def __init__(self, insect_type): self.type = insect_type self.x, self.y, self.z = get_random_water_position() self.yaw = random.uniform(0, 360) self.timer = random.uniform(0, 100) if self.type == "mosquito": self.speed = 1.8 self.size = 1.2 else: self.speed = 0.9 self.size = 1.6 def update(self, player_pos): self.timer += 0.05 px, py, pz = player_pos dist_to_player = math.hypot(self.x - px, self.y - py) if self.type == "mosquito": can_detect = (pz <= self.z) if can_detect and dist_to_player < 100: escape_angle = math.degrees(math.atan2(self.y - py, self.x - px)) self.yaw += (escape_angle - self.yaw) * 0.05 self.yaw += math.sin(self.timer * 3) * 1.5 self.z += math.sin(self.timer * 1.5) * 0.2 elif self.type == "fly": if dist_to_player < 120: escape_angle = math.degrees(math.atan2(self.y - py, self.x - px)) self.yaw = escape_angle + random.uniform(-45, 45) self.z += random.choice([-1.5, 1.5]) else: if random.random() < 0.08: self.yaw += random.uniform(-60, 60) self.yaw += math.sin(self.timer * 6) * 4.0 self.z += math.cos(self.timer * 4) * 0.6 self.z = max(3.0, min(50.0, self.z)) rad = math.radians(self.yaw) next_x = self.x - math.sin(rad) * self.speed next_y = self.y + math.cos(rad) * self.speed if is_over_water(next_x, next_y): self.x, self.y = next_x, next_y else: self.yaw += 140.0 + random.uniform(-20, 20) if self.y > STREAM_MAX_Y: self.y -= WORLD_LEN elif self.y < STREAM_MIN_Y: self.y += WORLD_LEN def draw(self, attached=False): if self.type == "mosquito": glColor3f(*COLOR_MOSQUITO) else: glColor3f(*COLOR_FLY) glPushMatrix() if not attached: glTranslatef(self.x, self.y, self.z) glRotatef(self.yaw, 0, 0, 1) s = self.size glBegin(GL_LINES) glVertex3f(0, s, 0) glVertex3f(0, -s, 0) w_offset = 0.0 if attached else math.sin(self.timer * 30) * (s * 0.8) glVertex3f(0, 0, 0) glVertex3f(-s * 1.5, 0, w_offset) glVertex3f(0, 0, 0) glVertex3f(s * 1.5, 0, w_offset) glEnd() glPopMatrix() class Frog: def __init__(self): self.pick_new_destination() self.x, self.y = self.target_x, self.target_y self.z = 0.6 self.speed = 0.35 self.yaw = 0.0 self.swim_timer = 0.0 def pick_new_destination(self): destinations = [(rx, ry) for rx, ry, _, _ in REEDS] + [(ix, iy) for ix, iy, _ in ISLANDS] dest = random.choice(destinations) self.target_x, self.target_y = dest[0] + random.uniform(-30, 30), dest[1] + random.uniform(-30, 30) def update(self): self.swim_timer += 0.15 dx = self.target_x - self.x dy = self.target_y - self.y dist = math.hypot(dx, dy) if dist < 6.0: self.pick_new_destination() else: self.yaw = math.degrees(math.atan2(dy, dx)) - 90.0 self.x += (dx / dist) * self.speed self.y += (dy / dist) * self.speed def draw(self): glColor3f(*COLOR_FROG) glPushMatrix() glTranslatef(self.x, self.y, self.z) glRotatef(self.yaw, 0, 0, 1) leg_offset = math.sin(self.swim_timer) * 1.5 glBegin(GL_LINES) glVertex3f(0.0, 0.0, 0.5) glVertex3f(0.0, -5.0, 0.5) glVertex3f(-2.0, 2.0, 0.5) glVertex3f(2.0, 2.0, 0.5) glVertex3f(-2.5, -2.0, 0.5) glVertex3f(2.5, -2.0, 0.5) glVertex3f(-2.0, 2.0, 0.5) glVertex3f(0.0, 0.0, 0.5) glVertex3f(2.0, 2.0, 0.5) glVertex3f(0.0, 0.0, 0.5) glVertex3f(-2.0, 2.0, 0.6) glVertex3f(2.0, 2.0, 0.6) glVertex3f(-2.0, 2.0, 0.6) glVertex3f(0.0, 4.5, 0.6) glVertex3f(2.0, 2.0, 0.6) glVertex3f(0.0, 4.5, 0.6) glVertex3f(-1.5, 3.5, 1.5) glVertex3f(-1.5, 3.5, 0.6) glVertex3f(1.5, 3.5, 1.5) glVertex3f(1.5, 3.5, 0.6) glVertex3f(-2.0, 2.0, 0.5) glVertex3f(-4.0, 3.0, 0.0) glVertex3f(-4.0, 3.0, 0.0) glVertex3f(-3.5, 4.0, 0.0) glVertex3f(2.0, 2.0, 0.5) glVertex3f(4.0, 3.0, 0.0) glVertex3f(4.0, 3.0, 0.0) glVertex3f(3.5, 4.0, 0.0) glVertex3f(-2.5, -2.0, 0.5) glVertex3f(-5.0 - leg_offset, -4.0, 0.0) glVertex3f(-5.0 - leg_offset, -4.0, 0.0) glVertex3f(-3.0 - leg_offset, -6.0, 0.0) glVertex3f(2.5, -2.0, 0.5) glVertex3f(5.0 + leg_offset, -4.0, 0.0) glVertex3f(5.0 + leg_offset, -4.0, 0.0) glVertex3f(3.0 + leg_offset, -6.0, 0.0) glEnd() glPopMatrix() # ========================================== # RENDERIZADO DE ESCENARIO Y MODELOS # ========================================== def draw_water_surface(time_counter): glColor3f(*COLOR_WATER) grid_size = 15 for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size * 2): glBegin(GL_LINE_STRIP) for y in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size): dist = math.hypot(x, y) if dist < POND_RADIUS - 30: z = 1.0 * math.sin(dist * 0.015 - time_counter * 3.0) + 0.5 * math.cos(x * 0.01 + time_counter) glVertex3f(x, y, z) glEnd() for y in range(int(STREAM_MIN_Y), int(STREAM_MAX_Y), 15): if abs(y) < POND_RADIUS: continue cx = get_river_center(y) glBegin(GL_LINE_STRIP) for dx in range(-100, 110, 10): x = cx + dx z = 0.5 * math.sin(y * 0.03 + dx * 0.08 + time_counter * 4.0) glVertex3f(x, y, z) glEnd() def draw_terrain(): glColor3f(*COLOR_LAND) grid_step = 120 for y in range(int(STREAM_MIN_Y - 300), int(STREAM_MAX_Y + 300), grid_step): glBegin(GL_LINE_STRIP) for x in range(-2500, 2500, grid_step): z = 1.5 * math.sin(x * 0.005) + 1.5 * math.cos(y * 0.005) glVertex3f(x, y, z) glEnd() width_river = 100.0 margin_land = 70.0 glColor3f(*COLOR_LAND_RIVER) for y in range(int(STREAM_MIN_Y), int(STREAM_MAX_Y), 30): if abs(y) < POND_RADIUS: continue cx = get_river_center(y) glBegin(GL_LINE_STRIP) for dx in range(int(-width_river - margin_land), int(-width_river), 15): x = cx + dx z = 0.6 * math.sin(y * 0.02 + x * 0.02) + 0.3 glVertex3f(x, y, z) glEnd() glBegin(GL_LINE_STRIP) for dx in range(int(width_river), int(width_river + margin_land), 15): x = cx + dx z = 0.6 * math.sin(y * 0.02 + x * 0.02) + 0.3 glVertex3f(x, y, z) glEnd() for ix, iy, ir in ISLANDS: glColor3f(*COLOR_ISLAND) for layer in range(1, 4): rad_layer = ir * (1 - layer * 0.25) height = layer * 1.2 glBegin(GL_LINE_LOOP) for i in range(16): angle = 2.0 * math.pi * i / 16 dx = rad_layer * math.cos(angle) dy = rad_layer * math.sin(angle) glVertex3f(ix + dx, iy + dy, height) glEnd() def draw_map(time_counter): draw_terrain() draw_water_surface(time_counter) offsets = [(-1.5, 0.6), (1.3, -0.6), (-0.7, -1.4), (1.1, 1.2), (0.0, 1.6), (-1.2, 0.0)] for rx, ry, rz, rh in REEDS: glColor3f(*COLOR_REED) glBegin(GL_LINES) glVertex3f(rx, ry, rz) glVertex3f(rx, ry, rz + rh) for ox, oy in offsets: glVertex3f(rx, ry, rz) glVertex3f(rx + ox, ry + oy, rz + rh * 0.85) glEnd() glColor3f(*COLOR_CATTAIL) glBegin(GL_LINES) for d in [-0.2, 0.0, 0.2]: glVertex3f(rx + d, ry, rz + rh - 4.5) glVertex3f(rx + d, ry, rz + rh) for ox, oy in offsets: for d in [-0.15, 0.15]: glVertex3f(rx + ox + d, ry + oy, (rz + rh * 0.85) - 3.8) glVertex3f(rx + ox + d, ry + oy, rz + rh * 0.85) glEnd() def draw_dragonfly(wing_angle, caught_insect=None): glColor3f(*COLOR_DRAGONFLY) glBegin(GL_LINES) glVertex3f(0, 1.5, 0) glVertex3f(0, -3.0, 0) glVertex3f(-0.4, 0.6, 0) glVertex3f(0.4, 0.6, 0) glVertex3f(-0.3, -0.6, 0) glVertex3f(0.3, -0.6, 0) glVertex3f(-0.3, 0.5, 0) glVertex3f(-0.2, 0.2, -0.6) glVertex3f(0.3, 0.5, 0) glVertex3f(0.2, 0.2, -0.6) glVertex3f(-0.2, -0.2, 0) glVertex3f(-0.1, -0.3, -0.6) glVertex3f(0.2, -0.2, 0) glVertex3f(0.1, -0.3, -0.6) glEnd() glBegin(GL_LINE_LOOP) for i in range(8): angle = 2.0 * math.pi * i / 8 glVertex3f(0.3 * math.cos(angle), 1.4 + 0.3 * math.sin(angle), 0) glEnd() rad_wing = math.radians(wing_angle) w_z = math.sin(rad_wing) * 1.2 if wing_angle != 0 else 0.0 # Par de alas delanteras glBegin(GL_LINE_LOOP) glVertex3f(0, 0.6, 0) glVertex3f(-4.5, 1.0, w_z) glVertex3f(-3.8, 0.2, w_z) glEnd() glBegin(GL_LINE_LOOP) glVertex3f(0, 0.6, 0) glVertex3f(4.5, 1.0, w_z) glVertex3f(3.8, 0.2, w_z) glEnd() # Par de alas traseras (añadidas para completar las 4 alas) glBegin(GL_LINE_LOOP) glVertex3f(0, -0.1, 0) glVertex3f(-3.8, -0.2, w_z) glVertex3f(-3.0, -0.8, w_z) glEnd() glBegin(GL_LINE_LOOP) glVertex3f(0, -0.1, 0) glVertex3f(3.8, -0.2, w_z) glVertex3f(3.0, -0.8, w_z) glEnd() if caught_insect: glPushMatrix() glTranslatef(0.0, 0.2, -0.6) caught_insect.draw(attached=True) glPopMatrix() # ========================================== # INTERFAZ 2D / HUD Y PANTALLA INICIAL # ========================================== def draw_text_2d(text_surface, x, y): text_data = pygame.image.tostring(text_surface, "RGBA", True) w, h = text_surface.get_size() glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glRasterPos2i(x, y) glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, text_data) glDisable(GL_BLEND) def draw_start_screen(font_title, font_subtitle, font_text, wing_angle): glDisable(GL_DEPTH_TEST) glMatrixMode(GL_PROJECTION) glPushMatrix() glLoadIdentity() gluOrtho2D(0, WIDTH, 0, HEIGHT) glMatrixMode(GL_MODELVIEW) glPushMatrix() glLoadIdentity() # TÍTULO PRINCIPAL txt_title = font_title.render("LIBÉLULA", True, (0, 220, 255)) draw_text_2d(txt_title, WIDTH // 2 - 130, HEIGHT - 100) # DIBUJO ANIMADO DE LA LIBÉLULA DE 4 ALAS EN LA PANTALLA INICIAL glPushMatrix() glTranslatef(WIDTH // 2 + 150, HEIGHT - 75, 0.0) glScalef(12.0, 12.0, 1.0) glColor3f(*COLOR_DRAGONFLY) glBegin(GL_LINES) glVertex3f(0, 1.5, 0) glVertex3f(0, -3.0, 0) glVertex3f(-0.4, 0.6, 0) glVertex3f(0.4, 0.6, 0) glEnd() # 4 alas aleteando en el menú rad_w = math.radians(wing_angle) w_off = math.sin(rad_w) * 1.5 # Alas delanteras glBegin(GL_LINE_LOOP) glVertex3f(0, 0.6, 0) glVertex3f(-4.5, 1.0, w_off) glVertex3f(-3.8, 0.2, w_off) glEnd() glBegin(GL_LINE_LOOP) glVertex3f(0, 0.6, 0) glVertex3f(4.5, 1.0, w_off) glVertex3f(3.8, 0.2, w_off) glEnd() # Alas traseras glBegin(GL_LINE_LOOP) glVertex3f(0, -0.1, 0) glVertex3f(-3.8, -0.2, w_off) glVertex3f(-3.0, -0.8, w_off) glEnd() glBegin(GL_LINE_LOOP) glVertex3f(0, -0.1, 0) glVertex3f(3.8, -0.2, w_off) glVertex3f(3.0, -0.8, w_off) glEnd() glPopMatrix() # OBJETIVO DEL JUEGO obj_line1 = font_text.render("Objetivo: Vuela libre por el estanque y sus arroyos capturando moscas", True, (255, 255, 255)) obj_line2 = font_text.render("y mosquitos. Una vez cazados debes devorarlos posado en un junco.", True, (255, 255, 255)) draw_text_2d(obj_line1, 70, HEIGHT - 170) draw_text_2d(obj_line2, 70, HEIGHT - 195) # CONTROLES Y TECLAS controls = [ "CONTROLES:", "W / S : Acelerar / Frenar", "Flecha Arriba / Abajo : Cambiar altitud", "Flecha Izquierda / Derecha : Girar / Virar", "ESPACIO : Picado rápido para caza", "P : Aterrizaje automático en junco cercano", "ESC : Salir del juego" ] y_pos = HEIGHT - 250 for ctrl in controls: is_title = ctrl.endswith(":") col = (255, 220, 50) if is_title else (200, 200, 200) t_surf = font_subtitle.render(ctrl, True, col) if is_title else font_text.render(ctrl, True, col) draw_text_2d(t_surf, 70, y_pos) y_pos -= 25 # LEYENDA VISUAL INFERIOR (Mosquito, Mosca y Junco) y_icon = 90 # Icono Mosquito glColor3f(*COLOR_MOSQUITO) glBegin(GL_LINES) glVertex2f(100, y_icon + 5) glVertex2f(100, y_icon - 5) glVertex2f(95, y_icon) glVertex2f(105, y_icon) glEnd() txt_m = font_text.render("Mosquito", True, (255, 230, 0)) draw_text_2d(txt_m, 120, y_icon - 8) # Icono Mosca glColor3f(*COLOR_FLY) glBegin(GL_LINES) glVertex2f(280, y_icon + 5) glVertex2f(280, y_icon - 5) glVertex2f(275, y_icon - 3) glVertex2f(285, y_icon + 3) glVertex2f(275, y_icon + 3) glVertex2f(285, y_icon - 3) glEnd() txt_f = font_text.render("Mosca", True, (200, 65, 40)) draw_text_2d(txt_f, 300, y_icon - 8) # Icono Junco glColor3f(*COLOR_REED) glBegin(GL_LINES) glVertex2f(450, y_icon - 10) glVertex2f(450, y_icon + 15) glEnd() glColor3f(*COLOR_CATTAIL) glBegin(GL_LINES) glVertex2f(450, y_icon + 8) glVertex2f(450, y_icon + 15) glEnd() txt_r = font_text.render("Junco", True, (100, 220, 100)) draw_text_2d(txt_r, 470, y_icon - 8) # MENSAJE PARA COMENZAR start_msg = font_subtitle.render("Pulsa INTRO o cualquier tecla para comenzar...", True, (100, 255, 100)) draw_text_2d(start_msg, WIDTH // 2 - 200, 30) glMatrixMode(GL_PROJECTION) glPopMatrix() glMatrixMode(GL_MODELVIEW) glPopMatrix() glEnable(GL_DEPTH_TEST) def draw_hud(player_pos, yaw, insects, eaten_mosquitos, eaten_flies, font): glDisable(GL_DEPTH_TEST) glMatrixMode(GL_PROJECTION) glPushMatrix() glLoadIdentity() gluOrtho2D(0, WIDTH, 0, HEIGHT) glMatrixMode(GL_MODELVIEW) glPushMatrix() glLoadIdentity() px, py, pz = player_pos # RADAR REDUCIDO cx, cy = WIDTH // 2, HEIGHT - 35 radius = 22 glColor3f(*COLOR_HUD) glBegin(GL_LINE_LOOP) for i in range(24): angle = 2.0 * math.pi * i / 24 glVertex2f(cx + radius * math.cos(angle), cy + radius * math.sin(angle)) glEnd() glBegin(GL_LINES) glVertex2f(cx, cy + radius) glVertex2f(cx, cy + radius - 5) glVertex2f(cx, cy - radius) glVertex2f(cx, cy - radius + 5) glVertex2f(cx - radius, cy) glVertex2f(cx - radius + 5, cy) glVertex2f(cx + radius, cy) glVertex2f(cx + radius - 5, cy) glEnd() closest_insect = None min_dist = 999999.0 relative_angle = 0.0 diff_z = 0.0 for insect in insects: dx = insect.x - px dy = insect.y - py dist = math.hypot(dx, dy) if dist < min_dist and dist > 0.1: angle_to_insect = math.degrees(math.atan2(dy, dx)) heading = (angle_to_insect - (90 - yaw)) % 360 if heading > 180: heading -= 360 if abs(heading) < 60: min_dist = dist closest_insect = insect relative_angle = heading diff_z = insect.z - pz if closest_insect: radar_x = cx - (relative_angle / 60.0) * (radius - 4) radar_y = cy + (diff_z / 20.0) * (radius - 4) dist_from_center = math.hypot(radar_x - cx, radar_y - cy) if dist_from_center > (radius - 3): factor = (radius - 3) / dist_from_center radar_x = cx + (radar_x - cx) * factor radar_y = cy + (radar_y - cy) * factor glColor3f(1.0, 1.0, 1.0) glBegin(GL_LINE_LOOP) for i in range(8): ang = 2.0 * math.pi * i / 8 glVertex2f(radar_x + 3 * math.cos(ang), radar_y + 3 * math.sin(ang)) glEnd() # DIBUJOS ESTÁTICOS 2D DE LOS INSECTOS EN EL HUD glColor3f(*COLOR_MOSQUITO) glBegin(GL_LINES) glVertex2f(25, HEIGHT - 33) glVertex2f(25, HEIGHT - 27) glVertex2f(21, HEIGHT - 30) glVertex2f(29, HEIGHT - 30) glEnd() glColor3f(*COLOR_FLY) glBegin(GL_LINES) glVertex2f(WIDTH - 82, HEIGHT - 33) glVertex2f(WIDTH - 82, HEIGHT - 27) glVertex2f(WIDTH - 86, HEIGHT - 28) glVertex2f(WIDTH - 78, HEIGHT - 32) glVertex2f(WIDTH - 86, HEIGHT - 32) glVertex2f(WIDTH - 78, HEIGHT - 28) glEnd() # TEXTOS DE CONTADORES txt_mosq = font.render(f"x {eaten_mosquitos}", True, (255, 230, 0)) txt_flies = font.render(f"x {eaten_flies}", True, (200, 65, 40)) draw_text_2d(txt_mosq, 38, HEIGHT - 40) draw_text_2d(txt_flies, WIDTH - 68, HEIGHT - 40) glMatrixMode(GL_PROJECTION) glPopMatrix() glMatrixMode(GL_MODELVIEW) glPopMatrix() glEnable(GL_DEPTH_TEST) # ========================================== # BUCLE PRINCIPAL # ========================================== def main(): pygame.init() pygame.font.init() font = pygame.font.SysFont("Arial", 22, bold=True) font_title = pygame.font.SysFont("Arial", 48, bold=True) font_subtitle = pygame.font.SysFont("Arial", 20, bold=True) font_text = pygame.font.SysFont("Arial", 16, bold=False) pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) pygame.display.set_caption("Libélula") glEnable(GL_DEPTH_TEST) glClearColor(*COLOR_BG, 1.0) glMatrixMode(GL_PROJECTION) gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0) glMatrixMode(GL_MODELVIEW) # Estado de la Libélula px, py, pz = 0.0, 0.0, 15.0 yaw = 0.0 roll = 0.0 pitch = 0.0 max_roll = 30.0 max_pitch = 20.0 speed = 0.0 max_speed = 3.0 acceleration = 0.06 deceleration = 0.03 min_altitude = 1.0 max_altitude = 120.0 wing_timer = 0.0 water_timer = 0.0 is_perched = False dive_phase = "none" pre_dive_alt = 15.0 pre_dive_speed = 0.0 auto_landing = False target_perch = None caught_insect = None eaten_mosquitos = 0 eaten_flies = 0 frog = Frog() insects = [] num_mosquitos = random.randint(MOSQUITO_MIN, MOSQUITO_MAX) num_flies = random.randint(FLY_MIN, FLY_MAX) for _ in range(num_mosquitos): insects.append(Insect("mosquito")) for _ in range(num_flies): insects.append(Insect("fly")) clock = pygame.time.Clock() in_start_screen = True running = True while running: dt = clock.tick(60) / 1000.0 water_timer += dt wing_timer += 60.0 for event in pygame.event.get(): if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): running = False elif event.type == KEYDOWN: if in_start_screen: in_start_screen = False # Salir de la pantalla de inicio con cualquier tecla else: if event.key == K_SPACE: if not is_perched and dive_phase == "none": dive_phase = "diving" pre_dive_alt = pz pre_dive_speed = speed auto_landing = False elif event.key == K_p: if not is_perched and dive_phase == "none": best_reed = None min_d = 999.0 for rx, ry, rz, rh in REEDS: dist = math.hypot(px - rx, py - ry) if dist < 35.0: angle_to_reed = math.degrees(math.atan2(ry - py, rx - px)) heading = (angle_to_reed - (90 - yaw)) % 360 if heading > 180: heading -= 360 if abs(heading) < 45 and dist < min_d: min_d = dist best_reed = (rx, ry, rz + rh) if best_reed: target_perch = best_reed auto_landing = True glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLoadIdentity() if in_start_screen: cam_x, cam_y, cam_z = 0.0, -100.0, 30.0 gluLookAt(cam_x, cam_y, cam_z, 0, 0, 15, 0, 0, 1) draw_map(water_timer) frog.draw() wing_angle = math.sin(wing_timer * 0.5) * 35.0 draw_start_screen(font_title, font_subtitle, font_text, wing_angle) else: keys = pygame.key.get_pressed() if is_perched: if keys[K_LEFT]: yaw += 3.0 if keys[K_RIGHT]: yaw -= 3.0 if keys[K_w]: is_perched = False speed = 0.5 wing_angle = 0.0 pitch = 0.0 roll = 0.0 dive_phase = "none" auto_landing = False else: if auto_landing and target_perch: tx, ty, tz = target_perch dx = tx - px dy = ty - py dist_to_target = math.hypot(dx, dy) speed = 1.2 px += (dx / dist_to_target) * speed if dist_to_target > 0.1 else 0 py += (dy / dist_to_target) * speed if dist_to_target > 0.1 else 0 pz += (tz - pz) * 0.1 if dist_to_target < 2.5 and abs(pz - tz) < 2.5: px, py, pz = target_perch speed = 0.0 roll = 0.0 pitch = 0.0 is_perched = True auto_landing = False target_perch = None if caught_insect: if caught_insect.type == "mosquito": eaten_mosquitos += 1 else: eaten_flies += 1 caught_insect = None elif dive_phase == "diving": speed = max_speed * 1.5 pz -= 1.2 target_pitch = -45.0 if pz <= min_altitude: pz = min_altitude dive_phase = "recovering" elif dive_phase == "recovering": target_pitch = 30.0 pz += 0.8 speed += (pre_dive_speed - speed) * 0.1 if pz >= pre_dive_alt: pz = pre_dive_alt speed = pre_dive_speed dive_phase = "none" else: if keys[K_w]: speed = min(speed + acceleration, max_speed) elif keys[K_s]: speed = max(speed - deceleration, 0.0) if speed < 0.3: for rx, ry, rz, rh in REEDS: dist = math.hypot(px - rx, py - ry) if dist < 10.0 and abs(pz - (rz + rh)) < 10.0: px, py, pz = rx, ry, rz + rh speed = 0.0 roll = 0.0 pitch = 0.0 is_perched = True if caught_insect: if caught_insect.type == "mosquito": eaten_mosquitos += 1 else: eaten_flies += 1 caught_insect = None break target_pitch = 0.0 if keys[K_UP]: pz = min(pz + 0.8, max_altitude) target_pitch = max_pitch elif keys[K_DOWN]: pz = max(pz - 0.8, min_altitude) target_pitch = -max_pitch if not auto_landing: pitch += (target_pitch - pitch) * 0.15 target_roll = 0.0 if keys[K_LEFT]: yaw += 2.8 target_roll = -max_roll elif keys[K_RIGHT]: yaw -= 2.8 target_roll = max_roll roll += (target_roll - roll) * 0.15 rad_yaw = math.radians(yaw) px += -math.sin(rad_yaw) * speed py += math.cos(rad_yaw) * speed if abs(py) < POND_RADIUS: max_limit_x = POND_RADIUS + 200.0 else: cx = get_river_center(py) max_limit_x = abs(cx) + 200.0 px = max(-max_limit_x, min(max_limit_x, px)) if py > STREAM_MAX_Y: py -= WORLD_LEN elif py < STREAM_MIN_Y: py += WORLD_LEN wing_timer += 80.0 if (dive_phase != "none" or auto_landing) else (60.0 if speed > 0.1 else 30.0) wing_angle = math.sin(wing_timer) * 35.0 frog.update() current_mosquitos = 0 current_flies = 0 for insect in insects[:]: insect.update((px, py, pz)) if insect.type == "mosquito": current_mosquitos += 1 else: current_flies += 1 if not caught_insect: dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2) if dist_to_dragonfly < 6.0: caught_insect = insect insects.remove(insect) if dive_phase == "diving": dive_phase = "recovering" if current_mosquitos < MOSQUITO_MIN: insects.append(Insect("mosquito")) if current_flies < FLY_MIN: insects.append(Insect("fly")) cam_dist = 18.0 cam_height = 8.0 rad_yaw = math.radians(yaw) cam_x = px + math.sin(rad_yaw) * cam_dist cam_y = py - math.cos(rad_yaw) * cam_dist cam_z = pz + cam_height gluLookAt(cam_x, cam_y, cam_z, px, py, pz + 0.5, 0, 0, 1) draw_map(water_timer) frog.draw() for insect in insects: insect.draw() glPushMatrix() glTranslatef(px, py, pz) glRotatef(yaw, 0, 0, 1) glRotatef(pitch, 1, 0, 0) glRotatef(roll, 0, 1, 0) draw_dragonfly(wing_angle, caught_insect) glPopMatrix() draw_hud((px, py, pz), yaw, insects, eaten_mosquitos, eaten_flies, font) pygame.display.flip() pygame.quit() sys.exit() if __name__ == "__main__": main()