Let's say out of \$n\$ \$D\$-sided dice, the highest roll I get is an \$x\$. What are the chances of that?
Well first, I had to roll nothing higher than \$x\$. So that's x
eligible rolls per die \$(1, 2, 3, ... x-1, x)\$, out of \$D\$ I could have rolled, compounded n
times:
$$P(n \text{ rolls} \le x) =
\left(\frac x D\right)^n$$
Now out of these \$x\$ possible values, I must have rolled the biggest one, \$x\$ itself, at least once. That means I didn't roll \$x-1\$ or lower all \$n\$ times:
$$\begin{align}
P(n \text{ rolls} < x | n \text{ rolls} \le x) &= \left(\frac {x - 1} x\right)^n\\
\\
P\left((\text{rolls} = x) \ge 1 | n \text{ rolls } \le x)\right) &= 1 - \left( \frac {x-1} x\right)^n
\end{align}$$
So, putting these together, the chance that my best roll was x
is:
$$\begin{align}
P\left(\text{highest roll} = x \text{ in } n\right) &= P(n \text{ rolls} \le x) \cdot P\left((\text{rolls} = x) \ge 1 | n \text{ rolls } \le x)\right)\\
&= \left( \frac x D \right)^n \cdot \left(1 - \left(\frac {x - 1} x\right) ^n\right)
\end{align}$$
So far so good. But some of my dice might get ruled out by rolling too high. To account for that, I'm going to leave the pure math world and go into some imperative code so we can work iteratively, hopefully a bit clearer than sigma notation. ;)
float[] ProbableBestRollsUnderLimit(int numDice, int numSides, int limit) {
// Initialize an array to hold our probability of rolling each value.
// Here I'll assume C# conventions where the array comes pre-initialized with 0s.
float[] pBest = new float[numSides + 1];
// Set the zero entry to the probability that we roll every die over the limit.
pBest[0] = power(numSides - limit, numDice);
for(int d = 1; d < numDice; d++) {
// Get probability that we roll exactly d dice within our limiting value.
float piWithinLimit = power(limit, d) * power(numSides - limit, numDice - d);
// Factor in combinations of *which* dice were over/within limit.
piWithinLimit *= choose(numDice, d);
// Using the formula worked out above, accumulate probabilities of rolling
// a best-of-d-dice result of i. Here we treat the dice as only limit-sided
// since our P(i dice within limit) term already rules out the higher rolls.
for(int i = 1; i <= limit; i++)
pBest[i] += piWithinLimit * (1f - power((i-1)/i, d))*power(i/limit, d);
}
return pBest;
}
This method returns a table of probabilities, where the i
th entry is the probability that my best roll is i
.
With two of these tables, one for me and one for my opponent, I can work out the probable results of the match:
// My probability table.
float[] myBest = ProbableBestRollsUnderLimit(myDiceCount, 20, myLimit);
// My opponent's probability table.
float[] vsBest = ProbableBestRollsUnderLimit(vsDiceCount, 20, vsLimit);
// Compute a cumulative probability table for "P(opponent rolled lower than X)"
float[] vsLowerThan = new float[21];
for(int i = 0; i < 20; i++)
vsLowerThan[i+1] = vsLowerThan[i] + vsBest[i];
float pWin = 0f;
float pLose = 0f;
float pTie = 0f;
for(int i = 0; i <= myLimit; i++) {
// I win if I roll i and my opponent rolled less than i.
pWin += myBest[i]*vsLowerThan[i];
// We tie if we both rolled exactly i.
pTie += myBest[i] * vsBest[i];
}
pLose = 1f - pWin - pTie;
Running these numbers with your example above (3 dice limit 11 vs 1 die limit 14),the 3-dice roller has a 55.97% chance to win, 7.28% chance to tie, and 36.75% chance to lose. (The dominant factor here being that the opponent has a 30% chance to roll over their limit and have their lone die discarded, while with 3 dice we've got only a 9% chance of complete disqualification this way)
This at least looks right to me. I'll offer this for now with the disclaimer that it's after midnight and I haven't tested this thoroughly, so it's entirely possible there's some blatant bugs lurking in there. ;) If you find one, let me know in the comments and we'll get it sorted out!