Last active
November 22, 2025 11:49
-
-
Save NoRaincheck/4576e215235a28795de8b70ef3849d3c to your computer and use it in GitHub Desktop.
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
| 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