@echecs/position 1.0.3 → 2.0.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.1] - 2026-04-05
4
+
5
+ ### Fixed
6
+
7
+ - Docs CI workflow using renamed script.
8
+ - Lockfile out of sync with dependency versions.
9
+
10
+ ## [2.0.0] - 2026-04-05
11
+
12
+ ### Added
13
+
14
+ - `Position.derive()` method — returns a new position with changes applied,
15
+ without mutating the original. Accepts piece changes as `[square, piece]`
16
+ tuples and optional position option overrides.
17
+ - `DeriveOptions` type exported for use with `derive()`.
18
+ - `EnPassantSquare` type — restricts en passant target squares to rank 3 and
19
+ rank 6 only.
20
+ - `SideCastlingRights` type — `{ king: boolean; queen: boolean }`.
21
+ - TypeDoc plugin to collapse expanded `Square` and `EnPassantSquare` unions in
22
+ generated docs.
23
+ - JSDoc comments on all public API members.
24
+
25
+ ### Changed
26
+
27
+ - `Color` values changed from `'b'`/`'w'` to `'black'`/`'white'`.
28
+ - `PieceType` values changed from single letters to full words: `'bishop'`,
29
+ `'king'`, `'knight'`, `'pawn'`, `'queen'`, `'rook'`.
30
+ - `CastlingRights` restructured from flat `{ bK, bQ, wK, wQ }` to nested
31
+ `{ black: { king, queen }, white: { king, queen } }`.
32
+ - `PositionOptions.enPassantSquare` now uses `EnPassantSquare` (rank 3/6 only)
33
+ instead of `Square`.
34
+ - TypeDoc docs script renamed to `docs:build` to avoid conflict with pnpm
35
+ built-in `docs` command.
36
+
37
+ ### Removed
38
+
39
+ - `Move` and `PromotionPieceType` types — consumers should define their own.
40
+ - `findPiece()` method — redundant with `pieces()` filtering.
41
+ - `COLORS`, `FILES`, `RANKS`, `PIECE_TYPES`, `SQUARES`, `EMPTY_BOARD` constants
42
+ from public API (still used internally).
43
+ - `squareColor`, `squareFile`, `squareRank` functions from public API.
44
+ - `./internal` export condition — `@echecs/game` should migrate to
45
+ `Position.derive()`.
46
+
3
47
  ## [1.0.3] - 2026-04-04
4
48
 
5
49
  ### Fixed
package/README.md CHANGED
@@ -9,7 +9,8 @@
9
9
  **Position** is a TypeScript library representing a complete chess position —
10
10
  the board, turn, castling rights, en passant square, halfmove clock, and
11
11
  fullmove number — as an immutable value object with a clean query API. It is the
12
- foundational package of the ECHECS monorepo. Zero runtime dependencies.
12
+ foundational package in the `@echecs` family of chess libraries. Zero runtime
13
+ dependencies.
13
14
 
14
15
  ## Installation
15
16
 
@@ -25,20 +26,27 @@ import { Position, STARTING_POSITION } from '@echecs/position';
25
26
  // Starting position
26
27
  const pos = new Position();
27
28
 
28
- console.log(pos.turn); // 'w'
29
+ console.log(pos.turn); // 'white'
29
30
  console.log(pos.fullmoveNumber); // 1
30
31
  console.log(pos.isCheck); // false
31
32
 
32
33
  // Query the board
33
- const piece = pos.piece('e1'); // { color: 'w', type: 'k' }
34
- const whites = pos.pieces('w'); // Map<Square, Piece> of all white pieces
35
-
36
- // Find all squares a piece occupies
37
- const whiteKing = pos.findPiece({ color: 'w', type: 'k' }); // ['e1']
34
+ const piece = pos.piece('e1'); // { color: 'white', type: 'king' }
35
+ const whites = pos.pieces('white'); // Map<Square, Piece> of all white pieces
38
36
 
39
37
  // Attack queries
40
- const attackers = pos.attackers('e5', 'b'); // squares of black pieces attacking e5
41
- const attacked = pos.isAttacked('f7', 'w'); // true if white attacks f7
38
+ const attackers = pos.attackers('e5', 'black'); // squares of black pieces attacking e5
39
+ const attacked = pos.isAttacked('f7', 'white'); // true if white attacks f7
40
+
41
+ // Derive a new position
42
+ const next = pos.derive({
43
+ changes: [
44
+ ['e2', undefined],
45
+ ['e4', { color: 'white', type: 'pawn' }],
46
+ ],
47
+ turn: 'black',
48
+ enPassantSquare: 'e3',
49
+ });
42
50
  ```
43
51
 
44
52
  ## API
@@ -50,13 +58,7 @@ Full API reference is available at https://mormubis.github.io/position/
50
58
  ```ts
51
59
  new Position()
52
60
  new Position(board: Map<Square, Piece>)
53
- new Position(board: Map<Square, Piece>, options?: {
54
- castlingRights?: CastlingRights
55
- enPassantSquare?: Square
56
- fullmoveNumber?: number
57
- halfmoveClock?: number
58
- turn?: Color
59
- })
61
+ new Position(board: Map<Square, Piece>, options?: PositionOptions)
60
62
  ```
61
63
 
62
64
  The no-argument form creates the standard chess starting position. Pass a custom
@@ -64,17 +66,17 @@ The no-argument form creates the standard chess starting position. Pass a custom
64
66
 
65
67
  ### Getters
66
68
 
67
- | Getter | Type | Description |
68
- | ------------------------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
69
- | `castlingRights` | `CastlingRights` | Which castling moves remain available |
70
- | `enPassantSquare` | `Square \| undefined` | En passant target square, if any |
71
- | `fullmoveNumber` | `number` | Fullmove counter (increments after Black's move) |
72
- | `halfmoveClock` | `number` | Halfmove clock for the fifty-move rule |
73
- | `hash` | `string` | Zobrist hash string for position identity |
74
- | `isCheck` | `boolean` | Whether the side to move is in check |
75
- | `isInsufficientMaterial` | `boolean` | Whether the position is a FIDE draw by insufficient material (K vs K, K+B vs K, K+N vs K, or K+B vs K+B with same-color bishops) |
76
- | `isValid` | `boolean` | Whether the position is legally reachable |
77
- | `turn` | `Color` | Side to move (`'w'` or `'b'`) |
69
+ | Getter | Type | Description |
70
+ | ------------------------ | ------------------------------ | --------------------------------------------------------------- |
71
+ | `castlingRights` | `CastlingRights` | Which castling moves remain available |
72
+ | `enPassantSquare` | `EnPassantSquare \| undefined` | En passant target square (rank 3 or 6), if any |
73
+ | `fullmoveNumber` | `number` | Game turn counter increments after each black move |
74
+ | `halfmoveClock` | `number` | Half-moves since last pawn advance or capture (fifty-move rule) |
75
+ | `hash` | `string` | Zobrist hash string for position identity |
76
+ | `isCheck` | `boolean` | Whether the side to move is in check |
77
+ | `isInsufficientMaterial` | `boolean` | Whether the position is a FIDE draw by insufficient material |
78
+ | `isValid` | `boolean` | Whether the position is legally reachable |
79
+ | `turn` | `Color` | Side to move (`'white'` or `'black'`) |
78
80
 
79
81
  ### Methods
80
82
 
@@ -83,15 +85,27 @@ The no-argument form creates the standard chess starting position. Pass a custom
83
85
  Returns all squares occupied by pieces of `color` that attack `square`.
84
86
 
85
87
  ```typescript
86
- pos.attackers('e5', 'b'); // e.g. ['d7', 'f6']
88
+ pos.attackers('e5', 'black'); // e.g. ['d7', 'f6']
87
89
  ```
88
90
 
89
- #### `findPiece(piece): Square[]`
91
+ #### `derive(changes?): Position`
90
92
 
91
- Returns all squares occupied by the given piece.
93
+ Returns a new `Position` with the given changes applied. The original is not
94
+ modified. Fields not provided are carried over from the source.
92
95
 
93
96
  ```typescript
94
- pos.findPiece({ color: 'w', type: 'q' }); // ['d1']
97
+ // move e2 pawn to e4
98
+ const next = pos.derive({
99
+ changes: [
100
+ ['e2', undefined],
101
+ ['e4', { color: 'white', type: 'pawn' }],
102
+ ],
103
+ turn: 'black',
104
+ enPassantSquare: 'e3',
105
+ });
106
+
107
+ // clone
108
+ const clone = pos.derive();
95
109
  ```
96
110
 
97
111
  #### `isAttacked(square, color): boolean`
@@ -99,7 +113,7 @@ pos.findPiece({ color: 'w', type: 'q' }); // ['d1']
99
113
  Returns `true` if any piece of `color` attacks `square`.
100
114
 
101
115
  ```typescript
102
- pos.isAttacked('f3', 'w'); // true if white attacks f3
116
+ pos.isAttacked('f3', 'white'); // true if white attacks f3
103
117
  ```
104
118
 
105
119
  #### `piece(square): Piece | undefined`
@@ -107,7 +121,7 @@ pos.isAttacked('f3', 'w'); // true if white attacks f3
107
121
  Returns the piece on `square`, or `undefined` if the square is empty.
108
122
 
109
123
  ```typescript
110
- pos.piece('e1'); // { color: 'w', type: 'k' }
124
+ pos.piece('e1'); // { color: 'white', type: 'king' }
111
125
  pos.piece('e5'); // undefined (empty in starting position)
112
126
  ```
113
127
 
@@ -117,42 +131,35 @@ Returns a map of all pieces, optionally filtered by `color`.
117
131
 
118
132
  ```typescript
119
133
  pos.pieces(); // all 32 pieces in starting position
120
- pos.pieces('w'); // 16 white pieces
134
+ pos.pieces('white'); // 16 white pieces
121
135
  ```
122
136
 
123
137
  ### Constants
124
138
 
125
139
  ```typescript
126
- import {
127
- COLORS, // ['b', 'w']
128
- FILES, // ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
129
- RANKS, // ['1', '2', '3', '4', '5', '6', '7', '8']
130
- PIECE_TYPES, // ['b', 'k', 'n', 'p', 'q', 'r']
131
- SQUARES, // all 64 squares, a8–h1 (rank 8 to rank 1)
132
- EMPTY_BOARD, // empty Map<Square, Piece>
133
- STARTING_POSITION, // Position instance for the standard starting position
134
- } from '@echecs/position';
140
+ import { STARTING_POSITION } from '@echecs/position';
135
141
  ```
136
142
 
143
+ `STARTING_POSITION` is a `Position` instance for the standard chess starting
144
+ position. Equivalent to `new Position()`.
145
+
137
146
  ### Types
138
147
 
139
148
  All types are exported for use in consuming code and companion packages.
140
149
 
141
150
  ```typescript
142
151
  import type {
143
- CastlingRights, // { bK: boolean; bQ: boolean; wK: boolean; wQ: boolean }
144
- Color, // 'w' | 'b'
152
+ CastlingRights, // { black: SideCastlingRights; white: SideCastlingRights }
153
+ Color, // 'black' | 'white'
154
+ DeriveOptions, // options accepted by Position.derive()
155
+ EnPassantSquare, // en passant target square (rank 3 or 6 only)
145
156
  File, // 'a' | 'b' | ... | 'h'
146
- Move, // { from: Square; to: Square; promotion: PromotionPieceType | undefined }
147
157
  Piece, // { color: Color; type: PieceType }
148
- PieceType, // 'b' | 'k' | 'n' | 'p' | 'q' | 'r'
158
+ PieceType, // 'bishop' | 'king' | 'knight' | 'pawn' | 'queen' | 'rook'
149
159
  PositionOptions, // options accepted by the Position constructor
150
- PromotionPieceType, // 'b' | 'n' | 'q' | 'r'
151
160
  Rank, // '1' | '2' | ... | '8'
161
+ SideCastlingRights, // { king: boolean; queen: boolean }
152
162
  Square, // 'a1' | 'a2' | ... | 'h8'
153
- SquareColor, // 'light' | 'dark'
163
+ SquareColor, // 'dark' | 'light'
154
164
  } from '@echecs/position';
155
165
  ```
156
-
157
- `Move` and `PromotionPieceType` are exported for use by companion packages
158
- (`@echecs/san`, `@echecs/game`) that build on this foundational type.
package/dist/index.d.ts CHANGED
@@ -1,12 +1,107 @@
1
- import { a as Piece, c as PromotionPieceType, d as SquareColor, i as Move, l as Rank, n as Color, o as PieceType, r as File, s as PositionOptions, t as CastlingRights, u as Square } from "./types-BN6JJgNK.js";
2
-
1
+ //#region src/types.d.ts
2
+ /** Side to move — `'white'` or `'black'`. */
3
+ type Color = 'black' | 'white';
4
+ /** Board file (column), `'a'` through `'h'`. */
5
+ type File = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h';
6
+ /** Chess piece type: bishop, king, knight, pawn, queen, or rook. */
7
+ type PieceType = 'bishop' | 'king' | 'knight' | 'pawn' | 'queen' | 'rook';
8
+ /** Board rank (row), `'1'` through `'8'`. */
9
+ type Rank = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8';
10
+ /** En passant target square — always on rank 3 or rank 6. */
11
+ type EnPassantSquare = `${File}${'3' | '6'}`;
12
+ /**
13
+ * A board square, e.g. `'e4'`. Combination of {@link File} and {@link Rank}.
14
+ *
15
+ * @preventExpand
16
+ */
17
+ type Square = `${File}${Rank}`;
18
+ /** Square color on the board — `'dark'` or `'light'`. */
19
+ type SquareColor = 'dark' | 'light';
20
+ /** Castling availability for one side. */
21
+ interface SideCastlingRights {
22
+ /** Can castle kingside. */
23
+ king: boolean;
24
+ /** Can castle queenside. */
25
+ queen: boolean;
26
+ }
27
+ /** Which castling moves remain available for each side. */
28
+ interface CastlingRights {
29
+ /** Black's castling rights. */
30
+ black: SideCastlingRights;
31
+ /** White's castling rights. */
32
+ white: SideCastlingRights;
33
+ }
34
+ /**
35
+ * Options accepted by {@link Position.derive}. Extends {@link PositionOptions}
36
+ * with a `changes` field for applying piece changes.
37
+ */
38
+ interface DeriveOptions extends PositionOptions {
39
+ /**
40
+ * Piece changes as `[square, piece]` tuples. Set piece to `undefined` to
41
+ * clear a square. Only changed squares need to be listed.
42
+ */
43
+ changes?: [Square, Piece | undefined][];
44
+ }
45
+ /**
46
+ * Options for constructing a {@link Position}. All fields are optional —
47
+ * omitted fields use defaults (standard starting position values).
48
+ */
49
+ interface PositionOptions {
50
+ /** Castling availability. Defaults to all four castling moves available. */
51
+ castlingRights?: CastlingRights;
52
+ /** En passant target square, if any. */
53
+ enPassantSquare?: EnPassantSquare;
54
+ /**
55
+ * Game turn counter — starts at `1` and increments after each black move.
56
+ * After `1. e4 e5 2. Nf3` the fullmove number is `2`. Defaults to `1`.
57
+ */
58
+ fullmoveNumber?: number;
59
+ /**
60
+ * Number of half-moves since the last pawn advance or capture. Resets to
61
+ * `0` on every pawn move or capture. When it reaches `100` (50 full moves
62
+ * per side) either player may claim a draw. Defaults to `0`.
63
+ */
64
+ halfmoveClock?: number;
65
+ /** Side to move. Defaults to `'white'`. */
66
+ turn?: Color;
67
+ }
68
+ /** A chess piece — color and type. */
69
+ interface Piece {
70
+ /** The piece's color. */
71
+ color: Color;
72
+ /** The piece's type. */
73
+ type: PieceType;
74
+ }
75
+ //#endregion
3
76
  //#region src/position.d.ts
77
+ /**
78
+ * An immutable chess position — board state, turn, castling rights,
79
+ * en passant square, and move counters.
80
+ *
81
+ * Query the position with getters and methods. Produce new positions
82
+ * with {@link Position.derive | derive}.
83
+ */
4
84
  declare class Position {
5
85
  #private;
86
+ /**
87
+ * Creates a new position.
88
+ *
89
+ * @param board - Piece placement. Defaults to the standard starting position.
90
+ * @param options - Turn, castling rights, en passant, and move counters.
91
+ */
6
92
  constructor(board?: Map<Square, Piece>, options?: PositionOptions);
93
+ /** Which castling moves remain available. */
7
94
  get castlingRights(): CastlingRights;
8
- get enPassantSquare(): Square | undefined;
95
+ /** En passant target square, or `undefined` if none. */
96
+ get enPassantSquare(): EnPassantSquare | undefined;
97
+ /**
98
+ * Game turn counter — starts at `1` and increments after each black move.
99
+ */
9
100
  get fullmoveNumber(): number;
101
+ /**
102
+ * Number of half-moves since the last pawn advance or capture. Resets on
103
+ * every pawn move or capture. A draw can be claimed when it reaches `100`.
104
+ */
10
105
  get halfmoveClock(): number;
11
106
  /**
12
107
  * A Zobrist hash of the position as a 16-character hex string.
@@ -17,36 +112,59 @@ declare class Position {
17
112
  * versions. Do not persist hashes across version upgrades.
18
113
  */
19
114
  get hash(): string;
115
+ /**
116
+ * Whether the position is a draw by insufficient material (FIDE rules):
117
+ * K vs K, K+B vs K, K+N vs K, or K+B vs K+B with same-color bishops.
118
+ */
20
119
  get isInsufficientMaterial(): boolean;
120
+ /**
121
+ * Whether the position is legally reachable: exactly one king per side,
122
+ * no pawns on ranks 1 or 8, and the side not to move is not in check.
123
+ */
21
124
  get isValid(): boolean;
125
+ /** Whether the side to move is in check. */
22
126
  get isCheck(): boolean;
127
+ /** Side to move — `'white'` or `'black'`. */
23
128
  get turn(): Color;
129
+ /**
130
+ * Returns a new position with the given changes applied. Fields not
131
+ * provided are carried over from the source. Calling with no argument
132
+ * returns a clone.
133
+ *
134
+ * @param changes - Board deltas and option overrides to apply.
135
+ */
136
+ derive(changes?: DeriveOptions): Position;
137
+ /**
138
+ * Returns all squares occupied by pieces of the given color that
139
+ * attack the target square.
140
+ *
141
+ * @param square - The target square.
142
+ * @param by - The attacking color.
143
+ */
24
144
  attackers(square: Square, by: Color): Square[];
25
- findPiece(piece: Piece): Square[];
145
+ /**
146
+ * Returns `true` if any piece of the given color attacks the target square.
147
+ *
148
+ * @param square - The target square.
149
+ * @param by - The attacking color.
150
+ */
26
151
  isAttacked(square: Square, by: Color): boolean;
152
+ /**
153
+ * Returns the piece on the given square, or `undefined` if empty.
154
+ *
155
+ * @param square - The square to query.
156
+ */
27
157
  piece(square: Square): Piece | undefined;
158
+ /**
159
+ * Returns a map of all pieces on the board, optionally filtered by color.
160
+ *
161
+ * @param color - If provided, only pieces of this color are returned.
162
+ */
28
163
  pieces(color?: Color): Map<Square, Piece>;
29
164
  }
30
165
  //#endregion
31
- //#region src/primitives.d.ts
32
- declare const COLORS: Color[];
33
- declare const FILES: File[];
34
- declare const RANKS: Rank[];
35
- declare const PIECE_TYPES: PieceType[];
36
- declare const SQUARES: Square[];
37
- //#endregion
38
166
  //#region src/constants.d.ts
39
- declare const EMPTY_BOARD: Map<"a1" | "a2" | "a3" | "a4" | "a5" | "a6" | "a7" | "a8" | "b1" | "b2" | "b3" | "b4" | "b5" | "b6" | "b7" | "b8" | "c1" | "c2" | "c3" | "c4" | "c5" | "c6" | "c7" | "c8" | "d1" | "d2" | "d3" | "d4" | "d5" | "d6" | "d7" | "d8" | "e1" | "e2" | "e3" | "e4" | "e5" | "e6" | "e7" | "e8" | "f1" | "f2" | "f3" | "f4" | "f5" | "f6" | "f7" | "f8" | "g1" | "g2" | "g3" | "g4" | "g5" | "g6" | "g7" | "g8" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "h7" | "h8", Piece>;
167
+ /** The standard chess starting position. */
40
168
  declare const STARTING_POSITION: Position;
41
169
  //#endregion
42
- //#region src/squares.d.ts
43
- declare function squareFile(square: Square): File;
44
- declare function squareRank(square: Square): Rank;
45
- /**
46
- * Returns 'dark' or 'light' for the given square.
47
- * a1 is dark: file index 0 + rank 1 = 1 (odd) → 'dark'.
48
- * b1 is light: file index 1 + rank 1 = 2 (even) → 'light'.
49
- */
50
- declare function squareColor(square: Square): SquareColor;
51
- //#endregion
52
- export { COLORS, type CastlingRights, type Color, EMPTY_BOARD, FILES, type File, type Move, PIECE_TYPES, type Piece, type PieceType, Position, type PositionOptions, type PromotionPieceType, RANKS, type Rank, SQUARES, STARTING_POSITION, type Square, type SquareColor, squareColor, squareFile, squareRank };
170
+ export { type CastlingRights, type Color, type DeriveOptions, type EnPassantSquare, type File, type Piece, type PieceType, Position, type PositionOptions, type Rank, STARTING_POSITION, type SideCastlingRights, type Square, type SquareColor };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{_ as e,a as t,c as n,f as r,h as i,i as a,l as o,n as s,o as c,p as l,r as u,s as d,t as f,u as p}from"./internal-BOZCKSNm.js";function m(e){return e[0]}function h(e){return e[1]}function g(e){return((e.codePointAt(0)??0)-(`a`.codePointAt(0)??0)+Number.parseInt(e[1]??`1`,10))%2==1?`dark`:`light`}const _=[`r`,`n`,`b`,`q`,`k`,`b`,`n`,`r`],v=[`a`,`b`,`c`,`d`,`e`,`f`,`g`,`h`],y=new Map;for(let[e,t]of _.entries()){let n=v[e];n!==void 0&&(y.set(`${n}1`,{color:`w`,type:t}),y.set(`${n}2`,{color:`w`,type:`p`}),y.set(`${n}7`,{color:`b`,type:`p`}),y.set(`${n}8`,{color:`b`,type:t}))}const b=y,x={castlingRights:{bK:!0,bQ:!0,wK:!0,wQ:!0},enPassantSquare:void 0,fullmoveNumber:1,halfmoveClock:0,turn:`w`};var S=class{#e;#t;#n;#r;#i;#a;#o;constructor(e,t){this.#e=new Map(e??b);let n={...x,...t};this.#t=n.castlingRights,this.#n=n.enPassantSquare,this.#r=n.fullmoveNumber,this.#i=n.halfmoveClock,this.#o=n.turn}get castlingRights(){return this.#t}get enPassantSquare(){return this.#n}get fullmoveNumber(){return this.#r}get halfmoveClock(){return this.#i}get hash(){if(this.#a!==void 0)return this.#a;let e=0n;for(let[t,n]of this.#e)e^=u[t]?.[n.type]?.[n.color]??0n;e^=a[this.#o];for(let[t,n]of Object.entries(this.#t))n&&(e^=f[t]??0n);if(this.#n!==void 0){let t=this.#n[0];e^=s[t]}return this.#a=e.toString(16).padStart(16,`0`),this.#a}get isInsufficientMaterial(){let e=[];for(let[t,n]of this.#e)n.type!==`k`&&e.push([t,n]);if(e.length===0)return!0;if(e.length===1){let t=e[0]?.[1];return t?.type===`b`||t?.type===`n`}if(e.every(([,e])=>e.type===`b`)){let t=e[0];if(t===void 0)return!0;let n=g(t[0]);return e.every(([e])=>g(e)===n)}return!1}get isValid(){let e=0,t=0;for(let[n,r]of this.#e)if(r.type===`k`&&(r.color===`b`?e++:t++),r.type===`p`&&(n[1]===`1`||n[1]===`8`))return!1;if(e!==1||t!==1)return!1;let n=this.#o===`w`?`b`:`w`;for(let[e,t]of this.#e)if(t.type===`k`&&t.color===n&&this.isAttacked(e,this.#o))return!1;return!0}get isCheck(){for(let[e,t]of this.#e)if(t.type===`k`&&t.color===this.#o){let t=this.#o===`w`?`b`:`w`;return this.isAttacked(e,t)}return!1}get turn(){return this.#o}#s(e,t,n,i,a){let o=t-n,s=o+119;if(s<0||s>=240||((p[s]??0)&(r[i.type]??0))===0||i.type===`p`&&(a===`w`&&o>0||a===`b`&&o<0))return!1;let c=l[s]??0;if(c===0)return!0;let u=n+c;for(;u!==t;){if(u&136||e[u]!==void 0)return!1;u+=c}return!0}attackers(t,n){let r=i(this.#e),a=e(t),o=[];for(let[t,i]of this.#e){if(i.color!==n)continue;let s=e(t);this.#s(r,a,s,i,n)&&o.push(t)}return o}findPiece(e){let t=[];for(let[n,r]of this.#e)r.color===e.color&&r.type===e.type&&t.push(n);return t}isAttacked(t,n){let r=i(this.#e),a=e(t);for(let[t,i]of this.#e){if(i.color!==n)continue;let o=e(t);if(this.#s(r,a,o,i,n))return!0}return!1}piece(e){return this.#e.get(e)}pieces(e){if(e===void 0)return new Map(this.#e);let t=new Map;for(let[n,r]of this.#e)r.color===e&&t.set(n,r);return t}};const C=new Map,w=new S;export{t as COLORS,C as EMPTY_BOARD,c as FILES,d as PIECE_TYPES,S as Position,n as RANKS,o as SQUARES,w as STARTING_POSITION,g as squareColor,m as squareFile,h as squareRank};
1
+ function e(e){let t=(e.codePointAt(0)??0)-(`a`.codePointAt(0)??0);return(8-Number.parseInt(e[1]??`1`,10))*16+t}function t(t){let n=Array.from({length:128});for(let[r,i]of t)n[e(r)]=i;return n}const n=[-33,-31,-18,-14,14,18,31,33],r=[-17,-15,15,17],i=[-16,-1,1,16],a=[-17,-16,-15,-1,1,15,16,17],o={bishop:4,king:16,knight:2,pawn:1,queen:12,rook:8},s=Array.from({length:240}).fill(0),c=Array.from({length:240}).fill(0);(function(){for(let e of n)s[e+119]=(s[e+119]??0)|2;for(let e of a)s[e+119]=(s[e+119]??0)|16;for(let e of[15,17])s[e+119]=(s[e+119]??0)|1,s[-e+119]=(s[-e+119]??0)|1;for(let e=0;e<=119;e++)if(!(e&136)){for(let t of i){let n=e+t;for(;!(n&136);){let r=n-e;s[r+119]=(s[r+119]??0)|8,c[r+119]=t,n+=t}}for(let t of r){let n=e+t;for(;!(n&136);){let r=n-e;s[r+119]=(s[r+119]??0)|4,c[r+119]=t,n+=t}}}})();const l=[`black`,`white`],u=[`a`,`b`,`c`,`d`,`e`,`f`,`g`,`h`],d=[`1`,`2`,`3`,`4`,`5`,`6`,`7`,`8`],f=[`bishop`,`king`,`knight`,`pawn`,`queen`,`rook`],p=u.flatMap(e=>d.toReversed().map(t=>`${e}${t}`));function m(e){let t=BigInt(e);return()=>(t=t*6364136223846793005n+1442695040888963407n&18446744073709551615n,t)}const h=m(3735928559),g=Object.fromEntries(p.map(e=>[e,Object.fromEntries(f.map(e=>[e,Object.fromEntries(l.map(e=>[e,h()]))]))])),_={black:h(),white:h()},v={"black.king":h(),"black.queen":h(),"white.king":h(),"white.queen":h()},y=Object.fromEntries(u.map(e=>[e,h()]));function b(e){return((e.codePointAt(0)??0)-(`a`.codePointAt(0)??0)+Number.parseInt(e[1]??`1`,10))%2==1?`dark`:`light`}const x=[`rook`,`knight`,`bishop`,`queen`,`king`,`bishop`,`knight`,`rook`],S=[`a`,`b`,`c`,`d`,`e`,`f`,`g`,`h`],C=new Map;for(let[e,t]of x.entries()){let n=S[e];n!==void 0&&(C.set(`${n}1`,{color:`white`,type:t}),C.set(`${n}2`,{color:`white`,type:`pawn`}),C.set(`${n}7`,{color:`black`,type:`pawn`}),C.set(`${n}8`,{color:`black`,type:t}))}const w=C,T={castlingRights:{black:{king:!0,queen:!0},white:{king:!0,queen:!0}},enPassantSquare:void 0,fullmoveNumber:1,halfmoveClock:0,turn:`white`};var E=class n{#e;#t;#n;#r;#i;#a;#o;constructor(e,t){this.#e=new Map(e??w);let n={...T,...t};this.#t=n.castlingRights,this.#n=n.enPassantSquare,this.#r=n.fullmoveNumber,this.#i=n.halfmoveClock,this.#o=n.turn}get castlingRights(){return this.#t}get enPassantSquare(){return this.#n}get fullmoveNumber(){return this.#r}get halfmoveClock(){return this.#i}get hash(){if(this.#a!==void 0)return this.#a;let e=0n;for(let[t,n]of this.#e)e^=g[t]?.[n.type]?.[n.color]??0n;e^=_[this.#o];for(let[t,n]of Object.entries(this.#t))for(let[r,i]of Object.entries(n))i&&(e^=v[`${t}.${r}`]??0n);if(this.#n!==void 0){let t=this.#n[0];e^=y[t]}return this.#a=e.toString(16).padStart(16,`0`),this.#a}get isInsufficientMaterial(){let e=[];for(let[t,n]of this.#e)n.type!==`king`&&e.push([t,n]);if(e.length===0)return!0;if(e.length===1){let t=e[0]?.[1];return t?.type===`bishop`||t?.type===`knight`}if(e.every(([,e])=>e.type===`bishop`)){let t=e[0];if(t===void 0)return!0;let n=b(t[0]);return e.every(([e])=>b(e)===n)}return!1}get isValid(){let e=0,t=0;for(let[n,r]of this.#e)if(r.type===`king`&&(r.color===`black`?e++:t++),r.type===`pawn`&&(n[1]===`1`||n[1]===`8`))return!1;if(e!==1||t!==1)return!1;let n=this.#o===`white`?`black`:`white`;for(let[e,t]of this.#e)if(t.type===`king`&&t.color===n&&this.isAttacked(e,this.#o))return!1;return!0}get isCheck(){for(let[e,t]of this.#e)if(t.type===`king`&&t.color===this.#o){let t=this.#o===`white`?`black`:`white`;return this.isAttacked(e,t)}return!1}get turn(){return this.#o}#s(e,t,n,r,i){let a=t-n,l=a+119;if(l<0||l>=240||((s[l]??0)&(o[r.type]??0))===0||r.type===`pawn`&&(i===`white`&&a>0||i===`black`&&a<0))return!1;let u=c[l]??0;if(u===0)return!0;let d=n+u;for(;d!==t;){if(d&136||e[d]!==void 0)return!1;d+=u}return!0}derive(e){let t=new Map(this.#e);if(e?.changes)for(let[n,r]of e.changes)r===void 0?t.delete(n):t.set(n,r);return new n(t,{castlingRights:e?.castlingRights??this.#t,enPassantSquare:`enPassantSquare`in(e??{})?e?.enPassantSquare:this.#n,fullmoveNumber:e?.fullmoveNumber??this.#r,halfmoveClock:e?.halfmoveClock??this.#i,turn:e?.turn??this.#o})}attackers(n,r){let i=t(this.#e),a=e(n),o=[];for(let[t,n]of this.#e){if(n.color!==r)continue;let s=e(t);this.#s(i,a,s,n,r)&&o.push(t)}return o}isAttacked(n,r){let i=t(this.#e),a=e(n);for(let[t,n]of this.#e){if(n.color!==r)continue;let o=e(t);if(this.#s(i,a,o,n,r))return!0}return!1}piece(e){return this.#e.get(e)}pieces(e){if(e===void 0)return new Map(this.#e);let t=new Map;for(let[n,r]of this.#e)r.color===e&&t.set(n,r);return t}};const D=new E;export{E as Position,D as STARTING_POSITION};
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["#board","#castlingRights","#enPassantSquare","#fullmoveNumber","#halfmoveClock","#turn","#hash","#isAttackedByPiece"],"sources":["../src/squares.ts","../src/starting-board.ts","../src/position.ts","../src/constants.ts"],"sourcesContent":["import type { File, Rank, Square, SquareColor } from './types.js';\n\nfunction squareFile(square: Square): File {\n return square[0] as File;\n}\n\nfunction squareRank(square: Square): Rank {\n return square[1] as Rank;\n}\n\n/**\n * Returns 'dark' or 'light' for the given square.\n * a1 is dark: file index 0 + rank 1 = 1 (odd) → 'dark'.\n * b1 is light: file index 1 + rank 1 = 2 (even) → 'light'.\n */\nfunction squareColor(square: Square): SquareColor {\n const file = (square.codePointAt(0) ?? 0) - ('a'.codePointAt(0) ?? 0);\n const rank = Number.parseInt(square[1] ?? '1', 10);\n return (file + rank) % 2 === 1 ? 'dark' : 'light';\n}\n\nexport { squareColor, squareFile, squareRank };\n","import type { Piece, Square } from './types.js';\n\nconst BACK_RANK_TYPES = ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'] as const;\nconst BACK_RANK_FILES = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] as const;\n\nconst startingBoardMap = new Map<Square, Piece>();\nfor (const [index, type] of BACK_RANK_TYPES.entries()) {\n const file = BACK_RANK_FILES[index];\n if (file === undefined) {\n continue;\n }\n startingBoardMap.set(`${file}1` as Square, { color: 'w', type });\n startingBoardMap.set(`${file}2` as Square, { color: 'w', type: 'p' });\n startingBoardMap.set(`${file}7` as Square, { color: 'b', type: 'p' });\n startingBoardMap.set(`${file}8` as Square, { color: 'b', type });\n}\n\nexport const startingBoard = startingBoardMap;\n","import {\n ATTACKS,\n CASTLING_TABLE,\n DIFF_OFFSET,\n EP_TABLE,\n OFF_BOARD,\n PIECE_MASKS,\n PIECE_TABLE,\n RAYS,\n TURN_TABLE,\n boardFromMap,\n squareToIndex,\n} from './internal/index.js';\nimport { squareColor } from './squares.js';\nimport { startingBoard } from './starting-board.js';\n\nimport type {\n CastlingRights,\n Color,\n File,\n Piece,\n PositionOptions,\n Square,\n} from './types.js';\n\nconst DEFAULT_OPTIONS: Required<Omit<PositionOptions, 'enPassantSquare'>> &\n Pick<PositionOptions, 'enPassantSquare'> = {\n castlingRights: { bK: true, bQ: true, wK: true, wQ: true },\n enPassantSquare: undefined,\n fullmoveNumber: 1,\n halfmoveClock: 0,\n turn: 'w',\n};\n\nexport class Position {\n readonly #board: Map<Square, Piece>;\n readonly #castlingRights: CastlingRights;\n readonly #enPassantSquare: Square | undefined;\n readonly #fullmoveNumber: number;\n readonly #halfmoveClock: number;\n #hash: string | undefined;\n readonly #turn: Color;\n\n constructor(board?: Map<Square, Piece>, options?: PositionOptions) {\n this.#board = new Map(board ?? startingBoard);\n const options_ = { ...DEFAULT_OPTIONS, ...options };\n this.#castlingRights = options_.castlingRights;\n this.#enPassantSquare = options_.enPassantSquare;\n this.#fullmoveNumber = options_.fullmoveNumber;\n this.#halfmoveClock = options_.halfmoveClock;\n this.#turn = options_.turn;\n }\n\n get castlingRights(): CastlingRights {\n return this.#castlingRights;\n }\n\n get enPassantSquare(): Square | undefined {\n return this.#enPassantSquare;\n }\n\n get fullmoveNumber(): number {\n return this.#fullmoveNumber;\n }\n\n get halfmoveClock(): number {\n return this.#halfmoveClock;\n }\n\n /**\n * A Zobrist hash of the position as a 16-character hex string.\n * Computed once and cached. Two positions with the same hash are\n * considered identical (with negligible collision probability).\n *\n * @remarks The hash algorithm and format are not stable across major\n * versions. Do not persist hashes across version upgrades.\n */\n get hash(): string {\n if (this.#hash !== undefined) {\n return this.#hash;\n }\n\n let h = 0n;\n\n for (const [sq, p] of this.#board) {\n h ^= PIECE_TABLE[sq]?.[p.type]?.[p.color] ?? 0n;\n }\n\n h ^= TURN_TABLE[this.#turn];\n\n for (const [right, active] of Object.entries(this.#castlingRights) as [\n string,\n boolean,\n ][]) {\n if (active) {\n h ^= CASTLING_TABLE[right] ?? 0n;\n }\n }\n\n if (this.#enPassantSquare !== undefined) {\n const file = this.#enPassantSquare[0] as File;\n h ^= EP_TABLE[file];\n }\n\n this.#hash = h.toString(16).padStart(16, '0');\n return this.#hash;\n }\n\n get isInsufficientMaterial(): boolean {\n const nonKingEntries: [Square, Piece][] = [];\n for (const [sq, p] of this.#board) {\n if (p.type !== 'k') {\n nonKingEntries.push([sq, p]);\n }\n }\n\n // K vs K\n if (nonKingEntries.length === 0) {\n return true;\n }\n\n // K vs KB or K vs KN\n if (nonKingEntries.length === 1) {\n const sole = nonKingEntries[0]?.[1];\n return sole?.type === 'b' || sole?.type === 'n';\n }\n\n // KB vs KB (any number) — all non-king pieces must be bishops on the same square color\n const allBishops = nonKingEntries.every(([, p]) => p.type === 'b');\n if (allBishops) {\n const first = nonKingEntries[0];\n if (first === undefined) {\n return true;\n }\n const firstSquareColor = squareColor(first[0]);\n return nonKingEntries.every(\n ([sq]) => squareColor(sq) === firstSquareColor,\n );\n }\n\n return false;\n }\n\n get isValid(): boolean {\n let blackKings = 0;\n let whiteKings = 0;\n\n for (const [square, p] of this.#board) {\n if (p.type === 'k') {\n if (p.color === 'b') {\n blackKings++;\n } else {\n whiteKings++;\n }\n }\n\n // No pawns on rank 1 or 8\n if (p.type === 'p' && (square[1] === '1' || square[1] === '8')) {\n return false;\n }\n }\n\n if (blackKings !== 1 || whiteKings !== 1) {\n return false;\n }\n\n // Side not to move must not be in check\n const opponent: Color = this.#turn === 'w' ? 'b' : 'w';\n for (const [square, p] of this.#board) {\n if (\n p.type === 'k' &&\n p.color === opponent &&\n this.isAttacked(square, this.#turn)\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n get isCheck(): boolean {\n for (const [square, p] of this.#board) {\n if (p.type === 'k' && p.color === this.#turn) {\n const opponent: Color = this.#turn === 'w' ? 'b' : 'w';\n return this.isAttacked(square, opponent);\n }\n }\n return false;\n }\n\n get turn(): Color {\n return this.#turn;\n }\n\n #isAttackedByPiece(\n board: (Piece | undefined)[],\n targetIndex: number,\n fromIndex: number,\n p: Piece,\n by: Color,\n ): boolean {\n const diff = targetIndex - fromIndex;\n const tableIndex = diff + DIFF_OFFSET;\n\n if (tableIndex < 0 || tableIndex >= 240) {\n return false;\n }\n\n const attackMask = ATTACKS[tableIndex] ?? 0;\n const pieceMask = PIECE_MASKS[p.type] ?? 0;\n\n if ((attackMask & pieceMask) === 0) {\n return false;\n }\n\n if (p.type === 'p') {\n if (by === 'w' && diff > 0) {\n return false;\n }\n\n if (by === 'b' && diff < 0) {\n return false;\n }\n }\n\n const ray = RAYS[tableIndex] ?? 0;\n\n if (ray === 0) {\n return true;\n }\n\n let index = fromIndex + ray;\n while (index !== targetIndex) {\n if ((index & OFF_BOARD) !== 0) {\n return false;\n }\n\n if (board[index] !== undefined) {\n return false;\n }\n\n index += ray;\n }\n\n return true;\n }\n\n attackers(square: Square, by: Color): Square[] {\n const board = boardFromMap(this.#board);\n const targetIndex = squareToIndex(square);\n const result: Square[] = [];\n\n for (const [sq, p] of this.#board) {\n if (p.color !== by) {\n continue;\n }\n\n const fromIndex = squareToIndex(sq);\n if (this.#isAttackedByPiece(board, targetIndex, fromIndex, p, by)) {\n result.push(sq);\n }\n }\n\n return result;\n }\n\n findPiece(piece: Piece): Square[] {\n const result: Square[] = [];\n for (const [sq, p] of this.#board) {\n if (p.color === piece.color && p.type === piece.type) {\n result.push(sq);\n }\n }\n return result;\n }\n\n isAttacked(square: Square, by: Color): boolean {\n const board = boardFromMap(this.#board);\n const targetIndex = squareToIndex(square);\n\n for (const [sq, p] of this.#board) {\n if (p.color !== by) {\n continue;\n }\n\n const fromIndex = squareToIndex(sq);\n if (this.#isAttackedByPiece(board, targetIndex, fromIndex, p, by)) {\n return true;\n }\n }\n\n return false;\n }\n\n piece(square: Square): Piece | undefined {\n return this.#board.get(square);\n }\n\n pieces(color?: Color): Map<Square, Piece> {\n if (color === undefined) {\n return new Map(this.#board);\n }\n const result = new Map<Square, Piece>();\n for (const [sq, p] of this.#board) {\n if (p.color === color) {\n result.set(sq, p);\n }\n }\n return result;\n }\n}\n","import { Position } from './position.js';\n\nimport type { Piece, Square } from './types.js';\n\nconst EMPTY_BOARD = new Map<Square, Piece>();\n\nconst STARTING_POSITION = new Position();\n\nexport { EMPTY_BOARD, STARTING_POSITION };\n\nexport { COLORS, FILES, PIECE_TYPES, RANKS, SQUARES } from './primitives.js';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["#board","#castlingRights","#enPassantSquare","#fullmoveNumber","#halfmoveClock","#turn","#hash","#isAttackedByPiece"],"sources":["../src/internal/board.ts","../src/internal/tables.ts","../src/primitives.ts","../src/internal/zobrist.ts","../src/squares.ts","../src/starting-board.ts","../src/position.ts","../src/constants.ts"],"sourcesContent":["import type { Piece, Square } from '../types.js';\n\nconst OFF_BOARD = 0x88;\n\nfunction squareToIndex(square: Square): number {\n const file = (square.codePointAt(0) ?? 0) - ('a'.codePointAt(0) ?? 0);\n const rank = Number.parseInt(square[1] ?? '1', 10);\n return (8 - rank) * 16 + file;\n}\n\n/**\n * Converts a Map<Square, Piece> to the internal 0x88 array representation.\n * The bridge between the public Position type and the 0x88 internal layout.\n */\nfunction boardFromMap(map: Map<Square, Piece>): (Piece | undefined)[] {\n const board: (Piece | undefined)[] = Array.from({ length: 128 });\n for (const [square, p] of map) {\n board[squareToIndex(square)] = p;\n }\n return board;\n}\n\nexport { OFF_BOARD, boardFromMap, squareToIndex };\n","import { OFF_BOARD } from './board.js';\n\nimport type { PieceType } from '../types.js';\n\n// ── Direction constants ───────────────────────────────────────────────────────\n\nconst KNIGHT_OFFSETS_0X88 = [-33, -31, -18, -14, 14, 18, 31, 33] as const;\nconst BISHOP_DIRS_0X88 = [-17, -15, 15, 17] as const;\nconst ROOK_DIRS_0X88 = [-16, -1, 1, 16] as const;\nconst KING_OFFSETS_0X88 = [-17, -16, -15, -1, 1, 15, 16, 17] as const;\n\n// ── Piece bitmasks ────────────────────────────────────────────────────────────\n\nconst PAWN_MASK = 0x01;\nconst KNIGHT_MASK = 0x02;\nconst BISHOP_MASK = 0x04;\nconst ROOK_MASK = 0x08;\nconst KING_MASK = 0x10;\n\nconst PIECE_MASKS: Record<PieceType, number> = {\n bishop: BISHOP_MASK,\n king: KING_MASK,\n knight: KNIGHT_MASK,\n pawn: PAWN_MASK,\n queen: BISHOP_MASK | ROOK_MASK,\n rook: ROOK_MASK,\n};\n\n// ── ATTACKS / RAYS lookup tables ──────────────────────────────────────────────\n//\n// ATTACKS[diff + DIFF_OFFSET] — bitmask of piece types that can attack along\n// the vector represented by `diff` (target_index - attacker_index).\n//\n// RAYS[diff + DIFF_OFFSET] — the ray step direction for sliding pieces (0 for\n// non-sliding pieces).\n//\n// DIFF_OFFSET = 119 centres the diff range [-119, +119] at index 0.\n\nconst DIFF_OFFSET = 119;\n\nconst ATTACKS: number[] = Array.from<number>({ length: 240 }).fill(0);\nconst RAYS: number[] = Array.from<number>({ length: 240 }).fill(0);\n\n(function initAttackTables() {\n // Knight\n for (const offset of KNIGHT_OFFSETS_0X88) {\n ATTACKS[offset + DIFF_OFFSET] =\n (ATTACKS[offset + DIFF_OFFSET] ?? 0) | KNIGHT_MASK;\n }\n\n // King\n for (const offset of KING_OFFSETS_0X88) {\n ATTACKS[offset + DIFF_OFFSET] =\n (ATTACKS[offset + DIFF_OFFSET] ?? 0) | KING_MASK;\n }\n\n // Pawns — white attacks at offsets -17 and -15 (toward rank 8 = lower index)\n // black attacks at +15 and +17. Both share PAWN_MASK; color checked at use time.\n for (const offset of [15, 17]) {\n ATTACKS[offset + DIFF_OFFSET] =\n (ATTACKS[offset + DIFF_OFFSET] ?? 0) | PAWN_MASK;\n ATTACKS[-offset + DIFF_OFFSET] =\n (ATTACKS[-offset + DIFF_OFFSET] ?? 0) | PAWN_MASK;\n }\n\n // Sliding pieces — walk every ray from every valid square\n for (let from = 0; from <= 119; from++) {\n if (from & OFF_BOARD) {\n continue;\n }\n\n for (const direction of ROOK_DIRS_0X88) {\n let to = from + direction;\n while (!(to & OFF_BOARD)) {\n const diff = to - from;\n ATTACKS[diff + DIFF_OFFSET] =\n (ATTACKS[diff + DIFF_OFFSET] ?? 0) | ROOK_MASK;\n RAYS[diff + DIFF_OFFSET] = direction;\n to += direction;\n }\n }\n\n for (const direction of BISHOP_DIRS_0X88) {\n let to = from + direction;\n while (!(to & OFF_BOARD)) {\n const diff = to - from;\n ATTACKS[diff + DIFF_OFFSET] =\n (ATTACKS[diff + DIFF_OFFSET] ?? 0) | BISHOP_MASK;\n RAYS[diff + DIFF_OFFSET] = direction;\n to += direction;\n }\n }\n }\n})();\n\nexport { ATTACKS, DIFF_OFFSET, PIECE_MASKS, RAYS };\n","import type { Color, File, PieceType, Rank, Square } from './types.js';\n\n/** All colors: `['black', 'white']`. */\nconst COLORS: Color[] = ['black', 'white'];\n\n/** All files: `['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']`. */\nconst FILES: File[] = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];\n\n/** All ranks: `['1', '2', '3', '4', '5', '6', '7', '8']`. */\nconst RANKS: Rank[] = ['1', '2', '3', '4', '5', '6', '7', '8'];\n\n/** All piece types: `['bishop', 'king', 'knight', 'pawn', 'queen', 'rook']`. */\nconst PIECE_TYPES: PieceType[] = [\n 'bishop',\n 'king',\n 'knight',\n 'pawn',\n 'queen',\n 'rook',\n];\n\n/** All 64 squares, ordered a8–h1 (file-major, rank 8 to rank 1). */\nconst SQUARES: Square[] = FILES.flatMap((f) =>\n RANKS.toReversed().map((r) => `${f}${r}` as Square),\n);\n\nexport { COLORS, FILES, PIECE_TYPES, RANKS, SQUARES };\n","import { COLORS, FILES, PIECE_TYPES, SQUARES } from '../primitives.js';\n\nimport type { Color, File, PieceType, Square } from '../types.js';\n\n// Seeded LCG for deterministic random numbers (no Math.random — must be stable across runs)\nfunction lcg(seed: number): () => bigint {\n let s = BigInt(seed);\n return () => {\n s =\n (s * 6_364_136_223_846_793_005n + 1_442_695_040_888_963_407n) &\n 0xff_ff_ff_ff_ff_ff_ff_ffn;\n return s;\n };\n}\n\nconst next = lcg(0xde_ad_be_ef);\n\n// Piece table: PIECE_TABLE[square][pieceType][color]\nconst PIECE_TABLE: Record<\n Square,\n Partial<Record<PieceType, Record<Color, bigint>>>\n> = Object.fromEntries(\n SQUARES.map((sq) => [\n sq,\n Object.fromEntries(\n PIECE_TYPES.map((pt) => [\n pt,\n Object.fromEntries(COLORS.map((c) => [c, next()])),\n ]),\n ),\n ]),\n) as Record<Square, Partial<Record<PieceType, Record<Color, bigint>>>>;\n\nconst TURN_TABLE: Record<Color, bigint> = {\n black: next(),\n white: next(),\n};\n\nconst CASTLING_TABLE: Record<string, bigint> = {\n 'black.king': next(),\n 'black.queen': next(),\n 'white.king': next(),\n 'white.queen': next(),\n};\n\nconst EP_TABLE: Record<File, bigint> = Object.fromEntries(\n FILES.map((f) => [f, next()]),\n) as Record<File, bigint>;\n\nexport { CASTLING_TABLE, EP_TABLE, PIECE_TABLE, TURN_TABLE };\n","import type { Square, SquareColor } from './types.js';\n\n/**\n * Returns the color of a square — `'dark'` or `'light'`.\n *\n * @param square - The square to query.\n */\nfunction squareColor(square: Square): SquareColor {\n const file = (square.codePointAt(0) ?? 0) - ('a'.codePointAt(0) ?? 0);\n const rank = Number.parseInt(square[1] ?? '1', 10);\n return (file + rank) % 2 === 1 ? 'dark' : 'light';\n}\n\nexport { squareColor };\n","import type { Piece, Square } from './types.js';\n\nconst BACK_RANK_TYPES = [\n 'rook',\n 'knight',\n 'bishop',\n 'queen',\n 'king',\n 'bishop',\n 'knight',\n 'rook',\n] as const;\nconst BACK_RANK_FILES = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] as const;\n\nconst startingBoardMap = new Map<Square, Piece>();\nfor (const [index, type] of BACK_RANK_TYPES.entries()) {\n const file = BACK_RANK_FILES[index];\n if (file === undefined) {\n continue;\n }\n startingBoardMap.set(`${file}1` as Square, { color: 'white', type });\n startingBoardMap.set(`${file}2` as Square, { color: 'white', type: 'pawn' });\n startingBoardMap.set(`${file}7` as Square, { color: 'black', type: 'pawn' });\n startingBoardMap.set(`${file}8` as Square, { color: 'black', type });\n}\n\nexport const startingBoard = startingBoardMap;\n","import {\n ATTACKS,\n CASTLING_TABLE,\n DIFF_OFFSET,\n EP_TABLE,\n OFF_BOARD,\n PIECE_MASKS,\n PIECE_TABLE,\n RAYS,\n TURN_TABLE,\n boardFromMap,\n squareToIndex,\n} from './internal/index.js';\nimport { squareColor } from './squares.js';\nimport { startingBoard } from './starting-board.js';\n\nimport type {\n CastlingRights,\n Color,\n DeriveOptions,\n EnPassantSquare,\n File,\n Piece,\n PositionOptions,\n Square,\n} from './types.js';\n\nconst DEFAULT_OPTIONS: Required<Omit<PositionOptions, 'enPassantSquare'>> &\n Pick<PositionOptions, 'enPassantSquare'> = {\n castlingRights: {\n black: { king: true, queen: true },\n white: { king: true, queen: true },\n },\n enPassantSquare: undefined,\n fullmoveNumber: 1,\n halfmoveClock: 0,\n turn: 'white',\n};\n\n/**\n * An immutable chess position — board state, turn, castling rights,\n * en passant square, and move counters.\n *\n * Query the position with getters and methods. Produce new positions\n * with {@link Position.derive | derive}.\n */\nexport class Position {\n readonly #board: Map<Square, Piece>;\n readonly #castlingRights: CastlingRights;\n readonly #enPassantSquare: EnPassantSquare | undefined;\n readonly #fullmoveNumber: number;\n readonly #halfmoveClock: number;\n #hash: string | undefined;\n readonly #turn: Color;\n\n /**\n * Creates a new position.\n *\n * @param board - Piece placement. Defaults to the standard starting position.\n * @param options - Turn, castling rights, en passant, and move counters.\n */\n constructor(board?: Map<Square, Piece>, options?: PositionOptions) {\n this.#board = new Map(board ?? startingBoard);\n const options_ = { ...DEFAULT_OPTIONS, ...options };\n this.#castlingRights = options_.castlingRights;\n this.#enPassantSquare = options_.enPassantSquare;\n this.#fullmoveNumber = options_.fullmoveNumber;\n this.#halfmoveClock = options_.halfmoveClock;\n this.#turn = options_.turn;\n }\n\n /** Which castling moves remain available. */\n get castlingRights(): CastlingRights {\n return this.#castlingRights;\n }\n\n /** En passant target square, or `undefined` if none. */\n get enPassantSquare(): EnPassantSquare | undefined {\n return this.#enPassantSquare;\n }\n\n /**\n * Game turn counter — starts at `1` and increments after each black move.\n */\n get fullmoveNumber(): number {\n return this.#fullmoveNumber;\n }\n\n /**\n * Number of half-moves since the last pawn advance or capture. Resets on\n * every pawn move or capture. A draw can be claimed when it reaches `100`.\n */\n get halfmoveClock(): number {\n return this.#halfmoveClock;\n }\n\n /**\n * A Zobrist hash of the position as a 16-character hex string.\n * Computed once and cached. Two positions with the same hash are\n * considered identical (with negligible collision probability).\n *\n * @remarks The hash algorithm and format are not stable across major\n * versions. Do not persist hashes across version upgrades.\n */\n get hash(): string {\n if (this.#hash !== undefined) {\n return this.#hash;\n }\n\n let h = 0n;\n\n for (const [sq, p] of this.#board) {\n h ^= PIECE_TABLE[sq]?.[p.type]?.[p.color] ?? 0n;\n }\n\n h ^= TURN_TABLE[this.#turn];\n\n for (const [color, sides] of Object.entries(this.#castlingRights) as [\n string,\n { king: boolean; queen: boolean },\n ][]) {\n for (const [side, active] of Object.entries(sides) as [\n string,\n boolean,\n ][]) {\n if (active) {\n h ^= CASTLING_TABLE[`${color}.${side}`] ?? 0n;\n }\n }\n }\n\n if (this.#enPassantSquare !== undefined) {\n const file = this.#enPassantSquare[0] as File;\n h ^= EP_TABLE[file];\n }\n\n this.#hash = h.toString(16).padStart(16, '0');\n return this.#hash;\n }\n\n /**\n * Whether the position is a draw by insufficient material (FIDE rules):\n * K vs K, K+B vs K, K+N vs K, or K+B vs K+B with same-color bishops.\n */\n get isInsufficientMaterial(): boolean {\n const nonKingEntries: [Square, Piece][] = [];\n for (const [sq, p] of this.#board) {\n if (p.type !== 'king') {\n nonKingEntries.push([sq, p]);\n }\n }\n\n // K vs K\n if (nonKingEntries.length === 0) {\n return true;\n }\n\n // K vs KB or K vs KN\n if (nonKingEntries.length === 1) {\n const sole = nonKingEntries[0]?.[1];\n return sole?.type === 'bishop' || sole?.type === 'knight';\n }\n\n // KB vs KB (any number) — all non-king pieces must be bishops on the same square color\n const allBishops = nonKingEntries.every(([, p]) => p.type === 'bishop');\n if (allBishops) {\n const first = nonKingEntries[0];\n if (first === undefined) {\n return true;\n }\n const firstSquareColor = squareColor(first[0]);\n return nonKingEntries.every(\n ([sq]) => squareColor(sq) === firstSquareColor,\n );\n }\n\n return false;\n }\n\n /**\n * Whether the position is legally reachable: exactly one king per side,\n * no pawns on ranks 1 or 8, and the side not to move is not in check.\n */\n get isValid(): boolean {\n let blackKings = 0;\n let whiteKings = 0;\n\n for (const [square, p] of this.#board) {\n if (p.type === 'king') {\n if (p.color === 'black') {\n blackKings++;\n } else {\n whiteKings++;\n }\n }\n\n // No pawns on rank 1 or 8\n if (p.type === 'pawn' && (square[1] === '1' || square[1] === '8')) {\n return false;\n }\n }\n\n if (blackKings !== 1 || whiteKings !== 1) {\n return false;\n }\n\n // Side not to move must not be in check\n const opponent: Color = this.#turn === 'white' ? 'black' : 'white';\n for (const [square, p] of this.#board) {\n if (\n p.type === 'king' &&\n p.color === opponent &&\n this.isAttacked(square, this.#turn)\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n /** Whether the side to move is in check. */\n get isCheck(): boolean {\n for (const [square, p] of this.#board) {\n if (p.type === 'king' && p.color === this.#turn) {\n const opponent: Color = this.#turn === 'white' ? 'black' : 'white';\n return this.isAttacked(square, opponent);\n }\n }\n return false;\n }\n\n /** Side to move — `'white'` or `'black'`. */\n get turn(): Color {\n return this.#turn;\n }\n\n #isAttackedByPiece(\n board: (Piece | undefined)[],\n targetIndex: number,\n fromIndex: number,\n p: Piece,\n by: Color,\n ): boolean {\n const diff = targetIndex - fromIndex;\n const tableIndex = diff + DIFF_OFFSET;\n\n if (tableIndex < 0 || tableIndex >= 240) {\n return false;\n }\n\n const attackMask = ATTACKS[tableIndex] ?? 0;\n const pieceMask = PIECE_MASKS[p.type] ?? 0;\n\n if ((attackMask & pieceMask) === 0) {\n return false;\n }\n\n if (p.type === 'pawn') {\n if (by === 'white' && diff > 0) {\n return false;\n }\n\n if (by === 'black' && diff < 0) {\n return false;\n }\n }\n\n const ray = RAYS[tableIndex] ?? 0;\n\n if (ray === 0) {\n return true;\n }\n\n let index = fromIndex + ray;\n while (index !== targetIndex) {\n if ((index & OFF_BOARD) !== 0) {\n return false;\n }\n\n if (board[index] !== undefined) {\n return false;\n }\n\n index += ray;\n }\n\n return true;\n }\n\n /**\n * Returns a new position with the given changes applied. Fields not\n * provided are carried over from the source. Calling with no argument\n * returns a clone.\n *\n * @param changes - Board deltas and option overrides to apply.\n */\n derive(changes?: DeriveOptions): Position {\n const board = new Map(this.#board);\n\n if (changes?.changes) {\n for (const [square, piece] of changes.changes) {\n if (piece === undefined) {\n board.delete(square);\n } else {\n board.set(square, piece);\n }\n }\n }\n\n return new Position(board, {\n castlingRights: changes?.castlingRights ?? this.#castlingRights,\n enPassantSquare:\n 'enPassantSquare' in (changes ?? {})\n ? changes?.enPassantSquare\n : this.#enPassantSquare,\n fullmoveNumber: changes?.fullmoveNumber ?? this.#fullmoveNumber,\n halfmoveClock: changes?.halfmoveClock ?? this.#halfmoveClock,\n turn: changes?.turn ?? this.#turn,\n });\n }\n\n /**\n * Returns all squares occupied by pieces of the given color that\n * attack the target square.\n *\n * @param square - The target square.\n * @param by - The attacking color.\n */\n attackers(square: Square, by: Color): Square[] {\n const board = boardFromMap(this.#board);\n const targetIndex = squareToIndex(square);\n const result: Square[] = [];\n\n for (const [sq, p] of this.#board) {\n if (p.color !== by) {\n continue;\n }\n\n const fromIndex = squareToIndex(sq);\n if (this.#isAttackedByPiece(board, targetIndex, fromIndex, p, by)) {\n result.push(sq);\n }\n }\n\n return result;\n }\n\n /**\n * Returns `true` if any piece of the given color attacks the target square.\n *\n * @param square - The target square.\n * @param by - The attacking color.\n */\n isAttacked(square: Square, by: Color): boolean {\n const board = boardFromMap(this.#board);\n const targetIndex = squareToIndex(square);\n\n for (const [sq, p] of this.#board) {\n if (p.color !== by) {\n continue;\n }\n\n const fromIndex = squareToIndex(sq);\n if (this.#isAttackedByPiece(board, targetIndex, fromIndex, p, by)) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Returns the piece on the given square, or `undefined` if empty.\n *\n * @param square - The square to query.\n */\n piece(square: Square): Piece | undefined {\n return this.#board.get(square);\n }\n\n /**\n * Returns a map of all pieces on the board, optionally filtered by color.\n *\n * @param color - If provided, only pieces of this color are returned.\n */\n pieces(color?: Color): Map<Square, Piece> {\n if (color === undefined) {\n return new Map(this.#board);\n }\n const result = new Map<Square, Piece>();\n for (const [sq, p] of this.#board) {\n if (p.color === color) {\n result.set(sq, p);\n }\n }\n return result;\n }\n}\n","import { Position } from './position.js';\n\n/** The standard chess starting position. */\nconst STARTING_POSITION = new Position();\n\nexport { STARTING_POSITION };\n\nexport { COLORS, FILES, PIECE_TYPES, RANKS, SQUARES } from './primitives.js';\n"],"mappings":"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"}
package/package.json CHANGED
@@ -13,13 +13,13 @@
13
13
  "eslint-config-prettier": "^10.1.8",
14
14
  "eslint-import-resolver-typescript": "^4.4.4",
15
15
  "eslint-plugin-import-x": "^4.16.2",
16
- "eslint-plugin-unicorn": "^63.0.0",
16
+ "eslint-plugin-unicorn": "^64.0.0",
17
17
  "husky": "^9.1.7",
18
18
  "lint-staged": "^16.4.0",
19
19
  "prettier": "^3.8.1",
20
20
  "tsdown": "^0.21.4",
21
21
  "typedoc": "^0.28.17",
22
- "typescript": "^5.9.3",
22
+ "typescript": "^6.0.2",
23
23
  "typescript-eslint": "^8.57.0",
24
24
  "vitest": "^4.1.0"
25
25
  },
@@ -30,10 +30,6 @@
30
30
  ".": {
31
31
  "import": "./dist/index.js",
32
32
  "types": "./dist/index.d.ts"
33
- },
34
- "./internal": {
35
- "import": "./dist/internal/index.js",
36
- "types": "./dist/internal/index.d.ts"
37
33
  }
38
34
  },
39
35
  "files": [
@@ -61,10 +57,10 @@
61
57
  "sideEffects": false,
62
58
  "type": "module",
63
59
  "types": "dist/index.d.ts",
64
- "version": "1.0.3",
60
+ "version": "2.0.1",
65
61
  "scripts": {
66
62
  "build": "tsdown",
67
- "docs": "typedoc",
63
+ "docs": "typedoc src/index.ts --out docs --entryPointStrategy expand --plugin ./typedoc-plugin-collapse-square.mjs",
68
64
  "format": "pnpm run format:ci --write",
69
65
  "format:ci": "prettier -l \"**/*.+(css|js|json|jsx|md|mjs|mts|ts|tsx|yml|yaml)\"",
70
66
  "lint": "pnpm run lint:style && pnpm run lint:types",
@@ -1,25 +0,0 @@
1
- import { a as Piece, n as Color, o as PieceType, r as File, u as Square } from "../types-BN6JJgNK.js";
2
-
3
- //#region src/internal/board.d.ts
4
- declare const OFF_BOARD = 136;
5
- declare function squareToIndex(square: Square): number;
6
- declare function indexToSquare(index: number): Square;
7
- /**
8
- * Converts a Map<Square, Piece> to the internal 0x88 array representation.
9
- * The bridge between the public Position type and the 0x88 internal layout.
10
- */
11
- declare function boardFromMap(map: Map<Square, Piece>): (Piece | undefined)[];
12
- //#endregion
13
- //#region src/internal/tables.d.ts
14
- declare const PIECE_MASKS: Record<PieceType, number>;
15
- declare const DIFF_OFFSET = 119;
16
- declare const ATTACKS: number[];
17
- declare const RAYS: number[];
18
- //#endregion
19
- //#region src/internal/zobrist.d.ts
20
- declare const PIECE_TABLE: Record<Square, Partial<Record<PieceType, Record<Color, bigint>>>>;
21
- declare const TURN_TABLE: Record<Color, bigint>;
22
- declare const CASTLING_TABLE: Record<string, bigint>;
23
- declare const EP_TABLE: Record<File, bigint>;
24
- //#endregion
25
- export { ATTACKS, CASTLING_TABLE, DIFF_OFFSET, EP_TABLE, OFF_BOARD, PIECE_MASKS, PIECE_TABLE, RAYS, TURN_TABLE, boardFromMap, indexToSquare, squareToIndex };
@@ -1 +0,0 @@
1
- import{_ as e,d as t,f as n,g as r,h as i,i as a,m as o,n as s,p as c,r as l,t as u,u as d}from"../internal-BOZCKSNm.js";export{d as ATTACKS,u as CASTLING_TABLE,t as DIFF_OFFSET,s as EP_TABLE,o as OFF_BOARD,n as PIECE_MASKS,l as PIECE_TABLE,c as RAYS,a as TURN_TABLE,i as boardFromMap,r as indexToSquare,e as squareToIndex};
@@ -1 +0,0 @@
1
- const e=136;function t(e){let t=(e.codePointAt(0)??0)-(`a`.codePointAt(0)??0);return(8-Number.parseInt(e[1]??`1`,10))*16+t}function n(e){let t=8-Math.floor(e/16),n=e%16;return`${String.fromCodePoint((`a`.codePointAt(0)??0)+n)}${t}`}function r(e){let n=Array.from({length:128});for(let[r,i]of e)n[t(r)]=i;return n}const i=[-33,-31,-18,-14,14,18,31,33],a=[-17,-15,15,17],o=[-16,-1,1,16],s=[-17,-16,-15,-1,1,15,16,17],c={b:4,k:16,n:2,p:1,q:12,r:8},l=119,u=Array.from({length:240}).fill(0),d=Array.from({length:240}).fill(0);(function(){for(let e of i)u[e+119]=(u[e+119]??0)|2;for(let e of s)u[e+119]=(u[e+119]??0)|16;for(let e of[15,17])u[e+119]=(u[e+119]??0)|1,u[-e+119]=(u[-e+119]??0)|1;for(let e=0;e<=119;e++)if(!(e&136)){for(let t of o){let n=e+t;for(;!(n&136);){let r=n-e;u[r+119]=(u[r+119]??0)|8,d[r+119]=t,n+=t}}for(let t of a){let n=e+t;for(;!(n&136);){let r=n-e;u[r+119]=(u[r+119]??0)|4,d[r+119]=t,n+=t}}}})();const f=[`b`,`w`],p=[`a`,`b`,`c`,`d`,`e`,`f`,`g`,`h`],m=[`1`,`2`,`3`,`4`,`5`,`6`,`7`,`8`],h=[`b`,`k`,`n`,`p`,`q`,`r`],g=p.flatMap(e=>m.toReversed().map(t=>`${e}${t}`));function _(e){let t=BigInt(e);return()=>(t=t*6364136223846793005n+1442695040888963407n&18446744073709551615n,t)}const v=_(3735928559),y=Object.fromEntries(g.map(e=>[e,Object.fromEntries(h.map(e=>[e,Object.fromEntries(f.map(e=>[e,v()]))]))])),b={b:v(),w:v()},x={bK:v(),bQ:v(),wK:v(),wQ:v()},S=Object.fromEntries(p.map(e=>[e,v()]));export{t as _,f as a,m as c,l as d,c as f,n as g,r as h,b as i,g as l,e as m,S as n,p as o,d as p,y as r,h as s,x as t,u};
@@ -1 +0,0 @@
1
- {"version":3,"file":"internal-BOZCKSNm.js","names":[],"sources":["../src/internal/board.ts","../src/internal/tables.ts","../src/primitives.ts","../src/internal/zobrist.ts"],"sourcesContent":["import type { Piece, Square } from '../types.js';\n\nconst OFF_BOARD = 0x88;\n\nfunction squareToIndex(square: Square): number {\n const file = (square.codePointAt(0) ?? 0) - ('a'.codePointAt(0) ?? 0);\n const rank = Number.parseInt(square[1] ?? '1', 10);\n return (8 - rank) * 16 + file;\n}\n\nfunction indexToSquare(index: number): Square {\n const rank = 8 - Math.floor(index / 16);\n const file = index % 16;\n return `${String.fromCodePoint(('a'.codePointAt(0) ?? 0) + file)}${rank}` as Square;\n}\n\n/**\n * Converts a Map<Square, Piece> to the internal 0x88 array representation.\n * The bridge between the public Position type and the 0x88 internal layout.\n */\nfunction boardFromMap(map: Map<Square, Piece>): (Piece | undefined)[] {\n const board: (Piece | undefined)[] = Array.from({ length: 128 });\n for (const [square, p] of map) {\n board[squareToIndex(square)] = p;\n }\n return board;\n}\n\nexport { OFF_BOARD, boardFromMap, indexToSquare, squareToIndex };\n","import { OFF_BOARD } from './board.js';\n\nimport type { PieceType } from '../types.js';\n\n// ── Direction constants ───────────────────────────────────────────────────────\n\nconst KNIGHT_OFFSETS_0X88 = [-33, -31, -18, -14, 14, 18, 31, 33] as const;\nconst BISHOP_DIRS_0X88 = [-17, -15, 15, 17] as const;\nconst ROOK_DIRS_0X88 = [-16, -1, 1, 16] as const;\nconst KING_OFFSETS_0X88 = [-17, -16, -15, -1, 1, 15, 16, 17] as const;\n\n// ── Piece bitmasks ────────────────────────────────────────────────────────────\n\nconst PAWN_MASK = 0x01;\nconst KNIGHT_MASK = 0x02;\nconst BISHOP_MASK = 0x04;\nconst ROOK_MASK = 0x08;\nconst KING_MASK = 0x10;\n\nconst PIECE_MASKS: Record<PieceType, number> = {\n b: BISHOP_MASK,\n k: KING_MASK,\n n: KNIGHT_MASK,\n p: PAWN_MASK,\n q: BISHOP_MASK | ROOK_MASK,\n r: ROOK_MASK,\n};\n\n// ── ATTACKS / RAYS lookup tables ──────────────────────────────────────────────\n//\n// ATTACKS[diff + DIFF_OFFSET] — bitmask of piece types that can attack along\n// the vector represented by `diff` (target_index - attacker_index).\n//\n// RAYS[diff + DIFF_OFFSET] — the ray step direction for sliding pieces (0 for\n// non-sliding pieces).\n//\n// DIFF_OFFSET = 119 centres the diff range [-119, +119] at index 0.\n\nconst DIFF_OFFSET = 119;\n\nconst ATTACKS: number[] = Array.from<number>({ length: 240 }).fill(0);\nconst RAYS: number[] = Array.from<number>({ length: 240 }).fill(0);\n\n(function initAttackTables() {\n // Knight\n for (const offset of KNIGHT_OFFSETS_0X88) {\n ATTACKS[offset + DIFF_OFFSET] =\n (ATTACKS[offset + DIFF_OFFSET] ?? 0) | KNIGHT_MASK;\n }\n\n // King\n for (const offset of KING_OFFSETS_0X88) {\n ATTACKS[offset + DIFF_OFFSET] =\n (ATTACKS[offset + DIFF_OFFSET] ?? 0) | KING_MASK;\n }\n\n // Pawns — white attacks at offsets -17 and -15 (toward rank 8 = lower index)\n // black attacks at +15 and +17. Both share PAWN_MASK; color checked at use time.\n for (const offset of [15, 17]) {\n ATTACKS[offset + DIFF_OFFSET] =\n (ATTACKS[offset + DIFF_OFFSET] ?? 0) | PAWN_MASK;\n ATTACKS[-offset + DIFF_OFFSET] =\n (ATTACKS[-offset + DIFF_OFFSET] ?? 0) | PAWN_MASK;\n }\n\n // Sliding pieces — walk every ray from every valid square\n for (let from = 0; from <= 119; from++) {\n if (from & OFF_BOARD) {\n continue;\n }\n\n for (const direction of ROOK_DIRS_0X88) {\n let to = from + direction;\n while (!(to & OFF_BOARD)) {\n const diff = to - from;\n ATTACKS[diff + DIFF_OFFSET] =\n (ATTACKS[diff + DIFF_OFFSET] ?? 0) | ROOK_MASK;\n RAYS[diff + DIFF_OFFSET] = direction;\n to += direction;\n }\n }\n\n for (const direction of BISHOP_DIRS_0X88) {\n let to = from + direction;\n while (!(to & OFF_BOARD)) {\n const diff = to - from;\n ATTACKS[diff + DIFF_OFFSET] =\n (ATTACKS[diff + DIFF_OFFSET] ?? 0) | BISHOP_MASK;\n RAYS[diff + DIFF_OFFSET] = direction;\n to += direction;\n }\n }\n }\n})();\n\nexport { ATTACKS, DIFF_OFFSET, PIECE_MASKS, RAYS };\n","import type { Color, File, PieceType, Rank, Square } from './types.js';\n\nconst COLORS: Color[] = ['b', 'w'];\nconst FILES: File[] = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];\nconst RANKS: Rank[] = ['1', '2', '3', '4', '5', '6', '7', '8'];\nconst PIECE_TYPES: PieceType[] = ['b', 'k', 'n', 'p', 'q', 'r'];\nconst SQUARES: Square[] = FILES.flatMap((f) =>\n RANKS.toReversed().map((r) => `${f}${r}` as Square),\n);\n\nexport { COLORS, FILES, PIECE_TYPES, RANKS, SQUARES };\n","import { COLORS, FILES, PIECE_TYPES, SQUARES } from '../primitives.js';\n\nimport type { Color, File, PieceType, Square } from '../types.js';\n\n// Seeded LCG for deterministic random numbers (no Math.random — must be stable across runs)\nfunction lcg(seed: number): () => bigint {\n let s = BigInt(seed);\n return () => {\n s =\n (s * 6_364_136_223_846_793_005n + 1_442_695_040_888_963_407n) &\n 0xff_ff_ff_ff_ff_ff_ff_ffn;\n return s;\n };\n}\n\nconst next = lcg(0xde_ad_be_ef);\n\n// Piece table: PIECE_TABLE[square][pieceType][color]\nconst PIECE_TABLE: Record<\n Square,\n Partial<Record<PieceType, Record<Color, bigint>>>\n> = Object.fromEntries(\n SQUARES.map((sq) => [\n sq,\n Object.fromEntries(\n PIECE_TYPES.map((pt) => [\n pt,\n Object.fromEntries(COLORS.map((c) => [c, next()])),\n ]),\n ),\n ]),\n) as Record<Square, Partial<Record<PieceType, Record<Color, bigint>>>>;\n\nconst TURN_TABLE: Record<Color, bigint> = {\n b: next(),\n w: next(),\n};\n\nconst CASTLING_TABLE: Record<string, bigint> = {\n bK: next(),\n bQ: next(),\n wK: next(),\n wQ: next(),\n};\n\nconst EP_TABLE: Record<File, bigint> = Object.fromEntries(\n FILES.map((f) => [f, next()]),\n) as Record<File, bigint>;\n\nexport { CASTLING_TABLE, EP_TABLE, PIECE_TABLE, TURN_TABLE };\n"],"mappings":"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"}
@@ -1,33 +0,0 @@
1
- //#region src/types.d.ts
2
- type Color = 'b' | 'w';
3
- type File = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h';
4
- type PieceType = 'b' | 'k' | 'n' | 'p' | 'q' | 'r';
5
- type PromotionPieceType = 'b' | 'n' | 'q' | 'r';
6
- type Rank = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8';
7
- /** @preventExpand */
8
- type Square = `${File}${Rank}`;
9
- type SquareColor = 'dark' | 'light';
10
- interface CastlingRights {
11
- bK: boolean;
12
- bQ: boolean;
13
- wK: boolean;
14
- wQ: boolean;
15
- }
16
- interface PositionOptions {
17
- castlingRights?: CastlingRights;
18
- enPassantSquare?: Square;
19
- fullmoveNumber?: number;
20
- halfmoveClock?: number;
21
- turn?: Color;
22
- }
23
- interface Move {
24
- from: Square;
25
- promotion: PromotionPieceType | undefined;
26
- to: Square;
27
- }
28
- interface Piece {
29
- color: Color;
30
- type: PieceType;
31
- }
32
- //#endregion
33
- export { Piece as a, PromotionPieceType as c, SquareColor as d, Move as i, Rank as l, Color as n, PieceType as o, File as r, PositionOptions as s, CastlingRights as t, Square as u };