Детерминированный конечный автомат: apply(state, action) -> {state, events}.
Весь набор правил из GAMEPLAY.md проверяется headless-тестами.
- карты: Suit/Rank/Card (флайвейт, 36 штук), колода
- GameState + клонирование и сериализация в JSON
- действия (attack/defend/take/pass) и события для слоя отображения
- раздача, выбор первого атакующего, лимиты подкидывания,
взятие карт, добор, выбывание игроков, определение дурака
- 64 теста, strict typecheck
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
45 lines
2.1 KiB
TypeScript
45 lines
2.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { deserializeState, serializeState } from '@core/rules/GameState';
|
|
import { apply, createGame, currentActor, legalActions } from '@core/rules/DurakRules';
|
|
|
|
describe('state serialization', () => {
|
|
it('round-trips a fresh game through JSON', () => {
|
|
const { state } = createGame({ players: ['you', 'bot'], seed: 4242 });
|
|
const restored = deserializeState(JSON.parse(JSON.stringify(serializeState(state))));
|
|
expect(serializeState(restored)).toEqual(serializeState(state));
|
|
});
|
|
|
|
it('round-trips a game in the middle of a turn', () => {
|
|
let { state } = createGame({ players: ['you', 'bot'], seed: 99 });
|
|
for (let i = 0; i < 5; i++) {
|
|
const actor = currentActor(state);
|
|
if (!actor) break;
|
|
state = apply(state, legalActions(state, actor)[0]).state;
|
|
}
|
|
|
|
const restored = deserializeState(JSON.parse(JSON.stringify(serializeState(state))));
|
|
expect(serializeState(restored)).toEqual(serializeState(state));
|
|
expect(currentActor(restored)).toBe(currentActor(state));
|
|
});
|
|
|
|
it('restores cards as the same flyweight instances', () => {
|
|
const { state } = createGame({ players: ['you', 'bot'], seed: 7 });
|
|
const restored = deserializeState(serializeState(state));
|
|
expect(restored.players[0].hand[0]).toBe(state.players[0].hand[0]);
|
|
expect(restored.trumpCard).toBe(state.trumpCard);
|
|
});
|
|
|
|
it('replays identically from a restored state', () => {
|
|
const { state } = createGame({ players: ['you', 'bot'], seed: 2001 });
|
|
const restored = deserializeState(serializeState(state));
|
|
|
|
// The same action object works on both, because cards are flyweights and
|
|
// an action carries no reference to the state it was generated from.
|
|
const actor = currentActor(state) as string;
|
|
const action = legalActions(state, actor)[0];
|
|
|
|
expect(serializeState(apply(restored, action).state))
|
|
.toEqual(serializeState(apply(state, action).state));
|
|
});
|
|
});
|