174 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Clojure
		
	
	
	
	
	
			
		
		
	
	
			174 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Clojure
		
	
	
	
	
	
| (ns 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."
 | |
|   (:require [taoensso.timbre :as l :refer [info error spy]]
 | |
|             [the-great-game.utils :refer [deep-merge]]
 | |
|             [the-great-game.gossip.gossip :refer [move-gossip]]
 | |
|             [the-great-game.merchants.planning :refer :all]
 | |
|             [the-great-game.merchants.merchant-utils :refer
 | |
|              [add-stock add-known-prices]]
 | |
|             [the-great-game.world.routes :refer [find-route]]))
 | |
| 
 | |
| (defn plan-and-buy
 | |
|   "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."
 | |
|   [merchant world]
 | |
|   (let [m (cond
 | |
|             (keyword? merchant)
 | |
|             (-> world :merchants merchant)
 | |
|             (map? merchant)
 | |
|             merchant)
 | |
|         id (:id m)
 | |
|         location (:location m)
 | |
|         market (-> world :cities location)
 | |
|         plan (select-cargo merchant world)]
 | |
|     (l/debug "plan-and-buy: merchant" id)
 | |
|     (cond
 | |
|       (not (empty? plan))
 | |
|       (let
 | |
|         [c (:commodity plan)
 | |
|          p (* (:quantity plan) (:buy-price plan))
 | |
|          q (:quantity plan)]
 | |
|         (l/info "Merchant" id "bought" q "units of" c "at" location "for" p plan)
 | |
|         {:merchants
 | |
|          {id
 | |
|           {:stock (add-stock (:stock m) {c q})
 | |
|            :cash (- (:cash m) p)
 | |
|            :known-prices (add-known-prices m world)
 | |
|            :plan plan}}
 | |
|          :cities
 | |
|          {location
 | |
|           {:stock (assoc (:stock market) c (- (-> market :stock c) q))
 | |
|            :cash (+ (:cash market) p)}}})
 | |
|       ;; if no plan, then if at home stay put
 | |
|       (= (:location m) (:home m))
 | |
|       (do
 | |
|         (l/info "Merchant" id "remains at home in" location)
 | |
|         {})
 | |
|       ;; else move towards home
 | |
|       :else
 | |
|       (let [route (find-route world location (:home m))
 | |
|             next-location (nth route 1)]
 | |
|         (l/info "No trade possible at" location "; merchant" id "moves to" next-location)
 | |
|         (merge
 | |
|           {:merchants
 | |
|            {id
 | |
|             {:location next-location}}}
 | |
|           (move-gossip id world next-location))))))
 | |
| 
 | |
| (defn re-plan
 | |
|   "Having failed to sell a cargo at current location, re-plan a route to
 | |
|   sell the current cargo. Returns a revised world."
 | |
|   [merchant world]
 | |
|   (let [m (cond
 | |
|             (keyword? merchant)
 | |
|             (-> world :merchants merchant)
 | |
|             (map? merchant)
 | |
|             merchant)
 | |
|         id (:id m)
 | |
|         location (:location m)
 | |
|         plan (augment-plan m world (plan-trade m world (-> m :plan :commodity)))]
 | |
|     (l/debug "re-plan: merchant" id)
 | |
|     (deep-merge
 | |
|       world
 | |
|       {:merchants
 | |
|        {id
 | |
|         {:plan plan}}})))
 | |
| 
 | |
| (defn sell-and-buy
 | |
|   "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."
 | |
|   ;; TODO: this either sells the entire cargo, or, if the market can't afford
 | |
|   ;; it, none of it. And it does not cope with selling different commodities
 | |
|   ;; in different markets.
 | |
|   [merchant world]
 | |
|   (let [m (cond
 | |
|             (keyword? merchant)
 | |
|             (-> world :merchants merchant)
 | |
|             (map? merchant)
 | |
|             merchant)
 | |
|         id (:id m)
 | |
|         location (:location m)
 | |
|         market (-> world :cities location)
 | |
|         stock-value (reduce
 | |
|                       +
 | |
|                       (map
 | |
|                         #(* (-> m :stock %) (-> market :prices m))
 | |
|                         (keys (:stock m))))]
 | |
|     (l/debug "sell-and-buy: merchant" id)
 | |
|     (if
 | |
|       (>= (:cash market) stock-value)
 | |
|       (do
 | |
|         (l/info "Merchant" id "sells" (:stock m) "at" location "for" stock-value)
 | |
|         (plan-and-buy
 | |
|           merchant
 | |
|           (deep-merge
 | |
|             world
 | |
|             {:merchants
 | |
|              {id
 | |
|               {:stock {}
 | |
|                :cash (+ (:cash m) stock-value)
 | |
|                :known-prices (add-known-prices m world)}}
 | |
|              :cities
 | |
|              {location
 | |
|               {:stock (add-stock (:stock m) (:stock market))
 | |
|                :cash (- (:cash market) stock-value)}}})))
 | |
|       ;; else
 | |
|       (re-plan merchant world))))
 | |
| 
 | |
| (defn move-merchant
 | |
|   "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."
 | |
|   [merchant world]
 | |
|   (let [m (cond
 | |
|             (keyword? merchant)
 | |
|             (-> world :merchants merchant)
 | |
|             (map? merchant)
 | |
|             merchant)
 | |
|         id (:id m)
 | |
|         at-destination? (and (:plan m) (= (:location m) (-> m :plan :destination)))
 | |
|         plan (:plan m)
 | |
|         next-location (if plan
 | |
|                         (nth
 | |
|                           (find-route
 | |
|                             world
 | |
|                             (:location m)
 | |
|                             (:destination plan))
 | |
|                           1)
 | |
|                         (:location m))]
 | |
|     (l/debug "move-merchant: merchant" id "at" (:location m)
 | |
|              "destination" (-> m :plan :destination) "next" next-location
 | |
|              "at destination" at-destination?)
 | |
|     (cond
 | |
|       ;; if the merchant is at the destination of their current plan
 | |
|       ;; sell all cargo and repurchase.
 | |
|       at-destination?
 | |
|       (sell-and-buy merchant world)
 | |
|       ;; if they don't have a plan, seek to create one
 | |
|       (nil? plan)
 | |
|       (plan-and-buy merchant world)
 | |
|       ;; otherwise, move one step towards their destination
 | |
|       (and next-location (not= next-location (:location m)))
 | |
|       (do
 | |
|         (l/info "Merchant " id " moving from " (:location m) " to " next-location)
 | |
|         (deep-merge
 | |
|           {:merchants
 | |
|            {id
 | |
|             {:location next-location
 | |
|              :known-prices (add-known-prices m world)}}}
 | |
|           (move-gossip id world next-location)))
 | |
|       :else
 | |
|       (do
 | |
|         (l/info "Merchant" id "has plan but no next-location; currently at"
 | |
|                 (:location m) ", destination is" (:destination plan))
 | |
|         world))))
 | |
| 
 |