Players Roll All D20s

Posted: 2025-06-19
Last Modified: 2025-06-19
Word Count: 804
Tags: d20 rpg

Table of Contents

So, I’m trying to figure out how to do a “players roll all dice” version of d20 where the probabilities remain the same for both sides.

Addenda to the d20 SRD suggest that enemies receive an Attack Score equal to 11 + their normal attack bonus and make Defense Checks equal to 1d20 + (AC - 10). However, when I brought this up on a Discord server, one user insisted that I should use (AC - 11), and indeed 11 across the board to convert static DCs to bonuses and vice versa.

That didn’t sound right to me, so I resolved to do my own calculations.

The Logical Approach

For argument’s sake let’s call the attack bonus of an attacker ATK and define the AC as 10 + DEF, where DEF is the total bonus provided by armor, Dexterity, magic, etc. So the fundamental equation of d20 games is:

1d20 + ATK >= 10 + DEF

or

1d20 >= 10 + DEF - ATK

If we set DEF == ATK for a moment, canceling them out, we find that the d20 must roll 10 or higher to hit. That’s a 55% chance. The probability of not getting hit is therefore 45%. If you’re rolling high for that on a d20, that’s a … 12 or more?

So does that mean your defense roll is 1d20 + DEF vs. 12 + ATK?

I was expecting a 10 or 11, not a 12.

The Algebraic Approach

Let’s try this again:

Attacker’s chance of success is:

1d20 + ATK >= 10 + DEF

1d20 >= 10 + DEF - ATK

Defender’s chance of success is therefore:

1d20 < 10 + DEF - ATK

(21 - 1d20) < 10 + DEF - ATK

since (21 - 1d20) produces the same probability distribution as 1d20.

-1d20 < -11 + DEF - ATK

Taking the negative of both sides, we get:

1d20 > 11 + ATK - DEF

Or, since all these quantities are integers:

1d20 >= 12 + ATK - DEF

Huh.

The Numeric Approach

Not believing in the 12, I decided to plug in a few numbers and see what I got. The results are below.

DEF - ATK ATK DC hit miss DEF DC
+6 16 25% 75% 6
+3 13 40% 60% 9
0 10 55% 45% 12
-3 7 70% 30% 15
-6 4 85% 15% 18

The Programmatic Approach

Not trusting my own arithmetic, I whipped up a quick Python script and ended up with the following numbers:

DEF - ATK ATK DC % hit % miss DEF DC
10 20 5 95 2
9 19 10 90 3
8 18 15 85 4
7 17 20 80 5
6 16 25 75 6
5 15 30 70 7
4 14 35 65 8
3 13 40 60 9
2 12 45 55 10
1 11 50 50 11
0 10 55 45 12
-1 9 60 40 13
-2 8 65 35 14
-3 7 70 30 15
-4 6 75 25 16
-5 5 80 20 17
-6 4 85 15 18
-7 3 90 10 19
-8 2 95 5 20

Note that I’m assuming that a Natural 20 always hits, and a Natural 1 always misses.

Conclusion

So it looks like when I revise The Elf System I’ll have to make sure that all the opponents DCs are based on 12, not 10.

What happened to the elevens? Well, if I’d started with this:

1d20 + ATK >= 11 + DEF

I’d probably have ended up with eleven on the other side:

1d20 + DEF >= 11 + ATK

That’s because a DC of 11 is exactly 50%, which gives the defender a 50% to not get hit. My way, on the other hand, biases rolls toward the attacker by 10%. Bummer for the defender, but it does get combats over with more quickly by having attackers whiff less.

Script

#!/bin/env python3

# /// script
# requires-python = ">=3.13"
# dependencies = [
#     "tabulate",
# ]
# ///

from fractions import Fraction
from tabulate import tabulate

FIVE_PERCENT = Fraction(1, 20)


def dc_to_p(dc: int) -> Fraction:
    if dc <= 1:
        return Fraction(19, 20)
    return Fraction((21 - dc), 20) if dc < 20 else FIVE_PERCENT


def p_to_dc(p: Fraction) -> int:
    if p >= 1:
        return 1
    return (21 - round(p * 20)) if p >= FIVE_PERCENT else 20


def main() -> None:
    headers: list[str] = ["DEF - ATK", "ATK DC", "% hit", "% miss", "DEF DC"]

    table: list[list[int | Fraction | float]] = [
        [
            diff,
            (atkdc := diff + 10),
            (atkp := dc_to_p(atkdc))*100,
            (defp := 1 - atkp)*100,
            p_to_dc(defp),
        ]
        for diff in range(+10, -9, -1)
    ]

    print(tabulate(table, headers=headers, tablefmt="pipe", floatfmt=".0f"))


if __name__ == "__main__":
    main()