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