diff --git a/worlds/yachtdice/Rules.py b/worlds/yachtdice/Rules.py index ecbfce599f..1f56c5a3b0 100644 --- a/worlds/yachtdice/Rules.py +++ b/worlds/yachtdice/Rules.py @@ -80,7 +80,7 @@ class ListState: return self.item_counts[item] -def extract_progression(state, player, options): +def extract_progression(state, player, frags_per_dice, frags_per_roll): """ method to obtain a list of what items the player has. this includes categories, dice, rolls and score multiplier etc. @@ -91,11 +91,11 @@ def extract_progression(state, player, options): number_of_dice = ( state.count("Dice", player) - + state.count("Dice Fragment", player) // options.number_of_dice_fragments_per_dice.value + + state.count("Dice Fragment", player) // frags_per_dice ) number_of_rerolls = ( state.count("Roll", player) - + state.count("Roll Fragment", player) // options.number_of_roll_fragments_per_roll.value + + state.count("Roll Fragment", player) // frags_per_roll ) number_of_fixed_mults = state.count("Fixed Score Multiplier", player) number_of_step_mults = state.count("Step Score Multiplier", player) @@ -215,20 +215,20 @@ def dice_simulation_strings(categories, num_dice, num_rolls, fixed_mult, step_mu return yachtdice_cache[tup] -def dice_simulation_fill_pool(state, options): +def dice_simulation_fill_pool(state, frags_per_dice, frags_per_roll, difficulty): """ Returns the feasible score that one can reach with the current state, options and difficulty. This function is called with state being a list, during filling of item pool. """ - categories, num_dice, num_rolls, fixed_mult, step_mult, expoints = extract_progression(state, "state_is_a_list", options) + categories, num_dice, num_rolls, fixed_mult, step_mult, expoints = extract_progression(state, "state_is_a_list", frags_per_dice, frags_per_roll) return ( dice_simulation_strings( - categories, num_dice, num_rolls, fixed_mult, step_mult, options.game_difficulty.value + categories, num_dice, num_rolls, fixed_mult, step_mult, difficulty ) + expoints ) -def dice_simulation_state_change(state, player, options): +def dice_simulation_state_change(state, player, frags_per_dice, frags_per_roll, difficulty): """ Returns the feasible score that one can reach with the current state, options and difficulty. This function is called with state being a AP state object, while doing access rules. @@ -236,10 +236,10 @@ def dice_simulation_state_change(state, player, options): if state.prog_items[player]["state_is_fresh"] == 0: state.prog_items[player]["state_is_fresh"] = 1 - categories, num_dice, num_rolls, fixed_mult, step_mult, expoints = extract_progression(state, player, options) + categories, num_dice, num_rolls, fixed_mult, step_mult, expoints = extract_progression(state, player, frags_per_dice, frags_per_roll) state.prog_items[player]["maximum_achievable_score"] = ( dice_simulation_strings( - categories, num_dice, num_rolls, fixed_mult, step_mult, options.game_difficulty.value + categories, num_dice, num_rolls, fixed_mult, step_mult, difficulty ) + expoints ) @@ -248,7 +248,7 @@ def dice_simulation_state_change(state, player, options): -def set_yacht_rules(world: MultiWorld, player: int, options): +def set_yacht_rules(world: MultiWorld, player: int, frags_per_dice, frags_per_roll, difficulty): """ Sets rules on entrances and advancements that are always applied """ @@ -256,7 +256,7 @@ def set_yacht_rules(world: MultiWorld, player: int, options): for location in world.get_locations(player): set_rule( location, - lambda state, curscore=location.yacht_dice_score, player=player: dice_simulation_state_change(state, player, options) + lambda state, curscore=location.yacht_dice_score, player=player: dice_simulation_state_change(state, player, frags_per_dice, frags_per_roll, difficulty) >= curscore, ) diff --git a/worlds/yachtdice/__init__.py b/worlds/yachtdice/__init__.py index 32b3c70adc..e26b7494a7 100644 --- a/worlds/yachtdice/__init__.py +++ b/worlds/yachtdice/__init__.py @@ -6,8 +6,8 @@ from worlds.AutoWorld import WebWorld, World from .Items import YachtDiceItem, item_groups, item_table from .Locations import YachtDiceLocation, all_locations, ini_locations -from .Options import YachtDiceOptions, yd_option_groups -from .Rules import dice_simulation_fill_pool, dice_simulation_state_change, set_yacht_completion_rules, set_yacht_rules +from .Options import YachtDiceOptions, yd_option_groups, GameDifficulty,MinimalNumberOfDiceAndRolls,PointsSize,MinimizeExtraItems,AddExtraPoints,AddStoryChapters,WhichStory,AllowManual +from .Rules import dice_simulation_fill_pool, set_yacht_completion_rules, set_yacht_rules class YachtDiceWeb(WebWorld): @@ -65,14 +65,43 @@ class YachtDiceWorld(World): self.precollected = [] # number of dice and rolls in the pull - ind_dice_rolls = self.options.minimal_number_of_dice_and_rolls.value - - num_of_dice = [0, 2, 5, 5, 6, 7, 8][ind_dice_rolls] - num_of_rolls = [0, 2, 3, 5, 4, 3, 2][ind_dice_rolls] + opt_dice_and_rolls = self.options.minimal_number_of_dice_and_rolls + + if opt_dice_and_rolls == MinimalNumberOfDiceAndRolls.option_5_dice_and_3_rolls: + num_of_dice = 5 + num_of_rolls = 3 + elif opt_dice_and_rolls == MinimalNumberOfDiceAndRolls.option_5_dice_and_5_rolls: + num_of_dice = 5 + num_of_rolls = 5 + elif opt_dice_and_rolls == MinimalNumberOfDiceAndRolls.option_6_dice_and_4_rolls: + num_of_dice = 6 + num_of_rolls = 4 + elif opt_dice_and_rolls == MinimalNumberOfDiceAndRolls.option_7_dice_and_3_rolls: + num_of_dice = 7 + num_of_rolls = 3 + elif opt_dice_and_rolls == MinimalNumberOfDiceAndRolls.option_8_dice_and_2_rolls: + num_of_dice = 8 + num_of_rolls = 2 + else: + raise Exception(f"[Yacht Dice] Unknown MinimalNumberOfDiceAndRolls options {opt_dice_and_rolls}") + # amount of dice and roll fragments needed to get a dice or roll - frags_per_dice = self.options.number_of_dice_fragments_per_dice.value - frags_per_roll = self.options.number_of_roll_fragments_per_roll.value + self.frags_per_dice = self.options.number_of_dice_fragments_per_dice.value + self.frags_per_roll = self.options.number_of_roll_fragments_per_roll.value + + # set difficulty + diff_value = self.options.game_difficulty + if diff_value == GameDifficulty.option_easy: + self.difficulty = 1 + elif diff_value == GameDifficulty.option_medium: + self.difficulty = 2 + elif diff_value == GameDifficulty.option_hard: + self.difficulty = 3 + elif diff_value == GameDifficulty.option_extreme: + self.difficulty = 4 + else: + raise Exception(f"[Yacht Dice] Unknown GameDifficulty options {diff_value}") # Create a list with the specified number of 1s num_ones = self.options.alternative_categories.value @@ -120,27 +149,29 @@ class YachtDiceWorld(World): self.precollected.append("Dice") # if one fragment per dice, just add "Dice" objects - if frags_per_dice == 1: + if self.frags_per_dice == 1: self.itempool += ["Dice"] * (num_of_dice - 1) # minus one because one is in start inventory else: self.itempool.append("Dice") # always add a full dice to make generation easier (will be early) - self.itempool += ["Dice Fragment"] * (frags_per_dice * (num_of_dice - 2)) + self.itempool += ["Dice Fragment"] * (self.frags_per_dice * (num_of_dice - 2)) # if one fragment per roll, just add "Roll" objects - if frags_per_roll == 1: + if self.frags_per_roll == 1: self.itempool += ["Roll"] * (num_of_rolls - 1) # minus one because one is in start inventory else: self.itempool.append("Roll") # always add a full roll to make generation easier (will be early) - self.itempool += ["Roll Fragment"] * (frags_per_roll * (num_of_rolls - 2)) + self.itempool += ["Roll Fragment"] * (self.frags_per_roll * (num_of_rolls - 2)) already_items = len(self.itempool) # Yacht Dice needs extra filler items so it doesn't get stuck in generation. # For now, we calculate the number of extra items we'll need later. - if self.options.minimize_extra_items.value: + if self.options.minimize_extra_items == MinimizeExtraItems.option_yes_please: extra_percentage = max(0.1, 0.8 - self.multiworld.players / 10) - else: + elif self.options.minimize_extra_items == MinimizeExtraItems.option_no_dont: extra_percentage = 0.7 + else: + raise Exception(f"[Yacht Dice] Unknown MinimizeExtraItems options {self.options.minimize_extra_items}") extra_locations_needed = max(10, math.ceil(already_items * extra_percentage)) # max score is the value of the last check. Goal score is the score needed to 'finish' the game @@ -160,14 +191,14 @@ class YachtDiceWorld(World): } # if the player wants extra rolls or dice, fill the pool with fragments until close to an extra roll/dice - if weights["Dice"] > 0 and frags_per_dice > 1: - self.itempool += ["Dice Fragment"] * (frags_per_dice - 1) - if weights["Roll"] > 0 and frags_per_roll > 1: - self.itempool += ["Roll Fragment"] * (frags_per_roll - 1) + if weights["Dice"] > 0 and self.frags_per_dice > 1: + self.itempool += ["Dice Fragment"] * (self.frags_per_dice - 1) + if weights["Roll"] > 0 and self.frags_per_roll > 1: + self.itempool += ["Roll Fragment"] * (self.frags_per_roll - 1) # calibrate the weights, since the impact of each of the items is different - weights["Dice"] = weights["Dice"] / 5 * frags_per_dice - weights["Roll"] = weights["Roll"] / 5 * frags_per_roll + weights["Dice"] = weights["Dice"] / 5 * self.frags_per_dice + weights["Roll"] = weights["Roll"] / 5 * self.frags_per_roll extra_points_added = 0 multipliers_added = 0 @@ -177,11 +208,11 @@ class YachtDiceWorld(World): items_added += 1 all_items = self.itempool + self.precollected - dice_fragments_in_pool = all_items.count("Dice") * frags_per_dice + all_items.count("Dice Fragment") - if dice_fragments_in_pool + 1 >= 9 * frags_per_dice: + dice_fragments_in_pool = all_items.count("Dice") * self.frags_per_dice + all_items.count("Dice Fragment") + if dice_fragments_in_pool + 1 >= 9 * self.frags_per_dice: weights["Dice"] = 0 # don't allow >=9 dice - roll_fragments_in_pool = all_items.count("Roll") * frags_per_roll + all_items.count("Roll Fragment") - if roll_fragments_in_pool + 1 >= 6 * frags_per_roll: + roll_fragments_in_pool = all_items.count("Roll") * self.frags_per_roll + all_items.count("Roll Fragment") + if roll_fragments_in_pool + 1 >= 6 * self.frags_per_roll: weights["Roll"] = 0 # don't allow >= 6 rolls # Don't allow too many multipliers @@ -206,11 +237,11 @@ class YachtDiceWorld(World): if which_item_to_add == "Dice": - weights["Dice"] /= 1 + frags_per_dice - return "Dice" if frags_per_dice == 1 else "Dice Fragment" + weights["Dice"] /= 1 + self.frags_per_dice + return "Dice" if self.frags_per_dice == 1 else "Dice Fragment" elif which_item_to_add == "Roll": - weights["Roll"] /= 1 + frags_per_roll - return "Roll" if frags_per_roll == 1 else "Roll Fragment" + weights["Roll"] /= 1 + self.frags_per_roll + return "Roll" if self.frags_per_roll == 1 else "Roll Fragment" elif which_item_to_add == "Fixed Score Multiplier": weights["Fixed Score Multiplier"] /= 1.05 multipliers_added += 1 @@ -227,16 +258,18 @@ class YachtDiceWorld(World): weights["Double category"] /= 1.1 return self.random.choices(possible_categories, weights=cat_weights)[0] elif which_item_to_add == "Points": - score_dist = self.options.points_size.value + score_dist = self.options.points_size probs = { "1 Point": 1, "10 Points": 0, "100 Points": 0} - if score_dist == 1: + if score_dist == PointsSize.option_small: probs = { "1 Point": 0.9, "10 Points": 0.1, "100 Points": 0} - if score_dist == 2: + elif score_dist == PointsSize.option_medium: probs = { "1 Point": 0, "10 Points": 1, "100 Points": 0} - if score_dist == 3: + elif score_dist == PointsSize.option_large: probs = { "1 Point": 0, "10 Points": 0.3, "100 Points": 0.7} - if score_dist == 4: + elif score_dist == PointsSize.option_mix: probs = { "1 Point": 0.3, "10 Points": 0.4, "100 Points": 0.3} + else: + raise Exception(f"[Yacht Dice] Unknown PointsSize options {score_dist}") choice = self.random.choices(list(probs.keys()), weights=list(probs.values()))[0] if choice == "1 Point": weights["Points"] /= 1.01 @@ -259,14 +292,14 @@ class YachtDiceWorld(World): for _ in range(17): self.itempool.append(get_item_to_add(weights, extra_points_added, multipliers_added, items_added)) - score_in_logic = dice_simulation_fill_pool(self.itempool + self.precollected, self.options) + score_in_logic = dice_simulation_fill_pool(self.itempool + self.precollected, self.frags_per_dice, self.frags_per_roll, self.difficulty) # if we overshoot, remove items until you get below 1000, then return the last removed item if score_in_logic > 1000: removed_item = "" while score_in_logic > 1000: removed_item = self.itempool.pop() - score_in_logic = dice_simulation_fill_pool(self.itempool + self.precollected, self.options) + score_in_logic = dice_simulation_fill_pool(self.itempool + self.precollected, self.frags_per_dice, self.frags_per_roll, self.difficulty) self.itempool.append(removed_item) else: # Keep adding items until a score of 1000 is in logic @@ -280,7 +313,7 @@ class YachtDiceWorld(World): elif item_to_add == "100 Points": score_in_logic += 100 else: - score_in_logic = dice_simulation_fill_pool(self.itempool + self.precollected, self.options) + score_in_logic = dice_simulation_fill_pool(self.itempool + self.precollected, self.frags_per_dice, self.frags_per_roll, self.difficulty) # count the number of locations in the game. already_items = len(self.itempool) + 1 # +1 because of Victory item @@ -293,30 +326,30 @@ class YachtDiceWorld(World): # making sure not to exceed the number of locations. # first, we flood the entire pool with extra points (useful), if that setting is chosen. - if self.options.add_bonus_points.value == 1: # all of the extra points + if self.options.add_bonus_points == AddExtraPoints.option_all_of_it: # all of the extra points already_items = len(self.itempool) + 1 self.itempool += ["Bonus Point"] * min(self.number_of_locations - already_items, 100) # second, we flood the entire pool with story chapters (filler), if that setting is chosen. - if self.options.add_story_chapters.value == 1: # all of the story chapters + if self.options.add_story_chapters == AddStoryChapters.option_all_of_it: # all of the story chapters already_items = len(self.itempool) + 1 number_of_items = min(self.number_of_locations - already_items, 100) number_of_items = (number_of_items // 10) * 10 # story chapters always come in multiples of 10 self.itempool += ["Story Chapter"] * number_of_items # add some extra points (useful) - if self.options.add_bonus_points.value == 2: # add extra points if wanted + if self.options.add_bonus_points == AddExtraPoints.option_sure: # add extra points if wanted already_items = len(self.itempool) + 1 self.itempool += ["Bonus Point"] * min(self.number_of_locations - already_items, 10) # add some story chapters (filler) - if self.options.add_story_chapters.value == 2: # add extra points if wanted + if self.options.add_story_chapters == AddStoryChapters.option_sure: # add extra points if wanted already_items = len(self.itempool) + 1 if self.number_of_locations - already_items >= 10: self.itempool += ["Story Chapter"] * 10 # add some more extra points if there is still room - if self.options.add_bonus_points.value == 2: + if self.options.add_bonus_points == AddExtraPoints.option_sure: already_items = len(self.itempool) + 1 self.itempool += ["Bonus Point"] * min(self.number_of_locations - already_items, 10) @@ -332,7 +365,8 @@ class YachtDiceWorld(World): # these items are filler and do not do anything. # probability of Good and Bad rng, based on difficulty for fun :) - p = 1.1 - 0.25 * self.options.game_difficulty.value + + p = 1.1 - 0.25 * self.difficulty already_items = len(self.itempool) + 1 self.itempool += self.random.choices( ["Good RNG", "Bad RNG"], weights=[p, 1 - p], k=self.number_of_locations - already_items @@ -360,7 +394,7 @@ class YachtDiceWorld(World): def create_regions(self): # call the ini_locations function, that generates locations based on the inputs. location_table = ini_locations( - self.goal_score, self.max_score, self.number_of_locations, self.options.game_difficulty.value + self.goal_score, self.max_score, self.number_of_locations, self.difficulty ) # simple menu-board construction @@ -389,7 +423,7 @@ class YachtDiceWorld(World): """ set rules per location, and add the rule for beating the game """ - set_yacht_rules(self.multiworld, self.player, self.options) + set_yacht_rules(self.multiworld, self.player, self.frags_per_dice, self.frags_per_roll, self.difficulty) set_yacht_completion_rules(self.multiworld, self.player) def fill_slot_data(self):