Skip to content

Instantly share code, notes, and snippets.

@Kodiologist
Created August 17, 2026 17:02
Show Gist options
  • Select an option

  • Save Kodiologist/e757e5c17d934a58753a34d57d30e676 to your computer and use it in GitHub Desktop.

Select an option

Save Kodiologist/e757e5c17d934a58753a34d57d30e676 to your computer and use it in GitHub Desktop.
#!/usr/bin/env hy
"Randomly generate ideas for a game of the tabletop RPG Kill Sector
using the system described in Gauntlet-O-Matic.
https://www.drivethrurpg.com/en/product/492971/gauntlet-o-matic-gauntlet-generation-tool-for-kill-sector
This program is in the public domain (although the linked book is not)."
(require
hyrule [do-n list-n])
(import
string
re
json
pathlib [Path]
hyrule [thru])
(setv _prompts None)
(defn prompts [[path None]]
(global _prompts)
(when (is _prompts None)
(setv _prompts (dfor
d (json.loads (.read-text (Path path)))
(:prompt d) (:options d))))
_prompts)
(defn random-seed []
(.join "" (hy.I.random.choices :k 20
(+ string.ascii-letters string.digits "_"))))
(defn generate-gauntlet [seed]
(setv buffer (hy.I.io.StringIO))
(defn pr [#* args #** kwargs] (print :file buffer #* args #** kwargs))
(setv R (hy.I.random.Random seed))
(pr "Seed:" (hy.repr seed))
(defn roll-choice [question]
"Roll 1d100 to pick an item for a prompt."
(setv i (R.randint 1 100))
(for [[i-range value] (.items (get (prompts) question))]
(setv [lo hi] (if (in "-" i-range)
(.split i-range "-")
[i-range i-range]))
(when (<= (int lo) i (int hi))
(return value)))
(assert False))
(defn roll-enemy-nature []
(setv n 1)
(setv parts [])
(while n
(setv x (get (re.match r"[A-Za-z]+" (roll-choice "Enemy nature")) 0))
(-= n 1)
(when (= x "Combo")
(+= n 2)
(continue))
(.append parts (if (= x "Guest")
f"Guest ({(roll-choice #[[Where's the arena?]])})"
x)))
(.join ", " (sorted parts)))
(defn roll-enemy-tier []
"Generate a random enemy type."
(setv good-traits [])
(setv bad-traits [])
(setv tiers {
"Horde Fighter" (dict :n-good 1 :n-bad 1)
"Squad Fighter" (dict :n-good 2 :n-bad 1)
"Warrior" (dict :n-good 4 :n-bad 1)
"Miniboss" (dict :n-good 5 :n-bad 2)
"Wildcard" (fn []
(.append good-traits (roll-choice "Good traits"))
(.append bad-traits (roll-choice "Bad traits")))
"Specialist" (fn []
(.remove good-traits (R.choice good-traits))
(.append good-traits (R.choice good-traits)))})
(setv callbacks [])
(setv full-tier-name "")
(while True
(setv tier (roll-choice "Enemy tier"))
(setv [tier-name tier-description] (.split tier ". " 1))
(when (and (= tier-name "Specialist") (<= (len good-traits) 2))
; We don't have enough good traits to apply this modifier. Try again.
(continue))
(+= full-tier-name " " tier-name)
(setv tier-effect (get tiers tier-name))
(when (isinstance tier-effect dict)
(.extend good-traits (list-n (:n-good tier-effect)
(roll-choice "Good traits")))
(.extend bad-traits (list-n (:n-bad tier-effect)
(roll-choice "Bad traits")))
(break))
(.append callbacks tier-effect))
(for [f (reversed callbacks)]
(f))
(dict
:name (.lstrip full-tier-name)
:desc (re.sub r", \d+ Good Traits?, \d+ Bad Traits?" "" tier-description)
:good-traits (sorted good-traits)
:bad-traits (sorted bad-traits)))
(setv [n-settings] (R.choices
:population [1 2 3]
:weights [12 5 3]))
(setv question None)
(defn prompt [q]
(nonlocal question)
(setv question q)
(pr)
(pr q))
(defn roll []
(roll-choice question))
(prompt "Where's the arena?")
(pr (if (= n-settings 1)
(roll)
(+ "Mash up the following:\n" (.join "\n" (gfor
i (range n-settings)
(+ "- " (roll)))))))
(prompt "Why are we here?")
(pr (roll))
(prompt "What's the scenario?")
(pr "(Use the first for the whole gauntlet, or use one per wave.)")
(do-n 3
(pr "-" (roll)))
(prompt "Is there a twist?")
(pr (roll))
(prompt "What's the halftime?")
(pr (roll))
(for [wave [1 2]]
(pr)
(pr "Enemies for wave" wave)
(for [enemy [1 2 3]]
(pr "- Enemy type" enemy)
(pr " Nature:" (roll-enemy-nature))
(setv enemy (roll-enemy-tier))
(pr " Tier:" (+ (:name enemy) ". " (:desc enemy)))
(pr " Good traits:" (:good-traits enemy))
(pr " Bad traits:" (:bad-traits enemy))))
(pr)
(pr "Boss")
(pr "Nature:" (roll-enemy-nature))
(pr "Good traits (exaggerated):" (list-n 3 (roll-choice "Good traits")))
(pr "Bad trait (exaggerated):" (roll-choice "Bad traits"))
(prompt "Sector layout")
(setv layout (roll))
(pr layout)
(for [sector-i (thru 1 (* (int (get layout 0)) (int (get layout 2))))]
(pr f"- Feature for sector {sector-i}:" (roll-choice "Sector features")))
(.getvalue buffer))
(when (= __name__ "__main__")
(prompts "kill_sector_gauntlet_o_matic.json")
(print :end "" (generate-gauntlet (random-seed))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment