Added ideas about scheduling daily behaviour.

All tests currently pass, but that's at least partly because a lot of the new code doesn't yet have tests.
This commit is contained in:
Simon Brooke 2024-01-02 21:00:20 +00:00
parent 1c4cb01af5
commit 81ceaec950
10 changed files with 347 additions and 37 deletions

View file

@ -4,17 +4,21 @@
(:require [cc.journeyman.the-great-game.objects.game-object :refer [ProtoObject]]
[cc.journeyman.the-great-game.objects.container :refer [ProtoContainer]]))
;;; hierarchy of needs probably gets implemented here
;;; I'm probably going to want to defprotocol stuff, to define the hierarchy
;;; of things in the gameworld; either that or drop to Java, wich I'd rather not do.
;;; hierarchy of needs probably gets implemented here
;;; I'm probably going to want to defprotocol stuff, to define the hierarchy
;;; of things in the gameworld; either that or drop to Java, wich I'd rather not do.
;;; attitudes - liking/disliking, attraction/repulsion, amity/hostility, trust/fear
;;; also need to live at this layer, even though dynamic change in attitudes belongs
;;; in the character layer.
(defprotocol ProtoAgent
"An object which can act in the world"
(act
[actor world circle]
"Allow `actor` to do something in this `world`, in the context of this
"Allow `actor` to do something in this `world`, in the context of this
`circle`; return the new state of the actor if something was done, `nil`
if nothing was done. Circle is expected to be one of
if nothing was done. `Circle` is expected to be one of
* `:active` - actors within visual/audible range of the player
character;
@ -22,24 +26,86 @@
to it that they may enter the active circle within a short period;
* `:background` - actors who are active in the background in order to
handle trade, news, et cetera;
* `other` - actors who are not members of any other circle, although
I'm not clear whether it would ever be appropriate to invoke an
`act` method on them.
* `:other` - actors who are not members of any other circle.
The `act` method *must not* have side effects; it must *only* return a
new state. If the actor's intention is to seek to change the state of
something else in the game world, it must add a representation of that
intention to the sequence which will be returned by its
`pending-intentions` method.")
(hungry? [actor world circle] "True if this actor is hungry and has no
immediate access to food.")
(pending-intentions
[actor]
"Returns a sequence of effects an actor intends, as a consequence of
acting. The encoding of these is not yet defined."))
acting.")
(pending-scheduled-action? [actor world circle]
"True if there is a plan in this `actor`'s
schedule which should be activated now.
NOTE THAT plans in the `daily` schedule are
NOT activated when in circles `:background`
or `:other`")
(plan-fight-or-flight [actor world circle]
"Return a plan to resolve any active threat to this
`actor` in this `world`.")
(plan-find-food [actor workd circle]
"Return a plan to find this `actor` food in this `world`.")
(plan-find-rest [actor workd circle]
"Return a plan to find this `actor` a safe place to rest, or
if in one, to actually rest, in this `world`.")
(plan-goal [actor world circle] "Return a plan to advance this `actor`
towards their personal objective, in this
world, or `nil` for default actors with no
objective.")
(plan-scheduled-action [actor workd circle]
"Return a plan taken from the schedule of this actor
for the current date and time, if any, else `nil`.")
(schedule [actor] "Return a map of scheduled actions for this `actor`.
TODO: work out the detailed format!")
(threatened? [actor world circle] "True if this `actor` is threatened in this
`world`.")
(tired? [actor world circle] "True if this `actor` needs rest."))
(defrecord Agent
;; "A default agent."
[name craft home culture]
[name craft home culture]
ProtoObject
ProtoContainer
ProtoAgent
)
(act
Return a world like this `world `except that this `actor `has acted in it.
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.
[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)))))

View file

@ -0,0 +1,63 @@
(ns cc.journeyman.the-great-game.agent.schedule
"Schedules of plans for actors in the game, in order that they may have
daily and seasonal patterns of behaviour.")
;; TODO: I don't have a good handle yet on when a new scheduled task can
;; interrupt an existing scheduled task. It's highly undesirable that
;; uncompleted scheduled tasks should be left on the queue. The simplest
;; solution is to arrange the schedule such that, under notmal circumstances,
;; no scheduled task will interrupt another. But if a scheduled task is
;; interrupted by an attack, say, or a conversation, and then continued,
;; there's a chance of overrunning the start of the next.
;;
;; Perhaps I need to give scheduled tasks the equivalent of a watchdog timer,
;; but that makes them much more sophisticated objects than I wanted them to
;; be.
;; NOTE: this assumes that a world contains a key `:time` whose values are
;; a map with at least the keys
;; 1. `:day`, whose value is an integer representing the current day of the
;; year, and
;; 2. `minute`, whose value is an integer representing the current minute of
;; the day.
;; it probably also includes a `:year`, but that isn't needed here.
;; (def default-human-schedule
;; "A sample schedule for a human actor. This assumes that each of:
;; 1. `find-food`;
;; 2. `goto-market`;
;; 3. `help-with-harvest`;
;; 3. `perform-craft`
;; 4. `sleep-until-dawn`
;; Are plans, which is to say, functions of three arguments, an `actor`,
;; a `world` and a `circle`."
;; {:annual {32 {:daily {1020 (fn [a w c] (attend-festival a w c :imbolc))}}
;; 122 {:daily {1020 (fn [a w c] (attend-festival a w c :bealtaine))}}
;; 210 {:daily {480 help-with-harvest}}
;; 211 {:daily {480 help-with-harvest}}
;; 212 {:daily {480 help-with-harvest}}
;; 213 {:daily {480 help-with-harvest}}
;; 214 {:daily {480 help-with-harvest
;; 1020 (fn [a w c](attend-festival a w c :lughnasadh))}}
;; 306 {:daily {1020 (fn [a w c] (attend-festival a w c :samhain))}}}
;; :daily {420 find-food
;; 480 (fn [actor world circle]
;; (case circle
;; (:other :background) nil
;; ;; else
;; (if (has-craft-supplies? actor world circle)
;; (goto-market actor world circle)
;; (perform-craft actor world circle))))
;; 720 find-food
;; 780 perform-craft
;; 1020 find-food
;; 1320 sleep-until-dawn}})
(defn plan-scheduled-action [actor world circle]
"Return the scheduled plan for the current time in this `world` from the
schedule of this `actor`, provided the `actor is in an appropriate `circle`"
(case circle
(:active :pending) (let [s (:schedule actor)
d (or (:daily (-> s :annual (-> world :time :day)))
(:daily s))]
(when d (d (-> world :time :minute))))))

View file

@ -0,0 +1,9 @@
(ns cc.journeyman.the-great-game.character.character
"A character that can talk; either human or dragon (although very probably
we won't do talking dragons until really well into this process). All
characters have the news-passing abilities of a gossip, but we use `gossip`
to mean a special character who is part of the news-passing network."
(:require [cc.journeyman.the-great-game.gossip.gossip :refer [dialogue]]
[cc.journeyman.the-great-game.agent.agent :refer [Agent]]))

View file

@ -36,6 +36,8 @@
[cc.journeyman.the-great-game.utils :refer [inc-or-one truthy?]]
[taoensso.timbre :as l]))
(declare interesting-location?)
(def news-topics
"Topics of interest to gossip agents. Topics are keyed in this map by
their `verbs`. The `keys` associated with each topic are the extra pieces
@ -116,7 +118,7 @@
:war {:verb :war :keys [:actor :other :location]
:inferences [{:verb :war :actor :other :other :actor}]}})
(def all-known-verbs
(def all-known-verbs
"All verbs currently known to the gossip system."
(set (keys news-topics)))
@ -131,7 +133,9 @@
;; TODO: we ought also check the relationships of the gossip.
;; Are relationships just propositions in the knowledge base?
(filter #(= (:actor %) character) (:knowledge gossip))
(filter #(= (:other %) character) (:knowledge gossip)))))
(filter #(= (:other %) character) (:knowledge gossip))
(when (interesting-location? gossip (:home character))
(list true)))))
(defn interesting-character?
"Boolean representation of whether this `character` is interesting to this
@ -180,19 +184,13 @@
;; TODO: Not yet (really) implemented
true)
(defn interesting-topic?
[gossip topic]
;; TODO: Not yet (really) implemented
true)
(defn interesting-verb?
"Is this `verb` interesting to this `gossip`?"
[gossip verb]
(let [vs (:interesting-verbs gossip)]
(truthy?
(if (set? vs)
(vs verb)
false))))
(when (set? vs)
(vs verb)))))
;; (interesting-verb? {:interesting-verbs #{:kill :sell}} :sell)
@ -249,8 +247,7 @@
(interesting-character? gossip (:actor item))
(interesting-character? gossip (:other item))
(interesting-location? gossip (:location item))
(interesting-object? gossip (:object item))
(interesting-topic? gossip (:verb item)))))
(interesting-object? gossip (:object item)))))
(defn infer
"Infer a new knowledge item from this `item`, following this `rule`."
@ -273,9 +270,9 @@
`item`."
[item]
(set
(map
#(infer item %)
(:inferences (news-topics (:verb item))))))
(map
#(infer item %)
(:inferences (news-topics (:verb item))))))
(defn degrade-character
"Return a character specification like this `character`, but comprising
@ -330,8 +327,8 @@
:knowledge
(set
(cons
item'
(:knowledge gossip))))]
item'
(:knowledge gossip))))]
(if follow-inferences?
(assoc
g