diff --git a/worlds/yachtdice/Items.py b/worlds/yachtdice/Items.py index e2c2de30ba..33cf3b3fc4 100644 --- a/worlds/yachtdice/Items.py +++ b/worlds/yachtdice/Items.py @@ -17,7 +17,10 @@ item_table = { "Dice Fragment": ItemData(16871244001, ItemClassification.progression), "Roll": ItemData(16871244002, ItemClassification.progression), "Roll Fragment": ItemData(16871244003, ItemClassification.progression), + "Score Multiplier": ItemData(16871244004, ItemClassification.progression), + "Fixed Score Multiplier": ItemData(16871244005, ItemClassification.progression), + "Step Score Multiplier": ItemData(16871244006, ItemClassification.progression), "Category Ones": ItemData(16871244103, ItemClassification.progression), "Category Twos": ItemData(16871244104, ItemClassification.progression), @@ -63,7 +66,7 @@ item_table = { "Story Chapter": ItemData(16871244202, ItemClassification.filler), "Good RNG": ItemData(16871244203, ItemClassification.filler), "Bad RNG": ItemData(16871244204, ItemClassification.trap), - "Extra Point": ItemData(16871244205, ItemClassification.useful), + "Extra Point": ItemData(16871244205, ItemClassification.useful), #not included in logic "1 Point": ItemData(16871244301, ItemClassification.progression_skip_balancing), "10 Points": ItemData(16871244302, ItemClassification.progression), @@ -71,6 +74,11 @@ item_table = { } ITEM_GROUPS = { + "Score Multiplier": { + "Score Multiplier", + "Step Score Multiplier", + "Fixed Score Multiplier" + }, "Categories": { "Category Ones", "Category Twos", diff --git a/worlds/yachtdice/Locations.py b/worlds/yachtdice/Locations.py index b54e896a6a..6bcc97dece 100644 --- a/worlds/yachtdice/Locations.py +++ b/worlds/yachtdice/Locations.py @@ -25,9 +25,8 @@ def all_locations_fun(max_score): return location_table #function that loads in all locations necessary for the game, so based on options. -def ini_locations(max_score, num_locs, dif): - location_table = {} - +#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: if dif == 1: @@ -35,25 +34,31 @@ def ini_locations(max_score, num_locs, dif): elif dif == 2: scaling = 2.2 + 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 hiscore = 0 - for i in range(num_locs): + for i in range(num_locs - 1): perc = (i/num_locs) curscore = int( 1 + (perc ** scaling) * (max_score-2) ) if(curscore <= hiscore): curscore = hiscore + 1 hiscore = curscore - location_table[f"{curscore} score"] = LocData(starting_index + curscore, "Board", curscore) + scores += [curscore] + + #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 - #Finally, add a check for the actual max_score. - #This is *not* counted in num_locs, since the victory items is not as well. - location_table[f"{max_score} score"] = LocData(starting_index + max_score, "Board", max_score) + scores += [max_score] + + location_table = {f"{score} score": LocData(starting_index + score, "Board", score) for score in scores} - return location_table + return location_table, scores.index(goal_score) lookup_id_to_name: typing.Dict[int, str] = {data.id: item_name for item_name, data in all_locations.items() if data.id} diff --git a/worlds/yachtdice/Options.py b/worlds/yachtdice/Options.py index a351da45ee..60c8a96ea7 100644 --- a/worlds/yachtdice/Options.py +++ b/worlds/yachtdice/Options.py @@ -1,26 +1,57 @@ from Options import Choice, Range, PerGameCommonOptions from dataclasses import dataclass -class numberOfDiceAndRolls(Choice): +class gameDifficulty(Choice): """ - Total number of dice and rolls in the pool. - You start with one dice and one roll. - This option does not change the final goal. + Difficulty. This setting determines how difficult the scores are to achieve. + Easy: for beginners. No luck required, just roll the dice and have fun. Lower final goal. + Medium: intended difficulty. If you play smart, you'll finish the game without any trouble. + Hard: you'll need to play smart and be lucky. + Extreme: really hard mode, which requires many brain wrinkles and insane luck. NOT RECOMMENDED FOR MULTIWORLDS. """ - display_name = "Number of dice and rolls in pool" - option_5_dice_and_5_rolls = 5 - option_6_dice_and_4_rolls = 6 - option_7_dice_and_3_rolls = 7 - option_8_dice_and_2_rolls = 8 - default = 5 + display_name = "Game difficulty" + option_easy = 1 + option_medium = 2 + option_hard = 3 + option_extreme = 4 + default = 2 + +class scoreForLastCheck(Range): + """ + The items in the itempool will always allow you to reach this score of 1000. + By default, the last check is at a score of 1000. + However, you can set the score for last check lower. This will make the game shorter and easier. + """ + display_name = "Score for last check" + range_start = 500 + range_end = 1000 + default = 1000 -# class numberOfExtraRolls(Range): -# """Total number of extra rolls you can add to your collection. -# Wait this is always 4? Yes, I removed other options, but they might return.""" -# display_name = "Number of extra rolls" -# range_start = 4 -# range_end = 4 -# default = 4 +class scoreForGoal(Range): + """ + This setting determines what score you need to get to finish the game. + It cannot be higher than the score for last check (if it is, it is changed automatically). + """ + display_name = "Score for goal" + range_start = 500 + range_end = 1000 + default = 777 + +class minimalNumberOfDiceAndRolls(Choice): + """ + The minimal number of dice and rolls in the pool. + There is an option later on that, if selected, can put more dice or rolls in the pool. + You can never get more than 8 dice and 5 rolls. + You start with one dice and one roll. + """ + display_name = "Minimal number of dice and rolls in pool" + option_2_dice_and_2_rolls = 1 + option_5_dice_and_3_rolls = 2 + option_5_dice_and_5_rolls = 3 + option_6_dice_and_4_rolls = 4 + option_7_dice_and_3_rolls = 5 + option_8_dice_and_2_rolls = 6 + default = 2 class numberDiceFragmentsPerDice(Range): """ @@ -34,8 +65,6 @@ class numberDiceFragmentsPerDice(Range): range_end = 5 default = 4 - - class numberRollFragmentsPerRoll(Range): """ Rolls can be split into fragments, gathering enough will give you an extra roll. @@ -48,140 +77,117 @@ class numberRollFragmentsPerRoll(Range): range_end = 5 default = 4 - -class numberExtraDiceFragments(Range): - """ - Number of extra dice fragments in the pool. - The number cannot exceed the number of dice fragments per dice, - if it does, the generation will lower this setting automatically. - The option will never give an extra full dice, but makes it easier to collect all dice. - """ - display_name = "Number of extra dice fragments in the pool" - range_start = 0 - range_end = 4 - default = 3 - - - -class numberExtraRollFragments(Range): - """ - Number of extra roll fragments in the pool. - The number cannot exceed the number of roll fragments per roll - if it does, the generation will lower this setting automatically. - The option will never give an extra full roll, but makes it easier to collect all roll. - """ - display_name = "Number of extra roll fragments in the pool" - range_start = 0 - range_end = 4 - default = 3 - - -class gameDifficulty(Choice): - """ - Difficulty. This setting determines how difficult the scores are to achieve. - Easy: for beginners. No luck required, just roll the dice and have fun. Lower final goal. - Medium: intended difficulty. If you play smart, you'll finish the game without any trouble. - Hard: you may need to play smart, be lucky and understand the score multiplier mechanic. Higher final goal. - Extreme: more strict logic, higher final goal. ONLY FOR EXPERIENCES PLAYERS IN MULTIWORLDS. - """ - display_name = "Game difficulty" - option_easy = 1 - option_medium = 2 - option_hard = 3 - option_extreme = 4 - #option_nightmare = 5 - - default = 2 - - -class goalLocationPercentage(Range): - """ - What percentage of checks you need to get, to 'finish' the game. - Low percentage means you can probably 'finish' the game with some of the dice/rolls/categories. - High percentage means you need most of the useful items, and on higher difficulties you might need them all. - """ - display_name = "Goal percentage location" - range_start = 70 - range_end = 100 - default = 90 - class alternativeCategories(Range): """ There are 16 default categories, but there are also 16 alternative categories. These alternative categories can replace the default categories. + They are a little strange, but can give a fun new experience. + In the game, you can hover over categories to check what they do. How many alternative categories would you like to see in your game? """ display_name = "Number of alternative categories" range_start = 0 range_end = 16 - default = 0 - + default = 0 - -class scoreMultiplierType(Choice): +class chanceOfDice(Range): """ - There are 10 Score Multiplier items available. - This options decides how the Score Multipliers work. - Both options are of similar difficulty. + The itempool is always filled in such a way, that you can get to a score of 1000. + Extra items are shuffled in the itempool that will help you on your quest. - fixed_multiplier: every multiplier item gives you +10%. - Every score gets multiplied with the multiplier. - So with all score multipliers, all scores get +100%. - - step_multiplier: every multiplier item gives you +1%. - Your multiplier increases with this percentage after every turn. - So in the first turn you have no multiplier, but in turn 16, you can have a +150% multiplier. - So, save your high-scoring categories for last. This option allow for more strategic games. + The higher this chance compared to the others, the more Dice (or Dice Fragments if selected) are added. + And of course, more dice = more points! """ - display_name = "Score multiplier type" - option_fixed_multiplier = 1 - option_step_multiplier = 2 - default = 1 + display_name = "Chance of adding Dice" + range_start = 0 + range_end = 100 + default = 5 -class gameMode(Choice): +class chanceOfRoll(Range): """ - Yacht Dice has three main game modes: + The itempool is always filled in such a way, that you can get to a score of 1000. + Extra items are shuffled in the itempool that will help you on your quest. - Standard. Get to 500 points on medium difficulty (and a bit lower/higher on other difficulties). - - Points mode: a bunch of "10 Points" items are shuffled through the item pool. Get to 1000 points. - The amount of "10 Points" items in the pool depends on your selected difficulty. - - I'll add a new category soon™ + The higher this chance compared to the others, the more Rolls (or Roll Fragments if selected) are added. + And with more rolls, you'll be able to reach higher scores. """ - # Extra categories: categories may appear multiple times. - # Getting a category again gives a x2 multiplier for that category. Get to 1000 points. - # The amount of "10 Points" items in the pool depends on your selected difficulty. - - display_name = "Game mode" - option_standard = 1 - option_points_mode = 2 - option_extra_categories = 3 - default = 1 - -class pointsGameMode(Choice): - """ - If points mode is selected, this option determines the value of the points items. - - yes_1_per_item: hundreds of "1 Point" items are shuffled into the pool. - NOT recommended in multiplayer, unless everyone is aware of the hundred of points items - - yes_10_per_item: puts tens of "10 Points" (and a few 1 Points) into the item pool. - - yes_100_per_item: puts a few "100 Points" (and a few 1 and 10 Points) into the item pool. - Warning: will unlock many checks if an 100 Points item is collected. - """ - display_name = "Value of points item" - option_1_per_item = 2 - option_10_per_item = 3 - option_100_per_item = 4 - default = 3 + display_name = "Chance of adding Roll" + range_start = 0 + range_end = 100 + default = 20 +class chanceOfFixedScoreMultiplier(Range): + """ + The itempool is always filled in such a way, that you can get to a score of 1000. + Extra items are shuffled in the itempool that will help you on your quest. + + The higher this chance compared to the others, the more Fixed Score Multipliers are added. + Getting a Fixed Score Multiplier will boost all future scores by 10%. + """ + display_name = "Chance of adding Fixed Score Multiplier" + range_start = 0 + range_end = 100 + default = 30 + +class chanceOfStepScoreMultiplier(Range): + """ + The itempool is always filled in such a way, that you can get to a score of 1000. + Extra items are shuffled in the itempool that will help you on your quest. + + The higher this chance compared to the others, the more Step Score Multipliers are added. + The Step Score Multiplier boosts your multiplier after every roll by 1%, and resets on reset. + So, keep high scoring categories for last to get the most of them. + By default, this item is not included. It is fun however, you just need to know the above strategy. + """ + display_name = "Chance of adding Step Score Multiplier" + range_start = 0 + range_end = 100 + default = 0 + +class chanceOfDoubleCategory(Range): + """ + The itempool is always filled in such a way, that you can get to a score of 1000. + Extra items are shuffled in the itempool that will help you on your quest. + + The higher this chance compared to the others, the more frequently categories will appear multiple times. + Getting a category for the second time, gives a x2 multiplier for that category. + And getting the category again will double it again! + """ + display_name = "Chance of adding Double Category" + range_start = 0 + range_end = 100 + default = 50 + +class chanceOfPoints(Range): + """ + The itempool is always filled in such a way, that you can get to a score of 1000. + Extra items are shuffled in the itempool that will help you on your quest. + + The higher this chance compared to the others, the more points are added into the pool. + And getting points gives you... points. You can get 1 point, 10 points, and even 100 points. + """ + display_name = "Chance of adding Points" + range_start = 0 + range_end = 100 + default = 20 + +class pointsSize(Choice): + """ + If you choose to add points to the item pool, do you prefer many small points, + a few larger points, or a mix of both? + """ + display_name = "Size of points" + option_small = 1 + option_medium = 2 + option_large = 3 + option_mix = 4 + default = 2 class minimizeExtraItems(Choice): """ - Would you like to minimize the number of extra items in the pool? - Note that if you put this on, categories Fives, Sixes and Pair are put early into the playthrough. + Besides necessary items, Yacht Dice needs extra items (see below) in the item pool to ensure success in generation. + It is possible however to decrease the number of extra items, + by putting categories Fives, Sixes and Pair early into the playthrough. Would you like to do this? """ display_name = "Minimize extra items" option_no_dont = 1 @@ -190,12 +196,13 @@ class minimizeExtraItems(Choice): class addExtraPoints(Choice): """ - Yacht Dice typically has space for more items. - Would you like extra points shuffled in the item pool? + Yacht Dice typically has space for extra items. + If there is space, would you like extra points shuffled in the item pool? They make the game a little bit easier, as they are not considered in the logic. - all_of_it: put as many extra points in locations as possible + + all_of_it: fill all locations with extra points sure: put some extra points in - never: don't but any extra points + never: don't put any extra points """ display_name = "Extra points in the pool" option_all_of_it = 1 @@ -206,8 +213,12 @@ class addExtraPoints(Choice): class addStoryChapters(Choice): """ Yacht Dice typically has space for more items. - Would you like story chapters shuffled in the item pool? - Note: if you have extra points on "all_of_it" there won't be story chapters. + If there is space, would you like story chapters shuffled in the item pool? + Note: if you have extra points on "all_of_it", there won't be story chapters. + + all_of_it: fill all locations with story chapters + sure: if there is space left, put in 10 story chapters. + never: don't put any story chapters, I don't like reading (but I'm glad you're reading THIS!) """ display_name = "Extra story chapters in the pool" option_all_of_it = 1 @@ -217,7 +228,8 @@ class addStoryChapters(Choice): class whichStory(Choice): """ - The most important part of Yacht Dice is the narrative. + The most important part of Yacht Dice is the narrative. + If you choose to 10 story chapters are shuffled into the item pool. You can read them in the feed on the website. Which story would you like to read? @@ -234,17 +246,25 @@ class whichStory(Choice): @dataclass class YachtDiceOptions(PerGameCommonOptions): - number_of_dice_and_rolls: numberOfDiceAndRolls - number_of_dice_fragments_per_dice: numberDiceFragmentsPerDice - number_of_extra_dice_fragments: numberExtraDiceFragments - number_of_roll_fragments_per_roll: numberRollFragmentsPerRoll - number_of_extra_roll_fragments: numberExtraRollFragments game_difficulty: gameDifficulty - goal_location_percentage: goalLocationPercentage + 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 + alternative_categories: alternativeCategories - score_multiplier_type: scoreMultiplierType - game_mode: gameMode - points_game_mode: pointsGameMode + + #the following options determine what extra items are shuffled into the pool: + chance_of_dice: chanceOfDice + chance_of_roll: chanceOfRoll + chance_of_fixed_score_multiplier: chanceOfFixedScoreMultiplier + chance_of_step_score_multiplier: chanceOfStepScoreMultiplier + chance_of_double_category: chanceOfDoubleCategory + chance_of_points: chanceOfPoints + points_size: pointsSize + minimize_extra_items: minimizeExtraItems add_extra_points: addExtraPoints add_story_chapters: addStoryChapters diff --git a/worlds/yachtdice/Rules.py b/worlds/yachtdice/Rules.py index 2039e0e966..299217bb36 100644 --- a/worlds/yachtdice/Rules.py +++ b/worlds/yachtdice/Rules.py @@ -68,44 +68,57 @@ def extractProgression(state, player, options): #method to obtain a list of what items the player has. #this includes categories, dice, rolls and score multiplier. - number_of_dice = ( - state.count("Dice", player) - + state.count("Dice Fragment", player) // options.number_of_dice_fragments_per_dice.value - ) - - number_of_rerolls = ( - state.count("Roll", player) - + state.count("Roll Fragment", player) // options.number_of_roll_fragments_per_roll.value - ) - - number_of_mults = state.count("Score Multiplier", player) + if player == "state_is_a_list": + number_of_dice = ( + state.count("Dice") + + state.count("Dice Fragment") // options.number_of_dice_fragments_per_dice.value + ) + number_of_rerolls = ( + state.count("Roll") + + state.count("Roll Fragment") // options.number_of_roll_fragments_per_roll.value + ) + roll = state.count("Roll") + rollfragments = state.count("Roll Fragment") + number_of_fixed_mults = state.count("Fixed Score Multiplier") + number_of_step_mults = state.count("Step Score Multiplier") + categories = [] + for category_name, category_value in category_mappings.items(): + if state.count(category_name) >= 1: + categories += [Category(category_value, state.count(category_name))] + 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: + number_of_dice = ( + state.count("Dice", player) + + state.count("Dice Fragment", player) // options.number_of_dice_fragments_per_dice.value + ) + number_of_rerolls = ( + state.count("Roll", player) + + state.count("Roll Fragment", player) // options.number_of_roll_fragments_per_roll.value + ) + roll = state.count("Roll", player) + rollfragments = state.count("Roll Fragment", player) + number_of_fixed_mults = state.count("Fixed Score Multiplier", player) + number_of_step_mults = state.count("Step Score Multiplier", player) + categories = [] + for category_name, category_value in category_mappings.items(): + if state.count(category_name, player) >= 1: + categories += [Category(category_value, state.count(category_name, player))] + extra_points_in_logic = state.count("1 Point", player) + extra_points_in_logic += state.count("10 Points", player) * 10 + extra_points_in_logic += state.count("100 Points", player) * 100 - - score_mult = -10000 - if options.score_multiplier_type.value == 1: #fixed - score_mult = 0.1 * number_of_mults - if options.score_multiplier_type.value == 2: #step - score_mult = 0.01 * number_of_mults - - categories = [] - - for category_name, category_value in category_mappings.items(): - if state.count(category_name, player) >= 1: - categories += [Category(category_value, state.count(category_name, player))] - - extra_points_in_logic = state.count("1 Point", player) - extra_points_in_logic += state.count("10 Points", player) * 10 - extra_points_in_logic += state.count("100 Points", player) * 100 - - - return [categories, number_of_dice, number_of_rerolls, score_mult, extra_points_in_logic] + 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. yachtdice_cache = {} #Function that returns the feasible score in logic based on items obtained. -def diceSimulationStrings(categories, nbDice, nbRolls, multiplier, diff, scoremulttype): - tup = tuple([tuple(sorted([c.name+str(c.quantity) for c in categories])), nbDice, nbRolls, multiplier, diff, scoremulttype]) #identifier +def diceSimulationStrings(categories, nbDice, nbRolls, fixed_mult, step_mult, diff): + tup = tuple([tuple(sorted([c.name+str(c.quantity) for c in categories])), + nbDice, nbRolls, fixed_mult, step_mult, diff]) #identifier #if already computed, return the result if tup in yachtdice_cache.keys(): @@ -161,11 +174,7 @@ def diceSimulationStrings(categories, nbDice, nbRolls, multiplier, diff, scoremu percReturn = [0, 0.4, 0.4, 0.45, 0.45, 0.45][diff] - diffDivide = [0, 9, 5, 3, 2, 1][diff] - - if scoremulttype == 2: - percReturn = [0, 0.4, 0.4, 0.45, 0.45, 0.45][diff] - diffDivide = [0, 9, 8, 5, 4, 3][diff] + diffDivide = [0, 9, 8, 5, 4, 3][diff] #calculate total distribution total_dist = {0: 1} @@ -178,15 +187,10 @@ def diceSimulationStrings(categories, nbDice, nbRolls, multiplier, diff, scoremu for key in dist.keys(): dist[key] /= 100000 - #for higher difficulties, the simulation gets multiple tries for categories. dist = max_dist(dist, max(1, len(categories) // diffDivide)) - cur_mult = -100 - if scoremulttype == 1: #fixed - cur_mult = multiplier - if scoremulttype == 2: #step - cur_mult = j * multiplier + cur_mult = fixed_mult + step_mult * j total_dist = add_distributions(total_dist, dist, (1 + cur_mult) * ( 2 ** (categories[j].quantity-1) )) #save result into the cache, then return it @@ -195,43 +199,10 @@ def diceSimulationStrings(categories, nbDice, nbRolls, multiplier, diff, scoremu # Returns the feasible score that one can reach with the current state, options and difficulty. def diceSimulation(state, player, options): - categories, nbDice, nbRolls, multiplier, expoints = extractProgression(state, player, options) - return diceSimulationStrings(categories, nbDice, nbRolls, multiplier, - options.game_difficulty.value, options.score_multiplier_type.value) + expoints + categories, nbDice, nbRolls, fixed_mult, step_mult, expoints = extractProgression(state, player, options) + return diceSimulationStrings(categories, nbDice, nbRolls, fixed_mult, step_mult, + options.game_difficulty.value) + expoints -def calculateScoreInLogic(state, options): - number_of_dice = ( - state.count("Dice") - + state.count("Dice Fragment") // options.number_of_dice_fragments_per_dice.value - ) - - number_of_rerolls = ( - state.count("Roll") - + state.count("Roll Fragment") // options.number_of_roll_fragments_per_roll.value - ) - - number_of_mults = state.count("Score Multiplier") - - - score_mult = -10000 - if options.score_multiplier_type.value == 1: #fixed - score_mult = 0.1 * number_of_mults - if options.score_multiplier_type.value == 2: #step - score_mult = 0.01 * number_of_mults - - categories = [] - - for category_name, category_value in category_mappings.items(): - if state.count(category_name) >= 1: - categories += [Category(category_value, state.count(category_name))] - - 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 - - return diceSimulationStrings(categories, number_of_dice, number_of_rerolls, score_mult, - options.game_difficulty.value, options.score_multiplier_type.value) + extra_points_in_logic - # Sets rules on entrances and advancements that are always applied def set_yacht_rules(world: MultiWorld, player: int, options): for l in world.get_locations(player): diff --git a/worlds/yachtdice/__init__.py b/worlds/yachtdice/__init__.py index 77b20330b4..49fff40c3b 100644 --- a/worlds/yachtdice/__init__.py +++ b/worlds/yachtdice/__init__.py @@ -2,7 +2,7 @@ from BaseClasses import Region, Entrance, Item, Tutorial from .Items import YachtDiceItem, item_table, ITEM_GROUPS from .Locations import YachtDiceLocation, all_locations, ini_locations from .Options import YachtDiceOptions -from .Rules import set_yacht_rules, set_yacht_completion_rules, calculateScoreInLogic, diceSimulation +from .Rules import set_yacht_rules, set_yacht_completion_rules, diceSimulation from ..AutoWorld import World, WebWorld import math import logging @@ -38,7 +38,7 @@ class YachtDiceWorld(World): item_name_groups = ITEM_GROUPS - ap_world_version = "1.1.1" + ap_world_version = "1.2" def _get_yachtdice_data(self): return { @@ -51,24 +51,17 @@ class YachtDiceWorld(World): def generate_early(self): self.itempool = [] - self.precollected = [] - - #calculate the maximum score goal: - game_difficulty = self.options.game_difficulty.value + self.precollected = [] #number of dice and rolls in the pull - numDice = self.options.number_of_dice_and_rolls.value # either 5, 6, 7 or 8 - numRolls = 10 - numDice # either 5, 4, 3 en 2 + ind_dice_rolls = self.options.minimal_number_of_dice_and_rolls.value + + numDice = [0, 2, 5, 5, 6, 7, 8][ind_dice_rolls] + numRolls = [0, 2, 3, 5, 4, 3, 2][ind_dice_rolls] #amount of dice and roll fragments needed to get a dice or roll amDiceF = self.options.number_of_dice_fragments_per_dice.value amRollsF = self.options.number_of_roll_fragments_per_roll.value - - #number of extra dice and roll fragments in the pool, - #so that you don't have to wait for that one last fragment - #capped to be one less than number of fragments needed to complete a new dice/roll. - exDiceF = max(0, min(amDiceF - 1, self.options.number_of_extra_dice_fragments.value) ) - exRollsF = max(0, min(amRollsF - 1, self.options.number_of_extra_roll_fragments.value) ) #count number of plando items not from pool, we need extra locations for them self.extra_plando_items = 0 @@ -87,7 +80,7 @@ class YachtDiceWorld(World): self.multiworld.random.shuffle(categorylist) - #add all categories. Note: not "choice" and "inverse choice", they are obtained at the start + #A list of all possible categories. all_categories = [ ["Category Choice", "Category Double Threes and Fours"], ["Category Inverse Choice", "Category Quadruple Ones and Twos"], @@ -107,43 +100,39 @@ class YachtDiceWorld(World): ["Category Yacht", "Category 4&5 Full House"] ] + #categories used in this game. possible_categories = [] for index, cats in enumerate(all_categories): possible_categories += [cats[categorylist[index]]] + + # Add Choice and Inverse choice (or their alts) to the precollected list. if index == 0 or index == 1: self.precollected += [cats[categorylist[index]]] else: self.itempool += [cats[categorylist[index]]] - + # Also start with one Roll and one Dice 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 amDiceF == 1: self.itempool += ["Dice"] * (numDice-1) #minus one because one is in start inventory else: - self.itempool += ["Dice"] #always add a full dice to make generation easier - #add dice fragments, note the -2 because one is added in the previous line, one is in start inventory - self.itempool += ["Dice Fragment"] * (amDiceF * (numDice-2) + exDiceF) + self.itempool += ["Dice"] #always add a full dice to make generation easier (will be 'early') + self.itempool += ["Dice Fragment"] * (amDiceF * (numDice-2)) #if one fragment per roll, just add "Roll" objects if amRollsF == 1: self.itempool += ["Roll"] * (numRolls-1) #minus one because one is in start inventory else: - self.itempool += ["Roll"] #always add a full roll to make generation easier - #add roll fragments, note the -2 because one is added in the previous line, one is in start inventory - self.itempool += ["Roll Fragment"] * (amRollsF * (numRolls-2) + exRollsF) - - #always add exactly 10 score multipliers - self.itempool += ["Score Multiplier"] * 10 - - #At this point, the itempool has all basic items. - scoreInLogic = calculateScoreInLogic(self.itempool+self.precollected, self.options) - + self.itempool += ["Roll"] #always add a full roll to make generation easier (will be 'early') + self.itempool += ["Roll Fragment"] * (amRollsF * (numRolls-2)) + already_items = len(self.itempool) + self.extra_plando_items + #Yacht Dice needs extra filler items so it doesn't get stuck in generation. if self.options.minimize_extra_items.value == 2: extraPercentage = max(0.1, 0.5 - self.multiworld.players / 10) else: @@ -151,39 +140,95 @@ class YachtDiceWorld(World): extraLocationsNeeded = max(10, math.ceil(already_items * extraPercentage)) - if self.options.game_mode.value == 1: - self.max_score = scoreInLogic - #print(f"Max score: {self.max_score}, difficulty {game_difficulty}") + self.max_score = self.options.score_for_last_check.value + self.goal_score = min(self.max_score, self.options.score_for_goal.value) - if self.options.game_mode.value == 2: - self.max_score = 1000 - self.extra_points_for_game_mode = self.max_score - scoreInLogic + weights = [ + self.options.chance_of_dice.value, + self.options.chance_of_roll.value, + self.options.chance_of_fixed_score_multiplier.value, + self.options.chance_of_step_score_multiplier.value, + self.options.chance_of_double_category.value, + self.options.chance_of_points.value + ] + + if self.options.chance_of_dice.value > 0: + if amDiceF > 1: + self.itempool += ["Dice Fragment"] * (amDiceF - 1) + if self.options.chance_of_roll.value > 0: + if amRollsF > 1: + self.itempool += ["Roll Fragment"] * (amRollsF - 1) + + + + while diceSimulation(self.itempool + self.precollected, "state_is_a_list", self.options) < 1000: + print("Max score currently ") + print(diceSimulation(self.itempool + self.precollected, "state_is_a_list", self.options)) + print(self.itempool) - if(self.options.points_game_mode.value >= 4): - while self.extra_points_for_game_mode >= 89: #rather have 100 points than lot of smaller items - self.itempool += ["100 Points"] - self.extra_points_for_game_mode -= 100 + allitems = self.itempool + self.precollected + dice_fragments_in_pool = allitems.count("Dice") * amDiceF + allitems.count("Dice Fragment") + if dice_fragments_in_pool + 1 >= 9 * amDiceF: + weights[0] = 0 #can't have 9 dice + roll_fragments_in_pool = allitems.count("Roll") * amDiceF + allitems.count("Roll Fragment") + if roll_fragments_in_pool + 1 >= 6 * amRollsF: + weights[1] = 0 # can't have 6 rolls + + #if all weights are zero, allow to add fixed score multiplier, double category, points. + if sum(weights) == 0: + weights[2] = 1 + weights[4] = 1 + weights[5] = 1 + + which_item_to_add = self.multiworld.random.choices([0,1,2,3,4,5], weights = weights)[0] + + if which_item_to_add == 0: + if amDiceF == 1: + self.itempool += ["Dice"] + else: + self.itempool += ["Dice Fragment"] + weights[0] /= 1.1 + elif which_item_to_add == 1: + if amRollsF == 1: + self.itempool += ["Roll"] + else: + self.itempool += ["Roll Fragment"] + weights[1] /= 1.1 + elif which_item_to_add == 2: + self.itempool += ["Fixed Score Multiplier"] + weights[2] /= 1.1 + elif which_item_to_add == 3: + self.itempool += ["Step Score Multiplier"] + weights[3] /= 1.1 + elif which_item_to_add == 4: + self.itempool += self.multiworld.random.choices(possible_categories) + weights[4] /= 1.1 + elif which_item_to_add == 5: + score_dist = self.options.points_size.value + probs = [1,0,0] + if score_dist == 1: + probs = [0.9,0.08,0] + if score_dist == 2: + probs = [0,1,0] + if score_dist == 3: + probs = [0,0.3,0.7] + if score_dist == 4: + probs = [0.3,0.4,0.3] + self.itempool += self.multiworld.random.choices(["1 Point", "10 Points", "100 Points"], + weights = probs) + weights[5] /= 1.1 + else: + raise Exception("Invalid index when adding new items in Yacht Dice") + + print("Max score after adding ") + print(self.itempool) + print(diceSimulation(self.itempool + self.precollected, "state_is_a_list", self.options)) + print() - if(self.options.points_game_mode.value >= 3): - while self.extra_points_for_game_mode >= 7: #rather have 10 points that lot of 1 points. - self.itempool += ["10 Points"] - self.extra_points_for_game_mode -= 10 - - if(self.options.points_game_mode.value >= 2 and self.extra_points_for_game_mode > 0): - self.itempool += ["1 Point"] * self.extra_points_for_game_mode - - if self.options.game_mode.value == 3: - self.max_score = 1000 - while calculateScoreInLogic(self.itempool+self.precollected, self.options) < 1000: - self.itempool += [self.multiworld.random.choice(possible_categories)] - - - - #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. - already_items = len(self.itempool) + self.extra_plando_items + already_items = len(self.itempool) + self.extra_plando_items + 1 #+1 because of Victory item self.number_of_locations = already_items + extraLocationsNeeded # From here, we'll count the number of items in the self.itempool, and add items to the pool, @@ -191,55 +236,47 @@ class YachtDiceWorld(World): #first, we flood the entire pool with extra points (useful), if that setting is chosen. if self.options.add_extra_points.value == 1: #all of the extra points - already_items = len(self.itempool) + self.extra_plando_items + already_items = len(self.itempool) + self.extra_plando_items + 1 self.itempool += ["Extra 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 - already_items = len(self.itempool) + self.extra_plando_items + 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 self.itempool += ["Story Chapter"] * number_of_items #add some extra points (useful) if self.options.add_extra_points.value == 2: #add extra points if wanted - already_items = len(self.itempool) + self.extra_plando_items + already_items = len(self.itempool) + self.extra_plando_items + 1 self.itempool += ["Extra 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 - already_items = len(self.itempool) + self.extra_plando_items + 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 extra points if there is still room if self.options.add_extra_points.value == 2: - already_items = len(self.itempool) + self.extra_plando_items + already_items = len(self.itempool) + self.extra_plando_items + 1 self.itempool += ["Extra Point"] * min(self.number_of_locations - already_items, 10) #add some encouragements filler-items if there is still room - already_items = len(self.itempool) + self.extra_plando_items + 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 - already_items = len(self.itempool) + self.extra_plando_items + 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 don't do anything. #probability of Good and Bad rng, based on difficulty for fun :) - p = 0.5 - if self.options.game_difficulty.value == 1: - p = 0.9 - elif self.options.game_difficulty.value == 2: - p = 0.7 - elif self.options.game_difficulty.value == 3: - p = 0.5 - elif self.options.game_difficulty.value == 4: - p = 0.1 + p = 1.1 - 0.25 * self.options.game_difficulty.value - already_items = len(self.itempool) + self.extra_plando_items + already_items = len(self.itempool) + self.extra_plando_items + 1 self.itempool += self.multiworld.random.choices( ["Good RNG", "Bad RNG"], weights=[p, 1-p], @@ -247,9 +284,10 @@ class YachtDiceWorld(World): ) #we're 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 - if len(self.itempool) != self.number_of_locations: - raise Exception(f"Number in self.itempool is not number of locations {len(self.itempool)} {self.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}.") for item in self.precollected: self.multiworld.push_precollected(self.create_item(item)) @@ -263,12 +301,9 @@ class YachtDiceWorld(World): self.multiworld.itempool += [item] def create_regions(self): - #we have no complicated regions, just one rule per location. - - game_difficulty = self.options.game_difficulty.value - - #call the ini_locations function, that generations locations based on the inputs. - location_table = ini_locations(self.max_score, self.number_of_locations, game_difficulty) + #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 menu = Region("Menu", self.player, self.multiworld) @@ -277,19 +312,15 @@ class YachtDiceWorld(World): #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] - - #parameter to see where the final check should be - goal_percentage_location = self.options.goal_location_percentage.value #which index of all locations should have the Victory item. - victory_id = int(goal_percentage_location / 100 * len(board.locations))-1 # Add the victory item to the correct location. # The website declares that the game is complete when the victory item is obtained. - board.locations[victory_id].place_locked_item(self.create_item("Victory")) + board.locations[goal_index].place_locked_item(self.create_item("Victory")) #these will be slot_data input - self.goal_score = board.locations[victory_id].yacht_dice_score + self.goal_score = board.locations[goal_index].yacht_dice_score self.max_score = board.locations[-1].yacht_dice_score #add the regions @@ -325,19 +356,24 @@ class YachtDiceWorld(World): yacht_dice_data = self._get_yachtdice_data() yacht_dice_options = self.options.as_dict( - "number_of_dice_and_rolls", - "number_of_dice_fragments_per_dice", - "number_of_roll_fragments_per_roll", - "number_of_extra_roll_fragments", - "game_difficulty", - "goal_location_percentage", - "alternative_categories", - "score_multiplier_type", - "game_mode", - "minimize_extra_items", - "add_extra_points", - "add_story_chapters", - "which_story" + "game_difficulty", + "score_for_last_check", + "score_for_goal", + "minimal_number_of_dice_and_rolls", + "number_of_dice_fragments_per_dice", + "number_of_roll_fragments_per_roll", + "alternative_categories", + "chance_of_dice", + "chance_of_roll", + "chance_of_fixed_score_multiplier", + "chance_of_step_score_multiplier", + "chance_of_double_category", + "chance_of_points", + "points_size", + "minimize_extra_items", + "add_extra_points", + "add_story_chapters", + "which_story" ) slot_data = {**yacht_dice_data, **yacht_dice_options} #combine the two @@ -345,6 +381,7 @@ class YachtDiceWorld(World): slot_data["goal_score"] = self.goal_score slot_data["last_check_score"] = self.max_score slot_data["ap_world_version"] = self.ap_world_version + print(1/0) return slot_data def create_item(self, name: str) -> Item: