Files
eolrb/cube.py
2024-08-31 10:18:44 +03:00

217 lines
6.1 KiB
Python

from dataclasses import dataclass
from typing import Dict, List, Tuple
import hashlib
M_SLICE_EDGES = ["UB", "UF", "DF", "DB"]
U_EDGES = ["UB", "UR", "UF", "UL"]
CENTERS = ["U", "F", "D", "B"]
U_CORNERS = ["UBR", "UFR", "UFL", "UBL"]
def cyclic_shift(d: Dict, keys: List, offset: int):
if not all(key in d for key in keys):
raise ValueError("some keys are missing from the dictionary")
values = [d[key] for key in keys]
effective_offset = offset % len(keys)
shifted_values = values[effective_offset:] + values[:effective_offset]
for key, value in zip(keys, shifted_values):
d[key] = value
@dataclass(unsafe_hash=True)
class Edge:
name: str
oriented: bool
class LSECube:
def __init__(self):
self.reset()
def reset(self):
self.edges = {
"UB": Edge("UB", True),
"UR": Edge("UR", True),
"UF": Edge("UF", True),
"UL": Edge("UL", True),
"DB": Edge("DB", True),
"DF": Edge("DF", True),
}
self.centers = {
"U": "U",
"B": "B",
"F": "F",
"D": "D",
}
self.corners = {
"UBR": "UBR",
"UFR": "UFR",
"UFL": "UFL",
"UBL": "UBL",
}
def eolrb_hash(self):
# TODO: I fucking hate this language. why can't hashing be simpelr
hash_dict = lambda d: hashlib.md5(str(sorted(d.items())).encode()).hexdigest()
eolrb_state = {}
# mask edges to only care about LR edges and orientation of everything else
for location, edge in self.edges.items():
if edge.name in ["UR", "UL"]:
eolrb_state[location] = edge
else:
eolrb_state[location] = Edge("", edge.oriented)
eolrb_state["center_oriented"] = self.centers["U"] in ["U", "D"]
# the value of UFR is supposed to be the key of the value "UFR" in self.corners but whatever
eolrb_state["UFR"] = self.corners["UFR"]
return int(
hashlib.md5(hash_dict(eolrb_state).encode()).hexdigest(),
16,
)
def __repr__(self):
return f"{self.edges=}, {self.centers=}, {self.corners=}"
def M(self):
cyclic_shift(self.edges, M_SLICE_EDGES, 3)
for edge in M_SLICE_EDGES:
self.edges[edge].oriented = not self.edges[edge].oriented
cyclic_shift(self.centers, CENTERS, 3)
def M2(self):
cyclic_shift(self.edges, M_SLICE_EDGES, 2)
cyclic_shift(self.centers, CENTERS, 2)
def Mp(self):
cyclic_shift(self.edges, M_SLICE_EDGES, 1)
for edge in M_SLICE_EDGES:
self.edges[edge].oriented = not self.edges[edge].oriented
cyclic_shift(self.centers, CENTERS, 1)
def U(self):
cyclic_shift(self.edges, U_EDGES, 3)
cyclic_shift(self.corners, U_CORNERS, 3)
def U2(self):
cyclic_shift(self.edges, U_EDGES, 2)
cyclic_shift(self.corners, U_CORNERS, 2)
def Up(self):
cyclic_shift(self.edges, U_EDGES, 1)
cyclic_shift(self.corners, U_CORNERS, 1)
def cycle_corners(self, n: int):
cyclic_shift(self.corners, U_CORNERS, 4 - n)
def fix_auf(self) -> str:
match self.corners["UFR"]:
case "UFR":
return ""
case "UBR":
self.Up()
return "U'"
case "UBL":
self.U2()
return "U2"
case "UFL":
self.U()
return "U"
return ""
def fix_m_slice(self) -> str:
match self.centers["U"]:
case "U":
return ""
case "B":
self.Mp()
return "M'"
case "D":
self.M2()
return "M2"
case "F":
self.M()
return "M"
return ""
def alg(self, alg: str):
move_functions = {
"U": self.U,
"U2": self.U2,
"U2'": self.U2,
"U'": self.Up,
"M": self.M,
"M2": self.M2,
"M2'": self.M2,
"M'": self.Mp,
}
for move in alg.split():
move_functions[move]()
def reverse_algorithm(moves: List[str]) -> List[str]:
def invert_move(move):
if move.endswith("'"):
return move[:-1]
elif move.endswith("2"):
return move
else:
return move + "'"
reversed_moves = [invert_move(move) for move in reversed(moves)]
return reversed_moves
def condense_algorithm(moves: List[str]) -> List[str]:
def apply_reduction(moves: List[str]) -> List[str]:
result = []
for move in moves:
base_move = move[0]
multiplier = 1
if move.endswith("2"):
multiplier = 2
if move.endswith("'"):
multiplier = 3
result.extend([base_move] * multiplier)
return result
def reduce_moves(moves: List[str]) -> List[Tuple[str, int]]:
if not moves:
return []
condensed = []
current_element = moves[0]
count = 1
for element in moves[1:]:
if element == current_element:
count += 1
else:
condensed.append((current_element, count))
current_element = element
count = 1
# Append the last element group
condensed.append((current_element, count))
return condensed
def build_reduced_alg(moves: List[Tuple[str, int]]) -> List[str]:
result = []
for move in moves:
move_type, count = move
count = count % 4
match count:
case 1:
result.append(move_type)
case 2:
result.append(move_type + "2")
case 3:
result.append(move_type + "'")
return result
reduced_moves = apply_reduction(moves)
counted_moves = reduce_moves(reduced_moves)
reduced_alg = build_reduced_alg(counted_moves)
return reduced_alg