import pyxel
import math
import random


class Vector3:

    def __init__(self, x, y, z):

        self.x = x
        self.y = y
        self.z = z

    def rotate_x(self, a):

        c = math.cos(a)
        s = math.sin(a)

        return Vector3(
            self.x,
            self.y * c - self.z * s,
            self.y * s + self.z * c
        )

    def rotate_y(self, a):

        c = math.cos(a)
        s = math.sin(a)

        return Vector3(
            self.x * c + self.z * s,
            self.y,
            -self.x * s + self.z * c
        )

    def rotate_z(self, a):

        c = math.cos(a)
        s = math.sin(a)

        return Vector3(
            self.x * c - self.y * s,
            self.x * s + self.y * c,
            self.z
        )


class Cubie:

    def __init__(self, x, y, z):

        self.x = x
        self.y = y
        self.z = z

        # COLOR SCHEME:
        # U = YELLOW
        # D = WHITE
        # F = GREEN
        # B = BLUE
        # R = ORANGE
        # L = RED

        self.faces = {

            # U = YELLOW
            "top": 7 if y == 1 else 0,

            # D = WHITE
            "bottom": 10 if y == -1 else 0,

            # F = GREEN
            "front": 11 if z == 1 else 0,

            # B = BLUE
            "back": 12 if z == -1 else 0,

            # R = ORANGE
            "right": 8 if x == 1 else 0,

            # L = RED
            "left": 9 if x == -1 else 0,
        }

    def apply_grid_rotation(self, axis, clockwise):

        x, y, z = self.x, self.y, self.z

        # X AXIS
        if axis == "x":

            if clockwise:
                self.y, self.z = -z, y
            else:
                self.y, self.z = z, -y

        # Y AXIS
        elif axis == "y":

            if clockwise:
                self.x, self.z = z, -x
            else:
                self.x, self.z = -z, x

        # Z AXIS
        elif axis == "z":

            if clockwise:
                self.x, self.y = -y, x
            else:
                self.x, self.y = y, -x

    def rotate_faces(self, axis, clockwise):

        f = self.faces.copy()

        # X AXIS
        if axis == "x":

            if clockwise:

                self.faces["top"] = f["back"]
                self.faces["bottom"] = f["front"]
                self.faces["front"] = f["top"]
                self.faces["back"] = f["bottom"]

            else:

                self.faces["top"] = f["front"]
                self.faces["bottom"] = f["back"]
                self.faces["front"] = f["bottom"]
                self.faces["back"] = f["top"]

        # Y AXIS
        elif axis == "y":

            if clockwise:

                self.faces["front"] = f["left"]
                self.faces["right"] = f["front"]
                self.faces["back"] = f["right"]
                self.faces["left"] = f["back"]

            else:

                self.faces["front"] = f["right"]
                self.faces["left"] = f["front"]
                self.faces["back"] = f["left"]
                self.faces["right"] = f["back"]

        # Z AXIS
        elif axis == "z":

            if clockwise:

                self.faces["top"] = f["right"]
                self.faces["left"] = f["top"]
                self.faces["bottom"] = f["left"]
                self.faces["right"] = f["bottom"]

            else:

                self.faces["top"] = f["left"]
                self.faces["right"] = f["top"]
                self.faces["bottom"] = f["right"]
                self.faces["left"] = f["bottom"]


class RubiksCube:

    def __init__(self):

        pyxel.init(
            256,
            256,
            title="Rubik's Cube",
            fps=60
        )

        self.camera_pitch = -0.35
        self.camera_yaw = 0.75
        self.camera_roll = 0

        self.target_pitch = self.camera_pitch
        self.target_yaw = self.camera_yaw
        self.target_roll = self.camera_roll
        
        self.animating = False
        self.anim_axis = ""
        self.anim_angle = 0
        self.anim_progress = 0
        self.anim_cubies = []

        self.scrambling = False

        self.message = ""
        self.message_timer = 0

        # TIMER
        self.timer_running = False
        self.start_time = 0
        self.elapsed_time = 0

        # SOLVE HISTORY
        self.solve_times = []
        self.best_time = None

        self.init_cube()

        pyxel.run(self.update, self.draw)
        
        
        
    def apply_camera(self, p):

        p = p.rotate_y(self.camera_yaw)
        p = p.rotate_x(self.camera_pitch)
        p = p.rotate_z(self.camera_roll)

        return p

    def init_cube(self):

        self.cubies = []

        for x in range(-1, 2):
            for y in range(-1, 2):
                for z in range(-1, 2):

                    if x == 0 and y == 0 and z == 0:
                        continue

                    self.cubies.append(
                        Cubie(x, y, z)
                    )

    def set_message(self, msg):

        self.message = msg
        self.message_timer = 90

    # TRUE SOLVE DETECTION
    # WORKS WITH SLICE + WIDE MOVES

    def is_solved(self):

        face_checks = {

            "top": [],
            "bottom": [],
            "front": [],
            "back": [],
            "right": [],
            "left": [],
        }

        for c in self.cubies:

            if c.y == 1:
                face_checks["top"].append(c.faces["top"])

            if c.y == -1:
                face_checks["bottom"].append(c.faces["bottom"])

            if c.z == 1:
                face_checks["front"].append(c.faces["front"])

            if c.z == -1:
                face_checks["back"].append(c.faces["back"])

            if c.x == 1:
                face_checks["right"].append(c.faces["right"])

            if c.x == -1:
                face_checks["left"].append(c.faces["left"])

        for colors in face_checks.values():

            first = colors[0]

            if first == 0:
                return False

            for c in colors:

                if c != first:
                    return False

        return True

    def rotate_face(self, face, prime=False):

        if self.animating:
            return

        # FIND CENTER POSITIONS

        centers = {}
        #the colours for the centres may look mismatched but that's the way it works, idk

        center_colors = {
            "U": 10,    # white
            "D": 7,   # yellow
            "F": 11,   # green
            "B": 12,   # blue
            "R": 9,    # red
            "L": 8     # orange
        }

        for move, color in center_colors.items():

            for c in self.cubies:

                if (
                    abs(c.x) + abs(c.y) + abs(c.z) == 1
                ):

                    if color in c.faces.values():

                        centers[move] = c
                        break

        # =========================
        # STANDARD FACE MOVES
        # =========================

        if face == "U":

            c = centers["U"]

            if c.x != 0:
                axis = "x"
                layer_value = c.x
            elif c.y != 0:
                axis = "y"
                layer_value = c.y
            else:
                axis = "z"
                layer_value = c.z

            clockwise = True

        elif face == "D":

            c = centers["D"]

            if c.x != 0:
                axis = "x"
                layer_value = c.x
            elif c.y != 0:
                axis = "y"
                layer_value = c.y
            else:
                axis = "z"
                layer_value = c.z

            clockwise = False

        elif face == "R":

            c = centers["R"]

            if c.x != 0:
                axis = "x"
                layer_value = c.x
            elif c.y != 0:
                axis = "y"
                layer_value = c.y
            else:
                axis = "z"
                layer_value = c.z

            clockwise = True

        elif face == "L":

            c = centers["L"]

            if c.x != 0:
                axis = "x"
                layer_value = c.x
            elif c.y != 0:
                axis = "y"
                layer_value = c.y
            else:
                axis = "z"
                layer_value = c.z

            clockwise = False

        elif face == "F":

            c = centers["F"]

            if c.x != 0:
                axis = "x"
                layer_value = c.x
            elif c.y != 0:
                axis = "y"
                layer_value = c.y
            else:
                axis = "z"
                layer_value = c.z

            clockwise = False

        elif face == "B":

            c = centers["B"]

            if c.x != 0:
                axis = "x"
                layer_value = c.x
            elif c.y != 0:
                axis = "y"
                layer_value = c.y
            else:
                axis = "z"
                layer_value = c.z

            clockwise = True

        # =========================
        # SLICE MOVES
        # =========================

        elif face == "M":

            axis = "x"
            layer = lambda c: c.x == 0
            clockwise = False

        elif face == "E":

            axis = "y"
            layer = lambda c: c.y == 0
            clockwise = False

        elif face == "S":

            axis = "z"
            layer = lambda c: c.z == 0
            clockwise = False

        # =========================
        # WIDE MOVES
        # =========================

        elif face == "Uw":

            c = centers["U"]

            if c.x != 0:
                axis = "x"
                layer = lambda cubie: (
                    cubie.x * c.x >= 0
                )
            elif c.y != 0:
                axis = "y"
                layer = lambda cubie: (
                    cubie.y * c.y >= 0
                )
            else:
                axis = "z"
                layer = lambda cubie: (
                    cubie.z * c.z >= 0
                )

            clockwise = True

        elif face == "Dw":

            c = centers["D"]

            if c.x != 0:
                axis = "x"
                layer = lambda cubie: (
                    cubie.x * c.x >= 0
                )
            elif c.y != 0:
                axis = "y"
                layer = lambda cubie: (
                    cubie.y * c.y >= 0
                )
            else:
                axis = "z"
                layer = lambda cubie: (
                    cubie.z * c.z >= 0
                )

            clockwise = False

        elif face == "Rw":

            c = centers["R"]

            if c.x != 0:
                axis = "x"
                layer = lambda cubie: (
                    cubie.x * c.x >= 0
                )
            elif c.y != 0:
                axis = "y"
                layer = lambda cubie: (
                    cubie.y * c.y >= 0
                )
            else:
                axis = "z"
                layer = lambda cubie: (
                    cubie.z * c.z >= 0
                )

            clockwise = True

        elif face == "Lw":

            c = centers["L"]

            if c.x != 0:
                axis = "x"
                layer = lambda cubie: (
                    cubie.x * c.x >= 0
                )
            elif c.y != 0:
                axis = "y"
                layer = lambda cubie: (
                    cubie.y * c.y >= 0
                )
            else:
                axis = "z"
                layer = lambda cubie: (
                    cubie.z * c.z >= 0
                )

            clockwise = False

        elif face == "Fw":

            c = centers["F"]

            if c.x != 0:
                axis = "x"
                layer = lambda cubie: (
                    cubie.x * c.x >= 0
                )
            elif c.y != 0:
                axis = "y"
                layer = lambda cubie: (
                    cubie.y * c.y >= 0
                )
            else:
                axis = "z"
                layer = lambda cubie: (
                    cubie.z * c.z >= 0
                )

            clockwise = False

        elif face == "Bw":

            c = centers["B"]

            if c.x != 0:
                axis = "x"
                layer = lambda cubie: (
                    cubie.x * c.x >= 0
                )
            elif c.y != 0:
                axis = "y"
                layer = lambda cubie: (
                    cubie.y * c.y >= 0
                )
            else:
                axis = "z"
                layer = lambda cubie: (
                    cubie.z * c.z >= 0
                )

            clockwise = True

        else:
            return

        # =========================
        # NORMAL FACE LAYERS
        # =========================

        if face in ["U", "D", "L", "R", "F", "B"]:

            if axis == "x":
                layer = lambda c: c.x == layer_value

            elif axis == "y":
                layer = lambda c: c.y == layer_value

            else:
                layer = lambda c: c.z == layer_value

        # PRIME

        if prime:
            clockwise = not clockwise

        angle = (
            math.pi / 2
            if clockwise
            else -math.pi / 2
        )

        self.anim_axis = axis
        self.anim_angle = angle
        self.anim_progress = 0

        self.anim_cubies = [
            c for c in self.cubies
            if layer(c)
        ]

        self.animating = True

        self.set_message(
            face + ("'" if prime else "")
        )

    def update_animation(self):

        if not self.animating:
            return

        self.anim_progress += 0.12

        if self.anim_progress >= 1:

            for c in self.anim_cubies:

                c.apply_grid_rotation(
                    self.anim_axis,
                    self.anim_angle > 0
                )

                c.rotate_faces(
                    self.anim_axis,
                    self.anim_angle > 0
                )

            self.animating = False

            # SOLVE DETECTION

            if (
                self.timer_running and
                self.is_solved()
            ):

                self.timer_running = False

                self.solve_times.insert(
                    0,
                    self.elapsed_time
                )

                self.solve_times = self.solve_times[:100]

                self.best_time = min(self.solve_times)

                self.set_message(
                    f"SOLVED! {self.format_time()}"
                )

    def scramble(self):
        
        
        last_face = None
        last_prime = None

        
        if self.animating:
            return

        self.scramble_moves = []

        all_moves = [
            "U", "D", "L", "R", "F", "B",
        ]

        for _ in range(25):

            while True:
                face = random.choice(all_moves)
                base_face = face[0]

                prime = random.choice([True, False])

                # allow same face repeat freely
                # BUT block immediate reversal like U then U'
                if base_face == last_face and prime != last_prime:
                    continue

                break

            self.scramble_moves.append((face, prime))

            last_face = base_face
            last_prime = prime

        self.scramble_index = 0
        self.scrambling = True

    def update_scramble(self):

        if not self.scrambling:
            return

        if (
            not self.animating and
            self.scramble_index < len(self.scramble_moves)
        ):

            face, prime = (
                self.scramble_moves[
                    self.scramble_index
                ]
            )

            self.rotate_face(
                face,
                prime
            )

            self.scramble_index += 1

        elif not self.animating:

            self.scrambling = False
            self.set_message("SCRAMBLED!")

    def format_time(self):

        minutes = int(self.elapsed_time // 60)
        seconds = self.elapsed_time % 60

        return f"{minutes}:{seconds:05.2f}"

    def update(self):

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

        self.update_animation()
        self.update_scramble()

        # TIMER

        if self.timer_running:

            self.elapsed_time = (
                pyxel.frame_count - self.start_time
            ) / 60

        speed = 0.15

        self.camera_pitch += (
            self.target_pitch
            - self.camera_pitch
        ) * speed

        self.camera_yaw += (
            self.target_yaw
            - self.camera_yaw
        ) * speed

        self.camera_roll += (
            self.target_roll
            - self.camera_roll
        ) * speed
        
        
        # SCRAMBLE (SPACE BAR)
        if pyxel.btnp(pyxel.KEY_SPACE):
            self.scramble()
        
        
        if self.animating:
            return

        # TIMER START

        if pyxel.btnp(pyxel.KEY_T):

            if not self.is_solved():

                self.timer_running = True
                self.start_time = pyxel.frame_count
                self.elapsed_time = 0

                self.set_message("TIMER STARTED!")

        # MODIFIERS

        ctrl = pyxel.btn(pyxel.KEY_CTRL)
        shift = pyxel.btn(pyxel.KEY_SHIFT)

        # U

        if pyxel.btnp(pyxel.KEY_U):

            self.rotate_face(
                "Uw" if ctrl else "U",
                shift
            )

        # D

        elif pyxel.btnp(pyxel.KEY_D):

            self.rotate_face(
                "Dw" if ctrl else "D",
                shift
            )

        # L

        elif pyxel.btnp(pyxel.KEY_L):

            self.rotate_face(
                "Lw" if ctrl else "L",
                shift
            )

        # R

        elif pyxel.btnp(pyxel.KEY_R):

            self.rotate_face(
                "Rw" if ctrl else "R",
                shift
            )

        # F

        elif pyxel.btnp(pyxel.KEY_F):

            self.rotate_face(
                "Fw" if ctrl else "F",
                shift
            )

        # B

        elif pyxel.btnp(pyxel.KEY_B):

            self.rotate_face(
                "Bw" if ctrl else "B",
                shift
            )

        # SLICE MOVES

        elif pyxel.btnp(pyxel.KEY_M):

            self.rotate_face(
                "M",
                shift
            )

        elif pyxel.btnp(pyxel.KEY_E):

            self.rotate_face(
                "E",
                shift
            )

        elif pyxel.btnp(pyxel.KEY_S):

            self.rotate_face(
                "S",
                shift
            )

        # CAMERA ROTATION
        # SCREEN RELATIVE

        rot_speed = 0.05

        if pyxel.btn(pyxel.KEY_LEFT):
            self.target_yaw -= rot_speed

        if pyxel.btn(pyxel.KEY_RIGHT):
            self.target_yaw += rot_speed

        if pyxel.btn(pyxel.KEY_UP):
            self.target_pitch -= rot_speed

        if pyxel.btn(pyxel.KEY_DOWN):
            self.target_pitch += rot_speed

        if pyxel.btn(pyxel.KEY_Q):
            self.target_roll += rot_speed

        if pyxel.btn(pyxel.KEY_W):
            self.target_roll -= rot_speed

        # RESET

        if pyxel.btnp(pyxel.KEY_RETURN):

            self.init_cube()

            self.timer_running = False
            self.elapsed_time = 0

            self.rotation_x = -0.35
            self.rotation_y = 0.75
            self.rotation_z = 0

            self.target_rotation_x = self.rotation_x
            self.target_rotation_y = self.rotation_y
            self.target_rotation_z = self.rotation_z

            self.set_message("RESET!")

    def project(self, p):

        p = self.apply_camera(p)

        scale = 5.5
        distance = 26

        z = p.z + distance

        factor = 170 / z

        x = p.x * scale * factor + 155
        y = -p.y * scale * factor + 128

        return x, y, z

    def draw_face(self, verts, color):

        for i in range(1, len(verts) - 1):

            pyxel.tri(

                verts[0][0],
                verts[0][1],

                verts[i][0],
                verts[i][1],

                verts[i + 1][0],
                verts[i + 1][1],

                color
            )

        for i in range(len(verts)):

            n = (i + 1) % len(verts)

            pyxel.line(

                verts[i][0],
                verts[i][1],

                verts[n][0],
                verts[n][1],

                0
            )

    def draw_cubie(self, cubie):

        size = 0.45

        verts = [

            Vector3(cubie.x - size, cubie.y - size, cubie.z - size),
            Vector3(cubie.x + size, cubie.y - size, cubie.z - size),
            Vector3(cubie.x + size, cubie.y + size, cubie.z - size),
            Vector3(cubie.x - size, cubie.y + size, cubie.z - size),

            Vector3(cubie.x - size, cubie.y - size, cubie.z + size),
            Vector3(cubie.x + size, cubie.y - size, cubie.z + size),
            Vector3(cubie.x + size, cubie.y + size, cubie.z + size),
            Vector3(cubie.x - size, cubie.y + size, cubie.z + size),
        ]

        if self.animating and cubie in self.anim_cubies:

            angle = self.anim_angle * self.anim_progress
            axis = self.anim_axis

            new_verts = []

            for v in verts:

                x, y, z = v.x, v.y, v.z

                if axis == "x":

                    c = math.cos(angle)
                    s = math.sin(angle)

                    y, z = (
                        y * c - z * s,
                        y * s + z * c
                    )

                elif axis == "y":

                    c = math.cos(angle)
                    s = math.sin(angle)

                    x, z = (
                        x * c + z * s,
                        -x * s + z * c
                    )

                elif axis == "z":

                    c = math.cos(angle)
                    s = math.sin(angle)

                    x, y = (
                        x * c - y * s,
                        x * s + y * c
                    )

                new_verts.append(Vector3(x, y, z))

            verts = new_verts

        verts2d = [
            self.project(v)
            for v in verts
        ]

        faces = [

            ([4,5,6,7], cubie.faces["front"], Vector3(0,0,1)),
            ([1,0,3,2], cubie.faces["back"], Vector3(0,0,-1)),

            ([0,4,7,3], cubie.faces["left"], Vector3(-1,0,0)),
            ([5,1,2,6], cubie.faces["right"], Vector3(1,0,0)),

            ([3,7,6,2], cubie.faces["top"], Vector3(0,1,0)),
            ([4,0,1,5], cubie.faces["bottom"], Vector3(0,-1,0)),
        ]

        for inds, color, normal in faces:

            if (
                self.animating and
                cubie in self.anim_cubies
            ):

                angle = (
                    self.anim_angle
                    * self.anim_progress
                )

                if self.anim_axis == "x":
                    normal = normal.rotate_x(angle)

                elif self.anim_axis == "y":
                    normal = normal.rotate_y(angle)

                elif self.anim_axis == "z":
                    normal = normal.rotate_z(angle)

            normal = normal.rotate_y(
                self.rotation_y
            )

            normal = normal.rotate_x(
                self.rotation_x
            )

            normal = normal.rotate_z(
                self.rotation_z
            )

            if normal.z > 0 and color != 0:

                poly = [

                    (
                        verts2d[i][0],
                        verts2d[i][1]
                    )

                    for i in inds
                ]

                self.draw_face(
                    poly,
                    color
                )

  
    def draw(self):

        pyxel.cls(1)

        all_faces = []

        for cubie in self.cubies:

            size = 0.45

            verts = [

                Vector3(cubie.x - size, cubie.y - size, cubie.z - size),
                Vector3(cubie.x + size, cubie.y - size, cubie.z - size),
                Vector3(cubie.x + size, cubie.y + size, cubie.z - size),
                Vector3(cubie.x - size, cubie.y + size, cubie.z - size),

                Vector3(cubie.x - size, cubie.y - size, cubie.z + size),
                Vector3(cubie.x + size, cubie.y - size, cubie.z + size),
                Vector3(cubie.x + size, cubie.y + size, cubie.z + size),
                Vector3(cubie.x - size, cubie.y + size, cubie.z + size),
            ]

            # ANIMATE TURNING LAYERS

            if self.animating and cubie in self.anim_cubies:

                angle = self.anim_angle * self.anim_progress

                new_verts = []

                for v in verts:

                    if self.anim_axis == "x":
                        v = v.rotate_x(angle)

                    elif self.anim_axis == "y":
                        v = v.rotate_y(angle)

                    elif self.anim_axis == "z":
                        v = v.rotate_z(angle)

                    new_verts.append(v)

                verts = new_verts

            # CAMERA ROTATION + PROJECTION

            projected = []

            for v in verts:

                # camera rotation applied ONCE consistently (inside project)
                proj = self.project(v)

                # still compute rotated version for depth sorting
                rotated = self.apply_camera(v)

                projected.append((rotated, proj))
                
                
            faces = [

                ([4,5,6,7], cubie.faces["front"], Vector3(0,0,1)),
                ([1,0,3,2], cubie.faces["back"], Vector3(0,0,-1)),

                ([0,4,7,3], cubie.faces["left"], Vector3(-1,0,0)),
                ([5,1,2,6], cubie.faces["right"], Vector3(1,0,0)),

                ([3,7,6,2], cubie.faces["top"], Vector3(0,1,0)),
                ([4,0,1,5], cubie.faces["bottom"], Vector3(0,-1,0)),
            ]

            for inds, color, normal in faces:

                if color == 0:
                    continue

                # ROTATE NORMALS DURING ANIMATION

                if self.animating and cubie in self.anim_cubies:

                    angle = self.anim_angle * self.anim_progress

                    if self.anim_axis == "x":
                        normal = normal.rotate_x(angle)

                    elif self.anim_axis == "y":
                        normal = normal.rotate_y(angle)

                    elif self.anim_axis == "z":
                        normal = normal.rotate_z(angle)

                # CAMERA ROTATION

                normal = self.apply_camera(normal)
                # BACKFACE CULLING

                if normal.z <= 0:
                    continue

                poly = []
                avg_z = 0

                for i in inds:

                    rotated, proj = projected[i]

                    poly.append(
                        (proj[0], proj[1])
                    )

                    avg_z += rotated.z

                avg_z /= 4

                all_faces.append(
                    (avg_z, poly, color)
                )

        # DRAW BACK TO FRONT

        all_faces.sort(key=lambda f: f[0])

        for _, poly, color in all_faces:

            self.draw_face(poly, color)
    
    # =========================
    # TITLE
    # =========================

        title = "RUBIK'S CUBE"

        colors = [9, 8, 7, 11, 12, 10]

        start_x = 155 - (len(title) * 2)

        for i, ch in enumerate(title):

            pyxel.text(
                start_x + i * 4,
                5,
                ch,
                colors[i % len(colors)]
            )

    # =========================
    # CONTROLS
    # =========================

        pyxel.text(4, 3, "U D L R F B = MOVES", 6)
        pyxel.text(4, 13, "SHIFT = PRIME", 6)
        pyxel.text(4, 23, "CTRL = WIDE MOVES", 6)
        pyxel.text(4, 33, "M E S = SLICE MOVES", 6)
        pyxel.text(4, 43, "ARROWS = ROTATE VIEW", 6)
        pyxel.text(4, 53, "Q/W = ROLL CAMERA", 6)
        pyxel.text(4, 63, "SPACE = SCRAMBLE", 6)
        pyxel.text(4, 73, "T = START TIMER", 6)
        pyxel.text(4, 83, "ENTER = RESET", 6)

    # =========================
    # SOLVE TIMES
    # =========================

        pyxel.text(
            5,
            120,
            "LAST TIMES:",
            7
        )

        for i, t in enumerate(self.solve_times):

            minutes = int(t // 60)
            seconds = t % 60

            time_text = f"{minutes}:{seconds:05.2f}"

            color = 11 if t == self.best_time else 7

            pyxel.text(
                5,
                130 + i * 8,
                time_text,
                color
            )

    # =========================
    # TIMER
    # =========================

        timer_text = self.format_time()

        timer_x = 160 - len(timer_text) * 4
        timer_y = 225

        pyxel.text(
            timer_x,
            timer_y,
            timer_text,
            7
        )

    # =========================
    # MESSAGE
    # =========================

        if self.message_timer > 0:

            pyxel.text(

                128 - len(self.message) * 2,

                242,

                self.message,

                10
            )


RubiksCube()