In Dungeons & Dragons, almost everything is decided by rolling a die. Typically, if the roll is greater than or equal to a specified value, your attempt at doing whatever you wanted to do succeeds, and fails otherwise. Most commonly, a 20-sided die (aka d20) is used to roll.

Other times, the skill challenge system is used. It is similar to the simple system described above, but success is determined by whether or not the player(s) succeed individual rolls a certain number of times before failing a certain number of times. For example, the player(s) may be trying to pick multiple locks on a door with a limited number of lockpicks. Individual successful rolls represent successfully picking one of the locks, and individual failing rolls represent breaking a lockpick. Overall success would mean successfully picking all of the locks before breaking all of the lockpicks.

Furthermore, certain rolls can be critical rolls. On a d20, rolling a 1 is a critical failure, resulting in immediately failing the entire challenge (in the above example, the player(s) might accidentally alert a guard). Rolling a 20 is a critical success, resulting in immediately succeeding the entire challenge (in the above example, the player(s) might find a set of keys to the locks, removing the need to pick them). In the case of a critical roll, the challenge is immediately over and the outcome decided, regardless of the previous number of successes and failures.

In this challenge, you will be given a difficulty, the number of successes needed, and the number of failures at which the challenge is failed. You must simulate a player attempting the challenge, and output the result.


3 integers, representing the value that must be met or exceeded to succeed at an individual roll, the number of successes needed to succeed at the challenge, and the number of failures at which the challenge is failed. The order and format of the inputs does not matter, as long as you specify what order you will be using. The difficulty will be between 1 and 20, inclusive, and the number of successes and failures will both be between 1 and 100, inclusive.


