import pyxel
import random
import math
import os

# ─── Konstanten ──────────────────────────────────────────────────────────────
SCREEN_W  = 256
SCREEN_H  = 256
TILE      = 16          # Tile-Größe in Pixeln
COLS      = SCREEN_W // TILE   # 16
ROWS      = SCREEN_H // TILE   # 16

# Spielzustände
STATE_INTRO   = "intro"
STATE_MENU    = "menu"
STATE_FLYING  = "flying"   # Wände fliegen ein → Kollision tödlich
STATE_LOCKED  = "locked"   # Labyrinth fertig  → Wände undurchdringbar
STATE_GHOST   = "ghost"
STATE_WIN     = "win"

# Spieler-Richtungen  (x = DIR*16 in Bank 0, y=0)
DIR_DOWN  = 0
DIR_LEFT  = 1
DIR_RIGHT = 2
DIR_UP    = 3
DIR_GHOST = 4

# ─── Sprite-Koordinaten ──────────────────────────────────────────────────────
# Bank 0
PLAYER_U_BASE = 0;  PLAYER_V = 0    # Spieler: x=DIR*16, y=0  (je 16×16)
KEY_U,   KEY_V   =  0, 16           # Schlüssel 8×8
CHEST_U, CHEST_V =  8, 16           # Truhe    16×16
TILE_U,  TILE_V  = 24, 16           # Boden    16×16
SHIELD_U, SHIELD_V = 80, 0          # Schutzschild 16x16

# Bank 1 – Hecken-Textur: einziges Tile 16×16 bei x=0, y=0
HEDGE_U, HEDGE_V = 0, 0

# Sounds
SND_KEY = 0;  SND_WIN = 1;  SND_DIE = 2;  SND_ALARM = 3

# Palette
COL_DARK=1; COL_GREEN=3; COL_BROWN=4; COL_WHITE=7; COL_RED=8
COL_GOLD=9; COL_YELLOW=10; COL_LGREEN=11; COL_LBLUE=12; COL_SKIN=15

# Level-Maps: 16×16, durch Algorithmus auf 0 Eckenprobleme geprüft.
# Snake-Maze-Prinzip: Nur horizontale Wände → keine diagonalen Innenwinkel.
LEVEL_MAPS = [
    # ── Level 1 – Leicht ─────────────────────────────────────────────
    [
        "################",
        "#..............#",
        "#.#######.####.#",
        "#.#K....#....#.#",
        "#.#.###.####.#.#",
        "#.#...#.K..#.#.#",
        "#.###.####.#.#.#",
        "#.#...P..#...#K#",
        "#.#.####.###.###",
        "#.#.#K.....#...#",
        "#.#.######.###.#",
        "#.#...K....#...#",
        "#.##########.#.#",
        "#...#...#.#..#.#",
        "#.#...#...#C.#.#",
        "################",
    ],
    # ── Level 2 – Mittel (Korrigiert: Alle Wege verbunden) ───────────
    [
        "################",
        "#........#K....#",
        "#.######.#.###.#",
        "#.#.K..#.#.#...#",
        "#.#.##.#.#.#.#.#",
        "#.#.#..#.#...#.#",
        "#.#.#.##.#####.#",
        "#K#.#....K.....#",
        "#.#.########.###",
        "#...#...P..#...#",
        "#.###.####.###.#",
        "#.#..K#K.......#",
        "#.#.##########.#",
        "#C#.#...#....#.#",
        "#.#...#...#K.#.#",
        "################",
    ],
    # ── Level 3 – Schwer (Korrigiert: Zentraler Gang öffnet alles) ───
    [
        "################",
        "#.....K..#..K..#",
        "#.######.#.###.#",
        "#..C...#.#.#K..#",
        "#.#.##.#.#.#.###",
        "#.#..#.#.#.#...#",
        "#.##.#.#.#.###.#",
        "#..K....P...K..#",
        "#.########.###.#",
        "#K.......#...#.#",
        "#.######.#.###.#",
        "#.#...#K.#.#.K.#",
        "#.#.####.#K#.#.#",
        "#...#.K..#...#.#",
        "#K#...#..#.#...#",
        "################",
    ],
]

# Geschwindigkeit der einfliegenden Wände je Level
LEVEL_CFG = {
    1: {"fly_speed": 0.3, "label": "Leicht", "time_limit": 60},  # 1 min
    2: {"fly_speed": 0.6, "label": "Mittel",  "time_limit": 45},  # 50 s
    3: {"fly_speed": 0.9, "label": "Schwer",  "time_limit":  30},  # 40 s
}

