# JUEGO 2D (PythonExtra + gint)
# CREADO_POR_ "VITUCHO"
# Puedes usarlo como plantilla para tus juegos

# Es mi aporte a la comunidad ClassPad 2 de codigo abierto
# importante " los textos no admiten palabras con tildes (ejemplo = á) no se mostrarán en la calculadora
# sino se mostrara por ejmplo (hóla = hla) se mostrara esto en la calculadora ("hla")"
# espero quede claro almenos eso me susedio en español, no probe otros idiomas, pero es lo que es, XD.
# ==========================

from gint import *
from pixelXD import image as tileset_img
import time
#from random import randint

# ==========================
# CONFIGURACIÓN GENERAL / CONSTANTES
# ==========================
TILE_IMG = 16
TILES_GLOBAL = tileset_img.width // TILE_IMG

# --- Ventana/cámara visible (ajusta si quieres otra rejilla de tiles en pantalla) ---
#     es jugable con 11x8, pero puedes probar otras dimensiones
VISIBLE_TILES_X = 11
VISIBLE_TILES_Y = 8
MARGEN_X = (DWIDTH - VISIBLE_TILES_X * TILE_IMG) // 2
MARGEN_Y = 0

# --- Tiles sólidos / especiales ---
# SÓLIDOS: colisión. BOTÓN: 8. PUERTA/OBSTÁCULO: 11. META/GANAR: 13.
SOLIDO_TILES = {0, 1, 11, 12}
TILE_BOTON = 8
TILE_BOTON_USADO = 9
TILE_PUERTA = 11
TILE_GOL = 13

# --- Sprites (índices dentro del tileset) ---
JUGADOR = 7 # sprite o tile personaje
JUGADOR_W, JUGADOR_H = 16, 16 # dimenciones personaje
JUGADOR_WW, JUGADOR_HH = JUGADOR_W - 1, JUGADOR_H - 1

TILE_MANCHA_BOMBA = 3
TILE_NUBE_TOXICA  = 6
TILE_BOMBA        = 5
TILE_EXPLOSION    = 5
TILE_JEFE         = 10  # sprite o tile del jefe

# --- Fisica / camara ---
VELOCIDAD_CORRER = 2
SALTO = 10
GRAVEDA, CAIDA_MAXIMA = 1, 8
CAMARA_ADELANTADA = 40
CAMARA_VELOSIDAD = 2

# ==========================
# MAPAS 1 2 3 (4_FINAL)
# ==========================
nivel1 = [
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2],
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 8, 1, 2, 2],
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 0, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2],
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 2, 1, 2, 2],
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,11, 2, 2],
    [ 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,11, 2, 2],
    [ 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,11, 2,13],
    [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]

nivel2 =[
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
    [ 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
    [ 2, 2, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1],
    [ 2, 2, 2, 2,11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 2, 2, 0, 2, 2, 2, 2],
    [ 2, 2, 2, 2,11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
    [ 2,13, 2, 2,11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2],
    [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]

nivel3 =[
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1],
    [ 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1],
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 0, 1, 8, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1],
    [ 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 1, 0, 2, 2, 2, 2, 2,11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,11, 1],
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,11, 1],
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,11, 2, 2, 2, 2,13, 2, 2, 2, 2, 2,11, 1],
    [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11, 1]
]

nivel4 =[
    [ 2, 2, 2, 2, 2, 2, 1, 1, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
    [ 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
    [ 1, 1, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 2],
    [ 1, 1, 1, 2, 2, 2, 1, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 1],
    [ 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
    [ 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0],
    [ 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0],
    [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]

mapas = [nivel1, nivel2, nivel3, nivel4]

# ==========================
# UTILIDADES
# ==========================
def ajustar(a, lo, hi): return lo if a < lo else hi if a > hi else a
def tile_uv(index): return (index % TILES_GLOBAL)*TILE_IMG, (index // TILES_GLOBAL)*TILE_IMG
def o_boton_tile(tx, ty): return 39 if ty<0 or ty>=MAP_H or tx<0 or tx>=MAP_W else level[ty][tx]
def solido(tx, ty): return o_boton_tile(tx, ty) in SOLIDO_TILES

def divujar_ties_pantalla(cam_x_px, cam_y_tiles):
    cam_tx = cam_x_px // TILE_IMG
    offset_x = -(cam_x_px % TILE_IMG)
    for vy_ in range(VISIBLE_TILES_Y):
        ty = cam_y_tiles + vy_
        sy = MARGEN_Y + vy_ * TILE_IMG
        if ty >= MAP_H: continue
        row = level[ty]
        for vx_ in range(VISIBLE_TILES_X):
            tx = cam_tx + vx_
            if tx < 0 or tx >= MAP_W: continue
            sx = MARGEN_X + offset_x + vx_ * TILE_IMG
            idx = row[tx]
            u,v = tile_uv(idx)
            dsubimage(sx, sy, tileset_img, u, v, TILE_IMG, TILE_IMG)

def dibujar_sprite_visible(x,y,cam_x,cam_y,frame):
    sx = MARGEN_X + (x-cam_x)
    sy = MARGEN_Y + (y-cam_y*TILE_IMG)
    if (sx<MARGEN_X-TILE_IMG or sx>MARGEN_X+VISIBLE_TILES_X*TILE_IMG or
        sy<MARGEN_Y-TILE_IMG or sy>MARGEN_Y+VISIBLE_TILES_Y*TILE_IMG): return
    u,v = tile_uv(frame)
    dsubimage(sx, sy, tileset_img, u, v, TILE_IMG, TILE_IMG)

# ==========================
# ENEMIGOS
# ==========================
class Enemigo:
    def __init__(self, x, y, etype, comportamiento_enemigo=None, range_x=100):
        self.x, self.y = x, y
        self.start_x = x
        self.vx = -1
        self.timer = 0
        self.alive = True
        self.type = etype  # "nube_toxica" | "mancha_bomba" | "jefe_final"
        self.comportamiento_enemigo = comportamiento_enemigo if comportamiento_enemigo else []
        self.range_x = range_x
        # Vida para jefe
        self.max_hp = 3 if etype == "jefe_final" else 1
        self.hp = self.max_hp

    def hit(self, dmg=1):
        if not self.alive: return
        self.hp -= dmg
        if self.hp <= 0:
            self.alive = False

    def jefe_final_disparos(self, px, py):
        #  Disparo discreto hacia el jugador: horizontal, arriba o diagonales 45°
        # velocidad base del proyectil
        s = 2
        # calcular dirección horizontal
        dir_x = 1 if px >= self.x else -1
        # decidir vertical: solo hacia arriba si el jugador está por encima
        # (py más pequeño => arriba)
        if py < self.y - 4:
            vy = -s
            vx = dir_x * s  # diagonales
            # si está casi alineado horizontalmente, dispara recto hacia arriba
            if abs(px - self.x) <= 6:
                vx = 0
        else:
            vy = 0
            vx = dir_x * s  # horizontal
        return [self.x, self.y, vx, vy]

    def update(self, px, py):
        if not self.alive: return []

        # --- JEFE FINAL ---
        if self.type == "jefe_final":
            self.timer += 1
            shots = []
            # disparo cada 0.8s aprox
            if self.timer % 40 == 0:
                shots.append(self.jefe_final_disparos(px, py))
            # movimiento horizontal sencillo (patrulla corta)
            if "patrulla" in self.comportamiento_enemigo:
                new_x = self.x + self.vx
                if abs(new_x - self.start_x) > self.range_x:
                    self.vx *= -1
                elif solido(new_x//TILE_IMG, self.y//TILE_IMG):
                    self.vx *= -1
                elif new_x < 0 or new_x > MAP_W*TILE_IMG - TILE_IMG:
                    self.vx *= -1
                else:
                    self.x = new_x
            return shots

        # --- Enemigos normales ---
        if "patrulla" in self.comportamiento_enemigo:
            new_x = self.x + self.vx
            if abs(new_x - self.start_x) > self.range_x:
                self.vx *= -1
            elif solido(new_x//TILE_IMG, self.y//TILE_IMG):
                self.vx *= -1
            elif new_x < 0 or new_x > MAP_W*TILE_IMG - TILE_IMG:
                self.vx *= -1
            else:
                self.x = new_x

        shots = []
        self.timer += 1
        if "disparo" in self.comportamiento_enemigo and self.type == "mancha_bomba":
            if abs(py - self.y) < 20 and self.timer % 80 == 0:
                dir = 1 if px > self.x else -1
                shots.append([self.x, self.y, dir*2, 0])
        return shots

    def draw(self, cam_x, cam_y):
        if not self.alive: return
        if self.type == "jefe_final":
            frame = TILE_JEFE
        elif self.type == "mancha_bomba":
            frame = TILE_MANCHA_BOMBA
        else:
            frame = TILE_NUBE_TOXICA

        dibujar_sprite_visible(self.x, self.y, cam_x, cam_y, frame)

        # Barra de vida del jefe
        if self.type == "jefe_final":
            # dibujar barra simple encima del jefe (3 segmentos)
            sx = MARGEN_X + (self.x - cam_x)
            sy = MARGEN_Y + (self.y - cam_y*TILE_IMG) - 6
            # fondo
            drect(sx-1, sy-1, sx+16, sy+4, C_RGB(2,2,2))
            # segmentos
            for i in range(self.max_hp):
                color = C_RGB(28,0,0) if i < self.hp else C_RGB(6,6,6)
                drect(sx + 1 + i*5, sy, sx + 4 + i*5, sy+3, color)

# ==========================
# ENEMIGOS POR NIVEL (usar  @ nombre @ ("patrulla" "disparo") o ("patrulla")
# ==========================
# ejemplo:
# TENER EN CUENTA QUE 16*16 PIXELES = 1 TILE
# Enemigo(pixel_x, pixel_y, "nube_toxica", comportamiento_enemigo=["patrulla"], range_x=80)
# ==========================
enemies_per_level = [
    # nivel 1
    [
        Enemigo(250, 96, "nube_toxica", comportamiento_enemigo=["patrulla"], range_x=80),
        Enemigo(330, 96, "nube_toxica", comportamiento_enemigo=["patrulla"], range_x=80),
        Enemigo(410, 96, "nube_toxica", comportamiento_enemigo=["patrulla"], range_x=80),
        Enemigo(490, 96, "nube_toxica", comportamiento_enemigo=["patrulla"], range_x=80),
        Enemigo(570, 96, "nube_toxica", comportamiento_enemigo=["patrulla"], range_x=80),
    ],
    # nivel 2
    [
        Enemigo(112, 32, "mancha_bomba", comportamiento_enemigo=["patrulla", "disparo"], range_x=60),
        Enemigo(288, 32, "mancha_bomba", comportamiento_enemigo=["patrulla", "disparo"], range_x=60),
        Enemigo(560, 32, "nube_toxica", comportamiento_enemigo=["patrulla"], range_x=60),
        Enemigo(200, 96, "mancha_bomba", comportamiento_enemigo=["patrulla", "disparo"], range_x=60),
    ],
    # nivel 3
    [
        Enemigo(64, 48, "mancha_bomba", comportamiento_enemigo=["patrulla", "disparo"], range_x=60),
        Enemigo(32, 96, "nube_toxica", comportamiento_enemigo=["patrulla"], range_x=60),
        Enemigo(250, 96, "nube_toxica", comportamiento_enemigo=["patrulla"], range_x=60),
        Enemigo(448, 96, "mancha_bomba", comportamiento_enemigo=["patrulla", "disparo"], range_x=80),
    ],
    # nivel 4
    [
        Enemigo(160, 80, "mancha_bomba", comportamiento_enemigo=["patrulla","disparo"], range_x=64),
        Enemigo(362, 96, "jefe_final", comportamiento_enemigo=["patrulla"], range_x=80),
    ],
]

# ==========================
# MOVIMIENTO Y COLISIÓN
# ==========================
def moverseycolisionar(px_, py_, vx_, vy_):
    saltar = False
    if vx_ != 0:
        new_x = px_ + vx_
        if vx_ > 0:
            tx = (new_x + JUGADOR_WW)//TILE_IMG
            if solido(tx, py_//TILE_IMG) or solido(tx, (py_+JUGADOR_HH)//TILE_IMG):
                new_x = tx*TILE_IMG - JUGADOR_W
        else:
            tx = new_x//TILE_IMG
            if solido(tx, py_//TILE_IMG) or solido(tx, (py_+JUGADOR_HH)//TILE_IMG):
                new_x = (tx+1)*TILE_IMG
        px_ = new_x
    if vy_ != 0:
        nuevo_y = py_ + vy_
        if vy_ > 0:
            ty = (nuevo_y+JUGADOR_HH)//TILE_IMG
            if solido(px_//TILE_IMG, ty) or solido((px_+JUGADOR_WW)//TILE_IMG, ty):
                nuevo_y = ty*TILE_IMG - JUGADOR_H
                vy_ = 0
                saltar = True
        else:
            ty = nuevo_y//TILE_IMG
            if solido(px_//TILE_IMG, ty) or solido((px_+JUGADOR_WW)//TILE_IMG, ty):
                nuevo_y = (ty+1)*TILE_IMG
                vy_ = 0
        py_ = nuevo_y
    return px_, py_, vx_, vy_, saltar

# ==========================
# MENÚ
# ==========================
def mostrar_menu(title, options, selected):
    dclear(C_WHITE)
    dtext(DWIDTH//2 - len(title)*4, 20, C_BLACK, title)
    for i, opt in enumerate(options):
        color = C_BLACK if i != selected else C_RGB(31,0,0)
        dtext(50, 60 + i*20, color, opt)
    dupdate()

def menu(title, options):
    pos = 0
    while True:
        mostrar_menu(title, options, pos)
        ev = getkey()
        if ev.key == KEY_UP and pos > 0: pos -= 1
        elif ev.key == KEY_DOWN and pos < len(options)-1: pos += 1
        elif ev.key == KEY_EXE: return pos
        elif ev.key == KEY_EXIT: return -1

# ==========================
# HUD / TRANSICIONES
# ==========================
def mostrar_fin_juego(msg="You have fallen!"):
    dclear(C_WHITE)
    dtext(20, 40, C_BLACK, msg)
    dtext(20, 60, C_BLACK, "EEXE: return to menu")
    dupdate()
    while True:
        ev = getkey()
        if ev.key == KEY_EXE: break

def mostrar_final(): # aqui se muestra el final del juego al derrotar al jefe
    dclear(C_WHITE)
    y = 20
    dtext(20,y,  C_BLACK, "Thus ends the story of the hero")
    dtext(20,y+20,C_BLACK, "who destroyed the evil")
    dtext(20,y+40,C_BLACK, "pollution. The “CO2”")
    dtext(20,y+60,C_BLACK, "is gone at last, thanks to")
    dtext(20,y+80,C_BLACK, "the countless plants")
    dtext(20,y+100,C_BLACK,"of every kind and color")
    dtext(20,y+130,C_BLACK,"that were planted.")
    dtext(20,y+150,C_BLACK,"Press EXE to exit.")
    dupdate()
    while True:
        ev = getkey()
        if ev.key == KEY_EXE: break

# ==========================
# BUCLE PRINCIPAL DE NIVEL
# ==========================
def jugar_nivel(mapa_sel):
    global level, enemies, MAP_H, MAP_W
    level = [row[:] for row in mapas[mapa_sel]]   # copia sin modificar del mapa
    enemies = [Enemigo(e.x, e.y, e.type, e.comportamiento_enemigo[:], e.range_x) for e in enemies_per_level[mapa_sel]]
    MAP_H, MAP_W = len(level), len(level[0])


    # jugador
    px, py = 16, 16     # posición de aparicion
    vx, vy = 0, 0
    on_ground = False

    # camara
    cam_x, cam_y = 0, 0

    # listas de proyectiles y explosiones
    balas, explosion = [], []

    # control del jefe
    jefe = None
    for e in enemies:
        if e.type == "jefe_final":
            jefe = e
            break

    # Contador de activaciones del botón para matar al jefe
    boss_hits = 0
    # Para evitar contar EXE varias veces mientras se mantiene pulsado
    last_button_press_time = 0

    while True:
        # --- Entrada inmediata ---
        cleareventflips(); clearevents()
        left, right = keydown(KEY_LEFT), keydown(KEY_RIGHT)
        jump_pressed = keydown(KEY_EXE) or keydown(KEY_UP)

        # --- Movimiento (input) ---
        vx = -VELOCIDAD_CORRER if left and not right else VELOCIDAD_CORRER if right and not left else 0
        if jump_pressed and on_ground:
            vy = -SALTO; on_ground = False
        if not on_ground: vy = min(vy+GRAVEDA, CAIDA_MAXIMA)

        # --- Físicas (colisión) ---
        px, py, vx, vy, on_ground = moverseycolisionar(px, py, vx, vy)

        # --- Límites del mapa (jugador nunca sale) ---
        px = ajustar(px, 0, MAP_W*TILE_IMG - JUGADOR_W)
        py = ajustar(py, 0, MAP_H*TILE_IMG - JUGADOR_H)

        # --- Cámara (centrada + adelantar_camara) sin salir del mapa ---
        adelantar_camara = CAMARA_ADELANTADA if vx > 0 else -CAMARA_ADELANTADA if vx < 0 else 0
        target_cam = ajustar(px - (VISIBLE_TILES_X*TILE_IMG)//2 + adelantar_camara,
                           0, MAP_W*TILE_IMG - VISIBLE_TILES_X*TILE_IMG)
        if cam_x < target_cam: cam_x = min(cam_x+CAMARA_VELOSIDAD, target_cam)
        elif cam_x > target_cam: cam_x = max(cam_x-CAMARA_VELOSIDAD, target_cam)

        # --- Interacción con tiles especiales ---
        tx, ty = px//TILE_IMG, py//TILE_IMG

        # BOTÓN (tile 8): hay que activarlo 3 veces
        # Cada activación quita 1 vida al jefe.
        if o_boton_tile(tx, ty) == 8:
            boss_hits += 1
            level[ty][tx] = 9
            # Abrir puertas (tiles 11 -> 2)
            for y in range(MAP_H):
                for x in range(MAP_W):
                    if level[y][x] == 11:
                        level[y][x] = 2
            # Daño al jefe
            if jefe and jefe.alive:
                jefe.hit(1)


        # si el botón usado se quiere "resetear" visualmente, podemos devolverlo a 8
        # cuando el jugador se aparte (no es obligatorio).
        if o_boton_tile(tx, ty) != TILE_BOTON and o_boton_tile(tx, ty) != TILE_BOTON_USADO:
            # opcional: buscar y devolver botones usados a 8 si quieres repetir animación
            pass

        # (tile 13): salir del nivel (si existiera otro flujo)
        if o_boton_tile(tx, ty) == TILE_GOL:
            return

        # fin del jefe: cuando sus 3 vidas se agotan, mostramos final
        if jefe and not jefe.alive:
            mostrar_final()
            return

        # enemigos: actualización y disparos
        for e in enemies:
            balas += e.update(px, py)

        # proyectiles (del jefe y enemigos)
        nueva_bala=[]
        for bx,by,bvx,bvy in balas:
            new_x,new_y=bx+bvx,by+bvy
            txx,tyy=new_x//TILE_IMG,new_y//TILE_IMG
            if solido(txx,tyy):
                explosion.append([txx,tyy,time.time()])
            else:
                nueva_bala.append([new_x,new_y,bvx,bvy])
                # Colisión jugador vs bala
                if (px<new_x+TILE_IMG and px+JUGADOR_W>new_x and
                    py<new_y+TILE_IMG and py+JUGADOR_H>new_y):
                    mostrar_fin_juego("A projectile hit you!")
                    return
        balas=nueva_bala

        # colisiones con enemigos (cuerpo a cuerpo)
        for e in enemies:
            if e.alive:
                if (px<e.x+TILE_IMG and px+JUGADOR_W>e.x and
                    py<e.y+TILE_IMG and py+JUGADOR_H>e.y):
                    if e.type=="mancha_bomba":
                        # Rebote si caes desde arriba
                        if vy>0 and py+JUGADOR_H-16<=e.y:
                            e.alive=False; vy=-SALTO
                        else:
                            mostrar_fin_juego("You exploded.")
                            return
                    elif e.type=="jefe_final":
                        mostrar_fin_juego("The boss crushed you.")
                        return
                    else:
                        mostrar_fin_juego("You have been defeated.")
                        return

        # explosiones temporales
        no=time.time()
        nuevo_explosions=[]
        for txx,tyy,t0 in explosion:
            if no-t0<1:
                dibujar_sprite_visible(txx*TILE_IMG,tyy*TILE_IMG,int(cam_x),cam_y,TILE_EXPLOSION)
                if (px < txx*TILE_IMG+TILE_IMG and px+JUGADOR_W > txx*TILE_IMG and
                    py < tyy*TILE_IMG+TILE_IMG and py+JUGADOR_H > tyy*TILE_IMG):
                    mostrar_fin_juego("¡Explosion!")
                    return
                nuevo_explosions.append([txx,tyy,t0])
        explosion=nuevo_explosions

        # dibujar frame
        dclear(C_WHITE)
        divujar_ties_pantalla(int(cam_x),cam_y)
        # jugador
        dibujar_sprite_visible(px,py,int(cam_x),cam_y,JUGADOR)
        # enemigos
        for e in enemies: e.draw(int(cam_x),cam_y)
        # proyectiles
        for bx,by,_,_ in balas: dibujar_sprite_visible(bx,by,int(cam_x),cam_y,TILE_BOMBA)

        # barra simple: mostrar progreso contra jefe (vivo)
        if jefe:
            dtext(5, 5, C_BLACK, f"JEFE: {min(boss_hits,3)}/3")

        dupdate()
        time.sleep(0.01)  # frame limiter suave

# ==========================
# MENU PRINCIPAL
# ==========================
while True:
    sel = menu("Slow but Steady BY VITUCHO",
               ["Play", "Story", "Quit"])
    if sel == 0:
        mapa_sel = menu("Select Map" ,[
            "ON THE WAY", "AT THE EDGE OF THE KINGDOM","INSIDE THE CASTLE", "FINAL_BOSS"])
        if mapa_sel >= 0:
            jugar_nivel(mapa_sel)
            
    elif sel == 1:
        dclear(C_WHITE)
        dtext(20, 20,C_BLACK,"In a polluted world")
        dtext(20, 45,C_BLACK,"humans fight against")
        dtext(20, 65,C_BLACK,"toxic creatures.")
        dtext(20, 85,C_BLACK,"You must collect")
        dtext(20,105,C_BLACK,"all the plants.")
        dtext(20,125,C_BLACK,"You are the last hero.")
        dtext(20,145,C_BLACK,"Press EXE to return.")
        dupdate()
        while True:
            ev = getkey()
            if ev.key == KEY_EXE: break
    elif sel == 2 or sel == -1:
        exit()