Skip to content

Instantly share code, notes, and snippets.

@NoRaincheck
Last active November 22, 2025 11:49
Show Gist options
  • Select an option

  • Save NoRaincheck/4576e215235a28795de8b70ef3849d3c to your computer and use it in GitHub Desktop.

Select an option

Save NoRaincheck/4576e215235a28795de8b70ef3849d3c to your computer and use it in GitHub Desktop.
import math
from collections import Counter
# primes chosen to be the 4, 6, 8, 12, 20, 100 primes to match the dice roll
# with this it is at least possible to get a reproducible sequence, for each one
# with length desired
DICE = [4, 6, 8, 12, 20, 100]
PRIME_NUMBERS = [11, 13, 19, 29, 41, 211]
def icg(x0, a, b, p, n=1) -> list[int]:
"""
Generate a sequence of numbers using inverse congruential generator
Args:
x0: the initial starting value (seed)
a: multiplier
b: increment
p: modulus (prime number)
n: number of values to generate, default=1
"""
sequence = []
x = x0
for _ in range(n):
x = (a * x + b) % p if x != 0 else b
sequence.append(x)
return sequence
def get_dice_roll_sequence(d, offset=0):
NUM_FULL_SEQUENCES = 2
p = PRIME_NUMBERS[DICE.index(d)]
a, b = find_valid_params_with_full_period(p)
full_sequence = icg(p // 2 + offset, a, b, p, p)
c = Counter()
dice_sequence = []
for x in full_sequence:
x_roll = (x % d) + 1
if c[x_roll] < NUM_FULL_SEQUENCES:
c[x_roll] += 1
dice_sequence.append(x_roll)
return dice_sequence
def find_valid_params_with_full_period(
p, num_iters=None, limit_primes=True, return_median=True
):
"""
Attempts to find whether or not there are valid parameters
If num_iters is None tries to brute force all possible parameters, (p**2)
"""
if limit_primes and p not in PRIME_NUMBERS:
raise ValueError("ensure that number is a valid prime for checking")
candidate_solution = []
iters = 0
for a in range(p, p * math.ceil(math.log(p))):
for b in range(1, p + 1):
sequence = icg(0, a, b, p, p)
if set(sequence) == set(range(p)):
candidate_solution.append((a, b))
iters += 1
if len(candidate_solution) > p / 2:
break
if num_iters is not None and iters > num_iters:
break
if return_median:
return candidate_solution[len(candidate_solution) // 2]
return candidate_solution
def main():
print("Hello from icg-pseudo-rand!")
# get the rolls for 600 events
output = {}
for d in DICE:
seq = []
iter = 0
for _ in range(600//d):
# add a bit of randomness
seq_temp = get_dice_roll_sequence(d, iter)
seq.extend(seq_temp)
iter = seq_temp[-1]
output[d] = seq
print(output)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment