feat: don't include king moves that put him in check

This commit is contained in:
Cozma Rares
2023-05-01 00:41:51 +03:00
parent 105a80d0f1
commit 8f47ce2ef7
4 changed files with 87 additions and 29 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ import { expect, test } from "vitest";
import Chess, { DEFAULT_POSITION } from "../chess/engine";
test("starting position", () => {
expect(Chess.load(DEFAULT_POSITION).getFEN()).toEqual(DEFAULT_POSITION);
expect(Chess.load().getFEN()).toEqual(DEFAULT_POSITION);
});
test("1. e4", () => {
@@ -411,9 +411,6 @@ describe("queen and king moves", () => {
{
square: "g3",
moves: [
{ from: "g3", to: "h2", flag: MOVE_FLAGS.NORMAL },
{ from: "g3", to: "g2", flag: MOVE_FLAGS.NORMAL },
{ from: "g3", to: "f2", flag: MOVE_FLAGS.NORMAL },
{ from: "g3", to: "f3", flag: MOVE_FLAGS.NORMAL },
{ from: "g3", to: "f4", flag: MOVE_FLAGS.NORMAL },
{ from: "g3", to: "g4", flag: MOVE_FLAGS.NORMAL },
@@ -424,9 +421,6 @@ describe("queen and king moves", () => {
{
square: "c7",
moves: [
{ from: "c7", to: "d8", flag: MOVE_FLAGS.NORMAL },
{ from: "c7", to: "c8", flag: MOVE_FLAGS.NORMAL },
{ from: "c7", to: "b8", flag: MOVE_FLAGS.NORMAL },
{ from: "c7", to: "b7", flag: MOVE_FLAGS.NORMAL },
{ from: "c7", to: "b6", flag: MOVE_FLAGS.NORMAL },
{ from: "c7", to: "c6", flag: MOVE_FLAGS.NORMAL },
@@ -0,0 +1,15 @@
import { test, expect } from "vitest";
import Chess from "../chess/engine";
const chess = Chess.load();
test("e4 attacked", () => {
expect(chess.isSquareAttacked("e4", "w")).toBe(true);
expect(chess.isSquareAttacked("e4", "b")).toBe(false);
});
test("e5 attacked", () => {
expect(chess.isSquareAttacked("e5", "w")).toBe(false);
expect(chess.isSquareAttacked("e5", "b")).toBe(true);
});
+71 -22
View File
@@ -86,19 +86,18 @@ type InternalMove = Move & {
beforeFEN: string;
};
// prettier-ignore
export const PIECE_MASKS: Record<keyof typeof PIECE, number> = Object.freeze({
PAWN: 0b000001,
KNIGHT: 0b000010,
BISHOP: 0b000100,
ROOK: 0b001000,
QUEEN: 0b010000,
KING: 0b100000,
export const PIECE_MASKS: Record<PieceType, number> = Object.freeze({
p: 0b000001,
n: 0b000010,
b: 0b000100,
r: 0b001000,
q: 0b010000,
k: 0b100000,
} as const);
export const COLOR_MASKS: Record<keyof typeof COLOR, number> = Object.freeze({
WHITE: 0b01,
BLACK: 0b10,
export const COLOR_MASKS: Record<Color, number> = Object.freeze({
w: 0b01,
b: 0b10,
} as const);
export function rank(squareIdx: number): number {
@@ -135,10 +134,21 @@ export function isDigit(c: string): boolean {
return /^\d$/.test(c);
}
export function squareIndex(square: Square): number {
if (square == EMPTY_SQUARE) return -1;
export function squareIndex(square: string): number {
if (square.length != 2) return -1;
return SQUARES.indexOf(square);
const file = square[0];
const rank = square[1];
if (file < "a" || file > "h") return -1;
if (rank < "1" || rank > "8") return -1;
return (
("8".charCodeAt(0) - rank.charCodeAt(0)) * 8 +
file.charCodeAt(0) -
"a".charCodeAt(0)
);
}
export function algebraic(square: number): Square {
@@ -476,12 +486,14 @@ export function generatePieceMoves(
}
export default class Chess {
private _board: Board;
private _turn: Color;
private _castling: Record<Color, number>;
private _enPassant: Square;
private _board;
private _turn;
private _castling;
private _enPassant;
private _halfMoves;
private _fullMoves;
private _moves: Move[] = [];
private _attacks: number[] = [];
private constructor(
board: Board,
@@ -497,6 +509,24 @@ export default class Chess {
this._enPassant = enPassant;
this._halfMoves = halfMoves;
this._fullMoves = fullMoves;
this._computeMoves();
this._computeAttacks();
}
private _computeMoves() {
for (let i = 0; i < this._board.length; i++)
this._moves = this._moves.concat(this.getMovesForSquare(i));
}
private _computeAttacks() {
this._attacks = new Array(64).fill(0);
this.getMoves().forEach(({ from, to }) => {
const pieceColor = (this.getPiece(from) as Piece).color;
const square = squareIndex(to);
this._attacks[square] |= COLOR_MASKS[pieceColor];
});
}
static load(fen = DEFAULT_POSITION) {
@@ -601,7 +631,7 @@ export default class Chess {
getPiece(square: Square | number) {
if (typeof square != "number") square = squareIndex(square);
return this._board[square];
return square < 0 || square > 63 ? null : this._board[square];
}
toAscii() {
@@ -630,19 +660,38 @@ export default class Chess {
return str;
}
getMoves() {}
getMoves(): Move[] {
// TODO: consider checks
return this._moves;
}
getMovesForSquare(square: Square | number): Move[] {
if (typeof square != "number") square = squareIndex(square);
const piece = this.getPiece(square);
return piece == null ? [] : generatePieceMoves(this._board, square, piece);
if (piece == null) return [];
const moves = generatePieceMoves(this._board, square, piece);
if (piece.type != PIECE.KING) return moves;
const other_color = COLOR_MASKS[swapColor(piece.color)];
return moves.filter(({ to }) => {
const square = squareIndex(to);
return (this._attacks[square] & other_color) == 0;
}, this);
}
makeMove() {}
isSquareAttacked() {}
isSquareAttacked(square: Square | number, color: Color) {
if (typeof square != "number") square = squareIndex(square);
return (this._attacks[square] & COLOR_MASKS[color]) != 0;
}
isCheck() {}