Style fixes

A bunch of style fixes. Mostly white space and style import order
tweaks, but a few other stylistic changes are present too.
This commit is contained in:
Kevin Cathcart 2017-12-17 00:25:46 -05:00
parent 8b20e39588
commit 823657bc26
18 changed files with 224 additions and 212 deletions

View File

@ -11,8 +11,7 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
def _get_help_string(self, action):
return textwrap.dedent(action.help)
if __name__ == '__main__':
def main():
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('--rom', default='ER_base.sfc', help='Path to an ALttP JAP(1.0) rom to use as a base.')
@ -47,3 +46,6 @@ if __name__ == '__main__':
logging.basicConfig(format='%(message)s', level=loglevel)
adjust(args=args)
if __name__ == '__main__':
main()

View File

@ -1,10 +1,9 @@
from collections import OrderedDict
from Utils import output_path
import os
import time
import logging
from Rom import LocalRom, apply_rom_settings
from Utils import output_path
from Rom import LocalRom, Sprite, apply_rom_settings
def adjust(args):
@ -22,7 +21,7 @@ def adjust(args):
outfilebase = 'ER_adjusted'
if (os.stat(args.rom).st_size == 2097152 and os.path.splitext(args.rom)[-1].lower() == '.sfc'):
if os.stat(args.rom).st_size == 2097152 and os.path.splitext(args.rom)[-1].lower() == '.sfc':
rom = LocalRom(args.rom, False)
else:
raise RuntimeError('Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.')
@ -32,6 +31,6 @@ def adjust(args):
rom.write_to_file(output_path('%s.sfc' % outfilebase))
logger.info('Done. Enjoy.')
logger.debug('Total Time: %s' % (time.clock() - start))
logger.debug('Total Time: %s', time.clock() - start)
return args

View File

@ -26,7 +26,6 @@ class World(object):
self._region_cache = {}
self._entrance_cache = {}
self._location_cache = {}
self._item_cache = {}
self.required_locations = []
self.place_dungeon_items = place_dungeon_items # configurable in future
self.shuffle_bonk_prizes = False
@ -142,7 +141,7 @@ class World(object):
'Small Key (Swamp Palace)', 'Big Key (Ice Palace)'] + ['Small Key (Ice Palace)'] * 2 + ['Big Key (Misery Mire)', 'Big Key (Turtle Rock)', 'Big Key (Ganons Tower)'] + ['Small Key (Misery Mire)'] * 3 + ['Small Key (Turtle Rock)'] * 4 + ['Small Key (Ganons Tower)'] * 4):
soft_collect(item)
ret.sweep_for_events()
ret._clear_cache()
ret.clear_cached_unreachable()
return ret
def find_items(self, item):
@ -158,7 +157,7 @@ class World(object):
if collect:
self.state.collect(item, location.event)
logging.getLogger('').debug('Placed %s at %s' % (item, location))
logging.getLogger('').debug('Placed %s at %s', item, location)
else:
raise RuntimeError('Cannot assign item %s to location %s.' % (item, location))
@ -187,7 +186,7 @@ class World(object):
def unlocks_new_location(self, item):
temp_state = self.state.copy()
temp_state._clear_cache()
temp_state.clear_cached_unreachable()
temp_state.collect(item, True)
for location in self.get_unfilled_locations():
@ -197,7 +196,8 @@ class World(object):
return False
def has_beaten_game(self, state):
if state.has('Triforce'): return True
if state.has('Triforce'):
return True
if self.goal in ['triforcehunt']:
if state.item_count('Triforce Piece') + state.item_count('Power Star') > self.treasure_hunt_count:
return True
@ -262,7 +262,7 @@ class CollectionState(object):
self.recursion_count = 0
self.events = []
def _clear_cache(self):
def clear_cached_unreachable(self):
# we only need to invalidate results which were False, places we could reach before we can still reach after adding more items
self.region_cache = {k: v for k, v in self.region_cache.items() if v}
self.location_cache = {k: v for k, v in self.location_cache.items() if v}
@ -337,7 +337,6 @@ class CollectionState(object):
def has(self, item, count=1):
if count == 1:
return item in self.prog_items
else:
return self.item_count(item) >= count
def item_count(self, item):
@ -424,10 +423,10 @@ class CollectionState(object):
changed = True
if changed:
self._clear_cache()
self.clear_cached_unreachable()
if not event:
self.sweep_for_events()
self._clear_cache()
self.clear_cached_unreachable()
def remove(self, item):
if item.advancement:
@ -497,10 +496,7 @@ class Region(object):
is_dungeon_item = item.key or item.map or item.compass
sewer_hack = self.world.mode == 'standard' and item.name == 'Small Key (Escape)'
if sewer_hack or (is_dungeon_item and not self.world.keysanity):
if self.dungeon and self.dungeon.is_dungeon_item(item):
return True
else:
return False
return self.dungeon and self.dungeon.is_dungeon_item(item)
return True
@ -522,9 +518,7 @@ class Entrance(object):
self.spot_type = 'Entrance'
self.recursion_count = 0
self.vanilla = None
def access_rule(self, state):
return True
self.access_rule = lambda state: True
def can_reach(self, state):
if self.access_rule(state) and state.can_reach(self.parent_region):
@ -586,12 +580,8 @@ class Location(object):
self.recursion_count = 0
self.staleness_count = 0
self.event = False
def access_rule(self, state):
return True
def item_rule(self, item):
return True
self.access_rule = lambda state: True
self.item_rule = lambda state: True
def can_fill(self, item):
return self.parent_region.can_fill(item) and self.item_rule(item)

View File

@ -1,7 +1,8 @@
from Items import ItemFactory
import random
from BaseClasses import Dungeon
from Fill import fill_restrictive
import random
from Items import ItemFactory
def create_dungeons(world):
@ -62,7 +63,7 @@ def fill_dungeons(world):
world.push_item(bk_location, big_key, False)
bk_location.event = True
dungeon_locations.remove(bk_location)
all_state._clear_cache()
all_state.clear_cached_unreachable()
big_key = None
# next place small keys
@ -88,7 +89,7 @@ def fill_dungeons(world):
world.push_item(sk_location, small_key, False)
sk_location.event = True
dungeon_locations.remove(sk_location)
all_state._clear_cache()
all_state.clear_cached_unreachable()
if small_keys:
# key placement not finished, loop again
@ -100,7 +101,7 @@ def fill_dungeons(world):
di_location = dungeon_locations.pop()
world.push_item(di_location, dungeon_item, False)
world.state._clear_cache()
world.state.clear_cached_unreachable()
def fill_dungeons_restrictive(world, shuffled_locations):
@ -119,7 +120,7 @@ def fill_dungeons_restrictive(world, shuffled_locations):
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items)
world.state._clear_cache()
world.state.clear_cached_unreachable()
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],

View File

@ -5,8 +5,8 @@ import random
import textwrap
import sys
from Main import main
from Gui import guiMain
from Main import main
from Utils import is_bundled, close_console
@ -16,7 +16,7 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
return textwrap.dedent(action.help)
if __name__ == '__main__':
def start():
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true')
parser.add_argument('--logic', default='noglitches', const='noglitches', nargs='?', choices=['noglitches', 'minorglitches'],
@ -219,8 +219,11 @@ if __name__ == '__main__':
guiMain(args)
elif args.count is not None:
seed = args.seed
for i in range(args.count):
for _ in range(args.count):
main(seed=seed, args=args)
seed = random.randint(0, 999999999)
else:
main(seed=args.seed, args=args)
if __name__ == '__main__':
start()

View File

@ -56,7 +56,7 @@ def distribute_items_cutoff(world, cutoffrate=0.33):
raise RuntimeError('No more progress items left to place.')
spot_to_fill = None
for location in (fill_locations if placed_advancement_items / total_advancement_items < cutoffrate else reversed(fill_locations)):
for location in fill_locations if placed_advancement_items / total_advancement_items < cutoffrate else reversed(fill_locations):
if world.state.can_reach(location) and location.can_fill(item_to_place):
spot_to_fill = location
break
@ -72,7 +72,7 @@ def distribute_items_cutoff(world, cutoffrate=0.33):
itempool.remove(item_to_place)
fill_locations.remove(spot_to_fill)
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s' % ([item.name for item in itempool], [location.name for location in fill_locations]))
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s', [item.name for item in itempool], [location.name for location in fill_locations])
def distribute_items_staleness(world):
@ -153,7 +153,7 @@ def distribute_items_staleness(world):
itempool.remove(item_to_place)
fill_locations.remove(spot_to_fill)
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s' % ([item.name for item in itempool], [location.name for location in fill_locations]))
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s', [item.name for item in itempool], [location.name for location in fill_locations])
def fill_restrictive(world, base_state, locations, itempool):
@ -226,7 +226,7 @@ def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=No
fast_fill(world, restitempool, fill_locations)
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s' % ([item.name for item in progitempool + prioitempool + restitempool], [location.name for location in fill_locations]))
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s', [item.name for item in progitempool + prioitempool + restitempool], [location.name for location in fill_locations])
def fast_fill(world, item_pool, fill_locations):

24
Gui.py
View File

@ -4,7 +4,7 @@ import json
import random
import os
import shutil
from tkinter import Checkbutton, OptionMenu, Toplevel, LabelFrame, PhotoImage, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, Y, Entry, Spinbox, Button, filedialog, messagebox, ttk
from tkinter import Checkbutton, OptionMenu, Toplevel, LabelFrame, PhotoImage, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, Entry, Spinbox, Button, filedialog, messagebox, ttk
from urllib.parse import urlparse
from urllib.request import urlopen
@ -250,7 +250,7 @@ def guiMain(args=None):
try:
if guiargs.count is not None:
seed = guiargs.seed
for i in range(guiargs.count):
for _ in range(guiargs.count):
main(seed=seed, args=guiargs)
seed = random.randint(0, 999999999)
else:
@ -425,15 +425,16 @@ class SpriteSelector(object):
for file in glob(output_path(path)):
sprite = Sprite(file)
image = get_image_for_sprite(sprite)
if image is None: continue
button = Button(frame, image=image, command=lambda sprite=sprite: self.select_sprite(sprite))
if image is None:
continue
button = Button(frame, image=image, command=lambda spr=sprite: self.select_sprite(spr))
ToolTips.register(button, sprite.name + ("\nBy: %s" % sprite.author_name if sprite.author_name is not None else ""))
button.image = image
button.grid(row=i // 16, column=i % 16)
i += 1
if i == 0:
label = Label(frame, text="Put sprites in the Sprites/Unoffical folder to have them appear here.")
label = Label(frame, text=no_results_label)
label.pack()
@ -511,7 +512,7 @@ class SpriteSelector(object):
sprite = filedialog.askopenfilename()
try:
self.callback(Sprite(sprite))
except:
except Exception:
self.callback(None)
self.window.destroy()
@ -535,22 +536,20 @@ class SpriteSelector(object):
def official_sprite_dir(self):
if is_bundled():
return output_path("sprites/official")
else:
return self.local_official_sprite_dir
@property
def local_official_sprite_dir(site):
def local_official_sprite_dir(self):
return local_path("data/sprites/official")
@property
def unofficial_sprite_dir(self):
if is_bundled():
return output_path("sprites/unofficial")
else:
return self.local_unofficial_sprite_dir
@property
def local_unofficial_sprite_dir(site):
def local_unofficial_sprite_dir(self):
return local_path("data/sprites/unofficial")
@ -563,9 +562,8 @@ def get_image_for_sprite(sprite):
def draw_sprite_into_gif(add_palette_color, set_pixel_color_index):
def drawsprite(spr, pal_as_colors, offset):
for y in range(len(spr)):
for x in range(len(spr[y])):
pal_index = spr[y][x]
for y, row in enumerate(spr):
for x, pal_index in enumerate(row):
if pal_index:
color = pal_as_colors[pal_index - 1]
set_pixel_color_index(x + offset[0], y + offset[1], color)

View File

@ -8,7 +8,7 @@ def set_icon(window):
er16 = tk.PhotoImage(file=local_path('data/ER16.gif'))
er32 = tk.PhotoImage(file=local_path('data/ER32.gif'))
er48 = tk.PhotoImage(file=local_path('data/ER32.gif'))
window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48)
window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48) # pylint: disable=protected-access
# Although tkinter is intended to be thread safe, there are many reports of issues
# some which may be platform specific, or depend on if the TCL library was compiled without
@ -54,9 +54,9 @@ class BackgroundTaskProgress(BackgroundTask):
self.window.attributes("-toolwindow", 1)
self.window.wm_title(title)
self.labelVar = tk.StringVar()
self.labelVar.set("")
self.label = tk.Label(self.window, textvariable = self.labelVar, width=50)
self.label_var = tk.StringVar()
self.label_var.set("")
self.label = tk.Label(self.window, textvariable=self.label_var, width=50)
self.label.pack()
self.window.resizable(width=False, height=False)
@ -66,7 +66,7 @@ class BackgroundTaskProgress(BackgroundTask):
#safe to call from worker thread
def update_status(self, text):
self.queue_event(lambda text=text: self.labelVar.set(text))
self.queue_event(lambda: self.label_var.set(text))
# only call this in an event callback
def close_window(self):
@ -123,7 +123,7 @@ class ToolTips(object):
cls.fg = "systeminfotext"
widget.winfo_rgb(cls.fg) # make sure system colors exist
widget.winfo_rgb(cls.bg)
except:
except Exception:
cls.bg = "#ffffe0"
cls.fg = "black"
@ -162,7 +162,6 @@ class ToolTips(object):
@classmethod
def motion(cls, event):
widget = event.widget
cls.xy = event.x_root + 16, event.y_root + 10
cls.event_xy = event.x, event.y

View File

@ -1,8 +1,9 @@
from Items import ItemFactory
from Fill import fill_restrictive
from collections import namedtuple
import random
from Items import ItemFactory
from Fill import fill_restrictive
#This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
#Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
@ -62,22 +63,20 @@ Difficulty = namedtuple('Difficulty',
'triforcehunt', 'triforce_pieces_required', 'conditional_extras',
'extras'])
TotalItemsToPlace = 153
total_items_to_place = 153
def easy_conditional_extras(timer, goal, mode, pool, placed_items):
extraitems = TotalItemsToPlace - len(pool) - len(placed_items)
def easy_conditional_extras(timer, _goal, _mode, pool, placed_items):
extraitems = total_items_to_place - len(pool) - len(placed_items)
if extraitems < len(easyextra):
return easylimitedextra
if timer in ['timed', 'timed-countdown']:
return easytimedotherextra
return []
def no_conditonal_extras(*args):
def no_conditonal_extras(*_args):
return []
#def Difficulty(**kwargs):
# protodifficulty._replace(**kwargs)
# pylint: disable=
difficulties = {
'normal': Difficulty(
baseitems = normalbaseitems,
@ -226,10 +225,10 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
pool.extend(alwaysitems)
def wantProgressives():
def want_progressives():
return random.choice([True, False]) if progressive == 'random' else progressive == 'on'
if wantProgressives():
if want_progressives():
pool.extend(progressivegloves)
else:
pool.extend(basicgloves)
@ -253,17 +252,17 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
# all bottles, since only one bottle is available
if diff.same_bottle:
thisbottle = random.choice(diff.bottles)
for i in range (diff.bottle_count):
for _ in range(diff.bottle_count):
if not diff.same_bottle:
thisbottle = random.choice(diff.bottles)
pool.append(thisbottle)
if wantProgressives():
if want_progressives():
pool.extend(diff.progressiveshield)
else:
pool.extend(diff.basicshield)
if wantProgressives():
if want_progressives():
pool.extend(diff.progressivearmor)
else:
pool.extend(diff.basicarmor)
@ -271,21 +270,21 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
if mode == 'swordless':
pool.extend(diff.swordless)
elif mode == 'standard':
if wantProgressives():
if want_progressives():
placed_items.append(('Link\'s Uncle', 'Progressive Sword'))
pool.extend(diff.progressivesword)
else:
placed_items.append(('Link\'s Uncle', 'Fighter Sword'))
pool.extend(diff.basicsword)
else:
if wantProgressives():
if want_progressives():
pool.extend(diff.progressivesword)
pool.extend(['Progressive Sword'])
else:
pool.extend(diff.basicsword)
pool.extend(['Fighter Sword'])
extraitems = TotalItemsToPlace - len(pool) - len(placed_items)
extraitems = total_items_to_place - len(pool) - len(placed_items)
if timer in ['timed', 'timed-countdown']:
pool.extend(diff.timedother)
@ -306,7 +305,7 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
extraitems -= len(cond_extras)
for extra in diff.extras:
if(extraitems > 0):
if extraitems > 0:
pool.extend(extra)
extraitems -= len(extra)
@ -315,7 +314,7 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
return (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon)
# A quick test to ensure all combinations generate the correct amount of items.
if __name__ == '__main__':
def test():
for difficulty in ['easy', 'normal', 'hard', 'expert', 'insane']:
for goal in ['ganon', 'triforcehunt', 'pedestal']:
for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']:
@ -325,9 +324,12 @@ if __name__ == '__main__':
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode)
count = len(out[0]) + len(out[1])
correct_count = TotalItemsToPlace
correct_count = total_items_to_place
if goal in ['pedestal']:
# pedestal goals generate one extra item
correct_count += 1
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode))
if __name__ == '__main__':
test()

View File

@ -1,7 +1,7 @@
from BaseClasses import World, Item
import random
import logging
from BaseClasses import Item
def ItemFactory(items):
ret = []
@ -14,12 +14,11 @@ def ItemFactory(items):
advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit = item_table[item]
ret.append(Item(item, advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit))
else:
logging.getLogger('').warning('Unknown Item: %s' % item)
logging.getLogger('').warning('Unknown Item: %s', item)
return None
if singleton:
return ret[0]
else:
return ret

25
Main.py
View File

@ -1,18 +1,18 @@
from collections import OrderedDict
import json
import logging
import random
import time
from BaseClasses import World, CollectionState, Item
from Regions import create_regions, mark_light_world_regions
from EntranceShuffle import link_entrances
from Rom import patch_rom, Sprite, LocalRom, JsonRom
from Rules import set_rules
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
from Items import ItemFactory
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, fill_restrictive, flood_items
from collections import OrderedDict
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items
from ItemList import generate_itempool
from Utils import output_path
import random
import time
import logging
import json
__version__ = '0.5.1-dev'
@ -39,7 +39,7 @@ def main(args, seed=None):
world.seed = int(seed)
random.seed(world.seed)
logger.info('ALttP Entrance Randomizer Version %s - Seed: %s\n\n' % (__version__, world.seed))
logger.info('ALttP Entrance Randomizer Version %s - Seed: %s\n\n', __version__, world.seed)
create_regions(world)
@ -116,7 +116,7 @@ def main(args, seed=None):
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
logger.info('Done. Enjoy.')
logger.debug('Total Time: %s' % (time.clock() - start))
logger.debug('Total Time: %s', time.clock() - start)
return world
@ -199,10 +199,10 @@ def create_playthrough(world):
collection_spheres.append(sphere)
logging.getLogger('').debug('Calculated sphere %i, containing %i of %i progress items.' % (len(collection_spheres), len(sphere), len(prog_locations)))
logging.getLogger('').debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(prog_locations))
if not sphere:
logging.getLogger('').debug('The following items could not be reached: %s' % ['%s at %s' % (location.item.name, location.name) for location in sphere_candidates])
logging.getLogger('').debug('The following items could not be reached: %s', ['%s at %s' % (location.item.name, location.name) for location in sphere_candidates])
if not world.check_beatable_only:
raise RuntimeError('Not all progression items reachable. Something went terribly wrong here.')
else:
@ -213,11 +213,10 @@ def create_playthrough(world):
to_delete = []
for location in sphere:
# we remove the item at location and check if game is still beatable
logging.getLogger('').debug('Checking if %s is required to beat the game.' % location.item.name)
logging.getLogger('').debug('Checking if %s is required to beat the game.', location.item.name)
old_item = location.item
location.item = None
state.remove(old_item)
world._item_cache = {} # need to invalidate
if world.can_beat_game():
to_delete.append(location)
else:

