Skip to content

Instantly share code, notes, and snippets.

@jumpluff
Last active February 26, 2025 00:57
Show Gist options
  • Save jumpluff/a670af1dec36d444dd1fa4691c3b8223 to your computer and use it in GitHub Desktop.
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.
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