83 lines
3.5 KiB
JavaScript
83 lines
3.5 KiB
JavaScript
// Pure math helpers. No state, no DOM.
|
|
|
|
export function clamp(value, min, max) {
|
|
return Math.min(max, Math.max(min, value));
|
|
}
|
|
|
|
/**
|
|
* Demand bonus from owning chips: diminishing returns (logarithmic), so a big
|
|
* lucky streak can never run away with the game.
|
|
*/
|
|
export function chipDemandBonus(chips, gamble) {
|
|
return 1 + gamble.chipBonusScale * Math.log2(1 + Math.max(0, chips));
|
|
}
|
|
|
|
/** Price of the next item when `owned` items are already owned. */
|
|
export function unitCost(base, growth, owned) {
|
|
return base * growth ** owned;
|
|
}
|
|
|
|
/** Total price of buying `count` more items (geometric series, rounded up). */
|
|
export function bulkCost(base, growth, owned, count) {
|
|
if (count <= 0) return 0;
|
|
return Math.ceil((unitCost(base, growth, owned) * (growth ** count - 1)) / (growth - 1));
|
|
}
|
|
|
|
/** How many items `budget` can pay for, robust against float error. */
|
|
export function maxAffordable(base, growth, owned, budget) {
|
|
const first = unitCost(base, growth, owned);
|
|
if (Number.isNaN(budget) || budget < first) return 0;
|
|
let n = Math.floor(Math.log((budget * (growth - 1)) / first + 1) / Math.log(growth));
|
|
while (n > 0 && bulkCost(base, growth, owned, n) > budget) n--;
|
|
while (bulkCost(base, growth, owned, n + 1) <= budget) n++;
|
|
return Math.max(n, 0);
|
|
}
|
|
|
|
/**
|
|
* Raw-material price per unit of material, before market noise. Expensive at
|
|
* first, then cheaper relative to revenue as the operation scales
|
|
* (economies of scale): it falls from `startUnitPrice` towards `floorUnitPrice`.
|
|
*/
|
|
export function materialUnitPrice(mm, totalProduced) {
|
|
const scale = 1 / Math.sqrt(1 + totalProduced / mm.scale);
|
|
return mm.floorUnitPrice + (mm.startUnitPrice - mm.floorUnitPrice) * scale;
|
|
}
|
|
|
|
/**
|
|
* What one purchase of material looks like.
|
|
*
|
|
* The lot covers `batchSecondsOfProduction` of current output (in doubling
|
|
* steps, never below `minBatch`), times `sizeMultiplier` from the "bigger
|
|
* stacks" upgrades. The price of a lot is proportional to its size: a bigger
|
|
* stack costs more in total, while the price PER UNIT keeps falling with scale
|
|
* (see materialUnitPrice). `factor` is the market's current noise and
|
|
* `discount` comes from upgrades. Cost is exact to the cent.
|
|
*/
|
|
export function materialBatch(mm, totalProduced, rate, factor, discount, sizeMultiplier = 1) {
|
|
const basis = rate * mm.batchSecondsOfProduction;
|
|
const doublings = Math.max(0, Math.floor(Math.log2(Math.max(1, basis / mm.minBatch))));
|
|
const size = mm.minBatch * 2 ** doublings * sizeMultiplier;
|
|
const cents = size * materialUnitPrice(mm, totalProduced) * factor * discount;
|
|
return { size, cost: Math.max(0.01, Math.round(cents * 100) / 100) };
|
|
}
|
|
|
|
/**
|
|
* Units customers want to buy per second at `price`: a smooth curve, with more
|
|
* demand the lower the price and less the higher it is (`referencePrice` is
|
|
* just where demand equals baseDemand x multiplier).
|
|
*/
|
|
export function demandPerSecond(market, price, multiplier) {
|
|
return market.baseDemand * multiplier * (market.referencePrice / price) ** market.elasticity;
|
|
}
|
|
|
|
/**
|
|
* The price at which customers buy exactly what is produced. Selling for less
|
|
* gains no volume (everything is already sold); selling for more leaves stock
|
|
* unsold. It moves with output and every demand bonus, and the UI deliberately
|
|
* does not reveal it: the player finds it from the market feedback.
|
|
*/
|
|
export function balancedPrice(market, supplyPerSecond, multiplier) {
|
|
const demandAtReference = market.baseDemand * multiplier;
|
|
return market.referencePrice * (demandAtReference / supplyPerSecond) ** (1 / market.elasticity);
|
|
}
|