import pyxel
import random

TILE = 16
MAP  = 19           # grössere Karte
W    = MAP * TILE   # 304
H    = MAP * TILE   # 304

# Pyxel-Palette:
# 0=schwarz 1=navy  2=lila  3=dunkelgrün 4=braun   5=dunkelgrau
# 6=grau    7=weiss 8=rot   9=orange    10=gelb   11=cyan
# 12=blau  13=indigo 14=pink 15=pfirsich


class Game:
    def __init__(self):
        pyxel.init(W, H, title="Pyramid Escape", fps=30)
        self._init_sounds()
        self.state       = "menu"
        self.level       = 1
        self.max_level   = 3
        self.confetti    = []
        self.anim        = 0
        self.menu_anim   = 0
        self.wrong_flash = 0
        self.enter_code  = ""
        self.target_code = ""
        self.collected   = []
        self.codes       = []
        # Wolken: (x, y, breite)
        self.clouds = [
            (random.randint(0, W), random.randint(12, 55), random.randint(30, 64))
            for _ in range(6)
        ]
        pyxel.run(self.update, self.draw)

    # ══════════════════════════════════════════════════════ SOUNDS ═══════════
    def _init_sounds(self):
        pyxel.sound(0).set("C3E3G3",   "PPP",  "543",  "NNN", 8)   # Aufsammeln
        pyxel.sound(1).set("C2B1A1",   "NNN",  "543",  "NNN", 7)   # Falscher Code
        pyxel.sound(2).set("C3E3G3C4", "PPPP", "4444", "NNNS", 7)  # Level-Up
        pyxel.sound(3).set("G2E2C2A1", "NNNN", "7654", "NNNN", 6)  # Erwischt

        # BGM: ruhige ägyptische Melodie, viele Pausen, nicht nervig
        pyxel.sound(4).set(
            "A2 R  R  E3 R  R  D3 R  C3 R  R  A2 R  R  G2 R",
            "T" * 16, "3" * 16, "N" * 16, 30
        )
        # BGM Bass – leiser Bordun auf A
        pyxel.sound(5).set(
            "A1 R  R  R  R  R  R  R  E1 R  R  R  R  R  R  R",
            "T" * 16, "2" * 16, "N" * 16, 30
        )
        # Siegesmelodie – fröhlich, kurz aufsteigend (max Oktave 4)
        pyxel.sound(6).set(
            "C3 E3 G3 C4 E4 G4 C4 R",
            "PPPPPPPP", "34567654", "NNNNNNNS", 10
        )

    def _play_music(self):
        pyxel.play(0, 4, loop=True)
        pyxel.play(1, 5, loop=True)

    def _stop_music(self):
        pyxel.stop(0)
        pyxel.stop(1)

    # ══════════════════════════════════════════════════════ MAZE ═════════════
    def generate_maze(self):
        maze = [[1] * MAP for _ in range(MAP)]

        def carve(x, y):
            dirs = [(2,0),(-2,0),(0,2),(0,-2)]
            random.shuffle(dirs)
            for dx, dy in dirs:
                nx, ny = x+dx, y+dy
                if 1 <= nx < MAP-1 and 1 <= ny < MAP-1 and maze[ny][nx] == 1:
                    maze[y+dy//2][x+dx//2] = 0
                    maze[ny][nx] = 0
                    carve(nx, ny)

        maze[1][1] = 0
        carve(1, 1)

        # Extra-Durchgänge → leichteres Entkommen
        for _ in range((MAP * MAP) // 7):
            x = random.randint(1, MAP-2)
            y = random.randint(1, MAP-2)
            if maze[y][x] == 1:
                nb = sum(maze[y+dy][x+dx] == 0
                         for dx, dy in [(1,0),(-1,0),(0,1),(0,-1)])
                if nb >= 2:
                    maze[y][x] = 0
        return maze

    def free_cells(self):
        return [(x,y) for y in range(MAP) for x in range(MAP) if self.maze[y][x] == 0]

    # ══════════════════════════════════════════════════════ LEVEL ════════════
    def new_level(self):
        self.maze = self.generate_maze()
        cells = self.free_cells()
        random.shuffle(cells)

        code_len = 2 + self.level
        self.target_code = "".join(str(random.randint(0,9)) for _ in range(code_len))
        self.collected   = []
        self.enter_code  = ""
        self.wrong_flash = 0

        self.player_x, self.player_y = cells[0]
        self.exit_x,   self.exit_y   = cells[1]

        start_idx = max(2+code_len, len(cells)*6//10)
        self.mummy_x, self.mummy_y   = cells[min(start_idx, len(cells)-1)]
        self.mummy_timer = 0
        self.mummy_speed = 12 - self.level * 2   # L1=10, L2=8, L3=6

        self.codes = [
            [cells[2+i][0], cells[2+i][1], self.target_code[i], i]
            for i in range(code_len)
        ]

    # ══════════════════════════════════════════════════════ BEWEGUNG ═════════
    def move(self, dx, dy):
        nx, ny = self.player_x+dx, self.player_y+dy
        if 0 <= nx < MAP and 0 <= ny < MAP and self.maze[ny][nx] == 0:
            self.player_x, self.player_y = nx, ny

    def mummy_move(self):
        self.mummy_timer += 1
        if self.mummy_timer < self.mummy_speed:
            return
        self.mummy_timer = 0
        dx = self.player_x - self.mummy_x
        dy = self.player_y - self.mummy_y
        steps = []
        if abs(dx) >= abs(dy):
            if dx: steps.append((1 if dx>0 else -1, 0))
            if dy: steps.append((0, 1 if dy>0 else -1))
        else:
            if dy: steps.append((0, 1 if dy>0 else -1))
            if dx: steps.append((1 if dx>0 else -1, 0))
        for mdx, mdy in steps:
            nx, ny = self.mummy_x+mdx, self.mummy_y+mdy
            if 0 <= nx < MAP and 0 <= ny < MAP and self.maze[ny][nx] == 0:
                self.mummy_x, self.mummy_y = nx, ny
                return

    def _collected_display(self):
        r = ["_"] * len(self.target_code)
        for slot, digit in self.collected:
            r[slot] = digit
        return " ".join(r)

    # ══════════════════════════════════════════════════════ UPDATE ═══════════
    def update(self):
        self.anim      += 1
        self.menu_anim += 1
        if self.wrong_flash > 0:
            self.wrong_flash -= 1

        if self.state == "menu":
            self.clouds = [((cx+0.25) % (W+64), cy, cw) for cx,cy,cw in self.clouds]
            if pyxel.btnp(pyxel.KEY_SPACE):
                self.level = 1
                self.new_level()
                self._play_music()
                self.state = "game"

        elif self.state == "game":
            if pyxel.btnp(pyxel.KEY_UP):    self.move(0,-1)
            if pyxel.btnp(pyxel.KEY_DOWN):  self.move(0, 1)
            if pyxel.btnp(pyxel.KEY_LEFT):  self.move(-1,0)
            if pyxel.btnp(pyxel.KEY_RIGHT): self.move( 1,0)

            for c in self.codes[:]:
                if (c[0],c[1]) == (self.player_x,self.player_y):
                    self.collected.append((c[3],c[2]))
                    self.codes.remove(c)
                    pyxel.play(3, 0)

            self.mummy_move()

            if (self.player_x,self.player_y) == (self.mummy_x,self.mummy_y):
                self._stop_music()
                pyxel.play(0, 3)
                self.state = "gameover"

            if (self.player_x,self.player_y) == (self.exit_x,self.exit_y):
                if len(self.collected) == len(self.target_code):
                    self.enter_code = ""
                    self.state = "enter"

        elif self.state == "enter":
            for i in range(10):
                if pyxel.btnp(getattr(pyxel, f"KEY_{i}")):
                    if len(self.enter_code) < len(self.target_code):
                        self.enter_code += str(i)
            if pyxel.btnp(pyxel.KEY_BACKSPACE):
                self.enter_code = self.enter_code[:-1]
            if pyxel.btnp(pyxel.KEY_RETURN):
                if self.enter_code == self.target_code:
                    self.level += 1
                    if self.level > self.max_level:
                        self._stop_music()
                        pyxel.play(0, 6)   # Siegesmelodie
                        self.state = "win"
                    else:
                        pyxel.play(3, 2)
                        self.new_level()
                        self.state = "game"
                else:
                    self.wrong_flash = 40
                    pyxel.play(3, 1)
                    self.enter_code = ""

        elif self.state == "win":
            self.clouds = [((cx+0.25) % (W+64), cy, cw) for cx,cy,cw in self.clouds]
            for _ in range(4):
                self.confetti.append([
                    random.randint(0,W-1), 0,
                    random.randint(1,4),
                    random.choice([9,10,11,8,15])
                ])
            for c in self.confetti:
                c[1] += c[2]
            self.confetti = [c for c in self.confetti if c[1] < H]
            if pyxel.btnp(pyxel.KEY_R):
                self.level    = 1
                self.confetti = []
                self.state    = "menu"

        elif self.state == "gameover":
            if pyxel.btnp(pyxel.KEY_R):
                self.state = "menu"

    # ══════════════════════════════════════════════════════ DRAW ════════════
    def draw(self):
        if   self.state == "menu":     self._draw_menu()
        elif self.state == "game":     self._draw_game()
        elif self.state == "enter":    self._draw_enter()
        elif self.state == "gameover": self._draw_gameover()
        elif self.state == "win":      self._draw_win()

    # ──────────────────────────── GEMEINSAME HINTERGRUND-ELEMENTE ───────────
    def _draw_sky_and_desert(self):
        """Blauer Himmel mit Wolken + Wüstenboden – nach Foto-Stimmung."""
        # Himmel: oben satt blau → unten heller
        pyxel.rect(0, 0,     W, H//3,       12)
        pyxel.rect(0, H//3,  W, H//6,        6)
        pyxel.rect(0, H//2,  W, H//2,        7)   # Übergangsstreifen

        # Wolken (fluffig, mehrschichtig)
        for cx, cy, cw in self.clouds:
            ix, iy = int(cx)-30, int(cy)
            pyxel.rect(ix,       iy+4,  cw,   10, 7)
            pyxel.rect(ix+4,     iy,    cw-8,  7, 7)
            pyxel.rect(ix+cw//3, iy-3,  cw//3, 6, 7)
            pyxel.rect(ix+2,     iy+11, cw-2,  4, 6)   # Wolkenschatten

        # Wüstenboden mit Dünen-Schichtung (aus Foto: warm sandfarben)
        base_y = H - 68
        pyxel.rect(0, base_y,    W, 68,  4)    # Hauptsand
        pyxel.rect(0, base_y,    W,  3,  9)    # Kante
        pyxel.rect(0, base_y+16, W,  6,  9)    # Dünenlinie 1
        pyxel.rect(0, base_y+30, W,  4, 10)    # Dünenlinie 2 (heller)
        pyxel.rect(0, base_y+48, W,  3,  9)    # Dünenlinie 3

    def _draw_pyramid(self, apex_y=68, base_w=220, door=True):
        """Pyramide im Foto-Stil: links Licht (gelb 10), rechts Schatten (orange 9)."""
        apex_x = W // 2
        base_y = H - 65

        rows = 44
        for row in range(rows):
            t    = row / (rows-1)
            rw   = int(base_w * t)
            rx   = apex_x - rw // 2
            ry   = apex_y + int((base_y - apex_y) * t)
            rh   = int((base_y - apex_y) / rows) + 2
            half = rw // 2
            pyxel.rect(rx,        ry, half,      rh, 10)   # Lichtseite gelb
            pyxel.rect(rx + half, ry, rw - half, rh,  9)   # Schattensiete orange
            # Steinblock-Fugen (alle 4 Zeilen)
            if row % 4 == 0 and rw > 4:
                pyxel.line(rx, ry, rx+rw-1, ry, 4)

        # Aussenlinien
        pyxel.line(apex_x, apex_y, apex_x - base_w//2, base_y, 4)
        pyxel.line(apex_x, apex_y, apex_x + base_w//2, base_y, 4)
        pyxel.line(apex_x, apex_y, apex_x,             base_y, 4)  # Grat

        # Eingangstor
        if door:
            dx, dy = W//2 - 11, base_y - 28
            pyxel.rect(dx,    dy,    22, 28, 1)   # Schatten innen
            pyxel.rect(dx+3,  dy+3,  16,  9, 0)  # Dunkelheit
            pyxel.rectb(dx,   dy,    22, 28, 4)   # Rahmen

    # ────────────────────────────────────────────────────────── MENÜ ─────────
    def _draw_menu(self):
        self._draw_sky_and_desert()
        self._draw_pyramid(apex_y=62, base_w=230, door=True)

        # Titel
        pyxel.rect(0, 6, W, 18, 0)
        pyxel.text(W//2 - 44, 12, "PYRAMID  ESCAPE", 10)

        # Anleitungsbox
        bx, bw2 = 24, W - 48
        by, bh  = 152, 88
        pyxel.rect(bx, by, bw2, bh, 0)
        pyxel.rectb(bx, by, bw2, bh, 9)
        lx = bx + 10
        pyxel.text(lx, by+ 8, "Sammle alle Ziffern im Labyrinth", 7)
        pyxel.text(lx, by+18, "Gehe zum Ausgang  (OUT)", 7)
        pyxel.text(lx, by+28, "Tippe den Code  +  ENTER", 7)
        pyxel.text(lx, by+42, "Weiche der Mumie aus!", 8)
        pyxel.text(lx, by+54, "Steuerung: Pfeiltasten", 5)
        pyxel.text(lx, by+66, "R = Neustart  |  3 Level", 5)

        blink = 10 if (self.menu_anim//15) % 2 == 0 else 7
        pyxel.text(W//2 - 56, H - 12, "[ LEERTASTE ZUM STARTEN ]", blink)

    # ────────────────────────────────────────────────────────── SPIEL ────────
    def _draw_game(self):
        pyxel.cls(4)

        for y in range(MAP):
            for x in range(MAP):
                tx, ty = x*TILE, y*TILE
                if self.maze[y][x] == 1:
                    pyxel.rect(tx, ty, TILE, TILE, 9)
                    pyxel.line(tx,        ty,        tx+TILE-1, ty,        10)
                    pyxel.line(tx,        ty,        tx,        ty+TILE-1, 10)
                    pyxel.line(tx+TILE-1, ty+1,      tx+TILE-1, ty+TILE-1,  4)
                else:
                    pyxel.rect(tx, ty, TILE, TILE, 0)

        ex, ey = self.exit_x*TILE, self.exit_y*TILE
        gc = 11 if (self.anim//8)%2==0 else 3
        pyxel.rect(ex+1, ey+1, TILE-2, TILE-2, gc)
        pyxel.rectb(ex, ey, TILE, TILE, 7)
        pyxel.text(ex+1, ey+5, "OUT", 0)

        for c in self.codes:
            cx2, cy2 = c[0]*TILE, c[1]*TILE
            col = 12 if (self.anim//5)%2==0 else 13
            pyxel.rect(cx2+2, cy2+2, 12, 12, col)
            pyxel.rectb(cx2+2, cy2+2, 12, 12, 7)
            pyxel.text(cx2+5, cy2+5, str(c[2]), 10)

        self._draw_player(self.player_x*TILE, self.player_y*TILE)
        self._draw_mummy(self.mummy_x*TILE,   self.mummy_y*TILE)

        # HUD
        pyxel.rect(0, 0, W, 18, 0)
        pyxel.line(0, 18, W-1, 18, 9)
        cdisp     = self._collected_display()
        remaining = len(self.target_code) - len(self.collected)
        pyxel.text(3, 5, f"CODE: {cdisp}", 10)
        if remaining > 0:
            pyxel.text(210, 5, f"Noch:{remaining}", 6)
        else:
            blink = 9 if (self.anim//5)%2==0 else 10
            pyxel.text(174, 5, "-> AUSGANG!", blink)
        pyxel.text(272, 5, f"LV{self.level}", 7)

    # ────────────────────────────────────────────────────────── ENTER ────────
    def _draw_enter(self):
        pyxel.cls(4)
        for y in range(MAP):
            for x in range(MAP):
                if self.maze[y][x] == 1:
                    pyxel.rect(x*TILE, y*TILE, TILE, TILE, 9)

        pyxel.rect(22, 55, W-44, 160, 0)
        bc = 8 if self.wrong_flash > 0 else 9
        pyxel.rectb(22, 55, W-44, 160, bc)
        pyxel.rectb(24, 57, W-48, 156, 10)

        pyxel.text(W//2 - 40, 68, "CODE EINGEBEN", 10)
        cdisp = self._collected_display()
        pyxel.text(34, 86, f"Gesammelt: {cdisp}", 7)

        code_len = len(self.target_code)
        bw2, gap = 22, 4
        total = code_len*(bw2+gap) - gap
        sx = (W - total) // 2

        for i in range(code_len):
            bx = sx + i*(bw2+gap)
            by = 106
            bg = 8 if self.wrong_flash > 0 else 4
            pyxel.rect(bx, by, bw2, 26, bg)
            bc2 = 10 if i < len(self.enter_code) else 6
            pyxel.rectb(bx, by, bw2, 26, bc2)
            ch  = self.enter_code[i] if i < len(self.enter_code) else "_"
            col = 8 if self.wrong_flash > 0 else (10 if i < len(self.enter_code) else 6)
            pyxel.text(bx+8, by+9, ch, col)

        if self.wrong_flash > 0:
            pyxel.text(W//2-36, 146, "FALSCHER CODE!", 8)
        else:
            pyxel.text(W//2-44, 146, "ENTER bestaetigen", 7)
        pyxel.text(W//2-50, 160, "BACKSPACE = loeschen", 6)

    # ────────────────────────────────────────────────────────── GAME OVER ────
    def _draw_gameover(self):
        pyxel.cls(0)
        for y in range(0, H, 8):
            pyxel.line(0, y, W-1, y, 1 if (y//8)%2==0 else 0)
        self._draw_mummy(W//2-8, H//2-28)
        pyxel.rect(0, H//2+14, W, 46, 0)
        pyxel.rectb(0, H//2+14, W, 46, 8)
        pyxel.text(W//2-28, H//2+23, "GAME OVER", 8)
        pyxel.text(W//2-29, H//2+22, "GAME OVER", 15)
        pyxel.text(W//2-52, H//2+36, "Die Mumie hat dich erwischt!", 6)
        pyxel.text(W//2-36, H//2+48, "R = neu starten", 9)

    # ────────────────────────────────────────────────────────── SIEG ─────────
    def _draw_win(self):
        self._draw_sky_and_desert()
        self._draw_pyramid(apex_y=75, base_w=210, door=False)

        for c in self.confetti:
            pyxel.pset(c[0], c[1], c[3])

        pyxel.rect(0, 20, W, 62, 0)
        pyxel.rectb(0, 20, W, 62, 10)
        pyxel.text(W//2 - 44, 33, "Du bist entkommen.", 10)
        pyxel.text(W//2 - 44, 33, "Du bist entkommen.", 10)
        pyxel.text(W//2 - 43, 32, "Du bist entkommen.",  9)
        pyxel.text(W//2 - 40, 50, "Der Schatz gehoert dir!", 7)
        if (self.anim//12) % 2 == 0:
            pyxel.text(W//2 - 36, 66, "R = neu starten", 6)

    # ══════════════════════════════════════════════════════ SPIELER-SPRITE ════
    # Ägyptischer Erkunder: Nemes-Kopftuch, Kilt, Fackel
    # 4-Frame Walk-Cycle für flüssige Animation
    def _draw_player(self, x, y):
        s = (self.anim // 5) % 4

        # Schatten
        pyxel.rect(x+2, y+15, 12, 2, 5)

        # Beine – 4-Frame-Zyklus
        ll = 1 if s in (1,2) else 0
        lr = 1 if s in (3,0) else 0
        pyxel.rect(x+4, y+10+ll, 3, 5-ll, 15)
        pyxel.rect(x+9, y+10+lr, 3, 5-lr, 15)
        pyxel.rect(x+3+ll, y+14, 4, 2, 4)   # Sandale L
        pyxel.rect(x+8+lr, y+14, 4, 2, 4)   # Sandale R

        # Kilt – weiss, mit vertikalen Faltenlinien
        pyxel.rect(x+3, y+7, 10, 4, 7)
        pyxel.line(x+3, y+10, x+12, y+10, 6)
        pyxel.line(x+5, y+7,  x+5,  y+10, 6)
        pyxel.line(x+8, y+7,  x+8,  y+10, 6)
        pyxel.line(x+11,y+7,  x+11, y+10, 6)

        # Torso mit Usekh-Kragen
        pyxel.rect(x+4, y+4, 8, 4, 15)
        pyxel.rect(x+3, y+7, 10, 1, 9)
        for i in range(5): pyxel.pset(x+3+i*2, y+7, 10)

        # Arme – Gegenschwung
        arm_up = s in (0,1)
        al = y+4 if arm_up else y+5
        ar = y+5 if arm_up else y+4
        pyxel.rect(x+1,  al, 3, 4, 15)
        pyxel.rect(x+12, ar, 3, 4, 15)

        # Fackel rechts – flackernde Flamme
        fy = ar - 1
        pyxel.rect(x+14, fy+1, 1, 4, 4)
        fl = 10 if (self.anim//3)%2==0 else 9
        pyxel.pset(x+14, fy,   fl)
        pyxel.pset(x+15, fy,   10)
        pyxel.pset(x+13, fy+1, 9)

        # Kopf
        pyxel.rect(x+4, y+1, 8, 4, 15)
        pyxel.pset(x+6, y+3, 0)
        pyxel.pset(x+9, y+3, 0)
        pyxel.pset(x+5, y+3, 5)
        pyxel.pset(x+10,y+3, 5)
        pyxel.pset(x+7, y+4, 4)
        pyxel.pset(x+8, y+4, 4)

        # Nemes-Kopftuch blau + goldener Stirnreif
        pyxel.rect(x+4,  y,    8, 2, 12)
        pyxel.rect(x+3,  y+1,  2, 3, 12)
        pyxel.rect(x+11, y+1,  2, 3, 12)
        pyxel.line(x+4,  y,    x+11, y, 10)
        pyxel.pset(x+7,  y, 10)

    # ══════════════════════════════════════════════════════ MUMIEN-SPRITE ═════
    # Mumie: breite Schultern, Bandagen-Animation, drohende Knochenfinger
    # 4-Frame Schlurfen
    def _draw_mummy(self, x, y):
        s = (self.anim // 4) % 4

        # Schatten
        pyxel.rect(x+1, y+15, 14, 2, 5)

        # Beine schleppend
        ll = 1 if s in (1,2) else 0
        lr = 1 if s in (3,0) else 0
        pyxel.rect(x+3,  y+10+ll, 5, 5-ll, 6)
        pyxel.rect(x+9,  y+10+lr, 5, 5-lr, 6)
        pyxel.line(x+3,  y+11, x+7,  y+13, 13)
        pyxel.line(x+9,  y+11, x+13, y+13, 13)
        pyxel.rect(x+2+ll, y+14, 6, 2, 13)   # Fuss L
        pyxel.rect(x+8+lr, y+14, 6, 2,  6)   # Fuss R

        # Körper – breiter
        pyxel.rect(x+2, y+5, 13, 6, 6)
        boff = s % 2
        pyxel.line(x+2, y+6+boff,  x+14, y+6+boff,  13)
        pyxel.line(x+2, y+8-boff,  x+14, y+8-boff,  13)
        pyxel.line(x+2, y+10,      x+14, y+10,       13)
        pyxel.pset(x+7, y+7, 5)

        # Ausgestreckte Arme – auf/ab animiert
        arm_raise = 1 if s in (0,2) else 0
        pyxel.rect(x-2,  y+4+arm_raise, 5, 3, 6)
        pyxel.rect(x+14, y+4+arm_raise, 5, 3, 6)
        pyxel.line(x-2,  y+4+arm_raise, x+2,  y+6+arm_raise, 13)
        pyxel.line(x+14, y+4+arm_raise, x+18, y+6+arm_raise, 13)
        # Knochenfinger
        for f in range(3):
            pyxel.pset(x-2+f, y+4+arm_raise, 7 if f==1 else 5)
            pyxel.pset(x+15+f,y+4+arm_raise, 7 if f==1 else 5)

        # Kopf
        pyxel.rect(x+3, y+1, 11, 5, 6)
        pyxel.line(x+3, y+2, x+13, y+2, 13)
        pyxel.line(x+3, y+4, x+13, y+4, 13)
        pyxel.line(x+3, y+1, x+3,  y+5, 13)
        pyxel.line(x+13,y+1, x+13, y+5, 13)

        # Pulsierende Augen
        eye_c = 11 if (self.anim//3)%2==0 else 8
        pyxel.rect(x+4,  y+2, 3, 2, eye_c)
        pyxel.rect(x+10, y+2, 3, 2, eye_c)
        pyxel.pset(x+5,  y+3, 0)
        pyxel.pset(x+11, y+3, 0)


Game()