explodorama / files / test / scores.test.ts
scores.test.ts
0580e85e 2026-10-24 Pavel Safronov | 1 | import { describe, expect, it } from 'vitest' |
| 2 | import { newRecord } from '../src/sim/record' | |
| 3 | import { addEntry, BOARD_SIZE, compareEntries, countsForScores, scoreEntry, type ScoreEntry } from '../src/sim/scores' | |
| 4 | import { step } from '../src/sim/turn' | |
| 5 | import { board } from './helpers' | |
| 6 | ||
| 7 | const entry = (over: Partial<ScoreEntry>): ScoreEntry => ({ | |
| 8 | format: 1, simVersion: 1, date: '2026-10-24T10:00:00.000Z', seed: 1, options: { dev: false, immortal: false }, | |
| 9 | won: false, level: 1, coins: 0, kills: 0, turns: 10, livesLost: 3, death: null, powers: [], | |
| 10 | stats: { capacity: 1, fire: 1, fuse: 3 }, record: newRecord(1), ...over, | |
| 11 | }) | |
| 12 | ||
| 13 | describe('the high-score board', () => { | |
| 14 | it("ranks a win first, then the level, then coins, then fewer turns (user's choice)", () => { | |
| 15 | const order = [ | |
| 16 | entry({ won: true, level: 20, coins: 1 }), | |
| 17 | entry({ level: 9, coins: 0 }), | |
| 18 | entry({ level: 5, coins: 50 }), | |
| 19 | entry({ level: 5, coins: 20, turns: 100 }), | |
| 20 | entry({ level: 5, coins: 20, turns: 200 }), | |
| 21 | ] | |
| 22 | expect([...order].reverse().sort(compareEntries)).toEqual(order) | |
| 23 | }) | |
| 24 | ||
| 25 | it('keeps the best BOARD_SIZE, and says where a new one ranks', () => { | |
| 26 | let b: ScoreEntry[] = [] | |
| 27 | for (let i = 1; i <= BOARD_SIZE; i++) b = addEntry(b, entry({ level: i })).board | |
| 28 | const worse = addEntry(b, entry({ level: 0 })) | |
| 29 | expect(worse.rank).toBe(0) | |
| 30 | expect(worse.board).toHaveLength(BOARD_SIZE) | |
| 31 | const best = addEntry(b, entry({ level: 99 })) | |
| 32 | expect(best.rank).toBe(1) | |
| 33 | }) | |
| 34 | ||
| 35 | it('an entry has what the board shows: level, coins, kills, how it ended, powers, seed and the record', () => { | |
| 36 | const s = board(` | |
| 37 | ##### | |
| 38 | #P1.# | |
| 39 | #####`) | |
| 40 | s.lives = 1 | |
| 41 | s.coins = 7 | |
| 42 | s.runKills = 4 | |
| 43 | s.powers.run = true | |
| 44 | const over = step(s, { id: 'wait' }).state | |
| 45 | expect(over.status).toBe('gameOver') | |
| 46 | expect(countsForScores(over)).toBe(true) | |
| 47 | const e = scoreEntry(over, newRecord(over.seed), new Date('2026-10-24T10:00:00Z')) | |
| 48 | expect(e).toMatchObject({ won: false, level: 1, coins: 7, kills: 4, seed: over.seed, powers: ['run'] }) | |
| 49 | expect(e.death?.cause).toBe('blast') | |
| 50 | expect(e.record.seed).toBe(over.seed) | |
| 51 | }) | |
| 52 | ||
| 53 | it("an Immortal run doesn't count; a run still going doesn't either", () => { | |
| 54 | const s = board(` | |
| 55 | ### | |
| 56 | #P# | |
| 57 | ###`) | |
| 58 | expect(countsForScores(s)).toBe(false) | |
| 59 | expect(countsForScores({ ...s, status: 'gameOver', options: { dev: false, immortal: true } })).toBe(false) | |
| 60 | expect(countsForScores({ ...s, status: 'won', options: { dev: true, immortal: false } })).toBe(true) | |
| 61 | }) | |
| 62 | ||
| 63 | it('kills are counted over the run', () => { | |
| 64 | const s = board(` | |
| 65 | ###### | |
| 66 | #P.1B# | |
| 67 | ######`) | |
| 68 | expect(step(s, { id: 'wait' }).state.runKills).toBe(1) | |
| 69 | }) | |
| 70 | }) |