Last active
February 15, 2021 07:17
-
-
Save SpiredMoth/da6d71ec19b5ccd3c75227feccfa0a07 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Random swaps picker for Faulty Technology Pokémon challenge runs. | |
Inspired by the challenge as found on Smogon's forums | |
https://www.smogon.com/forums/threads/faulty-technology.3509964/ | |
This script requires Python 3.6 or above. | |
Can be run from a command prompt/terminal with no arguments: | |
Windows: | |
py -3 faulty_technology.py | |
Mac / Linux: | |
python3 faulty_technology.py | |
When run from command prompt/terminal, it should explain itself fairly | |
well. | |
""" | |
from distutils.util import strtobool | |
import json | |
import math | |
import os | |
import random | |
class FaultyTech: | |
# Required defaults for setattr checking | |
min = 1 | |
max = 6 | |
def __init__(self, swap_min: int = 1, swap_max: int = 1, party_size: int = 6, swapins: bool = False, diff_swaps: bool = True, boxed_pkm: int = 0, shifts: bool = False) -> None: | |
# Standard Faulty Tech options | |
self.party_size = party_size # current party size | |
self.min = swap_min # fewest allowed swaps | |
self.max = swap_max # most allowed swaps | |
# Extra randomization options | |
self.boxed = boxed_pkm # total pkm currently stored in PC | |
self.diff_swaps = diff_swaps # can withdrawal <= deposited | |
self.swapins = swapins # randomize Box 1 members to swap in | |
self.shifts = shifts # randomize box shifts | |
def __setattr__(self, name: str, value) -> None: | |
if name in ('shifts', 'swapins', 'diff_swaps'): # Booleans | |
value = bool(value) | |
else: | |
# ???: make sure value is an integer? | |
value = int(value) | |
if value < 0: | |
print(f"Cannot set {name} to a negative number") | |
return None | |
if name in ('min', 'max', 'party_size') and value > 6: | |
print(f"Cannot set '{name}' to '{value}' because it's not a legitimate party size") | |
return None | |
if name == 'min' and value > self.max: | |
print("Cannot set a 'min' to a number greater than 'max'.") | |
return None | |
elif name == 'max' and value < self.min: | |
print("Cannot set a 'max' to a number lower than 'min'.") | |
return None | |
return super(FaultyTech, self).__setattr__(name, value) | |
def get_config(self) -> dict: | |
"""Return instance attributes in a dict.""" | |
config = { | |
"party_size": self.party_size, | |
"min": self.min, | |
"max": self.max, | |
"boxed": self.boxed, | |
"swapins": self.swapins, | |
"shifts": self.shifts, | |
"diff_swaps": self.diff_swaps | |
} | |
return config | |
def from_config(self, config: dict) -> None: | |
"""Set instance attributes from a dict.""" | |
for key, value in config.items(): | |
super(FaultyTech, self).__setattr__(key, value) | |
def _pick(self, swap_count, swap_population) -> list: | |
"""Pick swaps as 1-based slot indices.""" | |
swaps = random.sample(range(1, swap_population + 1), swap_count) | |
return swaps | |
def swaps(self) -> list: | |
"""Generate list of swaps to be made.""" | |
if self.party_size == 1 and self.boxed == 0: | |
print("Cannot make changes to party with only 1 Pokémon.") | |
return [] | |
output = [] | |
swap_count = min(random.randint(self.min, self.max), | |
self.party_size - int(self.boxed == 0)) | |
output.append(self._pick(swap_count, self.party_size)) | |
# if user wants a chance at fewer swap-ins than swap-outs | |
if self.diff_swaps: | |
# reroll swap_count for new value | |
# interval used: [0, swap_count] biased towards swap_count | |
swap_count -= round(random.triangular(mode=0) * swap_count) | |
if self.swapins and swap_count: | |
remaining = min(self.boxed, 30) | |
output.append(self._pick( | |
min(swap_count, remaining), min(remaining, 30))) | |
if self.shifts and self.boxed > 30 and swap_count: | |
if not self.swapins: | |
output.append([]) | |
for box in range(1, math.ceil(self.boxed / 30)): | |
remaining = self.boxed - 30 * box | |
output.append(self._pick( | |
min(swap_count, remaining), min(remaining, 30))) | |
return output | |
def main() -> None: | |
"""Picks random party swaps for Faulty Technology Pokemon runs.""" | |
import sys | |
main_menu = """ | |
Pokémon Challenge Run - Faulty Technology | |
Swap Randomization Script | |
{} | |
1 - Pick swaps | |
2 - Set number of boxed Pokémon | |
3 - Change configuration | |
4 - Load configuration | |
5 - Save configuration | |
6 - Reset configuration | |
0 - Exit | |
clear - Clear previous messages | |
""" | |
config_str = """ | |
Current Configuration{}: | |
- Party size: {party_size} | |
- Minimum number to swap: {min} | |
- Maximum number to swap: {max} | |
- Number of boxed Pokémon: {boxed} | |
- Randomize swap-ins: {swapins} | |
- Allow fewer swap-ins: {diff_swaps} | |
- Randomize box swaps: {shifts} | |
""" | |
run = FaultyTech() | |
saved_runs = dict() | |
loaded_run = "" | |
changes = False | |
clear = 'cls' if os.name == 'nt' else 'clear' | |
if os.path.exists("faulty_technology.json"): | |
with open("faulty_technology.json", "r", encoding="UTF-8") as f: | |
saved_runs = json.loads(f.read()) | |
choice = None | |
while choice != '0': | |
config = run.get_config() | |
print(main_menu.format(config_str.format(f" ({loaded_run})" if loaded_run else "", **config))) | |
choice = input("Choice: ") | |
if choice == "0": | |
leave = "y" | |
if changes: | |
leave = input("\nConfiguration has changed since last save. Exit anyway? (y/n): ") | |
if leave != "y": | |
choice = "" | |
else: | |
print("\nHope you had fun... despite the Pokémon Center's buggy equipment.") | |
sys.exit() | |
# Pick random swaps | |
if choice == "1": | |
swap_list = run.swaps() | |
length = len(swap_list) | |
src = "Party" | |
target = "boxes" | |
print() # spacer line | |
if length == 0: | |
print("No swaps to be made.") | |
continue | |
for box, swaps in enumerate(swap_list): | |
if len(swaps): | |
print(f"{src} Pokémon moving to {target}: {', '.join(['{:>2}'.format(str(s)) for s in swaps])}") | |
src = f"Box {box + 1}" | |
target = f"Box {box}" | |
if box == 0: | |
target = "Party" | |
input("\nPress Enter to continue") | |
print("\n\n\n\n") | |
# Set caught | |
elif choice == "2": | |
old_box = run.boxed | |
print("\nYou can either set the boxed count to a specific number or change it by an amount") | |
print(" '30' would set the boxed count to 30") | |
print(" '+7' would increase the current boxed count by 7") | |
print(" '-3' would decrease the current boxed count by 3") | |
new_box = input("Change boxed count to/by: ").strip().strip(" '\"") | |
sign = new_box[0] | |
try: | |
count = int(new_box) | |
except ValueError: | |
print("\nBoxed count can only be set to a number.") | |
else: | |
if sign.isdigit(): | |
run.boxed = 0 | |
run.boxed += count | |
if run.boxed < 0: | |
run.boxed = 0 | |
if sign in ('+', '-'): | |
print(f"Boxed count {'in' if sign == '+' else 'de'}creased by {abs(count)} to a total of {run.boxed}.\n") | |
else: | |
print(f"Boxed count set to {run.boxed}.\n") | |
if old_box != run.boxed: | |
changes = True | |
# Change config (party size, min swaps, max swaps, etc.) | |
elif choice == "3": | |
changes = config_menu(config, loaded_run) | |
run.from_config(config) | |
os.system(clear) | |
# Load | |
elif choice == "4": | |
if len(saved_runs): | |
print("\n\tCurrently saved configurations:\n") | |
run_names = sorted(saved_runs.keys()) | |
for save in run_names: | |
print(f"\t\t{save}") | |
print("\n\tEnter the name of the configuration you wish to load.") | |
print("\tTo cancel, press Enter without providing a name.") | |
name = input("\nRun to load: ") | |
os.system(clear) | |
if name in saved_runs: | |
run.from_config(saved_runs[name]) | |
if loaded_run != name: | |
loaded_run = name | |
changes = False | |
elif name: | |
print(f"No run named '{name}'") | |
else: | |
print("Didn't find any saved configuations to load.") | |
# Save | |
elif choice == "5": | |
print("\n\tCurrently saved configurations:\n") | |
run_names = sorted(saved_runs.keys()) | |
for save in run_names: | |
print(f"\t\t{save}") | |
print("\n\tEnter a name to save the current configuration as.") | |
print("\tIf you enter one of the names above, this will overwrite" | |
"\n\tthe configuration currently saved under that name.") | |
print("\tTo cancel, press Enter without providing a name.") | |
name = input("\nName for configuration: ") | |
if name: | |
saved_runs[name] = run.get_config() | |
with open("faulty_technology.json", "w", encoding="UTF-8") as f: | |
f.write(json.dumps(saved_runs, indent=4)) | |
if loaded_run != name: | |
loaded_run = name | |
changes = False | |
print(f"Configuration saved under name {name}") | |
# Reset | |
elif choice == "6": | |
run = FaultyTech() | |
loaded_run = "" | |
os.system(clear) | |
print("Entire configuration has been reset to defaults.\n") | |
elif choice.lower() == "clear": | |
os.system(clear) | |
def config_menu(config: dict, loaded_run: str = "") -> bool: | |
"""Modify a Faulty Tech run's configuration.""" | |
config_menu = """ | |
Configurable settings for {} and their current values: | |
1 - Party size: {party_size} | |
2 - Minimum number to swap: {min} | |
3 - Maximum number to swap: {max} | |
4 - Randomize swap-ins: {swapins} | |
5 - Allow fewer swap-ins: {diff_swaps} | |
6 - Randomize box swaps: {shifts} | |
0 - Go back to previous menu | |
clear - Clear previous messages | |
""" | |
choice = "9" | |
clear = 'cls' if os.name == 'nt' else 'clear' | |
old_config = dict(config) | |
while not choice.startswith("0"): | |
print(config_menu.format(f"'{loaded_run}'" if loaded_run else "this script", **config)) | |
choice = input("Enter the number of the setting you want to edit: ").strip() | |
# party size | |
if choice.startswith("1"): | |
print("\n\tParty size partially controls how many swaps the script" | |
"\n\tcan give you. This script will never suggest a number" | |
"\n\tof swaps greater than this setting's value.") | |
print(f"\n\tCurrent value: {config['party_size']}") | |
try: | |
new_value = int(input("\nNew value: ").strip()) | |
if 7 > new_value > 0: | |
config["party_size"] = new_value | |
else: | |
raise ValueError | |
except ValueError: | |
print("Invalid value for 'Party size'") | |
# min swaps | |
elif choice.startswith("2"): | |
print("\n\tThis setting tells the script 'Do not give me less than" | |
"\n\tthis number of swaps'") | |
print(f"\n\tCurrent value: {config['min']}") | |
try: | |
new_value = int(input("\nNew value: ").strip()) | |
if config["max"] >= new_value > 0: | |
config["min"] = new_value | |
else: | |
raise ValueError | |
except ValueError: | |
print("Invalid value for 'Minimum swaps'") | |
# max swaps | |
elif choice.startswith("3"): | |
print("\n\tThis setting tells the script 'Do not give me more than" | |
"\n\tthis number of swaps'") | |
print(f"\n\tCurrent value: {config['max']}") | |
try: | |
new_value = int(input("\nNew value: ").strip()) | |
if 7 > new_value >= config["min"]: | |
config["max"] = new_value | |
else: | |
raise ValueError | |
except ValueError: | |
print("Invalid value for 'Maximum swaps'") | |
# random swapins | |
elif choice.startswith("4"): | |
print("\n\tWhen this is 'True' the script will recommend which" | |
"\n\tslots of Box 1 are to be used to refill your party.") | |
print(f"\n\tCurrent value: {config['swapins']}") | |
try: | |
new_value = input("\nNew value (True / False): ").strip().lower() | |
config['swapins'] = bool(strtobool(new_value)) | |
except ValueError: | |
print("Invalid value for 'Randomize swap-ins'") | |
# fewer swapins | |
elif choice.startswith("5"): | |
print("\n\tThis setting controls whether or not the script can" | |
"\n\tsuggest a fewer number of Pokémon to swap in to your" | |
"\n\tparty than it suggested be swapped out." | |
"\n\tFewer swap-ins can help deal with times when multiple" | |
"\n\tmembers of your team share a weakness, or if you need" | |
"\n\tan HM that otherwise doesn't fit on your team.") | |
print(f"\n\tCurrent value: {config['diff_swaps']}") | |
try: | |
new_value = input("\nNew value (True / False): ").strip().lower() | |
config['diff_swaps'] = bool(strtobool(new_value)) | |
except ValueError: | |
print("Invalid value for 'Allow fewer swap-ins'") | |
# random box downfills | |
elif choice.startswith("6"): | |
print("\n\tThis setting controls whether the script will suggest" | |
"\n\twhich Pokémon in later boxes should be moved down to" | |
"\n\tfill in openings in earlier boxes.") | |
print(f"\n\tCurrent value: {config['shifts']}") | |
try: | |
new_value = input("\nNew value (True / False): ").strip().lower() | |
config['shifts'] = bool(strtobool(new_value)) | |
except ValueError: | |
print("Invalid value for 'Randomize box swaps'") | |
# clear console | |
elif choice.lower() == "clear": | |
os.system(clear) | |
return old_config != config | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment