Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
a2ab62c94a
60 changed files with 3185 additions and 88 deletions
44
src/cc/journeyman/the_great_game/agent/agent.clj
Normal file
44
src/cc/journeyman/the_great_game/agent/agent.clj
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
(ns cc.journeyman.the-great-game.agent.agent
|
||||
"Anything in the game world with agency"
|
||||
(:require [the-great-game.objects.game-object :refer [ProtoObject]]
|
||||
[the-great-game.objects.container :refer [ProtoContainer]]))
|
||||
|
||||
;; hierarchy of needs probably gets implemented here
|
||||
;; I'm probably going to want to defprotocol stuff, to define the hierarchy
|
||||
;; of things in the gameworld; either that or drop to Java, wich I'd rather not do.
|
||||
|
||||
(defprotocol ProtoAgent
|
||||
"An object which can act in the world"
|
||||
(act
|
||||
[actor world circle]
|
||||
"Allow `actor` to do something in this `world`, in the context of this
|
||||
`circle`; return the new state of the actor if something was done, `nil`
|
||||
if nothing was done. Circle is expected to be one of
|
||||
|
||||
* `:active` - actors within visual/audible range of the player
|
||||
character;
|
||||
* `:pending` - actors not in the active circle, but sufficiently close
|
||||
to it that they may enter the active circle within a short period;
|
||||
* `:background` - actors who are active in the background in order to
|
||||
handle trade, news, et cetera;
|
||||
* `other` - actors who are not members of any other circle, although
|
||||
I'm not clear whether it would ever be appropriate to invoke an
|
||||
`act` method on them.
|
||||
|
||||
The `act` method *must not* have side effects; it must *only* return a
|
||||
new state. If the actor's intention is to seek to change the state of
|
||||
something else in the game world, it must add a representation of that
|
||||
intention to the sequence which will be returned by its
|
||||
`pending-intentions` method.")
|
||||
(pending-intentions
|
||||
[actor]
|
||||
"Returns a sequence of effects an actor intends, as a consequence of
|
||||
acting. The encoding of these is not yet defined."))
|
||||
|
||||
(defrecord Agent
|
||||
;; "A default agent."
|
||||
[name home tribe]
|
||||
ProtoObject
|
||||
ProtoContainer
|
||||
ProtoAgent
|
||||
)
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
(ns the-great-game.gossip.gossip
|
||||
(ns cc.journeyman.the-great-game.gossip.gossip
|
||||
"Interchange of news events between gossip agents"
|
||||
(:require [the-great-game.utils :refer [deep-merge]]
|
||||
[the-great-game.gossip.news-items :refer [learn-news-item]]))
|
||||
(:require [cc.journeyman.the-great-game.utils :refer [deep-merge]]
|
||||
[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
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
(ns the-great-game.gossip.news-items
|
||||
(ns cc.journeyman.the-great-game.gossip.news-items
|
||||
"Categories of news events interesting to gossip agents"
|
||||
(:require [the-great-game.world.location :refer [distance-between]]
|
||||
[the-great-game.time :refer [game-time]]))
|
||||
(:require [cc.journeyman.the-great-game.world.location :refer [distance-between]]
|
||||
[cc.journeyman.the-great-game.time :refer [game-time]]))
|
||||
|
||||
;; The ideas here are based on the essay 'The spread of knowledge in a large
|
||||
;; game world', q.v.; they've advanced a little beyond that and will doubtless
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
(ns the-great-game.merchants.markets
|
||||
(ns cc.journeyman.the-great-game.merchants.markets
|
||||
"Adjusting quantities and prices in markets."
|
||||
(:require [taoensso.timbre :as l :refer [info error]]
|
||||
[the-great-game.utils :refer [deep-merge]]))
|
||||
[cc.journeyman.the-great-game.utils :refer [deep-merge]]))
|
||||
|
||||
(defn new-price
|
||||
"If `stock` is greater than the maximum of `supply` and `demand`, then
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(ns the-great-game.merchants.merchant-utils
|
||||
(ns cc.journeyman.the-great-game.merchants.merchant-utils
|
||||
"Useful functions for doing low-level things with merchants.")
|
||||
|
||||
(defn expected-price
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(ns the-great-game.merchants.merchants
|
||||
(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]]
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
(ns the-great-game.merchants.planning
|
||||
(ns 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."
|
||||
(:require [the-great-game.utils :refer [deep-merge make-target-filter]]
|
||||
[the-great-game.merchants.merchant-utils :refer :all]
|
||||
[the-great-game.world.routes :refer [find-route]]
|
||||
[the-great-game.world.world :refer [actual-price default-world]]))
|
||||
(:require [cc.journeyman.the-great-game.utils :refer [deep-merge make-target-filter]]
|
||||
[cc.journeyman.the-great-game.merchants.merchant-utils :refer [can-afford can-carry expected-price]]
|
||||
[cc.journeyman.the-great-game.world.routes :refer [find-route]]
|
||||
[cc.journeyman.the-great-game.world.world :refer [actual-price default-world]]))
|
||||
|
||||
(defn generate-trade-plans
|
||||
"Generate all possible trade plans for this `merchant` and this `commodity`
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(ns the-great-game.merchants.strategies.simple
|
||||
(ns 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
|
||||
|
|
@ -7,12 +7,12 @@
|
|||
profitably, moves towards home with no cargo. If at home and no commodity
|
||||
can be traded profitably, does not move."
|
||||
(:require [taoensso.timbre :as l :refer [info error spy]]
|
||||
[the-great-game.utils :refer [deep-merge]]
|
||||
[the-great-game.gossip.gossip :refer [move-gossip]]
|
||||
[the-great-game.merchants.planning :refer :all]
|
||||
[the-great-game.merchants.merchant-utils :refer
|
||||
[cc.journeyman.the-great-game.utils :refer [deep-merge]]
|
||||
[cc.journeyman.the-great-game.gossip.gossip :refer [move-gossip]]
|
||||
[cc.journeyman.the-great-game.merchants.planning :refer [augment-plan plan-trade select-cargo]]
|
||||
[cc.journeyman.the-great-game.merchants.merchant-utils :refer
|
||||
[add-stock add-known-prices]]
|
||||
[the-great-game.world.routes :refer [find-route]]))
|
||||
[cc.journeyman.the-great-game.world.routes :refer [find-route]]))
|
||||
|
||||
(defn plan-and-buy
|
||||
"Return a world like this `world`, in which this `merchant` has planned
|
||||
11
src/cc/journeyman/the_great_game/objects/container.clj
Normal file
11
src/cc/journeyman/the_great_game/objects/container.clj
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
(ns cc.journeyman.the-great-game.objects.container
|
||||
(:require
|
||||
[cc.journeyman.the-great-game.objects.game-object :refer :all]))
|
||||
|
||||
(defprotocol ProtoContainer
|
||||
(contents
|
||||
[container]
|
||||
"Return a sequence of the contents of this `container`, or `nil` if empty.")
|
||||
(is-empty?
|
||||
[container]
|
||||
"Return `true` if this `container` is empty, else `false`."))
|
||||
19
src/cc/journeyman/the_great_game/objects/game_object.clj
Normal file
19
src/cc/journeyman/the_great_game/objects/game_object.clj
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
(ns cc.journeyman.the-great-game.objects.game-object
|
||||
"Anything at all in the game world")
|
||||
|
||||
(defprotocol ProtoObject
|
||||
"An object in the world"
|
||||
(id [object] "Returns the unique id of this object.")
|
||||
(reify-object
|
||||
[object]
|
||||
"Adds this `object` to the global object list. If the `object` has a
|
||||
non-nil value for its `id` method, keys it to that id - **but** if the
|
||||
id value is already in use, throws a hard exception. Returns the id to
|
||||
which the object is keyed in the global object list."))
|
||||
|
||||
(defrecord GameObject
|
||||
[id]
|
||||
;; "An object in the world"
|
||||
ProtoObject
|
||||
(id [_] id)
|
||||
(reify-object [object] "TODO: doesn't work yet"))
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(ns the-great-game.time
|
||||
(ns cc.journeyman.the-great-game.time
|
||||
(:require [clojure.string :as s]))
|
||||
|
||||
(def game-start-time
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(ns the-great-game.utils)
|
||||
(ns cc.journeyman.the-great-game.utils)
|
||||
|
||||
(defn cyclic?
|
||||
"True if two or more elements of `route` are identical"
|
||||
|
|
@ -33,3 +33,13 @@
|
|||
(list (first %) 'm)
|
||||
(nth % 1))
|
||||
targets)))))
|
||||
|
||||
(defn value-or-default
|
||||
"Return the value of this key `k` in this map `m`, or this `dflt` value if
|
||||
there is none."
|
||||
[m k dflt]
|
||||
(or (when (map? m) (m k)) dflt))
|
||||
|
||||
;; (value-or-default {:x 0 :y 0 :altitude 7} :altitude 8)
|
||||
;; (value-or-default {:x 0 :y 0 :altitude 7} :alt 8)
|
||||
;; (value-or-default nil :altitude 8)
|
||||
159
src/cc/journeyman/the_great_game/world/heightmap.clj
Normal file
159
src/cc/journeyman/the_great_game/world/heightmap.clj
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
(ns cc.journeyman.the-great-game.world.heightmap
|
||||
"Functions dealing with the tessellated multi-layer heightmap."
|
||||
(: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]]
|
||||
[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
|
||||
;; map into The Great Game, and whether it passes through Walkmap to get here.
|
||||
;; This file as currently written assumes it doesn't.
|
||||
|
||||
;; It's utterly impossible to hold a whole continent at one metre scale in
|
||||
;; memory at one time. So we have to be able to regenerate high resolution
|
||||
;; surfaces from much lower resolution heightmaps.
|
||||
;;
|
||||
;; Thus to reproduce a segment of surface at a particular level of detail,
|
||||
;; we:
|
||||
;; 1. load the base heightmap into a grid (see
|
||||
;; `mw-engine.heightmap/apply-heightmap`);
|
||||
;; 2. scale the base hightmap to kilometre scale (see `scale-grid`);
|
||||
;; 3. exerpt the portion of that that we want to reproduce (see `exerpt-grid`);
|
||||
;; 4. interpolate that grid to get the resolution we require (see
|
||||
;; `interpolate-grid`);
|
||||
;; 5. create an appropriate purturbation grid from the noise map(s) for the
|
||||
;; same coordinates to break up the smooth interpolation;
|
||||
;; 6. sum the altitudes of the two grids.
|
||||
;;
|
||||
;; In production this will have to be done **very** fast!
|
||||
|
||||
(def ^:dynamic *base-map* "resources/maps/heightmap.png")
|
||||
(def ^:dynamic *noise-map* "resources/maps/noise.png")
|
||||
|
||||
(defn scale-grid
|
||||
"multiply all `:x` and `:y` values in this `grid` by this `n`."
|
||||
[grid n]
|
||||
(map-world grid (fn [w c x] (assoc c :x (* (:x c) n) :y (* (:y c) n)))))
|
||||
|
||||
|
||||
|
||||
;; Each of the east-west curve and the north-south curve are of course two
|
||||
;; dimensional curves; the east-west curve is in the :x/:z plane and the
|
||||
;; north-south curve is in the :y/:z plane (except, perhaps unwisely,
|
||||
;; we've been using :altitude to label the :z plane). We have a library
|
||||
;; function `walkmap.edge/intersection2d`, but as currently written it
|
||||
;; can only find intersections in :x/:y plane.
|
||||
;;
|
||||
;; TODO: rewrite the function so that it can use arbitrary coordinates.
|
||||
;; AFTER TRYING: OK, there are too many assumptions about the way that
|
||||
;; function is written to allow for easy rotation. TODO: think!
|
||||
|
||||
(defn interpolate-altitude
|
||||
"Return the altitude of the point at `x-offset`, `y-offset` within this
|
||||
`cell` having this `src-width`, taken from this `grid`."
|
||||
[cell grid src-width x-offset y-offset ]
|
||||
(let [c-alt (:altitude cell)
|
||||
n-alt (or (:altitude (get-cell grid (:x cell) (dec (:y cell)))) c-alt)
|
||||
w-alt (or (:altitude (get-cell grid (inc (:x cell)) (:y cell))) c-alt)
|
||||
s-alt (or (:altitude (get-cell grid (:x cell) (inc (:y cell)))) c-alt)
|
||||
e-alt (or (:altitude (get-cell grid (dec (:x cell)) (:y cell))) c-alt)]
|
||||
;; TODO: construct two curves (arcs of circles good enough for now)
|
||||
;; n-alt...c-alt...s-alt and e-alt...c-alt...w-alt;
|
||||
;; then interpolate x-offset along e-alt...c-alt...w-alt and y-offset
|
||||
;; along n-alt...c-alt...s-alt;
|
||||
;; then return the average of the two
|
||||
|
||||
0))
|
||||
|
||||
(defn interpolate-cell
|
||||
"Construct a grid (array of arrays) of cells each of width `target-width`
|
||||
from this `cell`, of width `src-width`, taken from this `grid`"
|
||||
[cell grid src-width target-width]
|
||||
(let [offsets (map #(* target-width %) (range (/ src-width target-width)))]
|
||||
(into
|
||||
[]
|
||||
(map
|
||||
(fn [r]
|
||||
(into
|
||||
[]
|
||||
(map
|
||||
(fn [c]
|
||||
(assoc cell
|
||||
:x (+ (:x cell) c)
|
||||
:y (+ (:y cell) r)
|
||||
:altitude (interpolate-altitude cell grid src-width c r)))
|
||||
offsets)))
|
||||
offsets))))
|
||||
|
||||
(defn interpolate-grid
|
||||
"Return a grid interpolated from this `grid` of rows, cols given scaling
|
||||
from this `src-width` to this `target-width`"
|
||||
[grid src-width target-width]
|
||||
(reduce
|
||||
concat
|
||||
(into
|
||||
[]
|
||||
(map
|
||||
(fn [row]
|
||||
(reduce
|
||||
(fn [g1 g2]
|
||||
(into [] (map #(into [] (concat %1 %2)) g1 g2)))
|
||||
(into [] (map #(interpolate-cell % grid src-width target-width) row))))
|
||||
grid))))
|
||||
|
||||
(defn excerpt-grid
|
||||
"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`."
|
||||
[grid x-offset y-offset width height]
|
||||
(into
|
||||
[]
|
||||
(remove
|
||||
nil?
|
||||
(map
|
||||
(fn [row]
|
||||
(when
|
||||
(and
|
||||
(>= (:y (first row)) y-offset)
|
||||
(< (:y (first row)) (+ y-offset height)))
|
||||
(into
|
||||
[]
|
||||
(remove
|
||||
nil?
|
||||
(map
|
||||
(fn [cell]
|
||||
(when
|
||||
(and
|
||||
(>= (:x cell) x-offset)
|
||||
(< (:x cell) (+ x-offset width)))
|
||||
cell))
|
||||
row)))))
|
||||
grid))))
|
||||
|
||||
(defn get-surface
|
||||
"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."
|
||||
([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))
|
||||
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)))
|
||||
b ;; actually do stuff
|
||||
(throw (Exception. "Surface out of bounds for map.")))
|
||||
)))
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(ns the-great-game.world.location
|
||||
(ns cc.journeyman.the-great-game.world.location
|
||||
"Functions dealing with location in the world."
|
||||
(:require [clojure.math.numeric-tower :refer [expt sqrt]]))
|
||||
|
||||
7
src/cc/journeyman/the_great_game/world/mw.clj
Normal file
7
src/cc/journeyman/the_great_game/world/mw.clj
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
(ns cc.journeyman.the-great-game.world.mw
|
||||
"Functions dealing with building a great game world from a MicroWorld world."
|
||||
(:require [clojure.math.numeric-tower :refer [expt sqrt]]
|
||||
[mw-engine.core :refer []]
|
||||
[mw-engine.world :refer []]))
|
||||
|
||||
;; 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.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
(ns the-great-game.world.routes
|
||||
(ns cc.journeyman.the-great-game.world.routes
|
||||
"Conceptual (plan level) routes, represented as tuples of location ids."
|
||||
(:require [the-great-game.utils :refer [cyclic?]]))
|
||||
(:require [cc.journeyman.the-great-game.utils :refer [cyclic?]]))
|
||||
|
||||
(defn find-routes
|
||||
"Find routes from among these `routes` from `from`; if `to` is supplied,
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
(ns the-great-game.world.run
|
||||
(ns cc.journeyman.the-great-game.world.run
|
||||
"Run the whole simulation"
|
||||
(:require [environ.core :refer [env]]
|
||||
[taoensso.timbre :as timbre]
|
||||
[taoensso.timbre.appenders.3rd-party.rotor :as rotor]
|
||||
[the-great-game.gossip.gossip :as g]
|
||||
[the-great-game.merchants.merchants :as m]
|
||||
[the-great-game.merchants.markets :as k]
|
||||
[the-great-game.world.world :as w]))
|
||||
[cc.journeyman.the-great-game.gossip.gossip :as g]
|
||||
[cc.journeyman.the-great-game.merchants.merchants :as m]
|
||||
[cc.journeyman.the-great-game.merchants.markets :as k]
|
||||
[cc.journeyman.the-great-game.world.world :as w]))
|
||||
|
||||
(defn init
|
||||
([]
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(ns the-great-game.world.world
|
||||
(ns cc.journeyman.the-great-game.world.world
|
||||
"Access to data about the world")
|
||||
|
||||
;;; The world has to work either as map or a database. Initially, and for
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
(ns the-great-game.agent.agent
|
||||
"Anything in the game world with agency")
|
||||
|
||||
;; hierarchy of needs probably gets implemented here
|
||||
;; I'm probably going to want to defprotocol stuff, to define the hierarchy
|
||||
;; of things in the gameworld; either that or drop to Java, wich I'd rather not do.
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue