Tactical commit before major refactor

This commit is contained in:
Simon Brooke 2019-05-18 09:33:50 +01:00
parent 28db4866eb
commit 23e24bd381
12 changed files with 161 additions and 104 deletions

5
.gitignore vendored
View file

@ -2,13 +2,18 @@ pom.xml
pom.xml.asc pom.xml.asc
*.jar *.jar
*.class *.class
*.log
[0-9a-f]*-init.clj
/lib/ /lib/
/classes/ /classes/
/target/ /target/
/checkouts/ /checkouts/
/.clj-kondo/
.eastwood
.lein-deps-sum .lein-deps-sum
.lein-repl-history .lein-repl-history
.lein-plugins/ .lein-plugins/
.lein-failures .lein-failures
.nrepl-port .nrepl-port
.cpcache/ .cpcache/
*~

View file

@ -4,6 +4,7 @@
:output-path "docs" :output-path "docs"
:source-uri "https://github.com/simon-brooke/the-great-game/blob/master/{filepath}#L{line}"} :source-uri "https://github.com/simon-brooke/the-great-game/blob/master/{filepath}#L{line}"}
:dependencies [[org.clojure/clojure "1.8.0"] :dependencies [[org.clojure/clojure "1.8.0"]
[environ "1.1.0"]
[com.taoensso/timbre "4.10.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." :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" :license {:name "GNU General Public License,version 2.0 or (at your option) any later version"

View file

@ -49,9 +49,9 @@
[gossip world new-location] [gossip world new-location]
(let [id (cond (let [id (cond
(map? gossip) (map? gossip)
(:id (-> world :gossipe gossip) (-> world :gossips gossip :id)
(keyword? gossip) (keyword? gossip)
gossip))] gossip)]
(deep-merge (deep-merge
world world
{:gossips {:gossips

View file

@ -1,8 +1,7 @@
(ns the-great-game.merchants.markets (ns the-great-game.merchants.markets
"Adjusting quantities and prices in markets." "Adjusting quantities and prices in markets."
(:require [taoensso.timbre :as l :refer [info error]] (:require [taoensso.timbre :as l :refer [info error]]
[the-great-game.utils :refer [deep-merge]] [the-great-game.utils :refer [deep-merge]]))
[the-great-game.world.world :refer [actual-price default-world]]))
(defn new-price (defn new-price
"If `stock` is greater than the maximum of `supply` and `demand`, then "If `stock` is greater than the maximum of `supply` and `demand`, then
@ -30,19 +29,24 @@
su (or (-> c :supplies commodity) 0) su (or (-> c :supplies commodity) 0)
decrement (min st d) decrement (min st d)
increment (cond increment (cond
;; if its profitable to produce this commodity, the craftspeople ;; if we've two turns' production of this commodity in
;; of the city will do so. ;; 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 (> p 1) su
;; otherwise, if there isn't a turn's production in stock, top up ;; otherwise, if there isn't a turn's production in
;; the stock, so that there's something for incoming merchants to ;; stock, top up the stock, so that there's something for
;; buy ;; incoming merchants to buy
(> su st) (> su st)
(- su st) (- su st)
true 0) :else
0)
n (new-price p st su d)] n (new-price p st su d)]
(if (if
(not (= p n)) (not= p n)
(l/info "Price of " commodity " at " id " has changed from " (float p) " to " (float n))) (l/info "Price of" commodity "at" id "has changed from" (float p) "to" (float n)))
{:cities {id {:cities {id
{:stock {:stock
{commodity (+ (- st decrement) increment)} {commodity (+ (- st decrement) increment)}

View file

@ -1,6 +1,6 @@
(ns the-great-game.merchants.merchants (ns the-great-game.merchants.merchants
"Trade planning for merchants, primarily." "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.utils :refer [deep-merge]]
[the-great-game.gossip.gossip :refer [move-gossip]] [the-great-game.gossip.gossip :refer [move-gossip]]
[the-great-game.world.routes :refer [find-route]] [the-great-game.world.routes :refer [find-route]]
@ -30,7 +30,7 @@
(-> world :merchants merchant) (-> world :merchants merchant)
(map? merchant) (map? merchant)
merchant) merchant)
cargo (-> m :stock)] cargo (:stock m)]
(reduce (reduce
+ +
0 0
@ -65,26 +65,26 @@
Returned plans are maps with keys: Returned plans are maps with keys:
* :merchant - the id of the `merchant` for whom the plan was created; * :merchant - the id of the `merchant` for whom the plan was created;
* :origin - the city from which the trade starts; * :origin - the city from which the trade starts;
* :destination - the city to which the trade is planned; * :destination - the city to which the trade is planned;
* :commodity - the `commodity` to be carried; * :commodity - the `commodity` to be carried;
* :buy-price - the price at which that `commodity` can be bought; * :buy-price - the price at which that `commodity` can be bought;
* :expected-price - the price at which the `merchant` anticipates * :expected-price - the price at which the `merchant` anticipates
that `commodity` can be sold; that `commodity` can be sold;
* :distance - the number of stages in the planned journey * :distance - the number of stages in the planned journey
* :dist-to-home - the distance from `destination` to the `merchant`'s * :dist-to-home - the distance from `destination` to the `merchant`'s
home city." home city."
[merchant world commodity] [merchant world commodity]
(let [m (cond (let [m (cond
(keyword? merchant) (keyword? merchant)
(-> world :merchants merchant) (-> world :merchants merchant)
(map? merchant) (map? merchant)
merchant) merchant)
origin (-> m :location)] origin (:location m)]
(map (map
#(hash-map #(hash-map
:merchant (-> m :id) :merchant (:id m)
:origin origin :origin origin
:destination % :destination %
:commodity commodity :commodity commodity
@ -97,10 +97,10 @@
(find-route world origin %)) (find-route world origin %))
:dist-to-home (count :dist-to-home (count
(find-route (find-route
world world
(:home m) (:home m)
%))) %)))
(remove #(= % origin) (keys (-> world :cities)))))) (remove #(= % origin) (-> world :cities keys)))))
(defn nearest-with-targets (defn nearest-with-targets
"Return the distance to the nearest destination among those of these "Return the distance to the nearest destination among those of these
@ -183,7 +183,7 @@
merchant) merchant)
l (:location m)] l (:location m)]
(quot (quot
(-> m :cash) (:cash m)
(-> world :cities l :prices commodity)))) (-> world :cities l :prices commodity))))
(defn augment-plan (defn augment-plan
@ -219,7 +219,7 @@
(-> world :merchants merchant) (-> world :merchants merchant)
(map? merchant) (map? merchant)
merchant) merchant)
origin (-> m :location) origin (:location m)
available (-> world :cities origin :stock) available (-> world :cities origin :stock)
plans (map plans (map
#(augment-plan #(augment-plan
@ -228,7 +228,7 @@
(plan-trade m world %)) (plan-trade m world %))
(filter (filter
#(let [q (-> world :cities origin :stock %)] #(let [q (-> world :cities origin :stock %)]
(and (number? q) (> q 0))) (and (number? q) (pos? q)))
(keys available)))] (keys available)))]
(if (if
(not (empty? plans)) (not (empty? plans))
@ -281,48 +281,48 @@
a new trade, and bought appropriate stock for it. If no profitable trade a new trade, and bought appropriate stock for it. If no profitable trade
can be planned, the merchant is simply moved towards their home." can be planned, the merchant is simply moved towards their home."
[merchant world] [merchant world]
(deep-merge (let [m (cond
world (keyword? merchant)
(let [m (cond (-> world :merchants merchant)
(keyword? merchant) (map? merchant)
(-> world :merchants merchant) merchant)
(map? merchant) id (:id m)
merchant) location (:location m)
id (:id m) market (-> world :cities location)
location (:location m) plan (select-cargo merchant world)]
market (-> world :cities location) (l/debug "plan-and-buy: merchant" id)
plan (select-cargo merchant world)] (cond
(cond (not (empty? plan))
(not (empty? plan)) (let
(let [c (:commodity plan)
[c (:commodity plan) p (* (:quantity plan) (:buy-price plan))
p (* (:quantity plan) (:buy-price plan)) q (:quantity plan)]
q (:quantity plan)] (l/info "Merchant" id "bought" q "units of" c "at" location "for" p plan)
(l/info "Merchant " id " bought " q " units of " c " at " location " for " p) {: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 {: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 {id
{:location next-location}}} {:location next-location}}}
(move-gossip id world next-location))))))) (move-gossip id world next-location))))))
(defn re-plan (defn re-plan
"Having failed to sell a cargo at current location, re-plan a route to "Having failed to sell a cargo at current location, re-plan a route to
@ -336,6 +336,7 @@
id (:id m) id (:id m)
location (:location m) location (:location m)
plan (augment-plan m world (plan-trade m world (-> m :plan :commodity)))] plan (augment-plan m world (plan-trade m world (-> m :plan :commodity)))]
(l/debug "re-plan: merchant" id)
(deep-merge (deep-merge
world world
{:merchants {:merchants
@ -363,11 +364,11 @@
(map (map
#(* (-> m :stock %) (-> market :prices m)) #(* (-> m :stock %) (-> market :prices m))
(keys (:stock m))))] (keys (:stock m))))]
(l/debug "sell-and-buy: merchant" id)
(if (if
(>= (:cash market) stock-value) (>= (:cash market) stock-value)
(do (do
(l/info (l/info "Merchant" id "sells" (:stock m) "at" location "for" stock-value)
(apply str (flatten (list "Merchant " id " sells " (:stock m) " at " location " for " stock-value))))
(plan-and-buy (plan-and-buy
merchant merchant
(deep-merge (deep-merge
@ -385,7 +386,9 @@
(re-plan merchant world)))) (re-plan merchant world))))
(defn move-merchant (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] [merchant world]
(let [m (cond (let [m (cond
(keyword? merchant) (keyword? merchant)
@ -396,28 +399,42 @@
at-destination? (and (:plan m) (= (:location m) (-> m :plan :destination))) at-destination? (and (:plan m) (= (:location m) (-> m :plan :destination)))
plan (:plan m) plan (:plan m)
next-location (if plan next-location (if plan
(nth 1 (find-route world (:location m) (:destination plan))) (nth
(find-route
world
(:location m)
(:destination plan))
1)
(:location m))] (: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 (cond
;; if the merchant is at the destination of their current plan ;; if the merchant is at the destination of their current plan
;; sell all cargo and repurchase. ;; sell all cargo and repurchase.
at-destination? at-destination?
(sell-and-buy merchant world plan) (sell-and-buy merchant world)
;; if they don't have a plan, seek to create one ;; if they don't have a plan, seek to create one
(nil? (:plan m)) (nil? plan)
(plan-and-buy merchant world) (plan-and-buy merchant world)
;; otherwise, move one step towards their destination ;; otherwise, move one step towards their destination
true (and next-location (not= next-location (:location m)))
(deep-merge (do
{:merchants (l/info "Merchant " id " moving from " (:location m) " to " next-location)
{id (deep-merge
{:location next-location {:merchants
:known-prices (add-known-prices m world)}}} {id
(move-gossip id world next-location))))) {: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 (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] [world]
(try (try
(reduce (reduce
@ -429,7 +446,7 @@
(catch Exception any (catch Exception any
(l/error any "Failure while moving merchant " %) (l/error any "Failure while moving merchant " %)
{})) {}))
(keys (:merchants world)))) (keys (:merchants world))))
(catch Exception any (catch Exception any
(l/error any "Failure while moving merchants") (l/error any "Failure while moving merchants")
world))) world)))

View file

@ -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]]))

View file

@ -0,0 +1,2 @@
(ns the-great-game.merchants.strategies.simple
)

View file

@ -3,7 +3,7 @@
(defn cyclic? (defn cyclic?
"True if two or more elements of `route` are identical" "True if two or more elements of `route` are identical"
[route] [route]
(not (= (count route)(count (set route))))) (not= (count route)(count (set route))))
(defn deep-merge (defn deep-merge
"Recursively merges maps. Stolen from "Recursively merges maps. Stolen from

View file

@ -9,11 +9,11 @@
(map (map
(fn [to] (cons from to)) (fn [to] (cons from to))
(remove (remove
#(empty? %) empty?
(map (map
(fn [route] (fn [route]
(filter (remove
#(not (= from %)) #(= from %)
(if (some #(= % from) route) route))) (if (some #(= % from) route) route)))
routes)))) routes))))
([routes from to] ([routes from to]
@ -30,14 +30,12 @@
(not (empty? steps)) (not (empty? steps))
(let [paths (remove (let [paths (remove
cyclic? cyclic?
(apply (mapcat
concat
(map
(fn [path] (fn [path]
(map (map
(fn [x] (concat path (rest x))) (fn [x] (concat path (rest x)))
(find-routes routes (last path)))) (find-routes routes (last path))))
steps))) steps))
found (filter found (filter
#(= (last %) to) paths)] #(= (last %) to) paths)]
(if (if

View file

@ -1,17 +1,39 @@
(ns the-great-game.world.run (ns the-great-game.world.run
"Run the whole simulation" "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.gossip.gossip :as g]
[the-great-game.merchants.merchants :as m] [the-great-game.merchants.merchants :as m]
[the-great-game.merchants.markets :as k] [the-great-game.merchants.markets :as k]
[the-great-game.world.world :as w])) [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 (defn run
"The pipeline to run the simulation each game day. Returns a world like "The pipeline to run the simulation each game day. Returns a world like
this world, with all the various active elements updated." this world, with all the various active elements updated. The optional
[world] `date` argument, if supplied, is set as the `:date` of the returned world."
([world]
(g/run (g/run
(m/run (m/run
(k/run (k/run
(w/run world))))) (w/run world)))))
([world date]
(g/run
(m/run
(k/run
(w/run world date))))))

View file

@ -183,8 +183,10 @@
(-> world :cities city :prices commodity)) (-> world :cities city :prices commodity))
(defn run (defn run
"Return a world like this `world` with only the `:date` value updated "Return a world like this `world` with only the `:date` to this `date`
(incremented by one). For running other aspects of the simulation, see (or id `date` not supplied, the current value incremented by one). For
[[the-great-game.world.run]]." running other aspects of the simulation, see [[the-great-game.world.run]]."
[world] ([world]
(assoc world :date (inc (or (:date world) 0)))) (run world (inc (or (:date world) 0))))
([world date]
(assoc world :date date)))