From 23e24bd381b90327c257a3986d805cc937965adc Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sat, 18 May 2019 09:33:50 +0100 Subject: [PATCH 1/9] 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))) From 66388bc944d29f29e0c68fb1640f42dc3a1015f4 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sat, 18 May 2019 10:17:15 +0100 Subject: [PATCH 2/9] Refactor complete, all tests still pass --- docs/index.html | 2 +- docs/intro.html | 2 +- docs/the-great-game.core.html | 2 +- docs/the-great-game.gossip.gossip.html | 2 +- docs/the-great-game.merchants.markets.html | 2 +- ...e-great-game.merchants.merchant-utils.html | 3 + docs/the-great-game.merchants.merchants.html | 25 +- docs/the-great-game.merchants.planning.html | 26 ++ ...reat-game.merchants.strategies.simple.html | 4 + docs/the-great-game.utils.html | 2 +- docs/the-great-game.world.routes.html | 2 +- docs/the-great-game.world.run.html | 2 +- docs/the-great-game.world.world.html | 2 +- .../merchants/merchant_utils.clj | 92 ++++ src/the_great_game/merchants/merchants.clj | 437 +----------------- src/the_great_game/merchants/planning.clj | 154 +++++- .../merchants/strategies/simple.clj | 173 ++++++- src/the_great_game/utils.clj | 18 + .../merchants/merchant_utils_test.clj | 24 + .../{merchants_test.clj => planning_test.clj} | 21 +- 20 files changed, 509 insertions(+), 486 deletions(-) create mode 100644 docs/the-great-game.merchants.merchant-utils.html create mode 100644 docs/the-great-game.merchants.planning.html create mode 100644 docs/the-great-game.merchants.strategies.simple.html create mode 100644 src/the_great_game/merchants/merchant_utils.clj create mode 100644 test/the_great_game/merchants/merchant_utils_test.clj rename test/the_great_game/merchants/{merchants_test.clj => planning_test.clj} (81%) diff --git a/docs/index.html b/docs/index.html index 1f40cc8..5370800 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,3 +1,3 @@ -The-great-game 0.1.0-SNAPSHOT

The-great-game 0.1.0-SNAPSHOT

Released under the GNU General Public License,version 2.0 or (at your option) any later version

Prototype code towards the great game I've been writing about for ten years, and know I will never finish.

Installation

To install, add the following dependency to your project or build file:

[the-great-game "0.1.0-SNAPSHOT"]

Topics

Namespaces

the-great-game.core

TODO: write docs

Public variables and functions:

the-great-game.gossip.gossip

Interchange of news events between agents agents

Public variables and functions:

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

Public variables and functions:

the-great-game.utils

TODO: write docs

Public variables and functions:

the-great-game.world.routes

Conceptual (plan level) routes, represented as tuples of location ids.

Public variables and functions:

the-great-game.world.run

Run the whole simulation

Public variables and functions:

the-great-game.world.world

Access to data about the world

Public variables and functions:

\ No newline at end of file +The-great-game 0.1.0-SNAPSHOT

The-great-game 0.1.0-SNAPSHOT

Released under the GNU General Public License,version 2.0 or (at your option) any later version

Prototype code towards the great game I've been writing about for ten years, and know I will never finish.

Installation

To install, add the following dependency to your project or build file:

[the-great-game "0.1.0-SNAPSHOT"]

Topics

Namespaces

the-great-game.core

TODO: write docs

Public variables and functions:

the-great-game.gossip.gossip

Interchange of news events between agents agents

Public variables and functions:

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

Public variables and functions:

the-great-game.merchants.merchant-utils

Useful functions for doing low-level things with merchants.

the-great-game.merchants.merchants

Trade planning for merchants, primarily.

Public variables and functions:

the-great-game.merchants.planning

Trade planning for merchants, primarily.

the-great-game.merchants.strategies.simple

Default trading strategy for merchants.

Public variables and functions:

the-great-game.utils

TODO: write docs

Public variables and functions:

the-great-game.world.routes

Conceptual (plan level) routes, represented as tuples of location ids.

Public variables and functions:

the-great-game.world.run

Run the whole simulation

Public variables and functions:

the-great-game.world.world

Access to data about the world

Public variables and functions:

\ No newline at end of file diff --git a/docs/intro.html b/docs/intro.html index 7d118f7..3822424 100644 --- a/docs/intro.html +++ b/docs/intro.html @@ -1,6 +1,6 @@ -Introduction to the-great-game

Introduction to the-great-game

+Introduction to the-great-game

Introduction to the-great-game

The Great Game

In this essay I’m going to try to pull together a number of my architectural ideas about the Great Game which I know I’m never actually going to build - because it’s vastly too big for any one person to build - into one overall vision.

So, firstly, how does one characterise this game?

diff --git a/docs/the-great-game.core.html b/docs/the-great-game.core.html index 5fe59ef..504fb69 100644 --- a/docs/the-great-game.core.html +++ b/docs/the-great-game.core.html @@ -1,3 +1,3 @@ -the-great-game.core documentation

the-great-game.core

TODO: write docs

foo

(foo x)

I don’t do a whole lot.

\ No newline at end of file +the-great-game.core documentation

the-great-game.core

TODO: write docs

foo

(foo x)

I don’t do a whole lot.

\ No newline at end of file diff --git a/docs/the-great-game.gossip.gossip.html b/docs/the-great-game.gossip.gossip.html index 460937c..1245955 100644 --- a/docs/the-great-game.gossip.gossip.html +++ b/docs/the-great-game.gossip.gossip.html @@ -1,3 +1,3 @@ -the-great-game.gossip.gossip documentation

the-great-game.gossip.gossip

Interchange of news events between agents agents

dialogue

(dialogue enquirer respondent world)

Dialogue between an enquirer and an agent in this world; returns a map identical to enquirer except that its :gossip collection may have additional entries.

gather-news

(gather-news world)(gather-news world gossip)

TODO: write docs

move-gossip

(move-gossip gossip world new-location)

Return a world like this world but with this gossip moved to this new-location. Many gossips are essentially shadow-records of agents of other types, and the movement if the gossip should be controlled by the run function of the type of the record they shadow. The #run function below does NOT call this function.

run

(run world)

Return a world like this world, with news items exchanged between gossip agents.

\ No newline at end of file +the-great-game.gossip.gossip documentation

the-great-game.gossip.gossip

Interchange of news events between agents agents

dialogue

(dialogue enquirer respondent world)

Dialogue between an enquirer and an agent in this world; returns a map identical to enquirer except that its :gossip collection may have additional entries.

gather-news

(gather-news world)(gather-news world gossip)

TODO: write docs

move-gossip

(move-gossip gossip world new-location)

Return a world like this world but with this gossip moved to this new-location. Many gossips are essentially shadow-records of agents of other types, and the movement if the gossip should be controlled by the run function of the type of the record they shadow. The #run function below does NOT call this function.

run

(run world)

Return a world like this world, with news items exchanged between gossip agents.

\ No newline at end of file diff --git a/docs/the-great-game.merchants.markets.html b/docs/the-great-game.merchants.markets.html index 51fd23c..6358e64 100644 --- a/docs/the-great-game.merchants.markets.html +++ b/docs/the-great-game.merchants.markets.html @@ -1,3 +1,3 @@ -the-great-game.merchants.markets documentation

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

adjust-quantity-and-price

(adjust-quantity-and-price world city commodity)

Adjust the quantity of this commodity currently in stock in this city of this world. Return a fragmentary world which can be deep-merged into this world.

new-price

(new-price old stock supply demand)

If stock is greater than the maximum of supply and demand, then there is surplus and old price is too high, so shold be reduced. If lower, then it is too low and should be increased.

run

(run world)

Return a world like this world, with quantities and prices in markets updated to reflect supply and demand.

update-markets

(update-markets world)(update-markets world city)(update-markets world city commodity)

Return a world like this world, with quantities and prices in markets updated to reflect supply and demand. If city or city and commodity are specified, return a fragmentary world with only the changes for that city (and commodity if specified) populated.

\ No newline at end of file +the-great-game.merchants.markets documentation

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

adjust-quantity-and-price

(adjust-quantity-and-price world city commodity)

Adjust the quantity of this commodity currently in stock in this city of this world. Return a fragmentary world which can be deep-merged into this world.

new-price

(new-price old stock supply demand)

If stock is greater than the maximum of supply and demand, then there is surplus and old price is too high, so shold be reduced. If lower, then it is too low and should be increased.

run

(run world)

Return a world like this world, with quantities and prices in markets updated to reflect supply and demand.

update-markets

(update-markets world)(update-markets world city)(update-markets world city commodity)

Return a world like this world, with quantities and prices in markets updated to reflect supply and demand. If city or city and commodity are specified, return a fragmentary world with only the changes for that city (and commodity if specified) populated.

\ No newline at end of file diff --git a/docs/the-great-game.merchants.merchant-utils.html b/docs/the-great-game.merchants.merchant-utils.html new file mode 100644 index 0000000..8936d0a --- /dev/null +++ b/docs/the-great-game.merchants.merchant-utils.html @@ -0,0 +1,3 @@ + +the-great-game.merchants.merchant-utils documentation

the-great-game.merchants.merchant-utils

Useful functions for doing low-level things with merchants.

add-known-prices

(add-known-prices merchant world)

Add the current prices at this merchant’s location in the world to a new cacke of known prices, and return it.

add-stock

(add-stock a b)

Where a and b are both maps all of whose values are numbers, return a map whose keys are a union of the keys of a and b and whose values are the sums of their respective values.

burden

(burden merchant world)

The total weight of the current cargo carried by this merchant in this world.

can-afford

(can-afford merchant world commodity)

Return the number of units of this commodity which this merchant can afford to buy in this world.

can-carry

(can-carry merchant world commodity)

Return the number of units of this commodity which this merchant can carry in this world, given their current burden.

expected-price

(expected-price merchant commodity city)

Find the price anticipated, given this world, by this merchant for this commodity in this city. If no information, assume 1. merchant should be passed as a map, commodity and city should be passed as keywords.

\ No newline at end of file diff --git a/docs/the-great-game.merchants.merchants.html b/docs/the-great-game.merchants.merchants.html index 78436a0..f1b8e01 100644 --- a/docs/the-great-game.merchants.merchants.html +++ b/docs/the-great-game.merchants.merchants.html @@ -1,26 +1,3 @@ -the-great-game.merchants.merchants documentation

the-great-game.merchants.merchants

Trade planning for merchants, primarily.

add-known-prices

(add-known-prices merchant world)

Add the current prices at this merchant’s location in the world to a new cacke of known prices, and return it.

add-stock

(add-stock a b)

Where a and b are both maps all of whose values are numbers, return a map whose keys are a union of the keys of a and b and whose values are the sums of their respective values.

augment-plan

(augment-plan merchant world plan)

Augment this plan constructed in this world for this merchant with the :quantity of goods which should be bought and the :expected-profit of the trade.

-

Returns the augmented plan.

burden

(burden merchant world)

The total weight of the current cargo carried by this merchant in this world.

can-afford

(can-afford merchant world commodity)

Return the number of units of this commodity which this merchant can afford to buy in this world.

can-carry

(can-carry merchant world commodity)

Return the number of units of this commodity which this merchant can carry in this world, given their current burden.

expected-price

(expected-price merchant commodity city)

Find the price anticipated, given this world, by this merchant for this commodity in this city. If no information, assume 1. merchant should be passed as a map, commodity and city should be passed as keywords.

generate-trade-plans

(generate-trade-plans merchant world commodity)

Generate all possible trade plans for this merchant and this commodity in this world.

-

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.
  • -

make-target-filter

(make-target-filter targets)

Construct a filter which, when applied to a list of maps, will pass those which match these targets, where each target is a tuple [key value].

move-merchant

(move-merchant merchant world)

Handle general en route movement of this merchant in this world.

nearest-with-targets

(nearest-with-targets plans targets)

Return the distance to the nearest destination among those of these plans which match these targets. Plans are expected to be plans as returned by generate-trade-plans, q.v.; targets are expected to be as accepted by make-target-filter, q.v.

plan-and-buy

(plan-and-buy merchant world)

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.

plan-trade

(plan-trade merchant world commodity)

Find the best destination in this world for this commodity given this merchant and this origin. If two cities are anticipated to offer the same price, the nearer should be preferred; if two are equally distant, the ones nearer to the merchant’s home should be preferred. merchant may be passed as a map or a keyword; commodity should be passed as a keyword.

-

The returned plan is a map 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.
  • -

re-plan

(re-plan merchant world)

Having failed to sell a cargo at current location, re-plan a route to sell the current cargo. Returns a revised world.

run

(run world)

Return a world like this world, but with each merchant moved.

select-cargo

(select-cargo merchant world)

A merchant, in a given location in a world, will choose to buy a cargo within the limit they are capable of carrying, which they can anticipate selling for a profit at a destination.

sell-and-buy

(sell-and-buy merchant world)

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.

\ No newline at end of file +the-great-game.merchants.merchants documentation

the-great-game.merchants.merchants

Trade planning for merchants, primarily.

run

(run world)

Return a partial world based on this world, but with each merchant moved.

\ No newline at end of file diff --git a/docs/the-great-game.merchants.planning.html b/docs/the-great-game.merchants.planning.html new file mode 100644 index 0000000..9e74759 --- /dev/null +++ b/docs/the-great-game.merchants.planning.html @@ -0,0 +1,26 @@ + +the-great-game.merchants.planning documentation

the-great-game.merchants.planning

Trade planning for merchants, primarily.

augment-plan

(augment-plan merchant world plan)

Augment this plan constructed in this world for this merchant with the :quantity of goods which should be bought and the :expected-profit of the trade.

+

Returns the augmented plan.

generate-trade-plans

(generate-trade-plans merchant world commodity)

Generate all possible trade plans for this merchant and this commodity in this world.

+

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.
  • +

nearest-with-targets

(nearest-with-targets plans targets)

Return the distance to the nearest destination among those of these plans which match these targets. Plans are expected to be plans as returned by generate-trade-plans, q.v.; targets are expected to be as accepted by make-target-filter, q.v.

plan-trade

(plan-trade merchant world commodity)

Find the best destination in this world for this commodity given this merchant and this origin. If two cities are anticipated to offer the same price, the nearer should be preferred; if two are equally distant, the ones nearer to the merchant’s home should be preferred. merchant may be passed as a map or a keyword; commodity should be passed as a keyword.

+

The returned plan is a map 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.
  • +

select-cargo

(select-cargo merchant world)

A merchant, in a given location in a world, will choose to buy a cargo within the limit they are capable of carrying, which they can anticipate selling for a profit at a destination.

\ No newline at end of file diff --git a/docs/the-great-game.merchants.strategies.simple.html b/docs/the-great-game.merchants.strategies.simple.html new file mode 100644 index 0000000..6b707f0 --- /dev/null +++ b/docs/the-great-game.merchants.strategies.simple.html @@ -0,0 +1,4 @@ + +the-great-game.merchants.strategies.simple documentation

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.

move-merchant

(move-merchant merchant 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.

plan-and-buy

(plan-and-buy merchant world)

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.

re-plan

(re-plan merchant world)

Having failed to sell a cargo at current location, re-plan a route to sell the current cargo. Returns a revised world.

sell-and-buy

(sell-and-buy merchant world)

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.

\ No newline at end of file diff --git a/docs/the-great-game.utils.html b/docs/the-great-game.utils.html index bb4ad9f..b46fa14 100644 --- a/docs/the-great-game.utils.html +++ b/docs/the-great-game.utils.html @@ -1,3 +1,3 @@ -the-great-game.utils documentation

the-great-game.utils

TODO: write docs

cyclic?

(cyclic? route)

True if two or more elements of route are identical

deep-merge

(deep-merge & maps)
\ No newline at end of file +the-great-game.utils documentation

the-great-game.utils

TODO: write docs

cyclic?

(cyclic? route)

True if two or more elements of route are identical

deep-merge

(deep-merge & maps)

make-target-filter

(make-target-filter targets)

Construct a filter which, when applied to a list of maps, will pass those which match these targets, where each target is a tuple [key value].

\ No newline at end of file diff --git a/docs/the-great-game.world.routes.html b/docs/the-great-game.world.routes.html index d5d1cbd..8108252 100644 --- a/docs/the-great-game.world.routes.html +++ b/docs/the-great-game.world.routes.html @@ -1,3 +1,3 @@ -the-great-game.world.routes documentation

the-great-game.world.routes

Conceptual (plan level) routes, represented as tuples of location ids.

find-route

(find-route world-or-routes from to)

Find a single route from from to to in this world-or-routes, which may be either a world as defined in the-great-game.world.world or else a sequence of tuples of keywords.

find-routes

(find-routes routes from)(find-routes routes from to)(find-routes routes from to steps)

Find routes from among these routes from from; if to is supplied, to to, by breadth-first search.

\ No newline at end of file +the-great-game.world.routes documentation

the-great-game.world.routes

Conceptual (plan level) routes, represented as tuples of location ids.

find-route

(find-route world-or-routes from to)

Find a single route from from to to in this world-or-routes, which may be either a world as defined in the-great-game.world.world or else a sequence of tuples of keywords.

find-routes

(find-routes routes from)(find-routes routes from to)(find-routes routes from to steps)

Find routes from among these routes from from; if to is supplied, to to, by breadth-first search.

\ No newline at end of file diff --git a/docs/the-great-game.world.run.html b/docs/the-great-game.world.run.html index dd8152d..2ae1008 100644 --- a/docs/the-great-game.world.run.html +++ b/docs/the-great-game.world.run.html @@ -1,3 +1,3 @@ -the-great-game.world.run documentation

the-great-game.world.run

Run the whole simulation

run

(run world)

The pipeline to run the simulation each game day. Returns a world like this world, with all the various active elements updated.

\ No newline at end of file +the-great-game.world.run documentation

the-great-game.world.run

Run the whole simulation

init

(init)(init config)

TODO: write docs

run

(run world)(run world date)

The pipeline to run the simulation each game day. Returns a world like this world, with all the various active elements updated. The optional date argument, if supplied, is set as the :date of the returned world.

\ No newline at end of file diff --git a/docs/the-great-game.world.world.html b/docs/the-great-game.world.world.html index f2d2aa0..efe608f 100644 --- a/docs/the-great-game.world.world.html +++ b/docs/the-great-game.world.world.html @@ -1,3 +1,3 @@ -the-great-game.world.world documentation

the-great-game.world.world

Access to data about the world

actual-price

(actual-price world commodity city)

Find the actual current price of this commodity in this city given this world. NOTE that merchants can only know the actual prices in the city in which they are currently located.

default-world

A basic world for testing concepts

run

(run world)

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.

\ No newline at end of file +the-great-game.world.world documentation

the-great-game.world.world

Access to data about the world

actual-price

(actual-price world commodity city)

Find the actual current price of this commodity in this city given this world. NOTE that merchants can only know the actual prices in the city in which they are currently located.

default-world

A basic world for testing concepts

run

(run world)(run world date)

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.

\ No newline at end of file diff --git a/src/the_great_game/merchants/merchant_utils.clj b/src/the_great_game/merchants/merchant_utils.clj new file mode 100644 index 0000000..b8cd969 --- /dev/null +++ b/src/the_great_game/merchants/merchant_utils.clj @@ -0,0 +1,92 @@ +(ns the-great-game.merchants.merchant-utils + "Useful functions for doing low-level things with merchants.") + +(defn expected-price + "Find the price anticipated, given this `world`, by this `merchant` for + this `commodity` in this `city`. If no information, assume 1. + `merchant` should be passed as a map, `commodity` and `city` should be passed as keywords." + [merchant commodity city] + (or + (:price + (last + (sort-by + :date + (-> merchant :known-prices city commodity)))) + 1)) + +(defn burden + "The total weight of the current cargo carried by this `merchant` in this + `world`." + [merchant world] + (let [m (cond + (keyword? merchant) + (-> world :merchants merchant) + (map? merchant) + merchant) + cargo (:stock m)] + (reduce + + + 0 + (map + #(* (cargo %) (-> world :commodities % :weight)) + (keys cargo))))) + + +(defn can-carry + "Return the number of units of this `commodity` which this `merchant` + can carry in this `world`, given their current burden." + [merchant world commodity] + (let [m (cond + (keyword? merchant) + (-> world :merchants merchant) + (map? merchant) + merchant)] + (quot + (- (:capacity m) (burden m world)) + (-> world :commodities commodity :weight)))) + +(defn can-afford + "Return the number of units of this `commodity` which this `merchant` + can afford to buy in this `world`." + [merchant world commodity] + (let [m (cond + (keyword? merchant) + (-> world :merchants merchant) + (map? merchant) + merchant) + l (:location m)] + (quot + (:cash m) + (-> world :cities l :prices commodity)))) + +(defn add-stock + "Where `a` and `b` are both maps all of whose values are numbers, return + a map whose keys are a union of the keys of `a` and `b` and whose values + are the sums of their respective values." + [a b] + (reduce + merge + a + (map + #(hash-map % (+ (or (a %) 0) (or (b %) 0))) + (keys b)))) + +(defn add-known-prices + "Add the current prices at this `merchant`'s location in the `world` + to a new cacke of known prices, and return it." + [merchant world] + (let [m (cond + (keyword? merchant) + (-> world :merchants merchant) + (map? merchant) + merchant) + k (:known-prices m) + l (:location m) + d (:date world) + p (-> world :cities l :prices)] + (reduce + merge + k + (map + #(hash-map % (apply vector cons {:price (p %) :date d} (k %))) + (-> world :commodities keys))))) diff --git a/src/the_great_game/merchants/merchants.clj b/src/the_great_game/merchants/merchants.clj index 7b10dd2..9ef160d 100644 --- a/src/the_great_game/merchants/merchants.clj +++ b/src/the_great_game/merchants/merchants.clj @@ -2,437 +2,9 @@ "Trade planning for merchants, primarily." (: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]] - [the-great-game.world.world :refer [actual-price default-world]])) + [the-great-game.merchants.strategies.simple :refer [move-merchant]])) -(defn expected-price - "Find the price anticipated, given this `world`, by this `merchant` for - this `commodity` in this `city`. If no information, assume 1. - `merchant` should be passed as a map, `commodity` and `city` should be passed as keywords." - [merchant commodity city] - (or - (:price - (last - (sort-by - :date - (-> merchant :known-prices city commodity)))) - 1)) - - -(defn burden - "The total weight of the current cargo carried by this `merchant` in this - `world`." - [merchant world] - (let [m (cond - (keyword? merchant) - (-> world :merchants merchant) - (map? merchant) - merchant) - cargo (:stock m)] - (reduce - + - 0 - (map - #(* (cargo %) (-> world :commodities % :weight)) - (keys cargo))))) - - -(defn make-target-filter - "Construct a filter which, when applied to a list of maps, - will pass those which match these `targets`, where each target - is a tuple [key value]." - ;; TODO: this would probably be more elegant as a macro - [targets] - (eval - (list - 'fn - (vector 'plan) - (cons - 'and - (map - #(list - '= - (list (first %) 'plan) - (nth % 1)) - targets))))) - - -(defn generate-trade-plans - "Generate all possible trade plans for this `merchant` and this `commodity` - in this `world`. - - 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 world commodity] - (let [m (cond - (keyword? merchant) - (-> world :merchants merchant) - (map? merchant) - merchant) - origin (:location m)] - (map - #(hash-map - :merchant (:id m) - :origin origin - :destination % - :commodity commodity - :buy-price (actual-price world commodity origin) - :expected-price (expected-price - m - commodity - %) - :distance (count - (find-route world origin %)) - :dist-to-home (count - (find-route - world - (:home m) - %))) - (remove #(= % origin) (-> world :cities keys))))) - -(defn nearest-with-targets - "Return the distance to the nearest destination among those of these - `plans` which match these `targets`. Plans are expected to be plans - as returned by `generate-trade-plans`, q.v.; `targets` are expected to be - as accepted by `make-target-filter`, q.v." - [plans targets] - (apply - min - (map - :distance - (filter - (make-target-filter targets) - plans)))) - -(defn plan-trade - "Find the best destination in this `world` for this `commodity` given this - `merchant` and this `origin`. If two cities are anticipated to offer the - same price, the nearer should be preferred; if two are equally distant, the - ones nearer to the merchant's home should be preferred. - `merchant` may be passed as a map or a keyword; `commodity` should be - passed as a keyword. - - The returned plan is a map 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 world commodity] - (let [plans (generate-trade-plans merchant world commodity) - best-prices (filter - (make-target-filter - [[:expected-price - (apply - max - (filter number? (map :expected-price plans)))]]) - plans)] - (first - (sort-by - ;; all other things being equal, a merchant would prefer to end closer - ;; to home. - #(- 0 (:dist-to-home %)) - ;; a merchant will seek the best price, but won't go further than - ;; needed to get it. - (filter - (make-target-filter - [[:distance - (apply min (filter number? (map :distance best-prices)))]]) - best-prices))))) - - -(defn can-carry - "Return the number of units of this `commodity` which this `merchant` - can carry in this `world`, given their current burden." - [merchant world commodity] - (let [m (cond - (keyword? merchant) - (-> world :merchants merchant) - (map? merchant) - merchant)] - (quot - (- (:capacity m) (burden m world)) - (-> world :commodities commodity :weight)))) - -(defn can-afford - "Return the number of units of this `commodity` which this `merchant` - can afford to buy in this `world`." - [merchant world commodity] - (let [m (cond - (keyword? merchant) - (-> world :merchants merchant) - (map? merchant) - merchant) - l (:location m)] - (quot - (:cash m) - (-> world :cities l :prices commodity)))) - -(defn augment-plan - "Augment this `plan` constructed in this `world` for this `merchant` with - the `:quantity` of goods which should be bought and the `:expected-profit` - of the trade. - - Returns the augmented plan." - [merchant world plan] - (let [c (:commodity plan) - o (:origin plan) - q (min - (or - (-> world :cities o :stock c) - 0) - (can-carry merchant world c) - (can-afford merchant world c)) - p (* q (- (:expected-price plan) (:buy-price plan)))] - (assoc plan :quantity q :expected-profit p))) - -;; (-> default-world :cities :buckie :stock :iron) -;; (burden :fiona default-world) -;; (-> default-world :commodities :iron :weight) -;; (quot 0 10) - -(defn select-cargo - "A `merchant`, in a given location in a `world`, will choose to buy a cargo - within the limit they are capable of carrying, which they can anticipate - selling for a profit at a destination." - [merchant world] - (let [m (cond - (keyword? merchant) - (-> world :merchants merchant) - (map? merchant) - merchant) - origin (:location m) - available (-> world :cities origin :stock) - plans (map - #(augment-plan - m - world - (plan-trade m world %)) - (filter - #(let [q (-> world :cities origin :stock %)] - (and (number? q) (pos? q))) - (keys available)))] - (if - (not (empty? plans)) - (first - (sort-by - #(- 0 (:dist-to-home %)) - (filter - (make-target-filter - [[:expected-profit - (apply max (filter number? (map :expected-profit plans)))]]) - plans)))))) - -(defn add-stock - "Where `a` and `b` are both maps all of whose values are numbers, return - a map whose keys are a union of the keys of `a` and `b` and whose values - are the sums of their respective values." - [a b] - (reduce - merge - a - (map - #(hash-map % (+ (or (a %) 0) (or (b %) 0))) - (keys b)))) - -(defn add-known-prices - "Add the current prices at this `merchant`'s location in the `world` - to a new cacke of known prices, and return it." - [merchant world] - (let [m (cond - (keyword? merchant) - (-> world :merchants merchant) - (map? merchant) - merchant) - k (:known-prices m) - l (:location m) - d (:date world) - p (-> world :cities l :prices)] - (reduce - merge - k - (map - #(hash-map % (apply vector cons {:price (p %) :date d} (k %))) - (-> world :commodities keys))))) - -;;; Right, from here on in we're actually moving things in the world, so -;;; functions generally return modified partial worlds. - -(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)))) - (defn run "Return a partial world based on this `world`, but with each merchant moved." [world] @@ -442,11 +14,14 @@ world (map #(try - (move-merchant % world) + (let [move-fn (or + (-> world :merchants % :move-fn) + move-merchant)] + (apply move-fn (list % world))) (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 index eb2a2ef..8ac2d01 100644 --- a/src/the_great_game/merchants/planning.clj +++ b/src/the_great_game/merchants/planning.clj @@ -1,6 +1,156 @@ (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]] + (:require [the-great-game.utils :refer [deep-merge make-target-filter]] + [the-great-game.merchants.merchant-utils :refer :all] [the-great-game.world.routes :refer [find-route]] [the-great-game.world.world :refer [actual-price default-world]])) + +(defn generate-trade-plans + "Generate all possible trade plans for this `merchant` and this `commodity` + in this `world`. + + 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 world commodity] + (let [m (cond + (keyword? merchant) + (-> world :merchants merchant) + (map? merchant) + merchant) + origin (:location m)] + (map + #(hash-map + :merchant (:id m) + :origin origin + :destination % + :commodity commodity + :buy-price (actual-price world commodity origin) + :expected-price (expected-price + m + commodity + %) + :distance (count + (find-route world origin %)) + :dist-to-home (count + (find-route + world + (:home m) + %))) + (remove #(= % origin) (-> world :cities keys))))) + +(defn nearest-with-targets + "Return the distance to the nearest destination among those of these + `plans` which match these `targets`. Plans are expected to be plans + as returned by `generate-trade-plans`, q.v.; `targets` are expected to be + as accepted by `make-target-filter`, q.v." + [plans targets] + (apply + min + (map + :distance + (filter + (make-target-filter targets) + plans)))) + +(defn plan-trade + "Find the best destination in this `world` for this `commodity` given this + `merchant` and this `origin`. If two cities are anticipated to offer the + same price, the nearer should be preferred; if two are equally distant, the + ones nearer to the merchant's home should be preferred. + `merchant` may be passed as a map or a keyword; `commodity` should be + passed as a keyword. + + The returned plan is a map 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 world commodity] + (let [plans (generate-trade-plans merchant world commodity) + best-prices (filter + (make-target-filter + [[:expected-price + (apply + max + (filter number? (map :expected-price plans)))]]) + plans)] + (first + (sort-by + ;; all other things being equal, a merchant would prefer to end closer + ;; to home. + #(- 0 (:dist-to-home %)) + ;; a merchant will seek the best price, but won't go further than + ;; needed to get it. + (filter + (make-target-filter + [[:distance + (apply min (filter number? (map :distance best-prices)))]]) + best-prices))))) + +(defn augment-plan + "Augment this `plan` constructed in this `world` for this `merchant` with + the `:quantity` of goods which should be bought and the `:expected-profit` + of the trade. + + Returns the augmented plan." + [merchant world plan] + (let [c (:commodity plan) + o (:origin plan) + q (min + (or + (-> world :cities o :stock c) + 0) + (can-carry merchant world c) + (can-afford merchant world c)) + p (* q (- (:expected-price plan) (:buy-price plan)))] + (assoc plan :quantity q :expected-profit p))) + +(defn select-cargo + "A `merchant`, in a given location in a `world`, will choose to buy a cargo + within the limit they are capable of carrying, which they can anticipate + selling for a profit at a destination." + [merchant world] + (let [m (cond + (keyword? merchant) + (-> world :merchants merchant) + (map? merchant) + merchant) + origin (:location m) + available (-> world :cities origin :stock) + plans (map + #(augment-plan + m + world + (plan-trade m world %)) + (filter + #(let [q (-> world :cities origin :stock %)] + (and (number? q) (pos? q))) + (keys available)))] + (if + (not (empty? plans)) + (first + (sort-by + #(- 0 (:dist-to-home %)) + (filter + (make-target-filter + [[:expected-profit + (apply max (filter number? (map :expected-profit plans)))]]) + plans)))))) + diff --git a/src/the_great_game/merchants/strategies/simple.clj b/src/the_great_game/merchants/strategies/simple.clj index 621d29c..d43e952 100644 --- a/src/the_great_game/merchants/strategies/simple.clj +++ b/src/the_great_game/merchants/strategies/simple.clj @@ -1,2 +1,173 @@ (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)))) + diff --git a/src/the_great_game/utils.clj b/src/the_great_game/utils.clj index 60cee43..98c2f6f 100644 --- a/src/the_great_game/utils.clj +++ b/src/the_great_game/utils.clj @@ -15,3 +15,21 @@ (last xs)))] (reduce m maps))) +(defn make-target-filter + "Construct a filter which, when applied to a list of maps, + will pass those which match these `targets`, where each target + is a tuple [key value]." + ;; TODO: this would probably be more elegant as a macro + [targets] + (eval + (list + 'fn + (vector 'm) + (cons + 'and + (map + #(list + '= + (list (first %) 'm) + (nth % 1)) + targets))))) diff --git a/test/the_great_game/merchants/merchant_utils_test.clj b/test/the_great_game/merchants/merchant_utils_test.clj new file mode 100644 index 0000000..2f9071e --- /dev/null +++ b/test/the_great_game/merchants/merchant_utils_test.clj @@ -0,0 +1,24 @@ +(ns the-great-game.merchants.merchant-utils-test + (:require [clojure.test :refer :all] + [the-great-game.utils :refer [deep-merge]] + [the-great-game.world.world :refer [default-world]] + [the-great-game.merchants.merchant-utils :refer :all])) + +(deftest expected-price-test + (testing "Anticipated prices in markets" + (let [world (deep-merge + default-world + {:merchants + {:archie + {:known-prices + {:buckie + {:iron + [{:price 1.7 :date 1} + {:price 2 :date 0}]}}}}})] + (let [actual (expected-price (-> world :merchants :archie) :fish :edinburgh) + expected 1] ;; + (is (= actual expected) "if no information assume 1")) + (let [actual (expected-price (-> world :merchants :archie) :iron :buckie) + expected 1.7] ;; + (is (= actual expected) "if information select the most recent"))))) + diff --git a/test/the_great_game/merchants/merchants_test.clj b/test/the_great_game/merchants/planning_test.clj similarity index 81% rename from test/the_great_game/merchants/merchants_test.clj rename to test/the_great_game/merchants/planning_test.clj index 8089c86..5662e35 100644 --- a/test/the_great_game/merchants/merchants_test.clj +++ b/test/the_great_game/merchants/planning_test.clj @@ -1,26 +1,9 @@ -(ns the-great-game.merchants.merchants-test +(ns the-great-game.merchants.planning-test (:require [clojure.test :refer :all] [the-great-game.utils :refer [deep-merge]] [the-great-game.world.world :refer [default-world]] - [the-great-game.merchants.merchants :refer :all])) + [the-great-game.merchants.planning :refer :all])) -(deftest expected-price-test - (testing "Anticipated prices in markets" - (let [world (deep-merge - default-world - {:merchants - {:archie - {:known-prices - {:buckie - {:iron - [{:price 1.7 :date 1} - {:price 2 :date 0}]}}}}})] - (let [actual (expected-price (-> world :merchants :archie) :fish :edinburgh) - expected 1] ;; - (is (= actual expected) "if no information assume 1")) - (let [actual (expected-price (-> world :merchants :archie) :iron :buckie) - expected 1.7] ;; - (is (= actual expected) "if information select the most recent"))))) (deftest plan-trade-test (testing "Lower level trade planning" From 4e94084f6dc5b71713461b36b4021b90814ee1fd Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 26 May 2019 09:58:04 +0100 Subject: [PATCH 3/9] Stage one in integrating Codox and Cloverage output --- doc/Population.ods | Bin 0 -> 16101 bytes doc/economy.md | 49 ++ doc/modelling_trading_cost_and_risk.md | 9 + doc/sexual-dimorphism.md | 45 ++ docs/_config.yml | 1 - docs/cloverage/coverage.css | 40 ++ docs/cloverage/index.html | 192 ++++++ docs/cloverage/the_great_game/core.clj.html | 26 + .../the_great_game/gossip/gossip.clj.html | 203 ++++++ .../the_great_game/merchants/markets.clj.html | 260 ++++++++ .../merchants/merchant_utils.clj.html | 284 +++++++++ .../merchants/merchants.clj.html | 92 +++ .../merchants/planning.clj.html | 485 +++++++++++++++ .../merchants/strategies/simple.clj.html | 527 ++++++++++++++++ docs/cloverage/the_great_game/utils.clj.html | 113 ++++ .../the_great_game/world/routes.clj.html | 173 ++++++ .../the_great_game/world/run.clj.html | 125 ++++ .../the_great_game/world/world.clj.html | 584 ++++++++++++++++++ docs/{ => codox}/css/default.css | 0 docs/{ => codox}/css/highlight.css | 0 docs/codox/economy.html | 33 + docs/codox/index.html | 3 + docs/{ => codox}/intro.html | 2 +- docs/{ => codox}/js/highlight.min.js | 0 docs/{ => codox}/js/jquery.min.js | 0 docs/{ => codox}/js/page_effects.js | 0 .../modelling_trading_cost_and_risk.html | 7 + docs/codox/sexual-dimorphism.html | 28 + docs/codox/the-great-game.core.html | 3 + docs/codox/the-great-game.gossip.gossip.html | 3 + .../the-great-game.merchants.markets.html | 3 + ...e-great-game.merchants.merchant-utils.html | 3 + .../the-great-game.merchants.merchants.html | 3 + .../the-great-game.merchants.planning.html | 26 + ...reat-game.merchants.strategies.simple.html | 4 + docs/codox/the-great-game.utils.html | 3 + docs/codox/the-great-game.world.routes.html | 3 + docs/codox/the-great-game.world.run.html | 3 + docs/codox/the-great-game.world.world.html | 3 + docs/index.html | 3 - docs/the-great-game.core.html | 3 - docs/the-great-game.gossip.gossip.html | 3 - docs/the-great-game.merchants.markets.html | 3 - ...e-great-game.merchants.merchant-utils.html | 3 - docs/the-great-game.merchants.merchants.html | 3 - docs/the-great-game.merchants.planning.html | 26 - ...reat-game.merchants.strategies.simple.html | 4 - docs/the-great-game.utils.html | 3 - docs/the-great-game.world.routes.html | 3 - docs/the-great-game.world.run.html | 3 - docs/the-great-game.world.world.html | 3 - project.clj | 14 +- src/the_great_game/merchants/planning.clj | 5 +- 53 files changed, 3353 insertions(+), 64 deletions(-) create mode 100644 doc/Population.ods create mode 100644 doc/economy.md create mode 100644 doc/modelling_trading_cost_and_risk.md create mode 100644 doc/sexual-dimorphism.md delete mode 100644 docs/_config.yml create mode 100644 docs/cloverage/coverage.css create mode 100644 docs/cloverage/index.html create mode 100644 docs/cloverage/the_great_game/core.clj.html create mode 100644 docs/cloverage/the_great_game/gossip/gossip.clj.html create mode 100644 docs/cloverage/the_great_game/merchants/markets.clj.html create mode 100644 docs/cloverage/the_great_game/merchants/merchant_utils.clj.html create mode 100644 docs/cloverage/the_great_game/merchants/merchants.clj.html create mode 100644 docs/cloverage/the_great_game/merchants/planning.clj.html create mode 100644 docs/cloverage/the_great_game/merchants/strategies/simple.clj.html create mode 100644 docs/cloverage/the_great_game/utils.clj.html create mode 100644 docs/cloverage/the_great_game/world/routes.clj.html create mode 100644 docs/cloverage/the_great_game/world/run.clj.html create mode 100644 docs/cloverage/the_great_game/world/world.clj.html rename docs/{ => codox}/css/default.css (100%) rename docs/{ => codox}/css/highlight.css (100%) create mode 100644 docs/codox/economy.html create mode 100644 docs/codox/index.html rename docs/{ => codox}/intro.html (87%) rename docs/{ => codox}/js/highlight.min.js (100%) rename docs/{ => codox}/js/jquery.min.js (100%) rename docs/{ => codox}/js/page_effects.js (100%) create mode 100644 docs/codox/modelling_trading_cost_and_risk.html create mode 100644 docs/codox/sexual-dimorphism.html create mode 100644 docs/codox/the-great-game.core.html create mode 100644 docs/codox/the-great-game.gossip.gossip.html create mode 100644 docs/codox/the-great-game.merchants.markets.html create mode 100644 docs/codox/the-great-game.merchants.merchant-utils.html create mode 100644 docs/codox/the-great-game.merchants.merchants.html create mode 100644 docs/codox/the-great-game.merchants.planning.html create mode 100644 docs/codox/the-great-game.merchants.strategies.simple.html create mode 100644 docs/codox/the-great-game.utils.html create mode 100644 docs/codox/the-great-game.world.routes.html create mode 100644 docs/codox/the-great-game.world.run.html create mode 100644 docs/codox/the-great-game.world.world.html delete mode 100644 docs/index.html delete mode 100644 docs/the-great-game.core.html delete mode 100644 docs/the-great-game.gossip.gossip.html delete mode 100644 docs/the-great-game.merchants.markets.html delete mode 100644 docs/the-great-game.merchants.merchant-utils.html delete mode 100644 docs/the-great-game.merchants.merchants.html delete mode 100644 docs/the-great-game.merchants.planning.html delete mode 100644 docs/the-great-game.merchants.strategies.simple.html delete mode 100644 docs/the-great-game.utils.html delete mode 100644 docs/the-great-game.world.routes.html delete mode 100644 docs/the-great-game.world.run.html delete mode 100644 docs/the-great-game.world.world.html diff --git a/doc/Population.ods b/doc/Population.ods new file mode 100644 index 0000000000000000000000000000000000000000..494ac80ed3fdce857c47a365994e2243ed7f43ab GIT binary patch literal 16101 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%0A`MW zvmVFR9yq1_^lrm0E?b56GJ~krGjos)WT?e3E2k1hOoq*sqE9+vVo^?3yW}d2^)~4{C z^Nuhc46#U4;hVc^9wi`@;+u=nW_n#cBZzpvI~pfjYQELm?_E7nG_?lcJPS#kkZ^H= z_4rDiFpe(jvrX-4N7h9=>Pl-5tgsDu<*3mQPusCbdLT3zgvIH$e*>(O=W;u?Gv5Vw zZKcKCq zSUnP_KvQiTn~HwO64$*Pw5K`lM@vlbOcOG5Dvywqiz+A(op(tBG^ zclL*@y)0Rxhr}hpA|LSg3$Eo2Wq z#Wl~td6Cc1Y!dsV=dZAZgB7taCwU;Dt_c}_Oal2_KVS6VR>Yq*KedmN#CfN{hMhO^ zIldL>YepZ0s|YcvYArPkvvx_heZH0#kxO+`cs9G&x9qMKWlGA)0f`1=os+e_kidNV zCR44^5w$`ylARLIyy<$5Uw_7x?o8up_ia8i`8Fn`!t%4WqCY)|<6XZOGG1^wuh@x1 zXgMePYZtY2(^}YVQu*4=4gduWs>Nrk4E_XKJM^t$bWCPTg%NO@$8B=yy#6}f!84g znDWMFeVvfV!D$IdFs@m;2d#Irz+YMWHb+c9n(#p1|2eu!0u{DFVmir0@HFlQ^}8HF zoAEg3RCQ3cZbtU|w|(J;h&e&tpEK*J#&^TRjkG|V_8EmXVrD=m311x%4g<6Iljg(Q z4ZaWlR*+5yK>G1Rq{)yq#gw7It>q=2H)jUq4|i$3csAhWjlXrX%gQYS%`=Meb7~*j zLLIPNef?47tNGV4r)uS!Xhq%-+dC@;FF~FB528ru60v zVomgISfdM^c+a5>O|~DNj25mI+e6yd1K@p@Gfy}Y2y8YsaJ;oaY}9Ikpz|PDcMtk; zhsa27_u-y-dD3X$J(0m6H42qT&=lMYhlP^17_E2ZA4rSC-^&&M zW%UMJv9V`qD>DT39TPoFzvJ?mBhc<+qK*LBO141u4%&UwS~5NN3I#AaP)Fy#C#>zy zuRIUQ9tDT?U(@41H*IPkL!9t}rrV3JVi7@`86ggP8s;sX(ZLgdJVudJvqjOt<{Z5g z$%;%?#pAt|a-DN0;uTS|XmP=^K#TVH--0mmmQ2;Q6q+$XI7>kFP>^k|NchXOF6jZ_ zbY9S4=_od3kFu`&5*<(>odT;o+a3mkmX0g^pB=+e!MIZb--+Ile^1AkQuYr%|C((l zHT|dQ`0t2(fU}2<>AzftYg)GUD;y|q?fN7|oj*YnsF$ShtgC`tty@6lm};r-stm}b zDW$`~#0I4NULQ!OJzY!+l@exHghE?_v5jYv0FWjSwI9DHkKn_PM zYDk1yeO${;jK)ykXl9de^Dg_@%VDxP_7I2B$Uwl_h(k+P6*$xEE<6ASOzM>>!kQ}c z$8RWWB;$H-c2Lj2A^}MsGpkOO`zRyClsyq}R0k!2sa}SoRKx`qM$D}N8z=>Wz#)Pk zQ>S2bOu2h}0#!9N`w59xi5{BPb!!LZHk8D2w~M2V%uv*wH{L`Bd*ti&~U2R9jDs z^fN^u>3W>X&~}T*NRMR^A>ldXnU&7s(F?CgTetAL4xBsnFyyC1loQ-ru#>>{AWaInzk%$OR41$Qu^H4F&XYD&5y`o$e94H#k ztAu_j|6Fb?9@0`Q|rtxW1tuUxtxbC1hvN&{~^++NhnPd8(6y0EJ zCxzh|`zb=3R^g#$hAh9?Qpd4RW2k;X`k^K)0(R|;s4^AlG=~zd8Ng}?eCD*%4xyqn zPH9+TqmeytNIEQbPK^<)XvW=@_s?k&%B=SbS<$jx0IJil@Fy}(+zJ#gGq7*va5r78 zVGb5&v1F85(qcF<%QG`V?poy#W>P|}fxgY&H6lyA2Rz_ldeQt$n8mp1lxEu!!7zNX z)kTnSiK@c%E^7MRmD7be^>J5g@XPi+R^Zi-dNSV_dOa_j>$EDOh@Ui^!hIZXA(a9p zU`cBfZJn`~2hcu#rdF9aW`hA2$UBm#bdJAZmVH=vs zMkusG_`G7CZ(E;GzoasQS*oyrI@t_XM!4I~7=$m8b;u;1KZw3Q$!`DPqiGVb0)C{2 zIHb;LBy`Dw3(wun=UaogC!O(Wr9nkan^YcYOJPRIW38@4_*Pn%uH^He@|2JwY1*q9 z+04f0W5ivZcC6~;1cSw_;jdH;aWlEr1~b@`n|`9J1@GyNbI5%&LqpzPA1lgvAfs-R zv0tPd%z`~XTj{@PSXsejz^3s~Pdi>qOnfIwC=0#DK^Rw5lYo*b?H<=w%bTq|&TY{H zXHZu48^I99ZEJ08mINpr-w7&#u=o9spN?u_ zu;dQz{*4pEIW!YhXrY7b;F%>8f%L3a1hcndd{Whp`NMWLo2Ps4CQDMNBd{ZvB`1MO z_=9?~U6zAv{!i~%tKZ&1e~)sfHwxGlP#_@0RR3v|``2h&@1g2d z#O+ZLVXi*Os8FxjOvS#c3yQ_|6|TU_6<;$jgUv)g8hHAmUyW-(U~GgkRL^9rS-z^EwCN$J=WE?$GV0;^6UV-TP%O zgjJQUfp_-fRaMmVF!j{abf7~&Ca(+I^0QI2M+iPpjB>m2sNwEe^&|H8R0X4K6EATO zNskZEma>xgA&z|Kfa5{hA^S46HI-qhZcQ|l?rw`73wzz40Ui2I>?tKeb6E4y`kANL z8s5M{g-tWg`v-gPVXE2ZK~Wob?ys}CSHNe4J9!GX`=eMG-IfoO31!^AJod1+y3eii+i!=To5ALrXQLZ9n*F%ibx{ramFg&@Z`kjkg^ay?$TCs*p(p~7M z=+Jp&IhUiFSVaD+hHFY{CPw;NliM7uQ8gjnT^CRja?#Va%6jpBv-HiR<>^$B!x zBZC&s^PrU#;_Z4nh&o>X#gyQuX%Vpgs&zVu->)7h*awh*UTgZR>i;5ttUUeiTK|1D z>7Od9zjps=ZRsBm&^%HaIw#Q&7pU#nFA%8>t|`Ns;`Kkxi6hUd@YPbB}7wfT3x z=ii_IPtE^Y7yIY2=ieax$^ZP{qWt_Dlt1~P|67#3zd`wvANpsM-_*&Uf=~K4b@La# z=$~fhk}5B}(%!T!Baaeo8$S3c>VaegyOf6CC`;QTwk^v_8DULtU^|5;6c z<(vKs=Z{YOS2E?_J2CGsod09Je(_I#bmBiW|N7ncn{4@0Jjwr!din2)e;rDHe-{2J zuuOmbNc``5f3?292iia7oa0wf|M#%_-?jc~J$_r}Kjnt&AMCWE4CJqo9s~sS*U$8q L(XrzBee}No&pC(_ literal 0 HcmV?d00001 diff --git a/doc/economy.md b/doc/economy.md new file mode 100644 index 0000000..45ed00a --- /dev/null +++ b/doc/economy.md @@ -0,0 +1,49 @@ +# Game world economy + +Broadly this essay extends ideas presented in [Populating a game world](https://blog.journeyman.cc/2013/07/populating-game-world.html), q.v. + +## Primary producers + +### Herdsfolk + +Herdsfolk are nomadic; it's reasonable to think they'll bring their herds to market, rather than selling it lots of tiny markets. So in the spring, shepherds will visit specific towns at the edge of open land, to hold a shearing festival/carnevale; and that both shepherds and cattle herders will visit towns on the edge of open land to sell fatstock in the autumn. + +### Miners + +Miners mine. They're settled, but they're settled usually in specialist settlements at the location where the ore body is accessible, usually in mountenous territory. They'll consume a lot of food, so there will be a local market for foodstuffs encouraging local farming. Different mines obviously mine different ores, but, for example, lead and silver are frequently found together. + +### Foresters + +Foresters are more or less settled at the edge of forests, at locations from which timber can be moved by navigable water; again in specialist settlements. In addition to timber, foresters hunt and produce both meat and furs, so have less need for other food producers locally. + +### Farmers + +Farmers are settled. Farmers occupy standard runrig plots, but because they don't employ journeymen or apprentices, and don't have workshops, the plots are mostly open with little building. Most farmers are 'mixed farmers', producing cereals, meat, eggs and milk. Some will be more specialist. Farm produce, taken broadly to include orchardsfolk, include: + +* meat +* milk and milk products +* hides +* eggs +* cereals +* root vegetables, onions, etc +* peas and beans +* leaf vegetables +* fruits +* fibres: linen, hemp and silk (from silk-moths in mulberry orchards) +* possibly other stuff I've forgotten. + +Farmers are all basically subsistence farmers, farming first to feed their own household and selling only surplus in the market. + +## Crafts + +Crafts generally process primary goods into secondary goods - whether intermediate stages or final consumer items. Some elite 'crafts' deal with abstract primary goods like law and knowledge, and they may be seen as somewhat separate. + +A master craftsperson occupies a standard runrig plot, much like a farmer's plot. Like a farmer, a poor master crafter household will cultivate part of the plot to produce food for the house - at least grow vegetables and keep hens. However, as the crafter takes on apprentices and journeymen - and gets richer - more buildings will be required as accommodation, workshop space and materials stores. + +Generally, primary goods aren't transported over land - because overland transport is expensive, by the time they've been transported they're no longer low cost goods. So often the craftspeople who process primary produce into at least commodity intermediate forms will live close to the source of the primary goods. + +So, for example, the town(s) where the shepherds hold their shearing fairs will tend to have a lot of weavers. While around mines there will be smelters producing ingots and bar stock to be marketed to smiths all over the place, there will also be smiths close to the mines producing commodity tools and weapons. + +See the table in Populating a game world. + + diff --git a/doc/modelling_trading_cost_and_risk.md b/doc/modelling_trading_cost_and_risk.md new file mode 100644 index 0000000..3574d5b --- /dev/null +++ b/doc/modelling_trading_cost_and_risk.md @@ -0,0 +1,9 @@ +# Modelling trading cost and risk + +In a dynamic pre-firearms world with many small states and contested regions, trade is not going to be straightforward. Not only will different routes have different physical characteristics - more or less mountainous, more or fewer unbridged river crossings - they will also have different political characteristics: more of less taxed, more or less effectively policed. + +Raids by outlaws are expected to be part of the game economy. News of raids are the sort of things which may propagate through the [[gossip]] system. So are changes in taxation regime. Obviously, knowledge items can affect merchants' trading strategy; in existing prototype code, individual merchants already each keep their own cache of known historical prices, and exchange historical price data with one another; and use this price data to select trades to make. + +So: to what extent is it worth modelling the spread of knowledge of trade cost and risk? + +Obviously the more we model, the more compute power modelling consumes. If the core objective is a Role Playing Games as currently understood, then there is no need to model very complex trade risk assessment behaviour. diff --git a/doc/sexual-dimorphism.md b/doc/sexual-dimorphism.md new file mode 100644 index 0000000..5325f5c --- /dev/null +++ b/doc/sexual-dimorphism.md @@ -0,0 +1,45 @@ +# Sexual dimorphism + +This essay is going to upset a lot of people, so let's start with a statement of what it is about: it is an attempt to describe the systematically different behaviours of men and women, in sufficient detail that this can be represented by agents in a game world. It's trying to allow as broad as possible a range of cultures to be represented, so when I'm talking about what I consider to be behaviours of particular cultures, I'll say that. + +Of course, I'm writing this from the view point of an old white male. It's not possible to write about these things from a totally neutral viewpoint, and every one of us will have prejudices. + +OK? Let's start. + +When a man and a woman have sex, there's a non-zero chance that the woman will get pregnant. There's a zero chance that the male will get pregnant. + +A woman can typically give birth to of the order of twelve children in the course of her life, and each childbirth involves a non-zero risk of death. If modelling the sort of bronze-age-to-late-medieval cultures I'm generally considering, there are no available reliable methods of contraception, although their may be, for example, known spermicidal or abortifacient spells or potions. If it's abortifacient, that's pretty unpleasant for the woman, too. + +Children, especially when young, are very vulnerable and need protection. Children with good protection are much more likely to survive to adulthood. Raising children involves a fair amount of work. + +For all sorts of reasons, some of which are clearly cultural but others of which are fundamental, it's much easier for men to walk away from responsibility for their children than it is for women. For example, considering a pre-modern world, women would always know for certain which children were theirs, and men would not. + +For a woman, consequently, the best breeding strategy is to have sex only with men who will be 'good fathers', where there are three essential parameters to "good fathers": + +1. Desirable genetic traits; +2. Preparedness to stick around and share the work; +3. Ability to provide and protect. + +The essential trade-off in the traditional western marriage is that the man gets to have sex, and the woman gets to have protection for her progeny. + +Another significant point is that women's ability to bear children ceases at a much younger age than men's ability to father them. + +## Why have sex at all? + +If a character has 'having children' - the **Ancestor** aspiration, in my typology - as their key aim, then they will want to have sex. But to have children in this sense is to have acknowledged children, so while a male character may be motivated to have multiple female partners, he will never the less have some degree of long term committment to them, and will want both to feel confident that the children are his and to be recognised by their father. + +From the point of view of seeking to become an Ancestor, there is little benefit to the woman in having multiple partners, except in very harsh environments. It will be easier to give one partner confidence that all your children are his, and while a man can increase his number of potential progeny by having multiple wives, mistresses or other classes of long-term female sexual partners, a woman cannot. + +## Why have children? + +In modern Scotland, I have met a lot of women with a strong drive to have children for the sake of having children, where the best explanation they could give is that it's instinctual; it may be so. But beyond that, in many cultures children provide their (acknowledged) parents with care and security in their old age, may tend their graves and perform belief-related services after they die, and carry on their name and their stories into the future. + +Not everyone wants to have children; in thinking about the driving aspirations of game characters, I view having children one of six potential key aspirations. + +## Why else have sex? + +Sex, done right, is an extremely pleasant pastime. Sex can also be used to create and maintain bonds of committment, to demonstrate social status, to defuse tense situations, and transactionally in many ways, both formal and informal. + +For women, sex with other women carries with it no risk of pregnancy, so can be enjoyed or used for any of these purposes in very much the same way as it can by men; in other words, particularly for women, homosexual sex can be more lighthearted and carefree than heterosexual sex. To what extend our notions of homosexuality and heterosexuality are cultural I simply don't know. But because no children will result, a woman can afford to be more promiscuous with other women than she can with men. + +## How does this impact on diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index c741881..0000000 --- a/docs/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-slate \ No newline at end of file diff --git a/docs/cloverage/coverage.css b/docs/cloverage/coverage.css new file mode 100644 index 0000000..2be4e57 --- /dev/null +++ b/docs/cloverage/coverage.css @@ -0,0 +1,40 @@ +.covered { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; + background-color: #558B55; +} + +.not-covered { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; + background-color: red; +} + +.partial { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; + background-color: orange; +} + +.not-tracked { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; +} + +.blank { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; +} + +td { + padding-right: 10px; +} + +td.with-bar { + width: 250px; + text-align: center; +} + +td.with-number { + text-align: right; +} + +td.ns-name { + min-width: 150px; + padding-right: 25px; +} diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html new file mode 100644 index 0000000..eba4573 --- /dev/null +++ b/docs/cloverage/index.html @@ -0,0 +1,192 @@ + + + + + Coverage Summary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Namespace Forms Forms % Lines Lines %TotalBlankInstrumented
the-great-game.core
2
4
33.33 %
2
1
66.67 %613
the-great-game.gossip.gossip
5
105
4.55 %
5
34
12.82 %65539
the-great-game.merchants.markets
160
53
75.12 %
25
5
14
68.18 %84844
the-great-game.merchants.merchant-utils
104
122
46.02 %
33
3
25
59.02 %92761
the-great-game.merchants.merchants
2
69
2.82 %
2
14
12.50 %28316
the-great-game.merchants.planning
264
33
88.89 %
71
4
10
88.24 %1591185
the-great-game.merchants.strategies.simple
5
612
0.81 %
5
119
4.03 %1736124
the-great-game.utils
72
100.00 %
19
100.00 %35319
the-great-game.world.routes
127
1
99.22 %
41
1
100.00 %55242
the-great-game.world.run
3
58
4.92 %
3
17
15.00 %39220
the-great-game.world.world
420
18
95.89 %
65
2
97.01 %192467
Totals:51.99 %54.62 %
+ + diff --git a/docs/cloverage/the_great_game/core.clj.html b/docs/cloverage/the_great_game/core.clj.html new file mode 100644 index 0000000..3f2962e --- /dev/null +++ b/docs/cloverage/the_great_game/core.clj.html @@ -0,0 +1,26 @@ + + + + the_great_game/core.clj + + + + 001  (ns the-great-game.core) +
+ + 002   +
+ + 003  (defn foo +
+ + 004    "I don't do a whole lot." +
+ + 005    [x] +
+ + 006    (println x "Hello, World!")) +
+ + diff --git a/docs/cloverage/the_great_game/gossip/gossip.clj.html b/docs/cloverage/the_great_game/gossip/gossip.clj.html new file mode 100644 index 0000000..df4c108 --- /dev/null +++ b/docs/cloverage/the_great_game/gossip/gossip.clj.html @@ -0,0 +1,203 @@ + + + + the_great_game/gossip/gossip.clj + + + + 001  (ns the-great-game.gossip.gossip +
+ + 002    "Interchange of news events between agents agents" +
+ + 003    (:require [the-great-game.utils :refer [deep-merge]])) +
+ + 004   +
+ + 005  ;; Note that habitual travellers are all gossip agents; specifically, at this +
+ + 006  ;; stage, that means merchants. When merchants are moved we also need to +
+ + 007  ;; update the location of the gossip with the same key. +
+ + 008   +
+ + 009  (defn dialogue +
+ + 010    "Dialogue between an `enquirer` and an `agent` in this `world`; returns a +
+ + 011    map identical to `enquirer` except that its `:gossip` collection may have +
+ + 012    additional entries." +
+ + 013    ;; TODO: not yet written, this is a stub. +
+ + 014    [enquirer respondent world] +
+ + 015    enquirer) +
+ + 016   +
+ + 017  (defn gather-news +
+ + 018    ([world] +
+ + 019     (reduce +
+ + 020       deep-merge +
+ + 021       world +
+ + 022       (map +
+ + 023         #(gather-news world %) +
+ + 024         (keys (:gossips world))))) +
+ + 025    ([world gossip] +
+ + 026     (let [g (cond (keyword? gossip) +
+ + 027                   (-> world :gossips gossip) +
+ + 028                   (map? gossip) +
+ + 029                   gossip)] +
+ + 030       {:gossips +
+ + 031        {(:id g) +
+ + 032         (reduce +
+ + 033           deep-merge +
+ + 034           {} +
+ + 035           (map +
+ + 036             #(dialogue g % world) +
+ + 037             (remove +
+ + 038               #( = g %) +
+ + 039               (filter +
+ + 040                 #(= (:location %) (:location g)) +
+ + 041                 (vals (:gossips world))))))}}))) +
+ + 042   +
+ + 043  (defn move-gossip +
+ + 044    "Return a world like this `world` but with this `gossip` moved to this +
+ + 045    `new-location`. Many gossips are essentially shadow-records of agents of +
+ + 046    other types, and the movement if the gossip should be controlled by the +
+ + 047    run function of the type of the record they shadow. The [[#run]] function +
+ + 048    below does NOT call this function." +
+ + 049    [gossip world new-location] +
+ + 050    (let [id (cond +
+ + 051              (map? gossip) +
+ + 052              (-> world :gossips gossip :id) +
+ + 053              (keyword? gossip) +
+ + 054              gossip)] +
+ + 055    (deep-merge +
+ + 056      world +
+ + 057      {:gossips +
+ + 058       {id +
+ + 059        {:location new-location}}}))) +
+ + 060   +
+ + 061  (defn run +
+ + 062    "Return a world like this `world`, with news items exchanged between gossip +
+ + 063    agents." +
+ + 064    [world] +
+ + 065    (gather-news world)) +
+ + diff --git a/docs/cloverage/the_great_game/merchants/markets.clj.html b/docs/cloverage/the_great_game/merchants/markets.clj.html new file mode 100644 index 0000000..618f25f --- /dev/null +++ b/docs/cloverage/the_great_game/merchants/markets.clj.html @@ -0,0 +1,260 @@ + + + + the_great_game/merchants/markets.clj + + + + 001  (ns the-great-game.merchants.markets +
+ + 002    "Adjusting quantities and prices in markets." +
+ + 003    (:require [taoensso.timbre :as l :refer [info error]] +
+ + 004              [the-great-game.utils :refer [deep-merge]])) +
+ + 005   +
+ + 006  (defn new-price +
+ + 007    "If `stock` is greater than the maximum of `supply` and `demand`, then +
+ + 008    there is surplus and `old` price is too high, so shold be reduced. If +
+ + 009    lower, then it is too low and should be increased." +
+ + 010    [old stock supply demand] +
+ + 011    (let +
+ + 012      [delta (dec' (/ (max supply demand 1) (max stock 1))) +
+ + 013       scaled (/ delta 100)] +
+ + 014      (+ old scaled))) +
+ + 015   +
+ + 016   +
+ + 017  (defn adjust-quantity-and-price +
+ + 018    "Adjust the quantity of this `commodity` currently in stock in this `city` +
+ + 019    of this `world`. Return a fragmentary world which can be deep-merged into +
+ + 020    this world." +
+ + 021    [world city commodity] +
+ + 022    (let [c (cond +
+ + 023              (keyword? city) (-> world :cities city) +
+ + 024              (map? city) city) +
+ + 025          id (:id c) +
+ + 026          p (or (-> c :prices commodity) 0) +
+ + 027          d (or (-> c :demands commodity) 0) +
+ + 028          st (or (-> c :stock commodity) 0) +
+ + 029          su (or (-> c :supplies commodity) 0) +
+ + 030          decrement (min st d) +
+ + 031          increment (cond +
+ + 032                      ;; if we've two turns' production of this commodity in +
+ + 033                      ;; stock, halt production +
+ + 034                      (> st (* su 2)) +
+ + 035                      0 +
+ + 036                      ;; if it is profitable to produce this commodity, the +
+ + 037                      ;; craftspeople of the city will do so. +
+ + 038                      (> p 1) su +
+ + 039                      ;; otherwise, if there isn't a turn's production in +
+ + 040                      ;; stock, top up the stock, so that there's something for +
+ + 041                      ;; incoming merchants to buy +
+ + 042                      (> su st) +
+ + 043                      (- su st) +
+ + 044                      :else +
+ + 045                      0) +
+ + 046          n (new-price p st su d)] +
+ + 047      (if +
+ + 048        (not= p n) +
+ + 049        (l/info "Price of" commodity "at" id "has changed from" (float p) "to" (float n))) +
+ + 050      {:cities {id +
+ + 051                {:stock +
+ + 052                 {commodity (+ (- st decrement) increment)} +
+ + 053                 :prices +
+ + 054                 {commodity n}}}})) +
+ + 055   +
+ + 056   +
+ + 057  (defn update-markets +
+ + 058    "Return a world like this `world`, with quantities and prices in markets +
+ + 059    updated to reflect supply and demand. If `city` or `city` and `commodity` +
+ + 060    are specified, return a fragmentary world with only the changes for that +
+ + 061    `city` (and `commodity` if specified) populated." +
+ + 062    ([world] +
+ + 063     (reduce +
+ + 064       deep-merge +
+ + 065       world +
+ + 066       (map +
+ + 067         #(update-markets world %) +
+ + 068         (keys (:cities world))))) +
+ + 069    ([world city] +
+ + 070     (reduce +
+ + 071       deep-merge +
+ + 072       {} +
+ + 073       (map #(update-markets world city %) +
+ + 074            (keys (:commodities world))))) +
+ + 075    ([world city commodity] +
+ + 076      (adjust-quantity-and-price world city commodity))) +
+ + 077   +
+ + 078   +
+ + 079  (defn run +
+ + 080    "Return a world like this `world`, with quantities and prices in markets +
+ + 081    updated to reflect supply and demand." +
+ + 082    [world] +
+ + 083    (update-markets world)) +
+ + 084   +
+ + diff --git a/docs/cloverage/the_great_game/merchants/merchant_utils.clj.html b/docs/cloverage/the_great_game/merchants/merchant_utils.clj.html new file mode 100644 index 0000000..70cd0f3 --- /dev/null +++ b/docs/cloverage/the_great_game/merchants/merchant_utils.clj.html @@ -0,0 +1,284 @@ + + + + the_great_game/merchants/merchant_utils.clj + + + + 001  (ns 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 (: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      (quot +
+ + 045        (- (:capacity m) (burden m world)) +
+ + 046        (-> world :commodities commodity :weight)))) +
+ + 047   +
+ + 048  (defn can-afford +
+ + 049    "Return the number of units of this `commodity` which this `merchant` +
+ + 050    can afford to buy in this `world`." +
+ + 051    [merchant world commodity] +
+ + 052    (let [m (cond +
+ + 053              (keyword? merchant) +
+ + 054              (-> world :merchants merchant) +
+ + 055              (map? merchant) +
+ + 056              merchant) +
+ + 057          l (:location m)] +
+ + 058      (quot +
+ + 059        (:cash m) +
+ + 060        (-> world :cities l :prices commodity)))) +
+ + 061   +
+ + 062  (defn add-stock +
+ + 063    "Where `a` and `b` are both maps all of whose values are numbers, return +
+ + 064    a map whose keys are a union of the keys of `a` and `b` and whose values +
+ + 065    are the sums of their respective values." +
+ + 066    [a b] +
+ + 067    (reduce +
+ + 068      merge +
+ + 069      a +
+ + 070      (map +
+ + 071        #(hash-map % (+ (or (a %) 0) (or (b %) 0))) +
+ + 072        (keys b)))) +
+ + 073   +
+ + 074  (defn add-known-prices +
+ + 075    "Add the current prices at this `merchant`'s location in the `world` +
+ + 076    to a new cacke of known prices, and return it." +
+ + 077    [merchant world] +
+ + 078    (let [m (cond +
+ + 079              (keyword? merchant) +
+ + 080              (-> world :merchants merchant) +
+ + 081              (map? merchant) +
+ + 082              merchant) +
+ + 083          k (:known-prices m) +
+ + 084          l (:location m) +
+ + 085          d (:date world) +
+ + 086          p (-> world :cities l :prices)] +
+ + 087      (reduce +
+ + 088        merge +
+ + 089        k +
+ + 090        (map +
+ + 091          #(hash-map % (apply vector cons {:price (p %) :date d} (k %))) +
+ + 092          (-> world :commodities keys))))) +
+ + diff --git a/docs/cloverage/the_great_game/merchants/merchants.clj.html b/docs/cloverage/the_great_game/merchants/merchants.clj.html new file mode 100644 index 0000000..6228705 --- /dev/null +++ b/docs/cloverage/the_great_game/merchants/merchants.clj.html @@ -0,0 +1,92 @@ + + + + the_great_game/merchants/merchants.clj + + + + 001  (ns the-great-game.merchants.merchants +
+ + 002    "Trade planning for merchants, primarily." +
+ + 003    (:require [taoensso.timbre :as l :refer [info error spy]] +
+ + 004              [the-great-game.utils :refer [deep-merge]] +
+ + 005              [the-great-game.merchants.strategies.simple :refer [move-merchant]])) +
+ + 006   +
+ + 007   +
+ + 008  (defn run +
+ + 009    "Return a partial world based on this `world`, but with each merchant moved." +
+ + 010    [world] +
+ + 011    (try +
+ + 012      (reduce +
+ + 013        deep-merge +
+ + 014        world +
+ + 015        (map +
+ + 016          #(try +
+ + 017             (let [move-fn (or +
+ + 018                             (-> world :merchants % :move-fn) +
+ + 019                             move-merchant)] +
+ + 020               (apply move-fn (list % world))) +
+ + 021             (catch Exception any +
+ + 022               (l/error any "Failure while moving merchant " %) +
+ + 023               {})) +
+ + 024          (keys (:merchants world)))) +
+ + 025      (catch Exception any +
+ + 026        (l/error any "Failure while moving merchants") +
+ + 027        world))) +
+ + 028   +
+ + diff --git a/docs/cloverage/the_great_game/merchants/planning.clj.html b/docs/cloverage/the_great_game/merchants/planning.clj.html new file mode 100644 index 0000000..fcbe3f8 --- /dev/null +++ b/docs/cloverage/the_great_game/merchants/planning.clj.html @@ -0,0 +1,485 @@ + + + + the_great_game/merchants/planning.clj + + + + 001  (ns the-great-game.merchants.planning +
+ + 002    "Trade planning for merchants, primarily. This follows a simple-minded +
+ + 003    generate-and-test strategy and currently generates plans for all possible +
+ + 004    routes from the current location. This may not scale. Also, routes do not +
+ + 005    currently have cost or risk associated with them." +
+ + 006    (:require [the-great-game.utils :refer [deep-merge make-target-filter]] +
+ + 007              [the-great-game.merchants.merchant-utils :refer :all] +
+ + 008              [the-great-game.world.routes :refer [find-route]] +
+ + 009              [the-great-game.world.world :refer [actual-price default-world]])) +
+ + 010   +
+ + 011  (defn generate-trade-plans +
+ + 012    "Generate all possible trade plans for this `merchant` and this `commodity` +
+ + 013    in this `world`. +
+ + 014   +
+ + 015    Returned plans are maps with keys: +
+ + 016   +
+ + 017    * :merchant - the id of the `merchant` for whom the plan was created; +
+ + 018    * :origin - the city from which the trade starts; +
+ + 019    * :destination - the city to which the trade is planned; +
+ + 020    * :commodity - the `commodity` to be carried; +
+ + 021    * :buy-price - the price at which that `commodity` can be bought; +
+ + 022    * :expected-price - the price at which the `merchant` anticipates +
+ + 023    that `commodity` can be sold; +
+ + 024    * :distance - the number of stages in the planned journey +
+ + 025    * :dist-to-home - the distance from `destination` to the `merchant`'s +
+ + 026    home city." +
+ + 027    [merchant world commodity] +
+ + 028    (let [m (cond +
+ + 029              (keyword? merchant) +
+ + 030              (-> world :merchants merchant) +
+ + 031              (map? merchant) +
+ + 032              merchant) +
+ + 033          origin (:location m)] +
+ + 034      (map +
+ + 035        #(hash-map +
+ + 036           :merchant (:id m) +
+ + 037           :origin origin +
+ + 038           :destination % +
+ + 039           :commodity commodity +
+ + 040           :buy-price (actual-price world commodity origin) +
+ + 041           :expected-price (expected-price +
+ + 042                             m +
+ + 043                             commodity +
+ + 044                             %) +
+ + 045           :distance (count +
+ + 046                       (find-route world origin %)) +
+ + 047           :dist-to-home (count +
+ + 048                           (find-route +
+ + 049                             world +
+ + 050                             (:home m) +
+ + 051                             %))) +
+ + 052        (remove #(= % origin) (-> world :cities keys))))) +
+ + 053   +
+ + 054  (defn nearest-with-targets +
+ + 055    "Return the distance to the nearest destination among those of these +
+ + 056    `plans` which match these `targets`. Plans are expected to be plans +
+ + 057    as returned by `generate-trade-plans`, q.v.; `targets` are expected to be +
+ + 058    as accepted by `make-target-filter`, q.v." +
+ + 059    [plans targets] +
+ + 060    (apply +
+ + 061      min +
+ + 062      (map +
+ + 063        :distance +
+ + 064        (filter +
+ + 065          (make-target-filter targets) +
+ + 066          plans)))) +
+ + 067   +
+ + 068  (defn plan-trade +
+ + 069    "Find the best destination in this `world` for this `commodity` given this +
+ + 070    `merchant` and this `origin`. If two cities are anticipated to offer the +
+ + 071    same price, the nearer should be preferred; if two are equally distant, the +
+ + 072    ones nearer to the merchant's home should be preferred. +
+ + 073    `merchant` may be passed as a map or a keyword; `commodity` should  be +
+ + 074    passed as a keyword. +
+ + 075   +
+ + 076    The returned plan is a map with keys: +
+ + 077   +
+ + 078    * :merchant - the id of the `merchant` for whom the plan was created; +
+ + 079    * :origin - the city from which the trade starts; +
+ + 080    * :destination - the city to which the trade is planned; +
+ + 081    * :commodity - the `commodity` to be carried; +
+ + 082    * :buy-price - the price at which that `commodity` can be bought; +
+ + 083    * :expected-price - the price at which the `merchant` anticipates +
+ + 084    that `commodity` can be sold; +
+ + 085    * :distance - the number of stages in the planned journey +
+ + 086    * :dist-to-home - the distance from `destination` to the `merchant`'s +
+ + 087    home city." +
+ + 088    [merchant world commodity] +
+ + 089    (let [plans (generate-trade-plans merchant world commodity) +
+ + 090          best-prices (filter +
+ + 091                        (make-target-filter +
+ + 092                          [[:expected-price +
+ + 093                            (apply +
+ + 094                              max +
+ + 095                              (filter number? (map :expected-price plans)))]]) +
+ + 096                        plans)] +
+ + 097      (first +
+ + 098        (sort-by +
+ + 099          ;; all other things being equal, a merchant would prefer to end closer +
+ + 100          ;; to home. +
+ + 101          #(- 0 (:dist-to-home %)) +
+ + 102          ;; a merchant will seek the best price, but won't go further than +
+ + 103          ;; needed to get it. +
+ + 104          (filter +
+ + 105            (make-target-filter +
+ + 106              [[:distance +
+ + 107                (apply min (filter number? (map :distance best-prices)))]]) +
+ + 108            best-prices))))) +
+ + 109   +
+ + 110  (defn augment-plan +
+ + 111    "Augment this `plan` constructed in this `world` for this `merchant` with +
+ + 112    the `:quantity` of goods which should be bought and the `:expected-profit` +
+ + 113    of the trade. +
+ + 114   +
+ + 115    Returns the augmented plan." +
+ + 116    [merchant world plan] +
+ + 117    (let [c (:commodity plan) +
+ + 118          o (:origin plan) +
+ + 119          q (min +
+ + 120              (or +
+ + 121                (-> world :cities o :stock c) +
+ + 122                0) +
+ + 123              (can-carry merchant world c) +
+ + 124              (can-afford merchant world c)) +
+ + 125          p (* q (- (:expected-price plan) (:buy-price plan)))] +
+ + 126      (assoc plan :quantity q :expected-profit p))) +
+ + 127   +
+ + 128  (defn select-cargo +
+ + 129    "A `merchant`, in a given location in a `world`, will choose to buy a cargo +
+ + 130    within the limit they are capable of carrying, which they can anticipate +
+ + 131    selling for a profit at a destination." +
+ + 132    [merchant world] +
+ + 133    (let [m (cond +
+ + 134              (keyword? merchant) +
+ + 135              (-> world :merchants merchant) +
+ + 136              (map? merchant) +
+ + 137              merchant) +
+ + 138          origin (:location m) +
+ + 139          available (-> world :cities origin :stock) +
+ + 140          plans (map +
+ + 141                  #(augment-plan +
+ + 142                     m +
+ + 143                     world +
+ + 144                     (plan-trade m world %)) +
+ + 145                  (filter +
+ + 146                    #(let [q (-> world :cities origin :stock %)] +
+ + 147                       (and (number? q) (pos? q))) +
+ + 148                    (keys available)))] +
+ + 149      (if +
+ + 150        (not (empty? plans)) +
+ + 151        (first +
+ + 152          (sort-by +
+ + 153            #(- 0 (:dist-to-home %)) +
+ + 154            (filter +
+ + 155              (make-target-filter +
+ + 156                [[:expected-profit +
+ + 157                  (apply max (filter number? (map :expected-profit plans)))]]) +
+ + 158              plans)))))) +
+ + 159   +
+ + diff --git a/docs/cloverage/the_great_game/merchants/strategies/simple.clj.html b/docs/cloverage/the_great_game/merchants/strategies/simple.clj.html new file mode 100644 index 0000000..7f5ba97 --- /dev/null +++ b/docs/cloverage/the_great_game/merchants/strategies/simple.clj.html @@ -0,0 +1,527 @@ + + + + the_great_game/merchants/strategies/simple.clj + + + + 001  (ns the-great-game.merchants.strategies.simple +
+ + 002    "Default trading strategy for merchants. +
+ + 003   +
+ + 004    The simple strategy buys a single product in the local market if there is +
+ + 005    one which can be traded profitably, trades it to the chosen target market, +
+ + 006    and sells it there. If there is no commodity locally which can be traded +
+ + 007    profitably, moves towards home with no cargo. If at home and no commodity +
+ + 008    can be traded profitably, does not move." +
+ + 009    (:require [taoensso.timbre :as l :refer [info error spy]] +
+ + 010              [the-great-game.utils :refer [deep-merge]] +
+ + 011              [the-great-game.gossip.gossip :refer [move-gossip]] +
+ + 012              [the-great-game.merchants.planning :refer :all] +
+ + 013              [the-great-game.merchants.merchant-utils :refer +
+ + 014               [add-stock add-known-prices]] +
+ + 015              [the-great-game.world.routes :refer [find-route]])) +
+ + 016   +
+ + 017  (defn plan-and-buy +
+ + 018    "Return a world like this `world`, in which this `merchant` has planned +
+ + 019    a new trade, and bought appropriate stock for it. If no profitable trade +
+ + 020    can be planned, the merchant is simply moved towards their home." +
+ + 021    [merchant world] +
+ + 022    (let [m (cond +
+ + 023              (keyword? merchant) +
+ + 024              (-> world :merchants merchant) +
+ + 025              (map? merchant) +
+ + 026              merchant) +
+ + 027          id (:id m) +
+ + 028          location (:location m) +
+ + 029          market (-> world :cities location) +
+ + 030          plan (select-cargo merchant world)] +
+ + 031      (l/debug "plan-and-buy: merchant" id) +
+ + 032      (cond +
+ + 033        (not (empty? plan)) +
+ + 034        (let +
+ + 035          [c (:commodity plan) +
+ + 036           p (* (:quantity plan) (:buy-price plan)) +
+ + 037           q (:quantity plan)] +
+ + 038          (l/info "Merchant" id "bought" q "units of" c "at" location "for" p plan) +
+ + 039          {:merchants +
+ + 040           {id +
+ + 041            {:stock (add-stock (:stock m) {c q}) +
+ + 042             :cash (- (:cash m) p) +
+ + 043             :known-prices (add-known-prices m world) +
+ + 044             :plan plan}} +
+ + 045           :cities +
+ + 046           {location +
+ + 047            {:stock (assoc (:stock market) c (- (-> market :stock c) q)) +
+ + 048             :cash (+ (:cash market) p)}}}) +
+ + 049        ;; if no plan, then if at home stay put +
+ + 050        (= (:location m) (:home m)) +
+ + 051        (do +
+ + 052          (l/info "Merchant" id "remains at home in" location) +
+ + 053          {}) +
+ + 054        ;; else move towards home +
+ + 055        :else +
+ + 056        (let [route (find-route world location (:home m)) +
+ + 057              next-location (nth route 1)] +
+ + 058          (l/info "No trade possible at" location "; merchant" id "moves to" next-location) +
+ + 059          (merge +
+ + 060            {:merchants +
+ + 061             {id +
+ + 062              {:location next-location}}} +
+ + 063            (move-gossip id world next-location)))))) +
+ + 064   +
+ + 065  (defn re-plan +
+ + 066    "Having failed to sell a cargo at current location, re-plan a route to +
+ + 067    sell the current cargo. Returns a revised world." +
+ + 068    [merchant world] +
+ + 069    (let [m (cond +
+ + 070              (keyword? merchant) +
+ + 071              (-> world :merchants merchant) +
+ + 072              (map? merchant) +
+ + 073              merchant) +
+ + 074          id (:id m) +
+ + 075          location (:location m) +
+ + 076          plan (augment-plan m world (plan-trade m world (-> m :plan :commodity)))] +
+ + 077      (l/debug "re-plan: merchant" id) +
+ + 078      (deep-merge +
+ + 079        world +
+ + 080        {:merchants +
+ + 081         {id +
+ + 082          {:plan plan}}}))) +
+ + 083   +
+ + 084  (defn sell-and-buy +
+ + 085    "Return a new world like this `world`, in which this `merchant` has sold +
+ + 086    their current stock in their current location, and planned a new trade, and +
+ + 087    bought appropriate stock for it." +
+ + 088    ;; TODO: this either sells the entire cargo, or, if the market can't afford +
+ + 089    ;; it, none of it. And it does not cope with selling different commodities +
+ + 090    ;; in different markets. +
+ + 091    [merchant world] +
+ + 092    (let [m (cond +
+ + 093              (keyword? merchant) +
+ + 094              (-> world :merchants merchant) +
+ + 095              (map? merchant) +
+ + 096              merchant) +
+ + 097          id (:id m) +
+ + 098          location (:location m) +
+ + 099          market (-> world :cities location) +
+ + 100          stock-value (reduce +
+ + 101                        + +
+ + 102                        (map +
+ + 103                          #(* (-> m :stock %) (-> market :prices m)) +
+ + 104                          (keys (:stock m))))] +
+ + 105      (l/debug "sell-and-buy: merchant" id) +
+ + 106      (if +
+ + 107        (>= (:cash market) stock-value) +
+ + 108        (do +
+ + 109          (l/info "Merchant" id "sells" (:stock m) "at" location "for" stock-value) +
+ + 110          (plan-and-buy +
+ + 111            merchant +
+ + 112            (deep-merge +
+ + 113              world +
+ + 114              {:merchants +
+ + 115               {id +
+ + 116                {:stock {} +
+ + 117                 :cash (+ (:cash m) stock-value) +
+ + 118                 :known-prices (add-known-prices m world)}} +
+ + 119               :cities +
+ + 120               {location +
+ + 121                {:stock (add-stock (:stock m) (:stock market)) +
+ + 122                 :cash (- (:cash market) stock-value)}}}))) +
+ + 123        ;; else +
+ + 124        (re-plan merchant world)))) +
+ + 125   +
+ + 126  (defn move-merchant +
+ + 127    "Handle general en route movement of this `merchant` in this `world`; +
+ + 128    return a (partial or full) world like this `world` but in which the +
+ + 129    merchant may have been moved ot updated." +
+ + 130    [merchant world] +
+ + 131    (let [m (cond +
+ + 132              (keyword? merchant) +
+ + 133              (-> world :merchants merchant) +
+ + 134              (map? merchant) +
+ + 135              merchant) +
+ + 136          id (:id m) +
+ + 137          at-destination? (and (:plan m) (= (:location m) (-> m :plan :destination))) +
+ + 138          plan (:plan m) +
+ + 139          next-location (if plan +
+ + 140                          (nth +
+ + 141                            (find-route +
+ + 142                              world +
+ + 143                              (:location m) +
+ + 144                              (:destination plan)) +
+ + 145                            1) +
+ + 146                          (:location m))] +
+ + 147      (l/debug "move-merchant: merchant" id "at" (:location m) +
+ + 148               "destination" (-> m :plan :destination) "next" next-location +
+ + 149               "at destination" at-destination?) +
+ + 150      (cond +
+ + 151        ;; if the merchant is at the destination of their current plan +
+ + 152        ;; sell all cargo and repurchase. +
+ + 153        at-destination? +
+ + 154        (sell-and-buy merchant world) +
+ + 155        ;; if they don't have a plan, seek to create one +
+ + 156        (nil? plan) +
+ + 157        (plan-and-buy merchant world) +
+ + 158        ;; otherwise, move one step towards their destination +
+ + 159        (and next-location (not= next-location (:location m))) +
+ + 160        (do +
+ + 161          (l/info "Merchant " id " moving from " (:location m) " to " next-location) +
+ + 162          (deep-merge +
+ + 163            {:merchants +
+ + 164             {id +
+ + 165              {:location next-location +
+ + 166               :known-prices (add-known-prices m world)}}} +
+ + 167            (move-gossip id world next-location))) +
+ + 168        :else +
+ + 169        (do +
+ + 170          (l/info "Merchant" id "has plan but no next-location; currently at" +
+ + 171                  (:location m) ", destination is" (:destination plan)) +
+ + 172          world)))) +
+ + 173   +
+ + diff --git a/docs/cloverage/the_great_game/utils.clj.html b/docs/cloverage/the_great_game/utils.clj.html new file mode 100644 index 0000000..f07ef01 --- /dev/null +++ b/docs/cloverage/the_great_game/utils.clj.html @@ -0,0 +1,113 @@ + + + + the_great_game/utils.clj + + + + 001  (ns the-great-game.utils) +
+ + 002   +
+ + 003  (defn cyclic? +
+ + 004    "True if two or more elements of `route` are identical" +
+ + 005    [route] +
+ + 006    (not= (count route)(count (set route)))) +
+ + 007   +
+ + 008  (defn deep-merge +
+ + 009    "Recursively merges maps. Stolen from +
+ + 010    https://dnaeon.github.io/recursively-merging-maps-in-clojure/" +
+ + 011    [& maps] +
+ + 012    (letfn [(m [& xs] +
+ + 013               (if (some #(and (map? %) (not (record? %))) xs) +
+ + 014                 (apply merge-with m xs) +
+ + 015                 (last xs)))] +
+ + 016      (reduce m maps))) +
+ + 017   +
+ + 018  (defn make-target-filter +
+ + 019    "Construct a filter which, when applied to a list of maps, +
+ + 020    will pass those which match these `targets`, where each target +
+ + 021    is a tuple [key value]." +
+ + 022    ;; TODO: this would probably be more elegant as a macro +
+ + 023    [targets] +
+ + 024    (eval +
+ + 025      (list +
+ + 026        'fn +
+ + 027        (vector 'm) +
+ + 028        (cons +
+ + 029          'and +
+ + 030          (map +
+ + 031            #(list +
+ + 032               '= +
+ + 033               (list (first %) 'm) +
+ + 034               (nth % 1)) +
+ + 035            targets))))) +
+ + diff --git a/docs/cloverage/the_great_game/world/routes.clj.html b/docs/cloverage/the_great_game/world/routes.clj.html new file mode 100644 index 0000000..730f295 --- /dev/null +++ b/docs/cloverage/the_great_game/world/routes.clj.html @@ -0,0 +1,173 @@ + + + + the_great_game/world/routes.clj + + + + 001  (ns the-great-game.world.routes +
+ + 002    "Conceptual (plan level) routes, represented as tuples of location ids." +
+ + 003    (:require [the-great-game.utils :refer [cyclic?]])) +
+ + 004   +
+ + 005  (defn find-routes +
+ + 006    "Find routes from among these `routes` from `from`; if `to` is supplied, +
+ + 007    to `to`, by breadth-first search." +
+ + 008    ([routes from] +
+ + 009     (map +
+ + 010       (fn [to] (cons from to)) +
+ + 011       (remove +
+ + 012         empty? +
+ + 013         (map +
+ + 014           (fn [route] +
+ + 015             (remove +
+ + 016               #(= from %) +
+ + 017               (if (some #(= % from) route) route))) +
+ + 018           routes)))) +
+ + 019    ([routes from to] +
+ + 020     (let [steps (find-routes routes from) +
+ + 021           found (filter +
+ + 022                   (fn [step] (if (some #(= to %) step) step)) +
+ + 023                   steps)] +
+ + 024       (if +
+ + 025         (empty? found) +
+ + 026         (find-routes routes from to steps) +
+ + 027         found))) +
+ + 028    ([routes from to steps] +
+ + 029     (if +
+ + 030       (not (empty? steps)) +
+ + 031       (let [paths (remove +
+ + 032                     cyclic? +
+ + 033                     (mapcat +
+ + 034                         (fn [path] +
+ + 035                           (map +
+ + 036                             (fn [x] (concat path (rest x))) +
+ + 037                             (find-routes routes (last path)))) +
+ + 038                         steps)) +
+ + 039             found (filter +
+ + 040                     #(= (last %) to) paths)] +
+ + 041         (if +
+ + 042           (empty? found) +
+ + 043           (find-routes routes from to paths) +
+ + 044           found))))) +
+ + 045   +
+ + 046  (defn find-route +
+ + 047    "Find a single route from `from` to `to` in this `world-or-routes`, which +
+ + 048    may be either a world as defined in [[the-great-game.world.world]] or else +
+ + 049    a sequence of tuples of keywords." +
+ + 050    [world-or-routes from to] +
+ + 051    (first +
+ + 052      (find-routes +
+ + 053        (or (:routes world-or-routes) world-or-routes) +
+ + 054        from +
+ + 055        to))) +
+ + diff --git a/docs/cloverage/the_great_game/world/run.clj.html b/docs/cloverage/the_great_game/world/run.clj.html new file mode 100644 index 0000000..e6ea4e4 --- /dev/null +++ b/docs/cloverage/the_great_game/world/run.clj.html @@ -0,0 +1,125 @@ + + + + the_great_game/world/run.clj + + + + 001  (ns the-great-game.world.run +
+ + 002    "Run the whole simulation" +
+ + 003    (:require [environ.core :refer [env]] +
+ + 004              [taoensso.timbre :as timbre] +
+ + 005              [taoensso.timbre.appenders.3rd-party.rotor :as rotor] +
+ + 006              [the-great-game.gossip.gossip :as g] +
+ + 007              [the-great-game.merchants.merchants :as m] +
+ + 008              [the-great-game.merchants.markets :as k] +
+ + 009              [the-great-game.world.world :as w])) +
+ + 010   +
+ + 011  (defn init +
+ + 012    ([] +
+ + 013     (init {})) +
+ + 014    ([config] +
+ + 015     (timbre/merge-config! +
+ + 016       {:appenders +
+ + 017        {:rotor (rotor/rotor-appender +
+ + 018                  {:path "the-great-game.log" +
+ + 019                   :max-size (* 512 1024) +
+ + 020                   :backlog 10})} +
+ + 021        :level (or +
+ + 022                 (:log-level config) +
+ + 023                 (if (env :dev) :debug) +
+ + 024                 :info)}))) +
+ + 025   +
+ + 026  (defn run +
+ + 027    "The pipeline to run the simulation each game day. Returns a world like +
+ + 028    this world, with all the various active elements updated. The optional +
+ + 029    `date` argument, if supplied, is set as the `:date` of the returned world." +
+ + 030    ([world] +
+ + 031    (g/run +
+ + 032      (m/run +
+ + 033        (k/run +
+ + 034          (w/run world))))) +
+ + 035    ([world date] +
+ + 036    (g/run +
+ + 037      (m/run +
+ + 038        (k/run +
+ + 039          (w/run world date)))))) +
+ + diff --git a/docs/cloverage/the_great_game/world/world.clj.html b/docs/cloverage/the_great_game/world/world.clj.html new file mode 100644 index 0000000..94d91d0 --- /dev/null +++ b/docs/cloverage/the_great_game/world/world.clj.html @@ -0,0 +1,584 @@ + + + + the_great_game/world/world.clj + + + + 001  (ns the-great-game.world.world +
+ + 002    "Access to data about the world") +
+ + 003   +
+ + 004  ;;; The world has to work either as map or a database. Initially, and for +
+ + 005  ;;; unit tests, I'll use a map; later, there will be a database. But the +
+ + 006  ;;; API needs to be agnostic, so that heirarchies which interact with +
+ + 007  ;;; `world` don't have to know which they've got - as far as they're concerned +
+ + 008  ;;; it's just a handle. +
+ + 009   +
+ + 010  (def default-world +
+ + 011    "A basic world for testing concepts" +
+ + 012    {:date 0 ;; the age of this world in game days +
+ + 013     :cities +
+ + 014     {:aberdeen +
+ + 015      {:id :aberdeen +
+ + 016       :supplies +
+ + 017       ;; `supplies` is the quantity of each commodity added to the stock +
+ + 018       ;; each game day. If the price in the market is lower than 1 (the +
+ + 019       ;; cost of production of a unit) no goods will be added. +
+ + 020       {:fish 10 +
+ + 021        :leather 5} +
+ + 022       :demands +
+ + 023       ;; `stock` is the quantity of each commodity in the market at any +
+ + 024       ;; given time. It is adjusted for production and consumption at +
+ + 025       ;; the end of each game day. +
+ + 026       {:iron 1 +
+ + 027        :cloth 10 +
+ + 028        :whisky 10} +
+ + 029       :port true +
+ + 030       :prices +
+ + 031       ;; `prices`: the current price (both buying and selling, for simplicity) +
+ + 032       ;; of each commodity in the market. Updated each game day based on current +
+ + 033       ;; stock. +
+ + 034       {:cloth 1 +
+ + 035        :fish 1 +
+ + 036        :leather 1 +
+ + 037        :iron 1 +
+ + 038        :whisky 1} +
+ + 039       :stock +
+ + 040       ;; `stock` is the quantity of each commodity in the market at any +
+ + 041       ;; given time. It is adjusted for production and consumption at +
+ + 042       ;; the end of each game day. +
+ + 043       {:cloth 0 +
+ + 044        :fish 0 +
+ + 045        :leather 0 +
+ + 046        :iron 0 +
+ + 047        :whisky 0} +
+ + 048       :cash 100} +
+ + 049      :buckie +
+ + 050      {:id :buckie +
+ + 051       :supplies +
+ + 052       {:fish 20} +
+ + 053       :demands +
+ + 054       {:cloth 5 +
+ + 055        :leather 3 +
+ + 056        :whisky 5 +
+ + 057        :iron 1} +
+ + 058       :port true +
+ + 059       :prices {:cloth 1 +
+ + 060                :fish 1 +
+ + 061                :leather 1 +
+ + 062                :iron 1 +
+ + 063                :whisky 1} +
+ + 064       :stock {:cloth 0 +
+ + 065               :fish 0 +
+ + 066               :leather 0 +
+ + 067               :iron 0 +
+ + 068               :whisky 0} +
+ + 069       :cash 100} +
+ + 070      :callander +
+ + 071      {:id :callander +
+ + 072       :supplies {:leather 20} +
+ + 073       :demands +
+ + 074       {:cloth 5 +
+ + 075        :fish 3 +
+ + 076        :whisky 5 +
+ + 077        :iron 1} +
+ + 078       :prices {:cloth 1 +
+ + 079                :fish 1 +
+ + 080                :leather 1 +
+ + 081                :iron 1 +
+ + 082                :whisky 1} +
+ + 083       :stock {:cloth 0 +
+ + 084               :fish 0 +
+ + 085               :leather 0 +
+ + 086               :iron 0 +
+ + 087               :whisky 0} +
+ + 088       :cash 100} +
+ + 089      :dundee {:id :dundee} +
+ + 090      :edinburgh {:id :dundee} +
+ + 091      :falkirk +
+ + 092      {:id :falkirk +
+ + 093       :supplies {:iron 10} +
+ + 094       :demands +
+ + 095       {:cloth 5 +
+ + 096        :leather 3 +
+ + 097        :whisky 5 +
+ + 098        :fish 10} +
+ + 099       :port true +
+ + 100       :prices {:cloth 1 +
+ + 101                :fish 1 +
+ + 102                :leather 1 +
+ + 103                :iron 1 +
+ + 104                :whisky 1} +
+ + 105       :stock {:cloth 0 +
+ + 106               :fish 0 +
+ + 107               :leather 0 +
+ + 108               :iron 0 +
+ + 109               :whisky 0} +
+ + 110       :cash 100} +
+ + 111      :glasgow +
+ + 112      {:id :glasgow +
+ + 113       :supplies {:whisky 10} +
+ + 114       :demands +
+ + 115       {:cloth 5 +
+ + 116        :leather 3 +
+ + 117        :iron 5 +
+ + 118        :fish 10} +
+ + 119       :port true +
+ + 120       :prices {:cloth 1 +
+ + 121                :fish 1 +
+ + 122                :leather 1 +
+ + 123                :iron 1 +
+ + 124                :whisky 1} +
+ + 125       :stock {:cloth 0 +
+ + 126               :fish 0 +
+ + 127               :leather 0 +
+ + 128               :iron 0 +
+ + 129               :whisky 0} +
+ + 130       :cash 100}} +
+ + 131     :merchants +
+ + 132     {:archie {:id :archie +
+ + 133               :home :aberdeen :location :aberdeen :cash 100 :capacity 10 +
+ + 134               :known-prices {} +
+ + 135               :stock {}} +
+ + 136      :belinda {:id :belinda +
+ + 137                :home :buckie :location :buckie :cash 100 :capacity 10 +
+ + 138                :known-prices {} +
+ + 139                :stock {}} +
+ + 140      :callum {:id :callum +
+ + 141               :home :callander :location :calander :cash 100 :capacity 10 +
+ + 142               :known-prices {} +
+ + 143               :stock {}} +
+ + 144      :deirdre {:id :deidre +
+ + 145                :home :dundee :location :dundee :cash 100 :capacity 10 +
+ + 146                :known-prices {} +
+ + 147                :stock {}} +
+ + 148      :euan {:id :euan +
+ + 149             :home :edinbirgh :location :edinburgh :cash 100 :capacity 10 +
+ + 150               :known-prices {} +
+ + 151               :stock {}} +
+ + 152      :fiona {:id :fiona +
+ + 153              :home :falkirk :location :falkirk :cash 100 :capacity 10 +
+ + 154              :known-prices {} +
+ + 155              :stock {}}} +
+ + 156     :routes +
+ + 157     ;; all routes can be traversed in either direction and are assumed to +
+ + 158     ;; take the same amount of time. +
+ + 159     [[:aberdeen :buckie] +
+ + 160      [:aberdeen :dundee] +
+ + 161      [:callander :glasgow] +
+ + 162      [:dundee :callander] +
+ + 163      [:dundee :edinburgh] +
+ + 164      [:dundee :falkirk] +
+ + 165      [:edinburgh :falkirk] +
+ + 166      [:falkirk :glasgow]] +
+ + 167     :commodities +
+ + 168     ;; cost of commodities is expressed in person/days; +
+ + 169     ;; weight in packhorse loads. Transport in this model +
+ + 170     ;; is all overland; you don't take bulk cargoes overland +
+ + 171     ;; in this period, it's too expensive. +
+ + 172     {:cloth {:id :cloth :cost 1 :weight 0.25} +
+ + 173      :fish {:id :fish :cost 1 :weight 1} +
+ + 174      :leather {:id :leather :cost 1 :weight 0.5} +
+ + 175      :whisky {:id :whisky :cost 1 :weight 0.1} +
+ + 176      :iron {:id :iron :cost 1 :weight 10}}}) +
+ + 177   +
+ + 178  (defn actual-price +
+ + 179    "Find the actual current price of this `commodity` in this `city` given +
+ + 180    this `world`. **NOTE** that merchants can only know the actual prices in +
+ + 181    the city in which they are currently located." +
+ + 182    [world commodity city] +
+ + 183    (-> world :cities city :prices commodity)) +
+ + 184   +
+ + 185  (defn run +
+ + 186    "Return a world like this `world` with only the `:date` to this `date` +
+ + 187    (or id `date` not supplied, the current value incremented by one). For +
+ + 188    running other aspects of the simulation, see [[the-great-game.world.run]]." +
+ + 189    ([world] +
+ + 190     (run world (inc (or (:date world) 0)))) +
+ + 191    ([world date] +
+ + 192     (assoc world :date date))) +
+ + diff --git a/docs/css/default.css b/docs/codox/css/default.css similarity index 100% rename from docs/css/default.css rename to docs/codox/css/default.css diff --git a/docs/css/highlight.css b/docs/codox/css/highlight.css similarity index 100% rename from docs/css/highlight.css rename to docs/codox/css/highlight.css diff --git a/docs/codox/economy.html b/docs/codox/economy.html new file mode 100644 index 0000000..ce3f593 --- /dev/null +++ b/docs/codox/economy.html @@ -0,0 +1,33 @@ + +Game world economy

Game world economy

+

Broadly this essay extends ideas presented in Populating a game world, q.v.

+

Primary producers

+

Herdsfolk

+

Herdsfolk are nomadic; it’s reasonable to think they’ll bring their herds to market, rather than selling it lots of tiny markets. So in the spring, shepherds will visit specific towns at the edge of open land, to hold a shearing festival/carnevale; and that both shepherds and cattle herders will visit towns on the edge of open land to sell fatstock in the autumn.

+

Miners

+

Miners mine. They’re settled, but they’re settled usually in specialist settlements at the location where the ore body is accessible, usually in mountenous territory. They’ll consume a lot of food, so there will be a local market for foodstuffs encouraging local farming. Different mines obviously mine different ores, but, for example, lead and silver are frequently found together.

+

Foresters

+

Foresters are more or less settled at the edge of forests, at locations from which timber can be moved by navigable water; again in specialist settlements. In addition to timber, foresters hunt and produce both meat and furs, so have less need for other food producers locally.

+

Farmers

+

Farmers are settled. Farmers occupy standard runrig plots, but because they don’t employ journeymen or apprentices, and don’t have workshops, the plots are mostly open with little building. Most farmers are ‘mixed farmers’, producing cereals, meat, eggs and milk. Some will be more specialist. Farm produce, taken broadly to include orchardsfolk, include:

+
    +
  • meat
  • +
  • milk and milk products
  • +
  • hides
  • +
  • eggs
  • +
  • cereals
  • +
  • root vegetables, onions, etc
  • +
  • peas and beans
  • +
  • leaf vegetables
  • +
  • fruits
  • +
  • fibres: linen, hemp and silk (from silk-moths in mulberry orchards)
  • +
  • possibly other stuff I’ve forgotten.
  • +
+

Farmers are all basically subsistence farmers, farming first to feed their own household and selling only surplus in the market.

+

Crafts

+

Crafts generally process primary goods into secondary goods - whether intermediate stages or final consumer items. Some elite ‘crafts’ deal with abstract primary goods like law and knowledge, and they may be seen as somewhat separate.

+

A master craftsperson occupies a standard runrig plot, much like a farmer’s plot. Like a farmer, a poor master crafter household will cultivate part of the plot to produce food for the house - at least grow vegetables and keep hens. However, as the crafter takes on apprentices and journeymen - and gets richer - more buildings will be required as accommodation, workshop space and materials stores.

+

Generally, primary goods aren’t transported over land - because overland transport is expensive, by the time they’ve been transported they’re no longer low cost goods. So often the craftspeople who process primary produce into at least commodity intermediate forms will live close to the source of the primary goods.

+

So, for example, the town(s) where the shepherds hold their shearing fairs will tend to have a lot of weavers. While around mines there will be smelters producing ingots and bar stock to be marketed to smiths all over the place, there will also be smiths close to the mines producing commodity tools and weapons.

+

See the table in Populating a game world.

\ No newline at end of file diff --git a/docs/codox/index.html b/docs/codox/index.html new file mode 100644 index 0000000..545a41e --- /dev/null +++ b/docs/codox/index.html @@ -0,0 +1,3 @@ + +The-great-game 0.1.0-SNAPSHOT

The-great-game 0.1.0-SNAPSHOT

Released under the GNU General Public License,version 2.0 or (at your option) any later version

Prototype code towards the great game I've been writing about for ten years, and know I will never finish.

Installation

To install, add the following dependency to your project or build file:

[the-great-game "0.1.0-SNAPSHOT"]

Topics

Namespaces

the-great-game.core

TODO: write docs

Public variables and functions:

the-great-game.gossip.gossip

Interchange of news events between agents agents

Public variables and functions:

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

Public variables and functions:

the-great-game.merchants.merchant-utils

Useful functions for doing low-level things with merchants.

the-great-game.merchants.merchants

Trade planning for merchants, primarily.

Public variables and functions:

the-great-game.merchants.planning

Trade planning for merchants, primarily. This follows a simple-minded generate-and-test strategy and currently generates plans for all possible routes from the current location. This may not scale. Also, routes do not currently have cost or risk associated with them.

the-great-game.merchants.strategies.simple

Default trading strategy for merchants.

Public variables and functions:

the-great-game.utils

TODO: write docs

Public variables and functions:

the-great-game.world.routes

Conceptual (plan level) routes, represented as tuples of location ids.

Public variables and functions:

the-great-game.world.run

Run the whole simulation

Public variables and functions:

the-great-game.world.world

Access to data about the world

Public variables and functions:

\ No newline at end of file diff --git a/docs/intro.html b/docs/codox/intro.html similarity index 87% rename from docs/intro.html rename to docs/codox/intro.html index 3822424..691703e 100644 --- a/docs/intro.html +++ b/docs/codox/intro.html @@ -1,6 +1,6 @@ -Introduction to the-great-game

Introduction to the-great-game

+Introduction to the-great-game

Introduction to the-great-game

The Great Game

In this essay I’m going to try to pull together a number of my architectural ideas about the Great Game which I know I’m never actually going to build - because it’s vastly too big for any one person to build - into one overall vision.

So, firstly, how does one characterise this game?

diff --git a/docs/js/highlight.min.js b/docs/codox/js/highlight.min.js similarity index 100% rename from docs/js/highlight.min.js rename to docs/codox/js/highlight.min.js diff --git a/docs/js/jquery.min.js b/docs/codox/js/jquery.min.js similarity index 100% rename from docs/js/jquery.min.js rename to docs/codox/js/jquery.min.js diff --git a/docs/js/page_effects.js b/docs/codox/js/page_effects.js similarity index 100% rename from docs/js/page_effects.js rename to docs/codox/js/page_effects.js diff --git a/docs/codox/modelling_trading_cost_and_risk.html b/docs/codox/modelling_trading_cost_and_risk.html new file mode 100644 index 0000000..71c862f --- /dev/null +++ b/docs/codox/modelling_trading_cost_and_risk.html @@ -0,0 +1,7 @@ + +Modelling trading cost and risk

Modelling trading cost and risk

+

In a dynamic pre-firearms world with many small states and contested regions, trade is not going to be straightforward. Not only will different routes have different physical characteristics - more or less mountainous, more or fewer unbridged river crossings - they will also have different political characteristics: more of less taxed, more or less effectively policed.

+

Raids by outlaws are expected to be part of the game economy. News of raids are the sort of things which may propagate through the gossip system. So are changes in taxation regime. Obviously, knowledge items can affect merchants’ trading strategy; in existing prototype code, individual merchants already each keep their own cache of known historical prices, and exchange historical price data with one another; and use this price data to select trades to make.

+

So: to what extent is it worth modelling the spread of knowledge of trade cost and risk?

+

Obviously the more we model, the more compute power modelling consumes. If the core objective is a Role Playing Games as currently understood, then there is no need to model very complex trade risk assessment behaviour.

\ No newline at end of file diff --git a/docs/codox/sexual-dimorphism.html b/docs/codox/sexual-dimorphism.html new file mode 100644 index 0000000..54624d7 --- /dev/null +++ b/docs/codox/sexual-dimorphism.html @@ -0,0 +1,28 @@ + +Sexual dimorphism

Sexual dimorphism

+

This essay is going to upset a lot of people, so let’s start with a statement of what it is about: it is an attempt to describe the systematically different behaviours of men and women, in sufficient detail that this can be represented by agents in a game world. It’s trying to allow as broad as possible a range of cultures to be represented, so when I’m talking about what I consider to be behaviours of particular cultures, I’ll say that.

+

Of course, I’m writing this from the view point of an old white male. It’s not possible to write about these things from a totally neutral viewpoint, and every one of us will have prejudices.

+

OK? Let’s start.

+

When a man and a woman have sex, there’s a non-zero chance that the woman will get pregnant. There’s a zero chance that the male will get pregnant.

+

A woman can typically give birth to of the order of twelve children in the course of her life, and each childbirth involves a non-zero risk of death. If modelling the sort of bronze-age-to-late-medieval cultures I’m generally considering, there are no available reliable methods of contraception, although their may be, for example, known spermicidal or abortifacient spells or potions. If it’s abortifacient, that’s pretty unpleasant for the woman, too.

+

Children, especially when young, are very vulnerable and need protection. Children with good protection are much more likely to survive to adulthood. Raising children involves a fair amount of work.

+

For all sorts of reasons, some of which are clearly cultural but others of which are fundamental, it’s much easier for men to walk away from responsibility for their children than it is for women. For example, considering a pre-modern world, women would always know for certain which children were theirs, and men would not.

+

For a woman, consequently, the best breeding strategy is to have sex only with men who will be ‘good fathers’, where there are three essential parameters to “good fathers”:

+
    +
  1. Desirable genetic traits;
  2. +
  3. Preparedness to stick around and share the work;
  4. +
  5. Ability to provide and protect.
  6. +
+

The essential trade-off in the traditional western marriage is that the man gets to have sex, and the woman gets to have protection for her progeny.

+

Another significant point is that women’s ability to bear children ceases at a much younger age than men’s ability to father them.

+

Why have sex at all?

+

If a character has ‘having children’ - the Ancestor aspiration, in my typology - as their key aim, then they will want to have sex. But to have children in this sense is to have acknowledged children, so while a male character may be motivated to have multiple female partners, he will never the less have some degree of long term committment to them, and will want both to feel confident that the children are his and to be recognised by their father.

+

From the point of view of seeking to become an Ancestor, there is little benefit to the woman in having multiple partners, except in very harsh environments. It will be easier to give one partner confidence that all your children are his, and while a man can increase his number of potential progeny by having multiple wives, mistresses or other classes of long-term female sexual partners, a woman cannot.

+

Why have children?

+

In modern Scotland, I have met a lot of women with a strong drive to have children for the sake of having children, where the best explanation they could give is that it’s instinctual; it may be so. But beyond that, in many cultures children provide their (acknowledged) parents with care and security in their old age, may tend their graves and perform belief-related services after they die, and carry on their name and their stories into the future.

+

Not everyone wants to have children; in thinking about the driving aspirations of game characters, I view having children one of six potential key aspirations.

+

Why else have sex?

+

Sex, done right, is an extremely pleasant pastime. Sex can also be used to create and maintain bonds of committment, to demonstrate social status, to defuse tense situations, and transactionally in many ways, both formal and informal.

+

For women, sex with other women carries with it no risk of pregnancy, so can be enjoyed or used for any of these purposes in very much the same way as it can by men; in other words, particularly for women, homosexual sex can be more lighthearted and carefree than heterosexual sex. To what extend our notions of homosexuality and heterosexuality are cultural I simply don’t know. But because no children will result, a woman can afford to be more promiscuous with other women than she can with men.

+

How does this impact on

\ No newline at end of file diff --git a/docs/codox/the-great-game.core.html b/docs/codox/the-great-game.core.html new file mode 100644 index 0000000..72be981 --- /dev/null +++ b/docs/codox/the-great-game.core.html @@ -0,0 +1,3 @@ + +the-great-game.core documentation

the-great-game.core

TODO: write docs

foo

(foo x)

I don’t do a whole lot.

\ No newline at end of file diff --git a/docs/codox/the-great-game.gossip.gossip.html b/docs/codox/the-great-game.gossip.gossip.html new file mode 100644 index 0000000..46e5d39 --- /dev/null +++ b/docs/codox/the-great-game.gossip.gossip.html @@ -0,0 +1,3 @@ + +the-great-game.gossip.gossip documentation

the-great-game.gossip.gossip

Interchange of news events between agents agents

dialogue

(dialogue enquirer respondent world)

Dialogue between an enquirer and an agent in this world; returns a map identical to enquirer except that its :gossip collection may have additional entries.

gather-news

(gather-news world)(gather-news world gossip)

TODO: write docs

move-gossip

(move-gossip gossip world new-location)

Return a world like this world but with this gossip moved to this new-location. Many gossips are essentially shadow-records of agents of other types, and the movement if the gossip should be controlled by the run function of the type of the record they shadow. The #run function below does NOT call this function.

run

(run world)

Return a world like this world, with news items exchanged between gossip agents.

\ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.markets.html b/docs/codox/the-great-game.merchants.markets.html new file mode 100644 index 0000000..a991e82 --- /dev/null +++ b/docs/codox/the-great-game.merchants.markets.html @@ -0,0 +1,3 @@ + +the-great-game.merchants.markets documentation

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

adjust-quantity-and-price

(adjust-quantity-and-price world city commodity)

Adjust the quantity of this commodity currently in stock in this city of this world. Return a fragmentary world which can be deep-merged into this world.

new-price

(new-price old stock supply demand)

If stock is greater than the maximum of supply and demand, then there is surplus and old price is too high, so shold be reduced. If lower, then it is too low and should be increased.

run

(run world)

Return a world like this world, with quantities and prices in markets updated to reflect supply and demand.

update-markets

(update-markets world)(update-markets world city)(update-markets world city commodity)

Return a world like this world, with quantities and prices in markets updated to reflect supply and demand. If city or city and commodity are specified, return a fragmentary world with only the changes for that city (and commodity if specified) populated.

\ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.merchant-utils.html b/docs/codox/the-great-game.merchants.merchant-utils.html new file mode 100644 index 0000000..d7940c2 --- /dev/null +++ b/docs/codox/the-great-game.merchants.merchant-utils.html @@ -0,0 +1,3 @@ + +the-great-game.merchants.merchant-utils documentation

the-great-game.merchants.merchant-utils

Useful functions for doing low-level things with merchants.

add-known-prices

(add-known-prices merchant world)

Add the current prices at this merchant’s location in the world to a new cacke of known prices, and return it.

add-stock

(add-stock a b)

Where a and b are both maps all of whose values are numbers, return a map whose keys are a union of the keys of a and b and whose values are the sums of their respective values.

burden

(burden merchant world)

The total weight of the current cargo carried by this merchant in this world.

can-afford

(can-afford merchant world commodity)

Return the number of units of this commodity which this merchant can afford to buy in this world.

can-carry

(can-carry merchant world commodity)

Return the number of units of this commodity which this merchant can carry in this world, given their current burden.

expected-price

(expected-price merchant commodity city)

Find the price anticipated, given this world, by this merchant for this commodity in this city. If no information, assume 1. merchant should be passed as a map, commodity and city should be passed as keywords.

\ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.merchants.html b/docs/codox/the-great-game.merchants.merchants.html new file mode 100644 index 0000000..f63ba36 --- /dev/null +++ b/docs/codox/the-great-game.merchants.merchants.html @@ -0,0 +1,3 @@ + +the-great-game.merchants.merchants documentation

the-great-game.merchants.merchants

Trade planning for merchants, primarily.

run

(run world)

Return a partial world based on this world, but with each merchant moved.

\ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.planning.html b/docs/codox/the-great-game.merchants.planning.html new file mode 100644 index 0000000..9751a9b --- /dev/null +++ b/docs/codox/the-great-game.merchants.planning.html @@ -0,0 +1,26 @@ + +the-great-game.merchants.planning documentation

the-great-game.merchants.planning

Trade planning for merchants, primarily. This follows a simple-minded generate-and-test strategy and currently generates plans for all possible routes from the current location. This may not scale. Also, routes do not currently have cost or risk associated with them.

augment-plan

(augment-plan merchant world plan)

Augment this plan constructed in this world for this merchant with the :quantity of goods which should be bought and the :expected-profit of the trade.

+

Returns the augmented plan.

generate-trade-plans

(generate-trade-plans merchant world commodity)

Generate all possible trade plans for this merchant and this commodity in this world.

+

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.
  • +

nearest-with-targets

(nearest-with-targets plans targets)

Return the distance to the nearest destination among those of these plans which match these targets. Plans are expected to be plans as returned by generate-trade-plans, q.v.; targets are expected to be as accepted by make-target-filter, q.v.

plan-trade

(plan-trade merchant world commodity)

Find the best destination in this world for this commodity given this merchant and this origin. If two cities are anticipated to offer the same price, the nearer should be preferred; if two are equally distant, the ones nearer to the merchant’s home should be preferred. merchant may be passed as a map or a keyword; commodity should be passed as a keyword.

+

The returned plan is a map 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.
  • +

select-cargo

(select-cargo merchant world)

A merchant, in a given location in a world, will choose to buy a cargo within the limit they are capable of carrying, which they can anticipate selling for a profit at a destination.

\ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.strategies.simple.html b/docs/codox/the-great-game.merchants.strategies.simple.html new file mode 100644 index 0000000..8032a66 --- /dev/null +++ b/docs/codox/the-great-game.merchants.strategies.simple.html @@ -0,0 +1,4 @@ + +the-great-game.merchants.strategies.simple documentation

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.

move-merchant

(move-merchant merchant 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.

plan-and-buy

(plan-and-buy merchant world)

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.

re-plan

(re-plan merchant world)

Having failed to sell a cargo at current location, re-plan a route to sell the current cargo. Returns a revised world.

sell-and-buy

(sell-and-buy merchant world)

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.

\ No newline at end of file diff --git a/docs/codox/the-great-game.utils.html b/docs/codox/the-great-game.utils.html new file mode 100644 index 0000000..4242f1b --- /dev/null +++ b/docs/codox/the-great-game.utils.html @@ -0,0 +1,3 @@ + +the-great-game.utils documentation

the-great-game.utils

TODO: write docs

cyclic?

(cyclic? route)

True if two or more elements of route are identical

deep-merge

(deep-merge & maps)

make-target-filter

(make-target-filter targets)

Construct a filter which, when applied to a list of maps, will pass those which match these targets, where each target is a tuple [key value].

\ No newline at end of file diff --git a/docs/codox/the-great-game.world.routes.html b/docs/codox/the-great-game.world.routes.html new file mode 100644 index 0000000..4cde466 --- /dev/null +++ b/docs/codox/the-great-game.world.routes.html @@ -0,0 +1,3 @@ + +the-great-game.world.routes documentation

the-great-game.world.routes

Conceptual (plan level) routes, represented as tuples of location ids.

find-route

(find-route world-or-routes from to)

Find a single route from from to to in this world-or-routes, which may be either a world as defined in the-great-game.world.world or else a sequence of tuples of keywords.

find-routes

(find-routes routes from)(find-routes routes from to)(find-routes routes from to steps)

Find routes from among these routes from from; if to is supplied, to to, by breadth-first search.

\ No newline at end of file diff --git a/docs/codox/the-great-game.world.run.html b/docs/codox/the-great-game.world.run.html new file mode 100644 index 0000000..109956c --- /dev/null +++ b/docs/codox/the-great-game.world.run.html @@ -0,0 +1,3 @@ + +the-great-game.world.run documentation

the-great-game.world.run

Run the whole simulation

init

(init)(init config)

TODO: write docs

run

(run world)(run world date)

The pipeline to run the simulation each game day. Returns a world like this world, with all the various active elements updated. The optional date argument, if supplied, is set as the :date of the returned world.

\ No newline at end of file diff --git a/docs/codox/the-great-game.world.world.html b/docs/codox/the-great-game.world.world.html new file mode 100644 index 0000000..0194ed5 --- /dev/null +++ b/docs/codox/the-great-game.world.world.html @@ -0,0 +1,3 @@ + +the-great-game.world.world documentation

the-great-game.world.world

Access to data about the world

actual-price

(actual-price world commodity city)

Find the actual current price of this commodity in this city given this world. NOTE that merchants can only know the actual prices in the city in which they are currently located.

default-world

A basic world for testing concepts

run

(run world)(run world date)

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.

\ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 5370800..0000000 --- a/docs/index.html +++ /dev/null @@ -1,3 +0,0 @@ - -The-great-game 0.1.0-SNAPSHOT

The-great-game 0.1.0-SNAPSHOT

Released under the GNU General Public License,version 2.0 or (at your option) any later version

Prototype code towards the great game I've been writing about for ten years, and know I will never finish.

Installation

To install, add the following dependency to your project or build file:

[the-great-game "0.1.0-SNAPSHOT"]

Topics

Namespaces

the-great-game.core

TODO: write docs

Public variables and functions:

the-great-game.gossip.gossip

Interchange of news events between agents agents

Public variables and functions:

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

Public variables and functions:

the-great-game.merchants.merchant-utils

Useful functions for doing low-level things with merchants.

the-great-game.merchants.merchants

Trade planning for merchants, primarily.

Public variables and functions:

the-great-game.merchants.planning

Trade planning for merchants, primarily.

the-great-game.merchants.strategies.simple

Default trading strategy for merchants.

Public variables and functions:

the-great-game.utils

TODO: write docs

Public variables and functions:

the-great-game.world.routes

Conceptual (plan level) routes, represented as tuples of location ids.

Public variables and functions:

the-great-game.world.run

Run the whole simulation

Public variables and functions:

the-great-game.world.world

Access to data about the world

Public variables and functions:

\ No newline at end of file diff --git a/docs/the-great-game.core.html b/docs/the-great-game.core.html deleted file mode 100644 index 504fb69..0000000 --- a/docs/the-great-game.core.html +++ /dev/null @@ -1,3 +0,0 @@ - -the-great-game.core documentation

the-great-game.core

TODO: write docs

foo

(foo x)

I don’t do a whole lot.

\ No newline at end of file diff --git a/docs/the-great-game.gossip.gossip.html b/docs/the-great-game.gossip.gossip.html deleted file mode 100644 index 1245955..0000000 --- a/docs/the-great-game.gossip.gossip.html +++ /dev/null @@ -1,3 +0,0 @@ - -the-great-game.gossip.gossip documentation

the-great-game.gossip.gossip

Interchange of news events between agents agents

dialogue

(dialogue enquirer respondent world)

Dialogue between an enquirer and an agent in this world; returns a map identical to enquirer except that its :gossip collection may have additional entries.

gather-news

(gather-news world)(gather-news world gossip)

TODO: write docs

move-gossip

(move-gossip gossip world new-location)

Return a world like this world but with this gossip moved to this new-location. Many gossips are essentially shadow-records of agents of other types, and the movement if the gossip should be controlled by the run function of the type of the record they shadow. The #run function below does NOT call this function.

run

(run world)

Return a world like this world, with news items exchanged between gossip agents.

\ No newline at end of file diff --git a/docs/the-great-game.merchants.markets.html b/docs/the-great-game.merchants.markets.html deleted file mode 100644 index 6358e64..0000000 --- a/docs/the-great-game.merchants.markets.html +++ /dev/null @@ -1,3 +0,0 @@ - -the-great-game.merchants.markets documentation

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

adjust-quantity-and-price

(adjust-quantity-and-price world city commodity)

Adjust the quantity of this commodity currently in stock in this city of this world. Return a fragmentary world which can be deep-merged into this world.

new-price

(new-price old stock supply demand)

If stock is greater than the maximum of supply and demand, then there is surplus and old price is too high, so shold be reduced. If lower, then it is too low and should be increased.

run

(run world)

Return a world like this world, with quantities and prices in markets updated to reflect supply and demand.

update-markets

(update-markets world)(update-markets world city)(update-markets world city commodity)

Return a world like this world, with quantities and prices in markets updated to reflect supply and demand. If city or city and commodity are specified, return a fragmentary world with only the changes for that city (and commodity if specified) populated.

\ No newline at end of file diff --git a/docs/the-great-game.merchants.merchant-utils.html b/docs/the-great-game.merchants.merchant-utils.html deleted file mode 100644 index 8936d0a..0000000 --- a/docs/the-great-game.merchants.merchant-utils.html +++ /dev/null @@ -1,3 +0,0 @@ - -the-great-game.merchants.merchant-utils documentation

the-great-game.merchants.merchant-utils

Useful functions for doing low-level things with merchants.

add-known-prices

(add-known-prices merchant world)

Add the current prices at this merchant’s location in the world to a new cacke of known prices, and return it.

add-stock

(add-stock a b)

Where a and b are both maps all of whose values are numbers, return a map whose keys are a union of the keys of a and b and whose values are the sums of their respective values.

burden

(burden merchant world)

The total weight of the current cargo carried by this merchant in this world.

can-afford

(can-afford merchant world commodity)

Return the number of units of this commodity which this merchant can afford to buy in this world.

can-carry

(can-carry merchant world commodity)

Return the number of units of this commodity which this merchant can carry in this world, given their current burden.

expected-price

(expected-price merchant commodity city)

Find the price anticipated, given this world, by this merchant for this commodity in this city. If no information, assume 1. merchant should be passed as a map, commodity and city should be passed as keywords.

\ No newline at end of file diff --git a/docs/the-great-game.merchants.merchants.html b/docs/the-great-game.merchants.merchants.html deleted file mode 100644 index f1b8e01..0000000 --- a/docs/the-great-game.merchants.merchants.html +++ /dev/null @@ -1,3 +0,0 @@ - -the-great-game.merchants.merchants documentation

the-great-game.merchants.merchants

Trade planning for merchants, primarily.

run

(run world)

Return a partial world based on this world, but with each merchant moved.

\ No newline at end of file diff --git a/docs/the-great-game.merchants.planning.html b/docs/the-great-game.merchants.planning.html deleted file mode 100644 index 9e74759..0000000 --- a/docs/the-great-game.merchants.planning.html +++ /dev/null @@ -1,26 +0,0 @@ - -the-great-game.merchants.planning documentation

the-great-game.merchants.planning

Trade planning for merchants, primarily.

augment-plan

(augment-plan merchant world plan)

Augment this plan constructed in this world for this merchant with the :quantity of goods which should be bought and the :expected-profit of the trade.

-

Returns the augmented plan.

generate-trade-plans

(generate-trade-plans merchant world commodity)

Generate all possible trade plans for this merchant and this commodity in this world.

-

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.
  • -

nearest-with-targets

(nearest-with-targets plans targets)

Return the distance to the nearest destination among those of these plans which match these targets. Plans are expected to be plans as returned by generate-trade-plans, q.v.; targets are expected to be as accepted by make-target-filter, q.v.

plan-trade

(plan-trade merchant world commodity)

Find the best destination in this world for this commodity given this merchant and this origin. If two cities are anticipated to offer the same price, the nearer should be preferred; if two are equally distant, the ones nearer to the merchant’s home should be preferred. merchant may be passed as a map or a keyword; commodity should be passed as a keyword.

-

The returned plan is a map 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.
  • -

select-cargo

(select-cargo merchant world)

A merchant, in a given location in a world, will choose to buy a cargo within the limit they are capable of carrying, which they can anticipate selling for a profit at a destination.

\ No newline at end of file diff --git a/docs/the-great-game.merchants.strategies.simple.html b/docs/the-great-game.merchants.strategies.simple.html deleted file mode 100644 index 6b707f0..0000000 --- a/docs/the-great-game.merchants.strategies.simple.html +++ /dev/null @@ -1,4 +0,0 @@ - -the-great-game.merchants.strategies.simple documentation

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.

move-merchant

(move-merchant merchant 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.

plan-and-buy

(plan-and-buy merchant world)

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.

re-plan

(re-plan merchant world)

Having failed to sell a cargo at current location, re-plan a route to sell the current cargo. Returns a revised world.

sell-and-buy

(sell-and-buy merchant world)

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.

\ No newline at end of file diff --git a/docs/the-great-game.utils.html b/docs/the-great-game.utils.html deleted file mode 100644 index b46fa14..0000000 --- a/docs/the-great-game.utils.html +++ /dev/null @@ -1,3 +0,0 @@ - -the-great-game.utils documentation

the-great-game.utils

TODO: write docs

cyclic?

(cyclic? route)

True if two or more elements of route are identical

deep-merge

(deep-merge & maps)

make-target-filter

(make-target-filter targets)

Construct a filter which, when applied to a list of maps, will pass those which match these targets, where each target is a tuple [key value].

\ No newline at end of file diff --git a/docs/the-great-game.world.routes.html b/docs/the-great-game.world.routes.html deleted file mode 100644 index 8108252..0000000 --- a/docs/the-great-game.world.routes.html +++ /dev/null @@ -1,3 +0,0 @@ - -the-great-game.world.routes documentation

the-great-game.world.routes

Conceptual (plan level) routes, represented as tuples of location ids.

find-route

(find-route world-or-routes from to)

Find a single route from from to to in this world-or-routes, which may be either a world as defined in the-great-game.world.world or else a sequence of tuples of keywords.

find-routes

(find-routes routes from)(find-routes routes from to)(find-routes routes from to steps)

Find routes from among these routes from from; if to is supplied, to to, by breadth-first search.

\ No newline at end of file diff --git a/docs/the-great-game.world.run.html b/docs/the-great-game.world.run.html deleted file mode 100644 index 2ae1008..0000000 --- a/docs/the-great-game.world.run.html +++ /dev/null @@ -1,3 +0,0 @@ - -the-great-game.world.run documentation

the-great-game.world.run

Run the whole simulation

init

(init)(init config)

TODO: write docs

run

(run world)(run world date)

The pipeline to run the simulation each game day. Returns a world like this world, with all the various active elements updated. The optional date argument, if supplied, is set as the :date of the returned world.

\ No newline at end of file diff --git a/docs/the-great-game.world.world.html b/docs/the-great-game.world.world.html deleted file mode 100644 index efe608f..0000000 --- a/docs/the-great-game.world.world.html +++ /dev/null @@ -1,3 +0,0 @@ - -the-great-game.world.world documentation

the-great-game.world.world

Access to data about the world

actual-price

(actual-price world commodity city)

Find the actual current price of this commodity in this city given this world. NOTE that merchants can only know the actual prices in the city in which they are currently located.

default-world

A basic world for testing concepts

run

(run world)(run world date)

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.

\ No newline at end of file diff --git a/project.clj b/project.clj index e7d8a3b..5b84e60 100644 --- a/project.clj +++ b/project.clj @@ -1,7 +1,8 @@ (defproject the-great-game "0.1.0-SNAPSHOT" + :cloverage {:output "docs/cloverage"} :codox {:metadata {:doc "**TODO**: write docs" :doc/format :markdown} - :output-path "docs" + :output-path "docs/codox" :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"] @@ -10,5 +11,16 @@ :license {:name "GNU General Public License,version 2.0 or (at your option) any later version" :url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"} :plugins [[lein-codox "0.10.3"]] + + :release-tasks [["vcs" "assert-committed"] + ["change" "version" "leiningen.release/bump-version" "release"] + ["vcs" "commit"] + ["vcs" "tag" "v." "--no-sign"] + ["clean"] + ["" ] + ["uberjar"] + ["change" "version" "leiningen.release/bump-version"] + ["vcs" "commit"]] + :url "https://github.com/simon-brooke/the-great-game" ) diff --git a/src/the_great_game/merchants/planning.clj b/src/the_great_game/merchants/planning.clj index 8ac2d01..e784036 100644 --- a/src/the_great_game/merchants/planning.clj +++ b/src/the_great_game/merchants/planning.clj @@ -1,5 +1,8 @@ (ns the-great-game.merchants.planning - "Trade planning for merchants, primarily." + "Trade planning for merchants, primarily. This follows a simple-minded + generate-and-test strategy and currently generates plans for all possible + routes from the current location. This may not scale. Also, routes do not + currently have cost or risk associated with them." (:require [the-great-game.utils :refer [deep-merge make-target-filter]] [the-great-game.merchants.merchant-utils :refer :all] [the-great-game.world.routes :refer [find-route]] From be8ec50c36f0720b4a612f6523e8d8d57b3f7199 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 29 May 2019 12:16:37 +0100 Subject: [PATCH 4/9] Added documentation index --- docs/index.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 docs/index.html diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..35656b5 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,13 @@ + + + + The Great Game: Dcocumentation + + +

The Great Game: Dcocumentation

+ + + From 8204e497c125d6dab45cd9e3a5c87b27ffc1c4b4 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 29 May 2019 13:15:14 +0100 Subject: [PATCH 5/9] Added documentation generation to release tasks --- project.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 5b84e60..266f8da 100644 --- a/project.clj +++ b/project.clj @@ -17,7 +17,8 @@ ["vcs" "commit"] ["vcs" "tag" "v." "--no-sign"] ["clean"] - ["" ] + ["codox"] + ["cloverage"] ["uberjar"] ["change" "version" "leiningen.release/bump-version"] ["vcs" "commit"]] From d7f13373ea29f870365bd1fa778cc3e0ff8e1cd9 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 29 May 2019 13:34:55 +0100 Subject: [PATCH 6/9] Attempt to solve the codox release problem --- project.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 266f8da..1bf2b63 100644 --- a/project.clj +++ b/project.clj @@ -10,7 +10,8 @@ :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" :url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"} - :plugins [[lein-codox "0.10.3"]] + :plugins [[lein-cloverage "1.1.1"] + [lein-codox "0.10.7"]] :release-tasks [["vcs" "assert-committed"] ["change" "version" "leiningen.release/bump-version" "release"] From 66742ebfa98d2e5e4d2f5d9f4ada566ddfe7b517 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 29 May 2019 13:35:04 +0100 Subject: [PATCH 7/9] Version 0.1.0 --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 1bf2b63..02d99dd 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject the-great-game "0.1.0-SNAPSHOT" +(defproject the-great-game "0.1.0" :cloverage {:output "docs/cloverage"} :codox {:metadata {:doc "**TODO**: write docs" :doc/format :markdown} From 39b1271378d5a98322390c953ba6d687fa183982 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 29 May 2019 13:35:26 +0100 Subject: [PATCH 8/9] Version 0.1.1-SNAPSHOT --- docs/codox/economy.html | 2 +- docs/codox/index.html | 2 +- docs/codox/intro.html | 2 +- docs/codox/modelling_trading_cost_and_risk.html | 2 +- docs/codox/sexual-dimorphism.html | 2 +- docs/codox/the-great-game.core.html | 2 +- docs/codox/the-great-game.gossip.gossip.html | 2 +- docs/codox/the-great-game.merchants.markets.html | 2 +- docs/codox/the-great-game.merchants.merchant-utils.html | 2 +- docs/codox/the-great-game.merchants.merchants.html | 2 +- docs/codox/the-great-game.merchants.planning.html | 2 +- docs/codox/the-great-game.merchants.strategies.simple.html | 2 +- docs/codox/the-great-game.utils.html | 2 +- docs/codox/the-great-game.world.routes.html | 2 +- docs/codox/the-great-game.world.run.html | 2 +- docs/codox/the-great-game.world.world.html | 2 +- project.clj | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/codox/economy.html b/docs/codox/economy.html index ce3f593..8d85673 100644 --- a/docs/codox/economy.html +++ b/docs/codox/economy.html @@ -1,6 +1,6 @@ -Game world economy

Game world economy

+Game world economy

Game world economy

Broadly this essay extends ideas presented in Populating a game world, q.v.

Primary producers

Herdsfolk

diff --git a/docs/codox/index.html b/docs/codox/index.html index 545a41e..0a34e6b 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -The-great-game 0.1.0-SNAPSHOT

The-great-game 0.1.0-SNAPSHOT

Released under the GNU General Public License,version 2.0 or (at your option) any later version

Prototype code towards the great game I've been writing about for ten years, and know I will never finish.

Installation

To install, add the following dependency to your project or build file:

[the-great-game "0.1.0-SNAPSHOT"]

Topics

Namespaces

the-great-game.core

TODO: write docs

Public variables and functions:

the-great-game.gossip.gossip

Interchange of news events between agents agents

Public variables and functions:

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

Public variables and functions:

the-great-game.merchants.merchant-utils

Useful functions for doing low-level things with merchants.

the-great-game.merchants.merchants

Trade planning for merchants, primarily.

Public variables and functions:

the-great-game.merchants.planning

Trade planning for merchants, primarily. This follows a simple-minded generate-and-test strategy and currently generates plans for all possible routes from the current location. This may not scale. Also, routes do not currently have cost or risk associated with them.

the-great-game.merchants.strategies.simple

Default trading strategy for merchants.

Public variables and functions:

the-great-game.utils

TODO: write docs

Public variables and functions:

the-great-game.world.routes

Conceptual (plan level) routes, represented as tuples of location ids.

Public variables and functions:

the-great-game.world.run

Run the whole simulation

Public variables and functions:

the-great-game.world.world

Access to data about the world

Public variables and functions:

\ No newline at end of file +The-great-game 0.1.0

The-great-game 0.1.0

Released under the GNU General Public License,version 2.0 or (at your option) any later version

Prototype code towards the great game I've been writing about for ten years, and know I will never finish.

Installation

To install, add the following dependency to your project or build file:

[the-great-game "0.1.0"]

Topics

Namespaces

the-great-game.core

TODO: write docs

Public variables and functions:

the-great-game.gossip.gossip

Interchange of news events between agents agents

Public variables and functions:

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

Public variables and functions:

the-great-game.merchants.merchant-utils

Useful functions for doing low-level things with merchants.

the-great-game.merchants.merchants

Trade planning for merchants, primarily.

Public variables and functions:

the-great-game.merchants.planning

Trade planning for merchants, primarily. This follows a simple-minded generate-and-test strategy and currently generates plans for all possible routes from the current location. This may not scale. Also, routes do not currently have cost or risk associated with them.

the-great-game.merchants.strategies.simple

Default trading strategy for merchants.

Public variables and functions:

the-great-game.utils

TODO: write docs

Public variables and functions:

the-great-game.world.routes

Conceptual (plan level) routes, represented as tuples of location ids.

Public variables and functions:

the-great-game.world.run

Run the whole simulation

Public variables and functions:

the-great-game.world.world

Access to data about the world

Public variables and functions:

\ No newline at end of file diff --git a/docs/codox/intro.html b/docs/codox/intro.html index 691703e..32cce64 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -1,6 +1,6 @@ -Introduction to the-great-game

Introduction to the-great-game

+Introduction to the-great-game

Introduction to the-great-game

The Great Game

In this essay I’m going to try to pull together a number of my architectural ideas about the Great Game which I know I’m never actually going to build - because it’s vastly too big for any one person to build - into one overall vision.

So, firstly, how does one characterise this game?

diff --git a/docs/codox/modelling_trading_cost_and_risk.html b/docs/codox/modelling_trading_cost_and_risk.html index 71c862f..88dd04c 100644 --- a/docs/codox/modelling_trading_cost_and_risk.html +++ b/docs/codox/modelling_trading_cost_and_risk.html @@ -1,6 +1,6 @@ -Modelling trading cost and risk

Modelling trading cost and risk

+Modelling trading cost and risk

Modelling trading cost and risk

In a dynamic pre-firearms world with many small states and contested regions, trade is not going to be straightforward. Not only will different routes have different physical characteristics - more or less mountainous, more or fewer unbridged river crossings - they will also have different political characteristics: more of less taxed, more or less effectively policed.

Raids by outlaws are expected to be part of the game economy. News of raids are the sort of things which may propagate through the gossip system. So are changes in taxation regime. Obviously, knowledge items can affect merchants’ trading strategy; in existing prototype code, individual merchants already each keep their own cache of known historical prices, and exchange historical price data with one another; and use this price data to select trades to make.

So: to what extent is it worth modelling the spread of knowledge of trade cost and risk?

diff --git a/docs/codox/sexual-dimorphism.html b/docs/codox/sexual-dimorphism.html index 54624d7..8cf4c50 100644 --- a/docs/codox/sexual-dimorphism.html +++ b/docs/codox/sexual-dimorphism.html @@ -1,6 +1,6 @@ -Sexual dimorphism

Sexual dimorphism

+Sexual dimorphism

Sexual dimorphism

This essay is going to upset a lot of people, so let’s start with a statement of what it is about: it is an attempt to describe the systematically different behaviours of men and women, in sufficient detail that this can be represented by agents in a game world. It’s trying to allow as broad as possible a range of cultures to be represented, so when I’m talking about what I consider to be behaviours of particular cultures, I’ll say that.

Of course, I’m writing this from the view point of an old white male. It’s not possible to write about these things from a totally neutral viewpoint, and every one of us will have prejudices.

OK? Let’s start.

diff --git a/docs/codox/the-great-game.core.html b/docs/codox/the-great-game.core.html index 72be981..e85f03f 100644 --- a/docs/codox/the-great-game.core.html +++ b/docs/codox/the-great-game.core.html @@ -1,3 +1,3 @@ -the-great-game.core documentation

the-great-game.core

TODO: write docs

foo

(foo x)

I don’t do a whole lot.

\ No newline at end of file +the-great-game.core documentation

the-great-game.core

TODO: write docs

foo

(foo x)

I don’t do a whole lot.

\ No newline at end of file diff --git a/docs/codox/the-great-game.gossip.gossip.html b/docs/codox/the-great-game.gossip.gossip.html index 46e5d39..3e8cf5c 100644 --- a/docs/codox/the-great-game.gossip.gossip.html +++ b/docs/codox/the-great-game.gossip.gossip.html @@ -1,3 +1,3 @@ -the-great-game.gossip.gossip documentation

the-great-game.gossip.gossip

Interchange of news events between agents agents

dialogue

(dialogue enquirer respondent world)

Dialogue between an enquirer and an agent in this world; returns a map identical to enquirer except that its :gossip collection may have additional entries.

gather-news

(gather-news world)(gather-news world gossip)

TODO: write docs

move-gossip

(move-gossip gossip world new-location)

Return a world like this world but with this gossip moved to this new-location. Many gossips are essentially shadow-records of agents of other types, and the movement if the gossip should be controlled by the run function of the type of the record they shadow. The #run function below does NOT call this function.

run

(run world)

Return a world like this world, with news items exchanged between gossip agents.

\ No newline at end of file +the-great-game.gossip.gossip documentation

the-great-game.gossip.gossip

Interchange of news events between agents agents

dialogue

(dialogue enquirer respondent world)

Dialogue between an enquirer and an agent in this world; returns a map identical to enquirer except that its :gossip collection may have additional entries.

gather-news

(gather-news world)(gather-news world gossip)

TODO: write docs

move-gossip

(move-gossip gossip world new-location)

Return a world like this world but with this gossip moved to this new-location. Many gossips are essentially shadow-records of agents of other types, and the movement if the gossip should be controlled by the run function of the type of the record they shadow. The #run function below does NOT call this function.

run

(run world)

Return a world like this world, with news items exchanged between gossip agents.

\ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.markets.html b/docs/codox/the-great-game.merchants.markets.html index a991e82..3c3d546 100644 --- a/docs/codox/the-great-game.merchants.markets.html +++ b/docs/codox/the-great-game.merchants.markets.html @@ -1,3 +1,3 @@ -the-great-game.merchants.markets documentation

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

adjust-quantity-and-price

(adjust-quantity-and-price world city commodity)

Adjust the quantity of this commodity currently in stock in this city of this world. Return a fragmentary world which can be deep-merged into this world.

new-price

(new-price old stock supply demand)

If stock is greater than the maximum of supply and demand, then there is surplus and old price is too high, so shold be reduced. If lower, then it is too low and should be increased.

run

(run world)

Return a world like this world, with quantities and prices in markets updated to reflect supply and demand.

update-markets

(update-markets world)(update-markets world city)(update-markets world city commodity)

Return a world like this world, with quantities and prices in markets updated to reflect supply and demand. If city or city and commodity are specified, return a fragmentary world with only the changes for that city (and commodity if specified) populated.

\ No newline at end of file +the-great-game.merchants.markets documentation

the-great-game.merchants.markets

Adjusting quantities and prices in markets.

adjust-quantity-and-price

(adjust-quantity-and-price world city commodity)

Adjust the quantity of this commodity currently in stock in this city of this world. Return a fragmentary world which can be deep-merged into this world.

new-price

(new-price old stock supply demand)

If stock is greater than the maximum of supply and demand, then there is surplus and old price is too high, so shold be reduced. If lower, then it is too low and should be increased.

run

(run world)

Return a world like this world, with quantities and prices in markets updated to reflect supply and demand.

update-markets

(update-markets world)(update-markets world city)(update-markets world city commodity)

Return a world like this world, with quantities and prices in markets updated to reflect supply and demand. If city or city and commodity are specified, return a fragmentary world with only the changes for that city (and commodity if specified) populated.

\ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.merchant-utils.html b/docs/codox/the-great-game.merchants.merchant-utils.html index d7940c2..b1b0b5f 100644 --- a/docs/codox/the-great-game.merchants.merchant-utils.html +++ b/docs/codox/the-great-game.merchants.merchant-utils.html @@ -1,3 +1,3 @@ -the-great-game.merchants.merchant-utils documentation

the-great-game.merchants.merchant-utils

Useful functions for doing low-level things with merchants.

add-known-prices

(add-known-prices merchant world)

Add the current prices at this merchant’s location in the world to a new cacke of known prices, and return it.

add-stock

(add-stock a b)

Where a and b are both maps all of whose values are numbers, return a map whose keys are a union of the keys of a and b and whose values are the sums of their respective values.

burden

(burden merchant world)

The total weight of the current cargo carried by this merchant in this world.

can-afford

(can-afford merchant world commodity)

Return the number of units of this commodity which this merchant can afford to buy in this world.

can-carry

(can-carry merchant world commodity)

Return the number of units of this commodity which this merchant can carry in this world, given their current burden.

expected-price

(expected-price merchant commodity city)

Find the price anticipated, given this world, by this merchant for this commodity in this city. If no information, assume 1. merchant should be passed as a map, commodity and city should be passed as keywords.

\ No newline at end of file +the-great-game.merchants.merchant-utils documentation

the-great-game.merchants.merchant-utils

Useful functions for doing low-level things with merchants.

add-known-prices

(add-known-prices merchant world)

Add the current prices at this merchant’s location in the world to a new cacke of known prices, and return it.

add-stock

(add-stock a b)

Where a and b are both maps all of whose values are numbers, return a map whose keys are a union of the keys of a and b and whose values are the sums of their respective values.

burden

(burden merchant world)

The total weight of the current cargo carried by this merchant in this world.

can-afford

(can-afford merchant world commodity)

Return the number of units of this commodity which this merchant can afford to buy in this world.

can-carry

(can-carry merchant world commodity)

Return the number of units of this commodity which this merchant can carry in this world, given their current burden.

expected-price

(expected-price merchant commodity city)

Find the price anticipated, given this world, by this merchant for this commodity in this city. If no information, assume 1. merchant should be passed as a map, commodity and city should be passed as keywords.

\ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.merchants.html b/docs/codox/the-great-game.merchants.merchants.html index f63ba36..870c8d7 100644 --- a/docs/codox/the-great-game.merchants.merchants.html +++ b/docs/codox/the-great-game.merchants.merchants.html @@ -1,3 +1,3 @@ -the-great-game.merchants.merchants documentation

the-great-game.merchants.merchants

Trade planning for merchants, primarily.

run

(run world)

Return a partial world based on this world, but with each merchant moved.

\ No newline at end of file +the-great-game.merchants.merchants documentation

the-great-game.merchants.merchants

Trade planning for merchants, primarily.

run

(run world)

Return a partial world based on this world, but with each merchant moved.

\ No newline at end of file diff --git a/docs/codox/the-great-game.merchants.planning.html b/docs/codox/the-great-game.merchants.planning.html index 9751a9b..1274f66 100644 --- a/docs/codox/the-great-game.merchants.planning.html +++ b/docs/codox/the-great-game.merchants.planning.html @@ -1,6 +1,6 @@ -the-great-game.merchants.planning documentation

the-great-game.merchants.planning

Trade planning for merchants, primarily. This follows a simple-minded generate-and-test strategy and currently generates plans for all possible routes from the current location. This may not scale. Also, routes do not currently have cost or risk associated with them.

augment-plan

(augment-plan merchant world plan)

Augment this plan constructed in this world for this merchant with the :quantity of goods which should be bought and the :expected-profit of the trade.

+the-great-game.merchants.planning documentation

the-great-game.merchants.planning

Trade planning for merchants, primarily. This follows a simple-minded generate-and-test strategy and currently generates plans for all possible routes from the current location. This may not scale. Also, routes do not currently have cost or risk associated with them.

augment-plan

(augment-plan merchant world plan)

Augment this plan constructed in this world for this merchant with the :quantity of goods which should be bought and the :expected-profit of the trade.

Returns the augmented plan.

generate-trade-plans

(generate-trade-plans merchant world commodity)

Generate all possible trade plans for this merchant and this commodity in this world.

Returned plans are maps with keys:

    diff --git a/docs/codox/the-great-game.merchants.strategies.simple.html b/docs/codox/the-great-game.merchants.strategies.simple.html index 8032a66..ca81e5b 100644 --- a/docs/codox/the-great-game.merchants.strategies.simple.html +++ b/docs/codox/the-great-game.merchants.strategies.simple.html @@ -1,4 +1,4 @@ -the-great-game.merchants.strategies.simple documentation

    the-great-game.merchants.strategies.simple

    Default trading strategy for merchants.

    +the-great-game.merchants.strategies.simple documentation

    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.

    move-merchant

    (move-merchant merchant 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.

    plan-and-buy

    (plan-and-buy merchant world)

    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.

    re-plan

    (re-plan merchant world)

    Having failed to sell a cargo at current location, re-plan a route to sell the current cargo. Returns a revised world.

    sell-and-buy

    (sell-and-buy merchant world)

    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.

    \ No newline at end of file diff --git a/docs/codox/the-great-game.utils.html b/docs/codox/the-great-game.utils.html index 4242f1b..b23284d 100644 --- a/docs/codox/the-great-game.utils.html +++ b/docs/codox/the-great-game.utils.html @@ -1,3 +1,3 @@ -the-great-game.utils documentation

    the-great-game.utils

    TODO: write docs

    cyclic?

    (cyclic? route)

    True if two or more elements of route are identical

    deep-merge

    (deep-merge & maps)

    make-target-filter

    (make-target-filter targets)

    Construct a filter which, when applied to a list of maps, will pass those which match these targets, where each target is a tuple [key value].

    \ No newline at end of file +the-great-game.utils documentation

    the-great-game.utils

    TODO: write docs

    cyclic?

    (cyclic? route)

    True if two or more elements of route are identical

    deep-merge

    (deep-merge & maps)

    make-target-filter

    (make-target-filter targets)

    Construct a filter which, when applied to a list of maps, will pass those which match these targets, where each target is a tuple [key value].

    \ No newline at end of file diff --git a/docs/codox/the-great-game.world.routes.html b/docs/codox/the-great-game.world.routes.html index 4cde466..4694894 100644 --- a/docs/codox/the-great-game.world.routes.html +++ b/docs/codox/the-great-game.world.routes.html @@ -1,3 +1,3 @@ -the-great-game.world.routes documentation

    the-great-game.world.routes

    Conceptual (plan level) routes, represented as tuples of location ids.

    find-route

    (find-route world-or-routes from to)

    Find a single route from from to to in this world-or-routes, which may be either a world as defined in the-great-game.world.world or else a sequence of tuples of keywords.

    find-routes

    (find-routes routes from)(find-routes routes from to)(find-routes routes from to steps)

    Find routes from among these routes from from; if to is supplied, to to, by breadth-first search.

    \ No newline at end of file +the-great-game.world.routes documentation

    the-great-game.world.routes

    Conceptual (plan level) routes, represented as tuples of location ids.

    find-route

    (find-route world-or-routes from to)

    Find a single route from from to to in this world-or-routes, which may be either a world as defined in the-great-game.world.world or else a sequence of tuples of keywords.

    find-routes

    (find-routes routes from)(find-routes routes from to)(find-routes routes from to steps)

    Find routes from among these routes from from; if to is supplied, to to, by breadth-first search.

    \ No newline at end of file diff --git a/docs/codox/the-great-game.world.run.html b/docs/codox/the-great-game.world.run.html index 109956c..1d3b216 100644 --- a/docs/codox/the-great-game.world.run.html +++ b/docs/codox/the-great-game.world.run.html @@ -1,3 +1,3 @@ -the-great-game.world.run documentation

    the-great-game.world.run

    Run the whole simulation

    init

    (init)(init config)

    TODO: write docs

    run

    (run world)(run world date)

    The pipeline to run the simulation each game day. Returns a world like this world, with all the various active elements updated. The optional date argument, if supplied, is set as the :date of the returned world.

    \ No newline at end of file +the-great-game.world.run documentation

    the-great-game.world.run

    Run the whole simulation

    init

    (init)(init config)

    TODO: write docs

    run

    (run world)(run world date)

    The pipeline to run the simulation each game day. Returns a world like this world, with all the various active elements updated. The optional date argument, if supplied, is set as the :date of the returned world.

    \ No newline at end of file diff --git a/docs/codox/the-great-game.world.world.html b/docs/codox/the-great-game.world.world.html index 0194ed5..fcec6c3 100644 --- a/docs/codox/the-great-game.world.world.html +++ b/docs/codox/the-great-game.world.world.html @@ -1,3 +1,3 @@ -the-great-game.world.world documentation

    the-great-game.world.world

    Access to data about the world

    actual-price

    (actual-price world commodity city)

    Find the actual current price of this commodity in this city given this world. NOTE that merchants can only know the actual prices in the city in which they are currently located.

    default-world

    A basic world for testing concepts

    run

    (run world)(run world date)

    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.

    \ No newline at end of file +the-great-game.world.world documentation

    the-great-game.world.world

    Access to data about the world

    actual-price

    (actual-price world commodity city)

    Find the actual current price of this commodity in this city given this world. NOTE that merchants can only know the actual prices in the city in which they are currently located.

    default-world

    A basic world for testing concepts

    run

    (run world)(run world date)

    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.

    \ No newline at end of file diff --git a/project.clj b/project.clj index 02d99dd..ff35841 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject the-great-game "0.1.0" +(defproject the-great-game "0.1.1-SNAPSHOT" :cloverage {:output "docs/cloverage"} :codox {:metadata {:doc "**TODO**: write docs" :doc/format :markdown} From 2849a01db64d552b4e278e21914a3d78ee27e635 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 30 Jun 2019 11:23:04 +0100 Subject: [PATCH 9/9] Notes made to just catch some ideas --- doc/sandbox.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 doc/sandbox.md diff --git a/doc/sandbox.md b/doc/sandbox.md new file mode 100644 index 0000000..736f0ec --- /dev/null +++ b/doc/sandbox.md @@ -0,0 +1,66 @@ +# Sandbox + +Up to now I've been thinking of the Great Game as essentially an RPG with some sandbox-like elements; but I think it may be better to think of it as a sandbox game with some RPG like elements. + +Why? + +The core of the game is a world in which non-player characters have enough individual knowledge of the world and their immediate surroundings that they can sensibly answer questions like + +* Where is the nearest craftsman of this craft? +* What price can I expect to get for this item in the local market? +* What news have you heard recently? +* Where does this person from your village live? + +and where there's a sufficiently sophisticated and robust economy simulation that buying goods in one market and selling them in another is viable. + +The original BBC Micro space trading game Elite had very little more in terms of game mechanics than a sandbox with a means to navigate it and an economy simulation, which wasn't even nearly as sophisticated as the one I have working now. Yet that combination resulted in engaging game play. + +## Main sandbox roles + +The idea of a sandbox is that the player character should be able to do pretty much anything they like within the mechanics of the game. From that, it seems to me reasonable that the player ought to be able to do more or less everything a non-player character can do. But creating the game mechanics to make each additional task doable takes time and investment, so there's a need to prioritise. + +So, as Elite did, I propose to make the first available sandbox roles + +### Merchant + +Someone who travels from city to city, buying goods cheap in one and selling them for more in another; and + +### Outlaw + +Someone who intercepts and steals from merchants (and may also attack outlying farms and villages) + +## Second tier playable roles + +The next tier of playable roles rotates around issues arising from the mercantile ecosystem. + +### Aristocracy + +Aristocrats are basically settled outlaws who seek to establish a monopoly on extracting taxes from inhabitants and travellers in a particular region by driving out all other outlaws. Within the comain of an aristocrat, you have to pay tax but you're reasonably safe from being attacked by other outlaws and losing everything. Aristocrats may also maintain and improve roads and bridges and do other things to boost the economy of their territory, may expant into adjoining territory with no current aristocratic control, and may wage war on other aristocrats. + +An outlaw ought to be able to become an aristocrat, by dominating an ungoverned area or by defeating an existing aristocrat. + +### Soldiery + +Soldiers, like aristocrats, are basically on the same spectrum as outlaws. Outlaws may hire themselves out to merchants as caravan guards, or to aristocrats as soldiers. Soldiers or guards, falling on bad times, may revert to outlawry. + +## Routine, Discretion and Playability + +There's a term that's used in criticism of many computer games which is worth thinking about hard here: that term is 'farming'. 'Farming', in this sense, is doing something repetitive and dull to earn credits in a game. Generally this is not fun. What makes roles in a game-world fun is having individual discretion - the ability to choose between actions and strategies - and a lack of routine. + +Most craft skills - especially in the learning phase - are not like this, and crafts which are sophisticated enough to be actually engaging are very hard to model in a game. Learning a craft is essentially, inherently, repetitive and dull, and if you take that repetition out of it you probably don't have enough left to yield the feeling of mastery which would reward success; so it doesn't seem to me that making craft roles playable should be a priority. + +## Cruise control + +One of the most enjoyable aspects of The Witcher 3 - still my go-to game for ideas I want to improve on - is simply travelling through the world. Although fast travel is possible I find I rarely use it, and a journey which takes fifteen minutes of real world wall clock time can be enjoyable in and of itself. This is, of course, a credit to the beautiful way the world is realised. + +But nevertheless, in The Witcher 3, a decision was made to pack incident fairly densely - because players would find just travelling boring. This leads to a situation where peaceful villages exist two minutes travel from dangerous monsters or bandit camps, and the suspension of disbelief gets a little strained. Building a world big enough that a market simulation is believable means that for the individual, the travel time to a market where a particular desired good is likely to be cheaper becomes costly in itself. Otherwise, there's no arbitrage between markets and no ecological niche for a merchant to fill. The journey time from market to market has to be several in-game days. + +An in-game day doesn't have to be as long as a wall clock day, and, indeed, typically isn't. But nevertheless, doing several game days of incident-free travel, even in beautiful scenery, is not going to be engaging - which implies a fast-travel mechanic. + +I don't like fast travel, I find it a too-obvious breaking of immersion. Also, of course, one of the interesting things about a game in a merchant/outlaw ecosystem is the risk of interception on a journey. The Dragon Age series handled interrupted travel in 'fast travel' by randomly interacting the loading screen you get when moving from location to location in Dragon Age's patchwork worlds by dumping you into a tiny arena with enemies. That's really, really bad - there's no other way to say this. Everything about it shouts artifice. + +So I'm thinking of a different mechanism: one I'm calling cruise control. + +You set out on a task which will take a long time - such as a journey, but also such as any routine task. You're shown either a 'fast forward' of your character carrying out this task, or a series of cinematic 'shots along the way'. This depends, of course, on their being continuous renderable landscape between your departure and your destination, but there will be. This fast-forward proceeds at a substantially higher time gearing than normal game time - ten times as fast perhaps; we need it to, because as well as doing backgound scenery loading to move from one location to another, we're also simulating lots of non-player agents actions in parts of the world where the player currently isn't. So a 'jump cut' from one location to another isn't going to work anyway. + +The player can interrupt 'fast forward' at any time. But also, the game itself may bring you out of fast forward when it anticipates that there may be action which requires decision - for example, when there are outlaws in the vicinity. And it will do this **before** the player's party is under immediate attack - the player will have time to take stock of the situation and prepare appropriately. Finally, this will take place in the full open world; the player will have the option to choose *not* to enter the narrow defile, for example, to ask local people (if there are any) for any news of outlaw activity, or, if they are available, to send forward scouts.