chesspal 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agents.js +2 -1
- package/dist/commands/ai-say.d.ts +2 -1
- package/dist/commands/analyse-game.d.ts +2 -0
- package/dist/commands/analyse-game.js +7 -4
- package/dist/commands/annotate-game.d.ts +2 -0
- package/dist/commands/annotate-game.js +26 -8
- package/dist/commands/answer-lines.d.ts +8 -4
- package/dist/commands/answer-lines.js +19 -3
- package/dist/commands/coach.js +2 -2
- package/dist/commands/context.d.ts +2 -0
- package/dist/commands/delete-review.js +3 -2
- package/dist/commands/game-summary.d.ts +7 -2
- package/dist/commands/game-summary.js +7 -0
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/look.d.ts +9 -5
- package/dist/commands/look.js +19 -9
- package/dist/commands/ponder.js +1 -1
- package/dist/commands/position.js +2 -2
- package/dist/commands/say.d.ts +11 -6
- package/dist/commands/say.js +21 -13
- package/dist/commands/status.d.ts +3 -1
- package/dist/commands/status.js +31 -6
- package/dist/commands/study.d.ts +11 -1
- package/dist/commands/study.js +31 -2
- package/dist/commands/view.d.ts +17 -5
- package/dist/commands/view.js +67 -10
- package/dist/core/facts.d.ts +14 -2
- package/dist/core/facts.js +32 -7
- package/dist/core/game.d.ts +5 -0
- package/dist/core/game.js +9 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/placement.d.ts +63 -0
- package/dist/core/placement.js +259 -0
- package/dist/core/possibilities.d.ts +38 -0
- package/dist/core/possibilities.js +254 -0
- package/dist/core/review.d.ts +4 -1
- package/dist/core/review.js +17 -6
- package/dist/games/analysis.d.ts +8 -4
- package/dist/games/analysis.js +9 -5
- package/dist/games/current-game.d.ts +16 -1
- package/dist/games/current-game.js +10 -8
- package/dist/games/files.d.ts +2 -0
- package/dist/games/files.js +7 -1
- package/dist/games/index.d.ts +2 -2
- package/dist/games/index.js +1 -1
- package/dist/games/table-talk.d.ts +2 -0
- package/dist/games/table-talk.js +5 -1
- package/dist/games/talk.d.ts +4 -3
- package/dist/games/talk.js +3 -3
- package/dist/page/app/app.js +36 -12
- package/dist/page/app/keys.js +16 -2
- package/dist/page/app/served.js +2 -2
- package/dist/page/board/board.js +2 -1
- package/dist/page/board/marks.js +4 -24
- package/dist/page/board/signs.js +23 -0
- package/dist/page/game/column.js +2 -2
- package/dist/page/game/game.css +12 -0
- package/dist/page/game/move-list.js +2 -2
- package/dist/page/game/plies.js +8 -0
- package/dist/page/helps/helps.css +3 -1
- package/dist/page/helps/helps.js +15 -15
- package/dist/page/layout/layout.css +29 -5
- package/dist/page/legend/legend.css +203 -0
- package/dist/page/legend/legend.js +169 -0
- package/dist/page/legend/view.js +54 -0
- package/dist/page/style.css +2 -0
- package/dist/page/talk/chat.css +90 -0
- package/dist/page/talk/talk.js +27 -5
- package/dist/page/text.js +38 -2
- package/dist/server/mcp.js +8 -6
- package/dist/server/page/game.js +20 -7
- package/dist/server/page/page.js +5 -3
- package/instructions/opponent.md +1 -1
- package/package.json +1 -1
package/dist/page/talk/talk.js
CHANGED
|
@@ -59,29 +59,35 @@ export function Legend({ review, words }) {
|
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* The player's words of the game, the newest by the box they write in with how far it got, and the box: Enter or the button sends the words alone, the next
|
|
62
|
-
* move takes them with it. 200 characters from one move of the AI's to the next: once they are written, the box goes until its move (D-54).
|
|
62
|
+
* move takes them with it. 200 characters from one move of the AI's to the next: once they are written, the box goes until its move (D-54). In analysis the
|
|
63
|
+
* AI's answers are in the same thread, and each line of the player's names the move it was written on (D-101).
|
|
63
64
|
*/
|
|
64
|
-
export function Compose({ lines, newestFirst, spent, draft, onDraft, onSend, canMove, name, problem, text }) {
|
|
65
|
+
export function Compose({ lines, newestFirst, spent, draft, onDraft, onSend, canMove, name, problem, about, text }) {
|
|
65
66
|
const list = useRef(null);
|
|
66
67
|
useOnlyWhole(list, lines.map((line) => line.text));
|
|
67
68
|
const left = talkLimits.line - spent;
|
|
68
69
|
const blank = draft.trim() === "";
|
|
70
|
+
const lastWritten = lines.findLastIndex((line) => line.who === "player");
|
|
71
|
+
const lastAnswer = lines.findLastIndex((line) => line.who === "ai");
|
|
72
|
+
const prompt = about === undefined ? text.writeTo(name) : text.askAbout(name);
|
|
69
73
|
const submit = () => {
|
|
70
74
|
if (!blank) {
|
|
71
75
|
onSend();
|
|
72
76
|
}
|
|
73
77
|
};
|
|
74
|
-
return (_jsxs(_Fragment, { children: [_jsx("ol", { ref: list, class: "sent", "aria-live": "polite", children: inOrder(lines.map((line, index) => (_jsxs("li", {
|
|
78
|
+
return (_jsxs(_Fragment, { children: [_jsx("ol", { ref: list, class: "sent", "aria-live": "polite", children: inOrder(lines.map((line, index) => (_jsxs("li", { class: line.who === "ai" ? "from-ai" : undefined, style: about === undefined ? aged(lines.length - 1 - index) : reread(index, lastAnswer), children: [line.who === "ai"
|
|
79
|
+
? (_jsx("p", { class: "say", children: _jsx(Marked, { text: line.text }) }))
|
|
80
|
+
: (_jsxs("p", { class: "said", children: [about === undefined || line.shown?.move === undefined ? null : _jsx(ShownMove, { move: line.shown.move, onShow: () => about.onShow(line.shown.at), text: text }), line.text] })), index === lastWritten ? _jsx(Status, { heard: line.heard, name: name, text: text }) : null] }, index))), newestFirst) }), left <= 0 ? null : (_jsxs("form", { class: about === undefined ? "compose" : "compose asking", onSubmit: (event) => {
|
|
75
81
|
event.preventDefault();
|
|
76
82
|
submit();
|
|
77
|
-
}, children: [_jsx("p", { class: "limit", role: "status", children: problem ?? "" }), _jsx("textarea", { rows: 2, maxLength: left, placeholder:
|
|
83
|
+
}, children: [_jsx("p", { class: "limit", role: "status", children: problem ?? "" }), _jsx("textarea", { rows: 2, maxLength: left, placeholder: prompt, "aria-label": prompt, value: draft, onInput: (event) => onDraft(event.currentTarget.value), onKeyDown: (event) => {
|
|
78
84
|
if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
|
|
79
85
|
event.preventDefault();
|
|
80
86
|
submit();
|
|
81
87
|
}
|
|
82
88
|
} }), _jsxs("div", { class: "foot", children: [spent + draft.length >= talkLimits.line - 50
|
|
83
89
|
? _jsxs("span", { class: "count", children: [spent + draft.length, "/", talkLimits.line] })
|
|
84
|
-
: _jsx("span", { class: "hint", children: !blank && canMove ? text.withMove : "" }), _jsxs("button", { class: "send", type: "submit", disabled: blank, children: [text.send, _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M5 12H18M13 7L18 12L13 17" }) })] })] })] }))] }));
|
|
90
|
+
: _jsx("span", { class: "hint", children: about === undefined ? !blank && canMove ? text.withMove : "" : _jsx(OnMove, { ply: about.ply, text: text }) }), _jsxs("button", { class: "send", type: "submit", disabled: blank, children: [text.send, _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M5 12H18M13 7L18 12L13 17" }) })] })] })] }))] }));
|
|
85
91
|
}
|
|
86
92
|
/**
|
|
87
93
|
* The talk narrowed to a card beside the board: the Torretta above, the player below, each with their name and side written down the card, a ring on
|
|
@@ -90,6 +96,18 @@ export function Compose({ lines, newestFirst, spent, draft, onDraft, onSend, can
|
|
|
90
96
|
export function TalkFaces({ name, aiPicture, you, picture, ai, player, aiMoves, playerMoves, unread, text }) {
|
|
91
97
|
return (_jsxs("div", { class: "faces", children: [_jsx(RailPlayer, { place: "ai", picture: aiPicture ?? _jsx(Torretta, { size: 40 }), name: name, ...(ai === undefined ? {} : { side: text.sides[ai] }), moves: aiMoves, unread: unread }), _jsx(RailPlayer, { place: "you", picture: picture, name: you, ...(player === undefined ? {} : { side: text.sides[player] }), moves: playerMoves })] }));
|
|
92
98
|
}
|
|
99
|
+
/** The move a line of the player's was written on, 14.♘f3, small at the line's head: a press shows it on the board again (Zoe). */
|
|
100
|
+
function ShownMove({ move, onShow, text }) {
|
|
101
|
+
const [, number = "", san = move] = /^(\d+\.+)(.+)$/.exec(move) ?? [];
|
|
102
|
+
return (_jsxs("button", { type: "button", class: "shown-move", title: text.goTo(move), "aria-label": text.goTo(move), onClick: onShow, children: [_jsx("span", { class: "move-number", children: number }), _jsx(Figurines, { san: san, side: number.endsWith("...") ? "black" : "white" })] }));
|
|
103
|
+
}
|
|
104
|
+
/** What the words go with in analysis: the move shown, or the whole game at its start. */
|
|
105
|
+
function OnMove({ ply, text }) {
|
|
106
|
+
if (ply === undefined) {
|
|
107
|
+
return _jsx(_Fragment, { children: text.onWholeGame });
|
|
108
|
+
}
|
|
109
|
+
return (_jsxs(_Fragment, { children: [text.onMove, " ", _jsxs("span", { class: "move-number", children: [ply.number, ply.side === "white" ? "." : "..."] }), _jsx(Figurines, { san: ply.san, side: ply.side })] }));
|
|
110
|
+
}
|
|
93
111
|
function OnBoard({ text }) {
|
|
94
112
|
const point = useContext(PointSquare);
|
|
95
113
|
return (_jsx(_Fragment, { children: text.split(boardWords).map((part, index) => index % 2 === 1
|
|
@@ -130,6 +148,10 @@ function inOrder(items, newestFirst) {
|
|
|
130
148
|
function aged(age) {
|
|
131
149
|
return { opacity: age === 0 ? 1 : Math.max(0.2, 0.4 - (age - 1) * 0.1) };
|
|
132
150
|
}
|
|
151
|
+
/** In analysis the talk is read again: the AI's last answer and what follows it whole, the older lines a little back, still readable (Zoe). */
|
|
152
|
+
function reread(index, lastAnswer) {
|
|
153
|
+
return { opacity: index >= lastAnswer ? 1 : 0.75 };
|
|
154
|
+
}
|
|
133
155
|
/**
|
|
134
156
|
* A list that never scrolls: a bubble that does not fit whole in it is hidden, the oldest first since the newest sit by the card, and shows again when the
|
|
135
157
|
* window grows.
|
package/dist/page/text.js
CHANGED
|
@@ -64,7 +64,7 @@ const english = {
|
|
|
64
64
|
noGame: "No game yet: press + to start one, or ask for one in the chat.",
|
|
65
65
|
foldCard: "Fold the card",
|
|
66
66
|
openCard: "Open the card",
|
|
67
|
-
saveMoves: "
|
|
67
|
+
saveMoves: "PGN",
|
|
68
68
|
saveMovesHint: "The moves alone, to open on Lichess or chess.com",
|
|
69
69
|
noGameNow: "No game in progress",
|
|
70
70
|
analysing: "Analysis in progress",
|
|
@@ -103,6 +103,8 @@ const english = {
|
|
|
103
103
|
send: "Send",
|
|
104
104
|
sent: "Sent",
|
|
105
105
|
withMove: "Or move: your words go with it",
|
|
106
|
+
onMove: "On",
|
|
107
|
+
onWholeGame: "On the whole game",
|
|
106
108
|
talk: "The talk",
|
|
107
109
|
move: "Move",
|
|
108
110
|
draw: "draw",
|
|
@@ -129,6 +131,7 @@ const english = {
|
|
|
129
131
|
startClocks: "Start",
|
|
130
132
|
thinking: { move: "Thinking about its move", ponder: "Thinking while you play" },
|
|
131
133
|
asleep: (name) => `${name} is not listening: write a message, such as "hi", in the window where you opened ${name}, not here. Its clock waits until it answers.`,
|
|
134
|
+
asleepInAnalysis: (name) => `${name} is not listening: write to it in the window where you opened it, not here.`,
|
|
132
135
|
commandHere: (name) => `/clear and the other commands go in the window where you opened ${name}, not here.`,
|
|
133
136
|
refused: {
|
|
134
137
|
"no-game": () => "There is no game going on: press + to start one, or ask for one in the chat.",
|
|
@@ -149,6 +152,8 @@ const english = {
|
|
|
149
152
|
piece: (letter, side) => `${side} ${englishNames[letter]}`,
|
|
150
153
|
taken: (pieces, ahead) => `Pieces taken: ${pieces.length === 0 ? "none" : pieces.join(", ")}${ahead > 0 ? `; ahead by ${ahead}` : ""}`,
|
|
151
154
|
writeTo: (name) => `Write to ${name}…`,
|
|
155
|
+
askAbout: (name) => `Ask ${name} about this game…`,
|
|
156
|
+
goTo: (move) => `Go to ${move}`,
|
|
152
157
|
readBy: (name) => `Read by ${name}`,
|
|
153
158
|
wrote: (name) => `${name} wrote`,
|
|
154
159
|
offers: "offers a draw",
|
|
@@ -325,6 +330,19 @@ const english = {
|
|
|
325
330
|
pawnSquare: (pawn, caught) => `The enemy king is ${caught ? "inside" : "outside"} the square of the pawn on ${pawn}`,
|
|
326
331
|
opposition: (side) => `${side === "white" ? "White" : "Black"} has the opposition`,
|
|
327
332
|
},
|
|
333
|
+
legend: {
|
|
334
|
+
title: "Marks on the board",
|
|
335
|
+
hint: "Marks on the board (?)",
|
|
336
|
+
none: "No marks on the board now",
|
|
337
|
+
badge: "the move's judgment",
|
|
338
|
+
sign: (kind, tone) => `${tone === "threat" ? "Your" : tone === "chance" ? "Opponent's" : "A"} ${{ risk: "piece at risk", pin: "pinned piece", skewer: "skewered piece" }[kind]}`,
|
|
339
|
+
pinLine: "The pin's line",
|
|
340
|
+
skewerLine: "The skewer's line",
|
|
341
|
+
drawnBy: (name) => `drawn by ${name}`,
|
|
342
|
+
drawnByCoach: "drawn by the coach",
|
|
343
|
+
control: "How many of White's pieces and of Black's reach the square",
|
|
344
|
+
ends: { crown: "Won", mate: "Checkmated", flag: "Resigned", half: "Draw, half a point each" },
|
|
345
|
+
},
|
|
328
346
|
};
|
|
329
347
|
const italian = {
|
|
330
348
|
language: "it",
|
|
@@ -385,7 +403,7 @@ const italian = {
|
|
|
385
403
|
noGame: "Nessuna partita: premi + per iniziarne una, o chiedila nella chat.",
|
|
386
404
|
foldCard: "Chiudi la scheda",
|
|
387
405
|
openCard: "Apri la scheda",
|
|
388
|
-
saveMoves: "PGN
|
|
406
|
+
saveMoves: "PGN",
|
|
389
407
|
saveMovesHint: "Solo le mosse, da aprire su Lichess o chess.com",
|
|
390
408
|
noGameNow: "Nessuna partita in corso",
|
|
391
409
|
analysing: "Analisi in corso",
|
|
@@ -424,6 +442,8 @@ const italian = {
|
|
|
424
442
|
send: "Manda",
|
|
425
443
|
sent: "Mandato",
|
|
426
444
|
withMove: "Oppure muovi: parte con la mossa",
|
|
445
|
+
onMove: "Su",
|
|
446
|
+
onWholeGame: "Su tutta la partita",
|
|
427
447
|
talk: "Conversazione",
|
|
428
448
|
move: "Mossa",
|
|
429
449
|
draw: "patta",
|
|
@@ -450,6 +470,7 @@ const italian = {
|
|
|
450
470
|
startClocks: "Inizia",
|
|
451
471
|
thinking: { move: "Sta pensando alla sua mossa", ponder: "Pensa mentre giochi tu" },
|
|
452
472
|
asleep: (name) => `${name} non ti sta ascoltando: scrivi un messaggio, per esempio "ciao", nella finestra dove hai aperto ${name}, non qui. Il suo orologio aspetta che risponda.`,
|
|
473
|
+
asleepInAnalysis: (name) => `${name} non ti sta ascoltando: scrivigli nella finestra dove l'hai aperto, non qui.`,
|
|
453
474
|
commandHere: (name) => `/clear e gli altri comandi vanno scritti nella finestra dove hai aperto ${name}, non qui.`,
|
|
454
475
|
refused: {
|
|
455
476
|
"no-game": () => "Nessuna partita in corso: premi + per iniziarne una, o chiedila nella chat.",
|
|
@@ -471,6 +492,8 @@ const italian = {
|
|
|
471
492
|
piece: (letter, side) => `${italianNames[letter]} ${side === "white" ? (letter === "Q" || letter === "R" ? "bianca" : "bianco") : (letter === "Q" || letter === "R" ? "nera" : "nero")}`,
|
|
472
493
|
taken: (pieces, ahead) => `Pezzi presi: ${pieces.length === 0 ? "nessuno" : pieces.join(", ")}${ahead > 0 ? `; avanti di ${ahead}` : ""}`,
|
|
473
494
|
writeTo: (name) => `Scrivi a ${name}…`,
|
|
495
|
+
askAbout: (name) => `Chiedi a ${name} di questa partita…`,
|
|
496
|
+
goTo: (move) => `Vai a ${move}`,
|
|
474
497
|
readBy: (name) => `Letto da ${name}`,
|
|
475
498
|
wrote: (name) => `${name} ha scritto`,
|
|
476
499
|
offers: "propone patta",
|
|
@@ -647,6 +670,19 @@ const italian = {
|
|
|
647
670
|
pawnSquare: (pawn, caught) => `Il re nemico è ${caught ? "dentro il" : "fuori dal"} quadrato del pedone in ${pawn}`,
|
|
648
671
|
opposition: (side) => `L'opposizione è del ${side === "white" ? "Bianco" : "Nero"}`,
|
|
649
672
|
},
|
|
673
|
+
legend: {
|
|
674
|
+
title: "Segni sulla scacchiera",
|
|
675
|
+
hint: "Segni sulla scacchiera (?)",
|
|
676
|
+
none: "Nessun segno sulla scacchiera ora",
|
|
677
|
+
badge: "il giudizio della mossa",
|
|
678
|
+
sign: (kind, tone) => `${tone === "threat" ? "Tuo pezzo" : tone === "chance" ? "Pezzo avversario" : "Pezzo"} ${{ risk: "in pericolo", pin: "inchiodato", skewer: "infilato" }[kind]}`,
|
|
679
|
+
pinLine: "Linea dell'inchiodatura",
|
|
680
|
+
skewerLine: "Linea dell'infilata",
|
|
681
|
+
drawnBy: (name) => `disegnato da ${name}`,
|
|
682
|
+
drawnByCoach: "disegnato dall'allenatore",
|
|
683
|
+
control: "Quanti pezzi del Bianco e del Nero raggiungono la casa",
|
|
684
|
+
ends: { crown: "Ha vinto", mate: "Scacco matto", flag: "Ha abbandonato", half: "Patta, mezzo punto a testa" },
|
|
685
|
+
},
|
|
650
686
|
};
|
|
651
687
|
// How each piece moves, said when it was taken where it cannot go (Anatolij's words).
|
|
652
688
|
const englishMoves = {
|
package/dist/server/mcp.js
CHANGED
|
@@ -154,14 +154,16 @@ function toolConfig(command) {
|
|
|
154
154
|
return { description: command.description, inputSchema: z.looseObject({}).meta({ ...shownInput(command), $schema: undefined }), annotations, _meta: meta };
|
|
155
155
|
}
|
|
156
156
|
async function runTool(command, input, context, caller, meta) {
|
|
157
|
-
const
|
|
157
|
+
const calling = await sharingCaller(command, meta, context);
|
|
158
|
+
const agent = calling !== undefined && "agent" in calling ? calling.agent : undefined;
|
|
158
159
|
const chat = command.byAgents === undefined && agent === undefined;
|
|
159
160
|
let refused = false;
|
|
160
161
|
try {
|
|
161
162
|
if (chat) {
|
|
162
163
|
context.calls?.();
|
|
163
164
|
}
|
|
164
|
-
const
|
|
165
|
+
const who = agent !== undefined ? { agent } : calling !== undefined ? { chat: true } : {};
|
|
166
|
+
const result = await command.run(checkedInput(command, input), { ...context, caller, ...who });
|
|
165
167
|
return { content: [{ type: "text", text: command.format(result, context) }] };
|
|
166
168
|
}
|
|
167
169
|
catch (error) {
|
|
@@ -177,16 +179,16 @@ async function runTool(command, input, context, caller, meta) {
|
|
|
177
179
|
}
|
|
178
180
|
}
|
|
179
181
|
/**
|
|
180
|
-
*
|
|
181
|
-
* goes on, so that no call of the chat's waits for it while it plays
|
|
182
|
+
* Who calls a tool the chat shares with an agent, as the analyst (D-89): the chat, or the agent by its type; looked up only in a folder where one may run
|
|
183
|
+
* and while no game goes on, so that no call of the chat's waits for it while it plays. None when it cannot be told, or for an agent the tool is not shared with.
|
|
182
184
|
*/
|
|
183
|
-
async function
|
|
185
|
+
async function sharingCaller(command, meta, context) {
|
|
184
186
|
const agents = command.sharedWith?.filter((agent) => hasAgent(context.folder, agent, context.host?.())) ?? [];
|
|
185
187
|
if (agents.length === 0 || gameGoingOn(context)) {
|
|
186
188
|
return undefined;
|
|
187
189
|
}
|
|
188
190
|
const caller = await callingAgent(meta, context.folder);
|
|
189
|
-
return caller !== undefined && "agent" in caller && agents.includes(caller.agent) ?
|
|
191
|
+
return caller !== undefined && "agent" in caller && !agents.includes(caller.agent) ? undefined : caller;
|
|
190
192
|
}
|
|
191
193
|
/** Whether a game goes on, or may: a game that cannot be read is the command's to report, as the chat's call (Federico). */
|
|
192
194
|
function gameGoingOn(context) {
|
package/dist/server/page/game.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { analyseGame, answerDraw, bookPlies, ChatNotClear, DrawRefused, endGame, gameAt, gameToStudy, namedGame, newGame, offerDraw, playMove, samePosition, say, startNewGame, } from "../../commands/index.js";
|
|
1
|
+
import { analyseGame, answerDraw, bookPlies, ChatNotClear, DrawRefused, endGame, gameAt, gameToStudy, namedGame, newGame, offerDraw, playMove, samePosition, say, shownPosition, startNewGame, } from "../../commands/index.js";
|
|
2
2
|
import { clockStarted, isReviewed, writePgn } from "../../core/index.js";
|
|
3
3
|
import { aiModelOf, archiveName, coachShown, coachWords, drawOfferOf, endOf, gameInAnalysis, heardLines, loadGame, loadTableTalk, playerPgn, ponderingOn, saveGame, startAnalysis, talkLimits, thoughtKey, wakesTheChat, WordsRefused, } from "../../games/index.js";
|
|
4
4
|
import { gameFolder, helpsIn } from "../../profiles/index.js";
|
|
@@ -72,28 +72,34 @@ async function moveFromPage(request, response, served) {
|
|
|
72
72
|
served.bell.ring();
|
|
73
73
|
send(response, 200, json, JSON.stringify({ played: true }));
|
|
74
74
|
}
|
|
75
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* The player's words from the page, without a move: saved with the game as player_say saves them, refused in the player's words, the doorbell rung for the
|
|
77
|
+
* first; in analysis with the position the page shows, and the doorbell rung for each (D-101).
|
|
78
|
+
*/
|
|
76
79
|
async function messageFromPage(request, response, served) {
|
|
77
80
|
if (!isJson(request)) {
|
|
78
81
|
send(response, 415, json, JSON.stringify({ error: 'Send the message as JSON: {"text": "…"}.' }));
|
|
79
82
|
return;
|
|
80
83
|
}
|
|
81
|
-
const
|
|
84
|
+
const body = jsonOf(await bodyOf(request));
|
|
85
|
+
const text = textOf(body?.text);
|
|
86
|
+
const shown = typeof body?.game === "string" && typeof body.at === "number" ? shownPosition(served.context, body.game, body.at) : undefined;
|
|
82
87
|
if (text === undefined) {
|
|
83
88
|
send(response, 400, json, JSON.stringify({ error: `Send the message as JSON, ${talkLimits.player} characters at most: {"text": "…"}.` }));
|
|
84
89
|
return;
|
|
85
90
|
}
|
|
86
91
|
let current;
|
|
87
92
|
try {
|
|
88
|
-
current = say(gameFolder(served.context.home()), "player", text, served.context.now());
|
|
93
|
+
current = say(gameFolder(served.context.home()), "player", text, served.context.now(), undefined, shown);
|
|
89
94
|
}
|
|
90
95
|
catch (error) {
|
|
91
96
|
const noGame = refusalOf(served)?.refusal === "no-game";
|
|
92
97
|
send(response, 409, json, JSON.stringify(refusalFrom(error, noGame ? "no-game" : undefined)));
|
|
93
98
|
return;
|
|
94
99
|
}
|
|
95
|
-
// The first words of a move wake the chat, and between games the first since the AI spoke; the AI finds the rest at its next call (D-54, D-74).
|
|
96
|
-
|
|
100
|
+
// The first words of a move wake the chat, and between games the first since the AI spoke; the AI finds the rest at its next call (D-54, D-74). In
|
|
101
|
+
// analysis every line does: a chat that answered in its own window would not hear the next (Priya).
|
|
102
|
+
if (shown !== undefined || wakesTheChat(current)) {
|
|
97
103
|
served.bell.ring();
|
|
98
104
|
}
|
|
99
105
|
send(response, 200, json, JSON.stringify({ said: true }));
|
|
@@ -244,7 +250,14 @@ function sendGame(response, served) {
|
|
|
244
250
|
// With no game the page still shows the talk before the first (D-77).
|
|
245
251
|
const spoken = current ?? loadTableTalk(folder);
|
|
246
252
|
const heard = heardLines(spoken, served.context.given);
|
|
247
|
-
const talk = spoken.talk.map(({ who, text, ply }, index) =>
|
|
253
|
+
const talk = spoken.talk.map(({ who, text, ply, shown }, index) => ({
|
|
254
|
+
who,
|
|
255
|
+
text,
|
|
256
|
+
ply,
|
|
257
|
+
...(heard[index] === undefined ? {} : { heard: heard[index] }),
|
|
258
|
+
// In analysis the page goes back to the move the player wrote about (D-101).
|
|
259
|
+
...(shown === undefined ? {} : { shown }),
|
|
260
|
+
}));
|
|
248
261
|
const offer = current === undefined ? undefined : drawOfferOf(current);
|
|
249
262
|
const coach = coachOn(folder, current);
|
|
250
263
|
const game = current === undefined ? null : {
|
package/dist/server/page/page.js
CHANGED
|
@@ -4,7 +4,7 @@ import { watch } from "node:fs";
|
|
|
4
4
|
import { createServer } from "node:http";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { positionIn, samePosition } from "../../commands/index.js";
|
|
7
|
-
import { gameStamp } from "../../games/index.js";
|
|
7
|
+
import { gameStamp, tableTalkStamp } from "../../games/index.js";
|
|
8
8
|
import { outdatedHostFiles } from "../../host-files/index.js";
|
|
9
9
|
import { gameFolder } from "../../profiles/index.js";
|
|
10
10
|
import { Bell, bellProbe, pagePorts } from "../bell.js";
|
|
@@ -111,12 +111,14 @@ function answer(request, response, served) {
|
|
|
111
111
|
function hasNews(context) {
|
|
112
112
|
return gameSeen(context) !== context.seen;
|
|
113
113
|
}
|
|
114
|
-
// The game the chat sees: the profile's folder, and the
|
|
114
|
+
// The game the chat sees: the profile's folder, and the stamps of its game.json and of the talk with no game, where the player's words go in analysis
|
|
115
|
+
// before any game was played here (Priya); none while the data folder cannot be made.
|
|
115
116
|
function gameSeen(context) {
|
|
116
117
|
try {
|
|
117
118
|
const folder = gameFolder(context.home());
|
|
118
119
|
return `${folder}
|
|
119
|
-
${gameStamp(folder) ?? ""}
|
|
120
|
+
${gameStamp(folder) ?? ""}
|
|
121
|
+
${tableTalkStamp(folder) ?? ""}`;
|
|
120
122
|
}
|
|
121
123
|
catch {
|
|
122
124
|
return "";
|
package/instructions/opponent.md
CHANGED
|
@@ -10,6 +10,6 @@ This folder is the player's chess table, and you are their opponent: you choose
|
|
|
10
10
|
- You have a face on the board page, and it shows how you feel. Give it as the `mood` of `ai_move` on each move where the game turns for you: a piece won or lost, a blunder seen, your own or theirs, a threat that appears or a plan that works or fails. It stays for three of your moves (a surprise for one), then turns neutral by itself; let it colour your words while it lasts. When the game ends on the player's move, give how you feel as it ends with `ai_say`'s `mood`.
|
|
11
11
|
- Draws and resignations: `offer_draw` on `ai_move` offers a draw, on `player_move` records the player's, made with their move; an offer stands until the other side moves; `end_game` `draw_agreed` accepts a standing offer, `player_resigns` records the player's resignation in the chat, `ai_resigns` resigns yours. On the board page the player offers, answers and resigns there.
|
|
12
12
|
- The player chooses a new game in the board page's New game window, which asks the sides, the clock and the level: when they ask you for a game, call `new_game` without choices, which opens it there, and don't ask those in the chat. Ask them in the chat, and give them to `new_game`, only when it says there is no board page.
|
|
13
|
-
- To go over a game with the player once no game goes on, the one just over or one they bring as a PGN, give it to `analyse_game`: it reads the PGN for you; where Chesspal's answers say to start the analyst agent for it, start that instead, once per game
|
|
13
|
+
- To go over a game with the player once no game goes on, the one just over or one they bring as a PGN, give it to `analyse_game`: it reads the PGN for you; where Chesspal's answers say to start the analyst agent for it, start that instead, once per game, and never tell them it starts again, even when they ask for the analysis again. While it works, and after, talk the game over whenever the player asks: `look` and `position` with game and move show you any of its positions. When they show you something the review missed, first correct that move's note with `annotate_game` {game, notes: [{move, badge, note}]}, that move only, then answer. Words from the board page you answer with `ai_say`; words in the chat, in the chat. While a game goes on, go over no game, not even in your head or from a PGN they paste: tell them it waits until this one ends.
|
|
14
14
|
- How to read the facts: attackers and defenders count only the pieces that reach the square directly, and a piece in brackets stands behind them; "Loose" means neither attacked nor defended, not attacked now; "At risk" lists the pieces attacked and undefended, attacked by a cheaper piece, or attacked more times than defended.
|
|
15
15
|
- Talk with the player in their language, in a few words. The facts and the tools are for you: tell the player what a chess player would say, never the lists or the tools' names. Name pieces and moves in words, as people talk at the board ("your bishop took on f7"); when you write a move, yours or the player's, write it with the pieces' figurines, as the tools show it (♞f6 for Nf6, ♝xf7+ for Bxf7+), for both sides and in every language: never with letters, neither N B R Q K nor the player's own (Cf6), even when they write them.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chesspal",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Play chess against an AI with a character, and learn with an AI coach: Chesspal keeps the rules, the board, the games and the memory; the AI plays, reasons and talks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|