from pyxel import*
import pyxel
import random
import math
from collections import deque

# =========================================================
# ESCAPE THE CHICKEN FARM - FINAL VERSION
# =========================================================

# Stats wie Player Speed ect #
# Wir haben die Maps Selber gemacht. Zeile 100-237 #
# Jedes def Draw im code ist selbst gemacht/Designt #
# Musik mit etwas hilfe von KI tiefen und schnelligkeit der Töne verändert #
TILE = 16
FPS = 60

MAP_W = 32
MAP_H = 24
WORLD_W = MAP_W * TILE
WORLD_H = MAP_H * TILE

SCREEN_W = 256
SCREEN_H = 256

PLAYER_SIZE = 12
ALLY_SIZE = 10

PLAYER_BASE_SPEED = 1.3
BASE_FARMER_SPEED = 1.0
BASE_DOG_SPEED = 1.2
ALLY_SPEED = 4.0
ALLY_BUMP_STUN = 58

EGG_SPEED = 6.0
EGG_COOLDOWN_FRAMES = 150
MAX_EGG_DISTANCE = 94

CHAOS_EGG_SPEED = 6.0
CHAOS_EGG_COOLDOWN_FRAMES = 220
CHAOS_RADIUS = 38

SPECIAL_EGG_SPEED = 4.0
SPECIAL_EGG_DISTANCE = 90

FARMER_SHOT_INTERVAL = 10 * FPS
FARMER_COUNTDOWN_FRAMES = 3 * FPS
ARROW_SPEED = 8.0
SHOT_FLASH_FRAMES = 8

BOOST_SPAWN_INTERVAL_MIN = 130
BOOST_SPAWN_INTERVAL_MAX = 220
BOOST_LIFETIME = 260
BOOST_DURATION = 190

SPECIAL_PICKUP_INTERVAL_MIN = 130
SPECIAL_PICKUP_INTERVAL_MAX = 210
SPECIAL_PICKUP_LIFETIME = 340

SPLAT_LIFETIME = 280
SLIP_STUN_FRAMES = 44
SLIDE_SPEED = 1.85

FREEZE_STUN_FRAMES = 90
STINK_DURATION = 155
STINK_RADIUS = 48

ALARM_DURATION = 100
ALARM_SPEED_MULT = 1.18

CLOCK_FRAMES_PER_HOUR = 150

COL_BLACK = 0
COL_DARKBLUE = 1
COL_PURPLE = 2
COL_DARKGREEN = 3
COL_BROWN = 4
COL_GRAY = 5
COL_LIGHTBLUE = 6
COL_WHITE = 7
COL_RED = 8
COL_ORANGE = 9
COL_YELLOW = 10
COL_LIME = 11
COL_SKY = 12
COL_PINK = 13
COL_PEACH = 14
COL_CREAM = 15

STATE_PLAYING = "playing"
STATE_LEVEL_CLEAR = "level_clear"
STATE_GAMEOVER = "gameover"
STATE_FINAL_WIN = "final_win"

STAR_THRESHOLDS = [
    (25, 38),
    (29, 43),
    (34, 50),
    (39, 58),
    (46, 68),
]



RAW_LEVELS = [
    # Level 1
    [
        "################################",
        "#S.................Q..........##",
        "#.######.#######.######.####..##",
        "#......#.....H.#......#....#..##",
        "#.####.#.#####.#.####.#.##.#..##",
        "#....#.#.....#.#....#.#..#.#..##",
        "####.#.#####.#.####.#.##.#.#..##",
        "#......F...#.#......#....#.#..##",
        "#.##########.######.######.#..##",
        "#.................#........#..##",
        "#..H..............#....C...#..##",
        "#..............D..#........#..##",
        "#.##########################..##",
        "#.............................##",
        "#..........Q..................##",
        "#.............................##",
        "#....H........................##",
        "#..............######.........##",
        "#.............................##",
        "#..............C..............##",
        "#..........................E..##",
        "#.............................##",
        "#.............................##",
        "################################",
    ],
    # Level 2
    [
        "################################",
        "#S....#........#.....#........##",
        "#.###.#.######.#.###.#.######.##",
        "#...#.#....#...#...#.#....H#..##",
        "###.#.####.#.#####.#.#######.###",
        "#...#......#..Q..#.#.......#..##",
        "#.##############.#.#######.##.##",
        "#...#......H...#.#.....#...#..##",
        "###.#.########.#.#####.#.###.###",
        "#...#........#.#...#...#...#..##",
        "#.#######.#.#.#.#.#.#####.#..###",
        "#...#.....#.#...#.#.....#.#...##",
        "###.#.###.#.#####.#####.#.###.##",
        "#.#...#...#.....#.....#.#...#.##",
        "#.#.###.#######.#####.#.###.#.##",
        "#.#...#.....F.#.....#.#...#.#.##",
        "#.###.#####.#.#####.#.###.#.#.##",
        "#...#.....#.#.....#.#.....#.#.##",
        "###.#####.#.#####.#.#######.#.##",
        "#...#...#.#...H.#.#...#.....#.##",
        "#.###.#.#.#####.#.###.#.#####.##",
        "#......Q....................E.##",
        "#.............................##",
        "################################",
    ],
    # Level 3
    [
        "################################",
        "#S....#........#.....#........##",
        "#.###.#.######.#.###.#.######.##",
        "#...#.#....#...#...#.#....H#..##",
        "###.#.####.#.#####.#.#######.###",
        "#...#......#.....#.#.......#..##",
        "#.##############.#.#######.##.##",
        "#...#......H...#.#.....#...#..##",
        "###.#.########.#.#####.#.###.###",
        "#...#........#.#...#...#...#..##",
        "#.#######.#.#.#.#.#.#####.#..###",
        "#...#.....#.#...#.#.....#.#...##",
        "###.#.###.#.#####.#####.#.###.##",
        "#.#...#...#.....#.....#.#...#.##",
        "#.#.###.#######.#####.#.###.#.##",
        "#.#...#.....F.#.....#.#...#.#.##",
        "#.###.#####.#.#####.#.###.#.#.##",
        "#...#.....#.#.....#.#..Q..#.#.##",
        "###.#####.#.#####.#.#######.#.##",
        "#...#...#.#...H.#.#...#.....#.##",
        "#.###.#.#.#####.#.###.#.#####.##",
        "#..............D..........#..E##",
        "#.............................##",
        "################################",
    ],
    # Level 4
    [
        "################################",
        "#S...#................#.......##",
        "#.##.#.##############.#.####..##",
        "#..#.#....H......D..#.#.#..#..##",
        "##.#.######.######..#.#.#.##.###",
        "#..#........#....#..#...#....###",
        "#.######.#.#.##.#.#####.##.#.###",
        "#......#.#.#.##.#.....#....#..##",
        "######.#.#.#....#####.#######.##",
        "#....#.#.#.##########.#.....#.##",
        "#.##.#.#.#.....Q....#.#.###.#.##",
        "#.##.#.#.##########.#.#.#...#.##",
        "#....#........D....#...#.#.#..##",
        "#.########.######.#####.#.#.#.##",
        "#.#......#......#.....#.#.#...##",
        "#.#.####.######.#####.#.#.###.##",
        "#.#....F......#.....#.#...#.#.##",
        "#.######.####.#.###.#.###.#.#.##",
        "#......#....#.#...#.#.....#.#.##",
        "######.####.#.###.#.#######.#.##",
        "#...H.............D.....Q...#.##",
        "#..........................E..##",
        "#.............................##",
        "################################",
    ],
    # Level 5
    [
        "################################",
        "#S...#.......................###",
        "#.##.#.###########.#########.###",
        "#..#.#.....Q.....#.#.......#.###",
        "##.#.######.###.#.#.#.###.#.####",
        "#..#............#...#.#...#...##",
        "#.######.#.#.#.#####.#.#####.##.",
        "#......#.#.#.#.....#.#.....#.#.#",
        "######.#.#.#.#####.#.#####.#.#.#",
        "#....#.#...#.....#.#.....#.#.#.#",
        "#.##.#.#########.#.#####.#.#.#.#",
        "#.##.#...........#.....#.#...#.#",
        "#....###############.#.#.#####.#",
        "#.######...........#.#.#...D...#",
        "#.#....#.#########.#.#.#########",
        "#.#.Q..#.#.......#.#.#.........#",
        "#.#.####.#.#####.#.#.#########.#",
        "#.#....#.#...F.#.#.#.....D...#.#",
        "#.####.#.#####.#.#.#######.#.#.#",
        "#....#.#.....#.#.#...H...#.#...#",
        "####.#.#####.#.#.#######.#.#####",
        "#.............#...........D....#",
        "#.............................E#",
        "################################",
    ],
]


def normalize_map(rows):
    result = []
    for row in rows[:MAP_H]:
        if len(row) < MAP_W:
            row = row + "#" * (MAP_W - len(row))
        elif len(row) > MAP_W:
            row = row[:MAP_W]
        result.append(row)
    while len(result) < MAP_H:
        result.append("#" * MAP_W)
    return result


LEVELS = [normalize_map(level) for level in RAW_LEVELS]


def clamp(value, minimum, maximum):
    return max(minimum, min(maximum, value))


def rects_overlap(x1, y1, w1, h1, x2, y2, w2, h2):
    return (
        x1 < x2 + w2 and
        x1 + w1 > x2 and
        y1 < y2 + h2 and
        y1 + h1 > y2
    )


def dist(x1, y1, x2, y2):
    return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)


def angle_diff(a, b):
    d = a - b
    while d > math.pi:
        d -= 2 * math.pi
    while d < -math.pi:
        d += 2 * math.pi
    return abs(d)


class Camera:
    def __init__(self):
        self.x = 0
        self.y = 0

    def update(self, target_x, target_y):
        self.x = int(target_x - SCREEN_W / 2)
        self.y = int(target_y - SCREEN_H / 2)
        self.x = clamp(self.x, 0, WORLD_W - SCREEN_W)
        self.y = clamp(self.y, 0, WORLD_H - SCREEN_H)


class Splat:
    def __init__(self, x, y):
        self.x = x - 8
        self.y = y - 8
        self.w = 16
        self.h = 16
        self.timer = SPLAT_LIFETIME
        self.alive = True

    def update(self):
        self.timer -= 1
        if self.timer <= 0:
            self.alive = False

    def draw(self, cam):
        sx = int(self.x - cam.x)
        sy = int(self.y - cam.y)
        pyxel.circ(sx + 5, sy + 8, 4, COL_WHITE)
        pyxel.circ(sx + 11, sy + 8, 4, COL_WHITE)
        pyxel.circ(sx + 8, sy + 5, 4, COL_WHITE)
        pyxel.circ(sx + 8, sy + 11, 4, COL_WHITE)
        pyxel.circ(sx + 8, sy + 8, 3, COL_YELLOW)
        pyxel.circb(sx + 8, sy + 8, 7, COL_CREAM)


class FreezeBurst:
    def __init__(self, x, y, radius=28):
        self.x = x
        self.y = y
        self.radius = radius
        self.timer = 22
        self.alive = True

    def update(self):
        self.timer -= 1
        if self.timer <= 0:
            self.alive = False

    def draw(self, cam):
        sx = int(self.x - cam.x)
        sy = int(self.y - cam.y)
        r = max(2, int(self.radius * self.timer / 22))
        pyxel.circ(sx + 6, sy + 6, 3, COL_SKY)
        pyxel.circb(sx + 6, sy + 6, 4, COL_WHITE)


class StinkCloud:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.radius = STINK_RADIUS
        self.timer = STINK_DURATION
        self.alive = True

    def update(self):
        self.timer -= 1
        if self.timer <= 0:
            self.alive = False

    def draw(self, cam):
        sx = int(self.x - cam.x)
        sy = int(self.y - cam.y)
        if pyxel.frame_count % 4 < 3:
            pyxel.circ(sx - 8, sy, 8, COL_LIME)
            pyxel.circ(sx + 3, sy - 4, 9, COL_DARKGREEN)
            pyxel.circ(sx + 11, sy + 4, 8, COL_LIME)
            pyxel.circ(sx - 1, sy + 9, 7, COL_DARKGREEN)
            pyxel.text(sx - 7, sy - 15, "PUAH!", COL_YELLOW)


class SpecialPickup:
    def __init__(self, x, y, kind):
        self.x = x
        self.y = y
        self.w = 12
        self.h = 12
        self.kind = kind
        self.timer = SPECIAL_PICKUP_LIFETIME
        self.alive = True

    def update(self):
        self.timer -= 1
        if self.timer <= 0:
            self.alive = False

    def draw(self, cam):
        sx = int(self.x - cam.x)
        sy = int(self.y - cam.y)
        if self.timer < 60 and pyxel.frame_count % 6 < 3:
            return
        col = COL_SKY if self.kind == "freeze" else COL_LIME
        txt = "F" if self.kind == "freeze" else "S"
        pyxel.circ(sx + 6, sy + 6, 4, COL_SKY)
        pyxel.circb(sx + 6, sy + 6, 5, COL_WHITE)
        pyxel.text(sx + 5, sy + 4, "F", COL_BLACK)


class Boost:
    def __init__(self, x, y, boost_type):
        self.x = x
        self.y = y
        self.w = 12
        self.h = 12
        self.type = boost_type
        self.lifetime = BOOST_LIFETIME
        self.alive = True

    def update(self):
        self.lifetime -= 1
        if self.lifetime <= 0:
            self.alive = False

    def draw(self, cam):
        sx = int(self.x - cam.x)
        sy = int(self.y - cam.y)
        if self.lifetime < 60 and pyxel.frame_count % 6 < 3:
            return
        pyxel.rect(sx + 2, sy + 2, 8, 2, COL_YELLOW)
        pyxel.line(sx + 9, sy + 4, sx + 2, sy + 8, COL_YELLOW)
        
        pyxel.line(sx + 8, sy + 5, sx + 3, sy + 8, COL_YELLOW)
        pyxel.line(sx + 3, sy + 8, sx + 6, sy + 10, COL_YELLOW)
        pyxel.line(sx + 4, sy + 7, sx + 6, sy + 10, COL_YELLOW)
        pyxel.line(sx + 2, sy + 9, sx + 6, sy + 10, COL_YELLOW)
        pyxel.line(sx + 7, sy + 5, sx + 4, sy + 8, COL_YELLOW)
        pyxel.rect(sx + 7, sy + 4, 2, 1, COL_YELLOW)
        pyxel.rect(sx + 6, sy + 5, 2, 1, COL_YELLOW)
        pyxel.line(sx + 6, sy + 4, sx + 4, sy + 6, COL_YELLOW)
        pyxel.pset(sx + 5, sy + 4, COL_YELLOW)
        pyxel.pset(sx + 4, sy + 4, COL_YELLOW)
        pyxel.text(sx - 5, sy + 9, "Speed", COL_BLACK)
        

