from pyxel import *
import math

# ── Konstanten ────────────────────────────────────────────────────────────────
SCREEN_W        = 512
SCREEN_H        = 512
GRAVITY         = 0.5
JUMP_FORCE      = -8.5
MOVE_SPEED      = 4.0
ROCKET_DURATION = 50
ROCKET_MAX_USES = 2

# TILE = 26 → 20 Spalten × 26 = 520 (leicht über 512, wird geclamped)
# Spielfeld: 512 px breit, 14 Zeilen × 36 px = 504 px + 16 HUD = 520 → TILE=36 für 14 Zeilen
# Besser: TILE=26, 20 Spalten=520 (map_pixel_w wird auf SCREEN_W geclampt)
TILE            = 26

PLAYER_W     = 22
PLAYER_H     = 26
CRYSTAL_SIZE = 10
MP_W         = TILE * 2
MP_H         = TILE // 2

MAP_OFFSET_Y = 20   # HUD-Höhe

# ── Farben ────────────────────────────────────────────────────────────────────
COL_PLATFORM = 4
COL_PLAYER   = 11
COL_CRYSTAL  = 10
COL_SPIKE    = 7
COL_ROCKET   = 9
COL_TEXT     = 7
COL_HUD_BG   = 0

# ── Spielzustände ─────────────────────────────────────────────────────────────
STATE_TITLE    = 0
STATE_SELECT   = 1
STATE_PLAY     = 2
STATE_DEAD     = 3
STATE_WIN_LVL  = 4
STATE_WIN_ALL  = 5

# ══════════════════════════════════════════════════════════════════════════════
#  LEVEL-DATEN
# ══════════════════════════════════════════════════════════════════════════════
LEVELS = [
    {
        "name": "Mondstation",
        "map": [
            "                    ",
            "                    ",
            "   2        2       ",
            "  111      111      ",
            "                    ",
            "      2        2    ",
            "     111      111   ",
            "                    ",
            "  2       2         ",
            " 111     111   2    ",
            "               111  ",
            "5       2         5 ",
            "111   11111   5   3 ",
            "11111111111111111111",
        ],
        "start": (0, 11),
        "bg_color": 1,
    },
    {
        "name": "Asteroidenguertel",
        "map": [
            "                    ",
            " 2           2      ",
            "11    2     111     ",
            "     111            ",
            "           2     2  ",
            "5         111   111 ",
            "1    5  5           ",
            "     1  1 2     25  ",
            "         111   111  ",
            "  2                 ",
            " 111  5    2      3 ",
            "      1   111       ",
            "5         5  5  5   ",
            "1111111111111111111 ",
        ],
        "start": (2, 11),
        "bg_color": 2,
        "moving_platforms": [
            {"col": 7,  "row": 3, "range": 4, "speed": 0.8},
            {"col": 12, "row": 6, "range": 2, "speed": 1.2},
        ],
    },
    {
        "name": "Raumstation Alpha",
        "map": [
            "                    ",
            "  2    2            ",
            " 111   111  52      ",
            "5           111     ",
            "1    2              ",
            "    111   5    25   ",
            "         111  111   ",
            " 2                  ",
            "111    2    5    2  ",
            "      111   1   111 ",
            "  2              5  ",
            " 111  5   2      1 3",
            "      1  111        ",
            "111111111111111111  ",
        ],
        "start": (0, 3),
        "bg_color": 1,
        "moving_platforms": [
            {"col": 4,  "row": 2,  "range": 2, "speed": 1.2},
            {"col": 10, "row": 5,  "range": 4, "speed": 1.5},
            {"col": 9,  "row": 9,  "range": 2, "speed": 1.1},
        ],
    },
    {
        "name": "Meteoritensturm",
        "map": [
            "                    ",
            "  2     2    52     ",
            " 111   111   111    ",
            "                    ",
            "5   25  5   2    2  ",
            "1  111  1  111  111 ",
            "                    ",
            "     5   2      5   ",
            "    111 111    111  ",
            "  2          25     ",
            " 111  5  5  111  2  ",
            "      1  1       111",
            "  2            5  3 ",
            "11111  11111  111111",
        ],
        "start": (0, 12),
        "bg_color": 2,
        "moving_platforms": [
            {"col": 8,  "row": 2,  "range": 5, "speed": 1.5},
            {"col": 3,  "row": 6,  "range": 4, "speed": 1.8},
            {"col": 13, "row": 9,  "range": 4, "speed": 1.4},
        ],
    },
    {
        "name": "Schwarzes Loch",
        "map": [
            "                    ",
            "2      25    2      ",
            "1     111   111     ",
            "   2                ",
            "  111   5  2    5   ",
            "        1 111   1   ",
            "  2               2 ",
            " 111  2    5     111",
            "     111   1        ",
            "  5      2     2    ",
            "  1     111   111   ",
            "     5         5  3 ",
            "2    1    2 5  1111 ",
            "1111111  111111     ",
        ],
        "start": (0, 11),
        "bg_color": 1,
        "moving_platforms": [
            {"col": 5,  "row": 1,  "range": 5, "speed": 1.8},
            {"col": 11, "row": 4,  "range": 4, "speed": 2.1},
            {"col": 4,  "row": 7,  "range": 5, "speed": 1.5},
            {"col": 11, "row": 10, "range": 2, "speed": 2.0},
        ],
    },
    {
        "name": "Nebulapassage",
        "map": [
            " 2                  ",
            "11   2      2       ",
            "    111    111  25  ",
            "               111  ",
            " 5      25          ",
            " 1     111  5   2   ",
            "            1  111 5",
            "  2    5           1",
            " 111   1   2  5     ",
            "          111 1     ",
            "5    2              ",
            "1   111  5    2   3 ",
            "         1   111 5  ",
            "1111111111111111111 ",
        ],
        "start": (2, 9),
        "bg_color": 2,
        "moving_platforms": [
            {"col": 6,  "row": 2,  "range": 4, "speed": 1.6},
            {"col": 1,  "row": 5,  "range": 4, "speed": 2.2},
            {"col": 10, "row": 7,  "range": 5, "speed": 1.6},
            {"col": 4,  "row": 10, "range": 4, "speed": 2.1},
        ],
    },
    {
        "name": "Galaktisches Zentrum",
        "map": [
            "  2      2    2     ",
            " 111    111  111    ",
            "                    ",
            "5   25       2   5  ",
            "1  111  5   111  1  ",
            "        1           ",
            "  2          2   2  ",
            " 111  5     111  11 ",
            "      1             ",
            "5   2      5    25  ",
            "1  111     1   111  ",
            "       2        5   ",
            "  5   111  2    1 3 ",
            "1111111111111111111 ",
        ],
        "start": (0, 12),
        "bg_color": 1,
        "moving_platforms": [
            {"col": 4,  "row": 1,  "range": 3, "speed": 2.1},
            {"col": 13, "row": 3,  "range": 4, "speed": 2.4},
            {"col": 2,  "row": 6,  "range": 5, "speed": 2.0},
            {"col": 7,  "row": 8,  "range": 4, "speed": 2.3},
            {"col": 5,  "row": 11, "range": 5, "speed": 1.8},
        ],
    },
]

