# Pyxel Studio

import pyxel
import pyxel
import random

# ── Konstanten ──────────────────────────────────────────────────────────────
W, H = 256, 144
GROUND = H - 24
GRAVITY = 0.5
JUMP_FORCE = -9
PLAYER_SPEED = 2
SCROLL_SPEED_BASE = 1.5
TSUNAMI_INTERVAL = 600
PLATFORM_H = 8

# Farben
COL_SKY      = 1
COL_GROUND   = 4
COL_PLATFORM = 5
COL_HIGH_PLAT= 11
COL_PLAYER   = 7
COL_COIN     = 10
COL_POWERUP  = 8
COL_TSUNAMI  = 12
COL_EXIT     = 11
COL_HUD      = 7
COL_HOLE     = 0

# ── Hilfsfunktionen ──────────────────────────────────────────────────────────
def rects_overlap(ax, ay, aw, ah, bx, by, bw, bh):
    return ax < bx + bw and ax + aw > bx and ay < by + bh and ay + ah > by

# ── Klassen ───────────────────────────────────────────────────────────────────
class Player:
    W, H = 8, 10

    def __init__(self):
        self.reset()

    def reset(self):
        self.x = 40.0
        self.y = float(GROUND - self.H)
        self.vy = 0.0
        self.on_ground = False
        self.alive = True

    def jump(self):
        if self.on_ground:
            self.vy = JUMP_FORCE

    def update(self, platforms, holes, gravity_mod):
        self.vy += GRAVITY * gravity_mod
        self.y += self.vy
        self.on_ground = False

        # Boden
        if self.y + self.H >= GROUND:
            self.y = GROUND - self.H
            self.vy = 0
            self.on_ground = True

        # Plattformen
        for p in platforms:
            if (self.vy >= 0
                    and self.x + self.W > p.x and self.x < p.x + p.w
                    and self.y + self.H > p.y and self.y + self.H < p.y + PLATFORM_H + 4):
                self.y = p.y - self.H
                self.vy = 0
                self.on_ground = True

        # Löcher
        for (hx, hw) in holes:
            if (self.x + self.W > hx + 3 and self.x < hx + hw - 3
                    and self.y + self.H >= GROUND):
                self.alive = False

    def draw(self):
        pyxel.rect(int(self.x), int(self.y), self.W, self.H, COL_PLAYER)
        pyxel.pset(int(self.x) + 2, int(self.y) + 2, 0)
        pyxel.pset(int(self.x) + 5, int(self.y) + 2, 0)


class Platform:
    def __init__(self, x, y, w, elevated=False):
        self.x = float(x)
        self.y = float(y)
        self.w = w
        self.elevated = elevated

    def update(self, scroll):
        self.x -= scroll

    def draw(self):
        col = COL_HIGH_PLAT if self.elevated else COL_PLATFORM
        pyxel.rect(int(self.x), int(self.y), self.w, PLATFORM_H, col)
        if self.elevated:
            pyxel.text(int(self.x) + 2, int(self.y) - 6, "SAFE", 11)


class Coin:
    def __init__(self, x, y):
        self.x = float(x)
        self.y = float(y)
        self.collected = False

    def update(self, scroll):
        self.x -= scroll

    def draw(self):
        if not self.collected:
            pyxel.circ(int(self.x), int(self.y), 3, COL_COIN)


class PowerUp:
    TYPES = ["2x", "slow", "fly"]

    def __init__(self, x, y):
        self.x = float(x)
        self.y = float(y)
        self.kind = random.choice(self.TYPES)
        self.collected = False

    def update(self, scroll):
        self.x -= scroll

    def draw(self):
        if not self.collected:
            pyxel.rect(int(self.x) - 4, int(self.y) - 4, 8, 8, COL_POWERUP)
            label = self.kind[0].upper()
            pyxel.text(int(self.x) - 1, int(self.y) - 3, label, 7)