class CageChicken:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.w = ALLY_SIZE
        self.h = ALLY_SIZE
        self.target = None
        self.bump_immunity = 0
        self.retarget_timer = 0
        self.path = []
        self.path_index = 0
        self.hop_timer = 0

    def center_x(self):
        return self.x + self.w / 2

    def center_y(self):
        return self.y + self.h / 2

    def tile_pos(self):
        return int(self.center_x() // TILE), int(self.center_y() // TILE)

    def choose_target(self, game):
        sx, sy = self.tile_pos()
        best_enemy = None
        best_path = None
        best_len = 999999
        for enemy in game.enemies:
            if not enemy.alive:
                continue
            ex = int(enemy.center_x() // TILE)
            ey = int(enemy.center_y() // TILE)
            path = game.find_path(sx, sy, ex, ey, jumper=True)
            if path and len(path) < best_len:
                best_len = len(path)
                best_path = path
                best_enemy = enemy
        self.target = best_enemy
        self.path = best_path if best_path else []
        self.path_index = 1 if len(self.path) > 1 else 0
        self.retarget_timer = 8

    def move(self, dx, dy, game):
        new_x = self.x + dx
        new_y = self.y + dy
        if not game.collides_with_solid_actor(new_x, self.y, self.w, self.h, actor_kind="jumper"):
            self.x = new_x
        if not game.collides_with_solid_actor(self.x, new_y, self.w, self.h, actor_kind="jumper"):
            self.y = new_y
        self.x = clamp(self.x, 0, WORLD_W - self.w)
        self.y = clamp(self.y, 0, WORLD_H - self.h)

    def update(self, game):
        if self.bump_immunity > 0:
            self.bump_immunity -= 1
        if self.retarget_timer > 0:
            self.retarget_timer -= 1
        self.hop_timer = (self.hop_timer + 1) % 12
        if self.target is None or not self.target.alive or self.retarget_timer <= 0:
            self.choose_target(game)
        if self.target and self.path and self.path_index < len(self.path):
            tx, ty = self.path[self.path_index]
            px = tx * TILE + TILE / 2
            py = ty * TILE + TILE / 2
            dx = px - self.center_x()
            dy = py - self.center_y()
            l = math.sqrt(dx * dx + dy * dy)
            if l < 3:
                self.path_index += 1
            elif l > 0:
                dx /= l
                dy /= l
                self.move(dx * ALLY_SPEED, dy * ALLY_SPEED, game)
        elif self.target:
            dx = self.target.center_x() - self.center_x()
            dy = self.target.center_y() - self.center_y()
            l = math.sqrt(dx * dx + dy * dy)
            if l > 0:
                dx /= l
                dy /= l
                self.move(dx * ALLY_SPEED, dy * ALLY_SPEED, game)
        for enemy in game.enemies:
            if not enemy.alive:
                continue
            if rects_overlap(self.x, self.y, self.w, self.h, enemy.x, enemy.y, enemy.w, enemy.h):
                if self.bump_immunity <= 0:
                    ex = enemy.center_x() - self.center_x()
                    ey = enemy.center_y() - self.center_y()
                    l = math.sqrt(ex * ex + ey * ey)
                    if l == 0:
                        ex, ey = 1, 0
                    else:
                        ex /= l
                        ey /= l
                    enemy.begin_slip(ex * 1.8, ey * 1.2)
                    enemy.slip_timer = max(enemy.slip_timer, ALLY_BUMP_STUN)
                    enemy.slip_immunity = 28
                    self.bump_immunity = 24
                    self.target = None
                    self.retarget_timer = 0

    def draw(self, cam):
        sx = int(self.x - cam.x)
        sy = int(self.y - cam.y)
        hop = -2 if self.hop_timer < 6 else 0
        walk = (pyxel.frame_count // 4) % 2
        pyxel.circ(sx + 5, sy + 7 + hop, 3, COL_WHITE)
        pyxel.circ(sx + 7, sy + 5 + hop, 2, COL_WHITE)
        pyxel.pset(sx + 7, sy + 5 + hop, COL_BLACK)
        pyxel.tri(sx + 8, sy + 5 + hop, sx + 10, sy + 4 + hop, sx + 10, sy + 6 + hop, COL_ORANGE)
        pyxel.pset(sx + 7, sy + 3 + hop, COL_RED)
        if walk == 0:
            pyxel.line(sx + 4, sy + 9 + hop, sx + 4, sy + 11 + hop, COL_ORANGE)
            pyxel.line(sx + 6, sy + 9 + hop, sx + 7, sy + 11 + hop, COL_ORANGE)
        else:
            pyxel.line(sx + 4, sy + 9 + hop, sx + 5, sy + 11 + hop, COL_ORANGE)
            pyxel.line(sx + 6, sy + 9 + hop, sx + 6, sy + 11 + hop, COL_ORANGE)


class Cage:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.w = 14
        self.h = 14
        self.opened = False

    def update(self, game):
        if self.opened:
            return
        if rects_overlap(self.x, self.y, self.w, self.h, game.player.x, game.player.y, game.player.w, game.player.h):
            self.open(game)

    def open(self, game):
        if self.opened:
            return
        self.opened = True
        game.allies.append(CageChicken(self.x + 2, self.y + 2))

    def draw(self, cam):
        sx = int(self.x - cam.x)
        sy = int(self.y - cam.y)
        if not self.opened:
            pyxel.rect(sx, sy, 14, 14, COL_GRAY)
            pyxel.rectb(sx, sy, 14, 14, COL_YELLOW)
            pyxel.rectb(sx - 1, sy - 1, 16, 16, COL_WHITE)
            for k in [2, 5, 8, 11]:
                pyxel.line(sx + k, sy + 1, sx + k, sy + 12, COL_BLACK)
            pyxel.line(sx + 1, sy + 4, sx + 12, sy + 4, COL_BLACK)
            pyxel.line(sx + 1, sy + 9, sx + 12, sy + 9, COL_BLACK)
            pyxel.circ(sx + 7, sy + 8, 3, COL_WHITE)
            pyxel.circ(sx + 8, sy + 6, 2, COL_WHITE)
            pyxel.tri(sx + 9, sy + 6, sx + 11, sy + 5, sx + 11, sy + 7, COL_ORANGE)
        else:
            pyxel.rectb(sx, sy, 14, 14, COL_BROWN)
            pyxel.line(sx + 1, sy + 13, sx + 13, sy + 6, COL_BROWN)


class ActorBase:
    def __init__(self):
        self.slip_timer = 0
        self.slip_immunity = 0
        self.slip_vx = 0
        self.slip_vy = 0

    def update_slip_timers(self):
        if self.slip_timer > 0:
            self.slip_timer -= 1
        if self.slip_immunity > 0:
            self.slip_immunity -= 1
        if self.slip_timer <= 0:
            self.slip_vx = 0
            self.slip_vy = 0

    def is_stunned(self):
        return self.slip_timer > 0

    def begin_slip(self, vx, vy):
        self.slip_vx = vx
        self.slip_vy = vy

    def try_slip_on_splats(self, game, pref_vx=0, pref_vy=0):
        if self.slip_timer > 0 or self.slip_immunity > 0:
            return
        for splat in game.splats:
            if rects_overlap(self.x, self.y, self.w, self.h, splat.x, splat.y, splat.w, splat.h):
                vx = pref_vx
                vy = pref_vy
                if abs(vx) < 0.1 and abs(vy) < 0.1:
                    vx = random.choice([-1, 1])
                    vy = random.choice([-1, 1]) * 0.6
                self.begin_slip(vx * SLIDE_SPEED, vy * SLIDE_SPEED)
                self.slip_timer = SLIP_STUN_FRAMES
                self.slip_immunity = 65
                return


class Player(ActorBase):
    def __init__(self, x, y):
        super().__init__()
        self.x = x
        self.y = y
        self.w = PLAYER_SIZE
        self.h = PLAYER_SIZE
        self.base_speed = PLAYER_BASE_SPEED
        self.speed = PLAYER_BASE_SPEED
        self.facing_dx = 1
        self.facing_dy = 0
        self.speed_boost_timer = 0
        self.invisible_timer = 0
        self.egg_cooldown = 0
        self.chaos_egg_cooldown = 0
        self.is_moving = False
        self.freeze_eggs = 0
        self.stink_eggs = 0

    def center_x(self):
        return self.x + self.w / 2

    def center_y(self):
        return self.y + self.h / 2

    def is_invisible(self):
        return self.invisible_timer > 0

    def current_egg_cooldown(self):
        return EGG_COOLDOWN_FRAMES

    def facing_angle(self):
        return math.atan2(self.facing_dy, self.facing_dx)

    def update_timers(self):
        self.update_slip_timers()
        if self.speed_boost_timer > 0:
            self.speed_boost_timer -= 1
        if self.invisible_timer > 0:
            self.invisible_timer -= 1
        if self.egg_cooldown > 0:
            self.egg_cooldown -= 1
        if self.chaos_egg_cooldown > 0:
            self.chaos_egg_cooldown -= 1
        self.speed = self.base_speed * 1.45 if self.speed_boost_timer > 0 else self.base_speed

    def try_move(self, dx, dy, game):
        if dx == 0 and dy == 0:
            self.is_moving = False
            return
        self.is_moving = True
        new_x = self.x + dx
        new_y = self.y + dy
        if not game.collides_with_solid_actor(new_x, self.y, self.w, self.h, actor_kind="normal"):
            self.x = new_x
        if not game.collides_with_solid_actor(self.x, new_y, self.w, self.h, actor_kind="normal"):
            self.y = new_y
        self.x = clamp(self.x, 0, WORLD_W - self.w)
        self.y = clamp(self.y, 0, WORLD_H - self.h)

    def update(self, game):
        self.update_timers()
        dx = 0
        dy = 0
        if pyxel.btn(pyxel.KEY_LEFT):
            dx -= self.speed
            self.facing_dx, self.facing_dy = -1, 0
        if pyxel.btn(pyxel.KEY_RIGHT):
            dx += self.speed
            self.facing_dx, self.facing_dy = 1, 0
        if pyxel.btn(pyxel.KEY_UP):
            dy -= self.speed
            self.facing_dx, self.facing_dy = 0, -1
        if pyxel.btn(pyxel.KEY_DOWN):
            dy += self.speed
            self.facing_dx, self.facing_dy = 0, 1
        self.try_slip_on_splats(game, dx if dx != 0 else self.facing_dx, dy if dy != 0 else self.facing_dy)
        if self.is_stunned():
            self.is_moving = True
            self.try_move(self.slip_vx, self.slip_vy, game)
            return
        self.try_move(dx, dy, game)

    def draw(self, cam):
        sx = int(self.x - cam.x)
        sy = int(self.y - cam.y)
        pyxel.circ(sx + 6, sy + 11, 4, COL_BLACK)
        body_col = COL_WHITE
        if self.is_invisible() and pyxel.frame_count % 6 < 3:
            body_col = COL_SKY
        walk = (pyxel.frame_count // 5) % 2 if self.is_moving else 0
        pyxel.circ(sx + 5, sy + 7, 5, COL_BLACK)
        pyxel.circ(sx + 9, sy + 4, 4, COL_BLACK)
        pyxel.circ(sx + 5, sy + 7, 4, body_col)
        pyxel.circ(sx + 8, sy + 6, 3, body_col)
        pyxel.circ(sx + 9, sy + 4, 3, body_col)
        pyxel.circ(sx + 4, sy + 7, 2, COL_CREAM)
        pyxel.pset(sx + 8, sy + 1, COL_RED)
        pyxel.pset(sx + 9, sy + 0, COL_RED)
        pyxel.pset(sx + 10, sy + 1, COL_RED)
        pyxel.pset(sx + 9 + self.facing_dx, sy + 4 + self.facing_dy, COL_BLACK)
        bx = sx + 11 + self.facing_dx
        by = sy + 4 + self.facing_dy
        pyxel.tri(bx, by, bx + 3, by - 1, bx + 3, by + 1, COL_ORANGE)
        leg_col = COL_ORANGE if not self.is_stunned() else COL_YELLOW
        if walk == 0:
            pyxel.line(sx + 4, sy + 10, sx + 4, sy + 12, leg_col)
            pyxel.line(sx + 7, sy + 10, sx + 7, sy + 12, leg_col)
        else:
            pyxel.line(sx + 4, sy + 10, sx + 5, sy + 12, leg_col)
            pyxel.line(sx + 7, sy + 10, sx + 6, sy + 12, leg_col)
        if self.is_stunned() and pyxel.frame_count % 8 < 4:
            pyxel.text(sx - 1, sy - 6, "SLIP!", COL_YELLOW)


class Egg:
    def __init__(self, x, y, dx, dy, kind="normal"):
        self.x = x
        self.y = y
        self.r = 3 if kind != "chaos" else 4
        self.dx = dx
        self.dy = dy
        self.kind = kind
        self.alive = True
        if kind == "chaos":
            self.speed = CHAOS_EGG_SPEED
            self.max_distance = 9999
        elif kind in ["freeze", "stink"]:
            self.speed = SPECIAL_EGG_SPEED
            self.max_distance = SPECIAL_EGG_DISTANCE
        else:
            self.speed = EGG_SPEED
            self.max_distance = MAX_EGG_DISTANCE
        self.distance_left = self.max_distance

    def explode(self, game):
        if self.kind == "normal":
            game.splats.append(Splat(self.x, self.y))
        elif self.kind == "chaos":
            game.explode_chaos_egg(self.x, self.y, radius=CHAOS_RADIUS)
        elif self.kind == "freeze":
            game.freeze_burst(self.x, self.y)
        elif self.kind == "stink":
            game.stink_cloud(self.x, self.y)

    def update(self, game):
        step_x = self.dx * self.speed
        step_y = self.dy * self.speed
        self.x += step_x
        self.y += step_y
        self.distance_left -= math.sqrt(step_x * step_x + step_y * step_y)
        if game.point_hits_solid(self.x, self.y):
            self.explode(game)
            self.alive = False
            return
        for cage in game.cages:
            if not cage.opened and rects_overlap(cage.x, cage.y, cage.w, cage.h, self.x - 3, self.y - 3, 6, 6):
                cage.open(game)
                if self.kind != "normal":
                    self.explode(game)
                self.alive = False
                return
        for enemy in game.enemies[:]:
            if enemy.alive and rects_overlap(
                self.x - self.r, self.y - self.r, self.r * 2, self.r * 2,
                enemy.x, enemy.y, enemy.w, enemy.h
            ):
                if self.kind == "normal":
                    enemy.alive = False
                else:
                    self.explode(game)
                self.alive = False
                return
        if self.distance_left <= 0:
            self.explode(game)
            self.alive = False
            return
        if self.x < 0 or self.x > WORLD_W or self.y < 0 or self.y > WORLD_H:
            self.alive = False

    def draw(self, cam):
        sx = int(self.x - cam.x)
        sy = int(self.y - cam.y)
        if self.kind == "chaos":
            pyxel.circ(sx, sy, 4, COL_PURPLE)
            pyxel.circb(sx, sy, 4, COL_WHITE)
        elif self.kind == "freeze":
            pyxel.circ(sx, sy, 4, COL_SKY)
            pyxel.circb(sx, sy, 4, COL_WHITE)
        elif self.kind == "stink":
            pyxel.circ(sx, sy, 3, COL_LIME)
            pyxel.pset(sx, sy, COL_BLACK)
        else:
            pyxel.circ(sx, sy, 3, COL_WHITE)
            pyxel.pset(sx + 1, sy - 1, COL_CREAM)


class FarmerArrow:
    def __init__(self, x, y, dx, dy):
        self.x = x
        self.y = y
        self.dx = dx
        self.dy = dy
        self.speed = ARROW_SPEED
        self.alive = True
        self.w = 10
        self.h = 4

    def rect(self):
        return self.x - 5, self.y - 2, 10, 4

    def update(self, game):
        self.x += self.dx * self.speed
        self.y += self.dy * self.speed
        if game.point_hits_solid(self.x, self.y):
            self.alive = False
            return
        rx, ry, rw, rh = self.rect()
        if rects_overlap(rx, ry, rw, rh, game.player.x, game.player.y, game.player.w, game.player.h):
            self.alive = False
            game.state = STATE_GAMEOVER
            return
        if self.x < -10 or self.x > WORLD_W + 10 or self.y < -10 or self.y > WORLD_H + 10:
            self.alive = False

    def draw(self, cam):
        sx = int(self.x - cam.x)
        sy = int(self.y - cam.y)
        tx = int(sx + self.dx * 5)
        ty = int(sy + self.dy * 5)
        bx = int(sx - self.dx * 4)
        by = int(sy - self.dy * 4)
        pyxel.line(bx, by, tx, ty, COL_BROWN)
        px = int(tx + self.dx * 3)
        py = int(ty + self.dy * 3)
        side_x = -self.dy
        side_y = self.dx
        pyxel.tri(tx, ty, int(px + side_x * 2), int(py + side_y * 2), int(px - side_x * 2), int(py - side_y * 2), COL_YELLOW)
        feather_x = int(bx - self.dx * 2)
        feather_y = int(by - self.dy * 2)
        pyxel.line(bx, by, int(feather_x + side_x * 2), int(feather_y + side_y * 2), COL_RED)
        pyxel.line(bx, by, int(feather_x - side_x * 2), int(feather_y - side_y * 2), COL_RED)


class Enemy(ActorBase):
    def __init__(self, x, y, enemy_type, level_index):
        super().__init__()
        self.x = x
        self.y = y
        self.type = enemy_type
        self.alive = True
        self.w = 12
        self.h = 12
        self.dx = 0
        self.dy = 0
        self.look_dx = 1
        self.look_dy = 0
        self.state = "patrol"
        self.direction_timer = random.randint(12, 40)
        self.lost_timer = 0
        self.bark_timer = 0
        self.seen_player_last = False
        self.freeze_timer = 0
        self.confused_timer = 0
        difficulty = level_index * 0.12
        if self.type == "farmer":
            self.base_speed = BASE_FARMER_SPEED + difficulty
            self.detect_radius = 120 + level_index * 8
            self.chase_memory = 110 + level_index * 15
        else:
            self.base_speed = BASE_DOG_SPEED + difficulty
            self.detect_radius = 135 + level_index * 8
            self.chase_memory = 125 + level_index * 15

    def current_speed(self, game):
        s = self.base_speed
        if game.alarm_timer > 0:
            s *= ALARM_SPEED_MULT
        if self.freeze_timer > 0:
            s *= 0.22
        return s

    def center_x(self):
        return self.x + self.w / 2

    def center_y(self):
        return self.y + self.h / 2

    def random_patrol_direction(self):
        self.dx, self.dy = random.choice([(1, 0), (-1, 0), (0, 1), (0, -1)])

    def move_with_collision(self, dx, dy, game):
        new_x = self.x + dx
        new_y = self.y + dy
        moved = False
        if not game.collides_with_solid_actor(new_x, self.y, self.w, self.h, actor_kind="normal"):
            self.x = new_x
            moved = True
        if not game.collides_with_solid_actor(self.x, new_y, self.w, self.h, actor_kind="normal"):
            self.y = new_y
            moved = True
        self.x = clamp(self.x, 0, WORLD_W - self.w)
        self.y = clamp(self.y, 0, WORLD_H - self.h)
        if not moved:
            self.random_patrol_direction()

    def can_see_target(self, target, game):
        if target == game.player and game.player.is_invisible():
            return False
        d = dist(self.center_x(), self.center_y(), target.center_x(), target.center_y())
        if d > self.detect_radius:
            return False
        return game.line_of_sight_clear(self.center_x(), self.center_y(), target.center_x(), target.center_y())

    def choose_target(self, game):
        candidates = [game.player] + game.allies
        best = None
        best_score = 999999
        for target in candidates:
            if self.can_see_target(target, game):
                d = dist(self.center_x(), self.center_y(), target.center_x(), target.center_y())
                if d < best_score:
                    best = target
                    best_score = d
        return best

    def turn_toward_player(self, game):
        vx = game.player.center_x() - self.center_x()
        vy = game.player.center_y() - self.center_y()
        l = math.sqrt(vx * vx + vy * vy)
        if l == 0:
            self.look_dx, self.look_dy = 1, 0
        else:
            self.look_dx = vx / l
            self.look_dy = vy / l

    def fire_arrow(self, game):
        if self.type != "farmer":
            return
        self.turn_toward_player(game)
        game.farmer_arrows.append(FarmerArrow(self.center_x(), self.center_y(), self.look_dx, self.look_dy))

    def update(self, game):
        self.update_slip_timers()
        if self.bark_timer > 0:
            self.bark_timer -= 1
        if self.freeze_timer > 0:
            self.freeze_timer -= 1
        if self.confused_timer > 0:
            self.confused_timer -= 1
        if not self.alive:
            return
        self.try_slip_on_splats(game, self.dx if self.dx != 0 else 1, self.dy)
        if self.is_stunned():
            self.move_with_collision(self.slip_vx, self.slip_vy, game)
            return
        speed = self.current_speed(game)
        for cloud in game.stink_clouds:
            if dist(self.center_x(), self.center_y(), cloud.x, cloud.y) <= cloud.radius:
                self.confused_timer = max(self.confused_timer, 28)
                vx = self.center_x() - cloud.x
                vy = self.center_y() - cloud.y
                l = math.sqrt(vx * vx + vy * vy)
                if l != 0:
                    vx /= l
                    vy /= l
                self.dx = vx
                self.dy = vy
                self.look_dx = vx
                self.look_dy = vy
                self.move_with_collision(vx * speed * 1.35, vy * speed * 1.35, game)
                return
        target = self.choose_target(game)
        sees_player_now = (target == game.player)
        if sees_player_now:
            game.trigger_alarm()
        if self.type == "dog" and sees_player_now and not self.seen_player_last:
            self.bark_timer = 24
        self.seen_player_last = sees_player_now
        if target:
            self.state = "chase"
            self.lost_timer = self.chase_memory
        else:
            if self.lost_timer > 0:
                self.lost_timer -= 1
                self.state = "chase"
                target = game.player
            else:
                self.state = "patrol"
        if self.state == "chase" and target:
            px = target.center_x()
            py = target.center_y()
            ex = self.center_x()
            ey = self.center_y()
            vx = px - ex
            vy = py - ey
            l = math.sqrt(vx * vx + vy * vy)
            if l != 0:
                vx /= l
                vy /= l
            self.dx = vx
            self.dy = vy
            self.look_dx = vx
            self.look_dy = vy
            self.move_with_collision(vx * speed, vy * speed, game)
        else:
            self.direction_timer -= 1
            if self.direction_timer <= 0 or self.confused_timer > 0:
                self.random_patrol_direction()
                self.direction_timer = random.randint(12, 40)
            if abs(self.dx) > 0.01 or abs(self.dy) > 0.01:
                self.look_dx = self.dx
                self.look_dy = self.dy
            self.move_with_collision(self.dx * speed, self.dy * speed, game)

    def draw(self, cam):
        sx = int(self.x - cam.x)
        sy = int(self.y - cam.y)
        if self.type == "farmer":
            pyxel.line(sx + 4, sy + 8, sx + 4, sy + 12, COL_BLACK)
            pyxel.line(sx + 7, sy + 8, sx + 7, sy + 12, COL_BLACK)
            pyxel.rect(sx + 3, sy + 5, 6, 5, COL_SKY if self.freeze_timer <= 0 else COL_LIGHTBLUE)
            pyxel.line(sx + 2, sy + 6, sx + 0, sy + 8, COL_PEACH)
            pyxel.line(sx + 9, sy + 6, sx + 11, sy + 8, COL_PEACH)
            pyxel.circ(sx + 6, sy + 3, 3, COL_PEACH)
            pyxel.rect(sx + 3, sy + 0, 6, 2, COL_BROWN)
            pyxel.rect(sx + 2, sy + 2, 8, 1, COL_BROWN)
            pyxel.line(sx + 6, sy + 5, sx + 10, sy + 5, COL_YELLOW)
            eye_dx = int(self.look_dx * 1)
            eye_dy = int(self.look_dy * 1)
            pyxel.pset(sx + 5 + eye_dx, sy + 3 + eye_dy, COL_BLACK)
            pyxel.pset(sx + 7 + eye_dx, sy + 3 + eye_dy, COL_BLACK)
        else:
            body = COL_BROWN if self.freeze_timer <= 0 else COL_LIGHTBLUE
            pyxel.rect(sx + 5, sy + 4, 7, 5, body)
            pyxel.rect(sx + 7, sy + 2, 5, 2, body)
            pyxel.rect(sx + 8, sy + 0, 1, 2, body)
            pyxel.rect(sx + 10, sy + 0, 1, 2, body)
            pyxel.rect(sx + 12, sy + 3, 1, 2, body)
            pyxel.circ(sx + 3, sy + 6, 2, body)
            pyxel.line(sx + 13, sy + 3, sx + 13, sy + 4, COL_BLACK)
            eye_dx = int(self.look_dx * 1)
            eye_dy = int(self.look_dy * 1)
            pyxel.pset(sx + 10 + eye_dx, sy + 3 + eye_dy, COL_BLACK)
            pyxel.line(sx + 3, sy + 9, sx + 3, sy + 11, COL_BLACK)
            pyxel.line(sx + 5, sy + 9, sx + 5, sy + 11, COL_BLACK)
            pyxel.line(sx + 10, sy + 9, sx + 10, sy + 11, COL_BLACK)
            pyxel.line(sx + 8, sy + 9, sx + 8, sy + 11, COL_BLACK)
            pyxel.line(sx + 0, sy + 6, sx + -2, sy + 4, COL_BLACK)
            if self.bark_timer > 0 and pyxel.frame_count % 6 < 4:
                pyxel.text(sx - 2, sy - 6, "WOOF!", COL_RED)
        if self.is_stunned() and pyxel.frame_count % 8 < 4:
            pyxel.text(sx + 1, sy - 6, "SLIP!", COL_YELLOW)
        elif self.freeze_timer > 0 and pyxel.frame_count % 8 < 4:
            pyxel.text(sx + 1, sy - 6, "COLD!", COL_SKY)


class Game:
    def __init__(self):
        pyxel.init(SCREEN_W, SCREEN_H, title="Escape the Chicken Farm", fps=FPS)
        pyxel.load("res2.pyxres")
        self.camera = Camera()
        self.level_index = 0
        self.state = "menu"
        self.world_clock_frames = 0
        self.level_start_clock_frames = 0
        self.last_level_seconds = 0
        self.last_level_stars = 1
        self.saved_freeze_eggs = 0
        self.saved_stink_eggs = 0
        self.current_music_mode = None
        self.prev_player_slip = 0
        self.prev_alarm_digit = None
        self.prev_enemy_count = 0
        self.prev_enemy_slips = {}
        self.reset_level(keep_inventory=False)
        self.init_audio()
        self.update_music()
        pyxel.run(self.update, self.draw)

    def init_audio(self):
        # =================================================================
        # TAG MUSIK: "Old MacDonald Had a Farm" - so nah am Original wie
        # in Pyxel möglich. Melodie in G-Dur:
        #
        # "Old Mac-Don-ald  had  a   farm" -> G G G D E E D (lang)
        # "E  I  E  I  O"                 -> B B A A G (lang)
        # "And on  that farm he  had a   chick-en" -> D D D D G G E E D (lang)
        # "E  I  E  I  O"                 -> B B A A G (lang)
        # "With a  cluck cluck here..."   -> G G G G G G G
        # "And a  cluck cluck there"      -> G G G G G G G
        # "Here a  cluck, there a  cluck" -> G A G G A G
        # "Ev-'ry-where a cluck cluck"    -> G G A G G
        # "Old Mac-Don-ald had  a   farm" -> G G G D E E D
        # "E  I  E  I  O"                 -> B B A A G
        #
        # Pyxel Noten: g3=G3, a3=A3, b3=B3, d3=D3(tief), e3=E3, d4=D4
        # =================================================================

        # Kanal 0: Hauptmelodie Old MacDonald (Triangle - hell & klar)
        pyxel.sounds[20].set(
            # "Old Mac-Don-ald had a farm"
            "g3 g3 g3 d3 e3 e3 d3 r "
            # "E-I-E-I-O"
            "b3 b3 a3 a3 g3 r r r "
            # "And on that farm he had a chicken"
            "d4 d4 d4 d4 g3 g3 e3 e3 d3 r r r "
            # "E-I-E-I-O"
            "b3 b3 a3 a3 g3 r r r "
            # "With a cluck cluck here, and a cluck cluck there"
            "g3 g3 g3 g3 g3 g3 g3 r "
            "g3 g3 g3 g3 g3 g3 g3 r "
            # "Here a cluck, there a cluck, everywhere a cluck cluck"
            "g3 a3 g3 r g3 a3 g3 r "
            "g3 g3 a3 a3 g3 r r r "
            # "Old MacDonald had a farm, E-I-E-I-O"
            "g3 g3 g3 d3 e3 e3 d3 r "
            "b3 b3 a3 a3 g3 r r r",
            "t",
            "4444444444444444444444444444444444444444444444444444444444444444444444444444444444",
            "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn",
            18
        )

        # Kanal 1: Bass-Linie (Square) - G und D wechselnd
        pyxel.sounds[21].set(
            "g1 r r g1 r r d1 r "
            "g1 r d1 r g1 r r r "
            "g1 r r d1 r g1 r d1 "
            "g1 r d1 r g1 r r r "
            "c2 r g1 r c2 r g1 r "
            "c2 r g1 r d1 r g1 r "
            "g1 r d1 r g1 r d1 r "
            "g1 r d1 r g1 r r r "
            "g1 r r g1 r r d1 r "
            "g1 r d1 r g1 r r r",
            "s",
            "1111111111111111111111111111111111111111111111111111111111111111111111111111111111",
            "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn",
            16
        )

        # Kanal 2: Harmonie (Pulse) - Akkorde
        pyxel.sounds[22].set(
            "b2 d3 r b2 g2 b2 r r "
            "d3 g3 r d3 g2 r r r "
            "d3 f#3 r d3 g3 b3 r r "
            "d3 g3 r a2 g2 r r r "
            "e3 g3 r e3 g3 r e3 r "
            "e3 g3 r e3 a3 r d3 r "
            "b2 d3 r r b2 d3 r r "
            "g2 b2 d3 r g2 r r r "
            "b2 d3 r b2 g2 b2 r r "
            "d3 g3 r d3 g2 r r r",
            "p",
            "111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
            "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn",
            14
        )

        pyxel.sounds[23].set(
            "g2 r r r g2 r r r "
            "g2 r r r g2 r r r "
            "g2 r r r g2 r r r "
            "g2 r r r g2 r r r "
            "g2 r g2 r g2 r g2 r "
            "g2 r g2 r g2 r g2 r "
            "g2 r r r g2 r r r "
            "g2 r r r g2 r r r "
            "g2 r r r g2 r r r "
            "g2 r r r g2 r r r",
            "n",
            "1111111111111111111111111111111111111111111111111111111111111111111111111111111111",
            "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn",
            14
        )

        pyxel.musics[0].set([20], [21], [22], [23])

        # =================================================================
        # NACHT MUSIK: "Old MacDonald" in E-Moll - spooky & düster
        # Gleiche Melodie, aber Moll-Tonart macht sie gruselig
        # E-Moll: e3 e3 e3 b2 c3 c3 b2 | g3 g3 f#3 f#3 e3
        # =================================================================

        # Kanal 0: Moll-Melodie (Triangle, langsamer, gespenstisch)
        pyxel.sounds[24].set(
            # "Old Mac-Don-ald had a farm" in Moll
            "e3 e3 e3 b2 c3 c3 b2 r "
            # "E-I-E-I-O" in Moll
            "g3 g3 f#3 f#3 e3 r r r "
            # Strophe 2
            "b3 b3 b3 b3 e3 e3 c3 c3 b2 r r r "
            # "E-I-E-I-O" in Moll
            "g3 g3 f#3 f#3 e3 r r r "
            # "cluck cluck" Teil - düsterer
            "e3 e3 e3 e3 e3 f#3 g3 r "
            "e3 e3 e3 e3 e3 f#3 g3 r "
            "e3 f#3 e3 r e3 f#3 e3 r "
            "e3 e3 f#3 g3 e3 r r r "
            # Schluss-Strophe
            "e3 e3 e3 b2 c3 c3 b2 r "
            "g3 g3 f#3 f#3 e3 r r r",
            "t",
            "4444444444444444444444444444444444444444444444444444444444444444444444444444444444",
            "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn",
            16
        )

        # Kanal 1: Tiefer Moll-Bass
        pyxel.sounds[25].set(
            "e1 r r e1 r r b0 r "
            "e1 r b0 r e1 r r r "
            "e1 r r b0 r e1 r b0 "
            "e1 r b0 r e1 r r r "
            "a1 r e1 r a1 r e1 r "
            "a1 r e1 r b0 r e1 r "
            "e1 r b0 r e1 r b0 r "
            "e1 r b0 r e1 r r r "
            "e1 r r e1 r r b0 r "
            "e1 r b0 r e1 r r r",
            "s",
            "1111111111111111111111111111111111111111111111111111111111111111111111111111111111",
            "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn",
            14
        )

        # Kanal 2: Moll-Harmonie, dissonant
        pyxel.sounds[26].set(
            "g2 b2 r g2 e2 g2 r r "
            "b2 e3 r b2 e2 r r r "
            "b2 d3 r b2 e3 g3 r r "
            "b2 e3 r f#2 e2 r r r "
            "c3 e3 r c3 e3 r c3 r "
            "c3 e3 r c3 f#3 r b2 r "
            "g2 b2 r r g2 b2 r r "
            "e2 g2 b2 r e2 r r r "
            "g2 b2 r g2 e2 g2 r r "
            "b2 e3 r b2 e2 r r r",
            "p",
            "111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
            "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn",
            12
        )

        # Kanal 3: Langsamer, schwerer Nacht-Beat
        pyxel.sounds[27].set(
            "e2 r r r r r r r "
            "e2 r r r r r r r "
            "e2 r r r r r r r "
            "e2 r r r r r r r "
            "e2 r r r e2 r r r "
            "e2 r r r e2 r r r "
            "e2 r r r r r r r "
            "e2 r r r r r r r "
            "e2 r r r r r r r "
            "e2 r r r r r r r",
            "n",
            "1111111111111111111111111111111111111111111111111111111111111111111111111111111111",
            "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnf",
            10
        )
        pyxel.musics[1].set([24], [25], [26], [27])

        # =================================================================
        # SFX - Komplett neu: kürzer, markanter, charaktervoller
        # =================================================================

        # 40 = Normales Ei - kurzes fröhliches "Plop!"
        pyxel.sounds[40].set(
            "g4 b4 d4",
            "t",
            "754",
            "nnn",
            30
        )

        # 41 = Chaos-Ei - chaotisches Rumpeln
        pyxel.sounds[41].set(
            "c3 g4 c3 g4 c2",
            "n",
            "75375",
            "fffff",
            22
        )

        # 42 = Frost-Ei - kristallines Klingeln absteigend
        pyxel.sounds[42].set(
            "e4 b4 g4 d4",
            "t",
            "7654",
            "nnnn",
            20
        )

        # 43 = Stink-Ei - tiefes schleimiges Gluckern
        pyxel.sounds[43].set(
            "g1 c2 g1 c1",
            "n",
            "7654",
            "nnnn",
            18
        )

        # 44 = Gegner besiegt - lustiger "Boing"-Fanfare
        pyxel.sounds[44].set(
            "c4 e4 g4 c4",
            "t",
            "5555",
            "nnnn",
            28
        )

        # 45 = Speed/Invis Powerup - aufsteigendes Pling
        pyxel.sounds[45].set(
            "c4 e4 g4 b4 c4",
            "t",
            "55555",
            "nnnnn",
            30
        )

        # 46 = Spezial-Pickup (Frost/Stink-Ei) - magisches Funkeln
        pyxel.sounds[46].set(
            "g4 d4 g4 d4",
            "p",
            "6767",
            "nnnn",
            28
        )

        # 47 = Alarm - kurzer harter Warnton
        pyxel.sounds[47].set(
            "c4 r c4",
            "n",
            "777",
            "fff",
            20
        )

        # 48 = Level clear - "E-I-E-I-O!" Fanfare (Old MacDonald Ending)
        pyxel.sounds[48].set(
            "g4 r a4 r g4 r e4 r g4",
            "t",
            "777777777",
            "nnnnnnnnn",
            24
        )

        # 49 = Game over - trauriges Huhn, kurz absteigend
        pyxel.sounds[49].set(
            "g3 e3 c3 a2 g2",
            "n",
            "77777",
            "fffff",
            20
        )

        # 50 = Slip/Ausrutschen - komisches Squeak
        pyxel.sounds[50].set(
            "c4 g4 c4",
            "p",
            "765",
            "nnn",
            18
        )

        # 51 = Countdown Tick - tiefes Klick
        pyxel.sounds[51].set(
            "c3 c4",
            "p",
            "75",
            "nn",
            26
        )

        # 52 = Farmer schiesst - pfeifendes Swoosh
        pyxel.sounds[52].set(
            "g4 d4 g3",
            "n",
            "765",
            "fff",
            18
        )

    def play_bgm(self, mode):
        if mode == "day":
            pyxel.playm(0, loop=True)
        else:
            pyxel.playm(1, loop=True)

    def play_sfx(self, snd):
        for ch in [1, 2, 3]:
            if pyxel.play_pos(ch) is None:
                pyxel.play(ch, snd)
                return
        pyxel.play(3, snd)

    def update_music(self):
        _, darkness = self.get_light_values()
        if darkness < 0.18:
            target_mode = "day"
        else:
            target_mode = "night"
        if self.current_music_mode != target_mode:
            self.current_music_mode = target_mode
            self.play_bgm(target_mode)

    def get_clock_float_hour(self):
        return (12 + self.world_clock_frames / CLOCK_FRAMES_PER_HOUR) % 24

    def get_clock_hour_minute(self):
        total_hours = 12 + (self.world_clock_frames // CLOCK_FRAMES_PER_HOUR)
        hour = total_hours % 24
        rem = self.world_clock_frames % CLOCK_FRAMES_PER_HOUR
        minute = int(rem / CLOCK_FRAMES_PER_HOUR * 60)
        return hour, minute

    def get_time_string(self):
        h, m = self.get_clock_hour_minute()
        return f"{h:02d}:{m:02d}"

    def get_light_values(self):
        h = self.get_clock_float_hour()
        if 6 <= h < 19:
            darkness = 0.0
            warmth = 0.0
        elif 19 <= h < 23:
            t = (h - 15) / 6.0
            darkness = 0.05 + 0.35 * t
            warmth = 0.10 + 0.70 * t
        elif h >= 23 or h < 3:
            if h >= 21:
                t = (h - 21) / 6.0
            else:
                t = (h + 3) / 6.0
            darkness = 0.45 + 0.45 * t
            warmth = 0.65 - 0.20 * t
        else:
            t = (h - 3) / 3.0
            darkness = 0.90 - 0.80 * t
            warmth = 0.40 - 0.30 * t
        return clamp(warmth, 0, 1), clamp(darkness, 0, 1)

    def get_level_seconds(self):
        return int((self.world_clock_frames - self.level_start_clock_frames) / FPS)

    def compute_stars(self, level_index, seconds):
        three_star, two_star = STAR_THRESHOLDS[level_index]
        if seconds <= three_star:
            return 3
        elif seconds <= two_star:
            return 2
        return 1

    def reset_level(self, keep_inventory=True):
        if keep_inventory and hasattr(self, "player") and self.player is not None:
            self.saved_freeze_eggs = self.player.freeze_eggs
            self.saved_stink_eggs = self.player.stink_eggs
        self.map_data = []
        self.enemies = []
        self.eggs = []
        self.farmer_arrows = []
        self.boosts = []
        self.splats = []
        self.cages = []
        self.allies = []
        self.freeze_bursts = []
        self.stink_clouds = []
        self.special_pickups = []
        self.player = None
        self.exit_tile = None
        self.message_timer = 0
        self.last_boost_name = ""
        self.next_boost_spawn = random.randint(BOOST_SPAWN_INTERVAL_MIN, BOOST_SPAWN_INTERVAL_MAX)
        self.next_special_spawn = random.randint(SPECIAL_PICKUP_INTERVAL_MIN, SPECIAL_PICKUP_INTERVAL_MAX)
        self.alarm_timer = 0
        self.global_shot_timer = FARMER_SHOT_INTERVAL
        self.shot_flash_timer = 0
        self.level_start_clock_frames = self.world_clock_frames
        self.load_map()
        self.spawn_extra_enemies()
        self.player.freeze_eggs = self.saved_freeze_eggs
        self.player.stink_eggs = self.saved_stink_eggs
        self.camera.update(self.player.center_x(), self.player.center_y())
        self.prev_player_slip = 0
        self.prev_alarm_digit = None
        self.prev_enemy_count = len(self.enemies)
        self.prev_enemy_slips = {id(enemy): enemy.slip_timer for enemy in self.enemies}

    def next_level(self):
        self.saved_freeze_eggs = self.player.freeze_eggs
        self.saved_stink_eggs = self.player.stink_eggs
        if self.level_index < len(LEVELS) - 1:
            self.level_index += 1
            self.reset_level(keep_inventory=False)
            self.player.freeze_eggs = self.saved_freeze_eggs
            self.player.stink_eggs = self.saved_stink_eggs
            self.state = STATE_PLAYING
        else:
            self.state = STATE_FINAL_WIN

    def trigger_alarm(self):
        was_off = self.alarm_timer <= 0
        self.alarm_timer = max(self.alarm_timer, ALARM_DURATION)
        if was_off:
            self.play_sfx(47)

    def load_map(self):
        self.map_data = [list(row) for row in LEVELS[self.level_index]]
        for ty in range(MAP_H):
            for tx in range(MAP_W):
                cell = self.map_data[ty][tx]
                px = tx * TILE + 1
                py = ty * TILE + 1
                if cell == "S":
                    self.player = Player(px + 1, py + 1)
                    self.map_data[ty][tx] = "."
                elif cell == "E":
                    self.exit_tile = (tx, ty)
                elif cell == "F":
                    self.enemies.append(Enemy(px + 1, py + 1, "farmer", self.level_index))
                    self.map_data[ty][tx] = "."
                elif cell == "D":
                    self.enemies.append(Enemy(px + 1, py + 1, "dog", self.level_index))
                    self.map_data[ty][tx] = "."
                elif cell == "Q":
                    self.cages.append(Cage(tx * TILE + 1, ty * TILE + 1))
                    self.map_data[ty][tx] = "."

    def spawn_extra_enemies(self):
        farmer_count = [2, 3, 5, 7, 8][self.level_index]
        dog_count = [1, 3, 4, 6, 6][self.level_index]
        empty_tiles = []
        for ty in range(MAP_H):
            for tx in range(MAP_W):
                if self.map_data[ty][tx] == ".":
                    px = tx * TILE + 2
                    py = ty * TILE + 2
                    if dist(px, py, self.player.x, self.player.y) > 95:
                        empty_tiles.append((tx, ty))
        random.shuffle(empty_tiles)
        for _ in range(farmer_count):
            if not empty_tiles:
                break
            tx, ty = empty_tiles.pop()
            self.enemies.append(Enemy(tx * TILE + 2, ty * TILE + 2, "farmer", self.level_index))
        for _ in range(dog_count):
            if not empty_tiles:
                break
            tx, ty = empty_tiles.pop()
            self.enemies.append(Enemy(tx * TILE + 2, ty * TILE + 2, "dog", self.level_index))

    def tile_at_pixel(self, px, py):
        tx = int(px // TILE)
        ty = int(py // TILE)
        if tx < 0 or ty < 0 or tx >= MAP_W or ty >= MAP_H:
            return "#"
        return self.map_data[ty][tx]

    def tile_is_solid_for_actor(self, tile, actor_kind="normal"):
        if actor_kind == "jumper":
            return tile in ["#"]
        return tile in ["#", "H", "C"]

    def point_hits_solid(self, px, py):
        return self.tile_at_pixel(px, py) in ["#", "H", "C"]

    def collides_with_solid_actor(self, x, y, w, h, actor_kind="normal"):
        corners = [(x, y), (x + w - 1, y), (x, y + h - 1), (x + w - 1, y + h - 1)]
        for px, py in corners:
            if self.tile_is_solid_for_actor(self.tile_at_pixel(px, py), actor_kind):
                return True
        return False

    def line_of_sight_clear(self, x1, y1, x2, y2):
        steps = int(max(abs(x2 - x1), abs(y2 - y1)) / 3)
        if steps <= 0:
            return True
        for i in range(1, steps):
            t = i / steps
            sx = x1 + (x2 - x1) * t
            sy = y1 + (y2 - y1) * t
            tile = self.tile_at_pixel(sx, sy)
            if tile in ["#", "H", "C"]:
                return False
        return True

    def is_walkable_tile(self, tx, ty, jumper=False):
        if tx < 0 or ty < 0 or tx >= MAP_W or ty >= MAP_H:
            return False
        cell = self.map_data[ty][tx]
        if jumper:
            return cell != "#"
        return cell not in ["#", "H", "C"]

    def find_path(self, sx, sy, ex, ey, jumper=False):
        if not self.is_walkable_tile(sx, sy, jumper=jumper):
            return None
        if not self.is_walkable_tile(ex, ey, jumper=jumper):
            return None
        q = deque()
        q.append((sx, sy))
        came = {(sx, sy): None}
        dirs = [(1, 0), (-1, 0), (0, 1), (0, -1)]
        while q:
            x, y = q.popleft()
            if (x, y) == (ex, ey):
                break
            for dx, dy in dirs:
                nx, ny = x + dx, y + dy
                if (nx, ny) not in came and self.is_walkable_tile(nx, ny, jumper=jumper):
                    came[(nx, ny)] = (x, y)
                    q.append((nx, ny))
        if (ex, ey) not in came:
            return None
        path = []
        cur = (ex, ey)
        while cur is not None:
            path.append(cur)
            cur = came[cur]
        path.reverse()
        return path

    def spawn_boost(self):
        empty_spots = []
        for ty in range(MAP_H):
            for tx in range(MAP_W):
                if self.map_data[ty][tx] == ".":
                    px = tx * TILE + 2
                    py = ty * TILE + 2
                    if dist(px, py, self.player.x, self.player.y) > 40:
                        empty_spots.append((px, py))
        if empty_spots:
            bx, by = random.choice(empty_spots)
            boost_type = random.choice(["speed", "invisible", "speed"])
            self.boosts.append(Boost(bx, by, boost_type))

    def spawn_special_pickup(self):
        empty_spots = []
        for ty in range(MAP_H):
            for tx in range(MAP_W):
                if self.map_data[ty][tx] == ".":
                    px = tx * TILE + 2
                    py = ty * TILE + 2
                    if dist(px, py, self.player.x, self.player.y) > 35:
                        empty_spots.append((px, py))
        if empty_spots:
            bx, by = random.choice(empty_spots)
            kind = random.choice(["freeze", "stink", "freeze", "stink", "freeze"])
            self.special_pickups.append(SpecialPickup(bx, by, kind))

    def apply_boost(self, boost_type):
        self.last_boost_name = boost_type
        self.message_timer = 75
        self.play_sfx(45)
        if boost_type == "speed":
            self.player.speed_boost_timer = BOOST_DURATION
        elif boost_type == "invisible":
            self.player.invisible_timer = BOOST_DURATION

    def freeze_burst(self, x, y):
        self.freeze_bursts.append(FreezeBurst(x, y, radius=30))
        for enemy in self.enemies:
            if enemy.alive and dist(x, y, enemy.center_x(), enemy.center_y()) <= 30:
                enemy.freeze_timer = max(enemy.freeze_timer, FREEZE_STUN_FRAMES)

    def stink_cloud(self, x, y):
        self.stink_clouds.append(StinkCloud(x, y))

    def shoot_egg(self, kind="normal"):
        if self.player.is_stunned():
            return
        dx = self.player.facing_dx
        dy = self.player.facing_dy
        if dx == 0 and dy == 0:
            dx = 1
        start_x = self.player.center_x()
        start_y = self.player.center_y()
        if kind == "chaos":
            if self.player.chaos_egg_cooldown <= 0:
                self.eggs.append(Egg(start_x, start_y, dx, dy, kind="chaos"))
                self.player.chaos_egg_cooldown = CHAOS_EGG_COOLDOWN_FRAMES
                self.play_sfx(41)
        elif kind == "freeze":
            if self.player.freeze_eggs > 0:
                self.eggs.append(Egg(start_x, start_y, dx, dy, kind="freeze"))
                self.player.freeze_eggs -= 1
                self.play_sfx(42)
        elif kind == "stink":
            if self.player.stink_eggs > 0:
                self.eggs.append(Egg(start_x, start_y, dx, dy, kind="stink"))
                self.player.stink_eggs -= 1
                self.play_sfx(43)
        else:
            if self.player.egg_cooldown <= 0:
                self.eggs.append(Egg(start_x, start_y, dx, dy, kind="normal"))
                self.player.egg_cooldown = self.player.current_egg_cooldown()
                self.play_sfx(40)

    def explode_chaos_egg(self, x, y, radius=CHAOS_RADIUS):
        killed_any = False
        for enemy in self.enemies:
            if enemy.alive and dist(x, y, enemy.center_x(), enemy.center_y()) <= radius:
                enemy.alive = False
                killed_any = True
        if killed_any:
            self.play_sfx(44)

    def get_countdown_digit(self):
        if self.shot_flash_timer > 0:
            return 0
        if self.global_shot_timer <= FARMER_COUNTDOWN_FRAMES:
            return max(1, math.ceil(self.global_shot_timer / FPS))
        return None

    def update(self):
        if self.state == "menu":
            if pyxel.btnp(pyxel.KEY_SPACE):
                self.reset_level(keep_inventory=False)
                self.state = STATE_PLAYING
            return

        if self.state == STATE_LEVEL_CLEAR:
            if pyxel.btnp(pyxel.KEY_RETURN) or pyxel.btnp(pyxel.KEY_SPACE):
                self.next_level()
            return

        if self.state == STATE_GAMEOVER:
            if pyxel.btnp(pyxel.KEY_R):
                self.reset_level(keep_inventory=True)
                self.state = STATE_PLAYING
            return

        if self.state == STATE_FINAL_WIN:
            if pyxel.btnp(pyxel.KEY_R):
                self.level_index = 0
                self.world_clock_frames = 0
                self.saved_freeze_eggs = 0
                self.saved_stink_eggs = 0
                self.reset_level(keep_inventory=False)
                self.state = STATE_PLAYING
            return

        self.world_clock_frames += 1
        self.update_music()

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

        self.player.update(self)

        if self.player.slip_timer > 0 and self.prev_player_slip <= 0:
            self.play_sfx(50)
        self.prev_player_slip = self.player.slip_timer

        self.global_shot_timer -= 1
        if self.global_shot_timer <= 0:
            shot_happened = False
            for enemy in self.enemies:
                if enemy.alive and enemy.type == "farmer":
                    enemy.fire_arrow(self)
                    shot_happened = True
            if shot_happened:
                self.play_sfx(52)
            self.global_shot_timer = FARMER_SHOT_INTERVAL
            self.shot_flash_timer = SHOT_FLASH_FRAMES

        cd = self.get_countdown_digit()
        if cd is not None and cd != 0 and cd != self.prev_alarm_digit:
            self.play_sfx(51)
        self.prev_alarm_digit = cd

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

        if pyxel.btnp(pyxel.KEY_SPACE):
            self.shoot_egg("normal")
        if pyxel.btnp(pyxel.KEY_X):
            self.shoot_egg("chaos")
        if pyxel.btnp(pyxel.KEY_Z):
            self.shoot_egg("freeze")
        if pyxel.btnp(pyxel.KEY_C):
            self.shoot_egg("stink")

        old_enemy_count = len([e for e in self.enemies if e.alive])

        for egg in self.eggs[:]:
            egg.update(self)
            if not egg.alive:
                self.eggs.remove(egg)

        for arrow in self.farmer_arrows[:]:
            arrow.update(self)
            if not arrow.alive:
                self.farmer_arrows.remove(arrow)

        for splat in self.splats[:]:
            splat.update()
            if not splat.alive:
                self.splats.remove(splat)

        for burst in self.freeze_bursts[:]:
            burst.update()
            if not burst.alive:
                self.freeze_bursts.remove(burst)

        for cloud in self.stink_clouds[:]:
            cloud.update()
            if not cloud.alive:
                self.stink_clouds.remove(cloud)

        for cage in self.cages:
            cage.update(self)

        for ally in self.allies:
            ally.update(self)

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

        for enemy in self.enemies:
            prev = self.prev_enemy_slips.get(id(enemy), 0)
            if enemy.slip_timer > 0 and prev <= 0:
                self.play_sfx(50)
            self.prev_enemy_slips[id(enemy)] = enemy.slip_timer

        self.enemies = [e for e in self.enemies if e.alive]
        self.prev_enemy_slips = {id(enemy): enemy.slip_timer for enemy in self.enemies}

        new_enemy_count = len(self.enemies)
        if new_enemy_count < old_enemy_count:
            self.play_sfx(44)

        for boost in self.boosts[:]:
            boost.update()
            if rects_overlap(
                self.player.x, self.player.y, self.player.w, self.player.h,
                boost.x, boost.y, boost.w, boost.h
            ):
                self.apply_boost(boost.type)
                self.boosts.remove(boost)
                continue
            if not boost.alive:
                self.boosts.remove(boost)

        for pickup in self.special_pickups[:]:
            pickup.update()
            if rects_overlap(
                self.player.x, self.player.y, self.player.w, self.player.h,
                pickup.x, pickup.y, pickup.w, pickup.h
            ):
                if pickup.kind == "freeze":
                    self.player.freeze_eggs += 1
                else:
                    self.player.stink_eggs += 1
                self.play_sfx(46)
                self.special_pickups.remove(pickup)
                continue
            if not pickup.alive:
                self.special_pickups.remove(pickup)

        self.next_boost_spawn -= 1
        if self.next_boost_spawn <= 0:
            self.spawn_boost()
            self.next_boost_spawn = random.randint(
                BOOST_SPAWN_INTERVAL_MIN, BOOST_SPAWN_INTERVAL_MAX
            )

        self.next_special_spawn -= 1
        if self.next_special_spawn <= 0:
            self.spawn_special_pickup()
            self.next_special_spawn = random.randint(
                SPECIAL_PICKUP_INTERVAL_MIN, SPECIAL_PICKUP_INTERVAL_MAX
            )

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

        for enemy in self.enemies:
            if rects_overlap(
                self.player.x, self.player.y, self.player.w, self.player.h,
                enemy.x, enemy.y, enemy.w, enemy.h
            ):
                self.play_sfx(49)
                self.state = STATE_GAMEOVER
                return

        exit_tx, exit_ty = self.exit_tile
        exit_px = exit_tx * TILE
        exit_py = exit_ty * TILE

        if rects_overlap(
            self.player.x, self.player.y, self.player.w, self.player.h,
            exit_px, exit_py, TILE, TILE
        ):
            self.last_level_seconds = self.get_level_seconds()
            self.last_level_stars = self.compute_stars(
                self.level_index, self.last_level_seconds
            )
            self.play_sfx(48)
            self.state = STATE_LEVEL_CLEAR
            return

        self.camera.update(self.player.center_x(), self.player.center_y())

    def draw_ground_tile(self, sx, sy, tx, ty, cell):
        warmth, darkness = self.get_light_values()

        if darkness < 0.12:
            grass_a = COL_LIME
            grass_b = COL_DARKGREEN
            soil_a = COL_LIME
            soil_b = COL_DARKGREEN
        elif darkness < 0.33:
            grass_a = COL_PURPLE
            grass_b = COL_DARKBLUE
            soil_a = COL_PURPLE
            soil_b = COL_DARKBLUE
        else:
            grass_a = COL_DARKBLUE
            grass_b = COL_BLACK
            soil_a = COL_DARKBLUE
            soil_b = COL_BLACK

        if warmth > 0.4 and darkness < 0.6:
            soil_b = COL_PURPLE
            grass_a = COL_BROWN

        base = grass_a if (tx + ty) % 2 == 0 else grass_b
        pyxel.rect(int(sx), int(sy), TILE, TILE, base)

        if cell == ".":
            dirt = soil_a if (tx + ty) % 2 == 0 else soil_b
            pyxel.rect(int(sx + 1), int(sy + 2), 14, 12, dirt)
            pyxel.pset(int(sx + 4), int(sy + 5), COL_CREAM)
            pyxel.pset(int(sx + 11), int(sy + 8), COL_YELLOW)
            pyxel.line(int(sx + 6), int(sy + 10), int(sx + 8), int(sy + 9), COL_ORANGE)

    def draw_tile(self, tx, ty, cell):
        x = tx * TILE - self.camera.x
        y = ty * TILE - self.camera.y

        if x < -TILE or y < -TILE or x > SCREEN_W or y > SCREEN_H:
            return

        self.draw_ground_tile(x, y, tx, ty, cell)

        if cell == "#":
            pyxel.rect(int(x), int(y), TILE, TILE, COL_BROWN)
            pyxel.line(int(x + 2), int(y), int(x + 2), int(y + 15), COL_BLACK)
            pyxel.line(int(x + 7), int(y), int(x + 7), int(y + 15), COL_BLACK)
            pyxel.line(int(x + 12), int(y), int(x + 12), int(y + 15), COL_BLACK)
            pyxel.line(int(x), int(y + 4), int(x + 15), int(y + 4), COL_BLACK)
            pyxel.line(int(x), int(y + 10), int(x + 15), int(y + 10), COL_BLACK)

        elif cell == "H":
            pyxel.rect(int(x + 1), int(y + 3), 14, 10, COL_YELLOW)
            pyxel.rectb(int(x + 1), int(y + 3), 14, 10, COL_ORANGE)
            pyxel.rect(int(x + 5), int(y + 3), 1, 10, COL_BROWN)
            pyxel.rect(int(x + 10), int(y + 3), 1, 10, COL_BROWN)

        elif cell == "C":
            pyxel.rect(int(x + 1), int(y + 1), 14, 14, COL_GRAY)
            pyxel.rectb(int(x + 1), int(y + 1), 14, 14, COL_WHITE)
            pyxel.line(int(x + 1), int(y + 1), int(x + 14), int(y + 14), COL_BLACK)
            pyxel.line(int(x + 14), int(y + 1), int(x + 1), int(y + 14), COL_BLACK)
            pyxel.line(int(x + 1), int(y + 7), int(x + 14), int(y + 7), COL_BLACK)

        elif cell == "E":
            pyxel.rect(int(x + 1), int(y + 1), 14, 14, COL_LIME)
            pyxel.rectb(int(x + 1), int(y + 1), 14, 14, COL_WHITE)
            pyxel.text(int(x + 3), int(y + 5), "EXIT", COL_BLACK)

    def draw_map(self):
        for ty in range(MAP_H):
            for tx in range(MAP_W):
                if (tx, ty) == self.exit_tile:
                    self.draw_tile(tx, ty, "E")
                else:
                    self.draw_tile(tx, ty, self.map_data[ty][tx])

    def draw_night_overlay(self):
        _, darkness = self.get_light_values()
        if darkness < 0.16:
            return

        px = int(self.player.center_x() - self.camera.x)
        py = int(self.player.center_y() - self.camera.y)
        near_radius = int(82 - darkness * 42)
        cone_radius = int(150 - darkness * 62)
        cone_half_angle = 1.05 - darkness * 0.45
        facing = self.player.facing_angle()
        step = 8

        for y in range(0, SCREEN_H, step):
            for x in range(0, SCREEN_W, step):
                cx = x + step // 2
                cy = y + step // 2
                d = dist(cx, cy, px, py)
                visible = False

                if d <= near_radius:
                    visible = True
                else:
                    ang = math.atan2(cy - py, cx - px)
                    if d <= cone_radius and angle_diff(ang, facing) <= cone_half_angle:
                        visible = True

                if not visible:
                    color = COL_DARKBLUE if darkness < 0.55 else COL_BLACK
                    pyxel.rect(x, y, step, step, color)

    def draw_stars(self, x, y, count):
        for i in range(3):
            col = COL_YELLOW if i < count else COL_GRAY
            ox = x + i * 18
            pyxel.tri(ox + 5, y, ox + 7, y + 5, ox + 10, y, col)
            pyxel.tri(ox + 2, y + 4, ox + 8, y + 4, ox + 5, y + 10, col)
            pyxel.tri(ox + 12, y + 4, ox + 6, y + 4, ox + 9, y + 10, col)

    def draw_analog_clock(self, cx, cy, radius):
        pyxel.circb(cx, cy, radius, COL_WHITE)
        pyxel.circ(cx, cy, 1, COL_WHITE)

        for i in range(12):
            ang = -math.pi / 2 + i * (2 * math.pi / 12)
            x1 = int(cx + math.cos(ang) * (radius - 1))
            y1 = int(cy + math.sin(ang) * (radius - 1))
            x2 = int(cx + math.cos(ang) * (radius - 3))
            y2 = int(cy + math.sin(ang) * (radius - 3))
            pyxel.line(x1, y1, x2, y2, COL_WHITE)

        hour, minute = self.get_clock_hour_minute()
        minute_ang = -math.pi / 2 + (minute / 60) * 2 * math.pi
        hour_ang = -math.pi / 2 + (((hour % 12) + minute / 60) / 12) * 2 * math.pi

        mx = int(cx + math.cos(minute_ang) * (radius - 2))
        my = int(cy + math.sin(minute_ang) * (radius - 2))
        hx = int(cx + math.cos(hour_ang) * (radius - 5))
        hy = int(cy + math.sin(hour_ang) * (radius - 5))

        pyxel.line(cx, cy, hx, hy, COL_RED)
        pyxel.line(cx, cy, mx, my, COL_RED)

    def draw_big_digit(self, x, y, digit, scale=8):
        patterns = {
            0: ["111", "101", "101", "101", "111"],
            1: ["010", "110", "010", "010", "111"],
            2: ["111", "001", "111", "100", "111"],
            3: ["111", "001", "111", "001", "111"],
        }

        pat = patterns.get(digit)
        if pat is None:
            return

        for row, line in enumerate(pat):
            for col, ch in enumerate(line):
                if ch == "1":
                    px = x + col * scale
                    py = y + row * scale
                    pyxel.rect(px, py, scale - 1, scale - 1, COL_YELLOW)
                    pyxel.rectb(px, py, scale - 1, scale - 1, COL_RED)

    def draw_countdown_overlay(self):
        cd = self.get_countdown_digit()
        if cd is None:
            return

        x = SCREEN_W // 2 - 12
        y = 34
        self.draw_big_digit(x, y, cd, scale=9)

        if cd == 0:
            pyxel.text(SCREEN_W // 2 - 16, y + 50, "SHOOT!", COL_RED)
        else:
            pyxel.text(SCREEN_W // 2 - 30, y + 50, "FARMERS SHOOT", COL_RED)

    def draw_ui(self):
        pyxel.rect(0, 0, SCREEN_W, 28, COL_BLACK)
        pyxel.line(0, 27, SCREEN_W, 27, COL_CREAM)

        egg_ready = "OK" if self.player.egg_cooldown <= 0 else str(max(1, math.ceil(self.player.egg_cooldown / FPS)))
        chaos_ready = "OK" if self.player.chaos_egg_cooldown <= 0 else str(max(1, math.ceil(self.player.chaos_egg_cooldown / FPS)))

        pyxel.text(4, 4, "LVL " + str(self.level_index + 1), COL_WHITE)
        pyxel.text(40, 4, self.get_time_string(), COL_YELLOW)
        pyxel.text(92, 4, "EGG " + egg_ready, COL_WHITE)
        pyxel.text(142, 4, "CHAOS " + chaos_ready, COL_WHITE)
        pyxel.text(4, 17, "Z FROST:" + str(self.player.freeze_eggs), COL_SKY)
        pyxel.text(84, 17, "C STINK:" + str(self.player.stink_eggs), COL_LIME)
        pyxel.text(170, 17, "Q " + str(len(self.allies)), COL_WHITE)

        if self.alarm_timer > 0 and pyxel.frame_count % 6 < 4:
            pyxel.text(198, 17, "ALARM!", COL_RED)

        self.draw_analog_clock(238, 13, 10)

    def draw_level_clear(self):
        pyxel.rect(30, 74, 196, 108, COL_BLACK)
        pyxel.rectb(30, 74, 196, 108, COL_WHITE)
        pyxel.text(78, 90, "LEVEL CLEARED!", COL_LIME)
        pyxel.text(58, 110, "Time: " + str(self.last_level_seconds) + " sec", COL_WHITE)
        pyxel.text(52, 126, "Rating:", COL_WHITE)
        self.draw_stars(104, 123, self.last_level_stars)
        pyxel.text(52, 154, "Press ENTER for next level", COL_WHITE)

    def draw_game_over(self):
        pyxel.rect(34, 86, 188, 72, COL_BLACK)
        pyxel.rectb(34, 86, 188, 72, COL_WHITE)
        pyxel.text(92, 104, "GAME OVER", COL_RED)
        pyxel.text(66, 124, "Press R to retry", COL_WHITE)

    def draw_final_win(self):
        pyxel.rect(24, 78, 208, 92, COL_BLACK)
        pyxel.rectb(24, 78, 208, 92, COL_WHITE)
        pyxel.text(86, 98, "YOU WON!", COL_LIME)
        pyxel.text(42, 118, "The chicken escaped all 5 levels", COL_WHITE)
        pyxel.text(54, 134, "and led a chicken rebellion!", COL_WHITE)
        pyxel.text(68, 152, "Press R to restart all", COL_WHITE)

    def draw(self):
        pyxel.cls(COL_BLACK)

        if self.state == "menu":
            pyxel.blt(0, 0, 2, 0, 0, 256, 256)
            if pyxel.frame_count % 40 < 20:
                pyxel.rect(70, 220, 116, 14, COL_BLACK)
                pyxel.rectb(70, 220, 116, 14, COL_WHITE)
                pyxel.text(83, 224, "PRESS SPACE TO START", COL_YELLOW)
            return

        _, darkness = self.get_light_values()
        if darkness < 0.12:
            bg = COL_DARKGREEN
        elif darkness < 0.3:
            bg = COL_BROWN
        elif darkness < 0.55:
            bg = COL_PURPLE
        elif darkness < 0.8:
            bg = COL_DARKBLUE
        else:
            bg = COL_BLACK

        pyxel.cls(bg)

        self.draw_map()

        for splat in self.splats:
            splat.draw(self.camera)
        for cloud in self.stink_clouds:
            cloud.draw(self.camera)
        for burst in self.freeze_bursts:
            burst.draw(self.camera)
        for cage in self.cages:
            cage.draw(self.camera)
        for boost in self.boosts:
            boost.draw(self.camera)
        for pickup in self.special_pickups:
            pickup.draw(self.camera)
        for egg in self.eggs:
            egg.draw(self.camera)
        for arrow in self.farmer_arrows:
            arrow.draw(self.camera)
        for ally in self.allies:
            ally.draw(self.camera)
        for enemy in self.enemies:
            enemy.draw(self.camera)

        self.player.draw(self.camera)
        self.draw_night_overlay()
        self.draw_ui()
        self.draw_countdown_overlay()

        if self.state == STATE_LEVEL_CLEAR:
            self.draw_level_clear()
        elif self.state == STATE_GAMEOVER:
            self.draw_game_over()
        elif self.state == STATE_FINAL_WIN:
            self.draw_final_win()


Game()