@alepot55/chessboardjs 2.2.2 → 2.3.0
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 +1477 -382
- package/dist/chessboard.css +22 -7
- package/dist/chessboard.esm.js +1477 -382
- package/dist/chessboard.iife.js +1477 -382
- package/dist/chessboard.umd.js +1477 -382
- package/package.json +18 -3
- package/src/components/Piece.js +509 -26
- package/src/components/Square.js +3 -3
- package/src/core/Chessboard.js +625 -218
- package/src/core/ChessboardConfig.js +257 -8
- package/src/services/MoveService.js +37 -99
- package/src/services/PieceService.js +51 -24
- package/src/styles/board.css +22 -3
- package/.eslintrc.json +0 -227
- package/chessboard.bundle.js +0 -4072
- package/config/.babelrc +0 -4
- package/config/jest.config.js +0 -15
- package/config/rollup.config.js +0 -36
- package/jest.config.js +0 -2
- package/rollup.config.js +0 -2
- package/tests/unit/chessboard-config-animations.test.js +0 -106
- package/tests/unit/chessboard-robust.test.js +0 -163
- package/tests/unit/chessboard.test.js +0 -183
package/dist/chessboard.iife.js
CHANGED
|
@@ -1071,11 +1071,84 @@ var ChessboardLib = (function (exports) {
|
|
|
1071
1071
|
* @constant
|
|
1072
1072
|
* @type {Object}
|
|
1073
1073
|
*/
|
|
1074
|
+
/**
|
|
1075
|
+
* Flip mode options
|
|
1076
|
+
* @constant
|
|
1077
|
+
* @type {Object}
|
|
1078
|
+
*/
|
|
1079
|
+
const FLIP_MODES = Object.freeze({
|
|
1080
|
+
visual: 'visual', // CSS flexbox visual flip (instant, no piece animation)
|
|
1081
|
+
animate: 'animate', // Animate pieces to mirrored positions (smooth animation)
|
|
1082
|
+
none: 'none' // No visual change, only internal orientation
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Movement style options - how pieces travel from source to destination
|
|
1087
|
+
* @constant
|
|
1088
|
+
* @type {Object}
|
|
1089
|
+
*/
|
|
1090
|
+
const MOVE_STYLES = Object.freeze({
|
|
1091
|
+
slide: 'slide', // Linear movement (default) - piece slides in straight line
|
|
1092
|
+
arc: 'arc', // Arc trajectory - piece lifts up then comes down
|
|
1093
|
+
hop: 'hop', // Parabolic jump - like a knight hopping
|
|
1094
|
+
teleport: 'teleport', // Instant - no animation, piece appears at destination
|
|
1095
|
+
fade: 'fade' // Crossfade - fades out at source, fades in at destination
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* Capture animation options - what happens to captured pieces
|
|
1100
|
+
* @constant
|
|
1101
|
+
* @type {Object}
|
|
1102
|
+
*/
|
|
1103
|
+
const CAPTURE_STYLES = Object.freeze({
|
|
1104
|
+
fade: 'fade', // Fade out (default)
|
|
1105
|
+
shrink: 'shrink', // Shrink then fade
|
|
1106
|
+
instant: 'instant', // Disappears immediately
|
|
1107
|
+
explode: 'explode' // Scale up and fade (dramatic effect)
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* Appearance animation options - how new pieces appear on the board
|
|
1112
|
+
* @constant
|
|
1113
|
+
* @type {Object}
|
|
1114
|
+
*/
|
|
1115
|
+
const APPEARANCE_STYLES = Object.freeze({
|
|
1116
|
+
fade: 'fade', // Opacity + subtle scale (default)
|
|
1117
|
+
pulse: 'pulse', // Double pulse scale bounce
|
|
1118
|
+
pop: 'pop', // Scale 0→1.15→1 with spring easing
|
|
1119
|
+
drop: 'drop', // Drops from above with bounce
|
|
1120
|
+
instant: 'instant' // No animation
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Landing effect options - what happens when piece reaches destination
|
|
1125
|
+
* @constant
|
|
1126
|
+
* @type {Object}
|
|
1127
|
+
*/
|
|
1128
|
+
const LANDING_EFFECTS = Object.freeze({
|
|
1129
|
+
none: 'none', // No effect (default)
|
|
1130
|
+
bounce: 'bounce', // Slight bounce on landing
|
|
1131
|
+
pulse: 'pulse', // Quick scale pulse
|
|
1132
|
+
settle: 'settle' // Subtle settling animation
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Drag style options - how pieces behave during drag
|
|
1137
|
+
* @constant
|
|
1138
|
+
* @type {Object}
|
|
1139
|
+
*/
|
|
1140
|
+
const DRAG_STYLES = Object.freeze({
|
|
1141
|
+
smooth: 'smooth', // Piece follows cursor smoothly (default)
|
|
1142
|
+
snap: 'snap', // Piece snaps to cursor position
|
|
1143
|
+
elastic: 'elastic' // Slight elastic lag behind cursor
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1074
1146
|
const DEFAULT_CONFIG$1 = Object.freeze({
|
|
1075
1147
|
id: 'board',
|
|
1076
1148
|
position: 'start',
|
|
1077
1149
|
orientation: 'w',
|
|
1078
1150
|
mode: 'normal',
|
|
1151
|
+
flipMode: 'visual', // 'visual', 'animate', or 'none'
|
|
1079
1152
|
size: 'auto',
|
|
1080
1153
|
draggable: true,
|
|
1081
1154
|
hints: true,
|
|
@@ -1083,15 +1156,45 @@ var ChessboardLib = (function (exports) {
|
|
|
1083
1156
|
movableColors: 'both',
|
|
1084
1157
|
moveHighlight: true,
|
|
1085
1158
|
overHighlight: true,
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1159
|
+
|
|
1160
|
+
// Movement configuration
|
|
1161
|
+
moveStyle: 'slide', // 'slide', 'arc', 'hop', 'teleport', 'fade'
|
|
1162
|
+
moveEasing: 'ease', // CSS easing function
|
|
1163
|
+
moveTime: 'fast', // Duration: 'instant', 'veryFast', 'fast', 'normal', 'slow', 'verySlow' or ms
|
|
1164
|
+
moveArcHeight: 0.3, // Arc height as ratio of distance (for 'arc' and 'hop' styles)
|
|
1165
|
+
|
|
1166
|
+
// Capture configuration
|
|
1167
|
+
captureStyle: 'fade', // 'fade', 'shrink', 'instant', 'explode'
|
|
1168
|
+
captureTime: 'fast', // Duration for capture animation
|
|
1169
|
+
|
|
1170
|
+
// Appearance configuration
|
|
1171
|
+
appearanceStyle: 'fade', // 'fade', 'pulse', 'pop', 'drop', 'instant'
|
|
1172
|
+
appearanceTime: 'fast', // Duration for appearance animation
|
|
1173
|
+
|
|
1174
|
+
// Landing effect configuration
|
|
1175
|
+
landingEffect: 'none', // 'none', 'bounce', 'pulse', 'settle'
|
|
1176
|
+
landingDuration: 150, // Duration for landing effect in ms
|
|
1177
|
+
|
|
1178
|
+
// Drag configuration
|
|
1179
|
+
dragStyle: 'smooth', // 'smooth', 'snap', 'elastic'
|
|
1180
|
+
dragScale: 1.05, // Scale factor while dragging (1.0 = no scale)
|
|
1181
|
+
dragOpacity: 0.9, // Opacity while dragging
|
|
1182
|
+
|
|
1183
|
+
// Snapback configuration
|
|
1089
1184
|
snapbackTime: 'fast',
|
|
1090
|
-
|
|
1185
|
+
snapbackEasing: 'ease',
|
|
1186
|
+
|
|
1187
|
+
// Other timing
|
|
1188
|
+
dropOffBoard: 'snapback',
|
|
1091
1189
|
dropCenterTime: 'veryFast',
|
|
1092
1190
|
dropCenterAnimation: 'ease',
|
|
1093
1191
|
fadeTime: 'fast',
|
|
1094
1192
|
fadeAnimation: 'ease',
|
|
1193
|
+
|
|
1194
|
+
// Legacy compatibility
|
|
1195
|
+
moveAnimation: 'ease',
|
|
1196
|
+
snapbackAnimation: 'ease',
|
|
1197
|
+
|
|
1095
1198
|
ratio: 0.9,
|
|
1096
1199
|
piecesPath: '../assets/themes/default',
|
|
1097
1200
|
animationStyle: 'simultaneous',
|
|
@@ -1186,6 +1289,7 @@ var ChessboardLib = (function (exports) {
|
|
|
1186
1289
|
this.position = config.position;
|
|
1187
1290
|
this.orientation = config.orientation;
|
|
1188
1291
|
this.mode = config.mode;
|
|
1292
|
+
this.flipMode = this._validateFlipMode(config.flipMode);
|
|
1189
1293
|
this.dropOffBoard = config.dropOffBoard;
|
|
1190
1294
|
this.size = config.size;
|
|
1191
1295
|
this.movableColors = config.movableColors;
|
|
@@ -1200,12 +1304,27 @@ var ChessboardLib = (function (exports) {
|
|
|
1200
1304
|
this.onDrop = this._validateCallback(config.onDrop);
|
|
1201
1305
|
this.onSnapbackEnd = this._validateCallback(config.onSnapbackEnd);
|
|
1202
1306
|
|
|
1203
|
-
// Animation properties
|
|
1307
|
+
// Animation properties (legacy)
|
|
1204
1308
|
this.moveAnimation = this._setTransitionFunction(config.moveAnimation);
|
|
1205
1309
|
this.snapbackAnimation = this._setTransitionFunction(config.snapbackAnimation);
|
|
1206
1310
|
this.dropCenterAnimation = this._setTransitionFunction(config.dropCenterAnimation);
|
|
1207
1311
|
this.fadeAnimation = this._setTransitionFunction(config.fadeAnimation);
|
|
1208
1312
|
|
|
1313
|
+
// Movement configuration (new system)
|
|
1314
|
+
this.moveStyle = this._validateMoveStyle(config.moveStyle);
|
|
1315
|
+
this.moveEasing = this._setTransitionFunction(config.moveEasing);
|
|
1316
|
+
this.moveArcHeight = this._validateNumber(config.moveArcHeight, 0, 1, 'moveArcHeight');
|
|
1317
|
+
this.captureStyle = this._validateCaptureStyle(config.captureStyle);
|
|
1318
|
+
this.captureTime = this._setTime(config.captureTime);
|
|
1319
|
+
this.appearanceStyle = this._validateAppearanceStyle(config.appearanceStyle);
|
|
1320
|
+
this.appearanceTime = this._setTime(config.appearanceTime);
|
|
1321
|
+
this.landingEffect = this._validateLandingEffect(config.landingEffect);
|
|
1322
|
+
this.landingDuration = this._validateNumber(config.landingDuration, 0, 2000, 'landingDuration');
|
|
1323
|
+
this.dragStyle = this._validateDragStyle(config.dragStyle);
|
|
1324
|
+
this.dragScale = this._validateNumber(config.dragScale, 0.5, 2, 'dragScale');
|
|
1325
|
+
this.dragOpacity = this._validateNumber(config.dragOpacity, 0.1, 1, 'dragOpacity');
|
|
1326
|
+
this.snapbackEasing = this._setTransitionFunction(config.snapbackEasing);
|
|
1327
|
+
|
|
1209
1328
|
// Boolean properties
|
|
1210
1329
|
this.hints = this._setBoolean(config.hints);
|
|
1211
1330
|
this.clickable = this._setBoolean(config.clickable);
|
|
@@ -1309,6 +1428,111 @@ var ChessboardLib = (function (exports) {
|
|
|
1309
1428
|
return delay;
|
|
1310
1429
|
}
|
|
1311
1430
|
|
|
1431
|
+
/**
|
|
1432
|
+
* Validates flip mode
|
|
1433
|
+
* @private
|
|
1434
|
+
* @param {string} mode - Flip mode
|
|
1435
|
+
* @returns {string} Validated mode
|
|
1436
|
+
* @throws {ConfigurationError} If mode is invalid
|
|
1437
|
+
*/
|
|
1438
|
+
_validateFlipMode(mode) {
|
|
1439
|
+
if (!mode || !FLIP_MODES[mode]) {
|
|
1440
|
+
throw new ConfigurationError(
|
|
1441
|
+
`Invalid flip mode: ${mode}. Valid options: ${Object.keys(FLIP_MODES).join(', ')}`,
|
|
1442
|
+
'flipMode',
|
|
1443
|
+
mode
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
return mode;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
/**
|
|
1450
|
+
* Validates move style
|
|
1451
|
+
* @private
|
|
1452
|
+
* @param {string} style - Move style
|
|
1453
|
+
* @returns {string} Validated style
|
|
1454
|
+
*/
|
|
1455
|
+
_validateMoveStyle(style) {
|
|
1456
|
+
if (!style || !MOVE_STYLES[style]) {
|
|
1457
|
+
console.warn(`Invalid move style: ${style}. Using 'slide'. Valid: ${Object.keys(MOVE_STYLES).join(', ')}`);
|
|
1458
|
+
return 'slide';
|
|
1459
|
+
}
|
|
1460
|
+
return style;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
/**
|
|
1464
|
+
* Validates capture style
|
|
1465
|
+
* @private
|
|
1466
|
+
* @param {string} style - Capture style
|
|
1467
|
+
* @returns {string} Validated style
|
|
1468
|
+
*/
|
|
1469
|
+
_validateCaptureStyle(style) {
|
|
1470
|
+
if (!style || !CAPTURE_STYLES[style]) {
|
|
1471
|
+
console.warn(`Invalid capture style: ${style}. Using 'fade'. Valid: ${Object.keys(CAPTURE_STYLES).join(', ')}`);
|
|
1472
|
+
return 'fade';
|
|
1473
|
+
}
|
|
1474
|
+
return style;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
/**
|
|
1478
|
+
* Validates appearance style
|
|
1479
|
+
* @private
|
|
1480
|
+
* @param {string} style - Appearance style
|
|
1481
|
+
* @returns {string} Validated style
|
|
1482
|
+
*/
|
|
1483
|
+
_validateAppearanceStyle(style) {
|
|
1484
|
+
if (!style || !APPEARANCE_STYLES[style]) {
|
|
1485
|
+
console.warn(`Invalid appearance style: ${style}. Using 'fade'. Valid: ${Object.keys(APPEARANCE_STYLES).join(', ')}`);
|
|
1486
|
+
return 'fade';
|
|
1487
|
+
}
|
|
1488
|
+
return style;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
/**
|
|
1492
|
+
* Validates landing effect
|
|
1493
|
+
* @private
|
|
1494
|
+
* @param {string} effect - Landing effect
|
|
1495
|
+
* @returns {string} Validated effect
|
|
1496
|
+
*/
|
|
1497
|
+
_validateLandingEffect(effect) {
|
|
1498
|
+
if (!effect || !LANDING_EFFECTS[effect]) {
|
|
1499
|
+
console.warn(`Invalid landing effect: ${effect}. Using 'none'. Valid: ${Object.keys(LANDING_EFFECTS).join(', ')}`);
|
|
1500
|
+
return 'none';
|
|
1501
|
+
}
|
|
1502
|
+
return effect;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
/**
|
|
1506
|
+
* Validates drag style
|
|
1507
|
+
* @private
|
|
1508
|
+
* @param {string} style - Drag style
|
|
1509
|
+
* @returns {string} Validated style
|
|
1510
|
+
*/
|
|
1511
|
+
_validateDragStyle(style) {
|
|
1512
|
+
if (!style || !DRAG_STYLES[style]) {
|
|
1513
|
+
console.warn(`Invalid drag style: ${style}. Using 'smooth'. Valid: ${Object.keys(DRAG_STYLES).join(', ')}`);
|
|
1514
|
+
return 'smooth';
|
|
1515
|
+
}
|
|
1516
|
+
return style;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
/**
|
|
1520
|
+
* Validates a number within a range
|
|
1521
|
+
* @private
|
|
1522
|
+
* @param {number} value - Value to validate
|
|
1523
|
+
* @param {number} min - Minimum value
|
|
1524
|
+
* @param {number} max - Maximum value
|
|
1525
|
+
* @param {string} name - Property name for error message
|
|
1526
|
+
* @returns {number} Validated value
|
|
1527
|
+
*/
|
|
1528
|
+
_validateNumber(value, min, max, name) {
|
|
1529
|
+
if (typeof value !== 'number' || value < min || value > max) {
|
|
1530
|
+
console.warn(`Invalid ${name}: ${value}. Must be between ${min} and ${max}.`);
|
|
1531
|
+
return (min + max) / 2; // Return middle value as default
|
|
1532
|
+
}
|
|
1533
|
+
return value;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1312
1536
|
/**
|
|
1313
1537
|
* Sets a CSS custom property
|
|
1314
1538
|
* @private
|
|
@@ -1414,6 +1638,7 @@ var ChessboardLib = (function (exports) {
|
|
|
1414
1638
|
position: this.position,
|
|
1415
1639
|
orientation: this.orientation,
|
|
1416
1640
|
mode: this.mode,
|
|
1641
|
+
flipMode: this.flipMode,
|
|
1417
1642
|
size: this.size,
|
|
1418
1643
|
draggable: this.draggable,
|
|
1419
1644
|
hints: this.hints,
|
|
@@ -1421,11 +1646,26 @@ var ChessboardLib = (function (exports) {
|
|
|
1421
1646
|
movableColors: this.movableColors,
|
|
1422
1647
|
moveHighlight: this.moveHighlight,
|
|
1423
1648
|
overHighlight: this.overHighlight,
|
|
1424
|
-
|
|
1649
|
+
// Movement configuration
|
|
1650
|
+
moveStyle: this.moveStyle,
|
|
1651
|
+
moveEasing: this.moveEasing,
|
|
1425
1652
|
moveTime: this.moveTime,
|
|
1426
|
-
|
|
1653
|
+
moveArcHeight: this.moveArcHeight,
|
|
1654
|
+
captureStyle: this.captureStyle,
|
|
1655
|
+
captureTime: this.captureTime,
|
|
1656
|
+
appearanceStyle: this.appearanceStyle,
|
|
1657
|
+
appearanceTime: this.appearanceTime,
|
|
1658
|
+
landingEffect: this.landingEffect,
|
|
1659
|
+
landingDuration: this.landingDuration,
|
|
1660
|
+
dragStyle: this.dragStyle,
|
|
1661
|
+
dragScale: this.dragScale,
|
|
1662
|
+
dragOpacity: this.dragOpacity,
|
|
1427
1663
|
snapbackTime: this.snapbackTime,
|
|
1664
|
+
snapbackEasing: this.snapbackEasing,
|
|
1665
|
+
// Legacy
|
|
1666
|
+
moveAnimation: this.moveAnimation,
|
|
1428
1667
|
snapbackAnimation: this.snapbackAnimation,
|
|
1668
|
+
dropOffBoard: this.dropOffBoard,
|
|
1429
1669
|
dropCenterTime: this.dropCenterTime,
|
|
1430
1670
|
dropCenterAnimation: this.dropCenterAnimation,
|
|
1431
1671
|
fadeTime: this.fadeTime,
|
|
@@ -1630,6 +1870,121 @@ var ChessboardLib = (function (exports) {
|
|
|
1630
1870
|
}
|
|
1631
1871
|
}
|
|
1632
1872
|
|
|
1873
|
+
/**
|
|
1874
|
+
* Animate piece appearance with configurable style
|
|
1875
|
+
* @param {string} style - Appearance style: 'fade', 'pulse', 'pop', 'drop', 'instant'
|
|
1876
|
+
* @param {number} duration - Animation duration in ms
|
|
1877
|
+
* @param {Function} callback - Callback when complete
|
|
1878
|
+
*/
|
|
1879
|
+
appearAnimate(style, duration, callback) {
|
|
1880
|
+
if (!this.element) {
|
|
1881
|
+
if (callback) callback();
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
const element = this.element;
|
|
1886
|
+
const cleanup = () => {
|
|
1887
|
+
if (element) {
|
|
1888
|
+
element.style.opacity = '1';
|
|
1889
|
+
element.style.transform = '';
|
|
1890
|
+
}
|
|
1891
|
+
if (callback) callback();
|
|
1892
|
+
};
|
|
1893
|
+
|
|
1894
|
+
const smoothDecel = 'cubic-bezier(0.33, 1, 0.68, 1)';
|
|
1895
|
+
const springOvershoot = 'cubic-bezier(0.34, 1.56, 0.64, 1)';
|
|
1896
|
+
|
|
1897
|
+
switch (style) {
|
|
1898
|
+
case 'instant':
|
|
1899
|
+
element.style.opacity = '1';
|
|
1900
|
+
cleanup();
|
|
1901
|
+
break;
|
|
1902
|
+
|
|
1903
|
+
case 'fade':
|
|
1904
|
+
if (element.animate) {
|
|
1905
|
+
element.style.opacity = '0';
|
|
1906
|
+
const anim = element.animate([
|
|
1907
|
+
{ opacity: 0, transform: 'scale(0.95)' },
|
|
1908
|
+
{ opacity: 1, transform: 'scale(1)' }
|
|
1909
|
+
], { duration, easing: smoothDecel, fill: 'forwards' });
|
|
1910
|
+
anim.onfinish = () => {
|
|
1911
|
+
anim.cancel();
|
|
1912
|
+
cleanup();
|
|
1913
|
+
};
|
|
1914
|
+
} else {
|
|
1915
|
+
element.style.opacity = '0';
|
|
1916
|
+
element.style.transform = 'scale(0.95)';
|
|
1917
|
+
setTimeout(cleanup, duration);
|
|
1918
|
+
}
|
|
1919
|
+
break;
|
|
1920
|
+
|
|
1921
|
+
case 'pulse':
|
|
1922
|
+
if (element.animate) {
|
|
1923
|
+
element.style.opacity = '0';
|
|
1924
|
+
const anim = element.animate([
|
|
1925
|
+
{ opacity: 0, transform: 'scale(0.6)', offset: 0 },
|
|
1926
|
+
{ opacity: 1, transform: 'scale(1.12)', offset: 0.3 },
|
|
1927
|
+
{ opacity: 1, transform: 'scale(0.92)', offset: 0.55 },
|
|
1928
|
+
{ opacity: 1, transform: 'scale(1.06)', offset: 0.8 },
|
|
1929
|
+
{ opacity: 1, transform: 'scale(1)', offset: 1 }
|
|
1930
|
+
], { duration, easing: smoothDecel, fill: 'forwards' });
|
|
1931
|
+
anim.onfinish = () => {
|
|
1932
|
+
anim.cancel();
|
|
1933
|
+
cleanup();
|
|
1934
|
+
};
|
|
1935
|
+
} else {
|
|
1936
|
+
element.style.opacity = '0';
|
|
1937
|
+
element.style.transform = 'scale(0.6)';
|
|
1938
|
+
setTimeout(cleanup, duration);
|
|
1939
|
+
}
|
|
1940
|
+
break;
|
|
1941
|
+
|
|
1942
|
+
case 'pop':
|
|
1943
|
+
if (element.animate) {
|
|
1944
|
+
element.style.opacity = '0';
|
|
1945
|
+
const anim = element.animate([
|
|
1946
|
+
{ opacity: 0, transform: 'scale(0)', offset: 0 },
|
|
1947
|
+
{ opacity: 1, transform: 'scale(1.15)', offset: 0.6 },
|
|
1948
|
+
{ opacity: 1, transform: 'scale(1)', offset: 1 }
|
|
1949
|
+
], { duration, easing: springOvershoot, fill: 'forwards' });
|
|
1950
|
+
anim.onfinish = () => {
|
|
1951
|
+
anim.cancel();
|
|
1952
|
+
cleanup();
|
|
1953
|
+
};
|
|
1954
|
+
} else {
|
|
1955
|
+
element.style.opacity = '0';
|
|
1956
|
+
element.style.transform = 'scale(0)';
|
|
1957
|
+
setTimeout(cleanup, duration);
|
|
1958
|
+
}
|
|
1959
|
+
break;
|
|
1960
|
+
|
|
1961
|
+
case 'drop':
|
|
1962
|
+
if (element.animate) {
|
|
1963
|
+
element.style.opacity = '0';
|
|
1964
|
+
const anim = element.animate([
|
|
1965
|
+
{ opacity: 0, transform: 'translateY(-15px) scale(0.95)', offset: 0 },
|
|
1966
|
+
{ opacity: 1, transform: 'translateY(3px) scale(1.02)', offset: 0.5 },
|
|
1967
|
+
{ opacity: 1, transform: 'translateY(-1px) scale(1)', offset: 0.75 },
|
|
1968
|
+
{ opacity: 1, transform: 'translateY(0) scale(1)', offset: 1 }
|
|
1969
|
+
], { duration, easing: smoothDecel, fill: 'forwards' });
|
|
1970
|
+
anim.onfinish = () => {
|
|
1971
|
+
anim.cancel();
|
|
1972
|
+
cleanup();
|
|
1973
|
+
};
|
|
1974
|
+
} else {
|
|
1975
|
+
element.style.opacity = '0';
|
|
1976
|
+
element.style.transform = 'translateY(-15px) scale(0.95)';
|
|
1977
|
+
setTimeout(cleanup, duration);
|
|
1978
|
+
}
|
|
1979
|
+
break;
|
|
1980
|
+
|
|
1981
|
+
default:
|
|
1982
|
+
this.appearAnimate('fade', duration, callback);
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
/** @deprecated Use appearAnimate() instead */
|
|
1633
1988
|
fadeIn(duration, speed, transition_f, callback) {
|
|
1634
1989
|
let start = performance.now();
|
|
1635
1990
|
let opacity = 0;
|
|
@@ -1663,15 +2018,105 @@ var ChessboardLib = (function (exports) {
|
|
|
1663
2018
|
if (elapsed < duration) {
|
|
1664
2019
|
requestAnimationFrame(fade);
|
|
1665
2020
|
} else {
|
|
1666
|
-
if (!piece.element) {
|
|
2021
|
+
if (!piece.element) { if (callback) callback(); return; }
|
|
1667
2022
|
piece.element.style.opacity = 0;
|
|
1668
|
-
|
|
2023
|
+
// Remove element from DOM after fade completes
|
|
2024
|
+
if (piece.element.parentNode) {
|
|
2025
|
+
piece.element.parentNode.removeChild(piece.element);
|
|
2026
|
+
}
|
|
1669
2027
|
if (callback) callback();
|
|
1670
2028
|
}
|
|
1671
2029
|
};
|
|
1672
2030
|
fade();
|
|
1673
2031
|
}
|
|
1674
2032
|
|
|
2033
|
+
/**
|
|
2034
|
+
* Animate piece capture with configurable style
|
|
2035
|
+
* Uses fluid easing for smooth, connected animations
|
|
2036
|
+
* @param {string} style - Capture style: 'fade', 'shrink', 'instant', 'explode'
|
|
2037
|
+
* @param {number} duration - Animation duration in ms
|
|
2038
|
+
* @param {Function} callback - Callback when complete
|
|
2039
|
+
*/
|
|
2040
|
+
captureAnimate(style, duration, callback) {
|
|
2041
|
+
if (!this.element) {
|
|
2042
|
+
if (callback) callback();
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
const element = this.element;
|
|
2047
|
+
const cleanup = () => {
|
|
2048
|
+
if (element && element.parentNode) {
|
|
2049
|
+
element.parentNode.removeChild(element);
|
|
2050
|
+
}
|
|
2051
|
+
if (callback) callback();
|
|
2052
|
+
};
|
|
2053
|
+
|
|
2054
|
+
// Fluid easing functions
|
|
2055
|
+
const smoothDecel = 'cubic-bezier(0.33, 1, 0.68, 1)';
|
|
2056
|
+
const smoothAccel = 'cubic-bezier(0.32, 0, 0.67, 0)';
|
|
2057
|
+
|
|
2058
|
+
switch (style) {
|
|
2059
|
+
case 'instant':
|
|
2060
|
+
cleanup();
|
|
2061
|
+
break;
|
|
2062
|
+
|
|
2063
|
+
case 'fade':
|
|
2064
|
+
if (element.animate) {
|
|
2065
|
+
// Smooth fade with subtle scale down
|
|
2066
|
+
const anim = element.animate([
|
|
2067
|
+
{ opacity: 1, transform: 'scale(1)' },
|
|
2068
|
+
{ opacity: 0, transform: 'scale(0.9)' }
|
|
2069
|
+
], { duration, easing: smoothDecel, fill: 'forwards' });
|
|
2070
|
+
anim.onfinish = cleanup;
|
|
2071
|
+
} else {
|
|
2072
|
+
element.style.transition = `opacity ${duration}ms ease-out, transform ${duration}ms ease-out`;
|
|
2073
|
+
element.style.opacity = '0';
|
|
2074
|
+
element.style.transform = 'scale(0.9)';
|
|
2075
|
+
setTimeout(cleanup, duration);
|
|
2076
|
+
}
|
|
2077
|
+
break;
|
|
2078
|
+
|
|
2079
|
+
case 'shrink':
|
|
2080
|
+
if (element.animate) {
|
|
2081
|
+
// Smooth shrink with accelerating easing for "sucked in" feel
|
|
2082
|
+
const anim = element.animate([
|
|
2083
|
+
{ transform: 'scale(1)', opacity: 1, offset: 0 },
|
|
2084
|
+
{ transform: 'scale(0.7)', opacity: 0.6, offset: 0.6 },
|
|
2085
|
+
{ transform: 'scale(0)', opacity: 0, offset: 1 }
|
|
2086
|
+
], { duration, easing: smoothAccel, fill: 'forwards' });
|
|
2087
|
+
anim.onfinish = cleanup;
|
|
2088
|
+
} else {
|
|
2089
|
+
element.style.transition = `transform ${duration}ms ease-in, opacity ${duration}ms ease-in`;
|
|
2090
|
+
element.style.transform = 'scale(0)';
|
|
2091
|
+
element.style.opacity = '0';
|
|
2092
|
+
setTimeout(cleanup, duration);
|
|
2093
|
+
}
|
|
2094
|
+
break;
|
|
2095
|
+
|
|
2096
|
+
case 'explode':
|
|
2097
|
+
if (element.animate) {
|
|
2098
|
+
// Subtle expand with smooth deceleration - less dramatic
|
|
2099
|
+
const anim = element.animate([
|
|
2100
|
+
{ transform: 'scale(1)', opacity: 1, offset: 0 },
|
|
2101
|
+
{ transform: 'scale(1.15)', opacity: 0.5, offset: 0.5 },
|
|
2102
|
+
{ transform: 'scale(1.25)', opacity: 0, offset: 1 }
|
|
2103
|
+
], { duration, easing: smoothDecel, fill: 'forwards' });
|
|
2104
|
+
anim.onfinish = cleanup;
|
|
2105
|
+
} else {
|
|
2106
|
+
element.style.transition = `transform ${duration}ms ease-out, opacity ${duration}ms ease-out`;
|
|
2107
|
+
element.style.transform = 'scale(1.25)';
|
|
2108
|
+
element.style.opacity = '0';
|
|
2109
|
+
setTimeout(cleanup, duration);
|
|
2110
|
+
}
|
|
2111
|
+
break;
|
|
2112
|
+
|
|
2113
|
+
default:
|
|
2114
|
+
// Default to fade
|
|
2115
|
+
this.captureAnimate('fade', duration, callback);
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
|
|
1675
2120
|
setDrag(f) {
|
|
1676
2121
|
if (!this.element) { console.debug(`[Piece] setDrag: ${this.id} - element is null`); return; }
|
|
1677
2122
|
|
|
@@ -1711,41 +2156,319 @@ var ChessboardLib = (function (exports) {
|
|
|
1711
2156
|
}
|
|
1712
2157
|
}
|
|
1713
2158
|
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
2159
|
+
/**
|
|
2160
|
+
* Translate piece to target position with configurable movement style
|
|
2161
|
+
* Uses Chessground-style cubic easing for fluid, natural movement
|
|
2162
|
+
* @param {HTMLElement} to - Target element
|
|
2163
|
+
* @param {number} duration - Animation duration in ms
|
|
2164
|
+
* @param {Function} transition_f - Transition function (unused, for compatibility)
|
|
2165
|
+
* @param {number} speed - Speed factor (unused, for compatibility)
|
|
2166
|
+
* @param {Function} callback - Callback when complete
|
|
2167
|
+
* @param {Object} options - Movement options
|
|
2168
|
+
* @param {string} options.style - Movement style: 'slide', 'arc', 'hop', 'teleport', 'fade'
|
|
2169
|
+
* @param {string} options.easing - CSS easing function
|
|
2170
|
+
* @param {number} options.arcHeight - Arc height ratio (for arc/hop styles)
|
|
2171
|
+
* @param {string} options.landingEffect - Landing effect: 'none', 'bounce', 'pulse', 'settle'
|
|
2172
|
+
* @param {number} options.landingDuration - Landing effect duration in ms
|
|
2173
|
+
*/
|
|
2174
|
+
translate(to, duration, transition_f, speed, callback = null, options = {}) {
|
|
2175
|
+
if (!this.element) {
|
|
2176
|
+
console.debug(`[Piece] translate: ${this.id} - element is null`);
|
|
2177
|
+
if (callback) callback();
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
1724
2180
|
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
2181
|
+
const style = options.style || 'slide';
|
|
2182
|
+
const arcHeight = options.arcHeight || 0.3;
|
|
2183
|
+
const landingEffect = options.landingEffect || 'none';
|
|
2184
|
+
const landingDuration = options.landingDuration || 150;
|
|
2185
|
+
|
|
2186
|
+
// Map easing names to Chessground-style fluid cubic-bezier curves
|
|
2187
|
+
// Default: smooth deceleration like Lichess/Chessground
|
|
2188
|
+
const easingMap = {
|
|
2189
|
+
'ease': 'cubic-bezier(0.33, 1, 0.68, 1)', // Chessground default - smooth decel
|
|
2190
|
+
'linear': 'linear',
|
|
2191
|
+
'ease-in': 'cubic-bezier(0.32, 0, 0.67, 0)', // Smooth acceleration
|
|
2192
|
+
'ease-out': 'cubic-bezier(0.33, 1, 0.68, 1)', // Smooth deceleration (same as default)
|
|
2193
|
+
'ease-in-out': 'cubic-bezier(0.65, 0, 0.35, 1)' // Smooth both ways
|
|
2194
|
+
};
|
|
2195
|
+
const easing = easingMap[options.easing] || easingMap['ease'];
|
|
2196
|
+
|
|
2197
|
+
// Handle teleport (instant)
|
|
2198
|
+
if (style === 'teleport' || duration === 0) {
|
|
2199
|
+
if (callback) callback();
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
// Handle fade style
|
|
2204
|
+
if (style === 'fade') {
|
|
2205
|
+
this._translateFade(to, duration, easing, landingEffect, landingDuration, callback);
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
// Calculate movement vectors
|
|
2210
|
+
const sourceRect = this.element.getBoundingClientRect();
|
|
2211
|
+
const targetRect = to.getBoundingClientRect();
|
|
2212
|
+
const dx = (targetRect.left + targetRect.width / 2) - (sourceRect.left + sourceRect.width / 2);
|
|
2213
|
+
const dy = (targetRect.top + targetRect.height / 2) - (sourceRect.top + sourceRect.height / 2);
|
|
2214
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
2215
|
+
|
|
2216
|
+
// Build keyframes based on style
|
|
2217
|
+
let keyframes;
|
|
2218
|
+
let animationEasing = easing;
|
|
2219
|
+
|
|
2220
|
+
switch (style) {
|
|
2221
|
+
case 'arc':
|
|
2222
|
+
keyframes = this._buildArcKeyframes(dx, dy, distance, arcHeight);
|
|
2223
|
+
// Arc uses linear easing because the curve is in the keyframes
|
|
2224
|
+
animationEasing = 'linear';
|
|
2225
|
+
break;
|
|
2226
|
+
case 'hop':
|
|
2227
|
+
keyframes = this._buildHopKeyframes(dx, dy, distance, arcHeight);
|
|
2228
|
+
// Hop uses ease-out for natural landing feel
|
|
2229
|
+
animationEasing = 'cubic-bezier(0.33, 1, 0.68, 1)';
|
|
2230
|
+
break;
|
|
2231
|
+
case 'slide':
|
|
2232
|
+
default:
|
|
2233
|
+
keyframes = [
|
|
2234
|
+
{ transform: 'translate(0, 0)' },
|
|
2235
|
+
{ transform: `translate(${dx}px, ${dy}px)` }
|
|
2236
|
+
];
|
|
2237
|
+
}
|
|
1729
2238
|
|
|
2239
|
+
// Animate
|
|
1730
2240
|
if (this.element.animate) {
|
|
1731
|
-
|
|
2241
|
+
const animation = this.element.animate(keyframes, {
|
|
1732
2242
|
duration: duration,
|
|
1733
|
-
easing:
|
|
1734
|
-
fill: '
|
|
2243
|
+
easing: animationEasing,
|
|
2244
|
+
fill: 'forwards'
|
|
1735
2245
|
});
|
|
1736
2246
|
|
|
1737
2247
|
animation.onfinish = () => {
|
|
1738
|
-
if (!this.element) {
|
|
1739
|
-
|
|
2248
|
+
if (!this.element) {
|
|
2249
|
+
if (callback) callback();
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
// Cancel animation and move piece in DOM first
|
|
2254
|
+
animation.cancel();
|
|
1740
2255
|
if (this.element) this.element.style = '';
|
|
1741
|
-
|
|
2256
|
+
|
|
2257
|
+
// Callback moves piece to new square in DOM
|
|
2258
|
+
if (callback) callback();
|
|
2259
|
+
|
|
2260
|
+
// Apply landing effect AFTER piece is in new position
|
|
2261
|
+
if (landingEffect !== 'none') {
|
|
2262
|
+
// Small delay to ensure DOM is updated
|
|
2263
|
+
requestAnimationFrame(() => {
|
|
2264
|
+
this._applyLandingEffect(landingEffect, landingDuration);
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
1742
2267
|
};
|
|
1743
2268
|
} else {
|
|
1744
|
-
|
|
2269
|
+
// Fallback for browsers without Web Animations API
|
|
2270
|
+
this.element.style.transition = `transform ${duration}ms ${animationEasing}`;
|
|
1745
2271
|
this.element.style.transform = `translate(${dx}px, ${dy}px)`;
|
|
2272
|
+
setTimeout(() => {
|
|
2273
|
+
if (!this.element) {
|
|
2274
|
+
if (callback) callback();
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2277
|
+
if (this.element) this.element.style = '';
|
|
2278
|
+
if (callback) callback();
|
|
2279
|
+
|
|
2280
|
+
// Apply landing effect after DOM update
|
|
2281
|
+
if (landingEffect !== 'none') {
|
|
2282
|
+
requestAnimationFrame(() => {
|
|
2283
|
+
this._applyLandingEffect(landingEffect, landingDuration);
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
}, duration);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
/**
|
|
2291
|
+
* Build arc-shaped keyframes (smooth parabolic curve)
|
|
2292
|
+
* Uses many keyframes for fluid motion without relying on easing
|
|
2293
|
+
* @private
|
|
2294
|
+
*/
|
|
2295
|
+
_buildArcKeyframes(dx, dy, distance, arcHeight) {
|
|
2296
|
+
const lift = distance * arcHeight;
|
|
2297
|
+
const steps = 10;
|
|
2298
|
+
const keyframes = [];
|
|
2299
|
+
|
|
2300
|
+
for (let i = 0; i <= steps; i++) {
|
|
2301
|
+
const t = i / steps;
|
|
2302
|
+
// Smooth easing for horizontal movement (Chessground-style cubic)
|
|
2303
|
+
const easedT = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
2304
|
+
// Parabolic arc for vertical lift: peaks at t=0.5
|
|
2305
|
+
const arcT = 4 * t * (1 - t); // Parabola: 0 at t=0, 1 at t=0.5, 0 at t=1
|
|
2306
|
+
|
|
2307
|
+
const x = dx * easedT;
|
|
2308
|
+
const y = dy * easedT - lift * arcT;
|
|
2309
|
+
|
|
2310
|
+
keyframes.push({
|
|
2311
|
+
transform: `translate(${x}px, ${y}px)`,
|
|
2312
|
+
offset: t
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
return keyframes;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
/**
|
|
2319
|
+
* Build hop-shaped keyframes (knight-like jump with subtle scale)
|
|
2320
|
+
* More aggressive vertical movement, subtle scale for emphasis
|
|
2321
|
+
* @private
|
|
2322
|
+
*/
|
|
2323
|
+
_buildHopKeyframes(dx, dy, distance, arcHeight) {
|
|
2324
|
+
const lift = distance * arcHeight * 1.2;
|
|
2325
|
+
const steps = 12;
|
|
2326
|
+
const keyframes = [];
|
|
2327
|
+
|
|
2328
|
+
for (let i = 0; i <= steps; i++) {
|
|
2329
|
+
const t = i / steps;
|
|
2330
|
+
// Chessground-style cubic easing for smooth acceleration/deceleration
|
|
2331
|
+
const easedT = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
2332
|
+
// Sharp parabolic hop - peaks earlier for snappier feel
|
|
2333
|
+
const hopT = Math.sin(t * Math.PI); // Sine for smooth hop curve
|
|
2334
|
+
// Subtle scale: peaks at 1.03 at the top of the hop
|
|
2335
|
+
const scale = 1 + 0.03 * hopT;
|
|
2336
|
+
|
|
2337
|
+
const x = dx * easedT;
|
|
2338
|
+
const y = dy * easedT - lift * hopT;
|
|
2339
|
+
|
|
2340
|
+
keyframes.push({
|
|
2341
|
+
transform: `translate(${x}px, ${y}px) scale(${scale})`,
|
|
2342
|
+
offset: t
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
return keyframes;
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
/**
|
|
2349
|
+
* Translate using fade effect (smooth crossfade with scale)
|
|
2350
|
+
* @private
|
|
2351
|
+
*/
|
|
2352
|
+
_translateFade(to, duration, easing, landingEffect, landingDuration, callback) {
|
|
2353
|
+
const halfDuration = duration / 2;
|
|
2354
|
+
// Use smooth deceleration for fade
|
|
2355
|
+
const fadeEasing = 'cubic-bezier(0.33, 1, 0.68, 1)';
|
|
2356
|
+
|
|
2357
|
+
// Fade out with subtle scale down
|
|
2358
|
+
if (this.element.animate) {
|
|
2359
|
+
const fadeOut = this.element.animate([
|
|
2360
|
+
{ opacity: 1, transform: 'scale(1)' },
|
|
2361
|
+
{ opacity: 0, transform: 'scale(0.95)' }
|
|
2362
|
+
], { duration: halfDuration, easing: fadeEasing, fill: 'forwards' });
|
|
2363
|
+
|
|
2364
|
+
fadeOut.onfinish = () => {
|
|
2365
|
+
if (!this.element) {
|
|
2366
|
+
if (callback) callback();
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
// Move instantly (hidden)
|
|
2370
|
+
fadeOut.cancel();
|
|
2371
|
+
this.element.style.opacity = '0';
|
|
2372
|
+
this.element.style.transform = 'scale(0.95)';
|
|
2373
|
+
|
|
2374
|
+
// Let parent move the piece in DOM, then fade in
|
|
2375
|
+
if (callback) callback();
|
|
2376
|
+
|
|
2377
|
+
// Fade in at new position with subtle scale up
|
|
2378
|
+
requestAnimationFrame(() => {
|
|
2379
|
+
if (!this.element) return;
|
|
2380
|
+
const fadeIn = this.element.animate([
|
|
2381
|
+
{ opacity: 0, transform: 'scale(0.95)' },
|
|
2382
|
+
{ opacity: 1, transform: 'scale(1)' }
|
|
2383
|
+
], { duration: halfDuration, easing: fadeEasing, fill: 'forwards' });
|
|
2384
|
+
|
|
2385
|
+
fadeIn.onfinish = () => {
|
|
2386
|
+
if (this.element) {
|
|
2387
|
+
fadeIn.cancel();
|
|
2388
|
+
this.element.style.opacity = '';
|
|
2389
|
+
this.element.style.transform = '';
|
|
2390
|
+
this._applyLandingEffect(landingEffect, landingDuration);
|
|
2391
|
+
}
|
|
2392
|
+
};
|
|
2393
|
+
});
|
|
2394
|
+
};
|
|
2395
|
+
} else {
|
|
2396
|
+
// Fallback
|
|
2397
|
+
this.element.style.transition = `opacity ${halfDuration}ms ease, transform ${halfDuration}ms ease`;
|
|
2398
|
+
this.element.style.opacity = '0';
|
|
2399
|
+
this.element.style.transform = 'scale(0.95)';
|
|
2400
|
+
setTimeout(() => {
|
|
2401
|
+
if (callback) callback();
|
|
2402
|
+
if (this.element) {
|
|
2403
|
+
this.element.style.opacity = '1';
|
|
2404
|
+
this.element.style.transform = 'scale(1)';
|
|
2405
|
+
setTimeout(() => {
|
|
2406
|
+
if (this.element) this.element.style = '';
|
|
2407
|
+
}, halfDuration);
|
|
2408
|
+
}
|
|
2409
|
+
}, halfDuration);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
/**
|
|
2414
|
+
* Apply landing effect after movement completes
|
|
2415
|
+
* Uses spring-like overshoot easing for natural, connected feel
|
|
2416
|
+
* @private
|
|
2417
|
+
*/
|
|
2418
|
+
_applyLandingEffect(effect, duration, callback) {
|
|
2419
|
+
if (!this.element || effect === 'none') {
|
|
2420
|
+
if (callback) callback();
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
let keyframes;
|
|
2425
|
+
// Overshoot easing for spring-like natural feel
|
|
2426
|
+
let effectEasing = 'cubic-bezier(0.34, 1.56, 0.64, 1)';
|
|
2427
|
+
|
|
2428
|
+
switch (effect) {
|
|
2429
|
+
case 'bounce':
|
|
2430
|
+
// Subtle bounce using spring easing - single smooth bounce
|
|
2431
|
+
keyframes = [
|
|
2432
|
+
{ transform: 'translateY(0)', offset: 0 },
|
|
2433
|
+
{ transform: 'translateY(-5px)', offset: 0.4 },
|
|
2434
|
+
{ transform: 'translateY(0)', offset: 1 }
|
|
2435
|
+
];
|
|
2436
|
+
// Use ease-out for natural deceleration
|
|
2437
|
+
effectEasing = 'cubic-bezier(0.33, 1, 0.68, 1)';
|
|
2438
|
+
break;
|
|
2439
|
+
case 'pulse':
|
|
2440
|
+
// Subtle scale pulse - less aggressive, more refined
|
|
2441
|
+
keyframes = [
|
|
2442
|
+
{ transform: 'scale(1)', offset: 0 },
|
|
2443
|
+
{ transform: 'scale(1.08)', offset: 0.5 },
|
|
2444
|
+
{ transform: 'scale(1)', offset: 1 }
|
|
2445
|
+
];
|
|
2446
|
+
break;
|
|
2447
|
+
case 'settle':
|
|
2448
|
+
// Minimal settle - piece "clicks" into place
|
|
2449
|
+
keyframes = [
|
|
2450
|
+
{ transform: 'scale(1.02)', offset: 0 },
|
|
2451
|
+
{ transform: 'scale(1)', offset: 1 }
|
|
2452
|
+
];
|
|
2453
|
+
effectEasing = 'cubic-bezier(0.33, 1, 0.68, 1)';
|
|
2454
|
+
break;
|
|
2455
|
+
default:
|
|
2456
|
+
if (callback) callback();
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
if (this.element.animate) {
|
|
2461
|
+
const animation = this.element.animate(keyframes, {
|
|
2462
|
+
duration: duration,
|
|
2463
|
+
easing: effectEasing,
|
|
2464
|
+
fill: 'forwards'
|
|
2465
|
+
});
|
|
2466
|
+
animation.onfinish = () => {
|
|
2467
|
+
if (this.element) animation.cancel();
|
|
2468
|
+
if (callback) callback();
|
|
2469
|
+
};
|
|
2470
|
+
} else {
|
|
1746
2471
|
if (callback) callback();
|
|
1747
|
-
if (this.element) this.element.style = '';
|
|
1748
|
-
console.debug(`[Piece] translate complete (no animate): ${this.id}`);
|
|
1749
2472
|
}
|
|
1750
2473
|
}
|
|
1751
2474
|
|
|
@@ -1866,9 +2589,9 @@ var ChessboardLib = (function (exports) {
|
|
|
1866
2589
|
}
|
|
1867
2590
|
|
|
1868
2591
|
putPiece(piece) {
|
|
1869
|
-
// If there's already a piece,
|
|
2592
|
+
// If there's already a piece, destroy it to avoid orphaned DOM elements
|
|
1870
2593
|
if (this.piece) {
|
|
1871
|
-
this.removePiece(
|
|
2594
|
+
this.removePiece(false);
|
|
1872
2595
|
}
|
|
1873
2596
|
this.piece = piece;
|
|
1874
2597
|
if (piece && piece.element) {
|
|
@@ -1980,7 +2703,7 @@ var ChessboardLib = (function (exports) {
|
|
|
1980
2703
|
}
|
|
1981
2704
|
|
|
1982
2705
|
getColor() {
|
|
1983
|
-
return this.piece.getColor();
|
|
2706
|
+
return this.piece ? this.piece.getColor() : null;
|
|
1984
2707
|
}
|
|
1985
2708
|
|
|
1986
2709
|
check() {
|
|
@@ -4638,25 +5361,11 @@ var ChessboardLib = (function (exports) {
|
|
|
4638
5361
|
to: move.to.id
|
|
4639
5362
|
};
|
|
4640
5363
|
|
|
4641
|
-
console.log('executeMove - move.promotion:', move.promotion);
|
|
4642
|
-
console.log('executeMove - move.hasPromotion():', move.hasPromotion());
|
|
4643
|
-
|
|
4644
5364
|
if (move.hasPromotion()) {
|
|
4645
5365
|
moveOptions.promotion = move.promotion;
|
|
4646
5366
|
}
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
const result = game.move(moveOptions);
|
|
4651
|
-
console.log('executeMove - result:', result);
|
|
4652
|
-
|
|
4653
|
-
// Check what's actually on the board after the move
|
|
4654
|
-
if (result) {
|
|
4655
|
-
const pieceOnDestination = game.get(move.to.id);
|
|
4656
|
-
console.log('executeMove - piece on destination after move:', pieceOnDestination);
|
|
4657
|
-
}
|
|
4658
|
-
|
|
4659
|
-
return result;
|
|
5367
|
+
|
|
5368
|
+
return game.move(moveOptions);
|
|
4660
5369
|
}
|
|
4661
5370
|
|
|
4662
5371
|
/**
|
|
@@ -4666,27 +5375,19 @@ var ChessboardLib = (function (exports) {
|
|
|
4666
5375
|
*/
|
|
4667
5376
|
requiresPromotion(move) {
|
|
4668
5377
|
const moveKey = `${move.from.id}->${move.to.id}`;
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
// Get detailed stack trace
|
|
4672
|
-
const stack = new Error().stack.split('\n');
|
|
4673
|
-
console.log('Call stack:', stack[1]);
|
|
4674
|
-
console.log('Caller:', stack[2]);
|
|
4675
|
-
console.log('Caller 2:', stack[3]);
|
|
4676
|
-
console.log('Caller 3:', stack[4]);
|
|
4677
|
-
|
|
4678
|
-
// Track recently processed moves to prevent processing the same move multiple times
|
|
5378
|
+
|
|
5379
|
+
// Track recently processed moves - return cached result for duplicates
|
|
4679
5380
|
const now = Date.now();
|
|
4680
|
-
if (this._recentMoves.has(moveKey)
|
|
4681
|
-
|
|
4682
|
-
|
|
5381
|
+
if (this._recentMoves.has(moveKey) &&
|
|
5382
|
+
this._lastPromotionCheck === moveKey &&
|
|
5383
|
+
this._lastPromotionResult !== null) {
|
|
5384
|
+
return this._lastPromotionResult;
|
|
4683
5385
|
}
|
|
4684
|
-
|
|
5386
|
+
|
|
4685
5387
|
// Debounce identical promotion checks within 100ms
|
|
4686
|
-
if (this._lastPromotionCheck === moveKey &&
|
|
5388
|
+
if (this._lastPromotionCheck === moveKey &&
|
|
4687
5389
|
this._lastPromotionResult !== null &&
|
|
4688
5390
|
now - this._lastPromotionTime < 100) {
|
|
4689
|
-
console.log('Using cached promotion result:', this._lastPromotionResult);
|
|
4690
5391
|
return this._lastPromotionResult;
|
|
4691
5392
|
}
|
|
4692
5393
|
|
|
@@ -4727,53 +5428,41 @@ var ChessboardLib = (function (exports) {
|
|
|
4727
5428
|
*/
|
|
4728
5429
|
_doRequiresPromotion(move) {
|
|
4729
5430
|
if (!this.config.onlyLegalMoves) {
|
|
4730
|
-
console.log('Not in legal moves mode, no promotion required');
|
|
4731
5431
|
return false;
|
|
4732
5432
|
}
|
|
4733
|
-
|
|
5433
|
+
|
|
4734
5434
|
const game = this.positionService.getGame();
|
|
4735
5435
|
if (!game) {
|
|
4736
|
-
console.log('No game instance available');
|
|
4737
5436
|
return false;
|
|
4738
5437
|
}
|
|
4739
|
-
|
|
5438
|
+
|
|
4740
5439
|
const piece = game.get(move.from.id);
|
|
4741
5440
|
if (!piece || piece.type !== 'p') {
|
|
4742
|
-
console.log('Not a pawn move, no promotion required');
|
|
4743
5441
|
return false;
|
|
4744
5442
|
}
|
|
4745
|
-
|
|
5443
|
+
|
|
4746
5444
|
const targetRank = move.to.row;
|
|
4747
5445
|
if (targetRank !== 1 && targetRank !== 8) {
|
|
4748
|
-
console.log('Not reaching promotion rank, no promotion required');
|
|
4749
5446
|
return false;
|
|
4750
5447
|
}
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
// Additional validation: check if the pawn can actually reach this square
|
|
5448
|
+
|
|
5449
|
+
// Validate pawn move
|
|
4755
5450
|
if (!this._isPawnMoveValid(move.from, move.to, piece.color)) {
|
|
4756
|
-
console.log('Pawn move not valid, no promotion required');
|
|
4757
5451
|
return false;
|
|
4758
5452
|
}
|
|
4759
|
-
|
|
4760
|
-
//
|
|
4761
|
-
// Check if it's a legal move by using the game's moves() method
|
|
5453
|
+
|
|
5454
|
+
// Check if it's a legal move
|
|
4762
5455
|
const legalMoves = game.moves({ square: move.from.id, verbose: true });
|
|
4763
5456
|
const matchingMove = legalMoves.find(m => m.to === move.to.id);
|
|
4764
|
-
|
|
5457
|
+
|
|
4765
5458
|
if (!matchingMove) {
|
|
4766
|
-
console.log('Move not in legal moves list, no promotion required');
|
|
4767
5459
|
return false;
|
|
4768
5460
|
}
|
|
4769
|
-
|
|
4770
|
-
//
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
console.log('Promotion required:', requiresPromotion);
|
|
4776
|
-
return requiresPromotion;
|
|
5461
|
+
|
|
5462
|
+
// Promotion is required if move has promotion flag or reaches promotion rank
|
|
5463
|
+
return matchingMove.flags.includes('p') ||
|
|
5464
|
+
(piece.color === 'w' && targetRank === 8) ||
|
|
5465
|
+
(piece.color === 'b' && targetRank === 1);
|
|
4777
5466
|
}
|
|
4778
5467
|
|
|
4779
5468
|
/**
|
|
@@ -4789,43 +5478,34 @@ var ChessboardLib = (function (exports) {
|
|
|
4789
5478
|
const toRank = to.row;
|
|
4790
5479
|
const fromFile = from.col;
|
|
4791
5480
|
const toFile = to.col;
|
|
4792
|
-
|
|
4793
|
-
console.log(`Validating pawn move: ${from.id} -> ${to.id} (${color})`);
|
|
4794
|
-
console.log(`Ranks: ${fromRank} -> ${toRank}, Files: ${fromFile} -> ${toFile}`);
|
|
4795
|
-
|
|
4796
|
-
// Direction of pawn movement
|
|
5481
|
+
|
|
4797
5482
|
const direction = color === 'w' ? 1 : -1;
|
|
4798
5483
|
const rankDiff = toRank - fromRank;
|
|
4799
5484
|
const fileDiff = Math.abs(toFile - fromFile);
|
|
4800
|
-
|
|
5485
|
+
|
|
4801
5486
|
// Pawn can only move forward
|
|
4802
5487
|
if (rankDiff * direction <= 0) {
|
|
4803
|
-
console.log('Invalid: Pawn cannot move backward or stay in place');
|
|
4804
5488
|
return false;
|
|
4805
5489
|
}
|
|
4806
|
-
|
|
4807
|
-
// Pawn can only move 1
|
|
5490
|
+
|
|
5491
|
+
// Pawn can only move 1-2 ranks
|
|
4808
5492
|
if (Math.abs(rankDiff) > 2) {
|
|
4809
|
-
console.log('Invalid: Pawn cannot move more than 2 ranks');
|
|
4810
5493
|
return false;
|
|
4811
5494
|
}
|
|
4812
|
-
|
|
5495
|
+
|
|
4813
5496
|
// If moving 2 ranks, must be from starting position
|
|
4814
5497
|
if (Math.abs(rankDiff) === 2) {
|
|
4815
5498
|
const startingRank = color === 'w' ? 2 : 7;
|
|
4816
5499
|
if (fromRank !== startingRank) {
|
|
4817
|
-
console.log(`Invalid: Pawn cannot move 2 ranks from rank ${fromRank}`);
|
|
4818
5500
|
return false;
|
|
4819
5501
|
}
|
|
4820
5502
|
}
|
|
4821
|
-
|
|
4822
|
-
// Pawn can only move to adjacent files
|
|
5503
|
+
|
|
5504
|
+
// Pawn can only move to adjacent files or same file
|
|
4823
5505
|
if (fileDiff > 1) {
|
|
4824
|
-
console.log('Invalid: Pawn cannot move more than 1 file');
|
|
4825
5506
|
return false;
|
|
4826
5507
|
}
|
|
4827
|
-
|
|
4828
|
-
console.log('Pawn move validation passed');
|
|
5508
|
+
|
|
4829
5509
|
return true;
|
|
4830
5510
|
}
|
|
4831
5511
|
|
|
@@ -4866,24 +5546,17 @@ var ChessboardLib = (function (exports) {
|
|
|
4866
5546
|
* @private
|
|
4867
5547
|
*/
|
|
4868
5548
|
_showPromotionInColumn(targetSquare, piece, squares, onPromotionSelect, onPromotionCancel) {
|
|
4869
|
-
console.log('Setting up promotion for', targetSquare.id, 'piece color:', piece.color);
|
|
4870
|
-
|
|
4871
5549
|
// Set up promotion choices starting from border row
|
|
4872
5550
|
PROMOTION_PIECES.forEach((pieceType, index) => {
|
|
4873
5551
|
const choiceSquare = this._findPromotionSquare(targetSquare, index, squares);
|
|
4874
|
-
|
|
5552
|
+
|
|
4875
5553
|
if (choiceSquare) {
|
|
4876
5554
|
const pieceId = pieceType + piece.color;
|
|
4877
5555
|
const piecePath = this._getPiecePathForPromotion(pieceId);
|
|
4878
|
-
|
|
4879
|
-
console.log('Setting up promotion choice:', pieceType, 'on square:', choiceSquare.id);
|
|
4880
|
-
|
|
5556
|
+
|
|
4881
5557
|
choiceSquare.putPromotion(piecePath, () => {
|
|
4882
|
-
console.log('Promotion choice selected:', pieceType);
|
|
4883
5558
|
onPromotionSelect(pieceType);
|
|
4884
5559
|
});
|
|
4885
|
-
} else {
|
|
4886
|
-
console.log('Could not find square for promotion choice:', pieceType, 'index:', index);
|
|
4887
5560
|
}
|
|
4888
5561
|
});
|
|
4889
5562
|
|
|
@@ -4910,40 +5583,28 @@ var ChessboardLib = (function (exports) {
|
|
|
4910
5583
|
_findPromotionSquare(targetSquare, index, squares) {
|
|
4911
5584
|
const col = targetSquare.col;
|
|
4912
5585
|
const baseRow = targetSquare.row;
|
|
4913
|
-
|
|
4914
|
-
console.log('Looking for promotion square - target:', targetSquare.id, 'index:', index, 'col:', col, 'baseRow:', baseRow);
|
|
4915
|
-
|
|
5586
|
+
|
|
4916
5587
|
// Calculate row based on index and promotion direction
|
|
4917
|
-
// Start from the border row (1 or 8) and go inward
|
|
4918
5588
|
let row;
|
|
4919
5589
|
if (baseRow === 8) {
|
|
4920
|
-
// White promotion: start from row 8 and go down
|
|
4921
5590
|
row = 8 - index;
|
|
4922
5591
|
} else if (baseRow === 1) {
|
|
4923
|
-
// Black promotion: start from row 1 and go up
|
|
4924
5592
|
row = 1 + index;
|
|
4925
5593
|
} else {
|
|
4926
|
-
console.log('Invalid promotion row:', baseRow);
|
|
4927
5594
|
return null;
|
|
4928
5595
|
}
|
|
4929
|
-
|
|
4930
|
-
console.log('Calculated row:', row);
|
|
4931
|
-
|
|
4932
|
-
// Ensure row is within bounds
|
|
5596
|
+
|
|
4933
5597
|
if (row < 1 || row > 8) {
|
|
4934
|
-
console.log('Row out of bounds:', row);
|
|
4935
5598
|
return null;
|
|
4936
5599
|
}
|
|
4937
|
-
|
|
5600
|
+
|
|
4938
5601
|
// Find square by row/col
|
|
4939
5602
|
for (const square of Object.values(squares)) {
|
|
4940
5603
|
if (square.col === col && square.row === row) {
|
|
4941
|
-
console.log('Found promotion square:', square.id);
|
|
4942
5604
|
return square;
|
|
4943
5605
|
}
|
|
4944
5606
|
}
|
|
4945
|
-
|
|
4946
|
-
console.log('No square found for col:', col, 'row:', row);
|
|
5607
|
+
|
|
4947
5608
|
return null;
|
|
4948
5609
|
}
|
|
4949
5610
|
|
|
@@ -5179,50 +5840,61 @@ var ChessboardLib = (function (exports) {
|
|
|
5179
5840
|
piece.setDrag(dragFunction(square, piece));
|
|
5180
5841
|
}
|
|
5181
5842
|
|
|
5182
|
-
if (fade
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5843
|
+
if (fade) {
|
|
5844
|
+
const appearanceStyle = this.config.appearanceStyle || 'fade';
|
|
5845
|
+
const duration = this.config.appearanceTime || this.config.fadeTime || 200;
|
|
5846
|
+
|
|
5847
|
+
if (duration > 0 && appearanceStyle !== 'instant') {
|
|
5848
|
+
piece.appearAnimate(appearanceStyle, duration, () => {
|
|
5849
|
+
piece.visible();
|
|
5850
|
+
if (callback) callback();
|
|
5851
|
+
});
|
|
5852
|
+
} else {
|
|
5853
|
+
piece.visible();
|
|
5854
|
+
if (callback) callback();
|
|
5855
|
+
}
|
|
5189
5856
|
} else {
|
|
5857
|
+
piece.visible();
|
|
5190
5858
|
if (callback) callback();
|
|
5191
5859
|
}
|
|
5192
|
-
|
|
5193
|
-
piece.visible();
|
|
5194
5860
|
}
|
|
5195
5861
|
|
|
5196
5862
|
/**
|
|
5197
|
-
* Removes a piece from a square with
|
|
5863
|
+
* Removes a piece from a square with configurable capture animation
|
|
5198
5864
|
* @param {Square} square - Source square
|
|
5199
|
-
* @param {boolean} [
|
|
5865
|
+
* @param {boolean} [animate=true] - Whether to animate the removal
|
|
5200
5866
|
* @param {Function} [callback] - Callback when animation completes
|
|
5201
5867
|
* @returns {Piece} The removed piece
|
|
5202
5868
|
* @throws {PieceError} When square has no piece to remove
|
|
5203
5869
|
*/
|
|
5204
|
-
removePieceFromSquare(square,
|
|
5870
|
+
removePieceFromSquare(square, animate = true, callback) {
|
|
5205
5871
|
console.debug(`[PieceService] removePieceFromSquare: ${square.id}`);
|
|
5206
5872
|
square.check();
|
|
5207
5873
|
|
|
5208
5874
|
const piece = square.piece;
|
|
5209
5875
|
if (!piece) {
|
|
5210
5876
|
if (callback) callback();
|
|
5211
|
-
|
|
5877
|
+
return null;
|
|
5212
5878
|
}
|
|
5213
5879
|
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5880
|
+
// Always remove piece reference synchronously to prevent stale state
|
|
5881
|
+
// when a new update starts before the animation completes
|
|
5882
|
+
square.piece = null;
|
|
5883
|
+
|
|
5884
|
+
const captureStyle = this.config.captureStyle || 'fade';
|
|
5885
|
+
const duration = this.config.captureTime || this.config.fadeTime || 200;
|
|
5886
|
+
|
|
5887
|
+
if (animate && duration > 0) {
|
|
5888
|
+
// Animate visual element, then destroy
|
|
5889
|
+
piece.captureAnimate(captureStyle, duration, () => {
|
|
5890
|
+
piece.destroy();
|
|
5891
|
+
if (callback) callback();
|
|
5892
|
+
});
|
|
5221
5893
|
} else {
|
|
5894
|
+
piece.destroy();
|
|
5222
5895
|
if (callback) callback();
|
|
5223
5896
|
}
|
|
5224
5897
|
|
|
5225
|
-
square.removePiece();
|
|
5226
5898
|
return piece;
|
|
5227
5899
|
}
|
|
5228
5900
|
|
|
@@ -5241,12 +5913,22 @@ var ChessboardLib = (function (exports) {
|
|
|
5241
5913
|
return;
|
|
5242
5914
|
}
|
|
5243
5915
|
|
|
5916
|
+
// Build movement options from config
|
|
5917
|
+
const moveOptions = {
|
|
5918
|
+
style: this.config.moveStyle || 'slide',
|
|
5919
|
+
easing: this.config.moveEasing || this.config.moveAnimation || 'ease',
|
|
5920
|
+
arcHeight: this.config.moveArcHeight || 0.3,
|
|
5921
|
+
landingEffect: this.config.landingEffect || 'none',
|
|
5922
|
+
landingDuration: this.config.landingDuration || 150
|
|
5923
|
+
};
|
|
5924
|
+
|
|
5244
5925
|
piece.translate(
|
|
5245
5926
|
targetSquare,
|
|
5246
5927
|
duration,
|
|
5247
5928
|
this._getTransitionTimingFunction(),
|
|
5248
5929
|
this.config.moveAnimation,
|
|
5249
|
-
callback
|
|
5930
|
+
callback,
|
|
5931
|
+
moveOptions
|
|
5250
5932
|
);
|
|
5251
5933
|
}
|
|
5252
5934
|
|
|
@@ -5260,8 +5942,8 @@ var ChessboardLib = (function (exports) {
|
|
|
5260
5942
|
*/
|
|
5261
5943
|
translatePiece(move, removeTarget, animate, dragFunction = null, callback = null) {
|
|
5262
5944
|
console.debug(`[PieceService] translatePiece: ${move.piece.id} from ${move.from.id} to ${move.to.id}`);
|
|
5263
|
-
if (!move.piece) {
|
|
5264
|
-
console.warn('PieceService.translatePiece: move.piece is null, skipping translation');
|
|
5945
|
+
if (!move.piece || !move.piece.element) {
|
|
5946
|
+
console.warn('PieceService.translatePiece: move.piece or element is null, skipping translation');
|
|
5265
5947
|
if (callback) callback();
|
|
5266
5948
|
return;
|
|
5267
5949
|
}
|
|
@@ -5273,6 +5955,12 @@ var ChessboardLib = (function (exports) {
|
|
|
5273
5955
|
}
|
|
5274
5956
|
|
|
5275
5957
|
const changeSquareCallback = () => {
|
|
5958
|
+
// If piece element was destroyed (e.g., by a newer update), skip
|
|
5959
|
+
if (!move.piece.element || !move.piece.element.parentNode) {
|
|
5960
|
+
if (callback) callback();
|
|
5961
|
+
return;
|
|
5962
|
+
}
|
|
5963
|
+
|
|
5276
5964
|
// Check if piece still exists and is on the source square
|
|
5277
5965
|
if (move.from.piece === move.piece) {
|
|
5278
5966
|
move.from.removePiece(true); // Preserve the piece when moving
|
|
@@ -7651,7 +8339,8 @@ var ChessboardLib = (function (exports) {
|
|
|
7651
8339
|
this._handleConstructorError(error);
|
|
7652
8340
|
}
|
|
7653
8341
|
this._undoneMoves = [];
|
|
7654
|
-
this.
|
|
8342
|
+
this._destroyed = false;
|
|
8343
|
+
this._animationTimeouts = [];
|
|
7655
8344
|
}
|
|
7656
8345
|
|
|
7657
8346
|
/**
|
|
@@ -7763,6 +8452,11 @@ var ChessboardLib = (function (exports) {
|
|
|
7763
8452
|
this._buildSquares();
|
|
7764
8453
|
this._addListeners();
|
|
7765
8454
|
this._updateBoardPieces(true, true); // Initial position load
|
|
8455
|
+
|
|
8456
|
+
// Apply flipped class if initial orientation is black
|
|
8457
|
+
if (this.coordinateService.getOrientation() === 'b' && this.boardService.element) {
|
|
8458
|
+
this.boardService.element.classList.add('flipped');
|
|
8459
|
+
}
|
|
7766
8460
|
}
|
|
7767
8461
|
|
|
7768
8462
|
/**
|
|
@@ -7791,9 +8485,7 @@ var ChessboardLib = (function (exports) {
|
|
|
7791
8485
|
* Best practice: always remove squares (destroy JS/DOM) before clearing the board container.
|
|
7792
8486
|
*/
|
|
7793
8487
|
_buildBoard() {
|
|
7794
|
-
console.log('CHIAMATO: _buildBoard');
|
|
7795
8488
|
if (this._isUndoRedo) {
|
|
7796
|
-
console.log('SKIP _buildBoard per undo/redo');
|
|
7797
8489
|
return;
|
|
7798
8490
|
}
|
|
7799
8491
|
// Forza la pulizia completa del contenitore board (DOM)
|
|
@@ -7813,9 +8505,7 @@ var ChessboardLib = (function (exports) {
|
|
|
7813
8505
|
* @private
|
|
7814
8506
|
*/
|
|
7815
8507
|
_buildSquares() {
|
|
7816
|
-
console.log('CHIAMATO: _buildSquares');
|
|
7817
8508
|
if (this._isUndoRedo) {
|
|
7818
|
-
console.log('SKIP _buildSquares per undo/redo');
|
|
7819
8509
|
return;
|
|
7820
8510
|
}
|
|
7821
8511
|
if (this.boardService && this.boardService.removeSquares) {
|
|
@@ -8028,6 +8718,14 @@ var ChessboardLib = (function (exports) {
|
|
|
8028
8718
|
const isEnPassant = this.moveService.isEnPassant(gameMove);
|
|
8029
8719
|
|
|
8030
8720
|
if (animate && move.from.piece) {
|
|
8721
|
+
// For simultaneous castle, start rook animation alongside the king
|
|
8722
|
+
const isSimultaneousCastle = isCastle && this.config.animationStyle === 'simultaneous';
|
|
8723
|
+
if (isSimultaneousCastle) {
|
|
8724
|
+
setTimeout(() => {
|
|
8725
|
+
this._handleCastleMove(gameMove, true);
|
|
8726
|
+
}, this.config.simultaneousAnimationDelay);
|
|
8727
|
+
}
|
|
8728
|
+
|
|
8031
8729
|
this.pieceService.translatePiece(
|
|
8032
8730
|
move,
|
|
8033
8731
|
!!move.to.piece, // was there a capture?
|
|
@@ -8035,24 +8733,22 @@ var ChessboardLib = (function (exports) {
|
|
|
8035
8733
|
this._createDragFunction.bind(this),
|
|
8036
8734
|
() => {
|
|
8037
8735
|
// After the main piece animation completes...
|
|
8038
|
-
|
|
8736
|
+
// For sequential castle, animate rook AFTER king finishes
|
|
8737
|
+
// For simultaneous, rook was already animated above - don't animate again
|
|
8738
|
+
if (isCastle && !isSimultaneousCastle) {
|
|
8039
8739
|
this._handleSpecialMoveAnimation(gameMove);
|
|
8040
8740
|
} else if (isEnPassant) {
|
|
8041
8741
|
this._handleSpecialMoveAnimation(gameMove);
|
|
8042
8742
|
}
|
|
8043
8743
|
// Notify user that the move is fully complete
|
|
8044
8744
|
this.config.onMoveEnd(gameMove);
|
|
8045
|
-
//
|
|
8046
|
-
|
|
8745
|
+
// For simultaneous castle, the rook callback will handle the final sync
|
|
8746
|
+
// to avoid interfering with the ongoing rook animation
|
|
8747
|
+
if (!isSimultaneousCastle) {
|
|
8748
|
+
this._updateBoardPieces(false);
|
|
8749
|
+
}
|
|
8047
8750
|
}
|
|
8048
8751
|
);
|
|
8049
|
-
|
|
8050
|
-
// For simultaneous castle, animate the rook alongside the king
|
|
8051
|
-
if (isCastle && this.config.animationStyle === 'simultaneous') {
|
|
8052
|
-
setTimeout(() => {
|
|
8053
|
-
this._handleCastleMove(gameMove, true);
|
|
8054
|
-
}, this.config.simultaneousAnimationDelay);
|
|
8055
|
-
}
|
|
8056
8752
|
} else {
|
|
8057
8753
|
// If not animating, handle special moves immediately and update the board
|
|
8058
8754
|
if (isCastle) {
|
|
@@ -8105,12 +8801,9 @@ var ChessboardLib = (function (exports) {
|
|
|
8105
8801
|
const rookToSquare = this.boardService.getSquare(rookMove.to);
|
|
8106
8802
|
|
|
8107
8803
|
if (!rookFromSquare || !rookToSquare || !rookFromSquare.piece) {
|
|
8108
|
-
console.warn('Castle rook move failed - squares or piece not found');
|
|
8109
8804
|
return;
|
|
8110
8805
|
}
|
|
8111
8806
|
|
|
8112
|
-
console.log(`Castle: moving rook from ${rookMove.from} to ${rookMove.to}`);
|
|
8113
|
-
|
|
8114
8807
|
if (animate) {
|
|
8115
8808
|
// Always use translatePiece for smooth sliding animation
|
|
8116
8809
|
const rookPiece = rookFromSquare.piece;
|
|
@@ -8142,12 +8835,9 @@ var ChessboardLib = (function (exports) {
|
|
|
8142
8835
|
|
|
8143
8836
|
const capturedSquareObj = this.boardService.getSquare(capturedSquare);
|
|
8144
8837
|
if (!capturedSquareObj || !capturedSquareObj.piece) {
|
|
8145
|
-
console.warn('En passant captured square not found or empty');
|
|
8146
8838
|
return;
|
|
8147
8839
|
}
|
|
8148
8840
|
|
|
8149
|
-
console.log(`En passant: removing captured pawn from ${capturedSquare}`);
|
|
8150
|
-
|
|
8151
8841
|
if (animate) {
|
|
8152
8842
|
// Animate the captured pawn removal
|
|
8153
8843
|
this.pieceService.removePieceFromSquare(capturedSquareObj, true);
|
|
@@ -8168,10 +8858,9 @@ var ChessboardLib = (function (exports) {
|
|
|
8168
8858
|
* @param {boolean} [isPositionLoad=false] - Whether this is a position load
|
|
8169
8859
|
*/
|
|
8170
8860
|
_updateBoardPieces(animation = false, isPositionLoad = false) {
|
|
8171
|
-
|
|
8861
|
+
if (this._destroyed) return;
|
|
8172
8862
|
// Check if services are available
|
|
8173
8863
|
if (!this.positionService || !this.moveService || !this.eventService) {
|
|
8174
|
-
console.log('Cannot update board pieces - services not available');
|
|
8175
8864
|
return;
|
|
8176
8865
|
}
|
|
8177
8866
|
|
|
@@ -8234,33 +8923,24 @@ var ChessboardLib = (function (exports) {
|
|
|
8234
8923
|
* @param {boolean} [isPositionLoad=false] - Whether this is a position load (affects delay)
|
|
8235
8924
|
*/
|
|
8236
8925
|
_doUpdateBoardPieces(animation = false, isPositionLoad = false) {
|
|
8926
|
+
if (this._destroyed) return;
|
|
8237
8927
|
// Skip update if we're in the middle of a promotion
|
|
8238
8928
|
if (this._isPromoting) {
|
|
8239
|
-
console.log('Skipping board update during promotion');
|
|
8240
8929
|
return;
|
|
8241
8930
|
}
|
|
8242
8931
|
|
|
8243
8932
|
// Check if services are available
|
|
8244
8933
|
if (!this.positionService || !this.positionService.getGame()) {
|
|
8245
|
-
console.log('Cannot update board pieces - position service not available');
|
|
8246
8934
|
return;
|
|
8247
8935
|
}
|
|
8248
8936
|
|
|
8249
8937
|
const squares = this.boardService.getAllSquares();
|
|
8250
8938
|
const gameStateBefore = this.positionService.getGame().fen();
|
|
8251
|
-
|
|
8252
|
-
console.log('_doUpdateBoardPieces - current FEN:', gameStateBefore);
|
|
8253
|
-
console.log('_doUpdateBoardPieces - animation:', animation, 'style:', this.config.animationStyle, 'isPositionLoad:', isPositionLoad);
|
|
8254
|
-
|
|
8255
|
-
// Determine which animation style to use
|
|
8256
8939
|
const useSimultaneous = this.config.animationStyle === 'simultaneous';
|
|
8257
|
-
console.log('_doUpdateBoardPieces - useSimultaneous:', useSimultaneous);
|
|
8258
8940
|
|
|
8259
8941
|
if (useSimultaneous) {
|
|
8260
|
-
|
|
8261
|
-
this._doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad);
|
|
8942
|
+
this._doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad, animation);
|
|
8262
8943
|
} else {
|
|
8263
|
-
console.log('Using sequential animation');
|
|
8264
8944
|
this._doSequentialUpdate(squares, gameStateBefore, animation);
|
|
8265
8945
|
}
|
|
8266
8946
|
}
|
|
@@ -8273,7 +8953,23 @@ var ChessboardLib = (function (exports) {
|
|
|
8273
8953
|
* @param {boolean} animation - Whether to animate
|
|
8274
8954
|
*/
|
|
8275
8955
|
_doSequentialUpdate(squares, gameStateBefore, animation) {
|
|
8276
|
-
//
|
|
8956
|
+
// Cancel running animations and clean orphaned elements
|
|
8957
|
+
Object.values(squares).forEach(square => {
|
|
8958
|
+
const imgs = square.element.querySelectorAll('img.piece');
|
|
8959
|
+
imgs.forEach(img => {
|
|
8960
|
+
if (img.getAnimations) {
|
|
8961
|
+
img.getAnimations().forEach(anim => anim.cancel());
|
|
8962
|
+
}
|
|
8963
|
+
if (!square.piece || img !== square.piece.element) {
|
|
8964
|
+
img.remove();
|
|
8965
|
+
}
|
|
8966
|
+
});
|
|
8967
|
+
if (square.piece && square.piece.element) {
|
|
8968
|
+
square.piece.element.style = '';
|
|
8969
|
+
square.piece.element.style.opacity = '1';
|
|
8970
|
+
}
|
|
8971
|
+
});
|
|
8972
|
+
|
|
8277
8973
|
const expectedMap = {};
|
|
8278
8974
|
Object.values(squares).forEach(square => {
|
|
8279
8975
|
expectedMap[square.id] = this.positionService.getGamePieceId(square.id);
|
|
@@ -8289,12 +8985,13 @@ var ChessboardLib = (function (exports) {
|
|
|
8289
8985
|
return;
|
|
8290
8986
|
}
|
|
8291
8987
|
|
|
8292
|
-
//
|
|
8988
|
+
// Remove current piece if it doesn't match expected
|
|
8293
8989
|
if (currentPiece && currentPieceId !== expectedPieceId) {
|
|
8294
|
-
|
|
8990
|
+
// Always remove synchronously to avoid race condition with addition
|
|
8991
|
+
this.pieceService.removePieceFromSquare(square, false);
|
|
8295
8992
|
}
|
|
8296
8993
|
|
|
8297
|
-
//
|
|
8994
|
+
// Add expected piece if it doesn't match current
|
|
8298
8995
|
if (expectedPieceId && currentPieceId !== expectedPieceId) {
|
|
8299
8996
|
const newPiece = this.pieceService.convertPiece(expectedPieceId);
|
|
8300
8997
|
this.pieceService.addPieceOnSquare(
|
|
@@ -8319,9 +9016,43 @@ var ChessboardLib = (function (exports) {
|
|
|
8319
9016
|
* @param {Object} squares - All squares
|
|
8320
9017
|
* @param {string} gameStateBefore - Game state before update
|
|
8321
9018
|
* @param {boolean} [isPositionLoad=false] - Whether this is a position load
|
|
9019
|
+
* @param {boolean} [animation=true] - Whether to animate
|
|
8322
9020
|
*/
|
|
8323
|
-
_doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad = false) {
|
|
8324
|
-
//
|
|
9021
|
+
_doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad = false, animation = true) {
|
|
9022
|
+
// Increment generation to invalidate stale animation callbacks
|
|
9023
|
+
this._updateGeneration = (this._updateGeneration || 0) + 1;
|
|
9024
|
+
const generation = this._updateGeneration;
|
|
9025
|
+
|
|
9026
|
+
// Cancel pending animation timeouts from previous update
|
|
9027
|
+
if (this._animationTimeouts) {
|
|
9028
|
+
this._animationTimeouts.forEach(tid => clearTimeout(tid));
|
|
9029
|
+
this._animationTimeouts = [];
|
|
9030
|
+
}
|
|
9031
|
+
|
|
9032
|
+
// Cancel all running animations and force-sync DOM state
|
|
9033
|
+
Object.values(squares).forEach(square => {
|
|
9034
|
+
const imgs = square.element.querySelectorAll('img.piece');
|
|
9035
|
+
imgs.forEach(img => {
|
|
9036
|
+
// Cancel all Web Animations on this element so onfinish callbacks don't fire
|
|
9037
|
+
if (img.getAnimations) {
|
|
9038
|
+
img.getAnimations().forEach(anim => anim.cancel());
|
|
9039
|
+
}
|
|
9040
|
+
// Remove orphaned images not matching current piece
|
|
9041
|
+
if (!square.piece || img !== square.piece.element) {
|
|
9042
|
+
img.remove();
|
|
9043
|
+
}
|
|
9044
|
+
});
|
|
9045
|
+
// Reset current piece element to clean state (remove animation artifacts)
|
|
9046
|
+
if (square.piece && square.piece.element) {
|
|
9047
|
+
square.piece.element.style = '';
|
|
9048
|
+
square.piece.element.style.opacity = '1';
|
|
9049
|
+
// Ensure element is attached to correct square
|
|
9050
|
+
if (!square.element.contains(square.piece.element)) {
|
|
9051
|
+
square.element.appendChild(square.piece.element);
|
|
9052
|
+
}
|
|
9053
|
+
}
|
|
9054
|
+
});
|
|
9055
|
+
|
|
8325
9056
|
const currentMap = {};
|
|
8326
9057
|
const expectedMap = {};
|
|
8327
9058
|
|
|
@@ -8347,35 +9078,16 @@ var ChessboardLib = (function (exports) {
|
|
|
8347
9078
|
const animationDelay = isPositionLoad ? 0 : this.config.simultaneousAnimationDelay;
|
|
8348
9079
|
let animationIndex = 0;
|
|
8349
9080
|
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
if (totalAnimations === 0) {
|
|
8355
|
-
this._addListeners();
|
|
8356
|
-
const gameStateAfter = this.positionService.getGame().fen();
|
|
8357
|
-
if (gameStateBefore !== gameStateAfter) {
|
|
8358
|
-
this.config.onChange(gameStateAfter);
|
|
8359
|
-
}
|
|
8360
|
-
return;
|
|
8361
|
-
}
|
|
8362
|
-
|
|
8363
|
-
const onAnimationComplete = () => {
|
|
8364
|
-
animationsCompleted++;
|
|
8365
|
-
if (animationsCompleted === totalAnimations) {
|
|
8366
|
-
this._addListeners();
|
|
8367
|
-
const gameStateAfter = this.positionService.getGame().fen();
|
|
8368
|
-
if (gameStateBefore !== gameStateAfter) {
|
|
8369
|
-
this.config.onChange(gameStateAfter);
|
|
8370
|
-
}
|
|
8371
|
-
}
|
|
8372
|
-
};
|
|
9081
|
+
// First pass: compute matching for all piece types
|
|
9082
|
+
const allRemovals = [];
|
|
9083
|
+
const allAdditions = [];
|
|
9084
|
+
const allMoves = [];
|
|
8373
9085
|
|
|
8374
9086
|
Object.keys(expectedMap).forEach(key => {
|
|
8375
9087
|
const fromList = (currentMap[key] || []).slice();
|
|
8376
9088
|
const toList = expectedMap[key].slice();
|
|
8377
9089
|
|
|
8378
|
-
//
|
|
9090
|
+
// Build distance matrix
|
|
8379
9091
|
const distances = [];
|
|
8380
9092
|
for (let i = 0; i < fromList.length; i++) {
|
|
8381
9093
|
distances[i] = [];
|
|
@@ -8385,10 +9097,9 @@ var ChessboardLib = (function (exports) {
|
|
|
8385
9097
|
}
|
|
8386
9098
|
}
|
|
8387
9099
|
|
|
8388
|
-
//
|
|
9100
|
+
// Greedy matching: pair closest pieces
|
|
8389
9101
|
const fromMatched = new Array(fromList.length).fill(false);
|
|
8390
9102
|
const toMatched = new Array(toList.length).fill(false);
|
|
8391
|
-
const moves = [];
|
|
8392
9103
|
|
|
8393
9104
|
while (true) {
|
|
8394
9105
|
let minDist = Infinity, minI = -1, minJ = -1;
|
|
@@ -8404,58 +9115,140 @@ var ChessboardLib = (function (exports) {
|
|
|
8404
9115
|
}
|
|
8405
9116
|
}
|
|
8406
9117
|
if (minI === -1 || minJ === -1) break;
|
|
8407
|
-
|
|
9118
|
+
fromMatched[minI] = true;
|
|
9119
|
+
toMatched[minJ] = true;
|
|
9120
|
+
// Skip unchanged pieces (same square)
|
|
8408
9121
|
if (fromList[minI].square === toList[minJ].square) {
|
|
8409
|
-
fromMatched[minI] = true;
|
|
8410
|
-
toMatched[minJ] = true;
|
|
8411
9122
|
continue;
|
|
8412
9123
|
}
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
9124
|
+
allMoves.push({ from: fromList[minI].square, to: toList[minJ].square, piece: fromList[minI].square.piece });
|
|
9125
|
+
}
|
|
9126
|
+
|
|
9127
|
+
// Collect unmatched current pieces (to remove)
|
|
9128
|
+
for (let i = 0; i < fromList.length; i++) {
|
|
9129
|
+
if (!fromMatched[i]) {
|
|
9130
|
+
allRemovals.push(fromList[i].square);
|
|
9131
|
+
}
|
|
9132
|
+
}
|
|
9133
|
+
|
|
9134
|
+
// Collect unmatched expected pieces (to add)
|
|
9135
|
+
for (let j = 0; j < toList.length; j++) {
|
|
9136
|
+
if (!toMatched[j]) {
|
|
9137
|
+
allAdditions.push({ square: toList[j].square, key });
|
|
9138
|
+
}
|
|
9139
|
+
}
|
|
9140
|
+
});
|
|
9141
|
+
|
|
9142
|
+
// Also count removals for pieces whose type doesn't exist in expectedMap
|
|
9143
|
+
Object.keys(currentMap).forEach(key => {
|
|
9144
|
+
if (!expectedMap[key]) {
|
|
9145
|
+
currentMap[key].forEach(entry => {
|
|
9146
|
+
allRemovals.push(entry.square);
|
|
9147
|
+
});
|
|
9148
|
+
}
|
|
9149
|
+
});
|
|
9150
|
+
|
|
9151
|
+
// Count only actual animations
|
|
9152
|
+
totalAnimations = allRemovals.length + allAdditions.length + allMoves.length;
|
|
9153
|
+
|
|
9154
|
+
if (totalAnimations === 0) {
|
|
9155
|
+
this._addListeners();
|
|
9156
|
+
const gameStateAfter = this.positionService.getGame().fen();
|
|
9157
|
+
if (gameStateBefore !== gameStateAfter) {
|
|
9158
|
+
this.config.onChange(gameStateAfter);
|
|
8417
9159
|
}
|
|
9160
|
+
return;
|
|
9161
|
+
}
|
|
8418
9162
|
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
}, animationIndex * animationDelay);
|
|
8425
|
-
animationIndex++;
|
|
8426
|
-
}
|
|
9163
|
+
// Detach moving pieces from source squares BEFORE any removals/additions
|
|
9164
|
+
// This prevents additions to a move's source square from destroying the piece
|
|
9165
|
+
allMoves.forEach(move => {
|
|
9166
|
+
if (move.from.piece === move.piece) {
|
|
9167
|
+
move.from.removePiece(true); // preserve element, just detach reference
|
|
8427
9168
|
}
|
|
9169
|
+
});
|
|
8428
9170
|
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
9171
|
+
// No animation: apply all changes synchronously
|
|
9172
|
+
if (!animation) {
|
|
9173
|
+
allRemovals.forEach(square => {
|
|
9174
|
+
this.pieceService.removePieceFromSquare(square, false);
|
|
9175
|
+
});
|
|
9176
|
+
allMoves.forEach(move => {
|
|
9177
|
+
this.pieceService.translatePiece(
|
|
9178
|
+
move, false, false, this._createDragFunction.bind(this)
|
|
9179
|
+
);
|
|
9180
|
+
});
|
|
9181
|
+
allAdditions.forEach(({ square, key }) => {
|
|
9182
|
+
const newPiece = this.pieceService.convertPiece(key);
|
|
9183
|
+
this.pieceService.addPieceOnSquare(
|
|
9184
|
+
square, newPiece, false, this._createDragFunction.bind(this)
|
|
9185
|
+
);
|
|
9186
|
+
});
|
|
9187
|
+
this._addListeners();
|
|
9188
|
+
const gameStateAfter = this.positionService.getGame().fen();
|
|
9189
|
+
if (gameStateBefore !== gameStateAfter) {
|
|
9190
|
+
this.config.onChange(gameStateAfter);
|
|
9191
|
+
}
|
|
9192
|
+
return;
|
|
9193
|
+
}
|
|
9194
|
+
|
|
9195
|
+
// Animated path
|
|
9196
|
+
if (!this._animationTimeouts) this._animationTimeouts = [];
|
|
9197
|
+
|
|
9198
|
+
const onAnimationComplete = () => {
|
|
9199
|
+
// Ignore callbacks from stale/destroyed boards
|
|
9200
|
+
if (this._destroyed || this._updateGeneration !== generation) return;
|
|
9201
|
+
animationsCompleted++;
|
|
9202
|
+
if (animationsCompleted === totalAnimations) {
|
|
9203
|
+
this._addListeners();
|
|
9204
|
+
const gameStateAfter = this.positionService.getGame().fen();
|
|
9205
|
+
if (gameStateBefore !== gameStateAfter) {
|
|
9206
|
+
this.config.onChange(gameStateAfter);
|
|
8443
9207
|
}
|
|
8444
9208
|
}
|
|
9209
|
+
};
|
|
8445
9210
|
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
9211
|
+
// Dispatch moves first (pieces already detached from source)
|
|
9212
|
+
allMoves.forEach(move => {
|
|
9213
|
+
const tid = setTimeout(() => {
|
|
9214
|
+
if (this._destroyed || this._updateGeneration !== generation) return;
|
|
9215
|
+
this.pieceService.translatePiece(
|
|
9216
|
+
move,
|
|
9217
|
+
false,
|
|
9218
|
+
true,
|
|
9219
|
+
this._createDragFunction.bind(this),
|
|
9220
|
+
onAnimationComplete
|
|
9221
|
+
);
|
|
9222
|
+
}, animationIndex * animationDelay);
|
|
9223
|
+
this._animationTimeouts.push(tid);
|
|
9224
|
+
animationIndex++;
|
|
9225
|
+
});
|
|
9226
|
+
|
|
9227
|
+
// Dispatch removals
|
|
9228
|
+
allRemovals.forEach(square => {
|
|
9229
|
+
const tid = setTimeout(() => {
|
|
9230
|
+
if (this._destroyed || this._updateGeneration !== generation) return;
|
|
9231
|
+
this.pieceService.removePieceFromSquare(square, true, onAnimationComplete);
|
|
9232
|
+
}, animationIndex * animationDelay);
|
|
9233
|
+
this._animationTimeouts.push(tid);
|
|
9234
|
+
animationIndex++;
|
|
9235
|
+
});
|
|
9236
|
+
|
|
9237
|
+
// Dispatch additions
|
|
9238
|
+
allAdditions.forEach(({ square, key }) => {
|
|
9239
|
+
const tid = setTimeout(() => {
|
|
9240
|
+
if (this._destroyed || this._updateGeneration !== generation) return;
|
|
9241
|
+
const newPiece = this.pieceService.convertPiece(key);
|
|
9242
|
+
this.pieceService.addPieceOnSquare(
|
|
9243
|
+
square,
|
|
9244
|
+
newPiece,
|
|
9245
|
+
true,
|
|
9246
|
+
this._createDragFunction.bind(this),
|
|
9247
|
+
onAnimationComplete
|
|
9248
|
+
);
|
|
9249
|
+
}, animationIndex * animationDelay);
|
|
9250
|
+
this._animationTimeouts.push(tid);
|
|
9251
|
+
animationIndex++;
|
|
8459
9252
|
});
|
|
8460
9253
|
}
|
|
8461
9254
|
|
|
@@ -8483,10 +9276,6 @@ var ChessboardLib = (function (exports) {
|
|
|
8483
9276
|
}
|
|
8484
9277
|
});
|
|
8485
9278
|
|
|
8486
|
-
console.log('Position Analysis:');
|
|
8487
|
-
console.log('Current pieces:', Array.from(currentPieces.entries()));
|
|
8488
|
-
console.log('Expected pieces:', Array.from(expectedPieces.entries()));
|
|
8489
|
-
|
|
8490
9279
|
// Identify different types of changes
|
|
8491
9280
|
const moves = []; // Pieces that can slide to new positions
|
|
8492
9281
|
const removes = []; // Pieces that need to be removed
|
|
@@ -8500,8 +9289,6 @@ var ChessboardLib = (function (exports) {
|
|
|
8500
9289
|
const expectedPieceId = expectedPieces.get(square);
|
|
8501
9290
|
|
|
8502
9291
|
if (currentPieceId === expectedPieceId) {
|
|
8503
|
-
// Same piece type on same square - no movement needed
|
|
8504
|
-
console.log(`UNCHANGED: ${currentPieceId} stays on ${square}`);
|
|
8505
9292
|
unchanged.push({
|
|
8506
9293
|
piece: currentPieceId,
|
|
8507
9294
|
square: square
|
|
@@ -8523,7 +9310,6 @@ var ChessboardLib = (function (exports) {
|
|
|
8523
9310
|
|
|
8524
9311
|
if (availableDestination) {
|
|
8525
9312
|
const [toSquare, expectedId] = availableDestination;
|
|
8526
|
-
console.log(`MOVE: ${currentPieceId} from ${fromSquare} to ${toSquare}`);
|
|
8527
9313
|
moves.push({
|
|
8528
9314
|
piece: currentPieceId,
|
|
8529
9315
|
from: fromSquare,
|
|
@@ -8533,8 +9319,6 @@ var ChessboardLib = (function (exports) {
|
|
|
8533
9319
|
});
|
|
8534
9320
|
processedSquares.add(toSquare);
|
|
8535
9321
|
} else {
|
|
8536
|
-
// This piece needs to be removed
|
|
8537
|
-
console.log(`REMOVE: ${currentPieceId} from ${fromSquare}`);
|
|
8538
9322
|
removes.push({
|
|
8539
9323
|
piece: currentPieceId,
|
|
8540
9324
|
square: fromSquare,
|
|
@@ -8546,7 +9330,6 @@ var ChessboardLib = (function (exports) {
|
|
|
8546
9330
|
// Third pass: handle pieces that need to be added
|
|
8547
9331
|
expectedPieces.forEach((expectedPieceId, toSquare) => {
|
|
8548
9332
|
if (!processedSquares.has(toSquare)) {
|
|
8549
|
-
console.log(`ADD: ${expectedPieceId} to ${toSquare}`);
|
|
8550
9333
|
adds.push({
|
|
8551
9334
|
piece: expectedPieceId,
|
|
8552
9335
|
square: toSquare,
|
|
@@ -8572,26 +9355,13 @@ var ChessboardLib = (function (exports) {
|
|
|
8572
9355
|
* @param {boolean} [isPositionLoad=false] - Whether this is a position load
|
|
8573
9356
|
*/
|
|
8574
9357
|
_executeSimultaneousChanges(changeAnalysis, gameStateBefore, isPositionLoad = false) {
|
|
8575
|
-
const { moves, removes, adds
|
|
8576
|
-
|
|
8577
|
-
console.log(`Position changes analysis:`, {
|
|
8578
|
-
moves: moves.length,
|
|
8579
|
-
removes: removes.length,
|
|
8580
|
-
adds: adds.length,
|
|
8581
|
-
unchanged: unchanged.length
|
|
8582
|
-
});
|
|
8583
|
-
|
|
8584
|
-
// Log unchanged pieces for debugging
|
|
8585
|
-
if (unchanged.length > 0) {
|
|
8586
|
-
console.log('Pieces staying in place:', unchanged.map(u => `${u.piece} on ${u.square}`));
|
|
8587
|
-
}
|
|
9358
|
+
const { moves, removes, adds } = changeAnalysis;
|
|
8588
9359
|
|
|
8589
9360
|
let animationsCompleted = 0;
|
|
8590
9361
|
const totalAnimations = moves.length + removes.length + adds.length;
|
|
8591
9362
|
|
|
8592
9363
|
// If no animations are needed, complete immediately
|
|
8593
9364
|
if (totalAnimations === 0) {
|
|
8594
|
-
console.log('No animations needed, completing immediately');
|
|
8595
9365
|
this._addListeners();
|
|
8596
9366
|
|
|
8597
9367
|
// Trigger change event if position changed
|
|
@@ -8604,9 +9374,7 @@ var ChessboardLib = (function (exports) {
|
|
|
8604
9374
|
|
|
8605
9375
|
const onAnimationComplete = () => {
|
|
8606
9376
|
animationsCompleted++;
|
|
8607
|
-
console.log(`Animation completed: ${animationsCompleted}/${totalAnimations}`);
|
|
8608
9377
|
if (animationsCompleted === totalAnimations) {
|
|
8609
|
-
console.log('All simultaneous animations completed');
|
|
8610
9378
|
this._addListeners();
|
|
8611
9379
|
|
|
8612
9380
|
// Trigger change event if position changed
|
|
@@ -8617,45 +9385,33 @@ var ChessboardLib = (function (exports) {
|
|
|
8617
9385
|
}
|
|
8618
9386
|
};
|
|
8619
9387
|
|
|
8620
|
-
// Determine delay: 0 for position loads, configured delay for normal moves
|
|
8621
9388
|
const animationDelay = isPositionLoad ? 0 : this.config.simultaneousAnimationDelay;
|
|
8622
|
-
console.log(`Using animation delay: ${animationDelay}ms (position load: ${isPositionLoad})`);
|
|
8623
|
-
|
|
8624
9389
|
let animationIndex = 0;
|
|
8625
9390
|
|
|
8626
|
-
// Process moves
|
|
9391
|
+
// Process moves
|
|
8627
9392
|
moves.forEach(move => {
|
|
8628
9393
|
const delay = animationIndex * animationDelay;
|
|
8629
|
-
console.log(`Scheduling move ${move.piece} from ${move.from} to ${move.to} with delay ${delay}ms`);
|
|
8630
|
-
|
|
8631
9394
|
setTimeout(() => {
|
|
8632
9395
|
this._animatePieceMove(move, onAnimationComplete);
|
|
8633
9396
|
}, delay);
|
|
8634
|
-
|
|
8635
9397
|
animationIndex++;
|
|
8636
9398
|
});
|
|
8637
9399
|
|
|
8638
|
-
// Process removes
|
|
9400
|
+
// Process removes
|
|
8639
9401
|
removes.forEach(remove => {
|
|
8640
9402
|
const delay = animationIndex * animationDelay;
|
|
8641
|
-
console.log(`Scheduling removal of ${remove.piece} from ${remove.square} with delay ${delay}ms`);
|
|
8642
|
-
|
|
8643
9403
|
setTimeout(() => {
|
|
8644
9404
|
this._animatePieceRemoval(remove, onAnimationComplete);
|
|
8645
9405
|
}, delay);
|
|
8646
|
-
|
|
8647
9406
|
animationIndex++;
|
|
8648
9407
|
});
|
|
8649
9408
|
|
|
8650
|
-
// Process adds
|
|
9409
|
+
// Process adds
|
|
8651
9410
|
adds.forEach(add => {
|
|
8652
9411
|
const delay = animationIndex * animationDelay;
|
|
8653
|
-
console.log(`Scheduling addition of ${add.piece} to ${add.square} with delay ${delay}ms`);
|
|
8654
|
-
|
|
8655
9412
|
setTimeout(() => {
|
|
8656
9413
|
this._animatePieceAddition(add, onAnimationComplete);
|
|
8657
9414
|
}, delay);
|
|
8658
|
-
|
|
8659
9415
|
animationIndex++;
|
|
8660
9416
|
});
|
|
8661
9417
|
}
|
|
@@ -8671,23 +9427,16 @@ var ChessboardLib = (function (exports) {
|
|
|
8671
9427
|
const piece = fromSquare.piece;
|
|
8672
9428
|
|
|
8673
9429
|
if (!piece) {
|
|
8674
|
-
console.warn(`No piece found on ${move.from} for move animation`);
|
|
8675
9430
|
onComplete();
|
|
8676
9431
|
return;
|
|
8677
9432
|
}
|
|
8678
9433
|
|
|
8679
|
-
console.log(`Animating piece move: ${move.piece} from ${move.from} to ${move.to}`);
|
|
8680
|
-
|
|
8681
|
-
// Use translatePiece for smooth sliding animation
|
|
8682
9434
|
this.pieceService.translatePiece(
|
|
8683
9435
|
{ from: fromSquare, to: toSquare, piece: piece },
|
|
8684
|
-
false,
|
|
8685
|
-
true,
|
|
9436
|
+
false,
|
|
9437
|
+
true,
|
|
8686
9438
|
this._createDragFunction.bind(this),
|
|
8687
|
-
|
|
8688
|
-
console.log(`Piece move animation completed: ${move.piece} to ${move.to}`);
|
|
8689
|
-
onComplete();
|
|
8690
|
-
}
|
|
9439
|
+
onComplete
|
|
8691
9440
|
);
|
|
8692
9441
|
}
|
|
8693
9442
|
|
|
@@ -8698,12 +9447,7 @@ var ChessboardLib = (function (exports) {
|
|
|
8698
9447
|
* @param {Function} onComplete - Callback when animation completes
|
|
8699
9448
|
*/
|
|
8700
9449
|
_animatePieceRemoval(remove, onComplete) {
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
this.pieceService.removePieceFromSquare(remove.squareObj, true, () => {
|
|
8704
|
-
console.log(`Piece removal animation completed: ${remove.piece} from ${remove.square}`);
|
|
8705
|
-
onComplete();
|
|
8706
|
-
});
|
|
9450
|
+
this.pieceService.removePieceFromSquare(remove.squareObj, true, onComplete);
|
|
8707
9451
|
}
|
|
8708
9452
|
|
|
8709
9453
|
/**
|
|
@@ -8713,18 +9457,13 @@ var ChessboardLib = (function (exports) {
|
|
|
8713
9457
|
* @param {Function} onComplete - Callback when animation completes
|
|
8714
9458
|
*/
|
|
8715
9459
|
_animatePieceAddition(add, onComplete) {
|
|
8716
|
-
console.log(`Animating piece addition: ${add.piece} to ${add.square}`);
|
|
8717
|
-
|
|
8718
9460
|
const newPiece = this.pieceService.convertPiece(add.piece);
|
|
8719
9461
|
this.pieceService.addPieceOnSquare(
|
|
8720
9462
|
add.squareObj,
|
|
8721
9463
|
newPiece,
|
|
8722
9464
|
true,
|
|
8723
9465
|
this._createDragFunction.bind(this),
|
|
8724
|
-
|
|
8725
|
-
console.log(`Piece addition animation completed: ${add.piece} to ${add.square}`);
|
|
8726
|
-
onComplete();
|
|
8727
|
-
}
|
|
9466
|
+
onComplete
|
|
8728
9467
|
);
|
|
8729
9468
|
}
|
|
8730
9469
|
|
|
@@ -8824,7 +9563,7 @@ var ChessboardLib = (function (exports) {
|
|
|
8824
9563
|
const animate = opts.animate !== undefined ? opts.animate : true;
|
|
8825
9564
|
// Use the default starting position from config or fallback
|
|
8826
9565
|
const startPosition = this.config && this.config.position ? this.config.position : 'start';
|
|
8827
|
-
|
|
9566
|
+
// setPosition already calls _updateBoardPieces, don't call it twice
|
|
8828
9567
|
return this.setPosition(startPosition, { animate });
|
|
8829
9568
|
}
|
|
8830
9569
|
/**
|
|
@@ -8839,10 +9578,13 @@ var ChessboardLib = (function (exports) {
|
|
|
8839
9578
|
return false;
|
|
8840
9579
|
}
|
|
8841
9580
|
if (this._clearVisualState) this._clearVisualState();
|
|
9581
|
+
|
|
9582
|
+
// Clear the game state
|
|
8842
9583
|
this.positionService.getGame().clear();
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
9584
|
+
|
|
9585
|
+
// Let _updateBoardPieces handle removal (no manual loop to avoid race conditions)
|
|
9586
|
+
this._updateBoardPieces(animate, true);
|
|
9587
|
+
|
|
8846
9588
|
return true;
|
|
8847
9589
|
}
|
|
8848
9590
|
|
|
@@ -8879,6 +9621,32 @@ var ChessboardLib = (function (exports) {
|
|
|
8879
9621
|
}
|
|
8880
9622
|
return false;
|
|
8881
9623
|
}
|
|
9624
|
+
/**
|
|
9625
|
+
* Move a piece from one square to another
|
|
9626
|
+
* @param {string} moveStr - Move in format 'e2e4' or 'e7e8q' (with promotion)
|
|
9627
|
+
* @param {Object} [opts]
|
|
9628
|
+
* @param {boolean} [opts.animate=true]
|
|
9629
|
+
* @returns {Object|boolean} Move result or false if invalid
|
|
9630
|
+
*/
|
|
9631
|
+
movePiece(moveStr, opts = {}) {
|
|
9632
|
+
const animate = opts.animate !== false;
|
|
9633
|
+
if (typeof moveStr !== 'string' || moveStr.length < 4) {
|
|
9634
|
+
return false;
|
|
9635
|
+
}
|
|
9636
|
+
const from = moveStr.slice(0, 2);
|
|
9637
|
+
const to = moveStr.slice(2, 4);
|
|
9638
|
+
const promotion = moveStr.length > 4 ? moveStr[4].toLowerCase() : undefined;
|
|
9639
|
+
|
|
9640
|
+
const moveObj = { from, to };
|
|
9641
|
+
if (promotion) moveObj.promotion = promotion;
|
|
9642
|
+
|
|
9643
|
+
const result = this.positionService.getGame().move(moveObj);
|
|
9644
|
+
if (result) {
|
|
9645
|
+
this._updateBoardPieces(animate);
|
|
9646
|
+
}
|
|
9647
|
+
return result || false;
|
|
9648
|
+
}
|
|
9649
|
+
|
|
8882
9650
|
/**
|
|
8883
9651
|
* Get legal moves for a square
|
|
8884
9652
|
* @param {string} square
|
|
@@ -8893,10 +9661,10 @@ var ChessboardLib = (function (exports) {
|
|
|
8893
9661
|
* @returns {string|null}
|
|
8894
9662
|
*/
|
|
8895
9663
|
getPiece(square) {
|
|
8896
|
-
//
|
|
8897
|
-
|
|
8898
|
-
if (!
|
|
8899
|
-
const piece =
|
|
9664
|
+
// Use game state as source of truth
|
|
9665
|
+
// Returns piece in format 'wq' (color + type)
|
|
9666
|
+
if (!this.positionService || !this.positionService.getGame()) return null;
|
|
9667
|
+
const piece = this.positionService.getGame().get(square);
|
|
8900
9668
|
if (!piece) return null;
|
|
8901
9669
|
return (piece.color + piece.type).toLowerCase();
|
|
8902
9670
|
}
|
|
@@ -8957,45 +9725,355 @@ var ChessboardLib = (function (exports) {
|
|
|
8957
9725
|
if (!this.validationService.isValidSquare(square)) {
|
|
8958
9726
|
throw new Error(`[removePiece] Invalid square: ${square}`);
|
|
8959
9727
|
}
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
squareObj.piece = null;
|
|
9728
|
+
if (!this.positionService || !this.positionService.getGame()) {
|
|
9729
|
+
return false;
|
|
9730
|
+
}
|
|
8964
9731
|
const game = this.positionService.getGame();
|
|
8965
|
-
game
|
|
9732
|
+
// Remove from game state first (source of truth)
|
|
9733
|
+
const removed = game.remove(square);
|
|
9734
|
+
// Then update the board visually
|
|
9735
|
+
const squareObj = this.boardService.getSquare(square);
|
|
9736
|
+
if (squareObj) {
|
|
9737
|
+
squareObj.piece = null;
|
|
9738
|
+
}
|
|
8966
9739
|
this._updateBoardPieces(animate);
|
|
8967
|
-
return
|
|
9740
|
+
return removed !== null;
|
|
8968
9741
|
}
|
|
8969
9742
|
|
|
8970
9743
|
// --- BOARD CONTROL ---
|
|
8971
9744
|
/**
|
|
8972
9745
|
* Flip the board orientation
|
|
8973
9746
|
* @param {Object} [opts]
|
|
8974
|
-
* @param {boolean} [opts.animate=true]
|
|
9747
|
+
* @param {boolean} [opts.animate=true] - Enable animation (for 'animate' mode)
|
|
9748
|
+
* @param {string} [opts.mode] - Override flip mode ('visual', 'animate', 'none')
|
|
8975
9749
|
*/
|
|
8976
9750
|
flipBoard(opts = {}) {
|
|
9751
|
+
const flipMode = opts.mode || this.config.flipMode || 'visual';
|
|
9752
|
+
|
|
9753
|
+
// Update internal orientation state
|
|
8977
9754
|
if (this.coordinateService && this.coordinateService.flipOrientation) {
|
|
8978
9755
|
this.coordinateService.flipOrientation();
|
|
8979
9756
|
}
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
9757
|
+
|
|
9758
|
+
const boardElement = this.boardService.element;
|
|
9759
|
+
const isFlipped = this.coordinateService.getOrientation() === 'b';
|
|
9760
|
+
|
|
9761
|
+
switch (flipMode) {
|
|
9762
|
+
case 'visual':
|
|
9763
|
+
// CSS flexbox flip - instant, no piece animation needed
|
|
9764
|
+
this._flipVisual(boardElement, isFlipped);
|
|
9765
|
+
break;
|
|
9766
|
+
|
|
9767
|
+
case 'animate':
|
|
9768
|
+
// Animate pieces to mirrored positions
|
|
9769
|
+
this._flipAnimate(opts.animate !== false);
|
|
9770
|
+
break;
|
|
9771
|
+
|
|
9772
|
+
case 'none':
|
|
9773
|
+
// No visual change - only internal orientation updated
|
|
9774
|
+
// Useful for programmatic orientation without visual feedback
|
|
9775
|
+
break;
|
|
9776
|
+
|
|
9777
|
+
default:
|
|
9778
|
+
this._flipVisual(boardElement, isFlipped);
|
|
9779
|
+
}
|
|
9780
|
+
}
|
|
9781
|
+
|
|
9782
|
+
/**
|
|
9783
|
+
* Visual flip using CSS flexbox (instant)
|
|
9784
|
+
* @private
|
|
9785
|
+
* @param {HTMLElement} boardElement - Board DOM element
|
|
9786
|
+
* @param {boolean} isFlipped - Whether board should be flipped
|
|
9787
|
+
*/
|
|
9788
|
+
_flipVisual(boardElement, isFlipped) {
|
|
9789
|
+
if (!boardElement) return;
|
|
9790
|
+
|
|
9791
|
+
if (isFlipped) {
|
|
9792
|
+
boardElement.classList.add('flipped');
|
|
9793
|
+
} else {
|
|
9794
|
+
boardElement.classList.remove('flipped');
|
|
9795
|
+
}
|
|
9796
|
+
}
|
|
9797
|
+
|
|
9798
|
+
/**
|
|
9799
|
+
* Animate flip using FLIP technique (First-Last-Invert-Play)
|
|
9800
|
+
* Same end state as visual mode (CSS flip), but pieces animate smoothly.
|
|
9801
|
+
* @private
|
|
9802
|
+
* @param {boolean} animate - Whether to animate the movement
|
|
9803
|
+
*/
|
|
9804
|
+
_flipAnimate(animate) {
|
|
9805
|
+
const boardElement = this.boardService.element;
|
|
9806
|
+
if (!boardElement) return;
|
|
9807
|
+
|
|
9808
|
+
const squares = this.boardService.getAllSquares();
|
|
9809
|
+
|
|
9810
|
+
// FIRST: Record current visual position of every piece
|
|
9811
|
+
const pieceRects = {};
|
|
9812
|
+
for (const [id, square] of Object.entries(squares)) {
|
|
9813
|
+
if (square.piece && square.piece.element) {
|
|
9814
|
+
pieceRects[id] = square.piece.element.getBoundingClientRect();
|
|
9815
|
+
}
|
|
9816
|
+
}
|
|
9817
|
+
|
|
9818
|
+
// LAST: Apply CSS flip (instant) - same as visual mode
|
|
9819
|
+
const isFlipped = this.coordinateService.getOrientation() === 'b';
|
|
9820
|
+
this._flipVisual(boardElement, isFlipped);
|
|
9821
|
+
|
|
9822
|
+
if (!animate || Object.keys(pieceRects).length === 0) return;
|
|
9823
|
+
|
|
9824
|
+
// INVERT + PLAY: Animate each piece from old position to new
|
|
9825
|
+
const duration = this.config.moveTime || 200;
|
|
9826
|
+
const easing = 'cubic-bezier(0.33, 1, 0.68, 1)';
|
|
9827
|
+
|
|
9828
|
+
for (const [id, oldRect] of Object.entries(pieceRects)) {
|
|
9829
|
+
const square = squares[id];
|
|
9830
|
+
if (!square || !square.piece || !square.piece.element) continue;
|
|
9831
|
+
|
|
9832
|
+
const piece = square.piece;
|
|
9833
|
+
const newRect = piece.element.getBoundingClientRect();
|
|
9834
|
+
const dx = oldRect.left - newRect.left;
|
|
9835
|
+
const dy = oldRect.top - newRect.top;
|
|
9836
|
+
|
|
9837
|
+
if (Math.abs(dx) < 1 && Math.abs(dy) < 1) continue;
|
|
9838
|
+
|
|
9839
|
+
if (piece.element.animate) {
|
|
9840
|
+
const anim = piece.element.animate([
|
|
9841
|
+
{ transform: `translate(${dx}px, ${dy}px)` },
|
|
9842
|
+
{ transform: 'translate(0, 0)' }
|
|
9843
|
+
], { duration, easing, fill: 'forwards' });
|
|
9844
|
+
anim.onfinish = () => {
|
|
9845
|
+
anim.cancel();
|
|
9846
|
+
if (piece.element) piece.element.style.transform = '';
|
|
9847
|
+
};
|
|
9848
|
+
} else {
|
|
9849
|
+
// setTimeout fallback for jsdom / older browsers
|
|
9850
|
+
piece.element.style.transform = `translate(${dx}px, ${dy}px)`;
|
|
9851
|
+
setTimeout(() => {
|
|
9852
|
+
if (!piece.element) return;
|
|
9853
|
+
piece.element.style.transition = `transform ${duration}ms`;
|
|
9854
|
+
piece.element.style.transform = 'translate(0, 0)';
|
|
9855
|
+
setTimeout(() => {
|
|
9856
|
+
if (!piece.element) return;
|
|
9857
|
+
piece.element.style.transition = '';
|
|
9858
|
+
piece.element.style.transform = '';
|
|
9859
|
+
}, duration);
|
|
9860
|
+
}, 0);
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
}
|
|
9864
|
+
|
|
9865
|
+
/**
|
|
9866
|
+
* Set the flip mode at runtime
|
|
9867
|
+
* @param {'visual'|'animate'|'none'} mode - The flip mode to use
|
|
9868
|
+
*/
|
|
9869
|
+
setFlipMode(mode) {
|
|
9870
|
+
const validModes = ['visual', 'animate', 'none'];
|
|
9871
|
+
if (!validModes.includes(mode)) {
|
|
9872
|
+
console.warn(`Invalid flip mode: ${mode}. Valid options: ${validModes.join(', ')}`);
|
|
9873
|
+
return;
|
|
9874
|
+
}
|
|
9875
|
+
this.config.flipMode = mode;
|
|
9876
|
+
}
|
|
9877
|
+
|
|
9878
|
+
/**
|
|
9879
|
+
* Get the current flip mode
|
|
9880
|
+
* @returns {string} Current flip mode
|
|
9881
|
+
*/
|
|
9882
|
+
getFlipMode() {
|
|
9883
|
+
return this.config.flipMode || 'visual';
|
|
9884
|
+
}
|
|
9885
|
+
|
|
9886
|
+
// --- MOVEMENT CONFIGURATION ---
|
|
9887
|
+
|
|
9888
|
+
/**
|
|
9889
|
+
* Set the movement style
|
|
9890
|
+
* @param {'slide'|'arc'|'hop'|'teleport'|'fade'} style - Movement style
|
|
9891
|
+
*/
|
|
9892
|
+
setMoveStyle(style) {
|
|
9893
|
+
const validStyles = ['slide', 'arc', 'hop', 'teleport', 'fade'];
|
|
9894
|
+
if (!validStyles.includes(style)) {
|
|
9895
|
+
console.warn(`Invalid move style: ${style}. Valid: ${validStyles.join(', ')}`);
|
|
9896
|
+
return;
|
|
9897
|
+
}
|
|
9898
|
+
this.config.moveStyle = style;
|
|
9899
|
+
}
|
|
9900
|
+
|
|
9901
|
+
/**
|
|
9902
|
+
* Get the current movement style
|
|
9903
|
+
* @returns {string} Current movement style
|
|
9904
|
+
*/
|
|
9905
|
+
getMoveStyle() {
|
|
9906
|
+
return this.config.moveStyle || 'slide';
|
|
9907
|
+
}
|
|
9908
|
+
|
|
9909
|
+
/**
|
|
9910
|
+
* Set the capture animation style
|
|
9911
|
+
* @param {'fade'|'shrink'|'instant'|'explode'} style - Capture style
|
|
9912
|
+
*/
|
|
9913
|
+
setCaptureStyle(style) {
|
|
9914
|
+
const validStyles = ['fade', 'shrink', 'instant', 'explode'];
|
|
9915
|
+
if (!validStyles.includes(style)) {
|
|
9916
|
+
console.warn(`Invalid capture style: ${style}. Valid: ${validStyles.join(', ')}`);
|
|
9917
|
+
return;
|
|
9918
|
+
}
|
|
9919
|
+
this.config.captureStyle = style;
|
|
9920
|
+
}
|
|
9921
|
+
|
|
9922
|
+
/**
|
|
9923
|
+
* Get the current capture style
|
|
9924
|
+
* @returns {string} Current capture style
|
|
9925
|
+
*/
|
|
9926
|
+
getCaptureStyle() {
|
|
9927
|
+
return this.config.captureStyle || 'fade';
|
|
9928
|
+
}
|
|
9929
|
+
|
|
9930
|
+
/**
|
|
9931
|
+
* Set the appearance animation style
|
|
9932
|
+
* @param {'fade'|'pulse'|'pop'|'drop'|'instant'} style - Appearance style
|
|
9933
|
+
*/
|
|
9934
|
+
setAppearanceStyle(style) {
|
|
9935
|
+
const validStyles = ['fade', 'pulse', 'pop', 'drop', 'instant'];
|
|
9936
|
+
if (!validStyles.includes(style)) {
|
|
9937
|
+
console.warn(`Invalid appearance style: ${style}. Valid: ${validStyles.join(', ')}`);
|
|
9938
|
+
return;
|
|
9939
|
+
}
|
|
9940
|
+
this.config.appearanceStyle = style;
|
|
9941
|
+
}
|
|
9942
|
+
|
|
9943
|
+
/**
|
|
9944
|
+
* Get the current appearance style
|
|
9945
|
+
* @returns {string} Current appearance style
|
|
9946
|
+
*/
|
|
9947
|
+
getAppearanceStyle() {
|
|
9948
|
+
return this.config.appearanceStyle || 'fade';
|
|
9949
|
+
}
|
|
9950
|
+
|
|
9951
|
+
/**
|
|
9952
|
+
* Set the landing effect
|
|
9953
|
+
* @param {'none'|'bounce'|'pulse'|'settle'} effect - Landing effect
|
|
9954
|
+
*/
|
|
9955
|
+
setLandingEffect(effect) {
|
|
9956
|
+
const validEffects = ['none', 'bounce', 'pulse', 'settle'];
|
|
9957
|
+
if (!validEffects.includes(effect)) {
|
|
9958
|
+
console.warn(`Invalid landing effect: ${effect}. Valid: ${validEffects.join(', ')}`);
|
|
9959
|
+
return;
|
|
9960
|
+
}
|
|
9961
|
+
this.config.landingEffect = effect;
|
|
9962
|
+
}
|
|
9963
|
+
|
|
9964
|
+
/**
|
|
9965
|
+
* Get the current landing effect
|
|
9966
|
+
* @returns {string} Current landing effect
|
|
9967
|
+
*/
|
|
9968
|
+
getLandingEffect() {
|
|
9969
|
+
return this.config.landingEffect || 'none';
|
|
8985
9970
|
}
|
|
9971
|
+
|
|
9972
|
+
/**
|
|
9973
|
+
* Set the movement duration
|
|
9974
|
+
* @param {number|string} duration - Duration in ms or preset name ('instant', 'veryFast', 'fast', 'normal', 'slow', 'verySlow')
|
|
9975
|
+
*/
|
|
9976
|
+
setMoveTime(duration) {
|
|
9977
|
+
const presets = { instant: 0, veryFast: 100, fast: 200, normal: 400, slow: 600, verySlow: 1000 };
|
|
9978
|
+
if (typeof duration === 'string' && presets[duration] !== undefined) {
|
|
9979
|
+
this.config.moveTime = presets[duration];
|
|
9980
|
+
} else if (typeof duration === 'number' && duration >= 0) {
|
|
9981
|
+
this.config.moveTime = duration;
|
|
9982
|
+
} else {
|
|
9983
|
+
console.warn(`Invalid move time: ${duration}`);
|
|
9984
|
+
}
|
|
9985
|
+
}
|
|
9986
|
+
|
|
9987
|
+
/**
|
|
9988
|
+
* Get the current movement duration
|
|
9989
|
+
* @returns {number} Duration in ms
|
|
9990
|
+
*/
|
|
9991
|
+
getMoveTime() {
|
|
9992
|
+
return this.config.moveTime;
|
|
9993
|
+
}
|
|
9994
|
+
|
|
9995
|
+
/**
|
|
9996
|
+
* Set the easing function for movements
|
|
9997
|
+
* @param {string} easing - CSS easing function
|
|
9998
|
+
*/
|
|
9999
|
+
setMoveEasing(easing) {
|
|
10000
|
+
const validEasings = ['ease', 'linear', 'ease-in', 'ease-out', 'ease-in-out'];
|
|
10001
|
+
if (!validEasings.includes(easing)) {
|
|
10002
|
+
console.warn(`Invalid easing: ${easing}. Valid: ${validEasings.join(', ')}`);
|
|
10003
|
+
return;
|
|
10004
|
+
}
|
|
10005
|
+
this.config.moveEasing = easing;
|
|
10006
|
+
}
|
|
10007
|
+
|
|
10008
|
+
/**
|
|
10009
|
+
* Configure multiple movement settings at once
|
|
10010
|
+
* @param {Object} options - Movement configuration
|
|
10011
|
+
* @param {string} [options.style] - Movement style
|
|
10012
|
+
* @param {string} [options.captureStyle] - Capture animation style
|
|
10013
|
+
* @param {string} [options.landingEffect] - Landing effect
|
|
10014
|
+
* @param {number|string} [options.duration] - Movement duration
|
|
10015
|
+
* @param {string} [options.easing] - Easing function
|
|
10016
|
+
* @param {number} [options.arcHeight] - Arc height for arc/hop styles (0-1)
|
|
10017
|
+
*/
|
|
10018
|
+
configureMovement(options) {
|
|
10019
|
+
if (options.style) this.setMoveStyle(options.style);
|
|
10020
|
+
if (options.captureStyle) this.setCaptureStyle(options.captureStyle);
|
|
10021
|
+
if (options.appearanceStyle) this.setAppearanceStyle(options.appearanceStyle);
|
|
10022
|
+
if (options.landingEffect) this.setLandingEffect(options.landingEffect);
|
|
10023
|
+
if (options.duration !== undefined) this.setMoveTime(options.duration);
|
|
10024
|
+
if (options.easing) this.setMoveEasing(options.easing);
|
|
10025
|
+
if (options.arcHeight !== undefined) {
|
|
10026
|
+
this.config.moveArcHeight = Math.max(0, Math.min(1, options.arcHeight));
|
|
10027
|
+
}
|
|
10028
|
+
}
|
|
10029
|
+
|
|
10030
|
+
/**
|
|
10031
|
+
* Get all movement configuration
|
|
10032
|
+
* @returns {Object} Current movement configuration
|
|
10033
|
+
*/
|
|
10034
|
+
getMovementConfig() {
|
|
10035
|
+
return {
|
|
10036
|
+
style: this.config.moveStyle || 'slide',
|
|
10037
|
+
captureStyle: this.config.captureStyle || 'fade',
|
|
10038
|
+
appearanceStyle: this.config.appearanceStyle || 'fade',
|
|
10039
|
+
landingEffect: this.config.landingEffect || 'none',
|
|
10040
|
+
duration: this.config.moveTime,
|
|
10041
|
+
easing: this.config.moveEasing || 'ease',
|
|
10042
|
+
arcHeight: this.config.moveArcHeight || 0.3
|
|
10043
|
+
};
|
|
10044
|
+
}
|
|
10045
|
+
|
|
8986
10046
|
/**
|
|
8987
10047
|
* Set the board orientation
|
|
8988
10048
|
* @param {'w'|'b'} color
|
|
8989
10049
|
* @param {Object} [opts]
|
|
8990
|
-
* @param {boolean} [opts.animate=true]
|
|
10050
|
+
* @param {boolean} [opts.animate=true] - Enable animation (for 'animate' mode)
|
|
10051
|
+
* @param {string} [opts.mode] - Override flip mode ('visual', 'animate', 'none')
|
|
8991
10052
|
*/
|
|
8992
10053
|
setOrientation(color, opts = {}) {
|
|
8993
10054
|
if (this.validationService.isValidOrientation(color)) {
|
|
8994
|
-
this.coordinateService.
|
|
8995
|
-
if (
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
10055
|
+
const currentOrientation = this.coordinateService.getOrientation();
|
|
10056
|
+
if (currentOrientation !== color) {
|
|
10057
|
+
this.coordinateService.setOrientation(color);
|
|
10058
|
+
|
|
10059
|
+
const flipMode = opts.mode || this.config.flipMode || 'visual';
|
|
10060
|
+
const boardElement = this.boardService.element;
|
|
10061
|
+
const isFlipped = color === 'b';
|
|
10062
|
+
|
|
10063
|
+
switch (flipMode) {
|
|
10064
|
+
case 'visual':
|
|
10065
|
+
this._flipVisual(boardElement, isFlipped);
|
|
10066
|
+
break;
|
|
10067
|
+
case 'animate':
|
|
10068
|
+
this._flipAnimate(opts.animate !== false);
|
|
10069
|
+
break;
|
|
10070
|
+
case 'none':
|
|
10071
|
+
// No visual change
|
|
10072
|
+
break;
|
|
10073
|
+
default:
|
|
10074
|
+
this._flipVisual(boardElement, isFlipped);
|
|
10075
|
+
}
|
|
10076
|
+
}
|
|
8999
10077
|
}
|
|
9000
10078
|
return this.coordinateService.getOrientation();
|
|
9001
10079
|
}
|
|
@@ -9113,7 +10191,40 @@ var ChessboardLib = (function (exports) {
|
|
|
9113
10191
|
/**
|
|
9114
10192
|
* Destroy the board and cleanup
|
|
9115
10193
|
*/
|
|
9116
|
-
destroy() {
|
|
10194
|
+
destroy() {
|
|
10195
|
+
this._destroyed = true;
|
|
10196
|
+
|
|
10197
|
+
// Remove all event listeners
|
|
10198
|
+
if (this.eventService) {
|
|
10199
|
+
this.eventService.removeAllListeners();
|
|
10200
|
+
this.eventService.destroy();
|
|
10201
|
+
}
|
|
10202
|
+
|
|
10203
|
+
// Clear all timeouts
|
|
10204
|
+
if (this._updateTimeout) {
|
|
10205
|
+
clearTimeout(this._updateTimeout);
|
|
10206
|
+
this._updateTimeout = null;
|
|
10207
|
+
}
|
|
10208
|
+
|
|
10209
|
+
// Clear all animation timeouts
|
|
10210
|
+
if (this._animationTimeouts) {
|
|
10211
|
+
this._animationTimeouts.forEach(tid => clearTimeout(tid));
|
|
10212
|
+
this._animationTimeouts = [];
|
|
10213
|
+
}
|
|
10214
|
+
|
|
10215
|
+
// Destroy services
|
|
10216
|
+
if (this.moveService) this.moveService.destroy();
|
|
10217
|
+
if (this.animationService && this.animationService.destroy) this.animationService.destroy();
|
|
10218
|
+
if (this.pieceService && this.pieceService.destroy) this.pieceService.destroy();
|
|
10219
|
+
if (this.boardService && this.boardService.destroy) this.boardService.destroy();
|
|
10220
|
+
if (this.positionService && this.positionService.destroy) this.positionService.destroy();
|
|
10221
|
+
if (this.coordinateService && this.coordinateService.destroy) this.coordinateService.destroy();
|
|
10222
|
+
if (this.validationService) this.validationService.destroy();
|
|
10223
|
+
if (this.config && this.config.destroy) this.config.destroy();
|
|
10224
|
+
|
|
10225
|
+
// Clear references
|
|
10226
|
+
this._cleanup();
|
|
10227
|
+
}
|
|
9117
10228
|
/**
|
|
9118
10229
|
* Rebuild the board
|
|
9119
10230
|
*/
|
|
@@ -9129,7 +10240,11 @@ var ChessboardLib = (function (exports) {
|
|
|
9129
10240
|
* Set new config
|
|
9130
10241
|
* @param {Object} newConfig
|
|
9131
10242
|
*/
|
|
9132
|
-
setConfig(newConfig) {
|
|
10243
|
+
setConfig(newConfig) {
|
|
10244
|
+
if (this.config && typeof this.config.update === 'function') {
|
|
10245
|
+
this.config.update(newConfig);
|
|
10246
|
+
}
|
|
10247
|
+
}
|
|
9133
10248
|
|
|
9134
10249
|
// --- ALIASES/DEPRECATED ---
|
|
9135
10250
|
/**
|
|
@@ -9164,17 +10279,10 @@ var ChessboardLib = (function (exports) {
|
|
|
9164
10279
|
}
|
|
9165
10280
|
|
|
9166
10281
|
/**
|
|
9167
|
-
* Gets the current position
|
|
9168
|
-
* @
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
return this.positionService.getPosition();
|
|
9172
|
-
}
|
|
9173
|
-
|
|
9174
|
-
/**
|
|
9175
|
-
* Sets a new position
|
|
9176
|
-
* @param {string|Object} position - New position
|
|
9177
|
-
* @param {boolean} [animate=true] - Whether to animate
|
|
10282
|
+
* Gets or sets the current position
|
|
10283
|
+
* @param {string|Object} [position] - Position to set (FEN or object). If omitted, returns current position.
|
|
10284
|
+
* @param {boolean} [animate=true] - Whether to animate when setting
|
|
10285
|
+
* @returns {Object} Current position object (when getting)
|
|
9178
10286
|
*/
|
|
9179
10287
|
position(position, animate = true) {
|
|
9180
10288
|
if (position === undefined) {
|
|
@@ -9437,34 +10545,21 @@ var ChessboardLib = (function (exports) {
|
|
|
9437
10545
|
// Ensure all public API methods from README are present and routed
|
|
9438
10546
|
insert(square, piece) { return this.putPiece(piece, square); }
|
|
9439
10547
|
get(square) { return this.getPiece(square); }
|
|
9440
|
-
position(
|
|
9441
|
-
if (color) this.setOrientation(color);
|
|
9442
|
-
return this.setPosition(position);
|
|
9443
|
-
}
|
|
9444
|
-
flip(animation = true) { return this.flipBoard({ animate: animation }); }
|
|
10548
|
+
// Note: position() is defined above at line ~1684 with getter/setter functionality
|
|
9445
10549
|
build() { return this._initialize(); }
|
|
9446
10550
|
resize(value) { return this.resizeBoard(value); }
|
|
9447
|
-
destroy() { return this._cleanup(); }
|
|
9448
10551
|
piece(square) { return this.getPiece(square); }
|
|
9449
|
-
highlight(square) { return true; }
|
|
9450
|
-
dehighlight(square) { return true; }
|
|
9451
|
-
turn() { return this.positionService.getGame().turn(); }
|
|
9452
10552
|
ascii() { return this.positionService.getGame().ascii(); }
|
|
9453
10553
|
board() { return this.positionService.getGame().board(); }
|
|
9454
10554
|
getCastlingRights(color) { return this.positionService.getGame().getCastlingRights(color); }
|
|
9455
10555
|
getComment() { return this.positionService.getGame().getComment(); }
|
|
9456
10556
|
getComments() { return this.positionService.getGame().getComments(); }
|
|
9457
|
-
history(options = {}) { return this.positionService.getGame().history(options); }
|
|
9458
10557
|
lastMove() { return this.positionService.getGame().lastMove(); }
|
|
9459
10558
|
moveNumber() { return this.positionService.getGame().moveNumber(); }
|
|
9460
10559
|
moves(options = {}) { return this.positionService.getGame().moves(options); }
|
|
9461
|
-
pgn(options = {}) { return this.positionService.getGame().pgn(options); }
|
|
9462
10560
|
squareColor(squareId) { return this.boardService.getSquare(squareId).isWhite() ? 'light' : 'dark'; }
|
|
9463
|
-
isCheckmate() { return this.positionService.getGame().isCheckmate(); }
|
|
9464
|
-
isDraw() { return this.positionService.getGame().isDraw(); }
|
|
9465
10561
|
isDrawByFiftyMoves() { return this.positionService.getGame().isDrawByFiftyMoves(); }
|
|
9466
10562
|
isInsufficientMaterial() { return this.positionService.getGame().isInsufficientMaterial(); }
|
|
9467
|
-
isGameOver() { return this.positionService.getGame().isGameOver(); }
|
|
9468
10563
|
isStalemate() { return this.positionService.getGame().isStalemate(); }
|
|
9469
10564
|
isThreefoldRepetition() { return this.positionService.getGame().isThreefoldRepetition(); }
|
|
9470
10565
|
load(fen, options = {}, animation = true) { return this.setPosition(fen, { ...options, animate: animation }); }
|