Skip to content

Commit

Permalink
An attempt to more correctly solve pinning.
Browse files Browse the repository at this point in the history
  • Loading branch information
aryann committed Oct 24, 2024
1 parent 7064d30 commit b400e40
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 41 deletions.
27 changes: 27 additions & 0 deletions engine/src/moves.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
]);
});
});
9 changes: 4 additions & 5 deletions engine/src/moves.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
UP_LEFT,
UP_RIGHT,
} from "./offsets";
import { pins } from "./pins";
import { pins, TAllowedMoves } from "./pins";
import {
getRank,
getSide,
Expand Down Expand Up @@ -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<TSquare, TAllowedMoves> = new Map();
try {
const kingSquare = this.board.getKingSquare(side);
pinnedPieces = pins(this.board, kingSquare);
Expand All @@ -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;
}

Expand Down
10 changes: 8 additions & 2 deletions engine/src/offsets.ts
Original file line number Diff line number Diff line change
@@ -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 };

Expand All @@ -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[] = [
Expand Down
118 changes: 106 additions & 12 deletions engine/src/pins.test.ts
Original file line number Diff line number Diff line change
@@ -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<TSquare, TAllowedMoves>([]));
});

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<TSquare, TAllowedMoves>([
["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<TSquare, TAllowedMoves>([
["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<TSquare, TAllowedMoves>([
["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<TSquare, TAllowedMoves>([
["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<TSquare, TAllowedMoves>([["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<TSquare, TAllowedMoves>([["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<TSquare, TAllowedMoves>([["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<TSquare, TAllowedMoves>([
["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<TSquare, TAllowedMoves>([["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<TSquare, TAllowedMoves>([["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<TSquare, TAllowedMoves>([["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<TSquare, TAllowedMoves>([["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<TSquare, TAllowedMoves>([
["d4", []],
["d6", []],
])
);
});
});
69 changes: 47 additions & 22 deletions engine/src/pins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<TSquare, TAllowedMoves> => {
const pins = new Map<TSquare, TAllowedMoves>();

const piece = board.get(square);
if (!piece) {
return [];
return pins;
}

const side = getSide(piece);
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -70,29 +84,40 @@ 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;
}
}
}
}

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;
};

0 comments on commit b400e40

Please sign in to comment.