@lichess-org/chessground 9.3.1 → 9.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/autoPieces.js +5 -7
- package/dist/autoPieces.js.map +1 -1
- package/dist/board.js +9 -14
- package/dist/board.js.map +1 -1
- package/dist/chessground.min.js +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +3 -4
- package/dist/config.js.map +1 -1
- package/dist/drag.js +1 -2
- package/dist/drag.js.map +1 -1
- package/dist/draw.js +1 -2
- package/dist/draw.js.map +1 -1
- package/dist/premove.js +138 -66
- package/dist/premove.js.map +1 -1
- package/dist/render.js +4 -6
- package/dist/render.js.map +1 -1
- package/dist/state.d.ts +1 -1
- package/dist/svg.js +8 -15
- package/dist/svg.js.map +1 -1
- package/dist/util.d.ts +7 -1
- package/dist/util.js +32 -18
- package/dist/util.js.map +1 -1
- package/package.json +3 -2
- package/src/board.ts +2 -0
- package/src/config.ts +1 -1
- package/src/premove.ts +211 -138
- package/src/state.ts +1 -1
- package/src/util.ts +41 -20
package/dist/util.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as cg from './types.js';
|
|
2
2
|
export const invRanks = [...cg.ranks].reverse();
|
|
3
|
-
export const allKeys =
|
|
3
|
+
export const allKeys = cg.files.flatMap(f => cg.ranks.map(r => (f + r)));
|
|
4
4
|
export const pos2key = (pos) => allKeys[8 * pos[0] + pos[1]];
|
|
5
5
|
export const key2pos = (k) => [k.charCodeAt(0) - 97, k.charCodeAt(1) - 49];
|
|
6
6
|
export const uciToMove = (uci) => {
|
|
@@ -42,11 +42,9 @@ export const timer = () => {
|
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
44
|
export const opposite = (c) => (c === 'white' ? 'black' : 'white');
|
|
45
|
-
export const distanceSq = (pos1, pos2) =>
|
|
46
|
-
const dx = pos1[0] - pos2[0], dy = pos1[1] - pos2[1];
|
|
47
|
-
return dx * dx + dy * dy;
|
|
48
|
-
};
|
|
45
|
+
export const distanceSq = (pos1, pos2) => (pos1[0] - pos2[0]) ** 2 + (pos1[1] - pos2[1]) ** 2;
|
|
49
46
|
export const samePiece = (p1, p2) => p1.role === p2.role && p1.color === p2.color;
|
|
47
|
+
export const samePos = (p1, p2) => p1[0] === p2[0] && p1[1] === p2[1];
|
|
50
48
|
export const posToTranslate = (bounds) => (pos, asWhite) => [
|
|
51
49
|
((asWhite ? pos[0] : 7 - pos[0]) * bounds.width) / 8,
|
|
52
50
|
((asWhite ? 7 - pos[1] : pos[1]) * bounds.height) / 8,
|
|
@@ -61,10 +59,9 @@ export const setVisible = (el, v) => {
|
|
|
61
59
|
el.style.visibility = v ? 'visible' : 'hidden';
|
|
62
60
|
};
|
|
63
61
|
export const eventPosition = (e) => {
|
|
64
|
-
var _a;
|
|
65
62
|
if (e.clientX || e.clientX === 0)
|
|
66
63
|
return [e.clientX, e.clientY];
|
|
67
|
-
if (
|
|
64
|
+
if (e.targetTouches?.[0])
|
|
68
65
|
return [e.targetTouches[0].clientX, e.targetTouches[0].clientY];
|
|
69
66
|
return; // touchend has no position!
|
|
70
67
|
};
|
|
@@ -87,15 +84,20 @@ export function computeSquareCenter(key, asWhite, bounds) {
|
|
|
87
84
|
];
|
|
88
85
|
}
|
|
89
86
|
export const diff = (a, b) => Math.abs(a - b);
|
|
90
|
-
export const knightDir = (x1, y1, x2, y2) =>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return (xd === 1 && yd === 2) || (xd === 2 && yd === 1);
|
|
94
|
-
};
|
|
95
|
-
export const rookDir = (x1, y1, x2, y2) => x1 === x2 || y1 === y2;
|
|
96
|
-
export const bishopDir = (x1, y1, x2, y2) => diff(x1, x2) === diff(y1, y2);
|
|
87
|
+
export const knightDir = (x1, y1, x2, y2) => diff(x1, x2) * diff(y1, y2) === 2;
|
|
88
|
+
export const rookDir = (x1, y1, x2, y2) => (x1 === x2) !== (y1 === y2);
|
|
89
|
+
export const bishopDir = (x1, y1, x2, y2) => diff(x1, x2) === diff(y1, y2) && x1 !== x2;
|
|
97
90
|
export const queenDir = (x1, y1, x2, y2) => rookDir(x1, y1, x2, y2) || bishopDir(x1, y1, x2, y2);
|
|
98
|
-
|
|
91
|
+
export const kingDirNonCastling = (x1, y1, x2, y2) => Math.max(diff(x1, x2), diff(y1, y2)) === 1;
|
|
92
|
+
export const pawnDirCapture = (x1, y1, x2, y2, isDirectionUp) => diff(x1, x2) === 1 && y2 === y1 + (isDirectionUp ? 1 : -1);
|
|
93
|
+
export const pawnDirAdvance = (x1, y1, x2, y2, isDirectionUp) => {
|
|
94
|
+
const step = isDirectionUp ? 1 : -1;
|
|
95
|
+
return (x1 === x2 &&
|
|
96
|
+
(y2 === y1 + step ||
|
|
97
|
+
// allow 2 squares from first two ranks, for horde
|
|
98
|
+
(y2 === y1 + 2 * step && (isDirectionUp ? y1 <= 1 : y1 >= 6))));
|
|
99
|
+
};
|
|
100
|
+
/** Returns all board squares between (x1, y1) and (x2, y2) exclusive,
|
|
99
101
|
* along a straight line (rook or bishop path). Returns [] if not aligned, or none between.
|
|
100
102
|
*/
|
|
101
103
|
export const squaresBetween = (x1, y1, x2, y2) => {
|
|
@@ -104,9 +106,7 @@ export const squaresBetween = (x1, y1, x2, y2) => {
|
|
|
104
106
|
// Must be a straight or diagonal line
|
|
105
107
|
if (dx && dy && Math.abs(dx) !== Math.abs(dy))
|
|
106
108
|
return [];
|
|
107
|
-
|
|
108
|
-
const stepX = dx === 0 ? 0 : dx > 0 ? 1 : -1;
|
|
109
|
-
const stepY = dy === 0 ? 0 : dy > 0 ? 1 : -1;
|
|
109
|
+
const stepX = Math.sign(dx), stepY = Math.sign(dy);
|
|
110
110
|
const squares = [];
|
|
111
111
|
let x = x1 + stepX, y = y1 + stepY;
|
|
112
112
|
while (x !== x2 || y !== y2) {
|
|
@@ -116,4 +116,18 @@ export const squaresBetween = (x1, y1, x2, y2) => {
|
|
|
116
116
|
}
|
|
117
117
|
return squares.map(sq => pos2key(sq));
|
|
118
118
|
};
|
|
119
|
+
export const adjacentSquares = (square) => {
|
|
120
|
+
const pos = key2pos(square);
|
|
121
|
+
const adjacentSquares = [];
|
|
122
|
+
if (pos[0] > 0)
|
|
123
|
+
adjacentSquares.push([pos[0] - 1, pos[1]]);
|
|
124
|
+
if (pos[0] < 7)
|
|
125
|
+
adjacentSquares.push([pos[0] + 1, pos[1]]);
|
|
126
|
+
return adjacentSquares.map(pos2key);
|
|
127
|
+
};
|
|
128
|
+
export const squareShiftedVertically = (square, delta) => {
|
|
129
|
+
const pos = key2pos(square);
|
|
130
|
+
pos[1] += delta;
|
|
131
|
+
return pos2key(pos);
|
|
132
|
+
};
|
|
119
133
|
//# sourceMappingURL=util.js.map
|
package/dist/util.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,MAAM,CAAC,MAAM,QAAQ,GAAuB,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;AAEpE,MAAM,CAAC,MAAM,OAAO,GAAsB,
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lichess-org/chessground",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.4.1",
|
|
4
4
|
"//": "version is managed by the build system",
|
|
5
5
|
"description": "lichess.org chess ui",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"check-format": "prettier --check .",
|
|
37
37
|
"bundle": "esbuild src/chessground.ts --bundle --format=esm --outfile=dist/chessground.min.js --minify",
|
|
38
38
|
"dist": "$npm_execpath run compile && $npm_execpath run bundle",
|
|
39
|
-
"test": "vitest"
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest"
|
|
40
41
|
},
|
|
41
42
|
"files": [
|
|
42
43
|
"/dist/*.js",
|
package/src/board.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
computeSquareCenter,
|
|
9
9
|
queenDir,
|
|
10
10
|
knightDir,
|
|
11
|
+
samePos,
|
|
11
12
|
} from './util.js';
|
|
12
13
|
import { premove } from './premove.js';
|
|
13
14
|
import * as cg from './types.js';
|
|
@@ -362,6 +363,7 @@ export function getSnappedKeyAtDomPos(
|
|
|
362
363
|
const origPos = key2pos(orig);
|
|
363
364
|
const validSnapPos = allPos.filter(
|
|
364
365
|
pos2 =>
|
|
366
|
+
samePos(origPos, pos2) ||
|
|
365
367
|
queenDir(origPos[0], origPos[1], pos2[0], pos2[1]) ||
|
|
366
368
|
knightDir(origPos[0], origPos[1], pos2[0], pos2[1]),
|
|
367
369
|
);
|
package/src/config.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface Config {
|
|
|
47
47
|
castle?: boolean; // whether to allow king castle premoves
|
|
48
48
|
dests?: cg.Key[]; // premove destinations for the current selection
|
|
49
49
|
customDests?: cg.Dests; // use custom valid premoves. {"a2" ["a3" "a4"] "b1" ["a3" "c3"]}
|
|
50
|
-
|
|
50
|
+
unrestrictedPremoves?: boolean; // if falsy, the positions of friendly pieces will be used to trim premove options
|
|
51
51
|
events?: {
|
|
52
52
|
set?: (orig: cg.Key, dest: cg.Key, metadata?: cg.SetPremoveMetadata) => void; // called after the premove has been set
|
|
53
53
|
unset?: () => void; // called after the premove has been unset
|
package/src/premove.ts
CHANGED
|
@@ -2,156 +2,229 @@ import * as util from './util.js';
|
|
|
2
2
|
import * as cg from './types.js';
|
|
3
3
|
import { HeadlessState } from './state.js';
|
|
4
4
|
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
5
|
+
type MobilityContext = {
|
|
6
|
+
pos1: cg.Pos;
|
|
7
|
+
pos2: cg.Pos;
|
|
8
|
+
allPieces: cg.Pieces;
|
|
9
|
+
friendlies: cg.Pieces;
|
|
10
|
+
enemies: cg.Pieces;
|
|
11
|
+
unrestrictedPremoves: boolean;
|
|
12
|
+
color: cg.Color;
|
|
13
|
+
canCastle: boolean;
|
|
14
|
+
rookFilesFriendlies: number[];
|
|
15
|
+
lastMove: cg.Key[] | undefined;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type Mobility = (ctx: MobilityContext) => boolean;
|
|
19
|
+
|
|
20
|
+
const isDestOccupiedByFriendly = (ctx: MobilityContext): boolean =>
|
|
21
|
+
ctx.friendlies.has(util.pos2key(ctx.pos2));
|
|
22
|
+
|
|
23
|
+
const isDestOccupiedByEnemy = (ctx: MobilityContext): boolean => ctx.enemies.has(util.pos2key(ctx.pos2));
|
|
24
|
+
|
|
25
|
+
const anyPieceBetween = (pos1: cg.Pos, pos2: cg.Pos, pieces: cg.Pieces): boolean =>
|
|
26
|
+
util.squaresBetween(...pos1, ...pos2).some(s => pieces.has(s));
|
|
27
|
+
|
|
28
|
+
const canEnemyPawnAdvanceToSquare = (pawnStart: cg.Key, dest: cg.Key, ctx: MobilityContext): boolean => {
|
|
29
|
+
const piece = ctx.enemies.get(pawnStart);
|
|
30
|
+
if (piece?.role !== 'pawn') return false;
|
|
31
|
+
const step = piece.color === 'white' ? 1 : -1;
|
|
32
|
+
const startPos = util.key2pos(pawnStart);
|
|
33
|
+
const destPos = util.key2pos(dest);
|
|
34
|
+
return (
|
|
35
|
+
util.pawnDirAdvance(...startPos, ...destPos, piece.color === 'white') &&
|
|
36
|
+
!anyPieceBetween(startPos, [destPos[0], destPos[1] + step], ctx.allPieces)
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const canEnemyPawnCaptureOnSquare = (pawnStart: cg.Key, dest: cg.Key, ctx: MobilityContext): boolean => {
|
|
41
|
+
const enemyPawn = ctx.enemies.get(pawnStart);
|
|
42
|
+
return (
|
|
43
|
+
enemyPawn?.role === 'pawn' &&
|
|
44
|
+
util.pawnDirCapture(...util.key2pos(pawnStart), ...util.key2pos(dest), enemyPawn.color === 'white') &&
|
|
45
|
+
(ctx.friendlies.has(dest) ||
|
|
46
|
+
canBeCapturedBySomeEnemyEnPassant(
|
|
47
|
+
util.squareShiftedVertically(dest, enemyPawn.color === 'white' ? -1 : 1),
|
|
48
|
+
ctx.friendlies,
|
|
49
|
+
ctx.enemies,
|
|
50
|
+
ctx.lastMove,
|
|
51
|
+
))
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const canSomeEnemyPawnAdvanceToDest = (ctx: MobilityContext): boolean =>
|
|
56
|
+
[...ctx.enemies.keys()].some(key => canEnemyPawnAdvanceToSquare(key, util.pos2key(ctx.pos2), ctx));
|
|
57
|
+
|
|
58
|
+
const isDestControlledByEnemy = (ctx: MobilityContext, pieceRolesExclude?: cg.Role[]): boolean => {
|
|
59
|
+
const square: cg.Pos = ctx.pos2;
|
|
60
|
+
return [...ctx.enemies].some(([key, piece]) => {
|
|
61
|
+
const piecePos = util.key2pos(key);
|
|
62
|
+
return (
|
|
63
|
+
!pieceRolesExclude?.includes(piece.role) &&
|
|
64
|
+
((piece.role === 'pawn' && util.pawnDirCapture(...piecePos, ...square, piece.color === 'white')) ||
|
|
65
|
+
(piece.role === 'knight' && util.knightDir(...piecePos, ...square)) ||
|
|
66
|
+
(piece.role === 'bishop' && util.bishopDir(...piecePos, ...square)) ||
|
|
67
|
+
(piece.role === 'rook' && util.rookDir(...piecePos, ...square)) ||
|
|
68
|
+
(piece.role === 'queen' && util.queenDir(...piecePos, ...square)) ||
|
|
69
|
+
(piece.role === 'king' && util.kingDirNonCastling(...piecePos, ...square))) &&
|
|
70
|
+
(!['bishop', 'rook', 'queen'].includes(piece.role) || !anyPieceBetween(piecePos, square, ctx.allPieces))
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const isFriendlyOnDestAndAttacked = (ctx: MobilityContext): boolean =>
|
|
76
|
+
isDestOccupiedByFriendly(ctx) &&
|
|
77
|
+
(canBeCapturedBySomeEnemyEnPassant(util.pos2key(ctx.pos2), ctx.friendlies, ctx.enemies, ctx.lastMove) ||
|
|
78
|
+
isDestControlledByEnemy(ctx));
|
|
79
|
+
|
|
80
|
+
const canBeCapturedBySomeEnemyEnPassant = (
|
|
81
|
+
potentialSquareOfFriendlyPawn: cg.Key,
|
|
82
|
+
friendlies: cg.Pieces,
|
|
83
|
+
enemies: cg.Pieces,
|
|
84
|
+
lastMove?: cg.Key[],
|
|
24
85
|
): boolean => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
86
|
+
if (lastMove && potentialSquareOfFriendlyPawn !== lastMove[1]) return false;
|
|
87
|
+
const pos = util.key2pos(potentialSquareOfFriendlyPawn);
|
|
88
|
+
const friendly = friendlies.get(potentialSquareOfFriendlyPawn);
|
|
89
|
+
return (
|
|
90
|
+
friendly?.role === 'pawn' &&
|
|
91
|
+
pos[1] === (friendly.color === 'white' ? 3 : 4) &&
|
|
92
|
+
(!lastMove || util.diff(util.key2pos(lastMove[0])[1], pos[1]) === 2) &&
|
|
93
|
+
[1, -1].some(delta => enemies.get(util.pos2key([pos[0] + delta, pos[1]]))?.role === 'pawn')
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const isPathClearEnoughOfFriendliesForPremove = (ctx: MobilityContext): boolean => {
|
|
98
|
+
if (ctx.unrestrictedPremoves) return true;
|
|
99
|
+
const squaresBetween = util.squaresBetween(...ctx.pos1, ...ctx.pos2);
|
|
100
|
+
const squaresOfFriendliesBetween = squaresBetween.filter(s => ctx.friendlies.has(s));
|
|
33
101
|
return (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
102
|
+
!squaresOfFriendliesBetween.length ||
|
|
103
|
+
(squaresOfFriendliesBetween.length === 1 &&
|
|
104
|
+
canBeCapturedBySomeEnemyEnPassant(
|
|
105
|
+
squaresOfFriendliesBetween[0],
|
|
106
|
+
ctx.friendlies,
|
|
107
|
+
ctx.enemies,
|
|
108
|
+
ctx.lastMove,
|
|
109
|
+
) &&
|
|
110
|
+
!squaresBetween.includes(
|
|
111
|
+
util.squareShiftedVertically(squaresOfFriendliesBetween[0], ctx.color === 'white' ? -1 : 1),
|
|
112
|
+
))
|
|
40
113
|
);
|
|
41
114
|
};
|
|
42
115
|
|
|
43
|
-
const
|
|
44
|
-
(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
116
|
+
const isPathClearEnoughOfEnemiesForPremove = (ctx: MobilityContext): boolean => {
|
|
117
|
+
if (ctx.unrestrictedPremoves) return true;
|
|
118
|
+
const squaresBetween = util.squaresBetween(...ctx.pos1, ...ctx.pos2);
|
|
119
|
+
const squaresOfEnemiesBetween = squaresBetween.filter(s => ctx.enemies.has(s));
|
|
120
|
+
if (squaresOfEnemiesBetween.length > 1) return false;
|
|
121
|
+
if (!squaresOfEnemiesBetween.length) return true;
|
|
122
|
+
const enemySquare = squaresOfEnemiesBetween[0];
|
|
123
|
+
const enemy = ctx.enemies.get(enemySquare);
|
|
124
|
+
if (!enemy || enemy.role !== 'pawn') return true;
|
|
125
|
+
|
|
126
|
+
const enemyStep = enemy.color === 'white' ? 1 : -1;
|
|
127
|
+
const squareAbove = util.squareShiftedVertically(enemySquare, enemyStep);
|
|
128
|
+
const enemyPawnDests: cg.Key[] = [
|
|
129
|
+
...util.adjacentSquares(squareAbove).filter(s => canEnemyPawnCaptureOnSquare(enemySquare, s, ctx)),
|
|
130
|
+
...[squareAbove, util.squareShiftedVertically(squareAbove, enemyStep)].filter(s =>
|
|
131
|
+
canEnemyPawnAdvanceToSquare(enemySquare, s, ctx),
|
|
132
|
+
),
|
|
133
|
+
];
|
|
134
|
+
const badSquares = [...squaresBetween, util.pos2key(ctx.pos1)];
|
|
135
|
+
return enemyPawnDests.some(square => !badSquares.includes(square));
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const isPathClearEnoughForPremove = (ctx: MobilityContext): boolean =>
|
|
139
|
+
isPathClearEnoughOfFriendliesForPremove(ctx) && isPathClearEnoughOfEnemiesForPremove(ctx);
|
|
140
|
+
|
|
141
|
+
const pawn: Mobility = (ctx: MobilityContext) => {
|
|
142
|
+
const step = ctx.color === 'white' ? 1 : -1;
|
|
143
|
+
if (util.diff(ctx.pos1[0], ctx.pos2[0]) > 1) return false;
|
|
144
|
+
if (!util.diff(ctx.pos1[0], ctx.pos2[0])) {
|
|
48
145
|
return (
|
|
49
|
-
|
|
50
|
-
(
|
|
51
|
-
// allow 2 squares from first two ranks, for horde
|
|
52
|
-
(y2 === y1 + 2 * step && (color === 'white' ? y1 <= 1 : y1 >= 6))) &&
|
|
53
|
-
(premoveThroughFriendlies || !squaresFriendlyPiecesBetween(x1, y1, x2, y2 + step, pieces, color).length)
|
|
146
|
+
util.pawnDirAdvance(...ctx.pos1, ...ctx.pos2, ctx.color === 'white') &&
|
|
147
|
+
isPathClearEnoughForPremove({ ...ctx, pos2: [ctx.pos2[0], ctx.pos2[1] + step] })
|
|
54
148
|
);
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const knight: Mobility = (x1, y1, x2, y2) => util.knightDir(x1, y1, x2, y2);
|
|
58
|
-
|
|
59
|
-
const bishop =
|
|
60
|
-
(
|
|
61
|
-
pieces: cg.Pieces,
|
|
62
|
-
color: cg.Color,
|
|
63
|
-
premoveThroughFriendlies: boolean,
|
|
64
|
-
lastMove: cg.Key[] | undefined,
|
|
65
|
-
): Mobility =>
|
|
66
|
-
(x1, y1, x2, y2) =>
|
|
67
|
-
util.bishopDir(x1, y1, x2, y2) &&
|
|
68
|
-
(premoveThroughFriendlies ||
|
|
69
|
-
noFriendliesBetweenOrJustOneEnPassantTarget(x1, y1, x2, y2, pieces, color, lastMove));
|
|
70
|
-
|
|
71
|
-
const rook =
|
|
72
|
-
(
|
|
73
|
-
pieces: cg.Pieces,
|
|
74
|
-
color: cg.Color,
|
|
75
|
-
premoveThroughFriendlies: boolean,
|
|
76
|
-
lastMove: cg.Key[] | undefined,
|
|
77
|
-
): Mobility =>
|
|
78
|
-
(x1, y1, x2, y2) =>
|
|
79
|
-
util.rookDir(x1, y1, x2, y2) &&
|
|
80
|
-
(premoveThroughFriendlies ||
|
|
81
|
-
noFriendliesBetweenOrJustOneEnPassantTarget(x1, y1, x2, y2, pieces, color, lastMove));
|
|
82
|
-
|
|
83
|
-
const queen =
|
|
84
|
-
(
|
|
85
|
-
pieces: cg.Pieces,
|
|
86
|
-
color: cg.Color,
|
|
87
|
-
premoveThroughFriendlies: boolean,
|
|
88
|
-
lastMove: cg.Key[] | undefined,
|
|
89
|
-
): Mobility =>
|
|
90
|
-
(x1, y1, x2, y2) =>
|
|
91
|
-
bishop(pieces, color, premoveThroughFriendlies, lastMove)(x1, y1, x2, y2) ||
|
|
92
|
-
rook(pieces, color, premoveThroughFriendlies, lastMove)(x1, y1, x2, y2);
|
|
93
|
-
|
|
94
|
-
const king =
|
|
95
|
-
(
|
|
96
|
-
pieces: cg.Pieces,
|
|
97
|
-
color: cg.Color,
|
|
98
|
-
premoveThroughFriendlies: boolean,
|
|
99
|
-
rookFiles: number[],
|
|
100
|
-
canCastle: boolean,
|
|
101
|
-
): Mobility =>
|
|
102
|
-
(x1, y1, x2, y2) =>
|
|
103
|
-
(util.diff(x1, x2) < 2 && util.diff(y1, y2) < 2) ||
|
|
104
|
-
(canCastle &&
|
|
105
|
-
y1 === y2 &&
|
|
106
|
-
y1 === (color === 'white' ? 0 : 7) &&
|
|
107
|
-
((x1 === 4 && ((x2 === 2 && rookFiles.includes(0)) || (x2 === 6 && rookFiles.includes(7)))) ||
|
|
108
|
-
rookFiles.includes(x2)) &&
|
|
109
|
-
(premoveThroughFriendlies ||
|
|
110
|
-
/* The following checks if no non-rook friendly piece is in the way between the king and its castling destination.
|
|
111
|
-
Note that for the Chess960 edge case of Kb1 "long castling", the check passes even if there is a piece in the way
|
|
112
|
-
on c1. But this is fine, since the king can always premove recapturing Ra1. */
|
|
113
|
-
squaresFriendlyPiecesBetween(x1, y1, x2 > x1 ? 7 : 1, y2, pieces, color).every(
|
|
114
|
-
s => pieces.get(s)!.role === 'rook',
|
|
115
|
-
)));
|
|
116
|
-
|
|
117
|
-
const rookFilesOf = (pieces: cg.Pieces, color: cg.Color) => {
|
|
118
|
-
const backrank = color === 'white' ? '1' : '8';
|
|
119
|
-
const files = [];
|
|
120
|
-
for (const [key, piece] of pieces) {
|
|
121
|
-
if (key[1] === backrank && piece.color === color && piece.role === 'rook') {
|
|
122
|
-
files.push(util.key2pos(key)[0]);
|
|
123
|
-
}
|
|
124
149
|
}
|
|
125
|
-
return
|
|
150
|
+
if (ctx.pos2[1] !== ctx.pos1[1] + step) return false;
|
|
151
|
+
if (ctx.unrestrictedPremoves || isDestOccupiedByEnemy(ctx)) return true;
|
|
152
|
+
if (isDestOccupiedByFriendly(ctx)) return isDestControlledByEnemy(ctx);
|
|
153
|
+
else
|
|
154
|
+
return (
|
|
155
|
+
canSomeEnemyPawnAdvanceToDest(ctx) ||
|
|
156
|
+
canBeCapturedBySomeEnemyEnPassant(
|
|
157
|
+
util.pos2key([ctx.pos2[0], ctx.pos2[1] + step]),
|
|
158
|
+
ctx.friendlies,
|
|
159
|
+
ctx.enemies,
|
|
160
|
+
ctx.lastMove,
|
|
161
|
+
) ||
|
|
162
|
+
isDestControlledByEnemy(ctx, ['pawn'])
|
|
163
|
+
);
|
|
126
164
|
};
|
|
127
165
|
|
|
166
|
+
const knight: Mobility = (ctx: MobilityContext) =>
|
|
167
|
+
util.knightDir(...ctx.pos1, ...ctx.pos2) &&
|
|
168
|
+
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx));
|
|
169
|
+
|
|
170
|
+
const bishop: Mobility = (ctx: MobilityContext) =>
|
|
171
|
+
util.bishopDir(...ctx.pos1, ...ctx.pos2) &&
|
|
172
|
+
isPathClearEnoughForPremove(ctx) &&
|
|
173
|
+
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx));
|
|
174
|
+
|
|
175
|
+
const rook: Mobility = (ctx: MobilityContext) =>
|
|
176
|
+
util.rookDir(...ctx.pos1, ...ctx.pos2) &&
|
|
177
|
+
isPathClearEnoughForPremove(ctx) &&
|
|
178
|
+
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx));
|
|
179
|
+
|
|
180
|
+
const queen: Mobility = (ctx: MobilityContext) => bishop(ctx) || rook(ctx);
|
|
181
|
+
|
|
182
|
+
const king: Mobility = (ctx: MobilityContext) =>
|
|
183
|
+
(util.kingDirNonCastling(...ctx.pos1, ...ctx.pos2) &&
|
|
184
|
+
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx))) ||
|
|
185
|
+
(ctx.canCastle &&
|
|
186
|
+
ctx.pos1[1] === ctx.pos2[1] &&
|
|
187
|
+
ctx.pos1[1] === (ctx.color === 'white' ? 0 : 7) &&
|
|
188
|
+
((ctx.pos1[0] === 4 &&
|
|
189
|
+
((ctx.pos2[0] === 2 && ctx.rookFilesFriendlies.includes(0)) ||
|
|
190
|
+
(ctx.pos2[0] === 6 && ctx.rookFilesFriendlies.includes(7)))) ||
|
|
191
|
+
ctx.rookFilesFriendlies.includes(ctx.pos2[0])) &&
|
|
192
|
+
(ctx.unrestrictedPremoves ||
|
|
193
|
+
/* The following checks if no non-rook friendly piece is in the way between the king and its castling destination.
|
|
194
|
+
Note that for the Chess960 edge case of Kb1 "long castling", the check passes even if there is a piece in the way
|
|
195
|
+
on c1. But this is fine, since premoving from b1 to a1 as a normal move would have already returned true. */
|
|
196
|
+
util
|
|
197
|
+
.squaresBetween(...ctx.pos1, ctx.pos2[0] > ctx.pos1[0] ? 7 : 1, ctx.pos2[1])
|
|
198
|
+
.map(s => ctx.allPieces.get(s))
|
|
199
|
+
.every(p => !p || util.samePiece(p, { role: 'rook', color: ctx.color }))));
|
|
200
|
+
|
|
201
|
+
const mobilityByRole = { pawn, knight, bishop, rook, queen, king };
|
|
202
|
+
|
|
128
203
|
export function premove(state: HeadlessState, key: cg.Key): cg.Key[] {
|
|
129
204
|
const pieces = state.pieces,
|
|
130
205
|
canCastle = state.premovable.castle,
|
|
131
|
-
|
|
206
|
+
unrestrictedPremoves = !!state.premovable.unrestrictedPremoves;
|
|
132
207
|
const piece = pieces.get(key);
|
|
133
|
-
if (!piece) return [];
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return util.allPos
|
|
155
|
-
.filter(pos2 => (pos[0] !== pos2[0] || pos[1] !== pos2[1]) && mobility(pos[0], pos[1], pos2[0], pos2[1]))
|
|
156
|
-
.map(util.pos2key);
|
|
208
|
+
if (!piece || piece.color === state.turnColor) return [];
|
|
209
|
+
const color = piece.color,
|
|
210
|
+
friendlies = new Map([...pieces].filter(([_, p]) => p.color === color)),
|
|
211
|
+
enemies = new Map([...pieces].filter(([_, p]) => p.color === util.opposite(color))),
|
|
212
|
+
pos = util.key2pos(key),
|
|
213
|
+
mobility: Mobility = mobilityByRole[piece.role],
|
|
214
|
+
ctx = {
|
|
215
|
+
pos1: pos,
|
|
216
|
+
allPieces: pieces,
|
|
217
|
+
friendlies: friendlies,
|
|
218
|
+
enemies: enemies,
|
|
219
|
+
unrestrictedPremoves: unrestrictedPremoves,
|
|
220
|
+
color: color,
|
|
221
|
+
canCastle: canCastle,
|
|
222
|
+
rookFilesFriendlies: Array.from(pieces)
|
|
223
|
+
.filter(
|
|
224
|
+
([k, p]) => k[1] === (color === 'white' ? '1' : '8') && p.color === color && p.role === 'rook',
|
|
225
|
+
)
|
|
226
|
+
.map(([k]) => util.key2pos(k)[0]),
|
|
227
|
+
lastMove: state.lastMove,
|
|
228
|
+
};
|
|
229
|
+
return util.allPos.filter(pos2 => mobility({ ...ctx, pos2 })).map(util.pos2key);
|
|
157
230
|
}
|
package/src/state.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface HeadlessState {
|
|
|
51
51
|
dests?: cg.Key[]; // premove destinations for the current selection
|
|
52
52
|
customDests?: cg.Dests; // use custom valid premoves. {"a2" ["a3" "a4"] "b1" ["a3" "c3"]}
|
|
53
53
|
current?: cg.KeyPair; // keys of the current saved premove ["e2" "e4"]
|
|
54
|
-
|
|
54
|
+
unrestrictedPremoves?: boolean; // if falsy, the positions of friendly pieces will be used to trim premove options
|
|
55
55
|
events: {
|
|
56
56
|
set?: (orig: cg.Key, dest: cg.Key, metadata?: cg.SetPremoveMetadata) => void; // called after the premove has been set
|
|
57
57
|
unset?: () => void; // called after the premove has been unset
|