diff --git a/worlds/yachtdice/Items.py b/worlds/yachtdice/Items.py index cc2f30e028..011aaed7fd 100644 --- a/worlds/yachtdice/Items.py +++ b/worlds/yachtdice/Items.py @@ -11,6 +11,7 @@ class YachtDiceItem(Item): #the starting index is chosen semi-randomly to be 16871244000 item_table = { + #victory item, always placed manually at goal location "Victory": ItemData(16871244000-1, ItemClassification.progression), "Dice": ItemData(16871244000, ItemClassification.progression), @@ -18,7 +19,7 @@ item_table = { "Roll": ItemData(16871244002, ItemClassification.progression), "Roll Fragment": ItemData(16871244003, ItemClassification.progression), - "Score Multiplier": ItemData(16871244004, ItemClassification.progression), + #"Score Multiplier": ItemData(16871244004, ItemClassification.progression), #not used anymore "Fixed Score Multiplier": ItemData(16871244005, ItemClassification.progression), "Step Score Multiplier": ItemData(16871244006, ItemClassification.progression), @@ -28,10 +29,8 @@ item_table = { "Category Fours": ItemData(16871244106, ItemClassification.progression), "Category Fives": ItemData(16871244107, ItemClassification.progression), "Category Sixes": ItemData(16871244108, ItemClassification.progression), - "Category Choice": ItemData(16871244109, ItemClassification.progression), "Category Inverse Choice": ItemData(16871244110, ItemClassification.progression), - "Category Pair": ItemData(16871244111, ItemClassification.progression), "Category Three of a Kind": ItemData(16871244112, ItemClassification.progression), "Category Four of a Kind": ItemData(16871244113, ItemClassification.progression), @@ -47,10 +46,8 @@ item_table = { "Category Twos and Threes": ItemData(16871244126, ItemClassification.progression), "Category Sum of Odds": ItemData(16871244127, ItemClassification.progression), "Category Sum of Evens": ItemData(16871244128, ItemClassification.progression), - "Category Double Threes and Fours": ItemData(16871244129, ItemClassification.progression), "Category Quadruple Ones and Twos": ItemData(16871244130, ItemClassification.progression), - "Category Micro Straight": ItemData(16871244131, ItemClassification.progression), "Category Three Odds": ItemData(16871244132, ItemClassification.progression), "Category 1-2-1 Consecutive": ItemData(16871244133, ItemClassification.progression), @@ -60,7 +57,7 @@ item_table = { "Category Five Distinct Dice": ItemData(16871244137, ItemClassification.progression), "Category 4&5 Full House": ItemData(16871244138, ItemClassification.progression), - + #filler items "Encouragement": ItemData(16871244200, ItemClassification.filler), "Fun Fact": ItemData(16871244201, ItemClassification.filler), "Story Chapter": ItemData(16871244202, ItemClassification.filler), @@ -68,14 +65,15 @@ item_table = { "Bad RNG": ItemData(16871244204, ItemClassification.trap), "Bonus Point": ItemData(16871244205, ItemClassification.useful), #not included in logic + #These points are included in the logic and might be necessary to progress. "1 Point": ItemData(16871244301, ItemClassification.progression_skip_balancing), "10 Points": ItemData(16871244302, ItemClassification.progression), "100 Points": ItemData(16871244303, ItemClassification.progression) } +#item groups for better hinting item_groups = { "Score Multiplier": { - "Score Multiplier", "Step Score Multiplier", "Fixed Score Multiplier" }, @@ -86,10 +84,8 @@ item_groups = { "Category Fours", "Category Fives", "Category Sixes", - "Category Choice", "Category Inverse Choice", - "Category Pair", "Category Three of a Kind", "Category Four of a Kind", @@ -98,17 +94,14 @@ item_groups = { "Category Large Straight", "Category Full House", "Category Yacht", - "Category Distincts", "Category Two times Ones", "Category Half of Sixes", "Category Twos and Threes", "Category Sum of Odds", "Category Sum of Evens", - "Category Double Threes and Fours", "Category Quadruple Ones and Twos", - "Category Micro Straight", "Category Three Odds", "Category 1-2-1 Consecutive", diff --git a/worlds/yachtdice/Locations.py b/worlds/yachtdice/Locations.py index 1bd091767b..1b0adccaac 100644 --- a/worlds/yachtdice/Locations.py +++ b/worlds/yachtdice/Locations.py @@ -32,7 +32,7 @@ def ini_locations(goal_score, max_score, num_locs, dif): if dif == 1: scaling = 3 elif dif == 2: - scaling = 2.2 + scaling = 2.3 scores = [] #the scores follow the function int( 1 + (perc ** scaling) * (max_score-1) ) @@ -64,5 +64,4 @@ def ini_locations(goal_score, max_score, num_locs, dif): lookup_id_to_name: typing.Dict[int, str] = {data.id: item_name for item_name, data in all_locations.items() if data.id} # we need to run this function to initialize all scores from 1 to 1000, even though not all are used -# this in order to make sure no other worlds use any ids that are similar to Yacht Dice all_locations = all_locations_fun(1000) diff --git a/worlds/yachtdice/Rules.py b/worlds/yachtdice/Rules.py index fbd7db26c0..a81e17bb56 100644 --- a/worlds/yachtdice/Rules.py +++ b/worlds/yachtdice/Rules.py @@ -4,6 +4,7 @@ from .YachtWeights import yacht_weights import math from collections import defaultdict +#List of categories, and the name of the logic class associated with it category_mappings = { "Category Ones": "Ones", "Category Twos": "Twos", @@ -49,7 +50,6 @@ category_mappings = { #The score is logic is *much* lower than the actual maximum reachable score. - class Category: def __init__(self, name, quantity = 1): self.name = name @@ -65,12 +65,11 @@ class Category: return mean_score * self.quantity - def extract_progression(state, player, options): #method to obtain a list of what items the player has. - #this includes categories, dice, rolls and score multiplier. + #this includes categories, dice, rolls and score multiplier etc. - if player == "state_is_a_list": + if player == "state_is_a_list": #the state variable is just a list with the names of the items number_of_dice = ( state.count("Dice") + state.count("Dice Fragment") // options.number_of_dice_fragments_per_dice.value @@ -90,7 +89,7 @@ def extract_progression(state, player, options): extra_points_in_logic = state.count("1 Point") extra_points_in_logic += state.count("10 Points") * 10 extra_points_in_logic += state.count("100 Points") * 100 - else: + else: #state is an Archipelago object, so we need state.count(..., player) number_of_dice = ( state.count("Dice", player) + state.count("Dice Fragment", player) // options.number_of_dice_fragments_per_dice.value @@ -126,10 +125,11 @@ def dice_simulation_strings(categories, num_dice, num_rolls, fixed_mult, step_mu if tup in yachtdice_cache.keys(): return yachtdice_cache[tup] - #sort categories because for the step multiplier, you will want low-scorig categories first + #sort categories because for the step multiplier, you will want low-scoring categories first categories.sort(key=lambda category: category.mean_score(num_dice, num_rolls)) #function to add two discrete distribution. + #defaultdict is a dict where you don't need to check if a id is present, you can just use += (lot faster) def add_distributions(dist1, dist2): combined_dist = defaultdict(float) for val1, prob1 in dist1.items(): @@ -138,7 +138,7 @@ def dice_simulation_strings(categories, num_dice, num_rolls, fixed_mult, step_mu return dict(combined_dist) #function to take the maximum of "times" i.i.d. dist1. - #I have tried using defaultdict but this made it slower. + #(I have tried using defaultdict here too but this made it slower.) def max_dist(dist1, mults): new_dist = {0: 1} for mult in mults: @@ -172,7 +172,9 @@ def dice_simulation_strings(categories, num_dice, num_rolls, fixed_mult, step_mu # Return the first value if percentile is lower than all probabilities return prev_val if prev_val is not None else sorted_values[0] - + #parameters for logic. + #perc_return is, per difficulty, the percentages of total score it returns (it averages out the values) + #diff_divide determines how many shots the logic gets per category. Lower = more shots. perc_return = [[0], [0.1, 0.5], [0.3, 0.7], [0.55, 0.85], [0.85, 0.95]][diff] diff_divide = [0, 9, 7, 3, 2][diff] @@ -188,10 +190,10 @@ def dice_simulation_strings(categories, num_dice, num_rolls, fixed_mult, step_mu dist[key] /= 100000 cat_mult = 2 ** (categories[j].quantity-1) - max_tries = j // diff_divide - mults = [(1 + fixed_mult + step_mult * ii) * cat_mult for ii in range(max(0,j - max_tries), j+1)] - + #for higher difficulties, the simulation gets multiple tries for categories. + max_tries = j // diff_divide + mults = [(1 + fixed_mult + step_mult * ii) * cat_mult for ii in range(max(0,j - max_tries), j+1)] dist = max_dist(dist, mults) total_dist = add_distributions(total_dist, dist) diff --git a/worlds/yachtdice/__init__.py b/worlds/yachtdice/__init__.py index f0dc86fc90..d0e5379141 100644 --- a/worlds/yachtdice/__init__.py +++ b/worlds/yachtdice/__init__.py @@ -40,7 +40,7 @@ class YachtDiceWorld(World): item_name_groups = item_groups - ap_world_version = "2.0.3" + ap_world_version = "2.0.4" def _get_yachtdice_data(self): return { @@ -51,7 +51,8 @@ class YachtDiceWorld(World): "race": self.multiworld.is_race, } - def generate_early(self): + #In generate early, we fill the item-pool, then determine the number of locations, and add filler items. + def generate_early(self): self.itempool = [] self.precollected = [] @@ -72,8 +73,6 @@ class YachtDiceWorld(World): if plando_setting.get("from_pool", False) is False: self.extra_plando_items += sum(value for value in plando_setting["items"].values()) - - # Create a list with the specified number of 1s num_ones = self.options.alternative_categories.value categorylist = [1] * num_ones + [0] * (16 - num_ones) @@ -81,8 +80,9 @@ class YachtDiceWorld(World): # Shuffle the list to randomize the order self.multiworld.random.shuffle(categorylist) - #A list of all possible categories. + #Every entry in the list has two categories, one 'default' category and one 'alt'. + #You get either of the two for every entry, so a total of 16 unique categories. all_categories = [ ["Category Choice", "Category Double Threes and Fours"], ["Category Inverse Choice", "Category Quadruple Ones and Twos"], @@ -135,16 +135,20 @@ class YachtDiceWorld(World): already_items = len(self.itempool) + self.extra_plando_items #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: extraPercentage = max(0.1, 0.8 - self.multiworld.players / 10) else: extraPercentage = 0.7 - extra_locations_needed = max(10, math.ceil(already_items * extraPercentage)) + #max score is the value of the last check. Goal score is the score needed to 'finish' the game self.max_score = self.options.score_for_last_check.value self.goal_score = min(self.max_score, self.options.score_for_goal.value) + #Yacht Dice adds items into the pool until a score of at least 1000 is reached. + #the yaml contains weights, which determine how likely it is that specific items get added. + #If all weights are 0, some of them will be made to be non-zero later. weights = [ self.options.weight_of_dice.value, self.options.weight_of_roll.value, @@ -167,19 +171,22 @@ class YachtDiceWorld(World): extra_points_added = 0 multipliers_added = 0 + #Keep adding items until a score of 1000 is in logic while dice_simulation(self.itempool + self.precollected, "state_is_a_list", self.options) < 1000: 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: - weights[0] = 0 #cannot have 9 dice + weights[0] = 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: - weights[1] = 0 # cannot have 6 rolls + weights[1] = 0 # don't allow >= 6 rolls + #Don't allow too many multipliers if multipliers_added > 50: weights[2] = 0 weights[3] = 0 + #Don't allow too many extra points if extra_points_added > 300: weights[5] = 0 @@ -191,8 +198,8 @@ class YachtDiceWorld(World): if extra_points_added <= 300: weights[5] = 1 + #Next, add the appropriate item. We'll slightly alter weights to avoid too many of the same item which_item_to_add = self.multiworld.random.choices([0,1,2,3,4,5], weights = weights)[0] - if which_item_to_add == 0: if frags_per_dice == 1: self.itempool += ["Dice"] @@ -214,7 +221,6 @@ class YachtDiceWorld(World): weights[3] /= 1.1 multipliers_added += 1 elif which_item_to_add == 4: - #increase chances of "free-score categories" cat_weights = [2, 2, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1] self.itempool += self.multiworld.random.choices(possible_categories, weights = cat_weights) weights[4] /= 1.1 @@ -247,15 +253,14 @@ class YachtDiceWorld(World): else: raise Exception("Invalid index when adding new items in Yacht Dice") - #count the number of locations in the game. extra_plando_items is set in generate_early - #and counts number of plando items *not* from pool. - + #count the number of locations in the game. already_items = len(self.itempool) + self.extra_plando_items + 1 #+1 because of Victory item - extra_locations_needed += (already_items - 70) // 20 + #We need to add more filler/useful items if there are many items in the pool to guarantee succesful generation + extra_locations_needed += (already_items - 45) // 15 self.number_of_locations = already_items + extra_locations_needed - # From here, we will count the number of items in the self.itempool, and add items to the pool, + # From here, we will count the number of items in the self.itempool, and add usuful/filler items to the pool, # making sure not to exceed the number of locations. #first, we flood the entire pool with extra points (useful), if that setting is chosen. @@ -281,7 +286,7 @@ class YachtDiceWorld(World): if(self.number_of_locations - already_items >= 10): self.itempool += ["Story Chapter"] * 10 - #add some extra points if there is still room + #add some more extra points if there is still room if self.options.add_bonus_points.value == 2: already_items = len(self.itempool) + self.extra_plando_items + 1 self.itempool += ["Bonus Point"] * min(self.number_of_locations - already_items, 10) @@ -299,7 +304,6 @@ class YachtDiceWorld(World): #probability of Good and Bad rng, based on difficulty for fun :) p = 1.1 - 0.25 * self.options.game_difficulty.value - already_items = len(self.itempool) + self.extra_plando_items + 1 self.itempool += self.multiworld.random.choices( ["Good RNG", "Bad RNG"], @@ -313,15 +317,16 @@ class YachtDiceWorld(World): raise Exception(f"[Yacht Dice] Number in self.itempool is not number of locations " f"{already_items} {self.number_of_locations}.") + #add precollected items using push_precollected. Items in self.itempool get created in create_items for item in self.precollected: self.multiworld.push_precollected(self.create_item(item)) def create_items(self): #convert strings to actual items - itempoolO = [item for item in map(lambda name: self.create_item(name), self.itempool)] + itempool_created = [item for item in map(lambda name: self.create_item(name), self.itempool)] #and add them to the itempool - for item in itempoolO: + for item in itempool_created: self.multiworld.itempool += [item] def create_regions(self): @@ -358,11 +363,6 @@ class YachtDiceWorld(World): set_yacht_rules(self.multiworld, self.player, self.options) set_yacht_completion_rules(self.multiworld, self.player) - maxScoreWithItems = dice_simulation(self.multiworld.get_all_state(False), self.player, self.options) - - if self.goal_score > maxScoreWithItems: - raise Exception("In Yacht Dice, with all items in the pool, it is impossible to get to the goal.") - def pre_fill(self): #in the pre_fill, make sure one dice and one roll is early, so that you will have 2 dice and 2 rolls soon self.multiworld.early_items[self.player]["Dice"] = 1 @@ -370,9 +370,7 @@ class YachtDiceWorld(World): def fill_slot_data(self): #make slot data, which consists of yachtdice_data, options, and some other variables. - yacht_dice_data = self._get_yachtdice_data() - yacht_dice_options = self.options.as_dict( "game_difficulty", "score_for_last_check", @@ -394,9 +392,7 @@ class YachtDiceWorld(World): "which_story", "allow_manual_input" ) - - slot_data = {**yacht_dice_data, **yacht_dice_options} #combine the two - + slot_data = {**yacht_dice_data, **yacht_dice_options} #combine the two slot_data["goal_score"] = self.goal_score slot_data["last_check_score"] = self.max_score slot_data["ap_world_version"] = self.ap_world_version