From b250310efc1d043417c399d9473bc98d346781e1 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 12 Apr 2020 16:47:37 +0100 Subject: [PATCH 01/12] Added a stub file for agent --- src/the_great_game/agent/agent.clj | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/the_great_game/agent/agent.clj diff --git a/src/the_great_game/agent/agent.clj b/src/the_great_game/agent/agent.clj new file mode 100644 index 0000000..b9232ef --- /dev/null +++ b/src/the_great_game/agent/agent.clj @@ -0,0 +1,4 @@ +(ns the-great-game.agent.agent + "Anything in the game world with agency") + +;; hierarchy of needs probably gets implemented here From f6d989135e7893b065ef7b811a58aeeefdef805b Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 12 Apr 2020 17:11:21 +0100 Subject: [PATCH 02/12] Added cucumber and gorilla --- .gitignore | 2 ++ doc/naming-of-characters.md | 34 +++++++++++++++++++++++++++ doc/on-dying.ods | 13 ++++++++++ doc/orgnic-quests.md | 47 +++++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 doc/naming-of-characters.md create mode 100644 doc/on-dying.ods create mode 100644 doc/orgnic-quests.md diff --git a/.gitignore b/.gitignore index 0910231..7387005 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ pom.xml.asc .nrepl-port .cpcache/ *~ + +docs/cloverage/ diff --git a/doc/naming-of-characters.md b/doc/naming-of-characters.md new file mode 100644 index 0000000..8ca8e80 --- /dev/null +++ b/doc/naming-of-characters.md @@ -0,0 +1,34 @@ +# Naming of Characters + +Generally speaking, in modern RPGs, every character with any impact on the plot has a distinct name. But if we are going to give all non-player characters sufficient agency to impact on the plot, then we must have a way of naming tens or hundreds of thousands of characters, and distinct names will become problematic (even if we're procedurally generating names, which we shall have to do. So this note is about how characters are named. + + +The full name of each character will be made up as follows: + +[epithet] [clan] [personal-name] the [trade-or-rank] of [location], son/daughter of [parent] + +Based on, roughly, historical name patterns like + +Archibald (personal-name) the Grim (epithet), Earl (trade-or-rank) of Douglas (location) + +Where + +1. *epithet* is a prefix based on some notable feature or feat of the character. Most characters won't have an epithet, unless they have some notable feature or they've done something notable. If a character does something notable in the course of the game, they will subsequently gain an epithet; 'notability' may be measured by how many times the event is transmitted through the gossip network. + +2. *clan* is special to the Western Clans, although people from the Great Place may possible use the name of their house similarly. + +3. *personal-name* is chosen from one of a limited set of limited sets; different cultural groups will have different (possibly overlapping) sets of names, but within each set there will only be a limited subset + +4. *trade-or-rank* is just that. "Smith", "Miller", "Ariston", "Captain". Either only master craftsfolk have the trade-or-rank name of their craft, or we distinguish between 'Calon the Smith', who may be a journeyman, and 'Calon the Master Smith', who is a master. + +5. *location* is the name of a location; a village, town, city or province. The location which forms part of a character's name is the location where there current home is, not the location where they were born or where their ancestors came from + +Full names will almost never be used - only, perhaps, in extremely formal circumstances. The form of a name used will depend on context, and will generally be just sufficient to disambiguate the character in the context. + +If the speaker is in Sinhua and referring to someone from Sinhua, they won't refer to them as 'of Sinhua'. + +If everyone present is a bargee and the speaker referring to someone who is also a bargee, they won't refer to them as 'the bargee'. + +The question asked influences the context: in answer to the question 'who is the best sword smith', the answer will not be 'Calon the Smith' but 'Calon of Sinhua'. + +Patronymics/matronymics will not normally be used of adults (although they may be used of apprentices and journeymen. diff --git a/doc/on-dying.ods b/doc/on-dying.ods new file mode 100644 index 0000000..0251f2d --- /dev/null +++ b/doc/on-dying.ods @@ -0,0 +1,13 @@ +# On Dying + +Death is the end of your story. One of the tropes in games which, for me, most breaks immersion is when you lose a fight and are presented with a screen that says 'you are dead. Do you want to reload your last save?' Life is not like that. We do not have save-states. We die. + +So how could this be better handled? + +You lose a fight. Switch to cutscene: the battlefield, after the fight, your body is there. Probably no sound. A party of non-enemies crosses the battlefield and finds your body. We see surprise and concern. They gather around you. Cut to interior scene, you are in a bed, unconcious, being tended; cut to similar interior scene, you are in a bed, conscious, being tended; cut to exterior scene, you are sitting with some of your saviours, and the game restarts. + +Time has passed; events in the game world have moved on. You can talk to your saviours about it. You have lost a lot of strength, and most of the gear you were carrying. You must do whatever it is you do within the game mechanics to rebuild strength, and to acquire more gear. Significantly you have acquired a debt of honour to your saviours, which they may call on later. You almost certainly have new scars, and might possibly have some lasting effects (although how that interacts with other game mechanics might be tricky). + +So who are the non-enemies? It depends on context. If you have a party, and some of that party survived the fight, it's your party. Otherwise, if you're in a populated place, it's locals. If it's on a road or other route, it's passing merchants. If you're in the wilderness, a hunting party. It's a bunch of non-hostiles who might reasonably be expected to be around: that's what matters. It's about not breaking immersion. + +Obviously losing a fight must have weight, it must have meaning, it must have in-game consequences; otherwise it is meaningless. diff --git a/doc/orgnic-quests.md b/doc/orgnic-quests.md new file mode 100644 index 0000000..bf789b7 --- /dev/null +++ b/doc/orgnic-quests.md @@ -0,0 +1,47 @@ +# Organic Quests + +The structure of a modern Role Playing Came revolves around 'quests': tasks that the player character is invited to do, either by the framing narrative of the game or by some non-player character ('the Quest Giver'). Normally there is one core quest which provides the overarching narrative for the whole game. [Wikipedia](https://en.wikipedia.org/wiki/Quest_(gaming)) offers a typology of quests as follows: + +1. Kill quests +2. Combo quests +3. Delivery quests +4. Gather quests +5. Escort quests +6. Syntax quests +7. Hybrids + +'Gather quests' are more frequently referred to in the literature as 'fetch quests', and 'kill quests' are simply a specialised form of fetch quest where the item to be fetched is a trophy of the kill. A delivery quest is a sort of reverse fetch quest: instead of going to some location or NPC and getting a specific item to return to the quest giver, the player is tasked to take a specific item from the quest giver to some location or NPC. + +Hybrids are in effect chains of quests: do this task in order to get this precondition of this other task, in order to get the overall objective; obviously such chains can be deep and involved - the 'main quest' of every role playing game I know of is a chain or hybrid quest. + +My understanding is that what Wikipedia means by a 'syntax quest' is what one would normally call a puzzle. + +An escort quest is typically a request to take a specified non-player character safely through a dangerous area. + +Combo quests are not, in my opinion, particularly relevant to the sorts of game we're discussing here. + +So essentially quests break down into three core types + +1. Fetch and deliver quests +2. Escort quests +3. Puzzles + +which are combined together into more or less complex chains, where the simplest chain is a single quest. + +Given that quests are as simple as this, it's obvious that narrative sophistication is required to make them interesting; and this point is clearly made by some variants of roguelike games which procedurally generate quests: they're generally pretty dull. By contrast, the Witcher series is full of fetch-quests which are made to really matter by being wrapped in interesting character interaction and narrative plausibility. Very often this takes the form of tragedy: as one reviewer pointed out, the missing relatives that Geralt is asked to find generally turn out to be (horribly) dead. In other words, creative scripting tends to deliver much more narratively satisfying quests than is usually delivered by procedural generation. + +But, if we're thinking of a game with much more intelligent non-player characters with much more conversational repertoir, as I am, can satisfying quests emerge organically? In space trading games such as [Elite](https://www.telegraph.co.uk/games/11051122/Elite-the-game-that-changed-the-world.html), a primary activity is moving goods from markets with surplus (and thus low prices) to markets with shortage (and thus high prices). This is, in effect, a game made up of deliver quests - but rather than deliver quests which are scripted, they are deliver quests which arise organically out of the structure of the game world. + +I already have working code for non-player character merchants, who move goods from city to city based on market information available to them. For player characters to join in this trading is an organic activity emerging from the structure of the world, which provides an activity. But moving merchants provides a market opportunity for bandits, who can intercept and steal cargoes, and so for mercenaries, who can protect cargoes from bandits, and so on. And because I have an architecture that allows non-player characters to fill economic niches, there will be non-player characters in all these niches. + +Where a non-player character can act, so can a player character: when a (non-player character) merchant seeks to hire a caravan guard and a player character responds, that's an organic escort quest. + +The key idea behind organic quests is that the circumstance and requirments for quests emerges as an emergent behaviour out of the mechanics of the game world. A non-player character doesn't know that there is a player character who is different from them; rather, when a non-player character needs something they can't readily achieve for themselves, they will ask other characters to help, and that may include the player character. + +This means, of course, that characters need a goal-seeking planning algorithm to decide their actions, with one option in any plan being 'ask for help'. Thus, 'asking for help' becomes a mechanism within the game, a normal behaviour. Ideally non-player characters will keep track of quite complex webs of loyalty and of obligation - debts of honour, duties of hospitality, collective loyalties. So that, if you do a favour for some character in the world, that character's tribe, friends, obligation circle, whatever, are now more likely to do favours for you. + +Obviously, this doesn't stop you doing jobs you get directly paid/rewarded for, but I'd like the web of obligation to be at least potentially much richer than just tit for tat. + +Related to this notion is the notion that, if you are asked to do a task by a character and you do it well, whether for pay or as a favour, your reputation for being competent in tasks of that kind will improve and the more likely it is that other characters will ask you to do similar tasks; and this will apply to virtually anything another character can ask of you in the game world, from carrying out an assassination to delivering a message to finding a quantiy of some specific commodity to having sex. + +So quests can emerge organically from the mechanics of the world and be richly varied; I'm confident that will work. What I'm not confident of is that they can be narratively satisfying. This relates directly to the generation of speech. From 5d37ad29eb87304c7ef6e83b65b0c22330a08a07 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 12 Apr 2020 17:13:49 +0100 Subject: [PATCH 03/12] Sweep-up of changes on illuminator --- .../merchants/merchant_utils.clj.html | 188 +++++++++++------- docs/index.html | 4 +- project.clj | 5 +- src/the_great_game/core.clj | 6 - .../merchants/merchant_utils.clj | 46 +++-- .../the_great_game/merchants/markets_test.clj | 31 ++- .../merchants/merchant_utils_test.clj | 103 ++++++++++ 7 files changed, 281 insertions(+), 102 deletions(-) delete mode 100644 src/the_great_game/core.clj diff --git a/docs/cloverage/the_great_game/merchants/merchant_utils.clj.html b/docs/cloverage/the_great_game/merchants/merchant_utils.clj.html index 70cd0f3..8774b44 100644 --- a/docs/cloverage/the_great_game/merchants/merchant_utils.clj.html +++ b/docs/cloverage/the_great_game/merchants/merchant_utils.clj.html @@ -64,13 +64,13 @@ 020    [merchant world]
- + 021    (let [m (cond
022              (keyword? merchant)
- + 023              (-> world :merchants merchant)
@@ -79,8 +79,8 @@ 025              merchant)
- - 026          cargo (:stock m)] + + 026          cargo (or (:stock m) {})]
027      (reduce @@ -94,7 +94,7 @@ 030        (map
- + 031          #(* (cargo %) (-> world :commodities % :weight))
@@ -124,7 +124,7 @@ 040              (keyword? merchant)
- + 041              (-> world :merchants merchant)
@@ -133,152 +133,194 @@ 043              merchant)]
- - 044      (quot + + 044      (max
- - 045        (- (:capacity m) (burden m world)) + + 045        0 +
+ + 046        (quot +
+ + 047          (- (or (:capacity m) 0) (burden m world))
- 046        (-> world :commodities commodity :weight)))) + 048          (-> world :commodities commodity :weight)))))
- 047   + 049  
- 048  (defn can-afford + 050  (defn can-afford
- 049    "Return the number of units of this `commodity` which this `merchant` + 051    "Return the number of units of this `commodity` which this `merchant`
- 050    can afford to buy in this `world`." + 052    can afford to buy in this `world`."
- 051    [merchant world commodity] + 053    [merchant world commodity]
- 052    (let [m (cond + 054    (let [m (cond
- 053              (keyword? merchant) + 055              (keyword? merchant)
- - 054              (-> world :merchants merchant) + + 056              (-> world :merchants merchant)
- 055              (map? merchant) + 057              (map? merchant)
- 056              merchant) + 058              merchant)
- 057          l (:location m)] + 059          l (:location m)] +
+ + 060      (cond +
+ + 061        (nil? m) +
+ + 062        (throw (Exception. "No merchant?")) +
+ + 063        (or (nil? l) (nil? (-> world :cities l))) +
+ + 064        (throw (Exception. (str "No known location for merchant " m))) +
+ + 065        :else
- 058      (quot + 066        (quot
- 059        (:cash m) + 067          (:cash m)
- 060        (-> world :cities l :prices commodity)))) + 068          (-> world :cities l :prices commodity)))))
- 061   + 069  
- 062  (defn add-stock + 070  (defn add-stock
- 063    "Where `a` and `b` are both maps all of whose values are numbers, return + 071    "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 + 072    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." + 073    are the sums of their respective values."
- 066    [a b] + 074    [a b]
- - 067    (reduce + + 075    (reduce
- - 068      merge + + 076      merge
- - 069      a + + 077      a
- - 070      (map + + 078      (map
- - 071        #(hash-map % (+ (or (a %) 0) (or (b %) 0))) + + 079        #(hash-map % (+ (or (a %) 0) (or (b %) 0)))
- - 072        (keys b)))) + + 080        (keys b))))
- 073   + 081  
- 074  (defn add-known-prices + 082  (defn add-known-prices
- 075    "Add the current prices at this `merchant`'s location in the `world` + 083    "Add the current prices at this `merchant`'s location in the `world`
- 076    to a new cacke of known prices, and return it." + 084    to a new cache of known prices, and return it."
- 077    [merchant world] + 085    [merchant world]
- 078    (let [m (cond + 086    (let [m (cond
- 079              (keyword? merchant) + 087              (keyword? merchant)
- 080              (-> world :merchants merchant) + 088              (-> world :merchants merchant)
- 081              (map? merchant) + 089              (map? merchant)
- 082              merchant) + 090              merchant) +
+ + 091          k (or (:known-prices m) {})
- 083          k (:known-prices m) + 092          l (:location m)
- - 084          l (:location m) -
- - 085          d (:date world) + + 093          d (or (:date world) 0)
- 086          p (-> world :cities l :prices)] -
- - 087      (reduce -
- - 088        merge -
- - 089        k + 094          p (-> world :cities l :prices)]
- 090        (map + 095      (cond +
+ + 096        (nil? m) +
+ + 097        (throw (Exception. "No merchant?")) +
+ + 098        (or (nil? l) (nil? (-> world :cities l))) +
+ + 099        (throw (Exception. (str "No known location for merchant " m))) +
+ + 100        :else +
+ + 101        (reduce +
+ + 102          merge +
+ + 103          k +
+ + 104          (map
- 091          #(hash-map % (apply vector cons {:price (p %) :date d} (k %))) + 105            #(hash-map % (apply vector cons {:price (p %) :date d} (k %)))
- 092          (-> world :commodities keys))))) + 106            (-> world :commodities keys))))))
diff --git a/docs/index.html b/docs/index.html index 35656b5..044b871 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,8 +6,8 @@

The Great Game: Dcocumentation

diff --git a/project.clj b/project.clj index ff35841..67a2163 100644 --- a/project.clj +++ b/project.clj @@ -4,6 +4,7 @@ :doc/format :markdown} :output-path "docs/codox" :source-uri "https://github.com/simon-brooke/the-great-game/blob/master/{filepath}#L{line}"} + :cucumber-feature-paths ["test/features/"] :dependencies [[org.clojure/clojure "1.8.0"] [environ "1.1.0"] [com.taoensso/timbre "4.10.0"]] @@ -11,7 +12,9 @@ :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-cloverage "1.1.1"] - [lein-codox "0.10.7"]] + [lein-codox "0.10.7"] + [lein-cucumber "1.0.2"] + [lein-gorilla "0.4.0"]] :release-tasks [["vcs" "assert-committed"] ["change" "version" "leiningen.release/bump-version" "release"] diff --git a/src/the_great_game/core.clj b/src/the_great_game/core.clj deleted file mode 100644 index 7a102ed..0000000 --- a/src/the_great_game/core.clj +++ /dev/null @@ -1,6 +0,0 @@ -(ns the-great-game.core) - -(defn foo - "I don't do a whole lot." - [x] - (println x "Hello, World!")) diff --git a/src/the_great_game/merchants/merchant_utils.clj b/src/the_great_game/merchants/merchant_utils.clj index b8cd969..9cfb5b4 100644 --- a/src/the_great_game/merchants/merchant_utils.clj +++ b/src/the_great_game/merchants/merchant_utils.clj @@ -23,7 +23,7 @@ (-> world :merchants merchant) (map? merchant) merchant) - cargo (:stock m)] + cargo (or (:stock m) {})] (reduce + 0 @@ -41,9 +41,11 @@ (-> world :merchants merchant) (map? merchant) merchant)] - (quot - (- (:capacity m) (burden m world)) - (-> world :commodities commodity :weight)))) + (max + 0 + (quot + (- (or (:capacity m) 0) (burden m world)) + (-> world :commodities commodity :weight))))) (defn can-afford "Return the number of units of this `commodity` which this `merchant` @@ -55,9 +57,15 @@ (map? merchant) merchant) l (:location m)] - (quot - (:cash m) - (-> world :cities l :prices commodity)))) + (cond + (nil? m) + (throw (Exception. "No merchant?")) + (or (nil? l) (nil? (-> world :cities l))) + (throw (Exception. (str "No known location for merchant " m))) + :else + (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 @@ -73,20 +81,26 @@ (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." + to a new cache of known prices, and return it." [merchant world] (let [m (cond (keyword? merchant) (-> world :merchants merchant) (map? merchant) merchant) - k (:known-prices m) + k (or (:known-prices m) {}) l (:location m) - d (:date world) + d (or (:date world) 0) p (-> world :cities l :prices)] - (reduce - merge - k - (map - #(hash-map % (apply vector cons {:price (p %) :date d} (k %))) - (-> world :commodities keys))))) + (cond + (nil? m) + (throw (Exception. "No merchant?")) + (or (nil? l) (nil? (-> world :cities l))) + (throw (Exception. (str "No known location for merchant " m))) + :else + (reduce + merge + k + (map + #(hash-map % (apply vector cons {:price (p %) :date d} (k %))) + (-> world :commodities keys)))))) diff --git a/test/the_great_game/merchants/markets_test.clj b/test/the_great_game/merchants/markets_test.clj index bc3f515..7cacd30 100644 --- a/test/the_great_game/merchants/markets_test.clj +++ b/test/the_great_game/merchants/markets_test.clj @@ -79,8 +79,31 @@ (-> actual :cities :falkirk :stock :iron) 17) "Stock should be topped up by the difference between the supply and - the demand amount.")) + the demand amount.")))) - ) - - ) +(deftest run-test + (let [world (deep-merge + default-world + {:cities + {:aberdeen + {:stock {:fish 5} + :supplies {:fish 12} + :prices {:fish 1.1}} + :falkirk + {:stock {:iron 10} + :demands {:iron 5} + :supplies {:iron 12} + :prices {:iron 1.1}}}}) + actual (run world)] + (is + (= + (-> actual :cities :aberdeen :stock :fish) + (+ (-> world :cities :aberdeen :supplies :fish) + (-> world :cities :aberdeen :stock :fish))) + "If stock is not empty and price is above cost, stock should be topped up by supply amount.") + (is + (= + (-> actual :cities :falkirk :stock :iron) + 17) + "Stock should be topped up by the difference between the supply and + the demand amount."))) diff --git a/test/the_great_game/merchants/merchant_utils_test.clj b/test/the_great_game/merchants/merchant_utils_test.clj index 2f9071e..086c691 100644 --- a/test/the_great_game/merchants/merchant_utils_test.clj +++ b/test/the_great_game/merchants/merchant_utils_test.clj @@ -22,3 +22,106 @@ expected 1.7] ;; (is (= actual expected) "if information select the most recent"))))) +(deftest burden-test + (testing "Burden of merchant" + (let [world (deep-merge + default-world + {:merchants + {:archie + {:stock + {:iron 1}} + :belinda + {:stock + {:fish 2}} + :callum + {:stock + {:iron 1 + :fish 1}}}})] + (let [actual (burden :archie world) + expected (-> world :commodities :iron :weight)] + (is (= actual expected))) + (let [actual (burden :belinda world) + expected (* 2 (-> world :commodities :fish :weight))] + (is (= actual expected))) + (let [actual (burden :callum world) + expected (+ + (-> world :commodities :iron :weight) + (-> world :commodities :fish :weight))] + (is (= actual expected))) + (let [actual (burden {} world) + expected 0] + (is (= actual expected))) + (let [actual (burden (-> world :merchants :deidre) world) + expected 0] + (is (= actual expected)))))) + +(deftest can-carry-test + (testing "What merchants can carry" + (let [world (deep-merge + default-world + {:merchants + {:archie + {:cash 5 + :stock + {:iron 1}} + :belinda + {:stock + {:fish 2}} + :callum + {:stock + {:iron 1 + :fish 1}}}})] + (let [actual (can-carry :archie world :fish) + expected 0] + (is (= actual expected))) + (let [actual (can-carry :belinda world :fish) + expected 8] + (is (= actual expected))) + (let [actual (can-carry (-> world :merchants :archie) world :fish) + expected 0] + (is (= actual expected))) + (let [actual (can-carry {:stock {:fish 7} :capacity 10} world :fish) + expected 3] + (is (= actual expected)))))) + + +(deftest affordability-test + (testing "What merchants can afford to purchase" + (let [world (deep-merge + default-world + {:merchants + {:archie + {:cash 5 + :stock + {:iron 1}} + :belinda + {:stock + {:fish 2}} + :callum + {:stock + {:iron 1 + :fish 1}}}})] + (let [actual (can-afford :archie world :fish) + expected 5] + (is (= actual expected))) + (let [actual (can-afford :belinda world :fish) + expected 100] + (is (= actual expected))) + (let [actual (can-afford (-> world :merchants :archie) world :fish) + expected 5] + (is (= actual expected))) + (let [actual (can-afford {:cash 3 :location :buckie} world :fish) + expected 3] + (is (= actual expected))) + (is (thrown-with-msg? + Exception + #"No merchant?" + (can-afford :no-one world :fish))) + (is (thrown-with-msg? + Exception + #"No known location for merchant.*" + (can-afford {:cash 3} world :fish)))))) + +(deftest add-stock-test + (let [actual (add-stock {:iron 2 :fish 5} {:fish 3 :whisky 7}) + expected {:iron 2 :fish 8 :whisky 7}])) From 7e7a55c8ec9734d9a2e9094f4734396b7e5fb066 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 14 Apr 2020 08:30:41 +0100 Subject: [PATCH 04/12] Today is the Fifth Plough of the Plough Implemented almost the whole of the Myth of the God Incarnate calendar --- .gitignore | 2 - docs/cloverage/index.html | 127 ++- .../the_great_game/agent/agent.clj.html | 29 + .../the_great_game/gossip/gossip.clj.html | 4 +- .../the_great_game/gossip/news_items.clj.html | 740 ++++++++++++++++++ .../the_great_game/merchants/markets.clj.html | 34 +- docs/cloverage/the_great_game/time.clj.html | 440 +++++++++++ .../the_great_game/world/location.clj.html | 119 +++ docs/codox/economy.html | 2 +- docs/codox/index.html | 2 +- docs/codox/intro.html | 2 +- .../modelling_trading_cost_and_risk.html | 2 +- docs/codox/naming-of-characters.html | 26 + docs/codox/orgnic-quests.html | 34 + docs/codox/sandbox.html | 39 + docs/codox/sexual-dimorphism.html | 2 +- docs/codox/the-great-game.agent.agent.html | 3 + docs/codox/the-great-game.gossip.gossip.html | 2 +- .../the-great-game.gossip.news-items.html | 18 + .../the-great-game.merchants.markets.html | 2 +- ...e-great-game.merchants.merchant-utils.html | 2 +- .../the-great-game.merchants.merchants.html | 2 +- .../the-great-game.merchants.planning.html | 2 +- ...reat-game.merchants.strategies.simple.html | 2 +- docs/codox/the-great-game.time.html | 3 + docs/codox/the-great-game.utils.html | 2 +- docs/codox/the-great-game.world.location.html | 3 + 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 +- src/the_great_game/agent/agent.clj | 3 + src/the_great_game/gossip/gossip.clj | 3 +- src/the_great_game/gossip/news_items.clj | 171 ++-- src/the_great_game/time.clj | 144 ++++ src/the_great_game/world/location.clj | 29 +- test/the_great_game/gossip/gossip_test.clj | 4 + .../the_great_game/gossip/news_items_test.clj | 132 ++++ test/the_great_game/time_test.clj | 79 ++ test/the_great_game/world/location_test.clj | 36 + 39 files changed, 2085 insertions(+), 167 deletions(-) create mode 100644 docs/cloverage/the_great_game/agent/agent.clj.html create mode 100644 docs/cloverage/the_great_game/gossip/news_items.clj.html create mode 100644 docs/cloverage/the_great_game/time.clj.html create mode 100644 docs/cloverage/the_great_game/world/location.clj.html create mode 100644 docs/codox/naming-of-characters.html create mode 100644 docs/codox/orgnic-quests.html create mode 100644 docs/codox/sandbox.html create mode 100644 docs/codox/the-great-game.agent.agent.html create mode 100644 docs/codox/the-great-game.gossip.news-items.html create mode 100644 docs/codox/the-great-game.time.html create mode 100644 docs/codox/the-great-game.world.location.html create mode 100644 src/the_great_game/time.clj create mode 100644 test/the_great_game/gossip/gossip_test.clj create mode 100644 test/the_great_game/gossip/news_items_test.clj create mode 100644 test/the_great_game/time_test.clj create mode 100644 test/the_great_game/world/location_test.clj diff --git a/.gitignore b/.gitignore index 7387005..0910231 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,3 @@ pom.xml.asc .nrepl-port .cpcache/ *~ - -docs/cloverage/ diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index eba4573..4590c73 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -15,19 +15,15 @@ TotalBlankInstrumented - the-great-game.core
2
4
-33.33 % -
2
the-great-game.agent.agent
1
-66.67 % -613 +100.00 % +
1
+100.00 % +721 the-great-game.gossip.gossip
65539 - the-great-game.merchants.markets
160
53
-75.12 % + the-great-game.gossip.news-items
461
55
+89.34 %
25
5
14
-68.18 % + style="width:83.65384615384616%; + float:left;"> 87
9
8
+92.31 % +24429104 + + + the-great-game.merchants.markets
206
7
+96.71 % +
41
2
1
+97.73 % 84844 the-great-game.merchants.merchant-utils
104
122
-46.02 % + style="width:66.12377850162866%; + float:left;"> 203
104
+66.12 %
33
3
25
-59.02 % -92761 + style="width:66.66666666666667%; + float:left;"> 48
4
20
+72.22 % +106772 the-great-game.merchants.merchants
4.03 % 1736124 + + the-great-game.time
259
5
+98.11 % +
58
1
1
+98.33 % +1442160 + the-great-game.utils
100.00 % 35319 + + the-great-game.world.location
76
10
+88.37 % +
12
3
2
+88.24 % +37417 + the-great-game.world.routes
-51.99 % +66.14 % -54.62 % +67.89 % diff --git a/docs/cloverage/the_great_game/agent/agent.clj.html b/docs/cloverage/the_great_game/agent/agent.clj.html new file mode 100644 index 0000000..bbdf18f --- /dev/null +++ b/docs/cloverage/the_great_game/agent/agent.clj.html @@ -0,0 +1,29 @@ + + + + the_great_game/agent/agent.clj + + + + 001  (ns the-great-game.agent.agent +
+ + 002    "Anything in the game world with agency") +
+ + 003   +
+ + 004  ;;  hierarchy of needs probably gets implemented here +
+ + 005  ;;  I'm probably going to want to defprotocol stuff, to define the hierarchy +
+ + 006  ;;  of things in the gameworld; either that or drop to Java, wich I'd rather not do. +
+ + 007   +
+ + diff --git a/docs/cloverage/the_great_game/gossip/gossip.clj.html b/docs/cloverage/the_great_game/gossip/gossip.clj.html index df4c108..069225a 100644 --- a/docs/cloverage/the_great_game/gossip/gossip.clj.html +++ b/docs/cloverage/the_great_game/gossip/gossip.clj.html @@ -8,7 +8,7 @@ 001  (ns the-great-game.gossip.gossip
- 002    "Interchange of news events between agents agents" + 002    "Interchange of news events between gossip agents"
003    (:require [the-great-game.utils :refer [deep-merge]])) @@ -140,7 +140,7 @@ 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 + 046    other types, and the movement of the gossip should be controlled by the
047    run function of the type of the record they shadow. The [[#run]] function diff --git a/docs/cloverage/the_great_game/gossip/news_items.clj.html b/docs/cloverage/the_great_game/gossip/news_items.clj.html new file mode 100644 index 0000000..0b410d6 --- /dev/null +++ b/docs/cloverage/the_great_game/gossip/news_items.clj.html @@ -0,0 +1,740 @@ + + + + the_great_game/gossip/news_items.clj + + + + 001  (ns the-great-game.gossip.news-items +
+ + 002    "Categories of news events interesting to gossip agents" +
+ + 003    (:require [the-great-game.world.location :refer [distance-between]] +
+ + 004              [the-great-game.time :refer [now]])) +
+ + 005   +
+ + 006  ;; The ideas here are based on the essay 'The spread of knowledge in a large +
+ + 007  ;; game world', q.v.; they've advanced a little beyond that and will doubtless +
+ + 008  ;; advance further in the course of writing and debugging this namespace. +
+ + 009   +
+ + 010  ;; A news item is a map with the keys: +
+ + 011  ;; +
+ + 012  ;; * `date` - the date on which the reported event happened; +
+ + 013  ;; * `nth-hand` - the number of agents the news item has passed through; +
+ + 014  ;; * `verb` - what it is that happened (key into `news-topics`); +
+ + 015  ;; +
+ + 016  ;; plus other keys taken from the `keys` value associated with the verb in +
+ + 017  ;; `news-topics` +
+ + 018   +
+ + 019  (def news-topics +
+ + 020    "Topics of interest to gossip agents. Topics are keyed in this map by +
+ + 021    their `verbs`. The `keys` associated with each topic are the extra pieces +
+ + 022    of information required to give context to a gossip item. Generally: +
+ + 023   +
+ + 024    * `actor` is the id of the character who it is reported performed the +
+ + 025    action; +
+ + 026    * `other` is the id of the character on whom it is reported the action +
+ + 027    was performed; +
+ + 028    * `location` is the place at which the action was performed; +
+ + 029    * `object` is an object (or possibly list of objects?) relevant to the +
+ + 030    action; +
+ + 031    * `price` is special to buy/sell, but of significant interest to merchants. +
+ + 032   +
+ + 033    #### Notes: +
+ + 034   +
+ + 035    ##### Characters: +
+ + 036   +
+ + 037    *TODO* but note that at most all the receiver can learn about a character +
+ + 038    from a news item is what the giver knows about that character, degraded by +
+ + 039    what the receiver finds interesting about them. If we just pass the id here, +
+ + 040    then either the receiver knows everything in the database about the +
+ + 041    character, or else the receiver knows nothing at all about the character. +
+ + 042    Neither is desirable. Further thought needed. +
+ + 043   +
+ + 044    ##### Locations: +
+ + 045   +
+ + 046    A 'location' value is a list comprising at most the x/y coordinate location +
+ + 047    and the ids of the settlement and region (possibly hierarchically) that contain +
+ + 048    the location. If the x/y is not local to the home of the receiving agent, they +
+ + 049    won't remember it and won't pass it on; if any of the ids are not interesting +
+ + 050    So location information will degrade progressively as the item is passed along. +
+ + 051   +
+ + 052    It is assumed that the `:home` of a character is a location in this sense. +
+ + 053   +
+ + 054    ##### Inferences: +
+ + 055   +
+ + 056    If an agent learns that Adam has married Betty, they can infer that Betty has +
+ + 057    married Adam; if they learn that Charles killed Dorothy, that Dorothy has died. +
+ + 058    I'm not convinced that my representation of inferences here is ideal. +
+ + 059    " +
+ + 060    { ;; A significant attack is interesting whether or not it leads to deaths +
+ + 061      :attack {:verb :attack :keys [:actor :other :location]} +
+ + 062      ;; Deaths of characters may be interesting +
+ + 063      :die {:verb :attack :keys [:actor :location]} +
+ + 064      ;; Deliberate killings are interesting. +
+ + 065      :kill {:verb :kill :keys [:actor :other :location] +
+ + 066             :inferences [{:verb :die :actor :other :other :nil}]} +
+ + 067      ;; Marriages may be interesting +
+ + 068      :marry {:verb :marry :keys [:actor :other :location] +
+ + 069              :inferences [{:verb :marry :actor :other :other :actor}]} +
+ + 070      ;; The end of ongoing open conflict between to characters may be interesting +
+ + 071      :peace {:verb :peace :keys [:actor :other :location] +
+ + 072              :inferences [{:verb :peace :actor :other :other :actor}]} +
+ + 073      ;; Things related to the plot are interesting, but will require special +
+ + 074      ;; handling. Extra keys may be required by particular plot events. +
+ + 075      :plot {:verb :plot :keys [:actor :other :object :location]} +
+ + 076      ;; Rapes are interesting. +
+ + 077      :rape {:verb :rape :keys [:actor :other :location] +
+ + 078             ;; Should you also infer from rape that actor is male and adult? +
+ + 079             :inferences [{:verb :attack} +
+ + 080                          {:verb :sex} +
+ + 081                          {:verb :sex :actor :other :other :actor}]} +
+ + 082      ;; Merchants, especially, are interested in prices in other markets +
+ + 083      :sell {:verb :sell :keys [:actor :other :object :location :price]} +
+ + 084      ;; Sex can juicy gossip, although not normally if the participants are in an +
+ + 085      ;; established sexual relationship. +
+ + 086      :sex {:verb :sex :keys [:actor :other :location] +
+ + 087            :inferences [{:verb :sex :actor :other :other :actor}]} +
+ + 088      ;; Thefts are interesting +
+ + 089      :steal {:verb :steal :keys [:actor :other :object :location]} +
+ + 090      ;; The succession of rulers is interesting; of respected craftsmen, +
+ + 091      ;; potentially also interesting. +
+ + 092      :succession {:verb :succession :keys [:actor :other :location :rank]} +
+ + 093      ;; The start of ongoing open conflict between to characters may be interesting +
+ + 094      :war {:verb :war :keys [:actor :other :location] +
+ + 095            :inferences [{:verb :war :actor :other :other :actor}]} +
+ + 096      }) +
+ + 097   +
+ + 098   +
+ + 099  (defn interest-in-character +
+ + 100    "Integer representation of how interesting this `character` is to this +
+ + 101    `gossip`. +
+ + 102    *TODO:* this assumes that characters are passed as keywords, but, as +
+ + 103    documented above, they probably have to be maps, to allow for degradation." +
+ + 104    [gossip character] +
+ + 105    (count +
+ + 106      (concat +
+ + 107        (filter #(= (:actor % character)) (:knowledge gossip)) +
+ + 108        (filter #(= (:other % character)) (:knowledge gossip))))) +
+ + 109   +
+ + 110  (defn interesting-character? +
+ + 111    "Boolean representation of whether this `character` is interesting to this +
+ + 112    `gossip`." +
+ + 113    [gossip character] +
+ + 114    (> (interest-in-character gossip character) 0)) +
+ + 115   +
+ + 116  (defn interest-in-location +
+ + 117    "Integer representation of how interesting this `location` is to this +
+ + 118    `gossip`." +
+ + 119    [gossip location] +
+ + 120    (cond +
+ + 121      (and (map? location) (number? (:x location)) (number? (:y location))) +
+ + 122      (if-let [home (:home gossip)] +
+ + 123        (let [d (distance-between location home) +
+ + 124              i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should +
+ + 125              ;;fall of with distance from home, but possibly on a log scale +
+ + 126              ] +
+ + 127          (if (> i 1) i 0)) +
+ + 128        0) +
+ + 129      (coll? location) +
+ + 130      (reduce +
+ + 131        + +
+ + 132        (map +
+ + 133          #(interest-in-location gossip %) +
+ + 134          location)) +
+ + 135      :else +
+ + 136      (count +
+ + 137        (filter +
+ + 138          #(some (fn [x] (= x location)) (:location %)) +
+ + 139          (:knowledge gossip))))) +
+ + 140   +
+ + 141  (defn interesting-location? +
+ + 142    "True if the location of this news `item` is interesting to this `gossip`." +
+ + 143    [gossip item] +
+ + 144    (> (interest-in-location gossip (:location item)) 1)) +
+ + 145   +
+ + 146  (defn interesting-object? +
+ + 147    [gossip object] +
+ + 148    ;; TODO: Not yet (really) implemented +
+ + 149    true) +
+ + 150   +
+ + 151  (defn interesting-topic? +
+ + 152    [gossip topic] +
+ + 153    ;; TODO: Not yet (really) implemented +
+ + 154    true) +
+ + 155   +
+ + 156  (defn interesting-item? +
+ + 157    "True if anything about this news `item` is interesting to this `gossip`." +
+ + 158    [gossip item] +
+ + 159       (or +
+ + 160         (interesting-character? gossip (:actor item)) +
+ + 161         (interesting-character? gossip (:other item)) +
+ + 162         (interesting-location? gossip (:location item)) +
+ + 163         (interesting-object? gossip (:object item)) +
+ + 164         (interesting-topic? gossip (:verb item)))) +
+ + 165   +
+ + 166  (defn infer +
+ + 167    "Infer a new knowledge item from this `item`, following this `rule`" +
+ + 168    [item rule] +
+ + 169    (reduce merge +
+ + 170            item +
+ + 171            (cons +
+ + 172              {:verb (:verb rule)} +
+ + 173              (map (fn [k] {k (apply (k rule) (list item))}) +
+ + 174                   (remove +
+ + 175                     #(= % :verb) +
+ + 176                     (keys rule)))))) +
+ + 177   +
+ + 178  (declare learn-news-item) +
+ + 179   +
+ + 180  (defn make-all-inferences +
+ + 181    "Return a list of knowledge entries inferred from this news `item` by this +
+ + 182    `gossip`." +
+ + 183    [item] +
+ + 184    (set +
+ + 185      (reduce +
+ + 186        concat +
+ + 187        (map +
+ + 188          #(:knowledge (learn-news-item {} (infer item %) false)) +
+ + 189          (:inferences (news-topics (:verb item))))))) +
+ + 190   +
+ + 191  (defn degrade-character +
+ + 192    "Return a character specification like this `character`, but comprising +
+ + 193    only those properties this `gossip` is interested in." +
+ + 194    [gossip character] +
+ + 195    ;; TODO: Not yet (really) implemented +
+ + 196    character) +
+ + 197   +
+ + 198  (defn degrade-location +
+ + 199    "Return a location specification like this `location`, but comprising +
+ + 200    only those elements this `gossip` is interested in. If none, return +
+ + 201    `nil`." +
+ + 202    [gossip location] +
+ + 203    (let [l (if +
+ + 204      (coll? location) +
+ + 205      (filter +
+ + 206        #(when (interesting-location? gossip %) %) +
+ + 207        location))] +
+ + 208      (when-not (empty? l) l))) +
+ + 209   +
+ + 210  (defn learn-news-item +
+ + 211    "Return a gossip like this `gossip`, which has learned this news `item` if +
+ + 212    it is of interest to them." +
+ + 213    ;; TODO: Not yet implemented +
+ + 214    ([gossip item] +
+ + 215     (learn-news-item gossip item true)) +
+ + 216    ([gossip item follow-inferences?] +
+ + 217     (if +
+ + 218       (interesting-item? gossip item) +
+ + 219       (let [g (assoc gossip :knowledge +
+ + 220                 (cons +
+ + 221                   (assoc +
+ + 222                     item +
+ + 223                     :nth-hand (if +
+ + 224                                 (number? (:nth-hand item)) +
+ + 225                                 (inc (:nth-hand item)) +
+ + 226                                 1) +
+ + 227                     :date (if (number? (:date item)) (:date item) (now)) +
+ + 228                     :location (degrade-location gossip (:location item)) +
+ + 229                     ;; ought to degratde the location +
+ + 230                     ;; ought to maybe-degrade characters we're not yet interested in +
+ + 231                     ) +
+ + 232                   ;; ought not to add knowledge items we already have, except +
+ + 233                   ;; to replace if new item is of increased specificity +
+ + 234                   (:knowledge gossip)))] +
+ + 235         (if follow-inferences? +
+ + 236           (assoc +
+ + 237             g +
+ + 238             :knowledge +
+ + 239             (concat (:knowledge g) (make-all-inferences item))) +
+ + 240           g)) +
+ + 241       gossip))) +
+ + 242   +
+ + 243   +
+ + 244   +
+ + diff --git a/docs/cloverage/the_great_game/merchants/markets.clj.html b/docs/cloverage/the_great_game/merchants/markets.clj.html index 618f25f..46dc44a 100644 --- a/docs/cloverage/the_great_game/merchants/markets.clj.html +++ b/docs/cloverage/the_great_game/merchants/markets.clj.html @@ -79,22 +79,22 @@ 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
@@ -190,46 +190,46 @@ 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)))
@@ -250,7 +250,7 @@ 082    [world]
- + 083    (update-markets world))
diff --git a/docs/cloverage/the_great_game/time.clj.html b/docs/cloverage/the_great_game/time.clj.html new file mode 100644 index 0000000..8822abe --- /dev/null +++ b/docs/cloverage/the_great_game/time.clj.html @@ -0,0 +1,440 @@ + + + + the_great_game/time.clj + + + + 001  (ns the-great-game.time +
+ + 002    (:require [clojure.string :as s])) +
+ + 003   +
+ + 004  (def game-start-time +
+ + 005    "The start time of this run." +
+ + 006    (System/currentTimeMillis)) +
+ + 007   +
+ + 008  (def ^:const game-day-length +
+ + 009    "The Java clock advances in milliseconds, which is fine. +
+ + 010    But we need game-days to be shorter than real world days. +
+ + 011    A Witcher 3 game day is 1 hour 36 minutes, or 96 minutes, which is +
+ + 012    presumably researched. Round it up to 100 minutes for easier +
+ + 013    calculation." +
+ + 014    (* 100          ;; minutes per game day +
+ + 015       60           ;; seconds per minute +
+ + 016       1000))       ;; milliseconds per second +
+ + 017   +
+ + 018  (defn now +
+ + 019    "For now, we'll use Java timestamp for time; ultimately, we need a +
+ + 020    concept of game-time which allows us to drive day/night cycle, seasons, +
+ + 021    et cetera, but what matters about time is that it is a value which +
+ + 022    increases." +
+ + 023    [] +
+ + 024    (System/currentTimeMillis)) +
+ + 025   +
+ + 026  (def ^:const canonical-ordering-of-houses +
+ + 027    "The canonical ordering of religious houses." +
+ + 028    [:eye +
+ + 029     :foot +
+ + 030     :nose +
+ + 031     :hand +
+ + 032     :ear +
+ + 033     :mouth +
+ + 034     :stomach +
+ + 035     :furrow +
+ + 036     :plough]) +
+ + 037   +
+ + 038  (def ^:const days-of-week +
+ + 039    "The eight-day week of the game world. This differs from the canonical +
+ + 040    ordering of houses in that it omits the eye." +
+ + 041    (rest canonical-ordering-of-houses)) +
+ + 042   +
+ + 043  (def ^:const days-in-week +
+ + 044    "This world has an eight day week." +
+ + 045    (count days-of-week)) +
+ + 046   +
+ + 047  (def ^:const seasons-of-year +
+ + 048    "The ordering of seasons in the year is different from the canonical +
+ + 049    ordering of the houses, for reasons of the agricultural cycle." +
+ + 050    [:foot +
+ + 051     :nose +
+ + 052     :hand +
+ + 053     :ear +
+ + 054     :mouth +
+ + 055     :stomach +
+ + 056     :plough +
+ + 057     :furrow +
+ + 058     :eye]) +
+ + 059   +
+ + 060  (def ^:const seasons-in-year +
+ + 061    "Nine seasons in a year, one for each house (although the order is +
+ + 062    different." +
+ + 063    (count seasons-of-year)) +
+ + 064   +
+ + 065  (def ^:const weeks-of-season +
+ + 066    "To fit nine seasons of eight day weeks into 365 days, each must be of +
+ + 067    five weeks." +
+ + 068    [:first :second :third :fourth :fifth]) +
+ + 069   +
+ + 070  (def ^:const weeks-in-season +
+ + 071    "To fit nine seasons of eight day weeks into 365 days, each must be of +
+ + 072    five weeks." +
+ + 073    (count weeks-of-season)) +
+ + 074   +
+ + 075  (def ^:const days-in-season +
+ + 076    (* weeks-in-season days-in-week)) +
+ + 077   +
+ + 078  (defn game-time +
+ + 079    "With no arguments, the current game time. If a Java `timestamp` value is +
+ + 080    passed (as a `long`), the game time represented by that value." +
+ + 081    ([] (game-time (now))) +
+ + 082    ([timestamp] +
+ + 083     (- timestamp game-start-time))) +
+ + 084   +
+ + 085  (defmacro day-of-year +
+ + 086    "The day of the year represented by this `game-time`, ignoring leap years." +
+ + 087    [game-time] +
+ + 088    `(mod (long (/ ~game-time game-day-length)) 365)) +
+ + 089   +
+ + 090  (def waiting-day? +
+ + 091    "Does this `game-time` represent a waiting day?" +
+ + 092    (memoize +
+ + 093      ;; we're likely to call this several times in quick succession on the +
+ + 094      ;; same timestamp +
+ + 095      (fn [game-time] +
+ + 096          (>= +
+ + 097            (day-of-year game-time) +
+ + 098            (* seasons-in-year weeks-in-season days-in-week))))) +
+ + 099   +
+ + 100  (defn day +
+ + 101    "Day of the eight-day week represented by this `game-time`." +
+ + 102    [game-time] +
+ + 103    (let [day-of-week (mod (day-of-year game-time) days-in-week)] +
+ + 104      (if (waiting-day? game-time) +
+ + 105        (nth weeks-of-season day-of-week) +
+ + 106        (nth days-of-week day-of-week)))) +
+ + 107   +
+ + 108  (defn week +
+ + 109    "Week of season represented by this `game-time`." +
+ + 110    [game-time] +
+ + 111    (let [day-of-season (mod (day-of-year game-time) days-in-season) +
+ + 112          week (/ day-of-season days-in-week)] +
+ + 113      (if (waiting-day? game-time) +
+ + 114        :waiting +
+ + 115        (nth weeks-of-season week)))) +
+ + 116   +
+ + 117  (defn season +
+ + 118    [game-time] +
+ + 119    (let [season (int (/ (day-of-year game-time) days-in-season))] +
+ + 120      (if (waiting-day? game-time) +
+ + 121        :waiting +
+ + 122        (nth seasons-of-year season)))) +
+ + 123   +
+ + 124  (defn date-string +
+ + 125    "Return a correctly formatted date for this `game-time` in the calendar of +
+ + 126    the Great Place." +
+ + 127    [game-time] +
+ + 128    (s/join +
+ + 129      " " +
+ + 130      (if +
+ + 131        (waiting-day? game-time) +
+ + 132        [(s/capitalize +
+ + 133           (name +
+ + 134             (nth +
+ + 135               weeks-of-season +
+ + 136               (mod (day-of-year game-time) days-in-week)))) +
+ + 137         "waiting day"] +
+ + 138        [(s/capitalize (name (week game-time))) +
+ + 139         (s/capitalize (name (day game-time))) +
+ + 140         "of the" +
+ + 141         (s/capitalize (name (season game-time)))]))) +
+ + 142   +
+ + 143   +
+ + 144   +
+ + diff --git a/docs/cloverage/the_great_game/world/location.clj.html b/docs/cloverage/the_great_game/world/location.clj.html new file mode 100644 index 0000000..195df85 --- /dev/null +++ b/docs/cloverage/the_great_game/world/location.clj.html @@ -0,0 +1,119 @@ + + + + the_great_game/world/location.clj + + + + 001  (ns the-great-game.world.location +
+ + 002    "Functions dealing with location in the world." +
+ + 003    (:require [clojure.math.numeric-tower :refer [expt sqrt]])) +
+ + 004   +
+ + 005  ;;   A 'location' value is a list comprising at most the x/y coordinate location +
+ + 006  ;;   and the ids of the settlement and region (possibly hierarchically) that contain +
+ + 007  ;;   the location. If the x/y is not local to the home of the receiving agent, they +
+ + 008  ;;   won't remember it and won't pass it on; if any of the ids are not interesting +
+ + 009  ;;   So location information will degrade progressively as the item is passed along. +
+ + 010   +
+ + 011  ;;   It is assumed that the `:home` of a character is a location in this sense. +
+ + 012   +
+ + 013  (defn get-coords +
+ + 014    "Return the coordinates in the game world of `location`, which may be +
+ + 015    1. A coordinate pair in the format {:x 5 :y 32}; +
+ + 016    2. A location, as discussed above; +
+ + 017    3. Any other gameworld object, having a `:location` property whose value +
+ + 018      is one of the above." +
+ + 019    [location] +
+ + 020    (cond +
+ + 021      (empty? location) nil +
+ + 022      (map? location) +
+ + 023      (cond +
+ + 024        (and (number? (:x location)) (number? (:y location))) +
+ + 025        location +
+ + 026        (:location location) +
+ + 027        (:location location)) +
+ + 028      :else +
+ + 029      (get-coords (first (remove keyword? location))))) +
+ + 030   +
+ + 031  (defn distance-between +
+ + 032    [location-1 location-2] +
+ + 033    (let [c1 (get-coords location-1) +
+ + 034          c2 (get-coords location-2)] +
+ + 035      (when +
+ + 036        (and c1 c2) +
+ + 037        (sqrt (+ (expt (- (:x c1) (:x c2)) 2) (expt (- (:y c1) (:y c2)) 2)))))) +
+ + diff --git a/docs/codox/economy.html b/docs/codox/economy.html index 8d85673..a232ad2 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 0a34e6b..375d513 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -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 +The-great-game 0.1.1-SNAPSHOT

The-great-game 0.1.1-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.1-SNAPSHOT"]

Topics

Namespaces

the-great-game.agent.agent

Anything in the game world with agency

Public variables and functions:

    the-great-game.gossip.gossip

    Interchange of news events between gossip 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.location

    Functions dealing with location in the world.

    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 32cce64..b17bd13 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 88dd04c..72b183e 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/naming-of-characters.html b/docs/codox/naming-of-characters.html new file mode 100644 index 0000000..6d76b98 --- /dev/null +++ b/docs/codox/naming-of-characters.html @@ -0,0 +1,26 @@ + +Naming of Characters

    Naming of Characters

    +

    Generally speaking, in modern RPGs, every character with any impact on the plot has a distinct name. But if we are going to give all non-player characters sufficient agency to impact on the plot, then we must have a way of naming tens or hundreds of thousands of characters, and distinct names will become problematic (even if we’re procedurally generating names, which we shall have to do. So this note is about how characters are named.

    +

    The full name of each character will be made up as follows:

    +

    [epithet] [clan] [personal-name] the [trade-or-rank] of [location], son/daughter of [parent]

    +

    Based on, roughly, historical name patterns like

    +

    Archibald (personal-name) the Grim (epithet), Earl (trade-or-rank) of Douglas (location)

    +

    Where

    +
      +
    1. +

      epithet is a prefix based on some notable feature or feat of the character. Most characters won’t have an epithet, unless they have some notable feature or they’ve done something notable. If a character does something notable in the course of the game, they will subsequently gain an epithet; ‘notability’ may be measured by how many times the event is transmitted through the gossip network.

    2. +
    3. +

      clan is special to the Western Clans, although people from the Great Place may possible use the name of their house similarly.

    4. +
    5. +

      personal-name is chosen from one of a limited set of limited sets; different cultural groups will have different (possibly overlapping) sets of names, but within each set there will only be a limited subset

    6. +
    7. +

      trade-or-rank is just that. “Smith”, “Miller”, “Ariston”, “Captain”. Either only master craftsfolk have the trade-or-rank name of their craft, or we distinguish between ‘Calon the Smith’, who may be a journeyman, and ‘Calon the Master Smith’, who is a master.

    8. +
    9. +

      location is the name of a location; a village, town, city or province. The location which forms part of a character’s name is the location where there current home is, not the location where they were born or where their ancestors came from

    10. +
    +

    Full names will almost never be used - only, perhaps, in extremely formal circumstances. The form of a name used will depend on context, and will generally be just sufficient to disambiguate the character in the context.

    +

    If the speaker is in Sinhua and referring to someone from Sinhua, they won’t refer to them as ‘of Sinhua’.

    +

    If everyone present is a bargee and the speaker referring to someone who is also a bargee, they won’t refer to them as ‘the bargee’.

    +

    The question asked influences the context: in answer to the question ‘who is the best sword smith’, the answer will not be ‘Calon the Smith’ but ‘Calon of Sinhua’.

    +

    Patronymics/matronymics will not normally be used of adults (although they may be used of apprentices and journeymen.

    \ No newline at end of file diff --git a/docs/codox/orgnic-quests.html b/docs/codox/orgnic-quests.html new file mode 100644 index 0000000..ca6e4a8 --- /dev/null +++ b/docs/codox/orgnic-quests.html @@ -0,0 +1,34 @@ + +Organic Quests

    Organic Quests

    +

    The structure of a modern Role Playing Came revolves around ‘quests’: tasks that the player character is invited to do, either by the framing narrative of the game or by some non-player character (‘the Quest Giver’). Normally there is one core quest which provides the overarching narrative for the whole game. [Wikipedia](https://en.wikipedia.org/wiki/Quest_(gaming)) offers a typology of quests as follows:

    +
      +
    1. Kill quests
    2. +
    3. Combo quests
    4. +
    5. Delivery quests
    6. +
    7. Gather quests
    8. +
    9. Escort quests
    10. +
    11. Syntax quests
    12. +
    13. Hybrids
    14. +
    +

    ‘Gather quests’ are more frequently referred to in the literature as ‘fetch quests’, and ‘kill quests’ are simply a specialised form of fetch quest where the item to be fetched is a trophy of the kill. A delivery quest is a sort of reverse fetch quest: instead of going to some location or NPC and getting a specific item to return to the quest giver, the player is tasked to take a specific item from the quest giver to some location or NPC.

    +

    Hybrids are in effect chains of quests: do this task in order to get this precondition of this other task, in order to get the overall objective; obviously such chains can be deep and involved - the ‘main quest’ of every role playing game I know of is a chain or hybrid quest.

    +

    My understanding is that what Wikipedia means by a ‘syntax quest’ is what one would normally call a puzzle.

    +

    An escort quest is typically a request to take a specified non-player character safely through a dangerous area.

    +

    Combo quests are not, in my opinion, particularly relevant to the sorts of game we’re discussing here.

    +

    So essentially quests break down into three core types

    +
      +
    1. Fetch and deliver quests
    2. +
    3. Escort quests
    4. +
    5. Puzzles
    6. +
    +

    which are combined together into more or less complex chains, where the simplest chain is a single quest.

    +

    Given that quests are as simple as this, it’s obvious that narrative sophistication is required to make them interesting; and this point is clearly made by some variants of roguelike games which procedurally generate quests: they’re generally pretty dull. By contrast, the Witcher series is full of fetch-quests which are made to really matter by being wrapped in interesting character interaction and narrative plausibility. Very often this takes the form of tragedy: as one reviewer pointed out, the missing relatives that Geralt is asked to find generally turn out to be (horribly) dead. In other words, creative scripting tends to deliver much more narratively satisfying quests than is usually delivered by procedural generation.

    +

    But, if we’re thinking of a game with much more intelligent non-player characters with much more conversational repertoir, as I am, can satisfying quests emerge organically? In space trading games such as Elite, a primary activity is moving goods from markets with surplus (and thus low prices) to markets with shortage (and thus high prices). This is, in effect, a game made up of deliver quests - but rather than deliver quests which are scripted, they are deliver quests which arise organically out of the structure of the game world.

    +

    I already have working code for non-player character merchants, who move goods from city to city based on market information available to them. For player characters to join in this trading is an organic activity emerging from the structure of the world, which provides an activity. But moving merchants provides a market opportunity for bandits, who can intercept and steal cargoes, and so for mercenaries, who can protect cargoes from bandits, and so on. And because I have an architecture that allows non-player characters to fill economic niches, there will be non-player characters in all these niches.

    +

    Where a non-player character can act, so can a player character: when a (non-player character) merchant seeks to hire a caravan guard and a player character responds, that’s an organic escort quest.

    +

    The key idea behind organic quests is that the circumstance and requirments for quests emerges as an emergent behaviour out of the mechanics of the game world. A non-player character doesn’t know that there is a player character who is different from them; rather, when a non-player character needs something they can’t readily achieve for themselves, they will ask other characters to help, and that may include the player character.

    +

    This means, of course, that characters need a goal-seeking planning algorithm to decide their actions, with one option in any plan being ‘ask for help’. Thus, ‘asking for help’ becomes a mechanism within the game, a normal behaviour. Ideally non-player characters will keep track of quite complex webs of loyalty and of obligation - debts of honour, duties of hospitality, collective loyalties. So that, if you do a favour for some character in the world, that character’s tribe, friends, obligation circle, whatever, are now more likely to do favours for you.

    +

    Obviously, this doesn’t stop you doing jobs you get directly paid/rewarded for, but I’d like the web of obligation to be at least potentially much richer than just tit for tat.

    +

    Related to this notion is the notion that, if you are asked to do a task by a character and you do it well, whether for pay or as a favour, your reputation for being competent in tasks of that kind will improve and the more likely it is that other characters will ask you to do similar tasks; and this will apply to virtually anything another character can ask of you in the game world, from carrying out an assassination to delivering a message to finding a quantiy of some specific commodity to having sex.

    +

    So quests can emerge organically from the mechanics of the world and be richly varied; I’m confident that will work. What I’m not confident of is that they can be narratively satisfying. This relates directly to the generation of speech.

    \ No newline at end of file diff --git a/docs/codox/sandbox.html b/docs/codox/sandbox.html new file mode 100644 index 0000000..fefc173 --- /dev/null +++ b/docs/codox/sandbox.html @@ -0,0 +1,39 @@ + +Sandbox

    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.

    \ No newline at end of file diff --git a/docs/codox/sexual-dimorphism.html b/docs/codox/sexual-dimorphism.html index 8cf4c50..3cb1205 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.agent.agent.html b/docs/codox/the-great-game.agent.agent.html new file mode 100644 index 0000000..02353e9 --- /dev/null +++ b/docs/codox/the-great-game.agent.agent.html @@ -0,0 +1,3 @@ + +the-great-game.agent.agent documentation

    the-great-game.agent.agent

    Anything in the game world with agency

    \ 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 3e8cf5c..559aa1c 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 gossip 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 of 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.gossip.news-items.html b/docs/codox/the-great-game.gossip.news-items.html new file mode 100644 index 0000000..e145054 --- /dev/null +++ b/docs/codox/the-great-game.gossip.news-items.html @@ -0,0 +1,18 @@ + +the-great-game.gossip.news-items documentation

    the-great-game.gossip.news-items

    Categories of news events interesting to gossip agents

    degrade-character

    (degrade-character gossip character)

    Return a character specification like this character, but comprising only those properties this gossip is interested in.

    degrade-location

    (degrade-location gossip location)

    Return a location specification like this location, but comprising only those elements this gossip is interested in. If none, return nil.

    infer

    (infer item rule)

    Infer a new knowledge item from this item, following this rule

    interest-in-character

    (interest-in-character gossip character)

    Integer representation of how interesting this character is to this gossip. TODO: this assumes that characters are passed as keywords, but, as documented above, they probably have to be maps, to allow for degradation.

    interest-in-location

    (interest-in-location gossip location)

    Integer representation of how interesting this location is to this gossip.

    interesting-character?

    (interesting-character? gossip character)

    Boolean representation of whether this character is interesting to this gossip.

    interesting-item?

    (interesting-item? gossip item)

    True if anything about this news item is interesting to this gossip.

    interesting-location?

    (interesting-location? gossip item)

    True if the location of this news item is interesting to this gossip.

    interesting-object?

    (interesting-object? gossip object)

    TODO: write docs

    interesting-topic?

    (interesting-topic? gossip topic)

    TODO: write docs

    learn-news-item

    (learn-news-item gossip item)(learn-news-item gossip item follow-inferences?)

    Return a gossip like this gossip, which has learned this news item if it is of interest to them.

    make-all-inferences

    (make-all-inferences item)

    Return a list of knowledge entries inferred from this news item by this gossip.

    news-topics

    Topics of interest to gossip agents. Topics are keyed in this map by their verbs. The keys associated with each topic are the extra pieces of information required to give context to a gossip item. Generally:

    +
      +
    • actor is the id of the character who it is reported performed the action;
    • +
    • other is the id of the character on whom it is reported the action was performed;
    • +
    • location is the place at which the action was performed;
    • +
    • object is an object (or possibly list of objects?) relevant to the action;
    • +
    • price is special to buy/sell, but of significant interest to merchants.
    • +
    +

    Notes:

    +
    Characters:
    +

    TODO but note that at most all the receiver can learn about a character from a news item is what the giver knows about that character, degraded by what the receiver finds interesting about them. If we just pass the id here, then either the receiver knows everything in the database about the character, or else the receiver knows nothing at all about the character. Neither is desirable. Further thought needed.

    +
    Locations:
    +

    A ‘location’ value is a list comprising at most the x/y coordinate location and the ids of the settlement and region (possibly hierarchically) that contain the location. If the x/y is not local to the home of the receiving agent, they won’t remember it and won’t pass it on; if any of the ids are not interesting So location information will degrade progressively as the item is passed along.

    +

    It is assumed that the :home of a character is a location in this sense.

    +
    Inferences:
    +

    If an agent learns that Adam has married Betty, they can infer that Betty has married Adam; if they learn that Charles killed Dorothy, that Dorothy has died. I’m not convinced that my representation of inferences here is ideal.

    \ 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 3c3d546..7d7e83e 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 b1b0b5f..58e0534 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 cache 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 870c8d7..46e3dc1 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 1274f66..2d17459 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 ca81e5b..d5ba1e3 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.time.html b/docs/codox/the-great-game.time.html new file mode 100644 index 0000000..76496a1 --- /dev/null +++ b/docs/codox/the-great-game.time.html @@ -0,0 +1,3 @@ + +the-great-game.time documentation

      the-great-game.time

      TODO: write docs

      canonical-ordering-of-houses

      The canonical ordering of religious houses.

      date-string

      (date-string game-time)

      Return a correctly formatted date for this game-time in the calendar of the Great Place.

      day

      (day game-time)

      Day of the eight-day week represented by this game-time.

      day-of-year

      macro

      (day-of-year game-time)

      The day of the year represented by this game-time, ignoring leap years.

      days-in-season

      TODO: write docs

      days-in-week

      This world has an eight day week.

      days-of-week

      The eight-day week of the game world. This differs from the canonical ordering of houses in that it omits the eye.

      game-day-length

      The Java clock advances in milliseconds, which is fine. But we need game-days to be shorter than real world days. A Witcher 3 game day is 1 hour 36 minutes, or 96 minutes, which is presumably researched. Round it up to 100 minutes for easier calculation.

      game-start-time

      The start time of this run.

      game-time

      (game-time)(game-time timestamp)

      With no arguments, the current game time. If a Java timestamp value is passed (as a long), the game time represented by that value.

      now

      (now)

      For now, we’ll use Java timestamp for time; ultimately, we need a concept of game-time which allows us to drive day/night cycle, seasons, et cetera, but what matters about time is that it is a value which increases.

      season

      (season game-time)

      TODO: write docs

      seasons-in-year

      Nine seasons in a year, one for each house (although the order is different.

      seasons-of-year

      The ordering of seasons in the year is different from the canonical ordering of the houses, for reasons of the agricultural cycle.

      waiting-day?

      Does this game-time represent a waiting day?

      week

      (week game-time)

      Week of season represented by this game-time.

      weeks-in-season

      To fit nine seasons of eight day weeks into 365 days, each must be of five weeks.

      weeks-of-season

      To fit nine seasons of eight day weeks into 365 days, each must be of five weeks.

      \ 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 b23284d..84eba13 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.location.html b/docs/codox/the-great-game.world.location.html new file mode 100644 index 0000000..e3836dc --- /dev/null +++ b/docs/codox/the-great-game.world.location.html @@ -0,0 +1,3 @@ + +the-great-game.world.location documentation

      the-great-game.world.location

      Functions dealing with location in the world.

      distance-between

      (distance-between location-1 location-2)

      TODO: write docs

      get-coords

      (get-coords location)

      Return the coordinates in the game world of location, which may be 1. A coordinate pair in the format {:x 5 :y 32}; 2. A location, as discussed above; 3. Any other gameworld object, having a :location property whose value is one of the above.

      \ 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 4694894..f5a5455 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 1d3b216..0aca065 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 fcec6c3..c417377 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/src/the_great_game/agent/agent.clj b/src/the_great_game/agent/agent.clj index b9232ef..7a17103 100644 --- a/src/the_great_game/agent/agent.clj +++ b/src/the_great_game/agent/agent.clj @@ -2,3 +2,6 @@ "Anything in the game world with agency") ;; hierarchy of needs probably gets implemented here +;; I'm probably going to want to defprotocol stuff, to define the hierarchy +;; of things in the gameworld; either that or drop to Java, wich I'd rather not do. + diff --git a/src/the_great_game/gossip/gossip.clj b/src/the_great_game/gossip/gossip.clj index 13aa961..af743f3 100644 --- a/src/the_great_game/gossip/gossip.clj +++ b/src/the_great_game/gossip/gossip.clj @@ -1,6 +1,7 @@ (ns the-great-game.gossip.gossip "Interchange of news events between gossip agents" - (:require [the-great-game.utils :refer [deep-merge]])) + (:require [the-great-game.utils :refer [deep-merge]] + [the-great-game.gossip.news-items :refer [learn-news-item]])) ;; Note that habitual travellers are all gossip agents; specifically, at this ;; stage, that means merchants. When merchants are moved we also need to diff --git a/src/the_great_game/gossip/news_items.clj b/src/the_great_game/gossip/news_items.clj index bcb0398..4591f3a 100644 --- a/src/the_great_game/gossip/news_items.clj +++ b/src/the_great_game/gossip/news_items.clj @@ -1,6 +1,7 @@ (ns the-great-game.gossip.news-items "Categories of news events interesting to gossip agents" - (:require [clojure.math.numeric-tower :refer [expt sqrt]])) + (:require [the-great-game.world.location :refer [distance-between]] + [the-great-game.time :refer [now]])) ;; The ideas here are based on the essay 'The spread of knowledge in a large ;; game world', q.v.; they've advanced a little beyond that and will doubtless @@ -20,14 +21,26 @@ their `verbs`. The `keys` associated with each topic are the extra pieces of information required to give context to a gossip item. Generally: - * `actor` is the id of the character who performed the action; - * `other` is the id of the character on whom the action was performed; + * `actor` is the id of the character who it is reported performed the + action; + * `other` is the id of the character on whom it is reported the action + was performed; * `location` is the place at which the action was performed; - * `object` is an object (or possibly list of objects?) relevant to the action; + * `object` is an object (or possibly list of objects?) relevant to the + action; * `price` is special to buy/sell, but of significant interest to merchants. #### Notes: + ##### Characters: + + *TODO* but note that at most all the receiver can learn about a character + from a news item is what the giver knows about that character, degraded by + what the receiver finds interesting about them. If we just pass the id here, + then either the receiver knows everything in the database about the + character, or else the receiver knows nothing at all about the character. + Neither is desirable. Further thought needed. + ##### Locations: A 'location' value is a list comprising at most the x/y coordinate location @@ -62,6 +75,7 @@ :plot {:verb :plot :keys [:actor :other :object :location]} ;; Rapes are interesting. :rape {:verb :rape :keys [:actor :other :location] + ;; Should you also infer from rape that actor is male and adult? :inferences [{:verb :attack} {:verb :sex} {:verb :sex :actor :other :other :actor}]} @@ -84,7 +98,9 @@ (defn interest-in-character "Integer representation of how interesting this `character` is to this - `gossip`." + `gossip`. + *TODO:* this assumes that characters are passed as keywords, but, as + documented above, they probably have to be maps, to allow for degradation." [gossip character] (count (concat @@ -97,99 +113,46 @@ [gossip character] (> (interest-in-character gossip character) 0)) -(defn get-coords - "Return the coordinates in the game world of `location`, which may be - 1. A coordinate pair in the format {:x 5 :y 32}; - 2. A location, as discussed above; - 3. Any other gameworld object, having a `:location` property whose value - is one of the above." - [location] - (cond - (empty? location) nil - (map? location) - (cond - (and (number? (:x location)) (number? (:y location))) - location - (:location location) - (:location location)) - :else - (get-coords (first (remove keyword? location))))) - -;; (get-coords {:x 5 :y 7}) -;; (get-coords [{:x -4 :y 55} :auchencairn :galloway :scotland]) - -(defn distance-between - [location-1 location-2] - (let [c1 (get-coords location-1) - c2 (get-coords location-2)] - (if - (and c1 c2) - (sqrt (+ (expt (- (:x c1) (:x c2)) 2) (expt (- (:y c1) (:y c2)) 2)))))) - -;; (distance-between {:x 5 :y 5} {:x 2 :y 2}) -;; (distance-between {:x 5 :y 5} {:x 2 :y 5}) -;; (distance-between {:x 5 :y 5} [{:x -4 :y 55} :auchencairn :galloway :scotland]) -;; (distance-between {:x 5 :y 5} [:auchencairn :galloway :scotland]) - (defn interest-in-location "Integer representation of how interesting this `location` is to this `gossip`." [gossip location] (cond + (and (map? location) (number? (:x location)) (number? (:y location))) + (if-let [home (:home gossip)] + (let [d (distance-between location home) + i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should + ;;fall of with distance from home, but possibly on a log scale + ] + (if (> i 1) i 0)) + 0) (coll? location) (reduce + (map #(interest-in-location gossip %) location)) - (and (map? location) (:x location) (:y location)) - (if-let [home (:home gossip)] - (let [d (distance-between location home) - i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should - ;;fall of with distance from home, but possibly on a log scale - ] - (if (i > 1) i 0) - i)) :else (count (filter #(some (fn [x] (= x location)) (:location %)) (:knowledge gossip))))) -;; (interest-in-location -;; {:knowledge [{:verb :steal -;; :actor :albert -;; :other :belinda -;; :object :foo -;; :location [{:x 35 :y 23} :auchencairn :galloway]}]} -;; :galloway) - -;; (interest-in-location -;; {:knowledge [{:verb :steal -;; :actor :albert -;; :other :belinda -;; :object :foo -;; :location [{:x 35 :y 23} :auchencairn :galloway]}]} -;; [:galloway :scotland]) - - -;; (interest-in-location -;; {:knowledge [{:verb :steal -;; :actor :albert -;; :other :belinda -;; :object :foo -;; :location [{:x 35 :y 23} :auchencairn :galloway]}]} -;; :dumfries) - -;; (interest-in-location -;; {:home {:x 35 :y 23}} -;; {:x 35 :y 24}) - (defn interesting-location? "True if the location of this news `item` is interesting to this `gossip`." [gossip item] (> (interest-in-location gossip (:location item)) 1)) +(defn interesting-object? + [gossip object] + ;; TODO: Not yet (really) implemented + true) + +(defn interesting-topic? + [gossip topic] + ;; TODO: Not yet (really) implemented + true) + (defn interesting-item? "True if anything about this news `item` is interesting to this `gossip`." [gossip item] @@ -212,18 +175,44 @@ #(= % :verb) (keys rule)))))) -;; (infer {:verb :marry :actor :adam :other :belinda} -;; {:verb :marry :actor :other :other :actor}) -;; (infer {:verb :rape :actor :adam :other :belinda} -;; {:verb :attack}) -;; (infer {:verb :rape :actor :adam :other :belinda} -;; {:verb :sex :actor :other :other :actor}) +(declare learn-news-item) + +(defn make-all-inferences + "Return a list of knowledge entries inferred from this news `item` by this + `gossip`." + [item] + (set + (reduce + concat + (map + #(:knowledge (learn-news-item {} (infer item %) false)) + (:inferences (news-topics (:verb item))))))) + +(defn degrade-character + "Return a character specification like this `character`, but comprising + only those properties this `gossip` is interested in." + [gossip character] + ;; TODO: Not yet (really) implemented + character) + +(defn degrade-location + "Return a location specification like this `location`, but comprising + only those elements this `gossip` is interested in. If none, return + `nil`." + [gossip location] + (let [l (if + (coll? location) + (filter + #(when (interesting-location? gossip %) %) + location))] + (when-not (empty? l) l))) (defn learn-news-item "Return a gossip like this `gossip`, which has learned this news `item` if it is of interest to them." + ;; TODO: Not yet implemented ([gossip item] - (learn-news-item gossip item false)) + (learn-news-item gossip item true)) ([gossip item follow-inferences?] (if (interesting-item? gossip item) @@ -235,17 +224,21 @@ (number? (:nth-hand item)) (inc (:nth-hand item)) 1) - ;; ought to degrate the location + :date (if (number? (:date item)) (:date item) (now)) + :location (degrade-location gossip (:location item)) + ;; ought to degratde the location ;; ought to maybe-degrade characters we're not yet interested in ) ;; ought not to add knowledge items we already have, except ;; to replace if new item is of increased specificity (:knowledge gossip)))] (if follow-inferences? - (reduce - merge + (assoc g - (map - #(learn-news-item gossip (infer item %) false) - (:inferences (news-topics (:verb item)))))))))) + :knowledge + (concat (:knowledge g) (make-all-inferences item))) + g)) + gossip))) + + diff --git a/src/the_great_game/time.clj b/src/the_great_game/time.clj new file mode 100644 index 0000000..2378937 --- /dev/null +++ b/src/the_great_game/time.clj @@ -0,0 +1,144 @@ +(ns the-great-game.time + (:require [clojure.string :as s])) + +(def game-start-time + "The start time of this run." + (System/currentTimeMillis)) + +(def ^:const game-day-length + "The Java clock advances in milliseconds, which is fine. + But we need game-days to be shorter than real world days. + A Witcher 3 game day is 1 hour 36 minutes, or 96 minutes, which is + presumably researched. Round it up to 100 minutes for easier + calculation." + (* 100 ;; minutes per game day + 60 ;; seconds per minute + 1000)) ;; milliseconds per second + +(defn now + "For now, we'll use Java timestamp for time; ultimately, we need a + concept of game-time which allows us to drive day/night cycle, seasons, + et cetera, but what matters about time is that it is a value which + increases." + [] + (System/currentTimeMillis)) + +(def ^:const canonical-ordering-of-houses + "The canonical ordering of religious houses." + [:eye + :foot + :nose + :hand + :ear + :mouth + :stomach + :furrow + :plough]) + +(def ^:const days-of-week + "The eight-day week of the game world. This differs from the canonical + ordering of houses in that it omits the eye." + (rest canonical-ordering-of-houses)) + +(def ^:const days-in-week + "This world has an eight day week." + (count days-of-week)) + +(def ^:const seasons-of-year + "The ordering of seasons in the year is different from the canonical + ordering of the houses, for reasons of the agricultural cycle." + [:foot + :nose + :hand + :ear + :mouth + :stomach + :plough + :furrow + :eye]) + +(def ^:const seasons-in-year + "Nine seasons in a year, one for each house (although the order is + different." + (count seasons-of-year)) + +(def ^:const weeks-of-season + "To fit nine seasons of eight day weeks into 365 days, each must be of + five weeks." + [:first :second :third :fourth :fifth]) + +(def ^:const weeks-in-season + "To fit nine seasons of eight day weeks into 365 days, each must be of + five weeks." + (count weeks-of-season)) + +(def ^:const days-in-season + (* weeks-in-season days-in-week)) + +(defn game-time + "With no arguments, the current game time. If a Java `timestamp` value is + passed (as a `long`), the game time represented by that value." + ([] (game-time (now))) + ([timestamp] + (- timestamp game-start-time))) + +(defmacro day-of-year + "The day of the year represented by this `game-time`, ignoring leap years." + [game-time] + `(mod (long (/ ~game-time game-day-length)) 365)) + +(def waiting-day? + "Does this `game-time` represent a waiting day?" + (memoize + ;; we're likely to call this several times in quick succession on the + ;; same timestamp + (fn [game-time] + (>= + (day-of-year game-time) + (* seasons-in-year weeks-in-season days-in-week))))) + +(defn day + "Day of the eight-day week represented by this `game-time`." + [game-time] + (let [day-of-week (mod (day-of-year game-time) days-in-week)] + (if (waiting-day? game-time) + (nth weeks-of-season day-of-week) + (nth days-of-week day-of-week)))) + +(defn week + "Week of season represented by this `game-time`." + [game-time] + (let [day-of-season (mod (day-of-year game-time) days-in-season) + week (/ day-of-season days-in-week)] + (if (waiting-day? game-time) + :waiting + (nth weeks-of-season week)))) + +(defn season + [game-time] + (let [season (int (/ (day-of-year game-time) days-in-season))] + (if (waiting-day? game-time) + :waiting + (nth seasons-of-year season)))) + +(defn date-string + "Return a correctly formatted date for this `game-time` in the calendar of + the Great Place." + [game-time] + (s/join + " " + (if + (waiting-day? game-time) + [(s/capitalize + (name + (nth + weeks-of-season + (mod (day-of-year game-time) days-in-week)))) + "waiting day"] + [(s/capitalize (name (week game-time))) + (s/capitalize (name (day game-time))) + "of the" + (s/capitalize (name (season game-time)))]))) + + + diff --git a/src/the_great_game/world/location.clj b/src/the_great_game/world/location.clj index 6709f30..b7c3fd0 100644 --- a/src/the_great_game/world/location.clj +++ b/src/the_great_game/world/location.clj @@ -1,5 +1,6 @@ (ns the-great-game.world.location - "Functions dealing with location in the world.") + "Functions dealing with location in the world." + (:require [clojure.math.numeric-tower :refer [expt sqrt]])) ;; A 'location' value is a list comprising at most the x/y coordinate location ;; and the ids of the settlement and region (possibly hierarchically) that contain @@ -8,3 +9,29 @@ ;; So location information will degrade progressively as the item is passed along. ;; It is assumed that the `:home` of a character is a location in this sense. + +(defn get-coords + "Return the coordinates in the game world of `location`, which may be + 1. A coordinate pair in the format {:x 5 :y 32}; + 2. A location, as discussed above; + 3. Any other gameworld object, having a `:location` property whose value + is one of the above." + [location] + (cond + (empty? location) nil + (map? location) + (cond + (and (number? (:x location)) (number? (:y location))) + location + (:location location) + (:location location)) + :else + (get-coords (first (remove keyword? location))))) + +(defn distance-between + [location-1 location-2] + (let [c1 (get-coords location-1) + c2 (get-coords location-2)] + (when + (and c1 c2) + (sqrt (+ (expt (- (:x c1) (:x c2)) 2) (expt (- (:y c1) (:y c2)) 2)))))) diff --git a/test/the_great_game/gossip/gossip_test.clj b/test/the_great_game/gossip/gossip_test.clj new file mode 100644 index 0000000..176fab4 --- /dev/null +++ b/test/the_great_game/gossip/gossip_test.clj @@ -0,0 +1,4 @@ +(ns the-great-game.gossip.gossip-test + (:require [clojure.test :refer :all] + [the-great-game.gossip.gossip :refer :all])) + diff --git a/test/the_great_game/gossip/news_items_test.clj b/test/the_great_game/gossip/news_items_test.clj new file mode 100644 index 0000000..908e330 --- /dev/null +++ b/test/the_great_game/gossip/news_items_test.clj @@ -0,0 +1,132 @@ +(ns the-great-game.gossip.news-items-test + (:require [clojure.test :refer :all] + [the-great-game.gossip.news-items :refer :all])) + + +(deftest location-test + (testing "Interest in locations" + (let [expected 1 + actual (interest-in-location + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :location [{:x 35 :y 23} :auchencairn :galloway]}]} + :galloway)] + (is (= actual expected))) + (let [expected 2 + actual (interest-in-location + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :location [{:x 35 :y 23} :auchencairn :galloway :scotland]}]} + [:galloway :scotland])] + (is (= actual expected))) + (let [expected 0 + actual (interest-in-location + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :location [{:x 35 :y 23} :auchencairn :galloway]}]} + [:dumfries])] + (is (= actual expected))) + (let [expected 7071.067811865475 + actual (interest-in-location + {:home [{:x 35 :y 23}]} + [{:x 34 :y 24}])] + (is (= actual expected) + "TODO: 7071.067811865475 is actually a bad answer.")) + (let [expected 0 + actual (interest-in-location + {:home [{:x 35 :y 23}]} + [{:x 34 :y 24000}])] + (is (= actual expected) + "Too far apart (> 10000).")) + (let [expected true + actual (interesting-location? + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :location [{:x 35 :y 23} :auchencairn :galloway]}]} + :galloway)] + (is (= actual expected))) + (let [expected true + actual (interesting-location? + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :location [{:x 35 :y 23} :auchencairn :galloway]}]} + [:galloway :scotland])] + (is (= actual expected))) + (let [expected false + actual (interesting-location? + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :location [{:x 35 :y 23} :auchencairn :galloway]}]} + [:dumfries])] + (is (= actual expected))) + (let [expected true + actual (interesting-location? + {:home [{:x 35 :y 23}]} + [{:x 34 :y 24}])] + (is (= actual expected))) + (let [expected false + actual (interesting-location? + {:home [{:x 35 :y 23}]} + [{:x 34 :y 240000}])] + (is (= actual expected)))) + (testing "Degrading locations" + (let [expected [:galloway] + actual (degrade-location + {:home [{0 0} :test-home :galloway]} + [{-4 55} :auchencairn :galloway])] + (is (= actual expected))) + (let [expected nil + actual (degrade-location + {:home [{0 0} :test-home :galloway]} + [:froboz])] + (is (= actual expected))))) + +(deftest inference-tests + (testing "Ability to infer new knowledge from news items: single rule tests" + (let [expected {:verb :marry, :actor :belinda, :other :adam} + actual (infer {:verb :marry :actor :adam :other :belinda} + {:verb :marry :actor :other :other :actor})] + (is (= actual expected))) + (let [expected {:verb :attack, :actor :adam, :other :belinda} + actual (infer {:verb :rape :actor :adam :other :belinda} + {:verb :attack})] + (is (= actual expected))) + (let [expected {:verb :sex, :actor :belinda, :other :adam} + actual (infer {:verb :rape :actor :adam :other :belinda} + {:verb :sex :actor :other :other :actor})] + (is (= actual expected)))) + (testing "Ability to infer new knowledge from news items: all applicable rules" + (let [expected #{{:verb :sex, :actor :belinda, :other :adam, :location nil, :nth-hand 1} + {:verb :sex, :actor :adam, :other :belinda, :location nil, :nth-hand 1} + {:verb :attack, :actor :adam, :other :belinda, :location nil, :nth-hand 1}} + ;; dates will not be and cannot be expected to be equal + actual (make-all-inferences + {:verb :rape :actor :adam :other :belinda :location :test-home}) + actual' (map #(dissoc % :date) actual)] + (is (= actual' expected))))) + +;; (deftest learn-tests +;; (testing "Learning from an interesting news item." +;; (let [expected {:home [{0 0} :test-home], +;; :knowledge ({:verb :rape, :actor :adam, :other :belinda, :location nil, :nth-hand 1} +;; {:verb :sex, :actor :belinda, :other :adam, :location nil, :nth-hand 1} +;; {:verb :attack, :actor :adam, :other :belinda, :location nil, :nth-hand 1} +;; {:verb :sex, :actor :adam, :other :belinda, :location nil, :nth-hand 1})} +;; actual (learn-news-item +;; {:home [{0, 0} :test-home] +;; :knowledge []} +;; {:verb :rape :actor :adam :other :belinda :location [:test-home]}) +;; actual' (assoc actual :knowledge (map #(dissoc % :date) (:knowledge actual)))] +;; (is (= actual' expected))))) diff --git a/test/the_great_game/time_test.clj b/test/the_great_game/time_test.clj new file mode 100644 index 0000000..4727fdd --- /dev/null +++ b/test/the_great_game/time_test.clj @@ -0,0 +1,79 @@ +(ns the-great-game.time-test + (:require [clojure.test :refer :all] +;; [clojure.core.async :refer [thread t1 game-start-time)) + (Thread/sleep 1000) + (is (> (now) t1))))) + +(deftest game-time-tests + (testing "Getting game-time" + (is (= (game-time (inc game-start-time)) 1)))) + +(deftest calendar-tests + (testing "In-game calendar functions" + (let [expected :foot + actual (day 0)] + (is (= actual expected))) + (let [expected :stomach + actual (day (* 5 game-day-length))] + (is (= actual expected))) + (let [expected :foot + actual (day (* days-in-week game-day-length))] + (is (= actual expected))) + (let [expected :first ;; waiting day + actual (day (* 360 game-day-length))] + (is (= actual expected))) + (let [expected :first + actual (week 0)] + (is (= actual expected))) + (let [expected :second + actual (week (* days-in-week game-day-length))] + (is (= actual expected))) + (let [expected :first + actual (week (* days-in-season game-day-length))] + (is (= actual expected))) + (let [expected :foot + actual (season 0)] + (is (= actual expected))) + (let [expected :mouth + actual (season (* 180 game-day-length))] + (is (= actual expected))) + (let [expected :eye + actual (season (* 359 game-day-length))] + (is (= actual expected))) + (let [expected :waiting + actual (season (* 360 game-day-length))] + (is (= actual expected))) + (let [expected :foot + actual (season (* 365 game-day-length))] + (is (= actual expected))))) + +(deftest date-string-tests + (testing "Date-string formatting" + (let [expected "First Foot of the Foot" + actual (date-string 0)] + (is (= actual expected))) + (let [expected "First Foot of the Nose" + actual (date-string + (* days-in-season game-day-length))] + (is (= actual expected))) + (let [expected "Third Mouth of the Mouth" + actual (date-string (* 180 game-day-length))] + (is (= actual expected))) + (let [expected "Fifth Plough of the Eye" + actual (date-string (* 359 game-day-length))] + (is (= actual expected))) + (let [expected "First waiting day" + actual (date-string (* 360 game-day-length))] + (is (= actual expected))) + (let [expected "First Foot of the Foot" + actual (date-string (* 365 game-day-length))] + (is (= actual expected))))) + + + diff --git a/test/the_great_game/world/location_test.clj b/test/the_great_game/world/location_test.clj new file mode 100644 index 0000000..7303e1f --- /dev/null +++ b/test/the_great_game/world/location_test.clj @@ -0,0 +1,36 @@ +(ns the-great-game.world.location-test + (:require [clojure.test :refer :all] + [the-great-game.world.location :refer :all])) + +(deftest get-coords-test + (testing "Get coordinates of location" + (let [expected {:x 5 :y 7} + actual (get-coords {:x 5 :y 7})] + (is (= actual expected))) + (let [expected {:x -4 :y 55} + actual (get-coords [{:x -4 :y 55} :auchencairn :galloway :scotland])] + (is (= actual expected))) + (let [expected nil + actual (get-coords [:auchencairn :galloway :scotland])] + (is (= actual expected))) + )) + +(deftest distance-test + (testing "Distance between two locations" + (let [expected 4.242640687119285 + actual (distance-between {:x 5 :y 5} {:x 2 :y 2})] + (is (= actual expected))) + (let [expected 3 + actual (distance-between {:x 5 :y 5} {:x 2 :y 5})] + (is (= actual expected))) + (let [expected 50.80354318352215 + actual (distance-between + {:x 5 :y 5} + [{:x -4 :y 55} :auchencairn :galloway :scotland])] + (is (= actual expected))) + (let [expected nil + actual (distance-between + {:x 5 :y 5} + [:auchencairn :galloway :scotland])] + (is (= actual expected))) + )) From 3fcf16e0798470601a27c49d5ba3103e8560630d Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 15 Apr 2020 16:40:59 +0100 Subject: [PATCH 05/12] Much work on tidying documentation, not yet complete. --- .gitignore | 2 + doc/Baking-the-world.md | 81 +++++ doc/Populating-a-game-world.md | 112 +++++++ doc/Settling-a-game-world.md | 87 +++++ ...read-of-knowledge-in-a-large-game-world.md | 64 ++++ doc/Voice-acting-considered-harmful.md | 90 ++++++ doc/{on-dying.ods => on-dying.md} | 0 doc/sandbox.md | 6 +- docs/cloverage/index.html | 46 ++- .../the_great_game/gossip/gossip.clj.html | 129 ++++---- .../the_great_game/gossip/news_items.clj.html | 266 ++++++++-------- docs/cloverage/the_great_game/time.clj.html | 2 +- docs/codox/Baking-the-world.html | 54 ++++ docs/codox/Populating-a-game-world.html | 297 ++++++++++++++++++ docs/codox/Settling-a-game-world.html | 68 ++++ ...ad-of-knowledge-in-a-large-game-world.html | 41 +++ .../Voice-acting-considered-harmful.html | 49 +++ docs/codox/economy.html | 2 +- docs/codox/index.html | 2 +- docs/codox/intro.html | 2 +- .../modelling_trading_cost_and_risk.html | 2 +- docs/codox/naming-of-characters.html | 2 +- docs/codox/on-dying.html | 9 + docs/codox/orgnic-quests.html | 2 +- docs/codox/sandbox.html | 8 +- docs/codox/sexual-dimorphism.html | 2 +- docs/codox/the-great-game.agent.agent.html | 2 +- docs/codox/the-great-game.gossip.gossip.html | 2 +- .../the-great-game.gossip.news-items.html | 2 +- .../the-great-game.merchants.markets.html | 2 +- ...e-great-game.merchants.merchant-utils.html | 2 +- .../the-great-game.merchants.merchants.html | 2 +- .../the-great-game.merchants.planning.html | 2 +- ...reat-game.merchants.strategies.simple.html | 2 +- docs/codox/the-great-game.time.html | 2 +- docs/codox/the-great-game.utils.html | 2 +- docs/codox/the-great-game.world.location.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 +- docs/index.html | 7 +- src/the_great_game/gossip/news_items.clj | 10 +- .../the_great_game/gossip/news_items_test.clj | 30 +- 43 files changed, 1233 insertions(+), 267 deletions(-) create mode 100644 doc/Baking-the-world.md create mode 100644 doc/Populating-a-game-world.md create mode 100644 doc/Settling-a-game-world.md create mode 100644 doc/The-spread-of-knowledge-in-a-large-game-world.md create mode 100644 doc/Voice-acting-considered-harmful.md rename doc/{on-dying.ods => on-dying.md} (100%) create mode 100644 docs/codox/Baking-the-world.html create mode 100644 docs/codox/Populating-a-game-world.html create mode 100644 docs/codox/Settling-a-game-world.html create mode 100644 docs/codox/The-spread-of-knowledge-in-a-large-game-world.html create mode 100644 docs/codox/Voice-acting-considered-harmful.html create mode 100644 docs/codox/on-dying.html diff --git a/.gitignore b/.gitignore index 0910231..b0ebaf1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ pom.xml.asc .nrepl-port .cpcache/ *~ + +doc/.~lock.Population.ods# diff --git a/doc/Baking-the-world.md b/doc/Baking-the-world.md new file mode 100644 index 0000000..c41c6cc --- /dev/null +++ b/doc/Baking-the-world.md @@ -0,0 +1,81 @@ +# Baking the world + +#### Wednesday, 8 May 2019 + +![Devogilla's Bridge in Dumfries, early foourteenth century](https://2.bp.blogspot.com/-qxkySlJNmtY/XNKvJksmSjI/AAAAAAAAnXU/z1Zv2LmjydMmi_1q2mWdwVALmdfi9OItwCLcBGAs/s1600/Devorgillas-Bridge.jpg) + + In previous posts, I've described algorithms for dynamically [populating](Populating-a-game-world.html) and dynamically [settling](Settling-a-game-world.html) a game world. But at kilometre scale (and I think we need a higher resolution than that - something closer to hectare scale), settling the British Isles using my existing algorithms takes about 24 hours of continuous compute on an eight core, 3GHz machine. You cannot do that every time you launch a new game. + + So the game development has to run in four phases: the first three phases happen during development, to create a satisfactory, already populated and settled, initial world for the game to start from. This is particularly necessary if hand-crafted buildings and environments are going to be added to the world; the designers of those buildings and environments have to be able to see the context into which their models must fit. + +## Phase one: proving - the procedural world + + I'm going to call the initial phase of the game run - the phase which takes place before the quest team write their quests and the art department adds their hand-crafted models - 'proving', as when dough has been been made and set aside to rise. + + Then, when the landscape has developed - the areas of forest, scrub, open meadow, moorland, savanah and desert are determined, the rivers plotted, the settlers moved in, their trades determined and their settlements allocated, the roadways which link settlements routed, river crossings and ports defined - the proving process ends, and the world is turned over to the plot-writers, quest builders and designers, for a process we can see as analogous to kneading. + + But, before going there, to summarise the proving stage. The inputs are: + +1. A raster height map (although this could be randomly generated using any one of many fractal algorithms) - this probably uses ideas from [tessellated multi-layer height map](../../2013/07/tessellated-multi-layer-height-map.html); +1. Optionally, a raster rainfall map at 1km resolution (although my personal preference is that this should be generated procedurally from the height map). + + The outputs are + +1. A vector drainage map (rivers); +1. A raster biome map at roughly 1 km resolution (it might be anything between hectare resolution and 1Km resolution,  but obviously higher resolution takes more storage); +1. A database of settlers and their settlements, such that the settlements have x,y co-ordinates; +1. A vector road map. + + In this sense, the 'biome map' is just the end state of a [Microworld](../../2014/08/modelling-settlement-with-cellular.html) run. The 'biomes' include things like 'forest', 'scrub', 'heath', 'pasture', but they may also include human settlement, and even settlement by different cultural groups. + + This gives us all we need to vegetate and furnish the world. When rendering each square metre we have + +1. The x,y coordinates, obviously; +1. The altitude, taken from the height map; +1. The biome, taken from the biome map; +1. The biomes of adjacent cells in the biome map; +1. The proximity of the nearest watercourse; +1. The proximity of the nearest road or pathway; +1. Whether we are inside, or outside, a settlement (where for these purposes, 'settlement' includes enclosed field), and if inside, what type of settlement it is. + + Given these parameters, and using the x, y coordinates as seed of a deterministic pseudo-random number generator, we can generate appropriate vegetation and buildings to render a believable world. The reason for pulling adjacent biomes into the renderer is that sharp transitions from one biome to another - especially ones which align to a rectangular grid - rarely exist in nature, and that consequently most transitions from one biome to another should be gradual. + + Note that proving, although extremely compute intensive, is not necessarily a one-time job. If the designers aren't satisfied with the first world to emerge from this process, they can run it again, and again, to generate a world with which they are satisfied. It's also possible to hand-edit the output of proving, if needed. + + But now, designers and story-writers can see the world in which their creations will be set. + +## Phase two: kneading - making the world fit our needs + + Enough of proving, let's get on to kneading. + + Hand-designed buildings and environments are likely to be needed, or at least useful, for plot; also, particularly, very high status buildings are probably better hand designed. I'm inclined to think that less is more here, for two reasons: + + You cannot hand design a very large world, it's just impossible. How CD Project Red managed with Witcher 3 I don't know, since I understand that is largely hand designed; but that was a very large team, and even so it isn't a world on the scale I'm envisaging. + + Procedurally generated models take a wee bit of compute power to reify, but not a huge amount, and they're trivial to store - you need one single birch leaf model and one single birch-bark texture generator to make every birch tree in the game, and probably a single parameterised tree function can draw every tree of every species (and quite a lot of shrubs and ground-cover plants, too). But once reified, they take no longer to render than a manually crafted model. + + By contrast, a manually crafted model will take a very great deal more space to store, such that being able to render a large world from hand crafted models, without excessive model re-use, isn't going to be possible. + + So it's better in my opinion to put effort into good procedural generation functions, not just for foliage but also for buildings. My reason for using a picture of a medieval bridge at the head of the essay is to illustrate exactly this point: even in the medieval period, bridges comprise a series of repeating modules. Take one arch module and one ramp module from Devorgilla's bridge as models, add texture skins for several different stone types, stretch the modules a little in whatever dimension is needed, and repeat the arch module as many times as needed, and you can create a range of bridges to span many different rivers - which will all be visibly similar, but that's fine, that's the nature of a traditional culture - but each slightly different. + + Take half a dozen sets of models - timber bridges for forested biomes, brick bridges for biomes without stone or timber - and you can build procedural bridges across a whole continent without ever exactly repeating yourself. + + However, in some places the designers and story writers will want, for plot reasons and to create iconic environments, to add models. I'm inclined not to over do this, both for reasons of development effort and for reasons of storage cost, but they will. Very high status buildings may need to be unique and distinctive, for example. These need to be designed and their locations and spatial dimensions added to the database, so that the models can be rendered in the right positions (and, critically, procedurally generated models can be omitted in those positions!) + + Story and quest writers will also want characters for their plots. While there's no reason why writers cannot add entirely new characters to the database, there's no reason why they cannot incorporate characters generated in the settlement phase into the story; for this reason, characters need to be able to be tagged in the database as plot characters, and with what quests/elements of the plot they're associated. + + This allows a mechanism to prevent a plot character from being killed by another non-player character, or dying of disease or starvation, before the plot elements in which they feature have been completed. + +## Phase three: baking - making it delicious + + Once the world has been populated, settled, vegetated, the story has been written, the models built, the quests designed, there is probably a process of optimisation - stripping out things which aren't needed at play time, streamlining things that are - before you have a game ready to ship; but really I haven't yet given that much thought. + +## Phase four: eating! + + At the end, though, you have a game, and a player plays it. How much of the dynamic, organic life that brought the game through proving continues on into the playing phase? If the [gossip](The-spread-of-knowledge-in-a-large-game.html) ideas are to work, if unscripted, non-plot-related events (as well as scripted, plot related events) are to happen while the player plays, if news of these events is to percolate through the world and reach the player in organic, unscripted ways, if a lot of the emergent gameplay I'm imagining is to work, then quite a lot of the dynamic things must be happening. + + Of course, part of this depends on the length of 'game world time' is expected to elapse in the course of one play through of the game. If it's less than a year, then you don't need children dynamically being born, and characters dynamically growing older; but if more, then you do. Similarly, you don't need a real simulation of trading to dynamically drive prices in markets, but for a fun trading sub-game to emerge, you probably do, and if you are using merchants as news spreading agents the additional compute cost is not high. + + And I understand that many game writers will shudder at the thought that a war might (or might not) start in the middle of their plot, that a battle might, one time in a thousand, take place right where they've plotted some significant encounter. Most modern video games are essentially just very complicated state machines: if you make this sequence of choices, this outcome will happen, guaranteed. Or else they're puddles of random soup, where everything that happens is more or less driven by a random number generator. What I'm envisaging is something quite different: a world in which traders gonna trade, robbers gonna rob, lovers gonna love, scandal-mongers gonna make scandal, organically and dynamically whether the player is there or not, and news of these events will filter through to the player through the gossip network also organically and dynamically. + + A world, in short, through which no two runs will ever be the same, in which interesting bits of story will happen with no-one directing or scripting them. And for that to work, some of the same dynamic processes that drove the proving phase have to continue into the eating phase. diff --git a/doc/Populating-a-game-world.md b/doc/Populating-a-game-world.md new file mode 100644 index 0000000..1783a78 --- /dev/null +++ b/doc/Populating-a-game-world.md @@ -0,0 +1,112 @@ +# Populating a game world + +#### Saturday, 6 July 2013 + + *(You might want to read this essay in conjunction with my older essay, [Settling a game world](../../2009/12/settling-game-world.html), which covers similar ground but which this hopefully advances on)* + + For an economy to work people have to be able to move between occupations to fill economic niches. In steady state, non player character (NPC) males become adult as 'vagrants', and then move through the state transitions described in this document. The pattern for females is different. + +## Basic occupations + + The following are 'unskilled' occupations which form the base of the occupation system. Generally a male character at maturity becomes a 'Vagrant' and wanders though the world until he encounters a condition which allows him to advance up the occupation graph. If an occupation wholly fails, the character can revert to being a 'Vagrant' and start again. + + + +| Occupation | Dwelling | condition | New trade | Notes | +| --- | --- | --- | --- | --- | +| Vagrant | None | land available and animals available | Herdsman | | +| Vagrant | None | arable land available | Farmer | See crops | +| Vagrant | None | has weapons | Outlaw | | +| Herdsman | None | Insufficient food | Vagrant | | +| Farmer | Farm | Insufficient food | Vagrant | | +| Outlaw | None | loses weapons | Vagrant | | +| Vagrant | None | craftsman willing to take on apprentice | Apprentice | | +| Herdsman | None | arable land available | Farmer | | +| Outlaw | None | Battle hardened | OutlawLeader | | +| Apprentice | (craftsman's) | Qualified | Journeyman | | +| Journeyman | None | Unserviced customers available | Craftsman | See crafts | +| Craftsman | See crafts | Too few customers | Journeyman | | +| Journeyman | None | arable land available | Farmer | | +| Vagrant | None | Lord with vacancies available | Soldier | See military | +| OutlawLeader | None | Unprotected farms available | Laird | See nobility | + + +### Gender dimorphism + + In the paragraph above I said 'a male character'. It may seem unfair to create a game world in which the sexual inequality of the real world is carried over, and for that reason it seems sensible that female children should have the same opportunities as male children. But games work on conflicts and injustices, and so it seems reasonable to me to have a completely different occupation graph for women. I haven't yet drawn that up. + +### Wandering + + Vagrants wander in a fairly random way. While vagrants are wandering they are assumed to live off the land and require no resources. Solitary outlaws similarly wander until they find a leader, although they will avoid the areas protected by nobles. Herdsmen also wander but only over unenclosed pasture. They visit markets, if available, periodically; otherwise, they live off their herds. Journeymen wander from market to market, but are assumed to trade skills with farmers along the way. + +## Crafts + + Crafts are occupations which require acquired skills. In the initial seeding of the game world there are probably 'pioneers', who are special vagrants who, on encountering the conditions for a particular craft to thrive, instantly become masters of that craft. + + +| Craft | Dwelling | Supplies | Perishable? | Customer types | Needs market? | Customers | Supplier | Suppliers | Recruits | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| | | | | | | Solo | Per journeyman | Per apprentice | | | | +| | | | | | | --- | --- | --- | | | | +| | | | | | | Min | Max | Min | Max | Min | Max | | | | +| --- | | | | | | --- | --- | --- | --- | --- | --- | | | | +| Smith | Forge | Metal Items | no | Farmer, Soldier | No | 6 | 10 | 4 | 6 | 1 | 3 | Miner | 1 | Vagrant | +| Baker | Bakery | Bread | yes | All NPCs | No | 20 | 30 | 12 | 18 | 6 | 10 | Miller | 1 | Vagrant | +| Miller | Mill | Flour, meal | no | Baker, Innkeeper | No | 2 | 3 | 1 | 2 | 1 | 1 | Farmer | 6 | Vagrant | +| Weaver | Weaver's house | Cloth | no | All NPCs | Yes | 6 | 10 | 4 | 6 | 1 | 3 | Herdsman | 2 | Vagrant | +| Innkeeper | Inn | Food, hospitality | yes | Merhant, Soldier, Farmer, Lord | No | 10 | 20 | 5 | 10 | 2 | 4 | Farmer,Herdsman | 2 | Vagrant | +| Miner | Mine | Ores | no | Smith | Yes | 2 | 3 | 1 | 2 | 1 | 1 | Farmer | 1 | Vagrant | +| Butcher | Butchery | Meat | yes | All NPCs | No | 10 | 20 | 4 | 8 | 2 | 4 | Farmer, Herdsman | 2 | Vagrant | +| Merchant | Townhouse | Transport, logistics | n/a | Craftsmen, nobility | Yes | 10 | 20 | 4 | 8 | 2 | 4 | n/a | n/a | Vagrant | +| Banker | Bank | Financial services | yes | Merchant | Yes | 10 | 20 | 4 | 8 | 2 | 4 | n/a | n/a | Merchant | +| Scholar | Academy | Knowledge | n/a | Ariston, Tyrranos, General, Banker | No | 1 | 4 | 1 | 2 | 0.25 | 0.5 | n/a | n/a | Vagrant | +| Priest | Temple | Religion | n/a | All NPCs | No | 50 | 100 | | | | | | | Scholar | +| Chancellor | Chancellory | Administration | n/a | Ariston, Tyrranos | No | 1 | 1 | 0 | 0 | 0 | 0 | | | Scholar | +| Lawyer | Townhouse | Legal services | n/a | Ariston, Merchant, Banker | No | 4 | 6 | 2 | 3 | 1 | 2 | | | Scholar | +| Magus | Townhouse | Magic | n/a | Tyrranos, General | No | 3 | 4 | 1 | 2 | 0.25 | 0.5 | | | Scholar | + + + A craftsman starts as an apprentice to a master of the chosen crafts. Most crafts recruit from vagrants, A character must be a journeyman merchant before becoming an apprentice banker, while various intellectual crafts recruit from journeyman scholars. + + It's assumed that a journeyman scholar, presented with the opportunity, would prefer to become an apprentice magus than a master scholar. + + A journeyman settles and becomes a master when he finds a location with at least the solo/min number of appropriate customer type who are not serviced by another master craftsman of the same craft; he also (obviously) needs to find enough free land to set up his dwelling. The radius within which his serviced customers must live may be a fixed 10Km or it may be variable dependent on craft. If there are unserviced customers within his service radius, the master craftsman may take on apprentices and journeymen to service the additional customers up to a fixed limit – perhaps a maximum of four of each, perhaps variable by craft. If the number of customers falls, the master craftsman will first dismiss journeymen, and only in desperate circumstances dismiss apprentices. Every apprentice becomes a journeyman after three years service. + + The list of crafts given here is illustrative, not necessarily exhaustive. + +## Aristocracy + + As in the real world, aristocracy is essentially a protection racket, and all nobles are originally outlaw leaders who found an area with rich pickings and settled down. + + +| Rank | Follower rank | Client type | Clients protected | Trade in market | Followers per client | +| --- | --- | --- | --- | --- | --- | +| | | | Min | Max | Min | Max | Min | Max | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Bonnet Laird | Private | Farmer | 6 | 20 | 0 | 100 | 0.25 | 0.5 | +| Ariston | Captain | Bonnet Laird | 10 | 30 | 25 | 1000 | 0.5 | 1 | +| Tyrranos | General | Ariston | 10 | unlimited | 250 | unlimited | 0.1 | 0.5 | + + + Every noble establishes a market and, if he employs a chancellor, taxes trade in it. Crafts which 'need a market' can only be established in the vicinity of a market, irrespective of whether there are sufficient customers elsewhere. All non-perishable goods are traded through the markets, and merchants will transfer surpluses between markets if they can make a profit from it. + + My world has essentially three ranks of nobility. The title of the lowest rank will probably change to something vaguely italianate. An aristocrat advances to the next rank when either the requisite number of clients become available in the locality to support the next rank, or the trade in his market becomes sufficient to support the next rank. + + Obviously when a province has eleven unprotected bonnet lairds, under the rules given above any of them may become the ariston, and essentially it will be the next one to move after the condition becomes true. If the number of available clients drops below the minimum and the market trade also drops below the minimum, the noble sinks to a lower level – in the case of the bonnet laird, to outlaw leader. + +## Military + + The aristocracy is supported by the military. An outlaw becomes a soldier when his leader becomes a noble. Otherwise, vagrants are recruited as soldiers by bonnet lairds or sergeants who have vacancies. Captains are recruited similarly by aristons or generals, and generals are recruited by tyrranos. If the conditions for employment no longer exist, a soldier is allowed a period of unemployment while he lives off savings and finds another employer, but if no employer is found he will eventually become an outlaw (or, if an officer, an outlaw leader). A private is employed by his sergeant or bonnet laird, a sergeant by his captain, a captain by his arison or general, a general by his tyrranos. + + +| Rank | Follower rank | Followers | | Condition | New rank | +| --- | --- | --- | --- | --- | --- | +| | | Min | Max | | | +| --- | --- | --- | --- | --- | --- | +| Private | None | 0 | 0 | Battle hardened, unled privates | Sergeant | +| Sergeant | Private | 5 | 15 | More battle hardened, unled sergeantts | Captain | +| Captain | Sergeant | 5 | 15 | More battle hardened, unled captains | General | +| General | Captain | 5 | unlimited | | | + + + Soldiers have no loyalty to their employer's employer. diff --git a/doc/Settling-a-game-world.md b/doc/Settling-a-game-world.md new file mode 100644 index 0000000..e391a3c --- /dev/null +++ b/doc/Settling-a-game-world.md @@ -0,0 +1,87 @@ +# Settling a game world + +#### Wednesday, 30 December 2009 + + *This essay is part of a series with '[Worlds and Flats](Worlds-and-flats.html)' and '[The spread of knowledge in a large game world](The-spread-of-knowledge-in-a-large-game-world.html)'; if you haven't read those you may want to read them before reading this. This essay describes how a large world can come into being and can evolve. I've written again on this subject since - see '[Populating a game world](Populating-a-game-world.html)')* + +### Microworld + + Some twenty years ago I wrote a rather sophisticated cellular automaton which I called 'Microworld' which modelled the spread of human population over a landscape. It did this by first fractally folding a grid to assign elevations to cells. Then, cells below a critical elevation – the tree line – were assigned as forest. For each cycle – 'year' – a cell remained forest, its soil fertility would increase. Random events – 'lightning strikes' could change a cell from forest to clearing. Then the following transitions might take place, each with a probability, where each cell is considered to have eight neighbours: + +* A forest cell with a lightning strike as a neighbour may catch fire and burn +* A forest cell with a fire as a neighbour may catch fire and burn +* A burning cell become a clearing cell +* A clearing cell with forest or scrub as a neighbour may become scrub +* A scrub cell may become forest + + This more or less completes the 'natural' cycle... then we get to settlement. Pastoral and agrarian 1 cells gradually degrade soil fertility (erosion, etc). Agrarian 2 cells do not degrade fertility. + +* A clearing cell (including cells above the treeline) may become a pastoral cell (pastoral 1, no settlement) +* A pastoral 1 cell whose soil fertility falls below a threshhold becomes waste +* A pastoral 1 cell with no pastoral neighbours may become waste +* A waste cell below the treeline may become scrub +* A waste cell may become clearing +* A pastoral 1 cell with two or more pastoral neighbours may become a pastoral 2 cell (settlement) +* A forest cell with two or more pastoral neighbours may become clearing +* A pastoral 2 cell with two or more pastoral 2 neighbours may become agrarian 1 +* An agrarian 1 cell which falls below a critical fertility becomes pastoral 1 +* An agrarian 1 cell with three or more agrarian 1 neighbours becomes agrarian 2 (smith, mill) +* A cell with three or more agrarian 2 neighbours becomes market +* A market cell with no agrarian 2, market or urban neighbours becomes waste +* A cell with two or more market neighbours becomes urban + + That's simple, but it provides a remarkable good model of population spread. however, it is essentially a grid and so doesn't make for natural-seeming landscapes when considered as a three dimensional rendered world. How can we do better? + +### Microworld Two + + The objective of this essay is to outline an angorithm for creating inhabited landscapes in which games can be set, which are satisfyingly believable when rendered in three dimensions. The objective of creating landscapes 'procedurally' – that is, with algorithms – is that they can be very much larger than designed landscapes for the same richness of local detail. This does not mean that every aspect of the final landscape must be 'procedural'. It would be possible to use the techniques outlined here to create landscapes which were different every time the game was played, but it would be equally possible to create a landscape which was frozen at a particular point and then hand edited to add features useful to the game's plot. And while I'm principally thinking in this about role playing games, this sort of landscape would be applicable to many other sorts of games – strategy games, god games, first person shooters... + +### The physical geography + + Consider our landscape as, once again, a fractally folded sheet on which any given point has characteristics based on its elevation and orientation. There are two critical levels – water level and treeline. The water level is, overall, sea level, but in the case of a localised depression it is equal to the lowest land height between the depression and the sea (lakes form in depressions). Computing the fractal sheet forms stage one in computing the landscape. Next, we need functions which, for any given point on the landscape, compute two different dimensions of soil fertility: water and warmth. We'll assume a coriolis prevailing wind blowing from the west, bringing in damp air from an ocean in that direction. Western slopes are wetter than eastern slopes. In principle, also, there's likely to be a rain shadow to the east of high ground leading to considerable aridity, but that may be too expensive to compute. Rain runs swiftly off steeper slopes, more slowly on flatter ground, so flatter ground is wetter than steeper ground. Water flows down hill, so lower ground is on the whole wetter than higher ground. This isn't a precise model of soil hydrology, but I think it's good enough. From each lake a watercourse follows the lowest possible path to the sea. Watercourses modify the land overwhich they flow, carving out a route at least sufficient to carry the amount of water collected in the watershed above each point. Where watercourses flow down steeper gradients, they carve out gullies, possibly with waterfalls. Where they cross shallower gradients or level ground, they become broader. Computing the watercourses becomes the second stage of computing the lanscape. + +### Vegetation + + Now sprinkle seeds randomly across the landscape at a density of roughly one every ten square metres. Seeds which fall in water, ignore (? or make into water plants?). The position of the plant is taken from the random sprinkling. The species and size of the plant that grows from the plant are a function of the water and warmth functions described above, with latitude and longitude as seeds for pseudo-random functions delivering aspects like branching and so on – enough to make individual plants distinct and not carbon copies even of other plants of the same species, but nevertheless recreatable from just the latitude and longitude. So for each plant only two integers need to be stored, yet every time a player passes he will see an identically recreated world. Of course there is a trade-off between storage space and rendering time, and it may be more efficient to build and cache a detailed model of each plant. Like a lot of other things it depends on the game being designed and the processing power of the platform on which that game is delivered. As to how the functions which select the vegetation type work, obviously trees grow better in wetter places, grassland plants in dryer places; within the wetter places, coniferous trees are more prevalent where it is cooler, broadleaves where it is warmer. In the very wettest places, willows, alders and marshland plants. These plants – the seeded plants – are the feature plants of the landscape. When rendering the landscape the renderer would first apply a suitable local surface texture, for example, in grassland areas, grass. + +### Settling the world + + So now we need to make this an inhabited landscape. My proposal for this is to introduce proto-actors, which may be the same software agents as the non-player characters the user will interact with (see my essay on the spread of knowledge). At this stage in their lifecycle, the proto-actors are fairly simple state transition machines. Generally, their algorithm is as follows: Starting from one or two seed points, proto-agents will initially move across the landscape travelling at most 20Km in a day, preferring to stop at inns or else at existing settlements; and will maintain a history of the places they have been, never revisiting a place until they have settled. Whenever moving, whether before they have settled or after, proto-actors will plan their route across the landscape, avoiding trees, buildings, and steep gradients, and will prefer to cross rivers at a bridge (if available) or else a ferry (if available), or failing that at the narrowest convenient point. When proto-actors settle, they will claim an area of territory appropriate to their trade – more below; the system must build up a database of land holdings. In particular a land holding will never cross a watercourse, an existing road or overlap another land holding (although roads may develop across existing holdings). This is key because I don't want holdings normally to have regular shapes. A settled proto-agent will build a building to live in, and possibly an additional one for his trade. When building buildings, proto-actors will prefer to build at the edge of their land holding, as close as possible to existing buildings and ideally at the side of an existing road. The richer an existing building is, the more attractive it will be to new buildings. Buildings will be built with their long edge aligned with the edge of the owner's hoding. + +* A proto-actor is initially, as described above, an itinerant. Itinerants are introduced into the world at a small number of geographical locations, and gradually, not all at once. Itinerants travel as described above. As they move they will leave breadcrumb trails with a roughly ten metre resolution. If they cross an existing track which goes in roughly the right direction they will prefer to follow it. Once a track has been followed by a certain number of proto-actors, it becomes a road. +* An itinerant who finds an area of unsettled grassland of ten hectares with low soil fertility and not more than one hundred trees settles and becomes a pastoralist. He builds a cottage. +* An itinerant who finds an area of unsettled grassland of ten hectares with medium or high soil fertility becomes an agrarian. He builds a homestead. Depending on the fertility of his land he can support between zero and ten labourers, 10% of a smith, 10% of a miller and 10% of a bonnet laird. +* An itinerant who finds an area of unsettled land of 100 square metres within five hundred metres of a homestead with unfulfilled labourer demand becomes a labourer. He builds a cottage. +* An itinerant who finds an area of unsettled land of 100 square metres within five kilometres of ten farmers with unfilled smithing slots becomes a smith. He builds a cottage and a forge. +* An itinerant who finds an area of unsettled land either at the side of a water course or at the top of a hill, and within 5 kilometers of ten farmers with unfilled milling slots becomes a miller. He builds a mill – water or wind, appropriate to location. +* Any settler who plays host to more than a certain number of travellers becomes an innkeeper. He claims 400 square metres of unclaimed land as close as possible to his existing settlement and buids an inn and stableyard. +* An itinerant who finds 400 square metres of unclaimed land within a certain distance of an inn and a smith will become a merchant, provided that there are three smiths within a 5Km radius who have unfilled market slots. The merchant builds a marketplace and a counting house. +* An itinerant who finds 200 square metres of unclaimed land within a specified distance of a market with an unfilled chapel slot becomes a priest and builds a chapel and manse, and possibly a school. +* An itinerant who finds 100 square metres of unclaimed land adjacent to where a road crosses a river becomes a ferryman. +* A ferryman who carries more than a certain number of passengers in a given period becomes a tollkeeper and builds a bridge. + + This set of rules – and possibly others like them (woodcutters, fishermen, hunters...) provide the first wave of settlement. Once the landscape is sufficiently settled by this first wave, there needs to be a period of establishing trading routes. First, every settler will visit his nearest market, leaving a permanent track if there is not already a road. Where several of these tracks overlay one another, once again a road is created. Each merchant then visits each of the ten markets nearest his own, following existing tracks and roads where available. Wherever the merchants do not find roads, new roads are created. This completes the roads network. Each market is now assigned a value which is a function of + +* the number of people for whom it is the nearest market +* the sum of the wealth (soil fertility) of the homesteads for which it is the nearest market +* the wealth of other markets within a day's travel + + Depending on its wealth a market may support up to twenty stallholders, including bakers, butchers, tanners, weavers, cobblers, chandlers and so on. So a second wave of itinerants sets off. These follow the original rules for itinerants, but if they find an unsettled 100 square metres within five hundred metres of a market, will set up as a stallholder, building a town house and appropriate trade building on their own settlement, and a stall in the market. An itinerant finding a hundred square metres within five hundred metres of a market which has all its stallholder slots filled may become a slum landlord, and build a tenement for day-labourers. Finally, aristocracy. In the second wave an itinerant who finds either a hilltop, an island in a lake or river, or a significant river crossing, with one hectare of unclaimed land and within 5Km of ten farms with unfilled bonnet laird slots becomes a bonnet laird (or 'squire', if you prefer) and builds a fortified house. At the end of the second wave of settlement the ten percent of bonnet lairds with the richest fiefs (using much the same metric as for the wealth of markets) become barons and build castles. + +### Rendering the buildings + + This seems to me to provide an algorithmic means of settling a landscape which will generate organic and satisfying patterns of settlement. But it all fails if the buildings are chosen from a limited palette of models. As with the trees I think we need algorithmic mechanisms of building similar-but-different buildings which can be repeatably rendered from relatively small data sets. As an example of what I mean, in damper landscapes where wood is likely to be available, there might be a higher probability of stave buildings, or weatherboarding, with mainly shingle roofs. In slightly less damp areas where timber is still available, cruck frames and half timbered buildings will prevail, with mostly thatched roofs. In the dryest areas, cob and brick buildings will be common, often with tile roofs. On steeper hillsides, stone buildings will be common, perhaps with slate roofs. Within each of these types there are essential cells from which a building is created. These cells can be longer or shorter, taller or lower, wider or narrower. A building may comprise a single cell, or more. If more than three cells they may be arranged in a row or round a courtyard. And they may have one story or two. Which they have can be based – like the details of the plants – on functions which take latitude and longitude as arguments and which, internally use pseudo-randoms seeded from those latitude and longitude values. + +### How vast a world? + + OK, so, with this general approach, how big can we go? The answer seems to me to be 'big enough'. A 32 bit integer gives somewhat over four billion values, so can resolve down to one millimetre precision in a world 4000 kilometres by 4000 kilometres. But we don't actually need millimetre resolution; centimetre would be quite small enough. And that gives us potential for a world 40000Km square, or 1.6 billion square kilometres, which is three times the surface area of planet Earth. + + In practice we can't go that big for all sorts of space and time reasons. Recording land heights is inevitably an issue. I don't know of a pseudo random function which will generate satisfying land heights. Anything based on Julia sets, for example, ends up with landforms symmetrical around a central point. Furthermore, the shapes of fractals which can be constructed from simple functions tend to have a noticable and unnatural degree of self-similarity across scales. I'd dearly like to be wrong on this, but I think we need to store at minimum elevation values at ten metre intervals. If we can accept 100mm resolution for elevations, storing 16 bit values gives a range of 6,500 metres - 21,000 feet - from the deepest seabed to the peaks of the highest mountains. + + This means that landform information alone requires 20Kbytes per square kilometre - unindexed, but seeing it's a rigid ten metre grid that isn't a problem. Which, in turn, means that you can store landform information for a planet the size of Earth in one terrabyte. But we don't need a planet the size of earth. Scotland is 80,000 square kilometers of land area; allowing for a bit of sea around to the horizon around it, say 100,000 square kilometers. That seems to me more than big enough to be a game space. It amounts to 160Mb of landform data, which is completely manageable. + + If we stored plant data for every distinctive plant in Scotland - even at one per ten square metres - that does become an impractically large data set, because quite apart from anything else, the plant locations do have to be indexed. But actually, given that the actual plants that grow are a function of the location at which they grow, no player is going to notice if the pattern of the locations of plants is the same for each square kilometre. So we can manage plant data for a land area the size of Scotland in 400,000 bytes - we could do it in less (if the locations were generated using a pseudo-random function, much less). + + Building data is different. We need to store the latitude, longitude and type of every building explicitly, and again they need to be indexed in order that we can recover the buildings in a given area efficiently. We need about 16 bytes per building (four bytes latitude, four longitude, two type; then for each tile a null-terminated vector of pointers to building records). If we assume that our feudal land of 80,000 square kilometers has a population of a million, and that there are on average five occupants of every building, that's two hundred thousand buildings, implying 3.2Mb of data. + + Of course, that's just the backing store size. As tiles are loaded into active memory - see the essay 'Tiles and Flats' this raw backing data has to be inflated procedurally into actual models that can be rendered; models which may have thousands of vertices and hundreds of kilobytes of textures. The functions which do that inflating have some finite size, and, significantly, they'll need to work on prototype models which will in turn take storage space. Finally there are hand-edited models potentially used at particular plot locations; those need to be stored more or less in full. But all this has not become an unmanageable amount of data. It seems to me plausible that you could store a fully populated 100,000 square kilometer game world on one uncompressed 700Mb CD. On a 4Gb DVD, you could do it very easily. diff --git a/doc/The-spread-of-knowledge-in-a-large-game-world.md b/doc/The-spread-of-knowledge-in-a-large-game-world.md new file mode 100644 index 0000000..40e842b --- /dev/null +++ b/doc/The-spread-of-knowledge-in-a-large-game-world.md @@ -0,0 +1,64 @@ +# The spread of knowledge in a large game world + +#### Saturday, 26 April 2008 + +![part of the role of Dandelion, in The Witcher games, is to provide the player with news](https://4.bp.blogspot.com/-F2gxx0dRM8o/UlfSsRe8ybI/AAAAAAAAYIA/I1I9D5Yk7to/s1600/Tw2_full_Dandelion.png) + + +### Note + +_This version of this essay has been adapted to use the code in `the-great-game.gossip.news-items`, [q.v.](the-great-game.gossip.news-items.html). The original version of the essay is [still available on my blog](https://blog.journeyman.cc/2008/04/the-spread-of-knowledge-in-large-game.html)._ + + These days we have television, and news. But in a late bronze age world there are no broadcast media. News spreads by word of mouth. If non-player characters are to respond effectively to events in the world, knowledge has to spread. + + How to model this? + + Some non-player characters - doesn't need to be many - are news-spreaders. News-spreaders need to travel. They have to travel even when there are no player characters in the vicinity. But, they don't have to travel very often - once or twice every game day. When a news-spreader is in the immediate vicinity of another character, the pair may (with some degree of randomness) exchange news. There needs to be a hierarchy in the exchange of news, so that 'I-saw' events need to be more likely to be passed on than 'I-heard' events; there needs to be a counter which counts the number of times a knowledge item has been passed on, and also an age counter so that knowledge items are less likely to be passed on as they get older. + + One obvious class of news-spreader is a merchant. Merchant agents can either shuttle mechanically between a fixed group of markets or else possibly respond intelligently to supply and demand. Provided that there is a mesh of merchant routes covering the markets of the game world, and that a useful subset of non-merchant characters are required to visit a market every few game days, this should give a reasonably realistic framework for news spreading. + + What else? What things qualify as news items? I think at least the following: + +* Deaths of sentient characters, especially if violent +* Commodity prices +* Changes of rulers in cities +* Marriages of sentient characters +* Plot events, flagged as events by the game designer + + Obviously, news is more valuable if the people involved are important or notorious: the significance of a story is probably the product of the significance of the people concerned. + + So a news item becomes a tuple + + `(days-old nth-hand significance action (actors))` + + for example + + `(54 2 10 'killed '(fred joe))` + + meaning 'I spoke to a man who'd spoken to a man who said he saw notorious fred kill well-liked joe on 54 days ago'. Obviously, the non-player character must be able to construct a natural language sentence from the tuple when speaking within the hearing of a player character, but there's no need for a non-player character to produce a natural language sentence for another non-player character to parse; instead they can just exchange tuples. + + But if we're exchanging knowledge between agents, then agents must have a means of representing knowledge. This knowledge is an association between subjects and sets of statement, such that when the agent learns the statement + + `(54 2 10 'killed '(fred joe))` + + it adds this statement (with the 2 incremented to 3) to the set of statements it knows about fred and also to the set of statements it knows about joe. It's possible that the receiving agent could then challenge for further statements about fred and/or joe, the automated equivalent of a 'who's joe?' question. + + There could be feedback in this. Fred's and joe's significance scores could be incremented for each character to whom the statement is passed on, increasing the likeliness that fred, at least, would feature in more news stories in future. There needs also to be some means of managing how the non-player character's attitude to the subjects of the statement are affected. For example, If fred kills joe, and the character (say bill) receiving the news feels positively towards joe, then bill's attitude to fred should become sharply more hostile. If bill feels neutral about joe, then bill's attitude to fred should still become a bit more hostile, since killing people is on the whole a bad thing. But it bill feels very hostile towards joe, then bill's attitude to fred should become more friendly. + + Obviously the rate of decay, and the degree of randomness, of the news-passing algorithm would need to be tuned, but this schema seems to me to describe a system with the following features: + +* Non-player characters can respond to questions about significant things which happen in the world - without it all having to be scripted +* If you travel fast enough, you can keep ahead of your notoriety +* Characters on major trade routes will know more about what is happening in the world than characters in backwaters + + This seems to me a reasonably good model of news spread. + +### Scaling of the algorithm + + Let's work around the idea that a 'game day' equates to about two hours of wall clock time. Let's work around the idea that there are of the order of fifty markets in the game world, and that for each market there are two or three merchants whose 'home base' it is. + + Obviously non-player characters who are within the vicinity of a player character have to be 'awake', in order that the player can see them interacting with their world and can interact with them. Those characters have to be in working memory and have to be in the action polling loop in any case. So there's no extra cost to their gossiping away between each other - around the player there's a moving bubble of gossip, allowing each character the player interacts with to have a high probability of having some recent news. + + But the merchants who aren't in the vicinity of a player don't have to be in working memory all the time. Each merchant simply requires to be 'woken up' - loaded into memory - once per game day, move a day's journey in one hop, and then, if arriving at an inn or at a market, wake and exchange news with one resident character - an innkeeper or a gossip. So the cost of this algorithm in a fifty-market game is at worst the cost of loading and unloading two non-player characters from memory every minute, and copying two or three statements from the knowledge set of one to the knowledge set of the other. If you're dynamically modifying significance scores, of course, you'd need to also load the characters about whom news was being passed on; but this still doesn't seem unduly onerous. + + Obviously, if memory is not too constrained it may be possible to maintain all the merchants, all the innkeepers and all the characters currently being talked about in memory all the time, further reducing the cost. diff --git a/doc/Voice-acting-considered-harmful.md b/doc/Voice-acting-considered-harmful.md new file mode 100644 index 0000000..9abf923 --- /dev/null +++ b/doc/Voice-acting-considered-harmful.md @@ -0,0 +1,90 @@ +# Voice acting considered harmful + +#### Wednesday, 25 February 2015 + +![The Witcher: Conversation with Kalkstein](https://3.bp.blogspot.com/-ZI90HLjEcuo/VO4f-yXP3sI/AAAAAAAAZt4/C0hQ7hScWyM/s1600/witcher_conversation.jpg) + + Long, long, time ago, I can still remember when... we played (and wrote) adventure games where the user typed at a command line, and the system printed back at them. A Read-Eval-Print loop in the classic Lisp sense, and I wrote my adventure games in Lisp. I used the same opportunistic parser whether the developer was building the game + Create a new room north of here called dungeon-3 the player was playing the game + Pick up the rusty sword and go north or the player was talking to a non-player character + Say to the wizard 'can you tell me the way to the castle' Of course, the parser didn't 'understand' English. It worked on trees of words, in which terminal nodes were actions and branching nodes were key words, and it had the property that any word it didn't recognise at that point in sentence was a noise word and could be ignored. A few special hacks (such as 'the', 'a', or 'an' was an indicator that what came next was probably a noun phrase, and thus that if there was more than one sword in the player's immediate environment the one that was wanted was the one tagged with the adjective 'rusty'), and you ended up with a parser that most of the time convincingly interpreted most of what the player threw at it. + + Text adventures fell into desuetude partly because they weren't graphic, but mainly because people didn't find typing natural, or became dissatisfied with the repertoire of their parsers. Trying to find exactly the right combination tokens to persuade the game to carry out some simple action is not 'fun', it's just frustrating, and it turned people off. Which is a shame because just at the time when people were abandoning text adventures we were beginning to have command parsers which were actually pretty good. Mine, I think, were good - you could have a pretty natural conversation with them, and in 'building' mode, when it hit a 'sorry I don't understand' point, it allowed you to input a path of keywords and a Lisp function so that in future it would understand. + + So much, so [Eliza](http://www.csee.umbc.edu/courses/331/papers/eliza.html). + + Modern role-playing games - the evolutionary successors of those high and far off text adventures - don't have text input. Instead, at each stage in a conversation, the user is offered a choice of three or four canned responses, and can pick one; very often what the player's character actually says then differs from the text the user has chosen, often with differences of nuance which the user feels (s)he didn't intend. And the non-player-character's response is similarly canned. Indeed, the vast majority of non-player characters in most games have a 'repertoire', if one may call it that, of only one sentence. Others will have one shallow conversational tree, addressing one minor quest or plot-point. + + If you want to talk to them about anything else - well, you just can't. + + Only a very few key non-player characters will have a large repertoire of conversational trees, relevant to all parts of the plot. And even those trees are not deep. You soon exhaust them; the characters' ability to simulate real agency just isn't there. + + I first wrote about the limiting effects of voice acting in [my review of the original Witcher game](../../2008/02/the-witcher-story-telling-of-high-order.html), back in 2008; things haven't got better. + + +## On phones: speaking + + In my pocket I carry a phone. It's not big: 127 x 64.9 x 8.6mm. A small thing. + + When I first used Android phones for navigation, I used to delight in their pronunciation of Scots placenames - pronouncing them phonetically, as spelled, and as though their spelling were modern English. What's delightful about Scots placenames is that they are linguistically and orthographically so varied - their components may be Brythonic, Goidaelic, Anglian, Norn, French, English, or even Latin; and very frequently they combine elements of more than one language (Benlaw Hill, anyone? Derrywoodwachy?). + + Yes, gentle reader, this does seem a long way from game design; be patient, I'm getting there. But I'm going to digress even further for first... + + There have been orthographic changes, and pronunciation changes consequent on orthographic changes. For example, medieval Scots used the letter [Yogh](http://en.wikipedia.org/wiki/Yogh) (ȝ), which isn't present in the English alphabet. So when Edinburgh printers in the early modern period bought type for their printing presses from England, there was no Yogh in the font. So they substituted Zed. So we get names like Dalȝiel, Kirkgunȝeon, Menȝies, Cockenȝie. How do you pronounce them? + + The letter that looks like a 'z' is pronounced rather like a 'y'; so + +* Deeyell +* Kirkgunyeon +* Mingis + + and... drumroll... + +* Cockenzie. + + What happened? + + Well, Dalȝiel and Menȝies are personal names, and people are protective of their own names. Kirkgunȝeon is a small, unimportant place, and all the locals know how it is pronounced. Scots folk, are, after all, used to Scots orthography and its peculiarities. So those names haven't changed. + + But at Cockenȝie, another small, unimportant place, a nuclear power station was built. The nuclear power station was built by people (mostly) from England, who didn't know about Yogh or the peculiarities of Scots orthography - and were possibly too arrogant to care. So they called it 'Cockenzie'. And as there were so many more of them and they had so much higher status than the locals, their name stuck, and nowadays even local people mostly say 'Cockenzie', as though it were spelled with a Zed. Because, of course, it is spelled with a Zed. Because, as any British schoolchild knows, there's no Yogh in the alphabet. + + Except, of course, when there is. + + Another more interesting example of the same thing is '[Kirkcudbright](http://www.journeyman.cc/placenames/place?id=153)'. It's a town built around the kirk (church) of saint Cuthbert. So how does it come to have a 'd' in it? And why is it pronounced 'Kirkoobry'? Well, the venerable Cuthbert pronounced his name in a way which would be represented in modern English as 'Coothbrecht', but he spelled it 'Cuðbrecht'. See that 'ð'? That's not a 'd', it's an Eth. Because Cuðbrecht was Anglian, and the Anglian alphabet had [Eth](http://en.wikipedia.org/wiki/Eth); it's pronounced as a soft 'th', and Icelandic still has it (as well as Thorn, þ, a hard 'th' sound). Medieval scribes didn't know about Eth, so in copying out ð they wrote the more familiar d. The local people, however, mostly couldn't read, so the pronunciation of the name didn't change with the change in spelling (although the pronunciation, too, has drifted a little with time). + + So, in brief, pronouncing Scots placenames is hard, and there are a lot of curious rules, and consequently it's not surprising that five years ago, listening to Android's pronunciation of Scots placenames was really funny. + + But what's really curious is that now it isn't. Now, it rarely makes a mistake. Now, Android can do text to speech on unusual and perverse orthography, and get it right better than 95% of the time - and manage a reasonably natural speaking voice while doing so. On a small, low power machine which fits in my pocket. + + +## On phones: listening + + But navigation is not all I can do with my phone. I can also dictate. By which I don't mean I can make a voice recording, play it back later and type what I hear, although, of course, I can. I mean I can dictate, for example, an email, and see it in text on my phone before I send it. It quickly learned my peculiarities of diction, and it now needs very little correction. On a small, low power machine which fits in my pocket. + + +## And breathe + + Right, so where am I going with all this? Well, we interact with modern computer role playing games through very restricted, entirely scripted dialogues. Why do we do so? Why, on our modern machines with huge amounts of store, do our non-player characters - and worse still, our player character, our own avatar - have such restricted repertoires? + + Because they are voice acted. Don't get me wrong, voice acting makes a game far more engaging. But for voice acting to work, the people doing the acting have to know not only the full range of sentences that their character is going to speak, but also roughly how they feel (angry? sad? excited?) when they say it. Ten years ago, voice acting was probably the only way you could have got this immediacy into games, because ten years ago, text-to-speech systems were pretty crude - think of Stephen Hawking's voice synthesiser. But now, Edinburgh University's [open source synthesiser](http://www.cstr.ed.ac.uk/projects/festival/morevoices.html) is pretty good, and comes with twenty-four voices (and seeing it's open source, you can of course add your own). Speech to text was probably better ten years ago - think of [Dragon Naturally Speaking](http://en.wikipedia.org/wiki/Dragon_NaturallySpeaking) - but it was proprietary software, and used a fair proportion of a machine's horsepower. Now there's (among others) Carnegie Mellon's open source [Sphinx](http://cmusphinx.sourceforge.net/) engine, which can quickly adapt to your voice. + + So, we have text-to-speech engines which can generate from samples of many different voices, and speech to text engines which can easily be tuned to your particular voice. There's even a program called [Voice Attack](http://www.voiceattack.com/), built on top of Microsoft's proprietary speech to text engine, which already allows you to [control games with speech](https://www.youtube.com/watch?v=8dnJ--pSjdE). Where does that take us? + + Well, we already know how to make sophisticated natural language parsers for text, given moderately limited domains - we don't need full natural language comprehension here. + + +## You may think it's a long way down the road to the chemist + + There are things one needs to know in a game world. For example: I need a sword, where's the nearest swordsmith? In a real quasi-medieval world, certainly every soldier would be able to tell you, and everyone from the swordsmith's town or village. Very celebrated swordsmiths would be known more widely. + + And the thing is, the game engine knows where the nearest swordsmith is. It knows what potion will heal what wound, and what herbs and what tincture to use to make it. It knows which meats are good to eat, and which inns have rooms free. It knows good campsites. It knows where there be dragons. It knows where the treasure is hid. It knows - as far as the game and its plot are concerned - everything. + + So to make an in-game Siri - an omniscient companion you could ask anything of - would be easy. Trivial. It also wouldn't add verisimilitude to the game. But to model which non-player characters know what is not that much harder. Local people know what's where in their locality. Merchants know the prices in nearby markets. They, and minstrels, know the game-world's news - major events that affect the plot. Apothecaries, alchemists and witches know the properties of herbs and minerals. + + And to model which non-player characters are friendly, and willing to answer your every question; which neutral or busy, and liable to answer tersely; and which actively hostile, and likely, if they answer at all, to deliberately mislead - that's not very much harder. + + I'm not arguing that voice acting, and scripted dialogue trees, should be done away with altogether. They still have a use, as cutscenes do, to advance plot. And I'm not suggesting that we use voice to control the player characters movements and actions - I'm not not suggesting that we should say 'run north; attack the troll with the rusty sword'. Keyboards and mice may be awkward ways to control action, but they're better than that. Bur I am suggesting that one should be able to talk to any (supposedly sentient) character in the game, and have them talk reasonably sensibly back. As one can already do physically in wandering an open world, a full voice interaction system would allow one to go off piste - to leave the limited, constrained pre-scripted interaction of the voice-acted dialogue tree. And that has got to make our worlds, and our interactions with them, richer, more surprising, more engaging. + + A hybrid system needn't be hard to achieve, needn't be jarring in use. You can record the phonemes of your voice actor's voice, so that the same character will have roughly the same voice - the same timbre, the same vowel sounds, the same characteristics of  pronunciation - whether in a voice acted dialogue or in a generated one. + + We don't need to let voice acting limit the repertoires of our characters any more. And we shouldn't. diff --git a/doc/on-dying.ods b/doc/on-dying.md similarity index 100% rename from doc/on-dying.ods rename to doc/on-dying.md diff --git a/doc/sandbox.md b/doc/sandbox.md index 736f0ec..82098d6 100644 --- a/doc/sandbox.md +++ b/doc/sandbox.md @@ -31,7 +31,7 @@ Someone who intercepts and steals from merchants (and may also attack outlying f ## Second tier playable roles -The next tier of playable roles rotates around issues arising from the mercantile ecosystem. +The next tier of playable roles rotates around issues arising from the mercantile ecosystem. ### Aristocracy @@ -57,10 +57,10 @@ But nevertheless, in The Witcher 3, a decision was made to pack incident fairly 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. +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 interrupting 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. +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 there 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. diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index 4590c73..516511b 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -38,24 +38,24 @@ style="width:87.17948717948718%; float:left;"> 34
      12.82 % -65539 +66539 the-great-game.gossip.news-items
      461
      55
      -89.34 % + style="width:92.73422562141491%; + float:left;"> 485
      38
      +92.73 %
      87
      91
      9
      8
      -92.31 % -24429104 + style="width:3.8461538461538463%; + float:left;"> 4
      +96.15 % +24630104 the-great-game.merchants.markets
      the-great-game.time
      259
      5
      -98.11 % + style="width:99.62121212121212%; + float:left;"> 263
      1
      +99.62 %
      58
      1
      59
      1
      -98.33 % +100.00 % 1442160 @@ -230,9 +228,9 @@ Totals: -66.14 % +66.88 % -67.89 % +68.59 % diff --git a/docs/cloverage/the_great_game/gossip/gossip.clj.html b/docs/cloverage/the_great_game/gossip/gossip.clj.html index 069225a..8a83a2d 100644 --- a/docs/cloverage/the_great_game/gossip/gossip.clj.html +++ b/docs/cloverage/the_great_game/gossip/gossip.clj.html @@ -11,193 +11,196 @@ 002    "Interchange of news events between gossip agents"
      - 003    (:require [the-great-game.utils :refer [deep-merge]])) + 003    (:require [the-great-game.utils :refer [deep-merge]] +
      + + 004              [the-great-game.gossip.news-items :refer [learn-news-item]]))
      - 004   + 005  
      - 005  ;; Note that habitual travellers are all gossip agents; specifically, at this + 006  ;; 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  ;; stage, that means merchants. When merchants are moved we also need to
      - 007  ;; update the location of the gossip with the same key. + 008  ;; update the location of the gossip with the same key.
      - 008   + 009  
      - 009  (defn dialogue + 010  (defn dialogue
      - 010    "Dialogue between an `enquirer` and an `agent` in this `world`; returns a + 011    "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    map identical to `enquirer` except that its `:gossip` collection may have
      - 012    additional entries." + 013    additional entries."
      - 013    ;; TODO: not yet written, this is a stub. + 014    ;; TODO: not yet written, this is a stub.
      - 014    [enquirer respondent world] + 015    [enquirer respondent world]
      - 015    enquirer) + 016    enquirer)
      - 016   + 017  
      - 017  (defn gather-news + 018  (defn gather-news
      - 018    ([world] + 019    ([world]
      - 019     (reduce + 020     (reduce
      - 020       deep-merge + 021       deep-merge
      - 021       world + 022       world
      - 022       (map + 023       (map
      - 023         #(gather-news world %) + 024         #(gather-news world %)
      - 024         (keys (:gossips world))))) + 025         (keys (:gossips world)))))
      - 025    ([world gossip] + 026    ([world gossip]
      - 026     (let [g (cond (keyword? gossip) + 027     (let [g (cond (keyword? gossip)
      - 027                   (-> world :gossips gossip) + 028                   (-> world :gossips gossip)
      - 028                   (map? gossip) + 029                   (map? gossip)
      - 029                   gossip)] + 030                   gossip)]
      - 030       {:gossips + 031       {:gossips
      - 031        {(:id g) + 032        {(:id g)
      - 032         (reduce + 033         (reduce
      - 033           deep-merge + 034           deep-merge
      - 034           {} + 035           {}
      - 035           (map + 036           (map
      - 036             #(dialogue g % world) + 037             #(dialogue g % world)
      - 037             (remove + 038             (remove
      - 038               #( = g %) + 039               #( = g %)
      - 039               (filter + 040               (filter
      - 040                 #(= (:location %) (:location g)) + 041                 #(= (:location %) (:location g))
      - 041                 (vals (:gossips world))))))}}))) + 042                 (vals (:gossips world))))))}})))
      - 042   + 043  
      - 043  (defn move-gossip + 044  (defn move-gossip
      - 044    "Return a world like this `world` but with this `gossip` moved to this + 045    "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    `new-location`. Many gossips are essentially shadow-records of agents of
      - 046    other types, and the movement of the gossip should be controlled by the + 047    other types, and the movement of the gossip should be controlled by the
      - 047    run function of the type of the record they shadow. The [[#run]] function + 048    run function of the type of the record they shadow. The [[#run]] function
      - 048    below does NOT call this function." + 049    below does NOT call this function."
      - 049    [gossip world new-location] + 050    [gossip world new-location]
      - 050    (let [id (cond + 051    (let [id (cond
      - 051              (map? gossip) + 052              (map? gossip)
      - 052              (-> world :gossips gossip :id) + 053              (-> world :gossips gossip :id)
      - 053              (keyword? gossip) + 054              (keyword? gossip)
      - 054              gossip)] + 055              gossip)]
      - 055    (deep-merge + 056    (deep-merge
      - 056      world + 057      world
      - 057      {:gossips + 058      {:gossips
      - 058       {id + 059       {id
      - 059        {:location new-location}}}))) + 060        {:location new-location}}})))
      - 060   + 061  
      - 061  (defn run + 062  (defn run
      - 062    "Return a world like this `world`, with news items exchanged between gossip + 063    "Return a world like this `world`, with news items exchanged between gossip
      - 063    agents." + 064    agents."
      - 064    [world] + 065    [world]
      - 065    (gather-news world)) + 066    (gather-news world))
      diff --git a/docs/cloverage/the_great_game/gossip/news_items.clj.html b/docs/cloverage/the_great_game/gossip/news_items.clj.html index 0b410d6..6e5f68b 100644 --- a/docs/cloverage/the_great_game/gossip/news_items.clj.html +++ b/docs/cloverage/the_great_game/gossip/news_items.clj.html @@ -14,7 +14,7 @@ 003    (:require [the-great-game.world.location :refer [distance-between]]
      - 004              [the-great-game.time :refer [now]])) + 004              [the-great-game.time :refer [game-time]]))
      005   @@ -418,323 +418,329 @@ 138          #(some (fn [x] (= x location)) (:location %))
      - - 139          (:knowledge gossip))))) + + 139          (cons {:location (:home gossip)} (:knowledge gossip))))))
      140  
      + + 141  ;; (interest-in-location {:home [{0, 0} :test-home] :knowledge []} [:test-home]) +
      + + 142   +
      - 141  (defn interesting-location? + 143  (defn interesting-location?
      - 142    "True if the location of this news `item` is interesting to this `gossip`." + 144    "True if the location of this news `item` is interesting to this `gossip`."
      - 143    [gossip item] + 145    [gossip item]
      - 144    (> (interest-in-location gossip (:location item)) 1)) + 146    (> (interest-in-location gossip (:location item)) 0))
      - 145   + 147  
      - 146  (defn interesting-object? + 148  (defn interesting-object?
      - 147    [gossip object] + 149    [gossip object]
      - 148    ;; TODO: Not yet (really) implemented + 150    ;; TODO: Not yet (really) implemented
      - 149    true) + 151    true)
      - 150   + 152  
      - 151  (defn interesting-topic? + 153  (defn interesting-topic?
      - 152    [gossip topic] + 154    [gossip topic]
      - 153    ;; TODO: Not yet (really) implemented + 155    ;; TODO: Not yet (really) implemented
      - 154    true) + 156    true)
      - 155   + 157  
      - 156  (defn interesting-item? + 158  (defn interesting-item?
      - 157    "True if anything about this news `item` is interesting to this `gossip`." + 159    "True if anything about this news `item` is interesting to this `gossip`."
      - 158    [gossip item] + 160    [gossip item]
      - 159       (or + 161       (or
      - 160         (interesting-character? gossip (:actor item)) + 162         (interesting-character? gossip (:actor item))
      - 161         (interesting-character? gossip (:other item)) + 163         (interesting-character? gossip (:other item))
      - 162         (interesting-location? gossip (:location item)) + 164         (interesting-location? gossip (:location item))
      - 163         (interesting-object? gossip (:object item)) + 165         (interesting-object? gossip (:object item))
      - 164         (interesting-topic? gossip (:verb item)))) + 166         (interesting-topic? gossip (:verb item))))
      - 165   + 167  
      - 166  (defn infer + 168  (defn infer
      - 167    "Infer a new knowledge item from this `item`, following this `rule`" + 169    "Infer a new knowledge item from this `item`, following this `rule`"
      - 168    [item rule] + 170    [item rule]
      - 169    (reduce merge + 171    (reduce merge
      - 170            item + 172            item
      - 171            (cons + 173            (cons
      - 172              {:verb (:verb rule)} + 174              {:verb (:verb rule)}
      - 173              (map (fn [k] {k (apply (k rule) (list item))}) + 175              (map (fn [k] {k (apply (k rule) (list item))})
      - 174                   (remove + 176                   (remove
      - 175                     #(= % :verb) + 177                     #(= % :verb)
      - 176                     (keys rule)))))) -
      - - 177   -
      - - 178  (declare learn-news-item) + 178                     (keys rule))))))
      179  
      - - 180  (defn make-all-inferences -
      - - 181    "Return a list of knowledge entries inferred from this news `item` by this -
      - - 182    `gossip`." -
      - - 183    [item] -
      - 184    (set + 180  (declare learn-news-item)
      - - 185      (reduce + + 181  
      - 186        concat + 182  (defn make-all-inferences +
      + + 183    "Return a list of knowledge entries inferred from this news `item` by this +
      + + 184    `gossip`." +
      + + 185    [item] +
      + + 186    (set +
      + + 187      (reduce +
      + + 188        concat
      - 187        (map + 189        (map
      - 188          #(:knowledge (learn-news-item {} (infer item %) false)) + 190          #(:knowledge (learn-news-item {} (infer item %) false))
      - 189          (:inferences (news-topics (:verb item))))))) + 191          (:inferences (news-topics (:verb item)))))))
      - 190   + 192  
      - 191  (defn degrade-character + 193  (defn degrade-character
      - 192    "Return a character specification like this `character`, but comprising + 194    "Return a character specification like this `character`, but comprising
      - 193    only those properties this `gossip` is interested in." + 195    only those properties this `gossip` is interested in."
      - 194    [gossip character] + 196    [gossip character]
      - 195    ;; TODO: Not yet (really) implemented + 197    ;; TODO: Not yet (really) implemented
      - 196    character) + 198    character)
      - 197   + 199  
      - 198  (defn degrade-location + 200  (defn degrade-location
      - 199    "Return a location specification like this `location`, but comprising + 201    "Return a location specification like this `location`, but comprising
      - 200    only those elements this `gossip` is interested in. If none, return + 202    only those elements this `gossip` is interested in. If none, return
      - 201    `nil`." + 203    `nil`."
      - 202    [gossip location] + 204    [gossip location]
      - 203    (let [l (if + 205    (let [l (if
      - 204      (coll? location) + 206      (coll? location)
      - 205      (filter + 207      (filter
      - 206        #(when (interesting-location? gossip %) %) + 208        #(when (interesting-location? gossip %) %)
      - 207        location))] + 209        location))]
      - 208      (when-not (empty? l) l))) + 210      (when-not (empty? l) l)))
      - 209   + 211  
      - 210  (defn learn-news-item + 212  (defn learn-news-item
      - 211    "Return a gossip like this `gossip`, which has learned this news `item` if + 213    "Return a gossip like this `gossip`, which has learned this news `item` if
      - 212    it is of interest to them." + 214    it is of interest to them."
      - 213    ;; TODO: Not yet implemented + 215    ;; TODO: Not yet implemented
      - 214    ([gossip item] + 216    ([gossip item]
      - - 215     (learn-news-item gossip item true)) + + 217     (learn-news-item gossip item true))
      - 216    ([gossip item follow-inferences?] + 218    ([gossip item follow-inferences?]
      - 217     (if + 219     (if
      - 218       (interesting-item? gossip item) + 220       (interesting-item? gossip item)
      - 219       (let [g (assoc gossip :knowledge + 221       (let [g (assoc gossip :knowledge
      - 220                 (cons + 222                 (cons
      - 221                   (assoc + 223                   (assoc
      - 222                     item + 224                     item
      - 223                     :nth-hand (if + 225                     :nth-hand (if
      - 224                                 (number? (:nth-hand item)) + 226                                 (number? (:nth-hand item))
      - 225                                 (inc (:nth-hand item)) + 227                                 (inc (:nth-hand item))
      - 226                                 1) + 228                                 1)
      - 227                     :date (if (number? (:date item)) (:date item) (now)) + 229                     :date (if (number? (:date item)) (:date item) (game-time))
      - 228                     :location (degrade-location gossip (:location item)) + 230                     :location (degrade-location gossip (:location item))
      - 229                     ;; ought to degratde the location + 231                     ;; ought to degratde the location
      - 230                     ;; ought to maybe-degrade characters we're not yet interested in + 232                     ;; ought to maybe-degrade characters we're not yet interested in
      - 231                     ) + 233                     )
      - 232                   ;; ought not to add knowledge items we already have, except + 234                   ;; ought not to add knowledge items we already have, except
      - 233                   ;; to replace if new item is of increased specificity + 235                   ;; to replace if new item is of increased specificity
      - 234                   (:knowledge gossip)))] + 236                   (:knowledge gossip)))]
      - 235         (if follow-inferences? + 237         (if follow-inferences?
      - - 236           (assoc -
      - - 237             g -
      - - 238             :knowledge -
      - - 239             (concat (:knowledge g) (make-all-inferences item))) + + 238           (assoc
      - 240           g)) + 239             g +
      + + 240             :knowledge +
      + + 241             (concat (:knowledge g) (make-all-inferences item))) +
      + + 242           g))
      - 241       gossip))) -
      - - 242   -
      - - 243   + 243       gossip)))
      244  
      + + 245   +
      + + 246   +
      diff --git a/docs/cloverage/the_great_game/time.clj.html b/docs/cloverage/the_great_game/time.clj.html index 8822abe..2c7d56e 100644 --- a/docs/cloverage/the_great_game/time.clj.html +++ b/docs/cloverage/the_great_game/time.clj.html @@ -244,7 +244,7 @@ 080    passed (as a `long`), the game time represented by that value."
      - + 081    ([] (game-time (now)))
      diff --git a/docs/codox/Baking-the-world.html b/docs/codox/Baking-the-world.html new file mode 100644 index 0000000..5924b7f --- /dev/null +++ b/docs/codox/Baking-the-world.html @@ -0,0 +1,54 @@ + +Baking the world

      Baking the world

      +

      Wednesday, 8 May 2019

      +

      Devogilla’s Bridge in Dumfries, early foourteenth century

      +

      In previous posts, I’ve described algorithms for dynamically populating and dynamically settling a game world. But at kilometre scale (and I think we need a higher resolution than that - something closer to hectare scale), settling the British Isles using my existing algorithms takes about 24 hours of continuous compute on an eight core, 3GHz machine. You cannot do that every time you launch a new game.

      +

      So the game development has to run in four phases: the first three phases happen during development, to create a satisfactory, already populated and settled, initial world for the game to start from. This is particularly necessary if hand-crafted buildings and environments are going to be added to the world; the designers of those buildings and environments have to be able to see the context into which their models must fit.

      +

      Phase one: proving - the procedural world

      +

      I’m going to call the initial phase of the game run - the phase which takes place before the quest team write their quests and the art department adds their hand-crafted models - ‘proving’, as when dough has been been made and set aside to rise.

      +

      Then, when the landscape has developed - the areas of forest, scrub, open meadow, moorland, savanah and desert are determined, the rivers plotted, the settlers moved in, their trades determined and their settlements allocated, the roadways which link settlements routed, river crossings and ports defined - the proving process ends, and the world is turned over to the plot-writers, quest builders and designers, for a process we can see as analogous to kneading.

      +

      But, before going there, to summarise the proving stage. The inputs are:

      +
        +
      1. A raster height map (although this could be randomly generated using any one of many fractal algorithms) - this probably uses ideas from tessellated multi-layer height map;
      2. +
      3. Optionally, a raster rainfall map at 1km resolution (although my personal preference is that this should be generated procedurally from the height map).
      4. +
      +

      The outputs are

      +
        +
      1. A vector drainage map (rivers);
      2. +
      3. A raster biome map at roughly 1 km resolution (it might be anything between hectare resolution and 1Km resolution,  but obviously higher resolution takes more storage);
      4. +
      5. A database of settlers and their settlements, such that the settlements have x,y co-ordinates;
      6. +
      7. A vector road map.
      8. +
      +

      In this sense, the ‘biome map’ is just the end state of a Microworld run. The ‘biomes’ include things like ‘forest’, ‘scrub’, ‘heath’, ‘pasture’, but they may also include human settlement, and even settlement by different cultural groups.

      +

      This gives us all we need to vegetate and furnish the world. When rendering each square metre we have

      +
        +
      1. The x,y coordinates, obviously;
      2. +
      3. The altitude, taken from the height map;
      4. +
      5. The biome, taken from the biome map;
      6. +
      7. The biomes of adjacent cells in the biome map;
      8. +
      9. The proximity of the nearest watercourse;
      10. +
      11. The proximity of the nearest road or pathway;
      12. +
      13. Whether we are inside, or outside, a settlement (where for these purposes, ‘settlement’ includes enclosed field), and if inside, what type of settlement it is.
      14. +
      +

      Given these parameters, and using the x, y coordinates as seed of a deterministic pseudo-random number generator, we can generate appropriate vegetation and buildings to render a believable world. The reason for pulling adjacent biomes into the renderer is that sharp transitions from one biome to another - especially ones which align to a rectangular grid - rarely exist in nature, and that consequently most transitions from one biome to another should be gradual.

      +

      Note that proving, although extremely compute intensive, is not necessarily a one-time job. If the designers aren’t satisfied with the first world to emerge from this process, they can run it again, and again, to generate a world with which they are satisfied. It’s also possible to hand-edit the output of proving, if needed.

      +

      But now, designers and story-writers can see the world in which their creations will be set.

      +

      Phase two: kneading - making the world fit our needs

      +

      Enough of proving, let’s get on to kneading.

      +

      Hand-designed buildings and environments are likely to be needed, or at least useful, for plot; also, particularly, very high status buildings are probably better hand designed. I’m inclined to think that less is more here, for two reasons:

      +

      You cannot hand design a very large world, it’s just impossible. How CD Project Red managed with Witcher 3 I don’t know, since I understand that is largely hand designed; but that was a very large team, and even so it isn’t a world on the scale I’m envisaging.

      +

      Procedurally generated models take a wee bit of compute power to reify, but not a huge amount, and they’re trivial to store - you need one single birch leaf model and one single birch-bark texture generator to make every birch tree in the game, and probably a single parameterised tree function can draw every tree of every species (and quite a lot of shrubs and ground-cover plants, too). But once reified, they take no longer to render than a manually crafted model.

      +

      By contrast, a manually crafted model will take a very great deal more space to store, such that being able to render a large world from hand crafted models, without excessive model re-use, isn’t going to be possible.

      +

      So it’s better in my opinion to put effort into good procedural generation functions, not just for foliage but also for buildings. My reason for using a picture of a medieval bridge at the head of the essay is to illustrate exactly this point: even in the medieval period, bridges comprise a series of repeating modules. Take one arch module and one ramp module from Devorgilla’s bridge as models, add texture skins for several different stone types, stretch the modules a little in whatever dimension is needed, and repeat the arch module as many times as needed, and you can create a range of bridges to span many different rivers - which will all be visibly similar, but that’s fine, that’s the nature of a traditional culture - but each slightly different.

      +

      Take half a dozen sets of models - timber bridges for forested biomes, brick bridges for biomes without stone or timber - and you can build procedural bridges across a whole continent without ever exactly repeating yourself.

      +

      However, in some places the designers and story writers will want, for plot reasons and to create iconic environments, to add models. I’m inclined not to over do this, both for reasons of development effort and for reasons of storage cost, but they will. Very high status buildings may need to be unique and distinctive, for example. These need to be designed and their locations and spatial dimensions added to the database, so that the models can be rendered in the right positions (and, critically, procedurally generated models can be omitted in those positions!)

      +

      Story and quest writers will also want characters for their plots. While there’s no reason why writers cannot add entirely new characters to the database, there’s no reason why they cannot incorporate characters generated in the settlement phase into the story; for this reason, characters need to be able to be tagged in the database as plot characters, and with what quests/elements of the plot they’re associated.

      +

      This allows a mechanism to prevent a plot character from being killed by another non-player character, or dying of disease or starvation, before the plot elements in which they feature have been completed.

      +

      Phase three: baking - making it delicious

      +

      Once the world has been populated, settled, vegetated, the story has been written, the models built, the quests designed, there is probably a process of optimisation - stripping out things which aren’t needed at play time, streamlining things that are - before you have a game ready to ship; but really I haven’t yet given that much thought.

      +

      Phase four: eating!

      +

      At the end, though, you have a game, and a player plays it. How much of the dynamic, organic life that brought the game through proving continues on into the playing phase? If the gossip ideas are to work, if unscripted, non-plot-related events (as well as scripted, plot related events) are to happen while the player plays, if news of these events is to percolate through the world and reach the player in organic, unscripted ways, if a lot of the emergent gameplay I’m imagining is to work, then quite a lot of the dynamic things must be happening.

      +

      Of course, part of this depends on the length of ‘game world time’ is expected to elapse in the course of one play through of the game. If it’s less than a year, then you don’t need children dynamically being born, and characters dynamically growing older; but if more, then you do. Similarly, you don’t need a real simulation of trading to dynamically drive prices in markets, but for a fun trading sub-game to emerge, you probably do, and if you are using merchants as news spreading agents the additional compute cost is not high.

      +

      And I understand that many game writers will shudder at the thought that a war might (or might not) start in the middle of their plot, that a battle might, one time in a thousand, take place right where they’ve plotted some significant encounter. Most modern video games are essentially just very complicated state machines: if you make this sequence of choices, this outcome will happen, guaranteed. Or else they’re puddles of random soup, where everything that happens is more or less driven by a random number generator. What I’m envisaging is something quite different: a world in which traders gonna trade, robbers gonna rob, lovers gonna love, scandal-mongers gonna make scandal, organically and dynamically whether the player is there or not, and news of these events will filter through to the player through the gossip network also organically and dynamically.

      +

      A world, in short, through which no two runs will ever be the same, in which interesting bits of story will happen with no-one directing or scripting them. And for that to work, some of the same dynamic processes that drove the proving phase have to continue into the eating phase.

      \ No newline at end of file diff --git a/docs/codox/Populating-a-game-world.html b/docs/codox/Populating-a-game-world.html new file mode 100644 index 0000000..8a50174 --- /dev/null +++ b/docs/codox/Populating-a-game-world.html @@ -0,0 +1,297 @@ + +Populating a game world

      Populating a game world

      +

      Saturday, 6 July 2013

      +

      (You might want to read this essay in conjunction with my older essay, Settling a game world, which covers similar ground but which this hopefully advances on)

      +

      For an economy to work people have to be able to move between occupations to fill economic niches. In steady state, non player character (NPC) males become adult as ‘vagrants’, and then move through the state transitions described in this document. The pattern for females is different.

      +

      Basic occupations

      +

      The following are ‘unskilled’ occupations which form the base of the occupation system. Generally a male character at maturity becomes a ‘Vagrant’ and wanders though the world until he encounters a condition which allows him to advance up the occupation graph. If an occupation wholly fails, the character can revert to being a ‘Vagrant’ and start again.

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Occupation Dwelling condition New trade Notes
      Vagrant None land available and animals available Herdsman
      Vagrant None arable land available Farmer See crops
      Vagrant None has weapons Outlaw
      Herdsman None Insufficient food Vagrant
      Farmer Farm Insufficient food Vagrant
      Outlaw None loses weapons Vagrant
      Vagrant None craftsman willing to take on apprentice Apprentice
      Herdsman None arable land available Farmer
      Outlaw None Battle hardened OutlawLeader
      Apprentice (craftsman’s) Qualified Journeyman
      Journeyman None Unserviced customers available Craftsman See crafts
      Craftsman See crafts Too few customers Journeyman
      Journeyman None arable land available Farmer
      Vagrant None Lord with vacancies available Soldier See military
      OutlawLeader None Unprotected farms available Laird See nobility
      +

      Gender dimorphism

      +

      In the paragraph above I said ‘a male character’. It may seem unfair to create a game world in which the sexual inequality of the real world is carried over, and for that reason it seems sensible that female children should have the same opportunities as male children. But games work on conflicts and injustices, and so it seems reasonable to me to have a completely different occupation graph for women. I haven’t yet drawn that up.

      +

      Wandering

      +

      Vagrants wander in a fairly random way. While vagrants are wandering they are assumed to live off the land and require no resources. Solitary outlaws similarly wander until they find a leader, although they will avoid the areas protected by nobles. Herdsmen also wander but only over unenclosed pasture. They visit markets, if available, periodically; otherwise, they live off their herds. Journeymen wander from market to market, but are assumed to trade skills with farmers along the way.

      +

      Crafts

      +

      Crafts are occupations which require acquired skills. In the initial seeding of the game world there are probably ‘pioneers’, who are special vagrants who, on encountering the conditions for a particular craft to thrive, instantly become masters of that craft.

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Craft Dwelling Supplies Perishable? Customer types Needs market? Customers Supplier Suppliers Recruits
      Solo Per journeyman Per apprentice
      +

      | | | | | | | — | — | — | | | | | | | | | | | Min | Max | Min | Max | Min | Max | | | | | — | | | | | | — | — | — | — | — | — | | | | | Smith | Forge | Metal Items | no | Farmer, Soldier | No | 6 | 10 | 4 | 6 | 1 | 3 | Miner | 1 | Vagrant | | Baker | Bakery | Bread | yes | All NPCs | No | 20 | 30 | 12 | 18 | 6 | 10 | Miller | 1 | Vagrant | | Miller | Mill | Flour, meal | no | Baker, Innkeeper | No | 2 | 3 | 1 | 2 | 1 | 1 | Farmer | 6 | Vagrant | | Weaver | Weaver’s house | Cloth | no | All NPCs | Yes | 6 | 10 | 4 | 6 | 1 | 3 | Herdsman | 2 | Vagrant | | Innkeeper | Inn | Food, hospitality | yes | Merhant, Soldier, Farmer, Lord | No | 10 | 20 | 5 | 10 | 2 | 4 | Farmer,Herdsman | 2 | Vagrant | | Miner | Mine | Ores | no | Smith | Yes | 2 | 3 | 1 | 2 | 1 | 1 | Farmer | 1 | Vagrant | | Butcher | Butchery | Meat | yes | All NPCs | No | 10 | 20 | 4 | 8 | 2 | 4 | Farmer, Herdsman | 2 | Vagrant | | Merchant | Townhouse | Transport, logistics | n/a | Craftsmen, nobility | Yes | 10 | 20 | 4 | 8 | 2 | 4 | n/a | n/a | Vagrant | | Banker | Bank | Financial services | yes | Merchant | Yes | 10 | 20 | 4 | 8 | 2 | 4 | n/a | n/a | Merchant | | Scholar | Academy | Knowledge | n/a | Ariston, Tyrranos, General, Banker | No | 1 | 4 | 1 | 2 | 0.25 | 0.5 | n/a | n/a | Vagrant | | Priest | Temple | Religion | n/a | All NPCs | No | 50 | 100 | | | | | | | Scholar | | Chancellor | Chancellory | Administration | n/a | Ariston, Tyrranos | No | 1 | 1 | 0 | 0 | 0 | 0 | | | Scholar | | Lawyer | Townhouse | Legal services | n/a | Ariston, Merchant, Banker | No | 4 | 6 | 2 | 3 | 1 | 2 | | | Scholar | | Magus | Townhouse | Magic | n/a | Tyrranos, General | No | 3 | 4 | 1 | 2 | 0.25 | 0.5 | | | Scholar |

      +

      A craftsman starts as an apprentice to a master of the chosen crafts. Most crafts recruit from vagrants, A character must be a journeyman merchant before becoming an apprentice banker, while various intellectual crafts recruit from journeyman scholars.

      +

      It’s assumed that a journeyman scholar, presented with the opportunity, would prefer to become an apprentice magus than a master scholar.

      +

      A journeyman settles and becomes a master when he finds a location with at least the solo/min number of appropriate customer type who are not serviced by another master craftsman of the same craft; he also (obviously) needs to find enough free land to set up his dwelling. The radius within which his serviced customers must live may be a fixed 10Km or it may be variable dependent on craft. If there are unserviced customers within his service radius, the master craftsman may take on apprentices and journeymen to service the additional customers up to a fixed limit – perhaps a maximum of four of each, perhaps variable by craft. If the number of customers falls, the master craftsman will first dismiss journeymen, and only in desperate circumstances dismiss apprentices. Every apprentice becomes a journeyman after three years service.

      +

      The list of crafts given here is illustrative, not necessarily exhaustive.

      +

      Aristocracy

      +

      As in the real world, aristocracy is essentially a protection racket, and all nobles are originally outlaw leaders who found an area with rich pickings and settled down.

      + + + + + + + + + + + + + + + + + + + + + + + + +
      Rank Follower rank Client type Clients protected Trade in market Followers per client
      Min Max Min Max Min Max
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Bonnet Laird Private Farmer 6 20 0 100 0.25 0.5
      Ariston Captain Bonnet Laird 10 30 25 1000 0.5 1
      Tyrranos General Ariston 10 unlimited 250 unlimited 0.1 0.5
      +

      Every noble establishes a market and, if he employs a chancellor, taxes trade in it. Crafts which ‘need a market’ can only be established in the vicinity of a market, irrespective of whether there are sufficient customers elsewhere. All non-perishable goods are traded through the markets, and merchants will transfer surpluses between markets if they can make a profit from it.

      +

      My world has essentially three ranks of nobility. The title of the lowest rank will probably change to something vaguely italianate. An aristocrat advances to the next rank when either the requisite number of clients become available in the locality to support the next rank, or the trade in his market becomes sufficient to support the next rank.

      +

      Obviously when a province has eleven unprotected bonnet lairds, under the rules given above any of them may become the ariston, and essentially it will be the next one to move after the condition becomes true. If the number of available clients drops below the minimum and the market trade also drops below the minimum, the noble sinks to a lower level – in the case of the bonnet laird, to outlaw leader.

      +

      Military

      +

      The aristocracy is supported by the military. An outlaw becomes a soldier when his leader becomes a noble. Otherwise, vagrants are recruited as soldiers by bonnet lairds or sergeants who have vacancies. Captains are recruited similarly by aristons or generals, and generals are recruited by tyrranos. If the conditions for employment no longer exist, a soldier is allowed a period of unemployment while he lives off savings and finds another employer, but if no employer is found he will eventually become an outlaw (or, if an officer, an outlaw leader). A private is employed by his sergeant or bonnet laird, a sergeant by his captain, a captain by his arison or general, a general by his tyrranos.

      + + + + + + + + + + + + + + + + + + + + + +
      Rank Follower rank Followers Condition New rank
      Min Max
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Private None 0 0 Battle hardened, unled privates Sergeant
      Sergeant Private 5 15 More battle hardened, unled sergeantts Captain
      Captain Sergeant 5 15 More battle hardened, unled captains General
      General Captain 5 unlimited
      +

      Soldiers have no loyalty to their employer’s employer.

      \ No newline at end of file diff --git a/docs/codox/Settling-a-game-world.html b/docs/codox/Settling-a-game-world.html new file mode 100644 index 0000000..f7b58df --- /dev/null +++ b/docs/codox/Settling-a-game-world.html @@ -0,0 +1,68 @@ + +Settling a game world

      Settling a game world

      +

      Wednesday, 30 December 2009

      +

      This essay is part of a series with ‘Worlds and Flats’ and ‘The spread of knowledge in a large game world’; if you haven’t read those you may want to read them before reading this. This essay describes how a large world can come into being and can evolve. I’ve written again on this subject since - see ‘Populating a game world’)

      +

      Microworld

      +

      Some twenty years ago I wrote a rather sophisticated cellular automaton which I called ‘Microworld’ which modelled the spread of human population over a landscape. It did this by first fractally folding a grid to assign elevations to cells. Then, cells below a critical elevation – the tree line – were assigned as forest. For each cycle – ‘year’ – a cell remained forest, its soil fertility would increase. Random events – ‘lightning strikes’ could change a cell from forest to clearing. Then the following transitions might take place, each with a probability, where each cell is considered to have eight neighbours:

      +
        +
      • A forest cell with a lightning strike as a neighbour may catch fire and burn
      • +
      • A forest cell with a fire as a neighbour may catch fire and burn
      • +
      • A burning cell become a clearing cell
      • +
      • A clearing cell with forest or scrub as a neighbour may become scrub
      • +
      • A scrub cell may become forest
      • +
      +

      This more or less completes the ‘natural’ cycle… then we get to settlement. Pastoral and agrarian 1 cells gradually degrade soil fertility (erosion, etc). Agrarian 2 cells do not degrade fertility.

      +
        +
      • A clearing cell (including cells above the treeline) may become a pastoral cell (pastoral 1, no settlement)
      • +
      • A pastoral 1 cell whose soil fertility falls below a threshhold becomes waste
      • +
      • A pastoral 1 cell with no pastoral neighbours may become waste
      • +
      • A waste cell below the treeline may become scrub
      • +
      • A waste cell may become clearing
      • +
      • A pastoral 1 cell with two or more pastoral neighbours may become a pastoral 2 cell (settlement)
      • +
      • A forest cell with two or more pastoral neighbours may become clearing
      • +
      • A pastoral 2 cell with two or more pastoral 2 neighbours may become agrarian 1
      • +
      • An agrarian 1 cell which falls below a critical fertility becomes pastoral 1
      • +
      • An agrarian 1 cell with three or more agrarian 1 neighbours becomes agrarian 2 (smith, mill)
      • +
      • A cell with three or more agrarian 2 neighbours becomes market
      • +
      • A market cell with no agrarian 2, market or urban neighbours becomes waste
      • +
      • A cell with two or more market neighbours becomes urban
      • +
      +

      That’s simple, but it provides a remarkable good model of population spread. however, it is essentially a grid and so doesn’t make for natural-seeming landscapes when considered as a three dimensional rendered world. How can we do better?

      +

      Microworld Two

      +

      The objective of this essay is to outline an angorithm for creating inhabited landscapes in which games can be set, which are satisfyingly believable when rendered in three dimensions. The objective of creating landscapes ‘procedurally’ – that is, with algorithms – is that they can be very much larger than designed landscapes for the same richness of local detail. This does not mean that every aspect of the final landscape must be ‘procedural’. It would be possible to use the techniques outlined here to create landscapes which were different every time the game was played, but it would be equally possible to create a landscape which was frozen at a particular point and then hand edited to add features useful to the game’s plot. And while I’m principally thinking in this about role playing games, this sort of landscape would be applicable to many other sorts of games – strategy games, god games, first person shooters…

      +

      The physical geography

      +

      Consider our landscape as, once again, a fractally folded sheet on which any given point has characteristics based on its elevation and orientation. There are two critical levels – water level and treeline. The water level is, overall, sea level, but in the case of a localised depression it is equal to the lowest land height between the depression and the sea (lakes form in depressions). Computing the fractal sheet forms stage one in computing the landscape. Next, we need functions which, for any given point on the landscape, compute two different dimensions of soil fertility: water and warmth. We’ll assume a coriolis prevailing wind blowing from the west, bringing in damp air from an ocean in that direction. Western slopes are wetter than eastern slopes. In principle, also, there’s likely to be a rain shadow to the east of high ground leading to considerable aridity, but that may be too expensive to compute. Rain runs swiftly off steeper slopes, more slowly on flatter ground, so flatter ground is wetter than steeper ground. Water flows down hill, so lower ground is on the whole wetter than higher ground. This isn’t a precise model of soil hydrology, but I think it’s good enough. From each lake a watercourse follows the lowest possible path to the sea. Watercourses modify the land overwhich they flow, carving out a route at least sufficient to carry the amount of water collected in the watershed above each point. Where watercourses flow down steeper gradients, they carve out gullies, possibly with waterfalls. Where they cross shallower gradients or level ground, they become broader. Computing the watercourses becomes the second stage of computing the lanscape.

      +

      Vegetation

      +

      Now sprinkle seeds randomly across the landscape at a density of roughly one every ten square metres. Seeds which fall in water, ignore (? or make into water plants?). The position of the plant is taken from the random sprinkling. The species and size of the plant that grows from the plant are a function of the water and warmth functions described above, with latitude and longitude as seeds for pseudo-random functions delivering aspects like branching and so on – enough to make individual plants distinct and not carbon copies even of other plants of the same species, but nevertheless recreatable from just the latitude and longitude. So for each plant only two integers need to be stored, yet every time a player passes he will see an identically recreated world. Of course there is a trade-off between storage space and rendering time, and it may be more efficient to build and cache a detailed model of each plant. Like a lot of other things it depends on the game being designed and the processing power of the platform on which that game is delivered. As to how the functions which select the vegetation type work, obviously trees grow better in wetter places, grassland plants in dryer places; within the wetter places, coniferous trees are more prevalent where it is cooler, broadleaves where it is warmer. In the very wettest places, willows, alders and marshland plants. These plants – the seeded plants – are the feature plants of the landscape. When rendering the landscape the renderer would first apply a suitable local surface texture, for example, in grassland areas, grass.

      +

      Settling the world

      +

      So now we need to make this an inhabited landscape. My proposal for this is to introduce proto-actors, which may be the same software agents as the non-player characters the user will interact with (see my essay on the spread of knowledge). At this stage in their lifecycle, the proto-actors are fairly simple state transition machines. Generally, their algorithm is as follows: Starting from one or two seed points, proto-agents will initially move across the landscape travelling at most 20Km in a day, preferring to stop at inns or else at existing settlements; and will maintain a history of the places they have been, never revisiting a place until they have settled. Whenever moving, whether before they have settled or after, proto-actors will plan their route across the landscape, avoiding trees, buildings, and steep gradients, and will prefer to cross rivers at a bridge (if available) or else a ferry (if available), or failing that at the narrowest convenient point. When proto-actors settle, they will claim an area of territory appropriate to their trade – more below; the system must build up a database of land holdings. In particular a land holding will never cross a watercourse, an existing road or overlap another land holding (although roads may develop across existing holdings). This is key because I don’t want holdings normally to have regular shapes. A settled proto-agent will build a building to live in, and possibly an additional one for his trade. When building buildings, proto-actors will prefer to build at the edge of their land holding, as close as possible to existing buildings and ideally at the side of an existing road. The richer an existing building is, the more attractive it will be to new buildings. Buildings will be built with their long edge aligned with the edge of the owner’s hoding.

      +
        +
      • A proto-actor is initially, as described above, an itinerant. Itinerants are introduced into the world at a small number of geographical locations, and gradually, not all at once. Itinerants travel as described above. As they move they will leave breadcrumb trails with a roughly ten metre resolution. If they cross an existing track which goes in roughly the right direction they will prefer to follow it. Once a track has been followed by a certain number of proto-actors, it becomes a road.
      • +
      • An itinerant who finds an area of unsettled grassland of ten hectares with low soil fertility and not more than one hundred trees settles and becomes a pastoralist. He builds a cottage.
      • +
      • An itinerant who finds an area of unsettled grassland of ten hectares with medium or high soil fertility becomes an agrarian. He builds a homestead. Depending on the fertility of his land he can support between zero and ten labourers, 10% of a smith, 10% of a miller and 10% of a bonnet laird.
      • +
      • An itinerant who finds an area of unsettled land of 100 square metres within five hundred metres of a homestead with unfulfilled labourer demand becomes a labourer. He builds a cottage.
      • +
      • An itinerant who finds an area of unsettled land of 100 square metres within five kilometres of ten farmers with unfilled smithing slots becomes a smith. He builds a cottage and a forge.
      • +
      • An itinerant who finds an area of unsettled land either at the side of a water course or at the top of a hill, and within 5 kilometers of ten farmers with unfilled milling slots becomes a miller. He builds a mill – water or wind, appropriate to location.
      • +
      • Any settler who plays host to more than a certain number of travellers becomes an innkeeper. He claims 400 square metres of unclaimed land as close as possible to his existing settlement and buids an inn and stableyard.
      • +
      • An itinerant who finds 400 square metres of unclaimed land within a certain distance of an inn and a smith will become a merchant, provided that there are three smiths within a 5Km radius who have unfilled market slots. The merchant builds a marketplace and a counting house.
      • +
      • An itinerant who finds 200 square metres of unclaimed land within a specified distance of a market with an unfilled chapel slot becomes a priest and builds a chapel and manse, and possibly a school.
      • +
      • An itinerant who finds 100 square metres of unclaimed land adjacent to where a road crosses a river becomes a ferryman.
      • +
      • A ferryman who carries more than a certain number of passengers in a given period becomes a tollkeeper and builds a bridge.
      • +
      +

      This set of rules – and possibly others like them (woodcutters, fishermen, hunters…) provide the first wave of settlement. Once the landscape is sufficiently settled by this first wave, there needs to be a period of establishing trading routes. First, every settler will visit his nearest market, leaving a permanent track if there is not already a road. Where several of these tracks overlay one another, once again a road is created. Each merchant then visits each of the ten markets nearest his own, following existing tracks and roads where available. Wherever the merchants do not find roads, new roads are created. This completes the roads network. Each market is now assigned a value which is a function of

      +
        +
      • the number of people for whom it is the nearest market
      • +
      • the sum of the wealth (soil fertility) of the homesteads for which it is the nearest market
      • +
      • the wealth of other markets within a day’s travel
      • +
      +

      Depending on its wealth a market may support up to twenty stallholders, including bakers, butchers, tanners, weavers, cobblers, chandlers and so on. So a second wave of itinerants sets off. These follow the original rules for itinerants, but if they find an unsettled 100 square metres within five hundred metres of a market, will set up as a stallholder, building a town house and appropriate trade building on their own settlement, and a stall in the market. An itinerant finding a hundred square metres within five hundred metres of a market which has all its stallholder slots filled may become a slum landlord, and build a tenement for day-labourers. Finally, aristocracy. In the second wave an itinerant who finds either a hilltop, an island in a lake or river, or a significant river crossing, with one hectare of unclaimed land and within 5Km of ten farms with unfilled bonnet laird slots becomes a bonnet laird (or ‘squire’, if you prefer) and builds a fortified house. At the end of the second wave of settlement the ten percent of bonnet lairds with the richest fiefs (using much the same metric as for the wealth of markets) become barons and build castles.

      +

      Rendering the buildings

      +

      This seems to me to provide an algorithmic means of settling a landscape which will generate organic and satisfying patterns of settlement. But it all fails if the buildings are chosen from a limited palette of models. As with the trees I think we need algorithmic mechanisms of building similar-but-different buildings which can be repeatably rendered from relatively small data sets. As an example of what I mean, in damper landscapes where wood is likely to be available, there might be a higher probability of stave buildings, or weatherboarding, with mainly shingle roofs. In slightly less damp areas where timber is still available, cruck frames and half timbered buildings will prevail, with mostly thatched roofs. In the dryest areas, cob and brick buildings will be common, often with tile roofs. On steeper hillsides, stone buildings will be common, perhaps with slate roofs. Within each of these types there are essential cells from which a building is created. These cells can be longer or shorter, taller or lower, wider or narrower. A building may comprise a single cell, or more. If more than three cells they may be arranged in a row or round a courtyard. And they may have one story or two. Which they have can be based – like the details of the plants – on functions which take latitude and longitude as arguments and which, internally use pseudo-randoms seeded from those latitude and longitude values.

      +

      How vast a world?

      +

      OK, so, with this general approach, how big can we go? The answer seems to me to be ‘big enough’. A 32 bit integer gives somewhat over four billion values, so can resolve down to one millimetre precision in a world 4000 kilometres by 4000 kilometres. But we don’t actually need millimetre resolution; centimetre would be quite small enough. And that gives us potential for a world 40000Km square, or 1.6 billion square kilometres, which is three times the surface area of planet Earth.

      +

      In practice we can’t go that big for all sorts of space and time reasons. Recording land heights is inevitably an issue. I don’t know of a pseudo random function which will generate satisfying land heights. Anything based on Julia sets, for example, ends up with landforms symmetrical around a central point. Furthermore, the shapes of fractals which can be constructed from simple functions tend to have a noticable and unnatural degree of self-similarity across scales. I’d dearly like to be wrong on this, but I think we need to store at minimum elevation values at ten metre intervals. If we can accept 100mm resolution for elevations, storing 16 bit values gives a range of 6,500 metres - 21,000 feet - from the deepest seabed to the peaks of the highest mountains.

      +

      This means that landform information alone requires 20Kbytes per square kilometre - unindexed, but seeing it’s a rigid ten metre grid that isn’t a problem. Which, in turn, means that you can store landform information for a planet the size of Earth in one terrabyte. But we don’t need a planet the size of earth. Scotland is 80,000 square kilometers of land area; allowing for a bit of sea around to the horizon around it, say 100,000 square kilometers. That seems to me more than big enough to be a game space. It amounts to 160Mb of landform data, which is completely manageable.

      +

      If we stored plant data for every distinctive plant in Scotland - even at one per ten square metres - that does become an impractically large data set, because quite apart from anything else, the plant locations do have to be indexed. But actually, given that the actual plants that grow are a function of the location at which they grow, no player is going to notice if the pattern of the locations of plants is the same for each square kilometre. So we can manage plant data for a land area the size of Scotland in 400,000 bytes - we could do it in less (if the locations were generated using a pseudo-random function, much less).

      +

      Building data is different. We need to store the latitude, longitude and type of every building explicitly, and again they need to be indexed in order that we can recover the buildings in a given area efficiently. We need about 16 bytes per building (four bytes latitude, four longitude, two type; then for each tile a null-terminated vector of pointers to building records). If we assume that our feudal land of 80,000 square kilometers has a population of a million, and that there are on average five occupants of every building, that’s two hundred thousand buildings, implying 3.2Mb of data.

      +

      Of course, that’s just the backing store size. As tiles are loaded into active memory - see the essay ‘Tiles and Flats’ this raw backing data has to be inflated procedurally into actual models that can be rendered; models which may have thousands of vertices and hundreds of kilobytes of textures. The functions which do that inflating have some finite size, and, significantly, they’ll need to work on prototype models which will in turn take storage space. Finally there are hand-edited models potentially used at particular plot locations; those need to be stored more or less in full. But all this has not become an unmanageable amount of data. It seems to me plausible that you could store a fully populated 100,000 square kilometer game world on one uncompressed 700Mb CD. On a 4Gb DVD, you could do it very easily.

      \ No newline at end of file diff --git a/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html b/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html new file mode 100644 index 0000000..df5c42d --- /dev/null +++ b/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html @@ -0,0 +1,41 @@ + +The spread of knowledge in a large game world

      The spread of knowledge in a large game world

      +

      Saturday, 26 April 2008

      +

      part of the role of Dandelion, in The Witcher games, is to provide the player with news

      +

      Note

      +

      This version of this essay has been adapted to use the code in the-great-game.gossip.news-items, q.v.. The original version of the essay is still available on my blog.

      +

      These days we have television, and news. But in a late bronze age world there are no broadcast media. News spreads by word of mouth. If non-player characters are to respond effectively to events in the world, knowledge has to spread.

      +

      How to model this?

      +

      Some non-player characters - doesn’t need to be many - are news-spreaders. News-spreaders need to travel. They have to travel even when there are no player characters in the vicinity. But, they don’t have to travel very often - once or twice every game day. When a news-spreader is in the immediate vicinity of another character, the pair may (with some degree of randomness) exchange news. There needs to be a hierarchy in the exchange of news, so that ‘I-saw’ events need to be more likely to be passed on than ‘I-heard’ events; there needs to be a counter which counts the number of times a knowledge item has been passed on, and also an age counter so that knowledge items are less likely to be passed on as they get older.

      +

      One obvious class of news-spreader is a merchant. Merchant agents can either shuttle mechanically between a fixed group of markets or else possibly respond intelligently to supply and demand. Provided that there is a mesh of merchant routes covering the markets of the game world, and that a useful subset of non-merchant characters are required to visit a market every few game days, this should give a reasonably realistic framework for news spreading.

      +

      What else? What things qualify as news items? I think at least the following:

      +
        +
      • Deaths of sentient characters, especially if violent
      • +
      • Commodity prices
      • +
      • Changes of rulers in cities
      • +
      • Marriages of sentient characters
      • +
      • Plot events, flagged as events by the game designer
      • +
      +

      Obviously, news is more valuable if the people involved are important or notorious: the significance of a story is probably the product of the significance of the people concerned.

      +

      So a news item becomes a tuple

      +

      (days-old nth-hand significance action (actors))

      +

      for example

      +

      (54 2 10 'killed '(fred joe))

      +

      meaning ‘I spoke to a man who’d spoken to a man who said he saw notorious fred kill well-liked joe on 54 days ago’. Obviously, the non-player character must be able to construct a natural language sentence from the tuple when speaking within the hearing of a player character, but there’s no need for a non-player character to produce a natural language sentence for another non-player character to parse; instead they can just exchange tuples.

      +

      But if we’re exchanging knowledge between agents, then agents must have a means of representing knowledge. This knowledge is an association between subjects and sets of statement, such that when the agent learns the statement

      +

      (54 2 10 'killed '(fred joe))

      +

      it adds this statement (with the 2 incremented to 3) to the set of statements it knows about fred and also to the set of statements it knows about joe. It’s possible that the receiving agent could then challenge for further statements about fred and/or joe, the automated equivalent of a ‘who’s joe?’ question.

      +

      There could be feedback in this. Fred’s and joe’s significance scores could be incremented for each character to whom the statement is passed on, increasing the likeliness that fred, at least, would feature in more news stories in future. There needs also to be some means of managing how the non-player character’s attitude to the subjects of the statement are affected. For example, If fred kills joe, and the character (say bill) receiving the news feels positively towards joe, then bill’s attitude to fred should become sharply more hostile. If bill feels neutral about joe, then bill’s attitude to fred should still become a bit more hostile, since killing people is on the whole a bad thing. But it bill feels very hostile towards joe, then bill’s attitude to fred should become more friendly.

      +

      Obviously the rate of decay, and the degree of randomness, of the news-passing algorithm would need to be tuned, but this schema seems to me to describe a system with the following features:

      +
        +
      • Non-player characters can respond to questions about significant things which happen in the world - without it all having to be scripted
      • +
      • If you travel fast enough, you can keep ahead of your notoriety
      • +
      • Characters on major trade routes will know more about what is happening in the world than characters in backwaters
      • +
      +

      This seems to me a reasonably good model of news spread.

      +

      Scaling of the algorithm

      +

      Let’s work around the idea that a ‘game day’ equates to about two hours of wall clock time. Let’s work around the idea that there are of the order of fifty markets in the game world, and that for each market there are two or three merchants whose ‘home base’ it is.

      +

      Obviously non-player characters who are within the vicinity of a player character have to be ‘awake’, in order that the player can see them interacting with their world and can interact with them. Those characters have to be in working memory and have to be in the action polling loop in any case. So there’s no extra cost to their gossiping away between each other - around the player there’s a moving bubble of gossip, allowing each character the player interacts with to have a high probability of having some recent news.

      +

      But the merchants who aren’t in the vicinity of a player don’t have to be in working memory all the time. Each merchant simply requires to be ‘woken up’ - loaded into memory - once per game day, move a day’s journey in one hop, and then, if arriving at an inn or at a market, wake and exchange news with one resident character - an innkeeper or a gossip. So the cost of this algorithm in a fifty-market game is at worst the cost of loading and unloading two non-player characters from memory every minute, and copying two or three statements from the knowledge set of one to the knowledge set of the other. If you’re dynamically modifying significance scores, of course, you’d need to also load the characters about whom news was being passed on; but this still doesn’t seem unduly onerous.

      +

      Obviously, if memory is not too constrained it may be possible to maintain all the merchants, all the innkeepers and all the characters currently being talked about in memory all the time, further reducing the cost.

      \ No newline at end of file diff --git a/docs/codox/Voice-acting-considered-harmful.html b/docs/codox/Voice-acting-considered-harmful.html new file mode 100644 index 0000000..7e5c1ad --- /dev/null +++ b/docs/codox/Voice-acting-considered-harmful.html @@ -0,0 +1,49 @@ + +Voice acting considered harmful

      Voice acting considered harmful

      +

      Wednesday, 25 February 2015

      +

      The Witcher: Conversation with Kalkstein

      +

      Long, long, time ago, I can still remember when… we played (and wrote) adventure games where the user typed at a command line, and the system printed back at them. A Read-Eval-Print loop in the classic Lisp sense, and I wrote my adventure games in Lisp. I used the same opportunistic parser whether the developer was building the game Create a new room north of here called dungeon-3 the player was playing the game Pick up the rusty sword and go north or the player was talking to a non-player character Say to the wizard ‘can you tell me the way to the castle’ Of course, the parser didn’t ‘understand’ English. It worked on trees of words, in which terminal nodes were actions and branching nodes were key words, and it had the property that any word it didn’t recognise at that point in sentence was a noise word and could be ignored. A few special hacks (such as ‘the’, ‘a’, or ‘an’ was an indicator that what came next was probably a noun phrase, and thus that if there was more than one sword in the player’s immediate environment the one that was wanted was the one tagged with the adjective ‘rusty’), and you ended up with a parser that most of the time convincingly interpreted most of what the player threw at it.

      +

      Text adventures fell into desuetude partly because they weren’t graphic, but mainly because people didn’t find typing natural, or became dissatisfied with the repertoire of their parsers. Trying to find exactly the right combination tokens to persuade the game to carry out some simple action is not ‘fun’, it’s just frustrating, and it turned people off. Which is a shame because just at the time when people were abandoning text adventures we were beginning to have command parsers which were actually pretty good. Mine, I think, were good - you could have a pretty natural conversation with them, and in ‘building’ mode, when it hit a ‘sorry I don’t understand’ point, it allowed you to input a path of keywords and a Lisp function so that in future it would understand.

      +

      So much, so Eliza.

      +

      Modern role-playing games - the evolutionary successors of those high and far off text adventures - don’t have text input. Instead, at each stage in a conversation, the user is offered a choice of three or four canned responses, and can pick one; very often what the player’s character actually says then differs from the text the user has chosen, often with differences of nuance which the user feels (s)he didn’t intend. And the non-player-character’s response is similarly canned. Indeed, the vast majority of non-player characters in most games have a ‘repertoire’, if one may call it that, of only one sentence. Others will have one shallow conversational tree, addressing one minor quest or plot-point.

      +

      If you want to talk to them about anything else - well, you just can’t.

      +

      Only a very few key non-player characters will have a large repertoire of conversational trees, relevant to all parts of the plot. And even those trees are not deep. You soon exhaust them; the characters’ ability to simulate real agency just isn’t there.

      +

      I first wrote about the limiting effects of voice acting in my review of the original Witcher game, back in 2008; things haven’t got better.

      +

      On phones: speaking

      +

      In my pocket I carry a phone. It’s not big: 127 x 64.9 x 8.6mm. A small thing.

      +

      When I first used Android phones for navigation, I used to delight in their pronunciation of Scots placenames - pronouncing them phonetically, as spelled, and as though their spelling were modern English. What’s delightful about Scots placenames is that they are linguistically and orthographically so varied - their components may be Brythonic, Goidaelic, Anglian, Norn, French, English, or even Latin; and very frequently they combine elements of more than one language (Benlaw Hill, anyone? Derrywoodwachy?).

      +

      Yes, gentle reader, this does seem a long way from game design; be patient, I’m getting there. But I’m going to digress even further for first…

      +

      There have been orthographic changes, and pronunciation changes consequent on orthographic changes. For example, medieval Scots used the letter Yogh (ȝ), which isn’t present in the English alphabet. So when Edinburgh printers in the early modern period bought type for their printing presses from England, there was no Yogh in the font. So they substituted Zed. So we get names like Dalȝiel, Kirkgunȝeon, Menȝies, Cockenȝie. How do you pronounce them?

      +

      The letter that looks like a ‘z’ is pronounced rather like a ‘y’; so

      +
        +
      • Deeyell
      • +
      • Kirkgunyeon
      • +
      • Mingis
      • +
      +

      and… drumroll…

      +
        +
      • Cockenzie.
      • +
      +

      What happened?

      +

      Well, Dalȝiel and Menȝies are personal names, and people are protective of their own names. Kirkgunȝeon is a small, unimportant place, and all the locals know how it is pronounced. Scots folk, are, after all, used to Scots orthography and its peculiarities. So those names haven’t changed.

      +

      But at Cockenȝie, another small, unimportant place, a nuclear power station was built. The nuclear power station was built by people (mostly) from England, who didn’t know about Yogh or the peculiarities of Scots orthography - and were possibly too arrogant to care. So they called it ‘Cockenzie’. And as there were so many more of them and they had so much higher status than the locals, their name stuck, and nowadays even local people mostly say ‘Cockenzie’, as though it were spelled with a Zed. Because, of course, it is spelled with a Zed. Because, as any British schoolchild knows, there’s no Yogh in the alphabet.

      +

      Except, of course, when there is.

      +

      Another more interesting example of the same thing is ‘Kirkcudbright’. It’s a town built around the kirk (church) of saint Cuthbert. So how does it come to have a ‘d’ in it? And why is it pronounced ‘Kirkoobry’? Well, the venerable Cuthbert pronounced his name in a way which would be represented in modern English as ‘Coothbrecht’, but he spelled it ‘Cuðbrecht’. See that ‘ð’? That’s not a ‘d’, it’s an Eth. Because Cuðbrecht was Anglian, and the Anglian alphabet had Eth; it’s pronounced as a soft ‘th’, and Icelandic still has it (as well as Thorn, þ, a hard ‘th’ sound). Medieval scribes didn’t know about Eth, so in copying out ð they wrote the more familiar d. The local people, however, mostly couldn’t read, so the pronunciation of the name didn’t change with the change in spelling (although the pronunciation, too, has drifted a little with time).

      +

      So, in brief, pronouncing Scots placenames is hard, and there are a lot of curious rules, and consequently it’s not surprising that five years ago, listening to Android’s pronunciation of Scots placenames was really funny.

      +

      But what’s really curious is that now it isn’t. Now, it rarely makes a mistake. Now, Android can do text to speech on unusual and perverse orthography, and get it right better than 95% of the time - and manage a reasonably natural speaking voice while doing so. On a small, low power machine which fits in my pocket.

      +

      On phones: listening

      +

      But navigation is not all I can do with my phone. I can also dictate. By which I don’t mean I can make a voice recording, play it back later and type what I hear, although, of course, I can. I mean I can dictate, for example, an email, and see it in text on my phone before I send it. It quickly learned my peculiarities of diction, and it now needs very little correction. On a small, low power machine which fits in my pocket.

      +

      And breathe

      +

      Right, so where am I going with all this? Well, we interact with modern computer role playing games through very restricted, entirely scripted dialogues. Why do we do so? Why, on our modern machines with huge amounts of store, do our non-player characters - and worse still, our player character, our own avatar - have such restricted repertoires?

      +

      Because they are voice acted. Don’t get me wrong, voice acting makes a game far more engaging. But for voice acting to work, the people doing the acting have to know not only the full range of sentences that their character is going to speak, but also roughly how they feel (angry? sad? excited?) when they say it. Ten years ago, voice acting was probably the only way you could have got this immediacy into games, because ten years ago, text-to-speech systems were pretty crude - think of Stephen Hawking’s voice synthesiser. But now, Edinburgh University’s open source synthesiser is pretty good, and comes with twenty-four voices (and seeing it’s open source, you can of course add your own). Speech to text was probably better ten years ago - think of Dragon Naturally Speaking - but it was proprietary software, and used a fair proportion of a machine’s horsepower. Now there’s (among others) Carnegie Mellon’s open source Sphinx engine, which can quickly adapt to your voice.

      +

      So, we have text-to-speech engines which can generate from samples of many different voices, and speech to text engines which can easily be tuned to your particular voice. There’s even a program called Voice Attack, built on top of Microsoft’s proprietary speech to text engine, which already allows you to control games with speech. Where does that take us?

      +

      Well, we already know how to make sophisticated natural language parsers for text, given moderately limited domains - we don’t need full natural language comprehension here.

      +

      You may think it’s a long way down the road to the chemist

      +

      There are things one needs to know in a game world. For example: I need a sword, where’s the nearest swordsmith? In a real quasi-medieval world, certainly every soldier would be able to tell you, and everyone from the swordsmith’s town or village. Very celebrated swordsmiths would be known more widely.

      +

      And the thing is, the game engine knows where the nearest swordsmith is. It knows what potion will heal what wound, and what herbs and what tincture to use to make it. It knows which meats are good to eat, and which inns have rooms free. It knows good campsites. It knows where there be dragons. It knows where the treasure is hid. It knows - as far as the game and its plot are concerned - everything.

      +

      So to make an in-game Siri - an omniscient companion you could ask anything of - would be easy. Trivial. It also wouldn’t add verisimilitude to the game. But to model which non-player characters know what is not that much harder. Local people know what’s where in their locality. Merchants know the prices in nearby markets. They, and minstrels, know the game-world’s news - major events that affect the plot. Apothecaries, alchemists and witches know the properties of herbs and minerals.

      +

      And to model which non-player characters are friendly, and willing to answer your every question; which neutral or busy, and liable to answer tersely; and which actively hostile, and likely, if they answer at all, to deliberately mislead - that’s not very much harder.

      +

      I’m not arguing that voice acting, and scripted dialogue trees, should be done away with altogether. They still have a use, as cutscenes do, to advance plot. And I’m not suggesting that we use voice to control the player characters movements and actions - I’m not not suggesting that we should say ‘run north; attack the troll with the rusty sword’. Keyboards and mice may be awkward ways to control action, but they’re better than that. Bur I am suggesting that one should be able to talk to any (supposedly sentient) character in the game, and have them talk reasonably sensibly back. As one can already do physically in wandering an open world, a full voice interaction system would allow one to go off piste - to leave the limited, constrained pre-scripted interaction of the voice-acted dialogue tree. And that has got to make our worlds, and our interactions with them, richer, more surprising, more engaging.

      +

      A hybrid system needn’t be hard to achieve, needn’t be jarring in use. You can record the phonemes of your voice actor’s voice, so that the same character will have roughly the same voice - the same timbre, the same vowel sounds, the same characteristics of  pronunciation - whether in a voice acted dialogue or in a generated one.

      +

      We don’t need to let voice acting limit the repertoires of our characters any more. And we shouldn’t.

      \ No newline at end of file diff --git a/docs/codox/economy.html b/docs/codox/economy.html index a232ad2..d81537f 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 375d513..d645463 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -The-great-game 0.1.1-SNAPSHOT

      The-great-game 0.1.1-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.1-SNAPSHOT"]

      Topics

      Namespaces

      the-great-game.agent.agent

      Anything in the game world with agency

      Public variables and functions:

        the-great-game.gossip.gossip

        Interchange of news events between gossip 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.location

        Functions dealing with location in the world.

        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.1-SNAPSHOT

        The-great-game 0.1.1-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.1-SNAPSHOT"]

        Topics

        Namespaces

        the-great-game.agent.agent

        Anything in the game world with agency

        Public variables and functions:

          the-great-game.gossip.gossip

          Interchange of news events between gossip 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.location

          Functions dealing with location in the world.

          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 b17bd13..d16ad24 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 72b183e..5292eb6 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/naming-of-characters.html b/docs/codox/naming-of-characters.html index 6d76b98..3258a8a 100644 --- a/docs/codox/naming-of-characters.html +++ b/docs/codox/naming-of-characters.html @@ -1,6 +1,6 @@ -Naming of Characters

          Naming of Characters

          +Naming of Characters

          Naming of Characters

          Generally speaking, in modern RPGs, every character with any impact on the plot has a distinct name. But if we are going to give all non-player characters sufficient agency to impact on the plot, then we must have a way of naming tens or hundreds of thousands of characters, and distinct names will become problematic (even if we’re procedurally generating names, which we shall have to do. So this note is about how characters are named.

          The full name of each character will be made up as follows:

          [epithet] [clan] [personal-name] the [trade-or-rank] of [location], son/daughter of [parent]

          diff --git a/docs/codox/on-dying.html b/docs/codox/on-dying.html new file mode 100644 index 0000000..d84d7de --- /dev/null +++ b/docs/codox/on-dying.html @@ -0,0 +1,9 @@ + +On Dying

          On Dying

          +

          Death is the end of your story. One of the tropes in games which, for me, most breaks immersion is when you lose a fight and are presented with a screen that says ‘you are dead. Do you want to reload your last save?’ Life is not like that. We do not have save-states. We die.

          +

          So how could this be better handled?

          +

          You lose a fight. Switch to cutscene: the battlefield, after the fight, your body is there. Probably no sound. A party of non-enemies crosses the battlefield and finds your body. We see surprise and concern. They gather around you. Cut to interior scene, you are in a bed, unconcious, being tended; cut to similar interior scene, you are in a bed, conscious, being tended; cut to exterior scene, you are sitting with some of your saviours, and the game restarts.

          +

          Time has passed; events in the game world have moved on. You can talk to your saviours about it. You have lost a lot of strength, and most of the gear you were carrying. You must do whatever it is you do within the game mechanics to rebuild strength, and to acquire more gear. Significantly you have acquired a debt of honour to your saviours, which they may call on later. You almost certainly have new scars, and might possibly have some lasting effects (although how that interacts with other game mechanics might be tricky).

          +

          So who are the non-enemies? It depends on context. If you have a party, and some of that party survived the fight, it’s your party. Otherwise, if you’re in a populated place, it’s locals. If it’s on a road or other route, it’s passing merchants. If you’re in the wilderness, a hunting party. It’s a bunch of non-hostiles who might reasonably be expected to be around: that’s what matters. It’s about not breaking immersion.

          +

          Obviously losing a fight must have weight, it must have meaning, it must have in-game consequences; otherwise it is meaningless.

          \ No newline at end of file diff --git a/docs/codox/orgnic-quests.html b/docs/codox/orgnic-quests.html index ca6e4a8..05c712d 100644 --- a/docs/codox/orgnic-quests.html +++ b/docs/codox/orgnic-quests.html @@ -1,6 +1,6 @@ -Organic Quests

          Organic Quests

          +Organic Quests

          Organic Quests

          The structure of a modern Role Playing Came revolves around ‘quests’: tasks that the player character is invited to do, either by the framing narrative of the game or by some non-player character (‘the Quest Giver’). Normally there is one core quest which provides the overarching narrative for the whole game. [Wikipedia](https://en.wikipedia.org/wiki/Quest_(gaming)) offers a typology of quests as follows:

          1. Kill quests
          2. diff --git a/docs/codox/sandbox.html b/docs/codox/sandbox.html index fefc173..e5b4a25 100644 --- a/docs/codox/sandbox.html +++ b/docs/codox/sandbox.html @@ -1,6 +1,6 @@ -Sandbox

            Sandbox

            +Sandbox

            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

            @@ -20,7 +20,7 @@

            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.

            +

            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.

            @@ -33,7 +33,7 @@

            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.

            +

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

            +

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

            \ No newline at end of file diff --git a/docs/codox/sexual-dimorphism.html b/docs/codox/sexual-dimorphism.html index 3cb1205..de010b7 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.agent.agent.html b/docs/codox/the-great-game.agent.agent.html index 02353e9..079a700 100644 --- a/docs/codox/the-great-game.agent.agent.html +++ b/docs/codox/the-great-game.agent.agent.html @@ -1,3 +1,3 @@ -the-great-game.agent.agent documentation

            the-great-game.agent.agent

            Anything in the game world with agency

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

            the-great-game.agent.agent

            Anything in the game world with agency

            \ 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 559aa1c..177c964 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 gossip 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 of 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 gossip 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 of 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.gossip.news-items.html b/docs/codox/the-great-game.gossip.news-items.html index e145054..64f1d4c 100644 --- a/docs/codox/the-great-game.gossip.news-items.html +++ b/docs/codox/the-great-game.gossip.news-items.html @@ -1,6 +1,6 @@ -the-great-game.gossip.news-items documentation

            the-great-game.gossip.news-items

            Categories of news events interesting to gossip agents

            degrade-character

            (degrade-character gossip character)

            Return a character specification like this character, but comprising only those properties this gossip is interested in.

            degrade-location

            (degrade-location gossip location)

            Return a location specification like this location, but comprising only those elements this gossip is interested in. If none, return nil.

            infer

            (infer item rule)

            Infer a new knowledge item from this item, following this rule

            interest-in-character

            (interest-in-character gossip character)

            Integer representation of how interesting this character is to this gossip. TODO: this assumes that characters are passed as keywords, but, as documented above, they probably have to be maps, to allow for degradation.

            interest-in-location

            (interest-in-location gossip location)

            Integer representation of how interesting this location is to this gossip.

            interesting-character?

            (interesting-character? gossip character)

            Boolean representation of whether this character is interesting to this gossip.

            interesting-item?

            (interesting-item? gossip item)

            True if anything about this news item is interesting to this gossip.

            interesting-location?

            (interesting-location? gossip item)

            True if the location of this news item is interesting to this gossip.

            interesting-object?

            (interesting-object? gossip object)

            TODO: write docs

            interesting-topic?

            (interesting-topic? gossip topic)

            TODO: write docs

            learn-news-item

            (learn-news-item gossip item)(learn-news-item gossip item follow-inferences?)

            Return a gossip like this gossip, which has learned this news item if it is of interest to them.

            make-all-inferences

            (make-all-inferences item)

            Return a list of knowledge entries inferred from this news item by this gossip.

            news-topics

            Topics of interest to gossip agents. Topics are keyed in this map by their verbs. The keys associated with each topic are the extra pieces of information required to give context to a gossip item. Generally:

            +the-great-game.gossip.news-items documentation

            the-great-game.gossip.news-items

            Categories of news events interesting to gossip agents

            degrade-character

            (degrade-character gossip character)

            Return a character specification like this character, but comprising only those properties this gossip is interested in.

            degrade-location

            (degrade-location gossip location)

            Return a location specification like this location, but comprising only those elements this gossip is interested in. If none, return nil.

            infer

            (infer item rule)

            Infer a new knowledge item from this item, following this rule

            interest-in-character

            (interest-in-character gossip character)

            Integer representation of how interesting this character is to this gossip. TODO: this assumes that characters are passed as keywords, but, as documented above, they probably have to be maps, to allow for degradation.

            interest-in-location

            (interest-in-location gossip location)

            Integer representation of how interesting this location is to this gossip.

            interesting-character?

            (interesting-character? gossip character)

            Boolean representation of whether this character is interesting to this gossip.

            interesting-item?

            (interesting-item? gossip item)

            True if anything about this news item is interesting to this gossip.

            interesting-location?

            (interesting-location? gossip item)

            True if the location of this news item is interesting to this gossip.

            interesting-object?

            (interesting-object? gossip object)

            TODO: write docs

            interesting-topic?

            (interesting-topic? gossip topic)

            TODO: write docs

            learn-news-item

            (learn-news-item gossip item)(learn-news-item gossip item follow-inferences?)

            Return a gossip like this gossip, which has learned this news item if it is of interest to them.

            make-all-inferences

            (make-all-inferences item)

            Return a list of knowledge entries inferred from this news item by this gossip.

            news-topics

            Topics of interest to gossip agents. Topics are keyed in this map by their verbs. The keys associated with each topic are the extra pieces of information required to give context to a gossip item. Generally:

            • actor is the id of the character who it is reported performed the action;
            • other is the id of the character on whom it is reported the action was performed;
            • diff --git a/docs/codox/the-great-game.merchants.markets.html b/docs/codox/the-great-game.merchants.markets.html index 7d7e83e..fa68c6e 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 58e0534..7667e12 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 cache 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 cache 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 46e3dc1..d5abf84 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 2d17459..476d46a 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 d5ba1e3..b1a47a3 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.time.html b/docs/codox/the-great-game.time.html index 76496a1..2fc8c02 100644 --- a/docs/codox/the-great-game.time.html +++ b/docs/codox/the-great-game.time.html @@ -1,3 +1,3 @@ -the-great-game.time documentation

                the-great-game.time

                TODO: write docs

                canonical-ordering-of-houses

                The canonical ordering of religious houses.

                date-string

                (date-string game-time)

                Return a correctly formatted date for this game-time in the calendar of the Great Place.

                day

                (day game-time)

                Day of the eight-day week represented by this game-time.

                day-of-year

                macro

                (day-of-year game-time)

                The day of the year represented by this game-time, ignoring leap years.

                days-in-season

                TODO: write docs

                days-in-week

                This world has an eight day week.

                days-of-week

                The eight-day week of the game world. This differs from the canonical ordering of houses in that it omits the eye.

                game-day-length

                The Java clock advances in milliseconds, which is fine. But we need game-days to be shorter than real world days. A Witcher 3 game day is 1 hour 36 minutes, or 96 minutes, which is presumably researched. Round it up to 100 minutes for easier calculation.

                game-start-time

                The start time of this run.

                game-time

                (game-time)(game-time timestamp)

                With no arguments, the current game time. If a Java timestamp value is passed (as a long), the game time represented by that value.

                now

                (now)

                For now, we’ll use Java timestamp for time; ultimately, we need a concept of game-time which allows us to drive day/night cycle, seasons, et cetera, but what matters about time is that it is a value which increases.

                season

                (season game-time)

                TODO: write docs

                seasons-in-year

                Nine seasons in a year, one for each house (although the order is different.

                seasons-of-year

                The ordering of seasons in the year is different from the canonical ordering of the houses, for reasons of the agricultural cycle.

                waiting-day?

                Does this game-time represent a waiting day?

                week

                (week game-time)

                Week of season represented by this game-time.

                weeks-in-season

                To fit nine seasons of eight day weeks into 365 days, each must be of five weeks.

                weeks-of-season

                To fit nine seasons of eight day weeks into 365 days, each must be of five weeks.

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

                the-great-game.time

                TODO: write docs

                canonical-ordering-of-houses

                The canonical ordering of religious houses.

                date-string

                (date-string game-time)

                Return a correctly formatted date for this game-time in the calendar of the Great Place.

                day

                (day game-time)

                Day of the eight-day week represented by this game-time.

                day-of-year

                macro

                (day-of-year game-time)

                The day of the year represented by this game-time, ignoring leap years.

                days-in-season

                TODO: write docs

                days-in-week

                This world has an eight day week.

                days-of-week

                The eight-day week of the game world. This differs from the canonical ordering of houses in that it omits the eye.

                game-day-length

                The Java clock advances in milliseconds, which is fine. But we need game-days to be shorter than real world days. A Witcher 3 game day is 1 hour 36 minutes, or 96 minutes, which is presumably researched. Round it up to 100 minutes for easier calculation.

                game-start-time

                The start time of this run.

                game-time

                (game-time)(game-time timestamp)

                With no arguments, the current game time. If a Java timestamp value is passed (as a long), the game time represented by that value.

                now

                (now)

                For now, we’ll use Java timestamp for time; ultimately, we need a concept of game-time which allows us to drive day/night cycle, seasons, et cetera, but what matters about time is that it is a value which increases.

                season

                (season game-time)

                TODO: write docs

                seasons-in-year

                Nine seasons in a year, one for each house (although the order is different.

                seasons-of-year

                The ordering of seasons in the year is different from the canonical ordering of the houses, for reasons of the agricultural cycle.

                waiting-day?

                Does this game-time represent a waiting day?

                week

                (week game-time)

                Week of season represented by this game-time.

                weeks-in-season

                To fit nine seasons of eight day weeks into 365 days, each must be of five weeks.

                weeks-of-season

                To fit nine seasons of eight day weeks into 365 days, each must be of five weeks.

                \ 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 84eba13..b52db2b 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.location.html b/docs/codox/the-great-game.world.location.html index e3836dc..641a92c 100644 --- a/docs/codox/the-great-game.world.location.html +++ b/docs/codox/the-great-game.world.location.html @@ -1,3 +1,3 @@ -the-great-game.world.location documentation

                the-great-game.world.location

                Functions dealing with location in the world.

                distance-between

                (distance-between location-1 location-2)

                TODO: write docs

                get-coords

                (get-coords location)

                Return the coordinates in the game world of location, which may be 1. A coordinate pair in the format {:x 5 :y 32}; 2. A location, as discussed above; 3. Any other gameworld object, having a :location property whose value is one of the above.

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

                the-great-game.world.location

                Functions dealing with location in the world.

                distance-between

                (distance-between location-1 location-2)

                TODO: write docs

                get-coords

                (get-coords location)

                Return the coordinates in the game world of location, which may be 1. A coordinate pair in the format {:x 5 :y 32}; 2. A location, as discussed above; 3. Any other gameworld object, having a :location property whose value is one of the above.

                \ 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 f5a5455..725f405 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 0aca065..277b977 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 c417377..67d8c86 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/docs/index.html b/docs/index.html index 044b871..ee9bafd 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,13 +1,14 @@ - The Great Game: Dcocumentation + The Great Game: Documentation + -

                The Great Game: Dcocumentation

                +

                The Great Game: Documentation

                diff --git a/src/the_great_game/gossip/news_items.clj b/src/the_great_game/gossip/news_items.clj index 4591f3a..f2572d4 100644 --- a/src/the_great_game/gossip/news_items.clj +++ b/src/the_great_game/gossip/news_items.clj @@ -1,7 +1,7 @@ (ns the-great-game.gossip.news-items "Categories of news events interesting to gossip agents" (:require [the-great-game.world.location :refer [distance-between]] - [the-great-game.time :refer [now]])) + [the-great-game.time :refer [game-time]])) ;; The ideas here are based on the essay 'The spread of knowledge in a large ;; game world', q.v.; they've advanced a little beyond that and will doubtless @@ -136,12 +136,14 @@ (count (filter #(some (fn [x] (= x location)) (:location %)) - (:knowledge gossip))))) + (cons {:location (:home gossip)} (:knowledge gossip)))))) + +;; (interest-in-location {:home [{0, 0} :test-home] :knowledge []} [:test-home]) (defn interesting-location? "True if the location of this news `item` is interesting to this `gossip`." [gossip item] - (> (interest-in-location gossip (:location item)) 1)) + (> (interest-in-location gossip (:location item)) 0)) (defn interesting-object? [gossip object] @@ -224,7 +226,7 @@ (number? (:nth-hand item)) (inc (:nth-hand item)) 1) - :date (if (number? (:date item)) (:date item) (now)) + :date (if (number? (:date item)) (:date item) (game-time)) :location (degrade-location gossip (:location item)) ;; ought to degratde the location ;; ought to maybe-degrade characters we're not yet interested in diff --git a/test/the_great_game/gossip/news_items_test.clj b/test/the_great_game/gossip/news_items_test.clj index 908e330..db5ee89 100644 --- a/test/the_great_game/gossip/news_items_test.clj +++ b/test/the_great_game/gossip/news_items_test.clj @@ -23,6 +23,11 @@ :location [{:x 35 :y 23} :auchencairn :galloway :scotland]}]} [:galloway :scotland])] (is (= actual expected))) + (let [expected 2 + actual (interest-in-location + {:home [{:x 35 :y 23} :auchencairn :galloway :scotland]} + [:galloway :scotland])] + (is (= actual expected))) (let [expected 0 actual (interest-in-location {:knowledge [{:verb :steal @@ -114,19 +119,16 @@ ;; dates will not be and cannot be expected to be equal actual (make-all-inferences {:verb :rape :actor :adam :other :belinda :location :test-home}) - actual' (map #(dissoc % :date) actual)] + actual' (set (map #(dissoc % :date) actual))] (is (= actual' expected))))) -;; (deftest learn-tests -;; (testing "Learning from an interesting news item." -;; (let [expected {:home [{0 0} :test-home], -;; :knowledge ({:verb :rape, :actor :adam, :other :belinda, :location nil, :nth-hand 1} -;; {:verb :sex, :actor :belinda, :other :adam, :location nil, :nth-hand 1} -;; {:verb :attack, :actor :adam, :other :belinda, :location nil, :nth-hand 1} -;; {:verb :sex, :actor :adam, :other :belinda, :location nil, :nth-hand 1})} -;; actual (learn-news-item -;; {:home [{0, 0} :test-home] -;; :knowledge []} -;; {:verb :rape :actor :adam :other :belinda :location [:test-home]}) -;; actual' (assoc actual :knowledge (map #(dissoc % :date) (:knowledge actual)))] -;; (is (= actual' expected))))) +(deftest learn-tests + (testing "Learning from an interesting news item." + (let [expected {:home [{0 0} :test-home], + :knowledge [{:verb :sex, :actor :adam, :other :belinda, :location nil, :nth-hand 1} + {:verb :sex, :actor :belinda, :other :adam, :location nil, :nth-hand 1}]} + actual (learn-news-item + {:home [{0, 0} :test-home] :knowledge []} + {:verb :sex :actor :adam :other :belinda :location [:test-home]}) + actual' (assoc actual :knowledge (vec (map #(dissoc % :date) (:knowledge actual))))] + (is (= actual' expected))))) From 08cab01f6db44c09c91da6c00178c4d7fd5ac5d6 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 15 Apr 2020 18:46:43 +0100 Subject: [PATCH 06/12] Further work on documentation and thinking. Not all tests pass. --- doc/Baking-the-world.md | 4 +- ...read-of-knowledge-in-a-large-game-world.md | 32 +++++++++++++--- doc/economy.md | 2 +- doc/intro.md | 16 ++++---- doc/modelling_trading_cost_and_risk.md | 2 +- docs/codox/Baking-the-world.html | 4 +- ...ad-of-knowledge-in-a-large-game-world.html | 32 +++++++++++++--- docs/codox/economy.html | 2 +- docs/codox/intro.html | 14 +++---- .../modelling_trading_cost_and_risk.html | 2 +- src/the_great_game/gossip/news_items.clj | 38 +++++++++++-------- .../the_great_game/gossip/news_items_test.clj | 4 +- 12 files changed, 101 insertions(+), 51 deletions(-) diff --git a/doc/Baking-the-world.md b/doc/Baking-the-world.md index c41c6cc..a2f3739 100644 --- a/doc/Baking-the-world.md +++ b/doc/Baking-the-world.md @@ -16,7 +16,7 @@ But, before going there, to summarise the proving stage. The inputs are: -1. A raster height map (although this could be randomly generated using any one of many fractal algorithms) - this probably uses ideas from [tessellated multi-layer height map](../../2013/07/tessellated-multi-layer-height-map.html); +1. A raster height map (although this could be randomly generated using any one of many fractal algorithms) - this probably uses ideas from [tessellated multi-layer height map](https://blog.journeyman.cc/2013/07/tessellated-multi-layer-height-map.html); 1. Optionally, a raster rainfall map at 1km resolution (although my personal preference is that this should be generated procedurally from the height map). The outputs are @@ -26,7 +26,7 @@ 1. A database of settlers and their settlements, such that the settlements have x,y co-ordinates; 1. A vector road map. - In this sense, the 'biome map' is just the end state of a [Microworld](../../2014/08/modelling-settlement-with-cellular.html) run. The 'biomes' include things like 'forest', 'scrub', 'heath', 'pasture', but they may also include human settlement, and even settlement by different cultural groups. + In this sense, the 'biome map' is just the end state of a [Microworld](https://blog.journeyman.cc/2014/08/modelling-settlement-with-cellular.html) run. The 'biomes' include things like 'forest', 'scrub', 'heath', 'pasture', but they may also include human settlement, and even settlement by different cultural groups. This gives us all we need to vegetate and furnish the world. When rendering each square metre we have diff --git a/doc/The-spread-of-knowledge-in-a-large-game-world.md b/doc/The-spread-of-knowledge-in-a-large-game-world.md index 40e842b..937da44 100644 --- a/doc/The-spread-of-knowledge-in-a-large-game-world.md +++ b/doc/The-spread-of-knowledge-in-a-large-game-world.md @@ -27,24 +27,46 @@ _This version of this essay has been adapted to use the code in `the-great-game. Obviously, news is more valuable if the people involved are important or notorious: the significance of a story is probably the product of the significance of the people concerned. - So a news item becomes a tuple + So a news item becomes a map with keys similar to - `(days-old nth-hand significance action (actors))` + [:verb :actor :other :location :nth-hand :time-stamp] + + The [exact keys for each verb are specified here](the-great-game.gossip.news-items.html#var-news-topics). for example - `(54 2 10 'killed '(fred joe))` + {:verb :kill, + :actor {:id :fred :name "Fred"}, + :other {:id :joe :name "Joe"}, + :location [{45467 78613} :hanshua :plateau], + :nth-hand 3, + :time-stamp 17946463} - meaning 'I spoke to a man who'd spoken to a man who said he saw notorious fred kill well-liked joe on 54 days ago'. Obviously, the non-player character must be able to construct a natural language sentence from the tuple when speaking within the hearing of a player character, but there's no need for a non-player character to produce a natural language sentence for another non-player character to parse; instead they can just exchange tuples. + meaning 'I spoke to a man who'd spoken to a man who said he saw fred kill joe at the game time represented by the time stamp 17946463, at the coordinates {45467 78613} in Hans'hua on the Plateau'. Obviously, the non-player character must be able to construct a natural language sentence from the tuple when speaking within the hearing of a player character, but there's no need for a non-player character to produce a natural language sentence for another non-player character to parse; instead they can just exchange tuples. But if we're exchanging knowledge between agents, then agents must have a means of representing knowledge. This knowledge is an association between subjects and sets of statement, such that when the agent learns the statement - `(54 2 10 'killed '(fred joe))` + {:verb :kill, + :actor {:id :fred :name "Fred"}, + :other {:id :joe :name "Joe"}, + :location [{45467 78613} :hanshua :plateau], + :nth-hand 3, + :time-stamp 17946463} it adds this statement (with the 2 incremented to 3) to the set of statements it knows about fred and also to the set of statements it knows about joe. It's possible that the receiving agent could then challenge for further statements about fred and/or joe, the automated equivalent of a 'who's joe?' question. There could be feedback in this. Fred's and joe's significance scores could be incremented for each character to whom the statement is passed on, increasing the likeliness that fred, at least, would feature in more news stories in future. There needs also to be some means of managing how the non-player character's attitude to the subjects of the statement are affected. For example, If fred kills joe, and the character (say bill) receiving the news feels positively towards joe, then bill's attitude to fred should become sharply more hostile. If bill feels neutral about joe, then bill's attitude to fred should still become a bit more hostile, since killing people is on the whole a bad thing. But it bill feels very hostile towards joe, then bill's attitude to fred should become more friendly. +But also, the added knowledge is *degraded*. If the recipient isn't from Hans'hua, the exact location isn't meaningful to them, for example. If the recipient isn't interested in Joe, precisely who was killed may be forgotten. So what is stored could become: + + {:verb :kill, + :actor {:id :fred :name "Fred"}, + :location [:hanshua :plateau], + :nth-hand 4, + :time-stamp 17946463} + +The timestamp could also be degraded, or lost altother - although how exactly this is represnted I'm not certain. Someone interested in the incident may remember 'it was exactly 17 days ago', whereas someone else may remember that it was 'this month, I think'. + Obviously the rate of decay, and the degree of randomness, of the news-passing algorithm would need to be tuned, but this schema seems to me to describe a system with the following features: * Non-player characters can respond to questions about significant things which happen in the world - without it all having to be scripted diff --git a/doc/economy.md b/doc/economy.md index 45ed00a..15111dc 100644 --- a/doc/economy.md +++ b/doc/economy.md @@ -1,6 +1,6 @@ # 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. +Broadly this essay extends ideas presented in [Populating a game world](Populating-a-game-world.html), q.v. ## Primary producers diff --git a/doc/intro.md b/doc/intro.md index 3c67ed7..3ae3526 100644 --- a/doc/intro.md +++ b/doc/intro.md @@ -19,7 +19,7 @@ that I need to be able to use it to tell stories, in order to create initial threads of narrative from which players can start their exploration. Note that, by 'conflict', here, I explicitly do not mean 'killing people', -or even 'killing non-player characters'. I have [written extensively](https://blog.journeyman.cc/2015/02/voice-acting-considered-harmful.html) +or even 'killing non-player characters'. I have [written extensively](Voice-acting-considered-harmful.html) about the problem in many current video games that all too often the only way of interacting with non-player characters is to kill them. Killing people should be one of the potential ways of resolving conflicts, because @@ -32,18 +32,18 @@ repertoire of speech. ## Previous essays that are relevant -* [The spread of knowledge in a large game world](https://blog.journeyman.cc/2008/04/the-spread-of-knowledge-in-large-game.html) (2008) discusses what individual non-player characters know, and how to model dynamic updates to their knowledge; +* [The spread of knowledge in a large game world](The-spread-of-knowledge-in-a-large-game-world.html) (2008) discusses what individual non-player characters know, and how to model dynamic updates to their knowledge; * [Settling a game world](https://blog.journeyman.cc/2009/12/settling-game-world.html) (2009) gives rough outline of ideas about creating the environment, including modelling things like soil fertility, local building materials, and consequently local architecture; * [Tessellated multi-layer height map](https://blog.journeyman.cc/2013/07/tessellated-multi-layer-height-map.html) (2013) gives ideas for how a designed geography for a very large world could be stored relatively economically; * [Genetic Buildings](https://blog.journeyman.cc/2013/07/genetic-buildings.html) (2013) sketches algorithms which would allow procedurally-generated buildings to be site-appropriate, broadly variable and reproducable; -* [Populating a game world](https://blog.journeyman.cc/2013/07/populating-game-world.html) (2013) provides outline algorithms for how a world can be populated, and how organic mixes of trades and crafts can be modelled; +* [Populating a game world](Populating-a-game-world.html) (2013) provides outline algorithms for how a world can be populated, and how organic mixes of trades and crafts can be modelled; * [Modelling the change from rural to urban](https://blog.journeyman.cc/2013/07/modelling-change-from-rural-to-urban.html) (2013) describes the idea of procedurally modelling settlements, but it is grid-based and not particularly satisfactory and has largely been superceded in my thinking; -* [Of pigeons, and long distance messaging in a game world]() (2013) builds on ideas about flows of information; +* [Of pigeons, and long distance messaging in a game world](https://blog.journeyman.cc/2013/10/of-pigeons-and-long-distance-messaging.html) (2013) builds on ideas about flows of information; * [Modelling rural to urban, take two](https://blog.journeyman.cc/2013/10/modelling-rural-to-urban-take-two.html) (2013) revisited the idea of modelling organic settlement structures, trying to find algorithms which would naturally produce more persuasive settlement models, including further ideas on the procedural generation of buildings; * [More on modelling rivers](https://blog.journeyman.cc/2014/09/more-on-modelling-rivers.html) (2014) talks about modelling hydrology, with implications for soil fertility; * [Modelling settlement with cellular automata](https://blog.journeyman.cc/2014/08/modelling-settlement-with-cellular.html) (2014) talks about successful implementation of algorithms to model vegetative environment, human settlement and the impact of human settlement on the environment; * [Voice acting considered harmful](https://blog.journeyman.cc/2015/02/voice-acting-considered-harmful.html) (2015) outlines the ideas behind full speech interaction with non-player characters, and modelling what those non-player characters should be able to speak about; -* [Baking the world](https://blog.journeyman.cc/2019/05/baking-world.html) (2019) an outline of the overall process of creating a world. +* [Baking the world](Baking-the-world.html) (2019) an outline of the overall process of creating a world. ## Organic and emergent game-play @@ -183,8 +183,8 @@ easy: So each agent is assigned - by the dreaded random number generator - one top level goal when they are instantiated. I don't think it's necessary to model change of top level goals, although of course that does happen in real life; -however, although each agent has one top level goal, they will have lower l -evel 'stretch goals' also taken from this list: so at each decision point in +however, although each agent has one top level goal, they will have lower +level 'stretch goals' also taken from this list: so at each decision point in an agent's planning loop, if base level needs are satisfied and progress on the top level goal is blocked, actions should be chosen which progress one of the lower goals. Indeed, it's possible that all agents could have all @@ -379,4 +379,4 @@ Each game day, every habitual traveller within the 'local' gossip bubble exchanges some items of gossip with the nearest innkeeper to their current location. In the second and third gossip bubbles, it's probably only more favoured gossip agents who do this. See -[The spread of knowledge in a large game world](https://blog.journeyman.cc/2008/04/the-spread-of-knowledge-in-large-game.html) +[The spread of knowledge in a large game world](The-spread-of-knowledge-in-a-large-game-world.html) diff --git a/doc/modelling_trading_cost_and_risk.md b/doc/modelling_trading_cost_and_risk.md index 3574d5b..78ae9dd 100644 --- a/doc/modelling_trading_cost_and_risk.md +++ b/doc/modelling_trading_cost_and_risk.md @@ -2,7 +2,7 @@ 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. +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](the-great-game.gossip.gossip.html) 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/Baking-the-world.html b/docs/codox/Baking-the-world.html index 5924b7f..c588bd8 100644 --- a/docs/codox/Baking-the-world.html +++ b/docs/codox/Baking-the-world.html @@ -10,7 +10,7 @@

                Then, when the landscape has developed - the areas of forest, scrub, open meadow, moorland, savanah and desert are determined, the rivers plotted, the settlers moved in, their trades determined and their settlements allocated, the roadways which link settlements routed, river crossings and ports defined - the proving process ends, and the world is turned over to the plot-writers, quest builders and designers, for a process we can see as analogous to kneading.

                But, before going there, to summarise the proving stage. The inputs are:

                  -
                1. A raster height map (although this could be randomly generated using any one of many fractal algorithms) - this probably uses ideas from tessellated multi-layer height map;
                2. +
                3. A raster height map (although this could be randomly generated using any one of many fractal algorithms) - this probably uses ideas from tessellated multi-layer height map;
                4. Optionally, a raster rainfall map at 1km resolution (although my personal preference is that this should be generated procedurally from the height map).

                The outputs are

                @@ -20,7 +20,7 @@
              • A database of settlers and their settlements, such that the settlements have x,y co-ordinates;
              • A vector road map.
          -

          In this sense, the ‘biome map’ is just the end state of a Microworld run. The ‘biomes’ include things like ‘forest’, ‘scrub’, ‘heath’, ‘pasture’, but they may also include human settlement, and even settlement by different cultural groups.

          +

          In this sense, the ‘biome map’ is just the end state of a Microworld run. The ‘biomes’ include things like ‘forest’, ‘scrub’, ‘heath’, ‘pasture’, but they may also include human settlement, and even settlement by different cultural groups.

          This gives us all we need to vegetate and furnish the world. When rendering each square metre we have

          1. The x,y coordinates, obviously;
          2. diff --git a/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html b/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html index df5c42d..9dd26f5 100644 --- a/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html +++ b/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html @@ -18,15 +18,37 @@
          3. Plot events, flagged as events by the game designer

        Obviously, news is more valuable if the people involved are important or notorious: the significance of a story is probably the product of the significance of the people concerned.

        -

        So a news item becomes a tuple

        -

        (days-old nth-hand significance action (actors))

        +

        So a news item becomes a map with keys similar to

        +
        [:verb :actor :other :location :nth-hand :time-stamp]
        +
        +

        The exact keys for each verb are specified here.

        for example

        -

        (54 2 10 'killed '(fred joe))

        -

        meaning ‘I spoke to a man who’d spoken to a man who said he saw notorious fred kill well-liked joe on 54 days ago’. Obviously, the non-player character must be able to construct a natural language sentence from the tuple when speaking within the hearing of a player character, but there’s no need for a non-player character to produce a natural language sentence for another non-player character to parse; instead they can just exchange tuples.

        +
         {:verb :kill,
        + :actor {:id :fred :name "Fred"},
        + :other {:id :joe :name "Joe"},
        + :location [{45467 78613} :hanshua :plateau],
        + :nth-hand 3,
        + :time-stamp 17946463}
        +
        +

        meaning ‘I spoke to a man who’d spoken to a man who said he saw fred kill joe at the game time represented by the time stamp 17946463, at the coordinates {45467 78613} in Hans’hua on the Plateau’. Obviously, the non-player character must be able to construct a natural language sentence from the tuple when speaking within the hearing of a player character, but there’s no need for a non-player character to produce a natural language sentence for another non-player character to parse; instead they can just exchange tuples.

        But if we’re exchanging knowledge between agents, then agents must have a means of representing knowledge. This knowledge is an association between subjects and sets of statement, such that when the agent learns the statement

        -

        (54 2 10 'killed '(fred joe))

        +
         {:verb :kill,
        + :actor {:id :fred :name "Fred"},
        + :other {:id :joe :name "Joe"},
        + :location [{45467 78613} :hanshua :plateau],
        + :nth-hand 3,
        + :time-stamp 17946463}
        +

        it adds this statement (with the 2 incremented to 3) to the set of statements it knows about fred and also to the set of statements it knows about joe. It’s possible that the receiving agent could then challenge for further statements about fred and/or joe, the automated equivalent of a ‘who’s joe?’ question.

        There could be feedback in this. Fred’s and joe’s significance scores could be incremented for each character to whom the statement is passed on, increasing the likeliness that fred, at least, would feature in more news stories in future. There needs also to be some means of managing how the non-player character’s attitude to the subjects of the statement are affected. For example, If fred kills joe, and the character (say bill) receiving the news feels positively towards joe, then bill’s attitude to fred should become sharply more hostile. If bill feels neutral about joe, then bill’s attitude to fred should still become a bit more hostile, since killing people is on the whole a bad thing. But it bill feels very hostile towards joe, then bill’s attitude to fred should become more friendly.

        +

        But also, the added knowledge is degraded. If the recipient isn’t from Hans’hua, the exact location isn’t meaningful to them, for example. If the recipient isn’t interested in Joe, precisely who was killed may be forgotten. So what is stored could become:

        +
         {:verb :kill,
        + :actor {:id :fred :name "Fred"},
        + :location [:hanshua :plateau],
        + :nth-hand 4,
        + :time-stamp 17946463}
        +
        +

        The timestamp could also be degraded, or lost altother - although how exactly this is represnted I’m not certain. Someone interested in the incident may remember ‘it was exactly 17 days ago’, whereas someone else may remember that it was ‘this month, I think’.

        Obviously the rate of decay, and the degree of randomness, of the news-passing algorithm would need to be tuned, but this schema seems to me to describe a system with the following features:

        • Non-player characters can respond to questions about significant things which happen in the world - without it all having to be scripted
        • diff --git a/docs/codox/economy.html b/docs/codox/economy.html index d81537f..b5c7ecb 100644 --- a/docs/codox/economy.html +++ b/docs/codox/economy.html @@ -1,7 +1,7 @@ Game world economy

          Game world economy

          -

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

          +

          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.

          diff --git a/docs/codox/intro.html b/docs/codox/intro.html index d16ad24..ebeff11 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -5,22 +5,22 @@

          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?

          It has strong elements of a Role Playing Game, as currently understood; some elements of a Simulation Game; some elements of a God Game. But what I see it as is fundamentally a sandbox in which the player(s) can explore ideas about human conflicts and how to resolve them, without immediate real-world consequences. It’s also a sandbox in which story tellers can tell stories, but that’s essentially a side-effect - a consequence of the fact that I need to be able to use it to tell stories, in order to create initial threads of narrative from which players can start their exploration.

          -

          Note that, by ‘conflict’, here, I explicitly do not mean ‘killing people’, or even ‘killing non-player characters’. I have written extensively about the problem in many current video games that all too often the only way of interacting with non-player characters is to kill them. Killing people should be one of the potential ways of resolving conflicts, because that is reality, but negotiation must be another.

          +

          Note that, by ‘conflict’, here, I explicitly do not mean ‘killing people’, or even ‘killing non-player characters’. I have written extensively about the problem in many current video games that all too often the only way of interacting with non-player characters is to kill them. Killing people should be one of the potential ways of resolving conflicts, because that is reality, but negotiation must be another.

          So this is a game in which rich interaction with non-player characters is possible. The NPCs have individual knowledge and individual agency: they have intentions, aspirations and desires. They also have a wide dynamic repertoire of speech.

          Previous essays that are relevant

          Organic and emergent game-play

          If a world is dynamically populated, with dynamic allocation of livelihoods then several aspects of gameplay will emerge organically. First, of course, is just exploring; in a dynamically changing world there will always be more to explore, and it will be different in each restart of the game.

          @@ -69,7 +69,7 @@
        • Conqueror: how many total vassales, recursively, has this agent?
        • Citizen: really, really tricky. Probably what is the average esteem for this agent among all agents within a specified radius? - although this will score more highly for agents who have taken part in notable events, and what I’m really thinking of for my ideal ‘good citizen’ is someone who really hasn’t.
        • -

          So each agent is assigned - by the dreaded random number generator - one top level goal when they are instantiated. I don’t think it’s necessary to model change of top level goals, although of course that does happen in real life; however, although each agent has one top level goal, they will have lower l evel ‘stretch goals’ also taken from this list: so at each decision point in an agent’s planning loop, if base level needs are satisfied and progress on the top level goal is blocked, actions should be chosen which progress one of the lower goals. Indeed, it’s possible that all agents could have all goals, but randomly ordered.

          +

          So each agent is assigned - by the dreaded random number generator - one top level goal when they are instantiated. I don’t think it’s necessary to model change of top level goals, although of course that does happen in real life; however, although each agent has one top level goal, they will have lower level ‘stretch goals’ also taken from this list: so at each decision point in an agent’s planning loop, if base level needs are satisfied and progress on the top level goal is blocked, actions should be chosen which progress one of the lower goals. Indeed, it’s possible that all agents could have all goals, but randomly ordered.

          At the lowest level there are immediate needs goals every agent must satisfy: food for tonight, a safe place to stay tonight, food for next year, a safe place to stay next year.

          On screen and off screen

          If we’re going to have a very large world with a very large number of characters (as an order of magnitude number, say 100,000), then obviously we cannot plan in detail every time each character lifts a cup to their lips to drink. When a character is on screen we must represent small actions, and at some level these must be planned for. But when they’re off screen, that’s just wasted computation. The only actions we need to plan are life altering actions, such as:

          @@ -125,4 +125,4 @@

          Generally, if a merchant buys goods in an ariston’s market, or sells goods in the ariston’s market, then the economy benefits and the ariston benefits from that; so the ‘tax’ element is part of the market markup. But if a caravan passes through an ariston’s territory without stopping at a market, there’s probably a tax of about 5% of value.

          Generally, an ariston’s army will control outlawry within the ariston’s domain, so outlaw encounters within a domain are unlikely. Soldiers could be able seek bribes, but that would bring a strongly negative impact on favour and I’m not sure it’s work modelling.

          Other habitual travellers: gossipers

          -

          Apart from merchants, the habitual travellers are diplomats (who, in the craft tree, are similar to chancellors) and minstrels (who aren’t on the craft tree but should be); and vagrants. However, vagrants almost certainly don’t have positive favour, so aren’t likely to be useful gossip agents. Each game day, every habitual traveller within the ‘local’ gossip bubble exchanges some items of gossip with the nearest innkeeper to their current location. In the second and third gossip bubbles, it’s probably only more favoured gossip agents who do this. See The spread of knowledge in a large game world

          \ No newline at end of file +

          Apart from merchants, the habitual travellers are diplomats (who, in the craft tree, are similar to chancellors) and minstrels (who aren’t on the craft tree but should be); and vagrants. However, vagrants almost certainly don’t have positive favour, so aren’t likely to be useful gossip agents. Each game day, every habitual traveller within the ‘local’ gossip bubble exchanges some items of gossip with the nearest innkeeper to their current location. In the second and third gossip bubbles, it’s probably only more favoured gossip agents who do this. See The spread of knowledge in a large game world

        \ No newline at end of file diff --git a/docs/codox/modelling_trading_cost_and_risk.html b/docs/codox/modelling_trading_cost_and_risk.html index 5292eb6..58c4b74 100644 --- a/docs/codox/modelling_trading_cost_and_risk.html +++ b/docs/codox/modelling_trading_cost_and_risk.html @@ -2,6 +2,6 @@ ""> 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.

        +

        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/src/the_great_game/gossip/news_items.clj b/src/the_great_game/gossip/news_items.clj index f2572d4..faf7c45 100644 --- a/src/the_great_game/gossip/news_items.clj +++ b/src/the_great_game/gossip/news_items.clj @@ -218,22 +218,28 @@ ([gossip item follow-inferences?] (if (interesting-item? gossip item) - (let [g (assoc gossip :knowledge - (cons - (assoc - item - :nth-hand (if - (number? (:nth-hand item)) - (inc (:nth-hand item)) - 1) - :date (if (number? (:date item)) (:date item) (game-time)) - :location (degrade-location gossip (:location item)) - ;; ought to degratde the location - ;; ought to maybe-degrade characters we're not yet interested in - ) - ;; ought not to add knowledge items we already have, except - ;; to replace if new item is of increased specificity - (:knowledge gossip)))] + (let + [g (assoc + gossip + :knowledge + (cons + (assoc + item + :nth-hand (if + (number? (:nth-hand item)) + (inc (:nth-hand item)) + 1) + :time-stamp (if + (number? (:time-stamp item)) + (:time-stamp item) + (game-time)) + :location (degrade-location gossip (:location item)) + ;; ought to degratde the location + ;; ought to maybe-degrade characters we're not yet interested in + ) + ;; ought not to add knowledge items we already have, except + ;; to replace if new item is of increased specificity + (:knowledge gossip)))] (if follow-inferences? (assoc g diff --git a/test/the_great_game/gossip/news_items_test.clj b/test/the_great_game/gossip/news_items_test.clj index db5ee89..ca7788e 100644 --- a/test/the_great_game/gossip/news_items_test.clj +++ b/test/the_great_game/gossip/news_items_test.clj @@ -119,7 +119,7 @@ ;; dates will not be and cannot be expected to be equal actual (make-all-inferences {:verb :rape :actor :adam :other :belinda :location :test-home}) - actual' (set (map #(dissoc % :date) actual))] + actual' (set (map #(dissoc % :time-stamp) actual))] (is (= actual' expected))))) (deftest learn-tests @@ -130,5 +130,5 @@ actual (learn-news-item {:home [{0, 0} :test-home] :knowledge []} {:verb :sex :actor :adam :other :belinda :location [:test-home]}) - actual' (assoc actual :knowledge (vec (map #(dissoc % :date) (:knowledge actual))))] + actual' (assoc actual :knowledge (vec (map #(dissoc % :time-stamp) (:knowledge actual))))] (is (= actual' expected))))) From bc7e0f44d392e8ac7eb4ad7e6044c8181afbb511 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 15 Apr 2020 18:46:43 +0100 Subject: [PATCH 07/12] Further work on documentation and thinking. Not all tests pass. --- doc/Baking-the-world.md | 4 +- ...read-of-knowledge-in-a-large-game-world.md | 32 +++++++++++--- doc/economy.md | 2 +- doc/intro.md | 16 +++---- doc/modelling_trading_cost_and_risk.md | 2 +- docs/codox/Baking-the-world.html | 4 +- ...ad-of-knowledge-in-a-large-game-world.html | 32 +++++++++++--- docs/codox/economy.html | 2 +- docs/codox/intro.html | 14 +++--- .../modelling_trading_cost_and_risk.html | 2 +- .../the-great-game.gossip.news-items.html | 3 +- src/the_great_game/gossip/news_items.clj | 43 ++++++++++++------- .../the_great_game/gossip/news_items_test.clj | 4 +- 13 files changed, 108 insertions(+), 52 deletions(-) diff --git a/doc/Baking-the-world.md b/doc/Baking-the-world.md index c41c6cc..a2f3739 100644 --- a/doc/Baking-the-world.md +++ b/doc/Baking-the-world.md @@ -16,7 +16,7 @@ But, before going there, to summarise the proving stage. The inputs are: -1. A raster height map (although this could be randomly generated using any one of many fractal algorithms) - this probably uses ideas from [tessellated multi-layer height map](../../2013/07/tessellated-multi-layer-height-map.html); +1. A raster height map (although this could be randomly generated using any one of many fractal algorithms) - this probably uses ideas from [tessellated multi-layer height map](https://blog.journeyman.cc/2013/07/tessellated-multi-layer-height-map.html); 1. Optionally, a raster rainfall map at 1km resolution (although my personal preference is that this should be generated procedurally from the height map). The outputs are @@ -26,7 +26,7 @@ 1. A database of settlers and their settlements, such that the settlements have x,y co-ordinates; 1. A vector road map. - In this sense, the 'biome map' is just the end state of a [Microworld](../../2014/08/modelling-settlement-with-cellular.html) run. The 'biomes' include things like 'forest', 'scrub', 'heath', 'pasture', but they may also include human settlement, and even settlement by different cultural groups. + In this sense, the 'biome map' is just the end state of a [Microworld](https://blog.journeyman.cc/2014/08/modelling-settlement-with-cellular.html) run. The 'biomes' include things like 'forest', 'scrub', 'heath', 'pasture', but they may also include human settlement, and even settlement by different cultural groups. This gives us all we need to vegetate and furnish the world. When rendering each square metre we have diff --git a/doc/The-spread-of-knowledge-in-a-large-game-world.md b/doc/The-spread-of-knowledge-in-a-large-game-world.md index 40e842b..937da44 100644 --- a/doc/The-spread-of-knowledge-in-a-large-game-world.md +++ b/doc/The-spread-of-knowledge-in-a-large-game-world.md @@ -27,24 +27,46 @@ _This version of this essay has been adapted to use the code in `the-great-game. Obviously, news is more valuable if the people involved are important or notorious: the significance of a story is probably the product of the significance of the people concerned. - So a news item becomes a tuple + So a news item becomes a map with keys similar to - `(days-old nth-hand significance action (actors))` + [:verb :actor :other :location :nth-hand :time-stamp] + + The [exact keys for each verb are specified here](the-great-game.gossip.news-items.html#var-news-topics). for example - `(54 2 10 'killed '(fred joe))` + {:verb :kill, + :actor {:id :fred :name "Fred"}, + :other {:id :joe :name "Joe"}, + :location [{45467 78613} :hanshua :plateau], + :nth-hand 3, + :time-stamp 17946463} - meaning 'I spoke to a man who'd spoken to a man who said he saw notorious fred kill well-liked joe on 54 days ago'. Obviously, the non-player character must be able to construct a natural language sentence from the tuple when speaking within the hearing of a player character, but there's no need for a non-player character to produce a natural language sentence for another non-player character to parse; instead they can just exchange tuples. + meaning 'I spoke to a man who'd spoken to a man who said he saw fred kill joe at the game time represented by the time stamp 17946463, at the coordinates {45467 78613} in Hans'hua on the Plateau'. Obviously, the non-player character must be able to construct a natural language sentence from the tuple when speaking within the hearing of a player character, but there's no need for a non-player character to produce a natural language sentence for another non-player character to parse; instead they can just exchange tuples. But if we're exchanging knowledge between agents, then agents must have a means of representing knowledge. This knowledge is an association between subjects and sets of statement, such that when the agent learns the statement - `(54 2 10 'killed '(fred joe))` + {:verb :kill, + :actor {:id :fred :name "Fred"}, + :other {:id :joe :name "Joe"}, + :location [{45467 78613} :hanshua :plateau], + :nth-hand 3, + :time-stamp 17946463} it adds this statement (with the 2 incremented to 3) to the set of statements it knows about fred and also to the set of statements it knows about joe. It's possible that the receiving agent could then challenge for further statements about fred and/or joe, the automated equivalent of a 'who's joe?' question. There could be feedback in this. Fred's and joe's significance scores could be incremented for each character to whom the statement is passed on, increasing the likeliness that fred, at least, would feature in more news stories in future. There needs also to be some means of managing how the non-player character's attitude to the subjects of the statement are affected. For example, If fred kills joe, and the character (say bill) receiving the news feels positively towards joe, then bill's attitude to fred should become sharply more hostile. If bill feels neutral about joe, then bill's attitude to fred should still become a bit more hostile, since killing people is on the whole a bad thing. But it bill feels very hostile towards joe, then bill's attitude to fred should become more friendly. +But also, the added knowledge is *degraded*. If the recipient isn't from Hans'hua, the exact location isn't meaningful to them, for example. If the recipient isn't interested in Joe, precisely who was killed may be forgotten. So what is stored could become: + + {:verb :kill, + :actor {:id :fred :name "Fred"}, + :location [:hanshua :plateau], + :nth-hand 4, + :time-stamp 17946463} + +The timestamp could also be degraded, or lost altother - although how exactly this is represnted I'm not certain. Someone interested in the incident may remember 'it was exactly 17 days ago', whereas someone else may remember that it was 'this month, I think'. + Obviously the rate of decay, and the degree of randomness, of the news-passing algorithm would need to be tuned, but this schema seems to me to describe a system with the following features: * Non-player characters can respond to questions about significant things which happen in the world - without it all having to be scripted diff --git a/doc/economy.md b/doc/economy.md index 45ed00a..15111dc 100644 --- a/doc/economy.md +++ b/doc/economy.md @@ -1,6 +1,6 @@ # 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. +Broadly this essay extends ideas presented in [Populating a game world](Populating-a-game-world.html), q.v. ## Primary producers diff --git a/doc/intro.md b/doc/intro.md index 3c67ed7..3ae3526 100644 --- a/doc/intro.md +++ b/doc/intro.md @@ -19,7 +19,7 @@ that I need to be able to use it to tell stories, in order to create initial threads of narrative from which players can start their exploration. Note that, by 'conflict', here, I explicitly do not mean 'killing people', -or even 'killing non-player characters'. I have [written extensively](https://blog.journeyman.cc/2015/02/voice-acting-considered-harmful.html) +or even 'killing non-player characters'. I have [written extensively](Voice-acting-considered-harmful.html) about the problem in many current video games that all too often the only way of interacting with non-player characters is to kill them. Killing people should be one of the potential ways of resolving conflicts, because @@ -32,18 +32,18 @@ repertoire of speech. ## Previous essays that are relevant -* [The spread of knowledge in a large game world](https://blog.journeyman.cc/2008/04/the-spread-of-knowledge-in-large-game.html) (2008) discusses what individual non-player characters know, and how to model dynamic updates to their knowledge; +* [The spread of knowledge in a large game world](The-spread-of-knowledge-in-a-large-game-world.html) (2008) discusses what individual non-player characters know, and how to model dynamic updates to their knowledge; * [Settling a game world](https://blog.journeyman.cc/2009/12/settling-game-world.html) (2009) gives rough outline of ideas about creating the environment, including modelling things like soil fertility, local building materials, and consequently local architecture; * [Tessellated multi-layer height map](https://blog.journeyman.cc/2013/07/tessellated-multi-layer-height-map.html) (2013) gives ideas for how a designed geography for a very large world could be stored relatively economically; * [Genetic Buildings](https://blog.journeyman.cc/2013/07/genetic-buildings.html) (2013) sketches algorithms which would allow procedurally-generated buildings to be site-appropriate, broadly variable and reproducable; -* [Populating a game world](https://blog.journeyman.cc/2013/07/populating-game-world.html) (2013) provides outline algorithms for how a world can be populated, and how organic mixes of trades and crafts can be modelled; +* [Populating a game world](Populating-a-game-world.html) (2013) provides outline algorithms for how a world can be populated, and how organic mixes of trades and crafts can be modelled; * [Modelling the change from rural to urban](https://blog.journeyman.cc/2013/07/modelling-change-from-rural-to-urban.html) (2013) describes the idea of procedurally modelling settlements, but it is grid-based and not particularly satisfactory and has largely been superceded in my thinking; -* [Of pigeons, and long distance messaging in a game world]() (2013) builds on ideas about flows of information; +* [Of pigeons, and long distance messaging in a game world](https://blog.journeyman.cc/2013/10/of-pigeons-and-long-distance-messaging.html) (2013) builds on ideas about flows of information; * [Modelling rural to urban, take two](https://blog.journeyman.cc/2013/10/modelling-rural-to-urban-take-two.html) (2013) revisited the idea of modelling organic settlement structures, trying to find algorithms which would naturally produce more persuasive settlement models, including further ideas on the procedural generation of buildings; * [More on modelling rivers](https://blog.journeyman.cc/2014/09/more-on-modelling-rivers.html) (2014) talks about modelling hydrology, with implications for soil fertility; * [Modelling settlement with cellular automata](https://blog.journeyman.cc/2014/08/modelling-settlement-with-cellular.html) (2014) talks about successful implementation of algorithms to model vegetative environment, human settlement and the impact of human settlement on the environment; * [Voice acting considered harmful](https://blog.journeyman.cc/2015/02/voice-acting-considered-harmful.html) (2015) outlines the ideas behind full speech interaction with non-player characters, and modelling what those non-player characters should be able to speak about; -* [Baking the world](https://blog.journeyman.cc/2019/05/baking-world.html) (2019) an outline of the overall process of creating a world. +* [Baking the world](Baking-the-world.html) (2019) an outline of the overall process of creating a world. ## Organic and emergent game-play @@ -183,8 +183,8 @@ easy: So each agent is assigned - by the dreaded random number generator - one top level goal when they are instantiated. I don't think it's necessary to model change of top level goals, although of course that does happen in real life; -however, although each agent has one top level goal, they will have lower l -evel 'stretch goals' also taken from this list: so at each decision point in +however, although each agent has one top level goal, they will have lower +level 'stretch goals' also taken from this list: so at each decision point in an agent's planning loop, if base level needs are satisfied and progress on the top level goal is blocked, actions should be chosen which progress one of the lower goals. Indeed, it's possible that all agents could have all @@ -379,4 +379,4 @@ Each game day, every habitual traveller within the 'local' gossip bubble exchanges some items of gossip with the nearest innkeeper to their current location. In the second and third gossip bubbles, it's probably only more favoured gossip agents who do this. See -[The spread of knowledge in a large game world](https://blog.journeyman.cc/2008/04/the-spread-of-knowledge-in-large-game.html) +[The spread of knowledge in a large game world](The-spread-of-knowledge-in-a-large-game-world.html) diff --git a/doc/modelling_trading_cost_and_risk.md b/doc/modelling_trading_cost_and_risk.md index 3574d5b..78ae9dd 100644 --- a/doc/modelling_trading_cost_and_risk.md +++ b/doc/modelling_trading_cost_and_risk.md @@ -2,7 +2,7 @@ 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. +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](the-great-game.gossip.gossip.html) 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/Baking-the-world.html b/docs/codox/Baking-the-world.html index 5924b7f..c588bd8 100644 --- a/docs/codox/Baking-the-world.html +++ b/docs/codox/Baking-the-world.html @@ -10,7 +10,7 @@

        Then, when the landscape has developed - the areas of forest, scrub, open meadow, moorland, savanah and desert are determined, the rivers plotted, the settlers moved in, their trades determined and their settlements allocated, the roadways which link settlements routed, river crossings and ports defined - the proving process ends, and the world is turned over to the plot-writers, quest builders and designers, for a process we can see as analogous to kneading.

        But, before going there, to summarise the proving stage. The inputs are:

          -
        1. A raster height map (although this could be randomly generated using any one of many fractal algorithms) - this probably uses ideas from tessellated multi-layer height map;
        2. +
        3. A raster height map (although this could be randomly generated using any one of many fractal algorithms) - this probably uses ideas from tessellated multi-layer height map;
        4. Optionally, a raster rainfall map at 1km resolution (although my personal preference is that this should be generated procedurally from the height map).

        The outputs are

        @@ -20,7 +20,7 @@
      • A database of settlers and their settlements, such that the settlements have x,y co-ordinates;
      • A vector road map.
      • -

        In this sense, the ‘biome map’ is just the end state of a Microworld run. The ‘biomes’ include things like ‘forest’, ‘scrub’, ‘heath’, ‘pasture’, but they may also include human settlement, and even settlement by different cultural groups.

        +

        In this sense, the ‘biome map’ is just the end state of a Microworld run. The ‘biomes’ include things like ‘forest’, ‘scrub’, ‘heath’, ‘pasture’, but they may also include human settlement, and even settlement by different cultural groups.

        This gives us all we need to vegetate and furnish the world. When rendering each square metre we have

        1. The x,y coordinates, obviously;
        2. diff --git a/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html b/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html index df5c42d..9dd26f5 100644 --- a/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html +++ b/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html @@ -18,15 +18,37 @@
        3. Plot events, flagged as events by the game designer
        4. Obviously, news is more valuable if the people involved are important or notorious: the significance of a story is probably the product of the significance of the people concerned.

          -

          So a news item becomes a tuple

          -

          (days-old nth-hand significance action (actors))

          +

          So a news item becomes a map with keys similar to

          +
          [:verb :actor :other :location :nth-hand :time-stamp]
          +
          +

          The exact keys for each verb are specified here.

          for example

          -

          (54 2 10 'killed '(fred joe))

          -

          meaning ‘I spoke to a man who’d spoken to a man who said he saw notorious fred kill well-liked joe on 54 days ago’. Obviously, the non-player character must be able to construct a natural language sentence from the tuple when speaking within the hearing of a player character, but there’s no need for a non-player character to produce a natural language sentence for another non-player character to parse; instead they can just exchange tuples.

          +
           {:verb :kill,
          + :actor {:id :fred :name "Fred"},
          + :other {:id :joe :name "Joe"},
          + :location [{45467 78613} :hanshua :plateau],
          + :nth-hand 3,
          + :time-stamp 17946463}
          +
          +

          meaning ‘I spoke to a man who’d spoken to a man who said he saw fred kill joe at the game time represented by the time stamp 17946463, at the coordinates {45467 78613} in Hans’hua on the Plateau’. Obviously, the non-player character must be able to construct a natural language sentence from the tuple when speaking within the hearing of a player character, but there’s no need for a non-player character to produce a natural language sentence for another non-player character to parse; instead they can just exchange tuples.

          But if we’re exchanging knowledge between agents, then agents must have a means of representing knowledge. This knowledge is an association between subjects and sets of statement, such that when the agent learns the statement

          -

          (54 2 10 'killed '(fred joe))

          +
           {:verb :kill,
          + :actor {:id :fred :name "Fred"},
          + :other {:id :joe :name "Joe"},
          + :location [{45467 78613} :hanshua :plateau],
          + :nth-hand 3,
          + :time-stamp 17946463}
          +

          it adds this statement (with the 2 incremented to 3) to the set of statements it knows about fred and also to the set of statements it knows about joe. It’s possible that the receiving agent could then challenge for further statements about fred and/or joe, the automated equivalent of a ‘who’s joe?’ question.

          There could be feedback in this. Fred’s and joe’s significance scores could be incremented for each character to whom the statement is passed on, increasing the likeliness that fred, at least, would feature in more news stories in future. There needs also to be some means of managing how the non-player character’s attitude to the subjects of the statement are affected. For example, If fred kills joe, and the character (say bill) receiving the news feels positively towards joe, then bill’s attitude to fred should become sharply more hostile. If bill feels neutral about joe, then bill’s attitude to fred should still become a bit more hostile, since killing people is on the whole a bad thing. But it bill feels very hostile towards joe, then bill’s attitude to fred should become more friendly.

          +

          But also, the added knowledge is degraded. If the recipient isn’t from Hans’hua, the exact location isn’t meaningful to them, for example. If the recipient isn’t interested in Joe, precisely who was killed may be forgotten. So what is stored could become:

          +
           {:verb :kill,
          + :actor {:id :fred :name "Fred"},
          + :location [:hanshua :plateau],
          + :nth-hand 4,
          + :time-stamp 17946463}
          +
          +

          The timestamp could also be degraded, or lost altother - although how exactly this is represnted I’m not certain. Someone interested in the incident may remember ‘it was exactly 17 days ago’, whereas someone else may remember that it was ‘this month, I think’.

          Obviously the rate of decay, and the degree of randomness, of the news-passing algorithm would need to be tuned, but this schema seems to me to describe a system with the following features:

          • Non-player characters can respond to questions about significant things which happen in the world - without it all having to be scripted
          • diff --git a/docs/codox/economy.html b/docs/codox/economy.html index d81537f..b5c7ecb 100644 --- a/docs/codox/economy.html +++ b/docs/codox/economy.html @@ -1,7 +1,7 @@ Game world economy

            Game world economy

            -

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

            +

            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.

            diff --git a/docs/codox/intro.html b/docs/codox/intro.html index d16ad24..ebeff11 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -5,22 +5,22 @@

            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?

            It has strong elements of a Role Playing Game, as currently understood; some elements of a Simulation Game; some elements of a God Game. But what I see it as is fundamentally a sandbox in which the player(s) can explore ideas about human conflicts and how to resolve them, without immediate real-world consequences. It’s also a sandbox in which story tellers can tell stories, but that’s essentially a side-effect - a consequence of the fact that I need to be able to use it to tell stories, in order to create initial threads of narrative from which players can start their exploration.

            -

            Note that, by ‘conflict’, here, I explicitly do not mean ‘killing people’, or even ‘killing non-player characters’. I have written extensively about the problem in many current video games that all too often the only way of interacting with non-player characters is to kill them. Killing people should be one of the potential ways of resolving conflicts, because that is reality, but negotiation must be another.

            +

            Note that, by ‘conflict’, here, I explicitly do not mean ‘killing people’, or even ‘killing non-player characters’. I have written extensively about the problem in many current video games that all too often the only way of interacting with non-player characters is to kill them. Killing people should be one of the potential ways of resolving conflicts, because that is reality, but negotiation must be another.

            So this is a game in which rich interaction with non-player characters is possible. The NPCs have individual knowledge and individual agency: they have intentions, aspirations and desires. They also have a wide dynamic repertoire of speech.

            Previous essays that are relevant

            Organic and emergent game-play

            If a world is dynamically populated, with dynamic allocation of livelihoods then several aspects of gameplay will emerge organically. First, of course, is just exploring; in a dynamically changing world there will always be more to explore, and it will be different in each restart of the game.

            @@ -69,7 +69,7 @@
          • Conqueror: how many total vassales, recursively, has this agent?
          • Citizen: really, really tricky. Probably what is the average esteem for this agent among all agents within a specified radius? - although this will score more highly for agents who have taken part in notable events, and what I’m really thinking of for my ideal ‘good citizen’ is someone who really hasn’t.
        -

        So each agent is assigned - by the dreaded random number generator - one top level goal when they are instantiated. I don’t think it’s necessary to model change of top level goals, although of course that does happen in real life; however, although each agent has one top level goal, they will have lower l evel ‘stretch goals’ also taken from this list: so at each decision point in an agent’s planning loop, if base level needs are satisfied and progress on the top level goal is blocked, actions should be chosen which progress one of the lower goals. Indeed, it’s possible that all agents could have all goals, but randomly ordered.

        +

        So each agent is assigned - by the dreaded random number generator - one top level goal when they are instantiated. I don’t think it’s necessary to model change of top level goals, although of course that does happen in real life; however, although each agent has one top level goal, they will have lower level ‘stretch goals’ also taken from this list: so at each decision point in an agent’s planning loop, if base level needs are satisfied and progress on the top level goal is blocked, actions should be chosen which progress one of the lower goals. Indeed, it’s possible that all agents could have all goals, but randomly ordered.

        At the lowest level there are immediate needs goals every agent must satisfy: food for tonight, a safe place to stay tonight, food for next year, a safe place to stay next year.

        On screen and off screen

        If we’re going to have a very large world with a very large number of characters (as an order of magnitude number, say 100,000), then obviously we cannot plan in detail every time each character lifts a cup to their lips to drink. When a character is on screen we must represent small actions, and at some level these must be planned for. But when they’re off screen, that’s just wasted computation. The only actions we need to plan are life altering actions, such as:

        @@ -125,4 +125,4 @@

        Generally, if a merchant buys goods in an ariston’s market, or sells goods in the ariston’s market, then the economy benefits and the ariston benefits from that; so the ‘tax’ element is part of the market markup. But if a caravan passes through an ariston’s territory without stopping at a market, there’s probably a tax of about 5% of value.

        Generally, an ariston’s army will control outlawry within the ariston’s domain, so outlaw encounters within a domain are unlikely. Soldiers could be able seek bribes, but that would bring a strongly negative impact on favour and I’m not sure it’s work modelling.

        Other habitual travellers: gossipers

        -

        Apart from merchants, the habitual travellers are diplomats (who, in the craft tree, are similar to chancellors) and minstrels (who aren’t on the craft tree but should be); and vagrants. However, vagrants almost certainly don’t have positive favour, so aren’t likely to be useful gossip agents. Each game day, every habitual traveller within the ‘local’ gossip bubble exchanges some items of gossip with the nearest innkeeper to their current location. In the second and third gossip bubbles, it’s probably only more favoured gossip agents who do this. See The spread of knowledge in a large game world

        \ No newline at end of file +

        Apart from merchants, the habitual travellers are diplomats (who, in the craft tree, are similar to chancellors) and minstrels (who aren’t on the craft tree but should be); and vagrants. However, vagrants almost certainly don’t have positive favour, so aren’t likely to be useful gossip agents. Each game day, every habitual traveller within the ‘local’ gossip bubble exchanges some items of gossip with the nearest innkeeper to their current location. In the second and third gossip bubbles, it’s probably only more favoured gossip agents who do this. See The spread of knowledge in a large game world

        \ No newline at end of file diff --git a/docs/codox/modelling_trading_cost_and_risk.html b/docs/codox/modelling_trading_cost_and_risk.html index 5292eb6..58c4b74 100644 --- a/docs/codox/modelling_trading_cost_and_risk.html +++ b/docs/codox/modelling_trading_cost_and_risk.html @@ -2,6 +2,6 @@ ""> 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.

        +

        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/the-great-game.gossip.news-items.html b/docs/codox/the-great-game.gossip.news-items.html index 64f1d4c..c2e6806 100644 --- a/docs/codox/the-great-game.gossip.news-items.html +++ b/docs/codox/the-great-game.gossip.news-items.html @@ -1,6 +1,6 @@ -the-great-game.gossip.news-items documentation

        the-great-game.gossip.news-items

        Categories of news events interesting to gossip agents

        degrade-character

        (degrade-character gossip character)

        Return a character specification like this character, but comprising only those properties this gossip is interested in.

        degrade-location

        (degrade-location gossip location)

        Return a location specification like this location, but comprising only those elements this gossip is interested in. If none, return nil.

        infer

        (infer item rule)

        Infer a new knowledge item from this item, following this rule

        interest-in-character

        (interest-in-character gossip character)

        Integer representation of how interesting this character is to this gossip. TODO: this assumes that characters are passed as keywords, but, as documented above, they probably have to be maps, to allow for degradation.

        interest-in-location

        (interest-in-location gossip location)

        Integer representation of how interesting this location is to this gossip.

        interesting-character?

        (interesting-character? gossip character)

        Boolean representation of whether this character is interesting to this gossip.

        interesting-item?

        (interesting-item? gossip item)

        True if anything about this news item is interesting to this gossip.

        interesting-location?

        (interesting-location? gossip item)

        True if the location of this news item is interesting to this gossip.

        interesting-object?

        (interesting-object? gossip object)

        TODO: write docs

        interesting-topic?

        (interesting-topic? gossip topic)

        TODO: write docs

        learn-news-item

        (learn-news-item gossip item)(learn-news-item gossip item follow-inferences?)

        Return a gossip like this gossip, which has learned this news item if it is of interest to them.

        make-all-inferences

        (make-all-inferences item)

        Return a list of knowledge entries inferred from this news item by this gossip.

        news-topics

        Topics of interest to gossip agents. Topics are keyed in this map by their verbs. The keys associated with each topic are the extra pieces of information required to give context to a gossip item. Generally:

        +the-great-game.gossip.news-items documentation

        the-great-game.gossip.news-items

        Categories of news events interesting to gossip agents

        degrade-character

        (degrade-character gossip character)

        Return a character specification like this character, but comprising only those properties this gossip is interested in.

        degrade-location

        (degrade-location gossip location)

        Return a location specification like this location, but comprising only those elements this gossip is interested in. If none, return nil.

        infer

        (infer item rule)

        Infer a new knowledge item from this item, following this rule

        interest-in-character

        (interest-in-character gossip character)

        Integer representation of how interesting this character is to this gossip. TODO: this assumes that characters are passed as keywords, but, as documented above, they probably have to be maps, to allow for degradation.

        interest-in-location

        (interest-in-location gossip location)

        Integer representation of how interesting this location is to this gossip.

        interesting-character?

        (interesting-character? gossip character)

        Boolean representation of whether this character is interesting to this gossip.

        interesting-item?

        (interesting-item? gossip item)

        True if anything about this news item is interesting to this gossip.

        interesting-location?

        (interesting-location? gossip item)

        True if the location of this news item is interesting to this gossip.

        interesting-object?

        (interesting-object? gossip object)

        TODO: write docs

        interesting-topic?

        (interesting-topic? gossip topic)

        TODO: write docs

        learn-news-item

        (learn-news-item gossip item)(learn-news-item gossip item follow-inferences?)

        Return a gossip like this gossip, which has learned this news item if it is of interest to them.

        make-all-inferences

        (make-all-inferences item)

        Return a list of knowledge entries inferred from this news item by this gossip.

        news-topics

        Topics of interest to gossip agents. Topics are keyed in this map by their verbs. The keys associated with each topic are the extra pieces of information required to give context to a gossip item. Generally:

        • actor is the id of the character who it is reported performed the action;
        • other is the id of the character on whom it is reported the action was performed;
        • @@ -11,6 +11,7 @@

          Notes:

          Characters:

          TODO but note that at most all the receiver can learn about a character from a news item is what the giver knows about that character, degraded by what the receiver finds interesting about them. If we just pass the id here, then either the receiver knows everything in the database about the character, or else the receiver knows nothing at all about the character. Neither is desirable. Further thought needed.

          +

          By implication, the character values passed should include ^all^ the information the giver knows about the character; that can then be degraded as the receiver stores only that segment which the receiver finds interesting.

          Locations:

          A ‘location’ value is a list comprising at most the x/y coordinate location and the ids of the settlement and region (possibly hierarchically) that contain the location. If the x/y is not local to the home of the receiving agent, they won’t remember it and won’t pass it on; if any of the ids are not interesting So location information will degrade progressively as the item is passed along.

          It is assumed that the :home of a character is a location in this sense.

          diff --git a/src/the_great_game/gossip/news_items.clj b/src/the_great_game/gossip/news_items.clj index f2572d4..dfe30ee 100644 --- a/src/the_great_game/gossip/news_items.clj +++ b/src/the_great_game/gossip/news_items.clj @@ -41,6 +41,11 @@ character, or else the receiver knows nothing at all about the character. Neither is desirable. Further thought needed. + By implication, the character values passed should include ^all^ the + information the giver knows about the character; that can then be degraded + as the receiver stores only that segment which the receiver finds + interesting. + ##### Locations: A 'location' value is a list comprising at most the x/y coordinate location @@ -218,22 +223,28 @@ ([gossip item follow-inferences?] (if (interesting-item? gossip item) - (let [g (assoc gossip :knowledge - (cons - (assoc - item - :nth-hand (if - (number? (:nth-hand item)) - (inc (:nth-hand item)) - 1) - :date (if (number? (:date item)) (:date item) (game-time)) - :location (degrade-location gossip (:location item)) - ;; ought to degratde the location - ;; ought to maybe-degrade characters we're not yet interested in - ) - ;; ought not to add knowledge items we already have, except - ;; to replace if new item is of increased specificity - (:knowledge gossip)))] + (let + [g (assoc + gossip + :knowledge + (cons + (assoc + item + :nth-hand (if + (number? (:nth-hand item)) + (inc (:nth-hand item)) + 1) + :time-stamp (if + (number? (:time-stamp item)) + (:time-stamp item) + (game-time)) + :location (degrade-location gossip (:location item)) + ;; ought to degratde the location + ;; ought to maybe-degrade characters we're not yet interested in + ) + ;; ought not to add knowledge items we already have, except + ;; to replace if new item is of increased specificity + (:knowledge gossip)))] (if follow-inferences? (assoc g diff --git a/test/the_great_game/gossip/news_items_test.clj b/test/the_great_game/gossip/news_items_test.clj index db5ee89..ca7788e 100644 --- a/test/the_great_game/gossip/news_items_test.clj +++ b/test/the_great_game/gossip/news_items_test.clj @@ -119,7 +119,7 @@ ;; dates will not be and cannot be expected to be equal actual (make-all-inferences {:verb :rape :actor :adam :other :belinda :location :test-home}) - actual' (set (map #(dissoc % :date) actual))] + actual' (set (map #(dissoc % :time-stamp) actual))] (is (= actual' expected))))) (deftest learn-tests @@ -130,5 +130,5 @@ actual (learn-news-item {:home [{0, 0} :test-home] :knowledge []} {:verb :sex :actor :adam :other :belinda :location [:test-home]}) - actual' (assoc actual :knowledge (vec (map #(dissoc % :date) (:knowledge actual))))] + actual' (assoc actual :knowledge (vec (map #(dissoc % :time-stamp) (:knowledge actual))))] (is (= actual' expected))))) From 66bf7ad8569d6af8b0545cbbfcb4bed1a99cc42d Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 15 Apr 2020 20:22:01 +0100 Subject: [PATCH 08/12] Trivial typo --- docs/codox/the-great-game.gossip.news-items.html | 2 +- src/the_great_game/gossip/news_items.clj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/codox/the-great-game.gossip.news-items.html b/docs/codox/the-great-game.gossip.news-items.html index c2e6806..ee90317 100644 --- a/docs/codox/the-great-game.gossip.news-items.html +++ b/docs/codox/the-great-game.gossip.news-items.html @@ -11,7 +11,7 @@

          Notes:

          Characters:

          TODO but note that at most all the receiver can learn about a character from a news item is what the giver knows about that character, degraded by what the receiver finds interesting about them. If we just pass the id here, then either the receiver knows everything in the database about the character, or else the receiver knows nothing at all about the character. Neither is desirable. Further thought needed.

          -

          By implication, the character values passed should include ^all^ the information the giver knows about the character; that can then be degraded as the receiver stores only that segment which the receiver finds interesting.

          +

          By implication, the character values passed should include all the information the giver knows about the character; that can then be degraded as the receiver stores only that segment which the receiver finds interesting.

          Locations:

          A ‘location’ value is a list comprising at most the x/y coordinate location and the ids of the settlement and region (possibly hierarchically) that contain the location. If the x/y is not local to the home of the receiving agent, they won’t remember it and won’t pass it on; if any of the ids are not interesting So location information will degrade progressively as the item is passed along.

          It is assumed that the :home of a character is a location in this sense.

          diff --git a/src/the_great_game/gossip/news_items.clj b/src/the_great_game/gossip/news_items.clj index dfe30ee..7ebf969 100644 --- a/src/the_great_game/gossip/news_items.clj +++ b/src/the_great_game/gossip/news_items.clj @@ -41,7 +41,7 @@ character, or else the receiver knows nothing at all about the character. Neither is desirable. Further thought needed. - By implication, the character values passed should include ^all^ the + By implication, the character values passed should include *all* the information the giver knows about the character; that can then be degraded as the receiver stores only that segment which the receiver finds interesting. From efb4a9f46de53b55c88da5ff3be577dbf7e8c2b1 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Thu, 16 Apr 2020 12:24:40 +0100 Subject: [PATCH 09/12] Minor fixes --- src/the_great_game/gossip/news_items.clj | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/the_great_game/gossip/news_items.clj b/src/the_great_game/gossip/news_items.clj index 7ebf969..2b02479 100644 --- a/src/the_great_game/gossip/news_items.clj +++ b/src/the_great_game/gossip/news_items.clj @@ -65,7 +65,7 @@ { ;; A significant attack is interesting whether or not it leads to deaths :attack {:verb :attack :keys [:actor :other :location]} ;; Deaths of characters may be interesting - :die {:verb :attack :keys [:actor :location]} + :die {:verb :die :keys [:actor :location]} ;; Deliberate killings are interesting. :kill {:verb :kill :keys [:actor :other :location] :inferences [{:verb :die :actor :other :other :nil}]} @@ -127,7 +127,7 @@ (if-let [home (:home gossip)] (let [d (distance-between location home) i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should - ;;fall of with distance from home, but possibly on a log scale + ;;fall off with distance from home, but possibly on a log scale ] (if (> i 1) i 0)) 0) @@ -185,8 +185,8 @@ (declare learn-news-item) (defn make-all-inferences - "Return a list of knowledge entries inferred from this news `item` by this - `gossip`." + "Return a list of knowledge entries that can be inferred from this news + `item`." [item] (set (reduce @@ -239,10 +239,9 @@ (:time-stamp item) (game-time)) :location (degrade-location gossip (:location item)) - ;; ought to degratde the location - ;; ought to maybe-degrade characters we're not yet interested in + ;; TODO: ought to maybe-degrade characters we're not yet interested in ) - ;; ought not to add knowledge items we already have, except + ;; TODO: ought not to add knowledge items we already have, except ;; to replace if new item is of increased specificity (:knowledge gossip)))] (if follow-inferences? From faf3dae725766cd7a95209f20b975e8d9fe86783 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sat, 18 Apr 2020 16:58:52 +0100 Subject: [PATCH 10/12] Experimenting with defprotocol and defrecord. --- src/the_great_game/agent/agent.clj | 38 +++++++++++++++++++++- src/the_great_game/objects/container.clj | 11 +++++++ src/the_great_game/objects/game-object.clj | 19 +++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/the_great_game/objects/container.clj create mode 100644 src/the_great_game/objects/game-object.clj diff --git a/src/the_great_game/agent/agent.clj b/src/the_great_game/agent/agent.clj index 7a17103..7d3f999 100644 --- a/src/the_great_game/agent/agent.clj +++ b/src/the_great_game/agent/agent.clj @@ -1,7 +1,43 @@ (ns the-great-game.agent.agent - "Anything in the game world with agency") + "Anything in the game world with agency" + (:require [the-great-game.objects.object :refer [ProtoObject]] + [the-great-game.objects.container :refer [ProtoContainer]])) ;; hierarchy of needs probably gets implemented here ;; I'm probably going to want to defprotocol stuff, to define the hierarchy ;; of things in the gameworld; either that or drop to Java, wich I'd rather not do. +(defprotocol ProtoAgent + "An object which can act in the world" + (act + [actor world circle] + "Allow `actor` to do something in this `world`, in the context of this + `circle`; return the new state of the actor if something was done, `nil` + if nothing was done. Circle is expected to be one of + + * `:active` - actors within visual/audible range of the player + character; + * `:pending` - actors not in the active circle, but sufficiently close + to it that they may enter the active circle within a short period; + * `:background` - actors who are active in the background in order to + handle trade, news, et cetera; + * `other` - actors who are not members of any other circle, although + I'm not clear whether it would ever be appropriate to invoke an + `act` method on them. + + The `act` method *must not* have side effects; it must *only* return a + new state. If the actor's intention is to seek to change the state of + something else in the game world, it must add a representation of that + intention to the sequence which will be returned by its + `pending-intentions` method.") + (pending-intentions + [actor] + "Returns a sequence of effects an actor intends, as a consequence of + acting. The encoding of these is not yet defined.")) + +;; (defrecord Agent +;; "A default agent." +;; ProtoObject +;; ProtoContainer +;; ProtoAgent +;; ) diff --git a/src/the_great_game/objects/container.clj b/src/the_great_game/objects/container.clj new file mode 100644 index 0000000..80a18ce --- /dev/null +++ b/src/the_great_game/objects/container.clj @@ -0,0 +1,11 @@ +(ns the-great-game.objects.container + (:require + [the-great-game.objects.game-object :refer :all])) + +(defprotocol ProtoContainer + (contents + [container] + "Return a sequence of the contents of this `container`, or `nil` if empty.") + (is-empty? + [container] + "Return `true` if this `container` is empty, else `false`.")) diff --git a/src/the_great_game/objects/game-object.clj b/src/the_great_game/objects/game-object.clj new file mode 100644 index 0000000..25c878c --- /dev/null +++ b/src/the_great_game/objects/game-object.clj @@ -0,0 +1,19 @@ +(ns the-great-game.objects.game-object + "Anything at all in the game world") + +(defprotocol ProtoObject + "An object in the world" + (id [object] "Returns the unique id of this object.") + (reify-object + [object] + "Adds this `object` to the global object list. If the `object` has a + non-nil value for its `id` method, keys it to that id - **but** if the + id value is already in use, throws a hard exception. Returns the id to + which the object is keyed in the global object list.")) + +(defrecord GameObject + [id] + ;; "An object in the world" + ProtoObject + (id [_] id) + (reify-object [object] "TODO: doesn't work yet")) From 70b139530bc051474536353679378c43c9086fcc Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 24 May 2020 10:40:48 +0100 Subject: [PATCH 11/12] Recovery from massive git messup. --- .gitignore | 2 +- doc/Pathmaking.md | 114 ++++ docs/cloverage/index.html | 52 +- .../the_great_game/agent/agent.clj.html | 120 +++- .../the_great_game/gossip/news_items.clj.html | 514 +++++++++--------- .../the_great_game/objects/container.clj.html | 41 ++ .../objects/game_object.clj.html | 65 +++ docs/codox/Baking-the-world.html | 2 +- docs/codox/Pathmaking.html | 79 +++ docs/codox/Populating-a-game-world.html | 2 +- docs/codox/Settling-a-game-world.html | 2 +- ...ad-of-knowledge-in-a-large-game-world.html | 2 +- .../Voice-acting-considered-harmful.html | 2 +- docs/codox/economy.html | 2 +- docs/codox/index.html | 2 +- docs/codox/intro.html | 2 +- .../modelling_trading_cost_and_risk.html | 2 +- docs/codox/naming-of-characters.html | 2 +- docs/codox/on-dying.html | 2 +- docs/codox/orgnic-quests.html | 2 +- docs/codox/sandbox.html | 2 +- docs/codox/sexual-dimorphism.html | 2 +- docs/codox/the-great-game.agent.agent.html | 9 +- docs/codox/the-great-game.gossip.gossip.html | 2 +- .../the-great-game.gossip.news-items.html | 2 +- .../the-great-game.merchants.markets.html | 2 +- ...e-great-game.merchants.merchant-utils.html | 2 +- .../the-great-game.merchants.merchants.html | 2 +- .../the-great-game.merchants.planning.html | 2 +- ...reat-game.merchants.strategies.simple.html | 2 +- .../the-great-game.objects.container.html | 3 + .../the-great-game.objects.game-object.html | 3 + docs/codox/the-great-game.time.html | 2 +- docs/codox/the-great-game.utils.html | 2 +- docs/codox/the-great-game.world.location.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 +- src/the_great_game/agent/agent.clj | 2 +- .../{game-object.clj => game_object.clj} | 0 40 files changed, 767 insertions(+), 291 deletions(-) create mode 100644 doc/Pathmaking.md create mode 100644 docs/cloverage/the_great_game/objects/container.clj.html create mode 100644 docs/cloverage/the_great_game/objects/game_object.clj.html create mode 100644 docs/codox/Pathmaking.html create mode 100644 docs/codox/the-great-game.objects.container.html create mode 100644 docs/codox/the-great-game.objects.game-object.html rename src/the_great_game/objects/{game-object.clj => game_object.clj} (100%) diff --git a/.gitignore b/.gitignore index b0ebaf1..db35160 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ pom.xml.asc *.jar *.class *.log +*.stl [0-9a-f]*-init.clj /lib/ /classes/ @@ -18,4 +19,3 @@ pom.xml.asc .cpcache/ *~ -doc/.~lock.Population.ods# diff --git a/doc/Pathmaking.md b/doc/Pathmaking.md new file mode 100644 index 0000000..216446b --- /dev/null +++ b/doc/Pathmaking.md @@ -0,0 +1,114 @@ +# Pathmaking + +**NOTE**: this file is called 'pathmaking', not 'pathfinding', because 'pathfinding' has a very specific meaning/usage in game design which is only part of what I want to talk about here. + +## Stages in creating routes between locations + +### The 'procedural' phase + +*see also [[Baking-the-world]]* + +Towards the end of the procedural phase of the build process, every agent within the game world must move through the complete range of their needs-driven repertoire. Merchants must traverse their trading routes; soldiers must patrol routes within their employers domain; primary producers and craftspeople must visit the craftspeople who supply them; every character must visit their local inn, and must move daily between their dwelling and their workplace if different; and so on. They must do this over a considerable period - say 365 simulated days. + +At the start of the baking phase, routes - roads, tracks and paths - designed by the game designers already exist. + +The algorithmic part of choosing a route is the same during this baking phase as in actual game play **except** that during the baking phase the routemap is being dynamically updated, creating a new path or augmenting an existing path wherever any agent goes. + +Thus the 'weight' of any section of route is a function of the total number of times that route segment has been traversed by an agent during this baking phase. At the end of the baking phase, routes travelled more than `R` times are rendered as roads, `T` times as tracks, and `P` times as footpaths, where `R`, `T` and `P` are all chosen by the game designer but generally `R > T > P`. + +### Algorithmic rules + +1. No route may pass through any part of a reserved holding, except the holding which is its origin, if any, and the holding which is its destination (and in any case we won't render paths or roads within holdings, although traversal information may be used to determine whether a holding, or part of it, is paved/cobbled; +2. No route may pass through any building, with the exception of a city gate; +3. We don't have bicycles: going uphill costs work, and you don't get that cost back on the down hill. Indeed, downhills are at least as expensive to traverse as flat ground; +4. Any existing route segment costs only a third as much to traverse as open ground having the same gradient; +5. A more used route costs less to traverse than a less used route. + +### River crossings + +Crossing rivers is expensive - say five times as expensive as level open ground (but this will probably need tuning). Where a river is shallow enough, (i.e. where the amount of water passing is below some threshold) then a path crossing will be rendered as stepping stones and a track crossing as a ford. Where it's deeper than that, a path crossing either isn't rendered at all or is rendered as a light footbridge. A track or road crossing is rendered as a bridge. However, the maximum length of a bridge varies with the amount of traffic on the route segment, and if the crossing exceeds that length then a ferry is used. Road bridges will be more substantial than track bridges, for example in a biome with both timber and stone available road bridges might be rendered as stone bridges while track bridges were rendered as timber. If the watercourse is marked as `navigable`, the bridge must have a lifting section. It is assumed here that bridges are genetic buildings like most other in-game buildings, and so don't need to be individually designed. + +### Representation + +At some stage in the future I'll have actual game models to work with and $DEITY knows what the representation of those will be like, but to get this started I need two inputs: a heightmap, from which gradients can be derived, and a route map. The heightmap can conventionally be a monochrome raster image, and that's easy. The route map needs to be a vector representation, and SVG will be as convenient as any. So from the point of view of routing during the baking phase, a route map shall be an SVG with the following classes: + +* `exclusion` used on polygons representing e.g. buildings, or impassable terrain which may not be traversed at all; +* `openwater` used on polygons representing oceans and lakes, which may be traversed only by boat (or possibly swimming, for limited distances); +* `watercourse` used on paths representing rivers or streams, with some additional attribute giving rate of flow; +* `navigable` may be an additional class on a path also marked `watercourse` indicating that it is navigable by cargo vessels; +* `route` used on paths representing a path, track or road whose final representation will be dynamically assigned at the end of baking, with some additional attribute giving total traversals to date; +* `path` used on paths representing a path designed by the designers, which will certainly be rendered as a path no matter how frequently it is traversed; +* `track` used on paths representing a track designed by the designers, which will certainly be rendered as a track no matter how frequently it is traversed; +* `road` used on paths representing a road designed by the designers, which will certainly be rendered as a road no matter how (in)frequently it is traversed. + +At the end of the baking process the routing engine should be able to write out an updated SVG. New routes should be splined curves, so that they have natural bends not sharp angles. + +### The 'Walkmap' + +Conventional game pathfinding practice is to divide the traversable area into a mesh of 'convex polygons', where a 'convex polygon' in this sense is, essentially, a polygon having no bays. Routes traverse from a starting point to the centre of a polygon ajacent to the polygon in which the starting point is located. I have reservations as to whether this will do what I need since I'm not convinced it will produce naturalistic paths; however, it's worth at least experimenting with. + +There are existing utilities (such as [hmm](https://github.com/fogleman/hmm)) which convert heightmaps into suitable geometry files; however all I've found so far convert to [binary STL](https://en.wikipedia.org/wiki/STL_(file_format)). This isn't a format I find very useful; I'd prefer an XML dialect, and SVG is good enough for me. + +`hmm` converts the heightmap into a tesselation of triangles, which are necessarily convex in the sense given above. Utilities (such as [binary-stl-toASCII](https://github.com/IsseiMori/binary-stl-toASCII)) exist to convert binary STL to an ASCII encoded equivalent, which may be easier to parse. + +So the pipeline seems to be + +1. heightmap to binary STL +2. (optional) binary STL to ASCII STL +3. STL to SVG (where 'SVG' here is shorthand for a convenient vector format) +4. Exclude holdings, buildings, open water, and other exclusions +5. Where we have excluded exclusions, ensure that any non-convex polygons we've created are divided into new convex polygons. + +I shall have to write custom code for 4 and 5 above, and, looking at what's available, probably 3 as well. + +I'm working on a separate library, [walkmap](https://simon-brooke.github.io/walkmap/), which will attempt to implement this pipeline. + +### Pathmaking and scale + +Dealing with large heightmaps - doing anything at all with them - is extremely compute intensive. Just converting a 1000x1000 heightmap from STL to SVG is currently taking 8 hours and 15 minutes; and it ends up with a 46 megabyte SVG file! However, most of the time cost is in writing. Reading the STL file takes four and a quarter seconds; converting that STL to SVG in memory takes less than five seconds. So the huge cost is writing the file with Dali. + + walkmap.core=> (time (def stl (decode-binary-stl "../the-great-game/resources/maps/heightmap.stl"))) + "Elapsed time: 4285.231513 msecs" + #'walkmap.core/stl + walkmap.core=> (time (def svg (stl-to-svg stl))) + "Elapsed time: 4865.798059 msecs" + #'walkmap.core/svg + + +"Elapsed time: 2.969569560662E7 msecs" + +We cannot effectively do routing at metre scale - which is what we ultimately need in settlements - across the entire thousand kilometre square map in one pass. But also we don't need to because much of the continent is by design relatively unpeopled and relatively untracked. The basic concept of the Steppe is that there are two north/south routes, the one over the Midnight Pass into the Great Place and the one via Hans'hua down to the Cities of the Coast, and those can be part of the 'designed roads' map. So we can basically exclude most of the Steppe from routing altogether. We can also - for equally obvious reasons exclude the ocean. The ocean makes up roughly half of the 1000x1000 kilometre map, the steppe and plateau take up half of what's left, mountain massifs eat into the remainder and my feeling is that much of the eastern part of the continent is probably too arid to be settled. So we probably end up only having to dynamically route about 20% of the entire map. + +However, this doesn't get round the main problem with scale, and pathmaking. If we pathmake at kilometre scale, then curves will be necessarily very long and sweeping - because each path segment will be at least a kilometre long. And, actually, that's fine for very long distance roads in unpopulated fairly flat territory. It's not so good for long distance roads in rugged terrain, but... + +#### Phase one: hand-designed routes + +While, given the bottlenecks of the few mountain passes and the one possible pass over the plateau, the caravan routes we want would almost certainly emerge organically out of dynamic routing. But, actually, I know more or less where they need to be and it's probably easiest to hand design them. It will certainly save an enormous amount of brute-force compute time. + +I think I have to accept that if I want Alpe d'Huez-style switchbacks up the Sunset and Midnight passes, they're going to have to be hand designed. The same applies to where the Hans'hua caravan road ascends the plateau. + +#### Phase two: route segments 'for free' out of settlement activity + +If we start by pathmaking around settlements, we can make a first start by giving the template for a holding a segment of track parallel to and just in front of its frontage, and a segment of path along its left hand and rear edges. That, actually, is going to provide 90% of all routing within a settlement, and it's done for us within the [[Settling-a-game-world]] phase. + +#### Phase three: metre scale routing around settlements + +So if we then collect groups of contiguous 100x100 metre zones each of which has at least one settled holding, we can route at one metre scale over that and what it will essentially do is join up and augment the route segments generated by settlement. Areas of dense settlement do not make up a great deal of the map. Note that experience may show that the metre scale routing is superflous. + +#### Phases four, five and six: increasing granularity + +Taking the augmented route map comprised of + +1. The hand-designed, mainly long distance or plot-important routes; +2. The route segments bordering holdings; +3. The metre scale routing + +we can then collect contiguous groups of zones each having at least one holding, where in phase four each zone is a kilometre square and divided into 100x100 grid so that we route at ten metre scale; in phase five we use ten kilometre by ten kilometre zones and we route at 100 metre scale; in phase six, 100 km by 100 km zones and we route at kilometre scale. This process should automatically link up all settlements on the south and west coasts, all those on the north coast, and all in the Great Place; and seeing that the posited pre-designed caravan roads already join the south coast to the north, the north to the Great Place and the Great Place to the south coast, we're done. + +At least one of phases three, four, five and six is probably redundant; but without trying I'm not sure which. + +### Tidying up + +After the full set of increasing-scale passes is complete, we should automatically cull any route segments generated in the settlement phase which have never actually been traversed. + +Following that, there may be scope for some final manual tweaking, if desired; I think this is most likely to happen where roads routed at kilometre scale cross rugged terrain. diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index 516511b..929e2e6 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -17,13 +17,13 @@ the-great-game.agent.agent
          1
          + float:left;"> 2
        100.00 %
        1
        + float:left;"> 2
        100.00 % -721 +4352 the-great-game.gossip.gossip
        38
        92.73 %
        91
        9
        4
        -96.15 % -24630104 + style="width:88.07339449541284%; + float:left;"> 96
        8
        5
        +95.41 % +25631109 the-great-game.merchants.markets
        4.03 % 1736124 + + the-great-game.objects.container
        2
        +100.00 % +
        2
        +100.00 % +1112 + + + the-great-game.objects.game-object
        3
        2
        +60.00 % +
        3
        2
        +60.00 % +1925 + the-great-game.time
        -66.88 % +66.90 % -68.59 % +68.74 % diff --git a/docs/cloverage/the_great_game/agent/agent.clj.html b/docs/cloverage/the_great_game/agent/agent.clj.html index bbdf18f..14d5821 100644 --- a/docs/cloverage/the_great_game/agent/agent.clj.html +++ b/docs/cloverage/the_great_game/agent/agent.clj.html @@ -8,22 +8,130 @@ 001  (ns the-great-game.agent.agent
        - 002    "Anything in the game world with agency") + 002    "Anything in the game world with agency" +
        + + 003    (:require [the-great-game.objects.game-object :refer [ProtoObject]] +
        + + 004              [the-great-game.objects.container :refer [ProtoContainer]]))
        - 003   + 005  
        - 004  ;;  hierarchy of needs probably gets implemented here + 006  ;;  hierarchy of needs probably gets implemented here
        - 005  ;;  I'm probably going to want to defprotocol stuff, to define the hierarchy + 007  ;;  I'm probably going to want to defprotocol stuff, to define the hierarchy
        - 006  ;;  of things in the gameworld; either that or drop to Java, wich I'd rather not do. + 008  ;;  of things in the gameworld; either that or drop to Java, wich I'd rather not do.
        - 007   + 009   +
        + + 010  (defprotocol ProtoAgent +
        + + 011    "An object which can act in the world" +
        + + 012    (act +
        + + 013      [actor world circle] +
        + + 014         "Allow `actor` to do something in this `world`, in the context of this +
        + + 015         `circle`; return the new state of the actor if something was done, `nil` +
        + + 016         if nothing was done. Circle is expected to be one of +
        + + 017   +
        + + 018         * `:active` - actors within visual/audible range of the player +
        + + 019           character; +
        + + 020         * `:pending` - actors not in the active circle, but sufficiently close +
        + + 021           to it that they may enter the active circle within a short period; +
        + + 022         * `:background` - actors who are active in the background in order to +
        + + 023           handle trade, news, et cetera; +
        + + 024         * `other` - actors who are not members of any other circle, although +
        + + 025           I'm not clear whether it would ever be appropriate to invoke an +
        + + 026           `act` method on them. +
        + + 027   +
        + + 028         The `act` method *must not* have side effects; it must *only* return a +
        + + 029         new state. If the actor's intention is to seek to change the state of +
        + + 030         something else in the game world, it must add a representation of that +
        + + 031         intention to the sequence which will be returned by its +
        + + 032         `pending-intentions` method.") +
        + + 033    (pending-intentions +
        + + 034      [actor] +
        + + 035      "Returns a sequence of effects an actor intends, as a consequence of +
        + + 036      acting. The encoding of these is not yet defined.")) +
        + + 037   +
        + + 038  ;; (defrecord Agent +
        + + 039  ;;   "A default agent." +
        + + 040  ;;   ProtoObject +
        + + 041  ;;   ProtoContainer +
        + + 042  ;;   ProtoAgent +
        + + 043  ;; )
        diff --git a/docs/cloverage/the_great_game/gossip/news_items.clj.html b/docs/cloverage/the_great_game/gossip/news_items.clj.html index 6e5f68b..b2e762e 100644 --- a/docs/cloverage/the_great_game/gossip/news_items.clj.html +++ b/docs/cloverage/the_great_game/gossip/news_items.clj.html @@ -134,337 +134,337 @@ 043  
        - 044    ##### Locations: + 044    By implication, the character values passed should include *all* the +
        + + 045    information the giver knows about the character; that can then be degraded +
        + + 046    as the receiver stores only that segment which the receiver finds +
        + + 047    interesting.
        - 045   + 048  
        - 046    A 'location' value is a list comprising at most the x/y coordinate location -
        - - 047    and the ids of the settlement and region (possibly hierarchically) that contain -
        - - 048    the location. If the x/y is not local to the home of the receiving agent, they -
        - - 049    won't remember it and won't pass it on; if any of the ids are not interesting -
        - - 050    So location information will degrade progressively as the item is passed along. + 049    ##### Locations:
        - 051   + 050  
        - 052    It is assumed that the `:home` of a character is a location in this sense. + 051    A 'location' value is a list comprising at most the x/y coordinate location +
        + + 052    and the ids of the settlement and region (possibly hierarchically) that contain +
        + + 053    the location. If the x/y is not local to the home of the receiving agent, they +
        + + 054    won't remember it and won't pass it on; if any of the ids are not interesting +
        + + 055    So location information will degrade progressively as the item is passed along.
        - 053   + 056  
        - 054    ##### Inferences: + 057    It is assumed that the `:home` of a character is a location in this sense.
        - 055   + 058  
        - 056    If an agent learns that Adam has married Betty, they can infer that Betty has + 059    ##### Inferences: +
        + + 060  
        - 057    married Adam; if they learn that Charles killed Dorothy, that Dorothy has died. + 061    If an agent learns that Adam has married Betty, they can infer that Betty has
        - 058    I'm not convinced that my representation of inferences here is ideal. + 062    married Adam; if they learn that Charles killed Dorothy, that Dorothy has died.
        - 059    " + 063    I'm not convinced that my representation of inferences here is ideal. +
        + + 064    "
        - 060    { ;; A significant attack is interesting whether or not it leads to deaths + 065    { ;; A significant attack is interesting whether or not it leads to deaths
        - 061      :attack {:verb :attack :keys [:actor :other :location]} + 066      :attack {:verb :attack :keys [:actor :other :location]}
        - 062      ;; Deaths of characters may be interesting + 067      ;; Deaths of characters may be interesting
        - 063      :die {:verb :attack :keys [:actor :location]} + 068      :die {:verb :die :keys [:actor :location]}
        - 064      ;; Deliberate killings are interesting. + 069      ;; Deliberate killings are interesting.
        - 065      :kill {:verb :kill :keys [:actor :other :location] + 070      :kill {:verb :kill :keys [:actor :other :location]
        - 066             :inferences [{:verb :die :actor :other :other :nil}]} + 071             :inferences [{:verb :die :actor :other :other :nil}]}
        - 067      ;; Marriages may be interesting + 072      ;; Marriages may be interesting
        - 068      :marry {:verb :marry :keys [:actor :other :location] + 073      :marry {:verb :marry :keys [:actor :other :location]
        - 069              :inferences [{:verb :marry :actor :other :other :actor}]} + 074              :inferences [{:verb :marry :actor :other :other :actor}]}
        - 070      ;; The end of ongoing open conflict between to characters may be interesting + 075      ;; The end of ongoing open conflict between to characters may be interesting
        - 071      :peace {:verb :peace :keys [:actor :other :location] + 076      :peace {:verb :peace :keys [:actor :other :location]
        - 072              :inferences [{:verb :peace :actor :other :other :actor}]} + 077              :inferences [{:verb :peace :actor :other :other :actor}]}
        - 073      ;; Things related to the plot are interesting, but will require special + 078      ;; Things related to the plot are interesting, but will require special
        - 074      ;; handling. Extra keys may be required by particular plot events. + 079      ;; handling. Extra keys may be required by particular plot events.
        - 075      :plot {:verb :plot :keys [:actor :other :object :location]} + 080      :plot {:verb :plot :keys [:actor :other :object :location]}
        - 076      ;; Rapes are interesting. + 081      ;; Rapes are interesting.
        - 077      :rape {:verb :rape :keys [:actor :other :location] + 082      :rape {:verb :rape :keys [:actor :other :location]
        - 078             ;; Should you also infer from rape that actor is male and adult? + 083             ;; Should you also infer from rape that actor is male and adult?
        - 079             :inferences [{:verb :attack} + 084             :inferences [{:verb :attack}
        - 080                          {:verb :sex} + 085                          {:verb :sex}
        - 081                          {:verb :sex :actor :other :other :actor}]} + 086                          {:verb :sex :actor :other :other :actor}]}
        - 082      ;; Merchants, especially, are interested in prices in other markets + 087      ;; Merchants, especially, are interested in prices in other markets
        - 083      :sell {:verb :sell :keys [:actor :other :object :location :price]} + 088      :sell {:verb :sell :keys [:actor :other :object :location :price]}
        - 084      ;; Sex can juicy gossip, although not normally if the participants are in an + 089      ;; Sex can juicy gossip, although not normally if the participants are in an
        - 085      ;; established sexual relationship. + 090      ;; established sexual relationship.
        - 086      :sex {:verb :sex :keys [:actor :other :location] + 091      :sex {:verb :sex :keys [:actor :other :location]
        - 087            :inferences [{:verb :sex :actor :other :other :actor}]} + 092            :inferences [{:verb :sex :actor :other :other :actor}]}
        - 088      ;; Thefts are interesting + 093      ;; Thefts are interesting
        - 089      :steal {:verb :steal :keys [:actor :other :object :location]} + 094      :steal {:verb :steal :keys [:actor :other :object :location]}
        - 090      ;; The succession of rulers is interesting; of respected craftsmen, + 095      ;; The succession of rulers is interesting; of respected craftsmen,
        - 091      ;; potentially also interesting. + 096      ;; potentially also interesting.
        - 092      :succession {:verb :succession :keys [:actor :other :location :rank]} + 097      :succession {:verb :succession :keys [:actor :other :location :rank]}
        - 093      ;; The start of ongoing open conflict between to characters may be interesting + 098      ;; The start of ongoing open conflict between to characters may be interesting
        - 094      :war {:verb :war :keys [:actor :other :location] + 099      :war {:verb :war :keys [:actor :other :location]
        - 095            :inferences [{:verb :war :actor :other :other :actor}]} + 100            :inferences [{:verb :war :actor :other :other :actor}]}
        - 096      }) + 101      })
        - 097   + 102  
        - 098   + 103  
        - 099  (defn interest-in-character + 104  (defn interest-in-character
        - 100    "Integer representation of how interesting this `character` is to this + 105    "Integer representation of how interesting this `character` is to this
        - 101    `gossip`. + 106    `gossip`.
        - 102    *TODO:* this assumes that characters are passed as keywords, but, as + 107    *TODO:* this assumes that characters are passed as keywords, but, as
        - 103    documented above, they probably have to be maps, to allow for degradation." + 108    documented above, they probably have to be maps, to allow for degradation."
        - 104    [gossip character] + 109    [gossip character]
        - 105    (count + 110    (count
        - 106      (concat + 111      (concat
        - 107        (filter #(= (:actor % character)) (:knowledge gossip)) + 112        (filter #(= (:actor % character)) (:knowledge gossip))
        - 108        (filter #(= (:other % character)) (:knowledge gossip))))) + 113        (filter #(= (:other % character)) (:knowledge gossip)))))
        - 109   + 114  
        - 110  (defn interesting-character? + 115  (defn interesting-character?
        - 111    "Boolean representation of whether this `character` is interesting to this + 116    "Boolean representation of whether this `character` is interesting to this
        - 112    `gossip`." + 117    `gossip`."
        - 113    [gossip character] + 118    [gossip character]
        - 114    (> (interest-in-character gossip character) 0)) + 119    (> (interest-in-character gossip character) 0))
        - 115   + 120  
        - 116  (defn interest-in-location + 121  (defn interest-in-location
        - 117    "Integer representation of how interesting this `location` is to this + 122    "Integer representation of how interesting this `location` is to this
        - 118    `gossip`." + 123    `gossip`."
        - 119    [gossip location] + 124    [gossip location]
        - 120    (cond + 125    (cond
        - 121      (and (map? location) (number? (:x location)) (number? (:y location))) + 126      (and (map? location) (number? (:x location)) (number? (:y location)))
        - 122      (if-let [home (:home gossip)] + 127      (if-let [home (:home gossip)]
        - 123        (let [d (distance-between location home) + 128        (let [d (distance-between location home)
        - 124              i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should + 129              i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should
        - 125              ;;fall of with distance from home, but possibly on a log scale + 130              ;;fall off with distance from home, but possibly on a log scale
        - 126              ] + 131              ]
        - 127          (if (> i 1) i 0)) + 132          (if (> i 1) i 0))
        - 128        0) + 133        0)
        - 129      (coll? location) + 134      (coll? location)
        - 130      (reduce + 135      (reduce
        - 131        + + 136        +
        - 132        (map + 137        (map
        - 133          #(interest-in-location gossip %) + 138          #(interest-in-location gossip %)
        - 134          location)) + 139          location))
        - 135      :else + 140      :else
        - 136      (count + 141      (count
        - 137        (filter + 142        (filter
        - 138          #(some (fn [x] (= x location)) (:location %)) + 143          #(some (fn [x] (= x location)) (:location %))
        - 139          (cons {:location (:home gossip)} (:knowledge gossip)))))) + 144          (cons {:location (:home gossip)} (:knowledge gossip))))))
        - 140   + 145  
        - 141  ;; (interest-in-location {:home [{0, 0} :test-home] :knowledge []} [:test-home]) -
        - - 142   -
        - - 143  (defn interesting-location? -
        - - 144    "True if the location of this news `item` is interesting to this `gossip`." -
        - - 145    [gossip item] -
        - - 146    (> (interest-in-location gossip (:location item)) 0)) + 146  ;; (interest-in-location {:home [{0, 0} :test-home] :knowledge []} [:test-home])
        147  
        - - 148  (defn interesting-object? + + 148  (defn interesting-location?
        - 149    [gossip object] + 149    "True if the location of this news `item` is interesting to this `gossip`."
        - 150    ;; TODO: Not yet (really) implemented + 150    [gossip item]
        - - 151    true) + + 151    (> (interest-in-location gossip (:location item)) 0))
        152  
        - - 153  (defn interesting-topic? + + 153  (defn interesting-object?
        - 154    [gossip topic] + 154    [gossip object]
        155    ;; TODO: Not yet (really) implemented @@ -475,272 +475,302 @@ 157  
        + + 158  (defn interesting-topic? +
        + + 159    [gossip topic] +
        + + 160    ;; TODO: Not yet (really) implemented +
        + + 161    true) +
        + + 162   +
        - 158  (defn interesting-item? + 163  (defn interesting-item?
        - 159    "True if anything about this news `item` is interesting to this `gossip`." + 164    "True if anything about this news `item` is interesting to this `gossip`."
        - 160    [gossip item] + 165    [gossip item]
        - 161       (or + 166       (or
        - 162         (interesting-character? gossip (:actor item)) + 167         (interesting-character? gossip (:actor item))
        - 163         (interesting-character? gossip (:other item)) + 168         (interesting-character? gossip (:other item))
        - 164         (interesting-location? gossip (:location item)) + 169         (interesting-location? gossip (:location item))
        - 165         (interesting-object? gossip (:object item)) + 170         (interesting-object? gossip (:object item))
        - 166         (interesting-topic? gossip (:verb item)))) + 171         (interesting-topic? gossip (:verb item))))
        - 167   + 172  
        - 168  (defn infer + 173  (defn infer
        - 169    "Infer a new knowledge item from this `item`, following this `rule`" + 174    "Infer a new knowledge item from this `item`, following this `rule`"
        - 170    [item rule] + 175    [item rule]
        - 171    (reduce merge + 176    (reduce merge
        - 172            item + 177            item
        - 173            (cons + 178            (cons
        - 174              {:verb (:verb rule)} + 179              {:verb (:verb rule)}
        - 175              (map (fn [k] {k (apply (k rule) (list item))}) + 180              (map (fn [k] {k (apply (k rule) (list item))})
        - 176                   (remove + 181                   (remove
        - 177                     #(= % :verb) + 182                     #(= % :verb)
        - 178                     (keys rule)))))) + 183                     (keys rule))))))
        - 179   + 184  
        - 180  (declare learn-news-item) + 185  (declare learn-news-item)
        - 181   + 186  
        - 182  (defn make-all-inferences + 187  (defn make-all-inferences
        - 183    "Return a list of knowledge entries inferred from this news `item` by this + 188    "Return a list of knowledge entries that can be inferred from this news
        - 184    `gossip`." + 189    `item`."
        - 185    [item] + 190    [item]
        - 186    (set + 191    (set
        - 187      (reduce + 192      (reduce
        - 188        concat + 193        concat
        - 189        (map + 194        (map
        - 190          #(:knowledge (learn-news-item {} (infer item %) false)) + 195          #(:knowledge (learn-news-item {} (infer item %) false))
        - 191          (:inferences (news-topics (:verb item))))))) + 196          (:inferences (news-topics (:verb item)))))))
        - 192   + 197  
        - 193  (defn degrade-character + 198  (defn degrade-character
        - 194    "Return a character specification like this `character`, but comprising + 199    "Return a character specification like this `character`, but comprising
        - 195    only those properties this `gossip` is interested in." + 200    only those properties this `gossip` is interested in."
        - 196    [gossip character] + 201    [gossip character]
        - 197    ;; TODO: Not yet (really) implemented + 202    ;; TODO: Not yet (really) implemented
        - 198    character) + 203    character)
        - 199   + 204  
        - 200  (defn degrade-location + 205  (defn degrade-location
        - 201    "Return a location specification like this `location`, but comprising + 206    "Return a location specification like this `location`, but comprising
        - 202    only those elements this `gossip` is interested in. If none, return + 207    only those elements this `gossip` is interested in. If none, return
        - 203    `nil`." + 208    `nil`."
        - 204    [gossip location] + 209    [gossip location]
        - 205    (let [l (if + 210    (let [l (if
        - 206      (coll? location) + 211      (coll? location)
        - 207      (filter + 212      (filter
        - 208        #(when (interesting-location? gossip %) %) + 213        #(when (interesting-location? gossip %) %)
        - 209        location))] + 214        location))]
        - 210      (when-not (empty? l) l))) + 215      (when-not (empty? l) l)))
        - 211   + 216  
        - 212  (defn learn-news-item + 217  (defn learn-news-item
        - 213    "Return a gossip like this `gossip`, which has learned this news `item` if + 218    "Return a gossip like this `gossip`, which has learned this news `item` if
        - 214    it is of interest to them." + 219    it is of interest to them."
        - 215    ;; TODO: Not yet implemented + 220    ;; TODO: Not yet implemented
        - 216    ([gossip item] + 221    ([gossip item]
        - 217     (learn-news-item gossip item true)) + 222     (learn-news-item gossip item true))
        - 218    ([gossip item follow-inferences?] + 223    ([gossip item follow-inferences?]
        - 219     (if + 224     (if
        - 220       (interesting-item? gossip item) -
        - - 221       (let [g (assoc gossip :knowledge -
        - - 222                 (cons -
        - - 223                   (assoc + 225       (interesting-item? gossip item)
        - 224                     item + 226       (let +
        + + 227         [g (assoc +
        + + 228              gossip +
        + + 229              :knowledge
        - 225                     :nth-hand (if + 230              (cons
        - 226                                 (number? (:nth-hand item)) + 231                (assoc +
        + + 232                  item +
        + + 233                  :nth-hand (if +
        + + 234                              (number? (:nth-hand item))
        - 227                                 (inc (:nth-hand item)) + 235                              (inc (:nth-hand item))
        - 228                                 1) + 236                              1)
        - - 229                     :date (if (number? (:date item)) (:date item) (game-time)) + + 237                  :time-stamp (if
        - - 230                     :location (degrade-location gossip (:location item)) + + 238                                (number? (:time-stamp item))
        - - 231                     ;; ought to degratde the location -
        - - 232                     ;; ought to maybe-degrade characters we're not yet interested in -
        - - 233                     ) -
        - - 234                   ;; ought not to add knowledge items we already have, except -
        - - 235                   ;; to replace if new item is of increased specificity -
        - - 236                   (:knowledge gossip)))] + + 239                                (:time-stamp item)
        - 237         (if follow-inferences? + 240                                (game-time))
        - - 238           (assoc -
        - - 239             g + + 241                  :location (degrade-location gossip (:location item))
        - 240             :knowledge + 242                  ;; TODO: ought to maybe-degrade characters we're not yet interested in
        - - 241             (concat (:knowledge g) (make-all-inferences item))) + + 243                  ) +
        + + 244                ;; TODO: ought not to add knowledge items we already have, except +
        + + 245                ;; to replace if new item is of increased specificity +
        + + 246                (:knowledge gossip)))] +
        + + 247         (if follow-inferences? +
        + + 248           (assoc
        - 242           g)) + 249             g +
        + + 250             :knowledge +
        + + 251             (concat (:knowledge g) (make-all-inferences item))) +
        + + 252           g))
        - 243       gossip))) + 253       gossip)))
        - 244   + 254  
        - 245   + 255  
        - 246   + 256  
        diff --git a/docs/cloverage/the_great_game/objects/container.clj.html b/docs/cloverage/the_great_game/objects/container.clj.html new file mode 100644 index 0000000..992deb8 --- /dev/null +++ b/docs/cloverage/the_great_game/objects/container.clj.html @@ -0,0 +1,41 @@ + + + + the_great_game/objects/container.clj + + + + 001  (ns the-great-game.objects.container +
        + + 002    (:require +
        + + 003      [the-great-game.objects.game-object :refer :all])) +
        + + 004   +
        + + 005  (defprotocol ProtoContainer +
        + + 006    (contents +
        + + 007      [container] +
        + + 008              "Return a sequence of the contents of this `container`, or `nil` if empty.") +
        + + 009    (is-empty? +
        + + 010      [container] +
        + + 011      "Return `true` if this `container` is empty, else `false`.")) +
        + + diff --git a/docs/cloverage/the_great_game/objects/game_object.clj.html b/docs/cloverage/the_great_game/objects/game_object.clj.html new file mode 100644 index 0000000..d553a53 --- /dev/null +++ b/docs/cloverage/the_great_game/objects/game_object.clj.html @@ -0,0 +1,65 @@ + + + + the_great_game/objects/game_object.clj + + + + 001  (ns the-great-game.objects.game-object +
        + + 002    "Anything at all in the game world") +
        + + 003   +
        + + 004  (defprotocol ProtoObject +
        + + 005    "An object in the world" +
        + + 006    (id [object] "Returns the unique id of this object.") +
        + + 007    (reify-object +
        + + 008      [object] +
        + + 009      "Adds this `object` to the global object list. If the `object` has a +
        + + 010      non-nil value for its `id` method, keys it to that id - **but** if the +
        + + 011      id value is already in use, throws a hard exception. Returns the id to +
        + + 012      which the object is keyed in the global object list.")) +
        + + 013   +
        + + 014  (defrecord GameObject +
        + + 015    [id] +
        + + 016    ;; "An object in the world" +
        + + 017    ProtoObject +
        + + 018    (id [_] id) +
        + + 019    (reify-object [object] "TODO: doesn't work yet")) +
        + + diff --git a/docs/codox/Baking-the-world.html b/docs/codox/Baking-the-world.html index c588bd8..0e624ad 100644 --- a/docs/codox/Baking-the-world.html +++ b/docs/codox/Baking-the-world.html @@ -1,6 +1,6 @@ -Baking the world

        Baking the world

        +Baking the world

        Baking the world

        Wednesday, 8 May 2019

        Devogilla’s Bridge in Dumfries, early foourteenth century

        In previous posts, I’ve described algorithms for dynamically populating and dynamically settling a game world. But at kilometre scale (and I think we need a higher resolution than that - something closer to hectare scale), settling the British Isles using my existing algorithms takes about 24 hours of continuous compute on an eight core, 3GHz machine. You cannot do that every time you launch a new game.

        diff --git a/docs/codox/Pathmaking.html b/docs/codox/Pathmaking.html new file mode 100644 index 0000000..966e6e7 --- /dev/null +++ b/docs/codox/Pathmaking.html @@ -0,0 +1,79 @@ + +Pathmaking

        Pathmaking

        +

        NOTE: this file is called ‘pathmaking’, not ‘pathfinding’, because ‘pathfinding’ has a very specific meaning/usage in game design which is only part of what I want to talk about here.

        +

        Stages in creating routes between locations

        +

        The ‘procedural’ phase

        +

        see also Baking-the-world

        +

        Towards the end of the procedural phase of the build process, every agent within the game world must move through the complete range of their needs-driven repertoire. Merchants must traverse their trading routes; soldiers must patrol routes within their employers domain; primary producers and craftspeople must visit the craftspeople who supply them; every character must visit their local inn, and must move daily between their dwelling and their workplace if different; and so on. They must do this over a considerable period - say 365 simulated days.

        +

        At the start of the baking phase, routes - roads, tracks and paths - designed by the game designers already exist.

        +

        The algorithmic part of choosing a route is the same during this baking phase as in actual game play except that during the baking phase the routemap is being dynamically updated, creating a new path or augmenting an existing path wherever any agent goes.

        +

        Thus the ‘weight’ of any section of route is a function of the total number of times that route segment has been traversed by an agent during this baking phase. At the end of the baking phase, routes travelled more than R times are rendered as roads, T times as tracks, and P times as footpaths, where R, T and P are all chosen by the game designer but generally R > T > P.

        +

        Algorithmic rules

        +
          +
        1. No route may pass through any part of a reserved holding, except the holding which is its origin, if any, and the holding which is its destination (and in any case we won’t render paths or roads within holdings, although traversal information may be used to determine whether a holding, or part of it, is paved/cobbled;
        2. +
        3. No route may pass through any building, with the exception of a city gate;
        4. +
        5. We don’t have bicycles: going uphill costs work, and you don’t get that cost back on the down hill. Indeed, downhills are at least as expensive to traverse as flat ground;
        6. +
        7. Any existing route segment costs only a third as much to traverse as open ground having the same gradient;
        8. +
        9. A more used route costs less to traverse than a less used route.
        10. +
        +

        River crossings

        +

        Crossing rivers is expensive - say five times as expensive as level open ground (but this will probably need tuning). Where a river is shallow enough, (i.e. where the amount of water passing is below some threshold) then a path crossing will be rendered as stepping stones and a track crossing as a ford. Where it’s deeper than that, a path crossing either isn’t rendered at all or is rendered as a light footbridge. A track or road crossing is rendered as a bridge. However, the maximum length of a bridge varies with the amount of traffic on the route segment, and if the crossing exceeds that length then a ferry is used. Road bridges will be more substantial than track bridges, for example in a biome with both timber and stone available road bridges might be rendered as stone bridges while track bridges were rendered as timber. If the watercourse is marked as navigable, the bridge must have a lifting section. It is assumed here that bridges are genetic buildings like most other in-game buildings, and so don’t need to be individually designed.

        +

        Representation

        +

        At some stage in the future I’ll have actual game models to work with and $DEITY knows what the representation of those will be like, but to get this started I need two inputs: a heightmap, from which gradients can be derived, and a route map. The heightmap can conventionally be a monochrome raster image, and that’s easy. The route map needs to be a vector representation, and SVG will be as convenient as any. So from the point of view of routing during the baking phase, a route map shall be an SVG with the following classes:

        +
          +
        • exclusion used on polygons representing e.g. buildings, or impassable terrain which may not be traversed at all;
        • +
        • openwater used on polygons representing oceans and lakes, which may be traversed only by boat (or possibly swimming, for limited distances);
        • +
        • watercourse used on paths representing rivers or streams, with some additional attribute giving rate of flow;
        • +
        • navigable may be an additional class on a path also marked watercourse indicating that it is navigable by cargo vessels;
        • +
        • route used on paths representing a path, track or road whose final representation will be dynamically assigned at the end of baking, with some additional attribute giving total traversals to date;
        • +
        • path used on paths representing a path designed by the designers, which will certainly be rendered as a path no matter how frequently it is traversed;
        • +
        • track used on paths representing a track designed by the designers, which will certainly be rendered as a track no matter how frequently it is traversed;
        • +
        • road used on paths representing a road designed by the designers, which will certainly be rendered as a road no matter how (in)frequently it is traversed.
        • +
        +

        At the end of the baking process the routing engine should be able to write out an updated SVG. New routes should be splined curves, so that they have natural bends not sharp angles.

        +

        The ‘Walkmap’

        +

        Conventional game pathfinding practice is to divide the traversable area into a mesh of ‘convex polygons’, where a ‘convex polygon’ in this sense is, essentially, a polygon having no bays. Routes traverse from a starting point to the centre of a polygon ajacent to the polygon in which the starting point is located. I have reservations as to whether this will do what I need since I’m not convinced it will produce naturalistic paths; however, it’s worth at least experimenting with.

        +

        There are existing utilities (such as hmm) which convert heightmaps into suitable geometry files; however all I’ve found so far convert to [binary STL](https://en.wikipedia.org/wiki/STL_(file_format)). This isn’t a format I find very useful; I’d prefer an XML dialect, and SVG is good enough for me.

        +

        hmm converts the heightmap into a tesselation of triangles, which are necessarily convex in the sense given above. Utilities (such as binary-stl-toASCII) exist to convert binary STL to an ASCII encoded equivalent, which may be easier to parse.

        +

        So the pipeline seems to be

        +
          +
        1. heightmap to binary STL
        2. +
        3. (optional) binary STL to ASCII STL
        4. +
        5. STL to SVG (where ‘SVG’ here is shorthand for a convenient vector format)
        6. +
        7. Exclude holdings, buildings, open water, and other exclusions
        8. +
        9. Where we have excluded exclusions, ensure that any non-convex polygons we’ve created are divided into new convex polygons.
        10. +
        +

        I shall have to write custom code for 4 and 5 above, and, looking at what’s available, probably 3 as well.

        +

        I’m working on a separate library, walkmap, which will attempt to implement this pipeline.

        +

        Pathmaking and scale

        +

        Dealing with large heightmaps - doing anything at all with them - is extremely compute intensive. Just converting a 1000x1000 heightmap from STL to SVG is currently taking 8 hours and 15 minutes; and it ends up with a 46 megabyte SVG file! However, most of the time cost is in writing. Reading the STL file takes four and a quarter seconds; converting that STL to SVG in memory takes less than five seconds. So the huge cost is writing the file with Dali.

        +
        walkmap.core=> (time (def stl (decode-binary-stl "../the-great-game/resources/maps/heightmap.stl")))
        +"Elapsed time: 4285.231513 msecs"
        +#'walkmap.core/stl
        +walkmap.core=> (time (def svg (stl-to-svg stl)))
        +"Elapsed time: 4865.798059 msecs"
        +#'walkmap.core/svg
        +
        +

        “Elapsed time: 2.969569560662E7 msecs”

        +

        We cannot effectively do routing at metre scale - which is what we ultimately need in settlements - across the entire thousand kilometre square map in one pass. But also we don’t need to because much of the continent is by design relatively unpeopled and relatively untracked. The basic concept of the Steppe is that there are two north/south routes, the one over the Midnight Pass into the Great Place and the one via Hans’hua down to the Cities of the Coast, and those can be part of the ‘designed roads’ map. So we can basically exclude most of the Steppe from routing altogether. We can also - for equally obvious reasons exclude the ocean. The ocean makes up roughly half of the 1000x1000 kilometre map, the steppe and plateau take up half of what’s left, mountain massifs eat into the remainder and my feeling is that much of the eastern part of the continent is probably too arid to be settled. So we probably end up only having to dynamically route about 20% of the entire map.

        +

        However, this doesn’t get round the main problem with scale, and pathmaking. If we pathmake at kilometre scale, then curves will be necessarily very long and sweeping - because each path segment will be at least a kilometre long. And, actually, that’s fine for very long distance roads in unpopulated fairly flat territory. It’s not so good for long distance roads in rugged terrain, but…

        +

        Phase one: hand-designed routes

        +

        While, given the bottlenecks of the few mountain passes and the one possible pass over the plateau, the caravan routes we want would almost certainly emerge organically out of dynamic routing. But, actually, I know more or less where they need to be and it’s probably easiest to hand design them. It will certainly save an enormous amount of brute-force compute time.

        +

        I think I have to accept that if I want Alpe d’Huez-style switchbacks up the Sunset and Midnight passes, they’re going to have to be hand designed. The same applies to where the Hans’hua caravan road ascends the plateau.

        +

        Phase two: route segments ‘for free’ out of settlement activity

        +

        If we start by pathmaking around settlements, we can make a first start by giving the template for a holding a segment of track parallel to and just in front of its frontage, and a segment of path along its left hand and rear edges. That, actually, is going to provide 90% of all routing within a settlement, and it’s done for us within the Settling-a-game-world phase.

        +

        Phase three: metre scale routing around settlements

        +

        So if we then collect groups of contiguous 100x100 metre zones each of which has at least one settled holding, we can route at one metre scale over that and what it will essentially do is join up and augment the route segments generated by settlement. Areas of dense settlement do not make up a great deal of the map. Note that experience may show that the metre scale routing is superflous.

        +

        Phases four, five and six: increasing granularity

        +

        Taking the augmented route map comprised of

        +
          +
        1. The hand-designed, mainly long distance or plot-important routes;
        2. +
        3. The route segments bordering holdings;
        4. +
        5. The metre scale routing
        6. +
        +

        we can then collect contiguous groups of zones each having at least one holding, where in phase four each zone is a kilometre square and divided into 100x100 grid so that we route at ten metre scale; in phase five we use ten kilometre by ten kilometre zones and we route at 100 metre scale; in phase six, 100 km by 100 km zones and we route at kilometre scale. This process should automatically link up all settlements on the south and west coasts, all those on the north coast, and all in the Great Place; and seeing that the posited pre-designed caravan roads already join the south coast to the north, the north to the Great Place and the Great Place to the south coast, we’re done.

        +

        At least one of phases three, four, five and six is probably redundant; but without trying I’m not sure which.

        +

        Tidying up

        +

        After the full set of increasing-scale passes is complete, we should automatically cull any route segments generated in the settlement phase which have never actually been traversed.

        +

        Following that, there may be scope for some final manual tweaking, if desired; I think this is most likely to happen where roads routed at kilometre scale cross rugged terrain.

        \ No newline at end of file diff --git a/docs/codox/Populating-a-game-world.html b/docs/codox/Populating-a-game-world.html index 8a50174..5ea5c9e 100644 --- a/docs/codox/Populating-a-game-world.html +++ b/docs/codox/Populating-a-game-world.html @@ -1,6 +1,6 @@ -Populating a game world

        Populating a game world

        +Populating a game world

        Populating a game world

        Saturday, 6 July 2013

        (You might want to read this essay in conjunction with my older essay, Settling a game world, which covers similar ground but which this hopefully advances on)

        For an economy to work people have to be able to move between occupations to fill economic niches. In steady state, non player character (NPC) males become adult as ‘vagrants’, and then move through the state transitions described in this document. The pattern for females is different.

        diff --git a/docs/codox/Settling-a-game-world.html b/docs/codox/Settling-a-game-world.html index f7b58df..1bdac1f 100644 --- a/docs/codox/Settling-a-game-world.html +++ b/docs/codox/Settling-a-game-world.html @@ -1,6 +1,6 @@ -Settling a game world

        Settling a game world

        +Settling a game world

        Settling a game world

        Wednesday, 30 December 2009

        This essay is part of a series with ‘Worlds and Flats’ and ‘The spread of knowledge in a large game world’; if you haven’t read those you may want to read them before reading this. This essay describes how a large world can come into being and can evolve. I’ve written again on this subject since - see ‘Populating a game world’)

        Microworld

        diff --git a/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html b/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html index 9dd26f5..21193ca 100644 --- a/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html +++ b/docs/codox/The-spread-of-knowledge-in-a-large-game-world.html @@ -1,6 +1,6 @@ -The spread of knowledge in a large game world

        The spread of knowledge in a large game world

        +The spread of knowledge in a large game world

        The spread of knowledge in a large game world

        Saturday, 26 April 2008

        part of the role of Dandelion, in The Witcher games, is to provide the player with news

        Note

        diff --git a/docs/codox/Voice-acting-considered-harmful.html b/docs/codox/Voice-acting-considered-harmful.html index 7e5c1ad..44121b1 100644 --- a/docs/codox/Voice-acting-considered-harmful.html +++ b/docs/codox/Voice-acting-considered-harmful.html @@ -1,6 +1,6 @@ -Voice acting considered harmful

        Voice acting considered harmful

        +Voice acting considered harmful

        Voice acting considered harmful

        Wednesday, 25 February 2015

        The Witcher: Conversation with Kalkstein

        Long, long, time ago, I can still remember when… we played (and wrote) adventure games where the user typed at a command line, and the system printed back at them. A Read-Eval-Print loop in the classic Lisp sense, and I wrote my adventure games in Lisp. I used the same opportunistic parser whether the developer was building the game Create a new room north of here called dungeon-3 the player was playing the game Pick up the rusty sword and go north or the player was talking to a non-player character Say to the wizard ‘can you tell me the way to the castle’ Of course, the parser didn’t ‘understand’ English. It worked on trees of words, in which terminal nodes were actions and branching nodes were key words, and it had the property that any word it didn’t recognise at that point in sentence was a noise word and could be ignored. A few special hacks (such as ‘the’, ‘a’, or ‘an’ was an indicator that what came next was probably a noun phrase, and thus that if there was more than one sword in the player’s immediate environment the one that was wanted was the one tagged with the adjective ‘rusty’), and you ended up with a parser that most of the time convincingly interpreted most of what the player threw at it.

        diff --git a/docs/codox/economy.html b/docs/codox/economy.html index b5c7ecb..c4dbafb 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 d645463..f7aab85 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -The-great-game 0.1.1-SNAPSHOT

        The-great-game 0.1.1-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.1-SNAPSHOT"]

        Topics

        Namespaces

        the-great-game.agent.agent

        Anything in the game world with agency

        Public variables and functions:

          the-great-game.gossip.gossip

          Interchange of news events between gossip 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.location

          Functions dealing with location in the world.

          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.1-SNAPSHOT

          The-great-game 0.1.1-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.1-SNAPSHOT"]

          Topics

          Namespaces

          the-great-game.agent.agent

          Anything in the game world with agency

          Public variables and functions:

          the-great-game.gossip.gossip

          Interchange of news events between gossip 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.objects.container

          TODO: write docs

          Public variables and functions:

          the-great-game.objects.game-object

          Anything at all in the game world

          Public variables and functions:

          the-great-game.utils

          TODO: write docs

          Public variables and functions:

          the-great-game.world.location

          Functions dealing with location in the world.

          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 ebeff11..a892c58 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 58c4b74..93ea97f 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/naming-of-characters.html b/docs/codox/naming-of-characters.html index 3258a8a..e0f2dea 100644 --- a/docs/codox/naming-of-characters.html +++ b/docs/codox/naming-of-characters.html @@ -1,6 +1,6 @@ -Naming of Characters

          Naming of Characters

          +Naming of Characters

          Naming of Characters

          Generally speaking, in modern RPGs, every character with any impact on the plot has a distinct name. But if we are going to give all non-player characters sufficient agency to impact on the plot, then we must have a way of naming tens or hundreds of thousands of characters, and distinct names will become problematic (even if we’re procedurally generating names, which we shall have to do. So this note is about how characters are named.

          The full name of each character will be made up as follows:

          [epithet] [clan] [personal-name] the [trade-or-rank] of [location], son/daughter of [parent]

          diff --git a/docs/codox/on-dying.html b/docs/codox/on-dying.html index d84d7de..a52e81a 100644 --- a/docs/codox/on-dying.html +++ b/docs/codox/on-dying.html @@ -1,6 +1,6 @@ -On Dying

          On Dying

          +On Dying

          On Dying

          Death is the end of your story. One of the tropes in games which, for me, most breaks immersion is when you lose a fight and are presented with a screen that says ‘you are dead. Do you want to reload your last save?’ Life is not like that. We do not have save-states. We die.

          So how could this be better handled?

          You lose a fight. Switch to cutscene: the battlefield, after the fight, your body is there. Probably no sound. A party of non-enemies crosses the battlefield and finds your body. We see surprise and concern. They gather around you. Cut to interior scene, you are in a bed, unconcious, being tended; cut to similar interior scene, you are in a bed, conscious, being tended; cut to exterior scene, you are sitting with some of your saviours, and the game restarts.

          diff --git a/docs/codox/orgnic-quests.html b/docs/codox/orgnic-quests.html index 05c712d..49b6973 100644 --- a/docs/codox/orgnic-quests.html +++ b/docs/codox/orgnic-quests.html @@ -1,6 +1,6 @@ -Organic Quests

          Organic Quests

          +Organic Quests

          Organic Quests

          The structure of a modern Role Playing Came revolves around ‘quests’: tasks that the player character is invited to do, either by the framing narrative of the game or by some non-player character (‘the Quest Giver’). Normally there is one core quest which provides the overarching narrative for the whole game. [Wikipedia](https://en.wikipedia.org/wiki/Quest_(gaming)) offers a typology of quests as follows:

          1. Kill quests
          2. diff --git a/docs/codox/sandbox.html b/docs/codox/sandbox.html index e5b4a25..975e6d8 100644 --- a/docs/codox/sandbox.html +++ b/docs/codox/sandbox.html @@ -1,6 +1,6 @@ -Sandbox

            Sandbox

            +Sandbox

            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

            diff --git a/docs/codox/sexual-dimorphism.html b/docs/codox/sexual-dimorphism.html index de010b7..90c7153 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.agent.agent.html b/docs/codox/the-great-game.agent.agent.html index 079a700..7e659af 100644 --- a/docs/codox/the-great-game.agent.agent.html +++ b/docs/codox/the-great-game.agent.agent.html @@ -1,3 +1,10 @@ -the-great-game.agent.agent documentation

            the-great-game.agent.agent

            Anything in the game world with agency

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

            the-great-game.agent.agent

            Anything in the game world with agency

            ProtoAgent

            protocol

            An object which can act in the world

            members

            act

            (act actor world circle)

            Allow actor to do something in this world, in the context of this circle; return the new state of the actor if something was done, nil if nothing was done. Circle is expected to be one of

            +
              +
            • :active - actors within visual/audible range of the player character;
            • +
            • :pending - actors not in the active circle, but sufficiently close to it that they may enter the active circle within a short period;
            • +
            • :background - actors who are active in the background in order to handle trade, news, et cetera;
            • +
            • other - actors who are not members of any other circle, although I’m not clear whether it would ever be appropriate to invoke an act method on them.
            • +
            +

            The act method must not have side effects; it must only return a new state. If the actor’s intention is to seek to change the state of something else in the game world, it must add a representation of that intention to the sequence which will be returned by its pending-intentions method.

            pending-intentions

            (pending-intentions actor)

            Returns a sequence of effects an actor intends, as a consequence of acting. The encoding of these is not yet defined.

            \ 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 177c964..3e5f1a4 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 gossip 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 of 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 gossip 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 of 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.gossip.news-items.html b/docs/codox/the-great-game.gossip.news-items.html index ee90317..f3c6832 100644 --- a/docs/codox/the-great-game.gossip.news-items.html +++ b/docs/codox/the-great-game.gossip.news-items.html @@ -1,6 +1,6 @@ -the-great-game.gossip.news-items documentation

            the-great-game.gossip.news-items

            Categories of news events interesting to gossip agents

            degrade-character

            (degrade-character gossip character)

            Return a character specification like this character, but comprising only those properties this gossip is interested in.

            degrade-location

            (degrade-location gossip location)

            Return a location specification like this location, but comprising only those elements this gossip is interested in. If none, return nil.

            infer

            (infer item rule)

            Infer a new knowledge item from this item, following this rule

            interest-in-character

            (interest-in-character gossip character)

            Integer representation of how interesting this character is to this gossip. TODO: this assumes that characters are passed as keywords, but, as documented above, they probably have to be maps, to allow for degradation.

            interest-in-location

            (interest-in-location gossip location)

            Integer representation of how interesting this location is to this gossip.

            interesting-character?

            (interesting-character? gossip character)

            Boolean representation of whether this character is interesting to this gossip.

            interesting-item?

            (interesting-item? gossip item)

            True if anything about this news item is interesting to this gossip.

            interesting-location?

            (interesting-location? gossip item)

            True if the location of this news item is interesting to this gossip.

            interesting-object?

            (interesting-object? gossip object)

            TODO: write docs

            interesting-topic?

            (interesting-topic? gossip topic)

            TODO: write docs

            learn-news-item

            (learn-news-item gossip item)(learn-news-item gossip item follow-inferences?)

            Return a gossip like this gossip, which has learned this news item if it is of interest to them.

            make-all-inferences

            (make-all-inferences item)

            Return a list of knowledge entries inferred from this news item by this gossip.

            news-topics

            Topics of interest to gossip agents. Topics are keyed in this map by their verbs. The keys associated with each topic are the extra pieces of information required to give context to a gossip item. Generally:

            +the-great-game.gossip.news-items documentation

            the-great-game.gossip.news-items

            Categories of news events interesting to gossip agents

            degrade-character

            (degrade-character gossip character)

            Return a character specification like this character, but comprising only those properties this gossip is interested in.

            degrade-location

            (degrade-location gossip location)

            Return a location specification like this location, but comprising only those elements this gossip is interested in. If none, return nil.

            infer

            (infer item rule)

            Infer a new knowledge item from this item, following this rule

            interest-in-character

            (interest-in-character gossip character)

            Integer representation of how interesting this character is to this gossip. TODO: this assumes that characters are passed as keywords, but, as documented above, they probably have to be maps, to allow for degradation.

            interest-in-location

            (interest-in-location gossip location)

            Integer representation of how interesting this location is to this gossip.

            interesting-character?

            (interesting-character? gossip character)

            Boolean representation of whether this character is interesting to this gossip.

            interesting-item?

            (interesting-item? gossip item)

            True if anything about this news item is interesting to this gossip.

            interesting-location?

            (interesting-location? gossip item)

            True if the location of this news item is interesting to this gossip.

            interesting-object?

            (interesting-object? gossip object)

            TODO: write docs

            interesting-topic?

            (interesting-topic? gossip topic)

            TODO: write docs

            learn-news-item

            (learn-news-item gossip item)(learn-news-item gossip item follow-inferences?)

            Return a gossip like this gossip, which has learned this news item if it is of interest to them.

            make-all-inferences

            (make-all-inferences item)

            Return a list of knowledge entries that can be inferred from this news item.

            news-topics

            Topics of interest to gossip agents. Topics are keyed in this map by their verbs. The keys associated with each topic are the extra pieces of information required to give context to a gossip item. Generally: