5
\$\begingroup\$

How would I go about calculating the odds of success someone has, given N total tries at a skill check, a skill modifier of M, and an increasing DC of a base X plus Y per success, for a total of S required successes?

I could probably figure out how to do that calculation if it was just three sequential checks, but I am being stymied by how the total tries factors in.

Consider the situation:

DC 10, increases by 5 per success, where you need 3 successes to truly succeed (so a sequential 10, 15, 20 DC). And you have 10 total tries. I’m trying to figure out how to get that into an anydice (or other) formula, where I can see how likely someone is to succeed based on different ability modifiers (and adjust the initial DC or the DC increase if necessary)

For this, DC being a check where you have to roll higher than the given value, on a D20, plus or minus the modifier

Ps: I’ve read this but I’m asking in relation to a conversation with someone else, so whether it’s a good idea to use as a mechanic is less relevant in this case

\$\endgroup\$
7
  • \$\begingroup\$ Welcome to RPG.SE! Take the tour if you haven't already, and check out the help center for more guidance. \$\endgroup\$
    – V2Blast
    Commented Oct 15, 2021 at 19:53
  • 1
    \$\begingroup\$ Just curious why this matters? There are no default situations I am aware of in 5e where this applies, and it smacks of 'rolling to failure' \$\endgroup\$
    – SeriousBri
    Commented Oct 15, 2021 at 21:52
  • \$\begingroup\$ @SeriousBri I agree, this is totally not the D&D mechanic - one task means one check and they you have succeeded or you haven’t.. \$\endgroup\$
    – Dale M
    Commented Oct 15, 2021 at 23:23
  • \$\begingroup\$ @SeriousBri from what I understand, the person I was talking with was doing a series of increasingly harder challenges, with a set total number of attempts. I was curious how to statistically calculate the chances of success. And on “rolling to failure”, look at the probabilities shown in the answer below; only a DC 10/15/20 with a +0 modifier is below a 50% chance of succeeding overall \$\endgroup\$ Commented Oct 16, 2021 at 1:28
  • 1
    \$\begingroup\$ @DaleM as the OP, I was trying to ask a question about the statistics of it all and how to calculate it, not relating to any particular game type or anything. If the person DM-ing this was asking, sure, you could call it X-Y. But I’m neither the DM nor a player affected by the mechanic, and the conversation had me curious about how to calculate that sort of thing. \$\endgroup\$ Commented Oct 16, 2021 at 10:49

2 Answers 2

15
\$\begingroup\$

Note: While this answer works, HighDiceRoller's solution is a lot simpler, faster and more elegant. I encourage anyone reading this to upvote it.


Here's a simple recursive AnyDice function that should hopefully do what you want, and allow fairly easy modification to explore variations of this mechanic:

function: roll S:n successes after K:n on DIE:d in N:n rolls {
  if S <= 0 { result: 1 }
  if N <= 0 { result: 0 } 
  result: [
   if DIE >= [dc after K successes]
   then [roll S-1 successes after K+1 on DIE in N-1 rolls]
   else [roll S successes after K on DIE in N-1 rolls]
  ]
}

This function makes use of two helper functions. One of them is the generic one from this answer, used to choose between two outcomes based on a die roll:

function: if COND:n then A else B {
  if COND { result: A } else { result: B }
} 

The other one is the custom dc after K successes function, which you can define (or even redefine several times in your program!) any way you like to test different kinds of DC adjustment mechanics.

For example, here's how to calculate the probability of getting 3 successes in 10 rolls using your mechanic, for various values of X, Y and M:

set "maximum function depth" to 20

X: 10
loop Y over {1,3,5} {
  function: dc after K:n successes {
    result: X + Y*K
  }

  loop M over {0..+3} {
    output [roll 3 successes after 0 on d20+M in 10 rolls] named "DC = [X] + [Y]*successes, mod = [M]"
  }
}

I originally wrote this answer before you clarified that you're talking about D&D 5th edition ability checks, where a success means rolling a result equal to or over the DC on d20+M, so I made the code generic enough to handle any kind of rolls, not just d20+constant. For example, if you want to see what happens when rolling with advantage, you can replace d20+M in the code above with 1@2d20+M (or 2@2d20+M for disadvantage).

The code does assume a roll-at-least-DC-to-succeed mechanic, but even that could be fairly easily changed by changing the DIE >= [dc after K successes] condition in the main function.