View File

@ -1,18 +1,19 @@
import argparse
import hashlib
import logging
import os
import random
import time
import sys
from BaseClasses import World
from Regions import create_regions
from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit
from Rom import patch_rom, LocalRom, Sprite, write_string_to_rom
from Rom import patch_rom, LocalRom, write_string_to_rom
from Rules import set_rules
from Dungeons import create_dungeons
from Items import ItemFactory
from Main import create_playthrough
import random
import time
import logging
import argparse
import os
import hashlib
import sys
__version__ = '0.2-dev'
@ -26,8 +27,8 @@ logic_hash = [182, 244, 144, 92, 149, 200, 93, 183, 124, 169, 226, 46, 111, 163,
153, 217, 252, 158, 25, 205, 22, 133, 254, 138, 203, 118, 210, 204, 82, 97, 52, 164, 68, 139, 120, 109, 54, 3, 41, 179, 212, 42]
def main(args, seed=None):
start = time.clock()
def main(args):
start_time = time.clock()
# initialize the world
world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False)
@ -41,7 +42,7 @@ def main(args, seed=None):
random.seed(world.seed)
logger.info('ALttP Plandomizer Version %s - Seed: %s\n\n' % (__version__, args.plando))
logger.info('ALttP Plandomizer Version %s - Seed: %s\n\n', __version__, args.plando)
create_regions(world)
create_dungeons(world)
@ -74,7 +75,7 @@ def main(args, seed=None):
logger.info('Patching ROM.')
if args.sprite is not None:
sprite = Sprite(args.sprite)
sprite = bytearray(open(args.sprite, 'rb').read())
else:
sprite = None
@ -84,8 +85,8 @@ def main(args, seed=None):
for textname, texttype, text in text_patches:
if texttype == 'text':
write_string_to_rom(rom, textname, text)
elif texttype == 'credit':
write_credits_string_to_rom(rom, textname, text)
#elif texttype == 'credit':
# write_credits_string_to_rom(rom, textname, text)
outfilebase = 'Plando_%s_%s' % (os.path.splitext(os.path.basename(args.plando))[0], world.seed)
@ -94,7 +95,7 @@ def main(args, seed=None):
world.spoiler.to_file('%s_Spoiler.txt' % outfilebase)
logger.info('Done. Enjoy.')
logger.debug('Total Time: %s' % (time.clock() - start))
logger.debug('Total Time: %s', time.clock() - start_time)
return world
@ -171,7 +172,7 @@ def fill_world(world, plando, text_patches):
locationstr, itemstr = line.split(':', 1)
location = world.get_location(locationstr.strip())
if location is None:
logger.warn('Unknown location: %s' % locationstr)
logger.warning('Unknown location: %s', locationstr)
continue
else:
item = ItemFactory(itemstr.strip())
@ -198,7 +199,7 @@ def fill_world(world, plando, text_patches):
world.get_location('Agahnim 2').item = ItemFactory('Beat Agahnim 2')
if __name__ == '__main__':
def start():
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true')
parser.add_argument('--ignore_unsolvable', help='Do not abort if seed is deemed unsolvable.', action='store_true')
@ -230,3 +231,6 @@ if __name__ == '__main__':
logging.basicConfig(format='%(message)s', level=loglevel)
main(args=args)
if __name__ == '__main__':
start()

