refactor: Part[] -> Record<string, Part>
This commit is contained in:
parent
118007168a
commit
65a3d682b6
|
|
@ -87,5 +87,6 @@ tests/ # Mirrors src/ structure with *.test.ts files
|
||||||
- **Prompt system**: Commands prompt for input via `PromptEvent` with `resolve`/`reject`; `tryCommit` accepts `Command | string` and validates against schema before custom validator
|
- **Prompt system**: Commands prompt for input via `PromptEvent` with `resolve`/`reject`; `tryCommit` accepts `Command | string` and validates against schema before custom validator
|
||||||
- **Barrel exports**: `src/index.ts` is the single public API surface
|
- **Barrel exports**: `src/index.ts` is the single public API surface
|
||||||
- **Game modules**: export `registry` and `createInitialState` for use with `createGameContextFromModule`
|
- **Game modules**: export `registry` and `createInitialState` for use with `createGameContextFromModule`
|
||||||
- **Region system**: plain `Region` type with `createRegion()` factory; `parts` are a separate record keyed by ID
|
- **Region system**: plain `Region` type with `createRegion()` factory; `parts` are stored as `Record<string, Part>` keyed by ID, with `partMap` in regions mapping position keys to part IDs
|
||||||
|
- **Part collections**: Game state uses `Record<string, Part<TMeta>>` (not arrays) for O(1) lookup by ID. Use `Object.values(parts)` when iteration is needed, `Object.keys(parts)` for count/IDs
|
||||||
- **Mutative**: used for immutable state updates inside `MutableSignal.produce()`
|
- **Mutative**: used for immutable state updates inside `MutableSignal.produce()`
|
||||||
|
|
|
||||||
|
|
@ -196,12 +196,12 @@ rng.setSeed(999); // reseed
|
||||||
|---|---|
|
|---|---|
|
||||||
| `Part<TMeta>` | Type representing a game piece with sides, position, and region. `TMeta` for game-specific fields |
|
| `Part<TMeta>` | Type representing a game piece with sides, position, and region. `TMeta` for game-specific fields |
|
||||||
| `PartTemplate<TMeta>` | Template type for creating parts (excludes `id`, requires metadata) |
|
| `PartTemplate<TMeta>` | Template type for creating parts (excludes `id`, requires metadata) |
|
||||||
| `PartPool<TMeta>` | Pool of parts with `draw()`, `return()`, and `remaining()` methods |
|
| `PartPool<TMeta>` | Pool of parts with `draw()`, `return()`, and `remaining()` methods. `parts` field is `Record<string, Part>` |
|
||||||
| `createPart(template, id)` | Create a single part from a template |
|
| `createPart(template, id)` | Create a single part from a template |
|
||||||
| `createParts(template, count, idPrefix)` | Create multiple identical parts with auto-generated IDs |
|
| `createParts(template, count, idPrefix)` | Create multiple identical parts with auto-generated IDs |
|
||||||
| `createPartPool(template, count, idPrefix)` | Create a pool of parts for lazy loading |
|
| `createPartPool(template, count, idPrefix)` | Create a pool of parts for lazy loading |
|
||||||
| `mergePartPools(...pools)` | Merge multiple part pools into one |
|
| `mergePartPools(...pools)` | Merge multiple part pools into one |
|
||||||
| `findPartById(parts, id)` | Find a part by ID in an array |
|
| `findPartById(parts, id)` | Find a part by ID in a Record |
|
||||||
| `isCellOccupied(parts, regionId, position)` | Check if a cell is occupied |
|
| `isCellOccupied(parts, regionId, position)` | Check if a cell is occupied |
|
||||||
| `getPartAtPosition(parts, regionId, position)` | Get the part at a specific position |
|
| `getPartAtPosition(parts, regionId, position)` | Get the part at a specific position |
|
||||||
| `flip(part)` | Cycle to the next side |
|
| `flip(part)` | Cycle to the next side |
|
||||||
|
|
@ -218,7 +218,7 @@ rng.setSeed(999); // reseed
|
||||||
| `applyAlign(region, parts)` | Compact parts according to axis alignment |
|
| `applyAlign(region, parts)` | Compact parts according to axis alignment |
|
||||||
| `shuffle(region, parts, rng)` | Randomize part positions |
|
| `shuffle(region, parts, rng)` | Randomize part positions |
|
||||||
| `moveToRegion(part, sourceRegion?, targetRegion, position?)` | Move a part to another region. `sourceRegion` is optional for first placement |
|
| `moveToRegion(part, sourceRegion?, targetRegion, position?)` | Move a part to another region. `sourceRegion` is optional for first placement |
|
||||||
| `moveToRegionAll(parts, sourceRegion?, targetRegion, positions?)` | Move multiple parts to another region. `sourceRegion` is optional for first placement |
|
| `moveToRegionAll(parts, sourceRegion?, targetRegion, positions?)` | Move multiple parts to another region. `parts` is `Record<string, Part>`. `sourceRegion` is optional for first placement |
|
||||||
| `removeFromRegion(part, region)` | Remove a part from its region |
|
| `removeFromRegion(part, region)` | Remove a part from its region |
|
||||||
|
|
||||||
### Commands
|
### Commands
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import {Part} from "./part";
|
||||||
export type PartTemplate<TMeta = {}> = Omit<Partial<Part<TMeta>>, 'id'> & TMeta;
|
export type PartTemplate<TMeta = {}> = Omit<Partial<Part<TMeta>>, 'id'> & TMeta;
|
||||||
|
|
||||||
export type PartPool<TMeta = {}> = {
|
export type PartPool<TMeta = {}> = {
|
||||||
parts: Part<TMeta>[];
|
parts: Record<string, Part<TMeta>>;
|
||||||
template: PartTemplate<TMeta>;
|
template: PartTemplate<TMeta>;
|
||||||
draw(): Part<TMeta> | undefined;
|
draw(): Part<TMeta> | undefined;
|
||||||
return(part: Part<TMeta>): void;
|
return(part: Part<TMeta>): void;
|
||||||
|
|
@ -39,8 +39,12 @@ export function createPartPool<TMeta = {}>(
|
||||||
count: number,
|
count: number,
|
||||||
idPrefix: string
|
idPrefix: string
|
||||||
): PartPool<TMeta> {
|
): PartPool<TMeta> {
|
||||||
const parts = createParts(template, count, idPrefix);
|
const partsArray = createParts(template, count, idPrefix);
|
||||||
const available = [...parts];
|
const parts: Record<string, Part<TMeta>> = {};
|
||||||
|
for (const part of partsArray) {
|
||||||
|
parts[part.id] = part;
|
||||||
|
}
|
||||||
|
const available = [...partsArray];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
parts,
|
parts,
|
||||||
|
|
@ -66,9 +70,13 @@ export function mergePartPools<TMeta = {}>(
|
||||||
return createPartPool({} as PartTemplate<TMeta>, 0, 'merged');
|
return createPartPool({} as PartTemplate<TMeta>, 0, 'merged');
|
||||||
}
|
}
|
||||||
|
|
||||||
const allParts = pools.flatMap(p => p.parts);
|
const allPartsArray = pools.flatMap(p => Object.values(p.parts));
|
||||||
|
const allParts: Record<string, Part<TMeta>> = {};
|
||||||
|
for (const part of allPartsArray) {
|
||||||
|
allParts[part.id] = part;
|
||||||
|
}
|
||||||
const template = pools[0].template;
|
const template = pools[0].template;
|
||||||
const available = allParts.filter(p => p.regionId === '');
|
const available = allPartsArray.filter(p => p.regionId === '');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
parts: allParts,
|
parts: allParts,
|
||||||
|
|
|
||||||
|
|
@ -27,16 +27,16 @@ export function roll<TMeta>(part: Part<TMeta>, rng: RNG) {
|
||||||
part.side = rng.nextInt(part.sides);
|
part.side = rng.nextInt(part.sides);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findPartById<TMeta>(parts: Part<TMeta>[], id: string): Part<TMeta> | undefined {
|
export function findPartById<TMeta>(parts: Record<string, Part<TMeta>>, id: string): Part<TMeta> | undefined {
|
||||||
return parts.find(p => p.id === id);
|
return parts[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCellOccupied<TMeta>(parts: Part<TMeta>[], regionId: string, position: number[]): boolean {
|
export function isCellOccupied<TMeta>(parts: Record<string, Part<TMeta>>, regionId: string, position: number[]): boolean {
|
||||||
const posKey = position.join(',');
|
const posKey = position.join(',');
|
||||||
return parts.some(p => p.regionId === regionId && p.position.join(',') === posKey);
|
return Object.values(parts).some(p => p.regionId === regionId && p.position.join(',') === posKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPartAtPosition<TMeta>(parts: Part<TMeta>[], regionId: string, position: number[]): Part<TMeta> | undefined {
|
export function getPartAtPosition<TMeta>(parts: Record<string, Part<TMeta>>, regionId: string, position: number[]): Part<TMeta> | undefined {
|
||||||
const posKey = position.join(',');
|
const posKey = position.join(',');
|
||||||
return parts.find(p => p.regionId === regionId && p.position.join(',') === posKey);
|
return Object.values(parts).find(p => p.regionId === regionId && p.position.join(',') === posKey);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,9 +132,10 @@ export function moveToRegion<TMeta>(part: Part<TMeta>, sourceRegion: Region | nu
|
||||||
part.regionId = targetRegion.id;
|
part.regionId = targetRegion.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function moveToRegionAll<TMeta>(parts: Part<TMeta>[], sourceRegion: Region | null, targetRegion: Region, positions?: number[][]) {
|
export function moveToRegionAll<TMeta>(parts: Record<string, Part<TMeta>>, sourceRegion: Region | null, targetRegion: Region, positions?: number[][]) {
|
||||||
for (let i = 0; i < parts.length; i++) {
|
const partIds = Object.keys(parts);
|
||||||
const part = parts[i];
|
for (let i = 0; i < partIds.length; i++) {
|
||||||
|
const part = parts[partIds[i]];
|
||||||
if (sourceRegion && part.regionId === sourceRegion.id) {
|
if (sourceRegion && part.regionId === sourceRegion.id) {
|
||||||
sourceRegion.childIds = sourceRegion.childIds.filter(id => id !== part.id);
|
sourceRegion.childIds = sourceRegion.childIds.filter(id => id !== part.id);
|
||||||
delete sourceRegion.partMap[part.position.join(',')];
|
delete sourceRegion.partMap[part.position.join(',')];
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export function createInitialState() {
|
||||||
{ name: 'x', min: 0, max: BOARD_SIZE - 1 },
|
{ name: 'x', min: 0, max: BOARD_SIZE - 1 },
|
||||||
{ name: 'y', min: 0, max: BOARD_SIZE - 1 },
|
{ name: 'y', min: 0, max: BOARD_SIZE - 1 },
|
||||||
]),
|
]),
|
||||||
pieces: [] as BoopPart[],
|
pieces: {} as Record<string, BoopPart>,
|
||||||
currentPlayer: 'white' as PlayerType,
|
currentPlayer: 'white' as PlayerType,
|
||||||
winner: null as WinnerType,
|
winner: null as WinnerType,
|
||||||
players: {
|
players: {
|
||||||
|
|
@ -119,7 +119,8 @@ registration.add('turn <player>', async function(cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (countPiecesOnBoard(this.context, turnPlayer) >= MAX_PIECES_PER_PLAYER) {
|
if (countPiecesOnBoard(this.context, turnPlayer) >= MAX_PIECES_PER_PLAYER) {
|
||||||
const availableKittens = this.context.value.pieces.filter(
|
const pieces = this.context.value.pieces;
|
||||||
|
const availableKittens = Object.values(pieces).filter(
|
||||||
p => p.player === turnPlayer && p.pieceType === 'kitten'
|
p => p.player === turnPlayer && p.pieceType === 'kitten'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -174,7 +175,7 @@ export function placePiece(host: MutableSignal<BoopState>, row: number, col: num
|
||||||
`${player}-${pieceType}-${count}`
|
`${player}-${pieceType}-${count}`
|
||||||
);
|
);
|
||||||
host.produce(s => {
|
host.produce(s => {
|
||||||
s.pieces.push(piece);
|
s.pieces[piece.id] = piece;
|
||||||
board.childIds.push(piece.id);
|
board.childIds.push(piece.id);
|
||||||
board.partMap[`${row},${col}`] = piece.id;
|
board.partMap[`${row},${col}`] = piece.id;
|
||||||
});
|
});
|
||||||
|
|
@ -184,10 +185,11 @@ export function placePiece(host: MutableSignal<BoopState>, row: number, col: num
|
||||||
export function applyBoops(host: MutableSignal<BoopState>, placedRow: number, placedCol: number, placedType: PieceType) {
|
export function applyBoops(host: MutableSignal<BoopState>, placedRow: number, placedCol: number, placedType: PieceType) {
|
||||||
const board = getBoardRegion(host);
|
const board = getBoardRegion(host);
|
||||||
const pieces = host.value.pieces;
|
const pieces = host.value.pieces;
|
||||||
|
const piecesArray = Object.values(pieces);
|
||||||
|
|
||||||
const piecesToBoop: { part: BoopPart; dr: number; dc: number }[] = [];
|
const piecesToBoop: { part: BoopPart; dr: number; dc: number }[] = [];
|
||||||
|
|
||||||
for (const part of pieces) {
|
for (const part of piecesArray) {
|
||||||
const [r, c] = part.position;
|
const [r, c] = part.position;
|
||||||
if (r === placedRow && c === placedCol) continue;
|
if (r === placedRow && c === placedCol) continue;
|
||||||
|
|
||||||
|
|
@ -223,7 +225,7 @@ export function applyBoops(host: MutableSignal<BoopState>, placedRow: number, pl
|
||||||
part.position = [newRow, newCol];
|
part.position = [newRow, newCol];
|
||||||
board.partMap = Object.fromEntries(
|
board.partMap = Object.fromEntries(
|
||||||
board.childIds.map(id => {
|
board.childIds.map(id => {
|
||||||
const p = pieces.find(x => x.id === id)!;
|
const p = pieces[id];
|
||||||
return [p.position.join(','), id];
|
return [p.position.join(','), id];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -235,7 +237,7 @@ export function removePieceFromBoard(host: MutableSignal<BoopState>, part: BoopP
|
||||||
const playerData = getPlayer(host, part.player);
|
const playerData = getPlayer(host, part.player);
|
||||||
board.childIds = board.childIds.filter(id => id !== part.id);
|
board.childIds = board.childIds.filter(id => id !== part.id);
|
||||||
delete board.partMap[part.position.join(',')];
|
delete board.partMap[part.position.join(',')];
|
||||||
host.value.pieces = host.value.pieces.filter(p => p.id !== part.id);
|
delete host.value.pieces[part.id];
|
||||||
playerData[part.pieceType].placed--;
|
playerData[part.pieceType].placed--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,9 +294,10 @@ export function hasWinningLine(positions: number[][]): boolean {
|
||||||
|
|
||||||
export function checkGraduation(host: MutableSignal<BoopState>, player: PlayerType): number[][][] {
|
export function checkGraduation(host: MutableSignal<BoopState>, player: PlayerType): number[][][] {
|
||||||
const pieces = host.value.pieces;
|
const pieces = host.value.pieces;
|
||||||
|
const piecesArray = Object.values(pieces);
|
||||||
const posSet = new Set<string>();
|
const posSet = new Set<string>();
|
||||||
|
|
||||||
for (const part of pieces) {
|
for (const part of piecesArray) {
|
||||||
if (part.player === player && part.pieceType === 'kitten') {
|
if (part.player === player && part.pieceType === 'kitten') {
|
||||||
posSet.add(`${part.position[0]},${part.position[1]}`);
|
posSet.add(`${part.position[0]},${part.position[1]}`);
|
||||||
}
|
}
|
||||||
|
|
@ -318,7 +321,8 @@ export function processGraduation(host: MutableSignal<BoopState>, player: Player
|
||||||
}
|
}
|
||||||
|
|
||||||
const board = getBoardRegion(host);
|
const board = getBoardRegion(host);
|
||||||
const partsToRemove = host.value.pieces.filter(
|
const pieces = host.value.pieces;
|
||||||
|
const partsToRemove = Object.values(pieces).filter(
|
||||||
p => p.player === player && p.pieceType === 'kitten' && allPositions.has(`${p.position[0]},${p.position[1]}`)
|
p => p.player === player && p.pieceType === 'kitten' && allPositions.has(`${p.position[0]},${p.position[1]}`)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -333,14 +337,15 @@ export function processGraduation(host: MutableSignal<BoopState>, player: Player
|
||||||
|
|
||||||
export function countPiecesOnBoard(host: MutableSignal<BoopState>, player: PlayerType): number {
|
export function countPiecesOnBoard(host: MutableSignal<BoopState>, player: PlayerType): number {
|
||||||
const pieces = host.value.pieces;
|
const pieces = host.value.pieces;
|
||||||
return pieces.filter(p => p.player === player).length;
|
return Object.values(pieces).filter(p => p.player === player).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkWinner(host: MutableSignal<BoopState>): WinnerType {
|
export function checkWinner(host: MutableSignal<BoopState>): WinnerType {
|
||||||
const pieces = host.value.pieces;
|
const pieces = host.value.pieces;
|
||||||
|
const piecesArray = Object.values(pieces);
|
||||||
|
|
||||||
for (const player of ['white', 'black'] as PlayerType[]) {
|
for (const player of ['white', 'black'] as PlayerType[]) {
|
||||||
const positions = pieces
|
const positions = piecesArray
|
||||||
.filter(p => p.player === player && p.pieceType === 'cat')
|
.filter(p => p.player === player && p.pieceType === 'cat')
|
||||||
.map(p => p.position);
|
.map(p => p.position);
|
||||||
if (hasWinningLine(positions)) return player;
|
if (hasWinningLine(positions)) return player;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export function createInitialState() {
|
||||||
{ name: 'x', min: 0, max: BOARD_SIZE - 1 },
|
{ name: 'x', min: 0, max: BOARD_SIZE - 1 },
|
||||||
{ name: 'y', min: 0, max: BOARD_SIZE - 1 },
|
{ name: 'y', min: 0, max: BOARD_SIZE - 1 },
|
||||||
]),
|
]),
|
||||||
parts: [] as TicTacToePart[],
|
parts: {} as Record<string, TicTacToePart>,
|
||||||
currentPlayer: 'X' as PlayerType,
|
currentPlayer: 'X' as PlayerType,
|
||||||
winner: null as WinnerType,
|
winner: null as WinnerType,
|
||||||
turn: 0,
|
turn: 0,
|
||||||
|
|
@ -104,26 +104,27 @@ export function hasWinningLine(positions: number[][]): boolean {
|
||||||
|
|
||||||
export function checkWinner(host: MutableSignal<TicTacToeState>): WinnerType {
|
export function checkWinner(host: MutableSignal<TicTacToeState>): WinnerType {
|
||||||
const parts = host.value.parts;
|
const parts = host.value.parts;
|
||||||
|
const partsArray = Object.values(parts);
|
||||||
|
|
||||||
const xPositions = parts.filter((p: TicTacToePart) => p.player === 'X').map((p: TicTacToePart) => p.position);
|
const xPositions = partsArray.filter((p: TicTacToePart) => p.player === 'X').map((p: TicTacToePart) => p.position);
|
||||||
const oPositions = parts.filter((p: TicTacToePart) => p.player === 'O').map((p: TicTacToePart) => p.position);
|
const oPositions = partsArray.filter((p: TicTacToePart) => p.player === 'O').map((p: TicTacToePart) => p.position);
|
||||||
|
|
||||||
if (hasWinningLine(xPositions)) return 'X';
|
if (hasWinningLine(xPositions)) return 'X';
|
||||||
if (hasWinningLine(oPositions)) return 'O';
|
if (hasWinningLine(oPositions)) return 'O';
|
||||||
if (parts.length >= MAX_TURNS) return 'draw';
|
if (partsArray.length >= MAX_TURNS) return 'draw';
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function placePiece(host: MutableSignal<TicTacToeState>, row: number, col: number, player: PlayerType) {
|
export function placePiece(host: MutableSignal<TicTacToeState>, row: number, col: number, player: PlayerType) {
|
||||||
const board = host.value.board;
|
const board = host.value.board;
|
||||||
const moveNumber = host.value.parts.length + 1;
|
const moveNumber = Object.keys(host.value.parts).length + 1;
|
||||||
const piece = createPart<{ player: PlayerType }>(
|
const piece = createPart<{ player: PlayerType }>(
|
||||||
{ regionId: 'board', position: [row, col], player },
|
{ regionId: 'board', position: [row, col], player },
|
||||||
`piece-${player}-${moveNumber}`
|
`piece-${player}-${moveNumber}`
|
||||||
);
|
);
|
||||||
host.produce(state => {
|
host.produce(state => {
|
||||||
state.parts.push(piece);
|
state.parts[piece.id] = piece;
|
||||||
board.childIds.push(piece.id);
|
board.childIds.push(piece.id);
|
||||||
board.partMap[`${row},${col}`] = piece.id;
|
board.partMap[`${row},${col}`] = piece.id;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ function waitForPrompt(ctx: ReturnType<typeof createTestContext>['ctx']): Promis
|
||||||
}
|
}
|
||||||
|
|
||||||
function getParts(state: MutableSignal<BoopState>) {
|
function getParts(state: MutableSignal<BoopState>) {
|
||||||
return state.value.pieces;
|
return Object.values(state.value.pieces);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Boop - helper functions', () => {
|
describe('Boop - helper functions', () => {
|
||||||
|
|
|
||||||
|
|
@ -163,9 +163,9 @@ describe('TicTacToe - helper functions', () => {
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
placePiece(state, 1, 1, 'X');
|
placePiece(state, 1, 1, 'X');
|
||||||
|
|
||||||
expect(state.value.parts.length).toBe(1);
|
expect(Object.keys(state.value.parts).length).toBe(1);
|
||||||
expect(state.value.parts.find(p => p.id === 'piece-X-1')!.position).toEqual([1, 1]);
|
expect(state.value.parts['piece-X-1']!.position).toEqual([1, 1]);
|
||||||
expect(state.value.parts.find(p => p.id === 'piece-X-1')!.player).toBe('X');
|
expect(state.value.parts['piece-X-1']!.player).toBe('X');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add piece to board region children', () => {
|
it('should add piece to board region children', () => {
|
||||||
|
|
@ -183,7 +183,7 @@ describe('TicTacToe - helper functions', () => {
|
||||||
placePiece(state, 0, 0, 'X');
|
placePiece(state, 0, 0, 'X');
|
||||||
placePiece(state, 0, 1, 'O');
|
placePiece(state, 0, 1, 'O');
|
||||||
|
|
||||||
const ids = state.value.parts.map(p => p.id);
|
const ids = Object.keys(state.value.parts);
|
||||||
expect(new Set(ids).size).toBe(2);
|
expect(new Set(ids).size).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -229,8 +229,8 @@ describe('TicTacToe - game flow', () => {
|
||||||
const result = await runPromise;
|
const result = await runPromise;
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
if (result.success) expect(result.result.winner).toBeNull();
|
if (result.success) expect(result.result.winner).toBeNull();
|
||||||
expect(ctx.state.value.parts.length).toBe(1);
|
expect(Object.keys(ctx.state.value.parts).length).toBe(1);
|
||||||
expect(ctx.state.value.parts.find(p => p.id === 'piece-X-1')!.position).toEqual([1, 1]);
|
expect(ctx.state.value.parts['piece-X-1']!.position).toEqual([1, 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject move for wrong player and re-prompt', async () => {
|
it('should reject move for wrong player and re-prompt', async () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue