From b400e4003a7ad2ee196ab9a0b4dabc196c21a285 Mon Sep 17 00:00:00 2001 From: Aryan Naraghi Date: Wed, 23 Oct 2024 18:23:26 -0700 Subject: [PATCH] An attempt to more correctly solve pinning. --- engine/src/moves.test.ts | 27 +++++++++ engine/src/moves.ts | 9 ++- engine/src/offsets.ts | 10 +++- engine/src/pins.test.ts | 118 +++++++++++++++++++++++++++++++++++---- engine/src/pins.ts | 69 +++++++++++++++-------- 5 files changed, 192 insertions(+), 41 deletions(-) diff --git a/engine/src/moves.test.ts b/engine/src/moves.test.ts index 12d6d4a..cd5ed9c 100644 --- a/engine/src/moves.test.ts +++ b/engine/src/moves.test.ts @@ -1070,4 +1070,31 @@ describe("castling", () => { }, ]); }); + + it("pinned pieces", () => { + const generator = make("K7/8/8/8/8/5Q2/8/7q"); + + assert.sameDeepMembers(generator.generateMoves("f3"), [ + { + from: "f3", + to: "e4", + type: "normal", + }, + { + from: "f3", + to: "d5", + type: "normal", + }, + { + from: "f3", + to: "c6", + type: "normal", + }, + { + from: "f3", + to: "b7", + type: "normal", + }, + ]); + }); }); diff --git a/engine/src/moves.ts b/engine/src/moves.ts index 2953afb..90edfcb 100644 --- a/engine/src/moves.ts +++ b/engine/src/moves.ts @@ -13,7 +13,7 @@ import { UP_LEFT, UP_RIGHT, } from "./offsets"; -import { pins } from "./pins"; +import { pins, TAllowedMoves } from "./pins"; import { getRank, getSide, @@ -54,7 +54,7 @@ export class MoveGenerator { const side = getSide(piece); // TODO(aryann): Remove this check once all test boards have kings on them. - let pinnedPieces: TSquare[] = []; + let pinnedPieces: Map = new Map(); try { const kingSquare = this.board.getKingSquare(side); pinnedPieces = pins(this.board, kingSquare); @@ -63,10 +63,9 @@ export class MoveGenerator { const moves: TMove[] = []; for (const move of this.generatePseudoLegalMoves(from)) { - if (pinnedPieces.includes(move.from)) { + const pinnedPiece = pinnedPieces.get(move.from); + if (pinnedPiece && !pinnedPiece.includes(move.to)) { // Pinned pieces cannot be moved. - - // TODO(aryann): Allow pinned piece moves that are aligned with the king. continue; } diff --git a/engine/src/offsets.ts b/engine/src/offsets.ts index 40b750e..28ad84a 100644 --- a/engine/src/offsets.ts +++ b/engine/src/offsets.ts @@ -1,4 +1,4 @@ -import { NUM_FILES, NUM_RANKS } from "./types"; +import { NUM_FILES, NUM_RANKS, TPiece } from "./types"; export type Offset = { file: number; rank: number }; @@ -11,13 +11,19 @@ export const DOWN_LEFT: Offset = { file: -1, rank: -1 }; export const LEFT: Offset = { file: -1, rank: 0 }; export const UP_LEFT: Offset = { file: -1, rank: 1 }; -export const SLIDING_PIECE_OFFSETS = { +export const SLIDING_PIECE_OFFSETS: { [key in TPiece]: Offset[] } = { Q: [UP, UP_RIGHT, RIGHT, DOWN_RIGHT, DOWN, DOWN_LEFT, LEFT, UP_LEFT], q: [UP, UP_RIGHT, RIGHT, DOWN_RIGHT, DOWN, DOWN_LEFT, LEFT, UP_LEFT], R: [UP, RIGHT, DOWN, LEFT], r: [UP, RIGHT, DOWN, LEFT], B: [UP_RIGHT, DOWN_RIGHT, DOWN_LEFT, UP_LEFT], b: [UP_RIGHT, DOWN_RIGHT, DOWN_LEFT, UP_LEFT], + p: [], + P: [], + n: [], + N: [], + k: [], + K: [], }; export const KNIGHT_OFFSETS: Offset[] = [ diff --git a/engine/src/pins.test.ts b/engine/src/pins.test.ts index e6314e8..499c932 100644 --- a/engine/src/pins.test.ts +++ b/engine/src/pins.test.ts @@ -1,53 +1,147 @@ import { assert, describe, it } from "vitest"; import { BoardState } from "./board"; -import { pins } from "./pins"; +import { pins, TAllowedMoves } from "./pins"; +import { TSquare } from "./types"; describe("pins", () => { it("initial state", () => { const board = new BoardState(); - assert.deepEqual(pins(board, "e1"), []); + assert.deepEqual(pins(board, "e1"), new Map([])); }); it("adjacent rooks", () => { const board = new BoardState("8/3r4/3R4/3K4/3R4/3r4/8/8 w KQkq - 0 1"); - assert.deepEqual(pins(board, "d5"), ["d4", "d6"]); + assert.deepEqual( + pins(board, "d5"), + new Map([ + ["d4", []], + ["d6", []], + ]) + ); }); - it("far rooks", () => { + it("far enemy rooks", () => { const board = new BoardState("3r4/8/3R4/3K4/3R4/8/8/3r4 w KQkq - 0 1"); - assert.deepEqual(pins(board, "d5"), ["d4", "d6"]); + assert.deepEqual( + pins(board, "d5"), + new Map([ + ["d4", []], + ["d6", []], + ]) + ); + }); + + it("far friendly rooks", () => { + const board = new BoardState("3r4/3R4/8/3K4/8/8/3R4/3r4 w KQkq - 0 1"); + + assert.deepEqual( + pins(board, "d5"), + new Map([ + ["d2", ["d4", "d3"]], + ["d7", ["d6"]], + ]) + ); }); it("adjacent bishops", () => { const board = new BoardState("8/1b6/2R5/3K4/4R3/5b2/8/8 w KQkq - 0 1"); - assert.deepEqual(pins(board, "d5"), ["e4", "c6"]); + assert.deepEqual( + pins(board, "d5"), + new Map([ + ["e4", []], + ["c6", []], + ]) + ); }); - it("far bishops", () => { + it("far enemy bishop", () => { const board = new BoardState("K7/1R6/8/8/8/8/8/7b w KQkq - 0 1"); - assert.deepEqual(pins(board, "a8"), ["b7"]); + assert.deepEqual( + pins(board, "a8"), + new Map([["b7", []]]) + ); + }); + + it("far friendly bishop with bishop pin", () => { + const board = new BoardState("K7/8/8/8/8/8/6B1/7b w KQkq - 0 1"); + + assert.deepEqual( + pins(board, "a8"), + new Map([["g2", ["b7", "c6", "d5", "e4", "f3"]]]) + ); + }); + + it("far friendly rook with bishop pin", () => { + const board = new BoardState("K7/8/8/8/8/8/6R1/7b w KQkq - 0 1"); + + assert.deepEqual( + pins(board, "a8"), + new Map([["g2", []]]) + ); }); it("adjacent queens", () => { const board = new BoardState("8/3q4/3R4/3K4/3R4/3q4/8/8 w KQkq - 0 1"); - assert.deepEqual(pins(board, "d5"), ["d4", "d6"]); + assert.deepEqual( + pins(board, "d5"), + new Map([ + ["d4", []], + ["d6", []], + ]) + ); }); - it("far queen", () => { + it("far enemy queen with rook pin", () => { const board = new BoardState("K7/1R6/8/8/8/8/8/7q w KQkq - 0 1"); - assert.deepEqual(pins(board, "a8"), ["b7"]); + assert.deepEqual( + pins(board, "a8"), + new Map([["b7", []]]) + ); + }); + + it("far enemy queen with bishop pin", () => { + const board = new BoardState("K7/8/8/8/8/8/6B1/7q w KQkq - 0 1"); + + assert.deepEqual( + pins(board, "a8"), + new Map([["g2", ["b7", "c6", "d5", "e4", "f3"]]]) + ); + }); + + it("far enemy queen with rook pin", () => { + const board = new BoardState("K7/8/8/8/8/8/6R1/7q w KQkq - 0 1"); + + assert.deepEqual( + pins(board, "a8"), + new Map([["g2", []]]) + ); + }); + + it("far enemy queen with queen pin", () => { + const board = new BoardState("K7/8/8/8/8/5Q2/8/7q w KQkq - 0 1"); + + assert.deepEqual( + pins(board, "a8"), + new Map([["f3", ["b7", "c6", "d5", "e4"]]]) + ); }); it("multiple defenders", () => { const board = new BoardState("3r4/3P4/3R4/3K4/3R4/3P4/8/3r4 w KQkq - 0 1"); - assert.deepEqual(pins(board, "d5"), ["d4", "d6"]); + assert.deepEqual( + pins(board, "d5"), + new Map([ + ["d4", []], + ["d6", []], + ]) + ); }); }); diff --git a/engine/src/pins.ts b/engine/src/pins.ts index 94d77b4..be02495 100644 --- a/engine/src/pins.ts +++ b/engine/src/pins.ts @@ -5,13 +5,14 @@ import { DOWN_RIGHT, isInRange, LEFT, + Offset, RIGHT, SLIDING_PIECE_OFFSETS, UP, UP_LEFT, UP_RIGHT, } from "./offsets"; -import { getSide, NUM_FILES, SQUARES, TSquare } from "./types"; +import { getSide, NUM_FILES, SQUARES, TPiece, TSquare } from "./types"; const OFFSETS = [ UP, @@ -24,11 +25,17 @@ const OFFSETS = [ UP_LEFT, ]; -export const pins = (board: BoardState, square: TSquare): TSquare[] => { - const pins: TSquare[] = []; +export type TAllowedMoves = TSquare[]; + +export const pins = ( + board: BoardState, + square: TSquare +): Map => { + const pins = new Map(); + const piece = board.get(square); if (!piece) { - return []; + return pins; } const side = getSide(piece); @@ -43,7 +50,10 @@ export const pins = (board: BoardState, square: TSquare): TSquare[] => { let newFile = file; let newRank = rank; - let pinCandidate: TSquare | undefined = undefined; + let pinCandidate: { square: TSquare; piece: TPiece } | undefined = + undefined; + let allowedMoves: TSquare[] = []; + for (;;) { newFile += offset.file; newRank += offset.rank; @@ -55,12 +65,16 @@ export const pins = (board: BoardState, square: TSquare): TSquare[] => { const currentSquare = SQUARES[newRank * NUM_FILES + newFile]; const currentPiece = board.get(currentSquare); if (!currentPiece) { + if (!pinCandidate) { + allowedMoves.push(currentSquare); + } + continue; } if (!pinCandidate) { if (getSide(currentPiece) === side) { - pinCandidate = currentSquare; + pinCandidate = { square: currentSquare, piece: currentPiece }; continue; } else { // The closest sliding piece along this direction is from the other @@ -70,25 +84,24 @@ export const pins = (board: BoardState, square: TSquare): TSquare[] => { } if (getSide(currentPiece) !== side) { - if ( - currentPiece != "Q" && - currentPiece != "q" && - currentPiece != "R" && - currentPiece != "r" && - currentPiece != "B" && - currentPiece != "b" - ) { - break; - } - - for (const pieceOffset of SLIDING_PIECE_OFFSETS[currentPiece]) { + for (const attackerOffset of SLIDING_PIECE_OFFSETS[currentPiece]) { if ( - pieceOffset.file === offset.file && - pieceOffset.rank === offset.rank + attackerOffset.file !== offset.file || + attackerOffset.rank !== offset.rank ) { - pins.push(pinCandidate); - break; + continue; } + + if (movesInSameDirection(pinCandidate.piece, attackerOffset)) { + // The + allowedMoves.push(currentSquare); + } else { + allowedMoves = []; + } + + pins.set(pinCandidate.square, allowedMoves); + + break; } } } @@ -96,3 +109,15 @@ export const pins = (board: BoardState, square: TSquare): TSquare[] => { return pins; }; + +const movesInSameDirection = (piece: TPiece, targetOffset: Offset): boolean => { + for (const offset of SLIDING_PIECE_OFFSETS[piece]) { + if ( + offset.file === targetOffset.file && + offset.rank === targetOffset.rank + ) { + return true; + } + } + return false; +};