Fixed a number of bugs

This also tangles out the unit tests.
This commit is contained in:
Howard Abrams 2022-02-26 10:28:42 -08:00
parent 0f03c237d2
commit 3c43efdbba
4 changed files with 326 additions and 95 deletions

View file

@ -169,7 +169,12 @@ We also need the name of the directory for this project, so that we can load tab
"The root directory to the rpgdm-ironsworn project.") "The root directory to the rpgdm-ironsworn project.")
#+END_SRC #+END_SRC
** Dice Roller ** Dice Roller
In *Ironsworn*, all dice rolls follow a pattern where you set the challenge level for a check by rolling /challenge dice/ (two d10s) and compare that against rolling an /action die/ (a single d6 ... adding all modifiers to that six-sided die). You always three possible values: In *Ironsworn*, all dice rolls follow a pattern where you set the challenge level for a check by rolling /challenge dice/ (two d10s) and compare that against rolling an /action die/ (a single d6 ... adding all modifiers to that six-sided die).
#+ATTR_HTML: :width 800px
[[file:images/dice-roll-all.png]]
You always three possible values:
- A *strong hit* where your action die + modifiers is larger than both d10 challenge dice. - A *strong hit* where your action die + modifiers is larger than both d10 challenge dice.
- A *weak hit* is where your action die + modifiers is larger than /only one/ of the d10 challenge dice. - A *weak hit* is where your action die + modifiers is larger than /only one/ of the d10 challenge dice.
@ -236,7 +241,7 @@ When we roll, I want one of those three results printed, but in different colors
So the following messages, given various /rolls/ should cover those possibilities with text properties: So the following messages, given various /rolls/ should cover those possibilities with text properties:
#+BEGIN_SRC emacs-lisp :tangle no #+BEGIN_SRC emacs-lisp :tangle rpgdm-ironsworn-tests.el
(ert-deftest rpgdm-ironsworn--results-test () (ert-deftest rpgdm-ironsworn--results-test ()
(should (equal (rpgdm-ironsworn--results 3 2 4 1) (should (equal (rpgdm-ironsworn--results 3 2 4 1)
"Strong hit :: 5 (3 + 2) → 4 / 1")) "Strong hit :: 5 (3 + 2) → 4 / 1"))
@ -250,7 +255,7 @@ So the following messages, given various /rolls/ should cover those possibilitie
(should (equal (rpgdm-ironsworn--results 3 2 8 6 7) (should (equal (rpgdm-ironsworn--results 3 2 8 6 7)
"Miss :: 5 (3 + 2) → 8 / 6 -- Burn momentum for a Weak hit")) "Miss :: 5 (3 + 2) → 8 / 6 -- Burn momentum for a Weak hit"))
(should (equal (rpgdm-ironsworn--results 3 2 8 6 9) (should (equal (rpgdm-ironsworn--results 3 2 8 6 9)
"Miss :: 5 (3 + 2) → 8 / 6 -- Burn momentum for a Strong hit"))))) "Miss :: 5 (3 + 2) → 8 / 6 -- Burn momentum for a Strong hit")))
#+END_SRC #+END_SRC
The basic interface will query for a modifer, roll all three dice, and then display the results using the [[https://gitlab.com/howardabrams/emacs-rpgdm/-/blob/main/rpgdm.el#L76][rpgdm-message]] function: The basic interface will query for a modifer, roll all three dice, and then display the results using the [[https://gitlab.com/howardabrams/emacs-rpgdm/-/blob/main/rpgdm.el#L76][rpgdm-message]] function:
@ -279,12 +284,12 @@ Assuming we have a new character, let's query the user for all of these stats, a
We assume you have created an org-file, and the /template/ will just append some basic text at the end of the buffer. Note that if you don't give this function a name, it will randomly choose one. We assume you have created an org-file, and the /template/ will just append some basic text at the end of the buffer. Note that if you don't give this function a name, it will randomly choose one.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn--new-character-template (name) (defun rpgdm-ironsworn--new-character-template (&optional name)
"Insert basic Ironsworn template at the end of the current buffer. "Insert basic Ironsworn template at the end of the current buffer.
A header is created with NAME, but if this is an empty string, A header is created with NAME, but if this is an empty string,
a random name is generated for the purposes of the template." a random name is generated for the purposes of the template."
(when (s-blank? name) (when (or (null name) (s-blank? name))
(setq name (rpgdm-tables-choose "names-ironlander"))) (setq name (rpgdm-tables-choose "name/ironlander")))
(let ((frmt (seq-random-elt '("* The Adventures of %s" (let ((frmt (seq-random-elt '("* The Adventures of %s"
"* The Journeys of %s" "* The Journeys of %s"
@ -390,7 +395,7 @@ When you start a character, you choose three assets, but what if we choose them
And I can write a little unit test to verify my test cases: And I can write a little unit test to verify my test cases:
#+BEGIN_SRC emacs-lisp :tangle no #+BEGIN_SRC emacs-lisp :tangle rpgdm-ironsworn-tests.el
(ert-deftest rpgdm-ironsworn--good-character-assets-test () (ert-deftest rpgdm-ironsworn--good-character-assets-test ()
(should (rpgdm-ironsworn--good-character-assets '("foo" "bar" "baz"))) (should (rpgdm-ironsworn--good-character-assets '("foo" "bar" "baz")))
(should-not (rpgdm-ironsworn--good-character-assets '("foo" "bar" "foo"))) (should-not (rpgdm-ironsworn--good-character-assets '("foo" "bar" "foo")))
@ -399,7 +404,7 @@ And I can write a little unit test to verify my test cases:
"assets/companions/monkey.org"))) "assets/companions/monkey.org")))
(should-not (rpgdm-ironsworn--good-character-assets '(("Companions :: Dog" . "assets/companions/dog.org") (should-not (rpgdm-ironsworn--good-character-assets '(("Companions :: Dog" . "assets/companions/dog.org")
("Paths :: Good Guy" . "assets/paths/good-guy.org") ("Paths :: Good Guy" . "assets/paths/good-guy.org")
("Companions :: Monkey" . "assets/companions/monkey.org")))))) ("Companions :: Monkey" . "assets/companions/monkey.org")))))
#+END_SRC #+END_SRC
The rules say to start off with three assets, so let's have a function that can give us three random assets: The rules say to start off with three assets, so let's have a function that can give us three random assets:
@ -416,7 +421,7 @@ The rules say to start off with three assets, so let's have a function that can
And again, a little unit test would be nice: And again, a little unit test would be nice:
#+BEGIN_SRC emacs-lisp :tangle no #+BEGIN_SRC emacs-lisp :tangle rpgdm-ironsworn-tests.el
(ert-deftest rpgdm-ironsworn--some-character-assets-test () (ert-deftest rpgdm-ironsworn--some-character-assets-test ()
(should (= 4 (seq-length (rpgdm-ironsworn--some-character-assets '(1 2 3 4 5 6) 4)))) (should (= 4 (seq-length (rpgdm-ironsworn--some-character-assets '(1 2 3 4 5 6) 4))))
(should (= 3 (seq-length (rpgdm-ironsworn--some-character-assets '(1 2 3 4 5 6)))))) (should (= 3 (seq-length (rpgdm-ironsworn--some-character-assets '(1 2 3 4 5 6))))))
@ -459,7 +464,7 @@ Whew. We finally can have a function that queries the user about a new character
(goto-char (point-max)) (goto-char (point-max))
(insert "\n** Assets\n") (insert "\n** Assets\n")
(if (y-or-n-p "Would you like three random assets? ") (if (y-or-n-p "Would you like three random assets? ")
(rpgdm-ironsworn-random-character-assets asset-files) (rpgdm-ironsworn-random-character-assets 3)
(if (y-or-n-p "Would you like to choose your assets? ") (if (y-or-n-p "Would you like to choose your assets? ")
(call-interactively 'rpgdm-ironsworn-insert-character-asset)))) (call-interactively 'rpgdm-ironsworn-insert-character-asset))))
#+END_SRC #+END_SRC
@ -482,10 +487,11 @@ This function will query the user for all of the stats and other properties that
(rpgdm-ironsworn-progress-create (read-string "What title should we give this new character's Epic vow? ") 1) (rpgdm-ironsworn-progress-create (read-string "What title should we give this new character's Epic vow? ") 1)
(rpgdm-ironsworn-progress-create "Bonds" 1) (rpgdm-ironsworn-progress-create "Bonds" 1)
(rpgdm-ironsworn-progress-mark "Bonds")
(search-forward ":END:") (search-forward ":END:")
(end-of-line) (end-of-line)
(insert "\n** Bonds\n") (insert "\n** Bonds\n")
(insert (format " - Your home settlement of %s\n" (rpgdm-tables-choose "settlement-names")))) (insert (format " - Your home settlement of %s\n" (rpgdm-tables-choose "settlement/name"))))
#+END_SRC #+END_SRC
**** New Character Interface **** New Character Interface
Do we choose the stats first, or the assets? Do we ask, or have two separate functions? Do we choose the stats first, or the assets? Do we ask, or have two separate functions?
@ -495,14 +501,12 @@ Perhaps the clearest approach is to do both, create two process functions, and t
(defun rpgdm-ironsworn--new-character-stats-first (&optional name) (defun rpgdm-ironsworn--new-character-stats-first (&optional name)
"Insert a new character template for character, NAME. "Insert a new character template for character, NAME.
The character stats are first queried, and then assets inserted." The character stats are first queried, and then assets inserted."
(rpgdm-ironsworn--new-character-template name)
(rpgdm-ironsworn--new-character-stats) (rpgdm-ironsworn--new-character-stats)
(rpgdm-ironsworn--new-character-assets)) (rpgdm-ironsworn--new-character-assets))
(defun rpgdm-ironsworn--new-character-assets-first (&optional name) (defun rpgdm-ironsworn--new-character-assets-first (&optional name)
"Insert a new character template for character, NAME. "Insert a new character template for character, NAME.
The assets are inserted first, and then character stats are queried." The assets are inserted first, and then character stats are queried."
(rpgdm-ironsworn--new-character-template name)
;; Saving and restoring point, means the properties should be in the ;; Saving and restoring point, means the properties should be in the
;; correct, top-level position. ;; correct, top-level position.
(save-excursion (save-excursion
@ -518,6 +522,8 @@ Perhaps the clearest approach is to do both, create two process functions, and t
(interactive (list (interactive (list
(read-string "What is the new character's name? ") (read-string "What is the new character's name? ")
(completing-read "What order should we build this? " '("Statistics first" "Assets first")))) (completing-read "What order should we build this? " '("Statistics first" "Assets first"))))
(rpgdm-ironsworn--new-character-template name)
(if (equal order "Assets first") (if (equal order "Assets first")
(rpgdm-ironsworn--new-character-assets-first) (rpgdm-ironsworn--new-character-assets-first)
(rpgdm-ironsworn--new-character-stats-first)) (rpgdm-ironsworn--new-character-stats-first))
@ -525,7 +531,7 @@ Perhaps the clearest approach is to do both, create two process functions, and t
#+END_SRC #+END_SRC
*** Showing a Character Stats *** Showing a Character Stats
Sure, you could open up the appropriate drawer to see a character's stats, but we could do better? An updated org table? A separate buffer? For the moment, I am just going to display it in the mini-buffer whenever I want to see it: Sure, you could open up the appropriate drawer to see a character's stats, but we could do better? An updated org table? A separate buffer? For the moment, I am just going to display it in the mini-buffer whenever I want to see it.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn--display-stat (stat character) (defun rpgdm-ironsworn--display-stat (stat character)
@ -719,13 +725,13 @@ The [[file:moves][moves]] directory contains one org file for each move. These f
And let's verify the format: And let's verify the format:
#+BEGIN_SRC emacs-lisp :tangle no #+BEGIN_SRC emacs-lisp :tangle rpgdm-ironsworn-tests.el
(ert-deftest rpgdm-ironsworn--move-tuple-test () (ert-deftest rpgdm-ironsworn--move-tuple-test ()
(let ((file "moves/fate/ask-the-oracle.org") (let ((file "moves/fate/ask-the-oracle.org")
(full "~/other/over/here/moves/fate/ask-the-oracle.org")) (full "~/other/over/here/moves/fate/ask-the-oracle.org"))
(should (equal (list "Fate :: Ask The Oracle" file) (should (equal (list "Fate :: ask the oracle" file)
(rpgdm-ironsworn--move-tuple file))) (rpgdm-ironsworn--move-tuple file)))
(should (equal (list "Fate :: Ask The Oracle" full) (should (equal (list "Fate :: ask the oracle" full)
(rpgdm-ironsworn--move-tuple full))))) (rpgdm-ironsworn--move-tuple full)))))
#+END_SRC #+END_SRC
@ -876,12 +882,26 @@ Given a label, can we get the level as a value:
(alist-get label rpgdm-ironsworn-progress-levels 8 nil 'equal)) (alist-get label rpgdm-ironsworn-progress-levels 8 nil 'equal))
#+END_SRC #+END_SRC
Since we'll store only the numbers, we might want to get back the label: Since we'll store only the numbers, we might want to get back the label. Originally, I used an =rassoc=, but since I =propertized= the labels, my new function is a bit more complicated:
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn-progress-level-label (level) (defun rpgdm-ironsworn-progress-level-label (level)
"Return the label associated with the progress LEVEL, e.g. dangerous." "Return the label associated with the progress LEVEL, e.g. dangerous."
(car (rassoc level rpgdm-ironsworn-progress-levels))) (thread-last rpgdm-ironsworn-progress-levels
(--filter (= (second it) level))
(first) ; Assume we only get one match
(first) ; Get the textual label
(s-split " ")
(first))) ; Word before the space
#+END_SRC
Basic tests for sanity:
#+BEGIN_SRC emacs-lisp :tangle rpgdm-ironsworn-tests.el
(ert-deftest rpgdm-ironsworn-progress-level-label-test ()
(should (equal (rpgdm-ironsworn-progress-level-label 1) "epic"))
(should (equal (rpgdm-ironsworn-progress-level-label 12) "troublesome"))
(should (equal (rpgdm-ironsworn-progress-level-label 4) "formidable")))
#+END_SRC #+END_SRC
A helper function for allowing the user to choose which track to mark progress against: A helper function for allowing the user to choose which track to mark progress against:
@ -923,6 +943,7 @@ Adding a progress to a character amounts to an arbitrary name, and the number of
("As a subheading to this?" subheading) ("As a subheading to this?" subheading)
("No new heading. Re-use this." no)))) ("No new heading. Re-use this." no))))
(when (called-interactively-p)
(cl-case (completing-read-value "Create a new heading? " option) (cl-case (completing-read-value "Create a new heading? " option)
('same-level (progn ('same-level (progn
(org-insert-heading-respect-content) (org-insert-heading-respect-content)
@ -930,7 +951,7 @@ Adding a progress to a character amounts to an arbitrary name, and the number of
('subheading (progn ('subheading (progn
(org-insert-heading-respect-content) (org-insert-heading-respect-content)
(org-shiftmetaright) (org-shiftmetaright)
(insert name)))) (insert name)))))
(org-set-property track-prop track-val))) (org-set-property track-prop track-val)))
#+END_SRC #+END_SRC
@ -948,22 +969,97 @@ Interactively, we can call the =-mark= function multiple times, but we might wan
(rpgdm-ironsworn-mark-progress-track name))) (rpgdm-ironsworn-mark-progress-track name)))
#+END_SRC #+END_SRC
The =rpgdm-ironsworn--progress-amount= function returns the information about a progress, by name. It returns a list of all aspects, allowing us to decide how to display it:
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn--progress-amount (name)
"Return list of aspects of progress by NAME.
Including:
- track :: The name of the track (same as `name')
- value :: The number complexity level, e.g. how many ticks per marked progress
- level :: The string label of a progress, e.g. Epic or Troublesome
- ticks :: The amount of ticks marked, shnould be <= 40
- boxes :: The number of completed boxes (ticks / 4, rounded down)
- remain :: The number of remaining ticks marked towards the next box."
(let* ((tracks (rpgdm-ironsworn-character-progresses))
(matched (assoc name tracks))
(track (first matched))
(value (second matched))
(level (rpgdm-ironsworn-progress-level-label value))
(ticks (third matched))
(boxes (/ ticks 4))
(remain (% ticks 4)))
(list track value level ticks boxes remain)))
#+END_SRC
Make a progress to visual table of boxes. Not sure if this is very helpful or not, but it sure will look cool:
#+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn--progress-box (boxes leftover)
"Return a org-like table of current completion of an Ironsworn progress.
For instance, with 4 boxes and 2 leftover tick marks, this will return:
| ■ | ■ | ■ | ■ | x | | | | | | "
(defun make-box (boxes leftover-ticks blanks)
(cond
((> boxes 0) (concat " ■ |" (make-box (1- boxes) leftover-ticks blanks)))
((> leftover-ticks 2) (concat " * |" (make-box 0 0 blanks)))
((> leftover-ticks 1) (concat " x |" (make-box 0 0 blanks)))
((> leftover-ticks 0) (concat " - |" (make-box 0 0 blanks)))
((> blanks 0) (concat " |" (make-box 0 0 (1- blanks))))))
(when (> leftover 3)
(setq boxes (+ boxes (/ leftover 4)))
(setq leftover (% leftover 4)))
(when (< boxes 0)
(setq boxes 0))
(when (> boxes 10)
(setq boxes 10))
(setq blanks (if (> leftover 0)
(- 9 boxes)
(- 10 boxes)))
(concat "|" (make-box boxes leftover blanks)))
#+END_SRC
Some tests may make it clear how that function will look:
#+BEGIN_SRC emacs-lisp
(ert-deftest rpgdm-ironsworn--progress-box-test ()
(should (equal (rpgdm-ironsworn--progress-box 0 0) "| | | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 0 1) "| - | | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 0 2) "| x | | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 0 3) "| * | | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 1 0) "| ■ | | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 2 0) "| ■ | ■ | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 3 0) "| ■ | ■ | ■ | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 4 0) "| ■ | ■ | ■ | ■ | | | | | | |"
(should (equal (rpgdm-ironsworn--progress-box 4 1) "| ■ | ■ | ■ | ■ | - | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 4 2) "| ■ | ■ | ■ | ■ | x | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 4 3) "| ■ | ■ | ■ | ■ | * | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 5 0) "| ■ | ■ | ■ | ■ | ■ | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 10 0) "| ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ |"))
;; Negative test cases
(should (equal (rpgdm-ironsworn--progress-box 11 0) "| ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ |"))
(should (equal (rpgdm-ironsworn--progress-box -1 0) "| | | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 4 8) "| ■ | ■ | ■ | ■ | ■ | ■ | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 4 6) "| ■ | ■ | ■ | ■ | ■ | x | | | | |")))
#+END_SRC
If we call the =-amount= function /interactively/, we should get the current results displayed, otherwise, we really just want the number of full boxes marked: If we call the =-amount= function /interactively/, we should get the current results displayed, otherwise, we really just want the number of full boxes marked:
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(defun rpgdm-ironsworn-progress-amount (name) (defun rpgdm-ironsworn-progress-amount (name)
"Display the progress made against a track, NAME." "Display the progress made against a track, NAME."
(interactive (list (rpgdm-ironsworn-progress-track-choose))) (interactive (list (rpgdm-ironsworn-progress-track-choose)))
(let* ((tracks (rpgdm-ironsworn-character-progresses)) (cl-destructuring-bind
(matched (--filter (equal name (first it)) tracks)) (track value level ticks boxes leftover)
(track (first matched)) (rpgdm-ironsworn--progress-amount name)
(value (second track)) (if (not (called-interactively-p 'any))
(level (rpgdm-ironsworn-progress-level-label value)) boxes
(ticks (third track)) (rpgdm-message "[%s] Progress on '%s': %d %s" level name boxes
(boxes (/ ticks 4))) (rpgdm-ironsworn--progress-box boxes leftover)))))
(if (called-interactively-p 'any)
(rpgdm-message "[%d] Progress on %s: %d (Ticks: %d)" level name boxes ticks)
boxes)))
#+END_SRC #+END_SRC
Rolling against the progress just means we need to request that value instead of rolling the d6, but in this case, we should either take a currently registered progress track, or simply specify a completed amount: Rolling against the progress just means we need to request that value instead of rolling the d6, but in this case, we should either take a currently registered progress track, or simply specify a completed amount:
@ -993,9 +1089,9 @@ When we've finished a track, we can remove it from the hash to not clutter the i
(remhash name tracks)))) (remhash name tracks))))
#+END_SRC #+END_SRC
Let's make sure these function work as we expect: Let's make sure these function work as we expect (keep in mind, these can only be run in an Org file):
#+BEGIN_SRC emacs-lisp :tangle no #+BEGIN_SRC emacs-lisp
(ert-deftest rpgdm-ironsworn-progress-test () (ert-deftest rpgdm-ironsworn-progress-test ()
(let ((track "Battling a Grue")) (let ((track "Battling a Grue"))
(rpgdm-ironsworn-progress-delete track) (rpgdm-ironsworn-progress-delete track)
@ -1523,7 +1619,7 @@ We also need a property-symbol to string conversion. I don't know if the string
A test is always explanatory for how this function should behave: A test is always explanatory for how this function should behave:
#+BEGIN_SRC emacs-lisp :tangle no #+BEGIN_SRC emacs-lisp :tangle rpgdm-ironsworn-tests.el
(ert-deftest rpgdm-ironsworn--progress-to-str-test () (ert-deftest rpgdm-ironsworn--progress-to-str-test ()
(should (equal (rpgdm-ironsworn--progress-to-str :IRONSWORN-PROGRESS-EPIC) (should (equal (rpgdm-ironsworn--progress-to-str :IRONSWORN-PROGRESS-EPIC)
"ironsworn-progress-epic"))) "ironsworn-progress-epic")))

BIN
images/dice-roll-all.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

65
rpgdm-ironsworn-tests.el Normal file
View file

@ -0,0 +1,65 @@
(ert-deftest rpgdm-ironsworn--results-test ()
(should (equal (rpgdm-ironsworn--results 3 2 4 1)
"Strong hit :: 5 (3 + 2) → 4 / 1"))
(should (equal (rpgdm-ironsworn--results 3 2 8 1)
"Weak hit :: 5 (3 + 2) → 8 / 1"))
(should (equal (rpgdm-ironsworn--results 3 2 8 6)
"Miss :: 5 (3 + 2) → 8 / 6"))
(should (equal (rpgdm-ironsworn--results 3 2 6 6)
"Miss :: 5 (3 + 2) → 6 / 6 ← Create a Twist"))
(should (equal (rpgdm-ironsworn--results 3 2 8 6 7)
"Miss :: 5 (3 + 2) → 8 / 6 -- Burn momentum for a Weak hit"))
(should (equal (rpgdm-ironsworn--results 3 2 8 6 9)
"Miss :: 5 (3 + 2) → 8 / 6 -- Burn momentum for a Strong hit")))
(ert-deftest rpgdm-ironsworn--good-character-assets-test ()
(should (rpgdm-ironsworn--good-character-assets '("foo" "bar" "baz")))
(should-not (rpgdm-ironsworn--good-character-assets '("foo" "bar" "foo")))
(should-not (rpgdm-ironsworn--good-character-assets '("assets/companions/dog.org"
"assets/paths/good-guy.org"
"assets/companions/monkey.org")))
(should-not (rpgdm-ironsworn--good-character-assets '(("Companions :: Dog" . "assets/companions/dog.org")
("Paths :: Good Guy" . "assets/paths/good-guy.org")
("Companions :: Monkey" . "assets/companions/monkey.org")))))
(ert-deftest rpgdm-ironsworn--some-character-assets-test ()
(should (= 4 (seq-length (rpgdm-ironsworn--some-character-assets '(1 2 3 4 5 6) 4))))
(should (= 3 (seq-length (rpgdm-ironsworn--some-character-assets '(1 2 3 4 5 6))))))
(ert-deftest rpgdm-ironsworn--move-tuple-test ()
(let ((file "moves/fate/ask-the-oracle.org")
(full "~/other/over/here/moves/fate/ask-the-oracle.org"))
(should (equal (list "Fate :: ask the oracle" file)
(rpgdm-ironsworn--move-tuple file)))
(should (equal (list "Fate :: ask the oracle" full)
(rpgdm-ironsworn--move-tuple full)))))
(ert-deftest rpgdm-ironsworn-progress-level-label-test ()
(should (equal (rpgdm-ironsworn-progress-level-label 1) "epic"))
(should (equal (rpgdm-ironsworn-progress-level-label 12) "troublesome"))
(should (equal (rpgdm-ironsworn-progress-level-label 4) "formidable")))
(ert-deftest rpgdm-ironsworn-progress-test ()
(let ((track "Battling a Grue"))
(rpgdm-ironsworn-progress-delete track)
(rpgdm-ironsworn-progress-create track "Dangerous")
(should (= (rpgdm-ironsworn-progress-amount track) 0))
(rpgdm-ironsworn-progress-mark track)
(should (= (rpgdm-ironsworn-progress-amount track) 2))
(rpgdm-ironsworn-progress-mark track 2)
(should (= (rpgdm-ironsworn-progress-amount track) 6))))
(ert-deftest rpgdm-ironsworn-progress-test ()
(let ((track "Battling an Extreme Grue"))
(rpgdm-ironsworn-progress-delete track)
(rpgdm-ironsworn-progress-create track "Extreme")
(should (= (rpgdm-ironsworn-progress-amount track) 0))
(rpgdm-ironsworn-progress-mark track)
(should (= (rpgdm-ironsworn-progress-amount track) 0))
(rpgdm-ironsworn-progress-mark track 2)
(should (= (rpgdm-ironsworn-progress-amount track) 1))))
(ert-deftest rpgdm-ironsworn--progress-to-str-test ()
(should (equal (rpgdm-ironsworn--progress-to-str :IRONSWORN-PROGRESS-EPIC)
"ironsworn-progress-epic")))

View file

@ -88,12 +88,12 @@ results."
one-challenge two-challenge one-challenge two-challenge
momentum)))) momentum))))
(defun rpgdm-ironsworn--new-character-template (name) (defun rpgdm-ironsworn--new-character-template (&optional name)
"Insert basic Ironsworn template at the end of the current buffer. "Insert basic Ironsworn template at the end of the current buffer.
A header is created with NAME, but if this is an empty string, A header is created with NAME, but if this is an empty string,
a random name is generated for the purposes of the template." a random name is generated for the purposes of the template."
(when (s-blank? name) (when (or (null name) (s-blank? name))
(setq name (rpgdm-tables-choose "names-ironlander"))) (setq name (rpgdm-tables-choose "name/ironlander")))
(let ((frmt (seq-random-elt '("* The Adventures of %s" (let ((frmt (seq-random-elt '("* The Adventures of %s"
"* The Journeys of %s" "* The Journeys of %s"
@ -205,7 +205,7 @@ The chosen assets are _good_ in that they won't have duplicates, etc."
(goto-char (point-max)) (goto-char (point-max))
(insert "\n** Assets\n") (insert "\n** Assets\n")
(if (y-or-n-p "Would you like three random assets? ") (if (y-or-n-p "Would you like three random assets? ")
(rpgdm-ironsworn-random-character-assets asset-files) (rpgdm-ironsworn-random-character-assets 3)
(if (y-or-n-p "Would you like to choose your assets? ") (if (y-or-n-p "Would you like to choose your assets? ")
(call-interactively 'rpgdm-ironsworn-insert-character-asset)))) (call-interactively 'rpgdm-ironsworn-insert-character-asset))))
@ -224,22 +224,21 @@ Note: The stats are added as properties using the
(rpgdm-ironsworn-progress-create (read-string "What title should we give this new character's Epic vow? ") 1) (rpgdm-ironsworn-progress-create (read-string "What title should we give this new character's Epic vow? ") 1)
(rpgdm-ironsworn-progress-create "Bonds" 1) (rpgdm-ironsworn-progress-create "Bonds" 1)
(rpgdm-ironsworn-progress-mark "Bonds")
(search-forward ":END:") (search-forward ":END:")
(end-of-line) (end-of-line)
(insert "\n** Bonds\n") (insert "\n** Bonds\n")
(insert (format " - Your home settlement of %s\n" (rpgdm-tables-choose "settlement-names")))) (insert (format " - Your home settlement of %s\n" (rpgdm-tables-choose "settlement/name"))))
(defun rpgdm-ironsworn--new-character-stats-first (&optional name) (defun rpgdm-ironsworn--new-character-stats-first (&optional name)
"Insert a new character template for character, NAME. "Insert a new character template for character, NAME.
The character stats are first queried, and then assets inserted." The character stats are first queried, and then assets inserted."
(rpgdm-ironsworn--new-character-template name)
(rpgdm-ironsworn--new-character-stats) (rpgdm-ironsworn--new-character-stats)
(rpgdm-ironsworn--new-character-assets)) (rpgdm-ironsworn--new-character-assets))
(defun rpgdm-ironsworn--new-character-assets-first (&optional name) (defun rpgdm-ironsworn--new-character-assets-first (&optional name)
"Insert a new character template for character, NAME. "Insert a new character template for character, NAME.
The assets are inserted first, and then character stats are queried." The assets are inserted first, and then character stats are queried."
(rpgdm-ironsworn--new-character-template name)
;; Saving and restoring point, means the properties should be in the ;; Saving and restoring point, means the properties should be in the
;; correct, top-level position. ;; correct, top-level position.
(save-excursion (save-excursion
@ -255,6 +254,8 @@ which should be using the `org-mode' major mode."
(interactive (list (interactive (list
(read-string "What is the new character's name? ") (read-string "What is the new character's name? ")
(completing-read "What order should we build this? " '("Statistics first" "Assets first")))) (completing-read "What order should we build this? " '("Statistics first" "Assets first"))))
(rpgdm-ironsworn--new-character-template name)
(if (equal order "Assets first") (if (equal order "Assets first")
(rpgdm-ironsworn--new-character-assets-first) (rpgdm-ironsworn--new-character-assets-first)
(rpgdm-ironsworn--new-character-stats-first)) (rpgdm-ironsworn--new-character-stats-first))
@ -505,7 +506,12 @@ For instance, if LABEL is `Dangerous', this returns `8'."
(defun rpgdm-ironsworn-progress-level-label (level) (defun rpgdm-ironsworn-progress-level-label (level)
"Return the label associated with the progress LEVEL, e.g. dangerous." "Return the label associated with the progress LEVEL, e.g. dangerous."
(car (rassoc level rpgdm-ironsworn-progress-levels))) (thread-last rpgdm-ironsworn-progress-levels
(--filter (= (second it) level))
(first) ; Assume we only get one match
(first) ; Get the textual label
(s-split " ")
(first))) ; Word before the space
(defun rpgdm-ironsworn-progress-track-choose (&optional allow-other) (defun rpgdm-ironsworn-progress-track-choose (&optional allow-other)
"Query the user to choose a track stored in the org file. "Query the user to choose a track stored in the org file.
@ -539,6 +545,7 @@ NAME should be a short title, not a description."
("As a subheading to this?" subheading) ("As a subheading to this?" subheading)
("No new heading. Re-use this." no)))) ("No new heading. Re-use this." no))))
(when (called-interactively-p)
(cl-case (completing-read-value "Create a new heading? " option) (cl-case (completing-read-value "Create a new heading? " option)
('same-level (progn ('same-level (progn
(org-insert-heading-respect-content) (org-insert-heading-respect-content)
@ -546,7 +553,7 @@ NAME should be a short title, not a description."
('subheading (progn ('subheading (progn
(org-insert-heading-respect-content) (org-insert-heading-respect-content)
(org-shiftmetaright) (org-shiftmetaright)
(insert name)))) (insert name)))))
(org-set-property track-prop track-val))) (org-set-property track-prop track-val)))
@ -559,19 +566,82 @@ the number of TIMES to mark progress."
(dotimes (idx times) (dotimes (idx times)
(rpgdm-ironsworn-mark-progress-track name))) (rpgdm-ironsworn-mark-progress-track name)))
(defun rpgdm-ironsworn--progress-amount (name)
"Return list of aspects of progress by NAME.
Including:
- track :: The name of the track (same as `name')
- value :: The number complexity level, e.g. how many ticks per marked progress
- level :: The string label of a progress, e.g. Epic or Troublesome
- ticks :: The amount of ticks marked, shnould be <= 40
- boxes :: The number of completed boxes (ticks / 4, rounded down)
- remain :: The number of remaining ticks marked towards the next box."
(let* ((tracks (rpgdm-ironsworn-character-progresses))
(matched (assoc name tracks))
(track (first matched))
(value (second matched))
(level (rpgdm-ironsworn-progress-level-label value))
(ticks (third matched))
(boxes (/ ticks 4))
(remain (% ticks 4)))
(list track value level ticks boxes remain)))
(defun rpgdm-ironsworn--progress-box (boxes leftover)
"Return a org-like table of current completion of an Ironsworn progress.
For instance, with 4 boxes and 2 leftover tick marks, this will return:
| | | | | x | | | | | | "
(defun make-box (boxes leftover-ticks blanks)
(cond
((> boxes 0) (concat " ■ |" (make-box (1- boxes) leftover-ticks blanks)))
((> leftover-ticks 2) (concat " * |" (make-box 0 0 blanks)))
((> leftover-ticks 1) (concat " x |" (make-box 0 0 blanks)))
((> leftover-ticks 0) (concat " - |" (make-box 0 0 blanks)))
((> blanks 0) (concat " |" (make-box 0 0 (1- blanks))))))
(when (> leftover 3)
(setq boxes (+ boxes (/ leftover 4)))
(setq leftover (% leftover 4)))
(when (< boxes 0)
(setq boxes 0))
(when (> boxes 10)
(setq boxes 10))
(setq blanks (if (> leftover 0)
(- 9 boxes)
(- 10 boxes)))
(concat "|" (make-box boxes leftover blanks)))
(ert-deftest rpgdm-ironsworn--progress-box-test ()
(should (equal (rpgdm-ironsworn--progress-box 0 0) "| | | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 0 1) "| - | | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 0 2) "| x | | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 0 3) "| * | | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 1 0) "| ■ | | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 2 0) "| ■ | ■ | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 3 0) "| ■ | ■ | ■ | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 4 0) "| ■ | ■ | ■ | ■ | | | | | | |"
(should (equal (rpgdm-ironsworn--progress-box 4 1) "| ■ | ■ | ■ | ■ | - | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 4 2) "| ■ | ■ | ■ | ■ | x | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 4 3) "| ■ | ■ | ■ | ■ | * | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 5 0) "| ■ | ■ | ■ | ■ | ■ | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 10 0) "| ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ |"))
;; Negative test cases
(should (equal (rpgdm-ironsworn--progress-box 11 0) "| ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ | ■ |"))
(should (equal (rpgdm-ironsworn--progress-box -1 0) "| | | | | | | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 4 8) "| ■ | ■ | ■ | ■ | ■ | ■ | | | | |"))
(should (equal (rpgdm-ironsworn--progress-box 4 6) "| ■ | ■ | ■ | ■ | ■ | x | | | | |")))
(defun rpgdm-ironsworn-progress-amount (name) (defun rpgdm-ironsworn-progress-amount (name)
"Display the progress made against a track, NAME." "Display the progress made against a track, NAME."
(interactive (list (rpgdm-ironsworn-progress-track-choose))) (interactive (list (rpgdm-ironsworn-progress-track-choose)))
(let* ((tracks (rpgdm-ironsworn-character-progresses)) (cl-destructuring-bind
(matched (--filter (equal name (first it)) tracks)) (track value level ticks boxes leftover)
(track (first matched)) (rpgdm-ironsworn--progress-amount name)
(value (second track)) (if (not (called-interactively-p 'any))
(level (rpgdm-ironsworn-progress-level-label value)) boxes
(ticks (third track)) (rpgdm-message "[%s] Progress on '%s': %d %s" level name boxes
(boxes (/ ticks 4))) (rpgdm-ironsworn--progress-box boxes leftover)))))
(if (called-interactively-p 'any)
(rpgdm-message "[%d] Progress on %s: %d (Ticks: %d)" level name boxes ticks)
boxes)))
(defun rpgdm-ironsworn-progress-roll (progress-value) (defun rpgdm-ironsworn-progress-roll (progress-value)
"Display a Hit/Miss message based on the PROGRESS-VALUE. "Display a Hit/Miss message based on the PROGRESS-VALUE.
@ -950,7 +1020,7 @@ Return 0 if not at a heading, or above first headline."
(defun rpgdm-ironsworn--current-character-state (results) (defun rpgdm-ironsworn--current-character-state (results)
"Recursive helper to insert current header properties in RESULTS. "Recursive helper to insert current header properties in RESULTS.
Calls itself if it is not looking at the top-level header in the Calls itself if it is not looking at the top level header in the
file. If a property is already in the hash table, RESULTS, it is file. If a property is already in the hash table, RESULTS, it is
not overwritten, thereby having lower-level subtrees take not overwritten, thereby having lower-level subtrees take
precendence over similar settings in higher headers." precendence over similar settings in higher headers."