update basic and normal boss shuffling with a less biased algorithm

This commit is contained in:
Fabian Dill 2021-03-26 04:05:36 +01:00
parent 4985daefee
commit 1f5bcb6273
2 changed files with 38 additions and 22 deletions

View File

@ -1059,6 +1059,9 @@ class Boss():
def can_defeat(self, state) -> bool: def can_defeat(self, state) -> bool:
return self.defeat_rule(state, self.player) return self.defeat_rule(state, self.player)
def __repr__(self):
return f"Boss({self.name})"
class Location(): class Location():
shop_slot: bool = False shop_slot: bool = False
shop_slot_disabled: bool = False shop_slot_disabled: bool = False

View File

@ -148,19 +148,19 @@ boss_table = {
} }
boss_location_table = [ boss_location_table = [
['Ganons Tower', 'top'], ('Ganons Tower', 'top'),
['Tower of Hera', None], ('Tower of Hera', None),
['Skull Woods', None], ('Skull Woods', None),
['Ganons Tower', 'middle'], ('Ganons Tower', 'middle'),
['Eastern Palace', None], ('Eastern Palace', None),
['Desert Palace', None], ('Desert Palace', None),
['Palace of Darkness', None], ('Palace of Darkness', None),
['Swamp Palace', None], ('Swamp Palace', None),
['Thieves Town', None], ('Thieves Town', None),
['Ice Palace', None], ('Ice Palace', None),
['Misery Mire', None], ('Misery Mire', None),
['Turtle Rock', None], ('Turtle Rock', None),
['Ganons Tower', 'bottom'], ('Ganons Tower', 'bottom'),
] ]
@ -187,6 +187,10 @@ def can_place_boss(boss: str, dungeon_name: str, level: Optional[str] = None) ->
return True return True
restrictive_boss_locations = {}
for location in boss_location_table:
restrictive_boss_locations[location] = not all(can_place_boss(boss, *location)
for boss in boss_table if not boss.startswith("Agahnim"))
def place_boss(world, player: int, boss: str, location: str, level: Optional[str]): def place_boss(world, player: int, boss: str, location: str, level: Optional[str]):
if location == 'Ganons Tower' and world.mode[player] == 'inverted': if location == 'Ganons Tower' and world.mode[player] == 'inverted':
@ -194,12 +198,16 @@ def place_boss(world, player: int, boss: str, location: str, level: Optional[str
logging.debug('Placing boss %s at %s', boss, location + (' (' + level + ')' if level else '')) logging.debug('Placing boss %s at %s', boss, location + (' (' + level + ')' if level else ''))
world.get_dungeon(location, player).bosses[level] = BossFactory(boss, player) world.get_dungeon(location, player).bosses[level] = BossFactory(boss, player)
def format_boss_location(location, level):
return location + (' (' + level + ')' if level else '')
def place_bosses(world, player: int): def place_bosses(world, player: int):
if world.boss_shuffle[player] == 'none': if world.boss_shuffle[player] == 'none':
return return
# Most to least restrictive order # Most to least restrictive order
boss_locations = boss_location_table.copy() boss_locations = boss_location_table.copy()
world.random.shuffle(boss_locations)
boss_locations.sort(key= lambda location: -int(restrictive_boss_locations[location]))
all_bosses = sorted(boss_table.keys()) # sorted to be deterministic on older pythons all_bosses = sorted(boss_table.keys()) # sorted to be deterministic on older pythons
placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']] placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']]
@ -225,7 +233,7 @@ def place_bosses(world, player: int):
already_placed_bosses.append(boss) already_placed_bosses.append(boss)
boss_locations.remove([loc, level]) boss_locations.remove([loc, level])
else: else:
raise Exception("Cannot place", boss, "at", loc, level, "for player", player) raise Exception(f"Cannot place {boss} at {format_boss_location(loc, level)} for player {player}.")
else: else:
boss = boss.title() boss = boss.title()
boss_locations, already_placed_bosses = place_where_possible(world, player, boss, boss_locations) boss_locations, already_placed_bosses = place_where_possible(world, player, boss, boss_locations)
@ -237,7 +245,7 @@ def place_bosses(world, player: int):
if world.boss_shuffle[player] == "basic": # vanilla bosses shuffled if world.boss_shuffle[player] == "basic": # vanilla bosses shuffled
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm'] bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
else: # all bosses present, the three duplicates chosen at random else: # all bosses present, the three duplicates chosen at random
bosses = all_bosses + [world.random.choice(placeable_bosses) for _ in range(3)] bosses = placeable_bosses + world.random.sample(placeable_bosses, 3)
# there is probably a better way to do this # there is probably a better way to do this
while already_placed_bosses: while already_placed_bosses:
@ -251,11 +259,17 @@ def place_bosses(world, player: int):
world.random.shuffle(bosses) world.random.shuffle(bosses)
for loc, level in boss_locations: for loc, level in boss_locations:
boss = next((b for b in bosses if can_place_boss(b, loc, level)), None) for _ in range(len(bosses)):
if not boss: boss = bosses.pop()
loc_text = loc + (' (' + level + ')' if level else '') if can_place_boss(boss, loc, level):
raise FillError('Could not place boss for location %s' % loc_text) break
bosses.remove(boss) # put the boss back in queue
bosses.insert(0, boss) # this would be faster with deque,
# but the deque size is small enough that it should not matter
else:
raise FillError(f'Could not place boss for location {format_boss_location(loc, level)}')
place_boss(world, player, boss, loc, level) place_boss(world, player, boss, loc, level)
elif shuffle_mode == "chaos": # all bosses chosen at random elif shuffle_mode == "chaos": # all bosses chosen at random
@ -264,8 +278,7 @@ def place_bosses(world, player: int):
boss = world.random.choice( boss = world.random.choice(
[b for b in placeable_bosses if can_place_boss(b, loc, level)]) [b for b in placeable_bosses if can_place_boss(b, loc, level)])
except IndexError: except IndexError:
loc_text = loc + (' (' + level + ')' if level else '') raise FillError(f'Could not place boss for location {format_boss_location(loc, level)}')
raise FillError('Could not place boss for location %s' % loc_text)
else: else:
place_boss(world, player, boss, loc, level) place_boss(world, player, boss, loc, level)