diff --git a/src/samples/boop/index.ts b/src/samples/boop/index.ts index b967b15..0d76fa5 100644 --- a/src/samples/boop/index.ts +++ b/src/samples/boop/index.ts @@ -16,7 +16,7 @@ export type PlayerType = 'white' | 'black'; export type PieceType = 'kitten' | 'cat'; export type WinnerType = PlayerType | 'draw' | null; -type BoopPart = Part<{ player: PlayerType; pieceType: PieceType }>; +export type BoopPart = Part<{ player: PlayerType; pieceType: PieceType }>; type PieceSupply = { supply: number; placed: number }; @@ -120,7 +120,7 @@ registration.add('turn ', async function(cmd) { const pieceType = type === 'cat' ? 'cat' : 'kitten'; placePiece(this.context, row, col, turnPlayer, pieceType); - applyBoops(this.context, row, col, pieceType); + await applyBoops(this.context, row, col, pieceType); const graduatedLines = checkGraduation(this.context, turnPlayer); if (graduatedLines.length > 0) { @@ -192,8 +192,7 @@ export function placePiece(host: MutableSignal, row: number, col: num decrementSupply(playerData, pieceType); } -export function applyBoops(host: MutableSignal, placedRow: number, placedCol: number, placedType: PieceType) { - const board = getBoardRegion(host); +export async function applyBoops(host: MutableSignal, placedRow: number, placedCol: number, placedType: PieceType) { const pieces = host.value.pieces; const piecesArray = Object.values(pieces); @@ -216,39 +215,49 @@ export function applyBoops(host: MutableSignal, placedRow: number, pl } } - for (const { part, dr, dc } of piecesToBoop) { - const [r, c] = part.position; - const newRow = r + dr; - const newCol = c + dc; + await host.produceAsync(state => { + const board = state.board; + const currentPieces = state.pieces; - if (newRow < 0 || newRow >= BOARD_SIZE || newCol < 0 || newCol >= BOARD_SIZE) { - const pt = part.pieceType; - const pl = part.player; - const playerData = getPlayer(host, pl); - removePieceFromBoard(host, part); - incrementSupply(playerData, pt); - continue; + for (const { part, dr, dc } of piecesToBoop) { + const [r, c] = part.position; + const newRow = r + dr; + const newCol = c + dc; + + if (newRow < 0 || newRow >= BOARD_SIZE || newCol < 0 || newCol >= BOARD_SIZE) { + const pt = part.pieceType; + const pl = part.player; + const playerData = state.players[pl]; + // Remove piece from board + board.childIds = board.childIds.filter(id => id !== part.id); + delete board.partMap[part.position.join(',')]; + delete currentPieces[part.id]; + playerData[pt].placed--; + playerData[pt].supply++; + continue; + } + + // Check if target cell is occupied + const targetPosKey = `${newRow},${newCol}`; + if (board.partMap[targetPosKey]) continue; + + // Move piece to new position + delete board.partMap[part.position.join(',')]; + part.position = [newRow, newCol]; + board.partMap[targetPosKey] = part.id; } - - if (isCellOccupied(host, newRow, newCol)) continue; - - part.position = [newRow, newCol]; - board.partMap = Object.fromEntries( - board.childIds.map(id => { - const p = pieces[id]; - return [p.position.join(','), id]; - }) - ); - } + }); } export function removePieceFromBoard(host: MutableSignal, part: BoopPart) { - const board = getBoardRegion(host); - const playerData = getPlayer(host, part.player); - board.childIds = board.childIds.filter(id => id !== part.id); - delete board.partMap[part.position.join(',')]; - delete host.value.pieces[part.id]; - playerData[part.pieceType].placed--; + host.produce(state => { + const board = state.board; + const playerData = state.players[part.player]; + board.childIds = board.childIds.filter(id => id !== part.id); + delete board.partMap[part.position.join(',')]; + delete state.pieces[part.id]; + playerData[part.pieceType].placed--; + }); } const DIRECTIONS: [number, number][] = [ @@ -363,3 +372,18 @@ export function checkWinner(host: MutableSignal): WinnerType { return null; } + +// 命令构建器 +export const commands = { + play: (player: PlayerType, row: number, col: number, type?: PieceType) => + `play ${player} ${row} ${col}${type ? ` ${type}` : ''}`, + turn: (player: PlayerType) => `turn ${player}`, + graduate: (row: number, col: number) => `graduate ${row} ${col}`, +} as const; + +// 导出游戏模块 +export const gameModule = { + createInitialState, + registry, + commands, +}; diff --git a/tests/samples/boop.test.ts b/tests/samples/boop.test.ts index 89ba9ac..ba042a2 100644 --- a/tests/samples/boop.test.ts +++ b/tests/samples/boop.test.ts @@ -170,7 +170,7 @@ describe('Boop - helper functions', () => { }); describe('applyBoops', () => { - it('should boop adjacent kitten away from placed kitten', () => { + it('should boop adjacent kitten away from placed kitten', async () => { const { ctx } = createTestContext(); const state = getState(ctx); @@ -180,12 +180,12 @@ describe('Boop - helper functions', () => { const whitePart = getParts(state)[1]; expect(whitePart.position).toEqual([2, 2]); - applyBoops(state, 3, 3, 'kitten'); + await applyBoops(state, 3, 3, 'kitten'); expect(whitePart.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', async () => { const { ctx } = createTestContext(); const state = getState(ctx); @@ -193,26 +193,26 @@ describe('Boop - helper functions', () => { const whitePart = getParts(state)[0]; whitePart.pieceType = 'cat'; - applyBoops(state, 3, 3, 'kitten'); + await applyBoops(state, 3, 3, 'kitten'); expect(whitePart.position).toEqual([3, 3]); }); - it('should remove piece that is booped off the board', () => { + it('should remove piece that is booped off the board', async () => { const { ctx } = createTestContext(); const state = getState(ctx); placePiece(state, 0, 0, 'white', 'kitten'); placePiece(state, 1, 1, 'black', 'kitten'); - applyBoops(state, 1, 1, 'kitten'); + await applyBoops(state, 1, 1, 'kitten'); expect(getParts(state).length).toBe(1); expect(getParts(state)[0].player).toBe('black'); 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', async () => { const { ctx } = createTestContext(); const state = getState(ctx); @@ -220,7 +220,7 @@ describe('Boop - helper functions', () => { placePiece(state, 2, 1, 'black', 'kitten'); placePiece(state, 0, 1, 'black', 'kitten'); - applyBoops(state, 0, 1, 'kitten'); + await applyBoops(state, 0, 1, 'kitten'); const whitePart = getParts(state).find(p => p.player === 'white'); expect(whitePart).toBeDefined(); @@ -229,7 +229,7 @@ describe('Boop - helper functions', () => { } }); - it('should boop multiple adjacent pieces', () => { + it('should boop multiple adjacent pieces', async () => { const { ctx } = createTestContext(); const state = getState(ctx); @@ -237,19 +237,19 @@ describe('Boop - helper functions', () => { placePiece(state, 2, 2, 'black', 'kitten'); placePiece(state, 2, 3, 'black', 'kitten'); - applyBoops(state, 3, 3, 'kitten'); + await applyBoops(state, 3, 3, 'kitten'); expect(getParts(state)[1].position).toEqual([1, 1]); expect(getParts(state)[2].position).toEqual([1, 3]); }); - it('should not boop the placed piece itself', () => { + it('should not boop the placed piece itself', async () => { const { ctx } = createTestContext(); const state = getState(ctx); placePiece(state, 3, 3, 'white', 'kitten'); - applyBoops(state, 3, 3, 'kitten'); + await applyBoops(state, 3, 3, 'kitten'); expect(getParts(state)[0].position).toEqual([3, 3]); });