diff --git a/docs/cloverage/cc/journeyman/the_great_game/agent/agent.clj.html b/docs/cloverage/cc/journeyman/the_great_game/agent/agent.clj.html new file mode 100644 index 0000000..cafc39e --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/agent/agent.clj.html @@ -0,0 +1,143 @@ + + + + cc/journeyman/the_great_game/agent/agent.clj + + + + 001  (ns cc.journeyman.the-great-game.agent.agent +
+ + 002    "Anything in the game world with agency; primarily but not exclusively +
+ + 003     characters." +
+ + 004    (:require [cc.journeyman.the-great-game.objects.game-object :refer [ProtoObject]] +
+ + 005              [cc.journeyman.the-great-game.objects.container :refer [ProtoContainer]])) +
+ + 006   +
+ + 007  ;;;  hierarchy of needs probably gets implemented here +
+ + 008  ;;;  I'm probably going to want to defprotocol stuff, to define the hierarchy +
+ + 009  ;;;  of things in the gameworld; either that or drop to Java, wich I'd rather not do. +
+ + 010   +
+ + 011  (defprotocol ProtoAgent +
+ + 012    "An object which can act in the world" +
+ + 013    (act +
+ + 014      [actor world circle] +
+ + 015         "Allow `actor` to do something in this `world`, in the context of this +
+ + 016         `circle`; return the new state of the actor if something was done, `nil` +
+ + 017         if nothing was done. Circle is expected to be one of +
+ + 018   +
+ + 019         * `:active` - actors within visual/audible range of the player +
+ + 020           character; +
+ + 021         * `:pending` - actors not in the active circle, but sufficiently close +
+ + 022           to it that they may enter the active circle within a short period; +
+ + 023         * `:background` - actors who are active in the background in order to +
+ + 024           handle trade, news, et cetera; +
+ + 025         * `other` - actors who are not members of any other circle, although +
+ + 026           I'm not clear whether it would ever be appropriate to invoke an +
+ + 027           `act` method on them. +
+ + 028   +
+ + 029         The `act` method *must not* have side effects; it must *only* return a +
+ + 030         new state. If the actor's intention is to seek to change the state of +
+ + 031         something else in the game world, it must add a representation of that +
+ + 032         intention to the sequence which will be returned by its +
+ + 033         `pending-intentions` method.") +
+ + 034    (pending-intentions +
+ + 035      [actor] +
+ + 036      "Returns a sequence of effects an actor intends, as a consequence of +
+ + 037      acting. The encoding of these is not yet defined.")) +
+ + 038   +
+ + 039  (defrecord Agent +
+ + 040    ;; "A default agent." +
+ + 041    [name craft home culture] +
+ + 042    ProtoObject +
+ + 043    ProtoContainer +
+ + 044    ProtoAgent +
+ + 045  ) +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/buildings/rectangular.clj.html b/docs/cloverage/cc/journeyman/the_great_game/buildings/rectangular.clj.html new file mode 100644 index 0000000..f276d7b --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/buildings/rectangular.clj.html @@ -0,0 +1,548 @@ + + + + cc/journeyman/the_great_game/buildings/rectangular.clj + + + + 001  (ns cc.journeyman.the-great-game.buildings.rectangular +
+ + 002    "Build buildings with a generally rectangular floow plan. +
+ + 003      +
+ + 004     ## Motivations +
+ + 005      +
+ + 006     Right, the idea behind this namespace is many fold. +
+ + 007   +
+ + 008     1. To establish the broad principle of genetic buildings, by creating a +
+ + 009        function which reproducibly creates reproducible buildings at specified +
+ + 010        locations, such that different buildings are credibly varied but a +
+ + 011        building at a specified location is always (modulo economic change) the +
+ + 012        same. +
+ + 013     2. Create good rectangular buildings, and investigate whether a single  +
+ + 014        function can be used to create buildings of more than one family (e.g. +
+ + 015        can it produce flat roofed, north African style, mud brick houses as +
+ + 016        well as pitch roofed, half timbered northern European houses?) +
+ + 017     3. Establish whether, in my current state of fairly severe mental illness, +
+ + 018        I can actually produce any usable code at all. +
+ + 019   +
+ + 020     ## Key factors in the creation of a building +
+ + 021   +
+ + 022     ### Holding +
+ + 023   +
+ + 024     Every building is on a holding, and, indeed, what I mean by 'building' here +
+ + 025     may well turn out to be 'the collection of all the permanent structures on +
+ + 026     a holding. A holding is a polygonal area of the map which does not  +
+ + 027     intersect with any other holding, but for the time being we'll make the  +
+ + 028     simplifying assumption that every holding is a rectangular strip, and that +
+ + 029     'urban' holdings are of a reasonably standard width (see Viking-period  +
+ + 030     York) and length. Rural holdings (farms, ?wood lots) may be much larger. +
+ + 031   +
+ + 032     ### Terrain +
+ + 033   +
+ + 034     A building is made of the stuff of the place. In a forest, buildings will  +
+ + 035     tend to be wooden; in a terrain with rocky outcrops -- normally found on  +
+ + 036     steep slopes -- stone. On the flat lands where there's river mud, of brick, +
+ + 037     cob, or wattle and daub. So to build a building we need to know the  +
+ + 038     terrain. Terrain can be inferred from location but in practice this will  +
+ + 039     be computationally expensive, so we'll pass terrain in as an argument to +
+ + 040     the build function. +
+ + 041   +
+ + 042     For the time being we'll pass it in simply as a keyword from a defined set +
+ + 043     of keywords; later it may be a more sophisticated data structure. +
+ + 044   +
+ + 045     ### Culture +
+ + 046   +
+ + 047     People of different cultures build distinctively different buildings, even +
+ + 048     when using the same materials. So, in our world, a Japanese wooden house  +
+ + 049     looks quite different from an Anglo Saxon stave house which looks quite  +
+ + 050     different from a Canadian log cabin, even though the materials are much the +
+ + 051     same and the tools available to build with are not much different. +
+ + 052   +
+ + 053     Culture can affect not just the overall shape of a building but also its  +
+ + 054     finish and surface detail. For example, in many places in England, stone +
+ + 055     buildings are typically left bare; in rural Scotland, typically painted  +
+ + 056     white or in pastel shades; in Ireland, often quite vivid colours. +
+ + 057   +
+ + 058     People may also show religious or cultural symbols on their buildings. +
+ + 059    +
+ + 060     For all these reasons, we need to know the culture of the occupant when +
+ + 061     creating a building. Again, this will initially be passed in as a keyword. +
+ + 062   +
+ + 063     ### Craft +
+ + 064   +
+ + 065     People in the game world have a craft, and some crafts will require  +
+ + 066     different features in the building. In the broadly late-bronze-age-to +
+ + 067     medieval period within which the game is set, residence and  workplace +
+ + 068     are for most people pretty much the same. +
+ + 069   +
+ + 070     So a baker needs an oven, a smith a forge, and so on. All crafts who do +
+ + 071     some degree retail trade will want a shop front as part of the ground  +
+ + 072     floor of their dwelling. Merchants and bankers will probably have houses +
+ + 073     that are a bit more showy than others. +
+ + 074   +
+ + 075     Whether the 'genetic buildings' idea will ever really produce suitable +
+ + 076     buildings for aristons I don't know; it seems more likely that significant +
+ + 077     strongholds (of which there will be relatively few) should all be hand +
+ + 078     modelled rather than procedurally generated." +
+ + 079    (:require [cc.journeyman.the-great-game.holdings.holding :refer [ProtoHolding]] +
+ + 080              [cc.journeyman.the-great-game.location.location :refer [ProtoLocation]]) +
+ + 081    (:import [org.apache.commons.math3.random MersenneTwister])) +
+ + 082   +
+ + 083    +
+ + 084  (def ^:dynamic *terrain-types*  +
+ + 085    "Types of terrain which affect building families. TODO: This is a placeholder; +
+ + 086     a more sophisticated model will be needed." +
+ + 087    #{:arable :arid :forest :plateau :upland}) +
+ + 088   +
+ + 089  (def ^:dynamic *cultures* +
+ + 090    "Cultures which affect building families. TODO: placeholder" +
+ + 091    #{:ariston :coastal :steppe-clans :western-clans :wild-herd}) +
+ + 092   +
+ + 093  (def ^:dynamic *crafts* +
+ + 094    "Crafts which affect building types in the game. See  +
+ + 095     `Populating a game world`. TODO: placeholder" +
+ + 096    #{:baker :banker :butcher :chancellor :innkeeper :lawyer :magus :merchant :miller :priest :scholar :smith :weaver}) +
+ + 097   +
+ + 098  (def ^:dynamic *building-families*  +
+ + 099    "Families of buildings. +
+ + 100      +
+ + 101     Each family has +
+ + 102      +
+ + 103     * terrain types to which it is appropriate; +
+ + 104     * crafts to which it is appropriate; +
+ + 105     * cultures to which it is appropriate.  +
+ + 106      +
+ + 107     Each generated building will be of one family, and will comprise modules  +
+ + 108     taken only from that family." +
+ + 109    {:pitched-rectangular {:terrains #{:arable :forest :upland} +
+ + 110                           :crafts *crafts* +
+ + 111                           :cultures #{:coastal :western-clans} +
+ + 112                           :modules []} +
+ + 113     :flatroof-rectangular {:terrains #{:arid :plateau} +
+ + 114                            :crafts *crafts* +
+ + 115                            :cultures #{:coastal} +
+ + 116                            :modules []}}) +
+ + 117   +
+ + 118  ;; TODO: So, modules need to contain +
+ + 119  ;; +
+ + 120  ;; 1. Ground floor modules, having external doors; +
+ + 121  ;; 2. Craft modules -- workshops -- which will normally be ground floor (except +
+ + 122  ;; weavers) and may have the constraint that no upper floor module can cover them; +
+ + 123  ;; 3. Upper floor modules, having NO external doors (but linking internal doors); +
+ + 124  ;; 4. Roof modules +
+ + 125  ;;  +
+ + 126  ;; There also needs to be an undercroft or platform module, such that the area of  +
+ + 127  ;; the top of the platform is identical with the footprint of the building, and  +
+ + 128  ;; the altitude of the top of the platform is equal to the altitude of the  +
+ + 129  ;; terrain at the heighest corner of the building; so that the actual  +
+ + 130  ;; building doesn't float in the air, and also so that none of the doors or windows +
+ + 131  ;; are partly underground. +
+ + 132  ;; +
+ + 133  ;; Each module needs to wrap an actual 3d model created in Blender or whatever,  +
+ + 134  ;; and have a list of optional textures with which that model can be rendered.  +
+ + 135  ;; So an upper floor bedroom module might have the following renders: +
+ + 136  ;; +
+ + 137  ;; 1. Bare masonry - constrained to upland or plateau terrain, and to coastal culture +
+ + 138  ;; 2. Painted masonry - constrained to upland or plateau terrain, and to coastal culture +
+ + 139  ;; 3. Half-timbered - not available on plateau terrain +
+ + 140  ;; 4. Weatherboarded - constrained to forest terrain +
+ + 141  ;; 5. Brick - constrained to arable or arid terrain +
+ + 142  ;; +
+ + 143  ;; of course these are only examples, and also, it's entirely possible to have +
+ + 144  ;; for example multiple different weatherboard renders for the same module.  +
+ + 145  ;; There needs to be a way of rendering what can be built above what: for +
+ + 146  ;; example, you can't have a masonry clad module over a half timbered one,  +
+ + 147  ;; but you can have a half-timbered one over a masonry one +
+ + 148   +
+ + 149  (defn building-family +
+ + 150    "A building family is essentially a collection of models of building modules +
+ + 151     which can be assembled to create buildings of a particular structural and +
+ + 152     architectural style." +
+ + 153    [terrain culture craft gene] +
+ + 154    (let [candidates (filter #(and +
+ + 155                               ((:terrains %) terrain) +
+ + 156                               ((:crafts %) craft) +
+ + 157                               ((:cultures %) culture)) +
+ + 158                             (vals *building-families*))] +
+ + 159      (nth candidates (mod (Math/abs (.nextInt gene)) (count candidates))))) +
+ + 160   +
+ + 161  (building-family :arable :coastal :baker (MersenneTwister. 5)) +
+ + 162   +
+ + 163  (defn build!  +
+ + 164    "Builds a building, and returns a data structure which represents it. In  +
+ + 165     building the building, it adds a model of the building to the representation +
+ + 166     of the world, so it does have a side effect." +
+ + 167    [holding terrain culture craft size] +
+ + 168    (if (satisfies? ProtoHolding holding) +
+ + 169    (let [location (.building-origin holding) +
+ + 170          gene (MersenneTwister. (int (+ (* (.easting location) 1000000) (.northing location)))) +
+ + 171          family (building-family terrain culture craft gene)] +
+ + 172    (if  +
+ + 173     (and (instance? ProtoLocation location) (:orientation location)) +
+ + 174      :stuff +
+ + 175      :nonsense +
+ + 176      )) +
+ + 177      :froboz)) +
+ + 178   +
+ + 179  ;; (def ol (cc.journeyman.the-great-game.location.location/OrientedLocation. 123.45 543.76 12.34 0.00 {})) +
+ + 180   +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/gossip/gossip.clj.html b/docs/cloverage/cc/journeyman/the_great_game/gossip/gossip.clj.html new file mode 100644 index 0000000..cf48455 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/gossip/gossip.clj.html @@ -0,0 +1,227 @@ + + + + cc/journeyman/the_great_game/gossip/gossip.clj + + + + 001  (ns cc.journeyman.the-great-game.gossip.gossip +
+ + 002    "Interchange of news events between gossip agents. +
+ + 003      +
+ + 004     Note that habitual travellers are all gossip agents; specifically, at this +
+ + 005     stage, that means merchants. When merchants are moved we also need to +
+ + 006     update the location of the gossip with the same key. +
+ + 007      +
+ + 008     Innkeepers are also gossip agents but do not typically move." +
+ + 009    (:require [cc.journeyman.the-great-game.utils :refer [deep-merge]] +
+ + 010              [cc.journeyman.the-great-game.gossip.news-items :refer [learn-news-item]] +
+ + 011              )) +
+ + 012   +
+ + 013   +
+ + 014  (defn dialogue +
+ + 015    "Dialogue between an `enquirer` and an `agent` in this `world`; returns a +
+ + 016    map identical to `enquirer` except that its `:gossip` collection may have +
+ + 017    additional entries." +
+ + 018    ;; TODO: not yet written, this is a stub. +
+ + 019    [enquirer respondent world] +
+ + 020    enquirer) +
+ + 021   +
+ + 022  (defn gather-news +
+ + 023    "Gather news for the specified `gossip` in this `world`." +
+ + 024    [world gossip] +
+ + 025    (let [g (cond (keyword? gossip) +
+ + 026                  (-> world :gossips gossip) +
+ + 027                  (map? gossip) +
+ + 028                  gossip)] +
+ + 029      (if g +
+ + 030        {:gossips +
+ + 031         {(:id g) +
+ + 032          (reduce +
+ + 033           deep-merge +
+ + 034           {} +
+ + 035           (map +
+ + 036            #(dialogue g % world) +
+ + 037            (remove +
+ + 038             #(= g %) +
+ + 039             (filter +
+ + 040              #(= (:location %) (:location g)) +
+ + 041              (vals (:gossips world))))))}} +
+ + 042        {}))) +
+ + 043   +
+ + 044  (defn move-gossip +
+ + 045    "Return a world like this `world` but with this `gossip` moved to this +
+ + 046    `new-location`. Many gossips are essentially shadow-records of agents of +
+ + 047    other types, and the movement of the gossip should be controlled by the +
+ + 048    run function of the type of the record they shadow. The [[#run]] function +
+ + 049    below does NOT call this function." +
+ + 050    [gossip world new-location] +
+ + 051    (let [id (cond +
+ + 052              (map? gossip) +
+ + 053              (-> world :gossips gossip :id) +
+ + 054              (keyword? gossip) +
+ + 055              gossip)] +
+ + 056    (deep-merge +
+ + 057      world +
+ + 058      {:gossips +
+ + 059       {id +
+ + 060        {:location new-location}}}))) +
+ + 061   +
+ + 062  (defn run +
+ + 063    "Return a world like this `world`, with news items exchanged between gossip +
+ + 064    agents." +
+ + 065    [world] +
+ + 066    (reduce +
+ + 067     deep-merge +
+ + 068     world +
+ + 069     (map +
+ + 070      #(gather-news world %) +
+ + 071      (keys (:gossips world))))) +
+ + 072   +
+ + 073   +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/gossip/news_items.clj.html b/docs/cloverage/cc/journeyman/the_great_game/gossip/news_items.clj.html new file mode 100644 index 0000000..3a27e1d --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/gossip/news_items.clj.html @@ -0,0 +1,947 @@ + + + + cc/journeyman/the_great_game/gossip/news_items.clj + + + + 001  (ns cc.journeyman.the-great-game.gossip.news-items +
+ + 002    "Categories of news events interesting to gossip agents. +
+ + 003      +
+ + 004     The ideas here are based on the essay [The spread of knowledge in a large +
+ + 005     game world](The-spread-of-knowledge-in-a-large-game-world.html), q.v.;  +
+ + 006     they've advanced a little beyond that and will doubtless +
+ + 007     advance further in the course of writing and debugging this namespace. +
+ + 008   +
+ + 009     A news item is a map with the keys: +
+ + 010    +
+ + 011     * `date` - the date on which the reported event happened; +
+ + 012     * `nth-hand` - the number of agents the news item has passed through; +
+ + 013     * `verb` - what it is that happened (key into `news-topics`); +
+ + 014   +
+ + 015     plus other keys taken from the `keys` value associated with the verb in +
+ + 016     `news-topics`. +
+ + 017      +
+ + 018     ## Notes: +
+ + 019      +
+ + 020     *TODO*    +
+ + 021     This namespace at present considers the `:knowledge` of a gossip to be a flat +
+ + 022     list of propositions, each of which must be checked every time any new +
+ + 023     proposition is offered. This is woefully inefficient. " +
+ + 024    (:require [cc.journeyman.the-great-game.world.location :refer [distance-between]] +
+ + 025              [cc.journeyman.the-great-game.time :refer [game-time]])) +
+ + 026   +
+ + 027   +
+ + 028  (def news-topics +
+ + 029    "Topics of interest to gossip agents. Topics are keyed in this map by +
+ + 030    their `verbs`. The `keys` associated with each topic are the extra pieces +
+ + 031    of information required to give context to a gossip item. Generally: +
+ + 032   +
+ + 033    * `actor` is the id of the character who it is reported performed the +
+ + 034    action; +
+ + 035    * `other` is the id of the character on whom it is reported the action +
+ + 036    was performed; +
+ + 037    * `location` is the place at which the action was performed; +
+ + 038    * `object` is an object (or possibly list of objects?) relevant to the +
+ + 039    action; +
+ + 040    * `price` is special to buy/sell, but of significant interest to merchants. +
+ + 041   +
+ + 042    ## Notes +
+ + 043   +
+ + 044    ### Characters +
+ + 045   +
+ + 046    *TODO* but note that at most all the receiver can learn about a character +
+ + 047    from a news item is what the giver knows about that character, degraded by +
+ + 048    what the receiver finds interesting about them. If we just pass the id here, +
+ + 049    then either the receiver knows everything in the database about the +
+ + 050    character, or else the receiver knows nothing at all about the character. +
+ + 051    Neither is desirable. Further thought needed. +
+ + 052   +
+ + 053    By implication, the character values passed should include *all* the +
+ + 054    information the giver knows about the character; that can then be degraded +
+ + 055    as the receiver stores only that segment which the receiver finds +
+ + 056    interesting. +
+ + 057   +
+ + 058    ### Locations +
+ + 059   +
+ + 060    A 'location' value is a list comprising at most the x/y coordinate location +
+ + 061    and the ids of the settlement and region (possibly hierarchically) that contain +
+ + 062    the location. If the x/y is not local to the home of the receiving agent, they +
+ + 063    won't remember it and won't pass it on; if any of the ids are not interesting +
+ + 064    So location information will degrade progressively as the item is passed along. +
+ + 065   +
+ + 066    It is assumed that the `:home` of a character is a location in this sense. +
+ + 067   +
+ + 068    ### Inferences +
+ + 069   +
+ + 070    If an agent learns that Adam has married Betty, they can infer that Betty has +
+ + 071    married Adam; if they learn that Charles killed Dorothy, that Dorothy has died. +
+ + 072    I'm not convinced that my representation of inferences here is ideal. +
+ + 073    " +
+ + 074    {;; A significant attack is interesting whether or not it leads to deaths +
+ + 075     :attack {:verb :attack :keys [:actor :other :location]} +
+ + 076      ;; Deaths of characters may be interesting +
+ + 077     :die {:verb :die :keys [:actor :location]} +
+ + 078      ;; Deliberate killings are interesting. +
+ + 079     :kill {:verb :kill :keys [:actor :other :location] +
+ + 080            :inferences [{:verb :die :actor :other :other :nil}]} +
+ + 081      ;; Marriages may be interesting +
+ + 082     :marry {:verb :marry :keys [:actor :other :location] +
+ + 083             :inferences [{:verb :marry :actor :other :other :actor}]} +
+ + 084      ;; The end of ongoing open conflict between to characters may be interesting +
+ + 085     :peace {:verb :peace :keys [:actor :other :location] +
+ + 086             :inferences [{:verb :peace :actor :other :other :actor}]} +
+ + 087      ;; Things related to the plot are interesting, but will require special +
+ + 088      ;; handling. Extra keys may be required by particular plot events. +
+ + 089     :plot {:verb :plot :keys [:actor :other :object :location]} +
+ + 090      ;; Rapes are interesting. +
+ + 091     :rape {:verb :rape :keys [:actor :other :location] +
+ + 092             ;; Should you also infer from rape that actor is male and adult? +
+ + 093            :inferences [{:verb :attack} +
+ + 094                         {:verb :sex} +
+ + 095                         {:verb :sex :actor :other :other :actor}]} +
+ + 096      ;; Merchants, especially, are interested in prices in other markets +
+ + 097     :sell {:verb :sell :keys [:actor :other :object :location :price]} +
+ + 098      ;; Sex can juicy gossip, although not normally if the participants are in an +
+ + 099      ;; established sexual relationship. +
+ + 100     :sex {:verb :sex :keys [:actor :other :location] +
+ + 101           :inferences [{:verb :sex :actor :other :other :actor}]} +
+ + 102      ;; Thefts are interesting. +
+ + 103     :steal {:verb :steal :keys [:actor :other :object :location]} +
+ + 104      ;; The succession of rulers is interesting; of respected craftsmen, +
+ + 105      ;; potentially also interesting. +
+ + 106     :succession {:verb :succession :keys [:actor :other :location :rank]} +
+ + 107      ;; The start of ongoing open conflict between two characters may be interesting. +
+ + 108     :war {:verb :war :keys [:actor :other :location] +
+ + 109           :inferences [{:verb :war :actor :other :other :actor}]}}) +
+ + 110   +
+ + 111   +
+ + 112  (defn interest-in-character +
+ + 113    "Integer representation of how interesting this `character` is to this +
+ + 114    `gossip`. +
+ + 115    *TODO:* this assumes that characters are passed as keywords, but, as +
+ + 116    documented above, they probably have to be maps, to allow for degradation." +
+ + 117    [gossip character] +
+ + 118    (count +
+ + 119     (concat +
+ + 120      (filter #(= (:actor % character)) (:knowledge gossip)) +
+ + 121      (filter #(= (:other % character)) (:knowledge gossip))))) +
+ + 122   +
+ + 123  (defn interesting-character? +
+ + 124    "Boolean representation of whether this `character` is interesting to this +
+ + 125    `gossip`." +
+ + 126    [gossip character] +
+ + 127    (> (interest-in-character gossip character) 0)) +
+ + 128   +
+ + 129  (defn interest-in-location +
+ + 130    "Integer representation of how interesting this `location` is to this +
+ + 131    `gossip`." +
+ + 132    [gossip location] +
+ + 133    (cond +
+ + 134      (and (map? location) (number? (:x location)) (number? (:y location))) +
+ + 135      (if-let [home (:home gossip)] +
+ + 136        (let [d (distance-between location home) +
+ + 137              i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should +
+ + 138              ;;fall off with distance from home, but possibly on a log scale +
+ + 139              ] +
+ + 140          (if (> i 1) i 0)) +
+ + 141        0) +
+ + 142      (coll? location) +
+ + 143      (reduce +
+ + 144       + +
+ + 145       (map +
+ + 146        #(interest-in-location gossip %) +
+ + 147        location)) +
+ + 148      :else +
+ + 149      (count +
+ + 150       (filter +
+ + 151        #(some (fn [x] (= x location)) (:location %)) +
+ + 152        (cons {:location (:home gossip)} (:knowledge gossip)))))) +
+ + 153   +
+ + 154  ;; (interest-in-location {:home [{0, 0} :test-home] :knowledge []} [:test-home]) +
+ + 155   +
+ + 156  (defn interesting-location? +
+ + 157    "True if the location of this news `item` is interesting to this `gossip`." +
+ + 158    [gossip item] +
+ + 159    (> (interest-in-location gossip (:location item)) 0)) +
+ + 160   +
+ + 161  (defn interesting-object? +
+ + 162    [gossip object] +
+ + 163    ;; TODO: Not yet (really) implemented +
+ + 164    true) +
+ + 165   +
+ + 166  (defn interesting-topic? +
+ + 167    [gossip topic] +
+ + 168    ;; TODO: Not yet (really) implemented +
+ + 169    true) +
+ + 170   +
+ + 171  (defn compatible-value? +
+ + 172    "True if `known-value` is the same as `new-value`, or, for each key present +
+ + 173     in `new-value`, has the same value for that key.  +
+ + 174      +
+ + 175     The rationale here is that if `new-value` contains new or different  +
+ + 176     information, it's worth learning; otherwise, not." +
+ + 177    [new-value known-value] +
+ + 178    (or +
+ + 179     (= new-value known-value) +
+ + 180     ;; TODO: some handwaving here about being a slightly better descriptor -- +
+ + 181     ;; having more keys than might  +
+ + 182     (when (and (map? new-value) (map? known-value)) +
+ + 183       (every? true? (map #(= (new-value %) (known-value %)) +
+ + 184                          (keys new-value)))))) +
+ + 185   +
+ + 186  (defn compatible-item? +
+ + 187    "True if `new-item` is identical with, or less specific than, `known-item`. +
+ + 188      +
+ + 189     If we already know 'Bad Joe killed Sweet Daisy', there's no point in  +
+ + 190     learning that 'someone killed Sweet Daisy', but there is point in learning +
+ + 191     'someone killed Sweet Daisy _with poison_'." +
+ + 192    [new-item known-item] +
+ + 193    (reduce +
+ + 194     #(and %1 %2) +
+ + 195     (map #(if +
+ + 196            (known-item %) ;; if known-item has this key +
+ + 197             (compatible-value? (new-item %) (known-item %)) +
+ + 198             true) +
+ + 199          (remove #{:nth-hand :confidence :learned-from} (keys new-item))))) +
+ + 200   +
+ + 201  (defn known-item? +
+ + 202    "True if this news `item` is already known to this `gossip`. +
+ + 203      +
+ + 204     This means that the `gossip` already knows an item which identifiably has +
+ + 205     the same _or more specific_ values for all the keys of this `item` except +
+ + 206     `:nth-hand`, `:confidence` and `:learned-from`." +
+ + 207    [gossip item] +
+ + 208    (reduce +
+ + 209     #(or %1 %2) +
+ + 210     (filter true? (map #(compatible-item? item %) (:knowledge gossip))))) +
+ + 211   +
+ + 212  (defn interesting-item? +
+ + 213    "True if anything about this news `item` is interesting to this `gossip`." +
+ + 214    [gossip item] +
+ + 215    (and (not (known-item? gossip item)) +
+ + 216         (or +
+ + 217          (interesting-character? gossip (:actor item)) +
+ + 218          (interesting-character? gossip (:other item)) +
+ + 219          (interesting-location? gossip (:location item)) +
+ + 220          (interesting-object? gossip (:object item)) +
+ + 221          (interesting-topic? gossip (:verb item))))) +
+ + 222   +
+ + 223  (defn infer +
+ + 224    "Infer a new knowledge item from this `item`, following this `rule`" +
+ + 225    [item rule] +
+ + 226    (reduce merge +
+ + 227            item +
+ + 228            (cons +
+ + 229             {:verb (:verb rule)} +
+ + 230             (map (fn [k] {k (apply (k rule) (list item))}) +
+ + 231                  (remove +
+ + 232                   #{:verb} +
+ + 233                   (keys rule)))))) +
+ + 234   +
+ + 235  (declare learn-news-item) +
+ + 236   +
+ + 237  (defn make-all-inferences +
+ + 238    "Return a list of knowledge entries that can be inferred from this news +
+ + 239    `item`." +
+ + 240    [item] +
+ + 241    (set +
+ + 242     (reduce +
+ + 243      concat +
+ + 244      (map +
+ + 245       #(:knowledge (learn-news-item {} (infer item %) false)) +
+ + 246       (:inferences (news-topics (:verb item))))))) +
+ + 247   +
+ + 248  (defn degrade-character +
+ + 249    "Return a character specification like this `character`, but comprising +
+ + 250    only those properties this `gossip` is interested in." +
+ + 251    [gossip character] +
+ + 252    ;; TODO: Not yet (really) implemented +
+ + 253    character) +
+ + 254   +
+ + 255  (defn degrade-location +
+ + 256    "Return a location specification like this `location`, but comprising +
+ + 257    only those elements this `gossip` is interested in. If none, return +
+ + 258    `nil`." +
+ + 259    [gossip location] +
+ + 260    (let [l (when +
+ + 261             (coll? location) +
+ + 262              (filter +
+ + 263               #(when (interesting-location? gossip %) %) +
+ + 264               location))] +
+ + 265      (when-not (empty? l) l))) +
+ + 266   +
+ + 267  (defn inc-or-one +
+ + 268    "If this `val` is a number, return that number incremented by one; otherwise, +
+ + 269     return 1. TODO: should probably be in `utils`." +
+ + 270    [val] +
+ + 271    (if +
+ + 272     (number? val) +
+ + 273      (inc val) +
+ + 274      1)) +
+ + 275   +
+ + 276  (defn learn-news-item +
+ + 277    "Return a gossip like this `gossip`, which has learned this news `item` if +
+ + 278    it is of interest to them." +
+ + 279    ([gossip item] +
+ + 280     (learn-news-item gossip item true)) +
+ + 281    ([gossip item follow-inferences?] +
+ + 282     (if +
+ + 283      (interesting-item? gossip item) +
+ + 284       (let [item' (assoc +
+ + 285                    item +
+ + 286                    :nth-hand (inc-or-one (:nth-hand item)) +
+ + 287                    :time-stamp (if +
+ + 288                                 (number? (:time-stamp item)) +
+ + 289                                  (:time-stamp item) +
+ + 290                                  (game-time)) +
+ + 291                    :location (degrade-location gossip (:location item)) +
+ + 292                    :actor (degrade-character gossip (:actor item)) +
+ + 293                    :other (degrade-character gossip (:other item)) +
+ + 294                    ;; TODO: do something to degrade confidence in the item, +
+ + 295                    ;; probably as a function of the provider's confidence in +
+ + 296                    ;; the item and the gossip's trust in the provider +
+ + 297                    ) +
+ + 298             g (assoc +
+ + 299                gossip +
+ + 300                :knowledge +
+ + 301                (cons +
+ + 302                 item' +
+ + 303                 (:knowledge gossip)))] +
+ + 304         (if follow-inferences? +
+ + 305           (assoc +
+ + 306            g +
+ + 307            :knowledge +
+ + 308            (concat (:knowledge g) (make-all-inferences item))) +
+ + 309           g))) +
+ + 310     gossip)) +
+ + 311   +
+ + 312   +
+ + 313   +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/holdings/holding.clj.html b/docs/cloverage/cc/journeyman/the_great_game/holdings/holding.clj.html new file mode 100644 index 0000000..1ed2918 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/holdings/holding.clj.html @@ -0,0 +1,146 @@ + + + + cc/journeyman/the_great_game/holdings/holding.clj + + + + 001  (ns cc.journeyman.the-great-game.holdings.holding +
+ + 002    (:require [cc.journeyman.the-great-game.agent.agent :refer [ProtoAgent]] +
+ + 003              [cc.journeyman.the-great-game.objects.container :refer [ProtoContainer]] +
+ + 004              [cc.journeyman.the-great-game.objects.game-object :refer [ProtoObject]] +
+ + 005  ;;            [cc.journeyman.the-great-game.location.location :refer [OrientedLocation]] +
+ + 006              [cc.journeyman.the-great-game.world.routes :refer []])) +
+ + 007   +
+ + 008  ;;; A holding is a polygonal area of the map which does not +
+ + 009  ;;; intersect with any other holding, or with any road or water feature. For  +
+ + 010  ;;; the time being we'll make the  +
+ + 011  ;;; simplifying assumption that every holding is a rectangular strip, and that +
+ + 012  ;;; 'urban' holdings are of a reasonably standard width (see Viking-period  +
+ + 013  ;;; York) and length. Rural holdings (farms, ?wood lots) may be much larger. +
+ + 014   +
+ + 015  (defprotocol ProtoHolding +
+ + 016    (frontage +
+ + 017      [holding] +
+ + 018      "Returns a sequence of two locations representing the edge of the polygon +
+ + 019      which defines this holding which is considered to be the front.") +
+ + 020    (building-origin +
+ + 021      [holding] +
+ + 022      "Returns an oriented location - normally the right hand end of the  +
+ + 023      frontage, for an urban holding - from which buildings on the holding +
+ + 024      should be built.")) +
+ + 025   +
+ + 026  (defrecord Holding [perimeter holder] +
+ + 027    ;; Perimeter should be a list of locations in exactly the same sense as a +
+ + 028    ;; route in `cc.journeyman.the-great-game.world.routes`. Some sanity checking +
+ + 029    ;; is needed to ensure this! +
+ + 030    ProtoContainer +
+ + 031    ProtoHolding +
+ + 032    (frontage  +
+ + 033      [holding] +
+ + 034     "TODO: this is WRONG, but will work for now. The frontage should +
+ + 035      be the side of the perimeter nearest to the nearest existing  +
+ + 036      route." +
+ + 037      [(first (perimeter holding)) (nth (perimeter holding) 1)]) +
+ + 038    (building-origin  +
+ + 039     [holding] +
+ + 040     "TODO: again this is WRONG. The default building origin for rectangular  +
+ + 041      buildings should be the right hand end of the frontage when viewed +
+ + 042      from outside the holding. But that's not general; celtic-style circular +
+ + 043      buildings should normally be in the centre of their holdings. So probably +
+ + 044      building-origin becomes a method of building-family rather than of holding." +
+ + 045     (first (frontage holding))) +
+ + 046    ProtoObject) +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/location/location.clj.html b/docs/cloverage/cc/journeyman/the_great_game/location/location.clj.html new file mode 100644 index 0000000..a27e3bf --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/location/location.clj.html @@ -0,0 +1,149 @@ + + + + cc/journeyman/the_great_game/location/location.clj + + + + 001  (ns cc.journeyman.the-great-game.location.location) +
+ + 002   +
+ + 003  ;;; There's probably conflict between this sense of a reified location and +
+ + 004  ;;; the simpler sense of a location described in  +
+ + 005  ;;; `cc.journeyman.the-great-game.world.location`, q.v. This needs to +
+ + 006  ;;; be resolved! +
+ + 007   +
+ + 008  (defprotocol ProtoLocation +
+ + 009    (easting [location] +
+ + 010     "Return the easting of this location") +
+ + 011    (northing [location] "Return the northing of this location") +
+ + 012    (altitude [location] +
+ + 013              "Return the absolute altitude of this location, which may be +
+ + 014               different from the terrain height at this location, if, for +
+ + 015               example, the location is underground or on an upper floor.") +
+ + 016    (terrain-altitude [location] +
+ + 017                      "Return the 'ground level' (altitude of the terrain) +
+ + 018                       at this location given this world. TODO: possibly +
+ + 019                       terrain-altitude should be a method of the world.") +
+ + 020    (settlement [location] +
+ + 021                "Return the settlement record of the settlement in this world +
+ + 022                 within whose parish polygon this location exists, or if none +
+ + 023                 whose centre (inn location) is closest to this location")) +
+ + 024   +
+ + 025   +
+ + 026  (defrecord Location [^Double easting ^Double northing ^Double altitude world] +
+ + 027    ProtoLocation +
+ + 028    (easting [l] (:easting l)) +
+ + 029    (northing [l] (:northing l)) +
+ + 030    (altitude [l] (:altitude l)) +
+ + 031    (terrain-altitude [l] 0.0) ;; TODO +
+ + 032    (settlement [l] :tchahua)) +
+ + 033   +
+ + 034  (defrecord OrientedLocation +
+ + 035    ;; "Identical to a Location except having, additionally, an orientation" +
+ + 036             [^Double easting ^Double northing ^Double altitude ^Double orientation world] +
+ + 037    ProtoLocation +
+ + 038    (easting [l] (:easting l)) +
+ + 039    (northing [l] (:northing l)) +
+ + 040    (altitude [l] (:altitude l)) +
+ + 041    (terrain-altitude [l] 0.0) ;; TODO +
+ + 042    (settlement [l] :tchahua)) ;; TODO +
+ + 043   +
+ + 044   ;; (.settlement (OrientedLocation. 123.45 543.76 12.34 0.00 {})) +
+ + 045   +
+ + 046   +
+ + 047  ;; (OrientedLocation. 123.45 543.76 12.34 0.00 {}) +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/merchants/markets.clj.html b/docs/cloverage/cc/journeyman/the_great_game/merchants/markets.clj.html new file mode 100644 index 0000000..ad08ec1 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/merchants/markets.clj.html @@ -0,0 +1,260 @@ + + + + cc/journeyman/the_great_game/merchants/markets.clj + + + + 001  (ns cc.journeyman.the-great-game.merchants.markets +
+ + 002    "Adjusting quantities and prices in markets." +
+ + 003    (:require [taoensso.timbre :as l :refer [info error]] +
+ + 004              [cc.journeyman.the-great-game.utils :refer [deep-merge]])) +
+ + 005   +
+ + 006  (defn new-price +
+ + 007    "If `stock` is greater than the maximum of `supply` and `demand`, then +
+ + 008    there is surplus and `old` price is too high, so shold be reduced. If +
+ + 009    lower, then it is too low and should be increased." +
+ + 010    [old stock supply demand] +
+ + 011    (let +
+ + 012      [delta (dec' (/ (max supply demand 1) (max stock 1))) +
+ + 013       scaled (/ delta 100)] +
+ + 014      (+ old scaled))) +
+ + 015   +
+ + 016   +
+ + 017  (defn adjust-quantity-and-price +
+ + 018    "Adjust the quantity of this `commodity` currently in stock in this `city` +
+ + 019    of this `world`. Return a fragmentary world which can be deep-merged into +
+ + 020    this world." +
+ + 021    [world city commodity] +
+ + 022    (let [c (cond +
+ + 023              (keyword? city) (-> world :cities city) +
+ + 024              (map? city) city) +
+ + 025          id (:id c) +
+ + 026          p (or (-> c :prices commodity) 0) +
+ + 027          d (or (-> c :demands commodity) 0) +
+ + 028          st (or (-> c :stock commodity) 0) +
+ + 029          su (or (-> c :supplies commodity) 0) +
+ + 030          decrement (min st d) +
+ + 031          increment (cond +
+ + 032                      ;; if we've two turns' production of this commodity in +
+ + 033                      ;; stock, halt production +
+ + 034                      (> st (* su 2)) +
+ + 035                      0 +
+ + 036                      ;; if it is profitable to produce this commodity, the +
+ + 037                      ;; craftspeople of the city will do so. +
+ + 038                      (> p 1) su +
+ + 039                      ;; otherwise, if there isn't a turn's production in +
+ + 040                      ;; stock, top up the stock, so that there's something for +
+ + 041                      ;; incoming merchants to buy +
+ + 042                      (> su st) +
+ + 043                      (- su st) +
+ + 044                      :else +
+ + 045                      0) +
+ + 046          n (new-price p st su d)] +
+ + 047      (if +
+ + 048        (not= p n) +
+ + 049        (l/info "Price of" commodity "at" id "has changed from" (float p) "to" (float n))) +
+ + 050      {:cities {id +
+ + 051                {:stock +
+ + 052                 {commodity (+ (- st decrement) increment)} +
+ + 053                 :prices +
+ + 054                 {commodity n}}}})) +
+ + 055   +
+ + 056   +
+ + 057  (defn update-markets +
+ + 058    "Return a world like this `world`, with quantities and prices in markets +
+ + 059    updated to reflect supply and demand. If `city` or `city` and `commodity` +
+ + 060    are specified, return a fragmentary world with only the changes for that +
+ + 061    `city` (and `commodity` if specified) populated." +
+ + 062    ([world] +
+ + 063     (reduce +
+ + 064       deep-merge +
+ + 065       world +
+ + 066       (map +
+ + 067         #(update-markets world %) +
+ + 068         (keys (:cities world))))) +
+ + 069    ([world city] +
+ + 070     (reduce +
+ + 071       deep-merge +
+ + 072       {} +
+ + 073       (map #(update-markets world city %) +
+ + 074            (keys (:commodities world))))) +
+ + 075    ([world city commodity] +
+ + 076      (adjust-quantity-and-price world city commodity))) +
+ + 077   +
+ + 078   +
+ + 079  (defn run +
+ + 080    "Return a world like this `world`, with quantities and prices in markets +
+ + 081    updated to reflect supply and demand." +
+ + 082    [world] +
+ + 083    (update-markets world)) +
+ + 084   +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/merchants/merchant_utils.clj.html b/docs/cloverage/cc/journeyman/the_great_game/merchants/merchant_utils.clj.html new file mode 100644 index 0000000..377f27a --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/merchants/merchant_utils.clj.html @@ -0,0 +1,326 @@ + + + + cc/journeyman/the_great_game/merchants/merchant_utils.clj + + + + 001  (ns cc.journeyman.the-great-game.merchants.merchant-utils +
+ + 002    "Useful functions for doing low-level things with merchants.") +
+ + 003   +
+ + 004  (defn expected-price +
+ + 005    "Find the price anticipated, given this `world`, by this `merchant` for +
+ + 006    this `commodity` in this `city`. If no information, assume 1. +
+ + 007    `merchant` should be passed as a map, `commodity` and `city` should be passed as keywords." +
+ + 008    [merchant commodity city] +
+ + 009    (or +
+ + 010      (:price +
+ + 011        (last +
+ + 012          (sort-by +
+ + 013            :date +
+ + 014            (-> merchant :known-prices city commodity)))) +
+ + 015      1)) +
+ + 016   +
+ + 017  (defn burden +
+ + 018    "The total weight of the current cargo carried by this `merchant` in this +
+ + 019    `world`." +
+ + 020    [merchant world] +
+ + 021    (let [m (cond +
+ + 022              (keyword? merchant) +
+ + 023              (-> world :merchants merchant) +
+ + 024              (map? merchant) +
+ + 025              merchant) +
+ + 026          cargo (or (:stock m) {})] +
+ + 027      (reduce +
+ + 028        + +
+ + 029        0 +
+ + 030        (map +
+ + 031          #(* (cargo %) (-> world :commodities % :weight)) +
+ + 032          (keys cargo))))) +
+ + 033   +
+ + 034   +
+ + 035  (defn can-carry +
+ + 036    "Return the number of units of this `commodity` which this `merchant` +
+ + 037    can carry in this `world`, given their current burden." +
+ + 038    [merchant world commodity] +
+ + 039    (let [m (cond +
+ + 040              (keyword? merchant) +
+ + 041              (-> world :merchants merchant) +
+ + 042              (map? merchant) +
+ + 043              merchant)] +
+ + 044      (max +
+ + 045        0 +
+ + 046        (quot +
+ + 047          (- (or (:capacity m) 0) (burden m world)) +
+ + 048          (-> world :commodities commodity :weight))))) +
+ + 049   +
+ + 050  (defn can-afford +
+ + 051    "Return the number of units of this `commodity` which this `merchant` +
+ + 052    can afford to buy in this `world`." +
+ + 053    [merchant world commodity] +
+ + 054    (let [m (cond +
+ + 055              (keyword? merchant) +
+ + 056              (-> world :merchants merchant) +
+ + 057              (map? merchant) +
+ + 058              merchant) +
+ + 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 +
+ + 066        (quot +
+ + 067          (:cash m) +
+ + 068          (-> world :cities l :prices commodity))))) +
+ + 069   +
+ + 070  (defn add-stock +
+ + 071    "Where `a` and `b` are both maps all of whose values are numbers, return +
+ + 072    a map whose keys are a union of the keys of `a` and `b` and whose values +
+ + 073    are the sums of their respective values." +
+ + 074    [a b] +
+ + 075    (reduce +
+ + 076      merge +
+ + 077      a +
+ + 078      (map +
+ + 079        #(hash-map % (+ (or (a %) 0) (or (b %) 0))) +
+ + 080        (keys b)))) +
+ + 081   +
+ + 082  (defn add-known-prices +
+ + 083    "Add the current prices at this `merchant`'s location in the `world` +
+ + 084    to a new cache of known prices, and return it." +
+ + 085    [merchant world] +
+ + 086    (let [m (cond +
+ + 087              (keyword? merchant) +
+ + 088              (-> world :merchants merchant) +
+ + 089              (map? merchant) +
+ + 090              merchant) +
+ + 091          k (or (:known-prices m) {}) +
+ + 092          l (:location m) +
+ + 093          d (or (:date world) 0) +
+ + 094          p (-> world :cities l :prices)] +
+ + 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 +
+ + 105            #(hash-map % (apply vector cons {:price (p %) :date d} (k %))) +
+ + 106            (-> world :commodities keys)))))) +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/merchants/merchants.clj.html b/docs/cloverage/cc/journeyman/the_great_game/merchants/merchants.clj.html new file mode 100644 index 0000000..94daa96 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/merchants/merchants.clj.html @@ -0,0 +1,92 @@ + + + + cc/journeyman/the_great_game/merchants/merchants.clj + + + + 001  (ns cc.journeyman.the-great-game.merchants.merchants +
+ + 002    "Trade planning for merchants, primarily." +
+ + 003    (:require [cc.journeyman.the-great-game.utils :refer [deep-merge]] +
+ + 004              [cc.journeyman.the-great-game.merchants.strategies.simple :refer [move-merchant]] +
+ + 005              [taoensso.timbre :as l])) +
+ + 006   +
+ + 007   +
+ + 008  (defn run +
+ + 009    "Return a partial world based on this `world`, but with each merchant moved." +
+ + 010    [world] +
+ + 011    (try +
+ + 012      (reduce +
+ + 013       deep-merge +
+ + 014       world +
+ + 015       (map +
+ + 016        #(try +
+ + 017           (let [move-fn (or +
+ + 018                          (-> world :merchants % :move-fn) +
+ + 019                          move-merchant)] +
+ + 020             (apply move-fn (list % world))) +
+ + 021           (catch Exception any +
+ + 022             (l/error any "Failure while moving merchant " %) +
+ + 023             {})) +
+ + 024        (keys (:merchants world)))) +
+ + 025      (catch Exception any +
+ + 026        (l/error any "Failure while moving merchants") +
+ + 027        world))) +
+ + 028   +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/merchants/planning.clj.html b/docs/cloverage/cc/journeyman/the_great_game/merchants/planning.clj.html new file mode 100644 index 0000000..ee3243d --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/merchants/planning.clj.html @@ -0,0 +1,485 @@ + + + + cc/journeyman/the_great_game/merchants/planning.clj + + + + 001  (ns cc.journeyman.the-great-game.merchants.planning +
+ + 002    "Trade planning for merchants, primarily. This follows a simple-minded +
+ + 003    generate-and-test strategy and currently generates plans for all possible +
+ + 004    routes from the current location. This may not scale. Also, routes do not +
+ + 005    currently have cost or risk associated with them." +
+ + 006    (:require [cc.journeyman.the-great-game.utils :refer [deep-merge make-target-filter]] +
+ + 007              [cc.journeyman.the-great-game.merchants.merchant-utils :refer [can-afford can-carry expected-price]] +
+ + 008              [cc.journeyman.the-great-game.world.routes :refer [find-route]] +
+ + 009              [cc.journeyman.the-great-game.world.world :refer [actual-price default-world]])) +
+ + 010   +
+ + 011  (defn generate-trade-plans +
+ + 012    "Generate all possible trade plans for this `merchant` and this `commodity` +
+ + 013    in this `world`. +
+ + 014   +
+ + 015    Returned plans are maps with keys: +
+ + 016   +
+ + 017    * :merchant - the id of the `merchant` for whom the plan was created; +
+ + 018    * :origin - the city from which the trade starts; +
+ + 019    * :destination - the city to which the trade is planned; +
+ + 020    * :commodity - the `commodity` to be carried; +
+ + 021    * :buy-price - the price at which that `commodity` can be bought; +
+ + 022    * :expected-price - the price at which the `merchant` anticipates +
+ + 023    that `commodity` can be sold; +
+ + 024    * :distance - the number of stages in the planned journey +
+ + 025    * :dist-to-home - the distance from `destination` to the `merchant`'s +
+ + 026    home city." +
+ + 027    [merchant world commodity] +
+ + 028    (let [m (cond +
+ + 029              (keyword? merchant) +
+ + 030              (-> world :merchants merchant) +
+ + 031              (map? merchant) +
+ + 032              merchant) +
+ + 033          origin (:location m)] +
+ + 034      (map +
+ + 035        #(hash-map +
+ + 036           :merchant (:id m) +
+ + 037           :origin origin +
+ + 038           :destination % +
+ + 039           :commodity commodity +
+ + 040           :buy-price (actual-price world commodity origin) +
+ + 041           :expected-price (expected-price +
+ + 042                             m +
+ + 043                             commodity +
+ + 044                             %) +
+ + 045           :distance (count +
+ + 046                       (find-route world origin %)) +
+ + 047           :dist-to-home (count +
+ + 048                           (find-route +
+ + 049                             world +
+ + 050                             (:home m) +
+ + 051                             %))) +
+ + 052        (remove #(= % origin) (-> world :cities keys))))) +
+ + 053   +
+ + 054  (defn nearest-with-targets +
+ + 055    "Return the distance to the nearest destination among those of these +
+ + 056    `plans` which match these `targets`. Plans are expected to be plans +
+ + 057    as returned by `generate-trade-plans`, q.v.; `targets` are expected to be +
+ + 058    as accepted by `make-target-filter`, q.v." +
+ + 059    [plans targets] +
+ + 060    (apply +
+ + 061      min +
+ + 062      (map +
+ + 063        :distance +
+ + 064        (filter +
+ + 065          (make-target-filter targets) +
+ + 066          plans)))) +
+ + 067   +
+ + 068  (defn plan-trade +
+ + 069    "Find the best destination in this `world` for this `commodity` given this +
+ + 070    `merchant` and this `origin`. If two cities are anticipated to offer the +
+ + 071    same price, the nearer should be preferred; if two are equally distant, the +
+ + 072    ones nearer to the merchant's home should be preferred. +
+ + 073    `merchant` may be passed as a map or a keyword; `commodity` should  be +
+ + 074    passed as a keyword. +
+ + 075   +
+ + 076    The returned plan is a map with keys: +
+ + 077   +
+ + 078    * :merchant - the id of the `merchant` for whom the plan was created; +
+ + 079    * :origin - the city from which the trade starts; +
+ + 080    * :destination - the city to which the trade is planned; +
+ + 081    * :commodity - the `commodity` to be carried; +
+ + 082    * :buy-price - the price at which that `commodity` can be bought; +
+ + 083    * :expected-price - the price at which the `merchant` anticipates +
+ + 084    that `commodity` can be sold; +
+ + 085    * :distance - the number of stages in the planned journey +
+ + 086    * :dist-to-home - the distance from `destination` to the `merchant`'s +
+ + 087    home city." +
+ + 088    [merchant world commodity] +
+ + 089    (let [plans (generate-trade-plans merchant world commodity) +
+ + 090          best-prices (filter +
+ + 091                        (make-target-filter +
+ + 092                          [[:expected-price +
+ + 093                            (apply +
+ + 094                              max +
+ + 095                              (filter number? (map :expected-price plans)))]]) +
+ + 096                        plans)] +
+ + 097      (first +
+ + 098        (sort-by +
+ + 099          ;; all other things being equal, a merchant would prefer to end closer +
+ + 100          ;; to home. +
+ + 101          #(- 0 (:dist-to-home %)) +
+ + 102          ;; a merchant will seek the best price, but won't go further than +
+ + 103          ;; needed to get it. +
+ + 104          (filter +
+ + 105            (make-target-filter +
+ + 106              [[:distance +
+ + 107                (apply min (filter number? (map :distance best-prices)))]]) +
+ + 108            best-prices))))) +
+ + 109   +
+ + 110  (defn augment-plan +
+ + 111    "Augment this `plan` constructed in this `world` for this `merchant` with +
+ + 112    the `:quantity` of goods which should be bought and the `:expected-profit` +
+ + 113    of the trade. +
+ + 114   +
+ + 115    Returns the augmented plan." +
+ + 116    [merchant world plan] +
+ + 117    (let [c (:commodity plan) +
+ + 118          o (:origin plan) +
+ + 119          q (min +
+ + 120              (or +
+ + 121                (-> world :cities o :stock c) +
+ + 122                0) +
+ + 123              (can-carry merchant world c) +
+ + 124              (can-afford merchant world c)) +
+ + 125          p (* q (- (:expected-price plan) (:buy-price plan)))] +
+ + 126      (assoc plan :quantity q :expected-profit p))) +
+ + 127   +
+ + 128  (defn select-cargo +
+ + 129    "A `merchant`, in a given location in a `world`, will choose to buy a cargo +
+ + 130    within the limit they are capable of carrying, which they can anticipate +
+ + 131    selling for a profit at a destination." +
+ + 132    [merchant world] +
+ + 133    (let [m (cond +
+ + 134              (keyword? merchant) +
+ + 135              (-> world :merchants merchant) +
+ + 136              (map? merchant) +
+ + 137              merchant) +
+ + 138          origin (:location m) +
+ + 139          available (-> world :cities origin :stock) +
+ + 140          plans (map +
+ + 141                  #(augment-plan +
+ + 142                     m +
+ + 143                     world +
+ + 144                     (plan-trade m world %)) +
+ + 145                  (filter +
+ + 146                    #(let [q (-> world :cities origin :stock %)] +
+ + 147                       (and (number? q) (pos? q))) +
+ + 148                    (keys available)))] +
+ + 149      (if +
+ + 150        (not (empty? plans)) +
+ + 151        (first +
+ + 152          (sort-by +
+ + 153            #(- 0 (:dist-to-home %)) +
+ + 154            (filter +
+ + 155              (make-target-filter +
+ + 156                [[:expected-profit +
+ + 157                  (apply max (filter number? (map :expected-profit plans)))]]) +
+ + 158              plans)))))) +
+ + 159   +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/merchants/strategies/simple.clj.html b/docs/cloverage/cc/journeyman/the_great_game/merchants/strategies/simple.clj.html new file mode 100644 index 0000000..48b73c0 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/merchants/strategies/simple.clj.html @@ -0,0 +1,527 @@ + + + + cc/journeyman/the_great_game/merchants/strategies/simple.clj + + + + 001  (ns cc.journeyman.the-great-game.merchants.strategies.simple +
+ + 002    "Default trading strategy for merchants. +
+ + 003   +
+ + 004    The simple strategy buys a single product in the local market if there is +
+ + 005    one which can be traded profitably, trades it to the chosen target market, +
+ + 006    and sells it there. If there is no commodity locally which can be traded +
+ + 007    profitably, moves towards home with no cargo. If at home and no commodity +
+ + 008    can be traded profitably, does not move." +
+ + 009    (:require [taoensso.timbre :as l :refer [info error spy]] +
+ + 010              [cc.journeyman.the-great-game.utils :refer [deep-merge]] +
+ + 011              [cc.journeyman.the-great-game.gossip.gossip :refer [move-gossip]] +
+ + 012              [cc.journeyman.the-great-game.merchants.planning :refer [augment-plan plan-trade select-cargo]] +
+ + 013              [cc.journeyman.the-great-game.merchants.merchant-utils :refer +
+ + 014               [add-stock add-known-prices]] +
+ + 015              [cc.journeyman.the-great-game.world.routes :refer [find-route]])) +
+ + 016   +
+ + 017  (defn plan-and-buy +
+ + 018    "Return a world like this `world`, in which this `merchant` has planned +
+ + 019    a new trade, and bought appropriate stock for it. If no profitable trade +
+ + 020    can be planned, the merchant is simply moved towards their home." +
+ + 021    [merchant world] +
+ + 022    (let [m (cond +
+ + 023              (keyword? merchant) +
+ + 024              (-> world :merchants merchant) +
+ + 025              (map? merchant) +
+ + 026              merchant) +
+ + 027          id (:id m) +
+ + 028          location (:location m) +
+ + 029          market (-> world :cities location) +
+ + 030          plan (select-cargo merchant world)] +
+ + 031      (l/debug "plan-and-buy: merchant" id) +
+ + 032      (cond +
+ + 033        (seq? plan) +
+ + 034        (let +
+ + 035          [c (:commodity plan) +
+ + 036           p (* (:quantity plan) (:buy-price plan)) +
+ + 037           q (:quantity plan)] +
+ + 038          (l/info "Merchant" id "bought" q "units of" c "at" location "for" p plan) +
+ + 039          {:merchants +
+ + 040           {id +
+ + 041            {:stock (add-stock (:stock m) {c q}) +
+ + 042             :cash (- (:cash m) p) +
+ + 043             :known-prices (add-known-prices m world) +
+ + 044             :plan plan}} +
+ + 045           :cities +
+ + 046           {location +
+ + 047            {:stock (assoc (:stock market) c (- (-> market :stock c) q)) +
+ + 048             :cash (+ (:cash market) p)}}}) +
+ + 049        ;; if no plan, then if at home stay put +
+ + 050        (= (:location m) (:home m)) +
+ + 051        (do +
+ + 052          (l/info "Merchant" id "remains at home in" location) +
+ + 053          {}) +
+ + 054        ;; else move towards home +
+ + 055        :else +
+ + 056        (let [route (find-route world location (:home m)) +
+ + 057              next-location (nth route 1)] +
+ + 058          (l/info "No trade possible at" location "; merchant" id "moves to" next-location) +
+ + 059          (merge +
+ + 060            {:merchants +
+ + 061             {id +
+ + 062              {:location next-location}}} +
+ + 063            (move-gossip id world next-location)))))) +
+ + 064   +
+ + 065  (defn re-plan +
+ + 066    "Having failed to sell a cargo at current location, re-plan a route to +
+ + 067    sell the current cargo. Returns a revised world." +
+ + 068    [merchant world] +
+ + 069    (let [m (cond +
+ + 070              (keyword? merchant) +
+ + 071              (-> world :merchants merchant) +
+ + 072              (map? merchant) +
+ + 073              merchant) +
+ + 074          id (:id m) +
+ + 075          location (:location m) +
+ + 076          plan (augment-plan m world (plan-trade m world (-> m :plan :commodity)))] +
+ + 077      (l/debug "re-plan: merchant" id) +
+ + 078      (deep-merge +
+ + 079        world +
+ + 080        {:merchants +
+ + 081         {id +
+ + 082          {:plan plan}}}))) +
+ + 083   +
+ + 084  (defn sell-and-buy +
+ + 085    "Return a new world like this `world`, in which this `merchant` has sold +
+ + 086    their current stock in their current location, and planned a new trade, and +
+ + 087    bought appropriate stock for it." +
+ + 088    ;; TODO: this either sells the entire cargo, or, if the market can't afford +
+ + 089    ;; it, none of it. And it does not cope with selling different commodities +
+ + 090    ;; in different markets. +
+ + 091    [merchant world] +
+ + 092    (let [m (cond +
+ + 093              (keyword? merchant) +
+ + 094              (-> world :merchants merchant) +
+ + 095              (map? merchant) +
+ + 096              merchant) +
+ + 097          id (:id m) +
+ + 098          location (:location m) +
+ + 099          market (-> world :cities location) +
+ + 100          stock-value (reduce +
+ + 101                        + +
+ + 102                        (map +
+ + 103                          #(* (-> m :stock %) (-> market :prices m)) +
+ + 104                          (keys (:stock m))))] +
+ + 105      (l/debug "sell-and-buy: merchant" id) +
+ + 106      (if +
+ + 107        (>= (:cash market) stock-value) +
+ + 108        (do +
+ + 109          (l/info "Merchant" id "sells" (:stock m) "at" location "for" stock-value) +
+ + 110          (plan-and-buy +
+ + 111            merchant +
+ + 112            (deep-merge +
+ + 113              world +
+ + 114              {:merchants +
+ + 115               {id +
+ + 116                {:stock {} +
+ + 117                 :cash (+ (:cash m) stock-value) +
+ + 118                 :known-prices (add-known-prices m world)}} +
+ + 119               :cities +
+ + 120               {location +
+ + 121                {:stock (add-stock (:stock m) (:stock market)) +
+ + 122                 :cash (- (:cash market) stock-value)}}}))) +
+ + 123        ;; else +
+ + 124        (re-plan merchant world)))) +
+ + 125   +
+ + 126  (defn move-merchant +
+ + 127    "Handle general en route movement of this `merchant` in this `world`; +
+ + 128    return a (partial or full) world like this `world` but in which the +
+ + 129    merchant may have been moved ot updated." +
+ + 130    [merchant world] +
+ + 131    (let [m (cond +
+ + 132              (keyword? merchant) +
+ + 133              (-> world :merchants merchant) +
+ + 134              (map? merchant) +
+ + 135              merchant) +
+ + 136          id (:id m) +
+ + 137          at-destination? (and (:plan m) (= (:location m) (-> m :plan :destination))) +
+ + 138          plan (:plan m) +
+ + 139          next-location (if plan +
+ + 140                          (nth +
+ + 141                            (find-route +
+ + 142                              world +
+ + 143                              (:location m) +
+ + 144                              (:destination plan)) +
+ + 145                            1) +
+ + 146                          (:location m))] +
+ + 147      (l/debug "move-merchant: merchant" id "at" (:location m) +
+ + 148               "destination" (-> m :plan :destination) "next" next-location +
+ + 149               "at destination" at-destination?) +
+ + 150      (cond +
+ + 151        ;; if the merchant is at the destination of their current plan +
+ + 152        ;; sell all cargo and repurchase. +
+ + 153        at-destination? +
+ + 154        (sell-and-buy merchant world) +
+ + 155        ;; if they don't have a plan, seek to create one +
+ + 156        (nil? plan) +
+ + 157        (plan-and-buy merchant world) +
+ + 158        ;; otherwise, move one step towards their destination +
+ + 159        (and next-location (not= next-location (:location m))) +
+ + 160        (do +
+ + 161          (l/info "Merchant " id " moving from " (:location m) " to " next-location) +
+ + 162          (deep-merge +
+ + 163            {:merchants +
+ + 164             {id +
+ + 165              {:location next-location +
+ + 166               :known-prices (add-known-prices m world)}}} +
+ + 167            (move-gossip id world next-location))) +
+ + 168        :else +
+ + 169        (do +
+ + 170          (l/info "Merchant" id "has plan but no next-location; currently at" +
+ + 171                  (:location m) ", destination is" (:destination plan)) +
+ + 172          world)))) +
+ + 173   +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/objects/container.clj.html b/docs/cloverage/cc/journeyman/the_great_game/objects/container.clj.html new file mode 100644 index 0000000..0140330 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/objects/container.clj.html @@ -0,0 +1,41 @@ + + + + cc/journeyman/the_great_game/objects/container.clj + + + + 001  (ns cc.journeyman.the-great-game.objects.container +
+ + 002    (:require +
+ + 003      [cc.journeyman.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/cc/journeyman/the_great_game/objects/game_object.clj.html b/docs/cloverage/cc/journeyman/the_great_game/objects/game_object.clj.html new file mode 100644 index 0000000..32e0483 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/objects/game_object.clj.html @@ -0,0 +1,71 @@ + + + + cc/journeyman/the_great_game/objects/game_object.clj + + + + 001  (ns cc.journeyman.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] +
+ + 020      "TODO: doesn't work yet" +
+ + 021      object)) +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/playroom.clj.html b/docs/cloverage/cc/journeyman/the_great_game/playroom.clj.html new file mode 100644 index 0000000..a2574c4 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/playroom.clj.html @@ -0,0 +1,224 @@ + + + + cc/journeyman/the_great_game/playroom.clj + + + + 001  (ns cc.journeyman.the-great-game.playroom +
+ + 002    (:require [jme-clj.core :refer [add add-to-root box defsimpleapp fly-cam geo  +
+ + 003                                    get* get-state load-texture rotate run set*  +
+ + 004                                    setc set-state start unshaded-mat]]) +
+ + 005    (:import [com.jme3.math ColorRGBA])) +
+ + 006   +
+ + 007  ;; At present this file is just somewhere to play around with jme-clj examples +
+ + 008   +
+ + 009  (declare app) +
+ + 010   +
+ + 011  (defn init [] +
+ + 012    (let [cube (geo "jMonkey cube" (box 1 1 1)) +
+ + 013          mat  (unshaded-mat)] +
+ + 014      (set* mat :texture "ColorMap" (load-texture "textures/Monkey.jpg")) +
+ + 015      (set* cube :material mat) +
+ + 016      (add-to-root cube) +
+ + 017      {:cube cube})) +
+ + 018   +
+ + 019  ;; Let's create simple-update fn with no body for now. +
+ + 020   (defn simple-update [tpf] +
+ + 021     (let [{:keys [cube]} (get-state)] +
+ + 022       (rotate cube 0 (* 2 tpf) 0))) +
+ + 023   +
+ + 024   +
+ + 025  ;; Kills the running app var and closes its window. +
+ + 026  ;; (unbind-app #'app) +
+ + 027   +
+ + 028  ;; We define the `app` var. +
+ + 029  (defsimpleapp app +
+ + 030                 :opts {:show-settings?       false +
+ + 031                        :pause-on-lost-focus? false +
+ + 032                        :settings             {:title          "My JME Game" +
+ + 033                                               :load-defaults? true +
+ + 034                                               :frame-rate     60 +
+ + 035                                               :width          800 +
+ + 036                                               :height         600}} +
+ + 037                 :init init +
+ + 038                 :update simple-update) +
+ + 039   +
+ + 040  (start app) +
+ + 041   +
+ + 042  ;; Reinitialises the running app +
+ + 043  ;;(run app +
+ + 044  ;;     (re-init init)) +
+ + 045    +
+ + 046   ;; By default, there is a Fly Camera attached to the app that you can control with W, A, S and D keys. +
+ + 047   ;; Let's increase its movement speed. Now, you fly faster :) +
+ + 048   (run app +
+ + 049        (set* (fly-cam) :move-speed 15)) +
+ + 050   +
+ + 051   +
+ + 052   ;; Updates the app  +
+ + 053  (run app +
+ + 054       (let [{:keys [cube]} (get-state)] +
+ + 055         (set* cube :local-translation (add (get* cube :local-translation) 1 1 1)))) +
+ + 056   +
+ + 057    ;; Updates the app adding a second cube +
+ + 058  (run app +
+ + 059        (let [cube (geo "jMonkey cube" (box 1 1 1)) +
+ + 060              mat  (unshaded-mat)] +
+ + 061          (set* mat :texture "ColorMap" (load-texture "textures/Monkey.jpg")) +
+ + 062          (setc cube +
+ + 063                :material mat +
+ + 064                :local-translation [-3 0 0]) +
+ + 065          (add-to-root cube) +
+ + 066          (set-state :cube2 cube))) +
+ + 067    +
+ + 068   ;; We added the new cube, but it's not rotating. We need to update the simple-update fn. +
+ + 069   (defn simple-update [tpf] +
+ + 070     (let [{:keys [cube cube2]} (get-state)] +
+ + 071       (rotate cube 0 (* 2 tpf) 0) +
+ + 072       (rotate cube2 0 (* 2 tpf) 0))) +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/time.clj.html b/docs/cloverage/cc/journeyman/the_great_game/time.clj.html new file mode 100644 index 0000000..f869045 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/time.clj.html @@ -0,0 +1,440 @@ + + + + cc/journeyman/the_great_game/time.clj + + + + 001  (ns cc.journeyman.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/cc/journeyman/the_great_game/utils.clj.html b/docs/cloverage/cc/journeyman/the_great_game/utils.clj.html new file mode 100644 index 0000000..640231f --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/utils.clj.html @@ -0,0 +1,143 @@ + + + + cc/journeyman/the_great_game/utils.clj + + + + 001  (ns cc.journeyman.the-great-game.utils) +
+ + 002   +
+ + 003  (defn cyclic? +
+ + 004    "True if two or more elements of `route` are identical" +
+ + 005    [route] +
+ + 006    (not= (count route)(count (set route)))) +
+ + 007   +
+ + 008  (defn deep-merge +
+ + 009    "Recursively merges maps. Stolen from +
+ + 010    https://dnaeon.github.io/recursively-merging-maps-in-clojure/" +
+ + 011    [& maps] +
+ + 012    (letfn [(m [& xs] +
+ + 013               (if (some #(and (map? %) (not (record? %))) xs) +
+ + 014                 (apply merge-with m xs) +
+ + 015                 (last xs)))] +
+ + 016      (reduce m maps))) +
+ + 017   +
+ + 018  (defn make-target-filter +
+ + 019    "Construct a filter which, when applied to a list of maps, +
+ + 020    will pass those which match these `targets`, where each target +
+ + 021    is a tuple [key value]." +
+ + 022    ;; TODO: this would probably be more elegant as a macro +
+ + 023    [targets] +
+ + 024    (eval +
+ + 025      (list +
+ + 026        'fn +
+ + 027        (vector 'm) +
+ + 028        (cons +
+ + 029          'and +
+ + 030          (map +
+ + 031            #(list +
+ + 032               '= +
+ + 033               (list (first %) 'm) +
+ + 034               (nth % 1)) +
+ + 035            targets))))) +
+ + 036   +
+ + 037  (defn value-or-default +
+ + 038    "Return the value of this key `k` in this map `m`, or this `dflt` value if +
+ + 039    there is none." +
+ + 040    [m k dflt] +
+ + 041    (or (when (map? m) (m k)) dflt)) +
+ + 042   +
+ + 043  ;; (value-or-default {:x 0 :y 0 :altitude 7} :altitude 8) +
+ + 044  ;; (value-or-default {:x 0 :y 0 :altitude 7} :alt 8) +
+ + 045  ;; (value-or-default nil :altitude 8) +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/world/heightmap.clj.html b/docs/cloverage/cc/journeyman/the_great_game/world/heightmap.clj.html new file mode 100644 index 0000000..4ca6508 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/world/heightmap.clj.html @@ -0,0 +1,485 @@ + + + + cc/journeyman/the_great_game/world/heightmap.clj + + + + 001  (ns cc.journeyman.the-great-game.world.heightmap +
+ + 002    "Functions dealing with the tessellated multi-layer heightmap." +
+ + 003      (:require [clojure.math.numeric-tower :refer [expt sqrt]] +
+ + 004                [mw-engine.core :refer []] +
+ + 005                [mw-engine.heightmap :refer [apply-heightmap]] +
+ + 006                [mw-engine.utils :refer [get-cell in-bounds? map-world]] +
+ + 007                [cc.journeyman.the-great-game.utils :refer [value-or-default]])) +
+ + 008   +
+ + 009  ;; It's not at all clear to me yet what the workflow for getting a MicroWorld +
+ + 010  ;; map into The Great Game, and whether it passes through Walkmap to get here. +
+ + 011  ;; This file as currently written assumes it doesn't. +
+ + 012   +
+ + 013  ;; It's utterly impossible to hold a whole continent at one metre scale in +
+ + 014  ;; memory at one time. So we have to be able to regenerate high resolution +
+ + 015  ;; surfaces from much lower resolution heightmaps. +
+ + 016  ;; +
+ + 017  ;; Thus to reproduce a segment of surface at a particular level of detail, +
+ + 018  ;; we: +
+ + 019  ;; 1. load the base heightmap into a grid (see +
+ + 020  ;;    `mw-engine.heightmap/apply-heightmap`); +
+ + 021  ;; 2. scale the base hightmap to kilometre scale (see `scale-grid`); +
+ + 022  ;; 3. exerpt the portion of that that we want to reproduce (see `exerpt-grid`); +
+ + 023  ;; 4. interpolate that grid to get the resolution we require (see +
+ + 024  ;;    `interpolate-grid`); +
+ + 025  ;; 5. create an appropriate purturbation grid from the noise map(s) for the +
+ + 026  ;;    same coordinates to break up the smooth interpolation; +
+ + 027  ;; 6. sum the altitudes of the two grids. +
+ + 028  ;; +
+ + 029  ;; In production this will have to be done **very** fast! +
+ + 030   +
+ + 031  (def ^:dynamic *base-map* "resources/maps/heightmap.png") +
+ + 032  (def ^:dynamic *noise-map* "resources/maps/noise.png") +
+ + 033   +
+ + 034  (defn scale-grid +
+ + 035    "multiply all `:x` and `:y` values in this `grid` by this `n`." +
+ + 036    [grid n] +
+ + 037    (map-world grid (fn [w c x] (assoc c :x (* (:x c) n) :y (* (:y c) n))))) +
+ + 038   +
+ + 039   +
+ + 040   +
+ + 041  ;; Each of the east-west curve and the north-south curve are of course two +
+ + 042  ;; dimensional curves; the east-west curve is in the :x/:z plane and the +
+ + 043  ;; north-south curve is in the :y/:z plane (except, perhaps unwisely, +
+ + 044  ;; we've been using :altitude to label the :z plane). We have a library +
+ + 045  ;; function `walkmap.edge/intersection2d`, but as currently written it +
+ + 046  ;; can only find intersections in :x/:y plane. +
+ + 047  ;; +
+ + 048  ;; TODO: rewrite the function so that it can use arbitrary coordinates. +
+ + 049  ;; AFTER TRYING: OK, there are too many assumptions about the way that +
+ + 050  ;; function is written to allow for easy rotation. TODO: think! +
+ + 051   +
+ + 052  (defn interpolate-altitude +
+ + 053    "Return the altitude of the point at `x-offset`, `y-offset` within this +
+ + 054    `cell` having this `src-width`, taken from this `grid`." +
+ + 055    [cell grid src-width x-offset y-offset ] +
+ + 056    (let [c-alt (:altitude cell) +
+ + 057          n-alt (or (:altitude (get-cell grid (:x cell) (dec (:y cell)))) c-alt) +
+ + 058          w-alt (or (:altitude (get-cell grid (inc (:x cell)) (:y cell))) c-alt) +
+ + 059          s-alt (or (:altitude (get-cell grid (:x cell) (inc (:y cell)))) c-alt) +
+ + 060          e-alt (or (:altitude (get-cell grid (dec (:x cell)) (:y cell))) c-alt)] +
+ + 061      ;; TODO: construct two curves (arcs of circles good enough for now) +
+ + 062      ;; n-alt...c-alt...s-alt and e-alt...c-alt...w-alt; +
+ + 063      ;; then interpolate x-offset along e-alt...c-alt...w-alt and y-offset +
+ + 064      ;; along n-alt...c-alt...s-alt; +
+ + 065      ;; then return the average of the two +
+ + 066   +
+ + 067      0)) +
+ + 068   +
+ + 069  (defn interpolate-cell +
+ + 070    "Construct a grid (array of arrays) of cells each of width `target-width` +
+ + 071    from this `cell`, of width `src-width`, taken from this `grid`" +
+ + 072    [cell grid src-width target-width] +
+ + 073    (let [offsets (map #(* target-width %) (range (/ src-width target-width)))] +
+ + 074      (into +
+ + 075        [] +
+ + 076        (map +
+ + 077          (fn [r] +
+ + 078            (into +
+ + 079              [] +
+ + 080              (map +
+ + 081                (fn [c] +
+ + 082                  (assoc cell +
+ + 083                    :x (+ (:x cell) c) +
+ + 084                    :y (+ (:y cell) r) +
+ + 085                    :altitude (interpolate-altitude cell grid src-width c r))) +
+ + 086                offsets))) +
+ + 087          offsets)))) +
+ + 088   +
+ + 089  (defn interpolate-grid +
+ + 090    "Return a grid interpolated from this `grid` of rows, cols given scaling +
+ + 091    from this `src-width` to this `target-width`" +
+ + 092    [grid src-width target-width] +
+ + 093    (reduce +
+ + 094      concat +
+ + 095      (into +
+ + 096        [] +
+ + 097        (map +
+ + 098          (fn [row] +
+ + 099            (reduce +
+ + 100              (fn [g1 g2] +
+ + 101                (into [] (map #(into [] (concat %1 %2)) g1 g2))) +
+ + 102              (into [] (map #(interpolate-cell % grid src-width target-width) row)))) +
+ + 103          grid)))) +
+ + 104   +
+ + 105  (defn excerpt-grid +
+ + 106    "Return that section of this `grid` where the `:x` co-ordinate of each cell +
+ + 107    is greater than or equal to this `x-offset`, the `:y` co-ordinate is greater +
+ + 108    than or equal to this `y-offset`, whose width is not greater than this +
+ + 109    `width`, and whose height is not greater than this `height`." +
+ + 110    [grid x-offset y-offset width height] +
+ + 111    (into +
+ + 112      [] +
+ + 113      (remove +
+ + 114        nil? +
+ + 115        (map +
+ + 116          (fn [row] +
+ + 117            (when +
+ + 118              (and +
+ + 119                (>= (:y (first row)) y-offset) +
+ + 120                (< (:y (first row)) (+ y-offset height))) +
+ + 121              (into +
+ + 122                [] +
+ + 123                (remove +
+ + 124                  nil? +
+ + 125                  (map +
+ + 126                    (fn [cell] +
+ + 127                      (when +
+ + 128                        (and +
+ + 129                          (>= (:x cell) x-offset) +
+ + 130                          (< (:x cell) (+ x-offset width))) +
+ + 131                        cell)) +
+ + 132                    row))))) +
+ + 133         grid)))) +
+ + 134   +
+ + 135  (defn get-surface +
+ + 136    "Return, as a vector of vectors of cells represented as Clojure maps, a +
+ + 137    segment of surface from this `base-map` as modified by this +
+ + 138    `noise-map` at this `cell-size` starting at this `x-offset` and `y-offset` +
+ + 139    and having this `width` and `height`. +
+ + 140   +
+ + 141    If `base-map` and `noise-map` are not supplied, the bindings of `*base-map*` +
+ + 142    and `*noise-map*` will be used, respectively. +
+ + 143   +
+ + 144    `base-map` and `noise-map` may be passed either as strings, assumed to be +
+ + 145    file paths of PNG files, or as MicroWorld style world arrays. It is assumed +
+ + 146    that one pixel in `base-map` represents one square kilometre in the game +
+ + 147    world. It is assumed that `cell-size`, `x-offset`, `y-offset`, `width` and +
+ + 148    `height` are integer numbers of metres." +
+ + 149    ([cell-size x-offset y-offset width height] +
+ + 150     (get-surface *base-map* *noise-map* cell-size x-offset y-offset width height)) +
+ + 151    ([base-map noise-map cell-size x-offset y-offset width height] +
+ + 152     (let [b (if (seq? base-map) base-map (scale-grid (apply-heightmap base-map) 1000)) +
+ + 153           n (if (seq? noise-map) noise-map (apply-heightmap noise-map))] +
+ + 154       (if (and (in-bounds? b x-offset y-offset) +
+ + 155                (in-bounds? b (+ x-offset width) (+ y-offset height))) +
+ + 156         b ;; actually do stuff +
+ + 157         (throw (Exception. "Surface out of bounds for map."))) +
+ + 158       ))) +
+ + 159   +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/world/location.clj.html b/docs/cloverage/cc/journeyman/the_great_game/world/location.clj.html new file mode 100644 index 0000000..d8fdc31 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/world/location.clj.html @@ -0,0 +1,119 @@ + + + + cc/journeyman/the_great_game/world/location.clj + + + + 001  (ns cc.journeyman.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/cloverage/cc/journeyman/the_great_game/world/mw.clj.html b/docs/cloverage/cc/journeyman/the_great_game/world/mw.clj.html new file mode 100644 index 0000000..80194bc --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/world/mw.clj.html @@ -0,0 +1,29 @@ + + + + cc/journeyman/the_great_game/world/mw.clj + + + + 001  (ns cc.journeyman.the-great-game.world.mw +
+ + 002    "Functions dealing with building a great game world from a MicroWorld world." +
+ + 003      (:require [clojure.math.numeric-tower :refer [expt sqrt]] +
+ + 004                [mw-engine.core :refer []] +
+ + 005                [mw-engine.world :refer []])) +
+ + 006   +
+ + 007  ;; It's not at all clear to me yet what the workflow for getting a MicroWorld map into The Great Game, and whether it passes through Walkmap to get here. This file as currently written assumes it doesn't. +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/world/routes.clj.html b/docs/cloverage/cc/journeyman/the_great_game/world/routes.clj.html new file mode 100644 index 0000000..5d5ee4e --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/world/routes.clj.html @@ -0,0 +1,173 @@ + + + + cc/journeyman/the_great_game/world/routes.clj + + + + 001  (ns cc.journeyman.the-great-game.world.routes +
+ + 002    "Conceptual (plan level) routes, represented as tuples of location ids." +
+ + 003    (:require [cc.journeyman.the-great-game.utils :refer [cyclic?]])) +
+ + 004   +
+ + 005  (defn find-routes +
+ + 006    "Find routes from among these `routes` from `from`; if `to` is supplied, +
+ + 007    to `to`, by breadth-first search." +
+ + 008    ([routes from] +
+ + 009     (map +
+ + 010       (fn [to] (cons from to)) +
+ + 011       (remove +
+ + 012         empty? +
+ + 013         (map +
+ + 014           (fn [route] +
+ + 015             (remove +
+ + 016               #(= from %) +
+ + 017               (if (some #(= % from) route) route))) +
+ + 018           routes)))) +
+ + 019    ([routes from to] +
+ + 020     (let [steps (find-routes routes from) +
+ + 021           found (filter +
+ + 022                   (fn [step] (if (some #(= to %) step) step)) +
+ + 023                   steps)] +
+ + 024       (if +
+ + 025         (empty? found) +
+ + 026         (find-routes routes from to steps) +
+ + 027         found))) +
+ + 028    ([routes from to steps] +
+ + 029     (if +
+ + 030       (not (empty? steps)) +
+ + 031       (let [paths (remove +
+ + 032                     cyclic? +
+ + 033                     (mapcat +
+ + 034                         (fn [path] +
+ + 035                           (map +
+ + 036                             (fn [x] (concat path (rest x))) +
+ + 037                             (find-routes routes (last path)))) +
+ + 038                         steps)) +
+ + 039             found (filter +
+ + 040                     #(= (last %) to) paths)] +
+ + 041         (if +
+ + 042           (empty? found) +
+ + 043           (find-routes routes from to paths) +
+ + 044           found))))) +
+ + 045   +
+ + 046  (defn find-route +
+ + 047    "Find a single route from `from` to `to` in this `world-or-routes`, which +
+ + 048    may be either a world as defined in [[the-great-game.world.world]] or else +
+ + 049    a sequence of tuples of keywords." +
+ + 050    [world-or-routes from to] +
+ + 051    (first +
+ + 052      (find-routes +
+ + 053        (or (:routes world-or-routes) world-or-routes) +
+ + 054        from +
+ + 055        to))) +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/world/run.clj.html b/docs/cloverage/cc/journeyman/the_great_game/world/run.clj.html new file mode 100644 index 0000000..f161313 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/world/run.clj.html @@ -0,0 +1,125 @@ + + + + cc/journeyman/the_great_game/world/run.clj + + + + 001  (ns cc.journeyman.the-great-game.world.run +
+ + 002    "Run the whole simulation" +
+ + 003    (:require [environ.core :refer [env]] +
+ + 004              [taoensso.timbre :as timbre] +
+ + 005              [taoensso.timbre.appenders.3rd-party.rotor :as rotor] +
+ + 006              [cc.journeyman.the-great-game.gossip.gossip :as g] +
+ + 007              [cc.journeyman.the-great-game.merchants.merchants :as m] +
+ + 008              [cc.journeyman.the-great-game.merchants.markets :as k] +
+ + 009              [cc.journeyman.the-great-game.world.world :as w])) +
+ + 010   +
+ + 011  (defn init +
+ + 012    ([] +
+ + 013     (init {})) +
+ + 014    ([config] +
+ + 015     (timbre/merge-config! +
+ + 016       {:appenders +
+ + 017        {:rotor (rotor/rotor-appender +
+ + 018                  {:path "the-great-game.log" +
+ + 019                   :max-size (* 512 1024) +
+ + 020                   :backlog 10})} +
+ + 021        :level (or +
+ + 022                 (:log-level config) +
+ + 023                 (if (env :dev) :debug) +
+ + 024                 :info)}))) +
+ + 025   +
+ + 026  (defn run +
+ + 027    "The pipeline to run the simulation each game day. Returns a world like +
+ + 028    this world, with all the various active elements updated. The optional +
+ + 029    `date` argument, if supplied, is set as the `:date` of the returned world." +
+ + 030    ([world] +
+ + 031    (g/run +
+ + 032      (m/run +
+ + 033        (k/run +
+ + 034          (w/run world))))) +
+ + 035    ([world date] +
+ + 036    (g/run +
+ + 037      (m/run +
+ + 038        (k/run +
+ + 039          (w/run world date)))))) +
+ + diff --git a/docs/cloverage/cc/journeyman/the_great_game/world/world.clj.html b/docs/cloverage/cc/journeyman/the_great_game/world/world.clj.html new file mode 100644 index 0000000..7170770 --- /dev/null +++ b/docs/cloverage/cc/journeyman/the_great_game/world/world.clj.html @@ -0,0 +1,584 @@ + + + + cc/journeyman/the_great_game/world/world.clj + + + + 001  (ns cc.journeyman.the-great-game.world.world +
+ + 002    "Access to data about the world") +
+ + 003   +
+ + 004  ;;; The world has to work either as map or a database. Initially, and for +
+ + 005  ;;; unit tests, I'll use a map; later, there will be a database. But the +
+ + 006  ;;; API needs to be agnostic, so that heirarchies which interact with +
+ + 007  ;;; `world` don't have to know which they've got - as far as they're concerned +
+ + 008  ;;; it's just a handle. +
+ + 009   +
+ + 010  (def default-world +
+ + 011    "A basic world for testing concepts" +
+ + 012    {:date 0 ;; the age of this world in game days +
+ + 013     :cities +
+ + 014     {:aberdeen +
+ + 015      {:id :aberdeen +
+ + 016       :supplies +
+ + 017       ;; `supplies` is the quantity of each commodity added to the stock +
+ + 018       ;; each game day. If the price in the market is lower than 1 (the +
+ + 019       ;; cost of production of a unit) no goods will be added. +
+ + 020       {:fish 10 +
+ + 021        :leather 5} +
+ + 022       :demands +
+ + 023       ;; `stock` is the quantity of each commodity in the market at any +
+ + 024       ;; given time. It is adjusted for production and consumption at +
+ + 025       ;; the end of each game day. +
+ + 026       {:iron 1 +
+ + 027        :cloth 10 +
+ + 028        :whisky 10} +
+ + 029       :port true +
+ + 030       :prices +
+ + 031       ;; `prices`: the current price (both buying and selling, for simplicity) +
+ + 032       ;; of each commodity in the market. Updated each game day based on current +
+ + 033       ;; stock. +
+ + 034       {:cloth 1 +
+ + 035        :fish 1 +
+ + 036        :leather 1 +
+ + 037        :iron 1 +
+ + 038        :whisky 1} +
+ + 039       :stock +
+ + 040       ;; `stock` is the quantity of each commodity in the market at any +
+ + 041       ;; given time. It is adjusted for production and consumption at +
+ + 042       ;; the end of each game day. +
+ + 043       {:cloth 0 +
+ + 044        :fish 0 +
+ + 045        :leather 0 +
+ + 046        :iron 0 +
+ + 047        :whisky 0} +
+ + 048       :cash 100} +
+ + 049      :buckie +
+ + 050      {:id :buckie +
+ + 051       :supplies +
+ + 052       {:fish 20} +
+ + 053       :demands +
+ + 054       {:cloth 5 +
+ + 055        :leather 3 +
+ + 056        :whisky 5 +
+ + 057        :iron 1} +
+ + 058       :port true +
+ + 059       :prices {:cloth 1 +
+ + 060                :fish 1 +
+ + 061                :leather 1 +
+ + 062                :iron 1 +
+ + 063                :whisky 1} +
+ + 064       :stock {:cloth 0 +
+ + 065               :fish 0 +
+ + 066               :leather 0 +
+ + 067               :iron 0 +
+ + 068               :whisky 0} +
+ + 069       :cash 100} +
+ + 070      :callander +
+ + 071      {:id :callander +
+ + 072       :supplies {:leather 20} +
+ + 073       :demands +
+ + 074       {:cloth 5 +
+ + 075        :fish 3 +
+ + 076        :whisky 5 +
+ + 077        :iron 1} +
+ + 078       :prices {:cloth 1 +
+ + 079                :fish 1 +
+ + 080                :leather 1 +
+ + 081                :iron 1 +
+ + 082                :whisky 1} +
+ + 083       :stock {:cloth 0 +
+ + 084               :fish 0 +
+ + 085               :leather 0 +
+ + 086               :iron 0 +
+ + 087               :whisky 0} +
+ + 088       :cash 100} +
+ + 089      :dundee {:id :dundee} +
+ + 090      :edinburgh {:id :dundee} +
+ + 091      :falkirk +
+ + 092      {:id :falkirk +
+ + 093       :supplies {:iron 10} +
+ + 094       :demands +
+ + 095       {:cloth 5 +
+ + 096        :leather 3 +
+ + 097        :whisky 5 +
+ + 098        :fish 10} +
+ + 099       :port true +
+ + 100       :prices {:cloth 1 +
+ + 101                :fish 1 +
+ + 102                :leather 1 +
+ + 103                :iron 1 +
+ + 104                :whisky 1} +
+ + 105       :stock {:cloth 0 +
+ + 106               :fish 0 +
+ + 107               :leather 0 +
+ + 108               :iron 0 +
+ + 109               :whisky 0} +
+ + 110       :cash 100} +
+ + 111      :glasgow +
+ + 112      {:id :glasgow +
+ + 113       :supplies {:whisky 10} +
+ + 114       :demands +
+ + 115       {:cloth 5 +
+ + 116        :leather 3 +
+ + 117        :iron 5 +
+ + 118        :fish 10} +
+ + 119       :port true +
+ + 120       :prices {:cloth 1 +
+ + 121                :fish 1 +
+ + 122                :leather 1 +
+ + 123                :iron 1 +
+ + 124                :whisky 1} +
+ + 125       :stock {:cloth 0 +
+ + 126               :fish 0 +
+ + 127               :leather 0 +
+ + 128               :iron 0 +
+ + 129               :whisky 0} +
+ + 130       :cash 100}} +
+ + 131     :merchants +
+ + 132     {:archie {:id :archie +
+ + 133               :home :aberdeen :location :aberdeen :cash 100 :capacity 10 +
+ + 134               :known-prices {} +
+ + 135               :stock {}} +
+ + 136      :belinda {:id :belinda +
+ + 137                :home :buckie :location :buckie :cash 100 :capacity 10 +
+ + 138                :known-prices {} +
+ + 139                :stock {}} +
+ + 140      :callum {:id :callum +
+ + 141               :home :callander :location :calander :cash 100 :capacity 10 +
+ + 142               :known-prices {} +
+ + 143               :stock {}} +
+ + 144      :deirdre {:id :deidre +
+ + 145                :home :dundee :location :dundee :cash 100 :capacity 10 +
+ + 146                :known-prices {} +
+ + 147                :stock {}} +
+ + 148      :euan {:id :euan +
+ + 149             :home :edinbirgh :location :edinburgh :cash 100 :capacity 10 +
+ + 150               :known-prices {} +
+ + 151               :stock {}} +
+ + 152      :fiona {:id :fiona +
+ + 153              :home :falkirk :location :falkirk :cash 100 :capacity 10 +
+ + 154              :known-prices {} +
+ + 155              :stock {}}} +
+ + 156     :routes +
+ + 157     ;; all routes can be traversed in either direction and are assumed to +
+ + 158     ;; take the same amount of time. +
+ + 159     [[:aberdeen :buckie] +
+ + 160      [:aberdeen :dundee] +
+ + 161      [:callander :glasgow] +
+ + 162      [:dundee :callander] +
+ + 163      [:dundee :edinburgh] +
+ + 164      [:dundee :falkirk] +
+ + 165      [:edinburgh :falkirk] +
+ + 166      [:falkirk :glasgow]] +
+ + 167     :commodities +
+ + 168     ;; cost of commodities is expressed in person/days; +
+ + 169     ;; weight in packhorse loads. Transport in this model +
+ + 170     ;; is all overland; you don't take bulk cargoes overland +
+ + 171     ;; in this period, it's too expensive. +
+ + 172     {:cloth {:id :cloth :cost 1 :weight 0.25} +
+ + 173      :fish {:id :fish :cost 1 :weight 1} +
+ + 174      :leather {:id :leather :cost 1 :weight 0.5} +
+ + 175      :whisky {:id :whisky :cost 1 :weight 0.1} +
+ + 176      :iron {:id :iron :cost 1 :weight 10}}}) +
+ + 177   +
+ + 178  (defn actual-price +
+ + 179    "Find the actual current price of this `commodity` in this `city` given +
+ + 180    this `world`. **NOTE** that merchants can only know the actual prices in +
+ + 181    the city in which they are currently located." +
+ + 182    [world commodity city] +
+ + 183    (-> world :cities city :prices commodity)) +
+ + 184   +
+ + 185  (defn run +
+ + 186    "Return a world like this `world` with only the `:date` to this `date` +
+ + 187    (or id `date` not supplied, the current value incremented by one). For +
+ + 188    running other aspects of the simulation, see [[the-great-game.world.run]]." +
+ + 189    ([world] +
+ + 190     (run world (inc (or (:date world) 0)))) +
+ + 191    ([world date] +
+ + 192     (assoc world :date date))) +
+ + diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index 6901d4d..724b600 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -15,44 +15,102 @@ TotalBlankInstrumented - the-great-game.gossip.gossip
cc.journeyman.the-great-game.agent.agent
3
+100.00 % +
3
+100.00 % +4553 + + + cc.journeyman.the-great-game.buildings.rectangular
106
36
+74.65 % +
24
1
6
+80.65 % +1802531 + + + cc.journeyman.the-great-game.gossip.gossip
5
103
4.63 %
5
34
-12.82 % -66539 + style="width:87.5%; + float:left;"> 35
+12.50 % +73740 - the-great-game.gossip.news-items
477
37
-92.80 % + cc.journeyman.the-great-game.gossip.news-items
437
197
+68.93 %
96
8
5
-95.41 % -25631109 + style="width:63.07692307692308%; + float:left;"> 82
10
38
+70.77 % +31336130 - the-great-game.merchants.markets
191
cc.journeyman.the-great-game.holdings.holding
3
18
+14.29 % +
3
4
+42.86 % +4637 + + + cc.journeyman.the-great-game.location.location
4
22
+15.38 % +
4
10
+28.57 % +47814 + + + cc.journeyman.the-great-game.merchants.markets
192
7
-96.46 % +96.48 %
41
84844 - the-great-game.merchants.merchant-utils
cc.journeyman.the-great-game.merchants.merchant-utils
197
106772 - the-great-game.merchants.merchants
cc.journeyman.the-great-game.merchants.merchants
2
69
-2.82 % + style="width:97.26027397260275%; + float:left;"> 71
+2.74 %
2
28316 - the-great-game.merchants.planning
cc.journeyman.the-great-game.merchants.planning
258
1591185 - the-great-game.merchants.strategies.simple
cc.journeyman.the-great-game.merchants.strategies.simple
5
600
-0.83 % + style="width:99.18433931484502%; + float:left;"> 608
+0.82 %
5
1736124 - the-great-game.objects.container
cc.journeyman.the-great-game.objects.container
2
100.00 % @@ -139,48 +197,86 @@ 1112 - the-great-game.objects.game-object
cc.journeyman.the-great-game.objects.game-object
3
2
-60.00 % + style="width:50.0%; + float:left;"> 3
+50.00 %
3
2
-60.00 % -1925 + style="width:50.0%; + float:left;"> 3
+50.00 % +2126 - the-great-game.time
240
1
-99.59 % + cc.journeyman.the-great-game.playroom
463
75
+86.06 %
59
28
5
2
+94.29 % +721235 + + + cc.journeyman.the-great-game.time
236
5
+97.93 % +
58
1
1
-100.00 % +98.33 % 1442160 - the-great-game.utils
69
-100.00 % + cc.journeyman.the-great-game.utils
70
13
+84.34 %
19
-100.00 % -35319 + style="width:95.23809523809524%; + float:left;"> 20
1
+95.24 % +45521 - the-great-game.world.location
cc.journeyman.the-great-game.world.heightmap
11
295
+3.59 % +
9
62
+12.68 % +1591671 + + + cc.journeyman.the-great-game.world.location
73
37417 - the-great-game.world.routes
cc.journeyman.the-great-game.world.mw
1
+100.00 % +
1
+100.00 % +711 + + + cc.journeyman.the-great-game.world.routes
123
55242 - the-great-game.world.run
cc.journeyman.the-great-game.world.run
3
39220 - the-great-game.world.world
cc.journeyman.the-great-game.world.world
420
-66.55 % +61.00 % -68.63 % +61.78 % diff --git a/docs/codox/Baking-the-world.html b/docs/codox/Baking-the-world.html index 1114c07..7d2d95a 100644 --- a/docs/codox/Baking-the-world.html +++ b/docs/codox/Baking-the-world.html @@ -1,9 +1,9 @@ -Baking the world

Baking the world

+Baking the world

Baking the world

Wednesday, 8 May 2019

-

Devorgilla’s Bridge in Dumfries, early foourteenth century

-

Devorgilla’s Bridge in Dumfries, early foourteenth century. This clearly shows how a genetic buildings approach to bridges can be made to work: a single element is repeated to span the necessary distance. That element can be stretched vertically and laterally to match the location, and can be rendered in different stone finishes to match local geology.

+

Devorgilla’s Bridge in Dumfries, early fourteenth century

+

Devorgilla’s Bridge in Dumfries, early fourteenth century. This clearly shows how a genetic buildings approach to bridges can be made to work: a single element is repeated to span the necessary distance. That element can be stretched vertically and laterally to match the location, and can be rendered in different stone finishes to match local geology.

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

diff --git a/docs/codox/Canonical-dictionary.html b/docs/codox/Canonical-dictionary.html new file mode 100644 index 0000000..b1b2312 --- /dev/null +++ b/docs/codox/Canonical-dictionary.html @@ -0,0 +1,35 @@ + +A Canonical dictionary for this documentation

A Canonical dictionary for this documentation

+

Where a word is used in the documentation for The Great Game and its related projects, this file describes the canonical meaning of that word. This is because a lot of the concepts in play are messy and ambiguous, so that at times even I am confused by what I mean. The presence of this file is an acknowledment of this difficulty, and an implicit admission that not all the documentation is, at this stage anyway, consistent.

+

Actor

+

An actor is a thing which performs actions within the game world. Thus a tree is (almost certainly) not an actor, and things like sheep and rabbits that run about are probably not actors, but an animal which may pro-actively interact with the player character (such as a predator, or a beast of burden, or even a prey species which may flee) is an actor. In god mode, if implemented, the player can inhabit any actor within the game world.

+

Agent

+

Agent is probably just a synonym for actor. If it is different in any way, that way has not yet been determined.

+

Gossip

+

A gossip is an actor who exchanges news with other actors, even when the player character is not nearby. Thus gossips are the mechanism by which news propagates through the game world, and also the mechanism by which information degrades. Broadly:

+
    +
  1. innkeepers (and possibly some others) are gossips who do not move; rather, they gather information from gossips who do move, and all non-player characters local to the are deemed to know everything that their local innkeeper knows;
  2. +
  3. merchants (and possibly some others) are gossips who do move from place to place, and thus transfer news.
  4. +
+

See the spread of knowledge in a large game world.

+

Heightmap

+

A heightmap is a raster image of the world, such that the intensity in which an area is coloured represents the value of some variable, by default height, of that area.

+

Holding

+

A holding is a polygon ‘owned’ by an actor on which are built appropriate building units representing the actors craft and status.

+

Location

+

A location value is a sequence 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, they won’t be passed on. 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.

+

Examples

+
    +
  1. [{:x 5445678 :y 9684351}]
  2. +
  3. [{:x 5445678 :y 9684351} :karalin-palace :hanshua]
  4. +
+

Merchant

+

A merchant is an actor and gossip who trades goods, and incidentally conveys news, between markets.

+

Non-player character

+

A non-player character is, for our purposes, an actor capable of engaging in conversation with the player character. Note, however, that, from a software point of view, the player character is just a special case of a non-player character.

+

Player character

+

The player character is the unique actor within the game currently controlled and inhabited by the player.

+

Route

+

A route is a pre-prepared path through the game world that an actor may take. Most actors are not constrained to follow routes, but in general routes have lower traversal cost than other terrain.

\ No newline at end of file diff --git a/docs/codox/Dynamic-consequences.html b/docs/codox/Dynamic-consequences.html new file mode 100644 index 0000000..3e99f8a --- /dev/null +++ b/docs/codox/Dynamic-consequences.html @@ -0,0 +1,41 @@ + +On the consequences of a dynamic game environment for storytelling

On the consequences of a dynamic game environment for storytelling

+

First, a framing disclaimer: in Racundra’s First Cruise, Arthur Ransome describes coming across a half built - and by the time he saw it, already obsolete - wooden sailing ship, in a Baltic forest. An old man was building it, by himself. He had been building it since he had been a young man. It’s clear that Ransome believed the ship would never be finished. It’s not clear whether the old man believed that it would, but nevertheless he was building it.

+

I will never build a complete version of The Great Game; it will probably never even be a playable prototype. It is a minor side-project of someone who

+
    +
  1. Is ill, and consequently has inconsistent levels of energy and concentration;
  2. +
  3. Has other things to do in the real world which necessarily take precedence.
  4. +
+

Nevertheless, in making design choices I want to specify something which could be built, which could, except for the technical innovations I’m trying myself to build, be built with the existing state of the art, and which if built, would be engaging and interesting to play.

+

The defining characteristic of Role Playing Games - the subcategory of games in which I am interested - is that the actions, decisions and choices of the player make a significant difference to the outcome of the plot, significantly affect change in the world. This already raises challenges for the cinematic elements in telling the game story, and those cinematic elements are one of the key rewards to the player, one of the elements of the game’s presentation which most build, and hold, player engagement. These challenges are clearly expressed in two very good videos I’ve watched recently: Who’s Commanding Shepard in Mass Effect?, which discusses how much control the player actually has/should have over the decisions of the character they play as; and What Happened with Mass Effect Andromeda’s Animation?, which discusses how the more control the player has, the bigger the task of authoring animation of all conversations and plot events becomes.

+

There are two key innovations I want to make in The Great Game which set it apart from existing Role Playing Games, both of which make the production of engaging cinematic presentation of conversation more difficult, nd I’ll handle each in turn. But before I do, there’s something I need to make clear about the nature of video games themselves: what they are for. Video games are a vehicle to tell stories, to convey narrative. They’re a rich vehicle, because the narrative is not fixed: it is at least to some degree mutable, responsive to the input of the audience: the player.

+

Clear? Let’s move on.

+

The innovations I am interested in are

+

Unconstrained natural speech input/output

+

I want the player to be able to interact with non-player characters (and, indeed, potentially with other player characters, in a multi-player context) simply by speaking to them. This means that the things the player character says cannot be scripted: there is no way for the game designer to predict the full repertoire of the player’s input. It also means that the game must construct, and put into the mouth of the non-player character being addressed, an appropriate response, given

+
    +
  1. The speech interpretation engine’s interpretation of what it is the player said;
  2. +
  3. The immediate game and plot context;
  4. +
  5. The particular non-player character addressed’s knowledge of the game world;
  6. +
  7. The particular non-player character’s attitude towards the player;
  8. +
  9. The particular non-player character’s speech idiosyncracies, dialect, and voice
  10. +
+

and it must be pretty clear that the full range of potential responses is extremely large. Consequently, it’s impossible that all non-player character speech acts can be voice acted; rather, this sort of generated speech must be synthesised. But a consequence of this is that the non-player character’s facial animation during the conversation also cannot be motion captured from a human actor; rather, it, too, must be synthesized.

+

This doesn’t mean that speech acts by non-player characters which make plot points or advance the narrative can’t be voice acted, but it does mean that the voice acting must be consistent with the simulated voice used for that non-player character - which is to say, probably, that the non-player character must use a synthetic voice derived from the voice of that particular voice actor.

+

Dynamic game environment

+

Modern Role Playing Games are, in effect, extremely complex state machines: if you do the same things in the same sequence, the same outcomes will always occur. In a world full of monsters, bandits, warring armies and other dangers, the same quest givers will be in the same places at the same times. They are clockwork worlds, filled with clockwork automata. Of course, this has the advantage that is makes testing easier - and in a game with a complex branching narrative and many quests, testing is inevitably hard.

+

My vision for The Great Game is different. It is that the economy - and with it, the day to day choices of non-player characters - should be modelled. This means, non-player characters may unexpectedly die. Of course, you could implement a tag for plot-relevant characters which prevents them being killed (except when required by the plot).

+

Plot follows player

+

As Role Playing Games have moved towards open worlds - where the player’s movement in the environment is relatively unconstrained - the clockwork has become strained. The player has to get to particular locations where particular events happen, and so the player has to be very heavily signposted. Another solution - which I’d like to explore - is ‘plot follows character’. The player is free to wander at will in the world, and plot relevant events will happen on their path. And by that I don’t mean that we associate a set of non-player characters which each quest - as current Role Playing Games do - and then uproot the whole set from wherever they normally live in the world and dumping down in the player’s path; but rather, for each role in a quest or plot event, we define a set of characteristics required to fulfill that role, and then, when the player comes to a place where there are a set of characters who have those characteristics, the quest or plot event will happen.

+

Cut scenes, cinematics and rewarding the player

+

There’s no doubt at all that ‘cut scenes’ - in effect, short movies spliced into game play during which the player has no decisions to make but can simply watch the scene unroll - are elements of modern games which players enjoy, and see to some extent as ‘rewards’. And in many games, these are beautifully constructed works. It is a very widely held view that the quality of cutscenes depends to a large degree on human authorship. The three choices I’ve made above:

+
    +
  1. We can’t always know exactly what non-player characters will say (although perhaps we can in the context of cut scenes where the player has no input);
  2. +
  3. We can’t always know exactly which non-player characters will speak the lines;
  4. +
  5. We can’t predict what a non-player character will say in response to a question, or how long that will take;
  6. +
  7. We can’t always know where any particular plot event will take place.
  8. +
+

Each of these, obviously, make the task of authoring an animation harder. The general summary of what I’m saying here is that, although in animating a conversation or cutscene what the animator is essentially animating is the skeletons of the characters, and, provided that all character models are rigged on essentially similar skeletons, substituting one character model for another in an animated scene isn’t a huge issue, with so much unknowable it is impossible that hand-authoring will be practicable, and so a lot will depend on the quality of the conversation system not merely to to produce convincingly enunciated and emoted sound, but also appropriate character animation and attractive cinematography. As you will have learned from the Mass Effect analysis videos I linked to above, that’s a big ask.

+

Essentially the gamble here is that players will find the much richer conversations, and consequent emergent gameplay, possible with non-player charcaters who have dynamic knowledge about their world sufficiently engaging to compensate for a less compelling cinematic experience. I believe that they would; but really the only way to find out would be to try.

+

Interestingly, an early preview of CD PRoject Red’s not-yet-complete Cyberpunk 2077 suggests that there will be very, very few cutscenes, suggesting that these very experienced storytellers don’t feel they need cutscenes either to tell their story or maintain player engagement. (Later) It has to be said other commentators who have also played the Cyberpunk 2077 preview say that there are a lot of cutscenes, one of them describing the prologue as ‘about half cutscenes’ - so this impression I formed may be wrong).

\ No newline at end of file diff --git a/docs/codox/Game_Play.html b/docs/codox/Game_Play.html index a1d8141..a602b52 100644 --- a/docs/codox/Game_Play.html +++ b/docs/codox/Game_Play.html @@ -1,6 +1,6 @@ -Game Play

Game Play

+Game Play

Game Play

The principles of game play which I’m looking for are a reaction against all I see as wrong in modern video games. So let’s set out what these are:

  1. diff --git a/docs/codox/Gossip_scripted_plot_and_Johnny_Silverhand.html b/docs/codox/Gossip_scripted_plot_and_Johnny_Silverhand.html index 33918d7..30a2724 100644 --- a/docs/codox/Gossip_scripted_plot_and_Johnny_Silverhand.html +++ b/docs/codox/Gossip_scripted_plot_and_Johnny_Silverhand.html @@ -1,7 +1,7 @@ -Gossip, scripted plot, and Johnny Silverhand

    Gossip, scripted plot, and Johnny Silverhand

    -

    I’ve been writing literally for years – since Voice acting considered harmful in 2015 – about game worlds in which the player speaks to non-player characters just by speaking the words they choose in their normal voice, and the non-player character replies using a pipeline that goes, essentially,

    +Gossip, scripted plot, and Johnny Silverhand

    Gossip, scripted plot, and Johnny Silverhand

    +

    I’ve been writing literally for years – since Voice acting considered harmful in 2015 – about game worlds in which the player speaks to non-player characters just by speaking the words they choose in their normal voice, and the non-player character replies using a pipeline that goes, essentially,

    1. Alexa/Siri style speech interpretation;
    2. A decision on whether to co-operate based on the particular NPC’s general demeanor and particular attitude to the player;
    3. diff --git a/docs/codox/Organic_Quests.html b/docs/codox/Organic_Quests.html index 055e53e..d2e0969 100644 --- a/docs/codox/Organic_Quests.html +++ b/docs/codox/Organic_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. @@ -12,14 +12,14 @@
      3. 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. And the trophy could be just the knowledge that the kill has happened. 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.

      -

      Note, however, that if we consider a delivery quest to have four locations, where some of these locations may be conincident, then a delivery quest and a fetch quest become the same thing. Thus

      +

      Note, however, that if we consider a delivery quest to have four locations, where some of these locations may be coincident, then a delivery quest and a fetch quest become the same thing. Thus

      1. The location of the quest giver at the beginning of the quest;
      2. The location from which the quest object must be collected;
      3. The location to which the quest object must be delivered;
      4. The location of the quest giver at the end of the quest.
      -

      This characterisation assumes that at the end of each quest, the player must rendezvous with the quest giver at the end of the quest, either to report completion or to collect a reward. Obviously, there could be some quests where this fourth location is not required, because there is no need to report back (for example, if the quest giver was dying/has died) and no reward to be collected.

      +

      This characterisation assumes that at the end of each quest, the player must rendezvous with the quest giver, either to report completion or to collect a reward. Obviously, there could be some quests where this fourth location is not required, because there is no need to report back (for example, if the quest giver was dying/has died) and no reward to be collected.

      Note that a location is not necessarily a fixed x/y location on the map; in a kill quest, for example, location 2 is the current location of the target, and moves when the target moves; location 3 and 4 are both normally the current location of the quest giver, and move when the quest giver moves.

      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.

      @@ -34,12 +34,13 @@

      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.

      +

      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. 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 change occupation 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.

      +

      The key idea behind organic quests is that the circumstance and requirements for quests emerge 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.

      Stuff to consider

      -

      The games Middle Earth: Shadow of Mordor, and Middle Earth: Shadow of War have a procedural story system called Nemesis, which is worth a look.

      \ No newline at end of file +

      The games Middle Earth: Shadow of Mordor, and Middle Earth: Shadow of War have a procedural story system called Nemesis, which is worth a look.

      +

      There’s an interesting critique of Red Dead Redemption 2 which is relevant to what I’m saying here.

      \ No newline at end of file diff --git a/docs/codox/Pathmaking.html b/docs/codox/Pathmaking.html index 17d6c51..3ad8777 100644 --- a/docs/codox/Pathmaking.html +++ b/docs/codox/Pathmaking.html @@ -1,6 +1,6 @@ -Pathmaking

      Pathmaking

      +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

      see also Baking the world

      diff --git a/docs/codox/Populating-a-game-world.html b/docs/codox/Populating-a-game-world.html index c02a951..f529c56 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/Roadmap.html b/docs/codox/Roadmap.html new file mode 100644 index 0000000..79a4459 --- /dev/null +++ b/docs/codox/Roadmap.html @@ -0,0 +1,22 @@ + +Roadmap

      Roadmap

      +

      This document outlines a plan to move forward from where I am in June 2021.

      +

      JMonkeyEngine

      +

      JMonkeyEngine is not, at this time, an AAA game engine. But at the same time I’m never, really, going to build an AAA game. It is a working game engine which can display characters on screen in scenery and have them move around, and, actually, they can be fairly sophisticated. It will be resaonably easy to integrate Clojure code with JMonkeyEngine - easier than it would be to integrate either Clojure or Common Lisp with Unreal Engine or Unity 3D. As a significant added bonus, JMonkeyEngine is open source.

      +

      Consequently I plan to stop agonising about what game engine to use, and seriously focus on getting something working in JMonkeyEngine.

      +

      Not Reinventing Wheels

      +

      JMonkeyEngine already has working code for walking animated characters, which is entirely adequate to proof-of-concept what I want to do. Rather than try to implement them myself, I just intend to use existing JMonkeyEngine code as far as possible.

      +

      The 1Km World

      +

      I propose to build a 1Km square world, containing one settlement, as a proof of concept for

      +
        +
      1. Procedural (genetic) buildings;
      2. +
      3. Procedural settlement planning;
      4. +
      5. Procedural characters, probably based on MakeHuman ‘Mass Produce’ plugin, using walk animation based on TestWalkingChar;
      6. +
      7. Characters with their own hierarchy of needs, and their own means of planning to fulfil these;
      8. +
      9. Characters with individualised knowledge about the world;
      10. +
      11. Characters who can parse typed questions, and produce either a textual or audio response;
      12. +
      13. Characters with procedurally generated accents (very stretch goal)!
      14. +
      15. Characters who can listen to spoken questions, and produce audio responses.
      16. +
      +

      At that stage, I have a technology demonstrator that will be interesting. It still leaves the big procedural world builder still to do, but it would be enough technology to get other people interested in the project.

      \ No newline at end of file diff --git a/docs/codox/Settling-a-game-world.html b/docs/codox/Settling-a-game-world.html index cbf569e..fa2eeed 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/Simulation-layers.html b/docs/codox/Simulation-layers.html index 0e37f3a..dac2565 100644 --- a/docs/codox/Simulation-layers.html +++ b/docs/codox/Simulation-layers.html @@ -1,6 +1,6 @@ -Simulation layers

      Simulation layers

      +Simulation layers

      Simulation layers

      In essence, the environment for The Great Game is broadly descended from games like the original Elite space trading game, and Sid Meier’s Pirates!, with some elements from political simulations like for example SimCity.

      That is to say there is

      An economy simulation

      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 eb95181..d6084f2 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/Uncanny_dialogue.html b/docs/codox/Uncanny_dialogue.html index e3e619e..6793ccc 100644 --- a/docs/codox/Uncanny_dialogue.html +++ b/docs/codox/Uncanny_dialogue.html @@ -1,6 +1,6 @@ -The Uncanny Valley, and dynamically generated dialogue

      The Uncanny Valley, and dynamically generated dialogue

      +The Uncanny Valley, and dynamically generated dialogue

      The Uncanny Valley, and dynamically generated dialogue

      If the player is allowed to just speak arbitrary dialogue, then the conversation animation of the player character cannot be designed. If non-player characters are able to engage dynamically generated dialogue, in response to events in the game which are not scripted, then their conversation animation for those dialogues cannot be designed. So conversation animation must almost always be dynamically generated, largely from an augmented text of the speech act. With non-player characters, emotional content of a speech act can be generated by exactly the same process which generates the text. Extracting emotional content information from the player character’s voice may be more challenging.

      It would be possible to avoid animating the player character’s face by using a first-person camera. However, I don’t personally find this makes for a very engaging game experience.

      These thoughts were prompted by a very interesting video and Twitter thread about the perceived failings in the character animation system of Mass Effect Andromeda.

      diff --git a/docs/codox/Voice-acting-considered-harmful.html b/docs/codox/Voice-acting-considered-harmful.html index 17e9dec..e47d8c2 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/building_on_microworld.html b/docs/codox/building_on_microworld.html new file mode 100644 index 0000000..60744ec --- /dev/null +++ b/docs/codox/building_on_microworld.html @@ -0,0 +1,7 @@ + +Building on Microworld

      Building on Microworld

      +

      In Settling a Game World I intended that a world should be populated by setting agents - settlers - to explore the map and select places to settle according to particular rules. In the meantime, I’ve built MicroWorld, a rule driven cellular automaton which makes a reasonably good job of modelling human settlement. It works, and I now plan to use it, as detailed in this note; but there are issues.

      +

      First and foremost, it’s slow, and both processor and memory hungry. That means that at continent scale, a cell of one kilometre square is the minimum size which is really possible, which isn’t small enough to create a settlement map of the density that a game will need. Even with 1 km cells, even on the most powerful machines I have access to, a continent-size map will take many days to run.

      +

      Of course it would be possible to do a run at one km scale top identify areas which would support settlement, and then to do a run on a ten metre grid on each of those areas to more precisely plot settlement. That’s an idea which I haven’t yet explored, which might prove fruitful.

      +

      Secondly, being a cellular automaton, MicroWorld works on a grid. This means that everything is grid aligned, which is absolutely not what I want! So I think the way to leverage this is to use MicroWorld to establish which kilometre square cells om the grid should be populated (and roughly with what), and then switch to ad hoc code to populate those cells.

      \ No newline at end of file diff --git a/docs/codox/cc.journeyman.the-great-game.agent.agent.html b/docs/codox/cc.journeyman.the-great-game.agent.agent.html new file mode 100644 index 0000000..95e3177 --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.agent.agent.html @@ -0,0 +1,10 @@ + +cc.journeyman.the-great-game.agent.agent documentation

      cc.journeyman.the-great-game.agent.agent

      Anything in the game world with agency; primarily but not exclusively characters.

      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/cc.journeyman.the-great-game.buildings.module.html b/docs/codox/cc.journeyman.the-great-game.buildings.module.html new file mode 100644 index 0000000..c2eb88c --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.buildings.module.html @@ -0,0 +1,39 @@ + +cc.journeyman.the-great-game.buildings.module documentation

      cc.journeyman.the-great-game.buildings.module

      A module of a building; essentially something like a portacabin, which can be assembled together with other modules to make a complete building.

      +

      Modules need to include

      +
        +
      1. Ground floor modules, having external doors;
      2. +
      3. Craft modules – workshops – which will normally be ground floor (except weavers) and may have the constraint that no upper floor module can cover them;
      4. +
      5. Upper floor modules, having NO external doors (but linking internal doors);
      6. +
      7. Roof modules
      8. +
      +

      Role must be one of:

      +
        +
      1. :primary a ground floor main entrance module
      2. +
      3. :secondary a module which can be upper or ground floor
      4. +
      5. :upper a module which can only be on an upper floor, for example one with a projecting gallery, balcony or overhang.
      6. +
      +

      Other values for role will emerge.

      +

      Exits must be a sequence of keywords taken from the following list:

      +
        +
      1. :left an exit in the centre of the left wall
      2. +
      3. :left-front an exit in the centre of the left half of the front wall
      4. +
      5. :front an exit in the centre of the front wall
      6. +
      7. :right-front an exit in the centre of the right half of the front wall
      8. +
      9. :right an exit in the centre of the right wall
      10. +
      11. :right-back an exit in the centre of the right half of the back wall
      12. +
      13. :left-back an exit in the centre of the back wall
      14. +
      +

      A module placed on an upper floor must have no exit which opens beyond the footprint of the floor below - no doors into mid air! However, it is allowable (and indeed is necessary) to allow doors into roof spaces if the adjacent module on the same floor does not yet exist, since otherwise it would be impossible to access a new room which might later be built there.

      +

      Load must be a small integer indicating both the weight of the module and the total amount of weight it can support. So for example a stone-built module might have a load value of 4, a brick built one of 3, and a half-timbered one of 2, and a tent of 0. This means a stone ground floor module could support one further floor of stone or brick, or two further floors of half timbered construction; while a brick built ground floor could support a single brick or half-timbered upper floor but not a stone one, and a half-timbered ground floor could only support a half timbered upper floor.

      +

      There also needs to be an undercroft or platform module, such that the area of the top of the platform is identical with the footprint of the building, and the altitude of the top of the platform is equal to the altitude of the terrain at the heighest corner of the building; so that the actual building doesn’t float in the air, and also so that none of the doors or windows are partly underground.

      +

      Each module needs to wrap an actual 3d model created in Blender or whatever, and have a list of optional textures with which that model can be rendered. So an upper floor bedroom module might have the following renders:

      +
        +
      1. Bare masonry - constrained to upland or plateau terrain, and to coastal culture
      2. +
      3. Painted masonry - constrained to upland or plateau terrain, and to coastal culture
      4. +
      5. Half-timbered - not available on plateau terrain
      6. +
      7. Weatherboarded - constrained to forest terrain
      8. +
      9. Brick - constrained to arable or arid terrain
      10. +
      +

      of course these are only examples, and also, it’s entirely possible to have for example multiple different weatherboard renders for the same module. There needs to be a way of rendering what can be built above what: for example, you can’t have a masonry clad module over a half timbered one, but you can have a half-timbered one over a masonry one.

      \ No newline at end of file diff --git a/docs/codox/cc.journeyman.the-great-game.buildings.rectangular.html b/docs/codox/cc.journeyman.the-great-game.buildings.rectangular.html new file mode 100644 index 0000000..19bbb30 --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.buildings.rectangular.html @@ -0,0 +1,32 @@ + +cc.journeyman.the-great-game.buildings.rectangular documentation

      cc.journeyman.the-great-game.buildings.rectangular

      Build buildings with a generally rectangular floow plan.

      +

      Motivations

      +

      Right, the idea behind this namespace is many fold.

      +
        +
      1. To establish the broad principle of genetic buildings, by creating a function which reproducibly creates reproducible buildings at specified locations, such that different buildings are credibly varied but a building at a specified location is always (modulo economic change) the same.
      2. +
      3. Create good rectangular buildings, and investigate whether a single function can be used to create buildings of more than one family (e.g. can it produce flat roofed, north African style, mud brick houses as well as pitch roofed, half timbered northern European houses?)
      4. +
      5. Establish whether, in my current state of fairly severe mental illness, I can actually produce any usable code at all.
      6. +
      +

      Key factors in the creation of a building

      +

      Holding

      +

      Every building is on a holding, and, indeed, what I mean by ‘building’ here may well turn out to be ’the collection of all the permanent structures on a holding. A holding is a polygonal area of the map which does not intersect with any other holding, but for the time being we’ll make the simplifying assumption that every holding is a rectangular strip, and that ‘urban’ holdings are of a reasonably standard width (see Viking-period York) and length. Rural holdings (farms, ?wood lots) may be much larger.

      +

      Terrain

      +

      A building is made of the stuff of the place. In a forest, buildings will tend to be wooden; in a terrain with rocky outcrops – normally found on steep slopes – stone. On the flat lands where there’s river mud, of brick, cob, or wattle and daub. So to build a building we need to know the terrain. Terrain can be inferred from location but in practice this will be computationally expensive, so we’ll pass terrain in as an argument to the build function.

      +

      For the time being we’ll pass it in simply as a keyword from a defined set of keywords; later it may be a more sophisticated data structure.

      +

      Culture

      +

      People of different cultures build distinctively different buildings, even when using the same materials. So, in our world, a Japanese wooden house looks quite different from an Anglo Saxon stave house which looks quite different from a Canadian log cabin, even though the materials are much the same and the tools available to build with are not much different.

      +

      Culture can affect not just the overall shape of a building but also its finish and surface detail. For example, in many places in England, stone buildings are typically left bare; in rural Scotland, typically painted white or in pastel shades; in Ireland, often quite vivid colours.

      +

      People may also show religious or cultural symbols on their buildings.

      +

      For all these reasons, we need to know the culture of the occupant when creating a building. Again, this will initially be passed in as a keyword.

      +

      Craft

      +

      People in the game world have a craft, and some crafts will require different features in the building. In the broadly late-bronze-age-to medieval period within which the game is set, residence and workplace are for most people pretty much the same.

      +

      So a baker needs an oven, a smith a forge, and so on. All crafts who do some degree retail trade will want a shop front as part of the ground floor of their dwelling. Merchants and bankers will probably have houses that are a bit more showy than others.

      +

      Whether the ‘genetic buildings’ idea will ever really produce suitable buildings for aristons I don’t know; it seems more likely that significant strongholds (of which there will be relatively few) should all be hand modelled rather than procedurally generated.

      *building-families*

      dynamic

      Families of buildings.

      +

      Each family has

      +
        +
      • terrain types to which it is appropriate;
      • +
      • crafts to which it is appropriate;
      • +
      • cultures to which it is appropriate.
      • +
      +

      Each generated building will be of one family, and will comprise modules taken only from that family.

      *crafts*

      dynamic

      Crafts which affect building types in the game. See Populating a game world. TODO: placeholder

      *cultures*

      dynamic

      Cultures which affect building families. TODO: placeholder

      *terrain-types*

      dynamic

      Types of terrain which affect building families. TODO: This is a placeholder; a more sophisticated model will be needed.

      build!

      (build! holding terrain culture craft size)

      Builds a building, and returns a data structure which represents it. In building the building, it adds a model of the building to the representation of the world, so it does have a side effect.

      building-family

      (building-family terrain culture craft gene)

      A building family is essentially a collection of models of building modules which can be assembled to create buildings of a particular structural and architectural style.

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

      cc.journeyman.the-great-game.gossip.gossip

      Interchange of news events between gossip agents.

      +

      Note that habitual travellers are all gossip agents; specifically, at this stage, that means merchants. When merchants are moved we also need to update the location of the gossip with the same key.

      +

      Innkeepers are also gossip agents but do not typically move.

      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 gossip)

      Gather news for the specified gossip in this world.

      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/cc.journeyman.the-great-game.gossip.news-items.html b/docs/codox/cc.journeyman.the-great-game.gossip.news-items.html new file mode 100644 index 0000000..9d3f18a --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.gossip.news-items.html @@ -0,0 +1,32 @@ + +cc.journeyman.the-great-game.gossip.news-items documentation

      cc.journeyman.the-great-game.gossip.news-items

      Categories of news events interesting to gossip agents.

      +

      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 advance further in the course of writing and debugging this namespace.

      +

      A news item is a map with the keys:

      +
        +
      • date - the date on which the reported event happened;
      • +
      • nth-hand - the number of agents the news item has passed through;
      • +
      • verb - what it is that happened (key into news-topics);
      • +
      +

      plus other keys taken from the keys value associated with the verb in news-topics.

      +

      Notes:

      +

      TODO
      This namespace at present considers the :knowledge of a gossip to be a flat list of propositions, each of which must be checked every time any new proposition is offered. This is woefully inefficient.

      compatible-item?

      (compatible-item? new-item known-item)

      True if new-item is identical with, or less specific than, known-item.

      +

      If we already know ‘Bad Joe killed Sweet Daisy’, there’s no point in learning that ‘someone killed Sweet Daisy’, but there is point in learning ‘someone killed Sweet Daisy with poison’.

      compatible-value?

      (compatible-value? new-value known-value)

      True if known-value is the same as new-value, or, for each key present in new-value, has the same value for that key.

      +

      The rationale here is that if new-value contains new or different information, it’s worth learning; otherwise, not.

      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.

      inc-or-one

      (inc-or-one val)

      If this val is a number, return that number incremented by one; otherwise, return 1. TODO: should probably be in utils.

      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

      known-item?

      (known-item? gossip item)

      True if this news item is already known to this gossip.

      +

      This means that the gossip already knows an item which identifiably has the same or more specific values for all the keys of this item except :nth-hand, :confidence and :learned-from.

      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:

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

      +

      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.

      +

      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/cc.journeyman.the-great-game.holdings.holding.html b/docs/codox/cc.journeyman.the-great-game.holdings.holding.html new file mode 100644 index 0000000..275d0cc --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.holdings.holding.html @@ -0,0 +1,3 @@ + +cc.journeyman.the-great-game.holdings.holding documentation

      cc.journeyman.the-great-game.holdings.holding

      TODO: write docs

      ProtoHolding

      protocol

      members

      building-origin

      (building-origin holding)

      Returns an oriented location - normally the right hand end of the frontage, for an urban holding - from which buildings on the holding should be built.

      frontage

      (frontage holding)

      Returns a sequence of two locations representing the edge of the polygon which defines this holding which is considered to be the front.

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

      cc.journeyman.the-great-game.location.location

      TODO: write docs

      ProtoLocation

      protocol

      members

      altitude

      (altitude location)

      Return the absolute altitude of this location, which may be different from the terrain height at this location, if, for example, the location is underground or on an upper floor.

      easting

      (easting location)

      Return the easting of this location

      northing

      (northing location)

      Return the northing of this location

      settlement

      (settlement location)

      Return the settlement record of the settlement in this world within whose parish polygon this location exists, or if none whose centre (inn location) is closest to this location

      terrain-altitude

      (terrain-altitude location)

      Return the ‘ground level’ (altitude of the terrain) at this location given this world. TODO: possibly terrain-altitude should be a method of the world.

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

      cc.journeyman.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/cc.journeyman.the-great-game.merchants.merchant-utils.html b/docs/codox/cc.journeyman.the-great-game.merchants.merchant-utils.html new file mode 100644 index 0000000..fb7adc1 --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.merchants.merchant-utils.html @@ -0,0 +1,3 @@ + +cc.journeyman.the-great-game.merchants.merchant-utils documentation

      cc.journeyman.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/cc.journeyman.the-great-game.merchants.merchants.html b/docs/codox/cc.journeyman.the-great-game.merchants.merchants.html new file mode 100644 index 0000000..95367d6 --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.merchants.merchants.html @@ -0,0 +1,3 @@ + +cc.journeyman.the-great-game.merchants.merchants documentation

      cc.journeyman.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/cc.journeyman.the-great-game.merchants.planning.html b/docs/codox/cc.journeyman.the-great-game.merchants.planning.html new file mode 100644 index 0000000..e10daee --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.merchants.planning.html @@ -0,0 +1,26 @@ + +cc.journeyman.the-great-game.merchants.planning documentation

      cc.journeyman.the-great-game.merchants.planning

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

      augment-plan

      (augment-plan merchant world plan)

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

      +

      Returns the augmented plan.

      generate-trade-plans

      (generate-trade-plans merchant world commodity)

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

      +

      Returned plans are maps with keys:

      +
        +
      • :merchant - the id of the merchant for whom the plan was created;
      • +
      • :origin - the city from which the trade starts;
      • +
      • :destination - the city to which the trade is planned;
      • +
      • :commodity - the commodity to be carried;
      • +
      • :buy-price - the price at which that commodity can be bought;
      • +
      • :expected-price - the price at which the merchant anticipates that commodity can be sold;
      • +
      • :distance - the number of stages in the planned journey
      • +
      • :dist-to-home - the distance from destination to the merchant’s home city.
      • +

      nearest-with-targets

      (nearest-with-targets plans targets)

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

      plan-trade

      (plan-trade merchant world commodity)

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

      +

      The returned plan is a map with keys:

      +
        +
      • :merchant - the id of the merchant for whom the plan was created;
      • +
      • :origin - the city from which the trade starts;
      • +
      • :destination - the city to which the trade is planned;
      • +
      • :commodity - the commodity to be carried;
      • +
      • :buy-price - the price at which that commodity can be bought;
      • +
      • :expected-price - the price at which the merchant anticipates that commodity can be sold;
      • +
      • :distance - the number of stages in the planned journey
      • +
      • :dist-to-home - the distance from destination to the merchant’s home city.
      • +

      select-cargo

      (select-cargo merchant world)

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

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

      cc.journeyman.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/cc.journeyman.the-great-game.objects.container.html b/docs/codox/cc.journeyman.the-great-game.objects.container.html new file mode 100644 index 0000000..a062605 --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.objects.container.html @@ -0,0 +1,3 @@ + +cc.journeyman.the-great-game.objects.container documentation

      cc.journeyman.the-great-game.objects.container

      TODO: write docs

      ProtoContainer

      protocol

      members

      contents

      (contents container)

      Return a sequence of the contents of this container, or nil if empty.

      is-empty?

      (is-empty? container)

      Return true if this container is empty, else false.

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

      cc.journeyman.the-great-game.objects.game-object

      Anything at all in the game world

      ProtoObject

      protocol

      An object in the world

      members

      id

      (id object)

      Returns the unique id of this object.

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

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

      cc.journeyman.the-great-game.playroom

      TODO: write docs

      app

      TODO: write docs

      init

      (init)

      TODO: write docs

      simple-update

      (simple-update tpf)

      TODO: write docs

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

      cc.journeyman.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/cc.journeyman.the-great-game.utils.html b/docs/codox/cc.journeyman.the-great-game.utils.html new file mode 100644 index 0000000..d44da8a --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.utils.html @@ -0,0 +1,3 @@ + +cc.journeyman.the-great-game.utils documentation

      cc.journeyman.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].

      value-or-default

      (value-or-default m k dflt)

      Return the value of this key k in this map m, or this dflt value if there is none.

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

      cc.journeyman.the-great-game.world.heightmap

      Functions dealing with the tessellated multi-layer heightmap.

      *base-map*

      dynamic

      TODO: write docs

      *noise-map*

      dynamic

      TODO: write docs

      excerpt-grid

      (excerpt-grid grid x-offset y-offset width height)

      Return that section of this grid where the :x co-ordinate of each cell is greater than or equal to this x-offset, the :y co-ordinate is greater than or equal to this y-offset, whose width is not greater than this width, and whose height is not greater than this height.

      get-surface

      (get-surface cell-size x-offset y-offset width height)(get-surface base-map noise-map cell-size x-offset y-offset width height)

      Return, as a vector of vectors of cells represented as Clojure maps, a segment of surface from this base-map as modified by this noise-map at this cell-size starting at this x-offset and y-offset and having this width and height.

      +

      If base-map and noise-map are not supplied, the bindings of *base-map* and *noise-map* will be used, respectively.

      +

      base-map and noise-map may be passed either as strings, assumed to be file paths of PNG files, or as MicroWorld style world arrays. It is assumed that one pixel in base-map represents one square kilometre in the game world. It is assumed that cell-size, x-offset, y-offset, width and height are integer numbers of metres.

      interpolate-altitude

      (interpolate-altitude cell grid src-width x-offset y-offset)

      Return the altitude of the point at x-offset, y-offset within this cell having this src-width, taken from this grid.

      interpolate-cell

      (interpolate-cell cell grid src-width target-width)

      Construct a grid (array of arrays) of cells each of width target-width from this cell, of width src-width, taken from this grid

      interpolate-grid

      (interpolate-grid grid src-width target-width)

      Return a grid interpolated from this grid of rows, cols given scaling from this src-width to this target-width

      scale-grid

      (scale-grid grid n)

      multiply all :x and :y values in this grid by this n.

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

      cc.journeyman.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/cc.journeyman.the-great-game.world.mw.html b/docs/codox/cc.journeyman.the-great-game.world.mw.html new file mode 100644 index 0000000..ffa00f5 --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.world.mw.html @@ -0,0 +1,3 @@ + +cc.journeyman.the-great-game.world.mw documentation

      cc.journeyman.the-great-game.world.mw

      Functions dealing with building a great game world from a MicroWorld world.

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

      cc.journeyman.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/cc.journeyman.the-great-game.world.run.html b/docs/codox/cc.journeyman.the-great-game.world.run.html new file mode 100644 index 0000000..47a7e63 --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.world.run.html @@ -0,0 +1,3 @@ + +cc.journeyman.the-great-game.world.run documentation

      cc.journeyman.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/cc.journeyman.the-great-game.world.world.html b/docs/codox/cc.journeyman.the-great-game.world.world.html new file mode 100644 index 0000000..f5064bf --- /dev/null +++ b/docs/codox/cc.journeyman.the-great-game.world.world.html @@ -0,0 +1,3 @@ + +cc.journeyman.the-great-game.world.world documentation

      cc.journeyman.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/codox/economy.html b/docs/codox/economy.html index 9bd68f7..b60378d 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 2d0f0d2..c5e9a95 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -The-great-game 0.1.2-SNAPSHOT

      The-great-game 0.1.2-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:

      [journeyman-cc/the-great-game "0.1.2-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 +The-great-game 0.1.2-SNAPSHOT

      The-great-game 0.1.2-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:

      [journeyman-cc/the-great-game "0.1.2-SNAPSHOT"]

      Topics

      Namespaces

      cc.journeyman.the-great-game.agent.agent

      Anything in the game world with agency; primarily but not exclusively characters.

      Public variables and functions:

      cc.journeyman.the-great-game.buildings.module

      A module of a building; essentially something like a portacabin, which can be assembled together with other modules to make a complete building.

      Public variables and functions:

        cc.journeyman.the-great-game.buildings.rectangular

        Build buildings with a generally rectangular floow plan.

        cc.journeyman.the-great-game.gossip.gossip

        Interchange of news events between gossip agents.

        Public variables and functions:

        cc.journeyman.the-great-game.holdings.holding

        TODO: write docs

        Public variables and functions:

        cc.journeyman.the-great-game.location.location

        TODO: write docs

        Public variables and functions:

        cc.journeyman.the-great-game.merchants.markets

        Adjusting quantities and prices in markets.

        Public variables and functions:

        cc.journeyman.the-great-game.merchants.merchant-utils

        Useful functions for doing low-level things with merchants.

        cc.journeyman.the-great-game.merchants.merchants

        Trade planning for merchants, primarily.

        Public variables and functions:

        cc.journeyman.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.

        cc.journeyman.the-great-game.merchants.strategies.simple

        Default trading strategy for merchants.

        Public variables and functions:

        cc.journeyman.the-great-game.objects.container

        TODO: write docs

        Public variables and functions:

        cc.journeyman.the-great-game.objects.game-object

        Anything at all in the game world

        Public variables and functions:

        cc.journeyman.the-great-game.playroom

        TODO: write docs

        Public variables and functions:

        cc.journeyman.the-great-game.world.heightmap

        Functions dealing with the tessellated multi-layer heightmap.

        cc.journeyman.the-great-game.world.location

        Functions dealing with location in the world.

        Public variables and functions:

        cc.journeyman.the-great-game.world.mw

        Functions dealing with building a great game world from a MicroWorld world.

        Public variables and functions:

          cc.journeyman.the-great-game.world.routes

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

          Public variables and functions:

          cc.journeyman.the-great-game.world.run

          Run the whole simulation

          Public variables and functions:

          cc.journeyman.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 708ac64..2b59e00 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 0e4b71a..a747227 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 d36f96d..fc52305 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 4dbc411..67198d7 100644 --- a/docs/codox/on-dying.html +++ b/docs/codox/on-dying.html @@ -1,7 +1,6 @@ -On Dying

          On Dying

          -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/sandbox.html b/docs/codox/sandbox.html index 6b51109..0583831 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 901ec84..d6c4a8c 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/src/the_great_game/architecture.md b/src/cc/journeyman/architecture.md similarity index 100% rename from src/the_great_game/architecture.md rename to src/cc/journeyman/architecture.md diff --git a/src/cc/journeyman/the_great_game/buildings/module.clj b/src/cc/journeyman/the_great_game/buildings/module.clj new file mode 100644 index 0000000..6791e9f --- /dev/null +++ b/src/cc/journeyman/the_great_game/buildings/module.clj @@ -0,0 +1,81 @@ +(ns cc.journeyman.the-great-game.buildings.module + + "A module of a building; essentially something like a portacabin, which can be + assembled together with other modules to make a complete building. + + Modules need to include + + 1. Ground floor modules, having external doors; + 2. Craft modules -- workshops -- which will normally be ground floor (except + weavers) and may have the constraint that no upper floor module can cover them; + 3. Upper floor modules, having NO external doors (but linking internal doors); + 4. Roof modules + + **Role** must be one of: + + 1. `:primary` a ground floor main entrance module + 2. `:secondary` a module which can be upper or ground floor + 3. `:upper` a module which can only be on an upper floor, for example one + with a projecting gallery, balcony or overhang. + + Other values for `role` will emerge. + + **Exits** must be a sequence of keywords taken from the following list: + + 1. `:left` an exit in the centre of the left wall + 2. `:left-front` an exit in the centre of the left half of the front wall + 3. `:front` an exit in the centre of the front wall + 4. `:right-front` an exit in the centre of the right half of the front wall + 5. `:right` an exit in the centre of the right wall + 6. `:right-back` an exit in the centre of the right half of the back wall + 7. `:left-back` an exit in the centre of the back wall + + A module placed on an upper floor must have no exit which opens beyond the + footprint of the floor below - no doors into mid air! However, it is allowable + (and indeed is necessary) to allow doors into roof spaces if the adjacent + module on the same floor does not yet exist, since otherwise it would be + impossible to access a new room which might later be built there. + + **Load** must be a small integer indicating both the weight of the module and + the total amount of weight it can support. So for example a stone-built module + might have a `load` value of 4, a brick built one of 3, and a half-timbered one + of 2, and a tent of 0. This means a stone ground floor module could support one + further floor of stone or brick, or two further floors of half timbered + construction; while a brick built ground floor could support a single brick or + half-timbered upper floor but not a stone one, and a half-timbered ground floor + could only support a half timbered upper floor. + + There also needs to be an undercroft or platform module, such that the area of + the top of the platform is identical with the footprint of the building, and + the altitude of the top of the platform is equal to the altitude of the + terrain at the heighest corner of the building; so that the actual + building doesn't float in the air, and also so that none of the doors or windows + are partly underground. + + Each module needs to wrap an actual 3d model created in Blender or whatever, + and have a list of optional **textures** with which that model can be rendered. + So an upper floor bedroom module might have the following renders: + + 1. Bare masonry - constrained to upland or plateau terrain, and to coastal culture + 2. Painted masonry - constrained to upland or plateau terrain, and to coastal culture + 3. Half-timbered - not available on plateau terrain + 4. Weatherboarded - constrained to forest terrain + 5. Brick - constrained to arable or arid terrain + + of course these are only examples, and also, it's entirely possible to have + for example multiple different weatherboard renders for the same module. + There needs to be a way of rendering what can be built above what: for + example, you can't have a masonry clad module over a half timbered one, + but you can have a half-timbered one over a masonry one.") + +(defrecord BuildingModule + [model + ^Double length + ^Double width + ^Double height + ^Integer load + ^clojure.lang.Keyword role + ^clojure.lang.IPersistentCollection textures + ^clojure.lang.IPersistentCollection exits + ] + ) \ No newline at end of file diff --git a/src/cc/journeyman/the_great_game/buildings/rectangular.clj b/src/cc/journeyman/the_great_game/buildings/rectangular.clj index 17a2c13..1000a6d 100644 --- a/src/cc/journeyman/the_great_game/buildings/rectangular.clj +++ b/src/cc/journeyman/the_great_game/buildings/rectangular.clj @@ -1,84 +1,86 @@ (ns cc.journeyman.the-great-game.buildings.rectangular + "Build buildings with a generally rectangular floow plan. + + ## Motivations + + Right, the idea behind this namespace is many fold. + + 1. To establish the broad principle of genetic buildings, by creating a + function which reproducibly creates reproducible buildings at specified + locations, such that different buildings are credibly varied but a + building at a specified location is always (modulo economic change) the + same. + 2. Create good rectangular buildings, and investigate whether a single + function can be used to create buildings of more than one family (e.g. + can it produce flat roofed, north African style, mud brick houses as + well as pitch roofed, half timbered northern European houses?) + 3. Establish whether, in my current state of fairly severe mental illness, + I can actually produce any usable code at all. + + ## Key factors in the creation of a building + + ### Holding + + Every building is on a holding, and, indeed, what I mean by 'building' here + may well turn out to be 'the collection of all the permanent structures on + a holding. A holding is a polygonal area of the map which does not + intersect with any other holding, but for the time being we'll make the + simplifying assumption that every holding is a rectangular strip, and that + 'urban' holdings are of a reasonably standard width (see Viking-period + York) and length. Rural holdings (farms, ?wood lots) may be much larger. + + ### Terrain + + A building is made of the stuff of the place. In a forest, buildings will + tend to be wooden; in a terrain with rocky outcrops -- normally found on + steep slopes -- stone. On the flat lands where there's river mud, of brick, + cob, or wattle and daub. So to build a building we need to know the + terrain. Terrain can be inferred from location but in practice this will + be computationally expensive, so we'll pass terrain in as an argument to + the build function. + + For the time being we'll pass it in simply as a keyword from a defined set + of keywords; later it may be a more sophisticated data structure. + + ### Culture + + People of different cultures build distinctively different buildings, even + when using the same materials. So, in our world, a Japanese wooden house + looks quite different from an Anglo Saxon stave house which looks quite + different from a Canadian log cabin, even though the materials are much the + same and the tools available to build with are not much different. + + Culture can affect not just the overall shape of a building but also its + finish and surface detail. For example, in many places in England, stone + buildings are typically left bare; in rural Scotland, typically painted + white or in pastel shades; in Ireland, often quite vivid colours. + + People may also show religious or cultural symbols on their buildings. + + For all these reasons, we need to know the culture of the occupant when + creating a building. Again, this will initially be passed in as a keyword. + + ### Craft + + People in the game world have a craft, and some crafts will require + different features in the building. In the broadly late-bronze-age-to + medieval period within which the game is set, residence and workplace + are for most people pretty much the same. + + So a baker needs an oven, a smith a forge, and so on. All crafts who do + some degree retail trade will want a shop front as part of the ground + floor of their dwelling. Merchants and bankers will probably have houses + that are a bit more showy than others. + + Whether the 'genetic buildings' idea will ever really produce suitable + buildings for aristons I don't know; it seems more likely that significant + strongholds (of which there will be relatively few) should all be hand + modelled rather than procedurally generated." (:require [cc.journeyman.the-great-game.holdings.holding :refer [ProtoHolding]] - [cc.journeyman.the-great-game.location.location :refer [ProtoLocation]] - ) - (:import [org.apache.commons.math3.random MersenneTwister] - )) - -;;; Right, the idea behind this namespace is many fold. -;;; -;;; 1. To establish the broad principle of genetic buildings, by creating a -;;; function which reproducibly creates reproducible buildings at specified -;;; locations, such that different buildings are credibly varied but a -;;; building at a specified location is always (modulo economic change) the -;;; same. -;;; 2. Create good rectangular buildings, and investigate whether a single -;;; function can be used to create buildings of more than one family (e.g. -;;; can it produce flat roofed, north African style, mud brick houses as -;;; well as pitch roofed, half timbered northern European houses?) -;;; 3. Establish whether, in my current state of fairly severe mental illness, -;;; I can actually produce any usable code at all. -;;; -;;; ## Key factors in the creation of a building -;;; -;;; ### Holding -;;; -;;; Every building is on a holding, and, indeed, what I mean by 'building' here -;;; may well turn out to be 'the collection of all the permanent structures on -;;; a holding. A holding is a polygonal area of the map which does not -;;; intersect with any other holding, but for the time being we'll make the -;;; simplifying assumption that every holding is a rectangular strip, and that -;;; 'urban' holdings are of a reasonably standard width (see Viking-period -;;; York) and length. Rural holdings (farms, ?wood lots) may be much larger. -;;; -;;; ### Terrain -;;; -;;; A building is made of the stuff of the place. In a forest, buildings will -;;; tend to be wooden; in a terrain with rocky outcrops -- normally found on -;;; steep slopes -- stone. On the flat lands where there's river mud, of brick, -;;; cob, or wattle and daub. So to build a building we need to know the -;;; terrain. Terrain can be inferred from location but in practice this will -;;; be computationally expensive, so we'll pass terrain in as an argument to -;;; the build function. -;;; -;;; For the time being we'll pass it in simply as a keyword from a defined set -;;; of keywords; later it may be a more sophisticated data structure. -;;; -;;; ### Culture -;;; -;;; People of different cultures build distinctively different buildings, even -;;; when using the same materials. So, in our world, a Japanese wooden house -;;; looks quite different from an Anglo Saxon stave house which looks quite -;;; different from a Canadian log cabin, even though the materials are much the -;;; same and the tools available to build with are not much different. -;;; -;;; Culture can affect not just the overall shape of a building but also its -;;; finish and surface detail. For example, in many places in England, stone -;;; buildings are typically left bare; in rural Scotland, typically painted -;;; white or in pastel shades; in Ireland, often quite vivid colours. -;;; -;;; People may also show religious or cultural symbols on their buildings. -;;; -;;; For all these reasons, we need to know the culture of the occupant when -;;; creating a building. Again, this will initially be passed in as a keyword. -;;; -;;; ### Craft -;;; -;;; People in the game world have a craft, and some crafts will require -;;; different features in the building. In the broadly late-bronze-age-to -;;; medieval period within which the game is set, residence and workplace -;;; are for most people pretty much the same. -;;; -;;; So a baker needs an oven, a smith a forge, and so on. All crafts who do -;;; some degree retail trade will want a shop front as part of the ground -;;; floor of their dwelling. Merchants and bankers will probably have houses -;;; that are a bit more showy than others. -;;; -;;; Whether the 'genetic buildings' idea will ever really produce suitable -;;; buildings for aristons I don't know; it seems more likely that significant -;;; strongholds (of which there will be relatively few) should all be hand -;;; modelled rather than procedurally generated. + [cc.journeyman.the-great-game.location.location :refer [ProtoLocation]]) + (:import [org.apache.commons.math3.random MersenneTwister])) + (def ^:dynamic *terrain-types* "Types of terrain which affect building families. TODO: This is a placeholder; a more sophisticated model will be needed." @@ -94,6 +96,16 @@ #{:baker :banker :butcher :chancellor :innkeeper :lawyer :magus :merchant :miller :priest :scholar :smith :weaver}) (def ^:dynamic *building-families* + "Families of buildings. + + Each family has + + * terrain types to which it is appropriate; + * crafts to which it is appropriate; + * cultures to which it is appropriate. + + Each generated building will be of one family, and will comprise modules + taken only from that family." {:pitched-rectangular {:terrains #{:arable :forest :upland} :crafts *crafts* :cultures #{:coastal :western-clans} @@ -103,36 +115,6 @@ :cultures #{:coastal} :modules []}}) -;; TODO: So, modules need to contain -;; -;; 1. Ground floor modules, having external doors; -;; 2. Craft modules -- workshops -- which will normally be ground floor (except -;; weavers) and may have the constraint that no upper floor module can cover them; -;; 3. Upper floor modules, having NO external doors (but linking internal doors); -;; 4. Roof modules -;; -;; There also needs to be an undercroft or platform module, such that the area of -;; the top of the platform is identical with the footprint of the building, and -;; the altitude of the top of the platform is equal to the altitude of the -;; terrain at the heighest corner of the building; so that the actual -;; building doesn't float in the air, and also so that none of the doors or windows -;; are partly underground. -;; -;; Each module needs to wrap an actual 3d model created in Blender or whatever, -;; and have a list of optional textures with which that model can be rendered. -;; So an upper floor bedroom module might have the following renders: -;; -;; 1. Bare masonry - constrained to upland or plateau terrain, and to coastal culture -;; 2. Painted masonry - constrained to upland or plateau terrain, and to coastal culture -;; 3. Half-timbered - not available on plateau terrain -;; 4. Weatherboarded - constrained to forest terrain -;; 5. Brick - constrained to arable or arid terrain -;; -;; of course these are only examples, and also, it's entirely possible to have -;; for example multiple different weatherboard renders for the same module. -;; There needs to be a way of rendering what can be built above what: for -;; example, you can't have a masonry clad module over a half timbered one, -;; but you can have a half-timbered one over a masonry one (defn building-family "A building family is essentially a collection of models of building modules diff --git a/src/cc/journeyman/the_great_game/gossip/gossip.clj b/src/cc/journeyman/the_great_game/gossip/gossip.clj index ee19889..6686c36 100644 --- a/src/cc/journeyman/the_great_game/gossip/gossip.clj +++ b/src/cc/journeyman/the_great_game/gossip/gossip.clj @@ -1,11 +1,15 @@ (ns cc.journeyman.the-great-game.gossip.gossip - "Interchange of news events between gossip agents" + "Interchange of news events between gossip agents. + + Note that habitual travellers are all gossip agents; specifically, at this + stage, that means merchants. When merchants are moved we also need to + update the location of the gossip with the same key. + + Innkeepers are also gossip agents but do not typically move." (:require [cc.journeyman.the-great-game.utils :refer [deep-merge]] - [cc.journeyman.the-great-game.gossip.news-items :refer [learn-news-item]])) + [cc.journeyman.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 -;; update the location of the gossip with the same key. (defn dialogue "Dialogue between an `enquirer` and an `agent` in this `world`; returns a @@ -16,30 +20,26 @@ enquirer) (defn gather-news - ([world] - (reduce - deep-merge - world - (map - #(gather-news world %) - (keys (:gossips world))))) - ([world gossip] - (let [g (cond (keyword? gossip) - (-> world :gossips gossip) - (map? gossip) - gossip)] - {:gossips - {(:id g) - (reduce + "Gather news for the specified `gossip` in this `world`." + [world gossip] + (let [g (cond (keyword? gossip) + (-> world :gossips gossip) + (map? gossip) + gossip)] + (if g + {:gossips + {(:id g) + (reduce deep-merge {} (map - #(dialogue g % world) - (remove - #( = g %) - (filter - #(= (:location %) (:location g)) - (vals (:gossips world))))))}}))) + #(dialogue g % world) + (remove + #(= g %) + (filter + #(= (:location %) (:location g)) + (vals (:gossips world))))))}} + {}))) (defn move-gossip "Return a world like this `world` but with this `gossip` moved to this @@ -63,4 +63,11 @@ "Return a world like this `world`, with news items exchanged between gossip agents." [world] - (gather-news world)) + (reduce + deep-merge + world + (map + #(gather-news world %) + (keys (:gossips world))))) + + diff --git a/src/cc/journeyman/the_great_game/gossip/news_items.clj b/src/cc/journeyman/the_great_game/gossip/news_items.clj index 988a7da..18600de 100644 --- a/src/cc/journeyman/the_great_game/gossip/news_items.clj +++ b/src/cc/journeyman/the_great_game/gossip/news_items.clj @@ -1,20 +1,30 @@ (ns cc.journeyman.the-great-game.gossip.news-items - "Categories of news events interesting to gossip agents" + "Using news items (propositions) to transfer knowledge between gossip agents. + + The ideas here are based on the essay [The spread of knowledge in a large + game world](The-spread-of-knowledge-in-a-large-game-world.html), q.v.; + they've advanced a little beyond that and will doubtless + advance further in the course of writing and debugging this namespace. + + A news item is a map with the keys: + + * `date` - the date on which the reported event happened; + * `nth-hand` - the number of agents the news item has passed through; + * `verb` - what it is that happened (key into `news-topics`); + + plus other keys taken from the `keys` value associated with the verb in + `news-topics`. + + ## Notes: + + *TODO* + This namespace at present considers the `:knowledge` of a gossip to be a flat + list of propositions, each of which must be checked every time any new + proposition is offered. This is woefully inefficient. " (:require [cc.journeyman.the-great-game.world.location :refer [distance-between]] - [cc.journeyman.the-great-game.time :refer [game-time]])) + [cc.journeyman.the-great-game.time :refer [game-time]] + [taoensso.timbre :as l])) -;; 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 -;; advance further in the course of writing and debugging this namespace. - -;; A news item is a map with the keys: -;; -;; * `date` - the date on which the reported event happened; -;; * `nth-hand` - the number of agents the news item has passed through; -;; * `verb` - what it is that happened (key into `news-topics`); -;; -;; plus other keys taken from the `keys` value associated with the verb in -;; `news-topics` (def news-topics "Topics of interest to gossip agents. Topics are keyed in this map by @@ -30,9 +40,9 @@ action; * `price` is special to buy/sell, but of significant interest to merchants. - #### Notes: + ## Notes - ##### Characters: + ### 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 @@ -46,7 +56,7 @@ as the receiver stores only that segment which the receiver finds interesting. - ##### Locations: + ### 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 @@ -56,49 +66,48 @@ It is assumed that the `:home` of a character is a location in this sense. - ##### Inferences: + ### 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. " - { ;; A significant attack is interesting whether or not it leads to deaths - :attack {:verb :attack :keys [:actor :other :location]} + {;; 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 :die :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}]} + :kill {:verb :kill :keys [:actor :other :location] + :inferences [{:verb :die :actor :other :other :nil}]} ;; Marriages may be interesting - :marry {:verb :marry :keys [:actor :other :location] - :inferences [{:verb :marry :actor :other :other :actor}]} + :marry {:verb :marry :keys [:actor :other :location] + :inferences [{:verb :marry :actor :other :other :actor}]} ;; The end of ongoing open conflict between to characters may be interesting - :peace {:verb :peace :keys [:actor :other :location] - :inferences [{:verb :peace :actor :other :other :actor}]} + :peace {:verb :peace :keys [:actor :other :location] + :inferences [{:verb :peace :actor :other :other :actor}]} ;; Things related to the plot are interesting, but will require special ;; handling. Extra keys may be required by particular plot events. - :plot {:verb :plot :keys [:actor :other :object :location]} + :plot {:verb :plot :keys [:actor :other :object :location]} ;; Rapes are interesting. - :rape {:verb :rape :keys [:actor :other :location] + :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}]} + :inferences [{:verb :attack} + {:verb :sex} + {:verb :sex :actor :other :other :actor}]} ;; Merchants, especially, are interested in prices in other markets - :sell {:verb :sell :keys [:actor :other :object :location :price]} + :sell {:verb :sell :keys [:actor :other :object :location :price]} ;; Sex can juicy gossip, although not normally if the participants are in an ;; established sexual relationship. - :sex {:verb :sex :keys [:actor :other :location] - :inferences [{:verb :sex :actor :other :other :actor}]} - ;; Thefts are interesting - :steal {:verb :steal :keys [:actor :other :object :location]} + :sex {:verb :sex :keys [:actor :other :location] + :inferences [{:verb :sex :actor :other :other :actor}]} + ;; Thefts are interesting. + :steal {:verb :steal :keys [:actor :other :object :location]} ;; The succession of rulers is interesting; of respected craftsmen, ;; potentially also interesting. - :succession {:verb :succession :keys [:actor :other :location :rank]} - ;; The start of ongoing open conflict between to characters may be interesting - :war {:verb :war :keys [:actor :other :location] - :inferences [{:verb :war :actor :other :other :actor}]} - }) + :succession {:verb :succession :keys [:actor :other :location :rank]} + ;; The start of ongoing open conflict between two characters may be interesting. + :war {:verb :war :keys [:actor :other :location] + :inferences [{:verb :war :actor :other :other :actor}]}}) (defn interest-in-character @@ -108,9 +117,9 @@ documented above, they probably have to be maps, to allow for degradation." [gossip character] (count - (concat - (filter #(= (:actor % character)) (:knowledge gossip)) - (filter #(= (:other % character)) (:knowledge gossip))))) + (concat + (filter #(= (:actor % character)) (:knowledge gossip)) + (filter #(= (:other % character)) (:knowledge gossip))))) (defn interesting-character? "Boolean representation of whether this `character` is interesting to this @@ -133,22 +142,22 @@ 0) (coll? location) (reduce - + - (map - #(interest-in-location gossip %) - location)) + + + (map + #(interest-in-location gossip %) + location)) :else (count - (filter - #(some (fn [x] (= x location)) (:location %)) - (cons {:location (:home gossip)} (:knowledge gossip)))))) + (filter + #(some (fn [x] (= x location)) (:location %)) + (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)) 0)) + [gossip location] + (> (interest-in-location gossip location) 0)) (defn interesting-object? [gossip object] @@ -160,40 +169,98 @@ ;; TODO: Not yet (really) implemented true) +(defn compatible-value? + "True if `known-value` is the same as `new-value`, or, for each key present + in `new-value`, has the same value for that key. + + The rationale here is that if `new-value` contains new or different + information, it's worth learning; otherwise, not." + [new-value known-value] + (or + (= new-value known-value) + ;; TODO: some handwaving here about being a slightly better descriptor -- + ;; having more keys than might + (when (and (map? new-value) (map? known-value)) + (every? true? (map #(= (new-value %) (known-value %)) + (keys new-value)))))) + +(defn compatible-item? + "True if `new-item` is identical with, or less specific than, `known-item`. + + If we already know 'Bad Joe killed Sweet Daisy', there's no point in + learning that 'someone killed Sweet Daisy', but there is point in learning + 'someone killed Sweet Daisy _with poison_'." + [new-item known-item] + (if + (reduce + #(and %1 %2) + (map #(if + (known-item %) ;; if known-item has this key + (compatible-value? (new-item %) (known-item %)) + true) + (remove #{:nth-hand :confidence :learned-from} (keys new-item)))) + true + false)) + +(defn known-item? + "True if this news `item` is already known to this `gossip`. + + This means that the `gossip` already knows an item which identifiably has + the same _or more specific_ values for all the keys of this `item` except + `:nth-hand`, `:confidence` and `:learned-from`." + [gossip item] + (if + (reduce + #(or %1 %2) + false + (filter true? (map #(compatible-item? item %) (:knowledge gossip)))) + true + false)) + (defn interesting-item? "True if anything about this news `item` is interesting to this `gossip`." [gossip item] - (or - (interesting-character? gossip (:actor item)) - (interesting-character? gossip (:other item)) - (interesting-location? gossip (:location item)) - (interesting-object? gossip (:object item)) - (interesting-topic? gossip (:verb item)))) + (and (not (known-item? gossip item)) + (or + (interesting-character? gossip (:actor item)) + (interesting-character? gossip (:other item)) + (interesting-location? gossip (:location item)) + (interesting-object? gossip (:object item)) + (interesting-topic? gossip (:verb item))))) + +(defn inc-or-one + "If this `val` is a number, return that number incremented by one; otherwise, + return 1. TODO: should probably be in `utils`." + [val] + (if + (number? val) + (inc val) + 1)) (defn infer - "Infer a new knowledge item from this `item`, following this `rule`" + "Infer a new knowledge item from this `item`, following this `rule`." [item rule] +;; (l/info "Applying rule '" rule "' to item '" item "'") (reduce merge item (cons - {:verb (:verb rule)} - (map (fn [k] {k (apply (k rule) (list item))}) - (remove - #(= % :verb) - (keys rule)))))) + {:verb (:verb rule) + :nth-hand (inc-or-one (:nth-hand item))} + (map (fn [k] {k (item (rule k))}) + (remove + #{:verb :nth-hand} + (keys rule)))))) (declare learn-news-item) (defn make-all-inferences - "Return a list of knowledge entries that can be inferred from this news + "Return a set of knowledge entries that can be inferred from this news `item`." [item] (set - (reduce - concat - (map - #(:knowledge (learn-news-item {} (infer item %) false)) - (:inferences (news-topics (:verb item))))))) + (map + #(infer item %) + (:inferences (news-topics (:verb item)))))) (defn degrade-character "Return a character specification like this `character`, but comprising @@ -207,50 +274,48 @@ 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))] + (let [l (when + (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 true)) ([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) - :time-stamp (if - (number? (:time-stamp item)) - (:time-stamp item) - (game-time)) - :location (degrade-location gossip (:location item)) - ;; TODO: ought to maybe-degrade characters we're not yet interested in - ) - ;; TODO: ought not to add knowledge items we already have, except - ;; to replace if new item is of increased specificity - (:knowledge gossip)))] + (interesting-item? gossip item) + (let [item' (assoc + item + :nth-hand (inc-or-one (:nth-hand item)) + :time-stamp (if + (number? (:time-stamp item)) + (:time-stamp item) + (game-time)) + :location (degrade-location gossip (:location item)) + :actor (degrade-character gossip (:actor item)) + :other (degrade-character gossip (:other item)) + ;; TODO: do something to degrade confidence in the item, + ;; probably as a function of the provider's confidence in + ;; the item and the gossip's trust in the provider + ) + g (assoc + gossip + :knowledge + (cons + item' + (:knowledge gossip)))] (if follow-inferences? (assoc - g - :knowledge - (concat (:knowledge g) (make-all-inferences item))) - g)) - gossip))) + g + :knowledge + (concat (:knowledge g) (make-all-inferences item))) + g))) + gossip)) diff --git a/src/cc/journeyman/the_great_game/holdings/holding.clj b/src/cc/journeyman/the_great_game/holdings/holding.clj index 2ea7f42..a84deff 100644 --- a/src/cc/journeyman/the_great_game/holdings/holding.clj +++ b/src/cc/journeyman/the_great_game/holdings/holding.clj @@ -29,14 +29,18 @@ ;; is needed to ensure this! ProtoContainer ProtoHolding - (frontage [holding] - ;; TODO: this is WRONG, but will work for now. The frontage should - ;; be the side of the perimeter nearest to the nearest existing - ;; route. + (frontage + [holding] + "TODO: this is WRONG, but will work for now. The frontage should + be the side of the perimeter nearest to the nearest existing + route." [(first (perimeter holding)) (nth (perimeter holding) 1)]) - (building-origin [holding] - ;; TODO: again this is wrong. The default building origin - ;; should be the right hand end of the frontage when viewed - ;; from outside the holding. - (first (frontage holding))) + (building-origin + [holding] + "TODO: again this is WRONG. The default building origin for rectangular + buildings should be the right hand end of the frontage when viewed + from outside the holding. But that's not general; celtic-style circular + buildings should normally be in the centre of their holdings. So probably + building-origin becomes a method of building-family rather than of holding." + (first (frontage holding))) ProtoObject) diff --git a/src/cc/journeyman/the_great_game/location/location.clj b/src/cc/journeyman/the_great_game/location/location.clj index 4b5895f..1fdc3b9 100644 --- a/src/cc/journeyman/the_great_game/location/location.clj +++ b/src/cc/journeyman/the_great_game/location/location.clj @@ -43,5 +43,3 @@ ;; (.settlement (OrientedLocation. 123.45 543.76 12.34 0.00 {})) - -;; (OrientedLocation. 123.45 543.76 12.34 0.00 {}) \ No newline at end of file diff --git a/src/cc/journeyman/the_great_game/merchants/merchants.clj b/src/cc/journeyman/the_great_game/merchants/merchants.clj index fe0e319..a4124b4 100644 --- a/src/cc/journeyman/the_great_game/merchants/merchants.clj +++ b/src/cc/journeyman/the_great_game/merchants/merchants.clj @@ -1,8 +1,8 @@ (ns cc.journeyman.the-great-game.merchants.merchants "Trade planning for merchants, primarily." - (:require [taoensso.timbre :as l :refer [info error spy]] - [the-great-game.utils :refer [deep-merge]] - [the-great-game.merchants.strategies.simple :refer [move-merchant]])) + (:require [cc.journeyman.the-great-game.utils :refer [deep-merge]] + [cc.journeyman.the-great-game.merchants.strategies.simple :refer [move-merchant]] + [taoensso.timbre :as l])) (defn run @@ -10,18 +10,18 @@ [world] (try (reduce - deep-merge - world - (map - #(try - (let [move-fn (or - (-> world :merchants % :move-fn) - move-merchant)] - (apply move-fn (list % world))) - (catch Exception any - (l/error any "Failure while moving merchant " %) - {})) - (keys (:merchants world)))) + deep-merge + world + (map + #(try + (let [move-fn (or + (-> world :merchants % :move-fn) + move-merchant)] + (apply move-fn (list % world))) + (catch Exception any + (l/error any "Failure while moving merchant " %) + {})) + (keys (:merchants world)))) (catch Exception any (l/error any "Failure while moving merchants") world))) diff --git a/src/cc/journeyman/the_great_game/merchants/strategies/simple.clj b/src/cc/journeyman/the_great_game/merchants/strategies/simple.clj index 7ccbdb2..b8062f7 100644 --- a/src/cc/journeyman/the_great_game/merchants/strategies/simple.clj +++ b/src/cc/journeyman/the_great_game/merchants/strategies/simple.clj @@ -30,7 +30,7 @@ plan (select-cargo merchant world)] (l/debug "plan-and-buy: merchant" id) (cond - (not (empty? plan)) + (seq? plan) (let [c (:commodity plan) p (* (:quantity plan) (:buy-price plan)) diff --git a/src/cc/journeyman/the_great_game/objects/game_object.clj b/src/cc/journeyman/the_great_game/objects/game_object.clj index 992b378..be497e3 100644 --- a/src/cc/journeyman/the_great_game/objects/game_object.clj +++ b/src/cc/journeyman/the_great_game/objects/game_object.clj @@ -12,8 +12,10 @@ which the object is keyed in the global object list.")) (defrecord GameObject - [id] + [id] ;; "An object in the world" ProtoObject (id [_] id) - (reify-object [object] "TODO: doesn't work yet")) + (reify-object [object] + "TODO: doesn't work yet" + object)) diff --git a/src/cc/journeyman/the_great_game/playroom.clj b/src/cc/journeyman/the_great_game/playroom.clj index a53cd1e..4ab671a 100644 --- a/src/cc/journeyman/the_great_game/playroom.clj +++ b/src/cc/journeyman/the_great_game/playroom.clj @@ -1,9 +1,13 @@ (ns cc.journeyman.the-great-game.playroom - (require [jme-clj.core :refer :all]) - (import [com.jme3.math ColorRGBA])) + (:require [jme-clj.core :refer [add add-to-root box defsimpleapp fly-cam geo + get* get-state load-texture rotate run set* + setc set-state start unshaded-mat]]) + (:import [com.jme3.math ColorRGBA])) ;; At present this file is just somewhere to play around with jme-clj examples +(declare app) + (defn init [] (let [cube (geo "jMonkey cube" (box 1 1 1)) mat (unshaded-mat)] diff --git a/src/cc/journeyman/the_great_game/world/heightmap.clj b/src/cc/journeyman/the_great_game/world/heightmap.clj index 07864ae..99369c8 100644 --- a/src/cc/journeyman/the_great_game/world/heightmap.clj +++ b/src/cc/journeyman/the_great_game/world/heightmap.clj @@ -3,7 +3,7 @@ (:require [clojure.math.numeric-tower :refer [expt sqrt]] [mw-engine.core :refer []] [mw-engine.heightmap :refer [apply-heightmap]] - [mw-engine.utils :refer [get-cell in-bounds? map-world scale-world]] + [mw-engine.utils :refer [get-cell in-bounds? map-world]] [cc.journeyman.the-great-game.utils :refer [value-or-default]])) ;; It's not at all clear to me yet what the workflow for getting a MicroWorld @@ -149,7 +149,7 @@ ([cell-size x-offset y-offset width height] (get-surface *base-map* *noise-map* cell-size x-offset y-offset width height)) ([base-map noise-map cell-size x-offset y-offset width height] - (let [b (if (seq? base-map) base-map (scale-world (apply-heightmap base-map) 1000)) + (let [b (if (seq? base-map) base-map (scale-grid (apply-heightmap base-map) 1000)) n (if (seq? noise-map) noise-map (apply-heightmap noise-map))] (if (and (in-bounds? b x-offset y-offset) (in-bounds? b (+ x-offset width) (+ y-offset height))) diff --git a/test/cc/journeyman/the_great_game/gossip/news_items_test.clj b/test/cc/journeyman/the_great_game/gossip/news_items_test.clj index 79137a6..50f062a 100644 --- a/test/cc/journeyman/the_great_game/gossip/news_items_test.clj +++ b/test/cc/journeyman/the_great_game/gossip/news_items_test.clj @@ -1,135 +1,163 @@ (ns cc.journeyman.the-great-game.gossip.news-items-test - (:require [clojure.test :refer :all] - [cc.journeyman.the-great-game.gossip.news-items :refer - [degrade-location infer interest-in-location interesting-location? + (:require [clojure.test :refer [deftest is testing]] + [cc.journeyman.the-great-game.gossip.news-items :refer + [compatible-item? degrade-location infer interest-in-location interesting-location? learn-news-item make-all-inferences]])) +(deftest compatible-item-test + (testing "Compatible item: items are identical" + (let [expected true + new-item {:verb :kills :location :tchahua :actor :fierce-fred :other :dainty-daisy} + known-item {:verb :kills :location :tchahua :actor :fierce-fred :other :dainty-daisy} + actual (compatible-item? new-item known-item)] + (is (= actual expected) "Items which are identical are compatible."))) + (testing "Compatible item: new item is less specific" + (let [expected true + new-item {:verb :kills :location :tchahua :other :dainty-daisy} + known-item {:verb :kills :location :tchahua :actor :fierce-fred :other :dainty-daisy} + actual (compatible-item? new-item known-item)] + (is (= actual expected) + "An item which is less specific is compatible with existing knowledge."))) + (testing "Compatible item: new item is more specific" + (let [expected true + new-item {:verb :kills :location :tchahua :actor :fierce-fred :other :dainty-daisy :date 20210609} + known-item {:verb :kills :location :tchahua :actor :fierce-fred :other :dainty-daisy} + actual (compatible-item? new-item known-item)] + (is (= actual expected) "A new item which is more specific adds knowledge and is not compatible"))) + (testing "Compatible item: new item conflicts with existing knowledge." + (let [expected false + new-item {:verb :kills :location :tchahua :actor :jealous-joe :other :dainty-daisy} + known-item {:verb :kills :location :tchahua :actor :fierce-fred :other :dainty-daisy} + actual (compatible-item? new-item known-item)] + (is (= actual expected) "A new item which we don't yet intelligently handle but is not compatible")))) + (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)] + {: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])] + {:knowledge [{:verb :steal + :actor :albert + :other :belinda + :object :foo + :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])] + {:home [{: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])] + {: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}])] + {: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}])] + {: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)] + {: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])] + {: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])] + {: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}])] + {: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}])] + {: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])] + {: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])] + {: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})] + item {:verb :marry :actor :adam :other :belinda} + rule {:verb :marry :actor :other :other :actor} + actual (infer item rule)] (is (= actual expected))) (let [expected {:verb :attack, :actor :adam, :other :belinda} - actual (infer {:verb :rape :actor :adam :other :belinda} - {:verb :attack})] + item {:verb :rape :actor :adam :other :belinda} + rule {:verb :attack} + actual (infer item rule)] (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})] + item {:verb :rape :actor :adam :other :belinda} + rule {:verb :sex :actor :other :other :actor} + actual (infer item rule)] (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}} + (let [expected #{{:verb :sex, :actor :belinda, :other :adam, :location :test-home, :nth-hand 1} + {:verb :sex, :actor :adam, :other :belinda, :location :test-home, :nth-hand 1} + {:verb :attack, :actor :adam, :other :belinda, :location :test-home, :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' (set (map #(dissoc % :time-stamp) actual))] - (is (= actual' expected))))) + actual (set (make-all-inferences + {:verb :rape :actor :adam :other :belinda :location :test-home :nth-hand 1}))] + (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}]} + (let [expected {:home [{0 0} :test-home] + :knowledge [{:verb :sex, :actor :adam, :other :belinda, :location [:test-home], :nth-hand 1} + {:verb :sex, :actor :belinda, :other :adam, :location [:test-home], :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 % :time-stamp) (:knowledge actual))))] - (is (= actual' expected))))) + {:home [{0, 0} :test-home] :knowledge []} + {:verb :sex :actor :adam :other :belinda :location [:test-home]})] + (is (= actual expected))))) diff --git a/test/cc/journeyman/the_great_game/merchants/markets_test.clj b/test/cc/journeyman/the_great_game/merchants/markets_test.clj index e86d173..d972257 100644 --- a/test/cc/journeyman/the_great_game/merchants/markets_test.clj +++ b/test/cc/journeyman/the_great_game/merchants/markets_test.clj @@ -1,5 +1,5 @@ (ns cc.journeyman.the-great-game.merchants.markets-test - (:require [clojure.test :refer :all] + (:require [clojure.test :refer [deftest is testing]] [cc.journeyman.the-great-game.utils :refer [deep-merge]] [cc.journeyman.the-great-game.world.world :refer [default-world]] [cc.journeyman.the-great-game.merchants.markets :refer [adjust-quantity-and-price new-price run]])) diff --git a/test/cc/journeyman/the_great_game/merchants/merchant_utils_test.clj b/test/cc/journeyman/the_great_game/merchants/merchant_utils_test.clj index 69f40fe..52766f8 100644 --- a/test/cc/journeyman/the_great_game/merchants/merchant_utils_test.clj +++ b/test/cc/journeyman/the_great_game/merchants/merchant_utils_test.clj @@ -1,5 +1,5 @@ (ns cc.journeyman.the-great-game.merchants.merchant-utils-test - (:require [clojure.test :refer :all] + (:require [clojure.test :refer [deftest is testing]] [cc.journeyman.the-great-game.utils :refer [deep-merge]] [cc.journeyman.the-great-game.world.world :refer [default-world]] [cc.journeyman.the-great-game.merchants.merchant-utils :refer diff --git a/test/cc/journeyman/the_great_game/merchants/planning_test.clj b/test/cc/journeyman/the_great_game/merchants/planning_test.clj index ef7595b..190aaf7 100644 --- a/test/cc/journeyman/the_great_game/merchants/planning_test.clj +++ b/test/cc/journeyman/the_great_game/merchants/planning_test.clj @@ -1,5 +1,5 @@ (ns cc.journeyman.the-great-game.merchants.planning-test - (:require [clojure.test :refer :all] + (:require [clojure.test :refer [deftest is testing]] [cc.journeyman.the-great-game.utils :refer [deep-merge]] [cc.journeyman.the-great-game.world.world :refer [default-world]] [cc.journeyman.the-great-game.merchants.planning :refer [plan-trade select-cargo]])) diff --git a/test/cc/journeyman/the_great_game/time_test.clj b/test/cc/journeyman/the_great_game/time_test.clj index eb5d856..cbb8689 100644 --- a/test/cc/journeyman/the_great_game/time_test.clj +++ b/test/cc/journeyman/the_great_game/time_test.clj @@ -1,5 +1,5 @@ (ns cc.journeyman.the-great-game.time-test - (:require [clojure.test :refer :all] + (:require [clojure.test :refer [deftest is testing]] ;; [clojure.core.async :refer [thread