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