Pass the fish better

This commit is contained in:
Mike A. Trethewey
2020-03-09 03:53:08 -07:00
parent ca7fc4d0f6
commit 37a1b70f3b
5 changed files with 60 additions and 110 deletions

12
CLI.py
View File

@@ -8,8 +8,6 @@ import textwrap
import shlex
import sys
from Main import main
import source.classes.constants as CONST
from source.classes.BabelFish import BabelFish
@@ -26,7 +24,15 @@ def parse_arguments(argv, no_defaults=False):
# get settings
settings = get_settings()
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
lang = "en"
if argv is not None:
priority = get_args_priority(None, None, argv)
if "load" in priority:
priority = priority["load"]
if "lang" in priority:
lang = priority["lang"]
fish = BabelFish(lang=lang)
# we need to know how many players we have first
parser = argparse.ArgumentParser(add_help=False)

View File

@@ -5,9 +5,6 @@ import operator as op
import time
from enum import unique, Flag
from source.classes.BabelFish import BabelFish
import CLI as cli
from functools import reduce
from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBarrier
from Regions import key_only_locations
@@ -308,9 +305,6 @@ def pair_existing_key_doors(world, player, door_a, door_b):
# paired_door.pair = False
def within_dungeon(world, player):
settings = cli.get_settings()
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
fix_big_key_doors_with_ugly_smalls(world, player)
overworld_prep(world, player)
entrances_map, potentials, connections = determine_entrance_list(world, player)
@@ -322,7 +316,7 @@ def within_dungeon(world, player):
dungeon_builders[key] = simple_dungeon_builder(key, sector_list)
dungeon_builders[key].entrance_list = list(entrances_map[key])
recombinant_builders = {}
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, world.fish)
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
paths = determine_required_paths(world, player)
@@ -332,15 +326,15 @@ def within_dungeon(world, player):
start = time.process_time()
for builder in world.dungeon_layouts[player].values():
shuffle_key_doors(builder, world, player)
logging.getLogger('').info('%s: %s', fish.translate("cli","cli","keydoor.shuffle.time"), time.process_time()-start)
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","keydoor.shuffle.time"), time.process_time()-start)
smooth_door_pairs(world, player)
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map):
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, fish):
for name, split_list in split_region_starts.items():
builder = dungeon_builders.pop(name)
recombinant_builders[name] = builder
split_builders = split_dungeon_builder(builder, split_list)
split_builders = split_dungeon_builder(builder, split_list, fish)
dungeon_builders.update(split_builders)
for sub_name, split_entrances in split_list.items():
sub_builder = dungeon_builders[name+' '+sub_name]
@@ -353,11 +347,6 @@ def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player):
settings = cli.get_args_priority(None, None, None)
if "load" in settings:
settings = settings["load"]
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
entrances_map, potentials, connections = connections_tuple
enabled_entrances = {}
sector_queue = deque(dungeon_builders.values())
@@ -377,7 +366,7 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
sector_queue.append(builder)
last_key = builder.name
else:
logging.getLogger('').info('%s: %s', fish.translate("cli","cli","generating.dungeon"), builder.name)
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","generating.dungeon"), builder.name)
ds = generate_dungeon(builder, origin_list_sans_drops, split_dungeon, world, player)
find_new_entrances(ds, connections, potentials, enabled_entrances, world, player)
ds.name = name
@@ -724,9 +713,6 @@ def cross_dungeon(world, player):
def assign_cross_keys(dungeon_builders, world, player):
settings = cli.get_settings()
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
start = time.process_time()
total_keys = remaining = 29
total_candidates = 0
@@ -806,7 +792,7 @@ def assign_cross_keys(dungeon_builders, world, player):
dungeon.small_keys = []
else:
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
logging.getLogger('').info('%s: %s', fish.translate("cli","cli","keydoor.shuffle.time.crossed"), time.process_time()-start)
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","keydoor.shuffle.time.crossed"), time.process_time()-start)
def reassign_boss(boss_region, boss_key, builder, gt, world, player):
@@ -962,18 +948,15 @@ def calc_used_dungeon_items(builder):
def find_valid_combination(builder, start_regions, world, player, drop_keys=True):
settings = cli.get_settings()
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
logger = logging.getLogger('')
logger.info('%s %s', fish.translate("cli","cli","shuffling.keydoors"), builder.name)
logger.info('%s %s', world.fish.translate("cli","cli","shuffling.keydoors"), builder.name)
# find valid combination of candidates
if len(builder.candidates) < builder.key_doors_num:
if not drop_keys:
logger.info('No valid layouts for %s with %s doors', builder.name, builder.key_doors_num)
return False
builder.key_doors_num = len(builder.candidates) # reduce number of key doors
logger.info('%s: %s', fish.translate("cli","cli","lowering.keys.candidates"), builder.name)
logger.info('%s: %s', world.fish.translate("cli","cli","lowering.keys.candidates"), builder.name)
combinations = ncr(len(builder.candidates), builder.key_doors_num)
itr = 0
start = time.process_time()
@@ -993,7 +976,7 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True
if not drop_keys:
logger.info('No valid layouts for %s with %s doors', builder.name, builder.key_doors_num)
return False
logger.info('%s: %s', fish.translate("cli","cli","lowering.keys.layouts"), builder.name)
logger.info('%s: %s', world.fish.translate("cli","cli","lowering.keys.layouts"), builder.name)
builder.key_doors_num -= 1
if builder.key_doors_num < 0:
raise Exception('Bad dungeon %s - 0 key doors not valid' % builder.name)

View File

@@ -8,9 +8,6 @@ from functools import reduce
import operator as op
from typing import List
from source.classes.BabelFish import BabelFish
import CLI as cli
from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, Sector, PolSlot, flooded_keys
from Regions import key_only_locations, dungeon_events, flooded_keys_reverse
from Dungeons import dungeon_regions
@@ -1097,11 +1094,6 @@ def simple_dungeon_builder(name, sector_list):
def create_dungeon_builders(all_sectors, world, player, dungeon_entrances=None):
settings = cli.get_args_priority(None, None, None)
if "load" in settings:
settings = settings["load"]
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
logger = logging.getLogger('')
logger.info('Shuffling Dungeon Sectors')
if dungeon_entrances is None:
@@ -1157,8 +1149,8 @@ def create_dungeon_builders(all_sectors, world, player, dungeon_entrances=None):
# polarity:
if not global_pole.is_valid(dungeon_map):
raise NeutralizingException('Either free location/crystal assignment is already globally invalid - lazy dev check this earlier!')
logger.info(fish.translate("cli","cli","balance.doors"))
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger)
logger.info(world.fish.translate("cli","cli","balance.doors"))
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger, world.fish)
# the rest
assign_the_rest(dungeon_map, neutral_sectors, global_pole)
return dungeon_map
@@ -1442,12 +1434,7 @@ def sum_polarity(sector_list):
return pol
def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger):
settings = cli.get_args_priority(None, None, None)
if "load" in settings:
settings = settings["load"]
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger, fish):
# step 1: fix polarity connection issues
logger.info(fish.translate("cli","cli","basic.traversal"))
unconnected_builders = identify_polarity_issues(dungeon_map)
@@ -1487,7 +1474,7 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger
problem_builders = identify_simple_branching_issues(problem_builders)
# step 3: fix neutrality issues
polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger)
polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger, fish)
# step 4: fix dead ends again
neutral_choices: List[List] = neutralize_the_rest(polarized_sectors)
@@ -1536,12 +1523,7 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger
tries += 1
def polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger):
settings = cli.get_args_priority(None, None, None)
if "load" in settings:
settings = settings["load"]
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
def polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger, fish):
builder_order = list(dungeon_map.values())
random.shuffle(builder_order)
for builder in builder_order:
@@ -1849,12 +1831,7 @@ def assign_the_rest(dungeon_map, neutral_sectors, global_pole):
assign_sector(sector_list[i], builder, neutral_sectors, global_pole)
def split_dungeon_builder(builder, split_list):
settings = cli.get_args_priority(None, None, None)
if "load" in settings:
settings = settings["load"]
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
def split_dungeon_builder(builder, split_list, fish):
logger = logging.getLogger('')
logger.info(fish.translate("cli","cli","splitting.up") + ' ' + 'Desert/Skull')
candidate_sectors = dict.fromkeys(builder.sectors)
@@ -1867,15 +1844,10 @@ def split_dungeon_builder(builder, split_list):
sub_builder.all_entrances = split_entrances
for r_name in split_entrances:
assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors, global_pole)
return balance_split(candidate_sectors, dungeon_map, global_pole)
return balance_split(candidate_sectors, dungeon_map, global_pole, fish)
def balance_split(candidate_sectors, dungeon_map, global_pole):
settings = cli.get_args_priority(None, None, None)
if "load" in settings:
settings = settings["load"]
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
def balance_split(candidate_sectors, dungeon_map, global_pole, fish):
logger = logging.getLogger('')
# categorize sectors
crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors)
@@ -1889,7 +1861,7 @@ def balance_split(candidate_sectors, dungeon_map, global_pole):
assign_crystal_barrier_sectors(dungeon_map, crystal_barriers, global_pole)
# polarity:
logger.info(fish.translate("cli","cli","re-balancing") + ' ' + next(iter(dungeon_map.keys())) + ' et al')
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger)
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger, fish)
# the rest
assign_the_rest(dungeon_map, neutral_sectors, global_pole)
return dungeon_map

View File

@@ -19,11 +19,6 @@ from Fill import FillError
def start():
args = parse_arguments(None)
settings = get_args_priority(None, None, None)
if "load" in settings:
settings = settings["load"]
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
if is_bundled() and len(sys.argv) == 1:
# for the bundled builds, if we have no arguments, the user
# probably wants the gui. Users of the bundled build who want the command line
@@ -49,6 +44,12 @@ def start():
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel]
logging.basicConfig(format='%(message)s', level=loglevel)
settings = get_args_priority(None, None, None)
lang = "en"
if "load" in settings and "lang" in settings["load"]:
lang = settings["load"]["lang"]
fish = BabelFish(lang=lang)
if args.gui:
from Gui import guiMain
guiMain(args)
@@ -58,7 +59,7 @@ def start():
logger = logging.getLogger('')
for _ in range(args.count):
try:
main(seed=seed, args=args)
main(seed=seed, args=args, fish=fish)
logger.info('%s %s', fish.translate("cli","cli","finished.run"), _+1)
except (FillError, Exception, RuntimeError) as err:
failures.append((err, seed))
@@ -73,7 +74,7 @@ def start():
logger.info('Generation fail rate: ' + str(fail_rate[0] ).rjust(3, " ") + '.' + str(fail_rate[1] ).ljust(6, '0') + '%')
logger.info('Generation success rate: ' + str(success_rate[0]).rjust(3, " ") + '.' + str(success_rate[1]).ljust(6, '0') + '%')
else:
main(seed=args.seed, args=args)
main(seed=args.seed, args=args, fish=fish)
if __name__ == '__main__':

62
Main.py
View File

@@ -8,9 +8,6 @@ import random
import time
import zlib
from source.classes.BabelFish import BabelFish
import CLI as cli
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
from Items import ItemFactory
from KeyDoorShuffle import validate_key_placement
@@ -30,18 +27,13 @@ from Utils import output_path, parse_player_names, print_wiki_doors_by_room
__version__ = '0.0.18.4d'
def main(args, seed=None):
def main(args, seed=None, fish=None):
if args.outputpath:
os.makedirs(args.outputpath, exist_ok=True)
output_path.cached_path = args.outputpath
start = time.perf_counter()
settings = cli.get_args_priority(None, None, None)
if "load" in settings:
settings = settings["load"]
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
# initialize the world
world = World(args.multi, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.retro, args.custom, args.customitemarray, args.hints)
logger = logging.getLogger('')
@@ -67,15 +59,16 @@ def main(args, seed=None):
world.beemizer = args.beemizer.copy()
world.experimental = args.experimental.copy()
world.dungeon_counters = args.dungeon_counters.copy()
world.fish = fish
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
logger.info(
'%s %s %s - %s: %s\n',
fish.translate("cli","cli","app.title"),
fish.translate("cli","cli","version"),
world.fish.translate("cli","cli","app.title"),
world.fish.translate("cli","cli","version"),
__version__,
fish.translate("cli","cli","seed"),
world.fish.translate("cli","cli","seed"),
world.seed
)
@@ -110,7 +103,7 @@ def main(args, seed=None):
create_rooms(world, player)
create_dungeons(world, player)
logger.info(fish.translate("cli","cli","shuffling.world"))
logger.info(world.fish.translate("cli","cli","shuffling.world"))
for player in range(1, world.players + 1):
if world.mode[player] != 'inverted':
@@ -118,7 +111,7 @@ def main(args, seed=None):
else:
link_inverted_entrances(world, player)
logger.info(fish.translate("cli","cli","shuffling.dungeons"))
logger.info(world.fish.translate("cli","cli","shuffling.dungeons"))
for player in range(1, world.players + 1):
link_doors(world, player)
@@ -126,21 +119,21 @@ def main(args, seed=None):
mark_light_world_regions(world, player)
else:
mark_dark_world_regions(world, player)
logger.info(fish.translate("cli","cli","generating.itempool"))
logger.info(world.fish.translate("cli","cli","generating.itempool"))
for player in range(1, world.players + 1):
generate_itempool(world, player)
logger.info(fish.translate("cli","cli","calc.access.rules"))
logger.info(world.fish.translate("cli","cli","calc.access.rules"))
for player in range(1, world.players + 1):
set_rules(world, player)
logger.info(fish.translate("cli","cli","placing.dungeon.prizes"))
logger.info(world.fish.translate("cli","cli","placing.dungeon.prizes"))
fill_prizes(world)
logger.info(fish.translate("cli","cli","placing.dungeon.items"))
logger.info(world.fish.translate("cli","cli","placing.dungeon.items"))
shuffled_locations = None
if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
@@ -157,14 +150,14 @@ def main(args, seed=None):
raise RuntimeError(
"%s: %s (%s %d)" %
(
fish.translate("cli","cli","keylock.detected"),
world.fish.translate("cli","cli","keylock.detected"),
key_layout.sector.name,
fish.translate("cli","cli","player"),
world.fish.translate("cli","cli","player"),
player
)
)
logger.info(fish.translate("cli","cli","fill.world"))
logger.info(world.fish.translate("cli","cli","fill.world"))
if args.algorithm == 'flood':
flood_items(world) # different algo, biased towards early game progress items
@@ -183,14 +176,14 @@ def main(args, seed=None):
distribute_items_restrictive(world, True)
if world.players > 1:
logger.info(fish.translate("cli","cli","balance.multiworld"))
logger.info(world.fish.translate("cli","cli","balance.multiworld"))
balance_multiworld_progression(world)
# if we only check for beatable, we can do this sanity check first before creating the rom
if not world.can_beat_game():
raise RuntimeError(fish.translate("cli","cli","cannot.beat.game"))
raise RuntimeError(world.fish.translate("cli","cli","cannot.beat.game"))
logger.info(fish.translate("cli","cli","patching.rom"))
logger.info(world.fish.translate("cli","cli","patching.rom"))
outfilebase = 'DR_%s' % (args.outputname if args.outputname else world.seed)
@@ -214,8 +207,8 @@ def main(args, seed=None):
if not args.jsonout:
rom = LocalRom.fromJsonRom(rom, args.rom, 0x400000)
else:
logging.warning(fish.translate("cli","cli","enemizer.not.found") + ': ' + args.enemizercli)
logging.warning(fish.translate("cli","cli","enemizer.nothing.applied"))
logging.warning(world.fish.translate("cli","cli","enemizer.not.found") + ': ' + args.enemizercli)
logging.warning(world.fish.translate("cli","cli","enemizer.nothing.applied"))
if args.race:
patch_race_rom(rom)
@@ -269,7 +262,7 @@ def main(args, seed=None):
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
if not args.skip_playthrough:
logger.info(fish.translate("cli","cli","calc.playthrough"))
logger.info(world.fish.translate("cli","cli","calc.playthrough"))
create_playthrough(world)
if args.jsonout:
@@ -277,8 +270,8 @@ def main(args, seed=None):
elif args.create_spoiler and not args.skip_playthrough:
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
logger.info(fish.translate("cli","cli","done"))
logger.info('%s: %s', fish.translate("cli","cli","total.time"), time.perf_counter() - start)
logger.info(world.fish.translate("cli","cli","done"))
logger.info('%s: %s', world.fish.translate("cli","cli","total.time"), time.perf_counter() - start)
# print_wiki_doors_by_room(dungeon_regions,world,1)
@@ -414,11 +407,6 @@ def copy_dynamic_regions_and_locations(world, ret):
def create_playthrough(world):
settings = cli.get_args_priority(None, None, None)
if "load" in settings:
settings = settings["load"]
fish = BabelFish(lang=settings["lang"] if "lang" in settings else "en")
# create a copy as we will modify it
old_world = world
world = copy_world(world)
@@ -429,7 +417,7 @@ def create_playthrough(world):
collection_spheres = []
state = CollectionState(world)
sphere_candidates = list(prog_locations)
logging.getLogger('').debug(fish.translate("cli","cli","building.collection.spheres"))
logging.getLogger('').debug(world.fish.translate("cli","cli","building.collection.spheres"))
while sphere_candidates:
state.sweep_for_events(key_only=True)
state.sweep_for_crystal_access()
@@ -452,7 +440,7 @@ def create_playthrough(world):
if not sphere:
logging.getLogger('').debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]):
raise RuntimeError(fish.translate("cli","cli","cannot.reach.progression"))
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.progression"))
else:
old_world.spoiler.unreachables = sphere_candidates.copy()
break
@@ -506,7 +494,7 @@ def create_playthrough(world):
logging.getLogger('').debug('Calculated final sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(required_locations))
if not sphere:
raise RuntimeError(fish.translate("cli","cli","cannot.reach.required"))
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.required"))
# store the required locations for statistical analysis
old_world.required_locations = [(location.name, location.player) for sphere in collection_spheres for location in sphere]