point-buy.el

(defun d (x y)
  "Roll XdY."
  (let ((total 0))
    (dotimes (i x)
      (setq total (+ total 1 (random y))))
    total))

;; (d 1 20) => 13

(defun roll (d)
  "Roll 3d6, or 4d6 and drop the lowest dice."
  (cond ((= d 3)
	 (d 3 6))
	((= d 4)
	 (let ((rolls (mapcar (lambda (i) (d 1 6)) '(1 2 3 4))))
	   (- (apply '+ rolls) (apply 'min rolls))))
	(t (error "Only takes 3 or 4 as an argument"))))

;; (mapcar (lambda (ignore) (roll 4)) (make-list 10 nil))
;; => (12 13 16 14 10 9 13 14 14 8)

(defun bonus (ability)
  "Return the ABILITY bonus."
  (- (/ ability 2) 5))

;; (mapcar 'bonus '(6 7 8 9 10 11 12 13 14 15 16 17 18))
;; => (-2 -2 -1 -1 0 0 1 1 2 2 3 3 4)

(defun abilities ()
  "Return a list of six ability scores."
  (let* ((scores (mapcar (lambda (ability) (roll 4))
			 '(str con dex int wis cha)))
	 (avg-bonus (apply '+ (mapcar 'bonus scores)))
	 (max-score (apply 'max scores)))
    (if (or (<= avg-bonus 0)
	    (<= max-score 13))
	(abilities); recursive
      scores)))

;; (abilities) => (13 12 11 13 14 14)

(defun points (ability)
  "Return the points ABILITY is worth."
  (let ((doubles (min 2 (max 0 (- ability 14))))
	(triples (max 0 (- ability 16))))
    (+ (- ability doubles triples)
       (* 2 doubles)
       (* 3 triples)
       -8)))

;; (mapcar 'points '(8 9 10 11 12 13 14 15 16 17 18))
;;  => (0 1 2 3 4 5 6 8 10 13 16)
;; (mapcar 'points '(3 4 5 6 7)) => (-5 -4 -3 -2 -1)

(defun avg (&rest list)
  "Average of a list of numbers."
  (/ (apply '+ list) 1.0 (length list)))

;; (avg 1 2 3) => 2.0

(defun test (num)
  (interactive "nNumber of characters to generate? ")
  (message "%S"
	   (apply 'avg (mapcar
			(lambda (ignore)
			  (apply '+ (mapcar 'points (abilities))))
			(make-list num nil)))))