Note that if you increase the number of rolls to 20 or more, you will also need to increase the "maximum function depth" setting to something higher than it. (AnyDice used to give a helpful error message when that limit was exceeded, but it seems that now it just crashes. I should probably report that as a bug.)

The results are best viewed in summary mode, where you can simply read the probabilities off the "mean" table:

AnyDice screenshot


Ps. Note that, since this function calls itself twice for each roll (to compute the probabilities of reaching the goal, given that the current roll is either a success or a failure), its running time scales approximately in proportion to something like \$N \times 2^{\min(N,S)}\$, where \$N\$ is the maximum number of rolls and \$S\$ is the required number of successes. For \$N, S ≤ 10\$ this is quite manageable, but if you go up to, say, \$N = S = 15\$ or so, it's likely to time out.

In principle, it should be possible to improve this exponential runtime to something proportional to, say, \$N \times S\$, but that would require either memoizing the results of the recursive function calls (which AnyDice doesn't currently support) or manually implementing an equivalent dynamic programming algorithm (which should be technically possibly in AnyDice, but quite difficult and complicated). At that point, one might as well just rewrite it in Python.

(…or you could just do what HighDiceRoller did, which is honestly a lot simpler and more efficient than my solution.)

\$\endgroup\$
2
  • \$\begingroup\$ I’ll have to try this out when I get back to my computer, this looks exactly like what I was interested in. So one would just paste in the relevant code and tweak the values? I’m an absolute newbie to using the more in-depth aspects of AnyDice \$\endgroup\$ Commented Oct 15, 2021 at 19:50
  • \$\begingroup\$ @fyrepenguin: Yes, you can start with this complete program that I linked from the answer. Note that pretty much all the basic tweakable parameters are either given in the function call in the output statement or in the definition of the dc after K successes function above it. \$\endgroup\$ Commented Oct 15, 2021 at 19:59
14
+100
\$\begingroup\$

Polynomial-time method without custom functions

Here's an example of a +5 modifier against DC 10, 15, and 20.

AnyDice link.

set "explode depth" to 100
M: 5
output 3 + [explode d20+M < 10] + [explode d20+M < 15] + [explode d20+M < 20]

Chance of taking at most N tries.

The x-axis is the number of tries, and the y-axis is the chance of taking at most that many tries to complete the series.

How it works

Instead of directly computing the chance of completing the series in \$N\$ tries, it's easier to compute the number of tries it would take to complete the series. If this is at most \$N\$, the series is a success, otherwise it's a failure.

The number of tries consumed by each "stage" in the series (in this example, we have three stages of DC 10, 15, and 20) are independent of each other, so we can just sum them.

From here, the number of failures experienced in each stage follows a geometric distribution. We can compute each of the stages by applying the built-in explode function to a single try at that stage:

  • A 0 on a single try represents a success, terminating the explosion and that stage.
  • A 1 on a single try represents a failure, consuming a try and forcing the player to try again, represented by the explosion.

We do need to increase the explode depth since AnyDice's default of 2 is too small for this purpose and would only count up to 3 failures per stage. Fortunately each explosion doesn't increase computation time much in this case. Therefore, we can just set the explode depth to something that exceeds any number of tries we could possibly be interested in.

Finally, we add 1 for each of the stages since the successful roll at each stage also consumes one try. This accounts for the initial 3.

The [lowest of ... and 20] is simply to cut off the graph at a reasonable end for visualization and could be omitted (as I've done in the copy of the code above).

\$\endgroup\$
3
  • 2
    \$\begingroup\$ +1, that's a very clever solution! I'm kind of tempted to suggest making it a bit more self-documenting by using a helper function like function: rolls to succeed on DIE vs DC { result: 1 + [explode DIE < DC] }, so that you wouldn't have to include the "magic" +3 in the final output expression, but it's very nice and efficient either way. \$\endgroup\$ Commented Oct 16, 2021 at 10:44
  • 1
    \$\begingroup\$ I’m definitely going to have to learn the more complex aspects of AnyDice, both answers here are introducing me to things I didn’t know it could do. \$\endgroup\$ Commented Oct 16, 2021 at 10:52
  • \$\begingroup\$ @IlmariKaronen Thank you for the bounty---it is an honor! \$\endgroup\$ Commented Oct 18, 2021 at 8:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .