Детерминированный конечный автомат: 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>
55 lines
1.2 KiB
TypeScript
55 lines
1.2 KiB
TypeScript
/**
|
|
* Card ranks present in a 36-card deck. The numeric values are the comparison
|
|
* order, so `a > b` is a valid "beats within suit" test.
|
|
*/
|
|
export enum Rank {
|
|
Six = 6,
|
|
Seven = 7,
|
|
Eight = 8,
|
|
Nine = 9,
|
|
Ten = 10,
|
|
Jack = 11,
|
|
Queen = 12,
|
|
King = 13,
|
|
Ace = 14,
|
|
}
|
|
|
|
export const ALL_RANKS: readonly Rank[] = [
|
|
Rank.Six, Rank.Seven, Rank.Eight, Rank.Nine, Rank.Ten,
|
|
Rank.Jack, Rank.Queen, Rank.King, Rank.Ace,
|
|
];
|
|
|
|
/** Short label, as printed on the card corner. */
|
|
const RANK_LABELS: Record<Rank, string> = {
|
|
[Rank.Six]: '6',
|
|
[Rank.Seven]: '7',
|
|
[Rank.Eight]: '8',
|
|
[Rank.Nine]: '9',
|
|
[Rank.Ten]: '10',
|
|
[Rank.Jack]: 'J',
|
|
[Rank.Queen]: 'Q',
|
|
[Rank.King]: 'K',
|
|
[Rank.Ace]: 'A',
|
|
};
|
|
|
|
/** Rank names as used by the card art file names. */
|
|
const RANK_NAMES: Record<Rank, string> = {
|
|
[Rank.Six]: '6',
|
|
[Rank.Seven]: '7',
|
|
[Rank.Eight]: '8',
|
|
[Rank.Nine]: '9',
|
|
[Rank.Ten]: '10',
|
|
[Rank.Jack]: 'jack',
|
|
[Rank.Queen]: 'queen',
|
|
[Rank.King]: 'king',
|
|
[Rank.Ace]: 'ace',
|
|
};
|
|
|
|
export function rankLabel(rank: Rank): string {
|
|
return RANK_LABELS[rank];
|
|
}
|
|
|
|
export function rankName(rank: Rank): string {
|
|
return RANK_NAMES[rank];
|
|
}
|