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