├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── eslint.config.mjs ├── package.json ├── pnpm-lock.yaml ├── scripts └── build.mjs ├── src ├── base.ts ├── board.ts ├── evaluate.ts ├── game.ts ├── index.ts ├── notation.ts ├── pieces │ ├── bishop.ts │ ├── king.ts │ ├── knight.ts │ ├── pawn.ts │ ├── queen.ts │ └── rook.ts └── utils.ts ├── tests ├── bishop.test-d.ts ├── board.test-d.ts ├── evaluate.test-d.ts ├── game.test-d.ts ├── king.test-d.ts ├── knight.test-d.ts ├── notation.test-d.ts ├── pawn.test-d.ts ├── queen.test-d.ts ├── rook.test-d.ts ├── string.test-d.ts ├── tsconfig.json └── utils.test-d.ts ├── tsconfig.json └── vitest.config.ts /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | 14 | - name: Setup Node 15 | uses: actions/setup-node@v4 16 | with: 17 | registry-url: https://registry.npmjs.org 18 | node-version: 22 19 | 20 | - name: Setup PNPM 21 | uses: pnpm/action-setup@v4 22 | with: 23 | run_install: true 24 | version: 9 25 | 26 | - name: ESLint 27 | run: pnpm lint 28 | 29 | - name: Vitest 30 | run: pnpm test 31 | 32 | - name: Build 33 | run: pnpm build 34 | 35 | - name: Release 36 | env: 37 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 38 | run: pnpm publish --no-git-checks --access public -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | 12 | - name: Setup Node 13 | uses: actions/setup-node@v4 14 | with: 15 | node-version: 22 16 | 17 | - name: Setup PNPM 18 | uses: pnpm/action-setup@v4 19 | with: 20 | run_install: true 21 | version: 9 22 | 23 | - name: ESLint 24 | run: pnpm lint 25 | 26 | - name: Vitest 27 | run: pnpm test 28 | 29 | - name: Build 30 | run: pnpm build 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | tsconfig.vitest-temp.json 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.12.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025-present, Scott Bedard. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `@bedard/chess-types` 2 | 3 | [![Test](https://github.com/scottbedard/type-chess/actions/workflows/test.yml/badge.svg)](https://github.com/scottbedard/type-chess/actions/workflows/test.yml) 4 | [![NPM](https://img.shields.io/npm/v/%40bedard%2Fchess-types)](https://www.npmjs.com/package/@bedard/chess-types) 5 | [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/scottbedard/chess-types/blob/main/LICENSE) 6 | 7 | Welcome to a strange experiment to play chess inside TypeScript's type system. 8 | 9 | [Check out the sandbox →](https://www.typescriptlang.org/play/?noErrorTruncation=true#code/JYWwDg9gTgLgBDAnmApnA3gKDnAwgCxQGciAjCAQygBMAabOAMWhApgHEKQV6cA5FAHdO3XnAEAPGAFkIANx6YAvnABmUCCDgByAAKkU1KtQD0AY0IkAtElRFtmTLbQi0AXnFDXAHgDaDbRQAJhQAFm1aHRQAdhQAVgiAgHMARlUAZgidUgAOMwA2RJxtVRSzcMiSvISxbQB5KzqsuBMTOAA6TswAXQA+R2c8MABXWQU4D0kZeRRvV16Wtu8rODAAGwpEOAokimAAOyJ4GEI4M00wYDWUKABCAeQ0ACFKGgm8SzJX6jmuFAXWnBlnB8DMoAgIHAiCg0Cc0GZhlAoCh9vByMYoTA2ChHICsICcDgcgAuOAAIjg4IAVHBSHAAI5wADWcBp+0p5IA3JgCYToqSKWBVsKhTSheKuTy2oS4PkBXAVmzWQrlYrJbycHF5TSVnSVkK1SsydyNXBQvK1U9lQAFVXK41SmVwdLalU65V8O0O01BeW2-1wW0rAO273SwkpeUAJXEcCtAEVlTGANIqsNwJS4tqqA4UNZrRCRFASSCwBCnXbcbZEbaYqAHJJwYZEBtMACinv2ECxMGAEH2D1QTBR72YUFYHD+v24ALa2igKVI9KZKX2UBMYE3YBSW5MQX2cT3pB36T3TxS1tPcT4QRM1vv1ov95MUb4T3jKSjyZStOZjJWcRmg4QA) 10 | 11 | ## Basic usage 12 | 13 | Create a game and apply moves using `{from}{to}{promotion?}` strings. Castling is done via `O-O` and `O-O-O` syntax, lowercase for black. 14 | 15 | ```ts 16 | import type { 17 | Chessboard, 18 | FormatGame, 19 | NewGame, 20 | NextMove, 21 | } from '@bedard/chess-types' 22 | 23 | type Game = NewGame<[ 24 | 'e2e4', 'e7e5', 25 | 'g1f3', 'b8c6', 26 | 'f1c4', 'f8c5', 27 | 'O-O', // ... 28 | ]> 29 | 30 | type CpuMove = NextMove // <- play against the compiler! 31 | 32 | type Board = Chessboard // <- hover to see the current board state 33 | 34 | // { 35 | // 8: " r * b q k * n r "; 36 | // 7: " p p p p * p p p "; 37 | // 6: " - * n * - * - * "; 38 | // 5: " * - b - p - * - "; 39 | // 4: " - * B * P * - * "; 40 | // 3: " * - * - * N * - "; 41 | // 2: " P P P P - P P P "; 42 | // 1: " R N B Q * R K - "; 43 | // } 44 | 45 | // finally, export the game as a string using FEN notation 46 | 47 | type Fen = FormatGame // 'r1bqk1nr/pppp1ppp/2n5/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQ1RK1 b kq - 5 4' 48 | ``` 49 | 50 | There are loads of other interesting types under the hood, like [`IsLegal`](https://github.com/scottbedard/chess-types/blob/a341df357eae5b2f7f293ddaf4335778ee357258/src/game.ts#L350-L421), [`ParseFen`](https://github.com/scottbedard/chess-types/blob/a341df357eae5b2f7f293ddaf4335778ee357258/src/notation.ts#L121-L137), and [`Evaluate`](https://github.com/scottbedard/chess-types/blob/a341df357eae5b2f7f293ddaf4335778ee357258/src/evaluate.ts#L75-L83). So if you're into that kind of thing, by all means dig around and let me know what you think! 51 | 52 | Side note, `NextMove` uses an extremely naive strategy. It simply adds up all the pieces and chooses the move with the best score. There are many improvements that could be made, but that was never the plan here. My goal was to play chess inside the compiler, not to build stockfish. 53 | 54 | ## Final thoughts 55 | 56 | TypeScript is amazing. 57 | 58 | It's type system can be thought of as it's own purely functional language, but for obvious reasons it shouldn't be used as one. There was no practical reason for any of this beyond learning, but in that sense I've enjoyed it and feel like I've succeeded. More specifically, I've gained a new appreciation for the recursive accumulator pattern, and generic type inference. 59 | 60 | If you find this repo interesting, here are some others you may like! 61 | 62 | - [`Type`](https://github.com/type-challenges/type-challenges) 63 | - [`typescript-types-only-wasm-runtime`](https://github.com/MichiganTypeScript/typescript-types-only-wasm-runtime) 64 | - [`RuyiLi/cursed-typescript`](https://github.com/RuyiLi/cursed-typescript) 65 | - [`susisu/typefuck`](https://github.com/susisu/typefuck) 66 | 67 | ## License 68 | 69 | [MIT](https://github.com/scottbedard/chess-types/blob/main/LICENSE) 70 | 71 | Copyright (c) 2025-present, Scott Bedard 72 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import eslint from '@eslint/js' 3 | import stylistic from '@stylistic/eslint-plugin' 4 | import tseslint from 'typescript-eslint' 5 | 6 | export default tseslint.config( 7 | { 8 | ignores: ['node_modules', 'dist'], 9 | }, 10 | 11 | eslint.configs.recommended, 12 | tseslint.configs.recommended, 13 | 14 | { 15 | plugins: { 16 | '@stylistic': stylistic, 17 | }, 18 | rules: { 19 | '@stylistic/eol-last': ['error', 'always'], 20 | '@stylistic/no-multi-spaces': 'error', 21 | '@stylistic/no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], 22 | '@stylistic/no-trailing-spaces': 'error', 23 | '@stylistic/quotes': ['error', 'single'], 24 | '@stylistic/semi': ['error', 'never'], 25 | '@typescript-eslint/no-unused-vars': ['error', { 'varsIgnorePattern': '^_' }], 26 | } 27 | } 28 | ) 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Scott Bedard", 3 | "bugs": { 4 | "url": "https://github.com/scottbedard/chess-types/issues" 5 | }, 6 | "devDependencies": { 7 | "@eslint/js": "^9.23.0", 8 | "@stylistic/eslint-plugin": "^4.2.0", 9 | "@types/node": "^22.13.11", 10 | "eslint": "^9.23.0", 11 | "typescript": "^5.8.2", 12 | "typescript-eslint": "^8.27.0", 13 | "vitest": "^3.0.9" 14 | }, 15 | "files": [ 16 | "/dist", 17 | "/tsconfig.json" 18 | ], 19 | "homepage": "https://github.com/scottbedard/chess-types#readme", 20 | "keywords": [], 21 | "license": "MIT", 22 | "main": "dist/index.mjs", 23 | "module": "dist/index.mjs", 24 | "name": "@bedard/chess-types", 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/scottbedard/chess-types.git" 28 | }, 29 | "scripts": { 30 | "build": "tsc --project ./tsconfig.json --emitDeclarationOnly && node ./scripts/build.mjs", 31 | "lint:fix": "eslint . --fix", 32 | "lint": "eslint .", 33 | "test": "vitest --typecheck" 34 | }, 35 | "type": "module", 36 | "types": "dist/index.d.ts", 37 | "version": "0.0.22" 38 | } 39 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | '@eslint/js': 12 | specifier: ^9.23.0 13 | version: 9.23.0 14 | '@stylistic/eslint-plugin': 15 | specifier: ^4.2.0 16 | version: 4.2.0(eslint@9.23.0)(typescript@5.8.2) 17 | '@types/node': 18 | specifier: ^22.13.11 19 | version: 22.13.11 20 | eslint: 21 | specifier: ^9.23.0 22 | version: 9.23.0 23 | typescript: 24 | specifier: ^5.8.2 25 | version: 5.8.2 26 | typescript-eslint: 27 | specifier: ^8.27.0 28 | version: 8.27.0(eslint@9.23.0)(typescript@5.8.2) 29 | vitest: 30 | specifier: ^3.0.9 31 | version: 3.0.9(@types/node@22.13.11) 32 | 33 | packages: 34 | 35 | '@esbuild/aix-ppc64@0.25.1': 36 | resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} 37 | engines: {node: '>=18'} 38 | cpu: [ppc64] 39 | os: [aix] 40 | 41 | '@esbuild/android-arm64@0.25.1': 42 | resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} 43 | engines: {node: '>=18'} 44 | cpu: [arm64] 45 | os: [android] 46 | 47 | '@esbuild/android-arm@0.25.1': 48 | resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} 49 | engines: {node: '>=18'} 50 | cpu: [arm] 51 | os: [android] 52 | 53 | '@esbuild/android-x64@0.25.1': 54 | resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} 55 | engines: {node: '>=18'} 56 | cpu: [x64] 57 | os: [android] 58 | 59 | '@esbuild/darwin-arm64@0.25.1': 60 | resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} 61 | engines: {node: '>=18'} 62 | cpu: [arm64] 63 | os: [darwin] 64 | 65 | '@esbuild/darwin-x64@0.25.1': 66 | resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} 67 | engines: {node: '>=18'} 68 | cpu: [x64] 69 | os: [darwin] 70 | 71 | '@esbuild/freebsd-arm64@0.25.1': 72 | resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} 73 | engines: {node: '>=18'} 74 | cpu: [arm64] 75 | os: [freebsd] 76 | 77 | '@esbuild/freebsd-x64@0.25.1': 78 | resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} 79 | engines: {node: '>=18'} 80 | cpu: [x64] 81 | os: [freebsd] 82 | 83 | '@esbuild/linux-arm64@0.25.1': 84 | resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} 85 | engines: {node: '>=18'} 86 | cpu: [arm64] 87 | os: [linux] 88 | 89 | '@esbuild/linux-arm@0.25.1': 90 | resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} 91 | engines: {node: '>=18'} 92 | cpu: [arm] 93 | os: [linux] 94 | 95 | '@esbuild/linux-ia32@0.25.1': 96 | resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} 97 | engines: {node: '>=18'} 98 | cpu: [ia32] 99 | os: [linux] 100 | 101 | '@esbuild/linux-loong64@0.25.1': 102 | resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} 103 | engines: {node: '>=18'} 104 | cpu: [loong64] 105 | os: [linux] 106 | 107 | '@esbuild/linux-mips64el@0.25.1': 108 | resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} 109 | engines: {node: '>=18'} 110 | cpu: [mips64el] 111 | os: [linux] 112 | 113 | '@esbuild/linux-ppc64@0.25.1': 114 | resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} 115 | engines: {node: '>=18'} 116 | cpu: [ppc64] 117 | os: [linux] 118 | 119 | '@esbuild/linux-riscv64@0.25.1': 120 | resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} 121 | engines: {node: '>=18'} 122 | cpu: [riscv64] 123 | os: [linux] 124 | 125 | '@esbuild/linux-s390x@0.25.1': 126 | resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} 127 | engines: {node: '>=18'} 128 | cpu: [s390x] 129 | os: [linux] 130 | 131 | '@esbuild/linux-x64@0.25.1': 132 | resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} 133 | engines: {node: '>=18'} 134 | cpu: [x64] 135 | os: [linux] 136 | 137 | '@esbuild/netbsd-arm64@0.25.1': 138 | resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} 139 | engines: {node: '>=18'} 140 | cpu: [arm64] 141 | os: [netbsd] 142 | 143 | '@esbuild/netbsd-x64@0.25.1': 144 | resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} 145 | engines: {node: '>=18'} 146 | cpu: [x64] 147 | os: [netbsd] 148 | 149 | '@esbuild/openbsd-arm64@0.25.1': 150 | resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} 151 | engines: {node: '>=18'} 152 | cpu: [arm64] 153 | os: [openbsd] 154 | 155 | '@esbuild/openbsd-x64@0.25.1': 156 | resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} 157 | engines: {node: '>=18'} 158 | cpu: [x64] 159 | os: [openbsd] 160 | 161 | '@esbuild/sunos-x64@0.25.1': 162 | resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} 163 | engines: {node: '>=18'} 164 | cpu: [x64] 165 | os: [sunos] 166 | 167 | '@esbuild/win32-arm64@0.25.1': 168 | resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} 169 | engines: {node: '>=18'} 170 | cpu: [arm64] 171 | os: [win32] 172 | 173 | '@esbuild/win32-ia32@0.25.1': 174 | resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} 175 | engines: {node: '>=18'} 176 | cpu: [ia32] 177 | os: [win32] 178 | 179 | '@esbuild/win32-x64@0.25.1': 180 | resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} 181 | engines: {node: '>=18'} 182 | cpu: [x64] 183 | os: [win32] 184 | 185 | '@eslint-community/eslint-utils@4.5.1': 186 | resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} 187 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 188 | peerDependencies: 189 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 190 | 191 | '@eslint-community/regexpp@4.12.1': 192 | resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} 193 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 194 | 195 | '@eslint/config-array@0.19.2': 196 | resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} 197 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 198 | 199 | '@eslint/config-helpers@0.2.0': 200 | resolution: {integrity: sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==} 201 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 202 | 203 | '@eslint/core@0.12.0': 204 | resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} 205 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 206 | 207 | '@eslint/eslintrc@3.3.1': 208 | resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} 209 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 210 | 211 | '@eslint/js@9.23.0': 212 | resolution: {integrity: sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==} 213 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 214 | 215 | '@eslint/object-schema@2.1.6': 216 | resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} 217 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 218 | 219 | '@eslint/plugin-kit@0.2.7': 220 | resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} 221 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 222 | 223 | '@humanfs/core@0.19.1': 224 | resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} 225 | engines: {node: '>=18.18.0'} 226 | 227 | '@humanfs/node@0.16.6': 228 | resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} 229 | engines: {node: '>=18.18.0'} 230 | 231 | '@humanwhocodes/module-importer@1.0.1': 232 | resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 233 | engines: {node: '>=12.22'} 234 | 235 | '@humanwhocodes/retry@0.3.1': 236 | resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} 237 | engines: {node: '>=18.18'} 238 | 239 | '@humanwhocodes/retry@0.4.2': 240 | resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} 241 | engines: {node: '>=18.18'} 242 | 243 | '@jridgewell/sourcemap-codec@1.5.0': 244 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 245 | 246 | '@nodelib/fs.scandir@2.1.5': 247 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 248 | engines: {node: '>= 8'} 249 | 250 | '@nodelib/fs.stat@2.0.5': 251 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 252 | engines: {node: '>= 8'} 253 | 254 | '@nodelib/fs.walk@1.2.8': 255 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 256 | engines: {node: '>= 8'} 257 | 258 | '@rollup/rollup-android-arm-eabi@4.37.0': 259 | resolution: {integrity: sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ==} 260 | cpu: [arm] 261 | os: [android] 262 | 263 | '@rollup/rollup-android-arm64@4.37.0': 264 | resolution: {integrity: sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA==} 265 | cpu: [arm64] 266 | os: [android] 267 | 268 | '@rollup/rollup-darwin-arm64@4.37.0': 269 | resolution: {integrity: sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA==} 270 | cpu: [arm64] 271 | os: [darwin] 272 | 273 | '@rollup/rollup-darwin-x64@4.37.0': 274 | resolution: {integrity: sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ==} 275 | cpu: [x64] 276 | os: [darwin] 277 | 278 | '@rollup/rollup-freebsd-arm64@4.37.0': 279 | resolution: {integrity: sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA==} 280 | cpu: [arm64] 281 | os: [freebsd] 282 | 283 | '@rollup/rollup-freebsd-x64@4.37.0': 284 | resolution: {integrity: sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA==} 285 | cpu: [x64] 286 | os: [freebsd] 287 | 288 | '@rollup/rollup-linux-arm-gnueabihf@4.37.0': 289 | resolution: {integrity: sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w==} 290 | cpu: [arm] 291 | os: [linux] 292 | 293 | '@rollup/rollup-linux-arm-musleabihf@4.37.0': 294 | resolution: {integrity: sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag==} 295 | cpu: [arm] 296 | os: [linux] 297 | 298 | '@rollup/rollup-linux-arm64-gnu@4.37.0': 299 | resolution: {integrity: sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA==} 300 | cpu: [arm64] 301 | os: [linux] 302 | 303 | '@rollup/rollup-linux-arm64-musl@4.37.0': 304 | resolution: {integrity: sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ==} 305 | cpu: [arm64] 306 | os: [linux] 307 | 308 | '@rollup/rollup-linux-loongarch64-gnu@4.37.0': 309 | resolution: {integrity: sha512-yCE0NnutTC/7IGUq/PUHmoeZbIwq3KRh02e9SfFh7Vmc1Z7atuJRYWhRME5fKgT8aS20mwi1RyChA23qSyRGpA==} 310 | cpu: [loong64] 311 | os: [linux] 312 | 313 | '@rollup/rollup-linux-powerpc64le-gnu@4.37.0': 314 | resolution: {integrity: sha512-NxcICptHk06E2Lh3a4Pu+2PEdZ6ahNHuK7o6Np9zcWkrBMuv21j10SQDJW3C9Yf/A/P7cutWoC/DptNLVsZ0VQ==} 315 | cpu: [ppc64] 316 | os: [linux] 317 | 318 | '@rollup/rollup-linux-riscv64-gnu@4.37.0': 319 | resolution: {integrity: sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw==} 320 | cpu: [riscv64] 321 | os: [linux] 322 | 323 | '@rollup/rollup-linux-riscv64-musl@4.37.0': 324 | resolution: {integrity: sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA==} 325 | cpu: [riscv64] 326 | os: [linux] 327 | 328 | '@rollup/rollup-linux-s390x-gnu@4.37.0': 329 | resolution: {integrity: sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A==} 330 | cpu: [s390x] 331 | os: [linux] 332 | 333 | '@rollup/rollup-linux-x64-gnu@4.37.0': 334 | resolution: {integrity: sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ==} 335 | cpu: [x64] 336 | os: [linux] 337 | 338 | '@rollup/rollup-linux-x64-musl@4.37.0': 339 | resolution: {integrity: sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w==} 340 | cpu: [x64] 341 | os: [linux] 342 | 343 | '@rollup/rollup-win32-arm64-msvc@4.37.0': 344 | resolution: {integrity: sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg==} 345 | cpu: [arm64] 346 | os: [win32] 347 | 348 | '@rollup/rollup-win32-ia32-msvc@4.37.0': 349 | resolution: {integrity: sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA==} 350 | cpu: [ia32] 351 | os: [win32] 352 | 353 | '@rollup/rollup-win32-x64-msvc@4.37.0': 354 | resolution: {integrity: sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA==} 355 | cpu: [x64] 356 | os: [win32] 357 | 358 | '@stylistic/eslint-plugin@4.2.0': 359 | resolution: {integrity: sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==} 360 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 361 | peerDependencies: 362 | eslint: '>=9.0.0' 363 | 364 | '@types/estree@1.0.6': 365 | resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 366 | 367 | '@types/json-schema@7.0.15': 368 | resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 369 | 370 | '@types/node@22.13.11': 371 | resolution: {integrity: sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==} 372 | 373 | '@typescript-eslint/eslint-plugin@8.27.0': 374 | resolution: {integrity: sha512-4henw4zkePi5p252c8ncBLzLce52SEUz2Ebj8faDnuUXz2UuHEONYcJ+G0oaCF+bYCWVZtrGzq3FD7YXetmnSA==} 375 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 376 | peerDependencies: 377 | '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 378 | eslint: ^8.57.0 || ^9.0.0 379 | typescript: '>=4.8.4 <5.9.0' 380 | 381 | '@typescript-eslint/parser@8.27.0': 382 | resolution: {integrity: sha512-XGwIabPallYipmcOk45DpsBSgLC64A0yvdAkrwEzwZ2viqGqRUJ8eEYoPz0CWnutgAFbNMPdsGGvzjSmcWVlEA==} 383 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 384 | peerDependencies: 385 | eslint: ^8.57.0 || ^9.0.0 386 | typescript: '>=4.8.4 <5.9.0' 387 | 388 | '@typescript-eslint/scope-manager@8.27.0': 389 | resolution: {integrity: sha512-8oI9GwPMQmBryaaxG1tOZdxXVeMDte6NyJA4i7/TWa4fBwgnAXYlIQP+uYOeqAaLJ2JRxlG9CAyL+C+YE9Xknw==} 390 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 391 | 392 | '@typescript-eslint/type-utils@8.27.0': 393 | resolution: {integrity: sha512-wVArTVcz1oJOIEJxui/nRhV0TXzD/zMSOYi/ggCfNq78EIszddXcJb7r4RCp/oBrjt8n9A0BSxRMKxHftpDxDA==} 394 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 395 | peerDependencies: 396 | eslint: ^8.57.0 || ^9.0.0 397 | typescript: '>=4.8.4 <5.9.0' 398 | 399 | '@typescript-eslint/types@8.27.0': 400 | resolution: {integrity: sha512-/6cp9yL72yUHAYq9g6DsAU+vVfvQmd1a8KyA81uvfDE21O2DwQ/qxlM4AR8TSdAu+kJLBDrEHKC5/W2/nxsY0A==} 401 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 402 | 403 | '@typescript-eslint/typescript-estree@8.27.0': 404 | resolution: {integrity: sha512-BnKq8cqPVoMw71O38a1tEb6iebEgGA80icSxW7g+kndx0o6ot6696HjG7NdgfuAVmVEtwXUr3L8R9ZuVjoQL6A==} 405 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 406 | peerDependencies: 407 | typescript: '>=4.8.4 <5.9.0' 408 | 409 | '@typescript-eslint/utils@8.27.0': 410 | resolution: {integrity: sha512-njkodcwH1yvmo31YWgRHNb/x1Xhhq4/m81PhtvmRngD8iHPehxffz1SNCO+kwaePhATC+kOa/ggmvPoPza5i0Q==} 411 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 412 | peerDependencies: 413 | eslint: ^8.57.0 || ^9.0.0 414 | typescript: '>=4.8.4 <5.9.0' 415 | 416 | '@typescript-eslint/visitor-keys@8.27.0': 417 | resolution: {integrity: sha512-WsXQwMkILJvffP6z4U3FYJPlbf/j07HIxmDjZpbNvBJkMfvwXj5ACRkkHwBDvLBbDbtX5TdU64/rcvKJ/vuInQ==} 418 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 419 | 420 | '@vitest/expect@3.0.9': 421 | resolution: {integrity: sha512-5eCqRItYgIML7NNVgJj6TVCmdzE7ZVgJhruW0ziSQV4V7PvLkDL1bBkBdcTs/VuIz0IxPb5da1IDSqc1TR9eig==} 422 | 423 | '@vitest/mocker@3.0.9': 424 | resolution: {integrity: sha512-ryERPIBOnvevAkTq+L1lD+DTFBRcjueL9lOUfXsLfwP92h4e+Heb+PjiqS3/OURWPtywfafK0kj++yDFjWUmrA==} 425 | peerDependencies: 426 | msw: ^2.4.9 427 | vite: ^5.0.0 || ^6.0.0 428 | peerDependenciesMeta: 429 | msw: 430 | optional: true 431 | vite: 432 | optional: true 433 | 434 | '@vitest/pretty-format@3.0.9': 435 | resolution: {integrity: sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA==} 436 | 437 | '@vitest/runner@3.0.9': 438 | resolution: {integrity: sha512-NX9oUXgF9HPfJSwl8tUZCMP1oGx2+Sf+ru6d05QjzQz4OwWg0psEzwY6VexP2tTHWdOkhKHUIZH+fS6nA7jfOw==} 439 | 440 | '@vitest/snapshot@3.0.9': 441 | resolution: {integrity: sha512-AiLUiuZ0FuA+/8i19mTYd+re5jqjEc2jZbgJ2up0VY0Ddyyxg/uUtBDpIFAy4uzKaQxOW8gMgBdAJJ2ydhu39A==} 442 | 443 | '@vitest/spy@3.0.9': 444 | resolution: {integrity: sha512-/CcK2UDl0aQ2wtkp3YVWldrpLRNCfVcIOFGlVGKO4R5eajsH393Z1yiXLVQ7vWsj26JOEjeZI0x5sm5P4OGUNQ==} 445 | 446 | '@vitest/utils@3.0.9': 447 | resolution: {integrity: sha512-ilHM5fHhZ89MCp5aAaM9uhfl1c2JdxVxl3McqsdVyVNN6JffnEen8UMCdRTzOhGXNQGo5GNL9QugHrz727Wnng==} 448 | 449 | acorn-jsx@5.3.2: 450 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 451 | peerDependencies: 452 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 453 | 454 | acorn@8.14.1: 455 | resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} 456 | engines: {node: '>=0.4.0'} 457 | hasBin: true 458 | 459 | ajv@6.12.6: 460 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 461 | 462 | ansi-styles@4.3.0: 463 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 464 | engines: {node: '>=8'} 465 | 466 | argparse@2.0.1: 467 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 468 | 469 | assertion-error@2.0.1: 470 | resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 471 | engines: {node: '>=12'} 472 | 473 | balanced-match@1.0.2: 474 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 475 | 476 | brace-expansion@1.1.11: 477 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 478 | 479 | brace-expansion@2.0.1: 480 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 481 | 482 | braces@3.0.3: 483 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 484 | engines: {node: '>=8'} 485 | 486 | cac@6.7.14: 487 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 488 | engines: {node: '>=8'} 489 | 490 | callsites@3.1.0: 491 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 492 | engines: {node: '>=6'} 493 | 494 | chai@5.2.0: 495 | resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} 496 | engines: {node: '>=12'} 497 | 498 | chalk@4.1.2: 499 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 500 | engines: {node: '>=10'} 501 | 502 | check-error@2.1.1: 503 | resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} 504 | engines: {node: '>= 16'} 505 | 506 | color-convert@2.0.1: 507 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 508 | engines: {node: '>=7.0.0'} 509 | 510 | color-name@1.1.4: 511 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 512 | 513 | concat-map@0.0.1: 514 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 515 | 516 | cross-spawn@7.0.6: 517 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 518 | engines: {node: '>= 8'} 519 | 520 | debug@4.4.0: 521 | resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 522 | engines: {node: '>=6.0'} 523 | peerDependencies: 524 | supports-color: '*' 525 | peerDependenciesMeta: 526 | supports-color: 527 | optional: true 528 | 529 | deep-eql@5.0.2: 530 | resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} 531 | engines: {node: '>=6'} 532 | 533 | deep-is@0.1.4: 534 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 535 | 536 | es-module-lexer@1.6.0: 537 | resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} 538 | 539 | esbuild@0.25.1: 540 | resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} 541 | engines: {node: '>=18'} 542 | hasBin: true 543 | 544 | escape-string-regexp@4.0.0: 545 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 546 | engines: {node: '>=10'} 547 | 548 | eslint-scope@8.3.0: 549 | resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} 550 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 551 | 552 | eslint-visitor-keys@3.4.3: 553 | resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 554 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 555 | 556 | eslint-visitor-keys@4.2.0: 557 | resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} 558 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 559 | 560 | eslint@9.23.0: 561 | resolution: {integrity: sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==} 562 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 563 | hasBin: true 564 | peerDependencies: 565 | jiti: '*' 566 | peerDependenciesMeta: 567 | jiti: 568 | optional: true 569 | 570 | espree@10.3.0: 571 | resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} 572 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 573 | 574 | esquery@1.6.0: 575 | resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} 576 | engines: {node: '>=0.10'} 577 | 578 | esrecurse@4.3.0: 579 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 580 | engines: {node: '>=4.0'} 581 | 582 | estraverse@5.3.0: 583 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 584 | engines: {node: '>=4.0'} 585 | 586 | estree-walker@3.0.3: 587 | resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 588 | 589 | esutils@2.0.3: 590 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 591 | engines: {node: '>=0.10.0'} 592 | 593 | expect-type@1.2.0: 594 | resolution: {integrity: sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==} 595 | engines: {node: '>=12.0.0'} 596 | 597 | fast-deep-equal@3.1.3: 598 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 599 | 600 | fast-glob@3.3.3: 601 | resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} 602 | engines: {node: '>=8.6.0'} 603 | 604 | fast-json-stable-stringify@2.1.0: 605 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 606 | 607 | fast-levenshtein@2.0.6: 608 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 609 | 610 | fastq@1.19.1: 611 | resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} 612 | 613 | file-entry-cache@8.0.0: 614 | resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 615 | engines: {node: '>=16.0.0'} 616 | 617 | fill-range@7.1.1: 618 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 619 | engines: {node: '>=8'} 620 | 621 | find-up@5.0.0: 622 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 623 | engines: {node: '>=10'} 624 | 625 | flat-cache@4.0.1: 626 | resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} 627 | engines: {node: '>=16'} 628 | 629 | flatted@3.3.3: 630 | resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} 631 | 632 | fsevents@2.3.3: 633 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 634 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 635 | os: [darwin] 636 | 637 | glob-parent@5.1.2: 638 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 639 | engines: {node: '>= 6'} 640 | 641 | glob-parent@6.0.2: 642 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 643 | engines: {node: '>=10.13.0'} 644 | 645 | globals@14.0.0: 646 | resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} 647 | engines: {node: '>=18'} 648 | 649 | graphemer@1.4.0: 650 | resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 651 | 652 | has-flag@4.0.0: 653 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 654 | engines: {node: '>=8'} 655 | 656 | ignore@5.3.2: 657 | resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 658 | engines: {node: '>= 4'} 659 | 660 | import-fresh@3.3.1: 661 | resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} 662 | engines: {node: '>=6'} 663 | 664 | imurmurhash@0.1.4: 665 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 666 | engines: {node: '>=0.8.19'} 667 | 668 | is-extglob@2.1.1: 669 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 670 | engines: {node: '>=0.10.0'} 671 | 672 | is-glob@4.0.3: 673 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 674 | engines: {node: '>=0.10.0'} 675 | 676 | is-number@7.0.0: 677 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 678 | engines: {node: '>=0.12.0'} 679 | 680 | isexe@2.0.0: 681 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 682 | 683 | js-yaml@4.1.0: 684 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 685 | hasBin: true 686 | 687 | json-buffer@3.0.1: 688 | resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 689 | 690 | json-schema-traverse@0.4.1: 691 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 692 | 693 | json-stable-stringify-without-jsonify@1.0.1: 694 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 695 | 696 | keyv@4.5.4: 697 | resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 698 | 699 | levn@0.4.1: 700 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 701 | engines: {node: '>= 0.8.0'} 702 | 703 | locate-path@6.0.0: 704 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 705 | engines: {node: '>=10'} 706 | 707 | lodash.merge@4.6.2: 708 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 709 | 710 | loupe@3.1.3: 711 | resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} 712 | 713 | magic-string@0.30.17: 714 | resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} 715 | 716 | merge2@1.4.1: 717 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 718 | engines: {node: '>= 8'} 719 | 720 | micromatch@4.0.8: 721 | resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 722 | engines: {node: '>=8.6'} 723 | 724 | minimatch@3.1.2: 725 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 726 | 727 | minimatch@9.0.5: 728 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 729 | engines: {node: '>=16 || 14 >=14.17'} 730 | 731 | ms@2.1.3: 732 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 733 | 734 | nanoid@3.3.11: 735 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 736 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 737 | hasBin: true 738 | 739 | natural-compare@1.4.0: 740 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 741 | 742 | optionator@0.9.4: 743 | resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} 744 | engines: {node: '>= 0.8.0'} 745 | 746 | p-limit@3.1.0: 747 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 748 | engines: {node: '>=10'} 749 | 750 | p-locate@5.0.0: 751 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 752 | engines: {node: '>=10'} 753 | 754 | parent-module@1.0.1: 755 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 756 | engines: {node: '>=6'} 757 | 758 | path-exists@4.0.0: 759 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 760 | engines: {node: '>=8'} 761 | 762 | path-key@3.1.1: 763 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 764 | engines: {node: '>=8'} 765 | 766 | pathe@2.0.3: 767 | resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 768 | 769 | pathval@2.0.0: 770 | resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} 771 | engines: {node: '>= 14.16'} 772 | 773 | picocolors@1.1.1: 774 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 775 | 776 | picomatch@2.3.1: 777 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 778 | engines: {node: '>=8.6'} 779 | 780 | picomatch@4.0.2: 781 | resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 782 | engines: {node: '>=12'} 783 | 784 | postcss@8.5.3: 785 | resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} 786 | engines: {node: ^10 || ^12 || >=14} 787 | 788 | prelude-ls@1.2.1: 789 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 790 | engines: {node: '>= 0.8.0'} 791 | 792 | punycode@2.3.1: 793 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 794 | engines: {node: '>=6'} 795 | 796 | queue-microtask@1.2.3: 797 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 798 | 799 | resolve-from@4.0.0: 800 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 801 | engines: {node: '>=4'} 802 | 803 | reusify@1.1.0: 804 | resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} 805 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 806 | 807 | rollup@4.37.0: 808 | resolution: {integrity: sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg==} 809 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 810 | hasBin: true 811 | 812 | run-parallel@1.2.0: 813 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 814 | 815 | semver@7.7.1: 816 | resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} 817 | engines: {node: '>=10'} 818 | hasBin: true 819 | 820 | shebang-command@2.0.0: 821 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 822 | engines: {node: '>=8'} 823 | 824 | shebang-regex@3.0.0: 825 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 826 | engines: {node: '>=8'} 827 | 828 | siginfo@2.0.0: 829 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 830 | 831 | source-map-js@1.2.1: 832 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 833 | engines: {node: '>=0.10.0'} 834 | 835 | stackback@0.0.2: 836 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 837 | 838 | std-env@3.8.1: 839 | resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==} 840 | 841 | strip-json-comments@3.1.1: 842 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 843 | engines: {node: '>=8'} 844 | 845 | supports-color@7.2.0: 846 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 847 | engines: {node: '>=8'} 848 | 849 | tinybench@2.9.0: 850 | resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} 851 | 852 | tinyexec@0.3.2: 853 | resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} 854 | 855 | tinypool@1.0.2: 856 | resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} 857 | engines: {node: ^18.0.0 || >=20.0.0} 858 | 859 | tinyrainbow@2.0.0: 860 | resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} 861 | engines: {node: '>=14.0.0'} 862 | 863 | tinyspy@3.0.2: 864 | resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} 865 | engines: {node: '>=14.0.0'} 866 | 867 | to-regex-range@5.0.1: 868 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 869 | engines: {node: '>=8.0'} 870 | 871 | ts-api-utils@2.1.0: 872 | resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} 873 | engines: {node: '>=18.12'} 874 | peerDependencies: 875 | typescript: '>=4.8.4' 876 | 877 | type-check@0.4.0: 878 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 879 | engines: {node: '>= 0.8.0'} 880 | 881 | typescript-eslint@8.27.0: 882 | resolution: {integrity: sha512-ZZ/8+Y0rRUMuW1gJaPtLWe4ryHbsPLzzibk5Sq+IFa2aOH1Vo0gPr1fbA6pOnzBke7zC2Da4w8AyCgxKXo3lqA==} 883 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 884 | peerDependencies: 885 | eslint: ^8.57.0 || ^9.0.0 886 | typescript: '>=4.8.4 <5.9.0' 887 | 888 | typescript@5.8.2: 889 | resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} 890 | engines: {node: '>=14.17'} 891 | hasBin: true 892 | 893 | undici-types@6.20.0: 894 | resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} 895 | 896 | uri-js@4.4.1: 897 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 898 | 899 | vite-node@3.0.9: 900 | resolution: {integrity: sha512-w3Gdx7jDcuT9cNn9jExXgOyKmf5UOTb6WMHz8LGAm54eS1Elf5OuBhCxl6zJxGhEeIkgsE1WbHuoL0mj/UXqXg==} 901 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 902 | hasBin: true 903 | 904 | vite@6.2.2: 905 | resolution: {integrity: sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==} 906 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 907 | hasBin: true 908 | peerDependencies: 909 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 910 | jiti: '>=1.21.0' 911 | less: '*' 912 | lightningcss: ^1.21.0 913 | sass: '*' 914 | sass-embedded: '*' 915 | stylus: '*' 916 | sugarss: '*' 917 | terser: ^5.16.0 918 | tsx: ^4.8.1 919 | yaml: ^2.4.2 920 | peerDependenciesMeta: 921 | '@types/node': 922 | optional: true 923 | jiti: 924 | optional: true 925 | less: 926 | optional: true 927 | lightningcss: 928 | optional: true 929 | sass: 930 | optional: true 931 | sass-embedded: 932 | optional: true 933 | stylus: 934 | optional: true 935 | sugarss: 936 | optional: true 937 | terser: 938 | optional: true 939 | tsx: 940 | optional: true 941 | yaml: 942 | optional: true 943 | 944 | vitest@3.0.9: 945 | resolution: {integrity: sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ==} 946 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 947 | hasBin: true 948 | peerDependencies: 949 | '@edge-runtime/vm': '*' 950 | '@types/debug': ^4.1.12 951 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 952 | '@vitest/browser': 3.0.9 953 | '@vitest/ui': 3.0.9 954 | happy-dom: '*' 955 | jsdom: '*' 956 | peerDependenciesMeta: 957 | '@edge-runtime/vm': 958 | optional: true 959 | '@types/debug': 960 | optional: true 961 | '@types/node': 962 | optional: true 963 | '@vitest/browser': 964 | optional: true 965 | '@vitest/ui': 966 | optional: true 967 | happy-dom: 968 | optional: true 969 | jsdom: 970 | optional: true 971 | 972 | which@2.0.2: 973 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 974 | engines: {node: '>= 8'} 975 | hasBin: true 976 | 977 | why-is-node-running@2.3.0: 978 | resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} 979 | engines: {node: '>=8'} 980 | hasBin: true 981 | 982 | word-wrap@1.2.5: 983 | resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} 984 | engines: {node: '>=0.10.0'} 985 | 986 | yocto-queue@0.1.0: 987 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 988 | engines: {node: '>=10'} 989 | 990 | snapshots: 991 | 992 | '@esbuild/aix-ppc64@0.25.1': 993 | optional: true 994 | 995 | '@esbuild/android-arm64@0.25.1': 996 | optional: true 997 | 998 | '@esbuild/android-arm@0.25.1': 999 | optional: true 1000 | 1001 | '@esbuild/android-x64@0.25.1': 1002 | optional: true 1003 | 1004 | '@esbuild/darwin-arm64@0.25.1': 1005 | optional: true 1006 | 1007 | '@esbuild/darwin-x64@0.25.1': 1008 | optional: true 1009 | 1010 | '@esbuild/freebsd-arm64@0.25.1': 1011 | optional: true 1012 | 1013 | '@esbuild/freebsd-x64@0.25.1': 1014 | optional: true 1015 | 1016 | '@esbuild/linux-arm64@0.25.1': 1017 | optional: true 1018 | 1019 | '@esbuild/linux-arm@0.25.1': 1020 | optional: true 1021 | 1022 | '@esbuild/linux-ia32@0.25.1': 1023 | optional: true 1024 | 1025 | '@esbuild/linux-loong64@0.25.1': 1026 | optional: true 1027 | 1028 | '@esbuild/linux-mips64el@0.25.1': 1029 | optional: true 1030 | 1031 | '@esbuild/linux-ppc64@0.25.1': 1032 | optional: true 1033 | 1034 | '@esbuild/linux-riscv64@0.25.1': 1035 | optional: true 1036 | 1037 | '@esbuild/linux-s390x@0.25.1': 1038 | optional: true 1039 | 1040 | '@esbuild/linux-x64@0.25.1': 1041 | optional: true 1042 | 1043 | '@esbuild/netbsd-arm64@0.25.1': 1044 | optional: true 1045 | 1046 | '@esbuild/netbsd-x64@0.25.1': 1047 | optional: true 1048 | 1049 | '@esbuild/openbsd-arm64@0.25.1': 1050 | optional: true 1051 | 1052 | '@esbuild/openbsd-x64@0.25.1': 1053 | optional: true 1054 | 1055 | '@esbuild/sunos-x64@0.25.1': 1056 | optional: true 1057 | 1058 | '@esbuild/win32-arm64@0.25.1': 1059 | optional: true 1060 | 1061 | '@esbuild/win32-ia32@0.25.1': 1062 | optional: true 1063 | 1064 | '@esbuild/win32-x64@0.25.1': 1065 | optional: true 1066 | 1067 | '@eslint-community/eslint-utils@4.5.1(eslint@9.23.0)': 1068 | dependencies: 1069 | eslint: 9.23.0 1070 | eslint-visitor-keys: 3.4.3 1071 | 1072 | '@eslint-community/regexpp@4.12.1': {} 1073 | 1074 | '@eslint/config-array@0.19.2': 1075 | dependencies: 1076 | '@eslint/object-schema': 2.1.6 1077 | debug: 4.4.0 1078 | minimatch: 3.1.2 1079 | transitivePeerDependencies: 1080 | - supports-color 1081 | 1082 | '@eslint/config-helpers@0.2.0': {} 1083 | 1084 | '@eslint/core@0.12.0': 1085 | dependencies: 1086 | '@types/json-schema': 7.0.15 1087 | 1088 | '@eslint/eslintrc@3.3.1': 1089 | dependencies: 1090 | ajv: 6.12.6 1091 | debug: 4.4.0 1092 | espree: 10.3.0 1093 | globals: 14.0.0 1094 | ignore: 5.3.2 1095 | import-fresh: 3.3.1 1096 | js-yaml: 4.1.0 1097 | minimatch: 3.1.2 1098 | strip-json-comments: 3.1.1 1099 | transitivePeerDependencies: 1100 | - supports-color 1101 | 1102 | '@eslint/js@9.23.0': {} 1103 | 1104 | '@eslint/object-schema@2.1.6': {} 1105 | 1106 | '@eslint/plugin-kit@0.2.7': 1107 | dependencies: 1108 | '@eslint/core': 0.12.0 1109 | levn: 0.4.1 1110 | 1111 | '@humanfs/core@0.19.1': {} 1112 | 1113 | '@humanfs/node@0.16.6': 1114 | dependencies: 1115 | '@humanfs/core': 0.19.1 1116 | '@humanwhocodes/retry': 0.3.1 1117 | 1118 | '@humanwhocodes/module-importer@1.0.1': {} 1119 | 1120 | '@humanwhocodes/retry@0.3.1': {} 1121 | 1122 | '@humanwhocodes/retry@0.4.2': {} 1123 | 1124 | '@jridgewell/sourcemap-codec@1.5.0': {} 1125 | 1126 | '@nodelib/fs.scandir@2.1.5': 1127 | dependencies: 1128 | '@nodelib/fs.stat': 2.0.5 1129 | run-parallel: 1.2.0 1130 | 1131 | '@nodelib/fs.stat@2.0.5': {} 1132 | 1133 | '@nodelib/fs.walk@1.2.8': 1134 | dependencies: 1135 | '@nodelib/fs.scandir': 2.1.5 1136 | fastq: 1.19.1 1137 | 1138 | '@rollup/rollup-android-arm-eabi@4.37.0': 1139 | optional: true 1140 | 1141 | '@rollup/rollup-android-arm64@4.37.0': 1142 | optional: true 1143 | 1144 | '@rollup/rollup-darwin-arm64@4.37.0': 1145 | optional: true 1146 | 1147 | '@rollup/rollup-darwin-x64@4.37.0': 1148 | optional: true 1149 | 1150 | '@rollup/rollup-freebsd-arm64@4.37.0': 1151 | optional: true 1152 | 1153 | '@rollup/rollup-freebsd-x64@4.37.0': 1154 | optional: true 1155 | 1156 | '@rollup/rollup-linux-arm-gnueabihf@4.37.0': 1157 | optional: true 1158 | 1159 | '@rollup/rollup-linux-arm-musleabihf@4.37.0': 1160 | optional: true 1161 | 1162 | '@rollup/rollup-linux-arm64-gnu@4.37.0': 1163 | optional: true 1164 | 1165 | '@rollup/rollup-linux-arm64-musl@4.37.0': 1166 | optional: true 1167 | 1168 | '@rollup/rollup-linux-loongarch64-gnu@4.37.0': 1169 | optional: true 1170 | 1171 | '@rollup/rollup-linux-powerpc64le-gnu@4.37.0': 1172 | optional: true 1173 | 1174 | '@rollup/rollup-linux-riscv64-gnu@4.37.0': 1175 | optional: true 1176 | 1177 | '@rollup/rollup-linux-riscv64-musl@4.37.0': 1178 | optional: true 1179 | 1180 | '@rollup/rollup-linux-s390x-gnu@4.37.0': 1181 | optional: true 1182 | 1183 | '@rollup/rollup-linux-x64-gnu@4.37.0': 1184 | optional: true 1185 | 1186 | '@rollup/rollup-linux-x64-musl@4.37.0': 1187 | optional: true 1188 | 1189 | '@rollup/rollup-win32-arm64-msvc@4.37.0': 1190 | optional: true 1191 | 1192 | '@rollup/rollup-win32-ia32-msvc@4.37.0': 1193 | optional: true 1194 | 1195 | '@rollup/rollup-win32-x64-msvc@4.37.0': 1196 | optional: true 1197 | 1198 | '@stylistic/eslint-plugin@4.2.0(eslint@9.23.0)(typescript@5.8.2)': 1199 | dependencies: 1200 | '@typescript-eslint/utils': 8.27.0(eslint@9.23.0)(typescript@5.8.2) 1201 | eslint: 9.23.0 1202 | eslint-visitor-keys: 4.2.0 1203 | espree: 10.3.0 1204 | estraverse: 5.3.0 1205 | picomatch: 4.0.2 1206 | transitivePeerDependencies: 1207 | - supports-color 1208 | - typescript 1209 | 1210 | '@types/estree@1.0.6': {} 1211 | 1212 | '@types/json-schema@7.0.15': {} 1213 | 1214 | '@types/node@22.13.11': 1215 | dependencies: 1216 | undici-types: 6.20.0 1217 | 1218 | '@typescript-eslint/eslint-plugin@8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.23.0)(typescript@5.8.2))(eslint@9.23.0)(typescript@5.8.2)': 1219 | dependencies: 1220 | '@eslint-community/regexpp': 4.12.1 1221 | '@typescript-eslint/parser': 8.27.0(eslint@9.23.0)(typescript@5.8.2) 1222 | '@typescript-eslint/scope-manager': 8.27.0 1223 | '@typescript-eslint/type-utils': 8.27.0(eslint@9.23.0)(typescript@5.8.2) 1224 | '@typescript-eslint/utils': 8.27.0(eslint@9.23.0)(typescript@5.8.2) 1225 | '@typescript-eslint/visitor-keys': 8.27.0 1226 | eslint: 9.23.0 1227 | graphemer: 1.4.0 1228 | ignore: 5.3.2 1229 | natural-compare: 1.4.0 1230 | ts-api-utils: 2.1.0(typescript@5.8.2) 1231 | typescript: 5.8.2 1232 | transitivePeerDependencies: 1233 | - supports-color 1234 | 1235 | '@typescript-eslint/parser@8.27.0(eslint@9.23.0)(typescript@5.8.2)': 1236 | dependencies: 1237 | '@typescript-eslint/scope-manager': 8.27.0 1238 | '@typescript-eslint/types': 8.27.0 1239 | '@typescript-eslint/typescript-estree': 8.27.0(typescript@5.8.2) 1240 | '@typescript-eslint/visitor-keys': 8.27.0 1241 | debug: 4.4.0 1242 | eslint: 9.23.0 1243 | typescript: 5.8.2 1244 | transitivePeerDependencies: 1245 | - supports-color 1246 | 1247 | '@typescript-eslint/scope-manager@8.27.0': 1248 | dependencies: 1249 | '@typescript-eslint/types': 8.27.0 1250 | '@typescript-eslint/visitor-keys': 8.27.0 1251 | 1252 | '@typescript-eslint/type-utils@8.27.0(eslint@9.23.0)(typescript@5.8.2)': 1253 | dependencies: 1254 | '@typescript-eslint/typescript-estree': 8.27.0(typescript@5.8.2) 1255 | '@typescript-eslint/utils': 8.27.0(eslint@9.23.0)(typescript@5.8.2) 1256 | debug: 4.4.0 1257 | eslint: 9.23.0 1258 | ts-api-utils: 2.1.0(typescript@5.8.2) 1259 | typescript: 5.8.2 1260 | transitivePeerDependencies: 1261 | - supports-color 1262 | 1263 | '@typescript-eslint/types@8.27.0': {} 1264 | 1265 | '@typescript-eslint/typescript-estree@8.27.0(typescript@5.8.2)': 1266 | dependencies: 1267 | '@typescript-eslint/types': 8.27.0 1268 | '@typescript-eslint/visitor-keys': 8.27.0 1269 | debug: 4.4.0 1270 | fast-glob: 3.3.3 1271 | is-glob: 4.0.3 1272 | minimatch: 9.0.5 1273 | semver: 7.7.1 1274 | ts-api-utils: 2.1.0(typescript@5.8.2) 1275 | typescript: 5.8.2 1276 | transitivePeerDependencies: 1277 | - supports-color 1278 | 1279 | '@typescript-eslint/utils@8.27.0(eslint@9.23.0)(typescript@5.8.2)': 1280 | dependencies: 1281 | '@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0) 1282 | '@typescript-eslint/scope-manager': 8.27.0 1283 | '@typescript-eslint/types': 8.27.0 1284 | '@typescript-eslint/typescript-estree': 8.27.0(typescript@5.8.2) 1285 | eslint: 9.23.0 1286 | typescript: 5.8.2 1287 | transitivePeerDependencies: 1288 | - supports-color 1289 | 1290 | '@typescript-eslint/visitor-keys@8.27.0': 1291 | dependencies: 1292 | '@typescript-eslint/types': 8.27.0 1293 | eslint-visitor-keys: 4.2.0 1294 | 1295 | '@vitest/expect@3.0.9': 1296 | dependencies: 1297 | '@vitest/spy': 3.0.9 1298 | '@vitest/utils': 3.0.9 1299 | chai: 5.2.0 1300 | tinyrainbow: 2.0.0 1301 | 1302 | '@vitest/mocker@3.0.9(vite@6.2.2(@types/node@22.13.11))': 1303 | dependencies: 1304 | '@vitest/spy': 3.0.9 1305 | estree-walker: 3.0.3 1306 | magic-string: 0.30.17 1307 | optionalDependencies: 1308 | vite: 6.2.2(@types/node@22.13.11) 1309 | 1310 | '@vitest/pretty-format@3.0.9': 1311 | dependencies: 1312 | tinyrainbow: 2.0.0 1313 | 1314 | '@vitest/runner@3.0.9': 1315 | dependencies: 1316 | '@vitest/utils': 3.0.9 1317 | pathe: 2.0.3 1318 | 1319 | '@vitest/snapshot@3.0.9': 1320 | dependencies: 1321 | '@vitest/pretty-format': 3.0.9 1322 | magic-string: 0.30.17 1323 | pathe: 2.0.3 1324 | 1325 | '@vitest/spy@3.0.9': 1326 | dependencies: 1327 | tinyspy: 3.0.2 1328 | 1329 | '@vitest/utils@3.0.9': 1330 | dependencies: 1331 | '@vitest/pretty-format': 3.0.9 1332 | loupe: 3.1.3 1333 | tinyrainbow: 2.0.0 1334 | 1335 | acorn-jsx@5.3.2(acorn@8.14.1): 1336 | dependencies: 1337 | acorn: 8.14.1 1338 | 1339 | acorn@8.14.1: {} 1340 | 1341 | ajv@6.12.6: 1342 | dependencies: 1343 | fast-deep-equal: 3.1.3 1344 | fast-json-stable-stringify: 2.1.0 1345 | json-schema-traverse: 0.4.1 1346 | uri-js: 4.4.1 1347 | 1348 | ansi-styles@4.3.0: 1349 | dependencies: 1350 | color-convert: 2.0.1 1351 | 1352 | argparse@2.0.1: {} 1353 | 1354 | assertion-error@2.0.1: {} 1355 | 1356 | balanced-match@1.0.2: {} 1357 | 1358 | brace-expansion@1.1.11: 1359 | dependencies: 1360 | balanced-match: 1.0.2 1361 | concat-map: 0.0.1 1362 | 1363 | brace-expansion@2.0.1: 1364 | dependencies: 1365 | balanced-match: 1.0.2 1366 | 1367 | braces@3.0.3: 1368 | dependencies: 1369 | fill-range: 7.1.1 1370 | 1371 | cac@6.7.14: {} 1372 | 1373 | callsites@3.1.0: {} 1374 | 1375 | chai@5.2.0: 1376 | dependencies: 1377 | assertion-error: 2.0.1 1378 | check-error: 2.1.1 1379 | deep-eql: 5.0.2 1380 | loupe: 3.1.3 1381 | pathval: 2.0.0 1382 | 1383 | chalk@4.1.2: 1384 | dependencies: 1385 | ansi-styles: 4.3.0 1386 | supports-color: 7.2.0 1387 | 1388 | check-error@2.1.1: {} 1389 | 1390 | color-convert@2.0.1: 1391 | dependencies: 1392 | color-name: 1.1.4 1393 | 1394 | color-name@1.1.4: {} 1395 | 1396 | concat-map@0.0.1: {} 1397 | 1398 | cross-spawn@7.0.6: 1399 | dependencies: 1400 | path-key: 3.1.1 1401 | shebang-command: 2.0.0 1402 | which: 2.0.2 1403 | 1404 | debug@4.4.0: 1405 | dependencies: 1406 | ms: 2.1.3 1407 | 1408 | deep-eql@5.0.2: {} 1409 | 1410 | deep-is@0.1.4: {} 1411 | 1412 | es-module-lexer@1.6.0: {} 1413 | 1414 | esbuild@0.25.1: 1415 | optionalDependencies: 1416 | '@esbuild/aix-ppc64': 0.25.1 1417 | '@esbuild/android-arm': 0.25.1 1418 | '@esbuild/android-arm64': 0.25.1 1419 | '@esbuild/android-x64': 0.25.1 1420 | '@esbuild/darwin-arm64': 0.25.1 1421 | '@esbuild/darwin-x64': 0.25.1 1422 | '@esbuild/freebsd-arm64': 0.25.1 1423 | '@esbuild/freebsd-x64': 0.25.1 1424 | '@esbuild/linux-arm': 0.25.1 1425 | '@esbuild/linux-arm64': 0.25.1 1426 | '@esbuild/linux-ia32': 0.25.1 1427 | '@esbuild/linux-loong64': 0.25.1 1428 | '@esbuild/linux-mips64el': 0.25.1 1429 | '@esbuild/linux-ppc64': 0.25.1 1430 | '@esbuild/linux-riscv64': 0.25.1 1431 | '@esbuild/linux-s390x': 0.25.1 1432 | '@esbuild/linux-x64': 0.25.1 1433 | '@esbuild/netbsd-arm64': 0.25.1 1434 | '@esbuild/netbsd-x64': 0.25.1 1435 | '@esbuild/openbsd-arm64': 0.25.1 1436 | '@esbuild/openbsd-x64': 0.25.1 1437 | '@esbuild/sunos-x64': 0.25.1 1438 | '@esbuild/win32-arm64': 0.25.1 1439 | '@esbuild/win32-ia32': 0.25.1 1440 | '@esbuild/win32-x64': 0.25.1 1441 | 1442 | escape-string-regexp@4.0.0: {} 1443 | 1444 | eslint-scope@8.3.0: 1445 | dependencies: 1446 | esrecurse: 4.3.0 1447 | estraverse: 5.3.0 1448 | 1449 | eslint-visitor-keys@3.4.3: {} 1450 | 1451 | eslint-visitor-keys@4.2.0: {} 1452 | 1453 | eslint@9.23.0: 1454 | dependencies: 1455 | '@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0) 1456 | '@eslint-community/regexpp': 4.12.1 1457 | '@eslint/config-array': 0.19.2 1458 | '@eslint/config-helpers': 0.2.0 1459 | '@eslint/core': 0.12.0 1460 | '@eslint/eslintrc': 3.3.1 1461 | '@eslint/js': 9.23.0 1462 | '@eslint/plugin-kit': 0.2.7 1463 | '@humanfs/node': 0.16.6 1464 | '@humanwhocodes/module-importer': 1.0.1 1465 | '@humanwhocodes/retry': 0.4.2 1466 | '@types/estree': 1.0.6 1467 | '@types/json-schema': 7.0.15 1468 | ajv: 6.12.6 1469 | chalk: 4.1.2 1470 | cross-spawn: 7.0.6 1471 | debug: 4.4.0 1472 | escape-string-regexp: 4.0.0 1473 | eslint-scope: 8.3.0 1474 | eslint-visitor-keys: 4.2.0 1475 | espree: 10.3.0 1476 | esquery: 1.6.0 1477 | esutils: 2.0.3 1478 | fast-deep-equal: 3.1.3 1479 | file-entry-cache: 8.0.0 1480 | find-up: 5.0.0 1481 | glob-parent: 6.0.2 1482 | ignore: 5.3.2 1483 | imurmurhash: 0.1.4 1484 | is-glob: 4.0.3 1485 | json-stable-stringify-without-jsonify: 1.0.1 1486 | lodash.merge: 4.6.2 1487 | minimatch: 3.1.2 1488 | natural-compare: 1.4.0 1489 | optionator: 0.9.4 1490 | transitivePeerDependencies: 1491 | - supports-color 1492 | 1493 | espree@10.3.0: 1494 | dependencies: 1495 | acorn: 8.14.1 1496 | acorn-jsx: 5.3.2(acorn@8.14.1) 1497 | eslint-visitor-keys: 4.2.0 1498 | 1499 | esquery@1.6.0: 1500 | dependencies: 1501 | estraverse: 5.3.0 1502 | 1503 | esrecurse@4.3.0: 1504 | dependencies: 1505 | estraverse: 5.3.0 1506 | 1507 | estraverse@5.3.0: {} 1508 | 1509 | estree-walker@3.0.3: 1510 | dependencies: 1511 | '@types/estree': 1.0.6 1512 | 1513 | esutils@2.0.3: {} 1514 | 1515 | expect-type@1.2.0: {} 1516 | 1517 | fast-deep-equal@3.1.3: {} 1518 | 1519 | fast-glob@3.3.3: 1520 | dependencies: 1521 | '@nodelib/fs.stat': 2.0.5 1522 | '@nodelib/fs.walk': 1.2.8 1523 | glob-parent: 5.1.2 1524 | merge2: 1.4.1 1525 | micromatch: 4.0.8 1526 | 1527 | fast-json-stable-stringify@2.1.0: {} 1528 | 1529 | fast-levenshtein@2.0.6: {} 1530 | 1531 | fastq@1.19.1: 1532 | dependencies: 1533 | reusify: 1.1.0 1534 | 1535 | file-entry-cache@8.0.0: 1536 | dependencies: 1537 | flat-cache: 4.0.1 1538 | 1539 | fill-range@7.1.1: 1540 | dependencies: 1541 | to-regex-range: 5.0.1 1542 | 1543 | find-up@5.0.0: 1544 | dependencies: 1545 | locate-path: 6.0.0 1546 | path-exists: 4.0.0 1547 | 1548 | flat-cache@4.0.1: 1549 | dependencies: 1550 | flatted: 3.3.3 1551 | keyv: 4.5.4 1552 | 1553 | flatted@3.3.3: {} 1554 | 1555 | fsevents@2.3.3: 1556 | optional: true 1557 | 1558 | glob-parent@5.1.2: 1559 | dependencies: 1560 | is-glob: 4.0.3 1561 | 1562 | glob-parent@6.0.2: 1563 | dependencies: 1564 | is-glob: 4.0.3 1565 | 1566 | globals@14.0.0: {} 1567 | 1568 | graphemer@1.4.0: {} 1569 | 1570 | has-flag@4.0.0: {} 1571 | 1572 | ignore@5.3.2: {} 1573 | 1574 | import-fresh@3.3.1: 1575 | dependencies: 1576 | parent-module: 1.0.1 1577 | resolve-from: 4.0.0 1578 | 1579 | imurmurhash@0.1.4: {} 1580 | 1581 | is-extglob@2.1.1: {} 1582 | 1583 | is-glob@4.0.3: 1584 | dependencies: 1585 | is-extglob: 2.1.1 1586 | 1587 | is-number@7.0.0: {} 1588 | 1589 | isexe@2.0.0: {} 1590 | 1591 | js-yaml@4.1.0: 1592 | dependencies: 1593 | argparse: 2.0.1 1594 | 1595 | json-buffer@3.0.1: {} 1596 | 1597 | json-schema-traverse@0.4.1: {} 1598 | 1599 | json-stable-stringify-without-jsonify@1.0.1: {} 1600 | 1601 | keyv@4.5.4: 1602 | dependencies: 1603 | json-buffer: 3.0.1 1604 | 1605 | levn@0.4.1: 1606 | dependencies: 1607 | prelude-ls: 1.2.1 1608 | type-check: 0.4.0 1609 | 1610 | locate-path@6.0.0: 1611 | dependencies: 1612 | p-locate: 5.0.0 1613 | 1614 | lodash.merge@4.6.2: {} 1615 | 1616 | loupe@3.1.3: {} 1617 | 1618 | magic-string@0.30.17: 1619 | dependencies: 1620 | '@jridgewell/sourcemap-codec': 1.5.0 1621 | 1622 | merge2@1.4.1: {} 1623 | 1624 | micromatch@4.0.8: 1625 | dependencies: 1626 | braces: 3.0.3 1627 | picomatch: 2.3.1 1628 | 1629 | minimatch@3.1.2: 1630 | dependencies: 1631 | brace-expansion: 1.1.11 1632 | 1633 | minimatch@9.0.5: 1634 | dependencies: 1635 | brace-expansion: 2.0.1 1636 | 1637 | ms@2.1.3: {} 1638 | 1639 | nanoid@3.3.11: {} 1640 | 1641 | natural-compare@1.4.0: {} 1642 | 1643 | optionator@0.9.4: 1644 | dependencies: 1645 | deep-is: 0.1.4 1646 | fast-levenshtein: 2.0.6 1647 | levn: 0.4.1 1648 | prelude-ls: 1.2.1 1649 | type-check: 0.4.0 1650 | word-wrap: 1.2.5 1651 | 1652 | p-limit@3.1.0: 1653 | dependencies: 1654 | yocto-queue: 0.1.0 1655 | 1656 | p-locate@5.0.0: 1657 | dependencies: 1658 | p-limit: 3.1.0 1659 | 1660 | parent-module@1.0.1: 1661 | dependencies: 1662 | callsites: 3.1.0 1663 | 1664 | path-exists@4.0.0: {} 1665 | 1666 | path-key@3.1.1: {} 1667 | 1668 | pathe@2.0.3: {} 1669 | 1670 | pathval@2.0.0: {} 1671 | 1672 | picocolors@1.1.1: {} 1673 | 1674 | picomatch@2.3.1: {} 1675 | 1676 | picomatch@4.0.2: {} 1677 | 1678 | postcss@8.5.3: 1679 | dependencies: 1680 | nanoid: 3.3.11 1681 | picocolors: 1.1.1 1682 | source-map-js: 1.2.1 1683 | 1684 | prelude-ls@1.2.1: {} 1685 | 1686 | punycode@2.3.1: {} 1687 | 1688 | queue-microtask@1.2.3: {} 1689 | 1690 | resolve-from@4.0.0: {} 1691 | 1692 | reusify@1.1.0: {} 1693 | 1694 | rollup@4.37.0: 1695 | dependencies: 1696 | '@types/estree': 1.0.6 1697 | optionalDependencies: 1698 | '@rollup/rollup-android-arm-eabi': 4.37.0 1699 | '@rollup/rollup-android-arm64': 4.37.0 1700 | '@rollup/rollup-darwin-arm64': 4.37.0 1701 | '@rollup/rollup-darwin-x64': 4.37.0 1702 | '@rollup/rollup-freebsd-arm64': 4.37.0 1703 | '@rollup/rollup-freebsd-x64': 4.37.0 1704 | '@rollup/rollup-linux-arm-gnueabihf': 4.37.0 1705 | '@rollup/rollup-linux-arm-musleabihf': 4.37.0 1706 | '@rollup/rollup-linux-arm64-gnu': 4.37.0 1707 | '@rollup/rollup-linux-arm64-musl': 4.37.0 1708 | '@rollup/rollup-linux-loongarch64-gnu': 4.37.0 1709 | '@rollup/rollup-linux-powerpc64le-gnu': 4.37.0 1710 | '@rollup/rollup-linux-riscv64-gnu': 4.37.0 1711 | '@rollup/rollup-linux-riscv64-musl': 4.37.0 1712 | '@rollup/rollup-linux-s390x-gnu': 4.37.0 1713 | '@rollup/rollup-linux-x64-gnu': 4.37.0 1714 | '@rollup/rollup-linux-x64-musl': 4.37.0 1715 | '@rollup/rollup-win32-arm64-msvc': 4.37.0 1716 | '@rollup/rollup-win32-ia32-msvc': 4.37.0 1717 | '@rollup/rollup-win32-x64-msvc': 4.37.0 1718 | fsevents: 2.3.3 1719 | 1720 | run-parallel@1.2.0: 1721 | dependencies: 1722 | queue-microtask: 1.2.3 1723 | 1724 | semver@7.7.1: {} 1725 | 1726 | shebang-command@2.0.0: 1727 | dependencies: 1728 | shebang-regex: 3.0.0 1729 | 1730 | shebang-regex@3.0.0: {} 1731 | 1732 | siginfo@2.0.0: {} 1733 | 1734 | source-map-js@1.2.1: {} 1735 | 1736 | stackback@0.0.2: {} 1737 | 1738 | std-env@3.8.1: {} 1739 | 1740 | strip-json-comments@3.1.1: {} 1741 | 1742 | supports-color@7.2.0: 1743 | dependencies: 1744 | has-flag: 4.0.0 1745 | 1746 | tinybench@2.9.0: {} 1747 | 1748 | tinyexec@0.3.2: {} 1749 | 1750 | tinypool@1.0.2: {} 1751 | 1752 | tinyrainbow@2.0.0: {} 1753 | 1754 | tinyspy@3.0.2: {} 1755 | 1756 | to-regex-range@5.0.1: 1757 | dependencies: 1758 | is-number: 7.0.0 1759 | 1760 | ts-api-utils@2.1.0(typescript@5.8.2): 1761 | dependencies: 1762 | typescript: 5.8.2 1763 | 1764 | type-check@0.4.0: 1765 | dependencies: 1766 | prelude-ls: 1.2.1 1767 | 1768 | typescript-eslint@8.27.0(eslint@9.23.0)(typescript@5.8.2): 1769 | dependencies: 1770 | '@typescript-eslint/eslint-plugin': 8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.23.0)(typescript@5.8.2))(eslint@9.23.0)(typescript@5.8.2) 1771 | '@typescript-eslint/parser': 8.27.0(eslint@9.23.0)(typescript@5.8.2) 1772 | '@typescript-eslint/utils': 8.27.0(eslint@9.23.0)(typescript@5.8.2) 1773 | eslint: 9.23.0 1774 | typescript: 5.8.2 1775 | transitivePeerDependencies: 1776 | - supports-color 1777 | 1778 | typescript@5.8.2: {} 1779 | 1780 | undici-types@6.20.0: {} 1781 | 1782 | uri-js@4.4.1: 1783 | dependencies: 1784 | punycode: 2.3.1 1785 | 1786 | vite-node@3.0.9(@types/node@22.13.11): 1787 | dependencies: 1788 | cac: 6.7.14 1789 | debug: 4.4.0 1790 | es-module-lexer: 1.6.0 1791 | pathe: 2.0.3 1792 | vite: 6.2.2(@types/node@22.13.11) 1793 | transitivePeerDependencies: 1794 | - '@types/node' 1795 | - jiti 1796 | - less 1797 | - lightningcss 1798 | - sass 1799 | - sass-embedded 1800 | - stylus 1801 | - sugarss 1802 | - supports-color 1803 | - terser 1804 | - tsx 1805 | - yaml 1806 | 1807 | vite@6.2.2(@types/node@22.13.11): 1808 | dependencies: 1809 | esbuild: 0.25.1 1810 | postcss: 8.5.3 1811 | rollup: 4.37.0 1812 | optionalDependencies: 1813 | '@types/node': 22.13.11 1814 | fsevents: 2.3.3 1815 | 1816 | vitest@3.0.9(@types/node@22.13.11): 1817 | dependencies: 1818 | '@vitest/expect': 3.0.9 1819 | '@vitest/mocker': 3.0.9(vite@6.2.2(@types/node@22.13.11)) 1820 | '@vitest/pretty-format': 3.0.9 1821 | '@vitest/runner': 3.0.9 1822 | '@vitest/snapshot': 3.0.9 1823 | '@vitest/spy': 3.0.9 1824 | '@vitest/utils': 3.0.9 1825 | chai: 5.2.0 1826 | debug: 4.4.0 1827 | expect-type: 1.2.0 1828 | magic-string: 0.30.17 1829 | pathe: 2.0.3 1830 | std-env: 3.8.1 1831 | tinybench: 2.9.0 1832 | tinyexec: 0.3.2 1833 | tinypool: 1.0.2 1834 | tinyrainbow: 2.0.0 1835 | vite: 6.2.2(@types/node@22.13.11) 1836 | vite-node: 3.0.9(@types/node@22.13.11) 1837 | why-is-node-running: 2.3.0 1838 | optionalDependencies: 1839 | '@types/node': 22.13.11 1840 | transitivePeerDependencies: 1841 | - jiti 1842 | - less 1843 | - lightningcss 1844 | - msw 1845 | - sass 1846 | - sass-embedded 1847 | - stylus 1848 | - sugarss 1849 | - supports-color 1850 | - terser 1851 | - tsx 1852 | - yaml 1853 | 1854 | which@2.0.2: 1855 | dependencies: 1856 | isexe: 2.0.0 1857 | 1858 | why-is-node-running@2.3.0: 1859 | dependencies: 1860 | siginfo: 2.0.0 1861 | stackback: 0.0.2 1862 | 1863 | word-wrap@1.2.5: {} 1864 | 1865 | yocto-queue@0.1.0: {} 1866 | -------------------------------------------------------------------------------- /scripts/build.mjs: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from 'node:path' 2 | import { existsSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs' 3 | import { fileURLToPath } from 'node:url' 4 | 5 | const dir = resolve(dirname(fileURLToPath(import.meta.url)), '..') 6 | const build = resolve(dir, 'build') 7 | const dist = resolve(dir, 'dist') 8 | const index = resolve(dist, 'index.d.ts') 9 | const pkg = JSON.parse(readFileSync(resolve(dir, 'package.json'), 'utf8')) 10 | 11 | const cleanup = () => existsSync(build) && rmSync(build, { recursive: true }) 12 | 13 | cleanup() 14 | 15 | renameSync(dist, build) 16 | 17 | renameSync(resolve(build, 'src'), dist) 18 | 19 | writeFileSync(index, readFileSync(index, 'utf8').replace('x.y.z', pkg.version)) 20 | 21 | writeFileSync(resolve(dist, 'index.mjs'), `const pkg = '@bedard/chess-types - ${pkg.version}';\nexport { pkg };\n`) 22 | 23 | cleanup() 24 | -------------------------------------------------------------------------------- /src/base.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @stylistic/no-multi-spaces */ 2 | 3 | /** File */ 4 | export type File = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' 5 | 6 | /** Rank */ 7 | export type Rank = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 8 | 9 | /** Black or white */ 10 | export type Color = 'b' | 'w' 11 | 12 | /** Enemy color */ 13 | export type EnemyColor = T extends 'b' ? 'w' : 'b' 14 | 15 | /** Black pieces */ 16 | export type BlackPiece = 'r' | 'n' | 'b' | 'q' | 'k' | 'p' 17 | 18 | /** White pieces */ 19 | export type WhitePiece = 'R' | 'N' | 'B' | 'Q' | 'K' | 'P' 20 | 21 | /** All pieces */ 22 | export type Piece = BlackPiece | WhitePiece 23 | 24 | /** Promotion pieces */ 25 | export type PromotionPiece = Exclude 26 | 27 | /** Unoccupied piece */ 28 | export type Unoccupied = ' ' 29 | 30 | /** Possible value at a position */ 31 | export type MaybePiece = Piece | Unoccupied 32 | 33 | /** Pieces by color */ 34 | export type PieceColor = T extends BlackPiece ? 'b' : 'w' 35 | 36 | /** Friendly pieces */ 37 | export type FriendlyPiece = T extends 'b' ? BlackPiece : WhitePiece 38 | 39 | /** Castling rights by color */ 40 | export type Castling = { 41 | K: boolean 42 | Q: boolean 43 | k: boolean 44 | q: boolean 45 | } 46 | 47 | /** Parsed game state */ 48 | export type ParsedGame = { 49 | board: [ 50 | MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, 51 | MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, 52 | MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, 53 | MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, 54 | MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, 55 | MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, 56 | MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, 57 | MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, MaybePiece, 58 | ] 59 | turn: Color 60 | castling: Castling 61 | ep: Index | null 62 | halfmove: number 63 | fullmove: number 64 | } 65 | 66 | /** Parsed move */ 67 | export type ParsedMove = { 68 | castle: 'K' | 'Q' | 'k' | 'q' | false 69 | from: Index 70 | to: Index 71 | promotion: PromotionPiece | '' 72 | } 73 | 74 | /** All possible board indices */ 75 | export type Indices = [ 76 | 0, 1, 2, 3, 4, 5, 6, 7, 77 | 8, 9, 10, 11, 12, 13, 14, 15, 78 | 16, 17, 18, 19, 20, 21, 22, 23, 79 | 24, 25, 26, 27, 28, 29, 30, 31, 80 | 32, 33, 34, 35, 36, 37, 38, 39, 81 | 40, 41, 42, 43, 44, 45, 46, 47, 82 | 48, 49, 50, 51, 52, 53, 54, 55, 83 | 56, 57, 58, 59, 60, 61, 62, 63, 84 | ] 85 | 86 | export type Index = Indices[number] 87 | 88 | /** Direction index of a 3x3 matrix */ 89 | export type DirectionIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 90 | 91 | /** Sorted list of positions by fen index */ 92 | export type Positions = [ 93 | 'a8', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8', 94 | 'a7', 'b7', 'c7', 'd7', 'e7', 'f7', 'g7', 'h7', 95 | 'a6', 'b6', 'c6', 'd6', 'e6', 'f6', 'g6', 'h6', 96 | 'a5', 'b5', 'c5', 'd5', 'e5', 'f5', 'g5', 'h5', 97 | 'a4', 'b4', 'c4', 'd4', 'e4', 'f4', 'g4', 'h4', 98 | 'a3', 'b3', 'c3', 'd3', 'e3', 'f3', 'g3', 'h3', 99 | 'a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2', 100 | 'a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1', 101 | ] 102 | 103 | /** Union of all positions */ 104 | export type Position = Positions[Index] 105 | 106 | /** Lookup table to get a position's fen index */ 107 | export type PositionIndex = { 108 | a8: 0, b8: 1, c8: 2, d8: 3, e8: 4, f8: 5, g8: 6, h8: 7, 109 | a7: 8, b7: 9, c7: 10, d7: 11, e7: 12, f7: 13, g7: 14, h7: 15, 110 | a6: 16, b6: 17, c6: 18, d6: 19, e6: 20, f6: 21, g6: 22, h6: 23, 111 | a5: 24, b5: 25, c5: 26, d5: 27, e5: 28, f5: 29, g5: 30, h5: 31, 112 | a4: 32, b4: 33, c4: 34, d4: 35, e4: 36, f4: 37, g4: 38, h4: 39, 113 | a3: 40, b3: 41, c3: 42, d3: 43, e3: 44, f3: 45, g3: 46, h3: 47, 114 | a2: 48, b2: 49, c2: 50, d2: 51, e2: 52, f2: 53, g2: 54, h2: 55, 115 | a1: 56, b1: 57, c1: 58, d1: 59, e1: 60, f1: 61, g1: 62, h1: 63, 116 | } 117 | 118 | /** 119 | * This graph represents the positions on a chessboard and their relationship to 120 | * one another. Each index coorelates to a position on the board, and each child 121 | * value represents the index of a neighboring position relative to a 3x3 grid, 122 | * with the original position at the center. -1 represents off the board. 123 | * 124 | * For example, to find the position directly above d4... 125 | * 126 | * [ 127 | * ... 128 | * // d4 is located at index 35, so skip to that row in the graph and 129 | * // take the 2nd value from that 3x3 matrix 130 | * [ ↓ 131 | * 26, 27, 28, 132 | * 34, 35, 36, 133 | * 42, 43, 44 134 | * ] 135 | * ] 136 | * 137 | */ 138 | export type Graph = [ 139 | [-1, -1, -1, -1, 0, 1, -1, 8, 9], 140 | [-1, -1, -1, 0, 1, 2, 8, 9, 10], 141 | [-1, -1, -1, 1, 2, 3, 9, 10, 11], 142 | [-1, -1, -1, 2, 3, 4, 10, 11, 12], 143 | [-1, -1, -1, 3, 4, 5, 11, 12, 13], 144 | [-1, -1, -1, 4, 5, 6, 12, 13, 14], 145 | [-1, -1, -1, 5, 6, 7, 13, 14, 15], 146 | [-1, -1, -1, 6, 7, -1, 14, 15, -1], 147 | [-1, 0, 1, -1, 8, 9, -1, 16, 17], 148 | [ 0, 1, 2, 8, 9, 10, 16, 17, 18], 149 | [ 1, 2, 3, 9, 10, 11, 17, 18, 19], 150 | [ 2, 3, 4, 10, 11, 12, 18, 19, 20], 151 | [ 3, 4, 5, 11, 12, 13, 19, 20, 21], 152 | [ 4, 5, 6, 12, 13, 14, 20, 21, 22], 153 | [ 5, 6, 7, 13, 14, 15, 21, 22, 23], 154 | [ 6, 7, -1, 14, 15, -1, 22, 23, -1], 155 | [-1, 8, 9, -1, 16, 17, -1, 24, 25], 156 | [ 8, 9, 10, 16, 17, 18, 24, 25, 26], 157 | [ 9, 10, 11, 17, 18, 19, 25, 26, 27], 158 | [10, 11, 12, 18, 19, 20, 26, 27, 28], 159 | [11, 12, 13, 19, 20, 21, 27, 28, 29], 160 | [12, 13, 14, 20, 21, 22, 28, 29, 30], 161 | [13, 14, 15, 21, 22, 23, 29, 30, 31], 162 | [14, 15, -1, 22, 23, -1, 30, 31, -1], 163 | [-1, 16, 17, -1, 24, 25, -1, 32, 33], 164 | [16, 17, 18, 24, 25, 26, 32, 33, 34], 165 | [17, 18, 19, 25, 26, 27, 33, 34, 35], 166 | [18, 19, 20, 26, 27, 28, 34, 35, 36], 167 | [19, 20, 21, 27, 28, 29, 35, 36, 37], 168 | [20, 21, 22, 28, 29, 30, 36, 37, 38], 169 | [21, 22, 23, 29, 30, 31, 37, 38, 39], 170 | [22, 23, -1, 30, 31, -1, 38, 39, -1], 171 | [-1, 24, 25, -1, 32, 33, -1, 40, 41], 172 | [24, 25, 26, 32, 33, 34, 40, 41, 42], 173 | [25, 26, 27, 33, 34, 35, 41, 42, 43], 174 | [26, 27, 28, 34, 35, 36, 42, 43, 44], 175 | [27, 28, 29, 35, 36, 37, 43, 44, 45], 176 | [28, 29, 30, 36, 37, 38, 44, 45, 46], 177 | [29, 30, 31, 37, 38, 39, 45, 46, 47], 178 | [30, 31, -1, 38, 39, -1, 46, 47, -1], 179 | [-1, 32, 33, -1, 40, 41, -1, 48, 49], 180 | [32, 33, 34, 40, 41, 42, 48, 49, 50], 181 | [33, 34, 35, 41, 42, 43, 49, 50, 51], 182 | [34, 35, 36, 42, 43, 44, 50, 51, 52], 183 | [35, 36, 37, 43, 44, 45, 51, 52, 53], 184 | [36, 37, 38, 44, 45, 46, 52, 53, 54], 185 | [37, 38, 39, 45, 46, 47, 53, 54, 55], 186 | [38, 39, -1, 46, 47, -1, 54, 55, -1], 187 | [-1, 40, 41, -1, 48, 49, -1, 56, 57], 188 | [40, 41, 42, 48, 49, 50, 56, 57, 58], 189 | [41, 42, 43, 49, 50, 51, 57, 58, 59], 190 | [42, 43, 44, 50, 51, 52, 58, 59, 60], 191 | [43, 44, 45, 51, 52, 53, 59, 60, 61], 192 | [44, 45, 46, 52, 53, 54, 60, 61, 62], 193 | [45, 46, 47, 53, 54, 55, 61, 62, 63], 194 | [46, 47, -1, 54, 55, -1, 62, 63, -1], 195 | [-1, 48, 49, -1, 56, 57, -1, -1, -1], 196 | [48, 49, 50, 56, 57, 58, -1, -1, -1], 197 | [49, 50, 51, 57, 58, 59, -1, -1, -1], 198 | [50, 51, 52, 58, 59, 60, -1, -1, -1], 199 | [51, 52, 53, 59, 60, 61, -1, -1, -1], 200 | [52, 53, 54, 60, 61, 62, -1, -1, -1], 201 | [53, 54, 55, 61, 62, 63, -1, -1, -1], 202 | [54, 55, -1, 62, 63, -1, -1, -1, -1], 203 | ] 204 | -------------------------------------------------------------------------------- /src/board.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Color, 3 | DirectionIndex, 4 | FriendlyPiece, 5 | Graph, 6 | Index, 7 | ParsedGame, 8 | PositionIndex, 9 | Positions, 10 | Unoccupied, 11 | } from './base' 12 | 13 | import type { ToPositions } from '@/notation' 14 | 15 | /** 16 | * Walk along the board, stopping short of friendly pieces 17 | **/ 18 | export type Walk< 19 | Game extends ParsedGame, 20 | Friendly extends Color, 21 | From extends Index, 22 | Direction extends Exclude, // <- cannot walk to center index 23 | Acc extends Index[] = [], 24 | To = Graph[From][Direction] 25 | > = To extends Index 26 | ? Game['board'][To] extends Unoccupied 27 | ? Walk 28 | : Game['board'][To] extends FriendlyPiece 29 | ? Acc 30 | : [...Acc, To] 31 | : Acc 32 | 33 | /** 34 | * Walk and cast results to positions 35 | */ 36 | export type WalkPositions< 37 | Game extends ParsedGame, 38 | Friendly extends Color, 39 | From extends Positions[Index], 40 | Direction extends Exclude, 41 | Path = Walk 42 | > = Path extends Index[] 43 | ? ToPositions 44 | : never 45 | -------------------------------------------------------------------------------- /src/evaluate.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Index, 3 | Indices, 4 | ParsedGame, 5 | ParsedMove, 6 | Piece, 7 | } from './base' 8 | 9 | import type { 10 | _ApplyMoveUnsafe, 11 | _CurrentMoves, 12 | } from './game' 13 | 14 | import { 15 | FormatSan, 16 | } from './notation' 17 | 18 | import type { 19 | IsGreater, 20 | Sum 21 | } from './utils' 22 | 23 | /** 24 | * Get the next move 25 | */ 26 | export type NextMove = Game['turn'] extends 'w' 27 | ? _Max<_Layer> extends infer Node extends _Node 28 | ? FormatSan 29 | : never 30 | : _Min<_Layer> extends infer Node extends _Node 31 | ? FormatSan 32 | : never 33 | 34 | type _Node = { 35 | move: ParsedMove 36 | score: number 37 | } 38 | 39 | type _Layer< 40 | Game extends ParsedGame, 41 | Moves extends ParsedMove[] = _CurrentMoves, 42 | Acc extends _Node[] = [] 43 | > = Moves extends [infer Head extends ParsedMove, ...infer Tail extends ParsedMove[]] 44 | ? _Layer> 47 | }]> 48 | : Acc 49 | 50 | type _Max< 51 | Nodes extends _Node[], 52 | Best extends _Node | false = false 53 | > = Nodes extends [infer Head extends _Node, ...infer Tail extends _Node[]] 54 | ? Best extends infer B extends _Node 55 | ? IsGreater extends true 56 | ? _Max 57 | : _Max 58 | : _Max 59 | : Best 60 | 61 | type _Min< 62 | Nodes extends _Node[], 63 | Best extends _Node | false = false 64 | > = Nodes extends [infer Head extends _Node, ...infer Tail extends _Node[]] 65 | ? Best extends infer B extends _Node 66 | ? IsGreater extends true 67 | ? _Min 68 | : _Min 69 | : _Min 70 | : Best 71 | 72 | /** 73 | * Evaluate game state 74 | */ 75 | export type Evaluate< 76 | Game extends ParsedGame, 77 | Value extends number = 0, 78 | Remaining extends Index[] = Indices 79 | > = Remaining extends [infer Head extends Index, ...infer Tail extends Index[]] 80 | ? Game['board'][Head] extends infer P extends Piece 81 | ? Evaluate, Tail> 82 | : Evaluate 83 | : Value 84 | 85 | type _PieceValue = { 86 | 'k': -90 87 | 'q': -9 88 | 'r': -5 89 | 'b': -3 90 | 'n': -3 91 | 'p': -1 92 | 'P': 1 93 | 'N': 3 94 | 'B': 3 95 | 'R': 5 96 | 'Q': 9 97 | 'K': 90 98 | } 99 | -------------------------------------------------------------------------------- /src/game.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @stylistic/no-multi-spaces */ 2 | import type { 3 | Color, 4 | EnemyColor, 5 | FriendlyPiece, 6 | Graph, 7 | Index, 8 | Indices, 9 | MaybePiece, 10 | ParsedGame, 11 | ParsedMove, 12 | Piece, 13 | PieceColor, 14 | Position, 15 | PositionIndex, 16 | Positions, 17 | Unoccupied, 18 | } from './base' 19 | 20 | import type { 21 | ParseFen, 22 | ParseSan, 23 | ParseSans, 24 | ToPositions, 25 | ToSans, 26 | } from './notation' 27 | 28 | import type { Increment } from './utils' 29 | 30 | import type { BishopMoves } from './pieces/bishop' 31 | import type { KingMoves } from './pieces/king' 32 | import type { KnightMoves } from './pieces/knight' 33 | import type { PawnMoves } from './pieces/pawn' 34 | import type { QueenMoves } from './pieces/queen' 35 | import type { RookMoves } from './pieces/rook' 36 | 37 | /** 38 | * Apply moves to a game, regardless of turn or legality 39 | */ 40 | export type ApplyMoveUnsafe< 41 | Game extends ParsedGame, 42 | San extends string 43 | > = _ApplyMoveUnsafe> 44 | 45 | export type _ApplyMoveUnsafe< 46 | Game extends ParsedGame, 47 | Move extends ParsedMove, 48 | > = _ApplyMovePiece extends infer P extends Piece 49 | ? { 50 | board: _UpdateBoard 51 | turn: EnemyColor> 52 | halfmove: _CountHalfmove 53 | fullmove: _CountFullmove 54 | castling: _UpdateCastling 55 | ep: _UpdateEnPassant 56 | } 57 | : never 58 | 59 | type _ApplyMovePiece< 60 | Game extends ParsedGame, 61 | Move extends ParsedMove, 62 | > = Move['castle'] extends 'K' 63 | ? 'K' 64 | : Move['castle'] extends 'k' 65 | ? 'k' 66 | : Move['castle'] extends 'Q' 67 | ? 'Q' 68 | : Move['castle'] extends 'q' 69 | ? 'q' 70 | : Game['board'][Move['from']] extends infer P extends Piece 71 | ? P 72 | : false 73 | 74 | type _CountHalfmove< 75 | Game extends ParsedGame, 76 | P extends Piece, 77 | Move extends ParsedMove, 78 | > = P extends 'p' | 'P' 79 | ? 0 80 | : Move['castle'] extends false 81 | ? Game['board'][Move['to']] extends Piece 82 | ? 0 83 | : Increment 84 | : Increment 85 | 86 | type _CountFullmove< 87 | Game extends ParsedGame, 88 | P extends MaybePiece, 89 | > = P extends Piece 90 | ? PieceColor

