@mirasen/chessboard 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +10 -13
- package/dist/extensions/factory/runtime.js +4 -1
- package/dist/extensions/first-party/legal-moves/types.js +4 -4
- package/dist/runtime/mutation/last-move.js +1 -1
- package/dist/state/board/check.d.ts +4 -3
- package/dist/state/board/check.js +6 -1
- package/dist/state/board/factory.js +3 -5
- package/dist/state/board/fen/main.d.ts +3 -3
- package/dist/state/board/fen/main.js +1 -1
- package/dist/state/board/position.d.ts +2 -2
- package/dist/state/board/reducers.d.ts +3 -5
- package/dist/state/board/reducers.js +13 -6
- package/dist/state/board/types/internal.d.ts +6 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @mirasen/chessboard
|
|
2
2
|
|
|
3
|
+
## 1.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8b3781c: dependabot: directory '/', update @ktarmyshov/assert
|
|
8
|
+
- 8b3781c: dependabot: directory '/', update es-toolkit
|
|
9
|
+
- 8b3781c: dependabot: directory '/', update typescript-eslint
|
|
10
|
+
- c877d35: dependabot: directory '/', update jsdom
|
|
11
|
+
|
|
3
12
|
## 1.1.0
|
|
4
13
|
|
|
5
14
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
A framework-agnostic TypeScript chessboard platform with real chess interaction built in and an extension-driven architecture.
|
|
10
10
|
|
|
11
|
+
## Try it live
|
|
12
|
+
|
|
13
|
+
- [Play a chess.js game](https://mirasen.io/chessboard/examples/chessjs.html)
|
|
14
|
+
- [Try the promotion flow](https://mirasen.io/chessboard/examples/promotion.html)
|
|
15
|
+
- [Try the minimal example](https://mirasen.io/chessboard/examples/minimal.html)
|
|
16
|
+
|
|
11
17
|
## Why this exists
|
|
12
18
|
|
|
13
19
|
Most chessboards fall into one of two buckets:
|
|
@@ -68,15 +74,6 @@ Rules and legality still belong to your game layer. Use the built-in `chess.js`
|
|
|
68
74
|
npm install @mirasen/chessboard
|
|
69
75
|
```
|
|
70
76
|
|
|
71
|
-
## Live examples
|
|
72
|
-
|
|
73
|
-
Interactive examples are available on the Mirasen website:
|
|
74
|
-
|
|
75
|
-
- [Examples overview](https://mirasen.io/chessboard/examples/)
|
|
76
|
-
- [Minimal interactive example](https://mirasen.io/chessboard/examples/minimal.html)
|
|
77
|
-
- [Promotion example](https://mirasen.io/chessboard/examples/promotion.html)
|
|
78
|
-
- [chess.js game example](https://mirasen.io/chessboard/examples/chessjs.html)
|
|
79
|
-
|
|
80
77
|
## Usage
|
|
81
78
|
|
|
82
79
|
### Minimal example
|
|
@@ -297,12 +294,16 @@ A board that only renders pieces and leaves everything else to app code often fo
|
|
|
297
294
|
|
|
298
295
|
A board that handles everything internally without clean boundaries becomes difficult to evolve, customize, or extend.
|
|
299
296
|
|
|
297
|
+
### Opt out, not build up
|
|
298
|
+
|
|
300
299
|
`@mirasen/chessboard` aims for a middle path:
|
|
301
300
|
|
|
302
301
|
- strong built-in baseline interaction
|
|
303
302
|
- clean internal architecture
|
|
304
303
|
- extension-driven feature growth
|
|
305
304
|
|
|
305
|
+
Common chessboard behavior is included by default: rendering, interaction, selection, legal target feedback, promotion, and animation. You start from a working interaction baseline and remove or replace extensions only when you want less or need something different.
|
|
306
|
+
|
|
306
307
|
## Project direction
|
|
307
308
|
|
|
308
309
|
This platform is being built as a foundation for richer chess learning and interaction workflows, but the board itself is designed to stand as a serious open-source UI platform in its own right.
|
|
@@ -323,7 +324,3 @@ For details, see:
|
|
|
323
324
|
|
|
324
325
|
- [./assets/pieces/chessnut/ATTRIBUTION.md](./assets/pieces/chessnut/ATTRIBUTION.md)
|
|
325
326
|
- [./assets/pieces/chessnut/LICENSE.txt](./assets/pieces/chessnut/LICENSE.txt)
|
|
326
|
-
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
```
|
|
@@ -45,8 +45,11 @@ function createExtensionRuntimeSurfaceEvents(getInternalState, runtimeSurfaceEve
|
|
|
45
45
|
subscribers = new Set();
|
|
46
46
|
internalState.eventSubscribers.set(type, subscribers);
|
|
47
47
|
}
|
|
48
|
+
const sizeBeforeAdd = subscribers.size;
|
|
48
49
|
subscribers.add(extensionDef.id);
|
|
49
|
-
|
|
50
|
+
if (sizeBeforeAdd === 0) {
|
|
51
|
+
runtimeSurfaceEvents.subscribeEvent(type);
|
|
52
|
+
}
|
|
50
53
|
},
|
|
51
54
|
unsubscribeEvent(type) {
|
|
52
55
|
const internalState = getInternalState();
|
|
@@ -6,14 +6,14 @@ export const DEFAULT_CONFIG = {
|
|
|
6
6
|
color: 'rgb(0, 0, 0)',
|
|
7
7
|
opacity: 0.14
|
|
8
8
|
},
|
|
9
|
-
radiusRatio: 0.
|
|
10
|
-
},
|
|
9
|
+
radiusRatio: 0.165
|
|
10
|
+
},
|
|
11
11
|
captureTarget: {
|
|
12
12
|
color: {
|
|
13
13
|
color: 'rgb(0, 0, 0)',
|
|
14
14
|
opacity: 0.14
|
|
15
15
|
},
|
|
16
|
-
radiusRatio: 0.
|
|
17
|
-
strokeWidthRatio: 0.
|
|
16
|
+
radiusRatio: 0.445,
|
|
17
|
+
strokeWidthRatio: 0.11
|
|
18
18
|
}
|
|
19
19
|
};
|
|
@@ -11,7 +11,7 @@ export const updateLastMoveOnBoardMove = (context, mutationSession) => {
|
|
|
11
11
|
export const clearLastMoveOnBoardSetPosition = (context, mutationSession) => {
|
|
12
12
|
const { current } = context;
|
|
13
13
|
if (!mutationSession.hasMutation({
|
|
14
|
-
causes: ['state.board.setPosition', 'state.board.setPiecePosition']
|
|
14
|
+
causes: ['state.board.setPosition', 'state.board.setPiecePosition', 'state.board.setTurn']
|
|
15
15
|
})) {
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Color, ColorShort, MoveRequestInput, Piece, PiecePositionInput, PiecePositionRecord, PiecePositionRecordShort, PiecePositionRecordString, PieceShort, PieceString, Role, RoleShort, SquareString } from './types/input.js';
|
|
2
|
-
import { EmptyPieceCode, MoveRequest, NonEmptyPieceCode, PieceCode, RolePromotionCode, Square, SquareFile, SquareRank } from './types/internal.js';
|
|
3
|
-
import { BoardStateSnapshot } from './types/main.js';
|
|
2
|
+
import { EmptyPieceCode, MoveRequest, NonEmptyPieceCode, PieceCode, PiecePositionSnapshot, PositionSnapshot, RolePromotionCode, Square, SquareFile, SquareRank } from './types/internal.js';
|
|
3
|
+
import type { BoardStateSnapshot } from './types/main.js';
|
|
4
4
|
export declare function isWhitePieceCode(code: NonEmptyPieceCode): boolean;
|
|
5
5
|
export declare function isBlackPieceCode(code: NonEmptyPieceCode): boolean;
|
|
6
6
|
export declare function isOccupied(board: BoardStateSnapshot, sq: Square): boolean;
|
|
7
7
|
export declare function isEmptyPieceCode(code: PieceCode): code is EmptyPieceCode;
|
|
8
8
|
export declare function isNonEmptyPieceCode(code: PieceCode): code is NonEmptyPieceCode;
|
|
9
|
-
export declare function piecePositionsEqual(pos1:
|
|
9
|
+
export declare function piecePositionsEqual(pos1: PiecePositionSnapshot, pos2: PiecePositionSnapshot): boolean;
|
|
10
|
+
export declare function positionsEqual(pos1: PositionSnapshot, pos2: PositionSnapshot): boolean;
|
|
10
11
|
export declare function isRank(n: number): n is SquareRank;
|
|
11
12
|
export declare function isFile(n: number): n is SquareFile;
|
|
12
13
|
export declare function isValidSquare(n: number): n is Square;
|
|
@@ -15,7 +15,7 @@ export function isNonEmptyPieceCode(code) {
|
|
|
15
15
|
return code !== 0 /* PieceCode.Empty */;
|
|
16
16
|
}
|
|
17
17
|
export function piecePositionsEqual(pos1, pos2) {
|
|
18
|
-
if (pos1.
|
|
18
|
+
if (pos1.pieces.length !== pos2.pieces.length)
|
|
19
19
|
return false;
|
|
20
20
|
for (let i = 0; i < pos1.pieces.length; i++) {
|
|
21
21
|
if (pos1.pieces[i] !== pos2.pieces[i])
|
|
@@ -23,6 +23,11 @@ export function piecePositionsEqual(pos1, pos2) {
|
|
|
23
23
|
}
|
|
24
24
|
return true;
|
|
25
25
|
}
|
|
26
|
+
export function positionsEqual(pos1, pos2) {
|
|
27
|
+
if (pos1.turn !== pos2.turn)
|
|
28
|
+
return false;
|
|
29
|
+
return piecePositionsEqual(pos1, pos2);
|
|
30
|
+
}
|
|
26
31
|
export function isRank(n) {
|
|
27
32
|
return Number.isInteger(n) && n >= 0 && n < 8;
|
|
28
33
|
}
|
|
@@ -2,7 +2,7 @@ import { cloneDeep } from 'es-toolkit/object';
|
|
|
2
2
|
import { isNormalizedMoveRequest } from './check.js';
|
|
3
3
|
import { normalizeColor, normalizeMoveRequest } from './normalize.js';
|
|
4
4
|
import { boardParsePiecePositionInput, boardParsePosition } from './position.js';
|
|
5
|
-
import { boardMove, boardSetPosition, boardSetTurn } from './reducers.js';
|
|
5
|
+
import { boardMove, boardSetPiecePosition, boardSetPosition, boardSetTurn } from './reducers.js';
|
|
6
6
|
function createBoardStateInternal(position) {
|
|
7
7
|
const parsedPosition = boardParsePosition(position);
|
|
8
8
|
return {
|
|
@@ -16,13 +16,11 @@ export function createBoardState(position) {
|
|
|
16
16
|
return {
|
|
17
17
|
setPosition(input, mutationSession) {
|
|
18
18
|
const position = boardParsePosition(input);
|
|
19
|
-
|
|
20
|
-
changed = boardSetTurn(internalState, position.turn) || changed;
|
|
21
|
-
return mutationSession.addMutation('state.board.setPosition', changed);
|
|
19
|
+
return mutationSession.addMutation('state.board.setPosition', boardSetPosition(internalState, position));
|
|
22
20
|
},
|
|
23
21
|
setPiecePosition(input, mutationSession) {
|
|
24
22
|
const pieces = boardParsePiecePositionInput(input);
|
|
25
|
-
return mutationSession.addMutation('state.board.setPiecePosition',
|
|
23
|
+
return mutationSession.addMutation('state.board.setPiecePosition', boardSetPiecePosition(internalState, pieces));
|
|
26
24
|
},
|
|
27
25
|
setTurn(turn, mutationSession) {
|
|
28
26
|
const normalizedTurn = normalizeColor(turn);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { FenString } from '../types/input.js';
|
|
2
|
-
import {
|
|
2
|
+
import { PositionSnapshot } from '../types/internal.js';
|
|
3
3
|
/**
|
|
4
|
-
* Parse a FEN string into a {@link
|
|
4
|
+
* Parse a FEN string into a {@link PositionSnapshot}.
|
|
5
5
|
*
|
|
6
6
|
* Only the first two FEN fields (piece placement + active color) are required
|
|
7
7
|
* for a board-only representation. The remaining fields (castling, en-passant,
|
|
8
8
|
* half-move clock, full-move number) are accepted but ignored.
|
|
9
9
|
*/
|
|
10
|
-
export declare function parseFen(fen: FenString):
|
|
10
|
+
export declare function parseFen(fen: FenString): PositionSnapshot;
|
|
@@ -74,7 +74,7 @@ function parseTurn(turn) {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
|
-
* Parse a FEN string into a {@link
|
|
77
|
+
* Parse a FEN string into a {@link PositionSnapshot}.
|
|
78
78
|
*
|
|
79
79
|
* Only the first two FEN fields (piece placement + active color) are required
|
|
80
80
|
* for a board-only representation. The remaining fields (castling, en-passant,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { PiecePositionInput, PositionInput } from './types/input.js';
|
|
2
|
-
import {
|
|
2
|
+
import { PositionSnapshot } from './types/internal.js';
|
|
3
3
|
export declare function boardParsePiecePositionInput(input: PiecePositionInput): Uint8Array;
|
|
4
|
-
export declare function boardParsePosition(input: PositionInput):
|
|
4
|
+
export declare function boardParsePosition(input: PositionInput): PositionSnapshot;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { ColorCode, Move, MoveRequest } from './types/internal.js';
|
|
1
|
+
import { ColorCode, Move, MoveRequest, PositionSnapshot } from './types/internal.js';
|
|
2
2
|
import { BoardStateInternal } from './types/main.js';
|
|
3
|
-
export declare function
|
|
4
|
-
/**
|
|
5
|
-
* Set active color turn.
|
|
6
|
-
*/
|
|
3
|
+
export declare function boardSetPiecePosition(state: BoardStateInternal, pieces: Uint8Array): boolean;
|
|
7
4
|
export declare function boardSetTurn(state: BoardStateInternal, turn: ColorCode): boolean;
|
|
5
|
+
export declare function boardSetPosition(state: BoardStateInternal, position: PositionSnapshot): boolean;
|
|
8
6
|
export declare function boardMove(state: BoardStateInternal, request: MoveRequest): Move;
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
import { isEmptyPieceCode } from './check.js';
|
|
1
|
+
import { isEmptyPieceCode, piecePositionsEqual, positionsEqual } from './check.js';
|
|
2
2
|
import { denormalizeSquare } from './denormalize.js';
|
|
3
3
|
import { fromPieceCode, toPieceCode } from './piece.js';
|
|
4
|
-
export function
|
|
4
|
+
export function boardSetPiecePosition(state, pieces) {
|
|
5
|
+
if (piecePositionsEqual(state, { pieces }))
|
|
6
|
+
return false; // no-op
|
|
5
7
|
state.pieces = new Uint8Array(pieces);
|
|
6
|
-
// Increment position epoch to prevent false animation across position resets
|
|
7
8
|
state.positionEpoch++;
|
|
8
9
|
return true;
|
|
9
10
|
}
|
|
10
|
-
/**
|
|
11
|
-
* Set active color turn.
|
|
12
|
-
*/
|
|
13
11
|
export function boardSetTurn(state, turn) {
|
|
14
12
|
if (state.turn === turn)
|
|
15
13
|
return false; // no-op
|
|
16
14
|
state.turn = turn;
|
|
15
|
+
state.positionEpoch++;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
export function boardSetPosition(state, position) {
|
|
19
|
+
if (positionsEqual(state, position))
|
|
20
|
+
return false; // no-op
|
|
21
|
+
state.pieces = new Uint8Array(position.pieces);
|
|
22
|
+
state.turn = position.turn;
|
|
23
|
+
state.positionEpoch++;
|
|
17
24
|
return true;
|
|
18
25
|
}
|
|
19
26
|
function buildMoveBase(state, request) {
|
|
@@ -48,7 +48,9 @@ export type MoveBaseSnapshot = ReadonlyDeep<MoveBase>;
|
|
|
48
48
|
export type MoveSnapshot = ReadonlyDeep<Move>;
|
|
49
49
|
export declare const FILE_START: number;
|
|
50
50
|
export declare const RANK_START: number;
|
|
51
|
-
export
|
|
52
|
-
pieces: Uint8Array
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
export interface PiecePositionSnapshot {
|
|
52
|
+
readonly pieces: Readonly<Uint8Array>;
|
|
53
|
+
}
|
|
54
|
+
export interface PositionSnapshot extends PiecePositionSnapshot {
|
|
55
|
+
readonly turn: ColorCode;
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mirasen/chessboard",
|
|
3
3
|
"description": "Framework-agnostic TypeScript chessboard platform with real chess interaction built in.",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Mirasen",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"check": "tsc --skipLibCheck --noEmit",
|
|
62
62
|
"check:test": "tsc --project tsconfig-test.json --skipLibCheck --noEmit",
|
|
63
63
|
"test": "npm run check:test && vitest run --run",
|
|
64
|
-
"coverage": "vitest run --coverage",
|
|
64
|
+
"coverage": "npm run check:test && vitest run --coverage",
|
|
65
65
|
"changeset:version": "changeset version && npm install && npm run format && git add --all",
|
|
66
66
|
"changeset:publish": "changeset publish"
|
|
67
67
|
},
|