mirror of
https://github.com/lichess-org/chessground.git
synced 2026-05-26 13:50:53 +00:00
Merge pull request #365 from johndoknjas/remove-premove-trimming
Remove premove trimming
This commit is contained in:
@@ -47,7 +47,6 @@ export interface Config {
|
||||
castle?: boolean; // whether to allow king castle premoves
|
||||
dests?: cg.Key[]; // premove destinations for the current selection
|
||||
customDests?: cg.Dests; // use custom valid premoves. {"a2" ["a3" "a4"] "b1" ["a3" "c3"]}
|
||||
unrestrictedPremoves?: boolean; // if falsy, the positions of friendly pieces will be used to trim premove options
|
||||
additionalPremoveRequirements?: cg.Mobility;
|
||||
events?: {
|
||||
set?: (orig: cg.Key, dest: cg.Key, metadata?: cg.SetPremoveMetadata) => void; // called after the premove has been set
|
||||
|
||||
+13
-181
@@ -3,204 +3,36 @@ import * as cg from './types.js';
|
||||
import { HeadlessState } from './state.js';
|
||||
import { Mobility, MobilityContext } from './types.js';
|
||||
|
||||
const isDestOccupiedByFriendly = (ctx: MobilityContext): boolean => ctx.friendlies.has(ctx.dest.key);
|
||||
const pawn: Mobility = (ctx: MobilityContext) =>
|
||||
util.diff(ctx.orig.pos[0], ctx.dest.pos[0]) <= 1 &&
|
||||
(util.diff(ctx.orig.pos[0], ctx.dest.pos[0]) === 1
|
||||
? ctx.dest.pos[1] === ctx.orig.pos[1] + (ctx.color === 'white' ? 1 : -1)
|
||||
: util.pawnDirAdvance(...ctx.orig.pos, ...ctx.dest.pos, ctx.color === 'white'));
|
||||
|
||||
const isDestOccupiedByEnemy = (ctx: MobilityContext): boolean => ctx.enemies.has(ctx.dest.key);
|
||||
const knight: Mobility = (ctx: MobilityContext) => util.knightDir(...ctx.orig.pos, ...ctx.dest.pos);
|
||||
|
||||
const anyPieceBetween = (orig: cg.Pos, dest: cg.Pos, pieces: cg.Pieces): boolean =>
|
||||
util.squaresBetween(...orig, ...dest).some(s => pieces.has(s));
|
||||
const bishop: Mobility = (ctx: MobilityContext) => util.bishopDir(...ctx.orig.pos, ...ctx.dest.pos);
|
||||
|
||||
const canEnemyPawnAdvanceToSquare = (pawnStart: cg.Key, dest: cg.Key, ctx: MobilityContext): boolean => {
|
||||
const piece = ctx.enemies.get(pawnStart);
|
||||
if (piece?.role !== 'pawn') return false;
|
||||
const step = piece.color === 'white' ? 1 : -1;
|
||||
const startPos = util.key2pos(pawnStart);
|
||||
const destPos = util.key2pos(dest);
|
||||
return (
|
||||
util.pawnDirAdvance(...startPos, ...destPos, piece.color === 'white') &&
|
||||
!anyPieceBetween(startPos, [destPos[0], destPos[1] + step], ctx.allPieces)
|
||||
);
|
||||
};
|
||||
|
||||
const canEnemyPawnCaptureOnSquare = (pawnStart: cg.Key, dest: cg.Key, ctx: MobilityContext): boolean => {
|
||||
const enemyPawn = ctx.enemies.get(pawnStart);
|
||||
return (
|
||||
enemyPawn?.role === 'pawn' &&
|
||||
util.pawnDirCapture(...util.key2pos(pawnStart), ...util.key2pos(dest), enemyPawn.color === 'white') &&
|
||||
(ctx.friendlies.has(dest) ||
|
||||
canBeCapturedBySomeEnemyEnPassant(
|
||||
util.squareShiftedVertically(dest, enemyPawn.color === 'white' ? -1 : 1),
|
||||
ctx.friendlies,
|
||||
ctx.enemies,
|
||||
ctx.lastMove,
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
const canSomeEnemyPawnAdvanceToDest = (ctx: MobilityContext): boolean =>
|
||||
[...ctx.enemies.keys()].some(key => canEnemyPawnAdvanceToSquare(key, ctx.dest.key, ctx));
|
||||
|
||||
const isDestControlledByEnemy = (ctx: MobilityContext, pieceRolesExclude?: cg.Role[]): boolean => {
|
||||
const square: cg.Pos = ctx.dest.pos;
|
||||
return [...ctx.enemies].some(([key, piece]) => {
|
||||
const piecePos = util.key2pos(key);
|
||||
return (
|
||||
!pieceRolesExclude?.includes(piece.role) &&
|
||||
((piece.role === 'pawn' && util.pawnDirCapture(...piecePos, ...square, piece.color === 'white')) ||
|
||||
(piece.role === 'knight' && util.knightDir(...piecePos, ...square)) ||
|
||||
(piece.role === 'bishop' && util.bishopDir(...piecePos, ...square)) ||
|
||||
(piece.role === 'rook' && util.rookDir(...piecePos, ...square)) ||
|
||||
(piece.role === 'queen' && util.queenDir(...piecePos, ...square)) ||
|
||||
(piece.role === 'king' && util.kingDirNonCastling(...piecePos, ...square))) &&
|
||||
(!['bishop', 'rook', 'queen'].includes(piece.role) || !anyPieceBetween(piecePos, square, ctx.allPieces))
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const isFriendlyOnDestAndAttacked = (ctx: MobilityContext): boolean =>
|
||||
isDestOccupiedByFriendly(ctx) &&
|
||||
(canBeCapturedBySomeEnemyEnPassant(ctx.dest.key, ctx.friendlies, ctx.enemies, ctx.lastMove) ||
|
||||
isDestControlledByEnemy(ctx));
|
||||
|
||||
const canBeCapturedBySomeEnemyEnPassant = (
|
||||
potentialSquareOfFriendlyPawn: cg.Key | undefined,
|
||||
friendlies: cg.Pieces,
|
||||
enemies: cg.Pieces,
|
||||
lastMove?: cg.Key[],
|
||||
): boolean => {
|
||||
if (!potentialSquareOfFriendlyPawn || (lastMove && potentialSquareOfFriendlyPawn !== lastMove[1]))
|
||||
return false;
|
||||
const pos = util.key2pos(potentialSquareOfFriendlyPawn);
|
||||
const friendly = friendlies.get(potentialSquareOfFriendlyPawn);
|
||||
return (
|
||||
friendly?.role === 'pawn' &&
|
||||
pos[1] === (friendly.color === 'white' ? 3 : 4) &&
|
||||
(!lastMove || util.diff(util.key2pos(lastMove[0])[1], pos[1]) === 2) &&
|
||||
[1, -1].some(delta => {
|
||||
const k = util.pos2key([pos[0] + delta, pos[1]]);
|
||||
return !!k && enemies.get(k)?.role === 'pawn';
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const isPathClearEnoughOfFriendliesForPremove = (ctx: MobilityContext, isPawnAdvance: boolean): boolean => {
|
||||
if (ctx.unrestrictedPremoves) return true;
|
||||
const squaresBetween = util.squaresBetween(...ctx.orig.pos, ...ctx.dest.pos);
|
||||
if (isPawnAdvance) squaresBetween.push(ctx.dest.key);
|
||||
const squaresOfFriendliesBetween = squaresBetween.filter(s => ctx.friendlies.has(s));
|
||||
if (!squaresOfFriendliesBetween.length) return true;
|
||||
const firstSquareOfFriendliesBetween = squaresOfFriendliesBetween[0];
|
||||
const nextSquare = util.squareShiftedVertically(
|
||||
firstSquareOfFriendliesBetween,
|
||||
ctx.color === 'white' ? -1 : 1,
|
||||
);
|
||||
return (
|
||||
squaresOfFriendliesBetween.length === 1 &&
|
||||
canBeCapturedBySomeEnemyEnPassant(
|
||||
firstSquareOfFriendliesBetween,
|
||||
ctx.friendlies,
|
||||
ctx.enemies,
|
||||
ctx.lastMove,
|
||||
) &&
|
||||
!!nextSquare &&
|
||||
!squaresBetween.includes(nextSquare)
|
||||
);
|
||||
};
|
||||
|
||||
const isPathClearEnoughOfEnemiesForPremove = (ctx: MobilityContext, isPawnAdvance: boolean): boolean => {
|
||||
if (ctx.unrestrictedPremoves) return true;
|
||||
const squaresBetween = util.squaresBetween(...ctx.orig.pos, ...ctx.dest.pos);
|
||||
if (isPawnAdvance) squaresBetween.push(ctx.dest.key);
|
||||
const squaresOfEnemiesBetween = squaresBetween.filter(s => ctx.enemies.has(s));
|
||||
if (squaresOfEnemiesBetween.length > 1) return false;
|
||||
if (!squaresOfEnemiesBetween.length) return true;
|
||||
const enemySquare = squaresOfEnemiesBetween[0];
|
||||
const enemy = ctx.enemies.get(enemySquare);
|
||||
if (!enemy || enemy.role !== 'pawn') return true;
|
||||
|
||||
const enemyStep = enemy.color === 'white' ? 1 : -1;
|
||||
const squareAbove = util.squareShiftedVertically(enemySquare, enemyStep);
|
||||
const enemyPawnDests: cg.Key[] = squareAbove
|
||||
? [
|
||||
...util.adjacentSquares(squareAbove).filter(s => canEnemyPawnCaptureOnSquare(enemySquare, s, ctx)),
|
||||
...[squareAbove, util.squareShiftedVertically(squareAbove, enemyStep)]
|
||||
.filter(s => !!s)
|
||||
.filter(s => canEnemyPawnAdvanceToSquare(enemySquare, s, ctx)),
|
||||
]
|
||||
: [];
|
||||
const badSquares = [...squaresBetween, ctx.orig.key];
|
||||
return enemyPawnDests.some(square => !badSquares.includes(square));
|
||||
};
|
||||
|
||||
const isPathClearEnoughForPremove = (ctx: MobilityContext, isPawnAdvance: boolean): boolean =>
|
||||
isPathClearEnoughOfFriendliesForPremove(ctx, isPawnAdvance) &&
|
||||
isPathClearEnoughOfEnemiesForPremove(ctx, isPawnAdvance);
|
||||
|
||||
const pawn: Mobility = (ctx: MobilityContext) => {
|
||||
const step = ctx.color === 'white' ? 1 : -1;
|
||||
if (util.diff(ctx.orig.pos[0], ctx.dest.pos[0]) > 1) return false;
|
||||
if (!util.diff(ctx.orig.pos[0], ctx.dest.pos[0]))
|
||||
return (
|
||||
util.pawnDirAdvance(...ctx.orig.pos, ...ctx.dest.pos, ctx.color === 'white') &&
|
||||
isPathClearEnoughForPremove(ctx, true)
|
||||
);
|
||||
if (ctx.dest.pos[1] !== ctx.orig.pos[1] + step) return false;
|
||||
if (ctx.unrestrictedPremoves || isDestOccupiedByEnemy(ctx)) return true;
|
||||
if (isDestOccupiedByFriendly(ctx)) return isDestControlledByEnemy(ctx);
|
||||
else
|
||||
return (
|
||||
canSomeEnemyPawnAdvanceToDest(ctx) ||
|
||||
canBeCapturedBySomeEnemyEnPassant(
|
||||
util.pos2key([ctx.dest.pos[0], ctx.dest.pos[1] + step]),
|
||||
ctx.friendlies,
|
||||
ctx.enemies,
|
||||
ctx.lastMove,
|
||||
) ||
|
||||
isDestControlledByEnemy(ctx, ['pawn'])
|
||||
);
|
||||
};
|
||||
|
||||
const knight: Mobility = (ctx: MobilityContext) =>
|
||||
util.knightDir(...ctx.orig.pos, ...ctx.dest.pos) &&
|
||||
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx));
|
||||
|
||||
const bishop: Mobility = (ctx: MobilityContext) =>
|
||||
util.bishopDir(...ctx.orig.pos, ...ctx.dest.pos) &&
|
||||
isPathClearEnoughForPremove(ctx, false) &&
|
||||
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx));
|
||||
|
||||
const rook: Mobility = (ctx: MobilityContext) =>
|
||||
util.rookDir(...ctx.orig.pos, ...ctx.dest.pos) &&
|
||||
isPathClearEnoughForPremove(ctx, false) &&
|
||||
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx));
|
||||
const rook: Mobility = (ctx: MobilityContext) => util.rookDir(...ctx.orig.pos, ...ctx.dest.pos);
|
||||
|
||||
const queen: Mobility = (ctx: MobilityContext) => bishop(ctx) || rook(ctx);
|
||||
|
||||
const king: Mobility = (ctx: MobilityContext) =>
|
||||
(util.kingDirNonCastling(...ctx.orig.pos, ...ctx.dest.pos) &&
|
||||
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx))) ||
|
||||
util.kingDirNonCastling(...ctx.orig.pos, ...ctx.dest.pos) ||
|
||||
(ctx.canCastle &&
|
||||
ctx.orig.pos[1] === ctx.dest.pos[1] &&
|
||||
ctx.orig.pos[1] === (ctx.color === 'white' ? 0 : 7) &&
|
||||
((ctx.orig.pos[0] === 4 &&
|
||||
((ctx.dest.pos[0] === 2 && ctx.rookFilesFriendlies.includes(0)) ||
|
||||
(ctx.dest.pos[0] === 6 && ctx.rookFilesFriendlies.includes(7)))) ||
|
||||
ctx.rookFilesFriendlies.includes(ctx.dest.pos[0])) &&
|
||||
(ctx.unrestrictedPremoves ||
|
||||
/* The following checks if no non-rook friendly piece is in the way between the king and its castling destination.
|
||||
Note that for the Chess960 edge case of Kb1 "long castling", the check passes even if there is a piece in the way
|
||||
on c1. But this is fine, since premoving from b1 to a1 as a normal move would have already returned true. */
|
||||
util
|
||||
.squaresBetween(...ctx.orig.pos, ctx.dest.pos[0] > ctx.orig.pos[0] ? 7 : 1, ctx.dest.pos[1])
|
||||
.map(s => ctx.allPieces.get(s))
|
||||
.every(p => !p || util.samePiece(p, { role: 'rook', color: ctx.color }))));
|
||||
ctx.rookFilesFriendlies.includes(ctx.dest.pos[0])));
|
||||
|
||||
const mobilityByRole = { pawn, knight, bishop, rook, queen, king };
|
||||
|
||||
export function premove(state: HeadlessState, key: cg.Key): cg.Key[] {
|
||||
// TODO - remove `castle` once https://github.com/lichess-org/lila/pull/18630 is merged.
|
||||
const pieces = state.pieces,
|
||||
canCastle = state.premovable.castle,
|
||||
unrestrictedPremoves = !!state.premovable.unrestrictedPremoves;
|
||||
canCastle = state.premovable.castle;
|
||||
const piece = pieces.get(key);
|
||||
if (!piece || piece.color === state.turnColor) return [];
|
||||
const color = piece.color,
|
||||
@@ -215,7 +47,6 @@ export function premove(state: HeadlessState, key: cg.Key): cg.Key[] {
|
||||
allPieces: pieces,
|
||||
friendlies: friendlies,
|
||||
enemies: enemies,
|
||||
unrestrictedPremoves: unrestrictedPremoves,
|
||||
color: color,
|
||||
canCastle: canCastle,
|
||||
rookFilesFriendlies: Array.from(pieces)
|
||||
@@ -225,5 +56,6 @@ export function premove(state: HeadlessState, key: cg.Key): cg.Key[] {
|
||||
.map(([k]) => util.key2pos(k)[0]),
|
||||
lastMove: state.lastMove,
|
||||
};
|
||||
// todo - remove more properties from MobilityContext that aren't used in this file, and adjust as needed in lila.
|
||||
return util.allPosAndKey.filter(dest => mobility({ ...partialCtx, dest })).map(pk => pk.key);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ export interface HeadlessState {
|
||||
dests?: cg.Key[]; // premove destinations for the current selection
|
||||
customDests?: cg.Dests; // use custom valid premoves. {"a2" ["a3" "a4"] "b1" ["a3" "c3"]}
|
||||
current?: cg.KeyPair; // keys of the current saved premove ["e2" "e4"]
|
||||
unrestrictedPremoves?: boolean; // if falsy, the positions of friendly pieces will be used to trim premove options
|
||||
additionalPremoveRequirements: cg.Mobility;
|
||||
events: {
|
||||
set?: (orig: cg.Key, dest: cg.Key, metadata?: cg.SetPremoveMetadata) => void; // called after the premove has been set
|
||||
|
||||
@@ -124,7 +124,6 @@ export type MobilityContext = {
|
||||
allPieces: Pieces;
|
||||
friendlies: Pieces;
|
||||
enemies: Pieces;
|
||||
unrestrictedPremoves: boolean;
|
||||
color: Color;
|
||||
canCastle: boolean;
|
||||
rookFilesFriendlies: number[];
|
||||
|
||||
+53
-107
@@ -4,27 +4,22 @@ import { defaults, HeadlessState } from '../src/state';
|
||||
import * as fen from '../src/fen';
|
||||
import * as util from '../src/util';
|
||||
|
||||
const diagonallyOpposite = (square: cg.Key): cg.Key =>
|
||||
util.pos2keyUnsafe(util.key2pos(square).map(n => 7 - n) as cg.Pos);
|
||||
const invertSquare = (square: cg.Key): cg.Key => {
|
||||
const asPos = util.key2pos(square);
|
||||
return util.pos2keyUnsafe([asPos[0], 7 - asPos[1]] as cg.Pos);
|
||||
};
|
||||
|
||||
const invertPieces = (pieces: cg.Pieces): cg.Pieces =>
|
||||
new Map(
|
||||
[...pieces].map(([key, piece]) => [
|
||||
diagonallyOpposite(key),
|
||||
invertSquare(key),
|
||||
{ role: piece.role, color: util.opposite(piece.color) },
|
||||
]),
|
||||
);
|
||||
|
||||
const makeState = (
|
||||
pieces: cg.Pieces,
|
||||
trimPremoves: boolean,
|
||||
lastMove: cg.Key[] | undefined,
|
||||
turnColor: cg.Color,
|
||||
): HeadlessState => {
|
||||
const makeState = (pieces: cg.Pieces, turnColor: cg.Color): HeadlessState => {
|
||||
const state = defaults();
|
||||
state.pieces = pieces;
|
||||
if (!trimPremoves) state.premovable.unrestrictedPremoves = true;
|
||||
state.lastMove = lastMove;
|
||||
state.turnColor = turnColor;
|
||||
return state;
|
||||
};
|
||||
@@ -32,11 +27,10 @@ const makeState = (
|
||||
const testPosition = (
|
||||
pieces: cg.Pieces,
|
||||
turnColor: cg.Color,
|
||||
lastMove: cg.Key[] | undefined,
|
||||
expectedPremoves: Map<cg.Key, Set<cg.Key>>,
|
||||
checkInverseToo: boolean,
|
||||
): void => {
|
||||
const state = makeState(pieces, true, lastMove, turnColor);
|
||||
const state = makeState(pieces, turnColor);
|
||||
for (const [from, expectedDests] of expectedPremoves) {
|
||||
expect(new Set(premove(state, from))).toEqual(expectedDests);
|
||||
}
|
||||
@@ -47,112 +41,64 @@ const testPosition = (
|
||||
testPosition(
|
||||
invertPieces(pieces),
|
||||
util.opposite(turnColor),
|
||||
lastMove?.map(sq => diagonallyOpposite(sq)),
|
||||
new Map(
|
||||
[...expectedPremoves].map(([start, dests]) => [
|
||||
diagonallyOpposite(start),
|
||||
new Set(Array.from(dests, diagonallyOpposite)),
|
||||
invertSquare(start),
|
||||
new Set(Array.from(dests, invertSquare)),
|
||||
]),
|
||||
),
|
||||
false,
|
||||
);
|
||||
};
|
||||
|
||||
test('premoves are trimmed appropriately', () => {
|
||||
test('premoves are found', () => {
|
||||
const expectedPremoves = new Map<cg.Key, Set<cg.Key>>([
|
||||
['f8', new Set(['g8', 'e8', 'd8', 'c8', 'f7', 'f6', 'f5', 'f4', 'f3', 'f2', 'f1'])],
|
||||
['f1', new Set(['h1', 'g1', 'e1', 'd1', 'c1', 'b1', 'a1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7'])],
|
||||
['g5', new Set(['h5', 'f5', 'e5', 'd5', 'c5', 'g6'])],
|
||||
['h8', new Set(['h7', 'g8'])],
|
||||
['e3', new Set(['g3', 'f3', 'd3', 'c3', 'b3', 'e2', 'e1', 'e4', 'e5', 'e6'])],
|
||||
['d6', new Set(['e6', 'f6', 'g6', 'c6', 'b6', 'a6', 'd7', 'd8', 'd5', 'd4', 'd3', 'd2', 'd1'])],
|
||||
['h1', new Set(['h2', 'h3', 'h4', 'g1', 'f1', 'g2', 'f3', 'e4', 'd5', 'c6', 'b7'])],
|
||||
['a4', new Set(['b3', 'b4', 'c4', 'd4', 'e4', 'f4', 'a5', 'a6'])],
|
||||
['c5', new Set(['c4', 'c3', 'd4', 'e3', 'd5', 'e5', 'f5', 'c6', 'b6', 'a7', 'b4'])],
|
||||
['a8', new Set(['b8', 'b7', 'a7'])],
|
||||
['c8', new Set(['e7', 'b6', 'a7'])],
|
||||
['g4', new Set(['h2', 'f6', 'e5', 'e3', 'f2'])],
|
||||
['c2', new Set(['e1', 'e3', 'd4', 'b4', 'a1'])],
|
||||
['b2', new Set(['c1', 'a1', 'c3', 'd4', 'e5', 'f6'])],
|
||||
['c7', new Set(['d8', 'b8', 'b6', 'a5'])],
|
||||
['h6', new Set(['h5'])],
|
||||
['g7', new Set(['g6', 'f6'])],
|
||||
['e5', new Set(['e4', 'd4'])],
|
||||
['b5', new Set(['b4'])],
|
||||
['a3', new Set([])],
|
||||
['a7', new Set(['a6', 'a5', 'b6'])],
|
||||
['b7', new Set(['b6', 'b5', 'a6', 'c6'])],
|
||||
['c7', new Set(['c6', 'c5', 'b6', 'd6'])],
|
||||
['d7', new Set(['d6', 'd5', 'c6', 'e6'])],
|
||||
['e7', new Set(['e6', 'e5', 'd6', 'f6'])],
|
||||
['f6', new Set(['f5', 'e5', 'g5'])],
|
||||
['g7', new Set(['g6', 'g5', 'f6', 'h6'])],
|
||||
['h7', new Set(['h6', 'h5', 'g6'])],
|
||||
['a8', new Set(['a7', 'a6', 'a5', 'a4', 'a3', 'a2', 'a1', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8'])],
|
||||
['b8', new Set(['a6', 'c6', 'd7'])],
|
||||
['c8', new Set(['a6', 'b7', 'd7', 'e6', 'f5', 'g4', 'h3'])],
|
||||
[
|
||||
'd8',
|
||||
new Set([
|
||||
'd7',
|
||||
'd6',
|
||||
'd5',
|
||||
'd4',
|
||||
'd3',
|
||||
'd2',
|
||||
'd1',
|
||||
'e8',
|
||||
'f8',
|
||||
'g8',
|
||||
'h8',
|
||||
'c8',
|
||||
'b8',
|
||||
'a8',
|
||||
'e7',
|
||||
'f6',
|
||||
'g5',
|
||||
'h4',
|
||||
'c7',
|
||||
'b6',
|
||||
'a5',
|
||||
]),
|
||||
],
|
||||
['e8', new Set(['d8', 'f8', 'd7', 'e7', 'f7', 'g8', 'h8', 'c8', 'a8'])],
|
||||
['f8', new Set(['g7', 'h6', 'e7', 'd6', 'c5', 'b4', 'a3'])],
|
||||
['g8', new Set(['h6', 'f6', 'e7'])],
|
||||
['h8', new Set(['h7', 'h6', 'h5', 'h4', 'h3', 'h2', 'h1', 'g8', 'f8', 'e8', 'd8', 'c8', 'b8', 'a8'])],
|
||||
]);
|
||||
|
||||
testPosition(
|
||||
fen.read('k1n2r1r/2bP2p1/3r3p/Ppq1pPr1/qP4n1/p3r1P1/PbnP2KP/R4r1q w - - 0 1'),
|
||||
fen.read('rnbqkbnr/ppppp1pp/5p2/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'),
|
||||
'white',
|
||||
['e7', 'e5'],
|
||||
expectedPremoves,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('anticipate all en passant captures if no last move', () => {
|
||||
const expectedPremoves = new Map<cg.Key, Set<cg.Key>>([
|
||||
['a2', new Set(['b1', 'b3', 'c4', 'd5', 'e6', 'f7', 'g8'])],
|
||||
['h2', new Set(['g1', 'g3', 'f4', 'e5', 'd6', 'c7', 'b8'])],
|
||||
['h3', new Set(['g3', 'f3', 'e3', 'd3', 'h4', 'h5'])],
|
||||
['f5', new Set(['e5', 'd5', 'c5', 'b5', 'a5', 'f6', 'f7', 'f8', 'f4', 'f3'])],
|
||||
['c4', new Set(['c5'])],
|
||||
['f4', new Set([])],
|
||||
['g5', new Set(['g6'])],
|
||||
['d3', new Set(['d4', 'e4'])],
|
||||
]);
|
||||
testPosition(
|
||||
fen.read('8/8/8/5RPp/1pP1pP2/3Pp2R/B6B/8 b - - 0 1'),
|
||||
'black',
|
||||
undefined,
|
||||
expectedPremoves,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('horde no en passant for first to third rank', () => {
|
||||
const expectedPremoves = new Map<cg.Key, Set<cg.Key>>([
|
||||
['f1', new Set(['f2', 'f3'])],
|
||||
['g3', new Set(['g4', 'h4'])],
|
||||
]);
|
||||
testPosition(
|
||||
fen.read('rnbqkbnr/ppppppp1/8/8/8/6Pp/8/5P2 w kq - 0 1'),
|
||||
'black',
|
||||
['g1', 'g3'],
|
||||
expectedPremoves,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('prod bug report lichess-org/lila#18224', () => {
|
||||
const expectedPremoves = new Map<cg.Key, Set<cg.Key>>([
|
||||
['a8', new Set(['a7', 'a6', 'a5', 'a4', 'a3', 'a2', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8'])],
|
||||
['f2', new Set(['f3', 'g3'])],
|
||||
['g2', new Set(['h1', 'g1', 'f1', 'h2', 'h3', 'g3', 'f3'])],
|
||||
]);
|
||||
testPosition(
|
||||
fen.read('R7/6k1/8/8/5pp1/8/p4PK1/r7 b - - 0 56'),
|
||||
'black',
|
||||
['h2', 'g2'],
|
||||
expectedPremoves,
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test('promotion premove allowed', () => {
|
||||
const expectedPremoves = new Map<cg.Key, Set<cg.Key>>([
|
||||
['c7', new Set(['c8', 'b8', 'd8'])],
|
||||
['e7', new Set(['d8', 'e8', 'f8'])],
|
||||
['g7', new Set(['f8'])],
|
||||
['g8', new Set(['f8', 'e8', 'd8', 'c8', 'b8', 'a8'])],
|
||||
['h7', new Set(['g8'])],
|
||||
['a7', new Set(['a8', 'b8'])],
|
||||
['c1', new Set(['b1', 'b2', 'c2', 'd2', 'd1'])],
|
||||
]);
|
||||
testPosition(
|
||||
fen.read('n3r1RB/PkP1P1PP/8/8/8/8/8/2K5 b - - 0 1'),
|
||||
'black',
|
||||
undefined,
|
||||
expectedPremoves,
|
||||
true,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user