import pyxel
import random
from dataclasses import dataclass

# ============================================================
# THERMOTRAIL - 256x256 VERSION
# Endless Runner / Puzzle Prototype for Pyxel Studio
# With parallax background layers + AUDIO
# ============================================================

# ------------------------------------------------------------
# CONFIG
# ------------------------------------------------------------
WIDTH = 256
HEIGHT = 256

TILE = 16
GRID_W = WIDTH // TILE          # 16
GRID_H = HEIGHT // TILE         # 16

ROW_BUFFER = 80
TEMP_MIN = -2
TEMP_MAX = 2
KEY_COUNT_TO_WIN = 3

# Terrain IDs
GRASS = 0
WATER = 1
HOT_WATER = 2
ICE = 3
STONE = 4
HOT_STONE = 5
LAVA = 6
FORTRESS = 7

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

def irnd(a, b):
    return random.randint(a, b)

# ------------------------------------------------------------
# DATA
# ------------------------------------------------------------
@dataclass
class Cell:
    base: int
    key: bool = False
    animal: bool = False
    fortress: bool = False

# ------------------------------------------------------------
# PLAYER
# ------------------------------------------------------------
class Player:
    def __init__(self, char_type=0):
        self.char_type = char_type  # 0 cold resistant, 1 heat resistant
        self.x = GRID_W // 2
        self.y = 3
        self.move_cd = 0

        self.cold_meter = 0
        self.heat_meter = 0
        self.grass_timer = 0

        self.alive = True
        self.death_reason = ""
        self.anim = 0

    def reset(self):
        self.x = GRID_W // 2
        self.y = 3
        self.move_cd = 0
        self.cold_meter = 0
        self.heat_meter = 0
        self.grass_timer = 0
        self.alive = True
        self.death_reason = ""
        self.anim = 0

    @property
    def cold_limit(self):
        return 550 if self.char_type == 0 else 275

    @property
    def heat_limit(self):
        return 550 if self.char_type == 1 else 275

# ------------------------------------------------------------
# GAME
# ------------------------------------------------------------
class Game:
    def __init__(self):
        pyxel.init(WIDTH, HEIGHT, title="ThermoTrail 256", fps=30)
        pyxel.load("res.pyxres")
        pyxel.mouse(False)

        self.state = "menu"
        self.selected_char = 0

        self.temp = 0
        self.score = 0
        self.keys = 0

        self.rows = []
        self.last_generated_index = -1

        self.player = Player(0)

        self.fortress_spawned = False
        self.fortress_row = -1

        self.msg = ""
        self.msg_timer = 0

        self.bg_stars = []
        self.bg_far_peaks = []
        self.bg_near_peaks = []
        self.embers = []
        self.snow = []

        self.eagle_warned = False

        self.setup_audio()
        self.setup_background()
        self.reset_world()

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

    # --------------------------------------------------------
    # AUDIO
    # --------------------------------------------------------
    def setup_audio(self):
        # ---------- BGM ----------
        # Kanal 0: Lead
        pyxel.sounds[0].set(
            "e3 g3 a3 g3 e3 d3 e3 r "
            "c3 e3 g3 e3 d3 c3 d3 r "
            "a2 c3 e3 c3 a2 g2 a2 r "
            "b2 d3 g3 d3 b2 a2 g2 r",
            "t",
            "6",
            "n",
            20
        )

        # Kanal 1: Bass
        pyxel.sounds[1].set(
            "c2 c2 c2 c2 a1 a1 a1 a1 "
            "f1 f1 f1 f1 g1 g1 g1 g1",
            "t",
            "4",
            "n",
            20
        )

        # Kanal 2: Percussion / funny pulse
        pyxel.sounds[2].set(
            "c1 r c1 r c1 r c1 r "
            "c1 r c1 r c1 r c1 r",
            "n",
            "5",
            "f",
            10
        )

        # ---------- SFX ----------
        # 3 = Schlüssel
        pyxel.sounds[3].set(
            "c3 e3 g3 c4",
            "p",
            "7",
            "n",
            8
        )

        # 4 = Festung betreten
        pyxel.sounds[4].set(
            "c3 e3 g3 c4 g3 c4",
            "t",
            "7",
            "f",
            18
        )

        # 5 = Temperatur hoch
        pyxel.sounds[5].set(
            "a2 b2 d3",
            "s",
            "6",
            "n",
            6
        )

        # 6 = Temperatur runter
        pyxel.sounds[6].set(
            "d3 b2 g2",
            "t",
            "5",
            "n",
            6
        )

        # 7 = Wasser / Splash
        pyxel.sounds[7].set(
            "f2 d2 c2",
            "n",
            "6",
            "f",
            12
        )

        # 8 = Lava
        pyxel.sounds[8].set(
            "c2 c#2 d2 f2 g2",
            "n",
            "7",
            "f",
            12
        )

        # 9 = Adler-Warnung
        pyxel.sounds[9].set(
            "c4 a3 c4 a3",
            "p",
            "6",
            "f",
            6
        )

        # 10 = Sieg
        pyxel.sounds[10].set(
            "c3 e3 g3 c4 e4",
            "p",
            "7",
            "f",
            18
        )

        # music[0] nutzt Kanäle 0,1,2
        # Kanal 3 bleibt für Soundeffekte frei
        pyxel.musics[0].set(
            [0],
            [1],
            [2],
            []
        )

    def play_bgm(self):
        pyxel.playm(0, loop=True)

    def stop_bgm(self):
        pyxel.stop(0)
        pyxel.stop(1)
        pyxel.stop(2)

    def sfx_key(self):
        pyxel.play(3, 3)

    def sfx_fortress(self):
        pyxel.play(3, 4)

    def sfx_temp_up(self):
        pyxel.play(3, 5)

    def sfx_temp_down(self):
        pyxel.play(3, 6)

    def sfx_splash(self):
        pyxel.play(3, 7)

    def sfx_lava(self):
        pyxel.play(3, 8)

    def sfx_eagle(self):
        pyxel.play(3, 9)

    def sfx_win(self):
        pyxel.play(3, 10)

    # --------------------------------------------------------
    # BACKGROUND
    # --------------------------------------------------------
    def setup_background(self):
        self.bg_stars = []
        self.bg_far_peaks = []
        self.bg_near_peaks = []
        self.embers = []
        self.snow = []

        for _ in range(50):
            self.bg_stars.append({
                "x": random.random() * WIDTH,
                "y": random.random() * 90,
                "c": random.choice([5, 6, 7, 12])
            })

        x = -20
        while x < WIDTH + 40:
            w = irnd(24, 60)
            h = irnd(16, 40)
            self.bg_far_peaks.append((x, w, h))
            x += irnd(16, 34)

        x = -10
        while x < WIDTH + 40:
            w = irnd(18, 42)
            h = irnd(18, 56)
            self.bg_near_peaks.append((x, w, h))
            x += irnd(14, 26)

        for _ in range(24):
            self.embers.append({
                "x": random.random() * WIDTH,
                "y": random.random() * HEIGHT,
                "vx": random.uniform(-0.15, 0.15),
                "vy": random.uniform(-0.6, -0.2),
                "r": random.choice([1, 1, 2])
            })

        for _ in range(32):
            self.snow.append({
                "x": random.random() * WIDTH,
                "y": random.random() * HEIGHT,
                "vx": random.uniform(-0.25, 0.15),
                "vy": random.uniform(0.2, 0.7),
                "r": random.choice([1, 1, 1, 2])
            })

    def update_background_particles(self):
        if self.temp >= 1:
            for p in self.embers:
                p["x"] += p["vx"]
                p["y"] += p["vy"]
                if p["y"] < -4:
                    p["y"] = HEIGHT + random.random() * 20
                    p["x"] = random.random() * WIDTH
                if p["x"] < -4:
                    p["x"] = WIDTH + 4
                if p["x"] > WIDTH + 4:
                    p["x"] = -4

        if self.temp <= -1:
            for p in self.snow:
                p["x"] += p["vx"]
                p["y"] += p["vy"]
                if p["y"] > HEIGHT + 4:
                    p["y"] = -4
                    p["x"] = random.random() * WIDTH
                if p["x"] < -4:
                    p["x"] = WIDTH + 4
                if p["x"] > WIDTH + 4:
                    p["x"] = -4

    # --------------------------------------------------------
    # WORLD RESET / GENERATION
    # --------------------------------------------------------
    def reset_world(self):
        self.temp = 0
        self.score = 0
        self.keys = 0
        self.msg = ""
        self.msg_timer = 0
        self.eagle_warned = False

        self.rows = []
        self.last_generated_index = -1

        self.fortress_spawned = False
        self.fortress_row = -1

        self.player = Player(self.selected_char)
        self.player.reset()

        for i in range(ROW_BUFFER):
            if i < 5:
                row = [Cell(STONE) for _ in range(GRID_W)]
            else:
                row = self.generate_row(i)
            self.rows.append(row)
            self.last_generated_index = i

        self.rows[self.player.y][self.player.x] = Cell(STONE)

    def ensure_rows(self, target_row):
        while self.last_generated_index < target_row + 18:
            idx = self.last_generated_index + 1
            self.rows.append(self.generate_row(idx))
            self.last_generated_index = idx

    def generate_row(self, row_index):
        row = []
        difficulty = min(1.0, row_index / 120.0)

        for x in range(GRID_W):
            r = random.random()

            if row_index < 10:
                if r < 0.45:
                    terrain = STONE
                elif r < 0.75:
                    terrain = GRASS
                elif r < 0.90:
                    terrain = WATER
                else:
                    terrain = ICE
            else:
                if r < 0.16:
                    terrain = GRASS
                elif r < 0.31:
                    terrain = STONE
                elif r < 0.49:
                    terrain = WATER
                elif r < 0.62:
                    terrain = ICE
                elif r < 0.77:
                    terrain = HOT_WATER
                elif r < 0.91:
                    terrain = HOT_STONE
                else:
                    terrain = LAVA

                if row_index < 18 and terrain == LAVA:
                    terrain = HOT_STONE

                if random.random() < difficulty * 0.10:
                    if terrain == WATER:
                        terrain = HOT_WATER
                    elif terrain == STONE:
                        terrain = HOT_STONE

            cell = Cell(base=terrain)

            if terrain in (WATER, HOT_WATER) and random.random() < 0.16:
                cell.animal = True

            if (
                self.keys < KEY_COUNT_TO_WIN
                and row_index > 10
                and random.random() < 0.04
            ):
                cell.key = True

            row.append(cell)

        # A little path support
        safe_count = 0
        for c in row:
            if c.base in (GRASS, STONE, ICE, WATER):
                safe_count += 1
        if safe_count < 3:
            for _ in range(3):
                sx = irnd(0, GRID_W - 1)
                row[sx].base = random.choice([STONE, GRASS, ICE])

        # Only one key per row
        found = False
        for c in row:
            if c.key:
                if found:
                    c.key = False
                found = True

        return row

    def spawn_fortress(self):
        if self.fortress_spawned:
            return

        self.fortress_spawned = True
        self.fortress_row = self.player.y + irnd(12, 20)
        self.ensure_rows(self.fortress_row)

        row = [Cell(STONE) for _ in range(GRID_W)]
        cx = irnd(3, GRID_W - 4)

        row[cx - 1] = Cell(STONE)
        row[cx] = Cell(FORTRESS, fortress=True)
        row[cx + 1] = Cell(STONE)

        # some extra support around fortress
        if cx - 2 >= 0:
            row[cx - 2] = Cell(STONE)
        if cx + 2 < GRID_W:
            row[cx + 2] = Cell(STONE)

        self.rows[self.fortress_row] = row
        self.show_message("Festung erschienen!", 80)

    # --------------------------------------------------------
    # GAME RULES
    # --------------------------------------------------------
    def effective_terrain(self, cell):
        t = cell.base

        if t == WATER:
            if self.temp <= -1:
                return ICE
            return WATER

        if t == HOT_WATER:
            if self.temp <= -2:
                return ICE
            if self.temp <= -1:
                return WATER
            return HOT_WATER

        if t == ICE:
            if self.temp >= 1:
                return WATER
            return ICE

        if t == HOT_STONE:
            if self.temp <= -1:
                return STONE
            return HOT_STONE

        if t == LAVA:
            if self.temp <= -2:
                return STONE
            if self.temp <= -1:
                return HOT_STONE
            return LAVA

        return t

    def is_safe_to_stand(self, cell):
        eff = self.effective_terrain(cell)

        if eff in (GRASS, ICE, STONE, FORTRESS):
            return True

        if eff in (WATER, HOT_WATER) and cell.animal:
            return True

        return False

    def is_hot_tile(self, cell):
        eff = self.effective_terrain(cell)
        return eff in (HOT_STONE, HOT_WATER, LAVA)

    def is_cold_tile(self, cell):
        eff = self.effective_terrain(cell)
        return eff == ICE and self.temp <= -1

    def show_message(self, text, duration=60):
        self.msg = text
        self.msg_timer = duration

    def start_game(self):
        self.state = "play"
        self.reset_world()
        self.play_bgm()

    def lose(self, reason):
        self.player.alive = False
        self.player.death_reason = reason
        self.stop_bgm()
        self.state = "lose"

    def win(self):
        self.stop_bgm()
        self.sfx_fortress()
        self.sfx_win()
        self.state = "win"

    # --------------------------------------------------------
    # INPUT
    # --------------------------------------------------------
    def handle_menu_input(self):
        if pyxel.btnp(pyxel.KEY_1):
            self.selected_char = 0
        if pyxel.btnp(pyxel.KEY_2):
            self.selected_char = 1

        if pyxel.btnp(pyxel.KEY_RETURN):
            self.start_game()

    def handle_play_input(self):
        if self.player.move_cd > 0:
            self.player.move_cd -= 1

        # Temperature
        if pyxel.btnp(pyxel.KEY_Q):
            old = self.temp
            self.temp = clamp(self.temp - 1, TEMP_MIN, TEMP_MAX)
            if self.temp != old:
                self.show_message("Temperatur runter", 40)
                self.sfx_temp_down()

        if pyxel.btnp(pyxel.KEY_E):
            old = self.temp
            self.temp = clamp(self.temp + 1, TEMP_MIN, TEMP_MAX)
            if self.temp != old:
                self.show_message("Temperatur hoch", 40)
                self.sfx_temp_up()

        if self.player.move_cd == 0:
            dx = 0
            dy = 0

            if pyxel.btnp(pyxel.KEY_A) or pyxel.btnp(pyxel.KEY_LEFT):
                dx = -1
            elif pyxel.btnp(pyxel.KEY_D) or pyxel.btnp(pyxel.KEY_RIGHT):
                dx = 1
            elif pyxel.btnp(pyxel.KEY_W) or pyxel.btnp(pyxel.KEY_UP):
                dy = 1
            elif pyxel.btnp(pyxel.KEY_S) or pyxel.btnp(pyxel.KEY_DOWN):
                dy = -1

            if dx != 0 or dy != 0:
                self.try_move(dx, dy)

    def try_move(self, dx, dy):
        nx = self.player.x + dx
        ny = self.player.y + dy

        if nx < 0 or nx >= GRID_W or ny < 0:
            return

        self.ensure_rows(ny)

        self.player.x = nx
        self.player.y = ny
        self.player.move_cd = 4

        self.score = max(self.score, self.player.y)

        cell = self.rows[self.player.y][self.player.x]
        if cell.key:
            cell.key = False
            self.keys += 1
            self.sfx_key()
            self.show_message(f"Schluessel {self.keys}/3", 70)
            if self.keys >= KEY_COUNT_TO_WIN:
                self.spawn_fortress()

    # --------------------------------------------------------
    # UPDATE
    # --------------------------------------------------------
    def update(self):
        if self.state == "menu":
            self.handle_menu_input()
            return

        if self.state in ("lose", "win"):
            if pyxel.btnp(pyxel.KEY_RETURN):
                self.stop_bgm()
                self.state = "menu"
            return

        self.handle_play_input()

        if self.player.move_cd > 0:
            self.player.anim = (self.player.anim + 1) % 20
        else:
            self.player.anim = 0

        self.update_background_particles()
        self.ensure_rows(self.player.y + GRID_H + 8)
        self.update_survival()

        if self.msg_timer > 0:
            self.msg_timer -= 1
            if self.msg_timer <= 0:
                self.msg = ""

        current = self.rows[self.player.y][self.player.x]
        if self.effective_terrain(current) == FORTRESS:
            self.win()

    def update_survival(self):
        cell = self.rows[self.player.y][self.player.x]
        eff = self.effective_terrain(cell)

        if not self.is_safe_to_stand(cell):
            if eff in (WATER, HOT_WATER):
                self.sfx_splash()
                self.lose("Du bist ins Wasser gefallen!")
            elif eff == LAVA:
                self.sfx_lava()
                self.lose("Du bist in Lava gefallen!")
            else:
                self.lose("Unsicherer Boden!")
            return

        if eff == GRASS:
            self.player.grass_timer += 1
        else:
            self.player.grass_timer = max(0, self.player.grass_timer - 3)

        eagle_limit = 250

        if self.player.grass_timer >= 85 and not self.eagle_warned:
            self.sfx_eagle()
            self.eagle_warned = True

        if self.player.grass_timer < 40:
            self.eagle_warned = False

        if self.player.grass_timer > eagle_limit:
            self.lose("Der Adler hat dich erwischt!")
            return

        cold_gain = 0
        heat_gain = 0

        if self.temp <= -2:
            cold_gain += 2
        elif self.temp == -1:
            cold_gain += 1

        if self.temp >= 2:
            heat_gain += 2
        elif self.temp == 1:
            heat_gain += 1

        if self.is_cold_tile(cell):
            cold_gain += 1

        if self.is_hot_tile(cell):
            if eff == HOT_STONE:
                heat_gain += 2
            elif eff == HOT_WATER:
                heat_gain += 2
            elif eff == LAVA:
                heat_gain += 999

        self.player.cold_meter += cold_gain
        self.player.heat_meter += heat_gain

        if cold_gain == 0:
            self.player.cold_meter = max(0, self.player.cold_meter - 2)
        if heat_gain == 0:
            self.player.heat_meter = max(0, self.player.heat_meter - 2)

        if self.player.cold_meter >= self.player.cold_limit:
            self.lose("Du bist erfroren!")
            return

        if self.player.heat_meter >= self.player.heat_limit:
            self.lose("Du bist verbrannt!")
            return

    # --------------------------------------------------------
    # DRAW - BACKGROUND
    # --------------------------------------------------------
    def draw_gradient_sky(self):
        if self.temp <= -1:
            top = 1
            mid = 5
            low = 12
        elif self.temp >= 1:
            top = 2
            mid = 9
            low = 10
        else:
            top = 1
            mid = 13
            low = 6

        pyxel.cls(top)

        for y in range(0, 80):
            pyxel.line(0, y, WIDTH, y, top)
        for y in range(80, 150):
            pyxel.line(0, y, WIDTH, y, mid)
        for y in range(150, 200):
            pyxel.line(0, y, WIDTH, y, low)

    def draw_sun_or_moon(self):
        if self.temp <= -1:
            pyxel.circ(212, 34, 14, 7)
            pyxel.circ(216, 30, 14, 5)
        elif self.temp >= 1:
            pyxel.circ(212, 36, 16, 10)
            pyxel.circ(212, 36, 11, 9)
        else:
            pyxel.circ(212, 36, 14, 7)

    def draw_stars(self):
        if self.temp <= -1:
            for s in self.bg_stars:
                pyxel.pset(int(s["x"]), int(s["y"]), s["c"])

    def draw_far_mountains(self, cam_row):
        offset = int(cam_row * 0.5) % WIDTH
        base_y = 144

        col = 5 if self.temp <= 0 else 8

        for x, w, h in self.bg_far_peaks:
            px = int((x - offset * 0.25) % (WIDTH + 60)) - 30
            pyxel.tri(px, base_y, px + w // 2, base_y - h, px + w, base_y, col)

    def draw_near_ridges(self, cam_row):
        offset = int(cam_row * 1.0) % WIDTH
        base_y = 176

        if self.temp <= -1:
            col = 13
        elif self.temp >= 1:
            col = 2
        else:
            col = 1

        for x, w, h in self.bg_near_peaks:
            px = int((x - offset * 0.55) % (WIDTH + 40)) - 20
            pyxel.tri(px, base_y, px + w // 2, base_y - h, px + w, base_y, col)

    def draw_temp_particles(self):
        if self.temp >= 1:
            for p in self.embers:
                c = 10 if p["r"] == 2 else 9
                if p["r"] == 1:
                    pyxel.pset(int(p["x"]), int(p["y"]), c)
                else:
                    pyxel.circ(int(p["x"]), int(p["y"]), 1, c)

        if self.temp <= -1:
            for p in self.snow:
                c = 7 if p["r"] == 2 else 12
                if p["r"] == 1:
                    pyxel.pset(int(p["x"]), int(p["y"]), c)
                else:
                    pyxel.circ(int(p["x"]), int(p["y"]), 1, c)

    # --------------------------------------------------------
    # DRAW - WORLD
    # --------------------------------------------------------
    def draw_cell(self, sx, sy, cell):
        eff = self.effective_terrain(cell)

        if eff == GRASS:
            col = 3
        elif eff == WATER:
            col = 12
        elif eff == HOT_WATER:
            col = 9
        elif eff == ICE:
            col = 6
        elif eff == STONE:
            col = 5
        elif eff == HOT_STONE:
            col = 8
        elif eff == LAVA:
            col = 10
        elif eff == FORTRESS:
            col = 13
        else:
            col = 0

        pyxel.rect(sx, sy, TILE, TILE, col)

        if eff == GRASS:
            for i in range(1, TILE, 4):
                pyxel.line(sx + i, sy + 3, sx + i, sy + 5, 11)
                pyxel.line(sx + (i + 2) % TILE, sy + 10, sx + (i + 2) % TILE, sy + 12, 11)

        elif eff == WATER:
            for i in range(0, TILE, 4):
                pyxel.line(sx + i, sy + 5, sx + i + 2, sy + 5, 1)
                pyxel.line(sx + i + 1, sy + 10, sx + i + 3, sy + 10, 1)

        elif eff == HOT_WATER:
            for i in range(0, TILE, 4):
                pyxel.line(sx + i, sy + 6, sx + i + 2, sy + 6, 7)
            pyxel.circ(sx + 4, sy + 4, 1, 7)
            pyxel.circ(sx + 11, sy + 3, 1, 7)

        elif eff == ICE:
            pyxel.line(sx + 2, sy + 2, sx + 13, sy + 13, 7)
            pyxel.line(sx + 13, sy + 2, sx + 2, sy + 13, 7)
            pyxel.line(sx + 8, sy + 1, sx + 14, sy + 7, 12)

        elif eff == STONE:
            pyxel.rectb(sx + 1, sy + 1, TILE - 2, TILE - 2, 1)
            pyxel.pset(sx + 5, sy + 5, 6)
            pyxel.pset(sx + 9, sy + 10, 6)
            pyxel.pset(sx + 12, sy + 7, 1)

        elif eff == HOT_STONE:
            pyxel.rectb(sx + 1, sy + 1, TILE - 2, TILE - 2, 1)
            pyxel.pset(sx + 5, sy + 5, 10)
            pyxel.pset(sx + 9, sy + 10, 10)
            pyxel.pset(sx + 12, sy + 7, 7)
            pyxel.pset(sx + 4, sy + 11, 7)

        elif eff == LAVA:
            for i in range(0, TILE, 4):
                pyxel.line(sx, sy + i, sx + TILE - 1, sy + i, 8)
            pyxel.circ(sx + 4, sy + 4, 1, 7)
            pyxel.circ(sx + 11, sy + 9, 1, 7)
            pyxel.circ(sx + 8, sy + 13, 1, 9)

        elif eff == FORTRESS:
            pyxel.rect(sx + 2, sy + 5, 12, 9, 1)
            pyxel.rect(sx + 4, sy + 2, 2, 4, 1)
            pyxel.rect(sx + 10, sy + 2, 2, 4, 1)
            pyxel.rect(sx + 6, sy + 9, 4, 5, 0)
            pyxel.rectb(sx + 2, sy + 5, 12, 9, 7)

        if cell.animal and eff in (WATER, HOT_WATER):
            pyxel.rect(sx + 4, sy + 7, 8, 4, 4)
            pyxel.rect(sx + 11, sy + 8, 2, 2, 4)
            pyxel.pset(sx + 12, sy + 8, 0)

        if cell.key:
            pyxel.circ(sx + 5, sy + 5, 2, 10)
            pyxel.line(sx + 7, sy + 5, sx + 12, sy + 5, 10)
            pyxel.line(sx + 10, sy + 5, sx + 10, sy + 8, 10)
            pyxel.line(sx + 11, sy + 5, sx + 11, sy + 7, 10)

    def draw_player(self, sx, sy):
        bob = 0 if self.player.anim < 10 else 1

        if self.player.move_cd > 0:
            if self.player.anim < 10:
                u = 16
            else:
                u = 32
        else:
            u = 0

        pyxel.blt(sx, sy + bob, 0, u, 16, 16, 16, 0)

    def draw_world(self):
        cam_row = max(0, self.player.y - 6)

        # background first
        self.draw_gradient_sky()
        self.draw_stars()
        self.draw_sun_or_moon()
        self.draw_far_mountains(cam_row)
        self.draw_near_ridges(cam_row)
        self.draw_temp_particles()

        # horizon strip
        if self.temp <= -1:
            pyxel.rect(0, 176, WIDTH, 80, 13)
        elif self.temp >= 1:
            pyxel.rect(0, 176, WIDTH, 80, 2)
        else:
            pyxel.rect(0, 176, WIDTH, 80, 1)

        # visible rows
        for screen_row in range(GRID_H - 3):
            world_row = cam_row + screen_row
            if world_row >= len(self.rows):
                continue

            sy = HEIGHT - ((screen_row + 1) * TILE)

            row = self.rows[world_row]
            for x in range(GRID_W):
                sx = x * TILE
                self.draw_cell(sx, sy, row[x])

        psx = self.player.x * TILE
        psy = HEIGHT - (((self.player.y - cam_row) + 1) * TILE)
        self.draw_player(psx, psy)

    # --------------------------------------------------------
    # DRAW - UI
    # --------------------------------------------------------
    def draw_ui(self):
        pyxel.rect(0, 0, WIDTH, 22, 0)
        pyxel.rectb(0, 0, WIDTH, 22, 7)

        pyxel.text(4, 4, f"TEMP {self.temp:+d}", 7)
        pyxel.text(66, 4, f"KEYS {self.keys}/3", 10)
        pyxel.text(136, 4, f"ROW {self.score}", 11)

        cold_w = min(46, self.player.cold_meter * 46 // max(1, self.player.cold_limit))
        heat_w = min(46, self.player.heat_meter * 46 // max(1, self.player.heat_limit))

        pyxel.text(4, 14, "C", 12)
        pyxel.rectb(12, 13, 48, 6, 7)
        pyxel.rect(13, 14, cold_w, 4, 12)

        pyxel.text(120, 14, "H", 8)
        pyxel.rectb(128, 13, 48, 6, 7)
        pyxel.rect(129, 14, heat_w, 4, 8)

        if self.msg:
            pyxel.text(184, 14, self.msg[:12], 7)

    def draw_menu_preview(self, x, y):
        tiles = [
            Cell(GRASS),
            Cell(WATER, animal=True),
            Cell(ICE),
            Cell(HOT_STONE),
            Cell(LAVA),
            Cell(FORTRESS, fortress=True),
        ]
        for i, c in enumerate(tiles):
            self.draw_cell(x + i * 18, y, c)

    def draw_menu_background(self):
        self.draw_gradient_sky()
        self.draw_stars()
        self.draw_sun_or_moon()
        self.draw_far_mountains(0)
        self.draw_near_ridges(0)
        self.draw_temp_particles()

        pyxel.rect(0, 180, WIDTH, 76, 1)
        for y in range(180, HEIGHT, 16):
            for x in range(0, WIDTH, 16):
                self.draw_cell(x, y, Cell(STONE if (x // 16 + y // 16) % 2 == 0 else GRASS))

    def draw_menu(self):
        self.draw_menu_background()

        pyxel.rect(20, 24, 216, 148, 0)
        pyxel.rectb(20, 24, 216, 148, 7)

        pyxel.text(95, 36, "THERMOTRAIL", 10)
        pyxel.text(62, 48, "ENDLESS RUNNER MIT TEMPERATUR", 7)

        pyxel.text(34, 70, "1 = KAELTE-CHARAKTER", 12)
        pyxel.text(34, 82, "2 = HITZE-CHARAKTER", 8)

        if self.selected_char == 0:
            pyxel.text(34, 98, "> FRIERT LANGSAMER", 7)
        else:
            pyxel.text(34, 98, "> VERBRENNT LANGSAMER", 7)

        pyxel.text(34, 116, "WASD / PFEILE = BEWEGEN", 7)
        pyxel.text(34, 126, "Q / E = TEMPERATUR AENDERN", 7)
        pyxel.text(34, 136, "3 SCHLUESSEL FINDEN", 10)
        pyxel.text(34, 146, "DANACH FESTUNG ERREICHEN", 10)

        self.draw_menu_preview(70, 188)

        if (pyxel.frame_count // 20) % 2 == 0:
            pyxel.text(92, 228, "ENTER ZUM START", 7)

    def draw_lose(self):
        self.draw_world()
        pyxel.rect(20, 70, 216, 110, 0)
        pyxel.rectb(20, 70, 216, 110, 8)

        pyxel.text(102, 86, "GAME OVER", 8)
        pyxel.text(34, 108, self.player.death_reason[:30], 7)
        pyxel.text(34, 126, f"REICHWEITE: {self.score}", 11)
        pyxel.text(34, 138, f"SCHLUESSEL: {self.keys}/3", 10)
        pyxel.text(70, 160, "ENTER FUER MENU", 7)

    def draw_win(self):
        self.draw_world()
        pyxel.rect(20, 70, 216, 110, 1)
        pyxel.rectb(20, 70, 216, 110, 10)

        pyxel.text(112, 86, "SIEG!", 10)
        pyxel.text(50, 108, "DU HAST DIE FESTUNG ERREICHT", 7)
        pyxel.text(34, 126, f"REICHWEITE: {self.score}", 11)
        pyxel.text(34, 138, f"SCHLUESSEL: {self.keys}/3", 10)
        pyxel.text(70, 160, "ENTER FUER MENU", 7)

        self.draw_cell(118, 146, Cell(FORTRESS, fortress=True))

    # --------------------------------------------------------
    # MAIN DRAW
    # --------------------------------------------------------
    def draw(self):
        if self.state == "menu":
            self.draw_menu()
            return

        if self.state == "play":
            self.draw_world()
            self.draw_ui()
            return

        if self.state == "lose":
            self.draw_lose()
            return

        if self.state == "win":
            self.draw_win()
            return


Game()