This commit is contained in:
spinerak
2024-06-07 23:16:16 +02:00
parent 3d30b3b8e5
commit 064c92977a
6 changed files with 185 additions and 167 deletions

View File

@@ -8,10 +8,10 @@ class ItemData(typing.NamedTuple):
class YachtDiceItem(Item):
game: str = "Yacht Dice"
#the starting index is chosen semi-randomly to be 16871244000
# the starting index is chosen semi-randomly to be 16871244000
item_table = {
#victory item, always placed manually at goal location
# victory item, always placed manually at goal location
"Victory": ItemData(16871244000-1, ItemClassification.progression),
"Dice": ItemData(16871244000, ItemClassification.progression),
@@ -19,7 +19,6 @@ item_table = {
"Roll": ItemData(16871244002, ItemClassification.progression),
"Roll Fragment": ItemData(16871244003, 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),
@@ -57,21 +56,21 @@ item_table = {
"Category Five Distinct Dice": ItemData(16871244137, ItemClassification.progression),
"Category 4&5 Full House": ItemData(16871244138, ItemClassification.progression),
#filler items
# filler items
"Encouragement": ItemData(16871244200, ItemClassification.filler),
"Fun Fact": ItemData(16871244201, ItemClassification.filler),
"Story Chapter": ItemData(16871244202, ItemClassification.filler),
"Good RNG": ItemData(16871244203, ItemClassification.filler),
"Bad RNG": ItemData(16871244204, ItemClassification.trap),
"Bonus Point": ItemData(16871244205, ItemClassification.useful), #not included in logic
"Bonus Point": ItemData(16871244205, ItemClassification.useful), # not included in logic
#These points are included in the logic and might be necessary to progress.
# 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 for better hinting
item_groups = {
"Score Multiplier": {
"Step Score Multiplier",

View File

@@ -14,31 +14,31 @@ class YachtDiceLocation(Location):
self.yacht_dice_score = score
all_locations = {}
starting_index = 16871244500 #500 more than the starting index for items
starting_index = 16871244500 # 500 more than the starting index for items
#Function that is called when this file is loaded, which loads in ALL possible locations, score 1 to 1000
# Function that is called when this file is loaded, which loads in ALL possible locations, score 1 to 1000
def all_locations_fun(max_score):
location_table = {}
for i in range(max_score+1):
location_table[f"{i} score"] = LocData(starting_index+i, "Board", i)
return location_table
#function that loads in all locations necessary for the game, so based on options.
#will make sure that goal_score and max_score are included locations
# function that loads in all locations necessary for the game, so based on options.
# will make sure that goal_score and max_score are included locations
def ini_locations(goal_score, max_score, num_locs, dif):
scaling = 2 #parameter that determines how many low-score location there are.
#need more low-score locations or lower difficulties:
scaling = 2 # parameter that determines how many low-score location there are.
# need more low-score locations or lower difficulties:
if dif == 1:
scaling = 3
elif dif == 2:
scaling = 2.3
scores = []
#the scores follow the function int( 1 + (perc ** scaling) * (max_score-1) )
#however, this will have many low values, sometimes repeating.
#to avoid repeating scores, hiscore keeps tracks of the highest score location
#and the next score will always be at least hiscore + 1
#note that curscore is at most max_score-1
# the scores follow the function int( 1 + (perc ** scaling) * (max_score-1) )
# however, this will have many low values, sometimes repeating.
# to avoid repeating scores, hiscore keeps tracks of the highest score location
# and the next score will always be at least hiscore + 1
# note that curscore is at most max_score-1
hiscore = 0
for i in range(num_locs - 1):
perc = (i/num_locs)
@@ -49,7 +49,7 @@ def ini_locations(goal_score, max_score, num_locs, dif):
scores += [curscore]
if goal_score != max_score:
#if the goal score is not in the list, find the closest one and make it the goal.
# if the goal score is not in the list, find the closest one and make it the goal.
if goal_score not in scores:
closest_num = min(scores, key=lambda x: abs(x - 500))
scores[scores.index(closest_num)] = goal_score

View File

@@ -15,8 +15,9 @@ class GameDifficulty(Choice):
option_hard = 3
option_extreme = 4
default = 2
class scoreForLastCheck(Range):
class ScoreForLastCheck(Range):
"""
The items in the item pool will always allow you to reach a score of 1000.
By default, the last check is also at a score of 1000.
@@ -26,8 +27,9 @@ class scoreForLastCheck(Range):
range_start = 500
range_end = 1000
default = 1000
class scoreForGoal(Range):
class ScoreForGoal(Range):
"""
This option determines what score you need to reach to finish the game.
It cannot be higher than the score for the last check (if it is, it is changed automatically).
@@ -37,7 +39,8 @@ class scoreForGoal(Range):
range_end = 1000
default = 777
class minimalNumberOfDiceAndRolls(Choice):
class MinimalNumberOfDiceAndRolls(Choice):
"""
The minimal number of dice and rolls in the pool.
These are guaranteed, unlike the later items.
@@ -52,7 +55,8 @@ class minimalNumberOfDiceAndRolls(Choice):
option_8_dice_and_2_rolls = 6
default = 2
class numberDiceFragmentsPerDice(Range):
class NumberDiceFragmentsPerDice(Range):
"""
Dice can be split into fragments, gathering enough will give you an extra dice.
You start with one dice, and there will always be one full dice in the pool.
@@ -63,8 +67,9 @@ class numberDiceFragmentsPerDice(Range):
range_start = 1
range_end = 5
default = 4
class numberRollFragmentsPerRoll(Range):
class NumberRollFragmentsPerRoll(Range):
"""
Rolls can be split into fragments, gathering enough will give you an extra roll.
You start with one roll, and there will always be one full roll in the pool.
@@ -75,9 +80,9 @@ class numberRollFragmentsPerRoll(Range):
range_start = 1
range_end = 5
default = 4
class alternativeCategories(Range):
class AlternativeCategories(Range):
"""
There are 16 default categories, but there are also 16 alternative categories.
These alternative categories can be randomly selected to replace the default categories.
@@ -89,9 +94,9 @@ class alternativeCategories(Range):
range_start = 0
range_end = 16
default = 0
class chanceOfDice(Range):
class ChanceOfDice(Range):
"""
The item pool is always filled in such a way that you can reach a score of 1000.
Extra progression items are added that will help you on your quest.
@@ -103,8 +108,9 @@ class chanceOfDice(Range):
range_start = 0
range_end = 100
default = 5
class chanceOfRoll(Range):
class ChanceOfRoll(Range):
"""
With more rolls, you will be able to reach higher scores.
"""
@@ -113,7 +119,8 @@ class chanceOfRoll(Range):
range_end = 100
default = 20
class chanceOfFixedScoreMultiplier(Range):
class ChanceOfFixedScoreMultiplier(Range):
"""
Getting a Fixed Score Multiplier will boost all future scores by 10%.
"""
@@ -121,8 +128,9 @@ class chanceOfFixedScoreMultiplier(Range):
range_start = 0
range_end = 100
default = 30
class chanceOfStepScoreMultiplier(Range):
class ChanceOfStepScoreMultiplier(Range):
"""
The Step Score Multiplier boosts your multiplier after every roll by 1%, and resets on sheet reset.
So, keep high scoring categories for later to get the most out of them.
@@ -132,8 +140,9 @@ class chanceOfStepScoreMultiplier(Range):
range_start = 0
range_end = 100
default = 0
class chanceOfDoubleCategory(Range):
class ChanceOfDoubleCategory(Range):
"""
This option allows categories to appear multiple times.
Each time you get a category after the first, its score value gets doubled.
@@ -142,8 +151,9 @@ class chanceOfDoubleCategory(Range):
range_start = 0
range_end = 100
default = 50
class chanceOfPoints(Range):
class ChanceOfPoints(Range):
"""
Getting points gives you... points. You can get 1 point, 10 points, and even 100 points.
"""
@@ -151,8 +161,9 @@ class chanceOfPoints(Range):
range_start = 0
range_end = 100
default = 20
class pointsSize(Choice):
class PointsSize(Choice):
"""
If you choose to add points to the item pool, do you prefer many small points,
medium-size points, a few larger points, or a mix of them?
@@ -163,8 +174,9 @@ class pointsSize(Choice):
option_large = 3
option_mix = 4
default = 2
class minimizeExtraItems(Choice):
class MinimizeExtraItems(Choice):
"""
Besides necessary items, Yacht Dice has extra useful/filler items in the item pool.
It is possible however to decrease the number of extra items in multiplayer games.
@@ -175,8 +187,9 @@ class minimizeExtraItems(Choice):
option_no_dont = 1
option_yes_please = 2
default = 1
class addExtraPoints(Choice):
class AddExtraPoints(Choice):
"""
Yacht Dice typically has space for extra items.
If there is space, would you like bonus points shuffled in the item pool?
@@ -191,8 +204,9 @@ class addExtraPoints(Choice):
option_sure = 2
option_never = 3
default = 2
class addStoryChapters(Choice):
class AddStoryChapters(Choice):
"""
Yacht Dice typically has space for more items.
If there is space, would you like story chapters shuffled in the item pool?
@@ -207,8 +221,9 @@ class addStoryChapters(Choice):
option_sure = 2
option_never = 3
default = 3
class whichStory(Choice):
class WhichStory(Choice):
"""
The most important part of Yacht Dice is the narrative.
If you choose to
@@ -225,8 +240,9 @@ class whichStory(Choice):
option_a_rollin_rhyme_adventure = 6
option_random_story = -1
default = -1
class allowManual(Choice):
class AllowManual(Choice):
"""
Yacht Dice allows players to roll IRL dice.
By sending "manual" in the chat, an input field appears where you can type your dice rolls.
@@ -237,32 +253,33 @@ class allowManual(Choice):
option_yes_allow = 1
option_no_dont_allow = 2
default = 1
@dataclass
class YachtDiceOptions(PerGameCommonOptions):
game_difficulty: gameDifficulty
score_for_last_check: scoreForLastCheck
score_for_goal: scoreForGoal
game_difficulty: GameDifficulty
score_for_last_check: ScoreForLastCheck
score_for_goal: ScoreForGoal
minimal_number_of_dice_and_rolls: minimalNumberOfDiceAndRolls
number_of_dice_fragments_per_dice: numberDiceFragmentsPerDice
number_of_roll_fragments_per_roll: numberRollFragmentsPerRoll
minimal_number_of_dice_and_rolls: MinimalNumberOfDiceAndRolls
number_of_dice_fragments_per_dice: NumberDiceFragmentsPerDice
number_of_roll_fragments_per_roll: NumberRollFragmentsPerRoll
alternative_categories: alternativeCategories
alternative_categories: AlternativeCategories
#the following options determine what extra items are shuffled into the pool:
weight_of_dice: chanceOfDice
weight_of_roll: chanceOfRoll
weight_of_fixed_score_multiplier: chanceOfFixedScoreMultiplier
weight_of_step_score_multiplier: chanceOfStepScoreMultiplier
weight_of_double_category: chanceOfDoubleCategory
weight_of_points: chanceOfPoints
points_size: pointsSize
weight_of_dice: ChanceOfDice
weight_of_roll: ChanceOfRoll
weight_of_fixed_score_multiplier: ChanceOfFixedScoreMultiplier
weight_of_step_score_multiplier: ChanceOfStepScoreMultiplier
weight_of_double_category: ChanceOfDoubleCategory
weight_of_points: ChanceOfPoints
points_size: PointsSize
minimize_extra_items: minimizeExtraItems
add_bonus_points: addExtraPoints
add_story_chapters: addStoryChapters
which_story: whichStory
minimize_extra_items: MinimizeExtraItems
add_bonus_points: AddExtraPoints
add_story_chapters: AddStoryChapters
which_story: WhichStory
allow_manual_input: allowManual
allow_manual_input: AllowManual

View File

@@ -4,7 +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
# List of categories, and the name of the logic class associated with it
category_mappings = {
"Category Ones": "Ones",
"Category Twos": "Twos",
@@ -41,21 +41,21 @@ category_mappings = {
"Category 4&5 Full House": "FourAndFiveFullHouse"
}
#This class adds logic to the apworld.
#In short, we ran a simulation for every possible combination of dice and rolls you can have, per category.
#This simulation has a good strategy for locking dice.
#This gives rise to an approximate discrete distribution per category.
#We calculate the distribution of the total score.
#We then pick a correct percentile to reflect the correct score that should be in logic.
#The score is logic is *much* lower than the actual maximum reachable score.
# This class adds logic to the apworld.
# In short, we ran a simulation for every possible combination of dice and rolls you can have, per category.
# This simulation has a good strategy for locking dice.
# This gives rise to an approximate discrete distribution per category.
# We calculate the distribution of the total score.
# We then pick a correct percentile to reflect the correct score that should be in logic.
# The score is logic is *much* lower than the actual maximum reachable score.
class Category:
def __init__(self, name, quantity = 1):
self.name = name
self.quantity = quantity #how many times you have the category
self.quantity = quantity # how many times you have the category
#return mean score of a category
# return mean score of a category
def mean_score(self, num_dice, num_rolls):
if num_dice == 0 or num_rolls == 0:
return 0
@@ -66,10 +66,10 @@ class Category:
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 etc.
# method to obtain a list of what items the player has.
# this includes categories, dice, rolls and score multiplier etc.
if player == "state_is_a_list": #the state variable is just a list with the names of the items
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
@@ -89,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: #state is an Archipelago object, so we need state.count(..., player)
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
@@ -113,23 +113,23 @@ def extract_progression(state, player, options):
return [categories, number_of_dice, number_of_rerolls,
number_of_fixed_mults * 0.1, number_of_step_mults * 0.01, extra_points_in_logic]
#We will store the results of this function as it is called often for the same parameters.
# We will store the results of this function as it is called often for the same parameters.
yachtdice_cache = {}
#Function that returns the feasible score in logic based on items obtained.
# Function that returns the feasible score in logic based on items obtained.
def dice_simulation_strings(categories, num_dice, num_rolls, fixed_mult, step_mult, diff):
tup = tuple([tuple(sorted([c.name+str(c.quantity) for c in categories])),
num_dice, num_rolls, fixed_mult, step_mult, diff]) #identifier
#if already computed, return the result
# if already computed, return the result
if tup in yachtdice_cache.keys():
return yachtdice_cache[tup]
#sort categories because for the step multiplier, you will want low-scoring 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 an id is present, you can just use += (lot faster)
# function to add two discrete distribution.
# defaultdict is a dict where you don't need to check if an id is present, you can just use += (lot faster)
def add_distributions(dist1, dist2):
combined_dist = defaultdict(float)
for val1, prob1 in dist1.items():
@@ -137,8 +137,8 @@ def dice_simulation_strings(categories, num_dice, num_rolls, fixed_mult, step_mu
combined_dist[val1 + val2] += prob1 * prob2
return dict(combined_dist)
#function to take the maximum of "times" i.i.d. dist1.
#(I have tried using defaultdict here too but this made it slower.)
# function to take the maximum of "times" i.i.d. dist1.
# (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:
@@ -157,7 +157,7 @@ def dice_simulation_strings(categories, num_dice, num_rolls, fixed_mult, step_mu
return new_dist
#Returns percentile value of a distribution.
# Returns percentile value of a distribution.
def percentile_distribution(dist, percentile):
sorted_values = sorted(dist.keys())
cumulative_prob = 0
@@ -172,13 +172,13 @@ 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.
# 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]
#calculate total distribution
# calculate total distribution
total_dist = {0: 1}
for j in range(len(categories)):
if num_dice == 0 or num_rolls == 0:
@@ -191,14 +191,14 @@ def dice_simulation_strings(categories, num_dice, num_rolls, fixed_mult, step_mu
cat_mult = 2 ** (categories[j].quantity-1)
#for higher difficulties, the simulation gets multiple tries for categories.
# 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)
#save result into the cache, then return it
# save result into the cache, then return it
outcome = sum([percentile_distribution(total_dist, perc) for perc in perc_return]) / len(perc_return)
yachtdice_cache[tup] = max(5, math.floor(outcome))
return yachtdice_cache[tup]

File diff suppressed because one or more lines are too long

View File

@@ -56,17 +56,17 @@ class YachtDiceWorld(World):
self.itempool = []
self.precollected = []
#number of dice and rolls in the pull
# 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]
#amount of dice and roll fragments needed to get a dice or roll
# 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
#count number of plando items not from pool, we need extra locations for them
# count number of plando items not from pool, we need extra locations for them
self.extra_plando_items = 0
for plando_setting in self.multiworld.plando_items[self.player]:
@@ -80,9 +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.
# 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"],
@@ -102,7 +102,7 @@ class YachtDiceWorld(World):
["Category Yacht", "Category 4&5 Full House"]
]
#categories used in this game.
# categories used in this game.
possible_categories = []
for index, cats in enumerate(all_categories):
@@ -118,37 +118,37 @@ class YachtDiceWorld(World):
self.precollected += ["Roll"]
self.precollected += ["Dice"]
#if one fragment per dice, just add "Dice" objects
# if one fragment per dice, just add "Dice" objects
if frags_per_dice == 1:
self.itempool += ["Dice"] * (num_of_dice-1) # minus one because one is in start inventory
else:
self.itempool += ["Dice"] #always add a full dice to make generation easier (will be early)
self.itempool += ["Dice"] # always add a full dice to make generation easier (will be early)
self.itempool += ["Dice Fragment"] * (frags_per_dice * (num_of_dice-2))
#if one fragment per roll, just add "Roll" objects
# if one fragment per roll, just add "Roll" objects
if frags_per_roll == 1:
self.itempool += ["Roll"] * (num_of_rolls-1) #minus one because one is in start inventory
self.itempool += ["Roll"] * (num_of_rolls-1) # minus one because one is in start inventory
else:
self.itempool += ["Roll"] #always add a full roll to make generation easier (will be early)
self.itempool += ["Roll"] # always add a full roll to make generation easier (will be early)
self.itempool += ["Roll Fragment"] * (frags_per_roll * (num_of_rolls-2))
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.
# 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
# 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.
# 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,
@@ -158,39 +158,39 @@ class YachtDiceWorld(World):
self.options.weight_of_points.value
]
#if the player wants extra rolls or dice, fill the pool with fragments until close to an extra roll/dice
# if the player wants extra rolls or dice, fill the pool with fragments until close to an extra roll/dice
if weights[0] > 0 and frags_per_dice > 1:
self.itempool += ["Dice Fragment"] * (frags_per_dice - 1)
if weights[1] > 0 and frags_per_roll > 1:
self.itempool += ["Roll Fragment"] * (frags_per_roll - 1)
#calibrate the weights, since the impact of each of the items is different
# calibrate the weights, since the impact of each of the items is different
weights[0] = weights[0] / 5 * frags_per_dice
weights[1] = weights[1] / 5 * frags_per_roll
extra_points_added = 0
multipliers_added = 0
#Keep adding items until a score of 1000 is in logic
# 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 #don't allow >=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 # don't allow >= 6 rolls
weights[1] = 0 # don't allow >= 6 rolls
#Don't allow too many multipliers
# Don't allow too many multipliers
if multipliers_added > 50:
weights[2] = 0
weights[3] = 0
#Don't allow too many extra points
# Don't allow too many extra points
if extra_points_added > 300:
weights[5] = 0
#if all weights are zero, allow to add fixed score multiplier, double category, points.
# if all weights are zero, allow to add fixed score multiplier, double category, points.
if sum(weights) == 0:
if multipliers_added <= 50:
weights[2] = 1
@@ -198,7 +198,7 @@ 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
# 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:
@@ -226,16 +226,16 @@ class YachtDiceWorld(World):
weights[4] /= 1.1
elif which_item_to_add == 5:
score_dist = self.options.points_size.value
probs = [1,0,0]
probs = [1, 0, 0]
if score_dist == 1:
probs = [0.9,0.08,0]
probs = [0.9, 0.08, 0]
if score_dist == 2:
probs = [0,1,0]
probs = [0, 1, 0]
if score_dist == 3:
probs = [0,0.3,0.7]
probs = [0, 0.3, 0.7]
if score_dist == 4:
probs = [0.3,0.4,0.3]
c = self.multiworld.random.choices([0,1,2], weights = probs)[0]
probs = [0.3, 0.4, 0.3]
c = self.multiworld.random.choices([0, 1, 2], weights = probs)[0]
if c == 0:
self.itempool += ["1 Point"]
extra_points_added += 1
@@ -253,56 +253,56 @@ class YachtDiceWorld(World):
else:
raise Exception("Invalid index when adding new items in Yacht Dice")
#count the number of locations in the game.
already_items = len(self.itempool) + self.extra_plando_items + 1 #+1 because of Victory item
# count the number of locations in the game.
already_items = len(self.itempool) + self.extra_plando_items + 1 # +1 because of Victory item
#We need to add more filler/useful items if there are many items in the pool to guarantee successful generation
# We need to add more filler/useful items if there are many items in the pool to guarantee successful 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 useful/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.
# 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
already_items = len(self.itempool) + self.extra_plando_items + 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
# 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
already_items = len(self.itempool) + self.extra_plando_items + 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
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
# add some extra points (useful)
if self.options.add_bonus_points.value == 2: # add extra points if wanted
already_items = len(self.itempool) + self.extra_plando_items + 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
# add some story chapters (filler)
if self.options.add_story_chapters.value == 2: # add extra points if wanted
already_items = len(self.itempool) + self.extra_plando_items + 1
if self.number_of_locations - already_items >= 10:
self.itempool += ["Story Chapter"] * 10
#add some more 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)
#add some encouragements filler-items if there is still room
# add some encouragements filler-items if there is still room
already_items = len(self.itempool) + self.extra_plando_items + 1
self.itempool += ["Encouragement"] * min(self.number_of_locations - already_items, 5)
#add some fun facts filler-items if there is still room
# add some fun facts filler-items if there is still room
already_items = len(self.itempool) + self.extra_plando_items + 1
self.itempool += ["Fun Fact"] * min(self.number_of_locations - already_items, 5)
#finally, add some "Good RNG" and "Bad RNG" items to complete the item pool
#these items are filler and do not do anything.
# finally, add some "Good RNG" and "Bad RNG" items to complete the item pool
# these items are filler and do not do anything.
#probability of Good and Bad rng, based on difficulty for fun :)
# 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(
@@ -311,17 +311,17 @@ class YachtDiceWorld(World):
k=self.number_of_locations - already_items
)
#we are done adding items. Now because of the last step, number of items should be number of locations
# we are done adding items. Now because of the last step, number of items should be number of locations
already_items = len(self.itempool) + self.extra_plando_items + 1
if already_items != self.number_of_locations:
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
# 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))
#make sure one dice and one roll is early, so that you will have 2 dice and 2 rolls soon
# 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
self.multiworld.early_items[self.player]["Roll"] = 1
@@ -329,41 +329,41 @@ class YachtDiceWorld(World):
self.multiworld.itempool += [self.create_item(name) for name in self.itempool]
def create_regions(self):
#call the ini_locations function, that generates locations based on the inputs.
# call the ini_locations function, that generates locations based on the inputs.
location_table, goal_index = ini_locations(self.goal_score, self.max_score, self.number_of_locations,
self.options.game_difficulty.value)
#simple menu-board construction
# simple menu-board construction
menu = Region("Menu", self.player, self.multiworld)
board = Region("Board", self.player, self.multiworld)
#add locations to board, one for every location in the location_table
# add locations to board, one for every location in the location_table
board.locations = [YachtDiceLocation(self.player, loc_name, loc_data.score, loc_data.id, board)
for loc_name, loc_data in location_table.items() if loc_data.region == board.name]
#which index of all locations should have the Victory item.
# which index of all locations should have the Victory item.
# Add the victory item to the correct location.
# The website declares that the game is complete when the victory item is obtained.
board.locations[goal_index].place_locked_item(self.create_item("Victory"))
#these will be slot_data input
# these will be slot_data input
self.goal_score = board.locations[goal_index].yacht_dice_score
self.max_score = board.locations[-1].yacht_dice_score
#add the regions
# add the regions
connection = Entrance(self.player, "New Board", menu)
menu.exits.append(connection)
connection.connect(board)
self.multiworld.regions += [menu, board]
def set_rules(self):
#set rules per location, and add the rule for beating the game
# set rules per location, and add the rule for beating the game
set_yacht_rules(self.multiworld, self.player, self.options)
set_yacht_completion_rules(self.multiworld, self.player)
def fill_slot_data(self):
#make slot data, which consists of yachtdice_data, options, and some other variables.
# 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",
@@ -386,7 +386,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
@@ -397,6 +397,7 @@ class YachtDiceWorld(World):
item = YachtDiceItem(name, item_data.classification, item_data.code, self.player)
return item
# We overwrite these function to monitor when states have changed. See also dice_simulation in Rules.py
def collect(self, state: CollectionState, item: Item) -> bool:
change = super().collect(state, item)
if change: