Probabilities Basics

First some basics about probabilities. The probability for a specific condition is the number of possible positive events for this condition divided by the total number of possible events. For example the probability to throw a dice6.gif with one 6-sided die is: one positive condition divided by six possible conditions, or 1/6 = 16.67%.

Calculating by Iteration

So if you want to calculate for example the chances of 2 attacking dice winning over 1 defending dice. You have 3 dice taking part. The total number of possible conditions that can happen are 6 possible events per dice for 3 dice. That are 6 * 6 * 6 = 216 possible events. Listing the events might look like this:

dice1.gif dice1.gif wins over a dice1.gif
dice1.gif dice2.gif wins over a dice1.gif
dice1.gif dice3.gif wins over a dice1.gif
...
dice2.gif dice1.gif wins over a dice1.gif
dice2.gif dice2.gif wins over a dice1.gif
dice2.gif dice3.gif wins over a dice1.gif
...
dice1.gif dice1.gif loses over a dice2.gif
dice1.gif dice2.gif wins over a dice2.gif
dice1.gif dice3.gif wins over a dice2.gif
...

For this situation, in 181 cases the attacker wins and in 35 cases the defender wins. Giving the attacker a winning chance of 181 / (181 + 35) = 83.8%

The following python script will use this iteration technique to calculate the table. This is the simple way without much brain power but using a lot of CPU power. In fact I don't think it will finish during a humans lifetime :-).
#!/usr/bin/python
 
def add(x,y): return x+y;
 
def iterate(dice, n):
  win = 0;                            # number of win conditions
  los = 0;                            # number of lose conditions
  while 1:
    if (reduce(add, dice[0:n]) > reduce(add, dice[n:])):   # add the left n dice as attackers, the remaining as defenders
      win+=1;                                              # if the attackers win, increment the winning conditions
    else:                                                  # otherwise the lose conditions
      los+=1;
                                                           # now get the next possible die combination
    dice[-1] += 1;                                         # add one to the right most die
    try:                                                   # we need to check if this die would be a "7"
      while 1:
        i = dice.index(7);                                 # is there a 7 among the dice array?
        if (i == 0):                                       # if the left most die is 7, we are finished with this iteration
          return 100.0 * win / (win + los);                # and can return the result.
        dice[i] = 1;                                       # otherwise set the 7 to a 1
        dice[i - 1] += 1;                                  # and increment the dice left to it. keep checking for sevens.
    except ValueError:                                     # if index() does not find a 7 it raises this error
      pass;                                                # which can be ignored, go on with the next dice combination.
 
for a in range(2, 9):               # iterate through attacking number of dice
  for d in range(1,9):              # iterate through defending number of dice
    print a, "attackers", d, "defenders:",;
    dice = [1] * (a + d);           # create an array of dice to be thrown: attacker + defender
    print iterate(dice, a);         # iterate through all possibilities of this array (1..6), add win / lose
                                    # conditions -> where wins divided by total possibilities is the result.
 

After some minutes following output can be seen:
2 attackers 1 defenders: 83.7962962963
2 attackers 2 defenders: 44.3672839506
2 attackers 3 defenders: 15.200617284
2 attackers 4 defenders: 3.58796296296
2 attackers 5 defenders: 0.610496684957
2 attackers 6 defenders: 0.0766246570645
Note that from step to step it gets significantly slower as more dice are involved. You can press Ctrl+C now to interrupt it. There is a better way.

Calculating by Sums


Lets look at the example with 2 attacking dice and 1 defending die again. What are the possibilities of values the attacker can throw? We can write the 2 dice in a table. The sum of both dice are written in the fields.

dice1.gif
dice2.gif
dice3.gif
dice4.gif
dice5.gif
dice6.gif
dice1.gif
2
3
4
5
6
7
dice2.gif
3
4
5
6
7
8
dice3.gif
4
5
6
7
8
9
dice4.gif
5
6
7
8
9
10
dice5.gif
6
7
8
9
10
11
dice6.gif
7
8
9
10
11
12
You will notice that the attacker throwing a total of 7 is far more likely than throwing for example a total of 2. In fact there are 6 conditions for 7 with 36 possible conditions yielding a chance of 6/36 ... 16.67% throwing a seven. While there is only one possible condition to throw a 2, by two dice1.gifs yielding a chance of 1/36 ... 2.78%.

The number of conditions for the attacker to throw following sums can be easily counted on the table above. They are:
Total
Conditions
2
1
3
2
4
3
5
4
6
5
7
6
8
5
9
4
10
3
11
2
12
1
The defender still has only 6 possibilities. The conditions of attacker and defender can be illustrated by yet another table.


Defender
1
2
3
4
5
6


Conditions
1
1
1
1
1
1
Attacker
Conditions
Winner






2
1

attacker
defender
defender
defender
defender
defender
3
2

attacker
attacker
defender
defender
defender
defender
4
3

attacker
attacker
attacker
defender
defender
defender
5
4

attacker
attacker
attacker
attacker
defender
defender
6
5

attacker
attacker
attacker
attacker
attacker
defender
7
6

attacker
attacker
attacker
attacker
attacker
attacker
8
5

attacker
attacker
attacker
attacker
attacker
attacker
9
4

attacker
attacker
attacker
attacker
attacker
attacker
10
3

attacker
attacker
attacker
attacker
attacker
attacker
11
2

attacker
attacker
attacker
attacker
attacker
attacker
12
1

attacker
attacker
attacker
attacker
attacker
attacker
If the number of conditions is taken into account yielding each result and they are added, they will give 181 winning cases and 35 losing cases, just like before.

Similar table of attacker totals and defender totals for different number of dice can be constructed using the formula (8) from Weisstein, Eric W. (mathworld.wolfram.com):

equation8.gif

Where:
c is the total number of dice combinations that give a specific total p
s is the number of sides of the dice, 6 in this case
n is the number of dice thrown
k is an iteration variable going from 0 to "floor" of (p - n)/s (floor means the next lower integer value)

With this formula all that has to be done, is the iterate over the possible defender totals, calculating how many combinations yield all possible totals while iterating over all possible defender totals. In each step look if the attacker wins (the attacker total > defender total) and add the number of combinations to either the count of winning or losing conditions. Number of winning conditions divided by total possible conditions yields the probability of an attacker success.

The following python script will calculate all the probabilities on a 2GHz machine in 1.9 seconds.
#!/usr/bin/python
import math;
 
precision = 1;         # you can change this: number of decimals shown after comma
 
def comb(n, k):
    """ returns the number of combination of n elements taken k time. """
    global fact;
    return fact[n] / (fact[k] * fact[n - k]);
 
def coef(n, p):
    """  returns the number of rolls which sum is p of n 6-sided dice. """
    kmax = int(math.floor((1.0 * p - n) / 6));
    x = 0;
    for k in range(0, kmax + 1):
        x += pow(-1, k)* comb(n, k) * comb(p - 6 * k - 1, n - 1);
    return x;
 
 
def chance(d, a):
    """ returns the chance a attacking dice win over d defending dice. """
    win = 0;                            # number of attacker win conditions
    los = 0;                            # number of atracker lose conditions
    for ds in range(d, d*6 + 1):        # iterate over possible defender sums
        for as in range(a, a*6 + 1):    # iterate over possible attacker sums
            if as > ds:
                win += coef(d, ds) * coef(a, as); # number of attack conditions winning
            else:
                los += coef(d, ds) * coef(a, as); # number of attack conditions losing
    return 100.0 * win / (win + los);
 
# precalculate factorials until 50 (higher bases are not going to be used)
fact = [0] * 50;
fact[0] = 1;
for i in range(1, 50):
    fact[i] = fact[i-1]*i;
 
# display the table head.
print "def/att".rjust(8),;
for a in range(2, 9):
    print repr(a).rjust(precision+4),;
print;
 
# iterate through the number of defender dice
for d in range(1, 9):
    print repr(d).rjust(8),;
    for a in range(2, 9):    # iterate through the number of attacker dice
        print ('%'+str(4+precision)+'.'+str(precision)+'f')%(chance(d, a)),;
    print;
 

The output will look like this:
 def/att     2     3     4     5     6     7     8
       1  83.8  97.3  99.7 100.0 100.0 100.0 100.0
       2  44.4  77.9  93.9  98.8  99.8 100.0 100.0
       3  15.2  45.4  74.3  90.9  97.5  99.5  99.9
       4   3.6  19.2  46.0  71.8  88.4  96.2  99.0
       5   0.6   6.1  22.0  46.4  70.0  86.2  94.8
       6   0.1   1.5   8.3  24.2  46.7  68.5  84.4
       7   0.0   0.3   2.5  10.4  26.0  46.9  67.3
       8   0.0   0.0   0.6   3.7  12.2  27.4  47.1

Or, if you are interested in more precision:
 def/att         2         3         4         5         6         7         8
       1  83.79630  97.29938  99.72994  99.98500  99.99964 100.00000 100.00000
       2  44.36728  77.85494  93.92361  98.79401  99.82169  99.98013  99.99834
       3  15.20062  45.35751  74.28305  90.93471  97.52998  99.46634  99.90692
       4   3.58796  19.17010  45.95282  71.80784  88.39535  96.15359  98.95340
       5   0.61050   6.07127  22.04424  46.36536  69.96164  86.23765  94.77315
       6   0.07662   1.48786   8.34228  24.24491  46.67306  68.51650  84.38738
       7   0.00709   0.28900   2.54497  10.36260  25.99838  46.91392  67.34556
       8   0.00047   0.04519   0.63795   3.67419  12.15070  27.43755  47.10907