Much work on tidying documentation, not yet complete.
This commit is contained in:
parent
7e7a55c8ec
commit
3fcf16e079
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -17,3 +17,5 @@ pom.xml.asc
|
|||
.nrepl-port
|
||||
.cpcache/
|
||||
*~
|
||||
|
||||
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](../../2013/07/tessellated-multi-layer-height-map.html);
|
||||
1. Optionally, a raster rainfall map at 1km resolution (although my personal preference is that this should be generated procedurally from the height map).
|
||||
|
||||
The outputs are
|
||||
|
||||
1. A vector drainage map (rivers);
|
||||
1. A raster biome map at roughly 1 km resolution (it might be anything between hectare resolution and 1Km resolution, but obviously higher resolution takes more storage);
|
||||
1. A database of settlers and their settlements, such that the settlements have x,y co-ordinates;
|
||||
1. A vector road map.
|
||||
|
||||
In this sense, the 'biome map' is just the end state of a [Microworld](../../2014/08/modelling-settlement-with-cellular.html) run. The 'biomes' include things like 'forest', 'scrub', 'heath', 'pasture', but they may also include human settlement, and even settlement by different cultural groups.
|
||||
|
||||
This gives us all we need to vegetate and furnish the world. When rendering each square metre we have
|
||||
|
||||
1. The x,y coordinates, obviously;
|
||||
1. The altitude, taken from the height map;
|
||||
1. The biome, taken from the biome map;
|
||||
1. The biomes of adjacent cells in the biome map;
|
||||
1. The proximity of the nearest watercourse;
|
||||
1. The proximity of the nearest road or pathway;
|
||||
1. Whether we are inside, or outside, a settlement (where for these purposes, 'settlement' includes enclosed field), and if inside, what type of settlement it is.
|
||||
|
||||
Given these parameters, and using the x, y coordinates as seed of a deterministic pseudo-random number generator, we can generate appropriate vegetation and buildings to render a believable world. The reason for pulling adjacent biomes into the renderer is that sharp transitions from one biome to another - especially ones which align to a rectangular grid - rarely exist in nature, and that consequently most transitions from one biome to another should be gradual.
|
||||
|
||||
Note that proving, although extremely compute intensive, is not necessarily a one-time job. If the designers aren't satisfied with the first world to emerge from this process, they can run it again, and again, to generate a world with which they are satisfied. It's also possible to hand-edit the output of proving, if needed.
|
||||
|
||||
But now, designers and story-writers can see the world in which their creations will be set.
|
||||
|
||||
## Phase two: kneading - making the world fit our needs
|
||||
|
||||
Enough of proving, let's get on to kneading.
|
||||
|
||||
Hand-designed buildings and environments are likely to be needed, or at least useful, for plot; also, particularly, very high status buildings are probably better hand designed. I'm inclined to think that less is more here, for two reasons:
|
||||
|
||||
You cannot hand design a very large world, it's just impossible. How CD Project Red managed with Witcher 3 I don't know, since I understand that is largely hand designed; but that was a very large team, and even so it isn't a world on the scale I'm envisaging.
|
||||
|
||||
Procedurally generated models take a wee bit of compute power to reify, but not a huge amount, and they're trivial to store - you need one single birch leaf model and one single birch-bark texture generator to make every birch tree in the game, and probably a single parameterised tree function can draw every tree of every species (and quite a lot of shrubs and ground-cover plants, too). But once reified, they take no longer to render than a manually crafted model.
|
||||
|
||||
By contrast, a manually crafted model will take a very great deal more space to store, such that being able to render a large world from hand crafted models, without excessive model re-use, isn't going to be possible.
|
||||
|
||||
So it's better in my opinion to put effort into good procedural generation functions, not just for foliage but also for buildings. My reason for using a picture of a medieval bridge at the head of the essay is to illustrate exactly this point: even in the medieval period, bridges comprise a series of repeating modules. Take one arch module and one ramp module from Devorgilla's bridge as models, add texture skins for several different stone types, stretch the modules a little in whatever dimension is needed, and repeat the arch module as many times as needed, and you can create a range of bridges to span many different rivers - which will all be visibly similar, but that's fine, that's the nature of a traditional culture - but each slightly different.
|
||||
|
||||
Take half a dozen sets of models - timber bridges for forested biomes, brick bridges for biomes without stone or timber - and you can build procedural bridges across a whole continent without ever exactly repeating yourself.
|
||||
|
||||
However, in some places the designers and story writers will want, for plot reasons and to create iconic environments, to add models. I'm inclined not to over do this, both for reasons of development effort and for reasons of storage cost, but they will. Very high status buildings may need to be unique and distinctive, for example. These need to be designed and their locations and spatial dimensions added to the database, so that the models can be rendered in the right positions (and, critically, procedurally generated models can be omitted in those positions!)
|
||||
|
||||
Story and quest writers will also want characters for their plots. While there's no reason why writers cannot add entirely new characters to the database, there's no reason why they cannot incorporate characters generated in the settlement phase into the story; for this reason, characters need to be able to be tagged in the database as plot characters, and with what quests/elements of the plot they're associated.
|
||||
|
||||
This allows a mechanism to prevent a plot character from being killed by another non-player character, or dying of disease or starvation, before the plot elements in which they feature have been completed.
|
||||
|
||||
## Phase three: baking - making it delicious
|
||||
|
||||
Once the world has been populated, settled, vegetated, the story has been written, the models built, the quests designed, there is probably a process of optimisation - stripping out things which aren't needed at play time, streamlining things that are - before you have a game ready to ship; but really I haven't yet given that much thought.
|
||||
|
||||
## Phase four: eating!
|
||||
|
||||
At the end, though, you have a game, and a player plays it. How much of the dynamic, organic life that brought the game through proving continues on into the playing phase? If the [gossip](The-spread-of-knowledge-in-a-large-game.html) ideas are to work, if unscripted, non-plot-related events (as well as scripted, plot related events) are to happen while the player plays, if news of these events is to percolate through the world and reach the player in organic, unscripted ways, if a lot of the emergent gameplay I'm imagining is to work, then quite a lot of the dynamic things must be happening.
|
||||
|
||||
Of course, part of this depends on the length of 'game world time' is expected to elapse in the course of one play through of the game. If it's less than a year, then you don't need children dynamically being born, and characters dynamically growing older; but if more, then you do. Similarly, you don't need a real simulation of trading to dynamically drive prices in markets, but for a fun trading sub-game to emerge, you probably do, and if you are using merchants as news spreading agents the additional compute cost is not high.
|
||||
|
||||
And I understand that many game writers will shudder at the thought that a war might (or might not) start in the middle of their plot, that a battle might, one time in a thousand, take place right where they've plotted some significant encounter. Most modern video games are essentially just very complicated state machines: if you make this sequence of choices, this outcome will happen, guaranteed. Or else they're puddles of random soup, where everything that happens is more or less driven by a random number generator. What I'm envisaging is something quite different: a world in which traders gonna trade, robbers gonna rob, lovers gonna love, scandal-mongers gonna make scandal, organically and dynamically whether the player is there or not, and news of these events will filter through to the player through the gossip network also organically and dynamically.
|
||||
|
||||
A world, in short, through which no two runs will ever be the same, in which interesting bits of story will happen with no-one directing or scripting them. And for that to work, some of the same dynamic processes that drove the proving phase have to continue into the eating phase.
|
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.
|
64
doc/The-spread-of-knowledge-in-a-large-game-world.md
Normal file
64
doc/The-spread-of-knowledge-in-a-large-game-world.md
Normal file
|
@ -0,0 +1,64 @@
|
|||
# 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 tuple
|
||||
|
||||
`(days-old nth-hand significance action (actors))`
|
||||
|
||||
for example
|
||||
|
||||
`(54 2 10 'killed '(fred joe))`
|
||||
|
||||
meaning 'I spoke to a man who'd spoken to a man who said he saw notorious fred kill well-liked joe on 54 days ago'. Obviously, the non-player character must be able to construct a natural language sentence from the tuple when speaking within the hearing of a player character, but there's no need for a non-player character to produce a natural language sentence for another non-player character to parse; instead they can just exchange tuples.
|
||||
|
||||
But if we're exchanging knowledge between agents, then agents must have a means of representing knowledge. This knowledge is an association between subjects and sets of statement, such that when the agent learns the statement
|
||||
|
||||
`(54 2 10 'killed '(fred joe))`
|
||||
|
||||
it adds this statement (with the 2 incremented to 3) to the set of statements it knows about fred and also to the set of statements it knows about joe. It's possible that the receiving agent could then challenge for further statements about fred and/or joe, the automated equivalent of a 'who's joe?' question.
|
||||
|
||||
There could be feedback in this. Fred's and joe's significance scores could be incremented for each character to whom the statement is passed on, increasing the likeliness that fred, at least, would feature in more news stories in future. There needs also to be some means of managing how the non-player character's attitude to the subjects of the statement are affected. For example, If fred kills joe, and the character (say bill) receiving the news feels positively towards joe, then bill's attitude to fred should become sharply more hostile. If bill feels neutral about joe, then bill's attitude to fred should still become a bit more hostile, since killing people is on the whole a bad thing. But it bill feels very hostile towards joe, then bill's attitude to fred should become more friendly.
|
||||
|
||||
Obviously the rate of decay, and the degree of randomness, of the news-passing algorithm would need to be tuned, but this schema seems to me to describe a system with the following features:
|
||||
|
||||
* Non-player characters can respond to questions about significant things which happen in the world - without it all having to be scripted
|
||||
* If you travel fast enough, you can keep ahead of your notoriety
|
||||
* Characters on major trade routes will know more about what is happening in the world than characters in backwaters
|
||||
|
||||
This seems to me a reasonably good model of news spread.
|
||||
|
||||
### Scaling of the algorithm
|
||||
|
||||
Let's work around the idea that a 'game day' equates to about two hours of wall clock time. Let's work around the idea that there are of the order of fifty markets in the game world, and that for each market there are two or three merchants whose 'home base' it is.
|
||||
|
||||
Obviously non-player characters who are within the vicinity of a player character have to be 'awake', in order that the player can see them interacting with their world and can interact with them. Those characters have to be in working memory and have to be in the action polling loop in any case. So there's no extra cost to their gossiping away between each other - around the player there's a moving bubble of gossip, allowing each character the player interacts with to have a high probability of having some recent news.
|
||||
|
||||
But the merchants who aren't in the vicinity of a player don't have to be in working memory all the time. Each merchant simply requires to be 'woken up' - loaded into memory - once per game day, move a day's journey in one hop, and then, if arriving at an inn or at a market, wake and exchange news with one resident character - an innkeeper or a gossip. So the cost of this algorithm in a fifty-market game is at worst the cost of loading and unloading two non-player characters from memory every minute, and copying two or three statements from the knowledge set of one to the knowledge set of the other. If you're dynamically modifying significance scores, of course, you'd need to also load the characters about whom news was being passed on; but this still doesn't seem unduly onerous.
|
||||
|
||||
Obviously, if memory is not too constrained it may be possible to maintain all the merchants, all the innkeepers and all the characters currently being talked about in memory all the time, further reducing the cost.
|
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.
|
|
@ -31,7 +31,7 @@ Someone who intercepts and steals from merchants (and may also attack outlying f
|
|||
|
||||
## Second tier playable roles
|
||||
|
||||
The next tier of playable roles rotates around issues arising from the mercantile ecosystem.
|
||||
The next tier of playable roles rotates around issues arising from the mercantile ecosystem.
|
||||
|
||||
### Aristocracy
|
||||
|
||||
|
@ -57,10 +57,10 @@ But nevertheless, in The Witcher 3, a decision was made to pack incident fairly
|
|||
|
||||
An in-game day doesn't have to be as long as a wall clock day, and, indeed, typically isn't. But nevertheless, doing several game days of incident-free travel, even in beautiful scenery, is not going to be engaging - which implies a fast-travel mechanic.
|
||||
|
||||
I don't like fast travel, I find it a too-obvious breaking of immersion. Also, of course, one of the interesting things about a game in a merchant/outlaw ecosystem is the risk of interception on a journey. The Dragon Age series handled interrupted travel in 'fast travel' by randomly interacting the loading screen you get when moving from location to location in Dragon Age's patchwork worlds by dumping you into a tiny arena with enemies. That's really, really bad - there's no other way to say this. Everything about it shouts artifice.
|
||||
I don't like fast travel, I find it a too-obvious breaking of immersion. Also, of course, one of the interesting things about a game in a merchant/outlaw ecosystem is the risk of interception on a journey. The Dragon Age series handled interrupted travel in 'fast travel' by randomly interrupting the loading screen you get when moving from location to location in Dragon Age's patchwork worlds by dumping you into a tiny arena with enemies. That's really, really bad - there's no other way to say this. Everything about it shouts artifice.
|
||||
|
||||
So I'm thinking of a different mechanism: one I'm calling cruise control.
|
||||
|
||||
You set out on a task which will take a long time - such as a journey, but also such as any routine task. You're shown either a 'fast forward' of your character carrying out this task, or a series of cinematic 'shots along the way'. This depends, of course, on their being continuous renderable landscape between your departure and your destination, but there will be. This fast-forward proceeds at a substantially higher time gearing than normal game time - ten times as fast perhaps; we need it to, because as well as doing backgound scenery loading to move from one location to another, we're also simulating lots of non-player agents actions in parts of the world where the player currently isn't. So a 'jump cut' from one location to another isn't going to work anyway.
|
||||
You set out on a task which will take a long time - such as a journey, but also such as any routine task. You're shown either a 'fast forward' of your character carrying out this task, or a series of cinematic 'shots along the way'. This depends, of course, on there being continuous renderable landscape between your departure and your destination, but there will be. This fast-forward proceeds at a substantially higher time gearing than normal game time - ten times as fast perhaps; we need it to, because as well as doing backgound scenery loading to move from one location to another, we're also simulating lots of non-player agents' actions in parts of the world where the player currently isn't. So a 'jump cut' from one location to another isn't going to work anyway.
|
||||
|
||||
The player can interrupt 'fast forward' at any time. But also, the game itself may bring you out of fast forward when it anticipates that there may be action which requires decision - for example, when there are outlaws in the vicinity. And it will do this **before** the player's party is under immediate attack - the player will have time to take stock of the situation and prepare appropriately. Finally, this will take place in the full open world; the player will have the option to choose *not* to enter the narrow defile, for example, to ask local people (if there are any) for any news of outlaw activity, or, if they are available, to send forward scouts.
|
||||
|
|
|
@ -38,24 +38,24 @@
|
|||
style="width:87.17948717948718%;
|
||||
float:left;"> 34 </div></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:89.34108527131782%;
|
||||
float:left;"> 461 </div><div class="not-covered"
|
||||
style="width:10.65891472868217%;
|
||||
float:left;"> 55 </div></td>
|
||||
<td class="with-number">89.34 %</td>
|
||||
style="width:92.73422562141491%;
|
||||
float:left;"> 485 </div><div class="not-covered"
|
||||
style="width:7.265774378585086%;
|
||||
float:left;"> 38 </div></td>
|
||||
<td class="with-number">92.73 %</td>
|
||||
<td class="with-bar"><div class="covered"
|
||||
style="width:83.65384615384616%;
|
||||
float:left;"> 87 </div><div class="partial"
|
||||
style="width:87.5%;
|
||||
float:left;"> 91 </div><div class="partial"
|
||||
style="width:8.653846153846153%;
|
||||
float:left;"> 9 </div><div class="not-covered"
|
||||
style="width:7.6923076923076925%;
|
||||
float:left;"> 8 </div></td>
|
||||
<td class="with-number">92.31 %</td>
|
||||
<td class="with-number">244</td><td class="with-number">29</td><td class="with-number">104</td>
|
||||
style="width:3.8461538461538463%;
|
||||
float:left;"> 4 </div></td>
|
||||
<td class="with-number">96.15 %</td>
|
||||
<td class="with-number">246</td><td class="with-number">30</td><td class="with-number">104</td>
|
||||
</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"
|
||||
|
@ -140,19 +140,17 @@
|
|||
</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:98.10606060606061%;
|
||||
float:left;"> 259 </div><div class="not-covered"
|
||||
style="width:1.893939393939394%;
|
||||
float:left;"> 5 </div></td>
|
||||
<td class="with-number">98.11 %</td>
|
||||
style="width:99.62121212121212%;
|
||||
float:left;"> 263 </div><div class="not-covered"
|
||||
style="width:0.3787878787878788%;
|
||||
float:left;"> 1 </div></td>
|
||||
<td class="with-number">99.62 %</td>
|
||||
<td class="with-bar"><div class="covered"
|
||||
style="width:96.66666666666667%;
|
||||
float:left;"> 58 </div><div class="partial"
|
||||
style="width:1.6666666666666667%;
|
||||
float:left;"> 1 </div><div class="not-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">98.33 %</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>
|
||||
|
@ -230,9 +228,9 @@
|
|||
</tr>
|
||||
<tr><td>Totals:</td>
|
||||
<td class="with-bar"></td>
|
||||
<td class="with-number">66.14 %</td>
|
||||
<td class="with-number">66.88 %</td>
|
||||
<td class="with-bar"></td>
|
||||
<td class="with-number">67.89 %</td>
|
||||
<td class="with-number">68.59 %</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
|
|
@ -11,193 +11,196 @@
|
|||
002 "Interchange of news events between gossip agents"
|
||||
</span><br/>
|
||||
<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 class="blank" title="0 out of 0 forms covered">
|
||||
004
|
||||
005
|
||||
</span><br/>
|
||||
<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 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 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 class="blank" title="0 out of 0 forms covered">
|
||||
008
|
||||
009
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
009 (defn dialogue
|
||||
010 (defn dialogue
|
||||
</span><br/>
|
||||
<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 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 class="not-tracked" title="0 out of 0 forms covered">
|
||||
012 additional entries."
|
||||
013 additional entries."
|
||||
</span><br/>
|
||||
<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 class="not-tracked" title="0 out of 0 forms covered">
|
||||
014 [enquirer respondent world]
|
||||
015 [enquirer respondent world]
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 1 forms covered">
|
||||
015 enquirer)
|
||||
016 enquirer)
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
016
|
||||
017
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
017 (defn gather-news
|
||||
018 (defn gather-news
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
018 ([world]
|
||||
019 ([world]
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 2 forms covered">
|
||||
019 (reduce
|
||||
020 (reduce
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 1 forms covered">
|
||||
020 deep-merge
|
||||
021 deep-merge
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 1 forms covered">
|
||||
021 world
|
||||
022 world
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 3 forms covered">
|
||||
022 (map
|
||||
023 (map
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 4 forms covered">
|
||||
023 #(gather-news world %)
|
||||
024 #(gather-news world %)
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 5 forms covered">
|
||||
024 (keys (:gossips world)))))
|
||||
025 (keys (:gossips world)))))
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
025 ([world gossip]
|
||||
026 ([world gossip]
|
||||
</span><br/>
|
||||
<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 class="not-covered" title="0 out of 5 forms covered">
|
||||
027 (-> world :gossips gossip)
|
||||
028 (-> world :gossips gossip)
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 3 forms covered">
|
||||
028 (map? gossip)
|
||||
029 (map? gossip)
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 1 forms covered">
|
||||
029 gossip)]
|
||||
030 gossip)]
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 2 forms covered">
|
||||
030 {:gossips
|
||||
031 {:gossips
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 4 forms covered">
|
||||
031 {(:id g)
|
||||
032 {(:id g)
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 2 forms covered">
|
||||
032 (reduce
|
||||
033 (reduce
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 1 forms covered">
|
||||
033 deep-merge
|
||||
034 deep-merge
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 1 forms covered">
|
||||
034 {}
|
||||
035 {}
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 3 forms covered">
|
||||
035 (map
|
||||
036 (map
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 5 forms covered">
|
||||
036 #(dialogue g % world)
|
||||
037 #(dialogue g % world)
|
||||
</span><br/>
|
||||
<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 %)
|
||||
039 #( = g %)
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 3 forms covered">
|
||||
039 (filter
|
||||
040 (filter
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 8 forms covered">
|
||||
040 #(= (:location %) (:location g))
|
||||
041 #(= (:location %) (:location g))
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 5 forms covered">
|
||||
041 (vals (:gossips world))))))}})))
|
||||
042 (vals (:gossips world))))))}})))
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
042
|
||||
043
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
043 (defn move-gossip
|
||||
044 (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
|
||||
045 "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
|
||||
046 `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 of the gossip should be controlled by the
|
||||
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">
|
||||
047 run function of the type of the record they shadow. The [[#run]] function
|
||||
048 run function of the type of the record they shadow. The [[#run]] function
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
048 below does NOT call this function."
|
||||
049 below does NOT call this function."
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
049 [gossip world new-location]
|
||||
050 [gossip world new-location]
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 4 forms covered">
|
||||
050 (let [id (cond
|
||||
051 (let [id (cond
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 3 forms covered">
|
||||
051 (map? gossip)
|
||||
052 (map? gossip)
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 7 forms covered">
|
||||
052 (-> world :gossips gossip :id)
|
||||
053 (-> world :gossips gossip :id)
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 3 forms covered">
|
||||
053 (keyword? gossip)
|
||||
054 (keyword? gossip)
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 1 forms covered">
|
||||
054 gossip)]
|
||||
055 gossip)]
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 2 forms covered">
|
||||
055 (deep-merge
|
||||
056 (deep-merge
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 1 forms covered">
|
||||
056 world
|
||||
057 world
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 2 forms covered">
|
||||
057 {:gossips
|
||||
058 {:gossips
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 2 forms covered">
|
||||
058 {id
|
||||
059 {id
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 3 forms covered">
|
||||
059 {:location new-location}}})))
|
||||
060 {:location new-location}}})))
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
060
|
||||
061
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
061 (defn run
|
||||
062 (defn run
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
062 "Return a world like this `world`, with news items exchanged between gossip
|
||||
063 "Return a world like this `world`, with news items exchanged between gossip
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
063 agents."
|
||||
064 agents."
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
064 [world]
|
||||
065 [world]
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 3 forms covered">
|
||||
065 (gather-news world))
|
||||
066 (gather-news world))
|
||||
</span><br/>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
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 [now]]))
|
||||
004 [the-great-game.time :refer [game-time]]))
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
005
|
||||
|
@ -418,323 +418,329 @@
|
|||
<span class="covered" title="10 out of 10 forms covered">
|
||||
138 #(some (fn [x] (= x location)) (:location %))
|
||||
</span><br/>
|
||||
<span class="covered" title="3 out of 3 forms covered">
|
||||
139 (:knowledge gossip)))))
|
||||
<span class="covered" title="10 out of 10 forms covered">
|
||||
139 (cons {:location (:home gossip)} (:knowledge gossip))))))
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
140
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
141 ;; (interest-in-location {:home [{0, 0} :test-home] :knowledge []} [:test-home])
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
142
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
141 (defn interesting-location?
|
||||
143 (defn interesting-location?
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
142 "True if the location of this news `item` is interesting to this `gossip`."
|
||||
144 "True if the location of this news `item` is interesting to this `gossip`."
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
143 [gossip item]
|
||||
145 [gossip item]
|
||||
</span><br/>
|
||||
<span class="covered" title="9 out of 9 forms covered">
|
||||
144 (> (interest-in-location gossip (:location item)) 1))
|
||||
146 (> (interest-in-location gossip (:location item)) 0))
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
145
|
||||
147
|
||||
</span><br/>
|
||||
<span class="covered" title="2 out of 2 forms covered">
|
||||
146 (defn interesting-object?
|
||||
148 (defn interesting-object?
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
147 [gossip object]
|
||||
149 [gossip object]
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
148 ;; TODO: Not yet (really) implemented
|
||||
150 ;; TODO: Not yet (really) implemented
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
149 true)
|
||||
151 true)
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
150
|
||||
152
|
||||
</span><br/>
|
||||
<span class="partial" title="1 out of 2 forms covered">
|
||||
151 (defn interesting-topic?
|
||||
153 (defn interesting-topic?
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
152 [gossip topic]
|
||||
154 [gossip topic]
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
153 ;; TODO: Not yet (really) implemented
|
||||
155 ;; TODO: Not yet (really) implemented
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
154 true)
|
||||
156 true)
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
155
|
||||
157
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
156 (defn interesting-item?
|
||||
158 (defn interesting-item?
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
157 "True if anything about this news `item` is interesting to this `gossip`."
|
||||
159 "True if anything about this news `item` is interesting to this `gossip`."
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
158 [gossip item]
|
||||
160 [gossip item]
|
||||
</span><br/>
|
||||
<span class="partial" title="13 out of 17 forms covered">
|
||||
159 (or
|
||||
161 (or
|
||||
</span><br/>
|
||||
<span class="covered" title="6 out of 6 forms covered">
|
||||
160 (interesting-character? gossip (:actor item))
|
||||
162 (interesting-character? gossip (:actor item))
|
||||
</span><br/>
|
||||
<span class="covered" title="6 out of 6 forms covered">
|
||||
161 (interesting-character? gossip (:other item))
|
||||
163 (interesting-character? gossip (:other item))
|
||||
</span><br/>
|
||||
<span class="covered" title="6 out of 6 forms covered">
|
||||
162 (interesting-location? gossip (:location item))
|
||||
164 (interesting-location? gossip (:location item))
|
||||
</span><br/>
|
||||
<span class="covered" title="6 out of 6 forms covered">
|
||||
163 (interesting-object? gossip (:object item))
|
||||
165 (interesting-object? gossip (:object item))
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 5 forms covered">
|
||||
164 (interesting-topic? gossip (:verb item))))
|
||||
166 (interesting-topic? gossip (:verb item))))
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
165
|
||||
167
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
166 (defn infer
|
||||
168 (defn infer
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
167 "Infer a new knowledge item from this `item`, following this `rule`"
|
||||
169 "Infer a new knowledge item from this `item`, following this `rule`"
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
168 [item rule]
|
||||
170 [item rule]
|
||||
</span><br/>
|
||||
<span class="covered" title="3 out of 3 forms covered">
|
||||
169 (reduce merge
|
||||
171 (reduce merge
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
170 item
|
||||
172 item
|
||||
</span><br/>
|
||||
<span class="covered" title="2 out of 2 forms covered">
|
||||
171 (cons
|
||||
173 (cons
|
||||
</span><br/>
|
||||
<span class="covered" title="5 out of 5 forms covered">
|
||||
172 {:verb (:verb rule)}
|
||||
174 {:verb (:verb rule)}
|
||||
</span><br/>
|
||||
<span class="covered" title="13 out of 13 forms covered">
|
||||
173 (map (fn [k] {k (apply (k rule) (list item))})
|
||||
175 (map (fn [k] {k (apply (k rule) (list item))})
|
||||
</span><br/>
|
||||
<span class="covered" title="3 out of 3 forms covered">
|
||||
174 (remove
|
||||
176 (remove
|
||||
</span><br/>
|
||||
<span class="covered" title="4 out of 4 forms covered">
|
||||
175 #(= % :verb)
|
||||
177 #(= % :verb)
|
||||
</span><br/>
|
||||
<span class="covered" title="3 out of 3 forms covered">
|
||||
176 (keys rule))))))
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
177
|
||||
</span><br/>
|
||||
<span class="covered" title="2 out of 2 forms covered">
|
||||
178 (declare learn-news-item)
|
||||
178 (keys rule))))))
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
179
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
180 (defn make-all-inferences
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
181 "Return a list of knowledge entries inferred from this news `item` by this
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
182 `gossip`."
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
183 [item]
|
||||
</span><br/>
|
||||
<span class="covered" title="2 out of 2 forms covered">
|
||||
184 (set
|
||||
180 (declare learn-news-item)
|
||||
</span><br/>
|
||||
<span class="covered" title="2 out of 2 forms covered">
|
||||
185 (reduce
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
181
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
186 concat
|
||||
182 (defn make-all-inferences
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
183 "Return a list of knowledge entries inferred from this news `item` by this
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
184 `gossip`."
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
185 [item]
|
||||
</span><br/>
|
||||
<span class="covered" title="2 out of 2 forms covered">
|
||||
186 (set
|
||||
</span><br/>
|
||||
<span class="covered" title="2 out of 2 forms covered">
|
||||
187 (reduce
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
188 concat
|
||||
</span><br/>
|
||||
<span class="covered" title="3 out of 3 forms covered">
|
||||
187 (map
|
||||
189 (map
|
||||
</span><br/>
|
||||
<span class="covered" title="10 out of 10 forms covered">
|
||||
188 #(:knowledge (learn-news-item {} (infer item %) false))
|
||||
190 #(:knowledge (learn-news-item {} (infer item %) false))
|
||||
</span><br/>
|
||||
<span class="covered" title="7 out of 7 forms covered">
|
||||
189 (:inferences (news-topics (:verb item)))))))
|
||||
191 (:inferences (news-topics (:verb item)))))))
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
190
|
||||
192
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
191 (defn degrade-character
|
||||
193 (defn degrade-character
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
192 "Return a character specification like this `character`, but comprising
|
||||
194 "Return a character specification like this `character`, but comprising
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
193 only those properties this `gossip` is interested in."
|
||||
195 only those properties this `gossip` is interested in."
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
194 [gossip character]
|
||||
196 [gossip character]
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
195 ;; TODO: Not yet (really) implemented
|
||||
197 ;; TODO: Not yet (really) implemented
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 1 forms covered">
|
||||
196 character)
|
||||
198 character)
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
197
|
||||
199
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
198 (defn degrade-location
|
||||
200 (defn degrade-location
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
199 "Return a location specification like this `location`, but comprising
|
||||
201 "Return a location specification like this `location`, but comprising
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
200 only those elements this `gossip` is interested in. If none, return
|
||||
202 only those elements this `gossip` is interested in. If none, return
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
201 `nil`."
|
||||
203 `nil`."
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
202 [gossip location]
|
||||
204 [gossip location]
|
||||
</span><br/>
|
||||
<span class="covered" title="2 out of 2 forms covered">
|
||||
203 (let [l (if
|
||||
205 (let [l (if
|
||||
</span><br/>
|
||||
<span class="covered" title="3 out of 3 forms covered">
|
||||
204 (coll? location)
|
||||
206 (coll? location)
|
||||
</span><br/>
|
||||
<span class="covered" title="3 out of 3 forms covered">
|
||||
205 (filter
|
||||
207 (filter
|
||||
</span><br/>
|
||||
<span class="partial" title="5 out of 7 forms covered">
|
||||
206 #(when (interesting-location? gossip %) %)
|
||||
208 #(when (interesting-location? gossip %) %)
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
207 location))]
|
||||
209 location))]
|
||||
</span><br/>
|
||||
<span class="partial" title="5 out of 7 forms covered">
|
||||
208 (when-not (empty? l) l)))
|
||||
210 (when-not (empty? l) l)))
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
209
|
||||
211
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
210 (defn learn-news-item
|
||||
212 (defn learn-news-item
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
211 "Return a gossip like this `gossip`, which has learned this news `item` if
|
||||
213 "Return a gossip like this `gossip`, which has learned this news `item` if
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
212 it is of interest to them."
|
||||
214 it is of interest to them."
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
213 ;; TODO: Not yet implemented
|
||||
215 ;; TODO: Not yet implemented
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
214 ([gossip item]
|
||||
216 ([gossip item]
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 5 forms covered">
|
||||
215 (learn-news-item gossip item true))
|
||||
<span class="covered" title="5 out of 5 forms covered">
|
||||
217 (learn-news-item gossip item true))
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
216 ([gossip item follow-inferences?]
|
||||
218 ([gossip item follow-inferences?]
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
217 (if
|
||||
219 (if
|
||||
</span><br/>
|
||||
<span class="covered" title="4 out of 4 forms covered">
|
||||
218 (interesting-item? gossip item)
|
||||
220 (interesting-item? gossip item)
|
||||
</span><br/>
|
||||
<span class="covered" title="5 out of 5 forms covered">
|
||||
219 (let [g (assoc gossip :knowledge
|
||||
221 (let [g (assoc gossip :knowledge
|
||||
</span><br/>
|
||||
<span class="covered" title="2 out of 2 forms covered">
|
||||
220 (cons
|
||||
222 (cons
|
||||
</span><br/>
|
||||
<span class="covered" title="5 out of 5 forms covered">
|
||||
221 (assoc
|
||||
223 (assoc
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
222 item
|
||||
224 item
|
||||
</span><br/>
|
||||
<span class="covered" title="2 out of 2 forms covered">
|
||||
223 :nth-hand (if
|
||||
225 :nth-hand (if
|
||||
</span><br/>
|
||||
<span class="covered" title="5 out of 5 forms covered">
|
||||
224 (number? (:nth-hand item))
|
||||
226 (number? (:nth-hand item))
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 5 forms covered">
|
||||
225 (inc (:nth-hand item))
|
||||
227 (inc (:nth-hand item))
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
226 1)
|
||||
228 1)
|
||||
</span><br/>
|
||||
<span class="partial" title="8 out of 11 forms covered">
|
||||
227 :date (if (number? (:date item)) (:date item) (now))
|
||||
229 :date (if (number? (:date item)) (:date item) (game-time))
|
||||
</span><br/>
|
||||
<span class="covered" title="6 out of 6 forms covered">
|
||||
228 :location (degrade-location gossip (:location item))
|
||||
230 :location (degrade-location gossip (:location item))
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
229 ;; ought to degratde the location
|
||||
231 ;; ought to degratde the location
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
230 ;; ought to maybe-degrade characters we're not yet interested in
|
||||
232 ;; ought to maybe-degrade characters we're not yet interested in
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
231 )
|
||||
233 )
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
232 ;; ought not to add knowledge items we already have, except
|
||||
234 ;; ought not to add knowledge items we already have, except
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
233 ;; to replace if new item is of increased specificity
|
||||
235 ;; to replace if new item is of increased specificity
|
||||
</span><br/>
|
||||
<span class="covered" title="3 out of 3 forms covered">
|
||||
234 (:knowledge gossip)))]
|
||||
236 (:knowledge gossip)))]
|
||||
</span><br/>
|
||||
<span class="covered" title="2 out of 2 forms covered">
|
||||
235 (if follow-inferences?
|
||||
237 (if follow-inferences?
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 3 forms covered">
|
||||
236 (assoc
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 1 forms covered">
|
||||
237 g
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
238 :knowledge
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 8 forms covered">
|
||||
239 (concat (:knowledge g) (make-all-inferences item)))
|
||||
<span class="covered" title="3 out of 3 forms covered">
|
||||
238 (assoc
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
240 g))
|
||||
239 g
|
||||
</span><br/>
|
||||
<span class="not-tracked" title="0 out of 0 forms covered">
|
||||
240 :knowledge
|
||||
</span><br/>
|
||||
<span class="covered" title="8 out of 8 forms covered">
|
||||
241 (concat (:knowledge g) (make-all-inferences item)))
|
||||
</span><br/>
|
||||
<span class="covered" title="1 out of 1 forms covered">
|
||||
242 g))
|
||||
</span><br/>
|
||||
<span class="not-covered" title="0 out of 1 forms covered">
|
||||
241 gossip)))
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
242
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
243
|
||||
243 gossip)))
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
244
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
245
|
||||
</span><br/>
|
||||
<span class="blank" title="0 out of 0 forms covered">
|
||||
246
|
||||
</span><br/>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -244,7 +244,7 @@
|
|||
<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="not-covered" title="0 out of 4 forms covered">
|
||||
<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">
|
||||
|
|
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
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
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
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
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
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
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
|
@ -1,13 +1,14 @@
|
|||
<html>
|
||||
<head>
|
||||
<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>
|
||||
<body>
|
||||
<h1>The Great Game: Dcocumentation</h1>
|
||||
<h1>The Great Game: Documentation</h1>
|
||||
<ul>
|
||||
<li><a href="cloverage/index.html">Test coverage</a></li>
|
||||
<li><a href="codox/index.html">Primary documentaion</a></li>
|
||||
<li><a href="cloverage/index.html">Test coverage</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
(ns the-great-game.gossip.news-items
|
||||
"Categories of news events interesting to gossip agents"
|
||||
(:require [the-great-game.world.location :refer [distance-between]]
|
||||
[the-great-game.time :refer [now]]))
|
||||
[the-great-game.time :refer [game-time]]))
|
||||
|
||||
;; The ideas here are based on the essay 'The spread of knowledge in a large
|
||||
;; game world', q.v.; they've advanced a little beyond that and will doubtless
|
||||
|
@ -136,12 +136,14 @@
|
|||
(count
|
||||
(filter
|
||||
#(some (fn [x] (= x location)) (:location %))
|
||||
(:knowledge gossip)))))
|
||||
(cons {:location (:home gossip)} (:knowledge gossip))))))
|
||||
|
||||
;; (interest-in-location {:home [{0, 0} :test-home] :knowledge []} [:test-home])
|
||||
|
||||
(defn interesting-location?
|
||||
"True if the location of this news `item` is interesting to this `gossip`."
|
||||
[gossip item]
|
||||
(> (interest-in-location gossip (:location item)) 1))
|
||||
(> (interest-in-location gossip (:location item)) 0))
|
||||
|
||||
(defn interesting-object?
|
||||
[gossip object]
|
||||
|
@ -224,7 +226,7 @@
|
|||
(number? (:nth-hand item))
|
||||
(inc (:nth-hand item))
|
||||
1)
|
||||
:date (if (number? (:date item)) (:date item) (now))
|
||||
:date (if (number? (:date item)) (:date item) (game-time))
|
||||
:location (degrade-location gossip (:location item))
|
||||
;; ought to degratde the location
|
||||
;; ought to maybe-degrade characters we're not yet interested in
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
:location [{:x 35 :y 23} :auchencairn :galloway :scotland]}]}
|
||||
[:galloway :scotland])]
|
||||
(is (= actual expected)))
|
||||
(let [expected 2
|
||||
actual (interest-in-location
|
||||
{:home [{:x 35 :y 23} :auchencairn :galloway :scotland]}
|
||||
[:galloway :scotland])]
|
||||
(is (= actual expected)))
|
||||
(let [expected 0
|
||||
actual (interest-in-location
|
||||
{:knowledge [{:verb :steal
|
||||
|
@ -114,19 +119,16 @@
|
|||
;; dates will not be and cannot be expected to be equal
|
||||
actual (make-all-inferences
|
||||
{:verb :rape :actor :adam :other :belinda :location :test-home})
|
||||
actual' (map #(dissoc % :date) actual)]
|
||||
actual' (set (map #(dissoc % :date) actual))]
|
||||
(is (= actual' expected)))))
|
||||
|
||||
;; (deftest learn-tests
|
||||
;; (testing "Learning from an interesting news item."
|
||||
;; (let [expected {:home [{0 0} :test-home],
|
||||
;; :knowledge ({:verb :rape, :actor :adam, :other :belinda, :location nil, :nth-hand 1}
|
||||
;; {:verb :sex, :actor :belinda, :other :adam, :location nil, :nth-hand 1}
|
||||
;; {:verb :attack, :actor :adam, :other :belinda, :location nil, :nth-hand 1}
|
||||
;; {:verb :sex, :actor :adam, :other :belinda, :location nil, :nth-hand 1})}
|
||||
;; actual (learn-news-item
|
||||
;; {:home [{0, 0} :test-home]
|
||||
;; :knowledge []}
|
||||
;; {:verb :rape :actor :adam :other :belinda :location [:test-home]})
|
||||
;; actual' (assoc actual :knowledge (map #(dissoc % :date) (:knowledge actual)))]
|
||||
;; (is (= actual' expected)))))
|
||||
(deftest learn-tests
|
||||
(testing "Learning from an interesting news item."
|
||||
(let [expected {:home [{0 0} :test-home],
|
||||
:knowledge [{:verb :sex, :actor :adam, :other :belinda, :location nil, :nth-hand 1}
|
||||
{:verb :sex, :actor :belinda, :other :adam, :location nil, :nth-hand 1}]}
|
||||
actual (learn-news-item
|
||||
{:home [{0, 0} :test-home] :knowledge []}
|
||||
{:verb :sex :actor :adam :other :belinda :location [:test-home]})
|
||||
actual' (assoc actual :knowledge (vec (map #(dissoc % :date) (:knowledge actual))))]
|
||||
(is (= actual' expected)))))
|
||||
|
|
Loading…
Reference in a new issue