001 (ns cc.journeyman.the-great-game.gossip.news-items
002 "Using news items (propositions) to transfer knowledge between gossip agents.
003
004 ## Status
005
006 What is here is essentially working. It's not, however, working with the
007 rich data objects which will be needed, and it's not yet nearly efficient
008 enough, but it allows knowledge to propagate through the world procedurally,
009 at a rate limited by the speed of movement of the gossip agents.
010
011 ## Discussion
012
013 The ideas here are based on the essay [The spread of knowledge in a large
014 game world](The-spread-of-knowledge-in-a-large-game-world.html), q.v.;
015 they've advanced a little beyond that and will doubtless
016 advance further in the course of writing and debugging this namespace.
017
018 A news item is a map with the keys:
019
020 * `date` - the date on which the reported event is claimed to have happened;
021 * `nth-hand` - the number of agents the news item has passed through;
022 * `verb` - what it is that happened (key into `news-topics`);
023
024 plus other keys taken from the `keys` value associated with the verb in
025 `news-topics`.
026
027 ## Notes:
028
029 *TODO*
030 This namespace at present considers the `:knowledge` of a gossip to be a flat
031 list of propositions, each of which must be checked every time any new
032 proposition is offered. This is woefully inefficient. "
033 (:require [cc.journeyman.the-great-game.world.location :refer [distance-between]]
034 [cc.journeyman.the-great-game.time :refer [game-time]]
035 [cc.journeyman.the-great-game.utils :refer [inc-or-one truthy?]]
036 [taoensso.timbre :as l]))
037
038 (def news-topics
039 "Topics of interest to gossip agents. Topics are keyed in this map by
040 their `verbs`. The `keys` associated with each topic are the extra pieces
041 of information required to give context to a gossip item. Generally:
042
043 * `actor` is the id of the character who it is reported performed the
044 action;
045 * `other` is the id of the character on whom it is reported the action
046 was performed;
047 * `location` is the place at which the action was performed;
048 * `object` is an object (or possibly list of objects?) relevant to the
049 action;
050 * `price` is special to buy/sell, but of significant interest to merchants.
051
052 ## Characters
053
054 *TODO* but note that at most all the receiver can learn about a character
055 from a news item is what the giver knows about that character, degraded by
056 what the receiver finds interesting about them. If we just pass the id here,
057 then either the receiver knows everything in the database about the
058 character, or else the receiver knows nothing at all about the character.
059 Neither is desirable. Further thought needed.
060
061 By implication, the character values passed should include *all* the
062 information the giver knows about the character; that can then be degraded
063 as the receiver stores only that segment which the receiver finds
064 interesting.
065
066 ## Locations
067
068 A 'location' value is a list comprising at most the x/y coordinate location
069 and the ids of the settlement and region (possibly hierarchically) that contain
070 the location. If the x/y is not local to the home of the receiving agent, they
071 won't remember it and won't pass it on; if any of the ids are not interesting
072 So location information will degrade progressively as the item is passed along.
073
074 It is assumed that the `:home` of a character is a location in this sense.
075
076 ## Inferences
077
078 If an agent learns that Adam has married Betty, they can infer that Betty has
079 married Adam; if they learn that Charles killed Dorothy, that Dorothy has died.
080 I'm not convinced that my representation of inferences here is ideal."
081 {;; A significant attack is interesting whether or not it leads to deaths
082 :attack {:verb :attack :keys [:actor :other :location]}
083 ;; Deaths of characters may be interesting
084 :die {:verb :die :keys [:actor :location]}
085 ;; Deliberate killings are interesting.
086 :kill {:verb :kill :keys [:actor :other :location]
087 :inferences [{:verb :die :actor :other :other :nil}]}
088 ;; Marriages may be interesting
089 :marry {:verb :marry :keys [:actor :other :location]
090 :inferences [{:verb :marry :actor :other :other :actor}]}
091 ;; The end of ongoing open conflict between to characters may be interesting
092 :peace {:verb :peace :keys [:actor :other :location]
093 :inferences [{:verb :peace :actor :other :other :actor}]}
094 ;; Things related to the plot are interesting, but will require special
095 ;; handling. Extra keys may be required by particular plot events.
096 :plot {:verb :plot :keys [:actor :other :object :location]}
097 ;; Rapes are interesting.
098 :rape {:verb :rape :keys [:actor :other :location]
099 ;; Should you also infer from rape that actor is male and adult?
100 :inferences [{:verb :attack}
101 {:verb :sex}
102 {:verb :sex :actor :other :other :actor}]}
103 ;; Merchants, especially, are interested in prices in other markets
104 :sell {:verb :sell :keys [:actor :other :object :location :quantity :price]}
105 ;; Sex can juicy gossip, although not normally if the participants are in an
106 ;; established sexual relationship.
107 :sex {:verb :sex :keys [:actor :other :location]
108 :inferences [{:verb :sex :actor :other :other :actor}]}
109 ;; Thefts are interesting.
110 :steal {:verb :steal :keys [:actor :other :object :location]}
111 ;; The succession of rulers is interesting; of respected craftsmen,
112 ;; potentially also interesting.
113 :succession {:verb :succession :keys [:actor :other :location :rank]}
114 ;; The start of ongoing open conflict between two characters may be interesting.
115 :war {:verb :war :keys [:actor :other :location]
116 :inferences [{:verb :war :actor :other :other :actor}]}})
117
118
119 (def all-known-verbs
120 "All verbs currently known to the gossip system."
121 (set (keys news-topics)))
122
123
124 (defn interest-in-character
125 "Integer representation of how interesting this `character` is to this
126 `gossip`.
127 *TODO:* this assumes that characters are passed as keywords, but, as
128 documented above, they probably have to be maps, to allow for degradation."
129 [gossip character]
130 (count
131 (concat
132 ;; TODO: we ought also check the relationships of the gossip.
133 ;; Are relationships just propositions in the knowledge base?
134 (filter #(= (:actor %) character) (:knowledge gossip))
135 (filter #(= (:other %) character) (:knowledge gossip)))))
136
137
138 (defn interesting-character?
139 "Boolean representation of whether this `character` is interesting to this
140 `gossip`."
141 [gossip character]
142 (> (interest-in-character gossip character) 0))
143
144 (defn interest-in-location
145 "Integer representation of how interesting this `location` is to this
146 `gossip`."
147 [gossip location]
148 (cond
149 (and (map? location) (number? (:x location)) (number? (:y location)))
150 (if-let [home (:home gossip)]
151 (let [d (distance-between location home)
152 i (if
153 (zero? d) 1
154 (/ 10000 d))
155 ;; 10000 at metre scale is 10km; interest should
156 ;;fall off with distance from home, but possibly on a log scale
157 ]
158 (if (>= i 1) i 0))
159 0)
160 (coll? location)
161 (reduce
162 +
163 (map
164 #(interest-in-location gossip %)
165 location))
166 :else
167 (count
168 (filter
169 #(some (fn [x] (= x location)) (:location %))
170 (cons {:location (:home gossip)} (:knowledge gossip))))))
171
172 ;; (distance-between {:x 25 :y 37} {:x 25 :y 37})
173 ;; (interest-in-location {:home [{0, 0} :test-home] :knowledge []} [:test-home])
174
175 (defn interesting-location?
176 "True if the location of this news `item` is interesting to this `gossip`."
177 [gossip location]
178 (> (interest-in-location gossip location) 0))
179
180 (defn interesting-object?
181 [gossip object]
182 ;; TODO: Not yet (really) implemented
183 true)
184
185 (defn interesting-topic?
186 [gossip topic]
187 ;; TODO: Not yet (really) implemented
188 true)
189
190 (defn interesting-verb?
191 "Is this `verb` interesting to this `gossip`?"
192 [gossip verb]
193 (let [vs (:interesting-verbs gossip)]
194 (truthy?
195 (if (set? vs)
196 (vs verb)
197 false))))
198
199 ;; (interesting-verb? {:interesting-verbs #{:kill :sell}} :sell)
200
201 (defn compatible-value?
202 "True if `known-value` is the same as `new-value`, or, for each key present
203 in `new-value`, has the same value for that key.
204
205 The rationale here is that if `new-value` contains new or different
206 information, it's worth learning; otherwise, not."
207 [new-value known-value]
208 (or
209 (= new-value known-value)
210 ;; TODO: some handwaving here about being a slightly better descriptor --
211 ;; having more keys than might
212 (when (and (map? new-value) (map? known-value))
213 (every? true? (map #(= (new-value %) (known-value %))
214 (keys new-value))))))
215
216 (defn compatible-item?
217 "True if `new-item` is identical with, or less specific than, `known-item`.
218
219 If we already know 'Bad Joe killed Sweet Daisy', there's no point in
220 learning that 'someone killed Sweet Daisy', but there is point in learning
221 'someone killed Sweet Daisy _with poison_'."
222 [new-item known-item]
223 (truthy?
224 (reduce
225 #(and %1 %2)
226 (map #(if
227 (known-item %) ;; if known-item has this key
228 (compatible-value? (new-item %) (known-item %))
229 true)
230 (remove #{:nth-hand :confidence :learned-from} (keys new-item))))))
231
232 (defn known-item?
233 "True if this news `item` is already known to this `gossip`.
234
235 This means that the `gossip` already knows an item which identifiably has
236 the same _or more specific_ values for all the keys of this `item` except
237 `:nth-hand`, `:confidence` and `:learned-from`."
238 [gossip item]
239 (truthy?
240 (reduce
241 #(or %1 %2)
242 false
243 (filter true? (map #(compatible-item? item %) (:knowledge gossip))))))
244
245 (defn interesting-item?
246 "True if anything about this news `item` is interesting to this `gossip`."
247 [gossip item]
248 (and (not (known-item? gossip item))
249 (interesting-verb? gossip item) ;; news is only interesting if the topic is.
250 (or
251 (interesting-character? gossip (:actor item))
252 (interesting-character? gossip (:other item))
253 (interesting-location? gossip (:location item))
254 (interesting-object? gossip (:object item))
255 (interesting-topic? gossip (:verb item)))))
256
257 (defn infer
258 "Infer a new knowledge item from this `item`, following this `rule`."
259 [item rule]
260 ;; (l/info "Applying rule '" rule "' to item '" item "'")
261 (reduce merge
262 item
263 (cons
264 {:verb (:verb rule)
265 :nth-hand (inc-or-one (:nth-hand item))}
266 (map (fn [k] {k (item (rule k))})
267 (remove
268 #{:verb :nth-hand}
269 (keys rule))))))
270
271 (declare learn-news-item)
272
273 (defn make-all-inferences
274 "Return a set of knowledge entries that can be inferred from this news
275 `item`."
276 [item]
277 (set
278 (map
279 #(infer item %)
280 (:inferences (news-topics (:verb item))))))
281
282 (defn degrade-character
283 "Return a character specification like this `character`, but comprising
284 only those properties this `gossip` is interested in."
285 [gossip character]
286 ;; TODO: Not yet (really) implemented
287 character)
288
289 (defn degrade-location
290 "Return a location specification like this `location`, but comprising
291 only those elements this `gossip` is interested in. If none, return
292 `nil`."
293 [gossip location]
294 (let [l (when
295 (coll? location)
296 (filter
297 #(when (interesting-location? gossip %) %)
298 location))]
299 (when-not (empty? l) l)))
300
301 (defn degrade-news-item
302 [gossip item]
303 (assoc
304 item
305 :nth-hand (inc-or-one (:nth-hand item))
306 :time-stamp (if
307 (number? (:time-stamp item))
308 (:time-stamp item)
309 (game-time))
310 :location (degrade-location gossip (:location item))
311 :actor (degrade-character gossip (:actor item))
312 :other (degrade-character gossip (:other item))
313 ;; TODO: do something to degrade confidence in the item,
314 ;; probably as a function of the provider's confidence in
315 ;; the item and the gossip's trust in the provider
316 ))
317
318 ;; (degrade-news-item {:home [{:x 25 :y 37} :auchencairn :scotland]}
319 ;; {:verb :marry :actor :adam :other :belinda :location [{:x 25 :y 37} :auchencairn :scotland]})
320
321 (defn learn-news-item
322 "Return a gossip like this `gossip`, which has learned this news `item` if
323 it is of interest to them."
324 ([gossip item]
325 (learn-news-item gossip item true))
326 ([gossip item follow-inferences?]
327 (if
328 (interesting-item? gossip item)
329 (let [item' (degrade-news-item gossip item)
330 g (assoc
331 gossip
332 :knowledge
333 (cons
334 item'
335 (:knowledge gossip)))]
336 (if follow-inferences?
337 (assoc
338 g
339 :knowledge
340 (concat (:knowledge g) (make-all-inferences item')))
341 g)))
342 gossip))
343
344
345