# Infinity Dice Rolling Engine

The miniatures game Infinity has some interesting and unique mechanics. The first is that the player who is not currently taking their turn gets to have their models react to the actions of the active player. The second mechanic is that if the action and reaction are directly opposed (for example, the two models are firing at one another), both players roll dice at the same time (called a Face to Face roll). The player who scores “better” wins the contest and their models performs the action.

Let’s take a very simple case of two basic infantry troops shooting each other. The active model is he Fusilier Angus, firing his Combi Rifle with Ballistic Skill (BS) of 12. This weapon lets him roll 3 20-sided dice, which will succeed on a roll of his BS of 12 or less on each die. His opponent is the Zhanshi Wen Liu, firing his Combi Rifle with BS 11. As he is reacting, he only gets a single die, and his BS is lower, so he is looking to roll an 11 or less. They both roll their dice at the same time, and then compare results. First, any dice that missed (rolled above the firer’s BS) are discarded. Then, any successes that rolled lower than the opponent’s highest die are discarded. This leaves at most one player with any successful hits, which are then applied to the target. There are a few more complexities, but this outlines the basic flow. To make it concrete – Angus rolls a 15, 10, and 7, and Wen Liu rolls a 9. Angus’s 15 is discarded as a miss (since he rolled greater than 12), and his 7 is discarded since it is lower than Wen Liu’s 9. Meanwhile, Wen Liu’s 9 is discarded since Angus rolled a 10. The only die left is Angus’s 10, meaning he scored one hit on Wen Liu.

In this basic case, the probabilities are relatively easy to calculate. For each of the active player’s dice, it is easy to calculate the chance that they rolled less than or equal to their target, minus the chance that their opponent rolled higher without going over their own target value. And in fact, there has long been an online tool called Infinity Math to help calculate probabilities where the reactive player is limited to one die.

The calculations get much more complicated when the reactive model is allowed to roll multiple dice. I attempted for some time to come up with a reasonably simple numerical way of calculating these chances, but kept on coming up short. Eventually, I remembered that my personal strengths are not in numerical analysis, but rather in computational and algorithmic optimization. Therefore, I decided to make a program which implements a really simple and straightforward means of calculating these probabilities, and then optimize it until it was acceptably efficient.

### Basic Design Overview

The basic design of my dice rolling engine is to enumerate every possible permutation of dice that could be rolled, evaluate the results, and then calculate how likely each possible result is. Since the number of dice required can change for every scenario, my program implements this step using recursion. This allowed me to produce accurate results, but very slowly. Even a moderately complex calculation could easily take several minutes to complete. As such, looked for ways to optimize the algorithm to reduce the time taken.

### Optimization – Matrix Symmetries

The first and biggest optimization that I implemented that decreased my running time by several orders of magnitude was to take advantage of symmetries in the matrix of all dice rolls I was generating. In other words, I made sure I never calculated results that varied only by the order of the numbers rolled.

Take this matrix showing all possible combinations of two four-sided dice as a simplified example:

(1,1) (1,2) (1,3) (1,4)
(2,1) (2,2) (2,3) (2,4)
(3,1) (3,2) (3,3) (3,4)
(4,1) (4,2) (4,3) (4,4)

Because a player doesn’t care what order they rolled their dice in (since all dice are rolled simultaneously), we can cut the number of dice rolled approximately in half, by including only those rolls where the second number is greater than or equal to the first.

(1,1) (1,2)*2 (1,3)*2 (1,4)*2
(2,2) (2,3)*2 (2,4)*2
(3,3) (3,4)*2
(4,4)

As you can see, the second matrix doesn’t have any duplicate entries, but does mark the ones that need to be double-counted. As the number of dimensions increases, the multiplicative factor can become much larger than 2, based on the number of repeated values.

As the number of dice increases, so do the number of dimensions of the matrix. In these higher-dimension matrices, the savings from matrix symmetries increase exponentially.

Dice Permutations After Optimization Percentage
1 20 20 100%
2 400 210 52.5%
3 8000 1540 19.25%
4 160000 8855 5.35%

And so forth.

### Optimization – Miss Consolidation

The great thing about that optimization is that it always works, no matter what the scenario. The next one I did, however, helps in most common cases, but will occasionally offer little to no benefit.

Since all dice that roll higher than the target number are discarded without being used, it is unnecessary to roll all of them. I let the first failing die through in order to count it in the statistics for failures and misses, and I simply multiply the number of results by how many other failure states I rolled up. From the initial example of Fusilier Angus trying to roll a 12 or less, this:

1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20

Becomes this:

1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 * 8

Obviously, the lower the target numbers involved, the greater the value of this optimization. Due to the way that this optimization stacks with the prior one, it frequently reduces the number of rolls calculated by well more than half.