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