@lichess-org/chessground 9.7.2 → 9.8.0
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/board.js +8 -8
- package/dist/board.js.map +1 -1
- package/dist/chessground.min.js +1 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.js.map +1 -1
- package/dist/drag.js +8 -2
- package/dist/drag.js.map +1 -1
- package/dist/draw.d.ts +1 -1
- package/dist/fen.js +2 -1
- package/dist/fen.js.map +1 -1
- package/dist/premove.js +61 -45
- package/dist/premove.js.map +1 -1
- package/dist/state.d.ts +2 -0
- package/dist/state.js +2 -1
- package/dist/state.js.map +1 -1
- package/dist/types.d.ts +18 -0
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +4 -2
- package/dist/util.js +5 -3
- package/dist/util.js.map +1 -1
- package/package.json +1 -1
- package/src/board.ts +10 -7
- package/src/config.ts +4 -3
- package/src/drag.ts +10 -2
- package/src/draw.ts +1 -1
- package/src/fen.ts +2 -1
- package/src/premove.ts +74 -75
- package/src/state.ts +4 -1
- package/src/types.ts +20 -0
- package/src/util.ts +9 -4
package/src/premove.ts
CHANGED
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
import * as util from './util.js';
|
|
2
2
|
import * as cg from './types.js';
|
|
3
3
|
import { HeadlessState } from './state.js';
|
|
4
|
+
import { Mobility, MobilityContext } from './types.js';
|
|
4
5
|
|
|
5
|
-
|
|
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));
|
|
6
|
+
const isDestOccupiedByFriendly = (ctx: MobilityContext): boolean => ctx.friendlies.has(ctx.dest.key);
|
|
22
7
|
|
|
23
|
-
const isDestOccupiedByEnemy = (ctx: MobilityContext): boolean => ctx.enemies.has(
|
|
8
|
+
const isDestOccupiedByEnemy = (ctx: MobilityContext): boolean => ctx.enemies.has(ctx.dest.key);
|
|
24
9
|
|
|
25
|
-
const anyPieceBetween = (
|
|
26
|
-
util.squaresBetween(...
|
|
10
|
+
const anyPieceBetween = (orig: cg.Pos, dest: cg.Pos, pieces: cg.Pieces): boolean =>
|
|
11
|
+
util.squaresBetween(...orig, ...dest).some(s => pieces.has(s));
|
|
27
12
|
|
|
28
13
|
const canEnemyPawnAdvanceToSquare = (pawnStart: cg.Key, dest: cg.Key, ctx: MobilityContext): boolean => {
|
|
29
14
|
const piece = ctx.enemies.get(pawnStart);
|
|
@@ -53,10 +38,10 @@ const canEnemyPawnCaptureOnSquare = (pawnStart: cg.Key, dest: cg.Key, ctx: Mobil
|
|
|
53
38
|
};
|
|
54
39
|
|
|
55
40
|
const canSomeEnemyPawnAdvanceToDest = (ctx: MobilityContext): boolean =>
|
|
56
|
-
[...ctx.enemies.keys()].some(key => canEnemyPawnAdvanceToSquare(key,
|
|
41
|
+
[...ctx.enemies.keys()].some(key => canEnemyPawnAdvanceToSquare(key, ctx.dest.key, ctx));
|
|
57
42
|
|
|
58
43
|
const isDestControlledByEnemy = (ctx: MobilityContext, pieceRolesExclude?: cg.Role[]): boolean => {
|
|
59
|
-
const square: cg.Pos = ctx.
|
|
44
|
+
const square: cg.Pos = ctx.dest.pos;
|
|
60
45
|
return [...ctx.enemies].some(([key, piece]) => {
|
|
61
46
|
const piecePos = util.key2pos(key);
|
|
62
47
|
return (
|
|
@@ -74,48 +59,58 @@ const isDestControlledByEnemy = (ctx: MobilityContext, pieceRolesExclude?: cg.Ro
|
|
|
74
59
|
|
|
75
60
|
const isFriendlyOnDestAndAttacked = (ctx: MobilityContext): boolean =>
|
|
76
61
|
isDestOccupiedByFriendly(ctx) &&
|
|
77
|
-
(canBeCapturedBySomeEnemyEnPassant(
|
|
62
|
+
(canBeCapturedBySomeEnemyEnPassant(ctx.dest.key, ctx.friendlies, ctx.enemies, ctx.lastMove) ||
|
|
78
63
|
isDestControlledByEnemy(ctx));
|
|
79
64
|
|
|
80
65
|
const canBeCapturedBySomeEnemyEnPassant = (
|
|
81
|
-
potentialSquareOfFriendlyPawn: cg.Key,
|
|
66
|
+
potentialSquareOfFriendlyPawn: cg.Key | undefined,
|
|
82
67
|
friendlies: cg.Pieces,
|
|
83
68
|
enemies: cg.Pieces,
|
|
84
69
|
lastMove?: cg.Key[],
|
|
85
70
|
): boolean => {
|
|
86
|
-
if (lastMove && potentialSquareOfFriendlyPawn !== lastMove[1])
|
|
71
|
+
if (!potentialSquareOfFriendlyPawn || (lastMove && potentialSquareOfFriendlyPawn !== lastMove[1]))
|
|
72
|
+
return false;
|
|
87
73
|
const pos = util.key2pos(potentialSquareOfFriendlyPawn);
|
|
88
74
|
const friendly = friendlies.get(potentialSquareOfFriendlyPawn);
|
|
89
75
|
return (
|
|
90
76
|
friendly?.role === 'pawn' &&
|
|
91
77
|
pos[1] === (friendly.color === 'white' ? 3 : 4) &&
|
|
92
78
|
(!lastMove || util.diff(util.key2pos(lastMove[0])[1], pos[1]) === 2) &&
|
|
93
|
-
[1, -1].some(delta =>
|
|
79
|
+
[1, -1].some(delta => {
|
|
80
|
+
const k = util.pos2key([pos[0] + delta, pos[1]]);
|
|
81
|
+
return !!k && enemies.get(k)?.role === 'pawn';
|
|
82
|
+
})
|
|
94
83
|
);
|
|
95
84
|
};
|
|
96
85
|
|
|
97
|
-
const isPathClearEnoughOfFriendliesForPremove = (ctx: MobilityContext): boolean => {
|
|
86
|
+
const isPathClearEnoughOfFriendliesForPremove = (ctx: MobilityContext, isPawnAdvance: boolean): boolean => {
|
|
98
87
|
if (ctx.unrestrictedPremoves) return true;
|
|
99
|
-
const squaresBetween = util.squaresBetween(...ctx.
|
|
88
|
+
const squaresBetween = util.squaresBetween(...ctx.orig.pos, ...ctx.dest.pos);
|
|
89
|
+
if (isPawnAdvance) squaresBetween.push(ctx.dest.key);
|
|
100
90
|
const squaresOfFriendliesBetween = squaresBetween.filter(s => ctx.friendlies.has(s));
|
|
91
|
+
if (!squaresOfFriendliesBetween.length) return true;
|
|
92
|
+
const firstSquareOfFriendliesBetween = squaresOfFriendliesBetween[0];
|
|
93
|
+
const nextSquare = util.squareShiftedVertically(
|
|
94
|
+
firstSquareOfFriendliesBetween,
|
|
95
|
+
ctx.color === 'white' ? -1 : 1,
|
|
96
|
+
);
|
|
101
97
|
return (
|
|
102
|
-
|
|
103
|
-
(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
util.squareShiftedVertically(squaresOfFriendliesBetween[0], ctx.color === 'white' ? -1 : 1),
|
|
112
|
-
))
|
|
98
|
+
squaresOfFriendliesBetween.length === 1 &&
|
|
99
|
+
canBeCapturedBySomeEnemyEnPassant(
|
|
100
|
+
firstSquareOfFriendliesBetween,
|
|
101
|
+
ctx.friendlies,
|
|
102
|
+
ctx.enemies,
|
|
103
|
+
ctx.lastMove,
|
|
104
|
+
) &&
|
|
105
|
+
!!nextSquare &&
|
|
106
|
+
!squaresBetween.includes(nextSquare)
|
|
113
107
|
);
|
|
114
108
|
};
|
|
115
109
|
|
|
116
|
-
const isPathClearEnoughOfEnemiesForPremove = (ctx: MobilityContext): boolean => {
|
|
110
|
+
const isPathClearEnoughOfEnemiesForPremove = (ctx: MobilityContext, isPawnAdvance: boolean): boolean => {
|
|
117
111
|
if (ctx.unrestrictedPremoves) return true;
|
|
118
|
-
const squaresBetween = util.squaresBetween(...ctx.
|
|
112
|
+
const squaresBetween = util.squaresBetween(...ctx.orig.pos, ...ctx.dest.pos);
|
|
113
|
+
if (isPawnAdvance) squaresBetween.push(ctx.dest.key);
|
|
119
114
|
const squaresOfEnemiesBetween = squaresBetween.filter(s => ctx.enemies.has(s));
|
|
120
115
|
if (squaresOfEnemiesBetween.length > 1) return false;
|
|
121
116
|
if (!squaresOfEnemiesBetween.length) return true;
|
|
@@ -125,36 +120,38 @@ const isPathClearEnoughOfEnemiesForPremove = (ctx: MobilityContext): boolean =>
|
|
|
125
120
|
|
|
126
121
|
const enemyStep = enemy.color === 'white' ? 1 : -1;
|
|
127
122
|
const squareAbove = util.squareShiftedVertically(enemySquare, enemyStep);
|
|
128
|
-
const enemyPawnDests: cg.Key[] =
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
123
|
+
const enemyPawnDests: cg.Key[] = squareAbove
|
|
124
|
+
? [
|
|
125
|
+
...util.adjacentSquares(squareAbove).filter(s => canEnemyPawnCaptureOnSquare(enemySquare, s, ctx)),
|
|
126
|
+
...[squareAbove, util.squareShiftedVertically(squareAbove, enemyStep)]
|
|
127
|
+
.filter(s => !!s)
|
|
128
|
+
.filter(s => canEnemyPawnAdvanceToSquare(enemySquare, s, ctx)),
|
|
129
|
+
]
|
|
130
|
+
: [];
|
|
131
|
+
const badSquares = [...squaresBetween, ctx.orig.key];
|
|
135
132
|
return enemyPawnDests.some(square => !badSquares.includes(square));
|
|
136
133
|
};
|
|
137
134
|
|
|
138
|
-
const isPathClearEnoughForPremove = (ctx: MobilityContext): boolean =>
|
|
139
|
-
isPathClearEnoughOfFriendliesForPremove(ctx) &&
|
|
135
|
+
const isPathClearEnoughForPremove = (ctx: MobilityContext, isPawnAdvance: boolean): boolean =>
|
|
136
|
+
isPathClearEnoughOfFriendliesForPremove(ctx, isPawnAdvance) &&
|
|
137
|
+
isPathClearEnoughOfEnemiesForPremove(ctx, isPawnAdvance);
|
|
140
138
|
|
|
141
139
|
const pawn: Mobility = (ctx: MobilityContext) => {
|
|
142
140
|
const step = ctx.color === 'white' ? 1 : -1;
|
|
143
|
-
if (util.diff(ctx.
|
|
144
|
-
if (!util.diff(ctx.
|
|
141
|
+
if (util.diff(ctx.orig.pos[0], ctx.dest.pos[0]) > 1) return false;
|
|
142
|
+
if (!util.diff(ctx.orig.pos[0], ctx.dest.pos[0]))
|
|
145
143
|
return (
|
|
146
|
-
util.pawnDirAdvance(...ctx.
|
|
147
|
-
isPathClearEnoughForPremove(
|
|
144
|
+
util.pawnDirAdvance(...ctx.orig.pos, ...ctx.dest.pos, ctx.color === 'white') &&
|
|
145
|
+
isPathClearEnoughForPremove(ctx, true)
|
|
148
146
|
);
|
|
149
|
-
|
|
150
|
-
if (ctx.pos2[1] !== ctx.pos1[1] + step) return false;
|
|
147
|
+
if (ctx.dest.pos[1] !== ctx.orig.pos[1] + step) return false;
|
|
151
148
|
if (ctx.unrestrictedPremoves || isDestOccupiedByEnemy(ctx)) return true;
|
|
152
149
|
if (isDestOccupiedByFriendly(ctx)) return isDestControlledByEnemy(ctx);
|
|
153
150
|
else
|
|
154
151
|
return (
|
|
155
152
|
canSomeEnemyPawnAdvanceToDest(ctx) ||
|
|
156
153
|
canBeCapturedBySomeEnemyEnPassant(
|
|
157
|
-
util.pos2key([ctx.
|
|
154
|
+
util.pos2key([ctx.dest.pos[0], ctx.dest.pos[1] + step]),
|
|
158
155
|
ctx.friendlies,
|
|
159
156
|
ctx.enemies,
|
|
160
157
|
ctx.lastMove,
|
|
@@ -164,37 +161,37 @@ const pawn: Mobility = (ctx: MobilityContext) => {
|
|
|
164
161
|
};
|
|
165
162
|
|
|
166
163
|
const knight: Mobility = (ctx: MobilityContext) =>
|
|
167
|
-
util.knightDir(...ctx.
|
|
164
|
+
util.knightDir(...ctx.orig.pos, ...ctx.dest.pos) &&
|
|
168
165
|
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx));
|
|
169
166
|
|
|
170
167
|
const bishop: Mobility = (ctx: MobilityContext) =>
|
|
171
|
-
util.bishopDir(...ctx.
|
|
172
|
-
isPathClearEnoughForPremove(ctx) &&
|
|
168
|
+
util.bishopDir(...ctx.orig.pos, ...ctx.dest.pos) &&
|
|
169
|
+
isPathClearEnoughForPremove(ctx, false) &&
|
|
173
170
|
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx));
|
|
174
171
|
|
|
175
172
|
const rook: Mobility = (ctx: MobilityContext) =>
|
|
176
|
-
util.rookDir(...ctx.
|
|
177
|
-
isPathClearEnoughForPremove(ctx) &&
|
|
173
|
+
util.rookDir(...ctx.orig.pos, ...ctx.dest.pos) &&
|
|
174
|
+
isPathClearEnoughForPremove(ctx, false) &&
|
|
178
175
|
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx));
|
|
179
176
|
|
|
180
177
|
const queen: Mobility = (ctx: MobilityContext) => bishop(ctx) || rook(ctx);
|
|
181
178
|
|
|
182
179
|
const king: Mobility = (ctx: MobilityContext) =>
|
|
183
|
-
(util.kingDirNonCastling(...ctx.
|
|
180
|
+
(util.kingDirNonCastling(...ctx.orig.pos, ...ctx.dest.pos) &&
|
|
184
181
|
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx))) ||
|
|
185
182
|
(ctx.canCastle &&
|
|
186
|
-
ctx.
|
|
187
|
-
ctx.
|
|
188
|
-
((ctx.
|
|
189
|
-
((ctx.
|
|
190
|
-
(ctx.
|
|
191
|
-
ctx.rookFilesFriendlies.includes(ctx.
|
|
183
|
+
ctx.orig.pos[1] === ctx.dest.pos[1] &&
|
|
184
|
+
ctx.orig.pos[1] === (ctx.color === 'white' ? 0 : 7) &&
|
|
185
|
+
((ctx.orig.pos[0] === 4 &&
|
|
186
|
+
((ctx.dest.pos[0] === 2 && ctx.rookFilesFriendlies.includes(0)) ||
|
|
187
|
+
(ctx.dest.pos[0] === 6 && ctx.rookFilesFriendlies.includes(7)))) ||
|
|
188
|
+
ctx.rookFilesFriendlies.includes(ctx.dest.pos[0])) &&
|
|
192
189
|
(ctx.unrestrictedPremoves ||
|
|
193
190
|
/* The following checks if no non-rook friendly piece is in the way between the king and its castling destination.
|
|
194
191
|
Note that for the Chess960 edge case of Kb1 "long castling", the check passes even if there is a piece in the way
|
|
195
192
|
on c1. But this is fine, since premoving from b1 to a1 as a normal move would have already returned true. */
|
|
196
193
|
util
|
|
197
|
-
.squaresBetween(...ctx.
|
|
194
|
+
.squaresBetween(...ctx.orig.pos, ctx.dest.pos[0] > ctx.orig.pos[0] ? 7 : 1, ctx.dest.pos[1])
|
|
198
195
|
.map(s => ctx.allPieces.get(s))
|
|
199
196
|
.every(p => !p || util.samePiece(p, { role: 'rook', color: ctx.color }))));
|
|
200
197
|
|
|
@@ -209,10 +206,12 @@ export function premove(state: HeadlessState, key: cg.Key): cg.Key[] {
|
|
|
209
206
|
const color = piece.color,
|
|
210
207
|
friendlies = new Map([...pieces].filter(([_, p]) => p.color === color)),
|
|
211
208
|
enemies = new Map([...pieces].filter(([_, p]) => p.color === util.opposite(color))),
|
|
212
|
-
|
|
213
|
-
mobility: Mobility =
|
|
214
|
-
|
|
215
|
-
|
|
209
|
+
orig = { key, pos: util.key2pos(key) },
|
|
210
|
+
mobility: Mobility = (ctx: MobilityContext) =>
|
|
211
|
+
mobilityByRole[piece.role](ctx) && state.premovable.additionalPremoveRequirements(ctx),
|
|
212
|
+
partialCtx = {
|
|
213
|
+
orig,
|
|
214
|
+
role: piece.role,
|
|
216
215
|
allPieces: pieces,
|
|
217
216
|
friendlies: friendlies,
|
|
218
217
|
enemies: enemies,
|
|
@@ -226,5 +225,5 @@ export function premove(state: HeadlessState, key: cg.Key): cg.Key[] {
|
|
|
226
225
|
.map(([k]) => util.key2pos(k)[0]),
|
|
227
226
|
lastMove: state.lastMove,
|
|
228
227
|
};
|
|
229
|
-
return util.
|
|
228
|
+
return util.allPosAndKey.filter(dest => mobility({ ...partialCtx, dest })).map(pk => pk.key);
|
|
230
229
|
}
|
package/src/state.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface HeadlessState {
|
|
|
23
23
|
blockTouchScroll: boolean; // block scrolling via touch dragging on the board
|
|
24
24
|
touchIgnoreRadius: number; // ignore touches within a radius of an occupied square, in units of its circumradius
|
|
25
25
|
pieceKey: boolean; // add a data-key attribute to piece elements
|
|
26
|
+
pixelCoordsOfMouchDownToMaybeClearShapes?: cg.NumberPair;
|
|
26
27
|
trustAllEvents?: boolean; // disable checking for human only input (e.isTrusted)
|
|
27
28
|
highlight: {
|
|
28
29
|
lastMove: boolean; // add last-move class to squares
|
|
@@ -53,6 +54,7 @@ export interface HeadlessState {
|
|
|
53
54
|
customDests?: cg.Dests; // use custom valid premoves. {"a2" ["a3" "a4"] "b1" ["a3" "c3"]}
|
|
54
55
|
current?: cg.KeyPair; // keys of the current saved premove ["e2" "e4"]
|
|
55
56
|
unrestrictedPremoves?: boolean; // if falsy, the positions of friendly pieces will be used to trim premove options
|
|
57
|
+
additionalPremoveRequirements: cg.Mobility;
|
|
56
58
|
events: {
|
|
57
59
|
set?: (orig: cg.Key, dest: cg.Key, metadata?: cg.SetPremoveMetadata) => void; // called after the premove has been set
|
|
58
60
|
unset?: () => void; // called after the premove has been unset
|
|
@@ -145,6 +147,7 @@ export function defaults(): HeadlessState {
|
|
|
145
147
|
enabled: true,
|
|
146
148
|
showDests: true,
|
|
147
149
|
castle: true,
|
|
150
|
+
additionalPremoveRequirements: _ => true,
|
|
148
151
|
events: {},
|
|
149
152
|
},
|
|
150
153
|
predroppable: {
|
|
@@ -174,7 +177,7 @@ export function defaults(): HeadlessState {
|
|
|
174
177
|
enabled: true, // can draw
|
|
175
178
|
visible: true, // can view
|
|
176
179
|
defaultSnapToValidMove: true,
|
|
177
|
-
|
|
180
|
+
eraseOnMovablePieceClick: true,
|
|
178
181
|
shapes: [],
|
|
179
182
|
autoShapes: [],
|
|
180
183
|
brushes: {
|
package/src/types.ts
CHANGED
|
@@ -5,6 +5,10 @@ export type Rank = (typeof ranks)[number];
|
|
|
5
5
|
export type Key = 'a0' | `${File}${Rank}`;
|
|
6
6
|
export type FEN = string;
|
|
7
7
|
export type Pos = [number, number];
|
|
8
|
+
export interface PosAndKey {
|
|
9
|
+
pos: Pos;
|
|
10
|
+
key: Key;
|
|
11
|
+
}
|
|
8
12
|
export interface Piece {
|
|
9
13
|
role: Role;
|
|
10
14
|
color: Color;
|
|
@@ -112,3 +116,19 @@ export type BrushColor = 'green' | 'red' | 'blue' | 'yellow';
|
|
|
112
116
|
export type SquareClasses = Map<Key, string>;
|
|
113
117
|
|
|
114
118
|
export type DirectionalCheck = (x1: number, y1: number, x2: number, y2: number) => boolean;
|
|
119
|
+
|
|
120
|
+
export type MobilityContext = {
|
|
121
|
+
orig: PosAndKey;
|
|
122
|
+
dest: PosAndKey;
|
|
123
|
+
role: Role;
|
|
124
|
+
allPieces: Pieces;
|
|
125
|
+
friendlies: Pieces;
|
|
126
|
+
enemies: Pieces;
|
|
127
|
+
unrestrictedPremoves: boolean;
|
|
128
|
+
color: Color;
|
|
129
|
+
canCastle: boolean;
|
|
130
|
+
rookFilesFriendlies: number[];
|
|
131
|
+
lastMove: Key[] | undefined;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export type Mobility = (ctx: MobilityContext) => boolean;
|
package/src/util.ts
CHANGED
|
@@ -4,7 +4,10 @@ export const invRanks: readonly cg.Rank[] = [...cg.ranks].reverse();
|
|
|
4
4
|
|
|
5
5
|
export const allKeys: readonly cg.Key[] = cg.files.flatMap(f => cg.ranks.map(r => (f + r) as cg.Key));
|
|
6
6
|
|
|
7
|
-
export const pos2key = (pos: cg.Pos): cg.Key
|
|
7
|
+
export const pos2key = (pos: cg.Pos): cg.Key | undefined =>
|
|
8
|
+
pos.every(x => x >= 0 && x <= 7) ? allKeys[8 * pos[0] + pos[1]] : undefined;
|
|
9
|
+
|
|
10
|
+
export const pos2keyUnsafe = (pos: cg.Pos): cg.Key => pos2key(pos)!;
|
|
8
11
|
|
|
9
12
|
export const key2pos = (k: cg.Key): cg.Pos => [k.charCodeAt(0) - 97, k.charCodeAt(1) - 49];
|
|
10
13
|
|
|
@@ -16,6 +19,8 @@ export const uciToMove = (uci: string | undefined): cg.Key[] | undefined => {
|
|
|
16
19
|
|
|
17
20
|
export const allPos: readonly cg.Pos[] = allKeys.map(key2pos);
|
|
18
21
|
|
|
22
|
+
export const allPosAndKey: readonly cg.PosAndKey[] = allKeys.map((key, i) => ({ key, pos: allPos[i] }));
|
|
23
|
+
|
|
19
24
|
export function memo<A>(f: () => A): cg.Memo<A> {
|
|
20
25
|
let v: A | undefined;
|
|
21
26
|
const ret = (): A => {
|
|
@@ -148,7 +153,7 @@ export const squaresBetween = (x1: number, y1: number, x2: number, y2: number):
|
|
|
148
153
|
x += stepX;
|
|
149
154
|
y += stepY;
|
|
150
155
|
}
|
|
151
|
-
return squares.map(
|
|
156
|
+
return squares.map(pos2key).filter(k => k !== undefined);
|
|
152
157
|
};
|
|
153
158
|
|
|
154
159
|
export const adjacentSquares = (square: cg.Key): cg.Key[] => {
|
|
@@ -156,10 +161,10 @@ export const adjacentSquares = (square: cg.Key): cg.Key[] => {
|
|
|
156
161
|
const adjacentSquares: cg.Pos[] = [];
|
|
157
162
|
if (pos[0] > 0) adjacentSquares.push([pos[0] - 1, pos[1]]);
|
|
158
163
|
if (pos[0] < 7) adjacentSquares.push([pos[0] + 1, pos[1]]);
|
|
159
|
-
return adjacentSquares.map(pos2key);
|
|
164
|
+
return adjacentSquares.map(pos2key).filter(k => k !== undefined);
|
|
160
165
|
};
|
|
161
166
|
|
|
162
|
-
export const squareShiftedVertically = (square: cg.Key, delta: number): cg.Key => {
|
|
167
|
+
export const squareShiftedVertically = (square: cg.Key, delta: number): cg.Key | undefined => {
|
|
163
168
|
const pos = key2pos(square);
|
|
164
169
|
pos[1] += delta;
|
|
165
170
|
return pos2key(pos);
|