The results of each of the d20 rolls (integers, in order), and the overall result of the challenge (a truthy/falsey value). The format does not matter, as long as the individual results are in order, the overall result either comes before or after all of the individual rolls (you can't output the overall result in the middle of the rolls, for example), and you specify what output format you use and use it consistently.

Examples (values in parentheses are for explanation and need not be included):


12 5 3 (difficulty successes failures)


15 (success, 1-0)
10 (failure, 1-1)
5  (failure, 1-2)
16 (success, 2-2)
12 (success, 3-2)
15 (success, 4-2)
19 (success, 5-2)
True (overall success)


15 2 3 (difficulty failures successes)


0  (overall failure)
15 (success, 1-0)
12 (failure, 1-1)
13 (failure, 1-2)


5 5 10 (successes failures difficulty)


11 (success, 1-0)
5  (failure, 1-1)
20 (critical success)
1  (overall success)


3 10 3 (failures difficulty successes)


12 (success, 1-0)
11 (success, 2-0)
1  (critical failure)
False (overall failure)


  • This is , so shortest code in bytes wins
  • You must randomly choose an integer value between 1 and 20 (inclusive) for each roll. Each value should have an equal probability of being chosen (or as close to equal as possible).
  • \$\begingroup\$ @BradGilbertb2gills the number of successes and failures will both be between 1 and 100, inclusive. So, yes, there is the possibility that a single failure results in failing the entire challenge. \$\endgroup\$
    – user45941
    Commented Dec 17, 2015 at 3:34
  • \$\begingroup\$ Should I assume that the true value representing overall success always has to be the same true value? Or could it just be the number of failures left? \$\endgroup\$ Commented Dec 17, 2015 at 5:59
  • \$\begingroup\$ @BradGilbertb2gills It does not have to be the same true value; I use the number of failures left in my Python answer. \$\endgroup\$
    – user45941
    Commented Dec 17, 2015 at 6:00
  • \$\begingroup\$ Ehh, I'm going to probably just leave it as returning a Bool, as that is only one byte, and it helps improve the readability of the output. \$\endgroup\$ Commented Dec 17, 2015 at 6:08
  • \$\begingroup\$ @BradGilbertb2gills Readability is much less important than score. \$\endgroup\$
    – user45941
    Commented Dec 17, 2015 at 6:09

8 Answers 8


Python, 134 bytes

Thanks Pietu1998 for the bytes saved

from random import*
def g(a,b,c):
 while z[0]*z[1]:d=randint(1,20);z[a<d]-=[1,z[a<d]][d in[1,20]];s+=[d]

Pretty simple, can probably be golfed a bit more, but we needed something to kick this off. Try it online.

  • \$\begingroup\$ You can save a couple of bytes: change the import to from random import* and drop random., use randint(1,20) instead of randrange(20)+1, replace and with *. You are also allowed to put the final result in the start of the output, saving the space. \$\endgroup\$ Commented Dec 16, 2015 at 2:12

Pip, 39 bytes

Someone said they wanted to see a solution in a golfing language.


I'm pretty sure this doesn't use any language features newer than the question. Takes input as command-line args in this order: difficulty, successes required, failures required. Outputs 0 for overall failure or nonzero for overall success. Try it online!

The approach is a fairly straightforward while-loop strategy, with a trick or two taken from other solutions. Here's a version with comments, whitespace, and some extra output:

; a,b,c are initialized to the cmdline args
; a = difficulty (roll >=a succeeds, roll <a fails)
; b = required successes to succeed the task
; c = required failures to fail the task
; d = single die roll

; Loop while c and b are both nonzero:
W c&b {
 ; d gets 1+randrange(20); output it
 O d:1+RR20
 ; If d<a, decrement req'd failures, else decrement req'd successes
 d<a ? --c --b
 ; Verbose output for the ungolfed version
 P " (" . (d=1|d=20 ? "critical " "") . (d<a ? "failure" "success") . ")"
 ; If d=1, req'd failures is * by 0 (becomes 0), else * by 1 (unchanged)
 c *: d>1
 ; If d=20, req'd successes is * by 0 (becomes 0), else * by 1 (unchanged)
 b *: d<20
; c, remaining failures, is the output: 0 if overall failure, nonzero if overall success
c . " (overall " . (c ? "success" "failure") . ")"

JavaScript, 83 78 76 75 bytes


This code recursively counts down successes and failures as they happen. When either successes (s) or failures (f) have counted down to 0, we finish with the true value !s when s is 0 or with the falsy value of f when f is 0.

Output is of the regular-expression form /^(-\d{1,2})+(0|true)$/ (or, more strictly, /^(-[1-9]|-1[0-9]|-20)+(0|true)$/). That is, the input has a leading hyphen, then roll values delineated by hyphens, and finally the ultimate outcome (0 or true), which is not delineated from the final roll. However, this is still an unambiguous grammar because the utilmate outcome and final roll can always be distinguished: the last character of the output (either 0 or e) is always indicative of the outcome, and a final 0 is always read separately from the number(s) of the final roll.

Sample outputs for F(11,3,4):

-3-14-12-16-16true  // normal success
-2-12-20true        // critical success
-20true             // first-roll critical success
-18-2-8-14-18-90    // normal failure
-18-12-10           // critical failure
-10                 // first-roll critical failure
-4-16-4-100         // normal failure where last roll is a 10


This code works by rolling a negative d20 and (ab)using the negative signs as delimiters.

F=(d,f,s)=>    // define function F(difficulty, fails, successes)

!s||   // if zero more successes needed, return true
f &&   // if zero more failures needed, return 0

    (r=~(Math.random()*20)  // add negative d20 to output, store in `r`
    +""+                    // string concatenation
    F(                      // recursive call to F with changed fail/success
       d,                   //   pass along d      
       ~r                   //   if r is -1, zero more fails needed
          &&f-              //   otherwise, reduce fails needed by
              (k=d>-r),     //   the boolean `d>-r` (and store in k)
       r+20                 //   if r is -20, zero more successes needed
           &&s-!k           //   otherwise, reduce successes needed by
                            //   the opposite of `k` (which indicates a fail)

Number-minus-boolean expressions work because true and false are cast to 1 and 0 in a numeric context. In this case, d>-r will be 1 if the roll is a failure and 0 if it was a success.


Python 2, 123 121 bytes

from random import*
def f(a,b,c):
 while c*b:
    r=randint(1,20);print r;c-=r<a;b-=r>=a
    if r in[1,20]:return r>9
 return c

(This answer mixes spaces and tabs, so the first indentation level is a single space, while the second is a single tab.)

The function f takes the following arguments:

a, the threshold for an individual die roll to count as a success,

b, the number of successes needed for overall success,

c, the number of failures needed for overall failure.

On each die roll either b or c is decremented (but not both). As long as both are positive, it loops again, except in the case of critical failure or critical success.

Assuming no critical successes or failures, when the loop finishes either b or c will be zero, but not both. In that case the function just returns the current value of c, which is zero (Falsey) if we exhausted all of our failures, and positive (Truthy) if we succeeded.

As a bonus, the output tells you how many failures you had remaining, which is nice in case there's (say) more locks to pick later. (Unless it terminated at a critical failure or success, in which case the output will be a boolean instead of an int.)


Ruby 2.2, 75 bytes

f=->(v,s,f){p(r=rand(20)+1)<2?f=0:r>19?s=0:r<v ?f-=1:s-=1while s*f>0
p s<1}

Basic iterative solution. Example run:

f[12, 5, 3]

Might output:


You can see it running on IDEONE here.

  • \$\begingroup\$ Makes me really jealous of langues where 0 is falsey! \$\endgroup\$ Commented Dec 17, 2015 at 22:16

VBA 180 Bytes

Sub P(d,s,f):k=1
Do While x<s And v<f:r=Int(20*Rnd()+1)
If r=20 Then x=s
If r=1 Then v=f
If r>=d Then: x=x+1: Else: v=v+1
Debug.Print r:Loop:If v>=f Then k=0
Debug.Print k:End Sub

Example Output

P 12,5,3

The last Digit of output will be a 0 for False or a 1 for True . Each Roll is separated by a newline. This uses VBA built in RNG rnd() which is know for being Not So Random , But this should fulfill the requirements as best as possible.

Sub P(d,s,f)
Do While x<s And v<f               'Keep Rolling while Current Successes and Failures are less then the Maximum Allowed
r=Int(20*Rnd()+1)                'Creates a Randomish Number between 1 and 20
If r=20 Then x=s                   'Checks for Crit Success
If r=1 Then v=f                    'Checks for Crit Failure
If r>=d Then: x=x+1: Else: v=v+1   'Increments Current Success or Fails
Debug.Print r                      'Prints (Could us MSGBOX, it is shorter)
If v>=f Then k=0                   'Checks & Changes Total Outcome to False
Debug.Print k                      'Prints (Could us MSGBOX, it is shorter)
End Sub

SpecBAS - 165 bytes

1 INPUT d,s,f
2 DIM p(2)
3 DO 
4 r=1+INT(RND*20): ?r
5 IF r IN [1,20] THEN EXIT 
6 INC p((r>=d)+1)
7 LOOP UNTIL p(1)>=f OR p(2)>=s
8  ?IIF$(r=1 OR p(1)>=f,"fail","success")

Input should be entered in difficulty, successes, failures order.

The new release of SpecBAS now allows "?" instead of PRINT and removes the need for LET in front of variable assignments, so this was a nice way to try them out.

As arrays are 1-based by default, line 6 returns 0/1 if roll beats difficulty and adds 1 to update the right index.


Perl 6,  101  99 bytes

->$/ {(1..20).roll(*).map({$1*$2||last;$2-=$0>$_;$2=0 when 1;$1-=$_>=$0;$1=0 when 20;$_}).eager,$2}
# 101 bytes
->$/ {
  (1..20).roll(*).map({  # roll an infinite sequence, and map over them
    $1*$2||last;         # stop if either counter is 0
    $2-=$0>$_;           # decrement failure counter when a failure
    $2=0 when 1;         # set failure counter to 0  when a critical failure
    $1-=$_>=$0;          # decrement success counter when a success
    $1=0 when 20;        # set success counter to 0  when a critical success
    $_                   # the rolled value
  }).eager,$2            # the value of failure counter

Input is a mutable array containing difficulty, successes, failures

Output is a two element list, first element is a list of the rolled values, second element is the number of failures remaining.


# give it a name for ease of use
my &code = {...}

for ^10 { say code [12, 5, 3] }
((14 4 15 5 5) 0)
((17 4 16 12 3 8) 0)
((2 14 14 7 14 19 19) 1)
((3 12 13 15 10 1) 0)
((3 17 16 10 11) 0)
((18 11 18 4 6) 0)
((15 13 1) 0)
((13 15 8 2 8) 0)
((16 17 8 10 11) 0)
((9 20) 2)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.