import pyxel
import random
import math

class Game:
    def __init__(self):
        pyxel.init(160, 120, title="Brain & Beauty")
        pyxel.load("res2.pyxres")
        pyxel.playm(0, loop=True)

        # Sound-IDs fuer Effekte. Wir erzeugen die Sounds hier direkt im
        # Code (statt sie aus der .pyxres zu laden), damit sie garantiert
        # existieren, egal was in res2.pyxres an Sound-Slots vorhanden ist.
        self.SOUND_CRASH = 1
        self.SOUND_STAR = 2

        # Crash-Sound: kurzer, fallender "Plopp" (Pulse-Welle, tiefe Toene)
        pyxel.sound(self.SOUND_CRASH).set(
            notes="f2d2a1f1",
            tones="pppp",
            volumes="7774",
            effects="nnnn",
            speed=12
        )

        # Stern-Sound: aufsteigender, heller "Ping" (Triangle-Welle).
        # Hinweis: Pyxel erlaubt nur Oktaven 0-4, daher c3e3g3c4 statt c4..c5.
        pyxel.sound(self.SOUND_STAR).set(
            notes="c3e3g3c4",
            tones="tttt",
            volumes="7765",
            effects="nnnn",
            speed=20
        )

        self.level = 1

        self.lanes = [30, 60, 90]
        self.current_lane = 1
        self.player_x = 20
        self.player_y = self.lanes[self.current_lane]
        self.outfit = 0

        # ── Fortschritts-Sterne (5 grosse Meilenstein-Sterne) ─
        # Werden nach jedem der 5 grossen Level-Abschnitte vergeben:
        # 1) Autofahrt 1, 2) Autofahrt 2, 3) Quiz (Mathe+Geo),
        # 4) Sternenregen, 5) Sprung-Challenge
        self.stars_earned = 0
        self.star_award_next_level = None  # wohin nach dem Stern-Screen gesprungen wird
        self.star_award_next_level_name = None  # Anzeigename des naechsten Levels

        # ── Driving shared state ──────────────────────────────
        self.lives = 3
        self.speed = 1
        self.has_helmet = False
        self.cars = []
        self.items = []
        self.timer = 0
        self.max_time = 600
        self.show_level_complete_text = False
        self.hit_flash = 0          # red flash frames on collision

        self.level3_cars = []
        self.level3_items = []
        self.level3_timer = 0
        self.level3_max_time = 550
        self.level3_has_helmet = False
        self.level3_speed = 1
        self.level3_current_lane = 1
        self.level3_player_y = self.lanes[1]
        self.show_level3_complete_text = False
        self.level3_lives = 3
        self.level3_hit_flash = 0

        # ── Quiz shared state ─────────────────────────────────
        self.quiz_total = 6
        self.quiz_count = 0
        self.quiz_question = ""
        self.quiz_choices = []
        self.quiz_correct_index = 0
        self.quiz_selected = -1
        self.quiz_feedback_timer = 0
        self.quiz_score_mathe = 0
        self.quiz_score_geo = 0

        # Streak system
        self.quiz_streak = 0
        self.quiz_bonus = 0
        self.quiz_show_bonus = 0
        self.quiz_time_limit = 180      # frames per question
        self.quiz_time_left = 180
        self.quiz_timed_out = False

        # Geo-Quiz: bereits gestellte Fragen merken, damit sich nichts
        # wiederholt, solange noch unverbrauchte Fragen im Pool sind
        self.geo_asked_questions = []
        self.geo_pool_order = []   # gemischte Reihenfolge fuer den aktuellen Durchlauf

        # Mathe typing
        self.mathe_answer = ""
        self.mathe_correct_answer = 0
        self.mathe_answer_color = 7
        self.mathe_correct_timer = 0
        self.mathe_wrong_timer = 0

        # ── Sternenregen-Finale ───────────────────────────────
        self.starrain_player_x = 80
        self.starrain_stars = []       # {"x","y","speed"}
        self.starrain_timer = 0
        self.starrain_max_time = 450
        self.starrain_score = 0
        self.starrain_missed = 0
        self.starrain_combo = 0
        self.starrain_best_combo = 0
        self.starrain_show_combo = 0
        self.starrain_done = False
        self.starrain_done_timer = 0

        # ── Jump-Level: Plattformen & Sterne ──────────────────
        self.jump_platforms = []       # {"x","y","w"}
        self.jump_stars = []           # {"x","y","taken"}
        self.jump_px = 76.0            # Spieler-Weltposition X
        self.jump_py = 0.0             # Spieler-Weltposition Y (0 = Boden)
        self.jump_vy = 0.0             # vertikale Geschwindigkeit
        self.jump_vx = 0.0
        self.jump_on_ground = True
        self.jump_camera_y = 0.0       # Kamera-Offset (scrollt nach oben)
        self.jump_score = 0
        self.jump_target = 30
        self.jump_lives = 3
        self.jump_done = False
        self.jump_done_timer = 0
        self.jump_fail_flash = 0
        self.jump_next_platform_y = 0  # naechste Hoehe, an der eine Plattform generiert wird
        self.jump_highest_y = 0        # hoechster je erreichter Punkt (fuer Generierung)
        self.jump_current_platform_y = 0.0  # y der Plattform, auf der der Spieler aktuell steht (fuer Kamera)

        # ── Confetti ─────────────────────────────────────────
        self.confetti = []

        # ── Particle effects ─────────────────────────────────
        self.particles = []     # {"x","y","dx","dy","color","life"}

        # ── Instruction blink ────────────────────────────────
        self.blink = 0

        # ── Screen shake (Politur) ────────────────────────────
        self.shake_timer = 0
        self.shake_strength = 0

        pyxel.run(self.update, self.draw)

    # =========================================================
    #  STERN-AWARD-SCREEN (Meilenstein-Sterne)
    # =========================================================
    def award_star(self, next_level, next_level_name=None):
        """Erhoeht die Anzahl gesammelter Meilenstein-Sterne um 1 und
        zeigt den Stern-Screen, bis der Spieler ENTER drueckt, bevor es
        zu next_level geht. Wird next_level=None uebergeben (letzter
        Stern), bleibt das Spiel auf dem Stern-Screen als Abschluss."""
        self.stars_earned = min(5, self.stars_earned + 1)
        self.star_award_next_level = next_level
        self.star_award_next_level_name = next_level_name
        self.play_sfx(self.SOUND_STAR)
        self.spawn_particles(80, 40, 10, 20)
        if next_level is None:
            self.level = "star_award_final"
        else:
            self.level = "star_award"

    def _update_star_award(self):
        if pyxel.btnp(pyxel.KEY_RETURN):
            self.level = self.star_award_next_level
            self.star_award_next_level = None
            self.star_award_next_level_name = None

    def _update_star_award_final(self):
        for _ in range(4):
            self.confetti.append({
                "x": random.randint(0, pyxel.width - 1),
                "y": 0,
                "color": random.randint(8, 15),
                "speed": random.uniform(1, 2.5)
            })
        for p in self.confetti:
            p["y"] += p["speed"]
        self.confetti = [p for p in self.confetti if p["y"] < pyxel.height]

        if pyxel.btnp(pyxel.KEY_R):
            self._full_reset()

    def _draw_star_award(self):
        pyxel.rectb(2, 2, 156, 116, 7)
        pyxel.rectb(3, 3, 154, 114, 10)
        t = "STERN VERDIENT!"
        pyxel.text((160 - len(t) * 4) // 2, 22, t, 10)

        # 5 Sterne mittig anordnen, die ersten `stars_earned` davon gelb
        n = 5
        spacing = 22
        total_w = spacing * (n - 1)
        start_x = (160 - total_w) // 2
        sy = 52
        for i in range(n):
            sx = start_x + i * spacing
            col = 10 if i < self.stars_earned else 5
            self._draw_big_star(sx, sy, col)

        sub = f"{self.stars_earned}/5 Sterne gesammelt"
        pyxel.text((160 - len(sub) * 4) // 2, 82, sub, 7)

        if self.star_award_next_level_name:
            nxt = f"Weiter zu {self.star_award_next_level_name}"
            pyxel.text((160 - len(nxt) * 4) // 2, 94, nxt, 7)

        if self.blink < 40:
            pyxel.text(40, 108, "ENTER = Start", 7)

    def _draw_star_award_final(self):
        pyxel.rectb(2, 2, 156, 116, 7)
        pyxel.rectb(3, 3, 154, 114, 10)
        t = "STERN VERDIENT!"
        pyxel.text((160 - len(t) * 4) // 2, 22, t, 10)

        n = 5
        spacing = 22
        total_w = spacing * (n - 1)
        start_x = (160 - total_w) // 2
        sy = 52
        for i in range(n):
            sx = start_x + i * spacing
            col = 10 if i < self.stars_earned else 5
            self._draw_big_star(sx, sy, col)

        sub = f"{self.stars_earned}/5 Sterne gesammelt"
        pyxel.text((160 - len(sub) * 4) // 2, 82, sub, 7)

        t2 = "ALLE STERNE GESCHAFFT!" if self.stars_earned >= 5 else "GLueCKWUNSCH!"
        pyxel.text((160 - len(t2) * 4) // 2, 94, t2, 11)

        for p in self.confetti:
            pyxel.pset(int(p["x"]), int(p["y"]), p["color"])

    def _draw_big_star(self, cx, cy, color):
        """Zeichnet einen groesseren, gut sichtbaren Stern um (cx, cy)."""
        pyxel.pset(cx, cy - 6, color)
        pyxel.pset(cx, cy - 5, color)
        pyxel.pset(cx, cy - 4, color)
        pyxel.pset(cx, cy - 3, color)
        pyxel.pset(cx, cy + 3, color)
        pyxel.pset(cx, cy + 4, color)
        pyxel.pset(cx, cy + 5, color)
        pyxel.pset(cx, cy + 6, color)
        pyxel.pset(cx - 6, cy, color)
        pyxel.pset(cx - 5, cy, color)
        pyxel.pset(cx - 4, cy, color)
        pyxel.pset(cx - 3, cy, color)
        pyxel.pset(cx + 3, cy, color)
        pyxel.pset(cx + 4, cy, color)
        pyxel.pset(cx + 5, cy, color)
        pyxel.pset(cx + 6, cy, color)
        pyxel.pset(cx, cy, color)
        pyxel.pset(cx - 1, cy - 1, color)
        pyxel.pset(cx + 1, cy - 1, color)
        pyxel.pset(cx - 1, cy + 1, color)
        pyxel.pset(cx + 1, cy + 1, color)
        pyxel.pset(cx - 2, cy - 2, color)
        pyxel.pset(cx + 2, cy - 2, color)
        pyxel.pset(cx - 2, cy + 2, color)
        pyxel.pset(cx + 2, cy + 2, color)

    # =========================================================
    #  PARTICLES
    # =========================================================
    def spawn_particles(self, x, y, color, count=8):
        for _ in range(count):
            angle = random.uniform(0, 2 * math.pi)
            speed = random.uniform(0.5, 2.5)
            self.particles.append({
                "x": float(x), "y": float(y),
                "dx": math.cos(angle) * speed,
                "dy": math.sin(angle) * speed,
                "color": color,
                "life": random.randint(12, 25)
            })

    def update_particles(self):
        for p in self.particles:
            p["x"] += p["dx"]
            p["y"] += p["dy"]
            p["dy"] += 0.08   # gravity
            p["life"] -= 1
        self.particles = [p for p in self.particles if p["life"] > 0]

    def trigger_shake(self, strength=3, duration=8):
        self.shake_timer = duration
        self.shake_strength = strength

    # =========================================================
    #  SOUND HELPER
    # =========================================================
    def play_sfx(self, sound_id):
        """Spielt einen Soundeffekt ab. Die Sounds (SOUND_CRASH/SOUND_STAR)
        werden in __init__ direkt im Code erzeugt, existieren also immer.
        Kanal 3 ist fuer Sound-Effekte reserviert (0-2 nutzt die Musik)."""
        try:
            pyxel.play(3, sound_id)
        except Exception:
            pass

    # =========================================================
    #  UPDATE
    # =========================================================
    def update(self):
        self.blink = (self.blink + 1) % 60
        self.update_particles()
        if self.shake_timer > 0:
            self.shake_timer -= 1

        if self.level == "star_award":
            self._update_star_award()

        elif self.level == "star_award_final":
            self._update_star_award_final()

        elif self.level == 1:
            if pyxel.btnp(pyxel.KEY_RIGHT):
                self.outfit = (self.outfit + 1) % 3
            if pyxel.btnp(pyxel.KEY_LEFT):
                self.outfit = (self.outfit - 1) % 3
            if pyxel.btnp(pyxel.KEY_RETURN):
                self.level = 1.5

        elif self.level == 1.5:
            if pyxel.btnp(pyxel.KEY_RETURN):
                self.level = 2

        elif self.level == 2:
            self._update_driving(
                timer_attr="timer", max_time_attr="max_time",
                cars_attr="cars", items_attr="items",
                lane_attr="current_lane", py_attr="player_y",
                speed_attr="speed", helmet_attr="has_helmet",
                lives_attr="lives", flash_attr="hit_flash",
                complete_attr="show_level_complete_text",
                next_level=2.5,
                reset_fn=self.reset_level2,
                car_interval=30, car_speed=3, item_interval=80,
                next_level_name="Level 2"
            )

        elif self.level == 2.5:
            if pyxel.btnp(pyxel.KEY_RETURN):
                self.level = 3
                self.reset_level3()

        elif self.level == 3:
            self._update_level3()

        elif self.level == 3.5:
            if pyxel.btnp(pyxel.KEY_RETURN):
                self.level = 4
                self.quiz_count = 0
                self.quiz_score_mathe = 0
                self.quiz_streak = 0
                self.quiz_bonus = 0
                self.mathe_answer = ""
                self.generate_quiz_mathe()
                self.quiz_time_left = self.quiz_time_limit

        elif self.level == 4:
            self._update_mathe()

        elif self.level == 4.5:
            if pyxel.btnp(pyxel.KEY_RETURN):
                self.level = 5
                self.quiz_count = 0
                self.quiz_score_geo = 0
                self.quiz_streak = 0
                self.geo_asked_questions = []
                self.geo_pool_order = []
                self.generate_quiz_mc("geo")
                self.quiz_time_left = self.quiz_time_limit

        elif self.level == 5:
            self._update_mc_quiz("geo")
            if self.quiz_count >= self.quiz_total:
                self.level = 5.5

        elif self.level == 5.5:
            if pyxel.btnp(pyxel.KEY_RETURN):
                self._init_starrain()
                self.award_star(6.5, "Level 4")

        elif self.level == 6.5:
            self._update_starrain()

        elif self.level == 7:
            if pyxel.btnp(pyxel.KEY_RETURN):
                self._init_jump()
                self.award_star(7.5, "Level 5")

        elif self.level == 7.5:
            self._update_jump()

    # ─── Unified driving update ───────────────────────────────
    def _update_driving(self, timer_attr, max_time_attr, cars_attr, items_attr,
                        lane_attr, py_attr, speed_attr, helmet_attr, lives_attr,
                        flash_attr, complete_attr,
                        next_level, reset_fn,
                        car_interval, car_speed, item_interval,
                        next_level_name=None):
        if getattr(self, flash_attr) > 0:
            setattr(self, flash_attr, getattr(self, flash_attr) - 1)

        timer = getattr(self, timer_attr) + 1
        setattr(self, timer_attr, timer)

        if timer > getattr(self, max_time_attr) and not getattr(self, complete_attr):
            setattr(self, complete_attr, True)
            self.award_star(next_level, next_level_name)
            return

        # Input
        if pyxel.btnp(pyxel.KEY_UP):
            setattr(self, lane_attr, max(0, getattr(self, lane_attr) - 1))
        if pyxel.btnp(pyxel.KEY_DOWN):
            setattr(self, lane_attr, min(2, getattr(self, lane_attr) + 1))
        setattr(self, py_attr, self.lanes[getattr(self, lane_attr)])

        # Cars
        spd = car_speed + (1 if getattr(self, speed_attr) > 1 else 0)
        cars = getattr(self, cars_attr)
        for car in cars:
            car["x"] -= spd
        setattr(self, cars_attr, [c for c in cars if c["x"] > -40])

        if pyxel.frame_count % car_interval == 0:
            getattr(self, cars_attr).append({"x": 160, "lane": random.randint(0, 2)})

        # Items
        items = getattr(self, items_attr)
        for item in items:
            item["x"] -= 2
        setattr(self, items_attr, [i for i in items if i["x"] > -20])

        if pyxel.frame_count % item_interval == 0:
            # Sterne kommen jetzt deutlich haeufiger vor als Schuh/Helm
            getattr(self, items_attr).append({
                "x": 160, "lane": random.randint(0, 2),
                "type": random.choices(
                    ["shoe", "helmet", "star"],
                    weights=[1, 1, 3]
                )[0]
            })

        # Collision with cars
        cur_lane = getattr(self, lane_attr)
        lives = getattr(self, lives_attr)
        cars = getattr(self, cars_attr)
        to_remove = []
        for car in cars:
            if car["lane"] == cur_lane and abs(car["x"] - self.player_x) < 18:
                if getattr(self, helmet_attr):
                    setattr(self, helmet_attr, False)
                    to_remove.append(car)
                    self.spawn_particles(self.player_x, getattr(self, py_attr), 9, 12)
                    self.play_sfx(self.SOUND_CRASH)
                    self.trigger_shake(2, 6)
                else:
                    lives -= 1
                    setattr(self, lives_attr, lives)
                    setattr(self, flash_attr, 30)
                    to_remove.append(car)
                    self.spawn_particles(self.player_x, getattr(self, py_attr), 8, 15)
                    self.play_sfx(self.SOUND_CRASH)
                    self.trigger_shake(4, 10)
                    if lives <= 0:
                        reset_fn()
                        return
        for car in to_remove:
            if car in getattr(self, cars_attr):
                getattr(self, cars_attr).remove(car)

        # Item pickup
        items = getattr(self, items_attr)
        to_remove = []
        for item in items:
            if item["lane"] == cur_lane and abs(item["x"] - self.player_x) < 15:
                self.spawn_particles(item["x"], self.lanes[item["lane"]], 10, 8)
                if item["type"] == "shoe":
                    setattr(self, speed_attr, 2)
                elif item["type"] == "helmet":
                    setattr(self, helmet_attr, True)
                elif item["type"] == "star":
                    if lives < 3:
                        setattr(self, lives_attr, min(3, lives + 1))
                    self.play_sfx(self.SOUND_STAR)
                to_remove.append(item)
        for item in to_remove:
            if item in getattr(self, items_attr):
                getattr(self, items_attr).remove(item)

    def _update_level3(self):
        if self.level3_hit_flash > 0:
            self.level3_hit_flash -= 1

        self.level3_timer += 1
        if self.level3_timer > self.level3_max_time and not self.show_level3_complete_text:
            self.show_level3_complete_text = True
            self.award_star(3.5, "Level 3")
            return

        if pyxel.btnp(pyxel.KEY_UP):
            self.level3_current_lane = max(0, self.level3_current_lane - 1)
        if pyxel.btnp(pyxel.KEY_DOWN):
            self.level3_current_lane = min(2, self.level3_current_lane + 1)
        self.level3_player_y = self.lanes[self.level3_current_lane]

        for car in self.level3_cars:
            car["x"] -= 5
            if pyxel.frame_count % 40 == car.get("id", 0) % 40:
                if random.random() < 0.4:
                    car["target_lane"] = random.randint(0, 2)
            target_y = float(self.lanes[car["target_lane"]])
            car["visual_y"] += (target_y - car["visual_y"]) * 0.1
            if abs(car["visual_y"] - self.lanes[car["target_lane"]]) < 3:
                car["lane"] = car["target_lane"]
        self.level3_cars = [c for c in self.level3_cars if c["x"] > -40]

        if pyxel.frame_count % 22 == 0:
            car_id = pyxel.frame_count
            sl = random.randint(0, 2)
            self.level3_cars.append({"x": 160, "lane": sl, "target_lane": sl,
                                     "visual_y": float(self.lanes[sl]), "id": car_id})

        for item in self.level3_items:
            item["x"] -= 3
        self.level3_items = [i for i in self.level3_items if i["x"] > -20]
        if pyxel.frame_count % 70 == 0:
            # Sterne kommen jetzt deutlich haeufiger vor als Schuh/Helm
            self.level3_items.append({
                "x": 160, "lane": random.randint(0, 2),
                "type": random.choices(
                    ["shoe", "helmet", "star"],
                    weights=[1, 1, 3]
                )[0]
            })

        to_remove = []
        for car in self.level3_cars:
            if car["lane"] == self.level3_current_lane and abs(car["x"] - self.player_x) < 18:
                if self.level3_has_helmet:
                    self.level3_has_helmet = False
                    to_remove.append(car)
                    self.spawn_particles(self.player_x, self.level3_player_y, 9, 12)
                    self.play_sfx(self.SOUND_CRASH)
                    self.trigger_shake(2, 6)
                else:
                    self.level3_lives -= 1
                    self.level3_hit_flash = 30
                    to_remove.append(car)
                    self.spawn_particles(self.player_x, self.level3_player_y, 8, 15)
                    self.play_sfx(self.SOUND_CRASH)
                    self.trigger_shake(4, 10)
                    if self.level3_lives <= 0:
                        self.reset_level3()
                        return
        for car in to_remove:
            if car in self.level3_cars:
                self.level3_cars.remove(car)

        to_remove = []
        for item in self.level3_items:
            if item["lane"] == self.level3_current_lane and abs(item["x"] - self.player_x) < 15:
                self.spawn_particles(item["x"], self.lanes[item["lane"]], 10, 8)
                if item["type"] == "shoe":
                    self.level3_speed = 2
                elif item["type"] == "helmet":
                    self.level3_has_helmet = True
                elif item["type"] == "star":
                    self.level3_lives = min(3, self.level3_lives + 1)
                    self.play_sfx(self.SOUND_STAR)
                to_remove.append(item)
        for item in to_remove:
            if item in self.level3_items:
                self.level3_items.remove(item)

    def _update_mathe(self):
        # Timer
        if self.mathe_correct_timer == 0 and self.mathe_wrong_timer == 0 and not self.quiz_timed_out:
            self.quiz_time_left -= 1
            if self.quiz_time_left <= 0:
                self.quiz_timed_out = True
                self.mathe_answer_color = 8
                self.mathe_wrong_timer = 45
                self.quiz_count += 1
                self.quiz_streak = 0

        if self.mathe_correct_timer > 0:
            self.mathe_correct_timer -= 1
            if self.mathe_correct_timer == 0:
                if self.quiz_count < self.quiz_total:
                    self.generate_quiz_mathe()
                    self.quiz_time_left = self.quiz_time_limit
                    self.quiz_timed_out = False

        if self.mathe_wrong_timer > 0:
            self.mathe_wrong_timer -= 1
            if self.mathe_wrong_timer == 0:
                self.mathe_answer = ""
                self.mathe_answer_color = 7
                self.quiz_timed_out = False
                if self.quiz_count < self.quiz_total:
                    self.generate_quiz_mathe()
                    self.quiz_time_left = self.quiz_time_limit

        if self.mathe_correct_timer == 0 and self.mathe_wrong_timer == 0 and not self.quiz_timed_out:
            for i in range(10):
                if pyxel.btnp(getattr(pyxel, f"KEY_{i}")):
                    self.mathe_answer += str(i)
            if pyxel.btnp(pyxel.KEY_BACKSPACE):
                self.mathe_answer = self.mathe_answer[:-1]
            if self.mathe_answer != "":
                try:
                    val = int(self.mathe_answer)
                    if val == self.mathe_correct_answer:
                        self.mathe_answer_color = 10
                        self.mathe_correct_timer = 35
                        self.quiz_score_mathe += 1
                        self.quiz_streak += 1
                        if self.quiz_streak >= 3:
                            self.quiz_bonus += 1
                            self.quiz_show_bonus = 40
                        self.quiz_count += 1
                        self.spawn_particles(80, 60, 10, 10)
                    elif len(self.mathe_answer) >= len(str(self.mathe_correct_answer)):
                        self.mathe_answer_color = 8
                        self.mathe_wrong_timer = 35
                        self.quiz_count += 1
                        self.quiz_streak = 0
                except ValueError:
                    pass

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

        if self.quiz_count >= self.quiz_total:
            self.level = 4.5

    def _update_mc_quiz(self, fach):
        if self.quiz_show_bonus > 0:
            self.quiz_show_bonus -= 1

        if self.quiz_feedback_timer > 0:
            self.quiz_feedback_timer -= 1
            if self.quiz_feedback_timer == 0:
                self.quiz_selected = -1
                self.quiz_timed_out = False
                if self.quiz_count < self.quiz_total:
                    self.generate_quiz_mc(fach)
                    self.quiz_time_left = self.quiz_time_limit
            return

        # Time limit
        self.quiz_time_left -= 1
        if self.quiz_time_left <= 0 and not self.quiz_timed_out:
            self.quiz_timed_out = True
            self.quiz_selected = -1
            self.quiz_count += 1
            self.quiz_streak = 0
            self.quiz_feedback_timer = 50
            return

        if pyxel.btnp(pyxel.KEY_A):
            self.quiz_selected = 0
            self._check_mc(fach)
        elif pyxel.btnp(pyxel.KEY_B):
            self.quiz_selected = 1
            self._check_mc(fach)
        elif pyxel.btnp(pyxel.KEY_C):
            self.quiz_selected = 2
            self._check_mc(fach)

    def _check_mc(self, fach):
        self.quiz_count += 1
        correct = (self.quiz_selected == self.quiz_correct_index)
        if correct:
            self.quiz_score_geo += 1
            self.quiz_streak += 1
            if self.quiz_streak >= 3:
                self.quiz_bonus += 1
                self.quiz_show_bonus = 50
            self.spawn_particles(80, 70, 10, 12)
        else:
            self.quiz_streak = 0
            self.spawn_particles(80, 70, 8, 8)
        self.quiz_feedback_timer = 50

    def _init_starrain(self):
        self.starrain_player_x = 76
        self.starrain_stars = []
        self.starrain_timer = 0
        self.starrain_score = 0
        self.starrain_missed = 0
        self.starrain_combo = 0
        self.starrain_best_combo = 0
        self.starrain_show_combo = 0
        self.starrain_done = False
        self.starrain_done_timer = 0

    def _update_starrain(self):
        if self.starrain_done:
            self.starrain_done_timer -= 1
            if self.starrain_done_timer <= 0:
                self.level = 7
            return

        self.starrain_timer += 1
        if self.starrain_show_combo > 0:
            self.starrain_show_combo -= 1

        if self.starrain_timer >= self.starrain_max_time:
            self.starrain_done = True
            self.starrain_done_timer = 90
            self.spawn_particles(80, 60, 10, 25)
            return

        # Spielerbewegung (links/rechts, schnelle Reaktion)
        move_speed = 4
        if pyxel.btn(pyxel.KEY_LEFT):
            self.starrain_player_x = max(4, self.starrain_player_x - move_speed)
        if pyxel.btn(pyxel.KEY_RIGHT):
            self.starrain_player_x = min(128, self.starrain_player_x + move_speed)

        # Schwierigkeit steigt mit der Zeit
        progress = self.starrain_timer / self.starrain_max_time
        spawn_chance = 0.10 + progress * 0.12
        fall_speed_base = 1.3 + progress * 1.4

        if random.random() < spawn_chance:
            self.starrain_stars.append({
                "x": float(random.randint(5, 150)),
                "y": -5.0,
                "speed": fall_speed_base + random.uniform(-0.3, 0.5)
            })

        catch_y_min, catch_y_max = 95, 115
        remaining = []
        for star in self.starrain_stars:
            star["y"] += star["speed"]
            # Eingefangen?
            if (catch_y_min <= star["y"] <= catch_y_max and
                    abs(star["x"] - (self.starrain_player_x + 16)) < 14):
                self.starrain_score += 1
                self.starrain_combo += 1
                self.starrain_best_combo = max(self.starrain_best_combo, self.starrain_combo)
                if self.starrain_combo % 5 == 0:
                    self.starrain_show_combo = 35
                self.spawn_particles(star["x"], star["y"], 10, 10)
                self.play_sfx(self.SOUND_STAR)
                continue
            if star["y"] > 122:
                self.starrain_missed += 1
                self.starrain_combo = 0
                continue
            remaining.append(star)
        self.starrain_stars = remaining

    # =========================================================
    #  JUMP-LEVEL: Plattform-Spruenge nach oben
    # =========================================================
    def _init_jump(self):
        self.jump_platforms = []
        self.jump_stars = []
        self.jump_px = 76.0
        self.jump_py = 0.0
        self.jump_vy = 0.0
        self.jump_vx = 0.0
        self.jump_on_ground = True
        self.jump_camera_y = 70.0   # Start so, dass Boden-Plattform sichtbar ist
        self.jump_score = 0
        self.jump_target = 30
        self.jump_lives = 3
        self.jump_done = False
        self.jump_done_timer = 0
        self.jump_fail_flash = 0
        self.jump_highest_y = 0
        self.jump_current_platform_y = 0.0

        # Start-Plattform (breit, sicher)
        self.jump_platforms.append({"x": 56.0, "y": 0.0, "w": 48})
        
        # Spieler direkt auf die Plattform setzen
        self.jump_px = 72.0
        self.jump_py = -30.0
        self.jump_vy = 0.0
        self.jump_on_ground = True
        
        # Diese Plattform als sichere Plattform merken
        self.jump_current_platform_y = 0.0
        self.jump_highest_y = 0.0
        # Spieler direkt auf die Plattform setzen
        self.jump_px = 72.0
        self.jump_py = -30.0
        self.jump_vy = 0.0
        self.jump_on_ground = True
        
        # Diese Plattform als sichere Plattform merken
        self.jump_current_platform_y = 0.0
        self.jump_highest_y = 0.0

        # Erste Reihe von Plattformen generieren
        self.jump_next_platform_y = 0.0
        for _ in range(14):
            self._spawn_jump_platform()

    def _spawn_jump_platform(self):
        """Erzeugt die naechste Plattform weiter oben, zunehmend schwieriger
        (schmaler & weiter versetzt), plus mit Chance einen schwebenden Stern."""
        gap = random.uniform(26, 38)
        self.jump_next_platform_y -= gap
        y = self.jump_next_platform_y

        # Schwierigkeit steigt mit der Hoehe (negativere y = hoeher)
        difficulty = min(abs(y) / 800.0, 1.0)
        width = max(18, int(34 - difficulty * 12))
        x = random.uniform(8, 160 - 8 - width)

        self.jump_platforms.append({"x": x, "y": y, "w": width})

        # Stern ueber der Plattform schweben lassen (hohe Chance)
        if random.random() < 0.75:
            star_x = x + width / 2 + random.uniform(-6, 6)
            star_y = y - random.uniform(14, 20)
            self.jump_stars.append({"x": star_x, "y": star_y, "taken": False})

    def _update_jump(self):
        if self.jump_done:
            self.jump_done_timer -= 1
            if self.jump_done_timer <= 0:
                self.award_star(None, None)
            return

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

        GRAVITY = 0.55
        JUMP_VELOCITY = -8.2
        MOVE_SPEED = 2.0
        MAX_FALL = 9.0

        # Horizontale Bewegung
        self.jump_vx = 0.0
        if pyxel.btn(pyxel.KEY_LEFT):
            self.jump_vx = -MOVE_SPEED
        if pyxel.btn(pyxel.KEY_RIGHT):
            self.jump_vx = MOVE_SPEED
        self.jump_px += self.jump_vx
        self.jump_px = max(2, min(160 - 18, self.jump_px))

        # Springen
        if self.jump_on_ground and (pyxel.btnp(pyxel.KEY_SPACE) or pyxel.btnp(pyxel.KEY_UP)):
            self.jump_vy = JUMP_VELOCITY
            self.jump_on_ground = False

        # Schwerkraft
        self.jump_vy += GRAVITY
        self.jump_vy = min(self.jump_vy, MAX_FALL)
        self.jump_py += self.jump_vy

        # Spielerfuesse (fuer Kollision mit Plattformen)
        feet_y = self.jump_py + 30
        player_w = 16
        landed_this_frame = False

        if self.jump_vy >= 0:  # nur beim Fallen auf Plattformen pruefen
            for plat in self.jump_platforms:
                plat_top = plat["y"]
                if (feet_y >= plat_top and feet_y <= plat_top + 8 and
                        self.jump_px + player_w > plat["x"] and
                        self.jump_px < plat["x"] + plat["w"]):
                    # gelandet!
                    self.jump_py = plat_top - 30
                    self.jump_vy = 0.0
                    self.jump_on_ground = True
                    landed_this_frame = True
                    self.jump_current_platform_y = plat_top
                    break

        if not landed_this_frame and self.jump_on_ground:
            # War auf einer Plattform, prueft ob er sie verlassen hat (Fall beginnt)
            still_on_any = False
            for plat in self.jump_platforms:
                plat_top = plat["y"]
                if (abs((self.jump_py + 30) - plat_top) < 2 and
                        self.jump_px + player_w > plat["x"] and
                        self.jump_px < plat["x"] + plat["w"]):
                    still_on_any = True
                    self.jump_current_platform_y = plat_top
                    break
            if not still_on_any:
                self.jump_on_ground = False

        # Absturz-Check: faellt zu tief unter die hoechste je erreichte Plattform
        if not self.jump_on_ground and (self.jump_py + 30) > (self.jump_highest_y + 140):
            self.jump_lives -= 1
            self.jump_fail_flash = 25
            self.play_sfx(self.SOUND_CRASH)
            self.spawn_particles(self.jump_px + 8, 60, 8, 14)
            self.trigger_shake(4, 10)
            if self.jump_lives <= 0:
                self.jump_done = True
                self.jump_done_timer = 90
                return
            # Auf die zuletzt sichere Plattform respawnen
            safe_y = self.jump_highest_y
            self.jump_py = safe_y - 30
            self.jump_vy = 0.0
            self.jump_on_ground = True
            self.jump_current_platform_y = safe_y

        # Hoechsten Punkt tracken (fuer Fail-Distanz & Kamera)
        if self.jump_py < self.jump_highest_y:
            self.jump_highest_y = self.jump_py

        # ── Kamera: Spieler UND aktuelle Standplattform bleiben immer sichtbar ──
        # Wir berechnen einen Zielwert, der den Spieler bei ~45% der Bildhoehe
        # haelt, begrenzen die Bewegung aber so, dass die Plattform, auf der er
        # zuletzt stand (oder gerade steht), nicht aus dem Sichtfeld faellt.
        # Das verhindert sowohl ein zu schnelles "Wegscrollen" nach oben als
        # auch ein Haengenbleiben weit ausserhalb des Bildes nach einem Sturz.
        desired_camera = -self.jump_py + 55

        # Referenzplattform: die, auf der der Spieler aktuell steht, sonst
        # die letzte sichere (jump_current_platform_y)
        ref_platform_y = self.jump_current_platform_y
        # Kamera darf die Referenzplattform nicht ueber screen_y=100 schieben
        # (zu weit unten) und nicht unter screen_y=15 (zu weit oben/raus)
        min_camera_for_platform = 15 - ref_platform_y   # Plattform nicht zu weit oben
        max_camera_for_platform = 100 - ref_platform_y  # Plattform nicht zu weit unten

        desired_camera = max(min_camera_for_platform, min(max_camera_for_platform, desired_camera))

        # Sanftes Nachfuehren statt hartem Sprung, fuehlt sich besser an
        cam_diff = desired_camera - self.jump_camera_y
        self.jump_camera_y += cam_diff * 0.18

        # Sicherheitsnetz: Spieler selbst darf NIE komplett aus dem Bild
        # verschwinden, egal was die Plattform-Logik oben berechnet hat.
        player_screen_y = self.jump_py + self.jump_camera_y
        if player_screen_y < -10:
            self.jump_camera_y += (-10 - player_screen_y)
        elif player_screen_y > 95:
            self.jump_camera_y += (95 - player_screen_y)

        # Sterne einsammeln
        for star in self.jump_stars:
            if star["taken"]:
                continue
            dx = (self.jump_px + 8) - star["x"]
            dy = (self.jump_py + 16) - star["y"]
            if dx * dx + dy * dy < 13 * 13:
                star["taken"] = True
                self.jump_score += 1
                self.spawn_particles(star["x"], star["y"], 10, 12)
                self.play_sfx(self.SOUND_STAR)
                if self.jump_score >= self.jump_target:
                    self.jump_done = True
                    self.jump_done_timer = 100
                    self.spawn_particles(80, 60, 10, 30)
                    return

        # Neue Plattformen nachgenerieren, wenn Spieler sich naehert
        while self.jump_next_platform_y > self.jump_py - 500:
            self._spawn_jump_platform()

        # Alte Plattformen/Sterne weit unterhalb entfernen (Performance)
        cutoff = self.jump_py + 250
        self.jump_platforms = [p for p in self.jump_platforms if p["y"] < cutoff]
        self.jump_stars = [s for s in self.jump_stars if s["y"] < cutoff]

    def _full_reset(self):
        self.level = 1
        self.outfit = 0
        self.stars_earned = 0
        self.star_award_next_level = None
        self.star_award_next_level_name = None
        self.lives = 3
        self.level3_lives = 3
        self.timer = 0
        self.level3_timer = 0
        self.quiz_count = 0
        self.quiz_score_mathe = 0
        self.quiz_score_geo = 0
        self.quiz_streak = 0
        self.quiz_bonus = 0
        self.geo_asked_questions = []
        self.geo_pool_order = []
        self.starrain_score = 0
        self.starrain_missed = 0
        self.starrain_combo = 0
        self.starrain_best_combo = 0
        self.starrain_done = False
        self.starrain_stars = []
        self.jump_score = 0
        self.jump_lives = 3
        self.jump_done = False
        self.jump_platforms = []
        self.jump_stars = []
        self.confetti = []
        self.particles = []
        self.cars = []
        self.items = []
        self.level3_cars = []
        self.level3_items = []
        self.reset_level2()
        self.reset_level3()

    # =========================================================
    #  DRAW
    # =========================================================
    def draw(self):
        pyxel.cls(0)

        # Screen-Shake: kleiner Kamera-Versatz bei Treffern, macht Crashes
        # spuerbarer ohne das Spiel unspielbar zu machen.
        shake_x, shake_y = 0, 0
        if self.shake_timer > 0:
            shake_x = random.randint(-self.shake_strength, self.shake_strength)
            shake_y = random.randint(-self.shake_strength, self.shake_strength)
            pyxel.camera(shake_x, shake_y)
        else:
            pyxel.camera(0, 0)

        if self.level == "star_award":
            self._draw_star_award()

        elif self.level == "star_award_final":
            self._draw_star_award_final()

        elif self.level == 1:
            self._draw_title()

        elif self.level == 1.5:
            self._draw_intro_screen()

        elif self.level == 2:
            self._draw_driving(
                timer_attr="timer", max_time_attr="max_time",
                cars_attr="cars", items_attr="items",
                lane_attr="current_lane", py_attr="player_y",
                helmet_attr="has_helmet", speed_attr="speed",
                lives_attr="lives", flash_attr="hit_flash",
                complete_attr="show_level_complete_text",
                next_stage_name="Level 2",
                stage_name="Level 1"
            )

        elif self.level == 2.5:
            self._draw_instruction("LEVEL 2",
                ["Die Autos kommen",
                 "jetzt viel schneller!"])

        elif self.level == 3:
            self._draw_level3()

        elif self.level == 3.5:
            self._draw_instruction("LEVEL 3",
                ["Mathequiz",
                 "Antwort eintippen"])

        elif self.level == 4:
            self._draw_mathe()

        elif self.level == 4.5:
            self._draw_score_transition("LEVEL 3",
                f"Mathe: {self.quiz_score_mathe}/{self.quiz_total} Punkte",
                f"Bonus: +{self.quiz_bonus}")

        elif self.level == 5:
            self._draw_mc_screen("Geographie")

        elif self.level == 5.5:
            self._draw_score_transition("LEVEL 4",
                f"Geo: {self.quiz_score_geo}/{self.quiz_total} Punkte",
                "Du musst alle Sterne fangen!",
                control_lines=["Pfeiltasten Links/Rechts", "um dich zu bewegen!"])

        elif self.level == 6.5:
            self._draw_starrain()

        elif self.level == 7:
            self._draw_score_transition("LEVEL 5",
                f"Sterne gefangen: {self.starrain_score}",
                "Springe und fange die Sterne ein!",
                control_lines=["Links/Rechts laufen,", "LEERTASTE = springen!"])

        elif self.level == 7.5:
            self._draw_jump()

        # Particles always on top
        self._draw_particles()

        if self.shake_timer > 0:
            pyxel.camera(0, 0)

    # ─── Title Screen ─────────────────────────────────────────
    def _draw_title(self):
        pyxel.bltm(0, 0, 0, 0, 0, 1024, 1024, 0)
        pyxel.text(53, 2, "BRAIN & BEAUTY", 1)
        pyxel.text(53, 25, "Outfit waehlen:", 1)
        pyxel.text(53, 107, "ENTER = Start", 7)
        px, py = 64, 40
        pyxel.blt(px, py, 0, 0, 16, 32, 32, 8)
        pyxel.blt(px + 8, py + 16, 0, self.outfit * 16, 48, 16, 16, 7)
        mid_y = py + 16
        pyxel.line(px - 8, mid_y - 4, px - 12, mid_y, 7)
        pyxel.line(px - 8, mid_y + 4, px - 12, mid_y, 7)
        pyxel.line(px + 40, mid_y - 4, px + 44, mid_y, 7)
        pyxel.line(px + 40, mid_y + 4, px + 44, mid_y, 7)

    # ─── Intro-Screen nach der Outfit-Wahl ─────────────────────
    def _draw_intro_screen(self):
        pyxel.cls(0)
        text = "Sammle alle fuenf Sterne in den Levels und gewinne!"
        max_chars_per_line = 26
        words = text.split(" ")
        lines = []
        current = ""
        for w in words:
            test = (current + " " + w).strip()
            if len(test) > max_chars_per_line:
                lines.append(current)
                current = w
            else:
                current = test
        if current:
            lines.append(current)

        total_h = len(lines) * 10
        start_y = (120 - total_h) // 2
        for i, line in enumerate(lines):
            lx = (160 - len(line) * 4) // 2
            pyxel.text(lx, start_y + i * 10, line, 7)

        if self.blink < 40:
            pyxel.text(56, 105, "ENTER = Start", 7)

    # ─── Generic instruction screen ───────────────────────────
    def _draw_instruction(self, title, lines):
        pyxel.rectb(2, 2, 156, 116, 7)
        pyxel.rectb(3, 3, 154, 114, 1)
        cx = (160 - len(f"-- {title} --") * 4) // 2
        pyxel.text(cx, 8, f"-- {title} --", 10)
        for i, line in enumerate(lines):
            pyxel.text(8, 24 + i * 12, line, 7)
        if self.blink < 40:
            pyxel.text(40, 108, "ENTER = Start", 7)

    # ─── Score transition screen ──────────────────────────────
    def _draw_score_transition(self, title, score_line, hint_line, control_lines=None):
        pyxel.rectb(2, 2, 156, 116, 7)
        pyxel.rectb(3, 3, 154, 114, 1)
        cx = (160 - len(f"-- {title} --") * 4) // 2
        pyxel.text(cx, 8, f"-- {title} --", 10)
        pyxel.text(10, 40, score_line, 7)
        pyxel.text(10, 55, hint_line, 10)
        if control_lines is None:
            control_lines = ["Druecke A / B / C", "fuer deine Antwort!"]
        for i, line in enumerate(control_lines):
            pyxel.text(10, 70 + i * 10, line, 7)
        if self.blink < 40:
            pyxel.text(40, 108, "ENTER = Start", 7)

    # ─── Driving draw ─────────────────────────────────────────
    def _draw_driving(self, timer_attr, max_time_attr, cars_attr, items_attr,
                      lane_attr, py_attr, helmet_attr, speed_attr,
                      lives_attr, flash_attr, complete_attr, next_stage_name,
                      stage_name="Level 1"):
        # Flash overlay on hit
        if getattr(self, flash_attr) > 0 and (getattr(self, flash_attr) // 5) % 2 == 0:
            pyxel.cls(8)
        else:
            pyxel.bltm(0, 0, 1, 0, 0, 256, 256)

        pyxel.text(35, 2, stage_name, 0)

        # Player sprite
        py = getattr(self, py_attr)
        pyxel.blt(self.player_x, py, 0, 0, 16, 32, 32, 8)
        pyxel.blt(self.player_x + 8, py + 16, 0, self.outfit * 16, 48, 16, 16, 7)

        # Cars
        for car in getattr(self, cars_attr):
            pyxel.blt(car["x"], self.lanes[car["lane"]], 0, 32, 16, 32, 32, 8)

        # Items
        for item in getattr(self, items_attr):
            iy = self.lanes[item["lane"]]
            if item["type"] == "shoe":
                pyxel.blt(item["x"], iy + 4, 0, 64, 32, 16, 16, 8)
            elif item["type"] == "helmet":
                pyxel.blt(item["x"], iy + 4, 0, 48, 48, 16, 16, 8)
            elif item["type"] == "star":
                # Draw a simple star with pixels
                sx, sy = item["x"], iy + 8
                pyxel.pset(sx + 4, sy, 10)
                pyxel.pset(sx + 4, sy + 2, 10)
                pyxel.pset(sx + 4, sy + 4, 10)
                pyxel.pset(sx + 2, sy + 2, 10)
                pyxel.pset(sx + 6, sy + 2, 10)

        # HUD – Zeit: nur der farbige Balken wird angezeigt,
        # der Text "Zeit: X" bleibt ausgeblendet.
        elapsed = getattr(self, timer_attr)
        total = getattr(self, max_time_attr)
        remaining = total - elapsed
        bar_w = int((remaining / total) * 80)
        pyxel.rect(75, 2, 80, 4, 1)
        pyxel.rect(75, 2, bar_w, 4, 10 if remaining > total // 3 else 8)

        # Lives (3 grosse, gut sichtbare Herzen oben links)
        for i in range(3):
            hx = 3 + i * 9
            hy = 2
            if i < getattr(self, lives_attr):
                # Gefuelltes Herz: rot mit weisser Umrandung
                pyxel.rect(hx, hy, 7, 6, 8)
                pyxel.pset(hx, hy - 1, 8)
                pyxel.pset(hx + 6, hy - 1, 8)
                pyxel.rectb(hx - 1, hy - 1, 9, 8, 7)
            else:
                # Leeres Herz: nur dunkler Umriss
                pyxel.rectb(hx - 1, hy - 1, 9, 8, 1)

        # Power-up-Status sauber untereinander (schwarze Schrift)
        hud_y = 14
        if getattr(self, helmet_attr):
            pyxel.text(3, hud_y, "[HELM]", 0)
            hud_y += 8
        if getattr(self, speed_attr) > 1:
            pyxel.text(3, hud_y, "[BOOST]", 0)

    def _draw_level3(self):
        if self.level3_hit_flash > 0 and (self.level3_hit_flash // 5) % 2 == 0:
            pyxel.cls(8)
        else:
            pyxel.bltm(0, 0, 1, 0, 0, 256, 256)

        pyxel.text(35, 2, "Level 2", 0)
        py = self.level3_player_y
        pyxel.blt(self.player_x, py, 0, 0, 16, 32, 32, 8)
        pyxel.blt(self.player_x + 8, py + 16, 0, self.outfit * 16, 48, 16, 16, 7)

        for car in self.level3_cars:
            pyxel.blt(car["x"], int(car["visual_y"]), 0, 32, 16, 32, 32, 8)

        for item in self.level3_items:
            iy = self.lanes[item["lane"]]
            if item["type"] == "shoe":
                pyxel.blt(item["x"], iy + 4, 0, 64, 32, 16, 16, 8)
            elif item["type"] == "helmet":
                pyxel.blt(item["x"], iy + 4, 0, 48, 48, 16, 16, 8)
            elif item["type"] == "star":
                sx, sy = item["x"], iy + 8
                pyxel.pset(sx + 4, sy, 10)
                pyxel.pset(sx + 4, sy + 2, 10)
                pyxel.pset(sx + 4, sy + 4, 10)
                pyxel.pset(sx + 2, sy + 2, 10)
                pyxel.pset(sx + 6, sy + 2, 10)

        # Zeit: nur der farbige Balken wird angezeigt,
        # der Text "Zeit: X" bleibt ausgeblendet.
        elapsed = self.level3_timer
        total = self.level3_max_time
        remaining = total - elapsed
        bar_w = int((remaining / total) * 80)
        pyxel.rect(75, 2, 80, 4, 1)
        pyxel.rect(75, 2, bar_w, 4, 10 if remaining > total // 3 else 8)

        # Lives (3 grosse, gut sichtbare Herzen)
        for i in range(3):
            hx = 3 + i * 9
            hy = 2
            if i < self.level3_lives:
                pyxel.rect(hx, hy, 7, 6, 8)
                pyxel.pset(hx, hy - 1, 8)
                pyxel.pset(hx + 6, hy - 1, 8)
                pyxel.rectb(hx - 1, hy - 1, 9, 8, 7)
            else:
                pyxel.rectb(hx - 1, hy - 1, 9, 8, 1)

        # Power-ups untereinander (schwarze Schrift)
        hud_y = 14
        if self.level3_has_helmet:
            pyxel.text(3, hud_y, "[HELM]", 0)
            hud_y += 8
        if self.level3_speed > 1:
            pyxel.text(3, hud_y, "[BOOST]", 0)

    # ─── Mathe screen ─────────────────────────────────────────
    def _draw_mathe(self):
        pyxel.bltm(0, 0, 2, 0, 0, 256, 256)
        pyxel.text(5, 2, "-- MATHEMATIK --", 10)
        pyxel.text(5, 10, f"Frage: {min(self.quiz_count+1, self.quiz_total)}/{self.quiz_total}", 7)

        # Progress bar
        pyxel.rect(5, 18, 150, 3, 1)
        pyxel.rect(5, 18, min(self.quiz_count, self.quiz_total) * 25, 3, 10)

        # Time bar
        tbar = int((self.quiz_time_left / self.quiz_time_limit) * 150)
        tcolor = 10 if self.quiz_time_left > self.quiz_time_limit // 3 else 8
        pyxel.rect(5, 22, 150, 3, 1)
        pyxel.rect(5, 22, tbar, 3, tcolor)

        # Spielfigur rechts, ausserhalb der Frage-Box
        fig_x = 122
        pyxel.blt(fig_x, 70, 0, 0, 16, 32, 32, 8)
        pyxel.blt(fig_x + 8, 86, 0, self.outfit * 16, 48, 16, 16, 7)

        if self.quiz_count < self.quiz_total:
            pyxel.rect(4, 36, 112, 30, 1)
            pyxel.rectb(4, 36, 112, 30, 7)
            pyxel.text(8, 40, self.quiz_question, 7)
            if self.quiz_timed_out:
                pyxel.text(8, 52, f"Zeit! Antwort: {self.mathe_correct_answer}", 8)
            else:
                pyxel.text(8, 52, "Antwort: " + self.mathe_answer + ("_" if self.blink < 30 else " "), self.mathe_answer_color)

            # Streak display
            if self.quiz_streak >= 2:
                pyxel.text(5, 70, f"Streak: {self.quiz_streak}!", 9)
            if self.quiz_show_bonus > 0:
                pyxel.text(5, 80, "BONUS +1!", 10)

    # ─── MC Quiz screen ───────────────────────────────────────
    def _draw_mc_screen(self, fach):
        pyxel.bltm(0, 0, 2, 0, 0, 256, 256)
        pyxel.text(5, 2, f"-- {fach.upper()} --", 10)
        pyxel.text(5, 10, f"Frage: {min(self.quiz_count+1, self.quiz_total)}/{self.quiz_total}", 7)

        # Progress bar
        pyxel.rect(5, 18, 150, 3, 1)
        pyxel.rect(5, 18, min(self.quiz_count, self.quiz_total) * 25, 3, 10)

        # Time bar
        tbar = int((self.quiz_time_left / self.quiz_time_limit) * 150)
        tcolor = 10 if self.quiz_time_left > self.quiz_time_limit // 3 else 8
        pyxel.rect(5, 22, 150, 3, 1)
        pyxel.rect(5, 22, tbar, 3, tcolor)

        if self.quiz_count < self.quiz_total:
            # Question box
            pyxel.rect(2, 27, 156, 14, 1)
            pyxel.rectb(2, 27, 156, 14, 7)
            # Word wrap question if needed
            q = self.quiz_question
            if len(q) > 28:
                pyxel.text(5, 30, q[:28], 7)
                pyxel.text(5, 37, q[28:], 7)
            else:
                pyxel.text(5, 32, q, 7)

            # Spielfigur rechts neben den Antwortfeldern
            fig_x = 110
            pyxel.blt(fig_x, 44, 0, 0, 16, 32, 32, 8)
            pyxel.blt(fig_x + 8, 60, 0, self.outfit * 16, 48, 16, 16, 7)

            choices_data = [
                (self.quiz_choices[0], 0),
                (self.quiz_choices[1], 1),
                (self.quiz_choices[2], 2),
            ]
            y_positions = [46, 64, 82]
            labels_abc = ["A)", "B)", "C)"]
            box_w = 100

            for (text, cidx), y in zip(choices_data, y_positions):
                # Background color
                if self.quiz_selected == cidx and self.quiz_feedback_timer > 0:
                    bg = 3 if cidx == self.quiz_correct_index else 2
                elif cidx == self.quiz_correct_index and self.quiz_selected != -1 and self.quiz_feedback_timer > 0:
                    bg = 3
                elif self.quiz_timed_out and cidx == self.quiz_correct_index:
                    bg = 3
                else:
                    bg = 1
                txt_col = 7 if bg != 7 else 0
                pyxel.rect(2, y, box_w, 15, bg)
                pyxel.rectb(2, y, box_w, 15, 7)
                pyxel.text(5, y + 4, labels_abc[choices_data.index((text, cidx))] + " " + text, txt_col)

            if self.quiz_timed_out:
                pyxel.text(15, 100, "ZEIT ABGELAUFEN!", 8)
            else:
                pyxel.text(5, 100, "A / B / C druecken", 7)

            # Streak
            if self.quiz_streak >= 2:
                pyxel.text(5, 108, f"Streak {self.quiz_streak}x!", 9)
            if self.quiz_show_bonus > 0:
                pyxel.text(90, 108, "BONUS +1!", 10)

    # ─── Sternenregen-Finale Screen ────────────────────────────
    def _draw_starrain(self):
        # Nachthimmel-Hintergrund (Verlauf simuliert durch zwei Farben)
        pyxel.rect(0, 0, 160, 90, 1)
        pyxel.rect(0, 90, 160, 30, 0)

        if not self.starrain_done:
            # Hintergrund-Deko-Sterne (statisch, schimmern leicht)
            for i in range(12):
                sx = (i * 37 + 11) % 155 + 2
                sy = (i * 23 + 5) % 70 + 3
                if (pyxel.frame_count // 8 + i) % 5 != 0:
                    pyxel.pset(sx, sy, 7)

            # Fallende Sterne (Fangobjekte)
            for star in self.starrain_stars:
                sx, sy = int(star["x"]), int(star["y"])
                col = 10
                pyxel.pset(sx, sy - 2, col)
                pyxel.pset(sx, sy + 2, col)
                pyxel.pset(sx - 2, sy, col)
                pyxel.pset(sx + 2, sy, col)
                pyxel.pset(sx, sy, col)
                pyxel.pset(sx - 1, sy - 1, col)
                pyxel.pset(sx + 1, sy - 1, col)
                pyxel.pset(sx - 1, sy + 1, col)
                pyxel.pset(sx + 1, sy + 1, col)

            # Boden-Streifen, damit klar ist wo man faengt
            pyxel.rect(0, 95, 160, 1, 5)

            # Spielfigur (folgt starrain_player_x)
            px = self.starrain_player_x
            pyxel.blt(px, 88, 0, 0, 16, 32, 32, 8)
            pyxel.blt(px + 8, 104, 0, self.outfit * 16, 48, 16, 16, 7)

            # HUD oben
            pyxel.rect(0, 0, 160, 11, 1)
            pyxel.text(3, 2, f"Sterne: {self.starrain_score}", 10)
            remaining = max(0, self.starrain_max_time - self.starrain_timer)
            pyxel.text(85, 2, f"Zeit: {remaining // 60 + 1}s", 7)

            # Zeit-Fortschrittsbalken
            bar_w = int((remaining / self.starrain_max_time) * 150)
            pyxel.rect(5, 9, 150, 2, 5)
            pyxel.rect(5, 9, bar_w, 2, 10 if remaining > self.starrain_max_time // 3 else 8)

            # Combo-Anzeige
            if self.starrain_combo >= 3:
                pyxel.text(3, 14, f"Combo x{self.starrain_combo}!", 9)
            if self.starrain_show_combo > 0:
                pyxel.text(55, 50, "SUPER!", 10)

            pyxel.text(3, 112, "<- Links / Rechts ->", 7)
        else:
            # Ergebnis-Anzeige
            pyxel.rectb(2, 2, 156, 116, 7)
            pyxel.rectb(3, 3, 154, 114, 10)
            t = "LEVEL 4 GESCHAFFT!"
            pyxel.text((160 - len(t) * 4) // 2, 30, t, 10)
            pyxel.text(35, 50, f"Sterne gefangen: {self.starrain_score}", 7)
            pyxel.text(35, 62, f"Beste Combo: {self.starrain_best_combo}", 7)
            pyxel.text(35, 74, f"Verpasst: {self.starrain_missed}", 7)
            pyxel.text(20, 92, "Weiter zu Level 5...", 7)

    # ─── Sprung-Challenge Screen ────────────────────────────────
    def _draw_jump(self):
        # Himmel-Hintergrund mit Hoehen-Verlauf (heller je hoeher gesprungen)
        pyxel.rect(0, 0, 160, 120, 6)
        pyxel.rect(0, 60, 160, 60, 12)

        if not self.jump_done:
            cam = self.jump_camera_y

            # Deko-Wolken im Hintergrund (leicht parallax, looped)
            for i in range(6):
                wx = (i * 53 + 17) % 170 - 10
                wy = (i * 41 + int(cam * 0.3)) % 140 - 10
                pyxel.rect(wx, wy, 14, 5, 7)
                pyxel.rect(wx + 3, wy - 2, 8, 4, 7)

            # Plattformen zeichnen
            for plat in self.jump_platforms:
                screen_y = plat["y"] + cam
                if -10 <= screen_y <= 130:
                    px = int(plat["x"])
                    pw = int(plat["w"])
                    # Aktuelle Standplattform deutlich hervorheben, damit
                    # immer klar ist, wo der Spieler steht
                    is_current = (abs(plat["y"] - self.jump_current_platform_y) < 1
                                  and self.jump_on_ground)
                    base_col = 11 if is_current else 3
                    top_col = 7 if is_current else 11
                    pyxel.rect(px, int(screen_y), pw, 6, base_col)
                    pyxel.rect(px, int(screen_y), pw, 2, top_col)

            # Sterne zeichnen
            for star in self.jump_stars:
                if star["taken"]:
                    continue
                screen_y = star["y"] + cam
                if -10 <= screen_y <= 130:
                    sx, sy = int(star["x"]), int(screen_y)
                    col = 10
                    pyxel.pset(sx, sy - 3, col)
                    pyxel.pset(sx, sy - 1, col)
                    pyxel.pset(sx, sy + 1, col)
                    pyxel.pset(sx - 2, sy - 1, col)
                    pyxel.pset(sx + 2, sy - 1, col)
                    pyxel.pset(sx - 1, sy - 2, col)
                    pyxel.pset(sx + 1, sy - 2, col)
                    pyxel.pset(sx - 1, sy, col)
                    pyxel.pset(sx + 1, sy, col)

            # Spieler zeichnen (mit rotem Flash bei Sturz)
            screen_py = self.jump_py + cam
            if self.jump_fail_flash > 0 and (self.jump_fail_flash // 4) % 2 == 0:
                pyxel.pal(8, 8)
            pyxel.blt(int(self.jump_px), int(screen_py), 0, 0, 16, 32, 32, 8)
            pyxel.blt(int(self.jump_px) + 8, int(screen_py) + 16, 0, self.outfit * 16, 48, 16, 16, 7)
            pyxel.pal()

            # Kleiner Pfeil-Indikator am Bildschirmrand, falls der Spieler
            # (z.B. beim sehr hohen Sprung) trotzdem mal knapp aus dem
            # sichtbaren Bereich geraet – zeigt, in welche Richtung er ist.
            if screen_py < -4:
                pyxel.tri(int(self.jump_px) + 8, 2, int(self.jump_px) + 2, 8,
                          int(self.jump_px) + 14, 8, 10)
            elif screen_py > 112:
                pyxel.tri(int(self.jump_px) + 8, 118, int(self.jump_px) + 2, 112,
                          int(self.jump_px) + 14, 112, 8)

            # HUD oben
            pyxel.rect(0, 0, 160, 11, 1)
            pyxel.text(3, 2, f"Sterne: {self.jump_score}/{self.jump_target}", 10)

            # Sterne-Fortschrittsbalken
            bar_w = int((self.jump_score / self.jump_target) * 60)
            pyxel.rect(95, 3, 60, 4, 5)
            pyxel.rect(95, 3, bar_w, 4, 10)

            # Leben (Herzen) oben rechts-mittig im HUD
            for i in range(3):
                hx = 3 + i * 9
                hy = 13
                if i < self.jump_lives:
                    pyxel.rect(hx, hy, 7, 6, 8)
                    pyxel.pset(hx, hy - 1, 8)
                    pyxel.pset(hx + 6, hy - 1, 8)
                    pyxel.rectb(hx - 1, hy - 1, 9, 8, 7)
                else:
                    pyxel.rectb(hx - 1, hy - 1, 9, 8, 1)

            pyxel.text(3, 112, "<-Links/Rechts-> SPACE=Springen", 0)
        else:
            pyxel.rect(2, 2, 156, 116, 0)
            pyxel.rectb(2, 2, 156, 116, 7)
            pyxel.rectb(3, 3, 154, 114, 10)
            success = self.jump_score >= self.jump_target
            t = "GESCHAFFT!" if success else "LEBEN VERLOREN!"
            pyxel.text((160 - len(t) * 4) // 2, 30, t, 10 if success else 8)
            pyxel.text(35, 50, f"Sterne gefangen: {self.jump_score}", 7)
            pyxel.text(35, 62, f"Ziel: {self.jump_target} Sterne", 7)
            pyxel.text(20, 84, "Letzter Stern wartet...", 7)

    # ─── Particles ────────────────────────────────────────────
    def _draw_particles(self):
        for p in self.particles:
            if 0 <= p["x"] < pyxel.width and 0 <= p["y"] < pyxel.height:
                pyxel.pset(int(p["x"]), int(p["y"]), p["color"])

    # =========================================================
    #  RESET HELPERS
    # =========================================================
    def reset_level2(self):
        self.cars = []
        self.items = []
        self.speed = 1
        self.has_helmet = False
        self.current_lane = 1
        self.player_y = self.lanes[self.current_lane]
        self.timer = 0
        self.show_level_complete_text = False
        # don't reset lives here – they persist within the level

    def reset_level3(self):
        self.level3_cars = []
        self.level3_items = []
        self.level3_speed = 1
        self.level3_has_helmet = False
        self.level3_current_lane = 1
        self.level3_player_y = self.lanes[1]
        self.level3_timer = 0
        self.level3_lives = 3
        self.show_level3_complete_text = False

    # =========================================================
    #  QUESTION GENERATORS
    # =========================================================
    def generate_quiz_mathe(self):
        difficulty = min(self.quiz_count, 4)
        op = random.choice(["*", "+", "-"] if difficulty < 3 else ["*", "+", "-", "+"])
        if op == "-":
            a = random.randint(1, 10 + difficulty * 2)
            b = random.randint(1, a)
            self.mathe_correct_answer = a - b
        elif op == "*":
            a = random.randint(1, 6 + difficulty)
            b = random.randint(1, 6 + difficulty)
            self.mathe_correct_answer = a * b
        else:
            a = random.randint(1, 15 + difficulty * 3)
            b = random.randint(1, 15 + difficulty * 3)
            self.mathe_correct_answer = a + b
        self.quiz_question = f"{a} {op} {b} = ?"
        self.mathe_answer = ""
        self.mathe_answer_color = 7
        self.quiz_timed_out = False

    def generate_quiz_mc(self, fach):
        pool = [
            ("Hoechster Berg der Schweiz?",
             ["Matterhorn", "Dufourspitze", "Jungfrau"], 1),
            ("Hauptstadt von Australien?",
             ["Sydney", "Melbourne", "Canberra"], 2),
            ("Laengster Fluss der Erde?",
             ["Amazonas", "Nil", "Limmat"], 1),
            ("Groesster Ozean?",
             ["Atlantik", "Pazifik", "Nordsee"], 1),
            ("Wo liegt die Sahara?",
             ["Asien", "Afrika", "Amerika"], 1),
            ("Wie viele Kontinente gibt es?",
             ["5", "6", "7"], 2),
            ("Hauptstadt von Japan?",
             ["Osaka", "Tokio", "Kyoto"], 1),
            ("Land mit den Pyramiden?",
             ["Marokko", "Sudan", "Aegypten"], 2),
            ("Groesstes Land der Erde?",
             ["Kanada", "Russland", "China"], 1),
            ("Hauptstadt der Schweiz?",
             ["Zuerich", "Genf", "Bern"], 2),
            ("In welchem Land ist der Amazonas?",
             ["Kolumbien", "Brasilien", "Peru"], 1),
            ("Welcher Kontinent ist am groessten?",
             ["Amerika", "Asien", "Afrika"], 1),
            ("Wo liegt Island?",
             ["Atlantik", "Pazifik", "Arktis"], 0),
            ("Hauptstadt von Brasilien?",
             ["Sao Paulo", "Rio de Janeiro", "Brasilia"], 2),
            ("Welcher Berg ist am hoechsten?",
             ["Mtterhorn", "Mont Blanc", "Mount Everest"], 2),
            ("Hauptstadt von Frankreich?",
             ["Lyon", "Marseille", "Paris"], 2),
            ("Kleinster Kontinent?",
             ["Europa", "Australien", "Antarktis"], 1),
            ("Welches Land hat die meisten Einwohner?",
             ["USA", "Indien", "Russland"], 1),
            ("Wo liegt der Mount Everest?",
             ["Nepal", "Indien", "China"], 0),
            ("Hauptstadt von Italien?",
             ["Mailand", "Rom", "Venedig"], 1),
        ]

        # Wenn beim letzten Lauf alle Fragen einmal verwendet wurden
        # (oder es der allererste Aufruf ist), Pool neu mischen und die
        # Liste der gestellten Fragen leeren – so kommen erst alle Fragen
        # einmal vor, bevor sich etwas wiederholt.
        if not self.geo_pool_order or len(self.geo_asked_questions) >= len(pool):
            self.geo_pool_order = list(range(len(pool)))
            random.shuffle(self.geo_pool_order)
            self.geo_asked_questions = []

        # Naechste noch nicht in diesem Durchlauf gestellte Frage finden
        for idx in self.geo_pool_order:
            if idx not in self.geo_asked_questions:
                q, choices, correct = pool[idx]
                self.geo_asked_questions.append(idx)

                # Antwortreihenfolge zusaetzlich zufaellig mischen, damit
                # die richtige Antwort nicht immer an derselben Position
                # auftaucht, auch wenn dieselbe Frage spaeter wiederkommt.
                shuffled = list(enumerate(choices))
                random.shuffle(shuffled)
                new_choices = [c for _, c in shuffled]
                new_correct = next(new_i for new_i, (orig_i, _) in enumerate(shuffled) if orig_i == correct)

                self.quiz_question = q
                self.quiz_choices = new_choices
                self.quiz_correct_index = new_correct
                self.quiz_selected = -1
                self.quiz_feedback_timer = 0
                self.quiz_timed_out = False
                return


Game()