@alepot55/chessboardjs 2.3.4 → 2.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/rollup.config.js +2 -1
- package/dist/chessboard.cjs.js +446 -471
- package/dist/chessboard.css +28 -6
- package/dist/chessboard.esm.js +446 -471
- package/dist/chessboard.iife.js +447 -471
- package/dist/chessboard.umd.js +446 -471
- package/package.json +6 -8
- package/src/components/Piece.js +11 -11
- package/src/components/Square.js +1 -1
- package/src/core/Chessboard.js +183 -266
- package/src/core/ChessboardConfig.js +0 -16
- package/src/core/ChessboardFactory.js +0 -5
- package/src/errors/messages.js +0 -1
- package/src/services/BoardService.js +20 -1
- package/src/services/EventService.js +131 -45
- package/src/services/MoveService.js +81 -94
- package/src/services/PieceService.js +18 -11
- package/src/services/ValidationService.js +1 -14
- package/src/styles/index.css +8 -0
- package/src/utils/validation.js +1 -5
- package/tests/unit/chessboard-config-animations.test.js +0 -9
- package/tests/unit/chessboard-robust.test.js +0 -44
- package/tools/build-html-examples.cjs +80 -0
- package/tools/build-html-examples.js +78 -0
- package/chessboard.bundle.js +0 -4072
package/src/core/Chessboard.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
653
|
+
* Aggiorna i pezzi sulla scacchiera con animazione e delay configurabile (greedy matching)
|
|
654
654
|
* @private
|
|
655
|
-
* @param {boolean} [animation=false] -
|
|
656
|
-
* @param {boolean} [isPositionLoad=false] -
|
|
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
|
-
|
|
660
|
-
if (this._isPromoting)
|
|
661
|
-
|
|
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
|
-
|
|
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
|
-
|
|
806
|
-
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
|
|
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
|
-
|
|
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 (
|
|
722
|
+
if (localFromMatched[i]) continue;
|
|
852
723
|
for (let j = 0; j < toList.length; j++) {
|
|
853
|
-
if (
|
|
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
|
-
|
|
865
|
-
|
|
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].
|
|
870
|
-
|
|
871
|
-
|
|
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
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1358
|
-
const
|
|
1359
|
-
if (!
|
|
1360
|
-
|
|
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
|
|
1392
|
-
|
|
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
|
-
|
|
1400
|
-
|
|
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,
|
|
1405
|
-
if (!result) throw new Error(`[putPiece] Game.put failed for ${pieceStr} on ${
|
|
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
|
-
|
|
1419
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
1427
|
+
this.boardService.highlightSquare(squareObj, opts);
|
|
1498
1428
|
} else if (this.eventService && this.eventService.highlightSquare) {
|
|
1499
|
-
this.eventService.highlightSquare(
|
|
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
|
-
|
|
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(
|
|
1442
|
+
this.boardService.dehighlightSquare(squareObj, opts);
|
|
1511
1443
|
} else if (this.eventService && this.eventService.dehighlightSquare) {
|
|
1512
|
-
this.eventService.dehighlightSquare(
|
|
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
|
-
//
|
|
1920
|
-
let
|
|
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
|
-
|
|
1924
|
-
|
|
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
|
-
|
|
1927
|
-
|
|
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
|
-
|
|
1934
|
-
|
|
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: ${
|
|
1859
|
+
throw new Error(`Invalid squares: ${move.from || move.substring(0, 2)} or ${move.to || move.substring(2, 4)}`);
|
|
1946
1860
|
}
|
|
1947
1861
|
|
|
1948
|
-
//
|
|
1949
|
-
|
|
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
|