@chesscam/recorder 0.0.0-stage → 0.2.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/LICENSE.md +21 -0
- package/README.md +192 -2
- package/dist/chess/board.d.ts +24 -0
- package/dist/chess/board.d.ts.map +1 -0
- package/dist/chess/board.js +75 -0
- package/dist/chess/board.js.map +1 -0
- package/dist/chess/moves.d.ts +6 -0
- package/dist/chess/moves.d.ts.map +1 -0
- package/dist/chess/moves.js +107 -0
- package/dist/chess/moves.js.map +1 -0
- package/dist/constants.d.ts +17 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +40 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +11 -0
- package/dist/errors.js.map +1 -0
- package/dist/game.d.ts +9 -0
- package/dist/game.d.ts.map +1 -0
- package/dist/game.js +61 -0
- package/dist/game.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/recorder.d.ts +37 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +207 -0
- package/dist/recorder.js.map +1 -0
- package/dist/rules/board.d.ts +33 -0
- package/dist/rules/board.d.ts.map +1 -0
- package/dist/rules/board.js +97 -0
- package/dist/rules/board.js.map +1 -0
- package/dist/rules/pgn.d.ts +10 -0
- package/dist/rules/pgn.d.ts.map +1 -0
- package/dist/rules/pgn.js +59 -0
- package/dist/rules/pgn.js.map +1 -0
- package/dist/rules/position.d.ts +55 -0
- package/dist/rules/position.d.ts.map +1 -0
- package/dist/rules/position.js +430 -0
- package/dist/rules/position.js.map +1 -0
- package/dist/rules/san.d.ts +6 -0
- package/dist/rules/san.d.ts.map +1 -0
- package/dist/rules/san.js +87 -0
- package/dist/rules/san.js.map +1 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/vision/bayesianTracker.d.ts +30 -0
- package/dist/vision/bayesianTracker.d.ts.map +1 -0
- package/dist/vision/bayesianTracker.js +351 -0
- package/dist/vision/bayesianTracker.js.map +1 -0
- package/dist/vision/beamTracker.d.ts +34 -0
- package/dist/vision/beamTracker.d.ts.map +1 -0
- package/dist/vision/beamTracker.js +197 -0
- package/dist/vision/beamTracker.js.map +1 -0
- package/dist/vision/model.d.ts +21 -0
- package/dist/vision/model.d.ts.map +1 -0
- package/dist/vision/model.js +187 -0
- package/dist/vision/model.js.map +1 -0
- package/dist/vision/position.d.ts +16 -0
- package/dist/vision/position.d.ts.map +1 -0
- package/dist/vision/position.js +98 -0
- package/dist/vision/position.js.map +1 -0
- package/dist/vision/replay.d.ts +9 -0
- package/dist/vision/replay.d.ts.map +1 -0
- package/dist/vision/replay.js +49 -0
- package/dist/vision/replay.js.map +1 -0
- package/dist/vision/video.d.ts +2 -0
- package/dist/vision/video.d.ts.map +1 -0
- package/dist/vision/video.js +9 -0
- package/dist/vision/video.js.map +1 -0
- package/dist/vision/worker.d.ts +19 -0
- package/dist/vision/worker.d.ts.map +1 -0
- package/dist/vision/worker.js +91 -0
- package/dist/vision/worker.js.map +1 -0
- package/package.json +54 -4
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ChessCam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,193 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @chesscam/recorder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@chesscam/recorder)
|
|
4
|
+
|
|
5
|
+
Point a camera at a real chess board and get the moves as they're played. Everything runs in the browser: no server,
|
|
6
|
+
no uploads.
|
|
7
|
+
|
|
8
|
+
This is the move detection from [ChessCam](https://www.chesscam.net), as a library. It works with a live camera, a
|
|
9
|
+
video file or a video stream.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
npm install @chesscam/recorder
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createRecorder, loadModel } from "@chesscam/recorder";
|
|
21
|
+
|
|
22
|
+
// 1. Load the model (about 24 MB, downloaded once)
|
|
23
|
+
const model = await loadModel();
|
|
24
|
+
|
|
25
|
+
// 2. Point a recorder at a <video> showing the board
|
|
26
|
+
const recorder = createRecorder({
|
|
27
|
+
model,
|
|
28
|
+
video: document.querySelector("video")!,
|
|
29
|
+
roi: { x: 438, y: 384, width: 444, height: 280 }, // where the board is, in video pixels
|
|
30
|
+
onUpdate: (game) => console.log(game.moves) // e.g. "1. e4 e5 2. Nf3"
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// 3. Start recording when the game starts
|
|
34
|
+
recorder.start();
|
|
35
|
+
|
|
36
|
+
// 4. When the game ends, stop and save it as PGN
|
|
37
|
+
recorder.pause();
|
|
38
|
+
const pgn = recorder.pgn({ White: "Alice", Black: "Bob" });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For a full working page with a camera or video file, see [`examples/demo`](examples/demo).
|
|
42
|
+
|
|
43
|
+
## Good to know
|
|
44
|
+
|
|
45
|
+
- **Tell it where the board is.** `roi` is the rectangle around the board in the video. Without it the whole frame is
|
|
46
|
+
used, which only works if the board fills the picture.
|
|
47
|
+
- **Start when the game starts.** Anything else on camera (an empty board, hands setting up the pieces) can be mistaken
|
|
48
|
+
for moves.
|
|
49
|
+
- **Earlier moves can change.** If the recorder realises it misread a move, often because a hand was in the way, it
|
|
50
|
+
fixes it a few seconds later. Always show the whole `game.moves` from each `onUpdate`, not just the newest move.
|
|
51
|
+
- **Which videos work.** Anything a `<video>` element can play and the page can read the pixels of: a camera, a local
|
|
52
|
+
file, a WebRTC stream, or HLS/DASH served with CORS. YouTube or Twitch embeds and DRM-protected video can't be read.
|
|
53
|
+
|
|
54
|
+
## Setup with a bundler
|
|
55
|
+
|
|
56
|
+
The model runs in a web worker, which Vite and webpack pick up automatically. With Vite, add this to your config so the
|
|
57
|
+
worker file is found:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
optimizeDeps: { exclude: ["@chesscam/recorder"] }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Making it faster (optional)
|
|
64
|
+
|
|
65
|
+
By default the model runs on one thread, about 6 frames a second on a Pixel 7a. That's already more than the 4 frames
|
|
66
|
+
a second it reads by default. To use up to four threads (about 15 frames a second):
|
|
67
|
+
|
|
68
|
+
1. Send these headers with your page:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Cross-Origin-Opener-Policy: same-origin
|
|
72
|
+
Cross-Origin-Embedder-Policy: credentialless
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
2. Serve the LiteRT.js runtime from your own site: copy `node_modules/@litertjs/core/wasm/` into your public files and
|
|
76
|
+
pass its URL with `loadModel({ wasmUrl: "/litert/" })`. It must be version `LITERT_VERSION`.
|
|
77
|
+
|
|
78
|
+
Serving the runtime yourself also lets the recorder work offline. You can host the model file yourself too, with
|
|
79
|
+
`loadModel({ modelUrl })`. By default it comes from Hugging Face.
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
### `loadModel(options?)`
|
|
84
|
+
|
|
85
|
+
Loads the model and returns a promise of it. Calling it again returns the same model.
|
|
86
|
+
|
|
87
|
+
| Option | What it does |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `modelUrl` | Load the `.tflite` model from here instead of Hugging Face |
|
|
90
|
+
| `wasmUrl` | Load the LiteRT.js runtime from here (a folder URL ending in `/`) instead of jsDelivr |
|
|
91
|
+
|
|
92
|
+
### `createRecorder(options)`
|
|
93
|
+
|
|
94
|
+
Creates a recorder. It starts paused; call `start()` to begin.
|
|
95
|
+
|
|
96
|
+
| Option | What it does |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `model` | The model from `loadModel()` (required) |
|
|
99
|
+
| `video` | The `<video>` element to read (required) |
|
|
100
|
+
| `roi` | Where the board is: `{ x, y, width, height }` in video pixels |
|
|
101
|
+
| `start` | The starting position as FEN, if not the normal start |
|
|
102
|
+
| `timed` | Record how long each move took, saved in the PGN (default `true`) |
|
|
103
|
+
| `finalMovesOnly` | Only report moves once they're certain, about 1.5 s after they're played. Useful when sending moves to an online game. |
|
|
104
|
+
| `maxFps` | How many frames a second to read (default 4) |
|
|
105
|
+
| `onUpdate(game)` | Called whenever the moves change |
|
|
106
|
+
| `onError(error)` | Called when a frame fails. Recording carries on. |
|
|
107
|
+
| `onFps(fps)` | The current frame rate, about once a second (`null` once it stops) |
|
|
108
|
+
| `onOrientation(corners)` | Called when it works out which way round the board is |
|
|
109
|
+
| `onDetect(detection)` | Called with the raw result of every frame |
|
|
110
|
+
|
|
111
|
+
A recorder has these methods and properties:
|
|
112
|
+
|
|
113
|
+
| | What it does |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `start()` / `pause()` | Start or pause reading. Paused time doesn't count towards move times. |
|
|
116
|
+
| `game` | The game so far |
|
|
117
|
+
| `running` | Whether it's currently reading frames |
|
|
118
|
+
| `pgn(headers?)` | The game as PGN text, with any headers you pass (e.g. `White`, `Event`) |
|
|
119
|
+
| `reset(start?)` | Start a new game |
|
|
120
|
+
| `setGame({ moves, start?, fromOpponent?, times? })` | Replace the game yourself, e.g. for a takeback or an online opponent's move |
|
|
121
|
+
| `setRoi(roi)` / `setVideo(video)` | Change where the board is, or which video to read |
|
|
122
|
+
| `destroy()` | Stop and clean up |
|
|
123
|
+
|
|
124
|
+
A **game** has:
|
|
125
|
+
|
|
126
|
+
| | |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `moves` | The moves so far, e.g. `"1. e4 e5 2. Nf3"` |
|
|
129
|
+
| `fen` | The current position |
|
|
130
|
+
| `start` | The starting position |
|
|
131
|
+
| `lastMove` | The last move, e.g. `"g1f3"` |
|
|
132
|
+
| `fromOpponent` | Whether the last move came from `setGame` rather than the board |
|
|
133
|
+
| `times` | When each move was played, in ms since recording started |
|
|
134
|
+
|
|
135
|
+
### `readPosition(model, video, roi?, turn?)`
|
|
136
|
+
|
|
137
|
+
Reads the current position from the board, to start recording a game that's already under way. Returns
|
|
138
|
+
`{ ok: true, fen }`, or `{ ok: false, problem }` if the board doesn't show a legal position (e.g. `"kings"`,
|
|
139
|
+
`"pawns-on-backrank"`).
|
|
140
|
+
|
|
141
|
+
### Errors
|
|
142
|
+
|
|
143
|
+
Errors are a `RecorderError` with a `code`, so you can show your own message:
|
|
144
|
+
|
|
145
|
+
| Code | Meaning |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `not-ready` | The model isn't loaded or the video hasn't got a frame yet |
|
|
148
|
+
| `worker-stopped` | The model's worker crashed, e.g. out of memory |
|
|
149
|
+
| `unloaded` | The model was deleted |
|
|
150
|
+
| `detection-failed` | Something else went wrong; see `error.message` |
|
|
151
|
+
|
|
152
|
+
### Games, moves and rules
|
|
153
|
+
|
|
154
|
+
For apps that keep the game themselves (e.g. in a store) and pass changes to the recorder with `setGame`:
|
|
155
|
+
|
|
156
|
+
| Export | |
|
|
157
|
+
|---|---|
|
|
158
|
+
| `newGame(start?)`, `makePgn(game, headers?)` | A game with no moves, and a game as PGN |
|
|
159
|
+
| `makeBoard(game)`, `GameBoard` | The board after a game's moves, to play more (`playSan`, `playUci`, `undo`) and read its `moves`, `fen`, `history` |
|
|
160
|
+
| `makeUpdatePayload(board, fromOpponent?)` | A board's `moves`, `fen`, `lastMove` and `fromOpponent`, for a `Game` |
|
|
161
|
+
| `getMoveText(board)` | The last one or two moves, numbered, e.g. `3...Nc6 4.Bb5` |
|
|
162
|
+
| `replay(probs, fps, start?)` | Runs saved model outputs through the tracking, frame by frame, giving the moves and when each was played; for scoring the tracking on recorded games |
|
|
163
|
+
|
|
164
|
+
The chess rules are exported too: `Position` (legal moves, `play`, `isCheck`, `outcome`, `fen()`), `parseFen`,
|
|
165
|
+
`makeFen`, `parseSan`, `makeSan`, `parseUci`, `makeUci`, and the types `Color`, `Role`, `Piece`, `Square`,
|
|
166
|
+
`SquareName` and `Move`. Castling is the king's two-square move, e.g. `e1g1`.
|
|
167
|
+
|
|
168
|
+
## Development
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
npm install
|
|
172
|
+
npm run check # tests, types, lint
|
|
173
|
+
npm run build # builds dist/
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
To run the demo: `cd examples/demo && npm install && npm run dev`. You can add `?video=<url>&t=<start s>&roi=x,y,w,h`
|
|
177
|
+
to the URL to play a video, and `&wasm=<url>` to load the runtime from the page's own site.
|
|
178
|
+
|
|
179
|
+
The code came from ChessCam's `src/vision` and `src/chess` at commit `880c130`. The tracker's settings are tuned on
|
|
180
|
+
replays of recorded games in ChessCam's research repo.
|
|
181
|
+
|
|
182
|
+
To release: bump `version` in `package.json`, commit and push, then publish a GitHub release tagged `v<version>`
|
|
183
|
+
(e.g. `v0.1.1`). `.github/workflows/publish.yml` publishes it to npm with trusted publishing, so no npm token is
|
|
184
|
+
needed.
|
|
185
|
+
|
|
186
|
+
## Licence
|
|
187
|
+
|
|
188
|
+
MIT. The chess rules, SAN, FEN and PGN code is its own (`src/rules`). Its dependencies have their own licences:
|
|
189
|
+
|
|
190
|
+
- [LiteRT.js](https://www.npmjs.com/package/@litertjs/core), which runs the model, is Apache-2.0.
|
|
191
|
+
- The default model, [chessq-chesscam](https://huggingface.co/PBatch23888/chessq-chesscam), is downloaded at runtime,
|
|
192
|
+
not included in the package. It is under the PolyForm Noncommercial License 1.0.0, so commercial use needs a
|
|
193
|
+
separate licence for it.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Move, Position } from "../rules/position.js";
|
|
2
|
+
type HistoryEntry = {
|
|
3
|
+
move: Move;
|
|
4
|
+
san: string;
|
|
5
|
+
};
|
|
6
|
+
export declare class GameBoard {
|
|
7
|
+
#private;
|
|
8
|
+
readonly start: string;
|
|
9
|
+
readonly history: HistoryEntry[];
|
|
10
|
+
constructor(start: string);
|
|
11
|
+
get position(): Position;
|
|
12
|
+
get fen(): string;
|
|
13
|
+
get lastMove(): string;
|
|
14
|
+
get moves(): string;
|
|
15
|
+
playSan(san: string): Move | null;
|
|
16
|
+
playUci(uci: string): Move | null;
|
|
17
|
+
undo(): void;
|
|
18
|
+
}
|
|
19
|
+
export declare const makeBoard: (game: {
|
|
20
|
+
moves: string;
|
|
21
|
+
start: string;
|
|
22
|
+
}) => GameBoard;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=board.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../src/chess/board.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAI3D,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAKhD,qBAAa,SAAS;;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,CAAM;IAGtC,YAAY,KAAK,EAAE,MAAM,EAGxB;IAED,IAAI,QAAQ,IAAI,QAAQ,CAEvB;IAED,IAAI,GAAG,IAAI,MAAM,CAEhB;IAGD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAGD,IAAI,KAAK,IAAI,MAAM,CAalB;IAGD,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAGhC;IAED,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAMhC;IAED,IAAI,SAQH;CAOF;AAGD,eAAO,MAAM,SAAS,SAAU;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,KAAG,SAMlE,CAAA"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Position } from "../rules/position.js";
|
|
2
|
+
import { makeSan, makeUci, parseSan, parseUci } from "../rules/san.js";
|
|
3
|
+
import { parseMoveText } from "../rules/pgn.js";
|
|
4
|
+
const positionFromFen = (fen) => Position.fromFen(fen);
|
|
5
|
+
// A game from its start position, keeping the moves played so they can be numbered and undone
|
|
6
|
+
export class GameBoard {
|
|
7
|
+
start;
|
|
8
|
+
history = [];
|
|
9
|
+
#position;
|
|
10
|
+
constructor(start) {
|
|
11
|
+
this.start = start;
|
|
12
|
+
this.#position = positionFromFen(start);
|
|
13
|
+
}
|
|
14
|
+
get position() {
|
|
15
|
+
return this.#position;
|
|
16
|
+
}
|
|
17
|
+
get fen() {
|
|
18
|
+
return this.#position.fen();
|
|
19
|
+
}
|
|
20
|
+
// The last move in UCI, or "" before any move
|
|
21
|
+
get lastMove() {
|
|
22
|
+
return (this.history.length === 0) ? "" : makeUci(this.history[this.history.length - 1].move);
|
|
23
|
+
}
|
|
24
|
+
// Numbered SAN from the start position, e.g. "1... e5 2. Nf3"
|
|
25
|
+
get moves() {
|
|
26
|
+
const pos = positionFromFen(this.start);
|
|
27
|
+
let text = "";
|
|
28
|
+
this.history.forEach(({ move, san }, index) => {
|
|
29
|
+
if (pos.turn === "white") {
|
|
30
|
+
text += `${pos.fullmoves}. `;
|
|
31
|
+
}
|
|
32
|
+
else if (index === 0) {
|
|
33
|
+
text += `${pos.fullmoves}... `;
|
|
34
|
+
}
|
|
35
|
+
text += `${san} `;
|
|
36
|
+
pos.play(move);
|
|
37
|
+
});
|
|
38
|
+
return text.trim();
|
|
39
|
+
}
|
|
40
|
+
// Each play returns the move, or null if it is not legal here
|
|
41
|
+
playSan(san) {
|
|
42
|
+
const move = parseSan(this.#position, san);
|
|
43
|
+
return (move === undefined) ? null : this.#play(move, san);
|
|
44
|
+
}
|
|
45
|
+
playUci(uci) {
|
|
46
|
+
const move = parseUci(uci);
|
|
47
|
+
if (move === undefined || !this.#position.isLegal(move)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return this.#play(move, makeSan(this.#position, move));
|
|
51
|
+
}
|
|
52
|
+
undo() {
|
|
53
|
+
if (this.history.pop() === undefined) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.#position = positionFromFen(this.start);
|
|
57
|
+
for (const { move } of this.history) {
|
|
58
|
+
this.#position.play(move);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
#play(move, san) {
|
|
62
|
+
this.history.push({ move, san });
|
|
63
|
+
this.#position.play(move);
|
|
64
|
+
return move;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// The board for a game's start position and moves (numbered SAN, as a Game keeps them)
|
|
68
|
+
export const makeBoard = (game) => {
|
|
69
|
+
const board = new GameBoard(game.start);
|
|
70
|
+
for (const san of parseMoveText(game.moves)) {
|
|
71
|
+
board.playSan(san);
|
|
72
|
+
}
|
|
73
|
+
return board;
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=board.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board.js","sourceRoot":"","sources":["../../src/chess/board.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Move, Position } from \"../rules/position.js\";\nimport { makeSan, makeUci, parseSan, parseUci } from \"../rules/san.js\";\nimport { parseMoveText } from \"../rules/pgn.js\";\n\ntype HistoryEntry = { move: Move; san: string };\n\nconst positionFromFen = (fen: string): Position => Position.fromFen(fen);\n\n// A game from its start position, keeping the moves played so they can be numbered and undone\nexport class GameBoard {\n readonly start: string;\n readonly history: HistoryEntry[] = [];\n #position: Position;\n\n constructor(start: string) {\n this.start = start;\n this.#position = positionFromFen(start);\n }\n\n get position(): Position {\n return this.#position;\n }\n\n get fen(): string {\n return this.#position.fen();\n }\n\n // The last move in UCI, or \"\" before any move\n get lastMove(): string {\n return (this.history.length === 0) ? \"\" : makeUci(this.history[this.history.length - 1].move);\n }\n\n // Numbered SAN from the start position, e.g. \"1... e5 2. Nf3\"\n get moves(): string {\n const pos = positionFromFen(this.start);\n let text = \"\";\n this.history.forEach(({ move, san }, index) => {\n if (pos.turn === \"white\") {\n text += `${pos.fullmoves}. `;\n } else if (index === 0) {\n text += `${pos.fullmoves}... `;\n }\n text += `${san} `;\n pos.play(move);\n });\n return text.trim();\n }\n\n // Each play returns the move, or null if it is not legal here\n playSan(san: string): Move | null {\n const move = parseSan(this.#position, san);\n return (move === undefined) ? null : this.#play(move, san);\n }\n\n playUci(uci: string): Move | null {\n const move = parseUci(uci);\n if (move === undefined || !this.#position.isLegal(move)) {\n return null;\n }\n return this.#play(move, makeSan(this.#position, move));\n }\n\n undo() {\n if (this.history.pop() === undefined) {\n return;\n }\n this.#position = positionFromFen(this.start);\n for (const { move } of this.history) {\n this.#position.play(move);\n }\n }\n\n #play(move: Move, san: string): Move {\n this.history.push({ move, san });\n this.#position.play(move);\n return move;\n }\n}\n\n// The board for a game's start position and moves (numbered SAN, as a Game keeps them)\nexport const makeBoard = (game: { moves: string, start: string }): GameBoard => {\n const board = new GameBoard(game.start);\n for (const san of parseMoveText(game.moves)) {\n board.playSan(san);\n }\n return board;\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Position } from "../rules/position.js";
|
|
2
|
+
import type { MovesPair } from "../types.js";
|
|
3
|
+
import type { GameBoard } from "./board.js";
|
|
4
|
+
export declare const getMovesPairs: (board: Position) => MovesPair[];
|
|
5
|
+
export declare const getMoveText: (board: GameBoard) => string;
|
|
6
|
+
//# sourceMappingURL=moves.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moves.d.ts","sourceRoot":"","sources":["../../src/chess/moves.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAG1E,OAAO,KAAK,EAAa,SAAS,EAAe,MAAM,aAAa,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAoE5C,eAAO,MAAM,aAAa,UAAW,QAAQ,gBAsC5C,CAAA;AAGD,eAAO,MAAM,WAAW,UAAW,SAAS,KAAG,MAa9C,CAAA"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { parseFen } from "../rules/position.js";
|
|
2
|
+
import { makeSan, makeUci } from "../rules/san.js";
|
|
3
|
+
import { SQUARE_MAP, LABEL_MAP, SQUARE_NAMES, pieceToLabel } from "../constants.js";
|
|
4
|
+
const castlingMap = {
|
|
5
|
+
"g1": [SQUARE_MAP.h1, SQUARE_MAP.f1, LABEL_MAP.R],
|
|
6
|
+
"c1": [SQUARE_MAP.a1, SQUARE_MAP.d1, LABEL_MAP.R],
|
|
7
|
+
"g8": [SQUARE_MAP.h8, SQUARE_MAP.f8, LABEL_MAP.r],
|
|
8
|
+
"c8": [SQUARE_MAP.a8, SQUARE_MAP.d8, LABEL_MAP.r]
|
|
9
|
+
};
|
|
10
|
+
const getData = (pos, move) => {
|
|
11
|
+
const fromSquares = [move.from];
|
|
12
|
+
const toSquares = [move.to];
|
|
13
|
+
const piece = pos.board.get(move.from);
|
|
14
|
+
const targetRole = move.promotion || piece?.role || 'pawn';
|
|
15
|
+
const targets = [pieceToLabel({ role: targetRole, color: pos.turn })];
|
|
16
|
+
// Castling
|
|
17
|
+
if (piece?.role === 'king' && Math.abs(move.to - move.from) === 2) {
|
|
18
|
+
const squareName = SQUARE_NAMES[move.to];
|
|
19
|
+
if (castlingMap[squareName]) {
|
|
20
|
+
const [from, to, target] = castlingMap[squareName];
|
|
21
|
+
fromSquares.push(from);
|
|
22
|
+
toSquares.push(to);
|
|
23
|
+
targets.push(target);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// En Passant
|
|
27
|
+
else if (piece?.role === 'pawn' && move.to !== move.from && !pos.board.get(move.to) && (move.from % 8 !== move.to % 8)) {
|
|
28
|
+
const capturedPawnSquare = (move.to % 8) + (move.from / 8 | 0) * 8;
|
|
29
|
+
fromSquares.push(capturedPawnSquare);
|
|
30
|
+
}
|
|
31
|
+
const moveData = {
|
|
32
|
+
uci: makeUci(move),
|
|
33
|
+
from: fromSquares,
|
|
34
|
+
to: toSquares,
|
|
35
|
+
targets
|
|
36
|
+
};
|
|
37
|
+
return moveData;
|
|
38
|
+
};
|
|
39
|
+
const combineData = (move1Data, move2Data) => {
|
|
40
|
+
const badSquares = move2Data.from.concat(move2Data.to);
|
|
41
|
+
const from1 = move1Data.from.filter(x => !badSquares.includes(x));
|
|
42
|
+
const to1 = [];
|
|
43
|
+
const targets1 = [];
|
|
44
|
+
for (let i = 0; i < move1Data.to.length; i++) {
|
|
45
|
+
if (badSquares.includes(move1Data.to[i])) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
to1.push(move1Data.to[i]);
|
|
49
|
+
targets1.push(move1Data.targets[i]);
|
|
50
|
+
}
|
|
51
|
+
const from = from1.concat(move2Data.from);
|
|
52
|
+
const to = to1.concat(move2Data.to);
|
|
53
|
+
const targets = targets1.concat(move2Data.targets);
|
|
54
|
+
const data = {
|
|
55
|
+
from,
|
|
56
|
+
to,
|
|
57
|
+
targets
|
|
58
|
+
};
|
|
59
|
+
return data;
|
|
60
|
+
};
|
|
61
|
+
export const getMovesPairs = (board) => {
|
|
62
|
+
const pos = board.clone();
|
|
63
|
+
const movesPairs = [];
|
|
64
|
+
for (const move1 of pos.legalMoves()) {
|
|
65
|
+
const data = getData(pos, move1);
|
|
66
|
+
// Only first moves are shown, so only they need SAN
|
|
67
|
+
const move1Data = { ...data, san: makeSan(pos, move1) };
|
|
68
|
+
const pos2 = pos.clone();
|
|
69
|
+
pos2.play(move1);
|
|
70
|
+
let done = true;
|
|
71
|
+
for (const move2 of pos2.legalMoves()) {
|
|
72
|
+
const move2Data = getData(pos2, move2);
|
|
73
|
+
const movesData = combineData(move1Data, move2Data);
|
|
74
|
+
const movesPair = {
|
|
75
|
+
move1: move1Data,
|
|
76
|
+
move2: move2Data,
|
|
77
|
+
moves: movesData
|
|
78
|
+
};
|
|
79
|
+
movesPairs.push(movesPair);
|
|
80
|
+
done = false;
|
|
81
|
+
}
|
|
82
|
+
if (done) {
|
|
83
|
+
const movesPair = {
|
|
84
|
+
move1: move1Data,
|
|
85
|
+
move2: null,
|
|
86
|
+
moves: null
|
|
87
|
+
};
|
|
88
|
+
movesPairs.push(movesPair);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return movesPairs;
|
|
92
|
+
};
|
|
93
|
+
// The last one or two moves, numbered from the board's start position, e.g. "3...Nc6 4.Bb5"
|
|
94
|
+
export const getMoveText = (board) => {
|
|
95
|
+
const setup = parseFen(board.start);
|
|
96
|
+
const initialPly = (setup.fullmoves - 1) * 2 + (setup.turn === 'white' ? 0 : 1);
|
|
97
|
+
const firstIndex = Math.max(0, board.history.length - 2);
|
|
98
|
+
return board.history.slice(firstIndex).map(({ san }, offset) => {
|
|
99
|
+
const ply = initialPly + firstIndex + offset;
|
|
100
|
+
const moveNumber = Math.floor(ply / 2) + 1;
|
|
101
|
+
if (ply % 2 === 0) {
|
|
102
|
+
return `${moveNumber}.${san}`;
|
|
103
|
+
}
|
|
104
|
+
return offset === 0 ? `${moveNumber}...${san}` : san;
|
|
105
|
+
}).join(" ");
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=moves.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moves.js","sourceRoot":"","sources":["../../src/chess/moves.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,QAAQ,EAAiB,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAIpF,MAAM,WAAW,GAA+B;IAC9C,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;IACjD,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;IACjD,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;IACjD,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;CAClD,CAAA;AAED,MAAM,OAAO,GAAG,CAAC,GAAa,EAAE,IAAU,EAAE,EAAE;IAC5C,MAAM,WAAW,GAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEtC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,EAAE,IAAI,IAAI,MAAM,CAAC;IAC3D,MAAM,OAAO,GAAG,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAEtE,WAAW;IACX,IAAI,KAAK,EAAE,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAClE,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzC,IAAI,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;YACnD,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvB,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,aAAa;SACR,IAAI,KAAK,EAAE,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,KAAK,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;QACvH,MAAM,kBAAkB,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QACnE,WAAW,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,QAAQ,GAAc;QAC1B,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC;QAClB,IAAI,EAAE,WAAW;QACjB,EAAE,EAAE,SAAS;QACb,OAAO;KACR,CAAA;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAA;AAED,MAAM,WAAW,GAAG,CAAC,SAAoB,EAAE,SAAoB,EAAE,EAAE;IACjE,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAElE,MAAM,GAAG,GAAG,EAAE,CAAC;IACf,MAAM,QAAQ,GAAG,EAAE,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7C,IAAI,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1B,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAEnD,MAAM,IAAI,GAAgB;QACxB,IAAI;QACJ,EAAE;QACF,OAAO;KACR,CAAA;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAe,EAAE,EAAE;IAC/C,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;IAE1B,MAAM,UAAU,GAAgB,EAAE,CAAC;IAEnC,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,UAAU,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACjC,oDAAoD;QACpD,MAAM,SAAS,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC;QAExD,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAEjB,IAAI,IAAI,GAAG,IAAI,CAAC;QAChB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;YACtC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAEvC,MAAM,SAAS,GAAG,WAAW,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YACpD,MAAM,SAAS,GAAc;gBAC3B,KAAK,EAAE,SAAS;gBAChB,KAAK,EAAE,SAAS;gBAChB,KAAK,EAAE,SAAS;aACjB,CAAA;YACD,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC3B,IAAI,GAAG,KAAK,CAAC;QACf,CAAC;QAED,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,SAAS,GAAc;gBAC3B,KAAK,EAAE,SAAS;gBAChB,KAAK,EAAE,IAAI;gBACX,KAAK,EAAE,IAAI;aACZ,CAAA;YACD,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC,CAAA;AAED,4FAA4F;AAC5F,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAgB,EAAU,EAAE;IACtD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,UAAU,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChF,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEzD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE;QAC7D,MAAM,GAAG,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;QAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAClB,OAAO,GAAG,UAAU,IAAI,GAAG,EAAE,CAAC;QAChC,CAAC;QACD,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IACvD,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC,CAAA","sourcesContent":["import { type Move, parseFen, type Position } from \"../rules/position.js\";\nimport { makeSan, makeUci } from \"../rules/san.js\";\nimport { SQUARE_MAP, LABEL_MAP, SQUARE_NAMES, pieceToLabel } from \"../constants.js\";\nimport type { MovesData, MovesPair, SquaresData } from \"../types.js\";\nimport type { GameBoard } from \"./board.js\";\n\nconst castlingMap: { [id: string]: number[] } = {\n \"g1\": [SQUARE_MAP.h1, SQUARE_MAP.f1, LABEL_MAP.R],\n \"c1\": [SQUARE_MAP.a1, SQUARE_MAP.d1, LABEL_MAP.R],\n \"g8\": [SQUARE_MAP.h8, SQUARE_MAP.f8, LABEL_MAP.r],\n \"c8\": [SQUARE_MAP.a8, SQUARE_MAP.d8, LABEL_MAP.r]\n}\n\nconst getData = (pos: Position, move: Move) => {\n const fromSquares: number[] = [move.from];\n const toSquares: number[] = [move.to];\n\n const piece = pos.board.get(move.from);\n const targetRole = move.promotion || piece?.role || 'pawn';\n const targets = [pieceToLabel({ role: targetRole, color: pos.turn })];\n\n // Castling\n if (piece?.role === 'king' && Math.abs(move.to - move.from) === 2) {\n const squareName = SQUARE_NAMES[move.to];\n if (castlingMap[squareName]) {\n const [from, to, target] = castlingMap[squareName];\n fromSquares.push(from);\n toSquares.push(to);\n targets.push(target);\n }\n }\n // En Passant\n else if (piece?.role === 'pawn' && move.to !== move.from && !pos.board.get(move.to) && (move.from % 8 !== move.to % 8)) {\n const capturedPawnSquare = (move.to % 8) + (move.from / 8 | 0) * 8;\n fromSquares.push(capturedPawnSquare);\n }\n\n const moveData: MovesData = {\n uci: makeUci(move),\n from: fromSquares,\n to: toSquares,\n targets\n }\n return moveData;\n}\n\nconst combineData = (move1Data: MovesData, move2Data: MovesData) => {\n const badSquares = move2Data.from.concat(move2Data.to);\n const from1 = move1Data.from.filter(x => !badSquares.includes(x));\n\n const to1 = [];\n const targets1 = [];\n for (let i = 0; i < move1Data.to.length; i++) {\n if (badSquares.includes(move1Data.to[i])) {\n continue;\n }\n to1.push(move1Data.to[i]);\n targets1.push(move1Data.targets[i]);\n }\n\n const from = from1.concat(move2Data.from);\n const to = to1.concat(move2Data.to);\n const targets = targets1.concat(move2Data.targets);\n\n const data: SquaresData = {\n from,\n to,\n targets\n }\n return data;\n}\n\nexport const getMovesPairs = (board: Position) => {\n const pos = board.clone();\n\n const movesPairs: MovesPair[] = [];\n\n for (const move1 of pos.legalMoves()) {\n const data = getData(pos, move1);\n // Only first moves are shown, so only they need SAN\n const move1Data = { ...data, san: makeSan(pos, move1) };\n\n const pos2 = pos.clone();\n pos2.play(move1);\n\n let done = true;\n for (const move2 of pos2.legalMoves()) {\n const move2Data = getData(pos2, move2);\n\n const movesData = combineData(move1Data, move2Data);\n const movesPair: MovesPair = {\n move1: move1Data,\n move2: move2Data,\n moves: movesData\n }\n movesPairs.push(movesPair);\n done = false;\n }\n\n if (done) {\n const movesPair: MovesPair = {\n move1: move1Data,\n move2: null,\n moves: null\n }\n movesPairs.push(movesPair);\n }\n }\n\n return movesPairs;\n}\n\n// The last one or two moves, numbered from the board's start position, e.g. \"3...Nc6 4.Bb5\"\nexport const getMoveText = (board: GameBoard): string => {\n const setup = parseFen(board.start);\n const initialPly = (setup.fullmoves - 1) * 2 + (setup.turn === 'white' ? 0 : 1);\n const firstIndex = Math.max(0, board.history.length - 2);\n\n return board.history.slice(firstIndex).map(({ san }, offset) => {\n const ply = initialPly + firstIndex + offset;\n const moveNumber = Math.floor(ply / 2) + 1;\n if (ply % 2 === 0) {\n return `${moveNumber}.${san}`;\n }\n return offset === 0 ? `${moveNumber}...${san}` : san;\n }).join(\" \");\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Piece, Role, SquareName } from "./rules/board.js";
|
|
2
|
+
export declare const START_FEN: string;
|
|
3
|
+
export declare const MODEL_SIZE: number;
|
|
4
|
+
export declare const NUM_LABELS: number;
|
|
5
|
+
export declare const EMPTY: number;
|
|
6
|
+
export declare const PIECE_SYMBOLS: Role[];
|
|
7
|
+
export declare const labelToPiece: (label: number) => Piece;
|
|
8
|
+
export declare const pieceToLabel: (piece: Piece) => number;
|
|
9
|
+
export declare const SQUARE_NAMES: SquareName[];
|
|
10
|
+
export declare const LABEL_MAP: {
|
|
11
|
+
[id: string]: number;
|
|
12
|
+
};
|
|
13
|
+
export declare const SQUARE_MAP: {
|
|
14
|
+
[id: string]: number;
|
|
15
|
+
};
|
|
16
|
+
export declare const zeros: (rows: number, columns: number) => number[][];
|
|
17
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEhE,eAAO,MAAM,SAAS,EAAE,MAAmE,CAAC;AAC5F,eAAO,MAAM,UAAU,EAAE,MAAY,CAAC;AAGtC,eAAO,MAAM,UAAU,EAAE,MAAsB,CAAC;AAChD,eAAO,MAAM,KAAK,EAAE,MAA4B,CAAC;AACjD,eAAO,MAAM,aAAa,EAAE,IAAI,EAA0D,CAAC;AAE3F,eAAO,MAAM,YAAY,UAAW,MAAM,KAAG,KAG3C,CAAC;AAEH,eAAO,MAAM,YAAY,UAAW,KAAK,KAAG,MAC6B,CAAC;AAC1E,eAAO,MAAM,YAAY,EAAE,UAAU,EAOY,CAAC;AASlD,eAAO,MAAM,SAAS,EAAE;IAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAmB,CAAC;AASlE,eAAO,MAAM,UAAU,EAAE;IAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAoB,CAAC;AAEpE,eAAO,MAAM,KAAK,SAAU,MAAM,WAAW,MAAM,KAAG,MAAM,EAAE,EAE7D,CAAA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const START_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
|
2
|
+
export const MODEL_SIZE = 320;
|
|
3
|
+
// Black pieces, white pieces, then the empty square
|
|
4
|
+
const LABELS = ["b", "k", "n", "p", "q", "r", "B", "K", "N", "P", "Q", "R", "."];
|
|
5
|
+
export const NUM_LABELS = LABELS.length;
|
|
6
|
+
export const EMPTY = LABELS.indexOf(".");
|
|
7
|
+
export const PIECE_SYMBOLS = ["bishop", "king", "knight", "pawn", "queen", "rook"];
|
|
8
|
+
export const labelToPiece = (label) => ({
|
|
9
|
+
role: PIECE_SYMBOLS[label % 6],
|
|
10
|
+
color: (label > 5) ? 'white' : 'black'
|
|
11
|
+
});
|
|
12
|
+
export const pieceToLabel = (piece) => ((piece.color === 'white') ? 6 : 0) + PIECE_SYMBOLS.indexOf(piece.role);
|
|
13
|
+
export const SQUARE_NAMES = ['a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1',
|
|
14
|
+
'a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2',
|
|
15
|
+
'a3', 'b3', 'c3', 'd3', 'e3', 'f3', 'g3', 'h3',
|
|
16
|
+
'a4', 'b4', 'c4', 'd4', 'e4', 'f4', 'g4', 'h4',
|
|
17
|
+
'a5', 'b5', 'c5', 'd5', 'e5', 'f5', 'g5', 'h5',
|
|
18
|
+
'a6', 'b6', 'c6', 'd6', 'e6', 'f6', 'g6', 'h6',
|
|
19
|
+
'a7', 'b7', 'c7', 'd7', 'e7', 'f7', 'g7', 'h7',
|
|
20
|
+
'a8', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8'];
|
|
21
|
+
const makeLabelMap = () => {
|
|
22
|
+
const d = {};
|
|
23
|
+
LABELS.forEach((label, i) => {
|
|
24
|
+
d[label] = i;
|
|
25
|
+
});
|
|
26
|
+
return d;
|
|
27
|
+
};
|
|
28
|
+
export const LABEL_MAP = makeLabelMap();
|
|
29
|
+
const makeSquareMap = () => {
|
|
30
|
+
const d = {};
|
|
31
|
+
SQUARE_NAMES.forEach((square, i) => {
|
|
32
|
+
d[square] = i;
|
|
33
|
+
});
|
|
34
|
+
return d;
|
|
35
|
+
};
|
|
36
|
+
export const SQUARE_MAP = makeSquareMap();
|
|
37
|
+
export const zeros = (rows, columns) => {
|
|
38
|
+
return Array.from({ length: rows }, () => Array(columns).fill(0));
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Piece, Role, SquareName } from \"./rules/board.js\";\n\nexport const START_FEN: string = \"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\";\nexport const MODEL_SIZE: number = 320;\n// Black pieces, white pieces, then the empty square\nconst LABELS: string[] = [\"b\", \"k\", \"n\", \"p\", \"q\", \"r\", \"B\", \"K\", \"N\", \"P\", \"Q\", \"R\", \".\"];\nexport const NUM_LABELS: number = LABELS.length;\nexport const EMPTY: number = LABELS.indexOf(\".\");\nexport const PIECE_SYMBOLS: Role[] = [\"bishop\", \"king\", \"knight\", \"pawn\", \"queen\", \"rook\"];\n\nexport const labelToPiece = (label: number): Piece => ({\n role: PIECE_SYMBOLS[label % 6],\n color: (label > 5) ? 'white' : 'black'\n});\n\nexport const pieceToLabel = (piece: Piece): number =>\n ((piece.color === 'white') ? 6 : 0) + PIECE_SYMBOLS.indexOf(piece.role);\nexport const SQUARE_NAMES: SquareName[] = ['a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1',\n 'a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2',\n 'a3', 'b3', 'c3', 'd3', 'e3', 'f3', 'g3', 'h3',\n 'a4', 'b4', 'c4', 'd4', 'e4', 'f4', 'g4', 'h4',\n 'a5', 'b5', 'c5', 'd5', 'e5', 'f5', 'g5', 'h5',\n 'a6', 'b6', 'c6', 'd6', 'e6', 'f6', 'g6', 'h6',\n 'a7', 'b7', 'c7', 'd7', 'e7', 'f7', 'g7', 'h7',\n 'a8', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8'];\n\nconst makeLabelMap = () => {\n const d: { [id: string]: number } = {};\n LABELS.forEach((label, i) => {\n d[label] = i;\n })\n return d;\n}\nexport const LABEL_MAP: { [id: string]: number } = makeLabelMap();\n\nconst makeSquareMap = () => {\n const d: { [id: string]: number } = {};\n SQUARE_NAMES.forEach((square, i) => {\n d[square] = i;\n })\n return d;\n}\nexport const SQUARE_MAP: { [id: string]: number } = makeSquareMap();\n\nexport const zeros = (rows: number, columns: number): number[][] => {\n return Array.from({ length: rows }, () => Array(columns).fill(0));\n}\n"]}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type RecorderErrorCode = "not-ready" | "worker-stopped" | "unloaded" | "detection-failed";
|
|
2
|
+
export declare class RecorderError extends Error {
|
|
3
|
+
readonly code: RecorderErrorCode;
|
|
4
|
+
constructor(code: RecorderErrorCode, message?: string, options?: ErrorOptions);
|
|
5
|
+
}
|
|
6
|
+
export declare const toRecorderError: (error: unknown) => RecorderError;
|
|
7
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,iBAAiB,GAEzB,WAAW,GAEX,gBAAgB,GAEhB,UAAU,GAEV,kBAAkB,CAAC;AAEvB,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAEjC,YAAY,IAAI,EAAE,iBAAiB,EAAE,OAAO,GAAE,MAAa,EAAE,OAAO,CAAC,EAAE,YAAY,EAIlF;CACF;AAED,eAAO,MAAM,eAAe,UAAW,OAAO,KAAG,aACoE,CAAC"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export class RecorderError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code, message = code, options) {
|
|
4
|
+
super(message, options);
|
|
5
|
+
this.name = "RecorderError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export const toRecorderError = (error) => (error instanceof RecorderError) ? error
|
|
10
|
+
: new RecorderError("detection-failed", (error instanceof Error) ? error.message : String(error), { cause: error });
|
|
11
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC7B,IAAI,CAAoB;IAEjC,YAAY,IAAuB,EAAE,OAAO,GAAW,IAAI,EAAE,OAAsB;QACjF,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAc,EAAiB,EAAE,CAAC,CAAC,KAAK,YAAY,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK;IACxG,CAAC,CAAC,IAAI,aAAa,CAAC,kBAAkB,EAAE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC","sourcesContent":["// Why detection failed, as a code for the app to show in its own words\nexport type RecorderErrorCode =\n // The model isn't loaded, or the video has no frame yet\n | \"not-ready\"\n // The CPU worker crashed (e.g. it ran out of memory), so the model must be loaded again\n | \"worker-stopped\"\n // The model was deleted\n | \"unloaded\"\n // Anything else; the message says more\n | \"detection-failed\";\n\nexport class RecorderError extends Error {\n readonly code: RecorderErrorCode;\n\n constructor(code: RecorderErrorCode, message: string = code, options?: ErrorOptions) {\n super(message, options);\n this.name = \"RecorderError\";\n this.code = code;\n }\n}\n\nexport const toRecorderError = (error: unknown): RecorderError => (error instanceof RecorderError) ? error\n : new RecorderError(\"detection-failed\", (error instanceof Error) ? error.message : String(error), { cause: error });\n"]}
|
package/dist/game.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type GameBoard } from "./chess/board.js";
|
|
2
|
+
import type { Game } from "./types.js";
|
|
3
|
+
export type GameUpdate = Omit<Game, "start" | "times">;
|
|
4
|
+
export declare const newGame: (start?: string) => Game;
|
|
5
|
+
export declare const makeUpdatePayload: (board: GameBoard, fromOpponent?: boolean) => GameUpdate;
|
|
6
|
+
export declare const keptTimes: (game: Game, moves: string) => number[];
|
|
7
|
+
export declare const updateTimes: (game: Game, moves: string, elapsed?: number, ago?: (number | null)[]) => number[];
|
|
8
|
+
export declare const makePgn: (game: Pick<Game, "moves" | "start"> & Partial<Pick<Game, "times">>, headers?: Record<string, string>) => string;
|
|
9
|
+
//# sourceMappingURL=game.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game.d.ts","sourceRoot":"","sources":["../src/game.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAa,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGvC,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC;AAEvD,eAAO,MAAM,OAAO,WAAW,MAAM,KAAe,IAElD,CAAC;AAEH,eAAO,MAAM,iBAAiB,UAAW,SAAS,iBAAgB,OAAO,KAAW,UAKlF,CAAC;AAGH,eAAO,MAAM,SAAS,SAAU,IAAI,SAAS,MAAM,KAAG,MAAM,EAQ3D,CAAA;AAMD,eAAO,MAAM,WAAW,SAAU,IAAI,SAAS,MAAM,YAAY,MAAM,QAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,KAAQ,MAAM,EAW5G,CAAA;AAID,eAAO,MAAM,OAAO,SAAU,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,YAC/E,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAQ,MAkBxC,CAAA"}
|