# Pyxel Studio

import pyxel
import random

SCREEN_W = 512
SCREEN_H = 512
TILE = 26
MAP_W = 16
MAP_H = 16

MAP_PIXEL_W = MAP_W * TILE
MAP_PIXEL_H = MAP_H * TILE

MAP_OX = (SCREEN_W - MAP_PIXEL_W) // 2
MAP_OY = 58

LOOP_TIME = 240
MAX_CLONES = 3

INVINCIBLE_TIME = 180
SHIELD_RESPAWN_LOOPS = 3

STATE_TITLE = 0
STATE_STORY = 1
STATE_GAME = 2
STATE_WIN = 3

LEVELS = [
    [
        "################",
        "#S......N......#",
        "#.######.#####.#",
        "#......#.....#.#",
        "#.####.#.###.#.#",
        "#....#.#...#.#.#",
        "#.##.#.###.#.#.#",
        "#..P.#.....#.#.#",
        "##L######N##.#.#",
        "#............#.#",
        "#.##########.#.#",
        "#..............#",
        "#.############.#",
        "#.............E#",
        "#..............#",
        "################",
    ],
    [
        "################",
        "#S....N..#....E#",
        "#.#####..#.###.#",
        "#.....#..#...#.#",
        "#####.#..###.#.#",
        "#...#.#......#.#",
        "#.P.#.######.#.#",
        "#.L.#..N...#.#.#",
        "#.L.######.#.#.#",
        "#.L......#.#.#.#",
        "#.L.##.#.#.#.#.#",
        "#...##.#.#.#...#",
        "#.####.#.#.###.#",
        "#..M...#....M..#",
        "#..............#",
        "################",
    ],
    [
        "################",
        "#S...........###",
        "#.######.###...#",
        "#......#...#..E#",
        "#.####.###.#.#.#",
        "#.#......#.#.#.#",
        "#.#.####.#.#.#.#",
        "#.#.P....#.#.#.#",
        "#.#L#####..#.#.#",
        "#.#L...N.....#.#",
        "#.#L######.###.#",
        "#...#......#...#",
        "#####.######.#.#",
        "#....BNM....#..#",
        "#..............#",
        "################",
    ],
    [
        "################",
        "#S....#.......E#",
        "#.##..#..###...#",
        "#..#.....B.#...#",
        "#..####.###.#..#",
        "#.....#...#.#..#",
        "#.###.....#.#..#",
        "#.#P#.....#.#..#",
        "#.#L###N..#.#..#",
        "#.#L........#..#",
        "#.#L######..#..#",
        "#.#..........#.#",
        "#.######.###.#.#",
        "#..M......#....#",
        "#..............#",
        "################",
    ],
    [
        "################",
        "#S.............#",
        "#.###########..#",
        "#.....#.....B..#",
        "#####.#.######.#",
        "#...#.#.#P...#.#",
        "#.#.#.#.#.##.#.#",
        "#.#.N.#.#L##.#.#",
        "#.#####.#L##.#.#",
        "#...N...#L...#.#",
        "#.#######L####.#",
        "#...M..........#",
        "#.###########..#",
        "#............E##",
        "#..............#",
        "################",
    ],
    [
        "################",
        "#S........#...E#",
        "#.######..#.##.#",
        "#.#....#..#...B#",
        "#.#.##.####.##.#",
        "#...##....#.##.#",
        "###.#####.#....#",
        "#...#P..#L####.#",
        "#.###N#.#L.....#",
        "#.....#.#L.###.#",
        "#.#####.#L.#...#",
        "#.#.M.#.#..#.###",
        "#.#...#.####...#",
        "#....M....N....#",
        "#..............#",
        "################",
    ],
    [
        "################",
        "#S.............#",
        "#.#####.######.#",
        "#.....#......#.#",
        "###.#.######.#.#",
        "#...#....P...#.#",
        "#.#####L####.#.#",
        "#.....#L...#.#.#",
        "#.###.#L#.#.#..#",
        "#.#...#.#.#.#..#",
        "#.#.###.#.#.#..#",
        "#.#.....#.#...##",
        "#.#######.###..#",
        "#...........M.E#",
        "#..............#",
        "################",
    ],
]

