refactor: improved line checks

This commit is contained in:
hypercross 2026-04-02 17:45:03 +08:00
parent 975d363769
commit d4d428b577
2 changed files with 57 additions and 86 deletions

View File

@ -193,68 +193,75 @@ export function removePieceFromBoard(host: Entity<BoopState>, part: Entity<BoopP
});
}
const DIRECTIONS: [number, number][] = [
[0, 1],
[1, 0],
[1, 1],
[1, -1],
];
export function* linesThrough(r: number, c: number): Generator<number[][]> {
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<number[][]> {
const seen = new Set<string>();
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<BoopState>, player: PlayerType): number[][][] {
const board = getBoardRegion(host);
const partsMap = board.partsMap.value;
const positions: number[][] = [];
const posSet = new Set<string>();
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);
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))) {
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<BoopState>): 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;
}

View File

@ -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', () => {