# =====================================================================
#  OMA GERTRUDE - DER GROSSE KEKS-RAUB  (Deluxe 4 - lebendige Fabrik)
#  Stealth-Spiel mit Pyxel (Python)  -  Aufloesung 512 x 512
#
#  Ablauf: 3 Fragmente sammeln + 2 Schalter aktivieren -> Laser-Wand faellt
#          -> Tresor mit RETURN oeffnen -> Keks klauen (Alarm!) -> Flucht.
#          Alle Bonus-Kekse sammeln = +1 Leben!
#
#  Steuerung: WASD/Pfeile laufen | Leertaste Turbo (laut!) |
#             RETURN Schalter/Tresor | SPACE Intro/Menue | Q beenden
#
#  Hinweis: pyxel.text() nutzt nur ASCII.
# =====================================================================

import pyxel
import math

SCREEN_W = 512
SCREEN_H = 512
WORLD_W = 2000
WORLD_H = 1200
TILE = 48
CELL = 16

REVEAL_R = 92
DARK_FACTOR = 0.55
GUARD_VIEW = 100
GUARD_HALF = math.radians(32)
CAM_VIEW = 112
CAM_HALF = math.radians(24)
HEAR_R = 120

SUS_MAX = 100.0
SUS_GAIN_GUARD = 2.0
SUS_GAIN_CAM = 2.7
SUS_GAIN_LASER = 22.0
SUS_DRAIN = 1.4
START_LIVES = 3
INTRO_PAGES = 8

NUM_FRAGS = 3
K = 2

BLACK, NAVY, PURP, TEAL = 0, 1, 2, 3
BROWN, DBLUE, LBLUE, WHITE = 4, 5, 6, 7
RED, ORANGE, YELLOW, GREEN = 8, 9, 10, 11
CYAN, INDIGO, PINK, PEACH = 12, 13, 14, 15


def clamp(v, lo, hi):
    return max(lo, min(hi, v))


def rects_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 point_in_rect(px, py, r):
    return r[0] <= px <= r[0] + r[2] and r[1] <= py <= r[1] + r[3]


def angle_diff(a, b):
    return abs(math.atan2(math.sin(a - b), math.cos(a - b)))


def cone_sees(ox, oy, facing, length, half, px, py):
    d = math.hypot(px - ox, py - oy)
    if d > length or d < 0.01:
        return False
    return angle_diff(math.atan2(py - oy, px - ox), facing) <= half


