From 8b89bf665bc6caf891525fd9b838ee66c88fca52 Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Fri, 18 Feb 2022 15:05:16 -0800 Subject: [PATCH 1/9] Add support for functions in the tables This is still programmatic, as opposed to dynamically loading source code. --- rpgdm-tables.el | 1 + 1 file changed, 1 insertion(+) diff --git a/rpgdm-tables.el b/rpgdm-tables.el index 94d0fd4..1676a45 100644 --- a/rpgdm-tables.el +++ b/rpgdm-tables.el @@ -104,6 +104,7 @@ dice table (see `rpgdm-tables--choose-dice-table')." (setq table (rpgdm-tables-load-file table table-name))) (let* ((result (cond ((dice-table-p table) (rpgdm-tables--choose-dice-table table)) ((hash-table-p table) (rpgdm-tables--choose-freq-table table)) + ((functionp table) (call-interactively table)) ((listp table) (rpgdm-tables--choose-list table)) (t "Error: Could choose anything from %s (internal bug?)" table-name))) ;; Replace any dice expression in the message with an roll: From 71777f579296214ca831c5dbab81d69d5880186f Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Fri, 18 Feb 2022 15:31:37 -0800 Subject: [PATCH 2/9] Converting from f12 to f6 As the F12 adjusts the volume on a Mac. Sheesh. --- README.org | 2 +- rpgdm.el | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.org b/README.org index 9be0256..b3af89a 100644 --- a/README.org +++ b/README.org @@ -5,7 +5,7 @@ The overlap between Emacs and running a Dungeons & Dragons campaign is... expected? Jotting notes and plans in an org-mode file has been great, but what if, during a game session, my notes became /more interactive/? I started creating some helper functions, which has now become a minor mode I use as a sort of layer on top of Org. Let's blame this insanity on the pandemic, but it has been fun. -The primary interface is =f12= which calls up a /sticky/ Hydra to call my functions, but still allowing full cursor movement (mostly): +The primary interface is =f6= which calls up a /sticky/ Hydra to call my functions, but still allowing full cursor movement (mostly): #+attr_html: :width 800px [[file:images/screenshot-of-hydra.png]] diff --git a/rpgdm.el b/rpgdm.el index dcedb2f..301d4ab 100644 --- a/rpgdm.el +++ b/rpgdm.el @@ -38,7 +38,7 @@ "Minor mode for layering role-playing game master functions over your notes." :lighter " D&D" :keymap (let ((map (make-sparse-keymap))) - (define-key map (kbd "") 'hydra-rpgdm/body) + (define-key map (kbd "") 'hydra-rpgdm/body) map)) (defhydra hydra-rpgdm (:color pink :hint nil) From 769fcc27d4a98f75d8c269e4b8696cfbe1500901 Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Thu, 24 Feb 2022 21:36:16 -0800 Subject: [PATCH 3/9] Move the Ironsworn code to its own repo --- README.org | 2 +- rpgdm-ironsworn.el | 313 --------------------------------------------- 2 files changed, 1 insertion(+), 314 deletions(-) delete mode 100644 rpgdm-ironsworn.el diff --git a/README.org b/README.org index b3af89a..012ba90 100644 --- a/README.org +++ b/README.org @@ -127,4 +127,4 @@ I'm also intrigued with rulesets that are unique, for instance: - [[file:docs/fate-rpg.org][FATE]] :: Easy character creation and a nice bell-curve dice roll, but it requires [[https://fudgerpg.com/products/fudge-dice.html][special Fudge dice]], that are easy enough to recreate in Emacs. See [[https://fate-srd.com/][fate-srd.com]] for details about this game. - [[file:docs/mythic-rpg.org][Mythic RPG]] :: A nice RPG for solo play as it has a GM-less option that I wanted to capture, see [[https://www.wordmillgames.com/mythic-rpg.html][Wordmill Games]] for details. - - [[file:docs/ironsworn-rpg.org][Ironsworn]] :: Another good solo RPG, I wanted to capture its quick check resolution. See [[https://www.ironswornrpg.com/][ironswornrpg.com]] for the free rules. + - [[https://gitlab.com/howardabrams/emacs-ironsworn][Ironsworn]] :: Another good solo RPG, I wanted to capture its quick check resolution. See [[https://www.ironswornrpg.com/][ironswornrpg.com]] for the free rules. diff --git a/rpgdm-ironsworn.el b/rpgdm-ironsworn.el deleted file mode 100644 index 51da487..0000000 --- a/rpgdm-ironsworn.el +++ /dev/null @@ -1,313 +0,0 @@ -(require 'rpgdm) - -(defun rpgdm-ironsworn--results (action modifier one-challenge two-challenge) - (let* ((action-results (+ action modifier)) - (str-results (cond - ((and (> action-results one-challenge) (> action-results two-challenge)) - (propertize "Strong hit" 'face '(:foreground "green"))) - ((or (> action-results one-challenge) (> action-results two-challenge)) - (propertize "Weak hit" 'face '(:foreground "yellow"))) - (t (propertize "Miss" 'face '(:foreground "red"))))) - (matched-msg (if (= one-challenge two-challenge) - (propertize " ← Create a Twist" 'face '(:foreground "orange")) - ""))) - (format "%s %s %d %s %d %s %d %s %d %s" str-results - (propertize "::" 'face '(:foreground "#888")) - action - (propertize "+" 'face '(:foreground "#888")) - modifier - (propertize "→" 'face '(:foreground "#888")) - one-challenge - (propertize "/" 'face '(:foreground "#888")) - two-challenge matched-msg))) - -(defun rpgdm-ironsworn-roll (modifier) - "Display a Hit/Miss message based on comparing a d6 action -roll (added to MODIFIER) vs. two d10 challenge dice." - (interactive "nModifier: ") - (let ((one-challenge (rpgdm--roll-die 10)) - (two-challenge (rpgdm--roll-die 10)) - (action-roll (rpgdm--roll-die 6))) - (rpgdm-message (rpgdm-ironsworn--results action-roll modifier - one-challenge two-challenge)))) - -(defun rpgdm-ironsworn-progress-roll (progress-value) - "Display a Hit/Miss message based on comparing the PROGRESS-VALUE -to rolling two d10 challenge dice." - (interactive "nCurrent Progress Value: ") - (let ((one-challenge (rpgdm--roll-die 10)) - (two-challenge (rpgdm--roll-die 10))) - (rpgdm-message (rpgdm-ironsworn--results progress-value 0 - one-challenge two-challenge)))) - -(define-hash-table-test 'str-or-keys - (lambda (a b) - (string-equal - (downcase - (if (symbolp a) (symbol-name a) a)) - (downcase - (if (symbolp b) (symbol-name b) b)))) - - (lambda (s) (sxhash-equal (downcase - (if (symbolp s) (symbol-name s) s))))) - -(defvar rpgdm-ironsworn-character (make-hash-table :test 'str-or-keys) - "Stats and attributes for the currently loaded character") - -(cl-defun rpgdm-ironsworn-character (&key edge heart iron shadow wits - (health 5) (spirit 5) (supply 5) - (momentum 2)) - "Store the player character's stats, as well as set up the defaults for the others." - (clrhash rpgdm-ironsworn-character) - ;; (setq rpgdm-ironsworn-character (make-hash-table :test 'str-or-keys)) - (puthash 'edge edge rpgdm-ironsworn-character) - (puthash 'heart heart rpgdm-ironsworn-character) - (puthash 'iron iron rpgdm-ironsworn-character) - (puthash 'shadow shadow rpgdm-ironsworn-character) - (puthash 'wits wits rpgdm-ironsworn-character) - - (puthash 'health health rpgdm-ironsworn-character) - (puthash 'spirit spirit rpgdm-ironsworn-character) - (puthash 'supply supply rpgdm-ironsworn-character) - (puthash 'momentum momentum rpgdm-ironsworn-character)) - -(defun rpgdm-ironsworn-adjust-health (health-adj) - "Increase or decrease the current character's health by HEALTH-ADJ." - (interactive "nHealth Adjustment: ") - (puthash 'health - (+ (gethash 'health rpgdm-ironsworn-character 5) health-adj) - rpgdm-ironsworn-character)) - -(defun rpgdm-ironsworn-adjust-spirit (spirit-adj) - "Increase or decrease the current character's spirit by SPIRIT-ADJ." - (interactive "nSpirit Adjustment: ") - (puthash 'spirit - (+ (gethash 'spirit rpgdm-ironsworn-character 5) spirit-adj) - rpgdm-ironsworn-character)) - -(defun rpgdm-ironsworn-adjust-supply (supply-adj) - "Increase or decrease the current character's supply by SUPPLY-ADJ." - (interactive "nSupply Adjustment: ") - (puthash 'supply - (+ (gethash 'supply rpgdm-ironsworn-character 5) supply-adj) - rpgdm-ironsworn-character)) - -(defun rpgdm-ironsworn-adjust-momentum (momentum-adj) - "Increase or decrease the current character's momentum by MOMENTUM-ADJ." - (interactive "nMomentum Adjustment: ") - (puthash 'momentum - (+ (gethash 'momentum rpgdm-ironsworn-character 5) momentum-adj) - rpgdm-ironsworn-character)) - -(defun rpgdm-ironsworn--display-stat (stat) - (let* ((value (gethash stat rpgdm-ironsworn-character 5)) - (s-val (number-to-string value)) - (color (cond - ((< value 1) "red") - ((< value 3) "orange") - ((< value 4) "yellow") - (t "green")))) - (propertize s-val 'face `(:foreground ,color)))) - -(defun rpgdm-ironsworn-character-display () - "Easily display the character's stats and other things." - (interactive) - (rpgdm-message "Edge: %d Heart: %d Iron: %d Shadow: %d Wits: %d -Health: %s Spirit: %s Supply: %s Momentum: %d" - (gethash 'edge rpgdm-ironsworn-character 0) - (gethash 'heart rpgdm-ironsworn-character 0) - (gethash 'iron rpgdm-ironsworn-character 0) - (gethash 'shadow rpgdm-ironsworn-character 0) - (gethash 'wits rpgdm-ironsworn-character 0) - - (rpgdm-ironsworn--display-stat 'health) - (rpgdm-ironsworn--display-stat 'spirit) - (rpgdm-ironsworn--display-stat 'supply) - - (gethash 'momentum rpgdm-ironsworn-character 5))) - -(defun rpgdm-ironsworn-roll-stat (stat modifier) - "Roll an action based on a loaded character's STAT with a MODIFIER." - (interactive (list (completing-read "Stat Modifier: " '(Edge Heart Iron Shadow Wits)) - (read-string "Other Modifier: "))) - (let ((all-mods (+ (gethash stat rpgdm-ironsworn-character) - (string-to-number modifier)))) - (rpgdm-ironsworn-roll all-mods))) - -(defun rpgdm-ironsworn-roll-edge (modifier) - "Roll an action based on a loaded character's Edge stat with a MODIFIER." - (interactive (list (read-string "Edge + Modifier: "))) - (rpgdm-ironsworn-roll-stat 'edge modifier)) - -(defun rpgdm-ironsworn-roll-heart (modifier) - "Roll an action based on a loaded character's Heart stat with a MODIFIER." - (interactive (list (read-string "Heart + Modifier: "))) - (rpgdm-ironsworn-roll-stat 'heart modifier)) - -(defun rpgdm-ironsworn-roll-iron (modifier) - "Roll an action based on a loaded character's Iron stat with a MODIFIER." - (interactive (list (read-string "Iron + Modifier: "))) - (rpgdm-ironsworn-roll-stat 'iron modifier)) - -(defun rpgdm-ironsworn-roll-shadow (modifier) - "Roll an action based on a loaded character's Shadow stat with a MODIFIER." - (interactive (list (read-string "Shadow + Modifier: "))) - (rpgdm-ironsworn-roll-stat 'shadow modifier)) - -(defun rpgdm-ironsworn-roll-wits (modifier) - "Roll an action based on a loaded character's Wits stat with a MODIFIER." - (interactive (list (read-string "Wits + Modifier: "))) - (rpgdm-ironsworn-roll-stat 'wits modifier)) - -(defun rpgdm-ironsworn-oracle-action-theme () - "Rolls on two tables at one time." - (interactive) - (let ((action (rpgdm-tables-choose "actions")) - (theme (rpgdm-tables-choose "themes"))) - (rpgdm-message "%s / %s" action theme))) - -(defun rpgdm-ironsworn-oracle-npc () - (interactive) - (let ((name (rpgdm-tables-choose "names-ironlander")) - (goal (rpgdm-tables-choose "character-goal")) - (role (rpgdm-tables-choose "character-role")) - (activity (rpgdm-tables-choose "character-activity")) - (description (rpgdm-tables-choose "character-descriptor")) - (disposition (rpgdm-tables-choose "character-disposition"))) - (rpgdm-message "%s, %s %s (Activity: %s Disposition: %s Goal: %s)" - name description role activity disposition goal))) - -(defun rpgdm-ironsworn-oracle-combat () - (interactive) - (let ((action (rpgdm-tables-choose "combat-action")) - (method (rpgdm-tables-choose "combat-event-method")) - (target (rpgdm-tables-choose "combat-event-target"))) - (rpgdm-message "%s %s or %s" method target action))) - -(defun rpgdm-ironsworn-oracle-feature () - "Rolls on two tables at one time for a Site's feature." - (interactive) - (let ((aspect (rpgdm-tables-choose "feature-aspect")) - (focus (rpgdm-tables-choose "feature-focus"))) - (rpgdm-message "%s / %s" aspect focus))) - -(defun rpgdm-ironsworn-oracle-site-nature () - "Rolls on two tables at one time for a random Site." - (interactive) - (let* ((theme (rpgdm-tables-choose "site-theme")) - (domain (rpgdm-tables-choose "site-domain")) - (place (downcase domain)) - (name (rpgdm-ironsworn-oracle-site-name place))) - (rpgdm-message "%s %s :: %s" theme domain name))) - -(defun rpgdm-ironsworn-oracle-site-name (&optional place-type) - "Rolling on multiple tables to return a random site name." - (interactive (list (completing-read "Place type: " - '(barrow cavern icereach mine pass ruin - sea-cave shadowfen stronghold - tanglewood underkeep)))) - (unless place-type - (setq place-type "unknown")) - (let ((description (rpgdm-tables-choose "site-name-description")) - (detail (rpgdm-tables-choose "site-name-detail")) - (namesake (rpgdm-tables-choose "site-name-namesake")) - (place (rpgdm-tables-choose (format "site-name-place-%s" place-type))) - (roll (rpgdm--roll-die 100))) - (rpgdm-message - (cond - ((<= roll 25) (format "%s %s" description place)) - ((<= roll 50) (format "%s of %s" place detail)) - ((<= roll 70) (format "%s of %s %s" place description detail)) - ((<= roll 80) (format "%s of %s's %s" place namesake detail)) - ((<= roll 85) (format "%s's %s" namesake place)) - ((<= roll 95) (format "%s %s of %s" description place namesake)) - (t (format "%s of %s" place namesake)))))) - -(defvar rpgdm-ironsworn-oracle-threats '("Burgeoning Conflict" "Ravaging Horde" - "Cursed Site" "Malignant Plague" - "Scheming Leader" "Zealous Cult" - "Environmental Calamity" "Power-Hungry Mystic" - "Rampaging Creature") - "A list of threats that correspond to tables") - -(defun rpgdm-ironsworn-oracle-threat-goal (&optional category) - "Given a CATEGORY, display a threat goal." - (interactive (list (completing-read "Threat: " rpgdm-ironsworn-oracle-threats))) - (unless category - (setq category (seq-random-elt rpgdm-ironsworn-oracle-threats))) - (let ((table-name (format "threat-%s" (downcase (string-replace " " "-" category))))) - (rpgdm-message "%s: %s" category (rpgdm-tables-choose table-name)))) - -(rpgdm-ironsworn-oracle-threat-goal) - -(defun rpgdm-ironsworn-oracle () - "Given a LIKLIHOOD as a single character, return weighted coin flip." - (interactive) - (let* ((prompt "What are the odds? - c) Almost Certain l) Likely h) 50/50 u) Unlikely n) Small Chance ") - (odds (read-char prompt)) - (roll (rpgdm--roll-die 100)) - (yes! (when (or (and (= roll 11) (eq odds ?c)) - (and (= roll 26) (eq odds ?l)) - (and (= roll 51) (eq odds ?h)) - (and (= roll 76) (eq odds ?u)) - (and (= roll 91) (eq odds ?n))) - t)) - (yes (when (or (and (> roll 11) (eq odds ?c)) - (and (> roll 26) (eq odds ?l)) - (and (> roll 51) (eq odds ?h)) - (and (> roll 76) (eq odds ?u)) - (and (> roll 91) (eq odds ?n))) - t))) - (rpgdm-message "%s %s %s" - (if yes! "Extreme" "") - (if yes "Yes" "No") - (if yes! "or a twist." "")))) - -(defhydra hydra-rpgdm (:color blue :hint nil) - " - ^Dice^ ^Adjust^ ^Oracles/Tables^ ^Moving^ ^Messages^ - ---------------------------------------------------------------------------------------------------------------------------------------------------- - _d_: Roll Dice _D_: Progress Dice _H_: Health _z_: Yes/No Oracle _a_: Action/Theme _n_: NPC _o_: Links ⌘-‿: Show Stats - _e_: Roll Edge _s_: Roll Shadow _S_: Spirit _c_: Show Oracle _c_: Combat Action _f_: Feature _J_/_K_: Page up/dn ⌘-l: Last Results - _h_: Roll Heart _w_: Roll Wits _G_: Supply _O_: Other Oracle _p_: Place Name _P_: Place _N_/_W_: Narrow/Widen ⌘-k: ↑ Previous - _i_: Roll Iron _x_: Roll Stat _M_: Momentum _T_: Load Oracles _t_: Threat Goal ⌘-j: ↓ Next " - ("d" rpgdm-ironsworn-roll) ("D" rpgdm-ironsworn-progress-roll) - ("z" rpgdm-ironsworn-oracle) ("O" rpgdm-oracle) - - ("a" rpgdm-ironsworn-oracle-action-theme) - ("n" rpgdm-ironsworn-oracle-npc) - ("c" rpgdm-ironsworn-oracle-combat) - ("f" rpgdm-ironsworn-oracle-feature) - ("P" rpgdm-ironsworn-oracle-site-nature) - ("p" rpgdm-ironsworn-oracle-site-name) - ("t" rpgdm-ironsworn-oracle-threat-goal) - - ("e" rpgdm-ironsworn-roll-edge) - ("h" rpgdm-ironsworn-roll-heart) - ("i" rpgdm-ironsworn-roll-iron) - ("s" rpgdm-ironsworn-roll-shadow) - ("w" rpgdm-ironsworn-roll-wits) - ("x" rpgdm-ironsworn-roll-stat :color pink) - - ("H" rpgdm-ironsworn-adjust-health :color pink) - ("S" rpgdm-ironsworn-adjust-spirit :color pink) - ("G" rpgdm-ironsworn-adjust-supply :color pink) - ("M" rpgdm-ironsworn-adjust-momentum :color pink) - - ("T" rpgdm-tables-load) ("c" rpgdm-tables-choose) ("C" rpgdm-tables-choose :color pink) - ("o" ace-link) ("N" org-narrow-to-subtree) ("W" widen) - ("K" scroll-down :color pink) ("J" scroll-up :color pink) - - ("s-SPC" rpgdm-ironsworn-character-display) - ("C-m" rpgdm-last-results :color pink) - ("C-n" rpgdm-last-results-next :color pink) - ("C-p" rpgdm-last-results-previous :color pink) - ("s-l" rpgdm-last-results :color pink) - ("s-j" rpgdm-last-results-next :color pink) - ("s-k" rpgdm-last-results-previous :color pink) - - ("q" nil "quit") ("" nil)) - -(provide 'rpgdm-ironsworn) -;;; rpgdm-ironsworn.el ends here From b0c3601e4c58add3e3066230fb657d688f3cd5ba Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Fri, 6 May 2022 21:13:41 -0700 Subject: [PATCH 4/9] Tables refer to other tables If an entry in a table references <>, that reference is replaced with a call to that other table. This allows building complex responses. --- rpgdm-tables.el | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/rpgdm-tables.el b/rpgdm-tables.el index 1676a45..e7279b5 100644 --- a/rpgdm-tables.el +++ b/rpgdm-tables.el @@ -106,13 +106,25 @@ dice table (see `rpgdm-tables--choose-dice-table')." ((hash-table-p table) (rpgdm-tables--choose-freq-table table)) ((functionp table) (call-interactively table)) ((listp table) (rpgdm-tables--choose-list table)) - (t "Error: Could choose anything from %s (internal bug?)" table-name))) + (t "Error: Could not 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))) - (kill-new no-alt-words) - (rpgdm-message "%s" no-alt-words)))) + (no-alt-words (rpgdm-tables--choose-string-list no-dice-nums)) + + ;; Can we replace a <> substring with the results from calling this function again? + (final-choice (replace-regexp-in-string + (rx "<<" (group (one-or-more (not ">"))) ">>") + 'rpgdm-tables--choose-replacement no-alt-words))) + (kill-new final-choice) + (rpgdm-message "%s" final-choice)))) + +(defun rpgdm-tables--choose-replacement (str) + "Given STR like, <>, return call to `rpgdm-tables-choose'. +However, the `<<...>>' characters are replaced when calling function." + (if (string-match (rx "<<" (group (one-or-more (not ">"))) ">>") str) + (rpgdm-tables-choose (match-string 1 str)) + str)) (defun rpgdm-tables--choose-list (lst) "Randomly choose (equal chance for any) element in LST." From 1a99ec653953458416df9c7640a111b11f61cbf2 Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Tue, 22 Feb 2022 14:26:32 -0800 Subject: [PATCH 5/9] Better description for a function. --- rpgdm-tables.el | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rpgdm-tables.el b/rpgdm-tables.el index e7279b5..a283aa1 100644 --- a/rpgdm-tables.el +++ b/rpgdm-tables.el @@ -37,7 +37,10 @@ "Directory path containing the tables to load and create functions.") (defvar rpgdm-tables (make-hash-table :test 'equal) - "Collection of tables and lists for the Dungeon Master.") + "Collection of tables and lists for the Dungeon Master. +When a table directory is first loaded, the _values_ are the +filenames. After a call to `rpgdm-tables-choose', the value +is replaced by the data (in the form of a data structure).") (defun rpgdm-tables-clear () "Clear previously loaded tables." From 14f6524eecd0c6ca4031634c2a3d10553911dcaf Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Tue, 19 Sep 2023 20:54:43 -0700 Subject: [PATCH 6/9] Add any message to the standard kill-ring too. You know, the clipboard! --- rpgdm.el | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/rpgdm.el b/rpgdm.el index 301d4ab..9d75c12 100644 --- a/rpgdm.el +++ b/rpgdm.el @@ -87,6 +87,7 @@ The FORMAT-STRING is a standard string for the `format' function, and ARGS are substitued values." (let ((message (apply 'format format-string args))) (ring-insert rpgdm-last-results message) + (kill-new message) (rpgdm-last-results))) (defun rpgdm-last-results () @@ -195,12 +196,12 @@ The formula is based on the NUMBER-OF-DICE. According to the Players Handbook in Dungeons and Dragons, we have this table to determine difficulty skill check levels: - - Very easy 5 - - Easy 10 - - Medium 15 - - Hard 20 - - Very hard 25 - - Nearly impossible 30 + - Very easy 5 + - Easy 10 + - Medium 15 + - Hard 20 + - Very hard 25 + - Nearly impossible 30 But I read somewhere that you could roll some 6 sided die to help add a bit of randomness to the leve setting. Essentially, roll From 5c5f17e7a6cc42af50ef5f003f070b0c89ccecd5 Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Sat, 18 Nov 2023 15:57:04 -0800 Subject: [PATCH 7/9] Reformatting and cleanup --- rpgdm-tables.el | 73 ++++++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 46 deletions(-) diff --git a/rpgdm-tables.el b/rpgdm-tables.el index a283aa1..aba54a7 100644 --- a/rpgdm-tables.el +++ b/rpgdm-tables.el @@ -3,7 +3,7 @@ ;; Copyright (C) 2021 Howard X. Abrams ;; ;; Author: Howard X. Abrams -;; Maintainer: Howard X. Abrams +;; Maintainer: Howard X. Abrams ;; Created: January 8, 2021 ;; ;; This file is not part of GNU Emacs. @@ -25,11 +25,10 @@ ;; ;;; Code: -(require 'ert) - -(require 'rpgdm-dice) -(require 'rpgdm-tables-freq) -(require 'rpgdm-tables-dice) +(defvar rpgdm-base ".") +(require 'rpgdm-dice (expand-file-name "rpgdm-dice.el" rpgdm-base) t) +(require 'rpgdm-tables-freq (expand-file-name "rpgdm-tables-freq.el" rpgdm-base) t) +(require 'rpgdm-tables-dice (expand-file-name "rpgdm-tables-dice.el" rpgdm-base) t) (defvar rpgdm-tables-directory @@ -91,7 +90,6 @@ Store it by NAME in the `rpgdm-tables' hash table." (message "Read: %s" name)) (puthash name contents rpgdm-tables))) - (defun rpgdm-tables-choose (table-name) "Return random item from a table of a given TABLE-NAME string. @@ -105,29 +103,25 @@ dice table (see `rpgdm-tables--choose-dice-table')." (when-let ((table (gethash table-name rpgdm-tables))) (when (stringp table) (setq table (rpgdm-tables-load-file table table-name))) - (let* ((result (cond ((dice-table-p table) (rpgdm-tables--choose-dice-table table)) + (let* ((initial (cond ((dice-table-p table) (rpgdm-tables--choose-dice-table table)) ((hash-table-p table) (rpgdm-tables--choose-freq-table table)) - ((functionp table) (call-interactively table)) + ((functionp table) (call-interactively table)) ((listp table) (rpgdm-tables--choose-list table)) (t "Error: Could not choose anything from %s (internal bug?)" table-name))) - ;; Replace any dice expression in the message with an roll: + + ;; Function to return dice expression as a sum (and string): (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)) - ;; Can we replace a <> substring with the results from calling this function again? - (final-choice (replace-regexp-in-string - (rx "<<" (group (one-or-more (not ">"))) ">>") - 'rpgdm-tables--choose-replacement no-alt-words))) - (kill-new final-choice) - (rpgdm-message "%s" final-choice)))) + (results (thread-last initial + ;; Replace dice expression in the message with an roll: + (replace-regexp-in-string rpgdm-roll-regexp dice-sum ) + ;; Replace [[table-name]] with results from table: + (rpgdm-tables--choose-string-from-table) + ;; Replace [one/two/three] with one of those words: + (rpgdm-tables--choose-string-list)))) -(defun rpgdm-tables--choose-replacement (str) - "Given STR like, <>, return call to `rpgdm-tables-choose'. -However, the `<<...>>' characters are replaced when calling function." - (if (string-match (rx "<<" (group (one-or-more (not ">"))) ">>") str) - (rpgdm-tables-choose (match-string 1 str)) - str)) + (kill-new results) + (rpgdm-message "%s" results)))) (defun rpgdm-tables--choose-list (lst) "Randomly choose (equal chance for any) element in LST." @@ -139,30 +133,17 @@ However, the `<<...>>' characters are replaced when calling function." 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 "/") it) - (seq-random-elt it) - (s-trim it))))) + (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))) -(ert-deftest rpgdm-tables--choose-string-list () - (let ((empty-string "") - (no-op-string "This is just a phrase.") - (two-choices "We can have [this/that]") - (two-choices1 "We can have this") - (two-choices2 "We can have that") - (tri-choices "We can have [this / that / the other].") - (tri-choices1 "We can have this.") - (tri-choices2 "We can have that.") - (tri-choices3 "We can have the other.")) - - (should (equal (rpgdm-tables--choose-string-list empty-string) empty-string)) - (should (equal (rpgdm-tables--choose-string-list no-op-string) no-op-string)) - (let ((chosen (rpgdm-tables--choose-string-list two-choices))) - (should (or (equal chosen two-choices1) (equal chosen two-choices2)))) - (let ((chosen (rpgdm-tables--choose-string-list tri-choices))) - (should (or (equal chosen tri-choices1) (equal chosen tri-choices2) (equal chosen tri-choices3)))))) +(defun rpgdm-tables--choose-string-from-table (str) + "Replace <> sequence in STR with call to `rpgdm-tables-choose'." + (let ((regexp (rx "<<" (+? any) ">>")) + (subbed (lambda (s) (rpgdm-tables-choose (substring s 2 -2))))) + (replace-regexp-in-string regexp subbed str nil nil 0))) ;; 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 From 25c282c09480c5306d511128acfaa0fbc78376cf Mon Sep 17 00:00:00 2001 From: Jeremy Friesen Date: Sun, 10 Dec 2023 09:47:10 -0500 Subject: [PATCH 8/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Favor=20`cl-`=20functi?= =?UTF-8?q?ons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When requiring `rpgdm.el` and it's "child" packages, I encounter the following warnings: > Warning: ‘destructuring-bind’ is an obsolete alias (as of 27.1); use > ‘cl-destructuring-bind’ instead. > > Warning: ‘incf’ is an obsolete alias (as of 27.1); use ‘cl-incf’ > instead. > > Warning: ‘decf’ is an obsolete alias (as of 27.1); use ‘cl-decf’ > instead. > > Warning: ‘defstruct’ is an obsolete alias (as of 27.1); use > ‘cl-defstruct’ instead. Since we've already required the `cl` package, this should be a noop change. --- docs/rpgdm-tables-dice.org | 2 +- docs/rpgdm-tables-freq.org | 2 +- rpgdm-dice.el | 6 +++--- rpgdm-tables-dice.el | 2 +- rpgdm-tables-freq.el | 2 +- rpgdm.el | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/rpgdm-tables-dice.org b/docs/rpgdm-tables-dice.org index 6cd062f..766a12c 100644 --- a/docs/rpgdm-tables-dice.org +++ b/docs/rpgdm-tables-dice.org @@ -46,7 +46,7 @@ For instance, Xanathar's Guide to Everything, a Dungeons and Dragons supplement To represent these types of tables, we create a special type, called a =dice-table=. Where the first "slot" is the dice expression (or the number of sides of a dice to roll), and an associative list of result values and the choice. #+BEGIN_SRC emacs-lisp :results silent -(defstruct dice-table dice rows) +(cl-defstruct dice-table dice rows) #+END_SRC How is this used to render the example table above? diff --git a/docs/rpgdm-tables-freq.org b/docs/rpgdm-tables-freq.org index f511fb2..c09d072 100644 --- a/docs/rpgdm-tables-freq.org +++ b/docs/rpgdm-tables-freq.org @@ -226,7 +226,7 @@ decrement the ROLL value." ;; (message "Comparing %d <= %d for %s" roll num-elems tag) (if (<= roll num-elems) (return tag) - (decf roll num-elems)))) + (cl-decf roll num-elems)))) (ert-deftest rpgdm-tables--find-tag-test () (let ((weighted-tags diff --git a/rpgdm-dice.el b/rpgdm-dice.el index 02e7859..03f2daa 100644 --- a/rpgdm-dice.el +++ b/rpgdm-dice.el @@ -81,7 +81,7 @@ This really tests the `rpgdm--test-rolls' function." (8 1 8) (20 1 20) (100 1 100))) - (destructuring-bind (die lowest highest) test-data + (cl-destructuring-bind (die lowest highest) test-data (rpgdm--test-rolls #'rpgdm--roll-die (list die) lowest highest)))) ;; ---------------------------------------------------------------------- @@ -155,7 +155,7 @@ average value of AVG, if given." ((3 6 4) 7 22) ((4 6 4 "-") 0 20)))) (dolist (test-seq test-data) - (destructuring-bind (dice-args lowest highest) test-seq + (cl-destructuring-bind (dice-args lowest highest) test-seq (rpgdm--test-roll-series 'rpgdm--roll dice-args lowest highest))))) @@ -240,7 +240,7 @@ the following: ("2d12" 2 24) ("3d6+2" 5 20)))) (dolist (test-data test-cases) - (destructuring-bind (dice-expression lowest highest) test-data + (cl-destructuring-bind (dice-expression lowest highest) test-data (rpgdm--test-roll-series 'rpgdm--roll-expression (list dice-expression) lowest highest))))) diff --git a/rpgdm-tables-dice.el b/rpgdm-tables-dice.el index 6cd28a7..32b42f8 100644 --- a/rpgdm-tables-dice.el +++ b/rpgdm-tables-dice.el @@ -17,7 +17,7 @@ ;;; Code: -(defstruct dice-table dice rows) +(cl-defstruct dice-table dice rows) (defun rpgdm-tables--choose-dice-table (table) "Choose a string from a random dice table." diff --git a/rpgdm-tables-freq.el b/rpgdm-tables-freq.el index 8a7f807..b257b5d 100644 --- a/rpgdm-tables-freq.el +++ b/rpgdm-tables-freq.el @@ -142,7 +142,7 @@ decrement the ROLL value." ;; (message "Comparing %d <= %d for %s" roll num-elems tag) (if (<= roll num-elems) (return tag) - (decf roll num-elems)))) + (cl-decf roll num-elems)))) (ert-deftest rpgdm-tables--find-tag-test () (let ((weighted-tags diff --git a/rpgdm.el b/rpgdm.el index 9d75c12..68044e3 100644 --- a/rpgdm.el +++ b/rpgdm.el @@ -109,7 +109,7 @@ and ARGS are substitued values." Meant to be used with `rpgdm-last-results-previous'." (interactive) (when (> rpgdm-last-results-ptr 0) - (decf rpgdm-last-results-ptr)) + (cl-decf rpgdm-last-results-ptr)) (message "%d> %s" rpgdm-last-results-ptr (ring-ref rpgdm-last-results rpgdm-last-results-ptr))) (defun rpgdm-paste-last-message () From cf24af20b6ecba852ce3b58439cd5e5262c49715 Mon Sep 17 00:00:00 2001 From: Jeremy Friesen Date: Sun, 10 Dec 2023 10:50:14 -0500 Subject: [PATCH 9/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20`rpgdm-core.?= =?UTF-8?q?el'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior to this commit, I experienced the following use case: Given I have the following code: ```emacs-lisp (require 'rpgdm-dice "~/git/emacs-rpgdm/rpgdm-dice.el") (require 'rpgdm-tables "~/git/emacs-rpgdm/rpgdm-tables.el") (require 'rpgdm-tables-dice "~/git/emacs-rpgdm/rpgdm-tables-dice.el") (require 'rpgdm-tables-freq "~/git/emacs-rpgdm/rpgdm-tables-freq.el") (setq rpgdm-base "~/git/emacs-rpgdm/") ``` And I call `rpgdm-tables-load` And load all the tables. When I then call `rpgdm-tables-choose` Then I get the following error: ``` let*: Symbol’s function definition is void: rpgdm-message ``` With this commit I'm able to skip requiring `rpgdm` and thus only use a segment of the `rpgdm` package ecosystem. --- README.org | 1 + rpgdm-core.el | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ rpgdm-npc.el | 4 +-- rpgdm-screen.el | 3 +- rpgdm-tables.el | 2 +- rpgdm.el | 65 +--------------------------------------- 6 files changed, 86 insertions(+), 69 deletions(-) create mode 100644 rpgdm-core.el diff --git a/README.org b/README.org index 012ba90..cd09a86 100644 --- a/README.org +++ b/README.org @@ -99,6 +99,7 @@ The second thing I realized is that Org's links can call Emacs functions. This a My initial ideas for listing a bunch of random NPC names and having a link that displayed one of them, got supplanted for the ideas I described above. * Code What do I have here: + - [[file:rpgdm-core.el][rpgdm-core]] :: Package that provides common functionality; namely in messaging and the =rpgdm-last-results= ring. - [[file:rpgdm.el][rpgdm]] :: Primary interface offering: - =rpgdm-mode=, plus a Hydra interface for easily calling the rest of these functions. - =rpgdm-yes-and-50/50=, flip a coin and make a give a result with or without complications or bonuses. diff --git a/rpgdm-core.el b/rpgdm-core.el new file mode 100644 index 0000000..531b16d --- /dev/null +++ b/rpgdm-core.el @@ -0,0 +1,80 @@ +;;; rpgdm-core --- Core functionality for rpgdm -*- lexical-binding: t -*- +;; +;; Copyright (C) 2021 Howard X. Abrams +;; +;; Author: Howard X. Abrams +;; Jeremy Friesen +;; Maintainer: Howard X. Abrams +;; Created: December 10, 2023 +;; +;; This file is NOT part of GNU Emacs. +;;; Commentary: +;; +;; There are functions shared across different `rpgdm' packages. These are +;; considered "core" functionality. +;; +;;; Code: +(defvar rpgdm-base + (seq-find (lambda (elt) (string-match "rpgdm" elt)) load-path (getenv "HOME")) + "Default directory to look for supporting data, like tables and charts.") + +(defvar rpgdm-last-results (make-ring 10) + "The results from calls to `rpgdm-screen-' functions are stored here.") + +(defvar rpgdm-last-results-ptr 0 + "Keeps track of where we are in the message display ring. +Each call to `rpgdm-last-results' resets this to 0.") + +(defun rpgdm-message (format-string &rest args) + "Replace `message' function allowing it to be re-displayed. +The FORMAT-STRING is a standard string for the `format' function, +and ARGS are substitued values." + (let ((message (apply 'format format-string args))) + (ring-insert rpgdm-last-results message) + (kill-new message) + (rpgdm-last-results))) + +(defun rpgdm-last-results () + "Display results from the last call to a `rpgdm-message' function." + (interactive) + (setq rpgdm-last-results-ptr 0) + (message (ring-ref rpgdm-last-results rpgdm-last-results-ptr))) + +(defun rpgdm-last-results-previous () + "Display results from an earlier call to `rpgdm-message'." + (interactive) + (cl-incf rpgdm-last-results-ptr) + (when (>= rpgdm-last-results-ptr (ring-length rpgdm-last-results)) + (setq rpgdm-last-results-ptr 0)) + (message "%d> %s" rpgdm-last-results-ptr (ring-ref rpgdm-last-results rpgdm-last-results-ptr))) + +(defun rpgdm-last-results-next () + "Display results from an later call to `rpgdm-message'. +Meant to be used with `rpgdm-last-results-previous'." + (interactive) + (when (> rpgdm-last-results-ptr 0) + (cl-decf rpgdm-last-results-ptr)) + (message "%d> %s" rpgdm-last-results-ptr (ring-ref rpgdm-last-results rpgdm-last-results-ptr))) + +(defun rpgdm-paste-last-message () + "Yank, e.g. paste, the last displayed message." + (interactive) + (insert (rpgdm-last-results))) + +(ert-deftest rpgdm-last-results-test () + (progn + (setq rpgdm-last-results (make-ring 10)) + (rpgdm-message "First in, so this is the oldest") + (rpgdm-message "Something or other") + (rpgdm-message "Almost the newest") + (rpgdm-message "Newest")) + + (should (equal "Newest" (rpgdm-last-results))) + (should (equal "1> Almost the newest" (rpgdm-last-results-previous))) + (should (equal "2> Something other" (rpgdm-last-results-previous))) + (should (equal "1> Almost the newest" (rpgdm-last-results-next))) + (should (equal "0> Almost the newest" (rpgdm-last-results-next))) + (should (equal "0> Almost the newest" (rpgdm-last-results-next)))) + +(provide 'rpgdm-core) +;;; rpgdm-core.el ends here diff --git a/rpgdm-npc.el b/rpgdm-npc.el index 680734b..5c424eb 100644 --- a/rpgdm-npc.el +++ b/rpgdm-npc.el @@ -28,9 +28,9 @@ ;;; ;;; CCode: -(defvar rpgdm-base ".") +(require 'rpgdm-core (expand-file-name "rpgdm-core.el" rpgdm-base) t) (require 'rpgdm-dice (expand-file-name "rpgdm-dice.el" rpgdm-base) t) -(require 'rpgdm-dice (expand-file-name "rpgdm-tables.el" rpgdm-base) t) +(require 'rpgdm-tables (expand-file-name "rpgdm-tables.el" rpgdm-base) t) (defun rpgdm-npc-gender-name () "Return nil or non-nil for male or female names." diff --git a/rpgdm-screen.el b/rpgdm-screen.el index 24f8571..12e9ea5 100644 --- a/rpgdm-screen.el +++ b/rpgdm-screen.el @@ -21,8 +21,7 @@ (require 'org) (require 'org-element) (require 's) - -(defvar rpgdm-base ".") +(require 'rpgdm-core (expand-file-name "rpgdm-core.el" rpgdm-base) t) (defvar rpgdm-screen-directory (expand-file-name "dnd-5e" rpgdm-base) diff --git a/rpgdm-tables.el b/rpgdm-tables.el index aba54a7..0db6942 100644 --- a/rpgdm-tables.el +++ b/rpgdm-tables.el @@ -25,7 +25,7 @@ ;; ;;; Code: -(defvar rpgdm-base ".") +(require 'rpgdm-core (expand-file-name "rpgdm-core.el" rpgdm-base) t) (require 'rpgdm-dice (expand-file-name "rpgdm-dice.el" rpgdm-base) t) (require 'rpgdm-tables-freq (expand-file-name "rpgdm-tables-freq.el" rpgdm-base) t) (require 'rpgdm-tables-dice (expand-file-name "rpgdm-tables-dice.el" rpgdm-base) t) diff --git a/rpgdm.el b/rpgdm.el index 68044e3..3d1ba66 100644 --- a/rpgdm.el +++ b/rpgdm.el @@ -25,15 +25,11 @@ (require 'ert) +(require 'rpgdm-core) (require 'rpgdm-dice) (require 'rpgdm-screen) (require 'rpgdm-tables) - -(defvar rpgdm-base - (seq-find (lambda (elt) (string-match "rpgdm" elt)) load-path (getenv "HOME")) - "Default directory to look for supporting data, like tables and charts.") - (define-minor-mode rpgdm-mode "Minor mode for layering role-playing game master functions over your notes." :lighter " D&D" @@ -73,65 +69,6 @@ ("q" nil "quit") ("" nil)) - -(defvar rpgdm-last-results (make-ring 10) - "The results from calls to `rpgdm-screen-' functions are stored here.") - -(defvar rpgdm-last-results-ptr 0 - "Keeps track of where we are in the message display ring. -Each call to `rpgdm-last-results' resets this to 0.") - -(defun rpgdm-message (format-string &rest args) - "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." - (let ((message (apply 'format format-string args))) - (ring-insert rpgdm-last-results message) - (kill-new message) - (rpgdm-last-results))) - -(defun rpgdm-last-results () - "Display results from the last call to a `rpgdm-message' function." - (interactive) - (setq rpgdm-last-results-ptr 0) - (message (ring-ref rpgdm-last-results rpgdm-last-results-ptr))) - -(defun rpgdm-last-results-previous () - "Display results from an earlier call to `rpgdm-message'." - (interactive) - (incf rpgdm-last-results-ptr) - (when (>= rpgdm-last-results-ptr (ring-length rpgdm-last-results)) - (setq rpgdm-last-results-ptr 0)) - (message "%d> %s" rpgdm-last-results-ptr (ring-ref rpgdm-last-results rpgdm-last-results-ptr))) - -(defun rpgdm-last-results-next () - "Display results from an later call to `rpgdm-message'. -Meant to be used with `rpgdm-last-results-previous'." - (interactive) - (when (> rpgdm-last-results-ptr 0) - (cl-decf rpgdm-last-results-ptr)) - (message "%d> %s" rpgdm-last-results-ptr (ring-ref rpgdm-last-results rpgdm-last-results-ptr))) - -(defun rpgdm-paste-last-message () - "Yank, e.g. paste, the last displayed message." - (interactive) - (insert (rpgdm-last-results))) - -(ert-deftest rpgdm-last-results-test () - (progn - (setq rpgdm-last-results (make-ring 10)) - (rpgdm-message "First in, so this is the oldest") - (rpgdm-message "Something or other") - (rpgdm-message "Almost the newest") - (rpgdm-message "Newest")) - - (should (equal "Newest" (rpgdm-last-results))) - (should (equal "1> Almost the newest" (rpgdm-last-results-previous))) - (should (equal "2> Something other" (rpgdm-last-results-previous))) - (should (equal "1> Almost the newest" (rpgdm-last-results-next))) - (should (equal "0> Almost the newest" (rpgdm-last-results-next))) - (should (equal "0> Almost the newest" (rpgdm-last-results-next)))) - (defvar rpgdm-oracle-mod 0 "Cummulative skew to create more tension.") (defun rpgdm-oracle ()