shadowrun.ss
#lang scheme
(require "common.ss")

;; Simulates a cumulative D6 roll as specified by the "Shadowrun"
;; rules.
(define cd6
  (make-dice 6 #t))

(provide cd6)

;; Simulates a Shadowrun skill test.
;; Params:
;;   skill     = The skill value.
;;   target    = The target number defining the difficulty of the
;;               test.
;;   generator = The PRNG to use.
;; Returns:
;;   A test result with the number of successes as its value.
(define (simulate-test skill target [generator (current-pseudo-random-generator)])
  (let-values ([(successes ones) (for/fold ([successes 0] [ones 0]) ([_ (in-range skill)])
                                   (let ([result (cd6 generator)])
                                     (cond
                                       [(= result 1)
                                        (values successes (+ ones 1))]
                                       [(>= result target)
                                        (values (+ successes 1) ones)]
                                       [else
                                        (values successes ones)])))])
    (make-test-result (> successes 0) (>= ones skill) successes)))

;; Determines the statistical properties of a Shadowrun skill test.
;; Params:
;;   skill     = The skill value.
;;   target    = The target number defining the difficulty of the
;;               test.
;; Returns:
;;   Test statistics with the expected number of successes as its
;;   expectation.
(define (analyze-test skill target)
  (let* ([cd6>=target (* (expt 1/6 (+ (quotient target 6) 1))
                         (- 7 (max (modulo target 6) 1)))]
         [cd6<target (- 1 cd6>=target)])
    (make-test-statistics (for/fold ([success 0]) ([n (in-range 1 (+ skill 1))])
                            (+ success
                               (* (/ (fact skill) (fact n) (fact (- skill n)))
                                  (expt cd6>=target n)
                                  (expt cd6<target (- skill n)))))
                          (expt 1/6 skill)
                          (* skill cd6>=target))))

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