cm-chess 3.6.3 → 4.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.
@@ -0,0 +1,11 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(node *)",
5
+ "Bash(npm version *)",
6
+ "Bash(gh issue *)",
7
+ "Bash(gh pr *)",
8
+ "Bash(gh search *)"
9
+ ]
10
+ }
11
+ }
package/README.md CHANGED
@@ -1,38 +1,252 @@
1
- # cm-chess
1
+ # cm-chess
2
2
 
3
- It is like [chess.js](https://github.com/jhlywa/chess.js) (and is based on an older version of chess.js),
4
- but can handle history variations (tree-structured move history),
5
- PGN header values, Nags and Comments.
3
+ A chess engine wrapper inspired by [chess.js](https://github.com/jhlywa/chess.js), with first-class support for:
4
+
5
+ - **Variations** — full tree-structured move history (not just a linear list)
6
+ - **PGN import/export** — header tags, NAGs, annotations and comments
7
+ - **Chess960 (Fischer Random)** — including proper O-O / O-O-O castling
8
+ - **Sloppy SAN** — lenient parsing of common notation mistakes
9
+ - **Observers** — subscribe to legal/illegal/undo events
10
+
11
+ It powers the online chess server [chessmail.de](https://www.chessmail.de).
6
12
 
7
13
  ## Install
8
14
 
9
- `npm install cm-chess`
15
+ ```bash
16
+ npm install cm-chess
17
+ ```
18
+
19
+ ## Quick start
20
+
21
+ ```js
22
+ import { Chess, COLOR, GAME_VARIANT } from "cm-chess"
23
+
24
+ const chess = new Chess()
25
+ chess.move("e4")
26
+ chess.move("e5")
27
+ chess.move("Nf3")
28
+
29
+ console.log(chess.fen()) // position after last move
30
+ console.log(chess.turn()) // "b"
31
+ console.log(chess.history()) // array of move nodes
32
+ console.log(chess.renderPgn()) // PGN string
33
+ ```
34
+
35
+ ## Run the tests
36
+
37
+ A hosted runner is available at **<https://shaack.com/projekte/cm-chess/test/>**.
38
+ Locally, open [`test/index.html`](test/index.html) in a browser.
39
+
40
+ ---
41
+
42
+ ## Constructor
43
+
44
+ `new Chess(props)` — `props` is an optional object:
45
+
46
+ | Option | Type | Default | Description |
47
+ |---------------|---------------------------|---------------|----------------------------------------------------------------|
48
+ | `fen` | `string` | start FEN | Load from a FEN. |
49
+ | `pgn` | `string` | — | Load from a PGN (overrides `fen`). |
50
+ | `gameVariant` | `"standard"` / `"chess960"` | `"standard"` | Variant. Required for Chess960 castling to work. |
51
+ | `sloppy` | `boolean` | `true` | Lenient SAN parsing (e.g. `Rc8c7`, `Rc8-c7`). |
52
+
53
+ ```js
54
+ new Chess()
55
+ new Chess({ fen: "4k3/pppppppp/8/8/8/8/PPPPPPPP/4K3 w - - 0 1" })
56
+ new Chess({ pgn: "1. e4 e5 2. Nf3" })
57
+ new Chess({ gameVariant: GAME_VARIANT.chess960 })
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Making moves
63
+
64
+ ### `move(san, previousMove?, sloppy?)`
65
+
66
+ Apply a move in SAN. Returns the created move node, or `null` if illegal.
67
+
68
+ ```js
69
+ chess.move("e4") // simple move
70
+ chess.move("gxh8=Q") // promotion
71
+ chess.move("dxc6") // en passant (flags include 'e')
72
+ chess.move("O-O") // castling, letter O (not zero)
73
+ chess.move("Nbd7", previousMove) // add a variation branching from previousMove
74
+ ```
75
+
76
+ ### `validateMove(san, previousMove?, sloppy?)`
77
+
78
+ Check whether a move would be legal without making it. Returns a move object or `null`.
79
+
80
+ ### `moves(options?, move?)`
81
+
82
+ Return all legal moves from a given position (defaults to the last move).
83
+
84
+ ```js
85
+ chess.moves() // all legal SAN strings
86
+ chess.moves({ square: "e2" }) // only moves from e2
87
+ chess.moves({ piece: "n" }) // only knight moves
88
+ chess.moves({ verbose: true }) // detailed move objects
89
+ ```
90
+
91
+ ### `undo(move?)`
92
+
93
+ Remove a move and everything after it from its variation. Defaults to the last move.
94
+
95
+ ---
96
+
97
+ ## Inspecting state
98
+
99
+ | Method | Returns |
100
+ |---------------------------|---------------------------------------------------------------|
101
+ | `fen(move?)` | FEN of the given move, or the last move |
102
+ | `setUpFen()` | Starting FEN (from PGN header or default start) |
103
+ | `turn(move?)` | `"w"` or `"b"` |
104
+ | `piece(square, move?)` | `{ type, color }` or `null` |
105
+ | `pieces(type?, color?, move?)` | array of pieces with `square`, `type`, `color` |
106
+ | `history()` | array of move nodes (main line) |
107
+ | `lastMove()` | last move node or `null` |
108
+ | `plyCount()` | number of plies played |
109
+ | `fenOfPly(n)` | FEN after the n-th ply (`fenOfPly(0)` returns the setup FEN) |
110
+ | `header()` | map of PGN header tags |
111
+
112
+ ---
113
+
114
+ ## Game status
10
115
 
11
- ## Features
116
+ All accept an optional `move` argument and default to the last move.
12
117
 
13
- - It has a similar API to chess.js
14
- - It can handle history variations
15
- - It can add Nags, Comments and Annotations
16
- - It is a core component of my online chess server [chessmail.de](https://www.chessmail.de) (it works)
118
+ | Method | True when… |
119
+ |------------------------------|------------------------------------------------|
120
+ | `gameOver(move?)` | the game has ended |
121
+ | `inCheck(move?)` | the side to move is in check |
122
+ | `inCheckmate(move?)` | the side to move is checkmated |
123
+ | `inStalemate(move?)` | the position is stalemate |
124
+ | `inDraw(move?)` | the game is drawn (any reason) |
125
+ | `insufficientMaterial(move?)`| insufficient material |
126
+ | `inThreefoldRepetition(move?)`| threefold repetition |
17
127
 
18
- ## API
128
+ ---
19
129
 
20
- For documentation, see the jsdoc annotations in [Chess.js](https://github.com/shaack/cm-chess/blob/master/src/Chess.js).
130
+ ## Loading
21
131
 
22
- cm-chess uses [cm-pgn](https://github.com/shaack/cm-pgn) for the
23
- header and history, therefore you have also the full API of **cm-pgn**.
132
+ ### `load(fen)`
24
133
 
25
- If I have some time, I will write a better documentation here in this README. :)
134
+ Replace the game state with the given FEN. Throws on an invalid FEN.
135
+
136
+ ### `loadPgn(pgn, sloppy?)`
137
+
138
+ Replace the game state from a PGN string, including header, variations, NAGs and comments.
139
+ If the PGN's `[Variant "Chess960"]` tag is present, `gameVariant` is set automatically.
140
+
141
+ ---
142
+
143
+ ## Exporting PGN
144
+
145
+ ### `renderPgn(renderHeader = true, renderComments = true, renderNags = true)`
146
+
147
+ Return a PGN string for the current game.
148
+
149
+ ```js
150
+ chess.renderPgn() // full PGN
151
+ chess.renderPgn(false) // no header
152
+ chess.renderPgn(true, false) // no comments
153
+ ```
154
+
155
+ ---
156
+
157
+ ## Variations
158
+
159
+ Move history is a tree. Each move node has `next`, `previous`, `variations` and `variation`
160
+ references, so you can walk the main line and any side lines.
161
+
162
+ ```js
163
+ const first = chess.history()[0]
164
+ console.log(first.san) // "e4"
165
+ console.log(first.next.san) // "e5"
166
+
167
+ // Start a side variation from `first`:
168
+ chess.move("e5", first) // becomes the main continuation of `first`
169
+ chess.move("c5", first) // creates a Sicilian variation off `first`
170
+ ```
171
+
172
+ Each move node also carries computed fields: `fen`, `uci`, `ply`, `color`, `flags`,
173
+ `piece`, `captured`, `promotion`, `inCheck`, `inCheckmate`, `inStalemate`,
174
+ `inDraw`, `insufficientMaterial`, `inThreefoldRepetition`, `gameOver`.
175
+
176
+ See [cm-pgn](https://github.com/shaack/cm-pgn) for the full history/header API.
177
+
178
+ ---
179
+
180
+ ## Events
181
+
182
+ Subscribe to moves with `addObserver(callback)`:
183
+
184
+ ```js
185
+ import { EVENT_TYPE } from "cm-chess"
186
+
187
+ chess.addObserver((event) => {
188
+ switch (event.type) {
189
+ case EVENT_TYPE.initialized: /* fen/pgn loaded */; break
190
+ case EVENT_TYPE.legalMove: console.log("played", event.move.san); break
191
+ case EVENT_TYPE.illegalMove: console.warn("rejected", event.move); break
192
+ case EVENT_TYPE.undoMove: console.log("undid", event.move.san); break
193
+ }
194
+ })
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Chess960 (Fischer Random)
200
+
201
+ Pass `gameVariant: GAME_VARIANT.chess960` to enable 960-style castling.
202
+ Use `O-O` / `O-O-O` (letter O, not zero) — cm-chess resolves them to the
203
+ correct king/rook destinations for the given FEN.
204
+
205
+ ```js
206
+ import { Chess, GAME_VARIANT } from "cm-chess"
207
+
208
+ const chess = new Chess({ gameVariant: GAME_VARIANT.chess960 })
209
+ chess.load("nrkbrnbq/pppppppp/8/8/8/8/PPPPPPPP/NRKBRNBQ w KQkq - 0 1")
210
+ chess.move("f4")
211
+ // …
212
+ chess.move("O-O-O")
213
+ ```
214
+
215
+ When loading a PGN containing `[Variant "Chess960"]`, the variant is detected
216
+ automatically.
217
+
218
+ ---
219
+
220
+ ## Exports
221
+
222
+ ```js
223
+ import {
224
+ Chess,
225
+ COLOR, // { white: "w", black: "b" }
226
+ PIECES, // { p, n, b, r, q, k } with { name, value }
227
+ FEN, // { empty, start }
228
+ EVENT_TYPE, // { initialized, legalMove, illegalMove, undoMove }
229
+ GAME_VARIANT // { standard, chess960 }
230
+ } from "cm-chess"
231
+ ```
232
+
233
+ ---
26
234
 
27
235
  ## Examples
28
236
 
29
- See the unit tests for usage examples
30
- https://github.com/shaack/cm-chess/blob/master/test/TestChess.js
237
+ - [`test/TestChess.js`](test/TestChess.js) — core API, loading, variations, events
238
+ - [`test/TestChess960.js`](test/TestChess960.js) — Chess960 castling and PGN
239
+ - [`test/TestFen.js`](test/TestFen.js) — FEN handling
240
+ - Tests of [cm-pgn](https://github.com/shaack/cm-pgn/tree/master/test) for the underlying history/header API
241
+
242
+ ---
31
243
 
32
- And also the unit tests of cm-pgn
33
- https://github.com/shaack/cm-pgn/tree/master/test
244
+ ## Related
34
245
 
35
- ## Test
246
+ - [cm-pgn](https://github.com/shaack/cm-pgn) — PGN parser and history tree (used internally)
247
+ - [chess.mjs](https://www.npmjs.com/package/chess.mjs) — ES-module fork of chess.js (used for move validation)
248
+ - [cm-chessboard](https://github.com/shaack/cm-chessboard) — companion chessboard component
36
249
 
37
- [Run the unit tests](https://shaack.com/projekte/cm-chess/test/)
250
+ ## License
38
251
 
252
+ MIT — see [LICENSE](LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cm-chess",
3
- "version": "3.6.3",
3
+ "version": "4.0.0",
4
4
  "description": "It's like chess.js, but in ES6 and can handle variations (tree-structured move history)",
5
5
  "main": "./src/Chess.js",
6
6
  "type": "module",
@@ -25,9 +25,9 @@
25
25
  "homepage": "https://github.com/shaack/cm-chess#readme",
26
26
  "dependencies": {
27
27
  "chess.mjs": "^2.3.2",
28
- "cm-pgn": "4.0.6"
28
+ "cm-pgn": "^5.0.0"
29
29
  },
30
30
  "devDependencies": {
31
- "teevi": "^2.3.0"
31
+ "teevi": "^2.4.1"
32
32
  }
33
33
  }
package/test/TestChess.js CHANGED
@@ -289,6 +289,33 @@ Kc4 7. Qb4+ Kd5 8. Qc5#) 5. Bc5# 1-0`)
289
289
  assert.true(result != null)
290
290
  })
291
291
 
292
+ it("should promote a pawn via SAN", function () {
293
+ const chess = new Chess({fen: "rnbqkbnr/ppppppPp/8/8/8/8/PPPPPP1P/RNBQKBNR w KQkq - 0 1"})
294
+ const move = chess.move("gxh8=Q")
295
+ assert.true(move !== null)
296
+ assert.equal(move.promotion, "q")
297
+ assert.true(move.flags.includes("p"))
298
+ assert.equal(chess.piece("h8").type, "q")
299
+ assert.equal(chess.piece("h8").color, "w")
300
+ })
301
+
302
+ it("should promote to a knight (underpromotion)", function () {
303
+ const chess = new Chess({fen: "4k3/P7/8/8/8/8/8/4K3 w - - 0 1"})
304
+ const move = chess.move("a8=N")
305
+ assert.true(move !== null)
306
+ assert.equal(move.promotion, "n")
307
+ assert.equal(chess.piece("a8").type, "n")
308
+ })
309
+
310
+ it("should capture en passant", function () {
311
+ const chess = new Chess({fen: "rnbqkbnr/pp1ppppp/8/2pP4/8/8/PPP1PPPP/RNBQKBNR w KQkq c6 0 1"})
312
+ const move = chess.move("dxc6")
313
+ assert.true(move !== null)
314
+ assert.true(move.flags.includes("e"))
315
+ assert.equal(move.fen, "rnbqkbnr/pp1ppppp/2P5/8/8/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1")
316
+ assert.equal(chess.piece("c5"), null)
317
+ })
318
+
292
319
  it("should load a sloppy pgn", function () {
293
320
  const pgn = `pgn [SetUp "1"]
294
321
  [FEN "r1R2r1k/1R6/1P2B2p/4pPp1/4N1P1/7P/5P2/2R3K1 w - - 1 44"]
@@ -33,6 +33,17 @@ describe("Testing Chess 960", function () {
33
33
 
34
34
  })
35
35
 
36
+ it("should do kingside castling (O-O) in chess960", function () {
37
+ const fen = "rnbqkbnr/pppppppp/8/8/8/3PP1BN/PPP2PPP/RNBQ1K1R w KQkq - 0 1"
38
+ const chess = new Chess({gameVariant: GAME_VARIANT.chess960})
39
+ chess.load(fen)
40
+ const move = chess.move("O-O")
41
+ assert.true(move !== null)
42
+ assert.equal(move.san, "O-O")
43
+ assert.equal(chess.piece("g1").type, "k")
44
+ assert.equal(chess.piece("f1").type, "r")
45
+ })
46
+
36
47
  it("should load a chess960 game from a valid chess960 pgn", function () {
37
48
  const pgn = `[Event "Freestyle Weissenhaus KO"]
38
49
  [Site "Wangels GER"]