from typing import Dict, List, Set, Tuple import yaml import dacite from dataclasses import dataclass @dataclass class FingerTrick: name: str move: str grip_pre_blacklist: Dict[str, List[str]] grip_pre: Dict[str, str] grip_post: Dict[str, str] score: float @dataclass class Grip: finger: str pre: str post: str score: float @dataclass class Regrip: finger: str start: str end: str score: float @dataclass class Finger: name: str default_score: int home_grips: List[str] grips: Dict[str, List[str]] regrips: List[Regrip] @dataclass class Config: fingers: List[Finger] finger_tricks: List[FingerTrick] @dataclass class Definitions: fingers: List[Finger] finger_tricks: List[FingerTrick] finger_regrips: List[Regrip] def build_missing_regrips( finger_regrips: Dict[Tuple[str, str], Regrip], finger: Finger, grips: Set[str] ) -> Dict[Tuple[str, str], Regrip]: # this might just be the worst piece of code I have ever written # fill in missing regrips so every pair of grips has a regrip for grip_start in grips: for grip_end in grips: if grip_start != grip_end: if (grip_start, grip_end) not in finger_regrips: # TODO: this only works for regrips which are 2 apart possible_start_regrips = { grips: regrip for grips, regrip in finger_regrips.items() if grip_start == regrip.start } possible_end_regrips = { grips: regrip for grips, regrip in finger_regrips.items() if grip_end == regrip.end } for start_regrip in possible_start_regrips.values(): for end_regrip in possible_end_regrips.values(): # if start grips ends at the beginning of end grip if start_regrip.end == end_regrip.start: finger_regrips[(start_regrip.start, end_regrip.end)] = ( Regrip( finger=finger.name, start=start_regrip.start, end=end_regrip.end, score=start_regrip.score + end_regrip.score, ) ) return finger_regrips def build_regrips_from_fingers(fingers: List[Finger]) -> List[Regrip]: regrips = [] for finger in fingers: finger_regrips = {} for grip_set in finger.grips.values(): # reate a dict with distances of every two elements in a list for i, grip_start in enumerate(grip_set): for j, grip_end in enumerate(grip_set): if grip_start != grip_end: finger_regrips[(grip_start, grip_end)] = Regrip( start=grip_start, end=grip_end, score=abs(j - i) * finger.default_score, finger=finger.name, ) for regrip in finger.regrips: finger_regrips[(regrip.start, regrip.end)] = regrip grips = {grip for grip_set in finger.grips.values() for grip in grip_set} finger_regrips = build_missing_regrips(finger_regrips, finger, grips) regrips.extend(finger_regrips.values()) return regrips def load_definitions(file_path: str) -> Definitions: with open(file_path, "r") as f: data_dict = yaml.safe_load(f.read()) config = dacite.from_dict(data_class=Config, data=data_dict) return Definitions( fingers=config.fingers, finger_tricks=config.finger_tricks, finger_regrips=build_regrips_from_fingers(config.fingers), ) 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 @dataclass class Algorithm: score: float finger_tricks_with_regrips: List[FingerTrickWithRegrip] def find_finger_trick_regrips( regrips: List[Regrip], finger_trick: FingerTrick, grip: Dict[str, str] ) -> List[Regrip]: alg_regrips = [] for finger in finger_trick.grip_pre.keys(): current_location = grip[finger] desired_location = finger_trick.grip_pre.get(finger) blacklisted_locations = finger_trick.grip_pre_blacklist.get(finger, []) if current_location != desired_location: # print("FINGA", finger) # print("CURRENT", current_location) # print("DESIRED", desired_location) # print("BLACK", blacklisted_locations) # __import__("pprint").pprint(regrips) alg_regrip = next( ( regrip for regrip in regrips if regrip.finger == finger and regrip.start == current_location and regrip.end == desired_location and regrip.start not in blacklisted_locations ), ) if alg_regrip: alg_regrips.append(alg_regrip) return alg_regrips def generate_home_grip(fingers: List[Finger]) -> Dict[str, str]: # TODO: make this smarter home_grip = {} for finger in fingers: home_grip[finger.name] = finger.home_grips[0] return home_grip def generate_finger_tricks( definitions: Definitions, moves: List[str] ) -> List[FingerTrickWithRegrip]: grip = generate_home_grip(definitions.fingers) alg: List[FingerTrickWithRegrip] = [] for move in moves: # print("current grip:", grip) # prit("current move:", move) possible_finger_tricks = [ finger_trick for finger_trick in definitions.finger_tricks if move == finger_trick.move ] # print("possible finger tricks:", possible_finger_tricks) finger_tricks_with_regrips = [ FingerTrickWithRegrip( finger_trick=finger_trick, regrips=find_finger_trick_regrips( definitions.finger_regrips, 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.end # apply move grip.update(best_finger_trick.finger_trick.grip_post) alg.append(best_finger_trick) # TODO: think about this # don't count the first regrip # alg[0].finger_trick.score = 0 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.start} to {regrip.end} ({regrip.score})" ) elems.append( f"{finger_trick_with_regrips.finger_trick.name} ({finger_trick_with_regrips.finger_trick.score})" ) return elems