From 66c57e38b04093ce46619d718e33e8d62c76b8d9 Mon Sep 17 00:00:00 2001 From: "Mike A. Trethewey" Date: Sun, 23 Feb 2020 21:38:22 -0800 Subject: [PATCH] Fix CLI precedence --- CLI.py | 96 ++++++++++++++++++++++------------ DungeonRandomizer.py | 3 +- Gui.py | 35 +++++++++---- classes/constants.py | 65 +++++++++++++++++++++++ gui/bottom.py | 65 +++-------------------- gui/loadcliargs.py | 93 ++++++++------------------------ gui/startinventory/overview.py | 9 ++-- 7 files changed, 189 insertions(+), 177 deletions(-) diff --git a/CLI.py b/CLI.py index d98e95a1..38fa91e8 100644 --- a/CLI.py +++ b/CLI.py @@ -34,7 +34,7 @@ def parse_arguments(argv, no_defaults=False): multiargs, _ = parser.parse_known_args(argv) parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) - parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') + parser.add_argument('--create_spoiler', default=defval(settings["create_spoiler"] != 0), help='Output a Spoiler File', action='store_true') parser.add_argument('--logic', default=defval(settings["logic"]), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'nologic'], help='''\ Select Enforcement of Item Requirements. (default: %(default)s) @@ -236,8 +236,8 @@ def parse_arguments(argv, no_defaults=False): and a few other little things make this more like Zelda-1. ''', action='store_true') parser.add_argument('--startinventory', default=defval(settings["startinventory"]), help='Specifies a list of items that will be in your starting inventory (separated by commas)') + parser.add_argument('--usestartinventory', default=defval(settings["usestartinventory"] != 0), help='Not supported.') parser.add_argument('--custom', default=defval(settings["custom"] != 0), help='Not supported.') - parser.add_argument('--customitemarray', default=defval(settings["customitemarray"] != 0), help='Not supported.') parser.add_argument('--accessibility', default=defval(settings["accessibility"]), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\ Select Item/Location Accessibility. (default: %(default)s) Items: You can reach all unique inventory items. No guarantees about @@ -270,7 +270,7 @@ def parse_arguments(argv, no_defaults=False): Alternatively, can be a ALttP Rom patched with a Link sprite that will be extracted. ''') - parser.add_argument('--suppress_rom', help='Do not create an output rom file.', action='store_true') + parser.add_argument('--suppress_rom', default=defval(settings["suppress_rom"] != 0), help='Do not create an output rom file.', action='store_true') parser.add_argument('--gui', help='Launch the GUI', action='store_true') parser.add_argument('--jsonout', action='store_true', help='''\ Output .json patch to stdout instead of a patched rom. Used @@ -324,10 +324,7 @@ def parse_arguments(argv, no_defaults=False): def get_settings(): # set default settings settings = { - "multi": 1, - "names": "", - "seed": None, - "count": 1, + "retro": False, "mode": "open", "logic": "noglitches", "goal": "ganon", @@ -340,41 +337,53 @@ def get_settings(): "progressive": "on", "accessibility": "items", "algorithm": "balanced", - "shuffle": "vanilla", - "door_shuffle": "basic", - "experimental": 0, - "dungeon_counters": "off", - "heartbeep": "normal", - "heartcolor": "red", - "fastmenu": "normal", - "create_spoiler": False, - "skip_playthrough": True, - "suppress_rom": False, + "openpyramid": False, + "shuffleganon": False, + "shuffle": "vanilla", + + "shufflepots": False, + "shuffleenemies": "none", + "shufflebosses": "none", + "enemy_damage": "default", + "enemy_health": "default", + "enemizercli": os.path.join(".","EnemizerCLI","EnemizerCLI.Core"), + "mapshuffle": False, "compassshuffle": False, "keyshuffle": False, "bigkeyshuffle": False, "keysanity": False, - "retro": False, - "startinventory": "", - "quickswap": False, + "door_shuffle": "basic", + "experimental": 0, + "dungeon_counters": "off", + + "multi": 1, + "names": "", + + "hints": True, "disablemusic": False, + "quickswap": False, + "heartcolor": "red", + "heartbeep": "normal", + "sprite": None, + "fastmenu": "normal", "ow_palettes": "default", "uw_palettes": "default", - "shuffleganon": True, - "hints": True, - "enemizercli": os.path.join(".","EnemizerCLI","EnemizerCLI.Core"), - "shufflebosses": "none", - "shuffleenemies": "none", - "enemy_health": "default", - "enemy_damage": "default", - "shufflepots": False, + + "create_spoiler": False, + "skip_playthrough": True, + "suppress_rom": False, + "usestartinventory": False, + "custom": False, + "rom": os.path.join(".","Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"), + + "seed": None, + "count": 1, + "startinventory": "", "beemizer": 0, "remote_items": False, "race": False, - "custom": False, - "usestartinventory": False, "customitemarray": { "bow": 0, "progressivebow": 2, @@ -448,8 +457,6 @@ def get_settings(): "rupoor": 0, "rupoorcost": 10 }, - "rom": os.path.join(".","Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"), - "sprite": None, "randomSprite": False, "outputpath": os.path.join(".") } @@ -467,3 +474,28 @@ def get_settings(): for k,v in data.items(): settings[k] = v return settings + +def get_args_priority(settings_args, gui_args, cli_args): + args = {} + args["settings"] = get_settings() if settings_args is None else settings_args + args["gui"] = {} if gui_args is None else gui_args + args["cli"] = cli_args + + args["load"] = args["settings"] + if args["gui"] is not None: + for k in args["gui"]: + if k not in args["load"] or args["load"][k] != args["gui"]: + args["load"][k] = args["gui"][k] + + if args["cli"] is None: + args["cli"] = {} + cli = vars(parse_arguments(None)) + for k,v in cli.items(): + if isinstance(v,dict): + args["cli"][k] = v[1] + else: + args["cli"][k] = v + if k not in args["load"] or args["load"][k] != args["cli"]: + args["load"][k] = args["cli"][k] + + return args diff --git a/DungeonRandomizer.py b/DungeonRandomizer.py index f7b481c3..d872ed01 100755 --- a/DungeonRandomizer.py +++ b/DungeonRandomizer.py @@ -59,7 +59,8 @@ def start(): seed = random.randint(0, 999999999) for fail in failures: logger.info('%s seed failed with: %s', fail[1], fail[0]) - logger.info('Generation fail rate: %f%%', 100*len(failures)/args.count) + logger.info('Generation fail rate: %f%%', 100 * len(failures) / args.count) + logger.info('Generation success rate: %f%%', 100 * (args.count - len(failures)) / args.count) else: main(seed=args.seed, args=args) diff --git a/Gui.py b/Gui.py index b824606f..40605db6 100755 --- a/Gui.py +++ b/Gui.py @@ -2,10 +2,10 @@ import json import os import sys -from tkinter import Tk, BOTTOM, TOP, StringVar, BooleanVar, X, BOTH, ttk +from tkinter import Tk, Button, BOTTOM, TOP, StringVar, BooleanVar, X, BOTH, RIGHT, ttk, messagebox from argparse import Namespace -from CLI import get_settings +from CLI import get_settings, get_args_priority from DungeonRandomizer import parse_arguments from gui.adjust.overview import adjust_page from gui.startinventory.overview import startinventory_page @@ -36,28 +36,39 @@ def guiMain(args=None): f.write(json.dumps(args, indent=2)) os.chmod(os.path.join(settings_path, "settings.json"),0o755) - # routine for exiting the app - def guiExit(): + def save_settings_from_gui(): gui_args = vars(create_guiargs(self)) if self.randomSprite.get(): gui_args['sprite'] = 'random' elif gui_args['sprite']: gui_args['sprite'] = gui_args['sprite'].name save_settings(gui_args) + messagebox.showinfo("Door Shuffle " + ESVersion,"Settings saved from GUI.") + + # routine for exiting the app + def guiExit(): + dosave = messagebox.askyesno("Door Shuffle " + ESVersion, "Save settings before exit?") + if dosave: + save_settings_from_gui() sys.exit(0) # make main window # add program title & version number mainWindow = Tk() self = mainWindow + mainWindow.wm_title("Door Shuffle %s" % ESVersion) mainWindow.protocol("WM_DELETE_WINDOW", guiExit) # intercept when user clicks the X # set program icon set_icon(mainWindow) + # get args + # getting Settings & CLI (no GUI built yet) + self.args = get_args_priority(None, None, None) + # get saved settings - self.settings = get_settings() + self.settings = self.args["settings"] # make array for pages self.pages = {} @@ -73,7 +84,7 @@ def guiMain(args=None): self.notebook.add(self.pages["randomizer"], text='Randomize') self.notebook.add(self.pages["adjust"], text='Adjust') self.notebook.add(self.pages["startinventory"], text='Starting Inventory') - self.notebook.add(self.pages["custom"], text='Custom') + self.notebook.add(self.pages["custom"], text='Custom Item Pool') self.notebook.pack() # randomizer controls @@ -125,6 +136,10 @@ def guiMain(args=None): # bottom of window: Open Output Directory, Open Documentation (if exists) self.frames["bottom"] = bottom_frame(self, self, None) + ## Save Settings Button + savesettingsButton = Button(self.frames["bottom"], text='Save Settings to File', command=save_settings_from_gui) + savesettingsButton.pack(side=RIGHT) + # set bottom frame to main window self.frames["bottom"].pack(side=BOTTOM, fill=X, padx=5, pady=5) @@ -141,7 +156,7 @@ def guiMain(args=None): # Custom Controls self.pages["custom"].content = custom_page(self,self.pages["custom"]) - self.pages["custom"].content.pack(side=TOP, pady=(17,0)) + self.pages["custom"].content.pack(side=TOP, fill=BOTH, expand=True) def validation(P): if str.isdigit(P) or P == "": @@ -150,12 +165,12 @@ def guiMain(args=None): return False vcmd=(self.pages["custom"].content.register(validation), '%P') + # load args + loadcliargs(self, self.args["load"]) + # load adjust settings into options loadadjustargs(self, self.settings) - # load args from CLI into options - loadcliargs(self, args, self.settings) - mainWindow.mainloop() diff --git a/classes/constants.py b/classes/constants.py index 0069c1e5..90434970 100644 --- a/classes/constants.py +++ b/classes/constants.py @@ -20,6 +20,11 @@ CUSTOMITEMS = [ "rupoorcost" ] +CANTSTARTWITH = [ + "triforcepiecesgoal", "triforce", "rupoor", + "rupoorcost" +] + CUSTOMITEMLABELS = [ "Bow", "Progressive Bow", "Blue Boomerang", "Red Boomerang", "Hookshot", "Mushroom", "Magic Powder", "Fire Rod", "Ice Rod", "Bombos", @@ -41,3 +46,63 @@ CUSTOMITEMLABELS = [ "Small Key (Universal)", "Triforce Piece", "Triforce Piece Goal", "Triforce", "Rupoor", "Rupoor Cost" ] + +SETTINGSTOPROCESS = { + "randomizer": { + "item": { + "retro": "retro", + "worldstate": "mode", + "logiclevel": "logic", + "goal": "goal", + "crystals_gt": "crystals_gt", + "crystals_ganon": "crystals_ganon", + "weapons": "swords", + "itempool": "difficulty", + "itemfunction": "item_functionality", + "timer": "timer", + "progressives": "progressive", + "accessibility": "accessibility", + "sortingalgo": "algorithm" + }, + "entrance": { + "openpyramid": "openpyramid", + "shuffleganon": "shuffleganon", + "entranceshuffle": "shuffle" + }, + "enemizer": { + "potshuffle": "shufflepots", + "enemyshuffle": "shuffleenemies", + "bossshuffle": "shufflebosses", + "enemydamage": "enemy_damage", + "enemyhealth": "enemy_health" + }, + "dungeon": { + "mapshuffle": "mapshuffle", + "compassshuffle": "compassshuffle", + "smallkeyshuffle": "keyshuffle", + "bigkeyshuffle": "bigkeyshuffle", + "dungeondoorshuffle": "door_shuffle", + "experimental": "experimental", + "dungeon_counters": "dungeon_counters" + }, + "multiworld": { + "names": "names" + }, + "gameoptions": { + "hints": "hints", + "nobgm": "disablemusic", + "quickswap": "quickswap", + "heartcolor": "heartcolor", + "heartbeep": "heartbeep", + "menuspeed": "fastmenu", + "owpalettes": "ow_palettes", + "uwpalettes": "uw_palettes" + }, + "generation": { + "spoiler": "create_spoiler", + "suppressrom": "suppress_rom", + "usestartinventory": "usestartinventory", + "usecustompool": "custom" + } + } +} diff --git a/gui/bottom.py b/gui/bottom.py index cea47a56..22d94d35 100644 --- a/gui/bottom.py +++ b/gui/bottom.py @@ -1,5 +1,6 @@ from tkinter import ttk, messagebox, StringVar, Button, Entry, Frame, Label, Spinbox, E, W, LEFT, RIGHT, X from argparse import Namespace +from functools import partial import logging import os import random @@ -79,11 +80,12 @@ def bottom_frame(self, parent, args=None): openOutputButton = Button(self, text='Open Output Directory', command=open_output) openOutputButton.pack(side=RIGHT) + ## Documentation Button if os.path.exists(local_path('README.html')): def open_readme(): open_file(local_path('README.html')) openReadmeButton = Button(self, text='Open Documentation', command=open_readme) - openReadmeButton.pack() + openReadmeButton.pack(side=RIGHT) return self @@ -93,63 +95,8 @@ def create_guiargs(parent): # set up settings to gather # Page::Subpage::GUI-id::param-id - options = { - "randomizer": { - "item": { - "retro": "retro", - "worldstate": "mode", - "logiclevel": "logic", - "goal": "goal", - "crystals_gt": "crystals_gt", - "crystals_ganon": "crystals_ganon", - "weapons": "swords", - "itempool": "difficulty", - "itemfunction": "item_functionality", - "timer": "timer", - "progressives": "progressive", - "accessibility": "accessibility", - "sortingalgo": "algorithm" - }, - "entrance": { - "openpyramid": "openpyramid", - "shuffleganon": "shuffleganon", - "entranceshuffle": "shuffle" - }, - "enemizer": { - "potshuffle": "shufflepots", - "enemyshuffle": "shuffleenemies", - "bossshuffle": "shufflebosses", - "enemydamage": "enemy_damage", - "enemyhealth": "enemy_health" - }, - "dungeon": { - "mapshuffle": "mapshuffle", - "compassshuffle": "compassshuffle", - "smallkeyshuffle": "keyshuffle", - "bigkeyshuffle": "bigkeyshuffle", - "dungeondoorshuffle": "door_shuffle", - "experimental": "experimental", - "dungeon_counters": "dungeon_counters" - }, - "multiworld": { - "names": "names" - }, - "gameoptions": { - "hints": "hints", - "nobgm": "disablemusic", - "quickswap": "quickswap", - "heartcolor": "heartcolor", - "heartbeep": "heartbeep", - "menuspeed": "fastmenu", - "owpalettes": "ow_palettes", - "uwpalettes": "uw_palettes" - }, - "generation": { - "spoiler": "create_spoiler", - "suppressrom": "suppress_rom" - } - } - } + options = CONST.SETTINGSTOPROCESS + for mainpage in options: for subpage in options[mainpage]: for widget in options[mainpage][subpage]: @@ -184,7 +131,7 @@ def create_guiargs(parent): guiargs.customitemarray = {} guiargs.startinventoryarray = {} for customitem in customitems: - if customitem not in ["triforcepiecesgoal", "rupoorcost"]: + if customitem not in ["triforcepiecesgoal", "triforce", "rupoor", "rupoorcost"]: amount = int(parent.pages["startinventory"].content.startingWidgets[customitem].storageVar.get()) guiargs.startinventoryarray[customitem] = amount for i in range(0, amount): diff --git a/gui/loadcliargs.py b/gui/loadcliargs.py index 5fa012bc..14a8b007 100644 --- a/gui/loadcliargs.py +++ b/gui/loadcliargs.py @@ -1,96 +1,45 @@ from classes.SpriteSelector import SpriteSelector as spriteSelector from gui.randomize.gameoptions import set_sprite from Rom import Sprite - +import classes.constants as CONST def loadcliargs(gui, args, settings=None): if args is not None: - for k, v in vars(args).items(): - if type(v) is dict: - setattr(args, k, v[1]) # only get values for player 1 for now +# for k, v in vars(args).items(): +# if type(v) is dict: +# setattr(args, k, v[1]) # only get values for player 1 for now # load values from commandline args # set up options to get # Page::Subpage::GUI-id::param-id - options = { - "randomizer": { - "item": { - "retro": "retro", - "worldstate": "mode", - "logiclevel": "logic", - "goal": "goal", - "crystals_gt": "crystals_gt", - "crystals_ganon": "crystals_ganon", - "weapons": "swords", - "itempool": "difficulty", - "itemfunction": "item_functionality", - "timer": "timer", - "progressives": "progressive", - "accessibility": "accessibility", - "sortingalgo": "algorithm" - }, - "entrance": { - "openpyramid": "openpyramid", - "shuffleganon": "shuffleganon", - "entranceshuffle": "shuffle" - }, - "enemizer": { - "potshuffle": "shufflepots", - "enemyshuffle": "shuffleenemies", - "bossshuffle": "shufflebosses", - "enemydamage": "enemy_damage", - "enemyhealth": "enemy_health" - }, - "dungeon": { - "mapshuffle": "mapshuffle", - "compassshuffle": "compassshuffle", - "smallkeyshuffle": "keyshuffle", - "bigkeyshuffle": "bigkeyshuffle", - "dungeondoorshuffle": "door_shuffle", - "experimental": "experimental" - }, - "gameoptions": { - "hints": "hints", - "nobgm": "disablemusic", - "quickswap": "quickswap", - "heartcolor": "heartcolor", - "heartbeep": "heartbeep", - "menuspeed": "fastmenu", - "owpalettes": "ow_palettes", - "uwpalettes": "uw_palettes" - }, - "generation": { - "spoiler": "create_spoiler", - "suppressrom": "suppress_rom" - } - } - } + options = CONST.SETTINGSTOPROCESS + for mainpage in options: for subpage in options[mainpage]: for widget in options[mainpage][subpage]: arg = options[mainpage][subpage][widget] - gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(getattr(args, arg)) + gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg]) if subpage == "gameoptions" and not widget == "hints": hasSettings = settings is not None hasWidget = ("adjust." + widget) in settings if hasSettings else None if hasWidget is None: - gui.pages["adjust"].content.widgets[widget].storageVar.set(getattr(args, arg)) + gui.pages["adjust"].content.widgets[widget].storageVar.set(args[arg]) - gui.pages["randomizer"].pages["enemizer"].enemizerCLIpathVar.set(args.enemizercli) - gui.pages["randomizer"].pages["generation"].romVar.set(args.rom) + gui.pages["randomizer"].pages["enemizer"].enemizerCLIpathVar.set(args["enemizercli"]) + gui.pages["randomizer"].pages["generation"].romVar.set(args["rom"]) - if args.multi: - gui.pages["randomizer"].pages["multiworld"].widgets["worlds"].storageVar.set(str(args.multi)) - if args.seed: - gui.frames["bottom"].seedVar.set(str(args.seed)) - if args.count: - gui.frames["bottom"].widgets["generationcount"].storageVar.set(str(args.count)) - gui.outputPath.set(args.outputpath) + if args["multi"]: + gui.pages["randomizer"].pages["multiworld"].widgets["worlds"].storageVar.set(str(args["multi"])) + if args["seed"]: + gui.frames["bottom"].seedVar.set(str(args["seed"])) + if args["count"]: + gui.frames["bottom"].widgets["generationcount"].storageVar.set(str(args["count"])) + gui.outputPath.set(args["outputpath"]) def sprite_setter(spriteObject): gui.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteObject"] = spriteObject - if args.sprite is not None: - sprite_obj = args.sprite if isinstance(args.sprite, Sprite) else Sprite(args.sprite) + if args["sprite"] is not None: + sprite_obj = args.sprite if isinstance(args["sprite"], Sprite) else Sprite(args["sprite"]) r_sprite_flag = args.randomSprite if hasattr(args, 'randomSprite') else False set_sprite(sprite_obj, r_sprite_flag, spriteSetter=sprite_setter, spriteNameVar=gui.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteNameVar"], @@ -98,9 +47,9 @@ def loadcliargs(gui, args, settings=None): def sprite_setter_adj(spriteObject): gui.pages["adjust"].content.sprite = spriteObject - if args.sprite is not None: + if args["sprite"] is not None: sprite_obj = args.sprite if isinstance(args.sprite, Sprite) else Sprite(args.sprite) - r_sprite_flag = args.randomSprite if hasattr(args, 'randomSprite') else False + r_sprite_flag = args["randomSprite"] if hasattr(args, 'randomSprite') else False set_sprite(sprite_obj, r_sprite_flag, spriteSetter=sprite_setter_adj, spriteNameVar=gui.pages["adjust"].content.spriteNameVar2, randomSpriteVar=gui.randomSprite) diff --git a/gui/startinventory/overview.py b/gui/startinventory/overview.py index 4d177865..aaa601b3 100644 --- a/gui/startinventory/overview.py +++ b/gui/startinventory/overview.py @@ -43,15 +43,18 @@ def startinventory_page(top,parent): with open(os.path.join("resources","app","gui","custom","overview","widgets.json")) as widgetDefns: myDict = json.load(widgetDefns) - del myDict["itemList5"]["triforcepiecesgoal"] - del myDict["itemList5"]["rupoorcost"] + for key in CONST.CANTSTARTWITH: + for num in range(1, 5 + 1): + thisList = "itemList" + str(num) + if key in myDict[thisList]: + del myDict[thisList][key] for framename,theseWidgets in myDict.items(): dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) for key in dictWidgets: self.startingWidgets[key] = dictWidgets[key] for key in CONST.CUSTOMITEMS: - if key not in ["triforcepiecesgoal", "rupoorcost"]: + if key not in CONST.CANTSTARTWITH: val = 0 if key in top.settings["startinventoryarray"]: val = top.settings["startinventoryarray"][key]