hyperchess-core 0.1.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 +674 -0
- package/README.md +83 -0
- package/dist/board/create.d.ts +13 -0
- package/dist/board/create.d.ts.map +1 -0
- package/dist/board/create.js +21 -0
- package/dist/board/create.js.map +1 -0
- package/dist/board/hfen.d.ts +31 -0
- package/dist/board/hfen.d.ts.map +1 -0
- package/dist/board/hfen.js +181 -0
- package/dist/board/hfen.js.map +1 -0
- package/dist/board/index.d.ts +6 -0
- package/dist/board/index.d.ts.map +1 -0
- package/dist/board/index.js +11 -0
- package/dist/board/index.js.map +1 -0
- package/dist/board/move.d.ts +16 -0
- package/dist/board/move.d.ts.map +1 -0
- package/dist/board/move.js +43 -0
- package/dist/board/move.js.map +1 -0
- package/dist/board/undo.d.ts +4 -0
- package/dist/board/undo.d.ts.map +1 -0
- package/dist/board/undo.js +22 -0
- package/dist/board/undo.js.map +1 -0
- package/dist/board/validate.d.ts +7 -0
- package/dist/board/validate.d.ts.map +1 -0
- package/dist/board/validate.js +14 -0
- package/dist/board/validate.js.map +1 -0
- package/dist/game/add-move.d.ts +7 -0
- package/dist/game/add-move.d.ts.map +1 -0
- package/dist/game/add-move.js +78 -0
- package/dist/game/add-move.js.map +1 -0
- package/dist/game/index.d.ts +21 -0
- package/dist/game/index.d.ts.map +1 -0
- package/dist/game/index.js +58 -0
- package/dist/game/index.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/io/hsan-exporter.d.ts +10 -0
- package/dist/io/hsan-exporter.d.ts.map +1 -0
- package/dist/io/hsan-exporter.js +70 -0
- package/dist/io/hsan-exporter.js.map +1 -0
- package/dist/io/hsan-parser.d.ts +12 -0
- package/dist/io/hsan-parser.d.ts.map +1 -0
- package/dist/io/hsan-parser.js +118 -0
- package/dist/io/hsan-parser.js.map +1 -0
- package/dist/io/index.d.ts +3 -0
- package/dist/io/index.d.ts.map +1 -0
- package/dist/io/index.js +8 -0
- package/dist/io/index.js.map +1 -0
- package/dist/moves/index.d.ts +22 -0
- package/dist/moves/index.d.ts.map +1 -0
- package/dist/moves/index.js +39 -0
- package/dist/moves/index.js.map +1 -0
- package/dist/standalone.d.ts +64 -0
- package/dist/standalone.d.ts.map +1 -0
- package/dist/standalone.js +323 -0
- package/dist/standalone.js.map +1 -0
- package/dist/types/board.d.ts +38 -0
- package/dist/types/board.d.ts.map +1 -0
- package/dist/types/board.js +7 -0
- package/dist/types/board.js.map +1 -0
- package/dist/types/game.d.ts +45 -0
- package/dist/types/game.d.ts.map +1 -0
- package/dist/types/game.js +7 -0
- package/dist/types/game.js.map +1 -0
- package/dist/types/move.d.ts +24 -0
- package/dist/types/move.d.ts.map +1 -0
- package/dist/types/move.js +7 -0
- package/dist/types/move.js.map +1 -0
- package/dist/types/piece.d.ts +50 -0
- package/dist/types/piece.d.ts.map +1 -0
- package/dist/types/piece.js +29 -0
- package/dist/types/piece.js.map +1 -0
- package/dist/utils/square-notation.d.ts +14 -0
- package/dist/utils/square-notation.d.ts.map +1 -0
- package/dist/utils/square-notation.js +27 -0
- package/dist/utils/square-notation.js.map +1 -0
- package/dist/wasm/engine.d.ts +23 -0
- package/dist/wasm/engine.d.ts.map +1 -0
- package/dist/wasm/engine.js +37 -0
- package/dist/wasm/engine.js.map +1 -0
- package/dist/wasm/snapshot.d.ts +47 -0
- package/dist/wasm/snapshot.d.ts.map +1 -0
- package/dist/wasm/snapshot.js +125 -0
- package/dist/wasm/snapshot.js.map +1 -0
- package/package.json +74 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# hyperchess-core
|
|
2
|
+
|
|
3
|
+
Pure HyperChess game logic with zero dependencies. Works on all platforms.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install hyperchess-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createBoard, getBoardHfen } from 'hyperchess-core';
|
|
15
|
+
|
|
16
|
+
// Create a board from starting position
|
|
17
|
+
const board = createBoard();
|
|
18
|
+
|
|
19
|
+
// Export to HFEN-I notation
|
|
20
|
+
const hfen = getBoardHfen(board);
|
|
21
|
+
console.log(hfen); // 12/abcdefghijkl/...
|
|
22
|
+
|
|
23
|
+
// Or create from custom HFEN
|
|
24
|
+
const customBoard = createBoard(hfen);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## API
|
|
28
|
+
|
|
29
|
+
### Board Operations
|
|
30
|
+
- `createBoard(hfen?: string): Board` - Create board from HFEN or default
|
|
31
|
+
- `applyMove(board: Board, move: Move): Board` - Apply move (returns new board)
|
|
32
|
+
- `isLegalMove(board: Board, move: Move): boolean` - Check move validity
|
|
33
|
+
- `undoMove(board: Board): Board` - Undo last move
|
|
34
|
+
- `getBoardHfen(board: Board): string` - Export to HFEN-I notation
|
|
35
|
+
|
|
36
|
+
### Move Generation
|
|
37
|
+
- `generateLegalMoves(board: Board): Move[]` - All legal moves
|
|
38
|
+
- `isInCheck(board: Board): boolean` - Is current player in check?
|
|
39
|
+
- `isCheckmate(board: Board): boolean` - Is checkmate?
|
|
40
|
+
|
|
41
|
+
### Game State
|
|
42
|
+
- `createGame(hfen?: string): Game` - Start new game
|
|
43
|
+
- `addMove(game: Game, move: string): Game | null` - Add move to game
|
|
44
|
+
- `removeLastMove(game: Game): Game` - Undo move
|
|
45
|
+
- `getGameStatus(game: Game): GameStatus` - Current status
|
|
46
|
+
|
|
47
|
+
## Architecture
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
src/
|
|
51
|
+
├── types/ # Type definitions (no logic)
|
|
52
|
+
│ ├── board.ts # Board state
|
|
53
|
+
│ ├── move.ts # Move types
|
|
54
|
+
│ ├── piece.ts # Piece types
|
|
55
|
+
│ └── game.ts # Game state
|
|
56
|
+
├── board/ # Board operations
|
|
57
|
+
│ ├── create.ts # HFEN parsing
|
|
58
|
+
│ ├── move.ts # Move application
|
|
59
|
+
│ ├── validate.ts # Legality checking
|
|
60
|
+
│ ├── undo.ts # Undo logic
|
|
61
|
+
│ └── hfen.ts # HFEN export
|
|
62
|
+
├── moves/ # Move generation
|
|
63
|
+
│ └── index.ts # generateLegalMoves, checks
|
|
64
|
+
├── game/ # Game state machine
|
|
65
|
+
│ └── index.ts # createGame, addMove, status
|
|
66
|
+
└── index.ts # Public API exports
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Tree-Shaking
|
|
70
|
+
|
|
71
|
+
All exports are separate files, allowing bundlers to eliminate unused code:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// Only import what you need
|
|
75
|
+
import { createBoard } from 'hyperchess-core/board';
|
|
76
|
+
import { generateLegalMoves } from 'hyperchess-core/moves';
|
|
77
|
+
|
|
78
|
+
// Unused piece types are not bundled
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Board } from '../types/board';
|
|
2
|
+
/**
|
|
3
|
+
* Create a board from HFEN-I notation (or the default starting position if
|
|
4
|
+
* `hfen` is omitted).
|
|
5
|
+
*
|
|
6
|
+
* Delegates parsing entirely to the Rust engine via WASM — this used to be
|
|
7
|
+
* a hand-written parser here that never implemented HFEN-I identity decoding
|
|
8
|
+
* and mis-parsed multi-digit empty-square runs, so `createBoard()` with no
|
|
9
|
+
* arguments (the SDK's own default starting position!) threw on every call.
|
|
10
|
+
* See docs/sdk-plan/WASM-MIGRATION-PLAN.md for the full history.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createBoard(hfen?: string): Board;
|
|
13
|
+
//# sourceMappingURL=create.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/board/create.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGvC;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAGhD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// HyperChess Core — hyperchess-core
|
|
3
|
+
// File: packages/core/src/board/create.ts
|
|
4
|
+
// Version: 1.0.0
|
|
5
|
+
// Copyright (c) 2026 HyperChess Developer Team
|
|
6
|
+
import { loadWasmBoard, snapshotFromWasmBoard } from '../wasm/engine';
|
|
7
|
+
/**
|
|
8
|
+
* Create a board from HFEN-I notation (or the default starting position if
|
|
9
|
+
* `hfen` is omitted).
|
|
10
|
+
*
|
|
11
|
+
* Delegates parsing entirely to the Rust engine via WASM — this used to be
|
|
12
|
+
* a hand-written parser here that never implemented HFEN-I identity decoding
|
|
13
|
+
* and mis-parsed multi-digit empty-square runs, so `createBoard()` with no
|
|
14
|
+
* arguments (the SDK's own default starting position!) threw on every call.
|
|
15
|
+
* See docs/sdk-plan/WASM-MIGRATION-PLAN.md for the full history.
|
|
16
|
+
*/
|
|
17
|
+
export function createBoard(hfen) {
|
|
18
|
+
const wasmBoard = loadWasmBoard(hfen);
|
|
19
|
+
return snapshotFromWasmBoard(wasmBoard);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/board/create.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,oCAAoC;AACpC,0CAA0C;AAC1C,iBAAiB;AACjB,+CAA+C;AAG/C,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEtE;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,IAAa;IACvC,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACtC,OAAO,qBAAqB,CAAC,SAAS,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Board } from '../types/board';
|
|
2
|
+
import { Piece, PieceType } from '../types/piece';
|
|
3
|
+
/** The starting type of an identity character, or `undefined` if it is not one. */
|
|
4
|
+
export declare function identityStartType(identity: string): PieceType | undefined;
|
|
5
|
+
/** True when `c` is a legal HFEN-I identity character (`A`-`X` / `a`-`x`). */
|
|
6
|
+
export declare function isIdentityChar(c: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Parse the position field of a HFEN/HFEN-I string into 144 squares.
|
|
9
|
+
*
|
|
10
|
+
* Index 0 is a1 and index 143 is l12, matching `Board.pieces`. Ranks in the
|
|
11
|
+
* string run 12 first down to 1 last.
|
|
12
|
+
*
|
|
13
|
+
* Handles the three token shapes the format allows, and nothing else:
|
|
14
|
+
* an inline `id:Type` pair (ONE square), a single piece/identity character,
|
|
15
|
+
* and a multi-digit empty-square run. Multi-digit runs matter — `12` is one
|
|
16
|
+
* fully empty rank, not two squares.
|
|
17
|
+
*
|
|
18
|
+
* Throws on a malformed field rather than returning a partly-filled board:
|
|
19
|
+
* a position that cannot be read is an error, never a default.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseHfenPosition(position: string): (Piece | undefined)[];
|
|
22
|
+
/**
|
|
23
|
+
* Export a board to HFEN-I notation.
|
|
24
|
+
*
|
|
25
|
+
* Identity-preserving whenever the board carries identity. This function is
|
|
26
|
+
* fed straight back into the engine by `generateLegalMoves`, `applyMove` and
|
|
27
|
+
* friends, so a type-only implementation here silently erased piece identity
|
|
28
|
+
* on every single move the SDK made.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getBoardHfen(board: Board): string;
|
|
31
|
+
//# sourceMappingURL=hfen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hfen.d.ts","sourceRoot":"","sources":["../../src/board/hfen.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAEvC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAkBlD,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAEzE;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,KAAK,GAAG,SAAS,CAAC,EAAE,CA2EzE;AAuBD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAuDjD"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// HyperChess Core — hyperchess-core
|
|
3
|
+
// File: packages/core/src/board/hfen.ts
|
|
4
|
+
// Version: 2.0.0
|
|
5
|
+
// Copyright (c) 2026 HyperChess Developer Team
|
|
6
|
+
/**
|
|
7
|
+
* HFEN-I identity character → the piece type that identity *starts* as.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors `piece_from_identity` in `crates/hyperchess-rules/src/core/
|
|
10
|
+
* piece_identity.rs`; the two must agree. Note that the identity alphabet and
|
|
11
|
+
* the type alphabet overlap while meaning different things: as an identity,
|
|
12
|
+
* `Q` is the white pawn from f3 and `K` the white hawk from k2.
|
|
13
|
+
*/
|
|
14
|
+
const IDENTITY_START_TYPE = {
|
|
15
|
+
A: 'E', B: 'H', C: 'R', D: 'N', E: 'B', F: 'Q', G: 'K', H: 'B', I: 'N', J: 'R', K: 'H', L: 'E',
|
|
16
|
+
M: 'P', N: 'P', O: 'P', P: 'P', Q: 'P', R: 'P', S: 'P', T: 'P', U: 'P', V: 'P', W: 'P', X: 'P',
|
|
17
|
+
};
|
|
18
|
+
/** Type letters legal in the right half of an inline `id:Type` pair. */
|
|
19
|
+
const PROMOTION_TYPES = 'QRBNEH';
|
|
20
|
+
/** The starting type of an identity character, or `undefined` if it is not one. */
|
|
21
|
+
export function identityStartType(identity) {
|
|
22
|
+
return IDENTITY_START_TYPE[identity.toUpperCase()];
|
|
23
|
+
}
|
|
24
|
+
/** True when `c` is a legal HFEN-I identity character (`A`-`X` / `a`-`x`). */
|
|
25
|
+
export function isIdentityChar(c) {
|
|
26
|
+
return c.length === 1 && IDENTITY_START_TYPE[c.toUpperCase()] !== undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parse the position field of a HFEN/HFEN-I string into 144 squares.
|
|
30
|
+
*
|
|
31
|
+
* Index 0 is a1 and index 143 is l12, matching `Board.pieces`. Ranks in the
|
|
32
|
+
* string run 12 first down to 1 last.
|
|
33
|
+
*
|
|
34
|
+
* Handles the three token shapes the format allows, and nothing else:
|
|
35
|
+
* an inline `id:Type` pair (ONE square), a single piece/identity character,
|
|
36
|
+
* and a multi-digit empty-square run. Multi-digit runs matter — `12` is one
|
|
37
|
+
* fully empty rank, not two squares.
|
|
38
|
+
*
|
|
39
|
+
* Throws on a malformed field rather than returning a partly-filled board:
|
|
40
|
+
* a position that cannot be read is an error, never a default.
|
|
41
|
+
*/
|
|
42
|
+
export function parseHfenPosition(position) {
|
|
43
|
+
const squares = new Array(144).fill(undefined);
|
|
44
|
+
const ranks = position.split('/');
|
|
45
|
+
if (ranks.length !== 12) {
|
|
46
|
+
throw new Error(`HFEN position needs 12 ranks, got ${ranks.length}`);
|
|
47
|
+
}
|
|
48
|
+
// Identity mode is on when any character is identity-only — i.e. an
|
|
49
|
+
// identity letter that is not also a legacy type letter. This mirrors
|
|
50
|
+
// `position_uses_identity` in the Rust engine.
|
|
51
|
+
const identityMode = /[ACDFGIJLMOSTUVWXacdfgijlmostuvwx]/.test(position);
|
|
52
|
+
ranks.forEach((rankStr, rankFromTop) => {
|
|
53
|
+
const rank = 11 - rankFromTop;
|
|
54
|
+
let file = 0;
|
|
55
|
+
const tokens = rankStr.match(/[A-Za-z]:[A-Za-z]|[A-Za-z]|\d+/g) ?? [];
|
|
56
|
+
// A rank whose characters did not all get consumed is malformed.
|
|
57
|
+
if (tokens.join('') !== rankStr) {
|
|
58
|
+
throw new Error(`Malformed HFEN rank ${rank + 1}: ${rankStr}`);
|
|
59
|
+
}
|
|
60
|
+
for (const token of tokens) {
|
|
61
|
+
if (/^\d+$/.test(token)) {
|
|
62
|
+
file += parseInt(token, 10);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (file >= 12) {
|
|
66
|
+
throw new Error(`Too many squares on HFEN rank ${rank + 1}`);
|
|
67
|
+
}
|
|
68
|
+
let identity;
|
|
69
|
+
let typeChar;
|
|
70
|
+
if (token.length === 3) {
|
|
71
|
+
identity = token[0];
|
|
72
|
+
typeChar = token[2];
|
|
73
|
+
if (!PROMOTION_TYPES.includes(typeChar.toUpperCase())) {
|
|
74
|
+
throw new Error(`Invalid \`id:Type\` type letter '${typeChar}' in ${token}: the type half must be ` +
|
|
75
|
+
`one of ${PROMOTION_TYPES.split('').join(' ')}`);
|
|
76
|
+
}
|
|
77
|
+
const identityIsWhite = identity === identity.toUpperCase();
|
|
78
|
+
const typeIsWhite = typeChar === typeChar.toUpperCase();
|
|
79
|
+
if (identityIsWhite !== typeIsWhite) {
|
|
80
|
+
throw new Error(`Type override ${token} changes piece color`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (identityMode && isIdentityChar(token)) {
|
|
84
|
+
identity = token;
|
|
85
|
+
typeChar = token;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
typeChar = token;
|
|
89
|
+
}
|
|
90
|
+
const color = typeChar === typeChar.toUpperCase() ? 'white' : 'black';
|
|
91
|
+
const type = token.length === 3 || !identity
|
|
92
|
+
? typeChar.toUpperCase()
|
|
93
|
+
: identityStartType(identity);
|
|
94
|
+
if (!type) {
|
|
95
|
+
throw new Error(`Invalid HFEN piece character: ${token}`);
|
|
96
|
+
}
|
|
97
|
+
squares[rank * 12 + file] = identity ? { type, color, identity } : { type, color };
|
|
98
|
+
file += 1;
|
|
99
|
+
}
|
|
100
|
+
if (file !== 12) {
|
|
101
|
+
throw new Error(`HFEN rank ${rank + 1} has ${file} squares, expected 12`);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return squares;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Serialize one square for the position field.
|
|
108
|
+
*
|
|
109
|
+
* A piece carrying identity writes its identity letter alone when that letter
|
|
110
|
+
* already parses back to the piece's current type, and an inline `id:Type`
|
|
111
|
+
* pair when it does not — which in practice means after a promotion. This is
|
|
112
|
+
* what keeps the string self-contained: both *which* piece it is and *what*
|
|
113
|
+
* it currently is live inside the position field, with no side table.
|
|
114
|
+
*/
|
|
115
|
+
function serializeSquare(piece) {
|
|
116
|
+
const typeChar = piece.color === 'white' ? piece.type : piece.type.toLowerCase();
|
|
117
|
+
if (!piece.identity) {
|
|
118
|
+
return typeChar;
|
|
119
|
+
}
|
|
120
|
+
if (identityStartType(piece.identity) === piece.type) {
|
|
121
|
+
return piece.identity;
|
|
122
|
+
}
|
|
123
|
+
return `${piece.identity}:${typeChar}`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Export a board to HFEN-I notation.
|
|
127
|
+
*
|
|
128
|
+
* Identity-preserving whenever the board carries identity. This function is
|
|
129
|
+
* fed straight back into the engine by `generateLegalMoves`, `applyMove` and
|
|
130
|
+
* friends, so a type-only implementation here silently erased piece identity
|
|
131
|
+
* on every single move the SDK made.
|
|
132
|
+
*/
|
|
133
|
+
export function getBoardHfen(board) {
|
|
134
|
+
// Piece placement. Standard FEN convention: highest rank first, so we walk
|
|
135
|
+
// rank indices 11 → 0 — within each rank, files still run 0 → 11.
|
|
136
|
+
const rankSegments = [];
|
|
137
|
+
for (let rank = 11; rank >= 0; rank--) {
|
|
138
|
+
let segment = '';
|
|
139
|
+
let emptyCount = 0;
|
|
140
|
+
for (let file = 0; file < 12; file++) {
|
|
141
|
+
const piece = board.pieces[rank * 12 + file];
|
|
142
|
+
if (!piece) {
|
|
143
|
+
emptyCount++;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
if (emptyCount > 0) {
|
|
147
|
+
segment += emptyCount;
|
|
148
|
+
emptyCount = 0;
|
|
149
|
+
}
|
|
150
|
+
segment += serializeSquare(piece);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (emptyCount > 0)
|
|
154
|
+
segment += emptyCount;
|
|
155
|
+
rankSegments.push(segment);
|
|
156
|
+
}
|
|
157
|
+
let hfen = rankSegments.join('/');
|
|
158
|
+
// Side to move
|
|
159
|
+
hfen += ` ${board.toMove === 'white' ? 'w' : 'b'}`;
|
|
160
|
+
// Castling rights
|
|
161
|
+
const castling = (board.castlingRights.whiteKingSide ? 'K' : '') +
|
|
162
|
+
(board.castlingRights.whiteQueenSide ? 'Q' : '') +
|
|
163
|
+
(board.castlingRights.blackKingSide ? 'k' : '') +
|
|
164
|
+
(board.castlingRights.blackQueenSide ? 'q' : '');
|
|
165
|
+
hfen += ` ${castling || '-'}`;
|
|
166
|
+
// En passant
|
|
167
|
+
if (board.enPassantSquare === -1) {
|
|
168
|
+
hfen += ' -';
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
const col = String.fromCharCode('a'.charCodeAt(0) + (board.enPassantSquare % 12));
|
|
172
|
+
const row = Math.floor(board.enPassantSquare / 12) + 1;
|
|
173
|
+
hfen += ` ${col}${row}`;
|
|
174
|
+
}
|
|
175
|
+
// Halfmove clock
|
|
176
|
+
hfen += ` ${board.halfmoveClock}`;
|
|
177
|
+
// Fullmove number
|
|
178
|
+
hfen += ` ${board.fullmoveNumber}`;
|
|
179
|
+
return hfen;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=hfen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hfen.js","sourceRoot":"","sources":["../../src/board/hfen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/board/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// HyperChess Core — hyperchess-core
|
|
3
|
+
// File: packages/core/src/board/index.ts
|
|
4
|
+
// Version: 1.0.0
|
|
5
|
+
// Copyright (c) 2026 HyperChess Developer Team
|
|
6
|
+
export { createBoard } from './create';
|
|
7
|
+
export { applyMove } from './move';
|
|
8
|
+
export { isLegalMove } from './validate';
|
|
9
|
+
export { undoMove } from './undo';
|
|
10
|
+
export { getBoardHfen } from './hfen';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/board/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,oCAAoC;AACpC,yCAAyC;AACzC,iBAAiB;AACjB,+CAA+C;AAE/C,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Board } from '../types/board';
|
|
2
|
+
import { Move } from '../types/move';
|
|
3
|
+
/**
|
|
4
|
+
* Apply a move to the board, returning a new board state.
|
|
5
|
+
* Does NOT validate legality - use isLegalMove() first.
|
|
6
|
+
*
|
|
7
|
+
* Delegates to the Rust engine via WASM — this used to hand-roll castling
|
|
8
|
+
* rook relocation, en passant capture removal, and castling-rights
|
|
9
|
+
* invalidation here, which drifted out of sync with the real rules more than
|
|
10
|
+
* once (see docs/sdk-plan/WASM-MIGRATION-PLAN.md). Round-trips through HFEN
|
|
11
|
+
* on each call rather than keeping a live `WasmBoard` across calls, trading
|
|
12
|
+
* some overhead for keeping `Board` a plain, directly-inspectable snapshot
|
|
13
|
+
* object everywhere else in the SDK.
|
|
14
|
+
*/
|
|
15
|
+
export declare function applyMove(board: Board, move: Move): Board;
|
|
16
|
+
//# sourceMappingURL=move.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"move.d.ts","sourceRoot":"","sources":["../../src/board/move.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAIrC;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,KAAK,CAyBzD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// HyperChess Core — hyperchess-core
|
|
3
|
+
// File: packages/core/src/board/move.ts
|
|
4
|
+
// Version: 1.0.0
|
|
5
|
+
// Copyright (c) 2026 HyperChess Developer Team
|
|
6
|
+
import { getBoardHfen } from './hfen';
|
|
7
|
+
import { loadWasmBoard, snapshotFromWasmBoard, moveToUci } from '../wasm/engine';
|
|
8
|
+
/**
|
|
9
|
+
* Apply a move to the board, returning a new board state.
|
|
10
|
+
* Does NOT validate legality - use isLegalMove() first.
|
|
11
|
+
*
|
|
12
|
+
* Delegates to the Rust engine via WASM — this used to hand-roll castling
|
|
13
|
+
* rook relocation, en passant capture removal, and castling-rights
|
|
14
|
+
* invalidation here, which drifted out of sync with the real rules more than
|
|
15
|
+
* once (see docs/sdk-plan/WASM-MIGRATION-PLAN.md). Round-trips through HFEN
|
|
16
|
+
* on each call rather than keeping a live `WasmBoard` across calls, trading
|
|
17
|
+
* some overhead for keeping `Board` a plain, directly-inspectable snapshot
|
|
18
|
+
* object everywhere else in the SDK.
|
|
19
|
+
*/
|
|
20
|
+
export function applyMove(board, move) {
|
|
21
|
+
const piece = board.pieces[move.from];
|
|
22
|
+
if (!piece) {
|
|
23
|
+
throw new Error(`No piece at source square ${move.from}`);
|
|
24
|
+
}
|
|
25
|
+
const wasmBoard = loadWasmBoard(getBoardHfen(board));
|
|
26
|
+
const applied = wasmBoard.apply_move(moveToUci(move));
|
|
27
|
+
if (!applied) {
|
|
28
|
+
throw new Error(`Illegal move: ${move.from} -> ${move.to}`);
|
|
29
|
+
}
|
|
30
|
+
const history = [
|
|
31
|
+
...board.history,
|
|
32
|
+
{
|
|
33
|
+
pieces: [...board.pieces],
|
|
34
|
+
toMove: board.toMove,
|
|
35
|
+
enPassantSquare: board.enPassantSquare,
|
|
36
|
+
castlingRights: { ...board.castlingRights },
|
|
37
|
+
halfmoveClock: board.halfmoveClock,
|
|
38
|
+
fullmoveNumber: board.fullmoveNumber,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
return snapshotFromWasmBoard(wasmBoard, history);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=move.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"move.js","sourceRoot":"","sources":["../../src/board/move.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,oCAAoC;AACpC,wCAAwC;AACxC,iBAAiB;AACjB,+CAA+C;AAI/C,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEjF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,KAAY,EAAE,IAAU;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,SAAS,GAAG,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IACtD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,OAAO,GAAG;QACd,GAAG,KAAK,CAAC,OAAO;QAChB;YACE,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;YACzB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,cAAc,EAAE,EAAE,GAAG,KAAK,CAAC,cAAc,EAAE;YAC3C,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,cAAc,EAAE,KAAK,CAAC,cAAc;SACrC;KACF,CAAC;IAEF,OAAO,qBAAqB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"undo.d.ts","sourceRoot":"","sources":["../../src/board/undo.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAEvC,yBAAyB;AACzB,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAgB5C"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// HyperChess Core — hyperchess-core
|
|
3
|
+
// File: packages/core/src/board/undo.ts
|
|
4
|
+
// Version: 1.0.0
|
|
5
|
+
// Copyright (c) 2026 HyperChess Developer Team
|
|
6
|
+
/** Undo the last move */
|
|
7
|
+
export function undoMove(board) {
|
|
8
|
+
if (board.history.length === 0) {
|
|
9
|
+
throw new Error('No moves to undo');
|
|
10
|
+
}
|
|
11
|
+
const lastState = board.history[board.history.length - 1];
|
|
12
|
+
return {
|
|
13
|
+
pieces: lastState.pieces,
|
|
14
|
+
toMove: lastState.toMove,
|
|
15
|
+
enPassantSquare: lastState.enPassantSquare,
|
|
16
|
+
castlingRights: lastState.castlingRights,
|
|
17
|
+
halfmoveClock: lastState.halfmoveClock,
|
|
18
|
+
fullmoveNumber: lastState.fullmoveNumber,
|
|
19
|
+
history: board.history.slice(0, -1),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=undo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"undo.js","sourceRoot":"","sources":["../../src/board/undo.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,oCAAoC;AACpC,wCAAwC;AACxC,iBAAiB;AACjB,+CAA+C;AAI/C,yBAAyB;AACzB,MAAM,UAAU,QAAQ,CAAC,KAAY;IACnC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAE1D,OAAO;QACL,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,eAAe,EAAE,SAAS,CAAC,eAAe;QAC1C,cAAc,EAAE,SAAS,CAAC,cAAc;QACxC,aAAa,EAAE,SAAS,CAAC,aAAa;QACtC,cAAc,EAAE,SAAS,CAAC,cAAc;QACxC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;KACpC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Board } from '../types/board';
|
|
2
|
+
import { Move } from '../types/move';
|
|
3
|
+
/**
|
|
4
|
+
* Check if a move is legal (in the set of all legal moves for the position)
|
|
5
|
+
*/
|
|
6
|
+
export declare function isLegalMove(board: Board, move: Move): boolean;
|
|
7
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/board/validate.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAGrC;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAI7D"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// HyperChess Core — hyperchess-core
|
|
3
|
+
// File: packages/core/src/board/validate.ts
|
|
4
|
+
// Version: 1.0.0
|
|
5
|
+
// Copyright (c) 2026 HyperChess Developer Team
|
|
6
|
+
import { generateLegalMoves } from '../moves/index';
|
|
7
|
+
/**
|
|
8
|
+
* Check if a move is legal (in the set of all legal moves for the position)
|
|
9
|
+
*/
|
|
10
|
+
export function isLegalMove(board, move) {
|
|
11
|
+
const legalMoves = generateLegalMoves(board);
|
|
12
|
+
return legalMoves.some((m) => m.from === move.from && m.to === move.to);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/board/validate.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,oCAAoC;AACpC,4CAA4C;AAC5C,iBAAiB;AACjB,+CAA+C;AAI/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,KAAY,EAAE,IAAU;IAClD,MAAM,UAAU,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAE7C,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Game } from '../types/game';
|
|
2
|
+
/**
|
|
3
|
+
* Add a move to the game (in algebraic or HSAN notation)
|
|
4
|
+
* Returns new Game state or null if move is invalid
|
|
5
|
+
*/
|
|
6
|
+
export declare function addMoveToGame(game: Game, moveStr: string): Game | null;
|
|
7
|
+
//# sourceMappingURL=add-move.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-move.d.ts","sourceRoot":"","sources":["../../src/game/add-move.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,IAAI,EAAc,MAAM,eAAe,CAAC;AAOjD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAoDtE"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// HyperChess Core — hyperchess-core
|
|
3
|
+
// File: packages/core/src/game/add-move.ts
|
|
4
|
+
// Version: 1.0.0
|
|
5
|
+
// Copyright (c) 2026 HyperChess Developer Team
|
|
6
|
+
import { generateLegalMoves, isCheckmate, isStalemate } from '../moves/index';
|
|
7
|
+
import { applyMove } from '../board/move';
|
|
8
|
+
import { parseHsanMove } from '../io/hsan-parser';
|
|
9
|
+
import { SQUARE_TOKEN, algebraicToSquare, squareToAlgebraic } from '../utils/square-notation';
|
|
10
|
+
/**
|
|
11
|
+
* Add a move to the game (in algebraic or HSAN notation)
|
|
12
|
+
* Returns new Game state or null if move is invalid
|
|
13
|
+
*/
|
|
14
|
+
export function addMoveToGame(game, moveStr) {
|
|
15
|
+
const board = game.board;
|
|
16
|
+
// Generate all legal moves
|
|
17
|
+
const legalMoves = generateLegalMoves(board);
|
|
18
|
+
if (legalMoves.length === 0) {
|
|
19
|
+
// Game already ended
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
// Try to parse as HSAN first, then as algebraic
|
|
23
|
+
let move = null;
|
|
24
|
+
// Try HSAN parsing
|
|
25
|
+
const hsanMove = parseHsanMove(board, moveStr);
|
|
26
|
+
if (hsanMove) {
|
|
27
|
+
move = hsanMove;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// Try as algebraic (e.g., "e2e4" or "e2-e4")
|
|
31
|
+
move = parseAlgebraicMove(moveStr);
|
|
32
|
+
}
|
|
33
|
+
if (!move) {
|
|
34
|
+
return null; // Invalid move format
|
|
35
|
+
}
|
|
36
|
+
// Check if move is in legal moves
|
|
37
|
+
const isLegal = legalMoves.some((m) => m.from === move.from && m.to === move.to);
|
|
38
|
+
if (!isLegal) {
|
|
39
|
+
return null; // Illegal move
|
|
40
|
+
}
|
|
41
|
+
// Apply the move
|
|
42
|
+
const newBoard = applyMove(board, move);
|
|
43
|
+
// Determine new game status
|
|
44
|
+
let status = 'active';
|
|
45
|
+
if (isCheckmate(newBoard)) {
|
|
46
|
+
status = 'checkmate';
|
|
47
|
+
}
|
|
48
|
+
else if (isStalemate(newBoard)) {
|
|
49
|
+
status = 'stalemate';
|
|
50
|
+
}
|
|
51
|
+
// Add move to history (in algebraic format)
|
|
52
|
+
const algebraicMove = `${squareToAlgebraic(move.from)}${squareToAlgebraic(move.to)}${move.promotion ? '=' + move.promotion : ''}`;
|
|
53
|
+
return {
|
|
54
|
+
board: newBoard,
|
|
55
|
+
moves: [...game.moves, algebraicMove],
|
|
56
|
+
metadata: game.metadata,
|
|
57
|
+
status,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parse algebraic move notation (e.g., "e2e4", "e2-e4", "e3e11").
|
|
62
|
+
*
|
|
63
|
+
* Ranks 10-12 are two digits on this 12x12 board, so the from/to squares
|
|
64
|
+
* can't be sliced at fixed offsets — each is matched as a variable-length
|
|
65
|
+
* file+rank token.
|
|
66
|
+
*/
|
|
67
|
+
function parseAlgebraicMove(moveStr) {
|
|
68
|
+
const normalized = moveStr.replace('-', '');
|
|
69
|
+
const match = normalized.match(new RegExp(`^(${SQUARE_TOKEN})(${SQUARE_TOKEN})(?:=([QRBNEH]))?$`));
|
|
70
|
+
if (!match) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const from = algebraicToSquare(match[1]);
|
|
74
|
+
const to = algebraicToSquare(match[2]);
|
|
75
|
+
const promotion = match[3];
|
|
76
|
+
return { from, to, promotion };
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=add-move.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-move.js","sourceRoot":"","sources":["../../src/game/add-move.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Game, GameStatus } from '../types/game';
|
|
2
|
+
import { HfenString } from '../types/board';
|
|
3
|
+
/**
|
|
4
|
+
* Create a new game from optional starting HFEN
|
|
5
|
+
*/
|
|
6
|
+
export declare function createGame(hfen?: HfenString): Game;
|
|
7
|
+
/**
|
|
8
|
+
* Add a move to the game (in algebraic or HSAN notation)
|
|
9
|
+
* Returns new Game state or null if move is invalid
|
|
10
|
+
*/
|
|
11
|
+
export declare function addMove(game: Game, moveStr: string): Game | null;
|
|
12
|
+
/**
|
|
13
|
+
* Remove the last move from the game
|
|
14
|
+
* Reconstructs the game state from move list
|
|
15
|
+
*/
|
|
16
|
+
export declare function removeLastMove(game: Game): Game;
|
|
17
|
+
/**
|
|
18
|
+
* Get current game status
|
|
19
|
+
*/
|
|
20
|
+
export declare function getGameStatus(game: Game): GameStatus;
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|