Last active
February 26, 2025 00:57
-
-
Save jumpluff/a670af1dec36d444dd1fa4691c3b8223 to your computer and use it in GitHub Desktop.
Python script to simulate consolidated probabilities of Infinity Nikki 4-star and 5-star banners, showing that there must be some form of weighting/soft pity active in order to achieve the advertised rates.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from random import random | |
from math import e | |
num = 100000 # number of pulls to do for each simulation | |
base_prob = { 4: 3.29/100, 5: 1.5/100 } # can set 5 to 0 and 4 to 10/100 for a 4-star banner | |
pity = { 4: 10, 5: 20 } # don't forget to change pity[4] to 5 for a 4-star banner | |
# the following are just test values: | |
exponential_pity_threshold = 2/5 # threshold after which soft pity formula should activate (fraction of hard pity) | |
k = { 4: 0.002, 5: 0.005 } # growth factors for the exponential pity formula | |
linear_pity_threshold = 13/20 | |
m = { 4: 0.025, 5: 0.03 } # slopes for the linear pity formula | |
def rollRand(pulls, pity_timers, adjust_prob, separate_pity_timer): | |
prob_5 = adjust_prob(5, pity_timers) | |
rand = random() | |
if base_prob[5] > 0 and rand <= prob_5: | |
roll5(pulls, pity_timers, separate_pity_timer) | |
elif rand <= prob_5 + adjust_prob(4, pity_timers): | |
roll4(pulls, pity_timers) | |
else: | |
pity_timers[4] += 1 | |
pity_timers[5] += 1 | |
def roll4(pulls, pity_timers): | |
pulls[4] += 1 | |
pity_timers[4] = 0 | |
pity_timers[5] += 1 | |
def roll4Plus(pulls, pity_timers, adjust_prob, separate_pity_timer, should_roll_4_plus): | |
if should_roll_4_plus and base_prob[5] > 0 and random() <= adjust_prob(5, pity_timers): | |
roll5(pulls, pity_timers, separate_pity_timer) | |
else: | |
roll4(pulls, pity_timers) | |
def roll5(pulls, pity_timers, separate_pity_timer): | |
pulls[5] += 1 | |
pity_timers[5] = 0 | |
if separate_pity_timer: | |
pity_timers[4] += 1 | |
else: | |
pity_timers[4] = 0 | |
def normalProb(rarity, pity_timers): | |
return base_prob[rarity] | |
def exponentialProb(rarity, pity_timers): | |
start = pity[rarity] * exponential_pity_threshold | |
if pity_timers[rarity] > start: | |
return base_prob[rarity] + (1 - base_prob[rarity]) * (1 - e ** (-k[rarity] * (pity_timers[rarity] - start))) | |
return base_prob[rarity] | |
def linearProb(rarity, pity_timers): | |
start = pity[rarity] * linear_pity_threshold | |
if pity_timers[rarity] > start: | |
return base_prob[rarity] + m[rarity] * (pity_timers[rarity] - start) | |
return base_prob[rarity] | |
def simulate(simulation): | |
pity_timers = { 4: 0, 5: 0 } | |
pulls = { 4: 0, 5: 0 } | |
separate_pity_timer = simulation['separate_pity_timer'] | |
should_roll_4_plus = simulation['roll_4_plus'] | |
adjust_prob = simulation['adjust_prob'] | |
for _ in range(num): | |
if base_prob[5] > 0 and pity_timers[5] >= pity[5] - 1: | |
roll5(pulls, pity_timers, separate_pity_timer) | |
elif pity_timers[4] >= pity[4] - 1: | |
roll4Plus(pulls, pity_timers, adjust_prob, separate_pity_timer, should_roll_4_plus) | |
else: | |
rollRand(pulls, pity_timers, adjust_prob, separate_pity_timer) | |
printResults(simulation['desc'], pulls[4], pulls[5]) | |
def printResults(desc, four_stars, five_stars): | |
indent = '---- ' | |
five_star_string = f'{indent}5 stars: {five_stars / num * 100}%\n' if base_prob[5] > 0 else '' | |
print(f'Assuming {desc}:\n{five_star_string}{indent}4 stars: {four_stars / num * 100}%\n{indent}3 stars: {(num - five_stars - four_stars)/num * 100}%') | |
shared_4plus = { | |
'separate_pity_timer': False, | |
'roll_4_plus': True, | |
'adjust_prob': normalProb, | |
'desc': 'shared pity timers with no soft pity and a 4+ roll' | |
} | |
separate_no_4plus = { | |
'separate_pity_timer': True, | |
'roll_4_plus': False, | |
'adjust_prob': normalProb, | |
'desc': 'separate pity timers with no soft pity and no 4+ roll' | |
} | |
separate_4plus = { | |
'separate_pity_timer': True, | |
'roll_4_plus': True, | |
'adjust_prob': normalProb, | |
'desc': 'separate pity timers with no soft pity and a 4+ roll' | |
} | |
exponential = { | |
'separate_pity_timer': True, | |
'roll_4_plus': True, | |
'adjust_prob': exponentialProb, | |
'desc': 'separate pity timers with exponential soft pity and a 4+ roll' | |
} | |
linear = { | |
'separate_pity_timer': True, | |
'roll_4_plus': True, | |
'adjust_prob': linearProb, | |
'desc': 'separate pity timers with linear soft pity and a 4+ roll' | |
} | |
simulations = [shared_4plus, separate_no_4plus, separate_4plus, exponential, linear] # adjust to include the tests you want to run | |
for simulation in simulations: | |
simulate(simulation) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment