I've been working on a battle game with my son. We've got an idea for how combat could work, but I'm struggling to work out a way to calculate probability.
It's similar to how combat works in "Risk":
- The Attacker has
x* 6 sided die. - The Defender has
y* 6 sided die.
Both roll their die & the dice are compared in parallel from highest roll to lowest roll. Example:
Attacker (rolls 4 dice): 6 4 3 2
Defender (rolls 3 dice): 5 5 3
In the above example:
- The Defender wins one dice pair.
- The Attacker wins one dice pair.
- The remaining dice pair is a draw.
With our proposed rules the Attacker & Defender lose one dice for each pair they lose & roll again:
Attacker (now rolls 3 dice): 6 3 1
Defender (now rolls 2 dice): 4 2
In this example the Defender loses both pairs, so they have been defeated.
I'm trying to work out how to compare the probabilities of different dice numbers - i.e. 4 dice vs 3 dice or 5 dice vs 2 dice.
Being a software developer I've thought of programmatically running various Attacker/ Defender ratios thousands (or millions) of times & recording the odds from the outcome.
However, this doesn't feel remotely elegant & I'm wondering if there's a formula I could use instead? I tend to use Google Sheets for these sorts of calculations.
Would it be possible to calculate the odds for this approach via a formula, or is it too complex?
An efficient pool evaluation algorithm
In general, if the function that scores a pool (or in this case, a pair of pools) is allowed to depend on the entire ordered rolled sequence, then the function would have to be evaluated on all possible such sequences, the number of which is exponential in the number of dice. Even if we restrict ourselves to functions that only depend on unordered rolled sequences (or equivalently, the sequence after sorting each pool), the number of such sequences grows as a high order polynomial (this can be derived from e.g. comments on this answer to another question).
However, it turns out that if we can phrase the pool scoring function as a state transition over tuples (outcome, number of dice in each pool that rolled that outcome) with not too many states, we can reduce the time complexity to a polynomial of reasonable order. Here's a fuller explanation of the algorithm. This RISK-like scoring can indeed be expressed like this.
Expressing a single stage
I implemented this approach as part of my hdroller Python library. You can try this computation in your browser using this JupyterLite notebook.
The first step is to express a single stage of a RISK-like pool evaluation as a state transition function as defined above. Given the number of dice on each side, this computes the distribution of the number of remaining dice on each side after a single stage.
Finding the fixed point
Since we reroll each stage until at least one die is eliminated, the number of stages is bounded by the total number of dice. Therefore we can then repeatedly apply this attrition until everything reaches an absorbing state. We could compute the maximum number of stages explicitly, or just look for a fixed point.
Denominator: 350190883979307611136000
If you just want to know the winner:
Denominator: 2244741412001587200
This can compute a dozen dice on each side without much issue, though the denominator can get quite massive.