Something went wrong. Try again.
A Social Deduction / Deception Game
Something went wrong. Try again.
5.5 kB · 130 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131import { describe, expect, it } from 'vitest'import { reduce } from './reducer.js'import { openSlotIndex, validateStationActivity } from './validate.js'import { timerFor } from './config.js'import { DIDS, doActivity, openVote, setup } from './testutil.js'
const [SAB, A, B, C] = DIDS
describe('validateStationActivity', () => { it('accepts a legal activity when the player stands in the station area, and picks a slot', () => { const s = setup({ startRound: true }) // everyone spawns at the origin, inside res-wood's area const r = validateStationActivity(s, A, 'res-wood', 'collect', 'wood') expect(r).toEqual({ ok: true, slot: 0 }) })
it('rejects when the player is not inside the station area', () => { const s = setup({ startRound: true }) // origin is NOT inside res-stone's area (x≈10) const r = validateStationActivity(s, A, 'res-stone', 'collect') expect(r).toEqual({ ok: false, reason: 'not-in-area' }) })
it('rejects an activity the player is not allowed to choose', () => { const s = setup({ startRound: true }) expect(validateStationActivity(s, A, 'res-wood', 'sabotage')).toEqual({ ok: false, reason: 'illegal-option', }) })
it('rejects when every slot is occupied or cooling down', () => { let s = setup({ startRound: true }) // occupy all three slots of res-wood with A, B, C for (const [i, did] of [A, B, C].entries()) { s = reduce(s, { type: 'actionStarted', serverTime: 1, did, stationId: 'res-wood', slot: i, kind: 'collect', endsAt: 1 + timerFor('collect', s.config), }) } expect(openSlotIndex(s, 'res-wood')).toBe(-1) expect(validateStationActivity(s, SAB, 'res-wood', 'collect', 'wood')).toEqual({ ok: false, reason: 'no-open-slot', }) })
it('rejects outside of a round and while already busy', () => { const lobby = setup() // not started expect(validateStationActivity(lobby, A, 'res-wood', 'collect')).toEqual({ ok: false, reason: 'not-in-round', })
let s = setup({ startRound: true }) s = reduce(s, { type: 'actionStarted', serverTime: 1, did: A, stationId: 'res-wood', slot: 0, kind: 'collect', endsAt: 1 + timerFor('collect', s.config), }) expect(validateStationActivity(s, A, 'res-wood', 'collect')).toEqual({ ok: false, reason: 'already-busy', }) })})
describe('validateStationActivity: carry / discard / deposit gating', () => { it('validates "discard and X": legal with the discard flag, illegal without, illegal empty-handed', () => { let s = setup({ startRound: true }) s = doActivity(s, A, 'res-wood', 0, 'collect', 100) // A carries real wood, still inside res-wood // discard-and-collect: legal (the drop comes first), and it picks a fresh slot (slot 0 is cooling) expect(validateStationActivity(s, A, 'res-wood', 'collect', 'wood', true)).toEqual({ ok: true, slot: 1 }) // the same collect WITHOUT the discard flag is illegal while carrying expect(validateStationActivity(s, A, 'res-wood', 'collect', 'wood', false)).toEqual({ ok: false, reason: 'illegal-option', }) // discard requested but nothing in hand → illegal const empty = setup({ startRound: true }) expect(validateStationActivity(empty, A, 'res-wood', 'collect', 'wood', true)).toEqual({ ok: false, reason: 'illegal-option', }) })
it('validates fakeDeposit at a deposit point and rejects an unaccepted resource', () => { let s = setup({ startRound: true }) s = doActivity(s, SAB, 'res-wood', 0, 'pretendCollect', 100) // fake wood s = reduce(s, { type: 'playerMoved', serverTime: 200, did: SAB, pos: { x: 0, y: 50 }, facing: 'south' }) expect(validateStationActivity(s, SAB, 'deposit', 'fakeDeposit', 'wood')).toEqual({ ok: true, slot: 0 }) expect(validateStationActivity(s, SAB, 'deposit', 'fakeDeposit', 'gold')).toEqual({ ok: false, reason: 'bad-resource', }) })
it('gates bare-handed break at a pure deposit point, but allows the fake-carry cost-1 break', () => { // empty-handed saboteur at the deposit point: break is illegal (deposit-point gating) let bare = setup({ startRound: true }) bare = reduce(bare, { type: 'playerMoved', serverTime: 50, did: SAB, pos: { x: 0, y: 50 }, facing: 'south' }) expect(validateStationActivity(bare, SAB, 'deposit', 'breakStation')).toEqual({ ok: false, reason: 'illegal-option', }) // fake-carry cover unlocks the cost-1 break let cover = setup({ startRound: true }) cover = doActivity(cover, SAB, 'res-wood', 0, 'pretendCollect', 100) cover = reduce(cover, { type: 'playerMoved', serverTime: 200, did: SAB, pos: { x: 0, y: 50 }, facing: 'south' }) expect(validateStationActivity(cover, SAB, 'deposit', 'breakStation')).toEqual({ ok: true, slot: 0 }) })
it('validates callVote at a voting station and rejects it while a vote already runs', () => { let s = setup({ startRound: true }) s = reduce(s, { type: 'playerMoved', serverTime: 50, did: A, pos: { x: 100, y: 50 }, facing: 'south' }) expect(validateStationActivity(s, A, 'meeting', 'callVote')).toEqual({ ok: true, slot: 0 }) s = openVote(s, B, 60) // B opens a vote expect(validateStationActivity(s, A, 'meeting', 'callVote')).toEqual({ ok: false, reason: 'vote-in-progress', }) })})