@alepot55/chessboardjs 2.3.4 → 2.3.6
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/config/rollup.config.js +2 -1
- package/dist/chessboard.cjs.js +446 -471
- package/dist/chessboard.css +28 -6
- package/dist/chessboard.esm.js +446 -471
- package/dist/chessboard.iife.js +447 -471
- package/dist/chessboard.umd.js +446 -471
- package/package.json +6 -8
- package/src/components/Piece.js +11 -11
- package/src/components/Square.js +1 -1
- package/src/core/Chessboard.js +183 -266
- package/src/core/ChessboardConfig.js +0 -16
- package/src/core/ChessboardFactory.js +0 -5
- package/src/errors/messages.js +0 -1
- package/src/services/BoardService.js +20 -1
- package/src/services/EventService.js +131 -45
- package/src/services/MoveService.js +81 -94
- package/src/services/PieceService.js +18 -11
- package/src/services/ValidationService.js +1 -14
- package/src/styles/index.css +8 -0
- package/src/utils/validation.js +1 -5
- package/tests/unit/chessboard-config-animations.test.js +0 -9
- package/tests/unit/chessboard-robust.test.js +0 -44
- package/tools/build-html-examples.cjs +80 -0
- package/tools/build-html-examples.js +78 -0
- package/chessboard.bundle.js +0 -4072
package/dist/chessboard.cjs.js
CHANGED
|
@@ -128,7 +128,6 @@ const ERROR_MESSAGES = Object.freeze({
|
|
|
128
128
|
invalid_fadeTime: 'Invalid fadeTime: ',
|
|
129
129
|
invalid_fadeAnimation: 'Invalid fadeAnimation: ',
|
|
130
130
|
invalid_ratio: 'Invalid ratio: ',
|
|
131
|
-
invalid_animationStyle: 'Invalid animationStyle: ',
|
|
132
131
|
animation_failed: 'Animation failed: ',
|
|
133
132
|
|
|
134
133
|
// Event handlers
|
|
@@ -380,7 +379,6 @@ const VALID_VALUES = Object.freeze({
|
|
|
380
379
|
movableColors: ['w', 'b', 'white', 'black', 'both', 'none'],
|
|
381
380
|
dropOffBoard: ['snapback', 'trash'],
|
|
382
381
|
easingTypes: ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'],
|
|
383
|
-
animationStyles: ['sequential', 'simultaneous'],
|
|
384
382
|
modes: ['normal', 'creative', 'analysis'],
|
|
385
383
|
promotionPieces: ['q', 'r', 'b', 'n', 'Q', 'R', 'B', 'N']
|
|
386
384
|
});
|
|
@@ -709,14 +707,6 @@ class ValidationService {
|
|
|
709
707
|
return this._validValues.easingTypes.includes(easing);
|
|
710
708
|
}
|
|
711
709
|
|
|
712
|
-
/**
|
|
713
|
-
* Validates animation style
|
|
714
|
-
* @param {string} style - Animation style to validate
|
|
715
|
-
* @returns {boolean} True if valid
|
|
716
|
-
*/
|
|
717
|
-
isValidAnimationStyle(style) {
|
|
718
|
-
return this._validValues.animationStyles.includes(style);
|
|
719
|
-
}
|
|
720
710
|
|
|
721
711
|
/**
|
|
722
712
|
* Validates CSS color format
|
|
@@ -913,11 +903,7 @@ class ValidationService {
|
|
|
913
903
|
if (config.dropOffBoard && !this.isValidDropOffBoard(config.dropOffBoard)) {
|
|
914
904
|
errors.push(ERROR_MESSAGES.invalid_dropOffBoard + config.dropOffBoard);
|
|
915
905
|
}
|
|
916
|
-
|
|
917
|
-
if (config.animationStyle && !this.isValidAnimationStyle(config.animationStyle)) {
|
|
918
|
-
errors.push(ERROR_MESSAGES.invalid_animationStyle + config.animationStyle);
|
|
919
|
-
}
|
|
920
|
-
|
|
906
|
+
|
|
921
907
|
// Validate callbacks
|
|
922
908
|
const callbacks = ['onMove', 'onMoveEnd', 'onChange', 'onDragStart', 'onDragMove', 'onDrop', 'onSnapbackEnd'];
|
|
923
909
|
for (const callback of callbacks) {
|
|
@@ -1095,7 +1081,6 @@ const DEFAULT_CONFIG$1 = Object.freeze({
|
|
|
1095
1081
|
fadeAnimation: 'ease',
|
|
1096
1082
|
ratio: 0.9,
|
|
1097
1083
|
piecesPath: '../assets/themes/default',
|
|
1098
|
-
animationStyle: 'simultaneous',
|
|
1099
1084
|
simultaneousAnimationDelay: 100,
|
|
1100
1085
|
onMove: () => true,
|
|
1101
1086
|
onMoveEnd: () => true,
|
|
@@ -1221,7 +1206,6 @@ class ChessboardConfig {
|
|
|
1221
1206
|
this.fadeTime = this._setTime(config.fadeTime);
|
|
1222
1207
|
|
|
1223
1208
|
// Animation style properties
|
|
1224
|
-
this.animationStyle = this._validateAnimationStyle(config.animationStyle);
|
|
1225
1209
|
this.simultaneousAnimationDelay = this._validateDelay(config.simultaneousAnimationDelay);
|
|
1226
1210
|
}
|
|
1227
1211
|
|
|
@@ -1282,19 +1266,6 @@ class ChessboardConfig {
|
|
|
1282
1266
|
return callback;
|
|
1283
1267
|
}
|
|
1284
1268
|
|
|
1285
|
-
/**
|
|
1286
|
-
* Validates animation style
|
|
1287
|
-
* @private
|
|
1288
|
-
* @param {string} style - Animation style
|
|
1289
|
-
* @returns {string} Validated style
|
|
1290
|
-
* @throws {ConfigurationError} If style is invalid
|
|
1291
|
-
*/
|
|
1292
|
-
_validateAnimationStyle(style) {
|
|
1293
|
-
if (!this._validationService.isValidAnimationStyle(style)) {
|
|
1294
|
-
throw new ConfigurationError('Invalid animation style', 'animationStyle', style);
|
|
1295
|
-
}
|
|
1296
|
-
return style;
|
|
1297
|
-
}
|
|
1298
1269
|
|
|
1299
1270
|
/**
|
|
1300
1271
|
* Validates animation delay
|
|
@@ -1432,7 +1403,6 @@ class ChessboardConfig {
|
|
|
1432
1403
|
fadeTime: this.fadeTime,
|
|
1433
1404
|
fadeAnimation: this.fadeAnimation,
|
|
1434
1405
|
piecesPath: this.piecesPath,
|
|
1435
|
-
animationStyle: this.animationStyle,
|
|
1436
1406
|
simultaneousAnimationDelay: this.simultaneousAnimationDelay,
|
|
1437
1407
|
onlyLegalMoves: this.onlyLegalMoves
|
|
1438
1408
|
};
|
|
@@ -1670,6 +1640,8 @@ class Piece {
|
|
|
1670
1640
|
if (callback) callback();
|
|
1671
1641
|
}
|
|
1672
1642
|
};
|
|
1643
|
+
// Se l'elemento è già stato rimosso, esci subito
|
|
1644
|
+
if (!this.element) { console.debug(`[Piece] fadeOut: ${this.id} - element is null (init)`); if (callback) callback(); return; }
|
|
1673
1645
|
fade();
|
|
1674
1646
|
}
|
|
1675
1647
|
|
|
@@ -1677,24 +1649,22 @@ class Piece {
|
|
|
1677
1649
|
if (!this.element) { console.debug(`[Piece] setDrag: ${this.id} - element is null`); return; }
|
|
1678
1650
|
this.element.ondragstart = (e) => { e.preventDefault(); };
|
|
1679
1651
|
this.element.onmousedown = f;
|
|
1652
|
+
this.element.ontouchstart = f; // Drag touch
|
|
1680
1653
|
console.debug(`[Piece] setDrag: ${this.id}`);
|
|
1681
1654
|
}
|
|
1682
1655
|
|
|
1683
1656
|
destroy() {
|
|
1657
|
+
if (!this.element) return; // Idempotente: già rimosso
|
|
1684
1658
|
console.debug(`[Piece] Destroy: ${this.id}`);
|
|
1685
1659
|
// Remove all event listeners
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
if (this.element.parentNode) {
|
|
1692
|
-
this.element.parentNode.removeChild(this.element);
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
// Clear references
|
|
1696
|
-
this.element = null;
|
|
1660
|
+
this.element.onmousedown = null;
|
|
1661
|
+
this.element.ondragstart = null;
|
|
1662
|
+
// Remove from DOM
|
|
1663
|
+
if (this.element.parentNode) {
|
|
1664
|
+
this.element.parentNode.removeChild(this.element);
|
|
1697
1665
|
}
|
|
1666
|
+
// Clear references
|
|
1667
|
+
this.element = null;
|
|
1698
1668
|
}
|
|
1699
1669
|
|
|
1700
1670
|
translate(to, duration, transition_f, speed, callback = null) {
|
|
@@ -1819,8 +1789,8 @@ class Square {
|
|
|
1819
1789
|
// Best practice: destroy the piece object if present
|
|
1820
1790
|
if (this.piece && typeof this.piece.destroy === 'function') {
|
|
1821
1791
|
this.piece.destroy();
|
|
1822
|
-
this.piece = null;
|
|
1823
1792
|
}
|
|
1793
|
+
this.piece = null;
|
|
1824
1794
|
// Remove any orphaned img.piece elements from the DOM
|
|
1825
1795
|
const pieceElements = this.element.querySelectorAll('img.piece');
|
|
1826
1796
|
pieceElements.forEach(element => {
|
|
@@ -2314,13 +2284,32 @@ class BoardService {
|
|
|
2314
2284
|
|
|
2315
2285
|
/**
|
|
2316
2286
|
* Gets a square by its ID
|
|
2317
|
-
* @param {string} squareId - Square identifier (
|
|
2287
|
+
* @param {string} squareId - Square identifier (API pubblica)
|
|
2318
2288
|
* @returns {Square|null} The square or null if not found
|
|
2319
2289
|
*/
|
|
2320
2290
|
getSquare(squareId) {
|
|
2321
2291
|
return this.squares[squareId] || null;
|
|
2322
2292
|
}
|
|
2323
2293
|
|
|
2294
|
+
/**
|
|
2295
|
+
* Highlight a square (solo oggetto)
|
|
2296
|
+
* @param {Square} square
|
|
2297
|
+
* @param {Object} [opts]
|
|
2298
|
+
*/
|
|
2299
|
+
highlightSquare(square, opts = {}) {
|
|
2300
|
+
if (!square) throw new Error('highlightSquare richiede oggetto Square');
|
|
2301
|
+
// ... logica esistente ...
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Dehighlight a square (solo oggetto)
|
|
2305
|
+
* @param {Square} square
|
|
2306
|
+
* @param {Object} [opts]
|
|
2307
|
+
*/
|
|
2308
|
+
dehighlightSquare(square, opts = {}) {
|
|
2309
|
+
if (!square) throw new Error('dehighlightSquare richiede oggetto Square');
|
|
2310
|
+
// ... logica esistente ...
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2324
2313
|
/**
|
|
2325
2314
|
* Gets all squares
|
|
2326
2315
|
* @returns {Object.<string, Square>} All squares indexed by ID
|
|
@@ -3160,24 +3149,77 @@ class EventService {
|
|
|
3160
3149
|
|
|
3161
3150
|
// Click handler
|
|
3162
3151
|
const handleClick = (e) => {
|
|
3152
|
+
// Ghost click prevention: ignora il click se appena gestito un touch
|
|
3153
|
+
if (square._ignoreNextClick) {
|
|
3154
|
+
square._ignoreNextClick = false;
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3163
3157
|
e.stopPropagation();
|
|
3164
3158
|
if (this.config.clickable && !this.isAnimating) {
|
|
3165
3159
|
onSquareClick(square);
|
|
3166
3160
|
}
|
|
3167
3161
|
};
|
|
3168
3162
|
|
|
3163
|
+
// --- Touch tap/drag separation ---
|
|
3164
|
+
let touchMoved = false;
|
|
3165
|
+
let touchStartX = 0;
|
|
3166
|
+
let touchStartY = 0;
|
|
3167
|
+
let touchTimeout = null;
|
|
3168
|
+
|
|
3169
|
+
const handleTouchStart = (e) => {
|
|
3170
|
+
if (!e.touches || e.touches.length > 1) return; // solo primo dito
|
|
3171
|
+
touchMoved = false;
|
|
3172
|
+
touchStartX = e.touches[0].clientX;
|
|
3173
|
+
touchStartY = e.touches[0].clientY;
|
|
3174
|
+
// Timeout: se il dito non si muove, dopo 150ms sarà considerato tap
|
|
3175
|
+
touchTimeout = setTimeout(() => {
|
|
3176
|
+
if (!touchMoved) {
|
|
3177
|
+
// Prevenzione ghost click: ignora il prossimo click
|
|
3178
|
+
square._ignoreNextClick = true;
|
|
3179
|
+
setTimeout(() => { square._ignoreNextClick = false; }, 400);
|
|
3180
|
+
handleClick(e); // Tap breve = selezione
|
|
3181
|
+
}
|
|
3182
|
+
}, 150);
|
|
3183
|
+
};
|
|
3184
|
+
|
|
3185
|
+
const handleTouchMove = (e) => {
|
|
3186
|
+
if (!e.touches || e.touches.length > 1) return;
|
|
3187
|
+
const dx = Math.abs(e.touches[0].clientX - touchStartX);
|
|
3188
|
+
const dy = Math.abs(e.touches[0].clientY - touchStartY);
|
|
3189
|
+
if (dx > 5 || dy > 5) {
|
|
3190
|
+
touchMoved = true;
|
|
3191
|
+
if (touchTimeout) {
|
|
3192
|
+
clearTimeout(touchTimeout);
|
|
3193
|
+
touchTimeout = null;
|
|
3194
|
+
}
|
|
3195
|
+
// Il drag vero e proprio è gestito da createDragFunction sul pezzo
|
|
3196
|
+
}
|
|
3197
|
+
};
|
|
3198
|
+
|
|
3199
|
+
const handleTouchEnd = (e) => {
|
|
3200
|
+
if (touchTimeout) {
|
|
3201
|
+
clearTimeout(touchTimeout);
|
|
3202
|
+
touchTimeout = null;
|
|
3203
|
+
}
|
|
3204
|
+
};
|
|
3205
|
+
|
|
3169
3206
|
// Add listeners
|
|
3170
3207
|
square.element.addEventListener('mouseover', throttledHover);
|
|
3171
3208
|
square.element.addEventListener('mouseout', throttledLeave);
|
|
3172
3209
|
square.element.addEventListener('click', handleClick);
|
|
3173
|
-
|
|
3210
|
+
// Touch: separa tap e drag
|
|
3211
|
+
square.element.addEventListener('touchstart', handleTouchStart);
|
|
3212
|
+
square.element.addEventListener('touchmove', handleTouchMove);
|
|
3213
|
+
square.element.addEventListener('touchend', handleTouchEnd);
|
|
3174
3214
|
|
|
3175
3215
|
// Store listeners for cleanup
|
|
3176
3216
|
listeners.push(
|
|
3177
3217
|
{ element: square.element, type: 'mouseover', handler: throttledHover },
|
|
3178
3218
|
{ element: square.element, type: 'mouseout', handler: throttledLeave },
|
|
3179
3219
|
{ element: square.element, type: 'click', handler: handleClick },
|
|
3180
|
-
{ element: square.element, type: 'touchstart', handler:
|
|
3220
|
+
{ element: square.element, type: 'touchstart', handler: handleTouchStart },
|
|
3221
|
+
{ element: square.element, type: 'touchmove', handler: handleTouchMove },
|
|
3222
|
+
{ element: square.element, type: 'touchend', handler: handleTouchEnd }
|
|
3181
3223
|
);
|
|
3182
3224
|
|
|
3183
3225
|
this.eventListeners.set(square.id, listeners);
|
|
@@ -3216,14 +3258,24 @@ class EventService {
|
|
|
3216
3258
|
}
|
|
3217
3259
|
|
|
3218
3260
|
// Track initial position for drag threshold
|
|
3261
|
+
const isTouch = event.type && event.type.startsWith('touch');
|
|
3219
3262
|
const startX = event.clientX || (event.touches && event.touches[0]?.clientX) || 0;
|
|
3220
3263
|
const startY = event.clientY || (event.touches && event.touches[0]?.clientY) || 0;
|
|
3221
3264
|
|
|
3265
|
+
// --- Touch scroll lock helper ---
|
|
3266
|
+
const addScrollLock = () => {
|
|
3267
|
+
document.body.classList.add('chessboardjs-dragging');
|
|
3268
|
+
};
|
|
3269
|
+
const removeScrollLock = () => {
|
|
3270
|
+
document.body.classList.remove('chessboardjs-dragging');
|
|
3271
|
+
};
|
|
3272
|
+
|
|
3273
|
+
// --- MOVE HANDLER (mouse + touch unified) ---
|
|
3222
3274
|
const moveAt = (event) => {
|
|
3223
3275
|
const boardElement = this.boardService.element;
|
|
3224
3276
|
const squareSize = boardElement.offsetWidth / 8;
|
|
3225
3277
|
|
|
3226
|
-
// Get mouse coordinates
|
|
3278
|
+
// Get mouse/touch coordinates
|
|
3227
3279
|
let clientX, clientY;
|
|
3228
3280
|
if (event.touches && event.touches[0]) {
|
|
3229
3281
|
clientX = event.touches[0].clientX;
|
|
@@ -3244,15 +3296,28 @@ class EventService {
|
|
|
3244
3296
|
return true;
|
|
3245
3297
|
};
|
|
3246
3298
|
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3299
|
+
// --- DRAG MOVE (mouse + touch) ---
|
|
3300
|
+
const onPointerMove = (event) => {
|
|
3301
|
+
// For touch, only handle the first finger
|
|
3302
|
+
if (event.touches && event.touches.length > 1) return;
|
|
3303
|
+
if (event.touches && !event.touches[0]) return;
|
|
3304
|
+
if (event.type && event.type.startsWith('touch')) event.preventDefault();
|
|
3305
|
+
|
|
3306
|
+
const currentX = event.clientX || (event.touches && event.touches[0]?.clientX) || 0;
|
|
3307
|
+
const currentY = event.clientY || (event.touches && event.touches[0]?.clientY) || 0;
|
|
3250
3308
|
const deltaX = Math.abs(currentX - startX);
|
|
3251
3309
|
const deltaY = Math.abs(currentY - startY);
|
|
3252
3310
|
|
|
3253
|
-
// Start dragging if mouse moved enough
|
|
3311
|
+
// Start dragging if mouse/touch moved enough
|
|
3254
3312
|
if (!isDragging && (deltaX > 3 || deltaY > 3)) {
|
|
3255
3313
|
isDragging = true;
|
|
3314
|
+
// Inizio drag: blocca update board
|
|
3315
|
+
if (this.chessboard) this.chessboard._isDragging = true;
|
|
3316
|
+
|
|
3317
|
+
// Mostra hint all'inizio del drag se attivi
|
|
3318
|
+
if (this.config.hints && typeof this.chessboard._boundOnPieceHover === 'function') {
|
|
3319
|
+
this.chessboard._boundOnPieceHover(from);
|
|
3320
|
+
}
|
|
3256
3321
|
|
|
3257
3322
|
// Set up drag state
|
|
3258
3323
|
if (!this.config.clickable) {
|
|
@@ -3265,7 +3330,6 @@ class EventService {
|
|
|
3265
3330
|
// Visual feedback
|
|
3266
3331
|
if (this.config.clickable) {
|
|
3267
3332
|
from.select();
|
|
3268
|
-
// Show hints would be handled by the main class
|
|
3269
3333
|
}
|
|
3270
3334
|
|
|
3271
3335
|
// Prepare piece for dragging
|
|
@@ -3275,6 +3339,9 @@ class EventService {
|
|
|
3275
3339
|
|
|
3276
3340
|
DragOptimizations.enableForDrag(img);
|
|
3277
3341
|
|
|
3342
|
+
// Lock scroll for touch
|
|
3343
|
+
if (isTouch) addScrollLock();
|
|
3344
|
+
|
|
3278
3345
|
// Call drag start callback
|
|
3279
3346
|
if (!onDragStart(square, piece)) {
|
|
3280
3347
|
return;
|
|
@@ -3288,8 +3355,16 @@ class EventService {
|
|
|
3288
3355
|
// Update target square
|
|
3289
3356
|
const boardElement = this.boardService.element;
|
|
3290
3357
|
const boardRect = boardElement.getBoundingClientRect();
|
|
3291
|
-
|
|
3292
|
-
|
|
3358
|
+
let clientX, clientY;
|
|
3359
|
+
if (event.touches && event.touches[0]) {
|
|
3360
|
+
clientX = event.touches[0].clientX;
|
|
3361
|
+
clientY = event.touches[0].clientY;
|
|
3362
|
+
} else {
|
|
3363
|
+
clientX = event.clientX;
|
|
3364
|
+
clientY = event.clientY;
|
|
3365
|
+
}
|
|
3366
|
+
const x = clientX - boardRect.left;
|
|
3367
|
+
const y = clientY - boardRect.top;
|
|
3293
3368
|
|
|
3294
3369
|
let newTo = null;
|
|
3295
3370
|
if (x >= 0 && x <= boardRect.width && y >= 0 && y <= boardRect.height) {
|
|
@@ -3308,18 +3383,26 @@ class EventService {
|
|
|
3308
3383
|
}
|
|
3309
3384
|
};
|
|
3310
3385
|
|
|
3311
|
-
|
|
3312
|
-
|
|
3386
|
+
// --- DRAG END (mouse + touch) ---
|
|
3387
|
+
const onPointerUp = (event) => {
|
|
3313
3388
|
previousHighlight?.dehighlight();
|
|
3314
|
-
document.removeEventListener('mousemove',
|
|
3315
|
-
window.removeEventListener('mouseup',
|
|
3389
|
+
document.removeEventListener('mousemove', onPointerMove);
|
|
3390
|
+
window.removeEventListener('mouseup', onPointerUp);
|
|
3391
|
+
document.removeEventListener('touchmove', onPointerMove);
|
|
3392
|
+
window.removeEventListener('touchend', onPointerUp);
|
|
3393
|
+
if (isTouch) removeScrollLock();
|
|
3394
|
+
// Fine drag: sblocca update board
|
|
3395
|
+
if (this.chessboard) this.chessboard._isDragging = false;
|
|
3396
|
+
|
|
3397
|
+
// Rimuovi hint alla fine del drag se attivi
|
|
3398
|
+
if (this.config.hints && typeof this.chessboard._boundOnPieceLeave === 'function') {
|
|
3399
|
+
this.chessboard._boundOnPieceLeave(from);
|
|
3400
|
+
}
|
|
3316
3401
|
|
|
3317
|
-
// If this was just a click, don't interfere
|
|
3318
3402
|
if (!isDragging) {
|
|
3319
3403
|
return;
|
|
3320
3404
|
}
|
|
3321
3405
|
|
|
3322
|
-
// Clean up drag state
|
|
3323
3406
|
img.style.zIndex = '20';
|
|
3324
3407
|
img.classList.remove('dragging');
|
|
3325
3408
|
img.style.willChange = 'auto';
|
|
@@ -3331,23 +3414,26 @@ class EventService {
|
|
|
3331
3414
|
if (isTrashDrop) {
|
|
3332
3415
|
this._handleTrashDrop(originalFrom, onRemove);
|
|
3333
3416
|
} else if (!to) {
|
|
3334
|
-
// Reset piece position instantly for snapback
|
|
3335
3417
|
img.style.position = '';
|
|
3336
3418
|
img.style.left = '';
|
|
3337
3419
|
img.style.top = '';
|
|
3338
3420
|
img.style.transform = '';
|
|
3339
|
-
|
|
3340
3421
|
this._handleSnapback(originalFrom, piece, onSnapback);
|
|
3341
3422
|
} else {
|
|
3342
|
-
// Handle drop like a click - simple and reliable
|
|
3343
3423
|
this._handleDrop(originalFrom, to, piece, onMove, onSnapback);
|
|
3344
3424
|
}
|
|
3345
3425
|
};
|
|
3346
3426
|
|
|
3347
|
-
// Attach
|
|
3348
|
-
window.addEventListener('mouseup',
|
|
3349
|
-
document.addEventListener('mousemove',
|
|
3350
|
-
img.addEventListener('mouseup',
|
|
3427
|
+
// --- Attach listeners (mouse + touch) ---
|
|
3428
|
+
window.addEventListener('mouseup', onPointerUp, { once: true });
|
|
3429
|
+
document.addEventListener('mousemove', onPointerMove);
|
|
3430
|
+
img.addEventListener('mouseup', onPointerUp, { once: true });
|
|
3431
|
+
// Touch events
|
|
3432
|
+
window.addEventListener('touchend', onPointerUp, { once: true });
|
|
3433
|
+
document.addEventListener('touchmove', onPointerMove, { passive: false });
|
|
3434
|
+
|
|
3435
|
+
// Per robustezza: se il drag parte da touch, blocca subito lo scroll
|
|
3436
|
+
if (isTouch) addScrollLock();
|
|
3351
3437
|
};
|
|
3352
3438
|
}
|
|
3353
3439
|
|
|
@@ -3570,6 +3656,7 @@ class EventService {
|
|
|
3570
3656
|
if (!from) {
|
|
3571
3657
|
if (this.moveService.canMove(square)) {
|
|
3572
3658
|
if (this.config.clickable) {
|
|
3659
|
+
this.boardService.applyToAllSquares('removeHint'); // Rimuovi hint prima di selezionare
|
|
3573
3660
|
onSelect(square);
|
|
3574
3661
|
}
|
|
3575
3662
|
this.clicked = square;
|
|
@@ -3579,9 +3666,13 @@ class EventService {
|
|
|
3579
3666
|
}
|
|
3580
3667
|
}
|
|
3581
3668
|
|
|
3582
|
-
//
|
|
3669
|
+
// --- Touch: non deselezionare su doppio tap sulla stessa casella ---
|
|
3583
3670
|
if (this.clicked === square) {
|
|
3671
|
+
if (window.event && window.event.type && window.event.type.startsWith('touch')) {
|
|
3672
|
+
return false;
|
|
3673
|
+
}
|
|
3584
3674
|
onDeselect(square);
|
|
3675
|
+
this.boardService.applyToAllSquares('removeHint');
|
|
3585
3676
|
this.clicked = null;
|
|
3586
3677
|
return false;
|
|
3587
3678
|
}
|
|
@@ -3590,68 +3681,52 @@ class EventService {
|
|
|
3590
3681
|
if (!promotion && this.moveService.requiresPromotion(new Move$1(from, square))) {
|
|
3591
3682
|
console.log('Move requires promotion:', from.id, '->', square.id);
|
|
3592
3683
|
|
|
3593
|
-
// Set up promotion UI
|
|
3594
3684
|
this.moveService.setupPromotion(
|
|
3595
3685
|
new Move$1(from, square),
|
|
3596
3686
|
this.boardService.squares,
|
|
3597
3687
|
(selectedPromotion) => {
|
|
3598
3688
|
console.log('Promotion selected:', selectedPromotion);
|
|
3599
|
-
|
|
3600
|
-
// Clear promotion UI first
|
|
3601
3689
|
this.boardService.applyToAllSquares('removePromotion');
|
|
3602
3690
|
this.boardService.applyToAllSquares('removeCover');
|
|
3603
|
-
|
|
3604
|
-
// Execute the move with promotion
|
|
3605
3691
|
const moveResult = onMove(from, square, selectedPromotion, animate);
|
|
3606
|
-
|
|
3607
3692
|
if (moveResult) {
|
|
3608
|
-
// After a successful promotion move, we need to replace the piece
|
|
3609
|
-
// after the drop animation completes
|
|
3610
3693
|
this._schedulePromotionPieceReplacement(square, selectedPromotion);
|
|
3611
|
-
|
|
3612
3694
|
onDeselect(from);
|
|
3695
|
+
this.boardService.applyToAllSquares('removeHint');
|
|
3613
3696
|
this.clicked = null;
|
|
3614
3697
|
}
|
|
3615
3698
|
},
|
|
3616
3699
|
() => {
|
|
3617
3700
|
console.log('Promotion cancelled');
|
|
3618
|
-
|
|
3619
|
-
// Clear promotion UI on cancel
|
|
3620
3701
|
this.boardService.applyToAllSquares('removePromotion');
|
|
3621
3702
|
this.boardService.applyToAllSquares('removeCover');
|
|
3622
|
-
|
|
3623
3703
|
onDeselect(from);
|
|
3704
|
+
this.boardService.applyToAllSquares('removeHint');
|
|
3624
3705
|
this.clicked = null;
|
|
3625
3706
|
}
|
|
3626
3707
|
);
|
|
3627
3708
|
return false;
|
|
3628
3709
|
}
|
|
3629
3710
|
|
|
3630
|
-
// Attempt to make move
|
|
3631
3711
|
const moveResult = onMove(from, square, promotion, animate);
|
|
3632
3712
|
|
|
3633
3713
|
if (moveResult) {
|
|
3634
|
-
// Move successful
|
|
3635
3714
|
onDeselect(from);
|
|
3715
|
+
this.boardService.applyToAllSquares('removeHint');
|
|
3636
3716
|
this.clicked = null;
|
|
3637
3717
|
return true;
|
|
3638
3718
|
} else {
|
|
3639
|
-
// Move failed - check if clicked square has a piece we can move
|
|
3640
3719
|
if (this.moveService.canMove(square)) {
|
|
3641
|
-
// Deselect the previous piece
|
|
3642
3720
|
onDeselect(from);
|
|
3643
|
-
|
|
3644
|
-
// Select the new piece if clicking is enabled
|
|
3721
|
+
this.boardService.applyToAllSquares('removeHint');
|
|
3645
3722
|
if (this.config.clickable) {
|
|
3646
3723
|
onSelect(square);
|
|
3647
3724
|
}
|
|
3648
|
-
|
|
3649
|
-
// Set the new piece as clicked
|
|
3650
3725
|
this.clicked = square;
|
|
3651
3726
|
return false;
|
|
3652
3727
|
} else {
|
|
3653
|
-
// Move failed and no valid piece to select
|
|
3654
3728
|
onDeselect(from);
|
|
3729
|
+
this.boardService.applyToAllSquares('removeHint');
|
|
3655
3730
|
this.clicked = null;
|
|
3656
3731
|
return false;
|
|
3657
3732
|
}
|
|
@@ -3915,27 +3990,27 @@ class MoveService {
|
|
|
3915
3990
|
*/
|
|
3916
3991
|
canMove(square) {
|
|
3917
3992
|
if (!square.piece) return false;
|
|
3918
|
-
|
|
3993
|
+
|
|
3919
3994
|
const { movableColors, onlyLegalMoves } = this.config;
|
|
3920
|
-
|
|
3995
|
+
|
|
3921
3996
|
if (movableColors === 'none') return false;
|
|
3922
3997
|
if (movableColors === 'w' && square.piece.color === 'b') return false;
|
|
3923
3998
|
if (movableColors === 'b' && square.piece.color === 'w') return false;
|
|
3924
|
-
|
|
3999
|
+
|
|
3925
4000
|
if (!onlyLegalMoves) return true;
|
|
3926
|
-
|
|
4001
|
+
|
|
3927
4002
|
// Check if position service and game are available
|
|
3928
4003
|
if (!this.positionService || !this.positionService.getGame()) {
|
|
3929
4004
|
return false;
|
|
3930
4005
|
}
|
|
3931
|
-
|
|
4006
|
+
|
|
3932
4007
|
const game = this.positionService.getGame();
|
|
3933
4008
|
return square.piece.color === game.turn();
|
|
3934
4009
|
}
|
|
3935
4010
|
|
|
3936
4011
|
/**
|
|
3937
4012
|
* Converts various move formats to a Move instance
|
|
3938
|
-
* @param {string|Move} move - Move in various formats
|
|
4013
|
+
* @param {string|Move|Object} move - Move in various formats
|
|
3939
4014
|
* @param {Object} squares - All board squares
|
|
3940
4015
|
* @returns {Move} Move instance
|
|
3941
4016
|
* @throws {MoveError} When move format is invalid
|
|
@@ -3944,19 +4019,22 @@ class MoveService {
|
|
|
3944
4019
|
if (move instanceof Move$1) {
|
|
3945
4020
|
return move;
|
|
3946
4021
|
}
|
|
3947
|
-
|
|
4022
|
+
if (typeof move === 'object' && move.from && move.to) {
|
|
4023
|
+
// Se sono id, converto in oggetti; se sono già oggetti, li uso direttamente
|
|
4024
|
+
const fromSquare = typeof move.from === 'string' ? squares[move.from] : move.from;
|
|
4025
|
+
const toSquare = typeof move.to === 'string' ? squares[move.to] : move.to;
|
|
4026
|
+
if (!fromSquare || !toSquare) throw new MoveError(ERROR_MESSAGES.invalid_move_format, move.from, move.to);
|
|
4027
|
+
return new Move$1(fromSquare, toSquare, move.promotion);
|
|
4028
|
+
}
|
|
3948
4029
|
if (typeof move === 'string' && move.length >= 4) {
|
|
3949
4030
|
const fromId = move.slice(0, 2);
|
|
3950
4031
|
const toId = move.slice(2, 4);
|
|
3951
4032
|
const promotion = move.slice(4, 5) || null;
|
|
3952
|
-
|
|
3953
4033
|
if (!squares[fromId] || !squares[toId]) {
|
|
3954
4034
|
throw new MoveError(ERROR_MESSAGES.invalid_move_format, fromId, toId);
|
|
3955
4035
|
}
|
|
3956
|
-
|
|
3957
4036
|
return new Move$1(squares[fromId], squares[toId], promotion);
|
|
3958
4037
|
}
|
|
3959
|
-
|
|
3960
4038
|
throw new MoveError(ERROR_MESSAGES.invalid_move_format, 'unknown', 'unknown');
|
|
3961
4039
|
}
|
|
3962
4040
|
|
|
@@ -3967,9 +4045,9 @@ class MoveService {
|
|
|
3967
4045
|
*/
|
|
3968
4046
|
isLegalMove(move) {
|
|
3969
4047
|
const legalMoves = this.getLegalMoves(move.from.id);
|
|
3970
|
-
|
|
3971
|
-
return legalMoves.some(legalMove =>
|
|
3972
|
-
legalMove.to === move.to.id &&
|
|
4048
|
+
|
|
4049
|
+
return legalMoves.some(legalMove =>
|
|
4050
|
+
legalMove.to === move.to.id &&
|
|
3973
4051
|
move.promotion === legalMove.promotion
|
|
3974
4052
|
);
|
|
3975
4053
|
}
|
|
@@ -3985,16 +4063,16 @@ class MoveService {
|
|
|
3985
4063
|
if (!this.positionService || !this.positionService.getGame()) {
|
|
3986
4064
|
return [];
|
|
3987
4065
|
}
|
|
3988
|
-
|
|
4066
|
+
|
|
3989
4067
|
const game = this.positionService.getGame();
|
|
3990
|
-
|
|
4068
|
+
|
|
3991
4069
|
if (!game) return [];
|
|
3992
|
-
|
|
4070
|
+
|
|
3993
4071
|
const options = { verbose };
|
|
3994
4072
|
if (from) {
|
|
3995
4073
|
options.square = from;
|
|
3996
4074
|
}
|
|
3997
|
-
|
|
4075
|
+
|
|
3998
4076
|
return game.moves(options);
|
|
3999
4077
|
}
|
|
4000
4078
|
|
|
@@ -4008,115 +4086,99 @@ class MoveService {
|
|
|
4008
4086
|
if (!this.positionService || !this.positionService.getGame()) {
|
|
4009
4087
|
return [];
|
|
4010
4088
|
}
|
|
4011
|
-
|
|
4089
|
+
|
|
4012
4090
|
const game = this.positionService.getGame();
|
|
4013
4091
|
if (!game) return [];
|
|
4014
|
-
|
|
4092
|
+
|
|
4015
4093
|
const cacheKey = `${square.id}-${game.fen()}`;
|
|
4016
4094
|
let moves = this._movesCache.get(cacheKey);
|
|
4017
|
-
|
|
4095
|
+
|
|
4018
4096
|
if (!moves) {
|
|
4019
4097
|
moves = game.moves({ square: square.id, verbose: true });
|
|
4020
4098
|
this._movesCache.set(cacheKey, moves);
|
|
4021
|
-
|
|
4099
|
+
|
|
4022
4100
|
// Clear cache after a short delay to prevent memory buildup
|
|
4023
4101
|
if (this._cacheTimeout) {
|
|
4024
4102
|
clearTimeout(this._cacheTimeout);
|
|
4025
4103
|
}
|
|
4026
|
-
|
|
4104
|
+
|
|
4027
4105
|
this._cacheTimeout = setTimeout(() => {
|
|
4028
4106
|
this._movesCache.clear();
|
|
4029
4107
|
}, 1000);
|
|
4030
4108
|
}
|
|
4031
|
-
|
|
4109
|
+
|
|
4032
4110
|
return moves;
|
|
4033
4111
|
}
|
|
4034
4112
|
|
|
4035
4113
|
/**
|
|
4036
4114
|
* Executes a move on the game
|
|
4037
|
-
* @param {Move} move - Move to execute
|
|
4115
|
+
* @param {Move} move - Move to execute (deve essere oggetto Move)
|
|
4038
4116
|
* @returns {Object|null} Move result from chess.js or null if invalid
|
|
4039
4117
|
*/
|
|
4040
4118
|
executeMove(move) {
|
|
4041
|
-
|
|
4119
|
+
if (!(move instanceof Move$1)) throw new Error('executeMove richiede un oggetto Move');
|
|
4042
4120
|
if (!this.positionService || !this.positionService.getGame()) {
|
|
4043
4121
|
return null;
|
|
4044
4122
|
}
|
|
4045
|
-
|
|
4046
4123
|
const game = this.positionService.getGame();
|
|
4047
4124
|
if (!game) return null;
|
|
4048
|
-
|
|
4049
4125
|
const moveOptions = {
|
|
4050
4126
|
from: move.from.id,
|
|
4051
4127
|
to: move.to.id
|
|
4052
4128
|
};
|
|
4053
|
-
|
|
4054
|
-
console.log('executeMove - move.promotion:', move.promotion);
|
|
4055
|
-
console.log('executeMove - move.hasPromotion():', move.hasPromotion());
|
|
4056
|
-
|
|
4057
4129
|
if (move.hasPromotion()) {
|
|
4058
4130
|
moveOptions.promotion = move.promotion;
|
|
4059
4131
|
}
|
|
4060
|
-
|
|
4061
|
-
console.log('executeMove - moveOptions:', moveOptions);
|
|
4062
|
-
|
|
4063
4132
|
const result = game.move(moveOptions);
|
|
4064
|
-
console.log('executeMove - result:', result);
|
|
4065
|
-
|
|
4066
|
-
// Check what's actually on the board after the move
|
|
4067
|
-
if (result) {
|
|
4068
|
-
const pieceOnDestination = game.get(move.to.id);
|
|
4069
|
-
console.log('executeMove - piece on destination after move:', pieceOnDestination);
|
|
4070
|
-
}
|
|
4071
|
-
|
|
4072
4133
|
return result;
|
|
4073
4134
|
}
|
|
4074
4135
|
|
|
4075
4136
|
/**
|
|
4076
|
-
*
|
|
4077
|
-
* @param {Move} move -
|
|
4078
|
-
* @returns {boolean}
|
|
4137
|
+
* Determina se una mossa richiede promozione
|
|
4138
|
+
* @param {Move} move - Deve essere oggetto Move
|
|
4139
|
+
* @returns {boolean}
|
|
4079
4140
|
*/
|
|
4080
4141
|
requiresPromotion(move) {
|
|
4142
|
+
if (!(move instanceof Move$1)) throw new Error('requiresPromotion richiede un oggetto Move');
|
|
4081
4143
|
console.log('Checking if move requires promotion:', move.from.id, '->', move.to.id);
|
|
4082
|
-
|
|
4144
|
+
|
|
4083
4145
|
if (!this.config.onlyLegalMoves) {
|
|
4084
4146
|
console.log('Not in legal moves mode, no promotion required');
|
|
4085
4147
|
return false;
|
|
4086
4148
|
}
|
|
4087
|
-
|
|
4149
|
+
|
|
4088
4150
|
const game = this.positionService.getGame();
|
|
4089
4151
|
if (!game) {
|
|
4090
4152
|
console.log('No game instance available');
|
|
4091
4153
|
return false;
|
|
4092
4154
|
}
|
|
4093
|
-
|
|
4155
|
+
|
|
4094
4156
|
const piece = game.get(move.from.id);
|
|
4095
4157
|
if (!piece || piece.type !== 'p') {
|
|
4096
4158
|
console.log('Not a pawn move, no promotion required');
|
|
4097
4159
|
return false;
|
|
4098
4160
|
}
|
|
4099
|
-
|
|
4161
|
+
|
|
4100
4162
|
const targetRank = move.to.row;
|
|
4101
4163
|
if (targetRank !== 1 && targetRank !== 8) {
|
|
4102
4164
|
console.log('Not reaching promotion rank, no promotion required');
|
|
4103
4165
|
return false;
|
|
4104
4166
|
}
|
|
4105
|
-
|
|
4167
|
+
|
|
4106
4168
|
console.log('Pawn reaching promotion rank, validating move...');
|
|
4107
|
-
|
|
4169
|
+
|
|
4108
4170
|
// Additional validation: check if the pawn can actually reach this square
|
|
4109
4171
|
if (!this._isPawnMoveValid(move.from, move.to, piece.color)) {
|
|
4110
4172
|
console.log('Pawn move not valid, no promotion required');
|
|
4111
4173
|
return false;
|
|
4112
4174
|
}
|
|
4113
|
-
|
|
4175
|
+
|
|
4114
4176
|
// First check if the move is legal without promotion
|
|
4115
4177
|
const simpleMoveObj = {
|
|
4116
4178
|
from: move.from.id,
|
|
4117
4179
|
to: move.to.id
|
|
4118
4180
|
};
|
|
4119
|
-
|
|
4181
|
+
|
|
4120
4182
|
try {
|
|
4121
4183
|
console.log('Testing move without promotion:', simpleMoveObj);
|
|
4122
4184
|
// Test if the move is legal without promotion first
|
|
@@ -4124,25 +4186,25 @@ class MoveService {
|
|
|
4124
4186
|
if (testMove) {
|
|
4125
4187
|
// Move was successful, but check if it was a promotion
|
|
4126
4188
|
const wasPromotion = testMove.promotion;
|
|
4127
|
-
|
|
4189
|
+
|
|
4128
4190
|
// Undo the test move
|
|
4129
4191
|
game.undo();
|
|
4130
|
-
|
|
4192
|
+
|
|
4131
4193
|
console.log('Move successful without promotion, was promotion:', wasPromotion !== undefined);
|
|
4132
|
-
|
|
4194
|
+
|
|
4133
4195
|
// If it was a promotion, return true
|
|
4134
4196
|
return wasPromotion !== undefined;
|
|
4135
4197
|
}
|
|
4136
4198
|
} catch (error) {
|
|
4137
4199
|
console.log('Move failed without promotion, trying with promotion:', error.message);
|
|
4138
|
-
|
|
4200
|
+
|
|
4139
4201
|
// If simple move fails, try with promotion
|
|
4140
4202
|
const promotionMoveObj = {
|
|
4141
4203
|
from: move.from.id,
|
|
4142
4204
|
to: move.to.id,
|
|
4143
4205
|
promotion: 'q' // test with queen
|
|
4144
4206
|
};
|
|
4145
|
-
|
|
4207
|
+
|
|
4146
4208
|
try {
|
|
4147
4209
|
console.log('Testing move with promotion:', promotionMoveObj);
|
|
4148
4210
|
const testMove = game.move(promotionMoveObj);
|
|
@@ -4158,7 +4220,7 @@ class MoveService {
|
|
|
4158
4220
|
return false;
|
|
4159
4221
|
}
|
|
4160
4222
|
}
|
|
4161
|
-
|
|
4223
|
+
|
|
4162
4224
|
console.log('Move validation complete, no promotion required');
|
|
4163
4225
|
return false;
|
|
4164
4226
|
}
|
|
@@ -4176,27 +4238,27 @@ class MoveService {
|
|
|
4176
4238
|
const toRank = to.row;
|
|
4177
4239
|
const fromFile = from.col;
|
|
4178
4240
|
const toFile = to.col;
|
|
4179
|
-
|
|
4241
|
+
|
|
4180
4242
|
console.log(`Validating pawn move: ${from.id} -> ${to.id} (${color})`);
|
|
4181
4243
|
console.log(`Ranks: ${fromRank} -> ${toRank}, Files: ${fromFile} -> ${toFile}`);
|
|
4182
|
-
|
|
4244
|
+
|
|
4183
4245
|
// Direction of pawn movement
|
|
4184
4246
|
const direction = color === 'w' ? 1 : -1;
|
|
4185
4247
|
const rankDiff = toRank - fromRank;
|
|
4186
4248
|
const fileDiff = Math.abs(toFile - fromFile);
|
|
4187
|
-
|
|
4249
|
+
|
|
4188
4250
|
// Pawn can only move forward
|
|
4189
4251
|
if (rankDiff * direction <= 0) {
|
|
4190
4252
|
console.log('Invalid: Pawn cannot move backward or stay in place');
|
|
4191
4253
|
return false;
|
|
4192
4254
|
}
|
|
4193
|
-
|
|
4255
|
+
|
|
4194
4256
|
// Pawn can only move 1 rank at a time (except for double move from starting position)
|
|
4195
4257
|
if (Math.abs(rankDiff) > 2) {
|
|
4196
4258
|
console.log('Invalid: Pawn cannot move more than 2 ranks');
|
|
4197
4259
|
return false;
|
|
4198
4260
|
}
|
|
4199
|
-
|
|
4261
|
+
|
|
4200
4262
|
// If moving 2 ranks, must be from starting position
|
|
4201
4263
|
if (Math.abs(rankDiff) === 2) {
|
|
4202
4264
|
const startingRank = color === 'w' ? 2 : 7;
|
|
@@ -4205,13 +4267,13 @@ class MoveService {
|
|
|
4205
4267
|
return false;
|
|
4206
4268
|
}
|
|
4207
4269
|
}
|
|
4208
|
-
|
|
4270
|
+
|
|
4209
4271
|
// Pawn can only move to adjacent files (diagonal capture) or same file (forward move)
|
|
4210
4272
|
if (fileDiff > 1) {
|
|
4211
4273
|
console.log('Invalid: Pawn cannot move more than 1 file');
|
|
4212
4274
|
return false;
|
|
4213
4275
|
}
|
|
4214
|
-
|
|
4276
|
+
|
|
4215
4277
|
console.log('Pawn move validation passed');
|
|
4216
4278
|
return true;
|
|
4217
4279
|
}
|
|
@@ -4226,45 +4288,45 @@ class MoveService {
|
|
|
4226
4288
|
*/
|
|
4227
4289
|
setupPromotion(move, squares, onPromotionSelect, onPromotionCancel) {
|
|
4228
4290
|
if (!this.requiresPromotion(move)) return false;
|
|
4229
|
-
|
|
4291
|
+
|
|
4230
4292
|
// Check if position service and game are available
|
|
4231
4293
|
if (!this.positionService || !this.positionService.getGame()) {
|
|
4232
4294
|
return false;
|
|
4233
4295
|
}
|
|
4234
|
-
|
|
4296
|
+
|
|
4235
4297
|
const game = this.positionService.getGame();
|
|
4236
4298
|
const piece = game.get(move.from.id);
|
|
4237
4299
|
const targetSquare = move.to;
|
|
4238
|
-
|
|
4300
|
+
|
|
4239
4301
|
// Clear any existing promotion UI
|
|
4240
4302
|
Object.values(squares).forEach(square => {
|
|
4241
4303
|
square.removePromotion();
|
|
4242
4304
|
square.removeCover();
|
|
4243
4305
|
});
|
|
4244
|
-
|
|
4306
|
+
|
|
4245
4307
|
// Always show promotion choices in a column
|
|
4246
4308
|
this._showPromotionInColumn(targetSquare, piece, squares, onPromotionSelect, onPromotionCancel);
|
|
4247
|
-
|
|
4309
|
+
|
|
4248
4310
|
return true;
|
|
4249
4311
|
}
|
|
4250
|
-
|
|
4312
|
+
|
|
4251
4313
|
/**
|
|
4252
4314
|
* Shows promotion choices in a column
|
|
4253
4315
|
* @private
|
|
4254
4316
|
*/
|
|
4255
4317
|
_showPromotionInColumn(targetSquare, piece, squares, onPromotionSelect, onPromotionCancel) {
|
|
4256
4318
|
console.log('Setting up promotion for', targetSquare.id, 'piece color:', piece.color);
|
|
4257
|
-
|
|
4319
|
+
|
|
4258
4320
|
// Set up promotion choices starting from border row
|
|
4259
4321
|
PROMOTION_PIECES.forEach((pieceType, index) => {
|
|
4260
4322
|
const choiceSquare = this._findPromotionSquare(targetSquare, index, squares);
|
|
4261
|
-
|
|
4323
|
+
|
|
4262
4324
|
if (choiceSquare) {
|
|
4263
4325
|
const pieceId = pieceType + piece.color;
|
|
4264
4326
|
const piecePath = this._getPiecePathForPromotion(pieceId);
|
|
4265
|
-
|
|
4327
|
+
|
|
4266
4328
|
console.log('Setting up promotion choice:', pieceType, 'on square:', choiceSquare.id);
|
|
4267
|
-
|
|
4329
|
+
|
|
4268
4330
|
choiceSquare.putPromotion(piecePath, () => {
|
|
4269
4331
|
console.log('Promotion choice selected:', pieceType);
|
|
4270
4332
|
onPromotionSelect(pieceType);
|
|
@@ -4273,7 +4335,7 @@ class MoveService {
|
|
|
4273
4335
|
console.log('Could not find square for promotion choice:', pieceType, 'index:', index);
|
|
4274
4336
|
}
|
|
4275
4337
|
});
|
|
4276
|
-
|
|
4338
|
+
|
|
4277
4339
|
// Set up cover squares (for cancellation)
|
|
4278
4340
|
Object.values(squares).forEach(square => {
|
|
4279
4341
|
if (!square.hasPromotion()) {
|
|
@@ -4282,7 +4344,7 @@ class MoveService {
|
|
|
4282
4344
|
});
|
|
4283
4345
|
}
|
|
4284
4346
|
});
|
|
4285
|
-
|
|
4347
|
+
|
|
4286
4348
|
return true;
|
|
4287
4349
|
}
|
|
4288
4350
|
|
|
@@ -4297,9 +4359,9 @@ class MoveService {
|
|
|
4297
4359
|
_findPromotionSquare(targetSquare, index, squares) {
|
|
4298
4360
|
const col = targetSquare.col;
|
|
4299
4361
|
const baseRow = targetSquare.row;
|
|
4300
|
-
|
|
4362
|
+
|
|
4301
4363
|
console.log('Looking for promotion square - target:', targetSquare.id, 'index:', index, 'col:', col, 'baseRow:', baseRow);
|
|
4302
|
-
|
|
4364
|
+
|
|
4303
4365
|
// Calculate row based on index and promotion direction
|
|
4304
4366
|
// Start from the border row (1 or 8) and go inward
|
|
4305
4367
|
let row;
|
|
@@ -4313,15 +4375,15 @@ class MoveService {
|
|
|
4313
4375
|
console.log('Invalid promotion row:', baseRow);
|
|
4314
4376
|
return null;
|
|
4315
4377
|
}
|
|
4316
|
-
|
|
4378
|
+
|
|
4317
4379
|
console.log('Calculated row:', row);
|
|
4318
|
-
|
|
4380
|
+
|
|
4319
4381
|
// Ensure row is within bounds
|
|
4320
4382
|
if (row < 1 || row > 8) {
|
|
4321
4383
|
console.log('Row out of bounds:', row);
|
|
4322
4384
|
return null;
|
|
4323
4385
|
}
|
|
4324
|
-
|
|
4386
|
+
|
|
4325
4387
|
// Find square by row/col
|
|
4326
4388
|
for (const square of Object.values(squares)) {
|
|
4327
4389
|
if (square.col === col && square.row === row) {
|
|
@@ -4329,7 +4391,7 @@ class MoveService {
|
|
|
4329
4391
|
return square;
|
|
4330
4392
|
}
|
|
4331
4393
|
}
|
|
4332
|
-
|
|
4394
|
+
|
|
4333
4395
|
console.log('No square found for col:', col, 'row:', row);
|
|
4334
4396
|
return null;
|
|
4335
4397
|
}
|
|
@@ -4344,11 +4406,11 @@ class MoveService {
|
|
|
4344
4406
|
// This would typically use the PieceService
|
|
4345
4407
|
// For now, we'll use a simple implementation
|
|
4346
4408
|
const { piecesPath } = this.config;
|
|
4347
|
-
|
|
4409
|
+
|
|
4348
4410
|
if (typeof piecesPath === 'string') {
|
|
4349
4411
|
return `${piecesPath}/${pieceId}.svg`;
|
|
4350
4412
|
}
|
|
4351
|
-
|
|
4413
|
+
|
|
4352
4414
|
// Fallback for other path types
|
|
4353
4415
|
return `assets/pieces/${pieceId}.svg`;
|
|
4354
4416
|
}
|
|
@@ -4362,20 +4424,20 @@ class MoveService {
|
|
|
4362
4424
|
if (typeof moveString !== 'string' || moveString.length < 4 || moveString.length > 5) {
|
|
4363
4425
|
return null;
|
|
4364
4426
|
}
|
|
4365
|
-
|
|
4427
|
+
|
|
4366
4428
|
const from = moveString.slice(0, 2);
|
|
4367
4429
|
const to = moveString.slice(2, 4);
|
|
4368
4430
|
const promotion = moveString.slice(4, 5);
|
|
4369
|
-
|
|
4431
|
+
|
|
4370
4432
|
// Basic validation
|
|
4371
4433
|
if (!/^[a-h][1-8]$/.test(from) || !/^[a-h][1-8]$/.test(to)) {
|
|
4372
4434
|
return null;
|
|
4373
4435
|
}
|
|
4374
|
-
|
|
4436
|
+
|
|
4375
4437
|
if (promotion && !['q', 'r', 'b', 'n'].includes(promotion.toLowerCase())) {
|
|
4376
4438
|
return null;
|
|
4377
4439
|
}
|
|
4378
|
-
|
|
4440
|
+
|
|
4379
4441
|
return {
|
|
4380
4442
|
from: from,
|
|
4381
4443
|
to: to,
|
|
@@ -4401,10 +4463,10 @@ class MoveService {
|
|
|
4401
4463
|
if (!this.isCastle(gameMove)) {
|
|
4402
4464
|
return null;
|
|
4403
4465
|
}
|
|
4404
|
-
|
|
4466
|
+
|
|
4405
4467
|
const isKingSide = gameMove.isKingsideCastle();
|
|
4406
4468
|
const isWhite = gameMove.color === 'w';
|
|
4407
|
-
|
|
4469
|
+
|
|
4408
4470
|
if (isKingSide) {
|
|
4409
4471
|
// King side castle
|
|
4410
4472
|
if (isWhite) {
|
|
@@ -4440,11 +4502,11 @@ class MoveService {
|
|
|
4440
4502
|
if (!this.isEnPassant(gameMove)) {
|
|
4441
4503
|
return null;
|
|
4442
4504
|
}
|
|
4443
|
-
|
|
4505
|
+
|
|
4444
4506
|
const toSquare = gameMove.to;
|
|
4445
4507
|
const rank = parseInt(toSquare[1]);
|
|
4446
4508
|
const file = toSquare[0];
|
|
4447
|
-
|
|
4509
|
+
|
|
4448
4510
|
// The captured pawn is on the same file but different rank
|
|
4449
4511
|
if (gameMove.color === 'w') {
|
|
4450
4512
|
// White captures black pawn one rank below
|
|
@@ -4552,19 +4614,26 @@ class PieceService {
|
|
|
4552
4614
|
|
|
4553
4615
|
/**
|
|
4554
4616
|
* Adds a piece to a square with optional fade-in animation
|
|
4555
|
-
* @param {Square} square - Target square
|
|
4556
|
-
* @param {Piece} piece - Piece to add
|
|
4617
|
+
* @param {Square} square - Target square (oggetto)
|
|
4618
|
+
* @param {Piece} piece - Piece to add (oggetto)
|
|
4557
4619
|
* @param {boolean} [fade=true] - Whether to fade in the piece
|
|
4558
4620
|
* @param {Function} dragFunction - Function to handle drag events
|
|
4559
4621
|
* @param {Function} [callback] - Callback when animation completes
|
|
4560
4622
|
*/
|
|
4561
4623
|
addPieceOnSquare(square, piece, fade = true, dragFunction, callback) {
|
|
4624
|
+
if (!square || !piece) throw new Error('addPieceOnSquare richiede oggetti Square e Piece');
|
|
4562
4625
|
console.debug(`[PieceService] addPieceOnSquare: ${piece.id} to ${square.id}`);
|
|
4563
4626
|
square.putPiece(piece);
|
|
4564
4627
|
|
|
4628
|
+
// Imposta sempre il drag (touch e mouse)
|
|
4565
4629
|
if (dragFunction) {
|
|
4566
4630
|
piece.setDrag(dragFunction(square, piece));
|
|
4567
4631
|
}
|
|
4632
|
+
// Forza il drag touch se manca (debug/robustezza)
|
|
4633
|
+
if (!piece.element.ontouchstart) {
|
|
4634
|
+
piece.element.ontouchstart = dragFunction ? dragFunction(square, piece) : () => { };
|
|
4635
|
+
console.debug(`[PieceService] Forzato ontouchstart su ${piece.id}`);
|
|
4636
|
+
}
|
|
4568
4637
|
|
|
4569
4638
|
if (fade && this.config.fadeTime > 0) {
|
|
4570
4639
|
piece.fadeIn(
|
|
@@ -4581,14 +4650,14 @@ class PieceService {
|
|
|
4581
4650
|
}
|
|
4582
4651
|
|
|
4583
4652
|
/**
|
|
4584
|
-
*
|
|
4585
|
-
* @param {Square} square -
|
|
4586
|
-
* @param {boolean} [fade=true]
|
|
4587
|
-
* @param {Function} [callback]
|
|
4588
|
-
* @returns {Piece}
|
|
4589
|
-
* @throws {PieceError} When square has no piece to remove
|
|
4653
|
+
* Rimuove un pezzo da una casella
|
|
4654
|
+
* @param {Square} square - Oggetto Square
|
|
4655
|
+
* @param {boolean} [fade=true]
|
|
4656
|
+
* @param {Function} [callback]
|
|
4657
|
+
* @returns {Piece} Il pezzo rimosso
|
|
4590
4658
|
*/
|
|
4591
4659
|
removePieceFromSquare(square, fade = true, callback) {
|
|
4660
|
+
if (!square) throw new Error('removePieceFromSquare richiede oggetto Square');
|
|
4592
4661
|
console.debug(`[PieceService] removePieceFromSquare: ${square.id}`);
|
|
4593
4662
|
square.check();
|
|
4594
4663
|
|
|
@@ -4622,8 +4691,8 @@ class PieceService {
|
|
|
4622
4691
|
*/
|
|
4623
4692
|
movePiece(piece, targetSquare, duration, callback) {
|
|
4624
4693
|
console.debug(`[PieceService] movePiece: ${piece.id} to ${targetSquare.id}`);
|
|
4625
|
-
if (!piece) {
|
|
4626
|
-
console.warn(
|
|
4694
|
+
if (!piece || !piece.element) {
|
|
4695
|
+
console.warn(`[PieceService] movePiece: piece or element is null, skipping animation`);
|
|
4627
4696
|
if (callback) callback();
|
|
4628
4697
|
return;
|
|
4629
4698
|
}
|
|
@@ -4679,7 +4748,7 @@ class PieceService {
|
|
|
4679
4748
|
};
|
|
4680
4749
|
|
|
4681
4750
|
// Check if piece is currently being dragged
|
|
4682
|
-
const isDragging = move.piece.element.classList.contains('dragging');
|
|
4751
|
+
const isDragging = move.piece.element && move.piece.element.classList.contains('dragging');
|
|
4683
4752
|
|
|
4684
4753
|
if (isDragging) {
|
|
4685
4754
|
// If piece is being dragged, don't animate - just move it immediately
|
|
@@ -7404,7 +7473,7 @@ let Chessboard$1 = class Chessboard {
|
|
|
7404
7473
|
const capturedPiece = move.to.piece;
|
|
7405
7474
|
|
|
7406
7475
|
// For castle moves in simultaneous mode, we need to coordinate both animations
|
|
7407
|
-
if (isCastleMove
|
|
7476
|
+
if (isCastleMove) {
|
|
7408
7477
|
// Start king animation
|
|
7409
7478
|
this.pieceService.translatePiece(
|
|
7410
7479
|
move,
|
|
@@ -7629,187 +7698,62 @@ let Chessboard$1 = class Chessboard {
|
|
|
7629
7698
|
}
|
|
7630
7699
|
|
|
7631
7700
|
/**
|
|
7632
|
-
*
|
|
7701
|
+
* Aggiorna i pezzi sulla scacchiera con animazione e delay configurabile (greedy matching)
|
|
7633
7702
|
* @private
|
|
7634
|
-
* @param {boolean} [animation=false] -
|
|
7635
|
-
* @param {boolean} [isPositionLoad=false] -
|
|
7703
|
+
* @param {boolean} [animation=false] - Se animare
|
|
7704
|
+
* @param {boolean} [isPositionLoad=false] - Se è un caricamento posizione (delay 0)
|
|
7636
7705
|
*/
|
|
7637
7706
|
_doUpdateBoardPieces(animation = false, isPositionLoad = false) {
|
|
7638
|
-
|
|
7639
|
-
if (this._isPromoting)
|
|
7640
|
-
|
|
7641
|
-
return;
|
|
7642
|
-
}
|
|
7643
|
-
|
|
7644
|
-
// Check if services are available
|
|
7645
|
-
if (!this.positionService || !this.positionService.getGame()) {
|
|
7646
|
-
console.log('Cannot update board pieces - position service not available');
|
|
7647
|
-
return;
|
|
7648
|
-
}
|
|
7649
|
-
|
|
7707
|
+
if (this._isDragging) return;
|
|
7708
|
+
if (this._isPromoting) return;
|
|
7709
|
+
if (!this.positionService || !this.positionService.getGame()) return;
|
|
7650
7710
|
const squares = this.boardService.getAllSquares();
|
|
7651
7711
|
const gameStateBefore = this.positionService.getGame().fen();
|
|
7652
|
-
|
|
7653
|
-
// PATCH ROBUSTA: se la board è completamente vuota, forza la rimozione di TUTTI i pezzi
|
|
7654
7712
|
if (/^8\/8\/8\/8\/8\/8\/8\/8/.test(gameStateBefore)) {
|
|
7655
|
-
console.log('Board vuota rilevata - rimozione forzata di tutti i pezzi dal DOM');
|
|
7656
|
-
|
|
7657
|
-
// 1. Rimuovi tutti gli elementi DOM dei pezzi dal contenitore della board
|
|
7658
7713
|
const boardContainer = document.getElementById(this.config.id_div);
|
|
7659
7714
|
if (boardContainer) {
|
|
7660
7715
|
const pieceElements = boardContainer.querySelectorAll('.piece');
|
|
7661
|
-
pieceElements.forEach(element =>
|
|
7662
|
-
console.log('Rimozione forzata elemento DOM pezzo:', element);
|
|
7663
|
-
element.remove();
|
|
7664
|
-
});
|
|
7716
|
+
pieceElements.forEach(element => element.remove());
|
|
7665
7717
|
}
|
|
7666
|
-
|
|
7667
|
-
// 2. Azzera tutti i riferimenti JS ai pezzi
|
|
7668
|
-
Object.values(squares).forEach(sq => {
|
|
7669
|
-
if (sq && sq.piece) {
|
|
7670
|
-
console.log('Azzero riferimento pezzo su casella:', sq.id);
|
|
7671
|
-
sq.piece = null;
|
|
7672
|
-
}
|
|
7673
|
-
});
|
|
7674
|
-
|
|
7675
|
-
// 3. Forza la pulizia di eventuali selezioni/hint
|
|
7718
|
+
Object.values(squares).forEach(sq => { if (sq && sq.piece) sq.piece = null; });
|
|
7676
7719
|
this._clearVisualState();
|
|
7677
|
-
|
|
7678
|
-
// 4. Aggiungi i listener e notifica il cambio
|
|
7679
7720
|
this._addListeners();
|
|
7680
7721
|
if (this.config.onChange) this.config.onChange(gameStateBefore);
|
|
7681
|
-
|
|
7682
|
-
console.log('Rimozione forzata completata');
|
|
7683
7722
|
return;
|
|
7684
7723
|
}
|
|
7685
7724
|
|
|
7686
|
-
|
|
7687
|
-
console.log('_doUpdateBoardPieces - animation:', animation, 'style:', this.config.animationStyle, 'isPositionLoad:', isPositionLoad);
|
|
7688
|
-
|
|
7689
|
-
// Determine which animation style to use
|
|
7690
|
-
const useSimultaneous = this.config.animationStyle === 'simultaneous';
|
|
7691
|
-
console.log('_doUpdateBoardPieces - useSimultaneous:', useSimultaneous);
|
|
7692
|
-
|
|
7693
|
-
if (useSimultaneous) {
|
|
7694
|
-
console.log('Using simultaneous animation');
|
|
7695
|
-
this._doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad);
|
|
7696
|
-
} else {
|
|
7697
|
-
console.log('Using sequential animation');
|
|
7698
|
-
this._doSequentialUpdate(squares, gameStateBefore, animation);
|
|
7699
|
-
}
|
|
7700
|
-
}
|
|
7701
|
-
|
|
7702
|
-
/**
|
|
7703
|
-
* Performs sequential piece updates (original behavior)
|
|
7704
|
-
* @private
|
|
7705
|
-
* @param {Object} squares - All squares
|
|
7706
|
-
* @param {string} gameStateBefore - Game state before update
|
|
7707
|
-
* @param {boolean} animation - Whether to animate
|
|
7708
|
-
*/
|
|
7709
|
-
_doSequentialUpdate(squares, gameStateBefore, animation) {
|
|
7710
|
-
// Mappa: squareId -> expectedPieceId
|
|
7711
|
-
const expectedMap = {};
|
|
7712
|
-
Object.values(squares).forEach(square => {
|
|
7713
|
-
expectedMap[square.id] = this.positionService.getGamePieceId(square.id);
|
|
7714
|
-
});
|
|
7715
|
-
|
|
7716
|
-
Object.values(squares).forEach(square => {
|
|
7717
|
-
const expectedPieceId = expectedMap[square.id];
|
|
7718
|
-
const currentPiece = square.piece;
|
|
7719
|
-
const currentPieceId = currentPiece ? currentPiece.getId() : null;
|
|
7720
|
-
|
|
7721
|
-
// Se il pezzo attuale e quello atteso sono identici, non fare nulla
|
|
7722
|
-
if (currentPieceId === expectedPieceId) {
|
|
7723
|
-
return;
|
|
7724
|
-
}
|
|
7725
|
-
|
|
7726
|
-
// Se c'è un pezzo attuale ma non è quello atteso, rimuovilo
|
|
7727
|
-
if (currentPiece && currentPieceId !== expectedPieceId) {
|
|
7728
|
-
this.pieceService.removePieceFromSquare(square, animation);
|
|
7729
|
-
}
|
|
7730
|
-
|
|
7731
|
-
// Se c'è un pezzo atteso ma non è quello attuale, aggiungilo
|
|
7732
|
-
if (expectedPieceId && currentPieceId !== expectedPieceId) {
|
|
7733
|
-
const newPiece = this.pieceService.convertPiece(expectedPieceId);
|
|
7734
|
-
this.pieceService.addPieceOnSquare(
|
|
7735
|
-
square,
|
|
7736
|
-
newPiece,
|
|
7737
|
-
animation,
|
|
7738
|
-
this._createDragFunction.bind(this)
|
|
7739
|
-
);
|
|
7740
|
-
}
|
|
7741
|
-
});
|
|
7742
|
-
|
|
7743
|
-
this._addListeners();
|
|
7744
|
-
const gameStateAfter = this.positionService.getGame().fen();
|
|
7745
|
-
if (gameStateBefore !== gameStateAfter) {
|
|
7746
|
-
this.config.onChange(gameStateAfter);
|
|
7747
|
-
}
|
|
7748
|
-
}
|
|
7749
|
-
|
|
7750
|
-
/**
|
|
7751
|
-
* Performs simultaneous piece updates
|
|
7752
|
-
* @private
|
|
7753
|
-
* @param {Object} squares - All squares
|
|
7754
|
-
* @param {string} gameStateBefore - Game state before update
|
|
7755
|
-
* @param {boolean} [isPositionLoad=false] - Whether this is a position load
|
|
7756
|
-
*/
|
|
7757
|
-
_doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad = false) {
|
|
7758
|
-
// Matching greedy per distanza minima, robusto
|
|
7725
|
+
// --- Matching greedy tra attuale e atteso ---
|
|
7759
7726
|
const currentMap = {};
|
|
7760
7727
|
const expectedMap = {};
|
|
7761
|
-
|
|
7762
7728
|
Object.values(squares).forEach(square => {
|
|
7763
7729
|
const currentPiece = square.piece;
|
|
7764
7730
|
const expectedPieceId = this.positionService.getGamePieceId(square.id);
|
|
7765
7731
|
if (currentPiece) {
|
|
7766
|
-
// Normalizza la chiave come 'color+type' lowercase
|
|
7767
7732
|
const key = (currentPiece.color + currentPiece.type).toLowerCase();
|
|
7768
7733
|
if (!currentMap[key]) currentMap[key] = [];
|
|
7769
|
-
currentMap[key].push({ square, id: square.id });
|
|
7734
|
+
currentMap[key].push({ square, id: square.id, piece: currentPiece });
|
|
7770
7735
|
}
|
|
7771
7736
|
if (expectedPieceId) {
|
|
7772
|
-
// Normalizza la chiave come 'color+type' lowercase
|
|
7773
7737
|
const key = expectedPieceId.toLowerCase();
|
|
7774
7738
|
if (!expectedMap[key]) expectedMap[key] = [];
|
|
7775
7739
|
expectedMap[key].push({ square, id: square.id });
|
|
7776
7740
|
}
|
|
7777
7741
|
});
|
|
7778
|
-
|
|
7779
|
-
let animationsCompleted = 0;
|
|
7742
|
+
const animationDelay = isPositionLoad ? 0 : this.config.simultaneousAnimationDelay || 0;
|
|
7780
7743
|
let totalAnimations = 0;
|
|
7781
|
-
|
|
7782
|
-
let animationIndex = 0;
|
|
7783
|
-
|
|
7784
|
-
Object.keys(expectedMap).forEach(key => {
|
|
7785
|
-
totalAnimations += Math.max((currentMap[key] || []).length, expectedMap[key].length);
|
|
7786
|
-
});
|
|
7787
|
-
|
|
7788
|
-
if (totalAnimations === 0) {
|
|
7789
|
-
this._addListeners();
|
|
7790
|
-
const gameStateAfter = this.positionService.getGame().fen();
|
|
7791
|
-
if (gameStateBefore !== gameStateAfter) {
|
|
7792
|
-
this.config.onChange(gameStateAfter);
|
|
7793
|
-
}
|
|
7794
|
-
return;
|
|
7795
|
-
}
|
|
7796
|
-
|
|
7797
|
-
const onAnimationComplete = () => {
|
|
7798
|
-
animationsCompleted++;
|
|
7799
|
-
if (animationsCompleted === totalAnimations) {
|
|
7800
|
-
this._addListeners();
|
|
7801
|
-
const gameStateAfter = this.positionService.getGame().fen();
|
|
7802
|
-
if (gameStateBefore !== gameStateAfter) {
|
|
7803
|
-
this.config.onChange(gameStateAfter);
|
|
7804
|
-
}
|
|
7805
|
-
}
|
|
7806
|
-
};
|
|
7744
|
+
let animationsCompleted = 0;
|
|
7807
7745
|
|
|
7746
|
+
// 1. Matching greedy: trova i movimenti
|
|
7747
|
+
const moves = [];
|
|
7748
|
+
const fromMatched = {};
|
|
7749
|
+
const toMatched = {};
|
|
7750
|
+
const unchanged = [];
|
|
7808
7751
|
Object.keys(expectedMap).forEach(key => {
|
|
7809
7752
|
const fromList = (currentMap[key] || []).slice();
|
|
7810
7753
|
const toList = expectedMap[key].slice();
|
|
7811
|
-
|
|
7812
|
-
|
|
7754
|
+
const localFromMatched = new Array(fromList.length).fill(false);
|
|
7755
|
+
const localToMatched = new Array(toList.length).fill(false);
|
|
7756
|
+
// Matrice delle distanze
|
|
7813
7757
|
const distances = [];
|
|
7814
7758
|
for (let i = 0; i < fromList.length; i++) {
|
|
7815
7759
|
distances[i] = [];
|
|
@@ -7818,18 +7762,12 @@ let Chessboard$1 = class Chessboard {
|
|
|
7818
7762
|
Math.abs(fromList[i].square.col - toList[j].square.col);
|
|
7819
7763
|
}
|
|
7820
7764
|
}
|
|
7821
|
-
|
|
7822
|
-
// 2. Matching greedy: abbina i più vicini
|
|
7823
|
-
const fromMatched = new Array(fromList.length).fill(false);
|
|
7824
|
-
const toMatched = new Array(toList.length).fill(false);
|
|
7825
|
-
const moves = [];
|
|
7826
|
-
|
|
7827
7765
|
while (true) {
|
|
7828
7766
|
let minDist = Infinity, minI = -1, minJ = -1;
|
|
7829
7767
|
for (let i = 0; i < fromList.length; i++) {
|
|
7830
|
-
if (
|
|
7768
|
+
if (localFromMatched[i]) continue;
|
|
7831
7769
|
for (let j = 0; j < toList.length; j++) {
|
|
7832
|
-
if (
|
|
7770
|
+
if (localToMatched[j]) continue;
|
|
7833
7771
|
if (distances[i][j] < minDist) {
|
|
7834
7772
|
minDist = distances[i][j];
|
|
7835
7773
|
minI = i;
|
|
@@ -7838,58 +7776,111 @@ let Chessboard$1 = class Chessboard {
|
|
|
7838
7776
|
}
|
|
7839
7777
|
}
|
|
7840
7778
|
if (minI === -1 || minJ === -1) break;
|
|
7841
|
-
// Se la posizione è la stessa, non fare nulla (pezzo unchanged)
|
|
7842
|
-
if (fromList[minI].square === toList[minJ].square) {
|
|
7843
|
-
|
|
7844
|
-
|
|
7779
|
+
// Se la posizione è la stessa E il Piece è lo stesso oggetto, non fare nulla (pezzo unchanged)
|
|
7780
|
+
if (fromList[minI].square === toList[minJ].square && squares[toList[minJ].square.id].piece === fromList[minI].piece) {
|
|
7781
|
+
unchanged.push({ square: fromList[minI].square, piece: fromList[minI].piece });
|
|
7782
|
+
localFromMatched[minI] = true;
|
|
7783
|
+
localToMatched[minJ] = true;
|
|
7784
|
+
fromMatched[fromList[minI].square.id] = true;
|
|
7785
|
+
toMatched[toList[minJ].square.id] = true;
|
|
7845
7786
|
continue;
|
|
7846
7787
|
}
|
|
7847
7788
|
// Altrimenti, sposta il pezzo
|
|
7848
|
-
moves.push({ from: fromList[minI].square, to: toList[minJ].square, piece: fromList[minI].
|
|
7849
|
-
|
|
7850
|
-
|
|
7789
|
+
moves.push({ from: fromList[minI].square, to: toList[minJ].square, piece: fromList[minI].piece });
|
|
7790
|
+
localFromMatched[minI] = true;
|
|
7791
|
+
localToMatched[minJ] = true;
|
|
7792
|
+
fromMatched[fromList[minI].square.id] = true;
|
|
7793
|
+
toMatched[toList[minJ].square.id] = true;
|
|
7851
7794
|
}
|
|
7795
|
+
});
|
|
7852
7796
|
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
animationIndex++;
|
|
7797
|
+
// 2. Rimozione: pezzi presenti solo in attuale (non matched)
|
|
7798
|
+
const removes = [];
|
|
7799
|
+
Object.keys(currentMap).forEach(key => {
|
|
7800
|
+
currentMap[key].forEach(({ square, piece }) => {
|
|
7801
|
+
if (!fromMatched[square.id]) {
|
|
7802
|
+
removes.push({ square, piece });
|
|
7860
7803
|
}
|
|
7804
|
+
});
|
|
7805
|
+
});
|
|
7806
|
+
|
|
7807
|
+
// 3. Aggiunta: pezzi presenti solo in atteso (non matched)
|
|
7808
|
+
const adds = [];
|
|
7809
|
+
Object.keys(expectedMap).forEach(key => {
|
|
7810
|
+
expectedMap[key].forEach(({ square, id }) => {
|
|
7811
|
+
if (!toMatched[square.id]) {
|
|
7812
|
+
adds.push({ square, pieceId: key });
|
|
7813
|
+
}
|
|
7814
|
+
});
|
|
7815
|
+
});
|
|
7816
|
+
|
|
7817
|
+
totalAnimations = moves.length + removes.length + adds.length;
|
|
7818
|
+
if (totalAnimations === 0) {
|
|
7819
|
+
this._addListeners();
|
|
7820
|
+
const gameStateAfter = this.positionService.getGame().fen();
|
|
7821
|
+
if (gameStateBefore !== gameStateAfter) {
|
|
7822
|
+
this.config.onChange(gameStateAfter);
|
|
7861
7823
|
}
|
|
7824
|
+
return;
|
|
7825
|
+
}
|
|
7862
7826
|
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7827
|
+
// Debug: logga i pezzi unchanged
|
|
7828
|
+
if (unchanged.length > 0) {
|
|
7829
|
+
console.debug('[Chessboard] Unchanged pieces:', unchanged.map(u => u.piece.id + '@' + u.square.id));
|
|
7830
|
+
}
|
|
7831
|
+
|
|
7832
|
+
const onAnimationComplete = () => {
|
|
7833
|
+
animationsCompleted++;
|
|
7834
|
+
if (animationsCompleted === totalAnimations) {
|
|
7835
|
+
// Pulizia finale robusta: rimuovi tutti i pezzi orfani dal DOM e dal riferimento JS
|
|
7836
|
+
Object.values(this.boardService.getAllSquares()).forEach(square => {
|
|
7837
|
+
const expectedPieceId = this.positionService.getGamePieceId(square.id);
|
|
7838
|
+
if (!expectedPieceId && typeof square.forceRemoveAllPieces === 'function') {
|
|
7839
|
+
square.forceRemoveAllPieces();
|
|
7840
|
+
}
|
|
7841
|
+
});
|
|
7842
|
+
this._addListeners();
|
|
7843
|
+
const gameStateAfter = this.positionService.getGame().fen();
|
|
7844
|
+
if (gameStateBefore !== gameStateAfter) {
|
|
7845
|
+
this.config.onChange(gameStateAfter);
|
|
7877
7846
|
}
|
|
7878
7847
|
}
|
|
7848
|
+
};
|
|
7879
7849
|
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7850
|
+
// 4. Esegui tutte le animazioni con delay
|
|
7851
|
+
let idx = 0;
|
|
7852
|
+
moves.forEach(move => {
|
|
7853
|
+
setTimeout(() => {
|
|
7854
|
+
this.pieceService.translatePiece(
|
|
7855
|
+
move,
|
|
7856
|
+
false,
|
|
7857
|
+
animation,
|
|
7858
|
+
this._createDragFunction.bind(this),
|
|
7859
|
+
onAnimationComplete
|
|
7860
|
+
);
|
|
7861
|
+
}, idx++ * animationDelay);
|
|
7862
|
+
});
|
|
7863
|
+
removes.forEach(op => {
|
|
7864
|
+
setTimeout(() => {
|
|
7865
|
+
if (typeof op.square.forceRemoveAllPieces === 'function') {
|
|
7866
|
+
op.square.forceRemoveAllPieces();
|
|
7867
|
+
onAnimationComplete();
|
|
7868
|
+
} else {
|
|
7869
|
+
this.pieceService.removePieceFromSquare(op.square, animation, onAnimationComplete);
|
|
7870
|
+
}
|
|
7871
|
+
}, idx++ * animationDelay);
|
|
7872
|
+
});
|
|
7873
|
+
adds.forEach(op => {
|
|
7874
|
+
setTimeout(() => {
|
|
7875
|
+
const newPiece = this.pieceService.convertPiece(op.pieceId);
|
|
7876
|
+
this.pieceService.addPieceOnSquare(
|
|
7877
|
+
op.square,
|
|
7878
|
+
newPiece,
|
|
7879
|
+
animation,
|
|
7880
|
+
this._createDragFunction.bind(this),
|
|
7881
|
+
onAnimationComplete
|
|
7882
|
+
);
|
|
7883
|
+
}, idx++ * animationDelay);
|
|
7893
7884
|
});
|
|
7894
7885
|
}
|
|
7895
7886
|
|
|
@@ -8246,6 +8237,8 @@ let Chessboard$1 = class Chessboard {
|
|
|
8246
8237
|
if (this._updateBoardPieces) {
|
|
8247
8238
|
this._updateBoardPieces(animate, true);
|
|
8248
8239
|
}
|
|
8240
|
+
// Forza la sincronizzazione dopo setPosition
|
|
8241
|
+
this._updateBoardPieces(true, false);
|
|
8249
8242
|
return true;
|
|
8250
8243
|
}
|
|
8251
8244
|
/**
|
|
@@ -8259,7 +8252,10 @@ let Chessboard$1 = class Chessboard {
|
|
|
8259
8252
|
// Use the default starting position from config or fallback
|
|
8260
8253
|
const startPosition = this.config && this.config.position ? this.config.position : 'start';
|
|
8261
8254
|
this._updateBoardPieces(animate);
|
|
8262
|
-
|
|
8255
|
+
const result = this.setPosition(startPosition, { animate });
|
|
8256
|
+
// Forza la sincronizzazione dopo reset
|
|
8257
|
+
this._updateBoardPieces(true, false);
|
|
8258
|
+
return result;
|
|
8263
8259
|
}
|
|
8264
8260
|
/**
|
|
8265
8261
|
* Clear the board
|
|
@@ -8283,6 +8279,8 @@ let Chessboard$1 = class Chessboard {
|
|
|
8283
8279
|
if (this._updateBoardPieces) {
|
|
8284
8280
|
this._updateBoardPieces(animate, true);
|
|
8285
8281
|
}
|
|
8282
|
+
// Forza la sincronizzazione dopo clear
|
|
8283
|
+
this._updateBoardPieces(true, false);
|
|
8286
8284
|
return true;
|
|
8287
8285
|
}
|
|
8288
8286
|
|
|
@@ -8297,7 +8295,8 @@ let Chessboard$1 = class Chessboard {
|
|
|
8297
8295
|
const undone = this.positionService.getGame().undo();
|
|
8298
8296
|
if (undone) {
|
|
8299
8297
|
this._undoneMoves.push(undone);
|
|
8300
|
-
|
|
8298
|
+
// Forza refresh completo di tutti i pezzi dopo undo
|
|
8299
|
+
this._updateBoardPieces(true, true);
|
|
8301
8300
|
return undone;
|
|
8302
8301
|
}
|
|
8303
8302
|
return null;
|
|
@@ -8314,7 +8313,8 @@ let Chessboard$1 = class Chessboard {
|
|
|
8314
8313
|
const moveObj = { from: move.from, to: move.to };
|
|
8315
8314
|
if (move.promotion) moveObj.promotion = move.promotion;
|
|
8316
8315
|
const result = this.positionService.getGame().move(moveObj);
|
|
8317
|
-
|
|
8316
|
+
// Forza refresh completo di tutti i pezzi dopo redo
|
|
8317
|
+
this._updateBoardPieces(true, true);
|
|
8318
8318
|
return result;
|
|
8319
8319
|
}
|
|
8320
8320
|
return false;
|
|
@@ -8333,17 +8333,19 @@ let Chessboard$1 = class Chessboard {
|
|
|
8333
8333
|
* @returns {string|null}
|
|
8334
8334
|
*/
|
|
8335
8335
|
getPiece(square) {
|
|
8336
|
-
//
|
|
8337
|
-
const
|
|
8338
|
-
if (!
|
|
8339
|
-
|
|
8336
|
+
// Sempre leggi lo stato aggiornato dal boardService
|
|
8337
|
+
const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
|
|
8338
|
+
if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[getPiece] Parametro square non valido');
|
|
8339
|
+
// Forza sync prima di leggere
|
|
8340
|
+
this._updateBoardPieces(false, false);
|
|
8341
|
+
const piece = squareObj.piece;
|
|
8340
8342
|
if (!piece) return null;
|
|
8341
8343
|
return (piece.color + piece.type).toLowerCase();
|
|
8342
8344
|
}
|
|
8343
8345
|
/**
|
|
8344
8346
|
* Put a piece on a square
|
|
8345
|
-
* @param {string} piece
|
|
8346
|
-
* @param {string} square
|
|
8347
|
+
* @param {string|Piece} piece
|
|
8348
|
+
* @param {string|Square} square
|
|
8347
8349
|
* @param {Object} [opts]
|
|
8348
8350
|
* @param {boolean} [opts.animate=true]
|
|
8349
8351
|
* @returns {boolean}
|
|
@@ -8354,7 +8356,6 @@ let Chessboard$1 = class Chessboard {
|
|
|
8354
8356
|
if (typeof piece === 'object' && piece.type && piece.color) {
|
|
8355
8357
|
pieceStr = (piece.color + piece.type).toLowerCase();
|
|
8356
8358
|
} else if (typeof piece === 'string' && piece.length === 2) {
|
|
8357
|
-
// Accetta sia 'wq' che 'qw', normalizza a 'wq'
|
|
8358
8359
|
const a = piece[0].toLowerCase();
|
|
8359
8360
|
const b = piece[1].toLowerCase();
|
|
8360
8361
|
const types = 'kqrbnp';
|
|
@@ -8367,42 +8368,36 @@ let Chessboard$1 = class Chessboard {
|
|
|
8367
8368
|
throw new Error(`[putPiece] Invalid piece: ${piece}`);
|
|
8368
8369
|
}
|
|
8369
8370
|
}
|
|
8370
|
-
const
|
|
8371
|
-
|
|
8372
|
-
if (!validSquare) throw new Error(`[putPiece] Invalid square: ${square}`);
|
|
8373
|
-
if (!validPiece) throw new Error(`[putPiece] Invalid piece: ${pieceStr}`);
|
|
8374
|
-
if (!this.positionService || !this.positionService.getGame()) {
|
|
8375
|
-
throw new Error('[putPiece] No positionService or game');
|
|
8376
|
-
}
|
|
8371
|
+
const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
|
|
8372
|
+
if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[putPiece] Parametro square non valido');
|
|
8377
8373
|
const pieceObj = this.pieceService.convertPiece(pieceStr);
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
squareObj.piece = pieceObj;
|
|
8374
|
+
if (!pieceObj || typeof pieceObj !== 'object' || !('type' in pieceObj)) throw new Error('[putPiece] Parametro piece non valido');
|
|
8375
|
+
// Aggiorna solo il motore chess.js
|
|
8381
8376
|
const chessJsPiece = { type: pieceObj.type, color: pieceObj.color };
|
|
8382
8377
|
const game = this.positionService.getGame();
|
|
8383
|
-
const result = game.put(chessJsPiece,
|
|
8384
|
-
if (!result) throw new Error(`[putPiece] Game.put failed for ${pieceStr} on ${
|
|
8378
|
+
const result = game.put(chessJsPiece, squareObj.id);
|
|
8379
|
+
if (!result) throw new Error(`[putPiece] Game.put failed for ${pieceStr} on ${squareObj.id}`);
|
|
8380
|
+
// Non aggiornare direttamente square.piece!
|
|
8381
|
+
// Riallinea la board JS allo stato del motore
|
|
8385
8382
|
this._updateBoardPieces(animate);
|
|
8386
8383
|
return true;
|
|
8387
8384
|
}
|
|
8388
8385
|
/**
|
|
8389
8386
|
* Remove a piece from a square
|
|
8390
|
-
* @param {string} square
|
|
8387
|
+
* @param {string|Square} square
|
|
8391
8388
|
* @param {Object} [opts]
|
|
8392
8389
|
* @param {boolean} [opts.animate=true]
|
|
8393
8390
|
* @returns {string|null}
|
|
8394
8391
|
*/
|
|
8395
8392
|
removePiece(square, opts = {}) {
|
|
8396
8393
|
const animate = opts.animate !== undefined ? opts.animate : true;
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
const squareObj = this.boardService.getSquare(square);
|
|
8401
|
-
if (!squareObj) return true;
|
|
8402
|
-
if (!squareObj.piece) return true;
|
|
8403
|
-
squareObj.piece = null;
|
|
8394
|
+
const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
|
|
8395
|
+
if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[removePiece] Parametro square non valido');
|
|
8396
|
+
// Aggiorna solo il motore chess.js
|
|
8404
8397
|
const game = this.positionService.getGame();
|
|
8405
|
-
game.remove(
|
|
8398
|
+
game.remove(squareObj.id);
|
|
8399
|
+
// Non aggiornare direttamente square.piece!
|
|
8400
|
+
// Riallinea la board JS allo stato del motore
|
|
8406
8401
|
this._updateBoardPieces(animate);
|
|
8407
8402
|
return true;
|
|
8408
8403
|
}
|
|
@@ -8467,28 +8462,32 @@ let Chessboard$1 = class Chessboard {
|
|
|
8467
8462
|
// --- HIGHLIGHTING & UI ---
|
|
8468
8463
|
/**
|
|
8469
8464
|
* Highlight a square
|
|
8470
|
-
* @param {string} square
|
|
8465
|
+
* @param {string|Square} square
|
|
8471
8466
|
* @param {Object} [opts]
|
|
8472
8467
|
*/
|
|
8473
8468
|
highlight(square, opts = {}) {
|
|
8474
|
-
|
|
8469
|
+
// API: accetta id, converte subito in oggetto
|
|
8470
|
+
const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
|
|
8471
|
+
if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[highlight] Parametro square non valido');
|
|
8475
8472
|
if (this.boardService && this.boardService.highlightSquare) {
|
|
8476
|
-
this.boardService.highlightSquare(
|
|
8473
|
+
this.boardService.highlightSquare(squareObj, opts);
|
|
8477
8474
|
} else if (this.eventService && this.eventService.highlightSquare) {
|
|
8478
|
-
this.eventService.highlightSquare(
|
|
8475
|
+
this.eventService.highlightSquare(squareObj, opts);
|
|
8479
8476
|
}
|
|
8480
8477
|
}
|
|
8481
8478
|
/**
|
|
8482
8479
|
* Remove highlight from a square
|
|
8483
|
-
* @param {string} square
|
|
8480
|
+
* @param {string|Square} square
|
|
8484
8481
|
* @param {Object} [opts]
|
|
8485
8482
|
*/
|
|
8486
8483
|
dehighlight(square, opts = {}) {
|
|
8487
|
-
|
|
8484
|
+
// API: accetta id, converte subito in oggetto
|
|
8485
|
+
const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
|
|
8486
|
+
if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[dehighlight] Parametro square non valido');
|
|
8488
8487
|
if (this.boardService && this.boardService.dehighlightSquare) {
|
|
8489
|
-
this.boardService.dehighlightSquare(
|
|
8488
|
+
this.boardService.dehighlightSquare(squareObj, opts);
|
|
8490
8489
|
} else if (this.eventService && this.eventService.dehighlightSquare) {
|
|
8491
|
-
this.eventService.dehighlightSquare(
|
|
8490
|
+
this.eventService.dehighlightSquare(squareObj, opts);
|
|
8492
8491
|
}
|
|
8493
8492
|
}
|
|
8494
8493
|
|
|
@@ -8513,6 +8512,8 @@ let Chessboard$1 = class Chessboard {
|
|
|
8513
8512
|
* @returns {boolean}
|
|
8514
8513
|
*/
|
|
8515
8514
|
isGameOver() {
|
|
8515
|
+
// Forza sync prima di interrogare il motore
|
|
8516
|
+
this._updateBoardPieces(false, false);
|
|
8516
8517
|
const game = this.positionService.getGame();
|
|
8517
8518
|
if (!game) return false;
|
|
8518
8519
|
if (game.isGameOver) return game.isGameOver();
|
|
@@ -8816,23 +8817,6 @@ let Chessboard$1 = class Chessboard {
|
|
|
8816
8817
|
}
|
|
8817
8818
|
}
|
|
8818
8819
|
|
|
8819
|
-
/**
|
|
8820
|
-
* Gets or sets the animation style
|
|
8821
|
-
* @param {string} [style] - New animation style ('sequential' or 'simultaneous')
|
|
8822
|
-
* @returns {string} Current animation style
|
|
8823
|
-
*/
|
|
8824
|
-
animationStyle(style) {
|
|
8825
|
-
if (style === undefined) {
|
|
8826
|
-
return this.config.animationStyle;
|
|
8827
|
-
}
|
|
8828
|
-
|
|
8829
|
-
if (this.validationService.isValidAnimationStyle(style)) {
|
|
8830
|
-
this.config.animationStyle = style;
|
|
8831
|
-
}
|
|
8832
|
-
|
|
8833
|
-
return this.config.animationStyle;
|
|
8834
|
-
}
|
|
8835
|
-
|
|
8836
8820
|
/**
|
|
8837
8821
|
* Gets or sets the simultaneous animation delay
|
|
8838
8822
|
* @param {number} [delay] - New delay in milliseconds
|
|
@@ -8882,7 +8866,7 @@ let Chessboard$1 = class Chessboard {
|
|
|
8882
8866
|
dehighlightSquare(square) {
|
|
8883
8867
|
return this.boardService.dehighlight(square);
|
|
8884
8868
|
}
|
|
8885
|
-
forceSync() { this._updateBoardPieces(true, true); }
|
|
8869
|
+
forceSync() { this._updateBoardPieces(true, true); this._updateBoardPieces(true, false); }
|
|
8886
8870
|
|
|
8887
8871
|
// Metodi mancanti che causano fallimenti nei test
|
|
8888
8872
|
/**
|
|
@@ -8895,37 +8879,37 @@ let Chessboard$1 = class Chessboard {
|
|
|
8895
8879
|
movePiece(move, opts = {}) {
|
|
8896
8880
|
const animate = opts.animate !== undefined ? opts.animate : true;
|
|
8897
8881
|
|
|
8898
|
-
//
|
|
8899
|
-
let
|
|
8882
|
+
// --- API: accetta id/stringhe, ma converte subito in oggetti ---
|
|
8883
|
+
let fromSquareObj, toSquareObj, promotion;
|
|
8900
8884
|
if (typeof move === 'string') {
|
|
8901
8885
|
if (move.length === 4) {
|
|
8902
|
-
|
|
8903
|
-
|
|
8886
|
+
fromSquareObj = this.boardService.getSquare(move.substring(0, 2));
|
|
8887
|
+
toSquareObj = this.boardService.getSquare(move.substring(2, 4));
|
|
8904
8888
|
} else if (move.length === 5) {
|
|
8905
|
-
|
|
8906
|
-
|
|
8889
|
+
fromSquareObj = this.boardService.getSquare(move.substring(0, 2));
|
|
8890
|
+
toSquareObj = this.boardService.getSquare(move.substring(2, 4));
|
|
8907
8891
|
promotion = move.substring(4, 5);
|
|
8908
8892
|
} else {
|
|
8909
8893
|
throw new Error(`Invalid move format: ${move}`);
|
|
8910
8894
|
}
|
|
8911
8895
|
} else if (typeof move === 'object' && move.from && move.to) {
|
|
8912
|
-
|
|
8913
|
-
|
|
8896
|
+
// Se sono id, converto in oggetti; se sono già oggetti, li uso direttamente
|
|
8897
|
+
fromSquareObj = typeof move.from === 'string' ? this.boardService.getSquare(move.from) : move.from;
|
|
8898
|
+
toSquareObj = typeof move.to === 'string' ? this.boardService.getSquare(move.to) : move.to;
|
|
8914
8899
|
promotion = move.promotion;
|
|
8915
8900
|
} else {
|
|
8916
8901
|
throw new Error(`Invalid move: ${move}`);
|
|
8917
8902
|
}
|
|
8918
8903
|
|
|
8919
|
-
// Get square objects
|
|
8920
|
-
const fromSquareObj = this.boardService.getSquare(fromSquare);
|
|
8921
|
-
const toSquareObj = this.boardService.getSquare(toSquare);
|
|
8922
|
-
|
|
8923
8904
|
if (!fromSquareObj || !toSquareObj) {
|
|
8924
|
-
throw new Error(`Invalid squares: ${
|
|
8905
|
+
throw new Error(`Invalid squares: ${move.from || move.substring(0, 2)} or ${move.to || move.substring(2, 4)}`);
|
|
8925
8906
|
}
|
|
8926
8907
|
|
|
8927
|
-
//
|
|
8928
|
-
|
|
8908
|
+
// --- Internamente: lavora solo con oggetti ---
|
|
8909
|
+
const result = this._onMove(fromSquareObj, toSquareObj, promotion, animate);
|
|
8910
|
+
// Dopo ogni mossa, forza la sincronizzazione della board
|
|
8911
|
+
this._updateBoardPieces(true, false);
|
|
8912
|
+
return result;
|
|
8929
8913
|
}
|
|
8930
8914
|
|
|
8931
8915
|
// Aliases for backward compatibility
|
|
@@ -9400,7 +9384,6 @@ class ChessboardFactory {
|
|
|
9400
9384
|
hints: true,
|
|
9401
9385
|
clickable: true,
|
|
9402
9386
|
moveHighlight: true,
|
|
9403
|
-
animationStyle: 'simultaneous'
|
|
9404
9387
|
});
|
|
9405
9388
|
|
|
9406
9389
|
// Tournament template
|
|
@@ -9411,7 +9394,6 @@ class ChessboardFactory {
|
|
|
9411
9394
|
clickable: true,
|
|
9412
9395
|
moveHighlight: true,
|
|
9413
9396
|
onlyLegalMoves: true,
|
|
9414
|
-
animationStyle: 'sequential'
|
|
9415
9397
|
});
|
|
9416
9398
|
|
|
9417
9399
|
// Analysis template
|
|
@@ -9422,7 +9404,6 @@ class ChessboardFactory {
|
|
|
9422
9404
|
clickable: true,
|
|
9423
9405
|
moveHighlight: true,
|
|
9424
9406
|
mode: 'analysis',
|
|
9425
|
-
animationStyle: 'simultaneous'
|
|
9426
9407
|
});
|
|
9427
9408
|
|
|
9428
9409
|
// Puzzle template
|
|
@@ -9433,7 +9414,6 @@ class ChessboardFactory {
|
|
|
9433
9414
|
clickable: true,
|
|
9434
9415
|
moveHighlight: true,
|
|
9435
9416
|
onlyLegalMoves: true,
|
|
9436
|
-
animationStyle: 'sequential'
|
|
9437
9417
|
});
|
|
9438
9418
|
|
|
9439
9419
|
// Demo template
|
|
@@ -9443,7 +9423,6 @@ class ChessboardFactory {
|
|
|
9443
9423
|
hints: false,
|
|
9444
9424
|
clickable: false,
|
|
9445
9425
|
moveHighlight: true,
|
|
9446
|
-
animationStyle: 'simultaneous'
|
|
9447
9426
|
});
|
|
9448
9427
|
}
|
|
9449
9428
|
|
|
@@ -10251,11 +10230,7 @@ function validateConfig(config) {
|
|
|
10251
10230
|
if (config.moveAnimation && !isValidEasing(config.moveAnimation)) {
|
|
10252
10231
|
errors.push('Invalid moveAnimation. Must be a valid easing function');
|
|
10253
10232
|
}
|
|
10254
|
-
|
|
10255
|
-
if (config.animationStyle && !['sequential', 'simultaneous'].includes(config.animationStyle)) {
|
|
10256
|
-
errors.push('Invalid animationStyle. Must be "sequential" or "simultaneous"');
|
|
10257
|
-
}
|
|
10258
|
-
|
|
10233
|
+
|
|
10259
10234
|
return {
|
|
10260
10235
|
success: errors.length === 0,
|
|
10261
10236
|
errors
|