feat(sim): economy rules
This commit is contained in:
1 parent
df24cf7516
commit
d97d60bf7e
2 files changed
+85
No files matched your search
@@ -0,0 +1,40 @@
|
||||
import { Catalog, Game, getGame } from './catalog';
|
||||
import { Condition, ItemRef, itemKey } from './inventory';
|
||||
import { francsToCents } from './money';
|
||||
|
||||
export const WHOLESALE_RATE = 0.65;
|
||||
export const USED_FACTOR: Record<Exclude<Condition, 'new'>, number> = {
|
||||
complete: 0.6,
|
||||
no_manual: 0.5,
|
||||
loose: 0.4,
|
||||
};
|
||||
export const CASH_BUYBACK_RATE = 0.35;
|
||||
export const TRADE_BUYBACK_RATE = 0.5;
|
||||
export const MONTHLY_RENT_CENTS = francsToCents(6000);
|
||||
export const STARTING_CASH_CENTS = francsToCents(30000);
|
||||
|
||||
export type BuyBackMode = 'cash' | 'trade';
|
||||
|
||||
export function wholesaleCost(game: Game): number {
|
||||
return Math.round(game.launchPriceCents * WHOLESALE_RATE);
|
||||
}
|
||||
|
||||
export function referencePrice(game: Game, condition: Condition): number {
|
||||
if (condition === 'new') return game.launchPriceCents;
|
||||
return Math.round(game.launchPriceCents * USED_FACTOR[condition]);
|
||||
}
|
||||
|
||||
/** 0.5 at the reference price, 1 at half price or less, 0 at 150% or more. */
|
||||
export function purchaseProbability(priceCents: number, referenceCents: number): number {
|
||||
const ratio = priceCents / referenceCents;
|
||||
return Math.min(1, Math.max(0, 1.5 - ratio));
|
||||
}
|
||||
|
||||
export function customerMinimumAsk(game: Game, condition: Condition, mode: BuyBackMode): number {
|
||||
const rate = mode === 'cash' ? CASH_BUYBACK_RATE : TRADE_BUYBACK_RATE;
|
||||
return Math.round(referencePrice(game, condition) * rate);
|
||||
}
|
||||
|
||||
export function priceOf(prices: Record<string, number>, catalog: Catalog, item: ItemRef): number {
|
||||
return prices[itemKey(item)] ?? referencePrice(getGame(catalog, item.gameId), item.condition);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
customerMinimumAsk, MONTHLY_RENT_CENTS, priceOf, purchaseProbability,
|
||||
referencePrice, wholesaleCost,
|
||||
} from '../../src/sim/economy';
|
||||
import { getGame } from '../../src/sim/catalog';
|
||||
import { testCatalog } from './helpers';
|
||||
|
||||
const catalog = testCatalog();
|
||||
const sml = getGame(catalog, 'sml'); // 3796 cents
|
||||
|
||||
describe('economy', () => {
|
||||
it('buys from the wholesaler at 65% of launch price', () => {
|
||||
expect(wholesaleCost(sml)).toBe(2467);
|
||||
});
|
||||
|
||||
it('derives used reference prices from the condition', () => {
|
||||
expect(referencePrice(sml, 'new')).toBe(3796);
|
||||
expect(referencePrice(sml, 'complete')).toBe(2278);
|
||||
expect(referencePrice(sml, 'no_manual')).toBe(1898);
|
||||
expect(referencePrice(sml, 'loose')).toBe(1518);
|
||||
});
|
||||
|
||||
it('maps price ratio to purchase probability', () => {
|
||||
expect(purchaseProbability(1000, 1000)).toBeCloseTo(0.5);
|
||||
expect(purchaseProbability(500, 1000)).toBe(1);
|
||||
expect(purchaseProbability(1500, 1000)).toBe(0);
|
||||
expect(purchaseProbability(3000, 1000)).toBe(0);
|
||||
});
|
||||
|
||||
it('computes what a customer accepts for a buy-back', () => {
|
||||
expect(customerMinimumAsk(sml, 'complete', 'cash')).toBe(797);
|
||||
expect(customerMinimumAsk(sml, 'complete', 'trade')).toBe(1139);
|
||||
});
|
||||
|
||||
it('uses the player price, else the reference price', () => {
|
||||
const item = { gameId: 'sml', condition: 'new' as const };
|
||||
expect(priceOf({}, catalog, item)).toBe(3796);
|
||||
expect(priceOf({ 'sml|new': 3500 }, catalog, item)).toBe(3500);
|
||||
});
|
||||
|
||||
it('charges 6000 F of rent per month', () => {
|
||||
expect(MONTHLY_RENT_CENTS).toBe(91469);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user