@lichess-org/chessground 9.7.4 → 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 +60 -44
- 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 +71 -73
- 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,7 +59,7 @@ 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 = (
|
|
@@ -91,32 +76,41 @@ const canBeCapturedBySomeEnemyEnPassant = (
|
|
|
91
76
|
friendly?.role === 'pawn' &&
|
|
92
77
|
pos[1] === (friendly.color === 'white' ? 3 : 4) &&
|
|
93
78
|
(!lastMove || util.diff(util.key2pos(lastMove[0])[1], pos[1]) === 2) &&
|
|
94
|
-
[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
|
+
})
|
|
95
83
|
);
|
|
96
84
|
};
|
|
97
85
|
|
|
98
|
-
const isPathClearEnoughOfFriendliesForPremove = (ctx: MobilityContext): boolean => {
|
|
86
|
+
const isPathClearEnoughOfFriendliesForPremove = (ctx: MobilityContext, isPawnAdvance: boolean): boolean => {
|
|
99
87
|
if (ctx.unrestrictedPremoves) return true;
|
|
100
|
-
const squaresBetween = util.squaresBetween(...ctx.
|
|
88
|
+
const squaresBetween = util.squaresBetween(...ctx.orig.pos, ...ctx.dest.pos);
|
|
89
|
+
if (isPawnAdvance) squaresBetween.push(ctx.dest.key);
|
|
101
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
|
+
);
|
|
102
97
|
return (
|
|
103
|
-
|
|
104
|
-
(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
util.squareShiftedVertically(squaresOfFriendliesBetween[0], ctx.color === 'white' ? -1 : 1),
|
|
113
|
-
))
|
|
98
|
+
squaresOfFriendliesBetween.length === 1 &&
|
|
99
|
+
canBeCapturedBySomeEnemyEnPassant(
|
|
100
|
+
firstSquareOfFriendliesBetween,
|
|
101
|
+
ctx.friendlies,
|
|
102
|
+
ctx.enemies,
|
|
103
|
+
ctx.lastMove,
|
|
104
|
+
) &&
|
|
105
|
+
!!nextSquare &&
|
|
106
|
+
!squaresBetween.includes(nextSquare)
|
|
114
107
|
);
|
|
115
108
|
};
|
|
116
109
|
|
|
117
|
-
const isPathClearEnoughOfEnemiesForPremove = (ctx: MobilityContext): boolean => {
|
|
110
|
+
const isPathClearEnoughOfEnemiesForPremove = (ctx: MobilityContext, isPawnAdvance: boolean): boolean => {
|
|
118
111
|
if (ctx.unrestrictedPremoves) return true;
|
|
119
|
-
const squaresBetween = util.squaresBetween(...ctx.
|
|
112
|
+
const squaresBetween = util.squaresBetween(...ctx.orig.pos, ...ctx.dest.pos);
|
|
113
|
+
if (isPawnAdvance) squaresBetween.push(ctx.dest.key);
|
|
120
114
|
const squaresOfEnemiesBetween = squaresBetween.filter(s => ctx.enemies.has(s));
|
|
121
115
|
if (squaresOfEnemiesBetween.length > 1) return false;
|
|
122
116
|
if (!squaresOfEnemiesBetween.length) return true;
|
|
@@ -126,36 +120,38 @@ const isPathClearEnoughOfEnemiesForPremove = (ctx: MobilityContext): boolean =>
|
|
|
126
120
|
|
|
127
121
|
const enemyStep = enemy.color === 'white' ? 1 : -1;
|
|
128
122
|
const squareAbove = util.squareShiftedVertically(enemySquare, enemyStep);
|
|
129
|
-
const enemyPawnDests: cg.Key[] =
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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];
|
|
136
132
|
return enemyPawnDests.some(square => !badSquares.includes(square));
|
|
137
133
|
};
|
|
138
134
|
|
|
139
|
-
const isPathClearEnoughForPremove = (ctx: MobilityContext): boolean =>
|
|
140
|
-
isPathClearEnoughOfFriendliesForPremove(ctx) &&
|
|
135
|
+
const isPathClearEnoughForPremove = (ctx: MobilityContext, isPawnAdvance: boolean): boolean =>
|
|
136
|
+
isPathClearEnoughOfFriendliesForPremove(ctx, isPawnAdvance) &&
|
|
137
|
+
isPathClearEnoughOfEnemiesForPremove(ctx, isPawnAdvance);
|
|
141
138
|
|
|
142
139
|
const pawn: Mobility = (ctx: MobilityContext) => {
|
|
143
140
|
const step = ctx.color === 'white' ? 1 : -1;
|
|
144
|
-
if (util.diff(ctx.
|
|
145
|
-
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]))
|
|
146
143
|
return (
|
|
147
|
-
util.pawnDirAdvance(...ctx.
|
|
148
|
-
isPathClearEnoughForPremove(
|
|
144
|
+
util.pawnDirAdvance(...ctx.orig.pos, ...ctx.dest.pos, ctx.color === 'white') &&
|
|
145
|
+
isPathClearEnoughForPremove(ctx, true)
|
|
149
146
|
);
|
|
150
|
-
|
|
151
|
-
if (ctx.pos2[1] !== ctx.pos1[1] + step) return false;
|
|
147
|
+
if (ctx.dest.pos[1] !== ctx.orig.pos[1] + step) return false;
|
|
152
148
|
if (ctx.unrestrictedPremoves || isDestOccupiedByEnemy(ctx)) return true;
|
|
153
149
|
if (isDestOccupiedByFriendly(ctx)) return isDestControlledByEnemy(ctx);
|
|
154
150
|
else
|
|
155
151
|
return (
|
|
156
152
|
canSomeEnemyPawnAdvanceToDest(ctx) ||
|
|
157
153
|
canBeCapturedBySomeEnemyEnPassant(
|
|
158
|
-
util.pos2key([ctx.
|
|
154
|
+
util.pos2key([ctx.dest.pos[0], ctx.dest.pos[1] + step]),
|
|
159
155
|
ctx.friendlies,
|
|
160
156
|
ctx.enemies,
|
|
161
157
|
ctx.lastMove,
|
|
@@ -165,37 +161,37 @@ const pawn: Mobility = (ctx: MobilityContext) => {
|
|
|
165
161
|
};
|
|
166
162
|
|
|
167
163
|
const knight: Mobility = (ctx: MobilityContext) =>
|
|
168
|
-
util.knightDir(...ctx.
|
|
164
|
+
util.knightDir(...ctx.orig.pos, ...ctx.dest.pos) &&
|
|
169
165
|
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx));
|
|
170
166
|
|
|
171
167
|
const bishop: Mobility = (ctx: MobilityContext) =>
|
|
172
|
-
util.bishopDir(...ctx.
|
|
173
|
-
isPathClearEnoughForPremove(ctx) &&
|
|
168
|
+
util.bishopDir(...ctx.orig.pos, ...ctx.dest.pos) &&
|
|
169
|
+
isPathClearEnoughForPremove(ctx, false) &&
|
|
174
170
|
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx));
|
|
175
171
|
|
|
176
172
|
const rook: Mobility = (ctx: MobilityContext) =>
|
|
177
|
-
util.rookDir(...ctx.
|
|
178
|
-
isPathClearEnoughForPremove(ctx) &&
|
|
173
|
+
util.rookDir(...ctx.orig.pos, ...ctx.dest.pos) &&
|
|
174
|
+
isPathClearEnoughForPremove(ctx, false) &&
|
|
179
175
|
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx));
|
|
180
176
|
|
|
181
177
|
const queen: Mobility = (ctx: MobilityContext) => bishop(ctx) || rook(ctx);
|
|
182
178
|
|
|
183
179
|
const king: Mobility = (ctx: MobilityContext) =>
|
|
184
|
-
(util.kingDirNonCastling(...ctx.
|
|
180
|
+
(util.kingDirNonCastling(...ctx.orig.pos, ...ctx.dest.pos) &&
|
|
185
181
|
(ctx.unrestrictedPremoves || !isDestOccupiedByFriendly(ctx) || isFriendlyOnDestAndAttacked(ctx))) ||
|
|
186
182
|
(ctx.canCastle &&
|
|
187
|
-
ctx.
|
|
188
|
-
ctx.
|
|
189
|
-
((ctx.
|
|
190
|
-
((ctx.
|
|
191
|
-
(ctx.
|
|
192
|
-
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])) &&
|
|
193
189
|
(ctx.unrestrictedPremoves ||
|
|
194
190
|
/* The following checks if no non-rook friendly piece is in the way between the king and its castling destination.
|
|
195
191
|
Note that for the Chess960 edge case of Kb1 "long castling", the check passes even if there is a piece in the way
|
|
196
192
|
on c1. But this is fine, since premoving from b1 to a1 as a normal move would have already returned true. */
|
|
197
193
|
util
|
|
198
|
-
.squaresBetween(...ctx.
|
|
194
|
+
.squaresBetween(...ctx.orig.pos, ctx.dest.pos[0] > ctx.orig.pos[0] ? 7 : 1, ctx.dest.pos[1])
|
|
199
195
|
.map(s => ctx.allPieces.get(s))
|
|
200
196
|
.every(p => !p || util.samePiece(p, { role: 'rook', color: ctx.color }))));
|
|
201
197
|
|
|
@@ -210,10 +206,12 @@ export function premove(state: HeadlessState, key: cg.Key): cg.Key[] {
|
|
|
210
206
|
const color = piece.color,
|
|
211
207
|
friendlies = new Map([...pieces].filter(([_, p]) => p.color === color)),
|
|
212
208
|
enemies = new Map([...pieces].filter(([_, p]) => p.color === util.opposite(color))),
|
|
213
|
-
|
|
214
|
-
mobility: Mobility =
|
|
215
|
-
|
|
216
|
-
|
|
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,
|
|
217
215
|
allPieces: pieces,
|
|
218
216
|
friendlies: friendlies,
|
|
219
217
|
enemies: enemies,
|
|
@@ -227,5 +225,5 @@ export function premove(state: HeadlessState, key: cg.Key): cg.Key[] {
|
|
|
227
225
|
.map(([k]) => util.key2pos(k)[0]),
|
|
228
226
|
lastMove: state.lastMove,
|
|
229
227
|
};
|
|
230
|
-
return util.
|
|
228
|
+
return util.allPosAndKey.filter(dest => mobility({ ...partialCtx, dest })).map(pk => pk.key);
|
|
231
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);
|