chesspal 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agents.js +2 -1
- package/dist/commands/ai-say.d.ts +2 -1
- package/dist/commands/analyse-game.d.ts +2 -0
- package/dist/commands/analyse-game.js +7 -4
- package/dist/commands/annotate-game.d.ts +2 -0
- package/dist/commands/annotate-game.js +26 -8
- package/dist/commands/answer-lines.d.ts +8 -4
- package/dist/commands/answer-lines.js +19 -3
- package/dist/commands/coach.js +2 -2
- package/dist/commands/context.d.ts +2 -0
- package/dist/commands/delete-review.js +3 -2
- package/dist/commands/game-summary.d.ts +7 -2
- package/dist/commands/game-summary.js +7 -0
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/look.d.ts +9 -5
- package/dist/commands/look.js +19 -9
- package/dist/commands/ponder.js +1 -1
- package/dist/commands/position.js +2 -2
- package/dist/commands/say.d.ts +11 -6
- package/dist/commands/say.js +21 -13
- package/dist/commands/status.d.ts +3 -1
- package/dist/commands/status.js +31 -6
- package/dist/commands/study.d.ts +11 -1
- package/dist/commands/study.js +31 -2
- package/dist/commands/view.d.ts +17 -5
- package/dist/commands/view.js +67 -10
- package/dist/core/facts.d.ts +27 -2
- package/dist/core/facts.js +59 -8
- package/dist/core/game.d.ts +5 -0
- package/dist/core/game.js +9 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/placement.d.ts +63 -0
- package/dist/core/placement.js +259 -0
- package/dist/core/possibilities.d.ts +38 -0
- package/dist/core/possibilities.js +254 -0
- package/dist/core/review.d.ts +4 -1
- package/dist/core/review.js +17 -6
- package/dist/games/analysis.d.ts +8 -4
- package/dist/games/analysis.js +9 -5
- package/dist/games/current-game.d.ts +16 -1
- package/dist/games/current-game.js +10 -8
- package/dist/games/files.d.ts +2 -0
- package/dist/games/files.js +7 -1
- package/dist/games/index.d.ts +2 -2
- package/dist/games/index.js +1 -1
- package/dist/games/table-talk.d.ts +2 -0
- package/dist/games/table-talk.js +5 -1
- package/dist/games/talk.d.ts +4 -3
- package/dist/games/talk.js +3 -3
- package/dist/page/app/app.js +36 -12
- package/dist/page/app/keys.js +16 -2
- package/dist/page/app/served.js +2 -2
- package/dist/page/board/board.js +10 -4
- package/dist/page/board/marks.js +53 -31
- package/dist/page/board/signs.js +23 -0
- package/dist/page/game/column.js +2 -2
- package/dist/page/game/game.css +12 -0
- package/dist/page/game/move-list.js +2 -2
- package/dist/page/game/plies.js +8 -0
- package/dist/page/helps/helps.css +87 -0
- package/dist/page/helps/helps.js +18 -13
- package/dist/page/helps/view.js +19 -5
- package/dist/page/layout/layout.css +29 -5
- package/dist/page/legend/legend.css +203 -0
- package/dist/page/legend/legend.js +169 -0
- package/dist/page/legend/view.js +54 -0
- package/dist/page/style.css +2 -0
- package/dist/page/talk/chat.css +90 -0
- package/dist/page/talk/talk.js +27 -5
- package/dist/page/text.js +46 -6
- package/dist/server/mcp.js +8 -6
- package/dist/server/page/game.js +20 -7
- package/dist/server/page/page.js +5 -3
- package/instructions/opponent.md +1 -1
- package/package.json +1 -1
package/dist/page/board/marks.js
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
|
|
2
|
-
// What the board draws over its squares and pieces: the kings' badges at the end, the arrows, the helps' corners and pins, the control map's counts, the
|
|
3
|
-
// bubble of a refused move, the choice of a promotion and the piece lifted off the board.
|
|
4
2
|
import { useEffect, useRef, useState } from "preact/hooks";
|
|
5
3
|
import { Piece } from "../pieces/pieces.js";
|
|
6
4
|
/** The corner folded on a piece at risk, red on the player's and green on the other side's, in ink when no side is theirs (D-91); before its line in the card. */
|
|
7
5
|
export function Notch({ tone }) {
|
|
8
6
|
return (_jsx("span", { class: tone === undefined ? "help-notch" : `help-notch ${tone}`, "aria-hidden": "true", children: _jsxs("svg", { viewBox: "0 0 10 10", children: [_jsx("path", { d: "M0.5 0.5V9.5H9.5Z" }), tone === undefined ? null : _jsx("path", { class: "edge", d: "M0.5 0.5V9.5H9.5Z" }), _jsx("circle", { cx: "2.9", cy: "7.1", r: "1.1" })] }) }));
|
|
9
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* The pushpin stuck in a pinned piece from the top left corner, its needle toward the piece, in the tone of its corner at risk (D-91); in ink in no side's;
|
|
10
|
+
* in the card's line, in its colour.
|
|
11
|
+
*/
|
|
12
|
+
export function Pushpin({ tone }) {
|
|
13
|
+
return _jsx(Stuck, { kind: "help-pin", shape: pushpin, tone: tone });
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The skewer stuck in the skewered piece, the one in front, as the pushpin in a pinned one (D-91): a stick in ink with a big bead and a small one in the tone,
|
|
17
|
+
* as the piece in front and the cheaper one behind it, so that it reads apart from the pushpin, all in the tone (Stefano); in the card's line, in its colour.
|
|
18
|
+
*/
|
|
19
|
+
export function Skewer({ tone }) {
|
|
20
|
+
return (_jsx(Stuck, { kind: "help-skewer", shape: skewer, tone: tone, children: tone === undefined || tone === "neutral" ? null : skewerBeads.map(({ y, radius }) => _jsx("circle", { cx: "12", cy: y, r: radius }, y)) }));
|
|
21
|
+
}
|
|
10
22
|
/** A king's badge in the corner of its square. */
|
|
11
23
|
export function Badge({ kind }) {
|
|
12
24
|
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" }) })) }));
|
|
@@ -46,13 +58,20 @@ export function Arrows({ arrows, at, explain, occupied }) {
|
|
|
46
58
|
const starts = [...new Set(shapes.map(({ from }) => from))];
|
|
47
59
|
return (_jsx("svg", { class: explain !== undefined ? "arrows explain" : occupied !== undefined ? "arrows drawn" : "arrows", viewBox: "0 0 8 8", "aria-hidden": "true", children: starts.map((start) => _jsx(ArrowGroup, { shapes: shapes.filter(({ from }) => from === start), edged: occupied !== undefined }, start)) }));
|
|
48
60
|
}
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Each pin as a dashed line from the pinning piece through the pinned one to what stands behind it (D-78), and each skewer as a dash-dot one, beads on its stick, over
|
|
63
|
+
* a halo that reads on every board. A pin and a skewer on the same stretch of a line stand apart on its two sides, so that both read.
|
|
64
|
+
*/
|
|
65
|
+
export function PinsAndSkewers({ pins, skewers, at }) {
|
|
66
|
+
const lines = [...pins.map((line) => ({ kind: "pin", line, other: skewers })), ...skewers.map((line) => ({ kind: "skewer", line, other: pins }))];
|
|
67
|
+
return (_jsx("svg", { class: "helps-layer", viewBox: "0 0 8 8", "aria-hidden": "true", children: lines.map(({ kind, line, other }) => {
|
|
52
68
|
// The line stops short of both ends, as an explaining arrow does: it hides neither piece, nor the corner of a pinner at risk.
|
|
53
|
-
const [
|
|
54
|
-
const [
|
|
55
|
-
|
|
69
|
+
const [startX, startY] = towards(at(line.by.square), at(line.to.square), pinTrim);
|
|
70
|
+
const [endX, endY] = towards(at(line.to.square), at(line.by.square), pinTrim);
|
|
71
|
+
const shift = other.some((each) => sharedSquares(each, line) > 1) ? (kind === "pin" ? 1 : -1) * sharedLineShift : 0;
|
|
72
|
+
const [dx, dy] = across([startX, startY], [endX, endY], shift);
|
|
73
|
+
const ends = { x1: startX + dx, y1: startY + dy, x2: endX + dx, y2: endY + dy };
|
|
74
|
+
return (_jsxs("g", { class: kind, children: [_jsx("line", { class: "halo", ...ends }), _jsx("line", { ...ends })] }, `${kind} ${line.by.square}${line.piece.square}`));
|
|
56
75
|
}) }));
|
|
57
76
|
}
|
|
58
77
|
/** 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. */
|
|
@@ -106,35 +125,30 @@ export function Lifted({ drag, letter }) {
|
|
|
106
125
|
const classes = ["lifted", up && !drag.back ? "up" : "", drag.touch ? "touch" : "", drag.back ? "back" : ""].filter((name) => name !== "").join(" ");
|
|
107
126
|
return (_jsx("div", { class: classes, style: { "--x": `${drag.at[0]}px`, "--y": `${drag.at[1]}px` }, children: _jsx(Piece, { letter: letter }) }));
|
|
108
127
|
}
|
|
109
|
-
/** 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. */
|
|
110
|
-
export function kingSigns(pieces, toMove, end) {
|
|
111
|
-
const kingOf = (side) => [...pieces].find(([, letter]) => letter === (side === "white" ? "K" : "k"))?.[0];
|
|
112
|
-
const signs = new Map();
|
|
113
|
-
const sign = (side, badge, classes) => {
|
|
114
|
-
const square = kingOf(side);
|
|
115
|
-
if (square !== undefined) {
|
|
116
|
-
signs.set(square, { badge, classes });
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
if (end.winner === undefined) {
|
|
120
|
-
sign("white", "half", end.how === "stalemate" && toMove === "white" ? "drawn stalled" : "drawn");
|
|
121
|
-
sign("black", "half", end.how === "stalemate" && toMove === "black" ? "drawn stalled" : "drawn");
|
|
122
|
-
return signs;
|
|
123
|
-
}
|
|
124
|
-
const loser = end.winner === "white" ? "black" : "white";
|
|
125
|
-
sign(end.winner, "crown", "winner");
|
|
126
|
-
if (end.how === "checkmate" || end.how === "resignation") {
|
|
127
|
-
sign(loser, end.how === "checkmate" ? "mate" : "flag", "fallen");
|
|
128
|
-
}
|
|
129
|
-
return signs;
|
|
130
|
-
}
|
|
131
|
-
export const noSigns = new Map();
|
|
132
128
|
/** Arrows off one square, the AI's on a white edge (D-78): every edge under every arrow, so one edge goes round them all where they meet. */
|
|
133
129
|
function ArrowGroup({ shapes, edged }) {
|
|
134
130
|
return (_jsxs("g", { children: [edged
|
|
135
131
|
? shapes.map(({ key, shaft, head }) => (_jsxs("g", { children: [_jsx("path", { class: "edge", d: shaft }), _jsx("polygon", { class: "edge", points: head })] }, `edge${key}`)))
|
|
136
132
|
: null, shapes.map(({ key, colour, shaft, head }) => (_jsxs("g", { class: colour, children: [_jsx("path", { d: shaft }), _jsx("polygon", { points: head })] }, key)))] }));
|
|
137
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* A sign stuck in a piece from its square's top left corner, tilted toward the piece; edged in ink when coloured (Zoe). Its box holds the tilted sign and
|
|
136
|
+
* its halo only, so that it sits in the corner (Stefano).
|
|
137
|
+
*/
|
|
138
|
+
function Stuck({ kind, shape, tone, children }) {
|
|
139
|
+
return (_jsx("span", { class: tone === undefined ? kind : `${kind} ${tone}`, "aria-hidden": "true", children: _jsx("svg", { viewBox: "2 0.5 18.5 22.5", children: _jsxs("g", { transform: "rotate(-32 12 12)", children: [_jsx("path", { d: shape }), tone === undefined || tone === "neutral" ? null : _jsx("path", { class: "edge", d: shape }), children] }) }) }));
|
|
140
|
+
}
|
|
141
|
+
/** How many of their three squares a pin and a skewer share: two when they lie on the same stretch of a line. */
|
|
142
|
+
function sharedSquares(one, other) {
|
|
143
|
+
const squares = [one.by.square, one.piece.square, one.to.square];
|
|
144
|
+
return [other.by.square, other.piece.square, other.to.square].filter((square) => squares.includes(square)).length;
|
|
145
|
+
}
|
|
146
|
+
/** A step of the given length across a line, always to the same side of it whichever way the line runs. */
|
|
147
|
+
function across(start, end, distance) {
|
|
148
|
+
const length = Math.hypot(end[0] - start[0], end[1] - start[1]);
|
|
149
|
+
const way = end[0] < start[0] || (end[0] === start[0] && end[1] < start[1]) ? -1 : 1;
|
|
150
|
+
return [-(end[1] - start[1]) / length * distance * way, (end[0] - start[0]) / length * distance * way];
|
|
151
|
+
}
|
|
138
152
|
/** A point `distance` squares from `point` toward `target`. */
|
|
139
153
|
function towards(point, target, distance) {
|
|
140
154
|
const length = Math.hypot(target[0] - point[0], target[1] - point[1]);
|
|
@@ -150,10 +164,18 @@ function ExplainIcon({ kind, piece }) {
|
|
|
150
164
|
: _jsx("path", { d: "M4 8h15M15 4l4 4-4 4M20 16H5M9 12l-4 4 4 4" }) })) }));
|
|
151
165
|
}
|
|
152
166
|
const promotionPieces = ["q", "n", "r", "b"];
|
|
167
|
+
// A pushpin standing up, as the bubble's pin: its cap, its body widening into the collar, and its needle. Tilted less than a diagonal, so it never lies along a pin's line.
|
|
168
|
+
const pushpin = "M8.6 2.2H15.4Q16.6 2.2 16.6 3.4V4.6Q16.6 5.8 15.4 5.8H14.8V9.4L18.4 13.1V14.9H13V20L12 22.6L11 20V14.9H5.6V13.1L9.2 9.4V5.8H8.6Q7.4 5.8 7.4 4.6V3.4Q7.4 2.2 8.6 2.2Z";
|
|
169
|
+
// A skewer standing up, in the pushpin's box: a round end, a big bead, a small one, a stretch of stick between them, and its point.
|
|
170
|
+
const skewer = "M10.9 2.6A1.1 1.1 0 0 1 13.1 2.6V3.75A4.2 4.2 0 0 1 13.1 11.85V13A3.1 3.1 0 0 1 13.1 18.8V20.2L12 23.4L10.9 20.2V18.8A3.1 3.1 0 0 1 10.9 13V11.85A4.2 4.2 0 0 1 10.9 3.75Z";
|
|
171
|
+
// The skewer's beads, on its stick: the big one and the small one, as its shape draws them.
|
|
172
|
+
const skewerBeads = [{ y: 7.8, radius: 4.2 }, { y: 15.9, radius: 3.1 }];
|
|
153
173
|
// How much an arrow that explains is cut, in squares: off the culprit at its start, and before an occupied square at its point.
|
|
154
174
|
const explainTrim = { start: 0.36, end: 0.4 };
|
|
155
175
|
// How much a pin's line is cut at each end, in squares.
|
|
156
176
|
const pinTrim = 0.35;
|
|
177
|
+
// How far a pin and a skewer on the same stretch of a line stand off it, each on its side, in squares: a little more than a line with its halo.
|
|
178
|
+
const sharedLineShift = 0.08;
|
|
157
179
|
// An arrow's head, in squares: how long, and how far each side of its line.
|
|
158
180
|
const arrowHead = { length: 0.42, width: 0.3 };
|
|
159
181
|
// The helps' point, smaller in step with their thinner shaft (Stefano).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** 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. */
|
|
2
|
+
export function kingSigns(pieces, toMove, end) {
|
|
3
|
+
const kingOf = (side) => [...pieces].find(([, letter]) => letter === (side === "white" ? "K" : "k"))?.[0];
|
|
4
|
+
const signs = new Map();
|
|
5
|
+
const sign = (side, badge, classes) => {
|
|
6
|
+
const square = kingOf(side);
|
|
7
|
+
if (square !== undefined) {
|
|
8
|
+
signs.set(square, { badge, classes });
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
if (end.winner === undefined) {
|
|
12
|
+
sign("white", "half", end.how === "stalemate" && toMove === "white" ? "drawn stalled" : "drawn");
|
|
13
|
+
sign("black", "half", end.how === "stalemate" && toMove === "black" ? "drawn stalled" : "drawn");
|
|
14
|
+
return signs;
|
|
15
|
+
}
|
|
16
|
+
const loser = end.winner === "white" ? "black" : "white";
|
|
17
|
+
sign(end.winner, "crown", "winner");
|
|
18
|
+
if (end.how === "checkmate" || end.how === "resignation") {
|
|
19
|
+
sign(loser, end.how === "checkmate" ? "mate" : "flag", "fallen");
|
|
20
|
+
}
|
|
21
|
+
return signs;
|
|
22
|
+
}
|
|
23
|
+
export const noSigns = new Map();
|
package/dist/page/game/column.js
CHANGED
|
@@ -10,11 +10,11 @@ import { ResultTile } from "./result-tile.js";
|
|
|
10
10
|
* The column, which the player folds with its tab; under the board, where three columns would leave the board small, always open. A game in analysis
|
|
11
11
|
* (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.
|
|
12
12
|
*/
|
|
13
|
-
export function GameColumn({ plies, result, how, lit, started, move, toMove, opening, helps, standings, analysis, shownPlies, onShow, onStep, onDownload, onSaveMoves, onAnalyse, text }) {
|
|
13
|
+
export function GameColumn({ plies, result, how, lit, started, move, toMove, opening, helps, legend, standings, analysis, shownPlies, onShow, onStep, onDownload, onSaveMoves, onAnalyse, text }) {
|
|
14
14
|
const bar = standings === undefined ? undefined : _jsx(LeadBar, { standing: standingAt(standings, shownPlies), shown: shownPlies, text: text });
|
|
15
15
|
return (_jsx("aside", { class: "side", id: "game", "aria-label": text.game, children: started === undefined && analysis === undefined
|
|
16
16
|
? (_jsxs(_Fragment, { children: [_jsx("p", { class: "card empty", children: text.noGame }), onAnalyse === undefined ? null : _jsx(AnalyseButton, { label: text.study.analyseLast, onAnalyse: onAnalyse })] }))
|
|
17
|
-
: (_jsxs(_Fragment, { children: [_jsx(GameCard, { plies: plies, result: result, how: how, lit: lit, started: started, move: move, toMove: toMove, bar: bar, text: text }), analysis === undefined || standings === undefined ? null : _jsx(LeadChart, { plies: plies, standings: standings, shown: shownPlies, onShow: onShow, text: text }), opening === undefined ? null : _jsx(OpeningCard, { opening: opening, text: text }), helps, 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 }), onDownload === undefined && onSaveMoves === undefined ? null : (_jsxs("div", { class: "card analysis-actions", children: [onDownload === undefined ? null : _jsx(SaveButton, { label: text.study.download, hint: text.study.downloadHint, onSave: onDownload }), onSaveMoves === undefined ? null : _jsx(SaveButton, { label: text.saveMoves, hint: text.saveMovesHint, onSave: onSaveMoves })] })), onAnalyse === undefined ? null : _jsx(AnalyseButton, { label: text.study.analyseIt, onAnalyse: onAnalyse }), _jsx(Steps, { shown: shownPlies, total: plies.length, onStep: onStep, text: text })] })) }));
|
|
17
|
+
: (_jsxs(_Fragment, { children: [_jsx(GameCard, { plies: plies, result: result, how: how, lit: lit, started: started, move: move, toMove: toMove, bar: bar, text: text }), analysis === undefined || standings === undefined ? null : _jsx(LeadChart, { plies: plies, standings: standings, shown: shownPlies, onShow: onShow, text: text }), opening === undefined ? null : _jsx(OpeningCard, { opening: opening, text: text }), helps, 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, tools: legend, text: text }), onDownload === undefined && onSaveMoves === undefined ? null : (_jsxs("div", { class: "card analysis-actions", children: [onDownload === undefined ? null : _jsx(SaveButton, { label: text.study.download, hint: text.study.downloadHint, onSave: onDownload }), onSaveMoves === undefined ? null : _jsx(SaveButton, { label: text.saveMoves, hint: text.saveMovesHint, onSave: onSaveMoves })] })), onAnalyse === undefined ? null : _jsx(AnalyseButton, { label: text.study.analyseIt, onAnalyse: onAnalyse }), _jsx(Steps, { shown: shownPlies, total: plies.length, onStep: onStep, text: text })] })) }));
|
|
18
18
|
}
|
|
19
19
|
/** A button that saves the game as a file in the player's downloads, its tooltip saying what the file holds. */
|
|
20
20
|
function SaveButton({ label, hint, onSave }) {
|
package/dist/page/game/game.css
CHANGED
|
@@ -352,6 +352,18 @@
|
|
|
352
352
|
padding: 2px 4px 0;
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
+
/* The title, and on its right the legend of the marks when no helps' card holds it (D-99) */
|
|
356
|
+
.moves-head {
|
|
357
|
+
display: flex;
|
|
358
|
+
align-items: center;
|
|
359
|
+
justify-content: space-between;
|
|
360
|
+
gap: 8px;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.moves-head .hold-button {
|
|
364
|
+
padding: 3px 6px;
|
|
365
|
+
}
|
|
366
|
+
|
|
355
367
|
/* Positioned, so that the moves' hidden names scroll with the list instead of stretching the page */
|
|
356
368
|
.moves {
|
|
357
369
|
position: relative;
|
|
@@ -7,7 +7,7 @@ import { FoldButton, useCardFold } from "./card-fold.js";
|
|
|
7
7
|
* The moves numbered in pairs, the one the board shows marked (`shown`, how many moves it shows), and the result once the game is over; a click on a move
|
|
8
8
|
* shows it. The list keeps its last move in sight, and the one shown while the player looks back.
|
|
9
9
|
*/
|
|
10
|
-
export function MoveList({ plies, result, shown, onShow, text }) {
|
|
10
|
+
export function MoveList({ plies, result, shown, onShow, tools, text }) {
|
|
11
11
|
const list = useRef(null);
|
|
12
12
|
const [folded, toggleFold] = useCardFold("moves");
|
|
13
13
|
const rows = rowsOf(plies);
|
|
@@ -30,7 +30,7 @@ export function MoveList({ plies, result, shown, onShow, text }) {
|
|
|
30
30
|
const move = (ply) => _jsx(Move, { ply: ply, current: ply !== undefined && ply === current, onShow: () => onShow(plies.indexOf(ply) + 1), text: text });
|
|
31
31
|
// The card's title with its icon, a score sheet, as the other cards of the column, which folds the card to it (Stefano); only the list under it
|
|
32
32
|
// scrolls, and it stays in the page while folded, so that it keeps its move in sight.
|
|
33
|
-
return (_jsxs("section", { class: `card moves-card${folded ? " folded" : ""}`, "aria-label": text.moves, children: [_jsx("h3", { class: "card-title", children: _jsxs(FoldButton, { folded: folded, onFold: toggleFold, text: text, children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M6 3H15L19 7V21H6ZM15 3V7H19M9 11H16M9 14.5H16M9 18H13" }) }), text.moves] }) }), _jsxs("div", { ref: list, class: "moves", hidden: folded, children: [_jsx("ol", { children: rows.map(({ number, white, black }) => (_jsxs("li", { children: [_jsxs("span", { class: "number", children: [number, "."] }), move(white), move(black)] }, number))) }), result === undefined ? null : _jsx("p", { class: "result", children: result })] })] }));
|
|
33
|
+
return (_jsxs("section", { class: `card moves-card${folded ? " folded" : ""}`, "aria-label": text.moves, children: [_jsxs("div", { class: "moves-head", children: [_jsx("h3", { class: "card-title", children: _jsxs(FoldButton, { folded: folded, onFold: toggleFold, text: text, children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M6 3H15L19 7V21H6ZM15 3V7H19M9 11H16M9 14.5H16M9 18H13" }) }), text.moves] }) }), tools] }), _jsxs("div", { ref: list, class: "moves", hidden: folded, children: [_jsx("ol", { children: rows.map(({ number, white, black }) => (_jsxs("li", { children: [_jsxs("span", { class: "number", children: [number, "."] }), move(white), move(black)] }, number))) }), result === undefined ? null : _jsx("p", { class: "result", children: result })] })] }));
|
|
34
34
|
}
|
|
35
35
|
/** The buttons that step through the moves: to the start, back, forward, and to the game as it is; each one off where it could not go. */
|
|
36
36
|
export function Steps({ shown, total, onStep, text }) {
|
package/dist/page/game/plies.js
CHANGED
|
@@ -58,6 +58,14 @@ export function judgmentsOf(plies, player, { judgments }) {
|
|
|
58
58
|
}
|
|
59
59
|
/** How long a line of the player's may be: the server's limit (D-54), which a test keeps equal. */
|
|
60
60
|
export const talkLimits = { line: 200 };
|
|
61
|
+
/**
|
|
62
|
+
* The talk over a game in analysis (D-101): from the player's first line on it, the lines said since, but for the player's on another game; the last ten,
|
|
63
|
+
* which is all half the column holds (Zoe).
|
|
64
|
+
*/
|
|
65
|
+
export function analysisTalk(talk, game) {
|
|
66
|
+
const first = talk.findIndex((line) => line.shown?.game === game);
|
|
67
|
+
return first === -1 ? [] : talk.slice(first).filter((line) => line.shown === undefined || line.shown.game === game).slice(-10);
|
|
68
|
+
}
|
|
61
69
|
/** How many times the AI has spoken in the game, its comments on its moves and its lines on the page: the page sees it speak when the count grows. */
|
|
62
70
|
export function aiSpoke(plies, ai, talk) {
|
|
63
71
|
return plies.filter((ply) => ply.side === ai && ply.comment !== undefined).length + talk.filter((line) => line.who === "ai").length;
|
|
@@ -49,6 +49,74 @@
|
|
|
49
49
|
stroke-width: 0.5;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/* A pinned piece: a pushpin stuck in it from the top left corner, in the notch's colours and on its halo; it takes the place of the rank's number there.
|
|
53
|
+
A skewered piece, the one in front: a skewer stuck in it the same way, beside the pushpin when the piece is pinned too */
|
|
54
|
+
.help-pin,
|
|
55
|
+
.help-skewer {
|
|
56
|
+
position: absolute;
|
|
57
|
+
left: 1px;
|
|
58
|
+
top: 1px;
|
|
59
|
+
z-index: 2;
|
|
60
|
+
display: block;
|
|
61
|
+
height: clamp(15px, 3.6cqw, 26px);
|
|
62
|
+
aspect-ratio: 18.5 / 22.5;
|
|
63
|
+
pointer-events: none;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.help-pin svg,
|
|
67
|
+
.help-skewer svg {
|
|
68
|
+
display: block;
|
|
69
|
+
width: 100%;
|
|
70
|
+
height: 100%;
|
|
71
|
+
overflow: visible;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.help-pin path,
|
|
75
|
+
.help-skewer path {
|
|
76
|
+
fill: var(--help-ink);
|
|
77
|
+
stroke: var(--help-halo);
|
|
78
|
+
stroke-width: 2.6;
|
|
79
|
+
stroke-linejoin: round;
|
|
80
|
+
paint-order: stroke;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.help-pin.threat path {
|
|
84
|
+
fill: var(--badge-blunder);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.help-pin.chance path {
|
|
88
|
+
fill: var(--badge-best);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* The skewer's stick stays in ink and its beads take the tone, so that it reads apart from the pushpin (Stefano) */
|
|
92
|
+
.help-skewer circle {
|
|
93
|
+
stroke: var(--help-ink);
|
|
94
|
+
stroke-width: 0.9;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.help-skewer.threat circle {
|
|
98
|
+
fill: var(--badge-blunder);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.help-skewer.chance circle {
|
|
102
|
+
fill: var(--badge-best);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.help-pin path.edge,
|
|
106
|
+
.help-skewer path.edge {
|
|
107
|
+
fill: none;
|
|
108
|
+
stroke: var(--help-ink);
|
|
109
|
+
stroke-width: 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.square:has(> .help-pin) > .help-skewer {
|
|
113
|
+
left: clamp(9px, 3cqw, 22px);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.square:has(> .help-pin, > .help-skewer) > .coordinate.rank {
|
|
117
|
+
visibility: hidden;
|
|
118
|
+
}
|
|
119
|
+
|
|
52
120
|
/* A pin: a thin dashed ink line from the pinner to what stands behind, under the arrows; without a head, never an arrow */
|
|
53
121
|
.helps-layer {
|
|
54
122
|
position: absolute;
|
|
@@ -73,6 +141,11 @@
|
|
|
73
141
|
stroke-linecap: round;
|
|
74
142
|
}
|
|
75
143
|
|
|
144
|
+
/* A skewer's line: a dash and a dot, beads on its stick, so that it reads apart from a pin's even dashes */
|
|
145
|
+
.helps-layer .skewer line:not(.halo) {
|
|
146
|
+
stroke-dasharray: 0.22 0.11 0 0.11;
|
|
147
|
+
}
|
|
148
|
+
|
|
76
149
|
/* The map (Zoe, after the round): every square on one neutral ground, then light where only White reaches, dark where only Black does, half and half
|
|
77
150
|
when both do; the two counts together in the middle, as a domino. The drawings and the pieces step back */
|
|
78
151
|
.board.mapping ~ .arrows,
|
|
@@ -383,6 +456,20 @@
|
|
|
383
456
|
fill: var(--card);
|
|
384
457
|
}
|
|
385
458
|
|
|
459
|
+
/* The pushpin and the skewer of the board, in the line's colour and without their halo */
|
|
460
|
+
.help-rows .help-pin,
|
|
461
|
+
.help-rows .help-skewer {
|
|
462
|
+
left: 7px;
|
|
463
|
+
top: 4px;
|
|
464
|
+
height: 17px;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.help-rows .help-pin path,
|
|
468
|
+
.help-rows .help-skewer path {
|
|
469
|
+
fill: var(--row);
|
|
470
|
+
stroke: none;
|
|
471
|
+
}
|
|
472
|
+
|
|
386
473
|
.help-rows .row-icon {
|
|
387
474
|
position: absolute;
|
|
388
475
|
left: 6px;
|
package/dist/page/helps/helps.js
CHANGED
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-ru
|
|
|
3
3
|
// the coach's words (D-88).
|
|
4
4
|
import { Fragment } from "preact";
|
|
5
5
|
import { useState } from "preact/hooks";
|
|
6
|
-
import { Notch } from "../board/marks.js";
|
|
6
|
+
import { Notch, Pushpin, Skewer } from "../board/marks.js";
|
|
7
7
|
import { BadgeIcon, NoteIcon } from "../game/badges.js";
|
|
8
8
|
import { FoldButton, useCardFold } from "../game/card-fold.js";
|
|
9
9
|
import { Figurines } from "../game/move-list.js";
|
|
@@ -15,7 +15,7 @@ import { groupOf, pinSquares, riskSquares, squaresOf, strategyOf } from "./view.
|
|
|
15
15
|
* its button or M is held, and the board without its drawings while their button is. A line under the pointer lights its squares on the board. Its title
|
|
16
16
|
* folds it to the title and opens it again (Stefano).
|
|
17
17
|
*/
|
|
18
|
-
export function HelpsCard({ helps, judged, hint, hintOpen, onHint, onPoint, onMap, mapping, onBare, bare, text }) {
|
|
18
|
+
export function HelpsCard({ helps, judged, hint, hintOpen, onHint, onPoint, onMap, mapping, onBare, bare, legend, text }) {
|
|
19
19
|
const [tab, setTab] = useState("tactics");
|
|
20
20
|
// The tactics' lines the player opened stay open from move to move, so that a pin is followed as the game goes on.
|
|
21
21
|
const [opened, setOpened] = useState(new Set());
|
|
@@ -29,7 +29,7 @@ export function HelpsCard({ helps, judged, hint, hintOpen, onHint, onPoint, onMa
|
|
|
29
29
|
}
|
|
30
30
|
toggleFold();
|
|
31
31
|
};
|
|
32
|
-
return (_jsxs("section", { class: `card helps-card${folded ? " folded" : ""}`, "aria-label": words.title, children: [_jsxs("div", { class: "helps-head", children: [_jsx("h3", { class: "card-title", children: _jsxs(FoldButton, { folded: folded, onFold: fold, text: text, children: [_jsx(HelpsIcon, {}), words.title] }) }), _jsxs("span", { class: "helps-tools", children: [_jsx(MapButton, { mapping: mapping, onMap: onMap, text: text }), _jsx(BareButton, { bare: bare, onBare: onBare, text: text })] })] }), folded ? null : (_jsxs(_Fragment, { children: [judged.length === 0 && hint === undefined ? null : (_jsxs("section", { class: `coach${judged.length === 0 ? " hint-only" : ""}`, "aria-label": words.coach, children: [judged.map((ply) => _jsx(Judged, { ply: ply, text: text }, `${ply.number} ${ply.side}`)), hint === undefined ? null : _jsx(HintLine, { hint: hint, open: hintOpen, onOpen: onHint, text: text })] })), _jsxs("div", { class: "tabs", role: "tablist", children: [_jsx("button", { type: "button", role: "tab", "aria-selected": tab === "tactics", onClick: () => setTab("tactics"), children: words.tactics }), _jsx("button", { type: "button", role: "tab", "aria-selected": tab === "strategy", onClick: () => setTab("strategy"), children: words.strategy })] }), tab === "tactics"
|
|
32
|
+
return (_jsxs("section", { class: `card helps-card${folded ? " folded" : ""}`, "aria-label": words.title, children: [_jsxs("div", { class: "helps-head", children: [_jsx("h3", { class: "card-title", children: _jsxs(FoldButton, { folded: folded, onFold: fold, text: text, children: [_jsx(HelpsIcon, {}), words.title] }) }), _jsxs("span", { class: "helps-tools", children: [_jsx(MapButton, { mapping: mapping, onMap: onMap, text: text }), _jsx(BareButton, { bare: bare, onBare: onBare, text: text }), legend] })] }), folded ? null : (_jsxs(_Fragment, { children: [judged.length === 0 && hint === undefined ? null : (_jsxs("section", { class: `coach${judged.length === 0 ? " hint-only" : ""}`, "aria-label": words.coach, children: [judged.map((ply) => _jsx(Judged, { ply: ply, text: text }, `${ply.number} ${ply.side}`)), hint === undefined ? null : _jsx(HintLine, { hint: hint, open: hintOpen, onOpen: onHint, text: text })] })), _jsxs("div", { class: "tabs", role: "tablist", children: [_jsx("button", { type: "button", role: "tab", "aria-selected": tab === "tactics", onClick: () => setTab("tactics"), children: words.tactics }), _jsx("button", { type: "button", role: "tab", "aria-selected": tab === "strategy", onClick: () => setTab("strategy"), children: words.strategy })] }), tab === "tactics"
|
|
33
33
|
? _jsx(Tactics, { helps: helps, opened: opened, onOpen: openLine, onPoint: onPoint, text: text })
|
|
34
34
|
: _jsx(Strategy, { helps: helps, onPoint: onPoint, text: text })] }))] }));
|
|
35
35
|
}
|
|
@@ -70,7 +70,7 @@ function Judged({ ply, text }) {
|
|
|
70
70
|
function Tactics({ helps, opened, onOpen, onPoint, text }) {
|
|
71
71
|
const words = text.helpsCard;
|
|
72
72
|
const point = pointing(onPoint);
|
|
73
|
-
const calm = !helps.lines.some((line) => line.kind === "risk" || line.kind === "pin");
|
|
73
|
+
const calm = !helps.lines.some((line) => line.kind === "risk" || line.kind === "pin" || line.kind === "skewer");
|
|
74
74
|
return (_jsxs("ul", { class: "help-rows", children: [calm
|
|
75
75
|
? (_jsxs("li", { class: "safe", children: [_jsxs(RowIcon, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5" }), _jsx("path", { d: "M5.2 8.2l2 2 3.8-4" })] }), words.nothingAtRisk] }))
|
|
76
76
|
: null, helps.lines.map((line) => {
|
|
@@ -86,17 +86,20 @@ function pointing(onPoint) {
|
|
|
86
86
|
onPointerLeave: (event) => event.pointerType === "mouse" && onPoint([]),
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
|
-
/** A line's sign: the corner of a piece at risk,
|
|
89
|
+
/** A line's sign, as the board draws it: the corner of a piece at risk, the pushpin, the skewer; a check's plus, the loose pieces' i; its colour is the line's. */
|
|
90
90
|
function LineIcon({ kind }) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
switch (kind) {
|
|
92
|
+
case "risk":
|
|
93
|
+
return _jsx(Notch, {});
|
|
94
|
+
case "pin":
|
|
95
|
+
return _jsx(Pushpin, {});
|
|
96
|
+
case "skewer":
|
|
97
|
+
return _jsx(Skewer, {});
|
|
98
|
+
case "loose":
|
|
99
|
+
return _jsx(InfoIcon, {});
|
|
100
|
+
case "check":
|
|
101
|
+
return (_jsxs(RowIcon, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5" }), _jsx("path", { d: "M8 5v6M5 8h6" })] }));
|
|
96
102
|
}
|
|
97
|
-
return (_jsx(RowIcon, { children: kind === "pin"
|
|
98
|
-
? _jsx("path", { d: "M6.5 9.5l3-3M7.5 4.5l1-1a2.5 2.5 0 0 1 3.5 3.5l-1 1M8.5 11.5l-1 1a2.5 2.5 0 0 1-3.5-3.5l1-1" })
|
|
99
|
-
: (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5" }), _jsx("path", { d: "M8 5v6M5 8h6" })] })) }));
|
|
100
103
|
}
|
|
101
104
|
/** How many: the loose pieces by side, each after its pawn, the player's first (Zoe); the others in one number. */
|
|
102
105
|
function LineCount({ line, helps, words }) {
|
|
@@ -115,6 +118,8 @@ function LineDetails({ line, helps, point, text }) {
|
|
|
115
118
|
return (_jsx("ul", { class: "line-details", children: line.items.map((risk) => (_jsxs("li", { ...point(riskSquares(risk)), children: [text.piece(risk.piece.piece, risk.piece.side), " ", _jsx(Squares, { squares: [risk.piece.square] }), " \u00B7 ", words.attackers, ": ", _jsx(Squares, { squares: risk.piece.attackers }), risk.why === "undefended" ? null : (_jsxs(_Fragment, { children: [" ", "\u00B7 ", words.defenders, ": ", _jsx(Squares, { squares: risk.piece.defenders })] })), " \u00B7 ", words[risk.why]] }, risk.piece.square))) }));
|
|
116
119
|
case "pin":
|
|
117
120
|
return (_jsx("ul", { class: "line-details", children: line.items.map((pin) => (_jsxs("li", { ...point(pinSquares(pin)), children: [text.piece(pin.piece.piece, pin.piece.side), " ", _jsx(Squares, { squares: [pin.piece.square] }), " ", words.pinnedTo(pin.to.piece), " ", _jsx(Squares, { squares: [pin.to.square] }), ", ", words.pinnedBy(pin.by.piece), " ", _jsx(Squares, { squares: [pin.by.square] })] }, pin.piece.square))) }));
|
|
121
|
+
case "skewer":
|
|
122
|
+
return (_jsx("ul", { class: "line-details", children: line.items.map((skewer) => (_jsxs("li", { ...point(pinSquares(skewer)), children: [text.piece(skewer.piece.piece, skewer.piece.side), " ", _jsx(Squares, { squares: [skewer.piece.square] }), " ", words.skeweredBefore(skewer.to.piece), " ", _jsx(Squares, { squares: [skewer.to.square] }), ", ", words.pinnedBy(skewer.by.piece), " ", _jsx(Squares, { squares: [skewer.by.square] })] }, skewer.piece.square))) }));
|
|
118
123
|
case "check":
|
|
119
124
|
return (_jsx("p", { class: "line-details", children: line.items.map((check) => (_jsx("span", { class: "help-move", children: _jsx(Figurines, { san: check, side: line.side }) }, check))) }));
|
|
120
125
|
case "loose":
|
package/dist/page/helps/view.js
CHANGED
|
@@ -7,6 +7,7 @@ export function helpsOf(game, you) {
|
|
|
7
7
|
const toMove = game.position.turn;
|
|
8
8
|
const atRisk = [...risks(facts.undefended, "undefended"), ...risks(facts.attackedByCheaper, "cheaper"), ...risks(facts.outnumbered, "outnumbered")];
|
|
9
9
|
const pins = facts.pins.map((pin) => ({ piece: on(pin.square), by: on(pin.by), to: on(pin.to) }));
|
|
10
|
+
const skewers = facts.skewers.map((skewer) => ({ piece: on(skewer.square), by: on(skewer.by), to: on(skewer.to) }));
|
|
10
11
|
const checks = facts.checks;
|
|
11
12
|
const otherChecks = facts.otherChecks ?? [];
|
|
12
13
|
const loose = facts.loose.map(on);
|
|
@@ -15,10 +16,11 @@ export function helpsOf(game, you) {
|
|
|
15
16
|
you,
|
|
16
17
|
atRisk,
|
|
17
18
|
pins,
|
|
19
|
+
skewers,
|
|
18
20
|
checks,
|
|
19
21
|
otherChecks,
|
|
20
22
|
loose,
|
|
21
|
-
lines: tacticLines({ toMove, atRisk, pins, checks, otherChecks, loose }, you),
|
|
23
|
+
lines: tacticLines({ toMove, atRisk, pins, skewers, checks, otherChecks, loose }, you),
|
|
22
24
|
strategy: game.profile,
|
|
23
25
|
sides: you === "black" ? ["black", "white"] : ["white", "black"],
|
|
24
26
|
};
|
|
@@ -28,12 +30,23 @@ export function riskOn(helps, square) {
|
|
|
28
30
|
const risk = helps.atRisk.find((risk) => risk.piece.square === square);
|
|
29
31
|
return risk === undefined ? undefined : toneOf(risk.piece.side, helps.you);
|
|
30
32
|
}
|
|
31
|
-
/**
|
|
33
|
+
/** How the helps mark a square's pinned piece: in the tone of a piece at risk, red when it is the player's, green when it is the other side's (D-91). */
|
|
34
|
+
export function pinOn(helps, square) {
|
|
35
|
+
const pin = helps.pins.find((pin) => pin.piece.square === square);
|
|
36
|
+
return pin === undefined ? undefined : toneOf(pin.piece.side, helps.you);
|
|
37
|
+
}
|
|
38
|
+
/** How the helps mark a square's skewered piece, the one in front: in the tone of a piece at risk, as a pinned one (D-91). */
|
|
39
|
+
export function skewerOn(helps, square) {
|
|
40
|
+
const skewer = helps.skewers.find((skewer) => skewer.piece.square === square);
|
|
41
|
+
return skewer === undefined ? undefined : toneOf(skewer.piece.side, helps.you);
|
|
42
|
+
}
|
|
43
|
+
/** The squares a line of the tactics lights on the board: its pieces, and those that attack, pin or skewer them; none for the checks. */
|
|
32
44
|
export function squaresOf(line) {
|
|
33
45
|
switch (line.kind) {
|
|
34
46
|
case "risk":
|
|
35
47
|
return line.items.flatMap(riskSquares);
|
|
36
48
|
case "pin":
|
|
49
|
+
case "skewer":
|
|
37
50
|
return line.items.flatMap(pinSquares);
|
|
38
51
|
case "check":
|
|
39
52
|
return [];
|
|
@@ -45,7 +58,7 @@ export function squaresOf(line) {
|
|
|
45
58
|
export function riskSquares(risk) {
|
|
46
59
|
return [risk.piece.square, ...risk.piece.attackers];
|
|
47
60
|
}
|
|
48
|
-
/** A pinned piece, the one that pins it and the one behind it. */
|
|
61
|
+
/** A pinned or skewered piece, the one that pins or skewers it and the one behind it. */
|
|
49
62
|
export function pinSquares(pin) {
|
|
50
63
|
return [pin.piece.square, pin.by.square, pin.to.square];
|
|
51
64
|
}
|
|
@@ -88,12 +101,13 @@ const cells = {
|
|
|
88
101
|
castled: castlingOf,
|
|
89
102
|
kingAttacks: (side) => side.king?.attacksNear ?? 0,
|
|
90
103
|
};
|
|
91
|
-
// The tactics' lines that have something: with the player's side, their pieces at risk and
|
|
92
|
-
// round; without it, White's then Black's; the loose pieces last.
|
|
104
|
+
// The tactics' lines that have something: with the player's side, their pieces at risk, pinned and skewered and the other side's checks, then the same the
|
|
105
|
+
// other way round; without it, White's then Black's; the loose pieces last.
|
|
93
106
|
function tacticLines(facts, you) {
|
|
94
107
|
const sideLines = (side) => [
|
|
95
108
|
{ kind: "risk", side, tone: toneOf(side, you), items: facts.atRisk.filter((risk) => risk.piece.side === side) },
|
|
96
109
|
{ kind: "pin", side, tone: toneOf(side, you), items: facts.pins.filter((pin) => pin.piece.side === side) },
|
|
110
|
+
{ kind: "skewer", side, tone: toneOf(side, you), items: facts.skewers.filter((skewer) => skewer.piece.side === side) },
|
|
97
111
|
];
|
|
98
112
|
const checkLine = (side) => ({
|
|
99
113
|
kind: "check",
|
|
@@ -751,10 +751,31 @@
|
|
|
751
751
|
display: none;
|
|
752
752
|
}
|
|
753
753
|
|
|
754
|
-
/* A game in analysis
|
|
755
|
-
|
|
754
|
+
/* A game in analysis: the card on top, the board, its steps under the thumb, the Torretta's note, the talk, the card below, the moves; turned round,
|
|
755
|
+
the player's card on top (Zoe) */
|
|
756
|
+
.app:has(.talk.studied) {
|
|
757
|
+
grid-template-areas: "head" "ai" "board" "steps" "note" "chat" "you" "side";
|
|
758
|
+
}
|
|
759
|
+
|
|
756
760
|
.app.flipped:has(.talk.studied) {
|
|
757
|
-
grid-template-areas: "head" "
|
|
761
|
+
grid-template-areas: "head" "you" "board" "steps" "note" "chat" "ai" "side";
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.talk.studied > .you-block {
|
|
765
|
+
display: contents;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
.talk.studied > .you-block > :not(.chat) {
|
|
769
|
+
grid-area: you;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/* The talk, under the note: half the screen of it at most */
|
|
773
|
+
.talk.studied .chat {
|
|
774
|
+
grid-area: chat;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
.talk.studied .sent {
|
|
778
|
+
max-height: 50vh;
|
|
758
779
|
}
|
|
759
780
|
|
|
760
781
|
.talk.studied > .ai-block {
|
|
@@ -771,9 +792,12 @@
|
|
|
771
792
|
}
|
|
772
793
|
|
|
773
794
|
/* With no chat listening, its note comes before the Torretta's (Zoe) */
|
|
774
|
-
.app:has(.talk.studied > .ai-block > .asleep)
|
|
795
|
+
.app:has(.talk.studied > .ai-block > .asleep) {
|
|
796
|
+
grid-template-areas: "head" "ai" "board" "steps" "asleep" "note" "chat" "you" "side";
|
|
797
|
+
}
|
|
798
|
+
|
|
775
799
|
.app.flipped:has(.talk.studied > .ai-block > .asleep) {
|
|
776
|
-
grid-template-areas: "head" "
|
|
800
|
+
grid-template-areas: "head" "you" "board" "steps" "asleep" "note" "chat" "ai" "side";
|
|
777
801
|
}
|
|
778
802
|
|
|
779
803
|
.talk.studied > .ai-block > .asleep {
|