A board-game simulator needs loaded dice. A die has faces 1, 2, ..., k with weights w1, ..., wk (a face with twice the weight comes up twice as often). Each die must give the same rolls for the same seed no matter what other dice do in between, so that games can be replayed.
Write two classes:
Die(weights, seed):roll()draws one numberu = rng.random()from the die's own generatorrandom.Random(seed)(exactly one draw per roll) and returns the first face whose cumulative weightw1 + ... + wjis larger thanu * total.roll_many(n)returns a list ofnrolls.counts()returns a dictionary from every face to how often it has been rolled so far.expected()returns the mean face valuesum(j * wj) / total.- Weights that are negative, or add up to zero, raise
ValueError.
Pool(dice), a pool made of a list ofDieobjects:roll()rolls every die once, in list order, and returns the total;spread(n)rolls the poolntimes and returns a dictionary from each total that occurred to its count.
The helper raises(fn, *args) returns the name of the exception a call raises (or None), and replayed(weights, seed, other, n) checks the replay promise: it rolls a die n times on its own, then again with a second die (other weights) rolled between its rolls, and returns both lists.
Examples
Input: d = Die([1, 1, 1, 1, 1, 1], 7); d.roll_many(8), d.counts()[6], d.expected()
Output: ([2, 1, 4, 1, 4, 3, 1, 4], 0, 3.5)
Input: Die([0, 3, 1], 1).expected()
Output: 2.25
Constraints
1 <= k <= 20; weights are non-negative numbers.- Up to
3 * 10**4rolls per test. - Do not use the module-level
randomfunctions: they share one generator between all dice.
Goals
- Give every object its own random number generator, so objects do not disturb each other
- Build a pool of dice out of dice objects and delegate the work to them
- Sample a face from unequal weights with one uniform number