initial commit
This commit is contained in:
165
.gitignore
vendored
Normal file
165
.gitignore
vendored
Normal 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
0
__init__.py
Normal file
128
__main__.py
Normal file
128
__main__.py
Normal 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
205
cube.py
Normal 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
126
eolr.py
Normal 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
24
generate_regrips.py
Normal 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
241
moves.yaml
Normal 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
129
scorer.py
Normal 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
104
solver.py
Normal 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
|
||||||
Reference in New Issue
Block a user