BIG_FONT = {
    "A": ["0110","1001","1111","1001","1001"],
    "B": ["1110","1001","1110","1001","1110"],
    "C": ["0111","1000","1000","1000","0111"],
    "D": ["1110","1001","1001","1001","1110"],
    "E": ["1111","1000","1110","1000","1111"],
    "F": ["1111","1000","1110","1000","1000"],
    "G": ["0111","1000","1011","1001","0111"],
    "H": ["1001","1001","1111","1001","1001"],
    "I": ["111","010","010","010","111"],
    "J": ["0011","0001","0001","1001","0110"],
    "K": ["1001","1010","1100","1010","1001"],
    "L": ["1000","1000","1000","1000","1111"],
    "M": ["10001","11011","10101","10001","10001"],
    "N": ["1001","1101","1011","1001","1001"],
    "O": ["0110","1001","1001","1001","0110"],
    "P": ["1110","1001","1110","1000","1000"],
    "Q": ["0110","1001","1001","1011","0111"],
    "R": ["1110","1001","1110","1010","1001"],
    "S": ["0111","1000","0110","0001","1110"],
    "T": ["11111","00100","00100","00100","00100"],
    "U": ["1001","1001","1001","1001","0110"],
    "V": ["10001","10001","01010","01010","00100"],
    "W": ["10001","10001","10101","11011","10001"],
    "X": ["1001","1001","0110","1001","1001"],
    "Y": ["1001","1001","0110","0010","0010"],
    "Z": ["1111","0001","0010","0100","1111"],
    "0": ["0110","1001","1001","1001","0110"],
    "1": ["010","110","010","010","111"],
    "2": ["1110","0001","0110","1000","1111"],
    "3": ["1110","0001","0110","0001","1110"],
    "4": ["1001","1001","1111","0001","0001"],
    "5": ["1111","1000","1110","0001","1110"],
    "6": ["0111","1000","1110","1001","0110"],
    "7": ["1111","0001","0010","0100","0100"],
    "8": ["0110","1001","0110","1001","0110"],
    "9": ["0110","1001","0111","0001","1110"],
    "-": ["000","000","111","000","000"],
    ":": ["0","1","0","1","0"],
    ".": ["0","0","0","1","0"],
    " ": ["0","0","0","0","0"],
}

class Actor:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Player(Actor):
    pass

class Enemy(Actor):
    def update(self, game):
        target_x = game.player.x
        target_y = game.player.y

        dx = (target_x > self.x) - (target_x < self.x)
        dy = (target_y > self.y) - (target_y < self.y)

        if abs(target_x - self.x) > abs(target_y - self.y):
            options = [(self.x + dx, self.y), (self.x, self.y + dy)]
        else:
            options = [(self.x, self.y + dy), (self.x + dx, self.y)]

        for nx, ny in options:
            if game.can_walk(nx, ny, ignore_enemy=True):
                self.x, self.y = nx, ny
                break

