; a helper function which prints each argument seperated by a space
(define output
        (lambda (. things)
                (cond
                  ((null? things) (display #\newline))
                  (else
                    (display (car things))
                    (display #\space)
                    (apply output (cdr things))))))

; a helper function which shows arguments before calling them
(define explicit-apply
        (lambda (f . args)
                (let ((result (apply f args)))
                     (output f args result)
                     result)))

; determine whether or not a given attack roll will hit
(define is-hit?
        (lambda (roll attack ac)
                (or (= roll 20) (and (< 1 roll) (>= (+ roll attack) ac)))))

; determine whether or not a given attack roll will crit
(define is-crit?
        (lambda (roll attack ac threat)
                (or (= roll 20) (and (is-hit? roll attack ac) (>= roll threat)))))

; determine the expected damage of a particular attack roll
(define roll-dmg
        (lambda (roll attack ac dmg threat mult)
                (cond
                  ((is-crit? roll attack ac threat) (* dmg mult))
                  ((is-hit? roll attack ac) dmg)
                  (else 0))))

; determine the expected damage across all attack rolls
(define expected-dmg
        (lambda (. args)
                (define adder
                        (lambda (total roll)
                                (if (> roll 20)
                                    total
                                    (adder (+ total (apply roll-dmg roll args))
                                           (+ roll 1)))))
                (/ (adder 0 1) 20)))

; find the best power attack score (and expected damage) versus an AC
(define find-best-power
        (lambda (power-max attack ac dmg threat mult)
                (define checker
                        (lambda (power best-power best-dmg)
                                (if (> power power-max)
                                    (list best-power best-dmg)
                                    (let* ((a (- attack power))
                                           (d (+ dmg power))
                                           (ed (expected-dmg a ac d threat mult)))
                                          (if (> ed best-dmg)
                                              (checker (+ power 1) power ed)
                                              (checker (+ power 1) best-power best-dmg))))))
                (checker 0 0 0)))

; iterate across a range of armor classes
(define iter
        (let ((max-power 6)
              (attack 6)
              (dmg 5.5)
              (threat 20)
              (mult 3))
             (lambda (ac max-ac)
                     (explicit-apply find-best-power max-power attack ac dmg threat mult)
                     (if (> ac max-ac) #f (iter (+ ac 1) max-ac)))))
(iter 10 30)