Merge branch 'develop'
This commit is contained in:
commit
41b7a7d229
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -18,4 +18,4 @@ pom.xml.asc
|
||||||
.cpcache/
|
.cpcache/
|
||||||
*~
|
*~
|
||||||
|
|
||||||
docs/cloverage/
|
doc/.~lock.Population.ods#
|
||||||
|
|
81
doc/Baking-the-world.md
Normal file
81
doc/Baking-the-world.md
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
# Baking the world
|
||||||
|
|
||||||
|
#### Wednesday, 8 May 2019
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
In previous posts, I've described algorithms for dynamically [populating](Populating-a-game-world.html) and dynamically [settling](Settling-a-game-world.html) a game world. But at kilometre scale (and I think we need a higher resolution than that - something closer to hectare scale), settling the British Isles using my existing algorithms takes about 24 hours of continuous compute on an eight core, 3GHz machine. You cannot do that every time you launch a new game.
|
||||||
|
|
||||||
|
So the game development has to run in four phases: the first three phases happen during development, to create a satisfactory, already populated and settled, initial world for the game to start from. This is particularly necessary if hand-crafted buildings and environments are going to be added to the world; the designers of those buildings and environments have to be able to see the context into which their models must fit.
|
||||||
|
|
||||||
|
## Phase one: proving - the procedural world
|
||||||
|
|
||||||
|
I'm going to call the initial phase of the game run - the phase which takes place before the quest team write their quests and the art department adds their hand-crafted models - 'proving', as when dough has been been made and set aside to rise.
|
||||||
|
|
||||||
|
Then, when the landscape has developed - the areas of forest, scrub, open meadow, moorland, savanah and desert are determined, the rivers plotted, the settlers moved in, their trades determined and their settlements allocated, the roadways which link settlements routed, river crossings and ports defined - the proving process ends, and the world is turned over to the plot-writers, quest builders and designers, for a process we can see as analogous to kneading.
|
||||||
|
|
||||||
|
But, before going there, to summarise the proving stage. The inputs are:
|
||||||
|
|
||||||
|
1. A raster height map (although this could be randomly generated using any one of many fractal algorithms) - this probably uses ideas from [tessellated multi-layer height map](https://blog.journeyman.cc/2013/07/tessellated-multi-layer-height-map.html);
|
||||||
|
1. Optionally, a raster rainfall map at 1km resolution (although my personal preference is that this should be generated procedurally from the height map).
|
||||||
|
|
||||||
|
The outputs are
|
||||||
|
|
||||||
|
1. A vector drainage map (rivers);
|
||||||
|
1. A raster biome map at roughly 1 km resolution (it might be anything between hectare resolution and 1Km resolution, but obviously higher resolution takes more storage);
|
||||||
|
1. A database of settlers and their settlements, such that the settlements have x,y co-ordinates;
|
||||||
|
1. A vector road map.
|
||||||
|
|
||||||
|
In this sense, the 'biome map' is just the end state of a [Microworld](https://blog.journeyman.cc/2014/08/modelling-settlement-with-cellular.html) run. The 'biomes' include things like 'forest', 'scrub', 'heath', 'pasture', but they may also include human settlement, and even settlement by different cultural groups.
|
||||||
|
|
||||||
|
This gives us all we need to vegetate and furnish the world. When rendering each square metre we have
|
||||||
|
|
||||||
|
1. The x,y coordinates, obviously;
|
||||||
|
1. The altitude, taken from the height map;
|
||||||
|
1. The biome, taken from the biome map;
|
||||||
|
1. The biomes of adjacent cells in the biome map;
|
||||||
|
1. The proximity of the nearest watercourse;
|
||||||
|
1. The proximity of the nearest road or pathway;
|
||||||
|
1. Whether we are inside, or outside, a settlement (where for these purposes, 'settlement' includes enclosed field), and if inside, what type of settlement it is.
|
||||||
|
|
||||||
|
Given these parameters, and using the x, y coordinates as seed of a deterministic pseudo-random number generator, we can generate appropriate vegetation and buildings to render a believable world. The reason for pulling adjacent biomes into the renderer is that sharp transitions from one biome to another - especially ones which align to a rectangular grid - rarely exist in nature, and that consequently most transitions from one biome to another should be gradual.
|
||||||
|
|
||||||
|
Note that proving, although extremely compute intensive, is not necessarily a one-time job. If the designers aren't satisfied with the first world to emerge from this process, they can run it again, and again, to generate a world with which they are satisfied. It's also possible to hand-edit the output of proving, if needed.
|
||||||
|
|
||||||
|
But now, designers and story-writers can see the world in which their creations will be set.
|
||||||
|
|
||||||
|
## Phase two: kneading - making the world fit our needs
|
||||||
|
|
||||||
|
Enough of proving, let's get on to kneading.
|
||||||
|
|
||||||
|
Hand-designed buildings and environments are likely to be needed, or at least useful, for plot; also, particularly, very high status buildings are probably better hand designed. I'm inclined to think that less is more here, for two reasons:
|
||||||
|
|
||||||
|
You cannot hand design a very large world, it's just impossible. How CD Project Red managed with Witcher 3 I don't know, since I understand that is largely hand designed; but that was a very large team, and even so it isn't a world on the scale I'm envisaging.
|
||||||
|
|
||||||
|
Procedurally generated models take a wee bit of compute power to reify, but not a huge amount, and they're trivial to store - you need one single birch leaf model and one single birch-bark texture generator to make every birch tree in the game, and probably a single parameterised tree function can draw every tree of every species (and quite a lot of shrubs and ground-cover plants, too). But once reified, they take no longer to render than a manually crafted model.
|
||||||
|
|
||||||
|
By contrast, a manually crafted model will take a very great deal more space to store, such that being able to render a large world from hand crafted models, without excessive model re-use, isn't going to be possible.
|
||||||
|
|
||||||
|
So it's better in my opinion to put effort into good procedural generation functions, not just for foliage but also for buildings. My reason for using a picture of a medieval bridge at the head of the essay is to illustrate exactly this point: even in the medieval period, bridges comprise a series of repeating modules. Take one arch module and one ramp module from Devorgilla's bridge as models, add texture skins for several different stone types, stretch the modules a little in whatever dimension is needed, and repeat the arch module as many times as needed, and you can create a range of bridges to span many different rivers - which will all be visibly similar, but that's fine, that's the nature of a traditional culture - but each slightly different.
|
||||||
|
|
||||||
|
Take half a dozen sets of models - timber bridges for forested biomes, brick bridges for biomes without stone or timber - and you can build procedural bridges across a whole continent without ever exactly repeating yourself.
|
||||||
|
|
||||||
|
However, in some places the designers and story writers will want, for plot reasons and to create iconic environments, to add models. I'm inclined not to over do this, both for reasons of development effort and for reasons of storage cost, but they will. Very high status buildings may need to be unique and distinctive, for example. These need to be designed and their locations and spatial dimensions added to the database, so that the models can be rendered in the right positions (and, critically, procedurally generated models can be omitted in those positions!)
|
||||||
|
|
||||||
|
Story and quest writers will also want characters for their plots. While there's no reason why writers cannot add entirely new characters to the database, there's no reason why they cannot incorporate characters generated in the settlement phase into the story; for this reason, characters need to be able to be tagged in the database as plot characters, and with what quests/elements of the plot they're associated.
|
||||||
|
|
||||||
|
This allows a mechanism to prevent a plot character from being killed by another non-player character, or dying of disease or starvation, before the plot elements in which they feature have been completed.
|
||||||
|
|
||||||
|
## Phase three: baking - making it delicious
|
||||||
|
|
||||||
|
Once the world has been populated, settled, vegetated, the story has been written, the models built, the quests designed, there is probably a process of optimisation - stripping out things which aren't needed at play time, streamlining things that are - before you have a game ready to ship; but really I haven't yet given that much thought.
|
||||||
|
|
||||||
|
## Phase four: eating!
|
||||||
|
|
||||||
|
At the end, though, you have a game, and a player plays it. How much of the dynamic, organic life that brought the game through proving continues on into the playing phase? If the [gossip](The-spread-of-knowledge-in-a-large-game.html) ideas are to work, if unscripted, non-plot-related events (as well as scripted, plot related events) are to happen while the player plays, if news of these events is to percolate through the world and reach the player in organic, unscripted ways, if a lot of the emergent gameplay I'm imagining is to work, then quite a lot of the dynamic things must be happening.
|
||||||
|
|
||||||
|
Of course, part of this depends on the length of 'game world time' is expected to elapse in the course of one play through of the game. If it's less than a year, then you don't need children dynamically being born, and characters dynamically growing older; but if more, then you do. Similarly, you don't need a real simulation of trading to dynamically drive prices in markets, but for a fun trading sub-game to emerge, you probably do, and if you are using merchants as news spreading agents the additional compute cost is not high.
|
||||||
|
|
||||||
|
And I understand that many game writers will shudder at the thought that a war might (or might not) start in the middle of their plot, that a battle might, one time in a thousand, take place right where they've plotted some significant encounter. Most modern video games are essentially just very complicated state machines: if you make this sequence of choices, this outcome will happen, guaranteed. Or else they're puddles of random soup, where everything that happens is more or less driven by a random number generator. What I'm envisaging is something quite different: a world in which traders gonna trade, robbers gonna rob, lovers gonna love, scandal-mongers gonna make scandal, organically and dynamically whether the player is there or not, and news of these events will filter through to the player through the gossip network also organically and dynamically.
|
||||||
|
|
||||||
|
A world, in short, through which no two runs will ever be the same, in which interesting bits of story will happen with no-one directing or scripting them. And for that to work, some of the same dynamic processes that drove the proving phase have to continue into the eating phase.
|
40
doc/Game_Play.md
Normal file
40
doc/Game_Play.md
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# 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. Superpower: the player character has some special powers or skills that other characters in the game fo not have.
|
||||||
|
|
||||||
|
2. Special status: the player character is 'the chosen one', 'the hero', or even just 'the Witcher' from the very beginning, without having done anything to earn those titles.
|
||||||
|
|
||||||
|
3. Boss fights: some non-player characters have special, and specially strong, combat repertoire, and block progress in the game until you overcome them.
|
||||||
|
|
||||||
|
4. Psychokiller: completing the game necessarily involves beating many, many other characters in combat.
|
||||||
|
|
||||||
|
5. Slaughterhouse: the main way to interact with other characters is to kill them.
|
||||||
|
|
||||||
|
7. The Script is King: everything is scripted. The player either can't diverge from the script, or if they do, will find no interesting content.
|
||||||
|
|
||||||
|
6. Dumb and dumber: non-player characters, even important ones, have extremely limited vocal repertoire.
|
||||||
|
|
||||||
|
Of these, the last two, I think, are key: they are the root cause of the other problems. In fact, to take it further, the real key is the last. We talk a lot about 'Game AI', but really there's nothing remotely approaching artificial intelligence ins modern games. Non-player characters do not think; they do not learn; they do not reason; they do not know. They speak only from the script. And they speak only from the script because of the fetish for voice acting.
|
||||||
|
|
||||||
|
## Death to Dumb-Dumb
|
||||||
|
|
||||||
|
As I've argued [elsewhere](), [repeatedly](), we can now generate a wide variety of naturalistic speaking voices, and have them narrate text. Now of course there's great deal of information conveyed in human vocal communication in addition to the words – of which emotion is only an example, although an important one. Generating voices with the right tone, the right emphasis, for different situations may be harder than I anticipate; there may be an '[uncanny valley](Uncanny_dialogue)' in which generated speech just sounds uncomfortably off.
|
||||||
|
|
||||||
|
But it's a trade off. For possibly less than perfect vocal performance, you get the possibility of much richer repertoire. You get not only the possibility that non-player characters can talk about the weather, or gossip about their neighbours, or give you directions to local places of interest. You get the possibility that a non-player character's attitude to you may be conditioned by the fact that they've heard that you stole from their second cousin, or that you killed an outlaw who'd raped one of their friends.
|
||||||
|
|
||||||
|
Suddenly, they can have attitudes about things that happen in the world, opinions about major political figures in it, about their neighbours, about you the player, which are not scripted, which are emergent. When they learn new information which conflicts with something they already knew, their attitudes will change, as that new information is integrated. Intelligent behaviour will emerge.
|
||||||
|
|
||||||
|
And with the emergence of intelligent behaviour comes the emergence of possibilities for negotiation, for diplomacy, for dynamic, unscripted, friendships and romances. Which means, there are things you can do to interact with every non-player character, even ones who are not 'plot' characters, other than just kill them.
|
||||||
|
|
||||||
|
And as now gameplay possibilities emerge, as new stories emerge organically out of the dynamically changing relationships between non-player characters in the world, the need for scripting decreases.
|
||||||
|
|
||||||
|
The problem with scripting is that it greatly limits player agency. The story can only have one of a few predetermined -- literally, scripted -- endings. This is clearly expressed in [a review of Red Dead Redemption 2](https://youtu.be/_JRikiQyzLA) which I recomment to you; but is equally true of almost all other games.
|
||||||
|
|
||||||
|
Dynamic side quests have fallen into disfavour, because, when they've been tried in earlier generation games, there were too few possibilities, and they became repetitive and boring. I don't believe, with the wealth of compute resource we now have, this any longer need be the case. On the contrary, I think we can now dynamically generate a wide range of different, and differently complex, side quests. I think, in fact, that these can [emerge organically](Organic_Quests.md) from the structure of the game world.
|
||||||
|
|
||||||
|
## Death to Psycho-Killer
|
||||||
|
|
||||||
|
If the main way a player can interact with non-player characters is to kill them, and if the player doesn't have a systematic combat advantage over non-player characters, then it's going to be a short game. This is why players in many or most video games do start with a systematic combat advantage, and that combat advantage tends to increase over the course of the game as the player becomes more proficient with the combat system, and acquires better weapons, armour and combat buffs. This in turn means that to keep combat 'interesting', the game either has to through larger and larger armies of 'bad' non-player characters against the player – a fault seen at its worst in [Dragon Age 2](https://youtu.be/Sc8Bn8yqPYQ?t=3150).
|
||||||
|
|
74
doc/Gossip_scripted_plot_and_Johnny_Silverhand.md
Normal file
74
doc/Gossip_scripted_plot_and_Johnny_Silverhand.md
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# 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. A search of the game state and lore for relevant information;
|
||||||
|
4. A filtering of the results based on what the particular NPC can be expected to know;
|
||||||
|
5. Generation of a textual response from those results based on a library of templates which defines the particular NPC's dialect and style of speech;
|
||||||
|
6. Production of audio using a [Lyrebird]{https://www.descript.com/overdub?lyrebird=true) style generated voice.
|
||||||
|
|
||||||
|
As I've argued before, the game engine necessarily knows everything about the lore, and the current state, of the game world. It would be possible for any non-player character to answer literally any question about the game world, from who was mayor of Night City in 2020 to who lives in the apartment one floor up from yours, to what the weather is like in North Oaks just now.
|
||||||
|
|
||||||
|
What individual characters know should, of course, be more limited. People who live in Japantown or Heywood are unlikely to know who lives in a particular apartment in Watson; only real old timers, like Rogue, are likely to remember who was mayor fifty years ago. That's the reason for filtering; but the filtering really isn't a big deal.
|
||||||
|
|
||||||
|
Again, the generation of distinct voices for hundreds of non-player characters isn't any longer a big deal. Distinct social groups -- the corpos, and the different gangs such as Maelstrom or the Mox, will have their own argot, their own slang, their own habitual figures of speech which can be encoded into template libraries, while technologies like Lyrebird can produce an infinite range of realistic-sounding voices.
|
||||||
|
|
||||||
|
In particular, they can mimic real voices. They can mimic the voices of real actors. They can mimic [Keanu Reeves](https://cyberpunk.fandom.com/wiki/Keanu_Reeves).
|
||||||
|
|
||||||
|
So: how do you integrate this free form 'you can say anything to any character' style of play with scripted plot?
|
||||||
|
|
||||||
|
Obviously, my vision -- as I've set out in [Organic Quests](Organic_Quests.md) -- is that many quests should emerge organically from modelling the lives, activities and motivations of non-player characters. But that's a radical vision and not one you can really expect many people to buy into until it has been demonstrated to work. I think that investors are still going to want to have confidence that there's something exciting in the game for players to engage with, and I think directors are still going to want to tell the stories they want to tell.
|
||||||
|
|
||||||
|
So if I'm to sell the idea of free-form speech interaction with characters in the game world, I need an account of how it works with scripted characters voiced by high value actors in a scripted plot. I'm picking Johnny Silverhand as a core example, here, because I think he presents particular challenges.
|
||||||
|
|
||||||
|
But I also think these challenges can be addressed very easily.
|
||||||
|
|
||||||
|
In [Cyberpunk 2077](https://www.cyberpunk.net/), the player can't just go and find Johnny Silverhand, to speak to him. On the contrary, Johnny will just appear when the script calls for him to appear, and when he does he'll always initiate conversation. When a plot NPC initiates conversation with the player, the game could show -- as it does now -- a menu of things the player can say, with the implicit promise that selecting any one of these things will at least bring an interesting response which will expand one's knowledge of that character or of the lore.
|
||||||
|
|
||||||
|
Just as the player does now, the player in a game with free form speech interaction could choose to say one of the things presented in the menu, and the implicit contract -- that this would lead to a new revelation, or would advance the plot -- would remain unchanged. But the player could also choose to go off script, to take the conversation in an unscripted direction, or just to end it.
|
||||||
|
|
||||||
|
It should be said that in Cyberpunk 2077, unlike some other games, the player already has the choice to abruptly break off conversations, even with plot characters, so how the game handles breaking off the conversation does not need to change.
|
||||||
|
|
||||||
|
How should the game handle unscripted responses in scripted dialogues?
|
||||||
|
|
||||||
|
Well, the first and obvious thing is to parse the unscripted response to see whether it's a variant of one of the scripted responses, and if it seems that it might be, perhaps ask the player to verify that:
|
||||||
|
|
||||||
|
> **V**: Just get on with it already.
|
||||||
|
>
|
||||||
|
> **Panam**: You mean, go to the shiv camp?
|
||||||
|
>
|
||||||
|
> **V**: Yes, dammit.
|
||||||
|
|
||||||
|
But the second thing is to respond to the response exactly as the non-player character would if the player had initiated the conversation, using the pipeline given at the beginning of this essay. Of course, in the special case of Johnny Silverhand, he is -- at least initially -- decidedly hostile and extremely selfish, so his response will typically come at step two in the pipeline:
|
||||||
|
|
||||||
|
> **V**: Hey, Johnny, what's the quickest way from here to Jig Jig Street?
|
||||||
|
>
|
||||||
|
> **Johnny**: What am I now, your fucking tour guide?
|
||||||
|
>
|
||||||
|
> **V**: Oh, come on, Johnny, help me out a bit here, Where's the nearest gun dealer?
|
||||||
|
>
|
||||||
|
> **Johnny**: How the fuck should I know? I haven't been here for fifty years, all I know is ancient history.
|
||||||
|
|
||||||
|
The benefit of this interaction style is that these responses could be real acted responses by the voice actor (in this case, Keanu Reeves), which avoids the 'uncanny valley' risk that generated speech from a character the player has become used to interacting with may not sound quite natural enough.
|
||||||
|
|
||||||
|
But, if we've used Lyrebird technology to capture and mimic Reeves' voice, and if Johnny is for some reason uncharacteristically mellow, then generated voice responses should be used. So suppose the player asks something which Johnny ought reasonably to know:
|
||||||
|
|
||||||
|
> **V:** Hey, Johnny, what's between you and Rogue?
|
||||||
|
|
||||||
|
That's lore. It's in at least one of the in-game 'shard' texts. The game engine knows it. A text can be generated for Johnny to respond:
|
||||||
|
|
||||||
|
> **Johnny**: We were lovers, back in the day.
|
||||||
|
|
||||||
|
In any of these cases, in order for the scripted plot to proceed, the non-player character can circle back to the thing they said that the player hasn't yet made an appropriate response to:
|
||||||
|
|
||||||
|
> **Johnny**:But you didn't answer my question. *Repeats question*.
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
> **Johnny**: As I said before, *Repeats what he said before*.
|
||||||
|
|
||||||
|
Again, for key plot characters, the voice actors can actually record multiple different canned texts of this form, so that, when played, they don't sound excessively repetitious.
|
||||||
|
|
||||||
|
In short, it doesn't seem to me that it would be at all hard to integrate free form voice interaction with a modern scripted video game. The advantage is that player interaction with non-player characters would become far richer and more engaging, and consequently it would be much easier to allow the player to progress through the plot without the default outcome of every encounter having to be a blood-bath.
|
|
@ -10,7 +10,18 @@ The structure of a modern Role Playing Came revolves around 'quests': tasks that
|
||||||
6. Syntax quests
|
6. Syntax quests
|
||||||
7. Hybrids
|
7. Hybrids
|
||||||
|
|
||||||
'Gather quests' are more frequently referred to in the literature as 'fetch quests', and 'kill quests' are simply a specialised form of fetch quest where the item to be fetched is a trophy of the kill. A delivery quest is a sort of reverse fetch quest: instead of going to some location or NPC and getting a specific item to return to the quest giver, the player is tasked to take a specific item from the quest giver to some location or NPC.
|
'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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
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.
|
||||||
|
|
||||||
|
@ -45,3 +56,7 @@ Obviously, this doesn't stop you doing jobs you get directly paid/rewarded for,
|
||||||
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.
|
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.
|
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](https://en.wikipedia.org/wiki/Middle-earth:_Shadow_of_Mordor), and [Middle Earth: Shadow of War](https://en.wikipedia.org/wiki/Middle-earth:_Shadow_of_War) have a procedural story system called [Nemesis](https://youtu.be/Lm_AzK27mZY), which is worth a look.
|
112
doc/Populating-a-game-world.md
Normal file
112
doc/Populating-a-game-world.md
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
# Populating a game world
|
||||||
|
|
||||||
|
#### Saturday, 6 July 2013
|
||||||
|
|
||||||
|
*(You might want to read this essay in conjunction with my older essay, [Settling a game world](../../2009/12/settling-game-world.html), which covers similar ground but which this hopefully advances on)*
|
||||||
|
|
||||||
|
For an economy to work people have to be able to move between occupations to fill economic niches. In steady state, non player character (NPC) males become adult as 'vagrants', and then move through the state transitions described in this document. The pattern for females is different.
|
||||||
|
|
||||||
|
## Basic occupations
|
||||||
|
|
||||||
|
The following are 'unskilled' occupations which form the base of the occupation system. Generally a male character at maturity becomes a 'Vagrant' and wanders though the world until he encounters a condition which allows him to advance up the occupation graph. If an occupation wholly fails, the character can revert to being a 'Vagrant' and start again.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Occupation | Dwelling | condition | New trade | Notes |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| Vagrant | None | land available and animals available | Herdsman | |
|
||||||
|
| Vagrant | None | arable land available | Farmer | See crops |
|
||||||
|
| Vagrant | None | has weapons | Outlaw | |
|
||||||
|
| Herdsman | None | Insufficient food | Vagrant | |
|
||||||
|
| Farmer | Farm | Insufficient food | Vagrant | |
|
||||||
|
| Outlaw | None | loses weapons | Vagrant | |
|
||||||
|
| Vagrant | None | craftsman willing to take on apprentice | Apprentice | |
|
||||||
|
| Herdsman | None | arable land available | Farmer | |
|
||||||
|
| Outlaw | None | Battle hardened | OutlawLeader | |
|
||||||
|
| Apprentice | (craftsman's) | Qualified | Journeyman | |
|
||||||
|
| Journeyman | None | Unserviced customers available | Craftsman | See crafts |
|
||||||
|
| Craftsman | See crafts | Too few customers | Journeyman | |
|
||||||
|
| Journeyman | None | arable land available | Farmer | |
|
||||||
|
| Vagrant | None | Lord with vacancies available | Soldier | See military |
|
||||||
|
| OutlawLeader | None | Unprotected farms available | Laird | See nobility |
|
||||||
|
|
||||||
|
|
||||||
|
### Gender dimorphism
|
||||||
|
|
||||||
|
In the paragraph above I said 'a male character'. It may seem unfair to create a game world in which the sexual inequality of the real world is carried over, and for that reason it seems sensible that female children should have the same opportunities as male children. But games work on conflicts and injustices, and so it seems reasonable to me to have a completely different occupation graph for women. I haven't yet drawn that up.
|
||||||
|
|
||||||
|
### Wandering
|
||||||
|
|
||||||
|
Vagrants wander in a fairly random way. While vagrants are wandering they are assumed to live off the land and require no resources. Solitary outlaws similarly wander until they find a leader, although they will avoid the areas protected by nobles. Herdsmen also wander but only over unenclosed pasture. They visit markets, if available, periodically; otherwise, they live off their herds. Journeymen wander from market to market, but are assumed to trade skills with farmers along the way.
|
||||||
|
|
||||||
|
## Crafts
|
||||||
|
|
||||||
|
Crafts are occupations which require acquired skills. In the initial seeding of the game world there are probably 'pioneers', who are special vagrants who, on encountering the conditions for a particular craft to thrive, instantly become masters of that craft.
|
||||||
|
|
||||||
|
|
||||||
|
| Craft | Dwelling | Supplies | Perishable? | Customer types | Needs market? | Customers | Supplier | Suppliers | Recruits |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| | | | | | | Solo | Per journeyman | Per apprentice | | | |
|
||||||
|
| | | | | | | --- | --- | --- | | | |
|
||||||
|
| | | | | | | Min | Max | Min | Max | Min | Max | | | |
|
||||||
|
| --- | | | | | | --- | --- | --- | --- | --- | --- | | | |
|
||||||
|
| Smith | Forge | Metal Items | no | Farmer, Soldier | No | 6 | 10 | 4 | 6 | 1 | 3 | Miner | 1 | Vagrant |
|
||||||
|
| Baker | Bakery | Bread | yes | All NPCs | No | 20 | 30 | 12 | 18 | 6 | 10 | Miller | 1 | Vagrant |
|
||||||
|
| Miller | Mill | Flour, meal | no | Baker, Innkeeper | No | 2 | 3 | 1 | 2 | 1 | 1 | Farmer | 6 | Vagrant |
|
||||||
|
| Weaver | Weaver's house | Cloth | no | All NPCs | Yes | 6 | 10 | 4 | 6 | 1 | 3 | Herdsman | 2 | Vagrant |
|
||||||
|
| Innkeeper | Inn | Food, hospitality | yes | Merhant, Soldier, Farmer, Lord | No | 10 | 20 | 5 | 10 | 2 | 4 | Farmer,Herdsman | 2 | Vagrant |
|
||||||
|
| Miner | Mine | Ores | no | Smith | Yes | 2 | 3 | 1 | 2 | 1 | 1 | Farmer | 1 | Vagrant |
|
||||||
|
| Butcher | Butchery | Meat | yes | All NPCs | No | 10 | 20 | 4 | 8 | 2 | 4 | Farmer, Herdsman | 2 | Vagrant |
|
||||||
|
| Merchant | Townhouse | Transport, logistics | n/a | Craftsmen, nobility | Yes | 10 | 20 | 4 | 8 | 2 | 4 | n/a | n/a | Vagrant |
|
||||||
|
| Banker | Bank | Financial services | yes | Merchant | Yes | 10 | 20 | 4 | 8 | 2 | 4 | n/a | n/a | Merchant |
|
||||||
|
| Scholar | Academy | Knowledge | n/a | Ariston, Tyrranos, General, Banker | No | 1 | 4 | 1 | 2 | 0.25 | 0.5 | n/a | n/a | Vagrant |
|
||||||
|
| Priest | Temple | Religion | n/a | All NPCs | No | 50 | 100 | | | | | | | Scholar |
|
||||||
|
| Chancellor | Chancellory | Administration | n/a | Ariston, Tyrranos | No | 1 | 1 | 0 | 0 | 0 | 0 | | | Scholar |
|
||||||
|
| Lawyer | Townhouse | Legal services | n/a | Ariston, Merchant, Banker | No | 4 | 6 | 2 | 3 | 1 | 2 | | | Scholar |
|
||||||
|
| Magus | Townhouse | Magic | n/a | Tyrranos, General | No | 3 | 4 | 1 | 2 | 0.25 | 0.5 | | | Scholar |
|
||||||
|
|
||||||
|
|
||||||
|
A craftsman starts as an apprentice to a master of the chosen crafts. Most crafts recruit from vagrants, A character must be a journeyman merchant before becoming an apprentice banker, while various intellectual crafts recruit from journeyman scholars.
|
||||||
|
|
||||||
|
It's assumed that a journeyman scholar, presented with the opportunity, would prefer to become an apprentice magus than a master scholar.
|
||||||
|
|
||||||
|
A journeyman settles and becomes a master when he finds a location with at least the solo/min number of appropriate customer type who are not serviced by another master craftsman of the same craft; he also (obviously) needs to find enough free land to set up his dwelling. The radius within which his serviced customers must live may be a fixed 10Km or it may be variable dependent on craft. If there are unserviced customers within his service radius, the master craftsman may take on apprentices and journeymen to service the additional customers up to a fixed limit – perhaps a maximum of four of each, perhaps variable by craft. If the number of customers falls, the master craftsman will first dismiss journeymen, and only in desperate circumstances dismiss apprentices. Every apprentice becomes a journeyman after three years service.
|
||||||
|
|
||||||
|
The list of crafts given here is illustrative, not necessarily exhaustive.
|
||||||
|
|
||||||
|
## Aristocracy
|
||||||
|
|
||||||
|
As in the real world, aristocracy is essentially a protection racket, and all nobles are originally outlaw leaders who found an area with rich pickings and settled down.
|
||||||
|
|
||||||
|
|
||||||
|
| Rank | Follower rank | Client type | Clients protected | Trade in market | Followers per client |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| | | | Min | Max | Min | Max | Min | Max |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| Bonnet Laird | Private | Farmer | 6 | 20 | 0 | 100 | 0.25 | 0.5 |
|
||||||
|
| Ariston | Captain | Bonnet Laird | 10 | 30 | 25 | 1000 | 0.5 | 1 |
|
||||||
|
| Tyrranos | General | Ariston | 10 | unlimited | 250 | unlimited | 0.1 | 0.5 |
|
||||||
|
|
||||||
|
|
||||||
|
Every noble establishes a market and, if he employs a chancellor, taxes trade in it. Crafts which 'need a market' can only be established in the vicinity of a market, irrespective of whether there are sufficient customers elsewhere. All non-perishable goods are traded through the markets, and merchants will transfer surpluses between markets if they can make a profit from it.
|
||||||
|
|
||||||
|
My world has essentially three ranks of nobility. The title of the lowest rank will probably change to something vaguely italianate. An aristocrat advances to the next rank when either the requisite number of clients become available in the locality to support the next rank, or the trade in his market becomes sufficient to support the next rank.
|
||||||
|
|
||||||
|
Obviously when a province has eleven unprotected bonnet lairds, under the rules given above any of them may become the ariston, and essentially it will be the next one to move after the condition becomes true. If the number of available clients drops below the minimum and the market trade also drops below the minimum, the noble sinks to a lower level – in the case of the bonnet laird, to outlaw leader.
|
||||||
|
|
||||||
|
## Military
|
||||||
|
|
||||||
|
The aristocracy is supported by the military. An outlaw becomes a soldier when his leader becomes a noble. Otherwise, vagrants are recruited as soldiers by bonnet lairds or sergeants who have vacancies. Captains are recruited similarly by aristons or generals, and generals are recruited by tyrranos. If the conditions for employment no longer exist, a soldier is allowed a period of unemployment while he lives off savings and finds another employer, but if no employer is found he will eventually become an outlaw (or, if an officer, an outlaw leader). A private is employed by his sergeant or bonnet laird, a sergeant by his captain, a captain by his arison or general, a general by his tyrranos.
|
||||||
|
|
||||||
|
|
||||||
|
| Rank | Follower rank | Followers | | Condition | New rank |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| | | Min | Max | | |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| Private | None | 0 | 0 | Battle hardened, unled privates | Sergeant |
|
||||||
|
| Sergeant | Private | 5 | 15 | More battle hardened, unled sergeantts | Captain |
|
||||||
|
| Captain | Sergeant | 5 | 15 | More battle hardened, unled captains | General |
|
||||||
|
| General | Captain | 5 | unlimited | | |
|
||||||
|
|
||||||
|
|
||||||
|
Soldiers have no loyalty to their employer's employer.
|
87
doc/Settling-a-game-world.md
Normal file
87
doc/Settling-a-game-world.md
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
# Settling a game world
|
||||||
|
|
||||||
|
#### Wednesday, 30 December 2009
|
||||||
|
|
||||||
|
*This essay is part of a series with '[Worlds and Flats](Worlds-and-flats.html)' and '[The spread of knowledge in a large game world](The-spread-of-knowledge-in-a-large-game-world.html)'; if you haven't read those you may want to read them before reading this. This essay describes how a large world can come into being and can evolve. I've written again on this subject since - see '[Populating a game world](Populating-a-game-world.html)')*
|
||||||
|
|
||||||
|
### Microworld
|
||||||
|
|
||||||
|
Some twenty years ago I wrote a rather sophisticated cellular automaton which I called 'Microworld' which modelled the spread of human population over a landscape. It did this by first fractally folding a grid to assign elevations to cells. Then, cells below a critical elevation – the tree line – were assigned as forest. For each cycle – 'year' – a cell remained forest, its soil fertility would increase. Random events – 'lightning strikes' could change a cell from forest to clearing. Then the following transitions might take place, each with a probability, where each cell is considered to have eight neighbours:
|
||||||
|
|
||||||
|
* A forest cell with a lightning strike as a neighbour may catch fire and burn
|
||||||
|
* A forest cell with a fire as a neighbour may catch fire and burn
|
||||||
|
* A burning cell become a clearing cell
|
||||||
|
* A clearing cell with forest or scrub as a neighbour may become scrub
|
||||||
|
* A scrub cell may become forest
|
||||||
|
|
||||||
|
This more or less completes the 'natural' cycle... then we get to settlement. Pastoral and agrarian 1 cells gradually degrade soil fertility (erosion, etc). Agrarian 2 cells do not degrade fertility.
|
||||||
|
|
||||||
|
* A clearing cell (including cells above the treeline) may become a pastoral cell (pastoral 1, no settlement)
|
||||||
|
* A pastoral 1 cell whose soil fertility falls below a threshhold becomes waste
|
||||||
|
* A pastoral 1 cell with no pastoral neighbours may become waste
|
||||||
|
* A waste cell below the treeline may become scrub
|
||||||
|
* A waste cell may become clearing
|
||||||
|
* A pastoral 1 cell with two or more pastoral neighbours may become a pastoral 2 cell (settlement)
|
||||||
|
* A forest cell with two or more pastoral neighbours may become clearing
|
||||||
|
* A pastoral 2 cell with two or more pastoral 2 neighbours may become agrarian 1
|
||||||
|
* An agrarian 1 cell which falls below a critical fertility becomes pastoral 1
|
||||||
|
* An agrarian 1 cell with three or more agrarian 1 neighbours becomes agrarian 2 (smith, mill)
|
||||||
|
* A cell with three or more agrarian 2 neighbours becomes market
|
||||||
|
* A market cell with no agrarian 2, market or urban neighbours becomes waste
|
||||||
|
* A cell with two or more market neighbours becomes urban
|
||||||
|
|
||||||
|
That's simple, but it provides a remarkable good model of population spread. however, it is essentially a grid and so doesn't make for natural-seeming landscapes when considered as a three dimensional rendered world. How can we do better?
|
||||||
|
|
||||||
|
### Microworld Two
|
||||||
|
|
||||||
|
The objective of this essay is to outline an angorithm for creating inhabited landscapes in which games can be set, which are satisfyingly believable when rendered in three dimensions. The objective of creating landscapes 'procedurally' – that is, with algorithms – is that they can be very much larger than designed landscapes for the same richness of local detail. This does not mean that every aspect of the final landscape must be 'procedural'. It would be possible to use the techniques outlined here to create landscapes which were different every time the game was played, but it would be equally possible to create a landscape which was frozen at a particular point and then hand edited to add features useful to the game's plot. And while I'm principally thinking in this about role playing games, this sort of landscape would be applicable to many other sorts of games – strategy games, god games, first person shooters...
|
||||||
|
|
||||||
|
### The physical geography
|
||||||
|
|
||||||
|
Consider our landscape as, once again, a fractally folded sheet on which any given point has characteristics based on its elevation and orientation. There are two critical levels – water level and treeline. The water level is, overall, sea level, but in the case of a localised depression it is equal to the lowest land height between the depression and the sea (lakes form in depressions). Computing the fractal sheet forms stage one in computing the landscape. Next, we need functions which, for any given point on the landscape, compute two different dimensions of soil fertility: water and warmth. We'll assume a coriolis prevailing wind blowing from the west, bringing in damp air from an ocean in that direction. Western slopes are wetter than eastern slopes. In principle, also, there's likely to be a rain shadow to the east of high ground leading to considerable aridity, but that may be too expensive to compute. Rain runs swiftly off steeper slopes, more slowly on flatter ground, so flatter ground is wetter than steeper ground. Water flows down hill, so lower ground is on the whole wetter than higher ground. This isn't a precise model of soil hydrology, but I think it's good enough. From each lake a watercourse follows the lowest possible path to the sea. Watercourses modify the land overwhich they flow, carving out a route at least sufficient to carry the amount of water collected in the watershed above each point. Where watercourses flow down steeper gradients, they carve out gullies, possibly with waterfalls. Where they cross shallower gradients or level ground, they become broader. Computing the watercourses becomes the second stage of computing the lanscape.
|
||||||
|
|
||||||
|
### Vegetation
|
||||||
|
|
||||||
|
Now sprinkle seeds randomly across the landscape at a density of roughly one every ten square metres. Seeds which fall in water, ignore (? or make into water plants?). The position of the plant is taken from the random sprinkling. The species and size of the plant that grows from the plant are a function of the water and warmth functions described above, with latitude and longitude as seeds for pseudo-random functions delivering aspects like branching and so on – enough to make individual plants distinct and not carbon copies even of other plants of the same species, but nevertheless recreatable from just the latitude and longitude. So for each plant only two integers need to be stored, yet every time a player passes he will see an identically recreated world. Of course there is a trade-off between storage space and rendering time, and it may be more efficient to build and cache a detailed model of each plant. Like a lot of other things it depends on the game being designed and the processing power of the platform on which that game is delivered. As to how the functions which select the vegetation type work, obviously trees grow better in wetter places, grassland plants in dryer places; within the wetter places, coniferous trees are more prevalent where it is cooler, broadleaves where it is warmer. In the very wettest places, willows, alders and marshland plants. These plants – the seeded plants – are the feature plants of the landscape. When rendering the landscape the renderer would first apply a suitable local surface texture, for example, in grassland areas, grass.
|
||||||
|
|
||||||
|
### Settling the world
|
||||||
|
|
||||||
|
So now we need to make this an inhabited landscape. My proposal for this is to introduce proto-actors, which may be the same software agents as the non-player characters the user will interact with (see my essay on the spread of knowledge). At this stage in their lifecycle, the proto-actors are fairly simple state transition machines. Generally, their algorithm is as follows: Starting from one or two seed points, proto-agents will initially move across the landscape travelling at most 20Km in a day, preferring to stop at inns or else at existing settlements; and will maintain a history of the places they have been, never revisiting a place until they have settled. Whenever moving, whether before they have settled or after, proto-actors will plan their route across the landscape, avoiding trees, buildings, and steep gradients, and will prefer to cross rivers at a bridge (if available) or else a ferry (if available), or failing that at the narrowest convenient point. When proto-actors settle, they will claim an area of territory appropriate to their trade – more below; the system must build up a database of land holdings. In particular a land holding will never cross a watercourse, an existing road or overlap another land holding (although roads may develop across existing holdings). This is key because I don't want holdings normally to have regular shapes. A settled proto-agent will build a building to live in, and possibly an additional one for his trade. When building buildings, proto-actors will prefer to build at the edge of their land holding, as close as possible to existing buildings and ideally at the side of an existing road. The richer an existing building is, the more attractive it will be to new buildings. Buildings will be built with their long edge aligned with the edge of the owner's hoding.
|
||||||
|
|
||||||
|
* A proto-actor is initially, as described above, an itinerant. Itinerants are introduced into the world at a small number of geographical locations, and gradually, not all at once. Itinerants travel as described above. As they move they will leave breadcrumb trails with a roughly ten metre resolution. If they cross an existing track which goes in roughly the right direction they will prefer to follow it. Once a track has been followed by a certain number of proto-actors, it becomes a road.
|
||||||
|
* An itinerant who finds an area of unsettled grassland of ten hectares with low soil fertility and not more than one hundred trees settles and becomes a pastoralist. He builds a cottage.
|
||||||
|
* An itinerant who finds an area of unsettled grassland of ten hectares with medium or high soil fertility becomes an agrarian. He builds a homestead. Depending on the fertility of his land he can support between zero and ten labourers, 10% of a smith, 10% of a miller and 10% of a bonnet laird.
|
||||||
|
* An itinerant who finds an area of unsettled land of 100 square metres within five hundred metres of a homestead with unfulfilled labourer demand becomes a labourer. He builds a cottage.
|
||||||
|
* An itinerant who finds an area of unsettled land of 100 square metres within five kilometres of ten farmers with unfilled smithing slots becomes a smith. He builds a cottage and a forge.
|
||||||
|
* An itinerant who finds an area of unsettled land either at the side of a water course or at the top of a hill, and within 5 kilometers of ten farmers with unfilled milling slots becomes a miller. He builds a mill – water or wind, appropriate to location.
|
||||||
|
* Any settler who plays host to more than a certain number of travellers becomes an innkeeper. He claims 400 square metres of unclaimed land as close as possible to his existing settlement and buids an inn and stableyard.
|
||||||
|
* An itinerant who finds 400 square metres of unclaimed land within a certain distance of an inn and a smith will become a merchant, provided that there are three smiths within a 5Km radius who have unfilled market slots. The merchant builds a marketplace and a counting house.
|
||||||
|
* An itinerant who finds 200 square metres of unclaimed land within a specified distance of a market with an unfilled chapel slot becomes a priest and builds a chapel and manse, and possibly a school.
|
||||||
|
* An itinerant who finds 100 square metres of unclaimed land adjacent to where a road crosses a river becomes a ferryman.
|
||||||
|
* A ferryman who carries more than a certain number of passengers in a given period becomes a tollkeeper and builds a bridge.
|
||||||
|
|
||||||
|
This set of rules – and possibly others like them (woodcutters, fishermen, hunters...) provide the first wave of settlement. Once the landscape is sufficiently settled by this first wave, there needs to be a period of establishing trading routes. First, every settler will visit his nearest market, leaving a permanent track if there is not already a road. Where several of these tracks overlay one another, once again a road is created. Each merchant then visits each of the ten markets nearest his own, following existing tracks and roads where available. Wherever the merchants do not find roads, new roads are created. This completes the roads network. Each market is now assigned a value which is a function of
|
||||||
|
|
||||||
|
* the number of people for whom it is the nearest market
|
||||||
|
* the sum of the wealth (soil fertility) of the homesteads for which it is the nearest market
|
||||||
|
* the wealth of other markets within a day's travel
|
||||||
|
|
||||||
|
Depending on its wealth a market may support up to twenty stallholders, including bakers, butchers, tanners, weavers, cobblers, chandlers and so on. So a second wave of itinerants sets off. These follow the original rules for itinerants, but if they find an unsettled 100 square metres within five hundred metres of a market, will set up as a stallholder, building a town house and appropriate trade building on their own settlement, and a stall in the market. An itinerant finding a hundred square metres within five hundred metres of a market which has all its stallholder slots filled may become a slum landlord, and build a tenement for day-labourers. Finally, aristocracy. In the second wave an itinerant who finds either a hilltop, an island in a lake or river, or a significant river crossing, with one hectare of unclaimed land and within 5Km of ten farms with unfilled bonnet laird slots becomes a bonnet laird (or 'squire', if you prefer) and builds a fortified house. At the end of the second wave of settlement the ten percent of bonnet lairds with the richest fiefs (using much the same metric as for the wealth of markets) become barons and build castles.
|
||||||
|
|
||||||
|
### Rendering the buildings
|
||||||
|
|
||||||
|
This seems to me to provide an algorithmic means of settling a landscape which will generate organic and satisfying patterns of settlement. But it all fails if the buildings are chosen from a limited palette of models. As with the trees I think we need algorithmic mechanisms of building similar-but-different buildings which can be repeatably rendered from relatively small data sets. As an example of what I mean, in damper landscapes where wood is likely to be available, there might be a higher probability of stave buildings, or weatherboarding, with mainly shingle roofs. In slightly less damp areas where timber is still available, cruck frames and half timbered buildings will prevail, with mostly thatched roofs. In the dryest areas, cob and brick buildings will be common, often with tile roofs. On steeper hillsides, stone buildings will be common, perhaps with slate roofs. Within each of these types there are essential cells from which a building is created. These cells can be longer or shorter, taller or lower, wider or narrower. A building may comprise a single cell, or more. If more than three cells they may be arranged in a row or round a courtyard. And they may have one story or two. Which they have can be based – like the details of the plants – on functions which take latitude and longitude as arguments and which, internally use pseudo-randoms seeded from those latitude and longitude values.
|
||||||
|
|
||||||
|
### How vast a world?
|
||||||
|
|
||||||
|
OK, so, with this general approach, how big can we go? The answer seems to me to be 'big enough'. A 32 bit integer gives somewhat over four billion values, so can resolve down to one millimetre precision in a world 4000 kilometres by 4000 kilometres. But we don't actually need millimetre resolution; centimetre would be quite small enough. And that gives us potential for a world 40000Km square, or 1.6 billion square kilometres, which is three times the surface area of planet Earth.
|
||||||
|
|
||||||
|
In practice we can't go that big for all sorts of space and time reasons. Recording land heights is inevitably an issue. I don't know of a pseudo random function which will generate satisfying land heights. Anything based on Julia sets, for example, ends up with landforms symmetrical around a central point. Furthermore, the shapes of fractals which can be constructed from simple functions tend to have a noticable and unnatural degree of self-similarity across scales. I'd dearly like to be wrong on this, but I think we need to store at minimum elevation values at ten metre intervals. If we can accept 100mm resolution for elevations, storing 16 bit values gives a range of 6,500 metres - 21,000 feet - from the deepest seabed to the peaks of the highest mountains.
|
||||||
|
|
||||||
|
This means that landform information alone requires 20Kbytes per square kilometre - unindexed, but seeing it's a rigid ten metre grid that isn't a problem. Which, in turn, means that you can store landform information for a planet the size of Earth in one terrabyte. But we don't need a planet the size of earth. Scotland is 80,000 square kilometers of land area; allowing for a bit of sea around to the horizon around it, say 100,000 square kilometers. That seems to me more than big enough to be a game space. It amounts to 160Mb of landform data, which is completely manageable.
|
||||||
|
|
||||||
|
If we stored plant data for every distinctive plant in Scotland - even at one per ten square metres - that does become an impractically large data set, because quite apart from anything else, the plant locations do have to be indexed. But actually, given that the actual plants that grow are a function of the location at which they grow, no player is going to notice if the pattern of the locations of plants is the same for each square kilometre. So we can manage plant data for a land area the size of Scotland in 400,000 bytes - we could do it in less (if the locations were generated using a pseudo-random function, much less).
|
||||||
|
|
||||||
|
Building data is different. We need to store the latitude, longitude and type of every building explicitly, and again they need to be indexed in order that we can recover the buildings in a given area efficiently. We need about 16 bytes per building (four bytes latitude, four longitude, two type; then for each tile a null-terminated vector of pointers to building records). If we assume that our feudal land of 80,000 square kilometers has a population of a million, and that there are on average five occupants of every building, that's two hundred thousand buildings, implying 3.2Mb of data.
|
||||||
|
|
||||||
|
Of course, that's just the backing store size. As tiles are loaded into active memory - see the essay 'Tiles and Flats' this raw backing data has to be inflated procedurally into actual models that can be rendered; models which may have thousands of vertices and hundreds of kilobytes of textures. The functions which do that inflating have some finite size, and, significantly, they'll need to work on prototype models which will in turn take storage space. Finally there are hand-edited models potentially used at particular plot locations; those need to be stored more or less in full. But all this has not become an unmanageable amount of data. It seems to me plausible that you could store a fully populated 100,000 square kilometer game world on one uncompressed 700Mb CD. On a 4Gb DVD, you could do it very easily.
|
21
doc/Simulation-layers.md
Normal file
21
doc/Simulation-layers.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
As goods are transported between cities, prices rise and fall based on simulated production and consumption. As prices of commodities rise, more citizens will take up trades which produce those commodities. The simulation needs to be sophisticated enough that, for example, as a city grows richer, its citizens may switch from preferring low cost textiles, eg perhaps wool or linen, to higher cost textiles, such as for example silk (or more complex weaves, or...) Similarly for foodstuffs and for beverages.
|
||||||
|
|
||||||
|
Agricultural production will be affected by climate simulation.
|
||||||
|
|
||||||
|
This is mainly a land game. Broadly, caravans take the place of ships in Elite or Pirates! Caravans are broadly made up of camels, although some may use mules or possibly horses. In any case, a merchant may own camels and hire camel drivers, or may hire contractor drivers who have their own camels; and there will also be whole teams of camel drivers with their animals which can be hired in a single contract.
|
||||||
|
|
||||||
|
## A political simulation
|
||||||
|
|
||||||
|
Broadly, aristons claim territories in an essentiallu feudal arrangement, drive out outlaws, and levy taxes.
|
||||||
|
|
||||||
|
An ariston will be popular if their regime is stable, if taxes are low, justice is considered fair, oppression is low and depredations by outlaws are minimal. The more unpopular an ariston is, the more resistant the populace will be to paying their taxes, meaning the more military force needs to be diverted to tax collection and the greater the oppression. Taxes are required to pay soldiers and to maintain high roads, bridges, markets and other infrastructure. Merchants will prefer to travel routes which are better policed and maintained, which means more merchants trading in your markets, which means more tax.
|
||||||
|
|
||||||
|
Aristons who can generate surplus can hire more soldiers, ascend the feudal hierarchy, and wage war against neighbours.
|
86
doc/The-spread-of-knowledge-in-a-large-game-world.md
Normal file
86
doc/The-spread-of-knowledge-in-a-large-game-world.md
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# The spread of knowledge in a large game world
|
||||||
|
|
||||||
|
#### Saturday, 26 April 2008
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### Note
|
||||||
|
|
||||||
|
_This version of this essay has been adapted to use the code in `the-great-game.gossip.news-items`, [q.v.](the-great-game.gossip.news-items.html). The original version of the essay is [still available on my blog](https://blog.journeyman.cc/2008/04/the-spread-of-knowledge-in-large-game.html)._
|
||||||
|
|
||||||
|
These days we have television, and news. But in a late bronze age world there are no broadcast media. News spreads by word of mouth. If non-player characters are to respond effectively to events in the world, knowledge has to spread.
|
||||||
|
|
||||||
|
How to model this?
|
||||||
|
|
||||||
|
Some non-player characters - doesn't need to be many - are news-spreaders. News-spreaders need to travel. They have to travel even when there are no player characters in the vicinity. But, they don't have to travel very often - once or twice every game day. When a news-spreader is in the immediate vicinity of another character, the pair may (with some degree of randomness) exchange news. There needs to be a hierarchy in the exchange of news, so that 'I-saw' events need to be more likely to be passed on than 'I-heard' events; there needs to be a counter which counts the number of times a knowledge item has been passed on, and also an age counter so that knowledge items are less likely to be passed on as they get older.
|
||||||
|
|
||||||
|
One obvious class of news-spreader is a merchant. Merchant agents can either shuttle mechanically between a fixed group of markets or else possibly respond intelligently to supply and demand. Provided that there is a mesh of merchant routes covering the markets of the game world, and that a useful subset of non-merchant characters are required to visit a market every few game days, this should give a reasonably realistic framework for news spreading.
|
||||||
|
|
||||||
|
What else? What things qualify as news items? I think at least the following:
|
||||||
|
|
||||||
|
* Deaths of sentient characters, especially if violent
|
||||||
|
* Commodity prices
|
||||||
|
* Changes of rulers in cities
|
||||||
|
* Marriages of sentient characters
|
||||||
|
* Plot events, flagged as events by the game designer
|
||||||
|
|
||||||
|
Obviously, news is more valuable if the people involved are important or notorious: the significance of a story is probably the product of the significance of the people concerned.
|
||||||
|
|
||||||
|
So a news item becomes a map with keys similar to
|
||||||
|
|
||||||
|
[:verb :actor :other :location :nth-hand :time-stamp]
|
||||||
|
|
||||||
|
The [exact keys for each verb are specified here](the-great-game.gossip.news-items.html#var-news-topics).
|
||||||
|
|
||||||
|
for example
|
||||||
|
|
||||||
|
{:verb :kill,
|
||||||
|
:actor {:id :fred :name "Fred"},
|
||||||
|
:other {:id :joe :name "Joe"},
|
||||||
|
:location [{45467 78613} :hanshua :plateau],
|
||||||
|
:nth-hand 3,
|
||||||
|
:time-stamp 17946463}
|
||||||
|
|
||||||
|
meaning 'I spoke to a man who'd spoken to a man who said he saw fred kill joe at the game time represented by the time stamp 17946463, at the coordinates {45467 78613} in Hans'hua on the Plateau'. Obviously, the non-player character must be able to construct a natural language sentence from the tuple when speaking within the hearing of a player character, but there's no need for a non-player character to produce a natural language sentence for another non-player character to parse; instead they can just exchange tuples.
|
||||||
|
|
||||||
|
But if we're exchanging knowledge between agents, then agents must have a means of representing knowledge. This knowledge is an association between subjects and sets of statement, such that when the agent learns the statement
|
||||||
|
|
||||||
|
{:verb :kill,
|
||||||
|
:actor {:id :fred :name "Fred"},
|
||||||
|
:other {:id :joe :name "Joe"},
|
||||||
|
:location [{45467 78613} :hanshua :plateau],
|
||||||
|
:nth-hand 3,
|
||||||
|
:time-stamp 17946463}
|
||||||
|
|
||||||
|
it adds this statement (with the 2 incremented to 3) to the set of statements it knows about fred and also to the set of statements it knows about joe. It's possible that the receiving agent could then challenge for further statements about fred and/or joe, the automated equivalent of a 'who's joe?' question.
|
||||||
|
|
||||||
|
There could be feedback in this. Fred's and joe's significance scores could be incremented for each character to whom the statement is passed on, increasing the likeliness that fred, at least, would feature in more news stories in future. There needs also to be some means of managing how the non-player character's attitude to the subjects of the statement are affected. For example, If fred kills joe, and the character (say bill) receiving the news feels positively towards joe, then bill's attitude to fred should become sharply more hostile. If bill feels neutral about joe, then bill's attitude to fred should still become a bit more hostile, since killing people is on the whole a bad thing. But it bill feels very hostile towards joe, then bill's attitude to fred should become more friendly.
|
||||||
|
|
||||||
|
But also, the added knowledge is *degraded*. If the recipient isn't from Hans'hua, the exact location isn't meaningful to them, for example. If the recipient isn't interested in Joe, precisely who was killed may be forgotten. So what is stored could become:
|
||||||
|
|
||||||
|
{:verb :kill,
|
||||||
|
:actor {:id :fred :name "Fred"},
|
||||||
|
:location [:hanshua :plateau],
|
||||||
|
:nth-hand 4,
|
||||||
|
:time-stamp 17946463}
|
||||||
|
|
||||||
|
The timestamp could also be degraded, or lost altother - although how exactly this is represnted I'm not certain. Someone interested in the incident may remember 'it was exactly 17 days ago', whereas someone else may remember that it was 'this month, I think'.
|
||||||
|
|
||||||
|
Obviously the rate of decay, and the degree of randomness, of the news-passing algorithm would need to be tuned, but this schema seems to me to describe a system with the following features:
|
||||||
|
|
||||||
|
* Non-player characters can respond to questions about significant things which happen in the world - without it all having to be scripted
|
||||||
|
* If you travel fast enough, you can keep ahead of your notoriety
|
||||||
|
* Characters on major trade routes will know more about what is happening in the world than characters in backwaters
|
||||||
|
|
||||||
|
This seems to me a reasonably good model of news spread.
|
||||||
|
|
||||||
|
### Scaling of the algorithm
|
||||||
|
|
||||||
|
Let's work around the idea that a 'game day' equates to about two hours of wall clock time. Let's work around the idea that there are of the order of fifty markets in the game world, and that for each market there are two or three merchants whose 'home base' it is.
|
||||||
|
|
||||||
|
Obviously non-player characters who are within the vicinity of a player character have to be 'awake', in order that the player can see them interacting with their world and can interact with them. Those characters have to be in working memory and have to be in the action polling loop in any case. So there's no extra cost to their gossiping away between each other - around the player there's a moving bubble of gossip, allowing each character the player interacts with to have a high probability of having some recent news.
|
||||||
|
|
||||||
|
But the merchants who aren't in the vicinity of a player don't have to be in working memory all the time. Each merchant simply requires to be 'woken up' - loaded into memory - once per game day, move a day's journey in one hop, and then, if arriving at an inn or at a market, wake and exchange news with one resident character - an innkeeper or a gossip. So the cost of this algorithm in a fifty-market game is at worst the cost of loading and unloading two non-player characters from memory every minute, and copying two or three statements from the knowledge set of one to the knowledge set of the other. If you're dynamically modifying significance scores, of course, you'd need to also load the characters about whom news was being passed on; but this still doesn't seem unduly onerous.
|
||||||
|
|
||||||
|
Obviously, if memory is not too constrained it may be possible to maintain all the merchants, all the innkeepers and all the characters currently being talked about in memory all the time, further reducing the cost.
|
9
doc/Uncanny_dialogue.md
Normal file
9
doc/Uncanny_dialogue.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# 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](https://youtu.be/NmLPpcVQFJM) and [Twitter thread](https://twitter.com/GameAnim/status/844961601732018176) about the perceived failings in the character animation system of Mass Effect Andromeda.
|
||||||
|
|
||||||
|
This gets even more problematic if, rather than heavily signposting the player towards locations where plot points will happen, we allow the player to roam the world relatively freely, and cause plot events to occur where the player is at the appropriate phase in the plot rather than when the player arrives at a particular location. This not only means that important plot beats will happen in unpredictable locations but also that we may have to dynamically assign the non-player character(s) who interact with the player character in order to deliver the plot point.
|
90
doc/Voice-acting-considered-harmful.md
Normal file
90
doc/Voice-acting-considered-harmful.md
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
# Voice acting considered harmful
|
||||||
|
|
||||||
|
#### Wednesday, 25 February 2015
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Long, long, time ago, I can still remember when... we played (and wrote) adventure games where the user typed at a command line, and the system printed back at them. A Read-Eval-Print loop in the classic Lisp sense, and I wrote my adventure games in Lisp. I used the same opportunistic parser whether the developer was building the game
|
||||||
|
Create a new room north of here called dungeon-3 the player was playing the game
|
||||||
|
Pick up the rusty sword and go north or the player was talking to a non-player character
|
||||||
|
Say to the wizard 'can you tell me the way to the castle' Of course, the parser didn't 'understand' English. It worked on trees of words, in which terminal nodes were actions and branching nodes were key words, and it had the property that any word it didn't recognise at that point in sentence was a noise word and could be ignored. A few special hacks (such as 'the', 'a', or 'an' was an indicator that what came next was probably a noun phrase, and thus that if there was more than one sword in the player's immediate environment the one that was wanted was the one tagged with the adjective 'rusty'), and you ended up with a parser that most of the time convincingly interpreted most of what the player threw at it.
|
||||||
|
|
||||||
|
Text adventures fell into desuetude partly because they weren't graphic, but mainly because people didn't find typing natural, or became dissatisfied with the repertoire of their parsers. Trying to find exactly the right combination tokens to persuade the game to carry out some simple action is not 'fun', it's just frustrating, and it turned people off. Which is a shame because just at the time when people were abandoning text adventures we were beginning to have command parsers which were actually pretty good. Mine, I think, were good - you could have a pretty natural conversation with them, and in 'building' mode, when it hit a 'sorry I don't understand' point, it allowed you to input a path of keywords and a Lisp function so that in future it would understand.
|
||||||
|
|
||||||
|
So much, so [Eliza](http://www.csee.umbc.edu/courses/331/papers/eliza.html).
|
||||||
|
|
||||||
|
Modern role-playing games - the evolutionary successors of those high and far off text adventures - don't have text input. Instead, at each stage in a conversation, the user is offered a choice of three or four canned responses, and can pick one; very often what the player's character actually says then differs from the text the user has chosen, often with differences of nuance which the user feels (s)he didn't intend. And the non-player-character's response is similarly canned. Indeed, the vast majority of non-player characters in most games have a 'repertoire', if one may call it that, of only one sentence. Others will have one shallow conversational tree, addressing one minor quest or plot-point.
|
||||||
|
|
||||||
|
If you want to talk to them about anything else - well, you just can't.
|
||||||
|
|
||||||
|
Only a very few key non-player characters will have a large repertoire of conversational trees, relevant to all parts of the plot. And even those trees are not deep. You soon exhaust them; the characters' ability to simulate real agency just isn't there.
|
||||||
|
|
||||||
|
I first wrote about the limiting effects of voice acting in [my review of the original Witcher game](../../2008/02/the-witcher-story-telling-of-high-order.html), back in 2008; things haven't got better.
|
||||||
|
|
||||||
|
|
||||||
|
## On phones: speaking
|
||||||
|
|
||||||
|
In my pocket I carry a phone. It's not big: 127 x 64.9 x 8.6mm. A small thing.
|
||||||
|
|
||||||
|
When I first used Android phones for navigation, I used to delight in their pronunciation of Scots placenames - pronouncing them phonetically, as spelled, and as though their spelling were modern English. What's delightful about Scots placenames is that they are linguistically and orthographically so varied - their components may be Brythonic, Goidaelic, Anglian, Norn, French, English, or even Latin; and very frequently they combine elements of more than one language (Benlaw Hill, anyone? Derrywoodwachy?).
|
||||||
|
|
||||||
|
Yes, gentle reader, this does seem a long way from game design; be patient, I'm getting there. But I'm going to digress even further for first...
|
||||||
|
|
||||||
|
There have been orthographic changes, and pronunciation changes consequent on orthographic changes. For example, medieval Scots used the letter [Yogh](http://en.wikipedia.org/wiki/Yogh) (ȝ), which isn't present in the English alphabet. So when Edinburgh printers in the early modern period bought type for their printing presses from England, there was no Yogh in the font. So they substituted Zed. So we get names like Dalȝiel, Kirkgunȝeon, Menȝies, Cockenȝie. How do you pronounce them?
|
||||||
|
|
||||||
|
The letter that looks like a 'z' is pronounced rather like a 'y'; so
|
||||||
|
|
||||||
|
* Deeyell
|
||||||
|
* Kirkgunyeon
|
||||||
|
* Mingis
|
||||||
|
|
||||||
|
and... drumroll...
|
||||||
|
|
||||||
|
* Cockenzie.
|
||||||
|
|
||||||
|
What happened?
|
||||||
|
|
||||||
|
Well, Dalȝiel and Menȝies are personal names, and people are protective of their own names. Kirkgunȝeon is a small, unimportant place, and all the locals know how it is pronounced. Scots folk, are, after all, used to Scots orthography and its peculiarities. So those names haven't changed.
|
||||||
|
|
||||||
|
But at Cockenȝie, another small, unimportant place, a nuclear power station was built. The nuclear power station was built by people (mostly) from England, who didn't know about Yogh or the peculiarities of Scots orthography - and were possibly too arrogant to care. So they called it 'Cockenzie'. And as there were so many more of them and they had so much higher status than the locals, their name stuck, and nowadays even local people mostly say 'Cockenzie', as though it were spelled with a Zed. Because, of course, it is spelled with a Zed. Because, as any British schoolchild knows, there's no Yogh in the alphabet.
|
||||||
|
|
||||||
|
Except, of course, when there is.
|
||||||
|
|
||||||
|
Another more interesting example of the same thing is '[Kirkcudbright](http://www.journeyman.cc/placenames/place?id=153)'. It's a town built around the kirk (church) of saint Cuthbert. So how does it come to have a 'd' in it? And why is it pronounced 'Kirkoobry'? Well, the venerable Cuthbert pronounced his name in a way which would be represented in modern English as 'Coothbrecht', but he spelled it 'Cuðbrecht'. See that 'ð'? That's not a 'd', it's an Eth. Because Cuðbrecht was Anglian, and the Anglian alphabet had [Eth](http://en.wikipedia.org/wiki/Eth); it's pronounced as a soft 'th', and Icelandic still has it (as well as Thorn, þ, a hard 'th' sound). Medieval scribes didn't know about Eth, so in copying out ð they wrote the more familiar d. The local people, however, mostly couldn't read, so the pronunciation of the name didn't change with the change in spelling (although the pronunciation, too, has drifted a little with time).
|
||||||
|
|
||||||
|
So, in brief, pronouncing Scots placenames is hard, and there are a lot of curious rules, and consequently it's not surprising that five years ago, listening to Android's pronunciation of Scots placenames was really funny.
|
||||||
|
|
||||||
|
But what's really curious is that now it isn't. Now, it rarely makes a mistake. Now, Android can do text to speech on unusual and perverse orthography, and get it right better than 95% of the time - and manage a reasonably natural speaking voice while doing so. On a small, low power machine which fits in my pocket.
|
||||||
|
|
||||||
|
|
||||||
|
## On phones: listening
|
||||||
|
|
||||||
|
But navigation is not all I can do with my phone. I can also dictate. By which I don't mean I can make a voice recording, play it back later and type what I hear, although, of course, I can. I mean I can dictate, for example, an email, and see it in text on my phone before I send it. It quickly learned my peculiarities of diction, and it now needs very little correction. On a small, low power machine which fits in my pocket.
|
||||||
|
|
||||||
|
|
||||||
|
## And breathe
|
||||||
|
|
||||||
|
Right, so where am I going with all this? Well, we interact with modern computer role playing games through very restricted, entirely scripted dialogues. Why do we do so? Why, on our modern machines with huge amounts of store, do our non-player characters - and worse still, our player character, our own avatar - have such restricted repertoires?
|
||||||
|
|
||||||
|
Because they are voice acted. Don't get me wrong, voice acting makes a game far more engaging. But for voice acting to work, the people doing the acting have to know not only the full range of sentences that their character is going to speak, but also roughly how they feel (angry? sad? excited?) when they say it. Ten years ago, voice acting was probably the only way you could have got this immediacy into games, because ten years ago, text-to-speech systems were pretty crude - think of Stephen Hawking's voice synthesiser. But now, Edinburgh University's [open source synthesiser](http://www.cstr.ed.ac.uk/projects/festival/morevoices.html) is pretty good, and comes with twenty-four voices (and seeing it's open source, you can of course add your own). Speech to text was probably better ten years ago - think of [Dragon Naturally Speaking](http://en.wikipedia.org/wiki/Dragon_NaturallySpeaking) - but it was proprietary software, and used a fair proportion of a machine's horsepower. Now there's (among others) Carnegie Mellon's open source [Sphinx](http://cmusphinx.sourceforge.net/) engine, which can quickly adapt to your voice.
|
||||||
|
|
||||||
|
So, we have text-to-speech engines which can generate from samples of many different voices, and speech to text engines which can easily be tuned to your particular voice. There's even a program called [Voice Attack](http://www.voiceattack.com/), built on top of Microsoft's proprietary speech to text engine, which already allows you to [control games with speech](https://www.youtube.com/watch?v=8dnJ--pSjdE). Where does that take us?
|
||||||
|
|
||||||
|
Well, we already know how to make sophisticated natural language parsers for text, given moderately limited domains - we don't need full natural language comprehension here.
|
||||||
|
|
||||||
|
|
||||||
|
## You may think it's a long way down the road to the chemist
|
||||||
|
|
||||||
|
There are things one needs to know in a game world. For example: I need a sword, where's the nearest swordsmith? In a real quasi-medieval world, certainly every soldier would be able to tell you, and everyone from the swordsmith's town or village. Very celebrated swordsmiths would be known more widely.
|
||||||
|
|
||||||
|
And the thing is, the game engine knows where the nearest swordsmith is. It knows what potion will heal what wound, and what herbs and what tincture to use to make it. It knows which meats are good to eat, and which inns have rooms free. It knows good campsites. It knows where there be dragons. It knows where the treasure is hid. It knows - as far as the game and its plot are concerned - everything.
|
||||||
|
|
||||||
|
So to make an in-game Siri - an omniscient companion you could ask anything of - would be easy. Trivial. It also wouldn't add verisimilitude to the game. But to model which non-player characters know what is not that much harder. Local people know what's where in their locality. Merchants know the prices in nearby markets. They, and minstrels, know the game-world's news - major events that affect the plot. Apothecaries, alchemists and witches know the properties of herbs and minerals.
|
||||||
|
|
||||||
|
And to model which non-player characters are friendly, and willing to answer your every question; which neutral or busy, and liable to answer tersely; and which actively hostile, and likely, if they answer at all, to deliberately mislead - that's not very much harder.
|
||||||
|
|
||||||
|
I'm not arguing that voice acting, and scripted dialogue trees, should be done away with altogether. They still have a use, as cutscenes do, to advance plot. And I'm not suggesting that we use voice to control the player characters movements and actions - I'm not not suggesting that we should say 'run north; attack the troll with the rusty sword'. Keyboards and mice may be awkward ways to control action, but they're better than that. Bur I am suggesting that one should be able to talk to any (supposedly sentient) character in the game, and have them talk reasonably sensibly back. As one can already do physically in wandering an open world, a full voice interaction system would allow one to go off piste - to leave the limited, constrained pre-scripted interaction of the voice-acted dialogue tree. And that has got to make our worlds, and our interactions with them, richer, more surprising, more engaging.
|
||||||
|
|
||||||
|
A hybrid system needn't be hard to achieve, needn't be jarring in use. You can record the phonemes of your voice actor's voice, so that the same character will have roughly the same voice - the same timbre, the same vowel sounds, the same characteristics of pronunciation - whether in a voice acted dialogue or in a generated one.
|
||||||
|
|
||||||
|
We don't need to let voice acting limit the repertoires of our characters any more. And we shouldn't.
|
|
@ -1,6 +1,6 @@
|
||||||
# Game world economy
|
# Game world economy
|
||||||
|
|
||||||
Broadly this essay extends ideas presented in [Populating a game world](https://blog.journeyman.cc/2013/07/populating-game-world.html), q.v.
|
Broadly this essay extends ideas presented in [Populating a game world](Populating-a-game-world.html), q.v.
|
||||||
|
|
||||||
## Primary producers
|
## Primary producers
|
||||||
|
|
||||||
|
|
16
doc/intro.md
16
doc/intro.md
|
@ -19,7 +19,7 @@ that I need to be able to use it to tell stories, in order to create initial
|
||||||
threads of narrative from which players can start their exploration.
|
threads of narrative from which players can start their exploration.
|
||||||
|
|
||||||
Note that, by 'conflict', here, I explicitly do not mean 'killing people',
|
Note that, by 'conflict', here, I explicitly do not mean 'killing people',
|
||||||
or even 'killing non-player characters'. I have [written extensively](https://blog.journeyman.cc/2015/02/voice-acting-considered-harmful.html)
|
or even 'killing non-player characters'. I have [written extensively](Voice-acting-considered-harmful.html)
|
||||||
about the problem in many current video games that all too often the only
|
about the problem in many current video games that all too often the only
|
||||||
way of interacting with non-player characters is to kill them. Killing
|
way of interacting with non-player characters is to kill them. Killing
|
||||||
people should be one of the potential ways of resolving conflicts, because
|
people should be one of the potential ways of resolving conflicts, because
|
||||||
|
@ -32,18 +32,18 @@ repertoire of speech.
|
||||||
|
|
||||||
## Previous essays that are relevant
|
## Previous essays that are relevant
|
||||||
|
|
||||||
* [The spread of knowledge in a large game world](https://blog.journeyman.cc/2008/04/the-spread-of-knowledge-in-large-game.html) (2008) discusses what individual non-player characters know, and how to model dynamic updates to their knowledge;
|
* [The spread of knowledge in a large game world](The-spread-of-knowledge-in-a-large-game-world.html) (2008) discusses what individual non-player characters know, and how to model dynamic updates to their knowledge;
|
||||||
* [Settling a game world](https://blog.journeyman.cc/2009/12/settling-game-world.html) (2009) gives rough outline of ideas about creating the environment, including modelling things like soil fertility, local building materials, and consequently local architecture;
|
* [Settling a game world](https://blog.journeyman.cc/2009/12/settling-game-world.html) (2009) gives rough outline of ideas about creating the environment, including modelling things like soil fertility, local building materials, and consequently local architecture;
|
||||||
* [Tessellated multi-layer height map](https://blog.journeyman.cc/2013/07/tessellated-multi-layer-height-map.html) (2013) gives ideas for how a designed geography for a very large world could be stored relatively economically;
|
* [Tessellated multi-layer height map](https://blog.journeyman.cc/2013/07/tessellated-multi-layer-height-map.html) (2013) gives ideas for how a designed geography for a very large world could be stored relatively economically;
|
||||||
* [Genetic Buildings](https://blog.journeyman.cc/2013/07/genetic-buildings.html) (2013) sketches algorithms which would allow procedurally-generated buildings to be site-appropriate, broadly variable and reproducable;
|
* [Genetic Buildings](https://blog.journeyman.cc/2013/07/genetic-buildings.html) (2013) sketches algorithms which would allow procedurally-generated buildings to be site-appropriate, broadly variable and reproducable;
|
||||||
* [Populating a game world](https://blog.journeyman.cc/2013/07/populating-game-world.html) (2013) provides outline algorithms for how a world can be populated, and how organic mixes of trades and crafts can be modelled;
|
* [Populating a game world](Populating-a-game-world.html) (2013) provides outline algorithms for how a world can be populated, and how organic mixes of trades and crafts can be modelled;
|
||||||
* [Modelling the change from rural to urban](https://blog.journeyman.cc/2013/07/modelling-change-from-rural-to-urban.html) (2013) describes the idea of procedurally modelling settlements, but it is grid-based and not particularly satisfactory and has largely been superceded in my thinking;
|
* [Modelling the change from rural to urban](https://blog.journeyman.cc/2013/07/modelling-change-from-rural-to-urban.html) (2013) describes the idea of procedurally modelling settlements, but it is grid-based and not particularly satisfactory and has largely been superceded in my thinking;
|
||||||
* [Of pigeons, and long distance messaging in a game world]() (2013) builds on ideas about flows of information;
|
* [Of pigeons, and long distance messaging in a game world](https://blog.journeyman.cc/2013/10/of-pigeons-and-long-distance-messaging.html) (2013) builds on ideas about flows of information;
|
||||||
* [Modelling rural to urban, take two](https://blog.journeyman.cc/2013/10/modelling-rural-to-urban-take-two.html) (2013) revisited the idea of modelling organic settlement structures, trying to find algorithms which would naturally produce more persuasive settlement models, including further ideas on the procedural generation of buildings;
|
* [Modelling rural to urban, take two](https://blog.journeyman.cc/2013/10/modelling-rural-to-urban-take-two.html) (2013) revisited the idea of modelling organic settlement structures, trying to find algorithms which would naturally produce more persuasive settlement models, including further ideas on the procedural generation of buildings;
|
||||||
* [More on modelling rivers](https://blog.journeyman.cc/2014/09/more-on-modelling-rivers.html) (2014) talks about modelling hydrology, with implications for soil fertility;
|
* [More on modelling rivers](https://blog.journeyman.cc/2014/09/more-on-modelling-rivers.html) (2014) talks about modelling hydrology, with implications for soil fertility;
|
||||||
* [Modelling settlement with cellular automata](https://blog.journeyman.cc/2014/08/modelling-settlement-with-cellular.html) (2014) talks about successful implementation of algorithms to model vegetative environment, human settlement and the impact of human settlement on the environment;
|
* [Modelling settlement with cellular automata](https://blog.journeyman.cc/2014/08/modelling-settlement-with-cellular.html) (2014) talks about successful implementation of algorithms to model vegetative environment, human settlement and the impact of human settlement on the environment;
|
||||||
* [Voice acting considered harmful](https://blog.journeyman.cc/2015/02/voice-acting-considered-harmful.html) (2015) outlines the ideas behind full speech interaction with non-player characters, and modelling what those non-player characters should be able to speak about;
|
* [Voice acting considered harmful](https://blog.journeyman.cc/2015/02/voice-acting-considered-harmful.html) (2015) outlines the ideas behind full speech interaction with non-player characters, and modelling what those non-player characters should be able to speak about;
|
||||||
* [Baking the world](https://blog.journeyman.cc/2019/05/baking-world.html) (2019) an outline of the overall process of creating a world.
|
* [Baking the world](Baking-the-world.html) (2019) an outline of the overall process of creating a world.
|
||||||
|
|
||||||
## Organic and emergent game-play
|
## Organic and emergent game-play
|
||||||
|
|
||||||
|
@ -183,8 +183,8 @@ easy:
|
||||||
So each agent is assigned - by the dreaded random number generator - one top
|
So each agent is assigned - by the dreaded random number generator - one top
|
||||||
level goal when they are instantiated. I don't think it's necessary to model
|
level goal when they are instantiated. I don't think it's necessary to model
|
||||||
change of top level goals, although of course that does happen in real life;
|
change of top level goals, although of course that does happen in real life;
|
||||||
however, although each agent has one top level goal, they will have lower l
|
however, although each agent has one top level goal, they will have lower
|
||||||
evel 'stretch goals' also taken from this list: so at each decision point in
|
level 'stretch goals' also taken from this list: so at each decision point in
|
||||||
an agent's planning loop, if base level needs are satisfied and progress on
|
an agent's planning loop, if base level needs are satisfied and progress on
|
||||||
the top level goal is blocked, actions should be chosen which progress one
|
the top level goal is blocked, actions should be chosen which progress one
|
||||||
of the lower goals. Indeed, it's possible that all agents could have all
|
of the lower goals. Indeed, it's possible that all agents could have all
|
||||||
|
@ -379,4 +379,4 @@ Each game day, every habitual traveller within the 'local' gossip bubble
|
||||||
exchanges some items of gossip with the nearest innkeeper to their current
|
exchanges some items of gossip with the nearest innkeeper to their current
|
||||||
location. In the second and third gossip bubbles, it's probably only more
|
location. In the second and third gossip bubbles, it's probably only more
|
||||||
favoured gossip agents who do this. See
|
favoured gossip agents who do this. See
|
||||||
[The spread of knowledge in a large game world](https://blog.journeyman.cc/2008/04/the-spread-of-knowledge-in-large-game.html)
|
[The spread of knowledge in a large game world](The-spread-of-knowledge-in-a-large-game-world.html)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
In a dynamic pre-firearms world with many small states and contested regions, trade is not going to be straightforward. Not only will different routes have different physical characteristics - more or less mountainous, more or fewer unbridged river crossings - they will also have different political characteristics: more of less taxed, more or less effectively policed.
|
In a dynamic pre-firearms world with many small states and contested regions, trade is not going to be straightforward. Not only will different routes have different physical characteristics - more or less mountainous, more or fewer unbridged river crossings - they will also have different political characteristics: more of less taxed, more or less effectively policed.
|
||||||
|
|
||||||
Raids by outlaws are expected to be part of the game economy. News of raids are the sort of things which may propagate through the [[gossip]] system. So are changes in taxation regime. Obviously, knowledge items can affect merchants' trading strategy; in existing prototype code, individual merchants already each keep their own cache of known historical prices, and exchange historical price data with one another; and use this price data to select trades to make.
|
Raids by outlaws are expected to be part of the game economy. News of raids are the sort of things which may propagate through the [gossip](the-great-game.gossip.gossip.html) system. So are changes in taxation regime. Obviously, knowledge items can affect merchants' trading strategy; in existing prototype code, individual merchants already each keep their own cache of known historical prices, and exchange historical price data with one another; and use this price data to select trades to make.
|
||||||
|
|
||||||
So: to what extent is it worth modelling the spread of knowledge of trade cost and risk?
|
So: to what extent is it worth modelling the spread of knowledge of trade cost and risk?
|
||||||
|
|
||||||
|
|
66
doc/sandbox.md
Normal file
66
doc/sandbox.md
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# Sandbox
|
||||||
|
|
||||||
|
Up to now I've been thinking of the Great Game as essentially an RPG with some sandbox-like elements; but I think it may be better to think of it as a sandbox game with some RPG like elements.
|
||||||
|
|
||||||
|
Why?
|
||||||
|
|
||||||
|
The core of the game is a world in which non-player characters have enough individual knowledge of the world and their immediate surroundings that they can sensibly answer questions like
|
||||||
|
|
||||||
|
* Where is the nearest craftsman of this craft?
|
||||||
|
* What price can I expect to get for this item in the local market?
|
||||||
|
* What news have you heard recently?
|
||||||
|
* Where does this person from your village live?
|
||||||
|
|
||||||
|
and where there's a sufficiently sophisticated and robust economy simulation that buying goods in one market and selling them in another is viable.
|
||||||
|
|
||||||
|
The original BBC Micro space trading game Elite had very little more in terms of game mechanics than a sandbox with a means to navigate it and an economy simulation, which wasn't even nearly as sophisticated as the one I have working now. Yet that combination resulted in engaging game play.
|
||||||
|
|
||||||
|
## Main sandbox roles
|
||||||
|
|
||||||
|
The idea of a sandbox is that the player character should be able to do pretty much anything they like within the mechanics of the game. From that, it seems to me reasonable that the player ought to be able to do more or less everything a non-player character can do. But creating the game mechanics to make each additional task doable takes time and investment, so there's a need to prioritise.
|
||||||
|
|
||||||
|
So, as Elite did, I propose to make the first available sandbox roles
|
||||||
|
|
||||||
|
### Merchant
|
||||||
|
|
||||||
|
Someone who travels from city to city, buying goods cheap in one and selling them for more in another; and
|
||||||
|
|
||||||
|
### Outlaw
|
||||||
|
|
||||||
|
Someone who intercepts and steals from merchants (and may also attack outlying farms and villages)
|
||||||
|
|
||||||
|
## Second tier playable roles
|
||||||
|
|
||||||
|
The next tier of playable roles rotates around issues arising from the mercantile ecosystem.
|
||||||
|
|
||||||
|
### Aristocracy
|
||||||
|
|
||||||
|
Aristocrats are basically settled outlaws who seek to establish a monopoly on extracting taxes from inhabitants and travellers in a particular region by driving out all other outlaws. Within the comain of an aristocrat, you have to pay tax but you're reasonably safe from being attacked by other outlaws and losing everything. Aristocrats may also maintain and improve roads and bridges and do other things to boost the economy of their territory, may expant into adjoining territory with no current aristocratic control, and may wage war on other aristocrats.
|
||||||
|
|
||||||
|
An outlaw ought to be able to become an aristocrat, by dominating an ungoverned area or by defeating an existing aristocrat.
|
||||||
|
|
||||||
|
### Soldiery
|
||||||
|
|
||||||
|
Soldiers, like aristocrats, are basically on the same spectrum as outlaws. Outlaws may hire themselves out to merchants as caravan guards, or to aristocrats as soldiers. Soldiers or guards, falling on bad times, may revert to outlawry.
|
||||||
|
|
||||||
|
## Routine, Discretion and Playability
|
||||||
|
|
||||||
|
There's a term that's used in criticism of many computer games which is worth thinking about hard here: that term is 'farming'. 'Farming', in this sense, is doing something repetitive and dull to earn credits in a game. Generally this is not fun. What makes roles in a game-world fun is having individual discretion - the ability to choose between actions and strategies - and a lack of routine.
|
||||||
|
|
||||||
|
Most craft skills - especially in the learning phase - are not like this, and crafts which are sophisticated enough to be actually engaging are very hard to model in a game. Learning a craft is essentially, inherently, repetitive and dull, and if you take that repetition out of it you probably don't have enough left to yield the feeling of mastery which would reward success; so it doesn't seem to me that making craft roles playable should be a priority.
|
||||||
|
|
||||||
|
## Cruise control
|
||||||
|
|
||||||
|
One of the most enjoyable aspects of The Witcher 3 - still my go-to game for ideas I want to improve on - is simply travelling through the world. Although fast travel is possible I find I rarely use it, and a journey which takes fifteen minutes of real world wall clock time can be enjoyable in and of itself. This is, of course, a credit to the beautiful way the world is realised.
|
||||||
|
|
||||||
|
But nevertheless, in The Witcher 3, a decision was made to pack incident fairly densely - because players would find just travelling boring. This leads to a situation where peaceful villages exist two minutes travel from dangerous monsters or bandit camps, and the suspension of disbelief gets a little strained. Building a world big enough that a market simulation is believable means that for the individual, the travel time to a market where a particular desired good is likely to be cheaper becomes costly in itself. Otherwise, there's no arbitrage between markets and no ecological niche for a merchant to fill. The journey time from market to market has to be several in-game days.
|
||||||
|
|
||||||
|
An in-game day doesn't have to be as long as a wall clock day, and, indeed, typically isn't. But nevertheless, doing several game days of incident-free travel, even in beautiful scenery, is not going to be engaging - which implies a fast-travel mechanic.
|
||||||
|
|
||||||
|
I don't like fast travel, I find it a too-obvious breaking of immersion. Also, of course, one of the interesting things about a game in a merchant/outlaw ecosystem is the risk of interception on a journey. The Dragon Age series handled interrupted travel in 'fast travel' by randomly interrupting the loading screen you get when moving from location to location in Dragon Age's patchwork worlds by dumping you into a tiny arena with enemies. That's really, really bad - there's no other way to say this. Everything about it shouts artifice.
|
||||||
|
|
||||||
|
So I'm thinking of a different mechanism: one I'm calling cruise control.
|
||||||
|
|
||||||
|
You set out on a task which will take a long time - such as a journey, but also such as any routine task. You're shown either a 'fast forward' of your character carrying out this task, or a series of cinematic 'shots along the way'. This depends, of course, on there being continuous renderable landscape between your departure and your destination, but there will be. This fast-forward proceeds at a substantially higher time gearing than normal game time - ten times as fast perhaps; we need it to, because as well as doing backgound scenery loading to move from one location to another, we're also simulating lots of non-player agents' actions in parts of the world where the player currently isn't. So a 'jump cut' from one location to another isn't going to work anyway.
|
||||||
|
|
||||||
|
The player can interrupt 'fast forward' at any time. But also, the game itself may bring you out of fast forward when it anticipates that there may be action which requires decision - for example, when there are outlaws in the vicinity. And it will do this **before** the player's party is under immediate attack - the player will have time to take stock of the situation and prepare appropriately. Finally, this will take place in the full open world; the player will have the option to choose *not* to enter the narrow defile, for example, to ask local people (if there are any) for any news of outlaw activity, or, if they are available, to send forward scouts.
|
|
@ -26,7 +26,7 @@ Another significant point is that women's ability to bear children ceases at a m
|
||||||
|
|
||||||
## Why have sex at all?
|
## Why have sex at all?
|
||||||
|
|
||||||
If a character has 'having children' - the **Ancestor** aspiration, in my typology - as their key aim, then they will want to have sex. But to have children in this sense is to have acknowledged children, so while a male character may be motivated to have multiple female partners, he will never the less have some degree of long term committment to them, and will want both to feel confident that the children are his and to be recognised by their father.
|
If a character has 'having children' - the [**Ancestor**](intro.html#aspirations-and-goals) aspiration, in my typology - as their key aim, then they will want to have sex. But to have children in this sense is to have acknowledged children, so while a male character may be motivated to have multiple female partners, he will never the less have some degree of long term committment to them, and will want both to feel confident that the children are his and to be recognised by their father.
|
||||||
|
|
||||||
From the point of view of seeking to become an Ancestor, there is little benefit to the woman in having multiple partners, except in very harsh environments. It will be easier to give one partner confidence that all your children are his, and while a man can increase his number of potential progeny by having multiple wives, mistresses or other classes of long-term female sexual partners, a woman cannot.
|
From the point of view of seeking to become an Ancestor, there is little benefit to the woman in having multiple partners, except in very harsh environments. It will be easier to give one partner confidence that all your children are his, and while a man can increase his number of potential progeny by having multiple wives, mistresses or other classes of long-term female sexual partners, a woman cannot.
|
||||||
|
|
||||||
|
@ -42,4 +42,4 @@ Sex, done right, is an extremely pleasant pastime. Sex can also be used to creat
|
||||||
|
|
||||||
For women, sex with other women carries with it no risk of pregnancy, so can be enjoyed or used for any of these purposes in very much the same way as it can by men; in other words, particularly for women, homosexual sex can be more lighthearted and carefree than heterosexual sex. To what extend our notions of homosexuality and heterosexuality are cultural I simply don't know. But because no children will result, a woman can afford to be more promiscuous with other women than she can with men.
|
For women, sex with other women carries with it no risk of pregnancy, so can be enjoyed or used for any of these purposes in very much the same way as it can by men; in other words, particularly for women, homosexual sex can be more lighthearted and carefree than heterosexual sex. To what extend our notions of homosexuality and heterosexuality are cultural I simply don't know. But because no children will result, a woman can afford to be more promiscuous with other women than she can with men.
|
||||||
|
|
||||||
## How does this impact on
|
##
|
||||||
|
|
|
@ -14,69 +14,71 @@
|
||||||
<td class="with-number">Lines %</td>
|
<td class="with-number">Lines %</td>
|
||||||
<td class="with-number">Total</td><td class="with-number">Blank</td><td class="with-number">Instrumented</td>
|
<td class="with-number">Total</td><td class="with-number">Blank</td><td class="with-number">Instrumented</td>
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tr>
|
|
||||||
<td><a href="the_great_game/core.clj.html">the-great-game.core</a></td><td class="with-bar"><div class="covered"
|
|
||||||
style="width:33.333333333333336%;
|
|
||||||
float:left;"> 2 </div><div class="not-covered"
|
|
||||||
style="width:66.66666666666667%;
|
|
||||||
float:left;"> 4 </div></td>
|
|
||||||
<td class="with-number">33.33 %</td>
|
|
||||||
<td class="with-bar"><div class="covered"
|
|
||||||
style="width:66.66666666666667%;
|
|
||||||
float:left;"> 2 </div><div class="not-covered"
|
|
||||||
style="width:33.333333333333336%;
|
|
||||||
float:left;"> 1 </div></td>
|
|
||||||
<td class="with-number">66.67 %</td>
|
|
||||||
<td class="with-number">6</td><td class="with-number">1</td><td class="with-number">3</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="the_great_game/gossip/gossip.clj.html">the-great-game.gossip.gossip</a></td><td class="with-bar"><div class="covered"
|
<td><a href="the_great_game/gossip/gossip.clj.html">the-great-game.gossip.gossip</a></td><td class="with-bar"><div class="covered"
|
||||||
style="width:4.545454545454546%;
|
style="width:4.62962962962963%;
|
||||||
float:left;"> 5 </div><div class="not-covered"
|
float:left;"> 5 </div><div class="not-covered"
|
||||||
style="width:95.45454545454545%;
|
style="width:95.37037037037037%;
|
||||||
float:left;"> 105 </div></td>
|
float:left;"> 103 </div></td>
|
||||||
<td class="with-number">4.55 %</td>
|
<td class="with-number">4.63 %</td>
|
||||||
<td class="with-bar"><div class="covered"
|
<td class="with-bar"><div class="covered"
|
||||||
style="width:12.820512820512821%;
|
style="width:12.820512820512821%;
|
||||||
float:left;"> 5 </div><div class="not-covered"
|
float:left;"> 5 </div><div class="not-covered"
|
||||||
style="width:87.17948717948718%;
|
style="width:87.17948717948718%;
|
||||||
float:left;"> 34 </div></td>
|
float:left;"> 34 </div></td>
|
||||||
<td class="with-number">12.82 %</td>
|
<td class="with-number">12.82 %</td>
|
||||||
<td class="with-number">65</td><td class="with-number">5</td><td class="with-number">39</td>
|
<td class="with-number">66</td><td class="with-number">5</td><td class="with-number">39</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="the_great_game/gossip/news_items.clj.html">the-great-game.gossip.news-items</a></td><td class="with-bar"><div class="covered"
|
||||||
|
style="width:92.80155642023347%;
|
||||||
|
float:left;"> 477 </div><div class="not-covered"
|
||||||
|
style="width:7.198443579766537%;
|
||||||
|
float:left;"> 37 </div></td>
|
||||||
|
<td class="with-number">92.80 %</td>
|
||||||
|
<td class="with-bar"><div class="covered"
|
||||||
|
style="width:88.07339449541284%;
|
||||||
|
float:left;"> 96 </div><div class="partial"
|
||||||
|
style="width:7.339449541284404%;
|
||||||
|
float:left;"> 8 </div><div class="not-covered"
|
||||||
|
style="width:4.587155963302752%;
|
||||||
|
float:left;"> 5 </div></td>
|
||||||
|
<td class="with-number">95.41 %</td>
|
||||||
|
<td class="with-number">256</td><td class="with-number">31</td><td class="with-number">109</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="the_great_game/merchants/markets.clj.html">the-great-game.merchants.markets</a></td><td class="with-bar"><div class="covered"
|
<td><a href="the_great_game/merchants/markets.clj.html">the-great-game.merchants.markets</a></td><td class="with-bar"><div class="covered"
|
||||||
style="width:75.11737089201878%;
|
style="width:96.46464646464646%;
|
||||||
float:left;"> 160 </div><div class="not-covered"
|
float:left;"> 191 </div><div class="not-covered"
|
||||||
style="width:24.88262910798122%;
|
style="width:3.5353535353535355%;
|
||||||
float:left;"> 53 </div></td>
|
float:left;"> 7 </div></td>
|
||||||
<td class="with-number">75.12 %</td>
|
<td class="with-number">96.46 %</td>
|
||||||
<td class="with-bar"><div class="covered"
|
<td class="with-bar"><div class="covered"
|
||||||
style="width:56.81818181818182%;
|
style="width:93.18181818181819%;
|
||||||
float:left;"> 25 </div><div class="partial"
|
float:left;"> 41 </div><div class="partial"
|
||||||
style="width:11.363636363636363%;
|
style="width:4.545454545454546%;
|
||||||
float:left;"> 5 </div><div class="not-covered"
|
float:left;"> 2 </div><div class="not-covered"
|
||||||
style="width:31.818181818181817%;
|
style="width:2.272727272727273%;
|
||||||
float:left;"> 14 </div></td>
|
float:left;"> 1 </div></td>
|
||||||
<td class="with-number">68.18 %</td>
|
<td class="with-number">97.73 %</td>
|
||||||
<td class="with-number">84</td><td class="with-number">8</td><td class="with-number">44</td>
|
<td class="with-number">84</td><td class="with-number">8</td><td class="with-number">44</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="the_great_game/merchants/merchant_utils.clj.html">the-great-game.merchants.merchant-utils</a></td><td class="with-bar"><div class="covered"
|
<td><a href="the_great_game/merchants/merchant_utils.clj.html">the-great-game.merchants.merchant-utils</a></td><td class="with-bar"><div class="covered"
|
||||||
style="width:46.017699115044245%;
|
style="width:65.4485049833887%;
|
||||||
float:left;"> 104 </div><div class="not-covered"
|
float:left;"> 197 </div><div class="not-covered"
|
||||||
style="width:53.982300884955755%;
|
style="width:34.5514950166113%;
|
||||||
float:left;"> 122 </div></td>
|
float:left;"> 104 </div></td>
|
||||||
<td class="with-number">46.02 %</td>
|
<td class="with-number">65.45 %</td>
|
||||||
<td class="with-bar"><div class="covered"
|
<td class="with-bar"><div class="covered"
|
||||||
style="width:54.09836065573771%;
|
style="width:66.66666666666667%;
|
||||||
float:left;"> 33 </div><div class="partial"
|
float:left;"> 48 </div><div class="partial"
|
||||||
style="width:4.918032786885246%;
|
style="width:5.555555555555555%;
|
||||||
float:left;"> 3 </div><div class="not-covered"
|
float:left;"> 4 </div><div class="not-covered"
|
||||||
style="width:40.98360655737705%;
|
style="width:27.77777777777778%;
|
||||||
float:left;"> 25 </div></td>
|
float:left;"> 20 </div></td>
|
||||||
<td class="with-number">59.02 %</td>
|
<td class="with-number">72.22 %</td>
|
||||||
<td class="with-number">92</td><td class="with-number">7</td><td class="with-number">61</td>
|
<td class="with-number">106</td><td class="with-number">7</td><td class="with-number">72</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="the_great_game/merchants/merchants.clj.html">the-great-game.merchants.merchants</a></td><td class="with-bar"><div class="covered"
|
<td><a href="the_great_game/merchants/merchants.clj.html">the-great-game.merchants.merchants</a></td><td class="with-bar"><div class="covered"
|
||||||
|
@ -95,11 +97,11 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="the_great_game/merchants/planning.clj.html">the-great-game.merchants.planning</a></td><td class="with-bar"><div class="covered"
|
<td><a href="the_great_game/merchants/planning.clj.html">the-great-game.merchants.planning</a></td><td class="with-bar"><div class="covered"
|
||||||
style="width:88.88888888888889%;
|
style="width:89.27335640138408%;
|
||||||
float:left;"> 264 </div><div class="not-covered"
|
float:left;"> 258 </div><div class="not-covered"
|
||||||
style="width:11.11111111111111%;
|
style="width:10.726643598615917%;
|
||||||
float:left;"> 33 </div></td>
|
float:left;"> 31 </div></td>
|
||||||
<td class="with-number">88.89 %</td>
|
<td class="with-number">89.27 %</td>
|
||||||
<td class="with-bar"><div class="covered"
|
<td class="with-bar"><div class="covered"
|
||||||
style="width:83.52941176470588%;
|
style="width:83.52941176470588%;
|
||||||
float:left;"> 71 </div><div class="partial"
|
float:left;"> 71 </div><div class="partial"
|
||||||
|
@ -112,11 +114,11 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="the_great_game/merchants/strategies/simple.clj.html">the-great-game.merchants.strategies.simple</a></td><td class="with-bar"><div class="covered"
|
<td><a href="the_great_game/merchants/strategies/simple.clj.html">the-great-game.merchants.strategies.simple</a></td><td class="with-bar"><div class="covered"
|
||||||
style="width:0.8103727714748784%;
|
style="width:0.8264462809917356%;
|
||||||
float:left;"> 5 </div><div class="not-covered"
|
float:left;"> 5 </div><div class="not-covered"
|
||||||
style="width:99.18962722852513%;
|
style="width:99.17355371900827%;
|
||||||
float:left;"> 612 </div></td>
|
float:left;"> 600 </div></td>
|
||||||
<td class="with-number">0.81 %</td>
|
<td class="with-number">0.83 %</td>
|
||||||
<td class="with-bar"><div class="covered"
|
<td class="with-bar"><div class="covered"
|
||||||
style="width:4.032258064516129%;
|
style="width:4.032258064516129%;
|
||||||
float:left;"> 5 </div><div class="not-covered"
|
float:left;"> 5 </div><div class="not-covered"
|
||||||
|
@ -125,10 +127,25 @@
|
||||||
<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/time.clj.html">the-great-game.time</a></td><td class="with-bar"><div class="covered"
|
||||||
|
style="width:99.5850622406639%;
|
||||||
|
float:left;"> 240 </div><div class="not-covered"
|
||||||
|
style="width:0.4149377593360996%;
|
||||||
|
float:left;"> 1 </div></td>
|
||||||
|
<td class="with-number">99.59 %</td>
|
||||||
|
<td class="with-bar"><div class="covered"
|
||||||
|
style="width:98.33333333333333%;
|
||||||
|
float:left;"> 59 </div><div class="partial"
|
||||||
|
style="width:1.6666666666666667%;
|
||||||
|
float:left;"> 1 </div></td>
|
||||||
|
<td class="with-number">100.00 %</td>
|
||||||
|
<td class="with-number">144</td><td class="with-number">21</td><td class="with-number">60</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="the_great_game/utils.clj.html">the-great-game.utils</a></td><td class="with-bar"><div class="covered"
|
<td><a href="the_great_game/utils.clj.html">the-great-game.utils</a></td><td class="with-bar"><div class="covered"
|
||||||
style="width:100.0%;
|
style="width:100.0%;
|
||||||
float:left;"> 72 </div></td>
|
float:left;"> 69 </div></td>
|
||||||
<td class="with-number">100.00 %</td>
|
<td class="with-number">100.00 %</td>
|
||||||
<td class="with-bar"><div class="covered"
|
<td class="with-bar"><div class="covered"
|
||||||
style="width:100.0%;
|
style="width:100.0%;
|
||||||
|
@ -136,13 +153,30 @@
|
||||||
<td class="with-number">100.00 %</td>
|
<td class="with-number">100.00 %</td>
|
||||||
<td class="with-number">35</td><td class="with-number">3</td><td class="with-number">19</td>
|
<td class="with-number">35</td><td class="with-number">3</td><td class="with-number">19</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="the_great_game/world/location.clj.html">the-great-game.world.location</a></td><td class="with-bar"><div class="covered"
|
||||||
|
style="width:87.95180722891567%;
|
||||||
|
float:left;"> 73 </div><div class="not-covered"
|
||||||
|
style="width:12.048192771084338%;
|
||||||
|
float:left;"> 10 </div></td>
|
||||||
|
<td class="with-number">87.95 %</td>
|
||||||
|
<td class="with-bar"><div class="covered"
|
||||||
|
style="width:70.58823529411765%;
|
||||||
|
float:left;"> 12 </div><div class="partial"
|
||||||
|
style="width:17.647058823529413%;
|
||||||
|
float:left;"> 3 </div><div class="not-covered"
|
||||||
|
style="width:11.764705882352942%;
|
||||||
|
float:left;"> 2 </div></td>
|
||||||
|
<td class="with-number">88.24 %</td>
|
||||||
|
<td class="with-number">37</td><td class="with-number">4</td><td class="with-number">17</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="the_great_game/world/routes.clj.html">the-great-game.world.routes</a></td><td class="with-bar"><div class="covered"
|
<td><a href="the_great_game/world/routes.clj.html">the-great-game.world.routes</a></td><td class="with-bar"><div class="covered"
|
||||||
style="width:99.21875%;
|
style="width:99.19354838709677%;
|
||||||
float:left;"> 127 </div><div class="not-covered"
|
float:left;"> 123 </div><div class="not-covered"
|
||||||
style="width:0.78125%;
|
style="width:0.8064516129032258%;
|
||||||
float:left;"> 1 </div></td>
|
float:left;"> 1 </div></td>
|
||||||
<td class="with-number">99.22 %</td>
|
<td class="with-number">99.19 %</td>
|
||||||
<td class="with-bar"><div class="covered"
|
<td class="with-bar"><div class="covered"
|
||||||
style="width:97.61904761904762%;
|
style="width:97.61904761904762%;
|
||||||
float:left;"> 41 </div><div class="partial"
|
float:left;"> 41 </div><div class="partial"
|
||||||
|
@ -153,11 +187,11 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="the_great_game/world/run.clj.html">the-great-game.world.run</a></td><td class="with-bar"><div class="covered"
|
<td><a href="the_great_game/world/run.clj.html">the-great-game.world.run</a></td><td class="with-bar"><div class="covered"
|
||||||
style="width:4.918032786885246%;
|
style="width:5.0%;
|
||||||
float:left;"> 3 </div><div class="not-covered"
|
float:left;"> 3 </div><div class="not-covered"
|
||||||
style="width:95.08196721311475%;
|
style="width:95.0%;
|
||||||
float:left;"> 58 </div></td>
|
float:left;"> 57 </div></td>
|
||||||
<td class="with-number">4.92 %</td>
|
<td class="with-number">5.00 %</td>
|
||||||
<td class="with-bar"><div class="covered"
|
<td class="with-bar"><div class="covered"
|
||||||
style="width:15.0%;
|
style="width:15.0%;
|
||||||
float:left;"> 3 </div><div class="not-covered"
|
float:left;"> 3 </div><div class="not-covered"
|
||||||
|
@ -168,11 +202,11 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="the_great_game/world/world.clj.html">the-great-game.world.world</a></td><td class="with-bar"><div class="covered"
|
<td><a href="the_great_game/world/world.clj.html">the-great-game.world.world</a></td><td class="with-bar"><div class="covered"
|
||||||
style="width:95.89041095890411%;
|
style="width:96.10983981693364%;
|
||||||
float:left;"> 420 </div><div class="not-covered"
|
float:left;"> 420 </div><div class="not-covered"
|
||||||
style="width:4.109589041095891%;
|
style="width:3.8901601830663615%;
|
||||||
float:left;"> 18 </div></td>
|
float:left;"> 17 </div></td>
|
||||||
<td class="with-number">95.89 %</td>
|
<td class="with-number">96.11 %</td>
|
||||||
<td class="with-bar"><div class="covered"
|
<td class="with-bar"><div class="covered"
|
||||||
style="width:97.01492537313433%;
|
style="width:97.01492537313433%;
|
||||||
float:left;"> 65 </div><div class="not-covered"
|
float:left;"> 65 </div><div class="not-covered"
|
||||||
|
@ -183,9 +217,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr><td>Totals:</td>
|
<tr><td>Totals:</td>
|
||||||
<td class="with-bar"></td>
|
<td class="with-bar"></td>
|
||||||
<td class="with-number">51.99 %</td>
|
<td class="with-number">66.55 %</td>
|
||||||
<td class="with-bar"></td>
|
<td class="with-bar"></td>
|
||||||
<td class="with-number">54.62 %</td>
|
<td class="with-number">68.63 %</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</body>
|
</body>
|
||||||
|
|
29
docs/cloverage/the_great_game/agent/agent.clj.html
Normal file
29
docs/cloverage/the_great_game/agent/agent.clj.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<link rel="stylesheet" href="../../coverage.css"/> <title> the_great_game/agent/agent.clj </title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
001 (ns the-great-game.agent.agent
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
002 "Anything in the game world with agency")
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
003
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
004 ;; hierarchy of needs probably gets implemented here
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
005 ;; I'm probably going to want to defprotocol stuff, to define the hierarchy
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
006 ;; of things in the gameworld; either that or drop to Java, wich I'd rather not do.
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
007
|
||||||
|
</span><br/>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -8,196 +8,199 @@
|
||||||
001 (ns the-great-game.gossip.gossip
|
001 (ns the-great-game.gossip.gossip
|
||||||
</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 "Interchange of news events between agents agents"
|
002 "Interchange of news events between gossip agents"
|
||||||
</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">
|
||||||
003 (:require [the-great-game.utils :refer [deep-merge]]))
|
003 (:require [the-great-game.utils :refer [deep-merge]]
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
004 [the-great-game.gossip.news-items :refer [learn-news-item]]))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="blank" title="0 out of 0 forms covered">
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
004
|
005
|
||||||
</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 ;; Note that habitual travellers are all gossip agents; specifically, at this
|
006 ;; Note that habitual travellers are all gossip agents; specifically, at this
|
||||||
</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 ;; stage, that means merchants. When merchants are moved we also need to
|
007 ;; stage, that means merchants. When merchants are moved we also need to
|
||||||
</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">
|
||||||
007 ;; update the location of the gossip with the same key.
|
008 ;; update the location of the gossip with the same key.
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="blank" title="0 out of 0 forms covered">
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
008
|
009
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="1 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
009 (defn dialogue
|
010 (defn dialogue
|
||||||
</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">
|
||||||
010 "Dialogue between an `enquirer` and an `agent` in this `world`; returns a
|
011 "Dialogue between an `enquirer` and an `agent` in this `world`; returns a
|
||||||
</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">
|
||||||
011 map identical to `enquirer` except that its `:gossip` collection may have
|
012 map identical to `enquirer` except that its `:gossip` collection may have
|
||||||
</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">
|
||||||
012 additional entries."
|
013 additional entries."
|
||||||
</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">
|
||||||
013 ;; TODO: not yet written, this is a stub.
|
014 ;; TODO: not yet written, this is a stub.
|
||||||
</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">
|
||||||
014 [enquirer respondent world]
|
015 [enquirer respondent world]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
015 enquirer)
|
016 enquirer)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="blank" title="0 out of 0 forms covered">
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
016
|
017
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="1 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
017 (defn gather-news
|
018 (defn gather-news
|
||||||
</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">
|
||||||
018 ([world]
|
019 ([world]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 2 forms covered">
|
<span class="not-covered" title="0 out of 2 forms covered">
|
||||||
019 (reduce
|
020 (reduce
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
020 deep-merge
|
021 deep-merge
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
021 world
|
022 world
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
022 (map
|
023 (map
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 4 forms covered">
|
<span class="not-covered" title="0 out of 4 forms covered">
|
||||||
023 #(gather-news world %)
|
024 #(gather-news world %)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 5 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
024 (keys (:gossips world)))))
|
025 (keys (:gossips world)))))
|
||||||
</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">
|
||||||
025 ([world gossip]
|
026 ([world gossip]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 7 forms covered">
|
<span class="not-covered" title="0 out of 7 forms covered">
|
||||||
026 (let [g (cond (keyword? gossip)
|
027 (let [g (cond (keyword? gossip)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 5 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
027 (-> world :gossips gossip)
|
028 (-> world :gossips gossip)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
028 (map? gossip)
|
029 (map? gossip)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
029 gossip)]
|
030 gossip)]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 2 forms covered">
|
<span class="not-covered" title="0 out of 2 forms covered">
|
||||||
030 {:gossips
|
031 {:gossips
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 4 forms covered">
|
<span class="not-covered" title="0 out of 4 forms covered">
|
||||||
031 {(:id g)
|
032 {(:id g)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 2 forms covered">
|
<span class="not-covered" title="0 out of 2 forms covered">
|
||||||
032 (reduce
|
033 (reduce
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
033 deep-merge
|
034 deep-merge
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
034 {}
|
035 {}
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
035 (map
|
036 (map
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 5 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
036 #(dialogue g % world)
|
037 #(dialogue g % world)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
037 (remove
|
038 (remove
|
||||||
</span><br/>
|
|
||||||
<span class="not-covered" title="0 out of 4 forms covered">
|
|
||||||
038 #( = g %)
|
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
039 (filter
|
039 #( = g %)
|
||||||
</span><br/>
|
|
||||||
<span class="not-covered" title="0 out of 8 forms covered">
|
|
||||||
040 #(= (:location %) (:location g))
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-covered" title="0 out of 5 forms covered">
|
|
||||||
041 (vals (:gossips world))))))}})))
|
|
||||||
</span><br/>
|
|
||||||
<span class="blank" title="0 out of 0 forms covered">
|
|
||||||
042
|
|
||||||
</span><br/>
|
|
||||||
<span class="covered" title="1 out of 1 forms covered">
|
|
||||||
043 (defn move-gossip
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
|
||||||
044 "Return a world like this `world` but with this `gossip` moved to this
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
|
||||||
045 `new-location`. Many gossips are essentially shadow-records of agents of
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
|
||||||
046 other types, and the movement if the gossip should be controlled by the
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
|
||||||
047 run function of the type of the record they shadow. The [[#run]] function
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
|
||||||
048 below does NOT call this function."
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
|
||||||
049 [gossip world new-location]
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-covered" title="0 out of 4 forms covered">
|
|
||||||
050 (let [id (cond
|
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
051 (map? gossip)
|
040 (filter
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 7 forms covered">
|
<span class="not-covered" title="0 out of 7 forms covered">
|
||||||
052 (-> world :gossips gossip :id)
|
041 #(= (:location %) (:location g))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
053 (keyword? gossip)
|
042 (vals (:gossips world))))))}})))
|
||||||
</span><br/>
|
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
|
||||||
054 gossip)]
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-covered" title="0 out of 2 forms covered">
|
|
||||||
055 (deep-merge
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
|
||||||
056 world
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-covered" title="0 out of 2 forms covered">
|
|
||||||
057 {:gossips
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-covered" title="0 out of 2 forms covered">
|
|
||||||
058 {id
|
|
||||||
</span><br/>
|
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
|
||||||
059 {:location new-location}}})))
|
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="blank" title="0 out of 0 forms covered">
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
060
|
043
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="1 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
061 (defn run
|
044 (defn move-gossip
|
||||||
</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">
|
||||||
062 "Return a world like this `world`, with news items exchanged between gossip
|
045 "Return a world like this `world` but with this `gossip` moved to this
|
||||||
</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">
|
||||||
063 agents."
|
046 `new-location`. Many gossips are essentially shadow-records of agents of
|
||||||
</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">
|
||||||
064 [world]
|
047 other types, and the movement of the gossip should be controlled by the
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
048 run function of the type of the record they shadow. The [[#run]] function
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
049 below does NOT call this function."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
050 [gossip world new-location]
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 4 forms covered">
|
||||||
|
051 (let [id (cond
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
065 (gather-news world))
|
052 (map? gossip)
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 7 forms covered">
|
||||||
|
053 (-> world :gossips gossip :id)
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
|
054 (keyword? gossip)
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
|
055 gossip)]
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 2 forms covered">
|
||||||
|
056 (deep-merge
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
|
057 world
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 2 forms covered">
|
||||||
|
058 {:gossips
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 2 forms covered">
|
||||||
|
059 {id
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
|
060 {:location new-location}}})))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
061
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
062 (defn run
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
063 "Return a world like this `world`, with news items exchanged between gossip
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
064 agents."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
065 [world]
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
|
066 (gather-news world))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
776
docs/cloverage/the_great_game/gossip/news_items.clj.html
Normal file
776
docs/cloverage/the_great_game/gossip/news_items.clj.html
Normal file
|
@ -0,0 +1,776 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<link rel="stylesheet" href="../../coverage.css"/> <title> the_great_game/gossip/news_items.clj </title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
001 (ns the-great-game.gossip.news-items
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
002 "Categories of news events interesting to gossip agents"
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
003 (:require [the-great-game.world.location :refer [distance-between]]
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
004 [the-great-game.time :refer [game-time]]))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
005
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
006 ;; The ideas here are based on the essay 'The spread of knowledge in a large
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
007 ;; game world', q.v.; they've advanced a little beyond that and will doubtless
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
008 ;; advance further in the course of writing and debugging this namespace.
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
009
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
010 ;; A news item is a map with the keys:
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
011 ;;
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
012 ;; * `date` - the date on which the reported event happened;
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
013 ;; * `nth-hand` - the number of agents the news item has passed through;
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
014 ;; * `verb` - what it is that happened (key into `news-topics`);
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
015 ;;
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
016 ;; plus other keys taken from the `keys` value associated with the verb in
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
017 ;; `news-topics`
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
018
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
019 (def news-topics
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
020 "Topics of interest to gossip agents. Topics are keyed in this map by
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
021 their `verbs`. The `keys` associated with each topic are the extra pieces
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
022 of information required to give context to a gossip item. Generally:
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
023
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
024 * `actor` is the id of the character who it is reported performed the
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
025 action;
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
026 * `other` is the id of the character on whom it is reported the action
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
027 was performed;
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
028 * `location` is the place at which the action was performed;
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
029 * `object` is an object (or possibly list of objects?) relevant to the
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
030 action;
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
031 * `price` is special to buy/sell, but of significant interest to merchants.
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
032
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
033 #### Notes:
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
034
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
035 ##### Characters:
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
036
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
037 *TODO* but note that at most all the receiver can learn about a character
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
038 from a news item is what the giver knows about that character, degraded by
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
039 what the receiver finds interesting about them. If we just pass the id here,
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
040 then either the receiver knows everything in the database about the
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
041 character, or else the receiver knows nothing at all about the character.
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
042 Neither is desirable. Further thought needed.
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
043
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
044 By implication, the character values passed should include *all* the
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
045 information the giver knows about the character; that can then be degraded
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
046 as the receiver stores only that segment which the receiver finds
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
047 interesting.
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
048
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
049 ##### Locations:
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
050
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
051 A 'location' value is a list comprising at most the x/y coordinate location
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
052 and the ids of the settlement and region (possibly hierarchically) that contain
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
053 the location. If the x/y is not local to the home of the receiving agent, they
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
054 won't remember it and won't pass it on; if any of the ids are not interesting
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
055 So location information will degrade progressively as the item is passed along.
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
056
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
057 It is assumed that the `:home` of a character is a location in this sense.
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
058
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
059 ##### Inferences:
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
060
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
061 If an agent learns that Adam has married Betty, they can infer that Betty has
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
062 married Adam; if they learn that Charles killed Dorothy, that Dorothy has died.
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
063 I'm not convinced that my representation of inferences here is ideal.
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
064 "
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="13 out of 13 forms covered">
|
||||||
|
065 { ;; A significant attack is interesting whether or not it leads to deaths
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="8 out of 8 forms covered">
|
||||||
|
066 :attack {:verb :attack :keys [:actor :other :location]}
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
067 ;; Deaths of characters may be interesting
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="7 out of 7 forms covered">
|
||||||
|
068 :die {:verb :die :keys [:actor :location]}
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
069 ;; Deliberate killings are interesting.
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
070 :kill {:verb :kill :keys [:actor :other :location]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="8 out of 8 forms covered">
|
||||||
|
071 :inferences [{:verb :die :actor :other :other :nil}]}
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
072 ;; Marriages may be interesting
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
073 :marry {:verb :marry :keys [:actor :other :location]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="8 out of 8 forms covered">
|
||||||
|
074 :inferences [{:verb :marry :actor :other :other :actor}]}
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
075 ;; The end of ongoing open conflict between to characters may be interesting
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
076 :peace {:verb :peace :keys [:actor :other :location]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="8 out of 8 forms covered">
|
||||||
|
077 :inferences [{:verb :peace :actor :other :other :actor}]}
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
078 ;; Things related to the plot are interesting, but will require special
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
079 ;; handling. Extra keys may be required by particular plot events.
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
080 :plot {:verb :plot :keys [:actor :other :object :location]}
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
081 ;; Rapes are interesting.
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
082 :rape {:verb :rape :keys [:actor :other :location]
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
083 ;; Should you also infer from rape that actor is male and adult?
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="4 out of 4 forms covered">
|
||||||
|
084 :inferences [{:verb :attack}
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
085 {:verb :sex}
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="7 out of 7 forms covered">
|
||||||
|
086 {:verb :sex :actor :other :other :actor}]}
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
087 ;; Merchants, especially, are interested in prices in other markets
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="10 out of 10 forms covered">
|
||||||
|
088 :sell {:verb :sell :keys [:actor :other :object :location :price]}
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
089 ;; Sex can juicy gossip, although not normally if the participants are in an
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
090 ;; established sexual relationship.
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
091 :sex {:verb :sex :keys [:actor :other :location]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="8 out of 8 forms covered">
|
||||||
|
092 :inferences [{:verb :sex :actor :other :other :actor}]}
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
093 ;; Thefts are interesting
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
094 :steal {:verb :steal :keys [:actor :other :object :location]}
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
095 ;; The succession of rulers is interesting; of respected craftsmen,
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
096 ;; potentially also interesting.
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
097 :succession {:verb :succession :keys [:actor :other :location :rank]}
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
098 ;; The start of ongoing open conflict between to characters may be interesting
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
099 :war {:verb :war :keys [:actor :other :location]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="8 out of 8 forms covered">
|
||||||
|
100 :inferences [{:verb :war :actor :other :other :actor}]}
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
101 })
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
102
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
103
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
104 (defn interest-in-character
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
105 "Integer representation of how interesting this `character` is to this
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
106 `gossip`.
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
107 *TODO:* this assumes that characters are passed as keywords, but, as
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
108 documented above, they probably have to be maps, to allow for degradation."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
109 [gossip character]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
110 (count
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
111 (concat
|
||||||
|
</span><br/>
|
||||||
|
<span class="partial" title="6 out of 12 forms covered">
|
||||||
|
112 (filter #(= (:actor % character)) (:knowledge gossip))
|
||||||
|
</span><br/>
|
||||||
|
<span class="partial" title="6 out of 12 forms covered">
|
||||||
|
113 (filter #(= (:other % character)) (:knowledge gossip)))))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
114
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
115 (defn interesting-character?
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
116 "Boolean representation of whether this `character` is interesting to this
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
117 `gossip`."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
118 [gossip character]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="6 out of 6 forms covered">
|
||||||
|
119 (> (interest-in-character gossip character) 0))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
120
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
121 (defn interest-in-location
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
122 "Integer representation of how interesting this `location` is to this
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
123 `gossip`."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
124 [gossip location]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
125 (cond
|
||||||
|
</span><br/>
|
||||||
|
<span class="partial" title="20 out of 21 forms covered">
|
||||||
|
126 (and (map? location) (number? (:x location)) (number? (:y location)))
|
||||||
|
</span><br/>
|
||||||
|
<span class="partial" title="8 out of 9 forms covered">
|
||||||
|
127 (if-let [home (:home gossip)]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
|
128 (let [d (distance-between location home)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
129 i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
130 ;;fall off with distance from home, but possibly on a log scale
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
131 ]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="6 out of 6 forms covered">
|
||||||
|
132 (if (> i 1) i 0))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
133 0)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
134 (coll? location)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
135 (reduce
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
136 +
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
137 (map
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="4 out of 4 forms covered">
|
||||||
|
138 #(interest-in-location gossip %)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
139 location))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
140 :else
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
141 (count
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
142 (filter
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
143 #(some (fn [x] (= x location)) (:location %))
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="10 out of 10 forms covered">
|
||||||
|
144 (cons {:location (:home gossip)} (:knowledge gossip))))))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
145
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
146 ;; (interest-in-location {:home [{0, 0} :test-home] :knowledge []} [:test-home])
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
147
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
148 (defn interesting-location?
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
149 "True if the location of this news `item` is interesting to this `gossip`."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
150 [gossip item]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="8 out of 8 forms covered">
|
||||||
|
151 (> (interest-in-location gossip (:location item)) 0))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
152
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
153 (defn interesting-object?
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
154 [gossip object]
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
155 ;; TODO: Not yet (really) implemented
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
156 true)
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
157
|
||||||
|
</span><br/>
|
||||||
|
<span class="partial" title="1 out of 2 forms covered">
|
||||||
|
158 (defn interesting-topic?
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
159 [gossip topic]
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
160 ;; TODO: Not yet (really) implemented
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
161 true)
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
162
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
163 (defn interesting-item?
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
164 "True if anything about this news `item` is interesting to this `gossip`."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
165 [gossip item]
|
||||||
|
</span><br/>
|
||||||
|
<span class="partial" title="13 out of 17 forms covered">
|
||||||
|
166 (or
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="6 out of 6 forms covered">
|
||||||
|
167 (interesting-character? gossip (:actor item))
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="6 out of 6 forms covered">
|
||||||
|
168 (interesting-character? gossip (:other item))
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="6 out of 6 forms covered">
|
||||||
|
169 (interesting-location? gossip (:location item))
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="6 out of 6 forms covered">
|
||||||
|
170 (interesting-object? gossip (:object item))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
|
171 (interesting-topic? gossip (:verb item))))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
172
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
173 (defn infer
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
174 "Infer a new knowledge item from this `item`, following this `rule`"
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
175 [item rule]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
176 (reduce merge
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
177 item
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
178 (cons
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
|
179 {:verb (:verb rule)}
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="13 out of 13 forms covered">
|
||||||
|
180 (map (fn [k] {k (apply (k rule) (list item))})
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
181 (remove
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
182 #(= % :verb)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
183 (keys rule))))))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
184
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
185 (declare learn-news-item)
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
186
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
187 (defn make-all-inferences
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
188 "Return a list of knowledge entries that can be inferred from this news
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
189 `item`."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
190 [item]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
191 (set
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
192 (reduce
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
193 concat
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
194 (map
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="10 out of 10 forms covered">
|
||||||
|
195 #(:knowledge (learn-news-item {} (infer item %) false))
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="7 out of 7 forms covered">
|
||||||
|
196 (:inferences (news-topics (:verb item)))))))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
197
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
198 (defn degrade-character
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
199 "Return a character specification like this `character`, but comprising
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
200 only those properties this `gossip` is interested in."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
201 [gossip character]
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
202 ;; TODO: Not yet (really) implemented
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
|
203 character)
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
204
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
205 (defn degrade-location
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
206 "Return a location specification like this `location`, but comprising
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
207 only those elements this `gossip` is interested in. If none, return
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
208 `nil`."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
209 [gossip location]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
210 (let [l (if
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
211 (coll? location)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
212 (filter
|
||||||
|
</span><br/>
|
||||||
|
<span class="partial" title="5 out of 7 forms covered">
|
||||||
|
213 #(when (interesting-location? gossip %) %)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
214 location))]
|
||||||
|
</span><br/>
|
||||||
|
<span class="partial" title="5 out of 7 forms covered">
|
||||||
|
215 (when-not (empty? l) l)))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
216
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
217 (defn learn-news-item
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
218 "Return a gossip like this `gossip`, which has learned this news `item` if
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
219 it is of interest to them."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
220 ;; TODO: Not yet implemented
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
221 ([gossip item]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
|
222 (learn-news-item gossip item true))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
223 ([gossip item follow-inferences?]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
224 (if
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="4 out of 4 forms covered">
|
||||||
|
225 (interesting-item? gossip item)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
226 (let
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
227 [g (assoc
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
228 gossip
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
229 :knowledge
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
230 (cons
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
|
231 (assoc
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
232 item
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
233 :nth-hand (if
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
|
234 (number? (:nth-hand item))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 4 forms covered">
|
||||||
|
235 (inc (:nth-hand item))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
236 1)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
237 :time-stamp (if
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
|
238 (number? (:time-stamp item))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
|
239 (:time-stamp item)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
240 (game-time))
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="6 out of 6 forms covered">
|
||||||
|
241 :location (degrade-location gossip (:location item))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
242 ;; TODO: ought to maybe-degrade characters we're not yet interested in
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
243 )
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
244 ;; TODO: ought not to add knowledge items we already have, except
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
245 ;; to replace if new item is of increased specificity
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
246 (:knowledge gossip)))]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
247 (if follow-inferences?
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
248 (assoc
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
249 g
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
250 :knowledge
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="8 out of 8 forms covered">
|
||||||
|
251 (concat (:knowledge g) (make-all-inferences item)))
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
252 g))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
|
253 gossip)))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
254
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
255
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
256
|
||||||
|
</span><br/>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -37,13 +37,13 @@
|
||||||
<span class="covered" title="1 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
011 (let
|
011 (let
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="13 out of 13 forms covered">
|
<span class="covered" title="10 out of 10 forms covered">
|
||||||
012 [delta (dec' (/ (max supply demand 1) (max stock 1)))
|
012 [delta (dec' (/ (max supply demand 1) (max stock 1)))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="4 out of 4 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
013 scaled (/ delta 100)]
|
013 scaled (/ delta 100)]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="4 out of 4 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
014 (+ old scaled)))
|
014 (+ old scaled)))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="blank" title="0 out of 0 forms covered">
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
@ -79,22 +79,22 @@
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
025 id (:id c)
|
025 id (:id c)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="partial" title="9 out of 10 forms covered">
|
<span class="covered" title="10 out of 10 forms covered">
|
||||||
026 p (or (-> c :prices commodity) 0)
|
026 p (or (-> c :prices commodity) 0)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="10 out of 10 forms covered">
|
<span class="covered" title="10 out of 10 forms covered">
|
||||||
027 d (or (-> c :demands commodity) 0)
|
027 d (or (-> c :demands commodity) 0)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="partial" title="9 out of 10 forms covered">
|
<span class="covered" title="10 out of 10 forms covered">
|
||||||
028 st (or (-> c :stock commodity) 0)
|
028 st (or (-> c :stock commodity) 0)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="partial" title="9 out of 10 forms covered">
|
<span class="covered" title="10 out of 10 forms covered">
|
||||||
029 su (or (-> c :supplies commodity) 0)
|
029 su (or (-> c :supplies commodity) 0)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="4 out of 4 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
030 decrement (min st d)
|
030 decrement (min st d)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="partial" title="3 out of 6 forms covered">
|
<span class="partial" title="5 out of 6 forms covered">
|
||||||
031 increment (cond
|
031 increment (cond
|
||||||
</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">
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
033 ;; stock, halt production
|
033 ;; stock, halt production
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="7 out of 7 forms covered">
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
034 (> st (* su 2))
|
034 (> st (* su 2))
|
||||||
</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">
|
||||||
|
@ -115,7 +115,7 @@
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
037 ;; craftspeople of the city will do so.
|
037 ;; craftspeople of the city will do so.
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="5 out of 5 forms covered">
|
<span class="covered" title="4 out of 4 forms covered">
|
||||||
038 (> p 1) su
|
038 (> p 1) su
|
||||||
</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">
|
||||||
|
@ -127,10 +127,10 @@
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
041 ;; incoming merchants to buy
|
041 ;; incoming merchants to buy
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="4 out of 4 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
042 (> su st)
|
042 (> su st)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="4 out of 4 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
043 (- su st)
|
043 (- su st)
|
||||||
</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">
|
||||||
|
@ -148,7 +148,7 @@
|
||||||
<span class="covered" title="4 out of 4 forms covered">
|
<span class="covered" title="4 out of 4 forms covered">
|
||||||
048 (not= p n)
|
048 (not= p n)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="26 out of 26 forms covered">
|
<span class="covered" title="24 out of 24 forms covered">
|
||||||
049 (l/info "Price of" commodity "at" id "has changed from" (float p) "to" (float n)))
|
049 (l/info "Price of" commodity "at" id "has changed from" (float p) "to" (float n)))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="4 out of 4 forms covered">
|
<span class="covered" title="4 out of 4 forms covered">
|
||||||
|
@ -157,7 +157,7 @@
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
051 {:stock
|
051 {:stock
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="9 out of 9 forms covered">
|
<span class="covered" title="7 out of 7 forms covered">
|
||||||
052 {commodity (+ (- st decrement) increment)}
|
052 {commodity (+ (- st decrement) increment)}
|
||||||
</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">
|
||||||
|
@ -190,46 +190,46 @@
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
062 ([world]
|
062 ([world]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 2 forms covered">
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
063 (reduce
|
063 (reduce
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
064 deep-merge
|
064 deep-merge
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
065 world
|
065 world
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
066 (map
|
066 (map
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 4 forms covered">
|
<span class="covered" title="4 out of 4 forms covered">
|
||||||
067 #(update-markets world %)
|
067 #(update-markets world %)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 5 forms covered">
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
068 (keys (:cities world)))))
|
068 (keys (:cities world)))))
|
||||||
</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">
|
||||||
069 ([world city]
|
069 ([world city]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 2 forms covered">
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
070 (reduce
|
070 (reduce
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
071 deep-merge
|
071 deep-merge
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
072 {}
|
072 {}
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 8 forms covered">
|
<span class="covered" title="8 out of 8 forms covered">
|
||||||
073 (map #(update-markets world city %)
|
073 (map #(update-markets world city %)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 5 forms covered">
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
074 (keys (:commodities world)))))
|
074 (keys (:commodities world)))))
|
||||||
</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">
|
||||||
075 ([world city commodity]
|
075 ([world city commodity]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 5 forms covered">
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
076 (adjust-quantity-and-price world city commodity)))
|
076 (adjust-quantity-and-price world city commodity)))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="blank" title="0 out of 0 forms covered">
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
@ -250,7 +250,7 @@
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
082 [world]
|
082 [world]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
083 (update-markets world))
|
083 (update-markets world))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="blank" title="0 out of 0 forms covered">
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
|
|
@ -94,7 +94,7 @@
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
030 (map
|
030 (map
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="12 out of 12 forms covered">
|
<span class="covered" title="11 out of 11 forms covered">
|
||||||
031 #(* (cargo %) (-> world :commodities % :weight))
|
031 #(* (cargo %) (-> world :commodities % :weight))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
@ -133,16 +133,16 @@
|
||||||
<span class="covered" title="1 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
043 merchant)]
|
043 merchant)]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
044 (max
|
044 (max
|
||||||
</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">
|
||||||
045 0
|
045 0
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="2 out of 2 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
046 (quot
|
046 (quot
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="partial" title="13 out of 14 forms covered">
|
<span class="partial" title="12 out of 13 forms covered">
|
||||||
047 (- (or (:capacity m) 0) (burden m world))
|
047 (- (or (:capacity m) 0) (burden m world))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="7 out of 7 forms covered">
|
<span class="covered" title="7 out of 7 forms covered">
|
||||||
|
@ -199,7 +199,7 @@
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
065 :else
|
065 :else
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="2 out of 2 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
066 (quot
|
066 (quot
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
@ -238,7 +238,7 @@
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
078 (map
|
078 (map
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="partial" title="20 out of 21 forms covered">
|
<span class="partial" title="19 out of 20 forms covered">
|
||||||
079 #(hash-map % (+ (or (a %) 0) (or (b %) 0)))
|
079 #(hash-map % (+ (or (a %) 0) (or (b %) 0)))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
|
|
@ -136,13 +136,13 @@
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
044 %)
|
044 %)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="2 out of 2 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
045 :distance (count
|
045 :distance (count
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="5 out of 5 forms covered">
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
046 (find-route world origin %))
|
046 (find-route world origin %))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="2 out of 2 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
047 :dist-to-home (count
|
047 :dist-to-home (count
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
@ -157,7 +157,7 @@
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
051 %)))
|
051 %)))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="12 out of 12 forms covered">
|
<span class="covered" title="11 out of 11 forms covered">
|
||||||
052 (remove #(= % origin) (-> world :cities keys)))))
|
052 (remove #(= % origin) (-> world :cities keys)))))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="blank" title="0 out of 0 forms covered">
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
@ -304,7 +304,7 @@
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
100 ;; to home.
|
100 ;; to home.
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 6 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
101 #(- 0 (:dist-to-home %))
|
101 #(- 0 (:dist-to-home %))
|
||||||
</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">
|
||||||
|
@ -376,7 +376,7 @@
|
||||||
<span class="covered" title="5 out of 5 forms covered">
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
124 (can-afford merchant world c))
|
124 (can-afford merchant world c))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="11 out of 11 forms covered">
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
125 p (* q (- (:expected-price plan) (:buy-price plan)))]
|
125 p (* q (- (:expected-price plan) (:buy-price plan)))]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="7 out of 7 forms covered">
|
<span class="covered" title="7 out of 7 forms covered">
|
||||||
|
@ -442,7 +442,7 @@
|
||||||
<span class="covered" title="10 out of 10 forms covered">
|
<span class="covered" title="10 out of 10 forms covered">
|
||||||
146 #(let [q (-> world :cities origin :stock %)]
|
146 #(let [q (-> world :cities origin :stock %)]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="partial" title="9 out of 10 forms covered">
|
<span class="partial" title="8 out of 9 forms covered">
|
||||||
147 (and (number? q) (pos? q)))
|
147 (and (number? q) (pos? q)))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
@ -460,7 +460,7 @@
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
152 (sort-by
|
152 (sort-by
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 6 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
153 #(- 0 (:dist-to-home %))
|
153 #(- 0 (:dist-to-home %))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="2 out of 2 forms covered">
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
035 [c (:commodity plan)
|
035 [c (:commodity plan)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 8 forms covered">
|
<span class="not-covered" title="0 out of 7 forms covered">
|
||||||
036 p (* (:quantity plan) (:buy-price plan))
|
036 p (* (:quantity plan) (:buy-price plan))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
<span class="not-covered" title="0 out of 13 forms covered">
|
<span class="not-covered" title="0 out of 13 forms covered">
|
||||||
041 {:stock (add-stock (:stock m) {c q})
|
041 {:stock (add-stock (:stock m) {c q})
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 6 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
042 :cash (- (:cash m) p)
|
042 :cash (- (:cash m) p)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 4 forms covered">
|
<span class="not-covered" title="0 out of 4 forms covered">
|
||||||
|
@ -142,16 +142,16 @@
|
||||||
<span class="not-covered" title="0 out of 2 forms covered">
|
<span class="not-covered" title="0 out of 2 forms covered">
|
||||||
046 {location
|
046 {location
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 17 forms covered">
|
<span class="not-covered" title="0 out of 16 forms covered">
|
||||||
047 {:stock (assoc (:stock market) c (- (-> market :stock c) q))
|
047 {:stock (assoc (:stock market) c (- (-> market :stock c) q))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 6 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
048 :cash (+ (:cash market) p)}}})
|
048 :cash (+ (:cash market) p)}}})
|
||||||
</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">
|
||||||
049 ;; if no plan, then if at home stay put
|
049 ;; if no plan, then if at home stay put
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 8 forms covered">
|
<span class="not-covered" title="0 out of 7 forms covered">
|
||||||
050 (= (:location m) (:home m))
|
050 (= (:location m) (:home m))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
|
@ -172,7 +172,7 @@
|
||||||
<span class="not-covered" title="0 out of 8 forms covered">
|
<span class="not-covered" title="0 out of 8 forms covered">
|
||||||
056 (let [route (find-route world location (:home m))
|
056 (let [route (find-route world location (:home m))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 4 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
057 next-location (nth route 1)]
|
057 next-location (nth route 1)]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 20 forms covered">
|
<span class="not-covered" title="0 out of 20 forms covered">
|
||||||
|
@ -310,7 +310,7 @@
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
102 (map
|
102 (map
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 12 forms covered">
|
<span class="not-covered" title="0 out of 11 forms covered">
|
||||||
103 #(* (-> m :stock %) (-> market :prices m))
|
103 #(* (-> m :stock %) (-> market :prices m))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 5 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
|
@ -322,7 +322,7 @@
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
106 (if
|
106 (if
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 6 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
107 (>= (:cash market) stock-value)
|
107 (>= (:cash market) stock-value)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 1 forms covered">
|
<span class="not-covered" title="0 out of 1 forms covered">
|
||||||
|
@ -352,7 +352,7 @@
|
||||||
<span class="not-covered" title="0 out of 5 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
116 {:stock {}
|
116 {:stock {}
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 6 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
117 :cash (+ (:cash m) stock-value)
|
117 :cash (+ (:cash m) stock-value)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 4 forms covered">
|
<span class="not-covered" title="0 out of 4 forms covered">
|
||||||
|
@ -367,7 +367,7 @@
|
||||||
<span class="not-covered" title="0 out of 11 forms covered">
|
<span class="not-covered" title="0 out of 11 forms covered">
|
||||||
121 {:stock (add-stock (:stock m) (:stock market))
|
121 {:stock (add-stock (:stock m) (:stock market))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 6 forms covered">
|
<span class="not-covered" title="0 out of 5 forms covered">
|
||||||
122 :cash (- (:cash market) stock-value)}}})))
|
122 :cash (- (:cash market) stock-value)}}})))
|
||||||
</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">
|
||||||
|
@ -412,7 +412,7 @@
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
136 id (:id m)
|
136 id (:id m)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 17 forms covered">
|
<span class="not-covered" title="0 out of 16 forms covered">
|
||||||
137 at-destination? (and (:plan m) (= (:location m) (-> m :plan :destination)))
|
137 at-destination? (and (:plan m) (= (:location m) (-> m :plan :destination)))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
|
@ -421,7 +421,7 @@
|
||||||
<span class="not-covered" title="0 out of 2 forms covered">
|
<span class="not-covered" title="0 out of 2 forms covered">
|
||||||
139 next-location (if plan
|
139 next-location (if plan
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 3 forms covered">
|
<span class="not-covered" title="0 out of 2 forms covered">
|
||||||
140 (nth
|
140 (nth
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 2 forms covered">
|
<span class="not-covered" title="0 out of 2 forms covered">
|
||||||
|
|
440
docs/cloverage/the_great_game/time.clj.html
Normal file
440
docs/cloverage/the_great_game/time.clj.html
Normal file
|
@ -0,0 +1,440 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<link rel="stylesheet" href="../coverage.css"/> <title> the_great_game/time.clj </title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
001 (ns the-great-game.time
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
002 (:require [clojure.string :as s]))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
003
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
004 (def game-start-time
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
005 "The start time of this run."
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
006 (System/currentTimeMillis))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
007
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
008 (def ^:const game-day-length
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
009 "The Java clock advances in milliseconds, which is fine.
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
010 But we need game-days to be shorter than real world days.
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
011 A Witcher 3 game day is 1 hour 36 minutes, or 96 minutes, which is
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
012 presumably researched. Round it up to 100 minutes for easier
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
013 calculation."
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
|
014 (* 100 ;; minutes per game day
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
015 60 ;; seconds per minute
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
016 1000)) ;; milliseconds per second
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
017
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
018 (defn now
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
019 "For now, we'll use Java timestamp for time; ultimately, we need a
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
020 concept of game-time which allows us to drive day/night cycle, seasons,
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
021 et cetera, but what matters about time is that it is a value which
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
022 increases."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
023 []
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
024 (System/currentTimeMillis))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
025
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
026 (def ^:const canonical-ordering-of-houses
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
027 "The canonical ordering of religious houses."
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="10 out of 10 forms covered">
|
||||||
|
028 [:eye
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
029 :foot
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
030 :nose
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
031 :hand
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
032 :ear
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
033 :mouth
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
034 :stomach
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
035 :furrow
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
036 :plough])
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
037
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
038 (def ^:const days-of-week
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
039 "The eight-day week of the game world. This differs from the canonical
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
040 ordering of houses in that it omits the eye."
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
041 (rest canonical-ordering-of-houses))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
042
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
043 (def ^:const days-in-week
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
044 "This world has an eight day week."
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
045 (count days-of-week))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
046
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
047 (def ^:const seasons-of-year
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
048 "The ordering of seasons in the year is different from the canonical
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
049 ordering of the houses, for reasons of the agricultural cycle."
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="10 out of 10 forms covered">
|
||||||
|
050 [:foot
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
051 :nose
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
052 :hand
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
053 :ear
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
054 :mouth
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
055 :stomach
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
056 :plough
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
057 :furrow
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
058 :eye])
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
059
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
060 (def ^:const seasons-in-year
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
061 "Nine seasons in a year, one for each house (although the order is
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
062 different."
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
063 (count seasons-of-year))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
064
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
065 (def ^:const weeks-of-season
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
066 "To fit nine seasons of eight day weeks into 365 days, each must be of
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
067 five weeks."
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="6 out of 6 forms covered">
|
||||||
|
068 [:first :second :third :fourth :fifth])
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
069
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
070 (def ^:const weeks-in-season
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
071 "To fit nine seasons of eight day weeks into 365 days, each must be of
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
072 five weeks."
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
073 (count weeks-of-season))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
074
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
075 (def ^:const days-in-season
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
076 (* weeks-in-season days-in-week))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
077
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
078 (defn game-time
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
079 "With no arguments, the current game time. If a Java `timestamp` value is
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
080 passed (as a `long`), the game time represented by that value."
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="4 out of 4 forms covered">
|
||||||
|
081 ([] (game-time (now)))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
082 ([timestamp]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
083 (- timestamp game-start-time)))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
084
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="54 out of 54 forms covered">
|
||||||
|
085 (defmacro day-of-year
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
086 "The day of the year represented by this `game-time`, ignoring leap years."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
087 [game-time]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
088 `(mod (long (/ ~game-time game-day-length)) 365))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
089
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
090 (def waiting-day?
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
091 "Does this `game-time` represent a waiting day?"
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
092 (memoize
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
093 ;; we're likely to call this several times in quick succession on the
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
094 ;; same timestamp
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
095 (fn [game-time]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
096 (>=
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
|
097 (day-of-year game-time)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
|
098 (* seasons-in-year weeks-in-season days-in-week)))))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
099
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
100 (defn day
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
101 "Day of the eight-day week represented by this `game-time`."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
102 [game-time]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
103 (let [day-of-week (mod (day-of-year game-time) days-in-week)]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="4 out of 4 forms covered">
|
||||||
|
104 (if (waiting-day? game-time)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
105 (nth weeks-of-season day-of-week)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
106 (nth days-of-week day-of-week))))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
107
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
108 (defn week
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
109 "Week of season represented by this `game-time`."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
110 [game-time]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
111 (let [day-of-season (mod (day-of-year game-time) days-in-season)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
112 week (/ day-of-season days-in-week)]
|
||||||
|
</span><br/>
|
||||||
|
<span class="partial" title="4 out of 5 forms covered">
|
||||||
|
113 (if (waiting-day? game-time)
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
114 :waiting
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
115 (nth weeks-of-season week))))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
116
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
117 (defn season
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
118 [game-time]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
119 (let [season (int (/ (day-of-year game-time) days-in-season))]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="5 out of 5 forms covered">
|
||||||
|
120 (if (waiting-day? game-time)
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
121 :waiting
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
122 (nth seasons-of-year season))))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
123
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
124 (defn date-string
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
125 "Return a correctly formatted date for this `game-time` in the calendar of
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
126 the Great Place."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
127 [game-time]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
128 (s/join
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
129 " "
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
130 (if
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
131 (waiting-day? game-time)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="4 out of 4 forms covered">
|
||||||
|
132 [(s/capitalize
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
133 (name
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
134 (nth
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
135 weeks-of-season
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="8 out of 8 forms covered">
|
||||||
|
136 (mod (day-of-year game-time) days-in-week))))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
137 "waiting day"]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
|
138 [(s/capitalize (name (week game-time)))
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="7 out of 7 forms covered">
|
||||||
|
139 (s/capitalize (name (day game-time)))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
140 "of the"
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="7 out of 7 forms covered">
|
||||||
|
141 (s/capitalize (name (season game-time)))])))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
142
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
143
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
144
|
||||||
|
</span><br/>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -19,7 +19,7 @@
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
005 [route]
|
005 [route]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="10 out of 10 forms covered">
|
<span class="covered" title="8 out of 8 forms covered">
|
||||||
006 (not= (count route)(count (set route))))
|
006 (not= (count route)(count (set route))))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="blank" title="0 out of 0 forms covered">
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
<span class="covered" title="6 out of 6 forms covered">
|
<span class="covered" title="6 out of 6 forms covered">
|
||||||
033 (list (first %) 'm)
|
033 (list (first %) 'm)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="4 out of 4 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
034 (nth % 1))
|
034 (nth % 1))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="1 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
|
119
docs/cloverage/the_great_game/world/location.clj.html
Normal file
119
docs/cloverage/the_great_game/world/location.clj.html
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<link rel="stylesheet" href="../../coverage.css"/> <title> the_great_game/world/location.clj </title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
001 (ns the-great-game.world.location
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
002 "Functions dealing with location in the world."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
003 (:require [clojure.math.numeric-tower :refer [expt sqrt]]))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
004
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
005 ;; A 'location' value is a list comprising at most the x/y coordinate location
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
006 ;; and the ids of the settlement and region (possibly hierarchically) that contain
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
007 ;; the location. If the x/y is not local to the home of the receiving agent, they
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
008 ;; won't remember it and won't pass it on; if any of the ids are not interesting
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
009 ;; So location information will degrade progressively as the item is passed along.
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
010
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
011 ;; It is assumed that the `:home` of a character is a location in this sense.
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
012
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
013 (defn get-coords
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
014 "Return the coordinates in the game world of `location`, which may be
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
015 1. A coordinate pair in the format {:x 5 :y 32};
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
016 2. A location, as discussed above;
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
017 3. Any other gameworld object, having a `:location` property whose value
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
018 is one of the above."
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
019 [location]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="4 out of 4 forms covered">
|
||||||
|
020 (cond
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
021 (empty? location) nil
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
022 (map? location)
|
||||||
|
</span><br/>
|
||||||
|
<span class="partial" title="1 out of 3 forms covered">
|
||||||
|
023 (cond
|
||||||
|
</span><br/>
|
||||||
|
<span class="partial" title="13 out of 14 forms covered">
|
||||||
|
024 (and (number? (:x location)) (number? (:y location)))
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
025 location
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
|
026 (:location location)
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
|
027 (:location location))
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
028 :else
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="8 out of 8 forms covered">
|
||||||
|
029 (get-coords (first (remove keyword? location)))))
|
||||||
|
</span><br/>
|
||||||
|
<span class="blank" title="0 out of 0 forms covered">
|
||||||
|
030
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
031 (defn distance-between
|
||||||
|
</span><br/>
|
||||||
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
|
032 [location-1 location-2]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="4 out of 4 forms covered">
|
||||||
|
033 (let [c1 (get-coords location-1)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
|
034 c2 (get-coords location-2)]
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
|
035 (when
|
||||||
|
</span><br/>
|
||||||
|
<span class="partial" title="5 out of 6 forms covered">
|
||||||
|
036 (and c1 c2)
|
||||||
|
</span><br/>
|
||||||
|
<span class="covered" title="23 out of 23 forms covered">
|
||||||
|
037 (sqrt (+ (expt (- (:x c1) (:x c2)) 2) (expt (- (:y c1) (:y c2)) 2))))))
|
||||||
|
</span><br/>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -49,10 +49,10 @@
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
015 (remove
|
015 (remove
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="4 out of 4 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
016 #(= from %)
|
016 #(= from %)
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="10 out of 10 forms covered">
|
<span class="covered" title="9 out of 9 forms covered">
|
||||||
017 (if (some #(= % from) route) route)))
|
017 (if (some #(= % from) route) route)))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="1 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
<span class="covered" title="2 out of 2 forms covered">
|
<span class="covered" title="2 out of 2 forms covered">
|
||||||
021 found (filter
|
021 found (filter
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="11 out of 11 forms covered">
|
<span class="covered" title="10 out of 10 forms covered">
|
||||||
022 (fn [step] (if (some #(= to %) step) step))
|
022 (fn [step] (if (some #(= to %) step) step))
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="1 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
@ -121,7 +121,7 @@
|
||||||
<span class="covered" title="3 out of 3 forms covered">
|
<span class="covered" title="3 out of 3 forms covered">
|
||||||
039 found (filter
|
039 found (filter
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="7 out of 7 forms covered">
|
<span class="covered" title="6 out of 6 forms covered">
|
||||||
040 #(= (last %) to) paths)]
|
040 #(= (last %) to) paths)]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="covered" title="1 out of 1 forms covered">
|
<span class="covered" title="1 out of 1 forms covered">
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
<span class="not-covered" title="0 out of 6 forms covered">
|
<span class="not-covered" title="0 out of 6 forms covered">
|
||||||
018 {:path "the-great-game.log"
|
018 {:path "the-great-game.log"
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 4 forms covered">
|
<span class="not-covered" title="0 out of 3 forms covered">
|
||||||
019 :max-size (* 512 1024)
|
019 :max-size (* 512 1024)
|
||||||
</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">
|
||||||
|
|
|
@ -571,7 +571,7 @@
|
||||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||||
189 ([world]
|
189 ([world]
|
||||||
</span><br/>
|
</span><br/>
|
||||||
<span class="not-covered" title="0 out of 13 forms covered">
|
<span class="not-covered" title="0 out of 12 forms covered">
|
||||||
190 (run world (inc (or (:date world) 0))))
|
190 (run world (inc (or (:date world) 0))))
|
||||||
</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">
|
||||||
|
|
54
docs/codox/Baking-the-world.html
Normal file
54
docs/codox/Baking-the-world.html
Normal file
File diff suppressed because one or more lines are too long
31
docs/codox/Game_Play.html
Normal file
31
docs/codox/Game_Play.html
Normal file
File diff suppressed because one or more lines are too long
56
docs/codox/Gossip_scripted_plot_and_Johnny_Silverhand.html
Normal file
56
docs/codox/Gossip_scripted_plot_and_Johnny_Silverhand.html
Normal file
File diff suppressed because one or more lines are too long
13
docs/codox/NewCh1.html
Normal file
13
docs/codox/NewCh1.html
Normal file
File diff suppressed because one or more lines are too long
45
docs/codox/Organic_Quests.html
Normal file
45
docs/codox/Organic_Quests.html
Normal file
File diff suppressed because one or more lines are too long
297
docs/codox/Populating-a-game-world.html
Normal file
297
docs/codox/Populating-a-game-world.html
Normal file
File diff suppressed because one or more lines are too long
68
docs/codox/Settling-a-game-world.html
Normal file
68
docs/codox/Settling-a-game-world.html
Normal file
File diff suppressed because one or more lines are too long
13
docs/codox/Simulation-layers.html
Normal file
13
docs/codox/Simulation-layers.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
7
docs/codox/Uncanny_dialogue.html
Normal file
7
docs/codox/Uncanny_dialogue.html
Normal file
File diff suppressed because one or more lines are too long
49
docs/codox/Voice-acting-considered-harmful.html
Normal file
49
docs/codox/Voice-acting-considered-harmful.html
Normal file
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
26
docs/codox/naming-of-characters.html
Normal file
26
docs/codox/naming-of-characters.html
Normal file
File diff suppressed because one or more lines are too long
9
docs/codox/on-dying.html
Normal file
9
docs/codox/on-dying.html
Normal file
File diff suppressed because one or more lines are too long
34
docs/codox/orgnic-quests.html
Normal file
34
docs/codox/orgnic-quests.html
Normal file
File diff suppressed because one or more lines are too long
39
docs/codox/sandbox.html
Normal file
39
docs/codox/sandbox.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
docs/codox/the-great-game.agent.agent.html
Normal file
3
docs/codox/the-great-game.agent.agent.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
19
docs/codox/the-great-game.gossip.news-items.html
Normal file
19
docs/codox/the-great-game.gossip.news-items.html
Normal file
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
3
docs/codox/the-great-game.time.html
Normal file
3
docs/codox/the-great-game.time.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
docs/codox/the-great-game.world.location.html
Normal file
3
docs/codox/the-great-game.world.location.html
Normal file
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
|
@ -1,13 +1,14 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
<title>The Great Game: Dcocumentation</title>
|
<title>The Great Game: Documentation</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="codox/css/default.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>The Great Game: Dcocumentation</h1>
|
<h1>The Great Game: Documentation</h1>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="cloverage/index.html">Test coverage</a></li>
|
|
||||||
<li><a href="codox/index.html">Primary documentaion</a></li>
|
<li><a href="codox/index.html">Primary documentaion</a></li>
|
||||||
|
<li><a href="cloverage/index.html">Test coverage</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject the-great-game "0.1.1-SNAPSHOT"
|
(defproject the-great-game "0.1.1"
|
||||||
: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}
|
||||||
|
@ -6,6 +6,7 @@
|
||||||
: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 [[org.clojure/clojure "1.8.0"]
|
||||||
|
[org.clojure/math.numeric-tower "0.0.4"]
|
||||||
[environ "1.1.0"]
|
[environ "1.1.0"]
|
||||||
[com.taoensso/timbre "4.10.0"]]
|
[com.taoensso/timbre "4.10.0"]]
|
||||||
:description "Prototype code towards the great game I've been writing about for ten years, and know I will never finish."
|
:description "Prototype code towards the great game I've been writing about for ten years, and know I will never finish."
|
||||||
|
|
BIN
resources/maps/heightmap.inverted.png
Normal file
BIN
resources/maps/heightmap.inverted.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 249 KiB |
BIN
resources/maps/heightmap.inverted.xcf
Normal file
BIN
resources/maps/heightmap.inverted.xcf
Normal file
Binary file not shown.
BIN
resources/maps/heightmap.png
Normal file
BIN
resources/maps/heightmap.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 349 KiB |
325
resources/maps/planning-map.svg
Normal file
325
resources/maps/planning-map.svg
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="1189mm"
|
||||||
|
height="841mm"
|
||||||
|
viewBox="0 0 1189 841"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||||
|
sodipodi:docname="planning-map.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.49497475"
|
||||||
|
inkscape:cx="1453.7159"
|
||||||
|
inkscape:cy="582.07921"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1016"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,544)">
|
||||||
|
<image
|
||||||
|
sodipodi:absref="/home/simon/workspace/the-great-game/resources/maps/heightmap.inverted.png"
|
||||||
|
xlink:href="heightmap.inverted.png"
|
||||||
|
style="fill:#550000"
|
||||||
|
width="1164.0658"
|
||||||
|
height="1164.0658"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
id="image985"
|
||||||
|
x="142.94299"
|
||||||
|
y="-531.93903" />
|
||||||
|
<ellipse
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
id="path988"
|
||||||
|
cx="473.33432"
|
||||||
|
cy="72.760857"
|
||||||
|
rx="1.3363476"
|
||||||
|
ry="1.0690781" />
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="161.49434"
|
||||||
|
cx="441.79651"
|
||||||
|
id="ellipse999"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="118.9985"
|
||||||
|
cx="486.16327"
|
||||||
|
id="ellipse999-4"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="158.28711"
|
||||||
|
cx="537.74628"
|
||||||
|
id="ellipse999-8"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="129.08792"
|
||||||
|
cx="528.25818"
|
||||||
|
id="ellipse999-3"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="63.406433"
|
||||||
|
cx="812.23212"
|
||||||
|
id="ellipse999-1"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="9.6852579"
|
||||||
|
cx="794.05774"
|
||||||
|
id="ellipse999-2"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="165.77066"
|
||||||
|
cx="667.10474"
|
||||||
|
id="ellipse999-28"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="477.34338"
|
||||||
|
y="73.829979"
|
||||||
|
id="text1048"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1046"
|
||||||
|
x="477.34338"
|
||||||
|
y="73.829979"
|
||||||
|
style="stroke-width:0.26458332px">Hans'hua</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="445.271"
|
||||||
|
y="160.42528"
|
||||||
|
id="text1052"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1050"
|
||||||
|
x="445.271"
|
||||||
|
y="160.42528"
|
||||||
|
style="stroke-width:0.26458332px">Tchahua</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="486.43051"
|
||||||
|
y="124.07665"
|
||||||
|
id="text1056"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1054"
|
||||||
|
x="486.43051"
|
||||||
|
y="124.07665"
|
||||||
|
style="stroke-width:0.26458332px">Sinhua</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="541.48804"
|
||||||
|
y="158.5544"
|
||||||
|
id="text1060"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1058"
|
||||||
|
x="541.48804"
|
||||||
|
y="158.5544"
|
||||||
|
style="stroke-width:0.26458332px">Huandun</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="530.53003"
|
||||||
|
y="131.29291"
|
||||||
|
id="text1064"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1062"
|
||||||
|
x="530.53003"
|
||||||
|
y="131.29291"
|
||||||
|
style="stroke-width:0.26458332px">Koantuan</tspan></text>
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="8.0816402"
|
||||||
|
cx="302.5491"
|
||||||
|
id="ellipse999-6"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="306.02362"
|
||||||
|
y="8.6161976"
|
||||||
|
id="text1083"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1081"
|
||||||
|
x="306.02362"
|
||||||
|
y="8.6161976"
|
||||||
|
style="stroke-width:0.26458332px">Silverhold</tspan></text>
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="48.973877"
|
||||||
|
cx="288.65109"
|
||||||
|
id="ellipse999-12"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#550000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="291.32379"
|
||||||
|
y="50.577526"
|
||||||
|
id="text1093"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1091"
|
||||||
|
x="291.32379"
|
||||||
|
y="50.577526"
|
||||||
|
style="stroke-width:0.26458332px">Longwater</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="797.79956"
|
||||||
|
y="9.9525499"
|
||||||
|
id="text1097"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1095"
|
||||||
|
x="797.79956"
|
||||||
|
y="9.9525499"
|
||||||
|
style="stroke-width:0.26458332px">Horsewatermeet</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="773.47803"
|
||||||
|
y="58.595604"
|
||||||
|
id="text1101"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1099"
|
||||||
|
x="773.47803"
|
||||||
|
y="58.595604"
|
||||||
|
style="stroke-width:0.26458332px">The City at Her Gates</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="670.31201"
|
||||||
|
y="166.83974"
|
||||||
|
id="text1105"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
x="670.31201"
|
||||||
|
y="166.83974"
|
||||||
|
style="stroke-width:0.26458332px"
|
||||||
|
id="tspan1177">Quanjun</tspan></text>
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="65.811859"
|
||||||
|
cx="636.36877"
|
||||||
|
id="ellipse999-28-3"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="132.36197"
|
||||||
|
cx="466.38531"
|
||||||
|
id="ellipse999-28-9"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="469.8598"
|
||||||
|
y="134.76741"
|
||||||
|
id="text1130"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1128"
|
||||||
|
x="469.8598"
|
||||||
|
y="134.76741"
|
||||||
|
style="stroke-width:0.26458332px">Black Ford</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="607.23639"
|
||||||
|
y="70.889977"
|
||||||
|
id="text1134"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1132"
|
||||||
|
x="607.23639"
|
||||||
|
y="70.889977"
|
||||||
|
style="stroke-width:0.26458332px">Dragon Festival Site</tspan></text>
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="106.16956"
|
||||||
|
cx="462.10904"
|
||||||
|
id="ellipse999-28-95"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="465.58353"
|
||||||
|
y="107.50593"
|
||||||
|
id="text1163"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1161"
|
||||||
|
x="465.58353"
|
||||||
|
y="107.50593"
|
||||||
|
style="stroke-width:0.26458332px">South </tspan><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
x="465.58353"
|
||||||
|
y="112.46687"
|
||||||
|
style="stroke-width:0.26458332px"
|
||||||
|
id="tspan1165">Inn</tspan></text>
|
||||||
|
<ellipse
|
||||||
|
ry="1.0690781"
|
||||||
|
rx="1.3363476"
|
||||||
|
cy="38.817635"
|
||||||
|
cx="469.05801"
|
||||||
|
id="ellipse999-28-2"
|
||||||
|
style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:0.26499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.96875px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="472.53253"
|
||||||
|
y="40.955826"
|
||||||
|
id="text1175"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1173"
|
||||||
|
x="472.53253"
|
||||||
|
y="40.955826"
|
||||||
|
style="stroke-width:0.26458332px">North Inn</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 16 KiB |
7
src/the_great_game/agent/agent.clj
Normal file
7
src/the_great_game/agent/agent.clj
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
(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.
|
||||||
|
|
40
src/the_great_game/architecture.md
Normal file
40
src/the_great_game/architecture.md
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
OK, the basic idea is this
|
||||||
|
|
||||||
|
Everything (every game object, including the world) is a map.
|
||||||
|
|
||||||
|
Every object as an :id property; every :id property is distinct.
|
||||||
|
|
||||||
|
There is a master map - the `oblist` which contains every object, keyed by its :id.
|
||||||
|
|
||||||
|
Every object has a :run function, which returns either a new copy of itself or nil, and does not have side effects.
|
||||||
|
|
||||||
|
Every object has a :location function, which takes one argument, the object, and returns its location as a coordinate pair (or coordinate triple, probably) (this may involve fetching the location from the container in which it is contained, which implies that a contained object must hold a handle to its container).
|
||||||
|
|
||||||
|
Every collection of things in the world is represented as a list of :id values, by which the actual objects can be fetched from the `oblist`.
|
||||||
|
|
||||||
|
## Circles
|
||||||
|
|
||||||
|
Among those collections are the circles. The circles include, at minimum
|
||||||
|
|
||||||
|
1. Those objects in audible/visual range of the player; these have their run method invoked avery game loop. Weather, is always in this circle. The sun and moon are in this circle from shortly becore they rise to shortly after they set.
|
||||||
|
2. Those objects which might come into audible/visual range within a short period; these have their run method invoked every N game loops, where N is probably variable depending on overall system load
|
||||||
|
3. Those objects (actors) which are necessary to maintain the gossip system, etc. These should each have their run method invoked once per game day, but that is done by invoking the run method of a share of them each game loop.
|
||||||
|
|
||||||
|
So `run` takes three arguments - the object, the world and the circle; and returns nil if it makes no change, or a new copy of itself; and probably each of the main functions that run calls have the same behaviour. So, for example, a hierarchy of needs can be represented by
|
||||||
|
|
||||||
|
|
||||||
|
(defn run [character world circle]
|
||||||
|
(first
|
||||||
|
(handle-immediate-threat character world circle) ;; if being attacked, deal with it
|
||||||
|
(complete-current-action character world circle) ;; otherwise, continue the current
|
||||||
|
;; short-term unless completed
|
||||||
|
(handle-thirst character world circle) ;; perhaps adjust tactical plan to find water
|
||||||
|
(handle-hunger character world circle) ;; perhaps adjust tactical plan to find food
|
||||||
|
(handle-fatigue character world circle) ;; perhaps rest if safe to do so
|
||||||
|
(advance-current-plan character world circle) ;; select next step of current strategic plan
|
||||||
|
(select-next-plan character world circle) ;; plan new strategic objective
|
||||||
|
(return-home character world circle))) ;; if no other strategic objective, return
|
||||||
|
;; to home location
|
||||||
|
|
||||||
|
|
||||||
|
Atoms? Background threads?
|
|
@ -1,6 +1,7 @@
|
||||||
(ns the-great-game.gossip.gossip
|
(ns the-great-game.gossip.gossip
|
||||||
"Interchange of news events between agents agents"
|
"Interchange of news events between gossip agents"
|
||||||
(:require [the-great-game.utils :refer [deep-merge]]))
|
(:require [the-great-game.utils :refer [deep-merge]]
|
||||||
|
[the-great-game.gossip.news-items :refer [learn-news-item]]))
|
||||||
|
|
||||||
;; Note that habitual travellers are all gossip agents; specifically, at this
|
;; 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
|
||||||
|
@ -43,7 +44,7 @@
|
||||||
(defn move-gossip
|
(defn move-gossip
|
||||||
"Return a world like this `world` but with this `gossip` moved to this
|
"Return a world like this `world` but with this `gossip` moved to this
|
||||||
`new-location`. Many gossips are essentially shadow-records of agents of
|
`new-location`. Many gossips are essentially shadow-records of agents of
|
||||||
other types, and the movement if the gossip should be controlled by the
|
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
|
run function of the type of the record they shadow. The [[#run]] function
|
||||||
below does NOT call this function."
|
below does NOT call this function."
|
||||||
[gossip world new-location]
|
[gossip world new-location]
|
||||||
|
|
256
src/the_great_game/gossip/news_items.clj
Normal file
256
src/the_great_game/gossip/news_items.clj
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
(ns the-great-game.gossip.news-items
|
||||||
|
"Categories of news events interesting to gossip agents"
|
||||||
|
(:require [the-great-game.world.location :refer [distance-between]]
|
||||||
|
[the-great-game.time :refer [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
|
||||||
|
;; 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
|
||||||
|
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.
|
||||||
|
"
|
||||||
|
{ ;; 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]}
|
||||||
|
;; Deliberate killings are interesting.
|
||||||
|
: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}]}
|
||||||
|
;; 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}]}
|
||||||
|
;; 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]}
|
||||||
|
;; Rapes are interesting.
|
||||||
|
:rape {:verb :rape :keys [:actor :other :location]
|
||||||
|
;; Should you also infer from rape that actor is male and adult?
|
||||||
|
:inferences [{:verb :attack}
|
||||||
|
{:verb :sex}
|
||||||
|
{:verb :sex :actor :other :other :actor}]}
|
||||||
|
;; Merchants, especially, are interested in prices in other markets
|
||||||
|
: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]}
|
||||||
|
;; 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}]}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
(defn interest-in-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."
|
||||||
|
[gossip character]
|
||||||
|
(count
|
||||||
|
(concat
|
||||||
|
(filter #(= (:actor % character)) (:knowledge gossip))
|
||||||
|
(filter #(= (:other % character)) (:knowledge gossip)))))
|
||||||
|
|
||||||
|
(defn interesting-character?
|
||||||
|
"Boolean representation of whether this `character` is interesting to this
|
||||||
|
`gossip`."
|
||||||
|
[gossip character]
|
||||||
|
(> (interest-in-character gossip character) 0))
|
||||||
|
|
||||||
|
(defn interest-in-location
|
||||||
|
"Integer representation of how interesting this `location` is to this
|
||||||
|
`gossip`."
|
||||||
|
[gossip location]
|
||||||
|
(cond
|
||||||
|
(and (map? location) (number? (:x location)) (number? (:y location)))
|
||||||
|
(if-let [home (:home gossip)]
|
||||||
|
(let [d (distance-between location home)
|
||||||
|
i (/ 10000 d) ;; 10000 at metre scale is 10km; interest should
|
||||||
|
;;fall off with distance from home, but possibly on a log scale
|
||||||
|
]
|
||||||
|
(if (> i 1) i 0))
|
||||||
|
0)
|
||||||
|
(coll? location)
|
||||||
|
(reduce
|
||||||
|
+
|
||||||
|
(map
|
||||||
|
#(interest-in-location gossip %)
|
||||||
|
location))
|
||||||
|
:else
|
||||||
|
(count
|
||||||
|
(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))
|
||||||
|
|
||||||
|
(defn interesting-object?
|
||||||
|
[gossip object]
|
||||||
|
;; TODO: Not yet (really) implemented
|
||||||
|
true)
|
||||||
|
|
||||||
|
(defn interesting-topic?
|
||||||
|
[gossip topic]
|
||||||
|
;; TODO: Not yet (really) implemented
|
||||||
|
true)
|
||||||
|
|
||||||
|
(defn interesting-item?
|
||||||
|
"True if anything about this news `item` is interesting to this `gossip`."
|
||||||
|
[gossip item]
|
||||||
|
(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 infer
|
||||||
|
"Infer a new knowledge item from this `item`, following this `rule`"
|
||||||
|
[item rule]
|
||||||
|
(reduce merge
|
||||||
|
item
|
||||||
|
(cons
|
||||||
|
{:verb (:verb rule)}
|
||||||
|
(map (fn [k] {k (apply (k rule) (list item))})
|
||||||
|
(remove
|
||||||
|
#(= % :verb)
|
||||||
|
(keys rule))))))
|
||||||
|
|
||||||
|
(declare learn-news-item)
|
||||||
|
|
||||||
|
(defn make-all-inferences
|
||||||
|
"Return a list 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)))))))
|
||||||
|
|
||||||
|
(defn degrade-character
|
||||||
|
"Return a character specification like this `character`, but comprising
|
||||||
|
only those properties this `gossip` is interested in."
|
||||||
|
[gossip character]
|
||||||
|
;; TODO: Not yet (really) implemented
|
||||||
|
character)
|
||||||
|
|
||||||
|
(defn degrade-location
|
||||||
|
"Return a location specification like this `location`, but comprising
|
||||||
|
only those elements this `gossip` is interested in. If none, return
|
||||||
|
`nil`."
|
||||||
|
[gossip location]
|
||||||
|
(let [l (if
|
||||||
|
(coll? location)
|
||||||
|
(filter
|
||||||
|
#(when (interesting-location? gossip %) %)
|
||||||
|
location))]
|
||||||
|
(when-not (empty? l) l)))
|
||||||
|
|
||||||
|
(defn learn-news-item
|
||||||
|
"Return a gossip like this `gossip`, which has learned this news `item` if
|
||||||
|
it is of interest to them."
|
||||||
|
;; TODO: Not yet implemented
|
||||||
|
([gossip item]
|
||||||
|
(learn-news-item gossip item 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)))]
|
||||||
|
(if follow-inferences?
|
||||||
|
(assoc
|
||||||
|
g
|
||||||
|
:knowledge
|
||||||
|
(concat (:knowledge g) (make-all-inferences item)))
|
||||||
|
g))
|
||||||
|
gossip)))
|
||||||
|
|
||||||
|
|
||||||
|
|
144
src/the_great_game/time.clj
Normal file
144
src/the_great_game/time.clj
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
(ns the-great-game.time
|
||||||
|
(:require [clojure.string :as s]))
|
||||||
|
|
||||||
|
(def game-start-time
|
||||||
|
"The start time of this run."
|
||||||
|
(System/currentTimeMillis))
|
||||||
|
|
||||||
|
(def ^:const game-day-length
|
||||||
|
"The Java clock advances in milliseconds, which is fine.
|
||||||
|
But we need game-days to be shorter than real world days.
|
||||||
|
A Witcher 3 game day is 1 hour 36 minutes, or 96 minutes, which is
|
||||||
|
presumably researched. Round it up to 100 minutes for easier
|
||||||
|
calculation."
|
||||||
|
(* 100 ;; minutes per game day
|
||||||
|
60 ;; seconds per minute
|
||||||
|
1000)) ;; milliseconds per second
|
||||||
|
|
||||||
|
(defn now
|
||||||
|
"For now, we'll use Java timestamp for time; ultimately, we need a
|
||||||
|
concept of game-time which allows us to drive day/night cycle, seasons,
|
||||||
|
et cetera, but what matters about time is that it is a value which
|
||||||
|
increases."
|
||||||
|
[]
|
||||||
|
(System/currentTimeMillis))
|
||||||
|
|
||||||
|
(def ^:const canonical-ordering-of-houses
|
||||||
|
"The canonical ordering of religious houses."
|
||||||
|
[:eye
|
||||||
|
:foot
|
||||||
|
:nose
|
||||||
|
:hand
|
||||||
|
:ear
|
||||||
|
:mouth
|
||||||
|
:stomach
|
||||||
|
:furrow
|
||||||
|
:plough])
|
||||||
|
|
||||||
|
(def ^:const days-of-week
|
||||||
|
"The eight-day week of the game world. This differs from the canonical
|
||||||
|
ordering of houses in that it omits the eye."
|
||||||
|
(rest canonical-ordering-of-houses))
|
||||||
|
|
||||||
|
(def ^:const days-in-week
|
||||||
|
"This world has an eight day week."
|
||||||
|
(count days-of-week))
|
||||||
|
|
||||||
|
(def ^:const seasons-of-year
|
||||||
|
"The ordering of seasons in the year is different from the canonical
|
||||||
|
ordering of the houses, for reasons of the agricultural cycle."
|
||||||
|
[:foot
|
||||||
|
:nose
|
||||||
|
:hand
|
||||||
|
:ear
|
||||||
|
:mouth
|
||||||
|
:stomach
|
||||||
|
:plough
|
||||||
|
:furrow
|
||||||
|
:eye])
|
||||||
|
|
||||||
|
(def ^:const seasons-in-year
|
||||||
|
"Nine seasons in a year, one for each house (although the order is
|
||||||
|
different."
|
||||||
|
(count seasons-of-year))
|
||||||
|
|
||||||
|
(def ^:const weeks-of-season
|
||||||
|
"To fit nine seasons of eight day weeks into 365 days, each must be of
|
||||||
|
five weeks."
|
||||||
|
[:first :second :third :fourth :fifth])
|
||||||
|
|
||||||
|
(def ^:const weeks-in-season
|
||||||
|
"To fit nine seasons of eight day weeks into 365 days, each must be of
|
||||||
|
five weeks."
|
||||||
|
(count weeks-of-season))
|
||||||
|
|
||||||
|
(def ^:const days-in-season
|
||||||
|
(* weeks-in-season days-in-week))
|
||||||
|
|
||||||
|
(defn game-time
|
||||||
|
"With no arguments, the current game time. If a Java `timestamp` value is
|
||||||
|
passed (as a `long`), the game time represented by that value."
|
||||||
|
([] (game-time (now)))
|
||||||
|
([timestamp]
|
||||||
|
(- timestamp game-start-time)))
|
||||||
|
|
||||||
|
(defmacro day-of-year
|
||||||
|
"The day of the year represented by this `game-time`, ignoring leap years."
|
||||||
|
[game-time]
|
||||||
|
`(mod (long (/ ~game-time game-day-length)) 365))
|
||||||
|
|
||||||
|
(def waiting-day?
|
||||||
|
"Does this `game-time` represent a waiting day?"
|
||||||
|
(memoize
|
||||||
|
;; we're likely to call this several times in quick succession on the
|
||||||
|
;; same timestamp
|
||||||
|
(fn [game-time]
|
||||||
|
(>=
|
||||||
|
(day-of-year game-time)
|
||||||
|
(* seasons-in-year weeks-in-season days-in-week)))))
|
||||||
|
|
||||||
|
(defn day
|
||||||
|
"Day of the eight-day week represented by this `game-time`."
|
||||||
|
[game-time]
|
||||||
|
(let [day-of-week (mod (day-of-year game-time) days-in-week)]
|
||||||
|
(if (waiting-day? game-time)
|
||||||
|
(nth weeks-of-season day-of-week)
|
||||||
|
(nth days-of-week day-of-week))))
|
||||||
|
|
||||||
|
(defn week
|
||||||
|
"Week of season represented by this `game-time`."
|
||||||
|
[game-time]
|
||||||
|
(let [day-of-season (mod (day-of-year game-time) days-in-season)
|
||||||
|
week (/ day-of-season days-in-week)]
|
||||||
|
(if (waiting-day? game-time)
|
||||||
|
:waiting
|
||||||
|
(nth weeks-of-season week))))
|
||||||
|
|
||||||
|
(defn season
|
||||||
|
[game-time]
|
||||||
|
(let [season (int (/ (day-of-year game-time) days-in-season))]
|
||||||
|
(if (waiting-day? game-time)
|
||||||
|
:waiting
|
||||||
|
(nth seasons-of-year season))))
|
||||||
|
|
||||||
|
(defn date-string
|
||||||
|
"Return a correctly formatted date for this `game-time` in the calendar of
|
||||||
|
the Great Place."
|
||||||
|
[game-time]
|
||||||
|
(s/join
|
||||||
|
" "
|
||||||
|
(if
|
||||||
|
(waiting-day? game-time)
|
||||||
|
[(s/capitalize
|
||||||
|
(name
|
||||||
|
(nth
|
||||||
|
weeks-of-season
|
||||||
|
(mod (day-of-year game-time) days-in-week))))
|
||||||
|
"waiting day"]
|
||||||
|
[(s/capitalize (name (week game-time)))
|
||||||
|
(s/capitalize (name (day game-time)))
|
||||||
|
"of the"
|
||||||
|
(s/capitalize (name (season game-time)))])))
|
||||||
|
|
||||||
|
|
||||||
|
|
37
src/the_great_game/world/location.clj
Normal file
37
src/the_great_game/world/location.clj
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
(ns the-great-game.world.location
|
||||||
|
"Functions dealing with location in the world."
|
||||||
|
(:require [clojure.math.numeric-tower :refer [expt sqrt]]))
|
||||||
|
|
||||||
|
;; A 'location' value is a list comprising at most the x/y coordinate location
|
||||||
|
;; and the ids of the settlement and region (possibly hierarchically) that contain
|
||||||
|
;; 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.
|
||||||
|
|
||||||
|
(defn get-coords
|
||||||
|
"Return the coordinates in the game world of `location`, which may be
|
||||||
|
1. A coordinate pair in the format {:x 5 :y 32};
|
||||||
|
2. A location, as discussed above;
|
||||||
|
3. Any other gameworld object, having a `:location` property whose value
|
||||||
|
is one of the above."
|
||||||
|
[location]
|
||||||
|
(cond
|
||||||
|
(empty? location) nil
|
||||||
|
(map? location)
|
||||||
|
(cond
|
||||||
|
(and (number? (:x location)) (number? (:y location)))
|
||||||
|
location
|
||||||
|
(:location location)
|
||||||
|
(:location location))
|
||||||
|
:else
|
||||||
|
(get-coords (first (remove keyword? location)))))
|
||||||
|
|
||||||
|
(defn distance-between
|
||||||
|
[location-1 location-2]
|
||||||
|
(let [c1 (get-coords location-1)
|
||||||
|
c2 (get-coords location-2)]
|
||||||
|
(when
|
||||||
|
(and c1 c2)
|
||||||
|
(sqrt (+ (expt (- (:x c1) (:x c2)) 2) (expt (- (:y c1) (:y c2)) 2))))))
|
4
test/the_great_game/gossip/gossip_test.clj
Normal file
4
test/the_great_game/gossip/gossip_test.clj
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
(ns the-great-game.gossip.gossip-test
|
||||||
|
(:require [clojure.test :refer :all]
|
||||||
|
[the-great-game.gossip.gossip :refer :all]))
|
||||||
|
|
134
test/the_great_game/gossip/news_items_test.clj
Normal file
134
test/the_great_game/gossip/news_items_test.clj
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
(ns the-great-game.gossip.news-items-test
|
||||||
|
(:require [clojure.test :refer :all]
|
||||||
|
[the-great-game.gossip.news-items :refer :all]))
|
||||||
|
|
||||||
|
|
||||||
|
(deftest location-test
|
||||||
|
(testing "Interest in locations"
|
||||||
|
(let [expected 1
|
||||||
|
actual (interest-in-location
|
||||||
|
{:knowledge [{:verb :steal
|
||||||
|
:actor :albert
|
||||||
|
:other :belinda
|
||||||
|
:object :foo
|
||||||
|
:location [{:x 35 :y 23} :auchencairn :galloway]}]}
|
||||||
|
:galloway)]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected 2
|
||||||
|
actual (interest-in-location
|
||||||
|
{:knowledge [{:verb :steal
|
||||||
|
:actor :albert
|
||||||
|
:other :belinda
|
||||||
|
:object :foo
|
||||||
|
:location [{:x 35 :y 23} :auchencairn :galloway :scotland]}]}
|
||||||
|
[:galloway :scotland])]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected 2
|
||||||
|
actual (interest-in-location
|
||||||
|
{:home [{:x 35 :y 23} :auchencairn :galloway :scotland]}
|
||||||
|
[:galloway :scotland])]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected 0
|
||||||
|
actual (interest-in-location
|
||||||
|
{:knowledge [{:verb :steal
|
||||||
|
:actor :albert
|
||||||
|
:other :belinda
|
||||||
|
:object :foo
|
||||||
|
:location [{:x 35 :y 23} :auchencairn :galloway]}]}
|
||||||
|
[:dumfries])]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected 7071.067811865475
|
||||||
|
actual (interest-in-location
|
||||||
|
{:home [{:x 35 :y 23}]}
|
||||||
|
[{:x 34 :y 24}])]
|
||||||
|
(is (= actual expected)
|
||||||
|
"TODO: 7071.067811865475 is actually a bad answer."))
|
||||||
|
(let [expected 0
|
||||||
|
actual (interest-in-location
|
||||||
|
{:home [{:x 35 :y 23}]}
|
||||||
|
[{:x 34 :y 24000}])]
|
||||||
|
(is (= actual expected)
|
||||||
|
"Too far apart (> 10000)."))
|
||||||
|
(let [expected true
|
||||||
|
actual (interesting-location?
|
||||||
|
{:knowledge [{:verb :steal
|
||||||
|
:actor :albert
|
||||||
|
:other :belinda
|
||||||
|
:object :foo
|
||||||
|
:location [{:x 35 :y 23} :auchencairn :galloway]}]}
|
||||||
|
:galloway)]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected true
|
||||||
|
actual (interesting-location?
|
||||||
|
{:knowledge [{:verb :steal
|
||||||
|
:actor :albert
|
||||||
|
:other :belinda
|
||||||
|
:object :foo
|
||||||
|
:location [{:x 35 :y 23} :auchencairn :galloway]}]}
|
||||||
|
[:galloway :scotland])]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected false
|
||||||
|
actual (interesting-location?
|
||||||
|
{:knowledge [{:verb :steal
|
||||||
|
:actor :albert
|
||||||
|
:other :belinda
|
||||||
|
:object :foo
|
||||||
|
:location [{:x 35 :y 23} :auchencairn :galloway]}]}
|
||||||
|
[:dumfries])]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected true
|
||||||
|
actual (interesting-location?
|
||||||
|
{:home [{:x 35 :y 23}]}
|
||||||
|
[{:x 34 :y 24}])]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected false
|
||||||
|
actual (interesting-location?
|
||||||
|
{:home [{:x 35 :y 23}]}
|
||||||
|
[{:x 34 :y 240000}])]
|
||||||
|
(is (= actual expected))))
|
||||||
|
(testing "Degrading locations"
|
||||||
|
(let [expected [:galloway]
|
||||||
|
actual (degrade-location
|
||||||
|
{:home [{0 0} :test-home :galloway]}
|
||||||
|
[{-4 55} :auchencairn :galloway])]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected nil
|
||||||
|
actual (degrade-location
|
||||||
|
{:home [{0 0} :test-home :galloway]}
|
||||||
|
[:froboz])]
|
||||||
|
(is (= actual expected)))))
|
||||||
|
|
||||||
|
(deftest inference-tests
|
||||||
|
(testing "Ability to infer new knowledge from news items: single rule tests"
|
||||||
|
(let [expected {:verb :marry, :actor :belinda, :other :adam}
|
||||||
|
actual (infer {:verb :marry :actor :adam :other :belinda}
|
||||||
|
{:verb :marry :actor :other :other :actor})]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected {:verb :attack, :actor :adam, :other :belinda}
|
||||||
|
actual (infer {:verb :rape :actor :adam :other :belinda}
|
||||||
|
{:verb :attack})]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected {:verb :sex, :actor :belinda, :other :adam}
|
||||||
|
actual (infer {:verb :rape :actor :adam :other :belinda}
|
||||||
|
{:verb :sex :actor :other :other :actor})]
|
||||||
|
(is (= actual expected))))
|
||||||
|
(testing "Ability to infer new knowledge from news items: all applicable rules"
|
||||||
|
(let [expected #{{:verb :sex, :actor :belinda, :other :adam, :location nil, :nth-hand 1}
|
||||||
|
{:verb :sex, :actor :adam, :other :belinda, :location nil, :nth-hand 1}
|
||||||
|
{:verb :attack, :actor :adam, :other :belinda, :location nil, :nth-hand 1}}
|
||||||
|
;; dates will not be and cannot be expected to be equal
|
||||||
|
actual (make-all-inferences
|
||||||
|
{:verb :rape :actor :adam :other :belinda :location :test-home})
|
||||||
|
actual' (set (map #(dissoc % :time-stamp) actual))]
|
||||||
|
(is (= actual' expected)))))
|
||||||
|
|
||||||
|
(deftest learn-tests
|
||||||
|
(testing "Learning from an interesting news item."
|
||||||
|
(let [expected {:home [{0 0} :test-home],
|
||||||
|
:knowledge [{:verb :sex, :actor :adam, :other :belinda, :location nil, :nth-hand 1}
|
||||||
|
{:verb :sex, :actor :belinda, :other :adam, :location nil, :nth-hand 1}]}
|
||||||
|
actual (learn-news-item
|
||||||
|
{:home [{0, 0} :test-home] :knowledge []}
|
||||||
|
{:verb :sex :actor :adam :other :belinda :location [:test-home]})
|
||||||
|
actual' (assoc actual :knowledge (vec (map #(dissoc % :time-stamp) (:knowledge actual))))]
|
||||||
|
(is (= actual' expected)))))
|
79
test/the_great_game/time_test.clj
Normal file
79
test/the_great_game/time_test.clj
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
(ns the-great-game.time-test
|
||||||
|
(:require [clojure.test :refer :all]
|
||||||
|
;; [clojure.core.async :refer [thread <!]]
|
||||||
|
[the-great-game.time :refer :all]))
|
||||||
|
|
||||||
|
(deftest now-tests
|
||||||
|
(testing "Time progresses"
|
||||||
|
(let [t1 (now)]
|
||||||
|
(is (> t1 game-start-time))
|
||||||
|
(Thread/sleep 1000)
|
||||||
|
(is (> (now) t1)))))
|
||||||
|
|
||||||
|
(deftest game-time-tests
|
||||||
|
(testing "Getting game-time"
|
||||||
|
(is (= (game-time (inc game-start-time)) 1))))
|
||||||
|
|
||||||
|
(deftest calendar-tests
|
||||||
|
(testing "In-game calendar functions"
|
||||||
|
(let [expected :foot
|
||||||
|
actual (day 0)]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected :stomach
|
||||||
|
actual (day (* 5 game-day-length))]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected :foot
|
||||||
|
actual (day (* days-in-week game-day-length))]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected :first ;; waiting day
|
||||||
|
actual (day (* 360 game-day-length))]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected :first
|
||||||
|
actual (week 0)]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected :second
|
||||||
|
actual (week (* days-in-week game-day-length))]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected :first
|
||||||
|
actual (week (* days-in-season game-day-length))]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected :foot
|
||||||
|
actual (season 0)]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected :mouth
|
||||||
|
actual (season (* 180 game-day-length))]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected :eye
|
||||||
|
actual (season (* 359 game-day-length))]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected :waiting
|
||||||
|
actual (season (* 360 game-day-length))]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected :foot
|
||||||
|
actual (season (* 365 game-day-length))]
|
||||||
|
(is (= actual expected)))))
|
||||||
|
|
||||||
|
(deftest date-string-tests
|
||||||
|
(testing "Date-string formatting"
|
||||||
|
(let [expected "First Foot of the Foot"
|
||||||
|
actual (date-string 0)]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected "First Foot of the Nose"
|
||||||
|
actual (date-string
|
||||||
|
(* days-in-season game-day-length))]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected "Third Mouth of the Mouth"
|
||||||
|
actual (date-string (* 180 game-day-length))]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected "Fifth Plough of the Eye"
|
||||||
|
actual (date-string (* 359 game-day-length))]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected "First waiting day"
|
||||||
|
actual (date-string (* 360 game-day-length))]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected "First Foot of the Foot"
|
||||||
|
actual (date-string (* 365 game-day-length))]
|
||||||
|
(is (= actual expected)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
36
test/the_great_game/world/location_test.clj
Normal file
36
test/the_great_game/world/location_test.clj
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
(ns the-great-game.world.location-test
|
||||||
|
(:require [clojure.test :refer :all]
|
||||||
|
[the-great-game.world.location :refer :all]))
|
||||||
|
|
||||||
|
(deftest get-coords-test
|
||||||
|
(testing "Get coordinates of location"
|
||||||
|
(let [expected {:x 5 :y 7}
|
||||||
|
actual (get-coords {:x 5 :y 7})]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected {:x -4 :y 55}
|
||||||
|
actual (get-coords [{:x -4 :y 55} :auchencairn :galloway :scotland])]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected nil
|
||||||
|
actual (get-coords [:auchencairn :galloway :scotland])]
|
||||||
|
(is (= actual expected)))
|
||||||
|
))
|
||||||
|
|
||||||
|
(deftest distance-test
|
||||||
|
(testing "Distance between two locations"
|
||||||
|
(let [expected 4.242640687119285
|
||||||
|
actual (distance-between {:x 5 :y 5} {:x 2 :y 2})]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected 3
|
||||||
|
actual (distance-between {:x 5 :y 5} {:x 2 :y 5})]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected 50.80354318352215
|
||||||
|
actual (distance-between
|
||||||
|
{:x 5 :y 5}
|
||||||
|
[{:x -4 :y 55} :auchencairn :galloway :scotland])]
|
||||||
|
(is (= actual expected)))
|
||||||
|
(let [expected nil
|
||||||
|
actual (distance-between
|
||||||
|
{:x 5 :y 5}
|
||||||
|
[:auchencairn :galloway :scotland])]
|
||||||
|
(is (= actual expected)))
|
||||||
|
))
|
Loading…
Reference in a new issue