import pyxel
import random
import math

W, H = 512, 512
PW, PH = 28, 28
SCORE_SCALE = 2.5   # normalisiert Pixel-Hoehe auf Originalwerte

# Sprunghoehe = jmp^2 / (2*grav) >= 92px  (max Luecke 85px + Puffer)
CHARS = [
    {"name":"Hase",  "desc":"Ausgewogen",  "spd":6.0,"jmp":-18.0,"grav":1.80,"col":7, "acc":8, "egg_imm":False,"dbl":False},  # h=90px
    {"name":"Fuchs", "desc":"Schnell",     "spd":9.0,"jmp":-19.5,"grav":2.10,"col":9, "acc":10,"egg_imm":False,"dbl":False},  # h=91px
    {"name":"Vogel", "desc":"Doppelsprung","spd":5.0,"jmp":-16.0,"grav":1.30,"col":8, "acc":7, "egg_imm":False,"dbl":True },  # h=98px  rot/coral
    {"name":"Kaefer","desc":"Eier-immun",  "spd":5.0,"jmp":-20.0,"grav":2.20,"col":4, "acc":9, "egg_imm":True, "dbl":False},  # h=91px  braun
]


# â”€â”€ Zweite Klasse: Partikelsystem â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
class ParticleSystem:
    """Verwaltet Sprung-, Powerup- und Portalpartikel."""

    def __init__(self):
        self.particles = []

    def emit(self, x, y, count, colors, speed=5.0, life=35):
        if isinstance(colors, int):
            colors = [colors]
        for _ in range(count):
            angle = random.uniform(0, math.tau)
            spd = random.uniform(0.8, speed)
            self.particles.append({
                "x": float(x), "y": float(y),
                "vx": math.cos(angle) * spd,
                "vy": math.sin(angle) * spd - 2.0,
                "life": life, "max": life,
                "col": random.choice(colors),
            })

    def update(self):
        for p in self.particles:
            p["x"] += p["vx"]
            p["y"] += p["vy"]
            p["vy"] += 0.20
            p["life"] -= 1
        self.particles = [p for p in self.particles if p["life"] > 0]

    def draw(self):
        for p in self.particles:
            if p["life"] > p["max"] * 0.15:
                pyxel.pset(int(p["x"]), int(p["y"]), p["col"])


# â”€â”€ Hauptklasse â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
class Game:

    def __init__(self):
        pyxel.init(W, H, title="Parcours Jump - Osteredition 2.0")
        try:
            pyxel.load("res.pyxres")
        except Exception:
            pass
        self.particles = ParticleSystem()
        self._setup_sounds()
        self._build_bg()
        self.highscore = 0
        self.char_idx = 0
        self.state = "title"
        self._start()
        pyxel.run(self.update, self.draw)

    # â”€â”€ Sound / Musik â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

    def _setup_sounds(self):
        # Neue API: pyxel.sounds[n] statt pyxel.sound(n)
        # Einstellige Vol/Effekt-Strings â€“ pyxel wiederholt sie automatisch

        # pyxelstudio: max Oktave 4, kein r (Rest), sounds[n] API
        # â”€â”€ Soundeffekte (Kanal 3) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        pyxel.sounds[0].set("g4 b4 g4 b4", "s", "7", "n", 8)           # Sprung
        pyxel.sounds[1].set("c4 e4 g4 c4", "s", "7", "n", 8)           # Karotte
        pyxel.sounds[2].set("c4 e4 g4 c4 e4 c4", "p", "7", "n", 7)     # Gold
        pyxel.sounds[3].set("c4 b3 a3 g3 f3 e3 d3 c3", "p", "7", "n", 8)  # Tod
        pyxel.sounds[4].set("c3 d3 e3 f3 g3 a3 b3 c4", "t", "6", "n", 8)  # Portal
        pyxel.sounds[5].set("c4 g4 e4 c4", "t", "7", "n", 8)           # Schild
        pyxel.sounds[6].set("c4 e4 g4 e4", "s", "7", "n", 6)           # Speed
        pyxel.sounds[7].set(
            "c3 e3 g3 c4 e4 g4 c4 g3 e3 c3 g3 c4",
            "s", "7", "n", 8)                                            # Sieg

        # Musik entfernt â€“ nur Soundeffekte aktiv

    def _sfx(self, snd_id):
        pyxel.play(3, snd_id)

    # â”€â”€ Hintergrund (einmalig generiert) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

    def _build_bg(self):
        random.seed(42)
        self.bg_eggs = [
            {"x": random.randint(10, W - 20),
             "y": random.randint(-20000, H),
             "c": random.choice([8, 9, 10, 11, 14]),
             "s": random.choice([7, 6, 5])}
            for _ in range(60)
        ]
        self.bg_grass = [
            {"x": random.randint(0, W - 1),
             "y": random.randint(-20000, H),
             "h": random.randint(6, 20)}
            for _ in range(10000)
        ]
        random.seed()

    # â”€â”€ Spielstatus reset â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

    def _start(self):
        c = CHARS[self.char_idx]
        self.char = c
        self.px = float(W // 2)
        self.py = float(H - 60)
        self.vy = 0.0
        self.cam = 0.0
        self.max_height = 0
        self.score_penalty = 0
        self.plats = []
        self.spikes = []
        self.powerups = []
        self.portals = []
        self.portal_plat_counter = 0   # zaehlt Plattformen fuer Portal-Spawn
        self.count = 0
        self.current_plat = None
        self.particles.particles.clear()
        # Powerup-Zustaende
        self.boost_jumps = 0
        self.boost_gold = False
        self.boost_gold_held = False
        self.immunity_active = False
        self.immunity_timer = 0
        self.speed_active = False
        self.speed_timer = 0
        self.magnet_active = False
        self.magnet_timer = 0
        self.dbl_used = False
        # Timer
        self.penalty_timer = 0
        self.egg_cooldown = 0
        # Parallelwelt
        self.parallel_world = False
        # Korb
        self.basket_active = False
        self.basket_x = W // 2 - 100
        self.basket_y = None
        self.basket_landed = False

        y = H - 50
        x = W // 2
        for _ in range(20):
            x = max(30, min(W - 110, x + random.randint(-110, 110)))
            self._add_plat(x, y, random.randint(70, 110))
            y -= random.randint(60, 85)
        self.px = float(self.plats[0]["x"] + self.plats[0]["w"] // 2 - PW // 2)
        self.py = float(self.plats[0]["y"] - PH)

    # â”€â”€ Spielpunkte â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

    @property
    def score(self):
        base = max(0, self.max_height - self.score_penalty)
        return int(base / SCORE_SCALE * (1.5 if self.parallel_world else 1.0))

    # â”€â”€ Plattform- und Powerup-Erzeugung â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

    def _add_plat(self, x, y, w):
        diff = self.count // 20
        r = random.random()
        if diff == 0:   t = "s"
        elif diff == 1: t = "h" if r < 0.40 else "s"
        elif diff == 2: t = "h" if r < 0.35 else ("v" if r < 0.55 else "s")
        elif diff == 3: t = "h" if r < 0.30 else ("v" if r < 0.50 else "s")
        else:           t = "h" if r < 0.25 else ("v" if r < 0.45 else "s")

        self.plats.append({"x": x, "y": y, "w": w, "t": t,
                           "d": random.choice([-1, 1]),
                           "r": random.randint(40, 80), "by": y})

        margin = int(PW * 2)
        if random.random() < 0.6 and w > margin * 2 + 16:
            sx = random.randint(x + margin, x + w - margin - 16)
            self.spikes.append({"x": sx, "y": y - 14})

        if self.count > 0:
            cx = x + w // 2 - 8
            r2 = random.random()
            if self.count % 20 == 0 and r2 < 0.10 and self.score < 11000:
                self.powerups.append({"x": cx, "y": y - 16, "plat": self.plats[-1], "type": "gold_carrot"})
            elif self.count % 5 == 0:
                r3 = random.random()
                if   r3 < 0.18: self.powerups.append({"x": cx, "y": y - 16, "plat": self.plats[-1], "type": "carrot"})
                elif r3 < 0.28: self.powerups.append({"x": cx, "y": y - 16, "plat": self.plats[-1], "type": "shield"})
                elif r3 < 0.36: self.powerups.append({"x": cx, "y": y - 16, "plat": self.plats[-1], "type": "speed"})
                elif r3 < 0.42: self.powerups.append({"x": cx, "y": y - 16, "plat": self.plats[-1], "type": "magnet"})
        # Portal alle 30 Plattformen, 75% Chance, auf der Plattform selbst
        self.portal_plat_counter += 1
        if self.portal_plat_counter >= 30:
            self.portal_plat_counter = 0
            if random.random() < 0.75:
                self._add_portal(self.plats[-1])

        self.count += 1

    def _spawn(self):
        while len(self.plats) < 30:
            last = min(self.plats, key=lambda p: p["y"])
            ny = last["y"] - random.randint(60, 85)
            w  = random.randint(55, 80)
            x  = max(30, min(W - w - 30, last["x"] + random.randint(-110, 110)))
            self._add_plat(x, ny, w)

    def _add_portal(self, plat):
        # Portal sitzt auf einer Plattform â€“ erreichbar aber seitlich ausweichbar.
        # Links oder rechts auf der Plattform, nicht mittig (damit man drum herum kann).
        side = random.choice(["left", "right"])
        if side == "left":
            px = plat["x"] + 10
        else:
            px = plat["x"] + plat["w"] - 40
        px = max(10, min(W - 40, px))
        self.portals.append({"x": px, "y": plat["y"] - 44})

    def _apply_powerup(self, pu_type):
        if pu_type == "carrot":
            self.boost_jumps = 3
            self.boost_gold = self.boost_gold_held = False
            self._sfx(1)
        elif pu_type == "gold_carrot":
            self.boost_jumps = 1
            self.boost_gold = self.boost_gold_held = True
            self.immunity_active = False
            self.immunity_timer = 0
            self._sfx(2)
        elif pu_type == "shield":
            self.immunity_active = True
            self.immunity_timer = 300
            self._sfx(5)
        elif pu_type == "speed":
            self.speed_active = True
            self.speed_timer = 600
            self._sfx(6)
        elif pu_type == "magnet":
            self.magnet_active = True
            self.magnet_timer = 600
            self._sfx(1)

    def _die(self):
        self._sfx(3)
        self.particles.emit(int(self.px + PW // 2), int(self.py + PH // 2),
                            40, [8, 9, 7], speed=7, life=50)
        if self.score > self.highscore:
            self.highscore = self.score
        self.state = "dead"

    # â”€â”€ Update â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

    def update(self):
        if self.state == "title":
            if pyxel.btnp(pyxel.KEY_SPACE):
                self.state = "charselect"
            return

        if self.state == "charselect":
            if pyxel.btnp(pyxel.KEY_LEFT):
                self.char_idx = (self.char_idx - 1) % len(CHARS)
            if pyxel.btnp(pyxel.KEY_RIGHT):
                self.char_idx = (self.char_idx + 1) % len(CHARS)
            if pyxel.btnp(pyxel.KEY_SPACE):
                self._start()
                self.state = "playing"
                pass  # keine Hintergrundmusik
            return

        if self.state in ("dead", "won"):
            if pyxel.btnp(pyxel.KEY_R):
                self.state = "charselect"
            self.particles.update()
            return

        # â”€â”€ Bewegung â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        c = self.char
        sm = 1.6 if self.speed_active else 1.0
        if pyxel.btn(pyxel.KEY_LEFT):  self.px -= c["spd"] * sm
        if pyxel.btn(pyxel.KEY_RIGHT): self.px += c["spd"] * sm
        self.px = max(0.0, min(W - PW, self.px))

        # Normaler Sprung
        if pyxel.btnp(pyxel.KEY_UP):
            if self.current_plat is not None:
                self.vy = c["jmp"]
                self.current_plat = None
                self.dbl_used = False
                self._sfx(0)
                self.particles.emit(int(self.px + PW // 2), int(self.py + PH),
                                    8, [c["col"], c["acc"]], speed=4)
            elif c["dbl"] and not self.dbl_used and self.vy > -3:
                self.vy = c["jmp"] * 0.85
                self.dbl_used = True
                self._sfx(0)
                self.particles.emit(int(self.px + PW // 2), int(self.py + PH // 2),
                                    12, [12, 7, c["col"]], speed=5)

        # Boost-Sprung
        if pyxel.btnp(pyxel.KEY_SPACE) and self.current_plat is not None and self.boost_jumps > 0:
            if self.boost_gold_held:
                self.vy = c["jmp"] * (5 / 3) * 5
                self.boost_gold_held = False
                self.immunity_active = True
                self.immunity_timer = 600
                self.particles.emit(int(self.px + PW // 2), int(self.py + PH),
                                    30, [10, 7, 9, 14], speed=8, life=45)
            elif self.boost_gold:
                self.vy = c["jmp"] * (5 / 3) * 3
                self.particles.emit(int(self.px + PW // 2), int(self.py + PH),
                                    20, [10, 7], speed=6, life=35)
            else:
                self.vy = c["jmp"] * (5 / 3)
                self.particles.emit(int(self.px + PW // 2), int(self.py + PH),
                                    12, [9, 10], speed=5)
            self._sfx(0)
            self.boost_jumps -= 1
            if self.boost_jumps == 0:
                self.boost_gold = False
            self.current_plat = None

        # â”€â”€ Timers â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        if self.immunity_active:
            self.immunity_timer -= 1
            if self.immunity_timer <= 0:
                self.immunity_active = False
                self.boost_gold = False
        if self.speed_active:
            self.speed_timer -= 1
            if self.speed_timer <= 0:
                self.speed_active = False
        if self.magnet_active:
            self.magnet_timer -= 1
            if self.magnet_timer <= 0:
                self.magnet_active = False
            else:
                self.score_penalty = max(0, self.score_penalty - 6)
        if self.penalty_timer > 0:
            self.penalty_timer -= 1
        if self.egg_cooldown > 0:
            self.egg_cooldown -= 1

        # â”€â”€ Physik â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        self.vy += c["grav"]
        self.py += self.vy

        diff = self.count // 20
        plat_spd = min(1.5 + diff * 0.6, 8.0)

        for p in self.plats:
            if p["t"] == "h":
                p["x"] += p["d"] * plat_spd
                if p["x"] < 10 or p["x"] + p["w"] > W - 10:
                    p["d"] *= -1
            elif p["t"] == "v":
                p["y"] += p["d"] * plat_spd
                if p["y"] > p["by"] + p["r"]: p["d"] = -1
                if p["y"] < p["by"] - p["r"]: p["d"] = 1

        # â”€â”€ Plattform-Kollision â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        if self.current_plat is not None:
            p = self.current_plat
            if self.px + PW > p["x"] and self.px < p["x"] + p["w"]:
                self.py = p["y"] - PH
                self.vy = 0
                if p["t"] == "h": self.px += p["d"] * plat_spd
                if p["t"] == "v": self.py = p["y"] - PH
            else:
                self.current_plat = None
        else:
            if self.vy >= 0:
                for p in self.plats:
                    if (self.px + PW > p["x"] and self.px < p["x"] + p["w"]
                            and self.py + PH >= p["y"]
                            and self.py + PH <= p["y"] + 8 + self.vy + 1):
                        self.py = p["y"] - PH
                        self.vy = 0
                        self.current_plat = p
                        self.dbl_used = False
                        break

        # â”€â”€ Stachel-Kollision â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        if not self.immunity_active:
            for s in self.spikes:
                if (self.px < s["x"] + 16 and self.px + PW > s["x"]
                        and self.py < s["y"] + 14 and self.py + PH > s["y"]):
                    self._die()
                    return

        # â”€â”€ Powerup-Kollision â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        for pu in self.powerups[:]:
            p = pu["plat"]
            pu["x"] = p["x"] + p["w"] // 2 - 8
            pu["y"] = p["y"] - 16
            if (self.px < pu["x"] + 16 and self.px + PW > pu["x"]
                    and self.py < pu["y"] + 16 and self.py + PH > pu["y"]):
                self.particles.emit(int(pu["x"] + 8), int(pu["y"]),
                                    18, [10, 7, 9, 11, 8], speed=6, life=40)
                self._apply_powerup(pu["type"])
                self.powerups.remove(pu)

        # â”€â”€ Portal-Kollision â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        for portal in self.portals[:]:
            px2, py2 = portal["x"], portal["y"]
            if (self.px < px2 + 28 and self.px + PW > px2
                    and self.py < py2 + 44 and self.py + PH > py2):
                self.parallel_world = not self.parallel_world
                self._sfx(4)
                self.particles.emit(int(self.px + PW // 2), int(self.py + PH // 2),
                                    30, [12, 5, 14, 9], speed=7, life=50)
                self.portals.remove(portal)
                break

        # â”€â”€ Kamera â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        if self.py < H / 2 + self.cam:
            self.cam = self.py - H / 2

        h = int(-self.cam)
        if h > self.max_height:
            self.max_height = h

        # â”€â”€ Ei-Kollision â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        if not self.immunity_active and not c["egg_imm"] and self.egg_cooldown == 0:
            for e in self.bg_eggs:
                if (self.px < e["x"] + 12 and self.px + PW > e["x"] + 1
                        and self.py < e["y"] + 14 and self.py + PH > e["y"]):
                    self.score_penalty = min(self.score_penalty + int(1000 * SCORE_SCALE),
                                            self.max_height)
                    self.penalty_timer = 90
                    self.egg_cooldown = 60
                    break

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

        # Portale werden in _add_plat alle 30 Stufen gespawnt

        # â”€â”€ Korb â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        if self.score >= 15000 and not self.basket_active:
            self.basket_active = True
            self.basket_y = int(self.py) - int(H * 1.5)

        if self.basket_active and not self.basket_landed and self.basket_y is not None:
            basket_top = self.basket_y + 24
            if (self.vy >= 0
                    and self.px + PW > self.basket_x
                    and self.px < self.basket_x + 200
                    and self.py + PH >= basket_top
                    and self.py + PH <= basket_top + 10 + self.vy + 1):
                self.py = basket_top - PH
                self.vy = 0
                self.basket_landed = True
                pyxel.stop()
                self._sfx(7)
                if self.score > self.highscore:
                    self.highscore = self.score
                self.state = "won"
                return

        if not self.basket_landed:
            self._spawn()

        if self.basket_active and self.basket_y is not None:
            ceiling = self.basket_y - H + PH
            if self.py < ceiling:
                self.py = ceiling
                if self.vy < 0: self.vy = 0

        if self.current_plat is not None and self.current_plat not in self.plats:
            self.current_plat = None
        cutoff = self.cam + H + 200
        self.plats    = [p  for p  in self.plats    if p["y"]  < cutoff]
        self.powerups = [pu for pu in self.powerups  if pu["y"] < cutoff]
        self.spikes   = [s  for s  in self.spikes    if s["y"]  < cutoff]

        self.particles.update()

        if self.py > self.cam + H + 40:
            self._die()

    # â”€â”€ Zeichen-Hilfsmethoden â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

    def _egg(self, x, y, c, s, scale=1):
        if scale == 2:   # mittel ~12x14px (Hintergrund-Eier)
            pyxel.rect(x + 3,  y,      6,  3,  c)
            pyxel.rect(x + 1,  y + 3,  10, 8,  c)
            pyxel.rect(x + 3,  y + 11, 6,  3,  c)
            pyxel.rect(x + 1,  y + 6,  10, 2,  s)
        elif scale == 1:   # gross ~16x18px (Korb)
            pyxel.rect(x + 4,  y,      8,  4,  c)
            pyxel.rect(x + 2,  y + 4,  12, 10, c)
            pyxel.rect(x + 4,  y + 14, 8,  4,  c)
            pyxel.rect(x + 2,  y + 8,  12, 2,  s)
        else:              # klein ~8x9px (Titelscreen-Deko)
            pyxel.rect(x + 2, y,     4, 2, c)
            pyxel.rect(x + 1, y + 2, 6, 5, c)
            pyxel.rect(x + 2, y + 7, 4, 2, c)
            pyxel.rect(x + 1, y + 4, 6, 1, s)

    def _grass(self, x, y, h):
        pyxel.line(x, y, x, y - h, 11)
        pyxel.pset(x - 1, y - h + 2, 3)
        pyxel.pset(x + 1, y - h,     3)
        if h > 8: pyxel.pset(x, y - h // 2, 3)

    def _char(self, x, y, idx, air=False):
        x, y = int(x), int(y)
        if idx == 0:  # Hase
            pyxel.rect(x + 7,  y - 10, 4,  12, 7)
            pyxel.rect(x + 17, y - 10, 4,  12, 7)
            pyxel.pset(x + 8,  y - 8, 8)
            pyxel.pset(x + 18, y - 8, 8)
            pyxel.rect(x + 2,  y,      24, 20, 7)
            pyxel.rect(x + 4,  y + 20, 20, 6,  7)
            pyxel.pset(x + 7,  y + 5, 0)
            pyxel.pset(x + 18, y + 5, 0)
            pyxel.rect(x + 11, y + 11, 6, 3, 8)
            pyxel.rect(x + 22, y + 14, 6, 6, 7)

        elif idx == 1:  # Fuchs
            pyxel.tri(x + 4,  y,     x + 9,  y - 12, x + 12, y, 9)
            pyxel.tri(x + 16, y,     x + 19, y - 12, x + 24, y, 9)
            pyxel.rect(x + 2,  y,      24, 20, 9)
            pyxel.rect(x + 4,  y + 20, 20, 6,  9)
            pyxel.rect(x + 7,  y + 9,  14, 11, 7)
            pyxel.pset(x + 7,  y + 4, 0)
            pyxel.pset(x + 18, y + 4, 0)
            pyxel.rect(x + 10, y + 12, 8, 3, 10)
            pyxel.pset(x + 13, y + 12, 8)
            pyxel.rect(x + 22, y + 12, 8, 6, 10)

        elif idx == 2:  # Vogel â€“ rot/coral (8), sichtbar in beiden Welten
            if air:
                pyxel.tri(x,      y + 8,  x + 8,  y + 18, x,      y + 22, 8)
                pyxel.tri(x + 28, y + 8,  x + 20, y + 18, x + 28, y + 22, 8)
            else:
                pyxel.rect(x,      y + 8,  6, 12, 8)
                pyxel.rect(x + 22, y + 8,  6, 12, 8)
            pyxel.rect(x + 4,  y + 2,  20, 22, 8)
            pyxel.rect(x + 6,  y,       16, 4,  8)
            pyxel.rect(x + 6,  y + 24,  16, 4,  8)
            pyxel.rect(x + 8,  y + 10,  12, 12, 7)
            pyxel.pset(x + 9,  y + 5, 0)
            pyxel.pset(x + 18, y + 5, 0)
            pyxel.tri(x + 9, y + 13, x + 19, y + 13, x + 14, y + 19, 10)

        elif idx == 3:  # Kaefer â€“ braun (4), orange Akzente (9)
            pyxel.rect(x + 4,  y + 2,  20, 24, 4)
            pyxel.rect(x + 2,  y + 6,  24, 16, 4)
            pyxel.rect(x + 6,  y,       16, 4,  4)
            pyxel.line(x + 14, y + 2,  x + 14, y + 24, 9)   # Mittellinie orange
            pyxel.pset(x + 8,  y + 10, 9)
            pyxel.pset(x + 20, y + 10, 9)
            pyxel.pset(x + 8,  y + 18, 9)
            pyxel.pset(x + 20, y + 18, 9)
            pyxel.line(x + 8,  y,      x + 4,  y - 8,  9)   # Antennen orange
            pyxel.line(x + 20, y,      x + 24, y - 8,  9)
            pyxel.pset(x + 4,  y - 9, 9)
            pyxel.pset(x + 24, y - 9, 9)
            pyxel.pset(x + 8,  y + 4, 0)
            pyxel.pset(x + 18, y + 4, 0)

    def _draw_powerup(self, pu):
        x, y = int(pu["x"]), int(pu["y"])
        t = pu["type"]
        f = pyxel.frame_count
        if t == "carrot":
            pyxel.tri(x, y + 16, x + 8, y, x + 16, y + 16, 9)
            pyxel.rect(x + 6, y - 4, 4, 5, 11)
        elif t == "gold_carrot":
            pyxel.tri(x, y + 16, x + 8, y, x + 16, y + 16, 10)
            pyxel.pset(x + 8, y - 2, 7)
            pyxel.pset(x + 6, y - 4, 7)
            pyxel.pset(x + 10, y - 4, 7)
            if (f // 8) % 2 == 0:
                pyxel.pset(x + 5, y + 5, 7)
                pyxel.pset(x + 11, y + 3, 7)
        elif t == "shield":
            cols = [8, 9, 10, 14]
            c2 = cols[(f // 8) % 4]
            pyxel.rect(x + 5, y,      6, 16, c2)
            pyxel.rect(x,     y + 5,  16, 6,  c2)
            pyxel.pset(x + 8, y + 8, 7)
        elif t == "speed":
            pyxel.tri(x + 8, y,      x + 16, y,  x + 8, y + 8,  10)
            pyxel.tri(x,     y + 8,  x + 8,  y + 8, x + 8, y + 16, 10)
            pyxel.pset(x + 8, y + 7, 7)
        elif t == "magnet":
            pyxel.rect(x + 2,  y + 6,  4, 10, 8)
            pyxel.rect(x + 10, y + 6,  4, 10, 8)
            pyxel.rect(x + 2,  y + 2,  12, 6,  8)
            pyxel.pset(x + 2,  y + 15, 7)
            pyxel.pset(x + 13, y + 15, 9)
            if (f // 6) % 2 == 0:
                pyxel.pset(x + 2,  y + 16, 7)
                pyxel.pset(x + 13, y + 16, 9)

    def _draw_portal(self, portal):
        x, y = portal["x"], portal["y"]
        t = pyxel.frame_count
        col_a = 12 if not self.parallel_world else 9
        col_b = 5  if not self.parallel_world else 14
        for i in range(18):
            angle = math.radians(i * 20 + t * 4)
            gx = int(x + 14 + math.cos(angle) * 16)
            gy = int(y + 22 + math.sin(angle) * 24)
            pyxel.pset(gx, gy, col_a if i % 2 == 0 else col_b)
        fill = col_b if (t // 6) % 2 == 0 else (2 if self.parallel_world else 3)
        pyxel.rect(x + 4, y + 4, 20, 36, fill)
        pyxel.text(x + 2, y + 16, "POR", col_a)
        pyxel.text(x + 2, y + 24, "TAL", col_b)

    def _draw_basket_big(self, x, y):
        bw = 200
        pyxel.line(x + 40,  y + 24, x + 40,  y + 5,  4)
        pyxel.line(x + 160, y + 24, x + 160, y + 5,  4)
        pyxel.line(x + 40,  y + 5,  x + 160, y + 5,  4)
        pyxel.rect(x,       y + 24,  bw,      60, 4)
        pyxel.rect(x + 8,   y + 27,  bw - 16, 54, 9)
        pyxel.rect(x,       y + 24,  14, 60, 4)
        pyxel.rect(x + bw - 14, y + 24, 14, 60, 4)
        for i in range(11):
            pyxel.line(x + i * 18,     y + 24, x + i * 18 + 9, y + 84, 5)
            pyxel.line(x + i * 18 + 9, y + 24, x + i * 18,     y + 84, 5)
        eggs = [(x + 10, y + 46, 8, 7), (x + 35, y + 38, 10, 7),
                (x + 60, y + 46, 11, 7), (x + 85, y + 38, 9, 7),
                (x + 110, y + 46, 8, 7), (x + 135, y + 38, 10, 7),
                (x + 160, y + 46, 14, 7)]
        for ex, ey, ec, es in eggs:
            self._egg(ex, ey, ec, es, scale=0)

    def _draw_bg(self, cam):
        for row in range(H):
            wy = row + cam
            if self.parallel_world:
                c = 2 if wy < -10000 else (5 if wy < 0 else 13)
            else:
                c = 3 if wy < -10000 else (3 if wy < 0 else 11)
            pyxel.line(0, row, W, row, c)
        for g in self.bg_grass:
            sy = g["y"] - cam
            if -20 < sy < H + 10:
                self._grass(g["x"], int(sy), g["h"])
        for e in self.bg_eggs:
            sy = e["y"] - cam
            if -25 < sy < H + 10:
                if self.immunity_active and (pyxel.frame_count // 4) % 2 == 0:
                    continue
                self._egg(e["x"], int(sy), e["c"], e["s"], scale=2)

    # â”€â”€ Draw â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

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

        # â”€â”€ Titelbildschirm â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        if self.state == "title":
            for row in range(H):
                pyxel.line(0, row, W, row, 3 if row < H // 2 else 11)
            for g in self.bg_grass[:200]:
                self._grass(g["x"], int(g["y"] % H), g["h"])
            for e in self.bg_eggs[:20]:
                sy = int(e["y"] % H)
                if sy > 20: self._egg(e["x"], sy, e["c"], e["s"], scale=0)

            pyxel.rect(30, 20, 452, 38, 1)
            pyxel.rectb(30, 20, 452, 38, 9)
            pyxel.text(70, 28, "P A R C O U R S   J U M P", 10)
            pyxel.text(160, 40, "Osteredition 2.0", 9)

            pyxel.rect(30, 70, 210, 200, 1)
            pyxel.rectb(30, 70, 210, 200, 9)
            pyxel.text(40, 78, "-- STEUERUNG --", 9)
            pyxel.text(40, 92,  "< >    bewegen", 6)
            pyxel.text(40, 104, "OBEN   Springen", 6)
            pyxel.text(40, 116, "SPACE  Boost-Sprung", 6)
            pyxel.text(40, 130, "R      Neustart", 6)

            pyxel.rect(260, 70, 222, 200, 1)
            pyxel.rectb(260, 70, 222, 200, 7)
            pyxel.text(270, 78, "-- POWERUPS --", 7)
            pyxel.tri(270, 100, 276, 90, 282, 100, 9)
            pyxel.text(288, 93, "Karotte: 3x Boost", 7)
            pyxel.tri(270, 116, 276, 106, 282, 116, 10)
            pyxel.text(288, 109, "Gold: Mega+Immun", 10)
            pyxel.rect(270, 122, 10, 10, 8)
            pyxel.text(288, 124, "Blume: 5s Immun", 7)
            pyxel.tri(270, 144, 276, 134, 282, 144, 10)
            pyxel.text(288, 137, "Blitz: 10s Speed", 7)
            pyxel.rect(270, 148, 12, 10, 8)
            pyxel.text(288, 151, "Magnet: +Punkte", 7)
            pyxel.rectb(270, 165, 20, 30, 12)
            pyxel.text(296, 171, "Portal:", 12)
            pyxel.text(296, 181, "Spiegelwelt", 12)
            pyxel.text(296, 191, "= x1.5 Punkte!", 9)

            pyxel.rect(30, 286, 452, 24, 1)
            pyxel.rectb(30, 286, 452, 24, 7)
            self._egg(40, 290, 8, 7, scale=0)
            pyxel.text(58, 294, "Eier beruehren: -1000 Punkte!", 8)

            if (pyxel.frame_count // 20) % 2 == 0:
                pyxel.rect(136, 330, 240, 18, 8)
                pyxel.text(160, 336, "SPACE = WEITER", 0)
            return

        # â”€â”€ Charakterauswahl â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        if self.state == "charselect":
            for row in range(H):
                pyxel.line(0, row, W, row, 1 if row < H // 2 else 2)
            pyxel.text(170, 20, "CHARAKTER WAEHLEN", 10)
            pyxel.text(130, 34, "< > wechseln   SPACE = spielen", 7)

            for i, ch in enumerate(CHARS):
                bx = 15 + i * 122
                by = 60
                active = (i == self.char_idx)
                pyxel.rect(bx, by, 112, 200, 9 if active else 1)
                pyxel.rectb(bx, by, 112, 200, 10 if active else 5)
                self._char(bx + 42, by + 20, i)
                pyxel.text(bx + 8, by + 60, ch["name"], 7 if active else 6)
                pyxel.text(bx + 8, by + 72, ch["desc"], 13 if active else 5)
                # Balken
                spd_w = int(ch["spd"] / 9.0 * 90)
                jmp_w = int(abs(ch["jmp"]) / 20.0 * 90)
                pyxel.text(bx + 8, by + 88, "SPD", 13)
                pyxel.rect(bx + 8, by + 98, spd_w, 6, 10)
                pyxel.text(bx + 8, by + 110, "JMP", 13)
                pyxel.rect(bx + 8, by + 120, jmp_w, 6, 9)
                if ch["dbl"]:
                    pyxel.text(bx + 8, by + 136, "DBL JUMP", 12)
                if ch["egg_imm"]:
                    pyxel.text(bx + 8, by + 136, "EI-IMMUN", 11)
                if active:
                    h_px = round(ch["jmp"] ** 2 / (2 * ch["grav"]))
                    pyxel.text(bx + 8, by + 152, f"H:{h_px}px", 7)
                    pyxel.text(bx + 8, by + 162, f"Spd:{ch['spd']}", 7)

            if (pyxel.frame_count // 20) % 2 == 0:
                pyxel.rect(136, 470, 240, 18, 8)
                pyxel.text(156, 476, "SPACE = JETZT SPIELEN!", 0)
            return

        # â”€â”€ Spielwelt â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        self._draw_bg(int(self.cam))
        pyxel.camera(0, self.cam)

        for p in self.plats:
            if self.parallel_world:
                body_c, top_c = (2, 5) if p["t"] == "s" else ((13, 12) if p["t"] == "h" else (5, 2))
            else:
                body_c, top_c = (4, 5) if p["t"] == "s" else ((9, 10) if p["t"] == "h" else (5, 4))
            pyxel.rect(p["x"], p["y"], p["w"], 8, body_c)
            pyxel.line(p["x"], p["y"], p["x"] + p["w"] - 1, p["y"], top_c)

        for s in self.spikes:
            pyxel.tri(s["x"], s["y"] + 14, s["x"] + 8, s["y"], s["x"] + 16, s["y"] + 14, 8)

        for pu in self.powerups:
            self._draw_powerup(pu)

        for portal in self.portals:
            self._draw_portal(portal)

        if self.basket_active and self.basket_y is not None:
            self._draw_basket_big(self.basket_x, self.basket_y)
            t = pyxel.frame_count
            for i in range(12):
                angle = math.radians((t * 3 + i * 30) % 360)
                gx = int(self.basket_x + 100 + math.cos(angle) * 115)
                gy = int(self.basket_y + 50  + math.sin(angle) * 55)
                pyxel.pset(gx, gy, [7, 10, 9, 8, 14][i % 5])
            basket_screen_y = self.basket_y - self.cam
            if basket_screen_y < 10:
                ax = self.basket_x + 85
                awy = int(self.cam) + 16
                if (t // 10) % 2 == 0:
                    pyxel.tri(ax, awy, ax + 12, awy, ax + 6, awy - 10, 10)
                    pyxel.text(ax - 10, awy + 4, "KORB", 10)

        self._char(self.px, self.py, self.char_idx, air=self.current_plat is None)
        self.particles.draw()

        # Powerup-Aura um Spieler
        fc = pyxel.frame_count
        c = self.char
        if self.immunity_active and (fc // 6) % 2 == 0:
            pyxel.rectb(int(self.px) - 3, int(self.py) - 3, PW + 6, PH + 6, 10)
        elif self.boost_gold_held and (fc // 8) % 2 == 0:
            pyxel.rectb(int(self.px) - 3, int(self.py) - 3, PW + 6, PH + 6, 9)
        elif self.speed_active and (fc // 4) % 2 == 0:
            pyxel.rectb(int(self.px) - 3, int(self.py) - 3, PW + 6, PH + 6, 10)
        elif self.magnet_active and (fc // 6) % 2 == 0:
            pyxel.rectb(int(self.px) - 3, int(self.py) - 3, PW + 6, PH + 6, 8)

        pyxel.camera()

        # â”€â”€ HUD â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        hud_c = 12 if self.parallel_world else 7
        tag = "[SPIEGEL x1.5] " if self.parallel_world else ""
        pyxel.text(8, 8,  f"{tag}Punkte: {self.score}", hud_c)
        pyxel.text(8, 18, f"Highscore: {self.highscore}", 6)
        pyxel.text(8, 28, f"Charakter: {self.char['name']}", self.char["col"])

        if self.penalty_timer > 0:
            pyxel.text(8, 44, "-1000 Punkte!", 8)

        parts = []
        if self.boost_jumps > 0:
            lbl = "GOLD" if (self.boost_gold_held or self.boost_gold) else "BOOST"
            parts.append(f"{lbl} x{self.boost_jumps} [SPACE]")
        if self.immunity_active:
            parts.append(f"IMMUN {self.immunity_timer // 60}s")
        if self.speed_active:
            parts.append(f"SPEED {self.speed_timer // 60}s")
        if self.magnet_active:
            parts.append(f"MAGNET {self.magnet_timer // 60}s")
        if parts:
            col = 10 if self.immunity_active else 9
            pyxel.text(8, 38, "  ".join(parts)[:60], col)

        if 14000 <= self.score < 15000 and (fc // 15) % 2 == 0:
            pyxel.text(8, H - 20, "  >> Osterkorb kommt bald! <<", 10)
        elif self.basket_active and not self.basket_landed and (fc // 20) % 2 == 0:
            pyxel.text(8, H - 20, "  >> Springe auf den Korb! <<", 8)

        # â”€â”€ Sieg â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        if self.state == "won":
            pyxel.rect(56, 130, 400, 220, 1)
            pyxel.rectb(56, 130, 400, 220, 10)
            pyxel.rectb(58, 132, 396, 216, 9)
            pyxel.text(150, 148, "** GLUECKWUNSCH! **", 10)
            pyxel.text(90, 170, "Du hast den Osterhasentest", 9)
            pyxel.text(90, 182, "bestanden!", 9)
            pyxel.text(90, 200, f"Punkte:     {self.score}", 7)
            pyxel.text(90, 214, f"Highscore:  {self.highscore}", 10)
            t = pyxel.frame_count
            for i in range(16):
                angle = math.radians(i * 22.5 + t * 2)
                gx = int(256 + math.cos(angle) * 160)
                gy = int(240 + math.sin(angle) * 80)
                self._egg(gx - 4, gy - 4, [8,9,10,11,14][i%5], 7, scale=0)
            pyxel.text(136, 310, "R = Charakter waehlen", 6)
            return

        # â”€â”€ Game Over â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        if self.state == "dead":
            self.particles.draw()
            pyxel.rect(106, 170, 300, 140, 1)
            pyxel.rectb(106, 170, 300, 140, 8)
            pyxel.text(210, 186, "GAME OVER", 8)
            pyxel.text(160, 206, f"PUNKTE:    {self.score}", 7)
            pyxel.text(160, 220, f"HIGHSCORE: {self.highscore}", 10)
            pyxel.text(140, 244, f"Charakter: {self.char['name']}", self.char["col"])
            pyxel.text(150, 280, "R = Charakter waehlen", 7)


Game()