# ─── Hilfsfunktionen ─────────────────────────────────────────────────────────
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 dist(ax,ay,bx,by):
    return math.hypot(ax-bx, ay-by)


# ═══════════════════════════════════════════════════════════════════════════════
class WallPiece:
    """Ein einzelnes 16×16-Wand-Tile, das von einem Bildschirmrand zu seiner
    Zielposition fliegt und dort einrastet."""

    SNAP_DIST = 2.0   # Pixel-Distanz zum Einrasten

    def __init__(self, col, row, speed):
        self.tx = col * TILE    # Ziel-Pixel-x
        self.ty = row * TILE    # Ziel-Pixel-y
        self.snapped = False

        # Startposition: zufällige Seite, hinter dem Bildschirmrand
        side = random.randint(0, 3)
        if side == 0:    # oben
            self.x = random.uniform(-TILE, SCREEN_W)
            self.y = random.uniform(-TILE * 6, -TILE)
        elif side == 1:  # unten
            self.x = random.uniform(-TILE, SCREEN_W)
            self.y = random.uniform(SCREEN_H, SCREEN_H + TILE * 6)
        elif side == 2:  # links
            self.x = random.uniform(-TILE * 6, -TILE)
            self.y = random.uniform(-TILE, SCREEN_H)
        else:            # rechts
            self.x = random.uniform(SCREEN_W, SCREEN_W + TILE * 6)
            self.y = random.uniform(-TILE, SCREEN_H)

        # Richtungsvektor zur Zielposition
        dx = self.tx - self.x
        dy = self.ty - self.y
        d  = math.hypot(dx, dy) or 1
        # Variation: ±30 % Geschwindigkeit damit nicht alle gleichzeitig ankommen
        s = speed * random.uniform(0.7, 1.3)
        self.vx = dx / d * s
        self.vy = dy / d * s

    def update(self):
        if self.snapped:
            return
        self.x += self.vx
        self.y += self.vy
        if dist(self.x, self.y, self.tx, self.ty) < self.SNAP_DIST:
            self.x = self.tx
            self.y = self.ty
            self.snapped = True

    def draw(self, has_res):
        ix, iy = int(self.x), int(self.y)
        if has_res:
            pyxel.blt(ix, iy, 1, HEDGE_U, HEDGE_V, TILE, TILE, 0)
        else:
            pyxel.rect (ix+1, iy+1, 14, 14, COL_GREEN)
            pyxel.rectb(ix,   iy,   16, 16, COL_DARK)

    def pixel_rect(self):
        """Aktuelle Bounding-Box (für Kollision während Flug)."""
        return int(self.x)+1, int(self.y)+1, 14, 14

    def target_rect(self):
        """Ziel-Bounding-Box (für Wand-Blocking im LOCKED-Zustand)."""
        return self.tx+1, self.ty+1, 14, 14


