# Pyxel Studi
import pyxel
import random

TILE = 16
MAP_W = 20
MAP_H = 15

LOOP_TIME = 240
MAX_CLONES = 3

STATE_TITLE = 0
STATE_GAME = 1

levels = [

    # LEVEL 1
    [
        "####################",
        "#S....#............#",
        "#.##..#............#",
        "#.....#..######....#",
        "#.#####...L....#....#",
        "#.........L....#....#",
        "#.........L....#....#",
        "#.........L....#....#",
        "#.........L....#....#",
        "#.........L....#....#",
        "#.........L....#....#",
        "#.....P...L....#....#",
        "#..LLLLLLLLLLLLLL..#",
        "#..L....E.....L....#",
        "####################",
    ],

    # LEVEL 2
    [
        "####################",
        "#.................E#",
        "#..######..........#",
        "#..#....#..........#",
        "#..#....#....M.....#",
        "#..#....######.....#",
        "#..#...............#",
        "#..######..........#",
        "#..............#...#",
        "#..............#...#",
        "#....######....#...#",
        "#..............#...#",
        "#..............#...#",
        "#S.............#...#",
        "####################",
    ],

    # LEVEL 3
    [
        "####################",
        "#.................E#",
        "#..######..........#",
        "#..#....#....M.....#",
        "#..#....######.....#",
        "#..#...............#",
        "#..######LLLLLLLLL#",
        "#........LLLLLLLLL#",
        "#........LLLLLLLLL#",
        "#....######LLLLLLL#",
        "#...........LLLLLL#",
        "#...........LLLLLL#",
        "#...........P.....#",
        "#S................#",
        "####################",
    ],

    # LEVEL 4
    [
        "####################",
        "#....#......#....E##",
        "#.##.#.####.#.##...#",
        "#.#..#....#.#..#...#",
        "#.#.######.#.##.#..#",
        "#.#........#....#..#",
        "#.######.###.####..#",
        "#......#...#.......#",
        "#.####.#.#.#.#######",
        "#.#....#.#.#......##",
        "#.#.####.#.######.#",
        "#.#......#........#",
        "#.########.######.#",
        "#S...............##",
        "####################",
    ],

    # LEVEL 5 (JETZT MIT LASERN VOR DEM ZIEL)
    [
        "####################",
        "#S....#........#...#",
        "#.##..#.#####..#.##.#",
        "#....#.#.#.#...#....#",
        "####.#.#.#.#.#.#.####",
        "#....#...#...#...#..#",
        "#.#####.#########.#.#",
        "#.....#.....#.....#.#",
        "#.#####.###.###.#.#.#",
        "#.#.....#..P..#...#.#",
        "#.#.###.#.###.#.#.#.#",
        "#...#...#...#...#...#",
        "#.###.###.###.###.###",
        "#...........LLLE....#",
        "####################",
    ],

    # LEVEL 6
    [
        "####################",
        "#S#.....#.....#...#",
        "#.#.###.#.###.#.#.#",
        "#.#.#...#...#.#.#.#",
        "#.#.#.###L#.#.#.#.#",
        "#.#.#...#L#.#.#.#.#",
        "#.#.###.#L#.###.#.#",
        "#.#...#.#L#...#.#.#",
        "#.###.#.#L###.#.#.#",
        "#...#.#.#.....#.#.#",
        "###.#.#.#######.#.#",
        "#...#.#.....P...#.#",
        "#.###.#####L#####.#",
        "#.....#.........E#",
        "####################",
    ],

    # LEVEL 7
    [
        "####################",
        "#S.................#",
        "#.###############..#",
        "#.#.............#..#",
        "#.#.###########.#..#",
        "#.#.#.........#.#..#",
        "#.#.#.#######.#.#..#",
        "#.#.#.#.....#.#.#..#",
        "#.#.#.#.###.#.#.#..#",
        "#.#...#.#M#.#...#..#",
        "#.###.#.#.#.#.###.##",
        "#...#.#.#.#.#...#..#",
        "###.#.#.#.#.###.#M.#",
        "#.....#...#.....#..E#",
        "####################",
    ]

]

class Game:

    def __init__(self):
        pyxel.init(MAP_W * TILE, MAP_H * TILE, title="Cave Runner")

        self.state = STATE_TITLE
        self.level_index = 0
        self.win = False
        self.confetti = []

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

    def load_level(self):
        self.level = levels[self.level_index]
        self.enemies = []
        self.history = []
        self.clones = []
        self.time = 0
        self.revealed_walls = set()
        self.hidden_walls = set()

        self.has_switch = any("P" in row for row in self.level)

        for y, row in enumerate(self.level):
            for x, t in enumerate(row):
                if t == "S":
                    self.player_x = x
                    self.player_y = y
                if t == "M":
                    self.enemies.append([x, y])

    def next_level(self):
        self.level_index += 1
        if self.level_index >= len(levels):
            self.win = True
        else:
            self.load_level()

    def is_switch_active(self):
        if self.level[self.player_y][self.player_x] == "P":
            return True
        for clone in self.clones:
            if clone:
                i = min(self.time, len(clone) - 1)
                x, y = clone[i]
                if self.level[y][x] == "P":
                    return True
        return False

    def can_move(self, x, y):
        if x < 0 or y < 0 or x >= MAP_W or y >= MAP_H:
            return False

        tile = self.level[y][x]

        if tile == "#":
            if self.level_index == 6:
                self.revealed_walls.add((x, y))
            return False

        if tile == "L" and not self.is_switch_active():
            return False

        if tile == "E" and self.has_switch and not self.is_switch_active():
            return False

        return True

    def update_enemies(self):
        for e in self.enemies:
            dx = (self.player_x > e[0]) - (self.player_x < e[0])
            dy = (self.player_y > e[1]) - (self.player_y < e[1])
            nx, ny = e[0] + dx, e[1] + dy
            if self.can_move(nx, ny):
                e[0], e[1] = nx, ny
            if (e[0], e[1]) == (self.player_x, self.player_y):
                self.load_level()

    def update(self):
        if self.state == STATE_TITLE:
            if pyxel.btnp(pyxel.KEY_RETURN):
                self.state = STATE_GAME
            return

        if self.win:
            if pyxel.frame_count % 3 == 0:
                self.confetti.append([random.randint(0, MAP_W*TILE), 0, random.randint(6, 15)])
            for c in self.confetti:
                c[1] += 2
            return

        self.time += 1
        move = (0, 0)

        if pyxel.frame_count % 2 == 0:
            if pyxel.btn(pyxel.KEY_LEFT):   move = (-1, 0)
            elif pyxel.btn(pyxel.KEY_RIGHT): move = (1,  0)
            elif pyxel.btn(pyxel.KEY_UP):    move = (0, -1)
            elif pyxel.btn(pyxel.KEY_DOWN):  move = (0,  1)

        nx = self.player_x + move[0]
        ny = self.player_y + move[1]

        if self.can_move(nx, ny):
            self.player_x = nx
            self.player_y = ny

        self.history.append((self.player_x, self.player_y))

        if self.level[self.player_y][self.player_x] == "E":
            self.next_level()

        if self.time % 5 == 0:
            self.update_enemies()

        current_loop_time = LOOP_TIME
        if self.level_index == 4:
            current_loop_time = 420

        if self.time > current_loop_time:
            self.clones.append(self.history.copy())
            if len(self.clones) > MAX_CLONES:
                self.clones.pop(0)

            self.history = []
            self.time = 0

            for y, row in enumerate(self.level):
                for x, t in enumerate(row):
                    if t == "S":
                        self.player_x = x
                        self.player_y = y

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

        if self.state == STATE_TITLE:
            self.draw_title()
            return

        if self.win:
            pyxel.text(MAP_W * TILE // 2 - 16, MAP_H * TILE // 2, "YOU WIN!", 7)
            for c in self.confetti:
                pyxel.pset(c[0], c[1], c[2])
            return

        switch_active = self.is_switch_active()

        for y, row in enumerate(self.level):
            for x, t in enumerate(row):
                px = x * TILE
                py = y * TILE
                T = TILE

                if t == "#":
                    if self.level_index == 6 and (x, y) not in self.revealed_walls:
                        continue
                    pyxel.rect(px, py, T, T, 5)
                    pyxel.rect(px + 2, py + 2, T - 4, T - 4, 1)
                    pyxel.line(px + 2, py + 2, px + T - 3, py + 2, 6)
                    pyxel.line(px + 2, py + 2, px + 2, py + T - 3, 6)

                elif t == "E":
                    pyxel.rect(px, py, T, T, 3)
                    pyxel.rect(px + 3, py + 3, T - 6, T - 6, 11)
                    pyxel.rect(px + 5, py + 5, T - 10, T - 10, 7)

                elif t == "L":
                    col = 11 if switch_active else 2
                    pyxel.rect(px, py, T, T, col)
                    if switch_active:
                        pyxel.line(px, py + T // 2, px + T - 1, py + T // 2, 7)
                        pyxel.line(px + T // 2, py, px + T // 2, py + T - 1, 7)

                elif t == "P":
                    pyxel.rect(px, py, T, T, 9)
                    pyxel.rect(px + 3, py + 3, T - 6, T - 6, 10)

        for clone in self.clones:
            if clone:
                i = min(self.time, len(clone) - 1)
                cx, cy = clone[i]
                self._draw_player(cx * TILE, cy * TILE, color=12)

        for e in self.enemies:
            self._draw_enemy(e[0] * TILE, e[1] * TILE)

        self._draw_player(self.player_x * TILE, self.player_y * TILE, color=7)

        pyxel.rect(0, 0, 60, 9, 0)
        pyxel.text(3, 2, f"LEVEL: {self.level_index + 1}", 7)

    def _draw_player(self, px, py, color=7):
        T = TILE
        pyxel.rect(px + 5, py + 1,  6, 5, color)
        pyxel.pset(px + 6, py + 3, 0)
        pyxel.pset(px + 9, py + 3, 0)
        pyxel.rect(px + 4, py + 6, 8, 5, color)
        pyxel.rect(px + 1, py + 6, 3, 4, color)
        pyxel.rect(px + 12, py + 6, 3, 4, color)
        pyxel.rect(px + 4, py + 11, 3, 4, color)
        pyxel.rect(px + 9, py + 11, 3, 4, color)

    def _draw_enemy(self, px, py):
        T = TILE
        pyxel.rect(px + 3, py + 3, 10, 10, 8)
        pyxel.rect(px + 5, py + 5, 2, 2, 7)
        pyxel.rect(px + 9, py + 5, 2, 2, 7)
        pyxel.line(px + 5, py + 10, px + 11, py + 10, 7)
        pyxel.pset(px + 4, py + 2, 8)
        pyxel.pset(px + 11, py + 2, 8)

    def draw_title(self):
        W = MAP_W * TILE
        H = MAP_H * TILE

        for y in range(MAP_H):
            for x in range(MAP_W):
                if (x + y) % 3 == 0:
                    pyxel.rect(x * TILE, y * TILE, TILE, TILE, 1)

        pyxel.rect(W // 2 - 55, H // 2 - 35, 110, 25, 5)
        pyxel.rectb(W // 2 - 55, H // 2 - 35, 110, 25, 7)
        pyxel.text(W // 2 - 30, H // 2 - 28, "CAVE  RUNNER", 7)

        pyxel.text(W // 2 - 44, H // 2, "Time loops. Lasers. Clones.", 6)

        self._draw_player(W // 2 - 8, H // 2 + 20, color=7)

        blink = (pyxel.frame_count // 20) % 2
        if blink:
            pyxel.text(W // 2 - 28, H - 30, "PRESS ENTER", 6)

Game()