def los_blocked(x0, y0, x1, y1, walls):
    steps = int(math.hypot(x1 - x0, y1 - y0) // 6) + 2
    for i in range(1, steps):
        t = i / steps
        x = x0 + (x1 - x0) * t
        y = y0 + (y1 - y0) * t
        for w in walls:
            if w[0] <= x <= w[0] + w[2] and w[1] <= y <= w[1] + w[3]:
                return True
    return False


def seg_dist(px, py, x1, y1, x2, y2):
    vx, vy = x2 - x1, y2 - y1
    L2 = vx * vx + vy * vy
    if L2 == 0:
        return math.hypot(px - x1, py - y1)
    t = clamp(((px - x1) * vx + (py - y1) * vy) / L2, 0, 1)
    return math.hypot(px - (x1 + t * vx), py - (y1 + t * vy))


# =====================================================================
#  Sprites
# =====================================================================
# Oma-Sprite aus res.pyxres (Bild 0, bei 0,0, Groesse 32x48)
GMA_U, GMA_V, GMA_W, GMA_H = 0, 0, 48, 64
GMA_COLKEY = BROWN   # Braun (4) wird durchsichtig


def draw_grandma(cx, cy, facing=1, has_cookie=False, hidden=False, sit=False, k=K):
    scale = k / 2.0                 # grosse Darstellung (Original 48x64 bei k=2)
    if hidden:
        scale *= 0.7
        for c in range(16):         # alles auf Grau umfaerben -> "versteckt"
            pyxel.pal(c, INDIGO)
    w = -GMA_W if facing == -1 else GMA_W   # negative Breite = horizontal spiegeln
    pyxel.blt(cx - GMA_W / 2, cy - GMA_H / 2, 0,
              GMA_U, GMA_V, w, GMA_H, GMA_COLKEY, scale=scale)
    if hidden:
        pyxel.pal()                 # Palette zuruecksetzen
    if has_cookie and not hidden:   # geklaute Keksdose (verkleinert) tragen
        js = scale * 0.55
        jx = cx + facing * GMA_W * scale * 0.30
        pyxel.blt(jx - JAR_W / 2, cy - JAR_H / 2, 0,
                  JAR_U, JAR_V, JAR_W, JAR_H, JAR_COLKEY, scale=js)


def draw_guard(cx, cy, facing, alarm, spotting, k=K):
    pyxel.elli(cx - 7 * k, cy + 7 * k, 14 * k, 4 * k, NAVY)
    body = RED if (alarm and (pyxel.frame_count // 6) % 2 == 0) else DBLUE
    pyxel.rect(cx - 5 * k, cy - 5 * k, 10 * k, 12 * k, body)
    pyxel.rect(cx - 5 * k, cy + 2 * k, 10 * k, 2 * k, NAVY)
    pyxel.rect(cx - k, cy + 2 * k, 2 * k, 2 * k, YELLOW)
    pyxel.circ(cx + 3 * k, cy - k, k, YELLOW)
    pyxel.rect(cx - 4 * k, cy + 6 * k, 3 * k, 3 * k, NAVY)
    pyxel.rect(cx + k, cy + 6 * k, 3 * k, 3 * k, NAVY)
    pyxel.circ(cx, cy - 7 * k, 3 * k, PEACH)
    pyxel.rect(cx - 4 * k, cy - 11 * k, 8 * k, 4 * k, NAVY)
    pyxel.rect(cx - 4 * k, cy - 8 * k, 8 * k, 2 * k, BLACK)
    fx = cx + math.cos(facing) * 7 * k
    fy = cy + math.sin(facing) * 7 * k
    pyxel.rect(fx - k, fy - k, 3 * k, 3 * k, RED if spotting else YELLOW)


def draw_camera(x, y, facing, spotting, k=K):
    pyxel.rect(x - 5 * k, y - 4 * k, 10 * k, 6 * k, LBLUE)
    pyxel.rect(x - 7 * k, y - 6 * k, 14 * k, 3 * k, DBLUE)
    lx = x + math.cos(facing) * 6 * k
    ly = y + math.sin(facing) * 6 * k
    pyxel.circ(x, y, 4 * k, DBLUE)
    pyxel.circ(lx, ly, 2 * k, BLACK)
    pyxel.pset(x + 3 * k, y - 2 * k, RED if (pyxel.frame_count // 10) % 2 else BLACK)
    if spotting:
        pyxel.circb(x, y, 6 * k, RED)


JAR_U, JAR_V, JAR_W, JAR_H = 0, 80, 48, 56
JAR_COLKEY = PINK   # Pink (14) wird durchsichtig

# Extra-Sprites fuer die Sieg-Szene (Bild 0)
WOMA_U, WOMA_V, WOMA_W, WOMA_H = 48, 0, 40, 56    # Oma ohne Roller
RLR_U, RLR_V, RLR_W, RLR_H = 96, 0, 48, 32        # Roller ohne Oma
WIN_COLKEY = BROWN   # durchsichtige Farbe dieser Sprites


def draw_jar(cx, cy, taken, k=K):
    if taken:
        return                       # Dose ist geklaut -> nichts zeichnen
    scale = k / 3.0                  # etwas groesser; Intro/Sieg deutlicher sichtbar
    glow = YELLOW if (pyxel.frame_count // 6) % 2 else ORANGE
    pyxel.ellib(cx - JAR_W * scale / 2 - 3, cy - JAR_H * scale / 2 - 3,
                JAR_W * scale + 6, JAR_H * scale + 6, glow)
    pyxel.blt(cx - JAR_W / 2, cy - JAR_H / 2, 0,
              JAR_U, JAR_V, JAR_W, JAR_H, JAR_COLKEY, scale=scale)



def draw_frag(x, y):
    bob = math.sin(pyxel.frame_count / 8 + x) * 2
    yy = y + bob
    pyxel.circb(x, yy, 11, CYAN if (pyxel.frame_count // 6) % 2 else WHITE)
    pyxel.rect(x - 7, yy - 5, 12, 10, CYAN)
    pyxel.rect(x - 7, yy - 5, 12, 2, YELLOW)
    pyxel.tri(x + 5, yy - 5, x + 9, yy, x + 5, yy + 5, CYAN)
    pyxel.tri(x + 5, yy - 2, x + 8, yy + 1, x + 5, yy + 4, NAVY)
    pyxel.text(x - 8, yy - 15, "FRAG", CYAN)


def draw_cookie(x, y, k=K):
    bob = math.sin(pyxel.frame_count / 7 + x) * 1.5 * k
    pyxel.circ(x, y + bob, 4 * k, BROWN)
    pyxel.circb(x, y + bob, 4 * k, ORANGE)
    for dx, dy in ((-1, -1), (2, 0), (0, 2), (-2, 1)):
        pyxel.pset(x + dx * k, y + dy * k + bob, BLACK)


def draw_switch(x, y, on):
    pyxel.rect(x - 9, y - 12, 18, 24, DBLUE)
    pyxel.rectb(x - 9, y - 12, 18, 24, LBLUE)
    pyxel.rect(x - 4, y - 8, 8, 10, BLACK)
    if on:
        pyxel.rect(x - 2, y - 7, 4, 4, GREEN)
        pyxel.circ(x, y + 6, 2, GREEN)
    else:
        pyxel.rect(x - 2, y - 1, 4, 4, RED)
        pyxel.circ(x, y + 6, 2, RED if (pyxel.frame_count // 8) % 2 else BLACK)
    pyxel.text(x - 16, y - 21, "SICHERUNG", LBLUE)


def big_text(cx, y, s, col, scale=2):
    if not s:
        return
    w = len(s) * 4
    pyxel.rect(0, 0, w, 8, BLACK)
    pyxel.text(0, 0, s, col)
    pts = [(i, j) for j in range(8) for i in range(w) if pyxel.pget(i, j) == col]
    pyxel.rect(0, 0, w, 8, BLACK)
    x0 = cx - (w * scale) // 2
    for (i, j) in pts:
        pyxel.rect(x0 + i * scale, y + j * scale, scale, scale, col)


# =====================================================================
#  Player
# =====================================================================
class Player:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.w = 32
        self.h = 40
        self.base_speed = 2.0
        self.turbo_speed = 4.2
        self.vx = 0.0
        self.vy = 0.0
        self.turbo = 100.0
        self.turbo_max = 100.0
        self.turbo_on = False
        self.frags = 0
        self.has_cookie = False
        self.lives = START_LIVES
        self.score = 0
        self.hit_timer = 0
        self.facing = 1
        self.moving = False
        self.hidden = False

    @property
    def cx(self):
        return self.x + self.w / 2

    @property
    def cy(self):
        return self.y + self.h / 2

    def update(self, solids, ice):
        ix = iy = 0
        self.turbo_on = pyxel.btn(pyxel.KEY_SPACE) and self.turbo > 1
        speed = self.turbo_speed if self.turbo_on else self.base_speed
        if pyxel.btn(pyxel.KEY_LEFT) or pyxel.btn(pyxel.KEY_A):
            ix -= 1
            self.facing = -1
        if pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.KEY_D):
            ix += 1
            self.facing = 1
        if pyxel.btn(pyxel.KEY_UP) or pyxel.btn(pyxel.KEY_W):
            iy -= 1
        if pyxel.btn(pyxel.KEY_DOWN) or pyxel.btn(pyxel.KEY_S):
            iy += 1
        tvx, tvy = ix * speed, iy * speed
        if ice:
            self.vx += (tvx - self.vx) * 0.06
            self.vy += (tvy - self.vy) * 0.06
        else:
            self.vx, self.vy = tvx, tvy

        if self._move_axis(self.vx, 0, solids):
            self.vx = 0
        if self._move_axis(0, self.vy, solids):
            self.vy = 0
        self.x = clamp(self.x, 0, WORLD_W - self.w)
        self.y = clamp(self.y, 0, WORLD_H - self.h)

        self.moving = math.hypot(self.vx, self.vy) > 0.4
        if self.turbo_on and (ix or iy):
            self.turbo = max(0, self.turbo - 1.6)
        else:
            self.turbo = min(self.turbo_max, self.turbo + 0.5)
        if self.hit_timer > 0:
            self.hit_timer -= 1

    def _move_axis(self, dx, dy, solids):
        self.x += dx
        self.y += dy
        for w in solids:
            if rects_overlap(self.x, self.y, self.w, self.h, *w):
                self.x -= dx
                self.y -= dy
                return True
        return False

    def draw(self, cam_x, cam_y):
        if self.hit_timer > 0 and (pyxel.frame_count // 3) % 2 == 0:
            return
        scx = self.cx - cam_x
        scy = self.cy - cam_y
        bob = 0
        # --- Bewegungs-/Flow-Effekt waehrend des Fahrens ---
        if self.moving and not self.hidden:
            ang = math.atan2(self.vy, self.vx)
            ca, sa = math.cos(ang), math.sin(ang)
            perp = (-sa, ca)
            fast = self.turbo_on
            rows = (-16, -6, 4, 15) if fast else (-11, 0, 11)
            cnt = 4 if fast else 3
            for r in rows:
                ox, oy = perp[0] * r, perp[1] * r
                for i in range(cnt):
                    d = 12 + i * 8
                    ln = 9 - i * 2
                    bx = scx + ox - ca * d
                    by = scy + oy - sa * d
                    col = WHITE if i == 0 else (CYAN if fast else LBLUE)
                    pyxel.line(bx, by, bx - ca * ln, by - sa * ln, col)
            # kleine Staubwolken hinten unten
            if (pyxel.frame_count // 3) % 2 == 0:
                pyxel.circb(scx - ca * 16, scy + 15, 3, LBLUE)
                if fast:
                    pyxel.circb(scx - ca * 24, scy + 13, 2, WHITE)
            # leichtes Auf-und-Ab-Wippen ("Flow")
            bob = math.sin(pyxel.frame_count * (0.6 if fast else 0.4)) * (3 if fast else 2)
        draw_grandma(scx, scy + bob,
                     facing=self.facing, has_cookie=self.has_cookie,
                     hidden=self.hidden)


# =====================================================================
#  Guard
# =====================================================================
class Guard:
    def __init__(self, path, speed=2.0):
        self.path = path
        self.i = 0
        self.x, self.y = path[0]
        self.speed = speed
        self.facing = 0.0
        self.alert_timer = 0
        self.alert_pos = (0, 0)

    @property
    def cx(self):
        return self.x

    @property
    def cy(self):
        return self.y

    def hear(self, px, py):
        self.alert_timer = 90
        self.alert_pos = (px, py)

    def update(self, alarm):
        sp = self.speed * (1.8 if alarm else 1.0)
        if self.alert_timer > 0:
            self.alert_timer -= 1
            tx, ty = self.alert_pos
        else:
            tx, ty = self.path[self.i]
        dx, dy = tx - self.x, ty - self.y
        d = math.hypot(dx, dy)
        if d > 2:
            self.facing = math.atan2(dy, dx)
            self.x += dx / d * sp
            self.y += dy / d * sp
        elif self.alert_timer == 0:
            self.i = (self.i + 1) % len(self.path)

    def sees(self, px, py, walls, alarm):
        length = GUARD_VIEW * (1.25 if alarm else 1.0)
        if cone_sees(self.x, self.y, self.facing, length, GUARD_HALF, px, py):
            return not los_blocked(self.x, self.y, px, py, walls)
        return False

    def draw(self, cam_x, cam_y, alarm, spotting):
        sx, sy = self.x - cam_x, self.y - cam_y
        length = GUARD_VIEW * (1.25 if alarm else 1.0)
        a1, a2 = self.facing - GUARD_HALF, self.facing + GUARD_HALF
        col = RED if (alarm or spotting) else YELLOW
        pyxel.tri(sx, sy, sx + math.cos(a1) * length, sy + math.sin(a1) * length,
                  sx + math.cos(a2) * length, sy + math.sin(a2) * length, col)
        draw_guard(sx, sy, self.facing, alarm, spotting)


# =====================================================================
#  SecurityCam
# =====================================================================
class SecurityCam:
    def __init__(self, x, y, base, sweep, period):
        self.x = x
        self.y = y
        self.base = base
        self.sweep = sweep
        self.period = period

    def facing(self, alarm):
        spd = 2.0 if alarm else 1.0
        return self.base + math.sin(pyxel.frame_count / self.period * spd) * self.sweep

    def sees(self, px, py, walls, alarm):
        length = CAM_VIEW * (1.2 if alarm else 1.0)
        if cone_sees(self.x, self.y, self.facing(alarm), length, CAM_HALF, px, py):
            return not los_blocked(self.x, self.y, px, py, walls)
        return False

    def draw(self, cam_x, cam_y, alarm, spotting):
        sx, sy = self.x - cam_x, self.y - cam_y
        f = self.facing(alarm)
        length = CAM_VIEW * (1.2 if alarm else 1.0)
        a1, a2 = f - CAM_HALF, f + CAM_HALF
        col = RED if (alarm or spotting) else CYAN
        pyxel.tri(sx, sy, sx + math.cos(a1) * length, sy + math.sin(a1) * length,
                  sx + math.cos(a2) * length, sy + math.sin(a2) * length, col)
        draw_camera(sx, sy, f, spotting)


# =====================================================================
#  LaserGate
# =====================================================================
class LaserGate:
    def __init__(self, x1, y1, x2, y2, on, off, phase=0):
        self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
        self.on = on
        self.off = off
        self.phase = phase

    def is_on(self, alarm):
        t = (pyxel.frame_count + self.phase) % (self.on + self.off)
        return t < self.on * (1.4 if alarm else 1.0)

    def hits(self, px, py):
        return seg_dist(px, py, self.x1, self.y1, self.x2, self.y2) < 16

    def draw(self, cam_x, cam_y, alarm):
        x1, y1 = self.x1 - cam_x, self.y1 - cam_y
        x2, y2 = self.x2 - cam_x, self.y2 - cam_y
        for (ex, ey) in ((x1, y1), (x2, y2)):
            pyxel.rect(ex - 4, ey - 4, 8, 8, LBLUE)
            pyxel.rectb(ex - 4, ey - 4, 8, 8, DBLUE)
        if self.is_on(alarm):
            pyxel.line(x1, y1, x2, y2, RED)
            if (pyxel.frame_count // 3) % 2 == 0:
                pyxel.line(x1, y1 + 1, x2, y2 + 1, ORANGE)
                pyxel.line(x1, y1 - 1, x2, y2 - 1, ORANGE)
        else:
            steps = int(math.hypot(x2 - x1, y2 - y1) // 14) + 1
            for i in range(steps):
                t = i / steps
                pyxel.pset(x1 + (x2 - x1) * t, y1 + (y2 - y1) * t, DBLUE)


# =====================================================================
#  Switch / Treasure / Pickup / Door
# =====================================================================
class Switch:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.on = False

    def rect(self):
        return (self.x - 10, self.y - 14, 20, 28)

    def draw(self, cam_x, cam_y):
        draw_switch(self.x - cam_x, self.y - cam_y, self.on)


class Treasure:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.taken = False

    def draw(self, cam_x, cam_y):
        draw_jar(self.x - cam_x, self.y - cam_y, self.taken)


class Pickup:
    def __init__(self, x, y, kind):
        self.x = x
        self.y = y
        self.kind = kind
        self.taken = False

    def draw(self, cam_x, cam_y):
        if self.taken:
            return
        if self.kind == "frag":
            draw_frag(self.x - cam_x, self.y - cam_y)
        else:
            draw_cookie(self.x - cam_x, self.y - cam_y)


class Door:
    def __init__(self, x, y, w, h):
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.locked = True

    def rect(self):
        return (self.x, self.y, self.w, self.h)

    def draw(self, cam_x, cam_y):
        sx, sy = self.x - cam_x, self.y - cam_y
        if self.locked:
            pyxel.rect(sx, sy, self.w, self.h, DBLUE)
            for yy in range(int(sy), int(sy + self.h), 12):
                pyxel.rect(sx, yy, self.w, 6, YELLOW)
                pyxel.rect(sx, yy + 6, self.w, 6, BLACK)
            pyxel.rectb(sx, sy, self.w, self.h, LBLUE)
            pyxel.circ(sx + self.w / 2, sy + self.h / 2, 6, RED)
            pyxel.text(sx - 8, sy - 10, "TRESOR", LBLUE)
        else:
            pyxel.rect(sx, sy, self.w, self.h, NAVY)
            pyxel.circ(sx + self.w / 2, sy + self.h / 2, 6, GREEN)


# =====================================================================
#  Game
# =====================================================================
class Game:
    def __init__(self):
        pyxel.init(SCREEN_W, SCREEN_H,
                   title="Oma Gertrude - Der grosse Keks-Raub", fps=60)
        pyxel.load("res.pyxres", exclude_sounds=True, exclude_musics=True)
        self._init_sounds()
        self.state = "title"
        self.win_t = 0
        self.reset()
        pyxel.run(self.update, self.draw)

    def _init_sounds(self):
        pyxel.sounds[0].set("f1", "t", "1", "n", 22)
        pyxel.sounds[1].set("c2g2c3", "s", "3", "f", 7)
        pyxel.sounds[2].set("a3e3", "s", "5", "n", 16)
        pyxel.sounds[3].set("c3e3g3c4e4", "p", "6", "n", 6)
        pyxel.sounds[4].set("c3e3g3c4e4g4c4", "s", "6", "n", 11)
        pyxel.sounds[5].set("g2d2g1", "n", "6", "f", 14)
        pyxel.sounds[6].set("e3a3c4", "p", "5", "n", 7)
        pyxel.sounds[7].set("c2g2c3g3", "s", "5", "s", 9)
        pyxel.sounds[8].set("g3c4", "p", "4", "n", 7)
        pyxel.sounds[9].set("c4g3c3", "n", "5", "f", 9)
        pyxel.sounds[10].set("c3e3g3c4g3e3c3e3f3a3c4a3g3b3d4g3",
                             "p", "4", "n", 16)
        pyxel.sounds[11].set("c2rg2re2rg2a2re2rc2rg2",
                             "t", "3", "n", 14)
        pyxel.sounds[12].set("c3e3g3c4e4g4e4c4g3e3c3e3g3c4e4g4",
                             "s", "5", "n", 18)

    def reset(self):
        b = 24
        self.walls = [
            (0, 0, WORLD_W, b), (0, WORLD_H - b, WORLD_W, b),
            (0, 0, b, WORLD_H), (WORLD_W - b, 0, b, WORLD_H),
            (300, b, 24, 900 - b), (300, 1080, 24, WORLD_H - b - 1080),
            (620, b, 24, 120 - b), (620, 300, 24, WORLD_H - b - 300),
            (940, b, 24, 900 - b), (940, 1080, 24, WORLD_H - b - 1080),
            (1300, b, 24, 140 - b), (1300, 320, 24, WORLD_H - b - 320),
            (1660, b, 24, 520 - b), (1660, 720, 24, WORLD_H - b - 720),
            (120, 160, 60, 200), (120, 640, 60, 220),
            (400, 140, 110, 90), (360, 560, 140, 80),
            (700, 200, 90, 170), (820, 560, 90, 200),
            (1000, 200, 120, 70), (1180, 640, 90, 200), (1080, 420, 40, 160),
            (1360, 160, 50, 200), (1360, 520, 50, 260), (1540, 200, 50, 300),
            (1780, 500, 40, 40), (1880, 800, 80, 90),
        ]
        self.machines = [(400, 140, 110, 90), (700, 200, 90, 170),
                         (820, 560, 90, 200), (1080, 420, 40, 160)]
        self.servers = [(1360, 160, 50, 200), (1360, 520, 50, 260),
                        (1540, 200, 50, 300)]
        self.belts = [(360, 430, 240, 30), (680, 660, 220, 28)]
        self.dark_zone = (644, 40, 296, WORLD_H - 80)
        self.ice_zone = (964, 40, 336, WORLD_H - 80)
        self.server_zone = (1324, 40, 336, WORLD_H - 80)
        # Deckenlampen (Raster) und Wand-Bedienpanele
        self.lamps = [(x, y) for x in range(150, WORLD_W - 80, 250)
                      for y in range(140, WORLD_H - 80, 300)]
        self.panels = [(312, 360), (312, 760), (632, 520), (956, 360),
                       (956, 760), (1312, 260), (1672, 320)]
        self.laser_wall = (1300, 140, 24, 180)
        self.wall_powered = True
        self.cams = [
            SecurityCam(420, b + 14, math.pi / 2, 0.6, 50),
            SecurityCam(780, b + 14, math.pi / 2, 0.7, 42),
            SecurityCam(1100, b + 14, math.pi / 2, 0.6, 56),
            SecurityCam(1400, b + 14, math.pi / 2, 0.8, 36),
            SecurityCam(1560, b + 14, math.pi / 2, 0.8, 30),
            SecurityCam(1840, b + 14, math.pi / 2, 0.65, 48),
        ]
        self.lasers = [
            LaserGate(470, 150, 470, 360, 80, 70, 0),
            LaserGate(660, 420, 900, 420, 90, 60, 30),
            LaserGate(1140, 210, 1140, 560, 70, 80, 15),
            LaserGate(1340, 520, 1620, 520, 80, 70, 45),
            LaserGate(1480, 200, 1480, 500, 70, 70, 20),
        ]
        self.switches = [Switch(560, 240), Switch(700, 820)]
        self.hides = [(200, 860, 80, 70), (520, 150, 80, 60),
                      (760, 860, 90, 70), (1020, 820, 90, 70),
                      (1560, 860, 90, 70)]
        self.door = Door(1660, 520, 24, 200)
        self.exit_zone = (b, 540, 60, 160)
        self.player = Player(self.exit_zone[0] + 30, 600)
        self.treasure = Treasure(1840, 600)
        self.frags = [Pickup(540, 330, "frag"), Pickup(760, 800, "frag"),
                      Pickup(1240, 300, "frag")]
        self.cookies = [Pickup(260, 200, "cookie"), Pickup(470, 540, "cookie"),
                        Pickup(880, 300, "cookie"), Pickup(1240, 560, "cookie"),
                        Pickup(1600, 820, "cookie"), Pickup(1900, 300, "cookie")]
        self.guards = [
            Guard([(160, 250), (160, 860)], 2.0),
            Guard([(360, 300), (580, 300), (580, 720), (360, 720)], 2.2),
            Guard([(360, 470), (580, 470)], 2.0),
            Guard([(660, 300), (900, 300)], 2.3),
            Guard([(680, 820), (900, 820)], 2.1),
            Guard([(980, 300), (1260, 300), (1260, 820), (980, 820)], 2.4),
            Guard([(1340, 300), (1620, 300)], 2.6),
            Guard([(1340, 860), (1620, 860)], 2.3),
            Guard([(1700, 560), (1940, 560)], 2.4),
        ]
        self.alarm = False
        self.suspicion = 0.0
        self.spotting = set()
        self.laser_hit = False
        self.cam_x = self.cam_y = 0
        self.message = ""
        self.message_t = 0
        self.intro_page = 0
        self.prompt = None
        self.music = None

    def flash(self, text, frames=60):
        self.message = text
        self.message_t = frames

    def solids(self):
        s = list(self.walls)
        if self.door.locked:
            s.append(self.door.rect())
        if self.wall_powered:
            s.append(self.laser_wall)
        return s

    def near(self, r, m):
        return rects_overlap(self.player.x, self.player.y, self.player.w,
                             self.player.h, r[0] - m, r[1] - m,
                             r[2] + 2 * m, r[3] + 2 * m)

    def update_camera(self):
        self.cam_x = clamp(self.player.cx - SCREEN_W / 2, 0, WORLD_W - SCREEN_W)
        self.cam_y = clamp(self.player.cy - SCREEN_H / 2, 0, WORLD_H - SCREEN_H)

    # ----------------------------------------------------------------
    def set_music(self, kind):
        if self.music == kind:
            return
        self.music = kind
        pyxel.stop(3)
        if kind == "intro":
            pyxel.play(3, 10, loop=True)
        elif kind == "play":
            pyxel.play(3, 11, loop=True)
        elif kind == "win":
            pyxel.play(3, 12, loop=True)

    def update(self):
        if pyxel.btn(pyxel.KEY_Q):
            pyxel.quit()
        if self.state in ("title", "intro"):
            self.set_music("intro")
        elif self.state == "play":
            self.set_music(None if self.alarm else "play")
        elif self.state == "win":
            self.set_music("win")
        else:
            self.set_music(None)
        if self.state == "title":
            if pyxel.btnp(pyxel.KEY_SPACE):
                self.state = "intro"
                self.intro_page = 0
        elif self.state == "intro":
            if pyxel.btnp(pyxel.KEY_SPACE):
                self.intro_page += 1
                if self.intro_page >= INTRO_PAGES:
                    self.state = "play"
        elif self.state == "play":
            self.update_play()
        elif self.state in ("win", "gameover"):
            if self.state == "win":
                self.win_t += 1
            if pyxel.btnp(pyxel.KEY_SPACE):
                self.reset()
                self.state = "title"

    def update_play(self):
        p = self.player
        self.wall_powered = not all(sw.on for sw in self.switches)
        ice = point_in_rect(p.cx, p.cy, self.ice_zone)
        p.update(self.solids(), ice)
        p.hidden = any(point_in_rect(p.cx, p.cy, h) for h in self.hides)

        if p.turbo_on and p.moving:
            if pyxel.frame_count % 16 == 0:
                pyxel.play(1, 1)
            for g in self.guards:
                if math.hypot(g.cx - p.cx, g.cy - p.cy) < HEAR_R:
                    g.hear(p.cx, p.cy)

        for g in self.guards:
            g.update(self.alarm)
        self.update_camera()

        for fr in self.frags:
            if not fr.taken and math.hypot(fr.x - p.cx, fr.y - p.cy) < 24:
                fr.taken = True
                p.frags += 1
                pyxel.play(0, 6)
                self.flash("Fragment " + str(p.frags) + "/" + str(NUM_FRAGS) + "!", 70)

        for c in self.cookies:
            if not c.taken and math.hypot(c.x - p.cx, c.y - p.cy) < 20:
                c.taken = True
                p.score += 100
                pyxel.play(0, 8)
                if all(cc.taken for cc in self.cookies):
                    p.lives = min(6, p.lives + 1)
                    p.score += 300
                    pyxel.play(0, 4)
                    self.flash("ALLE KEKSE! +1 LEBEN!", 110)

        self.prompt = None
        for sw in self.switches:
            if self.near(sw.rect(), 22):
                if not sw.on:
                    self.prompt = "RETURN = Sicherung umlegen"
                    if pyxel.btnp(pyxel.KEY_RETURN):
                        sw.on = True
                        pyxel.play(0, 7)
                        n = sum(s.on for s in self.switches)
                        if n >= len(self.switches):
                            self.flash("Strom aus! Laser-Wand gefallen!", 80)
                        else:
                            self.flash("Sicherung " + str(n) + "/2 aktiv", 60)
                else:
                    self.prompt = "Sicherung ist aktiv"
        if self.door.locked and self.near(self.door.rect(), 26):
            if p.frags >= NUM_FRAGS:
                self.prompt = "RETURN = Tresor oeffnen"
                if pyxel.btnp(pyxel.KEY_RETURN):
                    self.door.locked = False
                    pyxel.play(0, 7)
                    self.flash("Tresor offen!", 50)
            else:
                self.prompt = "Brauche 3 Fragmente (" + str(p.frags) + "/3)"

        if not self.treasure.taken and \
           math.hypot(self.treasure.x - p.cx, self.treasure.y - p.cy) < 26:
            self.treasure.taken = True
            p.has_cookie = True
            p.score += 500
            self.alarm = True
            pyxel.play(0, 3)
            pyxel.play(2, 2, loop=True)
            self.flash("ALARM! Schnell zurueck zum Ausgang!", 100)

        self.spotting = set()
        self.laser_hit = False
        seen = False
        if not p.hidden and p.hit_timer == 0:
            for i, g in enumerate(self.guards):
                if g.sees(p.cx, p.cy, self.walls, self.alarm):
                    self.spotting.add(("g", i))
                    seen = True
            for i, c in enumerate(self.cams):
                if c.sees(p.cx, p.cy, self.walls, self.alarm):
                    self.spotting.add(("c", i))
                    seen = True
            for lz in self.lasers:
                if lz.is_on(self.alarm) and lz.hits(p.cx, p.cy):
                    self.laser_hit = True

        if self.laser_hit:
            self.suspicion = min(SUS_MAX, self.suspicion + SUS_GAIN_LASER)
            if pyxel.frame_count % 8 == 0:
                pyxel.play(0, 9)
        elif seen:
            cam_seen = any(s[0] == "c" for s in self.spotting)
            gain = SUS_GAIN_CAM if cam_seen else SUS_GAIN_GUARD
            self.suspicion += gain * (1.5 if self.alarm else 1.0)
        else:
            self.suspicion = max(0, self.suspicion - SUS_DRAIN)

        if self.suspicion >= SUS_MAX:
            self.suspicion = 0
            p.lives -= 1
            p.hit_timer = 80
            pyxel.play(0, 5)
            self.flash("ERWISCHT! -1 Leben", 55)
            if p.lives <= 0:
                pyxel.stop(2)
                self.state = "gameover"

        if p.has_cookie and rects_overlap(p.x, p.y, p.w, p.h, *self.exit_zone):
            pyxel.stop(2)
            p.score += 1500 + 50 * p.lives
            self.state = "win"
            self.win_t = 0

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

    # ----------------------------------------------------------------
    def draw(self):
        if self.state == "title":
            self.draw_title()
        elif self.state == "intro":
            self.draw_intro()
        elif self.state == "play":
            self.draw_play()
        elif self.state == "win":
            self.draw_win()
        else:
            self.draw_gameover()

    def draw_title(self):
        fc = pyxel.frame_count
        mid = SCREEN_W // 2
        pyxel.cls(BLACK)

        # --- dezenter Sternenhimmel ---
        for i, (sxp, syp) in enumerate([(38, 50), (92, 30), (150, 64),
                                        (470, 36), (430, 74), (372, 44),
                                        (250, 28), (300, 60), (60, 98),
                                        (208, 84), (460, 110), (120, 120)]):
            if (fc // 18 + i) % 5 != 0:
                pyxel.pset(sxp, syp, WHITE)
                if (i + fc // 18) % 3 == 0:
                    pyxel.pset(sxp + 1, syp, LBLUE)

        # --- Boden mit Fluchtpunkt-Perspektive ---
        horizon = 378
        pyxel.rect(0, horizon, SCREEN_W, SCREEN_H - horizon, NAVY)
        for gx in range(-160, SCREEN_W + 200, 54):
            pyxel.line(gx, SCREEN_H, mid, horizon, DBLUE)
        for i in range(1, 6):
            yy = int(horizon + (SCREEN_H - horizon) * (i / 6) ** 2)
            pyxel.line(0, yy, SCREEN_W, yy, DBLUE)
        pyxel.line(0, horizon, SCREEN_W, horizon, LBLUE)

        # --- warmer Lichtschein + Strahlen hinter der Keksdose ---
        jx, jy = mid, 290
        for rad, col in ((104, NAVY), (78, DBLUE), (56, ORANGE),
                         (36, YELLOW), (20, WHITE)):
            pyxel.circ(jx, jy, rad, col)
        for s in range(8):
            a = s * 0.785 + fc / 45
            pyxel.line(jx, jy, jx + math.cos(a) * 70, jy + math.sin(a) * 70,
                       YELLOW if s % 2 else ORANGE)
        pyxel.circ(jx, jy, 26, YELLOW)
        pyxel.circ(jx, jy, 14, WHITE)

        # --- Wache rechts mit sanft schwenkendem Suchscheinwerfer ---
        gx2, gy2 = 444, 250
        a = math.pi - 0.5 + math.sin(fc / 55) * 0.35
        L = 110
        pyxel.tri(gx2, gy2,
                  gx2 + math.cos(a - 0.26) * L, gy2 + math.sin(a - 0.26) * L,
                  gx2 + math.cos(a + 0.26) * L, gy2 + math.sin(a + 0.26) * L, YELLOW)
        draw_guard(gx2, gy2, a, False, False, k=2)

        # --- Podest + Keksdose ---
        pyxel.rect(jx - 42, jy + 46, 84, 10, DBLUE)
        pyxel.rect(jx - 34, jy + 38, 68, 9, LBLUE)
        pyxel.rect(jx - 28, jy + 28, 56, 11, DBLUE)
        pyxel.rectb(jx - 28, jy + 28, 56, 11, NAVY)
        draw_jar(jx, jy - 6, False, k=4)

        # --- Oma auf dem Roller (links, faehrt zur Dose) ---
        hop = -2 if (fc // 16) % 2 == 0 else 2
        draw_grandma(122, 414 + hop, facing=1, k=4)

        # --- Titel oben (mit Schatten) ---
        def tline(y, s, col, sc):
            big_text(mid + 2, y + 3, s, NAVY, sc)
            big_text(mid, y, s, col, sc)

        pyxel.rect(mid - 80, 18, 160, 22, ORANGE)
        pyxel.rectb(mid - 80, 18, 160, 22, YELLOW)
        big_text(mid, 24, "OMA GERTRUDE", BLACK, 2)
        draw_cookie(mid - 98, 29, k=1)
        draw_cookie(mid + 98, 29, k=1)
        tline(56, "DER GROSSE", ORANGE, 3)
        tline(96, "KEKS-RAUB", YELLOW, 6)

        # --- Footer: Start + Steuerung ---
        if (fc // 14) % 2 == 0:
            big_text(mid, 452, "SPACE = START", GREEN, 2)
        pyxel.text(mid - 116, 488,
                   "Pfeile/WASD Laufen   Leertaste Turbo   RETURN Aktion",
                   LBLUE)

        # --- Rahmen ---
        pyxel.rectb(4, 4, SCREEN_W - 8, SCREEN_H - 8, ORANGE)
        pyxel.rectb(6, 6, SCREEN_W - 12, SCREEN_H - 12, NAVY)

    def draw_machine(self, sx, sy, ww, wh):
        # Industrie-Maschine: Tank-Baender, Anzeige, Ventilrad, Bildschirm
        for by in range(int(sy) + 12, int(sy + wh) - 6, 14):
            pyxel.line(sx + 3, by, sx + ww - 3, by, NAVY)
        gx, gy = sx + 11, sy + wh - 13
        pyxel.circ(gx, gy, 5, WHITE)
        pyxel.circb(gx, gy, 5, BLACK)
        a = pyxel.frame_count / 22 + sx
        pyxel.line(gx, gy, gx + math.cos(a) * 4, gy + math.sin(a) * 4, RED)
        vx, vy = sx + ww - 11, sy + 16
        pyxel.circb(vx, vy, 5, LBLUE)
        pyxel.line(vx - 5, vy, vx + 5, vy, LBLUE)
        pyxel.line(vx, vy - 5, vx, vy + 5, LBLUE)
        scrx = sx + ww / 2 - 8
        pyxel.rect(scrx, sy + 10, 16, 11, BLACK)
        pyxel.rect(scrx + 2, sy + 12, 12, 7, TEAL)
        led = GREEN if (pyxel.frame_count // 12 + int(sx)) % 2 else RED
        pyxel.circ(sx + 6, sy + 9, 2, led)

    def draw_factory(self):
        cx, cy = self.cam_x, self.cam_y
        pyxel.cls(BLACK)
        pyxel.rect(-cx, -cy, WORLD_W, WORLD_H, NAVY)
        # Metallboden mit Platten, Nieten und etwas Schmutz
        x0 = int(cx) // TILE * TILE
        y0 = int(cy) // TILE * TILE
        for gx in range(x0, int(cx) + SCREEN_W + TILE, TILE):
            for gy in range(y0, int(cy) + SCREEN_H + TILE, TILE):
                sx, sy = gx - cx, gy - cy
                pyxel.rectb(sx, sy, TILE, TILE, BLACK)
                for ox, oy in ((4, 4), (TILE - 4, 4), (4, TILE - 4), (TILE - 4, TILE - 4)):
                    pyxel.pset(sx + ox, sy + oy, DBLUE)
                if point_in_rect(gx + TILE // 2, gy + TILE // 2, self.dark_zone):
                    pyxel.rect(sx + 6, sy + 6, 14, 11, BLACK)
                    pyxel.rect(sx + 26, sy + 28, 12, 9, BLACK)
                elif ((gx // TILE) * 7 + (gy // TILE) * 13) % 11 == 0:
                    pyxel.rect(sx + 16, sy + 16, 16, 10, BLACK)
                    pyxel.pset(sx + 20, sy + 20, DBLUE)

        # Kuehlhaus (Eis)
        zx, zy, zw, zh = self.ice_zone
        pyxel.rect(zx - cx, zy - cy, zw, zh, LBLUE)
        for fx in range(zx, zx + zw, 40):
            for fy in range(zy, zy + zh, 40):
                pyxel.pset(fx - cx, fy - cy, WHITE)
                pyxel.pset(fx - cx + 12, fy - cy + 20, WHITE)
        for fx in range(zx + 10, zx + zw, 70):
            pyxel.tri(fx - cx, zy - cy, fx + 8 - cx, zy - cy, fx + 4 - cx, zy + 14 - cy, WHITE)
        pyxel.text(zx - cx + 8, zy - cy + 6, "KUEHLHAUS", DBLUE)

        # Dunkelkammer: rote Notbeleuchtung an den Waenden (Boden ist abgedunkelt)
        zx, zy, zw, zh = self.dark_zone
        for (lx, ly) in ((660, 90), (840, 90), (920, 90),
                         (660, 600), (920, 600),
                         (660, 1090), (840, 1090), (920, 1090)):
            on = (pyxel.frame_count // 16 + lx) % 4 != 0
            if on:
                pyxel.circ(lx - cx, ly - cy, 6, RED)
                pyxel.circ(lx - cx, ly - cy, 3, ORANGE)
                pyxel.pset(lx - cx, ly - cy, YELLOW)
            else:
                pyxel.circ(lx - cx, ly - cy, 2, BROWN)
        pyxel.text(zx - cx + 8, zy - cy + 6, "DUNKELKAMMER", RED)

        pyxel.text(self.server_zone[0] - cx + 8, self.server_zone[1] - cy + 6,
                   "SERVERRAUM", DBLUE)

        # Foerderbaender
        for (bx, by, bw, bh) in self.belts:
            pyxel.rect(bx - cx, by - cy, bw, bh, DBLUE)
            pyxel.rectb(bx - cx, by - cy, bw, bh, LBLUE)
            off = pyxel.frame_count % 20
            for ax in range(bx, bx + bw - 8, 20):
                xx = ax + off - cx
                pyxel.tri(xx, by - cy + 4, xx, by + bh - cy - 4,
                          xx + 7, by + bh / 2 - cy, LBLUE)

        # Waende / Maschinen / Server (mit Schlagschatten + Paneelfugen)
        for w in self.walls:
            wx, wy, ww, wh = w
            sx, sy = wx - cx, wy - cy
            pyxel.rect(sx + 5, sy + wh, ww, 5, BLACK)
            pyxel.rect(sx + ww, sy + 5, 5, wh, BLACK)
            pyxel.rect(sx, sy, ww, wh, DBLUE)
            pyxel.rect(sx, sy, ww, 6, LBLUE)
            pyxel.rectb(sx, sy, ww, wh, NAVY)
            for fxp in range(int(wx) + 18, int(wx + ww) - 4, 26):
                pyxel.line(fxp - cx, sy + 7, fxp - cx, sy + wh - 2, NAVY)
            if ww >= 40 and wh >= 40:                      # Nieten an den Ecken
                for ox in (3, ww - 4):
                    for oy in (8, wh - 4):
                        pyxel.pset(sx + ox, sy + oy, LBLUE)
            if w in self.machines:
                self.draw_machine(sx, sy, ww, wh)
            elif w in self.servers:
                for ry in range(int(wy) + 8, int(wy + wh) - 8, 14):
                    pyxel.rect(sx + 6, ry - cy, ww - 12, 8, BLACK)
                    c = GREEN if (pyxel.frame_count // 8 + ry) % 3 else RED
                    pyxel.pset(sx + 10, ry - cy + 3, c)
                    pyxel.pset(sx + 15, ry - cy + 3, YELLOW)

        # Ausgang
        ex, ey, ew, eh = self.exit_zone
        pyxel.rect(ex - cx, ey - cy, ew, eh, TEAL)
        pyxel.rectb(ex - cx, ey - cy, ew, eh, GREEN)
        pyxel.text(ex - cx, ey - cy - 10, "AUSGANG", GREEN)

        # Verstecke
        for (hx, hy, hw, hh) in self.hides:
            pyxel.rect(hx - cx, hy - cy, hw, hh, BROWN)
            pyxel.rectb(hx - cx, hy - cy, hw, hh, ORANGE)
            pyxel.line(hx - cx, hy - cy, hx + hw - cx, hy + hh - cy, ORANGE)
            pyxel.line(hx + hw - cx, hy - cy, hx - cx, hy + hh - cy, ORANGE)
            pyxel.text(hx - cx + 6, hy - cy - 8, "VERSTECK", ORANGE)

        self.draw_details()

    def draw_details(self):
        cx, cy = self.cam_x, self.cam_y
        fc = pyxel.frame_count
        # Deckenträger + grosse Rohrleitung entlang oben
        pyxel.rect(-cx + 24, -cy + 8, WORLD_W - 48, 7, DBLUE)
        for px in range(60, WORLD_W - 60, 96):
            pyxel.rect(px - cx, -cy + 6, 6, 12, LBLUE)
        # Lueftungsgitter mit Luefter
        for vx in range(170, WORLD_W - 80, 360):
            x = vx - cx
            pyxel.rect(x, 18 - cy, 34, 16, DBLUE)
            pyxel.rectb(x, 18 - cy, 34, 16, NAVY)
            cxx, cyy = x + 17, 26 - cy
            a = fc / 6 + vx
            pyxel.line(cxx + math.cos(a) * 7, cyy + math.sin(a) * 6,
                       cxx - math.cos(a) * 7, cyy - math.sin(a) * 6, LBLUE)
            pyxel.line(cxx + math.cos(a + 1.5) * 7, cyy + math.sin(a + 1.5) * 6,
                       cxx - math.cos(a + 1.5) * 7, cyy - math.sin(a + 1.5) * 6, LBLUE)
        # Deckenlampen-Leuchten (zur Lichtinsel passend)
        # Dampf aus Maschinen
        for (mx, my, mw, mh) in self.machines:
            t = (fc + int(mx)) % 70
            if t < 42:
                pyxel.circb(mx + mw / 2 - cx, my - cy - t * 0.5,
                            3 + t * 0.08, LBLUE)
        # Wand-Bedienpanele (Bildschirm + Anzeigen)
        for (px, py) in self.panels:
            x, y = px - cx, py - cy
            pyxel.rect(x - 9, y - 8, 18, 16, DBLUE)
            pyxel.rectb(x - 9, y - 8, 18, 16, LBLUE)
            pyxel.rect(x - 6, y - 5, 12, 7, BLACK)
            for sl in range(3):
                c = GREEN if (fc // 6 + sl) % 2 else TEAL
                pyxel.line(x - 5, y - 4 + sl * 2, x + 4, y - 4 + sl * 2, c)
            pyxel.circ(x - 4, y + 4, 1, RED)
            pyxel.circ(x + 1, y + 4, 1, YELLOW)
            pyxel.circ(x + 5, y + 4, 1, GREEN)

    def draw_laser_wall(self):
        cx, cy = self.cam_x, self.cam_y
        wx, wy, ww, wh = self.laser_wall
        if self.wall_powered:
            for yy in range(int(wy), int(wy + wh), 10):
                pyxel.line(wx - cx, yy - cy, wx + ww - cx, yy - cy, RED)
                if (pyxel.frame_count // 3) % 2 == 0:
                    pyxel.line(wx - cx, yy + 1 - cy, wx + ww - cx, yy + 1 - cy, ORANGE)
            pyxel.text(wx - cx - 14, wy - cy - 10, "LASER-WAND", RED)
        else:
            pyxel.text(wx - cx - 12, wy - cy - 10, "STROM AUS", GREEN)

    def draw_play(self):
        cx, cy = self.cam_x, self.cam_y
        self.draw_factory()
        for lz in self.lasers:
            lz.draw(cx, cy, self.alarm)
        self.draw_laser_wall()
        for sw in self.switches:
            sw.draw(cx, cy)
        for c in self.cookies:
            c.draw(cx, cy)
        for fr in self.frags:
            fr.draw(cx, cy)
        self.door.draw(cx, cy)
        self.treasure.draw(cx, cy)
        for i, cam in enumerate(self.cams):
            cam.draw(cx, cy, self.alarm, ("c", i) in self.spotting)
        for i, g in enumerate(self.guards):
            g.draw(cx, cy, self.alarm, ("g", i) in self.spotting)
        r = REVEAL_R
        if point_in_rect(self.player.cx, self.player.cy, self.dark_zone):
            r = int(REVEAL_R * DARK_FACTOR)
        self.draw_fog(self.player.cx - cx, self.player.cy - cy, r)
        psx, psy = self.player.cx - cx, self.player.cy - cy
        pyxel.circb(psx, psy, r - 2, NAVY)
        pyxel.circb(psx, psy, r - 6, DBLUE)
        self.player.draw(cx, cy)
        for (kind, i) in self.spotting:
            src = self.cams[i] if kind == "c" else self.guards[i]
            if (pyxel.frame_count // 4) % 2 == 0:
                pyxel.text(src.x - cx - 1, src.y - cy - 22, "!", RED)
        self.draw_hud()

    def draw_fog(self, psx, psy, radius):
        r = radius
        top = int(psy - r)
        bot = int(psy + r)
        if top > 0:
            pyxel.rect(0, 0, SCREEN_W, top, BLACK)
        if bot < SCREEN_H:
            pyxel.rect(0, bot, SCREEN_W, SCREEN_H - bot, BLACK)
        for y in range(max(0, top), min(SCREEN_H, bot)):
            dx2 = r * r - (y - psy) ** 2
            if dx2 <= 0:
                pyxel.rect(0, y, SCREEN_W, 1, BLACK)
                continue
            hw = math.sqrt(dx2)
            left = int(psx - hw)
            right = int(psx + hw)
            if left > 0:
                pyxel.rect(0, y, left, 1, BLACK)
            if right < SCREEN_W:
                pyxel.rect(right, y, SCREEN_W - right, 1, BLACK)

    def draw_minimap(self):
        p = self.player
        px0, py0, pw, ph = SCREEN_W - 118, 4, 114, 72
        pyxel.rect(px0, py0, pw, ph, BLACK)
        pyxel.rectb(px0, py0, pw, ph, LBLUE)
        mx0, my0, mw, mh = px0 + 3, py0 + 3, pw - 6, ph - 6

        def mp(wx, wy):
            return (mx0 + wx / WORLD_W * mw, my0 + wy / WORLD_H * mh)

        def msize(ww, hh):
            return (max(1, ww / WORLD_W * mw), max(1, hh / WORLD_H * mh))

        # Spezialzonen einfaerben
        for zone, col in ((self.dark_zone, PURP), (self.ice_zone, LBLUE),
                          (self.server_zone, DBLUE)):
            x, y = mp(zone[0], zone[1])
            w, h = msize(zone[2], zone[3])
            pyxel.rect(x, y, w, h, col)
        # Waende
        for w in self.walls:
            x, y = mp(w[0], w[1])
            ww, hh = msize(w[2], w[3])
            pyxel.rect(x, y, ww, hh, NAVY)
        # Laser-Wand
        if self.wall_powered:
            x, y = mp(self.laser_wall[0], self.laser_wall[1])
            ww, hh = msize(self.laser_wall[2], self.laser_wall[3])
            pyxel.rect(x, y, ww, hh, RED)
        # Ausgang / Tresor / Tuer
        x, y = mp(self.exit_zone[0], self.exit_zone[1])
        pyxel.rect(x - 1, y, 3, 5, GREEN)
        x, y = mp(self.treasure.x, self.treasure.y)
        if not self.treasure.taken:
            pyxel.rect(x - 2, y - 2, 4, 4, YELLOW)
            if (pyxel.frame_count // 4) % 2 == 0:
                pyxel.rectb(x - 4, y - 4, 9, 9, YELLOW)
        x, y = mp(self.door.x, self.door.y)
        pyxel.rect(x - 1, y, 3, 4, RED if self.door.locked else GREEN)
        # Bonus-Kekse (braun)
        for c in self.cookies:
            if not c.taken:
                x, y = mp(c.x, c.y)
                pyxel.rect(x - 1, y - 1, 2, 2, BROWN)
        # Kameras
        for c in self.cams:
            x, y = mp(c.x, c.y)
            pyxel.rect(x - 1, y - 1, 3, 3, INDIGO)
        # Wachen (gelb)
        for g in self.guards:
            x, y = mp(g.cx, g.cy)
            pyxel.rect(x - 1, y - 1, 3, 3, YELLOW)
            pyxel.pset(x, y, ORANGE)
        # Fragmente (gruen)
        for fr in self.frags:
            if not fr.taken:
                x, y = mp(fr.x, fr.y)
                pyxel.rect(x - 1, y - 1, 3, 3, GREEN)
        # Schalter (blau)
        for sw in self.switches:
            x, y = mp(sw.x, sw.y)
            pyxel.rect(x - 1, y - 1, 3, 3, LBLUE if sw.on else DBLUE)
        # Spieler (gross + blinkend)
        x, y = mp(p.cx, p.cy)
        pyxel.rect(x - 1, y - 1, 3, 3, WHITE)
        if (pyxel.frame_count // 4) % 2 == 0:
            pyxel.rectb(x - 3, y - 3, 7, 7, WHITE)

    def draw_hud(self):
        p = self.player
        pyxel.rect(4, 4, 150, 48, BLACK)
        pyxel.rectb(4, 4, 150, 48, LBLUE)
        pyxel.text(10, 9, "LEBEN", WHITE)
        for i in range(p.lives):
            hx = 50 + i * 13
            pyxel.circ(hx, 12, 3, RED)
            pyxel.circ(hx + 3, 12, 3, RED)
            pyxel.tri(hx - 3, 13, hx + 6, 13, hx + 1, 17, RED)
            pyxel.pset(hx, 11, PINK)
        pyxel.text(10, 24, "TURBO", WHITE)
        pyxel.rect(50, 24, 96, 7, NAVY)
        pyxel.rectb(50, 24, 96, 7, DBLUE)
        pyxel.rect(51, 25, int(94 * p.turbo / p.turbo_max), 5,
                   CYAN if p.turbo > 20 else RED)
        pyxel.text(10, 38, "VERDACHT", WHITE)
        pyxel.rect(64, 38, 82, 7, NAVY)
        pyxel.rectb(64, 38, 82, 7, DBLUE)
        scol = RED if self.suspicion > 60 else (ORANGE if self.suspicion > 30 else YELLOW)
        pyxel.rect(65, 39, int(80 * self.suspicion / SUS_MAX), 5, scol)
        self.draw_minimap()
        sw_on = sum(s.on for s in self.switches)
        got = sum(1 for c in self.cookies if c.taken)
        total = len(self.cookies)
        bx = SCREEN_W - 116
        pyxel.text(bx, 78, "KEY   " + str(p.frags) + "/3", CYAN)
        pyxel.text(bx, 88, "STROM " + str(sw_on) + "/2",
                   GREEN if sw_on == 2 else ORANGE)
        pyxel.text(bx, 98, "KEKSE " + str(got) + "/" + str(total),
                   GREEN if got == total else PEACH)
        pyxel.text(bx, 108, "PKT " + str(p.score), YELLOW)
        if p.has_cookie:
            pyxel.text(bx, 118, "[KEKS]", RED)
        if sw_on < 2:
            obj = "ZIEL: 2 Sicherungen aktivieren"
        elif p.frags < NUM_FRAGS:
            obj = "ZIEL: Alle 3 Fragmente sammeln"
        elif self.door.locked:
            obj = "ZIEL: Tresor oeffnen (RETURN)"
        elif not p.has_cookie:
            obj = "ZIEL: Klau einen Keks"
        else:
            obj = "ZIEL: Zurueck zum Ausgang <<<"
        pyxel.rect(0, SCREEN_H - 12, SCREEN_W, 12, BLACK)
        pyxel.text(6, SCREEN_H - 9, obj, ORANGE if p.has_cookie else GREEN)
        if p.hidden:
            pyxel.text(SCREEN_W // 2 - 28, SCREEN_H - 26, "VERSTECKT", CYAN)
        if self.prompt and (pyxel.frame_count // 8) % 2 == 0:
            c = YELLOW if self.prompt.startswith("RETURN") else RED
            w = len(self.prompt) * 4
            pyxel.rect(SCREEN_W // 2 - w // 2 - 3, SCREEN_H - 46, w + 6, 10, BLACK)
            pyxel.text(SCREEN_W // 2 - w // 2, SCREEN_H - 44, self.prompt, c)
        if self.laser_hit and (pyxel.frame_count // 4) % 2 == 0:
            pyxel.text(SCREEN_W // 2 - 18, 64, "LASER!", RED)
        if self.alarm and (pyxel.frame_count // 8) % 2 == 0:
            pyxel.rectb(0, 0, SCREEN_W, SCREEN_H, RED)
            pyxel.rectb(1, 1, SCREEN_W - 2, SCREEN_H - 2, RED)
            pyxel.text(SCREEN_W // 2 - 16, 84, "ALARM!", RED)
        if self.message_t > 0:
            mx = SCREEN_W // 2 - len(self.message) * 2
            pyxel.rect(mx - 4, SCREEN_H // 2 - 6, len(self.message) * 4 + 8, 11, BLACK)
            pyxel.text(mx, SCREEN_H // 2 - 3, self.message, WHITE)

    # --- Story-Intro (8 Seiten, SPACE weiter) ---
    def draw_intro(self):
        pyxel.cls(BLACK)
        fc = pyxel.frame_count
        page = self.intro_page
        mid = SCREEN_W // 2

        def ttl(y, s, col):
            big_text(mid, y, s, col, 3)

        def lin(y, s, col):
            big_text(mid, y, s, col, 2)

        pyxel.rectb(8, 8, SCREEN_W - 16, SCREEN_H - 16, DBLUE)

        if page == 0:
            ttl(80, "DAS IST OMA GERTRUDE", YELLOW)
            wv = -3 if (fc // 15) % 2 == 0 else 3
            draw_grandma(mid, 225 + wv, facing=1, k=4)
            lin(320, "Sie ist 92 Jahre alt", WHITE)
            lin(348, "und liebt Schoko-Kekse", WHITE)
            ttl(378, "UEBER ALLES!", ORANGE)

        elif page == 1:
            ttl(70, "EIN PROBLEM", ORANGE)
            cx, cy = mid, 185
            pyxel.elli(cx - 60, cy - 35, 120, 70, PINK)
            pyxel.tri(cx - 38, cy - 28, cx - 26, cy - 52, cx - 12, cy - 26, PINK)
            for lx in (-38, -20, 12, 28):
                pyxel.rect(cx + lx, cy + 28, 12, 18, PINK)
            pyxel.elli(cx + 34, cy - 14, 30, 24, PEACH)
            pyxel.circb(cx + 44, cy - 2, 2, BLACK)
            pyxel.circ(cx + 16, cy - 12, 3, BLACK)
            pyxel.rect(cx - 14, cy - 42, 28, 5, BLACK)
            lin(296, "Die Rente reicht nicht", WHITE)
            lin(324, "fuer Schoko-Kekse...", WHITE)
            ttl(354, "OMA HAT HUNGER!", ORANGE)

        elif page == 2:
            ttl(36, "DIE LOESUNG", YELLOW)
            draw_jar(mid, 185, False, k=5)
            lin(300, "Im Werk lagert die", WHITE)
            ttl(328, "GOLDENE KEKSDOSE", YELLOW)

        elif page == 3:
            ttl(30, "GUT BEWACHT!", RED)
            gx, gy = 150, 165
            pyxel.tri(gx, gy, gx + 120, gy - 42, gx + 120, gy + 42, YELLOW)
            draw_guard(gx, gy, 0.0, False, False, k=2)
            pyxel.text(gx - 16, gy + 46, "WACHE", YELLOW)
            cmx, cmy = 360, 130
            pyxel.tri(cmx, cmy, cmx - 50, cmy + 90, cmx + 50, cmy + 90, CYAN)
            draw_camera(cmx, cmy, math.pi / 2, False, k=2)
            pyxel.text(cmx - 12, cmy - 22, "KAMERA", CYAN)
            pyxel.rect(116, 296, 8, 8, LBLUE)
            pyxel.rect(396, 296, 8, 8, LBLUE)
            pyxel.line(120, 300, 400, 300, RED)
            pyxel.text(178, 308, "LASER", RED)
            lin(360, "Wachen, Kameras, Laser!", WHITE)

        elif page == 4:
            ttl(28, "SPEZIAL-RAEUME", CYAN)
            lin(96, "Dunkelkammer:", WHITE)
            lin(122, "kaum Sicht!", LBLUE)
            lin(164, "Kuehlhaus:", WHITE)
            lin(190, "rutschiger Boden!", LBLUE)
            lin(232, "Serverraum:", WHITE)
            lin(258, "viele Kameras!", LBLUE)
            ttl(312, "VORSICHT!", RED)

        elif page == 5:
            ttl(28, "DEINE MISSION", GREEN)
            lin(92, "1) 3 Fragmente finden", WHITE)
            lin(130, "2) 2 Sicherungen an", WHITE)
            lin(168, "3) TRESOR: RETURN", WHITE)
            lin(206, "4) Keks klauen!", WHITE)
            lin(244, "5) Ab zum Ausgang", WHITE)
            draw_frag(mid - 60, 300)
            draw_switch(mid - 0, 300, True)
            draw_jar(mid + 60, 300, False, k=2)

        elif page == 6:
            ttl(30, "PUNKTE SAMMELN", CYAN)
            lin(100, "Bonus-Keks:  +100", WHITE)
            lin(138, "Keksdose:  +500", WHITE)
            lin(176, "Flucht:  +1500", WHITE)
            lin(206, "Alle Kekse = +1 LEBEN!", GREEN)
            lin(244, "Sammle viele Kekse", WHITE)
            lin(272, "fuer Highscore + Leben!", YELLOW)
            draw_cookie(mid, 360, k=3)

        else:  # page 7
            ttl(28, "STEUERUNG", CYAN)
            lin(90, "Pfeile/WASD = Laufen", WHITE)
            lin(122, "Leertaste = Turbo (laut!)", ORANGE)
            lin(158, "RETURN = Schalter/Tresor", WHITE)
            lin(196, "Maschinen = Deckung", LBLUE)
            lin(222, "Kisten = Verstecke", LBLUE)
            ttl(272, "BEREIT, OMA?", YELLOW)
            draw_grandma(mid, 384, facing=1, k=3)

        for i in range(INTRO_PAGES):
            col = YELLOW if i == page else DBLUE
            pyxel.circ(mid - (INTRO_PAGES - 1) * 6 + i * 12, 474, 3, col)
        if page < INTRO_PAGES - 1:
            if (fc // 16) % 2 == 0:
                lin(452, "SPACE = weiter >", LBLUE)
        else:
            if (fc // 12) % 2 == 0:
                lin(450, "SPACE = START !", GREEN)

    def draw_win(self):
        fc = pyxel.frame_count
        mid = SCREEN_W // 2
        # --- Wand mit Punkte-Tapete ---
        pyxel.cls(PEACH)
        for yy in range(24, 330, 28):
            for xx in range(24, SCREEN_W, 28):
                pyxel.pset(xx, yy, PINK)
        # --- Fussleiste + Holzboden ---
        pyxel.rect(0, 330, SCREEN_W, 10, BROWN)
        pyxel.rect(0, 340, SCREEN_W, SCREEN_H - 340, ORANGE)
        for yy in range(350, SCREEN_H, 18):
            pyxel.line(0, yy, SCREEN_W, yy, BROWN)
        # --- Fenster mit Nachthimmel ---
        wx, wy, ww, wh = 40, 66, 150, 120
        pyxel.rect(wx - 7, wy - 7, ww + 14, wh + 14, BROWN)
        pyxel.rect(wx, wy, ww, wh, NAVY)
        pyxel.circ(wx + ww - 36, wy + 34, 15, YELLOW)
        pyxel.circ(wx + ww - 30, wy + 30, 13, NAVY)
        for sx, sy in ((wx+22, wy+30), (wx+58, wy+62),
                       (wx+30, wy+94), (wx+96, wy+96), (wx+112, wy+44)):
            pyxel.pset(sx, sy, WHITE)
            pyxel.pset(sx + 1, sy, WHITE)
            pyxel.pset(sx, sy + 1, WHITE)
        pyxel.line(wx + ww // 2, wy, wx + ww // 2, wy + wh, BROWN)
        pyxel.line(wx, wy + wh // 2, wx + ww, wy + wh // 2, BROWN)
        pyxel.rect(wx - 12, wy - 12, ww + 24, 9, ORANGE)
        pyxel.rect(wx - 12, wy - 8, 16, wh + 14, RED)
        pyxel.rect(wx + ww - 4, wy - 8, 16, wh + 14, RED)
        # --- Gerahmtes Herz-Bild ---
        fx, fy = 300, 74
        pyxel.rect(fx - 4, fy - 4, 80, 64, BROWN)
        pyxel.rect(fx, fy, 72, 56, WHITE)
        hx, hy = fx + 36, fy + 24
        pyxel.circ(hx - 7, hy - 3, 7, RED)
        pyxel.circ(hx + 7, hy - 3, 7, RED)
        pyxel.tri(hx - 13, hy, hx + 13, hy, hx, hy + 16, RED)
        # --- Wanduhr ---
        ux, uy = 434, 102
        pyxel.circ(ux, uy, 24, WHITE)
        pyxel.circb(ux, uy, 24, BROWN)
        pyxel.circb(ux, uy, 23, BROWN)
        for hh in range(12):
            a = hh / 12 * 6.2832
            pyxel.pset(ux + math.cos(a) * 20, uy + math.sin(a) * 20, BROWN)
        a = fc / 80
        pyxel.line(ux, uy, ux + math.cos(a) * 10, uy + math.sin(a) * 10, BLACK)
        pyxel.line(ux, uy, ux + math.cos(a / 4 - 1.6) * 16,
                   uy + math.sin(a / 4 - 1.6) * 16, BLACK)
        pyxel.circ(ux, uy, 2, RED)
        # --- Teppich ---
        pyxel.elli(120, 398, 280, 86, RED)
        pyxel.ellib(120, 398, 280, 86, ORANGE)
        pyxel.ellib(155, 412, 210, 58, YELLOW)
        # --- Geparkter Roller am Rand ---
        rlx, rly, rs = 74, 436, 1.7
        pyxel.elli(rlx - RLR_W * rs / 2, rly + RLR_H * rs / 2 - 4,
                   RLR_W * rs, 8, BROWN)  # Schatten
        pyxel.blt(rlx - RLR_W / 2, rly - RLR_H / 2, 0,
                  RLR_U, RLR_V, RLR_W, RLR_H, WIN_COLKEY, scale=rs)
        # --- Sofa ---
        sx, sy, sw = 246, 292, 214
        pyxel.elli(sx - 6, sy - 12, 44, 44, DBLUE)
        pyxel.elli(sx + sw - 38, sy - 12, 44, 44, DBLUE)
        pyxel.rect(sx, sy, sw, 54, DBLUE)
        pyxel.rect(sx, sy, sw, 8, LBLUE)
        pyxel.rect(sx - 8, sy + 46, sw + 16, 48, DBLUE)
        pyxel.elli(sx - 26, sy + 20, 30, 28, DBLUE)
        pyxel.rect(sx - 24, sy + 32, 26, 62, DBLUE)
        pyxel.elli(sx + sw - 4, sy + 20, 30, 28, DBLUE)
        pyxel.rect(sx + sw - 2, sy + 32, 26, 62, DBLUE)
        pyxel.rect(sx + 10, sy + 52, 88, 30, PINK)
        pyxel.rectb(sx + 10, sy + 52, 88, 30, RED)
        pyxel.rect(sx + 112, sy + 52, 88, 30, PINK)
        pyxel.rectb(sx + 112, sy + 52, 88, 30, RED)
        pyxel.elli(sx - 4, sy + 14, 34, 34, RED)
        pyxel.rect(sx + 6, sy + 92, 12, 12, BROWN)
        pyxel.rect(sx + sw - 18, sy + 92, 12, 12, BROWN)
        # --- Beistelltisch + Keksdose + Teller ---
        tx, ty = 150, 356
        pyxel.rect(tx - 42, ty + 12, 8, 42, BROWN)
        pyxel.rect(tx + 34, ty + 12, 8, 42, BROWN)
        pyxel.rect(tx - 44, ty, 88, 12, BROWN)
        draw_jar(tx + 12, ty - 22, False, k=3)
        pyxel.elli(tx - 42, ty - 8, 42, 12, WHITE)
        for c in (-32, -22, -12):
            pyxel.circ(tx + c, ty - 4, 4, BROWN)
            pyxel.pset(tx + c - 1, ty - 5, BLACK)
        # --- Oma (ohne Roller) sitzt auf dem Sofa und isst Kekse ---
        gx, gy = sx + 108, sy + 20
        ws = 1.9                      # Oma groesser
        pyxel.blt(gx - WOMA_W / 2, gy - WOMA_H / 2, 0,
                  WOMA_U, WOMA_V, WOMA_W, WOMA_H, WIN_COLKEY, scale=ws)
        # --- Keks wandert im Bogen von der Hand zum Mund ---
        handx, handy = gx + 26, gy + 24
        mouthx, mouthy = gx + 4, gy - 10
        P = 64
        t = (fc % P) / P
        ct = min(1.0, t / 0.78)                       # 0..1: Hand -> Mund
        ckx = handx + (mouthx - handx) * ct
        cky = handy + (mouthy - handy) * ct - math.sin(ct * math.pi) * 4
        if t < 0.86:
            pyxel.circ(ckx, cky, 5, BROWN)
            pyxel.circb(ckx, cky, 5, ORANGE)
            pyxel.pset(ckx - 1, cky - 1, BLACK)
            pyxel.pset(ckx + 2, cky + 1, BLACK)
        if 0.78 <= t < 0.95 and (fc // 4) % 2 == 0:
            pyxel.text(mouthx + 12, mouthy - 12, "nom nom!", YELLOW)
        # --- Titel-Banner ---
        pyxel.rect(mid - 96, 10, 192, 30, RED)
        pyxel.rectb(mid - 96, 10, 192, 30, YELLOW)
        pyxel.text(mid - 20, 21, "GESCHAFFT!", WHITE)
        # --- Info-Leiste unten ---
        pyxel.rect(0, 470, SCREEN_W, SCREEN_H - 470, NAVY)
        pyxel.rectb(0, 470, SCREEN_W, SCREEN_H - 470, DBLUE)
        pyxel.text(mid - 148, 478,
                   "Oma Gertrude ist entkommen und geniesst es gemuetlich daheim.",
                   WHITE)
        pyxel.text(18, 495, "PUNKTE: " + str(self.player.score), YELLOW)
        if (fc // 16) % 2 == 0:
            pyxel.text(330, 495, "SPACE = neue Runde", LBLUE)

    def draw_gameover(self):
        pyxel.cls(BLACK)
        pyxel.rectb(40, 150, SCREEN_W - 80, 210, RED)
        pyxel.text(SCREEN_W // 2 - 32, 186, "ERWISCHT!", RED)
        pyxel.text(SCREEN_W // 2 - 82, 222,
                   "Die Wachen haben Oma Gertrude geschnappt.", WHITE)
        pyxel.text(SCREEN_W // 2 - 44, 252,
                   "PUNKTE: " + str(self.player.score), YELLOW)
        draw_grandma(SCREEN_W // 2, 312, facing=1)
        if (pyxel.frame_count // 16) % 2 == 0:
            pyxel.text(SCREEN_W // 2 - 100, 470,
                       "Druecke SPACE fuer neuen Versuch", YELLOW)


# ---------------------------------------------------------------------
Game()