From d4d428b577b7ef061e3dcb4e4ff87ae02aebafca Mon Sep 17 00:00:00 2001 From: hypercross Date: Thu, 2 Apr 2026 17:45:03 +0800 Subject: [PATCH] refactor: improved line checks --- src/samples/boop/index.ts | 141 +++++++++++++++---------------------- tests/samples/boop.test.ts | 2 +- 2 files changed, 57 insertions(+), 86 deletions(-) diff --git a/src/samples/boop/index.ts b/src/samples/boop/index.ts index 5df2bf6..426d519 100644 --- a/src/samples/boop/index.ts +++ b/src/samples/boop/index.ts @@ -193,68 +193,75 @@ export function removePieceFromBoard(host: Entity, part: Entity { + for (const [dr, dc] of DIRECTIONS) { + const minStart = -(WIN_LENGTH - 1); + for (let offset = minStart; offset <= 0; offset++) { + const startR = r + offset * dr; + const startC = c + offset * dc; + const endR = startR + (WIN_LENGTH - 1) * dr; + const endC = startC + (WIN_LENGTH - 1) * dc; + + if (startR < 0 || startR >= BOARD_SIZE || startC < 0 || startC >= BOARD_SIZE) continue; + if (endR < 0 || endR >= BOARD_SIZE || endC < 0 || endC >= BOARD_SIZE) continue; + + const line: number[][] = []; + for (let i = 0; i < WIN_LENGTH; i++) { + line.push([startR + i * dr, startC + i * dc]); + } + yield line; + } + } +} + +export function* allLines(): Generator { + const seen = new Set(); + for (let r = 0; r < BOARD_SIZE; r++) { + for (let c = 0; c < BOARD_SIZE; c++) { + for (const line of linesThrough(r, c)) { + const key = line.map(p => p.join(',')).join(';'); + if (!seen.has(key)) { + seen.add(key); + yield line; + } + } + } + } +} + +export function hasWinningLine(positions: number[][]): boolean { + const posSet = new Set(positions.map(p => `${p[0]},${p[1]}`)); + for (const line of allLines()) { + if (line.every(([lr, lc]) => posSet.has(`${lr},${lc}`))) return true; + } + return false; +} + export function checkGraduation(host: Entity, player: PlayerType): number[][][] { const board = getBoardRegion(host); const partsMap = board.partsMap.value; - const positions: number[][] = []; + const posSet = new Set(); for (const key in partsMap) { const part = partsMap[key] as Entity; if (part.value.player === player && part.value.pieceType === 'kitten') { - positions.push(part.value.position); + posSet.add(`${part.value.position[0]},${part.value.position[1]}`); } } const winningLines: number[][][] = []; - - for (let r = 0; r < BOARD_SIZE; r++) { - for (let c = 0; c <= BOARD_SIZE - WIN_LENGTH; c++) { - const line = []; - for (let i = 0; i < WIN_LENGTH; i++) { - line.push([r, c + i]); - } - if (line.every(([lr, lc]) => positions.some(([pr, pc]) => pr === lr && pc === lc))) { - winningLines.push(line); - } + for (const line of allLines()) { + if (line.every(([lr, lc]) => posSet.has(`${lr},${lc}`))) { + winningLines.push(line); } } - - for (let c = 0; c < BOARD_SIZE; c++) { - for (let r = 0; r <= BOARD_SIZE - WIN_LENGTH; r++) { - const line = []; - for (let i = 0; i < WIN_LENGTH; i++) { - line.push([r + i, c]); - } - if (line.every(([lr, lc]) => positions.some(([pr, pc]) => pr === lr && pc === lc))) { - winningLines.push(line); - } - } - } - - for (let r = 0; r <= BOARD_SIZE - WIN_LENGTH; r++) { - for (let c = 0; c <= BOARD_SIZE - WIN_LENGTH; c++) { - const line = []; - for (let i = 0; i < WIN_LENGTH; i++) { - line.push([r + i, c + i]); - } - if (line.every(([lr, lc]) => positions.some(([pr, pc]) => pr === lr && pc === lc))) { - winningLines.push(line); - } - } - } - - for (let r = WIN_LENGTH - 1; r < BOARD_SIZE; r++) { - for (let c = 0; c <= BOARD_SIZE - WIN_LENGTH; c++) { - const line = []; - for (let i = 0; i < WIN_LENGTH; i++) { - line.push([r - i, c + i]); - } - if (line.every(([lr, lc]) => positions.some(([pr, pc]) => pr === lr && pc === lc))) { - winningLines.push(line); - } - } - } - return winningLines; } @@ -312,39 +319,3 @@ export function checkWinner(host: Entity): WinnerType { return null; } - -export function hasWinningLine(positions: number[][]): boolean { - for (let r = 0; r < BOARD_SIZE; r++) { - for (let c = 0; c <= BOARD_SIZE - WIN_LENGTH; c++) { - const line = []; - for (let i = 0; i < WIN_LENGTH; i++) line.push([r, c + i]); - if (line.every(([lr, lc]) => positions.some(([pr, pc]) => pr === lr && pc === lc))) return true; - } - } - - for (let c = 0; c < BOARD_SIZE; c++) { - for (let r = 0; r <= BOARD_SIZE - WIN_LENGTH; r++) { - const line = []; - for (let i = 0; i < WIN_LENGTH; i++) line.push([r + i, c]); - if (line.every(([lr, lc]) => positions.some(([pr, pc]) => pr === lr && pc === lc))) return true; - } - } - - for (let r = 0; r <= BOARD_SIZE - WIN_LENGTH; r++) { - for (let c = 0; c <= BOARD_SIZE - WIN_LENGTH; c++) { - const line = []; - for (let i = 0; i < WIN_LENGTH; i++) line.push([r + i, c + i]); - if (line.every(([lr, lc]) => positions.some(([pr, pc]) => pr === lr && pc === lc))) return true; - } - } - - for (let r = WIN_LENGTH - 1; r < BOARD_SIZE; r++) { - for (let c = 0; c <= BOARD_SIZE - WIN_LENGTH; c++) { - const line = []; - for (let i = 0; i < WIN_LENGTH; i++) line.push([r - i, c + i]); - if (line.every(([lr, lc]) => positions.some(([pr, pc]) => pr === lr && pc === lc))) return true; - } - } - - return false; -} diff --git a/tests/samples/boop.test.ts b/tests/samples/boop.test.ts index c845485..d0864dc 100644 --- a/tests/samples/boop.test.ts +++ b/tests/samples/boop.test.ts @@ -332,7 +332,7 @@ describe('Boop - helper functions', () => { const lines = checkGraduation(state, 'white'); expect(lines.length).toBe(1); - expect(lines[0]).toEqual([[2, 0], [1, 1], [0, 2]]); + expect(lines[0]).toEqual([[0, 2], [1, 1], [2, 0]]); }); it('should not detect line with mixed piece types', () => {