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
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { ChessEngine } from "../src/chessEngine.js";
|
|
3
|
+
import { MoveResolver } from "../src/moveResolver.js";
|
|
4
|
+
import { MoveEvaluator } from "../src/moveEvaluator.js";
|
|
5
|
+
import { PersonaEngine, CHESS_PERSONAS } from "../src/personaEngine.js";
|
|
6
|
+
import { GameReviewer } from "../src/gameReviewer.js";
|
|
7
|
+
import { ClassicMatchStudio, CLASSIC_MATCHES } from "../src/classicMatches.js";
|
|
8
|
+
|
|
9
|
+
describe("ChessEngine Deterministic Rules", () => {
|
|
10
|
+
it("should initialize board to standard starting position", () => {
|
|
11
|
+
const engine = new ChessEngine();
|
|
12
|
+
expect(engine.turn()).toBe("white");
|
|
13
|
+
expect(engine.isCheck()).toBe(false);
|
|
14
|
+
expect(engine.isGameOver()).toBe(false);
|
|
15
|
+
|
|
16
|
+
const moves = engine.getAnnotatedMoves();
|
|
17
|
+
expect(moves.length).toBe(20); // 16 pawn moves + 4 knight moves
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should calculate equal material at start", () => {
|
|
21
|
+
const engine = new ChessEngine();
|
|
22
|
+
const balance = engine.getMaterialBalance();
|
|
23
|
+
expect(balance.diff).toBe(0);
|
|
24
|
+
expect(balance.description).toContain("equal");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should apply legal moves and update state", () => {
|
|
28
|
+
const engine = new ChessEngine();
|
|
29
|
+
const move = engine.makeMove("e4");
|
|
30
|
+
expect(move).not.toBeNull();
|
|
31
|
+
expect(move?.san).toBe("e4");
|
|
32
|
+
expect(engine.turn()).toBe("black");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should reject illegal moves", () => {
|
|
36
|
+
const engine = new ChessEngine();
|
|
37
|
+
const illegalMove = engine.makeMove("e5");
|
|
38
|
+
expect(illegalMove).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("MoveResolver (TypeSafe Choice & Confidence Gating)", () => {
|
|
43
|
+
const engine = new ChessEngine();
|
|
44
|
+
const resolver = new MoveResolver();
|
|
45
|
+
|
|
46
|
+
it("should resolve knight development to Nf3", async () => {
|
|
47
|
+
engine.reset();
|
|
48
|
+
engine.makeMove("e4");
|
|
49
|
+
engine.makeMove("e5");
|
|
50
|
+
|
|
51
|
+
const res = await resolver.resolveIntent(
|
|
52
|
+
engine,
|
|
53
|
+
"Develop my knight towards the center and attack their pawn"
|
|
54
|
+
);
|
|
55
|
+
expect(res.matchedMove).not.toBeNull();
|
|
56
|
+
expect(res.san).toBe("Nf3");
|
|
57
|
+
expect(res.confidence).toBeGreaterThan(0.5);
|
|
58
|
+
expect(res.clarificationNeeded).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should resolve pawn push to d4", async () => {
|
|
62
|
+
engine.reset();
|
|
63
|
+
engine.makeMove("e4");
|
|
64
|
+
engine.makeMove("e5");
|
|
65
|
+
|
|
66
|
+
const res = await resolver.resolveIntent(
|
|
67
|
+
engine,
|
|
68
|
+
"Push the queen's pawn two squares forward"
|
|
69
|
+
);
|
|
70
|
+
expect(res.matchedMove).not.toBeNull();
|
|
71
|
+
expect(res.san).toBe("d4");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should flag clarification for impossible commands", async () => {
|
|
75
|
+
engine.reset();
|
|
76
|
+
const res = await resolver.resolveIntent(
|
|
77
|
+
engine,
|
|
78
|
+
"Teleport my queen straight to the enemy backrank"
|
|
79
|
+
);
|
|
80
|
+
expect(res.clarificationNeeded).toBe(true);
|
|
81
|
+
expect(res.matchedMove).toBeNull();
|
|
82
|
+
expect(res.confidence).toBeLessThan(0.55);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("MoveEvaluator (Parallel Atomic Judgments)", () => {
|
|
87
|
+
const engine = new ChessEngine();
|
|
88
|
+
const evaluator = new MoveEvaluator();
|
|
89
|
+
|
|
90
|
+
it("should score quiet castling as low sharpness", async () => {
|
|
91
|
+
engine.reset();
|
|
92
|
+
// 1. e4 e5 2. Nf3 Nc6 3. Bc4 Bc5 4. O-O
|
|
93
|
+
engine.makeMove("e4");
|
|
94
|
+
engine.makeMove("e5");
|
|
95
|
+
engine.makeMove("Nf3");
|
|
96
|
+
engine.makeMove("Nc6");
|
|
97
|
+
engine.makeMove("Bc4");
|
|
98
|
+
engine.makeMove("Bc5");
|
|
99
|
+
|
|
100
|
+
const castleMove = engine.getAnnotatedMoves().find((m) => m.san === "O-O")!;
|
|
101
|
+
const evalRes = await evaluator.evaluateMove(engine, castleMove);
|
|
102
|
+
|
|
103
|
+
expect(evalRes.tacticalSharpness.score).toBeLessThan(1.5);
|
|
104
|
+
expect(evalRes.san).toBe("O-O");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should score tactical sacrifice as high sharpness", async () => {
|
|
108
|
+
engine.reset();
|
|
109
|
+
engine.makeMove("e4");
|
|
110
|
+
engine.makeMove("e5");
|
|
111
|
+
engine.makeMove("Nf3");
|
|
112
|
+
engine.makeMove("Nc6");
|
|
113
|
+
engine.makeMove("Bc4");
|
|
114
|
+
engine.makeMove("Bc5");
|
|
115
|
+
|
|
116
|
+
const sacMove = engine.getAnnotatedMoves().find((m) => m.san === "Bxf7+")!;
|
|
117
|
+
const evalRes = await evaluator.evaluateMove(engine, sacMove);
|
|
118
|
+
|
|
119
|
+
expect(evalRes.tacticalSharpness.score).toBeGreaterThan(2.0);
|
|
120
|
+
expect(evalRes.commentaryBadge).toContain("Sharp");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("PersonaEngine (Composite Scoring)", () => {
|
|
125
|
+
const engine = new ChessEngine();
|
|
126
|
+
const personaEngine = new PersonaEngine();
|
|
127
|
+
|
|
128
|
+
it("should have distinct weights for Tal and Petrosian", () => {
|
|
129
|
+
const tal = CHESS_PERSONAS.tal;
|
|
130
|
+
const petrosian = CHESS_PERSONAS.petrosian;
|
|
131
|
+
|
|
132
|
+
expect(tal.weights.aggression).toBeGreaterThan(petrosian.weights.aggression);
|
|
133
|
+
expect(petrosian.weights.prophylaxis).toBeGreaterThan(tal.weights.prophylaxis);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should select moves and rank candidates by composite score", async () => {
|
|
137
|
+
engine.reset();
|
|
138
|
+
engine.makeMove("e4");
|
|
139
|
+
engine.makeMove("e5");
|
|
140
|
+
engine.makeMove("Nf3");
|
|
141
|
+
engine.makeMove("Nc6");
|
|
142
|
+
engine.makeMove("Bc4");
|
|
143
|
+
engine.makeMove("Bc5");
|
|
144
|
+
|
|
145
|
+
const talDecision = await personaEngine.selectMove(engine, "tal");
|
|
146
|
+
expect(talDecision.selectedMove).toBeDefined();
|
|
147
|
+
expect(talDecision.rankedCandidates.length).toBeGreaterThan(0);
|
|
148
|
+
// Best move should have highest score
|
|
149
|
+
expect(talDecision.rankedCandidates[0]?.compositeScore).toBeGreaterThanOrEqual(
|
|
150
|
+
talDecision.rankedCandidates[1]?.compositeScore ?? -999
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("GameReviewer (Mistake Diagnostics)", () => {
|
|
156
|
+
const engine = new ChessEngine();
|
|
157
|
+
const reviewer = new GameReviewer();
|
|
158
|
+
|
|
159
|
+
it("should diagnose blunder move into actionable feedback", async () => {
|
|
160
|
+
engine.reset();
|
|
161
|
+
const blunder = {
|
|
162
|
+
san: "Qxh7??",
|
|
163
|
+
from: "d3",
|
|
164
|
+
to: "h7",
|
|
165
|
+
piece: "queen",
|
|
166
|
+
color: "white" as const,
|
|
167
|
+
isCapture: true,
|
|
168
|
+
isCheck: false,
|
|
169
|
+
isCastling: false,
|
|
170
|
+
description: "Queen captures h7 pawn",
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const diag = await reviewer.diagnoseMove(engine, blunder, 12);
|
|
174
|
+
expect(diag.moveNumber).toBe(12);
|
|
175
|
+
expect(diag.mistakeArchetype).toBe("king_safety_negligence");
|
|
176
|
+
expect(diag.coachAdvice).toContain("king");
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("ClassicMatchStudio (Historic Game Classification)", () => {
|
|
181
|
+
const matchStudio = new ClassicMatchStudio();
|
|
182
|
+
|
|
183
|
+
it("should classify The Immortal Game as a romantic swashbuckler with high brilliance", async () => {
|
|
184
|
+
const immortal = CLASSIC_MATCHES.find((m) => m.id === "immortal-game")!;
|
|
185
|
+
expect(immortal).toBeDefined();
|
|
186
|
+
|
|
187
|
+
const classification = await matchStudio.classifyMatch(immortal);
|
|
188
|
+
expect(classification.matchId).toBe("immortal-game");
|
|
189
|
+
expect(classification.archetype).toContain("SWASHBUCKLER");
|
|
190
|
+
expect(classification.aestheticBrilliance.score).toBeGreaterThan(2.0);
|
|
191
|
+
expect(classification.hasDecisiveSacrifice).toBe(true);
|
|
192
|
+
expect(classification.turningPoint.san).toBe("Ke2");
|
|
193
|
+
expect(classification.strategicBreakdown.tacticalStrikesPct).toBeGreaterThan(0);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should classify Kasparov's Immortal as tactical firestorm with sacrifice", async () => {
|
|
197
|
+
const kasparov = CLASSIC_MATCHES.find((m) => m.id === "kasparov-immortal")!;
|
|
198
|
+
expect(kasparov).toBeDefined();
|
|
199
|
+
|
|
200
|
+
const classification = await matchStudio.classifyMatch(kasparov);
|
|
201
|
+
expect(classification.matchId).toBe("kasparov-immortal");
|
|
202
|
+
expect(classification.archetype).toContain("FIRESTORM");
|
|
203
|
+
expect(classification.overallSharpness.score).toBeGreaterThan(2.0);
|
|
204
|
+
expect(classification.turningPoint.san).toBe("Rxd4");
|
|
205
|
+
});
|
|
206
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": "./src",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"isolatedModules": true
|
|
18
|
+
},
|
|
19
|
+
"include": ["src/**/*"],
|
|
20
|
+
"exclude": ["node_modules", "dist", "tests/**/*"]
|
|
21
|
+
}
|