diff --git a/.gitignore b/.gitignore index 7387005..0910231 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,3 @@ pom.xml.asc .nrepl-port .cpcache/ *~ - -docs/cloverage/ diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index eba4573..4590c73 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -15,19 +15,15 @@ TotalBlankInstrumented - the-great-game.core
2
4
-33.33 % -
2
the-great-game.agent.agent
1
-66.67 % -613 +100.00 % +
1
+100.00 % +721 the-great-game.gossip.gossip
65539 - the-great-game.merchants.markets
160
53
-75.12 % + the-great-game.gossip.news-items
461
55
+89.34 %
25
5
14
-68.18 % + style="width:83.65384615384616%; + float:left;"> 87
9
8
+92.31 % +24429104 + + + the-great-game.merchants.markets
206
7
+96.71 % +
41
2
1
+97.73 % 84844 the-great-game.merchants.merchant-utils
104
122
-46.02 % + style="width:66.12377850162866%; + float:left;"> 203
104
+66.12 %
33
3
25
-59.02 % -92761 + style="width:66.66666666666667%; + float:left;"> 48
4
20
+72.22 % +106772 the-great-game.merchants.merchants
4.03 % 1736124 + + the-great-game.time
259
5
+98.11 % +
58
1
1
+98.33 % +1442160 + the-great-game.utils
100.00 % 35319 + + the-great-game.world.location
76
10
+88.37 % +
12
3
2
+88.24 % +37417 + the-great-game.world.routes
-51.99 % +66.14 % -54.62 % +67.89 % diff --git a/docs/cloverage/the_great_game/agent/agent.clj.html b/docs/cloverage/the_great_game/agent/agent.clj.html new file mode 100644 index 0000000..bbdf18f --- /dev/null +++ b/docs/cloverage/the_great_game/agent/agent.clj.html @@ -0,0 +1,29 @@ + + + + the_great_game/agent/agent.clj + + + + 001  (ns the-great-game.agent.agent +
+ + 002    "Anything in the game world with agency") +
+ + 003   +
+ + 004  ;;  hierarchy of needs probably gets implemented here +
+ + 005  ;;  I'm probably going to want to defprotocol stuff, to define the hierarchy +
+ + 006  ;;  of things in the gameworld; either that or drop to Java, wich I'd rather not do. +
+ + 007   +
+ + diff --git a/docs/cloverage/the_great_game/gossip/gossip.clj.html b/docs/cloverage/the_great_game/gossip/gossip.clj.html index df4c108..069225a 100644 --- a/docs/cloverage/the_great_game/gossip/gossip.clj.html +++ b/docs/cloverage/the_great_game/gossip/gossip.clj.html @@ -8,7 +8,7 @@ 001  (ns the-great-game.gossip.gossip
- 002    "Interchange of news events between agents agents" + 002    "Interchange of news events between gossip agents"
003    (:require [the-great-game.utils :refer [deep-merge]])) @@ -140,7 +140,7 @@ 045    `new-location`. Many gossips are essentially shadow-records of agents of
- 046    other types, and the movement if the gossip should be controlled by the + 046    other types, and the movement of the gossip should be controlled by the
047    run function of the type of the record they shadow. The [[#run]] function diff --git a/docs/cloverage/the_great_game/gossip/news_items.clj.html b/docs/cloverage/the_great_game/gossip/news_items.clj.html new file mode 100644 index 0000000..0b410d6 --- /dev/null +++ b/docs/cloverage/the_great_game/gossip/news_items.clj.html @@ -0,0 +1,740 @@ + + + + the_great_game/gossip/news_items.clj + + + + 001  (ns the-great-game.gossip.news-items +
+ + 002    "Categories of news events interesting to gossip agents" +
+ + 003    (:require [the-great-game.world.location :refer [distance-between]] +
+ + 004              [the-great-game.time :refer [now]])) +
+ + 005   +
+ + 006  ;; The ideas here are based on the essay 'The spread of knowledge in a large +
+ + 007  ;; game world', q.v.; they've advanced a little beyond that and will doubtless +
+ + 008  ;; advance further in the course of writing and debugging this namespace. +
+ + 009   +
+ + 010  ;; A news item is a map with the keys: +
+ + 011  ;; +
+ + 012  ;; * `date` - the date on which the reported event happened; +
+ + 013  ;; * `nth-hand` - the number of agents the news item has passed through; +
+ + 014  ;; * `verb` - what it is that happened (key into `news-topics`); +
+ + 015  ;; +
+ + 016  ;; plus other keys taken from the `keys` value associated with the verb in +
+ + 017  ;; `news-topics` +
+ + 018   +
+ + 019  (def news-topics +
+ + 020    "Topics of interest to gossip agents. Topics are keyed in this map by +
+ + 021    their `verbs`. The `keys` associated with each topic are the extra pieces +
+ + 022    of information required to give context to a gossip item. Generally: +
+ + 023   +
+ + 024    * `actor` is the id of the character who it is reported performed the +
+ + 025    action; +
+ + 026    * `other` is the id of the character on whom it is reported the action +
+ + 027    was performed; +
+ + 028    * `location` is the place at which the action was performed; +
+ + 029    * `object` is an object (or possibly list of objects?) relevant to the +
+ + 030    action; +
+ + 031    * `price` is special to buy/sell, but of significant interest to merchants. +
+ + 032   +
+ + 033    #### Notes: +
+ + 034   +
+ + 035    ##### Characters: +
+ + 036   +
+ + 037    *TODO* but note that at most all the receiver can learn about a character +
+ + 038    from a news item is what the giver knows about that character, degraded by +
+ + 039    what the receiver finds interesting about them. If we just pass the id here, +
+ + 040    then either the receiver knows everything in the database about the +
+ + 041    character, or else the receiver knows nothing at all about the character. +
+ + 042    Neither is desirable. Further thought needed. +
+ + 043   +
+ + 044    ##### Locations: +
+ + 045   +
+ + 046    A 'location' value is a list comprising at most the x/y coordinate location +
+ + 047    and the ids of the settlement and region (possibly hierarchically) that contain +
+ + 048    the location. If the x/y is not local to the home of the receiving agent, they +
+ + 049    won't remember it and won't pass it on; if any of the ids are not interesting +
+ + 050    So location information will degrade progressively as the item is passed along. +
+ + 051   +
+ + 052    It is assumed that the `:home` of a character is a location in this sense. +
+ + 053   +
+ + 054    ##### Inferences: +
+ + 055   +
+ + 056    If an agent learns that Adam has married Betty, they can infer that Betty has +
+ + 057    married Adam; if they learn that Charles killed Dorothy, that Dorothy has died. +
+ + 058    I'm not convinced that my representation of inferences here is ideal. +
+ + 059    " +
+ + 060    { ;; A significant attack is interesting whether or not it leads to deaths +
+ + 061      :attack {:verb :attack :keys [:actor :other :location]} +
+ + 062      ;; Deaths of characters may be interesting +
+ + 063      :die {:verb :attack :keys [:actor :location]} +
+ + 064      ;; Deliberate killings are interesting. +
+ + 065      :kill {:verb :kill :keys [:actor :other :location] +
+ + 066             :inferences [{:verb :die :actor :other :other :nil}]} +
+ + 067      ;; Marriages may be interesting +
+ + 068      :marry {:verb :marry :keys [:actor :other :location] +
+ + 069              :inferences [{:verb :marry :actor :other :other :actor}]} +
+ + 070      ;; The end of ongoing open conflict between to characters may be interesting +
+ + 071      :peace {:verb :peace :keys [:actor :other :location] +
+ + 072              :inferences [{:verb :peace :actor :other :other :actor}]} +
+ + 073      ;; Things related to the plot are interesting, but will require special +
+ + 074      ;; handling. Extra keys may be required by particular plot events. +
+ + 075      :plot {:verb :plot :keys [:actor :other :object :location]} +
+ + 076      ;; Rapes are interesting. +
+ + 077      :rape {:verb :rape :keys [:actor :other :location] +
+ + 078             ;; Should you also infer from rape that actor is male and adult? +
+ + 079             :inferences [{:verb :attack} +
+ + 080                          {:verb :sex} +
+ + 081                          {:verb :sex :actor :other :other :actor}]} +
+ + 082      ;; Merchants, especially, are interested in prices in other markets +
+ + 083      :sell {:verb :sell :keys [:actor :other :object :location :price]} +
+ + 084      ;; Sex can juicy gossip, although not normally if the participants are in an +
+ + 085      ;; established sexual relationship. +
+ + 086      :sex {:verb :sex :keys [:actor :other :location] +
+ + 087            :inferences [{:verb :sex :actor :other :other :actor}]} +
+ + 088      ;; Thefts are interesting +
+ + 089      :steal {:verb :steal :keys [:actor :other :object :location]} +
+ + 090      ;; The succession of rulers is interesting; of respected craftsmen, +
+ + 091      ;; potentially also interesting. +
+ + 092      :succession {:verb :succession :keys [:actor :other :location :rank]} +
+ + 093      ;; The start of ongoing open conflict between to characters may be interesting +
+ + 094      :war {:verb :war :keys [:actor :other :location] +
+ + 095            :inferences [{:verb :war :actor :other :other :actor}]} +
+ + 096      }) +
+ + 097   +
+ + 098   +
+ + 099  (defn interest-in-character +
+ + 100    "Integer representation of how interesting this `character` is to this +
+ + 101    `gossip`. +
+ + 102    *TODO:* this assumes that characters are passed as keywords, but, as +
+ + 103    documented above, they probably have to be maps, to allow for degradation." +
+ + 104    [gossip character] +
+ + 105    (count +
+ + 106      (concat +
+ + 107        (filter #(= (:actor % character)) (:knowledge gossip)) +
+ + 108        (filter #(= (:other % character)) (:knowledge gossip))))) +
+ + 109   +
+ + 110  (defn interesting-character? +
+ + 111    "Boolean representation of whether this `character` is interesting to this +
+ + 112    `gossip`." +
+ + 113    [gossip character] +
+ + 114    (> (interest-in-character gossip character) 0)) +
+ + 115   +
+ + 116  (defn interest-in-location +
+ + 117    "Integer representation of how interesting this `location` is to this +
+ + 118    `gossip`." +
+ + 119    [gossip location] +
+ + 120    (cond +
+ + 121      (and (map? location) (number? (:x location)) (number? (:y location))) +
+ + 122      (if-let [home (:home gossip)] +
+ + 123        (let [d (distance-between location home) +
+ + 124              i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should +
+ + 125              ;;fall of with distance from home, but possibly on a log scale +
+ + 126              ] +
+ + 127          (if (> i 1) i 0)) +
+ + 128        0) +
+ + 129      (coll? location) +
+ + 130      (reduce +
+ + 131        + +
+ + 132        (map +
+ + 133          #(interest-in-location gossip %) +
+ + 134          location)) +
+ + 135      :else +
+ + 136      (count +
+ + 137        (filter +
+ + 138          #(some (fn [x] (= x location)) (:location %)) +
+ + 139          (:knowledge gossip))))) +
+ + 140   +
+ + 141  (defn interesting-location? +
+ + 142    "True if the location of this news `item` is interesting to this `gossip`." +
+ + 143    [gossip item] +
+ + 144    (> (interest-in-location gossip (:location item)) 1)) +
+ + 145   +
+ + 146  (defn interesting-object? +
+ + 147    [gossip object] +
+ + 148    ;; TODO: Not yet (really) implemented +
+ + 149    true) +
+ + 150   +
+ + 151  (defn interesting-topic? +
+ + 152    [gossip topic] +
+ + 153    ;; TODO: Not yet (really) implemented +
+ + 154    true) +
+ + 155   +
+ + 156  (defn interesting-item? +
+ + 157    "True if anything about this news `item` is interesting to this `gossip`." +
+ + 158    [gossip item] +
+ + 159       (or +
+ + 160         (interesting-character? gossip (:actor item)) +
+ + 161         (interesting-character? gossip (:other item)) +
+ + 162         (interesting-location? gossip (:location item)) +
+ + 163         (interesting-object? gossip (:object item)) +
+ + 164         (interesting-topic? gossip (:verb item)))) +
+ + 165   +
+ + 166  (defn infer +
+ + 167    "Infer a new knowledge item from this `item`, following this `rule`" +
+ + 168    [item rule] +
+ + 169    (reduce merge +
+ + 170            item +
+ + 171            (cons +
+ + 172              {:verb (:verb rule)} +
+ + 173              (map (fn [k] {k (apply (k rule) (list item))}) +
+ + 174                   (remove +
+ + 175                     #(= % :verb) +
+ + 176                     (keys rule)))))) +
+ + 177   +
+ + 178  (declare learn-news-item) +
+ + 179   +
+ + 180  (defn make-all-inferences +
+ + 181    "Return a list of knowledge entries inferred from this news `item` by this +
+ + 182    `gossip`." +
+ + 183    [item] +
+ + 184    (set +
+ + 185      (reduce +
+ + 186        concat +
+ + 187        (map +
+ + 188          #(:knowledge (learn-news-item {} (infer item %) false)) +
+ + 189          (:inferences (news-topics (:verb item))))))) +
+ + 190   +
+ + 191  (defn degrade-character +
+ + 192    "Return a character specification like this `character`, but comprising +
+ + 193    only those properties this `gossip` is interested in." +
+ + 194    [gossip character] +
+ + 195    ;; TODO: Not yet (really) implemented +
+ + 196    character) +
+ + 197   +
+ + 198  (defn degrade-location +
+ + 199    "Return a location specification like this `location`, but comprising +
+ + 200    only those elements this `gossip` is interested in. If none, return +
+ + 201    `nil`." +
+ + 202    [gossip location] +
+ + 203    (let [l (if +
+ + 204      (coll? location) +
+ + 205      (filter +
+ + 206        #(when (interesting-location? gossip %) %) +
+ + 207        location))] +
+ + 208      (when-not (empty? l) l))) +
+ + 209   +
+ + 210  (defn learn-news-item +
+ + 211    "Return a gossip like this `gossip`, which has learned this news `item` if +
+ + 212    it is of interest to them." +
+ + 213    ;; TODO: Not yet implemented +
+ + 214    ([gossip item] +
+ + 215     (learn-news-item gossip item true)) +
+ + 216    ([gossip item follow-inferences?] +
+ + 217     (if +
+ + 218       (interesting-item? gossip item) +
+ + 219       (let [g (assoc gossip :knowledge +
+ + 220                 (cons +
+ + 221                   (assoc +
+ + 222                     item +
+ + 223                     :nth-hand (if +
+ + 224                                 (number? (:nth-hand item)) +
+ + 225                                 (inc (:nth-hand item)) +
+ + 226                                 1) +
+ + 227                     :date (if (number? (:date item)) (:date item) (now)) +
+ + 228                     :location (degrade-location gossip (:location item)) +
+ + 229                     ;; ought to degratde the location +
+ + 230                     ;; ought to maybe-degrade characters we're not yet interested in +
+ + 231                     ) +
+ + 232                   ;; ought not to add knowledge items we already have, except +
+ + 233                   ;; to replace if new item is of increased specificity +
+ + 234                   (:knowledge gossip)))] +
+ + 235         (if follow-inferences? +
+ + 236           (assoc +
+ + 237             g +
+ + 238             :knowledge +
+ + 239             (concat (:knowledge g) (make-all-inferences item))) +
+ + 240           g)) +
+ + 241       gossip))) +
+ + 242   +
+ + 243   +
+ + 244   +
+ + diff --git a/docs/cloverage/the_great_game/merchants/markets.clj.html b/docs/cloverage/the_great_game/merchants/markets.clj.html index 618f25f..46dc44a 100644 --- a/docs/cloverage/the_great_game/merchants/markets.clj.html +++ b/docs/cloverage/the_great_game/merchants/markets.clj.html @@ -79,22 +79,22 @@ 025          id (:id c)
- + 026          p (or (-> c :prices commodity) 0)
027          d (or (-> c :demands commodity) 0)
- + 028          st (or (-> c :stock commodity) 0)
- + 029          su (or (-> c :supplies commodity) 0)
030          decrement (min st d)
- + 031          increment (cond
@@ -190,46 +190,46 @@ 062    ([world]
- + 063     (reduce
- + 064       deep-merge
- + 065       world
- + 066       (map
- + 067         #(update-markets world %)
- + 068         (keys (:cities world)))))
069    ([world city]
- + 070     (reduce
- + 071       deep-merge
- + 072       {}
- + 073       (map #(update-markets world city %)
- + 074            (keys (:commodities world)))))
075    ([world city commodity]
- + 076      (adjust-quantity-and-price world city commodity)))
@@ -250,7 +250,7 @@ 082    [world]
- + 083    (update-markets world))
diff --git a/docs/cloverage/the_great_game/time.clj.html b/docs/cloverage/the_great_game/time.clj.html new file mode 100644 index 0000000..8822abe --- /dev/null +++ b/docs/cloverage/the_great_game/time.clj.html @@ -0,0 +1,440 @@ + + + + the_great_game/time.clj + + + + 001  (ns the-great-game.time +
+ + 002    (:require [clojure.string :as s])) +
+ + 003   +
+ + 004  (def game-start-time +
+ + 005    "The start time of this run." +
+ + 006    (System/currentTimeMillis)) +
+ + 007   +
+ + 008  (def ^:const game-day-length +
+ + 009    "The Java clock advances in milliseconds, which is fine. +
+ + 010    But we need game-days to be shorter than real world days. +
+ + 011    A Witcher 3 game day is 1 hour 36 minutes, or 96 minutes, which is +
+ + 012    presumably researched. Round it up to 100 minutes for easier +
+ + 013    calculation." +
+ + 014    (* 100          ;; minutes per game day +
+ + 015       60           ;; seconds per minute +
+ + 016       1000))       ;; milliseconds per second +
+ + 017   +
+ + 018  (defn now +
+ + 019    "For now, we'll use Java timestamp for time; ultimately, we need a +
+ + 020    concept of game-time which allows us to drive day/night cycle, seasons, +
+ + 021    et cetera, but what matters about time is that it is a value which +
+ + 022    increases." +
+ + 023    [] +
+ + 024    (System/currentTimeMillis)) +
+ + 025   +
+ + 026  (def ^:const canonical-ordering-of-houses +
+ + 027    "The canonical ordering of religious houses." +
+ + 028    [:eye +
+ + 029     :foot +
+ + 030     :nose +
+ + 031     :hand +
+ + 032     :ear +
+ + 033     :mouth +
+ + 034     :stomach +
+ + 035     :furrow +
+ + 036     :plough]) +
+ + 037   +
+ + 038  (def ^:const days-of-week +
+ + 039    "The eight-day week of the game world. This differs from the canonical +
+ + 040    ordering of houses in that it omits the eye." +
+ + 041    (rest canonical-ordering-of-houses)) +
+ + 042   +
+ + 043  (def ^:const days-in-week +
+ + 044    "This world has an eight day week." +
+ + 045    (count days-of-week)) +
+ + 046   +
+ + 047  (def ^:const seasons-of-year +
+ + 048    "The ordering of seasons in the year is different from the canonical +
+ + 049    ordering of the houses, for reasons of the agricultural cycle." +
+ + 050    [:foot +
+ + 051     :nose +
+ + 052     :hand +
+ + 053     :ear +
+ + 054     :mouth +
+ + 055     :stomach +
+ + 056     :plough +
+ + 057     :furrow +
+ + 058     :eye]) +
+ + 059   +
+ + 060  (def ^:const seasons-in-year +
+ + 061    "Nine seasons in a year, one for each house (although the order is +
+ + 062    different." +
+ + 063    (count seasons-of-year)) +
+ + 064   +
+ + 065  (def ^:const weeks-of-season +
+ + 066    "To fit nine seasons of eight day weeks into 365 days, each must be of +
+ + 067    five weeks." +
+ + 068    [:first :second :third :fourth :fifth]) +
+ + 069   +
+ + 070  (def ^:const weeks-in-season +
+ + 071    "To fit nine seasons of eight day weeks into 365 days, each must be of +
+ + 072    five weeks." +
+ + 073    (count weeks-of-season)) +
+ + 074   +
+ + 075  (def ^:const days-in-season +
+ + 076    (* weeks-in-season days-in-week)) +
+ + 077   +
+ + 078  (defn game-time +
+ + 079    "With no arguments, the current game time. If a Java `timestamp` value is +
+ + 080    passed (as a `long`), the game time represented by that value." +
+ + 081    ([] (game-time (now))) +
+ + 082    ([timestamp] +
+ + 083     (- timestamp game-start-time))) +
+ + 084   +
+ + 085  (defmacro day-of-year +
+ + 086    "The day of the year represented by this `game-time`, ignoring leap years." +
+ + 087    [game-time] +
+ + 088    `(mod (long (/ ~game-time game-day-length)) 365)) +
+ + 089   +
+ + 090  (def waiting-day? +
+ + 091    "Does this `game-time` represent a waiting day?" +
+ + 092    (memoize +
+ + 093      ;; we're likely to call this several times in quick succession on the +
+ + 094      ;; same timestamp +
+ + 095      (fn [game-time] +
+ + 096          (>= +
+ + 097            (day-of-year game-time) +
+ + 098            (* seasons-in-year weeks-in-season days-in-week))))) +
+ + 099   +
+ + 100  (defn day +
+ + 101    "Day of the eight-day week represented by this `game-time`." +
+ + 102    [game-time] +
+ + 103    (let [day-of-week (mod (day-of-year game-time) days-in-week)] +
+ + 104      (if (waiting-day? game-time) +
+ + 105        (nth weeks-of-season day-of-week) +
+ + 106        (nth days-of-week day-of-week)))) +
+ + 107   +
+ + 108  (defn week +
+ + 109    "Week of season represented by this `game-time`." +
+ + 110    [game-time] +
+ + 111    (let [day-of-season (mod (day-of-year game-time) days-in-season) +
+ + 112          week (/ day-of-season days-in-week)] +
+ + 113      (if (waiting-day? game-time) +
+ + 114        :waiting +
+ + 115        (nth weeks-of-season week)))) +
+ + 116   +
+ + 117  (defn season +
+ + 118    [game-time] +
+ + 119    (let [season (int (/ (day-of-year game-time) days-in-season))] +
+ + 120      (if (waiting-day? game-time) +
+ + 121        :waiting +
+ + 122        (nth seasons-of-year season)))) +
+ + 123   +
+ + 124  (defn date-string +
+ + 125    "Return a correctly formatted date for this `game-time` in the calendar of +
+ + 126    the Great Place." +
+ + 127    [game-time] +
+ + 128    (s/join +
+ + 129      " " +
+ + 130      (if +
+ + 131        (waiting-day? game-time) +
+ + 132        [(s/capitalize +
+ + 133           (name +
+ + 134             (nth +
+ + 135               weeks-of-season +
+ + 136               (mod (day-of-year game-time) days-in-week)))) +
+ + 137         "waiting day"] +
+ + 138        [(s/capitalize (name (week game-time))) +
+ + 139         (s/capitalize (name (day game-time))) +
+ + 140         "of the" +
+ + 141         (s/capitalize (name (season game-time)))]))) +
+ + 142   +
+ + 143   +
+ + 144   +
+ + diff --git a/docs/cloverage/the_great_game/world/location.clj.html b/docs/cloverage/the_great_game/world/location.clj.html new file mode 100644 index 0000000..195df85 --- /dev/null +++ b/docs/cloverage/the_great_game/world/location.clj.html @@ -0,0 +1,119 @@ + + + + the_great_game/world/location.clj + + + + 001  (ns the-great-game.world.location +
+ + 002    "Functions dealing with location in the world." +
+ + 003    (:require [clojure.math.numeric-tower :refer [expt sqrt]])) +
+ + 004   +
+ + 005  ;;   A 'location' value is a list comprising at most the x/y coordinate location +
+ + 006  ;;   and the ids of the settlement and region (possibly hierarchically) that contain +
+ + 007  ;;   the location. If the x/y is not local to the home of the receiving agent, they +
+ + 008  ;;   won't remember it and won't pass it on; if any of the ids are not interesting +
+ + 009  ;;   So location information will degrade progressively as the item is passed along. +
+ + 010   +
+ + 011  ;;   It is assumed that the `:home` of a character is a location in this sense. +
+ + 012   +
+ + 013  (defn get-coords +
+ + 014    "Return the coordinates in the game world of `location`, which may be +
+ + 015    1. A coordinate pair in the format {:x 5 :y 32}; +
+ + 016    2. A location, as discussed above; +
+ + 017    3. Any other gameworld object, having a `:location` property whose value +
+ + 018      is one of the above." +
+ + 019    [location] +
+ + 020    (cond +
+ + 021      (empty? location) nil +
+ + 022      (map? location) +
+ + 023      (cond +
+ + 024        (and (number? (:x location)) (number? (:y location))) +
+ + 025        location +
+ + 026        (:location location) +
+ + 027        (:location location)) +
+ + 028      :else +
+ + 029      (get-coords (first (remove keyword? location))))) +
+ + 030   +
+ + 031  (defn distance-between +
+ + 032    [location-1 location-2] +
+ + 033    (let [c1 (get-coords location-1) +
+ + 034          c2 (get-coords location-2)] +
+ + 035      (when +
+ + 036        (and c1 c2) +
+ + 037        (sqrt (+ (expt (- (:x c1) (:x c2)) 2) (expt (- (:y c1) (:y c2)) 2)))))) +
+ + diff --git a/docs/codox/economy.html b/docs/codox/economy.html index 8d85673..a232ad2 100644 --- a/docs/codox/economy.html +++ b/docs/codox/economy.html @@ -1,6 +1,6 @@ -Game world economy

Game world economy

+Game world economy

Game world economy

Broadly this essay extends ideas presented in Populating a game world, q.v.

Primary producers

Herdsfolk

diff --git a/docs/codox/index.html b/docs/codox/index.html index 0a34e6b..375d513 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -The-great-game 0.1.0

The-great-game 0.1.0

Released under the GNU General Public License,version 2.0 or (at your option) any later version

Prototype code towards the great game I've been writing about for ten years, and know I will never finish.

Installation

To install, add the following dependency to your project or build file:

[the-great-game "0.1.0"]

Topics

Namespaces

the-great-game.core

TODO: write docs

Public variables and functions:

the-great-game.gossip.gossip

Interchange of news events between agents agents

Public variables and functions:

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

Public variables and functions:

the-great-game.merchants.merchant-utils

Useful functions for doing low-level things with merchants.

the-great-game.merchants.merchants

Trade planning for merchants, primarily.

Public variables and functions:

the-great-game.merchants.planning

Trade planning for merchants, primarily. This follows a simple-minded generate-and-test strategy and currently generates plans for all possible routes from the current location. This may not scale. Also, routes do not currently have cost or risk associated with them.

the-great-game.merchants.strategies.simple

Default trading strategy for merchants.

Public variables and functions:

the-great-game.utils

TODO: write docs

Public variables and functions:

the-great-game.world.routes

Conceptual (plan level) routes, represented as tuples of location ids.

Public variables and functions:

the-great-game.world.run

Run the whole simulation

Public variables and functions:

the-great-game.world.world

Access to data about the world

Public variables and functions:

\ No newline at end of file +The-great-game 0.1.1-SNAPSHOT

The-great-game 0.1.1-SNAPSHOT

Released under the GNU General Public License,version 2.0 or (at your option) any later version

Prototype code towards the great game I've been writing about for ten years, and know I will never finish.

Installation

To install, add the following dependency to your project or build file:

[the-great-game "0.1.1-SNAPSHOT"]

Topics

Namespaces

the-great-game.agent.agent

Anything in the game world with agency

Public variables and functions:

    the-great-game.gossip.gossip

    Interchange of news events between gossip agents

    Public variables and functions:

    the-great-game.merchants.markets

    Adjusting quantities and prices in markets.

    Public variables and functions:

    the-great-game.merchants.merchant-utils

    Useful functions for doing low-level things with merchants.

    the-great-game.merchants.merchants

    Trade planning for merchants, primarily.

    Public variables and functions:

    the-great-game.merchants.planning

    Trade planning for merchants, primarily. This follows a simple-minded generate-and-test strategy and currently generates plans for all possible routes from the current location. This may not scale. Also, routes do not currently have cost or risk associated with them.

    the-great-game.merchants.strategies.simple

    Default trading strategy for merchants.

    Public variables and functions:

    the-great-game.utils

    TODO: write docs

    Public variables and functions:

    the-great-game.world.location

    Functions dealing with location in the world.

    Public variables and functions:

    the-great-game.world.routes

    Conceptual (plan level) routes, represented as tuples of location ids.

    Public variables and functions:

    the-great-game.world.run

    Run the whole simulation

    Public variables and functions:

    the-great-game.world.world

    Access to data about the world

    Public variables and functions:

    \ No newline at end of file diff --git a/docs/codox/intro.html b/docs/codox/intro.html index 32cce64..b17bd13 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -1,6 +1,6 @@ -Introduction to the-great-game

    Introduction to the-great-game

    +Introduction to the-great-game

    Introduction to the-great-game

    The Great Game

    In this essay I’m going to try to pull together a number of my architectural ideas about the Great Game which I know I’m never actually going to build - because it’s vastly too big for any one person to build - into one overall vision.

    So, firstly, how does one characterise this game?

    diff --git a/docs/codox/modelling_trading_cost_and_risk.html b/docs/codox/modelling_trading_cost_and_risk.html index 88dd04c..72b183e 100644 --- a/docs/codox/modelling_trading_cost_and_risk.html +++ b/docs/codox/modelling_trading_cost_and_risk.html @@ -1,6 +1,6 @@ -Modelling trading cost and risk

    Modelling trading cost and risk

    +Modelling trading cost and risk

    Modelling trading cost and risk

    In a dynamic pre-firearms world with many small states and contested regions, trade is not going to be straightforward. Not only will different routes have different physical characteristics - more or less mountainous, more or fewer unbridged river crossings - they will also have different political characteristics: more of less taxed, more or less effectively policed.

    Raids by outlaws are expected to be part of the game economy. News of raids are the sort of things which may propagate through the gossip system. So are changes in taxation regime. Obviously, knowledge items can affect merchants’ trading strategy; in existing prototype code, individual merchants already each keep their own cache of known historical prices, and exchange historical price data with one another; and use this price data to select trades to make.

    So: to what extent is it worth modelling the spread of knowledge of trade cost and risk?

    diff --git a/docs/codox/naming-of-characters.html b/docs/codox/naming-of-characters.html new file mode 100644 index 0000000..6d76b98 --- /dev/null +++ b/docs/codox/naming-of-characters.html @@ -0,0 +1,26 @@ + +Naming of Characters

    Naming of Characters

    +

    Generally speaking, in modern RPGs, every character with any impact on the plot has a distinct name. But if we are going to give all non-player characters sufficient agency to impact on the plot, then we must have a way of naming tens or hundreds of thousands of characters, and distinct names will become problematic (even if we’re procedurally generating names, which we shall have to do. So this note is about how characters are named.

    +

    The full name of each character will be made up as follows:

    +

    [epithet] [clan] [personal-name] the [trade-or-rank] of [location], son/daughter of [parent]

    +

    Based on, roughly, historical name patterns like

    +

    Archibald (personal-name) the Grim (epithet), Earl (trade-or-rank) of Douglas (location)

    +

    Where

    +
      +
    1. +

      epithet is a prefix based on some notable feature or feat of the character. Most characters won’t have an epithet, unless they have some notable feature or they’ve done something notable. If a character does something notable in the course of the game, they will subsequently gain an epithet; ‘notability’ may be measured by how many times the event is transmitted through the gossip network.

    2. +
    3. +

      clan is special to the Western Clans, although people from the Great Place may possible use the name of their house similarly.

    4. +
    5. +

      personal-name is chosen from one of a limited set of limited sets; different cultural groups will have different (possibly overlapping) sets of names, but within each set there will only be a limited subset

    6. +
    7. +

      trade-or-rank is just that. “Smith”, “Miller”, “Ariston”, “Captain”. Either only master craftsfolk have the trade-or-rank name of their craft, or we distinguish between ‘Calon the Smith’, who may be a journeyman, and ‘Calon the Master Smith’, who is a master.

    8. +
    9. +

      location is the name of a location; a village, town, city or province. The location which forms part of a character’s name is the location where there current home is, not the location where they were born or where their ancestors came from

    10. +
    +

    Full names will almost never be used - only, perhaps, in extremely formal circumstances. The form of a name used will depend on context, and will generally be just sufficient to disambiguate the character in the context.

    +

    If the speaker is in Sinhua and referring to someone from Sinhua, they won’t refer to them as ‘of Sinhua’.

    +

    If everyone present is a bargee and the speaker referring to someone who is also a bargee, they won’t refer to them as ‘the bargee’.

    +

    The question asked influences the context: in answer to the question ‘who is the best sword smith’, the answer will not be ‘Calon the Smith’ but ‘Calon of Sinhua’.

    +

    Patronymics/matronymics will not normally be used of adults (although they may be used of apprentices and journeymen.

    \ No newline at end of file diff --git a/docs/codox/orgnic-quests.html b/docs/codox/orgnic-quests.html new file mode 100644 index 0000000..ca6e4a8 --- /dev/null +++ b/docs/codox/orgnic-quests.html @@ -0,0 +1,34 @@ + +Organic Quests

    Organic Quests

    +

    The structure of a modern Role Playing Came revolves around ‘quests’: tasks that the player character is invited to do, either by the framing narrative of the game or by some non-player character (‘the Quest Giver’). Normally there is one core quest which provides the overarching narrative for the whole game. [Wikipedia](https://en.wikipedia.org/wiki/Quest_(gaming)) offers a typology of quests as follows:

    +
      +
    1. Kill quests
    2. +
    3. Combo quests
    4. +
    5. Delivery quests
    6. +
    7. Gather quests
    8. +
    9. Escort quests
    10. +
    11. Syntax quests
    12. +
    13. Hybrids
    14. +
    +

    ‘Gather quests’ are more frequently referred to in the literature as ‘fetch quests’, and ‘kill quests’ are simply a specialised form of fetch quest where the item to be fetched is a trophy of the kill. A delivery quest is a sort of reverse fetch quest: instead of going to some location or NPC and getting a specific item to return to the quest giver, the player is tasked to take a specific item from the quest giver to some location or NPC.

    +

    Hybrids are in effect chains of quests: do this task in order to get this precondition of this other task, in order to get the overall objective; obviously such chains can be deep and involved - the ‘main quest’ of every role playing game I know of is a chain or hybrid quest.

    +

    My understanding is that what Wikipedia means by a ‘syntax quest’ is what one would normally call a puzzle.

    +

    An escort quest is typically a request to take a specified non-player character safely through a dangerous area.

    +

    Combo quests are not, in my opinion, particularly relevant to the sorts of game we’re discussing here.

    +

    So essentially quests break down into three core types

    +
      +
    1. Fetch and deliver quests
    2. +
    3. Escort quests
    4. +
    5. Puzzles
    6. +
    +

    which are combined together into more or less complex chains, where the simplest chain is a single quest.

    +

    Given that quests are as simple as this, it’s obvious that narrative sophistication is required to make them interesting; and this point is clearly made by some variants of roguelike games which procedurally generate quests: they’re generally pretty dull. By contrast, the Witcher series is full of fetch-quests which are made to really matter by being wrapped in interesting character interaction and narrative plausibility. Very often this takes the form of tragedy: as one reviewer pointed out, the missing relatives that Geralt is asked to find generally turn out to be (horribly) dead. In other words, creative scripting tends to deliver much more narratively satisfying quests than is usually delivered by procedural generation.

    +

    But, if we’re thinking of a game with much more intelligent non-player characters with much more conversational repertoir, as I am, can satisfying quests emerge organically? In space trading games such as Elite, a primary activity is moving goods from markets with surplus (and thus low prices) to markets with shortage (and thus high prices). This is, in effect, a game made up of deliver quests - but rather than deliver quests which are scripted, they are deliver quests which arise organically out of the structure of the game world.

    +

    I already have working code for non-player character merchants, who move goods from city to city based on market information available to them. For player characters to join in this trading is an organic activity emerging from the structure of the world, which provides an activity. But moving merchants provides a market opportunity for bandits, who can intercept and steal cargoes, and so for mercenaries, who can protect cargoes from bandits, and so on. And because I have an architecture that allows non-player characters to fill economic niches, there will be non-player characters in all these niches.

    +

    Where a non-player character can act, so can a player character: when a (non-player character) merchant seeks to hire a caravan guard and a player character responds, that’s an organic escort quest.

    +

    The key idea behind organic quests is that the circumstance and requirments for quests emerges as an emergent behaviour out of the mechanics of the game world. A non-player character doesn’t know that there is a player character who is different from them; rather, when a non-player character needs something they can’t readily achieve for themselves, they will ask other characters to help, and that may include the player character.

    +

    This means, of course, that characters need a goal-seeking planning algorithm to decide their actions, with one option in any plan being ‘ask for help’. Thus, ‘asking for help’ becomes a mechanism within the game, a normal behaviour. Ideally non-player characters will keep track of quite complex webs of loyalty and of obligation - debts of honour, duties of hospitality, collective loyalties. So that, if you do a favour for some character in the world, that character’s tribe, friends, obligation circle, whatever, are now more likely to do favours for you.

    +

    Obviously, this doesn’t stop you doing jobs you get directly paid/rewarded for, but I’d like the web of obligation to be at least potentially much richer than just tit for tat.

    +

    Related to this notion is the notion that, if you are asked to do a task by a character and you do it well, whether for pay or as a favour, your reputation for being competent in tasks of that kind will improve and the more likely it is that other characters will ask you to do similar tasks; and this will apply to virtually anything another character can ask of you in the game world, from carrying out an assassination to delivering a message to finding a quantiy of some specific commodity to having sex.

    +

    So quests can emerge organically from the mechanics of the world and be richly varied; I’m confident that will work. What I’m not confident of is that they can be narratively satisfying. This relates directly to the generation of speech.

    \ No newline at end of file diff --git a/docs/codox/sandbox.html b/docs/codox/sandbox.html new file mode 100644 index 0000000..fefc173 --- /dev/null +++ b/docs/codox/sandbox.html @@ -0,0 +1,39 @@ + +Sandbox

    Sandbox

    +

    Up to now I’ve been thinking of the Great Game as essentially an RPG with some sandbox-like elements; but I think it may be better to think of it as a sandbox game with some RPG like elements.

    +

    Why?

    +

    The core of the game is a world in which non-player characters have enough individual knowledge of the world and their immediate surroundings that they can sensibly answer questions like

    +
      +
    • Where is the nearest craftsman of this craft?
    • +
    • What price can I expect to get for this item in the local market?
    • +
    • What news have you heard recently?
    • +
    • Where does this person from your village live?
    • +
    +

    and where there’s a sufficiently sophisticated and robust economy simulation that buying goods in one market and selling them in another is viable.

    +

    The original BBC Micro space trading game Elite had very little more in terms of game mechanics than a sandbox with a means to navigate it and an economy simulation, which wasn’t even nearly as sophisticated as the one I have working now. Yet that combination resulted in engaging game play.

    +

    Main sandbox roles

    +

    The idea of a sandbox is that the player character should be able to do pretty much anything they like within the mechanics of the game. From that, it seems to me reasonable that the player ought to be able to do more or less everything a non-player character can do. But creating the game mechanics to make each additional task doable takes time and investment, so there’s a need to prioritise.

    +

    So, as Elite did, I propose to make the first available sandbox roles

    +

    Merchant

    +

    Someone who travels from city to city, buying goods cheap in one and selling them for more in another; and

    +

    Outlaw

    +

    Someone who intercepts and steals from merchants (and may also attack outlying farms and villages)

    +

    Second tier playable roles

    +

    The next tier of playable roles rotates around issues arising from the mercantile ecosystem.

    +

    Aristocracy

    +

    Aristocrats are basically settled outlaws who seek to establish a monopoly on extracting taxes from inhabitants and travellers in a particular region by driving out all other outlaws. Within the comain of an aristocrat, you have to pay tax but you’re reasonably safe from being attacked by other outlaws and losing everything. Aristocrats may also maintain and improve roads and bridges and do other things to boost the economy of their territory, may expant into adjoining territory with no current aristocratic control, and may wage war on other aristocrats.

    +

    An outlaw ought to be able to become an aristocrat, by dominating an ungoverned area or by defeating an existing aristocrat.

    +

    Soldiery

    +

    Soldiers, like aristocrats, are basically on the same spectrum as outlaws. Outlaws may hire themselves out to merchants as caravan guards, or to aristocrats as soldiers. Soldiers or guards, falling on bad times, may revert to outlawry.

    +

    Routine, Discretion and Playability

    +

    There’s a term that’s used in criticism of many computer games which is worth thinking about hard here: that term is ‘farming’. ‘Farming’, in this sense, is doing something repetitive and dull to earn credits in a game. Generally this is not fun. What makes roles in a game-world fun is having individual discretion - the ability to choose between actions and strategies - and a lack of routine.

    +

    Most craft skills - especially in the learning phase - are not like this, and crafts which are sophisticated enough to be actually engaging are very hard to model in a game. Learning a craft is essentially, inherently, repetitive and dull, and if you take that repetition out of it you probably don’t have enough left to yield the feeling of mastery which would reward success; so it doesn’t seem to me that making craft roles playable should be a priority.

    +

    Cruise control

    +

    One of the most enjoyable aspects of The Witcher 3 - still my go-to game for ideas I want to improve on - is simply travelling through the world. Although fast travel is possible I find I rarely use it, and a journey which takes fifteen minutes of real world wall clock time can be enjoyable in and of itself. This is, of course, a credit to the beautiful way the world is realised.

    +

    But nevertheless, in The Witcher 3, a decision was made to pack incident fairly densely - because players would find just travelling boring. This leads to a situation where peaceful villages exist two minutes travel from dangerous monsters or bandit camps, and the suspension of disbelief gets a little strained. Building a world big enough that a market simulation is believable means that for the individual, the travel time to a market where a particular desired good is likely to be cheaper becomes costly in itself. Otherwise, there’s no arbitrage between markets and no ecological niche for a merchant to fill. The journey time from market to market has to be several in-game days.

    +

    An in-game day doesn’t have to be as long as a wall clock day, and, indeed, typically isn’t. But nevertheless, doing several game days of incident-free travel, even in beautiful scenery, is not going to be engaging - which implies a fast-travel mechanic.

    +

    I don’t like fast travel, I find it a too-obvious breaking of immersion. Also, of course, one of the interesting things about a game in a merchant/outlaw ecosystem is the risk of interception on a journey. The Dragon Age series handled interrupted travel in ‘fast travel’ by randomly interacting the loading screen you get when moving from location to location in Dragon Age’s patchwork worlds by dumping you into a tiny arena with enemies. That’s really, really bad - there’s no other way to say this. Everything about it shouts artifice.

    +

    So I’m thinking of a different mechanism: one I’m calling cruise control.

    +

    You set out on a task which will take a long time - such as a journey, but also such as any routine task. You’re shown either a ‘fast forward’ of your character carrying out this task, or a series of cinematic ‘shots along the way’. This depends, of course, on their being continuous renderable landscape between your departure and your destination, but there will be. This fast-forward proceeds at a substantially higher time gearing than normal game time - ten times as fast perhaps; we need it to, because as well as doing backgound scenery loading to move from one location to another, we’re also simulating lots of non-player agents actions in parts of the world where the player currently isn’t. So a ‘jump cut’ from one location to another isn’t going to work anyway.

    +

    The player can interrupt ‘fast forward’ at any time. But also, the game itself may bring you out of fast forward when it anticipates that there may be action which requires decision - for example, when there are outlaws in the vicinity. And it will do this before the player’s party is under immediate attack - the player will have time to take stock of the situation and prepare appropriately. Finally, this will take place in the full open world; the player will have the option to choose not to enter the narrow defile, for example, to ask local people (if there are any) for any news of outlaw activity, or, if they are available, to send forward scouts.

    \ No newline at end of file diff --git a/docs/codox/sexual-dimorphism.html b/docs/codox/sexual-dimorphism.html index 8cf4c50..3cb1205 100644 --- a/docs/codox/sexual-dimorphism.html +++ b/docs/codox/sexual-dimorphism.html @@ -1,6 +1,6 @@ -Sexual dimorphism

    Sexual dimorphism

    +Sexual dimorphism

    Sexual dimorphism

    This essay is going to upset a lot of people, so let’s start with a statement of what it is about: it is an attempt to describe the systematically different behaviours of men and women, in sufficient detail that this can be represented by agents in a game world. It’s trying to allow as broad as possible a range of cultures to be represented, so when I’m talking about what I consider to be behaviours of particular cultures, I’ll say that.

    Of course, I’m writing this from the view point of an old white male. It’s not possible to write about these things from a totally neutral viewpoint, and every one of us will have prejudices.

    OK? Let’s start.

    diff --git a/docs/codox/the-great-game.agent.agent.html b/docs/codox/the-great-game.agent.agent.html new file mode 100644 index 0000000..02353e9 --- /dev/null +++ b/docs/codox/the-great-game.agent.agent.html @@ -0,0 +1,3 @@ + +the-great-game.agent.agent documentation

    the-great-game.agent.agent

    Anything in the game world with agency

    \ No newline at end of file diff --git a/docs/codox/the-great-game.gossip.gossip.html b/docs/codox/the-great-game.gossip.gossip.html index 3e8cf5c..559aa1c 100644 --- a/docs/codox/the-great-game.gossip.gossip.html +++ b/docs/codox/the-great-game.gossip.gossip.html @@ -1,3 +1,3 @@ -the-great-game.gossip.gossip documentation

    the-great-game.gossip.gossip

    Interchange of news events between agents agents

    dialogue

    (dialogue enquirer respondent world)

    Dialogue between an enquirer and an agent in this world; returns a map identical to enquirer except that its :gossip collection may have additional entries.

    gather-news

    (gather-news world)(gather-news world gossip)

    TODO: write docs

    move-gossip

    (move-gossip gossip world new-location)

    Return a world like this world but with this gossip moved to this new-location. Many gossips are essentially shadow-records of agents of other types, and the movement if the gossip should be controlled by the run function of the type of the record they shadow. The #run function below does NOT call this function.

    run

    (run world)

    Return a world like this world, with news items exchanged between gossip agents.

    \ No newline at end of file +the-great-game.gossip.gossip documentation

    the-great-game.gossip.gossip

    Interchange of news events between gossip agents

    dialogue

    (dialogue enquirer respondent world)

    Dialogue between an enquirer and an agent in this world; returns a map identical to enquirer except that its :gossip collection may have additional entries.

    gather-news

    (gather-news world)(gather-news world gossip)

    TODO: write docs

    move-gossip

    (move-gossip gossip world new-location)

    Return a world like this world but with this gossip moved to this new-location. Many gossips are essentially shadow-records of agents of other types, and the movement of the gossip should be controlled by the run function of the type of the record they shadow. The #run function below does NOT call this function.

    run

    (run world)

    Return a world like this world, with news items exchanged between gossip agents.

    \ No newline at end of file diff --git a/docs/codox/the-great-game.gossip.news-items.html b/docs/codox/the-great-game.gossip.news-items.html new file mode 100644 index 0000000..e145054 --- /dev/null +++ b/docs/codox/the-great-game.gossip.news-items.html @@ -0,0 +1,18 @@ + +the-great-game.gossip.news-items documentation

    the-great-game.gossip.news-items

    Categories of news events interesting to gossip agents

    degrade-character

    (degrade-character gossip character)

    Return a character specification like this character, but comprising only those properties this gossip is interested in.

    degrade-location

    (degrade-location gossip location)

    Return a location specification like this location, but comprising only those elements this gossip is interested in. If none, return nil.

    infer

    (infer item rule)

    Infer a new knowledge item from this item, following this rule

    interest-in-character

    (interest-in-character gossip character)

    Integer representation of how interesting this character is to this gossip. TODO: this assumes that characters are passed as keywords, but, as documented above, they probably have to be maps, to allow for degradation.

    interest-in-location

    (interest-in-location gossip location)

    Integer representation of how interesting this location is to this gossip.

    interesting-character?

    (interesting-character? gossip character)

    Boolean representation of whether this character is interesting to this gossip.

    interesting-item?

    (interesting-item? gossip item)

    True if anything about this news item is interesting to this gossip.

    interesting-location?

    (interesting-location? gossip item)

    True if the location of this news item is interesting to this gossip.

    interesting-object?

    (interesting-object? gossip object)

    TODO: write docs

    interesting-topic?

    (interesting-topic? gossip topic)

    TODO: write docs

    learn-news-item

    (learn-news-item gossip item)(learn-news-item gossip item follow-inferences?)

    Return a gossip like this gossip, which has learned this news item if it is of interest to them.

    make-all-inferences

    (make-all-inferences item)

    Return a list of knowledge entries inferred from this news item by this gossip.

    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 of information required to give context to a gossip item. Generally:

    +
      +
    • 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;
    • +
    • 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 and the ids of the settlement and region (possibly hierarchically) that contain the location. If the x/y is not local to the home of the receiving agent, they won’t remember it and won’t pass it on; if any of the ids are not interesting 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.

    +
    Inferences:
    +

    If an agent learns that Adam has married Betty, they can infer that Betty has married Adam; if they learn that Charles killed Dorothy, that Dorothy has died. I’m not convinced that my representation of inferences here is ideal.

    \ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.markets.html b/docs/codox/the-great-game.merchants.markets.html index 3c3d546..7d7e83e 100644 --- a/docs/codox/the-great-game.merchants.markets.html +++ b/docs/codox/the-great-game.merchants.markets.html @@ -1,3 +1,3 @@ -the-great-game.merchants.markets documentation

    the-great-game.merchants.markets

    Adjusting quantities and prices in markets.

    adjust-quantity-and-price

    (adjust-quantity-and-price world city commodity)

    Adjust the quantity of this commodity currently in stock in this city of this world. Return a fragmentary world which can be deep-merged into this world.

    new-price

    (new-price old stock supply demand)

    If stock is greater than the maximum of supply and demand, then there is surplus and old price is too high, so shold be reduced. If lower, then it is too low and should be increased.

    run

    (run world)

    Return a world like this world, with quantities and prices in markets updated to reflect supply and demand.

    update-markets

    (update-markets world)(update-markets world city)(update-markets world city commodity)

    Return a world like this world, with quantities and prices in markets updated to reflect supply and demand. If city or city and commodity are specified, return a fragmentary world with only the changes for that city (and commodity if specified) populated.

    \ No newline at end of file +the-great-game.merchants.markets documentation

    the-great-game.merchants.markets

    Adjusting quantities and prices in markets.

    adjust-quantity-and-price

    (adjust-quantity-and-price world city commodity)

    Adjust the quantity of this commodity currently in stock in this city of this world. Return a fragmentary world which can be deep-merged into this world.

    new-price

    (new-price old stock supply demand)

    If stock is greater than the maximum of supply and demand, then there is surplus and old price is too high, so shold be reduced. If lower, then it is too low and should be increased.

    run

    (run world)

    Return a world like this world, with quantities and prices in markets updated to reflect supply and demand.

    update-markets

    (update-markets world)(update-markets world city)(update-markets world city commodity)

    Return a world like this world, with quantities and prices in markets updated to reflect supply and demand. If city or city and commodity are specified, return a fragmentary world with only the changes for that city (and commodity if specified) populated.

    \ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.merchant-utils.html b/docs/codox/the-great-game.merchants.merchant-utils.html index b1b0b5f..58e0534 100644 --- a/docs/codox/the-great-game.merchants.merchant-utils.html +++ b/docs/codox/the-great-game.merchants.merchant-utils.html @@ -1,3 +1,3 @@ -the-great-game.merchants.merchant-utils documentation

    the-great-game.merchants.merchant-utils

    Useful functions for doing low-level things with merchants.

    add-known-prices

    (add-known-prices merchant world)

    Add the current prices at this merchant’s location in the world to a new cacke of known prices, and return it.

    add-stock

    (add-stock a b)

    Where a and b are both maps all of whose values are numbers, return a map whose keys are a union of the keys of a and b and whose values are the sums of their respective values.

    burden

    (burden merchant world)

    The total weight of the current cargo carried by this merchant in this world.

    can-afford

    (can-afford merchant world commodity)

    Return the number of units of this commodity which this merchant can afford to buy in this world.

    can-carry

    (can-carry merchant world commodity)

    Return the number of units of this commodity which this merchant can carry in this world, given their current burden.

    expected-price

    (expected-price merchant commodity city)

    Find the price anticipated, given this world, by this merchant for this commodity in this city. If no information, assume 1. merchant should be passed as a map, commodity and city should be passed as keywords.

    \ No newline at end of file +the-great-game.merchants.merchant-utils documentation

    the-great-game.merchants.merchant-utils

    Useful functions for doing low-level things with merchants.

    add-known-prices

    (add-known-prices merchant world)

    Add the current prices at this merchant’s location in the world to a new cache of known prices, and return it.

    add-stock

    (add-stock a b)

    Where a and b are both maps all of whose values are numbers, return a map whose keys are a union of the keys of a and b and whose values are the sums of their respective values.

    burden

    (burden merchant world)

    The total weight of the current cargo carried by this merchant in this world.

    can-afford

    (can-afford merchant world commodity)

    Return the number of units of this commodity which this merchant can afford to buy in this world.

    can-carry

    (can-carry merchant world commodity)

    Return the number of units of this commodity which this merchant can carry in this world, given their current burden.

    expected-price

    (expected-price merchant commodity city)

    Find the price anticipated, given this world, by this merchant for this commodity in this city. If no information, assume 1. merchant should be passed as a map, commodity and city should be passed as keywords.

    \ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.merchants.html b/docs/codox/the-great-game.merchants.merchants.html index 870c8d7..46e3dc1 100644 --- a/docs/codox/the-great-game.merchants.merchants.html +++ b/docs/codox/the-great-game.merchants.merchants.html @@ -1,3 +1,3 @@ -the-great-game.merchants.merchants documentation

    the-great-game.merchants.merchants

    Trade planning for merchants, primarily.

    run

    (run world)

    Return a partial world based on this world, but with each merchant moved.

    \ No newline at end of file +the-great-game.merchants.merchants documentation

    the-great-game.merchants.merchants

    Trade planning for merchants, primarily.

    run

    (run world)

    Return a partial world based on this world, but with each merchant moved.

    \ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.planning.html b/docs/codox/the-great-game.merchants.planning.html index 1274f66..2d17459 100644 --- a/docs/codox/the-great-game.merchants.planning.html +++ b/docs/codox/the-great-game.merchants.planning.html @@ -1,6 +1,6 @@ -the-great-game.merchants.planning documentation

    the-great-game.merchants.planning

    Trade planning for merchants, primarily. This follows a simple-minded generate-and-test strategy and currently generates plans for all possible routes from the current location. This may not scale. Also, routes do not currently have cost or risk associated with them.

    augment-plan

    (augment-plan merchant world plan)

    Augment this plan constructed in this world for this merchant with the :quantity of goods which should be bought and the :expected-profit of the trade.

    +the-great-game.merchants.planning documentation

    the-great-game.merchants.planning

    Trade planning for merchants, primarily. This follows a simple-minded generate-and-test strategy and currently generates plans for all possible routes from the current location. This may not scale. Also, routes do not currently have cost or risk associated with them.

    augment-plan

    (augment-plan merchant world plan)

    Augment this plan constructed in this world for this merchant with the :quantity of goods which should be bought and the :expected-profit of the trade.

    Returns the augmented plan.

    generate-trade-plans

    (generate-trade-plans merchant world commodity)

    Generate all possible trade plans for this merchant and this commodity in this world.

    Returned plans are maps with keys:

      diff --git a/docs/codox/the-great-game.merchants.strategies.simple.html b/docs/codox/the-great-game.merchants.strategies.simple.html index ca81e5b..d5ba1e3 100644 --- a/docs/codox/the-great-game.merchants.strategies.simple.html +++ b/docs/codox/the-great-game.merchants.strategies.simple.html @@ -1,4 +1,4 @@ -the-great-game.merchants.strategies.simple documentation

      the-great-game.merchants.strategies.simple

      Default trading strategy for merchants.

      +the-great-game.merchants.strategies.simple documentation

      the-great-game.merchants.strategies.simple

      Default trading strategy for merchants.

      The simple strategy buys a single product in the local market if there is one which can be traded profitably, trades it to the chosen target market, and sells it there. If there is no commodity locally which can be traded profitably, moves towards home with no cargo. If at home and no commodity can be traded profitably, does not move.

      move-merchant

      (move-merchant merchant world)

      Handle general en route movement of this merchant in this world; return a (partial or full) world like this world but in which the merchant may have been moved ot updated.

      plan-and-buy

      (plan-and-buy merchant world)

      Return a world like this world, in which this merchant has planned a new trade, and bought appropriate stock for it. If no profitable trade can be planned, the merchant is simply moved towards their home.

      re-plan

      (re-plan merchant world)

      Having failed to sell a cargo at current location, re-plan a route to sell the current cargo. Returns a revised world.

      sell-and-buy

      (sell-and-buy merchant world)

      Return a new world like this world, in which this merchant has sold their current stock in their current location, and planned a new trade, and bought appropriate stock for it.

      \ No newline at end of file diff --git a/docs/codox/the-great-game.time.html b/docs/codox/the-great-game.time.html new file mode 100644 index 0000000..76496a1 --- /dev/null +++ b/docs/codox/the-great-game.time.html @@ -0,0 +1,3 @@ + +the-great-game.time documentation

      the-great-game.time

      TODO: write docs

      canonical-ordering-of-houses

      The canonical ordering of religious houses.

      date-string

      (date-string game-time)

      Return a correctly formatted date for this game-time in the calendar of the Great Place.

      day

      (day game-time)

      Day of the eight-day week represented by this game-time.

      day-of-year

      macro

      (day-of-year game-time)

      The day of the year represented by this game-time, ignoring leap years.

      days-in-season

      TODO: write docs

      days-in-week

      This world has an eight day week.

      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.

      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.

      game-start-time

      The start time of this run.

      game-time

      (game-time)(game-time timestamp)

      With no arguments, the current game time. If a Java timestamp value is passed (as a long), the game time represented by that value.

      now

      (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.

      season

      (season game-time)

      TODO: write docs

      seasons-in-year

      Nine seasons in a year, one for each house (although the order is different.

      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.

      waiting-day?

      Does this game-time represent a waiting day?

      week

      (week game-time)

      Week of season represented by this game-time.

      weeks-in-season

      To fit nine seasons of eight day weeks into 365 days, each must be of five weeks.

      weeks-of-season

      To fit nine seasons of eight day weeks into 365 days, each must be of five weeks.

      \ No newline at end of file diff --git a/docs/codox/the-great-game.utils.html b/docs/codox/the-great-game.utils.html index b23284d..84eba13 100644 --- a/docs/codox/the-great-game.utils.html +++ b/docs/codox/the-great-game.utils.html @@ -1,3 +1,3 @@ -the-great-game.utils documentation

      the-great-game.utils

      TODO: write docs

      cyclic?

      (cyclic? route)

      True if two or more elements of route are identical

      deep-merge

      (deep-merge & maps)

      make-target-filter

      (make-target-filter targets)

      Construct a filter which, when applied to a list of maps, will pass those which match these targets, where each target is a tuple [key value].

      \ No newline at end of file +the-great-game.utils documentation

      the-great-game.utils

      TODO: write docs

      cyclic?

      (cyclic? route)

      True if two or more elements of route are identical

      deep-merge

      (deep-merge & maps)

      make-target-filter

      (make-target-filter targets)

      Construct a filter which, when applied to a list of maps, will pass those which match these targets, where each target is a tuple [key value].

      \ No newline at end of file diff --git a/docs/codox/the-great-game.world.location.html b/docs/codox/the-great-game.world.location.html new file mode 100644 index 0000000..e3836dc --- /dev/null +++ b/docs/codox/the-great-game.world.location.html @@ -0,0 +1,3 @@ + +the-great-game.world.location documentation

      the-great-game.world.location

      Functions dealing with location in the world.

      distance-between

      (distance-between location-1 location-2)

      TODO: write docs

      get-coords

      (get-coords location)

      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.

      \ No newline at end of file diff --git a/docs/codox/the-great-game.world.routes.html b/docs/codox/the-great-game.world.routes.html index 4694894..f5a5455 100644 --- a/docs/codox/the-great-game.world.routes.html +++ b/docs/codox/the-great-game.world.routes.html @@ -1,3 +1,3 @@ -the-great-game.world.routes documentation

      the-great-game.world.routes

      Conceptual (plan level) routes, represented as tuples of location ids.

      find-route

      (find-route world-or-routes from to)

      Find a single route from from to to in this world-or-routes, which may be either a world as defined in the-great-game.world.world or else a sequence of tuples of keywords.

      find-routes

      (find-routes routes from)(find-routes routes from to)(find-routes routes from to steps)

      Find routes from among these routes from from; if to is supplied, to to, by breadth-first search.

      \ No newline at end of file +the-great-game.world.routes documentation

      the-great-game.world.routes

      Conceptual (plan level) routes, represented as tuples of location ids.

      find-route

      (find-route world-or-routes from to)

      Find a single route from from to to in this world-or-routes, which may be either a world as defined in the-great-game.world.world or else a sequence of tuples of keywords.

      find-routes

      (find-routes routes from)(find-routes routes from to)(find-routes routes from to steps)

      Find routes from among these routes from from; if to is supplied, to to, by breadth-first search.

      \ No newline at end of file diff --git a/docs/codox/the-great-game.world.run.html b/docs/codox/the-great-game.world.run.html index 1d3b216..0aca065 100644 --- a/docs/codox/the-great-game.world.run.html +++ b/docs/codox/the-great-game.world.run.html @@ -1,3 +1,3 @@ -the-great-game.world.run documentation

      the-great-game.world.run

      Run the whole simulation

      init

      (init)(init config)

      TODO: write docs

      run

      (run world)(run world date)

      The pipeline to run the simulation each game day. Returns a world like this world, with all the various active elements updated. The optional date argument, if supplied, is set as the :date of the returned world.

      \ No newline at end of file +the-great-game.world.run documentation

      the-great-game.world.run

      Run the whole simulation

      init

      (init)(init config)

      TODO: write docs

      run

      (run world)(run world date)

      The pipeline to run the simulation each game day. Returns a world like this world, with all the various active elements updated. The optional date argument, if supplied, is set as the :date of the returned world.

      \ No newline at end of file diff --git a/docs/codox/the-great-game.world.world.html b/docs/codox/the-great-game.world.world.html index fcec6c3..c417377 100644 --- a/docs/codox/the-great-game.world.world.html +++ b/docs/codox/the-great-game.world.world.html @@ -1,3 +1,3 @@ -the-great-game.world.world documentation

      the-great-game.world.world

      Access to data about the world

      actual-price

      (actual-price world commodity city)

      Find the actual current price of this commodity in this city given this world. NOTE that merchants can only know the actual prices in the city in which they are currently located.

      default-world

      A basic world for testing concepts

      run

      (run world)(run world date)

      Return a world like this world with only the :date to this date (or id date not supplied, the current value incremented by one). For running other aspects of the simulation, see the-great-game.world.run.

      \ No newline at end of file +the-great-game.world.world documentation

      the-great-game.world.world

      Access to data about the world

      actual-price

      (actual-price world commodity city)

      Find the actual current price of this commodity in this city given this world. NOTE that merchants can only know the actual prices in the city in which they are currently located.

      default-world

      A basic world for testing concepts

      run

      (run world)(run world date)

      Return a world like this world with only the :date to this date (or id date not supplied, the current value incremented by one). For running other aspects of the simulation, see the-great-game.world.run.

      \ No newline at end of file diff --git a/src/the_great_game/agent/agent.clj b/src/the_great_game/agent/agent.clj index b9232ef..7a17103 100644 --- a/src/the_great_game/agent/agent.clj +++ b/src/the_great_game/agent/agent.clj @@ -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. + diff --git a/src/the_great_game/gossip/gossip.clj b/src/the_great_game/gossip/gossip.clj index 13aa961..af743f3 100644 --- a/src/the_great_game/gossip/gossip.clj +++ b/src/the_great_game/gossip/gossip.clj @@ -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 diff --git a/src/the_great_game/gossip/news_items.clj b/src/the_great_game/gossip/news_items.clj index bcb0398..4591f3a 100644 --- a/src/the_great_game/gossip/news_items.clj +++ b/src/the_great_game/gossip/news_items.clj @@ -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))) + + diff --git a/src/the_great_game/time.clj b/src/the_great_game/time.clj new file mode 100644 index 0000000..2378937 --- /dev/null +++ b/src/the_great_game/time.clj @@ -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)))]))) + + + diff --git a/src/the_great_game/world/location.clj b/src/the_great_game/world/location.clj index 6709f30..b7c3fd0 100644 --- a/src/the_great_game/world/location.clj +++ b/src/the_great_game/world/location.clj @@ -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)))))) diff --git a/test/the_great_game/gossip/gossip_test.clj b/test/the_great_game/gossip/gossip_test.clj new file mode 100644 index 0000000..176fab4 --- /dev/null +++ b/test/the_great_game/gossip/gossip_test.clj @@ -0,0 +1,4 @@ +(ns the-great-game.gossip.gossip-test + (:require [clojure.test :refer :all] + [the-great-game.gossip.gossip :refer :all])) + diff --git a/test/the_great_game/gossip/news_items_test.clj b/test/the_great_game/gossip/news_items_test.clj new file mode 100644 index 0000000..908e330 --- /dev/null +++ b/test/the_great_game/gossip/news_items_test.clj @@ -0,0 +1,132 @@ +(ns the-great-game.gossip.news-items-test + (:require [clojure.test :refer :all] + [the-great-game.gossip.news-items :refer :all])) + + +(deftest location-test + (testing "Interest in locations" + (let [expected 1 + actual (interest-in-location + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :location [{:x 35 :y 23} :auchencairn :galloway]}]} + :galloway)] + (is (= actual expected))) + (let [expected 2 + actual (interest-in-location + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :location [{:x 35 :y 23} :auchencairn :galloway :scotland]}]} + [:galloway :scotland])] + (is (= actual expected))) + (let [expected 0 + actual (interest-in-location + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :location [{:x 35 :y 23} :auchencairn :galloway]}]} + [:dumfries])] + (is (= actual expected))) + (let [expected 7071.067811865475 + actual (interest-in-location + {:home [{:x 35 :y 23}]} + [{:x 34 :y 24}])] + (is (= actual expected) + "TODO: 7071.067811865475 is actually a bad answer.")) + (let [expected 0 + actual (interest-in-location + {:home [{:x 35 :y 23}]} + [{:x 34 :y 24000}])] + (is (= actual expected) + "Too far apart (> 10000).")) + (let [expected true + actual (interesting-location? + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :location [{:x 35 :y 23} :auchencairn :galloway]}]} + :galloway)] + (is (= actual expected))) + (let [expected true + actual (interesting-location? + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :location [{:x 35 :y 23} :auchencairn :galloway]}]} + [:galloway :scotland])] + (is (= actual expected))) + (let [expected false + actual (interesting-location? + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :location [{:x 35 :y 23} :auchencairn :galloway]}]} + [:dumfries])] + (is (= actual expected))) + (let [expected true + actual (interesting-location? + {:home [{:x 35 :y 23}]} + [{:x 34 :y 24}])] + (is (= actual expected))) + (let [expected false + actual (interesting-location? + {:home [{:x 35 :y 23}]} + [{:x 34 :y 240000}])] + (is (= actual expected)))) + (testing "Degrading locations" + (let [expected [:galloway] + actual (degrade-location + {:home [{0 0} :test-home :galloway]} + [{-4 55} :auchencairn :galloway])] + (is (= actual expected))) + (let [expected nil + actual (degrade-location + {:home [{0 0} :test-home :galloway]} + [:froboz])] + (is (= actual expected))))) + +(deftest inference-tests + (testing "Ability to infer new knowledge from news items: single rule tests" + (let [expected {:verb :marry, :actor :belinda, :other :adam} + actual (infer {:verb :marry :actor :adam :other :belinda} + {:verb :marry :actor :other :other :actor})] + (is (= actual expected))) + (let [expected {:verb :attack, :actor :adam, :other :belinda} + actual (infer {:verb :rape :actor :adam :other :belinda} + {:verb :attack})] + (is (= actual expected))) + (let [expected {:verb :sex, :actor :belinda, :other :adam} + actual (infer {:verb :rape :actor :adam :other :belinda} + {:verb :sex :actor :other :other :actor})] + (is (= actual expected)))) + (testing "Ability to infer new knowledge from news items: all applicable rules" + (let [expected #{{:verb :sex, :actor :belinda, :other :adam, :location nil, :nth-hand 1} + {:verb :sex, :actor :adam, :other :belinda, :location nil, :nth-hand 1} + {:verb :attack, :actor :adam, :other :belinda, :location nil, :nth-hand 1}} + ;; dates will not be and cannot be expected to be equal + actual (make-all-inferences + {:verb :rape :actor :adam :other :belinda :location :test-home}) + actual' (map #(dissoc % :date) actual)] + (is (= actual' expected))))) + +;; (deftest learn-tests +;; (testing "Learning from an interesting news item." +;; (let [expected {:home [{0 0} :test-home], +;; :knowledge ({:verb :rape, :actor :adam, :other :belinda, :location nil, :nth-hand 1} +;; {:verb :sex, :actor :belinda, :other :adam, :location nil, :nth-hand 1} +;; {:verb :attack, :actor :adam, :other :belinda, :location nil, :nth-hand 1} +;; {:verb :sex, :actor :adam, :other :belinda, :location nil, :nth-hand 1})} +;; actual (learn-news-item +;; {:home [{0, 0} :test-home] +;; :knowledge []} +;; {:verb :rape :actor :adam :other :belinda :location [:test-home]}) +;; actual' (assoc actual :knowledge (map #(dissoc % :date) (:knowledge actual)))] +;; (is (= actual' expected))))) diff --git a/test/the_great_game/time_test.clj b/test/the_great_game/time_test.clj new file mode 100644 index 0000000..4727fdd --- /dev/null +++ b/test/the_great_game/time_test.clj @@ -0,0 +1,79 @@ +(ns the-great-game.time-test + (:require [clojure.test :refer :all] +;; [clojure.core.async :refer [thread t1 game-start-time)) + (Thread/sleep 1000) + (is (> (now) t1))))) + +(deftest game-time-tests + (testing "Getting game-time" + (is (= (game-time (inc game-start-time)) 1)))) + +(deftest calendar-tests + (testing "In-game calendar functions" + (let [expected :foot + actual (day 0)] + (is (= actual expected))) + (let [expected :stomach + actual (day (* 5 game-day-length))] + (is (= actual expected))) + (let [expected :foot + actual (day (* days-in-week game-day-length))] + (is (= actual expected))) + (let [expected :first ;; waiting day + actual (day (* 360 game-day-length))] + (is (= actual expected))) + (let [expected :first + actual (week 0)] + (is (= actual expected))) + (let [expected :second + actual (week (* days-in-week game-day-length))] + (is (= actual expected))) + (let [expected :first + actual (week (* days-in-season game-day-length))] + (is (= actual expected))) + (let [expected :foot + actual (season 0)] + (is (= actual expected))) + (let [expected :mouth + actual (season (* 180 game-day-length))] + (is (= actual expected))) + (let [expected :eye + actual (season (* 359 game-day-length))] + (is (= actual expected))) + (let [expected :waiting + actual (season (* 360 game-day-length))] + (is (= actual expected))) + (let [expected :foot + actual (season (* 365 game-day-length))] + (is (= actual expected))))) + +(deftest date-string-tests + (testing "Date-string formatting" + (let [expected "First Foot of the Foot" + actual (date-string 0)] + (is (= actual expected))) + (let [expected "First Foot of the Nose" + actual (date-string + (* days-in-season game-day-length))] + (is (= actual expected))) + (let [expected "Third Mouth of the Mouth" + actual (date-string (* 180 game-day-length))] + (is (= actual expected))) + (let [expected "Fifth Plough of the Eye" + actual (date-string (* 359 game-day-length))] + (is (= actual expected))) + (let [expected "First waiting day" + actual (date-string (* 360 game-day-length))] + (is (= actual expected))) + (let [expected "First Foot of the Foot" + actual (date-string (* 365 game-day-length))] + (is (= actual expected))))) + + + diff --git a/test/the_great_game/world/location_test.clj b/test/the_great_game/world/location_test.clj new file mode 100644 index 0000000..7303e1f --- /dev/null +++ b/test/the_great_game/world/location_test.clj @@ -0,0 +1,36 @@ +(ns the-great-game.world.location-test + (:require [clojure.test :refer :all] + [the-great-game.world.location :refer :all])) + +(deftest get-coords-test + (testing "Get coordinates of location" + (let [expected {:x 5 :y 7} + actual (get-coords {:x 5 :y 7})] + (is (= actual expected))) + (let [expected {:x -4 :y 55} + actual (get-coords [{:x -4 :y 55} :auchencairn :galloway :scotland])] + (is (= actual expected))) + (let [expected nil + actual (get-coords [:auchencairn :galloway :scotland])] + (is (= actual expected))) + )) + +(deftest distance-test + (testing "Distance between two locations" + (let [expected 4.242640687119285 + actual (distance-between {:x 5 :y 5} {:x 2 :y 2})] + (is (= actual expected))) + (let [expected 3 + actual (distance-between {:x 5 :y 5} {:x 2 :y 5})] + (is (= actual expected))) + (let [expected 50.80354318352215 + actual (distance-between + {:x 5 :y 5} + [{:x -4 :y 55} :auchencairn :galloway :scotland])] + (is (= actual expected))) + (let [expected nil + actual (distance-between + {:x 5 :y 5} + [:auchencairn :galloway :scotland])] + (is (= actual expected))) + ))