Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
Simon Brooke 2021-02-19 23:59:49 +00:00
commit a2ab62c94a
60 changed files with 3185 additions and 88 deletions

View file

@ -0,0 +1 @@
^:replace {:linters {}}

5
.gitignore vendored
View file

@ -3,6 +3,7 @@ pom.xml.asc
*.jar *.jar
*.class *.class
*.log *.log
*.stl
[0-9a-f]*-init.clj [0-9a-f]*-init.clj
/lib/ /lib/
/classes/ /classes/
@ -18,6 +19,6 @@ pom.xml.asc
.cpcache/ .cpcache/
*~ *~
doc/.~lock.Population.ods#
.settings/ .calva/output-window/output.calva-repl
.settings/

View file

@ -2,7 +2,9 @@
#### Wednesday, 8 May 2019 #### Wednesday, 8 May 2019
![Devogilla's Bridge in Dumfries, early foourteenth century](https://2.bp.blogspot.com/-qxkySlJNmtY/XNKvJksmSjI/AAAAAAAAnXU/z1Zv2LmjydMmi_1q2mWdwVALmdfi9OItwCLcBGAs/s1600/Devorgillas-Bridge.jpg) ![Devorgilla's Bridge in Dumfries, early fourteenth century](https://2.bp.blogspot.com/-qxkySlJNmtY/XNKvJksmSjI/AAAAAAAAnXU/z1Zv2LmjydMmi_1q2mWdwVALmdfi9OItwCLcBGAs/s1600/Devorgillas-Bridge.jpg)
*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](Populating-a-game-world.html) and dynamically [settling](Settling-a-game-world.html) a game world. But at kilometre scale (and I think we need a higher resolution than that - something closer to hectare scale), settling the British Isles using my existing algorithms takes about 24 hours of continuous compute on an eight core, 3GHz machine. You cannot do that every time you launch a new game. In previous posts, I've described algorithms for dynamically [populating](Populating-a-game-world.html) and dynamically [settling](Settling-a-game-world.html) a game world. But at kilometre scale (and I think we need a higher resolution than that - something closer to hectare scale), settling the British Isles using my existing algorithms takes about 24 hours of continuous compute on an eight core, 3GHz machine. You cannot do that every time you launch a new game.

View file