49
Rom.py
View File

@ -1,15 +1,17 @@
from Dungeons import dungeon_music_addresses
from Text import string_to_alttp_text, text_addresses, Credits
from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts
from Utils import local_path
import random
import io
import json
import hashlib
import logging
import os
import struct
import random
from Dungeons import dungeon_music_addresses
from Text import string_to_alttp_text, text_addresses, Credits
from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts
from Utils import local_path
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '1deebb05eccefd2ab68297c6e9c0d25f'
@ -33,13 +35,15 @@ class JsonRom(object):
self.write_bytes(address, int32_as_bytes(value))
def write_to_file(self, file):
json.dump([self.patches], open(file, 'w'))
with open(file, 'w') as stream:
json.dump([self.patches], stream)
class LocalRom(object):
def __init__(self, file, patch=True):
self.buffer = bytearray(open(file, 'rb').read())
with open(file, 'rb') as stream:
self.buffer = bytearray(stream.read())
if patch:
self.patch_base_rom()
@ -64,14 +68,15 @@ class LocalRom(object):
# verify correct checksum of baserom
basemd5 = hashlib.md5()
basemd5.update(self.buffer)
if not JAP10HASH == basemd5.hexdigest():
if JAP10HASH != basemd5.hexdigest():
logging.getLogger('').warning('Supplied Base Rom does not match known MD5 for JAP(1.0) release. Will try to patch anyway.')
# extend to 2MB
self.buffer.extend(bytearray([0x00] * (2097152 - len(self.buffer))))
# load randomizer patches
patches = json.load(open(local_path('data/base2current.json'), 'r'))
with open(local_path('data/base2current.json'), 'r') as stream:
patches = json.load(stream)
for patch in patches:
if isinstance(patch, dict):
for baseaddress, values in patch.items():
@ -80,7 +85,7 @@ class LocalRom(object):
# verify md5
patchedmd5 = hashlib.md5()
patchedmd5.update(self.buffer)
if not RANDOMIZERBASEHASH == patchedmd5.hexdigest():
if RANDOMIZERBASEHASH != patchedmd5.hexdigest():
raise RuntimeError('Provided Base Rom unsuitable for patching. Please provide a JAP(1.0) "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc" rom to use as a base.')
def write_crc(self):
@ -155,11 +160,15 @@ class Sprite(object):
for y in range(8):
for x in range(8):
position = 1<<(7-x)
val=0;
if self.sprite[pos+2*y] & position: val += 1
if self.sprite[pos+2*y+1] & position: val += 2
if self.sprite[pos+2*y+16] & position: val += 4
if self.sprite[pos+2*y+17] & position: val += 8
val = 0
if self.sprite[pos+2*y] & position:
val += 1
if self.sprite[pos+2*y+1] & position:
val += 2
if self.sprite[pos+2*y+16] & position:
val += 4
if self.sprite[pos+2*y+17] & position:
val += 8
arr[y][x] = val
return arr
@ -211,7 +220,7 @@ class Sprite(object):
real_csum = sum(filedata) % 0x10000
if real_csum != csum or real_csum ^ 0xFFFF != icsum:
logger.warning('ZSPR file has incorrect checksum. It may be corrupted.')
pass
sprite = filedata[sprite_offset:sprite_offset + sprite_size]
palette = filedata[palette_offset:palette_offset + palette_size]
@ -230,7 +239,8 @@ class Sprite(object):
def expand_color(i):
return ((i & 0x1F) * 8, (i>>5 & 0x1F) * 8, (i>>10 & 0x1F) * 8)
raw_palette = self.palette
if raw_palette is None: raw_palette = Sprite.default_palette
if raw_palette is None:
raw_palette = Sprite.default_palette
# turn palette data into a list of RGB tuples with 8 bit values
palette_as_colors = [expand_color(make_int16(chnk)) for chnk in array_chunk(raw_palette, 2)]
@ -836,7 +846,8 @@ def apply_rom_settings(rom, beep, quickswap, fastmenu, disable_music, sprite):
def write_sprite(rom, sprite):
if not sprite.valid: return
if not sprite.valid:
return
rom.write_bytes(0x80000, sprite.sprite)
if sprite.palette is not None:
rom.write_bytes(0xDD308, sprite.palette)

