From 23e24bd381b90327c257a3986d805cc937965adc Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sat, 18 May 2019 09:33:50 +0100 Subject: [PATCH] Tactical commit before major refactor --- .gitignore | 5 + project.clj | 1 + src/the_great_game/gossip/gossip.clj | 4 +- src/the_great_game/merchants/markets.clj | 24 +-- src/the_great_game/merchants/merchants.clj | 169 ++++++++++-------- src/the_great_game/merchants/planning.clj | 6 + .../merchants/strategies/simple.clj | 2 + src/the_great_game/merchants/strategy.clj | 0 src/the_great_game/utils.clj | 2 +- src/the_great_game/world/routes.clj | 12 +- src/the_great_game/world/run.clj | 28 ++- src/the_great_game/world/world.clj | 12 +- 12 files changed, 161 insertions(+), 104 deletions(-) create mode 100644 src/the_great_game/merchants/planning.clj create mode 100644 src/the_great_game/merchants/strategies/simple.clj delete mode 100644 src/the_great_game/merchants/strategy.clj diff --git a/.gitignore b/.gitignore index a4cb69a..0910231 100644 --- a/.gitignore +++ b/.gitignore @@ -2,13 +2,18 @@ pom.xml pom.xml.asc *.jar *.class +*.log +[0-9a-f]*-init.clj /lib/ /classes/ /target/ /checkouts/ +/.clj-kondo/ +.eastwood .lein-deps-sum .lein-repl-history .lein-plugins/ .lein-failures .nrepl-port .cpcache/ +*~ diff --git a/project.clj b/project.clj index 4ffd6a4..e7d8a3b 100644 --- a/project.clj +++ b/project.clj @@ -4,6 +4,7 @@ :output-path "docs" :source-uri "https://github.com/simon-brooke/the-great-game/blob/master/{filepath}#L{line}"} :dependencies [[org.clojure/clojure "1.8.0"] + [environ "1.1.0"] [com.taoensso/timbre "4.10.0"]] :description "Prototype code towards the great game I've been writing about for ten years, and know I will never finish." :license {:name "GNU General Public License,version 2.0 or (at your option) any later version" diff --git a/src/the_great_game/gossip/gossip.clj b/src/the_great_game/gossip/gossip.clj index 886c525..efe442f 100644 --- a/src/the_great_game/gossip/gossip.clj +++ b/src/the_great_game/gossip/gossip.clj @@ -49,9 +49,9 @@ [gossip world new-location] (let [id (cond (map? gossip) - (:id (-> world :gossipe gossip) + (-> world :gossips gossip :id) (keyword? gossip) - gossip))] + gossip)] (deep-merge world {:gossips diff --git a/src/the_great_game/merchants/markets.clj b/src/the_great_game/merchants/markets.clj index cd3deb5..c4f0898 100644 --- a/src/the_great_game/merchants/markets.clj +++ b/src/the_great_game/merchants/markets.clj @@ -1,8 +1,7 @@ (ns the-great-game.merchants.markets "Adjusting quantities and prices in markets." (:require [taoensso.timbre :as l :refer [info error]] - [the-great-game.utils :refer [deep-merge]] - [the-great-game.world.world :refer [actual-price default-world]])) + [the-great-game.utils :refer [deep-merge]])) (defn new-price "If `stock` is greater than the maximum of `supply` and `demand`, then @@ -30,19 +29,24 @@ su (or (-> c :supplies commodity) 0) decrement (min st d) increment (cond - ;; if its profitable to produce this commodity, the craftspeople - ;; of the city will do so. + ;; if we've two turns' production of this commodity in + ;; stock, halt production + (> st (* su 2)) + 0 + ;; if it is profitable to produce this commodity, the + ;; craftspeople of the city will do so. (> p 1) su - ;; otherwise, if there isn't a turn's production in stock, top up - ;; the stock, so that there's something for incoming merchants to - ;; buy + ;; otherwise, if there isn't a turn's production in + ;; stock, top up the stock, so that there's something for + ;; incoming merchants to buy (> su st) (- su st) - true 0) + :else + 0) n (new-price p st su d)] (if - (not (= p n)) - (l/info "Price of " commodity " at " id " has changed from " (float p) " to " (float n))) + (not= p n) + (l/info "Price of" commodity "at" id "has changed from" (float p) "to" (float n))) {:cities {id {:stock {commodity (+ (- st decrement) increment)} diff --git a/src/the_great_game/merchants/merchants.clj b/src/the_great_game/merchants/merchants.clj index 51c813a..7b10dd2 100644 --- a/src/the_great_game/merchants/merchants.clj +++ b/src/the_great_game/merchants/merchants.clj @@ -1,6 +1,6 @@ (ns the-great-game.merchants.merchants "Trade planning for merchants, primarily." - (:require [taoensso.timbre :as l :refer [info error]] + (: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.world.routes :refer [find-route]] @@ -30,7 +30,7 @@ (-> world :merchants merchant) (map? merchant) merchant) - cargo (-> m :stock)] + cargo (:stock m)] (reduce + 0 @@ -65,26 +65,26 @@ Returned plans are maps with keys: -* :merchant - the id of the `merchant` for whom the plan was created; -* :origin - the city from which the trade starts; -* :destination - the city to which the trade is planned; -* :commodity - the `commodity` to be carried; -* :buy-price - the price at which that `commodity` can be bought; -* :expected-price - the price at which the `merchant` anticipates - that `commodity` can be sold; -* :distance - the number of stages in the planned journey -* :dist-to-home - the distance from `destination` to the `merchant`'s - home city." + * :merchant - the id of the `merchant` for whom the plan was created; + * :origin - the city from which the trade starts; + * :destination - the city to which the trade is planned; + * :commodity - the `commodity` to be carried; + * :buy-price - the price at which that `commodity` can be bought; + * :expected-price - the price at which the `merchant` anticipates + that `commodity` can be sold; + * :distance - the number of stages in the planned journey + * :dist-to-home - the distance from `destination` to the `merchant`'s + home city." [merchant world commodity] (let [m (cond (keyword? merchant) (-> world :merchants merchant) (map? merchant) merchant) - origin (-> m :location)] + origin (:location m)] (map #(hash-map - :merchant (-> m :id) + :merchant (:id m) :origin origin :destination % :commodity commodity @@ -97,10 +97,10 @@ (find-route world origin %)) :dist-to-home (count (find-route - world - (:home m) - %))) - (remove #(= % origin) (keys (-> world :cities)))))) + world + (:home m) + %))) + (remove #(= % origin) (-> world :cities keys))))) (defn nearest-with-targets "Return the distance to the nearest destination among those of these @@ -183,7 +183,7 @@ merchant) l (:location m)] (quot - (-> m :cash) + (:cash m) (-> world :cities l :prices commodity)))) (defn augment-plan @@ -219,7 +219,7 @@ (-> world :merchants merchant) (map? merchant) merchant) - origin (-> m :location) + origin (:location m) available (-> world :cities origin :stock) plans (map #(augment-plan @@ -228,7 +228,7 @@ (plan-trade m world %)) (filter #(let [q (-> world :cities origin :stock %)] - (and (number? q) (> q 0))) + (and (number? q) (pos? q))) (keys available)))] (if (not (empty? plans)) @@ -281,48 +281,48 @@ 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] - (deep-merge - 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)] - (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) + (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 - {:stock (add-stock (:stock m) {c q}) - :cash (- (:cash m) p) - :known-prices (add-known-prices m world)}} - :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 - true - (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))))))) + (move-gossip id world next-location)))))) (defn re-plan "Having failed to sell a cargo at current location, re-plan a route to @@ -336,6 +336,7 @@ 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 @@ -363,11 +364,11 @@ (map #(* (-> m :stock %) (-> market :prices m)) (keys (:stock m))))] + (l/debug "sell-and-buy: merchant" id) (if (>= (:cash market) stock-value) (do - (l/info - (apply str (flatten (list "Merchant " id " sells " (:stock m) " at " location " for " stock-value)))) + (l/info "Merchant" id "sells" (:stock m) "at" location "for" stock-value) (plan-and-buy merchant (deep-merge @@ -385,7 +386,9 @@ (re-plan merchant world)))) (defn move-merchant - "Handle general en route movement of this `merchant` in this `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." [merchant world] (let [m (cond (keyword? merchant) @@ -396,28 +399,42 @@ at-destination? (and (:plan m) (= (:location m) (-> m :plan :destination))) plan (:plan m) next-location (if plan - (nth 1 (find-route world (:location m) (:destination plan))) + (nth + (find-route + world + (:location m) + (:destination plan)) + 1) (:location m))] - (l/info "Merchant " id " has moved from " (:location m) " to " next-location) + (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 plan) + (sell-and-buy merchant world) ;; if they don't have a plan, seek to create one - (nil? (:plan m)) + (nil? plan) (plan-and-buy merchant world) ;; otherwise, move one step towards their destination - true - (deep-merge - {:merchants - {id - {:location next-location - :known-prices (add-known-prices m world)}}} - (move-gossip id world next-location))))) + (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)))) (defn run - "Return a world like this `world`, but with each merchant moved." + "Return a partial world based on this `world`, but with each merchant moved." [world] (try (reduce @@ -429,7 +446,7 @@ (catch Exception any (l/error any "Failure while moving merchant " %) {})) - (keys (:merchants world)))) + (keys (:merchants world)))) (catch Exception any (l/error any "Failure while moving merchants") world))) diff --git a/src/the_great_game/merchants/planning.clj b/src/the_great_game/merchants/planning.clj new file mode 100644 index 0000000..eb2a2ef --- /dev/null +++ b/src/the_great_game/merchants/planning.clj @@ -0,0 +1,6 @@ +(ns the-great-game.merchants.planning + "Trade planning for merchants, primarily." + (:require [taoensso.timbre :as l :refer [info error spy]] + [the-great-game.utils :refer [deep-merge]] + [the-great-game.world.routes :refer [find-route]] + [the-great-game.world.world :refer [actual-price default-world]])) diff --git a/src/the_great_game/merchants/strategies/simple.clj b/src/the_great_game/merchants/strategies/simple.clj new file mode 100644 index 0000000..621d29c --- /dev/null +++ b/src/the_great_game/merchants/strategies/simple.clj @@ -0,0 +1,2 @@ +(ns the-great-game.merchants.strategies.simple + ) diff --git a/src/the_great_game/merchants/strategy.clj b/src/the_great_game/merchants/strategy.clj deleted file mode 100644 index e69de29..0000000 diff --git a/src/the_great_game/utils.clj b/src/the_great_game/utils.clj index 6ea3c84..60cee43 100644 --- a/src/the_great_game/utils.clj +++ b/src/the_great_game/utils.clj @@ -3,7 +3,7 @@ (defn cyclic? "True if two or more elements of `route` are identical" [route] - (not (= (count route)(count (set route))))) + (not= (count route)(count (set route)))) (defn deep-merge "Recursively merges maps. Stolen from diff --git a/src/the_great_game/world/routes.clj b/src/the_great_game/world/routes.clj index 93926ee..c45debc 100644 --- a/src/the_great_game/world/routes.clj +++ b/src/the_great_game/world/routes.clj @@ -9,11 +9,11 @@ (map (fn [to] (cons from to)) (remove - #(empty? %) + empty? (map (fn [route] - (filter - #(not (= from %)) + (remove + #(= from %) (if (some #(= % from) route) route))) routes)))) ([routes from to] @@ -30,14 +30,12 @@ (not (empty? steps)) (let [paths (remove cyclic? - (apply - concat - (map + (mapcat (fn [path] (map (fn [x] (concat path (rest x))) (find-routes routes (last path)))) - steps))) + steps)) found (filter #(= (last %) to) paths)] (if diff --git a/src/the_great_game/world/run.clj b/src/the_great_game/world/run.clj index 5abcc2d..b324101 100644 --- a/src/the_great_game/world/run.clj +++ b/src/the_great_game/world/run.clj @@ -1,17 +1,39 @@ (ns the-great-game.world.run "Run the whole simulation" - (:require [taoensso.timbre :as log] + (:require [environ.core :refer [env]] + [taoensso.timbre :as timbre] + [taoensso.timbre.appenders.3rd-party.rotor :as rotor] [the-great-game.gossip.gossip :as g] [the-great-game.merchants.merchants :as m] [the-great-game.merchants.markets :as k] [the-great-game.world.world :as w])) +(defn init + ([] + (init {})) + ([config] + (timbre/merge-config! + {:appenders + {:rotor (rotor/rotor-appender + {:path "the-great-game.log" + :max-size (* 512 1024) + :backlog 10})} + :level (or + (:log-level config) + (if (env :dev) :debug) + :info)}))) (defn run "The pipeline to run the simulation each game day. Returns a world like - this world, with all the various active elements updated." - [world] + this world, with all the various active elements updated. The optional + `date` argument, if supplied, is set as the `:date` of the returned world." + ([world] (g/run (m/run (k/run (w/run world))))) + ([world date] + (g/run + (m/run + (k/run + (w/run world date)))))) diff --git a/src/the_great_game/world/world.clj b/src/the_great_game/world/world.clj index d8a7c65..9334237 100644 --- a/src/the_great_game/world/world.clj +++ b/src/the_great_game/world/world.clj @@ -183,8 +183,10 @@ (-> world :cities city :prices commodity)) (defn run - "Return a world like this `world` with only the `:date` value updated - (incremented by one). For running other aspects of the simulation, see - [[the-great-game.world.run]]." - [world] - (assoc world :date (inc (or (:date world) 0)))) + "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]]." + ([world] + (run world (inc (or (:date world) 0)))) + ([world date] + (assoc world :date date)))