jev-chess 1.0.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/ARCHITECTURE.md +148 -0
- package/README.md +109 -0
- package/dist/chessEngine.d.ts +36 -0
- package/dist/chessEngine.d.ts.map +1 -0
- package/dist/chessEngine.js +173 -0
- package/dist/chessEngine.js.map +1 -0
- package/dist/classicMatches.d.ts +50 -0
- package/dist/classicMatches.d.ts.map +1 -0
- package/dist/classicMatches.js +223 -0
- package/dist/classicMatches.js.map +1 -0
- package/dist/demo.d.ts +2 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/demo.js +142 -0
- package/dist/demo.js.map +1 -0
- package/dist/gameReviewer.d.ts +11 -0
- package/dist/gameReviewer.d.ts.map +1 -0
- package/dist/gameReviewer.js +89 -0
- package/dist/gameReviewer.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/moveEvaluator.d.ts +11 -0
- package/dist/moveEvaluator.d.ts.map +1 -0
- package/dist/moveEvaluator.js +103 -0
- package/dist/moveEvaluator.js.map +1 -0
- package/dist/moveResolver.d.ts +15 -0
- package/dist/moveResolver.d.ts.map +1 -0
- package/dist/moveResolver.js +97 -0
- package/dist/moveResolver.js.map +1 -0
- package/dist/personaEngine.d.ts +15 -0
- package/dist/personaEngine.d.ts.map +1 -0
- package/dist/personaEngine.js +151 -0
- package/dist/personaEngine.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1490 -0
- package/dist/server.js.map +1 -0
- package/dist/typeSafeClient.d.ts +13 -0
- package/dist/typeSafeClient.d.ts.map +1 -0
- package/dist/typeSafeClient.js +336 -0
- package/dist/typeSafeClient.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/web/main.d.ts +2 -0
- package/dist/web/main.d.ts.map +1 -0
- package/dist/web/main.js +620 -0
- package/dist/web/main.js.map +1 -0
- package/package.json +35 -0
- package/public/app.js +45 -0
- package/public/index.html +790 -0
- package/src/chessEngine.ts +191 -0
- package/src/classicMatches.ts +291 -0
- package/src/demo.ts +160 -0
- package/src/gameReviewer.ts +113 -0
- package/src/index.ts +8 -0
- package/src/moveEvaluator.ts +122 -0
- package/src/moveResolver.ts +120 -0
- package/src/personaEngine.ts +193 -0
- package/src/server.ts +1509 -0
- package/src/typeSafeClient.ts +321 -0
- package/src/types.ts +118 -0
- package/src/web/main.ts +615 -0
- package/tests/chess.test.ts +206 -0
- package/tsconfig.json +21 -0
package/src/web/main.ts
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import { ChessEngine } from "../chessEngine.js";
|
|
2
|
+
import { MoveResolver } from "../moveResolver.js";
|
|
3
|
+
import { MoveEvaluator } from "../moveEvaluator.js";
|
|
4
|
+
import { PersonaEngine } from "../personaEngine.js";
|
|
5
|
+
import { ClassicMatchStudio, CLASSIC_MATCHES, type ClassicMatch } from "../classicMatches.js";
|
|
6
|
+
import { setApiKey, getApiKeyStatus } from "../typeSafeClient.js";
|
|
7
|
+
import type { AnnotatedMove, MoveEvaluation, PersonaId } from "../types.js";
|
|
8
|
+
|
|
9
|
+
const PIECE_UNICODE: Record<string, string> = {
|
|
10
|
+
p: "♟", n: "♞", b: "♝", r: "♜", q: "♛", k: "♚",
|
|
11
|
+
P: "♙", N: "♘", B: "♗", R: "♖", Q: "♕", K: "♔",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const engine = new ChessEngine();
|
|
15
|
+
const resolver = new MoveResolver();
|
|
16
|
+
const evaluator = new MoveEvaluator();
|
|
17
|
+
const personaEngine = new PersonaEngine();
|
|
18
|
+
const matchStudio = new ClassicMatchStudio();
|
|
19
|
+
|
|
20
|
+
let selectedSquare: string | null = null;
|
|
21
|
+
let legalMoves: AnnotatedMove[] = [];
|
|
22
|
+
let currentFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
|
23
|
+
let activePersona: PersonaId = "tal";
|
|
24
|
+
let lastFrom: string | null = null;
|
|
25
|
+
let lastTo: string | null = null;
|
|
26
|
+
|
|
27
|
+
let activeMatch: ClassicMatch | null = null;
|
|
28
|
+
let matchMoveIndex = 0;
|
|
29
|
+
let autoPlayTimer: any = null;
|
|
30
|
+
|
|
31
|
+
function renderBoard(fen: string) {
|
|
32
|
+
const boardEl = document.getElementById("board");
|
|
33
|
+
if (!boardEl) return;
|
|
34
|
+
boardEl.innerHTML = "";
|
|
35
|
+
const [placement, turn] = fen.split(" ");
|
|
36
|
+
|
|
37
|
+
const turnDot = document.getElementById("turnDot");
|
|
38
|
+
const turnLabel = document.getElementById("turnLabel");
|
|
39
|
+
if (turnDot && turnLabel) {
|
|
40
|
+
if (turn === "w") {
|
|
41
|
+
turnDot.className = "turn-dot white";
|
|
42
|
+
turnLabel.innerText = "White to move";
|
|
43
|
+
} else {
|
|
44
|
+
turnDot.className = "turn-dot black";
|
|
45
|
+
turnLabel.innerText = "Black to move";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const rows = (placement ?? "").split("/");
|
|
50
|
+
for (let r = 0; r < 8; r++) {
|
|
51
|
+
let col = 0;
|
|
52
|
+
const rowStr = rows[r] ?? "";
|
|
53
|
+
for (const ch of rowStr) {
|
|
54
|
+
if (!isNaN(Number(ch))) {
|
|
55
|
+
for (let empty = 0; empty < parseInt(ch, 10); empty++) {
|
|
56
|
+
createSquare(r, col, null);
|
|
57
|
+
col++;
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
createSquare(r, col, ch);
|
|
61
|
+
col++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function createSquare(r: number, c: number, piece: string | null) {
|
|
68
|
+
const files = ["a", "b", "c", "d", "e", "f", "g", "h"];
|
|
69
|
+
const ranks = ["8", "7", "6", "5", "4", "3", "2", "1"];
|
|
70
|
+
const sqName = files[c] + ranks[r];
|
|
71
|
+
const isLight = (r + c) % 2 === 0;
|
|
72
|
+
|
|
73
|
+
const div = document.createElement("div");
|
|
74
|
+
div.className = "square " + (isLight ? "light" : "dark");
|
|
75
|
+
div.dataset.sq = sqName;
|
|
76
|
+
|
|
77
|
+
if (sqName === selectedSquare) div.classList.add("selected");
|
|
78
|
+
if (sqName === lastFrom) div.classList.add("last-from");
|
|
79
|
+
if (sqName === lastTo) div.classList.add("last-to");
|
|
80
|
+
|
|
81
|
+
if (selectedSquare) {
|
|
82
|
+
const canMove = legalMoves.some((m) => m.from === selectedSquare && m.to === sqName);
|
|
83
|
+
if (canMove) div.classList.add("target");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (c === 0) {
|
|
87
|
+
const rankLabel = document.createElement("span");
|
|
88
|
+
rankLabel.className = "coord-label coord-rank";
|
|
89
|
+
rankLabel.innerText = ranks[r] ?? "";
|
|
90
|
+
div.appendChild(rankLabel);
|
|
91
|
+
}
|
|
92
|
+
if (r === 7) {
|
|
93
|
+
const fileLabel = document.createElement("span");
|
|
94
|
+
fileLabel.className = "coord-label coord-file";
|
|
95
|
+
fileLabel.innerText = files[c] ?? "";
|
|
96
|
+
div.appendChild(fileLabel);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (piece) {
|
|
100
|
+
const span = document.createElement("span");
|
|
101
|
+
span.innerText = PIECE_UNICODE[piece] || piece;
|
|
102
|
+
div.appendChild(span);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
div.onclick = () => onSquareClick(sqName);
|
|
106
|
+
document.getElementById("board")?.appendChild(div);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function updateLegalMoves() {
|
|
110
|
+
legalMoves = engine.getAnnotatedMoves();
|
|
111
|
+
const mat = engine.getMaterialBalance();
|
|
112
|
+
const matEl = document.getElementById("materialBalance");
|
|
113
|
+
if (matEl) matEl.innerText = mat.description || "Equal";
|
|
114
|
+
updateHistory(engine.history());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function updateHistory(history: string[]) {
|
|
118
|
+
const el = document.getElementById("historyList");
|
|
119
|
+
if (!el) return;
|
|
120
|
+
if (!history || history.length === 0) {
|
|
121
|
+
el.innerHTML = '<span style="color: #666;">No moves played yet</span>';
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
el.innerHTML = history
|
|
125
|
+
.map((m, i) => `<span class="history-item">${i % 2 === 0 ? Math.floor(i / 2 + 1) + ". " : ""}${m}</span>`)
|
|
126
|
+
.join(" ");
|
|
127
|
+
el.scrollTop = el.scrollHeight;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function onSquareClick(sq: string) {
|
|
131
|
+
if (selectedSquare) {
|
|
132
|
+
const move = legalMoves.find((m) => m.from === selectedSquare && m.to === sq);
|
|
133
|
+
if (move) {
|
|
134
|
+
await playMove(move.san);
|
|
135
|
+
selectedSquare = null;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const hasMoves = legalMoves.some((m) => m.from === sq);
|
|
140
|
+
selectedSquare = hasMoves ? sq : null;
|
|
141
|
+
renderBoard(currentFen);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function playMove(san: string) {
|
|
145
|
+
const move = engine.makeMove(san);
|
|
146
|
+
if (move) {
|
|
147
|
+
lastFrom = move.from;
|
|
148
|
+
lastTo = move.to;
|
|
149
|
+
currentFen = engine.fen();
|
|
150
|
+
renderBoard(currentFen);
|
|
151
|
+
updateLegalMoves();
|
|
152
|
+
|
|
153
|
+
const evaluation = await evaluator.evaluateMove(engine, move);
|
|
154
|
+
updateIntelligence(evaluation);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function formatTheme(theme: string | null | undefined): string {
|
|
159
|
+
if (!theme) return "--";
|
|
160
|
+
return theme
|
|
161
|
+
.split("_")
|
|
162
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
163
|
+
.join(" ");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function updateIntelligence(ev: MoveEvaluation | null) {
|
|
167
|
+
if (!ev) return;
|
|
168
|
+
const badge = document.getElementById("lastMoveBadge");
|
|
169
|
+
if (badge) badge.innerText = ev.commentaryBadge || "Move Evaluated";
|
|
170
|
+
|
|
171
|
+
const mSharp = document.getElementById("mSharp");
|
|
172
|
+
if (mSharp) mSharp.innerText = ev.tacticalSharpness.score.toFixed(1);
|
|
173
|
+
|
|
174
|
+
const mSharpBar = document.getElementById("mSharpBar");
|
|
175
|
+
if (mSharpBar) {
|
|
176
|
+
mSharpBar.style.width = Math.min(100, (ev.tacticalSharpness.score / 3.0) * 100) + "%";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const mTheme = document.getElementById("mTheme");
|
|
180
|
+
if (mTheme) mTheme.innerText = formatTheme(ev.strategicTheme.theme);
|
|
181
|
+
|
|
182
|
+
const mThemeConf = document.getElementById("mThemeConf");
|
|
183
|
+
if (mThemeConf) {
|
|
184
|
+
mThemeConf.innerText = "Confidence: " + (ev.strategicTheme.confidence * 100).toFixed(0) + "%";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const mKing = document.getElementById("mKing");
|
|
188
|
+
if (mKing) mKing.innerText = (ev.kingAttackRisk.probability * 100).toFixed(0) + "%";
|
|
189
|
+
|
|
190
|
+
const mKingBar = document.getElementById("mKingBar");
|
|
191
|
+
if (mKingBar) mKingBar.style.width = ev.kingAttackRisk.probability * 100 + "%";
|
|
192
|
+
|
|
193
|
+
const mPressure = document.getElementById("mPressure");
|
|
194
|
+
if (mPressure) mPressure.innerText = (ev.psychologicalPressure.probability * 100).toFixed(0) + "%";
|
|
195
|
+
|
|
196
|
+
const mPressureBar = document.getElementById("mPressureBar");
|
|
197
|
+
if (mPressureBar) mPressureBar.style.width = ev.psychologicalPressure.probability * 100 + "%";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function resetIntelligence() {
|
|
201
|
+
const badge = document.getElementById("lastMoveBadge");
|
|
202
|
+
if (badge) badge.innerText = "Waiting for move...";
|
|
203
|
+
const mSharp = document.getElementById("mSharp");
|
|
204
|
+
if (mSharp) mSharp.innerText = "--";
|
|
205
|
+
const mSharpBar = document.getElementById("mSharpBar");
|
|
206
|
+
if (mSharpBar) mSharpBar.style.width = "0%";
|
|
207
|
+
const mTheme = document.getElementById("mTheme");
|
|
208
|
+
if (mTheme) mTheme.innerText = "--";
|
|
209
|
+
const mThemeConf = document.getElementById("mThemeConf");
|
|
210
|
+
if (mThemeConf) mThemeConf.innerText = "Confidence: --";
|
|
211
|
+
const mKing = document.getElementById("mKing");
|
|
212
|
+
if (mKing) mKing.innerText = "--";
|
|
213
|
+
const mKingBar = document.getElementById("mKingBar");
|
|
214
|
+
if (mKingBar) mKingBar.style.width = "0%";
|
|
215
|
+
const mPressure = document.getElementById("mPressure");
|
|
216
|
+
if (mPressure) mPressure.innerText = "--";
|
|
217
|
+
const mPressureBar = document.getElementById("mPressureBar");
|
|
218
|
+
if (mPressureBar) mPressureBar.style.width = "0%";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function resolveAndPlayMove() {
|
|
222
|
+
const input = document.getElementById("nlInput") as HTMLInputElement | null;
|
|
223
|
+
if (!input) return;
|
|
224
|
+
const q = input.value.trim();
|
|
225
|
+
if (!q) return;
|
|
226
|
+
|
|
227
|
+
const banner = document.getElementById("resolutionResult");
|
|
228
|
+
const resText = document.getElementById("resText");
|
|
229
|
+
const resConf = document.getElementById("resConf");
|
|
230
|
+
if (banner) banner.style.display = "flex";
|
|
231
|
+
if (resText) resText.innerText = "Resolving intent via TypeSafe Choice...";
|
|
232
|
+
if (resConf) resConf.innerText = "";
|
|
233
|
+
|
|
234
|
+
const data = await resolver.resolveIntent(engine, q);
|
|
235
|
+
|
|
236
|
+
if (data.matchedMove) {
|
|
237
|
+
if (resText) resText.innerText = "Matched: " + data.matchedMove.san + " (" + data.matchedMove.description + ")";
|
|
238
|
+
if (resConf) resConf.innerText = (data.confidence * 100).toFixed(1) + "% confidence";
|
|
239
|
+
input.value = "";
|
|
240
|
+
await playMove(data.matchedMove.san);
|
|
241
|
+
} else {
|
|
242
|
+
if (resText) resText.innerText = "Ambiguous: " + (data.alternativeCandidates?.join(", ") || "No legal match");
|
|
243
|
+
if (resConf) resConf.innerText = "Low confidence (" + (data.confidence * 100).toFixed(1) + "%)";
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function setChip(txt: string) {
|
|
248
|
+
const input = document.getElementById("nlInput") as HTMLInputElement | null;
|
|
249
|
+
if (input) {
|
|
250
|
+
input.value = txt;
|
|
251
|
+
resolveAndPlayMove();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function selectPersona(id: PersonaId, el: HTMLElement) {
|
|
256
|
+
activePersona = id;
|
|
257
|
+
document.querySelectorAll(".persona-card").forEach((c) => c.classList.remove("active"));
|
|
258
|
+
el.classList.add("active");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function makePersonaMove() {
|
|
262
|
+
const rationaleEl = document.getElementById("personaRationale");
|
|
263
|
+
if (rationaleEl) rationaleEl.innerText = "Evaluating candidates with TypeSafe Composite Scoring...";
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const decision = await personaEngine.selectMove(engine, activePersona);
|
|
267
|
+
if (rationaleEl) rationaleEl.innerText = decision.rationale;
|
|
268
|
+
await playMove(decision.selectedMove.san);
|
|
269
|
+
} catch (err: any) {
|
|
270
|
+
if (rationaleEl) rationaleEl.innerText = err?.message || "Unable to make persona move.";
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function resetGame() {
|
|
275
|
+
stopAutoPlay();
|
|
276
|
+
engine.reset();
|
|
277
|
+
currentFen = engine.fen();
|
|
278
|
+
lastFrom = null;
|
|
279
|
+
lastTo = null;
|
|
280
|
+
selectedSquare = null;
|
|
281
|
+
const resResult = document.getElementById("resolutionResult");
|
|
282
|
+
if (resResult) resResult.style.display = "none";
|
|
283
|
+
renderBoard(currentFen);
|
|
284
|
+
updateLegalMoves();
|
|
285
|
+
resetIntelligence();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function undoMove() {
|
|
289
|
+
stopAutoPlay();
|
|
290
|
+
engine.chess.undo();
|
|
291
|
+
currentFen = engine.fen();
|
|
292
|
+
renderBoard(currentFen);
|
|
293
|
+
updateLegalMoves();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* Classic Matches Functions */
|
|
297
|
+
function initClassicMatches() {
|
|
298
|
+
const select = document.getElementById("matchSelect") as HTMLSelectElement | null;
|
|
299
|
+
if (!select) return;
|
|
300
|
+
select.innerHTML = CLASSIC_MATCHES.map(
|
|
301
|
+
(m) => `<option value="${m.id}">${m.title} (${m.year}) — ${m.white} vs ${m.black}</option>`
|
|
302
|
+
).join("");
|
|
303
|
+
|
|
304
|
+
if (CLASSIC_MATCHES.length > 0) {
|
|
305
|
+
selectMatch(CLASSIC_MATCHES[0]!.id);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function onMatchSelectChange(matchId: string) {
|
|
310
|
+
if (matchId) selectMatch(matchId);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function selectMatch(matchId: string) {
|
|
314
|
+
stopAutoPlay();
|
|
315
|
+
activeMatch = CLASSIC_MATCHES.find((m) => m.id === matchId) || null;
|
|
316
|
+
if (!activeMatch) return;
|
|
317
|
+
|
|
318
|
+
const metaBox = document.getElementById("matchMetaBox");
|
|
319
|
+
if (metaBox) metaBox.style.display = "block";
|
|
320
|
+
|
|
321
|
+
const pEl = document.getElementById("matchPlayers");
|
|
322
|
+
if (pEl) pEl.innerText = `${activeMatch.white} vs ${activeMatch.black}`;
|
|
323
|
+
|
|
324
|
+
const yEl = document.getElementById("matchYearEvent");
|
|
325
|
+
if (yEl) yEl.innerText = `${activeMatch.year} · ${activeMatch.event}`;
|
|
326
|
+
|
|
327
|
+
const dEl = document.getElementById("matchDesc");
|
|
328
|
+
if (dEl) dEl.innerText = activeMatch.description;
|
|
329
|
+
|
|
330
|
+
const ecoEl = document.getElementById("matchEco");
|
|
331
|
+
if (ecoEl) ecoEl.innerText = activeMatch.eco;
|
|
332
|
+
|
|
333
|
+
const openEl = document.getElementById("matchOpening");
|
|
334
|
+
if (openEl) openEl.innerText = activeMatch.opening;
|
|
335
|
+
|
|
336
|
+
const resEl = document.getElementById("matchResult");
|
|
337
|
+
if (resEl) resEl.innerText = activeMatch.result;
|
|
338
|
+
|
|
339
|
+
goToReplayMove(0);
|
|
340
|
+
const card = document.getElementById("classificationCard");
|
|
341
|
+
if (card) card.style.display = "none";
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function goToReplayMove(index: number) {
|
|
345
|
+
if (!activeMatch) return;
|
|
346
|
+
const target = Math.max(0, Math.min(activeMatch.moves.length, index));
|
|
347
|
+
matchMoveIndex = target;
|
|
348
|
+
|
|
349
|
+
const moveLabel = document.getElementById("replayMoveLabel");
|
|
350
|
+
if (moveLabel) moveLabel.innerText = `Move ${matchMoveIndex} / ${activeMatch.moves.length}`;
|
|
351
|
+
|
|
352
|
+
const pct = (matchMoveIndex / Math.max(1, activeMatch.moves.length)) * 100;
|
|
353
|
+
const bar = document.getElementById("replayProgressBar");
|
|
354
|
+
if (bar) bar.style.width = pct + "%";
|
|
355
|
+
|
|
356
|
+
const turnLabel = document.getElementById("replayTurnLabel");
|
|
357
|
+
if (turnLabel) {
|
|
358
|
+
if (matchMoveIndex === 0) {
|
|
359
|
+
turnLabel.innerText = "Starting Position";
|
|
360
|
+
} else {
|
|
361
|
+
const lastSan = activeMatch.moves[matchMoveIndex - 1];
|
|
362
|
+
const moveNum = Math.floor((matchMoveIndex - 1) / 2) + 1;
|
|
363
|
+
const color = (matchMoveIndex - 1) % 2 === 0 ? "White" : "Black";
|
|
364
|
+
turnLabel.innerText = `${moveNum}${color === "White" ? "." : "..."} ${lastSan}`;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
engine.reset();
|
|
369
|
+
let lastEvaluatedMove: AnnotatedMove | null = null;
|
|
370
|
+
for (let i = 0; i < target; i++) {
|
|
371
|
+
const mv = engine.makeMove(activeMatch.moves[i]!);
|
|
372
|
+
if (i === target - 1) {
|
|
373
|
+
lastEvaluatedMove = mv;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
currentFen = engine.fen();
|
|
378
|
+
if (lastEvaluatedMove) {
|
|
379
|
+
lastFrom = lastEvaluatedMove.from;
|
|
380
|
+
lastTo = lastEvaluatedMove.to;
|
|
381
|
+
} else {
|
|
382
|
+
lastFrom = null;
|
|
383
|
+
lastTo = null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
updateLegalMoves();
|
|
387
|
+
renderBoard(currentFen);
|
|
388
|
+
|
|
389
|
+
if (lastEvaluatedMove) {
|
|
390
|
+
const evaluation = await evaluator.evaluateMove(engine, lastEvaluatedMove);
|
|
391
|
+
updateIntelligence(evaluation);
|
|
392
|
+
} else {
|
|
393
|
+
resetIntelligence();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function stepFirst() {
|
|
398
|
+
stopAutoPlay();
|
|
399
|
+
goToReplayMove(0);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function stepPrev() {
|
|
403
|
+
stopAutoPlay();
|
|
404
|
+
if (matchMoveIndex > 0) goToReplayMove(matchMoveIndex - 1);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function stepNext() {
|
|
408
|
+
if (activeMatch && matchMoveIndex < activeMatch.moves.length) {
|
|
409
|
+
goToReplayMove(matchMoveIndex + 1);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function stepLast() {
|
|
414
|
+
stopAutoPlay();
|
|
415
|
+
if (activeMatch) goToReplayMove(activeMatch.moves.length);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function jumpToTurningPoint() {
|
|
419
|
+
stopAutoPlay();
|
|
420
|
+
if (activeMatch) goToReplayMove(activeMatch.keyMoveIndex);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function toggleAutoPlay() {
|
|
424
|
+
if (autoPlayTimer) {
|
|
425
|
+
stopAutoPlay();
|
|
426
|
+
} else {
|
|
427
|
+
if (!activeMatch) return;
|
|
428
|
+
if (matchMoveIndex >= activeMatch.moves.length) {
|
|
429
|
+
goToReplayMove(0);
|
|
430
|
+
}
|
|
431
|
+
const btn = document.getElementById("autoPlayBtn");
|
|
432
|
+
if (btn) btn.innerText = "Pause";
|
|
433
|
+
autoPlayTimer = setInterval(() => {
|
|
434
|
+
if (activeMatch && matchMoveIndex < activeMatch.moves.length) {
|
|
435
|
+
stepNext();
|
|
436
|
+
} else {
|
|
437
|
+
stopAutoPlay();
|
|
438
|
+
}
|
|
439
|
+
}, 1200);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function stopAutoPlay() {
|
|
444
|
+
if (autoPlayTimer) {
|
|
445
|
+
clearInterval(autoPlayTimer);
|
|
446
|
+
autoPlayTimer = null;
|
|
447
|
+
const btn = document.getElementById("autoPlayBtn");
|
|
448
|
+
if (btn) btn.innerText = "Auto Play";
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function classifyCurrentMatch() {
|
|
453
|
+
if (!activeMatch) return;
|
|
454
|
+
const btn = document.getElementById("classifyBtn") as HTMLButtonElement | null;
|
|
455
|
+
if (btn) {
|
|
456
|
+
btn.innerText = "Classifying with System One...";
|
|
457
|
+
btn.disabled = true;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
try {
|
|
461
|
+
const cl = await matchStudio.classifyMatch(activeMatch);
|
|
462
|
+
|
|
463
|
+
const card = document.getElementById("classificationCard");
|
|
464
|
+
if (card) card.style.display = "flex";
|
|
465
|
+
|
|
466
|
+
const aEl = document.getElementById("classArchetype");
|
|
467
|
+
if (aEl) aEl.innerText = cl.archetype;
|
|
468
|
+
|
|
469
|
+
const sEl = document.getElementById("classSacrifice");
|
|
470
|
+
if (sEl) sEl.innerText = cl.hasDecisiveSacrifice ? "Decisive Sacrifice Confirmed" : "No Decisive Sacrifice";
|
|
471
|
+
|
|
472
|
+
const bScore = document.getElementById("classBrillianceScore");
|
|
473
|
+
if (bScore) bScore.innerText = cl.aestheticBrilliance.score.toFixed(1) + " / 3.0";
|
|
474
|
+
|
|
475
|
+
const bLevel = document.getElementById("classBrillianceLevel");
|
|
476
|
+
if (bLevel) bLevel.innerText = cl.aestheticBrilliance.level;
|
|
477
|
+
|
|
478
|
+
const shScore = document.getElementById("classSharpnessScore");
|
|
479
|
+
if (shScore) shScore.innerText = cl.overallSharpness.score.toFixed(1) + " / 3.0";
|
|
480
|
+
|
|
481
|
+
const shLevel = document.getElementById("classSharpnessLevel");
|
|
482
|
+
if (shLevel) shLevel.innerText = cl.overallSharpness.level;
|
|
483
|
+
|
|
484
|
+
const tMove = document.getElementById("classTurningMove");
|
|
485
|
+
if (tMove) tMove.innerText = cl.turningPoint.san;
|
|
486
|
+
|
|
487
|
+
const tNum = document.getElementById("classTurningNum");
|
|
488
|
+
if (tNum) tNum.innerText = `Move ${cl.turningPoint.moveNumber}`;
|
|
489
|
+
|
|
490
|
+
const bdT = document.getElementById("bdTactical");
|
|
491
|
+
if (bdT) bdT.innerText = cl.strategicBreakdown.tacticalStrikesPct + "%";
|
|
492
|
+
|
|
493
|
+
const bdPw = document.getElementById("bdPawn");
|
|
494
|
+
if (bdPw) bdPw.innerText = cl.strategicBreakdown.pawnBreaksPct + "%";
|
|
495
|
+
|
|
496
|
+
const bdPc = document.getElementById("bdPiece");
|
|
497
|
+
if (bdPc) bdPc.innerText = cl.strategicBreakdown.pieceActivationPct + "%";
|
|
498
|
+
|
|
499
|
+
const bdPr = document.getElementById("bdProphyl");
|
|
500
|
+
if (bdPr) bdPr.innerText = cl.strategicBreakdown.prophylaxisPct + "%";
|
|
501
|
+
|
|
502
|
+
const verd = document.getElementById("classVerdict");
|
|
503
|
+
if (verd) verd.innerText = cl.verdict;
|
|
504
|
+
} catch (e) {
|
|
505
|
+
console.error("Failed to classify match", e);
|
|
506
|
+
} finally {
|
|
507
|
+
if (btn) {
|
|
508
|
+
btn.innerText = "Classify Match with System One";
|
|
509
|
+
btn.disabled = false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/* API Key Configuration */
|
|
515
|
+
function checkKeyStatus() {
|
|
516
|
+
const status = getApiKeyStatus();
|
|
517
|
+
const badge = document.getElementById("backendBadge");
|
|
518
|
+
const dot = document.getElementById("backendStatusDot");
|
|
519
|
+
const text = document.getElementById("backendStatusText");
|
|
520
|
+
const btnLabel = document.getElementById("keyBtnLabel");
|
|
521
|
+
|
|
522
|
+
if (status.isLive) {
|
|
523
|
+
if (badge) badge.className = "badge-status";
|
|
524
|
+
if (dot) dot.className = "status-dot live";
|
|
525
|
+
if (text) text.innerText = "Live API (jev-latest)";
|
|
526
|
+
if (btnLabel) btnLabel.innerText = status.maskedKey || "Live Key";
|
|
527
|
+
} else {
|
|
528
|
+
if (badge) badge.className = "badge-status badge-sim";
|
|
529
|
+
if (dot) dot.className = "status-dot sim";
|
|
530
|
+
if (text) text.innerText = "Simulation Mode (jev-latest calibrated)";
|
|
531
|
+
if (btnLabel) btnLabel.innerText = "API Key";
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function openKeyModal() {
|
|
536
|
+
const modal = document.getElementById("keyModal");
|
|
537
|
+
if (modal) modal.style.display = "flex";
|
|
538
|
+
const st = document.getElementById("modalStatus");
|
|
539
|
+
if (st) st.innerText = "";
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function closeKeyModal() {
|
|
543
|
+
const modal = document.getElementById("keyModal");
|
|
544
|
+
if (modal) modal.style.display = "none";
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function toggleKeyVisibility() {
|
|
548
|
+
const input = document.getElementById("keyInput") as HTMLInputElement | null;
|
|
549
|
+
const btn = document.getElementById("toggleVisibilityBtn");
|
|
550
|
+
if (!input || !btn) return;
|
|
551
|
+
const isPass = input.type === "password";
|
|
552
|
+
input.type = isPass ? "text" : "password";
|
|
553
|
+
btn.innerText = isPass ? "Hide" : "Show";
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function saveApiKey() {
|
|
557
|
+
const input = document.getElementById("keyInput") as HTMLInputElement | null;
|
|
558
|
+
if (!input) return;
|
|
559
|
+
const key = input.value.trim();
|
|
560
|
+
const statusEl = document.getElementById("modalStatus");
|
|
561
|
+
if (!key) {
|
|
562
|
+
if (statusEl) statusEl.innerHTML = '<span style="color: var(--danger)">Please enter a valid API key.</span>';
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
setApiKey(key);
|
|
566
|
+
if (statusEl) {
|
|
567
|
+
statusEl.innerHTML = '<span style="color: var(--success)">Successfully connected to live jev-latest model.</span>';
|
|
568
|
+
}
|
|
569
|
+
input.value = "";
|
|
570
|
+
checkKeyStatus();
|
|
571
|
+
setTimeout(closeKeyModal, 1000);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function clearApiKey() {
|
|
575
|
+
setApiKey(null);
|
|
576
|
+
const statusEl = document.getElementById("modalStatus");
|
|
577
|
+
if (statusEl) {
|
|
578
|
+
statusEl.innerHTML = '<span style="color: var(--warning)">Switched back to calibrated simulation mode.</span>';
|
|
579
|
+
}
|
|
580
|
+
checkKeyStatus();
|
|
581
|
+
setTimeout(closeKeyModal, 800);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Bind to window for HTML click handlers
|
|
585
|
+
const w = window as any;
|
|
586
|
+
w.renderBoard = renderBoard;
|
|
587
|
+
w.playMove = playMove;
|
|
588
|
+
w.resolveAndPlayMove = resolveAndPlayMove;
|
|
589
|
+
w.setChip = setChip;
|
|
590
|
+
w.selectPersona = selectPersona;
|
|
591
|
+
w.makePersonaMove = makePersonaMove;
|
|
592
|
+
w.resetGame = resetGame;
|
|
593
|
+
w.undoMove = undoMove;
|
|
594
|
+
w.onMatchSelectChange = onMatchSelectChange;
|
|
595
|
+
w.goToReplayMove = goToReplayMove;
|
|
596
|
+
w.stepFirst = stepFirst;
|
|
597
|
+
w.stepPrev = stepPrev;
|
|
598
|
+
w.stepNext = stepNext;
|
|
599
|
+
w.stepLast = stepLast;
|
|
600
|
+
w.jumpToTurningPoint = jumpToTurningPoint;
|
|
601
|
+
w.toggleAutoPlay = toggleAutoPlay;
|
|
602
|
+
w.classifyCurrentMatch = classifyCurrentMatch;
|
|
603
|
+
w.openKeyModal = openKeyModal;
|
|
604
|
+
w.closeKeyModal = closeKeyModal;
|
|
605
|
+
w.toggleKeyVisibility = toggleKeyVisibility;
|
|
606
|
+
w.saveApiKey = saveApiKey;
|
|
607
|
+
w.clearApiKey = clearApiKey;
|
|
608
|
+
|
|
609
|
+
// DOM Ready
|
|
610
|
+
window.addEventListener("DOMContentLoaded", () => {
|
|
611
|
+
renderBoard(currentFen);
|
|
612
|
+
updateLegalMoves();
|
|
613
|
+
checkKeyStatus();
|
|
614
|
+
initClassicMatches();
|
|
615
|
+
});
|