# ═══════════════════════════════════════════════════════════════════════════════
class Game:
    def __init__(self):
        pyxel.init(SCREEN_W, SCREEN_H, title="Labyrinth Quest", fps=30)

        # Ressource laden (Fallback wenn nicht vorhanden)
        res_path = "labyrinth_quest.pyxres"
        self.has_res = False
        if os.path.exists(res_path):
            try:
                pyxel.load(res_path)
                self.has_res = True
            except Exception:
                pass

        self.state       = STATE_INTRO
        self.level       = 1
        self.score       = 0
        self.total_keys  = 0
        self.total_time  = 0
        self.intro_tick  = 0
        self._first_run  = True
        self._reset_round()
        pyxel.run(self.update, self.draw)

    # ── Setup ─────────────────────────────────────────────────────────────────
    def _parse_map(self):
        """Liest die aktuelle Level-Map und befüllt Wände, Schlüssel, Truhe, Start."""
        lmap = LEVEL_MAPS[self.level - 1]
        self.wall_pieces   = []
        self.key_positions = []   # [[pixel_x, pixel_y], ...]
        self.chest_x = self.chest_y = 0
        self.start_x = self.start_y = 0
        # wall_grid[row][col] = True wenn dort Wand
        self.wall_grid = [[False]*COLS for _ in range(ROWS)]

        speed = LEVEL_CFG[self.level]["fly_speed"]
        for row, line in enumerate(lmap):
            for col, ch in enumerate(line):
                if ch == '#':
                    self.wall_pieces.append(WallPiece(col, row, speed))
                    self.wall_grid[row][col] = True
                elif ch == 'K':
                    self.key_positions.append([col*TILE + 4, row*TILE + 4])
                elif ch == 'C':
                    self.chest_x = col*TILE
                    self.chest_y = row*TILE
                elif ch == 'P':
                    self.start_x = col*TILE
                    self.start_y = row*TILE

        self.keys_needed = len(self.key_positions)

    def _reset_round(self):
        self._parse_map()
        self.px             = self.start_x
        self.py             = self.start_y
        self.direction      = DIR_DOWN
        self.keys_collected = 0
        self.round_time     = 0
        self.ghost_timer    = 0
        self.ghost_shake    = 0
        self.locked         = False   # True sobald alle Wände eingerastet
        self.hit_penalty    = 0       # Summierte Punktabzüge dieser Runde
        self.hit_cooldown   = 0       # Frames Unverwundbarkeit nach Treffer
        self.time_up        = False   # True wenn Zeitlimit abgelaufen
        self.alarm_playing  = False   # True wenn 10s-Alarm läuft

    # ── Update ────────────────────────────────────────────────────────────────
    def update(self):
        if   self.state == STATE_INTRO:  self._update_intro()
        elif self.state == STATE_MENU:   self._update_menu()
        elif self.state in (STATE_FLYING, STATE_LOCKED): self._update_playing()
        elif self.state == STATE_GHOST:  self._update_ghost()
        elif self.state == STATE_WIN:    self._update_win()

    def _update_intro(self):
        self.intro_tick += 1
        if (pyxel.btnp(pyxel.KEY_RETURN) or pyxel.btnp(pyxel.KEY_SPACE) or
                pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_DOWN) or
                pyxel.btnp(pyxel.KEY_LEFT) or pyxel.btnp(pyxel.KEY_RIGHT)):
            self.state = STATE_MENU

    def _update_menu(self):
        if   pyxel.btnp(pyxel.KEY_1): self.level=1; self._start_game()
        elif pyxel.btnp(pyxel.KEY_2): self.level=2; self._start_game()
        elif pyxel.btnp(pyxel.KEY_3): self.level=3; self._start_game()
        elif pyxel.btnp(pyxel.KEY_Q): pyxel.quit()

    def _start_game(self):
        self.score = 0; self.total_keys = 0; self.total_time = 0
        self._first_run = False
        self._reset_round()
        self.state = STATE_FLYING

    def _update_playing(self):
        self.round_time += 1

        # ── Zeitlimit prüfen ─────────────────────────────────────────────────
        limit_frames = LEVEL_CFG[self.level]["time_limit"] * 30
        if self.round_time >= limit_frames and not self.time_up:
            self.time_up = True
            if self.has_res:
                pyxel.stop(0)          # Alarm stoppen
                pyxel.play(0, SND_DIE)
            self.alarm_playing = False
            self.total_time += self.round_time
            self._calc_score()
            self.ghost_timer = 90
            self.ghost_shake = 0
            self.state = STATE_GHOST   # Geist-Animation → dann Menü
            return

        # ── 10-Sekunden-Alarm ────────────────────────────────────────────────
        if self.has_res and not self.alarm_playing and not self.time_up:
            limit_secs   = LEVEL_CFG[self.level]["time_limit"]
            elapsed_secs = self.round_time // 30
            remaining    = limit_secs - elapsed_secs
            if remaining <= 10:
                self.alarm_playing = True
                pyxel.play(0, SND_ALARM, loop=True)   # Loop bis gestoppt

        # ── Wände updaten ────────────────────────────────────────────────────
        all_snapped = True
        for w in self.wall_pieces:
            w.update()
            if not w.snapped:
                all_snapped = False

        if all_snapped and self.state == STATE_FLYING:
            self.state = STATE_LOCKED
            self.locked = True
            # Spieler sofort aus Wänden schieben – er könnte gerade
            # in einem Tile stehen das jetzt zur Wand geworden ist
            self._push_player_out_of_walls()

        # ── Spieler bewegen ──────────────────────────────────────────────────
        speed = 2
        dx, dy = 0, 0
        if   pyxel.btn(pyxel.KEY_LEFT):  dx=-speed; self.direction=DIR_LEFT
        elif pyxel.btn(pyxel.KEY_RIGHT): dx= speed; self.direction=DIR_RIGHT
        elif pyxel.btn(pyxel.KEY_UP):    dy=-speed; self.direction=DIR_UP
        elif pyxel.btn(pyxel.KEY_DOWN):  dy= speed; self.direction=DIR_DOWN

        if self.state == STATE_LOCKED:
            # Sicherheitsnetz: Falls Spieler feststeckt, sofort befreien
            self._push_player_out_of_walls()
            # Im LOCKED-Zustand: Wand-Kollision = Stopp (kein Tod)
            nx = max(0, min(SCREEN_W-TILE, self.px + dx))
            ny = max(0, min(SCREEN_H-TILE, self.py + dy))
            if dx != 0 and not self._wall_blocks(nx, self.py):
                self.px = nx
            if dy != 0 and not self._wall_blocks(self.px, ny):
                self.py = ny
        else:
            # Im FLYING-Zustand: freie Bewegung, Rand-Clamp
            self.px = max(0, min(SCREEN_W-TILE, self.px + dx))
            self.py = max(0, min(SCREEN_H-TILE, self.py + dy))

        # ── Schlüssel einsammeln ─────────────────────────────────────────────
        for kp in self.key_positions[:]:
            if rect_overlap(self.px+3, self.py+3, 10,10, kp[0], kp[1], 8,8):
                self.key_positions.remove(kp)
                self.keys_collected += 1
                if self.has_res: pyxel.play(0, SND_KEY)

        # ── Truhe erreichen ──────────────────────────────────────────────────
        if self.keys_collected >= self.keys_needed:
            if rect_overlap(self.px+2, self.py+2, 12,12,
                            self.chest_x, self.chest_y, 16,16):
                if self.has_res:
                    pyxel.stop(0)            # Alarm (falls aktiv) sofort stoppen
                    pyxel.play(0, SND_WIN)   # Gewinner-Sound
                self.alarm_playing = False
                self.total_keys += self.keys_collected
                self.total_time += self.round_time
                self._calc_score()
                self.state = STATE_WIN
                return

        # ── Kollision mit fliegenden Wänden (nur FLYING-Phase) ───────────────
        if self.state == STATE_FLYING and self.hit_cooldown == 0:
            for w in self.wall_pieces:
                wx, wy, ww, wh = w.pixel_rect()
                if rect_overlap(self.px+2, self.py+2, 12,12, wx,wy,ww,wh):
                    if self.has_res: pyxel.play(0, SND_DIE)
                    self.hit_penalty += 50          # 50 Punkte Abzug pro Treffer
                    self.hit_cooldown = 90          # ~3 s Unverwundbarkeit
                    self.ghost_timer  = 90          # Geist-Animation ~3 s
                    self.ghost_shake  = 0
                    self.state = STATE_GHOST        # Figur ist betäubt
                    return

        # Cooldown herunterzählen (auch während FLYING läuft er weiter)
        if self.hit_cooldown > 0:
            self.hit_cooldown -= 1

    def _wall_blocks(self, px, py):
        """Prüft ob die Spieler-Bounding-Box an Position (px,py) mit einer
        Wand im Grid überlappt."""
        # Vier Ecken des Spielers prüfen (mit 2px Innenrand)
        for cx, cy in [(px+2, py+2), (px+13, py+2),
                       (px+2, py+13), (px+13, py+13)]:
            col = cx // TILE
            row = cy // TILE
            if 0 <= row < ROWS and 0 <= col < COLS:
                if self.wall_grid[row][col]:
                    return True
        return False

    def _push_player_out_of_walls(self):
        """Schiebt den Spieler aus Wand-Tiles heraus falls er darin feststeckt.
        Sucht das nächste freie Grid-Tile und platziert den Spieler dort.
        Wird aufgerufen sobald das Labyrinth einrastet (FLYING→LOCKED) und
        nach jeder Ghost-Phase."""
        if not self._wall_blocks(self.px, self.py):
            return  # Kein Problem, nichts zu tun

        # Aktuelle Grid-Position des Spieler-Mittelpunkts
        cx = (self.px + TILE // 2) // TILE
        cy = (self.py + TILE // 2) // TILE

        # BFS-artiges Spiralsuchen: vom aktuellen Tile aus nach außen
        for radius in range(1, ROWS + COLS):
            for dr in range(-radius, radius + 1):
                for dc in range(-radius, radius + 1):
                    if abs(dr) != radius and abs(dc) != radius:
                        continue   # Nur die äußere Schale der Spirale
                    row = cy + dr
                    col = cx + dc
                    if not (0 <= row < ROWS and 0 <= col < COLS):
                        continue
                    if self.wall_grid[row][col]:
                        continue   # Wand-Tile überspringen
                    # Freies Tile gefunden – Spieler bündig auf Tile-Anfang setzen
                    nx = max(0, min(SCREEN_W - TILE, col * TILE))
                    ny = max(0, min(SCREEN_H - TILE, row * TILE))
                    # Nochmal prüfen (Sicherheit)
                    if not self._wall_blocks(nx, ny):
                        self.px = nx
                        self.py = ny
                        return

    def _calc_score(self):
        time_bonus       = max(0, 300 - self.total_time // 30)
        self.score       = self.total_keys * 100 + time_bonus - self.hit_penalty
        # Score darf negativ werden – zeigt wie oft man getroffen wurde

    def _update_ghost(self):
        # ── NEU: Wände fliegen auch während der Stun-Phase weiter! ──
        all_snapped = True
        for w in self.wall_pieces:
            w.update()
            if not w.snapped:
                all_snapped = False
        
        # Falls während der Stun-Phase alle Wände einrasten
        if all_snapped:
            self.locked = True

        self.ghost_timer -= 1
        self.ghost_shake += 1
        
        if self.ghost_timer <= 0:
            self.ghost_timer = 0
            self.ghost_shake = 0
            if self.time_up:
                # Zeitlimit: Runde beendet → Menü
                self.state = STATE_MENU
            else:
                # Hecken-Treffer überstanden: Status setzen
                self.state = STATE_LOCKED if self.locked else STATE_FLYING
                # Spieler aus Wänden schieben falls er darin feststeckt
                # (passiert wenn Wände während der Ghost-Phase eingerastet sind)
                if self.state == STATE_LOCKED:
                    self._push_player_out_of_walls()
                # Alarm wieder starten falls noch in der 10s-Phase
                if self.has_res and not self.time_up:
                    limit_secs   = LEVEL_CFG[self.level]["time_limit"]
                    elapsed_secs = self.round_time // 30
                    remaining    = limit_secs - elapsed_secs
                    if remaining <= 10:
                        self.alarm_playing = True
                        pyxel.play(0, SND_ALARM, loop=True)

    def _update_win(self):
        if pyxel.btnp(pyxel.KEY_RETURN) or pyxel.btnp(pyxel.KEY_SPACE):
            self.state = STATE_MENU

    # ── Draw ──────────────────────────────────────────────────────────────────
    def draw(self):
        pyxel.cls(0)
        if   self.state == STATE_INTRO:
            self._draw_intro()
        elif self.state == STATE_MENU:
            self._draw_bg(); self._draw_menu()
        elif self.state in (STATE_FLYING, STATE_LOCKED):
            self._draw_game()
        elif self.state == STATE_GHOST:
            self._draw_game(); self._draw_ghost()
        elif self.state == STATE_WIN:
            self._draw_game(); self._draw_win_overlay()

    # ── Hintergrund ───────────────────────────────────────────────────────────
    def _draw_bg(self):
        if self.has_res:
            for r in range(ROWS):
                for c in range(COLS):
                    pyxel.blt(c*TILE, r*TILE, 0, TILE_U, TILE_V, TILE, TILE, 0)
        else:
            for r in range(ROWS):
                for c in range(COLS):
                    col = COL_GREEN if (r+c)%2==0 else COL_LGREEN
                    pyxel.rect(c*TILE, r*TILE, TILE, TILE, col)

    # ── Spielfeld ─────────────────────────────────────────────────────────────
    def _draw_game(self):
        self._draw_bg()
        # Truhe
        self._draw_chest_sprite(self.chest_x, self.chest_y)
        # Schlüssel
        for kp in self.key_positions:
            self._draw_key_sprite(kp[0], kp[1])
        # Wände
        for w in self.wall_pieces:
            w.draw(self.has_res)
            
        # Spieler
        self._draw_player_sprite(self.px, self.py, self.direction)
        
        # --- NEU: Schutzschild-Overlay ---
        if self.hit_cooldown > 0 and self.state != STATE_GHOST:
            self._draw_shield_sprite(self.px, self.py, self.hit_cooldown)
        # ---------------------------------
        
        # HUD
        self._draw_hud()
        # Phase-Anzeige
        if self.state == STATE_FLYING or (self.state == STATE_GHOST and not self.locked):
            remaining = sum(1 for w in self.wall_pieces if not w.snapped)
            pct = 1.0 - remaining / max(1, len(self.wall_pieces))
            # Fortschrittsbalken
            bw = SCREEN_W - 60
            pyxel.rect (30, SCREEN_H-10, bw, 6, COL_DARK)
            pyxel.rect (30, SCREEN_H-10, int(bw*pct), 6, COL_LGREEN)
            pyxel.rectb(30, SCREEN_H-10, bw, 6, COL_WHITE)
            pyxel.text (30, SCREEN_H-20, "Hecken fliegen ein! AUSWEICHEN!", COL_RED)

    def _draw_hud(self):
        pyxel.text(4, 4,  f"KEYS: {self.keys_collected}/{self.keys_needed}", COL_WHITE)
        # Countdown: verbleibende Zeit
        limit_secs = LEVEL_CFG[self.level]["time_limit"]
        elapsed    = self.round_time // 30
        remaining  = max(0, limit_secs - elapsed)
        mins, secs = divmod(remaining, 60)
        time_str   = f"{mins}:{secs:02d}"
        # Farbe: normal=weiß, ≤30s=orange, ≤10s=rot blinkend
        if remaining <= 10:
            tcol = COL_RED if (self.round_time // 8) % 2 == 0 else COL_YELLOW
        elif remaining <= 30:
            tcol = COL_GOLD
        else:
            tcol = COL_WHITE
        pyxel.text(4, 12, f"ENDE IN: {time_str}", tcol)
        pyxel.text(SCREEN_W-60, 4,
                   f"LVL {self.level} {LEVEL_CFG[self.level]['label']}", COL_YELLOW)
        # Treffer-Anzeige
        if self.hit_penalty > 0:
            hits = self.hit_penalty // 50
            pyxel.text(4, 20, f"TREFFER: -{self.hit_penalty}Pts ({hits}x)", COL_RED)
        # Unverwundbarkeits-Blinker (optionaler Text)
        if self.hit_cooldown > 0 and (self.hit_cooldown // 5) % 2 == 0:
            pyxel.text(SCREEN_W-60, 12, "SCHUTZ!", COL_LBLUE)

    def _draw_ghost(self):
        shake_x = int(math.sin(self.ghost_shake * 0.8) * 4)
        self._draw_player_sprite(self.px + shake_x, self.py, DIR_GHOST)

    # ── Intro ─────────────────────────────────────────────────────────────────
    def _draw_intro(self):
        t = self.intro_tick
        self._draw_bg()
        # Deko: ein paar Wand-Tiles fliegen herum
        for i in range(6):
            phase = (t * 0.5 + i * 43) % (SCREEN_W + 32)
            ypos  = int((i * 61 + 16) % (SCREEN_H - 16))
            wx    = int(phase) - 16
            if self.has_res:
                pyxel.blt(wx, ypos, 1, HEDGE_U, HEDGE_V, TILE, TILE, 0)
            else:
                pyxel.rect(wx+1,ypos+1,14,14,COL_LGREEN)
                pyxel.rectb(wx,ypos,16,16,COL_DARK)

        # Titel
        pyxel.rect (16, 14, 224, 58, COL_DARK)
        pyxel.rectb(16, 14, 224, 58, COL_YELLOW)
        pyxel.rectb(18, 16, 220, 54, COL_GOLD)
        t1 = "LABYRINTH QUEST"
        pyxel.text((SCREEN_W-len(t1)*4)//2-1, 25, t1, 0)
        pyxel.text((SCREEN_W-len(t1)*4)//2,   24, t1, COL_YELLOW)
        pyxel.text(32, 40, "Hecken bauen sich auf - navigiere dich frei!", COL_LGREEN)
        pyxel.text(48, 52, "Sammle Schluessel und finde die Truhe", COL_GOLD)

        # Anleitung
        iy = 80
        pyxel.rect (16, iy, 224, 126, COL_DARK)
        pyxel.rectb(16, iy, 224, 126, COL_WHITE)
        lines = [
            (COL_YELLOW, "WIE SPIELT MAN?"),
            (0, ""),
            (COL_RED,    "Phase 1: Hecken fliegen ein!"),
            (COL_WHITE,  "  Pfeiltasten zum Ausweichen"),
            (COL_RED,    "  Beruehrung = -50 Punkte + Bremse"),
            (0, ""),
            (COL_LGREEN, "Phase 2: Labyrinth ist fertig"),
            (COL_WHITE,  "  Wege entlang navigieren"),
            (COL_GOLD,   "  Alle Schluessel einsammeln"),
            (COL_BROWN,  "  Dann zur Truhe gelangen"),
            (COL_RED,    "  Vor Ablauf der Zeit!"),
            (0, ""),
            (COL_LGREEN, "Level 1  Leicht  60 s  langsame Hecken"),
            (COL_YELLOW, "Level 2  Mittel  45 s  schnelle Hecken"),
            (COL_RED,    "Level 3  Schwer  30 s  sehr schnell"),
        ]
        for idx, (col, txt) in enumerate(lines):
            if txt and col:
                pyxel.text(24, iy+8+idx*8, txt, col)

        # Symbol-Leiste
        ly = 214
        pyxel.rect (16, ly, 224, 26, COL_DARK)
        pyxel.rectb(16, ly, 224, 26, COL_WHITE)
        self._draw_player_sprite(22, ly+5, DIR_DOWN)
        pyxel.text(40, ly+9, "Du", COL_WHITE)
        self._draw_key_sprite(78, ly+9)
        pyxel.text(88, ly+9, "Key", COL_GOLD)
        self._draw_chest_sprite(128, ly+5)
        pyxel.text(146, ly+9, "Truhe", COL_BROWN)
        if self.has_res:
            pyxel.blt(196, ly+5, 1, HEDGE_U, HEDGE_V, 16, 16, 0)
        else:
            pyxel.rect(197,ly+6,14,14,COL_GREEN); pyxel.rectb(196,ly+5,16,16,COL_DARK)
        pyxel.text(212, ly+9, "-50Pts", COL_RED)

        if (t//15)%2==0:
            hint = "ENTER oder Pfeiltaste zum Starten"
            pyxel.text((SCREEN_W-len(hint)*4)//2, SCREEN_H-7, hint, COL_LBLUE)
        if not self.has_res:
            pyxel.text(4, SCREEN_H-16, "! Keine .pyxres  Fallback aktiv", COL_RED)

    # ── Overlay-Dialoge ───────────────────────────────────────────────────────
    def _draw_win_overlay(self):
        pyxel.rect (20, 72, SCREEN_W-40, 112, COL_DARK)
        pyxel.rectb(20, 72, SCREEN_W-40, 112, COL_YELLOW)
        pyxel.rectb(22, 74, SCREEN_W-44, 108, COL_GOLD)
        title = "*** GEWONNEN! ***"
        pyxel.text((SCREEN_W-len(title)*4)//2, 80, title, COL_YELLOW)
        pyxel.text(30,  94, f"Schluessel: {self.total_keys}", COL_GOLD)
        secs = self.total_time//30
        pyxel.text(30, 104, f"abgelaufene Zeit:       {secs}s",           COL_WHITE)
        pyxel.text(30, 114, f"Score:      {self.score}",       COL_LGREEN)
        pyxel.text(30, 130, "ENTER / SPACE  Weiter",           COL_LBLUE)
        pyxel.text(30, 142, "Im Menue Level waehlen oder [Q]", COL_WHITE)

    def _draw_menu(self):
        for y in range(0, SCREEN_H, 2):
            pyxel.line(0, y, SCREEN_W-1, y, 0)
        title = "LABYRINTH QUEST"
        tx = (SCREEN_W-len(title)*4)//2
        pyxel.text(tx-1, 13, title, 0)
        pyxel.text(tx,   12, title, COL_YELLOW)

        y = 28
        if not self._first_run:
            won  = self.total_keys >= self.keys_needed
            if won:
                otxt = "GEWONNEN! Gut gemacht!";    ocol = COL_YELLOW
            elif self.time_up:
                otxt = "ZEIT IST ABGELAUFEN! Zu langsam!"; ocol = COL_RED
            else:
                otxt = "Leider getroffen... Nochmal?"; ocol = COL_RED
            pyxel.rect (28, y, SCREEN_W-56, 44, COL_DARK)
            pyxel.rectb(28, y, SCREEN_W-56, 44, COL_WHITE)
            pyxel.text(34, y+4,  otxt,                             ocol)
            pyxel.text(34, y+14, f"Schluessel: {self.total_keys}", COL_GOLD)
            hits = self.hit_penalty // 50
            pyxel.text(34, y+24, f"Treffer: {hits}x  (-{self.hit_penalty} Pts)", COL_RED)
            secs = self.total_time//30
            pyxel.text(34, y+34, f"Zeit {secs}s, Score: {self.score}", COL_WHITE)
            y += 58

        pyxel.rect (28, y, SCREEN_W-56, 82, COL_DARK)
        pyxel.rectb(28, y, SCREEN_W-56, 82, COL_WHITE)
        pyxel.text(34, y+4, "NEUE RUNDE:", COL_WHITE)
        for lv, cfg in LEVEL_CFG.items():
            col  = COL_YELLOW if lv==self.level else COL_WHITE
            mark = " <<" if lv==self.level else ""
            keys  = len([ch for row in LEVEL_MAPS[lv-1] for ch in row if ch=='K'])
            tlim  = cfg['time_limit']
            tmins, tsecs = divmod(tlim, 60)
            tstr  = f"{tmins}:{tsecs:02d}" if tmins else f"{tsecs}s"
            pyxel.text(34, y+16+(lv-1)*16,
                       f"[{lv}] Level {lv}  {cfg['label']}  {keys} Keys  {tstr}{mark}", col)
        pyxel.text(34, y+64, "[Q]  BEENDEN", COL_RED)

        if not self.has_res:
            pyxel.text(4, SCREEN_H-8, "! Fallback-Grafik aktiv", COL_RED)

    # ── Sprite-Zeichenfunktionen ──────────────────────────────────────────────
    def _draw_player_sprite(self, x, y, direction):
        if self.has_res:
            pyxel.blt(x, y, 0, PLAYER_U_BASE+direction*16, PLAYER_V, 16, 16, 0)
        else:
            self._draw_player_fallback(x, y, direction)

    def _draw_player_fallback(self, x, y, direction):
        if direction == DIR_GHOST:
            pyxel.circ(x+8, y+6, 5, COL_WHITE)
            pyxel.rect(x+3, y+6, 10, 7, COL_WHITE)
            for i in range(3): pyxel.rect(x+3+i*3, y+12, 2, 3, COL_DARK)
            pyxel.pset(x+6, y+5, COL_DARK); pyxel.pset(x+10, y+5, COL_DARK)
            return
        pyxel.rect(x+4, y+6, 8, 8, COL_RED)
        pyxel.rect(x+5, y+1, 6, 6, COL_SKIN)
        if   direction == DIR_DOWN:
            pyxel.pset(x+7,y+3,COL_DARK); pyxel.pset(x+9,y+3,COL_DARK)
        elif direction == DIR_UP:
            pyxel.rect(x+5,y+1,6,6,COL_BROWN)
        elif direction == DIR_LEFT:
            pyxel.pset(x+6,y+3,COL_DARK)
        elif direction == DIR_RIGHT:
            pyxel.pset(x+9,y+3,COL_DARK)
        pyxel.rect(x+4,y+13,3,3,COL_DARK); pyxel.rect(x+9,y+13,3,3,COL_DARK)

    def _draw_key_sprite(self, x, y):
        if self.has_res:
            pyxel.blt(x, y, 0, KEY_U, KEY_V, 8, 8, 0)
        else:
            pyxel.circ(x+2,y+2,2,COL_YELLOW); pyxel.rect(x+3,y+3,4,2,COL_YELLOW)
            pyxel.pset(x+5,y+5,COL_YELLOW);   pyxel.pset(x+7,y+5,COL_YELLOW)

    def _draw_chest_sprite(self, x, y):
        if self.has_res:
            pyxel.blt(x, y, 0, CHEST_U, CHEST_V, 16, 16, 0)
        else:
            pyxel.rect (x+1,y+4,14,10,COL_BROWN); pyxel.rect(x+1,y+1,14,5,4)
            pyxel.rectb(x+1,y+1,14,13,COL_DARK)
            pyxel.rect (x+6,y+7,4,4,COL_YELLOW)
            pyxel.pset(x+7,y+6,COL_YELLOW); pyxel.pset(x+8,y+6,COL_YELLOW)
            pyxel.line(x+1,y+6,x+14,y+6,COL_GOLD)

    def _draw_shield_sprite(self, x, y, cooldown):
        # Das Schild ist am Anfang durchgehend sichtbar. 
        # In der letzten Sekunde (< 30 Frames) fängt es an zu blinken.
        if cooldown < 30:
            show = (cooldown // 3) % 2 == 0
        else:
            show = True
            
        if show:
            if self.has_res:
                # Die 0 als letzter Parameter macht schwarzen Hintergrund transparent
                pyxel.blt(x, y, 0, SHIELD_U, SHIELD_V, 16, 16, 0)
            else:
                # Fallback ohne Grafikdatei: Ein hellblauer Doppelkreis
                pyxel.circb(x+8, y+8, 9, COL_LBLUE)
                pyxel.circb(x+8, y+8, 10, COL_LBLUE)

# ── Einstiegspunkt ────────────────────────────────────────────────────────────
if __name__ == "__main__":
    Game()