@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.
@@ -425,7 +425,7 @@ class Chessboard {
425
425
  const capturedPiece = move.to.piece;
426
426
 
427
427
  // For castle moves in simultaneous mode, we need to coordinate both animations
428
- if (isCastleMove && this.config.animationStyle === 'simultaneous') {
428
+ if (isCastleMove) {
429
429
  // Start king animation
430
430
  this.pieceService.translatePiece(
431
431
  move,
@@ -650,187 +650,64 @@ class Chessboard {
650
650
  }
651
651
 
652
652
  /**
653
- * Performs the actual board update
653
+ * Aggiorna i pezzi sulla scacchiera con animazione e delay configurabile (greedy matching)
654
654
  * @private
655
- * @param {boolean} [animation=false] - Whether to animate
656
- * @param {boolean} [isPositionLoad=false] - Whether this is a position load (affects delay)
655
+ * @param {boolean} [animation=false] - Se animare
656
+ * @param {boolean} [isPositionLoad=false] - Se è un caricamento posizione (delay 0)
657
657
  */
658
658
  _doUpdateBoardPieces(animation = false, isPositionLoad = false) {
659
- // Skip update if we're in the middle of a promotion
660
- if (this._isPromoting) {
661
- console.log('Skipping board update during promotion');
662
- return;
663
- }
664
-
665
- // Check if services are available
666
- if (!this.positionService || !this.positionService.getGame()) {
667
- console.log('Cannot update board pieces - position service not available');
668
- return;
669
- }
670
-
659
+ if (this._isDragging) return;
660
+ if (this._isPromoting) return;
661
+ if (!this.positionService || !this.positionService.getGame()) return;
671
662
  const squares = this.boardService.getAllSquares();
672
663
  const gameStateBefore = this.positionService.getGame().fen();
673
-
674
- // PATCH ROBUSTA: se la board è completamente vuota, forza la rimozione di TUTTI i pezzi
675
664
  if (/^8\/8\/8\/8\/8\/8\/8\/8/.test(gameStateBefore)) {
676
- console.log('Board vuota rilevata - rimozione forzata di tutti i pezzi dal DOM');
677
-
678
- // 1. Rimuovi tutti gli elementi DOM dei pezzi dal contenitore della board
679
665
  const boardContainer = document.getElementById(this.config.id_div);
680
666
  if (boardContainer) {
681
667
  const pieceElements = boardContainer.querySelectorAll('.piece');
682
- pieceElements.forEach(element => {
683
- console.log('Rimozione forzata elemento DOM pezzo:', element);
684
- element.remove();
685
- });
668
+ pieceElements.forEach(element => element.remove());
686
669
  }
687
-
688
- // 2. Azzera tutti i riferimenti JS ai pezzi
689
- Object.values(squares).forEach(sq => {
690
- if (sq && sq.piece) {
691
- console.log('Azzero riferimento pezzo su casella:', sq.id);
692
- sq.piece = null;
693
- }
694
- });
695
-
696
- // 3. Forza la pulizia di eventuali selezioni/hint
670
+ Object.values(squares).forEach(sq => { if (sq && sq.piece) sq.piece = null; });
697
671
  this._clearVisualState();
698
-
699
- // 4. Aggiungi i listener e notifica il cambio
700
672
  this._addListeners();
701
673
  if (this.config.onChange) this.config.onChange(gameStateBefore);
702
-
703
- console.log('Rimozione forzata completata');
704
674
  return;
705
675
  }
706
676
 
707
- console.log('_doUpdateBoardPieces - current FEN:', gameStateBefore);
708
- console.log('_doUpdateBoardPieces - animation:', animation, 'style:', this.config.animationStyle, 'isPositionLoad:', isPositionLoad);
709
-
710
- // Determine which animation style to use
711
- const useSimultaneous = this.config.animationStyle === 'simultaneous';
712
- console.log('_doUpdateBoardPieces - useSimultaneous:', useSimultaneous);
713
-
714
- if (useSimultaneous) {
715
- console.log('Using simultaneous animation');
716
- this._doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad);
717
- } else {
718
- console.log('Using sequential animation');
719
- this._doSequentialUpdate(squares, gameStateBefore, animation);
720
- }
721
- }
722
-
723
- /**
724
- * Performs sequential piece updates (original behavior)
725
- * @private
726
- * @param {Object} squares - All squares
727
- * @param {string} gameStateBefore - Game state before update
728
- * @param {boolean} animation - Whether to animate
729
- */
730
- _doSequentialUpdate(squares, gameStateBefore, animation) {
731
- // Mappa: squareId -> expectedPieceId
732
- const expectedMap = {};
733
- Object.values(squares).forEach(square => {
734
- expectedMap[square.id] = this.positionService.getGamePieceId(square.id);
735
- });
736
-
737
- Object.values(squares).forEach(square => {
738
- const expectedPieceId = expectedMap[square.id];
739
- const currentPiece = square.piece;
740
- const currentPieceId = currentPiece ? currentPiece.getId() : null;
741
-
742
- // Se il pezzo attuale e quello atteso sono identici, non fare nulla
743
- if (currentPieceId === expectedPieceId) {
744
- return;
745
- }
746
-
747
- // Se c'è un pezzo attuale ma non è quello atteso, rimuovilo
748
- if (currentPiece && currentPieceId !== expectedPieceId) {
749
- this.pieceService.removePieceFromSquare(square, animation);
750
- }
751
-
752
- // Se c'è un pezzo atteso ma non è quello attuale, aggiungilo
753
- if (expectedPieceId && currentPieceId !== expectedPieceId) {
754
- const newPiece = this.pieceService.convertPiece(expectedPieceId);
755
- this.pieceService.addPieceOnSquare(
756
- square,
757
- newPiece,
758
- animation,
759
- this._createDragFunction.bind(this)
760
- );
761
- }
762
- });
763
-
764
- this._addListeners();
765
- const gameStateAfter = this.positionService.getGame().fen();
766
- if (gameStateBefore !== gameStateAfter) {
767
- this.config.onChange(gameStateAfter);
768
- }
769
- }
770
-
771
- /**
772
- * Performs simultaneous piece updates
773
- * @private
774
- * @param {Object} squares - All squares
775
- * @param {string} gameStateBefore - Game state before update
776
- * @param {boolean} [isPositionLoad=false] - Whether this is a position load
777
- */
778
- _doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad = false) {
779
- // Matching greedy per distanza minima, robusto
677
+ // --- Matching greedy tra attuale e atteso ---
780
678
  const currentMap = {};
781
679
  const expectedMap = {};
782
-
783
680
  Object.values(squares).forEach(square => {
784
681
  const currentPiece = square.piece;
785
682
  const expectedPieceId = this.positionService.getGamePieceId(square.id);
786
683
  if (currentPiece) {
787
- // Normalizza la chiave come 'color+type' lowercase
788
684
  const key = (currentPiece.color + currentPiece.type).toLowerCase();
789
685
  if (!currentMap[key]) currentMap[key] = [];
790
- currentMap[key].push({ square, id: square.id });
686
+ currentMap[key].push({ square, id: square.id, piece: currentPiece });
791
687
  }
792
688
  if (expectedPieceId) {
793
- // Normalizza la chiave come 'color+type' lowercase
794
689
  const key = expectedPieceId.toLowerCase();
795
690
  if (!expectedMap[key]) expectedMap[key] = [];
796
691
  expectedMap[key].push({ square, id: square.id });
797
692
  }
798
693
  });
799
694
 
800
- let animationsCompleted = 0;
801
- let totalAnimations = 0;
802
- const animationDelay = isPositionLoad ? 0 : this.config.simultaneousAnimationDelay;
803
695
  let animationIndex = 0;
696
+ const animationDelay = isPositionLoad ? 0 : this.config.simultaneousAnimationDelay || 0;
697
+ let totalAnimations = 0;
698
+ let animationsCompleted = 0;
804
699
 
805
- Object.keys(expectedMap).forEach(key => {
806
- totalAnimations += Math.max((currentMap[key] || []).length, expectedMap[key].length);
807
- });
808
-
809
- if (totalAnimations === 0) {
810
- this._addListeners();
811
- const gameStateAfter = this.positionService.getGame().fen();
812
- if (gameStateBefore !== gameStateAfter) {
813
- this.config.onChange(gameStateAfter);
814
- }
815
- return;
816
- }
817
-
818
- const onAnimationComplete = () => {
819
- animationsCompleted++;
820
- if (animationsCompleted === totalAnimations) {
821
- this._addListeners();
822
- const gameStateAfter = this.positionService.getGame().fen();
823
- if (gameStateBefore !== gameStateAfter) {
824
- this.config.onChange(gameStateAfter);
825
- }
826
- }
827
- };
828
-
700
+ // 1. Matching greedy: trova i movimenti
701
+ const moves = [];
702
+ const fromMatched = {};
703
+ const toMatched = {};
704
+ const unchanged = [];
829
705
  Object.keys(expectedMap).forEach(key => {
830
706
  const fromList = (currentMap[key] || []).slice();
831
707
  const toList = expectedMap[key].slice();
832
-
833
- // 1. Costruisci matrice delle distanze
708
+ const localFromMatched = new Array(fromList.length).fill(false);
709
+ const localToMatched = new Array(toList.length).fill(false);
710
+ // Matrice delle distanze
834
711
  const distances = [];
835
712
  for (let i = 0; i < fromList.length; i++) {
836
713
  distances[i] = [];
@@ -839,18 +716,12 @@ class Chessboard {
839
716
  Math.abs(fromList[i].square.col - toList[j].square.col);
840
717
  }
841
718
  }
842
-
843
- // 2. Matching greedy: abbina i più vicini
844
- const fromMatched = new Array(fromList.length).fill(false);
845
- const toMatched = new Array(toList.length).fill(false);
846
- const moves = [];
847
-
848
719
  while (true) {
849
720
  let minDist = Infinity, minI = -1, minJ = -1;
850
721
  for (let i = 0; i < fromList.length; i++) {
851
- if (fromMatched[i]) continue;
722
+ if (localFromMatched[i]) continue;
852
723
  for (let j = 0; j < toList.length; j++) {
853
- if (toMatched[j]) continue;
724
+ if (localToMatched[j]) continue;
854
725
  if (distances[i][j] < minDist) {
855
726
  minDist = distances[i][j];
856
727
  minI = i;
@@ -859,58 +730,111 @@ class Chessboard {
859
730
  }
860
731
  }
861
732
  if (minI === -1 || minJ === -1) break;
862
- // Se la posizione è la stessa, non fare nulla (pezzo unchanged)
863
- if (fromList[minI].square === toList[minJ].square) {
864
- fromMatched[minI] = true;
865
- toMatched[minJ] = true;
733
+ // Se la posizione è la stessa E il Piece è lo stesso oggetto, non fare nulla (pezzo unchanged)
734
+ if (fromList[minI].square === toList[minJ].square && squares[toList[minJ].square.id].piece === fromList[minI].piece) {
735
+ unchanged.push({ square: fromList[minI].square, piece: fromList[minI].piece });
736
+ localFromMatched[minI] = true;
737
+ localToMatched[minJ] = true;
738
+ fromMatched[fromList[minI].square.id] = true;
739
+ toMatched[toList[minJ].square.id] = true;
866
740
  continue;
867
741
  }
868
742
  // Altrimenti, sposta il pezzo
869
- moves.push({ from: fromList[minI].square, to: toList[minJ].square, piece: fromList[minI].square.piece });
870
- fromMatched[minI] = true;
871
- toMatched[minJ] = true;
743
+ moves.push({ from: fromList[minI].square, to: toList[minJ].square, piece: fromList[minI].piece });
744
+ localFromMatched[minI] = true;
745
+ localToMatched[minJ] = true;
746
+ fromMatched[fromList[minI].square.id] = true;
747
+ toMatched[toList[minJ].square.id] = true;
872
748
  }
749
+ });
873
750
 
874
- // 3. Rimuovi i pezzi non abbinati (presenti solo in fromList)
875
- for (let i = 0; i < fromList.length; i++) {
876
- if (!fromMatched[i]) {
877
- setTimeout(() => {
878
- this.pieceService.removePieceFromSquare(fromList[i].square, true, onAnimationComplete);
879
- }, animationIndex * animationDelay);
880
- animationIndex++;
751
+ // 2. Rimozione: pezzi presenti solo in attuale (non matched)
752
+ const removes = [];
753
+ Object.keys(currentMap).forEach(key => {
754
+ currentMap[key].forEach(({ square, piece }) => {
755
+ if (!fromMatched[square.id]) {
756
+ removes.push({ square, piece });
757
+ }
758
+ });
759
+ });
760
+
761
+ // 3. Aggiunta: pezzi presenti solo in atteso (non matched)
762
+ const adds = [];
763
+ Object.keys(expectedMap).forEach(key => {
764
+ expectedMap[key].forEach(({ square, id }) => {
765
+ if (!toMatched[square.id]) {
766
+ adds.push({ square, pieceId: key });
881
767
  }
768
+ });
769
+ });
770
+
771
+ totalAnimations = moves.length + removes.length + adds.length;
772
+ if (totalAnimations === 0) {
773
+ this._addListeners();
774
+ const gameStateAfter = this.positionService.getGame().fen();
775
+ if (gameStateBefore !== gameStateAfter) {
776
+ this.config.onChange(gameStateAfter);
882
777
  }
778
+ return;
779
+ }
883
780
 
884
- // 4. Aggiungi i pezzi non abbinati (presenti solo in toList)
885
- for (let j = 0; j < toList.length; j++) {
886
- if (!toMatched[j]) {
887
- setTimeout(() => {
888
- const newPiece = this.pieceService.convertPiece(key);
889
- this.pieceService.addPieceOnSquare(
890
- toList[j].square,
891
- newPiece,
892
- true,
893
- this._createDragFunction.bind(this),
894
- onAnimationComplete
895
- );
896
- }, animationIndex * animationDelay);
897
- animationIndex++;
781
+ // Debug: logga i pezzi unchanged
782
+ if (unchanged.length > 0) {
783
+ console.debug('[Chessboard] Unchanged pieces:', unchanged.map(u => u.piece.id + '@' + u.square.id));
784
+ }
785
+
786
+ const onAnimationComplete = () => {
787
+ animationsCompleted++;
788
+ if (animationsCompleted === totalAnimations) {
789
+ // Pulizia finale robusta: rimuovi tutti i pezzi orfani dal DOM e dal riferimento JS
790
+ Object.values(this.boardService.getAllSquares()).forEach(square => {
791
+ const expectedPieceId = this.positionService.getGamePieceId(square.id);
792
+ if (!expectedPieceId && typeof square.forceRemoveAllPieces === 'function') {
793
+ square.forceRemoveAllPieces();
794
+ }
795
+ });
796
+ this._addListeners();
797
+ const gameStateAfter = this.positionService.getGame().fen();
798
+ if (gameStateBefore !== gameStateAfter) {
799
+ this.config.onChange(gameStateAfter);
898
800
  }
899
801
  }
802
+ };
900
803
 
901
- // 5. Anima i movimenti
902
- moves.forEach(move => {
903
- setTimeout(() => {
904
- this.pieceService.translatePiece(
905
- move,
906
- false,
907
- true,
908
- this._createDragFunction.bind(this),
909
- onAnimationComplete
910
- );
911
- }, animationIndex * animationDelay);
912
- animationIndex++;
913
- });
804
+ // 4. Esegui tutte le animazioni con delay
805
+ let idx = 0;
806
+ moves.forEach(move => {
807
+ setTimeout(() => {
808
+ this.pieceService.translatePiece(
809
+ move,
810
+ false,
811
+ animation,
812
+ this._createDragFunction.bind(this),
813
+ onAnimationComplete
814
+ );
815
+ }, idx++ * animationDelay);
816
+ });
817
+ removes.forEach(op => {
818
+ setTimeout(() => {
819
+ if (typeof op.square.forceRemoveAllPieces === 'function') {
820
+ op.square.forceRemoveAllPieces();
821
+ onAnimationComplete();
822
+ } else {
823
+ this.pieceService.removePieceFromSquare(op.square, animation, onAnimationComplete);
824
+ }
825
+ }, idx++ * animationDelay);
826
+ });
827
+ adds.forEach(op => {
828
+ setTimeout(() => {
829
+ const newPiece = this.pieceService.convertPiece(op.pieceId);
830
+ this.pieceService.addPieceOnSquare(
831
+ op.square,
832
+ newPiece,
833
+ animation,
834
+ this._createDragFunction.bind(this),
835
+ onAnimationComplete
836
+ );
837
+ }, idx++ * animationDelay);
914
838
  });
915
839
  }
916
840
 
@@ -1267,6 +1191,8 @@ class Chessboard {
1267
1191
  if (this._updateBoardPieces) {
1268
1192
  this._updateBoardPieces(animate, true);
1269
1193
  }
1194
+ // Forza la sincronizzazione dopo setPosition
1195
+ this._updateBoardPieces(true, false);
1270
1196
  return true;
1271
1197
  }
1272
1198
  /**
@@ -1280,7 +1206,10 @@ class Chessboard {
1280
1206
  // Use the default starting position from config or fallback
1281
1207
  const startPosition = this.config && this.config.position ? this.config.position : 'start';
1282
1208
  this._updateBoardPieces(animate);
1283
- return this.setPosition(startPosition, { animate });
1209
+ const result = this.setPosition(startPosition, { animate });
1210
+ // Forza la sincronizzazione dopo reset
1211
+ this._updateBoardPieces(true, false);
1212
+ return result;
1284
1213
  }
1285
1214
  /**
1286
1215
  * Clear the board
@@ -1304,6 +1233,8 @@ class Chessboard {
1304
1233
  if (this._updateBoardPieces) {
1305
1234
  this._updateBoardPieces(animate, true);
1306
1235
  }
1236
+ // Forza la sincronizzazione dopo clear
1237
+ this._updateBoardPieces(true, false);
1307
1238
  return true;
1308
1239
  }
1309
1240
 
@@ -1318,7 +1249,8 @@ class Chessboard {
1318
1249
  const undone = this.positionService.getGame().undo();
1319
1250
  if (undone) {
1320
1251
  this._undoneMoves.push(undone);
1321
- this._updateBoardPieces(opts.animate !== false);
1252
+ // Forza refresh completo di tutti i pezzi dopo undo
1253
+ this._updateBoardPieces(true, true);
1322
1254
  return undone;
1323
1255
  }
1324
1256
  return null;
@@ -1335,7 +1267,8 @@ class Chessboard {
1335
1267
  const moveObj = { from: move.from, to: move.to };
1336
1268
  if (move.promotion) moveObj.promotion = move.promotion;
1337
1269
  const result = this.positionService.getGame().move(moveObj);
1338
- this._updateBoardPieces(opts.animate !== false);
1270
+ // Forza refresh completo di tutti i pezzi dopo redo
1271
+ this._updateBoardPieces(true, true);
1339
1272
  return result;
1340
1273
  }
1341
1274
  return false;
@@ -1354,17 +1287,19 @@ class Chessboard {
1354
1287
  * @returns {string|null}
1355
1288
  */
1356
1289
  getPiece(square) {
1357
- // Restituisce sempre 'wq' (colore prima, tipo dopo, lowercase) o null
1358
- const sq = this.boardService.getSquare(square);
1359
- if (!sq) return null;
1360
- const piece = sq.piece;
1290
+ // Sempre leggi lo stato aggiornato dal boardService
1291
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
1292
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[getPiece] Parametro square non valido');
1293
+ // Forza sync prima di leggere
1294
+ this._updateBoardPieces(false, false);
1295
+ const piece = squareObj.piece;
1361
1296
  if (!piece) return null;
1362
1297
  return (piece.color + piece.type).toLowerCase();
1363
1298
  }
1364
1299
  /**
1365
1300
  * Put a piece on a square
1366
- * @param {string} piece
1367
- * @param {string} square
1301
+ * @param {string|Piece} piece
1302
+ * @param {string|Square} square
1368
1303
  * @param {Object} [opts]
1369
1304
  * @param {boolean} [opts.animate=true]
1370
1305
  * @returns {boolean}
@@ -1375,7 +1310,6 @@ class Chessboard {
1375
1310
  if (typeof piece === 'object' && piece.type && piece.color) {
1376
1311
  pieceStr = (piece.color + piece.type).toLowerCase();
1377
1312
  } else if (typeof piece === 'string' && piece.length === 2) {
1378
- // Accetta sia 'wq' che 'qw', normalizza a 'wq'
1379
1313
  const a = piece[0].toLowerCase();
1380
1314
  const b = piece[1].toLowerCase();
1381
1315
  const types = 'kqrbnp';
@@ -1388,42 +1322,36 @@ class Chessboard {
1388
1322
  throw new Error(`[putPiece] Invalid piece: ${piece}`);
1389
1323
  }
1390
1324
  }
1391
- const validSquare = this.validationService.isValidSquare(square);
1392
- const validPiece = this.validationService.isValidPiece(pieceStr);
1393
- if (!validSquare) throw new Error(`[putPiece] Invalid square: ${square}`);
1394
- if (!validPiece) throw new Error(`[putPiece] Invalid piece: ${pieceStr}`);
1395
- if (!this.positionService || !this.positionService.getGame()) {
1396
- throw new Error('[putPiece] No positionService or game');
1397
- }
1325
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
1326
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[putPiece] Parametro square non valido');
1398
1327
  const pieceObj = this.pieceService.convertPiece(pieceStr);
1399
- const squareObj = this.boardService.getSquare(square);
1400
- if (!squareObj) throw new Error(`[putPiece] Square not found: ${square}`);
1401
- squareObj.piece = pieceObj;
1328
+ if (!pieceObj || typeof pieceObj !== 'object' || !('type' in pieceObj)) throw new Error('[putPiece] Parametro piece non valido');
1329
+ // Aggiorna solo il motore chess.js
1402
1330
  const chessJsPiece = { type: pieceObj.type, color: pieceObj.color };
1403
1331
  const game = this.positionService.getGame();
1404
- const result = game.put(chessJsPiece, square);
1405
- if (!result) throw new Error(`[putPiece] Game.put failed for ${pieceStr} on ${square}`);
1332
+ const result = game.put(chessJsPiece, squareObj.id);
1333
+ if (!result) throw new Error(`[putPiece] Game.put failed for ${pieceStr} on ${squareObj.id}`);
1334
+ // Non aggiornare direttamente square.piece!
1335
+ // Riallinea la board JS allo stato del motore
1406
1336
  this._updateBoardPieces(animate);
1407
1337
  return true;
1408
1338
  }
1409
1339
  /**
1410
1340
  * Remove a piece from a square
1411
- * @param {string} square
1341
+ * @param {string|Square} square
1412
1342
  * @param {Object} [opts]
1413
1343
  * @param {boolean} [opts.animate=true]
1414
1344
  * @returns {string|null}
1415
1345
  */
1416
1346
  removePiece(square, opts = {}) {
1417
1347
  const animate = opts.animate !== undefined ? opts.animate : true;
1418
- if (!this.validationService.isValidSquare(square)) {
1419
- throw new Error(`[removePiece] Invalid square: ${square}`);
1420
- }
1421
- const squareObj = this.boardService.getSquare(square);
1422
- if (!squareObj) return true;
1423
- if (!squareObj.piece) return true;
1424
- squareObj.piece = null;
1348
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
1349
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[removePiece] Parametro square non valido');
1350
+ // Aggiorna solo il motore chess.js
1425
1351
  const game = this.positionService.getGame();
1426
- game.remove(square);
1352
+ game.remove(squareObj.id);
1353
+ // Non aggiornare direttamente square.piece!
1354
+ // Riallinea la board JS allo stato del motore
1427
1355
  this._updateBoardPieces(animate);
1428
1356
  return true;
1429
1357
  }
@@ -1488,28 +1416,32 @@ class Chessboard {
1488
1416
  // --- HIGHLIGHTING & UI ---
1489
1417
  /**
1490
1418
  * Highlight a square
1491
- * @param {string} square
1419
+ * @param {string|Square} square
1492
1420
  * @param {Object} [opts]
1493
1421
  */
1494
1422
  highlight(square, opts = {}) {
1495
- if (!this.validationService.isValidSquare(square)) return;
1423
+ // API: accetta id, converte subito in oggetto
1424
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
1425
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[highlight] Parametro square non valido');
1496
1426
  if (this.boardService && this.boardService.highlightSquare) {
1497
- this.boardService.highlightSquare(square, opts);
1427
+ this.boardService.highlightSquare(squareObj, opts);
1498
1428
  } else if (this.eventService && this.eventService.highlightSquare) {
1499
- this.eventService.highlightSquare(square, opts);
1429
+ this.eventService.highlightSquare(squareObj, opts);
1500
1430
  }
1501
1431
  }
1502
1432
  /**
1503
1433
  * Remove highlight from a square
1504
- * @param {string} square
1434
+ * @param {string|Square} square
1505
1435
  * @param {Object} [opts]
1506
1436
  */
1507
1437
  dehighlight(square, opts = {}) {
1508
- if (!this.validationService.isValidSquare(square)) return;
1438
+ // API: accetta id, converte subito in oggetto
1439
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
1440
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[dehighlight] Parametro square non valido');
1509
1441
  if (this.boardService && this.boardService.dehighlightSquare) {
1510
- this.boardService.dehighlightSquare(square, opts);
1442
+ this.boardService.dehighlightSquare(squareObj, opts);
1511
1443
  } else if (this.eventService && this.eventService.dehighlightSquare) {
1512
- this.eventService.dehighlightSquare(square, opts);
1444
+ this.eventService.dehighlightSquare(squareObj, opts);
1513
1445
  }
1514
1446
  }
1515
1447
 
@@ -1534,6 +1466,8 @@ class Chessboard {
1534
1466
  * @returns {boolean}
1535
1467
  */
1536
1468
  isGameOver() {
1469
+ // Forza sync prima di interrogare il motore
1470
+ this._updateBoardPieces(false, false);
1537
1471
  const game = this.positionService.getGame();
1538
1472
  if (!game) return false;
1539
1473
  if (game.isGameOver) return game.isGameOver();
@@ -1837,23 +1771,6 @@ class Chessboard {
1837
1771
  }
1838
1772
  }
1839
1773
 
1840
- /**
1841
- * Gets or sets the animation style
1842
- * @param {string} [style] - New animation style ('sequential' or 'simultaneous')
1843
- * @returns {string} Current animation style
1844
- */
1845
- animationStyle(style) {
1846
- if (style === undefined) {
1847
- return this.config.animationStyle;
1848
- }
1849
-
1850
- if (this.validationService.isValidAnimationStyle(style)) {
1851
- this.config.animationStyle = style;
1852
- }
1853
-
1854
- return this.config.animationStyle;
1855
- }
1856
-
1857
1774
  /**
1858
1775
  * Gets or sets the simultaneous animation delay
1859
1776
  * @param {number} [delay] - New delay in milliseconds
@@ -1903,7 +1820,7 @@ class Chessboard {
1903
1820
  dehighlightSquare(square) {
1904
1821
  return this.boardService.dehighlight(square);
1905
1822
  }
1906
- forceSync() { this._updateBoardPieces(true, true); }
1823
+ forceSync() { this._updateBoardPieces(true, true); this._updateBoardPieces(true, false); }
1907
1824
 
1908
1825
  // Metodi mancanti che causano fallimenti nei test
1909
1826
  /**
@@ -1916,37 +1833,37 @@ class Chessboard {
1916
1833
  movePiece(move, opts = {}) {
1917
1834
  const animate = opts.animate !== undefined ? opts.animate : true;
1918
1835
 
1919
- // Parse move string if needed
1920
- let fromSquare, toSquare, promotion;
1836
+ // --- API: accetta id/stringhe, ma converte subito in oggetti ---
1837
+ let fromSquareObj, toSquareObj, promotion;
1921
1838
  if (typeof move === 'string') {
1922
1839
  if (move.length === 4) {
1923
- fromSquare = move.substring(0, 2);
1924
- toSquare = move.substring(2, 4);
1840
+ fromSquareObj = this.boardService.getSquare(move.substring(0, 2));
1841
+ toSquareObj = this.boardService.getSquare(move.substring(2, 4));
1925
1842
  } else if (move.length === 5) {
1926
- fromSquare = move.substring(0, 2);
1927
- toSquare = move.substring(2, 4);
1843
+ fromSquareObj = this.boardService.getSquare(move.substring(0, 2));
1844
+ toSquareObj = this.boardService.getSquare(move.substring(2, 4));
1928
1845
  promotion = move.substring(4, 5);
1929
1846
  } else {
1930
1847
  throw new Error(`Invalid move format: ${move}`);
1931
1848
  }
1932
1849
  } else if (typeof move === 'object' && move.from && move.to) {
1933
- fromSquare = move.from;
1934
- toSquare = move.to;
1850
+ // Se sono id, converto in oggetti; se sono già oggetti, li uso direttamente
1851
+ fromSquareObj = typeof move.from === 'string' ? this.boardService.getSquare(move.from) : move.from;
1852
+ toSquareObj = typeof move.to === 'string' ? this.boardService.getSquare(move.to) : move.to;
1935
1853
  promotion = move.promotion;
1936
1854
  } else {
1937
1855
  throw new Error(`Invalid move: ${move}`);
1938
1856
  }
1939
1857
 
1940
- // Get square objects
1941
- const fromSquareObj = this.boardService.getSquare(fromSquare);
1942
- const toSquareObj = this.boardService.getSquare(toSquare);
1943
-
1944
1858
  if (!fromSquareObj || !toSquareObj) {
1945
- throw new Error(`Invalid squares: ${fromSquare} or ${toSquare}`);
1859
+ throw new Error(`Invalid squares: ${move.from || move.substring(0, 2)} or ${move.to || move.substring(2, 4)}`);
1946
1860
  }
1947
1861
 
1948
- // Execute the move
1949
- return this._onMove(fromSquareObj, toSquareObj, promotion, animate);
1862
+ // --- Internamente: lavora solo con oggetti ---
1863
+ const result = this._onMove(fromSquareObj, toSquareObj, promotion, animate);
1864
+ // Dopo ogni mossa, forza la sincronizzazione della board
1865
+ this._updateBoardPieces(true, false);
1866
+ return result;
1950
1867
  }
1951
1868
 
1952
1869
  // Aliases for backward compatibility