@alepot55/chessboardjs 2.2.0 → 2.3.2
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/.eslintrc.json +227 -0
- package/.github/instructions/copilot-instuctions.md +1671 -0
- package/README.md +127 -403
- package/assets/themes/alepot/theme.json +42 -0
- package/assets/themes/default/theme.json +42 -0
- package/chessboard.bundle.js +782 -119
- package/config/jest.config.js +15 -0
- package/config/rollup.config.js +35 -0
- package/dist/chessboard.cjs.js +10476 -0
- package/dist/chessboard.css +197 -0
- package/dist/chessboard.esm.js +10407 -0
- package/dist/chessboard.iife.js +10481 -0
- package/dist/chessboard.umd.js +10482 -0
- package/jest.config.js +2 -7
- package/package.json +18 -3
- package/rollup.config.js +2 -11
- package/{chessboard.move.js → src/components/Move.js} +3 -3
- package/src/components/Piece.js +273 -0
- package/{chessboard.square.js → src/components/Square.js} +60 -7
- package/src/constants/index.js +15 -0
- package/src/constants/positions.js +62 -0
- package/src/core/Chessboard.js +1930 -0
- package/src/core/ChessboardConfig.js +458 -0
- package/src/core/ChessboardFactory.js +385 -0
- package/src/core/index.js +141 -0
- package/src/errors/ChessboardError.js +133 -0
- package/src/errors/index.js +15 -0
- package/src/errors/messages.js +189 -0
- package/src/index.js +103 -0
- package/src/services/AnimationService.js +180 -0
- package/src/services/BoardService.js +156 -0
- package/src/services/CoordinateService.js +355 -0
- package/src/services/EventService.js +807 -0
- package/src/services/MoveService.js +594 -0
- package/src/services/PieceService.js +303 -0
- package/src/services/PositionService.js +237 -0
- package/src/services/ValidationService.js +673 -0
- package/src/services/index.js +14 -0
- package/src/styles/animations.css +46 -0
- package/{chessboard.css → src/styles/board.css} +3 -0
- package/src/styles/index.css +4 -0
- package/src/styles/pieces.css +66 -0
- package/src/utils/animations.js +37 -0
- package/{chess.js → src/utils/chess.js} +16 -16
- package/src/utils/coordinates.js +62 -0
- package/src/utils/cross-browser.js +150 -0
- package/src/utils/logger.js +422 -0
- package/src/utils/performance.js +311 -0
- package/src/utils/validation.js +458 -0
- package/tests/unit/chessboard-config-animations.test.js +106 -0
- package/tests/unit/chessboard-robust.test.js +163 -0
- package/tests/unit/chessboard.test.js +183 -0
- package/chessboard.config.js +0 -147
- package/chessboard.js +0 -981
- package/chessboard.piece.js +0 -115
- package/test/chessboard.test.js +0 -128
- /package/{alepot_theme → assets/themes/alepot}/bb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/bw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/kb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/kw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/nb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/nw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/pb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/pw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/qb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/qw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/rb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/rw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/bb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/bw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/kb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/kw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/nb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/nw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/pb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/pw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/qb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/qw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/rb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/rw.svg +0 -0
- /package/{.babelrc → config/.babelrc} +0 -0
package/jest.config.js
CHANGED
package/package.json
CHANGED
|
@@ -3,11 +3,25 @@
|
|
|
3
3
|
"chess.js": "^1.0.0"
|
|
4
4
|
},
|
|
5
5
|
"name": "@alepot55/chessboardjs",
|
|
6
|
-
"version": "2.2
|
|
7
|
-
"main": "
|
|
6
|
+
"version": "2.3.2",
|
|
7
|
+
"main": "src/index.js",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"scripts": {
|
|
10
|
-
"
|
|
10
|
+
"dev": "rollup -c config/rollup.config.js --watch",
|
|
11
|
+
"build": "rollup -c config/rollup.config.js",
|
|
12
|
+
"build:all": "npm run build:esm && npm run build:cjs && npm run build:umd && npm run build:iife",
|
|
13
|
+
"build:esm": "rollup -c config/rollup.config.js --format esm",
|
|
14
|
+
"build:cjs": "rollup -c config/rollup.config.js --format cjs",
|
|
15
|
+
"build:umd": "rollup -c config/rollup.config.js --format umd",
|
|
16
|
+
"build:iife": "rollup -c config/rollup.config.js --format iife",
|
|
17
|
+
"test": "jest --config config/jest.config.js",
|
|
18
|
+
"test:watch": "jest --config config/jest.config.js --watch",
|
|
19
|
+
"test:coverage": "jest --config config/jest.config.js --coverage",
|
|
20
|
+
"lint": "echo 'ESLint not configured yet'",
|
|
21
|
+
"lint:fix": "echo 'ESLint not configured yet'",
|
|
22
|
+
"docs": "echo 'JSDoc not configured yet'",
|
|
23
|
+
"examples": "echo 'Example server not configured yet'",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
11
25
|
},
|
|
12
26
|
"author": "alepot55",
|
|
13
27
|
"license": "ISC",
|
|
@@ -28,6 +42,7 @@
|
|
|
28
42
|
"@babel/core": "^7.26.8",
|
|
29
43
|
"@babel/preset-env": "^7.26.8",
|
|
30
44
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
45
|
+
"@rollup/plugin-replace": "^6.0.2",
|
|
31
46
|
"babel-jest": "^29.7.0",
|
|
32
47
|
"jest": "^29.7.0",
|
|
33
48
|
"jest-environment-jsdom": "^29.7.0",
|
package/rollup.config.js
CHANGED
|
@@ -1,11 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
input: 'chessboard.js', // entry point per la libreria
|
|
5
|
-
output: {
|
|
6
|
-
file: 'dist/chessboard.bundle.js', // percorso relativo per evitare errori di permessi
|
|
7
|
-
format: 'iife', // oppure "umd" per UMD
|
|
8
|
-
name: 'Chessboard'
|
|
9
|
-
},
|
|
10
|
-
plugins: [resolve()]
|
|
11
|
-
};
|
|
1
|
+
// Re-export the configuration from the config directory
|
|
2
|
+
export { default } from './config/rollup.config.js';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import Piece from "./
|
|
2
|
-
import Square from "./
|
|
1
|
+
import Piece from "./Piece.js";
|
|
2
|
+
import Square from "./Square.js";
|
|
3
3
|
|
|
4
4
|
class Move {
|
|
5
5
|
|
|
6
6
|
constructor(from, to, promotion = null, check = false) {
|
|
7
|
-
this.piece = from.getPiece();
|
|
7
|
+
this.piece = from ? from.getPiece() : null;
|
|
8
8
|
this.from = from;
|
|
9
9
|
this.to = to;
|
|
10
10
|
this.promotion = promotion;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
class Piece {
|
|
2
|
+
constructor(color, type, src, opacity = 1) {
|
|
3
|
+
this.color = color;
|
|
4
|
+
this.type = type;
|
|
5
|
+
this.id = this.getId();
|
|
6
|
+
this.src = src;
|
|
7
|
+
this.element = this.createElement(src, opacity);
|
|
8
|
+
console.debug(`[Piece] Constructed: ${this.id}`);
|
|
9
|
+
this.check();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getId() { return this.type + this.color }
|
|
13
|
+
|
|
14
|
+
createElement(src, opacity = 1) {
|
|
15
|
+
let element = document.createElement("img");
|
|
16
|
+
element.classList.add("piece");
|
|
17
|
+
element.id = this.id;
|
|
18
|
+
element.src = src || this.src;
|
|
19
|
+
element.style.opacity = opacity;
|
|
20
|
+
|
|
21
|
+
// Ensure the image loads properly
|
|
22
|
+
element.onerror = () => {
|
|
23
|
+
console.warn('Failed to load piece image:', element.src);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return element;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
visible() { if (this.element) { this.element.style.opacity = 1; console.debug(`[Piece] visible: ${this.id}`); } }
|
|
30
|
+
|
|
31
|
+
invisible() { if (this.element) { this.element.style.opacity = 0; console.debug(`[Piece] invisible: ${this.id}`); } }
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Updates the piece image source
|
|
35
|
+
* @param {string} newSrc - New image source
|
|
36
|
+
*/
|
|
37
|
+
updateSrc(newSrc) {
|
|
38
|
+
this.src = newSrc;
|
|
39
|
+
if (this.element) {
|
|
40
|
+
this.element.src = newSrc;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Transforms the piece to a new type with smooth animation
|
|
46
|
+
* @param {string} newType - New piece type
|
|
47
|
+
* @param {string} newSrc - New image source
|
|
48
|
+
* @param {number} [duration=200] - Animation duration in milliseconds
|
|
49
|
+
* @param {Function} [callback] - Callback when transformation is complete
|
|
50
|
+
*/
|
|
51
|
+
transformTo(newType, newSrc, duration = 200, callback = null) {
|
|
52
|
+
if (!this.element) { console.debug(`[Piece] transformTo: ${this.id} - element is null`); if (callback) callback(); return; }
|
|
53
|
+
const element = this.element;
|
|
54
|
+
const oldSrc = element.src;
|
|
55
|
+
|
|
56
|
+
// Add transformation class to disable all transitions temporarily
|
|
57
|
+
element.classList.add('transforming');
|
|
58
|
+
|
|
59
|
+
// Create a smooth scale animation for the transformation
|
|
60
|
+
const scaleDown = [
|
|
61
|
+
{ transform: 'scale(1)', opacity: '1' },
|
|
62
|
+
{ transform: 'scale(0.8)', opacity: '0.7' }
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const scaleUp = [
|
|
66
|
+
{ transform: 'scale(0.8)', opacity: '0.7' },
|
|
67
|
+
{ transform: 'scale(1)', opacity: '1' }
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const halfDuration = duration / 2;
|
|
71
|
+
|
|
72
|
+
// First animation: scale down
|
|
73
|
+
if (element.animate) {
|
|
74
|
+
const scaleDownAnimation = element.animate(scaleDown, {
|
|
75
|
+
duration: halfDuration,
|
|
76
|
+
easing: 'ease-in',
|
|
77
|
+
fill: 'forwards'
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
scaleDownAnimation.onfinish = () => {
|
|
81
|
+
if (!this.element) { console.debug(`[Piece] transformTo.scaleDown.onfinish: ${this.id} - element is null`); if (callback) callback(); return; }
|
|
82
|
+
// Change the piece type and source at the smallest scale
|
|
83
|
+
this.type = newType;
|
|
84
|
+
this.id = this.getId();
|
|
85
|
+
this.src = newSrc;
|
|
86
|
+
element.src = newSrc;
|
|
87
|
+
element.id = this.id;
|
|
88
|
+
|
|
89
|
+
// Second animation: scale back up
|
|
90
|
+
const scaleUpAnimation = element.animate(scaleUp, {
|
|
91
|
+
duration: halfDuration,
|
|
92
|
+
easing: 'ease-out',
|
|
93
|
+
fill: 'forwards'
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
scaleUpAnimation.onfinish = () => {
|
|
97
|
+
if (!this.element) { console.debug(`[Piece] transformTo.scaleUp.onfinish: ${this.id} - element is null`); if (callback) callback(); return; }
|
|
98
|
+
// Reset transform and remove transformation class
|
|
99
|
+
element.style.transform = '';
|
|
100
|
+
element.style.opacity = '';
|
|
101
|
+
element.classList.remove('transforming');
|
|
102
|
+
|
|
103
|
+
// Add a subtle bounce effect
|
|
104
|
+
element.classList.add('transform-complete');
|
|
105
|
+
|
|
106
|
+
// Remove bounce class after animation
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
if (!this.element) return;
|
|
109
|
+
element.classList.remove('transform-complete');
|
|
110
|
+
}, 400);
|
|
111
|
+
|
|
112
|
+
console.debug(`[Piece] transformTo complete: ${this.id}`);
|
|
113
|
+
if (callback) callback();
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
} else {
|
|
117
|
+
// Fallback for browsers without Web Animations API
|
|
118
|
+
element.style.transition = `transform ${halfDuration}ms ease-in, opacity ${halfDuration}ms ease-in`;
|
|
119
|
+
element.style.transform = 'scale(0.8)';
|
|
120
|
+
element.style.opacity = '0.7';
|
|
121
|
+
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
if (!this.element) { console.debug(`[Piece] transformTo (fallback): ${this.id} - element is null`); if (callback) callback(); return; }
|
|
124
|
+
// Change the piece
|
|
125
|
+
this.type = newType;
|
|
126
|
+
this.id = this.getId();
|
|
127
|
+
this.src = newSrc;
|
|
128
|
+
element.src = newSrc;
|
|
129
|
+
element.id = this.id;
|
|
130
|
+
|
|
131
|
+
// Scale back up
|
|
132
|
+
element.style.transition = `transform ${halfDuration}ms ease-out, opacity ${halfDuration}ms ease-out`;
|
|
133
|
+
element.style.transform = 'scale(1)';
|
|
134
|
+
element.style.opacity = '1';
|
|
135
|
+
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
if (!this.element) { console.debug(`[Piece] transformTo (fallback, cleanup): ${this.id} - element is null`); if (callback) callback(); return; }
|
|
138
|
+
// Clean up
|
|
139
|
+
element.style.transition = '';
|
|
140
|
+
element.style.transform = '';
|
|
141
|
+
element.style.opacity = '';
|
|
142
|
+
element.classList.remove('transforming');
|
|
143
|
+
|
|
144
|
+
// Add bounce effect
|
|
145
|
+
element.classList.add('transform-complete');
|
|
146
|
+
setTimeout(() => {
|
|
147
|
+
if (!this.element) return;
|
|
148
|
+
element.classList.remove('transform-complete');
|
|
149
|
+
}, 400);
|
|
150
|
+
|
|
151
|
+
console.debug(`[Piece] transformTo complete (fallback): ${this.id}`);
|
|
152
|
+
if (callback) callback();
|
|
153
|
+
}, halfDuration);
|
|
154
|
+
}, halfDuration);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fadeIn(duration, speed, transition_f, callback) {
|
|
159
|
+
let start = performance.now();
|
|
160
|
+
let opacity = 0;
|
|
161
|
+
let piece = this;
|
|
162
|
+
let fade = function () {
|
|
163
|
+
if (!piece.element) { console.debug(`[Piece] fadeIn: ${piece.id} - element is null`); if (callback) callback(); return; }
|
|
164
|
+
let elapsed = performance.now() - start;
|
|
165
|
+
opacity = transition_f(elapsed, duration, speed);
|
|
166
|
+
piece.element.style.opacity = opacity;
|
|
167
|
+
if (elapsed < duration) {
|
|
168
|
+
requestAnimationFrame(fade);
|
|
169
|
+
} else {
|
|
170
|
+
if (!piece.element) { console.debug(`[Piece] fadeIn: ${piece.id} - element is null (end)`); if (callback) callback(); return; }
|
|
171
|
+
piece.element.style.opacity = 1;
|
|
172
|
+
console.debug(`[Piece] fadeIn complete: ${piece.id}`);
|
|
173
|
+
if (callback) callback();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
fade();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fadeOut(duration, speed, transition_f, callback) {
|
|
180
|
+
let start = performance.now();
|
|
181
|
+
let opacity = 1;
|
|
182
|
+
let piece = this;
|
|
183
|
+
let fade = function () {
|
|
184
|
+
if (!piece.element) { console.debug(`[Piece] fadeOut: ${piece.id} - element is null`); if (callback) callback(); return; }
|
|
185
|
+
let elapsed = performance.now() - start;
|
|
186
|
+
opacity = 1 - transition_f(elapsed, duration, speed);
|
|
187
|
+
piece.element.style.opacity = opacity;
|
|
188
|
+
if (elapsed < duration) {
|
|
189
|
+
requestAnimationFrame(fade);
|
|
190
|
+
} else {
|
|
191
|
+
if (!piece.element) { console.debug(`[Piece] fadeOut: ${piece.id} - element is null (end)`); if (callback) callback(); return; }
|
|
192
|
+
piece.element.style.opacity = 0;
|
|
193
|
+
console.debug(`[Piece] fadeOut complete: ${piece.id}`);
|
|
194
|
+
if (callback) callback();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
fade();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
setDrag(f) {
|
|
201
|
+
if (!this.element) { console.debug(`[Piece] setDrag: ${this.id} - element is null`); return; }
|
|
202
|
+
this.element.ondragstart = (e) => { e.preventDefault() };
|
|
203
|
+
this.element.onmousedown = f;
|
|
204
|
+
console.debug(`[Piece] setDrag: ${this.id}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
destroy() {
|
|
208
|
+
console.debug(`[Piece] Destroy: ${this.id}`);
|
|
209
|
+
// Remove all event listeners
|
|
210
|
+
if (this.element) {
|
|
211
|
+
this.element.onmousedown = null;
|
|
212
|
+
this.element.ondragstart = null;
|
|
213
|
+
|
|
214
|
+
// Remove from DOM
|
|
215
|
+
if (this.element.parentNode) {
|
|
216
|
+
this.element.parentNode.removeChild(this.element);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Clear references
|
|
220
|
+
this.element = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
translate(to, duration, transition_f, speed, callback = null) {
|
|
225
|
+
if (!this.element) { console.debug(`[Piece] translate: ${this.id} - element is null`); if (callback) callback(); return; }
|
|
226
|
+
let sourceRect = this.element.getBoundingClientRect();
|
|
227
|
+
let targetRect = to.getBoundingClientRect();
|
|
228
|
+
let x_start = sourceRect.left + sourceRect.width / 2;
|
|
229
|
+
let y_start = sourceRect.top + sourceRect.height / 2;
|
|
230
|
+
let x_end = targetRect.left + targetRect.width / 2;
|
|
231
|
+
let y_end = targetRect.top + targetRect.height / 2;
|
|
232
|
+
let dx = x_end - x_start;
|
|
233
|
+
let dy = y_end - y_start;
|
|
234
|
+
|
|
235
|
+
let keyframes = [
|
|
236
|
+
{ transform: 'translate(0, 0)' },
|
|
237
|
+
{ transform: `translate(${dx}px, ${dy}px)` }
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
if (this.element.animate) {
|
|
241
|
+
let animation = this.element.animate(keyframes, {
|
|
242
|
+
duration: duration,
|
|
243
|
+
easing: 'ease',
|
|
244
|
+
fill: 'none'
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
animation.onfinish = () => {
|
|
248
|
+
if (!this.element) { console.debug(`[Piece] translate.onfinish: ${this.id} - element is null`); if (callback) callback(); return; }
|
|
249
|
+
if (callback) callback();
|
|
250
|
+
if (this.element) this.element.style = '';
|
|
251
|
+
console.debug(`[Piece] translate complete: ${this.id}`);
|
|
252
|
+
};
|
|
253
|
+
} else {
|
|
254
|
+
this.element.style.transition = `transform ${duration}ms ease`;
|
|
255
|
+
this.element.style.transform = `translate(${dx}px, ${dy}px)`;
|
|
256
|
+
if (callback) callback();
|
|
257
|
+
if (this.element) this.element.style = '';
|
|
258
|
+
console.debug(`[Piece] translate complete (no animate): ${this.id}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
check() {
|
|
263
|
+
if (['p', 'r', 'n', 'b', 'q', 'k'].indexOf(this.type) === -1) {
|
|
264
|
+
throw new Error("Invalid piece type");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (['w', 'b'].indexOf(this.color) === -1) {
|
|
268
|
+
throw new Error("Invalid piece color");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export default Piece;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Piece from "./
|
|
1
|
+
import Piece from "./Piece.js";
|
|
2
2
|
|
|
3
3
|
class Square {
|
|
4
4
|
|
|
@@ -54,11 +54,51 @@ class Square {
|
|
|
54
54
|
return this.element.getBoundingClientRect();
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
removePiece() {
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
removePiece(preserve = false) {
|
|
58
|
+
if (!this.piece) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
// Only destroy the piece if not preserving (i.e., not moving)
|
|
62
|
+
if (!preserve && typeof this.piece.destroy === 'function') {
|
|
63
|
+
this.piece.destroy();
|
|
64
|
+
}
|
|
60
65
|
this.piece = null;
|
|
61
|
-
return
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Forcefully removes all pieces from this square
|
|
71
|
+
*/
|
|
72
|
+
forceRemoveAllPieces() {
|
|
73
|
+
// Best practice: destroy the piece object if present
|
|
74
|
+
if (this.piece && typeof this.piece.destroy === 'function') {
|
|
75
|
+
this.piece.destroy();
|
|
76
|
+
this.piece = null;
|
|
77
|
+
}
|
|
78
|
+
// Remove any orphaned img.piece elements from the DOM
|
|
79
|
+
const pieceElements = this.element.querySelectorAll('img.piece');
|
|
80
|
+
pieceElements.forEach(element => {
|
|
81
|
+
if (element.parentNode === this.element) {
|
|
82
|
+
this.element.removeChild(element);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Replaces the current piece with a new one efficiently
|
|
89
|
+
* @param {Piece} newPiece - The new piece to place
|
|
90
|
+
*/
|
|
91
|
+
replacePiece(newPiece) {
|
|
92
|
+
// If there's an existing piece, remove it first
|
|
93
|
+
if (this.piece) {
|
|
94
|
+
this.removePiece();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Add the new piece
|
|
98
|
+
this.putPiece(newPiece);
|
|
99
|
+
|
|
100
|
+
// Ensure the piece is properly displayed
|
|
101
|
+
newPiece.element.style.opacity = '1';
|
|
62
102
|
}
|
|
63
103
|
|
|
64
104
|
addEventListener(event, callback) {
|
|
@@ -66,8 +106,14 @@ class Square {
|
|
|
66
106
|
}
|
|
67
107
|
|
|
68
108
|
putPiece(piece) {
|
|
109
|
+
// If there's already a piece, remove it first, but preserve if moving
|
|
110
|
+
if (this.piece) {
|
|
111
|
+
this.removePiece(true);
|
|
112
|
+
}
|
|
69
113
|
this.piece = piece;
|
|
70
|
-
|
|
114
|
+
if (piece && piece.element) {
|
|
115
|
+
this.element.appendChild(piece.element);
|
|
116
|
+
}
|
|
71
117
|
}
|
|
72
118
|
|
|
73
119
|
putHint(catchable) {
|
|
@@ -150,6 +196,10 @@ class Square {
|
|
|
150
196
|
|
|
151
197
|
}
|
|
152
198
|
|
|
199
|
+
hasPromotion() {
|
|
200
|
+
return this.element.querySelector('.choice') !== null;
|
|
201
|
+
}
|
|
202
|
+
|
|
153
203
|
removePromotion() {
|
|
154
204
|
let choice = this.element.querySelector('.choice');
|
|
155
205
|
if (choice) {
|
|
@@ -159,7 +209,10 @@ class Square {
|
|
|
159
209
|
}
|
|
160
210
|
|
|
161
211
|
destroy() {
|
|
212
|
+
// Remove all piece DOM nodes and clear reference
|
|
213
|
+
this.forceRemoveAllPieces();
|
|
162
214
|
this.element.remove();
|
|
215
|
+
this.piece = null;
|
|
163
216
|
}
|
|
164
217
|
|
|
165
218
|
hasPiece() {
|
|
@@ -177,7 +230,7 @@ class Square {
|
|
|
177
230
|
if (this.col < 1 || this.col > 8) {
|
|
178
231
|
throw new Error("Invalid square: col is out of bounds");
|
|
179
232
|
}
|
|
180
|
-
|
|
233
|
+
|
|
181
234
|
}
|
|
182
235
|
}
|
|
183
236
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants module exports
|
|
3
|
+
* @module constants
|
|
4
|
+
* @since 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
STANDARD_POSITIONS,
|
|
9
|
+
DEFAULT_STARTING_POSITION,
|
|
10
|
+
PIECE_TYPES,
|
|
11
|
+
PIECE_COLORS,
|
|
12
|
+
PROMOTION_PIECES,
|
|
13
|
+
BOARD_LETTERS,
|
|
14
|
+
BOARD_SIZE
|
|
15
|
+
} from './positions.js';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard chess positions and FEN constants
|
|
3
|
+
* @module constants/positions
|
|
4
|
+
* @since 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Standard chess positions used throughout the application
|
|
9
|
+
* @type {Object.<string, string>}
|
|
10
|
+
* @readonly
|
|
11
|
+
*/
|
|
12
|
+
export const STANDARD_POSITIONS = {
|
|
13
|
+
'start': 'start',
|
|
14
|
+
'default': 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
|
|
15
|
+
'empty': '8/8/8/8/8/8/8/8 w - - 0 1'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Default starting position FEN string
|
|
20
|
+
* @type {string}
|
|
21
|
+
* @readonly
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_STARTING_POSITION = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Chess piece types
|
|
27
|
+
* @type {string[]}
|
|
28
|
+
* @readonly
|
|
29
|
+
*/
|
|
30
|
+
export const PIECE_TYPES = ['p', 'r', 'n', 'b', 'q', 'k'];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Chess piece colors
|
|
34
|
+
* @type {string[]}
|
|
35
|
+
* @readonly
|
|
36
|
+
*/
|
|
37
|
+
export const PIECE_COLORS = ['w', 'b'];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Valid promotion pieces
|
|
41
|
+
* @type {string[]}
|
|
42
|
+
* @readonly
|
|
43
|
+
*/
|
|
44
|
+
export const PROMOTION_PIECES = ['q', 'r', 'b', 'n'];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Board coordinates letters
|
|
48
|
+
* @type {string}
|
|
49
|
+
* @readonly
|
|
50
|
+
*/
|
|
51
|
+
export const BOARD_LETTERS = 'abcdefgh';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Board size constants
|
|
55
|
+
* @type {Object}
|
|
56
|
+
* @readonly
|
|
57
|
+
*/
|
|
58
|
+
export const BOARD_SIZE = {
|
|
59
|
+
ROWS: 8,
|
|
60
|
+
COLS: 8,
|
|
61
|
+
TOTAL_SQUARES: 64
|
|
62
|
+
};
|