fix: Changing way to sell with demand
This commit is contained in:
@@ -3,9 +3,9 @@
|
||||
"click": { "yield": 1 },
|
||||
"unit": { "materialPerUnit": 1 },
|
||||
"market": {
|
||||
"fairPrice": 0.25,
|
||||
"referencePrice": 0.25,
|
||||
"baseDemand": 2.5,
|
||||
"elasticity": 2,
|
||||
"elasticity": 3,
|
||||
"minPrice": 0.01,
|
||||
"maxPrice": 50,
|
||||
"priceStep": 0.01
|
||||
@@ -17,13 +17,13 @@
|
||||
"startUnitPrice": 0.08,
|
||||
"floorUnitPrice": 0.01,
|
||||
"scale": 3000,
|
||||
"minFactor": 0.4,
|
||||
"maxFactor": 3,
|
||||
"volatility": 0.3,
|
||||
"reversion": 0.08,
|
||||
"spikeChance": 0.06,
|
||||
"spikeSize": 1.9,
|
||||
"updateEverySeconds": 1.5,
|
||||
"minFactor": 0.3,
|
||||
"maxFactor": 4,
|
||||
"volatility": 0.5,
|
||||
"reversion": 0.12,
|
||||
"spikeChance": 0.2,
|
||||
"spikeSize": 2,
|
||||
"updateEverySeconds": 10,
|
||||
"autoBuyBelowFactor": 1.2
|
||||
},
|
||||
"marketing": { "baseCost": 40, "costGrowth": 1.45, "demandPerLevel": 1.4 },
|
||||
|
||||
@@ -45,17 +45,26 @@ export function materialBatch(mm, totalProduced, rate, factor, discount) {
|
||||
const basis = Math.max(totalProduced * mm.batchShareOfTotal, rate * mm.batchSecondsOfProduction);
|
||||
const doublings = Math.max(0, Math.floor(Math.log2(Math.max(1, basis / mm.minBatch))));
|
||||
const size = mm.minBatch * 2 ** doublings;
|
||||
const cost = Math.max(1, Math.ceil(size * materialUnitPrice(mm, totalProduced) * factor * discount));
|
||||
return { size, cost };
|
||||
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`.
|
||||
* Demand is maxed at or below the fair price and drops steeply above it, so
|
||||
* extra production is only worth selling at a higher price while supply is
|
||||
* short; to sell more at the fair price, the player needs more demand.
|
||||
* 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) {
|
||||
const overpriced = Math.max(1, price / market.fairPrice);
|
||||
return (market.baseDemand * multiplier) / overpriced ** market.elasticity;
|
||||
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);
|
||||
}
|
||||
|
||||
+19
-4
@@ -6,7 +6,8 @@ import { t } from '../services/i18n.js';
|
||||
import { $, h, icon, refreshStaticIcons } from './dom.js';
|
||||
|
||||
const SLIDER_STEPS = 1000;
|
||||
const TREND = { '-1': '▼', 1: '▲' }; // last move of the material price
|
||||
const MARKET_TOLERANCE = 1.15; // demand within ±15 % of output counts as balanced
|
||||
const TREND ={ '-1': '▼', 1: '▲' }; // last move of the material price
|
||||
|
||||
function setText(node, text) {
|
||||
if (node.textContent !== text) node.textContent = text;
|
||||
@@ -184,14 +185,28 @@ export function createView(actions) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Feedback on the price: what customers want versus what is produced, and
|
||||
* which side of the balance the player is on. It never gives the balanced
|
||||
* price itself: it depends on output and every bonus, so it must be found.
|
||||
*/
|
||||
function marketHint(d) {
|
||||
const demand = fmt.number(d.demand);
|
||||
if (d.rate <= 0) return t('ui.hintDemandOnly', { demand });
|
||||
const ratio = d.demand / d.rate;
|
||||
let verdict = 'ui.marketBalanced';
|
||||
if (ratio > MARKET_TOLERANCE) verdict = 'ui.marketShort';
|
||||
else if (ratio < 1 / MARKET_TOLERANCE) verdict = 'ui.marketLong';
|
||||
return `${t('ui.hintDemand', { demand, production: fmt.number(d.rate) })} ${t(verdict)}`;
|
||||
}
|
||||
|
||||
function updateMarket(state, d) {
|
||||
const { fairPrice, minPrice } = ctx.rules.market;
|
||||
const { minPrice } = ctx.rules.market;
|
||||
if (document.activeElement !== el.priceInput) el.priceInput.value = state.price.toFixed(2);
|
||||
if (document.activeElement !== el.priceSlider) {
|
||||
el.priceSlider.value = Math.round(sliderFromPrice(Math.max(state.price, minPrice)));
|
||||
}
|
||||
const hintKey = state.price > fairPrice ? 'ui.hintHigh' : 'ui.hintFair';
|
||||
setText(el.priceHint, t(hintKey, { fair: money(fairPrice), demand: fmt.number(d.demand) }));
|
||||
setText(el.priceHint, marketHint(d));
|
||||
|
||||
const nextLevelMultiplier = ctx.rules.marketing.demandPerLevel ** (state.marketing + 1);
|
||||
setText(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"meta": { "name": "English" },
|
||||
"meta": {
|
||||
"name": "English"
|
||||
},
|
||||
"ui": {
|
||||
"theme": "Theme",
|
||||
"language": "Language",
|
||||
@@ -25,12 +27,15 @@
|
||||
"makeYield": "+{amount} per click",
|
||||
"batchInfo": "+{amount} {@theme.res.material} · {trend} {cost}",
|
||||
"marketingInfo": "Level {level} → demand ×{multiplier} · {cost}",
|
||||
"hintFair": "At or below the fair price ({fair}) demand is at its maximum: lowering the price sells nothing more.",
|
||||
"hintHigh": "Customers want {demand}/s at this price. Above the fair price ({fair}) demand drops fast.",
|
||||
"owned": "Owned: {count}",
|
||||
"rateEach": "{rate}/s each",
|
||||
"buyN": "Buy ×{count}",
|
||||
"upgradeCount": "{owned} / {total}"
|
||||
"upgradeCount": "{owned} / {total}",
|
||||
"hintDemand": "At this price customers want {demand}/s; you make {production}/s.",
|
||||
"hintDemandOnly": "At this price customers want {demand}/s.",
|
||||
"marketShort": "Demand outpaces your output: you are selling too cheap.",
|
||||
"marketLong": "You make more than customers want: stock piles up. Try a lower price.",
|
||||
"marketBalanced": "Supply and demand are about even."
|
||||
},
|
||||
"effect": {
|
||||
"click_mult": "Manual work ×{value}",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"meta": { "name": "Français" },
|
||||
"meta": {
|
||||
"name": "Français"
|
||||
},
|
||||
"ui": {
|
||||
"theme": "Thème",
|
||||
"language": "Langue",
|
||||
@@ -25,12 +27,15 @@
|
||||
"makeYield": "+{amount} par clic",
|
||||
"batchInfo": "+{amount} {@theme.res.material} · {trend} {cost}",
|
||||
"marketingInfo": "Niveau {level} → demande ×{multiplier} · {cost}",
|
||||
"hintFair": "Au prix juste ({fair}) ou en dessous, la demande est maximale : baisser le prix ne vend rien de plus.",
|
||||
"hintHigh": "Les clients veulent {demand}/s à ce prix. Au-dessus du prix juste ({fair}), la demande chute vite.",
|
||||
"owned": "Possédé : {count}",
|
||||
"rateEach": "{rate}/s chacun",
|
||||
"buyN": "Acheter ×{count}",
|
||||
"upgradeCount": "{owned} / {total}"
|
||||
"upgradeCount": "{owned} / {total}",
|
||||
"hintDemand": "À ce prix, les clients veulent {demand}/s ; vous produisez {production}/s.",
|
||||
"hintDemandOnly": "À ce prix, les clients veulent {demand}/s.",
|
||||
"marketShort": "La demande dépasse votre production : vous vendez trop bon marché.",
|
||||
"marketLong": "Vous produisez plus que la demande : le stock s'accumule. Essayez un prix plus bas.",
|
||||
"marketBalanced": "L'offre et la demande sont à peu près égales."
|
||||
},
|
||||
"effect": {
|
||||
"click_mult": "Travail manuel ×{value}",
|
||||
|
||||
Reference in New Issue
Block a user