@lichess-org/chessground 9.2.3
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/LICENSE +674 -0
- package/README.md +110 -0
- package/assets/chessground.base.css +224 -0
- package/assets/chessground.brown.css +62 -0
- package/assets/chessground.cburnett.css +37 -0
- package/dist/anim.d.ts +17 -0
- package/dist/anim.js +99 -0
- package/dist/anim.js.map +1 -0
- package/dist/api.d.ts +28 -0
- package/dist/api.js +98 -0
- package/dist/api.js.map +1 -0
- package/dist/autoPieces.d.ts +3 -0
- package/dist/autoPieces.js +38 -0
- package/dist/autoPieces.js.map +1 -0
- package/dist/board.d.ts +25 -0
- package/dist/board.js +331 -0
- package/dist/board.js.map +1 -0
- package/dist/chessground.d.ts +7 -0
- package/dist/chessground.js +63 -0
- package/dist/chessground.js.map +1 -0
- package/dist/chessground.min.js +1 -0
- package/dist/config.d.ts +87 -0
- package/dist/config.js +64 -0
- package/dist/config.js.map +1 -0
- package/dist/drag.d.ts +20 -0
- package/dist/drag.js +208 -0
- package/dist/drag.js.map +1 -0
- package/dist/draw.d.ts +65 -0
- package/dist/draw.js +90 -0
- package/dist/draw.js.map +1 -0
- package/dist/drop.d.ts +5 -0
- package/dist/drop.js +31 -0
- package/dist/drop.js.map +1 -0
- package/dist/events.d.ts +4 -0
- package/dist/events.js +72 -0
- package/dist/events.js.map +1 -0
- package/dist/explosion.d.ts +3 -0
- package/dist/explosion.js +18 -0
- package/dist/explosion.js.map +1 -0
- package/dist/fen.d.ts +4 -0
- package/dist/fen.js +79 -0
- package/dist/fen.js.map +1 -0
- package/dist/premove.d.ts +6 -0
- package/dist/premove.js +57 -0
- package/dist/premove.js.map +1 -0
- package/dist/render.d.ts +4 -0
- package/dist/render.js +235 -0
- package/dist/render.js.map +1 -0
- package/dist/state.d.ts +100 -0
- package/dist/state.js +92 -0
- package/dist/state.js.map +1 -0
- package/dist/svg.d.ts +8 -0
- package/dist/svg.js +348 -0
- package/dist/svg.js.map +1 -0
- package/dist/sync.d.ts +8 -0
- package/dist/sync.js +27 -0
- package/dist/sync.js.map +1 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +20 -0
- package/dist/util.js +89 -0
- package/dist/util.js.map +1 -0
- package/dist/wrap.d.ts +3 -0
- package/dist/wrap.js +90 -0
- package/dist/wrap.js.map +1 -0
- package/package.json +58 -0
- package/src/anim.ts +139 -0
- package/src/api.ts +187 -0
- package/src/autoPieces.ts +47 -0
- package/src/board.ts +371 -0
- package/src/chessground.ts +67 -0
- package/src/config.ts +165 -0
- package/src/drag.ts +223 -0
- package/src/draw.ts +154 -0
- package/src/drop.ts +36 -0
- package/src/events.ts +86 -0
- package/src/explosion.ts +19 -0
- package/src/fen.ts +78 -0
- package/src/premove.ts +76 -0
- package/src/render.ts +262 -0
- package/src/state.ts +199 -0
- package/src/svg.ts +441 -0
- package/src/sync.ts +36 -0
- package/src/types.ts +110 -0
- package/src/util.ts +105 -0
- package/src/wrap.ts +111 -0
package/src/fen.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { pos2key, invRanks } from './util.js';
|
|
2
|
+
import * as cg from './types.js';
|
|
3
|
+
|
|
4
|
+
export const initial: cg.FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR';
|
|
5
|
+
|
|
6
|
+
const roles: { [letter: string]: cg.Role } = {
|
|
7
|
+
p: 'pawn',
|
|
8
|
+
r: 'rook',
|
|
9
|
+
n: 'knight',
|
|
10
|
+
b: 'bishop',
|
|
11
|
+
q: 'queen',
|
|
12
|
+
k: 'king',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const letters = {
|
|
16
|
+
pawn: 'p',
|
|
17
|
+
rook: 'r',
|
|
18
|
+
knight: 'n',
|
|
19
|
+
bishop: 'b',
|
|
20
|
+
queen: 'q',
|
|
21
|
+
king: 'k',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function read(fen: cg.FEN): cg.Pieces {
|
|
25
|
+
if (fen === 'start') fen = initial;
|
|
26
|
+
const pieces: cg.Pieces = new Map();
|
|
27
|
+
let row = 7,
|
|
28
|
+
col = 0;
|
|
29
|
+
for (const c of fen) {
|
|
30
|
+
switch (c) {
|
|
31
|
+
case ' ':
|
|
32
|
+
case '[':
|
|
33
|
+
return pieces;
|
|
34
|
+
case '/':
|
|
35
|
+
--row;
|
|
36
|
+
if (row < 0) return pieces;
|
|
37
|
+
col = 0;
|
|
38
|
+
break;
|
|
39
|
+
case '~': {
|
|
40
|
+
const piece = pieces.get(pos2key([col - 1, row]));
|
|
41
|
+
if (piece) piece.promoted = true;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
default: {
|
|
45
|
+
const nb = c.charCodeAt(0);
|
|
46
|
+
if (nb < 57) col += nb - 48;
|
|
47
|
+
else {
|
|
48
|
+
const role = c.toLowerCase();
|
|
49
|
+
pieces.set(pos2key([col, row]), {
|
|
50
|
+
role: roles[role],
|
|
51
|
+
color: c === role ? 'black' : 'white',
|
|
52
|
+
});
|
|
53
|
+
++col;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return pieces;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function write(pieces: cg.Pieces): cg.FEN {
|
|
62
|
+
return invRanks
|
|
63
|
+
.map(y =>
|
|
64
|
+
cg.files
|
|
65
|
+
.map(x => {
|
|
66
|
+
const piece = pieces.get((x + y) as cg.Key);
|
|
67
|
+
if (piece) {
|
|
68
|
+
let p = letters[piece.role];
|
|
69
|
+
if (piece.color === 'white') p = p.toUpperCase();
|
|
70
|
+
if (piece.promoted) p += '~';
|
|
71
|
+
return p;
|
|
72
|
+
} else return '1';
|
|
73
|
+
})
|
|
74
|
+
.join(''),
|
|
75
|
+
)
|
|
76
|
+
.join('/')
|
|
77
|
+
.replace(/1{2,}/g, s => s.length.toString());
|
|
78
|
+
}
|
package/src/premove.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as util from './util.js';
|
|
2
|
+
import * as cg from './types.js';
|
|
3
|
+
|
|
4
|
+
type Mobility = (x1: number, y1: number, x2: number, y2: number) => boolean;
|
|
5
|
+
|
|
6
|
+
const diff = (a: number, b: number): number => Math.abs(a - b);
|
|
7
|
+
|
|
8
|
+
const pawn =
|
|
9
|
+
(color: cg.Color): Mobility =>
|
|
10
|
+
(x1, y1, x2, y2) =>
|
|
11
|
+
diff(x1, x2) < 2 &&
|
|
12
|
+
(color === 'white'
|
|
13
|
+
? // allow 2 squares from first two ranks, for horde
|
|
14
|
+
y2 === y1 + 1 || (y1 <= 1 && y2 === y1 + 2 && x1 === x2)
|
|
15
|
+
: y2 === y1 - 1 || (y1 >= 6 && y2 === y1 - 2 && x1 === x2));
|
|
16
|
+
|
|
17
|
+
export const knight: Mobility = (x1, y1, x2, y2) => {
|
|
18
|
+
const xd = diff(x1, x2);
|
|
19
|
+
const yd = diff(y1, y2);
|
|
20
|
+
return (xd === 1 && yd === 2) || (xd === 2 && yd === 1);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const bishop: Mobility = (x1, y1, x2, y2) => {
|
|
24
|
+
return diff(x1, x2) === diff(y1, y2);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const rook: Mobility = (x1, y1, x2, y2) => {
|
|
28
|
+
return x1 === x2 || y1 === y2;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const queen: Mobility = (x1, y1, x2, y2) => {
|
|
32
|
+
return bishop(x1, y1, x2, y2) || rook(x1, y1, x2, y2);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const king =
|
|
36
|
+
(color: cg.Color, rookFiles: number[], canCastle: boolean): Mobility =>
|
|
37
|
+
(x1, y1, x2, y2) =>
|
|
38
|
+
(diff(x1, x2) < 2 && diff(y1, y2) < 2) ||
|
|
39
|
+
(canCastle &&
|
|
40
|
+
y1 === y2 &&
|
|
41
|
+
y1 === (color === 'white' ? 0 : 7) &&
|
|
42
|
+
((x1 === 4 && ((x2 === 2 && rookFiles.includes(0)) || (x2 === 6 && rookFiles.includes(7)))) ||
|
|
43
|
+
rookFiles.includes(x2)));
|
|
44
|
+
|
|
45
|
+
function rookFilesOf(pieces: cg.Pieces, color: cg.Color) {
|
|
46
|
+
const backrank = color === 'white' ? '1' : '8';
|
|
47
|
+
const files = [];
|
|
48
|
+
for (const [key, piece] of pieces) {
|
|
49
|
+
if (key[1] === backrank && piece.color === color && piece.role === 'rook') {
|
|
50
|
+
files.push(util.key2pos(key)[0]);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return files;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function premove(pieces: cg.Pieces, key: cg.Key, canCastle: boolean): cg.Key[] {
|
|
57
|
+
const piece = pieces.get(key);
|
|
58
|
+
if (!piece) return [];
|
|
59
|
+
const pos = util.key2pos(key),
|
|
60
|
+
r = piece.role,
|
|
61
|
+
mobility: Mobility =
|
|
62
|
+
r === 'pawn'
|
|
63
|
+
? pawn(piece.color)
|
|
64
|
+
: r === 'knight'
|
|
65
|
+
? knight
|
|
66
|
+
: r === 'bishop'
|
|
67
|
+
? bishop
|
|
68
|
+
: r === 'rook'
|
|
69
|
+
? rook
|
|
70
|
+
: r === 'queen'
|
|
71
|
+
? queen
|
|
72
|
+
: king(piece.color, rookFilesOf(pieces, piece.color), canCastle);
|
|
73
|
+
return util.allPos
|
|
74
|
+
.filter(pos2 => (pos[0] !== pos2[0] || pos[1] !== pos2[1]) && mobility(pos[0], pos[1], pos2[0], pos2[1]))
|
|
75
|
+
.map(util.pos2key);
|
|
76
|
+
}
|
package/src/render.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { State } from './state.js';
|
|
2
|
+
import { key2pos, createEl, posToTranslate as posToTranslateFromBounds, translate } from './util.js';
|
|
3
|
+
import { whitePov } from './board.js';
|
|
4
|
+
import { AnimCurrent, AnimVectors, AnimVector, AnimFadings } from './anim.js';
|
|
5
|
+
import { DragCurrent } from './drag.js';
|
|
6
|
+
import * as cg from './types.js';
|
|
7
|
+
|
|
8
|
+
type PieceName = string; // `$color $role`
|
|
9
|
+
|
|
10
|
+
// ported from https://github.com/lichess-org/lichobile/blob/master/src/chessground/render.ts
|
|
11
|
+
// in case of bugs, blame @veloce
|
|
12
|
+
export function render(s: State): void {
|
|
13
|
+
const asWhite: boolean = whitePov(s),
|
|
14
|
+
posToTranslate = posToTranslateFromBounds(s.dom.bounds()),
|
|
15
|
+
boardEl: HTMLElement = s.dom.elements.board,
|
|
16
|
+
pieces: cg.Pieces = s.pieces,
|
|
17
|
+
curAnim: AnimCurrent | undefined = s.animation.current,
|
|
18
|
+
anims: AnimVectors = curAnim ? curAnim.plan.anims : new Map(),
|
|
19
|
+
fadings: AnimFadings = curAnim ? curAnim.plan.fadings : new Map(),
|
|
20
|
+
curDrag: DragCurrent | undefined = s.draggable.current,
|
|
21
|
+
squares: cg.SquareClasses = computeSquareClasses(s),
|
|
22
|
+
samePieces: Set<cg.Key> = new Set(),
|
|
23
|
+
sameSquares: Set<cg.Key> = new Set(),
|
|
24
|
+
movedPieces: Map<PieceName, cg.PieceNode[]> = new Map(),
|
|
25
|
+
movedSquares: Map<string, cg.SquareNode[]> = new Map(); // by class name
|
|
26
|
+
let k: cg.Key,
|
|
27
|
+
el: cg.PieceNode | cg.SquareNode | undefined,
|
|
28
|
+
pieceAtKey: cg.Piece | undefined,
|
|
29
|
+
elPieceName: PieceName,
|
|
30
|
+
anim: AnimVector | undefined,
|
|
31
|
+
fading: cg.Piece | undefined,
|
|
32
|
+
pMvdset: cg.PieceNode[] | undefined,
|
|
33
|
+
pMvd: cg.PieceNode | undefined,
|
|
34
|
+
sMvdset: cg.SquareNode[] | undefined,
|
|
35
|
+
sMvd: cg.SquareNode | undefined;
|
|
36
|
+
|
|
37
|
+
// walk over all board dom elements, apply animations and flag moved pieces
|
|
38
|
+
el = boardEl.firstChild as cg.PieceNode | cg.SquareNode | undefined;
|
|
39
|
+
while (el) {
|
|
40
|
+
k = el.cgKey;
|
|
41
|
+
if (isPieceNode(el)) {
|
|
42
|
+
pieceAtKey = pieces.get(k);
|
|
43
|
+
anim = anims.get(k);
|
|
44
|
+
fading = fadings.get(k);
|
|
45
|
+
elPieceName = el.cgPiece;
|
|
46
|
+
// if piece not being dragged anymore, remove dragging style
|
|
47
|
+
if (el.cgDragging && (!curDrag || curDrag.orig !== k)) {
|
|
48
|
+
el.classList.remove('dragging');
|
|
49
|
+
translate(el, posToTranslate(key2pos(k), asWhite));
|
|
50
|
+
el.cgDragging = false;
|
|
51
|
+
}
|
|
52
|
+
// remove fading class if it still remains
|
|
53
|
+
if (!fading && el.cgFading) {
|
|
54
|
+
el.cgFading = false;
|
|
55
|
+
el.classList.remove('fading');
|
|
56
|
+
}
|
|
57
|
+
// there is now a piece at this dom key
|
|
58
|
+
if (pieceAtKey) {
|
|
59
|
+
// continue animation if already animating and same piece
|
|
60
|
+
// (otherwise it could animate a captured piece)
|
|
61
|
+
if (anim && el.cgAnimating && elPieceName === pieceNameOf(pieceAtKey)) {
|
|
62
|
+
const pos = key2pos(k);
|
|
63
|
+
pos[0] += anim[2];
|
|
64
|
+
pos[1] += anim[3];
|
|
65
|
+
el.classList.add('anim');
|
|
66
|
+
translate(el, posToTranslate(pos, asWhite));
|
|
67
|
+
} else if (el.cgAnimating) {
|
|
68
|
+
el.cgAnimating = false;
|
|
69
|
+
el.classList.remove('anim');
|
|
70
|
+
translate(el, posToTranslate(key2pos(k), asWhite));
|
|
71
|
+
if (s.addPieceZIndex) el.style.zIndex = posZIndex(key2pos(k), asWhite);
|
|
72
|
+
}
|
|
73
|
+
// same piece: flag as same
|
|
74
|
+
if (elPieceName === pieceNameOf(pieceAtKey) && (!fading || !el.cgFading)) {
|
|
75
|
+
samePieces.add(k);
|
|
76
|
+
}
|
|
77
|
+
// different piece: flag as moved unless it is a fading piece
|
|
78
|
+
else {
|
|
79
|
+
if (fading && elPieceName === pieceNameOf(fading)) {
|
|
80
|
+
el.classList.add('fading');
|
|
81
|
+
el.cgFading = true;
|
|
82
|
+
} else {
|
|
83
|
+
appendValue(movedPieces, elPieceName, el);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// no piece: flag as moved
|
|
88
|
+
else {
|
|
89
|
+
appendValue(movedPieces, elPieceName, el);
|
|
90
|
+
}
|
|
91
|
+
} else if (isSquareNode(el)) {
|
|
92
|
+
const cn = el.className;
|
|
93
|
+
if (squares.get(k) === cn) sameSquares.add(k);
|
|
94
|
+
else appendValue(movedSquares, cn, el);
|
|
95
|
+
}
|
|
96
|
+
el = el.nextSibling as cg.PieceNode | cg.SquareNode | undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// walk over all squares in current set, apply dom changes to moved squares
|
|
100
|
+
// or append new squares
|
|
101
|
+
for (const [sk, className] of squares) {
|
|
102
|
+
if (!sameSquares.has(sk)) {
|
|
103
|
+
sMvdset = movedSquares.get(className);
|
|
104
|
+
sMvd = sMvdset && sMvdset.pop();
|
|
105
|
+
const translation = posToTranslate(key2pos(sk), asWhite);
|
|
106
|
+
if (sMvd) {
|
|
107
|
+
sMvd.cgKey = sk;
|
|
108
|
+
translate(sMvd, translation);
|
|
109
|
+
} else {
|
|
110
|
+
const squareNode = createEl('square', className) as cg.SquareNode;
|
|
111
|
+
squareNode.cgKey = sk;
|
|
112
|
+
translate(squareNode, translation);
|
|
113
|
+
boardEl.insertBefore(squareNode, boardEl.firstChild);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// walk over all pieces in current set, apply dom changes to moved pieces
|
|
119
|
+
// or append new pieces
|
|
120
|
+
for (const [k, p] of pieces) {
|
|
121
|
+
anim = anims.get(k);
|
|
122
|
+
if (!samePieces.has(k)) {
|
|
123
|
+
pMvdset = movedPieces.get(pieceNameOf(p));
|
|
124
|
+
pMvd = pMvdset && pMvdset.pop();
|
|
125
|
+
// a same piece was moved
|
|
126
|
+
if (pMvd) {
|
|
127
|
+
// apply dom changes
|
|
128
|
+
pMvd.cgKey = k;
|
|
129
|
+
if (pMvd.cgFading) {
|
|
130
|
+
pMvd.classList.remove('fading');
|
|
131
|
+
pMvd.cgFading = false;
|
|
132
|
+
}
|
|
133
|
+
const pos = key2pos(k);
|
|
134
|
+
if (s.addPieceZIndex) pMvd.style.zIndex = posZIndex(pos, asWhite);
|
|
135
|
+
if (anim) {
|
|
136
|
+
pMvd.cgAnimating = true;
|
|
137
|
+
pMvd.classList.add('anim');
|
|
138
|
+
pos[0] += anim[2];
|
|
139
|
+
pos[1] += anim[3];
|
|
140
|
+
}
|
|
141
|
+
translate(pMvd, posToTranslate(pos, asWhite));
|
|
142
|
+
}
|
|
143
|
+
// no piece in moved obj: insert the new piece
|
|
144
|
+
// assumes the new piece is not being dragged
|
|
145
|
+
else {
|
|
146
|
+
const pieceName = pieceNameOf(p),
|
|
147
|
+
pieceNode = createEl('piece', pieceName) as cg.PieceNode,
|
|
148
|
+
pos = key2pos(k);
|
|
149
|
+
|
|
150
|
+
pieceNode.cgPiece = pieceName;
|
|
151
|
+
pieceNode.cgKey = k;
|
|
152
|
+
if (anim) {
|
|
153
|
+
pieceNode.cgAnimating = true;
|
|
154
|
+
pos[0] += anim[2];
|
|
155
|
+
pos[1] += anim[3];
|
|
156
|
+
}
|
|
157
|
+
translate(pieceNode, posToTranslate(pos, asWhite));
|
|
158
|
+
|
|
159
|
+
if (s.addPieceZIndex) pieceNode.style.zIndex = posZIndex(pos, asWhite);
|
|
160
|
+
|
|
161
|
+
boardEl.appendChild(pieceNode);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// remove any element that remains in the moved sets
|
|
167
|
+
for (const nodes of movedPieces.values()) removeNodes(s, nodes);
|
|
168
|
+
for (const nodes of movedSquares.values()) removeNodes(s, nodes);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function renderResized(s: State): void {
|
|
172
|
+
const asWhite: boolean = whitePov(s),
|
|
173
|
+
posToTranslate = posToTranslateFromBounds(s.dom.bounds());
|
|
174
|
+
let el = s.dom.elements.board.firstChild as cg.PieceNode | cg.SquareNode | undefined;
|
|
175
|
+
while (el) {
|
|
176
|
+
if ((isPieceNode(el) && !el.cgAnimating) || isSquareNode(el)) {
|
|
177
|
+
translate(el, posToTranslate(key2pos(el.cgKey), asWhite));
|
|
178
|
+
}
|
|
179
|
+
el = el.nextSibling as cg.PieceNode | cg.SquareNode | undefined;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function updateBounds(s: State): void {
|
|
184
|
+
const bounds = s.dom.elements.wrap.getBoundingClientRect();
|
|
185
|
+
const container = s.dom.elements.container;
|
|
186
|
+
const ratio = bounds.height / bounds.width;
|
|
187
|
+
const width = (Math.floor((bounds.width * window.devicePixelRatio) / 8) * 8) / window.devicePixelRatio;
|
|
188
|
+
const height = width * ratio;
|
|
189
|
+
container.style.width = width + 'px';
|
|
190
|
+
container.style.height = height + 'px';
|
|
191
|
+
s.dom.bounds.clear();
|
|
192
|
+
|
|
193
|
+
s.addDimensionsCssVarsTo?.style.setProperty('---cg-width', width + 'px');
|
|
194
|
+
s.addDimensionsCssVarsTo?.style.setProperty('---cg-height', height + 'px');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const isPieceNode = (el: cg.PieceNode | cg.SquareNode): el is cg.PieceNode => el.tagName === 'PIECE';
|
|
198
|
+
const isSquareNode = (el: cg.PieceNode | cg.SquareNode): el is cg.SquareNode => el.tagName === 'SQUARE';
|
|
199
|
+
|
|
200
|
+
function removeNodes(s: State, nodes: HTMLElement[]): void {
|
|
201
|
+
for (const node of nodes) s.dom.elements.board.removeChild(node);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function posZIndex(pos: cg.Pos, asWhite: boolean): string {
|
|
205
|
+
const minZ = 3;
|
|
206
|
+
const rank = pos[1];
|
|
207
|
+
const z = asWhite ? minZ + 7 - rank : minZ + rank;
|
|
208
|
+
|
|
209
|
+
return `${z}`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const pieceNameOf = (piece: cg.Piece): string => `${piece.color} ${piece.role}`;
|
|
213
|
+
|
|
214
|
+
function computeSquareClasses(s: State): cg.SquareClasses {
|
|
215
|
+
const squares: cg.SquareClasses = new Map();
|
|
216
|
+
if (s.lastMove && s.highlight.lastMove)
|
|
217
|
+
for (const k of s.lastMove) {
|
|
218
|
+
addSquare(squares, k, 'last-move');
|
|
219
|
+
}
|
|
220
|
+
if (s.check && s.highlight.check) addSquare(squares, s.check, 'check');
|
|
221
|
+
if (s.selected) {
|
|
222
|
+
addSquare(squares, s.selected, 'selected');
|
|
223
|
+
if (s.movable.showDests) {
|
|
224
|
+
const dests = s.movable.dests?.get(s.selected);
|
|
225
|
+
if (dests)
|
|
226
|
+
for (const k of dests) {
|
|
227
|
+
addSquare(squares, k, 'move-dest' + (s.pieces.has(k) ? ' oc' : ''));
|
|
228
|
+
}
|
|
229
|
+
const pDests = s.premovable.customDests?.get(s.selected) ?? s.premovable.dests;
|
|
230
|
+
if (pDests)
|
|
231
|
+
for (const k of pDests) {
|
|
232
|
+
addSquare(squares, k, 'premove-dest' + (s.pieces.has(k) ? ' oc' : ''));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const premove = s.premovable.current;
|
|
237
|
+
if (premove) for (const k of premove) addSquare(squares, k, 'current-premove');
|
|
238
|
+
else if (s.predroppable.current) addSquare(squares, s.predroppable.current.key, 'current-premove');
|
|
239
|
+
|
|
240
|
+
const o = s.exploding;
|
|
241
|
+
if (o) for (const k of o.keys) addSquare(squares, k, 'exploding' + o.stage);
|
|
242
|
+
|
|
243
|
+
if (s.highlight.custom) {
|
|
244
|
+
s.highlight.custom.forEach((v: string, k: cg.Key) => {
|
|
245
|
+
addSquare(squares, k, v);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return squares;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function addSquare(squares: cg.SquareClasses, key: cg.Key, klass: string): void {
|
|
253
|
+
const classes = squares.get(key);
|
|
254
|
+
if (classes) squares.set(key, `${classes} ${klass}`);
|
|
255
|
+
else squares.set(key, klass);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function appendValue<K, V>(map: Map<K, V[]>, key: K, value: V): void {
|
|
259
|
+
const arr = map.get(key);
|
|
260
|
+
if (arr) arr.push(value);
|
|
261
|
+
else map.set(key, [value]);
|
|
262
|
+
}
|
package/src/state.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import * as fen from './fen.js';
|
|
2
|
+
import { AnimCurrent } from './anim.js';
|
|
3
|
+
import { DragCurrent } from './drag.js';
|
|
4
|
+
import { Drawable } from './draw.js';
|
|
5
|
+
import { timer } from './util.js';
|
|
6
|
+
import * as cg from './types.js';
|
|
7
|
+
|
|
8
|
+
export interface HeadlessState {
|
|
9
|
+
pieces: cg.Pieces;
|
|
10
|
+
orientation: cg.Color; // board orientation. white | black
|
|
11
|
+
turnColor: cg.Color; // turn to play. white | black
|
|
12
|
+
check?: cg.Key; // square currently in check "a2"
|
|
13
|
+
lastMove?: cg.Key[]; // squares part of the last move ["c3"; "c4"]
|
|
14
|
+
selected?: cg.Key; // square currently selected "a1"
|
|
15
|
+
coordinates: boolean; // include coords attributes
|
|
16
|
+
coordinatesOnSquares: boolean; // include coords attributes on every square
|
|
17
|
+
ranksPosition: cg.RanksPosition; // position ranks on either side. left | right
|
|
18
|
+
autoCastle: boolean; // immediately complete the castle by moving the rook after king move
|
|
19
|
+
viewOnly: boolean; // don't bind events: the user will never be able to move pieces around
|
|
20
|
+
disableContextMenu: boolean; // because who needs a context menu on a chessboard
|
|
21
|
+
addPieceZIndex: boolean; // adds z-index values to pieces (for 3D)
|
|
22
|
+
addDimensionsCssVarsTo?: HTMLElement; // add ---cg-width and ---cg-height CSS vars containing the board's dimensions to this element
|
|
23
|
+
blockTouchScroll: boolean; // block scrolling via touch dragging on the board, e.g. for coordinate training
|
|
24
|
+
pieceKey: boolean; // add a data-key attribute to piece elements
|
|
25
|
+
trustAllEvents?: boolean; // disable checking for human only input (e.isTrusted)
|
|
26
|
+
highlight: {
|
|
27
|
+
lastMove: boolean; // add last-move class to squares
|
|
28
|
+
check: boolean; // add check class to squares
|
|
29
|
+
custom?: cg.SquareClasses; // add custom classes to custom squares
|
|
30
|
+
};
|
|
31
|
+
animation: {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
duration: number;
|
|
34
|
+
current?: AnimCurrent;
|
|
35
|
+
};
|
|
36
|
+
movable: {
|
|
37
|
+
free: boolean; // all moves are valid - board editor
|
|
38
|
+
color?: cg.Color | 'both'; // color that can move. white | black | both
|
|
39
|
+
dests?: cg.Dests; // valid moves. {"a2" ["a3" "a4"] "b1" ["a3" "c3"]}
|
|
40
|
+
showDests: boolean; // whether to add the move-dest class on squares
|
|
41
|
+
events: {
|
|
42
|
+
after?: (orig: cg.Key, dest: cg.Key, metadata: cg.MoveMetadata) => void; // called after the move has been played
|
|
43
|
+
afterNewPiece?: (role: cg.Role, key: cg.Key, metadata: cg.MoveMetadata) => void; // called after a new piece is dropped on the board
|
|
44
|
+
};
|
|
45
|
+
rookCastle: boolean; // castle by moving the king to the rook
|
|
46
|
+
};
|
|
47
|
+
premovable: {
|
|
48
|
+
enabled: boolean; // allow premoves for color that can not move
|
|
49
|
+
showDests: boolean; // whether to add the premove-dest class on squares
|
|
50
|
+
castle: boolean; // whether to allow king castle premoves
|
|
51
|
+
dests?: cg.Key[]; // premove destinations for the current selection
|
|
52
|
+
customDests?: cg.Dests; // use custom valid premoves. {"a2" ["a3" "a4"] "b1" ["a3" "c3"]}
|
|
53
|
+
current?: cg.KeyPair; // keys of the current saved premove ["e2" "e4"]
|
|
54
|
+
events: {
|
|
55
|
+
set?: (orig: cg.Key, dest: cg.Key, metadata?: cg.SetPremoveMetadata) => void; // called after the premove has been set
|
|
56
|
+
unset?: () => void; // called after the premove has been unset
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
predroppable: {
|
|
60
|
+
enabled: boolean; // allow predrops for color that can not move
|
|
61
|
+
current?: {
|
|
62
|
+
// current saved predrop {role: 'knight'; key: 'e4'}
|
|
63
|
+
role: cg.Role;
|
|
64
|
+
key: cg.Key;
|
|
65
|
+
};
|
|
66
|
+
events: {
|
|
67
|
+
set?: (role: cg.Role, key: cg.Key) => void; // called after the predrop has been set
|
|
68
|
+
unset?: () => void; // called after the predrop has been unset
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
draggable: {
|
|
72
|
+
enabled: boolean; // allow moves & premoves to use drag'n drop
|
|
73
|
+
distance: number; // minimum distance to initiate a drag; in pixels
|
|
74
|
+
autoDistance: boolean; // lets chessground set distance to zero when user drags pieces
|
|
75
|
+
showGhost: boolean; // show ghost of piece being dragged
|
|
76
|
+
deleteOnDropOff: boolean; // delete a piece when it is dropped off the board
|
|
77
|
+
current?: DragCurrent;
|
|
78
|
+
};
|
|
79
|
+
dropmode: {
|
|
80
|
+
active: boolean;
|
|
81
|
+
piece?: cg.Piece;
|
|
82
|
+
};
|
|
83
|
+
selectable: {
|
|
84
|
+
// disable to enforce dragging over click-click move
|
|
85
|
+
enabled: boolean;
|
|
86
|
+
};
|
|
87
|
+
stats: {
|
|
88
|
+
// was last piece dragged or clicked?
|
|
89
|
+
// needs default to false for touch
|
|
90
|
+
dragged: boolean;
|
|
91
|
+
ctrlKey?: boolean;
|
|
92
|
+
};
|
|
93
|
+
events: {
|
|
94
|
+
change?: () => void; // called after the situation changes on the board
|
|
95
|
+
// called after a piece has been moved.
|
|
96
|
+
// capturedPiece is undefined or like {color: 'white'; 'role': 'queen'}
|
|
97
|
+
move?: (orig: cg.Key, dest: cg.Key, capturedPiece?: cg.Piece) => void;
|
|
98
|
+
dropNewPiece?: (piece: cg.Piece, key: cg.Key) => void;
|
|
99
|
+
select?: (key: cg.Key) => void; // called when a square is selected
|
|
100
|
+
insert?: (elements: cg.Elements) => void; // when the board DOM has been (re)inserted
|
|
101
|
+
};
|
|
102
|
+
drawable: Drawable;
|
|
103
|
+
exploding?: cg.Exploding;
|
|
104
|
+
hold: cg.Timer;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface State extends HeadlessState {
|
|
108
|
+
dom: cg.Dom;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function defaults(): HeadlessState {
|
|
112
|
+
return {
|
|
113
|
+
pieces: fen.read(fen.initial),
|
|
114
|
+
orientation: 'white',
|
|
115
|
+
turnColor: 'white',
|
|
116
|
+
coordinates: true,
|
|
117
|
+
coordinatesOnSquares: false,
|
|
118
|
+
ranksPosition: 'right',
|
|
119
|
+
autoCastle: true,
|
|
120
|
+
viewOnly: false,
|
|
121
|
+
disableContextMenu: false,
|
|
122
|
+
addPieceZIndex: false,
|
|
123
|
+
blockTouchScroll: false,
|
|
124
|
+
pieceKey: false,
|
|
125
|
+
trustAllEvents: false,
|
|
126
|
+
highlight: {
|
|
127
|
+
lastMove: true,
|
|
128
|
+
check: true,
|
|
129
|
+
},
|
|
130
|
+
animation: {
|
|
131
|
+
enabled: true,
|
|
132
|
+
duration: 200,
|
|
133
|
+
},
|
|
134
|
+
movable: {
|
|
135
|
+
free: true,
|
|
136
|
+
color: 'both',
|
|
137
|
+
showDests: true,
|
|
138
|
+
events: {},
|
|
139
|
+
rookCastle: true,
|
|
140
|
+
},
|
|
141
|
+
premovable: {
|
|
142
|
+
enabled: true,
|
|
143
|
+
showDests: true,
|
|
144
|
+
castle: true,
|
|
145
|
+
events: {},
|
|
146
|
+
},
|
|
147
|
+
predroppable: {
|
|
148
|
+
enabled: false,
|
|
149
|
+
events: {},
|
|
150
|
+
},
|
|
151
|
+
draggable: {
|
|
152
|
+
enabled: true,
|
|
153
|
+
distance: 3,
|
|
154
|
+
autoDistance: true,
|
|
155
|
+
showGhost: true,
|
|
156
|
+
deleteOnDropOff: false,
|
|
157
|
+
},
|
|
158
|
+
dropmode: {
|
|
159
|
+
active: false,
|
|
160
|
+
},
|
|
161
|
+
selectable: {
|
|
162
|
+
enabled: true,
|
|
163
|
+
},
|
|
164
|
+
stats: {
|
|
165
|
+
// on touchscreen, default to "tap-tap" moves
|
|
166
|
+
// instead of drag
|
|
167
|
+
dragged: !('ontouchstart' in window),
|
|
168
|
+
},
|
|
169
|
+
events: {},
|
|
170
|
+
drawable: {
|
|
171
|
+
enabled: true, // can draw
|
|
172
|
+
visible: true, // can view
|
|
173
|
+
defaultSnapToValidMove: true,
|
|
174
|
+
eraseOnClick: true,
|
|
175
|
+
shapes: [],
|
|
176
|
+
autoShapes: [],
|
|
177
|
+
brushes: {
|
|
178
|
+
green: { key: 'g', color: '#15781B', opacity: 1, lineWidth: 10 },
|
|
179
|
+
red: { key: 'r', color: '#882020', opacity: 1, lineWidth: 10 },
|
|
180
|
+
blue: { key: 'b', color: '#003088', opacity: 1, lineWidth: 10 },
|
|
181
|
+
yellow: { key: 'y', color: '#e68f00', opacity: 1, lineWidth: 10 },
|
|
182
|
+
paleBlue: { key: 'pb', color: '#003088', opacity: 0.4, lineWidth: 15 },
|
|
183
|
+
paleGreen: { key: 'pg', color: '#15781B', opacity: 0.4, lineWidth: 15 },
|
|
184
|
+
paleRed: { key: 'pr', color: '#882020', opacity: 0.4, lineWidth: 15 },
|
|
185
|
+
paleGrey: {
|
|
186
|
+
key: 'pgr',
|
|
187
|
+
color: '#4a4a4a',
|
|
188
|
+
opacity: 0.35,
|
|
189
|
+
lineWidth: 15,
|
|
190
|
+
},
|
|
191
|
+
purple: { key: 'purple', color: '#68217a', opacity: 0.65, lineWidth: 10 },
|
|
192
|
+
pink: { key: 'pink', color: '#ee2080', opacity: 0.5, lineWidth: 10 },
|
|
193
|
+
white: { key: 'white', color: 'white', opacity: 1, lineWidth: 10 },
|
|
194
|
+
},
|
|
195
|
+
prevSvgHash: '',
|
|
196
|
+
},
|
|
197
|
+
hold: timer(),
|
|
198
|
+
};
|
|
199
|
+
}
|