@alepot55/chessboardjs 2.3.4 → 2.3.5

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.
@@ -1670,6 +1670,8 @@ class Piece {
1670
1670
  if (callback) callback();
1671
1671
  }
1672
1672
  };
1673
+ // Se l'elemento è già stato rimosso, esci subito
1674
+ if (!this.element) { console.debug(`[Piece] fadeOut: ${this.id} - element is null (init)`); if (callback) callback(); return; }
1673
1675
  fade();
1674
1676
  }
1675
1677
 
@@ -1677,24 +1679,22 @@ class Piece {
1677
1679
  if (!this.element) { console.debug(`[Piece] setDrag: ${this.id} - element is null`); return; }
1678
1680
  this.element.ondragstart = (e) => { e.preventDefault(); };
1679
1681
  this.element.onmousedown = f;
1682
+ this.element.ontouchstart = f; // Drag touch
1680
1683
  console.debug(`[Piece] setDrag: ${this.id}`);
1681
1684
  }
1682
1685
 
1683
1686
  destroy() {
1687
+ if (!this.element) return; // Idempotente: già rimosso
1684
1688
  console.debug(`[Piece] Destroy: ${this.id}`);
1685
1689
  // 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;
1690
+ this.element.onmousedown = null;
1691
+ this.element.ondragstart = null;
1692
+ // Remove from DOM
1693
+ if (this.element.parentNode) {
1694
+ this.element.parentNode.removeChild(this.element);
1697
1695
  }
1696
+ // Clear references
1697
+ this.element = null;
1698
1698
  }
1699
1699
 
