started transitioning on the rewrite of skewb operations

This commit is contained in:
DomNomNomVR 2025-04-15 08:01:03 +12:00
parent 028fb0bc3a
commit 9a92a1e922

View File

@ -13,39 +13,35 @@ import pytest
print()
# twisting the bottom, opposing this color clockwise when this color is facing away
Axis = Literal["R", "B", "O", "G"]
axes: tuple[Axis, ...] = Axis.__args__
# how many clockwise twists along an axis has been twisted after being flush with the top or bottom.
CornerRotation = Literal[0, 1, 2]
# RBOG
Axis = Literal["G", "O", "B", "R"]
AXES: tuple[Axis, ...] = Axis.__args__
# How many clockwise twists along an axis has been twisted after being flush with the top or bottom.
CornerRotState = Literal[0, 1, 2]
LowerAntiAxis = Literal["R", "B", "O", "G", "r", "b", "o", "g"]
lower_anti_axes: tuple[LowerAntiAxis, ...] = LowerAntiAxis.__args__
to_anticlockwise: dict[Axis, LowerAntiAxis] = {
"R": "r",
"B": "b",
"O": "o",
"G": "g",
}
to_clockwise: dict[LowerAntiAxis, Axis] = {v: k for k, v in to_anticlockwise.items()}
to_opposite: dict[LowerAntiAxis, LowerAntiAxis] = {**to_anticlockwise, **to_clockwise}
Twist = Literal["G", "O", "B", "R", "g", "o", "b", "r"]
TWISTS: tuple[Twist, ...] = Twist.__args__
to_anticlockwise: dict[Axis, Twist] = {"G": "g", "O": "o", "B": "b", "R": "r"}
to_clockwise: dict[Twist, Axis] = {v: k for k, v in to_anticlockwise.items()}
to_opposite: dict[Twist, Twist] = {**to_anticlockwise, **to_clockwise}
@dataclass(frozen=True)
class Corner:
col: Axis
rot: CornerRotation
rot: CornerRotState
def __repr__(self) -> str:
return f"{self.col}{self.rot}"
MidRotatation = Literal["Y", "RG", "RB"]
MidRotState = Literal["Y", "O", "R"]
@dataclass(frozen=True)
class Middle:
col: Literal["R", "B", "O", "G", "Y"]
rot: MidRotatation
col: Literal["G", "O", "B", "R", "Y"]
rot: MidRotState
def __repr__(self) -> str:
return f"{self.col}{self.rot}"
@ -56,10 +52,8 @@ class Skewb:
"""Represents a Rubics cube variant sold as 'Qiyi Twisty Skewb'."""
# order: vertically below Axis primary color
# "R", "B", "O", "G"
top: Tuple[Corner, Corner, Corner, Corner]
bot: Tuple[Corner, Corner, Corner, Corner]
# order: RB BO OG GR Y
mids: Tuple[Middle, Middle, Middle, Middle, Middle]
# def __post_init__(self):
@ -68,89 +62,74 @@ class Skewb:
def assert_valid(self):
assert Counter(
corner.col for corners in [self.top, self.bot] for corner in corners
) == {col: 2 for col in axes}
) == {col: 2 for col in AXES}
assert Counter(mid.col for mid in self.mids) == Counter("RBOGY")
# forbidden rotations
assert self.mids[0].rot != "RB"
assert self.mids[1].rot != "RG"
assert self.mids[2].rot != "RB"
assert self.mids[3].rot != "RG"
assert self.mids[0].rot != "R"
assert self.mids[1].rot != "O"
assert self.mids[2].rot != "R"
assert self.mids[3].rot != "O"
assert self.mids[4].col == "Y" or self.mids[4].rot != "Y"
R0 = Corner("R", 0)
R1 = Corner("R", 1)
R2 = Corner("R", 2)
B0 = Corner("B", 0)
B1 = Corner("B", 1)
B2 = Corner("B", 2)
O0 = Corner("O", 0)
O1 = Corner("O", 1)
O2 = Corner("O", 2)
G0 = Corner("G", 0)
G1 = Corner("G", 1)
G2 = Corner("G", 2)
O0 = Corner("O", 0)
O1 = Corner("O", 1)
O2 = Corner("O", 2)
B0 = Corner("B", 0)
B1 = Corner("B", 1)
B2 = Corner("B", 2)
R0 = Corner("R", 0)
R1 = Corner("R", 1)
R2 = Corner("R", 2)
RY = Middle("R", "Y")
RRG = Middle("R", "RG")
RRB = Middle("R", "RB")
BY = Middle("B", "Y")
BRG = Middle("B", "RG")
BRB = Middle("B", "RB")
OY = Middle("O", "Y")
ORG = Middle("O", "RG")
ORB = Middle("O", "RB")
GR = Middle("G", "R")
OR = Middle("O", "R")
BR = Middle("B", "R")
RR = Middle("R", "R")
GO = Middle("G", "O")
OO = Middle("O", "O")
BO = Middle("B", "O")
RO = Middle("R", "O")
GY = Middle("G", "Y")
GRG = Middle("G", "RG")
GRB = Middle("G", "RB")
OY = Middle("O", "Y")
BY = Middle("B", "Y")
RY = Middle("R", "Y")
YY = Middle("Y", "Y")
YRG = Middle("Y", "RG")
YRB = Middle("Y", "RB")
def rotate_side_mid_rot_about_W(m: Middle) -> Middle:
def rotate_mid_about_W(m: Middle) -> Middle:
return Middle(
col=m.col,
rot="Y" if m.rot == "Y" else ("RB" if m.rot == "RG" else "RG"),
col=m.col, rot="Y" if m.rot == "Y" else ("R" if m.rot == "O" else "O")
)
def rotate_bot_mid_rot_about_W(m: Middle) -> Middle:
if m.col == "Y":
return Middle("Y", "Y")
return Middle(col=m.col, rot="RB" if m.rot == "RG" else "RG")
def rotate_everything_about_W(s: Skewb) -> Skewb:
"""Clockwise rotation when looking down upon white."""
return Skewb(
top=(s.top[-1],) + s.top[:-1],
bot=(s.bot[-1],) + s.bot[:-1],
mids=(
rotate_side_mid_rot_about_W(s.mids[3]),
rotate_side_mid_rot_about_W(s.mids[0]),
rotate_side_mid_rot_about_W(s.mids[1]),
rotate_side_mid_rot_about_W(s.mids[2]),
rotate_side_mid_rot_about_W(s.mids[4]),
rotate_mid_about_W(s.mids[3]),
rotate_mid_about_W(s.mids[0]),
rotate_mid_about_W(s.mids[1]),
rotate_mid_about_W(s.mids[2]),
rotate_mid_about_W(s.mids[4]),
),
)
desk_start = Skewb(
top=(R0, B0, O0, G2),
bot=(B0, O0, G1, R0),
mids=(BY, OY, RRG, GRB, YY),
)
solved_skewb = Skewb(
top=(O0, B0, R0, G0),
bot=(B0, R0, G0, O0),
mids=(BY, RY, GY, OY, YY),
top=(G0, O0, B0, R0),
bot=(G0, O0, B0, R0),
mids=(GY, OY, BY, RY, YY),
)
@pytest.mark.parametrize("s", [desk_start, solved_skewb])
@pytest.mark.parametrize("s", [solved_skewb])
def test_rotate_everything_about_W(s: Skewb):
ss = [s]
for i in range(4):
@ -160,10 +139,10 @@ def test_rotate_everything_about_W(s: Skewb):
def test_axes():
assert axes[0] == "R"
assert AXES[0] == "G"
type CornerRotPermutation = dict[CornerRotation, CornerRotation]
type CornerRotPermutation = dict[CornerRotState, CornerRotState]
BOT_LEFT_TO_TOP: CornerRotPermutation = {0: 2, 1: 0, 2: 1}
TOP_TO_BOT_RIGHT: CornerRotPermutation = {0: 2, 1: 0, 2: 1}
ROTATE_CORNER_CLOCKWISE: CornerRotPermutation = {0: 1, 1: 2, 2: 0}
@ -179,15 +158,11 @@ def test_rotation_permutations(p: CornerRotPermutation):
assert set(p.values()) == {0, 1, 2}
MID_DIR_INCREMENT: dict[MidRotatation, MidRotatation] = {
"RG": "Y",
"Y": "RB",
"RB": "RG",
}
MID_DIR_INCREMENT: dict[MidRotState, MidRotState] = {"Y": "O", "R": "Y", "O": "R"}
def clockwise_twist(s: Skewb, twist: Axis) -> Skewb:
rot_before, rot_after = {"R": (0, 0), "B": (3, 1), "O": (2, 2), "G": (1, 3)}[twist]
rot_before, rot_after = {"G": (0, 0), "O": (3, 1), "B": (2, 2), "R": (1, 3)}[twist]
for _ in range(rot_before):
s = rotate_everything_about_W(s)
s = Skewb(
@ -207,20 +182,16 @@ def clockwise_twist(s: Skewb, twist: Axis) -> Skewb:
s.mids[0],
Middle(
(m := s.mids[2]).col,
"Y"
if m.col == "Y"
else MID_DIR_INCREMENT[m.rot], # ("Y" if m.rot == "RG" else "RB"),
"Y" if m.col == "Y" else MID_DIR_INCREMENT[m.rot],
),
Middle(
(m := s.mids[4]).col,
"Y"
if m.col == "Y"
else MID_DIR_INCREMENT[m.rot], # ("RG" if m.rot == "RB" else "Y"),
"Y" if m.col == "Y" else MID_DIR_INCREMENT[m.rot],
),
s.mids[3],
Middle(
(m := s.mids[1]).col,
"Y" if m.col == "Y" else ("RB" if m.rot == "Y" else "RG"),
"Y" if m.col == "Y" else ("O" if m.rot == "Y" else "R"),
),
),
)
@ -233,8 +204,8 @@ def anticlockwise_twist(s: Skewb, twist: Axis) -> Skewb:
return clockwise_twist(clockwise_twist(s, twist), twist)
@pytest.mark.parametrize("start", [desk_start, solved_skewb])
@pytest.mark.parametrize("axis", axes)
@pytest.mark.parametrize("start", [solved_skewb])
@pytest.mark.parametrize("axis", AXES)
def test_clockwise_twist(start: Skewb, axis: Axis):
assert anticlockwise_twist(clockwise_twist(start, axis), axis) == start
assert clockwise_twist(anticlockwise_twist(start, axis), axis) == start
@ -252,113 +223,17 @@ def test_clockwise_twist(start: Skewb, axis: Axis):
assert clockwise_twist(anticlockwise_twist(s, axis), axis) == s
def test_clockwise_twist_simple():
assert clockwise_twist(desk_start, "R") == Skewb(
top=(
Corner("R", 0),
Corner("B", 0),
Corner("R", 2),
Corner("G", 2),
),
bot=(
Corner("B", 0),
Corner("O", 2),
Corner("G", 2),
Corner("O", 2),
),
mids=(
Middle("B", "Y"),
Middle("R", "Y"),
Middle("Y", "Y"),
Middle("G", "RB"),
Middle("O", "RB"),
),
)
def test_clockwise_twist_simple2():
assert clockwise_twist(clockwise_twist(desk_start, "R"), "R") == Skewb(
top=(
Corner("R", 0),
Corner("B", 0),
Corner("O", 1),
Corner("G", 2),
),
bot=(
Corner("B", 0),
Corner("R", 1),
Corner("G", 0),
Corner("O", 1),
),
mids=(
Middle("B", "Y"),
Middle("Y", "Y"),
Middle("O", "RG"),
Middle("G", "RB"),
Middle("R", "RB"),
),
)
def test_clockwise_twist_simple3():
start = Skewb(
top=(
Corner(col="R", rot=0),
Corner(col="B", rot=0),
Corner(col="O", rot=0),
Corner(col="G", rot=0),
),
bot=(
Corner(col="G", rot=0),
Corner(col="R", rot=1),
Corner(col="B", rot=2),
Corner(col="O", rot=1),
),
mids=(
Middle(col="Y", rot="Y"),
Middle(col="O", rot="Y"),
Middle(col="R", rot="RG"),
Middle(col="B", rot="RB"),
Middle(col="G", rot="RG"),
),
)
start.assert_valid()
end = Skewb(
top=(
Corner(col="R", rot=0),
Corner(col="B", rot=1),
Corner(col="O", rot=0),
Corner(col="G", rot=0),
),
bot=(
Corner(col="B", rot=2),
Corner(col="R", rot=2),
Corner(col="G", rot=2),
Corner(col="O", rot=1),
),
mids=(
Middle(col="O", rot="RG"),
Middle(col="G", rot="RB"),
Middle(col="R", rot="RG"),
Middle(col="B", rot="RB"),
Middle(col="Y", rot="Y"),
),
)
end.assert_valid()
assert clockwise_twist(start, "G") == end
def apply_twist(start: Skewb, twist: LowerAntiAxis) -> Skewb:
if twist in axes:
def apply_twist(start: Skewb, twist: Twist) -> Skewb:
if twist in AXES:
return clockwise_twist(start, twist)
return anticlockwise_twist(start, to_clockwise[twist])
def apply_opposite(s: Skewb, twist: LowerAntiAxis) -> Skewb:
def apply_opposite(s: Skewb, twist: Twist) -> Skewb:
return apply_twist(s, to_opposite[twist])
def apply_twists(start: Skewb, twists: list[LowerAntiAxis]) -> Skewb:
def apply_twists(start: Skewb, twists: list[Twist]) -> Skewb:
return reduce(apply_twist, twists, start)
@ -367,7 +242,7 @@ def instructions(twists: list[Axis]) -> str:
axis = "R"
for twist in twists:
while axis != twist:
axis = axes[(1 + axes.index(axis)) % 4]
axis = AXES[(1 + AXES.index(axis)) % 4]
out += "L"
out += "."
@ -378,15 +253,15 @@ def instructions(twists: list[Axis]) -> str:
def breadth_first_search(
start: Skewb, is_end: Callable[[Skewb], bool], max_steps: int = 2000000
) -> list[LowerAntiAxis] | None:
) -> list[Twist] | None:
start.assert_valid()
if is_end(start):
return []
q = deque([start])
# what action got us to this point
skewb_to_twist: dict[Skewb, LowerAntiAxis | None] = {skewb: None for skewb in q}
skewb_to_twist: dict[Skewb, Twist | None] = {skewb: None for skewb in q}
def get_path(end: Skewb) -> list[LowerAntiAxis]:
def get_path(end: Skewb) -> list[Twist]:
out = []
s = end
while twist := skewb_to_twist[s]:
@ -401,7 +276,7 @@ def breadth_first_search(
print(".", end="", flush=True)
parent = q.popleft()
for twist in lower_anti_axes:
for twist in TWISTS:
child = apply_twist(parent, twist)
if child in skewb_to_twist:
continue
@ -415,7 +290,7 @@ def breadth_first_search(
def test_breadth_first_search():
for twist in lower_anti_axes:
for twist in TWISTS:
assert breadth_first_search(
apply_opposite(solved_skewb, twist), is_end=lambda s: s == solved_skewb
) == [twist]
@ -431,15 +306,15 @@ def print_path(path: list[Axis]):
def bidirectional_search(
start: Skewb, max_steps: int, end: Skewb = solved_skewb
) -> list[LowerAntiAxis] | None:
) -> list[Twist] | None:
start.assert_valid()
q = deque([start])
q2 = deque([end])
# what action got us to this point
skewb_to_twist: dict[Skewb, LowerAntiAxis | None] = {skewb: None for skewb in q}
skewb_to_twist2: dict[Skewb, LowerAntiAxis | None] = {skewb: None for skewb in q2}
skewb_to_twist: dict[Skewb, Twist | None] = {skewb: None for skewb in q}
skewb_to_twist2: dict[Skewb, Twist | None] = {skewb: None for skewb in q2}
def get_path(meet: Skewb) -> list[LowerAntiAxis]:
def get_path(meet: Skewb) -> list[Twist]:
path = []
s = meet
while twist := skewb_to_twist[s]:
@ -457,7 +332,7 @@ def bidirectional_search(
axis = "R"
for twist in twists:
while axis != twist:
axis = axes[(1 + axes.index(axis)) % 4]
axis = AXES[(1 + AXES.index(axis)) % 4]
out += "L"
out += "."
@ -465,7 +340,7 @@ def bidirectional_search(
out = out.replace("LLL", "J")
return out
def on_meet(meet: Skewb) -> list[LowerAntiAxis]:
def on_meet(meet: Skewb) -> list[Twist]:
path = get_path(meet)
assert apply_twists(start, path) == end
return path
@ -478,7 +353,7 @@ def bidirectional_search(
print(".", end="", flush=True)
parent2 = q2.popleft()
for twist in lower_anti_axes:
for twist in TWISTS:
child = apply_opposite(parent2, twist)
if child in skewb_to_twist2:
continue
@ -488,7 +363,7 @@ def bidirectional_search(
q2.append(child)
parent = q.popleft()
for twist in lower_anti_axes:
for twist in TWISTS:
child = apply_twist(parent, twist)
if child in skewb_to_twist:
@ -551,30 +426,30 @@ def element_multiply(a: list[int], b: list[int]) -> list[int]:
def test_heuristic():
assert heuristic(desk_start) < heuristic(solved_skewb)
assert heuristic(apply_twist(solved_skewb, "O")) < heuristic(solved_skewb)
def random_skewb(seed: int = 4, twists: int = 20) -> Skewb:
return apply_twists(solved_skewb, random_skewb_twists(seed, twists))
def random_skewb_twists(seed: int = 4, twists: int = 20) -> list[LowerAntiAxis]:
out: list[LowerAntiAxis] = []
def random_skewb_twists(seed: int = 4, twists: int = 20) -> list[Twist]:
out: list[Twist] = []
rng = random.Random(seed)
ax_i = rng.randint(0, 3)
for _ in range(twists):
twist = axes[ax_i]
twist = AXES[ax_i]
if rng.getrandbits(1):
twist = to_opposite[twist]
out.append(twist)
ax_i = (ax_i + rng.randint(1, 3)) % len(axes)
ax_i = (ax_i + rng.randint(1, 3)) % len(AXES)
return out
def double_clockwise_to_anticlockwise(twists: list[Axis]) -> list[LowerAntiAxis]:
def double_clockwise_to_anticlockwise(twists: list[Axis]) -> list[Twist]:
i = 0
n = len(twists)
out: list[LowerAntiAxis] = []
out: list[Twist] = []
while i < n:
twist = twists[i]
if i < n - 1 and twist == twists[i + 1]:
@ -596,9 +471,7 @@ def double_clockwise_to_anticlockwise(twists: list[Axis]) -> list[LowerAntiAxis]
("RORR", "ROr"),
],
)
def test_double_clockwise_to_anticlockwise(
twists: list[Axis], want: list[LowerAntiAxis]
):
def test_double_clockwise_to_anticlockwise(twists: list[Axis], want: list[Twist]):
assert double_clockwise_to_anticlockwise(twists) == list(want)
@ -625,8 +498,8 @@ def shelve_it(file_name):
def get_paths_from_heuristic(
start: Skewb, heuristic_permutation: list[int]
) -> list[list[LowerAntiAxis]]:
out: list[list[LowerAntiAxis]] = []
) -> list[list[Twist]]:
out: list[list[Twist]] = []
s = start
mask = [0 for _ in heuristic_permutation]
total_path_length = 0
@ -742,9 +615,16 @@ if __name__ == "__main__":
# print(f"{hp=} {evaluate_permutation(hp, seed=200, sample_size=200)=}")
hp = [10, 0, 1, 2, 3, 8, 9, 11, 12, 4, 5, 6, 7]
path = get_paths_from_heuristic(
Skewb(top=(O0, B0, R0, G0), bot=(B2, R0, G1, O0), mids=(BY, YY, GY, RY, ORB)),
hp,
print(
bidirectional_search(
Skewb(
top=(G0, O0, B0, R0), bot=(B1, O1, G2, R1), mids=(OO, YY, RO, GR, BO)
),
max_steps=1000000,
)
print(path)
)
# path = get_paths_from_heuristic(
# Skewb(top=(G0, O0, B0, R0), bot=(B1, O1, G2, R1), mids=(OR, YY, RO, GR, BO)),
# hp,
# )
# RBOG