D&D Skill Challenges

Python, 134 bytes

Thanks Pietu1998 for the bytes saved

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

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


Pip, 39 bytes

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

Wc&b{Pd:1+RR20d<a?--c--bc*:d>1b*:d<20}c

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") . ")"

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.)