Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds some early ping detection logic #1

Merged
merged 4 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.skip("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",
},
]);
});
});
26 changes: 13 additions & 13 deletions engine/src/moves.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
UP_LEFT,
UP_RIGHT,
} from "./offsets";
import { pins } from "./pins";
import {
getRank,
getSide,
Expand Down Expand Up @@ -51,24 +50,25 @@ export class MoveGenerator {
if (!piece) {
throw Error(`Square ${from} is empty.`);
}
const side = getSide(piece);

// TODO(aryann): Remove this check once all test boards have kings on them.
let pinnedPieces: TSquare[] = [];
try {
const kingSquare = this.board.getKingSquare(side);
pinnedPieces = pins(this.board, kingSquare);
} catch (e) {}
// const side = getSide(piece);
// let pinnedPieces: Map<TSquare, TAllowedMoves> = new Map();
// try {
// const kingSquare = this.board.getKingSquare(side);
// pinnedPieces = pins(this.board, kingSquare);
// } catch (e) {}

const moves: TMove[] = [];

for (const move of this.generatePseudoLegalMoves(from)) {
if (pinnedPieces.includes(move.from)) {
// Pinned pieces cannot be moved.

// TODO(aryann): Allow pinned piece moves that are aligned with the king.
continue;
}
// TODO(aryann): Enable this once the pinning logic works:
//
// const pinnedPiece = pinnedPieces.get(move.from);
// if (pinnedPiece && !pinnedPiece.includes(move.to)) {
// // Pinned pieces cannot be moved.
// continue;
// }

// TODO(aryann): If the king is currently in check, exclude all moves that
// do not take the king out of check.
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
128 changes: 113 additions & 15 deletions engine/src/pins.test.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,151 @@
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", () => {
it.skip("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.skip("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("adjacent bishops", () => {
it.skip("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.skip("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.skip("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", () => {
it.skip("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", ["d3"]],
["d6", ["d7"]],
])
);
});

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", "h1"]],
])
);
});

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", "g2", "h1"]],
])
);
});

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", []],
])
);
});
});
71 changes: 50 additions & 21 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,11 @@ 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[] = [];
let canMove = true;

for (;;) {
newFile += offset.file;
newRank += offset.rank;
Expand All @@ -55,12 +66,15 @@ export const pins = (board: BoardState, square: TSquare): TSquare[] => {
const currentSquare = SQUARES[newRank * NUM_FILES + newFile];
const currentPiece = board.get(currentSquare);
if (!currentPiece) {
if (canMove) {
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 @@ -69,30 +83,45 @@ export const pins = (board: BoardState, square: TSquare): TSquare[] => {
}
}

if (getSide(currentPiece) !== side) {
if (getSide(currentPiece) === side) {
canMove = false;
continue;
}

for (const attackerOffset of SLIDING_PIECE_OFFSETS[currentPiece]) {
if (
currentPiece != "Q" &&
currentPiece != "q" &&
currentPiece != "R" &&
currentPiece != "r" &&
currentPiece != "B" &&
currentPiece != "b"
attackerOffset.file !== offset.file ||
attackerOffset.rank !== offset.rank
) {
break;
continue;
}

for (const pieceOffset of SLIDING_PIECE_OFFSETS[currentPiece]) {
if (
pieceOffset.file === offset.file &&
pieceOffset.rank === offset.rank
) {
pins.push(pinCandidate);
break;
if (movesInSameDirection(pinCandidate.piece, attackerOffset)) {
if (canMove) {
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;
};