'''
Prerequisites for StackableRolls:
'''
def hcf(*args):
"""
Calculate the highest common factor (HCF) of one or more integers.
Supports both individual integers and lists of integers.
Parameters:
*args: One or more integers or a single list of integers.
Returns:
int: The highest common factor of the provided integers.
If no arguments are provided, returns None.
If one argument is provided, returns its absolute value.
"""
# Handle case where a single list is passed
if len(args) == 1 and isinstance(args[0], (list, tuple)):
args = args[0] # Extract the list/tuple
if len(args) == 0:
return None # Return None for no input
elif len(args) == 1:
return abs(int(args[0])) # Return the absolute value for a single input
else:
result = abs(int(args[0]))
for num in args[1:]:
num = int(num)
while num:
result, num = num, result % num # Update result using Euclidean algorithm
if result == 1: # Early exit if HCF is 1
return 1
return result
def lcm(*args):
"""
Calculate the least common multiple (LCM) of one or more integers.
Supports both individual integers and lists of integers.
Parameters:
*args: One or more integers or a single list of integers.
Returns:
int: The least common multiple of the provided integers.
If no arguments are provided, returns None.
If one argument is provided, returns its absolute value.
"""
# Handle case where a single list is passed
if len(args) == 1 and isinstance(args[0], (list, tuple)):
args = args[0] # Extract the list/tuple
if len(args) == 0:
return None # Return None for no input
elif len(args) == 1:
return abs(int(args[0])) # Return the absolute value for a single input
else:
result = abs(int(args[0]))
for num in args[1:]:
num = int(num)
result = abs(result * num) // hcf(result, num) # LCM formula
return result
'''
Definition of the StackableRolls class:
Class attributes:
- roll: A dictionary storing all the possible values and their frequency
- fracCount: Stores the sum of frequency of all possible values
Class methods:
1. Constructors
- __init__(rollDict): Initializes an object of class StackableRolls with roll = rollDict
2. Mutators
- integer(): Sets values of the roll dictionary and fracCount to integers
- simplify(): Simplifies the roll values if possible
- scale_up(factor): Multiply the roll values by factor
3. Accessors
- smallerThan(value)
- largerThan(value)
- add(anotherRoll)
- multiply(anotherRoll)
- pow(pow_count)
'''
class StackableRolls:
def __init__(self, rollDict):
self.roll = {k: int(v) for k, v in rollDict.items()}
self.fracCount = sum(self.roll.values())
def sort(self, inplace=False):
"""
Sorts the roll dictionary by keys in ascending order.
Parameters:
- inplace (bool): If True, sort the current object. If False, return a new sorted StackableRolls object.
Returns:
- StackableRolls or None: A new StackableRolls object if inplace is False; otherwise, modifies the current object.
"""
sorted_roll = dict(sorted(self.roll.items(), key=lambda item: item[0]))
if inplace:
self.roll = sorted_roll
return self
else:
return StackableRolls(sorted_roll)
def integer(self, inplace=False):
if (inplace):
self.roll = {k: int(v) for k, v in self.roll.items()}
self.fracCount = int(self.fracCount)
return self
else:
new_roll_dict = {k: int(v) for k, v in self.roll.items()}
return StackableRolls(new_roll_dict)
def change_sign_of_keys(self, inplace=False):
if (inplace):
self.roll = {key * -1: value for key, value in self.roll.items()}
return self
else:
new_roll_dict = {key * -1: value for key, value in self.roll.items()}
return StackableRolls(new_roll_dict)
def change_sign_of_values(self, inplace=False):
if (inplace):
self.roll = {key: value * -1 for key, value in self.roll.items()}
return self
else:
new_roll_dict = {key: value * -1 for key, value in self.roll.items()}
return StackableRolls(new_roll_dict)
def simplify(self, setInteger=False):
'''
Simplifies the frequencies of the rolls by dividing them by their highest common factor (HCF).
'''
if (setInteger):
self.integer(inplace=True)
# Calculate the highest common factor (HCF) of the frequencies
div_factor = self.fracCount
for freq in self.roll.values():
div_factor = hcf(div_factor, freq)
if div_factor == 1:
return # Early exit if HCF is 1
# Divide each frequency by the HCF
for value in self.roll:
self.roll[value] //= div_factor # Use in-place division for clarity
# Update the total fractional count
self.fracCount //= div_factor
if (setInteger):
self.integer(inplace=True)
def scale_up(self, factor, setInteger=False):
'''
Only changes frequency
'''
# Too obvious
for x in self.roll:
self.roll[x] *= factor
self.fracCount *= factor
if (setInteger):
self.integer(inplace=True)
return
def dropZeros(self, setInteger=False):
'''
Removes entries with zero frequency
'''
# Create dictionary with correct values
resultDict = {}
for x in self.roll:
if (x != 0):
resultDict[x] = self.roll[x]
# Wrapup
result = StackableRolls(resultDict)
if (setInteger):
result.integer(inplace=True)
return result
def smallerThan(self, value, includeEqual=False, setInteger=False):
'''
Picks and keeps values smaller than a threshold
'''
# See above
resultDict = {}
for x in self.roll:
if (x < value):
resultDict[x] = self.roll[x]
elif ((x == value) and includeEqual):
resultDict[x] = self.roll[x]
result = StackableRolls(resultDict)
if (setInteger):
result.integer(inplace=True)
return result
def largerThan(self, value, includeEqual=False, setInteger=False):
'''
Picks and keeps values larger than a threshold
'''
# See above
resultDict = {}
for x in self.roll:
if (x > value):
resultDict[x] = self.roll[x]
elif ((x == value) and includeEqual):
resultDict[x] = self.roll[x]
result = StackableRolls(resultDict)
if (setInteger):
result.integer(inplace=True)
return result
def add(self, anotherRoll, setInteger=False):
"""
Combines two StackableRolls objects representing simultaneous events.
Parameters:
- anotherRoll (StackableRolls): The StackableRolls object to be added to the current instance.
Returns:
- StackableRolls: A new StackableRolls object that contains the combined frequencies of values
from both rolls. If either roll has a frequency count of zero, the other roll
is returned unchanged.
Notes:
- This method ensures that all values present in either of the original rolls are included in
the resulting roll. Frequencies for matching values are summed.
Example:
>>> roll1 = StackableRolls({10: 3, 20: 2})
>>> roll2 = StackableRolls({10: 1, 30: 4})
>>> combined_roll = roll1.add(roll2)
>>> print(combined_roll)
# Output: StackableRolls({10: 4, 20: 2, 30: 4})
"""
# Check for empty rolls
if self.fracCount == 0:
return anotherRoll
elif anotherRoll.fracCount == 0:
return self
# Create a dictionary with the combined frequencies
resultDict = self.roll.copy()
for value, freq in anotherRoll.roll.items():
resultDict[value] = resultDict.get(value, 0) + freq
# Wrap up the result
result = StackableRolls(resultDict)
if (setInteger):
result.integer(inplace=True)
return result
def multiply(self, anotherRoll, to_simplify=True, setInteger=False):
"""
Combines two StackableRolls objects representing sequential events.
Parameters:
- anotherRoll (StackableRolls): The StackableRolls object to be multiplied with the current instance.
- to_simplify (bool): Optional. If True (default), the resulting roll will be simplified by reducing
frequencies to their lowest terms.
Returns:
- StackableRolls: A new StackableRolls object that represents the sum of all possible outcomes
from rolling the values of both rolls sequentially.
Notes:
- The resulting roll includes values calculated by adding each value from the first roll
to each value from the second roll, weighted by their respective frequencies.
If to_simplify is True, the resulting roll will be simplified after multiplication.
Example:
>>> roll1 = StackableRolls({1: 2, 2: 3}) # 2 occurrences of 1, 3 occurrences of 2
>>> roll2 = StackableRolls({1: 1, 3: 4}) # 1 occurrence of 1, 4 occurrences of 3
>>> product_roll = roll1.multiply(roll2)
>>> print(product_roll)
# Output: StackableRolls({2: 2, 3: 3, 4: 8, 5: 12})
# Explanation:
# - 2 occurs 2 times (2 occurence of 1 from roll1 * 1 occurence of 1 from roll2)
# - 3 occurs 3 times (3 occurence of 2 from roll1 * 1 occurence of 1 from roll2)
# - 4 occurs 8 times (2 occurence of 1 from roll1 * 4 occurence of 3 from roll2)
# - 5 occurs 12 times (3 occurence of 2 from roll1 * 4 occurence of 3 from roll2)
"""
# Initialize
if (to_simplify):
self.simplify()
anotherRoll.simplify()
resultDict = {}
# Creating dictionary with correct values
self_items = list(self.roll.items())
another_items = list(anotherRoll.roll.items())
for (i, count_i) in self_items:
for (j, count_j) in another_items:
if i+j in resultDict:
resultDict[i+j] += count_i * count_j
else:
resultDict[i+j] = count_i * count_j
# Wrapup
result = StackableRolls(resultDict)
if (setInteger):
result.integer(inplace=True)
if (to_simplify):
result.simplify()
result.roll = dict(sorted(result.roll.items()))
return result
def pow(self, pow_count, acceptFloat=False, setInteger=False):
'''
Raises the current StackableRolls object to the power of pow_count by
multiplying it by itself pow_count times.
Parameters:
- pow_count (int or float): The exponent to which the current StackableRolls
object should be raised. Must be a non-negative integer if acceptFloat
is False.
- acceptFloat (bool): Optional. If True, allows pow_count to be a float.
If False (default), pow_count must be an integer.
- setInteger (bool): Optional. If True, ensures that the frequencies in
the resulting StackableRolls object are integers.
Returns:
- StackableRolls: A new StackableRolls object that represents the current
object raised to the power of pow_count. If pow_count is negative or
not a valid value, returns None.
Notes:
- The method uses exponentiation by squaring to efficiently compute the
power, which reduces the number of multiplications required.
- If pow_count is a float and acceptFloat is False, the method will
return None without performing any calculations.
- If pow_count is less than 0, the method will also return None.
Example:
>>> roll = StackableRolls({1: 2, 2: 3})
>>> result = roll.pow(3)
>>> print(result)
# Output: StackableRolls object representing the roll raised to the power of 3
'''
if (isinstance(pow_count, float) and (pow_count != float(int(pow_count))) and not acceptFloat):
return
if (pow_count < 0):
return
result = StackableRolls({0: 1}) # The default roll
temp = self
while (pow_count != 0):
if (pow_count % 2 == 1):
result = result.multiply(temp)
pow_count -= 1
temp = temp.multiply(temp)
pow_count /= 2
if (setInteger):
result.integer(inplace=True)
return result
def weightedAdd(StackableRollsList, weightingList, setInteger=False):
"""
Combines a list of StackableRolls objects with corresponding weights.
Parameters:
- StackableRollsList (list): A list of StackableRolls objects to be combined.
- weightingList (list): A list of weights corresponding to each StackableRolls object.
- setInteger (bool): If True, ensures that the frequencies in the result are integers.
Returns:
- StackableRolls: A new StackableRolls object that represents the weighted sum of the input rolls.
"""
# Check for equal length of lists
if len(StackableRollsList) != len(weightingList):
raise ValueError("StackableRollsList and weightingList must be the same length.")
# Simplify the weightingList by dividing by the HCF
weighting_hcf = hcf(weightingList)
weightingList = [w // weighting_hcf for w in weightingList]
# Scale StackableRolls according to their fractional counts and weights
fracCountList = [roll.fracCount for roll in StackableRollsList]
fracCountlcm = lcm(fracCountList)
for roll, weight in zip(StackableRollsList, weightingList):
scale_factor = fracCountlcm // roll.fracCount
roll.scale_up(scale_factor)
roll.scale_up(weight)
# Combine the adjusted rolls
result = StackableRollsList[0]
for roll in StackableRollsList[1:]:
result = result.add(roll)
if setInteger:
result.integer()
result.simplify()
return result
'''
Functions required to plug the StackableRolls class to needs for damage calculation and win chance evaluation
'''
def PokemonCritChanceOddsList(critStage, gen=9, baseSpeed=100):
"""
Calculate the odds of landing a critical hit based on the crit stage, Pokémon generation,
and base speed.
Parameters:
critStage (int): The critical hit stage, which affects the chance of landing a critical hit.
- Values 0-7 represent different crit stages.
- -1 indicates no crit chance (results in always missing).
gen (int): The generation of the Pokémon game. Default is 9.
- Generation 1 has unique crit calculations.
baseSpeed (int): The base speed of the Pokémon, used for critical hit calculations in Generation 1 only.
Default is 100, but can be ignored for other generations.
Returns:
list: A list containing two values:
- The chance of not landing a critical hit.
- The chance of landing a critical hit.
If the generation is invalid (less than 1 or greater than 9), returns None.
If critStage is invalid (less than -1 or greater than 7), returns [0, 1].
Notes:
- For Generation 1:
- Crit stages 4-7 correspond to battle stadium crit chances.
- Crit stages 2-3 and 6-7 correspond to focus energy crit chances.
- If critStage is not found or if an invalid generation is provided, the function returns None.
- Avoid using generation 0, as it will not work.
Example:
>>> PokemonCritChanceOddsList(3, gen=1, baseSpeed=120)
[x, y] # where x is the non-crit chance and y is the crit chance
"""
# Check for invalid generation
if gen < 1 or gen > 9:
return None
if gen == 1:
base_odds = 256
crit_chance = 0
# Calculate critical hit chance based on critStage
if critStage == 0:
crit_chance = (baseSpeed // 2)
elif critStage == 1:
crit_chance = 8 * (baseSpeed // 2)
elif critStage == 2:
crit_chance = (baseSpeed // 8)
elif critStage == 3:
crit_chance = 4 * (baseSpeed // 4)
elif critStage == 4:
crit_chance = (baseSpeed + 76) // 4
elif critStage == 5:
crit_chance = 8 * ((baseSpeed + 76) // 4)
elif critStage == 6:
crit_chance = ((baseSpeed + 236) // 4) * 2
elif critStage == 7:
crit_chance = 255
elif critStage == -1:
crit_chance = 0
# Cap crit_chance at 255
crit_chance = min(crit_chance, 255)
return [base_odds - crit_chance, crit_chance]
# Data for crit chances and base odds based on generation
gen_data = {
2: ([17, 1, 1, 85, 1], [256, 8, 4, 256, 2]),
(3, 5): ([1, 1, 1, 1, 1], [16, 8, 4, 3, 2]),
6: ([1, 1, 1, 1, 1], [16, 8, 2, 1, 1]),
(7, 9): ([1, 1, 1, 1, 1], [24, 8, 2, 1, 1])
}
# Determine crit chances and base odds based on generation
for key, value in gen_data.items():
if isinstance(key, tuple):
# Check if generation falls within the tuple range
if key[0] <= gen <= key[1]:
crit_chances, base_odds = value
break
elif key == gen:
# Exact match for generation
crit_chances, base_odds = value
break
else:
# Return if generation data is not found
return None
# Calculate the chances of not landing a critical hit
not_crit_chances = [b - c for b, c in zip(base_odds, crit_chances)]
critStage = int(critStage)
# Handle special case for critStage -1 (always crit)
if critStage == -1:
return [1, 0]
# Validate critStage range
if critStage < 0 or critStage > 4:
return [0, 1]
# Return the calculated chances for the given crit stage
return [not_crit_chances[critStage], crit_chances[critStage]]
def tupleRollToDict(tupleString, bracket='auto'):
"""
Convert a string representation of a tuple (or list) of integers into a dictionary
where each integer is a key and its count as the value.
Parameters:
tupleString (str): A string representation of a tuple or list (e.g., "(1, 2, 3)").
It can also be a string formatted with different types of brackets.
bracket (str): Optional. Specifies the type of brackets used in the input string.
Options are '(', '[', '{', or 'auto' to determine based on the first character.
Default is 'auto'.
Returns:
dict: A dictionary where each unique integer in the input string is a key,
and the value is the count of occurrences of that integer.
Returns an empty dictionary if the input string is improperly formatted
or does not start and end with matching brackets.
Example:
>>> tupleRollToDict("(1, 2, 2, 3)")
{1: 1, 2: 2, 3: 1}
>>> tupleRollToDict("[4, 4, 5]")
{4: 2, 5: 1}
>>> tupleRollToDict("{6, 7, 6}")
{6: 2, 7: 1}
>>> tupleRollToDict("Invalid input")
{}
"""
tupleString = tupleString.strip() # Strip leading and trailing whitespace
brackets = {'(': '()', '[': '[]', '{': '{}',
'auto': '()'}.get(tupleString[0], '()') if bracket == 'auto' else bracket
# Validation
if not (tupleString.startswith(brackets[0]) and tupleString.endswith(brackets[1])):
return {}
# Extracting and converting to dictionary
entryList = tupleString.lstrip(brackets[0]).rstrip(brackets[1]).split(",")
count_dict = {}
for x in entryList:
num = int(x.strip())
count_dict[num] = count_dict.get(num, 0) + 1 # Increment count
return count_dict
def addAccuracyToRoll(roll, ufrac, lfrac=100):
"""
Adjusts a roll to account for accuracy, combining it with a miss chance.
Parameters:
- roll (StackableRolls): The initial roll object representing the damage values.
- ufrac (int): The upper part of the accuracy fraction (numerator).
Represents the chance to hit.
- lfrac (int): The lower part of the accuracy fraction (denominator).
Represents the total possible outcomes.
Returns:
- StackableRolls: A new StackableRolls object that combines the original roll
with a miss chance based on the accuracy parameters.
If ufrac is greater than or equal to lfrac, returns the original roll.
If ufrac is less than or equal to 0, returns a roll that always misses.
Notes:
- The accuracy is determined by the ratio of ufrac to lfrac.
If the accuracy is 100% (ufrac = lfrac), the original roll is returned unchanged.
- If the accuracy is less than 100%, the function combines the original roll with a
miss chance (represented by a roll of {0: 1}) using weighted addition.
Example:
>>> roll = StackableRolls({10: 3, 20: 2})
>>> adjusted_roll = addAccuracyToRoll(roll, 80, 100)
>>> print(adjusted_roll)
# This would output a new StackableRolls object that accounts for an 80% chance to hit.
"""
if (ufrac >= lfrac):
return roll
elif (ufrac <= 0):
return StackableRolls({0:1})
else:
return StackableRolls.weightedAdd([roll, StackableRolls({0:1})], [ufrac, lfrac - ufrac])
def createRollWithCrit(non_crit, crit, critStage=0, accuracy_ufrac=100, accuracy_lfrac=100, gen=9, baseSpeed=100):
"""
Automatically detects the type of input for non_crit and crit,
and returns a StackableRolls object combining the rolls of critical hits
and non-critical hits, factoring accuracy.
Parameters:
- non_crit: Required. The roll when it is not a critical hit.
Supported input types:
StackableRolls, tuple / list / set, or its string equivalent
dictionary, with keys being the damage values and values being the frequency
- crit: Required. The roll when it is a critical hit.
Supported input types: See non_crit
- critStage (int): Optional. Critical hit stage. Default is 0.
- accuracy_ufrac (int): Optional. Upper part of fraction (Numerator) for accuracy. Default is 100.
- accuracy_lfrac (int): Optional. Lower part of fraction (Denominator) for accuracy. Default is 100.
- gen (int): Optional. Generation of the Pokémon game. Default is 9. Note that gen 1 is not tested.
- baseSpeed (int): Optional. Base speed of the Pokémon. Used for critical hit chance calculation for Gen 1.
Output:
- (StackableRolls): The damage roll for a single hit considering the possibility of critical hits and misses.
"""
# Helper function to convert input to StackableRolls
def to_stackable_rolls(data):
if isinstance(data, StackableRolls):
return data # Return as is if already a StackableRolls object
elif isinstance(data, dict):
return StackableRolls(data)
elif isinstance(data, (tuple, list, set)):
return StackableRolls(tupleRollToDict(str(data)))
elif isinstance(data, str):
return StackableRolls(tupleRollToDict(data))
else:
return StackableRolls({0: 1}) # Default case
# Convert inputs to StackableRolls
non_crit_roll = to_stackable_rolls(non_crit)
crit_roll = to_stackable_rolls(crit)
# Calculate critical hit odds
crit_odds = PokemonCritChanceOddsList(critStage, gen, baseSpeed)
# Combine rolls with critical hit calculations
combined_roll = StackableRolls.weightedAdd([non_crit_roll, crit_roll], crit_odds)
# Remove zeros due to extreme crit stages
combined_roll.dropZeros()
# Add accuracy to the combined roll
return addAccuracyToRoll(combined_roll, accuracy_ufrac, accuracy_lfrac).sort(inplace=True)
#Return ko chance in float
def koChance(HP, TheStackableRoll):
if (isinstance(HP, int)):
return 1 - TheStackableRoll.smallerThan(HP).fracCount / TheStackableRoll.fracCount
elif (isinstance(HP, (list, tuple, set))):
result = []
for x in HP:
result.append(1 - TheStackableRoll.smallerThan(HP).fracCount / TheStackableRoll.fracCount)
return result
elif (isinstance(HP, dict)):
result = {}
for x in HP.keys():
temp = 1 - TheStackableRoll.smallerThan(HP).fracCount / TheStackableRoll.fracCount
if (temp in result):
result[temp] += HP[x]
else:
result[temp] = HP[x]
return result
else:
return
#Print ko chance as fraction
def fracKOChance(HP, damageValues, printAsFrac=False):
'''
The chance a pokemon is KOed with the given HP and damage values provided.
Parameters:
- HP (int): Required. The HP of the pokemon.
- damageValues (StackableRolls): Required. The possible damage values and their corresponding frequencies.
- printAsFrac (bool): Optional. If true, print out the resultant fraction.
Output: ufrac, lfrac
- ufrac (int): The numerator of the chance to KO
- lfrac (int): The denominator of the chance to KO
'''
lfrac = int(damageValues.fracCount - damageValues.smallerThan(HP).fracCount)
ufrac = int(damageValues.fracCount)
x = hcf(lfrac, ufrac)
lfrac = lfrac // x
ufrac = ufrac // x
if (printAsFrac):
print(lfrac, "/", ufrac)
return ufrac, lfrac
def koChanceWithRecovery(initialHP, maxHP, recoveryPerTurn, damageRolls, startingTurn=1, maxTurns=10, isFrac=False, returnPostRecovery='auto'):
"""
Calculate the chance a Pokémon is KOed at different turns with variable recovery.
It is assumed all values of initialHP are greater than 0.
Parameters:
- initialHP (int or StackableRolls): The initial HP of the Pokémon.
- maxHP (int): The maximum HP of the Pokémon.
- recoveryPerTurn (int or list of int): HP gained from recovery per turn.
- damageRolls (StackableRolls or list of StackableRolls): Possible damage values and their frequencies.
- startingTurn (int): The turn KO chance begins to be displayed.
- maxTurns (int): The maximum turn to calculate the KO chance.
- isFrac (bool): Returns KO chance as fraction using list instead of float.
- returnPostRecovery: Determines whether post recovery KO chances should also be returned.
Returns:
- dict: Dictionary/ies where keys are turn numbers and values are the KO chance for that turn before (and after) recovery.
"""
# Initialize currentHP
currentHP = StackableRolls({initialHP: 1}) if not isinstance(initialHP, StackableRolls) else initialHP
# Prepare recoveryPerTurn as a list
recoveryPerTurn = [recoveryPerTurn] if isinstance(recoveryPerTurn, int) else recoveryPerTurn
recovery_length = len(recoveryPerTurn)
# Prepare damageRolls as a list
damageRolls = [damageRolls] if isinstance(damageRolls, StackableRolls) else damageRolls
damage_length = len(damageRolls)
ko_chances_pre_recovery = {}
ko_chances_post_recovery = {}
terminating_turn = 0
for turn in range(startingTurn, startingTurn + maxTurns):
# Determine damage for the current turn
damageRoll = damageRolls[min(turn - 1, damage_length - 1)]
currentHP = currentHP.multiply(damageRoll.change_sign_of_keys())
total_occurrences = currentHP.fracCount
# Filter occurrences with HP > 0
currentHP = currentHP.largerThan(0)
survive_occurrences_pre_recovery = currentHP.fracCount
# Determine recovery for the current turn
recoveryAmount = recoveryPerTurn[min(turn - 1, recovery_length - 1)]
recovery_this_turn = StackableRolls({recoveryAmount: 1})
currentHP = currentHP.multiply(recovery_this_turn, to_simplify=False)
survive_occurrences_post_recovery = currentHP.fracCount
# Fix HP values exceeding maximum HP
if maxHP not in currentHP.roll:
currentHP.roll[maxHP] = 0
# Accumulate occurrences into maxHP and prepare for deletion
for hp in list(currentHP.roll.keys()):
if hp > maxHP:
currentHP.roll[maxHP] += currentHP.roll[hp]
del currentHP.roll[hp]
# Append results
if isFrac:
hcf_pre_recovery = hcf(survive_occurrences_pre_recovery, total_occurrences)
ko_chances_pre_recovery[turn] = ((total_occurrences - survive_occurrences_pre_recovery) // hcf_pre_recovery, total_occurrences // hcf_pre_recovery)
hcf_post_recovery = hcf(survive_occurrences_post_recovery, total_occurrences)
ko_chances_post_recovery[turn] = ((total_occurrences - survive_occurrences_post_recovery) // hcf_post_recovery, total_occurrences // hcf_post_recovery)
else:
ko_chances_pre_recovery[turn] = 1 - survive_occurrences_pre_recovery / total_occurrences
ko_chances_post_recovery[turn] = 1 - survive_occurrences_post_recovery / total_occurrences
# Terminate if already KOed
if survive_occurrences_post_recovery == 0:
terminating_turn = turn
break
# Determine return type
return_type = 'pre_recovery' if returnPostRecovery in [False, 'auto'] else 'both' if returnPostRecovery == 'auto' and any(r < 0 for r in recoveryPerTurn[:terminating_turn]) else returnPostRecovery
return (ko_chances_pre_recovery, ko_chances_post_recovery) if return_type == 'both' else ko_chances_pre_recovery if return_type == 'pre_recovery' else ko_chances_post_recovery
'''
Instructions:
1. Copy paste your damage value, like (1, 2, 3, 4) etc.
- Then feed it to createRollWithCrit and store it in a variable
2. Calculate the possible damage range by multiplying the damage rolls
- So for example you use Move 1 three times and Move 2 two times, and the damage roll for the two moves are my_damage_roll_1 and my_damage_roll_2 respectively
- Then you do the following to save the damage range into a variable:
- totalDamageRoll = (my_damage_roll_1.pow(3)).multiply(my_damage_roll_2.pow(2))
-
- Advanced:
- StackableRolls.weightedAdd is used for summing up distinct possibilities for rolls
- multiply is used for combining sequential damage rolls
3. Get the koChance
- If you want to get a decimal number:
- koChance(HP_of_the_mon, totalDamageRoll)
- If you want to get the fraction:
- fracKOChance(HP_of_the_mon, totalDamageRoll, True)
WARNING:
- The KO chance calculated assumes no recovery
- It is totally possible that a pokemon has negative HP in intermediate calculations but the end result becomes positive HP and hence is considered not KOed if there is recovery
- The proper way to calculate KO chance with recovery is to initialize a roll with initial HP, then multiply with negative of the damage roll, then select values larger then 0, then multiply with the recovery, then iterate.
'''
# Example calculation
nc = '(13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15)'
c = '[19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23]'
icyWind = createRollWithCrit(nc, c, critStage=0, accuracy_ufrac=95, gen=9)
nc = ' (33, 34, 34, 34, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 39, 39)'
c = {50, 51, 51, 52, 52, 53, 54, 54, 55, 55, 56, 57, 57, 58, 58, 59}
moonblast = createRollWithCrit(nc, c, 0)
nc = (210, 212, 216, 218, 218, 222, 224, 228, 230, 234, 234, 236, 240, 242, 246, 248)
c = [314, 318, 320, 326, 330, 332, 336, 342, 344, 348, 350, 356, 360, 362, 366, 372]
hydroCannon = createRollWithCrit(nc, c, accuracy_ufrac=9, accuracy_lfrac=10)
totalDamageRoll = icyWind.multiply(moonblast.multiply(hydroCannon))
fracKOChance(377, totalDamageRoll, True)