@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/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 = Array.prototype.concat(...cg.files.map(c => cg.ranks.map(r => c + r)));
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 ((_a = e.targetTouches) === null || _a === void 0 ? void 0 : _a[0])
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
- const xd = diff(x1, x2);
92
- const yd = diff(y1, y2);
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
- /** Return all board squares between (x1, y1) and (x2, y2) exclusive,
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
- // Determine step direction
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,KAAK,CAAC,SAAS,CAAC,MAAM,CAC9D,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAC/C,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAW,EAAU,EAAE,CAAC,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAE7E,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;AAE3F,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,GAAuB,EAAwB,EAAE;IACzE,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAW,CAAC,CAAC;IACvD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAa,CAAC;AACxD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAsB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAE9D,MAAM,UAAU,IAAI,CAAI,CAAU;IAChC,IAAI,CAAgB,CAAC;IACrB,MAAM,GAAG,GAAG,GAAM,EAAE;QAClB,IAAI,CAAC,KAAK,SAAS;YAAE,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IACF,GAAG,CAAC,KAAK,GAAG,GAAG,EAAE;QACf,CAAC,GAAG,SAAS,CAAC;IAChB,CAAC,CAAC;IACF,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG,GAAa,EAAE;IAClC,IAAI,OAA2B,CAAC;IAChC,OAAO;QACL,KAAK;YACH,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAC9B,CAAC;QACD,MAAM;YACJ,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;QACD,IAAI;YACF,IAAI,CAAC,OAAO;gBAAE,OAAO,CAAC,CAAC;YACvB,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;YACzC,OAAO,GAAG,SAAS,CAAC;YACpB,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAW,EAAY,EAAE,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAEvF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,IAAY,EAAU,EAAE;IAC/D,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,EAC1B,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACzB,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAC3B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,EAAY,EAAE,EAAY,EAAW,EAAE,CAC/D,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,KAAK,CAAC;AAE/C,MAAM,CAAC,MAAM,cAAc,GACzB,CAAC,MAAuB,EAAsD,EAAE,CAChF,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC;IAChB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;IACpD,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;CACtD,CAAC;AAEJ,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,EAAe,EAAE,GAAkB,EAAQ,EAAE;IACrE,EAAE,CAAC,KAAK,CAAC,SAAS,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;AAC5D,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,EAAe,EAAE,GAAkB,EAAE,KAAK,GAAG,CAAC,EAAQ,EAAE;IACxF,EAAE,CAAC,KAAK,CAAC,SAAS,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,aAAa,KAAK,GAAG,CAAC;AAC5E,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EAAe,EAAE,CAAU,EAAQ,EAAE;IAC9D,EAAE,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;AACjD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAgB,EAA6B,EAAE;;IAC3E,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAQ,CAAC,CAAC;IACjE,IAAI,MAAA,CAAC,CAAC,aAAa,0CAAG,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC1F,OAAO,CAAC,4BAA4B;AACtC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAgB,EAAW,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AAE3E,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAE,SAAkB,EAAe,EAAE;IAC3E,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,SAAS;QAAE,EAAE,CAAC,SAAS,GAAG,SAAS,CAAC;IACxC,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,GAAW,EAAE,OAAgB,EAAE,MAAuB;IACxF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACzB,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACpB,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IACD,OAAO;QACL,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,GAAG,EAAE;QAC7D,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,EAAE;KACrE,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAEtE,MAAM,CAAC,MAAM,SAAS,GAAwB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;IAC/D,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACxB,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACxB,OAAO,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAC1D,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAwB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAEvF,MAAM,CAAC,MAAM,SAAS,GAAwB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;AAEhG,MAAM,CAAC,MAAM,QAAQ,GAAwB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAC9D,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AAEvD;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,EAAU,EAAE,EAAU,EAAE,EAAU,EAAE,EAAU,EAAY,EAAE;IACzF,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACnB,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAEnB,sCAAsC;IACtC,IAAI,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAAE,OAAO,EAAE,CAAC;IAEzD,2BAA2B;IAC3B,MAAM,KAAK,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAChB,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;IACjB,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;QAC5B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC,IAAI,KAAK,CAAC;QACX,CAAC,IAAI,KAAK,CAAC;IACb,CAAC;IACD,OAAO,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;AACxC,CAAC,CAAC"}
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.1",
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
- premoveThroughFriendlies?: boolean; // if falsy, the positions of friendly pieces will be used to trim premove options
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 Mobility = (x1: number, y1: number, x2: number, y2: number) => boolean;
6
-
7
- const squaresFriendlyPiecesBetween = (
8
- x1: number,
9
- y1: number,
10
- x2: number,
11
- y2: number,
12
- pieces: cg.Pieces,
13
- color: cg.Color,
14
- ): cg.Key[] => util.squaresBetween(x1, y1, x2, y2).filter(sq => pieces.get(sq)?.color === color);
15
-
16
- const noFriendliesBetweenOrJustOneEnPassantTarget = (
17
- x1: number,
18
- y1: number,
19
- x2: number,
20
- y2: number,
21
- pieces: cg.Pieces,
22
- color: cg.Color,
23
- lastMove: cg.Key[] | undefined,
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
- const squaresFriendliesBetween = squaresFriendlyPiecesBetween(x1, y1, x2, y2, pieces, color);
26
- if (!squaresFriendliesBetween.length) return true;
27
- if (squaresFriendliesBetween.length > 1 || !lastMove || squaresFriendliesBetween[0] !== lastMove[1])
28
- return false;
29
- const destKey = lastMove[1],
30
- srcPos = util.key2pos(lastMove[0]),
31
- destPos = util.key2pos(destKey),
32
- piece = pieces.get(destKey)!;
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
- piece.role === 'pawn' &&
35
- util.diff(srcPos[1], destPos[1]) === 2 &&
36
- [1, -1].some(delta => {
37
- const enemyPiece = pieces.get(util.pos2key([destPos[0] + delta, destPos[1]]));
38
- return enemyPiece?.role === 'pawn' && enemyPiece.color === util.opposite(piece.color);
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 pawn =
44
- (pieces: cg.Pieces, color: cg.Color, premoveThroughFriendlies: boolean): Mobility =>
45
- (x1, y1, x2, y2) => {
46
- const step = color === 'white' ? 1 : -1;
47
- if (util.diff(x1, x2) === 1) return y2 === y1 + step;
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
- x1 === x2 &&
50
- (y2 === y1 + step ||
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 files;
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
- premoveThroughFriendlies = !!state.premovable.premoveThroughFriendlies;
206
+ unrestrictedPremoves = !!state.premovable.unrestrictedPremoves;
132
207
  const piece = pieces.get(key);
133
- if (!piece) return [];
134
- const pos = util.key2pos(key),
135
- r = piece.role,
136
- mobility: Mobility =
137
- r === 'pawn'
138
- ? pawn(pieces, piece.color, premoveThroughFriendlies)
139
- : r === 'knight'
140
- ? knight
141
- : r === 'bishop'
142
- ? bishop(pieces, piece.color, premoveThroughFriendlies, state.lastMove)
143
- : r === 'rook'
144
- ? rook(pieces, piece.color, premoveThroughFriendlies, state.lastMove)
145
- : r === 'queen'
146
- ? queen(pieces, piece.color, premoveThroughFriendlies, state.lastMove)
147
- : king(
148
- pieces,
149
- piece.color,
150
- premoveThroughFriendlies,
151
- rookFilesOf(pieces, piece.color),
152
- canCastle,
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
- premoveThroughFriendlies?: boolean; // if falsy, the positions of friendly pieces will be used to trim premove options
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