NUM_LEVELS = len(LEVELS)

# ══════════════════════════════════════════════════════════════════════════════
#  HINTERGRUND – SPEKTAKULÄRES WELTALL
# ══════════════════════════════════════════════════════════════════════════════
_rng_seed = 42
def _rng():
    global _rng_seed
    _rng_seed = (_rng_seed * 1103515245 + 12345) & 0x7fffffff
    return _rng_seed

# Drei Parallax-Schichten: (x, y, col, phase, layer)
# layer 0 = weit weg (klein, langsam), 1 = mittel, 2 = nah (hell, schnell)
BG_STARS = []
for _i in range(80):
    BG_STARS.append((_rng() % SCREEN_W, _rng() % SCREEN_H, 5, _rng() % 90, 0))
for _i in range(50):
    BG_STARS.append((_rng() % SCREEN_W, _rng() % SCREEN_H, [6,5,5][_rng()%3], _rng() % 60, 1))
for _i in range(25):
    BG_STARS.append((_rng() % SCREEN_W, _rng() % SCREEN_H, 7, _rng() % 40, 2))

# Gaswolken: (cx, cy, rx, ry, col, alpha_step)
BG_CLOUDS = [
    (90,  90,  55, 28, 2,  3),
    (320, 55,  70, 22, 5,  4),
    (440, 180, 45, 20, 13, 5),
    (160, 300, 65, 26, 2,  3),
    (470, 360, 50, 18, 6,  4),
    (240, 430, 75, 24, 1,  5),
    (380, 480, 55, 20, 2,  3),
]

# Planeten: (cx, cy, r, col, ring_col, ring_tilt, moon)
BG_PLANETS = [
    (448, 72,  22, 9,  8,   True,  True),
    (58,  390, 14, 13, None, False, False),
    (495, 310, 9,  3,  None, False, False),
]

# Galaxienkern: (cx, cy, r, col)
GALAXY = (260, 200, 18, 7)

# Sternschnuppen
shooting_star  = {"active": False, "x": 0.0, "y": 0.0, "vx": 0.0, "vy": 0.0, "life": 0, "max_life": 40}
shooting_timer = 0

