Skip to content

Instantly share code, notes, and snippets.

@SpiredMoth
Last active February 15, 2021 07:17
Show Gist options
  • Save SpiredMoth/da6d71ec19b5ccd3c75227feccfa0a07 to your computer and use it in GitHub Desktop.
Save SpiredMoth/da6d71ec19b5ccd3c75227feccfa0a07 to your computer and use it in GitHub Desktop.
"""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