@alepot55/chessboardjs 2.2.0 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +227 -0
- package/.github/instructions/copilot-instuctions.md +1671 -0
- package/README.md +127 -403
- package/assets/themes/alepot/theme.json +42 -0
- package/assets/themes/default/theme.json +42 -0
- package/chessboard.bundle.js +782 -119
- package/config/jest.config.js +15 -0
- package/config/rollup.config.js +35 -0
- package/dist/chessboard.cjs.js +10476 -0
- package/dist/chessboard.css +197 -0
- package/dist/chessboard.esm.js +10407 -0
- package/dist/chessboard.iife.js +10481 -0
- package/dist/chessboard.umd.js +10482 -0
- package/jest.config.js +2 -7
- package/package.json +18 -3
- package/rollup.config.js +2 -11
- package/{chessboard.move.js → src/components/Move.js} +3 -3
- package/src/components/Piece.js +273 -0
- package/{chessboard.square.js → src/components/Square.js} +60 -7
- package/src/constants/index.js +15 -0
- package/src/constants/positions.js +62 -0
- package/src/core/Chessboard.js +1930 -0
- package/src/core/ChessboardConfig.js +458 -0
- package/src/core/ChessboardFactory.js +385 -0
- package/src/core/index.js +141 -0
- package/src/errors/ChessboardError.js +133 -0
- package/src/errors/index.js +15 -0
- package/src/errors/messages.js +189 -0
- package/src/index.js +103 -0
- package/src/services/AnimationService.js +180 -0
- package/src/services/BoardService.js +156 -0
- package/src/services/CoordinateService.js +355 -0
- package/src/services/EventService.js +807 -0
- package/src/services/MoveService.js +594 -0
- package/src/services/PieceService.js +303 -0
- package/src/services/PositionService.js +237 -0
- package/src/services/ValidationService.js +673 -0
- package/src/services/index.js +14 -0
- package/src/styles/animations.css +46 -0
- package/{chessboard.css → src/styles/board.css} +3 -0
- package/src/styles/index.css +4 -0
- package/src/styles/pieces.css +66 -0
- package/src/utils/animations.js +37 -0
- package/{chess.js → src/utils/chess.js} +16 -16
- package/src/utils/coordinates.js +62 -0
- package/src/utils/cross-browser.js +150 -0
- package/src/utils/logger.js +422 -0
- package/src/utils/performance.js +311 -0
- package/src/utils/validation.js +458 -0
- package/tests/unit/chessboard-config-animations.test.js +106 -0
- package/tests/unit/chessboard-robust.test.js +163 -0
- package/tests/unit/chessboard.test.js +183 -0
- package/chessboard.config.js +0 -147
- package/chessboard.js +0 -981
- package/chessboard.piece.js +0 -115
- package/test/chessboard.test.js +0 -128
- /package/{alepot_theme → assets/themes/alepot}/bb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/bw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/kb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/kw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/nb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/nw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/pb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/pw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/qb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/qw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/rb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/rw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/bb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/bw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/kb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/kw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/nb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/nw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/pb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/pw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/qb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/qw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/rb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/rw.svg +0 -0
- /package/{.babelrc → config/.babelrc} +0 -0
|
@@ -0,0 +1,1930 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Chessboard class - Orchestrates all services and components
|
|
3
|
+
* @module core/Chessboard
|
|
4
|
+
* @since 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import ChessboardConfig from './ChessboardConfig.js';
|
|
8
|
+
import Move from '../components/Move.js';
|
|
9
|
+
import {
|
|
10
|
+
AnimationService,
|
|
11
|
+
BoardService,
|
|
12
|
+
CoordinateService,
|
|
13
|
+
EventService,
|
|
14
|
+
MoveService,
|
|
15
|
+
PieceService,
|
|
16
|
+
PositionService,
|
|
17
|
+
ValidationService
|
|
18
|
+
} from '../services/index.js';
|
|
19
|
+
import { ERROR_MESSAGES } from '../errors/index.js';
|
|
20
|
+
import { ChessboardError, ValidationError, ConfigurationError } from '../errors/ChessboardError.js';
|
|
21
|
+
import { PerformanceMonitor } from '../utils/performance.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Main Chessboard class responsible for coordinating all services
|
|
25
|
+
* Implements the Facade pattern to provide a unified interface
|
|
26
|
+
* @class
|
|
27
|
+
*/
|
|
28
|
+
class Chessboard {
|
|
29
|
+
/**
|
|
30
|
+
* Creates a new Chessboard instance
|
|
31
|
+
* @param {Object} config - Configuration object
|
|
32
|
+
* @throws {ConfigurationError} If configuration is invalid
|
|
33
|
+
*/
|
|
34
|
+
constructor(config) {
|
|
35
|
+
try {
|
|
36
|
+
// Initialize performance monitoring
|
|
37
|
+
this._performanceMonitor = new PerformanceMonitor();
|
|
38
|
+
this._performanceMonitor.startMeasure('chessboard-initialization');
|
|
39
|
+
|
|
40
|
+
// Validate and initialize configuration
|
|
41
|
+
this._validateAndInitializeConfig(config);
|
|
42
|
+
|
|
43
|
+
// Initialize services
|
|
44
|
+
this._initializeServices();
|
|
45
|
+
|
|
46
|
+
// Initialize the board
|
|
47
|
+
this._initialize();
|
|
48
|
+
|
|
49
|
+
this._performanceMonitor.endMeasure('chessboard-initialization');
|
|
50
|
+
} catch (error) {
|
|
51
|
+
this._handleConstructorError(error);
|
|
52
|
+
}
|
|
53
|
+
this._undoneMoves = [];
|
|
54
|
+
this._updateBoardPieces(true, true); // Forza popolamento DOM subito
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validates and initializes configuration
|
|
59
|
+
* @private
|
|
60
|
+
* @param {Object} config - Raw configuration object
|
|
61
|
+
* @throws {ConfigurationError} If configuration is invalid
|
|
62
|
+
*/
|
|
63
|
+
_validateAndInitializeConfig(config) {
|
|
64
|
+
if (!config || typeof config !== 'object') {
|
|
65
|
+
throw new ConfigurationError('Configuration must be an object', 'config', config);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.config = new ChessboardConfig(config);
|
|
69
|
+
|
|
70
|
+
// Validate required configuration
|
|
71
|
+
if (!this.config.id_div) {
|
|
72
|
+
throw new ConfigurationError('Configuration must include id_div', 'id_div', this.config.id_div);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Handles constructor errors gracefully
|
|
78
|
+
* @private
|
|
79
|
+
* @param {Error} error - Error that occurred during construction
|
|
80
|
+
*/
|
|
81
|
+
_handleConstructorError(error) {
|
|
82
|
+
console.error('Chessboard initialization failed:', error);
|
|
83
|
+
|
|
84
|
+
// Clean up any partially initialized resources
|
|
85
|
+
this._cleanup();
|
|
86
|
+
|
|
87
|
+
// Re-throw with additional context
|
|
88
|
+
if (error instanceof ChessboardError) {
|
|
89
|
+
throw error;
|
|
90
|
+
} else {
|
|
91
|
+
throw new ChessboardError('Failed to initialize chessboard', 'INITIALIZATION_ERROR', {
|
|
92
|
+
originalError: error.message,
|
|
93
|
+
stack: error.stack
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Cleans up any partially initialized resources (safe to call multiple times)
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
102
|
+
_cleanup() {
|
|
103
|
+
// Remove event listeners if present
|
|
104
|
+
if (this.eventService && typeof this.eventService.removeListeners === 'function') {
|
|
105
|
+
this.eventService.removeListeners();
|
|
106
|
+
}
|
|
107
|
+
// Clear timeouts
|
|
108
|
+
if (this._updateTimeout) {
|
|
109
|
+
clearTimeout(this._updateTimeout);
|
|
110
|
+
this._updateTimeout = null;
|
|
111
|
+
}
|
|
112
|
+
// Null all services
|
|
113
|
+
this.validationService = null;
|
|
114
|
+
this.coordinateService = null;
|
|
115
|
+
this.positionService = null;
|
|
116
|
+
this.boardService = null;
|
|
117
|
+
this.pieceService = null;
|
|
118
|
+
this.animationService = null;
|
|
119
|
+
this.moveService = null;
|
|
120
|
+
this.eventService = null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Initializes all services
|
|
125
|
+
* @private
|
|
126
|
+
*/
|
|
127
|
+
_initializeServices() {
|
|
128
|
+
// Core services
|
|
129
|
+
this.validationService = new ValidationService();
|
|
130
|
+
this.coordinateService = new CoordinateService(this.config);
|
|
131
|
+
this.positionService = new PositionService(this.config);
|
|
132
|
+
this.boardService = new BoardService(this.config);
|
|
133
|
+
this.pieceService = new PieceService(this.config);
|
|
134
|
+
this.animationService = new AnimationService(this.config);
|
|
135
|
+
this.moveService = new MoveService(this.config, this.positionService);
|
|
136
|
+
this.eventService = new EventService(
|
|
137
|
+
this.config,
|
|
138
|
+
this.boardService,
|
|
139
|
+
this.moveService,
|
|
140
|
+
this.coordinateService,
|
|
141
|
+
this
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// State management
|
|
145
|
+
this._updateTimeout = null;
|
|
146
|
+
this._isAnimating = false;
|
|
147
|
+
|
|
148
|
+
// Bind methods to preserve context
|
|
149
|
+
this._boundUpdateBoardPieces = this._updateBoardPieces.bind(this);
|
|
150
|
+
this._boundOnSquareClick = this._onSquareClick.bind(this);
|
|
151
|
+
this._boundOnPieceHover = this._onPieceHover.bind(this);
|
|
152
|
+
this._boundOnPieceLeave = this._onPieceLeave.bind(this);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Initializes the board
|
|
157
|
+
* @private
|
|
158
|
+
*/
|
|
159
|
+
_initialize() {
|
|
160
|
+
this._initParams();
|
|
161
|
+
this._setGame(this.config.position);
|
|
162
|
+
this._buildBoard();
|
|
163
|
+
this._buildSquares();
|
|
164
|
+
this._addListeners();
|
|
165
|
+
this._updateBoardPieces(true, true); // Initial position load
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Initializes parameters and state
|
|
170
|
+
* @private
|
|
171
|
+
*/
|
|
172
|
+
_initParams() {
|
|
173
|
+
// Reset state
|
|
174
|
+
this.eventService.setClicked(null);
|
|
175
|
+
this.eventService.setPromoting(false);
|
|
176
|
+
this.eventService.setAnimating(false);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Sets up the game with initial position
|
|
181
|
+
* @private
|
|
182
|
+
* @param {string|Object} position - Initial position
|
|
183
|
+
*/
|
|
184
|
+
_setGame(position) {
|
|
185
|
+
this.positionService.setGame(position);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Builds the board DOM structure
|
|
190
|
+
* @private
|
|
191
|
+
* Best practice: always remove squares (destroy JS/DOM) before clearing the board container.
|
|
192
|
+
*/
|
|
193
|
+
_buildBoard() {
|
|
194
|
+
console.log('CHIAMATO: _buildBoard');
|
|
195
|
+
if (this._isUndoRedo) {
|
|
196
|
+
console.log('SKIP _buildBoard per undo/redo');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// Forza la pulizia completa del contenitore board (DOM)
|
|
200
|
+
const boardContainer = document.getElementById(this.config.id_div);
|
|
201
|
+
if (boardContainer) boardContainer.innerHTML = '';
|
|
202
|
+
// Force remove all pieces from all squares (no animation, best practice)
|
|
203
|
+
if (this.boardService && this.boardService.squares) {
|
|
204
|
+
Object.values(this.boardService.squares).forEach(sq => sq && sq.forceRemoveAllPieces && sq.forceRemoveAllPieces());
|
|
205
|
+
}
|
|
206
|
+
if (this.boardService && this.boardService.removeSquares) this.boardService.removeSquares();
|
|
207
|
+
if (this.boardService && this.boardService.removeBoard) this.boardService.removeBoard();
|
|
208
|
+
this.boardService.buildBoard();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Builds all squares on the board
|
|
213
|
+
* @private
|
|
214
|
+
*/
|
|
215
|
+
_buildSquares() {
|
|
216
|
+
console.log('CHIAMATO: _buildSquares');
|
|
217
|
+
if (this._isUndoRedo) {
|
|
218
|
+
console.log('SKIP _buildSquares per undo/redo');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (this.boardService && this.boardService.removeSquares) {
|
|
222
|
+
this.boardService.removeSquares();
|
|
223
|
+
}
|
|
224
|
+
this.boardService.buildSquares((row, col) => {
|
|
225
|
+
return this.coordinateService.realCoord(row, col);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Adds event listeners to squares
|
|
231
|
+
* @private
|
|
232
|
+
*/
|
|
233
|
+
_addListeners() {
|
|
234
|
+
this.eventService.addListeners(
|
|
235
|
+
this._boundOnSquareClick,
|
|
236
|
+
this._boundOnPieceHover,
|
|
237
|
+
this._boundOnPieceLeave
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Handles square click events
|
|
243
|
+
* @private
|
|
244
|
+
* @param {Square} square - Clicked square
|
|
245
|
+
* @param {boolean} [animate=true] - Whether to animate
|
|
246
|
+
* @param {boolean} [dragged=false] - Whether triggered by drag
|
|
247
|
+
* @returns {boolean} True if successful
|
|
248
|
+
*/
|
|
249
|
+
_onSquareClick(square, animate = true, dragged = false) {
|
|
250
|
+
return this.eventService.onClick(
|
|
251
|
+
square,
|
|
252
|
+
this._onMove.bind(this),
|
|
253
|
+
this._onSelect.bind(this),
|
|
254
|
+
this._onDeselect.bind(this),
|
|
255
|
+
animate,
|
|
256
|
+
dragged
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Handles piece hover events
|
|
262
|
+
* @private
|
|
263
|
+
* @param {Square} square - Hovered square
|
|
264
|
+
*/
|
|
265
|
+
_onPieceHover(square) {
|
|
266
|
+
if (this.config.hints && !this.eventService.getClicked()) {
|
|
267
|
+
// Only show hints if no square is selected
|
|
268
|
+
this._hintMoves(square);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Handles piece leave events
|
|
274
|
+
* @private
|
|
275
|
+
* @param {Square} square - Left square
|
|
276
|
+
*/
|
|
277
|
+
_onPieceLeave(square) {
|
|
278
|
+
if (this.config.hints && !this.eventService.getClicked()) {
|
|
279
|
+
// Only remove hints if no square is selected
|
|
280
|
+
this._dehintMoves(square);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Handles move execution
|
|
286
|
+
* @private
|
|
287
|
+
* @param {Square} fromSquare - Source square
|
|
288
|
+
* @param {Square} toSquare - Target square
|
|
289
|
+
* @param {string} [promotion] - Promotion piece
|
|
290
|
+
* @param {boolean} [animate=true] - Whether to animate
|
|
291
|
+
* @returns {boolean} True if move was successful
|
|
292
|
+
*/
|
|
293
|
+
_onMove(fromSquare, toSquare, promotion = null, animate = true) {
|
|
294
|
+
const move = new Move(fromSquare, toSquare, promotion);
|
|
295
|
+
|
|
296
|
+
if (!move.check()) {
|
|
297
|
+
// Clear state on failed move
|
|
298
|
+
this._clearVisualState();
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (this.config.onlyLegalMoves && !move.isLegal(this.positionService.getGame())) {
|
|
303
|
+
// Clear state on illegal move
|
|
304
|
+
this._clearVisualState();
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!move.hasPromotion() && this._requiresPromotion(move)) {
|
|
309
|
+
// Don't clear state for promotion - it's handled elsewhere
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (this.config.onMove(move)) {
|
|
314
|
+
// Clear state before executing move
|
|
315
|
+
this._clearVisualState();
|
|
316
|
+
this._executeMove(move, animate);
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Clear state on rejected move
|
|
321
|
+
this._clearVisualState();
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Handles square selection
|
|
327
|
+
* @private
|
|
328
|
+
* @param {Square} square - Selected square
|
|
329
|
+
*/
|
|
330
|
+
_onSelect(square) {
|
|
331
|
+
if (this.config.clickable) {
|
|
332
|
+
square.select();
|
|
333
|
+
this._hintMoves(square);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Handles square deselection
|
|
339
|
+
* @private
|
|
340
|
+
* @param {Square} square - Deselected square
|
|
341
|
+
*/
|
|
342
|
+
_onDeselect(square) {
|
|
343
|
+
this._clearVisualState();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Shows legal move hints for a square
|
|
348
|
+
* @private
|
|
349
|
+
* @param {Square} square - Square to show hints for
|
|
350
|
+
*/
|
|
351
|
+
_hintMoves(square) {
|
|
352
|
+
if (!this.moveService.canMove(square)) return;
|
|
353
|
+
|
|
354
|
+
// Clear existing hints first
|
|
355
|
+
this.boardService.applyToAllSquares('removeHint');
|
|
356
|
+
|
|
357
|
+
const moves = this.moveService.getCachedLegalMoves(square);
|
|
358
|
+
|
|
359
|
+
for (const move of moves) {
|
|
360
|
+
if (move.to && move.to.length === 2) {
|
|
361
|
+
const targetSquare = this.boardService.getSquare(move.to);
|
|
362
|
+
if (targetSquare) {
|
|
363
|
+
const hasEnemyPiece = targetSquare.piece &&
|
|
364
|
+
targetSquare.piece.color !== this.positionService.getGame().turn();
|
|
365
|
+
targetSquare.putHint(hasEnemyPiece);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Removes legal move hints for a square
|
|
373
|
+
* @private
|
|
374
|
+
* @param {Square} square - Square to remove hints for
|
|
375
|
+
*/
|
|
376
|
+
_dehintMoves(square) {
|
|
377
|
+
const moves = this.moveService.getCachedLegalMoves(square);
|
|
378
|
+
|
|
379
|
+
for (const move of moves) {
|
|
380
|
+
if (move.to && move.to.length === 2) {
|
|
381
|
+
const targetSquare = this.boardService.getSquare(move.to);
|
|
382
|
+
if (targetSquare) {
|
|
383
|
+
targetSquare.removeHint();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Checks if a move requires promotion
|
|
391
|
+
* @private
|
|
392
|
+
* @param {Move} move - Move to check
|
|
393
|
+
* @returns {boolean} True if promotion is required
|
|
394
|
+
*/
|
|
395
|
+
_requiresPromotion(move) {
|
|
396
|
+
return this.moveService.requiresPromotion(move);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Executes a move
|
|
401
|
+
* @private
|
|
402
|
+
* @param {Move} move - Move to execute
|
|
403
|
+
* @param {boolean} [animate=true] - Whether to animate
|
|
404
|
+
*/
|
|
405
|
+
_executeMove(move, animate = true) {
|
|
406
|
+
const gameStateBefore = this.positionService.getGame().fen();
|
|
407
|
+
|
|
408
|
+
if (this.config.onlyLegalMoves) {
|
|
409
|
+
this.boardService.applyToAllSquares('unmoved');
|
|
410
|
+
|
|
411
|
+
const gameMove = this.moveService.executeMove(move);
|
|
412
|
+
if (!gameMove) {
|
|
413
|
+
throw new Error('Move execution failed');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
move.from.moved();
|
|
417
|
+
move.to.moved();
|
|
418
|
+
|
|
419
|
+
// Check for special moves that need additional handling
|
|
420
|
+
const isCastleMove = this.moveService.isCastle(gameMove);
|
|
421
|
+
const isEnPassantMove = this.moveService.isEnPassant(gameMove);
|
|
422
|
+
|
|
423
|
+
// Animate the move if requested
|
|
424
|
+
if (animate && move.from.piece) {
|
|
425
|
+
const capturedPiece = move.to.piece;
|
|
426
|
+
|
|
427
|
+
// For castle moves in simultaneous mode, we need to coordinate both animations
|
|
428
|
+
if (isCastleMove && this.config.animationStyle === 'simultaneous') {
|
|
429
|
+
// Start king animation
|
|
430
|
+
this.pieceService.translatePiece(
|
|
431
|
+
move,
|
|
432
|
+
!!capturedPiece,
|
|
433
|
+
animate,
|
|
434
|
+
this._createDragFunction.bind(this),
|
|
435
|
+
() => {
|
|
436
|
+
// King animation completed, trigger change event
|
|
437
|
+
this.config.onMoveEnd(gameMove);
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
// Start rook animation simultaneously (with small delay)
|
|
442
|
+
setTimeout(() => {
|
|
443
|
+
this._handleCastleMove(gameMove, true);
|
|
444
|
+
}, this.config.simultaneousAnimationDelay);
|
|
445
|
+
} else {
|
|
446
|
+
// Regular move or sequential castle
|
|
447
|
+
this.pieceService.translatePiece(
|
|
448
|
+
move,
|
|
449
|
+
!!capturedPiece,
|
|
450
|
+
animate,
|
|
451
|
+
this._createDragFunction.bind(this),
|
|
452
|
+
() => {
|
|
453
|
+
// After animation, handle special moves and trigger change event
|
|
454
|
+
if (isCastleMove) {
|
|
455
|
+
this._handleSpecialMoveAnimation(gameMove);
|
|
456
|
+
} else if (isEnPassantMove) {
|
|
457
|
+
this._handleSpecialMoveAnimation(gameMove);
|
|
458
|
+
}
|
|
459
|
+
this.config.onMoveEnd(gameMove);
|
|
460
|
+
}
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
} else {
|
|
464
|
+
// For non-animated moves, handle special moves immediately
|
|
465
|
+
if (isCastleMove) {
|
|
466
|
+
this._handleSpecialMove(gameMove);
|
|
467
|
+
} else if (isEnPassantMove) {
|
|
468
|
+
this._handleSpecialMove(gameMove);
|
|
469
|
+
}
|
|
470
|
+
this._updateBoardPieces(false);
|
|
471
|
+
this.config.onMoveEnd(gameMove);
|
|
472
|
+
}
|
|
473
|
+
} else {
|
|
474
|
+
// Handle non-legal mode
|
|
475
|
+
const piece = this.positionService.getGamePieceId(move.from.id);
|
|
476
|
+
const game = this.positionService.getGame();
|
|
477
|
+
|
|
478
|
+
game.remove(move.from.id);
|
|
479
|
+
game.remove(move.to.id);
|
|
480
|
+
game.put({
|
|
481
|
+
type: move.hasPromotion() ? move.promotion : piece[0],
|
|
482
|
+
color: piece[1]
|
|
483
|
+
}, move.to.id);
|
|
484
|
+
|
|
485
|
+
// Update board for non-legal mode
|
|
486
|
+
this._updateBoardPieces(animate);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Handles special moves (castle, en passant) without animation
|
|
492
|
+
* @private
|
|
493
|
+
* @param {Object} gameMove - Game move object
|
|
494
|
+
*/
|
|
495
|
+
_handleSpecialMove(gameMove) {
|
|
496
|
+
if (this.moveService.isCastle(gameMove)) {
|
|
497
|
+
this._handleCastleMove(gameMove, false);
|
|
498
|
+
} else if (this.moveService.isEnPassant(gameMove)) {
|
|
499
|
+
this._handleEnPassantMove(gameMove, false);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Handles special moves (castle, en passant) with animation
|
|
505
|
+
* @private
|
|
506
|
+
* @param {Object} gameMove - Game move object
|
|
507
|
+
*/
|
|
508
|
+
_handleSpecialMoveAnimation(gameMove) {
|
|
509
|
+
if (this.moveService.isCastle(gameMove)) {
|
|
510
|
+
this._handleCastleMove(gameMove, true);
|
|
511
|
+
} else if (this.moveService.isEnPassant(gameMove)) {
|
|
512
|
+
this._handleEnPassantMove(gameMove, true);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Handles castle move by moving the rook
|
|
518
|
+
* @private
|
|
519
|
+
* @param {Object} gameMove - Game move object
|
|
520
|
+
* @param {boolean} animate - Whether to animate
|
|
521
|
+
*/
|
|
522
|
+
_handleCastleMove(gameMove, animate) {
|
|
523
|
+
const rookMove = this.moveService.getCastleRookMove(gameMove);
|
|
524
|
+
if (!rookMove) return;
|
|
525
|
+
|
|
526
|
+
const rookFromSquare = this.boardService.getSquare(rookMove.from);
|
|
527
|
+
const rookToSquare = this.boardService.getSquare(rookMove.to);
|
|
528
|
+
|
|
529
|
+
if (!rookFromSquare || !rookToSquare || !rookFromSquare.piece) {
|
|
530
|
+
console.warn('Castle rook move failed - squares or piece not found');
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
console.log(`Castle: moving rook from ${rookMove.from} to ${rookMove.to}`);
|
|
535
|
+
|
|
536
|
+
if (animate) {
|
|
537
|
+
// Always use translatePiece for smooth sliding animation
|
|
538
|
+
const rookPiece = rookFromSquare.piece;
|
|
539
|
+
this.pieceService.translatePiece(
|
|
540
|
+
{ from: rookFromSquare, to: rookToSquare, piece: rookPiece },
|
|
541
|
+
false, // No capture for rook in castle
|
|
542
|
+
animate,
|
|
543
|
+
this._createDragFunction.bind(this),
|
|
544
|
+
() => {
|
|
545
|
+
// After rook animation, update board state
|
|
546
|
+
this._updateBoardPieces(false);
|
|
547
|
+
}
|
|
548
|
+
);
|
|
549
|
+
} else {
|
|
550
|
+
// Just update the board state
|
|
551
|
+
this._updateBoardPieces(false);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Handles en passant move by removing the captured pawn
|
|
557
|
+
* @private
|
|
558
|
+
* @param {Object} gameMove - Game move object
|
|
559
|
+
* @param {boolean} animate - Whether to animate
|
|
560
|
+
*/
|
|
561
|
+
_handleEnPassantMove(gameMove, animate) {
|
|
562
|
+
const capturedSquare = this.moveService.getEnPassantCapturedSquare(gameMove);
|
|
563
|
+
if (!capturedSquare) return;
|
|
564
|
+
|
|
565
|
+
const capturedSquareObj = this.boardService.getSquare(capturedSquare);
|
|
566
|
+
if (!capturedSquareObj || !capturedSquareObj.piece) {
|
|
567
|
+
console.warn('En passant captured square not found or empty');
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
console.log(`En passant: removing captured pawn from ${capturedSquare}`);
|
|
572
|
+
|
|
573
|
+
if (animate) {
|
|
574
|
+
// Animate the captured pawn removal
|
|
575
|
+
this.pieceService.removePieceFromSquare(capturedSquareObj, true);
|
|
576
|
+
// Update board state after animation
|
|
577
|
+
setTimeout(() => {
|
|
578
|
+
this._updateBoardPieces(false);
|
|
579
|
+
}, this.config.moveTime);
|
|
580
|
+
} else {
|
|
581
|
+
// Just update the board state
|
|
582
|
+
this._updateBoardPieces(false);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Updates board pieces to match game state
|
|
588
|
+
* @private
|
|
589
|
+
* @param {boolean} [animation=false] - Whether to animate
|
|
590
|
+
* @param {boolean} [isPositionLoad=false] - Whether this is a position load
|
|
591
|
+
*/
|
|
592
|
+
_updateBoardPieces(animation = false, isPositionLoad = false) {
|
|
593
|
+
console.log('CHIAMATO: _updateBoardPieces', { animation, isPositionLoad, isUndoRedo: this._isUndoRedo });
|
|
594
|
+
// Check if services are available
|
|
595
|
+
if (!this.positionService || !this.moveService || !this.eventService) {
|
|
596
|
+
console.log('Cannot update board pieces - services not available');
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Clear any pending update
|
|
601
|
+
if (this._updateTimeout) {
|
|
602
|
+
clearTimeout(this._updateTimeout);
|
|
603
|
+
this._updateTimeout = null;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Clear moves cache
|
|
607
|
+
this.moveService.clearCache();
|
|
608
|
+
|
|
609
|
+
// Add small delay for click-to-move to avoid lag
|
|
610
|
+
if (animation && !this.eventService.getClicked()) {
|
|
611
|
+
this._updateTimeout = setTimeout(() => {
|
|
612
|
+
this._doUpdateBoardPieces(animation, isPositionLoad);
|
|
613
|
+
this._updateTimeout = null;
|
|
614
|
+
|
|
615
|
+
// Ensure hints are available for the next turn
|
|
616
|
+
this._ensureHintsAvailable();
|
|
617
|
+
}, 10);
|
|
618
|
+
} else {
|
|
619
|
+
this._doUpdateBoardPieces(animation, isPositionLoad);
|
|
620
|
+
|
|
621
|
+
// Ensure hints are available for the next turn
|
|
622
|
+
this._ensureHintsAvailable();
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Ensures hints are available for the current turn
|
|
628
|
+
* @private
|
|
629
|
+
*/
|
|
630
|
+
_ensureHintsAvailable() {
|
|
631
|
+
if (!this.config.hints) return;
|
|
632
|
+
|
|
633
|
+
// Small delay to ensure the board state is fully updated
|
|
634
|
+
setTimeout(() => {
|
|
635
|
+
// Clear any existing hints
|
|
636
|
+
this.boardService.applyToAllSquares('removeHint');
|
|
637
|
+
|
|
638
|
+
// The hints will be shown when the user hovers over pieces
|
|
639
|
+
// This just ensures the cache is ready
|
|
640
|
+
this.moveService.clearCache();
|
|
641
|
+
}, 50);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Updates board pieces after a delayed move
|
|
646
|
+
* @private
|
|
647
|
+
*/
|
|
648
|
+
_updateBoardPiecesDelayed() {
|
|
649
|
+
this._updateBoardPieces(false);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Performs the actual board update
|
|
654
|
+
* @private
|
|
655
|
+
* @param {boolean} [animation=false] - Whether to animate
|
|
656
|
+
* @param {boolean} [isPositionLoad=false] - Whether this is a position load (affects delay)
|
|
657
|
+
*/
|
|
658
|
+
_doUpdateBoardPieces(animation = false, isPositionLoad = false) {
|
|
659
|
+
// Skip update if we're in the middle of a promotion
|
|
660
|
+
if (this._isPromoting) {
|
|
661
|
+
console.log('Skipping board update during promotion');
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Check if services are available
|
|
666
|
+
if (!this.positionService || !this.positionService.getGame()) {
|
|
667
|
+
console.log('Cannot update board pieces - position service not available');
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const squares = this.boardService.getAllSquares();
|
|
672
|
+
const gameStateBefore = this.positionService.getGame().fen();
|
|
673
|
+
|
|
674
|
+
console.log('_doUpdateBoardPieces - current FEN:', gameStateBefore);
|
|
675
|
+
console.log('_doUpdateBoardPieces - animation:', animation, 'style:', this.config.animationStyle, 'isPositionLoad:', isPositionLoad);
|
|
676
|
+
|
|
677
|
+
// Determine which animation style to use
|
|
678
|
+
const useSimultaneous = this.config.animationStyle === 'simultaneous';
|
|
679
|
+
console.log('_doUpdateBoardPieces - useSimultaneous:', useSimultaneous);
|
|
680
|
+
|
|
681
|
+
if (useSimultaneous) {
|
|
682
|
+
console.log('Using simultaneous animation');
|
|
683
|
+
this._doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad);
|
|
684
|
+
} else {
|
|
685
|
+
console.log('Using sequential animation');
|
|
686
|
+
this._doSequentialUpdate(squares, gameStateBefore, animation);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Performs sequential piece updates (original behavior)
|
|
692
|
+
* @private
|
|
693
|
+
* @param {Object} squares - All squares
|
|
694
|
+
* @param {string} gameStateBefore - Game state before update
|
|
695
|
+
* @param {boolean} animation - Whether to animate
|
|
696
|
+
*/
|
|
697
|
+
_doSequentialUpdate(squares, gameStateBefore, animation) {
|
|
698
|
+
// Mappa: squareId -> expectedPieceId
|
|
699
|
+
const expectedMap = {};
|
|
700
|
+
Object.values(squares).forEach(square => {
|
|
701
|
+
expectedMap[square.id] = this.positionService.getGamePieceId(square.id);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
Object.values(squares).forEach(square => {
|
|
705
|
+
const expectedPieceId = expectedMap[square.id];
|
|
706
|
+
const currentPiece = square.piece;
|
|
707
|
+
const currentPieceId = currentPiece ? currentPiece.getId() : null;
|
|
708
|
+
|
|
709
|
+
// Se il pezzo attuale e quello atteso sono identici, non fare nulla
|
|
710
|
+
if (currentPieceId === expectedPieceId) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Se c'è un pezzo attuale ma non è quello atteso, rimuovilo
|
|
715
|
+
if (currentPiece && currentPieceId !== expectedPieceId) {
|
|
716
|
+
this.pieceService.removePieceFromSquare(square, animation);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Se c'è un pezzo atteso ma non è quello attuale, aggiungilo
|
|
720
|
+
if (expectedPieceId && currentPieceId !== expectedPieceId) {
|
|
721
|
+
const newPiece = this.pieceService.convertPiece(expectedPieceId);
|
|
722
|
+
this.pieceService.addPieceOnSquare(
|
|
723
|
+
square,
|
|
724
|
+
newPiece,
|
|
725
|
+
animation,
|
|
726
|
+
this._createDragFunction.bind(this)
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
this._addListeners();
|
|
732
|
+
const gameStateAfter = this.positionService.getGame().fen();
|
|
733
|
+
if (gameStateBefore !== gameStateAfter) {
|
|
734
|
+
this.config.onChange(gameStateAfter);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Performs simultaneous piece updates
|
|
740
|
+
* @private
|
|
741
|
+
* @param {Object} squares - All squares
|
|
742
|
+
* @param {string} gameStateBefore - Game state before update
|
|
743
|
+
* @param {boolean} [isPositionLoad=false] - Whether this is a position load
|
|
744
|
+
*/
|
|
745
|
+
_doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad = false) {
|
|
746
|
+
// Matching greedy per distanza minima, robusto
|
|
747
|
+
const currentMap = {};
|
|
748
|
+
const expectedMap = {};
|
|
749
|
+
|
|
750
|
+
Object.values(squares).forEach(square => {
|
|
751
|
+
const currentPiece = square.piece;
|
|
752
|
+
const expectedPieceId = this.positionService.getGamePieceId(square.id);
|
|
753
|
+
if (currentPiece) {
|
|
754
|
+
// Normalizza la chiave come 'color+type' lowercase
|
|
755
|
+
const key = (currentPiece.color + currentPiece.type).toLowerCase();
|
|
756
|
+
if (!currentMap[key]) currentMap[key] = [];
|
|
757
|
+
currentMap[key].push({ square, id: square.id });
|
|
758
|
+
}
|
|
759
|
+
if (expectedPieceId) {
|
|
760
|
+
// Normalizza la chiave come 'color+type' lowercase
|
|
761
|
+
const key = expectedPieceId.toLowerCase();
|
|
762
|
+
if (!expectedMap[key]) expectedMap[key] = [];
|
|
763
|
+
expectedMap[key].push({ square, id: square.id });
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
let animationsCompleted = 0;
|
|
768
|
+
let totalAnimations = 0;
|
|
769
|
+
const animationDelay = isPositionLoad ? 0 : this.config.simultaneousAnimationDelay;
|
|
770
|
+
let animationIndex = 0;
|
|
771
|
+
|
|
772
|
+
Object.keys(expectedMap).forEach(key => {
|
|
773
|
+
totalAnimations += Math.max((currentMap[key] || []).length, expectedMap[key].length);
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
if (totalAnimations === 0) {
|
|
777
|
+
this._addListeners();
|
|
778
|
+
const gameStateAfter = this.positionService.getGame().fen();
|
|
779
|
+
if (gameStateBefore !== gameStateAfter) {
|
|
780
|
+
this.config.onChange(gameStateAfter);
|
|
781
|
+
}
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const onAnimationComplete = () => {
|
|
786
|
+
animationsCompleted++;
|
|
787
|
+
if (animationsCompleted === totalAnimations) {
|
|
788
|
+
this._addListeners();
|
|
789
|
+
const gameStateAfter = this.positionService.getGame().fen();
|
|
790
|
+
if (gameStateBefore !== gameStateAfter) {
|
|
791
|
+
this.config.onChange(gameStateAfter);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
Object.keys(expectedMap).forEach(key => {
|
|
797
|
+
const fromList = (currentMap[key] || []).slice();
|
|
798
|
+
const toList = expectedMap[key].slice();
|
|
799
|
+
|
|
800
|
+
// 1. Costruisci matrice delle distanze
|
|
801
|
+
const distances = [];
|
|
802
|
+
for (let i = 0; i < fromList.length; i++) {
|
|
803
|
+
distances[i] = [];
|
|
804
|
+
for (let j = 0; j < toList.length; j++) {
|
|
805
|
+
distances[i][j] = Math.abs(fromList[i].square.row - toList[j].square.row) +
|
|
806
|
+
Math.abs(fromList[i].square.col - toList[j].square.col);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// 2. Matching greedy: abbina i più vicini
|
|
811
|
+
const fromMatched = new Array(fromList.length).fill(false);
|
|
812
|
+
const toMatched = new Array(toList.length).fill(false);
|
|
813
|
+
const moves = [];
|
|
814
|
+
|
|
815
|
+
while (true) {
|
|
816
|
+
let minDist = Infinity, minI = -1, minJ = -1;
|
|
817
|
+
for (let i = 0; i < fromList.length; i++) {
|
|
818
|
+
if (fromMatched[i]) continue;
|
|
819
|
+
for (let j = 0; j < toList.length; j++) {
|
|
820
|
+
if (toMatched[j]) continue;
|
|
821
|
+
if (distances[i][j] < minDist) {
|
|
822
|
+
minDist = distances[i][j];
|
|
823
|
+
minI = i;
|
|
824
|
+
minJ = j;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
if (minI === -1 || minJ === -1) break;
|
|
829
|
+
// Se la posizione è la stessa, non fare nulla (pezzo unchanged)
|
|
830
|
+
if (fromList[minI].square === toList[minJ].square) {
|
|
831
|
+
fromMatched[minI] = true;
|
|
832
|
+
toMatched[minJ] = true;
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
// Altrimenti, sposta il pezzo
|
|
836
|
+
moves.push({ from: fromList[minI].square, to: toList[minJ].square, piece: fromList[minI].square.piece });
|
|
837
|
+
fromMatched[minI] = true;
|
|
838
|
+
toMatched[minJ] = true;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// 3. Rimuovi i pezzi non abbinati (presenti solo in fromList)
|
|
842
|
+
for (let i = 0; i < fromList.length; i++) {
|
|
843
|
+
if (!fromMatched[i]) {
|
|
844
|
+
setTimeout(() => {
|
|
845
|
+
this.pieceService.removePieceFromSquare(fromList[i].square, true, onAnimationComplete);
|
|
846
|
+
}, animationIndex * animationDelay);
|
|
847
|
+
animationIndex++;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// 4. Aggiungi i pezzi non abbinati (presenti solo in toList)
|
|
852
|
+
for (let j = 0; j < toList.length; j++) {
|
|
853
|
+
if (!toMatched[j]) {
|
|
854
|
+
setTimeout(() => {
|
|
855
|
+
const newPiece = this.pieceService.convertPiece(key);
|
|
856
|
+
this.pieceService.addPieceOnSquare(
|
|
857
|
+
toList[j].square,
|
|
858
|
+
newPiece,
|
|
859
|
+
true,
|
|
860
|
+
this._createDragFunction.bind(this),
|
|
861
|
+
onAnimationComplete
|
|
862
|
+
);
|
|
863
|
+
}, animationIndex * animationDelay);
|
|
864
|
+
animationIndex++;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// 5. Anima i movimenti
|
|
869
|
+
moves.forEach(move => {
|
|
870
|
+
setTimeout(() => {
|
|
871
|
+
this.pieceService.translatePiece(
|
|
872
|
+
move,
|
|
873
|
+
false,
|
|
874
|
+
true,
|
|
875
|
+
this._createDragFunction.bind(this),
|
|
876
|
+
onAnimationComplete
|
|
877
|
+
);
|
|
878
|
+
}, animationIndex * animationDelay);
|
|
879
|
+
animationIndex++;
|
|
880
|
+
});
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Analyzes position changes to determine optimal animation strategy
|
|
886
|
+
* @private
|
|
887
|
+
* @param {Object} squares - All squares
|
|
888
|
+
* @returns {Object} Analysis of changes
|
|
889
|
+
*/
|
|
890
|
+
_analyzePositionChanges(squares) {
|
|
891
|
+
const currentPieces = new Map();
|
|
892
|
+
const expectedPieces = new Map();
|
|
893
|
+
|
|
894
|
+
// Map current and expected piece positions
|
|
895
|
+
Object.values(squares).forEach(square => {
|
|
896
|
+
const currentPiece = square.piece;
|
|
897
|
+
const expectedPieceId = this.positionService.getGamePieceId(square.id);
|
|
898
|
+
|
|
899
|
+
if (currentPiece) {
|
|
900
|
+
currentPieces.set(square.id, currentPiece.getId());
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
if (expectedPieceId) {
|
|
904
|
+
expectedPieces.set(square.id, expectedPieceId);
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
console.log('Position Analysis:');
|
|
909
|
+
console.log('Current pieces:', Array.from(currentPieces.entries()));
|
|
910
|
+
console.log('Expected pieces:', Array.from(expectedPieces.entries()));
|
|
911
|
+
|
|
912
|
+
// Identify different types of changes
|
|
913
|
+
const moves = []; // Pieces that can slide to new positions
|
|
914
|
+
const removes = []; // Pieces that need to be removed
|
|
915
|
+
const adds = []; // Pieces that need to be added
|
|
916
|
+
const unchanged = []; // Pieces that stay in place
|
|
917
|
+
|
|
918
|
+
// First pass: identify pieces that don't need to move (same piece type on same square)
|
|
919
|
+
const processedSquares = new Set();
|
|
920
|
+
|
|
921
|
+
currentPieces.forEach((currentPieceId, square) => {
|
|
922
|
+
const expectedPieceId = expectedPieces.get(square);
|
|
923
|
+
|
|
924
|
+
if (currentPieceId === expectedPieceId) {
|
|
925
|
+
// Same piece type on same square - no movement needed
|
|
926
|
+
console.log(`UNCHANGED: ${currentPieceId} stays on ${square}`);
|
|
927
|
+
unchanged.push({
|
|
928
|
+
piece: currentPieceId,
|
|
929
|
+
square: square
|
|
930
|
+
});
|
|
931
|
+
processedSquares.add(square);
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
// Second pass: handle pieces that need to move or be removed
|
|
936
|
+
currentPieces.forEach((currentPieceId, fromSquare) => {
|
|
937
|
+
if (processedSquares.has(fromSquare)) {
|
|
938
|
+
return; // Already processed as unchanged
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Try to find a destination for this piece
|
|
942
|
+
const availableDestination = Array.from(expectedPieces.entries()).find(([toSquare, expectedId]) =>
|
|
943
|
+
expectedId === currentPieceId && !processedSquares.has(toSquare)
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
if (availableDestination) {
|
|
947
|
+
const [toSquare, expectedId] = availableDestination;
|
|
948
|
+
console.log(`MOVE: ${currentPieceId} from ${fromSquare} to ${toSquare}`);
|
|
949
|
+
moves.push({
|
|
950
|
+
piece: currentPieceId,
|
|
951
|
+
from: fromSquare,
|
|
952
|
+
to: toSquare,
|
|
953
|
+
fromSquare: squares[fromSquare],
|
|
954
|
+
toSquare: squares[toSquare]
|
|
955
|
+
});
|
|
956
|
+
processedSquares.add(toSquare);
|
|
957
|
+
} else {
|
|
958
|
+
// This piece needs to be removed
|
|
959
|
+
console.log(`REMOVE: ${currentPieceId} from ${fromSquare}`);
|
|
960
|
+
removes.push({
|
|
961
|
+
piece: currentPieceId,
|
|
962
|
+
square: fromSquare,
|
|
963
|
+
squareObj: squares[fromSquare]
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
// Third pass: handle pieces that need to be added
|
|
969
|
+
expectedPieces.forEach((expectedPieceId, toSquare) => {
|
|
970
|
+
if (!processedSquares.has(toSquare)) {
|
|
971
|
+
console.log(`ADD: ${expectedPieceId} to ${toSquare}`);
|
|
972
|
+
adds.push({
|
|
973
|
+
piece: expectedPieceId,
|
|
974
|
+
square: toSquare,
|
|
975
|
+
squareObj: squares[toSquare]
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
return {
|
|
981
|
+
moves,
|
|
982
|
+
removes,
|
|
983
|
+
adds,
|
|
984
|
+
unchanged,
|
|
985
|
+
totalChanges: moves.length + removes.length + adds.length
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Executes simultaneous changes based on analysis
|
|
991
|
+
* @private
|
|
992
|
+
* @param {Object} changeAnalysis - Analysis of changes
|
|
993
|
+
* @param {string} gameStateBefore - Game state before update
|
|
994
|
+
* @param {boolean} [isPositionLoad=false] - Whether this is a position load
|
|
995
|
+
*/
|
|
996
|
+
_executeSimultaneousChanges(changeAnalysis, gameStateBefore, isPositionLoad = false) {
|
|
997
|
+
const { moves, removes, adds, unchanged } = changeAnalysis;
|
|
998
|
+
|
|
999
|
+
console.log(`Position changes analysis:`, {
|
|
1000
|
+
moves: moves.length,
|
|
1001
|
+
removes: removes.length,
|
|
1002
|
+
adds: adds.length,
|
|
1003
|
+
unchanged: unchanged.length
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
// Log unchanged pieces for debugging
|
|
1007
|
+
if (unchanged.length > 0) {
|
|
1008
|
+
console.log('Pieces staying in place:', unchanged.map(u => `${u.piece} on ${u.square}`));
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
let animationsCompleted = 0;
|
|
1012
|
+
const totalAnimations = moves.length + removes.length + adds.length;
|
|
1013
|
+
|
|
1014
|
+
// If no animations are needed, complete immediately
|
|
1015
|
+
if (totalAnimations === 0) {
|
|
1016
|
+
console.log('No animations needed, completing immediately');
|
|
1017
|
+
this._addListeners();
|
|
1018
|
+
|
|
1019
|
+
// Trigger change event if position changed
|
|
1020
|
+
const gameStateAfter = this.positionService.getGame().fen();
|
|
1021
|
+
if (gameStateBefore !== gameStateAfter) {
|
|
1022
|
+
this.config.onChange(gameStateAfter);
|
|
1023
|
+
}
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const onAnimationComplete = () => {
|
|
1028
|
+
animationsCompleted++;
|
|
1029
|
+
console.log(`Animation completed: ${animationsCompleted}/${totalAnimations}`);
|
|
1030
|
+
if (animationsCompleted === totalAnimations) {
|
|
1031
|
+
console.log('All simultaneous animations completed');
|
|
1032
|
+
this._addListeners();
|
|
1033
|
+
|
|
1034
|
+
// Trigger change event if position changed
|
|
1035
|
+
const gameStateAfter = this.positionService.getGame().fen();
|
|
1036
|
+
if (gameStateBefore !== gameStateAfter) {
|
|
1037
|
+
this.config.onChange(gameStateAfter);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
// Determine delay: 0 for position loads, configured delay for normal moves
|
|
1043
|
+
const animationDelay = isPositionLoad ? 0 : this.config.simultaneousAnimationDelay;
|
|
1044
|
+
console.log(`Using animation delay: ${animationDelay}ms (position load: ${isPositionLoad})`);
|
|
1045
|
+
|
|
1046
|
+
let animationIndex = 0;
|
|
1047
|
+
|
|
1048
|
+
// Process moves (pieces sliding to new positions)
|
|
1049
|
+
moves.forEach(move => {
|
|
1050
|
+
const delay = animationIndex * animationDelay;
|
|
1051
|
+
console.log(`Scheduling move ${move.piece} from ${move.from} to ${move.to} with delay ${delay}ms`);
|
|
1052
|
+
|
|
1053
|
+
setTimeout(() => {
|
|
1054
|
+
this._animatePieceMove(move, onAnimationComplete);
|
|
1055
|
+
}, delay);
|
|
1056
|
+
|
|
1057
|
+
animationIndex++;
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
// Process removes (pieces disappearing)
|
|
1061
|
+
removes.forEach(remove => {
|
|
1062
|
+
const delay = animationIndex * animationDelay;
|
|
1063
|
+
console.log(`Scheduling removal of ${remove.piece} from ${remove.square} with delay ${delay}ms`);
|
|
1064
|
+
|
|
1065
|
+
setTimeout(() => {
|
|
1066
|
+
this._animatePieceRemoval(remove, onAnimationComplete);
|
|
1067
|
+
}, delay);
|
|
1068
|
+
|
|
1069
|
+
animationIndex++;
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
// Process adds (pieces appearing)
|
|
1073
|
+
adds.forEach(add => {
|
|
1074
|
+
const delay = animationIndex * animationDelay;
|
|
1075
|
+
console.log(`Scheduling addition of ${add.piece} to ${add.square} with delay ${delay}ms`);
|
|
1076
|
+
|
|
1077
|
+
setTimeout(() => {
|
|
1078
|
+
this._animatePieceAddition(add, onAnimationComplete);
|
|
1079
|
+
}, delay);
|
|
1080
|
+
|
|
1081
|
+
animationIndex++;
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Animates a piece moving from one square to another
|
|
1087
|
+
* @private
|
|
1088
|
+
* @param {Object} move - Move information
|
|
1089
|
+
* @param {Function} onComplete - Callback when animation completes
|
|
1090
|
+
*/
|
|
1091
|
+
_animatePieceMove(move, onComplete) {
|
|
1092
|
+
const { fromSquare, toSquare } = move;
|
|
1093
|
+
const piece = fromSquare.piece;
|
|
1094
|
+
|
|
1095
|
+
if (!piece) {
|
|
1096
|
+
console.warn(`No piece found on ${move.from} for move animation`);
|
|
1097
|
+
onComplete();
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
console.log(`Animating piece move: ${move.piece} from ${move.from} to ${move.to}`);
|
|
1102
|
+
|
|
1103
|
+
// Use translatePiece for smooth sliding animation
|
|
1104
|
+
this.pieceService.translatePiece(
|
|
1105
|
+
{ from: fromSquare, to: toSquare, piece: piece },
|
|
1106
|
+
false, // Assume no capture for now
|
|
1107
|
+
true, // Always animate
|
|
1108
|
+
this._createDragFunction.bind(this),
|
|
1109
|
+
() => {
|
|
1110
|
+
console.log(`Piece move animation completed: ${move.piece} to ${move.to}`);
|
|
1111
|
+
onComplete();
|
|
1112
|
+
}
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Animates a piece being removed
|
|
1118
|
+
* @private
|
|
1119
|
+
* @param {Object} remove - Remove information
|
|
1120
|
+
* @param {Function} onComplete - Callback when animation completes
|
|
1121
|
+
*/
|
|
1122
|
+
_animatePieceRemoval(remove, onComplete) {
|
|
1123
|
+
console.log(`Animating piece removal: ${remove.piece} from ${remove.square}`);
|
|
1124
|
+
|
|
1125
|
+
this.pieceService.removePieceFromSquare(remove.squareObj, true, () => {
|
|
1126
|
+
console.log(`Piece removal animation completed: ${remove.piece} from ${remove.square}`);
|
|
1127
|
+
onComplete();
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Animates a piece being added
|
|
1133
|
+
* @private
|
|
1134
|
+
* @param {Object} add - Add information
|
|
1135
|
+
* @param {Function} onComplete - Callback when animation completes
|
|
1136
|
+
*/
|
|
1137
|
+
_animatePieceAddition(add, onComplete) {
|
|
1138
|
+
console.log(`Animating piece addition: ${add.piece} to ${add.square}`);
|
|
1139
|
+
|
|
1140
|
+
const newPiece = this.pieceService.convertPiece(add.piece);
|
|
1141
|
+
this.pieceService.addPieceOnSquare(
|
|
1142
|
+
add.squareObj,
|
|
1143
|
+
newPiece,
|
|
1144
|
+
true,
|
|
1145
|
+
this._createDragFunction.bind(this),
|
|
1146
|
+
() => {
|
|
1147
|
+
console.log(`Piece addition animation completed: ${add.piece} to ${add.square}`);
|
|
1148
|
+
onComplete();
|
|
1149
|
+
}
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* Creates a drag function for a piece
|
|
1155
|
+
* @private
|
|
1156
|
+
* @param {Square} square - Square containing the piece
|
|
1157
|
+
* @param {Piece} piece - Piece to create drag function for
|
|
1158
|
+
* @returns {Function} Drag function
|
|
1159
|
+
*/
|
|
1160
|
+
_createDragFunction(square, piece) {
|
|
1161
|
+
return this.eventService.createDragFunction(
|
|
1162
|
+
square,
|
|
1163
|
+
piece,
|
|
1164
|
+
this.config.onDragStart,
|
|
1165
|
+
this.config.onDragMove,
|
|
1166
|
+
this.config.onDrop,
|
|
1167
|
+
this._onSnapback.bind(this),
|
|
1168
|
+
this._onMove.bind(this),
|
|
1169
|
+
this._onRemove.bind(this)
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Handles snapback animation
|
|
1175
|
+
* @private
|
|
1176
|
+
* @param {Square} square - Square containing the piece
|
|
1177
|
+
* @param {Piece} piece - Piece to snapback
|
|
1178
|
+
*/
|
|
1179
|
+
_onSnapback(square, piece) {
|
|
1180
|
+
this.pieceService.snapbackPiece(square, this.config.snapbackAnimation);
|
|
1181
|
+
this.config.onSnapbackEnd(square, piece);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Handles piece removal
|
|
1186
|
+
* @private
|
|
1187
|
+
* @param {Square} square - Square containing the piece to remove
|
|
1188
|
+
*/
|
|
1189
|
+
_onRemove(square) {
|
|
1190
|
+
this.pieceService.removePieceFromSquare(square, true);
|
|
1191
|
+
this.positionService.getGame().remove(square.id);
|
|
1192
|
+
this._updateBoardPieces(true);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Clears all visual state (selections, hints, highlights)
|
|
1197
|
+
* @private
|
|
1198
|
+
*/
|
|
1199
|
+
_clearVisualState() {
|
|
1200
|
+
this.boardService.applyToAllSquares('deselect');
|
|
1201
|
+
this.boardService.applyToAllSquares('removeHint');
|
|
1202
|
+
this.boardService.applyToAllSquares('dehighlight');
|
|
1203
|
+
this.eventService.setClicked(null);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// -------------------
|
|
1207
|
+
// Public API Methods (Refactored)
|
|
1208
|
+
// -------------------
|
|
1209
|
+
|
|
1210
|
+
// --- POSITION & STATE ---
|
|
1211
|
+
/**
|
|
1212
|
+
* Get the current position as FEN
|
|
1213
|
+
* @returns {string}
|
|
1214
|
+
*/
|
|
1215
|
+
getPosition() { return this.fen(); }
|
|
1216
|
+
/**
|
|
1217
|
+
* Set the board position (FEN or object)
|
|
1218
|
+
* @param {string|Object} position
|
|
1219
|
+
* @param {Object} [opts]
|
|
1220
|
+
* @param {boolean} [opts.animate=true]
|
|
1221
|
+
* @returns {boolean}
|
|
1222
|
+
*/
|
|
1223
|
+
setPosition(position, opts = {}) {
|
|
1224
|
+
const animate = opts.animate !== undefined ? opts.animate : true;
|
|
1225
|
+
// Remove highlights and selections
|
|
1226
|
+
if (this.boardService && this.boardService.applyToAllSquares) {
|
|
1227
|
+
this.boardService.applyToAllSquares('removeHint');
|
|
1228
|
+
this.boardService.applyToAllSquares('deselect');
|
|
1229
|
+
this.boardService.applyToAllSquares('unmoved');
|
|
1230
|
+
}
|
|
1231
|
+
if (this.positionService && this.positionService.setGame) {
|
|
1232
|
+
this.positionService.setGame(position);
|
|
1233
|
+
}
|
|
1234
|
+
if (this._updateBoardPieces) {
|
|
1235
|
+
this._updateBoardPieces(animate, true);
|
|
1236
|
+
}
|
|
1237
|
+
return true;
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Reset the board to the starting position
|
|
1241
|
+
* @param {Object} [opts]
|
|
1242
|
+
* @param {boolean} [opts.animate=true]
|
|
1243
|
+
* @returns {boolean}
|
|
1244
|
+
*/
|
|
1245
|
+
reset(opts = {}) {
|
|
1246
|
+
const animate = opts.animate !== undefined ? opts.animate : true;
|
|
1247
|
+
// Use the default starting position from config or fallback
|
|
1248
|
+
const startPosition = this.config && this.config.position ? this.config.position : 'start';
|
|
1249
|
+
this._updateBoardPieces(animate);
|
|
1250
|
+
return this.setPosition(startPosition, { animate });
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Clear the board
|
|
1254
|
+
* @param {Object} [opts]
|
|
1255
|
+
* @param {boolean} [opts.animate=true]
|
|
1256
|
+
* @returns {boolean}
|
|
1257
|
+
*/
|
|
1258
|
+
clear(opts = {}) {
|
|
1259
|
+
const animate = opts.animate !== undefined ? opts.animate : true;
|
|
1260
|
+
if (!this.positionService || !this.positionService.getGame()) {
|
|
1261
|
+
return false;
|
|
1262
|
+
}
|
|
1263
|
+
if (this._clearVisualState) this._clearVisualState();
|
|
1264
|
+
this.positionService.getGame().clear();
|
|
1265
|
+
if (this._updateBoardPieces) {
|
|
1266
|
+
this._updateBoardPieces(animate, true);
|
|
1267
|
+
}
|
|
1268
|
+
return true;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// --- MOVE MANAGEMENT ---
|
|
1272
|
+
/**
|
|
1273
|
+
* Undo last move
|
|
1274
|
+
* @param {Object} [opts]
|
|
1275
|
+
* @param {boolean} [opts.animate=true]
|
|
1276
|
+
* @returns {boolean}
|
|
1277
|
+
*/
|
|
1278
|
+
undoMove(opts = {}) {
|
|
1279
|
+
const undone = this.positionService.getGame().undo();
|
|
1280
|
+
if (undone) {
|
|
1281
|
+
this._undoneMoves.push(undone);
|
|
1282
|
+
this._updateBoardPieces(opts.animate !== false);
|
|
1283
|
+
return undone;
|
|
1284
|
+
}
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Redo last undone move
|
|
1289
|
+
* @param {Object} [opts]
|
|
1290
|
+
* @param {boolean} [opts.animate=true]
|
|
1291
|
+
* @returns {boolean}
|
|
1292
|
+
*/
|
|
1293
|
+
redoMove(opts = {}) {
|
|
1294
|
+
if (this._undoneMoves && this._undoneMoves.length > 0) {
|
|
1295
|
+
const move = this._undoneMoves.pop();
|
|
1296
|
+
const moveObj = { from: move.from, to: move.to };
|
|
1297
|
+
if (move.promotion) moveObj.promotion = move.promotion;
|
|
1298
|
+
const result = this.positionService.getGame().move(moveObj);
|
|
1299
|
+
this._updateBoardPieces(opts.animate !== false);
|
|
1300
|
+
return result;
|
|
1301
|
+
}
|
|
1302
|
+
return false;
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Get legal moves for a square
|
|
1306
|
+
* @param {string} square
|
|
1307
|
+
* @returns {Array}
|
|
1308
|
+
*/
|
|
1309
|
+
getLegalMoves(square) { return this.legalMoves(square); }
|
|
1310
|
+
|
|
1311
|
+
// --- PIECE MANAGEMENT ---
|
|
1312
|
+
/**
|
|
1313
|
+
* Get the piece at a square
|
|
1314
|
+
* @param {string} square
|
|
1315
|
+
* @returns {string|null}
|
|
1316
|
+
*/
|
|
1317
|
+
getPiece(square) {
|
|
1318
|
+
// Restituisce sempre 'wq' (colore prima, tipo dopo, lowercase) o null
|
|
1319
|
+
const sq = this.boardService.getSquare(square);
|
|
1320
|
+
if (!sq) return null;
|
|
1321
|
+
const piece = sq.piece;
|
|
1322
|
+
if (!piece) return null;
|
|
1323
|
+
return (piece.color + piece.type).toLowerCase();
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Put a piece on a square
|
|
1327
|
+
* @param {string} piece
|
|
1328
|
+
* @param {string} square
|
|
1329
|
+
* @param {Object} [opts]
|
|
1330
|
+
* @param {boolean} [opts.animate=true]
|
|
1331
|
+
* @returns {boolean}
|
|
1332
|
+
*/
|
|
1333
|
+
putPiece(piece, square, opts = {}) {
|
|
1334
|
+
const animate = opts.animate !== undefined ? opts.animate : true;
|
|
1335
|
+
let pieceStr = piece;
|
|
1336
|
+
if (typeof piece === 'object' && piece.type && piece.color) {
|
|
1337
|
+
pieceStr = (piece.color + piece.type).toLowerCase();
|
|
1338
|
+
} else if (typeof piece === 'string' && piece.length === 2) {
|
|
1339
|
+
// Accetta sia 'wq' che 'qw', normalizza a 'wq'
|
|
1340
|
+
const a = piece[0].toLowerCase();
|
|
1341
|
+
const b = piece[1].toLowerCase();
|
|
1342
|
+
const types = 'kqrbnp';
|
|
1343
|
+
const colors = 'wb';
|
|
1344
|
+
if (types.includes(a) && colors.includes(b)) {
|
|
1345
|
+
pieceStr = b + a;
|
|
1346
|
+
} else if (colors.includes(a) && types.includes(b)) {
|
|
1347
|
+
pieceStr = a + b;
|
|
1348
|
+
} else {
|
|
1349
|
+
throw new Error(`[putPiece] Invalid piece: ${piece}`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
const validSquare = this.validationService.isValidSquare(square);
|
|
1353
|
+
const validPiece = this.validationService.isValidPiece(pieceStr);
|
|
1354
|
+
if (!validSquare) throw new Error(`[putPiece] Invalid square: ${square}`);
|
|
1355
|
+
if (!validPiece) throw new Error(`[putPiece] Invalid piece: ${pieceStr}`);
|
|
1356
|
+
if (!this.positionService || !this.positionService.getGame()) {
|
|
1357
|
+
throw new Error('[putPiece] No positionService or game');
|
|
1358
|
+
}
|
|
1359
|
+
const pieceObj = this.pieceService.convertPiece(pieceStr);
|
|
1360
|
+
const squareObj = this.boardService.getSquare(square);
|
|
1361
|
+
if (!squareObj) throw new Error(`[putPiece] Square not found: ${square}`);
|
|
1362
|
+
squareObj.piece = pieceObj;
|
|
1363
|
+
const chessJsPiece = { type: pieceObj.type, color: pieceObj.color };
|
|
1364
|
+
const game = this.positionService.getGame();
|
|
1365
|
+
const result = game.put(chessJsPiece, square);
|
|
1366
|
+
if (!result) throw new Error(`[putPiece] Game.put failed for ${pieceStr} on ${square}`);
|
|
1367
|
+
this._updateBoardPieces(animate);
|
|
1368
|
+
return true;
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Remove a piece from a square
|
|
1372
|
+
* @param {string} square
|
|
1373
|
+
* @param {Object} [opts]
|
|
1374
|
+
* @param {boolean} [opts.animate=true]
|
|
1375
|
+
* @returns {string|null}
|
|
1376
|
+
*/
|
|
1377
|
+
removePiece(square, opts = {}) {
|
|
1378
|
+
const animate = opts.animate !== undefined ? opts.animate : true;
|
|
1379
|
+
if (!this.validationService.isValidSquare(square)) {
|
|
1380
|
+
throw new Error(`[removePiece] Invalid square: ${square}`);
|
|
1381
|
+
}
|
|
1382
|
+
const squareObj = this.boardService.getSquare(square);
|
|
1383
|
+
if (!squareObj) return true;
|
|
1384
|
+
if (!squareObj.piece) return true;
|
|
1385
|
+
squareObj.piece = null;
|
|
1386
|
+
const game = this.positionService.getGame();
|
|
1387
|
+
game.remove(square);
|
|
1388
|
+
this._updateBoardPieces(animate);
|
|
1389
|
+
return true;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// --- BOARD CONTROL ---
|
|
1393
|
+
/**
|
|
1394
|
+
* Flip the board orientation
|
|
1395
|
+
* @param {Object} [opts]
|
|
1396
|
+
* @param {boolean} [opts.animate=true]
|
|
1397
|
+
*/
|
|
1398
|
+
flipBoard(opts = {}) {
|
|
1399
|
+
if (this.coordinateService && this.coordinateService.flipOrientation) {
|
|
1400
|
+
this.coordinateService.flipOrientation();
|
|
1401
|
+
}
|
|
1402
|
+
if (this._buildBoard) this._buildBoard();
|
|
1403
|
+
if (this._buildSquares) this._buildSquares();
|
|
1404
|
+
if (this._addListeners) this._addListeners();
|
|
1405
|
+
if (this._updateBoardPieces) this._updateBoardPieces(opts.animate !== false);
|
|
1406
|
+
console.log('FEN dopo flip:', this.fen(), 'Orientamento:', this.coordinateService.getOrientation());
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Set the board orientation
|
|
1410
|
+
* @param {'w'|'b'} color
|
|
1411
|
+
* @param {Object} [opts]
|
|
1412
|
+
* @param {boolean} [opts.animate=true]
|
|
1413
|
+
*/
|
|
1414
|
+
setOrientation(color, opts = {}) {
|
|
1415
|
+
if (this.validationService.isValidOrientation(color)) {
|
|
1416
|
+
this.coordinateService.setOrientation(color);
|
|
1417
|
+
if (this._buildBoard) this._buildBoard();
|
|
1418
|
+
if (this._buildSquares) this._buildSquares();
|
|
1419
|
+
if (this._addListeners) this._addListeners();
|
|
1420
|
+
if (this._updateBoardPieces) this._updateBoardPieces(opts.animate !== false);
|
|
1421
|
+
}
|
|
1422
|
+
return this.coordinateService.getOrientation();
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Get the current orientation
|
|
1426
|
+
* @returns {'w'|'b'}
|
|
1427
|
+
*/
|
|
1428
|
+
getOrientation() { return this.orientation(); }
|
|
1429
|
+
/**
|
|
1430
|
+
* Resize the board
|
|
1431
|
+
* @param {number|string} size
|
|
1432
|
+
*/
|
|
1433
|
+
resizeBoard(size) {
|
|
1434
|
+
if (size === 'auto') {
|
|
1435
|
+
this.config.size = 'auto';
|
|
1436
|
+
document.documentElement.style.setProperty('--dimBoard', 'auto');
|
|
1437
|
+
this._updateBoardPieces(false);
|
|
1438
|
+
return true;
|
|
1439
|
+
}
|
|
1440
|
+
if (typeof size !== 'number' || size < 50 || size > 3000) {
|
|
1441
|
+
throw new Error(`[resizeBoard] Invalid size: ${size}`);
|
|
1442
|
+
}
|
|
1443
|
+
this.config.size = size;
|
|
1444
|
+
document.documentElement.style.setProperty('--dimBoard', `${size}px`);
|
|
1445
|
+
this._updateBoardPieces(false);
|
|
1446
|
+
return true;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// --- HIGHLIGHTING & UI ---
|
|
1450
|
+
/**
|
|
1451
|
+
* Highlight a square
|
|
1452
|
+
* @param {string} square
|
|
1453
|
+
* @param {Object} [opts]
|
|
1454
|
+
*/
|
|
1455
|
+
highlight(square, opts = {}) {
|
|
1456
|
+
if (!this.validationService.isValidSquare(square)) return;
|
|
1457
|
+
if (this.boardService && this.boardService.highlightSquare) {
|
|
1458
|
+
this.boardService.highlightSquare(square, opts);
|
|
1459
|
+
} else if (this.eventService && this.eventService.highlightSquare) {
|
|
1460
|
+
this.eventService.highlightSquare(square, opts);
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Remove highlight from a square
|
|
1465
|
+
* @param {string} square
|
|
1466
|
+
* @param {Object} [opts]
|
|
1467
|
+
*/
|
|
1468
|
+
dehighlight(square, opts = {}) {
|
|
1469
|
+
if (!this.validationService.isValidSquare(square)) return;
|
|
1470
|
+
if (this.boardService && this.boardService.dehighlightSquare) {
|
|
1471
|
+
this.boardService.dehighlightSquare(square, opts);
|
|
1472
|
+
} else if (this.eventService && this.eventService.dehighlightSquare) {
|
|
1473
|
+
this.eventService.dehighlightSquare(square, opts);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// --- GAME INFO ---
|
|
1478
|
+
/**
|
|
1479
|
+
* Get FEN string
|
|
1480
|
+
* @returns {string}
|
|
1481
|
+
*/
|
|
1482
|
+
fen() {
|
|
1483
|
+
// Avoid recursion: call the underlying game object's fen()
|
|
1484
|
+
const game = this.positionService.getGame();
|
|
1485
|
+
if (!game || typeof game.fen !== 'function') return '';
|
|
1486
|
+
return game.fen();
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Get current turn
|
|
1490
|
+
* @returns {'w'|'b'}
|
|
1491
|
+
*/
|
|
1492
|
+
turn() { return this.positionService.getGame().turn(); }
|
|
1493
|
+
/**
|
|
1494
|
+
* Is the game over?
|
|
1495
|
+
* @returns {boolean}
|
|
1496
|
+
*/
|
|
1497
|
+
isGameOver() {
|
|
1498
|
+
const game = this.positionService.getGame();
|
|
1499
|
+
if (!game) return false;
|
|
1500
|
+
if (game.isGameOver) return game.isGameOver();
|
|
1501
|
+
// Fallback: checkmate or draw
|
|
1502
|
+
if (game.isCheckmate && game.isCheckmate()) return true;
|
|
1503
|
+
if (game.isDraw && game.isDraw()) return true;
|
|
1504
|
+
return false;
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Is it checkmate?
|
|
1508
|
+
* @returns {boolean}
|
|
1509
|
+
*/
|
|
1510
|
+
isCheckmate() {
|
|
1511
|
+
const game = this.positionService.getGame();
|
|
1512
|
+
if (!game) return false;
|
|
1513
|
+
return game.isCheckmate ? game.isCheckmate() : false;
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Is it draw?
|
|
1517
|
+
* @returns {boolean}
|
|
1518
|
+
*/
|
|
1519
|
+
isDraw() {
|
|
1520
|
+
const game = this.positionService.getGame();
|
|
1521
|
+
if (!game) return false;
|
|
1522
|
+
return game.isDraw ? game.isDraw() : false;
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Get move history
|
|
1526
|
+
* @returns {Array}
|
|
1527
|
+
*/
|
|
1528
|
+
getHistory() {
|
|
1529
|
+
const game = this.positionService.getGame();
|
|
1530
|
+
if (!game) return [];
|
|
1531
|
+
return game.history ? game.history() : [];
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// --- LIFECYCLE ---
|
|
1535
|
+
/**
|
|
1536
|
+
* Destroy the board and cleanup
|
|
1537
|
+
*/
|
|
1538
|
+
destroy() { /* TODO: robust destroy logic */ }
|
|
1539
|
+
/**
|
|
1540
|
+
* Rebuild the board
|
|
1541
|
+
*/
|
|
1542
|
+
rebuild() { this._initialize(); }
|
|
1543
|
+
|
|
1544
|
+
// --- CONFIGURATION ---
|
|
1545
|
+
/**
|
|
1546
|
+
* Get current config
|
|
1547
|
+
* @returns {Object}
|
|
1548
|
+
*/
|
|
1549
|
+
getConfig() { return this.config; }
|
|
1550
|
+
/**
|
|
1551
|
+
* Set new config
|
|
1552
|
+
* @param {Object} newConfig
|
|
1553
|
+
*/
|
|
1554
|
+
setConfig(newConfig) { this.setConfig(newConfig); }
|
|
1555
|
+
|
|
1556
|
+
// --- ALIASES/DEPRECATED ---
|
|
1557
|
+
// Note: These methods are now implemented as aliases at the end of the class
|
|
1558
|
+
|
|
1559
|
+
/**
|
|
1560
|
+
* Alias for flipBoard (for backward compatibility)
|
|
1561
|
+
*/
|
|
1562
|
+
flip(opts = {}) {
|
|
1563
|
+
this._updateBoardPieces(opts.animate !== false);
|
|
1564
|
+
return this.flipBoard(opts);
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
/**
|
|
1568
|
+
* Gets the current position as an object
|
|
1569
|
+
* @returns {Object} Position object
|
|
1570
|
+
*/
|
|
1571
|
+
position() {
|
|
1572
|
+
return this.positionService.getPosition();
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
/**
|
|
1576
|
+
* Sets a new position
|
|
1577
|
+
* @param {string|Object} position - New position
|
|
1578
|
+
* @param {boolean} [animate=true] - Whether to animate
|
|
1579
|
+
*/
|
|
1580
|
+
position(position, animate = true) {
|
|
1581
|
+
if (position === undefined) {
|
|
1582
|
+
return this.positionService.getPosition();
|
|
1583
|
+
}
|
|
1584
|
+
this.load(position, {}, animate); // load() already handles isPositionLoad=true
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
/**
|
|
1588
|
+
* Undoes the last move
|
|
1589
|
+
* @param {boolean} [animate=true] - Whether to animate
|
|
1590
|
+
* @returns {boolean} True if undo was successful
|
|
1591
|
+
*/
|
|
1592
|
+
undo(animate = true) {
|
|
1593
|
+
const undone = this.positionService.getGame().undo();
|
|
1594
|
+
if (undone) {
|
|
1595
|
+
this._undoneMoves.push(undone);
|
|
1596
|
+
this._updateBoardPieces(animate);
|
|
1597
|
+
return undone;
|
|
1598
|
+
}
|
|
1599
|
+
return null;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
/**
|
|
1603
|
+
* Redoes the last undone move
|
|
1604
|
+
* @param {boolean} [animate=true] - Whether to animate
|
|
1605
|
+
* @returns {boolean} True if redo was successful
|
|
1606
|
+
*/
|
|
1607
|
+
redo(animate = true) {
|
|
1608
|
+
if (this._undoneMoves && this._undoneMoves.length > 0) {
|
|
1609
|
+
const move = this._undoneMoves.pop();
|
|
1610
|
+
const moveObj = { from: move.from, to: move.to };
|
|
1611
|
+
if (move.promotion) moveObj.promotion = move.promotion;
|
|
1612
|
+
const result = this.positionService.getGame().move(moveObj);
|
|
1613
|
+
this._updateBoardPieces(animate);
|
|
1614
|
+
return result;
|
|
1615
|
+
}
|
|
1616
|
+
return false;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
/**
|
|
1620
|
+
* Gets the game history
|
|
1621
|
+
* @returns {Array} Array of moves
|
|
1622
|
+
*/
|
|
1623
|
+
history() {
|
|
1624
|
+
return this.positionService.getGame().history();
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
/**
|
|
1628
|
+
* Gets the current game state
|
|
1629
|
+
* @returns {Object} Game state object
|
|
1630
|
+
*/
|
|
1631
|
+
game() {
|
|
1632
|
+
return this.positionService.getGame();
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
/**
|
|
1636
|
+
* Gets or sets the orientation
|
|
1637
|
+
* @param {string} [orientation] - New orientation
|
|
1638
|
+
* @returns {string} Current orientation
|
|
1639
|
+
*/
|
|
1640
|
+
orientation(orientation) {
|
|
1641
|
+
if (orientation === undefined) {
|
|
1642
|
+
return this.coordinateService.getOrientation();
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
if (this.validationService.isValidOrientation(orientation)) {
|
|
1646
|
+
this.coordinateService.setOrientation(orientation);
|
|
1647
|
+
this.flip();
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
return this.coordinateService.getOrientation();
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
/**
|
|
1654
|
+
* Gets or sets the size
|
|
1655
|
+
* @param {number|string} [size] - New size
|
|
1656
|
+
* @returns {number|string} Current size
|
|
1657
|
+
*/
|
|
1658
|
+
size(size) {
|
|
1659
|
+
if (size === undefined) {
|
|
1660
|
+
return this.config.size;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
if (this.validationService.isValidSize(size)) {
|
|
1664
|
+
this.config.size = size;
|
|
1665
|
+
this.resize(size);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
return this.config.size;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
/**
|
|
1672
|
+
* Gets legal moves for a square
|
|
1673
|
+
* @param {string} square - Square to get moves for
|
|
1674
|
+
* @returns {Array} Array of legal moves
|
|
1675
|
+
*/
|
|
1676
|
+
legalMoves(square) {
|
|
1677
|
+
const squareObj = this.boardService.getSquare(square);
|
|
1678
|
+
if (!squareObj) return [];
|
|
1679
|
+
|
|
1680
|
+
return this.moveService.getCachedLegalMoves(squareObj);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
/**
|
|
1684
|
+
* Checks if a move is legal
|
|
1685
|
+
* @param {string|Object} move - Move to check
|
|
1686
|
+
* @returns {boolean} True if move is legal
|
|
1687
|
+
*/
|
|
1688
|
+
isLegal(move) {
|
|
1689
|
+
const moveObj = typeof move === 'string' ? this.moveService.parseMove(move) : move;
|
|
1690
|
+
if (!moveObj) return false;
|
|
1691
|
+
|
|
1692
|
+
const fromSquare = this.boardService.getSquare(moveObj.from);
|
|
1693
|
+
const toSquare = this.boardService.getSquare(moveObj.to);
|
|
1694
|
+
|
|
1695
|
+
if (!fromSquare || !toSquare) return false;
|
|
1696
|
+
|
|
1697
|
+
const moveInstance = new Move(fromSquare, toSquare, moveObj.promotion);
|
|
1698
|
+
return moveInstance.isLegal(this.positionService.getGame());
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/**
|
|
1702
|
+
* Checks if the game is over
|
|
1703
|
+
* @returns {boolean} True if game is over
|
|
1704
|
+
*/
|
|
1705
|
+
isGameOver() {
|
|
1706
|
+
return this.positionService.getGame().isGameOver();
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
/**
|
|
1710
|
+
* Checks if the current player is in check
|
|
1711
|
+
* @returns {boolean} True if in check
|
|
1712
|
+
*/
|
|
1713
|
+
inCheck() {
|
|
1714
|
+
return this.positionService.getGame().inCheck();
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* Checks if the current player is in checkmate
|
|
1719
|
+
* @returns {boolean} True if in checkmate
|
|
1720
|
+
*/
|
|
1721
|
+
inCheckmate() {
|
|
1722
|
+
return this.positionService.getGame().isCheckmate();
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
/**
|
|
1726
|
+
* Checks if the game is in stalemate
|
|
1727
|
+
* @returns {boolean} True if in stalemate
|
|
1728
|
+
*/
|
|
1729
|
+
inStalemate() {
|
|
1730
|
+
return this.positionService.getGame().isStalemate();
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
/**
|
|
1734
|
+
* Checks if the game is drawn
|
|
1735
|
+
* @returns {boolean} True if drawn
|
|
1736
|
+
*/
|
|
1737
|
+
inDraw() {
|
|
1738
|
+
return this.positionService.getGame().isDraw();
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
/**
|
|
1742
|
+
* Checks if position is threefold repetition
|
|
1743
|
+
* @returns {boolean} True if threefold repetition
|
|
1744
|
+
*/
|
|
1745
|
+
inThreefoldRepetition() {
|
|
1746
|
+
return this.positionService.getGame().isThreefoldRepetition();
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
/**
|
|
1750
|
+
* Gets the PGN representation of the game
|
|
1751
|
+
* @returns {string} PGN string
|
|
1752
|
+
*/
|
|
1753
|
+
pgn() {
|
|
1754
|
+
return this.positionService.getGame().pgn();
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
/**
|
|
1758
|
+
* Loads a PGN string
|
|
1759
|
+
* @param {string} pgn - PGN string to load
|
|
1760
|
+
* @param {boolean} [animate=true] - Whether to animate
|
|
1761
|
+
* @returns {boolean} True if loaded successfully
|
|
1762
|
+
*/
|
|
1763
|
+
loadPgn(pgn, animate = true) {
|
|
1764
|
+
try {
|
|
1765
|
+
const success = this.positionService.getGame().loadPgn(pgn);
|
|
1766
|
+
if (success) {
|
|
1767
|
+
this._updateBoardPieces(animate, true); // Position load
|
|
1768
|
+
}
|
|
1769
|
+
return success;
|
|
1770
|
+
} catch (error) {
|
|
1771
|
+
console.error('Error loading PGN:', error);
|
|
1772
|
+
return false;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
/**
|
|
1777
|
+
* Gets configuration options
|
|
1778
|
+
* @returns {Object} Configuration object
|
|
1779
|
+
*/
|
|
1780
|
+
getConfig() {
|
|
1781
|
+
return this.config.getConfig();
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* Updates configuration options
|
|
1786
|
+
* @param {Object} newConfig - New configuration options
|
|
1787
|
+
*/
|
|
1788
|
+
setConfig(newConfig) {
|
|
1789
|
+
this.config.update(newConfig);
|
|
1790
|
+
|
|
1791
|
+
// Rebuild board if necessary
|
|
1792
|
+
if (newConfig.size !== undefined) {
|
|
1793
|
+
this.resize(newConfig.size);
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
if (newConfig.orientation !== undefined) {
|
|
1797
|
+
this.orientation(newConfig.orientation);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
/**
|
|
1802
|
+
* Gets or sets the animation style
|
|
1803
|
+
* @param {string} [style] - New animation style ('sequential' or 'simultaneous')
|
|
1804
|
+
* @returns {string} Current animation style
|
|
1805
|
+
*/
|
|
1806
|
+
animationStyle(style) {
|
|
1807
|
+
if (style === undefined) {
|
|
1808
|
+
return this.config.animationStyle;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
if (this.validationService.isValidAnimationStyle(style)) {
|
|
1812
|
+
this.config.animationStyle = style;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
return this.config.animationStyle;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
/**
|
|
1819
|
+
* Gets or sets the simultaneous animation delay
|
|
1820
|
+
* @param {number} [delay] - New delay in milliseconds
|
|
1821
|
+
* @returns {number} Current delay
|
|
1822
|
+
*/
|
|
1823
|
+
simultaneousAnimationDelay(delay) {
|
|
1824
|
+
if (delay === undefined) {
|
|
1825
|
+
return this.config.simultaneousAnimationDelay;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
if (typeof delay === 'number' && delay >= 0) {
|
|
1829
|
+
this.config.simultaneousAnimationDelay = delay;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
return this.config.simultaneousAnimationDelay;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// Additional API methods would be added here following the same pattern
|
|
1836
|
+
// This is a good starting point for the refactored architecture
|
|
1837
|
+
|
|
1838
|
+
// Additional API methods and aliases for backward compatibility
|
|
1839
|
+
insert(square, piece) { return this.putPiece(piece, square); }
|
|
1840
|
+
build() { return this._initialize(); }
|
|
1841
|
+
ascii() { return this.positionService.getGame().ascii(); }
|
|
1842
|
+
board() { return this.positionService.getGame().board(); }
|
|
1843
|
+
getCastlingRights(color) { return this.positionService.getGame().getCastlingRights(color); }
|
|
1844
|
+
getComment() { return this.positionService.getGame().getComment(); }
|
|
1845
|
+
getComments() { return this.positionService.getGame().getComments(); }
|
|
1846
|
+
lastMove() { return this.positionService.getGame().lastMove(); }
|
|
1847
|
+
moveNumber() { return this.positionService.getGame().moveNumber(); }
|
|
1848
|
+
moves(options = {}) { return this.positionService.getGame().moves(options); }
|
|
1849
|
+
squareColor(squareId) { return this.boardService.getSquare(squareId).isWhite() ? 'light' : 'dark'; }
|
|
1850
|
+
isDrawByFiftyMoves() { return this.positionService.getGame().isDrawByFiftyMoves(); }
|
|
1851
|
+
isInsufficientMaterial() { return this.positionService.getGame().isInsufficientMaterial(); }
|
|
1852
|
+
removeComment() { return this.positionService.getGame().removeComment(); }
|
|
1853
|
+
removeComments() { return this.positionService.getGame().removeComments(); }
|
|
1854
|
+
removeHeader(field) { return this.positionService.getGame().removeHeader(field); }
|
|
1855
|
+
setCastlingRights(color, rights) { return this.positionService.getGame().setCastlingRights(color, rights); }
|
|
1856
|
+
setComment(comment) { return this.positionService.getGame().setComment(comment); }
|
|
1857
|
+
setHeader(key, value) { return this.positionService.getGame().setHeader(key, value); }
|
|
1858
|
+
validateFen(fen) { return this.positionService.getGame().validateFen(fen); }
|
|
1859
|
+
|
|
1860
|
+
// Implementazioni reali per highlight/dehighlight
|
|
1861
|
+
highlightSquare(square) {
|
|
1862
|
+
return this.boardService.highlight(square);
|
|
1863
|
+
}
|
|
1864
|
+
dehighlightSquare(square) {
|
|
1865
|
+
return this.boardService.dehighlight(square);
|
|
1866
|
+
}
|
|
1867
|
+
forceSync() { this._updateBoardPieces(true, true); }
|
|
1868
|
+
|
|
1869
|
+
// Metodi mancanti che causano fallimenti nei test
|
|
1870
|
+
/**
|
|
1871
|
+
* Move a piece from one square to another
|
|
1872
|
+
* @param {string|Object} move - Move in format 'e2e4' or {from: 'e2', to: 'e4'}
|
|
1873
|
+
* @param {Object} [opts] - Options
|
|
1874
|
+
* @param {boolean} [opts.animate=true] - Whether to animate
|
|
1875
|
+
* @returns {boolean} True if move was successful
|
|
1876
|
+
*/
|
|
1877
|
+
movePiece(move, opts = {}) {
|
|
1878
|
+
const animate = opts.animate !== undefined ? opts.animate : true;
|
|
1879
|
+
|
|
1880
|
+
// Parse move string if needed
|
|
1881
|
+
let fromSquare, toSquare, promotion;
|
|
1882
|
+
if (typeof move === 'string') {
|
|
1883
|
+
if (move.length === 4) {
|
|
1884
|
+
fromSquare = move.substring(0, 2);
|
|
1885
|
+
toSquare = move.substring(2, 4);
|
|
1886
|
+
} else if (move.length === 5) {
|
|
1887
|
+
fromSquare = move.substring(0, 2);
|
|
1888
|
+
toSquare = move.substring(2, 4);
|
|
1889
|
+
promotion = move.substring(4, 5);
|
|
1890
|
+
} else {
|
|
1891
|
+
throw new Error(`Invalid move format: ${move}`);
|
|
1892
|
+
}
|
|
1893
|
+
} else if (typeof move === 'object' && move.from && move.to) {
|
|
1894
|
+
fromSquare = move.from;
|
|
1895
|
+
toSquare = move.to;
|
|
1896
|
+
promotion = move.promotion;
|
|
1897
|
+
} else {
|
|
1898
|
+
throw new Error(`Invalid move: ${move}`);
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
// Get square objects
|
|
1902
|
+
const fromSquareObj = this.boardService.getSquare(fromSquare);
|
|
1903
|
+
const toSquareObj = this.boardService.getSquare(toSquare);
|
|
1904
|
+
|
|
1905
|
+
if (!fromSquareObj || !toSquareObj) {
|
|
1906
|
+
throw new Error(`Invalid squares: ${fromSquare} or ${toSquare}`);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
// Execute the move
|
|
1910
|
+
return this._onMove(fromSquareObj, toSquareObj, promotion, animate);
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
// Aliases for backward compatibility
|
|
1914
|
+
move(move, animate = true) {
|
|
1915
|
+
// On any new move, clear the redo stack
|
|
1916
|
+
this._undoneMoves = [];
|
|
1917
|
+
return this.movePiece(move, { animate });
|
|
1918
|
+
}
|
|
1919
|
+
get(square) { return this.getPiece(square); }
|
|
1920
|
+
piece(square) { return this.getPiece(square); }
|
|
1921
|
+
put(piece, square, opts = {}) { return this.putPiece(piece, square, opts); }
|
|
1922
|
+
remove(square, opts = {}) { return this.removePiece(square, opts); }
|
|
1923
|
+
load(position, opts = {}) { return this.setPosition(position, opts); }
|
|
1924
|
+
resize(size) { return this.resizeBoard(size); }
|
|
1925
|
+
start(opts = {}) { return this.reset(opts); }
|
|
1926
|
+
clearBoard(opts = {}) { return this.clear(opts); }
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
export { Chessboard };
|
|
1930
|
+
export default Chessboard;
|