chesspal 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -2
- package/dist/cli/chat-hook.d.ts +2 -0
- package/dist/cli/chat-hook.js +33 -0
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.js +28 -0
- package/dist/cli/command-line.d.ts +7 -0
- package/dist/cli/command-line.js +177 -0
- package/dist/commands/agents.d.ts +26 -0
- package/dist/commands/agents.js +72 -0
- package/dist/commands/ai-move.d.ts +28 -0
- package/dist/commands/ai-move.js +85 -0
- package/dist/commands/ai-say.d.ts +22 -0
- package/dist/commands/ai-say.js +58 -0
- package/dist/commands/analyse-game.d.ts +65 -0
- package/dist/commands/analyse-game.js +265 -0
- package/dist/commands/annotate-game.d.ts +39 -0
- package/dist/commands/annotate-game.js +131 -0
- package/dist/commands/answer-lines.d.ts +28 -0
- package/dist/commands/answer-lines.js +144 -0
- package/dist/commands/clock.d.ts +15 -0
- package/dist/commands/clock.js +45 -0
- package/dist/commands/coach.d.ts +45 -0
- package/dist/commands/coach.js +344 -0
- package/dist/commands/context.d.ts +67 -0
- package/dist/commands/context.js +37 -0
- package/dist/commands/delete-game.d.ts +7 -0
- package/dist/commands/delete-game.js +30 -0
- package/dist/commands/delete-review.d.ts +8 -0
- package/dist/commands/delete-review.js +34 -0
- package/dist/commands/draw-offer.d.ts +10 -0
- package/dist/commands/draw-offer.js +43 -0
- package/dist/commands/drawings.d.ts +13 -0
- package/dist/commands/drawings.js +25 -0
- package/dist/commands/end-game.d.ts +23 -0
- package/dist/commands/end-game.js +67 -0
- package/dist/commands/export-game.d.ts +14 -0
- package/dist/commands/export-game.js +27 -0
- package/dist/commands/game-summary.d.ts +78 -0
- package/dist/commands/game-summary.js +102 -0
- package/dist/commands/index.d.ts +18 -0
- package/dist/commands/index.js +64 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +73 -0
- package/dist/commands/lead-input.d.ts +8 -0
- package/dist/commands/lead-input.js +21 -0
- package/dist/commands/levels.d.ts +9 -0
- package/dist/commands/levels.js +25 -0
- package/dist/commands/look.d.ts +31 -0
- package/dist/commands/look.js +64 -0
- package/dist/commands/new-game.d.ts +68 -0
- package/dist/commands/new-game.js +218 -0
- package/dist/commands/opening-book.d.ts +13 -0
- package/dist/commands/opening-book.js +84 -0
- package/dist/commands/page-link.d.ts +11 -0
- package/dist/commands/page-link.js +28 -0
- package/dist/commands/past-games.d.ts +11 -0
- package/dist/commands/past-games.js +47 -0
- package/dist/commands/play-move.d.ts +37 -0
- package/dist/commands/play-move.js +94 -0
- package/dist/commands/player-move.d.ts +13 -0
- package/dist/commands/player-move.js +51 -0
- package/dist/commands/player-say.d.ts +5 -0
- package/dist/commands/player-say.js +16 -0
- package/dist/commands/ponder.d.ts +52 -0
- package/dist/commands/ponder.js +179 -0
- package/dist/commands/position.d.ts +14 -0
- package/dist/commands/position.js +130 -0
- package/dist/commands/profiles.d.ts +69 -0
- package/dist/commands/profiles.js +163 -0
- package/dist/commands/registry.d.ts +59 -0
- package/dist/commands/registry.js +62 -0
- package/dist/commands/say.d.ts +16 -0
- package/dist/commands/say.js +84 -0
- package/dist/commands/speech.d.ts +8 -0
- package/dist/commands/speech.js +20 -0
- package/dist/commands/status.d.ts +35 -0
- package/dist/commands/status.js +128 -0
- package/dist/commands/study-games.d.ts +5 -0
- package/dist/commands/study-games.js +40 -0
- package/dist/commands/study.d.ts +38 -0
- package/dist/commands/study.js +106 -0
- package/dist/commands/view.d.ts +14 -0
- package/dist/commands/view.js +125 -0
- package/dist/core/board-text.d.ts +7 -0
- package/dist/core/board-text.js +18 -0
- package/dist/core/book.json +1 -0
- package/dist/core/clock.d.ts +45 -0
- package/dist/core/clock.js +109 -0
- package/dist/core/facts.d.ts +55 -0
- package/dist/core/facts.js +175 -0
- package/dist/core/game.d.ts +95 -0
- package/dist/core/game.js +352 -0
- package/dist/core/illegal.d.ts +19 -0
- package/dist/core/illegal.js +141 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/index.js +12 -0
- package/dist/core/lead.d.ts +15 -0
- package/dist/core/lead.js +40 -0
- package/dist/core/levels.d.ts +20 -0
- package/dist/core/levels.js +33 -0
- package/dist/core/material.d.ts +9 -0
- package/dist/core/material.js +12 -0
- package/dist/core/moods.d.ts +21 -0
- package/dist/core/moods.js +45 -0
- package/dist/core/openings.d.ts +35 -0
- package/dist/core/openings.js +98 -0
- package/dist/core/openings.json +1 -0
- package/dist/core/pgn.d.ts +10 -0
- package/dist/core/pgn.js +213 -0
- package/dist/core/profile.d.ts +117 -0
- package/dist/core/profile.js +232 -0
- package/dist/core/record.d.ts +21 -0
- package/dist/core/record.js +25 -0
- package/dist/core/review.d.ts +80 -0
- package/dist/core/review.js +146 -0
- package/dist/games/ai-model.d.ts +5 -0
- package/dist/games/ai-model.js +15 -0
- package/dist/games/analysis.d.ts +12 -0
- package/dist/games/analysis.js +26 -0
- package/dist/games/archive.d.ts +63 -0
- package/dist/games/archive.js +242 -0
- package/dist/games/chat-start.d.ts +16 -0
- package/dist/games/chat-start.js +26 -0
- package/dist/games/coach.d.ts +57 -0
- package/dist/games/coach.js +144 -0
- package/dist/games/current-game.d.ts +56 -0
- package/dist/games/current-game.js +134 -0
- package/dist/games/files.d.ts +9 -0
- package/dist/games/files.js +53 -0
- package/dist/games/index.d.ts +11 -0
- package/dist/games/index.js +12 -0
- package/dist/games/plans.d.ts +9 -0
- package/dist/games/plans.js +23 -0
- package/dist/games/table-talk.d.ts +12 -0
- package/dist/games/table-talk.js +25 -0
- package/dist/games/talk.d.ts +36 -0
- package/dist/games/talk.js +131 -0
- package/dist/games/thoughts.d.ts +15 -0
- package/dist/games/thoughts.js +60 -0
- package/dist/home/data-folder.d.ts +12 -0
- package/dist/home/data-folder.js +55 -0
- package/dist/home/index.d.ts +2 -0
- package/dist/home/index.js +3 -0
- package/dist/home/open-folder.d.ts +2 -0
- package/dist/home/open-folder.js +14 -0
- package/dist/host-files/agents.d.ts +20 -0
- package/dist/host-files/agents.js +53 -0
- package/dist/host-files/host-files.d.ts +15 -0
- package/dist/host-files/host-files.js +182 -0
- package/dist/host-files/index.d.ts +2 -0
- package/dist/host-files/index.js +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/page/app/anchor.js +88 -0
- package/dist/page/app/app.js +458 -0
- package/dist/page/app/keys.js +94 -0
- package/dist/page/app/phase.js +30 -0
- package/dist/page/app/served.js +73 -0
- package/dist/page/avatars/avatars.js +19 -0
- package/dist/page/avatars/beanie.webp +0 -0
- package/dist/page/avatars/beard.webp +0 -0
- package/dist/page/avatars/bun.webp +0 -0
- package/dist/page/avatars/cat.webp +0 -0
- package/dist/page/avatars/curls.webp +0 -0
- package/dist/page/avatars/glasses.webp +0 -0
- package/dist/page/avatars/moustache.webp +0 -0
- package/dist/page/avatars/owl.webp +0 -0
- package/dist/page/avatars/short-hair.webp +0 -0
- package/dist/page/avatars/silver-bob.webp +0 -0
- package/dist/page/avatars/thought.js +10 -0
- package/dist/page/avatars/torretta.js +38 -0
- package/dist/page/base.css +160 -0
- package/dist/page/board/board.css +959 -0
- package/dist/page/board/board.js +125 -0
- package/dist/page/board/explain.js +58 -0
- package/dist/page/board/marks.js +160 -0
- package/dist/page/board/motion.js +71 -0
- package/dist/page/board/pointer.js +239 -0
- package/dist/page/board/squares.js +26 -0
- package/dist/page/dialogs/analyses.js +195 -0
- package/dist/page/dialogs/dialog.js +26 -0
- package/dist/page/dialogs/dialogs.css +1228 -0
- package/dist/page/dialogs/new-game.js +42 -0
- package/dist/page/dialogs/profiles.js +152 -0
- package/dist/page/dialogs/search.js +31 -0
- package/dist/page/dialogs/settings.js +42 -0
- package/dist/page/dialogs/studied.js +39 -0
- package/dist/page/dialogs/study.js +51 -0
- package/dist/page/game/badges.js +50 -0
- package/dist/page/game/browse.js +17 -0
- package/dist/page/game/card-fold.js +40 -0
- package/dist/page/game/column.js +51 -0
- package/dist/page/game/ending.js +73 -0
- package/dist/page/game/game.css +577 -0
- package/dist/page/game/move-list.js +72 -0
- package/dist/page/game/opening-book.js +14 -0
- package/dist/page/game/plies.js +82 -0
- package/dist/page/game/result-tile.js +14 -0
- package/dist/page/helps/helps.css +689 -0
- package/dist/page/helps/helps.js +220 -0
- package/dist/page/helps/view.js +127 -0
- package/dist/page/layout/fold-tab.js +11 -0
- package/dist/page/layout/folds.js +43 -0
- package/dist/page/layout/header.js +28 -0
- package/dist/page/layout/layout.css +834 -0
- package/dist/page/lead/lead.js +82 -0
- package/dist/page/lead/view.js +38 -0
- package/dist/page/main.js +8 -0
- package/dist/page/pieces/CREDITS.md +19 -0
- package/dist/page/pieces/capablanca-small.svg +20 -0
- package/dist/page/pieces/capablanca.svg +22 -0
- package/dist/page/pieces/nitida-small.svg +20 -0
- package/dist/page/pieces/nitida.svg +21 -0
- package/dist/page/pieces/pieces.js +28 -0
- package/dist/page/pieces/torretta-small.svg +20 -0
- package/dist/page/pieces/torretta.svg +21 -0
- package/dist/page/players/card.js +56 -0
- package/dist/page/players/clock-time.js +12 -0
- package/dist/page/players/model-name.js +20 -0
- package/dist/page/profile/profile.js +99 -0
- package/dist/page/profile/settings.js +77 -0
- package/dist/page/sounds/capture.ogg +0 -0
- package/dist/page/sounds/check.ogg +0 -0
- package/dist/page/sounds/draw.ogg +0 -0
- package/dist/page/sounds/flag.ogg +0 -0
- package/dist/page/sounds/mate.ogg +0 -0
- package/dist/page/sounds/move.ogg +0 -0
- package/dist/page/sounds/offer.ogg +0 -0
- package/dist/page/sounds/resign.ogg +0 -0
- package/dist/page/sounds/sounds.js +105 -0
- package/dist/page/style.css +9 -0
- package/dist/page/talk/talk.css +958 -0
- package/dist/page/talk/talk.js +165 -0
- package/dist/page/talk/voice.js +38 -0
- package/dist/page/text.js +711 -0
- package/dist/page/theme.css +292 -0
- package/dist/profiles/avatar.d.ts +23 -0
- package/dist/profiles/avatar.js +84 -0
- package/dist/profiles/front-matter.d.ts +9 -0
- package/dist/profiles/front-matter.js +33 -0
- package/dist/profiles/index.d.ts +3 -0
- package/dist/profiles/index.js +5 -0
- package/dist/profiles/profiles.d.ts +69 -0
- package/dist/profiles/profiles.js +254 -0
- package/dist/profiles/settings.d.ts +12 -0
- package/dist/profiles/settings.js +46 -0
- package/dist/server/bell.d.ts +68 -0
- package/dist/server/bell.js +225 -0
- package/dist/server/caller.d.ts +21 -0
- package/dist/server/caller.js +132 -0
- package/dist/server/mcp.d.ts +13 -0
- package/dist/server/mcp.js +207 -0
- package/dist/server/page/analyses.d.ts +23 -0
- package/dist/server/page/analyses.js +65 -0
- package/dist/server/page/files.d.ts +5 -0
- package/dist/server/page/files.js +92 -0
- package/dist/server/page/game.d.ts +6 -0
- package/dist/server/page/game.js +381 -0
- package/dist/server/page/http.d.ts +29 -0
- package/dist/server/page/http.js +68 -0
- package/dist/server/page/page.d.ts +41 -0
- package/dist/server/page/page.js +229 -0
- package/dist/server/page/profiles.d.ts +19 -0
- package/dist/server/page/profiles.js +215 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/instructions/analysis.md +4 -0
- package/instructions/analyst-agent.md +8 -0
- package/instructions/analyst.md +4 -0
- package/instructions/coach.md +11 -0
- package/instructions/opponent.md +15 -0
- package/instructions/ponder.md +9 -0
- package/package.json +66 -5
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
|
|
2
|
+
// The helps on the page (D-78, Zoe's sketch B): the board says where, the card says what, the facts the AI sees at the game's level and only those; and
|
|
3
|
+
// the coach's words (D-88).
|
|
4
|
+
import { Fragment } from "preact";
|
|
5
|
+
import { useState } from "preact/hooks";
|
|
6
|
+
import { Notch } from "../board/marks.js";
|
|
7
|
+
import { BadgeIcon, NoteIcon } from "../game/badges.js";
|
|
8
|
+
import { FoldButton, useCardFold } from "../game/card-fold.js";
|
|
9
|
+
import { Figurines } from "../game/move-list.js";
|
|
10
|
+
import { Figurine } from "../pieces/pieces.js";
|
|
11
|
+
import { Legend, Marked } from "../talk/talk.js";
|
|
12
|
+
import { groupOf, pinSquares, riskSquares, squaresOf, strategyOf } from "./view.js";
|
|
13
|
+
/**
|
|
14
|
+
* The helps' card, between the opening and the moves: the coach's words, then the tactics in lines, the strategy in a table and the control map, seen while
|
|
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
|
+
* folds it to the title and opens it again (Stefano).
|
|
17
|
+
*/
|
|
18
|
+
export function HelpsCard({ helps, judged, hint, hintOpen, onHint, onPoint, onMap, mapping, onBare, bare, text }) {
|
|
19
|
+
const [tab, setTab] = useState("tactics");
|
|
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
|
+
const [opened, setOpened] = useState(new Set());
|
|
22
|
+
const openLine = (key, open) => setOpened((lines) => new Set(open ? [...lines, key] : [...lines].filter((line) => line !== key)));
|
|
23
|
+
const [folded, toggleFold] = useCardFold("helps");
|
|
24
|
+
const words = text.helpsCard;
|
|
25
|
+
// The hint closes with the card, so that its drawings never stay on the board without its words (Zoe).
|
|
26
|
+
const fold = () => {
|
|
27
|
+
if (!folded) {
|
|
28
|
+
onHint(false);
|
|
29
|
+
}
|
|
30
|
+
toggleFold();
|
|
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"
|
|
33
|
+
? _jsx(Tactics, { helps: helps, opened: opened, onOpen: openLine, onPoint: onPoint, text: text })
|
|
34
|
+
: _jsx(Strategy, { helps: helps, onPoint: onPoint, text: text })] }))] }));
|
|
35
|
+
}
|
|
36
|
+
/** The map's button (D-78), held to see the map: an icon, its tooltip saying how (Stefano); in the card's title, and on a phone under the board. */
|
|
37
|
+
export function MapButton({ mapping, onMap, text }) {
|
|
38
|
+
return (_jsx(HoldButton, { on: mapping, onHold: onMap, title: text.helpsCard.mapHint, label: text.helpsCard.map, children: _jsxs("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: [_jsx("path", { d: "M2 2h5v5H2zM9 9h5v5H9z" }), _jsx("path", { d: "M9 2h5v5H9zM2 9h5v5H2z", opacity: ".35" })] }) }));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The button held to see the board bare, without the arrows and the squares the AI, the coach and the player drew on it, nor the helps' signs (Stefano): an eye shut.
|
|
42
|
+
*/
|
|
43
|
+
export function BareButton({ bare, onBare, text }) {
|
|
44
|
+
return (_jsx(HoldButton, { on: bare, onHold: onBare, title: text.helpsCard.bareHint, label: text.helpsCard.bare, kind: "bare-button", children: _jsxs("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: [_jsx("path", { d: "M1.5 8S4 3.5 8 3.5 14.5 8 14.5 8 12 12.5 8 12.5 1.5 8 1.5 8Z" }), _jsx("circle", { cx: "8", cy: "8", r: "2" }), _jsx("path", { d: "M2.5 2.5l11 11" })] }) }));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The coach's hint, a line that opens on its words and the legend of what it draws (D-88): in the helps' card, and on a phone under the board, beside the
|
|
48
|
+
* map, where its drawings are in sight (Zoe).
|
|
49
|
+
*/
|
|
50
|
+
export function HintLine({ hint, open, onOpen, text }) {
|
|
51
|
+
const words = text.helpsCard;
|
|
52
|
+
return (_jsxs("details", { class: "coach-hint", open: open, onToggle: (event) => onOpen(event.currentTarget.open), children: [_jsx("summary", { children: open ? words.hideHint : words.showHint }), _jsx("span", { class: "note", children: _jsx(Marked, { text: hint.text }) }), hint.arrows === undefined && hint.squares === undefined ? null : _jsx(Legend, { review: hint, words: words.drawn })] }));
|
|
53
|
+
}
|
|
54
|
+
/** An icon the mouse holds, while what it shows lasts; a finger, or the keyboard, turns it on and off (D-78). */
|
|
55
|
+
function HoldButton({ on, onHold, title, label, kind, children }) {
|
|
56
|
+
// A click carries its pointer's type: none from the keyboard.
|
|
57
|
+
const mouse = (event) => event instanceof PointerEvent && event.pointerType === "mouse";
|
|
58
|
+
return (_jsx("button", { type: "button", class: kind === undefined ? "hold-button" : `hold-button ${kind}`, "aria-pressed": on, "aria-label": label, title: title, onPointerDown: (event) => mouse(event) && onHold(true), onPointerUp: (event) => mouse(event) && onHold(false), onPointerLeave: (event) => mouse(event) && onHold(false), onClick: (event) => !mouse(event) && onHold(!on), children: children }));
|
|
59
|
+
}
|
|
60
|
+
/** The coach's judgment of the player's move: its badge and the move in a line, which opens on its note (D-78, D-88). */
|
|
61
|
+
function Judged({ ply, text }) {
|
|
62
|
+
const { badge, note } = ply.review ?? {};
|
|
63
|
+
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]] }));
|
|
64
|
+
return (_jsx("div", { class: `move-note judged ${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 }) })] })) }));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 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
|
|
68
|
+
* its pieces, each with its squares (Stefano).
|
|
69
|
+
*/
|
|
70
|
+
function Tactics({ helps, opened, onOpen, onPoint, text }) {
|
|
71
|
+
const words = text.helpsCard;
|
|
72
|
+
const point = pointing(onPoint);
|
|
73
|
+
const calm = !helps.lines.some((line) => line.kind === "risk" || line.kind === "pin");
|
|
74
|
+
return (_jsxs("ul", { class: "help-rows", children: [calm
|
|
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
|
+
: null, helps.lines.map((line) => {
|
|
77
|
+
const key = line.kind === "loose" ? "loose" : `${line.kind} ${line.side}`;
|
|
78
|
+
const open = opened.has(key);
|
|
79
|
+
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));
|
|
80
|
+
})] }));
|
|
81
|
+
}
|
|
82
|
+
/** 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. */
|
|
83
|
+
function pointing(onPoint) {
|
|
84
|
+
return (squares) => ({
|
|
85
|
+
onPointerEnter: () => onPoint(squares),
|
|
86
|
+
onPointerLeave: (event) => event.pointerType === "mouse" && onPoint([]),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/** A line's sign: the corner of a piece at risk, a pin's link, a check's plus, the loose pieces' i; its colour is the line's. */
|
|
90
|
+
function LineIcon({ kind }) {
|
|
91
|
+
if (kind === "risk") {
|
|
92
|
+
return _jsx(Notch, {});
|
|
93
|
+
}
|
|
94
|
+
if (kind === "loose") {
|
|
95
|
+
return _jsx(InfoIcon, {});
|
|
96
|
+
}
|
|
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
|
+
}
|
|
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 "check":
|
|
119
|
+
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
|
+
case "loose":
|
|
121
|
+
return (_jsx("p", { class: "line-details", children: helps.sides.map((side) => _jsx(Pieces, { pieces: line.items.filter((piece) => piece.side === side) }, side)) }));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** The helps' sign before the card's title: a lifebuoy (Stefano). */
|
|
125
|
+
function HelpsIcon() {
|
|
126
|
+
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" })] }));
|
|
127
|
+
}
|
|
128
|
+
/** 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. */
|
|
129
|
+
function RowIcon({ children }) {
|
|
130
|
+
return (_jsx("span", { class: "row-icon", "aria-hidden": "true", children: _jsx("svg", { viewBox: "0 0 16 16", children: children }) }));
|
|
131
|
+
}
|
|
132
|
+
/** An i in a ring, before the loose pieces and the strategy's notes. */
|
|
133
|
+
function InfoIcon() {
|
|
134
|
+
return (_jsxs(RowIcon, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5" }), _jsx("path", { d: "M8 7.3V11M8 4.9v.1" })] }));
|
|
135
|
+
}
|
|
136
|
+
/** Pieces of one side, each its figure and its square. */
|
|
137
|
+
function Pieces({ pieces }) {
|
|
138
|
+
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))) }));
|
|
139
|
+
}
|
|
140
|
+
function Squares({ squares }) {
|
|
141
|
+
return _jsx("b", { class: "square-name", children: squares.join(", ") });
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The strategy (Stefano): the phase and the open files on top, the squares each side controls in a bar, the table in groups each under its figure, a count
|
|
145
|
+
* or a tick in each cell, and the notes in lines as the tactics'. The player's side comes first, with no colour of good or bad (D-91).
|
|
146
|
+
*/
|
|
147
|
+
function Strategy({ helps, onPoint, text }) {
|
|
148
|
+
const words = text.helpsCard;
|
|
149
|
+
const profile = helps.strategy;
|
|
150
|
+
const { draw, pawnEnding } = profile;
|
|
151
|
+
const point = pointing(onPoint);
|
|
152
|
+
// 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).
|
|
153
|
+
const [explained, setExplained] = useState(undefined);
|
|
154
|
+
const lines = strategyOf(profile);
|
|
155
|
+
const sideName = (side) => side === helps.you ? `${words[side]} ${words.you}` : words[side];
|
|
156
|
+
const notes = [
|
|
157
|
+
profile.oppositeBishops ? words.oppositeBishops : undefined,
|
|
158
|
+
draw.repetitions > 1 ? words.repetitions(draw.repetitions) : undefined,
|
|
159
|
+
draw.fiftyMovesLeft <= fiftyMovesShown ? words.fiftyMoves(draw.fiftyMovesLeft) : undefined,
|
|
160
|
+
draw.insufficient ? words.insufficient : undefined,
|
|
161
|
+
...(pawnEnding?.squares ?? []).map((square) => words.pawnSquare(square.pawn, square.caught)),
|
|
162
|
+
pawnEnding?.opposition === undefined ? undefined : words.opposition(pawnEnding.opposition),
|
|
163
|
+
].filter((note) => note !== undefined);
|
|
164
|
+
return (_jsxs("div", { class: "strategy", children: [_jsxs("p", { class: "strategy-head", children: [_jsx("span", { class: "phase", children: words.phases[profile.phase] }), _jsxs("span", { children: [words.openFiles, ": ", _jsx(Chips, { items: profile.openFiles })] })] }), _jsx(ControlBar, { controlled: profile.controlled, sides: helps.sides, sideName: sideName, words: words }), _jsxs("table", { class: "strategy-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", {}), helps.sides.map((side) => (_jsxs("th", { children: [_jsx(Figurine, { letter: "P", side: side }), sideName(side)] }, side)))] }) }), groups.map(({ group, letter }) => {
|
|
165
|
+
const shown = lines.filter((line) => groupOf[line.row] === group);
|
|
166
|
+
return shown.length === 0 ? null : (_jsxs("tbody", { children: [_jsx("tr", { class: "group", children: _jsxs("th", { colSpan: 3, children: [_jsx(Figurine, { letter: letter, side: "white" }), words.groups[group]] }) }), shown.map((line) => {
|
|
167
|
+
const [name, meaning] = words.rows[line.row];
|
|
168
|
+
const open = explained === line.row;
|
|
169
|
+
const listed = helps.sides.filter((side) => typeof line[side] === "object");
|
|
170
|
+
return (_jsxs(Fragment, { children: [_jsxs("tr", { class: open ? "open" : undefined, ...point(squaresIn(line)), children: [_jsx("th", { children: _jsx("button", { type: "button", class: "term", title: meaning, "aria-expanded": open, onClick: () => setExplained(open ? undefined : line.row), children: name }) }), helps.sides.map((side) => (_jsx("td", { children: _jsx(Cell, { cell: line[side], words: words }) }, side)))] }), open
|
|
171
|
+
? (_jsx("tr", { class: "meaning", children: _jsxs("td", { colSpan: 3, children: [meaning, listed.map((side) => (_jsxs("span", { class: "listed", children: [sideName(side), ": ", _jsx(Chips, { items: line[side] })] }, side)))] }) }))
|
|
172
|
+
: null] }, line.row));
|
|
173
|
+
})] }, group));
|
|
174
|
+
})] }), notes.length === 0 ? null : (_jsx("ul", { class: "help-rows", children: notes.map((note) => (_jsxs("li", { class: "info", children: [_jsx(InfoIcon, {}), note] }, note))) }))] }));
|
|
175
|
+
}
|
|
176
|
+
// The groups of the strategy's table in their order, each with its figure.
|
|
177
|
+
const groups = [{ group: "pawns", letter: "P" }, { group: "pieces", letter: "N" }, { group: "king", letter: "K" }];
|
|
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
|
+
return [line.white, line.black].flatMap((cell) => typeof cell === "object" ? cell.filter((item) => /^[a-h][1-8]$/.test(item)) : []);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 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; its title says it in words.
|
|
186
|
+
*/
|
|
187
|
+
function ControlBar({ controlled, sides, sideName, words }) {
|
|
188
|
+
const { white, black, contested } = controlled;
|
|
189
|
+
const [left, right] = sides;
|
|
190
|
+
const parts = [{ side: left, count: controlled[left] }, { side: "both", count: contested }, { side: right, count: controlled[right] }].filter(({ count }) => count > 0);
|
|
191
|
+
return (_jsxs("div", { class: "control", title: words.controlled(white, black, contested), children: [_jsx("span", { class: "control-title", children: words.control }), _jsx("span", { class: "control-bar", role: "img", "aria-label": words.controlled(white, black, contested), children: parts.map(({ side, count }) => _jsx("span", { class: side, style: { flexGrow: count }, children: count }, side)) }), _jsxs("span", { class: "control-legend", "aria-hidden": "true", children: [_jsx("span", { children: sideName(left) }), _jsx("span", { children: words.both }), _jsx("span", { children: sideName(right) })] })] }));
|
|
192
|
+
}
|
|
193
|
+
/** Squares, files or islands in chips; none as a faint dash. */
|
|
194
|
+
function Chips({ items }) {
|
|
195
|
+
return items.length === 0 ? _jsx("span", { class: "none", children: "\u2013" }) : _jsx(_Fragment, { children: items.map((item) => _jsx("b", { class: "chip", children: item }, item)) });
|
|
196
|
+
}
|
|
197
|
+
/** 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. */
|
|
198
|
+
function Cell({ cell, words }) {
|
|
199
|
+
const none = _jsx("span", { class: "none", children: "\u2013" });
|
|
200
|
+
if (typeof cell === "object") {
|
|
201
|
+
return cell.length === 0 ? none : _jsx("b", { class: "count", children: cell.length });
|
|
202
|
+
}
|
|
203
|
+
if (typeof cell === "number") {
|
|
204
|
+
return cell === 0 ? none : _jsx("b", { class: "count", children: cell });
|
|
205
|
+
}
|
|
206
|
+
if (typeof cell === "boolean") {
|
|
207
|
+
return cell
|
|
208
|
+
? (_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" }) }) }))
|
|
209
|
+
: none;
|
|
210
|
+
}
|
|
211
|
+
if (cell === "unknown") {
|
|
212
|
+
return none;
|
|
213
|
+
}
|
|
214
|
+
if (cell === "not-yet") {
|
|
215
|
+
return _jsx("span", { class: "none", role: "img", "aria-label": words.notYet, title: words.notYet, children: "\u2013" });
|
|
216
|
+
}
|
|
217
|
+
return cell === "no-more"
|
|
218
|
+
? (_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" }) }) }))
|
|
219
|
+
: _jsx("b", { class: "chip", children: cell });
|
|
220
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/** The helps of a game's position, whatever the AI's level (D-79), seen from the player's side when the page knows it (D-91). */
|
|
2
|
+
export function helpsOf(game, you) {
|
|
3
|
+
const facts = game.facts;
|
|
4
|
+
const pieces = new Map(facts.pieces.map((piece) => [piece.square, piece]));
|
|
5
|
+
const on = (square) => pieces.get(square);
|
|
6
|
+
const risks = (squares, why) => squares.map((square) => ({ piece: on(square), why }));
|
|
7
|
+
const toMove = game.position.turn;
|
|
8
|
+
const atRisk = [...risks(facts.undefended, "undefended"), ...risks(facts.attackedByCheaper, "cheaper"), ...risks(facts.outnumbered, "outnumbered")];
|
|
9
|
+
const pins = facts.pins.map((pin) => ({ piece: on(pin.square), by: on(pin.by), to: on(pin.to) }));
|
|
10
|
+
const checks = facts.checks;
|
|
11
|
+
const otherChecks = facts.otherChecks ?? [];
|
|
12
|
+
const loose = facts.loose.map(on);
|
|
13
|
+
return {
|
|
14
|
+
toMove,
|
|
15
|
+
you,
|
|
16
|
+
atRisk,
|
|
17
|
+
pins,
|
|
18
|
+
checks,
|
|
19
|
+
otherChecks,
|
|
20
|
+
loose,
|
|
21
|
+
lines: tacticLines({ toMove, atRisk, pins, checks, otherChecks, loose }, you),
|
|
22
|
+
strategy: game.profile,
|
|
23
|
+
sides: you === "black" ? ["black", "white"] : ["white", "black"],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** How the helps mark a square's piece at risk (D-91): red when it is the player's, green when it is the other side's, in ink when no side is theirs. */
|
|
27
|
+
export function riskOn(helps, square) {
|
|
28
|
+
const risk = helps.atRisk.find((risk) => risk.piece.square === square);
|
|
29
|
+
return risk === undefined ? undefined : toneOf(risk.piece.side, helps.you);
|
|
30
|
+
}
|
|
31
|
+
/** The squares a line of the tactics lights on the board: its pieces, and those that attack or pin them; none for the checks. */
|
|
32
|
+
export function squaresOf(line) {
|
|
33
|
+
switch (line.kind) {
|
|
34
|
+
case "risk":
|
|
35
|
+
return line.items.flatMap(riskSquares);
|
|
36
|
+
case "pin":
|
|
37
|
+
return line.items.flatMap(pinSquares);
|
|
38
|
+
case "check":
|
|
39
|
+
return [];
|
|
40
|
+
case "loose":
|
|
41
|
+
return line.items.map((piece) => piece.square);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** A piece at risk and its attackers. */
|
|
45
|
+
export function riskSquares(risk) {
|
|
46
|
+
return [risk.piece.square, ...risk.piece.attackers];
|
|
47
|
+
}
|
|
48
|
+
/** A pinned piece, the one that pins it and the one behind it. */
|
|
49
|
+
export function pinSquares(pin) {
|
|
50
|
+
return [pin.piece.square, pin.by.square, pin.to.square];
|
|
51
|
+
}
|
|
52
|
+
/** Which group each line of the strategy's table stands in. */
|
|
53
|
+
export const groupOf = {
|
|
54
|
+
isolated: "pawns",
|
|
55
|
+
doubled: "pawns",
|
|
56
|
+
backward: "pawns",
|
|
57
|
+
passed: "pawns",
|
|
58
|
+
islands: "pawns",
|
|
59
|
+
halfOpen: "pieces",
|
|
60
|
+
heavyOnFiles: "pieces",
|
|
61
|
+
onSeventh: "pieces",
|
|
62
|
+
outposts: "pieces",
|
|
63
|
+
bishopPair: "pieces",
|
|
64
|
+
space: "pieces",
|
|
65
|
+
undeveloped: "pieces",
|
|
66
|
+
castled: "king",
|
|
67
|
+
kingAttacks: "king",
|
|
68
|
+
};
|
|
69
|
+
/** The strategy's table: only the lines where a side has something; the pawn islands only when a side has more than one. */
|
|
70
|
+
export function strategyOf(profile) {
|
|
71
|
+
const lines = Object.entries(cells).map(([row, cell]) => ({ row: row, white: cell(profile.white), black: cell(profile.black) }));
|
|
72
|
+
const split = profile.white.pawns.islands.length > 1 || profile.black.pawns.islands.length > 1;
|
|
73
|
+
return lines.filter((line) => line.row === "islands" ? split : something(line.white) || something(line.black));
|
|
74
|
+
}
|
|
75
|
+
const cells = {
|
|
76
|
+
isolated: (side) => side.pawns.isolated,
|
|
77
|
+
doubled: (side) => side.pawns.doubled,
|
|
78
|
+
backward: (side) => side.pawns.backward,
|
|
79
|
+
passed: (side) => side.pawns.passed,
|
|
80
|
+
islands: (side) => side.pawns.islands,
|
|
81
|
+
halfOpen: (side) => side.halfOpenFiles,
|
|
82
|
+
heavyOnFiles: (side) => side.heavyOnFiles,
|
|
83
|
+
onSeventh: (side) => side.onSeventh,
|
|
84
|
+
outposts: (side) => side.outposts.map((outpost) => outpost.square),
|
|
85
|
+
bishopPair: (side) => side.bishopPair,
|
|
86
|
+
space: (side) => side.space,
|
|
87
|
+
undeveloped: (side) => side.undeveloped,
|
|
88
|
+
castled: castlingOf,
|
|
89
|
+
kingAttacks: (side) => side.king?.attacksNear ?? 0,
|
|
90
|
+
};
|
|
91
|
+
// The tactics' lines that have something: with the player's side, their pieces at risk and pinned and the other side's checks, then the same the other way
|
|
92
|
+
// round; without it, White's then Black's; the loose pieces last.
|
|
93
|
+
function tacticLines(facts, you) {
|
|
94
|
+
const sideLines = (side) => [
|
|
95
|
+
{ kind: "risk", side, tone: toneOf(side, you), items: facts.atRisk.filter((risk) => risk.piece.side === side) },
|
|
96
|
+
{ kind: "pin", side, tone: toneOf(side, you), items: facts.pins.filter((pin) => pin.piece.side === side) },
|
|
97
|
+
];
|
|
98
|
+
const checkLine = (side) => ({
|
|
99
|
+
kind: "check",
|
|
100
|
+
side,
|
|
101
|
+
tone: you === undefined ? "neutral" : side === you ? "chance" : "threat",
|
|
102
|
+
toMove: side === facts.toMove,
|
|
103
|
+
items: side === facts.toMove ? facts.checks : facts.otherChecks,
|
|
104
|
+
});
|
|
105
|
+
// The player's threats are their pieces' troubles and the other side's checks; their chances the other way round.
|
|
106
|
+
const lines = you === undefined
|
|
107
|
+
? [...sideLines("white"), checkLine("white"), ...sideLines("black"), checkLine("black")]
|
|
108
|
+
: [...sideLines(you), checkLine(otherSide(you)), ...sideLines(otherSide(you)), checkLine(you)];
|
|
109
|
+
const loose = { kind: "loose", tone: "neutral", items: facts.loose };
|
|
110
|
+
return [...lines, loose].filter((line) => line.items.length > 0);
|
|
111
|
+
}
|
|
112
|
+
function otherSide(side) {
|
|
113
|
+
return side === "white" ? "black" : "white";
|
|
114
|
+
}
|
|
115
|
+
// A piece's trouble is a threat to its side's player and a chance for the other.
|
|
116
|
+
function toneOf(side, you) {
|
|
117
|
+
return you === undefined ? "neutral" : side === you ? "threat" : "chance";
|
|
118
|
+
}
|
|
119
|
+
function castlingOf(side) {
|
|
120
|
+
if (side.castled === "O-O" || side.castled === "O-O-O") {
|
|
121
|
+
return side.castled;
|
|
122
|
+
}
|
|
123
|
+
return side.castlingRights.length > 0 ? "not-yet" : side.castled === "no" ? "no-more" : "unknown";
|
|
124
|
+
}
|
|
125
|
+
function something(cell) {
|
|
126
|
+
return typeof cell === "object" ? cell.length > 0 : typeof cell === "number" ? cell > 0 : typeof cell === "boolean" ? cell : cell !== "unknown";
|
|
127
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { foldKeys } from "./folds.js";
|
|
3
|
+
/**
|
|
4
|
+
* The tab of a column: open, on the column's outer edge; folded, peeking out from under the board, with a dot when the AI spoke meanwhile, and a mark in the
|
|
5
|
+
* other corner, as the AI's thought cloud.
|
|
6
|
+
*/
|
|
7
|
+
export function FoldTab({ column, folded, label, unread = false, mark, onFold }) {
|
|
8
|
+
// The chevron points where a click moves the handle: in under the board, or out again.
|
|
9
|
+
const left = (column === "talk") === folded;
|
|
10
|
+
return (_jsx("div", { class: `fold-tab of-${column}${folded ? " folded" : ""}`, children: _jsxs("button", { id: `fold-${column}`, class: unread ? "unread" : undefined, type: "button", title: `${label} (${foldKeys[column]})`, "aria-label": label, "aria-controls": column, "aria-expanded": !folded, "aria-keyshortcuts": foldKeys[column], onClick: () => onFold(column), children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: left ? "M14 7L9 12L14 17" : "M10 7L15 12L10 17" }) }), mark] }) }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// The side columns the player folds: the talk on the left, which narrows to its two faces before it folds, the game on the right; each as this browser
|
|
2
|
+
// remembers it.
|
|
3
|
+
/** The key that folds each column, as in editors with two side bars. */
|
|
4
|
+
export const foldKeys = { talk: "[", game: "]" };
|
|
5
|
+
/** The columns as this browser left them; else both open. */
|
|
6
|
+
export function startFolds() {
|
|
7
|
+
return { talk: read("talk"), game: read("game") === "folded" ? "folded" : "open" };
|
|
8
|
+
}
|
|
9
|
+
/** What a click on a column's tab makes of it: the talk narrows to its faces, then folds, then opens whole again; the game folds and opens. */
|
|
10
|
+
export function nextFold(column, fold) {
|
|
11
|
+
if (fold === "open") {
|
|
12
|
+
return column === "talk" ? "rail" : "folded";
|
|
13
|
+
}
|
|
14
|
+
return fold === "rail" ? "folded" : "open";
|
|
15
|
+
}
|
|
16
|
+
/** Remembers how the player left a column. */
|
|
17
|
+
export function rememberFold(column, fold) {
|
|
18
|
+
try {
|
|
19
|
+
localStorage.setItem(keys[column], fold);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// The choice lasts as long as the page.
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Whether the game's column is under the board, where three columns would leave the board small, and does not fold. */
|
|
26
|
+
export function gameUnderBoard() {
|
|
27
|
+
return matchMedia("(max-width: 1099px)").matches;
|
|
28
|
+
}
|
|
29
|
+
/** Whether the page is in one column, as on a phone, where nothing folds. */
|
|
30
|
+
export function oneColumn() {
|
|
31
|
+
return matchMedia("(max-width: 840px)").matches;
|
|
32
|
+
}
|
|
33
|
+
const keys = { talk: "chesspal-talk", game: "chesspal-column" };
|
|
34
|
+
function read(column) {
|
|
35
|
+
try {
|
|
36
|
+
const fold = localStorage.getItem(keys[column]);
|
|
37
|
+
return fold === "rail" || fold === "folded" ? fold : "open";
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// A browser that keeps nothing for the page: the defaults decide.
|
|
41
|
+
return "open";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { PlayerPicture } from "../avatars/avatars.js";
|
|
3
|
+
import { Torretta } from "../avatars/torretta.js";
|
|
4
|
+
/**
|
|
5
|
+
* 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
|
|
6
|
+
* 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
|
|
7
|
+
* turned, the switches of the sounds and the theme, and the gear of the settings (D-56).
|
|
8
|
+
*/
|
|
9
|
+
export function Header({ text, verdict, kind, who, actions, onNewGame, standOut = false, onStudy, flipped, onFlip, sound, onSound, onTheme, onSettings, profile, onProfile }) {
|
|
10
|
+
return (_jsxs("header", { class: "top", children: [_jsx(Torretta, { size: 34 }), _jsx("b", { class: "title", children: "Chesspal" }), _jsxs("p", { class: `verdict${kind === undefined ? "" : ` ${kind}`}`, role: "status", children: [verdict === undefined || kind === undefined ? null : _jsx(Icon, { class: "kind", children: kindIcons[kind] }), who === undefined || verdict === undefined ? verdict : (_jsxs(_Fragment, { children: [_jsx("span", { class: "ai-name", children: who }), _jsx("span", { children: verdict })] }))] }), actions === undefined ? null : _jsx(Actions, { actions: actions, 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 : (_jsxs("button", { class: "command", type: "button", title: text.study.title, 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("span", { children: text.study.title })] })), _jsxs("button", { class: "command", type: "button", title: 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" }) }), _jsx("span", { children: text.flip })] }), _jsx("button", { class: "icon-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", 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" })] }) }), 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 }) }))] }));
|
|
11
|
+
}
|
|
12
|
+
/** The answer to the AI's draw offer while it stands, else the player's own offer; then the resignation. */
|
|
13
|
+
function Actions({ actions, text }) {
|
|
14
|
+
const { offer, canOffer, onOffer, onAnswer, onResign } = actions;
|
|
15
|
+
return (_jsxs(_Fragment, { children: [offer === "ai"
|
|
16
|
+
? (_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 })] })] }))
|
|
17
|
+
: (_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 })] })] }));
|
|
18
|
+
}
|
|
19
|
+
// A pair of scales, a flag, two hands, a pause.
|
|
20
|
+
const kindIcons = {
|
|
21
|
+
analysis: _jsx("path", { d: "M12 4V20M8 20H16M5 7H19M5 7L2.5 13A2.5 2 0 0 0 7.5 13ZM19 7L16.5 13A2.5 2 0 0 0 21.5 13Z" }),
|
|
22
|
+
over: _jsx("path", { d: "M6 21V4M6 4H17L14.5 8L17 12H6" }),
|
|
23
|
+
offer: _jsx("path", { d: "M3 12L7 8L11 10L14 7L21 12M7 14L10 17M10 13L13 16M13 12L16 15" }),
|
|
24
|
+
idle: _jsx("path", { d: "M9 6.5V17.5M15 6.5V17.5" }),
|
|
25
|
+
};
|
|
26
|
+
function Icon({ class: name, children }) {
|
|
27
|
+
return _jsx("svg", { class: `icon ${name}`, viewBox: "0 0 24 24", "aria-hidden": "true", children: children });
|
|
28
|
+
}
|