1700
1700
  translate(to, duration, transition_f, speed, callback = null) {
@@ -1819,8 +1819,8 @@ class Square {
1819
1819
  // Best practice: destroy the piece object if present
1820
1820
  if (this.piece && typeof this.piece.destroy === 'function') {
1821
1821
  this.piece.destroy();
1822
- this.piece = null;
1823
1822
  }
1823
+ this.piece = null;
1824
1824
  // Remove any orphaned img.piece elements from the DOM
1825
1825
  const pieceElements = this.element.querySelectorAll('img.piece');
1826
1826
  pieceElements.forEach(element => {
@@ -2314,13 +2314,32 @@ class BoardService {
2314
2314
 
2315
2315
  /**
2316
2316
  * Gets a square by its ID
2317
- * @param {string} squareId - Square identifier (e.g., 'e4')
2317
+ * @param {string} squareId - Square identifier (API pubblica)
2318
2318
  * @returns {Square|null} The square or null if not found
2319
2319
  */
2320
2320
  getSquare(squareId) {
2321
2321
  return this.squares[squareId] || null;
2322
2322
  }
2323
2323
 
2324
+ /**
2325
+ * Highlight a square (solo oggetto)
2326
+ * @param {Square} square
2327
+ * @param {Object} [opts]
2328
+ */
2329
+ highlightSquare(square, opts = {}) {
2330
+ if (!square) throw new Error('highlightSquare richiede oggetto Square');
2331
+ // ... logica esistente ...
2332
+ }
2333
+ /**
2334
+ * Dehighlight a square (solo oggetto)
2335
+ * @param {Square} square
2336
+ * @param {Object} [opts]
2337
+ */
2338
+ dehighlightSquare(square, opts = {}) {
2339
+ if (!square) throw new Error('dehighlightSquare richiede oggetto Square');
2340
+ // ... logica esistente ...
2341
+ }
2342
+
2324
2343
  /**
2325
2344
  * Gets all squares
2326
2345
  * @returns {Object.<string, Square>} All squares indexed by ID
@@ -3160,24 +3179,77 @@ class EventService {
3160
3179
 
3161
3180
  // Click handler
3162
3181
  const handleClick = (e) => {
3182
+ // Ghost click prevention: ignora il click se appena gestito un touch
3183
+ if (square._ignoreNextClick) {
3184
+ square._ignoreNextClick = false;
3185
+ return;
3186
+ }
3163
3187
  e.stopPropagation();
3164
3188
  if (this.config.clickable && !this.isAnimating) {
3165
3189
  onSquareClick(square);
3166
3190
  }
3167
3191
  };
3168
3192
 
3193
+ // --- Touch tap/drag separation ---
3194
+ let touchMoved = false;
3195
+ let touchStartX = 0;
3196
+ let touchStartY = 0;
3197
+ let touchTimeout = null;
3198
+
3199
+ const handleTouchStart = (e) => {
3200
+ if (!e.touches || e.touches.length > 1) return; // solo primo dito
3201
+ touchMoved = false;
3202
+ touchStartX = e.touches[0].clientX;
3203
+ touchStartY = e.touches[0].clientY;
3204
+ // Timeout: se il dito non si muove, dopo 150ms sarà considerato tap
3205
+ touchTimeout = setTimeout(() => {
3206
+ if (!touchMoved) {
3207
+ // Prevenzione ghost click: ignora il prossimo click
3208
+ square._ignoreNextClick = true;
3209
+ setTimeout(() => { square._ignoreNextClick = false; }, 400);
3210
+ handleClick(e); // Tap breve = selezione
3211
+ }
3212
+ }, 150);
3213
+ };
3214
+
3215
+ const handleTouchMove = (e) => {
3216
+ if (!e.touches || e.touches.length > 1) return;
3217
+ const dx = Math.abs(e.touches[0].clientX - touchStartX);
3218
+ const dy = Math.abs(e.touches[0].clientY - touchStartY);
3219
+ if (dx > 5 || dy > 5) {
3220
+ touchMoved = true;
3221
+ if (touchTimeout) {
3222
+ clearTimeout(touchTimeout);
3223
+ touchTimeout = null;
3224
+ }
3225
+ // Il drag vero e proprio è gestito da createDragFunction sul pezzo
3226
+ }
3227
+ };
3228
+
3229
+ const handleTouchEnd = (e) => {
3230
+ if (touchTimeout) {
3231
+ clearTimeout(touchTimeout);
3232
+ touchTimeout = null;
3233
+ }
3234
+ };
3235
+
3169
3236
  // Add listeners
3170
3237
  square.element.addEventListener('mouseover', throttledHover);
3171
3238
  square.element.addEventListener('mouseout', throttledLeave);
3172
3239
  square.element.addEventListener('click', handleClick);
3173
- square.element.addEventListener('touchstart', handleClick);
3240
+ // Touch: separa tap e drag
3241
+ square.element.addEventListener('touchstart', handleTouchStart);
3242
+ square.element.addEventListener('touchmove', handleTouchMove);
3243
+ square.element.addEventListener('touchend', handleTouchEnd);
3174
3244
 
3175
3245
  // Store listeners for cleanup
3176
3246
  listeners.push(
3177
3247
  { element: square.element, type: 'mouseover', handler: throttledHover },
3178
3248
  { element: square.element, type: 'mouseout', handler: throttledLeave },
3179
3249
  { element: square.element, type: 'click', handler: handleClick },
3180
- { element: square.element, type: 'touchstart', handler: handleClick }
3250
+ { element: square.element, type: 'touchstart', handler: handleTouchStart },
3251
+ { element: square.element, type: 'touchmove', handler: handleTouchMove },
3252
+ { element: square.element, type: 'touchend', handler: handleTouchEnd }
3181
3253
  );
3182
3254
 
3183
3255
  this.eventListeners.set(square.id, listeners);
@@ -3216,14 +3288,24 @@ class EventService {
3216
3288
  }
3217
3289
 
3218
3290
  // Track initial position for drag threshold
3291
+ const isTouch = event.type && event.type.startsWith('touch');
3219
3292
  const startX = event.clientX || (event.touches && event.touches[0]?.clientX) || 0;
3220
3293
  const startY = event.clientY || (event.touches && event.touches[0]?.clientY) || 0;
3221
3294
 
3295
+ // --- Touch scroll lock helper ---
3296
+ const addScrollLock = () => {
3297
+ document.body.classList.add('chessboardjs-dragging');
3298
+ };
3299
+ const removeScrollLock = () => {
3300
+ document.body.classList.remove('chessboardjs-dragging');
3301
+ };
3302
+
3303
+ // --- MOVE HANDLER (mouse + touch unified) ---
3222
3304
  const moveAt = (event) => {
3223
3305
  const boardElement = this.boardService.element;
3224
3306
  const squareSize = boardElement.offsetWidth / 8;
3225
3307
 
3226
- // Get mouse coordinates
3308
+ // Get mouse/touch coordinates
3227
3309
  let clientX, clientY;
3228
3310
  if (event.touches && event.touches[0]) {
3229
3311
  clientX = event.touches[0].clientX;
@@ -3244,15 +3326,28 @@ class EventService {
3244
3326
  return true;
3245
3327
  };
3246
3328
 
3247
- const onMouseMove = (event) => {
3248
- const currentX = event.clientX || 0;
3249
- const currentY = event.clientY || 0;
3329
+ // --- DRAG MOVE (mouse + touch) ---
3330
+ const onPointerMove = (event) => {
3331
+ // For touch, only handle the first finger
3332
+ if (event.touches && event.touches.length > 1) return;
3333
+ if (event.touches && !event.touches[0]) return;
3334
+ if (event.type && event.type.startsWith('touch')) event.preventDefault();
3335
+
3336
+ const currentX = event.clientX || (event.touches && event.touches[0]?.clientX) || 0;
3337
+ const currentY = event.clientY || (event.touches && event.touches[0]?.clientY) || 0;
3250
3338
  const deltaX = Math.abs(currentX - startX);
3251
3339
  const deltaY = Math.abs(currentY - startY);
3252
3340
 
3253
- // Start dragging if mouse moved enough
3341
+ // Start dragging if mouse/touch moved enough
3254
3342
  if (!isDragging && (deltaX > 3 || deltaY > 3)) {
3255
3343
  isDragging = true;
3344
+ // Inizio drag: blocca update board
3345
+ if (this.chessboard) this.chessboard._isDragging = true;
3346
+
3347
+ // Mostra hint all'inizio del drag se attivi
3348
+ if (this.config.hints && typeof this.chessboard._boundOnPieceHover === 'function') {
3349
+ this.chessboard._boundOnPieceHover(from);
3350
+ }
3256
3351
 
3257
3352
  // Set up drag state
3258
3353
  if (!this.config.clickable) {
@@ -3265,7 +3360,6 @@ class EventService {
3265
3360
  // Visual feedback
3266
3361
  if (this.config.clickable) {
3267
3362
  from.select();
3268
- // Show hints would be handled by the main class
3269
3363
  }
3270
3364
 
3271
3365
  // Prepare piece for dragging
@@ -3275,6 +3369,9 @@ class EventService {
3275
3369
 
3276
3370
  DragOptimizations.enableForDrag(img);
3277
3371
 
3372
+ // Lock scroll for touch
3373
+ if (isTouch) addScrollLock();
3374
+
3278
3375
  // Call drag start callback
3279
3376
  if (!onDragStart(square, piece)) {
3280
3377
  return;
@@ -3288,8 +3385,16 @@ class EventService {
3288
3385
  // Update target square
3289
3386
  const boardElement = this.boardService.element;
3290
3387
  const boardRect = boardElement.getBoundingClientRect();
3291
- const x = event.clientX - boardRect.left;
3292
- const y = event.clientY - boardRect.top;
3388
+ let clientX, clientY;
3389
+ if (event.touches && event.touches[0]) {
3390
+ clientX = event.touches[0].clientX;
3391
+ clientY = event.touches[0].clientY;
3392
+ } else {
3393
+ clientX = event.clientX;
3394
+ clientY = event.clientY;
3395
+ }
3396
+ const x = clientX - boardRect.left;
3397
+ const y = clientY - boardRect.top;
3293
3398
 
3294
3399
  let newTo = null;
3295
3400
  if (x >= 0 && x <= boardRect.width && y >= 0 && y <= boardRect.height) {
@@ -3308,18 +3413,26 @@ class EventService {
3308
3413
  }
3309
3414
  };
3310
3415
 
3311
- const onMouseUp = () => {
3312
- // Clean up visual feedback
3416
+ // --- DRAG END (mouse + touch) ---
3417
+ const onPointerUp = (event) => {
3313
3418
  previousHighlight?.dehighlight();
3314
- document.removeEventListener('mousemove', onMouseMove);
3315
- window.removeEventListener('mouseup', onMouseUp);
3419
+ document.removeEventListener('mousemove', onPointerMove);
3420
+ window.removeEventListener('mouseup', onPointerUp);
3421
+ document.removeEventListener('touchmove', onPointerMove);
3422
+ window.removeEventListener('touchend', onPointerUp);
3423
+ if (isTouch) removeScrollLock();
3424
+ // Fine drag: sblocca update board
3425
+ if (this.chessboard) this.chessboard._isDragging = false;
3426
+
3427
+ // Rimuovi hint alla fine del drag se attivi
3428
+ if (this.config.hints && typeof this.chessboard._boundOnPieceLeave === 'function') {
3429
+ this.chessboard._boundOnPieceLeave(from);
3430
+ }
3316
3431
 
3317
- // If this was just a click, don't interfere
3318
3432
  if (!isDragging) {
3319
3433
  return;
3320
3434
  }
3321
3435
 
3322
- // Clean up drag state
3323
3436
  img.style.zIndex = '20';
3324
3437
  img.classList.remove('dragging');
3325
3438
  img.style.willChange = 'auto';
@@ -3331,23 +3444,26 @@ class EventService {
3331
3444
  if (isTrashDrop) {
3332
3445
  this._handleTrashDrop(originalFrom, onRemove);
3333
3446
  } else if (!to) {
3334
- // Reset piece position instantly for snapback
3335
3447
  img.style.position = '';
3336
3448
  img.style.left = '';
3337
3449
  img.style.top = '';
3338
3450
  img.style.transform = '';
3339
-
3340
3451
  this._handleSnapback(originalFrom, piece, onSnapback);
3341
3452
  } else {
3342
- // Handle drop like a click - simple and reliable
3343
3453
  this._handleDrop(originalFrom, to, piece, onMove, onSnapback);
3344
3454
  }
3345
3455
  };
3346
3456
 
3347
- // Attach event listeners
3348
- window.addEventListener('mouseup', onMouseUp, { once: true });
3349
- document.addEventListener('mousemove', onMouseMove);
3350
- img.addEventListener('mouseup', onMouseUp, { once: true });
3457
+ // --- Attach listeners (mouse + touch) ---
3458
+ window.addEventListener('mouseup', onPointerUp, { once: true });
3459
+ document.addEventListener('mousemove', onPointerMove);
3460
+ img.addEventListener('mouseup', onPointerUp, { once: true });
3461
+ // Touch events
3462
+ window.addEventListener('touchend', onPointerUp, { once: true });
3463
+ document.addEventListener('touchmove', onPointerMove, { passive: false });
3464
+
3465
+ // Per robustezza: se il drag parte da touch, blocca subito lo scroll
3466
+ if (isTouch) addScrollLock();
3351
3467
  };
3352
3468
  }
3353
3469
 
@@ -3570,6 +3686,7 @@ class EventService {
3570
3686
  if (!from) {
3571
3687
  if (this.moveService.canMove(square)) {
3572
3688
  if (this.config.clickable) {
3689
+ this.boardService.applyToAllSquares('removeHint'); // Rimuovi hint prima di selezionare
3573
3690
  onSelect(square);
3574
3691
  }
3575
3692
  this.clicked = square;
@@ -3579,9 +3696,13 @@ class EventService {
3579
3696
  }
3580
3697
  }
3581
3698
 
3582
- // Clicking same square - deselect
3699
+ // --- Touch: non deselezionare su doppio tap sulla stessa casella ---
3583
3700
  if (this.clicked === square) {
3701
+ if (window.event && window.event.type && window.event.type.startsWith('touch')) {
3702
+ return false;
3703
+ }
3584
3704
  onDeselect(square);
3705
+ this.boardService.applyToAllSquares('removeHint');
3585
3706
  this.clicked = null;
3586
3707
  return false;
3587
3708
  }
@@ -3590,68 +3711,52 @@ class EventService {
3590
3711
  if (!promotion && this.moveService.requiresPromotion(new Move$1(from, square))) {
3591
3712
  console.log('Move requires promotion:', from.id, '->', square.id);
3592
3713
 
3593
- // Set up promotion UI
3594
3714
  this.moveService.setupPromotion(
3595
3715
  new Move$1(from, square),
3596
3716
  this.boardService.squares,
3597
3717
  (selectedPromotion) => {
3598
3718
  console.log('Promotion selected:', selectedPromotion);
3599
-
3600
- // Clear promotion UI first
3601
3719
  this.boardService.applyToAllSquares('removePromotion');
3602
3720
  this.boardService.applyToAllSquares('removeCover');
3603
-
3604
- // Execute the move with promotion
3605
3721
  const moveResult = onMove(from, square, selectedPromotion, animate);
3606
-
3607
3722
  if (moveResult) {
3608
- // After a successful promotion move, we need to replace the piece
3609
- // after the drop animation completes
3610
3723
  this._schedulePromotionPieceReplacement(square, selectedPromotion);
3611
-
3612
3724
  onDeselect(from);
3725
+ this.boardService.applyToAllSquares('removeHint');
3613
3726
  this.clicked = null;
3614
3727
  }
3615
3728
  },
3616
3729
  () => {
3617
3730
  console.log('Promotion cancelled');
3618
-
3619
- // Clear promotion UI on cancel
3620
3731
  this.boardService.applyToAllSquares('removePromotion');
3621
3732
  this.boardService.applyToAllSquares('removeCover');
3622
-
3623
3733
  onDeselect(from);
3734
+ this.boardService.applyToAllSquares('removeHint');
3624
3735
  this.clicked = null;
3625
3736
  }
3626
3737
  );
3627
3738
  return false;
3628
3739
  }
3629
3740
 
3630
- // Attempt to make move
3631
3741
  const moveResult = onMove(from, square, promotion, animate);
3632
3742
 
3633
3743
  if (moveResult) {
3634
- // Move successful
3635
3744
  onDeselect(from);
3745
+ this.boardService.applyToAllSquares('removeHint');
3636
3746
  this.clicked = null;
3637
3747
  return true;
3638
3748
  } else {
3639
- // Move failed - check if clicked square has a piece we can move
3640
3749
  if (this.moveService.canMove(square)) {
3641
- // Deselect the previous piece
3642
3750
  onDeselect(from);
3643
-
3644
- // Select the new piece if clicking is enabled
3751
+ this.boardService.applyToAllSquares('removeHint');
3645
3752
  if (this.config.clickable) {
3646
3753
  onSelect(square);
3647
3754
  }
3648
-
3649
- // Set the new piece as clicked
3650
3755
  this.clicked = square;
3651
3756
  return false;
3652
3757
  } else {
3653
- // Move failed and no valid piece to select
3654
3758
  onDeselect(from);
3759
+ this.boardService.applyToAllSquares('removeHint');
3655
3760
  this.clicked = null;
3656
3761
  return false;
3657
3762
  }
@@ -3915,27 +4020,27 @@ class MoveService {
3915
4020
  */
3916
4021
  canMove(square) {
3917
4022
  if (!square.piece) return false;
3918
-
4023
+
3919
4024
  const { movableColors, onlyLegalMoves } = this.config;
3920
-
4025
+
3921
4026
  if (movableColors === 'none') return false;
3922
4027
  if (movableColors === 'w' && square.piece.color === 'b') return false;
3923
4028
  if (movableColors === 'b' && square.piece.color === 'w') return false;
3924
-
4029
+
3925
4030
  if (!onlyLegalMoves) return true;
3926
-
4031
+
3927
4032
  // Check if position service and game are available
3928
4033
  if (!this.positionService || !this.positionService.getGame()) {
3929
4034
  return false;
3930
4035
  }
3931
-
4036
+
3932
4037
  const game = this.positionService.getGame();
3933
4038
  return square.piece.color === game.turn();
3934
4039
  }
3935
4040
 
3936
4041
  /**
3937
4042
  * Converts various move formats to a Move instance
3938
- * @param {string|Move} move - Move in various formats
4043
+ * @param {string|Move|Object} move - Move in various formats
3939
4044
  * @param {Object} squares - All board squares
3940
4045
  * @returns {Move} Move instance
3941
4046
  * @throws {MoveError} When move format is invalid
@@ -3944,19 +4049,22 @@ class MoveService {
3944
4049
  if (move instanceof Move$1) {
3945
4050
  return move;
3946
4051
  }
3947
-
4052
+ if (typeof move === 'object' && move.from && move.to) {
4053
+ // Se sono id, converto in oggetti; se sono già oggetti, li uso direttamente
4054
+ const fromSquare = typeof move.from === 'string' ? squares[move.from] : move.from;
4055
+ const toSquare = typeof move.to === 'string' ? squares[move.to] : move.to;
4056
+ if (!fromSquare || !toSquare) throw new MoveError(ERROR_MESSAGES.invalid_move_format, move.from, move.to);
4057
+ return new Move$1(fromSquare, toSquare, move.promotion);
4058
+ }
3948
4059
  if (typeof move === 'string' && move.length >= 4) {
3949
4060
  const fromId = move.slice(0, 2);
3950
4061
  const toId = move.slice(2, 4);
3951
4062
  const promotion = move.slice(4, 5) || null;
3952
-
3953
4063
  if (!squares[fromId] || !squares[toId]) {
3954
4064
  throw new MoveError(ERROR_MESSAGES.invalid_move_format, fromId, toId);
3955
4065
  }
3956
-
3957
4066
  return new Move$1(squares[fromId], squares[toId], promotion);
3958
4067
  }
3959
-
3960
4068
  throw new MoveError(ERROR_MESSAGES.invalid_move_format, 'unknown', 'unknown');
3961
4069
  }
3962
4070
 
@@ -3967,9 +4075,9 @@ class MoveService {
3967
4075
  */
3968
4076
  isLegalMove(move) {
3969
4077
  const legalMoves = this.getLegalMoves(move.from.id);
3970
-
3971
- return legalMoves.some(legalMove =>
3972
- legalMove.to === move.to.id &&
4078
+
4079
+ return legalMoves.some(legalMove =>
4080
+ legalMove.to === move.to.id &&
3973
4081
  move.promotion === legalMove.promotion
3974
4082
  );
3975
4083
  }
@@ -3985,16 +4093,16 @@ class MoveService {
3985
4093
  if (!this.positionService || !this.positionService.getGame()) {
3986
4094
  return [];
3987
4095
  }
3988
-
4096
+
3989
4097
  const game = this.positionService.getGame();
3990
-
4098
+
3991
4099
  if (!game) return [];
3992
-
4100
+
3993
4101
  const options = { verbose };
3994
4102
  if (from) {
3995
4103
  options.square = from;
3996
4104
  }
3997
-
4105
+
3998
4106
  return game.moves(options);
3999
4107
  }
4000
4108
 
@@ -4008,115 +4116,99 @@ class MoveService {
4008
4116
  if (!this.positionService || !this.positionService.getGame()) {
4009
4117
  return [];
4010
4118
  }
4011
-
4119
+
4012
4120
  const game = this.positionService.getGame();
4013
4121
  if (!game) return [];
4014
-
4122
+
4015
4123
  const cacheKey = `${square.id}-${game.fen()}`;
4016
4124
  let moves = this._movesCache.get(cacheKey);
4017
-
4125
+
4018
4126
  if (!moves) {
4019
4127
  moves = game.moves({ square: square.id, verbose: true });
4020
4128
  this._movesCache.set(cacheKey, moves);
4021
-
4129
+
4022
4130
  // Clear cache after a short delay to prevent memory buildup
4023
4131
  if (this._cacheTimeout) {
4024
4132
  clearTimeout(this._cacheTimeout);
4025
4133
  }
4026
-
4134
+
4027
4135
  this._cacheTimeout = setTimeout(() => {
4028
4136
  this._movesCache.clear();
4029
4137
  }, 1000);
4030
4138
  }
4031
-
4139
+
4032
4140
  return moves;
4033
4141
  }
4034
4142
 
4035
4143
  /**
4036
4144
  * Executes a move on the game
4037
- * @param {Move} move - Move to execute
4145
+ * @param {Move} move - Move to execute (deve essere oggetto Move)
4038
4146
  * @returns {Object|null} Move result from chess.js or null if invalid
4039
4147
  */
4040
4148
  executeMove(move) {
4041
- // Check if position service and game are available
4149
+ if (!(move instanceof Move$1)) throw new Error('executeMove richiede un oggetto Move');
4042
4150
  if (!this.positionService || !this.positionService.getGame()) {
4043
4151
  return null;
4044
4152
  }
4045
-
4046
4153
  const game = this.positionService.getGame();
4047
4154
  if (!game) return null;
4048
-
4049
4155
  const moveOptions = {
4050
4156
  from: move.from.id,
4051
4157
  to: move.to.id
4052
4158
  };
4053
-
4054
- console.log('executeMove - move.promotion:', move.promotion);
4055
- console.log('executeMove - move.hasPromotion():', move.hasPromotion());
4056
-
4057
4159
  if (move.hasPromotion()) {
4058
4160
  moveOptions.promotion = move.promotion;
4059
4161
  }
4060
-
4061
- console.log('executeMove - moveOptions:', moveOptions);
4062
-
4063
4162
  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
4163
  return result;
4073
4164
  }
4074
4165
 
4075
4166
  /**
4076
- * Checks if a move requires promotion
4077
- * @param {Move} move - Move to check
4078
- * @returns {boolean} True if promotion is required
4167
+ * Determina se una mossa richiede promozione
4168
+ * @param {Move} move - Deve essere oggetto Move
4169
+ * @returns {boolean}
4079
4170
  */
4080
4171
  requiresPromotion(move) {
4172
+ if (!(move instanceof Move$1)) throw new Error('requiresPromotion richiede un oggetto Move');
4081
4173
  console.log('Checking if move requires promotion:', move.from.id, '->', move.to.id);
4082
-
4174
+
4083
4175
  if (!this.config.onlyLegalMoves) {
4084
4176
  console.log('Not in legal moves mode, no promotion required');
4085
4177
  return false;
4086
4178
  }
4087
-
4179
+
4088
4180
  const game = this.positionService.getGame();
4089
4181
  if (!game) {
4090
4182
  console.log('No game instance available');
4091
4183
  return false;
4092
4184
  }
4093
-
4185
+
4094
4186
  const piece = game.get(move.from.id);
4095
4187
  if (!piece || piece.type !== 'p') {
4096
4188
  console.log('Not a pawn move, no promotion required');
4097
4189
  return false;
4098
4190
  }
4099
-
4191
+
4100
4192
  const targetRank = move.to.row;
4101
4193
  if (targetRank !== 1 && targetRank !== 8) {
4102
4194
  console.log('Not reaching promotion rank, no promotion required');
4103
4195
  return false;
4104
4196
  }
4105
-
4197
+
4106
4198
  console.log('Pawn reaching promotion rank, validating move...');
4107
-
4199
+
4108
4200
  // Additional validation: check if the pawn can actually reach this square
4109
4201
  if (!this._isPawnMoveValid(move.from, move.to, piece.color)) {
4110
4202
  console.log('Pawn move not valid, no promotion required');
4111
4203
  return false;
4112
4204
  }
4113
-
4205
+
4114
4206
  // First check if the move is legal without promotion
4115
4207
  const simpleMoveObj = {
4116
4208
  from: move.from.id,
4117
4209
  to: move.to.id
4118
4210
  };
4119
-
4211
+
4120
4212
  try {
4121
4213
  console.log('Testing move without promotion:', simpleMoveObj);
4122
4214
  // Test if the move is legal without promotion first
@@ -4124,25 +4216,25 @@ class MoveService {
4124
4216
  if (testMove) {
4125
4217
  // Move was successful, but check if it was a promotion
4126
4218
  const wasPromotion = testMove.promotion;
4127
-
4219
+
4128
4220
  // Undo the test move
4129
4221
  game.undo();
4130
-
4222
+
4131
4223
  console.log('Move successful without promotion, was promotion:', wasPromotion !== undefined);
4132
-
4224
+
4133
4225
  // If it was a promotion, return true
4134
4226
  return wasPromotion !== undefined;
4135
4227
  }
4136
4228
  } catch (error) {
4137
4229
  console.log('Move failed without promotion, trying with promotion:', error.message);
4138
-
4230
+
4139
4231
  // If simple move fails, try with promotion
4140
4232
  const promotionMoveObj = {
4141
4233
  from: move.from.id,
4142
4234
  to: move.to.id,
4143
4235
  promotion: 'q' // test with queen
4144
4236
  };
4145
-
4237
+
4146
4238
  try {
4147
4239
  console.log('Testing move with promotion:', promotionMoveObj);
4148
4240
  const testMove = game.move(promotionMoveObj);
@@ -4158,7 +4250,7 @@ class MoveService {
4158
4250
  return false;
4159
4251
  }
4160
4252
  }
4161
-
4253
+
4162
4254
  console.log('Move validation complete, no promotion required');
4163
4255
  return false;
4164
4256
  }
@@ -4176,27 +4268,27 @@ class MoveService {
4176
4268
  const toRank = to.row;
4177
4269
  const fromFile = from.col;
4178
4270
  const toFile = to.col;
4179
-
4271
+
4180
4272
  console.log(`Validating pawn move: ${from.id} -> ${to.id} (${color})`);
4181
4273
  console.log(`Ranks: ${fromRank} -> ${toRank}, Files: ${fromFile} -> ${toFile}`);
4182
-
4274
+
4183
4275
  // Direction of pawn movement
4184
4276
  const direction = color === 'w' ? 1 : -1;
4185
4277
  const rankDiff = toRank - fromRank;
4186
4278
  const fileDiff = Math.abs(toFile - fromFile);
4187
-
4279
+
4188
4280
  // Pawn can only move forward
4189
4281
  if (rankDiff * direction <= 0) {
4190
4282
  console.log('Invalid: Pawn cannot move backward or stay in place');
4191
4283
  return false;
4192
4284
  }
4193
-
4285
+
4194
4286
  // Pawn can only move 1 rank at a time (except for double move from starting position)
4195
4287
  if (Math.abs(rankDiff) > 2) {
4196
4288
  console.log('Invalid: Pawn cannot move more than 2 ranks');
4197
4289
  return false;
4198
4290
  }
4199
-
4291
+
4200
4292
  // If moving 2 ranks, must be from starting position
4201
4293
  if (Math.abs(rankDiff) === 2) {
4202
4294
  const startingRank = color === 'w' ? 2 : 7;
@@ -4205,13 +4297,13 @@ class MoveService {
4205
4297
  return false;
4206
4298
  }
4207
4299
  }
4208
-
4300
+
4209
4301
  // Pawn can only move to adjacent files (diagonal capture) or same file (forward move)
4210
4302
  if (fileDiff > 1) {
4211
4303
  console.log('Invalid: Pawn cannot move more than 1 file');
4212
4304
  return false;
4213
4305
  }
4214
-
4306
+
4215
4307
  console.log('Pawn move validation passed');
4216
4308
  return true;
4217
4309
  }
@@ -4226,45 +4318,45 @@ class MoveService {
4226
4318
  */
4227
4319
  setupPromotion(move, squares, onPromotionSelect, onPromotionCancel) {
4228
4320
  if (!this.requiresPromotion(move)) return false;
4229
-
4321
+
4230
4322
  // Check if position service and game are available
4231
4323
  if (!this.positionService || !this.positionService.getGame()) {
4232
4324
  return false;
4233
4325
  }
4234
-
4326
+
4235
4327
  const game = this.positionService.getGame();
4236
4328
  const piece = game.get(move.from.id);
4237
4329
  const targetSquare = move.to;
4238
-
4330
+
4239
4331
  // Clear any existing promotion UI
4240
4332
  Object.values(squares).forEach(square => {
4241
4333
  square.removePromotion();
4242
4334
  square.removeCover();
4243
4335
  });
4244
-
4336
+
4245
4337
  // Always show promotion choices in a column
4246
4338
  this._showPromotionInColumn(targetSquare, piece, squares, onPromotionSelect, onPromotionCancel);
4247
-
4339
+
4248
4340
  return true;
4249
4341
  }
4250
-
4342
+
4251
4343
  /**
4252
4344
  * Shows promotion choices in a column
4253
4345
  * @private
4254
4346
  */
4255
4347
  _showPromotionInColumn(targetSquare, piece, squares, onPromotionSelect, onPromotionCancel) {
4256
4348
  console.log('Setting up promotion for', targetSquare.id, 'piece color:', piece.color);
4257
-
4349
+
4258
4350
  // Set up promotion choices starting from border row
4259
4351
  PROMOTION_PIECES.forEach((pieceType, index) => {
4260
4352
  const choiceSquare = this._findPromotionSquare(targetSquare, index, squares);
4261
-
4353
+
4262
4354
  if (choiceSquare) {
4263
4355
  const pieceId = pieceType + piece.color;
4264
4356
  const piecePath = this._getPiecePathForPromotion(pieceId);
4265
-
4357
+
4266
4358
  console.log('Setting up promotion choice:', pieceType, 'on square:', choiceSquare.id);
4267
-
4359
+
4268
4360
  choiceSquare.putPromotion(piecePath, () => {
4269
4361
  console.log('Promotion choice selected:', pieceType);
4270
4362
  onPromotionSelect(pieceType);
@@ -4273,7 +4365,7 @@ class MoveService {
4273
4365
  console.log('Could not find square for promotion choice:', pieceType, 'index:', index);
4274
4366
  }
4275
4367
  });
4276
-
4368
+
4277
4369
  // Set up cover squares (for cancellation)
4278
4370
  Object.values(squares).forEach(square => {
4279
4371
  if (!square.hasPromotion()) {
@@ -4282,7 +4374,7 @@ class MoveService {
4282
4374
  });
4283
4375
  }
4284
4376
  });
4285
-
4377
+
4286
4378
  return true;
4287
4379
  }
4288
4380
 
@@ -4297,9 +4389,9 @@ class MoveService {
4297
4389
  _findPromotionSquare(targetSquare, index, squares) {
4298
4390
  const col = targetSquare.col;
4299
4391
  const baseRow = targetSquare.row;
4300
-
4392
+
4301
4393
  console.log('Looking for promotion square - target:', targetSquare.id, 'index:', index, 'col:', col, 'baseRow:', baseRow);
4302
-
4394
+
4303
4395
  // Calculate row based on index and promotion direction
4304
4396
  // Start from the border row (1 or 8) and go inward
4305
4397
  let row;
@@ -4313,15 +4405,15 @@ class MoveService {
4313
4405
  console.log('Invalid promotion row:', baseRow);
4314
4406
  return null;
4315
4407
  }
4316
-
4408
+
4317
4409
  console.log('Calculated row:', row);
4318
-
4410
+
4319
4411
  // Ensure row is within bounds
4320
4412
  if (row < 1 || row > 8) {
4321
4413
  console.log('Row out of bounds:', row);
4322
4414
  return null;
4323
4415
  }
4324
-
4416
+
4325
4417
  // Find square by row/col
4326
4418
  for (const square of Object.values(squares)) {
4327
4419
  if (square.col === col && square.row === row) {
@@ -4329,7 +4421,7 @@ class MoveService {
4329
4421
  return square;
4330
4422
  }
4331
4423
  }
4332
-
4424
+
4333
4425
  console.log('No square found for col:', col, 'row:', row);
4334
4426
  return null;
4335
4427
  }
@@ -4344,11 +4436,11 @@ class MoveService {
4344
4436
  // This would typically use the PieceService
4345
4437
  // For now, we'll use a simple implementation
4346
4438
  const { piecesPath } = this.config;
4347
-
4439
+
4348
4440
  if (typeof piecesPath === 'string') {
4349
4441
  return `${piecesPath}/${pieceId}.svg`;
4350
4442
  }
4351
-
4443
+
4352
4444
  // Fallback for other path types
4353
4445
  return `assets/pieces/${pieceId}.svg`;
4354
4446
  }
@@ -4362,20 +4454,20 @@ class MoveService {
4362
4454
  if (typeof moveString !== 'string' || moveString.length < 4 || moveString.length > 5) {
4363
4455
  return null;
4364
4456
  }
4365
-
4457
+
4366
4458
  const from = moveString.slice(0, 2);
4367
4459
  const to = moveString.slice(2, 4);
4368
4460
  const promotion = moveString.slice(4, 5);
4369
-
4461
+
4370
4462
  // Basic validation
4371
4463
  if (!/^[a-h][1-8]$/.test(from) || !/^[a-h][1-8]$/.test(to)) {
4372
4464
  return null;
4373
4465
  }
4374
-
4466
+
4375
4467
  if (promotion && !['q', 'r', 'b', 'n'].includes(promotion.toLowerCase())) {
4376
4468
  return null;
4377
4469
  }
4378
-
4470
+
4379
4471
  return {
4380
4472
  from: from,
4381
4473
  to: to,
@@ -4401,10 +4493,10 @@ class MoveService {
4401
4493
  if (!this.isCastle(gameMove)) {
4402
4494
  return null;
4403
4495
  }
4404
-
4496
+
4405
4497
  const isKingSide = gameMove.isKingsideCastle();
4406
4498
  const isWhite = gameMove.color === 'w';
4407
-
4499
+
4408
4500
  if (isKingSide) {
4409
4501
  // King side castle
4410
4502
  if (isWhite) {
@@ -4440,11 +4532,11 @@ class MoveService {
4440
4532
  if (!this.isEnPassant(gameMove)) {
4441
4533
  return null;
4442
4534
  }
4443
-
4535
+
4444
4536
  const toSquare = gameMove.to;
4445
4537
  const rank = parseInt(toSquare[1]);
4446
4538
  const file = toSquare[0];
4447
-
4539
+
4448
4540
  // The captured pawn is on the same file but different rank
4449
4541
  if (gameMove.color === 'w') {
4450
4542
  // White captures black pawn one rank below
@@ -4552,13 +4644,14 @@ class PieceService {
4552
4644
 
4553
4645
  /**
4554
4646
  * Adds a piece to a square with optional fade-in animation
4555
- * @param {Square} square - Target square
4556
- * @param {Piece} piece - Piece to add
4647
+ * @param {Square} square - Target square (oggetto)
4648
+ * @param {Piece} piece - Piece to add (oggetto)
4557
4649
  * @param {boolean} [fade=true] - Whether to fade in the piece
4558
4650
  * @param {Function} dragFunction - Function to handle drag events
4559
4651
  * @param {Function} [callback] - Callback when animation completes
4560
4652
  */
4561
4653
  addPieceOnSquare(square, piece, fade = true, dragFunction, callback) {
4654
+ if (!square || !piece) throw new Error('addPieceOnSquare richiede oggetti Square e Piece');
4562
4655
  console.debug(`[PieceService] addPieceOnSquare: ${piece.id} to ${square.id}`);
4563
4656
  square.putPiece(piece);
4564
4657
 
@@ -4581,14 +4674,14 @@ class PieceService {
4581
4674
  }
4582
4675
 
4583
4676
  /**
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
4677
+ * Rimuove un pezzo da una casella
4678
+ * @param {Square} square - Oggetto Square
4679
+ * @param {boolean} [fade=true]
4680
+ * @param {Function} [callback]
4681
+ * @returns {Piece} Il pezzo rimosso
4590
4682
  */
4591
4683
  removePieceFromSquare(square, fade = true, callback) {
4684
+ if (!square) throw new Error('removePieceFromSquare richiede oggetto Square');
4592
4685
  console.debug(`[PieceService] removePieceFromSquare: ${square.id}`);
4593
4686
  square.check();
4594
4687
 
@@ -7635,68 +7728,48 @@ let Chessboard$1 = class Chessboard {
7635
7728
  * @param {boolean} [isPositionLoad=false] - Whether this is a position load (affects delay)
7636
7729
  */
7637
7730
  _doUpdateBoardPieces(animation = false, isPositionLoad = false) {
7731
+ // Blocca update se un drag è in corso
7732
+ if (this._isDragging) return;
7638
7733
  // Skip update if we're in the middle of a promotion
7639
7734
  if (this._isPromoting) {
7640
- console.log('Skipping board update during promotion');
7641
7735
  return;
7642
7736
  }
7643
-
7644
- // Check if services are available
7645
7737
  if (!this.positionService || !this.positionService.getGame()) {
7646
- console.log('Cannot update board pieces - position service not available');
7647
7738
  return;
7648
7739
  }
7649
-
7650
7740
  const squares = this.boardService.getAllSquares();
7651
7741
  const gameStateBefore = this.positionService.getGame().fen();
7652
-
7653
- // PATCH ROBUSTA: se la board è completamente vuota, forza la rimozione di TUTTI i pezzi
7654
7742
  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
7743
  const boardContainer = document.getElementById(this.config.id_div);
7659
7744
  if (boardContainer) {
7660
7745
  const pieceElements = boardContainer.querySelectorAll('.piece');
7661
7746
  pieceElements.forEach(element => {
7662
- console.log('Rimozione forzata elemento DOM pezzo:', element);
7663
7747
  element.remove();
7664
7748
  });
7665
7749
  }
7666
-
7667
- // 2. Azzera tutti i riferimenti JS ai pezzi
7668
7750
  Object.values(squares).forEach(sq => {
7669
7751
  if (sq && sq.piece) {
7670
- console.log('Azzero riferimento pezzo su casella:', sq.id);
7671
7752
  sq.piece = null;
7672
7753
  }
7673
7754
  });
7674
-
7675
- // 3. Forza la pulizia di eventuali selezioni/hint
7676
7755
  this._clearVisualState();
7677
-
7678
- // 4. Aggiungi i listener e notifica il cambio
7679
7756
  this._addListeners();
7680
7757
  if (this.config.onChange) this.config.onChange(gameStateBefore);
7681
-
7682
- console.log('Rimozione forzata completata');
7683
7758
  return;
7684
7759
  }
7685
-
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
7760
  const useSimultaneous = this.config.animationStyle === 'simultaneous';
7691
- console.log('_doUpdateBoardPieces - useSimultaneous:', useSimultaneous);
7692
-
7693
7761
  if (useSimultaneous) {
7694
- console.log('Using simultaneous animation');
7695
7762
  this._doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad);
7696
7763
  } else {
7697
- console.log('Using sequential animation');
7698
7764
  this._doSequentialUpdate(squares, gameStateBefore, animation);
7699
7765
  }
7766
+ // Pulizia finale robusta: rimuovi tutti i pezzi orfani dal DOM e dal riferimento JS
7767
+ Object.values(this.boardService.getAllSquares()).forEach(square => {
7768
+ const expectedPieceId = this.positionService.getGamePieceId(square.id);
7769
+ if (!expectedPieceId && typeof square.forceRemoveAllPieces === 'function') {
7770
+ square.forceRemoveAllPieces();
7771
+ }
7772
+ });
7700
7773
  }
7701
7774
 
7702
7775
  /**
@@ -7725,7 +7798,12 @@ let Chessboard$1 = class Chessboard {
7725
7798
 
7726
7799
  // Se c'è un pezzo attuale ma non è quello atteso, rimuovilo
7727
7800
  if (currentPiece && currentPieceId !== expectedPieceId) {
7728
- this.pieceService.removePieceFromSquare(square, animation);
7801
+ // Rimozione robusta: elimina tutti i pezzi orfani dal DOM e dal riferimento JS
7802
+ if (typeof square.forceRemoveAllPieces === 'function') {
7803
+ square.forceRemoveAllPieces();
7804
+ } else {
7805
+ this.pieceService.removePieceFromSquare(square, animation);
7806
+ }
7729
7807
  }
7730
7808
 
7731
7809
  // Se c'è un pezzo atteso ma non è quello attuale, aggiungilo
@@ -7854,7 +7932,13 @@ let Chessboard$1 = class Chessboard {
7854
7932
  for (let i = 0; i < fromList.length; i++) {
7855
7933
  if (!fromMatched[i]) {
7856
7934
  setTimeout(() => {
7857
- this.pieceService.removePieceFromSquare(fromList[i].square, true, onAnimationComplete);
7935
+ // Rimozione robusta: elimina tutti i pezzi orfani dal DOM e dal riferimento JS
7936
+ if (typeof fromList[i].square.forceRemoveAllPieces === 'function') {
7937
+ fromList[i].square.forceRemoveAllPieces();
7938
+ } else {
7939
+ this.pieceService.removePieceFromSquare(fromList[i].square, true, onAnimationComplete);
7940
+ }
7941
+ onAnimationComplete();
7858
7942
  }, animationIndex * animationDelay);
7859
7943
  animationIndex++;
7860
7944
  }
@@ -8246,6 +8330,8 @@ let Chessboard$1 = class Chessboard {
8246
8330
  if (this._updateBoardPieces) {
8247
8331
  this._updateBoardPieces(animate, true);
8248
8332
  }
8333
+ // Forza la sincronizzazione dopo setPosition
8334
+ this._updateBoardPieces(true, false);
8249
8335
  return true;
8250
8336
  }
8251
8337
  /**
@@ -8259,7 +8345,10 @@ let Chessboard$1 = class Chessboard {
8259
8345
  // Use the default starting position from config or fallback
8260
8346
  const startPosition = this.config && this.config.position ? this.config.position : 'start';
8261
8347
  this._updateBoardPieces(animate);
8262
- return this.setPosition(startPosition, { animate });
8348
+ const result = this.setPosition(startPosition, { animate });
8349
+ // Forza la sincronizzazione dopo reset
8350
+ this._updateBoardPieces(true, false);
8351
+ return result;
8263
8352
  }
8264
8353
  /**
8265
8354
  * Clear the board
@@ -8283,6 +8372,8 @@ let Chessboard$1 = class Chessboard {
8283
8372
  if (this._updateBoardPieces) {
8284
8373
  this._updateBoardPieces(animate, true);
8285
8374
  }
8375
+ // Forza la sincronizzazione dopo clear
8376
+ this._updateBoardPieces(true, false);
8286
8377
  return true;
8287
8378
  }
8288
8379
 
@@ -8297,7 +8388,8 @@ let Chessboard$1 = class Chessboard {
8297
8388
  const undone = this.positionService.getGame().undo();
8298
8389
  if (undone) {
8299
8390
  this._undoneMoves.push(undone);
8300
- this._updateBoardPieces(opts.animate !== false);
8391
+ // Forza refresh completo di tutti i pezzi dopo undo
8392
+ this._updateBoardPieces(true, true);
8301
8393
  return undone;
8302
8394
  }
8303
8395
  return null;
@@ -8314,7 +8406,8 @@ let Chessboard$1 = class Chessboard {
8314
8406
  const moveObj = { from: move.from, to: move.to };
8315
8407
  if (move.promotion) moveObj.promotion = move.promotion;
8316
8408
  const result = this.positionService.getGame().move(moveObj);
8317
- this._updateBoardPieces(opts.animate !== false);
8409
+ // Forza refresh completo di tutti i pezzi dopo redo
8410
+ this._updateBoardPieces(true, true);
8318
8411
  return result;
8319
8412
  }
8320
8413
  return false;
@@ -8333,17 +8426,19 @@ let Chessboard$1 = class Chessboard {
8333
8426
  * @returns {string|null}
8334
8427
  */
8335
8428
  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;
8429
+ // Sempre leggi lo stato aggiornato dal boardService
8430
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8431
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[getPiece] Parametro square non valido');
8432
+ // Forza sync prima di leggere
8433
+ this._updateBoardPieces(false, false);
8434
+ const piece = squareObj.piece;
8340
8435
  if (!piece) return null;
8341
8436
  return (piece.color + piece.type).toLowerCase();
8342
8437
  }
8343
8438
  /**
8344
8439
  * Put a piece on a square
8345
- * @param {string} piece
8346
- * @param {string} square
8440
+ * @param {string|Piece} piece
8441
+ * @param {string|Square} square
8347
8442
  * @param {Object} [opts]
8348
8443
  * @param {boolean} [opts.animate=true]
8349
8444
  * @returns {boolean}
@@ -8354,7 +8449,6 @@ let Chessboard$1 = class Chessboard {
8354
8449
  if (typeof piece === 'object' && piece.type && piece.color) {
8355
8450
  pieceStr = (piece.color + piece.type).toLowerCase();
8356
8451
  } else if (typeof piece === 'string' && piece.length === 2) {
8357
- // Accetta sia 'wq' che 'qw', normalizza a 'wq'
8358
8452
  const a = piece[0].toLowerCase();
8359
8453
  const b = piece[1].toLowerCase();
8360
8454
  const types = 'kqrbnp';
@@ -8367,42 +8461,36 @@ let Chessboard$1 = class Chessboard {
8367
8461
  throw new Error(`[putPiece] Invalid piece: ${piece}`);
8368
8462
  }
8369
8463
  }
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
- }
8464
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8465
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[putPiece] Parametro square non valido');
8377
8466
  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;
8467
+ if (!pieceObj || typeof pieceObj !== 'object' || !('type' in pieceObj)) throw new Error('[putPiece] Parametro piece non valido');
8468
+ // Aggiorna solo il motore chess.js
8381
8469
  const chessJsPiece = { type: pieceObj.type, color: pieceObj.color };
8382
8470
  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}`);
8471
+ const result = game.put(chessJsPiece, squareObj.id);
8472
+ if (!result) throw new Error(`[putPiece] Game.put failed for ${pieceStr} on ${squareObj.id}`);
8473
+ // Non aggiornare direttamente square.piece!
8474
+ // Riallinea la board JS allo stato del motore
8385
8475
  this._updateBoardPieces(animate);
8386
8476
  return true;
8387
8477
  }
8388
8478
  /**
8389
8479
  * Remove a piece from a square
8390
- * @param {string} square
8480
+ * @param {string|Square} square
8391
8481
  * @param {Object} [opts]
8392
8482
  * @param {boolean} [opts.animate=true]
8393
8483
  * @returns {string|null}
8394
8484
  */
8395
8485
  removePiece(square, opts = {}) {
8396
8486
  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;
8487
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8488
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[removePiece] Parametro square non valido');
8489
+ // Aggiorna solo il motore chess.js
8404
8490
  const game = this.positionService.getGame();
8405
- game.remove(square);
8491
+ game.remove(squareObj.id);
8492
+ // Non aggiornare direttamente square.piece!
8493
+ // Riallinea la board JS allo stato del motore
8406
8494
  this._updateBoardPieces(animate);
8407
8495
  return true;
8408
8496
  }
@@ -8467,28 +8555,32 @@ let Chessboard$1 = class Chessboard {
8467
8555
  // --- HIGHLIGHTING & UI ---
8468
8556
  /**
8469
8557
  * Highlight a square
8470
- * @param {string} square
8558
+ * @param {string|Square} square
8471
8559
  * @param {Object} [opts]
8472
8560
  */
8473
8561
  highlight(square, opts = {}) {
8474
- if (!this.validationService.isValidSquare(square)) return;
8562
+ // API: accetta id, converte subito in oggetto
8563
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8564
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[highlight] Parametro square non valido');
8475
8565
  if (this.boardService && this.boardService.highlightSquare) {
8476
- this.boardService.highlightSquare(square, opts);
8566
+ this.boardService.highlightSquare(squareObj, opts);
8477
8567
  } else if (this.eventService && this.eventService.highlightSquare) {
8478
- this.eventService.highlightSquare(square, opts);
8568
+ this.eventService.highlightSquare(squareObj, opts);
8479
8569
  }
8480
8570
  }
8481
8571
  /**
8482
8572
  * Remove highlight from a square
8483
- * @param {string} square
8573
+ * @param {string|Square} square
8484
8574
  * @param {Object} [opts]
8485
8575
  */
8486
8576
  dehighlight(square, opts = {}) {
8487
- if (!this.validationService.isValidSquare(square)) return;
8577
+ // API: accetta id, converte subito in oggetto
8578
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8579
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[dehighlight] Parametro square non valido');
8488
8580
  if (this.boardService && this.boardService.dehighlightSquare) {
8489
- this.boardService.dehighlightSquare(square, opts);
8581
+ this.boardService.dehighlightSquare(squareObj, opts);
8490
8582
  } else if (this.eventService && this.eventService.dehighlightSquare) {
8491
- this.eventService.dehighlightSquare(square, opts);
8583
+ this.eventService.dehighlightSquare(squareObj, opts);
8492
8584
  }
8493
8585
  }
8494
8586
 
@@ -8513,6 +8605,8 @@ let Chessboard$1 = class Chessboard {
8513
8605
  * @returns {boolean}
8514
8606
  */
8515
8607
  isGameOver() {
8608
+ // Forza sync prima di interrogare il motore
8609
+ this._updateBoardPieces(false, false);
8516
8610
  const game = this.positionService.getGame();
8517
8611
  if (!game) return false;
8518
8612
  if (game.isGameOver) return game.isGameOver();
@@ -8882,7 +8976,7 @@ let Chessboard$1 = class Chessboard {
8882
8976
  dehighlightSquare(square) {
8883
8977
  return this.boardService.dehighlight(square);
8884
8978
  }
8885
- forceSync() { this._updateBoardPieces(true, true); }
8979
+ forceSync() { this._updateBoardPieces(true, true); this._updateBoardPieces(true, false); }
8886
8980
 
8887
8981
  // Metodi mancanti che causano fallimenti nei test
8888
8982
  /**
@@ -8895,37 +8989,37 @@ let Chessboard$1 = class Chessboard {
8895
8989
  movePiece(move, opts = {}) {
8896
8990
  const animate = opts.animate !== undefined ? opts.animate : true;
8897
8991
 
8898
- // Parse move string if needed
8899
- let fromSquare, toSquare, promotion;
8992
+ // --- API: accetta id/stringhe, ma converte subito in oggetti ---
8993
+ let fromSquareObj, toSquareObj, promotion;
8900
8994
  if (typeof move === 'string') {
8901
8995
  if (move.length === 4) {
8902
- fromSquare = move.substring(0, 2);
8903
- toSquare = move.substring(2, 4);
8996
+ fromSquareObj = this.boardService.getSquare(move.substring(0, 2));
8997
+ toSquareObj = this.boardService.getSquare(move.substring(2, 4));
8904
8998
  } else if (move.length === 5) {
8905
- fromSquare = move.substring(0, 2);
8906
- toSquare = move.substring(2, 4);
8999
+ fromSquareObj = this.boardService.getSquare(move.substring(0, 2));
9000
+ toSquareObj = this.boardService.getSquare(move.substring(2, 4));
8907
9001
  promotion = move.substring(4, 5);
8908
9002
  } else {
8909
9003
  throw new Error(`Invalid move format: ${move}`);
8910
9004
  }
8911
9005
  } else if (typeof move === 'object' && move.from && move.to) {
8912
- fromSquare = move.from;
8913
- toSquare = move.to;
9006
+ // Se sono id, converto in oggetti; se sono già oggetti, li uso direttamente
9007
+ fromSquareObj = typeof move.from === 'string' ? this.boardService.getSquare(move.from) : move.from;
9008
+ toSquareObj = typeof move.to === 'string' ? this.boardService.getSquare(move.to) : move.to;
8914
9009
  promotion = move.promotion;
8915
9010
  } else {
8916
9011
  throw new Error(`Invalid move: ${move}`);
8917
9012
  }
8918
9013
 
8919
- // Get square objects
8920
- const fromSquareObj = this.boardService.getSquare(fromSquare);
8921
- const toSquareObj = this.boardService.getSquare(toSquare);
8922
-
8923
9014
  if (!fromSquareObj || !toSquareObj) {
8924
- throw new Error(`Invalid squares: ${fromSquare} or ${toSquare}`);
9015
+ throw new Error(`Invalid squares: ${move.from || move.substring(0, 2)} or ${move.to || move.substring(2, 4)}`);
8925
9016
  }
8926
9017
 
8927
- // Execute the move
8928
- return this._onMove(fromSquareObj, toSquareObj, promotion, animate);
9018
+ // --- Internamente: lavora solo con oggetti ---
9019
+ const result = this._onMove(fromSquareObj, toSquareObj, promotion, animate);
9020
+ // Dopo ogni mossa, forza la sincronizzazione della board
9021
+ this._updateBoardPieces(true, false);
9022
+ return result;
8929
9023
  }
8930
9024
 
8931
9025
  // Aliases for backward compatibility