def init_shooting_star():
    shooting_star["active"]   = True
    shooting_star["x"]        = float(_rng() % SCREEN_W)
    shooting_star["y"]        = float(_rng() % (SCREEN_H // 3))
    speed = 5.0 + (_rng() % 40) / 10.0
    shooting_star["vx"]       = speed * math.cos(0.65)
    shooting_star["vy"]       = speed * math.sin(0.65)
    ml = 28 + _rng() % 22
    shooting_star["life"]     = ml
    shooting_star["max_life"] = ml

def update_bg():
    global shooting_timer
    shooting_timer += 1
    if not shooting_star["active"] and shooting_timer > 160 + _rng() % 140:
        init_shooting_star()
        shooting_timer = 0
    if shooting_star["active"]:
        shooting_star["x"] += shooting_star["vx"]
        shooting_star["y"] += shooting_star["vy"]
        shooting_star["life"] -= 1
        if shooting_star["life"] <= 0 or shooting_star["x"] > SCREEN_W or shooting_star["y"] > SCREEN_H:
            shooting_star["active"] = False

def draw_bg():
    t = timer

    # ── Gaswolken (dicht, mehrschichtig) ─────────────────────────────────────
    for (cx, cy, rx, ry, col, step) in BG_CLOUDS:
        for dx in range(-rx, rx + 1, step):
            for dy in range(-ry, ry + 1, step):
                if dx * dx * ry * ry + dy * dy * rx * rx < rx * rx * ry * ry:
                    pset(cx + dx, cy + dy, col)
        # hellerer Kern
        kx, ky = rx // 3, ry // 3
        for dx in range(-kx, kx + 1, 2):
            for dy in range(-ky, ky + 1, 2):
                if dx * dx * ky * ky + dy * dy * kx * kx < kx * kx * ky * ky:
                    pset(cx + dx, cy + dy, 7 if col != 1 else 5)

    # ── Galaxienkern (langsam rotierendes Leuchten) ───────────────────────────
    gx, gy, gr, gc = GALAXY
    for arm in range(4):
        angle_off = (t * 0.008 + arm * math.pi / 2)
        for r in range(2, gr + 8):
            ang = angle_off + r * 0.18
            px2 = int(gx + r * math.cos(ang) * 0.9)
            py2 = int(gy + r * math.sin(ang) * 0.4)
            col = 7 if r < 6 else (6 if r < 12 else 5)
            if 0 <= px2 < SCREEN_W and 0 <= py2 < SCREEN_H:
                pset(px2, py2, col)
    circ(gx, gy, 4, 7)
    circ(gx, gy, 2, 7)
    pset(gx, gy, 7)

    # ── Planeten ──────────────────────────────────────────────────────────────
    for (px2, py2, pr, pc, ring_col, has_ring, has_moon) in BG_PLANETS:
        # Schatten-Halo
        circb(px2 + 1, py2 + 1, pr + 1, 0)
        circ(px2, py2, pr, pc)
        # Oberflächenstreifen
        for stripe_y in range(py2 - pr + 3, py2 + pr - 3, 5):
            half = int(math.sqrt(max(0, pr * pr - (stripe_y - py2) ** 2)))
            if half > 2:
                line(px2 - half + 2, stripe_y, px2 + half - 2, stripe_y, pc - 1 if pc > 1 else 1)
        # Glanzpunkt
        pset(px2 - pr // 3, py2 - pr // 3, 7)
        pset(px2 - pr // 3 + 1, py2 - pr // 3, 7)
        # Ring
        if has_ring and ring_col is not None:
            for dx in range(-pr - 9, pr + 10):
                # flache Ellipse
                norm = dx / (pr + 9)
                ell_y = int(norm * norm * 4)
                for oy in [ell_y, ell_y + 1]:
                    rx2 = px2 + dx
                    ry2 = py2 + 3 + oy
                    if 0 <= rx2 < SCREEN_W and 0 <= ry2 < SCREEN_H:
                        # Lücke wo Planet davor ist
                        if not (abs(dx) < pr - 1 and abs(oy) < 3):
                            pset(rx2, ry2, ring_col)
        # Mond
        if has_moon:
            moon_ang = t * 0.03
            mx2 = int(px2 + (pr + 10) * math.cos(moon_ang))
            my2 = int(py2 + (pr + 5)  * math.sin(moon_ang) * 0.4)
            circ(mx2, my2, 4, 13)
            pset(mx2 - 1, my2 - 1, 7)

    # ── Sterne (Parallax-Twinkle) ─────────────────────────────────────────────
    for (sx, sy, sc, phase, layer) in BG_STARS:
        period = 50 + layer * 20
        if (t + phase) % period < period - 8:
            pset(sx, sy, sc)
        # Kreuz-Glanz nur für helle Vordergrundsterne
        if layer == 2 and (t + phase) % 40 < 32:
            pset(sx - 1, sy, 6)
            pset(sx + 1, sy, 6)
            pset(sx, sy - 1, 6)
            pset(sx, sy + 1, 6)
        elif layer == 1 and (t + phase) % 60 < 50:
            pset(sx - 1, sy, 5)
            pset(sx + 1, sy, 5)

    # ── Sternschnuppe mit Schweif ─────────────────────────────────────────────
    if shooting_star["active"]:
        sx2 = int(shooting_star["x"])
        sy2 = int(shooting_star["y"])
        life_frac = shooting_star["life"] / shooting_star["max_life"]
        pset(sx2, sy2, 7)
        tail_len = int(8 * life_frac) + 3
        for step in range(1, tail_len + 1):
            tx2 = sx2 - int(shooting_star["vx"] * step * 0.35)
            ty2 = sy2 - int(shooting_star["vy"] * step * 0.35)
            col = 7 if step <= 2 else (6 if step <= 5 else 5)
            if 0 <= tx2 < SCREEN_W and 0 <= ty2 < SCREEN_H:
                pset(tx2, ty2, col)

# ══════════════════════════════════════════════════════════════════════════════
#  GLOBALER SPIELSTAND
# ══════════════════════════════════════════════════════════════════════════════
x                = 0.0
y                = 0.0
vx               = 0.0
vy               = 0.0
player_on_ground = False
player_jumps     = 0
player_alive     = True

walk_frame = 0
walk_tick  = 0
WALK_SPEED = 8
is_moving  = False
facing_right = True

rocket_fuel   = ROCKET_DURATION
rocket_uses   = 0
rocket_active = False

level_map        = []
bg_color         = 1
portal_pos       = None
crystals         = []
spikes           = []
moving_platforms = []

score         = 0
current_level = 0
state         = STATE_TITLE
timer         = 0

unlocked      = [True] + [False] * (NUM_LEVELS - 1)
highscores    = [0] * NUM_LEVELS
level_timer   = 0
select_cursor = 0
missing_msg   = 0

# ══════════════════════════════════════════════════════════════════════════════
#  HILFSFUNKTIONEN
# ══════════════════════════════════════════════════════════════════════════════
def tile_at(col, row):
    if 0 <= row < len(level_map) and 0 <= col < len(level_map[row]):
        return level_map[row][col]
    return " "

def rect_overlap(ax, ay, aw, ah, bx, by, bw, bh):
    return ax < bx + bw and ax + aw > bx and ay < by + bh and ay + ah > by

def frames_to_time(f):
    secs      = f // 60
    hundredth = (f % 60) * 100 // 60
    mins      = secs // 60
    secs      = secs % 60
    return f"{mins:02}:{secs:02}.{hundredth:02}"

def btn_left():
    return btn(KEY_LEFT) or btn(KEY_A) or btn(GAMEPAD1_BUTTON_DPAD_LEFT)

def btn_right():
    return btn(KEY_RIGHT) or btn(KEY_D) or btn(GAMEPAD1_BUTTON_DPAD_RIGHT)

def btnp_jump():
    return (btnp(KEY_SPACE) or btnp(KEY_UP) or btnp(KEY_W)
            or btnp(GAMEPAD1_BUTTON_A) or btnp(GAMEPAD1_BUTTON_DPAD_UP))

def btnp_rocket():
    return btnp(KEY_Z) or btnp(GAMEPAD1_BUTTON_B) or btnp(GAMEPAD1_BUTTON_X)

def btnp_confirm():
    return btnp(KEY_RETURN) or btnp(KEY_SPACE) or btnp(GAMEPAD1_BUTTON_A)

def btnp_menu_up():
    return btnp(KEY_UP) or btnp(KEY_W) or btnp(GAMEPAD1_BUTTON_DPAD_UP)

def btnp_menu_down():
    return btnp(KEY_DOWN) or btnp(KEY_S) or btnp(GAMEPAD1_BUTTON_DPAD_DOWN)

# ══════════════════════════════════════════════════════════════════════════════
#  LEVEL LADEN
# ══════════════════════════════════════════════════════════════════════════════
def load_level(idx):
    global level_map, bg_color, portal_pos
    global crystals, spikes, moving_platforms
    global x, y, vx, vy
    global player_on_ground, player_jumps, player_alive
    global rocket_fuel, rocket_uses, rocket_active
    global level_timer, missing_msg
    global walk_frame, walk_tick, is_moving, facing_right

    data      = LEVELS[idx]
    level_map = data["map"]
    bg_color  = data.get("bg_color", 1)

    sx, sy = data["start"]
    lmap    = data["map"]
    max_col = len(lmap[0]) - 2 if lmap else 0
    found   = False
    for try_col in range(sx, max_col + 1):
        here  = lmap[sy][try_col]     if 0 <= sy     < len(lmap) and try_col < len(lmap[sy])     else " "
        below = lmap[sy + 1][try_col] if 0 <= sy + 1 < len(lmap) and try_col < len(lmap[sy + 1]) else " "
        if here != "5" and below == "1":
            sx    = try_col
            found = True
            break
    if not found:
        for try_col in range(sx, max_col + 1):
            here = lmap[sy][try_col] if 0 <= sy < len(lmap) and try_col < len(lmap[sy]) else " "
            if here != "5":
                sx = try_col
                break

    x                = float(sx * TILE)
    y                = float(sy * TILE)
    vx               = 0.0
    vy               = 0.0
    player_on_ground = False
    player_jumps     = 0
    player_alive     = True
    facing_right     = True

    rocket_fuel   = ROCKET_DURATION
    rocket_uses   = 0
    rocket_active = False

    level_timer = 0
    missing_msg = 0
    walk_frame  = 0
    walk_tick   = 0
    is_moving   = False

    crystals   = []
    spikes     = []
    portal_pos = None

    for row_i, row in enumerate(level_map):
        for col_i, ch in enumerate(row):
            cx = col_i * TILE + TILE // 2
            cy = row_i * TILE + TILE // 2
            if ch == "2":
                crystals.append({"x": cx, "y": cy, "collected": False, "anim": 0})
            elif ch == "3":
                portal_pos = (col_i * TILE, row_i * TILE)
            elif ch == "5":
                spikes.append({"x": col_i * TILE, "y": row_i * TILE + TILE - 10})

    moving_platforms = []
    for mp in data.get("moving_platforms", []):
        moving_platforms.append({
            "x":      float(mp["col"] * TILE),
            "base_x": float(mp["col"] * TILE),
            "y":      mp["row"] * TILE,
            "range":  mp["range"] * TILE,
            "speed":  mp["speed"],
            "dir":    1,
        })

# ══════════════════════════════════════════════════════════════════════════════
#  UPDATE-FUNKTIONEN
# ══════════════════════════════════════════════════════════════════════════════
def update_moving_platforms():
    for mp in moving_platforms:
        mp["x"] += mp["speed"] * mp["dir"]
        if mp["x"] >= mp["base_x"] + mp["range"] or mp["x"] <= mp["base_x"]:
            mp["dir"] *= -1

def update_crystals():
    for c in crystals:
        c["anim"] = (c["anim"] + 1) % 60

def collide_h():
    global x, vx
    for row in range(int(y // TILE), int((y + PLAYER_H - 1) // TILE) + 1):
        if vx > 0:
            col = int((x + PLAYER_W) // TILE)
            if tile_at(col, row) == "1":
                x  = col * TILE - PLAYER_W
                vx = 0.0
        elif vx < 0:
            col = int(x // TILE)
            if tile_at(col, row) == "1":
                x  = (col + 1) * TILE
                vx = 0.0

def collide_v():
    global y, vy, player_on_ground, player_jumps
    for col in range(int(x // TILE), int((x + PLAYER_W - 1) // TILE) + 1):
        if vy > 0:
            row = int((y + PLAYER_H) // TILE)
            if tile_at(col, row) == "1":
                y                = row * TILE - PLAYER_H
                vy               = 0.0
                player_on_ground = True
                player_jumps     = 0
        elif vy < 0:
            row = int(y // TILE)
            if tile_at(col, row) == "1":
                y  = (row + 1) * TILE
                vy = 0.0

def update_player():
    global x, y, vx, vy
    global player_on_ground, player_jumps, player_alive
    global rocket_fuel, rocket_uses, rocket_active
    global walk_frame, walk_tick, is_moving, facing_right

    if not player_alive:
        return

    moving_this_frame = False
    vx = 0.0
    if btn_left():
        vx = -MOVE_SPEED
        moving_this_frame = True
        facing_right = False
    elif btn_right():
        vx = MOVE_SPEED
        moving_this_frame = True
        facing_right = True

    is_moving = moving_this_frame

    if is_moving and player_on_ground:
        walk_tick += 1
        if walk_tick >= WALK_SPEED:
            walk_tick  = 0
            walk_frame = 1 - walk_frame
    else:
        walk_frame = 0
        walk_tick  = 0

    if btnp_rocket():
        if (not rocket_active
                and rocket_fuel == ROCKET_DURATION
                and rocket_uses < ROCKET_MAX_USES):
            rocket_active = True
            rocket_uses  += 1
            vy            = 0.0

    if rocket_active:
        rocket_fuel -= 1
        if y <= 0:
            y             = 0.0
            vy            = 0.0
        else:
            vy = -6.0
        if rocket_fuel <= 0:
            rocket_active = False
            rocket_fuel   = 0
    else:
        vy += GRAVITY
        vy  = min(vy, 12)
        if player_on_ground and rocket_fuel < ROCKET_DURATION:
            rocket_fuel = min(rocket_fuel + 2, ROCKET_DURATION)

    player_on_ground = False

    map_pixel_w = len(level_map[0]) * TILE if level_map else SCREEN_W
    x += vx
    x  = max(0.0, min(x, map_pixel_w - PLAYER_W))
    collide_h()

    y += vy
    if y < 0:
        y  = 0.0
        vy = 0.0
    collide_v()

    for mp in moving_platforms:
        if rect_overlap(x, y, PLAYER_W, PLAYER_H,
                        mp["x"], mp["y"], MP_W, MP_H):
            if vy >= 0 and y + PLAYER_H <= mp["y"] + MP_H + 6:
                y                = mp["y"] - PLAYER_H
                vy               = 0.0
                player_on_ground = True
                player_jumps     = 0
                x               += mp["speed"] * mp["dir"]

    if btnp_jump() and player_jumps < 2:
        vy               = JUMP_FORCE
        player_jumps    += 1
        player_on_ground = False

    map_pixel_h = len(level_map) * TILE if level_map else (SCREEN_H - MAP_OFFSET_Y)
    if y > map_pixel_h + 40:
        player_alive = False

# ══════════════════════════════════════════════════════════════════════════════
#  DRAW-FUNKTIONEN
# ══════════════════════════════════════════════════════════════════════════════
def draw_platform_tile(tx, ty):
    # Plattform mit leichtem Glanz oben
    rect(tx, ty, TILE, TILE, COL_PLATFORM)
    rect(tx, ty, TILE, 3, COL_PLATFORM + 2)
    # kleine Nieten-Punkte
    pset(tx + 4, ty + TILE - 5, COL_PLATFORM + 1)
    pset(tx + TILE - 5, ty + TILE - 5, COL_PLATFORM + 1)

def draw_spike(tx, ty):
    # 3 Stacheln pro Tile, etwas grösser
    spike_h = 10
    n = 3
    spacing = TILE // n
    for i in range(n):
        bx = tx + i * spacing + spacing // 2
        tri(bx - 4, ty + spike_h,
            bx + 4, ty + spike_h,
            bx,     ty,
            COL_SPIKE)

def draw_portal(tx, ty):
    # Portal als leuchtender Kreis
    cx = tx + TILE // 2
    cy = ty + TILE // 2
    r  = TILE // 2 - 1
    t  = timer
    inner_col = 10 if (t // 15) % 2 == 0 else 9
    circ(cx, cy, r, 6)
    circ(cx, cy, r - 3, inner_col)
    pset(cx, cy, 7)

def draw_crystal(cx, cy, anim):
    # Diamant-Form
    off = 1 if anim < 30 else 0
    r   = CRYSTAL_SIZE // 2
    # Schatten
    tri(cx - r + 1, cy + off + 1,
        cx + r + 1, cy + off + 1,
        cx + 1,     cy - r + off + 1, 1)
    # Kristall
    tri(cx - r, cy + off,
        cx + r, cy + off,
        cx,     cy - r + off, COL_CRYSTAL)
    tri(cx - r, cy + off,
        cx + r, cy + off,
        cx,     cy + r + off, 9)
    pset(cx - 1, cy - 1 + off, 7)

def draw_level():
    for row_i, row in enumerate(level_map):
        for col_i, ch in enumerate(row):
            tx = col_i * TILE
            ty = row_i * TILE + MAP_OFFSET_Y
            if ch == "1":
                draw_platform_tile(tx, ty)
            elif ch == "3":
                draw_portal(tx, ty)
            elif ch == "5":
                draw_spike(tx, ty)

def draw_entities():
    for mp in moving_platforms:
        mx = int(mp["x"])
        my = mp["y"] + MAP_OFFSET_Y
        rect(mx, my, MP_W, MP_H, COL_PLATFORM + 1)
        rect(mx, my, MP_W, 2, 7)
    for c in crystals:
        if not c["collected"]:
            draw_crystal(c["x"], c["y"] + MAP_OFFSET_Y, c["anim"])

def draw_player():
    if not player_alive:
        return
    px = int(x)
    py = int(y) + MAP_OFFSET_Y
    if rocket_active:
        blt(px, py, 0, 32, 0, 47, 18, 0)
    elif is_moving and player_on_ground:
        blt(px, py, 0, 16, 16 + walk_frame * 16, PLAYER_W, PLAYER_H, 0)
    else:
        blt(px, py, 0, 40, 16, PLAYER_W, PLAYER_H, 0)


def draw_hud():
    rect(0, 0, SCREEN_W, MAP_OFFSET_Y, COL_HUD_BG)
    collected = sum(1 for c in crystals if c["collected"])
    total     = len(crystals)
    text(2, 4, f"LVL:{current_level+1}  SCORE:{score}  {collected}/{total}", COL_TEXT)
    text(SCREEN_W // 2 - 20, 4, frames_to_time(level_timer), COL_CRYSTAL)
    hs = highscores[current_level]
    if hs > 0:
        text(SCREEN_W - 100, 4, f"BEST:{frames_to_time(hs)}", 6)
    fuel_pct  = rocket_fuel / ROCKET_DURATION
    uses_left = ROCKET_MAX_USES - rocket_uses
    bar_w     = int(50 * fuel_pct)
    rect(SCREEN_W - 68, 4, 50, 7, 1)
    rect(SCREEN_W - 68, 4, bar_w, 7, COL_ROCKET)
    text(SCREEN_W - 16, 4, f"x{uses_left}", COL_ROCKET)
    global missing_msg
    if missing_msg > 0:
        missing_msg -= 1
        rect(SCREEN_W // 2 - 70, SCREEN_H // 2 - 8, 140, 14, 8)
        text(SCREEN_W // 2 - 66, SCREEN_H // 2 - 4, "Alle Kristalle sammeln!", 7)

def draw_overlay(title, subtitle):
    rect(SCREEN_W // 2 - 70, SCREEN_H // 2 - 22, 140, 44, 0)
    text(SCREEN_W // 2 - len(title) * 2,    SCREEN_H // 2 - 14, title,    7)
    text(SCREEN_W // 2 - len(subtitle) * 2, SCREEN_H // 2 - 2,  subtitle, 6)

def draw_title():
    cls(1)
    draw_bg()
    cy = SCREEN_H // 2 - 40
    text(SCREEN_W // 2 - 28, cy,      "SKY RUNNER",                  10)
    text(SCREEN_W // 2 - 50, cy + 14, "Alien im Weltall-Raumschiff!", 7)
    text(SCREEN_W // 2 - 44, cy + 28, "PFEILE / WASD = Bewegen",      6)
    text(SCREEN_W // 2 - 44, cy + 36, "SPACE/UP/W    = Doppelsprung", 6)
    text(SCREEN_W // 2 - 44, cy + 44, "Z             = Raketenmodus", 9)
    if (timer // 30) % 2 == 0:
        text(SCREEN_W // 2 - 40, cy + 60, "ENTER oder SPACE druecken", 7)

def draw_select():
    cls(1)
    draw_bg()
    text(SCREEN_W // 2 - 36, 14, "── LEVEL AUSWAEHLEN ──", 10)
    text(SCREEN_W // 2 - 52, 24, "UP/DOWN = Waehlen  ENTER = Start", 6)
    for i, lvl in enumerate(LEVELS):
        row_y  = 40 + i * 20
        locked = not unlocked[i]
        if i == select_cursor:
            text(14, row_y, ">", 10)
        if locked:
            text(24, row_y,     f"LVL {i+1}  {lvl['name']}", 13)
            text(24, row_y + 9, "  [GESPERRT]",               8)
        else:
            text(24, row_y, f"LVL {i+1}  {lvl['name']}", 7)
            hs = highscores[i]
            if hs > 0:
                text(24, row_y + 9, f"  BEST: {frames_to_time(hs)}", COL_CRYSTAL)
            else:
                text(24, row_y + 9, "  noch kein Highscore", 5)

def draw_win_lvl():
    rect(SCREEN_W // 2 - 76, SCREEN_H // 2 - 40, 152, 80, 0)
    text(SCREEN_W // 2 - 40, SCREEN_H // 2 - 32, "LEVEL GESCHAFFT!", 10)
    text(SCREEN_W // 2 - 48, SCREEN_H // 2 - 18,
         f"Zeit:  {frames_to_time(level_timer)}", 7)
    hs = highscores[current_level]
    if hs > 0 and level_timer <= hs:
        text(SCREEN_W // 2 - 44, SCREEN_H // 2 - 6, "NEUER HIGHSCORE!", COL_CRYSTAL)
    elif hs > 0:
        text(SCREEN_W // 2 - 48, SCREEN_H // 2 - 6,
             f"BEST:  {frames_to_time(hs)}", 6)
    text(SCREEN_W // 2 - 54, SCREEN_H // 2 + 8,  "ENTER = Levelauswahl", 6)
    if current_level + 1 < NUM_LEVELS:
        text(SCREEN_W // 2 - 58, SCREEN_H // 2 + 20, "SPACE  = Naechstes Level", 7)

def draw_win_all():
    cls(1)
    draw_bg()
    cy = SCREEN_H // 2 - 40
    text(SCREEN_W // 2 - 48, cy,      "ALLE LEVEL GESCHAFFT!", 10)
    text(SCREEN_W // 2 - 44, cy + 14, f"Gesamtpunkte: {score}",  7)
    text(SCREEN_W // 2 - 44, cy + 24, "Highscores:",              6)
    for i in range(NUM_LEVELS):
        hs     = highscores[i]
        hs_str = frames_to_time(hs) if hs > 0 else "---"
        text(SCREEN_W // 2 - 44, cy + 34 + i * 10,
             f"  LVL{i+1}: {hs_str}", COL_CRYSTAL)
    if (timer // 30) % 2 == 0:
        text(SCREEN_W // 2 - 40, SCREEN_H - 20, "ENTER = Neustart", 7)

# ══════════════════════════════════════════════════════════════════════════════
#  HAUPT-UPDATE & HAUPT-DRAW
# ══════════════════════════════════════════════════════════════════════════════
def update():
    global state, timer, score, current_level, player_alive
    global select_cursor, missing_msg, level_timer

    timer += 1
    update_bg()

    if state == STATE_TITLE:
        if btnp_confirm():
            state = STATE_SELECT
            playm(0, loop=True)  # ← HIER: Musik startet, sobald das Menü öffnet!
        return

    if state == STATE_SELECT:
        if btnp_menu_up():
            select_cursor = (select_cursor - 1) % NUM_LEVELS
        if btnp_menu_down():
            select_cursor = (select_cursor + 1) % NUM_LEVELS
        if btnp_confirm():
            if unlocked[select_cursor]:
                current_level = select_cursor
                load_level(current_level)
                state = STATE_PLAY
                
                # ── AUCH HIER DIE MUSIK STARTEN ─────────────────────────────
                playm(0, loop=True)   
                # ────────────────────────────────────────────────────────────
        return

    if state == STATE_DEAD:
        if btnp_confirm():
            player_alive = True
            load_level(current_level)
            state = STATE_PLAY
            playm(0, loop=True)
            return
        if btnp(KEY_R):
            state = STATE_SELECT
            playm(0, loop=True)  # ← HIER: Musik startet wieder, wenn du ins Menü zurückgehst!
        return

    if state == STATE_WIN_LVL:
        if btnp(KEY_RETURN):
            state = STATE_SELECT
        if btnp(KEY_SPACE) and current_level + 1 < NUM_LEVELS:
            current_level += 1
            load_level(current_level)
            state = STATE_PLAY
        return

    if state == STATE_WIN_ALL:
        if btnp_confirm():
            score = 0
            state = STATE_SELECT
        return

   # ... (oberer Teil der update-Funktion mit den Zuständen) ...

    level_timer += 1
    update_moving_platforms()
    update_crystals()
    update_player()  # ← Nach dieser Zeile!

    # ── HIER KOMMT DER TEIL HIN ───────────────────────────────────────────────
    if not player_alive:
        if state != STATE_DEAD:
            stop()      # ← Stoppt die Musik (Fehlerfrei in jeder Pyxel-Version!)
            play(0, 6)  # ← Startet danach sofort deinen Sound 2 auf Kanal 0
            state = STATE_DEAD
        return
    # ──────────────────────────────────────────────────────────────────────────

    for c in crystals:
        if not c["collected"] and rect_overlap(
                x, y, PLAYER_W, PLAYER_H,
                c["x"], c["y"], CRYSTAL_SIZE, CRYSTAL_SIZE):
            c["collected"] = True
            score         += 10

    # ... (danach kommt die Kachel-Abfrage für die Stacheln) ...

    for sp in spikes:
        if rect_overlap(x, y, PLAYER_W, PLAYER_H, sp["x"], sp["y"], TILE, 6):
            if state != STATE_DEAD:
                player_alive = False
                stop()      # ← Stoppt die Musik fehlerfrei
                play(0, 6)  # ← Startet deinen Sound 2
                state        = STATE_DEAD
            return
        
        
    if portal_pos:
        ppx, ppy = portal_pos
        if rect_overlap(x, y, PLAYER_W, PLAYER_H, ppx, ppy, TILE, TILE):
            all_collected = all(c["collected"] for c in crystals)
            if not all_collected:
                missing_msg = 120
            else:
                hs = highscores[current_level]
                if hs == 0 or level_timer < hs:
                    highscores[current_level] = level_timer
                score += 50
                if current_level + 1 < NUM_LEVELS:
                    unlocked[current_level + 1] = True
                if current_level + 1 >= NUM_LEVELS:
                    state = STATE_WIN_ALL
                else:
                    state = STATE_WIN_LVL


def draw():
    cls(bg_color)

    if state == STATE_TITLE:
        draw_title()
        return
    if state == STATE_SELECT:
        draw_select()
        return
    if state == STATE_WIN_ALL:
        draw_win_all()
        return

    draw_bg()
    draw_level()
    draw_entities()
    draw_player()
    draw_hud()

    if state == STATE_DEAD:
        draw_overlay("YOU DIED!", "ENTER=Retry  R=Auswahl")
    elif state == STATE_WIN_LVL:
        draw_win_lvl()


# ══════════════════════════════════════════════════════════════════════════════
#  START DES SPIELS
# ══════════════════════════════════════════════════════════════════════════════
init(SCREEN_W, SCREEN_H, title="Sky Runner - Deep Space", fps=60)

try:
    load("res.pyxres")
except RuntimeError:
    print("Hinweis: res.pyxres nicht gefunden.")

try:
    playm(0, loop=True)
except Exception:
    pass

load_level(current_level)
run(update, draw)