explodorama / files / test / shop.test.ts
shop.test.ts
| 1 | import { describe, expect, it } from 'vitest' |
| 2 | import { LAST_LEVEL } from '../src/sim/levelgen' |
| 3 | import { drawShop, PRICES, rerollPrice, SHOP_EVERY, SHOP_SIZE, shopAfter } from '../src/sim/shop' |
| 4 | import { newRun } from '../src/sim/state' |
| 5 | import { step } from '../src/sim/turn' |
| 6 | import type { GameState } from '../src/sim/types' |
| 7 | |
| 8 | /** A run on `level`, just cleared, with `coins`. */ |
| 9 | function cleared(level: number, coins = 100): GameState { |
| 10 | const s = newRun(5, level) |
| 11 | s.status = 'cleared' |
| 12 | s.coins = coins |
| 13 | return s |
| 14 | } |
| 15 | |
| 16 | describe('the shop', () => { |
| 17 | it(`opens after every ${SHOP_EVERY}rd cleared level, not after the last`, () => { |
| 18 | expect([1, 2, 3, 4, 5, 6].filter(shopAfter)).toEqual([3, 6]) |
| 19 | expect(shopAfter(LAST_LEVEL)).toBe(false) |
| 20 | const { state, events } = step(cleared(SHOP_EVERY), { id: 'continue' }) |
| 21 | expect(events).toContainEqual({ type: 'shopOpened' }) |
| 22 | expect(state.level).toBe(SHOP_EVERY) |
| 23 | expect(state.shop!.items).toHaveLength(SHOP_SIZE) |
| 24 | // Going on from it starts the next level, the shop gone. |
| 25 | const next = step(state, { id: 'continue' }).state |
| 26 | expect(next.level).toBe(SHOP_EVERY + 1) |
| 27 | expect(next.shop).toBeNull() |
| 28 | }) |
| 29 | |
| 30 | it('goes straight on after the other levels', () => { |
| 31 | const { state } = step(cleared(1), { id: 'continue' }) |
| 32 | expect(state.level).toBe(2) |
| 33 | expect(state.shop).toBeNull() |
| 34 | }) |
| 35 | |
| 36 | it('sells power-ups not held (no Coin bag) and charges, the same for the same seed', () => { |
| 37 | const s = cleared(3) |
| 38 | s.powers.run = true |
| 39 | for (let r = 0; r < 30; r++) { |
| 40 | const items = drawShop(s, r) |
| 41 | expect(new Set(items).size).toBe(SHOP_SIZE) |
| 42 | expect(items).not.toContain('run') |
| 43 | expect(items).not.toContain('coinBag') |
| 44 | expect(drawShop(s, r)).toEqual(items) |
| 45 | } |
| 46 | }) |
| 47 | |
| 48 | it('buying takes the coins, works at once, carries on to the next level, and sells once', () => { |
| 49 | let s = step(cleared(3, 40), { id: 'continue' }).state |
| 50 | s.shop!.items = ['run', 'shield', 'heart', 'bombPlus'] |
| 51 | s = step(s, { id: 'buy', kind: 'run' }).state |
| 52 | expect(s.coins).toBe(40 - PRICES.run) |
| 53 | expect(s.powers.run).toBe(true) |
| 54 | s = step(s, { id: 'buy', kind: 'shield' }).state |
| 55 | expect(s.charge).toEqual({ kind: 'shield', left: 1 }) |
| 56 | expect(() => step(s, { id: 'buy', kind: 'run' })).toThrow() |
| 57 | const next = step(s, { id: 'continue' }).state |
| 58 | expect(next.powers.run).toBe(true) |
| 59 | expect(next.coins).toBe(40 - PRICES.run - PRICES.shield) |
| 60 | }) |
| 61 | |
| 62 | it('a charge bought while carrying another kind asks; the shop waits for the answer', () => { |
| 63 | let s = step(cleared(3, 40), { id: 'continue' }).state |
| 64 | s.charge = { kind: 'pierce', left: 3 } |
| 65 | s.shop!.items = ['nuke', 'run', 'heart', 'bombPlus'] |
| 66 | s = step(s, { id: 'buy', kind: 'nuke' }).state |
| 67 | expect(s.chargeOffer?.kind).toBe('nuke') |
| 68 | expect(() => step(s, { id: 'continue' })).toThrow() |
| 69 | s = step(s, { id: 'swapCharge' }).state |
| 70 | expect(s.charge).toEqual({ kind: 'nuke', left: 3 }) |
| 71 | expect(step(s, { id: 'continue' }).state.level).toBe(4) |
| 72 | }) |
| 73 | |
| 74 | it('refuses what the player can’t afford', () => { |
| 75 | const s = step(cleared(3, 3), { id: 'continue' }).state |
| 76 | expect(() => step(s, { id: 'buy', kind: s.shop!.items[0]! })).toThrow() |
| 77 | }) |
| 78 | |
| 79 | it('restocks for 1 coin, then 2, 3…, with a new draw', () => { |
| 80 | let s = step(cleared(3, 20), { id: 'continue' }).state |
| 81 | expect(rerollPrice(s.shop!)).toBe(1) |
| 82 | s = step(s, { id: 'reroll' }).state |
| 83 | expect(s.coins).toBe(19) |
| 84 | expect(rerollPrice(s.shop!)).toBe(2) |
| 85 | s = step(s, { id: 'reroll' }).state |
| 86 | expect(s.coins).toBe(17) |
| 87 | expect(s.shop!.items).toEqual(drawShop(s, 2)) |
| 88 | }) |
| 89 | |
| 90 | it('takes no actions on the board while open, and none outside a shop', () => { |
| 91 | const s = step(cleared(3), { id: 'continue' }).state |
| 92 | expect(() => step(s, { id: 'wait' })).toThrow() |
| 93 | expect(() => step(newRun(5), { id: 'buy', kind: 'heart' })).toThrow() |
| 94 | }) |
| 95 | }) |