extends 'b' 91 | ? Increment 92 | : Game['fullmove'] 93 | : Game['fullmove'] 94 | 95 | type _UpdateBoard< 96 | Game extends ParsedGame, 97 | P extends Piece, 98 | Move extends ParsedMove, 99 | > = Move['castle'] extends 'K' ? _ReplaceValues 105 | : Move['castle'] extends 'Q' ? _ReplaceValues 111 | : Move['castle'] extends 'k' ? _ReplaceValues 117 | : Move['castle'] extends 'q' ? _ReplaceValues 123 | : Game['ep'] extends Move['to'] 124 | ? P extends 'p' 125 | ? Graph[Move['to']][1] extends Index 126 | ? _ReplaceValues 131 | : never 132 | : P extends 'P' 133 | ? Graph[Move['to']][7] extends Index 134 | ? _ReplaceValues 139 | : never 140 | : _ReplaceValues 144 | : _ReplaceValues 148 | 149 | type _UpdateCastling< 150 | Game extends ParsedGame, 151 | Move extends ParsedMove, 152 | > = { 153 | K: Move['castle'] extends 'K' | 'Q' 154 | ? false 155 | : Game['castling']['K'], 156 | Q: Move['castle'] extends 'K' | 'Q' 157 | ? false 158 | : Game['castling']['Q'], 159 | k: Move['castle'] extends 'k' | 'q' 160 | ? false 161 | : Game['castling']['k'], 162 | q: Move['castle'] extends 'k' | 'q' 163 | ? false 164 | : Game['castling']['q'], 165 | } 166 | 167 | type _UpdateEnPassant< 168 | P extends Piece, 169 | Move extends ParsedMove, 170 | > = 171 | P extends 'p' 172 | ? Move['from'] extends 8 ? Move['to'] extends 24 ? 16 : null 173 | : Move['from'] extends 9 ? Move['to'] extends 25 ? 17 : null 174 | : Move['from'] extends 10 ? Move['to'] extends 26 ? 18 : null 175 | : Move['from'] extends 11 ? Move['to'] extends 27 ? 19 : null 176 | : Move['from'] extends 12 ? Move['to'] extends 28 ? 20 : null 177 | : Move['from'] extends 13 ? Move['to'] extends 29 ? 21 : null 178 | : Move['from'] extends 14 ? Move['to'] extends 30 ? 22 : null 179 | : Move['from'] extends 15 ? Move['to'] extends 31 ? 23 : null 180 | : null 181 | : P extends 'P' 182 | ? Move['from'] extends 48 ? Move['to'] extends 32 ? 40 : null 183 | : Move['from'] extends 49 ? Move['to'] extends 33 ? 41 : null 184 | : Move['from'] extends 50 ? Move['to'] extends 34 ? 42 : null 185 | : Move['from'] extends 51 ? Move['to'] extends 35 ? 43 : null 186 | : Move['from'] extends 52 ? Move['to'] extends 36 ? 44 : null 187 | : Move['from'] extends 53 ? Move['to'] extends 37 ? 45 : null 188 | : Move['from'] extends 54 ? Move['to'] extends 38 ? 46 : null 189 | : Move['from'] extends 55 ? Move['to'] extends 39 ? 47 : null 190 | : null 191 | : null 192 | 193 | export type _ReplaceValues< 194 | T extends MaybePiece[], 195 | U extends [Index, MaybePiece][] = [] 196 | > = U extends [infer Head extends [Index, MaybePiece], ...infer Tail extends [Index, MaybePiece][]] 197 | ? _ReplaceValues<_ReplaceAt, Tail> 198 | : T 199 | 200 | export type _ReplaceAt< 201 | T extends MaybePiece[], 202 | U extends Index, 203 | V extends MaybePiece 204 | > = [ 205 | U extends 0 ? V : T[0], U extends 1 ? V : T[1], U extends 2 ? V : T[2], U extends 3 ? V : T[3], U extends 4 ? V : T[4], U extends 5 ? V : T[5], U extends 6 ? V : T[6], U extends 7 ? V : T[7], 206 | U extends 8 ? V : T[8], U extends 9 ? V : T[9], U extends 10 ? V : T[10], U extends 11 ? V : T[11], U extends 12 ? V : T[12], U extends 13 ? V : T[13], U extends 14 ? V : T[14], U extends 15 ? V : T[15], 207 | U extends 16 ? V : T[16], U extends 17 ? V : T[17], U extends 18 ? V : T[18], U extends 19 ? V : T[19], U extends 20 ? V : T[20], U extends 21 ? V : T[21], U extends 22 ? V : T[22], U extends 23 ? V : T[23], 208 | U extends 24 ? V : T[24], U extends 25 ? V : T[25], U extends 26 ? V : T[26], U extends 27 ? V : T[27], U extends 28 ? V : T[28], U extends 29 ? V : T[29], U extends 30 ? V : T[30], U extends 31 ? V : T[31], 209 | U extends 32 ? V : T[32], U extends 33 ? V : T[33], U extends 34 ? V : T[34], U extends 35 ? V : T[35], U extends 36 ? V : T[36], U extends 37 ? V : T[37], U extends 38 ? V : T[38], U extends 39 ? V : T[39], 210 | U extends 40 ? V : T[40], U extends 41 ? V : T[41], U extends 42 ? V : T[42], U extends 43 ? V : T[43], U extends 44 ? V : T[44], U extends 45 ? V : T[45], U extends 46 ? V : T[46], U extends 47 ? V : T[47], 211 | U extends 48 ? V : T[48], U extends 49 ? V : T[49], U extends 50 ? V : T[50], U extends 51 ? V : T[51], U extends 52 ? V : T[52], U extends 53 ? V : T[53], U extends 54 ? V : T[54], U extends 55 ? V : T[55], 212 | U extends 56 ? V : T[56], U extends 57 ? V : T[57], U extends 58 ? V : T[58], U extends 59 ? V : T[59], U extends 60 ? V : T[60], U extends 61 ? V : T[61], U extends 62 ? V : T[62], U extends 63 ? V : T[63], 213 | ] 214 | 215 | /** 216 | * Get current legal moves 217 | */ 218 | export type CurrentMoves< 219 | Game extends ParsedGame, 220 | Turn extends Color = Game['turn'], 221 | > = ToSans<_CurrentMoves> 222 | 223 | export type _CurrentMoves< 224 | Game extends ParsedGame, 225 | Turn extends Color = Game['turn'], 226 | > = _CurrentMovesUnsafe extends infer UnsafeMoves extends ParsedMove[] 227 | ? _FilterIllegalMoves 228 | : [] 229 | 230 | type _FilterIllegalMoves< 231 | Game extends ParsedGame, 232 | Turn extends Color, 233 | Moves extends ParsedMove[], 234 | Acc extends ParsedMove[] = [] 235 | > = Moves extends [infer Head extends ParsedMove, ...infer Tail extends ParsedMove[]] 236 | ? _IsLegal extends true 237 | ? _FilterIllegalMoves 238 | : _FilterIllegalMoves 239 | : Acc 240 | 241 | /** 242 | * Get all possible moves, even ones that result in self-check 243 | **/ 244 | export type CurrentMovesUnsafe< 245 | Game extends ParsedGame, 246 | Turn extends Color = Game['turn'], 247 | From extends Index[] = _OccupiedBy, 248 | Acc extends ParsedMove[] = [] 249 | > = _CurrentMovesUnsafe extends infer M extends ParsedMove[] 250 | ? ToSans 251 | : never 252 | 253 | type _CurrentMovesUnsafe< 254 | Game extends ParsedGame, 255 | Turn extends Color = Game['turn'], 256 | From extends Index[] = _OccupiedBy, 257 | Acc extends ParsedMove[] = [] 258 | > = From extends [infer Head extends Index, ...infer Tail extends Index[]] 259 | ? Game['board'][Head] extends infer CurrentPiece extends Piece 260 | ? CurrentPiece extends 'p' | 'P' ? _CurrentMovesUnsafe, Head>]> 261 | : CurrentPiece extends 'n' | 'N' ? _CurrentMovesUnsafe, Head>]> 262 | : CurrentPiece extends 'b' | 'B' ? _CurrentMovesUnsafe, Head>]> 263 | : CurrentPiece extends 'r' | 'R' ? _CurrentMovesUnsafe, Head>]> 264 | : CurrentPiece extends 'q' | 'Q' ? _CurrentMovesUnsafe, Head>]> 265 | : CurrentPiece extends 'k' | 'K' ? _CurrentMovesUnsafe, Head>]> 266 | : never 267 | : never 268 | : Acc 269 | 270 | /** 271 | * Create a hover-display of the chessboard 272 | **/ 273 | export type Chessboard< 274 | T extends ParsedGame, 275 | Flipped extends boolean = false 276 | > = 277 | Flipped extends true 278 | ? { 279 | 1: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 280 | 2: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 281 | 3: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 282 | 4: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 283 | 5: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 284 | 6: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 285 | 7: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 286 | 8: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 287 | } 288 | : { 289 | 8: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 290 | 7: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 291 | 6: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 292 | 5: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 293 | 4: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 294 | 3: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 295 | 2: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 296 | 1: ` ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} ${_Square} `, 297 | } 298 | 299 | type _B = '*' 300 | 301 | type _W = '-' 302 | 303 | type _Square< 304 | Game extends ParsedGame, 305 | I extends Index, 306 | Char extends _B | _W 307 | > = Game['board'][I] extends infer P extends Piece ? P : Char 308 | 309 | /** 310 | * Find king by color 311 | */ 312 | export type FindKing< 313 | Game extends ParsedGame, 314 | C extends Color, 315 | King extends Piece = C extends 'w' ? 'K' : 'k', 316 | Remaining extends Index[] = Indices 317 | > = _FindKing extends infer K extends Index 318 | ? Positions[K] 319 | : false 320 | 321 | type _FindKing< 322 | Game extends ParsedGame, 323 | C extends Color, 324 | King extends Piece = C extends 'w' ? 'K' : 'k', 325 | Remaining extends Index[] = Indices 326 | > = Remaining extends [infer Head extends Index, ...infer Tail extends Index[]] 327 | ? Game['board'][Head] extends King 328 | ? Head 329 | : _FindKing 330 | : false 331 | 332 | /** 333 | * Test if a king is threatened 334 | **/ 335 | export type IsCheck< 336 | Game extends ParsedGame, 337 | KingColor extends Color = Game['turn'], 338 | > = _FindKing extends infer KingIndex extends Index 339 | ? _IsThreatened> 340 | : false 341 | 342 | /** 343 | * Test if a move is legal 344 | */ 345 | export type IsLegal< 346 | Game extends ParsedGame, 347 | San extends string, 348 | > = _IsLegal> 349 | 350 | type _IsLegal< 351 | Game extends ParsedGame, 352 | Move extends ParsedMove, 353 | > = Move['castle'] extends 'K' ? _HasBoardValues extends true 359 | ? Game['castling']['K'] extends true 360 | ? _CurrentMovesUnsafe extends infer UnsafeMoves extends ParsedMove[] 361 | ? _ContainsReachable extends false 362 | ? true 363 | : false // a castling square is threatened 364 | : never 365 | : false // no castling rights 366 | : false // invalid setup for castling 367 | : Move['castle'] extends 'Q' ? _HasBoardValues extends true 374 | ? Game['castling']['Q'] extends true 375 | ? _CurrentMovesUnsafe extends infer UnsafeMoves extends ParsedMove[] 376 | ? _ContainsReachable extends false 377 | ? true 378 | : false // a castling square is threatened 379 | : never 380 | : false // no castling rights 381 | : false // invalid setup for castling 382 | : Move['castle'] extends 'k' ? _HasBoardValues extends true 388 | ? Game['castling']['k'] extends true 389 | ? _CurrentMovesUnsafe extends infer UnsafeMoves extends ParsedMove[] 390 | ? _ContainsReachable extends false 391 | ? true 392 | : false // a castling square is threatened 393 | : never 394 | : false // no castling rights 395 | : false // invalid setup for castling 396 | : Move['castle'] extends 'q' ? _HasBoardValues extends true 403 | ? Game['castling']['q'] extends true 404 | ? _CurrentMovesUnsafe extends infer UnsafeMoves extends ParsedMove[] 405 | ? _ContainsReachable extends false 406 | ? true 407 | : false // a castling square is threatened 408 | : never 409 | : false // no castling rights 410 | : false // invalid setup for castling 411 | : Game['board'][Move['from']] extends infer P extends Piece 412 | ? PieceColor

