@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.
@@ -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
- if (this.element) {
1687
- this.element.onmousedown = null;
1688
- this.element.ondragstart = null;
1689
-
1690
- // Remove from DOM
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 (e.g., 'e4')
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
- square.element.addEventListener('touchstart', handleClick);
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: handleClick }
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
- const onMouseMove = (event) => {
3248
- const currentX = event.clientX || 0;
3249
- const currentY = event.clientY || 0;
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
- const x = event.clientX - boardRect.left;
3292
- const y = event.clientY - boardRect.top;
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
- const onMouseUp = () => {
3312
- // Clean up visual feedback
3386
+ // --- DRAG END (mouse + touch) ---
3387
+ const onPointerUp = (event) => {
3313
3388
  previousHighlight?.dehighlight();
3314
- document.removeEventListener('mousemove', onMouseMove);
3315
- window.removeEventListener('mouseup', onMouseUp);
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 event listeners
3348
- window.addEventListener('mouseup', onMouseUp, { once: true });
3349
- document.addEventListener('mousemove', onMouseMove);
3350
- img.addEventListener('mouseup', onMouseUp, { once: true });
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
- // Clicking same square - deselect
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
- // Check if position service and game are available
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
- * Checks if a move requires promotion
4077
- * @param {Move} move - Move to check
4078
- * @returns {boolean} True if promotion is required
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
- * Removes a piece from a square with optional fade-out animation
4585
- * @param {Square} square - Source square
4586
- * @param {boolean} [fade=true] - Whether to fade out the piece
4587
- * @param {Function} [callback] - Callback when animation completes
4588
- * @returns {Piece} The removed 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('PieceService.movePiece: piece is null, skipping animation');
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 && this.config.animationStyle === 'simultaneous') {
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
- * Performs the actual board update
7701
+ * Aggiorna i pezzi sulla scacchiera con animazione e delay configurabile (greedy matching)
7633
7702
  * @private
7634
- * @param {boolean} [animation=false] - Whether to animate
7635
- * @param {boolean} [isPositionLoad=false] - Whether this is a position load (affects delay)
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
- // Skip update if we're in the middle of a promotion
7639
- if (this._isPromoting) {
7640
- console.log('Skipping board update during promotion');
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
- console.log('_doUpdateBoardPieces - current FEN:', gameStateBefore);
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
- const animationDelay = isPositionLoad ? 0 : this.config.simultaneousAnimationDelay;
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
- // 1. Costruisci matrice delle distanze
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 (fromMatched[i]) continue;
7768
+ if (localFromMatched[i]) continue;
7831
7769
  for (let j = 0; j < toList.length; j++) {
7832
- if (toMatched[j]) continue;
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
- fromMatched[minI] = true;
7844
- toMatched[minJ] = true;
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].square.piece });
7849
- fromMatched[minI] = true;
7850
- toMatched[minJ] = true;
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
- // 3. Rimuovi i pezzi non abbinati (presenti solo in fromList)
7854
- for (let i = 0; i < fromList.length; i++) {
7855
- if (!fromMatched[i]) {
7856
- setTimeout(() => {
7857
- this.pieceService.removePieceFromSquare(fromList[i].square, true, onAnimationComplete);
7858
- }, animationIndex * animationDelay);
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
- // 4. Aggiungi i pezzi non abbinati (presenti solo in toList)
7864
- for (let j = 0; j < toList.length; j++) {
7865
- if (!toMatched[j]) {
7866
- setTimeout(() => {
7867
- const newPiece = this.pieceService.convertPiece(key);
7868
- this.pieceService.addPieceOnSquare(
7869
- toList[j].square,
7870
- newPiece,
7871
- true,
7872
- this._createDragFunction.bind(this),
7873
- onAnimationComplete
7874
- );
7875
- }, animationIndex * animationDelay);
7876
- animationIndex++;
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
- // 5. Anima i movimenti
7881
- moves.forEach(move => {
7882
- setTimeout(() => {
7883
- this.pieceService.translatePiece(
7884
- move,
7885
- false,
7886
- true,
7887
- this._createDragFunction.bind(this),
7888
- onAnimationComplete
7889
- );
7890
- }, animationIndex * animationDelay);
7891
- animationIndex++;
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
- return this.setPosition(startPosition, { animate });
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
- this._updateBoardPieces(opts.animate !== false);
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
- this._updateBoardPieces(opts.animate !== false);
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
- // Restituisce sempre 'wq' (colore prima, tipo dopo, lowercase) o null
8337
- const sq = this.boardService.getSquare(square);
8338
- if (!sq) return null;
8339
- const piece = sq.piece;
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 validSquare = this.validationService.isValidSquare(square);
8371
- const validPiece = this.validationService.isValidPiece(pieceStr);
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
- const squareObj = this.boardService.getSquare(square);
8379
- if (!squareObj) throw new Error(`[putPiece] Square not found: ${square}`);
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, square);
8384
- if (!result) throw new Error(`[putPiece] Game.put failed for ${pieceStr} on ${square}`);
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
- if (!this.validationService.isValidSquare(square)) {
8398
- throw new Error(`[removePiece] Invalid square: ${square}`);
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(square);
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
- if (!this.validationService.isValidSquare(square)) return;
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(square, opts);
8473
+ this.boardService.highlightSquare(squareObj, opts);
8477
8474
  } else if (this.eventService && this.eventService.highlightSquare) {
8478
- this.eventService.highlightSquare(square, opts);
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
- if (!this.validationService.isValidSquare(square)) return;
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(square, opts);
8488
+ this.boardService.dehighlightSquare(squareObj, opts);
8490
8489
  } else if (this.eventService && this.eventService.dehighlightSquare) {
8491
- this.eventService.dehighlightSquare(square, opts);
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
- // Parse move string if needed
8899
- let fromSquare, toSquare, promotion;
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
- fromSquare = move.substring(0, 2);
8903
- toSquare = move.substring(2, 4);
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
- fromSquare = move.substring(0, 2);
8906
- toSquare = move.substring(2, 4);
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
- fromSquare = move.from;
8913
- toSquare = move.to;
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: ${fromSquare} or ${toSquare}`);
8905
+ throw new Error(`Invalid squares: ${move.from || move.substring(0, 2)} or ${move.to || move.substring(2, 4)}`);
8925
8906
  }
8926
8907
 
8927
- // Execute the move
8928
- return this._onMove(fromSquareObj, toSquareObj, promotion, animate);
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