@alepot55/chessboardjs 2.3.7 → 2.3.8

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.
@@ -127,7 +127,6 @@ var Chessboard = (function (exports) {
127
127
  invalid_fadeTime: 'Invalid fadeTime: ',
128
128
  invalid_fadeAnimation: 'Invalid fadeAnimation: ',
129
129
  invalid_ratio: 'Invalid ratio: ',
130
- invalid_animationStyle: 'Invalid animationStyle: ',
131
130
  animation_failed: 'Animation failed: ',
132
131
 
133
132
  // Event handlers
@@ -349,7 +348,7 @@ var Chessboard = (function (exports) {
349
348
  }
350
349
 
351
350
  /**
352
- * Service for input validation and data sanitization
351
+ * ValidationService - Handles input validation and data sanitization
353
352
  * @module services/ValidationService
354
353
  * @since 2.0.0
355
354
  */
@@ -379,7 +378,6 @@ var Chessboard = (function (exports) {
379
378
  movableColors: ['w', 'b', 'white', 'black', 'both', 'none'],
380
379
  dropOffBoard: ['snapback', 'trash'],
381
380
  easingTypes: ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'],
382
- animationStyles: ['sequential', 'simultaneous'],
383
381
  modes: ['normal', 'creative', 'analysis'],
384
382
  promotionPieces: ['q', 'r', 'b', 'n', 'Q', 'R', 'B', 'N']
385
383
  });
@@ -399,45 +397,39 @@ var Chessboard = (function (exports) {
399
397
  /**
400
398
  * Service responsible for validating inputs and data
401
399
  * Implements caching for performance optimization
402
- * @class
400
+ * @class ValidationService
403
401
  */
404
402
  class ValidationService {
405
403
  /**
406
- * Creates a new ValidationService instance
404
+ * Create a new ValidationService instance
407
405
  */
408
406
  constructor() {
409
- // Cache for validation results to improve performance
410
407
  this._validationCache = new Map();
411
408
  this._cacheMaxSize = 1000;
412
-
413
- // Compile patterns for reuse
414
409
  this._patterns = VALIDATION_PATTERNS;
415
410
  this._validValues = VALID_VALUES;
416
411
  this._constraints = SIZE_CONSTRAINTS;
417
412
  }
418
413
 
419
414
  /**
420
- * Validates a square identifier with caching
415
+ * Validate a square identifier with caching
421
416
  * @param {string} square - Square to validate (e.g., 'e4')
422
417
  * @returns {boolean} True if valid
423
418
  */
424
419
  isValidSquare(square) {
425
420
  const cacheKey = `square:${square}`;
426
-
427
421
  if (this._validationCache.has(cacheKey)) {
428
422
  return this._validationCache.get(cacheKey);
429
423
  }
430
-
431
- const isValid = typeof square === 'string' &&
432
- square.length === 2 &&
433
- this._patterns.square.test(square);
434
-
424
+ const isValid = typeof square === 'string' &&
425
+ square.length === 2 &&
426
+ this._patterns.square.test(square);
435
427
  this._cacheValidationResult(cacheKey, isValid);
436
428
  return isValid;
437
429
  }
438
430
 
439
431
  /**
440
- * Validates a piece identifier with enhanced format support
432
+ * Validate a piece identifier with enhanced format support
441
433
  * @param {string} piece - Piece to validate (e.g., 'wK', 'bp')
442
434
  * @returns {boolean} True if valid
443
435
  */
@@ -445,28 +437,22 @@ var Chessboard = (function (exports) {
445
437
  if (typeof piece !== 'string' || piece.length !== 2) {
446
438
  return false;
447
439
  }
448
-
449
440
  const cacheKey = `piece:${piece}`;
450
-
451
441
  if (this._validationCache.has(cacheKey)) {
452
442
  return this._validationCache.get(cacheKey);
453
443
  }
454
-
455
444
  const [first, second] = piece.split('');
456
-
457
- // Check both formats: [type][color] and [color][type]
458
- const format1 = PIECE_TYPES.includes(first.toLowerCase()) &&
459
- PIECE_COLORS.includes(second);
460
- const format2 = PIECE_COLORS.includes(first) &&
461
- PIECE_TYPES.includes(second.toLowerCase());
462
-
445
+ const format1 = PIECE_TYPES.includes(first.toLowerCase()) &&
446
+ PIECE_COLORS.includes(second);
447
+ const format2 = PIECE_COLORS.includes(first) &&
448
+ PIECE_TYPES.includes(second.toLowerCase());
463
449
  const isValid = format1 || format2;
464
450
  this._cacheValidationResult(cacheKey, isValid);
465
451
  return isValid;
466
452
  }
467
453
 
468
454
  /**
469
- * Validates a FEN string with comprehensive checks
455
+ * Validate a FEN string with comprehensive checks
470
456
  * @param {string} fen - FEN string to validate
471
457
  * @returns {boolean} True if valid
472
458
  */
@@ -474,86 +460,64 @@ var Chessboard = (function (exports) {
474
460
  if (typeof fen !== 'string') {
475
461
  return false;
476
462
  }
477
-
478
463
  const cacheKey = `fen:${fen}`;
479
-
480
464
  if (this._validationCache.has(cacheKey)) {
481
465
  return this._validationCache.get(cacheKey);
482
466
  }
483
-
484
- // Basic pattern check
485
467
  if (!this._patterns.fen.test(fen)) {
486
468
  this._cacheValidationResult(cacheKey, false);
487
469
  return false;
488
470
  }
489
-
490
- // Additional FEN validation
491
471
  const isValid = this._validateFenStructure(fen);
492
472
  this._cacheValidationResult(cacheKey, isValid);
493
473
  return isValid;
494
474
  }
495
475
 
496
476
  /**
497
- * Validates FEN structure in detail
477
+ * Validate FEN structure in detail
498
478
  * @private
499
479
  * @param {string} fen - FEN string to validate
500
480
  * @returns {boolean} True if valid
501
481
  */
502
482
  _validateFenStructure(fen) {
503
483
  const parts = fen.split(' ');
504
-
505
484
  if (parts.length !== 6) {
506
485
  return false;
507
486
  }
508
-
509
- // Validate piece placement
510
487
  const ranks = parts[0].split('/');
511
488
  if (ranks.length !== 8) {
512
489
  return false;
513
490
  }
514
-
515
- // Validate each rank
516
491
  for (const rank of ranks) {
517
492
  if (!this._validateRank(rank)) {
518
493
  return false;
519
494
  }
520
495
  }
521
-
522
- // Validate active color
523
496
  if (!['w', 'b'].includes(parts[1])) {
524
497
  return false;
525
498
  }
526
-
527
- // Validate castling rights
528
499
  if (!/^[KQkq-]*$/.test(parts[2])) {
529
500
  return false;
530
501
  }
531
-
532
- // Validate en passant target
533
502
  if (parts[3] !== '-' && !this.isValidSquare(parts[3])) {
534
503
  return false;
535
504
  }
536
-
537
- // Validate halfmove clock and fullmove number
538
505
  const halfmove = parseInt(parts[4], 10);
539
506
  const fullmove = parseInt(parts[5], 10);
540
-
541
- return !isNaN(halfmove) && !isNaN(fullmove) &&
542
- halfmove >= 0 && fullmove >= 1;
507
+ return !isNaN(halfmove) && !isNaN(fullmove) &&
508
+ halfmove >= 0 && fullmove >= 1;
543
509
  }
544
510
 
545
511
  /**
546
- * Validates a single rank in FEN notation
512
+ * Validate a single rank in FEN notation
547
513
  * @private
548
514
  * @param {string} rank - Rank to validate
549
515
  * @returns {boolean} True if valid
550
516
  */
551
517
  _validateRank(rank) {
552
518
  let squares = 0;
553
-
554
519
  for (let i = 0; i < rank.length; i++) {
555
520
  const char = rank[i];
556
-
557
521
  if (/[1-8]/.test(char)) {
558
522
  squares += parseInt(char, 10);
559
523
  } else if (/[prnbqkPRNBQK]/.test(char)) {
@@ -562,12 +526,11 @@ var Chessboard = (function (exports) {
562
526
  return false;
563
527
  }
564
528
  }
565
-
566
529
  return squares === 8;
567
530
  }
568
531
 
569
532
  /**
570
- * Validates a move string with comprehensive format support
533
+ * Validate a move string with comprehensive format support
571
534
  * @param {string} move - Move string to validate (e.g., 'e2e4', 'e7e8q')
572
535
  * @returns {boolean} True if valid
573
536
  */
@@ -575,20 +538,17 @@ var Chessboard = (function (exports) {
575
538
  if (typeof move !== 'string') {
576
539
  return false;
577
540
  }
578
-
579
541
  const cacheKey = `move:${move}`;
580
-
581
542
  if (this._validationCache.has(cacheKey)) {
582
543
  return this._validationCache.get(cacheKey);
583
544
  }
584
-
585
545
  const isValid = this._validateMoveFormat(move);
586
546
  this._cacheValidationResult(cacheKey, isValid);
587
547
  return isValid;
588
548
  }
589
549
 
590
550
  /**
591
- * Validates move format in detail
551
+ * Validate move format in detail
592
552
  * @private
593
553
  * @param {string} move - Move string to validate
594
554
  * @returns {boolean} True if valid
@@ -597,21 +557,15 @@ var Chessboard = (function (exports) {
597
557
  if (move.length < 4 || move.length > 5) {
598
558
  return false;
599
559
  }
600
-
601
- const from = move.slice(0, 2);
602
- const to = move.slice(2, 4);
603
- const promotion = move.slice(4, 5);
604
-
560
+ const from = move.substring(0, 2);
561
+ const to = move.substring(2, 4);
605
562
  if (!this.isValidSquare(from) || !this.isValidSquare(to)) {
606
563
  return false;
607
564
  }
608
-
609
- if (promotion && !this._validValues.promotionPieces.includes(promotion)) {
565
+ if (move.length === 5 && !/[qrnb]/i.test(move[4])) {
610
566
  return false;
611
567
  }
612
-
613
- // Additional move validation (e.g., source and target different)
614
- return from !== to;
568
+ return true;
615
569
  }
616
570
 
617
571
  /**
@@ -641,11 +595,9 @@ var Chessboard = (function (exports) {
641
595
  if (size === 'auto') {
642
596
  return true;
643
597
  }
644
-
645
598
  if (typeof size === 'number') {
646
599
  return size >= this._constraints.min && size <= this._constraints.max;
647
600
  }
648
-
649
601
  return false;
650
602
  }
651
603
 
@@ -655,9 +607,9 @@ var Chessboard = (function (exports) {
655
607
  * @returns {boolean} True if valid
656
608
  */
657
609
  isValidTime(time) {
658
- return typeof time === 'number' &&
659
- time >= 0 &&
660
- time <= this._constraints.maxTime;
610
+ return typeof time === 'number' &&
611
+ time >= 0 &&
612
+ time <= this._constraints.maxTime;
661
613
  }
662
614
 
663
615
  /**
@@ -675,10 +627,10 @@ var Chessboard = (function (exports) {
675
627
  * @returns {boolean} True if valid
676
628
  */
677
629
  isValidElementId(id) {
678
- return typeof id === 'string' &&
679
- id.length > 0 &&
680
- id.length <= 100 && // Reasonable length limit
681
- /^[a-zA-Z][\w:-]*$/.test(id); // Valid HTML ID format
630
+ return typeof id === 'string' &&
631
+ id.length > 0 &&
632
+ id.length <= 100 && // Reasonable length limit
633
+ /^[a-zA-Z][\w:-]*$/.test(id); // Valid HTML ID format
682
634
  }
683
635
 
684
636
  /**
@@ -708,14 +660,6 @@ var Chessboard = (function (exports) {
708
660
  return this._validValues.easingTypes.includes(easing);
709
661
  }
710
662
 
711
- /**
712
- * Validates animation style
713
- * @param {string} style - Animation style to validate
714
- * @returns {boolean} True if valid
715
- */
716
- isValidAnimationStyle(style) {
717
- return this._validValues.animationStyles.includes(style);
718
- }
719
663
 
720
664
  /**
721
665
  * Validates CSS color format
@@ -726,24 +670,20 @@ var Chessboard = (function (exports) {
726
670
  if (typeof color !== 'string') {
727
671
  return false;
728
672
  }
729
-
730
673
  // Check for hex colors
731
674
  if (this._patterns.color.test(color)) {
732
675
  return true;
733
676
  }
734
-
735
677
  // Check for named colors (basic set)
736
678
  const namedColors = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'cyan', 'magenta'];
737
679
  if (namedColors.includes(color.toLowerCase())) {
738
680
  return true;
739
681
  }
740
-
741
682
  // Check for rgb/rgba format
742
683
  if (/^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/.test(color) ||
743
684
  /^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[0-1](\.\d+)?\s*\)$/.test(color)) {
744
685
  return true;
745
686
  }
746
-
747
687
  return false;
748
688
  }
749
689
 
@@ -756,8 +696,8 @@ var Chessboard = (function (exports) {
756
696
  validateSquare(square) {
757
697
  if (!this.isValidSquare(square)) {
758
698
  throw new ValidationError(
759
- ERROR_MESSAGES.invalid_square + square,
760
- 'square',
699
+ ERROR_MESSAGES.invalid_square + square,
700
+ 'square',
761
701
  square
762
702
  );
763
703
  }
@@ -773,8 +713,8 @@ var Chessboard = (function (exports) {
773
713
  validatePiece(piece) {
774
714
  if (!this.isValidPiece(piece)) {
775
715
  throw new ValidationError(
776
- ERROR_MESSAGES.invalid_piece + piece,
777
- 'piece',
716
+ ERROR_MESSAGES.invalid_piece + piece,
717
+ 'piece',
778
718
  piece
779
719
  );
780
720
  }
@@ -790,8 +730,8 @@ var Chessboard = (function (exports) {
790
730
  validateFen(fen) {
791
731
  if (!this.isValidFen(fen)) {
792
732
  throw new ValidationError(
793
- ERROR_MESSAGES.invalid_fen + fen,
794
- 'fen',
733
+ ERROR_MESSAGES.invalid_fen + fen,
734
+ 'fen',
795
735
  fen
796
736
  );
797
737
  }
@@ -807,8 +747,8 @@ var Chessboard = (function (exports) {
807
747
  validateMove(move) {
808
748
  if (!this.isValidMove(move)) {
809
749
  throw new ValidationError(
810
- ERROR_MESSAGES.invalid_move + move,
811
- 'move',
750
+ ERROR_MESSAGES.invalid_move + move,
751
+ 'move',
812
752
  move
813
753
  );
814
754
  }
@@ -824,16 +764,15 @@ var Chessboard = (function (exports) {
824
764
  validateOrientation(orientation) {
825
765
  if (!this.isValidOrientation(orientation)) {
826
766
  throw new ValidationError(
827
- ERROR_MESSAGES.invalid_orientation + orientation,
828
- 'orientation',
767
+ ERROR_MESSAGES.invalid_orientation + orientation,
768
+ 'orientation',
829
769
  orientation
830
770
  );
831
771
  }
832
-
833
772
  // Normalize to 'w' or 'b'
834
- return orientation === 'white' ? 'w' :
835
- orientation === 'black' ? 'b' :
836
- orientation;
773
+ return orientation === 'white' ? 'w' :
774
+ orientation === 'black' ? 'b' :
775
+ orientation;
837
776
  }
838
777
 
839
778
  /**
@@ -845,16 +784,15 @@ var Chessboard = (function (exports) {
845
784
  validateColor(color) {
846
785
  if (!this.isValidColor(color)) {
847
786
  throw new ValidationError(
848
- ERROR_MESSAGES.invalid_color + color,
849
- 'color',
787
+ ERROR_MESSAGES.invalid_color + color,
788
+ 'color',
850
789
  color
851
790
  );
852
791
  }
853
-
854
792
  // Normalize to 'w' or 'b'
855
- return color === 'white' ? 'w' :
856
- color === 'black' ? 'b' :
857
- color;
793
+ return color === 'white' ? 'w' :
794
+ color === 'black' ? 'b' :
795
+ color;
858
796
  }
859
797
 
860
798
  /**
@@ -866,8 +804,8 @@ var Chessboard = (function (exports) {
866
804
  validateSize(size) {
867
805
  if (!this.isValidSize(size)) {
868
806
  throw new ValidationError(
869
- ERROR_MESSAGES.invalid_size + size,
870
- 'size',
807
+ ERROR_MESSAGES.invalid_size + size,
808
+ 'size',
871
809
  size
872
810
  );
873
811
  }
@@ -883,40 +821,29 @@ var Chessboard = (function (exports) {
883
821
  validateConfig(config) {
884
822
  if (!config || typeof config !== 'object') {
885
823
  throw new ValidationError(
886
- 'Configuration must be an object',
887
- 'config',
824
+ 'Configuration must be an object',
825
+ 'config',
888
826
  config
889
827
  );
890
828
  }
891
-
892
829
  const errors = [];
893
-
894
830
  // Validate required fields
895
831
  if (!config.id && !config.id_div) {
896
832
  errors.push('Configuration must include id or id_div');
897
833
  }
898
-
899
834
  // Validate optional fields
900
835
  if (config.orientation && !this.isValidOrientation(config.orientation)) {
901
836
  errors.push(ERROR_MESSAGES.invalid_orientation + config.orientation);
902
837
  }
903
-
904
838
  if (config.size && !this.isValidSize(config.size)) {
905
839
  errors.push(ERROR_MESSAGES.invalid_size + config.size);
906
840
  }
907
-
908
841
  if (config.movableColors && !this.isValidMovableColors(config.movableColors)) {
909
842
  errors.push(ERROR_MESSAGES.invalid_color + config.movableColors);
910
843
  }
911
-
912
844
  if (config.dropOffBoard && !this.isValidDropOffBoard(config.dropOffBoard)) {
913
845
  errors.push(ERROR_MESSAGES.invalid_dropOffBoard + config.dropOffBoard);
914
846
  }
915
-
916
- if (config.animationStyle && !this.isValidAnimationStyle(config.animationStyle)) {
917
- errors.push(ERROR_MESSAGES.invalid_animationStyle + config.animationStyle);
918
- }
919
-
920
847
  // Validate callbacks
921
848
  const callbacks = ['onMove', 'onMoveEnd', 'onChange', 'onDragStart', 'onDragMove', 'onDrop', 'onSnapbackEnd'];
922
849
  for (const callback of callbacks) {
@@ -924,7 +851,6 @@ var Chessboard = (function (exports) {
924
851
  errors.push(`Invalid ${callback} callback`);
925
852
  }
926
853
  }
927
-
928
854
  // Validate colors
929
855
  const colorFields = ['whiteSquare', 'blackSquare', 'highlight', 'hintColor'];
930
856
  for (const field of colorFields) {
@@ -932,15 +858,13 @@ var Chessboard = (function (exports) {
932
858
  errors.push(`Invalid ${field} color: ${config[field]}`);
933
859
  }
934
860
  }
935
-
936
861
  if (errors.length > 0) {
937
862
  throw new ValidationError(
938
- `Configuration validation failed: ${errors.join(', ')}`,
939
- 'config',
863
+ `Configuration validation failed: ${errors.join(', ')}`,
864
+ 'config',
940
865
  config
941
866
  );
942
867
  }
943
-
944
868
  return config;
945
869
  }
946
870
 
@@ -956,7 +880,6 @@ var Chessboard = (function (exports) {
956
880
  const firstKey = this._validationCache.keys().next().value;
957
881
  this._validationCache.delete(firstKey);
958
882
  }
959
-
960
883
  this._validationCache.set(key, result);
961
884
  }
962
885
 
@@ -988,7 +911,6 @@ var Chessboard = (function (exports) {
988
911
  return validations.map(validation => {
989
912
  try {
990
913
  const { type, value } = validation;
991
-
992
914
  switch (type) {
993
915
  case 'square':
994
916
  return { valid: this.isValidSquare(value), value };
@@ -1094,8 +1016,7 @@ var Chessboard = (function (exports) {
1094
1016
  fadeAnimation: 'ease',
1095
1017
  ratio: 0.9,
1096
1018
  piecesPath: '../assets/themes/default',
1097
- animationStyle: 'simultaneous',
1098
- simultaneousAnimationDelay: 0,
1019
+ simultaneousAnimationDelay: 100,
1099
1020
  onMove: () => true,
1100
1021
  onMoveEnd: () => true,
1101
1022
  onChange: () => true,
@@ -1129,19 +1050,19 @@ var Chessboard = (function (exports) {
1129
1050
  constructor(settings = {}) {
1130
1051
  // Initialize validation service
1131
1052
  this._validationService = new ValidationService();
1132
-
1053
+
1133
1054
  // Validate input
1134
1055
  this._validateInput(settings);
1135
-
1056
+
1136
1057
  // Merge with defaults
1137
1058
  const config = this._mergeWithDefaults(settings);
1138
-
1059
+
1139
1060
  // Process and validate configuration
1140
1061
  this._processConfiguration(config);
1141
-
1062
+
1142
1063
  // Set CSS properties
1143
1064
  this._setCSSProperties(config);
1144
-
1065
+
1145
1066
  // Configure mode-specific settings
1146
1067
  this._configureModeSettings();
1147
1068
  }
@@ -1156,7 +1077,7 @@ var Chessboard = (function (exports) {
1156
1077
  if (settings !== null && typeof settings !== 'object') {
1157
1078
  throw new ConfigurationError('Settings must be an object', 'settings', settings);
1158
1079
  }
1159
-
1080
+
1160
1081
  // Validate using validation service
1161
1082
  try {
1162
1083
  this._validationService.validateConfig(settings);
@@ -1190,7 +1111,7 @@ var Chessboard = (function (exports) {
1190
1111
  this.size = config.size;
1191
1112
  this.movableColors = config.movableColors;
1192
1113
  this.piecesPath = config.piecesPath;
1193
-
1114
+
1194
1115
  // Event handlers
1195
1116
  this.onMove = this._validateCallback(config.onMove);
1196
1117
  this.onMoveEnd = this._validateCallback(config.onMoveEnd);
@@ -1218,9 +1139,8 @@ var Chessboard = (function (exports) {
1218
1139
  this.snapbackTime = this._setTime(config.snapbackTime);
1219
1140
  this.dropCenterTime = this._setTime(config.dropCenterTime);
1220
1141
  this.fadeTime = this._setTime(config.fadeTime);
1221
-
1142
+
1222
1143
  // Animation style properties
1223
- this.animationStyle = this._validateAnimationStyle(config.animationStyle);
1224
1144
  this.simultaneousAnimationDelay = this._validateDelay(config.simultaneousAnimationDelay);
1225
1145
  }
1226
1146
 
@@ -1281,19 +1201,6 @@ var Chessboard = (function (exports) {
1281
1201
  return callback;
1282
1202
  }
1283
1203
 
1284
- /**
1285
- * Validates animation style
1286
- * @private
1287
- * @param {string} style - Animation style
1288
- * @returns {string} Validated style
1289
- * @throws {ConfigurationError} If style is invalid
1290
- */
1291
- _validateAnimationStyle(style) {
1292
- if (!this._validationService.isValidAnimationStyle(style)) {
1293
- throw new ConfigurationError('Invalid animation style', 'animationStyle', style);
1294
- }
1295
- return style;
1296
- }
1297
1204
 
1298
1205
  /**
1299
1206
  * Validates animation delay
@@ -1351,11 +1258,11 @@ var Chessboard = (function (exports) {
1351
1258
  }
1352
1259
  return value;
1353
1260
  }
1354
-
1261
+
1355
1262
  if (typeof value === 'string' && value in ANIMATION_TIMES) {
1356
1263
  return ANIMATION_TIMES[value];
1357
1264
  }
1358
-
1265
+
1359
1266
  throw new ConfigurationError('Invalid time value', 'time', value);
1360
1267
  }
1361
1268
 
@@ -1370,11 +1277,11 @@ var Chessboard = (function (exports) {
1370
1277
  if (typeof value === 'boolean') {
1371
1278
  return value;
1372
1279
  }
1373
-
1280
+
1374
1281
  if (value in BOOLEAN_VALUES) {
1375
1282
  return BOOLEAN_VALUES[value];
1376
1283
  }
1377
-
1284
+
1378
1285
  throw new ConfigurationError('Invalid boolean value', 'boolean', value);
1379
1286
  }
1380
1287
 
@@ -1390,17 +1297,17 @@ var Chessboard = (function (exports) {
1390
1297
  if (typeof value === 'boolean') {
1391
1298
  return value ? TRANSITION_FUNCTIONS.ease : null;
1392
1299
  }
1393
-
1300
+
1394
1301
  // Handle string values
1395
1302
  if (typeof value === 'string' && value in TRANSITION_FUNCTIONS) {
1396
1303
  return TRANSITION_FUNCTIONS[value];
1397
1304
  }
1398
-
1305
+
1399
1306
  // Handle null/undefined
1400
1307
  if (value === null || value === undefined) {
1401
1308
  return null;
1402
1309
  }
1403
-
1310
+
1404
1311
  throw new ConfigurationError('Invalid transition function', 'transitionFunction', value);
1405
1312
  }
1406
1313
 
@@ -1431,7 +1338,6 @@ var Chessboard = (function (exports) {
1431
1338
  fadeTime: this.fadeTime,
1432
1339
  fadeAnimation: this.fadeAnimation,
1433
1340
  piecesPath: this.piecesPath,
1434
- animationStyle: this.animationStyle,
1435
1341
  simultaneousAnimationDelay: this.simultaneousAnimationDelay,
1436
1342
  onlyLegalMoves: this.onlyLegalMoves
1437
1343
  };
@@ -1453,7 +1359,7 @@ var Chessboard = (function (exports) {
1453
1359
 
1454
1360
  // Apply updates
1455
1361
  const newConfig = Object.assign({}, this.toObject(), updates);
1456
-
1362
+
1457
1363
  // Re-process configuration
1458
1364
  this._processConfiguration(newConfig);
1459
1365
  this._setCSSProperties(newConfig);
@@ -1676,22 +1582,10 @@ var Chessboard = (function (exports) {
1676
1582
 
1677
1583
  setDrag(f) {
1678
1584
  if (!this.element) { console.debug(`[Piece] setDrag: ${this.id} - element is null`); return; }
1679
- // Remove previous handlers
1680
- this.element.onmousedown = null;
1681
- this.element.ontouchstart = null;
1682
- this.element.ondragstart = null;
1683
- if (window.PointerEvent) {
1684
- this.element.onpointerdown = null;
1685
- }
1686
- // Set new handlers
1687
1585
  this.element.ondragstart = (e) => { e.preventDefault(); };
1688
1586
  this.element.onmousedown = f;
1689
1587
  this.element.ontouchstart = f; // Drag touch
1690
- if (window.PointerEvent) {
1691
- this.element.onpointerdown = f;
1692
- console.debug(`[Piece] setDrag: pointerdown set for ${this.id}`);
1693
- }
1694
- console.debug(`[Piece] setDrag: mousedown/ontouchstart set for ${this.id}`);
1588
+ console.debug(`[Piece] setDrag: ${this.id}`);
1695
1589
  }
1696
1590
 
1697
1591
  destroy() {
@@ -2031,180 +1925,155 @@ var Chessboard = (function (exports) {
2031
1925
  };
2032
1926
 
2033
1927
  /**
2034
- * Service for managing animations and transitions
1928
+ * AnimationService - Gestione centralizzata delle animazioni e transizioni per la scacchiera
2035
1929
  * @module services/AnimationService
2036
1930
  * @since 2.0.0
2037
1931
  */
2038
1932
 
2039
1933
  /**
2040
- * Service responsible for coordinating animations and transitions
2041
- * @class
1934
+ * Service responsabile del coordinamento delle animazioni e delle transizioni
1935
+ * @class AnimationService
2042
1936
  */
2043
1937
  class AnimationService {
2044
1938
  /**
2045
- * Creates a new AnimationService instance
2046
- * @param {ChessboardConfig} config - Board configuration
1939
+ * Crea una nuova istanza di AnimationService
1940
+ * @param {Object} config - Configurazione della scacchiera
2047
1941
  */
2048
1942
  constructor(config) {
1943
+ /** @type {Object} */
2049
1944
  this.config = config;
1945
+ /** @type {Map<number, {element: HTMLElement, animate: Function, callback?: Function}>} */
2050
1946
  this.activeAnimations = new Map();
1947
+ /** @type {number} */
2051
1948
  this.animationId = 0;
2052
1949
  }
2053
1950
 
2054
1951
  /**
2055
- * Creates a timing function for animations
2056
- * @param {string} [type='ease'] - Animation type
2057
- * @returns {Function} Timing function
1952
+ * Crea una funzione di timing per le animazioni
1953
+ * @param {string} [type='ease'] - Tipo di easing
1954
+ * @returns {Function} Funzione di timing
2058
1955
  */
2059
1956
  createTimingFunction(type = 'ease') {
2060
1957
  return (elapsed, duration) => {
2061
- const x = elapsed / duration;
2062
-
1958
+ const x = duration > 0 ? Math.max(0, Math.min(1, elapsed / duration)) : 1;
2063
1959
  switch (type) {
2064
- case 'linear':
2065
- return x;
2066
- case 'ease':
2067
- return (x ** 2) * (3 - 2 * x);
2068
- case 'ease-in':
2069
- return x ** 2;
2070
- case 'ease-out':
2071
- return -1 * (x - 1) ** 2 + 1;
2072
- case 'ease-in-out':
2073
- return (x < 0.5) ? 2 * x ** 2 : 4 * x - 2 * x ** 2 - 1;
2074
- default:
2075
- return x;
1960
+ case 'linear': return x;
1961
+ case 'ease': return (x ** 2) * (3 - 2 * x);
1962
+ case 'ease-in': return x ** 2;
1963
+ case 'ease-out': return -1 * (x - 1) ** 2 + 1;
1964
+ case 'ease-in-out': return (x < 0.5) ? 2 * x ** 2 : 4 * x - 2 * x ** 2 - 1;
1965
+ default: return x;
2076
1966
  }
2077
1967
  };
2078
1968
  }
2079
1969
 
2080
1970
  /**
2081
- * Animates an element with given properties
2082
- * @param {HTMLElement} element - Element to animate
2083
- * @param {Object} properties - Properties to animate
2084
- * @param {number} duration - Animation duration in milliseconds
2085
- * @param {string} [easing='ease'] - Easing function
2086
- * @param {Function} [callback] - Callback when animation completes
2087
- * @returns {number} Animation ID
1971
+ * Anima un elemento HTML con le proprietà specificate
1972
+ * @param {HTMLElement} element - Elemento da animare
1973
+ * @param {Object} properties - Proprietà da animare (es: {left: 100, top: 50, opacity: 1})
1974
+ * @param {number} duration - Durata in ms
1975
+ * @param {string} [easing='ease'] - Tipo di easing
1976
+ * @param {Function} [callback] - Callback al termine
1977
+ * @returns {number} ID dell'animazione
2088
1978
  */
2089
1979
  animate(element, properties, duration, easing = 'ease', callback) {
1980
+ if (!element || typeof properties !== 'object' || typeof duration !== 'number') {
1981
+ throw new Error('AnimationService.animate: parametri non validi');
1982
+ }
2090
1983
  const animationId = ++this.animationId;
2091
1984
  const timingFunction = this.createTimingFunction(easing);
2092
1985
  const startTime = performance.now();
2093
1986
  const startValues = {};
2094
-
2095
- // Store initial values
2096
1987
  Object.keys(properties).forEach(prop => {
2097
1988
  startValues[prop] = this._getInitialValue(element, prop);
2098
1989
  });
2099
-
2100
1990
  const animate = (currentTime) => {
2101
1991
  const elapsed = currentTime - startTime;
2102
1992
  const progress = Math.min(elapsed / duration, 1);
2103
1993
  const easedProgress = timingFunction(elapsed, duration);
2104
-
2105
- // Apply animated values
2106
1994
  Object.keys(properties).forEach(prop => {
2107
1995
  const startValue = startValues[prop];
2108
1996
  const endValue = properties[prop];
2109
1997
  const currentValue = this._interpolateValue(startValue, endValue, easedProgress);
2110
1998
  this._applyValue(element, prop, currentValue);
2111
1999
  });
2112
-
2113
2000
  if (progress < 1 && this.activeAnimations.has(animationId)) {
2114
2001
  requestAnimationFrame(animate);
2115
2002
  } else {
2116
2003
  this.activeAnimations.delete(animationId);
2117
- if (callback) callback();
2004
+ if (typeof callback === 'function') callback();
2118
2005
  }
2119
2006
  };
2120
-
2121
2007
  this.activeAnimations.set(animationId, { element, animate, callback });
2122
2008
  requestAnimationFrame(animate);
2123
-
2124
2009
  return animationId;
2125
2010
  }
2126
2011
 
2127
2012
  /**
2128
- * Cancels an animation
2129
- * @param {number} animationId - Animation ID to cancel
2013
+ * Annulla una animazione attiva
2014
+ * @param {number} animationId - ID dell'animazione
2130
2015
  */
2131
2016
  cancel(animationId) {
2132
- if (this.activeAnimations.has(animationId)) {
2133
- this.activeAnimations.delete(animationId);
2134
- }
2017
+ this.activeAnimations.delete(animationId);
2135
2018
  }
2136
2019
 
2137
2020
  /**
2138
- * Cancels all animations
2021
+ * Annulla tutte le animazioni attive
2139
2022
  */
2140
2023
  cancelAll() {
2141
2024
  this.activeAnimations.clear();
2142
2025
  }
2143
2026
 
2144
2027
  /**
2145
- * Gets initial value for a property
2028
+ * Ottiene il valore iniziale di una proprietà
2146
2029
  * @private
2147
- * @param {HTMLElement} element - Element
2148
- * @param {string} property - Property name
2149
- * @returns {number} Initial value
2030
+ * @param {HTMLElement} element
2031
+ * @param {string} property
2032
+ * @returns {number}
2150
2033
  */
2151
2034
  _getInitialValue(element, property) {
2152
2035
  if (!element || !element.style) return 0;
2153
2036
  switch (property) {
2154
- case 'opacity':
2155
- return parseFloat(getComputedStyle(element).opacity) || 1;
2156
- case 'left':
2157
- return parseFloat(element.style.left) || 0;
2158
- case 'top':
2159
- return parseFloat(element.style.top) || 0;
2160
- case 'scale':
2161
- return 1;
2162
- default:
2163
- return 0;
2037
+ case 'opacity': return parseFloat(getComputedStyle(element).opacity) || 1;
2038
+ case 'left': return parseFloat(element.style.left) || 0;
2039
+ case 'top': return parseFloat(element.style.top) || 0;
2040
+ case 'scale': return 1;
2041
+ default: return 0;
2164
2042
  }
2165
2043
  }
2166
2044
 
2167
2045
  /**
2168
- * Interpolates between two values
2046
+ * Interpola tra due valori
2169
2047
  * @private
2170
- * @param {number} start - Start value
2171
- * @param {number} end - End value
2172
- * @param {number} progress - Progress (0-1)
2173
- * @returns {number} Interpolated value
2048
+ * @param {number} start
2049
+ * @param {number} end
2050
+ * @param {number} progress
2051
+ * @returns {number}
2174
2052
  */
2175
2053
  _interpolateValue(start, end, progress) {
2176
2054
  return start + (end - start) * progress;
2177
2055
  }
2178
2056
 
2179
2057
  /**
2180
- * Applies animated value to element
2058
+ * Applica il valore animato all'elemento
2181
2059
  * @private
2182
- * @param {HTMLElement} element - Element
2183
- * @param {string} property - Property name
2184
- * @param {number} value - Value to apply
2060
+ * @param {HTMLElement} element
2061
+ * @param {string} property
2062
+ * @param {number} value
2185
2063
  */
2186
2064
  _applyValue(element, property, value) {
2187
2065
  if (!element || !element.style) return;
2188
2066
  switch (property) {
2189
- case 'opacity':
2190
- element.style.opacity = value;
2191
- break;
2192
- case 'left':
2193
- element.style.left = value + 'px';
2194
- break;
2195
- case 'top':
2196
- element.style.top = value + 'px';
2197
- break;
2198
- case 'scale':
2199
- element.style.transform = `scale(${value})`;
2200
- break;
2201
- default:
2202
- element.style[property] = value;
2067
+ case 'opacity': element.style.opacity = value; break;
2068
+ case 'left': element.style.left = value + 'px'; break;
2069
+ case 'top': element.style.top = value + 'px'; break;
2070
+ case 'scale': element.style.transform = `scale(${value})`; break;
2071
+ default: element.style[property] = value;
2203
2072
  }
2204
2073
  }
2205
2074
 
2206
2075
  /**
2207
- * Cleans up resources
2076
+ * Cleanup risorse e animazioni
2208
2077
  */
2209
2078
  destroy() {
2210
2079
  this.cancelAll();
@@ -2212,7 +2081,7 @@ var Chessboard = (function (exports) {
2212
2081
  }
2213
2082
 
2214
2083
  /**
2215
- * Service for managing board setup and DOM operations
2084
+ * BoardService - Handles board DOM setup, manipulation, and resource management
2216
2085
  * @module services/BoardService
2217
2086
  * @since 2.0.0
2218
2087
  */
@@ -2220,45 +2089,45 @@ var Chessboard = (function (exports) {
2220
2089
 
2221
2090
  /**
2222
2091
  * Service responsible for board DOM manipulation and setup
2223
- * @class
2092
+ * @class BoardService
2224
2093
  */
2225
2094
  class BoardService {
2226
2095
  /**
2227
- * Creates a new BoardService instance
2228
- * @param {ChessboardConfig} config - Board configuration
2096
+ * Create a new BoardService instance
2097
+ * @param {Object} config - Board configuration
2229
2098
  */
2230
2099
  constructor(config) {
2100
+ /** @type {Object} */
2231
2101
  this.config = config;
2102
+ /** @type {HTMLElement|null} */
2232
2103
  this.element = null;
2104
+ /** @type {Object.<string, Square>} */
2233
2105
  this.squares = {};
2234
2106
  }
2235
2107
 
2236
2108
  /**
2237
- * Builds the board DOM element and attaches it to the configured container
2238
- * @throws {DOMError} When the container element cannot be found
2109
+ * Build the board DOM element and attach it to the configured container
2110
+ * @throws {DOMError} If the container element cannot be found
2239
2111
  */
2240
2112
  buildBoard() {
2241
- console.log('BoardService.buildBoard: Looking for element with ID:', this.config.id_div);
2242
-
2243
2113
  this.element = document.getElementById(this.config.id_div);
2244
2114
  if (!this.element) {
2245
2115
  throw new DOMError(ERROR_MESSAGES.invalid_id_div + this.config.id_div, this.config.id_div);
2246
2116
  }
2247
-
2248
2117
  this.resize(this.config.size);
2249
- this.element.className = "board";
2118
+ this.element.className = 'board';
2250
2119
  }
2251
2120
 
2252
2121
  /**
2253
- * Creates all 64 squares and adds them to the board
2122
+ * Create all 64 squares and add them to the board
2254
2123
  * @param {Function} coordConverter - Function to convert row/col to real coordinates
2255
2124
  */
2256
2125
  buildSquares(coordConverter) {
2126
+ if (!this.element) throw new DOMError('Board element not initialized', this.config.id_div);
2257
2127
  for (let row = 0; row < BOARD_SIZE.ROWS; row++) {
2258
2128
  for (let col = 0; col < BOARD_SIZE.COLS; col++) {
2259
2129
  const [squareRow, squareCol] = coordConverter(row, col);
2260
2130
  const square = new Square(squareRow, squareCol);
2261
-
2262
2131
  this.squares[square.getId()] = square;
2263
2132
  this.element.appendChild(square.element);
2264
2133
  }
@@ -2266,20 +2135,17 @@ var Chessboard = (function (exports) {
2266
2135
  }
2267
2136
 
2268
2137
  /**
2269
- * Removes all squares from the board and cleans up their resources
2270
- * Best practice: always destroy JS objects and DOM nodes, and clear references.
2138
+ * Remove all squares from the board and clean up their resources
2271
2139
  */
2272
2140
  removeSquares() {
2273
2141
  for (const square of Object.values(this.squares)) {
2274
- // Always call destroy to remove DOM and clear piece reference
2275
2142
  square.destroy();
2276
2143
  }
2277
2144
  this.squares = {};
2278
2145
  }
2279
2146
 
2280
2147
  /**
2281
- * Removes all content from the board element
2282
- * Best practice: clear DOM and force element to be re-fetched on next build.
2148
+ * Remove all content from the board element
2283
2149
  */
2284
2150
  removeBoard() {
2285
2151
  if (this.element) {
@@ -2289,9 +2155,9 @@ var Chessboard = (function (exports) {
2289
2155
  }
2290
2156
 
2291
2157
  /**
2292
- * Resizes the board to the specified size
2158
+ * Resize the board to the specified size
2293
2159
  * @param {number|string} value - Size in pixels or 'auto'
2294
- * @throws {ValidationError} When size value is invalid
2160
+ * @throws {ValidationError} If size value is invalid
2295
2161
  */
2296
2162
  resize(value) {
2297
2163
  if (value === 'auto') {
@@ -2305,15 +2171,13 @@ var Chessboard = (function (exports) {
2305
2171
  }
2306
2172
 
2307
2173
  /**
2308
- * Calculates the optimal size when 'auto' is specified
2174
+ * Calculate the optimal size when 'auto' is specified
2309
2175
  * @private
2310
2176
  * @returns {number} Calculated size in pixels
2311
2177
  */
2312
2178
  _calculateAutoSize() {
2313
- if (!this.element) return 400; // Default fallback
2314
-
2179
+ if (!this.element) return 400;
2315
2180
  const { offsetWidth, offsetHeight } = this.element;
2316
-
2317
2181
  if (offsetWidth === 0) {
2318
2182
  return offsetHeight || 400;
2319
2183
  } else if (offsetHeight === 0) {
@@ -2324,8 +2188,8 @@ var Chessboard = (function (exports) {
2324
2188
  }
2325
2189
 
2326
2190
  /**
2327
- * Gets a square by its ID
2328
- * @param {string} squareId - Square identifier (API pubblica)
2191
+ * Get a square by its ID
2192
+ * @param {string} squareId - Square identifier
2329
2193
  * @returns {Square|null} The square or null if not found
2330
2194
  */
2331
2195
  getSquare(squareId) {
@@ -2333,26 +2197,37 @@ var Chessboard = (function (exports) {
2333
2197
  }
2334
2198
 
2335
2199
  /**
2336
- * Highlight a square (solo oggetto)
2200
+ * Highlight a square
2337
2201
  * @param {Square} square
2338
2202
  * @param {Object} [opts]
2339
2203
  */
2340
2204
  highlightSquare(square, opts = {}) {
2341
- if (!square) throw new Error('highlightSquare richiede oggetto Square');
2342
- // ... logica esistente ...
2205
+ if (!square) throw new Error('highlightSquare requires a Square object');
2206
+ // Implement highlight logic or call square.highlight() if available
2207
+ if (typeof square.highlight === 'function') {
2208
+ square.highlight(opts);
2209
+ } else {
2210
+ square.element.classList.add('highlighted');
2211
+ }
2343
2212
  }
2213
+
2344
2214
  /**
2345
- * Dehighlight a square (solo oggetto)
2215
+ * Remove highlight from a square
2346
2216
  * @param {Square} square
2347
2217
  * @param {Object} [opts]
2348
2218
  */
2349
2219
  dehighlightSquare(square, opts = {}) {
2350
- if (!square) throw new Error('dehighlightSquare richiede oggetto Square');
2351
- // ... logica esistente ...
2220
+ if (!square) throw new Error('dehighlightSquare requires a Square object');
2221
+ // Implement dehighlight logic or call square.dehighlight() if available
2222
+ if (typeof square.dehighlight === 'function') {
2223
+ square.dehighlight(opts);
2224
+ } else {
2225
+ square.element.classList.remove('highlighted');
2226
+ }
2352
2227
  }
2353
2228
 
2354
2229
  /**
2355
- * Gets all squares
2230
+ * Get all squares
2356
2231
  * @returns {Object.<string, Square>} All squares indexed by ID
2357
2232
  */
2358
2233
  getAllSquares() {
@@ -2360,7 +2235,7 @@ var Chessboard = (function (exports) {
2360
2235
  }
2361
2236
 
2362
2237
  /**
2363
- * Applies a method to all squares
2238
+ * Apply a method to all squares
2364
2239
  * @param {string} methodName - Name of the method to call on each square
2365
2240
  * @param {...*} args - Arguments to pass to the method
2366
2241
  */
@@ -2373,7 +2248,7 @@ var Chessboard = (function (exports) {
2373
2248
  }
2374
2249
 
2375
2250
  /**
2376
- * Cleans up all resources
2251
+ * Clean up all resources
2377
2252
  */
2378
2253
  destroy() {
2379
2254
  this.removeSquares();
@@ -2384,7 +2259,7 @@ var Chessboard = (function (exports) {
2384
2259
  }
2385
2260
 
2386
2261
  /**
2387
- * Service for managing coordinate conversions and board orientation
2262
+ * CoordinateService - Handles coordinate conversions and board orientation logic
2388
2263
  * @module services/CoordinateService
2389
2264
  * @since 2.0.0
2390
2265
  */
@@ -2392,19 +2267,20 @@ var Chessboard = (function (exports) {
2392
2267
 
2393
2268
  /**
2394
2269
  * Service responsible for coordinate conversions and board orientation
2395
- * @class
2270
+ * @class CoordinateService
2396
2271
  */
2397
2272
  class CoordinateService {
2398
2273
  /**
2399
- * Creates a new CoordinateService instance
2400
- * @param {ChessboardConfig} config - Board configuration
2274
+ * Create a new CoordinateService instance
2275
+ * @param {Object} config - Board configuration
2401
2276
  */
2402
2277
  constructor(config) {
2278
+ /** @type {Object} */
2403
2279
  this.config = config;
2404
2280
  }
2405
2281
 
2406
2282
  /**
2407
- * Converts logical coordinates to real coordinates based on board orientation
2283
+ * Convert logical coordinates to real coordinates based on board orientation
2408
2284
  * @param {number} row - Row index (0-7)
2409
2285
  * @param {number} col - Column index (0-7)
2410
2286
  * @returns {Array<number>} Real coordinates [row, col] (1-8)
@@ -2412,18 +2288,16 @@ var Chessboard = (function (exports) {
2412
2288
  realCoord(row, col) {
2413
2289
  let realRow = row;
2414
2290
  let realCol = col;
2415
-
2416
2291
  if (this.isWhiteOriented()) {
2417
2292
  realRow = 7 - row;
2418
2293
  } else {
2419
2294
  realCol = 7 - col;
2420
2295
  }
2421
-
2422
2296
  return [realRow + 1, realCol + 1];
2423
2297
  }
2424
2298
 
2425
2299
  /**
2426
- * Converts board coordinates to square ID
2300
+ * Convert board coordinates to square ID
2427
2301
  * @param {number} row - Row index (0-7)
2428
2302
  * @param {number} col - Column index (0-7)
2429
2303
  * @returns {string} Square ID (e.g., 'e4')
@@ -2431,7 +2305,6 @@ var Chessboard = (function (exports) {
2431
2305
  getSquareID(row, col) {
2432
2306
  row = parseInt(row);
2433
2307
  col = parseInt(col);
2434
-
2435
2308
  if (this.isWhiteOriented()) {
2436
2309
  row = 8 - row;
2437
2310
  col = col + 1;
@@ -2439,7 +2312,6 @@ var Chessboard = (function (exports) {
2439
2312
  row = row + 1;
2440
2313
  col = 8 - col;
2441
2314
  }
2442
-
2443
2315
  if (col < 1 || col > 8 || row < 1 || row > 8) {
2444
2316
  throw new ValidationError(
2445
2317
  `Invalid board coordinates: row=${row}, col=${col}`,
@@ -2447,13 +2319,12 @@ var Chessboard = (function (exports) {
2447
2319
  { row, col }
2448
2320
  );
2449
2321
  }
2450
-
2451
2322
  const letter = BOARD_LETTERS[col - 1];
2452
2323
  return letter + row;
2453
2324
  }
2454
2325
 
2455
2326
  /**
2456
- * Converts square ID to board coordinates
2327
+ * Convert square ID to board coordinates
2457
2328
  * @param {string} squareId - Square ID (e.g., 'e4')
2458
2329
  * @returns {Array<number>} Board coordinates [row, col] (0-7)
2459
2330
  */
@@ -2465,10 +2336,8 @@ var Chessboard = (function (exports) {
2465
2336
  squareId
2466
2337
  );
2467
2338
  }
2468
-
2469
2339
  const letter = squareId[0];
2470
2340
  const number = parseInt(squareId[1]);
2471
-
2472
2341
  const col = BOARD_LETTERS.indexOf(letter);
2473
2342
  if (col === -1) {
2474
2343
  throw new ValidationError(
@@ -2477,7 +2346,6 @@ var Chessboard = (function (exports) {
2477
2346
  squareId
2478
2347
  );
2479
2348
  }
2480
-
2481
2349
  if (number < 1 || number > 8) {
2482
2350
  throw new ValidationError(
2483
2351
  ERROR_MESSAGES.invalid_square + squareId,
@@ -2485,9 +2353,7 @@ var Chessboard = (function (exports) {
2485
2353
  squareId
2486
2354
  );
2487
2355
  }
2488
-
2489
2356
  let row, boardCol;
2490
-
2491
2357
  if (this.isWhiteOriented()) {
2492
2358
  row = 8 - number;
2493
2359
  boardCol = col;
@@ -2495,12 +2361,11 @@ var Chessboard = (function (exports) {
2495
2361
  row = number - 1;
2496
2362
  boardCol = 7 - col;
2497
2363
  }
2498
-
2499
2364
  return [row, boardCol];
2500
2365
  }
2501
2366
 
2502
2367
  /**
2503
- * Converts pixel coordinates to square ID
2368
+ * Convert pixel coordinates to square ID
2504
2369
  * @param {number} x - X coordinate in pixels
2505
2370
  * @param {number} y - Y coordinate in pixels
2506
2371
  * @param {HTMLElement} boardElement - Board DOM element
@@ -2508,21 +2373,15 @@ var Chessboard = (function (exports) {
2508
2373
  */
2509
2374
  pixelToSquareID(x, y, boardElement) {
2510
2375
  if (!boardElement) return null;
2511
-
2512
2376
  const rect = boardElement.getBoundingClientRect();
2513
2377
  const { width, height } = rect;
2514
-
2515
- // Check if coordinates are within board bounds
2516
2378
  if (x < 0 || x >= width || y < 0 || y >= height) {
2517
2379
  return null;
2518
2380
  }
2519
-
2520
2381
  const squareWidth = width / 8;
2521
2382
  const squareHeight = height / 8;
2522
-
2523
2383
  const col = Math.floor(x / squareWidth);
2524
2384
  const row = Math.floor(y / squareHeight);
2525
-
2526
2385
  try {
2527
2386
  return this.getSquareID(row, col);
2528
2387
  } catch (error) {
@@ -2531,25 +2390,21 @@ var Chessboard = (function (exports) {
2531
2390
  }
2532
2391
 
2533
2392
  /**
2534
- * Converts square ID to pixel coordinates
2393
+ * Convert square ID to pixel coordinates
2535
2394
  * @param {string} squareId - Square ID (e.g., 'e4')
2536
2395
  * @param {HTMLElement} boardElement - Board DOM element
2537
2396
  * @returns {Object|null} Pixel coordinates {x, y} or null if invalid
2538
2397
  */
2539
2398
  squareIDToPixel(squareId, boardElement) {
2540
2399
  if (!boardElement) return null;
2541
-
2542
2400
  try {
2543
2401
  const [row, col] = this.getCoordinatesFromSquareID(squareId);
2544
2402
  const rect = boardElement.getBoundingClientRect();
2545
2403
  const { width, height } = rect;
2546
-
2547
2404
  const squareWidth = width / 8;
2548
2405
  const squareHeight = height / 8;
2549
-
2550
2406
  const x = col * squareWidth;
2551
2407
  const y = row * squareHeight;
2552
-
2553
2408
  return { x, y };
2554
2409
  } catch (error) {
2555
2410
  return null;
@@ -2557,7 +2412,7 @@ var Chessboard = (function (exports) {
2557
2412
  }
2558
2413
 
2559
2414
  /**
2560
- * Gets the center pixel coordinates of a square
2415
+ * Get the center pixel coordinates of a square
2561
2416
  * @param {string} squareId - Square ID (e.g., 'e4')
2562
2417
  * @param {HTMLElement} boardElement - Board DOM element
2563
2418
  * @returns {Object|null} Center coordinates {x, y} or null if invalid
@@ -2565,11 +2420,9 @@ var Chessboard = (function (exports) {
2565
2420
  getSquareCenter(squareId, boardElement) {
2566
2421
  const coords = this.squareIDToPixel(squareId, boardElement);
2567
2422
  if (!coords) return null;
2568
-
2569
2423
  const rect = boardElement.getBoundingClientRect();
2570
2424
  const squareWidth = rect.width / 8;
2571
2425
  const squareHeight = rect.height / 8;
2572
-
2573
2426
  return {
2574
2427
  x: coords.x + squareWidth / 2,
2575
2428
  y: coords.y + squareHeight / 2
@@ -2577,7 +2430,7 @@ var Chessboard = (function (exports) {
2577
2430
  }
2578
2431
 
2579
2432
  /**
2580
- * Calculates the distance between two squares
2433
+ * Calculate the distance between two squares
2581
2434
  * @param {string} fromSquare - Source square ID
2582
2435
  * @param {string} toSquare - Target square ID
2583
2436
  * @returns {number} Distance between squares
@@ -2586,10 +2439,8 @@ var Chessboard = (function (exports) {
2586
2439
  try {
2587
2440
  const [fromRow, fromCol] = this.getCoordinatesFromSquareID(fromSquare);
2588
2441
  const [toRow, toCol] = this.getCoordinatesFromSquareID(toSquare);
2589
-
2590
2442
  const rowDiff = Math.abs(toRow - fromRow);
2591
2443
  const colDiff = Math.abs(toCol - fromCol);
2592
-
2593
2444
  return Math.sqrt(rowDiff * rowDiff + colDiff * colDiff);
2594
2445
  } catch (error) {
2595
2446
  return 0;
@@ -2597,7 +2448,7 @@ var Chessboard = (function (exports) {
2597
2448
  }
2598
2449
 
2599
2450
  /**
2600
- * Checks if the board is oriented from white's perspective
2451
+ * Check if the board is oriented from white's perspective
2601
2452
  * @returns {boolean} True if white-oriented
2602
2453
  */
2603
2454
  isWhiteOriented() {
@@ -2605,7 +2456,7 @@ var Chessboard = (function (exports) {
2605
2456
  }
2606
2457
 
2607
2458
  /**
2608
- * Checks if the board is oriented from black's perspective
2459
+ * Check if the board is oriented from black's perspective
2609
2460
  * @returns {boolean} True if black-oriented
2610
2461
  */
2611
2462
  isBlackOriented() {
@@ -2613,31 +2464,31 @@ var Chessboard = (function (exports) {
2613
2464
  }
2614
2465
 
2615
2466
  /**
2616
- * Flips the board orientation
2467
+ * Flip the board orientation
2617
2468
  */
2618
2469
  flipOrientation() {
2619
2470
  this.config.orientation = this.isWhiteOriented() ? 'b' : 'w';
2620
2471
  }
2621
2472
 
2622
2473
  /**
2623
- * Sets the board orientation
2624
- * @param {string} orientation - 'w' for white, 'b' for black
2625
- * @throws {ValidationError} When orientation is invalid
2474
+ * Set the board orientation
2475
+ * @param {string} orientation - 'w', 'b', 'white', or 'black'
2476
+ * @throws {ValidationError} If orientation is invalid
2626
2477
  */
2627
2478
  setOrientation(orientation) {
2628
- if (orientation !== 'w' && orientation !== 'b') {
2479
+ const normalized = orientation === 'white' ? 'w' : orientation === 'black' ? 'b' : orientation;
2480
+ if (normalized !== 'w' && normalized !== 'b') {
2629
2481
  throw new ValidationError(
2630
2482
  ERROR_MESSAGES.invalid_orientation + orientation,
2631
2483
  'orientation',
2632
2484
  orientation
2633
2485
  );
2634
2486
  }
2635
-
2636
- this.config.orientation = orientation;
2487
+ this.config.orientation = normalized;
2637
2488
  }
2638
2489
 
2639
2490
  /**
2640
- * Gets the current orientation
2491
+ * Get the current orientation
2641
2492
  * @returns {string} Current orientation ('w' or 'b')
2642
2493
  */
2643
2494
  getOrientation() {
@@ -2645,50 +2496,21 @@ var Chessboard = (function (exports) {
2645
2496
  }
2646
2497
 
2647
2498
  /**
2648
- * Sets the orientation
2649
- * @param {string} orientation - New orientation ('w', 'b', 'white', 'black')
2650
- */
2651
- setOrientation(orientation) {
2652
- // Normalize orientation
2653
- const normalizedOrientation = orientation === 'white' ? 'w' :
2654
- orientation === 'black' ? 'b' : orientation;
2655
-
2656
- if (normalizedOrientation !== 'w' && normalizedOrientation !== 'b') {
2657
- throw new ValidationError(
2658
- ERROR_MESSAGES.invalid_orientation + orientation,
2659
- 'orientation',
2660
- orientation
2661
- );
2662
- }
2663
-
2664
- this.config.orientation = normalizedOrientation;
2665
- }
2666
-
2667
- /**
2668
- * Flips the board orientation
2669
- */
2670
- flipOrientation() {
2671
- this.config.orientation = this.isWhiteOriented() ? 'b' : 'w';
2672
- }
2673
-
2674
- /**
2675
- * Gets all square IDs in order
2499
+ * Get all square IDs in order
2676
2500
  * @returns {Array<string>} Array of all square IDs
2677
2501
  */
2678
2502
  getAllSquareIDs() {
2679
2503
  const squares = [];
2680
-
2681
2504
  for (let row = 0; row < 8; row++) {
2682
2505
  for (let col = 0; col < 8; col++) {
2683
2506
  squares.push(this.getSquareID(row, col));
2684
2507
  }
2685
2508
  }
2686
-
2687
2509
  return squares;
2688
2510
  }
2689
2511
 
2690
2512
  /**
2691
- * Gets squares in a specific rank (row)
2513
+ * Get all squares in a specific rank (row)
2692
2514
  * @param {number} rank - Rank number (1-8)
2693
2515
  * @returns {Array<string>} Array of square IDs in the rank
2694
2516
  */
@@ -2700,19 +2522,16 @@ var Chessboard = (function (exports) {
2700
2522
  rank
2701
2523
  );
2702
2524
  }
2703
-
2704
2525
  const squares = [];
2705
-
2706
2526
  for (let col = 0; col < 8; col++) {
2707
2527
  const row = this.isWhiteOriented() ? 8 - rank : rank - 1;
2708
2528
  squares.push(this.getSquareID(row, col));
2709
2529
  }
2710
-
2711
2530
  return squares;
2712
2531
  }
2713
2532
 
2714
2533
  /**
2715
- * Gets squares in a specific file (column)
2534
+ * Get all squares in a specific file (column)
2716
2535
  * @param {string} file - File letter (a-h)
2717
2536
  * @returns {Array<string>} Array of square IDs in the file
2718
2537
  */
@@ -2725,13 +2544,10 @@ var Chessboard = (function (exports) {
2725
2544
  file
2726
2545
  );
2727
2546
  }
2728
-
2729
2547
  const squares = [];
2730
-
2731
2548
  for (let row = 0; row < 8; row++) {
2732
2549
  squares.push(this.getSquareID(row, col));
2733
2550
  }
2734
-
2735
2551
  return squares;
2736
2552
  }
2737
2553
  }
@@ -3111,6 +2927,67 @@ var Chessboard = (function (exports) {
3111
2927
  }
3112
2928
  };
3113
2929
 
2930
+ /**
2931
+ * ListenerManager - Utility per la gestione centralizzata dei listener DOM
2932
+ * Permette di aggiungere, rimuovere e pulire tutti i listener in modo sicuro e senza duplicazioni.
2933
+ * @module utils/listenerManager
2934
+ * @since 2.0.0
2935
+ */
2936
+
2937
+ class ListenerManager {
2938
+ constructor() {
2939
+ /**
2940
+ * Lista di tutti i listener registrati
2941
+ * @type {Array<{element: EventTarget, type: string, handler: Function, options?: any}>}
2942
+ */
2943
+ this.listeners = [];
2944
+ }
2945
+
2946
+ /**
2947
+ * Aggiunge un listener e lo traccia per la rimozione automatica
2948
+ * @param {EventTarget} element - Elemento su cui aggiungere il listener
2949
+ * @param {string} type - Tipo di evento
2950
+ * @param {Function} handler - Funzione handler
2951
+ * @param {Object|boolean} [options] - Opzioni per addEventListener
2952
+ */
2953
+ add(element, type, handler, options) {
2954
+ if (!element || !type || !handler) return;
2955
+ element.addEventListener(type, handler, options);
2956
+ this.listeners.push({ element, type, handler, options });
2957
+ }
2958
+
2959
+ /**
2960
+ * Rimuove un listener specifico
2961
+ * @param {EventTarget} element
2962
+ * @param {string} type
2963
+ * @param {Function} handler
2964
+ */
2965
+ remove(element, type, handler) {
2966
+ if (!element || !type || !handler) return;
2967
+ element.removeEventListener(type, handler);
2968
+ this.listeners = this.listeners.filter(
2969
+ l => !(l.element === element && l.type === type && l.handler === handler)
2970
+ );
2971
+ }
2972
+
2973
+ /**
2974
+ * Rimuove tutti i listener registrati
2975
+ */
2976
+ removeAll() {
2977
+ for (const { element, type, handler, options } of this.listeners) {
2978
+ element.removeEventListener(type, handler, options);
2979
+ }
2980
+ this.listeners = [];
2981
+ }
2982
+
2983
+ /**
2984
+ * Pulizia risorse
2985
+ */
2986
+ destroy() {
2987
+ this.removeAll();
2988
+ }
2989
+ }
2990
+
3114
2991
  /**
3115
2992
  * Service for managing events and user interactions
3116
2993
  * @module services/EventService
@@ -3143,8 +3020,8 @@ var Chessboard = (function (exports) {
3143
3020
  this.promoting = false;
3144
3021
  this.isAnimating = false;
3145
3022
 
3146
- // Event listeners storage for cleanup
3147
- this.eventListeners = new Map();
3023
+ // Listener manager per gestione centralizzata
3024
+ this.listenerManager = new ListenerManager();
3148
3025
  }
3149
3026
 
3150
3027
  /**
@@ -3154,8 +3031,8 @@ var Chessboard = (function (exports) {
3154
3031
  * @param {Function} onPieceLeave - Callback for piece leave
3155
3032
  */
3156
3033
  addListeners(onSquareClick, onPieceHover, onPieceLeave) {
3157
- // Remove existing listeners to avoid duplicates
3158
- this.removeListeners();
3034
+ // Rimuovi tutti i listener esistenti
3035
+ this.listenerManager.removeAll();
3159
3036
 
3160
3037
  const squares = this.boardService.getAllSquares();
3161
3038
 
@@ -3173,8 +3050,6 @@ var Chessboard = (function (exports) {
3173
3050
  * @param {Function} onPieceLeave - Leave callback
3174
3051
  */
3175
3052
  _addSquareListeners(square, onSquareClick, onPieceHover, onPieceLeave) {
3176
- const listeners = [];
3177
-
3178
3053
  // Throttled hover handlers for performance
3179
3054
  const throttledHover = rafThrottle((e) => {
3180
3055
  if (!this.clicked && this.config.hints) {
@@ -3244,26 +3119,13 @@ var Chessboard = (function (exports) {
3244
3119
  }
3245
3120
  };
3246
3121
 
3247
- // Add listeners
3248
- square.element.addEventListener('mouseover', throttledHover);
3249
- square.element.addEventListener('mouseout', throttledLeave);
3250
- square.element.addEventListener('click', handleClick);
3251
- // Touch: separa tap e drag
3252
- square.element.addEventListener('touchstart', handleTouchStart);
3253
- square.element.addEventListener('touchmove', handleTouchMove);
3254
- square.element.addEventListener('touchend', handleTouchEnd);
3255
-
3256
- // Store listeners for cleanup
3257
- listeners.push(
3258
- { element: square.element, type: 'mouseover', handler: throttledHover },
3259
- { element: square.element, type: 'mouseout', handler: throttledLeave },
3260
- { element: square.element, type: 'click', handler: handleClick },
3261
- { element: square.element, type: 'touchstart', handler: handleTouchStart },
3262
- { element: square.element, type: 'touchmove', handler: handleTouchMove },
3263
- { element: square.element, type: 'touchend', handler: handleTouchEnd }
3264
- );
3265
-
3266
- this.eventListeners.set(square.id, listeners);
3122
+ // Usa ListenerManager per aggiungere tutti i listener
3123
+ this.listenerManager.add(square.element, 'mouseover', throttledHover);
3124
+ this.listenerManager.add(square.element, 'mouseout', throttledLeave);
3125
+ this.listenerManager.add(square.element, 'click', handleClick);
3126
+ this.listenerManager.add(square.element, 'touchstart', handleTouchStart);
3127
+ this.listenerManager.add(square.element, 'touchmove', handleTouchMove);
3128
+ this.listenerManager.add(square.element, 'touchend', handleTouchEnd);
3267
3129
  }
3268
3130
 
3269
3131
  /**
@@ -3280,18 +3142,14 @@ var Chessboard = (function (exports) {
3280
3142
  */
3281
3143
  createDragFunction(square, piece, onDragStart, onDragMove, onDrop, onSnapback, onMove, onRemove) {
3282
3144
  return (event) => {
3283
- event.preventDefault();
3284
-
3285
3145
  if (!this.config.draggable || !piece || this.isAnimating) {
3286
3146
  return;
3287
3147
  }
3288
3148
 
3289
3149
  const originalFrom = square;
3290
- let isDragging = false;
3291
3150
  let from = originalFrom;
3292
3151
  let to = square;
3293
3152
  let previousHighlight = null;
3294
-
3295
3153
  const img = piece.element;
3296
3154
 
3297
3155
  if (!this.moveService.canMove(from)) {
@@ -3302,6 +3160,15 @@ var Chessboard = (function (exports) {
3302
3160
  const isTouch = event.type && event.type.startsWith('touch');
3303
3161
  const startX = event.clientX || (event.touches && event.touches[0]?.clientX) || 0;
3304
3162
  const startY = event.clientY || (event.touches && event.touches[0]?.clientY) || 0;
3163
+ let dragStarted = false;
3164
+
3165
+ // --- Calculate offset between cursor/finger and piece top-left at drag start ---
3166
+ const pieceRect = img.getBoundingClientRect();
3167
+ const boardElement = this.boardService.element;
3168
+ const boardRect = boardElement.getBoundingClientRect();
3169
+ // Offset from cursor/finger to top-left of piece
3170
+ const offsetX = startX - pieceRect.left;
3171
+ const offsetY = startY - pieceRect.top;
3305
3172
 
3306
3173
  // --- Touch scroll lock helper ---
3307
3174
  const addScrollLock = () => {
@@ -3313,10 +3180,6 @@ var Chessboard = (function (exports) {
3313
3180
 
3314
3181
  // --- MOVE HANDLER (mouse + touch unified) ---
3315
3182
  const moveAt = (event) => {
3316
- const boardElement = this.boardService.element;
3317
- const squareSize = boardElement.offsetWidth / 8;
3318
-
3319
- // Get mouse/touch coordinates
3320
3183
  let clientX, clientY;
3321
3184
  if (event.touches && event.touches[0]) {
3322
3185
  clientX = event.touches[0].clientX;
@@ -3325,77 +3188,54 @@ var Chessboard = (function (exports) {
3325
3188
  clientX = event.clientX;
3326
3189
  clientY = event.clientY;
3327
3190
  }
3328
-
3329
- // Calculate position relative to board
3330
- const boardRect = boardElement.getBoundingClientRect();
3331
- const x = clientX - boardRect.left - (squareSize / 2);
3332
- const y = clientY - boardRect.top - (squareSize / 2);
3333
-
3191
+ // Position the piece so the cursor/finger stays at the same relative point
3192
+ const x = clientX - boardRect.left - offsetX;
3193
+ const y = clientY - boardRect.top - offsetY;
3334
3194
  img.style.left = x + 'px';
3335
3195
  img.style.top = y + 'px';
3336
-
3337
3196
  return true;
3338
3197
  };
3339
3198
 
3340
3199
  // --- DRAG MOVE (mouse + touch) ---
3200
+ const moveThreshold = 5; // px
3341
3201
  const onPointerMove = (event) => {
3342
- // For touch, only handle the first finger
3343
3202
  if (event.touches && event.touches.length > 1) return;
3344
3203
  if (event.touches && !event.touches[0]) return;
3345
- if (event.type && event.type.startsWith('touch')) event.preventDefault();
3346
-
3347
3204
  const currentX = event.clientX || (event.touches && event.touches[0]?.clientX) || 0;
3348
3205
  const currentY = event.clientY || (event.touches && event.touches[0]?.clientY) || 0;
3349
3206
  const deltaX = Math.abs(currentX - startX);
3350
3207
  const deltaY = Math.abs(currentY - startY);
3351
-
3352
- // Start dragging if mouse/touch moved enough
3353
- if (!isDragging && (deltaX > 3 || deltaY > 3)) {
3354
- isDragging = true;
3355
- // Inizio drag: blocca update board
3208
+ if (!dragStarted && (deltaX > moveThreshold || deltaY > moveThreshold)) {
3209
+ dragStarted = true;
3210
+ if (isTouch) addScrollLock();
3356
3211
  if (this.chessboard) this.chessboard._isDragging = true;
3357
-
3358
- // Mostra hint all'inizio del drag se attivi
3359
3212
  if (this.config.hints && typeof this.chessboard._boundOnPieceHover === 'function') {
3360
3213
  this.chessboard._boundOnPieceHover(from);
3361
3214
  }
3362
-
3363
- // Set up drag state
3364
3215
  if (!this.config.clickable) {
3365
3216
  this.clicked = null;
3366
3217
  this.clicked = from;
3367
3218
  } else if (!this.clicked) {
3368
3219
  this.clicked = from;
3369
3220
  }
3370
-
3371
- // Visual feedback
3372
3221
  if (this.config.clickable) {
3373
3222
  from.select();
3374
3223
  }
3375
-
3376
- // Prepare piece for dragging
3377
3224
  img.style.position = 'absolute';
3378
3225
  img.style.zIndex = '100';
3379
3226
  img.classList.add('dragging');
3380
-
3381
3227
  DragOptimizations.enableForDrag(img);
3382
-
3383
- // Lock scroll for touch
3384
- if (isTouch) addScrollLock();
3385
-
3386
- // Call drag start callback
3387
3228
  if (!onDragStart(square, piece)) {
3388
3229
  return;
3389
3230
  }
3390
3231
  }
3391
-
3392
- if (!isDragging) return;
3393
-
3232
+ // Only block scroll and call preventDefault after drag has started
3233
+ if (dragStarted && isTouch && event.cancelable) {
3234
+ event.preventDefault();
3235
+ }
3236
+ if (!dragStarted) return;
3394
3237
  if (!moveAt(event)) ;
3395
-
3396
3238
  // Update target square
3397
- const boardElement = this.boardService.element;
3398
- const boardRect = boardElement.getBoundingClientRect();
3399
3239
  let clientX, clientY;
3400
3240
  if (event.touches && event.touches[0]) {
3401
3241
  clientX = event.touches[0].clientX;
@@ -3406,22 +3246,16 @@ var Chessboard = (function (exports) {
3406
3246
  }
3407
3247
  const x = clientX - boardRect.left;
3408
3248
  const y = clientY - boardRect.top;
3409
-
3410
3249
  let newTo = null;
3411
3250
  if (x >= 0 && x <= boardRect.width && y >= 0 && y <= boardRect.height) {
3412
3251
  const squareId = this.coordinateService.pixelToSquareID(x, y, boardElement);
3413
3252
  newTo = squareId ? this.boardService.getSquare(squareId) : null;
3414
3253
  }
3415
-
3416
3254
  to = newTo;
3417
3255
  onDragMove(from, to, piece);
3418
-
3419
- // Update visual feedback
3420
- if (to !== previousHighlight) {
3421
- to?.highlight();
3422
- previousHighlight?.dehighlight();
3423
- previousHighlight = to;
3424
- }
3256
+ if (to) to.highlight();
3257
+ if (previousHighlight && previousHighlight !== to) previousHighlight.dehighlight();
3258
+ previousHighlight = to;
3425
3259
  };
3426
3260
 
3427
3261
  // --- DRAG END (mouse + touch) ---
@@ -3432,26 +3266,19 @@ var Chessboard = (function (exports) {
3432
3266
  document.removeEventListener('touchmove', onPointerMove);
3433
3267
  window.removeEventListener('touchend', onPointerUp);
3434
3268
  if (isTouch) removeScrollLock();
3435
- // Fine drag: sblocca update board
3436
3269
  if (this.chessboard) this.chessboard._isDragging = false;
3437
-
3438
- // Rimuovi hint alla fine del drag se attivi
3439
3270
  if (this.config.hints && typeof this.chessboard._boundOnPieceLeave === 'function') {
3440
3271
  this.chessboard._boundOnPieceLeave(from);
3441
3272
  }
3442
-
3443
- if (!isDragging) {
3273
+ if (!dragStarted) {
3444
3274
  return;
3445
3275
  }
3446
-
3447
3276
  img.style.zIndex = '20';
3448
3277
  img.classList.remove('dragging');
3449
3278
  img.style.willChange = 'auto';
3450
-
3451
3279
  // Handle drop
3452
3280
  const dropResult = onDrop(originalFrom, to, piece);
3453
3281
  const isTrashDrop = !to && (this.config.dropOffBoard === 'trash' || dropResult === 'trash');
3454
-
3455
3282
  if (isTrashDrop) {
3456
3283
  this._handleTrashDrop(originalFrom, onRemove);
3457
3284
  } else if (!to) {
@@ -3465,16 +3292,12 @@ var Chessboard = (function (exports) {
3465
3292
  }
3466
3293
  };
3467
3294
 
3468
- // --- Attach listeners (mouse + touch) ---
3295
+ // Attach listeners (mouse + touch)
3469
3296
  window.addEventListener('mouseup', onPointerUp, { once: true });
3470
3297
  document.addEventListener('mousemove', onPointerMove);
3471
3298
  img.addEventListener('mouseup', onPointerUp, { once: true });
3472
- // Touch events
3473
3299
  window.addEventListener('touchend', onPointerUp, { once: true });
3474
3300
  document.addEventListener('touchmove', onPointerMove, { passive: false });
3475
-
3476
- // Per robustezza: se il drag parte da touch, blocca subito lo scroll
3477
- if (isTouch) addScrollLock();
3478
3301
  };
3479
3302
  }
3480
3303
 
@@ -3967,33 +3790,21 @@ var Chessboard = (function (exports) {
3967
3790
  * Removes all existing event listeners
3968
3791
  */
3969
3792
  removeListeners() {
3970
- this.eventListeners.forEach((listeners, squareId) => {
3971
- listeners.forEach(({ element, type, handler }) => {
3972
- element.removeEventListener(type, handler);
3973
- });
3974
- });
3975
-
3976
- this.eventListeners.clear();
3793
+ this.listenerManager.removeAll();
3977
3794
  }
3978
3795
 
3979
3796
  /**
3980
3797
  * Removes all event listeners
3981
3798
  */
3982
3799
  removeAllListeners() {
3983
- this.eventListeners.forEach((listeners, squareId) => {
3984
- listeners.forEach(({ element, type, handler }) => {
3985
- element.removeEventListener(type, handler);
3986
- });
3987
- });
3988
-
3989
- this.eventListeners.clear();
3800
+ this.listenerManager.removeAll();
3990
3801
  }
3991
3802
 
3992
3803
  /**
3993
3804
  * Cleans up resources
3994
3805
  */
3995
3806
  destroy() {
3996
- this.removeAllListeners();
3807
+ this.listenerManager.destroy();
3997
3808
  this.clicked = null;
3998
3809
  this.promoting = false;
3999
3810
  this.isAnimating = false;
@@ -4001,7 +3812,7 @@ var Chessboard = (function (exports) {
4001
3812
  }
4002
3813
 
4003
3814
  /**
4004
- * Service for managing chess moves and move validation
3815
+ * MoveService - Handles chess move management and validation
4005
3816
  * @module services/MoveService
4006
3817
  * @since 2.0.0
4007
3818
  */
@@ -4009,13 +3820,13 @@ var Chessboard = (function (exports) {
4009
3820
 
4010
3821
  /**
4011
3822
  * Service responsible for move management and validation
4012
- * @class
3823
+ * @class MoveService
4013
3824
  */
4014
3825
  class MoveService {
4015
3826
  /**
4016
- * Creates a new MoveService instance
4017
- * @param {ChessboardConfig} config - Board configuration
4018
- * @param {PositionService} positionService - Position service instance
3827
+ * Create a new MoveService instance
3828
+ * @param {Object} config - Board configuration
3829
+ * @param {Object} positionService - Position service instance
4019
3830
  */
4020
3831
  constructor(config, positionService) {
4021
3832
  this.config = config;
@@ -4025,43 +3836,32 @@ var Chessboard = (function (exports) {
4025
3836
  }
4026
3837
 
4027
3838
  /**
4028
- * Checks if a piece on a square can move
3839
+ * Check if a piece on a square can move
4029
3840
  * @param {Square} square - Square to check
4030
3841
  * @returns {boolean} True if piece can move
4031
3842
  */
4032
3843
  canMove(square) {
4033
3844
  if (!square.piece) return false;
4034
-
4035
3845
  const { movableColors, onlyLegalMoves } = this.config;
4036
-
4037
3846
  if (movableColors === 'none') return false;
4038
3847
  if (movableColors === 'w' && square.piece.color === 'b') return false;
4039
3848
  if (movableColors === 'b' && square.piece.color === 'w') return false;
4040
-
4041
3849
  if (!onlyLegalMoves) return true;
4042
-
4043
- // Check if position service and game are available
4044
- if (!this.positionService || !this.positionService.getGame()) {
4045
- return false;
4046
- }
4047
-
3850
+ if (!this.positionService || !this.positionService.getGame()) return false;
4048
3851
  const game = this.positionService.getGame();
4049
3852
  return square.piece.color === game.turn();
4050
3853
  }
4051
3854
 
4052
3855
  /**
4053
- * Converts various move formats to a Move instance
3856
+ * Convert various move formats to a Move instance
4054
3857
  * @param {string|Move|Object} move - Move in various formats
4055
3858
  * @param {Object} squares - All board squares
4056
3859
  * @returns {Move} Move instance
4057
3860
  * @throws {MoveError} When move format is invalid
4058
3861
  */
4059
3862
  convertMove(move, squares) {
4060
- if (move instanceof Move$1) {
4061
- return move;
4062
- }
3863
+ if (move instanceof Move$1) return move;
4063
3864
  if (typeof move === 'object' && move.from && move.to) {
4064
- // Se sono id, converto in oggetti; se sono già oggetti, li uso direttamente
4065
3865
  const fromSquare = typeof move.from === 'string' ? squares[move.from] : move.from;
4066
3866
  const toSquare = typeof move.to === 'string' ? squares[move.to] : move.to;
4067
3867
  if (!fromSquare || !toSquare) throw new MoveError(ERROR_MESSAGES.invalid_move_format, move.from, move.to);
@@ -4080,13 +3880,12 @@ var Chessboard = (function (exports) {
4080
3880
  }
4081
3881
 
4082
3882
  /**
4083
- * Checks if a move is legal
3883
+ * Check if a move is legal
4084
3884
  * @param {Move} move - Move to check
4085
3885
  * @returns {boolean} True if move is legal
4086
3886
  */
4087
3887
  isLegalMove(move) {
4088
3888
  const legalMoves = this.getLegalMoves(move.from.id);
4089
-
4090
3889
  return legalMoves.some(legalMove =>
4091
3890
  legalMove.to === move.to.id &&
4092
3891
  move.promotion === legalMove.promotion
@@ -4094,180 +3893,100 @@ var Chessboard = (function (exports) {
4094
3893
  }
4095
3894
 
4096
3895
  /**
4097
- * Gets all legal moves for a square or the entire position
3896
+ * Get all legal moves for a square or the entire position
4098
3897
  * @param {string} [from] - Square to get moves from (optional)
4099
3898
  * @param {boolean} [verbose=true] - Whether to return verbose move objects
4100
3899
  * @returns {Array} Array of legal moves
4101
3900
  */
4102
3901
  getLegalMoves(from = null, verbose = true) {
4103
- // Check if position service and game are available
4104
- if (!this.positionService || !this.positionService.getGame()) {
4105
- return [];
4106
- }
4107
-
3902
+ if (!this.positionService || !this.positionService.getGame()) return [];
4108
3903
  const game = this.positionService.getGame();
4109
-
4110
3904
  if (!game) return [];
4111
-
4112
3905
  const options = { verbose };
4113
- if (from) {
4114
- options.square = from;
4115
- }
4116
-
3906
+ if (from) options.square = from;
4117
3907
  return game.moves(options);
4118
3908
  }
4119
3909
 
4120
3910
  /**
4121
- * Gets legal moves with caching for performance
3911
+ * Get legal moves with caching for performance
4122
3912
  * @param {Square} square - Square to get moves from
4123
3913
  * @returns {Array} Array of legal moves
4124
3914
  */
4125
3915
  getCachedLegalMoves(square) {
4126
- // Check if position service and game are available
4127
- if (!this.positionService || !this.positionService.getGame()) {
4128
- return [];
4129
- }
4130
-
3916
+ if (!this.positionService || !this.positionService.getGame()) return [];
4131
3917
  const game = this.positionService.getGame();
4132
3918
  if (!game) return [];
4133
-
4134
3919
  const cacheKey = `${square.id}-${game.fen()}`;
4135
3920
  let moves = this._movesCache.get(cacheKey);
4136
-
4137
3921
  if (!moves) {
4138
3922
  moves = game.moves({ square: square.id, verbose: true });
4139
3923
  this._movesCache.set(cacheKey, moves);
4140
-
4141
- // Clear cache after a short delay to prevent memory buildup
4142
- if (this._cacheTimeout) {
4143
- clearTimeout(this._cacheTimeout);
4144
- }
4145
-
3924
+ if (this._cacheTimeout) clearTimeout(this._cacheTimeout);
4146
3925
  this._cacheTimeout = setTimeout(() => {
4147
3926
  this._movesCache.clear();
4148
3927
  }, 1000);
4149
3928
  }
4150
-
4151
3929
  return moves;
4152
3930
  }
4153
3931
 
4154
3932
  /**
4155
- * Executes a move on the game
4156
- * @param {Move} move - Move to execute (deve essere oggetto Move)
3933
+ * Execute a move on the game
3934
+ * @param {Move} move - Move to execute (must be a Move object)
4157
3935
  * @returns {Object|null} Move result from chess.js or null if invalid
4158
3936
  */
4159
3937
  executeMove(move) {
4160
- if (!(move instanceof Move$1)) throw new Error('executeMove richiede un oggetto Move');
4161
- if (!this.positionService || !this.positionService.getGame()) {
4162
- return null;
4163
- }
3938
+ if (!(move instanceof Move$1)) throw new Error('executeMove requires a Move object');
3939
+ if (!this.positionService || !this.positionService.getGame()) return null;
4164
3940
  const game = this.positionService.getGame();
4165
3941
  if (!game) return null;
4166
- const moveOptions = {
4167
- from: move.from.id,
4168
- to: move.to.id
4169
- };
4170
- if (move.hasPromotion()) {
4171
- moveOptions.promotion = move.promotion;
4172
- }
3942
+ const moveOptions = { from: move.from.id, to: move.to.id };
3943
+ if (move.hasPromotion()) moveOptions.promotion = move.promotion;
4173
3944
  const result = game.move(moveOptions);
4174
3945
  return result;
4175
3946
  }
4176
3947
 
4177
3948
  /**
4178
- * Determina se una mossa richiede promozione
4179
- * @param {Move} move - Deve essere oggetto Move
3949
+ * Determine if a move requires promotion
3950
+ * @param {Move} move - Must be a Move object
4180
3951
  * @returns {boolean}
4181
3952
  */
4182
3953
  requiresPromotion(move) {
4183
- if (!(move instanceof Move$1)) throw new Error('requiresPromotion richiede un oggetto Move');
4184
- console.log('Checking if move requires promotion:', move.from.id, '->', move.to.id);
4185
-
4186
- if (!this.config.onlyLegalMoves) {
4187
- console.log('Not in legal moves mode, no promotion required');
4188
- return false;
4189
- }
4190
-
3954
+ if (!(move instanceof Move$1)) throw new Error('requiresPromotion requires a Move object');
3955
+ if (!this.config.onlyLegalMoves) return false;
4191
3956
  const game = this.positionService.getGame();
4192
- if (!game) {
4193
- console.log('No game instance available');
4194
- return false;
4195
- }
4196
-
3957
+ if (!game) return false;
4197
3958
  const piece = game.get(move.from.id);
4198
- if (!piece || piece.type !== 'p') {
4199
- console.log('Not a pawn move, no promotion required');
4200
- return false;
4201
- }
4202
-
3959
+ if (!piece || piece.type !== 'p') return false;
4203
3960
  const targetRank = move.to.row;
4204
- if (targetRank !== 1 && targetRank !== 8) {
4205
- console.log('Not reaching promotion rank, no promotion required');
4206
- return false;
4207
- }
4208
-
4209
- console.log('Pawn reaching promotion rank, validating move...');
4210
-
4211
- // Additional validation: check if the pawn can actually reach this square
4212
- if (!this._isPawnMoveValid(move.from, move.to, piece.color)) {
4213
- console.log('Pawn move not valid, no promotion required');
4214
- return false;
4215
- }
4216
-
4217
- // First check if the move is legal without promotion
4218
- const simpleMoveObj = {
4219
- from: move.from.id,
4220
- to: move.to.id
4221
- };
4222
-
3961
+ if (targetRank !== 1 && targetRank !== 8) return false;
3962
+ if (!this._isPawnMoveValid(move.from, move.to, piece.color)) return false;
3963
+ // Try move without promotion
3964
+ const simpleMoveObj = { from: move.from.id, to: move.to.id };
4223
3965
  try {
4224
- console.log('Testing move without promotion:', simpleMoveObj);
4225
- // Test if the move is legal without promotion first
4226
3966
  const testMove = game.move(simpleMoveObj);
4227
3967
  if (testMove) {
4228
- // Move was successful, but check if it was a promotion
4229
3968
  const wasPromotion = testMove.promotion;
4230
-
4231
- // Undo the test move
4232
3969
  game.undo();
4233
-
4234
- console.log('Move successful without promotion, was promotion:', wasPromotion !== undefined);
4235
-
4236
- // If it was a promotion, return true
4237
3970
  return wasPromotion !== undefined;
4238
3971
  }
4239
3972
  } catch (error) {
4240
- console.log('Move failed without promotion, trying with promotion:', error.message);
4241
-
4242
- // If simple move fails, try with promotion
4243
- const promotionMoveObj = {
4244
- from: move.from.id,
4245
- to: move.to.id,
4246
- promotion: 'q' // test with queen
4247
- };
4248
-
3973
+ // Try with promotion
3974
+ const promotionMoveObj = { from: move.from.id, to: move.to.id, promotion: 'q' };
4249
3975
  try {
4250
- console.log('Testing move with promotion:', promotionMoveObj);
4251
3976
  const testMove = game.move(promotionMoveObj);
4252
3977
  if (testMove) {
4253
- // Undo the test move
4254
3978
  game.undo();
4255
- console.log('Move successful with promotion, promotion required');
4256
3979
  return true;
4257
3980
  }
4258
3981
  } catch (promotionError) {
4259
- console.log('Move failed even with promotion:', promotionError.message);
4260
- // Move is not legal even with promotion
4261
3982
  return false;
4262
3983
  }
4263
3984
  }
4264
-
4265
- console.log('Move validation complete, no promotion required');
4266
3985
  return false;
4267
3986
  }
4268
3987
 
4269
3988
  /**
4270
- * Validates if a pawn move is theoretically possible
3989
+ * Validate if a pawn move is theoretically possible
4271
3990
  * @private
4272
3991
  * @param {Square} from - Source square
4273
3992
  * @param {Square} to - Target square
@@ -4279,48 +3998,21 @@ var Chessboard = (function (exports) {
4279
3998
  const toRank = to.row;
4280
3999
  const fromFile = from.col;
4281
4000
  const toFile = to.col;
4282
-
4283
- console.log(`Validating pawn move: ${from.id} -> ${to.id} (${color})`);
4284
- console.log(`Ranks: ${fromRank} -> ${toRank}, Files: ${fromFile} -> ${toFile}`);
4285
-
4286
- // Direction of pawn movement
4287
4001
  const direction = color === 'w' ? 1 : -1;
4288
4002
  const rankDiff = toRank - fromRank;
4289
4003
  const fileDiff = Math.abs(toFile - fromFile);
4290
-
4291
- // Pawn can only move forward
4292
- if (rankDiff * direction <= 0) {
4293
- console.log('Invalid: Pawn cannot move backward or stay in place');
4294
- return false;
4295
- }
4296
-
4297
- // Pawn can only move 1 rank at a time (except for double move from starting position)
4298
- if (Math.abs(rankDiff) > 2) {
4299
- console.log('Invalid: Pawn cannot move more than 2 ranks');
4300
- return false;
4301
- }
4302
-
4303
- // If moving 2 ranks, must be from starting position
4004
+ if (rankDiff * direction <= 0) return false;
4005
+ if (Math.abs(rankDiff) > 2) return false;
4304
4006
  if (Math.abs(rankDiff) === 2) {
4305
4007
  const startingRank = color === 'w' ? 2 : 7;
4306
- if (fromRank !== startingRank) {
4307
- console.log(`Invalid: Pawn cannot move 2 ranks from rank ${fromRank}`);
4308
- return false;
4309
- }
4310
- }
4311
-
4312
- // Pawn can only move to adjacent files (diagonal capture) or same file (forward move)
4313
- if (fileDiff > 1) {
4314
- console.log('Invalid: Pawn cannot move more than 1 file');
4315
- return false;
4008
+ if (fromRank !== startingRank) return false;
4316
4009
  }
4317
-
4318
- console.log('Pawn move validation passed');
4010
+ if (fileDiff > 1) return false;
4319
4011
  return true;
4320
4012
  }
4321
4013
 
4322
4014
  /**
4323
- * Handles promotion UI setup
4015
+ * Handle promotion UI setup
4324
4016
  * @param {Move} move - Move requiring promotion
4325
4017
  * @param {Object} squares - All board squares
4326
4018
  * @param {Function} onPromotionSelect - Callback when promotion piece is selected
@@ -4329,55 +4021,33 @@ var Chessboard = (function (exports) {
4329
4021
  */
4330
4022
  setupPromotion(move, squares, onPromotionSelect, onPromotionCancel) {
4331
4023
  if (!this.requiresPromotion(move)) return false;
4332
-
4333
- // Check if position service and game are available
4334
- if (!this.positionService || !this.positionService.getGame()) {
4335
- return false;
4336
- }
4337
-
4024
+ if (!this.positionService || !this.positionService.getGame()) return false;
4338
4025
  const game = this.positionService.getGame();
4339
4026
  const piece = game.get(move.from.id);
4340
4027
  const targetSquare = move.to;
4341
-
4342
- // Clear any existing promotion UI
4343
4028
  Object.values(squares).forEach(square => {
4344
4029
  square.removePromotion();
4345
4030
  square.removeCover();
4346
4031
  });
4347
-
4348
- // Always show promotion choices in a column
4349
4032
  this._showPromotionInColumn(targetSquare, piece, squares, onPromotionSelect, onPromotionCancel);
4350
-
4351
4033
  return true;
4352
4034
  }
4353
4035
 
4354
4036
  /**
4355
- * Shows promotion choices in a column
4037
+ * Show promotion choices in a column
4356
4038
  * @private
4357
4039
  */
4358
4040
  _showPromotionInColumn(targetSquare, piece, squares, onPromotionSelect, onPromotionCancel) {
4359
- console.log('Setting up promotion for', targetSquare.id, 'piece color:', piece.color);
4360
-
4361
- // Set up promotion choices starting from border row
4362
4041
  PROMOTION_PIECES.forEach((pieceType, index) => {
4363
4042
  const choiceSquare = this._findPromotionSquare(targetSquare, index, squares);
4364
-
4365
4043
  if (choiceSquare) {
4366
4044
  const pieceId = pieceType + piece.color;
4367
4045
  const piecePath = this._getPiecePathForPromotion(pieceId);
4368
-
4369
- console.log('Setting up promotion choice:', pieceType, 'on square:', choiceSquare.id);
4370
-
4371
4046
  choiceSquare.putPromotion(piecePath, () => {
4372
- console.log('Promotion choice selected:', pieceType);
4373
4047
  onPromotionSelect(pieceType);
4374
4048
  });
4375
- } else {
4376
- console.log('Could not find square for promotion choice:', pieceType, 'index:', index);
4377
4049
  }
4378
4050
  });
4379
-
4380
- // Set up cover squares (for cancellation)
4381
4051
  Object.values(squares).forEach(square => {
4382
4052
  if (!square.hasPromotion()) {
4383
4053
  square.putCover(() => {
@@ -4385,181 +4055,119 @@ var Chessboard = (function (exports) {
4385
4055
  });
4386
4056
  }
4387
4057
  });
4388
-
4389
4058
  return true;
4390
4059
  }
4391
4060
 
4392
4061
  /**
4393
- * Finds the appropriate square for a promotion piece
4062
+ * Find the appropriate square for a promotion piece
4394
4063
  * @private
4395
4064
  * @param {Square} targetSquare - Target square of the promotion move
4396
- * @param {number} distance - Distance from target square
4065
+ * @param {number} index - Distance from target square
4397
4066
  * @param {Object} squares - All board squares
4398
4067
  * @returns {Square|null} Square for promotion piece or null
4399
4068
  */
4400
4069
  _findPromotionSquare(targetSquare, index, squares) {
4401
4070
  const col = targetSquare.col;
4402
4071
  const baseRow = targetSquare.row;
4403
-
4404
- console.log('Looking for promotion square - target:', targetSquare.id, 'index:', index, 'col:', col, 'baseRow:', baseRow);
4405
-
4406
- // Calculate row based on index and promotion direction
4407
- // Start from the border row (1 or 8) and go inward
4408
4072
  let row;
4409
4073
  if (baseRow === 8) {
4410
- // White promotion: start from row 8 and go down
4411
4074
  row = 8 - index;
4412
4075
  } else if (baseRow === 1) {
4413
- // Black promotion: start from row 1 and go up
4414
4076
  row = 1 + index;
4415
4077
  } else {
4416
- console.log('Invalid promotion row:', baseRow);
4417
4078
  return null;
4418
4079
  }
4419
-
4420
- console.log('Calculated row:', row);
4421
-
4422
- // Ensure row is within bounds
4423
- if (row < 1 || row > 8) {
4424
- console.log('Row out of bounds:', row);
4425
- return null;
4426
- }
4427
-
4428
- // Find square by row/col
4080
+ if (row < 1 || row > 8) return null;
4429
4081
  for (const square of Object.values(squares)) {
4430
4082
  if (square.col === col && square.row === row) {
4431
- console.log('Found promotion square:', square.id);
4432
4083
  return square;
4433
4084
  }
4434
4085
  }
4435
-
4436
- console.log('No square found for col:', col, 'row:', row);
4437
4086
  return null;
4438
4087
  }
4439
4088
 
4440
4089
  /**
4441
- * Gets piece path for promotion UI
4090
+ * Get piece path for promotion UI
4442
4091
  * @private
4443
4092
  * @param {string} pieceId - Piece identifier
4444
4093
  * @returns {string} Path to piece asset
4445
4094
  */
4446
4095
  _getPiecePathForPromotion(pieceId) {
4447
- // This would typically use the PieceService
4448
- // For now, we'll use a simple implementation
4449
4096
  const { piecesPath } = this.config;
4450
-
4451
4097
  if (typeof piecesPath === 'string') {
4452
4098
  return `${piecesPath}/${pieceId}.svg`;
4453
4099
  }
4454
-
4455
- // Fallback for other path types
4456
4100
  return `assets/pieces/${pieceId}.svg`;
4457
4101
  }
4458
4102
 
4459
4103
  /**
4460
- * Parses a move string into a move object
4104
+ * Parse a move string into a move object
4461
4105
  * @param {string} moveString - Move string (e.g., 'e2e4', 'e7e8q')
4462
4106
  * @returns {Object|null} Move object or null if invalid
4463
4107
  */
4464
4108
  parseMove(moveString) {
4465
- if (typeof moveString !== 'string' || moveString.length < 4 || moveString.length > 5) {
4466
- return null;
4467
- }
4468
-
4109
+ if (typeof moveString !== 'string' || moveString.length < 4 || moveString.length > 5) return null;
4469
4110
  const from = moveString.slice(0, 2);
4470
4111
  const to = moveString.slice(2, 4);
4471
4112
  const promotion = moveString.slice(4, 5);
4472
-
4473
- // Basic validation
4474
- if (!/^[a-h][1-8]$/.test(from) || !/^[a-h][1-8]$/.test(to)) {
4475
- return null;
4476
- }
4477
-
4478
- if (promotion && !['q', 'r', 'b', 'n'].includes(promotion.toLowerCase())) {
4479
- return null;
4480
- }
4481
-
4482
- return {
4483
- from: from,
4484
- to: to,
4485
- promotion: promotion || null
4486
- };
4113
+ if (!/^[a-h][1-8]$/.test(from) || !/^[a-h][1-8]$/.test(to)) return null;
4114
+ if (promotion && !['q', 'r', 'b', 'n'].includes(promotion.toLowerCase())) return null;
4115
+ return { from, to, promotion: promotion || null };
4487
4116
  }
4488
4117
 
4489
4118
  /**
4490
- * Checks if a move is a castle move
4119
+ * Check if a move is a castle move
4491
4120
  * @param {Object} gameMove - Game move object from chess.js
4492
4121
  * @returns {boolean} True if move is castle
4493
4122
  */
4494
4123
  isCastle(gameMove) {
4495
- return gameMove && (gameMove.isKingsideCastle() || gameMove.isQueensideCastle());
4124
+ return gameMove && (gameMove.isKingsideCastle && gameMove.isKingsideCastle() || gameMove.isQueensideCastle && gameMove.isQueensideCastle());
4496
4125
  }
4497
4126
 
4498
4127
  /**
4499
- * Gets the rook move for a castle move
4128
+ * Get the rook move for a castle move
4500
4129
  * @param {Object} gameMove - Game move object from chess.js
4501
4130
  * @returns {Object|null} Rook move object or null if not castle
4502
4131
  */
4503
4132
  getCastleRookMove(gameMove) {
4504
- if (!this.isCastle(gameMove)) {
4505
- return null;
4506
- }
4507
-
4133
+ if (!this.isCastle(gameMove)) return null;
4508
4134
  const isKingSide = gameMove.isKingsideCastle();
4509
4135
  const isWhite = gameMove.color === 'w';
4510
-
4511
4136
  if (isKingSide) {
4512
- // King side castle
4513
- if (isWhite) {
4514
- return { from: 'h1', to: 'f1' };
4515
- } else {
4516
- return { from: 'h8', to: 'f8' };
4517
- }
4137
+ return isWhite ? { from: 'h1', to: 'f1' } : { from: 'h8', to: 'f8' };
4518
4138
  } else {
4519
- // Queen side castle
4520
- if (isWhite) {
4521
- return { from: 'a1', to: 'd1' };
4522
- } else {
4523
- return { from: 'a8', to: 'd8' };
4524
- }
4139
+ return isWhite ? { from: 'a1', to: 'd1' } : { from: 'a8', to: 'd8' };
4525
4140
  }
4526
4141
  }
4527
4142
 
4528
4143
  /**
4529
- * Checks if a move is en passant
4144
+ * Check if a move is en passant
4530
4145
  * @param {Object} gameMove - Game move object from chess.js
4531
4146
  * @returns {boolean} True if move is en passant
4532
4147
  */
4533
4148
  isEnPassant(gameMove) {
4534
- return gameMove && gameMove.isEnPassant();
4149
+ return gameMove && gameMove.isEnPassant && gameMove.isEnPassant();
4535
4150
  }
4536
4151
 
4537
4152
  /**
4538
- * Gets the captured pawn square for en passant
4153
+ * Get the captured pawn square for en passant
4539
4154
  * @param {Object} gameMove - Game move object from chess.js
4540
4155
  * @returns {string|null} Square of captured pawn or null if not en passant
4541
4156
  */
4542
4157
  getEnPassantCapturedSquare(gameMove) {
4543
- if (!this.isEnPassant(gameMove)) {
4544
- return null;
4545
- }
4546
-
4158
+ if (!this.isEnPassant(gameMove)) return null;
4547
4159
  const toSquare = gameMove.to;
4548
4160
  const rank = parseInt(toSquare[1]);
4549
4161
  const file = toSquare[0];
4550
-
4551
- // The captured pawn is on the same file but different rank
4552
4162
  if (gameMove.color === 'w') {
4553
- // White captures black pawn one rank below
4554
4163
  return file + (rank - 1);
4555
4164
  } else {
4556
- // Black captures white pawn one rank above
4557
4165
  return file + (rank + 1);
4558
4166
  }
4559
4167
  }
4560
4168
 
4561
4169
  /**
4562
- * Clears the moves cache
4170
+ * Clear the moves cache
4563
4171
  */
4564
4172
  clearCache() {
4565
4173
  this._movesCache.clear();
@@ -4570,7 +4178,7 @@ var Chessboard = (function (exports) {
4570
4178
  }
4571
4179
 
4572
4180
  /**
4573
- * Cleans up resources
4181
+ * Clean up resources
4574
4182
  */
4575
4183
  destroy() {
4576
4184
  this.clearCache();
@@ -4579,7 +4187,7 @@ var Chessboard = (function (exports) {
4579
4187
  }
4580
4188
 
4581
4189
  /**
4582
- * Service for managing chess pieces and their operations
4190
+ * PieceService - Handles chess piece management and operations
4583
4191
  * @module services/PieceService
4584
4192
  * @since 2.0.0
4585
4193
  */
@@ -4587,11 +4195,11 @@ var Chessboard = (function (exports) {
4587
4195
 
4588
4196
  /**
4589
4197
  * Service responsible for piece management and operations
4590
- * @class
4198
+ * @class PieceService
4591
4199
  */
4592
4200
  class PieceService {
4593
4201
  /**
4594
- * Creates a new PieceService instance
4202
+ * Create a new PieceService instance
4595
4203
  * @param {ChessboardConfig} config - Board configuration
4596
4204
  */
4597
4205
  constructor(config) {
@@ -4599,14 +4207,13 @@ var Chessboard = (function (exports) {
4599
4207
  }
4600
4208
 
4601
4209
  /**
4602
- * Gets the path to a piece asset
4210
+ * Get the path to a piece asset
4603
4211
  * @param {string} piece - Piece identifier (e.g., 'wK', 'bP')
4604
4212
  * @returns {string} Path to piece asset
4605
4213
  * @throws {ValidationError} When piecesPath configuration is invalid
4606
4214
  */
4607
4215
  getPiecePath(piece) {
4608
4216
  const { piecesPath } = this.config;
4609
-
4610
4217
  if (typeof piecesPath === 'string') {
4611
4218
  return `${piecesPath}/${piece}.svg`;
4612
4219
  } else if (typeof piecesPath === 'object' && piecesPath !== null) {
@@ -4619,7 +4226,7 @@ var Chessboard = (function (exports) {
4619
4226
  }
4620
4227
 
4621
4228
  /**
4622
- * Converts various piece formats to a Piece instance
4229
+ * Convert various piece formats to a Piece instance
4623
4230
  * @param {string|Piece} piece - Piece in various formats
4624
4231
  * @returns {Piece} Piece instance
4625
4232
  * @throws {PieceError} When piece format is invalid
@@ -4628,54 +4235,41 @@ var Chessboard = (function (exports) {
4628
4235
  if (piece instanceof Piece) {
4629
4236
  return piece;
4630
4237
  }
4631
-
4632
4238
  if (typeof piece === 'string' && piece.length === 2) {
4633
4239
  const [first, second] = piece.split('');
4634
4240
  let type, color;
4635
-
4636
- // Check format: [type][color] (e.g., 'pw')
4637
4241
  if (PIECE_TYPES.includes(first.toLowerCase()) && PIECE_COLORS.includes(second)) {
4638
4242
  type = first.toLowerCase();
4639
4243
  color = second;
4640
- }
4641
- // Check format: [color][type] (e.g., 'wP')
4642
- else if (PIECE_COLORS.includes(first) && PIECE_TYPES.includes(second.toLowerCase())) {
4244
+ } else if (PIECE_COLORS.includes(first) && PIECE_TYPES.includes(second.toLowerCase())) {
4643
4245
  color = first;
4644
4246
  type = second.toLowerCase();
4645
4247
  } else {
4646
4248
  throw new PieceError(ERROR_MESSAGES.invalid_piece + piece, piece);
4647
4249
  }
4648
-
4649
4250
  const piecePath = this.getPiecePath(type + color);
4650
4251
  return new Piece(color, type, piecePath);
4651
4252
  }
4652
-
4653
4253
  throw new PieceError(ERROR_MESSAGES.invalid_piece + piece, piece);
4654
4254
  }
4655
4255
 
4656
4256
  /**
4657
- * Adds a piece to a square with optional fade-in animation
4658
- * @param {Square} square - Target square (oggetto)
4659
- * @param {Piece} piece - Piece to add (oggetto)
4257
+ * Add a piece to a square with optional fade-in animation
4258
+ * @param {Square} square - Target square
4259
+ * @param {Piece} piece - Piece to add
4660
4260
  * @param {boolean} [fade=true] - Whether to fade in the piece
4661
4261
  * @param {Function} dragFunction - Function to handle drag events
4662
4262
  * @param {Function} [callback] - Callback when animation completes
4663
4263
  */
4664
4264
  addPieceOnSquare(square, piece, fade = true, dragFunction, callback) {
4665
- if (!square || !piece) throw new Error('addPieceOnSquare richiede oggetti Square e Piece');
4666
- console.debug(`[PieceService] addPieceOnSquare: ${piece.id} to ${square.id}`);
4265
+ if (!square || !piece) throw new Error('addPieceOnSquare requires Square and Piece objects');
4667
4266
  square.putPiece(piece);
4668
-
4669
- // Imposta sempre il drag (touch e mouse)
4670
4267
  if (dragFunction) {
4671
4268
  piece.setDrag(dragFunction(square, piece));
4672
4269
  }
4673
- // Forza il drag touch se manca (debug/robustezza)
4674
4270
  if (!piece.element.ontouchstart) {
4675
4271
  piece.element.ontouchstart = dragFunction ? dragFunction(square, piece) : () => { };
4676
- console.debug(`[PieceService] Forzato ontouchstart su ${piece.id}`);
4677
4272
  }
4678
-
4679
4273
  if (fade && this.config.fadeTime > 0) {
4680
4274
  piece.fadeIn(
4681
4275
  this.config.fadeTime,
@@ -4686,28 +4280,24 @@ var Chessboard = (function (exports) {
4686
4280
  } else {
4687
4281
  if (callback) callback();
4688
4282
  }
4689
-
4690
4283
  piece.visible();
4691
4284
  }
4692
4285
 
4693
4286
  /**
4694
- * Rimuove un pezzo da una casella
4695
- * @param {Square} square - Oggetto Square
4287
+ * Remove a piece from a square
4288
+ * @param {Square} square - Square object
4696
4289
  * @param {boolean} [fade=true]
4697
4290
  * @param {Function} [callback]
4698
- * @returns {Piece} Il pezzo rimosso
4291
+ * @returns {Piece} The removed piece
4699
4292
  */
4700
4293
  removePieceFromSquare(square, fade = true, callback) {
4701
- if (!square) throw new Error('removePieceFromSquare richiede oggetto Square');
4702
- console.debug(`[PieceService] removePieceFromSquare: ${square.id}`);
4294
+ if (!square) throw new Error('removePieceFromSquare requires a Square object');
4703
4295
  square.check();
4704
-
4705
4296
  const piece = square.piece;
4706
4297
  if (!piece) {
4707
4298
  if (callback) callback();
4708
4299
  throw new PieceError(ERROR_MESSAGES.square_no_piece, null, square.getId());
4709
4300
  }
4710
-
4711
4301
  if (fade && this.config.fadeTime > 0) {
4712
4302
  piece.fadeOut(
4713
4303
  this.config.fadeTime,
@@ -4718,26 +4308,22 @@ var Chessboard = (function (exports) {
4718
4308
  } else {
4719
4309
  if (callback) callback();
4720
4310
  }
4721
-
4722
4311
  square.removePiece();
4723
4312
  return piece;
4724
4313
  }
4725
4314
 
4726
4315
  /**
4727
- * Moves a piece to a new position with animation
4316
+ * Move a piece to a new position with animation
4728
4317
  * @param {Piece} piece - Piece to move
4729
4318
  * @param {Square} targetSquare - Target square
4730
4319
  * @param {number} duration - Animation duration
4731
4320
  * @param {Function} [callback] - Callback function when animation completes
4732
4321
  */
4733
4322
  movePiece(piece, targetSquare, duration, callback) {
4734
- console.debug(`[PieceService] movePiece: ${piece.id} to ${targetSquare.id}`);
4735
4323
  if (!piece || !piece.element) {
4736
- console.warn(`[PieceService] movePiece: piece or element is null, skipping animation`);
4737
4324
  if (callback) callback();
4738
4325
  return;
4739
4326
  }
4740
-
4741
4327
  piece.translate(
4742
4328
  targetSquare,
4743
4329
  duration,
@@ -4748,7 +4334,7 @@ var Chessboard = (function (exports) {
4748
4334
  }
4749
4335
 
4750
4336
  /**
4751
- * Handles piece translation with optional capture
4337
+ * Handle piece translation with optional capture
4752
4338
  * @param {Move} move - Move object containing from/to squares and piece
4753
4339
  * @param {boolean} removeTarget - Whether to remove piece from target square
4754
4340
  * @param {boolean} animate - Whether to animate the move
@@ -4756,54 +4342,37 @@ var Chessboard = (function (exports) {
4756
4342
  * @param {Function} [callback] - Callback function when complete
4757
4343
  */
4758
4344
  translatePiece(move, removeTarget, animate, dragFunction = null, callback = null) {
4759
- console.debug(`[PieceService] translatePiece: ${move.piece.id} from ${move.from.id} to ${move.to.id}`);
4760
4345
  if (!move.piece) {
4761
- console.warn('PieceService.translatePiece: move.piece is null, skipping translation');
4762
4346
  if (callback) callback();
4763
4347
  return;
4764
4348
  }
4765
-
4766
4349
  if (removeTarget) {
4767
- // Deselect the captured piece before removing it
4768
4350
  move.to.deselect();
4769
4351
  this.removePieceFromSquare(move.to, false);
4770
4352
  }
4771
-
4772
4353
  const changeSquareCallback = () => {
4773
- // Check if piece still exists and is on the source square
4774
4354
  if (move.from.piece === move.piece) {
4775
- move.from.removePiece(true); // Preserve the piece when moving
4355
+ move.from.removePiece(true);
4776
4356
  }
4777
-
4778
- // Only put piece if destination square doesn't already have it
4779
4357
  if (move.to.piece !== move.piece) {
4780
4358
  move.to.putPiece(move.piece);
4781
-
4782
- // Re-attach drag handler if provided
4783
4359
  if (dragFunction && this.config.draggable && move.piece.element) {
4784
4360
  move.piece.setDrag(dragFunction(move.to, move.piece));
4785
4361
  }
4786
4362
  }
4787
-
4788
4363
  if (callback) callback();
4789
4364
  };
4790
-
4791
- // Check if piece is currently being dragged
4792
4365
  const isDragging = move.piece.element && move.piece.element.classList.contains('dragging');
4793
-
4794
4366
  if (isDragging) {
4795
- // If piece is being dragged, don't animate - just move it immediately
4796
- // The piece is already visually in the correct position from the drag
4797
4367
  changeSquareCallback();
4798
4368
  } else {
4799
- // Normal animation
4800
4369
  const duration = animate ? this.config.moveTime : 0;
4801
4370
  this.movePiece(move.piece, move.to, duration, changeSquareCallback);
4802
4371
  }
4803
4372
  }
4804
4373
 
4805
4374
  /**
4806
- * Snaps a piece back to its original position
4375
+ * Snap a piece back to its original position
4807
4376
  * @param {Square} square - Square containing the piece
4808
4377
  * @param {boolean} [animate=true] - Whether to animate the snapback
4809
4378
  */
@@ -4813,7 +4382,6 @@ var Chessboard = (function (exports) {
4813
4382
  }
4814
4383
  const piece = square.piece;
4815
4384
  const duration = animate ? this.config.snapbackTime : 0;
4816
- console.debug(`[PieceService] snapbackPiece: ${piece.id} on ${square.id}`);
4817
4385
  piece.translate(
4818
4386
  square,
4819
4387
  duration,
@@ -4823,7 +4391,7 @@ var Chessboard = (function (exports) {
4823
4391
  }
4824
4392
 
4825
4393
  /**
4826
- * Centers a piece in its square with animation (after successful drop)
4394
+ * Center a piece in its square with animation (after successful drop)
4827
4395
  * @param {Square} square - Square containing the piece to center
4828
4396
  * @param {boolean} animate - Whether to animate the centering
4829
4397
  */
@@ -4833,14 +4401,12 @@ var Chessboard = (function (exports) {
4833
4401
  }
4834
4402
  const piece = square.piece;
4835
4403
  const duration = animate ? this.config.dropCenterTime : 0;
4836
- console.debug(`[PieceService] centerPiece: ${piece.id} on ${square.id}`);
4837
4404
  piece.translate(
4838
4405
  square,
4839
4406
  duration,
4840
4407
  this._getTransitionTimingFunction(),
4841
4408
  this.config.dropCenterAnimation,
4842
4409
  () => {
4843
- // After animation, reset all drag-related styles
4844
4410
  if (!piece.element) return;
4845
4411
  piece.element.style.position = '';
4846
4412
  piece.element.style.left = '';
@@ -4852,14 +4418,13 @@ var Chessboard = (function (exports) {
4852
4418
  }
4853
4419
 
4854
4420
  /**
4855
- * Gets the transition timing function for animations
4421
+ * Get the transition timing function for animations
4856
4422
  * @private
4857
4423
  * @returns {Function} Timing function
4858
4424
  */
4859
4425
  _getTransitionTimingFunction() {
4860
4426
  return (elapsed, duration, type = 'ease') => {
4861
4427
  const x = elapsed / duration;
4862
-
4863
4428
  switch (type) {
4864
4429
  case 'linear':
4865
4430
  return x;
@@ -4878,7 +4443,7 @@ var Chessboard = (function (exports) {
4878
4443
  }
4879
4444
 
4880
4445
  /**
4881
- * Cleans up resources
4446
+ * Clean up resources
4882
4447
  */
4883
4448
  destroy() {
4884
4449
  // Cleanup any cached pieces or references
@@ -6869,7 +6434,7 @@ var Chessboard = (function (exports) {
6869
6434
  }
6870
6435
 
6871
6436
  /**
6872
- * Service for managing chess positions and FEN conversion
6437
+ * PositionService - Handles chess position management and FEN conversion
6873
6438
  * @module services/PositionService
6874
6439
  * @since 2.0.0
6875
6440
  */
@@ -6877,11 +6442,11 @@ var Chessboard = (function (exports) {
6877
6442
 
6878
6443
  /**
6879
6444
  * Service responsible for position management and FEN operations
6880
- * @class
6445
+ * @class PositionService
6881
6446
  */
6882
6447
  class PositionService {
6883
6448
  /**
6884
- * Creates a new PositionService instance
6449
+ * Create a new PositionService instance
6885
6450
  * @param {ChessboardConfig} config - Board configuration
6886
6451
  */
6887
6452
  constructor(config) {
@@ -6890,7 +6455,7 @@ var Chessboard = (function (exports) {
6890
6455
  }
6891
6456
 
6892
6457
  /**
6893
- * Converts various position formats to FEN string
6458
+ * Convert various position formats to FEN string
6894
6459
  * @param {string|Object} position - Position in various formats
6895
6460
  * @returns {string} FEN string representation
6896
6461
  * @throws {ValidationError} When position format is invalid
@@ -6906,7 +6471,7 @@ var Chessboard = (function (exports) {
6906
6471
  }
6907
6472
 
6908
6473
  /**
6909
- * Converts string position to FEN
6474
+ * Convert string position to FEN
6910
6475
  * @private
6911
6476
  * @param {string} position - String position
6912
6477
  * @returns {string} FEN string
@@ -6915,35 +6480,29 @@ var Chessboard = (function (exports) {
6915
6480
  if (position === 'start') {
6916
6481
  return DEFAULT_STARTING_POSITION;
6917
6482
  }
6918
-
6919
6483
  if (this.validateFen(position)) {
6920
6484
  return position;
6921
6485
  }
6922
-
6923
6486
  if (STANDARD_POSITIONS[position]) {
6924
6487
  return STANDARD_POSITIONS[position];
6925
6488
  }
6926
-
6927
6489
  throw new ValidationError(ERROR_MESSAGES.invalid_position + position, 'position', position);
6928
6490
  }
6929
6491
 
6930
6492
  /**
6931
- * Converts object position to FEN
6493
+ * Convert object position to FEN
6932
6494
  * @private
6933
6495
  * @param {Object} position - Object with square->piece mapping
6934
6496
  * @returns {string} FEN string
6935
6497
  */
6936
6498
  _convertObjectPosition(position) {
6937
6499
  const parts = [];
6938
-
6939
6500
  for (let row = 0; row < 8; row++) {
6940
6501
  const rowParts = [];
6941
6502
  let empty = 0;
6942
-
6943
6503
  for (let col = 0; col < 8; col++) {
6944
6504
  const square = this._getSquareID(row, col);
6945
6505
  const piece = position[square];
6946
-
6947
6506
  if (piece) {
6948
6507
  if (empty > 0) {
6949
6508
  rowParts.push(empty);
@@ -6956,25 +6515,21 @@ var Chessboard = (function (exports) {
6956
6515
  empty++;
6957
6516
  }
6958
6517
  }
6959
-
6960
6518
  if (empty > 0) {
6961
6519
  rowParts.push(empty);
6962
6520
  }
6963
-
6964
6521
  parts.push(rowParts.join(''));
6965
6522
  }
6966
-
6967
6523
  return parts.join('/') + ' w KQkq - 0 1';
6968
6524
  }
6969
6525
 
6970
6526
  /**
6971
- * Sets up the game with the given position
6527
+ * Set up the game with the given position
6972
6528
  * @param {string|Object} position - Position to set
6973
6529
  * @param {Object} [options] - Additional options for game setup
6974
6530
  */
6975
6531
  setGame(position, options = {}) {
6976
6532
  const fen = this.convertFen(position);
6977
-
6978
6533
  if (this.game) {
6979
6534
  this.game.load(fen, options);
6980
6535
  } else {
@@ -6983,7 +6538,7 @@ var Chessboard = (function (exports) {
6983
6538
  }
6984
6539
 
6985
6540
  /**
6986
- * Gets the current game instance
6541
+ * Get the current game instance
6987
6542
  * @returns {Chess} Current chess.js game instance
6988
6543
  */
6989
6544
  getGame() {
@@ -6991,7 +6546,7 @@ var Chessboard = (function (exports) {
6991
6546
  }
6992
6547
 
6993
6548
  /**
6994
- * Validates a FEN string
6549
+ * Validate a FEN string
6995
6550
  * @param {string} fen - FEN string to validate
6996
6551
  * @returns {boolean} True if valid, false otherwise
6997
6552
  */
@@ -7000,7 +6555,7 @@ var Chessboard = (function (exports) {
7000
6555
  }
7001
6556
 
7002
6557
  /**
7003
- * Gets piece information for a specific square
6558
+ * Get piece information for a specific square
7004
6559
  * @param {string} squareId - Square identifier
7005
6560
  * @returns {string|null} Piece ID or null if no piece
7006
6561
  */
@@ -7011,7 +6566,7 @@ var Chessboard = (function (exports) {
7011
6566
  }
7012
6567
 
7013
6568
  /**
7014
- * Checks if a specific piece is on a specific square
6569
+ * Check if a specific piece is on a specific square
7015
6570
  * @param {string} piece - Piece ID to check
7016
6571
  * @param {string} square - Square to check
7017
6572
  * @returns {boolean} True if piece is on square
@@ -7021,7 +6576,7 @@ var Chessboard = (function (exports) {
7021
6576
  }
7022
6577
 
7023
6578
  /**
7024
- * Converts board coordinates to square ID
6579
+ * Convert board coordinates to square ID
7025
6580
  * @private
7026
6581
  * @param {number} row - Row index (0-7)
7027
6582
  * @param {number} col - Column index (0-7)
@@ -7030,7 +6585,6 @@ var Chessboard = (function (exports) {
7030
6585
  _getSquareID(row, col) {
7031
6586
  row = parseInt(row);
7032
6587
  col = parseInt(col);
7033
-
7034
6588
  if (this.config.orientation === 'w') {
7035
6589
  row = 8 - row;
7036
6590
  col = col + 1;
@@ -7038,13 +6592,12 @@ var Chessboard = (function (exports) {
7038
6592
  row = row + 1;
7039
6593
  col = 8 - col;
7040
6594
  }
7041
-
7042
6595
  const letter = BOARD_LETTERS[col - 1];
7043
6596
  return letter + row;
7044
6597
  }
7045
6598
 
7046
6599
  /**
7047
- * Changes the turn in a FEN string
6600
+ * Change the turn in a FEN string
7048
6601
  * @param {string} fen - Original FEN string
7049
6602
  * @param {string} color - New turn color ('w' or 'b')
7050
6603
  * @returns {string} Modified FEN string
@@ -7056,35 +6609,33 @@ var Chessboard = (function (exports) {
7056
6609
  }
7057
6610
 
7058
6611
  /**
7059
- * Gets the current position as an object
6612
+ * Get the current position as an object
7060
6613
  * @returns {Object} Position object with piece placements
7061
6614
  */
7062
6615
  getPosition() {
7063
6616
  const position = {};
7064
6617
  const game = this.getGame();
7065
-
7066
- // Convert chess.js board to position object
7067
- const squares = ['a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1',
7068
- 'a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2',
7069
- 'a3', 'b3', 'c3', 'd3', 'e3', 'f3', 'g3', 'h3',
7070
- 'a4', 'b4', 'c4', 'd4', 'e4', 'f4', 'g4', 'h4',
7071
- 'a5', 'b5', 'c5', 'd5', 'e5', 'f5', 'g5', 'h5',
7072
- 'a6', 'b6', 'c6', 'd6', 'e6', 'f6', 'g6', 'h6',
7073
- 'a7', 'b7', 'c7', 'd7', 'e7', 'f7', 'g7', 'h7',
7074
- 'a8', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8'];
7075
-
6618
+ const squares = [
6619
+ 'a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1',
6620
+ 'a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2',
6621
+ 'a3', 'b3', 'c3', 'd3', 'e3', 'f3', 'g3', 'h3',
6622
+ 'a4', 'b4', 'c4', 'd4', 'e4', 'f4', 'g4', 'h4',
6623
+ 'a5', 'b5', 'c5', 'd5', 'e5', 'f5', 'g5', 'h5',
6624
+ 'a6', 'b6', 'c6', 'd6', 'e6', 'f6', 'g6', 'h6',
6625
+ 'a7', 'b7', 'c7', 'd7', 'e7', 'f7', 'g7', 'h7',
6626
+ 'a8', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8'
6627
+ ];
7076
6628
  for (const square of squares) {
7077
6629
  const piece = game.get(square);
7078
6630
  if (piece) {
7079
6631
  position[square] = piece.type + piece.color;
7080
6632
  }
7081
6633
  }
7082
-
7083
6634
  return position;
7084
6635
  }
7085
6636
 
7086
6637
  /**
7087
- * Toggles the turn in a FEN string
6638
+ * Toggle the turn in a FEN string
7088
6639
  * @param {string} fen - Original FEN string
7089
6640
  * @returns {string} Modified FEN string
7090
6641
  */
@@ -7095,7 +6646,7 @@ var Chessboard = (function (exports) {
7095
6646
  }
7096
6647
 
7097
6648
  /**
7098
- * Cleans up resources
6649
+ * Clean up resources
7099
6650
  */
7100
6651
  destroy() {
7101
6652
  this.game = null;
@@ -7103,7 +6654,8 @@ var Chessboard = (function (exports) {
7103
6654
  }
7104
6655
 
7105
6656
  /**
7106
- * Main Chessboard class - Orchestrates all services and components
6657
+ * Chessboard - Main class orchestrating all services and components
6658
+ * Implements the Facade pattern for the chessboard API
7107
6659
  * @module core/Chessboard
7108
6660
  * @since 2.0.0
7109
6661
  */
@@ -7111,12 +6663,11 @@ var Chessboard = (function (exports) {
7111
6663
 
7112
6664
  /**
7113
6665
  * Main Chessboard class responsible for coordinating all services
7114
- * Implements the Facade pattern to provide a unified interface
7115
- * @class
6666
+ * @class Chessboard
7116
6667
  */
7117
- class Chessboard {
6668
+ let Chessboard$1 = class Chessboard {
7118
6669
  /**
7119
- * Creates a new Chessboard instance
6670
+ * Create a new Chessboard instance
7120
6671
  * @param {Object} config - Configuration object
7121
6672
  * @throws {ConfigurationError} If configuration is invalid
7122
6673
  */
@@ -7139,6 +6690,8 @@ var Chessboard = (function (exports) {
7139
6690
  } catch (error) {
7140
6691
  this._handleConstructorError(error);
7141
6692
  }
6693
+ this._undoneMoves = [];
6694
+ this._updateBoardPieces(true, true); // Forza popolamento DOM subito
7142
6695
  }
7143
6696
 
7144
6697
  /**
@@ -7182,6 +6735,31 @@ var Chessboard = (function (exports) {
7182
6735
  }
7183
6736
  }
7184
6737
 
6738
+ /**
6739
+ * Cleans up any partially initialized resources (safe to call multiple times)
6740
+ * @private
6741
+ */
6742
+ _cleanup() {
6743
+ // Remove event listeners if present
6744
+ if (this.eventService && typeof this.eventService.removeListeners === 'function') {
6745
+ this.eventService.removeListeners();
6746
+ }
6747
+ // Clear timeouts
6748
+ if (this._updateTimeout) {
6749
+ clearTimeout(this._updateTimeout);
6750
+ this._updateTimeout = null;
6751
+ }
6752
+ // Null all services
6753
+ this.validationService = null;
6754
+ this.coordinateService = null;
6755
+ this.positionService = null;
6756
+ this.boardService = null;
6757
+ this.pieceService = null;
6758
+ this.animationService = null;
6759
+ this.moveService = null;
6760
+ this.eventService = null;
6761
+ }
6762
+
7185
6763
  /**
7186
6764
  * Initializes all services
7187
6765
  * @private
@@ -7250,8 +6828,23 @@ var Chessboard = (function (exports) {
7250
6828
  /**
7251
6829
  * Builds the board DOM structure
7252
6830
  * @private
6831
+ * Best practice: always remove squares (destroy JS/DOM) before clearing the board container.
7253
6832
  */
7254
6833
  _buildBoard() {
6834
+ console.log('CHIAMATO: _buildBoard');
6835
+ if (this._isUndoRedo) {
6836
+ console.log('SKIP _buildBoard per undo/redo');
6837
+ return;
6838
+ }
6839
+ // Forza la pulizia completa del contenitore board (DOM)
6840
+ const boardContainer = document.getElementById(this.config.id_div);
6841
+ if (boardContainer) boardContainer.innerHTML = '';
6842
+ // Force remove all pieces from all squares (no animation, best practice)
6843
+ if (this.boardService && this.boardService.squares) {
6844
+ Object.values(this.boardService.squares).forEach(sq => sq && sq.forceRemoveAllPieces && sq.forceRemoveAllPieces());
6845
+ }
6846
+ if (this.boardService && this.boardService.removeSquares) this.boardService.removeSquares();
6847
+ if (this.boardService && this.boardService.removeBoard) this.boardService.removeBoard();
7255
6848
  this.boardService.buildBoard();
7256
6849
  }
7257
6850
 
@@ -7260,6 +6853,14 @@ var Chessboard = (function (exports) {
7260
6853
  * @private
7261
6854
  */
7262
6855
  _buildSquares() {
6856
+ console.log('CHIAMATO: _buildSquares');
6857
+ if (this._isUndoRedo) {
6858
+ console.log('SKIP _buildSquares per undo/redo');
6859
+ return;
6860
+ }
6861
+ if (this.boardService && this.boardService.removeSquares) {
6862
+ this.boardService.removeSquares();
6863
+ }
7263
6864
  this.boardService.buildSquares((row, col) => {
7264
6865
  return this.coordinateService.realCoord(row, col);
7265
6866
  });
@@ -7464,7 +7065,7 @@ var Chessboard = (function (exports) {
7464
7065
  const capturedPiece = move.to.piece;
7465
7066
 
7466
7067
  // For castle moves in simultaneous mode, we need to coordinate both animations
7467
- if (isCastleMove && this.config.animationStyle === 'simultaneous') {
7068
+ if (isCastleMove) {
7468
7069
  // Start king animation
7469
7070
  this.pieceService.translatePiece(
7470
7071
  move,
@@ -7629,6 +7230,7 @@ var Chessboard = (function (exports) {
7629
7230
  * @param {boolean} [isPositionLoad=false] - Whether this is a position load
7630
7231
  */
7631
7232
  _updateBoardPieces(animation = false, isPositionLoad = false) {
7233
+ console.log('CHIAMATO: _updateBoardPieces', { animation, isPositionLoad, isUndoRedo: this._isUndoRedo });
7632
7234
  // Check if services are available
7633
7235
  if (!this.positionService || !this.moveService || !this.eventService) {
7634
7236
  console.log('Cannot update board pieces - services not available');
@@ -7688,112 +7290,125 @@ var Chessboard = (function (exports) {
7688
7290
  }
7689
7291
 
7690
7292
  /**
7691
- * Performs the actual board update
7293
+ * Aggiorna i pezzi sulla scacchiera con animazione e delay configurabile (greedy matching)
7692
7294
  * @private
7693
- * @param {boolean} [animation=false] - Whether to animate
7694
- * @param {boolean} [isPositionLoad=false] - Whether this is a position load (affects delay)
7295
+ * @param {boolean} [animation=false] - Se animare
7296
+ * @param {boolean} [isPositionLoad=false] - Se è un caricamento posizione (delay 0)
7695
7297
  */
7696
7298
  _doUpdateBoardPieces(animation = false, isPositionLoad = false) {
7697
- // Skip update if we're in the middle of a promotion
7698
- if (this._isPromoting) {
7699
- console.log('Skipping board update during promotion');
7700
- return;
7701
- }
7702
-
7703
- // Check if services are available
7704
- if (!this.positionService || !this.positionService.getGame()) {
7705
- console.log('Cannot update board pieces - position service not available');
7706
- return;
7707
- }
7708
-
7299
+ if (this._isDragging) return;
7300
+ if (this._isPromoting) return;
7301
+ if (!this.positionService || !this.positionService.getGame()) return;
7709
7302
  const squares = this.boardService.getAllSquares();
7710
7303
  const gameStateBefore = this.positionService.getGame().fen();
7711
-
7712
- console.log('_doUpdateBoardPieces - current FEN:', gameStateBefore);
7713
- console.log('_doUpdateBoardPieces - animation:', animation, 'style:', this.config.animationStyle, 'isPositionLoad:', isPositionLoad);
7714
-
7715
- // Determine which animation style to use
7716
- const useSimultaneous = this.config.animationStyle === 'simultaneous';
7717
- console.log('_doUpdateBoardPieces - useSimultaneous:', useSimultaneous);
7718
-
7719
- if (useSimultaneous) {
7720
- console.log('Using simultaneous animation');
7721
- this._doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad);
7722
- } else {
7723
- console.log('Using sequential animation');
7724
- this._doSequentialUpdate(squares, gameStateBefore, animation);
7304
+ if (/^8\/8\/8\/8\/8\/8\/8\/8/.test(gameStateBefore)) {
7305
+ const boardContainer = document.getElementById(this.config.id_div);
7306
+ if (boardContainer) {
7307
+ const pieceElements = boardContainer.querySelectorAll('.piece');
7308
+ pieceElements.forEach(element => element.remove());
7309
+ }
7310
+ Object.values(squares).forEach(sq => { if (sq && sq.piece) sq.piece = null; });
7311
+ this._clearVisualState();
7312
+ this._addListeners();
7313
+ if (this.config.onChange) this.config.onChange(gameStateBefore);
7314
+ return;
7725
7315
  }
7726
- }
7727
7316
 
7728
- /**
7729
- * Performs sequential piece updates (original behavior)
7730
- * @private
7731
- * @param {Object} squares - All squares
7732
- * @param {string} gameStateBefore - Game state before update
7733
- * @param {boolean} animation - Whether to animate
7734
- */
7735
- _doSequentialUpdate(squares, gameStateBefore, animation) {
7736
- // Update each square sequentially
7317
+ // --- Matching greedy tra attuale e atteso ---
7318
+ const currentMap = {};
7319
+ const expectedMap = {};
7737
7320
  Object.values(squares).forEach(square => {
7738
- const expectedPieceId = this.positionService.getGamePieceId(square.id);
7739
7321
  const currentPiece = square.piece;
7740
- const currentPieceId = currentPiece ? currentPiece.getId() : null;
7741
-
7742
- // Log only for squares that are changing
7743
- if (currentPieceId !== expectedPieceId) {
7744
- console.log(`_doSequentialUpdate - ${square.id}: ${currentPieceId} -> ${expectedPieceId}`);
7745
-
7746
- // Check if we already have the correct piece (from promotion)
7747
- if (currentPiece && currentPiece.getId() === expectedPieceId) {
7748
- console.log(`Piece ${expectedPieceId} already correctly placed on ${square.id}`);
7749
- } else {
7750
- // Remove current piece if exists
7751
- if (currentPiece) {
7752
- this.pieceService.removePieceFromSquare(square, animation);
7753
- }
7322
+ const expectedPieceId = this.positionService.getGamePieceId(square.id);
7323
+ if (currentPiece) {
7324
+ const key = (currentPiece.color + currentPiece.type).toLowerCase();
7325
+ if (!currentMap[key]) currentMap[key] = [];
7326
+ currentMap[key].push({ square, id: square.id, piece: currentPiece });
7327
+ }
7328
+ if (expectedPieceId) {
7329
+ const key = expectedPieceId.toLowerCase();
7330
+ if (!expectedMap[key]) expectedMap[key] = [];
7331
+ expectedMap[key].push({ square, id: square.id });
7332
+ }
7333
+ });
7334
+ const animationDelay = isPositionLoad ? 0 : this.config.simultaneousAnimationDelay || 0;
7335
+ let totalAnimations = 0;
7336
+ let animationsCompleted = 0;
7754
7337
 
7755
- // Add new piece if needed
7756
- if (expectedPieceId) {
7757
- const newPiece = this.pieceService.convertPiece(expectedPieceId);
7758
- this.pieceService.addPieceOnSquare(
7759
- square,
7760
- newPiece,
7761
- animation,
7762
- this._createDragFunction.bind(this)
7763
- );
7338
+ // 1. Matching greedy: trova i movimenti
7339
+ const moves = [];
7340
+ const fromMatched = {};
7341
+ const toMatched = {};
7342
+ const unchanged = [];
7343
+ Object.keys(expectedMap).forEach(key => {
7344
+ const fromList = (currentMap[key] || []).slice();
7345
+ const toList = expectedMap[key].slice();
7346
+ const localFromMatched = new Array(fromList.length).fill(false);
7347
+ const localToMatched = new Array(toList.length).fill(false);
7348
+ // Matrice delle distanze
7349
+ const distances = [];
7350
+ for (let i = 0; i < fromList.length; i++) {
7351
+ distances[i] = [];
7352
+ for (let j = 0; j < toList.length; j++) {
7353
+ distances[i][j] = Math.abs(fromList[i].square.row - toList[j].square.row) +
7354
+ Math.abs(fromList[i].square.col - toList[j].square.col);
7355
+ }
7356
+ }
7357
+ while (true) {
7358
+ let minDist = Infinity, minI = -1, minJ = -1;
7359
+ for (let i = 0; i < fromList.length; i++) {
7360
+ if (localFromMatched[i]) continue;
7361
+ for (let j = 0; j < toList.length; j++) {
7362
+ if (localToMatched[j]) continue;
7363
+ if (distances[i][j] < minDist) {
7364
+ minDist = distances[i][j];
7365
+ minI = i;
7366
+ minJ = j;
7367
+ }
7764
7368
  }
7765
7369
  }
7370
+ if (minI === -1 || minJ === -1) break;
7371
+ // Se la posizione è la stessa E il Piece è lo stesso oggetto, non fare nulla (pezzo unchanged)
7372
+ if (fromList[minI].square === toList[minJ].square && squares[toList[minJ].square.id].piece === fromList[minI].piece) {
7373
+ unchanged.push({ square: fromList[minI].square, piece: fromList[minI].piece });
7374
+ localFromMatched[minI] = true;
7375
+ localToMatched[minJ] = true;
7376
+ fromMatched[fromList[minI].square.id] = true;
7377
+ toMatched[toList[minJ].square.id] = true;
7378
+ continue;
7379
+ }
7380
+ // Altrimenti, sposta il pezzo
7381
+ moves.push({ from: fromList[minI].square, to: toList[minJ].square, piece: fromList[minI].piece });
7382
+ localFromMatched[minI] = true;
7383
+ localToMatched[minJ] = true;
7384
+ fromMatched[fromList[minI].square.id] = true;
7385
+ toMatched[toList[minJ].square.id] = true;
7766
7386
  }
7767
7387
  });
7768
7388
 
7769
- // Re-add listeners after updating pieces to ensure hover events work correctly
7770
- this._addListeners();
7771
-
7772
- // Trigger change event if position changed
7773
- const gameStateAfter = this.positionService.getGame().fen();
7774
- if (gameStateBefore !== gameStateAfter) {
7775
- this.config.onChange(gameStateAfter);
7776
- }
7777
- }
7389
+ // 2. Rimozione: pezzi presenti solo in attuale (non matched)
7390
+ const removes = [];
7391
+ Object.keys(currentMap).forEach(key => {
7392
+ currentMap[key].forEach(({ square, piece }) => {
7393
+ if (!fromMatched[square.id]) {
7394
+ removes.push({ square, piece });
7395
+ }
7396
+ });
7397
+ });
7778
7398
 
7779
- /**
7780
- * Performs simultaneous piece updates
7781
- * @private
7782
- * @param {Object} squares - All squares
7783
- * @param {string} gameStateBefore - Game state before update
7784
- * @param {boolean} [isPositionLoad=false] - Whether this is a position load
7785
- */
7786
- _doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad = false) {
7787
- console.log('_doSimultaneousUpdate - Starting simultaneous update');
7788
-
7789
- // Analyze what changes need to be made
7790
- const changeAnalysis = this._analyzePositionChanges(squares);
7399
+ // 3. Aggiunta: pezzi presenti solo in atteso (non matched)
7400
+ const adds = [];
7401
+ Object.keys(expectedMap).forEach(key => {
7402
+ expectedMap[key].forEach(({ square, id }) => {
7403
+ if (!toMatched[square.id]) {
7404
+ adds.push({ square, pieceId: key });
7405
+ }
7406
+ });
7407
+ });
7791
7408
 
7792
- if (changeAnalysis.totalChanges === 0) {
7793
- console.log('_doSimultaneousUpdate - No changes needed, returning');
7409
+ totalAnimations = moves.length + removes.length + adds.length;
7410
+ if (totalAnimations === 0) {
7794
7411
  this._addListeners();
7795
-
7796
- // Trigger change event if position changed
7797
7412
  const gameStateAfter = this.positionService.getGame().fen();
7798
7413
  if (gameStateBefore !== gameStateAfter) {
7799
7414
  this.config.onChange(gameStateAfter);
@@ -7801,10 +7416,64 @@ var Chessboard = (function (exports) {
7801
7416
  return;
7802
7417
  }
7803
7418
 
7804
- console.log('_doSimultaneousUpdate - Change analysis:', changeAnalysis);
7419
+ // Debug: logga i pezzi unchanged
7420
+ if (unchanged.length > 0) {
7421
+ console.debug('[Chessboard] Unchanged pieces:', unchanged.map(u => u.piece.id + '@' + u.square.id));
7422
+ }
7805
7423
 
7806
- // Execute all changes simultaneously
7807
- this._executeSimultaneousChanges(changeAnalysis, gameStateBefore, isPositionLoad);
7424
+ const onAnimationComplete = () => {
7425
+ animationsCompleted++;
7426
+ if (animationsCompleted === totalAnimations) {
7427
+ // Pulizia finale robusta: rimuovi tutti i pezzi orfani dal DOM e dal riferimento JS
7428
+ Object.values(this.boardService.getAllSquares()).forEach(square => {
7429
+ const expectedPieceId = this.positionService.getGamePieceId(square.id);
7430
+ if (!expectedPieceId && typeof square.forceRemoveAllPieces === 'function') {
7431
+ square.forceRemoveAllPieces();
7432
+ }
7433
+ });
7434
+ this._addListeners();
7435
+ const gameStateAfter = this.positionService.getGame().fen();
7436
+ if (gameStateBefore !== gameStateAfter) {
7437
+ this.config.onChange(gameStateAfter);
7438
+ }
7439
+ }
7440
+ };
7441
+
7442
+ // 4. Esegui tutte le animazioni con delay
7443
+ let idx = 0;
7444
+ moves.forEach(move => {
7445
+ setTimeout(() => {
7446
+ this.pieceService.translatePiece(
7447
+ move,
7448
+ false,
7449
+ animation,
7450
+ this._createDragFunction.bind(this),
7451
+ onAnimationComplete
7452
+ );
7453
+ }, idx++ * animationDelay);
7454
+ });
7455
+ removes.forEach(op => {
7456
+ setTimeout(() => {
7457
+ if (typeof op.square.forceRemoveAllPieces === 'function') {
7458
+ op.square.forceRemoveAllPieces();
7459
+ onAnimationComplete();
7460
+ } else {
7461
+ this.pieceService.removePieceFromSquare(op.square, animation, onAnimationComplete);
7462
+ }
7463
+ }, idx++ * animationDelay);
7464
+ });
7465
+ adds.forEach(op => {
7466
+ setTimeout(() => {
7467
+ const newPiece = this.pieceService.convertPiece(op.pieceId);
7468
+ this.pieceService.addPieceOnSquare(
7469
+ op.square,
7470
+ newPiece,
7471
+ animation,
7472
+ this._createDragFunction.bind(this),
7473
+ onAnimationComplete
7474
+ );
7475
+ }, idx++ * animationDelay);
7476
+ });
7808
7477
  }
7809
7478
 
7810
7479
  /**
@@ -8130,99 +7799,418 @@ var Chessboard = (function (exports) {
8130
7799
  }
8131
7800
 
8132
7801
  // -------------------
8133
- // Public API Methods
7802
+ // Public API Methods (Refactored)
8134
7803
  // -------------------
8135
7804
 
7805
+ // --- POSITION & STATE ---
8136
7806
  /**
8137
- * Gets the current position as FEN
8138
- * @returns {string} FEN string
7807
+ * Get the current position as FEN
7808
+ * @returns {string}
8139
7809
  */
8140
- fen() {
8141
- return this.positionService.getGame().fen();
7810
+ getPosition() { return this.fen(); }
7811
+ /**
7812
+ * Set the board position (FEN or object)
7813
+ * @param {string|Object} position
7814
+ * @param {Object} [opts]
7815
+ * @param {boolean} [opts.animate=true]
7816
+ * @returns {boolean}
7817
+ */
7818
+ setPosition(position, opts = {}) {
7819
+ const animate = opts.animate !== undefined ? opts.animate : true;
7820
+ // Remove highlights and selections
7821
+ if (this.boardService && this.boardService.applyToAllSquares) {
7822
+ this.boardService.applyToAllSquares('removeHint');
7823
+ this.boardService.applyToAllSquares('deselect');
7824
+ this.boardService.applyToAllSquares('unmoved');
7825
+ }
7826
+ if (this.positionService && this.positionService.setGame) {
7827
+ this.positionService.setGame(position);
7828
+ }
7829
+ if (this._updateBoardPieces) {
7830
+ this._updateBoardPieces(animate, true);
7831
+ }
7832
+ // Forza la sincronizzazione dopo setPosition
7833
+ this._updateBoardPieces(true, false);
7834
+ return true;
7835
+ }
7836
+ /**
7837
+ * Reset the board to the starting position
7838
+ * @param {Object} [opts]
7839
+ * @param {boolean} [opts.animate=true]
7840
+ * @returns {boolean}
7841
+ */
7842
+ reset(opts = {}) {
7843
+ const animate = opts.animate !== undefined ? opts.animate : true;
7844
+ // Use the default starting position from config or fallback
7845
+ const startPosition = this.config && this.config.position ? this.config.position : 'start';
7846
+ this._updateBoardPieces(animate);
7847
+ const result = this.setPosition(startPosition, { animate });
7848
+ // Forza la sincronizzazione dopo reset
7849
+ this._updateBoardPieces(true, false);
7850
+ return result;
7851
+ }
7852
+ /**
7853
+ * Clear the board
7854
+ * @param {Object} [opts]
7855
+ * @param {boolean} [opts.animate=true]
7856
+ * @returns {boolean}
7857
+ */
7858
+ clear(opts = {}) {
7859
+ const animate = opts.animate !== undefined ? opts.animate : true;
7860
+ if (!this.positionService || !this.positionService.getGame()) {
7861
+ return false;
7862
+ }
7863
+ if (this._clearVisualState) this._clearVisualState();
7864
+ this.positionService.getGame().clear();
7865
+ // Forza la rimozione di tutti i pezzi dal DOM
7866
+ if (this.boardService && this.boardService.squares) {
7867
+ Object.values(this.boardService.squares).forEach(sq => {
7868
+ if (sq && sq.piece) sq.piece = null;
7869
+ });
7870
+ }
7871
+ if (this._updateBoardPieces) {
7872
+ this._updateBoardPieces(animate, true);
7873
+ }
7874
+ // Forza la sincronizzazione dopo clear
7875
+ this._updateBoardPieces(true, false);
7876
+ return true;
8142
7877
  }
8143
7878
 
7879
+ // --- MOVE MANAGEMENT ---
8144
7880
  /**
8145
- * Gets current turn
8146
- * @returns {string} 'w' or 'b'
7881
+ * Undo last move
7882
+ * @param {Object} [opts]
7883
+ * @param {boolean} [opts.animate=true]
7884
+ * @returns {boolean}
8147
7885
  */
8148
- turn() {
8149
- return this.positionService.getGame().turn();
7886
+ undoMove(opts = {}) {
7887
+ const undone = this.positionService.getGame().undo();
7888
+ if (undone) {
7889
+ this._undoneMoves.push(undone);
7890
+ // Forza refresh completo di tutti i pezzi dopo undo
7891
+ this._updateBoardPieces(true, true);
7892
+ return undone;
7893
+ }
7894
+ return null;
8150
7895
  }
7896
+ /**
7897
+ * Redo last undone move
7898
+ * @param {Object} [opts]
7899
+ * @param {boolean} [opts.animate=true]
7900
+ * @returns {boolean}
7901
+ */
7902
+ redoMove(opts = {}) {
7903
+ if (this._undoneMoves && this._undoneMoves.length > 0) {
7904
+ const move = this._undoneMoves.pop();
7905
+ const moveObj = { from: move.from, to: move.to };
7906
+ if (move.promotion) moveObj.promotion = move.promotion;
7907
+ const result = this.positionService.getGame().move(moveObj);
7908
+ // Forza refresh completo di tutti i pezzi dopo redo
7909
+ this._updateBoardPieces(true, true);
7910
+ return result;
7911
+ }
7912
+ return false;
7913
+ }
7914
+ /**
7915
+ * Get legal moves for a square
7916
+ * @param {string} square
7917
+ * @returns {Array}
7918
+ */
7919
+ getLegalMoves(square) { return this.legalMoves(square); }
8151
7920
 
7921
+ // --- PIECE MANAGEMENT ---
8152
7922
  /**
8153
- * Loads a new position
8154
- * @param {string|Object} position - Position to load
8155
- * @param {Object} [options={}] - Loading options
8156
- * @param {boolean} [animation=true] - Whether to animate
7923
+ * Get the piece at a square
7924
+ * @param {string} square
7925
+ * @returns {string|null}
8157
7926
  */
8158
- load(position, options = {}, animation = true) {
8159
- this.boardService.applyToAllSquares('removeHint');
8160
- this.boardService.applyToAllSquares('deselect');
8161
- this.boardService.applyToAllSquares('unmoved');
7927
+ getPiece(square) {
7928
+ // Sempre leggi lo stato aggiornato dal boardService
7929
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
7930
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[getPiece] Parameter square is invalid');
7931
+ // Forza sync prima di leggere
7932
+ this._updateBoardPieces(false, false);
7933
+ const piece = squareObj.piece;
7934
+ if (!piece) return null;
7935
+ return (piece.color + piece.type).toLowerCase();
7936
+ }
7937
+ /**
7938
+ * Put a piece on a square
7939
+ * @param {string|Piece} piece
7940
+ * @param {string|Square} square
7941
+ * @param {Object} [opts]
7942
+ * @param {boolean} [opts.animate=true]
7943
+ * @returns {boolean}
7944
+ */
7945
+ putPiece(piece, square, opts = {}) {
7946
+ const animate = opts.animate !== undefined ? opts.animate : true;
7947
+ let pieceStr = piece;
7948
+ if (typeof piece === 'object' && piece.type && piece.color) {
7949
+ pieceStr = (piece.color + piece.type).toLowerCase();
7950
+ } else if (typeof piece === 'string' && piece.length === 2) {
7951
+ const a = piece[0].toLowerCase();
7952
+ const b = piece[1].toLowerCase();
7953
+ const types = 'kqrbnp';
7954
+ const colors = 'wb';
7955
+ if (types.includes(a) && colors.includes(b)) {
7956
+ pieceStr = b + a;
7957
+ } else if (colors.includes(a) && types.includes(b)) {
7958
+ pieceStr = a + b;
7959
+ } else {
7960
+ throw new Error(`[putPiece] Invalid piece: ${piece}`);
7961
+ }
7962
+ }
7963
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
7964
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[putPiece] Parameter square is invalid');
7965
+ const pieceObj = this.pieceService.convertPiece(pieceStr);
7966
+ if (!pieceObj || typeof pieceObj !== 'object' || !('type' in pieceObj)) throw new Error('[putPiece] Parameter piece is invalid');
7967
+ // Aggiorna solo il motore chess.js
7968
+ const chessJsPiece = { type: pieceObj.type, color: pieceObj.color };
7969
+ const game = this.positionService.getGame();
7970
+ const result = game.put(chessJsPiece, squareObj.id);
7971
+ if (!result) throw new Error(`[putPiece] Game.put failed for ${pieceStr} on ${squareObj.id}`);
7972
+ // Non aggiornare direttamente square.piece!
7973
+ // Riallinea la board JS allo stato del motore
7974
+ this._updateBoardPieces(animate);
7975
+ return true;
7976
+ }
7977
+ /**
7978
+ * Remove a piece from a square
7979
+ * @param {string|Square} square
7980
+ * @param {Object} [opts]
7981
+ * @param {boolean} [opts.animate=true]
7982
+ * @returns {string|null}
7983
+ */
7984
+ removePiece(square, opts = {}) {
7985
+ const animate = opts.animate !== undefined ? opts.animate : true;
7986
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
7987
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[removePiece] Parameter square is invalid');
7988
+ // Aggiorna solo il motore chess.js
7989
+ const game = this.positionService.getGame();
7990
+ game.remove(squareObj.id);
7991
+ // Non aggiornare direttamente square.piece!
7992
+ // Riallinea la board JS allo stato del motore
7993
+ this._updateBoardPieces(animate);
7994
+ return true;
7995
+ }
8162
7996
 
8163
- this.positionService.setGame(position, options);
8164
- this._updateBoardPieces(animation, true); // Position load
7997
+ // --- BOARD CONTROL ---
7998
+ /**
7999
+ * Flip the board orientation
8000
+ * @param {Object} [opts]
8001
+ * @param {boolean} [opts.animate=true]
8002
+ */
8003
+ flipBoard(opts = {}) {
8004
+ if (this.coordinateService && this.coordinateService.flipOrientation) {
8005
+ this.coordinateService.flipOrientation();
8006
+ }
8007
+ if (this._buildBoard) this._buildBoard();
8008
+ if (this._buildSquares) this._buildSquares();
8009
+ if (this._addListeners) this._addListeners();
8010
+ if (this._updateBoardPieces) this._updateBoardPieces(opts.animate !== false);
8011
+ console.log('FEN dopo flip:', this.fen(), 'Orientamento:', this.coordinateService.getOrientation());
8012
+ }
8013
+ /**
8014
+ * Set the board orientation
8015
+ * @param {'w'|'b'} color
8016
+ * @param {Object} [opts]
8017
+ * @param {boolean} [opts.animate=true]
8018
+ */
8019
+ setOrientation(color, opts = {}) {
8020
+ if (this.validationService.isValidOrientation(color)) {
8021
+ this.coordinateService.setOrientation(color);
8022
+ if (this._buildBoard) this._buildBoard();
8023
+ if (this._buildSquares) this._buildSquares();
8024
+ if (this._addListeners) this._addListeners();
8025
+ if (this._updateBoardPieces) this._updateBoardPieces(opts.animate !== false);
8026
+ }
8027
+ return this.coordinateService.getOrientation();
8028
+ }
8029
+ /**
8030
+ * Get the current orientation
8031
+ * @returns {'w'|'b'}
8032
+ */
8033
+ getOrientation() { return this.orientation(); }
8034
+ /**
8035
+ * Resize the board
8036
+ * @param {number|string} size
8037
+ */
8038
+ resizeBoard(size) {
8039
+ if (size === 'auto') {
8040
+ this.config.size = 'auto';
8041
+ document.documentElement.style.setProperty('--dimBoard', 'auto');
8042
+ this._updateBoardPieces(false);
8043
+ return true;
8044
+ }
8045
+ if (typeof size !== 'number' || size < 50 || size > 3000) {
8046
+ throw new Error(`[resizeBoard] Invalid size: ${size}`);
8047
+ }
8048
+ this.config.size = size;
8049
+ document.documentElement.style.setProperty('--dimBoard', `${size}px`);
8050
+ this._updateBoardPieces(false);
8051
+ return true;
8165
8052
  }
8166
8053
 
8054
+ // --- HIGHLIGHTING & UI ---
8167
8055
  /**
8168
- * Destroys the board and cleans up resources
8056
+ * Highlight a square
8057
+ * @param {string|Square} square
8058
+ * @param {Object} [opts]
8169
8059
  */
8170
- destroy() {
8171
- this.eventService.destroy();
8172
- this.boardService.destroy();
8173
- this.positionService.destroy();
8174
- this.pieceService.destroy();
8175
- this.moveService.destroy();
8176
- this.animationService.destroy();
8177
- this.validationService.destroy();
8060
+ highlight(square, opts = {}) {
8061
+ // API: accetta id, converte subito in oggetto
8062
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8063
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[highlight] Parameter square is invalid');
8064
+ if (this.boardService && this.boardService.highlightSquare) {
8065
+ this.boardService.highlightSquare(squareObj, opts);
8066
+ } else if (this.eventService && this.eventService.highlightSquare) {
8067
+ this.eventService.highlightSquare(squareObj, opts);
8068
+ }
8069
+ }
8070
+ /**
8071
+ * Remove highlight from a square
8072
+ * @param {string|Square} square
8073
+ * @param {Object} [opts]
8074
+ */
8075
+ dehighlight(square, opts = {}) {
8076
+ // API: accetta id, converte subito in oggetto
8077
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8078
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[dehighlight] Parameter square is invalid');
8079
+ if (this.boardService && this.boardService.dehighlightSquare) {
8080
+ this.boardService.dehighlightSquare(squareObj, opts);
8081
+ } else if (this.eventService && this.eventService.dehighlightSquare) {
8082
+ this.eventService.dehighlightSquare(squareObj, opts);
8083
+ }
8084
+ }
8085
+
8086
+ // --- GAME INFO ---
8087
+ /**
8088
+ * Get FEN string
8089
+ * @returns {string}
8090
+ */
8091
+ fen() {
8092
+ // Avoid recursion: call the underlying game object's fen()
8093
+ const game = this.positionService.getGame();
8094
+ if (!game || typeof game.fen !== 'function') return '';
8095
+ return game.fen();
8096
+ }
8097
+ /**
8098
+ * Get current turn
8099
+ * @returns {'w'|'b'}
8100
+ */
8101
+ turn() { return this.positionService.getGame().turn(); }
8102
+ /**
8103
+ * Is the game over?
8104
+ * @returns {boolean}
8105
+ */
8106
+ isGameOver() {
8107
+ // Forza sync prima di interrogare il motore
8108
+ this._updateBoardPieces(false, false);
8109
+ const game = this.positionService.getGame();
8110
+ if (!game) return false;
8111
+ if (game.isGameOver) return game.isGameOver();
8112
+ // Fallback: checkmate or draw
8113
+ if (game.isCheckmate && game.isCheckmate()) return true;
8114
+ if (game.isDraw && game.isDraw()) return true;
8115
+ return false;
8116
+ }
8117
+ /**
8118
+ * Is it checkmate?
8119
+ * @returns {boolean}
8120
+ */
8121
+ isCheckmate() {
8122
+ const game = this.positionService.getGame();
8123
+ if (!game) return false;
8124
+ return game.isCheckmate ? game.isCheckmate() : false;
8125
+ }
8126
+ /**
8127
+ * Is it draw?
8128
+ * @returns {boolean}
8129
+ */
8130
+ isDraw() {
8131
+ const game = this.positionService.getGame();
8132
+ if (!game) return false;
8133
+ return game.isDraw ? game.isDraw() : false;
8134
+ }
8135
+ /**
8136
+ * Get move history
8137
+ * @returns {Array}
8138
+ */
8139
+ getHistory() {
8140
+ const game = this.positionService.getGame();
8141
+ if (!game) return [];
8142
+ return game.history ? game.history() : [];
8143
+ }
8178
8144
 
8145
+ // --- LIFECYCLE ---
8146
+ /**
8147
+ * Destroy the board and cleanup all resources
8148
+ */
8149
+ destroy() {
8150
+ // Remove event listeners
8151
+ if (this.eventService && typeof this.eventService.removeListeners === 'function') {
8152
+ this.eventService.removeListeners();
8153
+ }
8154
+ // Clear timeouts
8179
8155
  if (this._updateTimeout) {
8180
8156
  clearTimeout(this._updateTimeout);
8181
8157
  this._updateTimeout = null;
8182
8158
  }
8159
+ // Destroy all services
8160
+ [
8161
+ 'validationService',
8162
+ 'coordinateService',
8163
+ 'positionService',
8164
+ 'boardService',
8165
+ 'pieceService',
8166
+ 'animationService',
8167
+ 'moveService',
8168
+ 'eventService',
8169
+ ].forEach(service => {
8170
+ if (this[service] && typeof this[service].destroy === 'function') {
8171
+ this[service].destroy();
8172
+ }
8173
+ this[service] = null;
8174
+ });
8175
+ // Nullify DOM references
8176
+ this._performanceMonitor = null;
8177
+ this._boundUpdateBoardPieces = null;
8178
+ this._boundOnSquareClick = null;
8179
+ this._boundOnPieceHover = null;
8180
+ this._boundOnPieceLeave = null;
8181
+ this._undoneMoves = null;
8182
+ // Remove board DOM if possible
8183
+ const boardContainer = document.getElementById(this.config?.id_div);
8184
+ if (boardContainer) {
8185
+ boardContainer.innerHTML = '';
8186
+ }
8183
8187
  }
8184
-
8185
8188
  /**
8186
- * Resizes the board
8187
- * @param {number|string} size - New size
8189
+ * Rebuild the board
8188
8190
  */
8189
- resize(size) {
8190
- this.boardService.resize(size);
8191
- this._updateBoardPieces();
8192
- }
8191
+ rebuild() { this._initialize(); }
8193
8192
 
8193
+ // --- CONFIGURATION ---
8194
8194
  /**
8195
- * Flips the board orientation
8195
+ * Get current config
8196
+ * @returns {Object}
8196
8197
  */
8197
- flip() {
8198
- this.coordinateService.flipOrientation();
8199
-
8200
- // Save current position before destroying
8201
- let currentPosition = null;
8202
- try {
8203
- // Check if there are any pieces on the board
8204
- const position = this.positionService.getPosition();
8205
- const hasPieces = Object.keys(position).length > 0;
8206
-
8207
- if (hasPieces) {
8208
- currentPosition = this.positionService.getGame().fen();
8209
- }
8210
- } catch (error) {
8211
- console.log('No valid position to save during flip');
8212
- }
8198
+ getConfig() { return this.config; }
8199
+ /**
8200
+ * Set new config
8201
+ * @param {Object} newConfig
8202
+ */
8203
+ setConfig(newConfig) { this.setConfig(newConfig); }
8213
8204
 
8214
- this.destroy();
8215
- this._initializeServices(); // Recreate all services
8216
- this._initParams();
8205
+ // --- ALIASES/DEPRECATED ---
8206
+ // Note: These methods are now implemented as aliases at the end of the class
8217
8207
 
8218
- // Restore position after rebuilding if we had one
8219
- if (currentPosition) {
8220
- this._setGame(currentPosition);
8221
- }
8222
- this._buildBoard();
8223
- this._buildSquares();
8224
- this._addListeners();
8225
- this._updateBoardPieces(true, true);
8208
+ /**
8209
+ * Alias for flipBoard (for backward compatibility)
8210
+ */
8211
+ flip(opts = {}) {
8212
+ this._updateBoardPieces(opts.animate !== false);
8213
+ return this.flipBoard(opts);
8226
8214
  }
8227
8215
 
8228
8216
  /**
@@ -8245,51 +8233,19 @@ var Chessboard = (function (exports) {
8245
8233
  this.load(position, {}, animate); // load() already handles isPositionLoad=true
8246
8234
  }
8247
8235
 
8248
- /**
8249
- * Makes a move on the board
8250
- * @param {string|Object} move - Move to make
8251
- * @param {boolean} [animate=true] - Whether to animate
8252
- * @returns {boolean} True if move was successful
8253
- */
8254
- move(move, animate = true) {
8255
- if (typeof move === 'string') {
8256
- // Parse move string (e.g., 'e2e4')
8257
- const moveObj = this.moveService.parseMove(move);
8258
- if (!moveObj) return false;
8259
-
8260
- const fromSquare = this.boardService.getSquare(moveObj.from);
8261
- const toSquare = this.boardService.getSquare(moveObj.to);
8262
-
8263
- if (!fromSquare || !toSquare) return false;
8264
-
8265
- return this._onMove(fromSquare, toSquare, moveObj.promotion, animate);
8266
- } else if (move && move.from && move.to) {
8267
- // Handle move object
8268
- const fromSquare = this.boardService.getSquare(move.from);
8269
- const toSquare = this.boardService.getSquare(move.to);
8270
-
8271
- if (!fromSquare || !toSquare) return false;
8272
-
8273
- return this._onMove(fromSquare, toSquare, move.promotion, animate);
8274
- }
8275
-
8276
- return false;
8277
- }
8278
-
8279
8236
  /**
8280
8237
  * Undoes the last move
8281
8238
  * @param {boolean} [animate=true] - Whether to animate
8282
8239
  * @returns {boolean} True if undo was successful
8283
8240
  */
8284
8241
  undo(animate = true) {
8285
- if (this.positionService.getGame().undo) {
8286
- const undoResult = this.positionService.getGame().undo();
8287
- if (undoResult) {
8288
- this._updateBoardPieces(animate, true); // Position change
8289
- return true;
8290
- }
8242
+ const undone = this.positionService.getGame().undo();
8243
+ if (undone) {
8244
+ this._undoneMoves.push(undone);
8245
+ this._updateBoardPieces(animate);
8246
+ return undone;
8291
8247
  }
8292
- return false;
8248
+ return null;
8293
8249
  }
8294
8250
 
8295
8251
  /**
@@ -8298,12 +8254,13 @@ var Chessboard = (function (exports) {
8298
8254
  * @returns {boolean} True if redo was successful
8299
8255
  */
8300
8256
  redo(animate = true) {
8301
- if (this.positionService.getGame().redo) {
8302
- const redoResult = this.positionService.getGame().redo();
8303
- if (redoResult) {
8304
- this._updateBoardPieces(animate, true); // Position change
8305
- return true;
8306
- }
8257
+ if (this._undoneMoves && this._undoneMoves.length > 0) {
8258
+ const move = this._undoneMoves.pop();
8259
+ const moveObj = { from: move.from, to: move.to };
8260
+ if (move.promotion) moveObj.promotion = move.promotion;
8261
+ const result = this.positionService.getGame().move(moveObj);
8262
+ this._updateBoardPieces(animate);
8263
+ return result;
8307
8264
  }
8308
8265
  return false;
8309
8266
  }
@@ -8465,112 +8422,6 @@ var Chessboard = (function (exports) {
8465
8422
  }
8466
8423
  }
8467
8424
 
8468
- /**
8469
- * Clears the board
8470
- * @param {boolean} [animate=true] - Whether to animate
8471
- */
8472
- clear(animate = true) {
8473
- // Check if services are available
8474
- if (!this.positionService || !this.positionService.getGame()) {
8475
- console.log('Cannot clear - position service not available');
8476
- return;
8477
- }
8478
-
8479
- // Clear visual state first
8480
- this._clearVisualState();
8481
-
8482
- // Clear game state
8483
- this.positionService.getGame().clear();
8484
-
8485
- // Update board visually
8486
- this._updateBoardPieces(animate, true); // Position change
8487
- }
8488
-
8489
- /**
8490
- * Resets the board to starting position
8491
- * @param {boolean} [animate=true] - Whether to animate
8492
- */
8493
- reset(animate = true) {
8494
- // Check if services are available
8495
- if (!this.positionService || !this.positionService.getGame()) {
8496
- console.log('Cannot reset - position service not available');
8497
- return;
8498
- }
8499
-
8500
- this.positionService.getGame().reset();
8501
- this._updateBoardPieces(animate, true); // Position change
8502
- }
8503
-
8504
- /**
8505
- * Puts a piece on a square
8506
- * @param {string} square - Square to put piece on
8507
- * @param {string} piece - Piece to put
8508
- * @param {boolean} [animate=true] - Whether to animate
8509
- * @returns {boolean} True if successful
8510
- */
8511
- put(square, piece, animate = true) {
8512
- console.log(`put() called with square: ${square}, piece: ${piece}`);
8513
-
8514
- if (!this.validationService.isValidSquare(square) || !this.validationService.isValidPiece(piece)) {
8515
- console.log('Validation failed');
8516
- return false;
8517
- }
8518
-
8519
- // Check if services are available
8520
- if (!this.positionService || !this.positionService.getGame()) {
8521
- console.log('Cannot put piece - position service not available');
8522
- return false;
8523
- }
8524
-
8525
- const pieceObj = this.pieceService.convertPiece(piece);
8526
- console.log(`Converted piece:`, pieceObj);
8527
- console.log(`Piece type: ${pieceObj.type}, color: ${pieceObj.color}`);
8528
-
8529
- const squareObj = this.boardService.getSquare(square);
8530
-
8531
- if (!squareObj) {
8532
- console.log('Square not found');
8533
- return false;
8534
- }
8535
-
8536
- // Update game state - note: chess.js expects (piece, square) order
8537
- const chessJsPiece = { type: pieceObj.type.toLowerCase(), color: pieceObj.color.toLowerCase() };
8538
- console.log(`Chess.js piece:`, chessJsPiece);
8539
-
8540
- const result = this.positionService.getGame().put(chessJsPiece, square);
8541
- console.log(`Chess.js put result:`, result);
8542
-
8543
- if (result) {
8544
- // Update visual representation
8545
- this._updateBoardPieces(animate, true); // Position change
8546
- }
8547
-
8548
- return result;
8549
- }
8550
-
8551
- /**
8552
- * Removes a piece from a square
8553
- * @param {string} square - Square to remove piece from
8554
- * @param {boolean} [animate=true] - Whether to animate
8555
- * @returns {boolean} True if successful
8556
- */
8557
- remove(square, animate = true) {
8558
- if (!this.validationService.isValidSquare(square)) {
8559
- return false;
8560
- }
8561
-
8562
- const squareObj = this.boardService.getSquare(square);
8563
- if (!squareObj) return false;
8564
-
8565
- // Update game state
8566
- this.positionService.getGame().remove(square);
8567
-
8568
- // Update visual representation
8569
- this._updateBoardPieces(animate, true); // Position change
8570
-
8571
- return true;
8572
- }
8573
-
8574
8425
  /**
8575
8426
  * Gets configuration options
8576
8427
  * @returns {Object} Configuration object
@@ -8596,23 +8447,6 @@ var Chessboard = (function (exports) {
8596
8447
  }
8597
8448
  }
8598
8449
 
8599
- /**
8600
- * Gets or sets the animation style
8601
- * @param {string} [style] - New animation style ('sequential' or 'simultaneous')
8602
- * @returns {string} Current animation style
8603
- */
8604
- animationStyle(style) {
8605
- if (style === undefined) {
8606
- return this.config.animationStyle;
8607
- }
8608
-
8609
- if (this.validationService.isValidAnimationStyle(style)) {
8610
- this.config.animationStyle = style;
8611
- }
8612
-
8613
- return this.config.animationStyle;
8614
- }
8615
-
8616
8450
  /**
8617
8451
  * Gets or sets the simultaneous animation delay
8618
8452
  * @param {number} [delay] - New delay in milliseconds
@@ -8632,7 +8466,97 @@ var Chessboard = (function (exports) {
8632
8466
 
8633
8467
  // Additional API methods would be added here following the same pattern
8634
8468
  // This is a good starting point for the refactored architecture
8635
- }
8469
+
8470
+ // Additional API methods and aliases for backward compatibility
8471
+ insert(square, piece) { return this.putPiece(piece, square); }
8472
+ build() { return this._initialize(); }
8473
+ ascii() { return this.positionService.getGame().ascii(); }
8474
+ board() { return this.positionService.getGame().board(); }
8475
+ getCastlingRights(color) { return this.positionService.getGame().getCastlingRights(color); }
8476
+ getComment() { return this.positionService.getGame().getComment(); }
8477
+ getComments() { return this.positionService.getGame().getComments(); }
8478
+ lastMove() { return this.positionService.getGame().lastMove(); }
8479
+ moveNumber() { return this.positionService.getGame().moveNumber(); }
8480
+ moves(options = {}) { return this.positionService.getGame().moves(options); }
8481
+ squareColor(squareId) { return this.boardService.getSquare(squareId).isWhite() ? 'light' : 'dark'; }
8482
+ isDrawByFiftyMoves() { return this.positionService.getGame().isDrawByFiftyMoves(); }
8483
+ isInsufficientMaterial() { return this.positionService.getGame().isInsufficientMaterial(); }
8484
+ removeComment() { return this.positionService.getGame().removeComment(); }
8485
+ removeComments() { return this.positionService.getGame().removeComments(); }
8486
+ removeHeader(field) { return this.positionService.getGame().removeHeader(field); }
8487
+ setCastlingRights(color, rights) { return this.positionService.getGame().setCastlingRights(color, rights); }
8488
+ setComment(comment) { return this.positionService.getGame().setComment(comment); }
8489
+ setHeader(key, value) { return this.positionService.getGame().setHeader(key, value); }
8490
+ validateFen(fen) { return this.positionService.getGame().validateFen(fen); }
8491
+
8492
+ // Implementazioni reali per highlight/dehighlight
8493
+ highlightSquare(square) {
8494
+ return this.boardService.highlight(square);
8495
+ }
8496
+ dehighlightSquare(square) {
8497
+ return this.boardService.dehighlight(square);
8498
+ }
8499
+ forceSync() { this._updateBoardPieces(true, true); this._updateBoardPieces(true, false); }
8500
+
8501
+ // Metodi mancanti che causano fallimenti nei test
8502
+ /**
8503
+ * Move a piece from one square to another
8504
+ * @param {string|Object} move - Move in format 'e2e4' or {from: 'e2', to: 'e4'}
8505
+ * @param {Object} [opts] - Options
8506
+ * @param {boolean} [opts.animate=true] - Whether to animate
8507
+ * @returns {boolean} True if move was successful
8508
+ */
8509
+ movePiece(move, opts = {}) {
8510
+ const animate = opts.animate !== undefined ? opts.animate : true;
8511
+
8512
+ // --- API: accetta id/stringhe, ma converte subito in oggetti ---
8513
+ let fromSquareObj, toSquareObj, promotion;
8514
+ if (typeof move === 'string') {
8515
+ if (move.length === 4) {
8516
+ fromSquareObj = this.boardService.getSquare(move.substring(0, 2));
8517
+ toSquareObj = this.boardService.getSquare(move.substring(2, 4));
8518
+ } else if (move.length === 5) {
8519
+ fromSquareObj = this.boardService.getSquare(move.substring(0, 2));
8520
+ toSquareObj = this.boardService.getSquare(move.substring(2, 4));
8521
+ promotion = move.substring(4, 5);
8522
+ } else {
8523
+ throw new Error(`Invalid move format: ${move}`);
8524
+ }
8525
+ } else if (typeof move === 'object' && move.from && move.to) {
8526
+ // Se sono id, converto in oggetti; se sono già oggetti, li uso direttamente
8527
+ fromSquareObj = typeof move.from === 'string' ? this.boardService.getSquare(move.from) : move.from;
8528
+ toSquareObj = typeof move.to === 'string' ? this.boardService.getSquare(move.to) : move.to;
8529
+ promotion = move.promotion;
8530
+ } else {
8531
+ throw new Error(`Invalid move: ${move}`);
8532
+ }
8533
+
8534
+ if (!fromSquareObj || !toSquareObj) {
8535
+ throw new Error(`Invalid squares: ${move.from || move.substring(0, 2)} or ${move.to || move.substring(2, 4)}`);
8536
+ }
8537
+
8538
+ // --- Internamente: lavora solo con oggetti ---
8539
+ const result = this._onMove(fromSquareObj, toSquareObj, promotion, animate);
8540
+ // Dopo ogni mossa, forza la sincronizzazione della board
8541
+ this._updateBoardPieces(true, false);
8542
+ return result;
8543
+ }
8544
+
8545
+ // Aliases for backward compatibility
8546
+ move(move, animate = true) {
8547
+ // On any new move, clear the redo stack
8548
+ this._undoneMoves = [];
8549
+ return this.movePiece(move, { animate });
8550
+ }
8551
+ get(square) { return this.getPiece(square); }
8552
+ piece(square) { return this.getPiece(square); }
8553
+ put(piece, square, opts = {}) { return this.putPiece(piece, square, opts); }
8554
+ remove(square, opts = {}) { return this.removePiece(square, opts); }
8555
+ load(position, opts = {}) { return this.setPosition(position, opts); }
8556
+ resize(size) { return this.resizeBoard(size); }
8557
+ start(opts = {}) { return this.reset(opts); }
8558
+ clearBoard(opts = {}) { return this.clear(opts); }
8559
+ };
8636
8560
 
8637
8561
  /**
8638
8562
  * Structured logging system for Chessboard.js
@@ -9090,7 +9014,6 @@ var Chessboard = (function (exports) {
9090
9014
  hints: true,
9091
9015
  clickable: true,
9092
9016
  moveHighlight: true,
9093
- animationStyle: 'simultaneous'
9094
9017
  });
9095
9018
 
9096
9019
  // Tournament template
@@ -9101,7 +9024,6 @@ var Chessboard = (function (exports) {
9101
9024
  clickable: true,
9102
9025
  moveHighlight: true,
9103
9026
  onlyLegalMoves: true,
9104
- animationStyle: 'sequential'
9105
9027
  });
9106
9028
 
9107
9029
  // Analysis template
@@ -9112,7 +9034,6 @@ var Chessboard = (function (exports) {
9112
9034
  clickable: true,
9113
9035
  moveHighlight: true,
9114
9036
  mode: 'analysis',
9115
- animationStyle: 'simultaneous'
9116
9037
  });
9117
9038
 
9118
9039
  // Puzzle template
@@ -9123,7 +9044,6 @@ var Chessboard = (function (exports) {
9123
9044
  clickable: true,
9124
9045
  moveHighlight: true,
9125
9046
  onlyLegalMoves: true,
9126
- animationStyle: 'sequential'
9127
9047
  });
9128
9048
 
9129
9049
  // Demo template
@@ -9133,7 +9053,6 @@ var Chessboard = (function (exports) {
9133
9053
  hints: false,
9134
9054
  clickable: false,
9135
9055
  moveHighlight: true,
9136
- animationStyle: 'simultaneous'
9137
9056
  });
9138
9057
  }
9139
9058
 
@@ -9179,7 +9098,7 @@ var Chessboard = (function (exports) {
9179
9098
  this.validationService.validateConfig(finalConfig);
9180
9099
 
9181
9100
  // Create chessboard instance
9182
- const chessboard = new Chessboard(finalConfig);
9101
+ const chessboard = new Chessboard$1(finalConfig);
9183
9102
 
9184
9103
  // Store instance for management
9185
9104
  this.instances.set(containerId, {
@@ -9439,13 +9358,44 @@ var Chessboard = (function (exports) {
9439
9358
  */
9440
9359
 
9441
9360
 
9361
+ /**
9362
+ * Main Chessboard factory function for backward compatibility
9363
+ * Supports both legacy and modern calling conventions
9364
+ * @param {string|Object} containerElm - Container element ID or configuration object
9365
+ * @param {Object} [config={}] - Configuration options (when first param is string)
9366
+ * @returns {ChessboardClass} Chessboard instance
9367
+ */
9368
+ function Chessboard(containerElm, config = {}) {
9369
+ const factoryLogger = logger.child('ChessboardFactory');
9370
+
9371
+ try {
9372
+ // If first parameter is an object, treat it as config
9373
+ if (typeof containerElm === 'object' && containerElm !== null) {
9374
+ factoryLogger.debug('Creating chessboard with config object');
9375
+ return new Chessboard$1(containerElm);
9376
+ }
9377
+
9378
+ // Otherwise, treat first parameter as element ID
9379
+ if (typeof containerElm === 'string') {
9380
+ factoryLogger.debug('Creating chessboard with element ID', { elementId: containerElm });
9381
+ const fullConfig = { ...config, id: containerElm };
9382
+ return new Chessboard$1(fullConfig);
9383
+ }
9384
+
9385
+ throw new Error('Invalid parameters: first parameter must be string or object');
9386
+ } catch (error) {
9387
+ factoryLogger.error('Failed to create chessboard instance', { error });
9388
+ throw error;
9389
+ }
9390
+ }
9391
+
9442
9392
  /**
9443
9393
  * Wrapper class that handles both calling conventions
9444
9394
  * Provides enhanced error handling and logging
9445
9395
  * @class
9446
9396
  * @extends ChessboardClass
9447
9397
  */
9448
- class ChessboardWrapper extends Chessboard {
9398
+ class ChessboardWrapper extends Chessboard$1 {
9449
9399
  /**
9450
9400
  * Creates a new ChessboardWrapper instance
9451
9401
  * @param {string|Object} containerElm - Container element ID or configuration object
@@ -9474,32 +9424,53 @@ var Chessboard = (function (exports) {
9474
9424
  }
9475
9425
  }
9476
9426
 
9477
- // Attach classes and utilities to the factory function for direct access
9478
- Chessboard.Class = ChessboardWrapper;
9479
- Chessboard.Chessboard = ChessboardWrapper;
9480
- Chessboard.Config = ChessboardConfig;
9481
- Chessboard.Factory = ChessboardFactory;
9427
+ /**
9428
+ * Refactored Chessboard API - see Chessboard.js for full method docs
9429
+ * @typedef {import('./Chessboard.js').Chessboard} Chessboard
9430
+ */
9482
9431
 
9483
- // Attach factory methods
9432
+ // --- STATIC/FACTORY METHODS ---
9433
+ /**
9434
+ * Create a new Chessboard instance
9435
+ * @param {string|Object} containerElm
9436
+ * @param {Object} [config]
9437
+ * @returns {Chessboard}
9438
+ */
9484
9439
  Chessboard.create = createChessboard;
9485
- Chessboard.createFromTemplate = createChessboardFromTemplate;
9440
+ /**
9441
+ * Create a Chessboard from a template
9442
+ * @param {string|Object} containerElm
9443
+ * @param {string} templateName
9444
+ * @param {Object} [config]
9445
+ * @returns {Chessboard}
9446
+ */
9447
+ Chessboard.fromTemplate = createChessboardFromTemplate;
9486
9448
  Chessboard.factory = chessboardFactory;
9487
9449
 
9488
- // Static methods for instance management
9450
+ // --- INSTANCE MANAGEMENT ---
9489
9451
  Chessboard.getInstance = (containerId) => chessboardFactory.getInstance(containerId);
9490
9452
  Chessboard.destroyInstance = (containerId) => chessboardFactory.destroy(containerId);
9491
9453
  Chessboard.destroyAll = () => chessboardFactory.destroyAll();
9492
9454
  Chessboard.listInstances = () => chessboardFactory.listInstances();
9493
9455
 
9494
- // Template management
9456
+ // --- TEMPLATE MANAGEMENT ---
9495
9457
  Chessboard.registerTemplate = (name, config) => chessboardFactory.registerTemplate(name, config);
9496
9458
  Chessboard.removeTemplate = (name) => chessboardFactory.removeTemplate(name);
9497
9459
  Chessboard.getTemplate = (name) => chessboardFactory.getTemplate(name);
9498
9460
  Chessboard.listTemplates = () => chessboardFactory.listTemplates();
9499
9461
 
9500
- // Statistics and debugging
9462
+ // --- STATS & DEBUG ---
9501
9463
  Chessboard.getStats = () => chessboardFactory.getStats();
9502
9464
 
9465
+ // --- DEPRECATED/LEGACY ALIASES ---
9466
+ /**
9467
+ * @deprecated Use Chessboard.create instead
9468
+ */
9469
+ Chessboard.Class = ChessboardWrapper;
9470
+ Chessboard.Chessboard = ChessboardWrapper;
9471
+ Chessboard.Config = ChessboardConfig;
9472
+ Chessboard.Factory = ChessboardFactory;
9473
+
9503
9474
  /**
9504
9475
  * Coordinate utilities for Chessboard.js
9505
9476
  */
@@ -9889,11 +9860,7 @@ var Chessboard = (function (exports) {
9889
9860
  if (config.moveAnimation && !isValidEasing(config.moveAnimation)) {
9890
9861
  errors.push('Invalid moveAnimation. Must be a valid easing function');
9891
9862
  }
9892
-
9893
- if (config.animationStyle && !['sequential', 'simultaneous'].includes(config.animationStyle)) {
9894
- errors.push('Invalid animationStyle. Must be "sequential" or "simultaneous"');
9895
- }
9896
-
9863
+
9897
9864
  return {
9898
9865
  success: errors.length === 0,
9899
9866
  errors