refactor: fix boop placement
This commit is contained in:
parent
ecb09c01a1
commit
975d363769
|
|
@ -4,18 +4,26 @@ const BOARD_SIZE = 6;
|
||||||
const MAX_PIECES_PER_PLAYER = 8;
|
const MAX_PIECES_PER_PLAYER = 8;
|
||||||
const WIN_LENGTH = 3;
|
const WIN_LENGTH = 3;
|
||||||
|
|
||||||
const DIRECTIONS = [
|
|
||||||
[-1, -1], [-1, 0], [-1, 1],
|
|
||||||
[0, -1], [0, 1],
|
|
||||||
[1, -1], [1, 0], [1, 1],
|
|
||||||
];
|
|
||||||
|
|
||||||
export type PlayerType = 'white' | 'black';
|
export type PlayerType = 'white' | 'black';
|
||||||
export type PieceType = 'kitten' | 'cat';
|
export type PieceType = 'kitten' | 'cat';
|
||||||
export type WinnerType = PlayerType | 'draw' | null;
|
export type WinnerType = PlayerType | 'draw' | null;
|
||||||
|
|
||||||
type BoopPart = Part & { player: PlayerType; pieceType: PieceType };
|
type BoopPart = Part & { player: PlayerType; pieceType: PieceType };
|
||||||
|
|
||||||
|
type PieceSupply = { supply: number; placed: number };
|
||||||
|
|
||||||
|
type PlayerSupply = {
|
||||||
|
kitten: PieceSupply;
|
||||||
|
cat: PieceSupply;
|
||||||
|
};
|
||||||
|
|
||||||
|
function createPlayerSupply(): PlayerSupply {
|
||||||
|
return {
|
||||||
|
kitten: { supply: MAX_PIECES_PER_PLAYER, placed: 0 },
|
||||||
|
cat: { supply: 0, placed: 0 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function createInitialState() {
|
export function createInitialState() {
|
||||||
return {
|
return {
|
||||||
board: new RegionEntity('board', {
|
board: new RegionEntity('board', {
|
||||||
|
|
@ -26,13 +34,12 @@ export function createInitialState() {
|
||||||
],
|
],
|
||||||
children: [],
|
children: [],
|
||||||
}),
|
}),
|
||||||
parts: [] as Entity<BoopPart>[],
|
|
||||||
currentPlayer: 'white' as PlayerType,
|
currentPlayer: 'white' as PlayerType,
|
||||||
winner: null as WinnerType,
|
winner: null as WinnerType,
|
||||||
whiteKittensInSupply: MAX_PIECES_PER_PLAYER,
|
players: {
|
||||||
blackKittensInSupply: MAX_PIECES_PER_PLAYER,
|
white: createPlayerSupply(),
|
||||||
whiteCatsInSupply: 0,
|
black: createPlayerSupply(),
|
||||||
blackCatsInSupply: 0,
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export type BoopState = ReturnType<typeof createInitialState>;
|
export type BoopState = ReturnType<typeof createInitialState>;
|
||||||
|
|
@ -65,23 +72,23 @@ registration.add('turn <player>', async function(cmd) {
|
||||||
|
|
||||||
while (retries < maxRetries) {
|
while (retries < maxRetries) {
|
||||||
retries++;
|
retries++;
|
||||||
const playCmd = await this.prompt('play <player> <row:number> <col:number>');
|
const playCmd = await this.prompt('play <player> <row:number> <col:number> [type:string]');
|
||||||
const [player, row, col] = playCmd.params as [PlayerType, number, number];
|
const [player, row, col, type] = playCmd.params as [PlayerType, number, number, PieceType?];
|
||||||
|
const pieceType = type === 'cat' ? 'cat' : 'kitten';
|
||||||
|
|
||||||
if (player !== turnPlayer) continue;
|
if (player !== turnPlayer) continue;
|
||||||
if (!isValidMove(row, col)) continue;
|
if (!isValidMove(row, col)) continue;
|
||||||
if (isCellOccupied(this.context, row, col)) continue;
|
if (isCellOccupied(this.context, row, col)) continue;
|
||||||
|
|
||||||
const state = this.context.value;
|
const supply = this.context.value.players[player][pieceType].supply;
|
||||||
const kittensInSupply = player === 'white' ? state.whiteKittensInSupply : state.blackKittensInSupply;
|
if (supply <= 0) continue;
|
||||||
if (kittensInSupply <= 0) continue;
|
|
||||||
|
|
||||||
placeKitten(this.context, row, col, turnPlayer);
|
placePiece(this.context, row, col, turnPlayer, pieceType);
|
||||||
applyBoops(this.context, row, col, 'kitten');
|
applyBoops(this.context, row, col, pieceType);
|
||||||
|
|
||||||
const graduatedRows = checkGraduation(this.context, turnPlayer);
|
const graduatedLines = checkGraduation(this.context, turnPlayer);
|
||||||
if (graduatedRows.length > 0) {
|
if (graduatedLines.length > 0) {
|
||||||
processGraduation(this.context, turnPlayer, graduatedRows);
|
processGraduation(this.context, turnPlayer, graduatedLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
const winner = checkWinner(this.context);
|
const winner = checkWinner(this.context);
|
||||||
|
|
@ -111,21 +118,21 @@ export function getPartAt(host: Entity<BoopState>, row: number, col: number): En
|
||||||
return (board.partsMap.value[`${row},${col}`] as Entity<BoopPart> | undefined) || null;
|
return (board.partsMap.value[`${row},${col}`] as Entity<BoopPart> | undefined) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function placeKitten(host: Entity<BoopState>, row: number, col: number, player: PlayerType) {
|
export function placePiece(host: Entity<BoopState>, row: number, col: number, player: PlayerType, pieceType: PieceType) {
|
||||||
const board = getBoardRegion(host);
|
const board = getBoardRegion(host);
|
||||||
const moveNumber = host.value.parts.length + 1;
|
const count = host.value.players[player][pieceType].placed + 1;
|
||||||
|
|
||||||
const piece: BoopPart = {
|
const piece: BoopPart = {
|
||||||
id: `piece-${player}-${moveNumber}`,
|
id: `${player}-${pieceType}-${count}`,
|
||||||
region: board,
|
region: board,
|
||||||
position: [row, col],
|
position: [row, col],
|
||||||
player,
|
player,
|
||||||
pieceType: 'kitten',
|
pieceType,
|
||||||
};
|
};
|
||||||
host.produce(state => {
|
host.produce(s => {
|
||||||
const e = entity(piece.id, piece);
|
const e = entity(piece.id, piece);
|
||||||
state.parts.push(e);
|
s.players[player][pieceType].supply--;
|
||||||
if (player === 'white') state.whiteKittensInSupply--;
|
s.players[player][pieceType].placed++;
|
||||||
else state.blackKittensInSupply--;
|
|
||||||
board.produce(draft => {
|
board.produce(draft => {
|
||||||
draft.children.push(e);
|
draft.children.push(e);
|
||||||
});
|
});
|
||||||
|
|
@ -162,11 +169,11 @@ export function applyBoops(host: Entity<BoopState>, placedRow: number, placedCol
|
||||||
const newCol = c + dc;
|
const newCol = c + dc;
|
||||||
|
|
||||||
if (newRow < 0 || newRow >= BOARD_SIZE || newCol < 0 || newCol >= BOARD_SIZE) {
|
if (newRow < 0 || newRow >= BOARD_SIZE || newCol < 0 || newCol >= BOARD_SIZE) {
|
||||||
|
const pt = part.value.pieceType;
|
||||||
|
const pl = part.value.player;
|
||||||
removePieceFromBoard(host, part);
|
removePieceFromBoard(host, part);
|
||||||
const player = part.value.player;
|
|
||||||
host.produce(state => {
|
host.produce(state => {
|
||||||
if (player === 'white') state.whiteKittensInSupply++;
|
state.players[pl][pt].supply++;
|
||||||
else state.blackKittensInSupply++;
|
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -181,17 +188,22 @@ export function applyBoops(host: Entity<BoopState>, placedRow: number, placedCol
|
||||||
|
|
||||||
export function removePieceFromBoard(host: Entity<BoopState>, part: Entity<BoopPart>) {
|
export function removePieceFromBoard(host: Entity<BoopState>, part: Entity<BoopPart>) {
|
||||||
const board = getBoardRegion(host);
|
const board = getBoardRegion(host);
|
||||||
host.produce(state => {
|
|
||||||
state.parts = state.parts.filter(p => p.id !== part.id);
|
|
||||||
board.produce(draft => {
|
board.produce(draft => {
|
||||||
draft.children = draft.children.filter(p => p.id !== part.id);
|
draft.children = draft.children.filter(p => p.id !== part.id);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkGraduation(host: Entity<BoopState>, player: PlayerType): number[][][] {
|
export function checkGraduation(host: Entity<BoopState>, player: PlayerType): number[][][] {
|
||||||
const parts = host.value.parts.filter(p => p.value.player === player && p.value.pieceType === 'kitten');
|
const board = getBoardRegion(host);
|
||||||
const positions = parts.map(p => p.value.position);
|
const partsMap = board.partsMap.value;
|
||||||
|
const positions: number[][] = [];
|
||||||
|
|
||||||
|
for (const key in partsMap) {
|
||||||
|
const part = partsMap[key] as Entity<BoopPart>;
|
||||||
|
if (part.value.player === player && part.value.pieceType === 'kitten') {
|
||||||
|
positions.push(part.value.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const winningLines: number[][][] = [];
|
const winningLines: number[][][] = [];
|
||||||
|
|
||||||
|
|
@ -271,25 +283,30 @@ export function processGraduation(host: Entity<BoopState>, player: PlayerType, l
|
||||||
|
|
||||||
const count = partsToRemove.length;
|
const count = partsToRemove.length;
|
||||||
host.produce(state => {
|
host.produce(state => {
|
||||||
const catsInSupply = player === 'white' ? state.whiteCatsInSupply : state.blackCatsInSupply;
|
state.players[player].cat.supply += count;
|
||||||
if (player === 'white') state.whiteCatsInSupply = catsInSupply + count;
|
|
||||||
else state.blackCatsInSupply = catsInSupply + count;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkWinner(host: Entity<BoopState>): WinnerType {
|
export function checkWinner(host: Entity<BoopState>): WinnerType {
|
||||||
for (const player of ['white', 'black'] as PlayerType[]) {
|
const board = getBoardRegion(host);
|
||||||
const parts = host.value.parts.filter(p => p.value.player === player && p.value.pieceType === 'cat');
|
const partsMap = board.partsMap.value;
|
||||||
const positions = parts.map(p => p.value.position);
|
|
||||||
|
|
||||||
|
for (const player of ['white', 'black'] as PlayerType[]) {
|
||||||
|
const positions: number[][] = [];
|
||||||
|
for (const key in partsMap) {
|
||||||
|
const part = partsMap[key] as Entity<BoopPart>;
|
||||||
|
if (part.value.player === player && part.value.pieceType === 'cat') {
|
||||||
|
positions.push(part.value.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (hasWinningLine(positions)) return player;
|
if (hasWinningLine(positions)) return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalParts = host.value.parts.length;
|
const state = host.value;
|
||||||
const whiteParts = host.value.parts.filter(p => p.value.player === 'white').length;
|
const whiteTotal = MAX_PIECES_PER_PLAYER - state.players.white.kitten.supply + state.players.white.cat.supply;
|
||||||
const blackParts = host.value.parts.filter(p => p.value.player === 'black').length;
|
const blackTotal = MAX_PIECES_PER_PLAYER - state.players.black.kitten.supply + state.players.black.cat.supply;
|
||||||
|
|
||||||
if (whiteParts >= MAX_PIECES_PER_PLAYER && blackParts >= MAX_PIECES_PER_PLAYER) {
|
if (whiteTotal >= MAX_PIECES_PER_PLAYER && blackTotal >= MAX_PIECES_PER_PLAYER) {
|
||||||
return 'draw';
|
return 'draw';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import {
|
||||||
checkWinner,
|
checkWinner,
|
||||||
isCellOccupied,
|
isCellOccupied,
|
||||||
getPartAt,
|
getPartAt,
|
||||||
placeKitten,
|
placePiece,
|
||||||
applyBoops,
|
applyBoops,
|
||||||
checkGraduation,
|
checkGraduation,
|
||||||
processGraduation,
|
processGraduation,
|
||||||
|
|
@ -35,6 +35,10 @@ function waitForPrompt(ctx: ReturnType<typeof createTestContext>['ctx']): Promis
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getParts(state: Entity<BoopState>) {
|
||||||
|
return state.value.board.value.children;
|
||||||
|
}
|
||||||
|
|
||||||
describe('Boop - helper functions', () => {
|
describe('Boop - helper functions', () => {
|
||||||
describe('isCellOccupied', () => {
|
describe('isCellOccupied', () => {
|
||||||
it('should return false for empty cell', () => {
|
it('should return false for empty cell', () => {
|
||||||
|
|
@ -47,7 +51,7 @@ describe('Boop - helper functions', () => {
|
||||||
it('should return true for occupied cell', () => {
|
it('should return true for occupied cell', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
placeKitten(state, 3, 3, 'white');
|
placePiece(state, 3, 3, 'white', 'kitten');
|
||||||
|
|
||||||
expect(isCellOccupied(state, 3, 3)).toBe(true);
|
expect(isCellOccupied(state, 3, 3)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
@ -55,7 +59,7 @@ describe('Boop - helper functions', () => {
|
||||||
it('should return false for different cell', () => {
|
it('should return false for different cell', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
placeKitten(state, 0, 0, 'white');
|
placePiece(state, 0, 0, 'white', 'kitten');
|
||||||
|
|
||||||
expect(isCellOccupied(state, 1, 1)).toBe(false);
|
expect(isCellOccupied(state, 1, 1)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
@ -72,7 +76,7 @@ describe('Boop - helper functions', () => {
|
||||||
it('should return the part at occupied cell', () => {
|
it('should return the part at occupied cell', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
placeKitten(state, 2, 2, 'black');
|
placePiece(state, 2, 2, 'black', 'kitten');
|
||||||
|
|
||||||
const part = getPartAt(state, 2, 2);
|
const part = getPartAt(state, 2, 2);
|
||||||
expect(part).not.toBeNull();
|
expect(part).not.toBeNull();
|
||||||
|
|
@ -83,35 +87,72 @@ describe('Boop - helper functions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('placeKitten', () => {
|
describe('placePiece', () => {
|
||||||
it('should add a kitten to the board', () => {
|
it('should add a kitten to the board', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
placeKitten(state, 2, 3, 'white');
|
placePiece(state, 2, 3, 'white', 'kitten');
|
||||||
|
|
||||||
expect(state.value.parts.length).toBe(1);
|
const parts = getParts(state);
|
||||||
expect(state.value.parts[0].value.position).toEqual([2, 3]);
|
expect(parts.length).toBe(1);
|
||||||
expect(state.value.parts[0].value.player).toBe('white');
|
expect(parts[0].value.position).toEqual([2, 3]);
|
||||||
expect(state.value.parts[0].value.pieceType).toBe('kitten');
|
expect(parts[0].value.player).toBe('white');
|
||||||
|
expect(parts[0].value.pieceType).toBe('kitten');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should name piece white-kitten-1', () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
const state = getState(ctx);
|
||||||
|
placePiece(state, 0, 0, 'white', 'kitten');
|
||||||
|
|
||||||
|
expect(getParts(state)[0].id).toBe('white-kitten-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should name piece white-kitten-2 for second white kitten', () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
const state = getState(ctx);
|
||||||
|
placePiece(state, 0, 0, 'white', 'kitten');
|
||||||
|
placePiece(state, 0, 1, 'white', 'kitten');
|
||||||
|
|
||||||
|
expect(getParts(state)[1].id).toBe('white-kitten-2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should name piece white-cat-1', () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
const state = getState(ctx);
|
||||||
|
placePiece(state, 0, 0, 'white', 'cat');
|
||||||
|
|
||||||
|
expect(getParts(state)[0].id).toBe('white-cat-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should decrement the correct player kitten supply', () => {
|
it('should decrement the correct player kitten supply', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 0, 0, 'white');
|
placePiece(state, 0, 0, 'white', 'kitten');
|
||||||
expect(state.value.whiteKittensInSupply).toBe(7);
|
expect(state.value.players.white.kitten.supply).toBe(7);
|
||||||
expect(state.value.blackKittensInSupply).toBe(8);
|
expect(state.value.players.black.kitten.supply).toBe(8);
|
||||||
|
|
||||||
placeKitten(state, 0, 1, 'black');
|
placePiece(state, 0, 1, 'black', 'kitten');
|
||||||
expect(state.value.whiteKittensInSupply).toBe(7);
|
expect(state.value.players.white.kitten.supply).toBe(7);
|
||||||
expect(state.value.blackKittensInSupply).toBe(7);
|
expect(state.value.players.black.kitten.supply).toBe(7);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should decrement the correct player cat supply', () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
const state = getState(ctx);
|
||||||
|
state.produce(s => {
|
||||||
|
s.players.white.cat.supply = 3;
|
||||||
|
});
|
||||||
|
|
||||||
|
placePiece(state, 0, 0, 'white', 'cat');
|
||||||
|
expect(state.value.players.white.cat.supply).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add piece to board region children', () => {
|
it('should add piece to board region children', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
placeKitten(state, 1, 1, 'white');
|
placePiece(state, 1, 1, 'white', 'kitten');
|
||||||
|
|
||||||
const board = getBoardRegion(state);
|
const board = getBoardRegion(state);
|
||||||
expect(board.value.children.length).toBe(1);
|
expect(board.value.children.length).toBe(1);
|
||||||
|
|
@ -120,10 +161,10 @@ describe('Boop - helper functions', () => {
|
||||||
it('should generate unique IDs for pieces', () => {
|
it('should generate unique IDs for pieces', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
placeKitten(state, 0, 0, 'white');
|
placePiece(state, 0, 0, 'white', 'kitten');
|
||||||
placeKitten(state, 0, 1, 'black');
|
placePiece(state, 0, 1, 'black', 'kitten');
|
||||||
|
|
||||||
const ids = state.value.parts.map(p => p.id);
|
const ids = getParts(state).map(p => p.id);
|
||||||
expect(new Set(ids).size).toBe(2);
|
expect(new Set(ids).size).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -133,22 +174,23 @@ describe('Boop - helper functions', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 3, 3, 'black');
|
placePiece(state, 3, 3, 'black', 'kitten');
|
||||||
placeKitten(state, 2, 2, 'white');
|
placePiece(state, 2, 2, 'white', 'kitten');
|
||||||
|
|
||||||
expect(state.value.parts[1].value.position).toEqual([2, 2]);
|
const whitePart = getParts(state)[1];
|
||||||
|
expect(whitePart.value.position).toEqual([2, 2]);
|
||||||
|
|
||||||
applyBoops(state, 3, 3, 'kitten');
|
applyBoops(state, 3, 3, 'kitten');
|
||||||
|
|
||||||
expect(state.value.parts[1].value.position).toEqual([1, 1]);
|
expect(whitePart.value.position).toEqual([1, 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not boop a cat when a kitten is placed', () => {
|
it('should not boop a cat when a kitten is placed', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 3, 3, 'black');
|
placePiece(state, 3, 3, 'black', 'kitten');
|
||||||
const whitePart = state.value.parts[0];
|
const whitePart = getParts(state)[0];
|
||||||
whitePart.produce(p => {
|
whitePart.produce(p => {
|
||||||
p.pieceType = 'cat';
|
p.pieceType = 'cat';
|
||||||
});
|
});
|
||||||
|
|
@ -162,27 +204,27 @@ describe('Boop - helper functions', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 0, 0, 'white');
|
placePiece(state, 0, 0, 'white', 'kitten');
|
||||||
placeKitten(state, 1, 1, 'black');
|
placePiece(state, 1, 1, 'black', 'kitten');
|
||||||
|
|
||||||
applyBoops(state, 1, 1, 'kitten');
|
applyBoops(state, 1, 1, 'kitten');
|
||||||
|
|
||||||
expect(state.value.parts.length).toBe(1);
|
expect(getParts(state).length).toBe(1);
|
||||||
expect(state.value.parts[0].value.player).toBe('black');
|
expect(getParts(state)[0].value.player).toBe('black');
|
||||||
expect(state.value.whiteKittensInSupply).toBe(8);
|
expect(state.value.players.white.kitten.supply).toBe(8);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not boop piece if target cell is occupied', () => {
|
it('should not boop piece if target cell is occupied', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 1, 1, 'white');
|
placePiece(state, 1, 1, 'white', 'kitten');
|
||||||
placeKitten(state, 2, 1, 'black');
|
placePiece(state, 2, 1, 'black', 'kitten');
|
||||||
placeKitten(state, 0, 1, 'black');
|
placePiece(state, 0, 1, 'black', 'kitten');
|
||||||
|
|
||||||
applyBoops(state, 0, 1, 'kitten');
|
applyBoops(state, 0, 1, 'kitten');
|
||||||
|
|
||||||
const whitePart = state.value.parts.find(p => p.value.player === 'white');
|
const whitePart = getParts(state).find(p => p.value.player === 'white');
|
||||||
expect(whitePart).toBeDefined();
|
expect(whitePart).toBeDefined();
|
||||||
if (whitePart) {
|
if (whitePart) {
|
||||||
expect(whitePart.value.position).toEqual([1, 1]);
|
expect(whitePart.value.position).toEqual([1, 1]);
|
||||||
|
|
@ -193,38 +235,37 @@ describe('Boop - helper functions', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 3, 3, 'white');
|
placePiece(state, 3, 3, 'white', 'kitten');
|
||||||
placeKitten(state, 2, 2, 'black');
|
placePiece(state, 2, 2, 'black', 'kitten');
|
||||||
placeKitten(state, 2, 3, 'black');
|
placePiece(state, 2, 3, 'black', 'kitten');
|
||||||
|
|
||||||
applyBoops(state, 3, 3, 'kitten');
|
applyBoops(state, 3, 3, 'kitten');
|
||||||
|
|
||||||
expect(state.value.parts[1].value.position).toEqual([1, 1]);
|
expect(getParts(state)[1].value.position).toEqual([1, 1]);
|
||||||
expect(state.value.parts[2].value.position).toEqual([1, 3]);
|
expect(getParts(state)[2].value.position).toEqual([1, 3]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not boop the placed piece itself', () => {
|
it('should not boop the placed piece itself', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 3, 3, 'white');
|
placePiece(state, 3, 3, 'white', 'kitten');
|
||||||
|
|
||||||
applyBoops(state, 3, 3, 'kitten');
|
applyBoops(state, 3, 3, 'kitten');
|
||||||
|
|
||||||
expect(state.value.parts[0].value.position).toEqual([3, 3]);
|
expect(getParts(state)[0].value.position).toEqual([3, 3]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('removePieceFromBoard', () => {
|
describe('removePieceFromBoard', () => {
|
||||||
it('should remove piece from parts and board children', () => {
|
it('should remove piece from board children', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
placeKitten(state, 2, 2, 'white');
|
placePiece(state, 2, 2, 'white', 'kitten');
|
||||||
const part = state.value.parts[0];
|
const part = getParts(state)[0];
|
||||||
|
|
||||||
removePieceFromBoard(state, part);
|
removePieceFromBoard(state, part);
|
||||||
|
|
||||||
expect(state.value.parts.length).toBe(0);
|
|
||||||
const board = getBoardRegion(state);
|
const board = getBoardRegion(state);
|
||||||
expect(board.value.children.length).toBe(0);
|
expect(board.value.children.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
@ -235,8 +276,8 @@ describe('Boop - helper functions', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 0, 0, 'white');
|
placePiece(state, 0, 0, 'white', 'kitten');
|
||||||
placeKitten(state, 2, 2, 'white');
|
placePiece(state, 2, 2, 'white', 'kitten');
|
||||||
|
|
||||||
const lines = checkGraduation(state, 'white');
|
const lines = checkGraduation(state, 'white');
|
||||||
expect(lines.length).toBe(0);
|
expect(lines.length).toBe(0);
|
||||||
|
|
@ -246,9 +287,9 @@ describe('Boop - helper functions', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 1, 0, 'white');
|
placePiece(state, 1, 0, 'white', 'kitten');
|
||||||
placeKitten(state, 1, 1, 'white');
|
placePiece(state, 1, 1, 'white', 'kitten');
|
||||||
placeKitten(state, 1, 2, 'white');
|
placePiece(state, 1, 2, 'white', 'kitten');
|
||||||
|
|
||||||
const lines = checkGraduation(state, 'white');
|
const lines = checkGraduation(state, 'white');
|
||||||
expect(lines.length).toBe(1);
|
expect(lines.length).toBe(1);
|
||||||
|
|
@ -259,9 +300,9 @@ describe('Boop - helper functions', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 0, 2, 'white');
|
placePiece(state, 0, 2, 'white', 'kitten');
|
||||||
placeKitten(state, 1, 2, 'white');
|
placePiece(state, 1, 2, 'white', 'kitten');
|
||||||
placeKitten(state, 2, 2, 'white');
|
placePiece(state, 2, 2, 'white', 'kitten');
|
||||||
|
|
||||||
const lines = checkGraduation(state, 'white');
|
const lines = checkGraduation(state, 'white');
|
||||||
expect(lines.length).toBe(1);
|
expect(lines.length).toBe(1);
|
||||||
|
|
@ -272,9 +313,9 @@ describe('Boop - helper functions', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 0, 0, 'white');
|
placePiece(state, 0, 0, 'white', 'kitten');
|
||||||
placeKitten(state, 1, 1, 'white');
|
placePiece(state, 1, 1, 'white', 'kitten');
|
||||||
placeKitten(state, 2, 2, 'white');
|
placePiece(state, 2, 2, 'white', 'kitten');
|
||||||
|
|
||||||
const lines = checkGraduation(state, 'white');
|
const lines = checkGraduation(state, 'white');
|
||||||
expect(lines.length).toBe(1);
|
expect(lines.length).toBe(1);
|
||||||
|
|
@ -285,9 +326,9 @@ describe('Boop - helper functions', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 2, 0, 'white');
|
placePiece(state, 2, 0, 'white', 'kitten');
|
||||||
placeKitten(state, 1, 1, 'white');
|
placePiece(state, 1, 1, 'white', 'kitten');
|
||||||
placeKitten(state, 0, 2, 'white');
|
placePiece(state, 0, 2, 'white', 'kitten');
|
||||||
|
|
||||||
const lines = checkGraduation(state, 'white');
|
const lines = checkGraduation(state, 'white');
|
||||||
expect(lines.length).toBe(1);
|
expect(lines.length).toBe(1);
|
||||||
|
|
@ -298,11 +339,11 @@ describe('Boop - helper functions', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 0, 0, 'white');
|
placePiece(state, 0, 0, 'white', 'kitten');
|
||||||
placeKitten(state, 0, 1, 'white');
|
placePiece(state, 0, 1, 'white', 'kitten');
|
||||||
placeKitten(state, 0, 2, 'white');
|
placePiece(state, 0, 2, 'white', 'kitten');
|
||||||
|
|
||||||
state.value.parts[1].produce(p => {
|
getParts(state)[1].produce(p => {
|
||||||
p.pieceType = 'cat';
|
p.pieceType = 'cat';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -316,34 +357,34 @@ describe('Boop - helper functions', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 0, 0, 'white');
|
placePiece(state, 0, 0, 'white', 'kitten');
|
||||||
placeKitten(state, 0, 1, 'white');
|
placePiece(state, 0, 1, 'white', 'kitten');
|
||||||
placeKitten(state, 0, 2, 'white');
|
placePiece(state, 0, 2, 'white', 'kitten');
|
||||||
|
|
||||||
const lines = checkGraduation(state, 'white');
|
const lines = checkGraduation(state, 'white');
|
||||||
expect(lines.length).toBe(1);
|
expect(lines.length).toBe(1);
|
||||||
|
|
||||||
processGraduation(state, 'white', lines);
|
processGraduation(state, 'white', lines);
|
||||||
|
|
||||||
expect(state.value.parts.length).toBe(0);
|
expect(getParts(state).length).toBe(0);
|
||||||
expect(state.value.whiteCatsInSupply).toBe(3);
|
expect(state.value.players.white.cat.supply).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only graduate pieces on the winning lines', () => {
|
it('should only graduate pieces on the winning lines', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 0, 0, 'white');
|
placePiece(state, 0, 0, 'white', 'kitten');
|
||||||
placeKitten(state, 0, 1, 'white');
|
placePiece(state, 0, 1, 'white', 'kitten');
|
||||||
placeKitten(state, 0, 2, 'white');
|
placePiece(state, 0, 2, 'white', 'kitten');
|
||||||
placeKitten(state, 3, 3, 'white');
|
placePiece(state, 3, 3, 'white', 'kitten');
|
||||||
|
|
||||||
const lines = checkGraduation(state, 'white');
|
const lines = checkGraduation(state, 'white');
|
||||||
processGraduation(state, 'white', lines);
|
processGraduation(state, 'white', lines);
|
||||||
|
|
||||||
expect(state.value.parts.length).toBe(1);
|
expect(getParts(state).length).toBe(1);
|
||||||
expect(state.value.parts[0].value.position).toEqual([3, 3]);
|
expect(getParts(state)[0].value.position).toEqual([3, 3]);
|
||||||
expect(state.value.whiteCatsInSupply).toBe(3);
|
expect(state.value.players.white.cat.supply).toBe(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -381,15 +422,9 @@ describe('Boop - helper functions', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 0, 0, 'white');
|
placePiece(state, 0, 0, 'white', 'cat');
|
||||||
placeKitten(state, 0, 1, 'white');
|
placePiece(state, 0, 1, 'white', 'cat');
|
||||||
placeKitten(state, 0, 2, 'white');
|
placePiece(state, 0, 2, 'white', 'cat');
|
||||||
|
|
||||||
state.value.parts.forEach(p => {
|
|
||||||
p.produce(part => {
|
|
||||||
part.pieceType = 'cat';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(checkWinner(state)).toBe('white');
|
expect(checkWinner(state)).toBe('white');
|
||||||
});
|
});
|
||||||
|
|
@ -399,10 +434,10 @@ describe('Boop - helper functions', () => {
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
for (let i = 0; i < 8; i++) {
|
for (let i = 0; i < 8; i++) {
|
||||||
placeKitten(state, i % 6, Math.floor(i / 6) + (i % 2), 'white');
|
placePiece(state, i % 6, Math.floor(i / 6) + (i % 2), 'white', 'kitten');
|
||||||
}
|
}
|
||||||
for (let i = 0; i < 8; i++) {
|
for (let i = 0; i < 8; i++) {
|
||||||
placeKitten(state, i % 6, Math.floor(i / 6) + 3 + (i % 2), 'black');
|
placePiece(state, i % 6, Math.floor(i / 6) + 3 + (i % 2), 'black', 'kitten');
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = checkWinner(state);
|
const result = checkWinner(state);
|
||||||
|
|
@ -450,8 +485,9 @@ describe('Boop - 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(getParts(ctx.state).length).toBe(1);
|
||||||
expect(ctx.state.value.parts[0].value.position).toEqual([2, 2]);
|
expect(getParts(ctx.state)[0].value.position).toEqual([2, 2]);
|
||||||
|
expect(getParts(ctx.state)[0].id).toBe('white-kitten-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject move for wrong player and re-prompt', async () => {
|
it('should reject move for wrong player and re-prompt', async () => {
|
||||||
|
|
@ -477,7 +513,7 @@ describe('Boop - game flow', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 2, 2, 'black');
|
placePiece(state, 2, 2, 'black', 'kitten');
|
||||||
|
|
||||||
const promptPromise = waitForPrompt(ctx);
|
const promptPromise = waitForPrompt(ctx);
|
||||||
const runPromise = ctx.commands.run<{winner: WinnerType}>('turn white');
|
const runPromise = ctx.commands.run<{winner: WinnerType}>('turn white');
|
||||||
|
|
@ -500,7 +536,7 @@ describe('Boop - game flow', () => {
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
state.produce(s => {
|
state.produce(s => {
|
||||||
s.whiteKittensInSupply = 0;
|
s.players.white.kitten.supply = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const promptPromise = waitForPrompt(ctx);
|
const promptPromise = waitForPrompt(ctx);
|
||||||
|
|
@ -528,7 +564,7 @@ describe('Boop - game flow', () => {
|
||||||
prompt.resolve({ name: 'play', params: ['white', 3, 3], options: {}, flags: {} });
|
prompt.resolve({ name: 'play', params: ['white', 3, 3], options: {}, flags: {} });
|
||||||
let result = await runPromise;
|
let result = await runPromise;
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
expect(state.value.parts.length).toBe(1);
|
expect(getParts(state).length).toBe(1);
|
||||||
|
|
||||||
promptPromise = waitForPrompt(ctx);
|
promptPromise = waitForPrompt(ctx);
|
||||||
runPromise = ctx.commands.run<{winner: WinnerType}>('turn black');
|
runPromise = ctx.commands.run<{winner: WinnerType}>('turn black');
|
||||||
|
|
@ -536,29 +572,74 @@ describe('Boop - game flow', () => {
|
||||||
prompt.resolve({ name: 'play', params: ['black', 2, 2], options: {}, flags: {} });
|
prompt.resolve({ name: 'play', params: ['black', 2, 2], options: {}, flags: {} });
|
||||||
result = await runPromise;
|
result = await runPromise;
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
expect(state.value.parts.length).toBe(2);
|
expect(getParts(state).length).toBe(2);
|
||||||
|
|
||||||
const whitePart = state.value.parts.find(p => p.value.player === 'white');
|
const whitePart = getParts(state).find(p => p.value.player === 'white');
|
||||||
expect(whitePart).toBeDefined();
|
expect(whitePart).toBeDefined();
|
||||||
if (whitePart) {
|
if (whitePart) {
|
||||||
expect(whitePart.value.position).not.toEqual([3, 3]);
|
expect(whitePart.value.position).not.toEqual([3, 3]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should graduate kittens to cats and check for cat win', async () => {
|
it('should graduate kittens to cats and check for cat win', () => {
|
||||||
const { ctx } = createTestContext();
|
const { ctx } = createTestContext();
|
||||||
const state = getState(ctx);
|
const state = getState(ctx);
|
||||||
|
|
||||||
placeKitten(state, 1, 0, 'white');
|
placePiece(state, 1, 0, 'white', 'kitten');
|
||||||
placeKitten(state, 1, 1, 'white');
|
placePiece(state, 1, 1, 'white', 'kitten');
|
||||||
placeKitten(state, 1, 2, 'white');
|
placePiece(state, 1, 2, 'white', 'kitten');
|
||||||
|
|
||||||
const lines = checkGraduation(state, 'white');
|
const lines = checkGraduation(state, 'white');
|
||||||
expect(lines.length).toBeGreaterThanOrEqual(1);
|
expect(lines.length).toBeGreaterThanOrEqual(1);
|
||||||
|
|
||||||
processGraduation(state, 'white', lines);
|
processGraduation(state, 'white', lines);
|
||||||
|
|
||||||
expect(state.value.parts.length).toBe(0);
|
expect(getParts(state).length).toBe(0);
|
||||||
expect(state.value.whiteCatsInSupply).toBe(3);
|
expect(state.value.players.white.cat.supply).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept placing a cat via play command', async () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
const state = getState(ctx);
|
||||||
|
|
||||||
|
state.produce(s => {
|
||||||
|
s.players.white.cat.supply = 3;
|
||||||
|
});
|
||||||
|
|
||||||
|
const promptPromise = waitForPrompt(ctx);
|
||||||
|
const runPromise = ctx.commands.run<{winner: WinnerType}>('turn white');
|
||||||
|
|
||||||
|
const promptEvent = await promptPromise;
|
||||||
|
promptEvent.resolve({ name: 'play', params: ['white', 2, 2, 'cat'], options: {}, flags: {} });
|
||||||
|
|
||||||
|
const result = await runPromise;
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(getParts(state).length).toBe(1);
|
||||||
|
expect(getParts(state)[0].id).toBe('white-cat-1');
|
||||||
|
expect(getParts(state)[0].value.pieceType).toBe('cat');
|
||||||
|
expect(state.value.players.white.cat.supply).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject placing a cat when supply is empty', async () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
const state = getState(ctx);
|
||||||
|
|
||||||
|
state.produce(s => {
|
||||||
|
s.players.white.cat.supply = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const promptPromise = waitForPrompt(ctx);
|
||||||
|
const runPromise = ctx.commands.run<{winner: WinnerType}>('turn white');
|
||||||
|
|
||||||
|
const promptEvent1 = await promptPromise;
|
||||||
|
promptEvent1.resolve({ name: 'play', params: ['white', 0, 0, 'cat'], options: {}, flags: {} });
|
||||||
|
|
||||||
|
const promptEvent2 = await waitForPrompt(ctx);
|
||||||
|
expect(promptEvent2).not.toBeNull();
|
||||||
|
|
||||||
|
promptEvent2.reject(new Error('test end'));
|
||||||
|
|
||||||
|
const result = await runPromise;
|
||||||
|
expect(result.success).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue