import random from enum import Enum from math import factorial, comb from src.evilkode import Observation from src.keypad import Keypad def total_valid_nkode_states(k: int, p: int) -> int: return factorial(k) ** (p-1) def total_shuffle_states(k: int, p: int) -> int: return comb((p-1), (p-1) // 2) * factorial(k) class ShuffleTypes(Enum): FULL_SHUFFLE = "FULL_SHUFFLE" SPLIT_SHUFFLE = "SPLIT_SHUFFLE" TOWER_SHUFFLE = "TOWER_SHUFFLE" def observations(target_passcode: list[int], number_of_keys:int, properties_per_key: int, min_complexity: int, min_disparity: int, shuffle_type: ShuffleTypes, number_of_observations: int = 100): k = number_of_keys p = properties_per_key keypad = Keypad.new_keypad(k, p) def obs_gen(): for _ in range(number_of_observations): yield Observation( keypad=keypad.keypad.copy(), key_selection=keypad.key_entry(target_passcode=target_passcode) ) match shuffle_type: case ShuffleTypes.FULL_SHUFFLE: keypad.full_shuffle() case ShuffleTypes.SPLIT_SHUFFLE: keypad.split_shuffle() case ShuffleTypes.TOWER_SHUFFLE: keypad.tower_shuffle() case _: raise Exception(f"no shuffle type {shuffle_type}") return obs_gen() def passcode_generator(k: int, p: int, n: int, c: int, d: int) -> list[int]: assert n >= c assert p*k >= c assert n >= d assert p >= d passcode_prop = [] passcode_set = [] valid_choices = {i for i in range(k*p)} repeat_set = n-d repeat_prop = n-c prop_added = set() set_added = set() for _ in range(n): prop = random.choice(list(valid_choices)) prop_set = prop//p passcode_prop.append(prop) passcode_set.append(prop_set) if prop in prop_added: repeat_prop -= 1 if prop_set in set_added: repeat_set -= 1 prop_added.add(prop) set_added.add(prop_set) if repeat_prop <= 0: valid_choices -= prop_added if repeat_set <= 0: for el in valid_choices.copy(): if el // p in set_added: valid_choices.remove(el) return passcode_prop