View File

@ -428,7 +428,7 @@ def open_rules(world):
# to prevent key-lock in keysanity we need to prevent these chests from having an item that
# blocks the small key
if (world.keysanity):
if world.keysanity:
set_rule(world.get_location('Hyrule Castle - Boomerang Chest'), lambda state: state.has('Small Key (Escape)'))
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest'), lambda state: state.has('Small Key (Escape)'))
@ -483,7 +483,10 @@ def set_trock_key_rules(world):
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)'), lambda state: state.has('Small Key (Turtle Rock)', 4))
# this is just the pokey room with one more key
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 2)) if not can_reach_back else set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 3))
if not can_reach_back:
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 2))
else:
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 3))
# the most complicated one
# if we have back entrance access, we could waste all keys before touching this

View File

@ -120,7 +120,7 @@ class Credits(object):
self.scene_order = ['castle', 'sancturary', 'kakariko', 'desert', 'hera', 'house', 'zora', 'witch',
'lumberjacks', 'grove', 'well', 'smithy', 'kakariko2', 'bridge', 'woods', 'pedestal']
def update_credits_line(self, scene, line, text, align='center'):
def update_credits_line(self, scene, line, text):
scenes = self.credit_scenes
text = text[:32]
@ -456,9 +456,10 @@ def char_to_alttp_char(char):
return char_map.get(char, 0xFF)
class TextMapper(object):
number_offset = None
alpha_offset = 0
char_map = {}
@classmethod
def map_char(cls, char):
if cls.number_offset is not None:

View File

@ -1,4 +1,5 @@
import os
import subprocess
import sys
def is_bundled():
@ -10,7 +11,7 @@ def local_path(path):
if is_bundled():
# we are running in a bundle
local_path.cached_path = sys._MEIPASS
local_path.cached_path = sys._MEIPASS # pylint: disable=protected-access,no-member
else:
# we are running in a normal Python environment
local_path.cached_path = os.path.dirname(os.path.abspath(__file__))
@ -40,7 +41,7 @@ def output_path(path):
documents = buf.value
elif sys.platform == 'darwin':
from AppKit import NSSearchPathForDirectoriesInDomains
from AppKit import NSSearchPathForDirectoriesInDomains # pylint: disable=import-error
# http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains
NSDocumentDirectory = 9
NSUserDomainMask = 1
@ -60,7 +61,7 @@ def open_file(filename):
if sys.platform == 'win32':
os.startfile(filename)
else:
open_Command = 'open' if sys.platform == 'darwin' else 'xdg-open'
open_command = 'open' if sys.platform == 'darwin' else 'xdg-open'
subprocess.call([open_command, filename])
def close_console():
@ -69,5 +70,5 @@ def close_console():
import ctypes.wintypes
try:
ctypes.windll.kernel32.FreeConsole()
except:
except Exception:
pass