das-schwarze-auge.ss
```#lang scheme
(require "common.ss")

;; Simulates a normal D20 roll as specified by the rules of
;; "Das Schwarze Auge".
(define d20
(make-dice 20 #f))

(provide d20)

;; Simulates a DSA attribute test.
;; Params:
;;   attribute = The value of the attribute.
;;   generator = The PRNG to use.
;; Returns:
;;   A test result with the simulated D20 dice roll as its value.
(define (simulate-test/attribute attribute [generator (current-pseudo-random-generator)])
(let ([result (d20 generator)])
(make-test-result (<= result (max 1 (min attribute 19)))
(case result
[(1 20)
#t]
[else
#f])
result)))

;; Determines the statistical properties of a DSA attribute test.
;; Params:
;;   attribute = The value of the attribute.
;; Returns:
;;   Test statistics with the expected result of the D20 dice roll
;;   as its expectation.
(define (analyze-test/attribute attribute)
(make-test-statistics (* 1/20 (max 1 (min attribute 19)))
2/20
21/2))

(provide/contract [simulate-test/attribute (->* (exact-integer?)
(pseudo-random-generator?)
test-result?)]
[analyze-test/attribute (-> exact-integer?
test-statistics?)])

;; Simulates a DSA talent test.
;; Params:
;;   attribute-0 = The first base attribute of the talent.
;;   attribute-1 = The second base attribute of the talent.
;;   attribute-2 = The third base attribute of the talent.
;;   talent      = The value of the talent.
;;   generator   = The PRNG to use.
;; Returns:
;;   A test result with the remaining talent points not used
;;   in the test as its value.
(define (simulate-test/talent attribute-0 attribute-1 attribute-2 talent [generator (current-pseudo-random-generator)])
(let* ([result-0 (simulate-test/attribute (+ attribute-0 talent))]
[result-1 (simulate-test/attribute (+ attribute-1 talent))]
[result-2 (simulate-test/attribute (+ attribute-2 talent))]
[remaining-talent (- talent
(max (- (test-result-value result-0) attribute-0) 0)
(max (- (test-result-value result-1) attribute-1) 0)
(max (- (test-result-value result-2) attribute-2) 0))])
(make-test-result (and (test-result-successful? result-0)
(test-result-successful? result-1)
(test-result-successful? result-2)
(or (<= talent 0)
(>= remaining-talent 0)))
(and (test-result-critical? result-0)
(test-result-critical? result-1)
(test-result-critical? result-2))
remaining-talent)))

;; Determines the statistical properties of a DSA talent test.
;; Params:
;;   attribute-0 = The first base attribute of the talent.
;;   attribute-1 = The second base attribute of the talent.
;;   attribute-2 = The third base attribute of the talent.
;;   talent      = The value of the talent.
;; Returns:
;;   Test statistics with the expected remaining talent points
;;   not used in the test as its expectation.
(define (analyze-test/talent attribute-0 attribute-1 attribute-2 talent)
(let-values ([(probability expectation) (for*/fold ([success 0] [expectation 0]) ([result-0 (in-range 1 21)]
[result-1 (in-range 1 21)]
[result-2 (in-range 1 21)])
(let ([remaining-talent (- talent
(max (- result-0 attribute-0) 0)
(max (- result-1 attribute-1) 0)
(max (- result-2 attribute-2) 0))])
(values (if (and (<= result-0 (max 1 (min (+ attribute-0 talent) 19)))
(<= result-1 (max 1 (min (+ attribute-1 talent) 19)))
(<= result-2 (max 1 (min (+ attribute-2 talent) 19)))
(or (<= talent 0)
(>= remaining-talent 0)))
(+ success 1/8000)
success)
(+ expectation (* 1/8000 remaining-talent)))))])
(make-test-statistics probability 2/8000 expectation)))

(provide/contract [simulate-test/talent (->* (exact-integer? exact-integer? exact-integer?
exact-integer?)
(pseudo-random-generator?)
test-result?)]
[analyze-test/talent (-> exact-integer? exact-integer? exact-integer?
exact-integer?
test-statistics?)])
```