Add dice tables
A dice table is a direct translation of a gaming table from a book, where the table has a stated dice expression, and each entry has a numeric range. Most tables can now be taken as is from published sources. Also, if a message from a randomly rolled table contains a dice expression, like Found 2d8 scrolls, the expression is automatically substituted. Finally if a message contains something like: You found a [wolf/fox/badger]. The displayed message will only include on of those entries.
This commit is contained in:
parent
106ed2e6dd
commit
656c168a52
8 changed files with 155 additions and 59 deletions
|
|
@ -3,7 +3,7 @@
|
|||
#+email: howard.abrams@gmail.com
|
||||
#+FILETAGS: :org-mode:emacs:rpgdm:
|
||||
#+STARTUP: inlineimages yes
|
||||
#+PROPERTY: header-args:emacs-lisp :tangle ../rpgdm-tables-dice.el :comments yes
|
||||
#+PROPERTY: header-args:emacs-lisp :tangle ../rpgdm-tables-dice.el :comments no
|
||||
#+PROPERTY: header-args :eval no-export
|
||||
#+PROPERTY: header-args :results silent
|
||||
#+PROPERTY: header-args :exports both
|
||||
|
|
@ -21,6 +21,10 @@
|
|||
;;
|
||||
;;
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; This file contains the source code, but the concept behind what I'm
|
||||
;; calling random dice tables is a bit complex, so I recommend looking
|
||||
;; at the original file in `docs/rpgdm-tables-dice.org'.
|
||||
|
||||
#+END_SRC
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#+email: howard.abrams@gmail.com
|
||||
#+FILETAGS: :org-mode:emacs:rpgdm:
|
||||
#+STARTUP: inlineimages yes
|
||||
#+PROPERTY: header-args:emacs-lisp :tangle ../rpgdm-tables-freq.el :comments yes
|
||||
#+PROPERTY: header-args:emacs-lisp :tangle ../rpgdm-tables-freq.el :comments no
|
||||
#+PROPERTY: header-args :eval no-export
|
||||
#+PROPERTY: header-args :results silent
|
||||
#+PROPERTY: header-args :exports both
|
||||
|
|
@ -20,13 +20,15 @@
|
|||
;; This file is not part of GNU Emacs.
|
||||
;;
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; This file contains the source code, but the concept behind what I'm
|
||||
;; calling random frequency tables is a bit complex, so I recommend looking
|
||||
;; at the original file in `docs/rpgdm-tables-freq.org'.
|
||||
|
||||
#+END_SRC
|
||||
|
||||
* Introduction
|
||||
While the majority of my tables are simple lists to choose a random element, some lists should return /some elements/ more often than /other elements/. While that sounds great in a sentence, I need to actually code what I mean. I call them /frequency tables/ and they look something like my Faction Encounter table:
|
||||
|
||||
|
||||
| Church of Talos :: Worshipers of the god of storms and destruction. | scarcely |
|
||||
| City Watch :: Members of the Waterdeep constabulary. | often |
|
||||
| Cult of the Dragon :: Cultists who venerate evil dragons. | seldom |
|
||||
|
|
|
|||
|
|
@ -210,6 +210,11 @@ the following:
|
|||
(re-search-forward rpgdm-roll-regexp))
|
||||
(goto-char (match-beginning 0)))
|
||||
|
||||
(defun rpgdm-dice-format-string (str)
|
||||
"Replace all dice expressions in STR with a dice roll results."
|
||||
(while (string-match rpgdm-roll-regexp str)
|
||||
(replace-regexp-in-string (concat rpgdm-roll-regexp "'") 'rpgdm-roll-sum str)))
|
||||
|
||||
;; Practice: somed8 d4 2d8 3d6+2
|
||||
|
||||
(defun rpgdm--roll-expression (expression)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@
|
|||
(require 'org-element)
|
||||
(require 's)
|
||||
|
||||
(defvar rpgdm-base ".")
|
||||
|
||||
(defvar rpgdm-screen-directory
|
||||
(expand-file-name "dnd-5e" rpgdm-base)
|
||||
"Directory path containing the tables to load and create functions.")
|
||||
|
||||
(defun rpgdm-screen--get-list-items ()
|
||||
"Return a list of all the list items in the org document."
|
||||
(org-element-map (org-element-parse-buffer) 'item
|
||||
|
|
@ -47,14 +53,17 @@ The contents of the item is displayed in the mini-buffer."
|
|||
(rpgdm-screen-choose-list)
|
||||
(widen)))
|
||||
|
||||
(defun rpgdm-screen ()
|
||||
(interactive)
|
||||
(defun rpgdm-screen (&optional filepath)
|
||||
"Hard-coded (currently) approach to displaying files from FILEPATH."
|
||||
(interactive (list (read-directory-name "DM Screen Directory: "
|
||||
rpgdm-screen-directory)))
|
||||
(delete-other-windows)
|
||||
|
||||
(let ((default-directory filepath))
|
||||
;; Start the Right Side with DIRED
|
||||
(split-window-right)
|
||||
(other-window 1)
|
||||
(dired "dnd-5e")
|
||||
(dired ".")
|
||||
(dired-hide-details-mode)
|
||||
|
||||
(split-window-right)
|
||||
|
|
@ -81,8 +90,7 @@ The contents of the item is displayed in the mini-buffer."
|
|||
(split-window-below)
|
||||
(other-window 1)
|
||||
(find-file "conditions.org")
|
||||
(other-window 1)
|
||||
)
|
||||
(other-window 1)))
|
||||
|
||||
(provide 'rpgdm-screen)
|
||||
;;; rpgdm-screen.el ends here
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
;; [[file:docs/rpgdm-tables-dice.org::+BEGIN_SRC emacs-lisp][No heading:1]]
|
||||
;;; rpgdm-tables-dice.el --- Rolling dice for choosing items from Tables -*- lexical-binding: t; -*-
|
||||
;;
|
||||
;; Copyright (C) 2021 Howard X. Abrams
|
||||
|
|
@ -11,4 +10,77 @@
|
|||
;;
|
||||
;;
|
||||
;;; Commentary:
|
||||
;; No heading:1 ends here
|
||||
;;
|
||||
;; This file contains the source code, but the concept behind what I'm
|
||||
;; calling random dice tables is a bit complex, so I recommend looking
|
||||
;; at the original file in `docs/rpgdm-tables-dice.org'.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defstruct dice-table dice rows)
|
||||
|
||||
(defun rpgdm-tables--choose-dice-table (table)
|
||||
"Choose a string from a random dice table."
|
||||
(let* ((roll (rpgdm-roll-sum (dice-table-dice table)))
|
||||
(rows (dice-table-rows table))
|
||||
(results (rpgdm-tables-dice--choose roll rows)))
|
||||
(if (stringp results)
|
||||
results
|
||||
(seq-random-elt results))))
|
||||
|
||||
(defun rpgdm-tables-dice--choose (roll rows)
|
||||
"Given a numeric ROLL, return row that matches.
|
||||
This assumes ROWS is a sorted list where the first element (the
|
||||
`car') is a numeric level that if ROLL is less than or equal, we
|
||||
return the `rest' of the row. Otherwise, we recursively call this
|
||||
function with the `rest' of the rows."
|
||||
(let* ((row (first rows))
|
||||
(level (car row))
|
||||
(answer (rest row)))
|
||||
|
||||
(if (<= roll level)
|
||||
answer
|
||||
(rpgdm-tables-dice--choose roll (rest rows)))))
|
||||
|
||||
(setq rpgdm-tables-dice-table-regexp (rx "Roll"
|
||||
(one-or-more space)
|
||||
(optional (or "on" "for"))
|
||||
(zero-or-more space)
|
||||
"Table:"
|
||||
(zero-or-more space)
|
||||
(group
|
||||
(regexp rpgdm-roll-regexp))))
|
||||
|
||||
(defun rpgdm-tables-dice-table? ()
|
||||
"Return non-nil if current buffer contains a dice-table"
|
||||
(goto-char (point-min))
|
||||
(re-search-forward rpgdm-tables-dice-table-regexp nil t))
|
||||
|
||||
(defun rpgdm-tables--parse-as-dice-table ()
|
||||
"Return `dice-table' of lines matching `rpgdm-tables-dice-table-rows'."
|
||||
(let ((dice (match-string-no-properties 1)) ; Grab expression before moving on
|
||||
(rows ()) ; Modify this with add-to-list
|
||||
(row-splitter (rx (* space) "|" (* space)))) ; Split rest of table row
|
||||
|
||||
(while (re-search-forward rgpdm-tables-dice-table-rows nil t)
|
||||
(let* ((levelstr (match-string-no-properties 1))
|
||||
(level (string-to-number levelstr))
|
||||
(row (match-string-no-properties 2))
|
||||
(choices (split-string row row-splitter t)))
|
||||
(add-to-list 'rows (cons level choices))))
|
||||
(make-dice-table :dice dice
|
||||
:rows (sort rows (lambda (a b) (< (first a) (first b)))))))
|
||||
|
||||
(setq rgpdm-tables-dice-table-rows (rx bol
|
||||
(zero-or-more space) "|" (zero-or-more space)
|
||||
(optional (one-or-more digit)
|
||||
(one-or-more "-"))
|
||||
(group
|
||||
(one-or-more digit))
|
||||
(zero-or-more space) "|" (zero-or-more space)
|
||||
(group (+? any))
|
||||
(zero-or-more space) "|" (zero-or-more space)
|
||||
eol))
|
||||
|
||||
(provide 'rpgdm-tables-dice)
|
||||
;;; rpgdm-tables-dice.el ends here
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
;; [[file:../../../../../Volumes/Personal/dropbox/org/rpg-dm/docs/rpgdm-tables-freq.org::+BEGIN_SRC emacs-lisp][No heading:1]]
|
||||
;;; rpgdm-tables-freq.el --- Rolling dice for choosing items from Tables -*- lexical-binding: t; -*-
|
||||
;;
|
||||
;; Copyright (C) 2021 Howard X. Abrams
|
||||
|
|
@ -10,17 +9,17 @@
|
|||
;; This file is not part of GNU Emacs.
|
||||
;;
|
||||
;;; Commentary:
|
||||
;; No heading:1 ends here
|
||||
;;
|
||||
;; This file contains the source code, but the concept behind what I'm
|
||||
;; calling random frequency tables is a bit complex, so I recommend looking
|
||||
;; at the original file in `docs/rpgdm-tables-freq.org'.
|
||||
|
||||
;; [[file:../../../../../Volumes/Personal/dropbox/org/rpg-dm/docs/rpgdm-tables-freq.org::*Parsing a Frequency Table][Parsing a Frequency Table:1]]
|
||||
(defun rpgdm-tables-freq-table? ()
|
||||
"Return non-nil if current buffer contains a frequency table"
|
||||
(goto-char (point-min))
|
||||
(re-search-forward rpgdm-tables--line-parse nil nil)
|
||||
(match-string 2))
|
||||
;; Parsing a Frequency Table:1 ends here
|
||||
|
||||
;; [[file:../../../../../Volumes/Personal/dropbox/org/rpg-dm/docs/rpgdm-tables-freq.org::*Parsing a Frequency Table][Parsing a Frequency Table:2]]
|
||||
(defun rpgdm-tables--parse-as-freq-table ()
|
||||
"Return hashtable of lines matching `rpgdm-tables--line-parse'.
|
||||
The keys in the hashtable are the tags from the file, and the
|
||||
|
|
@ -51,9 +50,7 @@ Would return a hashtable containing:
|
|||
|
||||
;; Combine the sublists of equivalent tags:
|
||||
(rpgdm-tables--merge-frequencies results)))
|
||||
;; Parsing a Frequency Table:2 ends here
|
||||
|
||||
;; [[file:../../../../../Volumes/Personal/dropbox/org/rpg-dm/docs/rpgdm-tables-freq.org::*Parsing a Frequency Table][Parsing a Frequency Table:3]]
|
||||
(defun rpgdm-tables--merge-frequencies (table)
|
||||
"Combine the values of equivalent table-tags in TABLE.
|
||||
A table, read as a hash table, may have similar, but equal tags.
|
||||
|
|
@ -68,9 +65,7 @@ For instance, `veryrare' and `very-rare' are the same."
|
|||
(gethash tag table)) table)
|
||||
(remhash tag table)))))
|
||||
table))
|
||||
;; Parsing a Frequency Table:3 ends here
|
||||
|
||||
;; [[file:../../../../../Volumes/Personal/dropbox/org/rpg-dm/docs/rpgdm-tables-freq.org::*Frequencies as Weights][Frequencies as Weights:3]]
|
||||
(defconst rpgdm-tables-tag-groups
|
||||
'(((12 "common")
|
||||
(7 "uncommon")
|
||||
|
|
@ -82,9 +77,7 @@ For instance, `veryrare' and `very-rare' are the same."
|
|||
(3 "seldom" "sometimes")
|
||||
(2 "scarcely" "scarce" "hardly ever")
|
||||
(1 "rarely"))))
|
||||
;; Frequencies as Weights:3 ends here
|
||||
|
||||
;; [[file:../../../../../Volumes/Personal/dropbox/org/rpg-dm/docs/rpgdm-tables-freq.org::*Choosing an Item][Choosing an Item:1]]
|
||||
(defun rpgdm-tables--choose-freq-table (table)
|
||||
"Select item from a hash TABLE.
|
||||
Note that tables stored in a hash table have weight keys and a list
|
||||
|
|
@ -171,9 +164,7 @@ Uses helper function, `rpgdm-tables--find-tag'."
|
|||
(roll (rpgdm--roll-die upper-limit)))
|
||||
;; (message "Rolled %d on %d" roll upper-limit)
|
||||
(rpgdm-tables--find-tag roll tags)))
|
||||
;; Choosing an Item:1 ends here
|
||||
|
||||
;; [[file:../../../../../Volumes/Personal/dropbox/org/rpg-dm/docs/rpgdm-tables-freq.org::*Match Table with Tag Group][Match Table with Tag Group:1]]
|
||||
(defun rpgdm-tables--which-tag-group (table)
|
||||
"Return the tag table-tags associated with TABLE."
|
||||
(let (results
|
||||
|
|
@ -184,9 +175,7 @@ Uses helper function, `rpgdm-tables--find-tag'."
|
|||
(-flatten))))
|
||||
(when (-contains? tag-list tag)
|
||||
(setq results table-tags))))))
|
||||
;; Match Table with Tag Group:1 ends here
|
||||
|
||||
;; [[file:../../../../../Volumes/Personal/dropbox/org/rpg-dm/docs/rpgdm-tables-freq.org::*Validating my Assumptions][Validating my Assumptions:1]]
|
||||
(defun rpgdm-tables-validate (&optional table-name iterations)
|
||||
"Return results randomly choosing many items from TABLE-NAME.
|
||||
Calls `rpgdm-tables-choose' a number of ITERATIONS (defaults to 500)."
|
||||
|
|
@ -209,9 +198,6 @@ Calls `rpgdm-tables-choose' a number of ITERATIONS (defaults to 500)."
|
|||
(item-name (first (s-split " :: " item))))
|
||||
(incf (gethash item-name accumulator 0))))
|
||||
accumulator))
|
||||
;; Validating my Assumptions:1 ends here
|
||||
|
||||
;; [[file:../../../../../Volumes/Personal/dropbox/org/rpg-dm/docs/rpgdm-tables-freq.org::*Validating my Assumptions][Validating my Assumptions:3]]
|
||||
(provide 'rpgdm-tables-freq)
|
||||
;;; rpgdm-tables-freq.el ends here
|
||||
;; Validating my Assumptions:3 ends here
|
||||
|
|
|
|||
|
|
@ -58,17 +58,34 @@ would be easy (see `rpgdm-tables--choose-list'), or it is either
|
|||
a freq-uency table (see `rpgdm-tables--choose-freq-table') or a
|
||||
dice table (see `rpgdm-tables--choose-dice-table')."
|
||||
(interactive (list (completing-read "Choose from Table: " (hash-table-keys rpgdm-tables))))
|
||||
|
||||
(let* ((table (gethash table-name rpgdm-tables))
|
||||
(response (cond ((dice-table-p table) (rpgdm-tables--choose-dice-table table))
|
||||
(result (cond ((dice-table-p table) (rpgdm-tables--choose-dice-table table))
|
||||
((hash-table-p table) (rpgdm-tables--choose-freq-table table))
|
||||
((listp table) (rpgdm-tables--choose-list table))
|
||||
(t "Error: Could choose anything from %s (internal bug?)" table-name))))
|
||||
(rpgdm-message "%s" response)))
|
||||
(t "Error: Could choose anything from %s (internal bug?)" table-name)))
|
||||
;; Replace any dice expression in the message with an roll:
|
||||
(dice-sum (lambda (dice-exp) (number-to-string (rpgdm-roll-sum dice-exp))))
|
||||
(no-dice-nums (replace-regexp-in-string rpgdm-roll-regexp dice-sum result))
|
||||
(no-alt-words (rpgdm-tables--choose-string-list no-dice-nums)))
|
||||
(rpgdm-message "%s" no-alt-words)))
|
||||
|
||||
(defun rpgdm-tables--choose-list (lst)
|
||||
"Randomly choose (equal chance for any) element in LST."
|
||||
(seq-random-elt lst))
|
||||
|
||||
(defun rpgdm-tables--choose-string-list (str)
|
||||
"Return a substituted item from _string-list_ in STR.
|
||||
For instance, the string: 'You found a [chair/box/statue]'
|
||||
would be converted randomly to something like: 'You found a box.'"
|
||||
(let ((regexp (rx "[" (+? any) "/" (+? any) "]"))
|
||||
(subbed (lambda (str) (--> str
|
||||
(substring it 1 -1)
|
||||
(s-split (rx (*? space) "/" (*? space)) it)
|
||||
(seq-random-elt it)))))
|
||||
(replace-regexp-in-string regexp subbed str)))
|
||||
|
||||
|
||||
;; I originally thought that I could have a single regular expression that
|
||||
;; matched all possible tables, but that is a bit too complicated. The following
|
||||
;; regular expression, however, will parse a list or a frequency table.
|
||||
|
|
|
|||
2
rpgdm.el
2
rpgdm.el
|
|
@ -71,11 +71,13 @@
|
|||
"Replace `messasge' function allowing it to be re-displayed.
|
||||
The FORMAT-STRING is a standard string for the `format' function,
|
||||
and ARGS are substitued values."
|
||||
;; TODO Push this onto a ring instead of reset this string variable:
|
||||
(setq rpgdm-last-results (apply 'format format-string args))
|
||||
(rpgdm-last-results))
|
||||
|
||||
(defun rpgdm-last-results ()
|
||||
"Display results from the last call to a `rpgdm-screen-' function."
|
||||
;; TODO Need to add a prefix and display a numeric version with last as a ring.
|
||||
(interactive)
|
||||
(message rpgdm-last-results))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue