@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/api.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { State } from './state.js';
|
|
2
|
+
import * as board from './board.js';
|
|
3
|
+
import { write as fenWrite } from './fen.js';
|
|
4
|
+
import { Config, configure, applyAnimation } from './config.js';
|
|
5
|
+
import { anim, render } from './anim.js';
|
|
6
|
+
import { cancel as dragCancel, dragNewPiece } from './drag.js';
|
|
7
|
+
import { DrawShape } from './draw.js';
|
|
8
|
+
import { explosion } from './explosion.js';
|
|
9
|
+
import * as cg from './types.js';
|
|
10
|
+
|
|
11
|
+
export interface Api {
|
|
12
|
+
// reconfigure the instance. Accepts all config options, except for viewOnly & drawable.visible.
|
|
13
|
+
// board will be animated accordingly, if animations are enabled.
|
|
14
|
+
set(config: Config): void;
|
|
15
|
+
|
|
16
|
+
// read chessground state; write at your own risks.
|
|
17
|
+
state: State;
|
|
18
|
+
|
|
19
|
+
// get the position as a FEN string (only contains pieces, no flags)
|
|
20
|
+
// e.g. rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR
|
|
21
|
+
getFen(): cg.FEN;
|
|
22
|
+
|
|
23
|
+
// change the view angle
|
|
24
|
+
toggleOrientation(): void;
|
|
25
|
+
|
|
26
|
+
// perform a move programmatically
|
|
27
|
+
move(orig: cg.Key, dest: cg.Key): void;
|
|
28
|
+
|
|
29
|
+
// add and/or remove arbitrary pieces on the board
|
|
30
|
+
setPieces(pieces: cg.PiecesDiff): void;
|
|
31
|
+
|
|
32
|
+
// click a square programmatically
|
|
33
|
+
selectSquare(key: cg.Key | null, force?: boolean): void;
|
|
34
|
+
|
|
35
|
+
// put a new piece on the board
|
|
36
|
+
newPiece(piece: cg.Piece, key: cg.Key): void;
|
|
37
|
+
|
|
38
|
+
// play the current premove, if any; returns true if premove was played
|
|
39
|
+
playPremove(): boolean;
|
|
40
|
+
|
|
41
|
+
// cancel the current premove, if any
|
|
42
|
+
cancelPremove(): void;
|
|
43
|
+
|
|
44
|
+
// play the current predrop, if any; returns true if premove was played
|
|
45
|
+
playPredrop(validate: (drop: cg.Drop) => boolean): boolean;
|
|
46
|
+
|
|
47
|
+
// cancel the current predrop, if any
|
|
48
|
+
cancelPredrop(): void;
|
|
49
|
+
|
|
50
|
+
// cancel the current move being made
|
|
51
|
+
cancelMove(): void;
|
|
52
|
+
|
|
53
|
+
// cancel current move and prevent further ones
|
|
54
|
+
stop(): void;
|
|
55
|
+
|
|
56
|
+
// make squares explode (atomic chess)
|
|
57
|
+
explode(keys: cg.Key[]): void;
|
|
58
|
+
|
|
59
|
+
// programmatically draw user shapes
|
|
60
|
+
setShapes(shapes: DrawShape[]): void;
|
|
61
|
+
|
|
62
|
+
// programmatically draw auto shapes
|
|
63
|
+
setAutoShapes(shapes: DrawShape[]): void;
|
|
64
|
+
|
|
65
|
+
// square name at this DOM position (like "e4")
|
|
66
|
+
getKeyAtDomPos(pos: cg.NumberPair): cg.Key | undefined;
|
|
67
|
+
|
|
68
|
+
// only useful when CSS changes the board width/height ratio (for 3D)
|
|
69
|
+
redrawAll: cg.Redraw;
|
|
70
|
+
|
|
71
|
+
// for crazyhouse and board editors
|
|
72
|
+
dragNewPiece(piece: cg.Piece, event: cg.MouchEvent, force?: boolean): void;
|
|
73
|
+
|
|
74
|
+
// unbinds all events
|
|
75
|
+
// (important for document-wide events like scroll and mousemove)
|
|
76
|
+
destroy: cg.Unbind;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// see API types and documentations in dts/api.d.ts
|
|
80
|
+
export function start(state: State, redrawAll: cg.Redraw): Api {
|
|
81
|
+
function toggleOrientation(): void {
|
|
82
|
+
board.toggleOrientation(state);
|
|
83
|
+
redrawAll();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
set(config): void {
|
|
88
|
+
if (config.orientation && config.orientation !== state.orientation) toggleOrientation();
|
|
89
|
+
applyAnimation(state, config);
|
|
90
|
+
(config.fen ? anim : render)(state => configure(state, config), state);
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
state,
|
|
94
|
+
|
|
95
|
+
getFen: () => fenWrite(state.pieces),
|
|
96
|
+
|
|
97
|
+
toggleOrientation,
|
|
98
|
+
|
|
99
|
+
setPieces(pieces): void {
|
|
100
|
+
anim(state => board.setPieces(state, pieces), state);
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
selectSquare(key, force): void {
|
|
104
|
+
if (key) anim(state => board.selectSquare(state, key, force), state);
|
|
105
|
+
else if (state.selected) {
|
|
106
|
+
board.unselect(state);
|
|
107
|
+
state.dom.redraw();
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
move(orig, dest): void {
|
|
112
|
+
anim(state => board.baseMove(state, orig, dest), state);
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
newPiece(piece, key): void {
|
|
116
|
+
anim(state => board.baseNewPiece(state, piece, key), state);
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
playPremove(): boolean {
|
|
120
|
+
if (state.premovable.current) {
|
|
121
|
+
if (anim(board.playPremove, state)) return true;
|
|
122
|
+
// if the premove couldn't be played, redraw to clear it up
|
|
123
|
+
state.dom.redraw();
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
playPredrop(validate): boolean {
|
|
129
|
+
if (state.predroppable.current) {
|
|
130
|
+
const result = board.playPredrop(state, validate);
|
|
131
|
+
state.dom.redraw();
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
cancelPremove(): void {
|
|
138
|
+
render(board.unsetPremove, state);
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
cancelPredrop(): void {
|
|
142
|
+
render(board.unsetPredrop, state);
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
cancelMove(): void {
|
|
146
|
+
render(state => {
|
|
147
|
+
board.cancelMove(state);
|
|
148
|
+
dragCancel(state);
|
|
149
|
+
}, state);
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
stop(): void {
|
|
153
|
+
render(state => {
|
|
154
|
+
board.stop(state);
|
|
155
|
+
dragCancel(state);
|
|
156
|
+
}, state);
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
explode(keys: cg.Key[]): void {
|
|
160
|
+
explosion(state, keys);
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
setAutoShapes(shapes: DrawShape[]): void {
|
|
164
|
+
render(state => (state.drawable.autoShapes = shapes), state);
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
setShapes(shapes: DrawShape[]): void {
|
|
168
|
+
render(state => (state.drawable.shapes = shapes.slice()), state);
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
getKeyAtDomPos(pos): cg.Key | undefined {
|
|
172
|
+
return board.getKeyAtDomPos(pos, board.whitePov(state), state.dom.bounds());
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
redrawAll,
|
|
176
|
+
|
|
177
|
+
dragNewPiece(piece, event, force): void {
|
|
178
|
+
dragNewPiece(state, piece, event, force);
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
destroy(): void {
|
|
182
|
+
board.stop(state);
|
|
183
|
+
state.dom.unbind && state.dom.unbind();
|
|
184
|
+
state.dom.destroyed = true;
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { State } from './state.js';
|
|
2
|
+
import { key2pos, createEl, posToTranslate as posToTranslateFromBounds, translateAndScale } from './util.js';
|
|
3
|
+
import { whitePov } from './board.js';
|
|
4
|
+
import * as cg from './types.js';
|
|
5
|
+
import { DrawShape } from './draw.js';
|
|
6
|
+
import { SyncableShape, Hash, syncShapes } from './sync.js';
|
|
7
|
+
|
|
8
|
+
export function render(state: State, autoPieceEl: HTMLElement): void {
|
|
9
|
+
const autoPieces = state.drawable.autoShapes.filter(autoShape => autoShape.piece);
|
|
10
|
+
const autoPieceShapes: SyncableShape[] = autoPieces.map((s: DrawShape) => {
|
|
11
|
+
return {
|
|
12
|
+
shape: s,
|
|
13
|
+
hash: hash(s),
|
|
14
|
+
current: false,
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
syncShapes(autoPieceShapes, autoPieceEl, shape => renderShape(state, shape, state.dom.bounds()));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function renderResized(state: State): void {
|
|
22
|
+
const asWhite: boolean = whitePov(state),
|
|
23
|
+
posToTranslate = posToTranslateFromBounds(state.dom.bounds());
|
|
24
|
+
let el = state.dom.elements.autoPieces?.firstChild as cg.PieceNode | undefined;
|
|
25
|
+
while (el) {
|
|
26
|
+
translateAndScale(el, posToTranslate(key2pos(el.cgKey), asWhite), el.cgScale);
|
|
27
|
+
el = el.nextSibling as cg.PieceNode | undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function renderShape(state: State, { shape, hash }: SyncableShape, bounds: DOMRectReadOnly): cg.PieceNode {
|
|
32
|
+
const orig = shape.orig;
|
|
33
|
+
const role = shape.piece?.role;
|
|
34
|
+
const color = shape.piece?.color;
|
|
35
|
+
const scale = shape.piece?.scale;
|
|
36
|
+
|
|
37
|
+
const pieceEl = createEl('piece', `${role} ${color}`) as cg.PieceNode;
|
|
38
|
+
pieceEl.setAttribute('cgHash', hash);
|
|
39
|
+
pieceEl.cgKey = orig;
|
|
40
|
+
pieceEl.cgScale = scale;
|
|
41
|
+
translateAndScale(pieceEl, posToTranslateFromBounds(bounds)(key2pos(orig), whitePov(state)), scale);
|
|
42
|
+
|
|
43
|
+
return pieceEl;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const hash = (autoPiece: DrawShape): Hash =>
|
|
47
|
+
[autoPiece.orig, autoPiece.piece?.role, autoPiece.piece?.color, autoPiece.piece?.scale].join(',');
|
package/src/board.ts
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { HeadlessState } from './state.js';
|
|
2
|
+
import { pos2key, key2pos, opposite, distanceSq, allPos, computeSquareCenter } from './util.js';
|
|
3
|
+
import { premove, queen, knight } from './premove.js';
|
|
4
|
+
import * as cg from './types.js';
|
|
5
|
+
|
|
6
|
+
export function callUserFunction<T extends (...args: any[]) => void>(
|
|
7
|
+
f: T | undefined,
|
|
8
|
+
...args: Parameters<T>
|
|
9
|
+
): void {
|
|
10
|
+
if (f) setTimeout(() => f(...args), 1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function toggleOrientation(state: HeadlessState): void {
|
|
14
|
+
state.orientation = opposite(state.orientation);
|
|
15
|
+
state.animation.current = state.draggable.current = state.selected = undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function reset(state: HeadlessState): void {
|
|
19
|
+
state.lastMove = undefined;
|
|
20
|
+
unselect(state);
|
|
21
|
+
unsetPremove(state);
|
|
22
|
+
unsetPredrop(state);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function setPieces(state: HeadlessState, pieces: cg.PiecesDiff): void {
|
|
26
|
+
for (const [key, piece] of pieces) {
|
|
27
|
+
if (piece) state.pieces.set(key, piece);
|
|
28
|
+
else state.pieces.delete(key);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function setCheck(state: HeadlessState, color: cg.Color | boolean): void {
|
|
33
|
+
state.check = undefined;
|
|
34
|
+
if (color === true) color = state.turnColor;
|
|
35
|
+
if (color)
|
|
36
|
+
for (const [k, p] of state.pieces) {
|
|
37
|
+
if (p.role === 'king' && p.color === color) {
|
|
38
|
+
state.check = k;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function setPremove(state: HeadlessState, orig: cg.Key, dest: cg.Key, meta: cg.SetPremoveMetadata): void {
|
|
44
|
+
unsetPredrop(state);
|
|
45
|
+
state.premovable.current = [orig, dest];
|
|
46
|
+
callUserFunction(state.premovable.events.set, orig, dest, meta);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function unsetPremove(state: HeadlessState): void {
|
|
50
|
+
if (state.premovable.current) {
|
|
51
|
+
state.premovable.current = undefined;
|
|
52
|
+
callUserFunction(state.premovable.events.unset);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function setPredrop(state: HeadlessState, role: cg.Role, key: cg.Key): void {
|
|
57
|
+
unsetPremove(state);
|
|
58
|
+
state.predroppable.current = { role, key };
|
|
59
|
+
callUserFunction(state.predroppable.events.set, role, key);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function unsetPredrop(state: HeadlessState): void {
|
|
63
|
+
const pd = state.predroppable;
|
|
64
|
+
if (pd.current) {
|
|
65
|
+
pd.current = undefined;
|
|
66
|
+
callUserFunction(pd.events.unset);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function tryAutoCastle(state: HeadlessState, orig: cg.Key, dest: cg.Key): boolean {
|
|
71
|
+
if (!state.autoCastle) return false;
|
|
72
|
+
|
|
73
|
+
const king = state.pieces.get(orig);
|
|
74
|
+
if (!king || king.role !== 'king') return false;
|
|
75
|
+
|
|
76
|
+
const origPos = key2pos(orig);
|
|
77
|
+
const destPos = key2pos(dest);
|
|
78
|
+
if ((origPos[1] !== 0 && origPos[1] !== 7) || origPos[1] !== destPos[1]) return false;
|
|
79
|
+
if (origPos[0] === 4 && !state.pieces.has(dest)) {
|
|
80
|
+
if (destPos[0] === 6) dest = pos2key([7, destPos[1]]);
|
|
81
|
+
else if (destPos[0] === 2) dest = pos2key([0, destPos[1]]);
|
|
82
|
+
}
|
|
83
|
+
const rook = state.pieces.get(dest);
|
|
84
|
+
if (!rook || rook.color !== king.color || rook.role !== 'rook') return false;
|
|
85
|
+
|
|
86
|
+
state.pieces.delete(orig);
|
|
87
|
+
state.pieces.delete(dest);
|
|
88
|
+
|
|
89
|
+
if (origPos[0] < destPos[0]) {
|
|
90
|
+
state.pieces.set(pos2key([6, destPos[1]]), king);
|
|
91
|
+
state.pieces.set(pos2key([5, destPos[1]]), rook);
|
|
92
|
+
} else {
|
|
93
|
+
state.pieces.set(pos2key([2, destPos[1]]), king);
|
|
94
|
+
state.pieces.set(pos2key([3, destPos[1]]), rook);
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function baseMove(state: HeadlessState, orig: cg.Key, dest: cg.Key): cg.Piece | boolean {
|
|
100
|
+
const origPiece = state.pieces.get(orig),
|
|
101
|
+
destPiece = state.pieces.get(dest);
|
|
102
|
+
if (orig === dest || !origPiece) return false;
|
|
103
|
+
const captured = destPiece && destPiece.color !== origPiece.color ? destPiece : undefined;
|
|
104
|
+
if (dest === state.selected) unselect(state);
|
|
105
|
+
callUserFunction(state.events.move, orig, dest, captured);
|
|
106
|
+
if (!tryAutoCastle(state, orig, dest)) {
|
|
107
|
+
state.pieces.set(dest, origPiece);
|
|
108
|
+
state.pieces.delete(orig);
|
|
109
|
+
}
|
|
110
|
+
state.lastMove = [orig, dest];
|
|
111
|
+
state.check = undefined;
|
|
112
|
+
callUserFunction(state.events.change);
|
|
113
|
+
return captured || true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function baseNewPiece(state: HeadlessState, piece: cg.Piece, key: cg.Key, force?: boolean): boolean {
|
|
117
|
+
if (state.pieces.has(key)) {
|
|
118
|
+
if (force) state.pieces.delete(key);
|
|
119
|
+
else return false;
|
|
120
|
+
}
|
|
121
|
+
callUserFunction(state.events.dropNewPiece, piece, key);
|
|
122
|
+
state.pieces.set(key, piece);
|
|
123
|
+
state.lastMove = [key];
|
|
124
|
+
state.check = undefined;
|
|
125
|
+
callUserFunction(state.events.change);
|
|
126
|
+
state.movable.dests = undefined;
|
|
127
|
+
state.turnColor = opposite(state.turnColor);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function baseUserMove(state: HeadlessState, orig: cg.Key, dest: cg.Key): cg.Piece | boolean {
|
|
132
|
+
const result = baseMove(state, orig, dest);
|
|
133
|
+
if (result) {
|
|
134
|
+
state.movable.dests = undefined;
|
|
135
|
+
state.turnColor = opposite(state.turnColor);
|
|
136
|
+
state.animation.current = undefined;
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function userMove(state: HeadlessState, orig: cg.Key, dest: cg.Key): boolean {
|
|
142
|
+
if (canMove(state, orig, dest)) {
|
|
143
|
+
const result = baseUserMove(state, orig, dest);
|
|
144
|
+
if (result) {
|
|
145
|
+
const holdTime = state.hold.stop();
|
|
146
|
+
unselect(state);
|
|
147
|
+
const metadata: cg.MoveMetadata = {
|
|
148
|
+
premove: false,
|
|
149
|
+
ctrlKey: state.stats.ctrlKey,
|
|
150
|
+
holdTime,
|
|
151
|
+
};
|
|
152
|
+
if (result !== true) metadata.captured = result;
|
|
153
|
+
callUserFunction(state.movable.events.after, orig, dest, metadata);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
} else if (canPremove(state, orig, dest)) {
|
|
157
|
+
setPremove(state, orig, dest, {
|
|
158
|
+
ctrlKey: state.stats.ctrlKey,
|
|
159
|
+
});
|
|
160
|
+
unselect(state);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
unselect(state);
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function dropNewPiece(state: HeadlessState, orig: cg.Key, dest: cg.Key, force?: boolean): void {
|
|
168
|
+
const piece = state.pieces.get(orig);
|
|
169
|
+
if (piece && (canDrop(state, orig, dest) || force)) {
|
|
170
|
+
state.pieces.delete(orig);
|
|
171
|
+
baseNewPiece(state, piece, dest, force);
|
|
172
|
+
callUserFunction(state.movable.events.afterNewPiece, piece.role, dest, {
|
|
173
|
+
premove: false,
|
|
174
|
+
predrop: false,
|
|
175
|
+
});
|
|
176
|
+
} else if (piece && canPredrop(state, orig, dest)) {
|
|
177
|
+
setPredrop(state, piece.role, dest);
|
|
178
|
+
} else {
|
|
179
|
+
unsetPremove(state);
|
|
180
|
+
unsetPredrop(state);
|
|
181
|
+
}
|
|
182
|
+
state.pieces.delete(orig);
|
|
183
|
+
unselect(state);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function selectSquare(state: HeadlessState, key: cg.Key, force?: boolean): void {
|
|
187
|
+
callUserFunction(state.events.select, key);
|
|
188
|
+
if (state.selected) {
|
|
189
|
+
if (state.selected === key && !state.draggable.enabled) {
|
|
190
|
+
unselect(state);
|
|
191
|
+
state.hold.cancel();
|
|
192
|
+
return;
|
|
193
|
+
} else if ((state.selectable.enabled || force) && state.selected !== key) {
|
|
194
|
+
if (userMove(state, state.selected, key)) {
|
|
195
|
+
state.stats.dragged = false;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (
|
|
201
|
+
(state.selectable.enabled || state.draggable.enabled) &&
|
|
202
|
+
(isMovable(state, key) || isPremovable(state, key))
|
|
203
|
+
) {
|
|
204
|
+
setSelected(state, key);
|
|
205
|
+
state.hold.start();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function setSelected(state: HeadlessState, key: cg.Key): void {
|
|
210
|
+
state.selected = key;
|
|
211
|
+
if (isPremovable(state, key)) {
|
|
212
|
+
// calculate chess premoves if custom premoves are not passed
|
|
213
|
+
if (!state.premovable.customDests) {
|
|
214
|
+
state.premovable.dests = premove(state.pieces, key, state.premovable.castle);
|
|
215
|
+
}
|
|
216
|
+
} else state.premovable.dests = undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function unselect(state: HeadlessState): void {
|
|
220
|
+
state.selected = undefined;
|
|
221
|
+
state.premovable.dests = undefined;
|
|
222
|
+
state.hold.cancel();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function isMovable(state: HeadlessState, orig: cg.Key): boolean {
|
|
226
|
+
const piece = state.pieces.get(orig);
|
|
227
|
+
return (
|
|
228
|
+
!!piece &&
|
|
229
|
+
(state.movable.color === 'both' ||
|
|
230
|
+
(state.movable.color === piece.color && state.turnColor === piece.color))
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export const canMove = (state: HeadlessState, orig: cg.Key, dest: cg.Key): boolean =>
|
|
235
|
+
orig !== dest &&
|
|
236
|
+
isMovable(state, orig) &&
|
|
237
|
+
(state.movable.free || !!state.movable.dests?.get(orig)?.includes(dest));
|
|
238
|
+
|
|
239
|
+
function canDrop(state: HeadlessState, orig: cg.Key, dest: cg.Key): boolean {
|
|
240
|
+
const piece = state.pieces.get(orig);
|
|
241
|
+
return (
|
|
242
|
+
!!piece &&
|
|
243
|
+
(orig === dest || !state.pieces.has(dest)) &&
|
|
244
|
+
(state.movable.color === 'both' ||
|
|
245
|
+
(state.movable.color === piece.color && state.turnColor === piece.color))
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function isPremovable(state: HeadlessState, orig: cg.Key): boolean {
|
|
250
|
+
const piece = state.pieces.get(orig);
|
|
251
|
+
return (
|
|
252
|
+
!!piece &&
|
|
253
|
+
state.premovable.enabled &&
|
|
254
|
+
state.movable.color === piece.color &&
|
|
255
|
+
state.turnColor !== piece.color
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function canPremove(state: HeadlessState, orig: cg.Key, dest: cg.Key): boolean {
|
|
260
|
+
const validPremoves: cg.Key[] =
|
|
261
|
+
state.premovable.customDests?.get(orig) ?? premove(state.pieces, orig, state.premovable.castle);
|
|
262
|
+
return orig !== dest && isPremovable(state, orig) && validPremoves.includes(dest);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function canPredrop(state: HeadlessState, orig: cg.Key, dest: cg.Key): boolean {
|
|
266
|
+
const piece = state.pieces.get(orig);
|
|
267
|
+
const destPiece = state.pieces.get(dest);
|
|
268
|
+
return (
|
|
269
|
+
!!piece &&
|
|
270
|
+
(!destPiece || destPiece.color !== state.movable.color) &&
|
|
271
|
+
state.predroppable.enabled &&
|
|
272
|
+
(piece.role !== 'pawn' || (dest[1] !== '1' && dest[1] !== '8')) &&
|
|
273
|
+
state.movable.color === piece.color &&
|
|
274
|
+
state.turnColor !== piece.color
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function isDraggable(state: HeadlessState, orig: cg.Key): boolean {
|
|
279
|
+
const piece = state.pieces.get(orig);
|
|
280
|
+
return (
|
|
281
|
+
!!piece &&
|
|
282
|
+
state.draggable.enabled &&
|
|
283
|
+
(state.movable.color === 'both' ||
|
|
284
|
+
(state.movable.color === piece.color && (state.turnColor === piece.color || state.premovable.enabled)))
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function playPremove(state: HeadlessState): boolean {
|
|
289
|
+
const move = state.premovable.current;
|
|
290
|
+
if (!move) return false;
|
|
291
|
+
const orig = move[0],
|
|
292
|
+
dest = move[1];
|
|
293
|
+
let success = false;
|
|
294
|
+
if (canMove(state, orig, dest)) {
|
|
295
|
+
const result = baseUserMove(state, orig, dest);
|
|
296
|
+
if (result) {
|
|
297
|
+
const metadata: cg.MoveMetadata = { premove: true };
|
|
298
|
+
if (result !== true) metadata.captured = result;
|
|
299
|
+
callUserFunction(state.movable.events.after, orig, dest, metadata);
|
|
300
|
+
success = true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
unsetPremove(state);
|
|
304
|
+
return success;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function playPredrop(state: HeadlessState, validate: (drop: cg.Drop) => boolean): boolean {
|
|
308
|
+
const drop = state.predroppable.current;
|
|
309
|
+
let success = false;
|
|
310
|
+
if (!drop) return false;
|
|
311
|
+
if (validate(drop)) {
|
|
312
|
+
const piece = {
|
|
313
|
+
role: drop.role,
|
|
314
|
+
color: state.movable.color,
|
|
315
|
+
} as cg.Piece;
|
|
316
|
+
if (baseNewPiece(state, piece, drop.key)) {
|
|
317
|
+
callUserFunction(state.movable.events.afterNewPiece, drop.role, drop.key, {
|
|
318
|
+
premove: false,
|
|
319
|
+
predrop: true,
|
|
320
|
+
});
|
|
321
|
+
success = true;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
unsetPredrop(state);
|
|
325
|
+
return success;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function cancelMove(state: HeadlessState): void {
|
|
329
|
+
unsetPremove(state);
|
|
330
|
+
unsetPredrop(state);
|
|
331
|
+
unselect(state);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function stop(state: HeadlessState): void {
|
|
335
|
+
state.movable.color = state.movable.dests = state.animation.current = undefined;
|
|
336
|
+
cancelMove(state);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function getKeyAtDomPos(
|
|
340
|
+
pos: cg.NumberPair,
|
|
341
|
+
asWhite: boolean,
|
|
342
|
+
bounds: DOMRectReadOnly,
|
|
343
|
+
): cg.Key | undefined {
|
|
344
|
+
let file = Math.floor((8 * (pos[0] - bounds.left)) / bounds.width);
|
|
345
|
+
if (!asWhite) file = 7 - file;
|
|
346
|
+
let rank = 7 - Math.floor((8 * (pos[1] - bounds.top)) / bounds.height);
|
|
347
|
+
if (!asWhite) rank = 7 - rank;
|
|
348
|
+
return file >= 0 && file < 8 && rank >= 0 && rank < 8 ? pos2key([file, rank]) : undefined;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function getSnappedKeyAtDomPos(
|
|
352
|
+
orig: cg.Key,
|
|
353
|
+
pos: cg.NumberPair,
|
|
354
|
+
asWhite: boolean,
|
|
355
|
+
bounds: DOMRectReadOnly,
|
|
356
|
+
): cg.Key | undefined {
|
|
357
|
+
const origPos = key2pos(orig);
|
|
358
|
+
const validSnapPos = allPos.filter(
|
|
359
|
+
pos2 =>
|
|
360
|
+
queen(origPos[0], origPos[1], pos2[0], pos2[1]) || knight(origPos[0], origPos[1], pos2[0], pos2[1]),
|
|
361
|
+
);
|
|
362
|
+
const validSnapCenters = validSnapPos.map(pos2 => computeSquareCenter(pos2key(pos2), asWhite, bounds));
|
|
363
|
+
const validSnapDistances = validSnapCenters.map(pos2 => distanceSq(pos, pos2));
|
|
364
|
+
const [, closestSnapIndex] = validSnapDistances.reduce(
|
|
365
|
+
(a, b, index) => (a[0] < b ? a : [b, index]),
|
|
366
|
+
[validSnapDistances[0], 0],
|
|
367
|
+
);
|
|
368
|
+
return pos2key(validSnapPos[closestSnapIndex]);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export const whitePov = (s: HeadlessState): boolean => s.orientation === 'white';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Api, start } from './api.js';
|
|
2
|
+
import { Config, configure } from './config.js';
|
|
3
|
+
import { HeadlessState, State, defaults } from './state.js';
|
|
4
|
+
|
|
5
|
+
import { renderWrap } from './wrap.js';
|
|
6
|
+
import * as events from './events.js';
|
|
7
|
+
import { render, renderResized, updateBounds } from './render.js';
|
|
8
|
+
import * as autoPieces from './autoPieces.js';
|
|
9
|
+
import * as svg from './svg.js';
|
|
10
|
+
import * as util from './util.js';
|
|
11
|
+
|
|
12
|
+
export function initModule({ el, config }: { el: HTMLElement; config?: Config }): Api {
|
|
13
|
+
return Chessground(el, config);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function Chessground(element: HTMLElement, config?: Config): Api {
|
|
17
|
+
const maybeState: State | HeadlessState = defaults();
|
|
18
|
+
|
|
19
|
+
configure(maybeState, config || {});
|
|
20
|
+
|
|
21
|
+
function redrawAll(): State {
|
|
22
|
+
const prevUnbind = 'dom' in maybeState ? maybeState.dom.unbind : undefined;
|
|
23
|
+
// compute bounds from existing board element if possible
|
|
24
|
+
// this allows non-square boards from CSS to be handled (for 3D)
|
|
25
|
+
const elements = renderWrap(element, maybeState),
|
|
26
|
+
bounds = util.memo(() => elements.board.getBoundingClientRect()),
|
|
27
|
+
redrawNow = (skipSvg?: boolean): void => {
|
|
28
|
+
render(state);
|
|
29
|
+
if (elements.autoPieces) autoPieces.render(state, elements.autoPieces);
|
|
30
|
+
if (!skipSvg && elements.svg) svg.renderSvg(state, elements.svg, elements.customSvg!);
|
|
31
|
+
},
|
|
32
|
+
onResize = (): void => {
|
|
33
|
+
updateBounds(state);
|
|
34
|
+
renderResized(state);
|
|
35
|
+
if (elements.autoPieces) autoPieces.renderResized(state);
|
|
36
|
+
};
|
|
37
|
+
const state = maybeState as State;
|
|
38
|
+
state.dom = {
|
|
39
|
+
elements,
|
|
40
|
+
bounds,
|
|
41
|
+
redraw: debounceRedraw(redrawNow),
|
|
42
|
+
redrawNow,
|
|
43
|
+
unbind: prevUnbind,
|
|
44
|
+
};
|
|
45
|
+
state.drawable.prevSvgHash = '';
|
|
46
|
+
updateBounds(state);
|
|
47
|
+
redrawNow(false);
|
|
48
|
+
events.bindBoard(state, onResize);
|
|
49
|
+
if (!prevUnbind) state.dom.unbind = events.bindDocument(state, onResize);
|
|
50
|
+
state.events.insert && state.events.insert(elements);
|
|
51
|
+
return state;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return start(redrawAll(), redrawAll);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function debounceRedraw(redrawNow: (skipSvg?: boolean) => void): () => void {
|
|
58
|
+
let redrawing = false;
|
|
59
|
+
return () => {
|
|
60
|
+
if (redrawing) return;
|
|
61
|
+
redrawing = true;
|
|
62
|
+
requestAnimationFrame(() => {
|
|
63
|
+
redrawNow();
|
|
64
|
+
redrawing = false;
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
}
|