extends infer C extends Color 413 | ? _CurrentMovesUnsafe extends infer UnsafeMoves extends ParsedMove[] 414 | ? _ContainsMove extends true 415 | ? _ExposesKing extends false 416 | ? true // move is legal 417 | : false // prohibit self-check 418 | : false // move is not among the unsafe moves 419 | : never // we're checking one position 420 | : never // pieces must have a color 421 | : false // no piece at from position 422 | 423 | type _ContainsMove< 424 | T extends ParsedMove, 425 | Acc extends ParsedMove[], 426 | > = Acc extends [infer Head extends ParsedMove, ...infer Tail extends ParsedMove[]] 427 | ? Head extends T 428 | ? true 429 | : _ContainsMove 430 | : false 431 | 432 | type _ContainsReachable< 433 | Game extends ParsedGame, 434 | Moves extends ParsedMove[], 435 | T extends Index[] 436 | > = T extends [infer Head extends Index, ...infer Tail extends Index[]] 437 | ? _IsReachable extends true 438 | ? true 439 | : _ContainsReachable 440 | : false 441 | 442 | type _HasBoardValues< 443 | Game extends ParsedGame, 444 | T extends [Index, MaybePiece][] = [] 445 | > = T extends [infer Head extends [Index, MaybePiece], ...infer Tail extends [Index, MaybePiece][]] 446 | ? Game['board'][Head[0]] extends Head[1] 447 | ? _HasBoardValues 448 | : false 449 | : true 450 | 451 | type _ExposesKing< 452 | Game extends ParsedGame, 453 | Move extends ParsedMove, 454 | > = Game['board'][Move['from']] extends infer P extends Piece 455 | ? IsCheck<_ApplyMoveUnsafe, PieceColor

> 456 | : false 457 | 458 | /** 459 | * Test if position is threatened by a hostile color 460 | **/ 461 | export type IsThreatened< 462 | Game extends ParsedGame, 463 | Target extends Position, 464 | HostileColor extends Color = EnemyColor, 465 | Acc extends Index[] = _OccupiedBy 466 | > = _IsThreatened 467 | 468 | type _IsThreatened< 469 | Game extends ParsedGame, 470 | TargetIndex extends Index, 471 | HostileColor extends Color = EnemyColor, 472 | Acc extends Index[] = _OccupiedBy 473 | > = Acc extends [infer PositionHead extends Index, ...infer PositionTail extends Index[]] 474 | ? Game['board'][PositionHead] extends FriendlyPiece 475 | ? _CurrentMovesUnsafe extends infer PositionMoves extends ParsedMove[] 476 | ? _IsReachable extends true 477 | ? true 478 | : _IsThreatened 479 | : false 480 | : unknown 481 | : false 482 | 483 | type _IsReachable< 484 | Target extends Index, 485 | Moves extends ParsedMove[] 486 | > = Moves extends [infer Head extends ParsedMove, ...infer Tail extends ParsedMove[]] 487 | ? Head['to'] extends Target 488 | ? true 489 | : _IsReachable 490 | : false 491 | 492 | /** 493 | * Get all positions occupied by a color 494 | **/ 495 | export type OccupiedBy< 496 | Game extends ParsedGame, 497 | C extends Color, 498 | Remaining extends Index[] = Indices, 499 | Acc extends Index[] = [] 500 | > = ToPositions<_OccupiedBy> 501 | 502 | type _OccupiedBy< 503 | Game extends ParsedGame, 504 | C extends Color, 505 | Remaining extends Index[] = Indices, 506 | Acc extends Index[] = [] 507 | > = Remaining extends [infer Head extends Index, ...infer Tail extends Index[]] 508 | ? Game['board'][Head] extends FriendlyPiece 509 | ? _OccupiedBy 510 | : _OccupiedBy 511 | : Acc 512 | 513 | /** 514 | * Play a game 515 | */ 516 | export type NewGame< 517 | Moves extends string[] = [] 518 | > = Play<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', Moves> 519 | 520 | export type Play< 521 | Game extends ParsedGame | string, 522 | Moves extends string[], 523 | > = Game extends infer G extends ParsedGame 524 | ? _Play> 525 | : Game extends string 526 | ? Play, Moves> 527 | : never 528 | 529 | type _Play< 530 | Game extends ParsedGame, 531 | Moves extends ParsedMove[], 532 | > = Moves extends [infer Head extends ParsedMove, ...infer Tail extends ParsedMove[]] 533 | ? _Play<_ApplyMoveUnsafe, Tail> 534 | : Game 535 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base' 2 | export * from './board' 3 | export * from './evaluate' 4 | export * from './game' 5 | export * from './notation' 6 | export * from './utils' 7 | 8 | export const pkg = '@bedard/chess-types - x.y.z' 9 | -------------------------------------------------------------------------------- /src/notation.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Castling, 3 | Color, 4 | DirectionIndex, 5 | File, 6 | Index, 7 | ParsedGame, 8 | ParsedMove, 9 | Piece, 10 | Position, 11 | PositionIndex, 12 | Positions, 13 | PromotionPiece, 14 | Rank, 15 | Unoccupied, 16 | } from './base' 17 | 18 | import type { 19 | Includes, 20 | Int, 21 | IsLength, 22 | } from './utils' 23 | 24 | /** Format parsed game to fen notation */ 25 | export type FormatGame< 26 | T extends ParsedGame, 27 | Board extends string = _FormatBoard<_ImplodeBoard>, 28 | > = `${Board} ${T['turn']} ${FormatCastling} ${T['ep'] extends Index ? Positions[T['ep']] extends infer E extends Position ? E : '-' : '-'} ${T['halfmove']} ${T['fullmove']}` 29 | 30 | export type FormatBoard = 31 | IsLength extends false ? never : _FormatBoard 32 | 33 | type _ImplodeBoard< 34 | T extends string[], 35 | Acc extends string = '' 36 | > = T extends [infer Head extends string, ...infer Tail extends string[]] 37 | ? _ImplodeBoard 38 | : Acc 39 | 40 | type _FormatBoard< 41 | T extends string, 42 | Acc extends string = '', 43 | Count extends DirectionIndex = 0, 44 | Skip extends DirectionIndex = 0, 45 | Rank extends DirectionIndex = 0 46 | > = Count extends 8 47 | ? _FormatBoard> 48 | : Skip extends 8 49 | ? never 50 | : T extends `${infer Head}${infer Tail}` 51 | ? Head extends Piece 52 | ? _FormatBoard, 0, Rank> 53 | : _FormatBoard, _Tick, Rank> 54 | : Acc 55 | 56 | type _Tick = 57 | T extends 0 ? 1 : 58 | T extends 1 ? 2 : 59 | T extends 2 ? 3 : 60 | T extends 3 ? 4 : 61 | T extends 4 ? 5 : 62 | T extends 5 ? 6 : 63 | T extends 6 ? 7 : 8 64 | 65 | /** stringify casting rights */ 66 | export type FormatCastling< 67 | T extends Castling, 68 | U = `${T['K'] extends true ? 'K' : ''}${T['Q'] extends true ? 'Q' : ''}${T['k'] extends true ? 'k' : ''}${T['q'] extends true ? 'q' : ''}` 69 | > = U extends '' ? '-' : U 70 | 71 | /** format san */ 72 | export type FormatSan< 73 | T extends ParsedMove, 74 | > = T['castle'] extends 'K' 75 | ? 'O-O' 76 | : T['castle'] extends 'Q' 77 | ? 'O-O-O' 78 | : T['castle'] extends 'k' 79 | ? 'o-o' 80 | : T['castle'] extends 'q' 81 | ? 'o-o-o' 82 | : `${Positions[T['from']]}${Positions[T['to']]}${T['promotion']}` 83 | 84 | /** Normalize fen board string to a 64 character string */ 85 | export type ParseBoard< 86 | T extends string, 87 | Acc extends string[] = [] 88 | > = T extends `${infer Head}${infer Rest}` 89 | ? Head extends '/' ? ParseBoard : 90 | Head extends '1' ? ParseBoard : 91 | Head extends '2' ? ParseBoard : 92 | Head extends '3' ? ParseBoard : 93 | Head extends '4' ? ParseBoard : 94 | Head extends '5' ? ParseBoard : 95 | Head extends '6' ? ParseBoard : 96 | Head extends '7' ? ParseBoard : 97 | Head extends '8' ? ParseBoard : 98 | Head extends Piece ? ParseBoard : never 99 | : Acc['length'] extends 64 ? Acc : never 100 | 101 | /** Parse castling rights */ 102 | export type ParseCastling = { 103 | K: Includes 104 | Q: Includes 105 | k: Includes 106 | q: Includes 107 | } 108 | 109 | /** Parse fen string */ 110 | export type ParseFen> = U extends ParsedGame 112 | ? U['board'] extends never ? never 113 | : U['castling'] extends never ? never 114 | : U['ep'] extends never ? never 115 | : U['halfmove'] extends never ? never 116 | : U['fullmove'] extends never ? never 117 | : U['turn'] extends never ? never 118 | : U 119 | : never 120 | 121 | export type _ParseFen = 122 | T extends `${infer _Board} ${infer _Turn} ${infer _Castling} ${infer _Ep} ${infer Halfmove} ${infer Fullmove}` 123 | ? { 124 | board: ParseBoard<_Board> 125 | ep: _Ep extends 126 | | 'a3' | 'b3' | 'c3' | 'd3' | 'e3' | 'f3' | 'g3' | 'h3' 127 | | 'a6' | 'b6' | 'c6' | 'd6' | 'e6' | 'f6' | 'g6' | 'h6' 128 | ? PositionIndex[_Ep] 129 | : _Ep extends '-' 130 | ? null 131 | : never 132 | halfmove: Int 133 | fullmove: Int 134 | castling: ParseCastling<_Castling> 135 | turn: _Turn extends Color ? _Turn : never 136 | } 137 | : never 138 | 139 | /** Parse move notation */ 140 | export type ParseSan = 141 | T extends 'O-O' 142 | ? { castle: 'K', from: 0, to: 0, promotion: '' } 143 | : T extends 'O-O-O' 144 | ? { castle: 'Q', from: 0, to: 0, promotion: '' } 145 | : T extends 'o-o' 146 | ? { castle: 'k', from: 0, to: 0, promotion: '' } 147 | : T extends 'o-o-o' 148 | ? { castle: 'q', from: 0, to: 0, promotion: '' } 149 | : T extends `${infer FromFile extends File}${infer FromRank extends Rank}${infer ToFile extends File}${infer ToRank extends Rank}${infer Promotion extends PromotionPiece | ''}` 150 | ? { 151 | castle: false, 152 | from: PositionIndex[`${FromFile}${FromRank}`], 153 | to: PositionIndex[`${ToFile}${ToRank}`], 154 | promotion: Promotion extends PromotionPiece ? Promotion : '' 155 | } 156 | : never 157 | 158 | /** 159 | * Parse a list of moves 160 | */ 161 | export type ParseSans< 162 | Moves extends string[], 163 | Acc extends ParsedMove[] = [] 164 | > = Moves extends [infer Head extends string, ...infer Tail extends string[]] 165 | ? ParseSans]> 166 | : Acc 167 | 168 | /** format tuple of sans */ 169 | export type ToSans< 170 | T extends ParsedMove[], 171 | Acc extends string[] = [] 172 | > = T extends [infer U extends ParsedMove, ...infer V extends ParsedMove[]] 173 | ? ToSans]> 174 | : Acc 175 | 176 | /** Map indices to their named position */ 177 | export type ToPositions< 178 | T extends Index[], 179 | Acc extends Positions[Index][] = [] 180 | > = T extends [infer U extends Index, ...infer V extends Index[]] 181 | ? ToPositions 182 | : Acc 183 | -------------------------------------------------------------------------------- /src/pieces/bishop.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Color, 3 | Index, 4 | ParsedGame, 5 | } from '../base' 6 | 7 | import type { Walk } from '../board' 8 | import type { ToMoves } from '../utils' 9 | 10 | export type BishopMoves< 11 | Game extends ParsedGame, 12 | Friendly extends Color, 13 | From extends Index, 14 | > = ToMoves<[ 15 | ...Walk, 16 | ...Walk, 17 | ...Walk, 18 | ...Walk, 19 | ], From> 20 | -------------------------------------------------------------------------------- /src/pieces/king.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Color, 3 | DirectionIndex, 4 | FriendlyPiece, 5 | Graph, 6 | Index, 7 | ParsedGame, 8 | Unoccupied, 9 | } from '../base' 10 | 11 | import type { ToMoves } from '../utils' 12 | 13 | export type KingMoves< 14 | Game extends ParsedGame, 15 | Friendly extends Color, 16 | From extends Index, 17 | > = ToMoves<[ 18 | ..._KingStep, 19 | ..._KingStep, 20 | ..._KingStep, 21 | ..._KingStep, 22 | ..._KingStep, 23 | ..._KingStep, 24 | ..._KingStep, 25 | ..._KingStep, 26 | ..._KingCastle, 27 | ..._KingCastle, 28 | ], From> 29 | 30 | /** Normal king movement */ 31 | export type _KingStep< 32 | Game extends ParsedGame, 33 | Friendly extends Color, 34 | From extends Index, 35 | Direction extends DirectionIndex, 36 | > = Graph[From][Direction] extends infer To extends Index 37 | ? Game['board'][To] extends FriendlyPiece 38 | ? [] 39 | : [To] 40 | : [] 41 | 42 | /** Castling moves */ 43 | type _KingCastle< 44 | Game extends ParsedGame, 45 | Friendly extends Color, 46 | From extends Index, 47 | Direction extends 3 | 5, 48 | > = From extends _KingCastleFrom 49 | ? _HasCastlingRights extends true 50 | ? Direction extends 5 51 | ? Graph[From][5] extends infer Short1 extends Index 52 | ? Game['board'][Short1] extends Unoccupied 53 | ? Graph[Short1][5] extends infer Short2 extends Index 54 | ? Game['board'][Short2] extends Unoccupied 55 | ? Graph[Short2][5] extends infer RookPosition extends Index 56 | ? Game['board'][RookPosition] extends _FriendlyRook 57 | ? [Short2] 58 | : [] 59 | : [] 60 | : [] 61 | : [] 62 | : [] 63 | : [] 64 | : Graph[From][3] extends infer Long1 extends Index 65 | ? Game['board'][Long1] extends Unoccupied 66 | ? Graph[Long1][3] extends infer Long2 extends Index 67 | ? Game['board'][Long2] extends Unoccupied 68 | ? Graph[Long2][3] extends infer Long3 extends Index 69 | ? Game['board'][Long3] extends Unoccupied 70 | ? Graph[Long3][3] extends infer RookPosition extends Index 71 | ? Game['board'][RookPosition] extends _FriendlyRook 72 | ? [Long2] 73 | : [] 74 | : [] 75 | : [] 76 | : [] 77 | : [] 78 | : [] 79 | : [] 80 | : [] 81 | : [] 82 | : [] 83 | 84 | type _KingCastleFrom = T extends 'w' ? 60 : 4 85 | 86 | type _HasCastlingRights< 87 | Game extends ParsedGame, 88 | Friendly extends Color, 89 | Direction extends 3 | 5, 90 | > = Friendly extends 'w' 91 | ? Direction extends 3 ? Game['castling']['Q'] : Game['castling']['K'] 92 | : Direction extends 3 ? Game['castling']['q'] : Game['castling']['k'] 93 | 94 | type _FriendlyRook = T extends 'w' ? 'R' : 'r' 95 | -------------------------------------------------------------------------------- /src/pieces/knight.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Color, 3 | FriendlyPiece, 4 | Graph, 5 | Index, 6 | ParsedGame, 7 | } from '../base' 8 | 9 | import type { ToMoves } from '../utils' 10 | 11 | export type KnightMoves< 12 | Game extends ParsedGame, 13 | Friendly extends Color, 14 | From extends Index, 15 | > = ToMoves<[ 16 | ..._KnightStep, 17 | ..._KnightStep, 18 | ..._KnightStep, 19 | ..._KnightStep, 20 | ..._KnightStep, 21 | ..._KnightStep, 22 | ..._KnightStep, 23 | ..._KnightStep, 24 | ], From> 25 | 26 | type _KnightStep< 27 | Game extends ParsedGame, 28 | Friendly extends Color, 29 | From extends Index, 30 | DiagonalDirection extends 0 | 2 | 6 | 8, 31 | OrthogonalDirection extends 1 | 3 | 5 | 7, 32 | > = Graph[From][DiagonalDirection] extends infer To extends Index 33 | ? Graph[To][OrthogonalDirection] extends infer Next extends Index 34 | ? Game['board'][Next] extends FriendlyPiece 35 | ? [] 36 | : [Next] 37 | : [] 38 | : [] 39 | -------------------------------------------------------------------------------- /src/pieces/pawn.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @stylistic/no-multi-spaces */ 2 | import type { 3 | Color, 4 | FriendlyPiece, 5 | Graph, 6 | Index, 7 | ParsedGame, 8 | ParsedMove, 9 | Unoccupied, 10 | } from '../base' 11 | 12 | import type { ToMoves } from '../utils' 13 | 14 | export type PawnMoves< 15 | Game extends ParsedGame, 16 | Friendly extends Color, 17 | From extends Index, 18 | Portside extends 0 | 8 = Friendly extends 'w' ? 0 : 8, 19 | Starboard extends 2 | 6 = Friendly extends 'w' ? 2 : 6 20 | > = _ExpandPromotions< 21 | ToMoves<[ 22 | ..._PawnAdvance, 23 | ..._PawnCapture, 24 | ..._PawnCapture, 25 | ..._PawnEnPassant, 26 | ..._PawnEnPassant, 27 | ], From>, 28 | Friendly 29 | > 30 | 31 | /** advance pawn forward, and if allowed advance again */ 32 | type _PawnAdvance< 33 | Game extends ParsedGame, 34 | Friendly extends Color, 35 | From extends Index, 36 | Forward extends 1 | 7 = Friendly extends 'w' ? 1 : 7, 37 | > = Graph[From][Forward] extends infer First extends Index 38 | ? Game['board'][First] extends Unoccupied 39 | ? [ 40 | First, 41 | ...From extends _PawnStartingPositions 42 | ? Graph[First][Forward] extends infer Second extends Index 43 | ? Game['board'][Second] extends Unoccupied 44 | ? [Second] 45 | : [] 46 | : [] 47 | : [] 48 | ] 49 | : [] 50 | : [] 51 | 52 | type _PawnStartingPositions = 53 | | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 54 | | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 55 | 56 | /** capture enemy piece */ 57 | type _PawnCapture< 58 | Game extends ParsedGame, 59 | Friendly extends Color, 60 | From extends Index, 61 | Direction extends 0 | 2 | 6 | 8, 62 | > = Graph[From][Direction] extends infer To extends Index 63 | ? Game['board'][To] extends FriendlyPiece | Unoccupied 64 | ? [] 65 | : [To] 66 | : [] 67 | 68 | /** capture en passant */ 69 | type _PawnEnPassant< 70 | Game extends ParsedGame, 71 | Friendly extends Color, 72 | From extends Index, 73 | Direction extends 0 | 2 | 6 | 8, 74 | > = Graph[From][Direction] extends infer To extends Game['ep'] 75 | ? Game['turn'] extends Friendly 76 | ? To extends Index 77 | ? [To] 78 | : [] 79 | : [] 80 | : [] 81 | 82 | /** promotions */ 83 | export type _ExpandPromotions< 84 | T extends ParsedMove[], 85 | U extends Color, 86 | Acc extends ParsedMove[] = [] 87 | > = T extends [infer Head extends ParsedMove, ...infer Tail extends ParsedMove[]] 88 | ? Head['to'] extends _PawnPromotionPositions 89 | ? _ExpandPromotions 96 | : _ExpandPromotions 97 | : Acc 98 | 99 | type _PawnPromotionPositions = 100 | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 101 | | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 102 | -------------------------------------------------------------------------------- /src/pieces/queen.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Color, 3 | Index, 4 | ParsedGame, 5 | } from '../base' 6 | 7 | import type { Walk } from '../board' 8 | import type { ToMoves } from '../utils' 9 | 10 | export type QueenMoves< 11 | Game extends ParsedGame, 12 | Friendly extends Color, 13 | From extends Index, 14 | > = ToMoves<[ 15 | ...Walk, 16 | ...Walk, 17 | ...Walk, 18 | ...Walk, 19 | ...Walk, 20 | ...Walk, 21 | ...Walk, 22 | ...Walk, 23 | ], From> 24 | -------------------------------------------------------------------------------- /src/pieces/rook.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Color, 3 | Index, 4 | ParsedGame, 5 | } from '../base' 6 | 7 | import type { Walk } from '../board' 8 | import type { ToMoves } from '../utils' 9 | 10 | export type RookMoves< 11 | Game extends ParsedGame, 12 | Friendly extends Color, 13 | From extends Index, 14 | > = ToMoves<[ 15 | ...Walk, 16 | ...Walk, 17 | ...Walk, 18 | ...Walk, 19 | ], From> 20 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Index } from './base' 2 | 3 | /** 4 | * Get the absolute value of a number 5 | */ 6 | export type Abs = `${T}` extends `-${string}` 7 | ? `${T}` extends `-${infer U}` 8 | ? `${U}` extends `${infer V extends number}` 9 | ? V 10 | : never 11 | : T 12 | : T 13 | 14 | /** 15 | * Fill array to a certain size 16 | */ 17 | type Fill< 18 | T extends number, 19 | Arr extends number[] = [] 20 | > = Arr['length'] extends T ? Arr : Fill 21 | 22 | /** 23 | * Test if integer `A` is greater than `B` 24 | */ 25 | export type IsGreater< 26 | A extends number, 27 | B extends number, 28 | > = [IsNegative, IsNegative] extends [infer NegA extends boolean, infer NegB extends boolean] 29 | // both values are negative 30 | ? [NegA, NegB] extends [true, true] 31 | ? [Abs, Abs] extends [infer AbsA extends number, infer AbsB extends number] 32 | ? IsGreater 33 | : never 34 | // only the right side is negative 35 | : [NegA, NegB] extends [false, true] 36 | ? true 37 | // only the left side is negative 38 | : [NegA, NegB] extends [true, false] 39 | ? false 40 | // both values are positive 41 | : [NegA, NegB] extends [false, false] 42 | ? _Balance extends [infer BalA extends number, number] 43 | ? BalA extends 0 44 | ? false 45 | : true 46 | : never 47 | : never 48 | : never 49 | 50 | /** Test if string `T` includes string `U` */ 51 | export type Includes = 52 | T extends `${string}${U}${string}` 53 | ? true 54 | : false 55 | 56 | /** Increment number `T` */ 57 | export type Increment = 58 | Acc['length'] extends T 59 | ? [...Acc, 1]['length'] 60 | : Increment 61 | 62 | /** Convert positive string integer `T` to `number` */ 63 | export type Int = 64 | `${T}` extends `-${string}` | `${string}.${string}` 65 | ? never 66 | : T extends `${infer U extends number}` 67 | ? U 68 | : never 69 | 70 | /** Test if string `T` is of length `U` */ 71 | export type IsLength< 72 | T extends string, 73 | U extends number, 74 | Acc extends unknown[] = [] 75 | > = T extends `${infer _}${infer Rest}` 76 | ? Acc['length'] extends U 77 | ? false 78 | : IsLength 79 | : Acc['length'] extends U 80 | ? true 81 | : false 82 | 83 | /** Test for negative value */ 84 | export type IsNegative = `${T}` extends `-${string}` ? true : false 85 | 86 | /** Test if a number is odd */ 87 | export type IsOdd = `${T}` extends `${string}${'1' | '3' | '5' | '7' | '9'}` ? true : false 88 | 89 | /** Multiple a number by -1 */ 90 | export type Negate = 91 | `${T}` extends `-${infer V extends number}` 92 | ? V 93 | : ToNumber<`-${T}`> 94 | 95 | /** Map a tuple of indices to san notation */ 96 | export type ToMoves< 97 | T extends Index[], 98 | From extends Index, 99 | Acc extends unknown[] = [] 100 | > = T extends [infer To extends Index, ...infer Tail extends Index[]] 101 | ? ToMoves 107 | : Acc 108 | 109 | /** Cast string value to a number */ 110 | type ToNumber = T extends `${infer U extends number}` ? U : never 111 | 112 | /** 113 | * Sum two numbers 114 | */ 115 | export type Sum< 116 | A extends number, 117 | B extends number, 118 | > = [IsNegative, IsNegative] extends [infer NegA extends boolean, infer NegB extends boolean] 119 | // negative + negative 120 | ? [NegA, NegB] extends [true, true] 121 | ? Negate<_Sum, Abs>> 122 | // negative + positive 123 | : [NegA, NegB] extends [true, false] 124 | ? _Balance extends [infer BalanceA extends number, infer BalanceB extends number] 125 | ? BalanceA extends 0 126 | ? BalanceB 127 | : Negate 128 | : never 129 | // positive + negative 130 | : [NegA, NegB] extends [false, true] 131 | ? _Balance extends [infer BalanceA extends number, infer BalanceB extends number] 132 | ? BalanceB extends 0 ? BalanceA : Negate 133 | : never 134 | // positive + positive 135 | : _Sum 136 | : never 137 | 138 | export type _Sum< 139 | A extends number, 140 | B extends number, 141 | > = [...Fill, ...Fill]['length'] extends infer U extends number ? U : never 142 | 143 | /** Iterate A and B towards 0, stop when either of them reach it */ 144 | type _Balance< 145 | A extends number, 146 | B extends number, 147 | SetA extends 1[] = Fill>, 148 | SetB extends 1[] = Fill>, 149 | > = SetA['length'] extends SetB['length'] 150 | ? [0, 0] 151 | : SetA extends [1, ...infer TailA extends 1[]] 152 | ? SetB extends [1, ...infer TailB extends 1[]] 153 | ? _Balance 154 | : [SetA['length'], 0] // Set B ran out first 155 | : [0, SetB['length']] // set A ran our first 156 | -------------------------------------------------------------------------------- /tests/bishop.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, describe, test } from 'vitest' 2 | import type { BishopMoves } from '@/pieces/bishop' 3 | import type { ParseFen, ToSans } from '@/notation' 4 | import type { PositionIndex } from '@/base' 5 | 6 | describe('BishopMoves', () => { 7 | test('c3', () => { 8 | type Game = ParseFen<'8/6r1/8/8/8/2B5/8/4R3 w - - 0 1'> 9 | 10 | type Result = ToSans> 11 | 12 | assertType([ 13 | 'c3b4', 'c3a5', 'c3d4', 'c3e5', 'c3f6', 'c3g7', 'c3b2', 'c3a1', 'c3d2' 14 | ]) 15 | }) 16 | 17 | test('e4', () => { 18 | type Game = ParseFen<'8/8/8/8/4b3/8/8/8 b - - 0 1'> 19 | 20 | type Result = ToSans> 21 | 22 | assertType([ 23 | 'e4d5', 'e4c6', 'e4b7', 'e4a8', 'e4f5', 'e4g6', 'e4h7', 'e4d3', 'e4c2', 'e4b1', 'e4f3', 'e4g2', 'e4h1' 24 | ]) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /tests/board.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, describe, test } from 'vitest' 2 | import type { ParseFen } from '@/notation' 3 | import type { WalkPositions } from '@/board' 4 | 5 | describe('WalkPositions', () => { 6 | test('north until friendly piece', () => { 7 | type Game = ParseFen<'8/3Q4/8/8/8/8/3Q4/8 w - - 0 1'> 8 | type Result = WalkPositions 9 | 10 | assertType(['d3', 'd4', 'd5', 'd6']) 11 | }) 12 | 13 | test('north east until hostile piece', () => { 14 | type Game = ParseFen<'7Q/8/8/8/8/8/8/8 w - - 0 1'> 15 | type Result = WalkPositions 16 | 17 | assertType(['b2', 'c3', 'd4', 'e5', 'f6', 'g7', 'h8']) 18 | }) 19 | 20 | test('south west until edge of board', () => { 21 | type Game = ParseFen<'8/8/8/8/8/8/8/8 w - - 0 1'> 22 | type Result = WalkPositions 23 | 24 | assertType(['e7', 'd6', 'c5', 'b4', 'a3']) 25 | }) 26 | 27 | test('north west until edge of board', () => { 28 | type Game = ParseFen<'8/8/8/8/8/8/8/8 w - - 0 1'> 29 | type Result = WalkPositions 30 | 31 | assertType(['g2', 'f3', 'e4', 'd5', 'c6', 'b7', 'a8']) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /tests/evaluate.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, describe, test } from 'vitest' 2 | import type { ParseFen } from '@/notation' 3 | import type { Evaluate, NextMove } from '@/evaluate' 4 | import type { NewGame } from '@/game' 5 | 6 | test('Evaluate', () => { 7 | type Result = Evaluate 8 | 9 | assertType(0) 10 | }) 11 | 12 | describe('NextMove', () => { 13 | test('white captures stronger piece', () => { 14 | type ShouldCaptureE5 = ParseFen<'8/8/8/2p1q3/3P4/8/8/8 w - - 0 1'> 15 | type ShouldCaptureC5 = ParseFen<'8/8/8/2q1p3/3P4/8/8/8 w - - 0 1'> 16 | 17 | type Result1 = NextMove 18 | type Result2 = NextMove 19 | 20 | assertType('d4e5') 21 | assertType('d4c5') 22 | }) 23 | 24 | test('white promotes to strongest piece', () => { 25 | type Game = ParseFen<'8/3P4/8/8/8/8/8/8 w - - 0 1'> 26 | 27 | type Result = NextMove 28 | 29 | assertType('d7d8Q') 30 | }) 31 | 32 | test('black captures stronger piece', () => { 33 | type ShouldCaptureD4 = ParseFen<'8/8/8/4p3/3Q1P2/8/8/8 b - - 0 1'> 34 | type ShouldCaptureF4 = ParseFen<'8/8/8/4p3/3P1Q2/8/8/8 b - - 0 1'> 35 | 36 | type Result1 = NextMove 37 | type Result2 = NextMove 38 | 39 | assertType('e5d4') 40 | assertType('e5f4') 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /tests/game.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, describe, test } from 'vitest' 2 | import type { FormatGame, ParseFen } from '@/notation' 3 | import type { Positions } from '@/base' 4 | 5 | import type { 6 | ApplyMoveUnsafe, 7 | Chessboard, 8 | CurrentMoves, 9 | CurrentMovesUnsafe, 10 | FindKing, 11 | IsCheck, 12 | IsLegal, 13 | IsThreatened, 14 | NewGame, 15 | OccupiedBy, 16 | Play, 17 | } from '@/game' 18 | 19 | describe('ApplyMoveUnsafe', () => { 20 | test('never if unoccupied', () => { 21 | type Game = ParseFen<'8/8/8/8/8/8/8/8 w KQkq - 0 1'> 22 | 23 | type Result = ApplyMoveUnsafe extends never ? true : false 24 | 25 | assertType(true) 26 | }) 27 | 28 | test('alternate color', () => { 29 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 30 | 31 | type WhiteMoved = ApplyMoveUnsafe 32 | 33 | assertType('b') 34 | 35 | type BlackMoved = ApplyMoveUnsafe 36 | 37 | assertType('w') 38 | }) 39 | 40 | test('counts halfmove', () => { 41 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 42 | 43 | assertType(0) 44 | 45 | type Move1 = ApplyMoveUnsafe 46 | 47 | assertType(1) 48 | 49 | type Move2 = ApplyMoveUnsafe 50 | 51 | assertType(0) 52 | 53 | type Move3 = ApplyMoveUnsafe 54 | 55 | assertType(0) 56 | 57 | type Move4 = ApplyMoveUnsafe 58 | 59 | assertType(1) 60 | }) 61 | 62 | test('moves piece and unoccupies from index', () => { 63 | type Game = ParseFen<'R7/8/8/8/8/8/8/8 w - - 0 1'> 64 | 65 | type Result = ApplyMoveUnsafe 66 | 67 | assertType(' ') 68 | 69 | assertType('R') 70 | }) 71 | 72 | test('castle white short', () => { 73 | type Game = ParseFen<'8/8/8/8/8/8/8/4K2R w K - 0 1'> 74 | 75 | type Result = FormatGame> 76 | 77 | assertType('8/8/8/8/8/8/8/5RK1 b - - 1 1') 78 | }) 79 | 80 | test('white castle long', () => { 81 | type Game = ParseFen<'8/8/8/8/8/8/8/R3K3 w Q - 0 1'> 82 | 83 | type Result = FormatGame> 84 | 85 | assertType('8/8/8/8/8/8/8/2KR4 b - - 1 1') 86 | }) 87 | 88 | test('black castle short', () => { 89 | type Game = ParseFen<'4k2r/8/8/8/8/8/8/8 b k - 0 1'> 90 | 91 | type Result = FormatGame> 92 | 93 | assertType('5rk1/8/8/8/8/8/8/8 w - - 1 2') 94 | }) 95 | 96 | test('black castle long', () => { 97 | type Game = ParseFen<'r3k3/8/8/8/8/8/8/8 b q - 0 1'> 98 | 99 | type Result = FormatGame> 100 | 101 | assertType('2kr4/8/8/8/8/8/8/8 w - - 1 2') 102 | }) 103 | 104 | test('set en passant', () => { 105 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 106 | 107 | type A3 = Positions[ApplyMoveUnsafe['ep']] 108 | type B3 = Positions[ApplyMoveUnsafe['ep']] 109 | type C3 = Positions[ApplyMoveUnsafe['ep']] 110 | type D3 = Positions[ApplyMoveUnsafe['ep']] 111 | type E3 = Positions[ApplyMoveUnsafe['ep']] 112 | type F3 = Positions[ApplyMoveUnsafe['ep']] 113 | type G3 = Positions[ApplyMoveUnsafe['ep']] 114 | type H3 = Positions[ApplyMoveUnsafe['ep']] 115 | 116 | type A6 = Positions[ApplyMoveUnsafe['ep']] 117 | type B6 = Positions[ApplyMoveUnsafe['ep']] 118 | type C6 = Positions[ApplyMoveUnsafe['ep']] 119 | type D6 = Positions[ApplyMoveUnsafe['ep']] 120 | type E6 = Positions[ApplyMoveUnsafe['ep']] 121 | type F6 = Positions[ApplyMoveUnsafe['ep']] 122 | type G6 = Positions[ApplyMoveUnsafe['ep']] 123 | type H6 = Positions[ApplyMoveUnsafe['ep']] 124 | 125 | assertType('a3') 126 | assertType('b3') 127 | assertType('c3') 128 | assertType('d3') 129 | assertType('e3') 130 | assertType('f3') 131 | assertType('g3') 132 | assertType

