chesspal 0.3.5 → 0.4.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 +1 -0
- package/dist/cli/cli.js +17 -4
- package/dist/cli/command-line.js +22 -12
- package/dist/cli/icon.d.ts +2 -0
- package/dist/cli/icon.js +11 -1
- package/dist/cli/prompt-hook.d.ts +17 -0
- package/dist/cli/prompt-hook.js +100 -0
- package/dist/commands/analysis/analyse.d.ts +2 -2
- package/dist/commands/analysis/analyse.js +10 -16
- package/dist/commands/analysis/annotate.d.ts +8 -0
- package/dist/commands/analysis/annotate.js +19 -3
- package/dist/commands/answers/agents.d.ts +17 -7
- package/dist/commands/answers/agents.js +46 -16
- package/dist/commands/answers/clock.js +2 -1
- package/dist/commands/answers/drawings.js +6 -2
- package/dist/commands/answers/index.d.ts +4 -3
- package/dist/commands/answers/index.js +4 -3
- package/dist/commands/answers/lines.d.ts +5 -3
- package/dist/commands/answers/lines.js +17 -9
- package/dist/commands/answers/opening-book.d.ts +13 -7
- package/dist/commands/answers/opening-book.js +34 -38
- package/dist/commands/answers/opening-tags.d.ts +4 -0
- package/dist/commands/answers/opening-tags.js +11 -0
- package/dist/commands/answers/play-move.d.ts +3 -1
- package/dist/commands/answers/play-move.js +9 -6
- package/dist/commands/answers/say.js +2 -1
- package/dist/commands/answers/summary.d.ts +7 -3
- package/dist/commands/answers/summary.js +16 -5
- package/dist/commands/answers/view.d.ts +1 -1
- package/dist/commands/answers/view.js +2 -2
- package/dist/commands/game/ai-move.js +4 -4
- package/dist/commands/game/coach.js +7 -6
- package/dist/commands/game/end.js +2 -2
- package/dist/commands/game/export.d.ts +1 -1
- package/dist/commands/game/index.d.ts +1 -1
- package/dist/commands/game/index.js +1 -1
- package/dist/commands/game/look.d.ts +7 -3
- package/dist/commands/game/look.js +9 -4
- package/dist/commands/game/new.js +42 -12
- package/dist/commands/game/player-move.d.ts +16 -0
- package/dist/commands/game/player-move.js +69 -19
- package/dist/commands/game/ponder.js +7 -4
- package/dist/commands/game/position.d.ts +2 -1
- package/dist/commands/game/position.js +9 -4
- package/dist/commands/game/status.d.ts +4 -2
- package/dist/commands/game/status.js +24 -14
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/index.js +3 -3
- package/dist/commands/profiles/past-games.d.ts +7 -2
- package/dist/commands/profiles/past-games.js +45 -18
- package/dist/commands/registry/context.d.ts +2 -0
- package/dist/commands/registry/context.js +5 -0
- package/dist/commands/registry/index.d.ts +1 -1
- package/dist/commands/registry/index.js +1 -1
- package/dist/commands/talk/ai-say.d.ts +1 -1
- package/dist/commands/talk/ai-say.js +7 -6
- package/dist/core/game.d.ts +13 -0
- package/dist/core/game.js +29 -3
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +3 -3
- package/dist/core/openings.d.ts +20 -6
- package/dist/core/openings.js +45 -22
- package/dist/core/pgn.d.ts +4 -0
- package/dist/core/pgn.js +35 -6
- package/dist/games/analysis.d.ts +7 -4
- package/dist/games/analysis.js +5 -3
- package/dist/games/archive.d.ts +25 -5
- package/dist/games/archive.js +52 -19
- package/dist/games/chat-start.d.ts +2 -2
- package/dist/games/chat-start.js +6 -1
- package/dist/games/current-game.d.ts +2 -2
- package/dist/games/current-game.js +3 -2
- package/dist/games/index.d.ts +3 -2
- package/dist/games/index.js +3 -2
- package/dist/games/position-shown.d.ts +24 -0
- package/dist/games/position-shown.js +51 -0
- package/dist/games/thoughts.d.ts +12 -4
- package/dist/games/thoughts.js +27 -10
- package/dist/home/index.d.ts +1 -0
- package/dist/home/index.js +1 -0
- package/dist/home/same-path.d.ts +7 -0
- package/dist/home/same-path.js +24 -0
- package/dist/host-files/host-files.d.ts +5 -0
- package/dist/host-files/host-files.js +21 -7
- package/dist/host-files/index.d.ts +1 -1
- package/dist/host-files/index.js +1 -1
- package/dist/page/app/anchor.js +15 -11
- package/dist/page/app/app.js +49 -14
- package/dist/page/app/served.js +30 -38
- package/dist/page/app/watch.js +97 -0
- package/dist/page/avatars/torretta.css +108 -0
- package/dist/page/base.css +1 -1
- package/dist/page/board/board.css +11 -8
- package/dist/page/board/marks.js +2 -2
- package/dist/page/board/pointer.js +2 -2
- package/dist/page/dialogs/analyses.js +5 -3
- package/dist/page/dialogs/dialogs.css +3 -1
- package/dist/page/dialogs/rows.js +72 -0
- package/dist/page/dialogs/site-games.js +3 -1
- package/dist/page/game/column.js +10 -8
- package/dist/page/game/game.css +137 -16
- package/dist/page/game/move-list.js +7 -5
- package/dist/page/game/plies.js +4 -0
- package/dist/page/game/review-progress.js +11 -0
- package/dist/page/guide/drawings/drawings.css +0 -1
- package/dist/page/guide/en.md +2 -2
- package/dist/page/guide/it.md +2 -2
- package/dist/page/helps/helps.css +5 -5
- package/dist/page/helps/helps.js +3 -181
- package/dist/page/helps/rows.js +16 -0
- package/dist/page/helps/strategy.js +105 -0
- package/dist/page/helps/tactics.js +69 -0
- package/dist/page/layout/header.js +49 -6
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/layout.css +49 -17
- package/dist/page/layout/window.js +45 -6
- package/dist/page/lead/lead.js +41 -4
- package/dist/page/legend/legend.css +4 -16
- package/dist/page/legend/legend.js +28 -15
- package/dist/page/profile/settings.js +1 -0
- package/dist/page/sounds/sounds.js +1 -1
- package/dist/page/style.css +1 -0
- package/dist/page/table/action.js +42 -0
- package/dist/page/table/table.css +57 -39
- package/dist/page/table/window.js +67 -67
- package/dist/page/talk/talk.css +52 -118
- package/dist/page/talk/talk.js +65 -20
- package/dist/page/talk/voice.js +92 -21
- package/dist/page/text.js +34 -8
- package/dist/page/theme.css +13 -0
- package/dist/profiles/profiles.js +5 -1
- package/dist/server/bell/bell.d.ts +24 -29
- package/dist/server/bell/bell.js +79 -133
- package/dist/server/bell/index.d.ts +1 -1
- package/dist/server/bell/index.js +2 -2
- package/dist/server/mcp/mcp.js +49 -7
- package/dist/server/page/analyses.js +15 -4
- package/dist/server/page/chat.d.ts +5 -1
- package/dist/server/page/chat.js +5 -1
- package/dist/server/page/files.js +3 -1
- package/dist/server/page/game.d.ts +5 -0
- package/dist/server/page/game.js +95 -15
- package/dist/server/page/http.d.ts +1 -1
- package/dist/server/page/page.d.ts +14 -9
- package/dist/server/page/page.js +111 -26
- package/dist/server/page/table.d.ts +40 -5
- package/dist/server/page/table.js +65 -25
- package/dist/server/page/update.d.ts +1 -1
- package/dist/server/page/update.js +4 -4
- package/dist/server/page/windows.d.ts +22 -0
- package/dist/server/page/windows.js +45 -0
- package/dist/server/ports/index.d.ts +1 -0
- package/dist/server/ports/index.js +2 -0
- package/dist/server/ports/ports.d.ts +78 -0
- package/dist/server/ports/ports.js +222 -0
- package/dist/server/table/browser.d.ts +22 -6
- package/dist/server/table/browser.js +98 -20
- package/dist/server/table/hosts/chats.d.ts +5 -0
- package/dist/server/table/hosts/chats.js +11 -4
- package/dist/server/table/hosts/hooks.d.ts +6 -0
- package/dist/server/table/hosts/hooks.js +38 -0
- package/dist/server/table/hosts/hosts.d.ts +13 -5
- package/dist/server/table/hosts/hosts.js +16 -10
- package/dist/server/table/hosts/index.d.ts +1 -0
- package/dist/server/table/hosts/index.js +1 -0
- package/dist/server/table/hosts/play.d.ts +7 -3
- package/dist/server/table/hosts/play.js +63 -9
- package/dist/server/table/icon/index.d.ts +2 -0
- package/dist/server/table/icon/index.js +16 -2
- package/dist/server/table/index.d.ts +2 -2
- package/dist/server/table/index.js +1 -1
- package/dist/server/table/launch.d.ts +5 -4
- package/dist/server/table/launch.js +18 -5
- package/dist/server/table/link.js +4 -1
- package/dist/server/table/table.d.ts +7 -0
- package/dist/server/table/table.js +40 -12
- package/dist/sites/index.d.ts +1 -1
- package/dist/sites/index.js +1 -1
- package/dist/sites/{chesscom.d.ts → ratings/chesscom.d.ts} +2 -2
- package/dist/sites/{chesscom.js → ratings/chesscom.js} +1 -1
- package/dist/sites/{choice.d.ts → ratings/choice.d.ts} +1 -1
- package/dist/sites/{choice.js → ratings/choice.js} +1 -1
- package/dist/sites/ratings/index.d.ts +1 -0
- package/dist/sites/ratings/index.js +2 -0
- package/dist/sites/{lichess.d.ts → ratings/lichess.d.ts} +2 -2
- package/dist/sites/{lichess.js → ratings/lichess.js} +1 -1
- package/dist/sites/{sites.d.ts → ratings/ratings.d.ts} +2 -2
- package/dist/sites/{sites.js → ratings/ratings.js} +1 -1
- package/instructions/opponent.md +1 -1
- package/instructions/start.md +1 -1
- package/package.json +1 -1
|
@@ -801,26 +801,26 @@
|
|
|
801
801
|
/* The thread that keeps the black half in sight on the dark theme, as the lead bar's */
|
|
802
802
|
.control-bar,
|
|
803
803
|
.row-tile {
|
|
804
|
-
border: 1px solid
|
|
804
|
+
border: 1px solid var(--side-edge);
|
|
805
805
|
}
|
|
806
806
|
|
|
807
807
|
/* The bar's colours, which the lines' tiles share */
|
|
808
808
|
.control-bar .white,
|
|
809
809
|
.row-tile .white {
|
|
810
|
-
background:
|
|
810
|
+
background: var(--side-white);
|
|
811
811
|
color: var(--fixed-ink);
|
|
812
812
|
}
|
|
813
813
|
|
|
814
814
|
.control-bar .both,
|
|
815
815
|
.row-tile .both {
|
|
816
|
-
background:
|
|
816
|
+
background: var(--side-both);
|
|
817
817
|
color: var(--fixed-ink);
|
|
818
818
|
}
|
|
819
819
|
|
|
820
820
|
.control-bar .black,
|
|
821
821
|
.row-tile .black {
|
|
822
|
-
background: var(--
|
|
823
|
-
color:
|
|
822
|
+
background: var(--side-black);
|
|
823
|
+
color: var(--side-white);
|
|
824
824
|
}
|
|
825
825
|
|
|
826
826
|
/* A line's tile: a half for each side, the player's first; the open files in one half as wide as the two */
|
package/dist/page/helps/helps.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
|
|
2
2
|
import { useState } from "preact/hooks";
|
|
3
|
-
import { Notch, Pushpin, Skewer } from "../board/marks.js";
|
|
4
3
|
import { BadgeIcon, NoteIcon } from "../game/badges.js";
|
|
5
4
|
import { FoldButton, useCardFold } from "../game/card-fold.js";
|
|
6
5
|
import { Figurines } from "../game/move-list.js";
|
|
7
|
-
import { Figurine } from "../pieces/pieces.js";
|
|
8
6
|
import { Legend, Marked } from "../talk/talk.js";
|
|
9
|
-
import {
|
|
7
|
+
import { RowIcon } from "./rows.js";
|
|
8
|
+
import { Strategy } from "./strategy.js";
|
|
9
|
+
import { Tactics } from "./tactics.js";
|
|
10
10
|
/**
|
|
11
11
|
* The helps' card, between the opening and the moves: the coach's words, then the tactics in lines, the strategy in rows and the control map, seen while
|
|
12
12
|
* 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
|
|
@@ -60,185 +60,7 @@ function Judged({ ply, text }) {
|
|
|
60
60
|
const head = (_jsxs(_Fragment, { children: [badge === undefined ? _jsx(NoteIcon, {}) : _jsx(BadgeIcon, { badge: badge }), _jsxs("b", { children: [_jsxs("span", { class: "move-number", children: [ply.number, ply.side === "white" ? "." : "..."] }), _jsx(Figurines, { san: ply.san, side: ply.side })] }), " ", badge === undefined ? null : text.badges[badge]] }));
|
|
61
61
|
return (_jsx("div", { class: `move-note judged ${badge ?? ""}`, style: badge === undefined ? undefined : { "--row": `var(--badge-${badge})` }, children: note === undefined ? _jsx("span", { class: "head", children: head }) : (_jsxs("details", { children: [_jsx("summary", { class: "head", children: head }), _jsx("span", { class: "note", children: _jsx(Marked, { text: note }) })] })) }));
|
|
62
62
|
}
|
|
63
|
-
/**
|
|
64
|
-
* The tactics in lines (D-91): one a kind of fact and a side, its count, red when it threatens the player and green when it is their chance; a click opens
|
|
65
|
-
* its pieces, each with its squares (Stefano).
|
|
66
|
-
*/
|
|
67
|
-
function Tactics({ helps, opened, onOpen, onPoint, text }) {
|
|
68
|
-
const words = text.helpsCard;
|
|
69
|
-
const point = pointing(onPoint);
|
|
70
|
-
const calm = !helps.lines.some((line) => line.kind === "risk" || line.kind === "pin" || line.kind === "skewer");
|
|
71
|
-
return (_jsxs("ul", { class: "help-rows", children: [calm
|
|
72
|
-
? (_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] }))
|
|
73
|
-
: null, helps.lines.map((line) => {
|
|
74
|
-
const key = line.kind === "loose" ? "loose" : `${line.kind} ${line.side}`;
|
|
75
|
-
const open = opened.has(key);
|
|
76
|
-
return (_jsxs("li", { class: `help-line ${line.kind} ${line.tone}${open ? " open" : ""}`, children: [_jsxs("button", { type: "button", class: "line-head", "aria-expanded": open, onClick: () => onOpen(key, !open), ...point(squaresOf(line)), children: [_jsx(LineIcon, { kind: line.kind }), _jsx("span", { class: "line-name", children: line.kind === "loose" ? words.loose : words.line(line.kind, line.side, helps.you, line.kind !== "check" || line.toMove) }), _jsx(LineCount, { line: line, helps: helps, words: words })] }), open ? _jsx(LineDetails, { line: line, helps: helps, point: point, text: text }) : null] }, key));
|
|
77
|
-
})] }));
|
|
78
|
-
}
|
|
79
|
-
/** What the pointer on a line or a cell does: it lights the line's squares; a touch keeps them lit until the position changes, as a square in the AI's words. */
|
|
80
|
-
function pointing(onPoint) {
|
|
81
|
-
return (squares) => ({
|
|
82
|
-
onPointerEnter: () => onPoint(squares),
|
|
83
|
-
onPointerLeave: (event) => event.pointerType === "mouse" && onPoint([]),
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
/** 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. */
|
|
87
|
-
function LineIcon({ kind }) {
|
|
88
|
-
switch (kind) {
|
|
89
|
-
case "risk":
|
|
90
|
-
return _jsx(Notch, {});
|
|
91
|
-
case "pin":
|
|
92
|
-
return _jsx(Pushpin, {});
|
|
93
|
-
case "skewer":
|
|
94
|
-
return _jsx(Skewer, {});
|
|
95
|
-
case "loose":
|
|
96
|
-
return _jsx(InfoIcon, {});
|
|
97
|
-
case "check":
|
|
98
|
-
return (_jsxs(RowIcon, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5" }), _jsx("path", { d: "M8 5v6M5 8h6" })] }));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
/** How many: the loose pieces by side, each after its pawn, the player's first (Zoe); the others in one number. */
|
|
102
|
-
function LineCount({ line, helps, words }) {
|
|
103
|
-
if (line.kind !== "loose") {
|
|
104
|
-
return _jsx("b", { class: "count", children: line.items.length });
|
|
105
|
-
}
|
|
106
|
-
const name = (side) => helps.you === undefined ? words[side] : side === helps.you ? words.yours : words.theirs;
|
|
107
|
-
const counts = helps.sides.map((side) => ({ side, count: line.items.filter((piece) => piece.side === side).length })).filter(({ count }) => count > 0);
|
|
108
|
-
return (_jsx("span", { class: "counts", children: counts.map(({ side, count }) => (_jsxs("span", { title: name(side), "aria-label": `${name(side)} ${count}`, children: [_jsx(Figurine, { letter: "P", side: side }), _jsx("b", { class: "count", children: count })] }, side))) }));
|
|
109
|
-
}
|
|
110
|
-
/** A line opened: its pieces, each lighting its own squares; the checks in figures; the loose pieces by side. */
|
|
111
|
-
function LineDetails({ line, helps, point, text }) {
|
|
112
|
-
const words = text.helpsCard;
|
|
113
|
-
switch (line.kind) {
|
|
114
|
-
case "risk":
|
|
115
|
-
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
|
-
case "pin":
|
|
117
|
-
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))) }));
|
|
118
|
-
case "skewer":
|
|
119
|
-
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))) }));
|
|
120
|
-
case "check":
|
|
121
|
-
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))) }));
|
|
122
|
-
case "loose":
|
|
123
|
-
return (_jsx("p", { class: "line-details", children: helps.sides.map((side) => _jsx(Pieces, { pieces: line.items.filter((piece) => piece.side === side) }, side)) }));
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
63
|
/** The helps' sign before the card's title: a lifebuoy (Stefano). */
|
|
127
64
|
function HelpsIcon() {
|
|
128
65
|
return (_jsxs("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("circle", { cx: "12", cy: "12", r: "8.5" }), _jsx("circle", { cx: "12", cy: "12", r: "3.5" }), _jsx("path", { d: "M6 6L9.5 9.5M18 6L14.5 9.5M6 18L9.5 14.5M18 18L14.5 14.5" })] }));
|
|
129
66
|
}
|
|
130
|
-
/** The icon before a line of facts, in its kind's colour (Stefano): a pin's link, nothing at risk's tick, a check's plus, the loose pieces' i. */
|
|
131
|
-
function RowIcon({ children }) {
|
|
132
|
-
return (_jsx("span", { class: "row-icon", "aria-hidden": "true", children: _jsx("svg", { viewBox: "0 0 16 16", children: children }) }));
|
|
133
|
-
}
|
|
134
|
-
/** An i in a ring, before the loose pieces and the strategy's notes. */
|
|
135
|
-
function InfoIcon() {
|
|
136
|
-
return (_jsxs(RowIcon, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5" }), _jsx("path", { d: "M8 7.3V11M8 4.9v.1" })] }));
|
|
137
|
-
}
|
|
138
|
-
/** Pieces of one side, each its figure and its square. */
|
|
139
|
-
function Pieces({ pieces }) {
|
|
140
|
-
return pieces.length === 0 ? null : (_jsx("span", { class: "help-pieces", children: pieces.map((piece) => (_jsxs("span", { class: "help-move", children: [_jsx(Figurine, { letter: piece.piece, side: piece.side }), _jsx("b", { class: "square-name", children: piece.square })] }, piece.square))) }));
|
|
141
|
-
}
|
|
142
|
-
function Squares({ squares }) {
|
|
143
|
-
return _jsx("b", { class: "square-name", children: squares.join(", ") });
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* The strategy (Stefano): the phases with the current one in a pill, the bar of the squares each side controls, then the lines in groups, each a name and a
|
|
147
|
-
* tile of a half per side, and the notes as the tactics'. The player's side comes first, with no colour of good or bad (D-91).
|
|
148
|
-
*/
|
|
149
|
-
function Strategy({ helps, onPoint, text }) {
|
|
150
|
-
const words = text.helpsCard;
|
|
151
|
-
const profile = helps.strategy;
|
|
152
|
-
const { draw, pawnEnding } = profile;
|
|
153
|
-
const point = pointing(onPoint);
|
|
154
|
-
// A line opens under it at a click or a tap, on its name's meaning and each side's squares or files, since a phone has no pointer for a title (Rosa).
|
|
155
|
-
const [explained, setExplained] = useState(undefined);
|
|
156
|
-
const lines = strategyOf(profile);
|
|
157
|
-
const sideName = (side) => side === helps.you ? `${words[side]} ${words.you}` : words[side];
|
|
158
|
-
const notes = [
|
|
159
|
-
profile.oppositeBishops ? words.oppositeBishops : undefined,
|
|
160
|
-
draw.repetitions > 1 ? words.repetitions(draw.repetitions) : undefined,
|
|
161
|
-
draw.fiftyMovesLeft <= fiftyMovesShown ? words.fiftyMoves(draw.fiftyMovesLeft) : undefined,
|
|
162
|
-
draw.insufficient ? words.insufficient : undefined,
|
|
163
|
-
...(pawnEnding?.squares ?? []).map((square) => words.pawnSquare(square.pawn, square.caught)),
|
|
164
|
-
pawnEnding?.opposition === undefined ? undefined : words.opposition(pawnEnding.opposition),
|
|
165
|
-
].filter((note) => note !== undefined);
|
|
166
|
-
return (_jsxs("div", { class: "strategy", children: [_jsx(Phases, { current: profile.phase, words: words }), _jsx(ControlBar, { controlled: profile.controlled, sides: helps.sides, you: helps.you, words: words }), groups.map((group) => {
|
|
167
|
-
const shown = lines.filter((line) => groupOf[line.row] === group);
|
|
168
|
-
return shown.length === 0 ? null : (_jsx("ul", { class: "strategy-rows", children: shown.map((line) => {
|
|
169
|
-
const [name, meaning] = words.rows[line.row];
|
|
170
|
-
const open = explained === line.row;
|
|
171
|
-
return (_jsxs("li", { class: open ? "open" : undefined, ...point(squaresIn(line)), children: [_jsxs("div", { class: "row-head", children: [_jsx("button", { type: "button", class: "term", title: meaning, "aria-expanded": open, onClick: () => setExplained(open ? undefined : line.row), children: name }), _jsx(Tile, { line: line, sides: helps.sides, sideName: sideName, words: words })] }), open ? _jsx(Meaning, { line: line, meaning: meaning, sides: helps.sides, sideName: sideName }) : null] }, line.row));
|
|
172
|
-
}) }, group));
|
|
173
|
-
}), notes.length === 0 ? null : (_jsx("ul", { class: "help-rows", children: notes.map((note) => (_jsxs("li", { class: "info", children: [_jsx(InfoIcon, {}), note] }, note))) }))] }));
|
|
174
|
-
}
|
|
175
|
-
// The strategy's groups in their order, apart only by a gap.
|
|
176
|
-
const groups = ["pawns", "pieces", "king"];
|
|
177
|
-
const phases = ["opening", "middlegame", "endgame"];
|
|
178
|
-
// The fifty-move count shows only when the draw is near: ten moves each, or less.
|
|
179
|
-
const fiftyMovesShown = 20;
|
|
180
|
-
/** The squares a side's cells of a strategy's line name, to light on the board; files and islands light none. */
|
|
181
|
-
function squaresIn(line) {
|
|
182
|
-
if (line.row === "openFiles") {
|
|
183
|
-
return [];
|
|
184
|
-
}
|
|
185
|
-
return [line.white, line.black].flatMap((cell) => typeof cell === "object" ? cell.filter((item) => /^[a-h][1-8]$/.test(item)) : []);
|
|
186
|
-
}
|
|
187
|
-
/** The game's three phases in a row of arrows: the current one full, those gone by lit, those to come not. */
|
|
188
|
-
function Phases({ current, words }) {
|
|
189
|
-
return (_jsx("ol", { class: "phases", children: phases.map((phase, at) => (_jsx("li", { class: at < phases.indexOf(current) ? "done" : undefined, "aria-current": phase === current ? "step" : undefined, children: words.phases[phase] }, phase))) }));
|
|
190
|
-
}
|
|
191
|
-
/** The squares only one side controls and those both do, in a bar as long as their counts, the player's side on the left; each part's title says it in words. */
|
|
192
|
-
function ControlBar({ controlled, sides, you, words }) {
|
|
193
|
-
const { white, black, contested } = controlled;
|
|
194
|
-
const [left, right] = sides;
|
|
195
|
-
const parts = [{ who: left, count: controlled[left] }, { who: "both", count: contested }, { who: right, count: controlled[right] }];
|
|
196
|
-
return (_jsx("span", { class: "control-bar", role: "img", "aria-label": words.controlled(white, black, contested), children: parts.filter(({ count }) => count > 0).map(({ who, count }) => _jsx("span", { class: who, style: { flexGrow: count }, title: words.controlledBy(who, count, who === you), children: count }, who)) }));
|
|
197
|
-
}
|
|
198
|
-
/** A line's tile: a half for each side, the player's first, in the bar's colours; the open files, both sides', in one half as wide as the two. */
|
|
199
|
-
function Tile({ line, sides, sideName, words }) {
|
|
200
|
-
if (line.row === "openFiles") {
|
|
201
|
-
return (_jsx("span", { class: "row-tile", children: _jsxs("span", { class: "both", title: words.bothSides, children: [_jsx("span", { class: "visually-hidden", children: `${words.bothSides} ` }), _jsx("b", { class: "count", children: line.both.length })] }) }));
|
|
202
|
-
}
|
|
203
|
-
return (_jsx("span", { class: "row-tile", children: sides.map((side) => (_jsxs("span", { class: side, title: sideName(side), children: [_jsx("span", { class: "visually-hidden", children: `${sideName(side)} ` }), _jsx(Cell, { cell: line[side], words: words })] }, side))) }));
|
|
204
|
-
}
|
|
205
|
-
/** A line opened: its meaning, then each listed side's squares or files in chips; the open files with no side's name. */
|
|
206
|
-
function Meaning({ line, meaning, sides, sideName }) {
|
|
207
|
-
if (line.row === "openFiles") {
|
|
208
|
-
return (_jsxs("div", { class: "meaning", children: [meaning, _jsx("span", { class: "listed", children: _jsx(Chips, { items: line.both }) })] }));
|
|
209
|
-
}
|
|
210
|
-
return (_jsxs("div", { class: "meaning", children: [meaning, sides.map((side) => {
|
|
211
|
-
const cell = line[side];
|
|
212
|
-
return typeof cell === "object"
|
|
213
|
-
? (_jsxs("span", { class: "listed", children: [sideName(side), ": ", _jsx(Chips, { items: cell })] }, side))
|
|
214
|
-
: null;
|
|
215
|
-
})] }));
|
|
216
|
-
}
|
|
217
|
-
/** Squares, files or islands in chips; none as a faint dash. */
|
|
218
|
-
function Chips({ items }) {
|
|
219
|
-
return items.length === 0 ? _jsx("span", { class: "none", children: "\u2013" }) : _jsx(_Fragment, { children: items.map((item) => _jsx("b", { class: "chip", children: item }, item)) });
|
|
220
|
-
}
|
|
221
|
-
/** A cell of the strategy (Stefano): a count in bold, a yes as a tick, nothing or not yet as a faint dash, no more as a faint cross: marks, not words. */
|
|
222
|
-
function Cell({ cell, words }) {
|
|
223
|
-
const none = _jsx("span", { class: "none", children: "\u2013" });
|
|
224
|
-
if (typeof cell === "object") {
|
|
225
|
-
return cell.length === 0 ? none : _jsx("b", { class: "count", children: cell.length });
|
|
226
|
-
}
|
|
227
|
-
if (typeof cell === "number") {
|
|
228
|
-
return cell === 0 ? none : _jsx("b", { class: "count", children: cell });
|
|
229
|
-
}
|
|
230
|
-
if (typeof cell === "boolean") {
|
|
231
|
-
return cell
|
|
232
|
-
? (_jsx("span", { class: "yes", role: "img", "aria-label": words.yes, title: words.yes, children: _jsx("svg", { viewBox: "0 0 16 16", children: _jsx("path", { d: "M3.5 8.5l3 3 6-7" }) }) }))
|
|
233
|
-
: none;
|
|
234
|
-
}
|
|
235
|
-
if (cell === "unknown") {
|
|
236
|
-
return none;
|
|
237
|
-
}
|
|
238
|
-
if (cell === "not-yet") {
|
|
239
|
-
return _jsx("span", { class: "none", role: "img", "aria-label": words.notYet, title: words.notYet, children: "\u2013" });
|
|
240
|
-
}
|
|
241
|
-
return cell === "no-more"
|
|
242
|
-
? (_jsx("span", { class: "no", role: "img", "aria-label": words.noMore, title: words.noMore, children: _jsx("svg", { viewBox: "0 0 16 16", children: _jsx("path", { d: "M4.5 4.5l7 7M11.5 4.5l-7 7" }) }) }))
|
|
243
|
-
: _jsx("b", { class: "chip", children: cell });
|
|
244
|
-
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
/** What the pointer on a line or a cell does: it lights the line's squares; a touch keeps them lit until the position changes, as a square in the AI's words. */
|
|
3
|
+
export function pointing(onPoint) {
|
|
4
|
+
return (squares) => ({
|
|
5
|
+
onPointerEnter: () => onPoint(squares),
|
|
6
|
+
onPointerLeave: (event) => event.pointerType === "mouse" && onPoint([]),
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
/** The icon before a line of facts, in its kind's colour (Stefano): a pin's link, nothing at risk's tick, a check's plus, the loose pieces' i. */
|
|
10
|
+
export function RowIcon({ children }) {
|
|
11
|
+
return (_jsx("span", { class: "row-icon", "aria-hidden": "true", children: _jsx("svg", { viewBox: "0 0 16 16", children: children }) }));
|
|
12
|
+
}
|
|
13
|
+
/** An i in a ring, before the loose pieces and the strategy's notes. */
|
|
14
|
+
export function InfoIcon() {
|
|
15
|
+
return (_jsxs(RowIcon, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5" }), _jsx("path", { d: "M8 7.3V11M8 4.9v.1" })] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
|
|
2
|
+
// The helps' strategy: the game's phase, the squares each side controls, and the positional facts in lines of a tile each, the player's side first.
|
|
3
|
+
import { useState } from "preact/hooks";
|
|
4
|
+
import { InfoIcon, pointing } from "./rows.js";
|
|
5
|
+
import { groupOf, strategyOf } from "./view.js";
|
|
6
|
+
/**
|
|
7
|
+
* The strategy (Stefano): the phases with the current one in a pill, the bar of the squares each side controls, then the lines in groups, each a name and a
|
|
8
|
+
* tile of a half per side, and the notes as the tactics'. The player's side comes first, with no colour of good or bad (D-91).
|
|
9
|
+
*/
|
|
10
|
+
export function Strategy({ helps, onPoint, text }) {
|
|
11
|
+
const words = text.helpsCard;
|
|
12
|
+
const profile = helps.strategy;
|
|
13
|
+
const { draw, pawnEnding } = profile;
|
|
14
|
+
const point = pointing(onPoint);
|
|
15
|
+
// A line opens under it at a click or a tap, on its name's meaning and each side's squares or files, since a phone has no pointer for a title (Rosa).
|
|
16
|
+
const [explained, setExplained] = useState(undefined);
|
|
17
|
+
const lines = strategyOf(profile);
|
|
18
|
+
const sideName = (side) => side === helps.you ? `${words[side]} ${words.you}` : words[side];
|
|
19
|
+
const notes = [
|
|
20
|
+
profile.oppositeBishops ? words.oppositeBishops : undefined,
|
|
21
|
+
draw.repetitions > 1 ? words.repetitions(draw.repetitions) : undefined,
|
|
22
|
+
draw.fiftyMovesLeft <= fiftyMovesShown ? words.fiftyMoves(draw.fiftyMovesLeft) : undefined,
|
|
23
|
+
draw.insufficient ? words.insufficient : undefined,
|
|
24
|
+
...(pawnEnding?.squares ?? []).map((square) => words.pawnSquare(square.pawn, square.caught)),
|
|
25
|
+
pawnEnding?.opposition === undefined ? undefined : words.opposition(pawnEnding.opposition),
|
|
26
|
+
].filter((note) => note !== undefined);
|
|
27
|
+
return (_jsxs("div", { class: "strategy", children: [_jsx(Phases, { current: profile.phase, words: words }), _jsx(ControlBar, { controlled: profile.controlled, sides: helps.sides, you: helps.you, words: words }), groups.map((group) => {
|
|
28
|
+
const shown = lines.filter((line) => groupOf[line.row] === group);
|
|
29
|
+
return shown.length === 0 ? null : (_jsx("ul", { class: "strategy-rows", children: shown.map((line) => {
|
|
30
|
+
const [name, meaning] = words.rows[line.row];
|
|
31
|
+
const open = explained === line.row;
|
|
32
|
+
return (_jsxs("li", { class: open ? "open" : undefined, ...point(squaresIn(line)), children: [_jsxs("div", { class: "row-head", children: [_jsx("button", { type: "button", class: "term", title: meaning, "aria-expanded": open, onClick: () => setExplained(open ? undefined : line.row), children: name }), _jsx(Tile, { line: line, sides: helps.sides, sideName: sideName, words: words })] }), open ? _jsx(Meaning, { line: line, meaning: meaning, sides: helps.sides, sideName: sideName }) : null] }, line.row));
|
|
33
|
+
}) }, group));
|
|
34
|
+
}), notes.length === 0 ? null : (_jsx("ul", { class: "help-rows", children: notes.map((note) => (_jsxs("li", { class: "info", children: [_jsx(InfoIcon, {}), note] }, note))) }))] }));
|
|
35
|
+
}
|
|
36
|
+
// The strategy's groups in their order, apart only by a gap.
|
|
37
|
+
const groups = ["pawns", "pieces", "king"];
|
|
38
|
+
const phases = ["opening", "middlegame", "endgame"];
|
|
39
|
+
// The fifty-move count shows only when the draw is near: ten moves each, or less.
|
|
40
|
+
const fiftyMovesShown = 20;
|
|
41
|
+
/** The squares a side's cells of a strategy's line name, to light on the board; files and islands light none. */
|
|
42
|
+
function squaresIn(line) {
|
|
43
|
+
if (line.row === "openFiles") {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
return [line.white, line.black].flatMap((cell) => typeof cell === "object" ? cell.filter((item) => /^[a-h][1-8]$/.test(item)) : []);
|
|
47
|
+
}
|
|
48
|
+
/** The game's three phases in a row of arrows: the current one full, those gone by lit, those to come not. */
|
|
49
|
+
function Phases({ current, words }) {
|
|
50
|
+
return (_jsx("ol", { class: "phases", children: phases.map((phase, at) => (_jsx("li", { class: at < phases.indexOf(current) ? "done" : undefined, "aria-current": phase === current ? "step" : undefined, children: words.phases[phase] }, phase))) }));
|
|
51
|
+
}
|
|
52
|
+
/** The squares only one side controls and those both do, in a bar as long as their counts, the player's side on the left; each part's title says it in words. */
|
|
53
|
+
function ControlBar({ controlled, sides, you, words }) {
|
|
54
|
+
const { white, black, contested } = controlled;
|
|
55
|
+
const [left, right] = sides;
|
|
56
|
+
const parts = [{ who: left, count: controlled[left] }, { who: "both", count: contested }, { who: right, count: controlled[right] }];
|
|
57
|
+
return (_jsx("span", { class: "control-bar", role: "img", "aria-label": words.controlled(white, black, contested), children: parts.filter(({ count }) => count > 0).map(({ who, count }) => _jsx("span", { class: who, style: { flexGrow: count }, title: words.controlledBy(who, count, who === you), children: count }, who)) }));
|
|
58
|
+
}
|
|
59
|
+
/** A line's tile: a half for each side, the player's first, in the bar's colours; the open files, both sides', in one half as wide as the two. */
|
|
60
|
+
function Tile({ line, sides, sideName, words }) {
|
|
61
|
+
if (line.row === "openFiles") {
|
|
62
|
+
return (_jsx("span", { class: "row-tile", children: _jsxs("span", { class: "both", title: words.bothSides, children: [_jsx("span", { class: "visually-hidden", children: `${words.bothSides} ` }), _jsx("b", { class: "count", children: line.both.length })] }) }));
|
|
63
|
+
}
|
|
64
|
+
return (_jsx("span", { class: "row-tile", children: sides.map((side) => (_jsxs("span", { class: side, title: sideName(side), children: [_jsx("span", { class: "visually-hidden", children: `${sideName(side)} ` }), _jsx(Cell, { cell: line[side], words: words })] }, side))) }));
|
|
65
|
+
}
|
|
66
|
+
/** A line opened: its meaning, then each listed side's squares or files in chips; the open files with no side's name. */
|
|
67
|
+
function Meaning({ line, meaning, sides, sideName }) {
|
|
68
|
+
if (line.row === "openFiles") {
|
|
69
|
+
return (_jsxs("div", { class: "meaning", children: [meaning, _jsx("span", { class: "listed", children: _jsx(Chips, { items: line.both }) })] }));
|
|
70
|
+
}
|
|
71
|
+
return (_jsxs("div", { class: "meaning", children: [meaning, sides.map((side) => {
|
|
72
|
+
const cell = line[side];
|
|
73
|
+
return typeof cell === "object"
|
|
74
|
+
? (_jsxs("span", { class: "listed", children: [sideName(side), ": ", _jsx(Chips, { items: cell })] }, side))
|
|
75
|
+
: null;
|
|
76
|
+
})] }));
|
|
77
|
+
}
|
|
78
|
+
/** Squares, files or islands in chips; none as a faint dash. */
|
|
79
|
+
function Chips({ items }) {
|
|
80
|
+
return items.length === 0 ? _jsx("span", { class: "none", children: "\u2013" }) : _jsx(_Fragment, { children: items.map((item) => _jsx("b", { class: "chip", children: item }, item)) });
|
|
81
|
+
}
|
|
82
|
+
/** A cell of the strategy (Stefano): a count in bold, a yes as a tick, nothing or not yet as a faint dash, no more as a faint cross: marks, not words. */
|
|
83
|
+
function Cell({ cell, words }) {
|
|
84
|
+
const none = _jsx("span", { class: "none", children: "\u2013" });
|
|
85
|
+
if (typeof cell === "object") {
|
|
86
|
+
return cell.length === 0 ? none : _jsx("b", { class: "count", children: cell.length });
|
|
87
|
+
}
|
|
88
|
+
if (typeof cell === "number") {
|
|
89
|
+
return cell === 0 ? none : _jsx("b", { class: "count", children: cell });
|
|
90
|
+
}
|
|
91
|
+
if (typeof cell === "boolean") {
|
|
92
|
+
return cell
|
|
93
|
+
? (_jsx("span", { class: "yes", role: "img", "aria-label": words.yes, title: words.yes, children: _jsx("svg", { viewBox: "0 0 16 16", children: _jsx("path", { d: "M3.5 8.5l3 3 6-7" }) }) }))
|
|
94
|
+
: none;
|
|
95
|
+
}
|
|
96
|
+
if (cell === "unknown") {
|
|
97
|
+
return none;
|
|
98
|
+
}
|
|
99
|
+
if (cell === "not-yet") {
|
|
100
|
+
return _jsx("span", { class: "none", role: "img", "aria-label": words.notYet, title: words.notYet, children: "\u2013" });
|
|
101
|
+
}
|
|
102
|
+
return cell === "no-more"
|
|
103
|
+
? (_jsx("span", { class: "no", role: "img", "aria-label": words.noMore, title: words.noMore, children: _jsx("svg", { viewBox: "0 0 16 16", children: _jsx("path", { d: "M4.5 4.5l7 7M11.5 4.5l-7 7" }) }) }))
|
|
104
|
+
: _jsx("b", { class: "chip", children: cell });
|
|
105
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
|
|
2
|
+
import { Notch, Pushpin, Skewer } from "../board/marks.js";
|
|
3
|
+
import { Figurines } from "../game/move-list.js";
|
|
4
|
+
import { Figurine } from "../pieces/pieces.js";
|
|
5
|
+
import { InfoIcon, pointing, RowIcon } from "./rows.js";
|
|
6
|
+
import { pinSquares, riskSquares, squaresOf } from "./view.js";
|
|
7
|
+
/**
|
|
8
|
+
* The tactics in lines (D-91): one a kind of fact and a side, its count, red when it threatens the player and green when it is their chance; a click opens
|
|
9
|
+
* its pieces, each with its squares (Stefano).
|
|
10
|
+
*/
|
|
11
|
+
export function Tactics({ helps, opened, onOpen, onPoint, text }) {
|
|
12
|
+
const words = text.helpsCard;
|
|
13
|
+
const point = pointing(onPoint);
|
|
14
|
+
const calm = !helps.lines.some((line) => line.kind === "risk" || line.kind === "pin" || line.kind === "skewer");
|
|
15
|
+
return (_jsxs("ul", { class: "help-rows", children: [calm
|
|
16
|
+
? (_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] }))
|
|
17
|
+
: null, helps.lines.map((line) => {
|
|
18
|
+
const key = line.kind === "loose" ? "loose" : `${line.kind} ${line.side}`;
|
|
19
|
+
const open = opened.has(key);
|
|
20
|
+
return (_jsxs("li", { class: `help-line ${line.kind} ${line.tone}${open ? " open" : ""}`, children: [_jsxs("button", { type: "button", class: "line-head", "aria-expanded": open, onClick: () => onOpen(key, !open), ...point(squaresOf(line)), children: [_jsx(LineIcon, { kind: line.kind }), _jsx("span", { class: "line-name", children: line.kind === "loose" ? words.loose : words.line(line.kind, line.side, helps.you, line.kind !== "check" || line.toMove) }), _jsx(LineCount, { line: line, helps: helps, words: words })] }), open ? _jsx(LineDetails, { line: line, helps: helps, point: point, text: text }) : null] }, key));
|
|
21
|
+
})] }));
|
|
22
|
+
}
|
|
23
|
+
/** 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. */
|
|
24
|
+
function LineIcon({ kind }) {
|
|
25
|
+
switch (kind) {
|
|
26
|
+
case "risk":
|
|
27
|
+
return _jsx(Notch, {});
|
|
28
|
+
case "pin":
|
|
29
|
+
return _jsx(Pushpin, {});
|
|
30
|
+
case "skewer":
|
|
31
|
+
return _jsx(Skewer, {});
|
|
32
|
+
case "loose":
|
|
33
|
+
return _jsx(InfoIcon, {});
|
|
34
|
+
case "check":
|
|
35
|
+
return (_jsxs(RowIcon, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5" }), _jsx("path", { d: "M8 5v6M5 8h6" })] }));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** How many: the loose pieces by side, each after its pawn, the player's first (Zoe); the others in one number. */
|
|
39
|
+
function LineCount({ line, helps, words }) {
|
|
40
|
+
if (line.kind !== "loose") {
|
|
41
|
+
return _jsx("b", { class: "count", children: line.items.length });
|
|
42
|
+
}
|
|
43
|
+
const name = (side) => helps.you === undefined ? words[side] : side === helps.you ? words.yours : words.theirs;
|
|
44
|
+
const counts = helps.sides.map((side) => ({ side, count: line.items.filter((piece) => piece.side === side).length })).filter(({ count }) => count > 0);
|
|
45
|
+
return (_jsx("span", { class: "counts", children: counts.map(({ side, count }) => (_jsxs("span", { title: name(side), "aria-label": `${name(side)} ${count}`, children: [_jsx(Figurine, { letter: "P", side: side }), _jsx("b", { class: "count", children: count })] }, side))) }));
|
|
46
|
+
}
|
|
47
|
+
/** A line opened: its pieces, each lighting its own squares; the checks in figures; the loose pieces by side. */
|
|
48
|
+
function LineDetails({ line, helps, point, text }) {
|
|
49
|
+
const words = text.helpsCard;
|
|
50
|
+
switch (line.kind) {
|
|
51
|
+
case "risk":
|
|
52
|
+
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))) }));
|
|
53
|
+
case "pin":
|
|
54
|
+
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))) }));
|
|
55
|
+
case "skewer":
|
|
56
|
+
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))) }));
|
|
57
|
+
case "check":
|
|
58
|
+
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))) }));
|
|
59
|
+
case "loose":
|
|
60
|
+
return (_jsx("p", { class: "line-details", children: helps.sides.map((side) => _jsx(Pieces, { pieces: line.items.filter((piece) => piece.side === side) }, side)) }));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Pieces of one side, each its figure and its square. */
|
|
64
|
+
function Pieces({ pieces }) {
|
|
65
|
+
return pieces.length === 0 ? null : (_jsx("span", { class: "help-pieces", children: pieces.map((piece) => (_jsxs("span", { class: "help-move", children: [_jsx(Figurine, { letter: piece.piece, side: piece.side }), _jsx("b", { class: "square-name", children: piece.square })] }, piece.square))) }));
|
|
66
|
+
}
|
|
67
|
+
function Squares({ squares }) {
|
|
68
|
+
return _jsx("b", { class: "square-name", children: squares.join(", ") });
|
|
69
|
+
}
|
|
@@ -1,22 +1,65 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
2
3
|
import { PlayerPicture } from "../avatars/avatars.js";
|
|
3
4
|
import { Torretta } from "../avatars/torretta.js";
|
|
4
5
|
import { UpdateMark } from "../table/update.js";
|
|
5
6
|
/**
|
|
6
7
|
* The header, with how a game over ended, or that none goes on (D-77), or the AI's draw offer with the player's answer, the player's draw
|
|
7
|
-
* and resignation in a game going on (D-65), a new game once the page has the folder's, the command that turns the board round and says whether it is
|
|
8
|
+
* and resignation in a game going on (D-65), which the header asks to confirm in the place of the word and the commands, a new game once the page has the folder's, the command that turns the board round and says whether it is
|
|
8
9
|
* turned, on the table the mark of a newer Chesspal (D-108), the switches of the sounds and the theme, the gear of the settings (D-56) and the book of
|
|
9
10
|
* the guide.
|
|
10
11
|
*/
|
|
11
12
|
export function Header({ text, verdict, kind, who, actions, onNewGame, standOut = false, onStudy, flipped, onFlip, sound, onSound, onTheme, onSettings, onGuide, profile, onProfile, update, onUpdate }) {
|
|
12
|
-
|
|
13
|
+
// The flag pressed asks the player in the header, as the AI's draw offer does; a game that ends meanwhile drops the question.
|
|
14
|
+
const [resigning, setResigning] = useState(false);
|
|
15
|
+
const asking = resigning && actions !== undefined;
|
|
16
|
+
// After "play on" the flag takes the focus back.
|
|
17
|
+
const backToFlag = useRef(false);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (actions === undefined) {
|
|
20
|
+
setResigning(false);
|
|
21
|
+
}
|
|
22
|
+
}, [actions === undefined]);
|
|
23
|
+
const answer = (resign) => {
|
|
24
|
+
setResigning(false);
|
|
25
|
+
backToFlag.current = !resign;
|
|
26
|
+
if (resign) {
|
|
27
|
+
actions?.onResign();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const shownKind = asking ? "over" : kind;
|
|
31
|
+
const shownVerdict = asking ? text.resignGame : verdict;
|
|
32
|
+
const shownWho = asking ? undefined : who;
|
|
33
|
+
return (_jsxs("header", { class: "top", children: [_jsx(Torretta, { size: 34 }), _jsx("b", { class: "title", children: "Chesspal" }), _jsxs("p", { class: `verdict${shownKind === undefined ? "" : ` ${shownKind}`}`, role: "status", children: [shownVerdict === undefined || shownKind === undefined ? null : _jsx(Icon, { class: "kind", children: kindIcons[shownKind] }), shownWho === undefined || shownVerdict === undefined ? shownVerdict : (_jsxs(_Fragment, { children: [_jsx("span", { class: "ai-name", children: shownWho }), _jsx("span", { children: shownVerdict })] }))] }), actions === undefined ? null : asking ? _jsx(ResignQuestion, { onAnswer: answer, text: text }) : _jsx(Actions, { actions: actions, onResign: () => setResigning(true), backToFlag: backToFlag, text: text }), onNewGame === undefined ? null : (_jsxs("button", { class: standOut ? "command primary" : "command", type: "button", title: text.newGame, onClick: onNewGame, children: [_jsx(Icon, { class: "new", children: _jsx("path", { d: "M12 5V19M5 12H19" }) }), _jsx("span", { children: text.newGame })] })), onStudy === undefined ? null : (_jsx("button", { class: "icon-button", type: "button", title: text.study.title, "aria-label": text.study.title, "aria-haspopup": "dialog", onClick: onStudy, children: _jsxs(Icon, { class: "study", children: [_jsx("circle", { cx: "10.5", cy: "10.5", r: "5.5" }), _jsx("path", { d: "M14.5 14.5L20 20" })] }) })), _jsx("button", { class: "icon-button flip-button", type: "button", title: text.flip, "aria-label": text.flip, "aria-pressed": flipped, onClick: onFlip, children: _jsx(Icon, { class: "flip", children: _jsx("path", { d: "M7 4V17M7 17L4 14M7 17L10 14M17 20V7M17 7L14 10M17 7L20 10" }) }) }), update === undefined || onUpdate === undefined ? null : _jsx(UpdateMark, { version: update.version, installing: update.installing, onOpen: onUpdate, text: text }), _jsx("button", { class: "icon-button sound-button", type: "button", title: text.sounds, "aria-label": text.sounds, "aria-pressed": sound, onClick: onSound, children: _jsxs(Icon, { class: "speaker", children: [_jsx("path", { d: "M5 9.5H8.5L13 5.5V18.5L8.5 14.5H5Z" }), sound ? _jsx("path", { d: "M16.5 9A4 4 0 0 1 16.5 15M19 6.5A7.5 7.5 0 0 1 19 17.5" }) : _jsx("path", { d: "M15.5 9L21.5 15M21.5 9L15.5 15" })] }) }), _jsxs("button", { class: "icon-button theme-button", type: "button", title: text.theme, "aria-label": text.theme, onClick: onTheme, children: [_jsxs(Icon, { class: "sun", children: [_jsx("circle", { cx: "12", cy: "12", r: "4" }), _jsx("path", { d: "M12 2V4M12 20V22M2 12H4M20 12H22M4.9 4.9L6.3 6.3M17.7 17.7L19.1 19.1M4.9 19.1L6.3 17.7M17.7 6.3L19.1 4.9" })] }), _jsx(Icon, { class: "moon", children: _jsx("path", { d: "M20 14.5A8 8 0 1 1 9.5 4A6.5 6.5 0 0 0 20 14.5Z" }) })] }), _jsx("button", { class: "icon-button", type: "button", title: text.settings, "aria-label": text.settings, onClick: onSettings, children: _jsxs(Icon, { class: "gear", children: [_jsx("circle", { cx: "12", cy: "12", r: "3" }), _jsx("path", { d: "M9.72 5.38L10.19 2.67L13.81 2.67L14.28 5.38L15.07 5.71L17.31 4.12L19.88 6.69L18.29 8.93L18.62 9.72L21.33 10.19L21.33 13.81L18.62 14.28L18.29 15.07L19.88 17.31L17.31 19.88L15.07 18.29L14.28 18.62L13.81 21.33L10.19 21.33L9.72 18.62L8.93 18.29L6.69 19.88L4.12 17.31L5.71 15.07L5.38 14.28L2.67 13.81L2.67 10.19L5.38 9.72L5.71 8.93L4.12 6.69L6.69 4.12L8.93 5.71Z" })] }) }), _jsx("button", { class: "icon-button guide-button", type: "button", title: text.guide.title, "aria-label": text.guide.title, "aria-haspopup": "dialog", onClick: onGuide, children: _jsx(Icon, { class: "book", children: _jsx("path", { d: "M12 6.5C10 5 7 4.5 3.5 5V18.5C7 18 10 18.5 12 20C14 18.5 17 18 20.5 18.5V5C17 4.5 14 5 12 6.5ZM12 6.5V20" }) }) }), profile === undefined || onProfile === undefined ? null : (_jsx("button", { class: "icon-button player", type: "button", title: `${profile.name}: ${text.profile.profiles}`, "aria-label": `${profile.name}: ${text.profile.profiles}`, "aria-haspopup": "dialog", onClick: onProfile, children: _jsx(PlayerPicture, { profile: profile, size: 28 }) }))] }));
|
|
13
34
|
}
|
|
14
|
-
/** The answer to the AI's draw offer while it stands, else the player's own offer; then the
|
|
15
|
-
function Actions({ actions, text }) {
|
|
16
|
-
const { offer, canOffer, onOffer, onAnswer
|
|
35
|
+
/** The answer to the AI's draw offer while it stands, else the player's own offer; then the flag, which asks before the player resigns. */
|
|
36
|
+
function Actions({ actions, onResign, backToFlag, text }) {
|
|
37
|
+
const { offer, canOffer, onOffer, onAnswer } = actions;
|
|
38
|
+
const flag = useRef(null);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (backToFlag.current) {
|
|
41
|
+
backToFlag.current = false;
|
|
42
|
+
flag.current?.focus();
|
|
43
|
+
}
|
|
44
|
+
}, []);
|
|
17
45
|
return (_jsxs(_Fragment, { children: [offer === "ai"
|
|
18
46
|
? (_jsxs(_Fragment, { children: [_jsxs("button", { class: "command answer", type: "button", title: text.acceptDraw, onClick: () => onAnswer(true), children: [_jsx(Icon, { class: "accept", children: _jsx("path", { d: "M5 12.5L10 17L19 7" }) }), _jsx("span", { children: text.acceptDraw })] }), _jsxs("button", { class: "command answer", type: "button", title: text.declineDrawTitle, onClick: () => onAnswer(false), children: [_jsx(Icon, { class: "decline", children: _jsx("path", { d: "M7 7L17 17M17 7L7 17" }) }), _jsx("span", { children: text.declineDraw })] })] }))
|
|
19
|
-
: (_jsxs("button", { class: "command", type: "button", title: offer === "player" ? text.drawOffered : canOffer ? text.offerDraw : text.offerDrawWhen, "aria-pressed": offer === "player", "aria-disabled": !canOffer, onClick: offer === "player" ? undefined : onOffer, children: [_jsx(Icon, { class: "half", children: _jsx("path", { d: "M5 7L7.5 5V11M16 5L8 19M13.5 14.5C13.5 13 17.5 12.5 17.5 15C17.5 16.5 13.5 18 13.5 19H18" }) }), _jsx("span", { children: text.offerDraw })] })), _jsxs("button", { class: "command", type: "button", title: text.resign, onClick: onResign, children: [_jsx(Icon, { class: "resign", children: _jsx("path", { d: "M6 21V4M6 4H18L15.5 8.5L18 13H6" }) }), _jsx("span", { children: text.resign })] })] }));
|
|
47
|
+
: (_jsxs("button", { class: "command", type: "button", title: offer === "player" ? text.drawOffered : canOffer ? text.offerDraw : text.offerDrawWhen, "aria-pressed": offer === "player", "aria-disabled": !canOffer, onClick: offer === "player" ? undefined : onOffer, children: [_jsx(Icon, { class: "half", children: _jsx("path", { d: "M5 7L7.5 5V11M16 5L8 19M13.5 14.5C13.5 13 17.5 12.5 17.5 15C17.5 16.5 13.5 18 13.5 19H18" }) }), _jsx("span", { children: text.offerDraw })] })), _jsxs("button", { ref: flag, class: "command", type: "button", title: text.resign, onClick: onResign, children: [_jsx(Icon, { class: "resign", children: _jsx("path", { d: "M6 21V4M6 4H18L15.5 8.5L18 13H6" }) }), _jsx("span", { children: text.resign })] })] }));
|
|
48
|
+
}
|
|
49
|
+
/** The question the flag asks, as the answer to a draw offer: resign, or play on, which has the focus and Esc too. */
|
|
50
|
+
function ResignQuestion({ onAnswer, text }) {
|
|
51
|
+
const playOn = useRef(null);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
playOn.current?.focus();
|
|
54
|
+
const escape = (event) => {
|
|
55
|
+
if (event.key === "Escape") {
|
|
56
|
+
onAnswer(false);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
window.addEventListener("keydown", escape);
|
|
60
|
+
return () => window.removeEventListener("keydown", escape);
|
|
61
|
+
}, []);
|
|
62
|
+
return (_jsxs(_Fragment, { children: [_jsxs("button", { class: "command answer", type: "button", title: text.resign, onClick: () => onAnswer(true), children: [_jsx(Icon, { class: "accept", children: _jsx("path", { d: "M5 12.5L10 17L19 7" }) }), _jsx("span", { children: text.resign })] }), _jsxs("button", { ref: playOn, class: "command answer", type: "button", title: text.keepPlaying, onClick: () => onAnswer(false), children: [_jsx(Icon, { class: "decline", children: _jsx("path", { d: "M7 7L17 17M17 7L7 17" }) }), _jsx("span", { children: text.keepPlaying })] })] }));
|
|
20
63
|
}
|
|
21
64
|
// A pair of scales, a flag, two hands, a pause.
|
|
22
65
|
const kindIcons = {
|
|
Binary file
|