class Exit:
    def __init__(self, x):
        self.x = float(x)
        self.y = float(GROUND - 20)
        self.w, self.h = 12, 20

    def update(self, scroll):
        self.x -= scroll

    def draw(self):
        pyxel.rect(int(self.x), int(self.y), self.w, self.h, COL_EXIT)
        pyxel.text(int(self.x) - 2, int(self.y) - 7, "EXIT", 11)


class Tsunami:
    def __init__(self):
        self.x = -20.0
        self.active = True
        self.speed = 3.5
        self.h = H - GROUND + 30

    def update(self):
        self.x += self.speed

    def draw(self):
        pyxel.rect(0, GROUND - self.h + 20, int(self.x), self.h, COL_TSUNAMI)
        for i in range(0, self.h, 8):
            pyxel.tri(
                int(self.x),      GROUND - self.h + 20 + i,
                int(self.x) + 10, GROUND - self.h + 20 + i + 4,
                int(self.x),      GROUND - self.h + 20 + i + 8,
                9
            )

    def touches_player(self, px, py):
        return px < self.x and py + 10 > GROUND - self.h + 20


# ── Haupt-App ─────────────────────────────────────────────────────────────────
class App:
    def __init__(self):
        pyxel.init(W, H, title="TsunamiRun", fps=60)
        self.state = "menu"
        self.highscores = []
        self.new_game()
        pyxel.run(self.update, self.draw)

    def new_game(self):
        self.player = Player()
        self.scroll_speed = SCROLL_SPEED_BASE
        self.score = 0
        self.multiplier = 1
        self.mult_timer = 0
        self.frame = 0
        self.tsunami_timer = TSUNAMI_INTERVAL
        self.tsunami = None
        self.tsunami_warning = 0

        self.gravity_mod = 1.0
        self.fly_timer = 0
        self.slow_timer = 0
        self.score_x2_timer = 0

        self.platforms = []
        self.coins = []
        self.powerups = []
        self.exits = []
        self.holes = []
        self.world_offset = 0.0
        self.next_gen_x = W
        self._generate_chunk(W)

        self.active_modifier_text = ""
        self.modifier_display_timer = 0
        self.name_input = ""
        self.final_score = 0

    # Level-Generierung
    def _generate_chunk(self, start_x):
        x = start_x
        wo = self.world_offset  # Offset fuer scrollbare Objekte
        while x < start_x + 600:
            kind = random.random()
            if kind < 0.10:
                hw = int(random.randint(20, 36) * 0.9)  # kleinere Loecher
                self.holes.append((x, hw))
                x += hw + random.randint(60, 100)
            elif kind < 0.55:
                pw = random.randint(40, 80)
                py = GROUND - random.randint(20, 45)
                self.platforms.append(Platform(x - wo, py, pw))
                for cx in range(int(x) + 8, int(x) + pw - 4, 12):
                    self.coins.append(Coin(cx - wo, py - 8))
                x += pw + random.randint(20, 60)
            elif kind < 0.65:
                pw = random.randint(50, 90)
                py = GROUND - random.randint(50, 70)
                self.platforms.append(Platform(x - wo, py, pw, elevated=True))
                x += pw + random.randint(40, 80)
            else:
                py = GROUND - random.randint(20, 50)
                self.powerups.append(PowerUp(x - wo, py))
                x += random.randint(40, 80)

            if random.random() < 0.08:
                self.exits.append(Exit(x - wo))
                x += 60

            if random.random() < 0.4:
                for cx in range(int(x), int(x) + 30, 12):
                    self.coins.append(Coin(cx - wo, GROUND - 8))
                x += 40

        self.next_gen_x = x

    def update(self):
        if self.state == "menu":
            if pyxel.btnp(pyxel.KEY_SPACE) or pyxel.btnp(pyxel.KEY_RETURN):
                self.state = "play"
            if pyxel.btnp(pyxel.KEY_H):
                self.state = "scores"
            return

        if self.state == "scores":
            if pyxel.btnp(pyxel.KEY_ESCAPE) or pyxel.btnp(pyxel.KEY_M):
                self.state = "menu"
            if pyxel.btnp(pyxel.KEY_R):
                self.new_game()
                self.state = "play"
            return

        if self.state == "name_input":
            for key_code in range(pyxel.KEY_A, pyxel.KEY_Z + 1):
                if pyxel.btnp(key_code):
                    if len(self.name_input) < 10:
                        self.name_input += chr(key_code)
            if pyxel.btnp(pyxel.KEY_BACKSPACE) and len(self.name_input) > 0:
                self.name_input = self.name_input[:-1]
            if pyxel.btnp(pyxel.KEY_RETURN) and len(self.name_input) > 0:
                self.highscores.append((self.name_input, self.final_score))
                self.highscores.sort(key=lambda e: e[1], reverse=True)
                self.highscores = self.highscores[:10]
                self.state = "win"
            return

        if self.state in ("dead", "win"):
            if pyxel.btnp(pyxel.KEY_R):
                self.new_game()
                self.state = "play"
            if pyxel.btnp(pyxel.KEY_H):
                self.state = "scores"
            if pyxel.btnp(pyxel.KEY_M):
                self.state = "menu"
            return

        self.frame += 1

        # Bewegungstasten
        right = pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.KEY_D)
        left  = pyxel.btn(pyxel.KEY_LEFT)  or pyxel.btn(pyxel.KEY_A)

        # Scrollgeschwindigkeit (langsamer bei Slowtime)
        base_speed = self.scroll_speed * (0.6 if self.slow_timer > 0 else 1.0)

        # Beide Richtungen möglich
        if right:
            scroll = base_speed
        elif left:
            scroll = -base_speed
        else:
            scroll = 0.0

        self.world_offset += scroll

        # Timer & Modifier
        self.slow_timer = max(0, self.slow_timer - 1)
        self.fly_timer = max(0, self.fly_timer - 1)
        self.score_x2_timer = max(0, self.score_x2_timer - 1)
        self.modifier_display_timer = max(0, self.modifier_display_timer - 1)

        self.mult_timer += 1
        if self.mult_timer % 300 == 0:
            self.multiplier = min(self.multiplier + 1, 8)
        if scroll > 0:
            self.score += self.multiplier * (2 if self.score_x2_timer > 0 else 1)

        self.gravity_mod = 0.2 if self.fly_timer > 0 else 1.0

        # Tsunami-Timer langsam bei Slowtime
        tsunami_speed_factor = 0.5 if self.slow_timer > 0 else 1.0
        self.tsunami_timer -= tsunami_speed_factor
        if self.tsunami_timer <= 120 and not self.tsunami_warning:
            self.tsunami_warning = 120
        if self.tsunami_timer <= 0 and self.tsunami is None:
            self.tsunami = Tsunami()
            self.tsunami_timer = TSUNAMI_INTERVAL - min(self.frame // 600 * 30, 180)
        self.tsunami_warning = max(0, self.tsunami_warning - 1)

        # Objekte scrollen
        for p in self.platforms: p.update(scroll)
        for c in self.coins: c.update(scroll)
        for pu in self.powerups: pu.update(scroll)
        for e in self.exits: e.update(scroll)

        # Neue Chunks erzeugen
        if self.world_offset + W + 200 > self.next_gen_x:
            self._generate_chunk(int(self.next_gen_x))

        # Sprung
        if pyxel.btnp(pyxel.KEY_SPACE) or pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_W):
            self.player.jump()

        # Löcher im Screen-Space
        screen_holes = [
            (hx - self.world_offset, hw)
            for (hx, hw) in self.holes
            if -hw < hx - self.world_offset < W + 10
        ]
        self.player.update(self.platforms, screen_holes, self.gravity_mod)

        if not self.player.alive:
            self.state = "dead"
            return

        # Münzen einsammeln
        pw, ph = Player.W, Player.H
        for c in self.coins:
            if not c.collected and rects_overlap(
                self.player.x, self.player.y, pw, ph,
                c.x - 3, c.y - 3, 6, 6):
                c.collected = True
                self.score += 10 * self.multiplier * (2 if self.score_x2_timer > 0 else 1)

        # Power-Ups
        for pu in self.powerups:
            if not pu.collected and rects_overlap(
                self.player.x, self.player.y, pw, ph,
                pu.x - 4, pu.y - 4, 8, 8):
                pu.collected = True
                self._apply_powerup(pu.kind)

        # Exit
        for e in self.exits:
            if rects_overlap(self.player.x, self.player.y, pw, ph, e.x, e.y, e.w, e.h):
                self.score += 500 * self.multiplier
                self.final_score = self.score
                self.name_input = ""
                self.state = "name_input"
                return

        # Tsunami
        if self.tsunami:
            self.tsunami.update()
            if self.tsunami.touches_player(self.player.x, self.player.y):
                self.player.alive = False
                self.state = "dead"
            if self.tsunami.x > W + 20:
                self.tsunami = None

    def _apply_powerup(self, kind):
        if kind == "2x":
            self.score_x2_timer = 600
            self.active_modifier_text = "2x SCORE!"
        elif kind == "slow":
            self.slow_timer = 400
            self.active_modifier_text = "SLOW TIME!"
        elif kind == "fly":
            self.fly_timer = 300
            self.active_modifier_text = "LOW GRAVITY!"
        self.modifier_display_timer = 120

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

        if self.state == "menu":
            self._draw_menu()
            return
        if self.state == "scores":
            self._draw_scores()
            return
        if self.state == "name_input":
            self._draw_name_input()
            return
        if self.state == "dead":
            self._draw_end(False)
            return
        if self.state == "win":
            self._draw_end(True)
            return

        for i in range(8):
            pyxel.line(0, i * 3, W, i * 3, 1 if i < 4 else 0)

        pyxel.rect(0, GROUND, W, H - GROUND, COL_GROUND)
        pyxel.line(0, GROUND, W, GROUND, 5)

        for (hx, hw) in self.holes:
            sx = hx - self.world_offset
            if -hw < sx < W + 10:
                pyxel.rect(int(sx), GROUND, int(hw), H - GROUND, COL_HOLE)

        for p in self.platforms: p.draw()
        for c in self.coins:
            if not c.collected: c.draw()
        for pu in self.powerups:
            if not pu.collected: pu.draw()
        for e in self.exits: e.draw()
        self.player.draw()

        if self.tsunami:
            self.tsunami.draw()

        if self.tsunami_warning > 0 and self.tsunami is None:
            if self.frame % 20 < 10:
                pyxel.text(W // 2 - 30, H // 2 - 10, "!! TSUNAMI !!", 8)

        self._draw_hud()

        if self.modifier_display_timer > 0:
            pyxel.text(W // 2 - 30, 30, self.active_modifier_text, 10)

    def _draw_hud(self):
        pyxel.rect(0, 0, W, 12, 0)
        pyxel.text(2, 2, f"SCORE: {self.score}", COL_HUD)
        pyxel.text(80, 2, f"x{self.multiplier}", 10)
        if self.score_x2_timer > 0:
            pyxel.text(110, 2, "2x", 10)
        if self.fly_timer > 0:
            pyxel.text(122, 2, "FLY", 11)
        if self.slow_timer > 0:
            pyxel.text(138, 2, "SLOW", 12)
        bar_w = int((self.tsunami_timer / TSUNAMI_INTERVAL) * 60)
        pyxel.rect(W - 64, 2, 60, 6, 1)
        pyxel.rect(W - 64, 2, bar_w, 6, 8 if self.tsunami_timer < 180 else 12)
        pyxel.text(W - 64, 2, "~", 12)

    def _draw_menu(self):
        pyxel.cls(1)

        # Animierte Hintergrund-Sterne
        for i in range(15):
            sx = (i * 37 + pyxel.frame_count // 4) % W
            sy = (i * 19 + 5) % 90
            pyxel.pset(sx, sy, 13 if i % 2 == 0 else 5)

        # Animierte Welle am Boden (Tsunami-Thema)
        for wx in range(0, W, 2):
            offset = (wx + pyxel.frame_count * 2) % 32
            wave_h = 2 if offset < 16 else 0
            pyxel.rect(wx, GROUND - wave_h, 2, H - GROUND + wave_h, 12)
        for wx in range(0, W, 8):
            offset = (wx + pyxel.frame_count * 2) % 32
            if offset < 8:
                pyxel.pset(wx, GROUND - 2, 9)

        # Titel mit Rahmen
        title_x = W // 2 - 38
        title_y = 14
        pyxel.rect(title_x - 6, title_y - 4, 88, 14, 0)
        pyxel.rectb(title_x - 7, title_y - 5, 90, 16, 8)
        pyxel.rectb(title_x - 6, title_y - 4, 88, 14, 12)
        tcol = 12 if pyxel.frame_count % 60 < 40 else 8
        pyxel.text(title_x, title_y, "TSUNAMI RUN", tcol)

        # Untertitel
        pyxel.text(W // 2 - 50, 33, "Ueberlebe den Tsunami!", 8)
        pyxel.text(W // 2 - 48, 42, "Sammle Muenzen & Power-Ups", 10)

        # Steuerung-Box
        bx, by, bw, bh = 35, 54, W - 70, 32
        pyxel.rect(bx, by, bw, bh, 0)
        pyxel.rectb(bx, by, bw, bh, 5)
        pyxel.text(bx + bw // 2 - 20, by + 3, "STEUERUNG", 7)
        pyxel.line(bx + 4, by + 10, bx + bw - 4, by + 10, 5)
        pyxel.text(bx + 6, by + 14, "Pfeiltasten / WASD  Laufen", 5)
        pyxel.text(bx + 6, by + 22, "SPACE / W           Springen", 5)

        # Blinkender Start-Prompt
        if pyxel.frame_count % 50 < 35:
            pyxel.text(W // 2 - 43, 97, "> SPACE = Spiel starten <", 7)

        # Menue-Optionen
        pyxel.text(W // 2 - 28, 112, "H = Highscores", 10)

    def _draw_scores(self):
        pyxel.cls(0)
        pyxel.text(W // 2 - 30, 10, "HIGHSCORES", 10)
        if not self.highscores:
            pyxel.text(W // 2 - 35, 50, "Noch keine Eintraege!", 5)
        else:
            for i, (name, score) in enumerate(self.highscores[:10]):
                y = 25 + i * 10
                pyxel.text(30, y, f"{i + 1}.", 7)
                pyxel.text(45, y, name, 7)
                pyxel.text(120, y, str(score), 10)
        pyxel.text(W // 2 - 55, 130, "M = Menue  R = Neues Spiel", 5)

    def _draw_name_input(self):
        pyxel.cls(0)
        pyxel.text(W // 2 - 20, 20, "ENTKOMMEN!", 11)
        pyxel.text(W // 2 - 30, 35, f"SCORE: {self.final_score}", 7)
        pyxel.text(W // 2 - 45, 60, "Gib deinen Namen ein:", 7)
        box_x = W // 2 - 35
        box_w = 70
        pyxel.rect(box_x, 72, box_w, 12, 1)
        pyxel.rectb(box_x, 72, box_w, 12, 7)
        display = self.name_input
        if pyxel.frame_count % 40 < 20:
            display += "_"
        pyxel.text(box_x + 3, 75, display, 7)
        pyxel.text(W // 2 - 40, 95, "ENTER = Bestaetigen", 5)
        pyxel.text(W // 2 - 35, 105, "(Max. 10 Zeichen)", 5)

    def _draw_end(self, won):
        pyxel.cls(0)
        if won:
            pyxel.text(W // 2 - 20, H // 2 - 20, "ENTKOMMEN!", 11)
        else:
            pyxel.text(W // 2 - 14, H // 2 - 20, "TOD!", 8)
        pyxel.text(W // 2 - 30, H // 2 - 5, f"SCORE: {self.score}", 7)
        pyxel.text(W // 2 - 30, H // 2 + 10, "R = Neu starten", 5)
        pyxel.text(W // 2 - 30, H // 2 + 20, "H = Highscores", 5)
        pyxel.text(W // 2 - 30, H // 2 + 30, "M = Menue", 5)


App()