How prices work and fail to work

In market.cs, the important function is supplyDemand(). It's used in changeSupply(), as changePrice(eIndex, supplyDemand(getWholePrice(eIndex))). eIndex is the resource index. changePrice() calls setPrice(), setPrice() sets the price, clamping between 1-1000.

if (iWholePrice > 100)
{
    iValue = (iValue / 100) + 20;
}
else if (iWholePrice > 10)
{
    iValue = (iValue / 100) + 10;
}
End of quote

Letting p be the current price, we learn the important formulas:

p += k(p/100 + 20) for p > 100

p += k(p/100 + 10) for p > 10

p += kp for p < 10

End of quote

k is a constant which depends on the player count. From standard ODE, we derive three exponential curves as a piecewise function:

p = ce^(x/m) - 2000 for p > 100, where c and m are some constants

p = be^(x/j) - 1000 for p > 10, where b and j are some constants

p = ae^(x/i) for p < 10, where a and i are some constants

End of quote

Here's how you can guarantee that Mohawk doesn't understand how its own price algorithm works. For 10 < p < 100, the change is almost a straight line, but is in fact very slightly nonlinear, for no purpose whatsoever. Notice how p/100 + 10 varies by only 9% across the range of p. For 100 < p, the change is also very slightly non-linear, also for no purpose whatsoever, with a discrepancy of only 9% for p < 300. For p < 10 it is exponential, which is where it is most complicated, and also matters least since the resource is worth nothing. The only effect of the exponential curve is to specify how many units can be sold before clamping kicks in. Here's the graph of the price change after selling a single resource: https://www.wolframalpha.com/input/?i=Plot[Piecewise[{{x,+x+%3C+10},+{10+%2B+x%2F100,+10+%3C+x+%3C+100},+{20+%2B+x%2F100,++x+%3E+100}}],+{x,+1,+200}] Notice how it is very slightly non-horizontal in each of the regions, just enough to mess with you, but not enough to affect anything.

So, what would be a better system? We can keep all the mechanics of the current system, but take out the useless complexity which has near-zero effect. This makes prices very easy to understand. If you buy 15 units, and the price is between 10 and 100, the price will change by a constant amount. If you buy 15 units, and the price is more than 100, the price will change by double that amount. For prices < 10, the price linearly tells you how far you have to go until clamping kicks in. For prices < 300, the following code has 2% standard deviation from the current system, while being much more logical. We get: https://www.wolframalpha.com/input/?i=Plot[Piecewise[{{9%2Flog+10,+x+%3C+10},+{10,+10+%3C+x+%3C+100},+{20,++x+%3E+100}}],+{x,+1,+200}]

if (iWholePrice > 100)
{
    iValue = 22;
}
else if (iWholePrice > 10)
{
    iValue = 10.5;
}
else iValue = 3.91;
End of quote

A small edit to the constants would make everything a nice number:

if (iWholePrice > 100)
{
    iValue = 20;
}
else if (iWholePrice > 10)
{
    iValue = 10;
}
else iValue = 5;
End of quote

EDIT: You could also remove the sudden thresholds at p=10, 100, and replace the system with a single formula, dp = p^(log 2/log10). Here, if the price becomes *10, the price change becomes *2. This makes the market hard to understand, but it's smooth and there are no jumps. Its behavior at p =10, 100 is the same as the existing formula, and it can handle even higher prices. http://www.wolframalpha.com/input/?i=plot+x^%28log2+%2F+log10%29+from+x+%3D+0+to+200

iValue = 4 * iWholePrice^(0.30103)
End of quote

20,979 views 6 replies
Reply #1 Top

thanks for pointing this out - "iValue = (iValue / 100) + 10;" was supposed to be "iValue = (iValue / 10) + 10;"

Reply #2 Top

Quoting Soren_Johnson, reply 1

thanks for pointing this out - "iValue = (iValue / 100) + 10;" was supposed to be "iValue = (iValue / 10) + 10;"
End of Soren_Johnson's quote

In that case, price changes are no longer constants anywhere, so why not use

iValue = (int)(4 * Math.Pow(iWholePrice, 0.30103));
End of quote
This formula has no arbitrary thresholds, and is completely smooth. This plot shows how piecewise functions are just approximations to better smooth functions.

https://www.wolframalpha.com/input/?i=Plot[{4.8+x^%28log2+%2F+log10%29,+Piecewise[{{x,+x+%3C+10},+{10+%2B+x%2F10,+10+%3C+x+%3C+100},+{20+%2B+x%2F100,++x+%3E+100}}]},+{x,+1,+200}]

Reply #3 Top

You are right that that is better, but every time we are calculating a players value (which involves resource value) we are simulating selling every single resource they have, one at a time, so I don't think using anything but integer math is a good idea for perf reasons.

Reply #4 Top

Prices become much easier and faster for the program to calculate using the single-line formula. The current algorithm sells units one at a time. But we can sell whole batches with no problem, because the ODE is solvable, and piecewise functions aren't. http://www.wolframalpha.com/input/?i=d%2Fdx+%28y%29+%3D+5+y^%28log+2+%2F+log+10%29

We can integrate this, we get http://www.wolframalpha.com/input/?i=integrate+%28c+%2B+kx%29^%28log+10%2Flog+5%29

The formula for price as a function of supply is (1 + kx)^(log 10 / log 5), where k is the player multiplier. Let k = 1/40 as an example. If the resource supply changes to 100, the new price is (1 + 100/40)^(log 10 / log 5) = $10. If some players buys 500 units, the resource supply changes to 600, the new price is (1 + 600/40)^(log 10 / log 5) = $53. (Note: we're using natural logarithms, base e)

Suppose the resource supply is 600, and a player sells 59 units. The money he makes is easy, we use the integral we calculated before, (log 5 / log 50) * (k x+1)^(log50/log5)/k. The total money in the market at 600 is (log 5 / log 50) * (600/40+1)^(log50/log5)*40 = 13905, rounded to an int. The total money after selling is (log 5 / log 50) * (541/40+1)^(log50/log5)*40 = 10992, rounded to an int. Subtracting these two rounded numbers gives $2913, so the player made 2913 after selling 59 resources. All of this is just one line of code, with no loops.

The drawback is that occasionally you will sell one unit of a resource for $10, then sell the next unit for $11. However, it's not possible for the player to cheat any money this way by buying and selling.

Reply #5 Top

Furthermore, suppose we have 3 players selling all their resources: 100, 50, 15, such as the AI at the end of a campaign round. Then this is also easy to split. Simply calculate the total market change from selling 165 resources, and then split the profits proportionally. No need to worry about selling order, and nobody gets an unfair advantage from selling more units or selling less units.

Reply #6 Top

In fact, you don't even need to do either FP operations or loops. There are only about a thousand or so possible total resource supplies. You can precompute the prices for all one thousand of them, then store them in a 4KB array. The CPU cost of this is less than computing the resource value for a single game tick, and only needs to be done once per game. Finding the value of 59 units is two lookups: one lookup at 600, one lookup at 541, and then a subtraction.

What's more, you can eliminate all $1 rounding spikes from my integral formula by simply going through the array and smoothing them all out. So if you sell a unit at $10, every sell after that will be necessarily $10 or less.