Today is the Fifth Plough of the Plough

Implemented almost the whole of the Myth of the God Incarnate calendar
This commit is contained in:
Simon Brooke 2020-04-14 08:30:41 +01:00
parent bd76e93568
commit 7e7a55c8ec
39 changed files with 2085 additions and 167 deletions

View file

@ -2,3 +2,6 @@
"Anything in the game world with agency")
;; 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.

View file

@ -1,6 +1,7 @@
(ns the-great-game.gossip.gossip
"Interchange of news events between gossip agents"
(:require [the-great-game.utils :refer [deep-merge]]))
(:require [the-great-game.utils :refer [deep-merge]]
[the-great-game.gossip.news-items :refer [learn-news-item]]))
;; Note that habitual travellers are all gossip agents; specifically, at this
;; stage, that means merchants. When merchants are moved we also need to

View file

@ -1,6 +1,7 @@
(ns the-great-game.gossip.news-items
"Categories of news events interesting to gossip agents"
(:require [clojure.math.numeric-tower :refer [expt sqrt]]))
(:require [the-great-game.world.location :refer [distance-between]]
[the-great-game.time :refer [now]]))
;; The ideas here are based on the essay 'The spread of knowledge in a large
;; game world', q.v.; they've advanced a little beyond that and will doubtless
@ -20,14 +21,26 @@
their `verbs`. The `keys` associated with each topic are the extra pieces
of information required to give context to a gossip item. Generally:
* `actor` is the id of the character who performed the action;
* `other` is the id of the character on whom the action was performed;
* `actor` is the id of the character who it is reported performed the
action;
* `other` is the id of the character on whom it is reported the action
was performed;
* `location` is the place at which the action was performed;
* `object` is an object (or possibly list of objects?) relevant to the action;
* `object` is an object (or possibly list of objects?) relevant to the
action;
* `price` is special to buy/sell, but of significant interest to merchants.
#### Notes:
##### Characters:
*TODO* but note that at most all the receiver can learn about a character
from a news item is what the giver knows about that character, degraded by
what the receiver finds interesting about them. If we just pass the id here,
then either the receiver knows everything in the database about the
character, or else the receiver knows nothing at all about the character.
Neither is desirable. Further thought needed.
##### Locations:
A 'location' value is a list comprising at most the x/y coordinate location
@ -62,6 +75,7 @@
:plot {:verb :plot :keys [:actor :other :object :location]}
;; Rapes are interesting.
:rape {:verb :rape :keys [:actor :other :location]
;; Should you also infer from rape that actor is male and adult?
:inferences [{:verb :attack}
{:verb :sex}
{:verb :sex :actor :other :other :actor}]}
@ -84,7 +98,9 @@
(defn interest-in-character
"Integer representation of how interesting this `character` is to this
`gossip`."
`gossip`.
*TODO:* this assumes that characters are passed as keywords, but, as
documented above, they probably have to be maps, to allow for degradation."
[gossip character]
(count
(concat
@ -97,99 +113,46 @@
[gossip character]
(> (interest-in-character gossip character) 0))
(defn get-coords
"Return the coordinates in the game world of `location`, which may be
1. A coordinate pair in the format {:x 5 :y 32};
2. A location, as discussed above;
3. Any other gameworld object, having a `:location` property whose value
is one of the above."
[location]
(cond
(empty? location) nil
(map? location)
(cond
(and (number? (:x location)) (number? (:y location)))
location
(:location location)
(:location location))
:else
(get-coords (first (remove keyword? location)))))
;; (get-coords {:x 5 :y 7})
;; (get-coords [{:x -4 :y 55} :auchencairn :galloway :scotland])
(defn distance-between
[location-1 location-2]
(let [c1 (get-coords location-1)
c2 (get-coords location-2)]
(if
(and c1 c2)
(sqrt (+ (expt (- (:x c1) (:x c2)) 2) (expt (- (:y c1) (:y c2)) 2))))))
;; (distance-between {:x 5 :y 5} {:x 2 :y 2})
;; (distance-between {:x 5 :y 5} {:x 2 :y 5})
;; (distance-between {:x 5 :y 5} [{:x -4 :y 55} :auchencairn :galloway :scotland])
;; (distance-between {:x 5 :y 5} [:auchencairn :galloway :scotland])
(defn interest-in-location
"Integer representation of how interesting this `location` is to this
`gossip`."
[gossip location]
(cond
(and (map? location) (number? (:x location)) (number? (:y location)))
(if-let [home (:home gossip)]
(let [d (distance-between location home)
i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should
;;fall of with distance from home, but possibly on a log scale
]
(if (> i 1) i 0))
0)
(coll? location)
(reduce
+
(map
#(interest-in-location gossip %)
location))
(and (map? location) (:x location) (:y location))
(if-let [home (:home gossip)]
(let [d (distance-between location home)
i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should
;;fall of with distance from home, but possibly on a log scale
]
(if (i > 1) i 0)
i))
:else
(count
(filter
#(some (fn [x] (= x location)) (:location %))
(:knowledge gossip)))))
;; (interest-in-location
;; {:knowledge [{:verb :steal
;; :actor :albert
;; :other :belinda
;; :object :foo
;; :location [{:x 35 :y 23} :auchencairn :galloway]}]}
;; :galloway)
;; (interest-in-location
;; {:knowledge [{:verb :steal
;; :actor :albert
;; :other :belinda
;; :object :foo
;; :location [{:x 35 :y 23} :auchencairn :galloway]}]}
;; [:galloway :scotland])
;; (interest-in-location
;; {:knowledge [{:verb :steal
;; :actor :albert
;; :other :belinda
;; :object :foo
;; :location [{:x 35 :y 23} :auchencairn :galloway]}]}
;; :dumfries)
;; (interest-in-location
;; {:home {:x 35 :y 23}}
;; {:x 35 :y 24})
(defn interesting-location?
"True if the location of this news `item` is interesting to this `gossip`."
[gossip item]
(> (interest-in-location gossip (:location item)) 1))
(defn interesting-object?
[gossip object]
;; TODO: Not yet (really) implemented
true)
(defn interesting-topic?
[gossip topic]
;; TODO: Not yet (really) implemented
true)
(defn interesting-item?
"True if anything about this news `item` is interesting to this `gossip`."
[gossip item]
@ -212,18 +175,44 @@
#(= % :verb)
(keys rule))))))
;; (infer {:verb :marry :actor :adam :other :belinda}
;; {:verb :marry :actor :other :other :actor})
;; (infer {:verb :rape :actor :adam :other :belinda}
;; {:verb :attack})
;; (infer {:verb :rape :actor :adam :other :belinda}
;; {:verb :sex :actor :other :other :actor})
(declare learn-news-item)
(defn make-all-inferences
"Return a list of knowledge entries inferred from this news `item` by this
`gossip`."
[item]
(set
(reduce
concat
(map
#(:knowledge (learn-news-item {} (infer item %) false))
(:inferences (news-topics (:verb item)))))))
(defn degrade-character
"Return a character specification like this `character`, but comprising
only those properties this `gossip` is interested in."
[gossip character]
;; TODO: Not yet (really) implemented
character)
(defn degrade-location
"Return a location specification like this `location`, but comprising
only those elements this `gossip` is interested in. If none, return
`nil`."
[gossip location]
(let [l (if
(coll? location)
(filter
#(when (interesting-location? gossip %) %)
location))]
(when-not (empty? l) l)))
(defn learn-news-item
"Return a gossip like this `gossip`, which has learned this news `item` if
it is of interest to them."
;; TODO: Not yet implemented
([gossip item]
(learn-news-item gossip item false))
(learn-news-item gossip item true))
([gossip item follow-inferences?]
(if
(interesting-item? gossip item)
@ -235,17 +224,21 @@
(number? (:nth-hand item))
(inc (:nth-hand item))
1)
;; ought to degrate the location
:date (if (number? (:date item)) (:date item) (now))
:location (degrade-location gossip (:location item))
;; ought to degratde the location
;; ought to maybe-degrade characters we're not yet interested in
)
;; ought not to add knowledge items we already have, except
;; to replace if new item is of increased specificity
(:knowledge gossip)))]
(if follow-inferences?
(reduce
merge
(assoc
g
(map
#(learn-news-item gossip (infer item %) false)
(:inferences (news-topics (:verb item))))))))))
:knowledge
(concat (:knowledge g) (make-all-inferences item)))
g))
gossip)))

144
src/the_great_game/time.clj Normal file
View file

@ -0,0 +1,144 @@
(ns the-great-game.time
(:require [clojure.string :as s]))
(def game-start-time
"The start time of this run."
(System/currentTimeMillis))
(def ^:const game-day-length
"The Java clock advances in milliseconds, which is fine.
But we need game-days to be shorter than real world days.
A Witcher 3 game day is 1 hour 36 minutes, or 96 minutes, which is
presumably researched. Round it up to 100 minutes for easier
calculation."
(* 100 ;; minutes per game day
60 ;; seconds per minute
1000)) ;; milliseconds per second
(defn now
"For now, we'll use Java timestamp for time; ultimately, we need a
concept of game-time which allows us to drive day/night cycle, seasons,
et cetera, but what matters about time is that it is a value which
increases."
[]
(System/currentTimeMillis))
(def ^:const canonical-ordering-of-houses
"The canonical ordering of religious houses."
[:eye
:foot
:nose
:hand
:ear
:mouth
:stomach
:furrow
:plough])
(def ^:const days-of-week
"The eight-day week of the game world. This differs from the canonical
ordering of houses in that it omits the eye."
(rest canonical-ordering-of-houses))
(def ^:const days-in-week
"This world has an eight day week."
(count days-of-week))
(def ^:const seasons-of-year
"The ordering of seasons in the year is different from the canonical
ordering of the houses, for reasons of the agricultural cycle."
[:foot
:nose
:hand
:ear
:mouth
:stomach
:plough
:furrow
:eye])
(def ^:const seasons-in-year
"Nine seasons in a year, one for each house (although the order is
different."
(count seasons-of-year))
(def ^:const weeks-of-season
"To fit nine seasons of eight day weeks into 365 days, each must be of
five weeks."
[:first :second :third :fourth :fifth])
(def ^:const weeks-in-season
"To fit nine seasons of eight day weeks into 365 days, each must be of
five weeks."
(count weeks-of-season))
(def ^:const days-in-season
(* weeks-in-season days-in-week))
(defn game-time
"With no arguments, the current game time. If a Java `timestamp` value is
passed (as a `long`), the game time represented by that value."
([] (game-time (now)))
([timestamp]
(- timestamp game-start-time)))
(defmacro day-of-year
"The day of the year represented by this `game-time`, ignoring leap years."
[game-time]
`(mod (long (/ ~game-time game-day-length)) 365))
(def waiting-day?
"Does this `game-time` represent a waiting day?"
(memoize
;; we're likely to call this several times in quick succession on the
;; same timestamp
(fn [game-time]
(>=
(day-of-year game-time)
(* seasons-in-year weeks-in-season days-in-week)))))
(defn day
"Day of the eight-day week represented by this `game-time`."
[game-time]
(let [day-of-week (mod (day-of-year game-time) days-in-week)]
(if (waiting-day? game-time)
(nth weeks-of-season day-of-week)
(nth days-of-week day-of-week))))
(defn week
"Week of season represented by this `game-time`."
[game-time]
(let [day-of-season (mod (day-of-year game-time) days-in-season)
week (/ day-of-season days-in-week)]
(if (waiting-day? game-time)
:waiting
(nth weeks-of-season week))))
(defn season
[game-time]
(let [season (int (/ (day-of-year game-time) days-in-season))]
(if (waiting-day? game-time)
:waiting
(nth seasons-of-year season))))
(defn date-string
"Return a correctly formatted date for this `game-time` in the calendar of
the Great Place."
[game-time]
(s/join
" "
(if
(waiting-day? game-time)
[(s/capitalize
(name
(nth
weeks-of-season
(mod (day-of-year game-time) days-in-week))))
"waiting day"]
[(s/capitalize (name (week game-time)))
(s/capitalize (name (day game-time)))
"of the"
(s/capitalize (name (season game-time)))])))

View file

@ -1,5 +1,6 @@
(ns the-great-game.world.location
"Functions dealing with location in the world.")
"Functions dealing with location in the world."
(:require [clojure.math.numeric-tower :refer [expt sqrt]]))
;; A 'location' value is a list comprising at most the x/y coordinate location
;; and the ids of the settlement and region (possibly hierarchically) that contain
@ -8,3 +9,29 @@
;; So location information will degrade progressively as the item is passed along.
;; It is assumed that the `:home` of a character is a location in this sense.
(defn get-coords
"Return the coordinates in the game world of `location`, which may be
1. A coordinate pair in the format {:x 5 :y 32};
2. A location, as discussed above;
3. Any other gameworld object, having a `:location` property whose value
is one of the above."
[location]
(cond
(empty? location) nil
(map? location)
(cond
(and (number? (:x location)) (number? (:y location)))
location
(:location location)
(:location location))
:else
(get-coords (first (remove keyword? location)))))
(defn distance-between
[location-1 location-2]
(let [c1 (get-coords location-1)
c2 (get-coords location-2)]
(when
(and c1 c2)
(sqrt (+ (expt (- (:x c1) (:x c2)) 2) (expt (- (:y c1) (:y c2)) 2))))))