And for another milestone: as of right now I have reached 99 recipes in my system, an increase of 25 from the listing given in this blog's very first post. See if you can spot all the new items on the table (fewer than 25 new ones, since some of the 25 are undisplayed "partial goods.") Several still need tweaking, and some need serious tweaking, but for now I have numbers I can work with.
Now that I've shown the latest version of the price table output, I'll take a few steps back and return to where I was last time with explaining the pipeline of programs which build up to this table itself. Today's agenda is the imports and pricing calculator, which proceeds in several steps.
- Since I have access to a list of all the types of resources and services which are available in my world, we first loop through all of the towns listed in ** Town Info**. If a town doesn't have a given service or material, we set the count for it equal to 0. This step is done purely for programming purposes, not because it represents anything special in the pricing algorithm. If I didn't do this "zeroing out" before anything else, I'd have to have repetitive code later in the file to check each town's info storage to check whether a given resource is present. This way, I can just assume that every town has an entry for every resource.
- I copy the Town Info items into a dictionary called Original Town Info. These will be kept so that I can refer ot the original values, since I will be updating the values in the Town Info dictionary in-place during the import step. While doing this, in order to save a step, I also calculate the original global totals for all materials. This is saved in the variable originalWorldResourceCounts (ought to be called ...materialCounts, but this code was written before I'd realized I wanted to reserve the word "resource" to refer to both "material" and "service." Oh well. One day I might change it, but it's in no way a priority.)
- We do some definitions which will be needed later. First, by dividing world production into originalWorldResourceCounts for each resource, we get the size of each reference of that resource in "concrete" units -- pounds, ounces, cubic feet, and so on. This is one thing we need to find the price of, say, a pound of iron ore.
- I don't think it's come up before, but: anywhere you see the Decimal() constructor function, just read it as the number which is parenthesized. Decimal() is a tool for representing decimal and fraction numbers exactly, without being subject to minute errors which arise when working with the basic decimal representation of numbers in Python and most other programming languages. I use numbers wrapped in Decimal() in order to preserve accuracy in my calculations, much like significant figures in chemistry equations.
- Finally, note that I don't have units for services. I don't yet have a need to figure out exactly how many people 1 reference to "blacksmith" represents. Perhaps some future upgrade will require this.
- I have labelled on the graph the distance between each town, and the resources each one has.
- Here is how importing to town A works:
- Let's begin with the products at town B, which is 2 units of grain. The distance from A to B is given here as 3. We add one to this distance: otherwise, in the case of a distance of 1 such as exists between B and C, then the exact same amount which is available at B would be available at C, and vice versa. That is to be avoided: we want travel distance to reduce the movement of goods.
- So: having added 1 to the distance of 3 to get 4, we then divide the 2 units of grain originally at B by this 4, and get 0.5. I emphasize "originally" because we do not want to be basing calculation of goods imported from A to B on a figure which includes that which B has already imported! Otherwise we'd be double-dipping: import to B based on A, then import to A from B based on figures which take A's import to B into account, then we could import to B from A and the figure goes up again ... it doesn't make sense. You could do it, but it wouldn't make any sense. There is one round of imports, only, and the amount to import from each place is always determined by the references it ORIGINALLY had, before anywhere did any importing from anywhere else.
- Thus, in Town Info (NOT in Original Town Info), we increase A's references to grain by 0.5. The higher that number goes, the cheaper grain will end up being at town A, since it represents more availability.
- OK! Partway done for town A. Importing from town C to town A is just the same procedure. The distance from A to C is 5, so our divisor will be 6. C has 1 reference each for sugarcane and copper ore, so A will import 1/6 of a reference for each of those. We add those fractions to Town Info for A. We are now done with importing for town A. Its final reference counts are: (1+1/6) or 7/6 copper ore; 1/6 sugarcane; 0.5 grain. Remember that by using Decimal(), I don't have to worry about calculating fractions like 7/6 by hand, or worrying about repeating decimals numbers like 1/6.
- We repeat this procedure for each town, importing from all other towns. Thus the number of import operations performed is N * (N-1): for each of N towns, we import to it from all of the (N-1) other towns.
- The algorithm here proceeds in two parts:
- For a resource R at town T, take the ratio of T's references of R to world total references for R, and divide the base value of a reference into that ratio to get the price per reference for R at T. The higher T's references to something are, the closer the ratio is to 1, and thus the lower that price per reference is.
- Then, divide that base price by the size of the reference, and store this value as the cost for a single "smallest unit" (1 lb, 1 oz, whatever) of the resource R.
- Let's do an example: let's arbitrarily fix the value of a reference at 1000 gold pieces (GP). Continuing the example above, the references to copper ore at town A are (7/6), and the world total references to copper PRE-IMPORT were 2. Thus the ratio is (7/6)/2 = ~0.583; we divide 1000 GP by this number to get ~1,714.286 GP. This is the base price per reference of copper, at town A.
- Then, assuming that the size of a copper ore reference is 2000 pounds, we divide again: ~1,714.286 GP / 2000 = ~0.857 GP per pound of copper ore.
- And there you have it! We now have a starting place for any recipe which works with copper ore; or the price paid to the assayer, if the players want to buy copper ore for some purpose or other.