001  (ns cc.journeyman.the-great-game.merchants.merchant-utils
002    "Useful functions for doing low-level things with merchants.")
003  
004  (defn expected-price
005    "Find the price anticipated, given this `world`, by this `merchant` for
006    this `commodity` in this `city`. If no information, assume 1.
007    `merchant` should be passed as a map, `commodity` and `city` should be passed as keywords."
008    [merchant commodity city]
009    (or
010      (:price
011        (last
012          (sort-by
013            :date
014            (-> merchant :known-prices city commodity))))
015      1))
016  
017  (defn burden
018    "The total weight of the current cargo carried by this `merchant` in this
019    `world`."
020    [merchant world]
021    (let [m (cond
022              (keyword? merchant)
023              (-> world :merchants merchant)
024              (map? merchant)
025              merchant)
026          cargo (or (:stock m) {})]
027      (reduce
028        +
029        0
030        (map
031          #(* (cargo %) (-> world :commodities % :weight))
032          (keys cargo)))))
033  
034  
035  (defn can-carry
036    "Return the number of units of this `commodity` which this `merchant`
037    can carry in this `world`, given their current burden."
038    [merchant world commodity]
039    (let [m (cond
040              (keyword? merchant)
041              (-> world :merchants merchant)
042              (map? merchant)
043              merchant)]
044      (max
045        0
046        (quot
047          (- (or (:capacity m) 0) (burden m world))
048          (-> world :commodities commodity :weight)))))
049  
050  (defn can-afford
051    "Return the number of units of this `commodity` which this `merchant`
052    can afford to buy in this `world`."
053    [merchant world commodity]
054    (let [m (cond
055              (keyword? merchant)
056              (-> world :merchants merchant)
057              (map? merchant)
058              merchant)
059          l (:location m)]
060      (cond
061        (nil? m)
062        (throw (Exception. "No merchant?"))
063        (or (nil? l) (nil? (-> world :cities l)))
064        (throw (Exception. (str "No known location for merchant " m)))
065        :else
066        (quot
067          (:cash m)
068          (-> world :cities l :prices commodity)))))
069  
070  (defn add-stock
071    "Where `a` and `b` are both maps all of whose values are numbers, return
072    a map whose keys are a union of the keys of `a` and `b` and whose values
073    are the sums of their respective values."
074    [a b]
075    (reduce
076      merge
077      a
078      (map
079        #(hash-map % (+ (or (a %) 0) (or (b %) 0)))
080        (keys b))))
081  
082  (defn add-known-prices
083    "Add the current prices at this `merchant`'s location in the `world`
084    to a new cache of known prices, and return it."
085    [merchant world]
086    (let [m (cond
087              (keyword? merchant)
088              (-> world :merchants merchant)
089              (map? merchant)
090              merchant)
091          k (or (:known-prices m) {})
092          l (:location m)
093          d (or (:date world) 0)
094          p (-> world :cities l :prices)]
095      (cond
096        (nil? m)
097        (throw (Exception. "No merchant?"))
098        (or (nil? l) (nil? (-> world :cities l)))
099        (throw (Exception. (str "No known location for merchant " m)))
100        :else
101        (reduce
102          merge
103          k
104          (map
105            #(hash-map % (apply vector cons {:price (p %) :date d} (k %)))
106            (-> world :commodities keys))))))