Massive code clean-up, all tests still pass.

...but I'm still struggling to understand how it works and how I
use it. Memo to self: document your code better.
This commit is contained in:
Simon Brooke 2024-04-08 22:29:21 +01:00
parent a5b7e8219b
commit 71cfda093d
59 changed files with 323 additions and 123 deletions

View file

@ -2,7 +2,7 @@
"Anything in the game world with agency; primarily but not exclusively
characters."
(:require [cc.journeyman.the-great-game.objects.game-object :refer [ProtoObject]]
[cc.journeyman.the-great-game.objects.container :refer [ProtoContainer]]))
[cc.journeyman.the-great-game.objects.container :refer [ProtoContainer contents is-empty?]]))
;;; hierarchy of needs probably gets implemented here
;;; I'm probably going to want to defprotocol stuff, to define the hierarchy
@ -66,46 +66,64 @@
`world`.")
(tired? [actor world circle] "True if this `actor` needs rest."))
;; (defrecord Agent
;; ;; "A default agent."
;; [name craft home culture]
;; ProtoObject
;; ProtoContainer
;; ProtoAgent
(defrecord Agent
;; "A default agent."
[name craft home culture]
ProtoObject
ProtoContainer
;; (act
;; “Return a world like this `world `except that this `actor `has acted in it.
;; Circle indicates which activation circle the actor is in.
(contents
"The `contents` of an actor are the contents of their pack(s) (if any), where
a pack may be any sort of bag or container which the actor could reasonably
be carrying."
[actor]
(flatten
(map contents (filter #(satisfies? ProtoContainer %)
(:burden actor)))))
;; Note that this implies that a `plan `is a function of three arguments, an
;; actor, a world. and a circle.”
;; [actor world circle]
;; (let [urgent (case circle
;; :other (cond
;; (pending-scheduled-action? actor world circle)
;; (plan-scheduled-action actor world circle)
;; (empty? (:plans actor))
;; (plan-goal actor world circle))
;; :background (cond
;; (threatened? actor world circle)
;; (plan-fight-or-flight actor world circle)
;; (pending-scheduled-action? actor world circle)
;; (plan-scheduled-action actor world circle)
;; (empty? (:plans actor))
;; (plan-goal actor world circle))
;; ;; else
;; (cond
;; (threatened? actor world circle)
;; (plan-fight-or-flight actor world circle)
;; (hungry? actor world circle)
;; (plan-find-food actor world circle)
;; (tired? actor world circle)
;; (plan-find-rest actor world circle)
;; (pending-scheduled-action? actor world circle)
;; (plan-scheduled-action actor world circle)
;; (empty? (:plans actor))
;; (plan-goal actor world circle)))
;; a (if urgent
;; (assoc actor :plans (cons urgent (:plans actor)))
;; actor)]
;; (eval ((first (:plans a)) a world)))))
(is-empty?
[actor]
(empty? (:burden actor)))
ProtoAgent
(act
Return a map in which :world is bound to a world like this `world `except that this `actor `has acted in it; and `:actor` is bound to an
actor like this `actor `except modified by the consequences of the action.
Circle indicates which activation circle the actor is in.
Note that this implies that a `plan `is a function of three arguments, an
actor, a world. and a circle, and returns exactly the sort of map this
function returns.
[actor world circle]
(let [urgent (case circle
:other (cond
(pending-scheduled-action? actor world circle)
(plan-scheduled-action actor world circle))
:background (cond
(threatened? actor world circle)
(plan-fight-or-flight actor world circle)
(pending-scheduled-action? actor world circle)
(plan-scheduled-action actor world circle))
;; else
(cond
(threatened? actor world circle)
(plan-fight-or-flight actor world circle)
(hungry? actor world circle)
(plan-find-food actor world circle)
(tired? actor world circle)
(plan-find-rest actor world circle)
(pending-scheduled-action? actor world circle)
(plan-scheduled-action actor world circle)))
next-action (cond urgent urgent
(empty? (:plans actor))
(plan-goal actor world circle)
:else (first (:plans actor)))
consequences (apply next-action (list actor world circle))]
;; we return consequences of the action, except that, if the action
;; was on the plans of the actor, we remove it.
(if-not (= next-action (first (:plans actor)))
consequences
(assoc consequences :actor
(assoc (:actor consequences) :plans
(rest (-> consequences :actor :plans))))))))

View file

@ -1,6 +1,4 @@
(ns cc.journeyman.the-great-game.objects.container
(:require
[cc.journeyman.the-great-game.objects.game-object :refer :all]))
(ns cc.journeyman.the-great-game.objects.container)
(defprotocol ProtoContainer
(contents

View file

@ -4,7 +4,9 @@
[mw-engine.drainage :refer [run-drainage]]
;;[mw-engine.flow :refer []]
[mw-engine.heightmap :refer [apply-heightmap]]
[mw-engine.utils :refer [map-world]]
[mw-parser.declarative :refer [compile]]
[taoensso.timbre :refer [info]]
[wherefore-art-thou.core :refer [*genders* generate]]))
(defn get-drainage-map
@ -30,21 +32,30 @@
(def ^:dynamic *life-goals*
"TODO: This definitely doesn't belong here, and will be moved."
[:ancestor :citizen :climber :conqueror :explorer :hoarder :master])
(into []
(flatten
(map #(repeat %2 %1)
;; goals
[:ancestor :citizen :climber :conqueror :explorer :hoarder :master]
;; relative frequency of these goals
[10 10 8 5 3 5 8]))))
(defn- create-npc
(defn- create-npc
;; TODO: this function needs not only to create a fully formed NPC, but
;; persist them in the database being built. This is just a sketch.
[prototype]
(let [g (or (:gender prototype) (rand-nth (keys *genders*)))
p (generate g)]
(merge {:age (+ 18 (rand-int 18))
:disposition (- (rand-int 9) 4) ;; -4: surly to +4 sunny
:gender g
:goal (rand-nth *life-goals*)
:family-name (generate)
:occupation :vagrant
:personal-name p} prototype)))
(dissoc (merge {:age (+ 18 (rand-int 18))
:disposition (- (rand-int 9) 4) ;; -4: surly to +4 sunny
:gender g
:goal (rand-nth *life-goals*)
:family-name (generate)
:occupation :vagrant
:personal-name p} prototype)
;; it's useful to have the world available to the create function,
;; but we don't want to return it in the results because clutter.
:world)))
(defn- populate-npcs
[prototype]
@ -54,10 +65,13 @@
(defn populate-cell
[world cell]
;; (info (format "populate-cell: w is %s; cell is %s" (type world) cell))
(let [npcs (case (:state cell)
:camp (populate-npcs {:world world :cell cell :occupation :nomad})
:house (populate-npcs {:world world :cell cell :occupation :farmer})
:inn (populate-npcs {:world world :cell cell :occupation :innkeeper}))]
:camp (populate-npcs {:world world :cell cell :occupation :nomad})
:house (populate-npcs {:world world :cell cell :occupation :peasant})
:inn (populate-npcs {:world world :cell cell :occupation :innkeeper})
;; else
nil)]
(if npcs (assoc cell :npcs npcs)
cell)))
@ -66,7 +80,8 @@
(probably some form of database) and return a structure which allows that
database o be interrogated."
[biome-map]
(let [world (run-world biome-map (compile (slurp "resources/baking/settlement-rules.txt")) (count biome-map))]
(let [world (run-world biome-map (compile (slurp "resources/baking/settlement-rules.txt")) (count biome-map))
with-npcs (map-world world (vary-meta (fn [w c] (populate-cell w c)) assoc :rule-type :ad-hoc))]
;; right, with that settled world, I'm going to put one herdsman with
;; five animals (either all sheep, all cattle, all goats, all horses or
;; all camels on each cell with state camp, and one settler; one farmer
@ -82,7 +97,7 @@
;; to a database I've stored all the NPCs in, and a (vector) roadmap of
;; all the roads that have been created, and a (vector) drainage map.
{:world (map #(populate-cell world %) world)
{:world with-npcs
:roadmap []}))
(defn get-road-map