From 70599d59b157e1ca654e3e57ee827b8c0dbca694 Mon Sep 17 00:00:00 2001 From: Victor Bodinaud Date: Thu, 8 Oct 2026 12:50:06 +0200 Subject: [PATCH] fix(sim): autoplay avoids unaffordable trades --- src/sim/autoplay.ts | 16 +++++++++++----- tests/sim/autoplay.test.ts | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/sim/autoplay.ts b/src/sim/autoplay.ts index a0cc25b..6597fc5 100644 --- a/src/sim/autoplay.ts +++ b/src/sim/autoplay.ts @@ -3,8 +3,8 @@ import { tick } from './clock'; import { Customer, chooseShelfItem } from './customers'; import { DayReport, endDay } from './day'; import { demandWeight } from './demand'; -import { customerMinimumAsk, MONTHLY_RENT_CENTS, wholesaleCost } from './economy'; -import { itemKey, parseItemKey, stockSlot, totalQty } from './inventory'; +import { customerMinimumAsk, MONTHLY_RENT_CENTS, priceOf, wholesaleCost } from './economy'; +import { getSlot, itemKey, parseItemKey, stockSlot, totalQty } from './inventory'; import { placeOrder } from './orders'; import { CLOSE_MINUTES, GameState } from './state'; import { buyBack, recordLostRequest, recordSatisfiedRequest, sell, trade } from './transactions'; @@ -25,7 +25,7 @@ export function restock(state: GameState): void { } } -function serve(state: GameState, catalog: Catalog, customer: Customer): void { +export function serveCustomer(state: GameState, catalog: Catalog, customer: Customer): void { switch (customer.intent) { case 'buy': { const slotId = chooseShelfItem(state, catalog, customer); @@ -54,7 +54,13 @@ function serve(state: GameState, catalog: Catalog, customer: Customer): void { const slotId = chooseShelfItem(state, catalog, { ...customer, intent: 'buy' }); if (!slotId) return; const game = getGame(catalog, customer.offered.gameId); - trade(state, catalog, customer, slotId, customerMinimumAsk(game, customer.offered.condition, 'trade')); + const credit = customerMinimumAsk(game, customer.offered.condition, 'trade'); + const slotItem = getSlot(state.inventory, slotId).item; + if (slotItem && !customer.cardId) { + const diff = priceOf(state.prices, catalog, slotItem) - credit; + if (diff < 0 && -diff > state.cash) return; + } + trade(state, catalog, customer, slotId, credit); return; } } @@ -77,7 +83,7 @@ export function autoplayDay(state: GameState, catalog: Catalog): DayReport { restock(state); while (state.minutes < CLOSE_MINUTES) { for (const event of tick(state, catalog, 60)) { - if (event.type === 'customer_arrived') serve(state, catalog, event.customer); + if (event.type === 'customer_arrived') serveCustomer(state, catalog, event.customer); } } autoOrder(state, catalog); diff --git a/tests/sim/autoplay.test.ts b/tests/sim/autoplay.test.ts index c176d96..58ac968 100644 --- a/tests/sim/autoplay.test.ts +++ b/tests/sim/autoplay.test.ts @@ -1,9 +1,11 @@ import { describe, it, expect } from 'vitest'; -import { autoplayDay } from '../../src/sim/autoplay'; +import { autoplayDay, serveCustomer } from '../../src/sim/autoplay'; import { createNewGame } from '../../src/sim/state'; import { compareDates } from '../../src/sim/calendar'; import { loadDefaultCatalog } from '../../src/sim/catalog'; import { STARTING_CASH_CENTS } from '../../src/sim/economy'; +import { getSlot } from '../../src/sim/inventory'; +import { makeCustomer, stock, testCatalog } from './helpers'; describe('autoplay', () => { it('plays one day end to end', () => { @@ -32,4 +34,17 @@ describe('autoplay', () => { expect(s.reputation).toBeGreaterThan(0); } }); + + it('skips trades the shop cannot afford instead of throwing', () => { + const catalog = testCatalog(); + const s = createNewGame(1); + const tetrisNew = { gameId: 'tetris', condition: 'new' as const }; + stock(s, 'slot-0', tetrisNew, 1); + s.prices['tetris|new'] = 100; + s.cash = 0; + const customer = makeCustomer({ intent: 'trade', offered: { gameId: 'sml', condition: 'complete' } }); + expect(() => serveCustomer(s, catalog, customer)).not.toThrow(); + expect(getSlot(s.inventory, 'slot-0').qty).toBe(1); + expect(s.day.trades).toBe(0); + }); });