@mirasen/chessboard 1.0.3 → 1.0.4
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 +6 -0
- package/README.md +119 -100
- package/dist/index.d.ts +3 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ This is not just a board renderer. It is a chessboard platform designed for real
|
|
|
24
24
|
|
|
25
25
|
## What you get out of the box
|
|
26
26
|
|
|
27
|
-
The
|
|
27
|
+
The default first-party extension baseline includes rendering, events, selection, active target feedback, legal move hints, last move feedback, promotion UI, and optional auto-promotion.
|
|
28
28
|
|
|
29
29
|
- `renderer` — the first-party rendering extension that validates the same extension architecture used for board features
|
|
30
30
|
- `events`
|
|
@@ -78,44 +78,48 @@ if (!element) {
|
|
|
78
78
|
const board = createBoard({
|
|
79
79
|
element
|
|
80
80
|
});
|
|
81
|
-
|
|
81
|
+
|
|
82
|
+
// User moves are disabled by default.
|
|
83
|
+
// Enable free movement when you want unconstrained UI moves.
|
|
82
84
|
board.setMovability({
|
|
83
85
|
mode: 'free'
|
|
84
86
|
});
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
###
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
import { createBoard, type Chessboard, type ChessboardExtensionInput } from '@mirasen/chessboard';
|
|
91
|
-
|
|
92
|
-
const extensionList = [
|
|
93
|
-
// Always add renderer first, otherwise nothing will be rendered
|
|
94
|
-
'renderer',
|
|
95
|
-
'lastMove',
|
|
96
|
-
// Keep autoPromote before promotion, otherwise promotion may defer the move first
|
|
97
|
-
'autoPromote',
|
|
98
|
-
'promotion'
|
|
99
|
-
// customExtensionDefinition
|
|
100
|
-
] as const satisfies readonly ChessboardExtensionInput[];
|
|
101
|
-
let board: Chessboard<typeof extensionList> | null = null;
|
|
102
|
-
|
|
103
|
-
const element = document.getElementById('board');
|
|
104
|
-
|
|
105
|
-
if (!element) {
|
|
106
|
-
throw new Error('Missing board element');
|
|
107
|
-
}
|
|
89
|
+
### Connect to a game/rules layer
|
|
108
90
|
|
|
109
|
-
board
|
|
110
|
-
element,
|
|
111
|
-
extensions: extensionList
|
|
112
|
-
});
|
|
113
|
-
```
|
|
91
|
+
`@mirasen/chessboard` handles board interaction and move intent. Your application can keep chess rules and game state in its own game/rules layer.
|
|
114
92
|
|
|
115
|
-
|
|
93
|
+
A typical integration uses a small adapter between your game move format and the board move format. The `game` object below represents your own chess rules layer, such as chess.js, a server-backed game model, or a custom engine.
|
|
116
94
|
|
|
117
95
|
```ts
|
|
118
|
-
import {
|
|
96
|
+
import {
|
|
97
|
+
createBoard,
|
|
98
|
+
type MoveDestinationInput,
|
|
99
|
+
type MoveOutput,
|
|
100
|
+
type MoveRequestInput
|
|
101
|
+
} from '@mirasen/chessboard';
|
|
102
|
+
|
|
103
|
+
type GameMove = {
|
|
104
|
+
from: string;
|
|
105
|
+
to: string;
|
|
106
|
+
capturedSquare?: string;
|
|
107
|
+
secondary?: {
|
|
108
|
+
from: string;
|
|
109
|
+
to: string;
|
|
110
|
+
};
|
|
111
|
+
promotedTo?: 'queen' | 'rook' | 'bishop' | 'knight';
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
type GameDestination = {
|
|
115
|
+
to: string;
|
|
116
|
+
capturedSquare?: string;
|
|
117
|
+
secondary?: {
|
|
118
|
+
from: string;
|
|
119
|
+
to: string;
|
|
120
|
+
};
|
|
121
|
+
promotedTo?: ('queen' | 'rook' | 'bishop' | 'knight')[];
|
|
122
|
+
};
|
|
119
123
|
|
|
120
124
|
const element = document.getElementById('board');
|
|
121
125
|
|
|
@@ -123,95 +127,72 @@ if (!element) {
|
|
|
123
127
|
throw new Error('Missing board element');
|
|
124
128
|
}
|
|
125
129
|
|
|
130
|
+
const game = createYourGameEngine(); // Your app/game layer.
|
|
126
131
|
const board = createBoard({
|
|
127
|
-
element
|
|
128
|
-
state: {
|
|
129
|
-
board: 'start',
|
|
130
|
-
interaction: {
|
|
131
|
-
movability: {
|
|
132
|
-
mode: 'free'
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
board.extensions.events.setOnUIMove((move) => {
|
|
139
|
-
console.log('UI move:', move);
|
|
132
|
+
element
|
|
140
133
|
});
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### setMovability - strict with explicit destinations
|
|
144
134
|
|
|
145
|
-
|
|
146
|
-
import { createBoard } from '@mirasen/chessboard';
|
|
135
|
+
board.setPosition(game.getPosition());
|
|
147
136
|
|
|
148
|
-
|
|
137
|
+
function toBoardDestination(gameDestination: GameDestination): MoveDestinationInput {
|
|
138
|
+
return {
|
|
139
|
+
to: gameDestination.to,
|
|
140
|
+
capturedSquare: gameDestination.capturedSquare,
|
|
141
|
+
secondary: gameDestination.secondary, // e.g. castling.
|
|
142
|
+
promotedTo: gameDestination.promotedTo
|
|
143
|
+
};
|
|
144
|
+
}
|
|
149
145
|
|
|
150
|
-
|
|
151
|
-
|
|
146
|
+
function toBoardMoveRequest(gameMove: GameMove): MoveRequestInput {
|
|
147
|
+
return {
|
|
148
|
+
from: gameMove.from,
|
|
149
|
+
to: gameMove.to,
|
|
150
|
+
capturedSquare: gameMove.capturedSquare,
|
|
151
|
+
secondary: gameMove.secondary,
|
|
152
|
+
promotedTo: gameMove.promotedTo
|
|
153
|
+
};
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
function toGameMove(move: MoveOutput): GameMove {
|
|
157
|
+
return {
|
|
158
|
+
from: move.from,
|
|
159
|
+
to: move.to,
|
|
160
|
+
capturedSquare: move.capturedSquare,
|
|
161
|
+
secondary: move.secondary,
|
|
162
|
+
promotedTo: move.promotedTo
|
|
163
|
+
};
|
|
164
|
+
}
|
|
157
165
|
|
|
158
166
|
board.setMovability({
|
|
159
167
|
mode: 'strict',
|
|
160
|
-
destinations: {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
{ to: 'd8', promotedTo: ['queen', 'rook', 'bishop', 'knight'] }
|
|
165
|
-
],
|
|
166
|
-
g1: [{ to: 'f3' }, { to: 'h3' }]
|
|
168
|
+
destinations: (source): MoveDestinationInput[] | undefined => {
|
|
169
|
+
const gameDestinations = game.getDestinationsFor(source);
|
|
170
|
+
|
|
171
|
+
return gameDestinations.length > 0 ? gameDestinations.map(toBoardDestination) : undefined;
|
|
167
172
|
}
|
|
168
173
|
});
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### setMovability - strict with a resolver
|
|
172
174
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
+
board.extensions.events.setOnUIMove((move) => {
|
|
176
|
+
// The board accepted and applied this UI move.
|
|
177
|
+
// Keep your game/rules layer in sync.
|
|
178
|
+
game.move(toGameMove(move));
|
|
179
|
+
});
|
|
175
180
|
|
|
176
|
-
|
|
181
|
+
function applyExternalMove(gameMove: GameMove) {
|
|
182
|
+
// Apply moves that come from outside the board UI:
|
|
183
|
+
// server, engine, opponent, imported game, replay, etc.
|
|
184
|
+
game.move(gameMove);
|
|
177
185
|
|
|
178
|
-
|
|
179
|
-
|
|
186
|
+
// Then animate/apply the same move on the board.
|
|
187
|
+
board.move(toBoardMoveRequest(gameMove));
|
|
180
188
|
}
|
|
189
|
+
```
|
|
181
190
|
|
|
182
|
-
|
|
183
|
-
element
|
|
184
|
-
});
|
|
191
|
+
`MoveDestinationInput` can describe more than a target square. For example, `secondary` can represent paired piece movement (e.g. castling), `capturedSquare` can represent captures away from the destination square (e.g. en passant), and `promotedTo` can represent promotion choices.
|
|
185
192
|
|
|
186
|
-
board.
|
|
187
|
-
mode: 'strict',
|
|
188
|
-
destinations: (source) => {
|
|
189
|
-
if (source === 'e2')
|
|
190
|
-
return [
|
|
191
|
-
{ to: 'e8', promotedTo: ['bishop', 'queen'] },
|
|
192
|
-
{ to: 'd8', promotedTo: ['rook', 'knight', 'queen'] }
|
|
193
|
-
];
|
|
194
|
-
if (source === 'f7')
|
|
195
|
-
return [
|
|
196
|
-
{ to: 'f8', promotedTo: ['bishop', 'rook', 'knight', 'queen'] },
|
|
197
|
-
{ to: 'g8', promotedTo: ['bishop', 'rook', 'knight', 'queen'] }
|
|
198
|
-
];
|
|
199
|
-
if (source === 'd7')
|
|
200
|
-
return [
|
|
201
|
-
{ to: 'd1', promotedTo: ['bishop', 'knight'] },
|
|
202
|
-
{ to: 'e1', promotedTo: ['rook', 'knight', 'queen'] }
|
|
203
|
-
];
|
|
204
|
-
if (source === 'c2')
|
|
205
|
-
return [
|
|
206
|
-
{ to: 'c1', promotedTo: ['bishop', 'rook', 'knight', 'queen'] },
|
|
207
|
-
{ to: 'b1', promotedTo: ['bishop', 'rook', 'knight', 'queen'] }
|
|
208
|
-
];
|
|
209
|
-
return undefined;
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
```
|
|
193
|
+
This lets your game/rules layer own chess legality and game state while the board owns interaction, selection, targeting, visual feedback, promotion UI, animation, and its own displayed position.
|
|
213
194
|
|
|
214
|
-
|
|
195
|
+
### Promotion and auto-promotion
|
|
215
196
|
|
|
216
197
|
Promotion is handled inside the board interaction pipeline through a natural deferred move flow.
|
|
217
198
|
|
|
@@ -249,6 +230,44 @@ const board = createBoard({
|
|
|
249
230
|
board.extensions.autoPromote.toQueen = true;
|
|
250
231
|
```
|
|
251
232
|
|
|
233
|
+
### Movability modes
|
|
234
|
+
|
|
235
|
+
Movability controls how the board accepts user-initiated moves.
|
|
236
|
+
|
|
237
|
+
- `disabled` — user moves are ignored
|
|
238
|
+
- `free` — user moves are accepted without legal destination filtering
|
|
239
|
+
- `strict` — user moves are accepted only when the target is allowed by the destinations provided by your app/rules layer
|
|
240
|
+
|
|
241
|
+
### Advanced: explicit extension lists
|
|
242
|
+
|
|
243
|
+
Most users can rely on the default first-party extension baseline. Use an explicit extension list only when you want to control which built-in or custom extensions are installed.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { createBoard, type Chessboard, type ChessboardExtensionInput } from '@mirasen/chessboard';
|
|
247
|
+
|
|
248
|
+
const extensionList = [
|
|
249
|
+
// Always add renderer first, otherwise nothing will be rendered
|
|
250
|
+
'renderer',
|
|
251
|
+
'lastMove',
|
|
252
|
+
// Keep autoPromote before promotion, otherwise promotion may defer the move first
|
|
253
|
+
'autoPromote',
|
|
254
|
+
'promotion'
|
|
255
|
+
// customExtensionDefinition
|
|
256
|
+
] as const satisfies readonly ChessboardExtensionInput[];
|
|
257
|
+
let board: Chessboard<typeof extensionList> | null = null;
|
|
258
|
+
|
|
259
|
+
const element = document.getElementById('board');
|
|
260
|
+
|
|
261
|
+
if (!element) {
|
|
262
|
+
throw new Error('Missing board element');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
board = createBoard({
|
|
266
|
+
element,
|
|
267
|
+
extensions: extensionList
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
252
271
|
## Architecture
|
|
253
272
|
|
|
254
273
|
`@mirasen/chessboard` is built around explicit boundaries:
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type { MoveOutput } from './extensions/build/index.js';
|
|
2
|
+
export type { MoveRequestInput, PiecePositionRecordString } from './state/board/types/input.js';
|
|
3
|
+
export type { MoveDestinationInput } from './state/interaction/types/input.js';
|
|
1
4
|
export { createBoard } from './wrapper/factory.js';
|
|
2
5
|
export type { Chessboard, ChessboardExtensionInput, ChessboardInitOptions } from './wrapper/types.js';
|
|
3
|
-
export type { MoveRequestInput, PiecePositionRecordString } from './state/board/types/input.js';
|
package/package.json
CHANGED