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