chesspal 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cli/cli.js +17 -4
- package/dist/cli/command-line.js +22 -12
- package/dist/cli/icon.d.ts +2 -0
- package/dist/cli/icon.js +11 -1
- package/dist/cli/prompt-hook.d.ts +17 -0
- package/dist/cli/prompt-hook.js +100 -0
- package/dist/commands/analysis/analyse.d.ts +2 -2
- package/dist/commands/analysis/analyse.js +10 -16
- package/dist/commands/analysis/annotate.d.ts +8 -0
- package/dist/commands/analysis/annotate.js +19 -3
- package/dist/commands/answers/agents.d.ts +17 -7
- package/dist/commands/answers/agents.js +46 -16
- package/dist/commands/answers/clock.js +2 -1
- package/dist/commands/answers/drawings.js +6 -2
- package/dist/commands/answers/index.d.ts +4 -3
- package/dist/commands/answers/index.js +4 -3
- package/dist/commands/answers/lines.d.ts +5 -3
- package/dist/commands/answers/lines.js +17 -9
- package/dist/commands/answers/opening-book.d.ts +13 -7
- package/dist/commands/answers/opening-book.js +34 -38
- package/dist/commands/answers/opening-tags.d.ts +4 -0
- package/dist/commands/answers/opening-tags.js +11 -0
- package/dist/commands/answers/play-move.d.ts +3 -1
- package/dist/commands/answers/play-move.js +9 -6
- package/dist/commands/answers/say.js +2 -1
- package/dist/commands/answers/summary.d.ts +7 -3
- package/dist/commands/answers/summary.js +16 -5
- package/dist/commands/answers/view.d.ts +1 -1
- package/dist/commands/answers/view.js +2 -2
- package/dist/commands/game/ai-move.js +4 -4
- package/dist/commands/game/coach.js +7 -6
- package/dist/commands/game/end.js +2 -2
- package/dist/commands/game/export.d.ts +1 -1
- package/dist/commands/game/index.d.ts +1 -1
- package/dist/commands/game/index.js +1 -1
- package/dist/commands/game/look.d.ts +7 -3
- package/dist/commands/game/look.js +9 -4
- package/dist/commands/game/new.js +42 -12
- package/dist/commands/game/player-move.d.ts +16 -0
- package/dist/commands/game/player-move.js +69 -19
- package/dist/commands/game/ponder.js +7 -4
- package/dist/commands/game/position.d.ts +2 -1
- package/dist/commands/game/position.js +9 -4
- package/dist/commands/game/status.d.ts +4 -2
- package/dist/commands/game/status.js +24 -14
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/index.js +3 -3
- package/dist/commands/profiles/past-games.d.ts +7 -2
- package/dist/commands/profiles/past-games.js +45 -18
- package/dist/commands/registry/context.d.ts +2 -0
- package/dist/commands/registry/context.js +5 -0
- package/dist/commands/registry/index.d.ts +1 -1
- package/dist/commands/registry/index.js +1 -1
- package/dist/commands/talk/ai-say.d.ts +1 -1
- package/dist/commands/talk/ai-say.js +7 -6
- package/dist/core/game.d.ts +13 -0
- package/dist/core/game.js +29 -3
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +3 -3
- package/dist/core/openings.d.ts +20 -6
- package/dist/core/openings.js +45 -22
- package/dist/core/pgn.d.ts +4 -0
- package/dist/core/pgn.js +35 -6
- package/dist/games/analysis.d.ts +7 -4
- package/dist/games/analysis.js +5 -3
- package/dist/games/archive.d.ts +25 -5
- package/dist/games/archive.js +52 -19
- package/dist/games/chat-start.d.ts +2 -2
- package/dist/games/chat-start.js +6 -1
- package/dist/games/current-game.d.ts +2 -2
- package/dist/games/current-game.js +3 -2
- package/dist/games/index.d.ts +3 -2
- package/dist/games/index.js +3 -2
- package/dist/games/position-shown.d.ts +24 -0
- package/dist/games/position-shown.js +51 -0
- package/dist/games/thoughts.d.ts +12 -4
- package/dist/games/thoughts.js +27 -10
- package/dist/home/index.d.ts +1 -0
- package/dist/home/index.js +1 -0
- package/dist/home/same-path.d.ts +7 -0
- package/dist/home/same-path.js +24 -0
- package/dist/host-files/host-files.d.ts +5 -0
- package/dist/host-files/host-files.js +21 -7
- package/dist/host-files/index.d.ts +1 -1
- package/dist/host-files/index.js +1 -1
- package/dist/page/app/anchor.js +15 -11
- package/dist/page/app/app.js +49 -14
- package/dist/page/app/served.js +30 -38
- package/dist/page/app/watch.js +97 -0
- package/dist/page/avatars/torretta.css +108 -0
- package/dist/page/base.css +1 -1
- package/dist/page/board/board.css +11 -8
- package/dist/page/board/marks.js +2 -2
- package/dist/page/board/pointer.js +2 -2
- package/dist/page/dialogs/analyses.js +5 -3
- package/dist/page/dialogs/dialogs.css +3 -1
- package/dist/page/dialogs/rows.js +72 -0
- package/dist/page/dialogs/site-games.js +3 -1
- package/dist/page/game/column.js +10 -8
- package/dist/page/game/game.css +137 -16
- package/dist/page/game/move-list.js +7 -5
- package/dist/page/game/plies.js +4 -0
- package/dist/page/game/review-progress.js +11 -0
- package/dist/page/guide/drawings/drawings.css +0 -1
- package/dist/page/guide/en.md +2 -2
- package/dist/page/guide/it.md +2 -2
- package/dist/page/helps/helps.css +5 -5
- package/dist/page/helps/helps.js +3 -181
- package/dist/page/helps/rows.js +16 -0
- package/dist/page/helps/strategy.js +105 -0
- package/dist/page/helps/tactics.js +69 -0
- package/dist/page/layout/header.js +49 -6
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/layout.css +49 -17
- package/dist/page/layout/window.js +45 -6
- package/dist/page/lead/lead.js +41 -4
- package/dist/page/legend/legend.css +4 -16
- package/dist/page/legend/legend.js +28 -15
- package/dist/page/profile/settings.js +1 -0
- package/dist/page/sounds/sounds.js +1 -1
- package/dist/page/style.css +1 -0
- package/dist/page/table/action.js +42 -0
- package/dist/page/table/table.css +57 -39
- package/dist/page/table/window.js +67 -67
- package/dist/page/talk/talk.css +52 -118
- package/dist/page/talk/talk.js +65 -20
- package/dist/page/talk/voice.js +92 -21
- package/dist/page/text.js +34 -8
- package/dist/page/theme.css +13 -0
- package/dist/profiles/profiles.js +5 -1
- package/dist/server/bell/bell.d.ts +24 -29
- package/dist/server/bell/bell.js +79 -133
- package/dist/server/bell/index.d.ts +1 -1
- package/dist/server/bell/index.js +2 -2
- package/dist/server/mcp/mcp.js +49 -7
- package/dist/server/page/analyses.js +15 -4
- package/dist/server/page/chat.d.ts +5 -1
- package/dist/server/page/chat.js +5 -1
- package/dist/server/page/files.js +3 -1
- package/dist/server/page/game.d.ts +5 -0
- package/dist/server/page/game.js +95 -15
- package/dist/server/page/http.d.ts +1 -1
- package/dist/server/page/page.d.ts +14 -9
- package/dist/server/page/page.js +111 -26
- package/dist/server/page/table.d.ts +40 -5
- package/dist/server/page/table.js +65 -25
- package/dist/server/page/update.d.ts +1 -1
- package/dist/server/page/update.js +4 -4
- package/dist/server/page/windows.d.ts +22 -0
- package/dist/server/page/windows.js +45 -0
- package/dist/server/ports/index.d.ts +1 -0
- package/dist/server/ports/index.js +2 -0
- package/dist/server/ports/ports.d.ts +78 -0
- package/dist/server/ports/ports.js +222 -0
- package/dist/server/table/browser.d.ts +22 -6
- package/dist/server/table/browser.js +98 -20
- package/dist/server/table/hosts/chats.d.ts +5 -0
- package/dist/server/table/hosts/chats.js +11 -4
- package/dist/server/table/hosts/hooks.d.ts +6 -0
- package/dist/server/table/hosts/hooks.js +38 -0
- package/dist/server/table/hosts/hosts.d.ts +13 -5
- package/dist/server/table/hosts/hosts.js +16 -10
- package/dist/server/table/hosts/index.d.ts +1 -0
- package/dist/server/table/hosts/index.js +1 -0
- package/dist/server/table/hosts/play.d.ts +7 -3
- package/dist/server/table/hosts/play.js +63 -9
- package/dist/server/table/icon/index.d.ts +2 -0
- package/dist/server/table/icon/index.js +16 -2
- package/dist/server/table/index.d.ts +2 -2
- package/dist/server/table/index.js +1 -1
- package/dist/server/table/launch.d.ts +5 -4
- package/dist/server/table/launch.js +18 -5
- package/dist/server/table/link.js +4 -1
- package/dist/server/table/table.d.ts +7 -0
- package/dist/server/table/table.js +40 -12
- package/dist/sites/index.d.ts +1 -1
- package/dist/sites/index.js +1 -1
- package/dist/sites/{chesscom.d.ts → ratings/chesscom.d.ts} +2 -2
- package/dist/sites/{chesscom.js → ratings/chesscom.js} +1 -1
- package/dist/sites/{choice.d.ts → ratings/choice.d.ts} +1 -1
- package/dist/sites/{choice.js → ratings/choice.js} +1 -1
- package/dist/sites/ratings/index.d.ts +1 -0
- package/dist/sites/ratings/index.js +2 -0
- package/dist/sites/{lichess.d.ts → ratings/lichess.d.ts} +2 -2
- package/dist/sites/{lichess.js → ratings/lichess.js} +1 -1
- package/dist/sites/{sites.d.ts → ratings/ratings.d.ts} +2 -2
- package/dist/sites/{sites.js → ratings/ratings.js} +1 -1
- package/instructions/opponent.md +1 -1
- package/instructions/start.md +1 -1
- package/package.json +1 -1
package/dist/server/page/game.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { analyseGame,
|
|
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
|
-
import { aiModelOf, archiveName, coachShown, coachWords, drawOfferOf, endOf, gameInAnalysis, heardLines, loadGame, loadTableTalk, playerPgn,
|
|
3
|
+
import { aiModelOf, archiveName, coachShown, coachWords, drawOfferOf, endOf, gameInAnalysis, GameMissing, heardLines, loadGame, loadTableTalk, playerPgn, ponderingUntil, rerunIfChanged, saveGame, startAnalysis, talkLimits, thoughtKey, wakesTheChat, WordsRefused, } from "../../games/index.js";
|
|
4
4
|
import { gameFolder, helpsIn } from "../../profiles/index.js";
|
|
5
5
|
import { version } from "../../version.js";
|
|
6
6
|
import { asTheChatSees } from "./chat.js";
|
|
7
|
+
import { tellWatchers } from "./events.js";
|
|
7
8
|
import { bodyOf, bytesOf, isJson, json, jsonOf, runRoute, send, textOf } from "./http.js";
|
|
8
9
|
import { profilesShown } from "./profiles.js";
|
|
9
|
-
import { chatToRelaunch, relaunchChat, tableShown } from "./table.js";
|
|
10
|
+
import { analystAtWork, chatToRelaunch, chatToWaitFor, refuseWaited, relaunchChat, tableShown } from "./table.js";
|
|
10
11
|
import { updateShown } from "./update.js";
|
|
11
12
|
/** Answers the page's requests about the game: true when the request was one. */
|
|
12
13
|
export function answerGame(request, response, url, served) {
|
|
@@ -26,6 +27,18 @@ export function chatCalls(served) {
|
|
|
26
27
|
rerunIfChanged(() => giveHeldTime(served, folder));
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Whether the AI's sign on the page is not the one the pages were last given: the ponder agent's every thought rewrites thoughts.json, and only the sign
|
|
32
|
+
* that comes or goes is news for them. True when the game cannot be read: they read it again.
|
|
33
|
+
*/
|
|
34
|
+
export function thinkingChanged(served) {
|
|
35
|
+
try {
|
|
36
|
+
return thinkingShown(served, loadGame(gameFolder(served.context.home()))).thinking !== thinkingSent.get(served);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
29
42
|
/** The AI's clock, held while nobody heard the player, starts now: the time it was held is not the AI's. */
|
|
30
43
|
function giveHeldTime(served, folder) {
|
|
31
44
|
const current = loadGame(folder);
|
|
@@ -111,19 +124,21 @@ async function messageFromPage(request, response, served) {
|
|
|
111
124
|
// 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
|
|
112
125
|
// analysis every line does: a chat that answered in its own window would not hear the next (Priya).
|
|
113
126
|
if (shown !== undefined || wakesTheChat(current)) {
|
|
114
|
-
|
|
127
|
+
// Words may come while the chat thinks on the AI's move, which they do not end.
|
|
128
|
+
served.bell.ring("game" in current && endOf(current.game) === undefined && current.game.position.turn === current.ai);
|
|
115
129
|
}
|
|
116
130
|
send(response, 200, json, JSON.stringify({ said: true }));
|
|
117
131
|
}
|
|
118
132
|
/**
|
|
119
133
|
* A new game from the page, with the sides, the level and the clock the player chose: the sides and the level they leave out are as in the game
|
|
120
134
|
* before, and no clock means a game without one. One going on goes to the archive unfinished, and the doorbell tells the chat, unless the table starts
|
|
121
|
-
* it again.
|
|
135
|
+
* it again. A chat the table holds that must be clear for it, and is at work, has the game wait with the choices kept until it has ended its answer (D-107).
|
|
122
136
|
*/
|
|
123
137
|
async function newGameFromPage(request, response, served) {
|
|
124
138
|
const asked = 'Send the new game as JSON: {"ai_colour": "black", "level": "club", "clock": "10+5"}, each one optional; no clock for a game without one.';
|
|
125
139
|
const body = isJson(request) ? jsonOf(await bodyOf(request)) : undefined;
|
|
126
140
|
let relaunching = false;
|
|
141
|
+
let chosen;
|
|
127
142
|
if (body === undefined) {
|
|
128
143
|
send(response, isJson(request) ? 400 : 415, json, JSON.stringify({ error: asked }));
|
|
129
144
|
return;
|
|
@@ -138,10 +153,17 @@ async function newGameFromPage(request, response, served) {
|
|
|
138
153
|
// The game starts from the page, open: its clocks wait for the player's Start there. A chat the table starts again is clean: no /clear is asked.
|
|
139
154
|
relaunching = chatToRelaunch(served);
|
|
140
155
|
const sight = relaunching ? served.context : asTheChatSees(served.context, served.chat);
|
|
156
|
+
chosen = input.data;
|
|
141
157
|
rerunIfChanged(() => startNewGame(input.data, { ...sight, pageOpen: () => true }, "page"));
|
|
142
158
|
}
|
|
143
159
|
catch (error) {
|
|
144
|
-
|
|
160
|
+
if (error instanceof ChatNotClear && chosen !== undefined && chatToWaitFor(served)) {
|
|
161
|
+
waitForChat(served, chosen);
|
|
162
|
+
send(response, 200, json, JSON.stringify({ waiting: true }));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
send(response, 409, json, JSON.stringify(refusalFrom(error)));
|
|
166
|
+
}
|
|
145
167
|
return;
|
|
146
168
|
}
|
|
147
169
|
// A seat lost meanwhile leaves the chat as it is: the doorbell tells it.
|
|
@@ -150,6 +172,34 @@ async function newGameFromPage(request, response, served) {
|
|
|
150
172
|
}
|
|
151
173
|
send(response, 200, json, JSON.stringify({ started: true }));
|
|
152
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* The new game waits, with the player's choices, for the chat at the table to end its answer; then it starts and the chat starts again, clean, for it, or
|
|
177
|
+
* one gone meanwhile hears the doorbell when another comes. It starts in the profile it was asked in: with another in use by then it is dropped. A game
|
|
178
|
+
* that cannot start leaves everything as it was, and the pages say why.
|
|
179
|
+
*/
|
|
180
|
+
function waitForChat(served, chosen) {
|
|
181
|
+
const askedIn = gameFolder(served.context.home());
|
|
182
|
+
const start = () => {
|
|
183
|
+
try {
|
|
184
|
+
if (gameFolder(served.context.home()) !== askedIn) {
|
|
185
|
+
refuseWaited(served, { error: "Another profile is in use since the new game was asked: ask for it again in this one.", refusal: "new-game-profile" });
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
rerunIfChanged(() => startNewGame(chosen, { ...served.context, pageOpen: () => true }, "page"));
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
refuseWaited(served, refusalFrom(error));
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
const relaunching = relaunchChat(served);
|
|
195
|
+
if (!relaunching) {
|
|
196
|
+
served.bell.ring();
|
|
197
|
+
}
|
|
198
|
+
return relaunching;
|
|
199
|
+
};
|
|
200
|
+
served.chatWait = { for: "new-game", start };
|
|
201
|
+
tellWatchers(served.watchers);
|
|
202
|
+
}
|
|
153
203
|
/** The player's draw from the page (D-65): an offer on their turn, which rings the doorbell, or their answer to the AI's, which rings it when accepted. */
|
|
154
204
|
async function drawFromPage(request, response, served) {
|
|
155
205
|
const asked = 'Send the draw as JSON: {"action": "offer"}, "accept" or "decline".';
|
|
@@ -233,6 +283,8 @@ async function studyFromPage(request, response, served) {
|
|
|
233
283
|
const last = body?.last === true;
|
|
234
284
|
// A game a link of the page opens is there to look at: the chat is not asked to go over it.
|
|
235
285
|
const look = body?.look === true;
|
|
286
|
+
// «Analyse again», the review just deleted: the chat starts the analyst without asking, even after one was stopped (Priya).
|
|
287
|
+
const redo = body?.redo === true;
|
|
236
288
|
if (pgn === undefined && game === undefined && !last) {
|
|
237
289
|
send(response, 400, json, JSON.stringify({ error: asked, refusal: "pgn-unreadable" }));
|
|
238
290
|
return;
|
|
@@ -249,10 +301,10 @@ async function studyFromPage(request, response, served) {
|
|
|
249
301
|
reviewed = isReviewed(opened.record);
|
|
250
302
|
brought = !reviewed && !look;
|
|
251
303
|
// A game not yet reviewed is marked as brought, for status to ask the AI to go over it; one reviewed is ready, and the chat sleeps on.
|
|
252
|
-
startAnalysis(gameFolder(served.context.home()), opened.name, served.context.now(), { brought });
|
|
304
|
+
startAnalysis(gameFolder(served.context.home()), opened.name, served.context.now(), { brought, redo });
|
|
253
305
|
}
|
|
254
306
|
catch (error) {
|
|
255
|
-
send(response, 400, json, JSON.stringify({ error: error.message, refusal:
|
|
307
|
+
send(response, 400, json, JSON.stringify({ error: error.message, refusal: refusalOfStudy(error) }));
|
|
256
308
|
return;
|
|
257
309
|
}
|
|
258
310
|
if (brought) {
|
|
@@ -289,13 +341,15 @@ function sendGame(response, served) {
|
|
|
289
341
|
name: archiveName(current),
|
|
290
342
|
talk,
|
|
291
343
|
// How many of its first moves are the book's: the page marks them while the game goes on, as the AI's review does after.
|
|
292
|
-
book: bookPlies(current.game
|
|
344
|
+
book: bookPlies(current.game),
|
|
293
345
|
...(current.clock === undefined ? {} : { clock: current.clock }),
|
|
294
346
|
...(offer === undefined ? {} : { drawOffer: offer }),
|
|
295
347
|
...(coach === undefined ? {} : { coach }),
|
|
296
348
|
};
|
|
297
349
|
const analysis = current === undefined || endOf(current.game) !== undefined ? analysisShown(served) : undefined;
|
|
298
350
|
const aiModel = aiModelOf(folder);
|
|
351
|
+
const thinking = thinkingShown(served, current);
|
|
352
|
+
thinkingSent.set(served, thinking.thinking);
|
|
299
353
|
const body = {
|
|
300
354
|
// The page loaded from another version loads again, its files and all (Federico).
|
|
301
355
|
version,
|
|
@@ -305,7 +359,7 @@ function sendGame(response, served) {
|
|
|
305
359
|
opponent: served.opponent(),
|
|
306
360
|
...(aiModel === undefined ? {} : { aiModel }),
|
|
307
361
|
listening: served.bell.listening,
|
|
308
|
-
...
|
|
362
|
+
...thinking,
|
|
309
363
|
...(served.outdated ? { outdated: true } : {}),
|
|
310
364
|
...updateShown(served),
|
|
311
365
|
...tableShown(served),
|
|
@@ -327,7 +381,21 @@ function thinkingShown(served, current) {
|
|
|
327
381
|
const onIt = served.bell.woke || (seen !== undefined && seen !== null && samePosition(seen, position));
|
|
328
382
|
return served.bell.inTurn && onIt ? { thinking: "move" } : {};
|
|
329
383
|
}
|
|
330
|
-
|
|
384
|
+
const until = ponderingUntil(gameFolder(served.context.home()), thoughtKey(current));
|
|
385
|
+
const left = until === undefined ? 0 : until.getTime() - served.context.now().getTime();
|
|
386
|
+
if (left <= 0) {
|
|
387
|
+
return {};
|
|
388
|
+
}
|
|
389
|
+
signGoesIn(served, left);
|
|
390
|
+
return { thinking: "ponder" };
|
|
391
|
+
}
|
|
392
|
+
// The AI's sign the pages were last given, one a table.
|
|
393
|
+
const thinkingSent = new WeakMap();
|
|
394
|
+
// The pages hear when the sign of an agent that stopped pondering without a word goes, which no file tells: one timer a table, the latest.
|
|
395
|
+
const signTimers = new WeakMap();
|
|
396
|
+
function signGoesIn(served, wait) {
|
|
397
|
+
clearTimeout(signTimers.get(served));
|
|
398
|
+
signTimers.set(served, setTimeout(() => tellWatchers(served.watchers), wait + 100).unref());
|
|
331
399
|
}
|
|
332
400
|
/**
|
|
333
401
|
* What the coach wrote that the player may see now, in a game going on with the helps on (D-88): its judgments, those whose note names a move they may still
|
|
@@ -353,11 +421,10 @@ function analysisShown(served) {
|
|
|
353
421
|
try {
|
|
354
422
|
const { name, record } = namedGame(served.context, analysis.game);
|
|
355
423
|
// Without a review, an AI is at it only while the chat is in a turn, or hears the player while its analyst holds the game (D-89): one the chat has
|
|
356
|
-
// started again since is gone (Priya).
|
|
424
|
+
// started again since is gone (Priya).
|
|
357
425
|
const { bell } = served;
|
|
358
|
-
const chat = { ...served.context, folder: served.attached?.chat.folder ?? served.folder };
|
|
359
426
|
const reviewed = isReviewed(record);
|
|
360
|
-
const reviewing = !reviewed && (bell.inTurn || (bell.listening &&
|
|
427
|
+
const reviewing = !reviewed && (bell.inTurn || (bell.listening && analystAtWork(served, analysis.game)));
|
|
361
428
|
// Without a review and with nobody at it, the analysis is incomplete: the page offers to bring it back to the AI (D-109).
|
|
362
429
|
const state = reviewed ? {} : reviewing ? { reviewing: true } : { incomplete: true };
|
|
363
430
|
// How far the book goes, which the page cannot tell without the book's moves: its card names the opening on those moves.
|
|
@@ -372,10 +439,23 @@ function analysisShown(served) {
|
|
|
372
439
|
function lastPlayed(context) {
|
|
373
440
|
const { name, record } = gameToStudy(context);
|
|
374
441
|
if (name === undefined) {
|
|
375
|
-
throw new
|
|
442
|
+
throw new GameNotArchived();
|
|
376
443
|
}
|
|
377
444
|
return { name, record };
|
|
378
445
|
}
|
|
446
|
+
/** The refusal of the game just over asked to analyse before the archive keeps it. */
|
|
447
|
+
class GameNotArchived extends Error {
|
|
448
|
+
constructor() {
|
|
449
|
+
super("The game just over is not in the archive yet: try again in a moment.");
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/** Why a game to study was refused, for the page to say it: a name no game has, the game just over not kept yet, or a game that cannot be read. */
|
|
453
|
+
function refusalOfStudy(error) {
|
|
454
|
+
if (error instanceof GameMissing) {
|
|
455
|
+
return "game-missing";
|
|
456
|
+
}
|
|
457
|
+
return error instanceof GameNotArchived ? "game-not-archived" : "pgn-unreadable";
|
|
458
|
+
}
|
|
379
459
|
/** A game brought as a PGN, kept to study as analyse_game keeps it: the one already kept when it is the same game, with its review. */
|
|
380
460
|
async function kept(served, pgn) {
|
|
381
461
|
await analyseGame.run({ pgn }, served.context);
|
|
@@ -8,7 +8,7 @@ export declare const json = "application/json; charset=utf-8";
|
|
|
8
8
|
*/
|
|
9
9
|
export interface Refused {
|
|
10
10
|
readonly error: string;
|
|
11
|
-
readonly refusal?: "no-game" | "ai-to-move" | "illegal" | "clear-chat" | `words-${WordsRefused["reason"]}` | `draw-${DrawRefused["reason"]}` | "profile-game" | "game-going" | "pgn-unreadable" | "update-game" | "update-chat";
|
|
11
|
+
readonly refusal?: "no-game" | "ai-to-move" | "illegal" | "clear-chat" | "new-game-profile" | `words-${WordsRefused["reason"]}` | `draw-${DrawRefused["reason"]}` | "profile-game" | "game-going" | "pgn-unreadable" | "game-missing" | "game-not-archived" | "update-game" | "update-chat";
|
|
12
12
|
readonly most?: number;
|
|
13
13
|
}
|
|
14
14
|
/** Answers a request, with nothing cached, sniffed or referred, and the page's policy unless the headers give another. */
|
|
@@ -2,7 +2,8 @@ import { type ServerResponse } from "node:http";
|
|
|
2
2
|
import { type Context } from "../../commands/index.ts";
|
|
3
3
|
import { Bell } from "../bell/index.ts";
|
|
4
4
|
import { type Attached, type ChatSeen } from "./chat.ts";
|
|
5
|
-
import { type Opening, type TableChats } from "./table.ts";
|
|
5
|
+
import { type ChatWait, type Opening, type TableChats, type WaitRefused } from "./table.ts";
|
|
6
|
+
import { type TableWindows } from "./windows.ts";
|
|
6
7
|
/**
|
|
7
8
|
* The page of a player's folder, served by the chat's process or by the table it is attached to (D-104): its address, how to tell it what the chat does
|
|
8
9
|
* and was given, which change no file, and how to stop it.
|
|
@@ -15,6 +16,8 @@ export interface Page {
|
|
|
15
16
|
chatCalls(): void | Promise<void>;
|
|
16
17
|
/** A tool of the chat's has ended: the table gets a copy of what the chat saw; the chat's own page reads it from the chat. */
|
|
17
18
|
toolEnded(): void;
|
|
19
|
+
/** Whether the doorbell finds the page: on its folder's usual ports, at the port it noted in the data folder, or at the table. */
|
|
20
|
+
readonly findable: boolean;
|
|
18
21
|
/** Whether a browser has the page open now: one listens to the game's changes. */
|
|
19
22
|
open(): boolean;
|
|
20
23
|
/** Whether the page is in use: open in a browser, or, on the table, with a chat attached. */
|
|
@@ -44,8 +47,8 @@ export interface TableUpdate {
|
|
|
44
47
|
restart(): void;
|
|
45
48
|
}
|
|
46
49
|
/**
|
|
47
|
-
* Serves the page of the context's folder on 127.0.0.1, on a port of its own when free, else on any free one,
|
|
48
|
-
* table's on its own ports alone, which keep two tables apart. The page reads the chat from the context in the chat's process, and on the table from
|
|
50
|
+
* Serves the page of the context's folder on 127.0.0.1, on a port of its own when free, else on any free one, which it notes in the data folder for the
|
|
51
|
+
* doorbell to find it there; the table's on its own ports alone, which keep two tables apart. The page reads the chat from the context in the chat's process, and on the table from
|
|
49
52
|
* what the chat attached to it tells it (D-104); it names the opponent as the chat's host does. The doorbell knows what the chat has read.
|
|
50
53
|
*/
|
|
51
54
|
export declare function startPage(context: Context, options?: PageOptions): Promise<Page>;
|
|
@@ -75,16 +78,18 @@ export interface Served {
|
|
|
75
78
|
readonly at: number;
|
|
76
79
|
readonly how: Opening;
|
|
77
80
|
} | undefined;
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
/** What the page asked while the chat was at work, waiting for it to end its answer: a start again with another host, or a new game (D-107). */
|
|
82
|
+
chatWait?: ChatWait | undefined;
|
|
83
|
+
/** Why the last new game that waited for the chat could not start, numbered so that each page says it once. */
|
|
84
|
+
waitRefused?: WaitRefused | undefined;
|
|
82
85
|
/** The newer Chesspal npm has (D-108); the table installing it, installed and waiting to be at rest, or starting again from it. */
|
|
83
86
|
newer?: string;
|
|
84
87
|
updating?: "installing" | "installed" | "restarting" | undefined;
|
|
88
|
+
/** The windows the table opened: the newest stays, the others close (D-104). */
|
|
89
|
+
windows?: TableWindows;
|
|
85
90
|
}
|
|
86
91
|
/**
|
|
87
92
|
* The chat has read the game as it is now, at the end of a tool's call: the doorbell wakes it only for what changes it after; and, when the tool `showed` it
|
|
88
|
-
* the game, it moves on that position and no other until a tool shows it another (D-71). A game that cannot be read shows none.
|
|
93
|
+
* the game, it moves on that position and no other until a tool shows it another (D-71). A game that cannot be read shows none. The data folder is the call's.
|
|
89
94
|
*/
|
|
90
|
-
export declare function chatHasRead(context: Context, showed: boolean): void;
|
|
95
|
+
export declare function chatHasRead(context: Context, showed: boolean, home?: () => string): void;
|
package/dist/server/page/page.js
CHANGED
|
@@ -2,26 +2,28 @@
|
|
|
2
2
|
// kept up to date as the game changes; the doorbell wakes the chat when the player acts there.
|
|
3
3
|
import { watch } from "node:fs";
|
|
4
4
|
import { createServer } from "node:http";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { positionIn, samePosition } from "../../commands/index.js";
|
|
5
|
+
import { basename, join } from "node:path";
|
|
6
|
+
import { analystQuietAt, positionIn, samePosition } from "../../commands/index.js";
|
|
7
7
|
import { aiNameOf } from "../../core/index.js";
|
|
8
8
|
import { forgetAnalyst, gameStamp, tableTalkStamp } from "../../games/index.js";
|
|
9
9
|
import { outdatedHostFiles } from "../../host-files/index.js";
|
|
10
10
|
import { gameFolder } from "../../profiles/index.js";
|
|
11
|
-
import { Bell,
|
|
11
|
+
import { Bell, forgetPagePort, notePagePort } from "../bell/index.js";
|
|
12
|
+
import { bellProbe, pagePorts } from "../ports/index.js";
|
|
12
13
|
import { analysesOf, answerAnalyses, sendSiteGames } from "./analyses.js";
|
|
13
14
|
import { answerChat } from "./attach.js";
|
|
14
15
|
import { attachedSeen, chatSeenOf, pageContext } from "./chat.js";
|
|
15
16
|
import { askNewGame, keepPosted, tellWatchers } from "./events.js";
|
|
16
17
|
import { sendFile, sendHtml } from "./files.js";
|
|
17
|
-
import { answerGame, chatCalls } from "./game.js";
|
|
18
|
+
import { answerGame, chatCalls, thinkingChanged } from "./game.js";
|
|
18
19
|
import { json, send } from "./http.js";
|
|
19
20
|
import { answerProfiles, readRatingsOf } from "./profiles.js";
|
|
20
|
-
import { answerTable,
|
|
21
|
+
import { answerTable, startWhenAtRest, updateWhenAtRest } from "./table.js";
|
|
21
22
|
import { watchForUpdates } from "./update.js";
|
|
23
|
+
import { windowConnected } from "./windows.js";
|
|
22
24
|
/**
|
|
23
|
-
* Serves the page of the context's folder on 127.0.0.1, on a port of its own when free, else on any free one,
|
|
24
|
-
* table's on its own ports alone, which keep two tables apart. The page reads the chat from the context in the chat's process, and on the table from
|
|
25
|
+
* Serves the page of the context's folder on 127.0.0.1, on a port of its own when free, else on any free one, which it notes in the data folder for the
|
|
26
|
+
* doorbell to find it there; the table's on its own ports alone, which keep two tables apart. The page reads the chat from the context in the chat's process, and on the table from
|
|
25
27
|
* what the chat attached to it tells it (D-104); it names the opponent as the chat's host does. The doorbell knows what the chat has read.
|
|
26
28
|
*/
|
|
27
29
|
export async function startPage(context, options = {}) {
|
|
@@ -34,9 +36,9 @@ export async function startPage(context, options = {}) {
|
|
|
34
36
|
context: pageContext(context),
|
|
35
37
|
chat,
|
|
36
38
|
opponent: options.opponent ?? (() => aiNameOf(chat.host())),
|
|
37
|
-
// A start again asked while the chat was at work comes once it has ended its answer (D-107), as an update installed meanwhile (D-108).
|
|
39
|
+
// A start again or a new game asked while the chat was at work comes once it has ended its answer (D-107), as an update installed meanwhile (D-108).
|
|
38
40
|
bell: new Bell(() => hasNews(context, chat), () => {
|
|
39
|
-
|
|
41
|
+
startWhenAtRest(served);
|
|
40
42
|
updateWhenAtRest(served);
|
|
41
43
|
tellWatchers(watchers);
|
|
42
44
|
}),
|
|
@@ -46,19 +48,36 @@ export async function startPage(context, options = {}) {
|
|
|
46
48
|
...(table === undefined ? {} : { table }),
|
|
47
49
|
};
|
|
48
50
|
const server = createServer((request, response) => answer(request, response, served));
|
|
49
|
-
|
|
51
|
+
// The folder's real path is read once: it costs a call to the disk for each part of the path.
|
|
52
|
+
const usual = pagePorts(folder);
|
|
53
|
+
const ports = table === undefined ? [...usual, 0] : usual;
|
|
50
54
|
for (const port of ports) {
|
|
51
55
|
if (await listened(server, port)) {
|
|
52
56
|
server.unref();
|
|
53
57
|
served.port = portOf(server);
|
|
58
|
+
// Noted before the game is watched: the notes' folder, made in the data folder, is no news for the pages.
|
|
59
|
+
const offPorts = table === undefined && !usual.includes(served.port);
|
|
60
|
+
const noted = offPorts ? notePort(served) : undefined;
|
|
54
61
|
const stopWatchingGame = watchGame(served);
|
|
55
62
|
const stopAskingNpm = watchForUpdates(served);
|
|
63
|
+
// A chat's process ends without closing its page: the note goes with it.
|
|
64
|
+
const forgetPort = () => {
|
|
65
|
+
if (noted !== undefined) {
|
|
66
|
+
forgetPagePort(noted, folder, served.port);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
if (noted !== undefined) {
|
|
70
|
+
process.once("exit", forgetPort);
|
|
71
|
+
}
|
|
56
72
|
const stopWatching = () => {
|
|
57
73
|
stopWatchingGame();
|
|
58
74
|
stopAskingNpm();
|
|
75
|
+
forgetPort();
|
|
76
|
+
process.off("exit", forgetPort);
|
|
59
77
|
};
|
|
60
78
|
return {
|
|
61
79
|
url: `http://127.0.0.1:${served.port}/`,
|
|
80
|
+
findable: !offPorts || noted !== undefined,
|
|
62
81
|
heard: () => tellWatchers(watchers),
|
|
63
82
|
chatCalls: () => chatCalls(served),
|
|
64
83
|
toolEnded: () => undefined,
|
|
@@ -76,14 +95,14 @@ export async function startPage(context, options = {}) {
|
|
|
76
95
|
}
|
|
77
96
|
/**
|
|
78
97
|
* The chat has read the game as it is now, at the end of a tool's call: the doorbell wakes it only for what changes it after; and, when the tool `showed` it
|
|
79
|
-
* the game, it moves on that position and no other until a tool shows it another (D-71). A game that cannot be read shows none.
|
|
98
|
+
* the game, it moves on that position and no other until a tool shows it another (D-71). A game that cannot be read shows none. The data folder is the call's.
|
|
80
99
|
*/
|
|
81
|
-
export function chatHasRead(context, showed) {
|
|
82
|
-
context.seen = gameSeen(
|
|
100
|
+
export function chatHasRead(context, showed, home = context.home) {
|
|
101
|
+
context.seen = gameSeen(home);
|
|
83
102
|
if (showed) {
|
|
84
103
|
const before = context.positionSeen;
|
|
85
104
|
try {
|
|
86
|
-
context.positionSeen = positionIn(gameFolder(
|
|
105
|
+
context.positionSeen = positionIn(gameFolder(home()));
|
|
87
106
|
}
|
|
88
107
|
catch {
|
|
89
108
|
context.positionSeen = null;
|
|
@@ -136,6 +155,7 @@ function answer(request, response, served) {
|
|
|
136
155
|
}
|
|
137
156
|
else if (path === "/api/events") {
|
|
138
157
|
keepPosted(request, response, served.watchers);
|
|
158
|
+
windowConnected(served, url);
|
|
139
159
|
// A page that opens reads the player's ratings again after a day (D-106).
|
|
140
160
|
readRatingsOf(served);
|
|
141
161
|
}
|
|
@@ -147,13 +167,29 @@ function answer(request, response, served) {
|
|
|
147
167
|
if (url.searchParams.get("analyst") === "gone") {
|
|
148
168
|
forgetGoneAnalyst(served);
|
|
149
169
|
}
|
|
150
|
-
|
|
170
|
+
// Nothing changed the game since the prompt hook played the move for the chat: the chat hears of it from the player's message.
|
|
171
|
+
const shown = url.searchParams.get("shown");
|
|
172
|
+
served.bell.listen(request, response, shown !== null && shown === stampNow(served));
|
|
151
173
|
}
|
|
152
174
|
}
|
|
175
|
+
else if (path === "/api/bell/prompt") {
|
|
176
|
+
// The prompt hook: a message starts the chat's turn. A path of its own, which a page of an older Chesspal answers with a 404, not as a hook waiting.
|
|
177
|
+
served.bell.chatPrompted();
|
|
178
|
+
send(response, 200, "text/plain; charset=utf-8", "at work");
|
|
179
|
+
}
|
|
153
180
|
else {
|
|
154
181
|
sendFile(response, path);
|
|
155
182
|
}
|
|
156
183
|
}
|
|
184
|
+
/** The stamp of the game in the data folder now; none when there is none or the folder cannot be read. */
|
|
185
|
+
function stampNow(served) {
|
|
186
|
+
try {
|
|
187
|
+
return gameStamp(gameFolder(served.context.home()));
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
157
193
|
/** The chat's hook says no analyst runs any more (D-109): the game it held is free, and its review, unless finished, incomplete. */
|
|
158
194
|
function forgetGoneAnalyst(served) {
|
|
159
195
|
try {
|
|
@@ -176,13 +212,13 @@ function tableChat(served) {
|
|
|
176
212
|
* flag that fell, or another profile in use, which wakes the chat at the next ring (D-66). The player's words the chat was given change nothing it has not read.
|
|
177
213
|
*/
|
|
178
214
|
function hasNews(context, chat) {
|
|
179
|
-
return gameSeen(context) !== chat.seen;
|
|
215
|
+
return gameSeen(context.home) !== chat.seen;
|
|
180
216
|
}
|
|
181
217
|
// 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
|
|
182
218
|
// before any game was played here (Priya); none while the data folder cannot be made.
|
|
183
|
-
function gameSeen(
|
|
219
|
+
function gameSeen(home) {
|
|
184
220
|
try {
|
|
185
|
-
const folder = gameFolder(
|
|
221
|
+
const folder = gameFolder(home());
|
|
186
222
|
return `${folder}
|
|
187
223
|
${gameStamp(folder) ?? ""}
|
|
188
224
|
${tableTalkStamp(folder) ?? ""}`;
|
|
@@ -198,19 +234,46 @@ ${tableTalkStamp(folder) ?? ""}`;
|
|
|
198
234
|
*/
|
|
199
235
|
function watchGame(served) {
|
|
200
236
|
let waiting;
|
|
237
|
+
let quiet;
|
|
201
238
|
let watching = [];
|
|
239
|
+
let game;
|
|
240
|
+
// An analyst that writes nothing for half an hour counts as gone, which no file tells: the pages, and what waits for the chat at rest, hear it then.
|
|
241
|
+
const analystGoesQuiet = () => {
|
|
242
|
+
clearTimeout(quiet);
|
|
243
|
+
const left = analystQuietIn(served);
|
|
244
|
+
quiet = left === undefined ? undefined : setTimeout(() => {
|
|
245
|
+
tellWatchers(served.watchers);
|
|
246
|
+
startWhenAtRest(served);
|
|
247
|
+
updateWhenAtRest(served);
|
|
248
|
+
}, left + 100).unref();
|
|
249
|
+
};
|
|
202
250
|
const watchAll = () => {
|
|
203
251
|
watching.forEach((watcher) => watcher.close());
|
|
204
|
-
|
|
252
|
+
const folders = watchedFolders(served);
|
|
253
|
+
game = folders.length === 0 ? undefined : basename(folders.at(-1));
|
|
254
|
+
watching = folders.flatMap((folder) => watchFolder(folder, heard));
|
|
255
|
+
analystGoesQuiet();
|
|
205
256
|
};
|
|
206
|
-
// A save writes a new file and renames it: the events of one save come together, and the pages hear of it once.
|
|
207
|
-
|
|
208
|
-
|
|
257
|
+
// A save writes a new file and renames it: the events of one save come together, and the pages hear of it once. Of the ponder agent's thoughts,
|
|
258
|
+
// only its sign coming or going.
|
|
259
|
+
let news = false;
|
|
260
|
+
// On Windows the profiles' folder hears every write in the game's folder, as a change of it: the game folder's own watcher hears what it was.
|
|
261
|
+
const heard = (name, event) => {
|
|
262
|
+
if (name !== null && (!watched.test(name) || (event === "change" && name === game))) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
news ||= name !== "thoughts.json";
|
|
266
|
+
if (waiting === undefined) {
|
|
209
267
|
waiting = setTimeout(() => {
|
|
210
268
|
waiting = undefined;
|
|
269
|
+
const tell = news || thinkingChanged(served);
|
|
270
|
+
news = false;
|
|
211
271
|
watchAll();
|
|
212
|
-
|
|
213
|
-
|
|
272
|
+
if (tell) {
|
|
273
|
+
tellWatchers(served.watchers);
|
|
274
|
+
}
|
|
275
|
+
// An analyst that has ended lets what waited for the chat start (D-107), and a game that has ended an update installed meanwhile (D-108).
|
|
276
|
+
startWhenAtRest(served);
|
|
214
277
|
updateWhenAtRest(served);
|
|
215
278
|
}, 30);
|
|
216
279
|
}
|
|
@@ -218,10 +281,21 @@ function watchGame(served) {
|
|
|
218
281
|
watchAll();
|
|
219
282
|
return () => {
|
|
220
283
|
clearTimeout(waiting);
|
|
284
|
+
clearTimeout(quiet);
|
|
221
285
|
watching.forEach((watcher) => watcher.close());
|
|
222
286
|
};
|
|
223
287
|
}
|
|
224
|
-
//
|
|
288
|
+
// How long the analyst at work may still write nothing before it counts as gone; none without one, one gone already, or while the data folder cannot be made.
|
|
289
|
+
function analystQuietIn(served) {
|
|
290
|
+
try {
|
|
291
|
+
const left = (analystQuietAt(served.context)?.getTime() ?? 0) - served.context.now().getTime();
|
|
292
|
+
return left > 0 ? left : undefined;
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// None while the data folder cannot be made: each tool call tells the chat why, and the page reads the game as it loads. The game's folder last.
|
|
225
299
|
function watchedFolders(served) {
|
|
226
300
|
try {
|
|
227
301
|
const home = served.context.home();
|
|
@@ -234,7 +308,7 @@ function watchedFolders(served) {
|
|
|
234
308
|
// A folder watched, without its subfolders; none when it cannot be, or is not there yet: the pages see its files only as they load them.
|
|
235
309
|
function watchFolder(folder, heard) {
|
|
236
310
|
try {
|
|
237
|
-
const watcher = watch(folder, (event, name) => heard(name));
|
|
311
|
+
const watcher = watch(folder, (event, name) => heard(name, event));
|
|
238
312
|
watcher.on("error", () => watcher.close());
|
|
239
313
|
watcher.unref();
|
|
240
314
|
return [watcher];
|
|
@@ -254,6 +328,17 @@ function fromHere(request, port, api) {
|
|
|
254
328
|
return hosts.includes(request.headers.host ?? "") && (origin === undefined || hosts.some((host) => origin === `http://${host}`))
|
|
255
329
|
&& (!api || site === undefined || site === "same-origin" || site === "none");
|
|
256
330
|
}
|
|
331
|
+
/** Notes the port of a page off its folder's usual ports in the data folder: the data folder it noted it in, or none when it could not. */
|
|
332
|
+
function notePort(served) {
|
|
333
|
+
try {
|
|
334
|
+
const home = served.context.home();
|
|
335
|
+
void notePagePort(home, served.folder, served.port);
|
|
336
|
+
return home;
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
257
342
|
function listened(server, port) {
|
|
258
343
|
return new Promise((done) => {
|
|
259
344
|
const failed = () => done(false);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { type Refused } from "./http.ts";
|
|
2
3
|
import type { Served } from "./page.ts";
|
|
3
4
|
/** A host the table offers a chat in: whether it is installed, the models and efforts the player may choose, and the command to open one by hand. */
|
|
4
5
|
export interface ChatHost {
|
|
@@ -19,13 +20,26 @@ export interface TableChats {
|
|
|
19
20
|
/** Asks for the chat again: with another host, or `updated`, from the Chesspal just installed (D-108). */
|
|
20
21
|
relaunch(folder: string, host?: string, updated?: boolean): boolean;
|
|
21
22
|
}
|
|
22
|
-
/** The chat `chesspal play` holds: the model and effort its host started with, none for the host's own. */
|
|
23
|
+
/** The chat `chesspal play` holds: the model and effort its host started with, none for the host's own, and the seat, the play's process. */
|
|
23
24
|
export interface HeldChat {
|
|
24
25
|
readonly model?: string | undefined;
|
|
25
26
|
readonly effort?: string | undefined;
|
|
27
|
+
readonly seat?: number;
|
|
26
28
|
}
|
|
27
29
|
/** A chat opening at the table: one the page asked for, the one started again for a new game, or with another model or host (D-107). */
|
|
28
30
|
export type Opening = "chat" | "again" | "restart";
|
|
31
|
+
/**
|
|
32
|
+
* What waits for the chat at the table to end its answer (D-107): a start again with another host, or a new game that wants a clean chat, which starts
|
|
33
|
+
* it again; the later request replaces the earlier. `start` starts the new game with the choices kept: whether the chat starts again for it; one that
|
|
34
|
+
* cannot start says why to the pages.
|
|
35
|
+
*/
|
|
36
|
+
export type ChatWait = {
|
|
37
|
+
readonly for: "restart";
|
|
38
|
+
readonly host: string;
|
|
39
|
+
} | {
|
|
40
|
+
readonly for: "new-game";
|
|
41
|
+
readonly start: () => boolean;
|
|
42
|
+
};
|
|
29
43
|
/** What the table's page shows of its chat (D-104, D-107). */
|
|
30
44
|
export interface TableShown {
|
|
31
45
|
/** Whether a chat is at the table, and its host among those offered, when it says one. */
|
|
@@ -36,9 +50,15 @@ export interface TableShown {
|
|
|
36
50
|
readonly started?: HeldChat;
|
|
37
51
|
readonly hosts: readonly ChatHost[];
|
|
38
52
|
readonly opening?: Opening;
|
|
39
|
-
/** A start again asked while the chat was at work, waiting for it to end its answer, or to be started at once or let go from the page. */
|
|
40
|
-
readonly waiting?:
|
|
53
|
+
/** A start again or a new game asked while the chat was at work, waiting for it to end its answer, or to be started at once or let go from the page. */
|
|
54
|
+
readonly waiting?: ChatWait["for"];
|
|
55
|
+
/** Why the last new game that waited for the chat could not start, numbered so that each page says it once. */
|
|
56
|
+
readonly refused?: WaitRefused;
|
|
41
57
|
}
|
|
58
|
+
/** A new game that waited for the chat and could not start: why, and its number, larger for each. */
|
|
59
|
+
export type WaitRefused = Refused & {
|
|
60
|
+
readonly number: number;
|
|
61
|
+
};
|
|
42
62
|
/** What the table's page shows of its chat: whether one is at the table, with which host, the hosts it can open one in, and one opening. */
|
|
43
63
|
export declare function tableShown(served: Served): {
|
|
44
64
|
table?: TableShown;
|
|
@@ -55,10 +75,25 @@ export declare function chatToRelaunch(served: Served): boolean;
|
|
|
55
75
|
* it was asked; the pages then show it opening.
|
|
56
76
|
*/
|
|
57
77
|
export declare function relaunchChat(served: Served, how?: "again" | "restart", host?: string): boolean;
|
|
58
|
-
/**
|
|
59
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Whether a new game that wants a clean chat waits for the chat at the table to end its answer, then starts it again (D-57, D-107): `chesspal play` holds
|
|
80
|
+
* it, it is at work, and no analyst of it is; with an analyst at work the player is asked for /clear, as with a chat opened by hand.
|
|
81
|
+
*/
|
|
82
|
+
export declare function chatToWaitFor(served: Served): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Does what the page asked while the chat was at work, now that it has ended its answer. A chat gone meanwhile takes a start again with it; a new game
|
|
85
|
+
* starts without it, and loses nothing: the next chat at the table is a clean one.
|
|
86
|
+
*/
|
|
87
|
+
export declare function startWhenAtRest(served: Served): void;
|
|
88
|
+
/** A new game that waited for the chat could not start (D-107): the pages say why, each once. */
|
|
89
|
+
export declare function refuseWaited(served: Served, refused: Refused): void;
|
|
60
90
|
/**
|
|
61
91
|
* Once npm has installed the newer Chesspal (D-108), and no game goes on and the chat is at rest, as a game or a chat may have started while npm worked:
|
|
62
92
|
* the chat `chesspal play` holds starts again from it, clean, and once it has left the table, the table closes to start again from it too.
|
|
63
93
|
*/
|
|
64
94
|
export declare function updateWhenAtRest(served: Served): void;
|
|
95
|
+
/**
|
|
96
|
+
* Whether the chat's analyst is at work, on `game` when named: one whose chat started again since it took its game, or that has written nothing for half an
|
|
97
|
+
* hour, is gone (D-109). The chat's starts are in its own folder: on the table, the attached chat's.
|
|
98
|
+
*/
|
|
99
|
+
export declare function analystAtWork(served: Served, game?: string | undefined): boolean;
|