72 lines
2.4 KiB
JavaScript
72 lines
2.4 KiB
JavaScript
import { clamp } from './formulas.js';
|
|
|
|
export const STATE_VERSION = 1;
|
|
|
|
const MAX_NUMBER = 1e300;
|
|
const MAX_OWNED = 4000; // keeps cost formulas (growth ** owned) finite
|
|
const MAX_MARKETING = 300;
|
|
|
|
export function createState(ctx, now = Date.now()) {
|
|
const { start, market } = ctx.rules;
|
|
return {
|
|
v: STATE_VERSION,
|
|
units: start.units,
|
|
funds: start.funds,
|
|
material: start.material,
|
|
totalProduced: 0,
|
|
totalSold: 0,
|
|
price: start.price ?? market.basePrice,
|
|
marketing: 0,
|
|
materialFactor: 1,
|
|
generators: Object.fromEntries(ctx.generators.map((g) => [g.id, 0])),
|
|
upgrades: [],
|
|
savedAt: now,
|
|
materialTimer: 0, // transient, never saved
|
|
materialTrend: 0, // transient: -1 falling, 0 steady, 1 rising
|
|
};
|
|
}
|
|
|
|
function num(value, fallback, min = 0, max = MAX_NUMBER) {
|
|
return Number.isFinite(value) ? clamp(value, min, max) : fallback;
|
|
}
|
|
|
|
function int(value, max) {
|
|
return Math.floor(num(value, 0, 0, max));
|
|
}
|
|
|
|
/**
|
|
* Turn untrusted data (cookie, imported file) into a valid state.
|
|
* Unknown ids are dropped and numbers are clamped; returns null if `raw` is not an object.
|
|
*/
|
|
export function sanitizeState(raw, ctx, now = Date.now()) {
|
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
const { market, materialMarket } = ctx.rules;
|
|
const base = createState(ctx, now);
|
|
|
|
const owned = Array.isArray(raw.upgrades) ? raw.upgrades : [];
|
|
return {
|
|
...base,
|
|
units: num(raw.units, base.units),
|
|
funds: num(raw.funds, base.funds),
|
|
material: num(raw.material, base.material),
|
|
totalProduced: num(raw.totalProduced, 0),
|
|
totalSold: num(raw.totalSold, 0),
|
|
price: Math.round(num(raw.price, base.price, market.minPrice, market.maxPrice) * 100) / 100,
|
|
marketing: int(raw.marketing, MAX_MARKETING),
|
|
materialFactor: num(raw.materialFactor, 1, materialMarket.minFactor, materialMarket.maxFactor),
|
|
generators: Object.fromEntries(
|
|
ctx.generators.map((g) => [g.id, int(raw.generators?.[g.id], MAX_OWNED)]),
|
|
),
|
|
upgrades: [...new Set(owned)].filter((id) => ctx.upgradesById.has(id)),
|
|
savedAt: num(raw.savedAt, now, 0, now),
|
|
};
|
|
}
|
|
|
|
/** Compact JSON (numbers trimmed to 10 significant digits) for cookies and export. */
|
|
export function serializeState(state) {
|
|
const { materialTimer, materialTrend, ...persisted } = state;
|
|
return JSON.stringify(persisted, (_key, value) =>
|
|
typeof value === 'number' && Number.isFinite(value) ? Number(value.toPrecision(10)) : value,
|
|
);
|
|
}
|