initial commit

This commit is contained in:
2024-08-22 16:19:27 +03:00
commit 47f2989373
9 changed files with 1122 additions and 0 deletions

165
.gitignore vendored Normal file
View File

@ -0,0 +1,165 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
output.json
prune_table.pkl

0
__init__.py Normal file
View File

128
__main__.py Normal file
View File

@ -0,0 +1,128 @@
from collections import defaultdict
import json
import os
import pickle
import sys
from eolr import (
eolrb_states_generator,
EOLROrientation,
EOLRPermutation,
EOLR_PERMUTATIONS,
)
import humanize
from dataclasses import dataclass
from solver import EOLRBSolution, solve_eolrb, create_prune_table
from scorer import (
FingerTrickWithRegrip,
load_config,
generate_finger_tricks,
build_pretty_string_from_finger_tricks_with_regrips,
)
import heapq
from typing import List, Any, Generator, Dict
SOLUTIONS_TO_EVAL = 40
SOLUTIONS_TO_SHOW = 3
PRUNE = 10
SOLVE = 7
@dataclass
class EOLRBSolutionWithFingertricks:
solution: EOLRBSolution
finger_tricks: List[FingerTrickWithRegrip]
def get_first_n_from_generator(gen: Generator[Any, None, Any], n: int) -> List[Any]:
results: List[Any] = []
try:
while len(results) < n:
item = next(gen)
results.append(item)
except StopIteration:
pass
return results
def load_or_generate_prune_table(file_path: str, prune_size: int) -> Dict:
if os.path.isfile(file_path):
print("opening prune table from", file_path)
with open(file_path, "rb") as file:
return pickle.load(file)
else:
print("generating prune table...")
prune_table = create_prune_table(prune_size)
print("saving prune table to", file_path)
with open(file_path, "wb") as file:
pickle.dump(prune_table, file)
return prune_table
def main():
config = load_config("./moves.yaml")
prune_table = load_or_generate_prune_table("prune_table.pkl", PRUNE)
print("prune table size:", humanize.naturalsize(sys.getsizeof(prune_table)))
# TODO: make this function a method of an object that contains a list of finger tricks
score_func = lambda solution: sum(ft.score() for ft in solution.finger_tricks)
data = defaultdict(list)
eolrb_states, n_states = eolrb_states_generator(
list(EOLROrientation), EOLR_PERMUTATIONS
)
# eolrb_states, n_states = eolrb_states_generator(
# [EOLROrientation.Solved], [EOLRPermutation(UR="UR", UL="UL")]
# )
for i, (eolrb_cube, ori, perm, pre_auf) in enumerate(eolrb_states):
print(
f"generating algs for {ori.name} (UR in {perm.UR}, UL in {perm.UL}, pre AUF {pre_auf}) ({i}/{n_states})"
)
alg_generator = solve_eolrb(eolrb_cube, prune_table, PRUNE)
solutions = get_first_n_from_generator(alg_generator, SOLUTIONS_TO_EVAL)
solutions_with_finger_tricks = [
EOLRBSolutionWithFingertricks(
solution, generate_finger_tricks(config, solution.alg)
)
for solution in solutions
]
best_solutions = heapq.nsmallest(
min(SOLUTIONS_TO_SHOW, len(solutions_with_finger_tricks)),
solutions_with_finger_tricks,
key=score_func,
)
for solution_with_finger_tricks in best_solutions:
solution = solution_with_finger_tricks.solution
finger_tricks = solution_with_finger_tricks.finger_tricks
pretty_alg = []
if solution.pre_auf:
pretty_alg.append(solution.pre_auf)
pretty_alg.extend(solution.alg)
if solution.post_auf:
pretty_alg.append(solution.post_auf)
if solution.mc:
pretty_alg.append("M")
pretty_finger_tricks = build_pretty_string_from_finger_tricks_with_regrips(
finger_tricks
)
score = score_func(solution_with_finger_tricks)
alg_name = f"{ori.name} {perm.UR} {perm.UL} {pre_auf}"
data[alg_name].append(
{
"alg": " ".join(pretty_alg),
"stm": len(solution.alg),
"score": score,
"raw_alg": " ".join(solution.alg),
"pre_auf": solution.pre_auf,
"post_auf": solution.post_auf,
"finger_tricks": pretty_finger_tricks,
}
)
with open("output.json", "w") as f:
f.write(json.dumps(data))
if __name__ == "__main__":
main()

205
cube.py Normal file
View File

@ -0,0 +1,205 @@
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.edges = {
"UB": Edge("", True),
"UR": Edge("UR", True),
"UF": Edge("", True),
"UL": Edge("UL", True),
"DB": Edge("", True),
"DF": Edge("", True),
}
self.centers = {
"U": "U",
"B": "B",
"F": "F",
"D": "D",
}
self.corners = {
"UBR": "UBR",
"UFR": "UFR",
"UFL": "UFL",
"UBL": "UBL",
}
def __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()
edges_hash = hash_dict(self.edges)
centers_hash = hash_dict(self.centers)
corners_hash = hash_dict(self.corners)
return int(
hashlib.md5(
(edges_hash + centers_hash + corners_hash).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 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)
# print(reduced_moves)
counted_moves = reduce_moves(reduced_moves)
# print(counted_moves)
reduced_alg = build_reduced_alg(counted_moves)
# print(reduced_alg)
return reduced_alg

126
eolr.py Normal file
View File

@ -0,0 +1,126 @@
from dataclasses import dataclass
from enum import Enum
import itertools
from typing import List, Tuple, Iterator
from cube import Edge, LSECube
class EOLROrientation(Enum):
Arrow = ["UR", "UF", "UL", "DF"]
BackArrow = ["UR", "UF", "UL", "DB"]
FourZero = ["UB", "UR", "UF", "UL"]
OneOne = ["UB", "DF"]
BackOneOne = ["UB", "DB"]
SixFlip = ["UB", "UR", "UF", "UL", "DF", "DB"]
Solved = []
TwoAdjTwo = ["UB", "UR", "DF", "DB"]
TwoAdjZero = ["UB", "UR"]
TwoOppTwo = ["UB", "UF", "DF", "DB"]
TwoOppZero = ["UB", "UF"]
ZeroTwo = ["DF", "DB"]
@dataclass
class EOLRPermutation:
UR: str
UL: str
EOLR_PERMUTATIONS = [
EOLRPermutation(ur_pos, ul_pos)
for ul_pos, ur_pos in itertools.permutations(
["UB", "UR", "UF", "UL", "DF", "DB"], 2
)
]
EOLR_EDGES = [
"UB",
"UR",
"UF",
"UL",
"DF",
"DB",
]
def apply_eolr_orientation(cube: LSECube, ori: EOLROrientation):
for edge in ori.value:
cube.edges[edge].oriented = not cube.edges[edge].oriented
def apply_eolr_permutation(cube: LSECube, perm: EOLRPermutation):
cube.edges[perm.UR], cube.edges["UR"] = cube.edges["UR"], cube.edges[perm.UR]
cube.edges[perm.UL], cube.edges["UL"] = cube.edges["UL"], cube.edges[perm.UL]
def apply_eolr_auf(cube: LSECube, auf: int):
cube.cycle_corners(auf)
def eolrb_states_generator(
orientations: List[EOLROrientation], permutations: List[EOLRPermutation]
) -> Tuple[Iterator[Tuple[LSECube, EOLROrientation, EOLRPermutation, int]], int]:
num_permutations = len(orientations) * len(permutations) * 4
def generator():
for ori in orientations:
for perm in permutations:
for auf in range(4):
state = LSECube()
apply_eolr_auf(state, auf)
apply_eolr_orientation(state, ori)
apply_eolr_permutation(state, perm)
yield (state, ori, perm, auf)
return generator(), num_permutations
def lr_solved(cube: LSECube):
return cube.edges["UR"] == Edge("UR", True) and cube.edges["UL"] == Edge("UL", True)
def eo_solved_non_mc(cube: LSECube) -> bool:
return (
cube.edges["UB"].oriented
and cube.edges["UF"].oriented
and cube.edges["DF"].oriented
and cube.edges["DB"].oriented
)
def centers_solved_non_mc(cube: LSECube) -> bool:
return cube.centers["U"] in ["U", "D"]
def eo_solved_mc(cube: LSECube) -> bool:
return (
not cube.edges["UB"].oriented
and not cube.edges["UF"].oriented
and not cube.edges["DF"].oriented
and not cube.edges["DB"].oriented
)
def centers_solved_mc(cube: LSECube) -> bool:
return cube.centers["U"] in ["F", "B"]
def eolrb_solved(cube: LSECube) -> bool:
"""
this function has weird side effects and modifies cube but we don't care about it for now
"""
if not lr_solved(cube):
return False
if centers_solved_non_mc(cube) and eo_solved_non_mc(cube):
return True
if centers_solved_mc(cube) and eo_solved_mc(cube):
return True
return False
def eolrb_solution_mc(cube: LSECube) -> bool:
"""
this function only works if eolrb is solved
"""
return centers_solved_mc(cube) and eo_solved_mc(cube)

24
generate_regrips.py Normal file
View File

@ -0,0 +1,24 @@
import itertools
import yaml
def generate_regrips():
finger_grips = {
"index": ["B", "BL", "FL", "F"],
"ring": ["B", "BD", "FD"],
"pinky": ["B", "BD", "FD", "F", "F floating"],
}
data = []
for finger, grips in finger_grips.items():
for pre, post in itertools.permutations(grips, 2):
data.append(
{
"finger": finger,
"pre": pre,
"post": post,
"score": 1.0,
}
)
print(yaml.safe_dump(data, sort_keys=False))
generate_regrips()

241
moves.yaml Normal file
View File

@ -0,0 +1,241 @@
finger_tricks:
- name: "U push"
move: "U"
grip_pre:
index: BL
grip_post:
index: B
score: 5
- name: "U flick"
move: "U"
grip_pre:
index: F
grip_post:
index: BL
score: 4
- name: "U' push"
move: "U'"
grip_pre:
index: FL
grip_post:
index: F
score: 5
- name: "U' flick"
move: "U'"
grip_pre:
index: B
grip_post:
index: FL
score: 3
- name: "U2 feido"
move: "U2"
grip_pre:
index: B
grip_post:
index: F
score: 5
- name: "U2 double flick"
move: "U2"
grip_pre:
index: B
grip_post:
index: FL
score: 6
- name: "U2' double flick"
move: "U2"
grip_pre:
index: F
grip_post:
index: BL
score: 9
- name: "U2 beido"
move: "U2"
grip_pre:
index: F
grip_post:
index: B
score: 5
- name: "M' ring flick"
move: "M'"
grip_pre:
ring: B
grip_post:
ring: DF
score: 3
- name: "M' pinky flick"
move: "M'"
grip_pre:
pinky: B
grip_post:
pinky: F floating
score: 6
- name: "M push"
move: "M"
grip_pre:
pinky: F
grip_post:
pinky: DB
score: 6
- name: "M2 double flick"
move: "M2"
grip_pre:
pinky: B
ring: B
grip_post:
pinky: F floating
ring: DB
score: 7
regrips:
# TODO: rethink this format?
- finger: index
pre: B
post: BL
score: 1
- finger: index
pre: B
post: FL
score: 1
- finger: index
pre: B
post: F
score: 1
- finger: index
pre: BL
post: B
score: 1
- finger: index
pre: BL
post: FL
score: 1
- finger: index
pre: BL
post: F
score: 1
- finger: index
pre: FL
post: B
score: 1
- finger: index
pre: FL
post: BL
score: 1
- finger: index
pre: FL
post: F
score: 1
- finger: index
pre: F
post: B
score: 1
- finger: index
pre: F
post: BL
score: 1
- finger: index
pre: F
post: FL
score: 1
- finger: ring
pre: B
post: DB
score: 1
- finger: ring
pre: B
post: DF
score: 1
- finger: ring
pre: DB
post: B
score: 1
- finger: ring
pre: DB
post: DF
score: 1
- finger: ring
pre: DF
post: B
score: 1
- finger: ring
pre: DF
post: DB
score: 1
- finger: pinky
pre: B
post: DB
score: 1
- finger: pinky
pre: B
post: DF
score: 1
- finger: pinky
pre: B
post: F
score: 1
- finger: pinky
pre: B
post: F floating
score: 1
- finger: pinky
pre: DB
post: B
score: 1
- finger: pinky
pre: DB
post: DF
score: 1
- finger: pinky
pre: DB
post: F
score: 1
- finger: pinky
pre: DB
post: F floating
score: 1
- finger: pinky
pre: DF
post: B
score: 1
- finger: pinky
pre: DF
post: DB
score: 1
- finger: pinky
pre: DF
post: F
score: 1
- finger: pinky
pre: DF
post: F floating
score: 1
- finger: pinky
pre: F
post: B
score: 1
- finger: pinky
pre: F
post: DB
score: 1
- finger: pinky
pre: F
post: DF
score: 1
- finger: pinky
pre: F
post: F floating
score: 1
- finger: pinky
pre: F floating
post: B
score: 1
- finger: pinky
pre: F floating
post: DB
score: 1
- finger: pinky
pre: F floating
post: DF
score: 1
- finger: pinky
pre: F floating
post: F
score: 1

129
scorer.py Normal file
View File

@ -0,0 +1,129 @@
from typing import Dict, List
import yaml
import dacite
from dataclasses import dataclass
@dataclass
class FingerTrick:
name: str
move: str
grip_pre: Dict[str, str]
grip_post: Dict[str, str]
score: float
@dataclass
class Regrip:
finger: str
pre: str
post: str
score: float
@dataclass
class Config:
finger_tricks: List[FingerTrick]
regrips: List[Regrip]
def home_grip() -> Dict[str, str]:
return {
"index": "B", # this could also be BL
"ring": "B",
"pinky": "BD",
}
def load_config(file_path: str) -> Config:
with open(file_path, "r") as f:
data_dict = yaml.safe_load(f.read())
return dacite.from_dict(data_class=Config, data=data_dict)
def grip_correct(current_grip: Dict[str, str], required_grip: Dict[str, str]):
return all(item in current_grip.items() for item in required_grip.items())
@dataclass
class FingerTrickWithRegrip:
finger_trick: FingerTrick
regrips: List[Regrip]
def score(self) -> float:
score = 0.0
score += self.finger_trick.score
if self.regrips:
longest_regrip = max(self.regrips, key=lambda regrip: regrip.score)
score += longest_regrip.score
return score
def calculate_finger_trick_regrips(
config: Config, finger_trick: FingerTrick, grip: Dict[str, str]
) -> List[Regrip]:
regrips = []
for finger in finger_trick.grip_pre.keys():
current_location = grip[finger]
desired_location = finger_trick.grip_pre.get(finger)
if current_location != desired_location:
regrip = next(
(
regrip
for regrip in config.regrips
if regrip.finger == finger
and regrip.pre == current_location
and regrip.post == desired_location
),
None,
)
if regrip:
regrips.append(regrip)
return regrips
def generate_finger_tricks(
config: Config, moves: List[str]
) -> List[FingerTrickWithRegrip]:
grip = home_grip()
alg = []
for move in moves:
# print("current grip:", grip)
# prit("current move:", move)
possible_finger_tricks = [
finger_trick
for finger_trick in config.finger_tricks
if move == finger_trick.move
]
# print("possible finger tricks:", possible_finger_tricks)
finger_tricks_with_regrips = [
FingerTrickWithRegrip(
finger_trick=finger_trick,
regrips=calculate_finger_trick_regrips(config, finger_trick, grip),
)
for finger_trick in possible_finger_tricks
]
best_finger_trick = min(
finger_tricks_with_regrips, key=lambda item: item.score()
)
# print("best finger trick:", best_finger_trick)
# apply regrips
for regrip in best_finger_trick.regrips:
grip[regrip.finger] = regrip.post
# apply move
grip.update(best_finger_trick.finger_trick.grip_post)
alg.append(best_finger_trick)
return alg
def build_pretty_string_from_finger_tricks_with_regrips(
finger_tricks_with_regrips: List[FingerTrickWithRegrip],
) -> List[str]:
elems = []
for finger_trick_with_regrips in finger_tricks_with_regrips:
for regrip in finger_trick_with_regrips.regrips:
elems.append(f"regrip {regrip.finger} from {regrip.pre} to {regrip.post}")
elems.append(finger_trick_with_regrips.finger_trick.name)
return elems

104
solver.py Normal file
View File

@ -0,0 +1,104 @@
from collections import defaultdict
import itertools
import copy
from dataclasses import dataclass
from cube import LSECube, condense_algorithm, reverse_algorithm
from eolr import eolrb_solved
from typing import Dict, List, Tuple
@dataclass
class EOLRBSolution:
alg: List[str]
pre_auf: str
post_auf: str
mc: bool
def lse_brute_force_generator(max_length: int):
list1 = ["M", "M'", "M2"]
list2 = ["U", "U'", "U2"]
def valid_combination(comb):
# Ensure no adjacent elements are from the same list
if any(
(comb[i] in list1 and comb[i + 1] in list1)
or (comb[i] in list2 and comb[i + 1] in list2)
for i in range(len(comb) - 1)
):
return False
# # Ensure the combination does not end with a U move
# if comb and comb[-1] in list2:
# return False
return True
def generate_combinations():
# Generate combinations of increasing lengths
for length in range(1, max_length + 1):
for comb in itertools.product(list1 + list2, repeat=length):
if valid_combination(comb):
yield list(comb)
return generate_combinations()
#
# prune table
#
def create_prune_table(prune_depth: int):
prune_table = defaultdict(list)
cube = LSECube()
prune_table[hash(cube)] = [[]]
generator = lse_brute_force_generator(prune_depth)
for alg in generator:
# TODO: turn the prune table into a dict of list values to store multiple solutions
cube.reset()
cube.alg(" ".join(alg))
setup = reverse_algorithm(alg)
prune_table[hash(cube)].append(setup)
return prune_table
def extract_pre_auf(alg: List[str]) -> Tuple[str, List[str]]:
if alg[0].startswith("U"):
pre_auf = alg.pop(0)
return pre_auf, alg
else:
return "", alg
def solve_eolrb(cube: LSECube, prune_table: Dict, solve: int):
for moves in lse_brute_force_generator(solve):
c = copy.deepcopy(cube)
c.alg(" ".join(moves))
if hash(c) in prune_table:
for alg_start in prune_table[hash(c)]:
alg = condense_algorithm(alg_start + moves)
# TODO: this is very weird
for _ in range(10):
alg = condense_algorithm(alg)
if len(alg) == 0:
yield EOLRBSolution(alg, "", "", False)
else:
# TODO: check if this is mc
pre_auf = ""
if alg[0].startswith("U"):
pre_auf = alg.pop(0)
post_auf = ""
if alg[-1].startswith("U"):
post_auf = alg.pop(-1)
yield EOLRBSolution(alg, pre_auf, post_auf, False)
# for auf in ["U", "U2", "U'", ""]:
# c = copy.deepcopy(cube)
# auf_alg = f"{' '.join(alg)} {auf}"
# c.alg(auf_alg)
# if eolrb_solved(c):
# yield alg