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.
Files changed (67) hide show
  1. package/ARCHITECTURE.md +148 -0
  2. package/README.md +109 -0
  3. package/dist/chessEngine.d.ts +36 -0
  4. package/dist/chessEngine.d.ts.map +1 -0
  5. package/dist/chessEngine.js +173 -0
  6. package/dist/chessEngine.js.map +1 -0
  7. package/dist/classicMatches.d.ts +50 -0
  8. package/dist/classicMatches.d.ts.map +1 -0
  9. package/dist/classicMatches.js +223 -0
  10. package/dist/classicMatches.js.map +1 -0
  11. package/dist/demo.d.ts +2 -0
  12. package/dist/demo.d.ts.map +1 -0
  13. package/dist/demo.js +142 -0
  14. package/dist/demo.js.map +1 -0
  15. package/dist/gameReviewer.d.ts +11 -0
  16. package/dist/gameReviewer.d.ts.map +1 -0
  17. package/dist/gameReviewer.js +89 -0
  18. package/dist/gameReviewer.js.map +1 -0
  19. package/dist/index.d.ts +9 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +9 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/moveEvaluator.d.ts +11 -0
  24. package/dist/moveEvaluator.d.ts.map +1 -0
  25. package/dist/moveEvaluator.js +103 -0
  26. package/dist/moveEvaluator.js.map +1 -0
  27. package/dist/moveResolver.d.ts +15 -0
  28. package/dist/moveResolver.d.ts.map +1 -0
  29. package/dist/moveResolver.js +97 -0
  30. package/dist/moveResolver.js.map +1 -0
  31. package/dist/personaEngine.d.ts +15 -0
  32. package/dist/personaEngine.d.ts.map +1 -0
  33. package/dist/personaEngine.js +151 -0
  34. package/dist/personaEngine.js.map +1 -0
  35. package/dist/server.d.ts +2 -0
  36. package/dist/server.d.ts.map +1 -0
  37. package/dist/server.js +1490 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/typeSafeClient.d.ts +13 -0
  40. package/dist/typeSafeClient.d.ts.map +1 -0
  41. package/dist/typeSafeClient.js +336 -0
  42. package/dist/typeSafeClient.js.map +1 -0
  43. package/dist/types.d.ts +100 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +5 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/web/main.d.ts +2 -0
  48. package/dist/web/main.d.ts.map +1 -0
  49. package/dist/web/main.js +620 -0
  50. package/dist/web/main.js.map +1 -0
  51. package/package.json +35 -0
  52. package/public/app.js +45 -0
  53. package/public/index.html +790 -0
  54. package/src/chessEngine.ts +191 -0
  55. package/src/classicMatches.ts +291 -0
  56. package/src/demo.ts +160 -0
  57. package/src/gameReviewer.ts +113 -0
  58. package/src/index.ts +8 -0
  59. package/src/moveEvaluator.ts +122 -0
  60. package/src/moveResolver.ts +120 -0
  61. package/src/personaEngine.ts +193 -0
  62. package/src/server.ts +1509 -0
  63. package/src/typeSafeClient.ts +321 -0
  64. package/src/types.ts +118 -0
  65. package/src/web/main.ts +615 -0
  66. package/tests/chess.test.ts +206 -0
  67. package/tsconfig.json +21 -0
@@ -0,0 +1,191 @@
1
+ import { Chess, type PieceSymbol, type Square } from "chess.js";
2
+ import type { AnnotatedMove, BoardContext, GamePhase, MaterialBalance } from "./types.js";
3
+
4
+ const PIECE_NAMES: Record<PieceSymbol, string> = {
5
+ p: "pawn",
6
+ n: "knight",
7
+ b: "bishop",
8
+ r: "rook",
9
+ q: "queen",
10
+ k: "king",
11
+ };
12
+
13
+ const PIECE_VALUES: Record<PieceSymbol, number> = {
14
+ p: 1,
15
+ n: 3,
16
+ b: 3,
17
+ r: 5,
18
+ q: 9,
19
+ k: 0,
20
+ };
21
+
22
+ export class ChessEngine {
23
+ public chess: Chess;
24
+
25
+ constructor(fen?: string) {
26
+ this.chess = new Chess(fen);
27
+ }
28
+
29
+ public reset(): void {
30
+ this.chess.reset();
31
+ }
32
+
33
+ public load(fen: string): boolean {
34
+ try {
35
+ this.chess.load(fen);
36
+ return true;
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ public fen(): string {
43
+ return this.chess.fen();
44
+ }
45
+
46
+ public turn(): "white" | "black" {
47
+ return this.chess.turn() === "w" ? "white" : "black";
48
+ }
49
+
50
+ public isCheck(): boolean {
51
+ return this.chess.inCheck();
52
+ }
53
+
54
+ public isGameOver(): boolean {
55
+ return this.chess.isGameOver();
56
+ }
57
+
58
+ public history(): string[] {
59
+ return this.chess.history();
60
+ }
61
+
62
+ /**
63
+ * Generates all legal moves in the current position, richly annotated with semantic facts.
64
+ * This is 100% deterministic code.
65
+ */
66
+ public getAnnotatedMoves(): AnnotatedMove[] {
67
+ const rawMoves = this.chess.moves({ verbose: true });
68
+ return rawMoves.map((m) => {
69
+ const pieceName = PIECE_NAMES[m.piece] ?? m.piece;
70
+ const isCastling = m.san === "O-O" || m.san === "O-O-O";
71
+ const isCapture = Boolean(m.captured);
72
+ const capturedName = m.captured ? PIECE_NAMES[m.captured] : undefined;
73
+
74
+ let description = `${pieceName} moves from ${m.from} to ${m.to}`;
75
+ if (isCastling) {
76
+ description = m.san === "O-O" ? "Kingside castle" : "Queenside castle";
77
+ } else if (isCapture) {
78
+ description = `${pieceName} on ${m.from} captures ${capturedName} on ${m.to}`;
79
+ }
80
+ if (m.san.includes("+")) {
81
+ description += " with check";
82
+ } else if (m.san.includes("#")) {
83
+ description += " with checkmate!";
84
+ }
85
+
86
+ return {
87
+ san: m.san,
88
+ from: m.from,
89
+ to: m.to,
90
+ piece: pieceName,
91
+ color: m.color === "w" ? "white" : "black",
92
+ isCapture,
93
+ capturedPiece: capturedName,
94
+ isCheck: m.san.includes("+") || m.san.includes("#"),
95
+ isCastling,
96
+ description,
97
+ };
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Calculates material balance between white and black.
103
+ */
104
+ public getMaterialBalance(): MaterialBalance {
105
+ const board = this.chess.board();
106
+ let whiteScore = 0;
107
+ let blackScore = 0;
108
+
109
+ for (const row of board) {
110
+ for (const square of row) {
111
+ if (!square) continue;
112
+ const val = PIECE_VALUES[square.type];
113
+ if (square.color === "w") whiteScore += val;
114
+ else blackScore += val;
115
+ }
116
+ }
117
+
118
+ const diff = whiteScore - blackScore;
119
+ let description = "Material is exactly equal";
120
+ if (diff > 0) {
121
+ description = `White is up +${diff} point${diff > 1 ? "s" : ""}`;
122
+ } else if (diff < 0) {
123
+ description = `Black is up +${Math.abs(diff)} point${Math.abs(diff) > 1 ? "s" : ""}`;
124
+ }
125
+
126
+ return { whiteScore, blackScore, diff, description };
127
+ }
128
+
129
+ /**
130
+ * Deterministically infers game phase based on remaining major/minor pieces and move number.
131
+ */
132
+ public getGamePhase(): GamePhase {
133
+ const board = this.chess.board();
134
+ let totalPieces = 0;
135
+ let queenCount = 0;
136
+
137
+ for (const row of board) {
138
+ for (const square of row) {
139
+ if (!square) continue;
140
+ if (square.type !== "p" && square.type !== "k") {
141
+ totalPieces++;
142
+ if (square.type === "q") queenCount++;
143
+ }
144
+ }
145
+ }
146
+
147
+ const moveCount = this.chess.history().length;
148
+ if (moveCount < 16 && totalPieces >= 12) {
149
+ return "opening";
150
+ }
151
+ if (totalPieces <= 6 || queenCount === 0) {
152
+ return "endgame";
153
+ }
154
+ return "middlegame";
155
+ }
156
+
157
+ /**
158
+ * Creates the complete structured context representing the board.
159
+ * This matches TypeSafe's principle: provide clear, structured JSON state.
160
+ */
161
+ public getBoardContext(): BoardContext {
162
+ const history = this.chess.history();
163
+ const recentHistory = history.slice(-6);
164
+
165
+ return {
166
+ fen: this.chess.fen(),
167
+ turn: this.turn(),
168
+ moveNumber: Math.floor(history.length / 2) + 1,
169
+ inCheck: this.isCheck(),
170
+ materialBalance: this.getMaterialBalance(),
171
+ gamePhase: this.getGamePhase(),
172
+ recentHistory,
173
+ candidateMoves: this.getAnnotatedMoves(),
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Deterministically applies a move in SAN (Standard Algebraic Notation).
179
+ */
180
+ public makeMove(sanOrUci: string): AnnotatedMove | null {
181
+ const candidate = this.getAnnotatedMoves().find(
182
+ (m) => m.san === sanOrUci || `${m.from}${m.to}` === sanOrUci
183
+ );
184
+ if (!candidate) {
185
+ return null;
186
+ }
187
+
188
+ this.chess.move(candidate.san);
189
+ return candidate;
190
+ }
191
+ }
@@ -0,0 +1,291 @@
1
+ import { ChessEngine } from "./chessEngine.js";
2
+ import { choice, getTypeSafeClient, noul, score } from "./typeSafeClient.js";
3
+
4
+ export interface ClassicMatch {
5
+ id: string;
6
+ title: string;
7
+ white: string;
8
+ black: string;
9
+ year: number;
10
+ event: string;
11
+ result: "1-0" | "0-1" | "1/2-1/2";
12
+ eco: string;
13
+ opening: string;
14
+ description: string;
15
+ moves: string[]; // SAN moves
16
+ keyMoveIndex: number; // Index of the iconic turning point move
17
+ }
18
+
19
+ export interface MatchClassification {
20
+ matchId: string;
21
+ title: string;
22
+ archetype: string;
23
+ aestheticBrilliance: {
24
+ score: number;
25
+ level: string;
26
+ confidence: number;
27
+ };
28
+ overallSharpness: {
29
+ score: number;
30
+ level: string;
31
+ };
32
+ hasDecisiveSacrifice: boolean;
33
+ turningPoint: {
34
+ moveNumber: number;
35
+ san: string;
36
+ annotation: string;
37
+ };
38
+ strategicBreakdown: {
39
+ tacticalStrikesPct: number;
40
+ pawnBreaksPct: number;
41
+ prophylaxisPct: number;
42
+ pieceActivationPct: number;
43
+ };
44
+ verdict: string;
45
+ }
46
+
47
+ export const CLASSIC_MATCHES: ClassicMatch[] = [
48
+ {
49
+ id: "immortal-game",
50
+ title: "The Immortal Game",
51
+ white: "Adolf Anderssen",
52
+ black: "Lionel Kieseritzky",
53
+ year: 1851,
54
+ event: "London (Casual match during 1st International Tournament)",
55
+ result: "1-0",
56
+ eco: "C33",
57
+ opening: "King's Gambit Accepted",
58
+ description:
59
+ "Anderssen gave up both rooks and a bishop, followed by his queen, delivering checkmate with his three remaining minor pieces.",
60
+ keyMoveIndex: 38, // 20. Ke2! or 22. Qf6+!
61
+ moves: [
62
+ "e4", "e5", "f4", "exf4", "Bc4", "Qh4+", "Kf1", "b5", "Bxb5", "Nf6",
63
+ "Nf3", "Qh6", "d3", "Nh5", "Nh4", "Qg5", "Nf5", "c6", "g4", "Nf6",
64
+ "Rg1", "cxb5", "h4", "Qg6", "h5", "Qg5", "Qf3", "Ng8", "Bxf4", "Qf6",
65
+ "Nc3", "Bc5", "Nd5", "Qxb2", "Bd6", "Bxg1", "e5", "Qxa1+", "Ke2", "Na6",
66
+ "Nxg7+", "Kd8", "Qf6+", "Nxf6", "Be7#"
67
+ ],
68
+ },
69
+ {
70
+ id: "opera-game",
71
+ title: "The Opera Game",
72
+ white: "Paul Morphy",
73
+ black: "Duke of Brunswick & Count Isouard",
74
+ year: 1858,
75
+ event: "Paris Opera House (Norma intermission)",
76
+ result: "1-0",
77
+ eco: "C41",
78
+ opening: "Philidor Defense",
79
+ description:
80
+ "Morphy's quintessential demonstration of rapid development, open lines, and deflection queen sacrifice ending in checkmate with rook and bishop.",
81
+ keyMoveIndex: 30, // 16. Qb8+!
82
+ moves: [
83
+ "e4", "e5", "Nf3", "d6", "d4", "Bg4", "dxe5", "Bxf3", "Qxf3", "dxe5",
84
+ "Bc4", "Nf6", "Qb3", "Qe7", "Nc3", "c6", "Bg5", "b5", "Nxb5", "cxb5",
85
+ "Bxb5+", "Nbd7", "O-O-O", "Rd8", "Rxd7", "Rxd7", "Rd1", "Qe6", "Bxd7+", "Nxd7",
86
+ "Qb8+", "Nxb8", "Rd8#"
87
+ ],
88
+ },
89
+ {
90
+ id: "game-of-the-century",
91
+ title: "Game of the Century",
92
+ white: "Donald Byrne",
93
+ black: "Bobby Fischer (age 13)",
94
+ year: 1956,
95
+ event: "Rosenwald Memorial Tournament, New York",
96
+ result: "0-1",
97
+ eco: "D92",
98
+ opening: "Grünfeld Defence, 5.Bf4",
99
+ description:
100
+ "A 13-year-old Bobby Fischer sacrifices his queen on move 17 (Be6!!) to unleash an unstoppable windmill attack with bishop and knight.",
101
+ keyMoveIndex: 33, // 17... Be6!!
102
+ moves: [
103
+ "Nf3", "Nf6", "c4", "g6", "Nc3", "Bg7", "d4", "O-O", "Bf4", "d5",
104
+ "Qb3", "dxc4", "Qxc4", "c6", "e4", "Nbd7", "Rd1", "Nb6", "Qc5", "Bg4",
105
+ "Bg5", "Na4", "Qa3", "Nxc3", "bxc3", "Nxe4", "Bxe7", "Qb6", "Bc4", "Nxc3",
106
+ "Bc5", "Rfe8+", "Kf1", "Be6", "Bxb6", "Bxc4+", "Kg1", "Ne2+", "Kf1", "Nxd4+",
107
+ "Kg1", "Ne2+", "Kf1", "Nc3+", "Kg1", "axb6", "Qb4", "Ra4", "Qxb6", "Nxd1",
108
+ "h3", "Rxa2", "Kh2", "Nxf2", "Re1", "Rxe1", "Qd8+", "Bf8", "Nxe1", "Bd5",
109
+ "Nf3", "Ne4", "Qb8", "b5", "h4", "h5", "Ne5", "Kg7", "Kg1", "Bc5+",
110
+ "Kf1", "Ng3+", "Ke1", "Bb4+", "Kd1", "Bb3+", "Kc1", "Ne2+", "Kb1", "Nc3+",
111
+ "Kc1", "Rc2#"
112
+ ],
113
+ },
114
+ {
115
+ id: "kasparov-immortal",
116
+ title: "Kasparov's Immortal",
117
+ white: "Garry Kasparov",
118
+ black: "Veselin Topalov",
119
+ year: 1999,
120
+ event: "Hoogovens Group A, Wijk aan Zee",
121
+ result: "1-0",
122
+ eco: "B07",
123
+ opening: "Pirc Defense",
124
+ description:
125
+ "Kasparov sacrifices his rook on d4 (24. Rxd4!!) initiating a spectacular 15-move king hunt that drove the black king across the entire board.",
126
+ keyMoveIndex: 46, // 24. Rxd4!!
127
+ moves: [
128
+ "e4", "d6", "d4", "Nf6", "Nc3", "g6", "Be3", "Bg7", "Qd2", "c6",
129
+ "f3", "b5", "Nge2", "Nbd7", "Bh6", "Bxh6", "Qxh6", "Bb7", "a3", "e5",
130
+ "O-O-O", "Qe7", "Kb1", "a6", "Nc1", "O-O-O", "Nb3", "exd4", "Rxd4", "c5",
131
+ "Rd1", "Nb6", "g3", "Kb8", "Na5", "Ba8", "Bh3", "d5", "Qf4+", "Ka7",
132
+ "Rhe1", "d4", "Nd5", "Nbxd5", "exd5", "Qd6", "Rxd4", "cxd4", "Re7+", "Kb6",
133
+ "Qxd4+", "Kxa5", "b4+", "Ka4", "Qc3", "Qxd5", "Ra7", "Bb7", "Rxb7", "Qc4",
134
+ "Qxf6", "Kxa3", "Qxa6+", "Kxb4", "c3+", "Kxc3", "Qa1+", "Kd2", "Qb2+", "Kd1",
135
+ "Bf1", "Rd2", "Rd7+", "Rxd7", "Bxc4", "bxc4", "Qxh8", "Rd3", "Qa8", "c3",
136
+ "Qa4+", "Ke1", "f4", "f5", "Kc1", "Rd2", "Qa7"
137
+ ],
138
+ },
139
+ {
140
+ id: "tal-larsen-1965",
141
+ title: "Tal's Wild Central Sacrifice",
142
+ white: "Mikhail Tal",
143
+ black: "Bent Larsen",
144
+ year: 1965,
145
+ event: "Candidates Semifinal, Bled, Match Game 10",
146
+ result: "1-0",
147
+ eco: "B57",
148
+ opening: "Sicilian Defense, Richter-Rauzer",
149
+ description:
150
+ "Tal's famous intuitive piece sacrifice 16. Nxd5!! blowing open the center against Larsen's uncastled king to clinch the match.",
151
+ keyMoveIndex: 30, // 16. Nxd5!!
152
+ moves: [
153
+ "e4", "c5", "Nf3", "Nc6", "d4", "cxd4", "Nxd4", "e6", "Nc3", "d6",
154
+ "Be3", "Nf6", "f4", "Be7", "Qf3", "O-O", "O-O-O", "Qc7", "Ndb5", "Qb8",
155
+ "g4", "a6", "Nd4", "Nxd4", "Bxd4", "b5", "g5", "Nd7", "Bd3", "b4",
156
+ "Nd5", "exd5", "exd5", "f5", "Rde1", "Rf7", "h4", "Bb7", "Bxf5", "Rxf5",
157
+ "Rxe7", "Ne5", "Qe4", "Qf8", "fxe5", "Rf4", "Qe3", "Rf3", "Qe2", "Qxe7",
158
+ "Qxf3", "dxe5", "Re1", "Rd8", "Rxe5", "Qd6", "Qf4", "Rf8", "Qe4", "Bc8",
159
+ "b3", "a5", "h5", "Bd7", "h6"
160
+ ],
161
+ },
162
+ ];
163
+
164
+ const MATCH_ARCHETYPES = {
165
+ romantic_swashbuckler: "Cascading material sacrifices culminating in a forced mating net",
166
+ dynamic_initiative: "Relentless piece tempo and coordinated piece pressure dominating passive defenders",
167
+ positional_squeeze: "Prophylactic clamp, suffocating opponent counterplay before technical breakthrough",
168
+ tactical_firestorm: "Sharp double-edged complications with king chases and critical calculate-or-die tactics",
169
+ };
170
+
171
+ const BRILLIANCE_RUBRIC = [
172
+ "Standard technical victory with routine exchanges",
173
+ "Well-played competitive game with solid tactical execution",
174
+ "Brilliant game featuring deep calculation and attractive piece play",
175
+ "Immortal artistic masterpiece that redefined chess literature",
176
+ ] as const;
177
+
178
+ const MATCH_SHARPNESS_RUBRIC = [
179
+ "Solid and quiet; long positional maneuvering behind closed pawns",
180
+ "Balanced fighting contest with strategic tension",
181
+ "Very sharp; frequent piece contact, king exposure, and tactical threats",
182
+ "Wild tactical hurricane; mutual piece sacrifices and all-out monarch assault",
183
+ ] as const;
184
+
185
+ export class ClassicMatchStudio {
186
+ private get client() {
187
+ return getTypeSafeClient();
188
+ }
189
+
190
+ /**
191
+ * Classifies a classic chess match using TypeSafe System One.
192
+ */
193
+ public async classifyMatch(match: ClassicMatch): Promise<MatchClassification> {
194
+ const turningMove = match.moves[match.keyMoveIndex] ?? match.moves[match.moves.length - 1] ?? "e4";
195
+ const moveNum = Math.floor((match.keyMoveIndex) / 2) + 1;
196
+
197
+ const state = {
198
+ match_meta: {
199
+ id: match.id,
200
+ title: match.title,
201
+ white: match.white,
202
+ black: match.black,
203
+ year: match.year,
204
+ eco: match.eco,
205
+ opening: match.opening,
206
+ result: match.result,
207
+ total_moves: match.moves.length,
208
+ },
209
+ turning_point: {
210
+ move_number: moveNum,
211
+ san: turningMove,
212
+ description: match.description,
213
+ },
214
+ moves_sample: match.moves.slice(0, 30),
215
+ };
216
+
217
+ const response = await this.client.systemOne({
218
+ state,
219
+ questions: {
220
+ archetype: choice(
221
+ "Which historical chess archetype best classifies `match_meta.title`?",
222
+ MATCH_ARCHETYPES
223
+ ),
224
+ brilliance: score(
225
+ "Rate the aesthetic and instructive brilliance of this classic match:",
226
+ BRILLIANCE_RUBRIC
227
+ ),
228
+ sharpness: score(
229
+ "Rate the overall tactical sharpness and volatility of this game:",
230
+ MATCH_SHARPNESS_RUBRIC
231
+ ),
232
+ decisive_sacrifice: noul(
233
+ "Does this match feature a sound, decisive piece or queen sacrifice for the initiative?"
234
+ ),
235
+ },
236
+ });
237
+
238
+ const archetype = response.answers.archetype.choice;
239
+ const brillianceScore = response.answers.brilliance.score;
240
+ const sharpnessScore = response.answers.sharpness.score;
241
+ const hasSac = response.answers.decisive_sacrifice.noul >= 0.6;
242
+
243
+ // Deterministically count tactical themes from move properties
244
+ let checksAndCaptures = 0;
245
+ let pawnMoves = 0;
246
+ for (const m of match.moves) {
247
+ if (m.includes("+") || m.includes("x") || m.includes("#")) checksAndCaptures++;
248
+ else if (m.toLowerCase()[0] >= "a" && m.toLowerCase()[0] <= "h" && !m.includes("O")) pawnMoves++;
249
+ }
250
+ const total = Math.max(1, match.moves.length);
251
+ const tacticalPct = Math.round((checksAndCaptures / total) * 100);
252
+ const pawnPct = Math.round((pawnMoves / total) * 100);
253
+ const piecePct = Math.max(10, 100 - tacticalPct - pawnPct);
254
+ const prophylPct = Math.round(piecePct * 0.35);
255
+
256
+ let verdict = `${match.title} (${match.year}) is a classic ${archetype.replace(/_/g, " ")}. `;
257
+ if (brillianceScore >= 2.3) {
258
+ verdict += "Widely celebrated as one of the finest artistic achievements in the history of the game.";
259
+ } else {
260
+ verdict += "A deeply instructive showcase of classical principles and aggressive tactical conversion.";
261
+ }
262
+
263
+ return {
264
+ matchId: match.id,
265
+ title: match.title,
266
+ archetype: archetype.replace(/_/g, " ").toUpperCase(),
267
+ aestheticBrilliance: {
268
+ score: brillianceScore,
269
+ level: BRILLIANCE_RUBRIC[Math.min(BRILLIANCE_RUBRIC.length - 1, Math.round(brillianceScore))] ?? "Masterpiece",
270
+ confidence: response.answers.brilliance.confidence,
271
+ },
272
+ overallSharpness: {
273
+ score: sharpnessScore,
274
+ level: MATCH_SHARPNESS_RUBRIC[Math.min(MATCH_SHARPNESS_RUBRIC.length - 1, Math.round(sharpnessScore))] ?? "Sharp",
275
+ },
276
+ hasDecisiveSacrifice: hasSac,
277
+ turningPoint: {
278
+ moveNumber: moveNum,
279
+ san: turningMove,
280
+ annotation: match.description,
281
+ },
282
+ strategicBreakdown: {
283
+ tacticalStrikesPct: tacticalPct,
284
+ pawnBreaksPct: pawnPct,
285
+ prophylaxisPct: prophylPct,
286
+ pieceActivationPct: piecePct,
287
+ },
288
+ verdict,
289
+ };
290
+ }
291
+ }
package/src/demo.ts ADDED
@@ -0,0 +1,160 @@
1
+ import { ChessEngine } from "./chessEngine.js";
2
+ import { MoveResolver } from "./moveResolver.js";
3
+ import { MoveEvaluator } from "./moveEvaluator.js";
4
+ import { PersonaEngine, CHESS_PERSONAS } from "./personaEngine.js";
5
+ import { GameReviewer } from "./gameReviewer.js";
6
+ import { getTypeSafeClient } from "./typeSafeClient.js";
7
+
8
+ async function runDemo() {
9
+ const client = getTypeSafeClient();
10
+ console.log("================================================================================");
11
+ console.log(" ♟️ JEV-CHESS: DESIGNING CHESS MOVES & GAMES WITH TYPESAFE AI ♟️");
12
+ console.log("================================================================================");
13
+ console.log(`Backend Mode: ${client.isLive ? "🟢 LIVE (TypeSafe API - jev-latest)" : "🟡 SIMULATED (Calibrated offline fallback)"}`);
14
+ console.log("Principle: Code enforces board physics & rules; TypeSafe System One provides fast semantic judgment.\n");
15
+
16
+ const engine = new ChessEngine();
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // SECTION 1: MOVE DESIGN - NATURAL LANGUAGE INTENT RESOLUTION
20
+ // ---------------------------------------------------------------------------
21
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
22
+ console.log("1. MOVE RESOLUTION: 'Select Instead of Generate' Pattern (TypeSafe Choice)");
23
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
24
+
25
+ // Play standard 1. e4 e5
26
+ engine.makeMove("e4");
27
+ engine.makeMove("e5");
28
+ console.log(`Current Board FEN: ${engine.fen()}`);
29
+ console.log(`Turn: ${engine.turn()} | Move: 2\n`);
30
+
31
+ const resolver = new MoveResolver();
32
+
33
+ const queries = [
34
+ "Develop my knight towards the center and attack their pawn",
35
+ "Push the queen's pawn two squares forward to contest the center",
36
+ "Teleport my rook directly to the other side", // Intent with no legal match
37
+ ];
38
+
39
+ for (const q of queries) {
40
+ console.log(`> Human command: "${q}"`);
41
+ const result = await resolver.resolveIntent(engine, q);
42
+ if (result.matchedMove) {
43
+ console.log(` ✅ Matched Legal Move: ${result.matchedMove.san} (${result.matchedMove.description})`);
44
+ console.log(` Confidence: ${(result.confidence * 100).toFixed(1)}%`);
45
+ } else {
46
+ console.log(` ⚠️ Ambiguous or Illegal Command!`);
47
+ console.log(` Confidence: ${(result.confidence * 100).toFixed(1)}% (Clarification Required)`);
48
+ if (result.alternativeCandidates.length > 0) {
49
+ console.log(` Did you mean: ${result.alternativeCandidates.join(", ")}?`);
50
+ }
51
+ }
52
+ console.log();
53
+ }
54
+
55
+ // Play White 2. Nf3, Black 2. Nc6, White 3. Bc4, Black 3. Bc5
56
+ engine.makeMove("Nf3");
57
+ engine.makeMove("Nc6");
58
+ engine.makeMove("Bc4");
59
+ engine.makeMove("Bc5");
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // SECTION 2: MOVE DESIGN - MULTI-DIMENSIONAL QUALITATIVE EVALUATION
63
+ // ---------------------------------------------------------------------------
64
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
65
+ console.log("2. MOVE EVALUATION: Parallel Atomic Judgments (Score, Choice, Noul)");
66
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
67
+
68
+ const evaluator = new MoveEvaluator();
69
+ const testMoves = ["c3", "O-O", "Bxf7+"];
70
+
71
+ for (const san of testMoves) {
72
+ // Check if legal in current or alternate position
73
+ const candidates = engine.getAnnotatedMoves();
74
+ let moveObj = candidates.find((m) => m.san === san);
75
+ if (!moveObj) {
76
+ // Create synthetic candidate for demonstration if not currently legal
77
+ moveObj = {
78
+ san,
79
+ from: "c2",
80
+ to: "c3",
81
+ piece: "pawn",
82
+ color: "white",
83
+ isCapture: san.includes("x"),
84
+ isCheck: san.includes("+"),
85
+ isCastling: san === "O-O",
86
+ description: san === "O-O" ? "Kingside castle" : `Move ${san}`,
87
+ };
88
+ }
89
+
90
+ const evalResult = await evaluator.evaluateMove(engine, moveObj);
91
+ console.log(`Move: ${evalResult.san}`);
92
+ console.log(` Badge: ${evalResult.commentaryBadge}`);
93
+ console.log(` Tactical Sharpness: ${evalResult.tacticalSharpness.score.toFixed(1)} / 3.0 (${evalResult.tacticalSharpness.level})`);
94
+ console.log(` Strategic Theme: ${evalResult.strategicTheme.theme} (Confidence: ${(evalResult.strategicTheme.confidence * 100).toFixed(0)}%)`);
95
+ console.log(` King Attack Threat: ${(evalResult.kingAttackRisk.probability * 100).toFixed(0)}%`);
96
+ console.log(` Psychological Pressure: ${(evalResult.psychologicalPressure.probability * 100).toFixed(0)}%`);
97
+ console.log();
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // SECTION 3: GAME DESIGN - PERSONA AI OPPONENTS VIA COMPOSITE SCORING
102
+ // ---------------------------------------------------------------------------
103
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
104
+ console.log("3. GAME DESIGN: Persona AI Opponents via Composite Scoring");
105
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
106
+ console.log("Demonstrating how atomic scores are re-weighted in code to produce distinct personalities without rerunning inference:\n");
107
+
108
+ const personaEngine = new PersonaEngine();
109
+ const personas: Array<keyof typeof CHESS_PERSONAS> = ["tal", "petrosian", "capablanca", "coffeehouse"];
110
+
111
+ for (const pId of personas) {
112
+ const decision = await personaEngine.selectMove(engine, pId, 4);
113
+ const p = decision.persona;
114
+ console.log(`👤 ${p.name} - "${p.title}"`);
115
+ console.log(` Weights: Aggression=${p.weights.aggression}, Prophylaxis=${p.weights.prophylaxis}, Complexity=${p.weights.complexity}, Greed=${p.weights.materialGreed}`);
116
+ console.log(` Decision: Plays ${decision.selectedMove.san} (${decision.selectedMove.description})`);
117
+ console.log(` Top 3 Ranked Moves:`);
118
+ for (let i = 0; i < Math.min(3, decision.rankedCandidates.length); i++) {
119
+ const c = decision.rankedCandidates[i]!;
120
+ console.log(` #${i + 1} ${c.move.san.padEnd(5)} | Composite Score: ${c.compositeScore.toFixed(3)} (Agg:${c.dimensionScores.aggression.toFixed(1)}, Pro:${c.dimensionScores.prophylaxis.toFixed(1)}, Comp:${c.dimensionScores.complexity.toFixed(1)}, Greed:${c.dimensionScores.materialGreed.toFixed(2)})`);
121
+ }
122
+ console.log();
123
+ }
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // SECTION 4: GAME REVIEW - POST-GAME BLUNDER TAXONOMY & DIAGNOSTICS
127
+ // ---------------------------------------------------------------------------
128
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
129
+ console.log("4. GAME REVIEW: Structured Mistake Diagnostics (The Cascade Pattern)");
130
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
131
+
132
+ const reviewer = new GameReviewer();
133
+ const sampleBlunder = {
134
+ san: "Qxh7??",
135
+ from: "d3",
136
+ to: "h7",
137
+ piece: "queen",
138
+ color: "white" as const,
139
+ isCapture: true,
140
+ isCheck: false,
141
+ isCastling: false,
142
+ description: "Queen captures h7 pawn without checking king safety",
143
+ };
144
+
145
+ const diagnosis = await reviewer.diagnoseMove(engine, sampleBlunder, 14);
146
+ console.log(`Reviewed Move: Move ${diagnosis.moveNumber} White played ${diagnosis.playedMove}`);
147
+ console.log(` Mistake Taxonomy: ${diagnosis.mistakeArchetype.toUpperCase()}`);
148
+ console.log(` Defensive Difficulty: ${diagnosis.defensiveDifficulty.toFixed(1)} / 3.0`);
149
+ console.log(` Game Tension Level: ${diagnosis.tacticalTension.toFixed(1)} / 3.0`);
150
+ console.log(` Coach Feedback: "${diagnosis.coachAdvice}"\n`);
151
+
152
+ console.log("================================================================================");
153
+ console.log(" ✅ TypeSafe Chess Moves and Games Architecture demonstrated successfully! ");
154
+ console.log("================================================================================");
155
+ }
156
+
157
+ runDemo().catch((err) => {
158
+ console.error("Demo failed:", err);
159
+ process.exit(1);
160
+ });