JBCKB · 2022-03-07 11:36
Hello ,
I'm looking to write a function, for my role-playing games, allowing me to create random tables with elements that have weight.
For example, consider the following list:
1 - 20 : 2d6 kobold
21 - 45 : 1 troll
46 - 100 : 3d6 orcs
Running the function I want to be able to pick 20% of the time the "2d6 kobold" etc.
Ideally, the function would allow me to automatically execute the dice roll. I will have then, for example: 5 kobolds!
Not being a programmer, all I can do is create a function to select an item from a "flat" list.
Any idea?
EcstaticBill2638 · 2022-03-07 12:01
I'm not a programmer either, but I have had a go at exactly this in the past, here's what I came up with:
(defun roll-weighted-list (alist)
"Transforms a weighted `alist' of format (item . odds) into a `pcase' sexp and evals it.
The total odds will be a sum of all the `odds' values in the `alist'."
(eval (cl-loop for (key . value) in alist
for running-total = (+ (or running-total 1) value)
collect `((pred (> ,running-total)) ,key) into list
finally return (append `(pcase (1+ (random ,(1- running-total))))
list))))
I think it works with your list if you enter it like this:
(roll-weighted-list '(("2d6 kobolds" . 20)
("1 troll" . 15)
("3d6 orcs" . 65)))
If you want it to do your die rolling in there, maybe use a helper function like this:
(defun roll-dice (&optional dice num &rest exclude)
"Roll a random number from 1 to `dice' (default 6).
If `num' is specified, roll `num' times and sum the result.
If `exclude' numbers are specified, re-roll any matching results (after summing)."
(let* ((dice (or dice 6))
(num (or num 1))
(output (cl-loop repeat num
sum (1+ (random dice)))))
(if (member output exclude)
(apply #'roll-dice dice num exclude)
output)))
and format the weighted list like this:
(roll-weighted-list `((,(format "%s kobolds" (roll-dice 6 2) . 20)
("1 troll" . 15)
("3d6 orcs" . 65)))
bendersteed · 2022-03-07 13:21
(defvar enemy-alist
'((20 (2 6) kobolds) (45 1 troll) (100 (3 6) orcs)))
(defun dice (times size)
(if (= times 1)
(1+ (random size))
(+ (1+ (random size)) (dice (1- times) size))))
(defun pick-random-encounter (roll enemy-alist)
(if (<= roll (first (car enemy-alist)))
(cons
(let ((number-or-spec (second (car enemy-alist))))
(if (listp number-or-spec)
(apply #'dice number-or-spec)
number-or-spec))
(third (car enemy-alist)))
(pick-random-encounter roll (cdr enemy-alist))))
(defun random-encounter (enemy-alist)
(let ((roll (dice 1 100)))
(pick-random-encounter roll enemy-alist)))
I think something like this could work, we express the possible partition of enemies as a list of specs (threshold (dice specs or number) enemy-symbol) with dice specs being lists and numbers plain atoms. It's important that the thresholds need to be in ascending order.
[deleted] · 2022-03-07 14:37
[removed]
JBCKB · 2022-03-07 14:51
Thank you kindly ! I rewrote the last snippet of code like this :
(defun dd5-aqua-encounter-lvl1-4 ()
(interactive)
(message (roll-weighted-list `(
(,(format "%s kobolds" (roll-dice 6 2)) . 20)
("1 troll" . 15)
(,(format "%s orcs" (roll-dice 6 3)) . 65)))))
EcstaticBill2638 · 2022-03-07 14:56
Ah yep, that's what I meant. Good luck with it.
JBCKB · 2022-03-07 14:58
thank you very much for your reply ^^
I will use the solution above but study yours to try to understand a little how it works ;)
JBCKB · 2022-03-07 15:00
I don't know if this is the place to start a debate on the different ways to practice ttrpg XD
However, I play D&D, but also QuestWorlds or PbtA ;) (among others)
jsled · 2022-03-07 16:55
This has been removed, as it is not very civil; please attack ideas, not people.
nv-elisp · 2022-03-07 18:52
Here's another approach:
(defun roll-weighted (items)
"Rerturn a random weighted item from ITEMS.
ITEMS is an alist of form: ((description . weight)...)."
(let* ((rand (random (cl-reduce #'+ items :key #'cdr)))
(len (length items))
(index -1)
found)
(catch 'found
(while t
(let* ((current (nth (mod (cl-incf index) len) items))
(current-weight (cdr current)))
(when (< (setq rand (- rand current-weight)) current-weight)
(throw 'found (car current))))))))
Translated from this [SO answer](https://stackoverflow.com/questions/1761626/weighted-random-numbers).