@echecs/position 1.0.2

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 ADDED
@@ -0,0 +1,63 @@
1
+ # Changelog
2
+
3
+ ## [1.0.2] - 2026-03-19
4
+
5
+ ### Fixed
6
+
7
+ - Lowercase hex literals in Zobrist table seed constants.
8
+
9
+ ## [1.0.1] - 2026-03-19
10
+
11
+ ### Changed
12
+
13
+ - Extracted primitive constants (`COLORS`, `FILES`, `RANKS`, `PIECE_TYPES`,
14
+ `SQUARES`) into `src/primitives.ts` to eliminate duplication in
15
+ `src/internal/zobrist.ts`. No API or behavior change.
16
+
17
+ ## [1.0.0] - 2026-03-19
18
+
19
+ ### Added
20
+
21
+ - `Position` class replacing the `Position` interface — immutable value object
22
+ with private board state and a clean query API
23
+ - Constructor signatures: `new Position()` (starting position),
24
+ `new Position(board)`, and `new Position(board, options)`
25
+ - Getters: `castlingRights`, `enPassantSquare`, `fullmoveNumber`,
26
+ `halfmoveClock`, `hash`, `isCheck`, `isInsufficientMaterial`, `isValid`,
27
+ `turn`
28
+ - Methods: `attackers`, `findPiece`, `isAttacked`, `piece`, `pieces`
29
+ - Zobrist hash (`hash` getter) — deterministic 16-character hex string for
30
+ position identity, computed once and cached
31
+ - `isValid` — validates king presence, no pawns on back ranks, and that the side
32
+ not to move is not in check
33
+ - `isInsufficientMaterial` — FIDE-compliant: K vs K, K+B vs K, K+N vs K, and K+B
34
+ vs K+B with same-color bishops
35
+ - Runtime constants: `COLORS`, `FILES`, `RANKS`, `PIECE_TYPES`, `SQUARES`
36
+ - `PositionOptions` type exported for use in consuming code
37
+ - `Move` and `PromotionPieceType` types exported for companion packages
38
+ - MIT `LICENSE` file
39
+ - `README.md` with installation, quick start, and full API reference
40
+ - GitHub Actions CI/CD workflows (lint, test, format, docs, release, auto-merge)
41
+
42
+ ### Changed
43
+
44
+ - `SquareColor` values changed from `'l'`/`'d'` to `'light'`/`'dark'`
45
+ - `STARTING_POSITION` is now a `Position` instance instead of a plain object
46
+ - Standalone query functions (`isAttacked`, `isCheck`, `piece`, `pieces`)
47
+ removed from public API — use `Position` methods instead
48
+
49
+ ### Removed
50
+
51
+ - `Position` interface (replaced by class)
52
+ - `startingBoard` export (implementation detail)
53
+
54
+ ## [0.1.0] - 2026-03-18
55
+
56
+ ### Added
57
+
58
+ - Initial release with `Position` interface, `EMPTY_BOARD`, `STARTING_POSITION`
59
+ - Internal 0x88 board representation (`./internal` export condition)
60
+ - Standalone query functions: `isAttacked`, `isCheck`, `piece`, `pieces`
61
+ - Square utilities: `squareColor`, `squareFile`, `squareRank`
62
+ - Types: `CastlingRights`, `Color`, `File`, `Move`, `Piece`, `PieceType`,
63
+ `PromotionPieceType`, `Rank`, `Square`, `SquareColor`
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adrian de la Rosa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # Position
2
+
3
+ [![npm](https://img.shields.io/npm/v/@echecs/position)](https://www.npmjs.com/package/@echecs/position)
4
+ [![Test](https://github.com/mormubis/position/actions/workflows/test.yml/badge.svg)](https://github.com/mormubis/position/actions/workflows/test.yml)
5
+ [![Coverage](https://codecov.io/gh/mormubis/position/branch/main/graph/badge.svg)](https://codecov.io/gh/mormubis/position)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+ [![API Docs](https://img.shields.io/badge/API-docs-blue.svg)](https://mormubis.github.io/position/)
8
+
9
+ **Position** is a TypeScript library representing a complete chess position —
10
+ the board, turn, castling rights, en passant square, halfmove clock, and
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.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @echecs/position
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { Position, STARTING_POSITION } from '@echecs/position';
24
+
25
+ // Starting position
26
+ const pos = new Position();
27
+
28
+ console.log(pos.turn); // 'w'
29
+ console.log(pos.fullmoveNumber); // 1
30
+ console.log(pos.isCheck); // false
31
+
32
+ // 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']
38
+
39
+ // 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
42
+ ```
43
+
44
+ ## API
45
+
46
+ Full API reference is available at https://mormubis.github.io/position/
47
+
48
+ ### Constructor
49
+
50
+ ```ts
51
+ new Position()
52
+ 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
+ })
60
+ ```
61
+
62
+ The no-argument form creates the standard chess starting position. Pass a custom
63
+ `board` map and optional `options` to construct any arbitrary position.
64
+
65
+ ### Getters
66
+
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'`) |
78
+
79
+ ### Methods
80
+
81
+ #### `attackers(square, color): Square[]`
82
+
83
+ Returns all squares occupied by pieces of `color` that attack `square`.
84
+
85
+ ```typescript
86
+ pos.attackers('e5', 'b'); // e.g. ['d7', 'f6']
87
+ ```
88
+
89
+ #### `findPiece(piece): Square[]`
90
+
91
+ Returns all squares occupied by the given piece.
92
+
93
+ ```typescript
94
+ pos.findPiece({ color: 'w', type: 'q' }); // ['d1']
95
+ ```
96
+
97
+ #### `isAttacked(square, color): boolean`
98
+
99
+ Returns `true` if any piece of `color` attacks `square`.
100
+
101
+ ```typescript
102
+ pos.isAttacked('f3', 'w'); // true if white attacks f3
103
+ ```
104
+
105
+ #### `piece(square): Piece | undefined`
106
+
107
+ Returns the piece on `square`, or `undefined` if the square is empty.
108
+
109
+ ```typescript
110
+ pos.piece('e1'); // { color: 'w', type: 'k' }
111
+ pos.piece('e5'); // undefined (empty in starting position)
112
+ ```
113
+
114
+ #### `pieces(color?): Map<Square, Piece>`
115
+
116
+ Returns a map of all pieces, optionally filtered by `color`.
117
+
118
+ ```typescript
119
+ pos.pieces(); // all 32 pieces in starting position
120
+ pos.pieces('w'); // 16 white pieces
121
+ ```
122
+
123
+ ### Constants
124
+
125
+ ```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';
135
+ ```
136
+
137
+ ### Types
138
+
139
+ All types are exported for use in consuming code and companion packages.
140
+
141
+ ```typescript
142
+ import type {
143
+ CastlingRights, // { bK: boolean; bQ: boolean; wK: boolean; wQ: boolean }
144
+ Color, // 'w' | 'b'
145
+ File, // 'a' | 'b' | ... | 'h'
146
+ Move, // { from: Square; to: Square; promotion: PromotionPieceType | undefined }
147
+ Piece, // { color: Color; type: PieceType }
148
+ PieceType, // 'b' | 'k' | 'n' | 'p' | 'q' | 'r'
149
+ PositionOptions, // options accepted by the Position constructor
150
+ PromotionPieceType, // 'b' | 'n' | 'q' | 'r'
151
+ Rank, // '1' | '2' | ... | '8'
152
+ Square, // 'a1' | 'a2' | ... | 'h8'
153
+ SquareColor, // 'light' | 'dark'
154
+ } from '@echecs/position';
155
+ ```
156
+
157
+ `Move` and `PromotionPieceType` are exported for use by companion packages
158
+ (`@echecs/san`, `@echecs/game`) that build on this foundational type.
@@ -0,0 +1,52 @@
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-DpJMtqKW.js";
2
+
3
+ //#region src/position.d.ts
4
+ declare class Position {
5
+ #private;
6
+ constructor(board?: Map<Square, Piece>, options?: PositionOptions);
7
+ get castlingRights(): CastlingRights;
8
+ get enPassantSquare(): Square | undefined;
9
+ get fullmoveNumber(): number;
10
+ get halfmoveClock(): number;
11
+ /**
12
+ * A Zobrist hash of the position as a 16-character hex string.
13
+ * Computed once and cached. Two positions with the same hash are
14
+ * considered identical (with negligible collision probability).
15
+ *
16
+ * @remarks The hash algorithm and format are not stable across major
17
+ * versions. Do not persist hashes across version upgrades.
18
+ */
19
+ get hash(): string;
20
+ get isInsufficientMaterial(): boolean;
21
+ get isValid(): boolean;
22
+ get isCheck(): boolean;
23
+ get turn(): Color;
24
+ attackers(square: Square, by: Color): Square[];
25
+ findPiece(piece: Piece): Square[];
26
+ isAttacked(square: Square, by: Color): boolean;
27
+ piece(square: Square): Piece | undefined;
28
+ pieces(color?: Color): Map<Square, Piece>;
29
+ }
30
+ //#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
+ //#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>;
40
+ declare const STARTING_POSITION: Position;
41
+ //#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 };
package/dist/index.js ADDED
@@ -0,0 +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};
@@ -0,0 +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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"}
@@ -0,0 +1,25 @@
1
+ import { a as Piece, n as Color, o as PieceType, r as File, u as Square } from "../types-DpJMtqKW.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 };
@@ -0,0 +1 @@
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};
@@ -0,0 +1 @@
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};
@@ -0,0 +1 @@
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":"AAEA,MAAM,EAAY,IAElB,SAAS,EAAc,EAAwB,CAC7C,IAAM,GAAQ,EAAO,YAAY,EAAE,EAAI,IAAM,IAAI,YAAY,EAAE,EAAI,GAEnE,OAAQ,EADK,OAAO,SAAS,EAAO,IAAM,IAAK,GAAG,EAC9B,GAAK,EAG3B,SAAS,EAAc,EAAuB,CAC5C,IAAM,EAAO,EAAI,KAAK,MAAM,EAAQ,GAAG,CACjC,EAAO,EAAQ,GACrB,MAAO,GAAG,OAAO,eAAe,IAAI,YAAY,EAAE,EAAI,GAAK,EAAK,GAAG,IAOrE,SAAS,EAAa,EAAgD,CACpE,IAAM,EAA+B,MAAM,KAAK,CAAE,OAAQ,IAAK,CAAC,CAChE,IAAK,GAAM,CAAC,EAAQ,KAAM,EACxB,EAAM,EAAc,EAAO,EAAI,EAEjC,OAAO,ECnBT,MAAM,EAAsB,CAAC,IAAK,IAAK,IAAK,IAAK,GAAI,GAAI,GAAI,GAAG,CAC1D,EAAmB,CAAC,IAAK,IAAK,GAAI,GAAG,CACrC,EAAiB,CAAC,IAAK,GAAI,EAAG,GAAG,CACjC,EAAoB,CAAC,IAAK,IAAK,IAAK,GAAI,EAAG,GAAI,GAAI,GAAG,CAUtD,EAAyC,CAC7C,EAAG,EACH,EAAG,GACH,EAAG,EACH,EAAG,EACH,EAAG,GACH,EAAG,EACJ,CAYK,EAAc,IAEd,EAAoB,MAAM,KAAa,CAAE,OAAQ,IAAK,CAAC,CAAC,KAAK,EAAE,CAC/D,EAAiB,MAAM,KAAa,CAAE,OAAQ,IAAK,CAAC,CAAC,KAAK,EAAE,EAEjE,UAA4B,CAE3B,IAAK,IAAM,KAAU,EACnB,EAAQ,EAAA,MACL,EAAQ,EAAA,MAAyB,GAAK,EAI3C,IAAK,IAAM,KAAU,EACnB,EAAQ,EAAA,MACL,EAAQ,EAAA,MAAyB,GAAK,GAK3C,IAAK,IAAM,IAAU,CAAC,GAAI,GAAG,CAC3B,EAAQ,EAAA,MACL,EAAQ,EAAA,MAAyB,GAAK,EACzC,EAAQ,CAAC,EAAA,MACN,EAAQ,CAAC,EAAA,MAAyB,GAAK,EAI5C,IAAK,IAAI,EAAO,EAAG,GAAQ,IAAK,IAC1B,OAAA,KAIJ,KAAK,IAAM,KAAa,EAAgB,CACtC,IAAI,EAAK,EAAO,EAChB,KAAO,EAAE,EAAA,MAAiB,CACxB,IAAM,EAAO,EAAK,EAClB,EAAQ,EAAA,MACL,EAAQ,EAAA,MAAuB,GAAK,EACvC,EAAK,EAAA,KAAsB,EAC3B,GAAM,GAIV,IAAK,IAAM,KAAa,EAAkB,CACxC,IAAI,EAAK,EAAO,EAChB,KAAO,EAAE,EAAA,MAAiB,CACxB,IAAM,EAAO,EAAK,EAClB,EAAQ,EAAA,MACL,EAAQ,EAAA,MAAuB,GAAK,EACvC,EAAK,EAAA,KAAsB,EAC3B,GAAM,QAIV,CC3FJ,MAAM,EAAkB,CAAC,IAAK,IAAI,CAC5B,EAAgB,CAAC,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAI,CACxD,EAAgB,CAAC,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAI,CACxD,EAA2B,CAAC,IAAK,IAAK,IAAK,IAAK,IAAK,IAAI,CACzD,EAAoB,EAAM,QAAS,GACvC,EAAM,YAAY,CAAC,IAAK,GAAM,GAAG,IAAI,IAAc,CACpD,CCHD,SAAS,EAAI,EAA4B,CACvC,IAAI,EAAI,OAAO,EAAK,CACpB,WACE,EACG,EAAI,qBAA6B,qBAClC,sBACK,GAIX,MAAM,EAAO,EAAI,WAAc,CAGzB,EAGF,OAAO,YACT,EAAQ,IAAK,GAAO,CAClB,EACA,OAAO,YACL,EAAY,IAAK,GAAO,CACtB,EACA,OAAO,YAAY,EAAO,IAAK,GAAM,CAAC,EAAG,GAAM,CAAC,CAAC,CAAC,CACnD,CAAC,CACH,CACF,CAAC,CACH,CAEK,EAAoC,CACxC,EAAG,GAAM,CACT,EAAG,GAAM,CACV,CAEK,EAAyC,CAC7C,GAAI,GAAM,CACV,GAAI,GAAM,CACV,GAAI,GAAM,CACV,GAAI,GAAM,CACX,CAEK,EAAiC,OAAO,YAC5C,EAAM,IAAK,GAAM,CAAC,EAAG,GAAM,CAAC,CAAC,CAC9B"}
@@ -0,0 +1,32 @@
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
+ type Square = `${File}${Rank}`;
8
+ type SquareColor = 'dark' | 'light';
9
+ interface CastlingRights {
10
+ bK: boolean;
11
+ bQ: boolean;
12
+ wK: boolean;
13
+ wQ: boolean;
14
+ }
15
+ interface PositionOptions {
16
+ castlingRights?: CastlingRights;
17
+ enPassantSquare?: Square;
18
+ fullmoveNumber?: number;
19
+ halfmoveClock?: number;
20
+ turn?: Color;
21
+ }
22
+ interface Move {
23
+ from: Square;
24
+ promotion: PromotionPieceType | undefined;
25
+ to: Square;
26
+ }
27
+ interface Piece {
28
+ color: Color;
29
+ type: PieceType;
30
+ }
31
+ //#endregion
32
+ 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 };
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "author": "Adrian de la Rosa <adrian@delarosab.me>",
3
+ "bugs": {
4
+ "url": "https://github.com/mormubis/position/issues"
5
+ },
6
+ "description": "Chess position type and board utilities. Foundation for @echecs/fen, @echecs/san, and @echecs/game.",
7
+ "devDependencies": {
8
+ "@eslint/js": "^10.0.1",
9
+ "@typescript-eslint/parser": "^8.57.0",
10
+ "@vitest/coverage-v8": "^4.1.0",
11
+ "@vitest/eslint-plugin": "^1.6.12",
12
+ "eslint": "^10.0.3",
13
+ "eslint-config-prettier": "^10.1.8",
14
+ "eslint-import-resolver-typescript": "^4.4.4",
15
+ "eslint-plugin-import-x": "^4.16.2",
16
+ "eslint-plugin-unicorn": "^63.0.0",
17
+ "husky": "^9.1.7",
18
+ "lint-staged": "^16.4.0",
19
+ "prettier": "^3.8.1",
20
+ "tsdown": "^0.21.4",
21
+ "typedoc": "^0.28.17",
22
+ "typescript": "^5.9.3",
23
+ "typescript-eslint": "^8.57.0",
24
+ "vitest": "^4.1.0"
25
+ },
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "exports": {
30
+ ".": {
31
+ "import": "./dist/index.js",
32
+ "types": "./dist/index.d.ts"
33
+ },
34
+ "./internal": {
35
+ "import": "./dist/internal/index.js",
36
+ "types": "./dist/internal/index.d.ts"
37
+ }
38
+ },
39
+ "files": [
40
+ "/dist/",
41
+ "LICENSE",
42
+ "README.md",
43
+ "CHANGELOG.md"
44
+ ],
45
+ "homepage": "https://github.com/mormubis/position#readme",
46
+ "keywords": [
47
+ "board",
48
+ "chess",
49
+ "fen",
50
+ "no-dependencies",
51
+ "position",
52
+ "typescript"
53
+ ],
54
+ "license": "MIT",
55
+ "main": "dist/index.js",
56
+ "name": "@echecs/position",
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "https://github.com/mormubis/position.git"
60
+ },
61
+ "sideEffects": false,
62
+ "type": "module",
63
+ "types": "dist/index.d.ts",
64
+ "version": "1.0.2",
65
+ "scripts": {
66
+ "build": "tsdown",
67
+ "docs": "typedoc",
68
+ "format": "pnpm run format:ci --write",
69
+ "format:ci": "prettier -l \"**/*.+(css|js|json|jsx|md|mjs|mts|ts|tsx|yml|yaml)\"",
70
+ "lint": "pnpm run lint:style && pnpm run lint:types",
71
+ "lint:ci": "pnpm run lint:style --max-warnings 0 && pnpm run lint:types",
72
+ "lint:style": "eslint \"src/**/*.{ts,tsx}\" \"*.mjs\" --fix",
73
+ "lint:types": "tsc --noEmit --project tsconfig.json",
74
+ "test": "vitest run",
75
+ "test:coverage": "pnpm run test --coverage",
76
+ "test:watch": "pnpm run test --watch"
77
+ }
78
+ }