Skip to content

Instantly share code, notes, and snippets.

@samskivert
Created December 31, 2015 01:41
Show Gist options
  • Save samskivert/39dab8a7801084e4cf56 to your computer and use it in GitHub Desktop.
Save samskivert/39dab8a7801084e4cf56 to your computer and use it in GitHub Desktop.
Hypothetical game logic rules/scripts
;;
;; Utility functions
;; returns a randomly chosen unit stat
(defun randstat () (pickrand 'hp 'attack))
;; returns a randomly chosen battle unit kind
;; (TODO: base this on all unit kinds that have >0 attack)
(defun randbattler () (pickrand 'strike 'defend 'drone))
;; returns true if a unit is a battle unit (can attack things)
(defun isbattle (unit) (> (stat unit 'attack) 0))
;; list helpers
(defun non-empty (list) (> list.size 0))
(defun is-empty (list) (= list.size 0))
;;
;; Planet generation
;; Example
;; (defpgen 'PKIND
;; (tags 'TAG 'TAG ...)
;; ...actions to init planet..
;; )
;;
;; PKIND defines the kind of planet, which is used later in the planet logic rules. It is
;; also used to resolve the icon image for the planet, so don't misspell it.
;;
;; TAGs are used in by the galaxy generation algorithm:
;; - 'seed planets are used to seed the starting area
;; - 'boss planets are used to create boss areas "around" the seed area
;; - 'neutral planets are scattered all around both seed and boss areas
;; some helpers to simplify creating certain kinds of planets
(defun init-resource (planet output)
(set planet.recipe (recipe ('miner 1) output))
(set planet.doubleProb (* (rand# 1 3) 5)) // 5-15
)
(defun init-trade (planet recipe boost-stat boost%)
(set planet.recipe recipe)
(set planet.boost-stat boost-stat)
(set planet.boost% boost%)
)
(defun init-factory (planet output)
(set planet.recipe (recipe () (output 1)))
(set planet.boost-stat (randstat))
(let boostIdx (rand# 1 4)) // 1-4
(set planet.boost% (* boostIdx 25)) // 25-100%
(let maxProb (- 7 boostIdx)) // 3-6
;; if output kind is not a battle unit, set boost prob to 0 (never boost)
(set planet.boostProb (if (isbattle kind) (* (rand# 1 maxProb) 5) 0))
)
;; seed planets (with some also in neutral)
(defpgen 'factory
(tags 'seed)
(init-factory planet 'miner)
)
(defpgen 'resource
(tags 'seed)
(init-resource planet ('metals 2 'crystals 1))
)
(defpgen 'resource
(tags 'seed)
(init-resource planet ('fuels 2 'crystals 1))
)
(defpgen 'trade
(tags 'seed)
(init-trade (recipe ('crystals 2) ('diplo 1)) 'hp 0)
)
(defpgen 'trade
(tags 'seed)
(init-trade (recipe ('metals 1 'fuels 1) ('strike 1 'defend 1)) 'attack 50)
)
(defpgen 'factory
(tags 'seed)
(init-factory planet (randbattler))
)
(defpgen 'buff
(tags 'seed)
(set planet.buffStat 'hp)
(set planet.buffAmount 10) ;; TODO: more random?
)
;; neutral planets
(defpgen 'trade
(tags 'neutral)
(init-trade (recipe ('fuels 2) ('strike 1)) 'attack 50)
)
(defpgen 'trade
(tags 'neutral)
(init-trade (recipe ('fuels 2) ('strike 1)) 'attack 50)
)
;; ...TODO: the rest...
;; enemy planets
(defpgen `stronghold
(tags `enemy)
(set planet.mode `init-colonizers) ;; part of stronghold state machine
)
;;
;; Planet logic
;; Example
;; (defprule "NAME"
;; (event 'EVENT)
;; (conds (...condition...) (...condition...) ...)
;; ...actions...
;; )
;;
;; NAME is just a human readable name that makes life easier for debugging.
;; EVENT is one of:
;; - 'onDock fired when a unit docks at a planet
;; - 'onSpawn fired when a unit is spawned
;; - 'onDeath fired when a unit is destroyed
;; - 'tick fired for each planet when the clock ticks
;; - 'produce fired after 'tick for planets which meet production criteria (no opponents in orbit;
;; recipe ingredients satisfied; can fire multiple times on a single tick)
;;
;; In the actions that are executed when a prule fires, 'planet' is bound to the planet in
;; question and for rules that involve a unit ('onDock & 'onSpawn) 'unit' is bound to that unit.
(defprule "buff-apply"
(event 'onDock)
(conds (= planet.type 'buff)
(= planet.dockedEnemies 0)
(> (stat unit 'attack) 0) ;; only buff battle units
(= bot.boosted 'false)) ;; don't buff already buffed units
(boost unit planet.buffStat planet.buffAmount)
)
(defprule "tomb-rebirth"
(event 'tick)
(conds (= planet.type 'tomb))
(rebirth planet) ;; TODO: make 'what kind of new planet' explicit rather than implicit
)
(defprule "hideout-spawn"
(event 'tick)
(conds (= planet.type 'hideout)
(let kind (if (prob planet.diploProb) 'diplo 'pirate))
(spawn planet kind)
)
(defprule "factory-produce"
(event 'produce)
(conds (= planet.type 'factory)
(< planet.dockedProducts 2))
(let unit (spawn planet planet.recipe.output-kind))
(if (prob planet.boostProb) (boost% unit planet.boost-stat planet.boost%))
)
(defprule "resource-produce"
(event 'produce)
(conds (= planet.type 'resource))
(spawn planet planet.recipe.output-kind)
(if (prob planet.doubleProp) (spawn planet planet.recipe.output-kind))
)
(defprule "trade-produce"
(event 'produce)
(conds (= planet.type 'trade))
(let unit (spawn planet planet.recipe.output-kind))
(if (> planet.boost% 0) (boost% unit planet.boost-stat planet.boost%))
)
;;
;; Enemy logic
;; note that enemy logic makes use of a special construct called a 'theater' which is populated
;; with an enumeration of all friendly, neutral and enemy planets within 'influence range' of
;; the planet in question as well all enemy units (by kind) in the theater
(defprule "init-colonizers"
(event 'tick)
(conds (= planet.type `stronghold)
(= planet.mode `init-colonizers))
(repeat 3
(let unit (spawn planet `diplo))
(set unit.mode (pickrand `colonize-factory `colonize-trade `colonize-resource))
)
(set planet.mode `defend)
)
(defprule "init-defenders"
(event 'tick)
(conds (= planet.type `stronghold)
(= planet.mode `init-defenders))
(repeat 3 (spawn planet `defend))
(set planet.mode `idle)
)
(defprule "react-to-intruders"
(event 'tick)
(conds (= planet.type `stronghold)
(= planet.mode `idle))
(for kind (`strike `strike `drone `drone)
(let unit (spawn planet kind))
(set unit.mode `seek-and-destroy)
)
(set planet.mode `on-defensive)
)
;; might have other rules that only fire when we're on the defensive
(defprule "relax-guard"
(event 'tick)
(conds (= planet.type `stronghold)
(= planet.mode `on-defensive)
(is-empty (theater.friendly-units `any)))
(set planet.mode `idle)
)
(defprule "attack-planet"
(event 'tick)
(conds (= planet.type 'stronghold)
(non-empty theater.friendly-planets.size)
(non-empty (theater.enemy-units 'strike)))
(let target (pickrand theater.friendly-planets))
(let attacker (theater.find-closest target (theater.enemy-units 'strike)))
(set-course attacker target)
)
(defprule "defend-hold"
(event 'tick)
(conds (= planet.type 'stronghold)
(< planet.defense-power 50)
(non-empty theater.enemy-units `defend))
(let defender (theater.find-closest planet (theater.enemy-units 'defend)))
(set-course defender planet)
)
(defprule "seek-and-destroy"
(event 'onDock)
(conds (= unit.mode `seek-and-destroy))
(if (is-empty (theater.friendly-units `any))
(set unit.mode `idle)
(set-course unit (theater.closet-friendly unit))
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment