@alepot55/chessboardjs 2.3.6 → 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.
@@ -349,7 +349,7 @@ class PieceError extends ChessboardError {
349
349
  }
350
350
 
351
351
  /**
352
- * Service for input validation and data sanitization
352
+ * ValidationService - Handles input validation and data sanitization
353
353
  * @module services/ValidationService
354
354
  * @since 2.0.0
355
355
  */
@@ -398,45 +398,39 @@ const SIZE_CONSTRAINTS = Object.freeze({
398
398
  /**
399
399
  * Service responsible for validating inputs and data
400
400
  * Implements caching for performance optimization
401
- * @class
401
+ * @class ValidationService
402
402
  */
403
403
  class ValidationService {
404
404
  /**
405
- * Creates a new ValidationService instance
405
+ * Create a new ValidationService instance
406
406
  */
407
407
  constructor() {
408
- // Cache for validation results to improve performance
409
408
  this._validationCache = new Map();
410
409
  this._cacheMaxSize = 1000;
411
-
412
- // Compile patterns for reuse
413
410
  this._patterns = VALIDATION_PATTERNS;
414
411
  this._validValues = VALID_VALUES;
415
412
  this._constraints = SIZE_CONSTRAINTS;
416
413
  }
417
414
 
418
415
  /**
419
- * Validates a square identifier with caching
416
+ * Validate a square identifier with caching
420
417
  * @param {string} square - Square to validate (e.g., 'e4')
421
418
  * @returns {boolean} True if valid
422
419
  */
423
420
  isValidSquare(square) {
424
421
  const cacheKey = `square:${square}`;
425
-
426
422
  if (this._validationCache.has(cacheKey)) {
427
423
  return this._validationCache.get(cacheKey);
428
424
  }
429
-
430
- const isValid = typeof square === 'string' &&
431
- square.length === 2 &&
432
- this._patterns.square.test(square);
433
-
425
+ const isValid = typeof square === 'string' &&
426
+ square.length === 2 &&
427
+ this._patterns.square.test(square);
434
428
  this._cacheValidationResult(cacheKey, isValid);
435
429
  return isValid;
436
430
  }
437
431
 
438
432
  /**
439
- * Validates a piece identifier with enhanced format support
433
+ * Validate a piece identifier with enhanced format support
440
434
  * @param {string} piece - Piece to validate (e.g., 'wK', 'bp')
441
435
  * @returns {boolean} True if valid
442
436
  */
@@ -444,28 +438,22 @@ class ValidationService {
444
438
  if (typeof piece !== 'string' || piece.length !== 2) {
445
439
  return false;
446
440
  }
447
-
448
441
  const cacheKey = `piece:${piece}`;
449
-
450
442
  if (this._validationCache.has(cacheKey)) {
451
443
  return this._validationCache.get(cacheKey);
452
444
  }
453
-
454
445
  const [first, second] = piece.split('');
455
-
456
- // Check both formats: [type][color] and [color][type]
457
- const format1 = PIECE_TYPES.includes(first.toLowerCase()) &&
458
- PIECE_COLORS.includes(second);
459
- const format2 = PIECE_COLORS.includes(first) &&
460
- PIECE_TYPES.includes(second.toLowerCase());
461
-
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());
462
450
  const isValid = format1 || format2;
463
451
  this._cacheValidationResult(cacheKey, isValid);
464
452
  return isValid;
465
453
  }
466
454
 
467
455
  /**
468
- * Validates a FEN string with comprehensive checks
456
+ * Validate a FEN string with comprehensive checks
469
457
  * @param {string} fen - FEN string to validate
470
458
  * @returns {boolean} True if valid
471
459
  */
@@ -473,86 +461,64 @@ class ValidationService {
473
461
  if (typeof fen !== 'string') {
474
462
  return false;
475
463
  }
476
-
477
464
  const cacheKey = `fen:${fen}`;
478
-
479
465
  if (this._validationCache.has(cacheKey)) {
480
466
  return this._validationCache.get(cacheKey);
481
467
  }
482
-
483
- // Basic pattern check
484
468
  if (!this._patterns.fen.test(fen)) {
485
469
  this._cacheValidationResult(cacheKey, false);
486
470
  return false;
487
471
  }
488
-
489
- // Additional FEN validation
490
472
  const isValid = this._validateFenStructure(fen);
491
473
  this._cacheValidationResult(cacheKey, isValid);
492
474
  return isValid;
493
475
  }
494
476
 
495
477
  /**
496
- * Validates FEN structure in detail
478
+ * Validate FEN structure in detail
497
479
  * @private
498
480
  * @param {string} fen - FEN string to validate
499
481
  * @returns {boolean} True if valid
500
482
  */
501
483
  _validateFenStructure(fen) {
502
484
  const parts = fen.split(' ');
503
-
504
485
  if (parts.length !== 6) {
505
486
  return false;
506
487
  }
507
-
508
- // Validate piece placement
509
488
  const ranks = parts[0].split('/');
510
489
  if (ranks.length !== 8) {
511
490
  return false;
512
491
  }
513
-
514
- // Validate each rank
515
492
  for (const rank of ranks) {
516
493
  if (!this._validateRank(rank)) {
517
494
  return false;
518
495
  }
519
496
  }
520
-
521
- // Validate active color
522
497
  if (!['w', 'b'].includes(parts[1])) {
523
498
  return false;
524
499
  }
525
-
526
- // Validate castling rights
527
500
  if (!/^[KQkq-]*$/.test(parts[2])) {
528
501
  return false;
529
502
  }
530
-
531
- // Validate en passant target
532
503
  if (parts[3] !== '-' && !this.isValidSquare(parts[3])) {
533
504
  return false;
534
505
  }
535
-
536
- // Validate halfmove clock and fullmove number
537
506
  const halfmove = parseInt(parts[4], 10);
538
507
  const fullmove = parseInt(parts[5], 10);
539
-
540
- return !isNaN(halfmove) && !isNaN(fullmove) &&
541
- halfmove >= 0 && fullmove >= 1;
508
+ return !isNaN(halfmove) && !isNaN(fullmove) &&
509
+ halfmove >= 0 && fullmove >= 1;
542
510
  }
543
511
 
544
512
  /**
545
- * Validates a single rank in FEN notation
513
+ * Validate a single rank in FEN notation
546
514
  * @private
547
515
  * @param {string} rank - Rank to validate
548
516
  * @returns {boolean} True if valid
549
517
  */
550
518
  _validateRank(rank) {
551
519
  let squares = 0;
552
-
553
520
  for (let i = 0; i < rank.length; i++) {
554
521
  const char = rank[i];
555
-
556
522
  if (/[1-8]/.test(char)) {
557
523
  squares += parseInt(char, 10);
558
524
  } else if (/[prnbqkPRNBQK]/.test(char)) {
@@ -561,12 +527,11 @@ class ValidationService {
561
527
  return false;
562
528
  }
563
529
  }
564
-
565
530
  return squares === 8;
566
531
  }
567
532
 
568
533
  /**
569
- * Validates a move string with comprehensive format support
534
+ * Validate a move string with comprehensive format support
570
535
  * @param {string} move - Move string to validate (e.g., 'e2e4', 'e7e8q')
571
536
  * @returns {boolean} True if valid
572
537
  */
@@ -574,20 +539,17 @@ class ValidationService {
574
539
  if (typeof move !== 'string') {
575
540
  return false;
576
541
  }
577
-
578
542
  const cacheKey = `move:${move}`;
579
-
580
543
  if (this._validationCache.has(cacheKey)) {
581
544
  return this._validationCache.get(cacheKey);
582
545
  }
583
-
584
546
  const isValid = this._validateMoveFormat(move);
585
547
  this._cacheValidationResult(cacheKey, isValid);
586
548
  return isValid;
587
549
  }
588
550
 
589
551
  /**
590
- * Validates move format in detail
552
+ * Validate move format in detail
591
553
  * @private
592
554
  * @param {string} move - Move string to validate
593
555
  * @returns {boolean} True if valid
@@ -596,21 +558,15 @@ class ValidationService {
596
558
  if (move.length < 4 || move.length > 5) {
597
559
  return false;
598
560
  }
599
-
600
- const from = move.slice(0, 2);
601
- const to = move.slice(2, 4);
602
- const promotion = move.slice(4, 5);
603
-
561
+ const from = move.substring(0, 2);
562
+ const to = move.substring(2, 4);
604
563
  if (!this.isValidSquare(from) || !this.isValidSquare(to)) {
605
564
  return false;
606
565
  }
607
-
608
- if (promotion && !this._validValues.promotionPieces.includes(promotion)) {
566
+ if (move.length === 5 && !/[qrnb]/i.test(move[4])) {
609
567
  return false;
610
568
  }
611
-
612
- // Additional move validation (e.g., source and target different)
613
- return from !== to;
569
+ return true;
614
570
  }
615
571
 
616
572
  /**
@@ -640,11 +596,9 @@ class ValidationService {
640
596
  if (size === 'auto') {
641
597
  return true;
642
598
  }
643
-
644
599
  if (typeof size === 'number') {
645
600
  return size >= this._constraints.min && size <= this._constraints.max;
646
601
  }
647
-
648
602
  return false;
649
603
  }
650
604
 
@@ -654,9 +608,9 @@ class ValidationService {
654
608
  * @returns {boolean} True if valid
655
609
  */
656
610
  isValidTime(time) {
657
- return typeof time === 'number' &&
658
- time >= 0 &&
659
- time <= this._constraints.maxTime;
611
+ return typeof time === 'number' &&
612
+ time >= 0 &&
613
+ time <= this._constraints.maxTime;
660
614
  }
661
615
 
662
616
  /**
@@ -674,10 +628,10 @@ class ValidationService {
674
628
  * @returns {boolean} True if valid
675
629
  */
676
630
  isValidElementId(id) {
677
- return typeof id === 'string' &&
678
- id.length > 0 &&
679
- id.length <= 100 && // Reasonable length limit
680
- /^[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
681
635
  }
682
636
 
683
637
  /**
@@ -717,24 +671,20 @@ class ValidationService {
717
671
  if (typeof color !== 'string') {
718
672
  return false;
719
673
  }
720
-
721
674
  // Check for hex colors
722
675
  if (this._patterns.color.test(color)) {
723
676
  return true;
724
677
  }
725
-
726
678
  // Check for named colors (basic set)
727
679
  const namedColors = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'cyan', 'magenta'];
728
680
  if (namedColors.includes(color.toLowerCase())) {
729
681
  return true;
730
682
  }
731
-
732
683
  // Check for rgb/rgba format
733
684
  if (/^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/.test(color) ||
734
685
  /^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[0-1](\.\d+)?\s*\)$/.test(color)) {
735
686
  return true;
736
687
  }
737
-
738
688
  return false;
739
689
  }
740
690
 
@@ -747,8 +697,8 @@ class ValidationService {
747
697
  validateSquare(square) {
748
698
  if (!this.isValidSquare(square)) {
749
699
  throw new ValidationError(
750
- ERROR_MESSAGES.invalid_square + square,
751
- 'square',
700
+ ERROR_MESSAGES.invalid_square + square,
701
+ 'square',
752
702
  square
753
703
  );
754
704
  }
@@ -764,8 +714,8 @@ class ValidationService {
764
714
  validatePiece(piece) {
765
715
  if (!this.isValidPiece(piece)) {
766
716
  throw new ValidationError(
767
- ERROR_MESSAGES.invalid_piece + piece,
768
- 'piece',
717
+ ERROR_MESSAGES.invalid_piece + piece,
718
+ 'piece',
769
719
  piece
770
720
  );
771
721
  }
@@ -781,8 +731,8 @@ class ValidationService {
781
731
  validateFen(fen) {
782
732
  if (!this.isValidFen(fen)) {
783
733
  throw new ValidationError(
784
- ERROR_MESSAGES.invalid_fen + fen,
785
- 'fen',
734
+ ERROR_MESSAGES.invalid_fen + fen,
735
+ 'fen',
786
736
  fen
787
737
  );
788
738
  }
@@ -798,8 +748,8 @@ class ValidationService {
798
748
  validateMove(move) {
799
749
  if (!this.isValidMove(move)) {
800
750
  throw new ValidationError(
801
- ERROR_MESSAGES.invalid_move + move,
802
- 'move',
751
+ ERROR_MESSAGES.invalid_move + move,
752
+ 'move',
803
753
  move
804
754
  );
805
755
  }
@@ -815,16 +765,15 @@ class ValidationService {
815
765
  validateOrientation(orientation) {
816
766
  if (!this.isValidOrientation(orientation)) {
817
767
  throw new ValidationError(
818
- ERROR_MESSAGES.invalid_orientation + orientation,
819
- 'orientation',
768
+ ERROR_MESSAGES.invalid_orientation + orientation,
769
+ 'orientation',
820
770
  orientation
821
771
  );
822
772
  }
823
-
824
773
  // Normalize to 'w' or 'b'
825
- return orientation === 'white' ? 'w' :
826
- orientation === 'black' ? 'b' :
827
- orientation;
774
+ return orientation === 'white' ? 'w' :
775
+ orientation === 'black' ? 'b' :
776
+ orientation;
828
777
  }
829
778
 
830
779
  /**
@@ -836,16 +785,15 @@ class ValidationService {
836
785
  validateColor(color) {
837
786
  if (!this.isValidColor(color)) {
838
787
  throw new ValidationError(
839
- ERROR_MESSAGES.invalid_color + color,
840
- 'color',
788
+ ERROR_MESSAGES.invalid_color + color,
789
+ 'color',
841
790
  color
842
791
  );
843
792
  }
844
-
845
793
  // Normalize to 'w' or 'b'
846
- return color === 'white' ? 'w' :
847
- color === 'black' ? 'b' :
848
- color;
794
+ return color === 'white' ? 'w' :
795
+ color === 'black' ? 'b' :
796
+ color;
849
797
  }
850
798
 
851
799
  /**
@@ -857,8 +805,8 @@ class ValidationService {
857
805
  validateSize(size) {
858
806
  if (!this.isValidSize(size)) {
859
807
  throw new ValidationError(
860
- ERROR_MESSAGES.invalid_size + size,
861
- 'size',
808
+ ERROR_MESSAGES.invalid_size + size,
809
+ 'size',
862
810
  size
863
811
  );
864
812
  }
@@ -874,36 +822,29 @@ class ValidationService {
874
822
  validateConfig(config) {
875
823
  if (!config || typeof config !== 'object') {
876
824
  throw new ValidationError(
877
- 'Configuration must be an object',
878
- 'config',
825
+ 'Configuration must be an object',
826
+ 'config',
879
827
  config
880
828
  );
881
829
  }
882
-
883
830
  const errors = [];
884
-
885
831
  // Validate required fields
886
832
  if (!config.id && !config.id_div) {
887
833
  errors.push('Configuration must include id or id_div');
888
834
  }
889
-
890
835
  // Validate optional fields
891
836
  if (config.orientation && !this.isValidOrientation(config.orientation)) {
892
837
  errors.push(ERROR_MESSAGES.invalid_orientation + config.orientation);
893
838
  }
894
-
895
839
  if (config.size && !this.isValidSize(config.size)) {
896
840
  errors.push(ERROR_MESSAGES.invalid_size + config.size);
897
841
  }
898
-
899
842
  if (config.movableColors && !this.isValidMovableColors(config.movableColors)) {
900
843
  errors.push(ERROR_MESSAGES.invalid_color + config.movableColors);
901
844
  }
902
-
903
845
  if (config.dropOffBoard && !this.isValidDropOffBoard(config.dropOffBoard)) {
904
846
  errors.push(ERROR_MESSAGES.invalid_dropOffBoard + config.dropOffBoard);
905
847
  }
906
-
907
848
  // Validate callbacks
908
849
  const callbacks = ['onMove', 'onMoveEnd', 'onChange', 'onDragStart', 'onDragMove', 'onDrop', 'onSnapbackEnd'];
909
850
  for (const callback of callbacks) {
@@ -911,7 +852,6 @@ class ValidationService {
911
852
  errors.push(`Invalid ${callback} callback`);
912
853
  }
913
854
  }
914
-
915
855
  // Validate colors
916
856
  const colorFields = ['whiteSquare', 'blackSquare', 'highlight', 'hintColor'];
917
857
  for (const field of colorFields) {
@@ -919,15 +859,13 @@ class ValidationService {
919
859
  errors.push(`Invalid ${field} color: ${config[field]}`);
920
860
  }
921
861
  }
922
-
923
862
  if (errors.length > 0) {
924
863
  throw new ValidationError(
925
- `Configuration validation failed: ${errors.join(', ')}`,
926
- 'config',
864
+ `Configuration validation failed: ${errors.join(', ')}`,
865
+ 'config',
927
866
  config
928
867
  );
929
868
  }
930
-
931
869
  return config;
932
870
  }
933
871
 
@@ -943,7 +881,6 @@ class ValidationService {
943
881
  const firstKey = this._validationCache.keys().next().value;
944
882
  this._validationCache.delete(firstKey);
945
883
  }
946
-
947
884
  this._validationCache.set(key, result);
948
885
  }
949
886
 
@@ -975,7 +912,6 @@ class ValidationService {
975
912
  return validations.map(validation => {
976
913
  try {
977
914
  const { type, value } = validation;
978
-
979
915
  switch (type) {
980
916
  case 'square':
981
917
  return { valid: this.isValidSquare(value), value };
@@ -1990,180 +1926,155 @@ let Move$1 = class Move {
1990
1926
  };
1991
1927
 
1992
1928
  /**
1993
- * Service for managing animations and transitions
1929
+ * AnimationService - Gestione centralizzata delle animazioni e transizioni per la scacchiera
1994
1930
  * @module services/AnimationService
1995
1931
  * @since 2.0.0
1996
1932
  */
1997
1933
 
1998
1934
  /**
1999
- * Service responsible for coordinating animations and transitions
2000
- * @class
1935
+ * Service responsabile del coordinamento delle animazioni e delle transizioni
1936
+ * @class AnimationService
2001
1937
  */
2002
1938
  class AnimationService {
2003
1939
  /**
2004
- * Creates a new AnimationService instance
2005
- * @param {ChessboardConfig} config - Board configuration
1940
+ * Crea una nuova istanza di AnimationService
1941
+ * @param {Object} config - Configurazione della scacchiera
2006
1942
  */
2007
1943
  constructor(config) {
1944
+ /** @type {Object} */
2008
1945
  this.config = config;
1946
+ /** @type {Map<number, {element: HTMLElement, animate: Function, callback?: Function}>} */
2009
1947
  this.activeAnimations = new Map();
1948
+ /** @type {number} */
2010
1949
  this.animationId = 0;
2011
1950
  }
2012
1951
 
2013
1952
  /**
2014
- * Creates a timing function for animations
2015
- * @param {string} [type='ease'] - Animation type
2016
- * @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
2017
1956
  */
2018
1957
  createTimingFunction(type = 'ease') {
2019
1958
  return (elapsed, duration) => {
2020
- const x = elapsed / duration;
2021
-
1959
+ const x = duration > 0 ? Math.max(0, Math.min(1, elapsed / duration)) : 1;
2022
1960
  switch (type) {
2023
- case 'linear':
2024
- return x;
2025
- case 'ease':
2026
- return (x ** 2) * (3 - 2 * x);
2027
- case 'ease-in':
2028
- return x ** 2;
2029
- case 'ease-out':
2030
- return -1 * (x - 1) ** 2 + 1;
2031
- case 'ease-in-out':
2032
- return (x < 0.5) ? 2 * x ** 2 : 4 * x - 2 * x ** 2 - 1;
2033
- default:
2034
- 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;
2035
1967
  }
2036
1968
  };
2037
1969
  }
2038
1970
 
2039
1971
  /**
2040
- * Animates an element with given properties
2041
- * @param {HTMLElement} element - Element to animate
2042
- * @param {Object} properties - Properties to animate
2043
- * @param {number} duration - Animation duration in milliseconds
2044
- * @param {string} [easing='ease'] - Easing function
2045
- * @param {Function} [callback] - Callback when animation completes
2046
- * @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
2047
1979
  */
2048
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
+ }
2049
1984
  const animationId = ++this.animationId;
2050
1985
  const timingFunction = this.createTimingFunction(easing);
2051
1986
  const startTime = performance.now();
2052
1987
  const startValues = {};
2053
-
2054
- // Store initial values
2055
1988
  Object.keys(properties).forEach(prop => {
2056
1989
  startValues[prop] = this._getInitialValue(element, prop);
2057
1990
  });
2058
-
2059
1991
  const animate = (currentTime) => {
2060
1992
  const elapsed = currentTime - startTime;
2061
1993
  const progress = Math.min(elapsed / duration, 1);
2062
1994
  const easedProgress = timingFunction(elapsed, duration);
2063
-
2064
- // Apply animated values
2065
1995
  Object.keys(properties).forEach(prop => {
2066
1996
  const startValue = startValues[prop];
2067
1997
  const endValue = properties[prop];
2068
1998
  const currentValue = this._interpolateValue(startValue, endValue, easedProgress);
2069
1999
  this._applyValue(element, prop, currentValue);
2070
2000
  });
2071
-
2072
2001
  if (progress < 1 && this.activeAnimations.has(animationId)) {
2073
2002
  requestAnimationFrame(animate);
2074
2003
  } else {
2075
2004
  this.activeAnimations.delete(animationId);
2076
- if (callback) callback();
2005
+ if (typeof callback === 'function') callback();
2077
2006
  }
2078
2007
  };
2079
-
2080
2008
  this.activeAnimations.set(animationId, { element, animate, callback });
2081
2009
  requestAnimationFrame(animate);
2082
-
2083
2010
  return animationId;
2084
2011
  }
2085
2012
 
2086
2013
  /**
2087
- * Cancels an animation
2088
- * @param {number} animationId - Animation ID to cancel
2014
+ * Annulla una animazione attiva
2015
+ * @param {number} animationId - ID dell'animazione
2089
2016
  */
2090
2017
  cancel(animationId) {
2091
- if (this.activeAnimations.has(animationId)) {
2092
- this.activeAnimations.delete(animationId);
2093
- }
2018
+ this.activeAnimations.delete(animationId);
2094
2019
  }
2095
2020
 
2096
2021
  /**
2097
- * Cancels all animations
2022
+ * Annulla tutte le animazioni attive
2098
2023
  */
2099
2024
  cancelAll() {
2100
2025
  this.activeAnimations.clear();
2101
2026
  }
2102
2027
 
2103
2028
  /**
2104
- * Gets initial value for a property
2029
+ * Ottiene il valore iniziale di una proprietà
2105
2030
  * @private
2106
- * @param {HTMLElement} element - Element
2107
- * @param {string} property - Property name
2108
- * @returns {number} Initial value
2031
+ * @param {HTMLElement} element
2032
+ * @param {string} property
2033
+ * @returns {number}
2109
2034
  */
2110
2035
  _getInitialValue(element, property) {
2111
2036
  if (!element || !element.style) return 0;
2112
2037
  switch (property) {
2113
- case 'opacity':
2114
- return parseFloat(getComputedStyle(element).opacity) || 1;
2115
- case 'left':
2116
- return parseFloat(element.style.left) || 0;
2117
- case 'top':
2118
- return parseFloat(element.style.top) || 0;
2119
- case 'scale':
2120
- return 1;
2121
- default:
2122
- 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;
2123
2043
  }
2124
2044
  }
2125
2045
 
2126
2046
  /**
2127
- * Interpolates between two values
2047
+ * Interpola tra due valori
2128
2048
  * @private
2129
- * @param {number} start - Start value
2130
- * @param {number} end - End value
2131
- * @param {number} progress - Progress (0-1)
2132
- * @returns {number} Interpolated value
2049
+ * @param {number} start
2050
+ * @param {number} end
2051
+ * @param {number} progress
2052
+ * @returns {number}
2133
2053
  */
2134
2054
  _interpolateValue(start, end, progress) {
2135
2055
  return start + (end - start) * progress;
2136
2056
  }
2137
2057
 
2138
2058
  /**
2139
- * Applies animated value to element
2059
+ * Applica il valore animato all'elemento
2140
2060
  * @private
2141
- * @param {HTMLElement} element - Element
2142
- * @param {string} property - Property name
2143
- * @param {number} value - Value to apply
2061
+ * @param {HTMLElement} element
2062
+ * @param {string} property
2063
+ * @param {number} value
2144
2064
  */
2145
2065
  _applyValue(element, property, value) {
2146
2066
  if (!element || !element.style) return;
2147
2067
  switch (property) {
2148
- case 'opacity':
2149
- element.style.opacity = value;
2150
- break;
2151
- case 'left':
2152
- element.style.left = value + 'px';
2153
- break;
2154
- case 'top':
2155
- element.style.top = value + 'px';
2156
- break;
2157
- case 'scale':
2158
- element.style.transform = `scale(${value})`;
2159
- break;
2160
- default:
2161
- 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;
2162
2073
  }
2163
2074
  }
2164
2075
 
2165
2076
  /**
2166
- * Cleans up resources
2077
+ * Cleanup risorse e animazioni
2167
2078
  */
2168
2079
  destroy() {
2169
2080
  this.cancelAll();
@@ -2171,7 +2082,7 @@ class AnimationService {
2171
2082
  }
2172
2083
 
2173
2084
  /**
2174
- * Service for managing board setup and DOM operations
2085
+ * BoardService - Handles board DOM setup, manipulation, and resource management
2175
2086
  * @module services/BoardService
2176
2087
  * @since 2.0.0
2177
2088
  */
@@ -2179,45 +2090,45 @@ class AnimationService {
2179
2090
 
2180
2091
  /**
2181
2092
  * Service responsible for board DOM manipulation and setup
2182
- * @class
2093
+ * @class BoardService
2183
2094
  */
2184
2095
  class BoardService {
2185
2096
  /**
2186
- * Creates a new BoardService instance
2187
- * @param {ChessboardConfig} config - Board configuration
2097
+ * Create a new BoardService instance
2098
+ * @param {Object} config - Board configuration
2188
2099
  */
2189
2100
  constructor(config) {
2101
+ /** @type {Object} */
2190
2102
  this.config = config;
2103
+ /** @type {HTMLElement|null} */
2191
2104
  this.element = null;
2105
+ /** @type {Object.<string, Square>} */
2192
2106
  this.squares = {};
2193
2107
  }
2194
2108
 
2195
2109
  /**
2196
- * Builds the board DOM element and attaches it to the configured container
2197
- * @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
2198
2112
  */
2199
2113
  buildBoard() {
2200
- console.log('BoardService.buildBoard: Looking for element with ID:', this.config.id_div);
2201
-
2202
2114
  this.element = document.getElementById(this.config.id_div);
2203
2115
  if (!this.element) {
2204
2116
  throw new DOMError(ERROR_MESSAGES.invalid_id_div + this.config.id_div, this.config.id_div);
2205
2117
  }
2206
-
2207
2118
  this.resize(this.config.size);
2208
- this.element.className = "board";
2119
+ this.element.className = 'board';
2209
2120
  }
2210
2121
 
2211
2122
  /**
2212
- * Creates all 64 squares and adds them to the board
2123
+ * Create all 64 squares and add them to the board
2213
2124
  * @param {Function} coordConverter - Function to convert row/col to real coordinates
2214
2125
  */
2215
2126
  buildSquares(coordConverter) {
2127
+ if (!this.element) throw new DOMError('Board element not initialized', this.config.id_div);
2216
2128
  for (let row = 0; row < BOARD_SIZE.ROWS; row++) {
2217
2129
  for (let col = 0; col < BOARD_SIZE.COLS; col++) {
2218
2130
  const [squareRow, squareCol] = coordConverter(row, col);
2219
2131
  const square = new Square(squareRow, squareCol);
2220
-
2221
2132
  this.squares[square.getId()] = square;
2222
2133
  this.element.appendChild(square.element);
2223
2134
  }
@@ -2225,20 +2136,17 @@ class BoardService {
2225
2136
  }
2226
2137
 
2227
2138
  /**
2228
- * Removes all squares from the board and cleans up their resources
2229
- * Best practice: always destroy JS objects and DOM nodes, and clear references.
2139
+ * Remove all squares from the board and clean up their resources
2230
2140
  */
2231
2141
  removeSquares() {
2232
2142
  for (const square of Object.values(this.squares)) {
2233
- // Always call destroy to remove DOM and clear piece reference
2234
2143
  square.destroy();
2235
2144
  }
2236
2145
  this.squares = {};
2237
2146
  }
2238
2147
 
2239
2148
  /**
2240
- * Removes all content from the board element
2241
- * Best practice: clear DOM and force element to be re-fetched on next build.
2149
+ * Remove all content from the board element
2242
2150
  */
2243
2151
  removeBoard() {
2244
2152
  if (this.element) {
@@ -2248,9 +2156,9 @@ class BoardService {
2248
2156
  }
2249
2157
 
2250
2158
  /**
2251
- * Resizes the board to the specified size
2159
+ * Resize the board to the specified size
2252
2160
  * @param {number|string} value - Size in pixels or 'auto'
2253
- * @throws {ValidationError} When size value is invalid
2161
+ * @throws {ValidationError} If size value is invalid
2254
2162
  */
2255
2163
  resize(value) {
2256
2164
  if (value === 'auto') {
@@ -2264,15 +2172,13 @@ class BoardService {
2264
2172
  }
2265
2173
 
2266
2174
  /**
2267
- * Calculates the optimal size when 'auto' is specified
2175
+ * Calculate the optimal size when 'auto' is specified
2268
2176
  * @private
2269
2177
  * @returns {number} Calculated size in pixels
2270
2178
  */
2271
2179
  _calculateAutoSize() {
2272
- if (!this.element) return 400; // Default fallback
2273
-
2180
+ if (!this.element) return 400;
2274
2181
  const { offsetWidth, offsetHeight } = this.element;
2275
-
2276
2182
  if (offsetWidth === 0) {
2277
2183
  return offsetHeight || 400;
2278
2184
  } else if (offsetHeight === 0) {
@@ -2283,8 +2189,8 @@ class BoardService {
2283
2189
  }
2284
2190
 
2285
2191
  /**
2286
- * Gets a square by its ID
2287
- * @param {string} squareId - Square identifier (API pubblica)
2192
+ * Get a square by its ID
2193
+ * @param {string} squareId - Square identifier
2288
2194
  * @returns {Square|null} The square or null if not found
2289
2195
  */
2290
2196
  getSquare(squareId) {
@@ -2292,26 +2198,37 @@ class BoardService {
2292
2198
  }
2293
2199
 
2294
2200
  /**
2295
- * Highlight a square (solo oggetto)
2201
+ * Highlight a square
2296
2202
  * @param {Square} square
2297
2203
  * @param {Object} [opts]
2298
2204
  */
2299
2205
  highlightSquare(square, opts = {}) {
2300
- if (!square) throw new Error('highlightSquare richiede oggetto Square');
2301
- // ... 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
+ }
2302
2213
  }
2214
+
2303
2215
  /**
2304
- * Dehighlight a square (solo oggetto)
2216
+ * Remove highlight from a square
2305
2217
  * @param {Square} square
2306
2218
  * @param {Object} [opts]
2307
2219
  */
2308
2220
  dehighlightSquare(square, opts = {}) {
2309
- if (!square) throw new Error('dehighlightSquare richiede oggetto Square');
2310
- // ... 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
+ }
2311
2228
  }
2312
2229
 
2313
2230
  /**
2314
- * Gets all squares
2231
+ * Get all squares
2315
2232
  * @returns {Object.<string, Square>} All squares indexed by ID
2316
2233
  */
2317
2234
  getAllSquares() {
@@ -2319,7 +2236,7 @@ class BoardService {
2319
2236
  }
2320
2237
 
2321
2238
  /**
2322
- * Applies a method to all squares
2239
+ * Apply a method to all squares
2323
2240
  * @param {string} methodName - Name of the method to call on each square
2324
2241
  * @param {...*} args - Arguments to pass to the method
2325
2242
  */
@@ -2332,7 +2249,7 @@ class BoardService {
2332
2249
  }
2333
2250
 
2334
2251
  /**
2335
- * Cleans up all resources
2252
+ * Clean up all resources
2336
2253
  */
2337
2254
  destroy() {
2338
2255
  this.removeSquares();
@@ -2343,7 +2260,7 @@ class BoardService {
2343
2260
  }
2344
2261
 
2345
2262
  /**
2346
- * Service for managing coordinate conversions and board orientation
2263
+ * CoordinateService - Handles coordinate conversions and board orientation logic
2347
2264
  * @module services/CoordinateService
2348
2265
  * @since 2.0.0
2349
2266
  */
@@ -2351,19 +2268,20 @@ class BoardService {
2351
2268
 
2352
2269
  /**
2353
2270
  * Service responsible for coordinate conversions and board orientation
2354
- * @class
2271
+ * @class CoordinateService
2355
2272
  */
2356
2273
  class CoordinateService {
2357
2274
  /**
2358
- * Creates a new CoordinateService instance
2359
- * @param {ChessboardConfig} config - Board configuration
2275
+ * Create a new CoordinateService instance
2276
+ * @param {Object} config - Board configuration
2360
2277
  */
2361
2278
  constructor(config) {
2279
+ /** @type {Object} */
2362
2280
  this.config = config;
2363
2281
  }
2364
2282
 
2365
2283
  /**
2366
- * Converts logical coordinates to real coordinates based on board orientation
2284
+ * Convert logical coordinates to real coordinates based on board orientation
2367
2285
  * @param {number} row - Row index (0-7)
2368
2286
  * @param {number} col - Column index (0-7)
2369
2287
  * @returns {Array<number>} Real coordinates [row, col] (1-8)
@@ -2371,18 +2289,16 @@ class CoordinateService {
2371
2289
  realCoord(row, col) {
2372
2290
  let realRow = row;
2373
2291
  let realCol = col;
2374
-
2375
2292
  if (this.isWhiteOriented()) {
2376
2293
  realRow = 7 - row;
2377
2294
  } else {
2378
2295
  realCol = 7 - col;
2379
2296
  }
2380
-
2381
2297
  return [realRow + 1, realCol + 1];
2382
2298
  }
2383
2299
 
2384
2300
  /**
2385
- * Converts board coordinates to square ID
2301
+ * Convert board coordinates to square ID
2386
2302
  * @param {number} row - Row index (0-7)
2387
2303
  * @param {number} col - Column index (0-7)
2388
2304
  * @returns {string} Square ID (e.g., 'e4')
@@ -2390,7 +2306,6 @@ class CoordinateService {
2390
2306
  getSquareID(row, col) {
2391
2307
  row = parseInt(row);
2392
2308
  col = parseInt(col);
2393
-
2394
2309
  if (this.isWhiteOriented()) {
2395
2310
  row = 8 - row;
2396
2311
  col = col + 1;
@@ -2398,7 +2313,6 @@ class CoordinateService {
2398
2313
  row = row + 1;
2399
2314
  col = 8 - col;
2400
2315
  }
2401
-
2402
2316
  if (col < 1 || col > 8 || row < 1 || row > 8) {
2403
2317
  throw new ValidationError(
2404
2318
  `Invalid board coordinates: row=${row}, col=${col}`,
@@ -2406,13 +2320,12 @@ class CoordinateService {
2406
2320
  { row, col }
2407
2321
  );
2408
2322
  }
2409
-
2410
2323
  const letter = BOARD_LETTERS[col - 1];
2411
2324
  return letter + row;
2412
2325
  }
2413
2326
 
2414
2327
  /**
2415
- * Converts square ID to board coordinates
2328
+ * Convert square ID to board coordinates
2416
2329
  * @param {string} squareId - Square ID (e.g., 'e4')
2417
2330
  * @returns {Array<number>} Board coordinates [row, col] (0-7)
2418
2331
  */
@@ -2424,10 +2337,8 @@ class CoordinateService {
2424
2337
  squareId
2425
2338
  );
2426
2339
  }
2427
-
2428
2340
  const letter = squareId[0];
2429
2341
  const number = parseInt(squareId[1]);
2430
-
2431
2342
  const col = BOARD_LETTERS.indexOf(letter);
2432
2343
  if (col === -1) {
2433
2344
  throw new ValidationError(
@@ -2436,7 +2347,6 @@ class CoordinateService {
2436
2347
  squareId
2437
2348
  );
2438
2349
  }
2439
-
2440
2350
  if (number < 1 || number > 8) {
2441
2351
  throw new ValidationError(
2442
2352
  ERROR_MESSAGES.invalid_square + squareId,
@@ -2444,9 +2354,7 @@ class CoordinateService {
2444
2354
  squareId
2445
2355
  );
2446
2356
  }
2447
-
2448
2357
  let row, boardCol;
2449
-
2450
2358
  if (this.isWhiteOriented()) {
2451
2359
  row = 8 - number;
2452
2360
  boardCol = col;
@@ -2454,12 +2362,11 @@ class CoordinateService {
2454
2362
  row = number - 1;
2455
2363
  boardCol = 7 - col;
2456
2364
  }
2457
-
2458
2365
  return [row, boardCol];
2459
2366
  }
2460
2367
 
2461
2368
  /**
2462
- * Converts pixel coordinates to square ID
2369
+ * Convert pixel coordinates to square ID
2463
2370
  * @param {number} x - X coordinate in pixels
2464
2371
  * @param {number} y - Y coordinate in pixels
2465
2372
  * @param {HTMLElement} boardElement - Board DOM element
@@ -2467,21 +2374,15 @@ class CoordinateService {
2467
2374
  */
2468
2375
  pixelToSquareID(x, y, boardElement) {
2469
2376
  if (!boardElement) return null;
2470
-
2471
2377
  const rect = boardElement.getBoundingClientRect();
2472
2378
  const { width, height } = rect;
2473
-
2474
- // Check if coordinates are within board bounds
2475
2379
  if (x < 0 || x >= width || y < 0 || y >= height) {
2476
2380
  return null;
2477
2381
  }
2478
-
2479
2382
  const squareWidth = width / 8;
2480
2383
  const squareHeight = height / 8;
2481
-
2482
2384
  const col = Math.floor(x / squareWidth);
2483
2385
  const row = Math.floor(y / squareHeight);
2484
-
2485
2386
  try {
2486
2387
  return this.getSquareID(row, col);
2487
2388
  } catch (error) {
@@ -2490,25 +2391,21 @@ class CoordinateService {
2490
2391
  }
2491
2392
 
2492
2393
  /**
2493
- * Converts square ID to pixel coordinates
2394
+ * Convert square ID to pixel coordinates
2494
2395
  * @param {string} squareId - Square ID (e.g., 'e4')
2495
2396
  * @param {HTMLElement} boardElement - Board DOM element
2496
2397
  * @returns {Object|null} Pixel coordinates {x, y} or null if invalid
2497
2398
  */
2498
2399
  squareIDToPixel(squareId, boardElement) {
2499
2400
  if (!boardElement) return null;
2500
-
2501
2401
  try {
2502
2402
  const [row, col] = this.getCoordinatesFromSquareID(squareId);
2503
2403
  const rect = boardElement.getBoundingClientRect();
2504
2404
  const { width, height } = rect;
2505
-
2506
2405
  const squareWidth = width / 8;
2507
2406
  const squareHeight = height / 8;
2508
-
2509
2407
  const x = col * squareWidth;
2510
2408
  const y = row * squareHeight;
2511
-
2512
2409
  return { x, y };
2513
2410
  } catch (error) {
2514
2411
  return null;
@@ -2516,7 +2413,7 @@ class CoordinateService {
2516
2413
  }
2517
2414
 
2518
2415
  /**
2519
- * Gets the center pixel coordinates of a square
2416
+ * Get the center pixel coordinates of a square
2520
2417
  * @param {string} squareId - Square ID (e.g., 'e4')
2521
2418
  * @param {HTMLElement} boardElement - Board DOM element
2522
2419
  * @returns {Object|null} Center coordinates {x, y} or null if invalid
@@ -2524,11 +2421,9 @@ class CoordinateService {
2524
2421
  getSquareCenter(squareId, boardElement) {
2525
2422
  const coords = this.squareIDToPixel(squareId, boardElement);
2526
2423
  if (!coords) return null;
2527
-
2528
2424
  const rect = boardElement.getBoundingClientRect();
2529
2425
  const squareWidth = rect.width / 8;
2530
2426
  const squareHeight = rect.height / 8;
2531
-
2532
2427
  return {
2533
2428
  x: coords.x + squareWidth / 2,
2534
2429
  y: coords.y + squareHeight / 2
@@ -2536,7 +2431,7 @@ class CoordinateService {
2536
2431
  }
2537
2432
 
2538
2433
  /**
2539
- * Calculates the distance between two squares
2434
+ * Calculate the distance between two squares
2540
2435
  * @param {string} fromSquare - Source square ID
2541
2436
  * @param {string} toSquare - Target square ID
2542
2437
  * @returns {number} Distance between squares
@@ -2545,10 +2440,8 @@ class CoordinateService {
2545
2440
  try {
2546
2441
  const [fromRow, fromCol] = this.getCoordinatesFromSquareID(fromSquare);
2547
2442
  const [toRow, toCol] = this.getCoordinatesFromSquareID(toSquare);
2548
-
2549
2443
  const rowDiff = Math.abs(toRow - fromRow);
2550
2444
  const colDiff = Math.abs(toCol - fromCol);
2551
-
2552
2445
  return Math.sqrt(rowDiff * rowDiff + colDiff * colDiff);
2553
2446
  } catch (error) {
2554
2447
  return 0;
@@ -2556,7 +2449,7 @@ class CoordinateService {
2556
2449
  }
2557
2450
 
2558
2451
  /**
2559
- * Checks if the board is oriented from white's perspective
2452
+ * Check if the board is oriented from white's perspective
2560
2453
  * @returns {boolean} True if white-oriented
2561
2454
  */
2562
2455
  isWhiteOriented() {
@@ -2564,7 +2457,7 @@ class CoordinateService {
2564
2457
  }
2565
2458
 
2566
2459
  /**
2567
- * Checks if the board is oriented from black's perspective
2460
+ * Check if the board is oriented from black's perspective
2568
2461
  * @returns {boolean} True if black-oriented
2569
2462
  */
2570
2463
  isBlackOriented() {
@@ -2572,31 +2465,31 @@ class CoordinateService {
2572
2465
  }
2573
2466
 
2574
2467
  /**
2575
- * Flips the board orientation
2468
+ * Flip the board orientation
2576
2469
  */
2577
2470
  flipOrientation() {
2578
2471
  this.config.orientation = this.isWhiteOriented() ? 'b' : 'w';
2579
2472
  }
2580
2473
 
2581
2474
  /**
2582
- * Sets the board orientation
2583
- * @param {string} orientation - 'w' for white, 'b' for black
2584
- * @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
2585
2478
  */
2586
2479
  setOrientation(orientation) {
2587
- if (orientation !== 'w' && orientation !== 'b') {
2480
+ const normalized = orientation === 'white' ? 'w' : orientation === 'black' ? 'b' : orientation;
2481
+ if (normalized !== 'w' && normalized !== 'b') {
2588
2482
  throw new ValidationError(
2589
2483
  ERROR_MESSAGES.invalid_orientation + orientation,
2590
2484
  'orientation',
2591
2485
  orientation
2592
2486
  );
2593
2487
  }
2594
-
2595
- this.config.orientation = orientation;
2488
+ this.config.orientation = normalized;
2596
2489
  }
2597
2490
 
2598
2491
  /**
2599
- * Gets the current orientation
2492
+ * Get the current orientation
2600
2493
  * @returns {string} Current orientation ('w' or 'b')
2601
2494
  */
2602
2495
  getOrientation() {
@@ -2604,50 +2497,21 @@ class CoordinateService {
2604
2497
  }
2605
2498
 
2606
2499
  /**
2607
- * Sets the orientation
2608
- * @param {string} orientation - New orientation ('w', 'b', 'white', 'black')
2609
- */
2610
- setOrientation(orientation) {
2611
- // Normalize orientation
2612
- const normalizedOrientation = orientation === 'white' ? 'w' :
2613
- orientation === 'black' ? 'b' : orientation;
2614
-
2615
- if (normalizedOrientation !== 'w' && normalizedOrientation !== 'b') {
2616
- throw new ValidationError(
2617
- ERROR_MESSAGES.invalid_orientation + orientation,
2618
- 'orientation',
2619
- orientation
2620
- );
2621
- }
2622
-
2623
- this.config.orientation = normalizedOrientation;
2624
- }
2625
-
2626
- /**
2627
- * Flips the board orientation
2628
- */
2629
- flipOrientation() {
2630
- this.config.orientation = this.isWhiteOriented() ? 'b' : 'w';
2631
- }
2632
-
2633
- /**
2634
- * Gets all square IDs in order
2500
+ * Get all square IDs in order
2635
2501
  * @returns {Array<string>} Array of all square IDs
2636
2502
  */
2637
2503
  getAllSquareIDs() {
2638
2504
  const squares = [];
2639
-
2640
2505
  for (let row = 0; row < 8; row++) {
2641
2506
  for (let col = 0; col < 8; col++) {
2642
2507
  squares.push(this.getSquareID(row, col));
2643
2508
  }
2644
2509
  }
2645
-
2646
2510
  return squares;
2647
2511
  }
2648
2512
 
2649
2513
  /**
2650
- * Gets squares in a specific rank (row)
2514
+ * Get all squares in a specific rank (row)
2651
2515
  * @param {number} rank - Rank number (1-8)
2652
2516
  * @returns {Array<string>} Array of square IDs in the rank
2653
2517
  */
@@ -2659,19 +2523,16 @@ class CoordinateService {
2659
2523
  rank
2660
2524
  );
2661
2525
  }
2662
-
2663
2526
  const squares = [];
2664
-
2665
2527
  for (let col = 0; col < 8; col++) {
2666
2528
  const row = this.isWhiteOriented() ? 8 - rank : rank - 1;
2667
2529
  squares.push(this.getSquareID(row, col));
2668
2530
  }
2669
-
2670
2531
  return squares;
2671
2532
  }
2672
2533
 
2673
2534
  /**
2674
- * Gets squares in a specific file (column)
2535
+ * Get all squares in a specific file (column)
2675
2536
  * @param {string} file - File letter (a-h)
2676
2537
  * @returns {Array<string>} Array of square IDs in the file
2677
2538
  */
@@ -2684,13 +2545,10 @@ class CoordinateService {
2684
2545
  file
2685
2546
  );
2686
2547
  }
2687
-
2688
2548
  const squares = [];
2689
-
2690
2549
  for (let row = 0; row < 8; row++) {
2691
2550
  squares.push(this.getSquareID(row, col));
2692
2551
  }
2693
-
2694
2552
  return squares;
2695
2553
  }
2696
2554
  }
@@ -3070,6 +2928,67 @@ const DragOptimizations = {
3070
2928
  }
3071
2929
  };
3072
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
+
3073
2992
  /**
3074
2993
  * Service for managing events and user interactions
3075
2994
  * @module services/EventService
@@ -3102,8 +3021,8 @@ class EventService {
3102
3021
  this.promoting = false;
3103
3022
  this.isAnimating = false;
3104
3023
 
3105
- // Event listeners storage for cleanup
3106
- this.eventListeners = new Map();
3024
+ // Listener manager per gestione centralizzata
3025
+ this.listenerManager = new ListenerManager();
3107
3026
  }
3108
3027
 
3109
3028
  /**
@@ -3113,8 +3032,8 @@ class EventService {
3113
3032
  * @param {Function} onPieceLeave - Callback for piece leave
3114
3033
  */
3115
3034
  addListeners(onSquareClick, onPieceHover, onPieceLeave) {
3116
- // Remove existing listeners to avoid duplicates
3117
- this.removeListeners();
3035
+ // Rimuovi tutti i listener esistenti
3036
+ this.listenerManager.removeAll();
3118
3037
 
3119
3038
  const squares = this.boardService.getAllSquares();
3120
3039
 
@@ -3132,8 +3051,6 @@ class EventService {
3132
3051
  * @param {Function} onPieceLeave - Leave callback
3133
3052
  */
3134
3053
  _addSquareListeners(square, onSquareClick, onPieceHover, onPieceLeave) {
3135
- const listeners = [];
3136
-
3137
3054
  // Throttled hover handlers for performance
3138
3055
  const throttledHover = rafThrottle((e) => {
3139
3056
  if (!this.clicked && this.config.hints) {
@@ -3203,26 +3120,13 @@ class EventService {
3203
3120
  }
3204
3121
  };
3205
3122
 
3206
- // Add listeners
3207
- square.element.addEventListener('mouseover', throttledHover);
3208
- square.element.addEventListener('mouseout', throttledLeave);
3209
- square.element.addEventListener('click', handleClick);
3210
- // Touch: separa tap e drag
3211
- square.element.addEventListener('touchstart', handleTouchStart);
3212
- square.element.addEventListener('touchmove', handleTouchMove);
3213
- square.element.addEventListener('touchend', handleTouchEnd);
3214
-
3215
- // Store listeners for cleanup
3216
- listeners.push(
3217
- { element: square.element, type: 'mouseover', handler: throttledHover },
3218
- { element: square.element, type: 'mouseout', handler: throttledLeave },
3219
- { element: square.element, type: 'click', handler: handleClick },
3220
- { element: square.element, type: 'touchstart', handler: handleTouchStart },
3221
- { element: square.element, type: 'touchmove', handler: handleTouchMove },
3222
- { element: square.element, type: 'touchend', handler: handleTouchEnd }
3223
- );
3224
-
3225
- 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);
3226
3130
  }
3227
3131
 
3228
3132
  /**
@@ -3239,18 +3143,14 @@ class EventService {
3239
3143
  */
3240
3144
  createDragFunction(square, piece, onDragStart, onDragMove, onDrop, onSnapback, onMove, onRemove) {
3241
3145
  return (event) => {
3242
- event.preventDefault();
3243
-
3244
3146
  if (!this.config.draggable || !piece || this.isAnimating) {
3245
3147
  return;
3246
3148
  }
3247
3149
 
3248
3150
  const originalFrom = square;
3249
- let isDragging = false;
3250
3151
  let from = originalFrom;
3251
3152
  let to = square;
3252
3153
  let previousHighlight = null;
3253
-
3254
3154
  const img = piece.element;
3255
3155
 
3256
3156
  if (!this.moveService.canMove(from)) {
@@ -3261,6 +3161,15 @@ class EventService {
3261
3161
  const isTouch = event.type && event.type.startsWith('touch');
3262
3162
  const startX = event.clientX || (event.touches && event.touches[0]?.clientX) || 0;
3263
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;
3264
3173
 
3265
3174
  // --- Touch scroll lock helper ---
3266
3175
  const addScrollLock = () => {
@@ -3272,10 +3181,6 @@ class EventService {
3272
3181
 
3273
3182
  // --- MOVE HANDLER (mouse + touch unified) ---
3274
3183
  const moveAt = (event) => {
3275
- const boardElement = this.boardService.element;
3276
- const squareSize = boardElement.offsetWidth / 8;
3277
-
3278
- // Get mouse/touch coordinates
3279
3184
  let clientX, clientY;
3280
3185
  if (event.touches && event.touches[0]) {
3281
3186
  clientX = event.touches[0].clientX;
@@ -3284,77 +3189,54 @@ class EventService {
3284
3189
  clientX = event.clientX;
3285
3190
  clientY = event.clientY;
3286
3191
  }
3287
-
3288
- // Calculate position relative to board
3289
- const boardRect = boardElement.getBoundingClientRect();
3290
- const x = clientX - boardRect.left - (squareSize / 2);
3291
- const y = clientY - boardRect.top - (squareSize / 2);
3292
-
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;
3293
3195
  img.style.left = x + 'px';
3294
3196
  img.style.top = y + 'px';
3295
-
3296
3197
  return true;
3297
3198
  };
3298
3199
 
3299
3200
  // --- DRAG MOVE (mouse + touch) ---
3201
+ const moveThreshold = 5; // px
3300
3202
  const onPointerMove = (event) => {
3301
- // For touch, only handle the first finger
3302
3203
  if (event.touches && event.touches.length > 1) return;
3303
3204
  if (event.touches && !event.touches[0]) return;
3304
- if (event.type && event.type.startsWith('touch')) event.preventDefault();
3305
-
3306
3205
  const currentX = event.clientX || (event.touches && event.touches[0]?.clientX) || 0;
3307
3206
  const currentY = event.clientY || (event.touches && event.touches[0]?.clientY) || 0;
3308
3207
  const deltaX = Math.abs(currentX - startX);
3309
3208
  const deltaY = Math.abs(currentY - startY);
3310
-
3311
- // Start dragging if mouse/touch moved enough
3312
- if (!isDragging && (deltaX > 3 || deltaY > 3)) {
3313
- isDragging = true;
3314
- // Inizio drag: blocca update board
3209
+ if (!dragStarted && (deltaX > moveThreshold || deltaY > moveThreshold)) {
3210
+ dragStarted = true;
3211
+ if (isTouch) addScrollLock();
3315
3212
  if (this.chessboard) this.chessboard._isDragging = true;
3316
-
3317
- // Mostra hint all'inizio del drag se attivi
3318
3213
  if (this.config.hints && typeof this.chessboard._boundOnPieceHover === 'function') {
3319
3214
  this.chessboard._boundOnPieceHover(from);
3320
3215
  }
3321
-
3322
- // Set up drag state
3323
3216
  if (!this.config.clickable) {
3324
3217
  this.clicked = null;
3325
3218
  this.clicked = from;
3326
3219
  } else if (!this.clicked) {
3327
3220
  this.clicked = from;
3328
3221
  }
3329
-
3330
- // Visual feedback
3331
3222
  if (this.config.clickable) {
3332
3223
  from.select();
3333
3224
  }
3334
-
3335
- // Prepare piece for dragging
3336
3225
  img.style.position = 'absolute';
3337
3226
  img.style.zIndex = '100';
3338
3227
  img.classList.add('dragging');
3339
-
3340
3228
  DragOptimizations.enableForDrag(img);
3341
-
3342
- // Lock scroll for touch
3343
- if (isTouch) addScrollLock();
3344
-
3345
- // Call drag start callback
3346
3229
  if (!onDragStart(square, piece)) {
3347
3230
  return;
3348
3231
  }
3349
3232
  }
3350
-
3351
- if (!isDragging) return;
3352
-
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;
3353
3238
  if (!moveAt(event)) ;
3354
-
3355
3239
  // Update target square
3356
- const boardElement = this.boardService.element;
3357
- const boardRect = boardElement.getBoundingClientRect();
3358
3240
  let clientX, clientY;
3359
3241
  if (event.touches && event.touches[0]) {
3360
3242
  clientX = event.touches[0].clientX;
@@ -3365,22 +3247,16 @@ class EventService {
3365
3247
  }
3366
3248
  const x = clientX - boardRect.left;
3367
3249
  const y = clientY - boardRect.top;
3368
-
3369
3250
  let newTo = null;
3370
3251
  if (x >= 0 && x <= boardRect.width && y >= 0 && y <= boardRect.height) {
3371
3252
  const squareId = this.coordinateService.pixelToSquareID(x, y, boardElement);
3372
3253
  newTo = squareId ? this.boardService.getSquare(squareId) : null;
3373
3254
  }
3374
-
3375
3255
  to = newTo;
3376
3256
  onDragMove(from, to, piece);
3377
-
3378
- // Update visual feedback
3379
- if (to !== previousHighlight) {
3380
- to?.highlight();
3381
- previousHighlight?.dehighlight();
3382
- previousHighlight = to;
3383
- }
3257
+ if (to) to.highlight();
3258
+ if (previousHighlight && previousHighlight !== to) previousHighlight.dehighlight();
3259
+ previousHighlight = to;
3384
3260
  };
3385
3261
 
3386
3262
  // --- DRAG END (mouse + touch) ---
@@ -3391,26 +3267,19 @@ class EventService {
3391
3267
  document.removeEventListener('touchmove', onPointerMove);
3392
3268
  window.removeEventListener('touchend', onPointerUp);
3393
3269
  if (isTouch) removeScrollLock();
3394
- // Fine drag: sblocca update board
3395
3270
  if (this.chessboard) this.chessboard._isDragging = false;
3396
-
3397
- // Rimuovi hint alla fine del drag se attivi
3398
3271
  if (this.config.hints && typeof this.chessboard._boundOnPieceLeave === 'function') {
3399
3272
  this.chessboard._boundOnPieceLeave(from);
3400
3273
  }
3401
-
3402
- if (!isDragging) {
3274
+ if (!dragStarted) {
3403
3275
  return;
3404
3276
  }
3405
-
3406
3277
  img.style.zIndex = '20';
3407
3278
  img.classList.remove('dragging');
3408
3279
  img.style.willChange = 'auto';
3409
-
3410
3280
  // Handle drop
3411
3281
  const dropResult = onDrop(originalFrom, to, piece);
3412
3282
  const isTrashDrop = !to && (this.config.dropOffBoard === 'trash' || dropResult === 'trash');
3413
-
3414
3283
  if (isTrashDrop) {
3415
3284
  this._handleTrashDrop(originalFrom, onRemove);
3416
3285
  } else if (!to) {
@@ -3424,16 +3293,12 @@ class EventService {
3424
3293
  }
3425
3294
  };
3426
3295
 
3427
- // --- Attach listeners (mouse + touch) ---
3296
+ // Attach listeners (mouse + touch)
3428
3297
  window.addEventListener('mouseup', onPointerUp, { once: true });
3429
3298
  document.addEventListener('mousemove', onPointerMove);
3430
3299
  img.addEventListener('mouseup', onPointerUp, { once: true });
3431
- // Touch events
3432
3300
  window.addEventListener('touchend', onPointerUp, { once: true });
3433
3301
  document.addEventListener('touchmove', onPointerMove, { passive: false });
3434
-
3435
- // Per robustezza: se il drag parte da touch, blocca subito lo scroll
3436
- if (isTouch) addScrollLock();
3437
3302
  };
3438
3303
  }
3439
3304
 
@@ -3926,33 +3791,21 @@ class EventService {
3926
3791
  * Removes all existing event listeners
3927
3792
  */
3928
3793
  removeListeners() {
3929
- this.eventListeners.forEach((listeners, squareId) => {
3930
- listeners.forEach(({ element, type, handler }) => {
3931
- element.removeEventListener(type, handler);
3932
- });
3933
- });
3934
-
3935
- this.eventListeners.clear();
3794
+ this.listenerManager.removeAll();
3936
3795
  }
3937
3796
 
3938
3797
  /**
3939
3798
  * Removes all event listeners
3940
3799
  */
3941
3800
  removeAllListeners() {
3942
- this.eventListeners.forEach((listeners, squareId) => {
3943
- listeners.forEach(({ element, type, handler }) => {
3944
- element.removeEventListener(type, handler);
3945
- });
3946
- });
3947
-
3948
- this.eventListeners.clear();
3801
+ this.listenerManager.removeAll();
3949
3802
  }
3950
3803
 
3951
3804
  /**
3952
3805
  * Cleans up resources
3953
3806
  */
3954
3807
  destroy() {
3955
- this.removeAllListeners();
3808
+ this.listenerManager.destroy();
3956
3809
  this.clicked = null;
3957
3810
  this.promoting = false;
3958
3811
  this.isAnimating = false;
@@ -3960,7 +3813,7 @@ class EventService {
3960
3813
  }
3961
3814
 
3962
3815
  /**
3963
- * Service for managing chess moves and move validation
3816
+ * MoveService - Handles chess move management and validation
3964
3817
  * @module services/MoveService
3965
3818
  * @since 2.0.0
3966
3819
  */
@@ -3968,13 +3821,13 @@ class EventService {
3968
3821
 
3969
3822
  /**
3970
3823
  * Service responsible for move management and validation
3971
- * @class
3824
+ * @class MoveService
3972
3825
  */
3973
3826
  class MoveService {
3974
3827
  /**
3975
- * Creates a new MoveService instance
3976
- * @param {ChessboardConfig} config - Board configuration
3977
- * @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
3978
3831
  */
3979
3832
  constructor(config, positionService) {
3980
3833
  this.config = config;
@@ -3984,43 +3837,32 @@ class MoveService {
3984
3837
  }
3985
3838
 
3986
3839
  /**
3987
- * Checks if a piece on a square can move
3840
+ * Check if a piece on a square can move
3988
3841
  * @param {Square} square - Square to check
3989
3842
  * @returns {boolean} True if piece can move
3990
3843
  */
3991
3844
  canMove(square) {
3992
3845
  if (!square.piece) return false;
3993
-
3994
3846
  const { movableColors, onlyLegalMoves } = this.config;
3995
-
3996
3847
  if (movableColors === 'none') return false;
3997
3848
  if (movableColors === 'w' && square.piece.color === 'b') return false;
3998
3849
  if (movableColors === 'b' && square.piece.color === 'w') return false;
3999
-
4000
3850
  if (!onlyLegalMoves) return true;
4001
-
4002
- // Check if position service and game are available
4003
- if (!this.positionService || !this.positionService.getGame()) {
4004
- return false;
4005
- }
4006
-
3851
+ if (!this.positionService || !this.positionService.getGame()) return false;
4007
3852
  const game = this.positionService.getGame();
4008
3853
  return square.piece.color === game.turn();
4009
3854
  }
4010
3855
 
4011
3856
  /**
4012
- * Converts various move formats to a Move instance
3857
+ * Convert various move formats to a Move instance
4013
3858
  * @param {string|Move|Object} move - Move in various formats
4014
3859
  * @param {Object} squares - All board squares
4015
3860
  * @returns {Move} Move instance
4016
3861
  * @throws {MoveError} When move format is invalid
4017
3862
  */
4018
3863
  convertMove(move, squares) {
4019
- if (move instanceof Move$1) {
4020
- return move;
4021
- }
3864
+ if (move instanceof Move$1) return move;
4022
3865
  if (typeof move === 'object' && move.from && move.to) {
4023
- // Se sono id, converto in oggetti; se sono già oggetti, li uso direttamente
4024
3866
  const fromSquare = typeof move.from === 'string' ? squares[move.from] : move.from;
4025
3867
  const toSquare = typeof move.to === 'string' ? squares[move.to] : move.to;
4026
3868
  if (!fromSquare || !toSquare) throw new MoveError(ERROR_MESSAGES.invalid_move_format, move.from, move.to);
@@ -4039,13 +3881,12 @@ class MoveService {
4039
3881
  }
4040
3882
 
4041
3883
  /**
4042
- * Checks if a move is legal
3884
+ * Check if a move is legal
4043
3885
  * @param {Move} move - Move to check
4044
3886
  * @returns {boolean} True if move is legal
4045
3887
  */
4046
3888
  isLegalMove(move) {
4047
3889
  const legalMoves = this.getLegalMoves(move.from.id);
4048
-
4049
3890
  return legalMoves.some(legalMove =>
4050
3891
  legalMove.to === move.to.id &&
4051
3892
  move.promotion === legalMove.promotion
@@ -4053,180 +3894,100 @@ class MoveService {
4053
3894
  }
4054
3895
 
4055
3896
  /**
4056
- * Gets all legal moves for a square or the entire position
3897
+ * Get all legal moves for a square or the entire position
4057
3898
  * @param {string} [from] - Square to get moves from (optional)
4058
3899
  * @param {boolean} [verbose=true] - Whether to return verbose move objects
4059
3900
  * @returns {Array} Array of legal moves
4060
3901
  */
4061
3902
  getLegalMoves(from = null, verbose = true) {
4062
- // Check if position service and game are available
4063
- if (!this.positionService || !this.positionService.getGame()) {
4064
- return [];
4065
- }
4066
-
3903
+ if (!this.positionService || !this.positionService.getGame()) return [];
4067
3904
  const game = this.positionService.getGame();
4068
-
4069
3905
  if (!game) return [];
4070
-
4071
3906
  const options = { verbose };
4072
- if (from) {
4073
- options.square = from;
4074
- }
4075
-
3907
+ if (from) options.square = from;
4076
3908
  return game.moves(options);
4077
3909
  }
4078
3910
 
4079
3911
  /**
4080
- * Gets legal moves with caching for performance
3912
+ * Get legal moves with caching for performance
4081
3913
  * @param {Square} square - Square to get moves from
4082
3914
  * @returns {Array} Array of legal moves
4083
3915
  */
4084
3916
  getCachedLegalMoves(square) {
4085
- // Check if position service and game are available
4086
- if (!this.positionService || !this.positionService.getGame()) {
4087
- return [];
4088
- }
4089
-
3917
+ if (!this.positionService || !this.positionService.getGame()) return [];
4090
3918
  const game = this.positionService.getGame();
4091
3919
  if (!game) return [];
4092
-
4093
3920
  const cacheKey = `${square.id}-${game.fen()}`;
4094
3921
  let moves = this._movesCache.get(cacheKey);
4095
-
4096
3922
  if (!moves) {
4097
3923
  moves = game.moves({ square: square.id, verbose: true });
4098
3924
  this._movesCache.set(cacheKey, moves);
4099
-
4100
- // Clear cache after a short delay to prevent memory buildup
4101
- if (this._cacheTimeout) {
4102
- clearTimeout(this._cacheTimeout);
4103
- }
4104
-
3925
+ if (this._cacheTimeout) clearTimeout(this._cacheTimeout);
4105
3926
  this._cacheTimeout = setTimeout(() => {
4106
3927
  this._movesCache.clear();
4107
3928
  }, 1000);
4108
3929
  }
4109
-
4110
3930
  return moves;
4111
3931
  }
4112
3932
 
4113
3933
  /**
4114
- * Executes a move on the game
4115
- * @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)
4116
3936
  * @returns {Object|null} Move result from chess.js or null if invalid
4117
3937
  */
4118
3938
  executeMove(move) {
4119
- if (!(move instanceof Move$1)) throw new Error('executeMove richiede un oggetto Move');
4120
- if (!this.positionService || !this.positionService.getGame()) {
4121
- return null;
4122
- }
3939
+ if (!(move instanceof Move$1)) throw new Error('executeMove requires a Move object');
3940
+ if (!this.positionService || !this.positionService.getGame()) return null;
4123
3941
  const game = this.positionService.getGame();
4124
3942
  if (!game) return null;
4125
- const moveOptions = {
4126
- from: move.from.id,
4127
- to: move.to.id
4128
- };
4129
- if (move.hasPromotion()) {
4130
- moveOptions.promotion = move.promotion;
4131
- }
3943
+ const moveOptions = { from: move.from.id, to: move.to.id };
3944
+ if (move.hasPromotion()) moveOptions.promotion = move.promotion;
4132
3945
  const result = game.move(moveOptions);
4133
3946
  return result;
4134
3947
  }
4135
3948
 
4136
3949
  /**
4137
- * Determina se una mossa richiede promozione
4138
- * @param {Move} move - Deve essere oggetto Move
3950
+ * Determine if a move requires promotion
3951
+ * @param {Move} move - Must be a Move object
4139
3952
  * @returns {boolean}
4140
3953
  */
4141
3954
  requiresPromotion(move) {
4142
- if (!(move instanceof Move$1)) throw new Error('requiresPromotion richiede un oggetto Move');
4143
- console.log('Checking if move requires promotion:', move.from.id, '->', move.to.id);
4144
-
4145
- if (!this.config.onlyLegalMoves) {
4146
- console.log('Not in legal moves mode, no promotion required');
4147
- return false;
4148
- }
4149
-
3955
+ if (!(move instanceof Move$1)) throw new Error('requiresPromotion requires a Move object');
3956
+ if (!this.config.onlyLegalMoves) return false;
4150
3957
  const game = this.positionService.getGame();
4151
- if (!game) {
4152
- console.log('No game instance available');
4153
- return false;
4154
- }
4155
-
3958
+ if (!game) return false;
4156
3959
  const piece = game.get(move.from.id);
4157
- if (!piece || piece.type !== 'p') {
4158
- console.log('Not a pawn move, no promotion required');
4159
- return false;
4160
- }
4161
-
3960
+ if (!piece || piece.type !== 'p') return false;
4162
3961
  const targetRank = move.to.row;
4163
- if (targetRank !== 1 && targetRank !== 8) {
4164
- console.log('Not reaching promotion rank, no promotion required');
4165
- return false;
4166
- }
4167
-
4168
- console.log('Pawn reaching promotion rank, validating move...');
4169
-
4170
- // Additional validation: check if the pawn can actually reach this square
4171
- if (!this._isPawnMoveValid(move.from, move.to, piece.color)) {
4172
- console.log('Pawn move not valid, no promotion required');
4173
- return false;
4174
- }
4175
-
4176
- // First check if the move is legal without promotion
4177
- const simpleMoveObj = {
4178
- from: move.from.id,
4179
- to: move.to.id
4180
- };
4181
-
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 };
4182
3966
  try {
4183
- console.log('Testing move without promotion:', simpleMoveObj);
4184
- // Test if the move is legal without promotion first
4185
3967
  const testMove = game.move(simpleMoveObj);
4186
3968
  if (testMove) {
4187
- // Move was successful, but check if it was a promotion
4188
3969
  const wasPromotion = testMove.promotion;
4189
-
4190
- // Undo the test move
4191
3970
  game.undo();
4192
-
4193
- console.log('Move successful without promotion, was promotion:', wasPromotion !== undefined);
4194
-
4195
- // If it was a promotion, return true
4196
3971
  return wasPromotion !== undefined;
4197
3972
  }
4198
3973
  } catch (error) {
4199
- console.log('Move failed without promotion, trying with promotion:', error.message);
4200
-
4201
- // If simple move fails, try with promotion
4202
- const promotionMoveObj = {
4203
- from: move.from.id,
4204
- to: move.to.id,
4205
- promotion: 'q' // test with queen
4206
- };
4207
-
3974
+ // Try with promotion
3975
+ const promotionMoveObj = { from: move.from.id, to: move.to.id, promotion: 'q' };
4208
3976
  try {
4209
- console.log('Testing move with promotion:', promotionMoveObj);
4210
3977
  const testMove = game.move(promotionMoveObj);
4211
3978
  if (testMove) {
4212
- // Undo the test move
4213
3979
  game.undo();
4214
- console.log('Move successful with promotion, promotion required');
4215
3980
  return true;
4216
3981
  }
4217
3982
  } catch (promotionError) {
4218
- console.log('Move failed even with promotion:', promotionError.message);
4219
- // Move is not legal even with promotion
4220
3983
  return false;
4221
3984
  }
4222
3985
  }
4223
-
4224
- console.log('Move validation complete, no promotion required');
4225
3986
  return false;
4226
3987
  }
4227
3988
 
4228
3989
  /**
4229
- * Validates if a pawn move is theoretically possible
3990
+ * Validate if a pawn move is theoretically possible
4230
3991
  * @private
4231
3992
  * @param {Square} from - Source square
4232
3993
  * @param {Square} to - Target square
@@ -4238,48 +3999,21 @@ class MoveService {
4238
3999
  const toRank = to.row;
4239
4000
  const fromFile = from.col;
4240
4001
  const toFile = to.col;
4241
-
4242
- console.log(`Validating pawn move: ${from.id} -> ${to.id} (${color})`);
4243
- console.log(`Ranks: ${fromRank} -> ${toRank}, Files: ${fromFile} -> ${toFile}`);
4244
-
4245
- // Direction of pawn movement
4246
4002
  const direction = color === 'w' ? 1 : -1;
4247
4003
  const rankDiff = toRank - fromRank;
4248
4004
  const fileDiff = Math.abs(toFile - fromFile);
4249
-
4250
- // Pawn can only move forward
4251
- if (rankDiff * direction <= 0) {
4252
- console.log('Invalid: Pawn cannot move backward or stay in place');
4253
- return false;
4254
- }
4255
-
4256
- // Pawn can only move 1 rank at a time (except for double move from starting position)
4257
- if (Math.abs(rankDiff) > 2) {
4258
- console.log('Invalid: Pawn cannot move more than 2 ranks');
4259
- return false;
4260
- }
4261
-
4262
- // If moving 2 ranks, must be from starting position
4005
+ if (rankDiff * direction <= 0) return false;
4006
+ if (Math.abs(rankDiff) > 2) return false;
4263
4007
  if (Math.abs(rankDiff) === 2) {
4264
4008
  const startingRank = color === 'w' ? 2 : 7;
4265
- if (fromRank !== startingRank) {
4266
- console.log(`Invalid: Pawn cannot move 2 ranks from rank ${fromRank}`);
4267
- return false;
4268
- }
4009
+ if (fromRank !== startingRank) return false;
4269
4010
  }
4270
-
4271
- // Pawn can only move to adjacent files (diagonal capture) or same file (forward move)
4272
- if (fileDiff > 1) {
4273
- console.log('Invalid: Pawn cannot move more than 1 file');
4274
- return false;
4275
- }
4276
-
4277
- console.log('Pawn move validation passed');
4011
+ if (fileDiff > 1) return false;
4278
4012
  return true;
4279
4013
  }
4280
4014
 
4281
4015
  /**
4282
- * Handles promotion UI setup
4016
+ * Handle promotion UI setup
4283
4017
  * @param {Move} move - Move requiring promotion
4284
4018
  * @param {Object} squares - All board squares
4285
4019
  * @param {Function} onPromotionSelect - Callback when promotion piece is selected
@@ -4288,55 +4022,33 @@ class MoveService {
4288
4022
  */
4289
4023
  setupPromotion(move, squares, onPromotionSelect, onPromotionCancel) {
4290
4024
  if (!this.requiresPromotion(move)) return false;
4291
-
4292
- // Check if position service and game are available
4293
- if (!this.positionService || !this.positionService.getGame()) {
4294
- return false;
4295
- }
4296
-
4025
+ if (!this.positionService || !this.positionService.getGame()) return false;
4297
4026
  const game = this.positionService.getGame();
4298
4027
  const piece = game.get(move.from.id);
4299
4028
  const targetSquare = move.to;
4300
-
4301
- // Clear any existing promotion UI
4302
4029
  Object.values(squares).forEach(square => {
4303
4030
  square.removePromotion();
4304
4031
  square.removeCover();
4305
4032
  });
4306
-
4307
- // Always show promotion choices in a column
4308
4033
  this._showPromotionInColumn(targetSquare, piece, squares, onPromotionSelect, onPromotionCancel);
4309
-
4310
4034
  return true;
4311
4035
  }
4312
4036
 
4313
4037
  /**
4314
- * Shows promotion choices in a column
4038
+ * Show promotion choices in a column
4315
4039
  * @private
4316
4040
  */
4317
4041
  _showPromotionInColumn(targetSquare, piece, squares, onPromotionSelect, onPromotionCancel) {
4318
- console.log('Setting up promotion for', targetSquare.id, 'piece color:', piece.color);
4319
-
4320
- // Set up promotion choices starting from border row
4321
4042
  PROMOTION_PIECES.forEach((pieceType, index) => {
4322
4043
  const choiceSquare = this._findPromotionSquare(targetSquare, index, squares);
4323
-
4324
4044
  if (choiceSquare) {
4325
4045
  const pieceId = pieceType + piece.color;
4326
4046
  const piecePath = this._getPiecePathForPromotion(pieceId);
4327
-
4328
- console.log('Setting up promotion choice:', pieceType, 'on square:', choiceSquare.id);
4329
-
4330
4047
  choiceSquare.putPromotion(piecePath, () => {
4331
- console.log('Promotion choice selected:', pieceType);
4332
4048
  onPromotionSelect(pieceType);
4333
4049
  });
4334
- } else {
4335
- console.log('Could not find square for promotion choice:', pieceType, 'index:', index);
4336
4050
  }
4337
4051
  });
4338
-
4339
- // Set up cover squares (for cancellation)
4340
4052
  Object.values(squares).forEach(square => {
4341
4053
  if (!square.hasPromotion()) {
4342
4054
  square.putCover(() => {
@@ -4344,181 +4056,119 @@ class MoveService {
4344
4056
  });
4345
4057
  }
4346
4058
  });
4347
-
4348
4059
  return true;
4349
4060
  }
4350
4061
 
4351
4062
  /**
4352
- * Finds the appropriate square for a promotion piece
4063
+ * Find the appropriate square for a promotion piece
4353
4064
  * @private
4354
4065
  * @param {Square} targetSquare - Target square of the promotion move
4355
- * @param {number} distance - Distance from target square
4066
+ * @param {number} index - Distance from target square
4356
4067
  * @param {Object} squares - All board squares
4357
4068
  * @returns {Square|null} Square for promotion piece or null
4358
4069
  */
4359
4070
  _findPromotionSquare(targetSquare, index, squares) {
4360
4071
  const col = targetSquare.col;
4361
4072
  const baseRow = targetSquare.row;
4362
-
4363
- console.log('Looking for promotion square - target:', targetSquare.id, 'index:', index, 'col:', col, 'baseRow:', baseRow);
4364
-
4365
- // Calculate row based on index and promotion direction
4366
- // Start from the border row (1 or 8) and go inward
4367
4073
  let row;
4368
4074
  if (baseRow === 8) {
4369
- // White promotion: start from row 8 and go down
4370
4075
  row = 8 - index;
4371
4076
  } else if (baseRow === 1) {
4372
- // Black promotion: start from row 1 and go up
4373
4077
  row = 1 + index;
4374
4078
  } else {
4375
- console.log('Invalid promotion row:', baseRow);
4376
4079
  return null;
4377
4080
  }
4378
-
4379
- console.log('Calculated row:', row);
4380
-
4381
- // Ensure row is within bounds
4382
- if (row < 1 || row > 8) {
4383
- console.log('Row out of bounds:', row);
4384
- return null;
4385
- }
4386
-
4387
- // Find square by row/col
4081
+ if (row < 1 || row > 8) return null;
4388
4082
  for (const square of Object.values(squares)) {
4389
4083
  if (square.col === col && square.row === row) {
4390
- console.log('Found promotion square:', square.id);
4391
4084
  return square;
4392
4085
  }
4393
4086
  }
4394
-
4395
- console.log('No square found for col:', col, 'row:', row);
4396
4087
  return null;
4397
4088
  }
4398
4089
 
4399
4090
  /**
4400
- * Gets piece path for promotion UI
4091
+ * Get piece path for promotion UI
4401
4092
  * @private
4402
4093
  * @param {string} pieceId - Piece identifier
4403
4094
  * @returns {string} Path to piece asset
4404
4095
  */
4405
4096
  _getPiecePathForPromotion(pieceId) {
4406
- // This would typically use the PieceService
4407
- // For now, we'll use a simple implementation
4408
4097
  const { piecesPath } = this.config;
4409
-
4410
4098
  if (typeof piecesPath === 'string') {
4411
4099
  return `${piecesPath}/${pieceId}.svg`;
4412
4100
  }
4413
-
4414
- // Fallback for other path types
4415
4101
  return `assets/pieces/${pieceId}.svg`;
4416
4102
  }
4417
4103
 
4418
4104
  /**
4419
- * Parses a move string into a move object
4105
+ * Parse a move string into a move object
4420
4106
  * @param {string} moveString - Move string (e.g., 'e2e4', 'e7e8q')
4421
4107
  * @returns {Object|null} Move object or null if invalid
4422
4108
  */
4423
4109
  parseMove(moveString) {
4424
- if (typeof moveString !== 'string' || moveString.length < 4 || moveString.length > 5) {
4425
- return null;
4426
- }
4427
-
4110
+ if (typeof moveString !== 'string' || moveString.length < 4 || moveString.length > 5) return null;
4428
4111
  const from = moveString.slice(0, 2);
4429
4112
  const to = moveString.slice(2, 4);
4430
4113
  const promotion = moveString.slice(4, 5);
4431
-
4432
- // Basic validation
4433
- if (!/^[a-h][1-8]$/.test(from) || !/^[a-h][1-8]$/.test(to)) {
4434
- return null;
4435
- }
4436
-
4437
- if (promotion && !['q', 'r', 'b', 'n'].includes(promotion.toLowerCase())) {
4438
- return null;
4439
- }
4440
-
4441
- return {
4442
- from: from,
4443
- to: to,
4444
- promotion: promotion || null
4445
- };
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 };
4446
4117
  }
4447
4118
 
4448
4119
  /**
4449
- * Checks if a move is a castle move
4120
+ * Check if a move is a castle move
4450
4121
  * @param {Object} gameMove - Game move object from chess.js
4451
4122
  * @returns {boolean} True if move is castle
4452
4123
  */
4453
4124
  isCastle(gameMove) {
4454
- return gameMove && (gameMove.isKingsideCastle() || gameMove.isQueensideCastle());
4125
+ return gameMove && (gameMove.isKingsideCastle && gameMove.isKingsideCastle() || gameMove.isQueensideCastle && gameMove.isQueensideCastle());
4455
4126
  }
4456
4127
 
4457
4128
  /**
4458
- * Gets the rook move for a castle move
4129
+ * Get the rook move for a castle move
4459
4130
  * @param {Object} gameMove - Game move object from chess.js
4460
4131
  * @returns {Object|null} Rook move object or null if not castle
4461
4132
  */
4462
4133
  getCastleRookMove(gameMove) {
4463
- if (!this.isCastle(gameMove)) {
4464
- return null;
4465
- }
4466
-
4134
+ if (!this.isCastle(gameMove)) return null;
4467
4135
  const isKingSide = gameMove.isKingsideCastle();
4468
4136
  const isWhite = gameMove.color === 'w';
4469
-
4470
4137
  if (isKingSide) {
4471
- // King side castle
4472
- if (isWhite) {
4473
- return { from: 'h1', to: 'f1' };
4474
- } else {
4475
- return { from: 'h8', to: 'f8' };
4476
- }
4138
+ return isWhite ? { from: 'h1', to: 'f1' } : { from: 'h8', to: 'f8' };
4477
4139
  } else {
4478
- // Queen side castle
4479
- if (isWhite) {
4480
- return { from: 'a1', to: 'd1' };
4481
- } else {
4482
- return { from: 'a8', to: 'd8' };
4483
- }
4140
+ return isWhite ? { from: 'a1', to: 'd1' } : { from: 'a8', to: 'd8' };
4484
4141
  }
4485
4142
  }
4486
4143
 
4487
4144
  /**
4488
- * Checks if a move is en passant
4145
+ * Check if a move is en passant
4489
4146
  * @param {Object} gameMove - Game move object from chess.js
4490
4147
  * @returns {boolean} True if move is en passant
4491
4148
  */
4492
4149
  isEnPassant(gameMove) {
4493
- return gameMove && gameMove.isEnPassant();
4150
+ return gameMove && gameMove.isEnPassant && gameMove.isEnPassant();
4494
4151
  }
4495
4152
 
4496
4153
  /**
4497
- * Gets the captured pawn square for en passant
4154
+ * Get the captured pawn square for en passant
4498
4155
  * @param {Object} gameMove - Game move object from chess.js
4499
4156
  * @returns {string|null} Square of captured pawn or null if not en passant
4500
4157
  */
4501
4158
  getEnPassantCapturedSquare(gameMove) {
4502
- if (!this.isEnPassant(gameMove)) {
4503
- return null;
4504
- }
4505
-
4159
+ if (!this.isEnPassant(gameMove)) return null;
4506
4160
  const toSquare = gameMove.to;
4507
4161
  const rank = parseInt(toSquare[1]);
4508
4162
  const file = toSquare[0];
4509
-
4510
- // The captured pawn is on the same file but different rank
4511
4163
  if (gameMove.color === 'w') {
4512
- // White captures black pawn one rank below
4513
4164
  return file + (rank - 1);
4514
4165
  } else {
4515
- // Black captures white pawn one rank above
4516
4166
  return file + (rank + 1);
4517
4167
  }
4518
4168
  }
4519
4169
 
4520
4170
  /**
4521
- * Clears the moves cache
4171
+ * Clear the moves cache
4522
4172
  */
4523
4173
  clearCache() {
4524
4174
  this._movesCache.clear();
@@ -4529,7 +4179,7 @@ class MoveService {
4529
4179
  }
4530
4180
 
4531
4181
  /**
4532
- * Cleans up resources
4182
+ * Clean up resources
4533
4183
  */
4534
4184
  destroy() {
4535
4185
  this.clearCache();
@@ -4538,7 +4188,7 @@ class MoveService {
4538
4188
  }
4539
4189
 
4540
4190
  /**
4541
- * Service for managing chess pieces and their operations
4191
+ * PieceService - Handles chess piece management and operations
4542
4192
  * @module services/PieceService
4543
4193
  * @since 2.0.0
4544
4194
  */
@@ -4546,11 +4196,11 @@ class MoveService {
4546
4196
 
4547
4197
  /**
4548
4198
  * Service responsible for piece management and operations
4549
- * @class
4199
+ * @class PieceService
4550
4200
  */
4551
4201
  class PieceService {
4552
4202
  /**
4553
- * Creates a new PieceService instance
4203
+ * Create a new PieceService instance
4554
4204
  * @param {ChessboardConfig} config - Board configuration
4555
4205
  */
4556
4206
  constructor(config) {
@@ -4558,14 +4208,13 @@ class PieceService {
4558
4208
  }
4559
4209
 
4560
4210
  /**
4561
- * Gets the path to a piece asset
4211
+ * Get the path to a piece asset
4562
4212
  * @param {string} piece - Piece identifier (e.g., 'wK', 'bP')
4563
4213
  * @returns {string} Path to piece asset
4564
4214
  * @throws {ValidationError} When piecesPath configuration is invalid
4565
4215
  */
4566
4216
  getPiecePath(piece) {
4567
4217
  const { piecesPath } = this.config;
4568
-
4569
4218
  if (typeof piecesPath === 'string') {
4570
4219
  return `${piecesPath}/${piece}.svg`;
4571
4220
  } else if (typeof piecesPath === 'object' && piecesPath !== null) {
@@ -4578,7 +4227,7 @@ class PieceService {
4578
4227
  }
4579
4228
 
4580
4229
  /**
4581
- * Converts various piece formats to a Piece instance
4230
+ * Convert various piece formats to a Piece instance
4582
4231
  * @param {string|Piece} piece - Piece in various formats
4583
4232
  * @returns {Piece} Piece instance
4584
4233
  * @throws {PieceError} When piece format is invalid
@@ -4587,54 +4236,41 @@ class PieceService {
4587
4236
  if (piece instanceof Piece) {
4588
4237
  return piece;
4589
4238
  }
4590
-
4591
4239
  if (typeof piece === 'string' && piece.length === 2) {
4592
4240
  const [first, second] = piece.split('');
4593
4241
  let type, color;
4594
-
4595
- // Check format: [type][color] (e.g., 'pw')
4596
4242
  if (PIECE_TYPES.includes(first.toLowerCase()) && PIECE_COLORS.includes(second)) {
4597
4243
  type = first.toLowerCase();
4598
4244
  color = second;
4599
- }
4600
- // Check format: [color][type] (e.g., 'wP')
4601
- else if (PIECE_COLORS.includes(first) && PIECE_TYPES.includes(second.toLowerCase())) {
4245
+ } else if (PIECE_COLORS.includes(first) && PIECE_TYPES.includes(second.toLowerCase())) {
4602
4246
  color = first;
4603
4247
  type = second.toLowerCase();
4604
4248
  } else {
4605
4249
  throw new PieceError(ERROR_MESSAGES.invalid_piece + piece, piece);
4606
4250
  }
4607
-
4608
4251
  const piecePath = this.getPiecePath(type + color);
4609
4252
  return new Piece(color, type, piecePath);
4610
4253
  }
4611
-
4612
4254
  throw new PieceError(ERROR_MESSAGES.invalid_piece + piece, piece);
4613
4255
  }
4614
4256
 
4615
4257
  /**
4616
- * Adds a piece to a square with optional fade-in animation
4617
- * @param {Square} square - Target square (oggetto)
4618
- * @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
4619
4261
  * @param {boolean} [fade=true] - Whether to fade in the piece
4620
4262
  * @param {Function} dragFunction - Function to handle drag events
4621
4263
  * @param {Function} [callback] - Callback when animation completes
4622
4264
  */
4623
4265
  addPieceOnSquare(square, piece, fade = true, dragFunction, callback) {
4624
- if (!square || !piece) throw new Error('addPieceOnSquare richiede oggetti Square e Piece');
4625
- console.debug(`[PieceService] addPieceOnSquare: ${piece.id} to ${square.id}`);
4266
+ if (!square || !piece) throw new Error('addPieceOnSquare requires Square and Piece objects');
4626
4267
  square.putPiece(piece);
4627
-
4628
- // Imposta sempre il drag (touch e mouse)
4629
4268
  if (dragFunction) {
4630
4269
  piece.setDrag(dragFunction(square, piece));
4631
4270
  }
4632
- // Forza il drag touch se manca (debug/robustezza)
4633
4271
  if (!piece.element.ontouchstart) {
4634
4272
  piece.element.ontouchstart = dragFunction ? dragFunction(square, piece) : () => { };
4635
- console.debug(`[PieceService] Forzato ontouchstart su ${piece.id}`);
4636
4273
  }
4637
-
4638
4274
  if (fade && this.config.fadeTime > 0) {
4639
4275
  piece.fadeIn(
4640
4276
  this.config.fadeTime,
@@ -4645,28 +4281,24 @@ class PieceService {
4645
4281
  } else {
4646
4282
  if (callback) callback();
4647
4283
  }
4648
-
4649
4284
  piece.visible();
4650
4285
  }
4651
4286
 
4652
4287
  /**
4653
- * Rimuove un pezzo da una casella
4654
- * @param {Square} square - Oggetto Square
4288
+ * Remove a piece from a square
4289
+ * @param {Square} square - Square object
4655
4290
  * @param {boolean} [fade=true]
4656
4291
  * @param {Function} [callback]
4657
- * @returns {Piece} Il pezzo rimosso
4292
+ * @returns {Piece} The removed piece
4658
4293
  */
4659
4294
  removePieceFromSquare(square, fade = true, callback) {
4660
- if (!square) throw new Error('removePieceFromSquare richiede oggetto Square');
4661
- console.debug(`[PieceService] removePieceFromSquare: ${square.id}`);
4295
+ if (!square) throw new Error('removePieceFromSquare requires a Square object');
4662
4296
  square.check();
4663
-
4664
4297
  const piece = square.piece;
4665
4298
  if (!piece) {
4666
4299
  if (callback) callback();
4667
4300
  throw new PieceError(ERROR_MESSAGES.square_no_piece, null, square.getId());
4668
4301
  }
4669
-
4670
4302
  if (fade && this.config.fadeTime > 0) {
4671
4303
  piece.fadeOut(
4672
4304
  this.config.fadeTime,
@@ -4677,26 +4309,22 @@ class PieceService {
4677
4309
  } else {
4678
4310
  if (callback) callback();
4679
4311
  }
4680
-
4681
4312
  square.removePiece();
4682
4313
  return piece;
4683
4314
  }
4684
4315
 
4685
4316
  /**
4686
- * Moves a piece to a new position with animation
4317
+ * Move a piece to a new position with animation
4687
4318
  * @param {Piece} piece - Piece to move
4688
4319
  * @param {Square} targetSquare - Target square
4689
4320
  * @param {number} duration - Animation duration
4690
4321
  * @param {Function} [callback] - Callback function when animation completes
4691
4322
  */
4692
4323
  movePiece(piece, targetSquare, duration, callback) {
4693
- console.debug(`[PieceService] movePiece: ${piece.id} to ${targetSquare.id}`);
4694
4324
  if (!piece || !piece.element) {
4695
- console.warn(`[PieceService] movePiece: piece or element is null, skipping animation`);
4696
4325
  if (callback) callback();
4697
4326
  return;
4698
4327
  }
4699
-
4700
4328
  piece.translate(
4701
4329
  targetSquare,
4702
4330
  duration,
@@ -4707,7 +4335,7 @@ class PieceService {
4707
4335
  }
4708
4336
 
4709
4337
  /**
4710
- * Handles piece translation with optional capture
4338
+ * Handle piece translation with optional capture
4711
4339
  * @param {Move} move - Move object containing from/to squares and piece
4712
4340
  * @param {boolean} removeTarget - Whether to remove piece from target square
4713
4341
  * @param {boolean} animate - Whether to animate the move
@@ -4715,54 +4343,37 @@ class PieceService {
4715
4343
  * @param {Function} [callback] - Callback function when complete
4716
4344
  */
4717
4345
  translatePiece(move, removeTarget, animate, dragFunction = null, callback = null) {
4718
- console.debug(`[PieceService] translatePiece: ${move.piece.id} from ${move.from.id} to ${move.to.id}`);
4719
4346
  if (!move.piece) {
4720
- console.warn('PieceService.translatePiece: move.piece is null, skipping translation');
4721
4347
  if (callback) callback();
4722
4348
  return;
4723
4349
  }
4724
-
4725
4350
  if (removeTarget) {
4726
- // Deselect the captured piece before removing it
4727
4351
  move.to.deselect();
4728
4352
  this.removePieceFromSquare(move.to, false);
4729
4353
  }
4730
-
4731
4354
  const changeSquareCallback = () => {
4732
- // Check if piece still exists and is on the source square
4733
4355
  if (move.from.piece === move.piece) {
4734
- move.from.removePiece(true); // Preserve the piece when moving
4356
+ move.from.removePiece(true);
4735
4357
  }
4736
-
4737
- // Only put piece if destination square doesn't already have it
4738
4358
  if (move.to.piece !== move.piece) {
4739
4359
  move.to.putPiece(move.piece);
4740
-
4741
- // Re-attach drag handler if provided
4742
4360
  if (dragFunction && this.config.draggable && move.piece.element) {
4743
4361
  move.piece.setDrag(dragFunction(move.to, move.piece));
4744
4362
  }
4745
4363
  }
4746
-
4747
4364
  if (callback) callback();
4748
4365
  };
4749
-
4750
- // Check if piece is currently being dragged
4751
4366
  const isDragging = move.piece.element && move.piece.element.classList.contains('dragging');
4752
-
4753
4367
  if (isDragging) {
4754
- // If piece is being dragged, don't animate - just move it immediately
4755
- // The piece is already visually in the correct position from the drag
4756
4368
  changeSquareCallback();
4757
4369
  } else {
4758
- // Normal animation
4759
4370
  const duration = animate ? this.config.moveTime : 0;
4760
4371
  this.movePiece(move.piece, move.to, duration, changeSquareCallback);
4761
4372
  }
4762
4373
  }
4763
4374
 
4764
4375
  /**
4765
- * Snaps a piece back to its original position
4376
+ * Snap a piece back to its original position
4766
4377
  * @param {Square} square - Square containing the piece
4767
4378
  * @param {boolean} [animate=true] - Whether to animate the snapback
4768
4379
  */
@@ -4772,7 +4383,6 @@ class PieceService {
4772
4383
  }
4773
4384
  const piece = square.piece;
4774
4385
  const duration = animate ? this.config.snapbackTime : 0;
4775
- console.debug(`[PieceService] snapbackPiece: ${piece.id} on ${square.id}`);
4776
4386
  piece.translate(
4777
4387
  square,
4778
4388
  duration,
@@ -4782,7 +4392,7 @@ class PieceService {
4782
4392
  }
4783
4393
 
4784
4394
  /**
4785
- * Centers a piece in its square with animation (after successful drop)
4395
+ * Center a piece in its square with animation (after successful drop)
4786
4396
  * @param {Square} square - Square containing the piece to center
4787
4397
  * @param {boolean} animate - Whether to animate the centering
4788
4398
  */
@@ -4792,14 +4402,12 @@ class PieceService {
4792
4402
  }
4793
4403
  const piece = square.piece;
4794
4404
  const duration = animate ? this.config.dropCenterTime : 0;
4795
- console.debug(`[PieceService] centerPiece: ${piece.id} on ${square.id}`);
4796
4405
  piece.translate(
4797
4406
  square,
4798
4407
  duration,
4799
4408
  this._getTransitionTimingFunction(),
4800
4409
  this.config.dropCenterAnimation,
4801
4410
  () => {
4802
- // After animation, reset all drag-related styles
4803
4411
  if (!piece.element) return;
4804
4412
  piece.element.style.position = '';
4805
4413
  piece.element.style.left = '';
@@ -4811,14 +4419,13 @@ class PieceService {
4811
4419
  }
4812
4420
 
4813
4421
  /**
4814
- * Gets the transition timing function for animations
4422
+ * Get the transition timing function for animations
4815
4423
  * @private
4816
4424
  * @returns {Function} Timing function
4817
4425
  */
4818
4426
  _getTransitionTimingFunction() {
4819
4427
  return (elapsed, duration, type = 'ease') => {
4820
4428
  const x = elapsed / duration;
4821
-
4822
4429
  switch (type) {
4823
4430
  case 'linear':
4824
4431
  return x;
@@ -4837,7 +4444,7 @@ class PieceService {
4837
4444
  }
4838
4445
 
4839
4446
  /**
4840
- * Cleans up resources
4447
+ * Clean up resources
4841
4448
  */
4842
4449
  destroy() {
4843
4450
  // Cleanup any cached pieces or references
@@ -6828,7 +6435,7 @@ class Chess {
6828
6435
  }
6829
6436
 
6830
6437
  /**
6831
- * Service for managing chess positions and FEN conversion
6438
+ * PositionService - Handles chess position management and FEN conversion
6832
6439
  * @module services/PositionService
6833
6440
  * @since 2.0.0
6834
6441
  */
@@ -6836,11 +6443,11 @@ class Chess {
6836
6443
 
6837
6444
  /**
6838
6445
  * Service responsible for position management and FEN operations
6839
- * @class
6446
+ * @class PositionService
6840
6447
  */
6841
6448
  class PositionService {
6842
6449
  /**
6843
- * Creates a new PositionService instance
6450
+ * Create a new PositionService instance
6844
6451
  * @param {ChessboardConfig} config - Board configuration
6845
6452
  */
6846
6453
  constructor(config) {
@@ -6849,7 +6456,7 @@ class PositionService {
6849
6456
  }
6850
6457
 
6851
6458
  /**
6852
- * Converts various position formats to FEN string
6459
+ * Convert various position formats to FEN string
6853
6460
  * @param {string|Object} position - Position in various formats
6854
6461
  * @returns {string} FEN string representation
6855
6462
  * @throws {ValidationError} When position format is invalid
@@ -6865,7 +6472,7 @@ class PositionService {
6865
6472
  }
6866
6473
 
6867
6474
  /**
6868
- * Converts string position to FEN
6475
+ * Convert string position to FEN
6869
6476
  * @private
6870
6477
  * @param {string} position - String position
6871
6478
  * @returns {string} FEN string
@@ -6874,35 +6481,29 @@ class PositionService {
6874
6481
  if (position === 'start') {
6875
6482
  return DEFAULT_STARTING_POSITION;
6876
6483
  }
6877
-
6878
6484
  if (this.validateFen(position)) {
6879
6485
  return position;
6880
6486
  }
6881
-
6882
6487
  if (STANDARD_POSITIONS[position]) {
6883
6488
  return STANDARD_POSITIONS[position];
6884
6489
  }
6885
-
6886
6490
  throw new ValidationError(ERROR_MESSAGES.invalid_position + position, 'position', position);
6887
6491
  }
6888
6492
 
6889
6493
  /**
6890
- * Converts object position to FEN
6494
+ * Convert object position to FEN
6891
6495
  * @private
6892
6496
  * @param {Object} position - Object with square->piece mapping
6893
6497
  * @returns {string} FEN string
6894
6498
  */
6895
6499
  _convertObjectPosition(position) {
6896
6500
  const parts = [];
6897
-
6898
6501
  for (let row = 0; row < 8; row++) {
6899
6502
  const rowParts = [];
6900
6503
  let empty = 0;
6901
-
6902
6504
  for (let col = 0; col < 8; col++) {
6903
6505
  const square = this._getSquareID(row, col);
6904
6506
  const piece = position[square];
6905
-
6906
6507
  if (piece) {
6907
6508
  if (empty > 0) {
6908
6509
  rowParts.push(empty);
@@ -6915,25 +6516,21 @@ class PositionService {
6915
6516
  empty++;
6916
6517
  }
6917
6518
  }
6918
-
6919
6519
  if (empty > 0) {
6920
6520
  rowParts.push(empty);
6921
6521
  }
6922
-
6923
6522
  parts.push(rowParts.join(''));
6924
6523
  }
6925
-
6926
6524
  return parts.join('/') + ' w KQkq - 0 1';
6927
6525
  }
6928
6526
 
6929
6527
  /**
6930
- * Sets up the game with the given position
6528
+ * Set up the game with the given position
6931
6529
  * @param {string|Object} position - Position to set
6932
6530
  * @param {Object} [options] - Additional options for game setup
6933
6531
  */
6934
6532
  setGame(position, options = {}) {
6935
6533
  const fen = this.convertFen(position);
6936
-
6937
6534
  if (this.game) {
6938
6535
  this.game.load(fen, options);
6939
6536
  } else {
@@ -6942,7 +6539,7 @@ class PositionService {
6942
6539
  }
6943
6540
 
6944
6541
  /**
6945
- * Gets the current game instance
6542
+ * Get the current game instance
6946
6543
  * @returns {Chess} Current chess.js game instance
6947
6544
  */
6948
6545
  getGame() {
@@ -6950,7 +6547,7 @@ class PositionService {
6950
6547
  }
6951
6548
 
6952
6549
  /**
6953
- * Validates a FEN string
6550
+ * Validate a FEN string
6954
6551
  * @param {string} fen - FEN string to validate
6955
6552
  * @returns {boolean} True if valid, false otherwise
6956
6553
  */
@@ -6959,7 +6556,7 @@ class PositionService {
6959
6556
  }
6960
6557
 
6961
6558
  /**
6962
- * Gets piece information for a specific square
6559
+ * Get piece information for a specific square
6963
6560
  * @param {string} squareId - Square identifier
6964
6561
  * @returns {string|null} Piece ID or null if no piece
6965
6562
  */
@@ -6970,7 +6567,7 @@ class PositionService {
6970
6567
  }
6971
6568
 
6972
6569
  /**
6973
- * Checks if a specific piece is on a specific square
6570
+ * Check if a specific piece is on a specific square
6974
6571
  * @param {string} piece - Piece ID to check
6975
6572
  * @param {string} square - Square to check
6976
6573
  * @returns {boolean} True if piece is on square
@@ -6980,7 +6577,7 @@ class PositionService {
6980
6577
  }
6981
6578
 
6982
6579
  /**
6983
- * Converts board coordinates to square ID
6580
+ * Convert board coordinates to square ID
6984
6581
  * @private
6985
6582
  * @param {number} row - Row index (0-7)
6986
6583
  * @param {number} col - Column index (0-7)
@@ -6989,7 +6586,6 @@ class PositionService {
6989
6586
  _getSquareID(row, col) {
6990
6587
  row = parseInt(row);
6991
6588
  col = parseInt(col);
6992
-
6993
6589
  if (this.config.orientation === 'w') {
6994
6590
  row = 8 - row;
6995
6591
  col = col + 1;
@@ -6997,13 +6593,12 @@ class PositionService {
6997
6593
  row = row + 1;
6998
6594
  col = 8 - col;
6999
6595
  }
7000
-
7001
6596
  const letter = BOARD_LETTERS[col - 1];
7002
6597
  return letter + row;
7003
6598
  }
7004
6599
 
7005
6600
  /**
7006
- * Changes the turn in a FEN string
6601
+ * Change the turn in a FEN string
7007
6602
  * @param {string} fen - Original FEN string
7008
6603
  * @param {string} color - New turn color ('w' or 'b')
7009
6604
  * @returns {string} Modified FEN string
@@ -7015,35 +6610,33 @@ class PositionService {
7015
6610
  }
7016
6611
 
7017
6612
  /**
7018
- * Gets the current position as an object
6613
+ * Get the current position as an object
7019
6614
  * @returns {Object} Position object with piece placements
7020
6615
  */
7021
6616
  getPosition() {
7022
6617
  const position = {};
7023
6618
  const game = this.getGame();
7024
-
7025
- // Convert chess.js board to position object
7026
- const squares = ['a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1',
7027
- 'a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2',
7028
- 'a3', 'b3', 'c3', 'd3', 'e3', 'f3', 'g3', 'h3',
7029
- 'a4', 'b4', 'c4', 'd4', 'e4', 'f4', 'g4', 'h4',
7030
- 'a5', 'b5', 'c5', 'd5', 'e5', 'f5', 'g5', 'h5',
7031
- 'a6', 'b6', 'c6', 'd6', 'e6', 'f6', 'g6', 'h6',
7032
- 'a7', 'b7', 'c7', 'd7', 'e7', 'f7', 'g7', 'h7',
7033
- 'a8', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8'];
7034
-
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
+ ];
7035
6629
  for (const square of squares) {
7036
6630
  const piece = game.get(square);
7037
6631
  if (piece) {
7038
6632
  position[square] = piece.type + piece.color;
7039
6633
  }
7040
6634
  }
7041
-
7042
6635
  return position;
7043
6636
  }
7044
6637
 
7045
6638
  /**
7046
- * Toggles the turn in a FEN string
6639
+ * Toggle the turn in a FEN string
7047
6640
  * @param {string} fen - Original FEN string
7048
6641
  * @returns {string} Modified FEN string
7049
6642
  */
@@ -7054,7 +6647,7 @@ class PositionService {
7054
6647
  }
7055
6648
 
7056
6649
  /**
7057
- * Cleans up resources
6650
+ * Clean up resources
7058
6651
  */
7059
6652
  destroy() {
7060
6653
  this.game = null;
@@ -7062,7 +6655,8 @@ class PositionService {
7062
6655
  }
7063
6656
 
7064
6657
  /**
7065
- * 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
7066
6660
  * @module core/Chessboard
7067
6661
  * @since 2.0.0
7068
6662
  */
@@ -7070,12 +6664,11 @@ class PositionService {
7070
6664
 
7071
6665
  /**
7072
6666
  * Main Chessboard class responsible for coordinating all services
7073
- * Implements the Facade pattern to provide a unified interface
7074
- * @class
6667
+ * @class Chessboard
7075
6668
  */
7076
6669
  let Chessboard$1 = class Chessboard {
7077
6670
  /**
7078
- * Creates a new Chessboard instance
6671
+ * Create a new Chessboard instance
7079
6672
  * @param {Object} config - Configuration object
7080
6673
  * @throws {ConfigurationError} If configuration is invalid
7081
6674
  */
@@ -8335,7 +7928,7 @@ let Chessboard$1 = class Chessboard {
8335
7928
  getPiece(square) {
8336
7929
  // Sempre leggi lo stato aggiornato dal boardService
8337
7930
  const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8338
- if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[getPiece] Parametro square non valido');
7931
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[getPiece] Parameter square is invalid');
8339
7932
  // Forza sync prima di leggere
8340
7933
  this._updateBoardPieces(false, false);
8341
7934
  const piece = squareObj.piece;
@@ -8369,9 +7962,9 @@ let Chessboard$1 = class Chessboard {
8369
7962
  }
8370
7963
  }
8371
7964
  const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8372
- if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[putPiece] Parametro square non valido');
7965
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[putPiece] Parameter square is invalid');
8373
7966
  const pieceObj = this.pieceService.convertPiece(pieceStr);
8374
- if (!pieceObj || typeof pieceObj !== 'object' || !('type' in pieceObj)) throw new Error('[putPiece] Parametro piece non valido');
7967
+ if (!pieceObj || typeof pieceObj !== 'object' || !('type' in pieceObj)) throw new Error('[putPiece] Parameter piece is invalid');
8375
7968
  // Aggiorna solo il motore chess.js
8376
7969
  const chessJsPiece = { type: pieceObj.type, color: pieceObj.color };
8377
7970
  const game = this.positionService.getGame();
@@ -8392,7 +7985,7 @@ let Chessboard$1 = class Chessboard {
8392
7985
  removePiece(square, opts = {}) {
8393
7986
  const animate = opts.animate !== undefined ? opts.animate : true;
8394
7987
  const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8395
- if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[removePiece] Parametro square non valido');
7988
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[removePiece] Parameter square is invalid');
8396
7989
  // Aggiorna solo il motore chess.js
8397
7990
  const game = this.positionService.getGame();
8398
7991
  game.remove(squareObj.id);
@@ -8468,7 +8061,7 @@ let Chessboard$1 = class Chessboard {
8468
8061
  highlight(square, opts = {}) {
8469
8062
  // API: accetta id, converte subito in oggetto
8470
8063
  const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8471
- if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[highlight] Parametro square non valido');
8064
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[highlight] Parameter square is invalid');
8472
8065
  if (this.boardService && this.boardService.highlightSquare) {
8473
8066
  this.boardService.highlightSquare(squareObj, opts);
8474
8067
  } else if (this.eventService && this.eventService.highlightSquare) {
@@ -8483,7 +8076,7 @@ let Chessboard$1 = class Chessboard {
8483
8076
  dehighlight(square, opts = {}) {
8484
8077
  // API: accetta id, converte subito in oggetto
8485
8078
  const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
8486
- if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[dehighlight] Parametro square non valido');
8079
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[dehighlight] Parameter square is invalid');
8487
8080
  if (this.boardService && this.boardService.dehighlightSquare) {
8488
8081
  this.boardService.dehighlightSquare(squareObj, opts);
8489
8082
  } else if (this.eventService && this.eventService.dehighlightSquare) {
@@ -8552,9 +8145,47 @@ let Chessboard$1 = class Chessboard {
8552
8145
 
8553
8146
  // --- LIFECYCLE ---
8554
8147
  /**
8555
- * Destroy the board and cleanup
8148
+ * Destroy the board and cleanup all resources
8556
8149
  */
8557
- destroy() { /* TODO: robust destroy logic */ }
8150
+ destroy() {
8151
+ // Remove event listeners
8152
+ if (this.eventService && typeof this.eventService.removeListeners === 'function') {
8153
+ this.eventService.removeListeners();
8154
+ }
8155
+ // Clear timeouts
8156
+ if (this._updateTimeout) {
8157
+ clearTimeout(this._updateTimeout);
8158
+ this._updateTimeout = null;
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
+ }
8188
+ }
8558
8189
  /**
8559
8190
  * Rebuild the board
8560
8191
  */