('h3') 133 | 134 | assertType('a6') 135 | assertType('b6') 136 | assertType('c6') 137 | assertType('d6') 138 | assertType('e6') 139 | assertType('f6') 140 | assertType('g6') 141 | assertType
('h6') 142 | }) 143 | 144 | test('clear en passant', () => { 145 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 146 | 147 | type Move1 = ApplyMoveUnsafe 148 | 149 | assertType('e3') 150 | 151 | type Move2 = ApplyMoveUnsafe 152 | 153 | assertType(null) 154 | }) 155 | 156 | test('capture en passant white', () => { 157 | type Game = ParseFen<'8/8/8/3pP3/8/8/8/8 w - d6 0 2'> 158 | 159 | type Result = FormatGame> 160 | 161 | assertType('8/8/3P4/8/8/8/8/8 b - - 0 2') 162 | }) 163 | 164 | test('capture en passant black', () => { 165 | type Game = ParseFen<'8/8/8/8/3Pp3/8/8/8 b - d3 0 1'> 166 | 167 | type Result = FormatGame> 168 | 169 | assertType('8/8/8/8/8/3p4/8/8 w - - 0 2') 170 | }) 171 | }) 172 | 173 | describe('Chessboard', () => { 174 | test('empty board', () => { 175 | type Game = ParseFen<'8/8/8/8/8/8/8/8 w KQkq - 0 1'> 176 | 177 | type White = Chessboard 178 | 179 | type Black = Chessboard 180 | 181 | assertType({ 182 | 8: ' - * - * - * - * ', 183 | 7: ' * - * - * - * - ', 184 | 6: ' - * - * - * - * ', 185 | 5: ' * - * - * - * - ', 186 | 4: ' - * - * - * - * ', 187 | 3: ' * - * - * - * - ', 188 | 2: ' - * - * - * - * ', 189 | 1: ' * - * - * - * - ', 190 | }) 191 | 192 | assertType({ 193 | 1: ' - * - * - * - * ', 194 | 2: ' * - * - * - * - ', 195 | 3: ' - * - * - * - * ', 196 | 4: ' * - * - * - * - ', 197 | 5: ' - * - * - * - * ', 198 | 6: ' * - * - * - * - ', 199 | 7: ' - * - * - * - * ', 200 | 8: ' * - * - * - * - ', 201 | }) 202 | }) 203 | 204 | test('starting position', () => { 205 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 206 | 207 | type White = Chessboard 208 | 209 | type Black = Chessboard 210 | 211 | assertType({ 212 | 8: ' r n b q k b n r ', 213 | 7: ' p p p p p p p p ', 214 | 6: ' - * - * - * - * ', 215 | 5: ' * - * - * - * - ', 216 | 4: ' - * - * - * - * ', 217 | 3: ' * - * - * - * - ', 218 | 2: ' P P P P P P P P ', 219 | 1: ' R N B Q K B N R ', 220 | }) 221 | 222 | assertType({ 223 | 1: ' R N B K Q B N R ', 224 | 2: ' P P P P P P P P ', 225 | 3: ' - * - * - * - * ', 226 | 4: ' * - * - * - * - ', 227 | 5: ' - * - * - * - * ', 228 | 6: ' * - * - * - * - ', 229 | 7: ' p p p p p p p p ', 230 | 8: ' r n b k q b n r ', 231 | }) 232 | }) 233 | }) 234 | 235 | describe('CurrentMoves', () => { 236 | test('empty board', () => { 237 | type Game = ParseFen<'8/8/8/8/8/8/8/8 w KQkq - 0 1'> 238 | 239 | type White = CurrentMoves 240 | type Black = CurrentMoves 241 | 242 | assertType([]) 243 | assertType([]) 244 | }) 245 | 246 | test('starting position', () => { 247 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 248 | 249 | type Result = CurrentMoves 250 | 251 | assertType([ 252 | 'a2a3', 'a2a4', 'b2b3', 'b2b4', 'c2c3', 'c2c4', 'd2d3', 'd2d4', 253 | 'e2e3', 'e2e4', 'f2f3', 'f2f4', 'g2g3', 'g2g4', 'h2h3', 'h2h4', 254 | 'b1a3', 'b1c3', 'g1f3', 'g1h3' 255 | ]) 256 | }) 257 | 258 | test('must block check', () => { 259 | type Game = ParseFen<'K6r/Q6r/8/8/8/8/8/8 w - - 0 1'> 260 | 261 | type Result = CurrentMoves 262 | 263 | assertType(['a7b8']) 264 | }) 265 | }) 266 | 267 | describe('CurrentMovesUnsafe', () => { 268 | test('empty board', () => { 269 | type Game = ParseFen<'8/8/8/8/8/8/8/8 w KQkq - 0 1'> 270 | 271 | type Result = CurrentMovesUnsafe 272 | 273 | assertType([]) 274 | }) 275 | 276 | test('starting position', () => { 277 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 278 | 279 | type Result = CurrentMovesUnsafe 280 | 281 | assertType([ 282 | 'a2a3', 'a2a4', 'b2b3', 'b2b4', 'c2c3', 'c2c4', 'd2d3', 'd2d4', 283 | 'e2e3', 'e2e4', 'f2f3', 'f2f4', 'g2g3', 'g2g4', 'h2h3', 'h2h4', 284 | 'b1a3', 'b1c3', 'g1f3', 'g1h3' 285 | ]) 286 | }) 287 | 288 | test('starting position, alternate color', () => { 289 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 290 | 291 | type Result = CurrentMovesUnsafe 292 | 293 | assertType([ 294 | 'b8a6', 'b8c6', 'g8f6', 'g8h6', 'a7a6', 'a7a5', 'b7b6', 'b7b5', 295 | 'c7c6', 'c7c5', 'd7d6', 'd7d5', 'e7e6', 'e7e5', 'f7f6', 'f7f5', 296 | 'g7g6', 'g7g5', 'h7h6', 'h7h5' 297 | ]) 298 | }) 299 | }) 300 | 301 | describe('FindKing', () => { 302 | test('empty board', () => { 303 | type Game = ParseFen<'8/8/8/8/8/8/8/8 w KQkq - 0 1'> 304 | 305 | type Black = FindKing 306 | type White = FindKing 307 | 308 | assertType(false) 309 | assertType(false) 310 | }) 311 | 312 | test('starting position', () => { 313 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 314 | 315 | type Black = FindKing 316 | type White = FindKing 317 | 318 | assertType('e8') 319 | assertType('e1') 320 | }) 321 | }) 322 | 323 | describe('IsCheck', () => { 324 | test('no check', () => { 325 | type Game = ParseFen<'8/8/8/8/8/8/4p3/4K3 w - - 0 1'> 326 | 327 | type Black = IsCheck 328 | type White = IsCheck 329 | 330 | assertType(false) 331 | assertType(false) 332 | }) 333 | 334 | test('white checked on turn', () => { 335 | type Game = ParseFen<'r3k3/8/8/8/8/8/5p2/4K3 w - - 0 1'> 336 | 337 | type Result = IsCheck 338 | 339 | assertType(true) 340 | }) 341 | 342 | test('black checked on turn', () => { 343 | type Game = ParseFen<'r3k3/8/8/1B6/8/8/8/4K3 b - - 0 1'> 344 | 345 | type Result = IsCheck 346 | 347 | assertType(true) 348 | }) 349 | 350 | test('check by explicit color', () => { 351 | type Game = ParseFen<'r3k3/8/8/8/8/8/5p2/4K3 w - - 0 1'> 352 | 353 | type Black = IsCheck 354 | type White = IsCheck 355 | 356 | assertType(false) 357 | assertType(true) 358 | }) 359 | }) 360 | 361 | describe('IsLegal', () => { 362 | test('false from empty positions', () => { 363 | type Game = ParseFen<'8/8/8/8/8/8/8/8 w KQkq - 0 1'> 364 | 365 | type Result = IsLegal 366 | 367 | assertType(false) 368 | }) 369 | 370 | test('true for legal moves', () => { 371 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 372 | 373 | type Result = IsLegal 374 | 375 | assertType(true) 376 | }) 377 | 378 | test('cannot self-check by moving pinned piece', () => { 379 | type Game = ParseFen<'7b/8/8/8/3P4/8/8/K7 w - - 0 1'> 380 | 381 | type Result = IsLegal 382 | 383 | assertType(false) 384 | }) 385 | 386 | test('cannot move king into check', () => { 387 | type Game = ParseFen<'8/8/3k4/8/4P3/8/8/8 b - - 0 1'> 388 | 389 | type Result = IsLegal 390 | 391 | assertType(false) 392 | }) 393 | 394 | test('cannot castle through check (O-O)', () => { 395 | type Result1 = IsLegal, 'O-O'> 396 | type Result2 = IsLegal, 'O-O'> 397 | type Result3 = IsLegal, 'O-O'> 398 | type Result4 = IsLegal, 'O-O'> 399 | 400 | assertType(false) 401 | assertType(false) 402 | assertType(false) 403 | assertType(true) 404 | }) 405 | 406 | test('cannot castle through check (O-O-O)', () => { 407 | type Result1 = IsLegal, 'O-O-O'> 408 | type Result2 = IsLegal, 'O-O-O'> 409 | type Result3 = IsLegal, 'O-O-O'> 410 | type Result4 = IsLegal, 'O-O-O'> 411 | type Result5 = IsLegal, 'O-O-O'> 412 | 413 | assertType(false) 414 | assertType(false) 415 | assertType(false) 416 | assertType(false) 417 | assertType(true) 418 | }) 419 | 420 | test('cannot castle through check (o-o)', () => { 421 | type Result1 = IsLegal, 'o-o'> 422 | type Result2 = IsLegal, 'o-o'> 423 | type Result3 = IsLegal, 'o-o'> 424 | type Result4 = IsLegal, 'o-o'> 425 | 426 | assertType(false) 427 | assertType(false) 428 | assertType(false) 429 | assertType(true) 430 | }) 431 | 432 | test('cannot castle through check (o-o-o)', () => { 433 | type Result1 = IsLegal, 'o-o-o'> 434 | type Result2 = IsLegal, 'o-o-o'> 435 | type Result3 = IsLegal, 'o-o-o'> 436 | type Result4 = IsLegal, 'o-o-o'> 437 | type Result5 = IsLegal, 'o-o-o'> 438 | 439 | assertType(false) 440 | assertType(false) 441 | assertType(false) 442 | assertType(false) 443 | assertType(true) 444 | }) 445 | }) 446 | 447 | describe('IsThreatened', () => { 448 | test('empty board', () => { 449 | type Game = ParseFen<'8/8/8/8/8/8/8/8 w KQkq - 0 1'> 450 | 451 | type Result = IsThreatened 452 | 453 | assertType(false) 454 | }) 455 | 456 | test('black obstructed', () => { 457 | type Game = ParseFen<'8/8/8/3R4/8/4P3/8/2b5 w - - 0 1'> 458 | 459 | type ThreatenedByBlack = IsThreatened 460 | type ThreatenedByWhite = IsThreatened 461 | 462 | assertType(false) 463 | assertType(true) 464 | }) 465 | 466 | test('white obstructed', () => { 467 | type Game = ParseFen<'8/8/8/3R1P2/8/8/8/2b5 w - - 0 1'> 468 | 469 | type ThreatenedByBlack = IsThreatened 470 | type ThreatenedByWhite = IsThreatened 471 | 472 | assertType(true) 473 | assertType(false) 474 | }) 475 | 476 | test('unreachable position', () => { 477 | type Game = ParseFen<'8/8/8/3R1P2/8/8/8/2b5 w - - 0 1'> 478 | 479 | type ThreatenedByBlack = IsThreatened 480 | type ThreatenedByWhite = IsThreatened 481 | 482 | assertType(false) 483 | assertType(false) 484 | }) 485 | 486 | test('threatened by pieces later in index', () => { 487 | type Game = ParseFen<'N7/4B3/8/8/8/8/8/2b5 w - - 0 1'> 488 | 489 | type ThreatenedByBlack = IsThreatened 490 | type ThreatenedByWhite = IsThreatened 491 | 492 | assertType(true) 493 | assertType(true) 494 | }) 495 | }) 496 | 497 | describe('OccupiedBy', () => { 498 | test('empty board', () => { 499 | type Game = ParseFen<'8/8/8/8/8/8/8/8 w KQkq - 0 1'> 500 | 501 | assertType>([]) 502 | assertType>([]) 503 | }) 504 | 505 | test('starting position', () => { 506 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 507 | 508 | type Black = OccupiedBy 509 | 510 | type White = OccupiedBy 511 | 512 | assertType([ 513 | 'a8', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8', 'a7', 'b7', 'c7', 'd7', 'e7', 'f7', 'g7', 'h7' 514 | ]) 515 | 516 | assertType([ 517 | 'a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2', 'a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1' 518 | ]) 519 | }) 520 | 521 | test('mid-game position', () => { 522 | type Game = ParseFen<'2b3k1/6pp/4p3/2p5/4p3/4K3/P1P3PP/7R b - - 1 23'> 523 | 524 | type Black = OccupiedBy 525 | 526 | type White = OccupiedBy 527 | 528 | assertType([ 529 | 'c8', 'g8', 'g7', 'h7', 'e6', 'c5', 'e4' 530 | ]) 531 | 532 | assertType([ 533 | 'e3', 'a2', 'c2', 'g2', 'h2', 'h1' 534 | ]) 535 | }) 536 | }) 537 | 538 | describe('Play', () => { 539 | test('NewGame', () => { 540 | type Result = NewGame<['e2e4']> 541 | 542 | type Fen = FormatGame 543 | 544 | assertType('rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1') 545 | }) 546 | 547 | test('Giuoco piano', () => { 548 | type Result = Play<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', [ 549 | 'e2e4', 'e7e5', 550 | 'g1f3', 'b8c6', 551 | 'f1c4', 'f8c5', 552 | 'O-O', 553 | ]> 554 | 555 | type Fen = FormatGame 556 | 557 | assertType('r1bqk1nr/pppp1ppp/2n5/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQ1RK1 b kq - 5 4') 558 | }) 559 | }) 560 | -------------------------------------------------------------------------------- /tests/king.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, describe, test } from 'vitest' 2 | import type { KingMoves } from '@/pieces/king' 3 | import type { ParseFen, ToSans } from '@/notation' 4 | import type { PositionIndex } from '@/base' 5 | 6 | describe('KingMoves', () => { 7 | test('c6', () => { 8 | type Game = ParseFen<'8/2pP4/2K5/8/8/8/8/8 w - - 0 1'> 9 | 10 | type Result = ToSans> 11 | 12 | assertType([ 13 | 'c6b7', 'c6c7', 'c6b6', 'c6d6', 'c6b5', 'c6c5', 'c6d5' 14 | ]) 15 | }) 16 | 17 | test('h4', () => { 18 | type Game = ParseFen<'8/8/8/8/7K/8/8/8 w - - 0 1'> 19 | 20 | type Result = ToSans> 21 | 22 | assertType([ 23 | 'h4g5', 'h4h5', 'h4g4', 'h4g3', 'h4h3' 24 | ]) 25 | }) 26 | 27 | test('white castle short', () => { 28 | type Game = ParseFen<'r3k2r/8/8/8/8/8/3PPP2/R2PK2R w KQkq - 0 1'> 29 | 30 | type Result = ToSans> 31 | 32 | assertType([ 33 | 'e1f1', 'e1g1' 34 | ]) 35 | }) 36 | 37 | test('white castle blocked 1', () => { 38 | type Game = ParseFen<'r3k2r/8/8/8/8/8/3PPP2/R2PKP1R w KQkq - 0 1'> 39 | 40 | type Result = ToSans> 41 | 42 | assertType([]) 43 | }) 44 | 45 | test('white castle short blocked 2', () => { 46 | type Game = ParseFen<'r3k2r/8/8/8/8/8/3PPP2/R2PK1PR w KQkq - 0 1'> 47 | 48 | type Result = ToSans> 49 | 50 | assertType([ 51 | 'e1f1' 52 | ]) 53 | }) 54 | 55 | test('white castle short without rook', () => { 56 | type Game = ParseFen<'r3k2r/8/8/8/8/8/3PPP2/R2PK3 w KQkq - 0 1'> 57 | 58 | type Result = ToSans> 59 | 60 | assertType([ 61 | 'e1f1' 62 | ]) 63 | }) 64 | 65 | test('white castle short without rights', () => { 66 | type Game = ParseFen<'r3k2r/8/8/8/8/8/3PPP2/R2PK2R w Qkq - 0 1'> 67 | 68 | type Result = ToSans> 69 | 70 | assertType([ 71 | 'e1f1' 72 | ]) 73 | }) 74 | 75 | test('white castle long', () => { 76 | type Game = ParseFen<'r3k2r/8/8/8/8/8/3PPP2/R3KP1R w KQkq - 0 1'> 77 | 78 | type Result = ToSans> 79 | 80 | assertType([ 81 | 'e1d1', 'e1c1' 82 | ]) 83 | }) 84 | 85 | test('white castle long blocked 2', () => { 86 | type Game = ParseFen<'r3k2r/8/8/8/8/8/3PPP2/R1P1KP2 w KQkq - 0 1'> 87 | 88 | type Result = ToSans> 89 | 90 | assertType([ 91 | 'e1d1' 92 | ]) 93 | }) 94 | 95 | test('white castle long blocked 3', () => { 96 | type Game = ParseFen<'r3k2r/8/8/8/8/8/3PPP2/RP2KP2 w KQkq - 0 1'> 97 | 98 | type Result = ToSans> 99 | 100 | assertType([ 101 | 'e1d1' 102 | ]) 103 | }) 104 | 105 | test('white castle long without rook', () => { 106 | type Game = ParseFen<'r3k2r/8/8/8/8/8/3PPP2/4KP2 w KQkq - 0 1'> 107 | 108 | type Result = ToSans> 109 | 110 | assertType([ 111 | 'e1d1' 112 | ]) 113 | }) 114 | 115 | test('white castle long without rights', () => { 116 | type Game = ParseFen<'r3k2r/8/8/8/8/8/3PPP2/R3KP2 w Kkq - 0 1'> 117 | 118 | type Result = ToSans> 119 | 120 | assertType([ 121 | 'e1d1' 122 | ]) 123 | }) 124 | 125 | test('black castle short', () => { 126 | type Game = ParseFen<'r2pk2r/3ppp2/8/8/8/8/8/8 b KQkq - 0 1'> 127 | 128 | type Result = ToSans> 129 | 130 | assertType([ 131 | 'e8f8', 'e8g8' 132 | ]) 133 | }) 134 | 135 | test('black castle blocked 1', () => { 136 | type Game = ParseFen<'r2pkp1r/3ppp2/8/8/8/8/8/8 b KQkq - 0 1'> 137 | 138 | type Result = ToSans> 139 | 140 | assertType([]) 141 | }) 142 | 143 | test('black castle short blocked 2', () => { 144 | type Game = ParseFen<'r2pk1pr/3ppp2/8/8/8/8/8/8 b KQkq - 0 1'> 145 | 146 | type Result = ToSans> 147 | 148 | assertType([ 149 | 'e8f8' 150 | ]) 151 | }) 152 | 153 | test('black castle short without rook', () => { 154 | type Game = ParseFen<'r2pk3/3ppp2/8/8/8/8/8/8 b KQkq - 0 1'> 155 | 156 | type Result = ToSans> 157 | 158 | assertType([ 159 | 'e8f8' 160 | ]) 161 | }) 162 | 163 | test('black castle short without rights', () => { 164 | type Game = ParseFen<'r2pk2r/3ppp2/8/8/8/8/8/8 b KQq - 0 1'> 165 | 166 | type Result = ToSans> 167 | 168 | assertType([ 169 | 'e8f8' 170 | ]) 171 | }) 172 | 173 | test('black castle long', () => { 174 | type Game = ParseFen<'r2pk2r/3ppp2/8/8/8/8/8/8 b KQkq - 0 1'> 175 | 176 | type Result = ToSans> 177 | 178 | assertType([ 179 | 'e8f8', 'e8g8' 180 | ]) 181 | }) 182 | 183 | test('black castle long blocked 2', () => { 184 | type Game = ParseFen<'r1p1kp1r/3ppp2/8/8/8/8/8/8 b KQkq - 0 1'> 185 | 186 | type Result = ToSans> 187 | 188 | assertType([ 189 | 'e8d8' 190 | ]) 191 | }) 192 | 193 | test('black castle long blocked 3', () => { 194 | type Game = ParseFen<'rp2kp1r/3ppp2/8/8/8/8/8/8 b KQkq - 0 1'> 195 | 196 | type Result = ToSans> 197 | 198 | assertType([ 199 | 'e8d8' 200 | ]) 201 | }) 202 | 203 | test('black castle long without rook', () => { 204 | type Game = ParseFen<'4kp1r/3ppp2/8/8/8/8/8/8 b KQkq - 0 1'> 205 | 206 | type Result = ToSans> 207 | 208 | assertType([ 209 | 'e8d8' 210 | ]) 211 | }) 212 | 213 | test('black castle long without rights', () => { 214 | type Game = ParseFen<'r3kp1r/3ppp2/8/8/8/8/8/8 b KQk - 0 1'> 215 | 216 | type Result = ToSans> 217 | 218 | assertType([ 219 | 'e8d8', 220 | ]) 221 | }) 222 | }) 223 | -------------------------------------------------------------------------------- /tests/knight.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, describe, test } from 'vitest' 2 | import type { KnightMoves } from '@/pieces/knight' 3 | import type { ParseFen, ToSans } from '@/notation' 4 | import type { PositionIndex } from '@/base' 5 | 6 | describe('KnightMoves', () => { 7 | test('e5', () => { 8 | type Game = ParseFen<'8/3r4/6R1/4N3/8/8/8/8 w - - 0 1'> 9 | 10 | type Result = ToSans> 11 | 12 | assertType([ 13 | 'e5d7', 'e5c6', 'e5f7', 'e5c4', 'e5d3', 'e5g4', 'e5f3' 14 | ]) 15 | }) 16 | 17 | test('c2', () => { 18 | type Game = ParseFen<'8/8/8/8/8/8/2n5/8 b - - 0 1'> 19 | 20 | type Result = ToSans> 21 | 22 | assertType([ 23 | 'c2b4', 'c2a3', 'c2d4', 'c2e3', 'c2a1', 'c2e1' 24 | ]) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /tests/notation.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, describe, test } from 'vitest' 2 | import type { PositionIndex } from '@/base' 3 | 4 | import type { 5 | FormatBoard, 6 | FormatCastling, 7 | FormatGame, 8 | FormatSan, 9 | ParseBoard, 10 | ParseCastling, 11 | ParseFen, 12 | ParseSan, 13 | } from '@/notation' 14 | 15 | describe('FormatBoard', () => { 16 | test('initial position', () => { 17 | type Result = FormatBoard<'rnbqkbnrpppppppp________________________________PPPPPPPPRNBQKBNR'> 18 | 19 | assertType('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR') 20 | }) 21 | 22 | test('empty board', () => { 23 | type Result = FormatBoard<'________________________________________________________________'> 24 | 25 | assertType('8/8/8/8/8/8/8/8') 26 | }) 27 | 28 | test('partially empty ranks', () => { 29 | type Result = FormatBoard<'p_______pp______ppp_____pppp____ppppp___pppppp__ppppppp_pppppppp'> 30 | 31 | assertType('p7/pp6/ppp5/pppp4/ppppp3/pppppp2/ppppppp1/pppppppp') 32 | }) 33 | 34 | test('error', () => { 35 | type Result = FormatBoard<'whoops'> extends never ? true : false 36 | 37 | assertType(true) 38 | }) 39 | }) 40 | 41 | describe('FormatCastling', () => { 42 | test('all rights', () => { 43 | type Result = FormatCastling<{ K: true, Q: true, k: true, q: true }> 44 | 45 | assertType('KQkq') 46 | }) 47 | 48 | test('no rights', () => { 49 | type Result = FormatCastling<{ K: false, Q: false, k: false, q: false }> 50 | 51 | assertType('-') 52 | }) 53 | 54 | test('some rights', () => { 55 | type Result = FormatCastling<{ K: true, Q: false, k: true, q: false }> 56 | 57 | assertType('Kk') 58 | }) 59 | }) 60 | 61 | describe('FormatGame', () => { 62 | test('starting position', () => { 63 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 64 | 65 | type Result = FormatGame 66 | 67 | assertType('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1') 68 | }) 69 | 70 | test('empty board', () => { 71 | type Game = ParseFen<'8/8/8/8/8/8/8/8 w KQkq - 0 1'> 72 | 73 | type Result = FormatGame 74 | 75 | assertType('8/8/8/8/8/8/8/8 w KQkq - 0 1') 76 | }) 77 | 78 | test('en passant', () => { 79 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1'> 80 | 81 | type Result = FormatGame 82 | 83 | assertType('rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1') 84 | }) 85 | }) 86 | 87 | describe('FormatSan', () => { 88 | test('a7a8Q', () => { 89 | type Result = FormatSan<{ 90 | castle: false, 91 | from: PositionIndex['a7'], 92 | to: PositionIndex['a8'], 93 | promotion: 'Q' 94 | }> 95 | 96 | assertType('a7a8Q') 97 | }) 98 | 99 | test('f5f6', () => { 100 | type Result = FormatSan<{ 101 | castle: false, 102 | from: PositionIndex['f5'], 103 | to: PositionIndex['f6'], 104 | promotion: '' 105 | }> 106 | 107 | assertType('f5f6') 108 | }) 109 | 110 | test('O-O', () => { 111 | type Result = FormatSan<{ 112 | castle: 'K', 113 | from: 0, 114 | to: 0, 115 | promotion: '' 116 | }> 117 | 118 | assertType('O-O') 119 | }) 120 | 121 | test('O-O-O', () => { 122 | type Result = FormatSan<{ 123 | castle: 'Q', 124 | from: 0, 125 | to: 0, 126 | promotion: '' 127 | }> 128 | 129 | assertType('O-O-O') 130 | }) 131 | 132 | test('o-o', () => { 133 | type Result = FormatSan<{ 134 | castle: 'k', 135 | from: 0, 136 | to: 0, 137 | promotion: '' 138 | }> 139 | 140 | assertType('o-o') 141 | }) 142 | 143 | test('o-o-o', () => { 144 | type Result = FormatSan<{ 145 | castle: 'q', 146 | from: 0, 147 | to: 0, 148 | promotion: '' 149 | }> 150 | 151 | assertType('o-o-o') 152 | }) 153 | }) 154 | 155 | describe('ParseBoard', () => { 156 | test('success', () => { 157 | type Result = ParseBoard<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'> 158 | 159 | assertType([ 160 | 'r', 'n', 'b', 'q', 'k', 'b', 'n', 'r', 161 | 'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p', 162 | ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 163 | ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 164 | ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 165 | ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 166 | 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P', 167 | 'R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R', 168 | ]) 169 | }) 170 | 171 | test('fail, invalid characters', () => { 172 | type Result = ParseBoard<'xnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'> // <- leading x 173 | 174 | assertType(0 as never) 175 | }) 176 | 177 | test('fail, board overflow', () => { 178 | type Result = ParseBoard<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNRp'> extends never ? true : false // <- extra pawn 179 | 180 | assertType(true) 181 | }) 182 | 183 | test('fail, board underflow', () => { 184 | type Test1 = ParseBoard<'rnbqkbnr/pppppppp/8/7/8/8/PPPPPPPP/RNBQKBNR'> extends never ? true : false // <- missing empty square 185 | type Test2 = ParseBoard<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBN'> extends never ? true : false // <- missing last piece 186 | 187 | assertType(true) 188 | assertType(true) 189 | }) 190 | }) 191 | 192 | describe('ParseCastling', () => { 193 | test('Kq', () => { 194 | type Result = ParseCastling<'Kq'> 195 | 196 | assertType({ 197 | k: false, 198 | q: true, 199 | K: true, 200 | Q: false, 201 | }) 202 | }) 203 | 204 | test('-', () => { 205 | type Result = ParseCastling<'-'> 206 | 207 | assertType({ 208 | K: false, 209 | Q: false, 210 | k: false, 211 | q: false, 212 | }) 213 | }) 214 | }) 215 | 216 | describe('ParseFen', () => { 217 | test('success', () => { 218 | type Result = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 219 | 220 | assertType({ 221 | board: [ 222 | 'r', 'n', 'b', 'q', 'k', 'b', 'n', 'r', 223 | 'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p', 224 | ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 225 | ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 226 | ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 227 | ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 228 | 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P', 229 | 'R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R', 230 | ], 231 | turn: 'w', 232 | castling: { 233 | k: true, 234 | q: true, 235 | K: true, 236 | Q: true, 237 | }, 238 | ep: null, 239 | halfmove: 0, 240 | fullmove: 1, 241 | }) 242 | }) 243 | 244 | test('success, en passant', () => { 245 | type Result = ParseFen<'8/8/8/8/3P4/8/8/8 b - d3 0 1'> 246 | 247 | assertType(true) 248 | }) 249 | 250 | test('fail, invalid turn color', () => { 251 | type Result = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR x KQkq - 0 1'> extends never ? true : false 252 | 253 | assertType(true) 254 | }) 255 | 256 | test('fail, invalid non-integer halfmove', () => { 257 | type Result = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0.5 1'> extends never ? true : false 258 | 259 | assertType(true) 260 | }) 261 | 262 | test('fail, negative halfmove', () => { 263 | type Result = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - -1 1'> extends never ? true : false 264 | 265 | assertType(true) 266 | }) 267 | 268 | test('fail, invalid non-integer halfmove', () => { 269 | type Result = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1.5'> extends never ? true : false 270 | 271 | assertType(true) 272 | }) 273 | 274 | test('fail, negative fullmove', () => { 275 | type Result = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 -1'> extends never ? true : false 276 | 277 | assertType(true) 278 | }) 279 | 280 | test('fail, invalid en passant', () => { 281 | type IllegalCharacter = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR - KQkq x 0 1'> extends never ? true : false 282 | type IllegalPosition = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR - KQkq c5 0 1'> extends never ? true : false 283 | 284 | assertType(true) 285 | assertType(true) 286 | }) 287 | }) 288 | 289 | describe('ParseSan', () => { 290 | test('e2e4', () => { 291 | type Result = ParseSan<'e2e4'> 292 | 293 | assertType({ 294 | castle: false, 295 | from: 52, 296 | to: 36, 297 | promotion: '', 298 | }) 299 | }) 300 | 301 | test('a7a8Q', () => { 302 | type Result = ParseSan<'a7a8Q'> 303 | 304 | assertType({ 305 | castle: false, 306 | from: 8, 307 | to: 0, 308 | promotion: 'Q', 309 | }) 310 | }) 311 | 312 | test('O-O', () => { 313 | type Result = ParseSan<'O-O'> 314 | 315 | assertType({ 316 | castle: 'K', 317 | from: 0, 318 | to: 0, 319 | promotion: '', 320 | }) 321 | }) 322 | 323 | test('O-O-O', () => { 324 | type Result = ParseSan<'O-O-O'> 325 | 326 | assertType({ 327 | castle: 'Q', 328 | from: 0, 329 | to: 0, 330 | promotion: '', 331 | }) 332 | }) 333 | 334 | test('o-o', () => { 335 | type Result = ParseSan<'o-o'> 336 | 337 | assertType({ 338 | castle: 'k', 339 | from: 0, 340 | to: 0, 341 | promotion: '', 342 | }) 343 | }) 344 | 345 | test('o-o-o', () => { 346 | type Result = ParseSan<'o-o-o'> 347 | 348 | assertType({ 349 | castle: 'q', 350 | from: 0, 351 | to: 0, 352 | promotion: '', 353 | }) 354 | }) 355 | 356 | test('whoops', () => { 357 | type Result = ParseSan<'whoops'> extends never ? true : false 358 | 359 | assertType(true) 360 | }) 361 | }) 362 | -------------------------------------------------------------------------------- /tests/pawn.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, describe, test } from 'vitest' 2 | import type { ParseFen, ToSans } from '@/notation' 3 | import type { PawnMoves } from '@/pieces/pawn' 4 | import type { PositionIndex } from '@/base' 5 | 6 | describe('PawnMoves', () => { 7 | test('white advance forward', () => { 8 | type Game = ParseFen<'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'> 9 | 10 | type Result = ToSans> 11 | 12 | assertType([ 13 | 'e2e3', 'e2e4' 14 | ]) 15 | }) 16 | 17 | test('white advance forward blocked friendly', () => { 18 | type Game = ParseFen<'8/8/8/8/8/3P4/3P4/8 w - - 0 1'> 19 | 20 | type Result = ToSans> 21 | 22 | assertType([]) 23 | }) 24 | 25 | test('white advance forward blocked enemy', () => { 26 | type Game = ParseFen<'8/8/8/8/8/3p4/3P4/8 w - - 0 1'> 27 | 28 | type Result = ToSans> 29 | 30 | assertType([]) 31 | }) 32 | 33 | test('white second advance two blocked friendly', () => { 34 | type Game = ParseFen<'8/8/8/8/3P4/8/3P4/8 w - - 0 1'> 35 | 36 | type Result = ToSans> 37 | 38 | assertType(['d2d3']) 39 | }) 40 | 41 | test('white second advance two blocked enemy', () => { 42 | type Game = ParseFen<'8/8/8/8/3p4/8/3P4/8 w - - 0 1'> 43 | 44 | type Result = ToSans> 45 | 46 | assertType(['d2d3']) 47 | }) 48 | 49 | test('white capture portside', () => { 50 | type Game = ParseFen<'8/8/8/8/8/2r2r2/3PP3/8 w - - 0 1'> 51 | 52 | type Result = ToSans> 53 | 54 | assertType([ 55 | 'd2d3', 'd2d4', 'd2c3' 56 | ]) 57 | }) 58 | 59 | test('white capture starboard', () => { 60 | type Game = ParseFen<'8/8/8/8/8/2r2r2/3PP3/8 w - - 0 1'> 61 | 62 | type Result = ToSans> 63 | 64 | assertType([ 65 | 'e2e3', 'e2e4', 'e2f3' 66 | ]) 67 | }) 68 | 69 | test('no friendly capture', () => { 70 | type Game = ParseFen<'8/8/3pp3/2r2r2/2R2R2/3PP3/8/8 w - - 0 1'> 71 | 72 | type BlackPortside = ToSans> 73 | type BlackStarboard = ToSans> 74 | type WhitePortside = ToSans> 75 | type WhiteStarboard = ToSans> 76 | 77 | assertType(['d6d5']) 78 | assertType(['e6e5']) 79 | assertType(['d3d4']) 80 | assertType(['e3e4']) 81 | }) 82 | 83 | test('only advance twice from starting position', () => { 84 | type Game = ParseFen<'8/8/4p3/8/8/3P4/8/8 w - - 0 1'> 85 | 86 | type BlackResult = ToSans> 87 | type WhiteResult = ToSans> 88 | 89 | assertType(['e6e5']) 90 | assertType(['d3d4']) 91 | }) 92 | 93 | test('white en passant portside', () => { 94 | // ... 95 | type Game = ParseFen<'8/8/8/3pP3/8/8/8/8 w - d6 0 2'> 96 | 97 | type Result = ToSans> 98 | 99 | assertType(['e5e6', 'e5d6']) 100 | }) 101 | 102 | test('white en passant starboard', () => { 103 | type Game = ParseFen<'8/8/8/3Pp3/8/8/8/8 w - e6 0 2'> 104 | 105 | type Result = ToSans> 106 | 107 | assertType(['d5d6', 'd5e6']) 108 | }) 109 | 110 | test('black advance forward', () => { 111 | type Game = ParseFen<'8/4p3/8/8/8/8/8/8 w - - 0 1'> 112 | 113 | type Result = ToSans> 114 | 115 | assertType(['e7e6', 'e7e5']) 116 | }) 117 | 118 | test('black advance forward blocked friendly', () => { 119 | type Game = ParseFen<'8/4p3/4p3/8/8/8/8/8 w - - 0 1'> 120 | 121 | type Result = ToSans> 122 | 123 | assertType([]) 124 | }) 125 | 126 | test('black advance forward blocked enemy', () => { 127 | type Game = ParseFen<'8/4p3/4P3/8/8/8/8/8 w - - 0 1'> 128 | 129 | type Result = ToSans> 130 | 131 | assertType([]) 132 | }) 133 | 134 | test('black second advance forward two blocked friendly', () => { 135 | type Game = ParseFen<'8/4p3/8/4p3/8/8/8/8 w - - 0 1'> 136 | 137 | type Result = ToSans> 138 | 139 | assertType(['e7e6']) 140 | }) 141 | 142 | test('black second advance forward two blocked enemy', () => { 143 | type Game = ParseFen<'8/4p3/8/4P3/8/8/8/8 w - - 0 1'> 144 | 145 | type Result = ToSans> 146 | 147 | assertType(['e7e6']) 148 | }) 149 | 150 | test('black capture portside', () => { 151 | type Game = ParseFen<'8/8/3pp3/2R2R2/8/8/8/8 w - - 0 1'> 152 | 153 | type Result = ToSans> 154 | 155 | assertType(['e6e5', 'e6f5']) 156 | }) 157 | 158 | test('black capture starboard', () => { 159 | type Game = ParseFen<'8/8/3pp3/2R2R2/8/8/8/8 w - - 0 1'> 160 | 161 | type Result = ToSans> 162 | 163 | assertType(['d6d5', 'd6c5']) 164 | }) 165 | 166 | test('black en passant portside', () => { 167 | type Game = ParseFen<'8/8/8/8/3pP3/8/8/8 b - e3 0 1'> 168 | 169 | type Result = ToSans> 170 | 171 | assertType(['d4d3', 'd4e3']) 172 | }) 173 | 174 | test('black en passant starboard', () => { 175 | type Game = ParseFen<'8/8/8/8/3Pp3/8/8/8 b - d3 0 1'> 176 | 177 | type Result = ToSans> 178 | 179 | assertType(['e4e3', 'e4d3']) 180 | }) 181 | 182 | test('white promotion', () => { 183 | type Game = ParseFen<'4r3/3P4/8/8/8/8/8/8 w - - 0 1'> 184 | 185 | type Result = ToSans> 186 | 187 | assertType([ 188 | 'd7d8Q', 'd7d8R', 'd7d8N', 'd7d8B', 'd7e8Q', 'd7e8R', 'd7e8N', 'd7e8B' 189 | ]) 190 | }) 191 | 192 | test('black promotion', () => { 193 | type Game = ParseFen<'8/8/8/8/8/8/4p3/3R4 b - - 0 1'> 194 | 195 | type Result = ToSans> 196 | 197 | assertType([ 198 | 'e2e1q', 'e2e1r', 'e2e1n', 'e2e1b', 'e2d1q', 'e2d1r', 'e2d1n', 'e2d1b' 199 | ]) 200 | }) 201 | }) 202 | -------------------------------------------------------------------------------- /tests/queen.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, describe, test } from 'vitest' 2 | import type { ParseFen, ToSans } from '@/notation' 3 | import type { PositionIndex } from '@/base' 4 | import type { QueenMoves } from '@/pieces/queen' 5 | 6 | describe('QueenMoves', () => { 7 | test('a1', () => { 8 | type Game = ParseFen<'q7/8/8/8/8/8/8/7Q w - - 0 1'> 9 | 10 | type Result = ToSans> 11 | 12 | assertType([ 13 | 'a1a2', 'a1a3', 'a1a4', 'a1a5', 'a1a6', 'a1a7', 'a1a8', 'a1b2', 'a1c3', 'a1d4', 'a1e5', 'a1f6', 'a1g7', 'a1h8', 'a1b1', 'a1c1', 'a1d1', 'a1e1', 'a1f1', 'a1g1' 14 | ]) 15 | }) 16 | 17 | test('c3', () => { 18 | type Game = ParseFen<'8/2R3r1/8/8/8/2Q5/8/8 w - - 0 1'> 19 | 20 | type Result = ToSans> 21 | 22 | assertType([ 23 | 'c3b4', 'c3a5', 'c3c4', 'c3c5', 'c3c6', 'c3d4', 'c3e5', 'c3f6', 'c3g7', 'c3b3', 'c3a3', 'c3d3', 'c3e3', 'c3f3', 'c3g3', 'c3h3', 'c3b2', 'c3a1', 'c3c2', 'c3c1', 'c3d2', 'c3e1' 24 | ]) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /tests/rook.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, describe, test } from 'vitest' 2 | import type { ParseFen, ToSans } from '@/notation' 3 | import type { PositionIndex } from '@/base' 4 | import type { RookMoves } from '@/pieces/rook' 5 | 6 | describe('RookMoves', () => { 7 | test('a1', () => { 8 | type Game = ParseFen<'q7/8/8/8/8/8/8/7Q w - - 0 1'> 9 | 10 | type Result = ToSans> 11 | 12 | assertType(['a1a2', 'a1a3', 'a1a4', 'a1a5', 'a1a6', 'a1a7', 'a1a8', 'a1b1', 'a1c1', 'a1d1', 'a1e1', 'a1f1', 'a1g1']) 13 | }) 14 | 15 | test('d4', () => { 16 | type Game = ParseFen<'8/8/8/8/8/8/8/8 w - - 0 1'> 17 | 18 | type Result = ToSans> 19 | 20 | assertType(['d4d5', 'd4d6', 'd4d7', 'd4d8', 'd4c4', 'd4b4', 'd4a4', 'd4e4', 'd4f4', 'd4g4', 'd4h4', 'd4d3', 'd4d2', 'd4d1']) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /tests/string.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, test } from 'vitest' 2 | import type { Includes, Int, IsLength } from '@/utils' 3 | 4 | test('Includes', () => { 5 | assertType>(true) // <- first 6 | assertType>(true) // <- middle 7 | assertType>(true) // <- last 8 | assertType>(false) // <- not in string 9 | }) 10 | 11 | test('Int', () => { 12 | assertType>(0) 13 | assertType>(1) 14 | assertType>(1234) 15 | assertType>(0 as never) 16 | assertType>(0 as never) 17 | assertType>(0 as never) 18 | }) 19 | 20 | test('IsLength', () => { 21 | assertType>(true) // <- exact 22 | assertType>(false) // <- less 23 | assertType>(false) // <- more 24 | }) 25 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["../src/*"], 5 | }, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/utils.test-d.ts: -------------------------------------------------------------------------------- 1 | import { assertType, test } from 'vitest' 2 | 3 | import type { 4 | Increment, 5 | IsGreater, 6 | Sum, 7 | } from '@/utils' 8 | 9 | test('Increment', () => { 10 | type Result = Increment<0> 11 | 12 | assertType(1) 13 | 14 | type Result2 = Increment 15 | 16 | assertType(2) 17 | }) 18 | 19 | test('IsGreater', () => { 20 | type Test1 = IsGreater<-1, 1> 21 | type Test2 = IsGreater<1, -1> 22 | type Test3 = IsGreater<-3, -2> 23 | type Test4 = IsGreater<-2, -3> 24 | 25 | assertType(false) 26 | assertType(true) 27 | assertType(false) 28 | assertType(true) 29 | }) 30 | 31 | test('Sum', () => { 32 | type Test1 = Sum<10, 10> 33 | type Test2 = Sum<-10, 10> 34 | type Test3 = Sum<10, -10> 35 | type Test4 = Sum<5, -10> 36 | type Test5 = Sum<10, -5> 37 | type test6 = Sum<-5, 10> 38 | type Test7 = Sum<-10, 5> 39 | type Test8 = Sum<-10, -10> 40 | 41 | assertType(20) 42 | assertType(0) 43 | assertType(0) 44 | assertType(-5) 45 | assertType(5) 46 | assertType(5) 47 | assertType(-5) 48 | assertType(-20) 49 | }) 50 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "lib": ["esnext"], 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "noErrorTruncation": true, 11 | "noImplicitAny": true, 12 | "outDir": "dist", 13 | "paths": { 14 | "@/*": ["src/*"], 15 | }, 16 | "rootDir": ".", 17 | "skipLibCheck": true, 18 | "strict": true, 19 | "strictNullChecks": true, 20 | "target": "es2016", 21 | }, 22 | "include": [ 23 | "src/**/*", 24 | "tests/**/*" 25 | ] 26 | } -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | resolve: { 5 | alias: { 6 | '@': '/src', 7 | }, 8 | }, 9 | }) 10 | --------------------------------------------------------------------------------