class Game:
    def __init__(self):
        pyxel.init(SCREEN_W, SCREEN_H, title="Echo Cave", display_scale=1)
        self.setup_audio()

        self.state = STATE_TITLE
        self.level_index = 0
        self.story_page = 0
        self.win_timer = 0
        self.confetti = []

        self.move_hold = 0
        self.move_delay = 8
        self.move_repeat = 3
        self.current_music = None

        self.hidden_walls_seen = set()
        self.squeeze = False

        self.invincible_timer = 0
        self.shield_spawn_pos = None
        self.shield_available_on_map = False
        self.shield_respawn_counter = 0

        self.load_level()
        self.play_music("title")
        pyxel.run(self.update, self.draw)

    def setup_audio(self):
        pyxel.sound(0).set("c3", "p", "4", "n", 18)
        pyxel.sound(1).set("e4", "p", "4", "n", 10)
        pyxel.sound(2).set("g4", "p", "4", "n", 10)
        pyxel.sound(3).set("a2f2", "n", "42", "f", 18)
        pyxel.sound(4).set("c3e3g3", "p", "333", "n", 18)
        pyxel.sound(5).set("d3", "p", "4", "n", 18)
        pyxel.sound(6).set("c2", "n", "4", "f", 16)
        pyxel.sound(7).set("c4e4g4", "p", "333", "n", 20)
        pyxel.sound(14).set("g3 c4", "p", "43", "n", 12)
        pyxel.sound(15).set("f3 d3", "p", "43", "n", 14)
        pyxel.sound(16).set("d3 a3", "p", "43", "n", 14)

        pyxel.sound(8).set(
            "c4 e4 g4 e4 d4 f4 a4 f4 e4 g4 b4 g4 d4 f4 a4 f4",
            "tttttttttttttttt",
            "2222222222222222",
            "nnnnnnnnnnnnnnnn",
            30
        )
        pyxel.sound(9).set(
            "c2 r c2 r a1 r a1 r f1 r f1 r g1 r g1 r",
            "ssssssssssssssss",
            "4444444444444444",
            "nnnnnnnnnnnnnnnn",
            42
        )
        pyxel.sound(10).set(
            "e4 g4 a4 g4 e4 g4 b4 g4 d4 f4 a4 f4 d4 f4 g4 f4",
            "tttttttttttttttt",
            "2222222222222222",
            "nnnnnnnnnnnnnnnn",
            32
        )
        pyxel.sound(11).set(
            "a2 e3 a2 e3 f2 c3 f2 c3 d2 a2 d3 a2 e2 b2 e3 b2",
            "ssssssssssssssss",
            "2222222222222222",
            "nnnnnnnnnnnnnnnn",
            38
        )
        pyxel.sound(12).set(
            "r e4 r g4 r e4 r d4 r f4 r d4 r e4 r g4",
            "pppppppppppppppp",
            "1111111111111111",
            "nnnnnnnnnnnnnnnn",
            18
        )
        pyxel.sound(13).set(
            "c4 e4 g4 c4 b4 g4 e4 c4",
            "pppppppp",
            "22222222",
            "nnnnnnnn",
            26
        )

        pyxel.music(0).set([8], [9], [], [])
        pyxel.music(1).set([10], [11], [12], [])
        pyxel.music(2).set([13], [], [], [])

    def play_music(self, name):
        if self.current_music == name:
            return
        self.current_music = name
        pyxel.stop()

        if name == "title":
            pyxel.playm(0, loop=True)
        elif name == "game":
            pyxel.playm(1, loop=True)
        elif name == "win":
            pyxel.playm(2, loop=True)

    def load_level(self):
        self.level = LEVELS[self.level_index][:]
        self.player = None
        self.enemies = []
        self.clones = []
        self.history = []
        self.time = 0
        self.flash_timer = 0
        self.message = ""
        self.message_timer = 0
        self.hidden_walls_seen = set()
        self.squeeze = False

        self.invincible_timer = 0
        self.shield_spawn_pos = None
        self.shield_available_on_map = False
        self.shield_respawn_counter = 0

        for y, row in enumerate(self.level):
            for x, ch in enumerate(row):
                if ch == "S":
                    self.player = Player(x, y)
                elif ch == "M":
                    self.enemies.append(Enemy(x, y))
                elif ch == "B":
                    self.shield_spawn_pos = (x, y)
                    self.shield_available_on_map = True

        if self.level_index == 0:
            self.set_message("P=SCHALTER B=SCHILD N=ENGPASS Q=QUETSCHEN", 150)
        elif self.level_index == len(LEVELS) - 1:
            self.set_message("FINALER SEKTOR: UNSICHTBARES LABYRINTH", 120)
        else:
            self.set_message(f"SEKTOR {self.level_index + 1}: ZEITRISS AKTIV", 100)

    def set_message(self, text, duration=60):
        self.message = text
        self.message_timer = duration

    def current_tile(self, x, y):
        if x < 0 or y < 0 or x >= MAP_W or y >= MAP_H:
            return "#"
        return self.level[y][x]

    def set_tile(self, x, y, value):
        row = list(self.level[y])
        row[x] = value
        self.level[y] = "".join(row)

    def has_switch(self):
        return any("P" in row for row in self.level)

    def is_switch_active(self):
        if self.current_tile(self.player.x, self.player.y) == "P":
            return True
        for clone in self.clones:
            if clone:
                i = min(self.time, len(clone) - 1)
                cx, cy = clone[i]
                if self.current_tile(cx, cy) == "P":
                    return True
        return False

    def laser_is_deadly(self, x, y):
        if self.invincible_timer > 0:
            return False
        return self.current_tile(x, y) == "L" and not self.is_switch_active()

    def enemy_hits_player(self):
        if self.invincible_timer > 0:
            return False
        for enemy in self.enemies:
            if enemy.x == self.player.x and enemy.y == self.player.y:
                return True
        return False

    def start_invincibility(self):
        self.invincible_timer = INVINCIBLE_TIME
        self.shield_respawn_counter = SHIELD_RESPAWN_LOOPS
        pyxel.play(3, 14)
        self.set_message("UNSTERBLICH FUR KURZE ZEIT", 60)

    def handle_shield_respawn(self):
        if self.shield_spawn_pos is None:
            return
        if self.shield_available_on_map:
            return
        if self.invincible_timer > 0:
            return
        if self.shield_respawn_counter > 0:
            return

        x, y = self.shield_spawn_pos
        if self.current_tile(x, y) == ".":
            self.set_tile(x, y, "B")
            self.shield_available_on_map = True
            self.set_message("SCHILD ERSCHEINT WIEDER", 60)

    def resolve_player_danger(self):
        laser_hit = self.laser_is_deadly(self.player.x, self.player.y)
        enemy_hit = self.enemy_hits_player()

        if not laser_hit and not enemy_hit:
            return False

        self.reset_level()
        return True

    def can_walk(self, x, y, ignore_enemy=False):
        if x < 0 or y < 0 or x >= MAP_W or y >= MAP_H:
            return False

        tile = self.current_tile(x, y)

        if tile == "#":
            if self.level_index == len(LEVELS) - 1:
                self.hidden_walls_seen.add((x, y))
            return False

        if tile == "N" and not self.squeeze:
            return False

        if tile == "E" and self.has_switch() and not self.is_switch_active():
            return False

        if not ignore_enemy and self.invincible_timer <= 0:
            for enemy in self.enemies:
                if enemy.x == x and enemy.y == y:
                    return False

        return True

    def reset_level(self):
        pyxel.play(3, 3)
        self.flash_timer = 10
        self.load_level()

    def next_level(self):
        pyxel.play(3, 4)
        self.level_index += 1
        if self.level_index >= len(LEVELS):
            self.state = STATE_WIN
            self.win_timer = 0
            self.confetti = []
            self.play_music("win")
            pyxel.play(3, 7)
        else:
            self.load_level()

    def spawn_loop(self):
        if self.history:
            self.clones.append(self.history.copy())
            if len(self.clones) > MAX_CLONES:
                self.clones.pop(0)

        pyxel.play(3, 5)

        for y, row in enumerate(self.level):
            for x, ch in enumerate(row):
                if ch == "S":
                    self.player.x = x
                    self.player.y = y

        self.history = []
        self.time = 0
        self.squeeze = False

        if self.shield_respawn_counter > 0 and self.invincible_timer <= 0:
            self.shield_respawn_counter -= 1
            if self.shield_respawn_counter <= 0:
                self.handle_shield_respawn()

        if self.invincible_timer <= 0:
            self.set_message("ZEITSCHLEIFE NEU GESTARTET", 70)

    def get_held_move(self):
        keys = [
            (pyxel.KEY_LEFT, -1, 0),
            (pyxel.KEY_RIGHT, 1, 0),
            (pyxel.KEY_UP, 0, -1),
            (pyxel.KEY_DOWN, 0, 1),
        ]

        for key, dx, dy in keys:
            if pyxel.btnp(key):
                self.move_hold = 0
                return dx, dy

        for key, dx, dy in keys:
            if pyxel.btn(key):
                self.move_hold += 1
                if self.move_hold >= self.move_delay:
                    if (self.move_hold - self.move_delay) % self.move_repeat == 0:
                        return dx, dy
                return 0, 0

        self.move_hold = 0
        return 0, 0

    def toggle_squeeze(self):
        self.squeeze = not self.squeeze
        pyxel.play(3, 16)
        if self.squeeze:
            self.set_message("QUETSCHMODUS AKTIV", 45)
        else:
            self.set_message("QUETSCHMODUS AUS", 45)

    def update_player(self):
        if pyxel.btnp(pyxel.KEY_Q):
            self.toggle_squeeze()

        move_x, move_y = self.get_held_move()

        if move_x != 0 or move_y != 0:
            nx = self.player.x + move_x
            ny = self.player.y + move_y

            if self.can_walk(nx, ny, ignore_enemy=True):
                self.player.x = nx
                self.player.y = ny
                pyxel.play(2, 1)

                tile = self.current_tile(self.player.x, self.player.y)

                if tile == "B":
                    self.start_invincibility()
                    self.set_tile(self.player.x, self.player.y, ".")
                    self.shield_available_on_map = False

                if tile == "P":
                    pyxel.play(1, 2)
                    self.set_message("SCHALTER AKTIVIERT", 50)

                if self.resolve_player_danger():
                    return
            else:
                pyxel.play(1, 6)

    def update_enemies(self):
        for enemy in self.enemies:
            enemy.update(self)

        if self.resolve_player_danger():
            return

    def update(self):
        if self.state == STATE_TITLE:
            self.play_music("title")
            if pyxel.btnp(pyxel.KEY_RETURN):
                pyxel.play(0, 0)
                self.state = STATE_STORY
            return

        if self.state == STATE_STORY:
            self.play_music("title")
            if pyxel.btnp(pyxel.KEY_RETURN):
                self.story_page += 1
                if self.story_page >= 3:
                    self.state = STATE_GAME
                    self.play_music("game")
            return

        if self.state == STATE_WIN:
            self.play_music("win")
            self.win_timer += 1

            if self.win_timer % 4 == 0:
                self.confetti.append([
                    random.randint(0, SCREEN_W - 4),
                    -10,
                    random.randint(1, 15),
                    random.randint(1, 3)
                ])

            for c in self.confetti:
                c[1] += c[3]

            if pyxel.btnp(pyxel.KEY_R):
                self.level_index = 0
                self.story_page = 0
                self.state = STATE_TITLE
                self.confetti = []
                self.load_level()
                self.play_music("title")
            return

        self.play_music("game")

        self.time += 1

        if self.invincible_timer > 0:
            self.invincible_timer -= 1
            if self.invincible_timer == 0:
                self.set_message("UNSTERBLICHKEIT ENDET", 50)

        self.update_player()

        if self.state != STATE_GAME:
            return

        if self.resolve_player_danger():
            return

        self.history.append((self.player.x, self.player.y))

        if self.current_tile(self.player.x, self.player.y) == "E":
            self.next_level()
            return

        if self.time % 12 == 0:
            self.update_enemies()

        if self.state != STATE_GAME:
            return

        if self.resolve_player_danger():
            return

        if self.time > LOOP_TIME:
            self.spawn_loop()

        self.handle_shield_respawn()

        if self.message_timer > 0:
            self.message_timer -= 1

        if self.flash_timer > 0:
            self.flash_timer -= 1

    def draw_background(self):
        pyxel.cls(0)
        for y in range(0, SCREEN_H, 16):
            col = 1 if (y // 16) % 2 == 0 else 0
            pyxel.rect(0, y, SCREEN_W, 16, col)

        for i in range(36):
            x = (i * 37 + pyxel.frame_count // 2) % SCREEN_W
            y = (i * 53) % SCREEN_H
            pyxel.pset(x, y, 1)

    def draw_player(self, px, py, col, ghost=False, trail=0):
        body = col
        suit = 1 if not ghost else 13
        face = 7 if not ghost else 12
        s = TILE / 32

        if ghost:
            pyxel.circb(int(px + 16 * s), int(py + 16 * s), int(14 * s), trail + 1)

        if self.invincible_timer > 0 and not ghost:
            shield_col = 11 if pyxel.frame_count % 8 < 4 else 12
            pyxel.circb(int(px + 16 * s), int(py + 16 * s), int(15 * s), shield_col)

        if self.squeeze and not ghost:
            pyxel.rect(int(px + 13 * s), int(py + 3 * s), int(6 * s), int(4 * s), 2)
            pyxel.rect(int(px + 13 * s), int(py + 7 * s), int(6 * s), int(8 * s), face)
            pyxel.pset(int(px + 14 * s), int(py + 10 * s), 0)
            pyxel.pset(int(px + 17 * s), int(py + 10 * s), 0)
            pyxel.line(int(px + 14 * s), int(py + 13 * s), int(px + 17 * s), int(py + 13 * s), 8)
            pyxel.rect(int(px + 12 * s), int(py + 15 * s), int(8 * s), int(10 * s), suit)
            pyxel.rect(int(px + 10 * s), int(py + 16 * s), int(2 * s), int(8 * s), body)
            pyxel.rect(int(px + 20 * s), int(py + 16 * s), int(2 * s), int(8 * s), body)
            pyxel.rect(int(px + 13 * s), int(py + 25 * s), int(2 * s), int(6 * s), body)
            pyxel.rect(int(px + 17 * s), int(py + 25 * s), int(2 * s), int(6 * s), body)
        else:
            pyxel.rect(int(px + 10 * s), int(py + 3 * s), int(12 * s), int(4 * s), 2)
            pyxel.rect(int(px + 9 * s), int(py + 7 * s), int(14 * s), int(8 * s), face)
            pyxel.pset(int(px + 13 * s), int(py + 10 * s), 0)
            pyxel.pset(int(px + 18 * s), int(py + 10 * s), 0)
            pyxel.line(int(px + 14 * s), int(py + 13 * s), int(px + 17 * s), int(py + 13 * s), 8)
            pyxel.rect(int(px + 8 * s), int(py + 15 * s), int(16 * s), int(10 * s), suit)
            pyxel.rect(int(px + 6 * s), int(py + 16 * s), int(3 * s), int(8 * s), body)
            pyxel.rect(int(px + 23 * s), int(py + 16 * s), int(3 * s), int(8 * s), body)
            pyxel.rect(int(px + 11 * s), int(py + 17 * s), int(3 * s), int(3 * s), 10)
            pyxel.rect(int(px + 18 * s), int(py + 17 * s), int(3 * s), int(3 * s), 10)

            if pyxel.frame_count % 16 < 8:
                pyxel.rect(int(px + 10 * s), int(py + 25 * s), int(4 * s), int(6 * s), body)
                pyxel.rect(int(px + 18 * s), int(py + 25 * s), int(4 * s), int(8 * s), body)
            else:
                pyxel.rect(int(px + 10 * s), int(py + 25 * s), int(4 * s), int(8 * s), body)
                pyxel.rect(int(px + 18 * s), int(py + 25 * s), int(4 * s), int(6 * s), body)

            pyxel.rect(int(px + 8 * s), int(py + 28 * s), int(6 * s), max(1, int(2 * s)), 5)
            pyxel.rect(int(px + 18 * s), int(py + 28 * s), int(6 * s), max(1, int(2 * s)), 5)

    def draw_enemy(self, px, py):
        s = TILE / 32
        blink = pyxel.frame_count % 20 < 10
        pyxel.rect(int(px + 6 * s), int(py + 6 * s), int(20 * s), int(18 * s), 8)
        pyxel.rect(int(px + 8 * s), int(py + 4 * s), int(4 * s), int(4 * s), 10)
        pyxel.rect(int(px + 20 * s), int(py + 4 * s), int(4 * s), int(4 * s), 10)
        pyxel.rect(int(px + 9 * s), int(py + 9 * s), int(4 * s), int(4 * s), 7)
        pyxel.rect(int(px + 19 * s), int(py + 9 * s), int(4 * s), int(4 * s), 7)
        pyxel.line(int(px + 10 * s), int(py + 20 * s), int(px + 22 * s), int(py + 20 * s), 7)
        if blink:
            pyxel.pset(int(px + 5 * s), int(py + 5 * s), 10)
            pyxel.pset(int(px + 26 * s), int(py + 5 * s), 10)

    def draw_map(self):
        switch_active = self.is_switch_active()
        last_level = self.level_index == len(LEVELS) - 1

        pyxel.rect(MAP_OX - 6, MAP_OY - 6, MAP_PIXEL_W + 12, MAP_PIXEL_H + 12, 1)
        pyxel.rectb(MAP_OX - 6, MAP_OY - 6, MAP_PIXEL_W + 12, MAP_PIXEL_H + 12, 6)

        for y, row in enumerate(self.level):
            for x, ch in enumerate(row):
                px = MAP_OX + x * TILE
                py = MAP_OY + y * TILE
                pyxel.rect(px, py, TILE, TILE, 0)

                if ch == "#":
                    if last_level and (x, y) not in self.hidden_walls_seen:
                        continue
                    pyxel.rect(px, py, TILE, TILE, 5)
                    pyxel.rect(px + 2, py + 2, TILE - 4, TILE - 4, 1)
                    pyxel.line(px + 2, py + 2, px + TILE - 3, py + 2, 6)
                    pyxel.line(px + 2, py + 2, px + 2, py + TILE - 3, 6)

                elif ch == "E":
                    pyxel.rect(px + 2, py + 2, TILE - 4, TILE - 4, 3)
                    inner = 11 if switch_active or not self.has_switch() else 2
                    pyxel.rect(px + 6, py + 6, TILE - 12, TILE - 12, inner)
                    pyxel.rectb(px + 1, py + 1, TILE - 2, TILE - 2, 7)

                elif ch == "L":
                    col = 6 if self.invincible_timer > 0 else (11 if switch_active else 8)
                    pyxel.rect(px + 3, py + 3, TILE - 6, TILE - 6, col)
                    pyxel.line(px + TILE // 2, py + 4, px + TILE // 2, py + TILE - 5, 7)
                    pyxel.line(px + 4, py + TILE // 2, px + TILE - 5, py + TILE // 2, 7)

                elif ch == "P":
                    pyxel.rect(px + 4, py + 4, TILE - 8, TILE - 8, 9)
                    pyxel.rect(px + 8, py + 8, TILE - 16, TILE - 16, 10)
                    pyxel.rectb(px + 2, py + 2, TILE - 4, TILE - 4, 7)

                elif ch == "B":
                    pyxel.rect(px + 4, py + 4, TILE - 8, TILE - 8, 12)
                    pyxel.rectb(px + 3, py + 3, TILE - 6, TILE - 6, 7)
                    pyxel.text(px + TILE // 2 - 2, py + TILE // 2 - 2, "B", 1)

                elif ch == "N":
                    blink = pyxel.frame_count % 24 < 12
                    col = 13 if blink else 5
                    pyxel.rect(px + TILE // 2 - 3, py + 2, 6, TILE - 4, col)
                    pyxel.line(px + TILE // 2 - 4, py + 2, px + TILE // 2 - 4, py + TILE - 3, 7)
                    pyxel.line(px + TILE // 2 + 3, py + 2, px + TILE // 2 + 3, py + TILE - 3, 7)

                else:
                    if (x + y) % 2 == 0:
                        pyxel.pset(px + TILE // 3, py + TILE // 3, 1)

        for i, clone in enumerate(self.clones):
            if clone:
                idx = min(self.time, len(clone) - 1)
                cx, cy = clone[idx]
                self.draw_player(MAP_OX + cx * TILE, MAP_OY + cy * TILE, 12, ghost=True, trail=i + 1)

        for enemy in self.enemies:
            self.draw_enemy(MAP_OX + enemy.x * TILE, MAP_OY + enemy.y * TILE)

        self.draw_player(MAP_OX + self.player.x * TILE, MAP_OY + self.player.y * TILE, 7, ghost=False)

    def draw_panel_text(self, x, y, lines, color=7, line_gap=18):
        for i, line in enumerate(lines):
            pyxel.text(x, y + i * line_gap, line, color)

    def draw_big_text(self, x, y, text, color=7, scale=3, spacing=1):
        cx = x
        for ch in text.upper():
            pattern = BIG_FONT.get(ch, BIG_FONT[" "])
            w = len(pattern[0])
            for ry, row in enumerate(pattern):
                for rx, bit in enumerate(row):
                    if bit == "1":
                        pyxel.rect(cx + rx * scale, y + ry * scale, scale, scale, color)
            cx += w * scale + spacing * scale

    def draw_hud(self):
        pyxel.rect(0, 0, SCREEN_W, 44, 0)
        pyxel.rectb(0, 0, SCREEN_W, 44, 5)

        pyxel.text(10, 10, f"LEVEL {self.level_index + 1}/{len(LEVELS)}", 7)
        pyxel.text(120, 10, f"LOOP {max(0, (LOOP_TIME - self.time) // 30)}", 6)
        pyxel.text(210, 10, f"ECHOS {len(self.clones)}/{MAX_CLONES}", 12)

        status = "SCHALTER AN" if self.is_switch_active() else "SCHALTER AUS"
        col = 11 if self.is_switch_active() else 8
        pyxel.text(340, 10, status, col)

        mode_text = "Q=SCHMAL" if self.squeeze else "Q=NORMAL"
        pyxel.text(10, 26, mode_text, 13 if self.squeeze else 7)
        pyxel.text(92, 26, "P=SCHALTER B=SCHILD N=ENGPASS", 6)

        if self.message_timer > 0:
            pyxel.rect(86, 24, 250, 14, 1)
            pyxel.rectb(86, 24, 250, 14, 7)
            pyxel.text(94, 28, self.message, 7)

    def draw_invincible_bar(self):
        if self.invincible_timer <= 0:
            return

        box_y = SCREEN_H - 26
        pyxel.rect(120, box_y, 272, 16, 1)
        pyxel.rectb(120, box_y, 272, 16, 7)
        pyxel.text(128, box_y + 5, "UNSTERBLICH", 11)
        pyxel.rect(210, box_y + 4, 172, 8, 0)
        pyxel.rectb(210, box_y + 4, 172, 8, 7)
        bar_w = int(168 * self.invincible_timer / INVINCIBLE_TIME)
        pyxel.rect(212, box_y + 6, bar_w, 4, 11)

    def draw_title(self):
        self.draw_background()

        pyxel.rect(34, 30, 444, 190, 1)
        pyxel.rectb(34, 30, 444, 190, 6)

        self.draw_big_text(74, 56, "ECHO CAVE", 7, scale=5, spacing=2)
        self.draw_big_text(88, 112, "ZEITSCHLEIFEN", 6, scale=3, spacing=1)
        self.draw_big_text(104, 142, "ECHOS UND ENGE", 12, scale=3, spacing=1)

        self.draw_player(240, 235, 7, ghost=False)
        self.draw_player(190, 245, 12, ghost=True, trail=2)
        self.draw_player(290, 245, 12, ghost=True, trail=3)

        pyxel.rect(70, 340, 372, 112, 1)
        pyxel.rectb(70, 340, 372, 112, 7)
        self.draw_panel_text(84, 360, [
            "ENTER = START",
            "PFEILE = LAUFEN",
            "Q = QUETSCHMODUS",
            "B = MACHT DICH KURZ UNSTERBLICH",
            "N = ENGER DURCHGANG",
        ], 7, 20)

    def draw_story(self):
        self.draw_background()

        pages = [
            [
                "DIE HOEHLE LEBT.",
                "P=SCHALTER B=SCHILD N=ENGPASS.",
                "DEINE FRUEHEREN LOOPS BLEIBEN ALS ECHOS."
            ],
            [
                "ECHOS HALTEN SCHALTER FUER DICH.",
                "DAS SCHILD MACHT DICH KURZ UNSTERBLICH.",
                "UNTEN SIEHST DU DIE RESTZEIT."
            ],
            [
                "DRUECKE Q FUER DEN QUETSCHMODUS.",
                "NUR SO PASST DU DURCH ENGE SPALTEN.",
                "DAS LETZTE LEVEL BLEIBT FAST UNSICHTBAR."
            ]
        ]

        pyxel.rect(36, 72, 440, 250, 1)
        pyxel.rectb(36, 72, 440, 250, 7)

        self.draw_big_text(170, 92, "BRIEFING", 10, scale=3, spacing=1)
        self.draw_panel_text(58, 160, pages[self.story_page], 7, 30)

        self.draw_player(110, 355, 12, ghost=True, trail=3)
        self.draw_player(185, 355, 12, ghost=True, trail=2)
        self.draw_player(260, 355, 7, ghost=False)

        pyxel.rect(120, 425, 270, 36, 1)
        pyxel.rectb(120, 425, 270, 36, 6)
        pyxel.text(172, 438, "ENTER ZUM WEITER", 7)

    def draw_win(self):
        self.draw_background()

        pyxel.rect(58, 104, 396, 150, 1)
        pyxel.rectb(58, 104, 396, 150, 11)

        self.draw_big_text(92, 130, "GESCHAFFT", 7, scale=4, spacing=2)
        self.draw_panel_text(108, 190, [
            "DU HAST DIE ECHO-HOEHLE",
            "MIT DEINEN EIGENEN SPUREN BESIEGT."
        ], 7, 24)

        for c in self.confetti:
            pyxel.rect(c[0], c[1], 3, 3, c[2])

        pyxel.rect(120, 420, 270, 36, 1)
        pyxel.rectb(120, 420, 270, 36, 10)
        pyxel.text(154, 433, "R DRUECKEN FUER NEUSTART", 7)

    def draw(self):
        if self.state == STATE_TITLE:
            self.draw_title()
            return

        if self.state == STATE_STORY:
            self.draw_story()
            return

        if self.state == STATE_WIN:
            self.draw_win()
            return

        self.draw_background()
        self.draw_hud()
        self.draw_map()
        self.draw_invincible_bar()

        if self.flash_timer > 0:
            pyxel.rect(0, 0, SCREEN_W, SCREEN_H, 7)

Game()