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 [game-time]]))
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    By implication, the character values passed should include *all* the
045    information the giver knows about the character; that can then be degraded
046    as the receiver stores only that segment which the receiver finds
047    interesting.
048  
049    ##### Locations:
050  
051    A 'location' value is a list comprising at most the x/y coordinate location
052    and the ids of the settlement and region (possibly hierarchically) that contain
053    the location. If the x/y is not local to the home of the receiving agent, they
054    won't remember it and won't pass it on; if any of the ids are not interesting
055    So location information will degrade progressively as the item is passed along.
056  
057    It is assumed that the `:home` of a character is a location in this sense.
058  
059    ##### Inferences:
060  
061    If an agent learns that Adam has married Betty, they can infer that Betty has
062    married Adam; if they learn that Charles killed Dorothy, that Dorothy has died.
063    I'm not convinced that my representation of inferences here is ideal.
064    "
065    { ;; A significant attack is interesting whether or not it leads to deaths
066      :attack {:verb :attack :keys [:actor :other :location]}
067      ;; Deaths of characters may be interesting
068      :die {:verb :die :keys [:actor :location]}
069      ;; Deliberate killings are interesting.
070      :kill {:verb :kill :keys [:actor :other :location]
071             :inferences [{:verb :die :actor :other :other :nil}]}
072      ;; Marriages may be interesting
073      :marry {:verb :marry :keys [:actor :other :location]
074              :inferences [{:verb :marry :actor :other :other :actor}]}
075      ;; The end of ongoing open conflict between to characters may be interesting
076      :peace {:verb :peace :keys [:actor :other :location]
077              :inferences [{:verb :peace :actor :other :other :actor}]}
078      ;; Things related to the plot are interesting, but will require special
079      ;; handling. Extra keys may be required by particular plot events.
080      :plot {:verb :plot :keys [:actor :other :object :location]}
081      ;; Rapes are interesting.
082      :rape {:verb :rape :keys [:actor :other :location]
083             ;; Should you also infer from rape that actor is male and adult?
084             :inferences [{:verb :attack}
085                          {:verb :sex}
086                          {:verb :sex :actor :other :other :actor}]}
087      ;; Merchants, especially, are interested in prices in other markets
088      :sell {:verb :sell :keys [:actor :other :object :location :price]}
089      ;; Sex can juicy gossip, although not normally if the participants are in an
090      ;; established sexual relationship.
091      :sex {:verb :sex :keys [:actor :other :location]
092            :inferences [{:verb :sex :actor :other :other :actor}]}
093      ;; Thefts are interesting
094      :steal {:verb :steal :keys [:actor :other :object :location]}
095      ;; The succession of rulers is interesting; of respected craftsmen,
096      ;; potentially also interesting.
097      :succession {:verb :succession :keys [:actor :other :location :rank]}
098      ;; The start of ongoing open conflict between to characters may be interesting
099      :war {:verb :war :keys [:actor :other :location]
100            :inferences [{:verb :war :actor :other :other :actor}]}
101      })
102  
103  
104  (defn interest-in-character
105    "Integer representation of how interesting this `character` is to this
106    `gossip`.
107    *TODO:* this assumes that characters are passed as keywords, but, as
108    documented above, they probably have to be maps, to allow for degradation."
109    [gossip character]
110    (count
111      (concat
112        (filter #(= (:actor % character)) (:knowledge gossip))
113        (filter #(= (:other % character)) (:knowledge gossip)))))
114  
115  (defn interesting-character?
116    "Boolean representation of whether this `character` is interesting to this
117    `gossip`."
118    [gossip character]
119    (> (interest-in-character gossip character) 0))
120  
121  (defn interest-in-location
122    "Integer representation of how interesting this `location` is to this
123    `gossip`."
124    [gossip location]
125    (cond
126      (and (map? location) (number? (:x location)) (number? (:y location)))
127      (if-let [home (:home gossip)]
128        (let [d (distance-between location home)
129              i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should
130              ;;fall off with distance from home, but possibly on a log scale
131              ]
132          (if (> i 1) i 0))
133        0)
134      (coll? location)
135      (reduce
136        +
137        (map
138          #(interest-in-location gossip %)
139          location))
140      :else
141      (count
142        (filter
143          #(some (fn [x] (= x location)) (:location %))
144          (cons {:location (:home gossip)} (:knowledge gossip))))))
145  
146  ;; (interest-in-location {:home [{0, 0} :test-home] :knowledge []} [:test-home])
147  
148  (defn interesting-location?
149    "True if the location of this news `item` is interesting to this `gossip`."
150    [gossip item]
151    (> (interest-in-location gossip (:location item)) 0))
152  
153  (defn interesting-object?
154    [gossip object]
155    ;; TODO: Not yet (really) implemented
156    true)
157  
158  (defn interesting-topic?
159    [gossip topic]
160    ;; TODO: Not yet (really) implemented
161    true)
162  
163  (defn interesting-item?
164    "True if anything about this news `item` is interesting to this `gossip`."
165    [gossip item]
166       (or
167         (interesting-character? gossip (:actor item))
168         (interesting-character? gossip (:other item))
169         (interesting-location? gossip (:location item))
170         (interesting-object? gossip (:object item))
171         (interesting-topic? gossip (:verb item))))
172  
173  (defn infer
174    "Infer a new knowledge item from this `item`, following this `rule`"
175    [item rule]
176    (reduce merge
177            item
178            (cons
179              {:verb (:verb rule)}
180              (map (fn [k] {k (apply (k rule) (list item))})
181                   (remove
182                     #(= % :verb)
183                     (keys rule))))))
184  
185  (declare learn-news-item)
186  
187  (defn make-all-inferences
188    "Return a list of knowledge entries that can be inferred from this news
189    `item`."
190    [item]
191    (set
192      (reduce
193        concat
194        (map
195          #(:knowledge (learn-news-item {} (infer item %) false))
196          (:inferences (news-topics (:verb item)))))))
197  
198  (defn degrade-character
199    "Return a character specification like this `character`, but comprising
200    only those properties this `gossip` is interested in."
201    [gossip character]
202    ;; TODO: Not yet (really) implemented
203    character)
204  
205  (defn degrade-location
206    "Return a location specification like this `location`, but comprising
207    only those elements this `gossip` is interested in. If none, return
208    `nil`."
209    [gossip location]
210    (let [l (if
211      (coll? location)
212      (filter
213        #(when (interesting-location? gossip %) %)
214        location))]
215      (when-not (empty? l) l)))
216  
217  (defn learn-news-item
218    "Return a gossip like this `gossip`, which has learned this news `item` if
219    it is of interest to them."
220    ;; TODO: Not yet implemented
221    ([gossip item]
222     (learn-news-item gossip item true))
223    ([gossip item follow-inferences?]
224     (if
225       (interesting-item? gossip item)
226       (let
227         [g (assoc
228              gossip
229              :knowledge
230              (cons
231                (assoc
232                  item
233                  :nth-hand (if
234                              (number? (:nth-hand item))
235                              (inc (:nth-hand item))
236                              1)
237                  :time-stamp (if
238                                (number? (:time-stamp item))
239                                (:time-stamp item)
240                                (game-time))
241                  :location (degrade-location gossip (:location item))
242                  ;; TODO: ought to maybe-degrade characters we're not yet interested in
243                  )
244                ;; TODO: ought not to add knowledge items we already have, except
245                ;; to replace if new item is of increased specificity
246                (:knowledge gossip)))]
247         (if follow-inferences?
248           (assoc
249             g
250             :knowledge
251             (concat (:knowledge g) (make-all-inferences item)))
252           g))
253       gossip)))
254  
255  
256