diff --git a/esquema-mapa.png b/esquema-mapa.png new file mode 100644 index 0000000..a220ef3 Binary files /dev/null and b/esquema-mapa.png differ diff --git a/libelula.py b/libelula.py index 1bc9614..b5bc3b6 100755 --- a/libelula.py +++ b/libelula.py @@ -409,24 +409,30 @@ class Frog: def draw_water_surface(time_counter): glColor3f(*COLOR_WATER) - grid_size = 15 + # Cuadrícula más densa y fluida para el estanque principal + grid_size = 8 # Reducido de 15 a 8 para mayor resolución en las ondas 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) + int(POND_RADIUS)*2, grid_size): + for y in range(int(-POND_RADIUS), int(-POND_RADIUS) + int(POND_RADIUS)*2, grid_size * 2): 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) + # Combinación de ondas complejas para dar un aspecto orgánico y natural + z = (1.2 * math.sin(dist * 0.015 - time_counter * 3.5) + + 0.6 * math.cos(x * 0.02 + time_counter * 2.0) + + 0.4 * math.sin(y * 0.025 - time_counter * 1.8)) glVertex3f(x, y, z) glEnd() - for y in range(int(STREAM_MIN_Y), int(STREAM_MAX_Y), 15): + # Ondas mejoradas para los arroyos + for y in range(int(STREAM_MIN_Y), int(STREAM_MAX_Y), 20): if abs(y) < POND_RADIUS: continue cx = get_river_center(y) glBegin(GL_LINE_STRIP) - for dx in range(-100, 110, 10): + for dx in range(-80, 90, 8): x = cx + dx - z = 0.5 * math.sin(y * 0.03 + dx * 0.08 + time_counter * 4.0) + z = (0.8 * math.sin(y * 0.04 + dx * 0.1 + time_counter * 4.5) + + 0.3 * math.cos(x * 0.05 - time_counter * 2.5)) glVertex3f(x, y, z) glEnd() @@ -676,8 +682,8 @@ def draw_start_screen(font_title, font_subtitle, font_text, wing_angle, menu_win 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)) + obj_line1 = font_text.render("Objetivo: Vuela libre por el estanque y sus arroyos capturando moscas y mosquitos.", True, (255, 255, 255)) + obj_line2 = font_text.render("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) @@ -1165,7 +1171,7 @@ def main(): py += WORLD_LEN # Aleteo rápido cuando está volando o maniobrando - wing_timer += 140.0 if (dive_phase != "none" or auto_landing) else (60.0 if speed > 0.1 else 100.0) + wing_timer += 180.0 if (dive_phase != "none" or auto_landing) else (40.0 if speed > 0.1 else 140.0) wing_angle = math.sin(wing_timer) * 35.0 frog.update() diff --git a/previos/dragonfly_game_v3.0.py b/previos/dragonfly_game_v3.0.py new file mode 100644 index 0000000..2b8d994 --- /dev/null +++ b/previos/dragonfly_game_v3.0.py @@ -0,0 +1,555 @@ +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_DRAGONFLY = (0.0, 1.0, 1.0) # Libélula alámbrica (Cian) +COLOR_MOSQUITO = (1.0, 0.2, 0.2) # Mosquitos (Rojo) +COLOR_FLY = (1.0, 0.9, 0.0) # Moscas (Amarillo/Dorado) + +# Dimensiones del Mundo +POND_RADIUS = 1800.0 # Estanque de 1800u de superficie +STREAM_LEN = 900.0 # Longitud de cada arroyo +STREAM_MIN_Y = -(POND_RADIUS + STREAM_LEN) # -2700.0 +STREAM_MAX_Y = (POND_RADIUS + STREAM_LEN) # 2700.0 +WORLD_LEN = STREAM_MAX_Y - STREAM_MIN_Y + +# Cantidades de insectos +MOSQUITO_MIN, MOSQUITO_MAX = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# ELEMENTOS DEL MAPA +# ========================================== +REEDS = [ + # Juncos en islas del estanque principal + (-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), + # Juncos en el arroyo inferior + (-90, -1950, 0, 20), (-110, -2150, 0, 23), (-80, -2350, 0, 19), (-100, -2550, 0, 22), + # Juncos en el arroyo superior + (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): + """Devuelve el centro X del arroyo fuera de los límites del estanque central.""" + 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): + """Comprueba si unas coordenadas (x, y) están dentro del agua.""" + 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(): + """Genera una posición inicial aleatoria sobre el agua.""" + 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 +# ========================================== +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: # Fly + 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": + if 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) + # Cuerpo + glVertex3f(0, s, 0) + glVertex3f(0, -s, 0) + # Alas + 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() + +# ========================================== +# RENDERIZADO DEL ESCENARIO +# ========================================== +def draw_water_surface(time_counter): + """Dibuja una malla 3D animada para el agua.""" + glColor3f(*COLOR_WATER) + + grid_size = 30 + for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size): + 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), 20): + if abs(y) < POND_RADIUS: + continue + cx = get_river_center(y) + glBegin(GL_LINE_STRIP) + for dx in range(-100, 110, 15): + 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(): + """Dibuja el terreno del mapa.""" + 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) + + # Orilla Izquierda + 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() + + # Orilla Derecha + 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): + """Dibuja todo el mapa.""" + draw_terrain() + draw_water_surface(time_counter) + + glColor3f(*COLOR_REED) + glBegin(GL_LINES) + for rx, ry, rz, rh in REEDS: + glVertex3f(rx, ry, rz) + glVertex3f(rx, ry, rz + rh) + glVertex3f(rx, ry, rz) + glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7) + glVertex3f(rx, ry, rz) + glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8) + glEnd() + +# ========================================== +# MODELO 3D DE LA LIBÉLULA +# ========================================== +def draw_dragonfly(wing_angle, caught_insect=None): + """Dibuja la libélula y el insecto atrapado en sus patas si tiene uno.""" + glColor3f(*COLOR_DRAGONFLY) + + # Cuerpo + 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) + + # Patas (Visibles sujetando al insecto) + 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() + + # Cabeza + 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() + + # Alas + rad_wing = math.radians(wing_angle) + w_z = math.sin(rad_wing) * 1.2 if wing_angle != 0 else 0.0 + + 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() + + 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() + + # Si lleva un insecto atrapado, se dibuja sujeto bajo su tórax + if caught_insect: + glPushMatrix() + glTranslatef(0.0, 0.2, -0.6) # Posición bajo las patas + caught_insect.draw(attached=True) + glPopMatrix() + +# ========================================== +# BUCLE PRINCIPAL Y LÓGICA +# ========================================== +def main(): + pygame.init() + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + + 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 + + # Estado de Caza + caught_insect = None # Insecto sujeto actualmente en las patas + eaten_mosquitos = 0 # Contador de mosquitos comidos + eaten_flies = 0 # Contador de moscas comidas + + def update_title(): + status = " (Llevando presa a un junco...)" if caught_insect else "" + pygame.display.set_caption( + f"Libélula 3D | Mosquitos cazados: {eaten_mosquitos} | Moscas cazadas: {eaten_flies}{status}" + ) + + update_title() + + # Inicializar Insectos + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + for event in pygame.event.get(): + if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): + running = False + + keys = pygame.key.get_pressed() + + # CONTROLES Y MOVIMIENTO DE LA LIBÉLULA + if is_perched: + if keys[K_LEFT]: + yaw += 3.0 + if keys[K_RIGHT]: + yaw -= 3.0 + + if keys[K_UP]: + is_perched = False + speed = 0.5 + + wing_angle = 0.0 + pitch = 0.0 + roll = 0.0 + else: + if keys[K_UP]: + speed = min(speed + acceleration, max_speed) + elif keys[K_DOWN]: + speed = max(speed - deceleration, 0.0) + + # Intentar posarse en la punta de un junco + if speed < 0.3: + for rx, ry, rz, rh in REEDS: + dist = math.hypot(px - rx, py - ry) + if dist < 8.0 and abs(pz - (rz + rh)) < 8.0: + px, py, pz = rx, ry, rz + rh + speed = 0.0 + roll = 0.0 + pitch = 0.0 + is_perched = True + + # Si lleva una presa al posarse, se la come + if caught_insect: + if caught_insect.type == "mosquito": + eaten_mosquitos += 1 + else: + eaten_flies += 1 + caught_insect = None + update_title() + break + + target_pitch = 0.0 + if keys[K_w]: + pz = min(pz + 0.8, max_altitude) + target_pitch = max_pitch + elif keys[K_s]: + pz = max(pz - 0.8, min_altitude) + target_pitch = -max_pitch + + 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 += 60.0 if speed > 0.1 else 30.0 + wing_angle = math.sin(wing_timer) * 35.0 + + # ACTUALIZACIÓN DE INSECTOS + 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 + + # Detección de contacto/choque con la libélula + 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: # Rozamiento o choque + caught_insect = insect + insects.remove(insect) + update_title() + + # Mantener poblaciones dentro del rango + if current_mosquitos < MOSQUITO_MIN: + insects.append(Insect("mosquito")) + if current_flies < FLY_MIN: + insects.append(Insect("fly")) + + # RENDERIZADO + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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) + + # Dibujar Insectos en el mapa + for insect in insects: + insect.draw() + + # Dibujar Libélula (con el insecto en las patas si lo ha capturado) + 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() + + pygame.display.flip() + + pygame.quit() + sys.exit() + +if __name__ == "__main__": + main() diff --git a/previos/dragonfly_game_v4.1.py b/previos/dragonfly_game_v4.1.py new file mode 100644 index 0000000..4567d4f --- /dev/null +++ b/previos/dragonfly_game_v4.1.py @@ -0,0 +1,702 @@ +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_DRAGONFLY = (0.0, 1.0, 1.0) # Libélula alámbrica (Cian) +COLOR_MOSQUITO = (1.0, 0.2, 0.2) # Mosquitos (Rojo) +COLOR_FLY = (1.0, 0.9, 0.0) # Moscas (Amarillo/Dorado) +COLOR_HUD = (1.0, 1.0, 1.0) # Retícula HUD (Blanco) + +# Dimensiones del Mundo +POND_RADIUS = 1800.0 # Estanque de 1800u de superficie +STREAM_LEN = 900.0 # Longitud de cada arroyo +STREAM_MIN_Y = -(POND_RADIUS + STREAM_LEN) # -2700.0 +STREAM_MAX_Y = (POND_RADIUS + STREAM_LEN) # 2700.0 +WORLD_LEN = STREAM_MAX_Y - STREAM_MIN_Y + +# Cantidades de insectos +MOSQUITO_MIN, MOSQUITO_MAX = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# ELEMENTOS DEL MAPA +# ========================================== +REEDS = [ + # Juncos en islas del estanque principal + (-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), + # Juncos en el arroyo inferior + (-90, -1950, 0, 20), (-110, -2150, 0, 23), (-80, -2350, 0, 19), (-100, -2550, 0, 22), + # Juncos en el arroyo superior + (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): + """Devuelve el centro X del arroyo fuera de los límites del estanque central.""" + 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): + """Comprueba si unas coordenadas (x, y) están dentro del agua.""" + 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(): + """Genera una posición inicial aleatoria sobre el agua.""" + 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 +# ========================================== +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: # Fly + 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": + if 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() + +# ========================================== +# RENDERIZADO DE ESCENARIO Y MODELOS +# ========================================== +def draw_water_surface(time_counter): + glColor3f(*COLOR_WATER) + + grid_size = 30 + for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size): + 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), 20): + if abs(y) < POND_RADIUS: + continue + cx = get_river_center(y) + glBegin(GL_LINE_STRIP) + for dx in range(-100, 110, 15): + 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) + + glColor3f(*COLOR_REED) + glBegin(GL_LINES) + for rx, ry, rz, rh in REEDS: + glVertex3f(rx, ry, rz) + glVertex3f(rx, ry, rz + rh) + glVertex3f(rx, ry, rz) + glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7) + glVertex3f(rx, ry, rz) + glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8) + 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 + + 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() + + 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 / RADAR / HUD +# ========================================== +def draw_hud(player_pos, yaw, insects, eaten_mosquitos, eaten_flies, font): + """Dibuja el Radar central y los contadores en 2D superpuestos.""" + glDisable(GL_DEPTH_TEST) + + # Cambiar a proyección 2D ortogonal + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + gluOrtho2D(0, WIDTH, 0, HEIGHT) + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + glLoadIdentity() + + px, py, pz = player_pos + + # ---------------------------------- + # 1. RADAR / MIRILLA (Centro Superior) + # ---------------------------------- + cx, cy = WIDTH // 2, HEIGHT - 70 + radius = 45 + + # Dibujar Círculo exterior + glColor3f(*COLOR_HUD) + glBegin(GL_LINE_LOOP) + for i in range(32): + angle = 2.0 * math.pi * i / 32 + glVertex2f(cx + radius * math.cos(angle), cy + radius * math.sin(angle)) + glEnd() + + # Dibujar Cruz Central y Horizonte en línea discontinua + glBegin(GL_LINES) + glVertex2f(cx, cy + radius) + glVertex2f(cx, cy + radius - 10) + glVertex2f(cx, cy - radius) + glVertex2f(cx, cy - radius + 10) + glVertex2f(cx - radius, cy) + glVertex2f(cx - radius + 10, cy) + glVertex2f(cx + radius, cy) + glVertex2f(cx + radius - 10, cy) + glEnd() + + # Cruz pequeña central + glBegin(GL_LINES) + glVertex2f(cx - 4, cy) + glVertex2f(cx + 4, cy) + glVertex2f(cx, cy - 4) + glVertex2f(cx, cy + 4) + glEnd() + + # Buscar el insecto más cercano al frente + closest_insect = None + min_dist = 999999.0 + relative_angle = 0.0 + diff_z = 0.0 + + rad_yaw = math.radians(yaw) + # Vector dirección del jugador + dir_x = -math.sin(rad_yaw) + dir_y = math.cos(rad_yaw) + + 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: + # Calcular ángulo relativo respecto a hacia dónde mira la libélula + angle_to_insect = math.degrees(math.atan2(dy, dx)) + # Ajuste de ángulo respecto al YAW + heading = (angle_to_insect - (90 - yaw)) % 360 + if heading > 180: + heading -= 360 + + # Solo mostrar si está en un cono frontal de visión (120 grados) + if abs(heading) < 60: + min_dist = dist + closest_insect = insect + relative_angle = heading + diff_z = insect.z - pz # Por encima (+) o debajo (-) + + # Si hay una presa cercana al frente, dibujarla en la mirilla del radar + if closest_insect: + # Mapear posición horizontal (-60° a +60° -> -radius a +radius) + radar_x = cx - (relative_angle / 60.0) * (radius - 8) + # Mapear altura (-20u a +20u de diferencia -> -radius a +radius) + radar_y = cy + (diff_z / 20.0) * (radius - 8) + + # Clampear dentro del círculo del radar + dist_from_center = math.hypot(radar_x - cx, radar_y - cy) + if dist_from_center > (radius - 6): + factor = (radius - 6) / dist_from_center + radar_x = cx + (radar_x - cx) * factor + radar_y = cy + (radar_y - cy) * factor + + # Color del punto según la especie + if closest_insect.type == "mosquito": + glColor3f(*COLOR_MOSQUITO) + else: + glColor3f(*COLOR_FLY) + + # Dibujar punto e indicador de la presa + glBegin(GL_TRIANGLE_FAN) + for i in range(12): + ang = 2.0 * math.pi * i / 12 + glVertex2f(radar_x + 4 * math.cos(ang), radar_y + 4 * math.sin(ang)) + glEnd() + + # Pequeñas alas en el radar + glBegin(GL_LINES) + glVertex2f(radar_x - 7, radar_y) + glVertex2f(radar_x + 7, radar_y) + glEnd() + + # ---------------------------------- + # 2. ICONOS Y CONTADORES HUD + # ---------------------------------- + # Marcador Izquierdo: Mosquitos + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + # Dibujar pequeño ícono de mosquito + glVertex2f(25, HEIGHT - 25) + glVertex2f(25, HEIGHT - 45) + glVertex2f(25, HEIGHT - 35) + glVertex2f(10, HEIGHT - 35) + glVertex2f(25, HEIGHT - 35) + glVertex2f(40, HEIGHT - 35) + glEnd() + + # Marcador Derecho: Moscas + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + # Dibujar pequeño ícono de mosca + glVertex2f(WIDTH - 45, HEIGHT - 25) + glVertex2f(WIDTH - 45, HEIGHT - 45) + glVertex2f(WIDTH - 45, HEIGHT - 35) + glVertex2f(WIDTH - 60, HEIGHT - 35) + glVertex2f(WIDTH - 45, HEIGHT - 35) + glVertex2f(WIDTH - 30, HEIGHT - 35) + glEnd() + + # Restaurar matrices 3D + glPopMatrix() + glMatrixMode(GL_PROJECTION) + glPopMatrix() + glMatrixMode(GL_MODELVIEW) + glEnable(GL_DEPTH_TEST) + + # Renderizar el texto de los contadores mediante imágenes PyGame + txt_mosq = font.render(f"x {eaten_mosquitos}", True, (255, 60, 60)) + txt_flies = font.render(f"x {eaten_flies}", True, (255, 230, 0)) + + surface = pygame.display.get_surface() + + # Crear textura rápida para el texto en pantalla + draw_text_pygame(txt_mosq, 50, 20) + draw_text_pygame(txt_flies, WIDTH - 110, 20) + +def draw_text_pygame(text_surface, x, y): + """Dibuja texto de PyGame en la pantalla de OpenGL.""" + 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) + glRasterPos2d(x, HEIGHT - y - h) + glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, text_data) + glDisable(GL_BLEND) + +# ========================================== +# BUCLE PRINCIPAL Y LÓGICA +# ========================================== +def main(): + pygame.init() + pygame.font.init() + font = pygame.font.SysFont("Arial", 26, bold=True) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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 + + # Estado de Caza + caught_insect = None + eaten_mosquitos = 0 + eaten_flies = 0 + + # Inicializar Insectos + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + for event in pygame.event.get(): + if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): + running = False + + keys = pygame.key.get_pressed() + + # CONTROLES Y MOVIMIENTO DE LA LIBÉLULA + if is_perched: + if keys[K_LEFT]: + yaw += 3.0 + if keys[K_RIGHT]: + yaw -= 3.0 + + if keys[K_UP]: + is_perched = False + speed = 0.5 + + wing_angle = 0.0 + pitch = 0.0 + roll = 0.0 + else: + if keys[K_UP]: + speed = min(speed + acceleration, max_speed) + elif keys[K_DOWN]: + 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 < 8.0 and abs(pz - (rz + rh)) < 8.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_w]: + pz = min(pz + 0.8, max_altitude) + target_pitch = max_pitch + elif keys[K_s]: + pz = max(pz - 0.8, min_altitude) + target_pitch = -max_pitch + + 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 += 60.0 if speed > 0.1 else 30.0 + wing_angle = math.sin(wing_timer) * 35.0 + + # ACTUALIZACIÓN DE INSECTOS + 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 current_mosquitos < MOSQUITO_MIN: + insects.append(Insect("mosquito")) + if current_flies < FLY_MIN: + insects.append(Insect("fly")) + + # RENDERIZADO + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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) + + 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() + + # DIBUJAR INTERFAZ (HUD) Y RADAR + draw_hud((px, py, pz), yaw, insects, eaten_mosquitos, eaten_flies, font) + + pygame.display.flip() + + pygame.quit() + sys.exit() + +if __name__ == "__main__": + main() diff --git a/previos/dragonfly_game_v4.3.py b/previos/dragonfly_game_v4.3.py new file mode 100644 index 0000000..768da4e --- /dev/null +++ b/previos/dragonfly_game_v4.3.py @@ -0,0 +1,675 @@ +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_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_HUD = (1.0, 1.0, 1.0) # Retícula HUD (Blanco) + +# Dimensiones del Mundo +POND_RADIUS = 1800.0 # Estanque de 1800u de superficie +STREAM_LEN = 900.0 # Longitud de cada arroyo +STREAM_MIN_Y = -(POND_RADIUS + STREAM_LEN) # -2700.0 +STREAM_MAX_Y = (POND_RADIUS + STREAM_LEN) # 2700.0 +WORLD_LEN = STREAM_MAX_Y - STREAM_MIN_Y + +# Cantidades de insectos +MOSQUITO_MIN, MOSQUITO_MAX = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# ELEMENTOS DEL MAPA +# ========================================== +REEDS = [ + # Juncos en islas del estanque principal + (-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), + # Juncos en el arroyo inferior + (-90, -1950, 0, 20), (-110, -2150, 0, 23), (-80, -2350, 0, 19), (-100, -2550, 0, 22), + # Juncos en el arroyo superior + (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): + """Devuelve el centro X del arroyo fuera de los límites del estanque central.""" + 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): + """Comprueba si unas coordenadas (x, y) están dentro del agua.""" + 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(): + """Genera una posición inicial aleatoria sobre el agua.""" + 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 +# ========================================== +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: # Fly + 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": + if 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() + +# ========================================== +# RENDERIZADO DE ESCENARIO Y MODELOS +# ========================================== +def draw_water_surface(time_counter): + glColor3f(*COLOR_WATER) + + grid_size = 30 + for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size): + 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), 20): + if abs(y) < POND_RADIUS: + continue + cx = get_river_center(y) + glBegin(GL_LINE_STRIP) + for dx in range(-100, 110, 15): + 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) + + glColor3f(*COLOR_REED) + glBegin(GL_LINES) + for rx, ry, rz, rh in REEDS: + glVertex3f(rx, ry, rz) + glVertex3f(rx, ry, rz + rh) + glVertex3f(rx, ry, rz) + glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7) + glVertex3f(rx, ry, rz) + glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8) + 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 + + 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() + + 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 / RADAR REDUCIDO / HUD +# ========================================== +def draw_hud(player_pos, yaw, insects, eaten_mosquitos, eaten_flies, font): + """Dibuja el Radar reducido y los contadores en 2D superpuestos fijados arriba.""" + glDisable(GL_DEPTH_TEST) + + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + gluOrtho2D(0, WIDTH, 0, HEIGHT) + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + glLoadIdentity() + + px, py, pz = player_pos + + # ---------------------------------- + # 1. RADAR REDUCIDO A LA MITAD (Centro Superior) + # ---------------------------------- + cx, cy = WIDTH // 2, HEIGHT - 35 + radius = 22 # Tamaño reducido a la mitad (era 45) + + # Dibujar Círculo exterior + 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() + + # Dibujar Cruz Central + 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() + + # Cruz pequeña central + glBegin(GL_LINES) + glVertex2f(cx - 2, cy) + glVertex2f(cx + 2, cy) + glVertex2f(cx, cy - 2) + glVertex2f(cx, cy + 2) + glEnd() + + # Buscar el insecto más cercano al frente + closest_insect = None + min_dist = 999999.0 + relative_angle = 0.0 + diff_z = 0.0 + + rad_yaw = math.radians(yaw) + + 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 + + # Si hay una presa cercana al frente, se dibuja un pequeño círculo genérico blanco + 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 + + # Círculo vacío blanco (genérico) + 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() + + # ---------------------------------- + # 2. ICONOS Y CONTADORES HUD FIJOS + # ---------------------------------- + # Marcador Izquierdo: Mosquitos (Amarillo) + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(25, HEIGHT - 20) + glVertex2f(25, HEIGHT - 40) + glVertex2f(25, HEIGHT - 30) + glVertex2f(12, HEIGHT - 30) + glVertex2f(25, HEIGHT - 30) + glVertex2f(38, HEIGHT - 30) + glEnd() + + # Marcador Derecho: Moscas (Rojo-Marrón) + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(WIDTH - 45, HEIGHT - 20) + glVertex2f(WIDTH - 45, HEIGHT - 40) + glVertex2f(WIDTH - 45, HEIGHT - 30) + glVertex2f(WIDTH - 58, HEIGHT - 30) + glVertex2f(WIDTH - 45, HEIGHT - 30) + glVertex2f(WIDTH - 32, HEIGHT - 30) + glEnd() + + glPopMatrix() + glMatrixMode(GL_PROJECTION) + glPopMatrix() + glMatrixMode(GL_MODELVIEW) + glEnable(GL_DEPTH_TEST) + + # Textos fija de conteo + txt_mosq = font.render(f"x {eaten_mosquitos}", True, (255, 230, 0)) # Texto amarillo + txt_flies = font.render(f"x {eaten_flies}", True, (200, 65, 40)) # Texto rojo-marrón + + draw_text_pygame(txt_mosq, 48, 18) + draw_text_pygame(txt_flies, WIDTH - 110, 18) + +def draw_text_pygame(text_surface, x, y): + """Dibuja texto de PyGame en la pantalla de OpenGL.""" + 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) + glRasterPos2d(x, HEIGHT - y - h) + glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, text_data) + glDisable(GL_BLEND) + +# ========================================== +# BUCLE PRINCIPAL Y LÓGICA +# ========================================== +def main(): + pygame.init() + pygame.font.init() + font = pygame.font.SysFont("Arial", 22, bold=True) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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 + + # Estado de Caza + caught_insect = None + eaten_mosquitos = 0 + eaten_flies = 0 + + # Inicializar Insectos + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + for event in pygame.event.get(): + if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): + running = False + + keys = pygame.key.get_pressed() + + # CONTROLES Y MOVIMIENTO DE LA LIBÉLULA + if is_perched: + if keys[K_LEFT]: + yaw += 3.0 + if keys[K_RIGHT]: + yaw -= 3.0 + + if keys[K_UP]: + is_perched = False + speed = 0.5 + + wing_angle = 0.0 + pitch = 0.0 + roll = 0.0 + else: + if keys[K_UP]: + speed = min(speed + acceleration, max_speed) + elif keys[K_DOWN]: + 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 < 8.0 and abs(pz - (rz + rh)) < 8.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_w]: + pz = min(pz + 0.8, max_altitude) + target_pitch = max_pitch + elif keys[K_s]: + pz = max(pz - 0.8, min_altitude) + target_pitch = -max_pitch + + 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 += 60.0 if speed > 0.1 else 30.0 + wing_angle = math.sin(wing_timer) * 35.0 + + # ACTUALIZACIÓN DE INSECTOS + 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 current_mosquitos < MOSQUITO_MIN: + insects.append(Insect("mosquito")) + if current_flies < FLY_MIN: + insects.append(Insect("fly")) + + # RENDERIZADO + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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) + + 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() + + # DIBUJAR INTERFAZ (HUD) Y RADAR + draw_hud((px, py, pz), yaw, insects, eaten_mosquitos, eaten_flies, font) + + pygame.display.flip() + + pygame.quit() + sys.exit() + +if __name__ == "__main__": + main() diff --git a/previos/dragonfly_game_v5.0.py b/previos/dragonfly_game_v5.0.py new file mode 100644 index 0000000..f849066 --- /dev/null +++ b/previos/dragonfly_game_v5.0.py @@ -0,0 +1,668 @@ +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_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_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 = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# 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 +# ========================================== +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": + if 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() + +# ========================================== +# RENDERIZADO DE ESCENARIO Y MODELOS +# ========================================== +def draw_water_surface(time_counter): + glColor3f(*COLOR_WATER) + + grid_size = 30 + for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size): + 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), 20): + if abs(y) < POND_RADIUS: + continue + cx = get_river_center(y) + glBegin(GL_LINE_STRIP) + for dx in range(-100, 110, 15): + 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) + + glColor3f(*COLOR_REED) + glBegin(GL_LINES) + for rx, ry, rz, rh in REEDS: + glVertex3f(rx, ry, rz) + glVertex3f(rx, ry, rz + rh) + glVertex3f(rx, ry, rz) + glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7) + glVertex3f(rx, ry, rz) + glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8) + 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 + + 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() + + 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 / RADAR Y TEXTOS FIJOS EN PANTALLA +# ========================================== +def draw_text_2d(text_surface, x, y): + """Renderiza texto de PyGame fijado en coordenadas 2D absolutas de pantalla.""" + 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) + + #glRasterPos utiliza la matriz 2D activa + glRasterPos2i(x, y) + glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, text_data) + glDisable(GL_BLEND) + +def draw_hud(player_pos, yaw, insects, eaten_mosquitos, eaten_flies, font): + """Dibuja todo el HUD fijado en el plano 2D de la pantalla.""" + glDisable(GL_DEPTH_TEST) + + # 1. Configurar proyección 2D ortogonal estricta + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + gluOrtho2D(0, WIDTH, 0, HEIGHT) + + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + glLoadIdentity() + + px, py, pz = player_pos + + # ---------------------------------- + # A. RADAR REDUCIDO (Centro Superior) + # ---------------------------------- + cx, cy = WIDTH // 2, HEIGHT - 35 + radius = 22 + + # Círculo del radar + 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() + + # Cruz + 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() + + # Buscar presa cercana al frente + closest_insect = None + min_dist = 999999.0 + relative_angle = 0.0 + diff_z = 0.0 + + rad_yaw = math.radians(yaw) + + 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 + + # Círculo blanco genérico en radar + 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() + + # ---------------------------------- + # B. ÍCONOS SUPERIORES FIJOS + # ---------------------------------- + # Ícono Mosquito (Izquierda - Amarillo) + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(25, HEIGHT - 20) + glVertex2f(25, HEIGHT - 40) + glVertex2f(25, HEIGHT - 30) + glVertex2f(12, HEIGHT - 30) + glVertex2f(25, HEIGHT - 30) + glVertex2f(38, HEIGHT - 30) + glEnd() + + # Ícono Mosca (Derecha - Rojo/Marrón) + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(WIDTH - 85, HEIGHT - 20) + glVertex2f(WIDTH - 85, HEIGHT - 40) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 98, HEIGHT - 30) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 72, HEIGHT - 30) + glEnd() + + # ---------------------------------- + # C. RENDERIZADO DE TEXTOS 2D FIJOS + # ---------------------------------- + txt_mosq = font.render(f"x {eaten_mosquitos}", True, (255, 230, 0)) + txt_flies = font.render(f"x {eaten_flies}", True, (200, 65, 40)) + + # Posicionar texto exacto en coordenadas de pantalla 2D + draw_text_2d(txt_mosq, 45, HEIGHT - 40) + draw_text_2d(txt_flies, WIDTH - 65, HEIGHT - 40) + + # Restaurar matrices + 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) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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 + + # Estado de Caza + caught_insect = None + eaten_mosquitos = 0 + eaten_flies = 0 + + # Inicializar Insectos + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + for event in pygame.event.get(): + if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): + running = False + + keys = pygame.key.get_pressed() + + # CONTROLES Y MOVIMIENTO DE LA LIBÉLULA + if is_perched: + if keys[K_LEFT]: + yaw += 3.0 + if keys[K_RIGHT]: + yaw -= 3.0 + + if keys[K_UP]: + is_perched = False + speed = 0.5 + + wing_angle = 0.0 + pitch = 0.0 + roll = 0.0 + else: + if keys[K_UP]: + speed = min(speed + acceleration, max_speed) + elif keys[K_DOWN]: + 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 < 8.0 and abs(pz - (rz + rh)) < 8.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_w]: + pz = min(pz + 0.8, max_altitude) + target_pitch = max_pitch + elif keys[K_s]: + pz = max(pz - 0.8, min_altitude) + target_pitch = -max_pitch + + 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 += 60.0 if speed > 0.1 else 30.0 + wing_angle = math.sin(wing_timer) * 35.0 + + # ACTUALIZACIÓN DE INSECTOS + 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 current_mosquitos < MOSQUITO_MIN: + insects.append(Insect("mosquito")) + if current_flies < FLY_MIN: + insects.append(Insect("fly")) + + # RENDERIZADO + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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) + + 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() + + # DIBUJAR INTERFAZ (HUD) Y TEXTOS FIJOS EN 2D + draw_hud((px, py, pz), yaw, insects, eaten_mosquitos, eaten_flies, font) + + pygame.display.flip() + + pygame.quit() + sys.exit() + +if __name__ == "__main__": + main() diff --git a/previos/dragonfly_game_v5.3.py b/previos/dragonfly_game_v5.3.py new file mode 100644 index 0000000..5e7312e --- /dev/null +++ b/previos/dragonfly_game_v5.3.py @@ -0,0 +1,649 @@ +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_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_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 = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# 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 +# ========================================== +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: # Fly + 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": + # VENTAJA TÁCTICA: Si la libélula vuela por encima de ellos (pz > self.z), + # los mosquitos NO la detectan y no intentan huir. + 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": + # Las moscas sí detectan siempre a la libélula desde cualquier ángulo/altura + 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() + +# ========================================== +# RENDERIZADO DE ESCENARIO Y MODELOS +# ========================================== +def draw_water_surface(time_counter): + glColor3f(*COLOR_WATER) + + grid_size = 30 + for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size): + 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), 20): + if abs(y) < POND_RADIUS: + continue + cx = get_river_center(y) + glBegin(GL_LINE_STRIP) + for dx in range(-100, 110, 15): + 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) + + glColor3f(*COLOR_REED) + glBegin(GL_LINES) + for rx, ry, rz, rh in REEDS: + glVertex3f(rx, ry, rz) + glVertex3f(rx, ry, rz + rh) + glVertex3f(rx, ry, rz) + glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7) + glVertex3f(rx, ry, rz) + glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8) + 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 + + 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() + + 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 / RADAR Y TEXTOS FIJOS +# ========================================== +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_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 + + rad_yaw = math.radians(yaw) + + 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() + + # ÍCONOS SUPERIORES + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(25, HEIGHT - 20) + glVertex2f(25, HEIGHT - 40) + glVertex2f(25, HEIGHT - 30) + glVertex2f(12, HEIGHT - 30) + glVertex2f(25, HEIGHT - 30) + glVertex2f(38, HEIGHT - 30) + glEnd() + + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(WIDTH - 85, HEIGHT - 20) + glVertex2f(WIDTH - 85, HEIGHT - 40) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 98, HEIGHT - 30) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 32, HEIGHT - 30) + glEnd() + + # TEXTOS + 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, 45, HEIGHT - 40) + draw_text_2d(txt_flies, WIDTH - 65, 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) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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 + + caught_insect = None + eaten_mosquitos = 0 + eaten_flies = 0 + + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + for event in pygame.event.get(): + if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): + running = False + + 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_UP]: + is_perched = False + speed = 0.5 + + wing_angle = 0.0 + pitch = 0.0 + roll = 0.0 + else: + if keys[K_UP]: + speed = min(speed + acceleration, max_speed) + elif keys[K_DOWN]: + 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 < 8.0 and abs(pz - (rz + rh)) < 8.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_w]: + pz = min(pz + 0.8, max_altitude) + target_pitch = max_pitch + elif keys[K_s]: + pz = max(pz - 0.8, min_altitude) + target_pitch = -max_pitch + + 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 += 60.0 if speed > 0.1 else 30.0 + wing_angle = math.sin(wing_timer) * 35.0 + + 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 current_mosquitos < MOSQUITO_MIN: + insects.append(Insect("mosquito")) + if current_flies < FLY_MIN: + insects.append(Insect("fly")) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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) + + 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() diff --git a/previos/dragonfly_game_v5.4.py b/previos/dragonfly_game_v5.4.py new file mode 100644 index 0000000..702c37b --- /dev/null +++ b/previos/dragonfly_game_v5.4.py @@ -0,0 +1,676 @@ +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_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_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 = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# 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 +# ========================================== +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: # Fly + 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() + +# ========================================== +# RENDERIZADO DE ESCENARIO Y MODELOS +# ========================================== +def draw_water_surface(time_counter): + glColor3f(*COLOR_WATER) + + grid_size = 30 + for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size): + 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), 20): + if abs(y) < POND_RADIUS: + continue + cx = get_river_center(y) + glBegin(GL_LINE_STRIP) + for dx in range(-100, 110, 15): + 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) + + glColor3f(*COLOR_REED) + glBegin(GL_LINES) + for rx, ry, rz, rh in REEDS: + glVertex3f(rx, ry, rz) + glVertex3f(rx, ry, rz + rh) + glVertex3f(rx, ry, rz) + glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7) + glVertex3f(rx, ry, rz) + glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8) + 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 + + 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() + + 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 / RADAR Y TEXTOS FIJOS +# ========================================== +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_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 + + rad_yaw = math.radians(yaw) + + 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() + + # ÍCONOS SUPERIORES + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(25, HEIGHT - 20) + glVertex2f(25, HEIGHT - 40) + glVertex2f(25, HEIGHT - 30) + glVertex2f(12, HEIGHT - 30) + glVertex2f(25, HEIGHT - 30) + glVertex2f(38, HEIGHT - 30) + glEnd() + + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(WIDTH - 85, HEIGHT - 20) + glVertex2f(WIDTH - 85, HEIGHT - 40) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 98, HEIGHT - 30) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 32, HEIGHT - 30) + glEnd() + + # TEXTOS + 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, 45, HEIGHT - 40) + draw_text_2d(txt_flies, WIDTH - 65, 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) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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 + is_diving = False # Estado de picado automático + + caught_insect = None + eaten_mosquitos = 0 + eaten_flies = 0 + + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + for event in pygame.event.get(): + if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): + running = False + # Activar movimiento automático de PICADO al pulsar la barra ESPACIO + elif event.type == KEYDOWN and event.key == K_SPACE and not is_perched: + is_diving = True + + keys = pygame.key.get_pressed() + + if is_perched: + # En el junco, las flechas izquierda/derecha giran + if keys[K_LEFT]: + yaw += 3.0 + if keys[K_RIGHT]: + yaw -= 3.0 + + # Despegar con W (Acelerar) + if keys[K_w]: + is_perched = False + speed = 0.5 + + wing_angle = 0.0 + pitch = 0.0 + roll = 0.0 + is_diving = False + else: + # ---------------------------------- + # CONTROLES Y MOVIMIENTO REORDENADOS + # ---------------------------------- + if is_diving: + # MODO PICADO AUTOMÁTICO (Caída a 45º a máxima velocidad) + speed = max_speed * 1.5 + pz -= 1.2 + target_pitch = -45.0 + + # Desactivar picado al tocar suelo/agua o soltar control + if pz <= min_altitude: + pz = min_altitude + is_diving = False + else: + # Controles estándar + # W: Acelerar / S: Frenar (hasta vuelo estático) + if keys[K_w]: + speed = min(speed + acceleration, max_speed) + elif keys[K_s]: + speed = max(speed - deceleration, 0.0) + + # Si está casi detenida, intentar posarse en un junco cercano + if speed < 0.3: + for rx, ry, rz, rh in REEDS: + dist = math.hypot(px - rx, py - ry) + if dist < 8.0 and abs(pz - (rz + rh)) < 8.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 + + # Flecha Arriba: Subir altitud / Flecha Abajo: Bajar altitud + 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 + + # Transición suave de cabeceo (Pitch) + pitch += (target_pitch - pitch) * 0.15 + + # Flecha Izquierda / Derecha: Giro lateral + Alabeo (Roll) + 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 + + # Traslación hacia adelante según la dirección (Yaw) + rad_yaw = math.radians(yaw) + px += -math.sin(rad_yaw) * speed + py += math.cos(rad_yaw) * speed + + # Muros invisibles + 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)) + + # Bucle continuo en arroyos + if py > STREAM_MAX_Y: + py -= WORLD_LEN + elif py < STREAM_MIN_Y: + py += WORLD_LEN + + wing_timer += 80.0 if is_diving else (60.0 if speed > 0.1 else 30.0) + wing_angle = math.sin(wing_timer) * 35.0 + + 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 current_mosquitos < MOSQUITO_MIN: + insects.append(Insect("mosquito")) + if current_flies < FLY_MIN: + insects.append(Insect("fly")) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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) + + 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() diff --git a/previos/dragonfly_game_v5.5.py b/previos/dragonfly_game_v5.5.py new file mode 100644 index 0000000..1726e49 --- /dev/null +++ b/previos/dragonfly_game_v5.5.py @@ -0,0 +1,694 @@ +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_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_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 = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# 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 +# ========================================== +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: # Fly + 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() + +# ========================================== +# RENDERIZADO DE ESCENARIO Y MODELOS +# ========================================== +def draw_water_surface(time_counter): + glColor3f(*COLOR_WATER) + + grid_size = 30 + for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size): + 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), 20): + if abs(y) < POND_RADIUS: + continue + cx = get_river_center(y) + glBegin(GL_LINE_STRIP) + for dx in range(-100, 110, 15): + 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) + + glColor3f(*COLOR_REED) + glBegin(GL_LINES) + for rx, ry, rz, rh in REEDS: + glVertex3f(rx, ry, rz) + glVertex3f(rx, ry, rz + rh) + glVertex3f(rx, ry, rz) + glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7) + glVertex3f(rx, ry, rz) + glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8) + 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 + + 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() + + 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 / RADAR Y TEXTOS FIJOS +# ========================================== +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_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 + + rad_yaw = math.radians(yaw) + + 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() + + # ÍCONOS SUPERIORES + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(25, HEIGHT - 20) + glVertex2f(25, HEIGHT - 40) + glVertex2f(25, HEIGHT - 30) + glVertex2f(12, HEIGHT - 30) + glVertex2f(25, HEIGHT - 30) + glVertex2f(38, HEIGHT - 30) + glEnd() + + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(WIDTH - 85, HEIGHT - 20) + glVertex2f(WIDTH - 85, HEIGHT - 40) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 98, HEIGHT - 30) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 32, HEIGHT - 30) + glEnd() + + # TEXTOS + 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, 45, HEIGHT - 40) + draw_text_2d(txt_flies, WIDTH - 65, 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) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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 + + # Estado de Picado y Recuperación + dive_phase = "none" # "none", "diving" o "recovering" + pre_dive_alt = 15.0 # Almacena la altitud previa al picado + pre_dive_speed = 0.0 # Almacena la velocidad previa al picado + + caught_insect = None + eaten_mosquitos = 0 + eaten_flies = 0 + + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + for event in pygame.event.get(): + if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): + running = False + # Activar movimiento automático de PICADO al pulsar ESPACIO + elif event.type == KEYDOWN and event.key == K_SPACE and not is_perched and dive_phase == "none": + dive_phase = "diving" + pre_dive_alt = pz + pre_dive_speed = speed + + 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" + else: + # ---------------------------------- + # GESTIÓN DE FASES DE PICADO Y RECUPERACIÓN + # ---------------------------------- + if dive_phase == "diving": + # FASE 1: CAÍDA EN PICADO A 45º Y MÁXIMA VELOCIDAD + speed = max_speed * 1.5 + pz -= 1.2 + target_pitch = -45.0 + + # Pasa a recuperación si toca agua/suelo o alcanza el límite inferior + if pz <= min_altitude: + pz = min_altitude + dive_phase = "recovering" + + elif dive_phase == "recovering": + # FASE 2: RECUPERACIÓN AUTOMÁTICA HASTA ALTITUD Y VELOCIDAD PREVIAS + target_pitch = 30.0 # Inclinación ascendente + pz += 0.8 + + # Ajustar suavemente la velocidad a la inicial antes del picado + speed += (pre_dive_speed - speed) * 0.1 + + # Al alcanzar la altitud original o superior, finaliza la maniobra + if pz >= pre_dive_alt: + pz = pre_dive_alt + speed = pre_dive_speed + dive_phase = "none" + else: + # VUELO ESTÁNDAR + 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 < 8.0 and abs(pz - (rz + rh)) < 8.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 + + # Transición suave del ángulo Pitch + pitch += (target_pitch - pitch) * 0.15 + + # Giros laterales con Flechas Izquierda / Derecha + 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 + + # Traslación + rad_yaw = math.radians(yaw) + px += -math.sin(rad_yaw) * speed + py += math.cos(rad_yaw) * speed + + # Muros invisibles + 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" else (60.0 if speed > 0.1 else 30.0) + wing_angle = math.sin(wing_timer) * 35.0 + + 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) + + # SI CAZA MIENTRAS ESTÁ EN PICADO, INICIA INMEDIATAMENTE LA RECUPERACIÓN DE ALTITUD + 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")) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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) + + 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() diff --git a/previos/dragonfly_game_v6.1.py b/previos/dragonfly_game_v6.1.py new file mode 100644 index 0000000..41f4293 --- /dev/null +++ b/previos/dragonfly_game_v6.1.py @@ -0,0 +1,696 @@ +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) # Espiga marrón oscuro de los juncos +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_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 = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# 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 +# ========================================== +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() + +# ========================================== +# RENDERIZADO DE ESCENARIO Y MODELOS +# ========================================== +def draw_water_surface(time_counter): + glColor3f(*COLOR_WATER) + + grid_size = 30 + for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size): + 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), 20): + if abs(y) < POND_RADIUS: + continue + cx = get_river_center(y) + glBegin(GL_LINE_STRIP) + for dx in range(-100, 110, 15): + 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) + + # Juncos realistas con múltiples varas en distintas direcciones y espigas marrones (cattails) + for rx, ry, rz, rh in REEDS: + # Tallo principal vertical + glColor3f(*COLOR_REED) + glBegin(GL_LINES) + glVertex3f(rx, ry, rz) + glVertex3f(rx, ry, rz + rh) + + # Varas secundarias inclinadas en diferentes direcciones + offsets = [(-1.8, 0.8), (1.5, -0.7), (-0.8, -1.6), (1.2, 1.4)] + for ox, oy in offsets: + glVertex3f(rx, ry, rz) + glVertex3f(rx + ox, ry + oy, rz + rh * 0.8) + glEnd() + + # Espigas / cilindros marrón oscuro en la punta de cada vara + glColor3f(*COLOR_CATTAIL) + glBegin(GL_LINES) + # Punta principal + glVertex3f(rx, ry, rz + rh - 3.5) + glVertex3f(rx, ry, rz + rh) + # Puntas secundarias + for ox, oy in offsets: + glVertex3f(rx + ox, ry + oy, (rz + rh * 0.8) - 3.0) + glVertex3f(rx + ox, ry + oy, rz + rh * 0.8) + 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 + + 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() + + 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 / RADAR Y TEXTOS FIJOS (LIMPIA) +# ========================================== +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_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 + + # 1. RADAR REDUCIDO (Centro Superior) + 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 + + rad_yaw = math.radians(yaw) + + 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() + + # 2. ÍCONOS SUPERIORES (SIN CRUCES, MÁS LIMPIO) + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(25, HEIGHT - 30) + glVertex2f(12, HEIGHT - 30) + glVertex2f(25, HEIGHT - 30) + glVertex2f(38, HEIGHT - 30) + glEnd() + + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 98, HEIGHT - 30) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 72, HEIGHT - 30) + glEnd() + + # 3. TEXTOS + 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, 45, HEIGHT - 40) + draw_text_2d(txt_flies, WIDTH - 65, 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) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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 + + caught_insect = None + eaten_mosquitos = 0 + eaten_flies = 0 + + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + 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 and event.key == K_SPACE and not is_perched and dive_phase == "none": + dive_phase = "diving" + pre_dive_alt = pz + pre_dive_speed = speed + + keys = pygame.key.get_pressed() + + if is_perched: + # Flecha Izquierda / Derecha para girar posada + if keys[K_LEFT]: + yaw += 3.0 + if keys[K_RIGHT]: + yaw -= 3.0 + + # W para acelerar y despegar + if keys[K_w]: + is_perched = False + speed = 0.5 + + wing_angle = 0.0 + pitch = 0.0 + roll = 0.0 + dive_phase = "none" + else: + if 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: + # W: Acelerar / S: Frenar + 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) + # Radio de detección ampliado y ajustado a la altura del junco para facilitar el aterrizaje + 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 + + # Flecha Arriba: Subir / Flecha Abajo: Bajar + 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 + + pitch += (target_pitch - pitch) * 0.15 + + # Flecha Izquierda / Derecha: Moverse lateralmente o girar en vuelo + 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" else (60.0 if speed > 0.1 else 30.0) + wing_angle = math.sin(wing_timer) * 35.0 + + 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")) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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) + + 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() diff --git a/previos/dragonfly_game_v6.2.py b/previos/dragonfly_game_v6.2.py new file mode 100644 index 0000000..9f1571d --- /dev/null +++ b/previos/dragonfly_game_v6.2.py @@ -0,0 +1,736 @@ +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_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 = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# 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 +# ========================================== +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() + +# ========================================== +# RENDERIZADO DE ESCENARIO Y MODELOS +# ========================================== +def draw_water_surface(time_counter): + glColor3f(*COLOR_WATER) + + grid_size = 30 + for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size): + 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), 20): + if abs(y) < POND_RADIUS: + continue + cx = get_river_center(y) + glBegin(GL_LINE_STRIP) + for dx in range(-100, 110, 15): + 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 + + 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() + + 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 +# ========================================== +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_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() + + # ÍCONOS SUPERIORES + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(25, HEIGHT - 30) + glVertex2f(12, HEIGHT - 30) + glVertex2f(25, HEIGHT - 30) + glVertex2f(38, HEIGHT - 30) + glEnd() + + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 98, HEIGHT - 30) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 72, HEIGHT - 30) + glEnd() + + # TEXTOS + 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, 45, HEIGHT - 40) + draw_text_2d(txt_flies, WIDTH - 65, 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) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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 + + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + 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 and 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.type == KEYDOWN and 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 + + 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 + + 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")) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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) + + 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() diff --git a/previos/dragonfly_game_v6.3.py b/previos/dragonfly_game_v6.3.py new file mode 100644 index 0000000..0b8c8b8 --- /dev/null +++ b/previos/dragonfly_game_v6.3.py @@ -0,0 +1,797 @@ +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.1, 0.9, 0.3) # Rana verde decorativa +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 = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# 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): + # Seleccionar dos puntos aleatorios de inicio y destino entre islas y juncos + self.pick_new_destination() + self.x, self.y = self.target_x, self.target_y + self.z = 0.5 + self.speed = 0.4 + self.yaw = 0.0 + + def pick_new_destination(self): + # Destinos posibles: islas o posiciones de juncos + 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(-20, 20), dest[1] + random.uniform(-20, 20) + + def update(self): + dx = self.target_x - self.x + dy = self.target_y - self.y + dist = math.hypot(dx, dy) + + if dist < 5.0: + self.pick_new_destination() + else: + self.yaw = math.degrees(math.atan2(dy, dx)) + 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) + + # Pequeño modelo alámbrico de rana + glBegin(GL_LINES) + # Cuerpo + glVertex3f(0, 2.0, 0) + glVertex3f(0, -2.0, 0) + glVertex3f(-1.5, 1.0, 0) + glVertex3f(1.5, 1.0, 0) + glVertex3f(-1.5, -1.0, 0) + glVertex3f(1.5, -1.0, 0) + # Ojos saltones + glVertex3f(-0.8, 1.8, 0.5) + glVertex3f(-0.8, 1.8, 0) + glVertex3f(0.8, 1.8, 0.5) + glVertex3f(0.8, 1.8, 0) + glEnd() + + glPopMatrix() + +# ========================================== +# RENDERIZADO DE ESCENARIO Y MODELOS +# ========================================== +def draw_water_surface(time_counter): + """Malla de agua con mayor densidad de líneas para mayor realismo visual.""" + glColor3f(*COLOR_WATER) + + grid_size = 15 # Reducido de 30 a 15 para duplicar la densidad de líneas + 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 + + 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() + + 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 +# ========================================== +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_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() + + # ÍCONOS SUPERIORES + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(25, HEIGHT - 30) + glVertex2f(12, HEIGHT - 30) + glVertex2f(25, HEIGHT - 30) + glVertex2f(38, HEIGHT - 30) + glEnd() + + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 98, HEIGHT - 30) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 72, HEIGHT - 30) + glEnd() + + # TEXTOS + 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, 45, HEIGHT - 40) + draw_text_2d(txt_flies, WIDTH - 65, 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) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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 + + # Instanciar elementos y animales + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + 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 and 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.type == KEYDOWN and 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 + + 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 + + # Actualizar rana y ambiente + 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")) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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) + + # Dibujar rana sobre el agua + 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() diff --git a/previos/dragonfly_game_v6.4.py b/previos/dragonfly_game_v6.4.py new file mode 100644 index 0000000..1489d4c --- /dev/null +++ b/previos/dragonfly_game_v6.4.py @@ -0,0 +1,828 @@ +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 = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# 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 + + 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): + 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)) + 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) + + # Modelo 3D de rana ampliado y detallado (Anatomía reconocible) + glBegin(GL_LINES) + # Tronco / Cuerpo robusto y ancho + glVertex3f(0.0, 5.0, 0.5) + glVertex3f(0.0, -4.0, 0.5) + glVertex3f(-3.0, 2.0, 0.5) + glVertex3f(3.0, 2.0, 0.5) + glVertex3f(-3.5, -2.0, 0.5) + glVertex3f(3.5, -2.0, 0.5) + + # Conexión lomo + glVertex3f(-3.0, 2.0, 0.5) + glVertex3f(0.0, 5.0, 0.5) + glVertex3f(3.0, 2.0, 0.5) + glVertex3f(0.0, 5.0, 0.5) + + # Cabeza ancha + glVertex3f(-2.5, 5.0, 0.6) + glVertex3f(2.5, 5.0, 0.6) + glVertex3f(-2.5, 5.0, 0.6) + glVertex3f(0.0, 7.5, 0.6) + glVertex3f(2.5, 5.0, 0.6) + glVertex3f(0.0, 7.5, 0.6) + + # Ojos saltones prominentes + glVertex3f(-2.0, 6.5, 1.8) + glVertex3f(-2.0, 6.5, 0.8) + glVertex3f(2.0, 6.5, 1.8) + glVertex3f(2.0, 6.5, 0.8) + + # Patas delanteras flexionadas + glVertex3f(-3.0, 2.0, 0.5) + glVertex3f(-5.0, 3.5, 0.0) + glVertex3f(-5.0, 3.5, 0.0) + glVertex3f(-4.5, 4.5, 0.0) + + glVertex3f(3.0, 2.0, 0.5) + glVertex3f(5.0, 3.5, 0.0) + glVertex3f(5.0, 3.5, 0.0) + glVertex3f(4.5, 4.5, 0.0) + + # Patas traseras musculosas y plegadas + glVertex3f(-3.5, -2.0, 0.5) + glVertex3f(-6.5, -4.0, 0.0) + glVertex3f(-6.5, -4.0, 0.0) + glVertex3f(-4.0, -6.0, 0.0) + + glVertex3f(3.5, -2.0, 0.5) + glVertex3f(6.5, -4.0, 0.0) + glVertex3f(6.5, -4.0, 0.0) + glVertex3f(4.0, -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 + + 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() + + 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 +# ========================================== +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_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() + + # ÍCONOS SUPERIORES + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(25, HEIGHT - 30) + glVertex2f(12, HEIGHT - 30) + glVertex2f(25, HEIGHT - 30) + glVertex2f(38, HEIGHT - 30) + glEnd() + + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 98, HEIGHT - 30) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 72, HEIGHT - 30) + glEnd() + + # TEXTOS + 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, 45, HEIGHT - 40) + draw_text_2d(txt_flies, WIDTH - 65, 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) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + 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 and 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.type == KEYDOWN and 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 + + 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")) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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() diff --git a/previos/dragonfly_game_v6.5.py b/previos/dragonfly_game_v6.5.py new file mode 100644 index 0000000..96fe80b --- /dev/null +++ b/previos/dragonfly_game_v6.5.py @@ -0,0 +1,834 @@ +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 = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# 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: + # Apuntar la cabeza hacia la dirección del movimiento (ajustado 90 grados para orientar el frente del modelo) + 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) + + # Offset de animación de las patas traseras para simular el nado + leg_offset = math.sin(self.swim_timer) * 1.5 + + # Modelo 3D de rana orientado al frente y con patas traseras móviles + glBegin(GL_LINES) + # Tronco / Cuerpo robusto + 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) + + # Conexión lomo + 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) + + # Cabeza (apuntando hacia adelante / eje Y positivo) + 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) + + # Ojos saltones prominentes + 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) + + # Patas delanteras estáticas + 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) + + # Patas traseras móviles simulando brazada/patada de natación + 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 + + 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() + + 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 +# ========================================== +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_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() + + # ÍCONOS SUPERIORES + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(25, HEIGHT - 30) + glVertex2f(12, HEIGHT - 30) + glVertex2f(25, HEIGHT - 30) + glVertex2f(38, HEIGHT - 30) + glEnd() + + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 98, HEIGHT - 30) + glVertex2f(WIDTH - 85, HEIGHT - 30) + glVertex2f(WIDTH - 72, HEIGHT - 30) + glEnd() + + # TEXTOS + 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, 45, HEIGHT - 40) + draw_text_2d(txt_flies, WIDTH - 65, 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) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + 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 and 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.type == KEYDOWN and 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 + + 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")) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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() diff --git a/previos/dragonfly_game_v7.0.py b/previos/dragonfly_game_v7.0.py new file mode 100644 index 0000000..fd28e52 --- /dev/null +++ b/previos/dragonfly_game_v7.0.py @@ -0,0 +1,830 @@ +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 = 10, 20 +FLY_MIN, FLY_MAX = 2, 7 + +# ========================================== +# 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 + + 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() + + 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 +# ========================================== +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_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 + # 1. Icono de Mosquito (Izquierda - Amarillo) + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(25, HEIGHT - 33) + glVertex2f(25, HEIGHT - 27) + glVertex2f(21, HEIGHT - 30) + glVertex2f(29, HEIGHT - 30) + glEnd() + + # 2. Icono de Mosca (Derecha - Rojo/Marrón) + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + # Cuerpo y alas estáticas de la mosca + 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) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + 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 and 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.type == KEYDOWN and 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 + + 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")) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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() diff --git a/previos/dragonfly_game_v7.2.py b/previos/dragonfly_game_v7.2.py new file mode 100644 index 0000000..fa3d899 --- /dev/null +++ b/previos/dragonfly_game_v7.2.py @@ -0,0 +1,827 @@ +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 (Aumentadas en +7 el mínimo y máximo) +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 + + 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() + + 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 +# ========================================== +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_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) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + 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 and 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.type == KEYDOWN and 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 + + 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")) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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() diff --git a/previos/dragonfly_game_v8.0.py b/previos/dragonfly_game_v8.0.py new file mode 100644 index 0000000..fa3d899 --- /dev/null +++ b/previos/dragonfly_game_v8.0.py @@ -0,0 +1,827 @@ +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 (Aumentadas en +7 el mínimo y máximo) +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 + + 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() + + 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 +# ========================================== +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_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) + + pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL) + pygame.display.set_caption("Simulador de Vuelo de Libélula 3D") + + 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() + + running = True + while running: + dt = clock.tick(60) / 1000.0 + water_timer += dt + + 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 and 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.type == KEYDOWN and 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 + + 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")) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + 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() diff --git a/previos/libelula_v8.2.py b/previos/libelula_v8.2.py new file mode 100644 index 0000000..f4c9cfa --- /dev/null +++ b/previos/libelula_v8.2.py @@ -0,0 +1,968 @@ +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 + + 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() + + 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 EN LA PANTALLA INICIAL + glPushMatrix() + glTranslatef(WIDTH // 2 + 150, HEIGHT - 75, 0.0) + glScalef(12.0, 12.0, 1.0) + + # Dibujar libélula estática en 2D para el menú + 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() + + # Alas aleteando + rad_w = math.radians(wing_angle) + w_off = math.sin(rad_w) * 1.5 + 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() + 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[cite: 4] + 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[cite: 4] + 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: + # Renderizar fondo del mundo de forma estática para ambientar el menú + 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() + + # Dibujar la pantalla de inicio 2D encima + 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() diff --git a/previos/libelula_v8.3.py b/previos/libelula_v8.3.py new file mode 100644 index 0000000..631455b --- /dev/null +++ b/previos/libelula_v8.3.py @@ -0,0 +1,982 @@ +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() diff --git a/previos/libelula_v8.4.py b/previos/libelula_v8.4.py new file mode 100644 index 0000000..66793d8 --- /dev/null +++ b/previos/libelula_v8.4.py @@ -0,0 +1,972 @@ +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) + + # Patas traseras con los pies mirando hacia el exterior (más realismo)[cite: 4] + 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(-6.5 - leg_offset, -3.2, 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(6.5 + leg_offset, -3.2, 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 + 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() + + rad_w = math.radians(wing_angle) + w_off = math.sin(rad_w) * 1.5 + + 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() + + 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 + y_icon = 90 + + 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) + + 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) + + 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) + + 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 + + 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() + + 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() + + 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) + + 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 + 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() diff --git a/previos/libelula_v9.0.py b/previos/libelula_v9.0.py new file mode 100644 index 0000000..0073f8a --- /dev/null +++ b/previos/libelula_v9.0.py @@ -0,0 +1,1139 @@ +import sys +import math +import random +import struct +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 del jugador (Cian) +COLOR_PURPLE_DRAGONFLY = (0.7, 0.2, 0.8) # Libélulas decorativas (Morado) +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 + +# ========================================== +# GENERADOR DE AUDIO PROCEDURAL (SIN ARCHIVOS) +# ========================================== +def generate_buzz_sound(): + try: + sample_rate = 22050 + duration = 0.3 + num_samples = int(sample_rate * duration) + buffer = bytearray() + for i in range(num_samples): + t = i / sample_rate + val = int(32767 * 0.25 * math.sin(2 * math.pi * 180 * t + math.sin(2 * math.pi * 25 * t) * 3)) + buffer.extend(struct.pack(' 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 PurpleDragonfly: + def __init__(self, behavior_type): + self.behavior = behavior_type # 1: Círculos, 2: Patrulla lineal, 3: Zig-zag errático + self.x, self.y, self.z = get_random_water_position() + self.yaw = random.uniform(0, 360) + self.timer = random.uniform(0, 100) + self.wing_timer = random.uniform(0, 100) + self.is_perched = False + self.perch_timer = 0 + + def update(self): + self.wing_timer += 60.0 + if self.is_perched: + self.perch_timer -= 1 + if self.perch_timer <= 0: + self.is_perched = False + return + + self.timer += 0.05 + if self.behavior == 1: + self.yaw += 1.2 + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.1 + self.y += math.cos(rad) * 1.1 + elif self.behavior == 2: + if random.random() < 0.02: + self.yaw += random.uniform(-40, 40) + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.3 + self.y += math.cos(rad) * 1.3 + else: + self.yaw += math.sin(self.timer * 3) * 3.5 + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.4 + self.y += math.cos(rad) * 1.4 + + self.z = 15.0 + math.sin(self.timer * 2) * 6.0 + + # Posibilidad de posarse en un junco de vez en cuando + if random.random() < 0.0015 and REEDS: + reed = random.choice(REEDS) + self.x, self.y, self.z = reed[0], reed[1], reed[2] + reed[3] + self.is_perched = True + self.perch_timer = random.randint(300, 600) + + if not is_over_water(self.x, self.y): + self.yaw += 150.0 + + if self.y > STREAM_MAX_Y: + self.y -= WORLD_LEN + elif self.y < STREAM_MIN_Y: + self.y += WORLD_LEN + + def draw(self): + glColor3f(*COLOR_PURPLE_DRAGONFLY) + glPushMatrix() + glTranslatef(self.x, self.y, self.z) + glRotatef(self.yaw, 0, 0, 1) + + 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) + 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() + + wing_angle = 0.0 if self.is_perched else math.sin(self.wing_timer) * 35.0 + rad_wing = math.radians(wing_angle) + w_z = math.sin(rad_wing) * 1.2 if wing_angle != 0 else 0.0 + + 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() + + 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() + + 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) + + # Pies hacia el exterior + 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(-6.5 - leg_offset, -3.2, 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(6.5 + leg_offset, -3.2, 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 + + 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() + + 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() + + txt_title = font_title.render("LIBÉLULA", True, (0, 220, 255)) + draw_text_2d(txt_title, WIDTH // 2 - 130, HEIGHT - 100) + + 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() + + rad_w = math.radians(wing_angle) + w_off = math.sin(rad_w) * 1.5 + + 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() + + 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() + + 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) + + 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 + + y_icon = 90 + + 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) + + 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) + + 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) + + 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 + + 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() + + 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() + + 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.mixer.init(frequency=22050, size=-16, channels=1) + 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) + + # Generar efectos de sonido procedurales + buzz_sound = generate_buzz_sound() + wind_sound = generate_wind_sound() + + 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() + + # Instanciar las 3 libélulas moradas decorativas con comportamientos distintos + purple_dragonflies = [ + PurpleDragonfly(1), + PurpleDragonfly(2), + PurpleDragonfly(3) + ] + + 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 + + buzz_timer = 0 + + 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 + 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() + for p_df in purple_dragonflies: + p_df.update() + p_df.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 wind_sound and not pygame.mixer.get_busy(): + wind_sound.play() + + 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) + if speed > 2.0 and wind_sound and random.random() < 0.1 and not pygame.mixer.get_busy(): + wind_sound.play() + 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() + + for p_df in purple_dragonflies: + p_df.update() + + current_mosquitos = 0 + current_flies = 0 + near_insect = False + + for insect in insects[:]: + insect.update((px, py, pz)) + + if insect.type == "mosquito": + current_mosquitos += 1 + else: + current_flies += 1 + + dist_to_df = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2) + if dist_to_df < 40.0: + near_insect = True + + if not caught_insect: + if dist_to_df < 6.0: + caught_insect = insect + insects.remove(insect) + if dive_phase == "diving": + dive_phase = "recovering" + + # Reproducir zumbido si estamos cerca de insectos + if near_insect and buzz_sound: + buzz_timer += 1 + if buzz_timer > 25 and not pygame.mixer.get_busy(): + buzz_sound.play() + buzz_timer = 0 + + 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 p_df in purple_dragonflies: + p_df.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() diff --git a/previos/libelula_v9.2.py b/previos/libelula_v9.2.py new file mode 100644 index 0000000..b3aab8e --- /dev/null +++ b/previos/libelula_v9.2.py @@ -0,0 +1,1199 @@ +import sys +import math +import random +import struct +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 del jugador (Cian) +COLOR_PURPLE_DRAGONFLY = (0.7, 0.2, 0.8) # Libélulas decorativas (Morado) +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 + +# ========================================== +# GENERADOR DE AUDIO PROCEDURAL (SIN ARCHIVOS) +# ========================================== +def generate_buzz_sound(): + try: + sample_rate = 22050 + duration = 0.3 + num_samples = int(sample_rate * duration) + buffer = bytearray() + for i in range(num_samples): + t = i / sample_rate + val = int(32767 * 0.25 * math.sin(2 * math.pi * 180 * t + math.sin(2 * math.pi * 25 * t) * 3)) + buffer.extend(struct.pack(' 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 PurpleDragonfly: + def __init__(self, behavior_type): + self.behavior = behavior_type + self.x, self.y, self.z = get_random_water_position() + self.yaw = random.uniform(0, 360) + self.timer = random.uniform(0, 100) + self.wing_timer = random.uniform(0, 100) + self.is_perched = False + self.perch_timer = 0 + + def update(self): + self.wing_timer += 60.0 + if self.is_perched: + self.perch_timer -= 1 + if self.perch_timer <= 0: + self.is_perched = False + return + + self.timer += 0.05 + if self.behavior == 1: + self.yaw += 1.2 + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.1 + self.y += math.cos(rad) * 1.1 + elif self.behavior == 2: + if random.random() < 0.02: + self.yaw += random.uniform(-40, 40) + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.3 + self.y += math.cos(rad) * 1.3 + else: + self.yaw += math.sin(self.timer * 3) * 3.5 + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.4 + self.y += math.cos(rad) * 1.4 + + self.z = 15.0 + math.sin(self.timer * 2) * 6.0 + + if random.random() < 0.0015 and REEDS: + reed = random.choice(REEDS) + self.x, self.y, self.z = reed[0], reed[1], reed[2] + reed[3] + self.is_perched = True + self.perch_timer = random.randint(300, 600) + + if not is_over_water(self.x, self.y): + self.yaw += 150.0 + + if self.y > STREAM_MAX_Y: + self.y -= WORLD_LEN + elif self.y < STREAM_MIN_Y: + self.y += WORLD_LEN + + def draw(self): + glColor3f(*COLOR_PURPLE_DRAGONFLY) + glPushMatrix() + glTranslatef(self.x, self.y, self.z) + glRotatef(self.yaw, 0, 0, 1) + + 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) + 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() + + wing_angle = 0.0 if self.is_perched else math.sin(self.wing_timer) * 35.0 + rad_wing = math.radians(wing_angle) + w_z = math.sin(rad_wing) * 1.2 if wing_angle != 0 else 0.0 + + 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() + + 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() + + 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, custom_x=None, custom_y=None, custom_z=None, scale=1.0, menu_icon=False): + glColor3f(*COLOR_FROG) + glPushMatrix() + if custom_x is not None and custom_y is not None and custom_z is not None: + glTranslatef(custom_x, custom_y, custom_z) + glScalef(scale, scale, scale) + else: + glTranslatef(self.x, self.y, self.z) + glRotatef(self.yaw, 0, 0, 1) + + leg_offset = 0.0 if menu_icon else 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) + + # Patas traseras con los pies hacia el exterior + 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(-6.5 - leg_offset, -3.2, 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(6.5 + leg_offset, -3.2, 0.0) + glEnd() + + # Pequeño cuadrado/rectángulo central para representar el cuerpo sólido en el icono del menú + if menu_icon: + glBegin(GL_LINE_LOOP) + glVertex3f(-1.5, -1.5, 0.4) + glVertex3f(1.5, -1.5, 0.4) + glVertex3f(1.5, 1.5, 0.4) + glVertex3f(-1.5, 1.5, 0.4) + 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) + int(POND_RADIUS)*2, 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 + + 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() + + 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, frog_instance): + glDisable(GL_DEPTH_TEST) + + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + gluOrtho2D(0, WIDTH, 0, HEIGHT) + + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + glLoadIdentity() + + # TÍTULO PRINCIPAL Y LIBÉLULA SUPERIOR + txt_title = font_title.render("LIBÉLULA", True, (0, 220, 255)) + draw_text_2d(txt_title, WIDTH // 2 - 130, HEIGHT - 100) + + 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() + + rad_w = math.radians(wing_angle) + w_off = math.sin(rad_w) * 1.5 + + 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() + + 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 cazar", + "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, Junco, Rana y Libélula) + y_icon = 90 + + # 1. Mosquito + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(60, y_icon + 5) + glVertex2f(60, y_icon - 5) + glVertex2f(55, y_icon) + glVertex2f(65, y_icon) + glEnd() + txt_m = font_text.render("Mosquito", True, (255, 230, 0)) + draw_text_2d(txt_m, 75, y_icon - 8) + + # 2. Mosca + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(210, y_icon + 5) + glVertex2f(210, y_icon - 5) + glVertex2f(205, y_icon - 3) + glVertex2f(215, y_icon + 3) + glVertex2f(205, y_icon + 3) + glVertex2f(215, y_icon - 3) + glEnd() + txt_f = font_text.render("Mosca", True, (200, 65, 40)) + draw_text_2d(txt_f, 225, y_icon - 8) + + # 3. Junco + glColor3f(*COLOR_REED) + glBegin(GL_LINES) + glVertex2f(340, y_icon - 10) + glVertex2f(340, y_icon + 15) + glEnd() + glColor3f(*COLOR_CATTAIL) + glBegin(GL_LINES) + glVertex2f(340, y_icon + 8) + glVertex2f(340, y_icon + 15) + glEnd() + txt_r = font_text.render("Junco", True, (100, 220, 100)) + draw_text_2d(txt_r, 355, y_icon - 8) + + # 4. Rana (Con su cuadradito de cuerpo sólido incorporado) + frog_instance.draw(custom_x=475, custom_y=y_icon, custom_z=0.0, scale=3.5, menu_icon=True) + txt_frog = font_text.render("Rana", True, (100, 255, 100)) + draw_text_2d(txt_frog, 505, y_icon - 8) + + # 5. Libélula (Con alas animadas) + glPushMatrix() + glTranslatef(625, y_icon, 0.0) + glScalef(6.0, 6.0, 1.0) + glColor3f(*COLOR_PURPLE_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() + 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() + 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() + + txt_df = font_text.render("Libélula", True, (0, 220, 255)) + draw_text_2d(txt_df, 654, 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 + + 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() + + 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() + + 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.mixer.init(frequency=22050, size=-16, channels=1) + 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) + + buzz_sound = generate_buzz_sound() + wind_sound = generate_wind_sound() + + 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() + + purple_dragonflies = [ + PurpleDragonfly(1), + PurpleDragonfly(2), + PurpleDragonfly(3) + ] + + 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 + + buzz_timer = 0 + + 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 + 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, -200.0, 50.0 + gluLookAt(cam_x, cam_y, cam_z, 0, 0, 15, 0, 0, 1) + + draw_map(water_timer) + frog.update() + frog.draw() + for p_df in purple_dragonflies: + p_df.update() + p_df.draw() + + wing_angle = math.sin(wing_timer * 0.5) * 35.0 + draw_start_screen(font_title, font_subtitle, font_text, wing_angle, frog) + 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 wind_sound and not pygame.mixer.get_busy(): + wind_sound.play() + + 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) + if speed > 2.0 and wind_sound and random.random() < 0.1 and not pygame.mixer.get_busy(): + wind_sound.play() + 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() + + for p_df in purple_dragonflies: + p_df.update() + + current_mosquitos = 0 + current_flies = 0 + near_insect = False + + for insect in insects[:]: + insect.update((px, py, pz)) + + if insect.type == "mosquito": + current_mosquitos += 1 + else: + current_flies += 1 + + dist_to_df = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2) + if dist_to_df < 40.0: + near_insect = True + + if not caught_insect: + if dist_to_df < 6.0: + caught_insect = insect + insects.remove(insect) + if dive_phase == "diving": + dive_phase = "recovering" + + if near_insect and buzz_sound: + buzz_timer += 1 + if buzz_timer > 25 and not pygame.mixer.get_busy(): + buzz_sound.play() + buzz_timer = 0 + + 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 p_df in purple_dragonflies: + p_df.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() diff --git a/previos/libelula_v9.3.py b/previos/libelula_v9.3.py new file mode 100644 index 0000000..4817603 --- /dev/null +++ b/previos/libelula_v9.3.py @@ -0,0 +1,1199 @@ +import sys +import math +import random +import struct +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 del jugador (Cian) +COLOR_PURPLE_DRAGONFLY = (0.7, 0.2, 0.8) # Libélulas decorativas (Morado) +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 + +# ========================================== +# GENERADOR DE AUDIO PROCEDURAL (SIN ARCHIVOS) +# ========================================== +def generate_buzz_sound(): + try: + sample_rate = 22050 + duration = 0.3 + num_samples = int(sample_rate * duration) + buffer = bytearray() + for i in range(num_samples): + t = i / sample_rate + val = int(32767 * 0.25 * math.sin(2 * math.pi * 180 * t + math.sin(2 * math.pi * 25 * t) * 3)) + buffer.extend(struct.pack(' 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 PurpleDragonfly: + def __init__(self, behavior_type): + self.behavior = behavior_type + self.x, self.y, self.z = get_random_water_position() + self.yaw = random.uniform(0, 360) + self.timer = random.uniform(0, 100) + self.wing_timer = random.uniform(0, 100) + self.is_perched = False + self.perch_timer = 0 + + def update(self): + self.wing_timer += 60.0 + if self.is_perched: + self.perch_timer -= 1 + if self.perch_timer <= 0: + self.is_perched = False + return + + self.timer += 0.05 + if self.behavior == 1: + self.yaw += 1.2 + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.1 + self.y += math.cos(rad) * 1.1 + elif self.behavior == 2: + if random.random() < 0.02: + self.yaw += random.uniform(-40, 40) + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.3 + self.y += math.cos(rad) * 1.3 + else: + self.yaw += math.sin(self.timer * 3) * 3.5 + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.4 + self.y += math.cos(rad) * 1.4 + + self.z = 15.0 + math.sin(self.timer * 2) * 6.0 + + if random.random() < 0.0015 and REEDS: + reed = random.choice(REEDS) + self.x, self.y, self.z = reed[0], reed[1], reed[2] + reed[3] + self.is_perched = True + self.perch_timer = random.randint(300, 600) + + if not is_over_water(self.x, self.y): + self.yaw += 150.0 + + if self.y > STREAM_MAX_Y: + self.y -= WORLD_LEN + elif self.y < STREAM_MIN_Y: + self.y += WORLD_LEN + + def draw(self): + glColor3f(*COLOR_PURPLE_DRAGONFLY) + glPushMatrix() + glTranslatef(self.x, self.y, self.z) + glRotatef(self.yaw, 0, 0, 1) + + 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) + 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() + + wing_angle = 0.0 if self.is_perched else math.sin(self.wing_timer) * 35.0 + rad_wing = math.radians(wing_angle) + w_z = math.sin(rad_wing) * 1.2 if wing_angle != 0 else 0.0 + + 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() + + 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() + + 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, custom_x=None, custom_y=None, custom_z=None, scale=1.0, menu_icon=False): + glColor3f(*COLOR_FROG) + glPushMatrix() + if custom_x is not None and custom_y is not None and custom_z is not None: + glTranslatef(custom_x, custom_y, custom_z) + glScalef(scale, scale, scale) + else: + glTranslatef(self.x, self.y, self.z) + glRotatef(self.yaw, 0, 0, 1) + + leg_offset = 0.0 if menu_icon else 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) + + # Patas traseras con los pies hacia el exterior + 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(-6.5 - leg_offset, -3.2, 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(6.5 + leg_offset, -3.2, 0.0) + glEnd() + + # Pequeño cuadrado/rectángulo central para representar el cuerpo sólido en el icono del menú + if menu_icon: + glBegin(GL_LINE_LOOP) + glVertex3f(-1.5, -1.5, 0.4) + glVertex3f(1.5, -1.5, 0.4) + glVertex3f(1.5, 1.5, 0.4) + glVertex3f(-1.5, 1.5, 0.4) + 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) + int(POND_RADIUS)*2, 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 + + 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() + + 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, frog_instance): + glDisable(GL_DEPTH_TEST) + + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + gluOrtho2D(0, WIDTH, 0, HEIGHT) + + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + glLoadIdentity() + + # TÍTULO PRINCIPAL Y LIBÉLULA SUPERIOR + txt_title = font_title.render("LIBÉLULA", True, (0, 220, 255)) + draw_text_2d(txt_title, WIDTH // 2 - 130, HEIGHT - 100) + + 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() + + rad_w = math.radians(wing_angle) + w_off = math.sin(rad_w) * 1.5 + + 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() + + 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 cazar", + "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, Junco, Rana y Libélula) + y_icon = 90 + + # 1. Mosquito + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(60, y_icon + 5) + glVertex2f(60, y_icon - 5) + glVertex2f(55, y_icon) + glVertex2f(65, y_icon) + glEnd() + txt_m = font_text.render("Mosquito", True, (255, 230, 0)) + draw_text_2d(txt_m, 75, y_icon - 8) + + # 2. Mosca + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(210, y_icon + 5) + glVertex2f(210, y_icon - 5) + glVertex2f(205, y_icon - 3) + glVertex2f(215, y_icon + 3) + glVertex2f(205, y_icon + 3) + glVertex2f(215, y_icon - 3) + glEnd() + txt_f = font_text.render("Mosca", True, (200, 65, 40)) + draw_text_2d(txt_f, 225, y_icon - 8) + + # 3. Junco + glColor3f(*COLOR_REED) + glBegin(GL_LINES) + glVertex2f(340, y_icon - 10) + glVertex2f(340, y_icon + 15) + glEnd() + glColor3f(*COLOR_CATTAIL) + glBegin(GL_LINES) + glVertex2f(340, y_icon + 8) + glVertex2f(340, y_icon + 15) + glEnd() + txt_r = font_text.render("Junco", True, (100, 220, 100)) + draw_text_2d(txt_r, 355, y_icon - 8) + + # 4. Rana (Con su cuadradito de cuerpo sólido incorporado) + frog_instance.draw(custom_x=475, custom_y=y_icon, custom_z=0.0, scale=3.5, menu_icon=True) + txt_frog = font_text.render("Rana", True, (100, 255, 100)) + draw_text_2d(txt_frog, 505, y_icon - 8) + + # 5. Libélula (Con alas animadas) + glPushMatrix() + glTranslatef(625, y_icon, 0.0) + glScalef(6.0, 6.0, 1.0) + glColor3f(*COLOR_PURPLE_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() + 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() + 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() + + txt_df = font_text.render("Libélulas", True, (178, 51, 204)) + draw_text_2d(txt_df, 654, 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 + + 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() + + 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() + + 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.mixer.init(frequency=22050, size=-16, channels=1) + 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) + + buzz_sound = generate_buzz_sound() + wind_sound = generate_wind_sound() + + 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() + + purple_dragonflies = [ + PurpleDragonfly(1), + PurpleDragonfly(2), + PurpleDragonfly(3) + ] + + 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 + + buzz_timer = 0 + + 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 + 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, -200.0, 50.0 + gluLookAt(cam_x, cam_y, cam_z, 0, 0, 15, 0, 0, 1) + + draw_map(water_timer) + frog.update() + frog.draw() + for p_df in purple_dragonflies: + p_df.update() + p_df.draw() + + wing_angle = math.sin(wing_timer * 0.5) * 35.0 + draw_start_screen(font_title, font_subtitle, font_text, wing_angle, frog) + 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 wind_sound and not pygame.mixer.get_busy(): + wind_sound.play() + + 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) + if speed > 2.0 and wind_sound and random.random() < 0.1 and not pygame.mixer.get_busy(): + wind_sound.play() + 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() + + for p_df in purple_dragonflies: + p_df.update() + + current_mosquitos = 0 + current_flies = 0 + near_insect = False + + for insect in insects[:]: + insect.update((px, py, pz)) + + if insect.type == "mosquito": + current_mosquitos += 1 + else: + current_flies += 1 + + dist_to_df = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2) + if dist_to_df < 40.0: + near_insect = True + + if not caught_insect: + if dist_to_df < 6.0: + caught_insect = insect + insects.remove(insect) + if dive_phase == "diving": + dive_phase = "recovering" + + if near_insect and buzz_sound: + buzz_timer += 1 + if buzz_timer > 25 and not pygame.mixer.get_busy(): + buzz_sound.play() + buzz_timer = 0 + + 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 p_df in purple_dragonflies: + p_df.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() diff --git a/previos/libelula_v9.4.py b/previos/libelula_v9.4.py new file mode 100644 index 0000000..3398d7d --- /dev/null +++ b/previos/libelula_v9.4.py @@ -0,0 +1,1204 @@ +import sys +import math +import random +import struct +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 del jugador (Cian) +COLOR_PURPLE_DRAGONFLY = (0.7, 0.2, 0.8) # Libélulas decorativas (Morado) +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 + +# ========================================== +# GENERADOR DE AUDIO PROCEDURAL (SIN ARCHIVOS) +# ========================================== +def generate_buzz_sound(): + try: + sample_rate = 22050 + duration = 0.3 + num_samples = int(sample_rate * duration) + buffer = bytearray() + for i in range(num_samples): + t = i / sample_rate + val = int(32767 * 0.25 * math.sin(2 * math.pi * 180 * t + math.sin(2 * math.pi * 25 * t) * 3)) + buffer.extend(struct.pack(' 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 PurpleDragonfly: + def __init__(self, behavior_type): + self.behavior = behavior_type + self.x, self.y, self.z = get_random_water_position() + self.yaw = random.uniform(0, 360) + self.timer = random.uniform(0, 100) + self.wing_timer = random.uniform(0, 100) + self.is_perched = False + self.perch_timer = 0 + + def update(self): + self.wing_timer += 60.0 + if self.is_perched: + self.perch_timer -= 1 + if self.perch_timer <= 0: + self.is_perched = False + return + + self.timer += 0.05 + if self.behavior == 1: + self.yaw += 1.2 + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.1 + self.y += math.cos(rad) * 1.1 + elif self.behavior == 2: + if random.random() < 0.02: + self.yaw += random.uniform(-40, 40) + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.3 + self.y += math.cos(rad) * 1.3 + else: + self.yaw += math.sin(self.timer * 3) * 3.5 + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.4 + self.y += math.cos(rad) * 1.4 + + self.z = 15.0 + math.sin(self.timer * 2) * 6.0 + + if random.random() < 0.0015 and REEDS: + reed = random.choice(REEDS) + self.x, self.y, self.z = reed[0], reed[1], reed[2] + reed[3] + self.is_perched = True + self.perch_timer = random.randint(300, 600) + + if not is_over_water(self.x, self.y): + self.yaw += 150.0 + + if self.y > STREAM_MAX_Y: + self.y -= WORLD_LEN + elif self.y < STREAM_MIN_Y: + self.y += WORLD_LEN + + def draw(self): + glColor3f(*COLOR_PURPLE_DRAGONFLY) + glPushMatrix() + glTranslatef(self.x, self.y, self.z) + glRotatef(self.yaw, 0, 0, 1) + + 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) + 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() + + wing_angle = 0.0 if self.is_perched else math.sin(self.wing_timer) * 35.0 + rad_wing = math.radians(wing_angle) + w_z = math.sin(rad_wing) * 1.2 if wing_angle != 0 else 0.0 + + 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() + + 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() + + 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, custom_x=None, custom_y=None, custom_z=None, scale=1.0, menu_icon=False): + glColor3f(*COLOR_FROG) + glPushMatrix() + if custom_x is not None and custom_y is not None and custom_z is not None: + glTranslatef(custom_x, custom_y, custom_z) + glScalef(scale, scale, scale) + else: + glTranslatef(self.x, self.y, self.z) + glRotatef(self.yaw, 0, 0, 1) + + leg_offset = 0.0 if menu_icon else 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) + + # Patas traseras con los pies hacia el exterior + 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(-6.5 - leg_offset, -3.2, 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(6.5 + leg_offset, -3.2, 0.0) + glEnd() + + if menu_icon: + glBegin(GL_LINE_LOOP) + glVertex3f(-1.5, -1.5, 0.4) + glVertex3f(1.5, -1.5, 0.4) + glVertex3f(1.5, 1.5, 0.4) + glVertex3f(-1.5, 1.5, 0.4) + 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) + int(POND_RADIUS)*2, 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 + + 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() + + 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, menu_wing_angle, frog_instance): + 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) + + # LIBÉLULA SUPERIOR AL LADO DEL TÍTULO (CON AMPLITUD DE ALAS AMPLIADA) + 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() + + # Amplitud mayor amplificada para que se aprecie con claridad el aleteo + rad_menu_w = math.radians(menu_wing_angle) + menu_w_off = math.sin(rad_menu_w) * 3.5 + + glBegin(GL_LINE_LOOP) + glVertex3f(0, 0.6, 0) + glVertex3f(-4.5, 1.0, menu_w_off) + glVertex3f(-3.8, 0.2, menu_w_off) + glEnd() + glBegin(GL_LINE_LOOP) + glVertex3f(0, 0.6, 0) + glVertex3f(4.5, 1.0, menu_w_off) + glVertex3f(3.8, 0.2, menu_w_off) + glEnd() + + glBegin(GL_LINE_LOOP) + glVertex3f(0, -0.1, 0) + glVertex3f(-3.8, -0.2, menu_w_off) + glVertex3f(-3.0, -0.8, menu_w_off) + glEnd() + glBegin(GL_LINE_LOOP) + glVertex3f(0, -0.1, 0) + glVertex3f(3.8, -0.2, menu_w_off) + glVertex3f(3.0, -0.8, menu_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 + y_icon = 90 + + # 1. Mosquito + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(60, y_icon + 5) + glVertex2f(60, y_icon - 5) + glVertex2f(55, y_icon) + glVertex2f(65, y_icon) + glEnd() + txt_m = font_text.render("Mosquito", True, (255, 230, 0)) + draw_text_2d(txt_m, 75, y_icon - 8) + + # 2. Mosca + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(210, y_icon + 5) + glVertex2f(210, y_icon - 5) + glVertex2f(205, y_icon - 3) + glVertex2f(215, y_icon + 3) + glVertex2f(205, y_icon + 3) + glVertex2f(215, y_icon - 3) + glEnd() + txt_f = font_text.render("Mosca", True, (200, 65, 40)) + draw_text_2d(txt_f, 225, y_icon - 8) + + # 3. Junco + glColor3f(*COLOR_REED) + glBegin(GL_LINES) + glVertex2f(340, y_icon - 10) + glVertex2f(340, y_icon + 15) + glEnd() + glColor3f(*COLOR_CATTAIL) + glBegin(GL_LINES) + glVertex2f(340, y_icon + 8) + glVertex2f(340, y_icon + 15) + glEnd() + txt_r = font_text.render("Junco", True, (100, 220, 100)) + draw_text_2d(txt_r, 355, y_icon - 8) + + # 4. Rana + frog_instance.draw(custom_x=475, custom_y=y_icon, custom_z=0.0, scale=3.5, menu_icon=True) + txt_frog = font_text.render("Rana", True, (100, 255, 100)) + draw_text_2d(txt_frog, 505, y_icon - 8) + + # 5. Libélulas (Moradas) + rad_w = math.radians(wing_angle) + w_off = math.sin(rad_w) * 1.5 + + glPushMatrix() + glTranslatef(625, y_icon, 0.0) + glScalef(6.0, 6.0, 1.0) + glColor3f(*COLOR_PURPLE_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() + 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() + 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() + + txt_df = font_text.render("Libélulas", True, (178, 51, 204)) + draw_text_2d(txt_df, 654, 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 + + 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() + + 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() + + 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.mixer.init(frequency=22050, size=-16, channels=1) + 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) + + buzz_sound = generate_buzz_sound() + wind_sound = generate_wind_sound() + + 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() + + purple_dragonflies = [ + PurpleDragonfly(1), + PurpleDragonfly(2), + PurpleDragonfly(3) + ] + + 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 + + buzz_timer = 0 + + 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 + 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, -200.0, 50.0 + gluLookAt(cam_x, cam_y, cam_z, 0, 0, 15, 0, 0, 1) + + draw_map(water_timer) + frog.update() + frog.draw() + for p_df in purple_dragonflies: + p_df.update() + p_df.draw() + + wing_angle = math.sin(wing_timer * 0.5) * 35.0 + menu_wing_angle = math.sin(wing_timer * 0.8) * 45.0 + draw_start_screen(font_title, font_subtitle, font_text, wing_angle, menu_wing_angle, frog) + 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 wind_sound and not pygame.mixer.get_busy(): + wind_sound.play() + + 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) + if speed > 2.0 and wind_sound and random.random() < 0.1 and not pygame.mixer.get_busy(): + wind_sound.play() + 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() + + for p_df in purple_dragonflies: + p_df.update() + + current_mosquitos = 0 + current_flies = 0 + near_insect = False + + for insect in insects[:]: + insect.update((px, py, pz)) + + if insect.type == "mosquito": + current_mosquitos += 1 + else: + current_flies += 1 + + dist_to_df = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2) + if dist_to_df < 40.0: + near_insect = True + + if not caught_insect: + if dist_to_df < 6.0: + caught_insect = insect + insects.remove(insect) + if dive_phase == "diving": + dive_phase = "recovering" + + if near_insect and buzz_sound: + buzz_timer += 1 + if buzz_timer > 25 and not pygame.mixer.get_busy(): + buzz_sound.play() + buzz_timer = 0 + + 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 p_df in purple_dragonflies: + p_df.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() diff --git a/previos/libelula_v9.5.py b/previos/libelula_v9.5.py new file mode 100644 index 0000000..24a09b5 --- /dev/null +++ b/previos/libelula_v9.5.py @@ -0,0 +1,1204 @@ +import sys +import math +import random +import struct +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 del jugador (Cian) +COLOR_PURPLE_DRAGONFLY = (0.7, 0.2, 0.8) # Libélulas decorativas (Morado) +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 + +# ========================================== +# GENERADOR DE AUDIO PROCEDURAL (SIN ARCHIVOS) +# ========================================== +def generate_buzz_sound(): + try: + sample_rate = 22050 + duration = 0.3 + num_samples = int(sample_rate * duration) + buffer = bytearray() + for i in range(num_samples): + t = i / sample_rate + val = int(32767 * 0.25 * math.sin(2 * math.pi * 180 * t + math.sin(2 * math.pi * 25 * t) * 3)) + buffer.extend(struct.pack(' 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 PurpleDragonfly: + def __init__(self, behavior_type): + self.behavior = behavior_type + self.x, self.y, self.z = get_random_water_position() + self.yaw = random.uniform(0, 360) + self.timer = random.uniform(0, 100) + self.wing_timer = random.uniform(0, 100) + self.is_perched = False + self.perch_timer = 0 + + def update(self): + self.wing_timer += 60.0 + if self.is_perched: + self.perch_timer -= 1 + if self.perch_timer <= 0: + self.is_perched = False + return + + self.timer += 0.05 + if self.behavior == 1: + self.yaw += 1.2 + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.1 + self.y += math.cos(rad) * 1.1 + elif self.behavior == 2: + if random.random() < 0.02: + self.yaw += random.uniform(-40, 40) + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.3 + self.y += math.cos(rad) * 1.3 + else: + self.yaw += math.sin(self.timer * 3) * 3.5 + rad = math.radians(self.yaw) + self.x += -math.sin(rad) * 1.4 + self.y += math.cos(rad) * 1.4 + + self.z = 15.0 + math.sin(self.timer * 2) * 6.0 + + if random.random() < 0.0015 and REEDS: + reed = random.choice(REEDS) + self.x, self.y, self.z = reed[0], reed[1], reed[2] + reed[3] + self.is_perched = True + self.perch_timer = random.randint(300, 600) + + if not is_over_water(self.x, self.y): + self.yaw += 150.0 + + if self.y > STREAM_MAX_Y: + self.y -= WORLD_LEN + elif self.y < STREAM_MIN_Y: + self.y += WORLD_LEN + + def draw(self): + glColor3f(*COLOR_PURPLE_DRAGONFLY) + glPushMatrix() + glTranslatef(self.x, self.y, self.z) + glRotatef(self.yaw, 0, 0, 1) + + 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) + 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() + + wing_angle = 0.0 if self.is_perched else math.sin(self.wing_timer) * 35.0 + rad_wing = math.radians(wing_angle) + w_z = math.sin(rad_wing) * 1.2 if wing_angle != 0 else 0.0 + + 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() + + 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() + + 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, custom_x=None, custom_y=None, custom_z=None, scale=1.0, menu_icon=False): + glColor3f(*COLOR_FROG) + glPushMatrix() + if custom_x is not None and custom_y is not None and custom_z is not None: + glTranslatef(custom_x, custom_y, custom_z) + glScalef(scale, scale, scale) + else: + glTranslatef(self.x, self.y, self.z) + glRotatef(self.yaw, 0, 0, 1) + + leg_offset = 0.0 if menu_icon else 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) + + # Patas traseras con los pies hacia el exterior + 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(-6.5 - leg_offset, -3.2, 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(6.5 + leg_offset, -3.2, 0.0) + glEnd() + + # Cuadrado vacío central simulando el cuerpo sólido con el borde del color de las patas + if menu_icon: + glBegin(GL_LINE_LOOP) + glVertex3f(-2.0, -2.5, 0.4) + glVertex3f(2.0, -2.5, 0.4) + glVertex3f(2.0, 1.5, 0.4) + glVertex3f(-2.0, 1.5, 0.4) + 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) + int(POND_RADIUS)*2, 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 + + 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() + + 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, menu_wing_angle, frog_instance): + 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) + + # LIBÉLULA SUPERIOR + 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() + + rad_menu_w = math.radians(menu_wing_angle) + menu_w_off = math.sin(rad_menu_w) * 3.5 + + glBegin(GL_LINE_LOOP) + glVertex3f(0, 0.6, 0) + glVertex3f(-4.5, 1.0, menu_w_off) + glVertex3f(-3.8, 0.2, menu_w_off) + glEnd() + glBegin(GL_LINE_LOOP) + glVertex3f(0, 0.6, 0) + glVertex3f(4.5, 1.0, menu_w_off) + glVertex3f(3.8, 0.2, menu_w_off) + glEnd() + + glBegin(GL_LINE_LOOP) + glVertex3f(0, -0.1, 0) + glVertex3f(-3.8, -0.2, menu_w_off) + glVertex3f(-3.0, -0.8, menu_w_off) + glEnd() + glBegin(GL_LINE_LOOP) + glVertex3f(0, -0.1, 0) + glVertex3f(3.8, -0.2, menu_w_off) + glVertex3f(3.0, -0.8, menu_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 cazar", + "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 + y_icon = 90 + + # 1. Mosquito + glColor3f(*COLOR_MOSQUITO) + glBegin(GL_LINES) + glVertex2f(60, y_icon + 5) + glVertex2f(60, y_icon - 5) + glVertex2f(55, y_icon) + glVertex2f(65, y_icon) + glEnd() + txt_m = font_text.render("Mosquito", True, (255, 230, 0)) + draw_text_2d(txt_m, 75, y_icon - 8) + + # 2. Mosca + glColor3f(*COLOR_FLY) + glBegin(GL_LINES) + glVertex2f(210, y_icon + 5) + glVertex2f(210, y_icon - 5) + glVertex2f(205, y_icon - 3) + glVertex2f(215, y_icon + 3) + glVertex2f(205, y_icon + 3) + glVertex2f(215, y_icon - 3) + glEnd() + txt_f = font_text.render("Mosca", True, (200, 65, 40)) + draw_text_2d(txt_f, 225, y_icon - 8) + + # 3. Junco + glColor3f(*COLOR_REED) + glBegin(GL_LINES) + glVertex2f(340, y_icon - 10) + glVertex2f(340, y_icon + 15) + glEnd() + glColor3f(*COLOR_CATTAIL) + glBegin(GL_LINES) + glVertex2f(340, y_icon + 8) + glVertex2f(340, y_icon + 15) + glEnd() + txt_r = font_text.render("Junco", True, (100, 220, 100)) + draw_text_2d(txt_r, 355, y_icon - 8) + + # 4. Rana (Con el cuadrado central de cuerpo sólido) + frog_instance.draw(custom_x=475, custom_y=y_icon, custom_z=0.0, scale=3.5, menu_icon=True) + txt_frog = font_text.render("Rana", True, (100, 255, 100)) + draw_text_2d(txt_frog, 505, y_icon - 8) + + # 5. Libélulas (Moradas) + rad_w = math.radians(wing_angle) + w_off = math.sin(rad_w) * 1.5 + + glPushMatrix() + glTranslatef(625, y_icon, 0.0) + glScalef(6.0, 6.0, 1.0) + glColor3f(*COLOR_PURPLE_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() + 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() + 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() + + txt_df = font_text.render("Libélulas", True, (178, 51, 204)) + draw_text_2d(txt_df, 654, 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 + + 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() + + 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() + + 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.mixer.init(frequency=22050, size=-16, channels=1) + 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) + + buzz_sound = generate_buzz_sound() + wind_sound = generate_wind_sound() + + 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() + + purple_dragonflies = [ + PurpleDragonfly(1), + PurpleDragonfly(2), + PurpleDragonfly(3) + ] + + 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 + + buzz_timer = 0 + + 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 + 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, -200.0, 50.0 + gluLookAt(cam_x, cam_y, cam_z, 0, 0, 15, 0, 0, 1) + + draw_map(water_timer) + frog.update() + frog.draw() + for p_df in purple_dragonflies: + p_df.update() + p_df.draw() + + wing_angle = math.sin(wing_timer * 0.5) * 35.0 + menu_wing_angle = math.sin(wing_timer * 0.8) * 45.0 + draw_start_screen(font_title, font_subtitle, font_text, wing_angle, menu_wing_angle, frog) + 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 wind_sound and not pygame.mixer.get_busy(): + wind_sound.play() + + 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) + if speed > 2.0 and wind_sound and random.random() < 0.1 and not pygame.mixer.get_busy(): + wind_sound.play() + 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() + + for p_df in purple_dragonflies: + p_df.update() + + current_mosquitos = 0 + current_flies = 0 + near_insect = False + + for insect in insects[:]: + insect.update((px, py, pz)) + + if insect.type == "mosquito": + current_mosquitos += 1 + else: + current_flies += 1 + + dist_to_df = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2) + if dist_to_df < 40.0: + near_insect = True + + if not caught_insect: + if dist_to_df < 6.0: + caught_insect = insect + insects.remove(insect) + if dive_phase == "diving": + dive_phase = "recovering" + + if near_insect and buzz_sound: + buzz_timer += 1 + if buzz_timer > 25 and not pygame.mixer.get_busy(): + buzz_sound.play() + buzz_timer = 0 + + 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 p_df in purple_dragonflies: + p_df.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() diff --git a/radar-ayuda-captura.png b/radar-ayuda-captura.png new file mode 100644 index 0000000..bbbe6dd Binary files /dev/null and b/radar-ayuda-captura.png differ