@ -0,0 +1,57 @@
# On the consequences of a dynamic game environment for storytelling
First, a framing disclaimer: in [Racundra's First Cruise](https://books.google.co.uk/books?id=Ol1-DwAAQBAJ&lpg=PP1&pg=PT77#v=twopage&q&f=false), 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. Has other things to do in the real world which necessarily take precedence.
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?](https://youtu.be/bm0S4cn_rfw), 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 Andromedas Animation?](https://youtu.be/NmLPpcVQFJM), 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. The immediate game and plot context;
3. The particular non-player character addressed's knowledge of the game world;
4. The particular non-player character's attitude towards the player;
5. The particular non-player character's speech idiosyncracies, dialect, and voice
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. We can't always know exactly which non-player characters will speak the lines;
3. We can't predict what a non-player character will say in response to a question, or how long that will take;
4. We can't always know where any particular plot event will take place.
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](https://youtu.be/VwwZx5t5MIc?t=327) 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).

144
doc/Pathmaking.md Normal file
View file

@ -0,0 +1,144 @@
# 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](Baking-the-world.html)*
Towards the end of the procedural phase of the build process, every agent within the game world must move through the complete range of their needs-driven repertoire. Merchants must traverse their trading routes; soldiers must patrol routes within their employers domain; primary producers and craftspeople must visit the craftspeople who supply them; every character must visit their local inn, and must move daily between their dwelling and their workplace if different; and so on. They must do this over a considerable period - say 365 simulated days.
At the start of the baking phase, routes - roads, tracks and paths - designed by the game designers already exist.
The algorithmic part of choosing a route is the same during this baking phase as in actual game play **except** that during the baking phase the routemap is being dynamically updated, creating a new path or augmenting an existing path wherever any agent goes.
Thus the 'weight' of any section of route is a function of the total number of times that route segment has been traversed by an agent during this baking phase. At the end of the baking phase, routes travelled more than `R` times are rendered as roads, `T` times as tracks, and `P` times as footpaths, where `R`, `T` and `P` are all chosen by the game designer but generally `R > T > P`.
### Routing
Routing is fundamentally by [A\*](https://www.redblobgames.com/pathfinding/a-star/introduction.html), I think.
#### Algorithmic rules
1. No route may pass through any part of a reserved holding, except the holding which is its origin, if any, and the holding which is its destination (and in any case we won't render paths or roads within holdings, although traversal information may be used to determine whether a holding, or part of it, is paved/cobbled;
2. No route may pass through any building, with the exception of a city gate;
3. We don't have bicycles: going uphill costs work, and you don't get that cost back on the down hill. Indeed, downhills are at least as expensive to traverse as flat ground;
4. Any existing route segment costs only a third as much to traverse as open ground having the same gradient;
5. A more used route costs less to traverse than a less used route.
#### Step costing:
Step cost is something like:
(/
(-
(+ distance
(expt height-gained height-gain-exponent)
(reduce + (map crossing-penalty watercourses-crossed)))
(reduce + (map bridge-bonus bridges-crossed)))
(or road-bonus 1))
**Where**
* `distance` traversed is in metres;
* `height-gained` is in metres;
* `height-gain-exponent` is tunable;
* river `crossing-penalty` varies with a (tunable) exponent of the flow;
* `bridge-bonus` works as follows: bridge bonus for a bridge entirely cancels the river crossing penalty
for the watercourse the bridge crosses; bridge bonus for a ferry cancels a (tunable) fraction the river
crossing penalty.
* road-bonus for a road is substantial - probably about 8; for a track is less than road but greater than footpath, say 5; for a footpath has to be at least 3, to provide an incentive to
stick to paths. All these values are tunable. Road bonus ought also to increase a small amount with each traversal of the path segment, but that's still to be worked on.
A lot of this is subject to tuning once we have prototype code running.
Somewhere into all this I need to factor tolls charged by local aristons,
especially for bridges/ferries, and risk factors of hostile action, whether
by outlaws or by hostile factions. But actually, that is at a per actor
level, rather than at a pathmaking level: richer actors are less deterred
by tolls, better armed actors less deterred by threat of hostile action.
### River crossings
River crossings appear automatically when the number of traversals of a particular route across a watercourse passes some threshhold. The threshold probably varies with an exponent of the flow; the threshold at which a ferry will appear is lower (by half?) than the threshold for a bridge. Of course river crossings, like roads, can also be pre-designed by game designers.
Where a river is shallow enough, (i.e. where the flow is below some threshold) then a path crossing will be rendered as stepping stones and a track crossing as a ford. Where it's deeper than that, a path crossing either isn't rendered at all or is rendered as a light footbridge. A track or road crossing is rendered as a bridge. However, the maximum length of a bridge varies with the amount of traffic on the route segment, and if the crossing exceeds that length then a ferry is used. Road bridges will be more substantial than track bridges, for example in a biome with both timber and stone available road bridges might be rendered as stone bridges while track bridges were rendered as timber. If the watercourse is marked as `navigable`, the bridge must have a lifting section. It is assumed here that bridges are genetic buildings like most other in-game buildings, and so don't need to be individually designed.
### Representation
At some stage in the future I'll have actual game models to work with and $DEITY knows what the representation of those will be like, but to get this started I need two inputs: a heightmap, from which gradients can be derived, and a route map. The heightmap can conventionally be a monochrome raster image, and that's easy. The route map needs to be a vector representation, and SVG will be as convenient as any. So from the point of view of routing during the baking phase, a route map shall be an SVG with the following classes:
* `exclusion` used on polygons representing e.g. buildings, or impassable terrain which may not be traversed at all;
* `openwater` used on polygons representing oceans and lakes, which may be traversed only by boat (or possibly swimming, for limited distances);
* `watercourse` used on paths representing rivers or streams, with some additional attribute giving rate of flow;
* `navigable` may be an additional class on a path also marked `watercourse` indicating that it is navigable by cargo vessels;
* `route` used on paths representing a path, track or road whose final representation will be dynamically assigned at the end of baking, with some additional attribute giving total traversals to date;
* `path` used on paths representing a path designed by the designers, which will certainly be rendered as a path no matter how frequently it is traversed;
* `track` used on paths representing a track designed by the designers, which will certainly be rendered as a track no matter how frequently it is traversed;
* `road` used on paths representing a road designed by the designers, which will certainly be rendered as a road no matter how (in)frequently it is traversed.
At the end of the baking process the routing engine should be able to write out an updated SVG. New routes should be splined curves, so that they have natural bends not sharp angles.
### The 'Walkmap'
Conventional game pathfinding practice is to divide the traversable area into a mesh of 'convex polygons', where a 'convex polygon' in this sense is, essentially, a polygon having no bays. Routes traverse from a starting point to the centre of a polygon ajacent to the polygon in which the starting point is located. I have reservations as to whether this will do what I need since I'm not convinced it will produce naturalistic paths; however, it's worth at least experimenting with.
There are existing utilities (such as [hmm](https://github.com/fogleman/hmm)) which convert heightmaps into suitable geometry files; however all I've found so far convert to [binary STL](https://en.wikipedia.org/wiki/STL_(file_format)). This isn't a format I find very useful; I'd prefer an XML dialect, and SVG is good enough for me.
`hmm` converts the heightmap into a tesselation of triangles, which are necessarily convex in the sense given above. Utilities (such as [binary-stl-toASCII](https://github.com/IsseiMori/binary-stl-toASCII)) exist to convert binary STL to an ASCII encoded equivalent, which may be easier to parse.
So the pipeline seems to be
1. heightmap to binary STL
2. (optional) binary STL to ASCII STL
3. STL to SVG (where 'SVG' here is shorthand for a convenient vector format)
4. Exclude holdings, buildings, open water, and other exclusions
5. Where we have excluded exclusions, ensure that any non-convex polygons we've created are divided into new convex polygons.
I shall have to write custom code for 4 and 5 above, and, looking at what's available, probably 3 as well.
I'm working on a separate library, [walkmap](https://simon-brooke.github.io/walkmap/), which will attempt to implement this pipeline.
### Pathmaking and scale
Dealing with large heightmaps - doing anything at all with them - is extremely compute intensive.
We cannot effectively do routing at metre scale - which is what we ultimately need in settlements - across the entire thousand kilometre square map in one pass. But also we don't need to because much of the continent is by design relatively unpeopled and relatively untracked. The basic concept of the Steppe is that there are two north/south routes, the one over the Midnight Pass into the Great Place and the one via Hans'hua down to the Cities of the Coast, and those can be part of the 'designed roads' map. So we can basically exclude most of the Steppe from routing altogether. We can also - for equally obvious reasons exclude the ocean. The ocean makes up roughly half of the 1000x1000 kilometre map, the steppe and plateau take up half of what's left, mountain massifs eat into the remainder and my feeling is that much of the eastern part of the continent is probably too arid to be settled. So we probably end up only having to dynamically route about 20% of the entire map.
However, this doesn't get round the main problem with scale, and pathmaking. If we pathmake at kilometre scale, then curves will be necessarily very long and sweeping - because each path segment will be at least a kilometre long. And, actually, that's fine for very long distance roads in unpopulated fairly flat territory. It's not so good for long distance roads in rugged terrain, but...
#### Phase one: hand-designed routes
While, given the bottlenecks of the few mountain passes and the one possible pass over the plateau, the caravan routes we want would almost certainly emerge organically out of dynamic routing. But, actually, I know more or less where they need to be and it's probably easiest to hand design them. It will certainly save an enormous amount of brute-force compute time.
I think I have to accept that if I want Alpe d'Huez-style switchbacks up the Sunset and Midnight passes, they're going to have to be hand designed. The same applies to where the Hans'hua caravan road ascends the plateau.
#### Phase two: route segments 'for free' out of settlement activity
If we start by pathmaking around settlements, we can make a first start by giving the template for a holding a segment of track parallel to and just in front of its frontage, and a segment of path along its left hand and rear edges. That, actually, is going to provide 90% of all routing within a settlement, and it's done for us within the [[Settling-a-game-world]] phase.
#### Phase three: metre scale routing around settlements
So if we then collect groups of contiguous 100x100 metre zones each of which has at least one settled holding, we can route at one metre scale over that and what it will essentially do is join up and augment the route segments generated by settlement. Areas of dense settlement do not make up a great deal of the map. Note that experience may show that the metre scale routing is superflous.
#### Phases four, five and six: increasing granularity
Taking the augmented route map comprised of
1. The hand-designed, mainly long distance or plot-important routes;
2. The route segments bordering holdings;
3. The metre scale routing
we can then collect contiguous groups of zones each having at least one holding, where in phase four each zone is a kilometre square and divided into 100x100 grid so that we route at ten metre scale; in phase five we use ten kilometre by ten kilometre zones and we route at 100 metre scale; in phase six, 100 km by 100 km zones and we route at kilometre scale. This process should automatically link up all settlements on the south and west coasts, all those on the north coast, and all in the Great Place; and seeing that the posited pre-designed caravan roads already join the south coast to the north, the north to the Great Place and the Great Place to the south coast, we're done.
At least one of phases three, four, five and six is probably redundant; but without trying I'm not sure which.
#### Relevant actor classes by phase
Craftspeople and primary producers do travel between settlements, but only exceptionally. They mainly travel within at most a few kilometres of home; so they are primarily relevant in phases four and five, and need not be activated during phase six. Similarly, merchants primarily travel between settlements, and rarely within settlements; therefore, they need not be activated in phase four, and probably not even in phase five; but they must do a lot of journeys - substantially their full repertoire - in phase six.
### Tidying up
After the full set of increasing-scale passes is complete, we should automatically cull any route segments generated in the settlement phase which have never actually been traversed.
Following that, there may be scope for some final manual tweaking, if desired; I think this is most likely to happen where roads routed at kilometre scale cross rugged terrain.

View file

@ -0,0 +1,9 @@
# Building on Microworld
In [Settling a Game World](Settling-a-game-world.html) 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](https://github.com/simon-brooke/mw-ui), 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.

View file

@ -127,6 +127,32 @@
<td class="with-number">4.03 %</td> <td class="with-number">4.03 %</td>
<td class="with-number">173</td><td class="with-number">6</td><td class="with-number">124</td> <td class="with-number">173</td><td class="with-number">6</td><td class="with-number">124</td>
</tr> </tr>
<tr>
<td><a href="the_great_game/objects/container.clj.html">the-great-game.objects.container</a></td><td class="with-bar"><div class="covered"
style="width:100.0%;
float:left;"> 2 </div></td>
<td class="with-number">100.00 %</td>
<td class="with-bar"><div class="covered"
style="width:100.0%;
float:left;"> 2 </div></td>
<td class="with-number">100.00 %</td>
<td class="with-number">11</td><td class="with-number">1</td><td class="with-number">2</td>
</tr>
<tr>
<td><a href="the_great_game/objects/game_object.clj.html">the-great-game.objects.game-object</a></td><td class="with-bar"><div class="covered"
style="width:60.0%;
float:left;"> 3 </div><div class="not-covered"
style="width:40.0%;
float:left;"> 2 </div></td>
<td class="with-number">60.00 %</td>
<td class="with-bar"><div class="covered"
style="width:60.0%;
float:left;"> 3 </div><div class="not-covered"
style="width:40.0%;
float:left;"> 2 </div></td>
<td class="with-number">60.00 %</td>
<td class="with-number">19</td><td class="with-number">2</td><td class="with-number">5</td>
</tr>
<tr> <tr>
<td><a href="the_great_game/time.clj.html">the-great-game.time</a></td><td class="with-bar"><div class="covered" <td><a href="the_great_game/time.clj.html">the-great-game.time</a></td><td class="with-bar"><div class="covered"
style="width:99.5850622406639%; style="width:99.5850622406639%;

View file

@ -8,22 +8,130 @@
001&nbsp;&nbsp;(ns&nbsp;the-great-game.agent.agent 001&nbsp;&nbsp;(ns&nbsp;the-great-game.agent.agent
</span><br/> </span><br/>
<span class="not-tracked" title="0 out of 0 forms covered"> <span class="not-tracked" title="0 out of 0 forms covered">
002&nbsp;&nbsp;&nbsp;&nbsp;&quot;Anything&nbsp;in&nbsp;the&nbsp;game&nbsp;world&nbsp;with&nbsp;agency&quot;) 002&nbsp;&nbsp;&nbsp;&nbsp;&quot;Anything&nbsp;in&nbsp;the&nbsp;game&nbsp;world&nbsp;with&nbsp;agency&quot;
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
003&nbsp;&nbsp;&nbsp;&nbsp;(:require&nbsp;[the-great-game.objects.game-object&nbsp;:refer&nbsp;[ProtoObject]]
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
004&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[the-great-game.objects.container&nbsp;:refer&nbsp;[ProtoContainer]]))
</span><br/> </span><br/>
<span class="blank" title="0 out of 0 forms covered"> <span class="blank" title="0 out of 0 forms covered">
003&nbsp;&nbsp; 005&nbsp;&nbsp;
</span><br/> </span><br/>
<span class="not-tracked" title="0 out of 0 forms covered"> <span class="not-tracked" title="0 out of 0 forms covered">
004&nbsp;&nbsp;;;&nbsp;&nbsp;hierarchy&nbsp;of&nbsp;needs&nbsp;probably&nbsp;gets&nbsp;implemented&nbsp;here 006&nbsp;&nbsp;;;&nbsp;&nbsp;hierarchy&nbsp;of&nbsp;needs&nbsp;probably&nbsp;gets&nbsp;implemented&nbsp;here
</span><br/> </span><br/>
<span class="not-tracked" title="0 out of 0 forms covered"> <span class="not-tracked" title="0 out of 0 forms covered">
005&nbsp;&nbsp;;;&nbsp;&nbsp;I&#x27;m&nbsp;probably&nbsp;going&nbsp;to&nbsp;want&nbsp;to&nbsp;defprotocol&nbsp;stuff,&nbsp;to&nbsp;define&nbsp;the&nbsp;hierarchy 007&nbsp;&nbsp;;;&nbsp;&nbsp;I&#x27;m&nbsp;probably&nbsp;going&nbsp;to&nbsp;want&nbsp;to&nbsp;defprotocol&nbsp;stuff,&nbsp;to&nbsp;define&nbsp;the&nbsp;hierarchy
</span><br/> </span><br/>
<span class="not-tracked" title="0 out of 0 forms covered"> <span class="not-tracked" title="0 out of 0 forms covered">
006&nbsp;&nbsp;;;&nbsp;&nbsp;of&nbsp;things&nbsp;in&nbsp;the&nbsp;gameworld;&nbsp;either&nbsp;that&nbsp;or&nbsp;drop&nbsp;to&nbsp;Java,&nbsp;wich&nbsp;I&#x27;d&nbsp;rather&nbsp;not&nbsp;do. 008&nbsp;&nbsp;;;&nbsp;&nbsp;of&nbsp;things&nbsp;in&nbsp;the&nbsp;gameworld;&nbsp;either&nbsp;that&nbsp;or&nbsp;drop&nbsp;to&nbsp;Java,&nbsp;wich&nbsp;I&#x27;d&nbsp;rather&nbsp;not&nbsp;do.
</span><br/> </span><br/>
<span class="blank" title="0 out of 0 forms covered"> <span class="blank" title="0 out of 0 forms covered">
007&nbsp;&nbsp; 009&nbsp;&nbsp;
</span><br/>
<span class="covered" title="1 out of 1 forms covered">
010&nbsp;&nbsp;(defprotocol&nbsp;ProtoAgent
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
011&nbsp;&nbsp;&nbsp;&nbsp;&quot;An&nbsp;object&nbsp;which&nbsp;can&nbsp;act&nbsp;in&nbsp;the&nbsp;world&quot;
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
012&nbsp;&nbsp;&nbsp;&nbsp;(act
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
013&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[actor&nbsp;world&nbsp;circle]
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
014&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;Allow&nbsp;`actor`&nbsp;to&nbsp;do&nbsp;something&nbsp;in&nbsp;this&nbsp;`world`,&nbsp;in&nbsp;the&nbsp;context&nbsp;of&nbsp;this
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
015&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`circle`;&nbsp;return&nbsp;the&nbsp;new&nbsp;state&nbsp;of&nbsp;the&nbsp;actor&nbsp;if&nbsp;something&nbsp;was&nbsp;done,&nbsp;`nil`
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
016&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;nothing&nbsp;was&nbsp;done.&nbsp;Circle&nbsp;is&nbsp;expected&nbsp;to&nbsp;be&nbsp;one&nbsp;of
</span><br/>
<span class="blank" title="0 out of 0 forms covered">
017&nbsp;&nbsp;
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
018&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;`:active`&nbsp;-&nbsp;actors&nbsp;within&nbsp;visual&#x2F;audible&nbsp;range&nbsp;of&nbsp;the&nbsp;player
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
019&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;character;
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
020&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;`:pending`&nbsp;-&nbsp;actors&nbsp;not&nbsp;in&nbsp;the&nbsp;active&nbsp;circle,&nbsp;but&nbsp;sufficiently&nbsp;close
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
021&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;to&nbsp;it&nbsp;that&nbsp;they&nbsp;may&nbsp;enter&nbsp;the&nbsp;active&nbsp;circle&nbsp;within&nbsp;a&nbsp;short&nbsp;period;
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
022&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;`:background`&nbsp;-&nbsp;actors&nbsp;who&nbsp;are&nbsp;active&nbsp;in&nbsp;the&nbsp;background&nbsp;in&nbsp;order&nbsp;to
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
023&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;handle&nbsp;trade,&nbsp;news,&nbsp;et&nbsp;cetera;
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
024&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;`other`&nbsp;-&nbsp;actors&nbsp;who&nbsp;are&nbsp;not&nbsp;members&nbsp;of&nbsp;any&nbsp;other&nbsp;circle,&nbsp;although
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
025&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;I&#x27;m&nbsp;not&nbsp;clear&nbsp;whether&nbsp;it&nbsp;would&nbsp;ever&nbsp;be&nbsp;appropriate&nbsp;to&nbsp;invoke&nbsp;an
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
026&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`act`&nbsp;method&nbsp;on&nbsp;them.
</span><br/>
<span class="blank" title="0 out of 0 forms covered">
027&nbsp;&nbsp;
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
028&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;The&nbsp;`act`&nbsp;method&nbsp;*must&nbsp;not*&nbsp;have&nbsp;side&nbsp;effects;&nbsp;it&nbsp;must&nbsp;*only*&nbsp;return&nbsp;a
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
029&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new&nbsp;state.&nbsp;If&nbsp;the&nbsp;actor&#x27;s&nbsp;intention&nbsp;is&nbsp;to&nbsp;seek&nbsp;to&nbsp;change&nbsp;the&nbsp;state&nbsp;of
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
030&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;something&nbsp;else&nbsp;in&nbsp;the&nbsp;game&nbsp;world,&nbsp;it&nbsp;must&nbsp;add&nbsp;a&nbsp;representation&nbsp;of&nbsp;that
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
031&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;intention&nbsp;to&nbsp;the&nbsp;sequence&nbsp;which&nbsp;will&nbsp;be&nbsp;returned&nbsp;by&nbsp;its
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
032&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`pending-intentions`&nbsp;method.&quot;)
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
033&nbsp;&nbsp;&nbsp;&nbsp;(pending-intentions
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
034&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[actor]
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
035&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;Returns&nbsp;a&nbsp;sequence&nbsp;of&nbsp;effects&nbsp;an&nbsp;actor&nbsp;intends,&nbsp;as&nbsp;a&nbsp;consequence&nbsp;of
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
036&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;acting.&nbsp;The&nbsp;encoding&nbsp;of&nbsp;these&nbsp;is&nbsp;not&nbsp;yet&nbsp;defined.&quot;))
</span><br/>
<span class="blank" title="0 out of 0 forms covered">
037&nbsp;&nbsp;
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
038&nbsp;&nbsp;;;&nbsp;(defrecord&nbsp;Agent
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
039&nbsp;&nbsp;;;&nbsp;&nbsp;&nbsp;&quot;A&nbsp;default&nbsp;agent.&quot;
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
040&nbsp;&nbsp;;;&nbsp;&nbsp;&nbsp;ProtoObject
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
041&nbsp;&nbsp;;;&nbsp;&nbsp;&nbsp;ProtoContainer
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
042&nbsp;&nbsp;;;&nbsp;&nbsp;&nbsp;ProtoAgent
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
043&nbsp;&nbsp;;;&nbsp;)
</span><br/> </span><br/>
</body> </body>
</html> </html>

View file

@ -0,0 +1,41 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="../../coverage.css"/> <title> the_great_game/objects/container.clj </title>
</head>
<body>
<span class="covered" title="1 out of 1 forms covered">
001&nbsp;&nbsp;(ns&nbsp;the-great-game.objects.container
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
002&nbsp;&nbsp;&nbsp;&nbsp;(:require
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
003&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[the-great-game.objects.game-object&nbsp;:refer&nbsp;:all]))
</span><br/>
<span class="blank" title="0 out of 0 forms covered">
004&nbsp;&nbsp;
</span><br/>
<span class="covered" title="1 out of 1 forms covered">
005&nbsp;&nbsp;(defprotocol&nbsp;ProtoContainer
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
006&nbsp;&nbsp;&nbsp;&nbsp;(contents
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
007&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[container]
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
008&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;Return&nbsp;a&nbsp;sequence&nbsp;of&nbsp;the&nbsp;contents&nbsp;of&nbsp;this&nbsp;`container`,&nbsp;or&nbsp;`nil`&nbsp;if&nbsp;empty.&quot;)
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
009&nbsp;&nbsp;&nbsp;&nbsp;(is-empty?
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
010&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[container]
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
011&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;Return&nbsp;`true`&nbsp;if&nbsp;this&nbsp;`container`&nbsp;is&nbsp;empty,&nbsp;else&nbsp;`false`.&quot;))
</span><br/>
</body>
</html>

View file

@ -0,0 +1,65 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="../../coverage.css"/> <title> the_great_game/objects/game_object.clj </title>
</head>
<body>
<span class="covered" title="1 out of 1 forms covered">
001&nbsp;&nbsp;(ns&nbsp;the-great-game.objects.game-object
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
002&nbsp;&nbsp;&nbsp;&nbsp;&quot;Anything&nbsp;at&nbsp;all&nbsp;in&nbsp;the&nbsp;game&nbsp;world&quot;)
</span><br/>
<span class="blank" title="0 out of 0 forms covered">
003&nbsp;&nbsp;
</span><br/>
<span class="covered" title="1 out of 1 forms covered">
004&nbsp;&nbsp;(defprotocol&nbsp;ProtoObject
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
005&nbsp;&nbsp;&nbsp;&nbsp;&quot;An&nbsp;object&nbsp;in&nbsp;the&nbsp;world&quot;
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
006&nbsp;&nbsp;&nbsp;&nbsp;(id&nbsp;[object]&nbsp;&quot;Returns&nbsp;the&nbsp;unique&nbsp;id&nbsp;of&nbsp;this&nbsp;object.&quot;)
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
007&nbsp;&nbsp;&nbsp;&nbsp;(reify-object
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
008&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[object]
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
009&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;Adds&nbsp;this&nbsp;`object`&nbsp;to&nbsp;the&nbsp;global&nbsp;object&nbsp;list.&nbsp;If&nbsp;the&nbsp;`object`&nbsp;has&nbsp;a
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
010&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;non-nil&nbsp;value&nbsp;for&nbsp;its&nbsp;`id`&nbsp;method,&nbsp;keys&nbsp;it&nbsp;to&nbsp;that&nbsp;id&nbsp;-&nbsp;**but**&nbsp;if&nbsp;the
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
011&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id&nbsp;value&nbsp;is&nbsp;already&nbsp;in&nbsp;use,&nbsp;throws&nbsp;a&nbsp;hard&nbsp;exception.&nbsp;Returns&nbsp;the&nbsp;id&nbsp;to
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
012&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;which&nbsp;the&nbsp;object&nbsp;is&nbsp;keyed&nbsp;in&nbsp;the&nbsp;global&nbsp;object&nbsp;list.&quot;))
</span><br/>
<span class="blank" title="0 out of 0 forms covered">
013&nbsp;&nbsp;
</span><br/>
<span class="covered" title="1 out of 1 forms covered">
014&nbsp;&nbsp;(defrecord&nbsp;GameObject
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
015&nbsp;&nbsp;&nbsp;&nbsp;[id]
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
016&nbsp;&nbsp;&nbsp;&nbsp;;;&nbsp;&quot;An&nbsp;object&nbsp;in&nbsp;the&nbsp;world&quot;
</span><br/>
<span class="not-tracked" title="0 out of 0 forms covered">
017&nbsp;&nbsp;&nbsp;&nbsp;ProtoObject
</span><br/>
<span class="not-covered" title="0 out of 1 forms covered">
018&nbsp;&nbsp;&nbsp;&nbsp;(id&nbsp;[_]&nbsp;id)
</span><br/>
<span class="not-covered" title="0 out of 1 forms covered">
019&nbsp;&nbsp;&nbsp;&nbsp;(reify-object&nbsp;[object]&nbsp;&quot;TODO:&nbsp;doesn&#x27;t&nbsp;work&nbsp;yet&quot;))
</span><br/>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,14 +1,19 @@
(defproject the-great-game "0.1.2-SNAPSHOT" (defproject journeyman-cc/the-great-game "0.1.2-SNAPSHOT"
:cloverage {:output "docs/cloverage"} :cloverage {:output "docs/cloverage"}
:codox {:metadata {:doc "**TODO**: write docs" :codox {:metadata {:doc "**TODO**: write docs"
:doc/format :markdown} :doc/format :markdown}
:output-path "docs/codox" :output-path "docs/codox"
:source-uri "https://github.com/simon-brooke/the-great-game/blob/master/{filepath}#L{line}"} :source-uri "https://github.com/simon-brooke/the-great-game/blob/master/{filepath}#L{line}"}
:cucumber-feature-paths ["test/features/"] :cucumber-feature-paths ["test/features/"]
:dependencies [[org.clojure/clojure "1.8.0"] :dependencies [[com.taoensso/timbre "4.10.0"]
[org.clojure/math.numeric-tower "0.0.4"]
[environ "1.1.0"] [environ "1.1.0"]
[com.taoensso/timbre "4.10.0"]] [journeyman-cc/walkmap "0.1.0-SNAPSHOT"]
[me.raynes/fs "1.4.6"]
[mw-engine "0.1.6-SNAPSHOT"]
[org.clojure/algo.generic "0.1.3"]
[org.clojure/clojure "1.8.0"]
[org.clojure/math.numeric-tower "0.0.4"]
]
:description "Prototype code towards the great game I've been writing about for ten years, and know I will never finish." :description "Prototype code towards the great game I've been writing about for ten years, and know I will never finish."
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version" :license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
:url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"} :url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"}
@ -17,6 +22,8 @@
[lein-cucumber "1.0.2"] [lein-cucumber "1.0.2"]
[lein-gorilla "0.4.0"]] [lein-gorilla "0.4.0"]]
;; NOTE WELL: `lein release` won't work until we have a release repository
;; set, which we don't!
:release-tasks [["vcs" "assert-committed"] :release-tasks [["vcs" "assert-committed"]
["change" "version" "leiningen.release/bump-version" "release"] ["change" "version" "leiningen.release/bump-version" "release"]
["vcs" "commit"] ["vcs" "commit"]

View file

@ -0,0 +1 @@
[]

View file

@ -0,0 +1 @@
<html><head><title>Microworld render</title><link href="https://www.journeyman.cc/mw-ui-assets/css/states.css" rel="stylesheet" type="text/css" /></head><body><table></table></body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
resources/maps/noise.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
resources/maps/noise.xcf Normal file

Binary file not shown.

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

View file

@ -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" "Interchange of news events between gossip agents"
(:require [the-great-game.utils :refer [deep-merge]] (:require [cc.journeyman.the-great-game.utils :refer [deep-merge]]
[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 ;; Note that habitual travellers are all gossip agents; specifically, at this
;; stage, that means merchants. When merchants are moved we also need to ;; stage, that means merchants. When merchants are moved we also need to

View file

@ -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" "Categories of news events interesting to gossip agents"
(:require [the-great-game.world.location :refer [distance-between]] (:require [cc.journeyman.the-great-game.world.location :refer [distance-between]]
[the-great-game.time :refer [game-time]])) [cc.journeyman.the-great-game.time :refer [game-time]]))
;; The ideas here are based on the essay 'The spread of knowledge in a large ;; 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 ;; game world', q.v.; they've advanced a little beyond that and will doubtless

View file

@ -1,7 +1,7 @@
(ns the-great-game.merchants.markets (ns cc.journeyman.the-great-game.merchants.markets
"Adjusting quantities and prices in markets." "Adjusting quantities and prices in markets."
(:require [taoensso.timbre :as l :refer [info error]] (:require [taoensso.timbre :as l :refer [info error]]
[the-great-game.utils :refer [deep-merge]])) [cc.journeyman.the-great-game.utils :refer [deep-merge]]))
(defn new-price (defn new-price
"If `stock` is greater than the maximum of `supply` and `demand`, then "If `stock` is greater than the maximum of `supply` and `demand`, then

View file

@ -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.") "Useful functions for doing low-level things with merchants.")
(defn expected-price (defn expected-price

View file

@ -1,4 +1,4 @@
(ns the-great-game.merchants.merchants (ns cc.journeyman.the-great-game.merchants.merchants
"Trade planning for merchants, primarily." "Trade planning for merchants, primarily."
(:require [taoensso.timbre :as l :refer [info error spy]] (:require [taoensso.timbre :as l :refer [info error spy]]
[the-great-game.utils :refer [deep-merge]] [the-great-game.utils :refer [deep-merge]]

View file

@ -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 "Trade planning for merchants, primarily. This follows a simple-minded
generate-and-test strategy and currently generates plans for all possible generate-and-test strategy and currently generates plans for all possible
routes from the current location. This may not scale. Also, routes do not routes from the current location. This may not scale. Also, routes do not
currently have cost or risk associated with them." currently have cost or risk associated with them."
(:require [the-great-game.utils :refer [deep-merge make-target-filter]] (:require [cc.journeyman.the-great-game.utils :refer [deep-merge make-target-filter]]
[the-great-game.merchants.merchant-utils :refer :all] [cc.journeyman.the-great-game.merchants.merchant-utils :refer [can-afford can-carry expected-price]]
[the-great-game.world.routes :refer [find-route]] [cc.journeyman.the-great-game.world.routes :refer [find-route]]
[the-great-game.world.world :refer [actual-price default-world]])) [cc.journeyman.the-great-game.world.world :refer [actual-price default-world]]))
(defn generate-trade-plans (defn generate-trade-plans
"Generate all possible trade plans for this `merchant` and this `commodity` "Generate all possible trade plans for this `merchant` and this `commodity`

View file

@ -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. "Default trading strategy for merchants.
The simple strategy buys a single product in the local market if there is 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 profitably, moves towards home with no cargo. If at home and no commodity
can be traded profitably, does not move." can be traded profitably, does not move."
(:require [taoensso.timbre :as l :refer [info error spy]] (:require [taoensso.timbre :as l :refer [info error spy]]
[the-great-game.utils :refer [deep-merge]] [cc.journeyman.the-great-game.utils :refer [deep-merge]]
[the-great-game.gossip.gossip :refer [move-gossip]] [cc.journeyman.the-great-game.gossip.gossip :refer [move-gossip]]
[the-great-game.merchants.planning :refer :all] [cc.journeyman.the-great-game.merchants.planning :refer [augment-plan plan-trade select-cargo]]
[the-great-game.merchants.merchant-utils :refer [cc.journeyman.the-great-game.merchants.merchant-utils :refer
[add-stock add-known-prices]] [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 (defn plan-and-buy
"Return a world like this `world`, in which this `merchant` has planned "Return a world like this `world`, in which this `merchant` has planned

View 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`."))

View 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"))

View file

@ -1,4 +1,4 @@
(ns the-great-game.time (ns cc.journeyman.the-great-game.time
(:require [clojure.string :as s])) (:require [clojure.string :as s]))
(def game-start-time (def game-start-time

View file

@ -1,4 +1,4 @@
(ns the-great-game.utils) (ns cc.journeyman.the-great-game.utils)
(defn cyclic? (defn cyclic?
"True if two or more elements of `route` are identical" "True if two or more elements of `route` are identical"
@ -33,3 +33,13 @@
(list (first %) 'm) (list (first %) 'm)
(nth % 1)) (nth % 1))
targets))))) 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)

View 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.")))
)))

View file

@ -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." "Functions dealing with location in the world."
(:require [clojure.math.numeric-tower :refer [expt sqrt]])) (:require [clojure.math.numeric-tower :refer [expt sqrt]]))

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

View file

@ -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." "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 (defn find-routes
"Find routes from among these `routes` from `from`; if `to` is supplied, "Find routes from among these `routes` from `from`; if `to` is supplied,

View file

@ -1,12 +1,12 @@
(ns the-great-game.world.run (ns cc.journeyman.the-great-game.world.run
"Run the whole simulation" "Run the whole simulation"
(:require [environ.core :refer [env]] (:require [environ.core :refer [env]]
[taoensso.timbre :as timbre] [taoensso.timbre :as timbre]
[taoensso.timbre.appenders.3rd-party.rotor :as rotor] [taoensso.timbre.appenders.3rd-party.rotor :as rotor]
[the-great-game.gossip.gossip :as g] [cc.journeyman.the-great-game.gossip.gossip :as g]
[the-great-game.merchants.merchants :as m] [cc.journeyman.the-great-game.merchants.merchants :as m]
[the-great-game.merchants.markets :as k] [cc.journeyman.the-great-game.merchants.markets :as k]
[the-great-game.world.world :as w])) [cc.journeyman.the-great-game.world.world :as w]))
(defn init (defn init
([] ([]

View file

@ -1,4 +1,4 @@
(ns the-great-game.world.world (ns cc.journeyman.the-great-game.world.world
"Access to data about the world") "Access to data about the world")
;;; The world has to work either as map or a database. Initially, and for ;;; The world has to work either as map or a database. Initially, and for

View file

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

View file

@ -0,0 +1,4 @@
(ns cc.journeyman.the-great-game.gossip.gossip-test
(:require [clojure.test :refer :all]
[cc.journeyman.the-great-game.gossip.gossip :refer :all]))

View file

@ -1,7 +1,8 @@
(ns the-great-game.gossip.news-items-test (ns cc.journeyman.the-great-game.gossip.news-items-test
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[the-great-game.gossip.news-items :refer :all])) [cc.journeyman.the-great-game.gossip.news-items :refer
[degrade-location infer interest-in-location interesting-location?
learn-news-item make-all-inferences]]))
(deftest location-test (deftest location-test
(testing "Interest in locations" (testing "Interest in locations"

View file

@ -1,8 +1,8 @@
(ns the-great-game.merchants.markets-test (ns cc.journeyman.the-great-game.merchants.markets-test
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[the-great-game.utils :refer [deep-merge]] [cc.journeyman.the-great-game.utils :refer [deep-merge]]
[the-great-game.world.world :refer [default-world]] [cc.journeyman.the-great-game.world.world :refer [default-world]]
[the-great-game.merchants.markets :refer :all])) [cc.journeyman.the-great-game.merchants.markets :refer [adjust-quantity-and-price new-price run]]))
(deftest new-price-test (deftest new-price-test
@ -23,7 +23,7 @@
"The greater the relative oversupply, the more prices should fall") "The greater the relative oversupply, the more prices should fall")
)) ))
(deftest adjust-qunatity-and-price-test (deftest adjust-quantity-and-price-test
(testing "Adjustment in quantity and price: supply only." (testing "Adjustment in quantity and price: supply only."
(let [world (deep-merge (let [world (deep-merge
default-world default-world

View file

@ -1,8 +1,9 @@
(ns the-great-game.merchants.merchant-utils-test (ns cc.journeyman.the-great-game.merchants.merchant-utils-test
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[the-great-game.utils :refer [deep-merge]] [cc.journeyman.the-great-game.utils :refer [deep-merge]]
[the-great-game.world.world :refer [default-world]] [cc.journeyman.the-great-game.world.world :refer [default-world]]
[the-great-game.merchants.merchant-utils :refer :all])) [cc.journeyman.the-great-game.merchants.merchant-utils :refer
[add-stock burden can-afford can-carry expected-price]]))
(deftest expected-price-test (deftest expected-price-test
(testing "Anticipated prices in markets" (testing "Anticipated prices in markets"

View file

@ -1,8 +1,8 @@
(ns the-great-game.merchants.planning-test (ns cc.journeyman.the-great-game.merchants.planning-test
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[the-great-game.utils :refer [deep-merge]] [cc.journeyman.the-great-game.utils :refer [deep-merge]]
[the-great-game.world.world :refer [default-world]] [cc.journeyman.the-great-game.world.world :refer [default-world]]
[the-great-game.merchants.planning :refer :all])) [cc.journeyman.the-great-game.merchants.planning :refer [plan-trade select-cargo]]))
(deftest plan-trade-test (deftest plan-trade-test

View file

@ -1,7 +1,9 @@
(ns the-great-game.time-test (ns cc.journeyman.the-great-game.time-test
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
;; [clojure.core.async :refer [thread <!]] ;; [clojure.core.async :refer [thread <!]]
[the-great-game.time :refer :all])) [cc.journeyman.the-great-game.time :refer
[date-string day days-in-season days-in-week game-day-length
game-time game-start-time now season week]]))
(deftest now-tests (deftest now-tests
(testing "Time progresses" (testing "Time progresses"

View file

@ -1,6 +1,6 @@
(ns the-great-game.utils-test (ns cc.journeyman.the-great-game.utils-test
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[the-great-game.utils :refer :all])) [cc.journeyman.the-great-game.utils :refer [cyclic?]]))
(deftest cyclic-tests (deftest cyclic-tests
(testing "Detecting cyclic routes" (testing "Detecting cyclic routes"

View file

@ -1,6 +1,6 @@
(ns the-great-game.world.location-test (ns cc.journeyman.the-great-game.world.location-test
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[the-great-game.world.location :refer :all])) [cc.journeyman.the-great-game.world.location :refer :all]))
(deftest get-coords-test (deftest get-coords-test
(testing "Get coordinates of location" (testing "Get coordinates of location"

View file

@ -1,7 +1,7 @@
(ns the-great-game.world.routes-test (ns cc.journeyman.the-great-game.world.routes-test
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[the-great-game.world.routes :refer :all] [cc.journeyman.the-great-game.world.routes :refer :all]
[the-great-game.world.world :refer [default-world]])) [cc.journeyman.the-great-game.world.world :refer [default-world]]))
(deftest routing-test (deftest routing-test

View file

@ -0,0 +1,4 @@
(ns cc.journeyman.the-great-game.world.world-test
(:require [clojure.test :refer :all]
[cc.journeyman.the-great-game.world.world :refer :all]))

View file

@ -1,4 +0,0 @@
(ns the-great-game.gossip.gossip-test
(:require [clojure.test :refer :all]
[the-great-game.gossip.gossip :refer :all]))

View file

@ -1,4 +0,0 @@
(ns the-great-game.world.world-test
(:require [clojure.test :refer :all]
[the-great-game.world.world :refer :all]))

17
workspace.code-workspace Normal file
View file

@ -0,0 +1,17 @@
{
"folders": [
{
"path": "."
},
{
"path": "../gossip"
},
{
"path": "../walkmap"
},
{
"path": "../genbuildings"
}
],
"settings": {}
}