chesspal 0.1.0 → 0.2.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/README.md +45 -82
- package/dist/cli/chat-hook.js +1 -1
- package/dist/cli/command-line.d.ts +1 -2
- package/dist/cli/command-line.js +2 -3
- package/dist/commands/agents.d.ts +26 -0
- package/dist/commands/agents.js +72 -0
- package/dist/commands/ai-move.d.ts +5 -14
- package/dist/commands/ai-move.js +28 -35
- package/dist/commands/ai-say.d.ts +3 -1
- package/dist/commands/ai-say.js +24 -6
- package/dist/commands/analyse-game.d.ts +4 -0
- package/dist/commands/analyse-game.js +43 -16
- package/dist/commands/annotate-game.d.ts +4 -0
- package/dist/commands/annotate-game.js +65 -12
- package/dist/commands/answer-lines.d.ts +28 -0
- package/dist/commands/answer-lines.js +144 -0
- package/dist/commands/coach.d.ts +45 -0
- package/dist/commands/coach.js +344 -0
- package/dist/commands/context.d.ts +16 -5
- package/dist/commands/context.js +1 -0
- package/dist/commands/delete-game.d.ts +7 -0
- package/dist/commands/delete-game.js +30 -0
- package/dist/commands/delete-review.d.ts +8 -0
- package/dist/commands/delete-review.js +34 -0
- package/dist/commands/drawings.d.ts +0 -2
- package/dist/commands/drawings.js +5 -3
- package/dist/commands/end-game.js +5 -2
- package/dist/commands/export-game.d.ts +1 -1
- package/dist/commands/export-game.js +4 -3
- package/dist/commands/game-summary.d.ts +21 -38
- package/dist/commands/game-summary.js +30 -167
- package/dist/commands/index.d.ts +15 -0
- package/dist/commands/index.js +47 -2
- package/dist/commands/init.d.ts +3 -3
- package/dist/commands/init.js +4 -4
- package/dist/commands/lead-input.d.ts +8 -0
- package/dist/commands/lead-input.js +21 -0
- package/dist/commands/look.js +4 -1
- package/dist/commands/new-game.d.ts +2 -3
- package/dist/commands/new-game.js +7 -4
- package/dist/commands/play-move.d.ts +8 -15
- package/dist/commands/play-move.js +5 -13
- package/dist/commands/player-move.d.ts +2 -6
- package/dist/commands/player-move.js +13 -6
- package/dist/commands/player-say.d.ts +1 -1
- package/dist/commands/player-say.js +1 -1
- package/dist/commands/ponder.d.ts +10 -0
- package/dist/commands/ponder.js +33 -21
- package/dist/commands/position.d.ts +1 -1
- package/dist/commands/position.js +3 -1
- package/dist/commands/profiles.d.ts +13 -4
- package/dist/commands/profiles.js +49 -15
- package/dist/commands/registry.d.ts +8 -3
- package/dist/commands/registry.js +12 -11
- package/dist/commands/say.d.ts +1 -1
- package/dist/commands/say.js +16 -16
- package/dist/commands/speech.d.ts +8 -0
- package/dist/commands/speech.js +20 -0
- package/dist/commands/status.d.ts +9 -3
- package/dist/commands/status.js +40 -15
- package/dist/commands/study-games.js +1 -1
- package/dist/commands/study.js +3 -19
- package/dist/core/facts.js +15 -15
- package/dist/core/game.d.ts +3 -1
- package/dist/core/game.js +8 -4
- package/dist/core/illegal.js +1 -1
- package/dist/core/index.d.ts +5 -4
- package/dist/core/index.js +5 -4
- package/dist/core/lead.d.ts +15 -0
- package/dist/core/lead.js +40 -0
- package/dist/core/levels.d.ts +1 -1
- package/dist/core/levels.js +1 -1
- package/dist/core/moods.d.ts +0 -2
- package/dist/core/moods.js +2 -2
- package/dist/core/openings.d.ts +0 -2
- package/dist/core/openings.js +1 -1
- package/dist/core/profile.js +1 -1
- package/dist/core/record.d.ts +1 -1
- package/dist/core/record.js +1 -1
- package/dist/core/review.d.ts +9 -3
- package/dist/core/review.js +29 -9
- package/dist/games/analysis.d.ts +1 -1
- package/dist/games/archive.d.ts +24 -3
- package/dist/games/archive.js +96 -22
- package/dist/games/coach.d.ts +57 -0
- package/dist/games/coach.js +144 -0
- package/dist/games/current-game.d.ts +2 -0
- package/dist/games/current-game.js +7 -1
- package/dist/games/files.js +1 -0
- package/dist/games/index.d.ts +4 -3
- package/dist/games/index.js +4 -3
- package/dist/games/talk.d.ts +1 -5
- package/dist/games/talk.js +14 -14
- package/dist/home/open-folder.js +1 -1
- package/dist/host-files/{ponder-agents.d.ts → agents.d.ts} +10 -2
- package/dist/host-files/agents.js +53 -0
- package/dist/host-files/host-files.d.ts +7 -2
- package/dist/host-files/host-files.js +27 -12
- package/dist/host-files/index.d.ts +2 -2
- package/dist/host-files/index.js +3 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -0
- package/dist/page/app/anchor.js +88 -0
- package/dist/page/{app.js → app/app.js} +112 -230
- package/dist/page/app/keys.js +94 -0
- package/dist/page/app/phase.js +30 -0
- package/dist/page/app/served.js +73 -0
- package/dist/page/avatars/avatars.js +19 -0
- package/dist/page/avatars/thought.js +10 -0
- package/dist/page/base.css +160 -0
- package/dist/page/board/board.css +959 -0
- package/dist/page/board/board.js +125 -0
- package/dist/page/{explain.js → board/explain.js} +1 -1
- package/dist/page/board/marks.js +160 -0
- package/dist/page/board/motion.js +71 -0
- package/dist/page/board/pointer.js +239 -0
- package/dist/page/board/squares.js +26 -0
- package/dist/page/dialogs/analyses.js +195 -0
- package/dist/page/{dialog.js → dialogs/dialog.js} +1 -1
- package/dist/page/dialogs/dialogs.css +1228 -0
- package/dist/page/{new-game-dialog.js → dialogs/new-game.js} +3 -3
- package/dist/page/{profile-dialogs.js → dialogs/profiles.js} +40 -11
- package/dist/page/dialogs/search.js +31 -0
- package/dist/page/dialogs/settings.js +42 -0
- package/dist/page/dialogs/studied.js +39 -0
- package/dist/page/dialogs/study.js +51 -0
- package/dist/page/{badges.js → game/badges.js} +1 -1
- package/dist/page/{browse.js → game/browse.js} +1 -1
- package/dist/page/game/card-fold.js +40 -0
- package/dist/page/game/column.js +51 -0
- package/dist/page/{ending.js → game/ending.js} +2 -2
- package/dist/page/game/game.css +577 -0
- package/dist/page/{move-list.js → game/move-list.js} +10 -6
- package/dist/page/{plies.js → game/plies.js} +30 -10
- package/dist/page/game/result-tile.js +14 -0
- package/dist/page/helps/helps.css +689 -0
- package/dist/page/helps/helps.js +220 -0
- package/dist/page/helps/view.js +127 -0
- package/dist/page/layout/fold-tab.js +11 -0
- package/dist/page/{folds.js → layout/folds.js} +1 -1
- package/dist/page/{header.js → layout/header.js} +5 -4
- package/dist/page/layout/layout.css +834 -0
- package/dist/page/lead/lead.js +82 -0
- package/dist/page/lead/view.js +38 -0
- package/dist/page/main.js +2 -2
- package/dist/page/pieces/CREDITS.md +12 -29
- package/dist/page/pieces/capablanca-small.svg +20 -0
- package/dist/page/pieces/capablanca.svg +22 -0
- package/dist/page/pieces/nitida-small.svg +3 -2
- package/dist/page/pieces/nitida.svg +3 -2
- package/dist/page/{pieces.js → pieces/pieces.js} +2 -2
- package/dist/page/pieces/torretta-small.svg +15 -14
- package/dist/page/pieces/torretta.svg +17 -15
- package/dist/page/{player-card.js → players/card.js} +2 -6
- package/dist/page/{clock-time.js → players/clock-time.js} +2 -2
- package/dist/page/{profile.js → profile/profile.js} +9 -5
- package/dist/page/{settings.js → profile/settings.js} +5 -4
- package/dist/page/style.css +9 -4398
- package/dist/page/talk/talk.css +958 -0
- package/dist/page/{talk.js → talk/talk.js} +39 -46
- package/dist/page/text.js +137 -19
- package/dist/page/theme.css +292 -0
- package/dist/profiles/index.d.ts +1 -1
- package/dist/profiles/profiles.d.ts +15 -4
- package/dist/profiles/profiles.js +24 -7
- package/dist/server/bell.d.ts +10 -2
- package/dist/server/bell.js +28 -12
- package/dist/server/caller.d.ts +14 -3
- package/dist/server/caller.js +44 -11
- package/dist/server/mcp.d.ts +1 -2
- package/dist/server/mcp.js +42 -14
- package/dist/server/page/analyses.d.ts +23 -0
- package/dist/server/page/analyses.js +65 -0
- package/dist/server/page/files.d.ts +5 -0
- package/dist/server/page/files.js +92 -0
- package/dist/server/page/game.d.ts +6 -0
- package/dist/server/page/game.js +381 -0
- package/dist/server/{http.d.ts → page/http.d.ts} +13 -0
- package/dist/server/{http.js → page/http.js} +11 -0
- package/dist/server/{page.d.ts → page/page.d.ts} +8 -13
- package/dist/server/page/page.js +229 -0
- package/dist/server/{profile-routes.d.ts → page/profiles.d.ts} +2 -2
- package/dist/server/{profile-routes.js → page/profiles.js} +47 -18
- package/instructions/analysis.md +4 -0
- package/instructions/analyst-agent.md +8 -0
- package/instructions/analyst.md +3 -7
- package/instructions/coach.md +11 -0
- package/instructions/opponent.md +4 -4
- package/package.json +1 -1
- package/dist/host-files/ponder-agents.js +0 -40
- package/dist/page/avatars.js +0 -16
- package/dist/page/board.js +0 -566
- package/dist/page/fold-tab.js +0 -9
- package/dist/page/game-column.js +0 -44
- package/dist/page/helps-view.js +0 -51
- package/dist/page/helps.js +0 -90
- package/dist/page/pieces/cburnett-small.svg +0 -20
- package/dist/page/pieces/cburnett.svg +0 -20
- package/dist/page/settings-panel.js +0 -32
- package/dist/page/studied.js +0 -14
- package/dist/page/study-dialog.js +0 -58
- package/dist/server/analyses.d.ts +0 -17
- package/dist/server/analyses.js +0 -46
- package/dist/server/page.js +0 -657
- /package/dist/page/{torretta.js → avatars/torretta.js} +0 -0
- /package/dist/page/{opening-book.js → game/opening-book.js} +0 -0
- /package/dist/page/{model-name.js → players/model-name.js} +0 -0
- /package/dist/page/{sounds.js → sounds/sounds.js} +0 -0
- /package/dist/page/{voice.js → talk/voice.js} +0 -0
package/dist/page/board.js
DELETED
|
@@ -1,566 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
|
|
2
|
-
// The board: eight ranks of squares from a FEN, one side at the bottom, Zoe's pieces (T-9), the ranks and files on the edge squares. The player moves
|
|
3
|
-
// with two clicks or by dragging a piece, which lifts off the board, in Zoe's design (T-70).
|
|
4
|
-
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
|
5
|
-
import { BadgeIcon } from "./badges.js";
|
|
6
|
-
import { bubblePlace, keptSquares } from "./explain.js";
|
|
7
|
-
import { atRiskOn } from "./helps-view.js";
|
|
8
|
-
import { Notch } from "./helps.js";
|
|
9
|
-
import { Piece } from "./pieces.js";
|
|
10
|
-
/**
|
|
11
|
-
* The board of a position, seen from one side, with the last move's squares marked. While the player may move, their pieces answer the pointer: a press
|
|
12
|
-
* picks one, and the legal moves of the piece picked show as dots, rings on captures; a second press on one of them, or a drag there, plays it. At any time
|
|
13
|
-
* the right button lights a square, or draws an arrow when dragged, until a left click.
|
|
14
|
-
*/
|
|
15
|
-
export function Board({ fen, bottom, lastMove, pointed, helps, control, badge, drawn, nextMove, jumped, check, end, player, selected, targets, dots, notice, promotion, onSelect, onMove, onRefuse, onPromote, text }) {
|
|
16
|
-
const pieces = piecesOf(fen);
|
|
17
|
-
const mated = end?.how === "checkmate" ? kingToMove(fen, pieces) : undefined;
|
|
18
|
-
const signs = end === undefined ? noSigns : kingSigns(pieces, fen.split(" ")[1] === "b" ? "black" : "white", end);
|
|
19
|
-
const ranks = bottom === "white" ? [8, 7, 6, 5, 4, 3, 2, 1] : [1, 2, 3, 4, 5, 6, 7, 8];
|
|
20
|
-
const columns = bottom === "white" ? [...files] : [...files].reverse();
|
|
21
|
-
const seen = (square) => [columns.indexOf(square[0]), ranks.indexOf(Number(square[1]))];
|
|
22
|
-
const centreOf = (square) => [columns.indexOf(square[0]) + 0.5, ranks.indexOf(Number(square[1])) + 0.5];
|
|
23
|
-
const board = useRef(null);
|
|
24
|
-
const press = useRef(undefined);
|
|
25
|
-
const [drag, setDrag] = useState(undefined);
|
|
26
|
-
const marking = useRef(undefined);
|
|
27
|
-
const [marks, setMarks] = useState(noMarks);
|
|
28
|
-
const [drawing, setDrawing] = useState(undefined);
|
|
29
|
-
// A new position slides the pieces of its move there, but for the one the player dragged, and shakes a king in check (T-74).
|
|
30
|
-
const before = useRef(undefined);
|
|
31
|
-
const dragged = useRef(undefined);
|
|
32
|
-
const motion = useMemo(() => {
|
|
33
|
-
const was = before.current === undefined ? undefined : piecesOf(before.current);
|
|
34
|
-
before.current = fen;
|
|
35
|
-
// A move played on the position before slides there, a step back over one slides it home; a jump through the game moves every piece to its place (T-88).
|
|
36
|
-
const fresh = was !== undefined && jumped !== true && lastMove !== undefined && playedOn(was, pieces, lastMove);
|
|
37
|
-
const undone = was !== undefined && jumped !== true && nextMove !== undefined && playedOn(pieces, was, nextMove);
|
|
38
|
-
const moved = was === undefined
|
|
39
|
-
? noMotion
|
|
40
|
-
: fresh
|
|
41
|
-
? motionOf(lastMove, dragged.current, check ? kingToMove(fen, pieces) : undefined, was)
|
|
42
|
-
: undone
|
|
43
|
-
? backOf(nextMove)
|
|
44
|
-
: jumped === true
|
|
45
|
-
? rearranged(was, pieces)
|
|
46
|
-
: noMotion;
|
|
47
|
-
dragged.current = undefined;
|
|
48
|
-
return moved;
|
|
49
|
-
}, [fen]);
|
|
50
|
-
const own = (square) => {
|
|
51
|
-
const piece = pieces.get(square);
|
|
52
|
-
return player !== undefined && piece !== undefined && (piece === piece.toUpperCase()) === (player === "white");
|
|
53
|
-
};
|
|
54
|
-
// Where a move to a square lands: there, or castling's square for a king taken onto its own rook, as on Lichess; none for a square it cannot go to.
|
|
55
|
-
const landing = (square) => targets.find((move) => move.to === square || rookOf(move) === square)?.to;
|
|
56
|
-
const isTarget = (square) => landing(square) !== undefined;
|
|
57
|
-
// A press on a square that is not the player's, and none of the piece picked's moves, puts the piece down, saying why it cannot go there.
|
|
58
|
-
const putDown = (square) => {
|
|
59
|
-
onSelect(undefined);
|
|
60
|
-
if (selected !== undefined) {
|
|
61
|
-
onRefuse(selected, square);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
// The keyboard (T-11): the arrows move between the squares as the board is turned, Enter and Space press one as a click does, Esc puts the piece down.
|
|
65
|
-
const [cursor, setCursor] = useState(undefined);
|
|
66
|
-
const focused = cursor ?? selected ?? `${columns[0]}${ranks[7]}`;
|
|
67
|
-
const key = (event, square) => {
|
|
68
|
-
const step = arrowSteps[event.key];
|
|
69
|
-
if (step !== undefined) {
|
|
70
|
-
const column = Math.min(7, Math.max(0, columns.indexOf(square[0]) + step[0]));
|
|
71
|
-
const row = Math.min(7, Math.max(0, ranks.indexOf(Number(square[1])) + step[1]));
|
|
72
|
-
const next = `${columns[column]}${ranks[row]}`;
|
|
73
|
-
event.preventDefault();
|
|
74
|
-
setCursor(next);
|
|
75
|
-
board.current.querySelector(`[data-square="${next}"]`).focus();
|
|
76
|
-
}
|
|
77
|
-
else if (event.key === "Enter" || event.key === " ") {
|
|
78
|
-
event.preventDefault();
|
|
79
|
-
pressKey(square);
|
|
80
|
-
}
|
|
81
|
-
else if (event.key === "Escape" && selected !== undefined) {
|
|
82
|
-
onSelect(undefined);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
// The focus goes back to the board after the choice of a promotion: to the pawn's square when it was cancelled, else to where it went.
|
|
86
|
-
const promote = (piece) => {
|
|
87
|
-
const back = piece === undefined ? promotion.from : promotion.to;
|
|
88
|
-
onPromote(piece);
|
|
89
|
-
setCursor(back);
|
|
90
|
-
requestAnimationFrame(() => board.current?.querySelector(`[data-square="${back}"]`)?.focus());
|
|
91
|
-
};
|
|
92
|
-
const pressKey = (square) => {
|
|
93
|
-
setMarks(noMarks);
|
|
94
|
-
if (player === undefined) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (selected !== undefined && isTarget(square)) {
|
|
98
|
-
onMove(selected, landing(square));
|
|
99
|
-
}
|
|
100
|
-
else if (own(square)) {
|
|
101
|
-
onSelect(selected === square ? undefined : square);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
putDown(square);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
// The square under a point, as the board is turned; none off the board.
|
|
108
|
-
const squareAt = (x, y) => {
|
|
109
|
-
const box = board.current.getBoundingClientRect();
|
|
110
|
-
const column = Math.floor((x - box.left) / (box.width / 8));
|
|
111
|
-
const row = Math.floor((y - box.top) / (box.height / 8));
|
|
112
|
-
return column >= 0 && column < 8 && row >= 0 && row < 8 ? `${columns[column]}${ranks[row]}` : undefined;
|
|
113
|
-
};
|
|
114
|
-
// Where a square's corner is in the window.
|
|
115
|
-
const cornerOf = (square) => {
|
|
116
|
-
const box = board.current.getBoundingClientRect();
|
|
117
|
-
const size = box.width / 8;
|
|
118
|
-
return [box.left + columns.indexOf(square[0]) * size, box.top + ranks.indexOf(Number(square[1])) * size];
|
|
119
|
-
};
|
|
120
|
-
const goBack = () => {
|
|
121
|
-
setDrag((now) => now === undefined ? undefined : { ...now, at: cornerOf(now.from), over: undefined, back: true });
|
|
122
|
-
};
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
if (drag === undefined) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
// Esc puts the piece back; a piece going back lands within 260 ms, whether its transition ends or not.
|
|
128
|
-
const escape = (event) => {
|
|
129
|
-
if (event.key === "Escape") {
|
|
130
|
-
press.current = undefined;
|
|
131
|
-
goBack();
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
const landed = drag.back ? setTimeout(() => setDrag(undefined), reducedMotion() ? 0 : 260) : undefined;
|
|
135
|
-
addEventListener("keydown", escape);
|
|
136
|
-
document.documentElement.classList.add("dragging");
|
|
137
|
-
return () => {
|
|
138
|
-
removeEventListener("keydown", escape);
|
|
139
|
-
document.documentElement.classList.remove("dragging");
|
|
140
|
-
clearTimeout(landed);
|
|
141
|
-
};
|
|
142
|
-
}, [drag?.back, drag === undefined]);
|
|
143
|
-
const down = (event) => {
|
|
144
|
-
const square = squareAt(event.clientX, event.clientY);
|
|
145
|
-
if (square === undefined) {
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
// The right button marks a square, or draws an arrow when dragged (T-73); the left one clears them all, and moves.
|
|
149
|
-
if (event.button === 2) {
|
|
150
|
-
marking.current = { from: square, id: event.pointerId };
|
|
151
|
-
board.current.setPointerCapture(event.pointerId);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
if (event.button !== 0) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
setMarks(noMarks);
|
|
158
|
-
if (player === undefined || drag !== undefined) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
if (selected !== undefined && isTarget(square)) {
|
|
162
|
-
// A finger plays when it lifts, so a page scrolled from a dot plays nothing.
|
|
163
|
-
if (event.pointerType === "touch") {
|
|
164
|
-
press.current = { from: selected, to: landing(square), x: event.clientX, y: event.clientY, was: false, touch: true, id: event.pointerId, moved: false };
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
onMove(selected, landing(square));
|
|
168
|
-
}
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
if (!own(square)) {
|
|
172
|
-
putDown(square);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
press.current = { from: square, x: event.clientX, y: event.clientY, was: selected === square, touch: event.pointerType === "touch", id: event.pointerId, moved: false };
|
|
176
|
-
board.current.setPointerCapture(event.pointerId);
|
|
177
|
-
onSelect(square);
|
|
178
|
-
};
|
|
179
|
-
const moveTo = (event) => {
|
|
180
|
-
const mark = marking.current;
|
|
181
|
-
if (mark !== undefined && event.pointerId === mark.id) {
|
|
182
|
-
const to = squareAt(event.clientX, event.clientY);
|
|
183
|
-
setDrawing(to === undefined || to === mark.from ? undefined : { from: mark.from, to });
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const now = press.current;
|
|
187
|
-
if (now === undefined || event.pointerId !== now.id) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const box = board.current.getBoundingClientRect();
|
|
191
|
-
const size = box.width / 8;
|
|
192
|
-
const distance = Math.hypot(event.clientX - now.x, event.clientY - now.y);
|
|
193
|
-
if (now.to !== undefined) {
|
|
194
|
-
if (distance >= 8) {
|
|
195
|
-
press.current = undefined;
|
|
196
|
-
}
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
// A press becomes a drag past 4 px of the mouse, 8 of a finger; before, it is a click.
|
|
200
|
-
if (drag === undefined && distance < (now.touch ? 8 : 4)) {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
now.moved ||= distance >= size / 2;
|
|
204
|
-
// A finger holds the piece three quarters of a square above it, and drops it where the piece is seen; until it has gone half a square, the
|
|
205
|
-
// piece stays on its own, so a touch never pushes it a square on by mistake.
|
|
206
|
-
const over = now.touch && !now.moved ? now.from : squareAt(event.clientX, now.touch ? event.clientY - 0.75 * size : event.clientY);
|
|
207
|
-
setDrag({ from: now.from, at: [event.clientX - size / 2, event.clientY - size / 2], over, touch: now.touch, back: false });
|
|
208
|
-
};
|
|
209
|
-
const release = (event, cancelled) => {
|
|
210
|
-
const mark = marking.current;
|
|
211
|
-
if (mark !== undefined && event.pointerId === mark.id) {
|
|
212
|
-
const to = squareAt(event.clientX, event.clientY);
|
|
213
|
-
marking.current = undefined;
|
|
214
|
-
setDrawing(undefined);
|
|
215
|
-
if (!cancelled && to !== undefined) {
|
|
216
|
-
setMarks((now) => toggled(now, mark.from, to));
|
|
217
|
-
}
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
const now = press.current;
|
|
221
|
-
if (now === undefined || event.pointerId !== now.id) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
press.current = undefined;
|
|
225
|
-
if (now.to !== undefined) {
|
|
226
|
-
if (!cancelled) {
|
|
227
|
-
onMove(now.from, now.to);
|
|
228
|
-
}
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
if (drag === undefined) {
|
|
232
|
-
// A click on the piece already picked puts it down.
|
|
233
|
-
if (now.was) {
|
|
234
|
-
onSelect(undefined);
|
|
235
|
-
}
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
const to = drag.over;
|
|
239
|
-
const landed = to === undefined || to === drag.from ? undefined : landing(to);
|
|
240
|
-
if (!cancelled && landed !== undefined) {
|
|
241
|
-
setDrag(undefined);
|
|
242
|
-
dragged.current = `${drag.from}${landed}`;
|
|
243
|
-
onMove(drag.from, landed);
|
|
244
|
-
}
|
|
245
|
-
else if (!cancelled && to === drag.from) {
|
|
246
|
-
setDrag(undefined);
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
goBack();
|
|
250
|
-
// A game that changed under the drag may have given the turn away: no reason for a piece that may not move.
|
|
251
|
-
if (!cancelled && to !== undefined && to !== drag.from && player !== undefined) {
|
|
252
|
-
onRefuse(drag.from, to);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { ref: board, class: ["board", player === undefined ? "" : "playable", end === undefined ? "" : endClasses(end), control === undefined ? "" : "mapping"].filter((name) => name !== "").join(" "), role: "group", "aria-label": text.boardLabel(fen.split(" ")[1] === "b" ? "black" : "white", lastMove?.san), onPointerDown: down, onPointerMove: moveTo, onPointerUp: (event) => release(event, false), onPointerCancel: (event) => release(event, true), onContextMenu: (event) => event.preventDefault(),
|
|
257
|
-
// A click takes no focus, nor leaves it elsewhere: the arrows keep browsing the moves; a square has the focus only from the keyboard.
|
|
258
|
-
onMouseDown: (event) => {
|
|
259
|
-
event.preventDefault();
|
|
260
|
-
document.activeElement?.blur();
|
|
261
|
-
}, children: [ranks.flatMap((rank, row) => columns.map((file, column) => {
|
|
262
|
-
const square = `${file}${rank}`;
|
|
263
|
-
const piece = pieces.get(square);
|
|
264
|
-
const dark = (files.indexOf(file) + rank) % 2 === 1;
|
|
265
|
-
const marked = lastMove !== undefined && (lastMove.from === square || lastMove.to === square);
|
|
266
|
-
const target = targets.find((move) => move.to === square);
|
|
267
|
-
const sign = signs.get(square);
|
|
268
|
-
const tinted = drawn?.squares?.find((one) => one.square === square)?.colour;
|
|
269
|
-
const classes = [
|
|
270
|
-
"square",
|
|
271
|
-
dark ? "dark" : "light",
|
|
272
|
-
marked ? "last" : "",
|
|
273
|
-
mated === square ? "mated" : "",
|
|
274
|
-
sign?.classes ?? "",
|
|
275
|
-
selected === square ? "selected" : "",
|
|
276
|
-
tinted === undefined ? "" : `noted ${tinted}`,
|
|
277
|
-
marks.squares.includes(square) ? "marked" : "",
|
|
278
|
-
pointed?.includes(square) === true ? "pointed" : "",
|
|
279
|
-
notice?.explanation?.ring === square ? "culprit" : "",
|
|
280
|
-
own(square) ? "own" : "",
|
|
281
|
-
target === undefined ? "" : !dots ? "target" : target.san.includes("x") ? "hint capture" : "hint",
|
|
282
|
-
drag?.over === square && drag.over !== drag.from ? "over" : "",
|
|
283
|
-
drag?.from === square ? "from" : "",
|
|
284
|
-
motion.slides.has(square) ? "slide" : "",
|
|
285
|
-
motion.shake === square ? "shake" : "",
|
|
286
|
-
motion.appear?.has(square) === true ? "appear" : "",
|
|
287
|
-
].filter((name) => name !== "").join(" ");
|
|
288
|
-
const named = piece === undefined ? square : `${square}, ${text.piece(piece.toUpperCase(), piece === piece.toUpperCase() ? "white" : "black")}`;
|
|
289
|
-
const label = [named, mated === square ? text.checkmate : "", isTarget(square) ? text.canGo : ""].filter((part) => part !== "").join(", ");
|
|
290
|
-
const from = motion.slides.get(square);
|
|
291
|
-
const moving = from !== undefined || motion.shake === square || motion.appear?.has(square) === true;
|
|
292
|
-
// How far the sliding piece comes from, in squares as the board is turned.
|
|
293
|
-
const away = from === undefined ? undefined : {
|
|
294
|
-
"--dx": columns.indexOf(from[0]) - column,
|
|
295
|
-
"--dy": ranks.indexOf(Number(from[1])) - row,
|
|
296
|
-
};
|
|
297
|
-
return (_jsxs("div", { class: classes, role: "button", "aria-label": label, "aria-pressed": own(square) ? selected === square : undefined, tabIndex: square === focused ? 0 : -1, "data-square": square, onKeyDown: (event) => key(event, square), onFocus: () => setCursor(square),
|
|
298
|
-
// A click with no pointer behind it, as a screen reader's in browse mode, presses the square as Enter does.
|
|
299
|
-
onClick: (event) => event.detail === 0 && pressKey(square), style: away, children: [column === 0 ? _jsx("span", { class: "coordinate rank", children: rank }) : null, row === 7 ? _jsx("span", { class: "coordinate file", children: file }) : null, _jsx("span", { class: "square-name", "aria-hidden": "true", children: square }), motion.taken?.square === square
|
|
300
|
-
? (_jsx("span", { class: "taken-piece", children: _jsx(Piece, { letter: motion.taken.letter }) }, `taken ${fen}`))
|
|
301
|
-
: null, motion.gone?.filter((gone) => gone.square === square).map((gone) => (_jsx("span", { class: "gone-piece", children: _jsx(Piece, { letter: gone.letter }) }, `gone ${fen}`))), control === undefined ? null : _jsx("span", { class: `control-ground ${controlOf(control[square])}`, "aria-hidden": "true" }), piece === undefined ? null : _jsx(Piece, { letter: piece }, moving ? fen : "still"), sign === undefined ? null : _jsx(Badge, { kind: sign.badge }), helps !== undefined && atRiskOn(helps, square) ? _jsx(Notch, {}) : null, control === undefined ? null : _jsx(ControlCount, { counts: control[square] }), badge === undefined || lastMove?.to !== square ? null : _jsx(BadgeIcon, { badge: badge.badge, title: badge.name })] }, square));
|
|
302
|
-
})), helps === undefined || helps.pins.length === 0 || control !== undefined ? null : _jsx(Pins, { pins: helps.pins, at: centreOf })] }), drawn?.arrows === undefined ? null : _jsx(Arrows, { arrows: drawn.arrows, at: centreOf, occupied: (square) => pieces.has(square) }), _jsx(Arrows, { arrows: drawing === undefined ? marks.arrows : [...marks.arrows, drawing], at: centreOf }), notice?.explanation?.arrow === undefined ? null : _jsx(Arrows, { arrows: [notice.explanation.arrow], at: centreOf, explain: { occupied: pieces.has(notice.explanation.arrow.to) } }), drag === undefined || !pieces.has(drag.from) ? null : _jsx(Lifted, { drag: drag, letter: pieces.get(drag.from) }), promotion === undefined
|
|
303
|
-
? null
|
|
304
|
-
: _jsx(PromotionChoice, { side: promotion.side, column: columns.indexOf(promotion.to[0]), down: ranks[0] === Number(promotion.to[1]), onPick: promote, text: text }), notice === undefined ? null : (_jsx(Bubble, { notice: notice, place: notice.square === undefined ? undefined : seen(notice.square), where: notice.square === undefined
|
|
305
|
-
? "above"
|
|
306
|
-
: bubblePlace(seen(notice.square), keptSquares(notice.explanation).map(seen), (board.current?.clientWidth ?? 0) < narrowBoard) }))] }));
|
|
307
|
-
}
|
|
308
|
-
/** The pieces of a FEN by square, as its letters: uppercase for White. */
|
|
309
|
-
export function piecesOf(fen) {
|
|
310
|
-
const pieces = new Map();
|
|
311
|
-
const rows = (fen.split(" ")[0] ?? "").split("/");
|
|
312
|
-
rows.forEach((row, index) => {
|
|
313
|
-
let file = 0;
|
|
314
|
-
for (const letter of row) {
|
|
315
|
-
if (/\d/.test(letter)) {
|
|
316
|
-
file += Number(letter);
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
pieces.set(`${files[file]}${8 - index}`, letter);
|
|
320
|
-
file += 1;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
return pieces;
|
|
325
|
-
}
|
|
326
|
-
const noMotion = { slides: new Map() };
|
|
327
|
-
/**
|
|
328
|
-
* The last move slides, but for a piece the player dragged there (`dragged`, its squares), over the piece it takes, as it stood `before`; a castling rook
|
|
329
|
-
* slides always.
|
|
330
|
-
*/
|
|
331
|
-
function motionOf(move, dragged, shake, before) {
|
|
332
|
-
const slides = new Map();
|
|
333
|
-
let taken;
|
|
334
|
-
if (dragged !== `${move.from}${move.to}`) {
|
|
335
|
-
slides.set(move.to, move.from);
|
|
336
|
-
// A pawn that takes en passant lands beside the pawn it takes.
|
|
337
|
-
const square = before.has(move.to) ? move.to : before.get(move.from)?.toLowerCase() === "p" && move.from[0] !== move.to[0] ? `${move.to[0]}${move.from[1]}` : undefined;
|
|
338
|
-
const letter = square === undefined ? undefined : before.get(square);
|
|
339
|
-
taken = square === undefined || letter === undefined ? undefined : { square, letter };
|
|
340
|
-
}
|
|
341
|
-
const rank = move.from[1];
|
|
342
|
-
if (move.san.startsWith("O-O-O")) {
|
|
343
|
-
slides.set(`d${rank}`, `a${rank}`);
|
|
344
|
-
}
|
|
345
|
-
else if (move.san.startsWith("O-O")) {
|
|
346
|
-
slides.set(`f${rank}`, `h${rank}`);
|
|
347
|
-
}
|
|
348
|
-
return { slides, ...(taken === undefined ? {} : { taken }), ...(shake === undefined ? {} : { shake }) };
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* A jump through the game, from the pieces `before` to those `now`: each piece slides to the nearest square where one like it stands now, the closest pairs
|
|
352
|
-
* first; a piece left over fades, and one that has no piece to come from appears.
|
|
353
|
-
*/
|
|
354
|
-
function rearranged(before, now) {
|
|
355
|
-
const left = [...before].filter(([square, letter]) => now.get(square) !== letter);
|
|
356
|
-
const arrived = [...now].filter(([square, letter]) => before.get(square) !== letter);
|
|
357
|
-
const pairs = arrived
|
|
358
|
-
.flatMap(([to, letter]) => left.filter(([, was]) => was === letter).map(([from]) => ({ from, to, distance: distanceOf(from, to) })))
|
|
359
|
-
.sort((one, other) => one.distance - other.distance);
|
|
360
|
-
const slides = new Map();
|
|
361
|
-
const moved = new Set();
|
|
362
|
-
for (const { from, to } of pairs) {
|
|
363
|
-
if (!slides.has(to) && !moved.has(from)) {
|
|
364
|
-
slides.set(to, from);
|
|
365
|
-
moved.add(from);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
return {
|
|
369
|
-
slides,
|
|
370
|
-
appear: new Set(arrived.map(([square]) => square).filter((square) => !slides.has(square))),
|
|
371
|
-
gone: left.filter(([square]) => !moved.has(square)).map(([square, letter]) => ({ square, letter })),
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
/** How far apart two squares are, in squares. */
|
|
375
|
-
function distanceOf(from, to) {
|
|
376
|
-
return Math.hypot(files.indexOf(from[0]) - files.indexOf(to[0]), Number(from[1]) - Number(to[1]));
|
|
377
|
-
}
|
|
378
|
-
/** A move taken back: its piece, and a castling rook, slide home; a piece it took is there again under the one leaving. */
|
|
379
|
-
function backOf(move) {
|
|
380
|
-
const rank = move.from[1];
|
|
381
|
-
const slides = new Map([[move.from, move.to]]);
|
|
382
|
-
if (move.san.startsWith("O-O-O")) {
|
|
383
|
-
slides.set(`a${rank}`, `d${rank}`);
|
|
384
|
-
}
|
|
385
|
-
else if (move.san.startsWith("O-O")) {
|
|
386
|
-
slides.set(`h${rank}`, `f${rank}`);
|
|
387
|
-
}
|
|
388
|
-
return { slides };
|
|
389
|
-
}
|
|
390
|
-
/** Whether the pieces `now` are those `before` with the move played: the piece on its square came from where it began, a pawn there when it promoted. */
|
|
391
|
-
function playedOn(before, now, move) {
|
|
392
|
-
const piece = before.get(move.from);
|
|
393
|
-
const landed = now.get(move.to);
|
|
394
|
-
return piece !== undefined && !now.has(move.from) && (landed === piece || (move.san.includes("=") && piece.toLowerCase() === "p"));
|
|
395
|
-
}
|
|
396
|
-
/** The square of the king whose side is to move in a FEN. */
|
|
397
|
-
function kingToMove(fen, pieces) {
|
|
398
|
-
const king = fen.split(" ")[1] === "b" ? "k" : "K";
|
|
399
|
-
return [...pieces].find(([, letter]) => letter === king)?.[0];
|
|
400
|
-
}
|
|
401
|
-
const noSigns = new Map();
|
|
402
|
-
/** The kings' signs at the end, by their squares: the loser's king lies down when mated or resigning; both kings of a draw stand, the stalemated one stuck. */
|
|
403
|
-
function kingSigns(pieces, toMove, end) {
|
|
404
|
-
const kingOf = (side) => [...pieces].find(([, letter]) => letter === (side === "white" ? "K" : "k"))?.[0];
|
|
405
|
-
const signs = new Map();
|
|
406
|
-
const sign = (side, badge, classes) => {
|
|
407
|
-
const square = kingOf(side);
|
|
408
|
-
if (square !== undefined) {
|
|
409
|
-
signs.set(square, { badge, classes });
|
|
410
|
-
}
|
|
411
|
-
};
|
|
412
|
-
if (end.winner === undefined) {
|
|
413
|
-
sign("white", "half", end.how === "stalemate" && toMove === "white" ? "drawn stalled" : "drawn");
|
|
414
|
-
sign("black", "half", end.how === "stalemate" && toMove === "black" ? "drawn stalled" : "drawn");
|
|
415
|
-
return signs;
|
|
416
|
-
}
|
|
417
|
-
const loser = end.winner === "white" ? "black" : "white";
|
|
418
|
-
sign(end.winner, "crown", "winner");
|
|
419
|
-
if (end.how === "checkmate" || end.how === "resignation") {
|
|
420
|
-
sign(loser, end.how === "checkmate" ? "mate" : "flag", "fallen");
|
|
421
|
-
}
|
|
422
|
-
return signs;
|
|
423
|
-
}
|
|
424
|
-
/** The board's classes at the end: how the game ended, and, a moment after it did, whether the player won or lost, which pick the effects in the style. */
|
|
425
|
-
function endClasses(end) {
|
|
426
|
-
const side = end.winner === undefined ? "" : end.winner === end.player ? "player-won" : "player-lost";
|
|
427
|
-
const classes = ["ended", end.how === undefined ? "" : `by-${end.how.replaceAll(" ", "-")}`, end.fresh ? `fresh ${side}` : ""];
|
|
428
|
-
return classes.filter((name) => name !== "").join(" ").trim();
|
|
429
|
-
}
|
|
430
|
-
/** A king's badge in the corner of its square. */
|
|
431
|
-
function Badge({ kind }) {
|
|
432
|
-
return (_jsx("span", { class: `badge ${kind}`, "aria-hidden": "true", children: kind === "mate" ? "#" : kind === "half" ? "½" : (_jsx("svg", { viewBox: "0 0 24 24", children: kind === "crown" ? _jsx("path", { d: "M4 18L3 7L8.5 11L12 5L15.5 11L21 7L20 18Z" }) : _jsx("path", { d: "M6 21V4M6 4.5C10 2.5 13 6.5 18 4.5V13.5C13 15.5 10 11.5 6 13.5" }) })) }));
|
|
433
|
-
}
|
|
434
|
-
const noMarks = { squares: [], arrows: [] };
|
|
435
|
-
/** The marks with a square, or an arrow when the squares differ, added; taken away when they were there. */
|
|
436
|
-
function toggled(marks, from, to) {
|
|
437
|
-
if (from === to) {
|
|
438
|
-
return { ...marks, squares: marks.squares.includes(to) ? marks.squares.filter((square) => square !== to) : [...marks.squares, to] };
|
|
439
|
-
}
|
|
440
|
-
const there = marks.arrows.some((arrow) => arrow.from === from && arrow.to === to);
|
|
441
|
-
return { ...marks, arrows: there ? marks.arrows.filter((arrow) => arrow.from !== from || arrow.to !== to) : [...marks.arrows, { from, to }] };
|
|
442
|
-
}
|
|
443
|
-
/**
|
|
444
|
-
* The arrows over the board, in squares from its top left corner: a knight's turns once, the long leg first. In ink when they explain a refused move, and
|
|
445
|
-
* cut short (T-91, Zoe): off the culprit's piece, and before the piece they point at, so both stay in sight.
|
|
446
|
-
*/
|
|
447
|
-
function Arrows({ arrows, at, explain, occupied }) {
|
|
448
|
-
if (arrows.length === 0) {
|
|
449
|
-
return null;
|
|
450
|
-
}
|
|
451
|
-
return (_jsx("svg", { class: explain !== undefined ? "arrows explain" : occupied !== undefined ? "arrows drawn" : "arrows", viewBox: "0 0 8 8", "aria-hidden": "true", children: arrows.map(({ from, to, colour }) => {
|
|
452
|
-
const [startX, startY] = at(from);
|
|
453
|
-
const [pointX, pointY] = at(to);
|
|
454
|
-
const knight = Math.abs(pointX - startX) * Math.abs(pointY - startY) === 2;
|
|
455
|
-
const corner = !knight
|
|
456
|
-
? undefined
|
|
457
|
-
: Math.abs(pointY - startY) > Math.abs(pointX - startX)
|
|
458
|
-
? [startX, pointY]
|
|
459
|
-
: [pointX, startY];
|
|
460
|
-
const [x0, y0] = explain === undefined ? [startX, startY] : towards([startX, startY], corner ?? [pointX, pointY], explainTrim.start);
|
|
461
|
-
const [x1, y1] = explain?.occupied === true || occupied?.(to) === true ? towards([pointX, pointY], corner ?? [startX, startY], explainTrim.end) : [pointX, pointY];
|
|
462
|
-
const [fromX, fromY] = corner ?? [x0, y0];
|
|
463
|
-
const length = Math.hypot(x1 - fromX, y1 - fromY);
|
|
464
|
-
const [dx, dy] = [(x1 - fromX) / length, (y1 - fromY) / length];
|
|
465
|
-
const [endX, endY] = [x1 - dx * arrowHead.length, y1 - dy * arrowHead.length];
|
|
466
|
-
const side = (sign) => `${endX - dy * arrowHead.width * sign},${endY + dx * arrowHead.width * sign}`;
|
|
467
|
-
// The shaft runs halfway into the point, so no seam shows where they meet; the layer's opacity hides the overlap.
|
|
468
|
-
const [shaftX, shaftY] = [x1 - dx * arrowHead.length / 2, y1 - dy * arrowHead.length / 2];
|
|
469
|
-
const shaft = `M${x0} ${y0}${corner === undefined ? "" : ` L${corner[0]} ${corner[1]}`} L${shaftX} ${shaftY}`;
|
|
470
|
-
const head = `${side(1)} ${x1},${y1} ${side(-1)}`;
|
|
471
|
-
return (_jsxs("g", { class: colour, children: [occupied === undefined ? null : (_jsxs(_Fragment, { children: [_jsx("path", { class: "edge", d: shaft }), _jsx("polygon", { class: "edge", points: head })] })), _jsx("path", { d: shaft }), _jsx("polygon", { points: head })] }, `${from}${to}`));
|
|
472
|
-
}) }));
|
|
473
|
-
}
|
|
474
|
-
/** Each pin as a dashed line from the pinning piece through the pinned one to what stands behind it (D-78), over a halo that reads on every board. */
|
|
475
|
-
function Pins({ pins, at }) {
|
|
476
|
-
return (_jsx("svg", { class: "helps-layer", viewBox: "0 0 8 8", "aria-hidden": "true", children: pins.map((pin) => {
|
|
477
|
-
// The line stops short of both ends, as an explaining arrow does: it hides neither piece, nor the corner of a pinner at risk.
|
|
478
|
-
const [x0, y0] = towards(at(pin.by.square), at(pin.to.square), pinTrim);
|
|
479
|
-
const [x1, y1] = towards(at(pin.to.square), at(pin.by.square), pinTrim);
|
|
480
|
-
return (_jsxs("g", { class: "pin", children: [_jsx("line", { class: "halo", x1: x0, y1: y0, x2: x1, y2: y1 }), _jsx("line", { x1: x0, y1: y0, x2: x1, y2: y1 })] }, `${pin.by.square}${pin.piece.square}`));
|
|
481
|
-
}) }));
|
|
482
|
-
}
|
|
483
|
-
/** Who reaches a square on the control map: only White, only Black, both or none. */
|
|
484
|
-
function controlOf(counts) {
|
|
485
|
-
const [white, black] = counts ?? [0, 0];
|
|
486
|
-
return white > 0 && black > 0 ? "both" : white > 0 ? "white" : black > 0 ? "black" : "none";
|
|
487
|
-
}
|
|
488
|
-
/** A square's counts on the control map, as a domino in its middle: how many of White's pieces reach it, and of Black's; none when no piece does. */
|
|
489
|
-
function ControlCount({ counts }) {
|
|
490
|
-
const [white, black] = counts ?? [0, 0];
|
|
491
|
-
if (white === 0 && black === 0) {
|
|
492
|
-
return null;
|
|
493
|
-
}
|
|
494
|
-
return (_jsxs("span", { class: "control-count", "aria-hidden": "true", children: [_jsx("b", { class: "white", children: white }), _jsx("b", { class: "black", children: black })] }));
|
|
495
|
-
}
|
|
496
|
-
/** A point `distance` squares from `point` toward `target`. */
|
|
497
|
-
function towards(point, target, distance) {
|
|
498
|
-
const length = Math.hypot(target[0] - point[0], target[1] - point[1]);
|
|
499
|
-
return [point[0] + (target[0] - point[0]) / length * distance, point[1] + (target[1] - point[1]) / length * distance];
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* A bubble by the piece of a refused move, where it hides neither the culprit nor the arrow's point (`where`, T-91), kept on the board at its edges; in the
|
|
503
|
-
* board's middle without a piece.
|
|
504
|
-
*/
|
|
505
|
-
function Bubble({ notice, place, where }) {
|
|
506
|
-
if (place === undefined) {
|
|
507
|
-
return _jsx("p", { class: "notice centre", role: "alert", children: notice.text }, notice.text);
|
|
508
|
-
}
|
|
509
|
-
const [column, row] = place;
|
|
510
|
-
const align = column <= 1 ? "start" : column >= 6 ? "end" : "middle";
|
|
511
|
-
const kind = notice.explanation?.kind;
|
|
512
|
-
return (_jsxs("p", { class: `notice ${align} ${where}`, role: "alert", style: { "--column": column, "--row": row }, children: [kind === undefined || notice.piece === undefined ? null : _jsx(ExplainIcon, { kind: kind, piece: notice.piece }), _jsx("span", { children: notice.text })] }, `${notice.square} ${notice.text}`));
|
|
513
|
-
}
|
|
514
|
-
/** The bubble's icon for a kind of reason (T-91): the piece moved, a pin, the player's king on red, a bar, the two ways of castling. */
|
|
515
|
-
function ExplainIcon({ kind, piece }) {
|
|
516
|
-
const white = piece === piece.toUpperCase();
|
|
517
|
-
return (_jsx("span", { class: `explain-icon ${kind}`, "aria-hidden": "true", children: kind === "piece" ? _jsx(Piece, { letter: piece }) : kind === "king" ? _jsx(Piece, { letter: white ? "K" : "k" }) : (_jsx("svg", { viewBox: "0 0 24 24", children: kind === "pin"
|
|
518
|
-
? _jsx("path", { d: "M9 3h6M10 3v6l-3 4h10l-3-4V3M12 13v8" })
|
|
519
|
-
: kind === "blocked"
|
|
520
|
-
? (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "8.5" }), _jsx("path", { class: "bar", d: "M7.5 12h9" })] }))
|
|
521
|
-
: _jsx("path", { d: "M4 8h15M15 4l4 4-4 4M20 16H5M9 12l-4 4 4 4" }) })) }));
|
|
522
|
-
}
|
|
523
|
-
/** The pieces a pawn may become, in a column from its square toward the middle of the board, over a shade that cancels the move; Esc cancels it too. */
|
|
524
|
-
function PromotionChoice({ side, column, down, onPick, text }) {
|
|
525
|
-
const first = useRef(null);
|
|
526
|
-
useEffect(() => {
|
|
527
|
-
first.current?.focus();
|
|
528
|
-
const escape = (event) => {
|
|
529
|
-
if (event.key === "Escape") {
|
|
530
|
-
onPick(undefined);
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
addEventListener("keydown", escape);
|
|
534
|
-
return () => removeEventListener("keydown", escape);
|
|
535
|
-
}, []);
|
|
536
|
-
return (_jsxs("div", { class: "promotion", role: "group", "aria-label": text.promoteTo, children: [_jsx("div", { class: "promotion-shade", onPointerDown: () => onPick(undefined) }), promotionPieces.map((piece, index) => (_jsx("button", { ref: index === 0 ? first : undefined, type: "button", class: "promotion-piece", style: { "--column": column, "--row": down ? index : 7 - index }, "aria-label": text.piece(piece.toUpperCase(), side), onClick: () => onPick(piece), children: _jsx(Piece, { letter: side === "white" ? piece.toUpperCase() : piece }) }, piece)))] }));
|
|
537
|
-
}
|
|
538
|
-
const promotionPieces = ["q", "n", "r", "b"];
|
|
539
|
-
/** The corner of the rook a castling moves; none for another move. */
|
|
540
|
-
function rookOf(move) {
|
|
541
|
-
return move.san.startsWith("O-O-O") ? `a${move.from[1]}` : move.san.startsWith("O-O") ? `h${move.from[1]}` : undefined;
|
|
542
|
-
}
|
|
543
|
-
// How each arrow moves the keyboard's square, in columns and rows as the board is seen.
|
|
544
|
-
const arrowSteps = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] };
|
|
545
|
-
// How much an arrow that explains is cut, in squares: off the culprit at its start, and before an occupied square at its point.
|
|
546
|
-
const explainTrim = { start: 0.36, end: 0.4 };
|
|
547
|
-
// A board narrower than this, in pixels, is a phone's: its bubble takes the board's width, and goes under the board when it has no room on it.
|
|
548
|
-
const narrowBoard = 520;
|
|
549
|
-
// How much a pin's line is cut at each end, in squares.
|
|
550
|
-
const pinTrim = 0.35;
|
|
551
|
-
// An arrow's head, in squares: how long, and how far each side of its line.
|
|
552
|
-
const arrowHead = { length: 0.42, width: 0.3 };
|
|
553
|
-
/** The piece lifted off the board: it rises a frame after it appears, so it grows into the air. */
|
|
554
|
-
function Lifted({ drag, letter }) {
|
|
555
|
-
const [up, setUp] = useState(false);
|
|
556
|
-
useEffect(() => {
|
|
557
|
-
const frame = requestAnimationFrame(() => setUp(true));
|
|
558
|
-
return () => cancelAnimationFrame(frame);
|
|
559
|
-
}, []);
|
|
560
|
-
const classes = ["lifted", up && !drag.back ? "up" : "", drag.touch ? "touch" : "", drag.back ? "back" : ""].filter((name) => name !== "").join(" ");
|
|
561
|
-
return (_jsx("div", { class: classes, style: { "--x": `${drag.at[0]}px`, "--y": `${drag.at[1]}px` }, children: _jsx(Piece, { letter: letter }) }));
|
|
562
|
-
}
|
|
563
|
-
function reducedMotion() {
|
|
564
|
-
return matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
565
|
-
}
|
|
566
|
-
const files = "abcdefgh";
|
package/dist/page/fold-tab.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "preact/jsx-runtime";
|
|
2
|
-
// The tab on a side column's outer edge, a drawer's handle, that slides the column under the board and pulls it out again (T-84).
|
|
3
|
-
import { foldKeys } from "./folds.js";
|
|
4
|
-
/** The tab of a column: open, on the column's outer edge; folded, peeking out from under the board, with a dot when the AI spoke meanwhile. */
|
|
5
|
-
export function FoldTab({ column, folded, label, unread = false, onFold }) {
|
|
6
|
-
// The chevron points where a click moves the handle: in under the board, or out again.
|
|
7
|
-
const left = (column === "talk") === folded;
|
|
8
|
-
return (_jsx("div", { class: `fold-tab of-${column}${folded ? " folded" : ""}`, children: _jsx("button", { id: `fold-${column}`, class: unread ? "unread" : undefined, type: "button", title: `${label} (${foldKeys[column]})`, "aria-label": label, "aria-controls": column, "aria-expanded": !folded, "aria-keyshortcuts": foldKeys[column], onClick: () => onFold(column), children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: left ? "M14 7L9 12L14 17" : "M10 7L15 12L10 17" }) }) }) }));
|
|
9
|
-
}
|
package/dist/page/game-column.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
-
import { outOfBookMove } from "../core/index.js";
|
|
3
|
-
import { MoveList, Steps } from "./move-list.js";
|
|
4
|
-
import { Piece } from "./pieces.js";
|
|
5
|
-
/**
|
|
6
|
-
* The column, which the player folds with its tab (T-84); under the board, where three columns would leave the board small, always open. A game in analysis
|
|
7
|
-
* (D-69) says above the moves that the AI is going over it, until its word on the whole game comes, which the talk shows.
|
|
8
|
-
*/
|
|
9
|
-
export function GameColumn({ plies, result, how, lit, started, move, toMove, opening, helps, analysis, shownPlies, onShow, onStep, onDownload, onSaveMoves, onAnalyse, text }) {
|
|
10
|
-
return (_jsx("aside", { class: "side", id: "game", "aria-label": text.game, children: started === undefined && analysis === undefined
|
|
11
|
-
? (_jsxs(_Fragment, { children: [_jsx("p", { class: "card empty", children: text.noGame }), onAnalyse === undefined ? null : _jsx(AnalyseButton, { label: text.study.analyseLast, onAnalyse: onAnalyse })] }))
|
|
12
|
-
: (_jsxs(_Fragment, { children: [_jsx(GameCard, { plies: plies, result: result, how: how, lit: lit, started: started, move: move, toMove: toMove, text: text }), opening === undefined ? null : _jsx(OpeningCard, { opening: opening, text: text }), helps, onDownload === undefined && onSaveMoves === undefined ? null : (_jsxs("div", { class: "card analysis-actions", children: [onDownload === undefined ? null : _jsx(SaveButton, { label: text.study.download, onSave: onDownload }), onSaveMoves === undefined ? null : _jsx(SaveButton, { label: text.saveMoves, onSave: onSaveMoves })] })), onAnalyse === undefined ? null : _jsx(AnalyseButton, { label: text.study.analyseIt, onAnalyse: onAnalyse }), analysis === undefined || analysis.summary !== undefined ? null : (_jsxs("p", { class: "card review waiting", role: "status", children: [_jsx("span", { class: "spinner", "aria-hidden": "true" }), text.reviewing(analysis.by)] })), _jsx(MoveList, { plies: plies, result: result, shown: shownPlies, onShow: onShow, text: text }), _jsx(Steps, { shown: shownPlies, total: plies.length, onStep: onStep, text: text })] })) }));
|
|
13
|
-
}
|
|
14
|
-
/** A button that saves the game as a file in the player's downloads. */
|
|
15
|
-
function SaveButton({ label, onSave }) {
|
|
16
|
-
return (_jsxs("button", { type: "button", onClick: onSave, children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M12 4V15M7 10L12 15L17 10M5 19H19" }) }), label] }));
|
|
17
|
-
}
|
|
18
|
-
/** The button that opens the game over in analysis. */
|
|
19
|
-
function AnalyseButton({ label, onAnalyse }) {
|
|
20
|
-
return (_jsx("div", { class: "card analysis-actions", children: _jsxs("button", { type: "button", onClick: onAnalyse, children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M12 4V20M8 20H16M5 7H19M5 7L2.5 13A2.5 2 0 0 0 7.5 13ZM19 7L16.5 13A2.5 2 0 0 0 21.5 13Z" }) }), label] }) }));
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* The game's card, as tall as a player's (T-84): a little board of four squares with the king to move, the move and when the game started; at the end, the result on the
|
|
24
|
-
* square, the winner's king standing over the loser's, laid down, or both standing in a draw, how it ended and how many moves it lasted; lit up a moment when the
|
|
25
|
-
* game ends under the player's eyes (T-77).
|
|
26
|
-
*/
|
|
27
|
-
function GameCard({ plies, result, how, lit, started, move, toMove, text }) {
|
|
28
|
-
const when = started === undefined ? undefined : new Date(started).toLocaleString(text.language, { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" });
|
|
29
|
-
if (result === undefined) {
|
|
30
|
-
return (_jsxs("section", { class: "card game-card", "aria-label": text.game, children: [_jsx("span", { class: "tile moving", "aria-hidden": "true", children: _jsx(Piece, { letter: toMove === "white" ? "K" : "k" }) }), _jsxs("div", { class: "what", children: [_jsxs("p", { class: "head", children: [text.move, " ", _jsx("b", { class: "big", children: move }), _jsxs("span", { class: "visually-hidden", children: [", ", text.toMove(toMove)] })] }), when === undefined ? null : _jsx("p", { class: "when", children: text.started(when) })] })] }));
|
|
31
|
-
}
|
|
32
|
-
const winner = result === "1-0" ? "white" : result === "0-1" ? "black" : undefined;
|
|
33
|
-
const first = plies[0];
|
|
34
|
-
const last = plies.at(-1);
|
|
35
|
-
const lasted = first === undefined || last === undefined ? 0 : last.number - first.number + 1;
|
|
36
|
-
return (_jsxs("section", { class: `card game-card${lit ? " lit" : ""}`, "aria-label": text.game, children: [winner === undefined
|
|
37
|
-
? (_jsxs("span", { class: "tile over draw", "aria-hidden": "true", children: [_jsx(Piece, { letter: "K" }), _jsx(Piece, { letter: "k" })] }))
|
|
38
|
-
: (_jsxs("span", { class: "tile over win", "aria-hidden": "true", children: [_jsx("span", { class: "down", children: _jsx(Piece, { letter: winner === "white" ? "k" : "K" }) }), _jsx("span", { class: "up", children: _jsx(Piece, { letter: winner === "white" ? "K" : "k" }) })] })), _jsxs("div", { class: "what", children: [_jsxs("p", { class: "head", children: [_jsx("b", { class: "big", children: result === "1/2-1/2" ? "½–½" : result.replace("-", "–") }), " \u00B7 ", winner === undefined ? text.draw : text.won(winner)] }), _jsxs("p", { class: "when", children: [how === undefined ? "" : `${text.ended[how]} · `, text.lasted(lasted)] })] })] }));
|
|
39
|
-
}
|
|
40
|
-
/** The opening by Lichess's book: a book, crossed out once the game left it, and the family; the variation under them, and the move the game left the book at. */
|
|
41
|
-
function OpeningCard({ opening, text }) {
|
|
42
|
-
const book = opening.inBook ? text.inBook : `${text.outOfBook} ${outOfBookMove(opening)}`;
|
|
43
|
-
return (_jsxs("section", { class: "card opening", "aria-label": text.opening, children: [_jsxs("p", { class: "family", children: [_jsx("span", { class: `book${opening.inBook ? "" : " out"}`, title: book, role: "img", "aria-label": book, children: _jsxs("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { d: "M12 6.5C10 5 7 4.5 4 5V18C7 17.5 10 18 12 19.5C14 18 17 17.5 20 18V5C17 4.5 14 5 12 6.5ZM12 6.5V19.5" }), opening.inBook ? null : _jsx("path", { d: "M3.5 3.5L20.5 20.5" })] }) }), _jsx("b", { children: opening.family })] }), opening.variation === undefined ? null : _jsx("p", { class: "variation", children: opening.variation }), opening.inBook ? null : _jsxs("p", { class: "out", children: [text.outOfBook, " ", outOfBookMove(opening)] })] }));
|
|
44
|
-
}
|