Various Item pool fixes

* Pedestal goal always left a spare item in the pool, unless vanilla swords was also selected
* extra items for the pool can now be given dynamically based on items still needed.
* easy item pool + swordless gets 4 bows, not 2 (weird combo, but ok)
* add some item pool unittests
* add easy item pool to CLI and GUI
This commit is contained in:
Fabian Dill 2020-08-01 16:49:46 +02:00
parent 8759ab83bc
commit 01ace95c32
5 changed files with 82 additions and 26 deletions

View File

@ -91,9 +91,10 @@ def parse_arguments(argv, no_defaults=False):
type=lambda value: min(max(int(value), 1), 90),
help='''Set Triforce Pieces required to win a Triforce Hunt''')
parser.add_argument('--difficulty', default=defval('normal'), const='normal', nargs='?',
choices=['normal', 'hard', 'expert'],
choices=['easy', 'normal', 'hard', 'expert'],
help='''\
Select game difficulty. Affects available itempool. (default: %(default)s)
Easy: An easier setting with some equipment duplicated and increased health.
Normal: Normal difficulty.
Hard: A harder setting with less equipment and reduced health.
Expert: A harder yet setting with minimum equipment and health.

2
Gui.py
View File

@ -272,7 +272,7 @@ def guiMain(args=None):
difficultyFrame = Frame(drowDownFrame)
difficultyVar = StringVar()
difficultyVar.set('normal')
difficultyOptionMenu = OptionMenu(difficultyFrame, difficultyVar, 'normal', 'hard', 'expert')
difficultyOptionMenu = OptionMenu(difficultyFrame, difficultyVar, 'easy', 'normal', 'hard', 'expert')
difficultyOptionMenu.pack(side=RIGHT)
difficultyLabel = Label(difficultyFrame, text='Difficulty: item pool')
difficultyLabel.pack(side=LEFT)

View File

@ -23,12 +23,12 @@ normalbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bott
hardbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Bee)',
'Bottle (Good Bee)']
easybaseitems = (['Sanctuary Heart Container', "Lamp"] + ['Rupees (300)'] * 4 + ['Magic Upgrade (1/2)'] * 2 +
['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12)
easyextra = ['Piece of Heart'] * 12 + ['Rupees (300)']
easyfirst15extra = ['Rupees (100)'] + ['Arrows (10)'] * 7 + ['Bombs (3)'] * 7
easysecond10extra = ['Bombs (3)'] * 7 + ['Rupee (1)', 'Rupees (50)', 'Bombs (10)']
easythird5extra = ['Rupees (50)'] * 2 + ['Bombs (3)'] * 2 + ['Arrows (10)']
easybaseitems = (['Sanctuary Heart Container', "Lamp"] + ['Rupees (300)'] * 5 + ['Magic Upgrade (1/2)'] * 2 +
['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24)
easyfirst15extra = ['Piece of Heart'] * 12 + ['Rupees (300)'] * 3
easysecond15extra = ['Rupees (100)'] + ['Arrows (10)'] * 7 + ['Bombs (3)'] * 7
easythird10extra = ['Bombs (3)'] * 7 + ['Rupee (1)', 'Rupees (50)', 'Bombs (10)']
easyfourth5extra = ['Rupees (50)'] * 2 + ['Bombs (3)'] * 2 + ['Arrows (10)']
easyfinal25extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 14 + ['Rupee (1)'] + ['Arrows (10)'] * 4 + ['Rupees (5)'] * 2
normalbaseitems = (['Magic Upgrade (1/2)', 'Single Arrow', 'Sanctuary Heart Container', 'Arrows (10)', 'Bombs (10)'] +
@ -66,18 +66,17 @@ difficulties = {
progressivebow=["Progressive Bow"] * 2,
basicbow=['Bow', 'Silver Bow'] * 2,
timedohko=['Green Clock'] * 25,
timedother=['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 5,
# +5 more Red Clocks if there is room
timedother=['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
triforcehunt=['Triforce Piece'] * 30,
retro=['Small Key (Universal)'] * 27,
extras=[easyextra, easyfirst15extra, easysecond10extra, easythird5extra, easyfinal25extra],
retro=['Small Key (Universal)'] * 28,
extras=[easyfirst15extra, easysecond15extra, easythird10extra, easyfourth5extra, easyfinal25extra],
progressive_sword_limit=8,
progressive_shield_limit=6,
progressive_armor_limit=2,
progressive_armor_limit=4,
progressive_bow_limit=4,
progressive_bottle_limit=8,
boss_heart_container_limit=10,
heart_piece_limit=24,
heart_piece_limit=36,
),
'normal': Difficulty(
baseitems=normalbaseitems,
@ -163,7 +162,8 @@ difficulties = {
),
}
def generate_itempool(world, player):
def generate_itempool(world, player: int):
if world.difficulty[player] not in difficulties:
raise NotImplementedError(f"Diffulty {world.difficulty[player]}")
if world.goal[player] not in {'ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt',
@ -465,14 +465,14 @@ def get_pool_core(world, player: int):
if logic in {'owglitches', 'nologic'} and world.glitch_boots[player]:
precollected_items.append('Pegasus Boots')
pool.remove('Pegasus Boots')
pool.extend(['Rupees (20)'])
pool.append('Rupees (20)')
if want_progressives():
pool.extend(progressivegloves)
else:
pool.extend(basicgloves)
# insanity shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start
# insanity legacy shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start
if shuffle == 'insanity_legacy':
place_item('Link\'s House', 'Magic Mirror')
place_item('Sanctuary', 'Moon Pearl')
@ -511,7 +511,10 @@ def get_pool_core(world, player: int):
elif swords != 'swordless':
pool.extend(diff.basicbow)
else:
pool.extend(['Bow', 'Silver Bow'])
swordless_bows = ['Bow', 'Silver Bow']
if difficulty == "easy":
swordless_bows *= 2
pool.extend(swordless_bows)
if swords == 'swordless':
pool.extend(diff.swordless)
@ -557,12 +560,17 @@ def get_pool_core(world, player: int):
treasure_hunt_icon = 'Triforce Piece'
for extra in diff.extras:
if extraitems > 0:
if extraitems >= len(extra):
pool.extend(extra)
extraitems -= len(extra)
elif extraitems > 0:
pool.extend(world.random.sample(extra, extraitems))
break
if goal == 'pedestal' and swords != 'vanilla':
place_item('Master Sword Pedestal', 'Triforce')
pool.remove("Rupees (20)")
if retro:
pool = [item.replace('Single Arrow', 'Rupees (5)') for item in pool]
pool = [item.replace('Arrows (10)', 'Rupees (5)') for item in pool]

View File

@ -0,0 +1,47 @@
from ItemList import difficulties
from test.TestBase import TestBase
base_items = 43
extra_counts = (15, 15, 10, 5, 25)
class TestDifficulty(TestBase):
pass
def build_difficulty_test(difficulty):
# binds difficulty to definition local scope
def build_for(function):
def wrapped(self, *args):
return function(self, difficulty, *args)
return wrapped
return build_for
def build_dynamic_tests():
for name, difficulty in difficulties.items():
@build_difficulty_test(difficulty)
def test_dyn_difficulty(self, difficulty):
base = len(difficulty.baseitems)
self.assertEqual(base, base_items)
setattr(TestDifficulty, f"testCountBase{name}", test_dyn_difficulty)
@build_difficulty_test(difficulty)
def test_dyn_difficulty(self, difficulty):
self.assertEqual(len(extra_counts), len(difficulty.extras))
setattr(TestDifficulty, f"testCountExtra{name}", test_dyn_difficulty)
@build_difficulty_test(difficulty)
def test_dyn_difficulty(self, difficulty):
for i, extras in enumerate(extra_counts):
self.assertEqual(extras, len(difficulty.extras[i]))
setattr(TestDifficulty, f"testCountExtras{name}", test_dyn_difficulty)
build_dynamic_tests()

0
test/items/__init__.py Normal file
View File