@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.
- package/dist/chessboard.cjs.js +437 -806
- package/dist/chessboard.css +8 -0
- package/dist/chessboard.esm.js +437 -806
- package/dist/chessboard.iife.js +437 -806
- package/dist/chessboard.umd.js +437 -806
- package/package.json +1 -1
- package/src/core/Chessboard.js +50 -12
- package/src/services/AnimationService.js +57 -82
- package/src/services/BoardService.js +40 -34
- package/src/services/CoordinateService.js +25 -84
- package/src/services/EventService.js +41 -104
- package/src/services/MoveService.js +63 -266
- package/src/services/PieceService.js +21 -64
- package/src/services/PositionService.js +26 -40
- package/src/services/ValidationService.js +54 -118
- package/src/services/index.js +1 -0
- package/src/styles/board.css +8 -0
- package/src/utils/listenerManager.js +62 -0
package/dist/chessboard.cjs.js
CHANGED
|
@@ -349,7 +349,7 @@ class PieceError extends ChessboardError {
|
|
|
349
349
|
}
|
|
350
350
|
|
|
351
351
|
/**
|
|
352
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
431
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
457
|
-
const
|
|
458
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
541
|
-
halfmove >= 0 && fullmove >= 1;
|
|
508
|
+
return !isNaN(halfmove) && !isNaN(fullmove) &&
|
|
509
|
+
halfmove >= 0 && fullmove >= 1;
|
|
542
510
|
}
|
|
543
511
|
|
|
544
512
|
/**
|
|
545
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
659
|
-
|
|
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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
-
|
|
827
|
-
|
|
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
|
-
|
|
848
|
-
|
|
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
|
-
*
|
|
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
|
|
2000
|
-
* @class
|
|
1935
|
+
* Service responsabile del coordinamento delle animazioni e delle transizioni
|
|
1936
|
+
* @class AnimationService
|
|
2001
1937
|
*/
|
|
2002
1938
|
class AnimationService {
|
|
2003
1939
|
/**
|
|
2004
|
-
*
|
|
2005
|
-
* @param {
|
|
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
|
-
*
|
|
2015
|
-
* @param {string} [type='ease'] -
|
|
2016
|
-
* @returns {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
|
-
|
|
2025
|
-
case 'ease':
|
|
2026
|
-
|
|
2027
|
-
case 'ease-in':
|
|
2028
|
-
|
|
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
|
-
*
|
|
2041
|
-
* @param {HTMLElement} element -
|
|
2042
|
-
* @param {Object} properties -
|
|
2043
|
-
* @param {number} duration -
|
|
2044
|
-
* @param {string} [easing='ease'] -
|
|
2045
|
-
* @param {Function} [callback] - Callback
|
|
2046
|
-
* @returns {number}
|
|
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
|
-
*
|
|
2088
|
-
* @param {number} animationId -
|
|
2014
|
+
* Annulla una animazione attiva
|
|
2015
|
+
* @param {number} animationId - ID dell'animazione
|
|
2089
2016
|
*/
|
|
2090
2017
|
cancel(animationId) {
|
|
2091
|
-
|
|
2092
|
-
this.activeAnimations.delete(animationId);
|
|
2093
|
-
}
|
|
2018
|
+
this.activeAnimations.delete(animationId);
|
|
2094
2019
|
}
|
|
2095
2020
|
|
|
2096
2021
|
/**
|
|
2097
|
-
*
|
|
2022
|
+
* Annulla tutte le animazioni attive
|
|
2098
2023
|
*/
|
|
2099
2024
|
cancelAll() {
|
|
2100
2025
|
this.activeAnimations.clear();
|
|
2101
2026
|
}
|
|
2102
2027
|
|
|
2103
2028
|
/**
|
|
2104
|
-
*
|
|
2029
|
+
* Ottiene il valore iniziale di una proprietà
|
|
2105
2030
|
* @private
|
|
2106
|
-
* @param {HTMLElement} element
|
|
2107
|
-
* @param {string} property
|
|
2108
|
-
* @returns {number}
|
|
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
|
-
|
|
2115
|
-
case '
|
|
2116
|
-
|
|
2117
|
-
|
|
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
|
-
*
|
|
2047
|
+
* Interpola tra due valori
|
|
2128
2048
|
* @private
|
|
2129
|
-
* @param {number} start
|
|
2130
|
-
* @param {number} end
|
|
2131
|
-
* @param {number} progress
|
|
2132
|
-
* @returns {number}
|
|
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
|
-
*
|
|
2059
|
+
* Applica il valore animato all'elemento
|
|
2140
2060
|
* @private
|
|
2141
|
-
* @param {HTMLElement} element
|
|
2142
|
-
* @param {string} property
|
|
2143
|
-
* @param {number} value
|
|
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
|
-
|
|
2150
|
-
|
|
2151
|
-
case '
|
|
2152
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2187
|
-
* @param {
|
|
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
|
-
*
|
|
2197
|
-
* @throws {DOMError}
|
|
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 =
|
|
2119
|
+
this.element.className = 'board';
|
|
2209
2120
|
}
|
|
2210
2121
|
|
|
2211
2122
|
/**
|
|
2212
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2159
|
+
* Resize the board to the specified size
|
|
2252
2160
|
* @param {number|string} value - Size in pixels or 'auto'
|
|
2253
|
-
* @throws {ValidationError}
|
|
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
|
-
*
|
|
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;
|
|
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
|
-
*
|
|
2287
|
-
* @param {string} squareId - Square identifier
|
|
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
|
|
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
|
|
2301
|
-
//
|
|
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
|
-
*
|
|
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
|
|
2310
|
-
//
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2359
|
-
* @param {
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2583
|
-
* @param {string} orientation - 'w'
|
|
2584
|
-
* @throws {ValidationError}
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
//
|
|
3106
|
-
this.
|
|
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
|
-
//
|
|
3117
|
-
this.
|
|
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
|
-
//
|
|
3207
|
-
square.element
|
|
3208
|
-
square.element
|
|
3209
|
-
square.element
|
|
3210
|
-
|
|
3211
|
-
square.element
|
|
3212
|
-
square.element
|
|
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
|
-
|
|
3289
|
-
const
|
|
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
|
-
|
|
3312
|
-
|
|
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 (
|
|
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
|
-
|
|
3379
|
-
|
|
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
|
-
//
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
3976
|
-
* @param {
|
|
3977
|
-
* @param {
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
4115
|
-
* @param {Move} move - Move to execute (
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
4138
|
-
* @param {Move} 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
|
|
4143
|
-
|
|
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
|
-
|
|
4165
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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}
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
4433
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
4617
|
-
* @param {Square} square - Target square
|
|
4618
|
-
* @param {Piece} piece - Piece to add
|
|
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
|
|
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
|
-
*
|
|
4654
|
-
* @param {Square} 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}
|
|
4292
|
+
* @returns {Piece} The removed piece
|
|
4658
4293
|
*/
|
|
4659
4294
|
removePieceFromSquare(square, fade = true, callback) {
|
|
4660
|
-
if (!square) throw new Error('removePieceFromSquare
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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);
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
7074
|
-
* @class
|
|
6667
|
+
* @class Chessboard
|
|
7075
6668
|
*/
|
|
7076
6669
|
let Chessboard$1 = class Chessboard {
|
|
7077
6670
|
/**
|
|
7078
|
-
*
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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() {
|
|
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
|
*/
|