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/commands/agents.js
CHANGED
|
@@ -21,7 +21,8 @@ export function withAnalyst(summary, context, over) {
|
|
|
21
21
|
/** How the chat has a game over gone over by the analyst agent, on the player's yes or for the game they brought (D-89). */
|
|
22
22
|
export function analystLine(game) {
|
|
23
23
|
return `start the chesspal-analyst agent in the background with the prompt "Analyse${game === undefined ? "" : ` ${game}`}. The player speaks <their language>.": it writes its review on the board `
|
|
24
|
-
+ "page, which shows it at work; tell the player in a word, and when it ends go over
|
|
24
|
+
+ "page, which shows it at work; tell the player in a word, talk the game over with them meanwhile whenever they ask, and when it ends go over it with them "
|
|
25
|
+
+ "from what it tells you.";
|
|
25
26
|
}
|
|
26
27
|
/** How the chat goes over a game the analyst agent has reviewed, or is reviewing: from that review, never with another agent (Stefano). */
|
|
27
28
|
export function reviewedLine(game) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type GameSummary } from "./game-summary.ts";
|
|
3
|
+
import { type Written } from "./say.ts";
|
|
3
4
|
interface Said {
|
|
4
5
|
/** The game, none when the AI spoke before the first (D-77). */
|
|
5
6
|
game?: GameSummary;
|
|
6
|
-
words:
|
|
7
|
+
words: Written[];
|
|
7
8
|
/** The coach yet to start on the player's move that ended the game (D-88). */
|
|
8
9
|
coach?: true;
|
|
9
10
|
}
|
|
@@ -14,6 +14,8 @@ interface MoveFacts {
|
|
|
14
14
|
/** After it, the pieces at risk of either side when they changed from the line before ("none" once no piece is), and the mates the side to move has; none once the board ends the game. */
|
|
15
15
|
atRisk?: string;
|
|
16
16
|
mates?: readonly string[];
|
|
17
|
+
/** After it, what the moves of the side to move would do (D-100): forks, double attacks, discovered and double checks, promotions, stalemates and traps. */
|
|
18
|
+
tactics?: string;
|
|
17
19
|
/** The first move the opening book does not know. */
|
|
18
20
|
outOfBook?: true;
|
|
19
21
|
drawOffer?: true;
|
|
@@ -8,14 +8,15 @@ import { bookOpening, bookReaches } from "./opening-book.js";
|
|
|
8
8
|
import { defineCommand } from "./registry.js";
|
|
9
9
|
import { speechLine, speechOf } from "./speech.js";
|
|
10
10
|
import { gameToStudy, knownTag, moveNumbers, namedGame, noGameGoingOn } from "./study.js";
|
|
11
|
-
import { balanceText, colourName, riskLine } from "./view.js";
|
|
11
|
+
import { balanceText, colourName, riskLine, tacticsText } from "./view.js";
|
|
12
12
|
// The analyst's instructions, read at the first analysis.
|
|
13
13
|
let analystText;
|
|
14
14
|
/** A game over move by move, for the analyst (D-68): the current one, one of the archive or kept to study, or one the player brings as a PGN, kept to study. */
|
|
15
15
|
export const analyseGame = defineCommand({
|
|
16
16
|
name: "analyse_game",
|
|
17
17
|
description: "Gives a game that is over move by move, to comment it with the player: each move with what it took and the time it took, the material "
|
|
18
|
-
+ "when it changes, the pieces at risk
|
|
18
|
+
+ "when it changes, the pieces at risk, the mates in one and what the next moves would do after it (forks, double attacks, discovered checks, "
|
|
19
|
+
+ "promotions, stalemates and stalemate traps), where the game left the opening book, and your comments and plans. Facts "
|
|
19
20
|
+ "only: the judgment is yours. Without game or pgn, the game just over, or else the last one played; game: a name as past_games or study_games "
|
|
20
21
|
+ "writes it; pgn: a game the player brings from elsewhere, kept to study in the profile. Never while a game goes on. The first time in a chat "
|
|
21
22
|
+ "it also says how to analyse. annotate_game then keeps your badges and notes for the board page. Example: analyse_game {}.",
|
|
@@ -145,7 +146,7 @@ function movesOf(record, ai) {
|
|
|
145
146
|
let inBook = record.tags.FEN === undefined && bookReaches(game.position.fen);
|
|
146
147
|
let before = game.facts;
|
|
147
148
|
let balance = materialOf(game.position.fen).balance;
|
|
148
|
-
const start = { atRisk: riskLine(before), mates: matesOf(before) };
|
|
149
|
+
const start = { atRisk: riskLine(before), mates: matesOf(before), tactics: tacticsText(before, game.possibilities, true) };
|
|
149
150
|
// What was at risk on the line before: a move tells it only when it changed.
|
|
150
151
|
let risk = start.atRisk;
|
|
151
152
|
const moves = record.moves.map(({ san, comment }, ply) => {
|
|
@@ -170,6 +171,7 @@ function movesOf(record, ai) {
|
|
|
170
171
|
material: now === balance ? undefined : balanceText(now),
|
|
171
172
|
atRisk: over || risks === risk ? undefined : (risks ?? "none"),
|
|
172
173
|
mates: over ? undefined : matesOf(facts),
|
|
174
|
+
tactics: over ? undefined : tacticsText(facts, game.possibilities, true),
|
|
173
175
|
outOfBook: outOfBook ? true : undefined,
|
|
174
176
|
drawOffer: comment?.includes("(=)") === true ? true : undefined,
|
|
175
177
|
said: side === ai ? saidIn(comment) : undefined,
|
|
@@ -206,7 +208,7 @@ function analysisHead(analysis) {
|
|
|
206
208
|
const review = analysis.review === undefined ? [] : [`Your review: ${analysis.review}`];
|
|
207
209
|
return [head, ...opening, ...review, `${name}.`, ...start];
|
|
208
210
|
}
|
|
209
|
-
/** 12...Nxe5: took P; spent 0:12, 4:32 left; material White +1; at risk: undefended Black Nf6; mate in one: Qh7#; out of book; you said "…"; your plan: …; ahead -1.5 */
|
|
211
|
+
/** 12...Nxe5: took P; spent 0:12, 4:32 left; material White +1; at risk: undefended Black Nf6; mate in one: Qh7#; tactics: fork Nc7+ (Ke8 Ra8); out of book; you said "…"; your plan: …; ahead -1.5 */
|
|
210
212
|
function moveLine(move) {
|
|
211
213
|
const left = move.left === undefined ? undefined : `${clockText(move.left)} left`;
|
|
212
214
|
const parts = [
|
|
@@ -215,6 +217,7 @@ function moveLine(move) {
|
|
|
215
217
|
move.material === undefined ? undefined : `material ${move.material}`,
|
|
216
218
|
move.atRisk === undefined ? undefined : `at risk: ${move.atRisk}`,
|
|
217
219
|
move.mates === undefined ? undefined : `mate in one: ${move.mates.join(" ")}`,
|
|
220
|
+
move.tactics === undefined ? undefined : `tactics: ${move.tactics}`,
|
|
218
221
|
move.outOfBook ? "out of book" : undefined,
|
|
219
222
|
move.drawOffer ? "offers a draw" : undefined,
|
|
220
223
|
move.said === undefined ? undefined : `you said "${move.said}"`,
|
|
@@ -10,6 +10,8 @@ interface Annotated {
|
|
|
10
10
|
/** How many moves took who is ahead after them, and those still without it, as analyse_game numbers them (D-90). */
|
|
11
11
|
estimated: number;
|
|
12
12
|
unestimated: string[];
|
|
13
|
+
/** The moves whose note the chat corrected with the player, told the analyst at each call: its notes leave them as they are (D-101). */
|
|
14
|
+
left: string[];
|
|
13
15
|
}
|
|
14
16
|
/** The AI's badges and notes on a game over, and its word on the whole game, written into the game's PGN (D-69). */
|
|
15
17
|
export declare const annotateGame: import("./registry.ts").Command<z.ZodObject<{
|
|
@@ -5,6 +5,7 @@ import { annotatorOf, badges, Game, leadOf, reviewed, reviewOf, withAnnotator, w
|
|
|
5
5
|
import { gameInAnalysis, rewriteGame, startAnalysis } from "../games/index.js";
|
|
6
6
|
import { analystAgent } from "../host-files/index.js";
|
|
7
7
|
import { gameFolder } from "../profiles/index.js";
|
|
8
|
+
import { analystRuns } from "./agents.js";
|
|
8
9
|
import { arrowsInput, drawingsOf, noteInput, squaresInput } from "./drawings.js";
|
|
9
10
|
import { leadFrom, leadInput } from "./lead-input.js";
|
|
10
11
|
import { defineCommand } from "./registry.js";
|
|
@@ -48,6 +49,14 @@ export const annotateGame = defineCommand({
|
|
|
48
49
|
// would be lost (Federico).
|
|
49
50
|
const caller = await context.caller?.();
|
|
50
51
|
const studied = game === undefined ? gameAnalysed(context) : namedGame(context, game);
|
|
52
|
+
const folder = gameFolder(context.home());
|
|
53
|
+
const numbers = moveNumbers(studied.record);
|
|
54
|
+
// Where the analyst reviews the games, the chat's notes are corrections it made with the player: the analyst, at work then or later, leaves those notes
|
|
55
|
+
// and is told of them at each call, and the chat is not asked for the analyst's work (D-101, Priya). Only a call the host ties to the chat is its own.
|
|
56
|
+
const correction = context.chat === true && analystRuns(context) && estimates.length === 0 && summary === undefined && closing === undefined;
|
|
57
|
+
const analysis = gameInAnalysis(folder);
|
|
58
|
+
const corrected = context.chat !== true && analysis?.game === studied.name ? analysis.corrected ?? [] : [];
|
|
59
|
+
let skipped = 0;
|
|
51
60
|
// Every move is checked before the game is written: all or nothing.
|
|
52
61
|
const moves = [...studied.record.moves];
|
|
53
62
|
for (const { move, badge, note, arrows = [], squares = [] } of notes) {
|
|
@@ -55,6 +64,10 @@ export const annotateGame = defineCommand({
|
|
|
55
64
|
throw new Error(`Give ${move} a badge, a note, or both: arrows and squares go with them. Nothing changed.`);
|
|
56
65
|
}
|
|
57
66
|
const index = indexIn(studied.record, move, "notes");
|
|
67
|
+
if (corrected.includes(numbers[index])) {
|
|
68
|
+
skipped += 1;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
58
71
|
// A note alone on a move of the book keeps its book badge: a fact Chesspal put there, not the AI's judgment.
|
|
59
72
|
const kept = badge ?? (reviewOf(moves[index].comment)?.badge === "book" ? "book" : undefined);
|
|
60
73
|
moves[index] = reviewed(moves[index], { ...(kept === undefined ? {} : { badge: kept }), ...(note === undefined ? {} : { note }), ...drawingsOf(arrows, squares) });
|
|
@@ -66,25 +79,25 @@ export const annotateGame = defineCommand({
|
|
|
66
79
|
// Who reviewed it, as far as the host says it for certain (D-81): on the command line, or by a host that names no model over one that did, as it was.
|
|
67
80
|
const kept = caller === undefined || (caller.hostOnly === true && annotatorOf(studied.record) !== undefined);
|
|
68
81
|
const marked = { ...(kept ? studied.record : withAnnotator(studied.record, caller)), moves };
|
|
69
|
-
rewriteGame(
|
|
70
|
-
startAnalysis(
|
|
71
|
-
const numbers = moveNumbers(marked);
|
|
82
|
+
rewriteGame(folder, studied.name, summary === undefined && closing === undefined ? marked : withGameReview(marked, { ...(summary === undefined ? {} : { summary }), ...(closing === undefined ? {} : { closing }) }));
|
|
83
|
+
startAnalysis(folder, studied.name, context.now(), false, correction ? notes.map(({ move }) => numbers[indexIn(studied.record, move, "notes")]) : []);
|
|
72
84
|
const bookUnnoted = moves.flatMap((move, index) => {
|
|
73
85
|
const review = reviewOf(move.comment);
|
|
74
86
|
return review?.badge === "book" && review.note === undefined ? [numbers[index]] : [];
|
|
75
87
|
});
|
|
76
88
|
return {
|
|
77
89
|
game: studied.name,
|
|
78
|
-
marked: notes.length,
|
|
90
|
+
marked: notes.length - skipped,
|
|
79
91
|
summary: summary !== undefined,
|
|
80
92
|
closing: closing !== undefined,
|
|
81
|
-
bookUnnoted,
|
|
93
|
+
bookUnnoted: correction ? [] : bookUnnoted,
|
|
82
94
|
estimated: estimates.length,
|
|
83
95
|
// Asked every time, of an older review too: an analyst whose instructions predate the estimates learns of them here.
|
|
84
|
-
unestimated: unestimatedMoves(marked, numbers),
|
|
96
|
+
unestimated: correction ? [] : unestimatedMoves(marked, numbers),
|
|
97
|
+
left: [...corrected],
|
|
85
98
|
};
|
|
86
99
|
},
|
|
87
|
-
format: ({ game, marked, summary, closing, bookUnnoted, estimated, unestimated }) => {
|
|
100
|
+
format: ({ game, marked, summary, closing, bookUnnoted, estimated, unestimated, left }) => {
|
|
88
101
|
const kept = [
|
|
89
102
|
marked === 0 ? undefined : `${marked} ${marked === 1 ? "move" : "moves"} marked`,
|
|
90
103
|
estimated === 0 ? undefined : `who is ahead after ${estimated} ${estimated === 1 ? "move" : "moves"}`,
|
|
@@ -100,7 +113,12 @@ export const annotateGame = defineCommand({
|
|
|
100
113
|
const shown = unestimated.slice(0, unestimatedShown).join(" ");
|
|
101
114
|
const more = unestimated.length > unestimatedShown ? ` and ${unestimated.length - unestimatedShown} more` : "";
|
|
102
115
|
const ahead = unestimated.length === 0 ? [] : [`Who is ahead is still missing after ${shown}${more}: give it with ahead, for the page's bar and chart.`];
|
|
103
|
-
|
|
116
|
+
const one = left.length === 1;
|
|
117
|
+
const corrected = left.length === 0
|
|
118
|
+
? []
|
|
119
|
+
: [`${left.join(" ")} ${one ? "keeps the note" : "keep the notes"} the AI wrote with the player: leave ${one ? "it" : "them"}, and don't contradict ${one ? "it" : "them"} in your summary${summary ? "; if this one does, give it again" : ""}.`];
|
|
120
|
+
const head = kept.length === 0 ? `Nothing new kept in ${game}.` : `Kept in ${game}: ${[kept.slice(0, -1).join(", "), kept.at(-1)].filter((part) => part !== "").join(" and ")}. The board page shows them.`;
|
|
121
|
+
return [head, ...corrected, ...book, ...ahead].join("\n");
|
|
104
122
|
},
|
|
105
123
|
});
|
|
106
124
|
/** The moves without who is ahead after them, as analyse_game numbers them: all but the last when the board ended the game there, which says it. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Side } from "../core/index.ts";
|
|
2
2
|
import { type GameSummary, type LastMove, type MovePlayed } from "./game-summary.ts";
|
|
3
|
+
import type { Written } from "./say.ts";
|
|
3
4
|
/** Where the game stands: the AI's position to decide on, with its last move unless the lines before named it; the player's turn; or how it ended. */
|
|
4
5
|
export declare function positionLines(game: GameSummary, withLastMove?: boolean): string[];
|
|
5
6
|
/** The head of a view: where the game stands, the last move unless the lines before named it, the opening, the material as the player sees it, and the FEN. */
|
|
@@ -14,14 +15,17 @@ export declare function shownMove(move: string): string;
|
|
|
14
15
|
/** The lines after a move, which the line before named: its note, where the game stands, a warning. closingLines end them. */
|
|
15
16
|
export declare function moveLines({ note, game, warning }: MovePlayed): string[];
|
|
16
17
|
/** The last lines of a tool's answer: the player's words from the board page, then the next step, which stays the last line. */
|
|
17
|
-
export declare function closingLines(game: GameSummary | undefined, words?: readonly
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
export declare function closingLines(game: GameSummary | undefined, words?: readonly Written[], pageClosed?: boolean): string[];
|
|
19
|
+
/**
|
|
20
|
+
* The player's words from the board page, one line each, in analysis with the move the page showed (D-101): lines they broke are joined, so no line of
|
|
21
|
+
* theirs reads as one of Chesspal's.
|
|
22
|
+
*/
|
|
23
|
+
export declare function wordLines(words: readonly Written[]): string[];
|
|
20
24
|
/**
|
|
21
25
|
* What the AI does next: the one line every host reads, even where the server's instructions are not shown. Words of the player's ask for an answer. With the
|
|
22
26
|
* board page open in no browser, a new game starts there, never in the chat.
|
|
23
27
|
*/
|
|
24
|
-
export declare function nextStep(game: GameSummary | undefined, words?: readonly
|
|
28
|
+
export declare function nextStep(game: GameSummary | undefined, words?: readonly Written[], pageClosed?: boolean): string;
|
|
25
29
|
/** How a new game starts: the player chooses it in the board page's window, which new_game without choices opens (D-85). */
|
|
26
30
|
export declare const newGameStep = "call new_game without choices: it opens the board page's New game window, where the player chooses the colour, the clock and the level.";
|
|
27
31
|
/** Who plays what, from the AI's side. */
|
|
@@ -13,7 +13,7 @@ export function positionLines(game, withLastMove = true) {
|
|
|
13
13
|
}
|
|
14
14
|
return [
|
|
15
15
|
...headLines(game, withLastMove),
|
|
16
|
-
...(game.facts === undefined ? [] : (
|
|
16
|
+
...(game.facts === undefined ? [] : viewLines(game.facts, game)),
|
|
17
17
|
...(game.book === undefined ? [] : [`Book moves, most named lines first: ${game.book.join(", ")}`]),
|
|
18
18
|
...(game.plan === undefined ? [] : [`Plan: ${game.plan}`]),
|
|
19
19
|
...(game.thought === undefined ? [] : [thoughtLine(game.thought, game.level)]),
|
|
@@ -21,6 +21,14 @@ export function positionLines(game, withLastMove = true) {
|
|
|
21
21
|
...(game.helps === "coached" ? [`${helpsLine} ${coachLine}`] : game.helps === "on" ? [helpsLine] : game.helps === "off now" ? [helpsOffLine] : []),
|
|
22
22
|
];
|
|
23
23
|
}
|
|
24
|
+
/** The view of the AI's position as its level sees it (D-60, D-100): the tactical facts from the club level, the other side's chances from the strong level. */
|
|
25
|
+
function viewLines(facts, game) {
|
|
26
|
+
if (!sees(game.level, "tactics")) {
|
|
27
|
+
return boardView(facts, game.turn);
|
|
28
|
+
}
|
|
29
|
+
const possible = game.possibilities === undefined ? undefined : { possibilities: game.possibilities, threats: game.threats, strong: sees(game.level, "strategy") };
|
|
30
|
+
return compactView(facts, game.turn, possible);
|
|
31
|
+
}
|
|
24
32
|
/** The helps turned on: the AI may teach as the analyst does (D-62, D-78), never playing for the player. */
|
|
25
33
|
const helpsLine = "Helps on: the player wants to learn. Tell your intentions, never their best move; draw arrows and squares to show what you say, and write up to "
|
|
26
34
|
+ `${talkLimits.aiHelps} characters.`;
|
|
@@ -63,9 +71,12 @@ export function moveLines({ note, game, warning }) {
|
|
|
63
71
|
export function closingLines(game, words = [], pageClosed = false) {
|
|
64
72
|
return [...wordLines(words), nextStep(game, words, pageClosed)];
|
|
65
73
|
}
|
|
66
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* The player's words from the board page, one line each, in analysis with the move the page showed (D-101): lines they broke are joined, so no line of
|
|
76
|
+
* theirs reads as one of Chesspal's.
|
|
77
|
+
*/
|
|
67
78
|
export function wordLines(words) {
|
|
68
|
-
return words.map((text) => `Player wrote on the board page: «${text.replace(/\s+/g, " ")}»`);
|
|
79
|
+
return words.map(({ text, at }) => `Player wrote on the board page${at === undefined ? "" : `, at ${at}`}: «${text.replace(/\s+/g, " ")}»`);
|
|
69
80
|
}
|
|
70
81
|
/**
|
|
71
82
|
* What the AI does next: the one line every host reads, even where the server's instructions are not shown. Words of the player's ask for an answer. With the
|
|
@@ -73,6 +84,11 @@ export function wordLines(words) {
|
|
|
73
84
|
*/
|
|
74
85
|
export function nextStep(game, words = [], pageClosed = false) {
|
|
75
86
|
const step = pageClosed ? closedPageGameStep : newGameStep;
|
|
87
|
+
// Words written on a game in analysis: the chat answers them there, and corrects the review where they show it wrong (D-101).
|
|
88
|
+
if (words.some((word) => word.at !== undefined) && (game === undefined || game.end !== undefined)) {
|
|
89
|
+
return "Next: answer the player with ai_say, about the move they were looking at. When they show you something the review missed, first correct that move's "
|
|
90
|
+
+ "note with annotate_game, that move only.";
|
|
91
|
+
}
|
|
76
92
|
if (game === undefined) {
|
|
77
93
|
return words.length === 0 ? `Next: when the player wants a game, ${step}` : `Next: answer the player with ai_say. When they want a game, ${step}`;
|
|
78
94
|
}
|
package/dist/commands/coach.js
CHANGED
|
@@ -202,7 +202,7 @@ function judgedLines({ game }, ply, player) {
|
|
|
202
202
|
`Their move to judge: ${lastMoves(played, 1)}. The position they played it in:`,
|
|
203
203
|
`FEN: ${before.position.fen}`,
|
|
204
204
|
`Material: ${materialText(materialOf(before.position.fen))}`,
|
|
205
|
-
...compactView(before.facts, player),
|
|
205
|
+
...compactView(before.facts, player, { possibilities: before.possibilities, threats: before.threats, strong: true }),
|
|
206
206
|
...(book.length === 0 ? [] : [`Book moves there, most named lines first: ${book.join(", ")}`]),
|
|
207
207
|
];
|
|
208
208
|
if (ply === record.moves.length) {
|
|
@@ -229,7 +229,7 @@ function nowLines(current, folder, player) {
|
|
|
229
229
|
return [
|
|
230
230
|
"Now, for who is ahead and your hint:",
|
|
231
231
|
`FEN: ${game.position.fen}`,
|
|
232
|
-
...compactView(game.facts, player),
|
|
232
|
+
...compactView(game.facts, player, { possibilities: game.possibilities, threats: game.threats, strong: true }),
|
|
233
233
|
...profileLines(game.profile),
|
|
234
234
|
...(lead === undefined ? [] : [`Who you saw ahead on their turn before: ${leadText(lead)}, ${aheadWords(lead)}`]),
|
|
235
235
|
...(hint === undefined || hint.position !== plies - 2 ? [] : [`Your hint on their turn before: «${hint.text}»`]),
|
|
@@ -43,6 +43,8 @@ export interface Context {
|
|
|
43
43
|
caller?: () => Promise<Caller | undefined>;
|
|
44
44
|
/** The agent that makes this call of a tool it shares with the chat, as the analyst (D-89); none for the chat, or when it cannot be told. */
|
|
45
45
|
agent?: string;
|
|
46
|
+
/** The chat makes this call of a tool it shares with an agent, as the host tells it: a caller that cannot be told is not the chat here (Federico). */
|
|
47
|
+
chat?: true;
|
|
46
48
|
}
|
|
47
49
|
/** Who made a tool call: the model, else the host's name, never what the AI says of itself; the effort only when the host gives it. */
|
|
48
50
|
export interface Caller {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// delete, never the AI.
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { isReviewed, unreviewed } from "../core/index.js";
|
|
5
|
-
import { forgetCoach, gameInAnalysis, isOverOnTable, rewriteGame, startAnalysis } from "../games/index.js";
|
|
5
|
+
import { endAnalysis, forgetCoach, gameInAnalysis, isOverOnTable, rewriteGame, startAnalysis } from "../games/index.js";
|
|
6
6
|
import { gameFolder } from "../profiles/index.js";
|
|
7
7
|
import { defineCommand } from "./registry.js";
|
|
8
8
|
import { namedGame } from "./study.js";
|
|
@@ -24,8 +24,9 @@ export const deleteReview = defineCommand({
|
|
|
24
24
|
if (isOverOnTable(folder, name)) {
|
|
25
25
|
forgetCoach(folder);
|
|
26
26
|
}
|
|
27
|
-
// The page showing the game hears of it, and shows it without its review.
|
|
27
|
+
// The page showing the game hears of it, and shows it without its review; the chat's corrections go with it.
|
|
28
28
|
if (gameInAnalysis(folder)?.game === name) {
|
|
29
|
+
endAnalysis(folder);
|
|
29
30
|
startAnalysis(folder, name, context.now());
|
|
30
31
|
}
|
|
31
32
|
return { game: name, reviewed: isReviewed(record) };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type AiLevel, type Facts, type GameRecord, type MoodNow, type Side, type Square } from "../core/index.ts";
|
|
1
|
+
import { type AiLevel, type Chances, type Facts, type GameRecord, type MoodNow, type Possibilities, type Side, type Square } from "../core/index.ts";
|
|
2
2
|
import { type CurrentGame, type Speaker, type Thought } from "../games/index.ts";
|
|
3
3
|
import { type ClockSummary } from "./clock.ts";
|
|
4
|
+
import type { Written } from "./say.ts";
|
|
4
5
|
/** The current game, as the commands give it. */
|
|
5
6
|
export interface GameSummary {
|
|
6
7
|
ai: Side;
|
|
@@ -17,6 +18,10 @@ export interface GameSummary {
|
|
|
17
18
|
opening?: string;
|
|
18
19
|
/** When the AI is to move: the facts of the position (D-26); below the club level, only the check and the legal moves of each piece are shown. */
|
|
19
20
|
facts?: Facts;
|
|
21
|
+
/** When the AI is to move, from the club level up: what the moves would do (D-100). */
|
|
22
|
+
possibilities?: Possibilities;
|
|
23
|
+
/** With the possibilities, out of check: what the player's moves would do if they were to move. */
|
|
24
|
+
threats?: Chances;
|
|
20
25
|
/** When the AI is to move at the master level, in the book: the moves it knows from the position (D-15, D-60). */
|
|
21
26
|
book?: readonly string[];
|
|
22
27
|
/** When the AI is to move: the plan it wrote last. */
|
|
@@ -58,7 +63,7 @@ export interface MovePlayed {
|
|
|
58
63
|
/** What went wrong after the move was saved, such as the archive not written. */
|
|
59
64
|
warning?: string;
|
|
60
65
|
/** What the player wrote on the board page that the AI has not been given yet. */
|
|
61
|
-
words?:
|
|
66
|
+
words?: Written[];
|
|
62
67
|
}
|
|
63
68
|
/** The game as the commands tell it; a game over with the name it has in the archive, when it is there. */
|
|
64
69
|
export declare function summaryOf(current: CurrentGame, now: Date, archive?: string): GameSummary;
|
|
@@ -90,6 +90,13 @@ function judgedBefore(current) {
|
|
|
90
90
|
function addAiFacts(summary, { game }) {
|
|
91
91
|
const plan = currentPlan(game.record);
|
|
92
92
|
summary.facts = game.facts;
|
|
93
|
+
if (sees(summary.level, "tactics")) {
|
|
94
|
+
summary.possibilities = game.possibilities;
|
|
95
|
+
const { threats } = game;
|
|
96
|
+
if (threats !== undefined) {
|
|
97
|
+
summary.threats = threats;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
93
100
|
if (sees(summary.level, "theory")) {
|
|
94
101
|
const book = knownMoves(summary.fen);
|
|
95
102
|
if (book.length > 0) {
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -13,6 +13,6 @@ export { playMove, PositionChanged, positionIn, samePosition } from "./play-move
|
|
|
13
13
|
export { chooseDrawnAvatar, GameGoingOn, newProfile, setAccounts, useProfile } from "./profiles.ts";
|
|
14
14
|
export { type AnyCommand, baseType, type Command, commandLineFieldProblem, inputProblems, isBoolean, isList, takesNumbers } from "./registry.ts";
|
|
15
15
|
export { say } from "./say.ts";
|
|
16
|
-
export { gameToStudy, namedGame } from "./study.ts";
|
|
16
|
+
export { gameToStudy, namedGame, shownPosition } from "./study.ts";
|
|
17
17
|
/** Every command of Chesspal, in the order the command line lists them and the MCP server offers them. */
|
|
18
18
|
export declare const commands: AnyCommand[];
|
package/dist/commands/index.js
CHANGED
|
@@ -34,7 +34,7 @@ export { playMove, PositionChanged, positionIn, samePosition } from "./play-move
|
|
|
34
34
|
export { chooseDrawnAvatar, GameGoingOn, newProfile, setAccounts, useProfile } from "./profiles.js";
|
|
35
35
|
export { baseType, commandLineFieldProblem, inputProblems, isBoolean, isList, takesNumbers } from "./registry.js";
|
|
36
36
|
export { say } from "./say.js";
|
|
37
|
-
export { gameToStudy, namedGame } from "./study.js";
|
|
37
|
+
export { gameToStudy, namedGame, shownPosition } from "./study.js";
|
|
38
38
|
/** Every command of Chesspal, in the order the command line lists them and the MCP server offers them. */
|
|
39
39
|
export const commands = [
|
|
40
40
|
init,
|
package/dist/commands/look.d.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { type Facts, type Side } from "../core/index.ts";
|
|
2
|
+
import { type Chances, type Facts, type Possibilities, type Side } from "../core/index.ts";
|
|
3
3
|
import { type GameSummary } from "./game-summary.ts";
|
|
4
|
-
|
|
4
|
+
import { type Written } from "./say.ts";
|
|
5
|
+
/** The game with the facts, the possibilities and the threats of its position, whoever is to move. */
|
|
5
6
|
type Viewed = GameSummary & {
|
|
6
7
|
facts: Facts;
|
|
8
|
+
possibilities: Possibilities;
|
|
7
9
|
};
|
|
8
|
-
/** A position of a game studied (D-68): where it stands,
|
|
10
|
+
/** A position of a game studied (D-68): where it stands, its facts, its possibilities and the threats. */
|
|
9
11
|
interface StudyView {
|
|
10
12
|
where: string;
|
|
11
13
|
turn: Side;
|
|
12
14
|
fen: string;
|
|
13
15
|
facts: Facts;
|
|
16
|
+
possibilities: Possibilities;
|
|
17
|
+
threats: Chances | undefined;
|
|
14
18
|
}
|
|
15
19
|
/** The whole view (D-26), when the compact one after the player's move is not enough; or the board for the player, where the host does not show it (D-36). */
|
|
16
20
|
export declare const look: import("./registry.ts").Command<z.ZodObject<{
|
|
@@ -23,9 +27,9 @@ export declare const look: import("./registry.ts").Command<z.ZodObject<{
|
|
|
23
27
|
board: string;
|
|
24
28
|
} | {
|
|
25
29
|
game: Viewed;
|
|
26
|
-
words:
|
|
30
|
+
words: Written[];
|
|
27
31
|
} | {
|
|
28
32
|
study: StudyView;
|
|
29
|
-
words:
|
|
33
|
+
words: Written[];
|
|
30
34
|
}>;
|
|
31
35
|
export {};
|
package/dist/commands/look.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { boardText, materialOf } from "../core/index.js";
|
|
2
|
+
import { boardText, materialOf, sees } from "../core/index.js";
|
|
3
3
|
import { analystAgent } from "../host-files/index.js";
|
|
4
4
|
import { gameFolder } from "../profiles/index.js";
|
|
5
5
|
import { headLines, wordLines } from "./answer-lines.js";
|
|
@@ -8,7 +8,7 @@ import { summaryOf } from "./game-summary.js";
|
|
|
8
8
|
import { refuseUnseen } from "./levels.js";
|
|
9
9
|
import { defineCommand } from "./registry.js";
|
|
10
10
|
import { newWords } from "./say.js";
|
|
11
|
-
import {
|
|
11
|
+
import { gameShown, positionAfter, positionLine, studyInput } from "./study.js";
|
|
12
12
|
import { fullView, materialText } from "./view.js";
|
|
13
13
|
/** The whole view (D-26), when the compact one after the player's move is not enough; or the board for the player, where the host does not show it (D-36). */
|
|
14
14
|
export const look = defineCommand({
|
|
@@ -34,31 +34,41 @@ export const look = defineCommand({
|
|
|
34
34
|
if (only !== "board") {
|
|
35
35
|
refuseUnseen(current, "tactics", 'look without only: "board"');
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
if (only === "board") {
|
|
38
|
+
return { board: boardText(current.game.position.fen) };
|
|
39
|
+
}
|
|
40
|
+
const { threats } = current.game;
|
|
41
|
+
const viewed = { ...summaryOf(current, context.now()), facts: current.game.facts, possibilities: current.game.possibilities };
|
|
42
|
+
return { game: threats === undefined ? viewed : { ...viewed, threats }, words: newWords(context) };
|
|
38
43
|
},
|
|
39
44
|
format: (look) => {
|
|
40
45
|
if ("board" in look) {
|
|
41
46
|
return look.board;
|
|
42
47
|
}
|
|
43
|
-
return [...("game" in look ?
|
|
48
|
+
return [...("game" in look ? gameLines(look.game) : studyLines(look.study)), ...wordLines(look.words)].join("\n");
|
|
44
49
|
},
|
|
45
50
|
});
|
|
46
51
|
/** A position of a game over, as the view of the game going on gives it. */
|
|
47
52
|
function studyLook(context, only, name, move) {
|
|
48
|
-
const studied =
|
|
53
|
+
const studied = gameShown(context, name);
|
|
49
54
|
const at = positionAfter(studied.record, move);
|
|
50
55
|
const { position } = at.game;
|
|
51
56
|
if (only === "board") {
|
|
52
57
|
return { board: boardText(position.fen) };
|
|
53
58
|
}
|
|
54
|
-
|
|
59
|
+
const view = { where: positionLine(at, studied.name), turn: position.turn, fen: position.fen, facts: at.game.facts, possibilities: at.game.possibilities, threats: at.game.threats };
|
|
60
|
+
return { study: view, words: newWords(context) };
|
|
61
|
+
}
|
|
62
|
+
/** The game going on: its head and the whole view, with the other side's chances from the strong level (D-100). */
|
|
63
|
+
function gameLines(game) {
|
|
64
|
+
return [...headLines(game), ...fullView(game.facts, game.turn, { possibilities: game.possibilities, threats: game.threats, strong: sees(game.level, "strategy") })];
|
|
55
65
|
}
|
|
56
|
-
/** Where the position stands, the material, the FEN, and the whole view. */
|
|
57
|
-
function studyLines({ where, turn, fen, facts }) {
|
|
66
|
+
/** Where the position stands, the material, the FEN, and the whole view, with every possibility, as the analyst sees it. */
|
|
67
|
+
function studyLines({ where, turn, fen, facts, possibilities, threats }) {
|
|
58
68
|
return [
|
|
59
69
|
where,
|
|
60
70
|
`Material: ${materialText(materialOf(fen))}`,
|
|
61
71
|
`FEN: ${fen}`,
|
|
62
|
-
...fullView(facts, turn),
|
|
72
|
+
...fullView(facts, turn, { possibilities, threats, strong: true }),
|
|
63
73
|
];
|
|
64
74
|
}
|
package/dist/commands/ponder.js
CHANGED
|
@@ -156,7 +156,7 @@ function startOf(current) {
|
|
|
156
156
|
const line = [
|
|
157
157
|
`Position ${game.record.moves.length}: the player (${colourName(player)}) to move, move ${game.position.moveNumber}; you play ${colourName(ai)} ${levelText(level)}.`,
|
|
158
158
|
`FEN: ${game.position.fen}`,
|
|
159
|
-
...(sees(level, "tactics") ? compactView : boardView
|
|
159
|
+
...(sees(level, "tactics") ? compactView(game.facts, player, { possibilities: game.possibilities, threats: game.threats, strong: sees(level, "strategy") }) : boardView(game.facts, player)),
|
|
160
160
|
].join("\n");
|
|
161
161
|
const said = talk.slice(-3).map((spoken) => `${spoken.who === "ai" ? "You" : "Player"}: «${spoken.text.replace(/\s+/g, " ")}»`);
|
|
162
162
|
return {
|
|
@@ -5,7 +5,7 @@ import { gameFolder } from "../profiles/index.js";
|
|
|
5
5
|
import { gameAt } from "./clock.js";
|
|
6
6
|
import { refuseUnseen } from "./levels.js";
|
|
7
7
|
import { defineCommand } from "./registry.js";
|
|
8
|
-
import {
|
|
8
|
+
import { gameShown, positionAfter, positionLine, studyInput } from "./study.js";
|
|
9
9
|
import { colourName, materialText } from "./view.js";
|
|
10
10
|
/** The positional profile of the game in progress (D-19), or of any position of a game over (D-68): separate facts, never a score. */
|
|
11
11
|
export const position = defineCommand({
|
|
@@ -21,7 +21,7 @@ export const position = defineCommand({
|
|
|
21
21
|
sharedWith: [analystAgent],
|
|
22
22
|
run: ({ game, move }, context) => {
|
|
23
23
|
if (game !== undefined || move !== undefined) {
|
|
24
|
-
const studied =
|
|
24
|
+
const studied = gameShown(context, game);
|
|
25
25
|
const at = positionAfter(studied.record, move);
|
|
26
26
|
return { ...at.game.profile, where: positionLine(at, studied.name) };
|
|
27
27
|
}
|
package/dist/commands/say.d.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { type Mood } from "../core/index.ts";
|
|
2
|
-
import { type Sayer, type Talk, type Words } from "../games/index.ts";
|
|
2
|
+
import { type Sayer, type Shown, type Talk, type Words } from "../games/index.ts";
|
|
3
3
|
import type { Context } from "./context.ts";
|
|
4
4
|
/**
|
|
5
5
|
* Adds a line to the talk of the current game, going on or just over, and saves it; the AI's mood once the game is over, which the archive keeps too (D-70).
|
|
6
6
|
* With no game the line goes in the talk before it (D-77). A line refused changes nothing: it throws, in words for whoever spoke.
|
|
7
7
|
*/
|
|
8
|
-
export declare function say(folder: string, who: Sayer, text: string, at: Date, mood?: Mood): Talk;
|
|
8
|
+
export declare function say(folder: string, who: Sayer, text: string, at: Date, mood?: Mood, shown?: Shown): Talk;
|
|
9
|
+
/** Words of the player's as the AI is given them: their text, and in analysis the move the board page showed as they wrote, 52...♚f6 (D-101). */
|
|
10
|
+
export interface Written {
|
|
11
|
+
readonly text: string;
|
|
12
|
+
readonly at?: string;
|
|
13
|
+
}
|
|
9
14
|
/** What the player wrote on the board page that the AI has not been given in this chat, given now: each game tool gives it once; never to an agent (D-89). */
|
|
10
|
-
export declare function newWords(context: Context):
|
|
15
|
+
export declare function newWords(context: Context): Written[];
|
|
11
16
|
/** Every word of the player's the AI has not answered, given again: status gives them all, for a chat that lost them in a summary. */
|
|
12
|
-
export declare function unansweredWords(context: Context):
|
|
17
|
+
export declare function unansweredWords(context: Context): Written[];
|
|
13
18
|
/** What the player wrote on the board page that the AI has not been given in this chat, still to give: the AI's move would count as its answer. */
|
|
14
19
|
export declare function waitingWords(context: Context, given?: ReadonlySet<string>): Words[];
|
|
15
|
-
/** Gives the words to the AI: from now on they count as given in this chat. Their text. */
|
|
16
|
-
export declare function give(context: Context, words: readonly Words[]):
|
|
20
|
+
/** Gives the words to the AI: from now on they count as given in this chat. Their text, with the move shown in analysis. */
|
|
21
|
+
export declare function give(context: Context, words: readonly Words[]): Written[];
|
package/dist/commands/say.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
// The talk on the board page (D-51): a line of the player's or the AI's, saved with the game, and the player's words the AI has not been given yet.
|
|
2
2
|
import { endWithMood } from "../core/index.js";
|
|
3
|
-
import { archiveEnded, endOf, loadGame, loadTableTalk, saveGame, saveTableTalk, talkLimits, talkLine, unheardWords } from "../games/index.js";
|
|
3
|
+
import { archiveEnded, endOf, gameInAnalysis, loadGame, loadTableTalk, saveGame, saveTableTalk, talkLimits, talkLine, unheardWords } from "../games/index.js";
|
|
4
4
|
import { gameFolder, helpsIn } from "../profiles/index.js";
|
|
5
|
+
import { shownMove } from "./answer-lines.js";
|
|
5
6
|
import { gameAt } from "./clock.js";
|
|
6
7
|
/**
|
|
7
8
|
* Adds a line to the talk of the current game, going on or just over, and saves it; the AI's mood once the game is over, which the archive keeps too (D-70).
|
|
8
9
|
* With no game the line goes in the talk before it (D-77). A line refused changes nothing: it throws, in words for whoever spoke.
|
|
9
10
|
*/
|
|
10
|
-
export function say(folder, who, text, at, mood) {
|
|
11
|
+
export function say(folder, who, text, at, mood, shown) {
|
|
11
12
|
const current = gameAt(folder, at);
|
|
12
13
|
if (current === undefined) {
|
|
13
|
-
return sayAtTable(folder, who, text, at, mood);
|
|
14
|
+
return sayAtTable(folder, who, text, at, mood, shown);
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
const going = endOf(current.game) === undefined;
|
|
17
|
+
if (mood !== undefined && going) {
|
|
16
18
|
throw new Error("While the game goes on your mood comes with ai_move: say it without mood. Nothing changed.");
|
|
17
19
|
}
|
|
18
|
-
// With the helps on the AI writes as long as the analyst (D-78).
|
|
19
|
-
current.talk.push(talkLine(current, who, text, at, undefined, aiMost(folder, who, text)));
|
|
20
|
+
// With the helps on the AI writes as long as the analyst (D-78), and once the game is over as long, to talk it over (D-101).
|
|
21
|
+
current.talk.push(talkLine(current, who, text, at, undefined, aiMost(folder, who, text, going), shown));
|
|
20
22
|
if (mood !== undefined) {
|
|
21
23
|
endWithMood(current.game, mood);
|
|
22
24
|
}
|
|
@@ -45,7 +47,7 @@ export function waitingWords(context, given = context.given) {
|
|
|
45
47
|
return [];
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
|
-
/** Gives the words to the AI: from now on they count as given in this chat. Their text. */
|
|
50
|
+
/** Gives the words to the AI: from now on they count as given in this chat. Their text, with the move shown in analysis. */
|
|
49
51
|
export function give(context, words) {
|
|
50
52
|
const before = context.given.size;
|
|
51
53
|
for (const { key } of words) {
|
|
@@ -54,14 +56,19 @@ export function give(context, words) {
|
|
|
54
56
|
if (context.given.size > before) {
|
|
55
57
|
context.heard?.();
|
|
56
58
|
}
|
|
57
|
-
|
|
59
|
+
const analysed = words.some((word) => word.shown !== undefined) ? gameInAnalysis(gameFolder(context.home()))?.game : undefined;
|
|
60
|
+
return words.map(({ text, shown }) => shown === undefined ? { text } : { text, at: placeOf(shown, analysed) });
|
|
58
61
|
}
|
|
59
|
-
/**
|
|
60
|
-
function
|
|
62
|
+
/** Where the page stood: the move shown, as the player saw it, or the start; with the game's name when the page shows another now. */
|
|
63
|
+
function placeOf({ game, move }, analysed) {
|
|
64
|
+
return `${move === undefined ? "the start" : shownMove(move)}${game === analysed ? "" : ` of ${game}`}`;
|
|
65
|
+
}
|
|
66
|
+
/** The most a line of the AI's may hold in the games of `folder`: an analyst's note with the helps on (D-78), or with no game going on (D-101); a longer one is refused. */
|
|
67
|
+
function aiMost(folder, who, text, going) {
|
|
61
68
|
if (who !== "ai") {
|
|
62
69
|
return talkLimits.ai;
|
|
63
70
|
}
|
|
64
|
-
if (helpsIn(folder)) {
|
|
71
|
+
if (!going || helpsIn(folder)) {
|
|
65
72
|
return talkLimits.aiHelps;
|
|
66
73
|
}
|
|
67
74
|
if (text.trim().length > talkLimits.ai) {
|
|
@@ -70,7 +77,7 @@ function aiMost(folder, who, text) {
|
|
|
70
77
|
return talkLimits.ai;
|
|
71
78
|
}
|
|
72
79
|
/** A line of the talk with no game, which starts it the first time; no mood, which only the end of a game takes. */
|
|
73
|
-
function sayAtTable(folder, who, text, at, mood) {
|
|
80
|
+
function sayAtTable(folder, who, text, at, mood, shown) {
|
|
74
81
|
if (who === "agent") {
|
|
75
82
|
throw new Error("There is no game to think on. Nothing changed.");
|
|
76
83
|
}
|
|
@@ -78,7 +85,8 @@ function sayAtTable(folder, who, text, at, mood) {
|
|
|
78
85
|
throw new Error("There is no game: a mood goes with a game's end. Say it without mood. Nothing changed.");
|
|
79
86
|
}
|
|
80
87
|
const before = loadTableTalk(folder);
|
|
81
|
-
const
|
|
88
|
+
const line = talkLine(before, who, text, at, undefined, aiMost(folder, who, text, false), shown);
|
|
89
|
+
const table = { started: before.started === "" ? at.toISOString() : before.started, talk: [...before.talk, line] };
|
|
82
90
|
saveTableTalk(folder, table);
|
|
83
91
|
return table;
|
|
84
92
|
}
|