diff --git a/.gitignore b/.gitignore
index a4cb69a..0910231 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,13 +2,18 @@ pom.xml
pom.xml.asc
*.jar
*.class
+*.log
+[0-9a-f]*-init.clj
/lib/
/classes/
/target/
/checkouts/
+/.clj-kondo/
+.eastwood
.lein-deps-sum
.lein-repl-history
.lein-plugins/
.lein-failures
.nrepl-port
.cpcache/
+*~
diff --git a/doc/Population.ods b/doc/Population.ods
new file mode 100644
index 0000000..494ac80
Binary files /dev/null and b/doc/Population.ods differ
diff --git a/doc/economy.md b/doc/economy.md
new file mode 100644
index 0000000..45ed00a
--- /dev/null
+++ b/doc/economy.md
@@ -0,0 +1,49 @@
+# Game world economy
+
+Broadly this essay extends ideas presented in [Populating a game world](https://blog.journeyman.cc/2013/07/populating-game-world.html), q.v.
+
+## Primary producers
+
+### Herdsfolk
+
+Herdsfolk are nomadic; it's reasonable to think they'll bring their herds to market, rather than selling it lots of tiny markets. So in the spring, shepherds will visit specific towns at the edge of open land, to hold a shearing festival/carnevale; and that both shepherds and cattle herders will visit towns on the edge of open land to sell fatstock in the autumn.
+
+### Miners
+
+Miners mine. They're settled, but they're settled usually in specialist settlements at the location where the ore body is accessible, usually in mountenous territory. They'll consume a lot of food, so there will be a local market for foodstuffs encouraging local farming. Different mines obviously mine different ores, but, for example, lead and silver are frequently found together.
+
+### Foresters
+
+Foresters are more or less settled at the edge of forests, at locations from which timber can be moved by navigable water; again in specialist settlements. In addition to timber, foresters hunt and produce both meat and furs, so have less need for other food producers locally.
+
+### Farmers
+
+Farmers are settled. Farmers occupy standard runrig plots, but because they don't employ journeymen or apprentices, and don't have workshops, the plots are mostly open with little building. Most farmers are 'mixed farmers', producing cereals, meat, eggs and milk. Some will be more specialist. Farm produce, taken broadly to include orchardsfolk, include:
+
+* meat
+* milk and milk products
+* hides
+* eggs
+* cereals
+* root vegetables, onions, etc
+* peas and beans
+* leaf vegetables
+* fruits
+* fibres: linen, hemp and silk (from silk-moths in mulberry orchards)
+* possibly other stuff I've forgotten.
+
+Farmers are all basically subsistence farmers, farming first to feed their own household and selling only surplus in the market.
+
+## Crafts
+
+Crafts generally process primary goods into secondary goods - whether intermediate stages or final consumer items. Some elite 'crafts' deal with abstract primary goods like law and knowledge, and they may be seen as somewhat separate.
+
+A master craftsperson occupies a standard runrig plot, much like a farmer's plot. Like a farmer, a poor master crafter household will cultivate part of the plot to produce food for the house - at least grow vegetables and keep hens. However, as the crafter takes on apprentices and journeymen - and gets richer - more buildings will be required as accommodation, workshop space and materials stores.
+
+Generally, primary goods aren't transported over land - because overland transport is expensive, by the time they've been transported they're no longer low cost goods. So often the craftspeople who process primary produce into at least commodity intermediate forms will live close to the source of the primary goods.
+
+So, for example, the town(s) where the shepherds hold their shearing fairs will tend to have a lot of weavers. While around mines there will be smelters producing ingots and bar stock to be marketed to smiths all over the place, there will also be smiths close to the mines producing commodity tools and weapons.
+
+See the table in Populating a game world.
+
+
diff --git a/doc/modelling_trading_cost_and_risk.md b/doc/modelling_trading_cost_and_risk.md
new file mode 100644
index 0000000..3574d5b
--- /dev/null
+++ b/doc/modelling_trading_cost_and_risk.md
@@ -0,0 +1,9 @@
+# Modelling trading cost and risk
+
+In a dynamic pre-firearms world with many small states and contested regions, trade is not going to be straightforward. Not only will different routes have different physical characteristics - more or less mountainous, more or fewer unbridged river crossings - they will also have different political characteristics: more of less taxed, more or less effectively policed.
+
+Raids by outlaws are expected to be part of the game economy. News of raids are the sort of things which may propagate through the [[gossip]] system. So are changes in taxation regime. Obviously, knowledge items can affect merchants' trading strategy; in existing prototype code, individual merchants already each keep their own cache of known historical prices, and exchange historical price data with one another; and use this price data to select trades to make.
+
+So: to what extent is it worth modelling the spread of knowledge of trade cost and risk?
+
+Obviously the more we model, the more compute power modelling consumes. If the core objective is a Role Playing Games as currently understood, then there is no need to model very complex trade risk assessment behaviour.
diff --git a/doc/sandbox.md b/doc/sandbox.md
new file mode 100644
index 0000000..736f0ec
--- /dev/null
+++ b/doc/sandbox.md
@@ -0,0 +1,66 @@
+# Sandbox
+
+Up to now I've been thinking of the Great Game as essentially an RPG with some sandbox-like elements; but I think it may be better to think of it as a sandbox game with some RPG like elements.
+
+Why?
+
+The core of the game is a world in which non-player characters have enough individual knowledge of the world and their immediate surroundings that they can sensibly answer questions like
+
+* Where is the nearest craftsman of this craft?
+* What price can I expect to get for this item in the local market?
+* What news have you heard recently?
+* Where does this person from your village live?
+
+and where there's a sufficiently sophisticated and robust economy simulation that buying goods in one market and selling them in another is viable.
+
+The original BBC Micro space trading game Elite had very little more in terms of game mechanics than a sandbox with a means to navigate it and an economy simulation, which wasn't even nearly as sophisticated as the one I have working now. Yet that combination resulted in engaging game play.
+
+## Main sandbox roles
+
+The idea of a sandbox is that the player character should be able to do pretty much anything they like within the mechanics of the game. From that, it seems to me reasonable that the player ought to be able to do more or less everything a non-player character can do. But creating the game mechanics to make each additional task doable takes time and investment, so there's a need to prioritise.
+
+So, as Elite did, I propose to make the first available sandbox roles
+
+### Merchant
+
+Someone who travels from city to city, buying goods cheap in one and selling them for more in another; and
+
+### Outlaw
+
+Someone who intercepts and steals from merchants (and may also attack outlying farms and villages)
+
+## Second tier playable roles
+
+The next tier of playable roles rotates around issues arising from the mercantile ecosystem.
+
+### Aristocracy
+
+Aristocrats are basically settled outlaws who seek to establish a monopoly on extracting taxes from inhabitants and travellers in a particular region by driving out all other outlaws. Within the comain of an aristocrat, you have to pay tax but you're reasonably safe from being attacked by other outlaws and losing everything. Aristocrats may also maintain and improve roads and bridges and do other things to boost the economy of their territory, may expant into adjoining territory with no current aristocratic control, and may wage war on other aristocrats.
+
+An outlaw ought to be able to become an aristocrat, by dominating an ungoverned area or by defeating an existing aristocrat.
+
+### Soldiery
+
+Soldiers, like aristocrats, are basically on the same spectrum as outlaws. Outlaws may hire themselves out to merchants as caravan guards, or to aristocrats as soldiers. Soldiers or guards, falling on bad times, may revert to outlawry.
+
+## Routine, Discretion and Playability
+
+There's a term that's used in criticism of many computer games which is worth thinking about hard here: that term is 'farming'. 'Farming', in this sense, is doing something repetitive and dull to earn credits in a game. Generally this is not fun. What makes roles in a game-world fun is having individual discretion - the ability to choose between actions and strategies - and a lack of routine.
+
+Most craft skills - especially in the learning phase - are not like this, and crafts which are sophisticated enough to be actually engaging are very hard to model in a game. Learning a craft is essentially, inherently, repetitive and dull, and if you take that repetition out of it you probably don't have enough left to yield the feeling of mastery which would reward success; so it doesn't seem to me that making craft roles playable should be a priority.
+
+## Cruise control
+
+One of the most enjoyable aspects of The Witcher 3 - still my go-to game for ideas I want to improve on - is simply travelling through the world. Although fast travel is possible I find I rarely use it, and a journey which takes fifteen minutes of real world wall clock time can be enjoyable in and of itself. This is, of course, a credit to the beautiful way the world is realised.
+
+But nevertheless, in The Witcher 3, a decision was made to pack incident fairly densely - because players would find just travelling boring. This leads to a situation where peaceful villages exist two minutes travel from dangerous monsters or bandit camps, and the suspension of disbelief gets a little strained. Building a world big enough that a market simulation is believable means that for the individual, the travel time to a market where a particular desired good is likely to be cheaper becomes costly in itself. Otherwise, there's no arbitrage between markets and no ecological niche for a merchant to fill. The journey time from market to market has to be several in-game days.
+
+An in-game day doesn't have to be as long as a wall clock day, and, indeed, typically isn't. But nevertheless, doing several game days of incident-free travel, even in beautiful scenery, is not going to be engaging - which implies a fast-travel mechanic.
+
+I don't like fast travel, I find it a too-obvious breaking of immersion. Also, of course, one of the interesting things about a game in a merchant/outlaw ecosystem is the risk of interception on a journey. The Dragon Age series handled interrupted travel in 'fast travel' by randomly 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.
+
+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.
+
+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.
diff --git a/doc/sexual-dimorphism.md b/doc/sexual-dimorphism.md
new file mode 100644
index 0000000..5325f5c
--- /dev/null
+++ b/doc/sexual-dimorphism.md
@@ -0,0 +1,45 @@
+# Sexual dimorphism
+
+This essay is going to upset a lot of people, so let's start with a statement of what it is about: it is an attempt to describe the systematically different behaviours of men and women, in sufficient detail that this can be represented by agents in a game world. It's trying to allow as broad as possible a range of cultures to be represented, so when I'm talking about what I consider to be behaviours of particular cultures, I'll say that.
+
+Of course, I'm writing this from the view point of an old white male. It's not possible to write about these things from a totally neutral viewpoint, and every one of us will have prejudices.
+
+OK? Let's start.
+
+When a man and a woman have sex, there's a non-zero chance that the woman will get pregnant. There's a zero chance that the male will get pregnant.
+
+A woman can typically give birth to of the order of twelve children in the course of her life, and each childbirth involves a non-zero risk of death. If modelling the sort of bronze-age-to-late-medieval cultures I'm generally considering, there are no available reliable methods of contraception, although their may be, for example, known spermicidal or abortifacient spells or potions. If it's abortifacient, that's pretty unpleasant for the woman, too.
+
+Children, especially when young, are very vulnerable and need protection. Children with good protection are much more likely to survive to adulthood. Raising children involves a fair amount of work.
+
+For all sorts of reasons, some of which are clearly cultural but others of which are fundamental, it's much easier for men to walk away from responsibility for their children than it is for women. For example, considering a pre-modern world, women would always know for certain which children were theirs, and men would not.
+
+For a woman, consequently, the best breeding strategy is to have sex only with men who will be 'good fathers', where there are three essential parameters to "good fathers":
+
+1. Desirable genetic traits;
+2. Preparedness to stick around and share the work;
+3. Ability to provide and protect.
+
+The essential trade-off in the traditional western marriage is that the man gets to have sex, and the woman gets to have protection for her progeny.
+
+Another significant point is that women's ability to bear children ceases at a much younger age than men's ability to father them.
+
+## Why have sex at all?
+
+If a character has 'having children' - the **Ancestor** aspiration, in my typology - as their key aim, then they will want to have sex. But to have children in this sense is to have acknowledged children, so while a male character may be motivated to have multiple female partners, he will never the less have some degree of long term committment to them, and will want both to feel confident that the children are his and to be recognised by their father.
+
+From the point of view of seeking to become an Ancestor, there is little benefit to the woman in having multiple partners, except in very harsh environments. It will be easier to give one partner confidence that all your children are his, and while a man can increase his number of potential progeny by having multiple wives, mistresses or other classes of long-term female sexual partners, a woman cannot.
+
+## Why have children?
+
+In modern Scotland, I have met a lot of women with a strong drive to have children for the sake of having children, where the best explanation they could give is that it's instinctual; it may be so. But beyond that, in many cultures children provide their (acknowledged) parents with care and security in their old age, may tend their graves and perform belief-related services after they die, and carry on their name and their stories into the future.
+
+Not everyone wants to have children; in thinking about the driving aspirations of game characters, I view having children one of six potential key aspirations.
+
+## Why else have sex?
+
+Sex, done right, is an extremely pleasant pastime. Sex can also be used to create and maintain bonds of committment, to demonstrate social status, to defuse tense situations, and transactionally in many ways, both formal and informal.
+
+For women, sex with other women carries with it no risk of pregnancy, so can be enjoyed or used for any of these purposes in very much the same way as it can by men; in other words, particularly for women, homosexual sex can be more lighthearted and carefree than heterosexual sex. To what extend our notions of homosexuality and heterosexuality are cultural I simply don't know. But because no children will result, a woman can afford to be more promiscuous with other women than she can with men.
+
+## How does this impact on
diff --git a/docs/_config.yml b/docs/_config.yml
deleted file mode 100644
index c741881..0000000
--- a/docs/_config.yml
+++ /dev/null
@@ -1 +0,0 @@
-theme: jekyll-theme-slate
\ No newline at end of file
diff --git a/docs/cloverage/coverage.css b/docs/cloverage/coverage.css
new file mode 100644
index 0000000..2be4e57
--- /dev/null
+++ b/docs/cloverage/coverage.css
@@ -0,0 +1,40 @@
+.covered {
+ font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
+ background-color: #558B55;
+}
+
+.not-covered {
+ font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
+ background-color: red;
+}
+
+.partial {
+ font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
+ background-color: orange;
+}
+
+.not-tracked {
+ font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
+}
+
+.blank {
+ font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
+}
+
+td {
+ padding-right: 10px;
+}
+
+td.with-bar {
+ width: 250px;
+ text-align: center;
+}
+
+td.with-number {
+ text-align: right;
+}
+
+td.ns-name {
+ min-width: 150px;
+ padding-right: 25px;
+}
diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html
new file mode 100644
index 0000000..eba4573
--- /dev/null
+++ b/docs/cloverage/index.html
@@ -0,0 +1,192 @@
+
+
Herdsfolk are nomadic; it’s reasonable to think they’ll bring their herds to market, rather than selling it lots of tiny markets. So in the spring, shepherds will visit specific towns at the edge of open land, to hold a shearing festival/carnevale; and that both shepherds and cattle herders will visit towns on the edge of open land to sell fatstock in the autumn.
+
Miners
+
Miners mine. They’re settled, but they’re settled usually in specialist settlements at the location where the ore body is accessible, usually in mountenous territory. They’ll consume a lot of food, so there will be a local market for foodstuffs encouraging local farming. Different mines obviously mine different ores, but, for example, lead and silver are frequently found together.
+
Foresters
+
Foresters are more or less settled at the edge of forests, at locations from which timber can be moved by navigable water; again in specialist settlements. In addition to timber, foresters hunt and produce both meat and furs, so have less need for other food producers locally.
+
Farmers
+
Farmers are settled. Farmers occupy standard runrig plots, but because they don’t employ journeymen or apprentices, and don’t have workshops, the plots are mostly open with little building. Most farmers are ‘mixed farmers’, producing cereals, meat, eggs and milk. Some will be more specialist. Farm produce, taken broadly to include orchardsfolk, include:
+
+
meat
+
milk and milk products
+
hides
+
eggs
+
cereals
+
root vegetables, onions, etc
+
peas and beans
+
leaf vegetables
+
fruits
+
fibres: linen, hemp and silk (from silk-moths in mulberry orchards)
+
possibly other stuff I’ve forgotten.
+
+
Farmers are all basically subsistence farmers, farming first to feed their own household and selling only surplus in the market.
+
Crafts
+
Crafts generally process primary goods into secondary goods - whether intermediate stages or final consumer items. Some elite ‘crafts’ deal with abstract primary goods like law and knowledge, and they may be seen as somewhat separate.
+
A master craftsperson occupies a standard runrig plot, much like a farmer’s plot. Like a farmer, a poor master crafter household will cultivate part of the plot to produce food for the house - at least grow vegetables and keep hens. However, as the crafter takes on apprentices and journeymen - and gets richer - more buildings will be required as accommodation, workshop space and materials stores.
+
Generally, primary goods aren’t transported over land - because overland transport is expensive, by the time they’ve been transported they’re no longer low cost goods. So often the craftspeople who process primary produce into at least commodity intermediate forms will live close to the source of the primary goods.
+
So, for example, the town(s) where the shepherds hold their shearing fairs will tend to have a lot of weavers. While around mines there will be smelters producing ingots and bar stock to be marketed to smiths all over the place, there will also be smiths close to the mines producing commodity tools and weapons.
+
See the table in Populating a game world.
\ No newline at end of file
diff --git a/docs/codox/index.html b/docs/codox/index.html
new file mode 100644
index 0000000..0a34e6b
--- /dev/null
+++ b/docs/codox/index.html
@@ -0,0 +1,3 @@
+
+The-great-game 0.1.0
Trade planning for merchants, primarily. This follows a simple-minded generate-and-test strategy and currently generates plans for all possible routes from the current location. This may not scale. Also, routes do not currently have cost or risk associated with them.
\ No newline at end of file
diff --git a/docs/intro.html b/docs/codox/intro.html
similarity index 85%
rename from docs/intro.html
rename to docs/codox/intro.html
index 7d118f7..32cce64 100644
--- a/docs/intro.html
+++ b/docs/codox/intro.html
@@ -1,6 +1,6 @@
-Introduction to the-great-game
In this essay I’m going to try to pull together a number of my architectural ideas about the Great Game which I know I’m never actually going to build - because it’s vastly too big for any one person to build - into one overall vision.
So, firstly, how does one characterise this game?
diff --git a/docs/js/highlight.min.js b/docs/codox/js/highlight.min.js
similarity index 100%
rename from docs/js/highlight.min.js
rename to docs/codox/js/highlight.min.js
diff --git a/docs/js/jquery.min.js b/docs/codox/js/jquery.min.js
similarity index 100%
rename from docs/js/jquery.min.js
rename to docs/codox/js/jquery.min.js
diff --git a/docs/js/page_effects.js b/docs/codox/js/page_effects.js
similarity index 100%
rename from docs/js/page_effects.js
rename to docs/codox/js/page_effects.js
diff --git a/docs/codox/modelling_trading_cost_and_risk.html b/docs/codox/modelling_trading_cost_and_risk.html
new file mode 100644
index 0000000..88dd04c
--- /dev/null
+++ b/docs/codox/modelling_trading_cost_and_risk.html
@@ -0,0 +1,7 @@
+
+Modelling trading cost and risk
In a dynamic pre-firearms world with many small states and contested regions, trade is not going to be straightforward. Not only will different routes have different physical characteristics - more or less mountainous, more or fewer unbridged river crossings - they will also have different political characteristics: more of less taxed, more or less effectively policed.
+
Raids by outlaws are expected to be part of the game economy. News of raids are the sort of things which may propagate through the gossip system. So are changes in taxation regime. Obviously, knowledge items can affect merchants’ trading strategy; in existing prototype code, individual merchants already each keep their own cache of known historical prices, and exchange historical price data with one another; and use this price data to select trades to make.
+
So: to what extent is it worth modelling the spread of knowledge of trade cost and risk?
+
Obviously the more we model, the more compute power modelling consumes. If the core objective is a Role Playing Games as currently understood, then there is no need to model very complex trade risk assessment behaviour.
\ No newline at end of file
diff --git a/docs/codox/sexual-dimorphism.html b/docs/codox/sexual-dimorphism.html
new file mode 100644
index 0000000..8cf4c50
--- /dev/null
+++ b/docs/codox/sexual-dimorphism.html
@@ -0,0 +1,28 @@
+
+Sexual dimorphism
This essay is going to upset a lot of people, so let’s start with a statement of what it is about: it is an attempt to describe the systematically different behaviours of men and women, in sufficient detail that this can be represented by agents in a game world. It’s trying to allow as broad as possible a range of cultures to be represented, so when I’m talking about what I consider to be behaviours of particular cultures, I’ll say that.
+
Of course, I’m writing this from the view point of an old white male. It’s not possible to write about these things from a totally neutral viewpoint, and every one of us will have prejudices.
+
OK? Let’s start.
+
When a man and a woman have sex, there’s a non-zero chance that the woman will get pregnant. There’s a zero chance that the male will get pregnant.
+
A woman can typically give birth to of the order of twelve children in the course of her life, and each childbirth involves a non-zero risk of death. If modelling the sort of bronze-age-to-late-medieval cultures I’m generally considering, there are no available reliable methods of contraception, although their may be, for example, known spermicidal or abortifacient spells or potions. If it’s abortifacient, that’s pretty unpleasant for the woman, too.
+
Children, especially when young, are very vulnerable and need protection. Children with good protection are much more likely to survive to adulthood. Raising children involves a fair amount of work.
+
For all sorts of reasons, some of which are clearly cultural but others of which are fundamental, it’s much easier for men to walk away from responsibility for their children than it is for women. For example, considering a pre-modern world, women would always know for certain which children were theirs, and men would not.
+
For a woman, consequently, the best breeding strategy is to have sex only with men who will be ‘good fathers’, where there are three essential parameters to “good fathers”:
+
+
Desirable genetic traits;
+
Preparedness to stick around and share the work;
+
Ability to provide and protect.
+
+
The essential trade-off in the traditional western marriage is that the man gets to have sex, and the woman gets to have protection for her progeny.
+
Another significant point is that women’s ability to bear children ceases at a much younger age than men’s ability to father them.
+
Why have sex at all?
+
If a character has ‘having children’ - the Ancestor aspiration, in my typology - as their key aim, then they will want to have sex. But to have children in this sense is to have acknowledged children, so while a male character may be motivated to have multiple female partners, he will never the less have some degree of long term committment to them, and will want both to feel confident that the children are his and to be recognised by their father.
+
From the point of view of seeking to become an Ancestor, there is little benefit to the woman in having multiple partners, except in very harsh environments. It will be easier to give one partner confidence that all your children are his, and while a man can increase his number of potential progeny by having multiple wives, mistresses or other classes of long-term female sexual partners, a woman cannot.
+
Why have children?
+
In modern Scotland, I have met a lot of women with a strong drive to have children for the sake of having children, where the best explanation they could give is that it’s instinctual; it may be so. But beyond that, in many cultures children provide their (acknowledged) parents with care and security in their old age, may tend their graves and perform belief-related services after they die, and carry on their name and their stories into the future.
+
Not everyone wants to have children; in thinking about the driving aspirations of game characters, I view having children one of six potential key aspirations.
+
Why else have sex?
+
Sex, done right, is an extremely pleasant pastime. Sex can also be used to create and maintain bonds of committment, to demonstrate social status, to defuse tense situations, and transactionally in many ways, both formal and informal.
+
For women, sex with other women carries with it no risk of pregnancy, so can be enjoyed or used for any of these purposes in very much the same way as it can by men; in other words, particularly for women, homosexual sex can be more lighthearted and carefree than heterosexual sex. To what extend our notions of homosexuality and heterosexuality are cultural I simply don’t know. But because no children will result, a woman can afford to be more promiscuous with other women than she can with men.
+
How does this impact on
\ No newline at end of file
diff --git a/docs/codox/the-great-game.core.html b/docs/codox/the-great-game.core.html
new file mode 100644
index 0000000..e85f03f
--- /dev/null
+++ b/docs/codox/the-great-game.core.html
@@ -0,0 +1,3 @@
+
+the-great-game.core documentation
\ No newline at end of file
diff --git a/docs/codox/the-great-game.gossip.gossip.html b/docs/codox/the-great-game.gossip.gossip.html
new file mode 100644
index 0000000..3e8cf5c
--- /dev/null
+++ b/docs/codox/the-great-game.gossip.gossip.html
@@ -0,0 +1,3 @@
+
+the-great-game.gossip.gossip documentation
Dialogue between an enquirer and an agent in this world; returns a map identical to enquirer except that its :gossip collection may have additional entries.
Return a world like this world but with this gossip moved to this new-location. Many gossips are essentially shadow-records of agents of other types, and the movement if the gossip should be controlled by the run function of the type of the record they shadow. The #run function below does NOT call this function.
\ No newline at end of file
diff --git a/docs/codox/the-great-game.merchants.markets.html b/docs/codox/the-great-game.merchants.markets.html
new file mode 100644
index 0000000..3c3d546
--- /dev/null
+++ b/docs/codox/the-great-game.merchants.markets.html
@@ -0,0 +1,3 @@
+
+the-great-game.merchants.markets documentation
Adjust the quantity of this commodity currently in stock in this city of this world. Return a fragmentary world which can be deep-merged into this world.
If stock is greater than the maximum of supply and demand, then there is surplus and old price is too high, so shold be reduced. If lower, then it is too low and should be increased.
(update-markets world)(update-markets world city)(update-markets world city commodity)
Return a world like this world, with quantities and prices in markets updated to reflect supply and demand. If city or city and commodity are specified, return a fragmentary world with only the changes for that city (and commodity if specified) populated.
\ No newline at end of file
diff --git a/docs/codox/the-great-game.merchants.merchant-utils.html b/docs/codox/the-great-game.merchants.merchant-utils.html
new file mode 100644
index 0000000..b1b0b5f
--- /dev/null
+++ b/docs/codox/the-great-game.merchants.merchant-utils.html
@@ -0,0 +1,3 @@
+
+the-great-game.merchants.merchant-utils documentation
Where a and b are both maps all of whose values are numbers, return a map whose keys are a union of the keys of a and b and whose values are the sums of their respective values.
Find the price anticipated, given this world, by this merchant for this commodity in this city. If no information, assume 1. merchant should be passed as a map, commodity and city should be passed as keywords.
\ No newline at end of file
diff --git a/docs/codox/the-great-game.merchants.merchants.html b/docs/codox/the-great-game.merchants.merchants.html
new file mode 100644
index 0000000..870c8d7
--- /dev/null
+++ b/docs/codox/the-great-game.merchants.merchants.html
@@ -0,0 +1,3 @@
+
+the-great-game.merchants.merchants documentation
\ No newline at end of file
diff --git a/docs/codox/the-great-game.merchants.planning.html b/docs/codox/the-great-game.merchants.planning.html
new file mode 100644
index 0000000..1274f66
--- /dev/null
+++ b/docs/codox/the-great-game.merchants.planning.html
@@ -0,0 +1,26 @@
+
+the-great-game.merchants.planning documentation
Trade planning for merchants, primarily. This follows a simple-minded generate-and-test strategy and currently generates plans for all possible routes from the current location. This may not scale. Also, routes do not currently have cost or risk associated with them.
augment-plan
(augment-plan merchant world plan)
Augment this plan constructed in this world for this merchant with the :quantity of goods which should be bought and the :expected-profit of the trade.
Return the distance to the nearest destination among those of these plans which match these targets. Plans are expected to be plans as returned by generate-trade-plans, q.v.; targets are expected to be as accepted by make-target-filter, q.v.
Find the best destination in this world for this commodity given this merchant and this origin. If two cities are anticipated to offer the same price, the nearer should be preferred; if two are equally distant, the ones nearer to the merchant’s home should be preferred. merchant may be passed as a map or a keyword; commodity should be passed as a keyword.
+
The returned plan is a map with keys:
+
+
:merchant - the id of the merchant for whom the plan was created;
+
:origin - the city from which the trade starts;
+
:destination - the city to which the trade is planned;
+
:commodity - the commodity to be carried;
+
:buy-price - the price at which that commodity can be bought;
+
:expected-price - the price at which the merchant anticipates that commodity can be sold;
+
:distance - the number of stages in the planned journey
+
:dist-to-home - the distance from destination to the merchant’s home city.
A merchant, in a given location in a world, will choose to buy a cargo within the limit they are capable of carrying, which they can anticipate selling for a profit at a destination.
\ No newline at end of file
diff --git a/docs/codox/the-great-game.merchants.strategies.simple.html b/docs/codox/the-great-game.merchants.strategies.simple.html
new file mode 100644
index 0000000..ca81e5b
--- /dev/null
+++ b/docs/codox/the-great-game.merchants.strategies.simple.html
@@ -0,0 +1,4 @@
+
+the-great-game.merchants.strategies.simple documentation
The simple strategy buys a single product in the local market if there is one which can be traded profitably, trades it to the chosen target market, and sells it there. If there is no commodity locally which can be traded profitably, moves towards home with no cargo. If at home and no commodity can be traded profitably, does not move.
move-merchant
(move-merchant merchant world)
Handle general en route movement of this merchant in this world; return a (partial or full) world like this world but in which the merchant may have been moved ot updated.
Return a world like this world, in which this merchant has planned a new trade, and bought appropriate stock for it. If no profitable trade can be planned, the merchant is simply moved towards their home.
Return a new world like this world, in which this merchant has sold their current stock in their current location, and planned a new trade, and bought appropriate stock for it.
\ No newline at end of file
diff --git a/docs/codox/the-great-game.utils.html b/docs/codox/the-great-game.utils.html
new file mode 100644
index 0000000..b23284d
--- /dev/null
+++ b/docs/codox/the-great-game.utils.html
@@ -0,0 +1,3 @@
+
+the-great-game.utils documentation
\ No newline at end of file
diff --git a/docs/codox/the-great-game.world.routes.html b/docs/codox/the-great-game.world.routes.html
new file mode 100644
index 0000000..4694894
--- /dev/null
+++ b/docs/codox/the-great-game.world.routes.html
@@ -0,0 +1,3 @@
+
+the-great-game.world.routes documentation
Conceptual (plan level) routes, represented as tuples of location ids.
find-route
(find-route world-or-routes from to)
Find a single route from from to to in this world-or-routes, which may be either a world as defined in the-great-game.world.world or else a sequence of tuples of keywords.
\ No newline at end of file
diff --git a/docs/codox/the-great-game.world.run.html b/docs/codox/the-great-game.world.run.html
new file mode 100644
index 0000000..1d3b216
--- /dev/null
+++ b/docs/codox/the-great-game.world.run.html
@@ -0,0 +1,3 @@
+
+the-great-game.world.run documentation
The pipeline to run the simulation each game day. Returns a world like this world, with all the various active elements updated. The optional date argument, if supplied, is set as the :date of the returned world.
\ No newline at end of file
diff --git a/docs/codox/the-great-game.world.world.html b/docs/codox/the-great-game.world.world.html
new file mode 100644
index 0000000..fcec6c3
--- /dev/null
+++ b/docs/codox/the-great-game.world.world.html
@@ -0,0 +1,3 @@
+
+the-great-game.world.world documentation
Find the actual current price of this commodity in this city given this world. NOTE that merchants can only know the actual prices in the city in which they are currently located.
Return a world like this world with only the :date to this date (or id date not supplied, the current value incremented by one). For running other aspects of the simulation, see the-great-game.world.run.
\ No newline at end of file
diff --git a/docs/index.html b/docs/index.html
index 1f40cc8..35656b5 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1,3 +1,13 @@
-
-The-great-game 0.1.0-SNAPSHOT
Dialogue between an enquirer and an agent in this world; returns a map identical to enquirer except that its :gossip collection may have additional entries.
Return a world like this world but with this gossip moved to this new-location. Many gossips are essentially shadow-records of agents of other types, and the movement if the gossip should be controlled by the run function of the type of the record they shadow. The #run function below does NOT call this function.
Adjust the quantity of this commodity currently in stock in this city of this world. Return a fragmentary world which can be deep-merged into this world.
If stock is greater than the maximum of supply and demand, then there is surplus and old price is too high, so shold be reduced. If lower, then it is too low and should be increased.
(update-markets world)(update-markets world city)(update-markets world city commodity)
Return a world like this world, with quantities and prices in markets updated to reflect supply and demand. If city or city and commodity are specified, return a fragmentary world with only the changes for that city (and commodity if specified) populated.
Where a and b are both maps all of whose values are numbers, return a map whose keys are a union of the keys of a and b and whose values are the sums of their respective values.
Augment this plan constructed in this world for this merchant with the :quantity of goods which should be bought and the :expected-profit of the trade.
Find the price anticipated, given this world, by this merchant for this commodity in this city. If no information, assume 1. merchant should be passed as a map, commodity and city should be passed as keywords.
Return the distance to the nearest destination among those of these plans which match these targets. Plans are expected to be plans as returned by generate-trade-plans, q.v.; targets are expected to be as accepted by make-target-filter, q.v.
Return a world like this world, in which this merchant has planned a new trade, and bought appropriate stock for it. If no profitable trade can be planned, the merchant is simply moved towards their home.
Find the best destination in this world for this commodity given this merchant and this origin. If two cities are anticipated to offer the same price, the nearer should be preferred; if two are equally distant, the ones nearer to the merchant’s home should be preferred. merchant may be passed as a map or a keyword; commodity should be passed as a keyword.
-
The returned plan is a map with keys:
-
-
:merchant - the id of the merchant for whom the plan was created;
-
:origin - the city from which the trade starts;
-
:destination - the city to which the trade is planned;
-
:commodity - the commodity to be carried;
-
:buy-price - the price at which that commodity can be bought;
-
:expected-price - the price at which the merchant anticipates that commodity can be sold;
-
:distance - the number of stages in the planned journey
-
:dist-to-home - the distance from destination to the merchant’s home city.
A merchant, in a given location in a world, will choose to buy a cargo within the limit they are capable of carrying, which they can anticipate selling for a profit at a destination.
Return a new world like this world, in which this merchant has sold their current stock in their current location, and planned a new trade, and bought appropriate stock for it.
Conceptual (plan level) routes, represented as tuples of location ids.
find-route
(find-route world-or-routes from to)
Find a single route from from to to in this world-or-routes, which may be either a world as defined in the-great-game.world.world or else a sequence of tuples of keywords.
Find the actual current price of this commodity in this city given this world. NOTE that merchants can only know the actual prices in the city in which they are currently located.
Return a world like this world with only the :date value updated (incremented by one). For running other aspects of the simulation, see the-great-game.world.run.
\ No newline at end of file
diff --git a/project.clj b/project.clj
index 4ffd6a4..ff35841 100644
--- a/project.clj
+++ b/project.clj
@@ -1,13 +1,28 @@
-(defproject the-great-game "0.1.0-SNAPSHOT"
+(defproject the-great-game "0.1.1-SNAPSHOT"
+ :cloverage {:output "docs/cloverage"}
:codox {:metadata {:doc "**TODO**: write docs"
:doc/format :markdown}
- :output-path "docs"
+ :output-path "docs/codox"
:source-uri "https://github.com/simon-brooke/the-great-game/blob/master/{filepath}#L{line}"}
:dependencies [[org.clojure/clojure "1.8.0"]
+ [environ "1.1.0"]
[com.taoensso/timbre "4.10.0"]]
:description "Prototype code towards the great game I've been writing about for ten years, and know I will never finish."
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
:url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"}
- :plugins [[lein-codox "0.10.3"]]
+ :plugins [[lein-cloverage "1.1.1"]
+ [lein-codox "0.10.7"]]
+
+ :release-tasks [["vcs" "assert-committed"]
+ ["change" "version" "leiningen.release/bump-version" "release"]
+ ["vcs" "commit"]
+ ["vcs" "tag" "v." "--no-sign"]
+ ["clean"]
+ ["codox"]
+ ["cloverage"]
+ ["uberjar"]
+ ["change" "version" "leiningen.release/bump-version"]
+ ["vcs" "commit"]]
+
:url "https://github.com/simon-brooke/the-great-game"
)
diff --git a/src/the_great_game/gossip/gossip.clj b/src/the_great_game/gossip/gossip.clj
index 886c525..efe442f 100644
--- a/src/the_great_game/gossip/gossip.clj
+++ b/src/the_great_game/gossip/gossip.clj
@@ -49,9 +49,9 @@
[gossip world new-location]
(let [id (cond
(map? gossip)
- (:id (-> world :gossipe gossip)
+ (-> world :gossips gossip :id)
(keyword? gossip)
- gossip))]
+ gossip)]
(deep-merge
world
{:gossips
diff --git a/src/the_great_game/merchants/markets.clj b/src/the_great_game/merchants/markets.clj
index cd3deb5..c4f0898 100644
--- a/src/the_great_game/merchants/markets.clj
+++ b/src/the_great_game/merchants/markets.clj
@@ -1,8 +1,7 @@
(ns the-great-game.merchants.markets
"Adjusting quantities and prices in markets."
(:require [taoensso.timbre :as l :refer [info error]]
- [the-great-game.utils :refer [deep-merge]]
- [the-great-game.world.world :refer [actual-price default-world]]))
+ [the-great-game.utils :refer [deep-merge]]))
(defn new-price
"If `stock` is greater than the maximum of `supply` and `demand`, then
@@ -30,19 +29,24 @@
su (or (-> c :supplies commodity) 0)
decrement (min st d)
increment (cond
- ;; if its profitable to produce this commodity, the craftspeople
- ;; of the city will do so.
+ ;; if we've two turns' production of this commodity in
+ ;; stock, halt production
+ (> st (* su 2))
+ 0
+ ;; if it is profitable to produce this commodity, the
+ ;; craftspeople of the city will do so.
(> p 1) su
- ;; otherwise, if there isn't a turn's production in stock, top up
- ;; the stock, so that there's something for incoming merchants to
- ;; buy
+ ;; otherwise, if there isn't a turn's production in
+ ;; stock, top up the stock, so that there's something for
+ ;; incoming merchants to buy
(> su st)
(- su st)
- true 0)
+ :else
+ 0)
n (new-price p st su d)]
(if
- (not (= p n))
- (l/info "Price of " commodity " at " id " has changed from " (float p) " to " (float n)))
+ (not= p n)
+ (l/info "Price of" commodity "at" id "has changed from" (float p) "to" (float n)))
{:cities {id
{:stock
{commodity (+ (- st decrement) increment)}
diff --git a/src/the_great_game/merchants/merchant_utils.clj b/src/the_great_game/merchants/merchant_utils.clj
new file mode 100644
index 0000000..b8cd969
--- /dev/null
+++ b/src/the_great_game/merchants/merchant_utils.clj
@@ -0,0 +1,92 @@
+(ns the-great-game.merchants.merchant-utils
+ "Useful functions for doing low-level things with merchants.")
+
+(defn expected-price
+ "Find the price anticipated, given this `world`, by this `merchant` for
+ this `commodity` in this `city`. If no information, assume 1.
+ `merchant` should be passed as a map, `commodity` and `city` should be passed as keywords."
+ [merchant commodity city]
+ (or
+ (:price
+ (last
+ (sort-by
+ :date
+ (-> merchant :known-prices city commodity))))
+ 1))
+
+(defn burden
+ "The total weight of the current cargo carried by this `merchant` in this
+ `world`."
+ [merchant world]
+ (let [m (cond
+ (keyword? merchant)
+ (-> world :merchants merchant)
+ (map? merchant)
+ merchant)
+ cargo (:stock m)]
+ (reduce
+ +
+ 0
+ (map
+ #(* (cargo %) (-> world :commodities % :weight))
+ (keys cargo)))))
+
+
+(defn can-carry
+ "Return the number of units of this `commodity` which this `merchant`
+ can carry in this `world`, given their current burden."
+ [merchant world commodity]
+ (let [m (cond
+ (keyword? merchant)
+ (-> world :merchants merchant)
+ (map? merchant)
+ merchant)]
+ (quot
+ (- (:capacity m) (burden m world))
+ (-> world :commodities commodity :weight))))
+
+(defn can-afford
+ "Return the number of units of this `commodity` which this `merchant`
+ can afford to buy in this `world`."
+ [merchant world commodity]
+ (let [m (cond
+ (keyword? merchant)
+ (-> world :merchants merchant)
+ (map? merchant)
+ merchant)
+ l (:location m)]
+ (quot
+ (:cash m)
+ (-> world :cities l :prices commodity))))
+
+(defn add-stock
+ "Where `a` and `b` are both maps all of whose values are numbers, return
+ a map whose keys are a union of the keys of `a` and `b` and whose values
+ are the sums of their respective values."
+ [a b]
+ (reduce
+ merge
+ a
+ (map
+ #(hash-map % (+ (or (a %) 0) (or (b %) 0)))
+ (keys b))))
+
+(defn add-known-prices
+ "Add the current prices at this `merchant`'s location in the `world`
+ to a new cacke of known prices, and return it."
+ [merchant world]
+ (let [m (cond
+ (keyword? merchant)
+ (-> world :merchants merchant)
+ (map? merchant)
+ merchant)
+ k (:known-prices m)
+ l (:location m)
+ d (:date world)
+ p (-> world :cities l :prices)]
+ (reduce
+ merge
+ k
+ (map
+ #(hash-map % (apply vector cons {:price (p %) :date d} (k %)))
+ (-> world :commodities keys)))))
diff --git a/src/the_great_game/merchants/merchants.clj b/src/the_great_game/merchants/merchants.clj
index 51c813a..9ef160d 100644
--- a/src/the_great_game/merchants/merchants.clj
+++ b/src/the_great_game/merchants/merchants.clj
@@ -1,423 +1,12 @@
(ns the-great-game.merchants.merchants
"Trade planning for merchants, primarily."
- (:require [taoensso.timbre :as l :refer [info error]]
+ (:require [taoensso.timbre :as l :refer [info error spy]]
[the-great-game.utils :refer [deep-merge]]
- [the-great-game.gossip.gossip :refer [move-gossip]]
- [the-great-game.world.routes :refer [find-route]]
- [the-great-game.world.world :refer [actual-price default-world]]))
+ [the-great-game.merchants.strategies.simple :refer [move-merchant]]))
-(defn expected-price
- "Find the price anticipated, given this `world`, by this `merchant` for
- this `commodity` in this `city`. If no information, assume 1.
- `merchant` should be passed as a map, `commodity` and `city` should be passed as keywords."
- [merchant commodity city]
- (or
- (:price
- (last
- (sort-by
- :date
- (-> merchant :known-prices city commodity))))
- 1))
-
-
-(defn burden
- "The total weight of the current cargo carried by this `merchant` in this
- `world`."
- [merchant world]
- (let [m (cond
- (keyword? merchant)
- (-> world :merchants merchant)
- (map? merchant)
- merchant)
- cargo (-> m :stock)]
- (reduce
- +
- 0
- (map
- #(* (cargo %) (-> world :commodities % :weight))
- (keys cargo)))))
-
-
-(defn make-target-filter
- "Construct a filter which, when applied to a list of maps,
- will pass those which match these `targets`, where each target
- is a tuple [key value]."
- ;; TODO: this would probably be more elegant as a macro
- [targets]
- (eval
- (list
- 'fn
- (vector 'plan)
- (cons
- 'and
- (map
- #(list
- '=
- (list (first %) 'plan)
- (nth % 1))
- targets)))))
-
-
-(defn generate-trade-plans
- "Generate all possible trade plans for this `merchant` and this `commodity`
- in this `world`.
-
- Returned plans are maps with keys:
-
-* :merchant - the id of the `merchant` for whom the plan was created;
-* :origin - the city from which the trade starts;
-* :destination - the city to which the trade is planned;
-* :commodity - the `commodity` to be carried;
-* :buy-price - the price at which that `commodity` can be bought;
-* :expected-price - the price at which the `merchant` anticipates
- that `commodity` can be sold;
-* :distance - the number of stages in the planned journey
-* :dist-to-home - the distance from `destination` to the `merchant`'s
- home city."
- [merchant world commodity]
- (let [m (cond
- (keyword? merchant)
- (-> world :merchants merchant)
- (map? merchant)
- merchant)
- origin (-> m :location)]
- (map
- #(hash-map
- :merchant (-> m :id)
- :origin origin
- :destination %
- :commodity commodity
- :buy-price (actual-price world commodity origin)
- :expected-price (expected-price
- m
- commodity
- %)
- :distance (count
- (find-route world origin %))
- :dist-to-home (count
- (find-route
- world
- (:home m)
- %)))
- (remove #(= % origin) (keys (-> world :cities))))))
-
-(defn nearest-with-targets
- "Return the distance to the nearest destination among those of these
- `plans` which match these `targets`. Plans are expected to be plans
- as returned by `generate-trade-plans`, q.v.; `targets` are expected to be
- as accepted by `make-target-filter`, q.v."
- [plans targets]
- (apply
- min
- (map
- :distance
- (filter
- (make-target-filter targets)
- plans))))
-
-(defn plan-trade
- "Find the best destination in this `world` for this `commodity` given this
- `merchant` and this `origin`. If two cities are anticipated to offer the
- same price, the nearer should be preferred; if two are equally distant, the
- ones nearer to the merchant's home should be preferred.
- `merchant` may be passed as a map or a keyword; `commodity` should be
- passed as a keyword.
-
- The returned plan is a map with keys:
-
- * :merchant - the id of the `merchant` for whom the plan was created;
- * :origin - the city from which the trade starts;
- * :destination - the city to which the trade is planned;
- * :commodity - the `commodity` to be carried;
- * :buy-price - the price at which that `commodity` can be bought;
- * :expected-price - the price at which the `merchant` anticipates
- that `commodity` can be sold;
- * :distance - the number of stages in the planned journey
- * :dist-to-home - the distance from `destination` to the `merchant`'s
- home city."
- [merchant world commodity]
- (let [plans (generate-trade-plans merchant world commodity)
- best-prices (filter
- (make-target-filter
- [[:expected-price
- (apply
- max
- (filter number? (map :expected-price plans)))]])
- plans)]
- (first
- (sort-by
- ;; all other things being equal, a merchant would prefer to end closer
- ;; to home.
- #(- 0 (:dist-to-home %))
- ;; a merchant will seek the best price, but won't go further than
- ;; needed to get it.
- (filter
- (make-target-filter
- [[:distance
- (apply min (filter number? (map :distance best-prices)))]])
- best-prices)))))
-
-
-(defn can-carry
- "Return the number of units of this `commodity` which this `merchant`
- can carry in this `world`, given their current burden."
- [merchant world commodity]
- (let [m (cond
- (keyword? merchant)
- (-> world :merchants merchant)
- (map? merchant)
- merchant)]
- (quot
- (- (:capacity m) (burden m world))
- (-> world :commodities commodity :weight))))
-
-(defn can-afford
- "Return the number of units of this `commodity` which this `merchant`
- can afford to buy in this `world`."
- [merchant world commodity]
- (let [m (cond
- (keyword? merchant)
- (-> world :merchants merchant)
- (map? merchant)
- merchant)
- l (:location m)]
- (quot
- (-> m :cash)
- (-> world :cities l :prices commodity))))
-
-(defn augment-plan
- "Augment this `plan` constructed in this `world` for this `merchant` with
- the `:quantity` of goods which should be bought and the `:expected-profit`
- of the trade.
-
- Returns the augmented plan."
- [merchant world plan]
- (let [c (:commodity plan)
- o (:origin plan)
- q (min
- (or
- (-> world :cities o :stock c)
- 0)
- (can-carry merchant world c)
- (can-afford merchant world c))
- p (* q (- (:expected-price plan) (:buy-price plan)))]
- (assoc plan :quantity q :expected-profit p)))
-
-;; (-> default-world :cities :buckie :stock :iron)
-;; (burden :fiona default-world)
-;; (-> default-world :commodities :iron :weight)
-;; (quot 0 10)
-
-(defn select-cargo
- "A `merchant`, in a given location in a `world`, will choose to buy a cargo
- within the limit they are capable of carrying, which they can anticipate
- selling for a profit at a destination."
- [merchant world]
- (let [m (cond
- (keyword? merchant)
- (-> world :merchants merchant)
- (map? merchant)
- merchant)
- origin (-> m :location)
- available (-> world :cities origin :stock)
- plans (map
- #(augment-plan
- m
- world
- (plan-trade m world %))
- (filter
- #(let [q (-> world :cities origin :stock %)]
- (and (number? q) (> q 0)))
- (keys available)))]
- (if
- (not (empty? plans))
- (first
- (sort-by
- #(- 0 (:dist-to-home %))
- (filter
- (make-target-filter
- [[:expected-profit
- (apply max (filter number? (map :expected-profit plans)))]])
- plans))))))
-
-(defn add-stock
- "Where `a` and `b` are both maps all of whose values are numbers, return
- a map whose keys are a union of the keys of `a` and `b` and whose values
- are the sums of their respective values."
- [a b]
- (reduce
- merge
- a
- (map
- #(hash-map % (+ (or (a %) 0) (or (b %) 0)))
- (keys b))))
-
-(defn add-known-prices
- "Add the current prices at this `merchant`'s location in the `world`
- to a new cacke of known prices, and return it."
- [merchant world]
- (let [m (cond
- (keyword? merchant)
- (-> world :merchants merchant)
- (map? merchant)
- merchant)
- k (:known-prices m)
- l (:location m)
- d (:date world)
- p (-> world :cities l :prices)]
- (reduce
- merge
- k
- (map
- #(hash-map % (apply vector cons {:price (p %) :date d} (k %)))
- (-> world :commodities keys)))))
-
-;;; Right, from here on in we're actually moving things in the world, so
-;;; functions generally return modified partial worlds.
-
-(defn plan-and-buy
- "Return a world like this `world`, in which this `merchant` has planned
- a new trade, and bought appropriate stock for it. If no profitable trade
- can be planned, the merchant is simply moved towards their home."
- [merchant world]
- (deep-merge
- world
- (let [m (cond
- (keyword? merchant)
- (-> world :merchants merchant)
- (map? merchant)
- merchant)
- id (:id m)
- location (:location m)
- market (-> world :cities location)
- plan (select-cargo merchant world)]
- (cond
- (not (empty? plan))
- (let
- [c (:commodity plan)
- p (* (:quantity plan) (:buy-price plan))
- q (:quantity plan)]
- (l/info "Merchant " id " bought " q " units of " c " at " location " for " p)
- {:merchants
- {id
- {:stock (add-stock (:stock m) {c q})
- :cash (- (:cash m) p)
- :known-prices (add-known-prices m world)}}
- :cities
- {location
- {:stock (assoc (:stock market) c (- (-> market :stock c) q))
- :cash (+ (:cash market) p)}}})
- ;; if no plan, then if at home stay put
- (= (:location m) (:home m))
- (do
- (l/info "Merchant " id " remains at home in " location)
- {})
- ;; else move towards home
- true
- (let [route (find-route world location (:home m))
- next-location (nth route 1)]
- (l/info "No trade possible at " location "; merchant " id " moves to " next-location)
- (merge
- {:merchants
- {id
- {:location next-location}}}
- (move-gossip id world next-location)))))))
-
-(defn re-plan
- "Having failed to sell a cargo at current location, re-plan a route to
- sell the current cargo. Returns a revised world."
- [merchant world]
- (let [m (cond
- (keyword? merchant)
- (-> world :merchants merchant)
- (map? merchant)
- merchant)
- id (:id m)
- location (:location m)
- plan (augment-plan m world (plan-trade m world (-> m :plan :commodity)))]
- (deep-merge
- world
- {:merchants
- {id
- {:plan plan}}})))
-
-(defn sell-and-buy
- "Return a new world like this `world`, in which this `merchant` has sold
- their current stock in their current location, and planned a new trade, and
- bought appropriate stock for it."
- ;; TODO: this either sells the entire cargo, or, if the market can't afford
- ;; it, none of it. And it does not cope with selling different commodities
- ;; in different markets.
- [merchant world]
- (let [m (cond
- (keyword? merchant)
- (-> world :merchants merchant)
- (map? merchant)
- merchant)
- id (:id m)
- location (:location m)
- market (-> world :cities location)
- stock-value (reduce
- +
- (map
- #(* (-> m :stock %) (-> market :prices m))
- (keys (:stock m))))]
- (if
- (>= (:cash market) stock-value)
- (do
- (l/info
- (apply str (flatten (list "Merchant " id " sells " (:stock m) " at " location " for " stock-value))))
- (plan-and-buy
- merchant
- (deep-merge
- world
- {:merchants
- {id
- {:stock {}
- :cash (+ (:cash m) stock-value)
- :known-prices (add-known-prices m world)}}
- :cities
- {location
- {:stock (add-stock (:stock m) (:stock market))
- :cash (- (:cash market) stock-value)}}})))
- ;; else
- (re-plan merchant world))))
-
-(defn move-merchant
- "Handle general en route movement of this `merchant` in this `world`."
- [merchant world]
- (let [m (cond
- (keyword? merchant)
- (-> world :merchants merchant)
- (map? merchant)
- merchant)
- id (:id m)
- at-destination? (and (:plan m) (= (:location m) (-> m :plan :destination)))
- plan (:plan m)
- next-location (if plan
- (nth 1 (find-route world (:location m) (:destination plan)))
- (:location m))]
- (l/info "Merchant " id " has moved from " (:location m) " to " next-location)
- (cond
- ;; if the merchant is at the destination of their current plan
- ;; sell all cargo and repurchase.
- at-destination?
- (sell-and-buy merchant world plan)
- ;; if they don't have a plan, seek to create one
- (nil? (:plan m))
- (plan-and-buy merchant world)
- ;; otherwise, move one step towards their destination
- true
- (deep-merge
- {:merchants
- {id
- {:location next-location
- :known-prices (add-known-prices m world)}}}
- (move-gossip id world next-location)))))
-
(defn run
- "Return a world like this `world`, but with each merchant moved."
+ "Return a partial world based on this `world`, but with each merchant moved."
[world]
(try
(reduce
@@ -425,7 +14,10 @@
world
(map
#(try
- (move-merchant % world)
+ (let [move-fn (or
+ (-> world :merchants % :move-fn)
+ move-merchant)]
+ (apply move-fn (list % world)))
(catch Exception any
(l/error any "Failure while moving merchant " %)
{}))
diff --git a/src/the_great_game/merchants/planning.clj b/src/the_great_game/merchants/planning.clj
new file mode 100644
index 0000000..e784036
--- /dev/null
+++ b/src/the_great_game/merchants/planning.clj
@@ -0,0 +1,159 @@
+(ns the-great-game.merchants.planning
+ "Trade planning for merchants, primarily. This follows a simple-minded
+ generate-and-test strategy and currently generates plans for all possible
+ routes from the current location. This may not scale. Also, routes do not
+ currently have cost or risk associated with them."
+ (:require [the-great-game.utils :refer [deep-merge make-target-filter]]
+ [the-great-game.merchants.merchant-utils :refer :all]
+ [the-great-game.world.routes :refer [find-route]]
+ [the-great-game.world.world :refer [actual-price default-world]]))
+
+(defn generate-trade-plans
+ "Generate all possible trade plans for this `merchant` and this `commodity`
+ in this `world`.
+
+ Returned plans are maps with keys:
+
+ * :merchant - the id of the `merchant` for whom the plan was created;
+ * :origin - the city from which the trade starts;
+ * :destination - the city to which the trade is planned;
+ * :commodity - the `commodity` to be carried;
+ * :buy-price - the price at which that `commodity` can be bought;
+ * :expected-price - the price at which the `merchant` anticipates
+ that `commodity` can be sold;
+ * :distance - the number of stages in the planned journey
+ * :dist-to-home - the distance from `destination` to the `merchant`'s
+ home city."
+ [merchant world commodity]
+ (let [m (cond
+ (keyword? merchant)
+ (-> world :merchants merchant)
+ (map? merchant)
+ merchant)
+ origin (:location m)]
+ (map
+ #(hash-map
+ :merchant (:id m)
+ :origin origin
+ :destination %
+ :commodity commodity
+ :buy-price (actual-price world commodity origin)
+ :expected-price (expected-price
+ m
+ commodity
+ %)
+ :distance (count
+ (find-route world origin %))
+ :dist-to-home (count
+ (find-route
+ world
+ (:home m)
+ %)))
+ (remove #(= % origin) (-> world :cities keys)))))
+
+(defn nearest-with-targets
+ "Return the distance to the nearest destination among those of these
+ `plans` which match these `targets`. Plans are expected to be plans
+ as returned by `generate-trade-plans`, q.v.; `targets` are expected to be
+ as accepted by `make-target-filter`, q.v."
+ [plans targets]
+ (apply
+ min
+ (map
+ :distance
+ (filter
+ (make-target-filter targets)
+ plans))))
+
+(defn plan-trade
+ "Find the best destination in this `world` for this `commodity` given this
+ `merchant` and this `origin`. If two cities are anticipated to offer the
+ same price, the nearer should be preferred; if two are equally distant, the
+ ones nearer to the merchant's home should be preferred.
+ `merchant` may be passed as a map or a keyword; `commodity` should be
+ passed as a keyword.
+
+ The returned plan is a map with keys:
+
+ * :merchant - the id of the `merchant` for whom the plan was created;
+ * :origin - the city from which the trade starts;
+ * :destination - the city to which the trade is planned;
+ * :commodity - the `commodity` to be carried;
+ * :buy-price - the price at which that `commodity` can be bought;
+ * :expected-price - the price at which the `merchant` anticipates
+ that `commodity` can be sold;
+ * :distance - the number of stages in the planned journey
+ * :dist-to-home - the distance from `destination` to the `merchant`'s
+ home city."
+ [merchant world commodity]
+ (let [plans (generate-trade-plans merchant world commodity)
+ best-prices (filter
+ (make-target-filter
+ [[:expected-price
+ (apply
+ max
+ (filter number? (map :expected-price plans)))]])
+ plans)]
+ (first
+ (sort-by
+ ;; all other things being equal, a merchant would prefer to end closer
+ ;; to home.
+ #(- 0 (:dist-to-home %))
+ ;; a merchant will seek the best price, but won't go further than
+ ;; needed to get it.
+ (filter
+ (make-target-filter
+ [[:distance
+ (apply min (filter number? (map :distance best-prices)))]])
+ best-prices)))))
+
+(defn augment-plan
+ "Augment this `plan` constructed in this `world` for this `merchant` with
+ the `:quantity` of goods which should be bought and the `:expected-profit`
+ of the trade.
+
+ Returns the augmented plan."
+ [merchant world plan]
+ (let [c (:commodity plan)
+ o (:origin plan)
+ q (min
+ (or
+ (-> world :cities o :stock c)
+ 0)
+ (can-carry merchant world c)
+ (can-afford merchant world c))
+ p (* q (- (:expected-price plan) (:buy-price plan)))]
+ (assoc plan :quantity q :expected-profit p)))
+
+(defn select-cargo
+ "A `merchant`, in a given location in a `world`, will choose to buy a cargo
+ within the limit they are capable of carrying, which they can anticipate
+ selling for a profit at a destination."
+ [merchant world]
+ (let [m (cond
+ (keyword? merchant)
+ (-> world :merchants merchant)
+ (map? merchant)
+ merchant)
+ origin (:location m)
+ available (-> world :cities origin :stock)
+ plans (map
+ #(augment-plan
+ m
+ world
+ (plan-trade m world %))
+ (filter
+ #(let [q (-> world :cities origin :stock %)]
+ (and (number? q) (pos? q)))
+ (keys available)))]
+ (if
+ (not (empty? plans))
+ (first
+ (sort-by
+ #(- 0 (:dist-to-home %))
+ (filter
+ (make-target-filter
+ [[:expected-profit
+ (apply max (filter number? (map :expected-profit plans)))]])
+ plans))))))
+
diff --git a/src/the_great_game/merchants/strategies/simple.clj b/src/the_great_game/merchants/strategies/simple.clj
new file mode 100644
index 0000000..d43e952
--- /dev/null
+++ b/src/the_great_game/merchants/strategies/simple.clj
@@ -0,0 +1,173 @@
+(ns the-great-game.merchants.strategies.simple
+ "Default trading strategy for merchants.
+
+ The simple strategy buys a single product in the local market if there is
+ one which can be traded profitably, trades it to the chosen target market,
+ and sells it there. If there is no commodity locally which can be traded
+ profitably, moves towards home with no cargo. If at home and no commodity
+ can be traded profitably, does not move."
+ (:require [taoensso.timbre :as l :refer [info error spy]]
+ [the-great-game.utils :refer [deep-merge]]
+ [the-great-game.gossip.gossip :refer [move-gossip]]
+ [the-great-game.merchants.planning :refer :all]
+ [the-great-game.merchants.merchant-utils :refer
+ [add-stock add-known-prices]]
+ [the-great-game.world.routes :refer [find-route]]))
+
+(defn plan-and-buy
+ "Return a world like this `world`, in which this `merchant` has planned
+ a new trade, and bought appropriate stock for it. If no profitable trade
+ can be planned, the merchant is simply moved towards their home."
+ [merchant world]
+ (let [m (cond
+ (keyword? merchant)
+ (-> world :merchants merchant)
+ (map? merchant)
+ merchant)
+ id (:id m)
+ location (:location m)
+ market (-> world :cities location)
+ plan (select-cargo merchant world)]
+ (l/debug "plan-and-buy: merchant" id)
+ (cond
+ (not (empty? plan))
+ (let
+ [c (:commodity plan)
+ p (* (:quantity plan) (:buy-price plan))
+ q (:quantity plan)]
+ (l/info "Merchant" id "bought" q "units of" c "at" location "for" p plan)
+ {:merchants
+ {id
+ {:stock (add-stock (:stock m) {c q})
+ :cash (- (:cash m) p)
+ :known-prices (add-known-prices m world)
+ :plan plan}}
+ :cities
+ {location
+ {:stock (assoc (:stock market) c (- (-> market :stock c) q))
+ :cash (+ (:cash market) p)}}})
+ ;; if no plan, then if at home stay put
+ (= (:location m) (:home m))
+ (do
+ (l/info "Merchant" id "remains at home in" location)
+ {})
+ ;; else move towards home
+ :else
+ (let [route (find-route world location (:home m))
+ next-location (nth route 1)]
+ (l/info "No trade possible at" location "; merchant" id "moves to" next-location)
+ (merge
+ {:merchants
+ {id
+ {:location next-location}}}
+ (move-gossip id world next-location))))))
+
+(defn re-plan
+ "Having failed to sell a cargo at current location, re-plan a route to
+ sell the current cargo. Returns a revised world."
+ [merchant world]
+ (let [m (cond
+ (keyword? merchant)
+ (-> world :merchants merchant)
+ (map? merchant)
+ merchant)
+ id (:id m)
+ location (:location m)
+ plan (augment-plan m world (plan-trade m world (-> m :plan :commodity)))]
+ (l/debug "re-plan: merchant" id)
+ (deep-merge
+ world
+ {:merchants
+ {id
+ {:plan plan}}})))
+
+(defn sell-and-buy
+ "Return a new world like this `world`, in which this `merchant` has sold
+ their current stock in their current location, and planned a new trade, and
+ bought appropriate stock for it."
+ ;; TODO: this either sells the entire cargo, or, if the market can't afford
+ ;; it, none of it. And it does not cope with selling different commodities
+ ;; in different markets.
+ [merchant world]
+ (let [m (cond
+ (keyword? merchant)
+ (-> world :merchants merchant)
+ (map? merchant)
+ merchant)
+ id (:id m)
+ location (:location m)
+ market (-> world :cities location)
+ stock-value (reduce
+ +
+ (map
+ #(* (-> m :stock %) (-> market :prices m))
+ (keys (:stock m))))]
+ (l/debug "sell-and-buy: merchant" id)
+ (if
+ (>= (:cash market) stock-value)
+ (do
+ (l/info "Merchant" id "sells" (:stock m) "at" location "for" stock-value)
+ (plan-and-buy
+ merchant
+ (deep-merge
+ world
+ {:merchants
+ {id
+ {:stock {}
+ :cash (+ (:cash m) stock-value)
+ :known-prices (add-known-prices m world)}}
+ :cities
+ {location
+ {:stock (add-stock (:stock m) (:stock market))
+ :cash (- (:cash market) stock-value)}}})))
+ ;; else
+ (re-plan merchant world))))
+
+(defn move-merchant
+ "Handle general en route movement of this `merchant` in this `world`;
+ return a (partial or full) world like this `world` but in which the
+ merchant may have been moved ot updated."
+ [merchant world]
+ (let [m (cond
+ (keyword? merchant)
+ (-> world :merchants merchant)
+ (map? merchant)
+ merchant)
+ id (:id m)
+ at-destination? (and (:plan m) (= (:location m) (-> m :plan :destination)))
+ plan (:plan m)
+ next-location (if plan
+ (nth
+ (find-route
+ world
+ (:location m)
+ (:destination plan))
+ 1)
+ (:location m))]
+ (l/debug "move-merchant: merchant" id "at" (:location m)
+ "destination" (-> m :plan :destination) "next" next-location
+ "at destination" at-destination?)
+ (cond
+ ;; if the merchant is at the destination of their current plan
+ ;; sell all cargo and repurchase.
+ at-destination?
+ (sell-and-buy merchant world)
+ ;; if they don't have a plan, seek to create one
+ (nil? plan)
+ (plan-and-buy merchant world)
+ ;; otherwise, move one step towards their destination
+ (and next-location (not= next-location (:location m)))
+ (do
+ (l/info "Merchant " id " moving from " (:location m) " to " next-location)
+ (deep-merge
+ {:merchants
+ {id
+ {:location next-location
+ :known-prices (add-known-prices m world)}}}
+ (move-gossip id world next-location)))
+ :else
+ (do
+ (l/info "Merchant" id "has plan but no next-location; currently at"
+ (:location m) ", destination is" (:destination plan))
+ world))))
+
diff --git a/src/the_great_game/merchants/strategy.clj b/src/the_great_game/merchants/strategy.clj
deleted file mode 100644
index e69de29..0000000
diff --git a/src/the_great_game/utils.clj b/src/the_great_game/utils.clj
index 6ea3c84..98c2f6f 100644
--- a/src/the_great_game/utils.clj
+++ b/src/the_great_game/utils.clj
@@ -3,7 +3,7 @@
(defn cyclic?
"True if two or more elements of `route` are identical"
[route]
- (not (= (count route)(count (set route)))))
+ (not= (count route)(count (set route))))
(defn deep-merge
"Recursively merges maps. Stolen from
@@ -15,3 +15,21 @@
(last xs)))]
(reduce m maps)))
+(defn make-target-filter
+ "Construct a filter which, when applied to a list of maps,
+ will pass those which match these `targets`, where each target
+ is a tuple [key value]."
+ ;; TODO: this would probably be more elegant as a macro
+ [targets]
+ (eval
+ (list
+ 'fn
+ (vector 'm)
+ (cons
+ 'and
+ (map
+ #(list
+ '=
+ (list (first %) 'm)
+ (nth % 1))
+ targets)))))
diff --git a/src/the_great_game/world/routes.clj b/src/the_great_game/world/routes.clj
index 93926ee..c45debc 100644
--- a/src/the_great_game/world/routes.clj
+++ b/src/the_great_game/world/routes.clj
@@ -9,11 +9,11 @@
(map
(fn [to] (cons from to))
(remove
- #(empty? %)
+ empty?
(map
(fn [route]
- (filter
- #(not (= from %))
+ (remove
+ #(= from %)
(if (some #(= % from) route) route)))
routes))))
([routes from to]
@@ -30,14 +30,12 @@
(not (empty? steps))
(let [paths (remove
cyclic?
- (apply
- concat
- (map
+ (mapcat
(fn [path]
(map
(fn [x] (concat path (rest x)))
(find-routes routes (last path))))
- steps)))
+ steps))
found (filter
#(= (last %) to) paths)]
(if
diff --git a/src/the_great_game/world/run.clj b/src/the_great_game/world/run.clj
index 5abcc2d..b324101 100644
--- a/src/the_great_game/world/run.clj
+++ b/src/the_great_game/world/run.clj
@@ -1,17 +1,39 @@
(ns the-great-game.world.run
"Run the whole simulation"
- (:require [taoensso.timbre :as log]
+ (:require [environ.core :refer [env]]
+ [taoensso.timbre :as timbre]
+ [taoensso.timbre.appenders.3rd-party.rotor :as rotor]
[the-great-game.gossip.gossip :as g]
[the-great-game.merchants.merchants :as m]
[the-great-game.merchants.markets :as k]
[the-great-game.world.world :as w]))
+(defn init
+ ([]
+ (init {}))
+ ([config]
+ (timbre/merge-config!
+ {:appenders
+ {:rotor (rotor/rotor-appender
+ {:path "the-great-game.log"
+ :max-size (* 512 1024)
+ :backlog 10})}
+ :level (or
+ (:log-level config)
+ (if (env :dev) :debug)
+ :info)})))
(defn run
"The pipeline to run the simulation each game day. Returns a world like
- this world, with all the various active elements updated."
- [world]
+ this world, with all the various active elements updated. The optional
+ `date` argument, if supplied, is set as the `:date` of the returned world."
+ ([world]
(g/run
(m/run
(k/run
(w/run world)))))
+ ([world date]
+ (g/run
+ (m/run
+ (k/run
+ (w/run world date))))))
diff --git a/src/the_great_game/world/world.clj b/src/the_great_game/world/world.clj
index d8a7c65..9334237 100644
--- a/src/the_great_game/world/world.clj
+++ b/src/the_great_game/world/world.clj
@@ -183,8 +183,10 @@
(-> world :cities city :prices commodity))
(defn run
- "Return a world like this `world` with only the `:date` value updated
- (incremented by one). For running other aspects of the simulation, see
- [[the-great-game.world.run]]."
- [world]
- (assoc world :date (inc (or (:date world) 0))))
+ "Return a world like this `world` with only the `:date` to this `date`
+ (or id `date` not supplied, the current value incremented by one). For
+ running other aspects of the simulation, see [[the-great-game.world.run]]."
+ ([world]
+ (run world (inc (or (:date world) 0))))
+ ([world date]
+ (assoc world :date date)))
diff --git a/test/the_great_game/merchants/merchant_utils_test.clj b/test/the_great_game/merchants/merchant_utils_test.clj
new file mode 100644
index 0000000..2f9071e
--- /dev/null
+++ b/test/the_great_game/merchants/merchant_utils_test.clj
@@ -0,0 +1,24 @@
+(ns the-great-game.merchants.merchant-utils-test
+ (:require [clojure.test :refer :all]
+ [the-great-game.utils :refer [deep-merge]]
+ [the-great-game.world.world :refer [default-world]]
+ [the-great-game.merchants.merchant-utils :refer :all]))
+
+(deftest expected-price-test
+ (testing "Anticipated prices in markets"
+ (let [world (deep-merge
+ default-world
+ {:merchants
+ {:archie
+ {:known-prices
+ {:buckie
+ {:iron
+ [{:price 1.7 :date 1}
+ {:price 2 :date 0}]}}}}})]
+ (let [actual (expected-price (-> world :merchants :archie) :fish :edinburgh)
+ expected 1] ;;
+ (is (= actual expected) "if no information assume 1"))
+ (let [actual (expected-price (-> world :merchants :archie) :iron :buckie)
+ expected 1.7] ;;
+ (is (= actual expected) "if information select the most recent")))))
+
diff --git a/test/the_great_game/merchants/merchants_test.clj b/test/the_great_game/merchants/planning_test.clj
similarity index 81%
rename from test/the_great_game/merchants/merchants_test.clj
rename to test/the_great_game/merchants/planning_test.clj
index 8089c86..5662e35 100644
--- a/test/the_great_game/merchants/merchants_test.clj
+++ b/test/the_great_game/merchants/planning_test.clj
@@ -1,26 +1,9 @@
-(ns the-great-game.merchants.merchants-test
+(ns the-great-game.merchants.planning-test
(:require [clojure.test :refer :all]
[the-great-game.utils :refer [deep-merge]]
[the-great-game.world.world :refer [default-world]]
- [the-great-game.merchants.merchants :refer :all]))
+ [the-great-game.merchants.planning :refer :all]))
-(deftest expected-price-test
- (testing "Anticipated prices in markets"
- (let [world (deep-merge
- default-world
- {:merchants
- {:archie
- {:known-prices
- {:buckie
- {:iron
- [{:price 1.7 :date 1}
- {:price 2 :date 0}]}}}}})]
- (let [actual (expected-price (-> world :merchants :archie) :fish :edinburgh)
- expected 1] ;;
- (is (= actual expected) "if no information assume 1"))
- (let [actual (expected-price (-> world :merchants :archie) :iron :buckie)
- expected 1.7] ;;
- (is (= actual expected) "if information select the most recent")))))
(deftest plan-trade-test
(testing "Lower level trade planning"