M1: ядро правил дурака (чистый TypeScript, без движка)
Детерминированный конечный автомат: 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>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
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));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user