Skip to content

Instantly share code, notes, and snippets.

@taikedz
Last active September 25, 2025 05:07
Show Gist options
  • Select an option

  • Save taikedz/5bfb1905d9403f554ccab19d5308a0c0 to your computer and use it in GitHub Desktop.

Select an option

Save taikedz/5bfb1905d9403f554ccab19d5308a0c0 to your computer and use it in GitHub Desktop.
d[N] dice chances

dN range roller

Quick script to see, when rolling a dice set complement of d6, d8, d10, d12, d20 , which are the most frequent numbers to appear.

Use this to determine how to subdivide your Foe Pool

See also fate chances

Then, aggregating those percentage point chances, what likelihood a given range would be hit.

This is for the construction of a list of foes for a game: given a list from 1-20 , roll a complement of die; choose the foes from the list at the corresponding numbers.

The intent is to have different foes occupy different levels - for example the list can be expressed as ranges:

  • 1-4 : imp
  • 5-9 : goblin
  • 10-17 : troll
  • 18-20 : dragon

With this distribution, we can see that the distribution of chances (percentages) lies such:

$ python3 dn-rolls.py -R 4,9,17,20
{
  "1..4": "42.0",
  "5..9": "40.0",
  "10..17": "15.0",
  "18..20": "3.0"
}

So if each round we introduce enemies with these dice, we have a 3% chance to encounter a dragon, and an 81% chance of getting either an imp or a goblin, and 15% for a troll, which works the rarities for each type decently.

Note the free definition of the range list, independent of the dice capacities themselves.

See the chances with just certain dice - for example d6 and d8:

$ python3 stats-tools/dN-rolls/dn-rolls.py -D 6,8
{
  "1..6": "87.5",
  "7..8": "12.5",
  "9..10": "0.00",
  "11..12": "0.00",
  "13..20": "0.00"
}

Combine arguments to find your preferred distribution of ranges and run shorter or longer simulations.

$ python3 stats-tools/dN-rolls/dn-rolls.py -R 3,6,14,17,20 -D 6,8
{
  "1..3": "43.8",
  "4..6": "43.7",
  "7..14": "12.5",
  "15..17": "0.0",
  "18..20": "0.0"
}

$ python3 stats-tools/dN-rolls/dn-rolls.py -R 3,6,14,17,20 -D 6,8,10,12
{
  "1..3": "35.6",
  "4..6": "35.6",
  "7..14": "28.8",
  "15..17": "0.0",
  "18..20": "0.0"
}

$ python3 stats-tools/dN-rolls/dn-rolls.py -R 3,6,14,17,20 -D 6,8,10,12,20
{
  "1..3": "31.5",
  "4..6": "31.5",
  "7..14": "31.0",
  "15..17": "3.0",
  "18..20": "3.0"
}
from argparse import ArgumentParser
import json
import pprint
from random import randint
DICE = [6, 8, 10, 12, 20]
# The pools are not linked to dice - they are ranges that can be defined
# by the foe list at-will
POOLS = [6, 8, 10, 12, 20]
def register(results:list[int]):
global DV_CHANCES
global VTOTAL
def pairs(items):
x = 0
while x < len(items):
yield x
def pools(limits):
if not 0 in limits:
limits = [0]+limits
x = 0
while x+1 < len(limits):
lo, hi = limits[x]+1, limits[x+1]
x+=1
yield list(range(lo,hi+1))
def test_pools():
assert list( pools([4, 8, 10]) ) == [[1,2,3,4],[5,6,7,8], [9,10]]
def pool_chances(dv_chances, vtotal, pool_levels, print_sum:bool):
chance_ = lambda x: 100*dv_chances[x]/vtotal
dn_chance = {}
for pool in pools(pool_levels):
dn_chance[f"{pool[0]}..{pool[-1]}"] = sum(chance_(x) for x in pool)
print(json.dumps({k:f"{v:.1f}" for k,v in dn_chance.items()}, indent=2))
if print_sum:
print(f"{sum([z for z in dn_chance.values()]):.1f}")
def individual_chances(dv_chances, vtotal):
pprint.pprint({d:f"{100*v/vtotal:.1f}" for d,v in dv_chances.items()})
def intlist(blob:str):
ints = list(set([int(n.strip()) for n in blob.split(",")]))
ints.sort()
return ints
def parse_args():
parser = ArgumentParser()
parser.add_argument("--dice", "-D", type=intlist, default=DICE)
parser.add_argument("--range-steps", "-R", type=intlist, default=POOLS)
parser.add_argument("--throws", "-T", type=int, default=1_000_000)
parser.add_argument("--print-pct-sum", "-S", action="store_true")
return parser.parse_args()
def main():
test_pools()
args = parse_args()
dv_chances = {n+1:0 for n in range(DICE[-1])}
vtotal = 0
for _ in range(args.throws):
# getvalues throws each die once and records the value returned
# we therefore are getting the chance of any given value appearing on
# a consistent collection of dice
for x in [randint(1, die) for die in args.dice]:
dv_chances[x] += 1
vtotal += 1
#individual_chances(dv_chances, vtotal)
pool_chances(dv_chances, vtotal, args.range_steps, args.print_pct_sum)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment