From 1a8d807a8d271c7e545df4a52809e17442f63e54 Mon Sep 17 00:00:00 2001 From: spinerak Date: Tue, 28 May 2024 22:47:49 +0200 Subject: [PATCH] New version of the apworld, with 1000 as final score, always Will still need to check difficulty and weights of adding items. Website is not ready yet, so this version is not usable yet :) --- worlds/yachtdice/Items.py | 10 +- worlds/yachtdice/Locations.py | 23 ++- worlds/yachtdice/Options.py | 316 ++++++++++++++++++---------------- worlds/yachtdice/Rules.py | 129 ++++++-------- worlds/yachtdice/__init__.py | 241 +++++++++++++++----------- 5 files changed, 380 insertions(+), 339 deletions(-) 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: