@alepot55/chessboardjs 2.3.6 → 2.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -128,6 +128,7 @@ const ERROR_MESSAGES = Object.freeze({
128
128
  invalid_fadeTime: 'Invalid fadeTime: ',
129
129
  invalid_fadeAnimation: 'Invalid fadeAnimation: ',
130
130
  invalid_ratio: 'Invalid ratio: ',
131
+ invalid_animationStyle: 'Invalid animationStyle: ',
131
132
  animation_failed: 'Animation failed: ',
132
133
 
133
134
  // Event handlers
@@ -379,6 +380,7 @@ const VALID_VALUES = Object.freeze({
379
380
  movableColors: ['w', 'b', 'white', 'black', 'both', 'none'],
380
381
  dropOffBoard: ['snapback', 'trash'],
381
382
  easingTypes: ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'],
383
+ animationStyles: ['sequential', 'simultaneous'],
382
384
  modes: ['normal', 'creative', 'analysis'],
383
385
  promotionPieces: ['q', 'r', 'b', 'n', 'Q', 'R', 'B', 'N']
384
386
  });
@@ -707,6 +709,14 @@ class ValidationService {
707
709
  return this._validValues.easingTypes.includes(easing);
708
710
  }
709
711
 
712
+ /**
713
+ * Validates animation style
714
+ * @param {string} style - Animation style to validate
715
+ * @returns {boolean} True if valid
716
+ */
717
+ isValidAnimationStyle(style) {
718
+ return this._validValues.animationStyles.includes(style);
719
+ }
710
720
 
711
721
  /**
712
722
  * Validates CSS color format
@@ -903,7 +913,11 @@ class ValidationService {
903
913
  if (config.dropOffBoard && !this.isValidDropOffBoard(config.dropOffBoard)) {
904
914
  errors.push(ERROR_MESSAGES.invalid_dropOffBoard + config.dropOffBoard);
905
915
  }
906
-
916
+
917
+ if (config.animationStyle && !this.isValidAnimationStyle(config.animationStyle)) {
918
+ errors.push(ERROR_MESSAGES.invalid_animationStyle + config.animationStyle);
919
+ }
920
+
907
921
  // Validate callbacks
908
922
  const callbacks = ['onMove', 'onMoveEnd', 'onChange', 'onDragStart', 'onDragMove', 'onDrop', 'onSnapbackEnd'];
909
923
  for (const callback of callbacks) {
@@ -1081,7 +1095,8 @@ const DEFAULT_CONFIG$1 = Object.freeze({
1081
1095
  fadeAnimation: 'ease',
1082
1096
  ratio: 0.9,
1083
1097
  piecesPath: '../assets/themes/default',
1084
- simultaneousAnimationDelay: 100,
1098
+ animationStyle: 'simultaneous',
1099
+ simultaneousAnimationDelay: 0,
1085
1100
  onMove: () => true,
1086
1101
  onMoveEnd: () => true,
1087
1102
  onChange: () => true,
@@ -1115,19 +1130,19 @@ class ChessboardConfig {
1115
1130
  constructor(settings = {}) {
1116
1131
  // Initialize validation service
1117
1132
  this._validationService = new ValidationService();
1118
-
1133
+
1119
1134
  // Validate input
1120
1135
  this._validateInput(settings);
1121
-
1136
+
1122
1137
  // Merge with defaults
1123
1138
  const config = this._mergeWithDefaults(settings);
1124
-
1139
+
1125
1140
  // Process and validate configuration
1126
1141
  this._processConfiguration(config);
1127
-
1142
+
1128
1143
  // Set CSS properties
1129
1144
  this._setCSSProperties(config);
1130
-
1145
+
1131
1146
  // Configure mode-specific settings
1132
1147
  this._configureModeSettings();
1133
1148
  }
@@ -1142,7 +1157,7 @@ class ChessboardConfig {
1142
1157
  if (settings !== null && typeof settings !== 'object') {
1143
1158
  throw new ConfigurationError('Settings must be an object', 'settings', settings);
1144
1159
  }
1145
-
1160
+
1146
1161
  // Validate using validation service
1147
1162
  try {
1148
1163
  this._validationService.validateConfig(settings);
@@ -1176,7 +1191,7 @@ class ChessboardConfig {
1176
1191
  this.size = config.size;
1177
1192
  this.movableColors = config.movableColors;
1178
1193
  this.piecesPath = config.piecesPath;
1179
-
1194
+
1180
1195
  // Event handlers
1181
1196
  this.onMove = this._validateCallback(config.onMove);
1182
1197
  this.onMoveEnd = this._validateCallback(config.onMoveEnd);
@@ -1204,8 +1219,9 @@ class ChessboardConfig {
1204
1219
  this.snapbackTime = this._setTime(config.snapbackTime);
1205
1220
  this.dropCenterTime = this._setTime(config.dropCenterTime);
1206
1221
  this.fadeTime = this._setTime(config.fadeTime);
1207
-
1222
+
1208
1223
  // Animation style properties
1224
+ this.animationStyle = this._validateAnimationStyle(config.animationStyle);
1209
1225
  this.simultaneousAnimationDelay = this._validateDelay(config.simultaneousAnimationDelay);
1210
1226
  }
1211
1227
 
@@ -1266,6 +1282,19 @@ class ChessboardConfig {
1266
1282
  return callback;
1267
1283
  }
1268
1284
 
1285
+ /**
1286
+ * Validates animation style
1287
+ * @private
1288
+ * @param {string} style - Animation style
1289
+ * @returns {string} Validated style
1290
+ * @throws {ConfigurationError} If style is invalid
1291
+ */
1292
+ _validateAnimationStyle(style) {
1293
+ if (!this._validationService.isValidAnimationStyle(style)) {
1294
+ throw new ConfigurationError('Invalid animation style', 'animationStyle', style);
1295
+ }
1296
+ return style;
1297
+ }
1269
1298
 
1270
1299
  /**
1271
1300
  * Validates animation delay
@@ -1323,11 +1352,11 @@ class ChessboardConfig {
1323
1352
  }
1324
1353
  return value;
1325
1354
  }
1326
-
1355
+
1327
1356
  if (typeof value === 'string' && value in ANIMATION_TIMES) {
1328
1357
  return ANIMATION_TIMES[value];
1329
1358
  }
1330
-
1359
+
1331
1360
  throw new ConfigurationError('Invalid time value', 'time', value);
1332
1361
  }
1333
1362
 
@@ -1342,11 +1371,11 @@ class ChessboardConfig {
1342
1371
  if (typeof value === 'boolean') {
1343
1372
  return value;
1344
1373
  }
1345
-
1374
+
1346
1375
  if (value in BOOLEAN_VALUES) {
1347
1376
  return BOOLEAN_VALUES[value];
1348
1377
  }
1349
-
1378
+
1350
1379
  throw new ConfigurationError('Invalid boolean value', 'boolean', value);
1351
1380
  }
1352
1381
 
@@ -1362,17 +1391,17 @@ class ChessboardConfig {
1362
1391
  if (typeof value === 'boolean') {
1363
1392
  return value ? TRANSITION_FUNCTIONS.ease : null;
1364
1393
  }
1365
-
1394
+
1366
1395
  // Handle string values
1367
1396
  if (typeof value === 'string' && value in TRANSITION_FUNCTIONS) {
1368
1397
  return TRANSITION_FUNCTIONS[value];
1369
1398
  }
1370
-
1399
+
1371
1400
  // Handle null/undefined
1372
1401
  if (value === null || value === undefined) {
1373
1402
  return null;
1374
1403
  }
1375
-
1404
+
1376
1405
  throw new ConfigurationError('Invalid transition function', 'transitionFunction', value);
1377
1406
  }
1378
1407
 
@@ -1403,6 +1432,7 @@ class ChessboardConfig {
1403
1432
  fadeTime: this.fadeTime,
1404
1433
  fadeAnimation: this.fadeAnimation,
1405
1434
  piecesPath: this.piecesPath,
1435
+ animationStyle: this.animationStyle,
1406
1436
  simultaneousAnimationDelay: this.simultaneousAnimationDelay,
1407
1437
  onlyLegalMoves: this.onlyLegalMoves
1408
1438
  };
@@ -1424,7 +1454,7 @@ class ChessboardConfig {
1424
1454
 
1425
1455
  // Apply updates
1426
1456
  const newConfig = Object.assign({}, this.toObject(), updates);
1427
-
1457
+
1428
1458
  // Re-process configuration
1429
1459
  this._processConfiguration(newConfig);
1430
1460
  this._setCSSProperties(newConfig);
@@ -1647,10 +1677,22 @@ class Piece {
1647
1677
 
1648
1678
  setDrag(f) {
1649
1679
  if (!this.element) { console.debug(`[Piece] setDrag: ${this.id} - element is null`); return; }
1680
+ // Remove previous handlers
1681
+ this.element.onmousedown = null;
1682
+ this.element.ontouchstart = null;
1683
+ this.element.ondragstart = null;
1684
+ if (window.PointerEvent) {
1685
+ this.element.onpointerdown = null;
1686
+ }
1687
+ // Set new handlers
1650
1688
  this.element.ondragstart = (e) => { e.preventDefault(); };
1651
1689
  this.element.onmousedown = f;
1652
1690
  this.element.ontouchstart = f; // Drag touch
1653
- console.debug(`[Piece] setDrag: ${this.id}`);
1691
+ if (window.PointerEvent) {
1692
+ this.element.onpointerdown = f;
1693
+ console.debug(`[Piece] setDrag: pointerdown set for ${this.id}`);
1694
+ }
1695
+ console.debug(`[Piece] setDrag: mousedown/ontouchstart set for ${this.id}`);
1654
1696
  }
1655
1697
 
1656
1698
  destroy() {
@@ -7073,7 +7115,7 @@ class PositionService {
7073
7115
  * Implements the Facade pattern to provide a unified interface
7074
7116
  * @class
7075
7117
  */
7076
- let Chessboard$1 = class Chessboard {
7118
+ class Chessboard {
7077
7119
  /**
7078
7120
  * Creates a new Chessboard instance
7079
7121
  * @param {Object} config - Configuration object
@@ -7098,8 +7140,6 @@ let Chessboard$1 = class Chessboard {
7098
7140
  } catch (error) {
7099
7141
  this._handleConstructorError(error);
7100
7142
  }
7101
- this._undoneMoves = [];
7102
- this._updateBoardPieces(true, true); // Forza popolamento DOM subito
7103
7143
  }
7104
7144
 
7105
7145
  /**
@@ -7143,31 +7183,6 @@ let Chessboard$1 = class Chessboard {
7143
7183
  }
7144
7184
  }
7145
7185
 
7146
- /**
7147
- * Cleans up any partially initialized resources (safe to call multiple times)
7148
- * @private
7149
- */
7150
- _cleanup() {
7151
- // Remove event listeners if present
7152
- if (this.eventService && typeof this.eventService.removeListeners === 'function') {
7153
- this.eventService.removeListeners();
7154
- }
7155
- // Clear timeouts
7156
- if (this._updateTimeout) {
7157
- clearTimeout(this._updateTimeout);
7158
- this._updateTimeout = null;
7159
- }
7160
- // Null all services
7161
- this.validationService = null;
7162
- this.coordinateService = null;
7163
- this.positionService = null;
7164
- this.boardService = null;
7165
- this.pieceService = null;
7166
- this.animationService = null;
7167
- this.moveService = null;
7168
- this.eventService = null;
7169
- }
7170
-
7171
7186
  /**
7172
7187
  * Initializes all services
7173
7188
  * @private
@@ -7236,23 +7251,8 @@ let Chessboard$1 = class Chessboard {
7236
7251
  /**
7237
7252
  * Builds the board DOM structure
7238
7253
  * @private
7239
- * Best practice: always remove squares (destroy JS/DOM) before clearing the board container.
7240
7254
  */
7241
7255
  _buildBoard() {
7242
- console.log('CHIAMATO: _buildBoard');
7243
- if (this._isUndoRedo) {
7244
- console.log('SKIP _buildBoard per undo/redo');
7245
- return;
7246
- }
7247
- // Forza la pulizia completa del contenitore board (DOM)
7248
- const boardContainer = document.getElementById(this.config.id_div);
7249
- if (boardContainer) boardContainer.innerHTML = '';
7250
- // Force remove all pieces from all squares (no animation, best practice)
7251
- if (this.boardService && this.boardService.squares) {
7252
- Object.values(this.boardService.squares).forEach(sq => sq && sq.forceRemoveAllPieces && sq.forceRemoveAllPieces());
7253
- }
7254
- if (this.boardService && this.boardService.removeSquares) this.boardService.removeSquares();
7255
- if (this.boardService && this.boardService.removeBoard) this.boardService.removeBoard();
7256
7256
  this.boardService.buildBoard();
7257
7257
  }
7258
7258
 
@@ -7261,14 +7261,6 @@ let Chessboard$1 = class Chessboard {
7261
7261
  * @private
7262
7262
  */
7263
7263
  _buildSquares() {
7264
- console.log('CHIAMATO: _buildSquares');
7265
- if (this._isUndoRedo) {
7266
- console.log('SKIP _buildSquares per undo/redo');
7267
- return;
7268
- }
7269
- if (this.boardService && this.boardService.removeSquares) {
7270
- this.boardService.removeSquares();
7271
- }
7272
7264
  this.boardService.buildSquares((row, col) => {
7273
7265
  return this.coordinateService.realCoord(row, col);
7274
7266
  });
@@ -7473,7 +7465,7 @@ let Chessboard$1 = class Chessboard {
7473
7465
  const capturedPiece = move.to.piece;
7474
7466
 
7475
7467
  // For castle moves in simultaneous mode, we need to coordinate both animations
7476
- if (isCastleMove) {
7468
+ if (isCastleMove && this.config.animationStyle === 'simultaneous') {
7477
7469
  // Start king animation
7478
7470
  this.pieceService.translatePiece(
7479
7471
  move,
@@ -7638,7 +7630,6 @@ let Chessboard$1 = class Chessboard {
7638
7630
  * @param {boolean} [isPositionLoad=false] - Whether this is a position load
7639
7631
  */
7640
7632
  _updateBoardPieces(animation = false, isPositionLoad = false) {
7641
- console.log('CHIAMATO: _updateBoardPieces', { animation, isPositionLoad, isUndoRedo: this._isUndoRedo });
7642
7633
  // Check if services are available
7643
7634
  if (!this.positionService || !this.moveService || !this.eventService) {
7644
7635
  console.log('Cannot update board pieces - services not available');
@@ -7698,125 +7689,112 @@ let Chessboard$1 = class Chessboard {
7698
7689
  }
7699
7690
 
7700
7691
  /**
7701
- * Aggiorna i pezzi sulla scacchiera con animazione e delay configurabile (greedy matching)
7692
+ * Performs the actual board update
7702
7693
  * @private
7703
- * @param {boolean} [animation=false] - Se animare
7704
- * @param {boolean} [isPositionLoad=false] - Se è un caricamento posizione (delay 0)
7694
+ * @param {boolean} [animation=false] - Whether to animate
7695
+ * @param {boolean} [isPositionLoad=false] - Whether this is a position load (affects delay)
7705
7696
  */
7706
7697
  _doUpdateBoardPieces(animation = false, isPositionLoad = false) {
7707
- if (this._isDragging) return;
7708
- if (this._isPromoting) return;
7709
- if (!this.positionService || !this.positionService.getGame()) return;
7698
+ // Skip update if we're in the middle of a promotion
7699
+ if (this._isPromoting) {
7700
+ console.log('Skipping board update during promotion');
7701
+ return;
7702
+ }
7703
+
7704
+ // Check if services are available
7705
+ if (!this.positionService || !this.positionService.getGame()) {
7706
+ console.log('Cannot update board pieces - position service not available');
7707
+ return;
7708
+ }
7709
+
7710
7710
  const squares = this.boardService.getAllSquares();
7711
7711
  const gameStateBefore = this.positionService.getGame().fen();
7712
- if (/^8\/8\/8\/8\/8\/8\/8\/8/.test(gameStateBefore)) {
7713
- const boardContainer = document.getElementById(this.config.id_div);
7714
- if (boardContainer) {
7715
- const pieceElements = boardContainer.querySelectorAll('.piece');
7716
- pieceElements.forEach(element => element.remove());
7717
- }
7718
- Object.values(squares).forEach(sq => { if (sq && sq.piece) sq.piece = null; });
7719
- this._clearVisualState();
7720
- this._addListeners();
7721
- if (this.config.onChange) this.config.onChange(gameStateBefore);
7722
- return;
7712
+
7713
+ console.log('_doUpdateBoardPieces - current FEN:', gameStateBefore);
7714
+ console.log('_doUpdateBoardPieces - animation:', animation, 'style:', this.config.animationStyle, 'isPositionLoad:', isPositionLoad);
7715
+
7716
+ // Determine which animation style to use
7717
+ const useSimultaneous = this.config.animationStyle === 'simultaneous';
7718
+ console.log('_doUpdateBoardPieces - useSimultaneous:', useSimultaneous);
7719
+
7720
+ if (useSimultaneous) {
7721
+ console.log('Using simultaneous animation');
7722
+ this._doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad);
7723
+ } else {
7724
+ console.log('Using sequential animation');
7725
+ this._doSequentialUpdate(squares, gameStateBefore, animation);
7723
7726
  }
7727
+ }
7724
7728
 
7725
- // --- Matching greedy tra attuale e atteso ---
7726
- const currentMap = {};
7727
- const expectedMap = {};
7729
+ /**
7730
+ * Performs sequential piece updates (original behavior)
7731
+ * @private
7732
+ * @param {Object} squares - All squares
7733
+ * @param {string} gameStateBefore - Game state before update
7734
+ * @param {boolean} animation - Whether to animate
7735
+ */
7736
+ _doSequentialUpdate(squares, gameStateBefore, animation) {
7737
+ // Update each square sequentially
7728
7738
  Object.values(squares).forEach(square => {
7729
- const currentPiece = square.piece;
7730
7739
  const expectedPieceId = this.positionService.getGamePieceId(square.id);
7731
- if (currentPiece) {
7732
- const key = (currentPiece.color + currentPiece.type).toLowerCase();
7733
- if (!currentMap[key]) currentMap[key] = [];
7734
- currentMap[key].push({ square, id: square.id, piece: currentPiece });
7735
- }
7736
- if (expectedPieceId) {
7737
- const key = expectedPieceId.toLowerCase();
7738
- if (!expectedMap[key]) expectedMap[key] = [];
7739
- expectedMap[key].push({ square, id: square.id });
7740
- }
7741
- });
7742
- const animationDelay = isPositionLoad ? 0 : this.config.simultaneousAnimationDelay || 0;
7743
- let totalAnimations = 0;
7744
- let animationsCompleted = 0;
7740
+ const currentPiece = square.piece;
7741
+ const currentPieceId = currentPiece ? currentPiece.getId() : null;
7745
7742
 
7746
- // 1. Matching greedy: trova i movimenti
7747
- const moves = [];
7748
- const fromMatched = {};
7749
- const toMatched = {};
7750
- const unchanged = [];
7751
- Object.keys(expectedMap).forEach(key => {
7752
- const fromList = (currentMap[key] || []).slice();
7753
- const toList = expectedMap[key].slice();
7754
- const localFromMatched = new Array(fromList.length).fill(false);
7755
- const localToMatched = new Array(toList.length).fill(false);
7756
- // Matrice delle distanze
7757
- const distances = [];
7758
- for (let i = 0; i < fromList.length; i++) {
7759
- distances[i] = [];
7760
- for (let j = 0; j < toList.length; j++) {
7761
- distances[i][j] = Math.abs(fromList[i].square.row - toList[j].square.row) +
7762
- Math.abs(fromList[i].square.col - toList[j].square.col);
7763
- }
7764
- }
7765
- while (true) {
7766
- let minDist = Infinity, minI = -1, minJ = -1;
7767
- for (let i = 0; i < fromList.length; i++) {
7768
- if (localFromMatched[i]) continue;
7769
- for (let j = 0; j < toList.length; j++) {
7770
- if (localToMatched[j]) continue;
7771
- if (distances[i][j] < minDist) {
7772
- minDist = distances[i][j];
7773
- minI = i;
7774
- minJ = j;
7775
- }
7743
+ // Log only for squares that are changing
7744
+ if (currentPieceId !== expectedPieceId) {
7745
+ console.log(`_doSequentialUpdate - ${square.id}: ${currentPieceId} -> ${expectedPieceId}`);
7746
+
7747
+ // Check if we already have the correct piece (from promotion)
7748
+ if (currentPiece && currentPiece.getId() === expectedPieceId) {
7749
+ console.log(`Piece ${expectedPieceId} already correctly placed on ${square.id}`);
7750
+ } else {
7751
+ // Remove current piece if exists
7752
+ if (currentPiece) {
7753
+ this.pieceService.removePieceFromSquare(square, animation);
7754
+ }
7755
+
7756
+ // Add new piece if needed
7757
+ if (expectedPieceId) {
7758
+ const newPiece = this.pieceService.convertPiece(expectedPieceId);
7759
+ this.pieceService.addPieceOnSquare(
7760
+ square,
7761
+ newPiece,
7762
+ animation,
7763
+ this._createDragFunction.bind(this)
7764
+ );
7776
7765
  }
7777
7766
  }
7778
- if (minI === -1 || minJ === -1) break;
7779
- // Se la posizione è la stessa E il Piece è lo stesso oggetto, non fare nulla (pezzo unchanged)
7780
- if (fromList[minI].square === toList[minJ].square && squares[toList[minJ].square.id].piece === fromList[minI].piece) {
7781
- unchanged.push({ square: fromList[minI].square, piece: fromList[minI].piece });
7782
- localFromMatched[minI] = true;
7783
- localToMatched[minJ] = true;
7784
- fromMatched[fromList[minI].square.id] = true;
7785
- toMatched[toList[minJ].square.id] = true;
7786
- continue;
7787
- }
7788
- // Altrimenti, sposta il pezzo
7789
- moves.push({ from: fromList[minI].square, to: toList[minJ].square, piece: fromList[minI].piece });
7790
- localFromMatched[minI] = true;
7791
- localToMatched[minJ] = true;
7792
- fromMatched[fromList[minI].square.id] = true;
7793
- toMatched[toList[minJ].square.id] = true;
7794
7767
  }
7795
7768
  });
7796
7769
 
7797
- // 2. Rimozione: pezzi presenti solo in attuale (non matched)
7798
- const removes = [];
7799
- Object.keys(currentMap).forEach(key => {
7800
- currentMap[key].forEach(({ square, piece }) => {
7801
- if (!fromMatched[square.id]) {
7802
- removes.push({ square, piece });
7803
- }
7804
- });
7805
- });
7770
+ // Re-add listeners after updating pieces to ensure hover events work correctly
7771
+ this._addListeners();
7806
7772
 
7807
- // 3. Aggiunta: pezzi presenti solo in atteso (non matched)
7808
- const adds = [];
7809
- Object.keys(expectedMap).forEach(key => {
7810
- expectedMap[key].forEach(({ square, id }) => {
7811
- if (!toMatched[square.id]) {
7812
- adds.push({ square, pieceId: key });
7813
- }
7814
- });
7815
- });
7773
+ // Trigger change event if position changed
7774
+ const gameStateAfter = this.positionService.getGame().fen();
7775
+ if (gameStateBefore !== gameStateAfter) {
7776
+ this.config.onChange(gameStateAfter);
7777
+ }
7778
+ }
7816
7779
 
7817
- totalAnimations = moves.length + removes.length + adds.length;
7818
- if (totalAnimations === 0) {
7780
+ /**
7781
+ * Performs simultaneous piece updates
7782
+ * @private
7783
+ * @param {Object} squares - All squares
7784
+ * @param {string} gameStateBefore - Game state before update
7785
+ * @param {boolean} [isPositionLoad=false] - Whether this is a position load
7786
+ */
7787
+ _doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad = false) {
7788
+ console.log('_doSimultaneousUpdate - Starting simultaneous update');
7789
+
7790
+ // Analyze what changes need to be made
7791
+ const changeAnalysis = this._analyzePositionChanges(squares);
7792
+
7793
+ if (changeAnalysis.totalChanges === 0) {
7794
+ console.log('_doSimultaneousUpdate - No changes needed, returning');
7819
7795
  this._addListeners();
7796
+
7797
+ // Trigger change event if position changed
7820
7798
  const gameStateAfter = this.positionService.getGame().fen();
7821
7799
  if (gameStateBefore !== gameStateAfter) {
7822
7800
  this.config.onChange(gameStateAfter);
@@ -7824,64 +7802,10 @@ let Chessboard$1 = class Chessboard {
7824
7802
  return;
7825
7803
  }
7826
7804
 
7827
- // Debug: logga i pezzi unchanged
7828
- if (unchanged.length > 0) {
7829
- console.debug('[Chessboard] Unchanged pieces:', unchanged.map(u => u.piece.id + '@' + u.square.id));
7830
- }
7831
-
7832
- const onAnimationComplete = () => {
7833
- animationsCompleted++;
7834
- if (animationsCompleted === totalAnimations) {
7835
- // Pulizia finale robusta: rimuovi tutti i pezzi orfani dal DOM e dal riferimento JS
7836
- Object.values(this.boardService.getAllSquares()).forEach(square => {
7837
- const expectedPieceId = this.positionService.getGamePieceId(square.id);
7838
- if (!expectedPieceId && typeof square.forceRemoveAllPieces === 'function') {
7839
- square.forceRemoveAllPieces();
7840
- }
7841
- });
7842
- this._addListeners();
7843
- const gameStateAfter = this.positionService.getGame().fen();
7844
- if (gameStateBefore !== gameStateAfter) {
7845
- this.config.onChange(gameStateAfter);
7846
- }
7847
- }
7848
- };
7805
+ console.log('_doSimultaneousUpdate - Change analysis:', changeAnalysis);
7849
7806
 
7850
- // 4. Esegui tutte le animazioni con delay
7851
- let idx = 0;
7852
- moves.forEach(move => {
7853
- setTimeout(() => {
7854
- this.pieceService.translatePiece(
7855
- move,
7856
- false,
7857
- animation,
7858
- this._createDragFunction.bind(this),
7859
- onAnimationComplete
7860
- );
7861
- }, idx++ * animationDelay);
7862
- });
7863
- removes.forEach(op => {
7864
- setTimeout(() => {
7865
- if (typeof op.square.forceRemoveAllPieces === 'function') {
7866
- op.square.forceRemoveAllPieces();
7867
- onAnimationComplete();
7868
- } else {
7869
- this.pieceService.removePieceFromSquare(op.square, animation, onAnimationComplete);
7870
- }
7871
- }, idx++ * animationDelay);
7872
- });
7873
- adds.forEach(op => {
7874
- setTimeout(() => {
7875
- const newPiece = this.pieceService.convertPiece(op.pieceId);
7876
- this.pieceService.addPieceOnSquare(
7877
- op.square,
7878
- newPiece,
7879
- animation,
7880
- this._createDragFunction.bind(this),
7881
- onAnimationComplete
7882
- );
7883
- }, idx++ * animationDelay);
7884
- });
7807
+ // Execute all changes simultaneously
7808
+ this._executeSimultaneousChanges(changeAnalysis, gameStateBefore, isPositionLoad);
7885
7809
  }
7886
7810
 
7887
7811
  /**
@@ -8207,380 +8131,99 @@ let Chessboard$1 = class Chessboard {
8207
8131
  }
8208
8132
 
8209
8133
  // -------------------
8210
- // Public API Methods (Refactored)
8134
+ // Public API Methods
8211
8135
  // -------------------
8212
8136
 
8213
- // --- POSITION & STATE ---
8214
- /**
8215
- * Get the current position as FEN
8216
- * @returns {string}
8217
- */
8218
- getPosition() { return this.fen(); }
8219
- /**
8220
- * Set the board position (FEN or object)
8221
- * @param {string|Object} position
8222
- * @param {Object} [opts]
8223
- * @param {boolean} [opts.animate=true]
8224
- * @returns {boolean}
8225
- */
8226
- setPosition(position, opts = {}) {
8227
- const animate = opts.animate !== undefined ? opts.animate : true;
8228
- // Remove highlights and selections
8229
- if (this.boardService && this.boardService.applyToAllSquares) {
8230
- this.boardService.applyToAllSquares('removeHint');
8231
- this.boardService.applyToAllSquares('deselect');
8232
- this.boardService.applyToAllSquares('unmoved');
8233
- }
8234
- if (this.positionService && this.positionService.setGame) {
8235
- this.positionService.setGame(position);
8236
- }
8237
- if (this._updateBoardPieces) {
8238
- this._updateBoardPieces(animate, true);
8239
- }
8240
- // Forza la sincronizzazione dopo setPosition
8241
- this._updateBoardPieces(true, false);
8242
- return true;
8243
- }
8244
- /**
8245
- * Reset the board to the starting position
8246
- * @param {Object} [opts]
8247
- * @param {boolean} [opts.animate=true]
8248
- * @returns {boolean}
8249
- */
8250
- reset(opts = {}) {
8251
- const animate = opts.animate !== undefined ? opts.animate : true;
8252
- // Use the default starting position from config or fallback
8253
- const startPosition = this.config && this.config.position ? this.config.position : 'start';
8254
- this._updateBoardPieces(animate);
8255
- const result = this.setPosition(startPosition, { animate });
8256
- // Forza la sincronizzazione dopo reset
8257
- this._updateBoardPieces(true, false);
8258
- return result;
8259
- }
8260
8137
  /**
8261
- * Clear the board
8262
- * @param {Object} [opts]
8263
- * @param {boolean} [opts.animate=true]
8264
- * @returns {boolean}
8138
+ * Gets the current position as FEN
8139
+ * @returns {string} FEN string
8265
8140
  */
8266
- clear(opts = {}) {
8267
- const animate = opts.animate !== undefined ? opts.animate : true;
8268
- if (!this.positionService || !this.positionService.getGame()) {
8269
- return false;
8270
- }
8271
- if (this._clearVisualState) this._clearVisualState();
8272
- this.positionService.getGame().clear();
8273
- // Forza la rimozione di tutti i pezzi dal DOM
8274
- if (this.boardService && this.boardService.squares) {
8275
- Object.values(this.boardService.squares).forEach(sq => {
8276
- if (sq && sq.piece) sq.piece = null;
8277
- });
8278
- }
8279
- if (this._updateBoardPieces) {
8280
- this._updateBoardPieces(animate, true);
8281
- }
8282
- // Forza la sincronizzazione dopo clear
8283
- this._updateBoardPieces(true, false);
8284
- return true;
8141
+ fen() {
8142
+ return this.positionService.getGame().fen();
8285
8143
  }
8286
8144
 
8287
- // --- MOVE MANAGEMENT ---
8288
- /**
8289
- * Undo last move
8290
- * @param {Object} [opts]
8291
- * @param {boolean} [opts.animate=true]
8292
- * @returns {boolean}
8293
- */
8294
- undoMove(opts = {}) {
8295
- const undone = this.positionService.getGame().undo();
8296
- if (undone) {
8297
- this._undoneMoves.push(undone);
8298
- // Forza refresh completo di tutti i pezzi dopo undo
8299
- this._updateBoardPieces(true, true);
8300
- return undone;
8301
- }
8302
- return null;
8303
- }
8304
8145
  /**
8305
- * Redo last undone move
8306
- * @param {Object} [opts]
8307
- * @param {boolean} [opts.animate=true]
8308
- * @returns {boolean}
8146
+ * Gets current turn
8147
+ * @returns {string} 'w' or 'b'
8309
8148
  */
8310
- redoMove(opts = {}) {
8311
- if (this._undoneMoves && this._undoneMoves.length > 0) {
8312
- const move = this._undoneMoves.pop();
8313
- const moveObj = { from: move.from, to: move.to };
8314
- if (move.promotion) moveObj.promotion = move.promotion;
8315
- const result = this.positionService.getGame().move(moveObj);
8316
- // Forza refresh completo di tutti i pezzi dopo redo
8317
- this._updateBoardPieces(true, true);
8318
- return result;
8319
- }
8320
- return false;
8149
+ turn() {
8150
+ return this.positionService.getGame().turn();
8321
8151
  }
8322
- /**
8323
- * Get legal moves for a square
8324
- * @param {string} square
8325
- * @returns {Array}
8326
- */
8327
- getLegalMoves(square) { return this.legalMoves(square); }
8328
8152
 
8329
- // --- PIECE MANAGEMENT ---
8330
8153
  /**
8331
- * Get the piece at a square
8332
- * @param {string} square
8333
- * @returns {string|null}
8334
- */
8335
- getPiece(square) {
8336
- // Sempre leggi lo stato aggiornato dal boardService
8337
- const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8338
- if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[getPiece] Parametro square non valido');
8339
- // Forza sync prima di leggere
8340
- this._updateBoardPieces(false, false);
8341
- const piece = squareObj.piece;
8342
- if (!piece) return null;
8343
- return (piece.color + piece.type).toLowerCase();
8344
- }
8345
- /**
8346
- * Put a piece on a square
8347
- * @param {string|Piece} piece
8348
- * @param {string|Square} square
8349
- * @param {Object} [opts]
8350
- * @param {boolean} [opts.animate=true]
8351
- * @returns {boolean}
8154
+ * Loads a new position
8155
+ * @param {string|Object} position - Position to load
8156
+ * @param {Object} [options={}] - Loading options
8157
+ * @param {boolean} [animation=true] - Whether to animate
8352
8158
  */
8353
- putPiece(piece, square, opts = {}) {
8354
- const animate = opts.animate !== undefined ? opts.animate : true;
8355
- let pieceStr = piece;
8356
- if (typeof piece === 'object' && piece.type && piece.color) {
8357
- pieceStr = (piece.color + piece.type).toLowerCase();
8358
- } else if (typeof piece === 'string' && piece.length === 2) {
8359
- const a = piece[0].toLowerCase();
8360
- const b = piece[1].toLowerCase();
8361
- const types = 'kqrbnp';
8362
- const colors = 'wb';
8363
- if (types.includes(a) && colors.includes(b)) {
8364
- pieceStr = b + a;
8365
- } else if (colors.includes(a) && types.includes(b)) {
8366
- pieceStr = a + b;
8367
- } else {
8368
- throw new Error(`[putPiece] Invalid piece: ${piece}`);
8369
- }
8370
- }
8371
- const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8372
- if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[putPiece] Parametro square non valido');
8373
- const pieceObj = this.pieceService.convertPiece(pieceStr);
8374
- if (!pieceObj || typeof pieceObj !== 'object' || !('type' in pieceObj)) throw new Error('[putPiece] Parametro piece non valido');
8375
- // Aggiorna solo il motore chess.js
8376
- const chessJsPiece = { type: pieceObj.type, color: pieceObj.color };
8377
- const game = this.positionService.getGame();
8378
- const result = game.put(chessJsPiece, squareObj.id);
8379
- if (!result) throw new Error(`[putPiece] Game.put failed for ${pieceStr} on ${squareObj.id}`);
8380
- // Non aggiornare direttamente square.piece!
8381
- // Riallinea la board JS allo stato del motore
8382
- this._updateBoardPieces(animate);
8383
- return true;
8384
- }
8385
- /**
8386
- * Remove a piece from a square
8387
- * @param {string|Square} square
8388
- * @param {Object} [opts]
8389
- * @param {boolean} [opts.animate=true]
8390
- * @returns {string|null}
8391
- */
8392
- removePiece(square, opts = {}) {
8393
- const animate = opts.animate !== undefined ? opts.animate : true;
8394
- const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8395
- if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[removePiece] Parametro square non valido');
8396
- // Aggiorna solo il motore chess.js
8397
- const game = this.positionService.getGame();
8398
- game.remove(squareObj.id);
8399
- // Non aggiornare direttamente square.piece!
8400
- // Riallinea la board JS allo stato del motore
8401
- this._updateBoardPieces(animate);
8402
- return true;
8403
- }
8159
+ load(position, options = {}, animation = true) {
8160
+ this.boardService.applyToAllSquares('removeHint');
8161
+ this.boardService.applyToAllSquares('deselect');
8162
+ this.boardService.applyToAllSquares('unmoved');
8404
8163
 
8405
- // --- BOARD CONTROL ---
8406
- /**
8407
- * Flip the board orientation
8408
- * @param {Object} [opts]
8409
- * @param {boolean} [opts.animate=true]
8410
- */
8411
- flipBoard(opts = {}) {
8412
- if (this.coordinateService && this.coordinateService.flipOrientation) {
8413
- this.coordinateService.flipOrientation();
8414
- }
8415
- if (this._buildBoard) this._buildBoard();
8416
- if (this._buildSquares) this._buildSquares();
8417
- if (this._addListeners) this._addListeners();
8418
- if (this._updateBoardPieces) this._updateBoardPieces(opts.animate !== false);
8419
- console.log('FEN dopo flip:', this.fen(), 'Orientamento:', this.coordinateService.getOrientation());
8420
- }
8421
- /**
8422
- * Set the board orientation
8423
- * @param {'w'|'b'} color
8424
- * @param {Object} [opts]
8425
- * @param {boolean} [opts.animate=true]
8426
- */
8427
- setOrientation(color, opts = {}) {
8428
- if (this.validationService.isValidOrientation(color)) {
8429
- this.coordinateService.setOrientation(color);
8430
- if (this._buildBoard) this._buildBoard();
8431
- if (this._buildSquares) this._buildSquares();
8432
- if (this._addListeners) this._addListeners();
8433
- if (this._updateBoardPieces) this._updateBoardPieces(opts.animate !== false);
8434
- }
8435
- return this.coordinateService.getOrientation();
8436
- }
8437
- /**
8438
- * Get the current orientation
8439
- * @returns {'w'|'b'}
8440
- */
8441
- getOrientation() { return this.orientation(); }
8442
- /**
8443
- * Resize the board
8444
- * @param {number|string} size
8445
- */
8446
- resizeBoard(size) {
8447
- if (size === 'auto') {
8448
- this.config.size = 'auto';
8449
- document.documentElement.style.setProperty('--dimBoard', 'auto');
8450
- this._updateBoardPieces(false);
8451
- return true;
8452
- }
8453
- if (typeof size !== 'number' || size < 50 || size > 3000) {
8454
- throw new Error(`[resizeBoard] Invalid size: ${size}`);
8455
- }
8456
- this.config.size = size;
8457
- document.documentElement.style.setProperty('--dimBoard', `${size}px`);
8458
- this._updateBoardPieces(false);
8459
- return true;
8164
+ this.positionService.setGame(position, options);
8165
+ this._updateBoardPieces(animation, true); // Position load
8460
8166
  }
8461
8167
 
8462
- // --- HIGHLIGHTING & UI ---
8463
8168
  /**
8464
- * Highlight a square
8465
- * @param {string|Square} square
8466
- * @param {Object} [opts]
8467
- */
8468
- highlight(square, opts = {}) {
8469
- // API: accetta id, converte subito in oggetto
8470
- const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8471
- if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[highlight] Parametro square non valido');
8472
- if (this.boardService && this.boardService.highlightSquare) {
8473
- this.boardService.highlightSquare(squareObj, opts);
8474
- } else if (this.eventService && this.eventService.highlightSquare) {
8475
- this.eventService.highlightSquare(squareObj, opts);
8476
- }
8477
- }
8478
- /**
8479
- * Remove highlight from a square
8480
- * @param {string|Square} square
8481
- * @param {Object} [opts]
8169
+ * Destroys the board and cleans up resources
8482
8170
  */
8483
- dehighlight(square, opts = {}) {
8484
- // API: accetta id, converte subito in oggetto
8485
- const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8486
- if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[dehighlight] Parametro square non valido');
8487
- if (this.boardService && this.boardService.dehighlightSquare) {
8488
- this.boardService.dehighlightSquare(squareObj, opts);
8489
- } else if (this.eventService && this.eventService.dehighlightSquare) {
8490
- this.eventService.dehighlightSquare(squareObj, opts);
8171
+ destroy() {
8172
+ this.eventService.destroy();
8173
+ this.boardService.destroy();
8174
+ this.positionService.destroy();
8175
+ this.pieceService.destroy();
8176
+ this.moveService.destroy();
8177
+ this.animationService.destroy();
8178
+ this.validationService.destroy();
8179
+
8180
+ if (this._updateTimeout) {
8181
+ clearTimeout(this._updateTimeout);
8182
+ this._updateTimeout = null;
8491
8183
  }
8492
8184
  }
8493
8185
 
8494
- // --- GAME INFO ---
8495
- /**
8496
- * Get FEN string
8497
- * @returns {string}
8498
- */
8499
- fen() {
8500
- // Avoid recursion: call the underlying game object's fen()
8501
- const game = this.positionService.getGame();
8502
- if (!game || typeof game.fen !== 'function') return '';
8503
- return game.fen();
8504
- }
8505
- /**
8506
- * Get current turn
8507
- * @returns {'w'|'b'}
8508
- */
8509
- turn() { return this.positionService.getGame().turn(); }
8510
- /**
8511
- * Is the game over?
8512
- * @returns {boolean}
8513
- */
8514
- isGameOver() {
8515
- // Forza sync prima di interrogare il motore
8516
- this._updateBoardPieces(false, false);
8517
- const game = this.positionService.getGame();
8518
- if (!game) return false;
8519
- if (game.isGameOver) return game.isGameOver();
8520
- // Fallback: checkmate or draw
8521
- if (game.isCheckmate && game.isCheckmate()) return true;
8522
- if (game.isDraw && game.isDraw()) return true;
8523
- return false;
8524
- }
8525
- /**
8526
- * Is it checkmate?
8527
- * @returns {boolean}
8528
- */
8529
- isCheckmate() {
8530
- const game = this.positionService.getGame();
8531
- if (!game) return false;
8532
- return game.isCheckmate ? game.isCheckmate() : false;
8533
- }
8534
- /**
8535
- * Is it draw?
8536
- * @returns {boolean}
8537
- */
8538
- isDraw() {
8539
- const game = this.positionService.getGame();
8540
- if (!game) return false;
8541
- return game.isDraw ? game.isDraw() : false;
8542
- }
8543
8186
  /**
8544
- * Get move history
8545
- * @returns {Array}
8187
+ * Resizes the board
8188
+ * @param {number|string} size - New size
8546
8189
  */
8547
- getHistory() {
8548
- const game = this.positionService.getGame();
8549
- if (!game) return [];
8550
- return game.history ? game.history() : [];
8190
+ resize(size) {
8191
+ this.boardService.resize(size);
8192
+ this._updateBoardPieces();
8551
8193
  }
8552
8194
 
8553
- // --- LIFECYCLE ---
8554
- /**
8555
- * Destroy the board and cleanup
8556
- */
8557
- destroy() { /* TODO: robust destroy logic */ }
8558
8195
  /**
8559
- * Rebuild the board
8196
+ * Flips the board orientation
8560
8197
  */
8561
- rebuild() { this._initialize(); }
8198
+ flip() {
8199
+ this.coordinateService.flipOrientation();
8562
8200
 
8563
- // --- CONFIGURATION ---
8564
- /**
8565
- * Get current config
8566
- * @returns {Object}
8567
- */
8568
- getConfig() { return this.config; }
8569
- /**
8570
- * Set new config
8571
- * @param {Object} newConfig
8572
- */
8573
- setConfig(newConfig) { this.setConfig(newConfig); }
8201
+ // Save current position before destroying
8202
+ let currentPosition = null;
8203
+ try {
8204
+ // Check if there are any pieces on the board
8205
+ const position = this.positionService.getPosition();
8206
+ const hasPieces = Object.keys(position).length > 0;
8574
8207
 
8575
- // --- ALIASES/DEPRECATED ---
8576
- // Note: These methods are now implemented as aliases at the end of the class
8208
+ if (hasPieces) {
8209
+ currentPosition = this.positionService.getGame().fen();
8210
+ }
8211
+ } catch (error) {
8212
+ console.log('No valid position to save during flip');
8213
+ }
8577
8214
 
8578
- /**
8579
- * Alias for flipBoard (for backward compatibility)
8580
- */
8581
- flip(opts = {}) {
8582
- this._updateBoardPieces(opts.animate !== false);
8583
- return this.flipBoard(opts);
8215
+ this.destroy();
8216
+ this._initializeServices(); // Recreate all services
8217
+ this._initParams();
8218
+
8219
+ // Restore position after rebuilding if we had one
8220
+ if (currentPosition) {
8221
+ this._setGame(currentPosition);
8222
+ }
8223
+ this._buildBoard();
8224
+ this._buildSquares();
8225
+ this._addListeners();
8226
+ this._updateBoardPieces(true, true);
8584
8227
  }
8585
8228
 
8586
8229
  /**
@@ -8603,19 +8246,51 @@ let Chessboard$1 = class Chessboard {
8603
8246
  this.load(position, {}, animate); // load() already handles isPositionLoad=true
8604
8247
  }
8605
8248
 
8249
+ /**
8250
+ * Makes a move on the board
8251
+ * @param {string|Object} move - Move to make
8252
+ * @param {boolean} [animate=true] - Whether to animate
8253
+ * @returns {boolean} True if move was successful
8254
+ */
8255
+ move(move, animate = true) {
8256
+ if (typeof move === 'string') {
8257
+ // Parse move string (e.g., 'e2e4')
8258
+ const moveObj = this.moveService.parseMove(move);
8259
+ if (!moveObj) return false;
8260
+
8261
+ const fromSquare = this.boardService.getSquare(moveObj.from);
8262
+ const toSquare = this.boardService.getSquare(moveObj.to);
8263
+
8264
+ if (!fromSquare || !toSquare) return false;
8265
+
8266
+ return this._onMove(fromSquare, toSquare, moveObj.promotion, animate);
8267
+ } else if (move && move.from && move.to) {
8268
+ // Handle move object
8269
+ const fromSquare = this.boardService.getSquare(move.from);
8270
+ const toSquare = this.boardService.getSquare(move.to);
8271
+
8272
+ if (!fromSquare || !toSquare) return false;
8273
+
8274
+ return this._onMove(fromSquare, toSquare, move.promotion, animate);
8275
+ }
8276
+
8277
+ return false;
8278
+ }
8279
+
8606
8280
  /**
8607
8281
  * Undoes the last move
8608
8282
  * @param {boolean} [animate=true] - Whether to animate
8609
8283
  * @returns {boolean} True if undo was successful
8610
8284
  */
8611
8285
  undo(animate = true) {
8612
- const undone = this.positionService.getGame().undo();
8613
- if (undone) {
8614
- this._undoneMoves.push(undone);
8615
- this._updateBoardPieces(animate);
8616
- return undone;
8286
+ if (this.positionService.getGame().undo) {
8287
+ const undoResult = this.positionService.getGame().undo();
8288
+ if (undoResult) {
8289
+ this._updateBoardPieces(animate, true); // Position change
8290
+ return true;
8291
+ }
8617
8292
  }
8618
- return null;
8293
+ return false;
8619
8294
  }
8620
8295
 
8621
8296
  /**
@@ -8624,13 +8299,12 @@ let Chessboard$1 = class Chessboard {
8624
8299
  * @returns {boolean} True if redo was successful
8625
8300
  */
8626
8301
  redo(animate = true) {
8627
- if (this._undoneMoves && this._undoneMoves.length > 0) {
8628
- const move = this._undoneMoves.pop();
8629
- const moveObj = { from: move.from, to: move.to };
8630
- if (move.promotion) moveObj.promotion = move.promotion;
8631
- const result = this.positionService.getGame().move(moveObj);
8632
- this._updateBoardPieces(animate);
8633
- return result;
8302
+ if (this.positionService.getGame().redo) {
8303
+ const redoResult = this.positionService.getGame().redo();
8304
+ if (redoResult) {
8305
+ this._updateBoardPieces(animate, true); // Position change
8306
+ return true;
8307
+ }
8634
8308
  }
8635
8309
  return false;
8636
8310
  }
@@ -8792,6 +8466,112 @@ let Chessboard$1 = class Chessboard {
8792
8466
  }
8793
8467
  }
8794
8468
 
8469
+ /**
8470
+ * Clears the board
8471
+ * @param {boolean} [animate=true] - Whether to animate
8472
+ */
8473
+ clear(animate = true) {
8474
+ // Check if services are available
8475
+ if (!this.positionService || !this.positionService.getGame()) {
8476
+ console.log('Cannot clear - position service not available');
8477
+ return;
8478
+ }
8479
+
8480
+ // Clear visual state first
8481
+ this._clearVisualState();
8482
+
8483
+ // Clear game state
8484
+ this.positionService.getGame().clear();
8485
+
8486
+ // Update board visually
8487
+ this._updateBoardPieces(animate, true); // Position change
8488
+ }
8489
+
8490
+ /**
8491
+ * Resets the board to starting position
8492
+ * @param {boolean} [animate=true] - Whether to animate
8493
+ */
8494
+ reset(animate = true) {
8495
+ // Check if services are available
8496
+ if (!this.positionService || !this.positionService.getGame()) {
8497
+ console.log('Cannot reset - position service not available');
8498
+ return;
8499
+ }
8500
+
8501
+ this.positionService.getGame().reset();
8502
+ this._updateBoardPieces(animate, true); // Position change
8503
+ }
8504
+
8505
+ /**
8506
+ * Puts a piece on a square
8507
+ * @param {string} square - Square to put piece on
8508
+ * @param {string} piece - Piece to put
8509
+ * @param {boolean} [animate=true] - Whether to animate
8510
+ * @returns {boolean} True if successful
8511
+ */
8512
+ put(square, piece, animate = true) {
8513
+ console.log(`put() called with square: ${square}, piece: ${piece}`);
8514
+
8515
+ if (!this.validationService.isValidSquare(square) || !this.validationService.isValidPiece(piece)) {
8516
+ console.log('Validation failed');
8517
+ return false;
8518
+ }
8519
+
8520
+ // Check if services are available
8521
+ if (!this.positionService || !this.positionService.getGame()) {
8522
+ console.log('Cannot put piece - position service not available');
8523
+ return false;
8524
+ }
8525
+
8526
+ const pieceObj = this.pieceService.convertPiece(piece);
8527
+ console.log(`Converted piece:`, pieceObj);
8528
+ console.log(`Piece type: ${pieceObj.type}, color: ${pieceObj.color}`);
8529
+
8530
+ const squareObj = this.boardService.getSquare(square);
8531
+
8532
+ if (!squareObj) {
8533
+ console.log('Square not found');
8534
+ return false;
8535
+ }
8536
+
8537
+ // Update game state - note: chess.js expects (piece, square) order
8538
+ const chessJsPiece = { type: pieceObj.type.toLowerCase(), color: pieceObj.color.toLowerCase() };
8539
+ console.log(`Chess.js piece:`, chessJsPiece);
8540
+
8541
+ const result = this.positionService.getGame().put(chessJsPiece, square);
8542
+ console.log(`Chess.js put result:`, result);
8543
+
8544
+ if (result) {
8545
+ // Update visual representation
8546
+ this._updateBoardPieces(animate, true); // Position change
8547
+ }
8548
+
8549
+ return result;
8550
+ }
8551
+
8552
+ /**
8553
+ * Removes a piece from a square
8554
+ * @param {string} square - Square to remove piece from
8555
+ * @param {boolean} [animate=true] - Whether to animate
8556
+ * @returns {boolean} True if successful
8557
+ */
8558
+ remove(square, animate = true) {
8559
+ if (!this.validationService.isValidSquare(square)) {
8560
+ return false;
8561
+ }
8562
+
8563
+ const squareObj = this.boardService.getSquare(square);
8564
+ if (!squareObj) return false;
8565
+
8566
+ // Update game state
8567
+ this.positionService.getGame().remove(square);
8568
+
8569
+ // Update visual representation
8570
+ this._updateBoardPieces(animate, true); // Position change
8571
+
8572
+ return true;
8573
+ }
8574
+
8795
8575
  /**
8796
8576
  * Gets configuration options
8797
8577
  * @returns {Object} Configuration object
@@ -8817,6 +8597,23 @@ let Chessboard$1 = class Chessboard {
8817
8597
  }
8818
8598
  }
8819
8599
 
8600
+ /**
8601
+ * Gets or sets the animation style
8602
+ * @param {string} [style] - New animation style ('sequential' or 'simultaneous')
8603
+ * @returns {string} Current animation style
8604
+ */
8605
+ animationStyle(style) {
8606
+ if (style === undefined) {
8607
+ return this.config.animationStyle;
8608
+ }
8609
+
8610
+ if (this.validationService.isValidAnimationStyle(style)) {
8611
+ this.config.animationStyle = style;
8612
+ }
8613
+
8614
+ return this.config.animationStyle;
8615
+ }
8616
+
8820
8617
  /**
8821
8618
  * Gets or sets the simultaneous animation delay
8822
8619
  * @param {number} [delay] - New delay in milliseconds
@@ -8836,97 +8633,7 @@ let Chessboard$1 = class Chessboard {
8836
8633
 
8837
8634
  // Additional API methods would be added here following the same pattern
8838
8635
  // This is a good starting point for the refactored architecture
8839
-
8840
- // Additional API methods and aliases for backward compatibility
8841
- insert(square, piece) { return this.putPiece(piece, square); }
8842
- build() { return this._initialize(); }
8843
- ascii() { return this.positionService.getGame().ascii(); }
8844
- board() { return this.positionService.getGame().board(); }
8845
- getCastlingRights(color) { return this.positionService.getGame().getCastlingRights(color); }
8846
- getComment() { return this.positionService.getGame().getComment(); }
8847
- getComments() { return this.positionService.getGame().getComments(); }
8848
- lastMove() { return this.positionService.getGame().lastMove(); }
8849
- moveNumber() { return this.positionService.getGame().moveNumber(); }
8850
- moves(options = {}) { return this.positionService.getGame().moves(options); }
8851
- squareColor(squareId) { return this.boardService.getSquare(squareId).isWhite() ? 'light' : 'dark'; }
8852
- isDrawByFiftyMoves() { return this.positionService.getGame().isDrawByFiftyMoves(); }
8853
- isInsufficientMaterial() { return this.positionService.getGame().isInsufficientMaterial(); }
8854
- removeComment() { return this.positionService.getGame().removeComment(); }
8855
- removeComments() { return this.positionService.getGame().removeComments(); }
8856
- removeHeader(field) { return this.positionService.getGame().removeHeader(field); }
8857
- setCastlingRights(color, rights) { return this.positionService.getGame().setCastlingRights(color, rights); }
8858
- setComment(comment) { return this.positionService.getGame().setComment(comment); }
8859
- setHeader(key, value) { return this.positionService.getGame().setHeader(key, value); }
8860
- validateFen(fen) { return this.positionService.getGame().validateFen(fen); }
8861
-
8862
- // Implementazioni reali per highlight/dehighlight
8863
- highlightSquare(square) {
8864
- return this.boardService.highlight(square);
8865
- }
8866
- dehighlightSquare(square) {
8867
- return this.boardService.dehighlight(square);
8868
- }
8869
- forceSync() { this._updateBoardPieces(true, true); this._updateBoardPieces(true, false); }
8870
-
8871
- // Metodi mancanti che causano fallimenti nei test
8872
- /**
8873
- * Move a piece from one square to another
8874
- * @param {string|Object} move - Move in format 'e2e4' or {from: 'e2', to: 'e4'}
8875
- * @param {Object} [opts] - Options
8876
- * @param {boolean} [opts.animate=true] - Whether to animate
8877
- * @returns {boolean} True if move was successful
8878
- */
8879
- movePiece(move, opts = {}) {
8880
- const animate = opts.animate !== undefined ? opts.animate : true;
8881
-
8882
- // --- API: accetta id/stringhe, ma converte subito in oggetti ---
8883
- let fromSquareObj, toSquareObj, promotion;
8884
- if (typeof move === 'string') {
8885
- if (move.length === 4) {
8886
- fromSquareObj = this.boardService.getSquare(move.substring(0, 2));
8887
- toSquareObj = this.boardService.getSquare(move.substring(2, 4));
8888
- } else if (move.length === 5) {
8889
- fromSquareObj = this.boardService.getSquare(move.substring(0, 2));
8890
- toSquareObj = this.boardService.getSquare(move.substring(2, 4));
8891
- promotion = move.substring(4, 5);
8892
- } else {
8893
- throw new Error(`Invalid move format: ${move}`);
8894
- }
8895
- } else if (typeof move === 'object' && move.from && move.to) {
8896
- // Se sono id, converto in oggetti; se sono già oggetti, li uso direttamente
8897
- fromSquareObj = typeof move.from === 'string' ? this.boardService.getSquare(move.from) : move.from;
8898
- toSquareObj = typeof move.to === 'string' ? this.boardService.getSquare(move.to) : move.to;
8899
- promotion = move.promotion;
8900
- } else {
8901
- throw new Error(`Invalid move: ${move}`);
8902
- }
8903
-
8904
- if (!fromSquareObj || !toSquareObj) {
8905
- throw new Error(`Invalid squares: ${move.from || move.substring(0, 2)} or ${move.to || move.substring(2, 4)}`);
8906
- }
8907
-
8908
- // --- Internamente: lavora solo con oggetti ---
8909
- const result = this._onMove(fromSquareObj, toSquareObj, promotion, animate);
8910
- // Dopo ogni mossa, forza la sincronizzazione della board
8911
- this._updateBoardPieces(true, false);
8912
- return result;
8913
- }
8914
-
8915
- // Aliases for backward compatibility
8916
- move(move, animate = true) {
8917
- // On any new move, clear the redo stack
8918
- this._undoneMoves = [];
8919
- return this.movePiece(move, { animate });
8920
- }
8921
- get(square) { return this.getPiece(square); }
8922
- piece(square) { return this.getPiece(square); }
8923
- put(piece, square, opts = {}) { return this.putPiece(piece, square, opts); }
8924
- remove(square, opts = {}) { return this.removePiece(square, opts); }
8925
- load(position, opts = {}) { return this.setPosition(position, opts); }
8926
- resize(size) { return this.resizeBoard(size); }
8927
- start(opts = {}) { return this.reset(opts); }
8928
- clearBoard(opts = {}) { return this.clear(opts); }
8929
- };
8636
+ }
8930
8637
 
8931
8638
  /**
8932
8639
  * Structured logging system for Chessboard.js
@@ -9384,6 +9091,7 @@ class ChessboardFactory {
9384
9091
  hints: true,
9385
9092
  clickable: true,
9386
9093
  moveHighlight: true,
9094
+ animationStyle: 'simultaneous'
9387
9095
  });
9388
9096
 
9389
9097
  // Tournament template
@@ -9394,6 +9102,7 @@ class ChessboardFactory {
9394
9102
  clickable: true,
9395
9103
  moveHighlight: true,
9396
9104
  onlyLegalMoves: true,
9105
+ animationStyle: 'sequential'
9397
9106
  });
9398
9107
 
9399
9108
  // Analysis template
@@ -9404,6 +9113,7 @@ class ChessboardFactory {
9404
9113
  clickable: true,
9405
9114
  moveHighlight: true,
9406
9115
  mode: 'analysis',
9116
+ animationStyle: 'simultaneous'
9407
9117
  });
9408
9118
 
9409
9119
  // Puzzle template
@@ -9414,6 +9124,7 @@ class ChessboardFactory {
9414
9124
  clickable: true,
9415
9125
  moveHighlight: true,
9416
9126
  onlyLegalMoves: true,
9127
+ animationStyle: 'sequential'
9417
9128
  });
9418
9129
 
9419
9130
  // Demo template
@@ -9423,6 +9134,7 @@ class ChessboardFactory {
9423
9134
  hints: false,
9424
9135
  clickable: false,
9425
9136
  moveHighlight: true,
9137
+ animationStyle: 'simultaneous'
9426
9138
  });
9427
9139
  }
9428
9140
 
@@ -9468,7 +9180,7 @@ class ChessboardFactory {
9468
9180
  this.validationService.validateConfig(finalConfig);
9469
9181
 
9470
9182
  // Create chessboard instance
9471
- const chessboard = new Chessboard$1(finalConfig);
9183
+ const chessboard = new Chessboard(finalConfig);
9472
9184
 
9473
9185
  // Store instance for management
9474
9186
  this.instances.set(containerId, {
@@ -9728,44 +9440,13 @@ function createChessboardFromTemplate(containerId, templateName, overrides = {})
9728
9440
  */
9729
9441
 
9730
9442
 
9731
- /**
9732
- * Main Chessboard factory function for backward compatibility
9733
- * Supports both legacy and modern calling conventions
9734
- * @param {string|Object} containerElm - Container element ID or configuration object
9735
- * @param {Object} [config={}] - Configuration options (when first param is string)
9736
- * @returns {ChessboardClass} Chessboard instance
9737
- */
9738
- function Chessboard(containerElm, config = {}) {
9739
- const factoryLogger = logger.child('ChessboardFactory');
9740
-
9741
- try {
9742
- // If first parameter is an object, treat it as config
9743
- if (typeof containerElm === 'object' && containerElm !== null) {
9744
- factoryLogger.debug('Creating chessboard with config object');
9745
- return new Chessboard$1(containerElm);
9746
- }
9747
-
9748
- // Otherwise, treat first parameter as element ID
9749
- if (typeof containerElm === 'string') {
9750
- factoryLogger.debug('Creating chessboard with element ID', { elementId: containerElm });
9751
- const fullConfig = { ...config, id: containerElm };
9752
- return new Chessboard$1(fullConfig);
9753
- }
9754
-
9755
- throw new Error('Invalid parameters: first parameter must be string or object');
9756
- } catch (error) {
9757
- factoryLogger.error('Failed to create chessboard instance', { error });
9758
- throw error;
9759
- }
9760
- }
9761
-
9762
9443
  /**
9763
9444
  * Wrapper class that handles both calling conventions
9764
9445
  * Provides enhanced error handling and logging
9765
9446
  * @class
9766
9447
  * @extends ChessboardClass
9767
9448
  */
9768
- class ChessboardWrapper extends Chessboard$1 {
9449
+ class ChessboardWrapper extends Chessboard {
9769
9450
  /**
9770
9451
  * Creates a new ChessboardWrapper instance
9771
9452
  * @param {string|Object} containerElm - Container element ID or configuration object
@@ -9794,53 +9475,32 @@ class ChessboardWrapper extends Chessboard$1 {
9794
9475
  }
9795
9476
  }
9796
9477
 
9797
- /**
9798
- * Refactored Chessboard API - see Chessboard.js for full method docs
9799
- * @typedef {import('./Chessboard.js').Chessboard} Chessboard
9800
- */
9478
+ // Attach classes and utilities to the factory function for direct access
9479
+ Chessboard.Class = ChessboardWrapper;
9480
+ Chessboard.Chessboard = ChessboardWrapper;
9481
+ Chessboard.Config = ChessboardConfig;
9482
+ Chessboard.Factory = ChessboardFactory;
9801
9483
 
9802
- // --- STATIC/FACTORY METHODS ---
9803
- /**
9804
- * Create a new Chessboard instance
9805
- * @param {string|Object} containerElm
9806
- * @param {Object} [config]
9807
- * @returns {Chessboard}
9808
- */
9484
+ // Attach factory methods
9809
9485
  Chessboard.create = createChessboard;
9810
- /**
9811
- * Create a Chessboard from a template
9812
- * @param {string|Object} containerElm
9813
- * @param {string} templateName
9814
- * @param {Object} [config]
9815
- * @returns {Chessboard}
9816
- */
9817
- Chessboard.fromTemplate = createChessboardFromTemplate;
9486
+ Chessboard.createFromTemplate = createChessboardFromTemplate;
9818
9487
  Chessboard.factory = chessboardFactory;
9819
9488
 
9820
- // --- INSTANCE MANAGEMENT ---
9489
+ // Static methods for instance management
9821
9490
  Chessboard.getInstance = (containerId) => chessboardFactory.getInstance(containerId);
9822
9491
  Chessboard.destroyInstance = (containerId) => chessboardFactory.destroy(containerId);
9823
9492
  Chessboard.destroyAll = () => chessboardFactory.destroyAll();
9824
9493
  Chessboard.listInstances = () => chessboardFactory.listInstances();
9825
9494
 
9826
- // --- TEMPLATE MANAGEMENT ---
9495
+ // Template management
9827
9496
  Chessboard.registerTemplate = (name, config) => chessboardFactory.registerTemplate(name, config);
9828
9497
  Chessboard.removeTemplate = (name) => chessboardFactory.removeTemplate(name);
9829
9498
  Chessboard.getTemplate = (name) => chessboardFactory.getTemplate(name);
9830
9499
  Chessboard.listTemplates = () => chessboardFactory.listTemplates();
9831
9500
 
9832
- // --- STATS & DEBUG ---
9501
+ // Statistics and debugging
9833
9502
  Chessboard.getStats = () => chessboardFactory.getStats();
9834
9503
 
9835
- // --- DEPRECATED/LEGACY ALIASES ---
9836
- /**
9837
- * @deprecated Use Chessboard.create instead
9838
- */
9839
- Chessboard.Class = ChessboardWrapper;
9840
- Chessboard.Chessboard = ChessboardWrapper;
9841
- Chessboard.Config = ChessboardConfig;
9842
- Chessboard.Factory = ChessboardFactory;
9843
-
9844
9504
  /**
9845
9505
  * Coordinate utilities for Chessboard.js
9846
9506
  */
@@ -10230,7 +9890,11 @@ function validateConfig(config) {
10230
9890
  if (config.moveAnimation && !isValidEasing(config.moveAnimation)) {
10231
9891
  errors.push('Invalid moveAnimation. Must be a valid easing function');
10232
9892
  }
10233
-
9893
+
9894
+ if (config.animationStyle && !['sequential', 'simultaneous'].includes(config.animationStyle)) {
9895
+ errors.push('Invalid animationStyle. Must be "sequential" or "simultaneous"');
9896
+ }
9897
+
10234
9898
  return {
10235
9899
  success: errors.length === 0,
10236
9900
  errors