@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.
Files changed (81) hide show
  1. package/.eslintrc.json +227 -0
  2. package/.github/instructions/copilot-instuctions.md +1671 -0
  3. package/README.md +127 -403
  4. package/assets/themes/alepot/theme.json +42 -0
  5. package/assets/themes/default/theme.json +42 -0
  6. package/chessboard.bundle.js +782 -119
  7. package/config/jest.config.js +15 -0
  8. package/config/rollup.config.js +35 -0
  9. package/dist/chessboard.cjs.js +10476 -0
  10. package/dist/chessboard.css +197 -0
  11. package/dist/chessboard.esm.js +10407 -0
  12. package/dist/chessboard.iife.js +10481 -0
  13. package/dist/chessboard.umd.js +10482 -0
  14. package/jest.config.js +2 -7
  15. package/package.json +18 -3
  16. package/rollup.config.js +2 -11
  17. package/{chessboard.move.js → src/components/Move.js} +3 -3
  18. package/src/components/Piece.js +273 -0
  19. package/{chessboard.square.js → src/components/Square.js} +60 -7
  20. package/src/constants/index.js +15 -0
  21. package/src/constants/positions.js +62 -0
  22. package/src/core/Chessboard.js +1930 -0
  23. package/src/core/ChessboardConfig.js +458 -0
  24. package/src/core/ChessboardFactory.js +385 -0
  25. package/src/core/index.js +141 -0
  26. package/src/errors/ChessboardError.js +133 -0
  27. package/src/errors/index.js +15 -0
  28. package/src/errors/messages.js +189 -0
  29. package/src/index.js +103 -0
  30. package/src/services/AnimationService.js +180 -0
  31. package/src/services/BoardService.js +156 -0
  32. package/src/services/CoordinateService.js +355 -0
  33. package/src/services/EventService.js +807 -0
  34. package/src/services/MoveService.js +594 -0
  35. package/src/services/PieceService.js +303 -0
  36. package/src/services/PositionService.js +237 -0
  37. package/src/services/ValidationService.js +673 -0
  38. package/src/services/index.js +14 -0
  39. package/src/styles/animations.css +46 -0
  40. package/{chessboard.css → src/styles/board.css} +3 -0
  41. package/src/styles/index.css +4 -0
  42. package/src/styles/pieces.css +66 -0
  43. package/src/utils/animations.js +37 -0
  44. package/{chess.js → src/utils/chess.js} +16 -16
  45. package/src/utils/coordinates.js +62 -0
  46. package/src/utils/cross-browser.js +150 -0
  47. package/src/utils/logger.js +422 -0
  48. package/src/utils/performance.js +311 -0
  49. package/src/utils/validation.js +458 -0
  50. package/tests/unit/chessboard-config-animations.test.js +106 -0
  51. package/tests/unit/chessboard-robust.test.js +163 -0
  52. package/tests/unit/chessboard.test.js +183 -0
  53. package/chessboard.config.js +0 -147
  54. package/chessboard.js +0 -981
  55. package/chessboard.piece.js +0 -115
  56. package/test/chessboard.test.js +0 -128
  57. /package/{alepot_theme → assets/themes/alepot}/bb.svg +0 -0
  58. /package/{alepot_theme → assets/themes/alepot}/bw.svg +0 -0
  59. /package/{alepot_theme → assets/themes/alepot}/kb.svg +0 -0
  60. /package/{alepot_theme → assets/themes/alepot}/kw.svg +0 -0
  61. /package/{alepot_theme → assets/themes/alepot}/nb.svg +0 -0
  62. /package/{alepot_theme → assets/themes/alepot}/nw.svg +0 -0
  63. /package/{alepot_theme → assets/themes/alepot}/pb.svg +0 -0
  64. /package/{alepot_theme → assets/themes/alepot}/pw.svg +0 -0
  65. /package/{alepot_theme → assets/themes/alepot}/qb.svg +0 -0
  66. /package/{alepot_theme → assets/themes/alepot}/qw.svg +0 -0
  67. /package/{alepot_theme → assets/themes/alepot}/rb.svg +0 -0
  68. /package/{alepot_theme → assets/themes/alepot}/rw.svg +0 -0
  69. /package/{default_pieces → assets/themes/default}/bb.svg +0 -0
  70. /package/{default_pieces → assets/themes/default}/bw.svg +0 -0
  71. /package/{default_pieces → assets/themes/default}/kb.svg +0 -0
  72. /package/{default_pieces → assets/themes/default}/kw.svg +0 -0
  73. /package/{default_pieces → assets/themes/default}/nb.svg +0 -0
  74. /package/{default_pieces → assets/themes/default}/nw.svg +0 -0
  75. /package/{default_pieces → assets/themes/default}/pb.svg +0 -0
  76. /package/{default_pieces → assets/themes/default}/pw.svg +0 -0
  77. /package/{default_pieces → assets/themes/default}/qb.svg +0 -0
  78. /package/{default_pieces → assets/themes/default}/qw.svg +0 -0
  79. /package/{default_pieces → assets/themes/default}/rb.svg +0 -0
  80. /package/{default_pieces → assets/themes/default}/rw.svg +0 -0
  81. /package/{.babelrc → config/.babelrc} +0 -0
package/jest.config.js CHANGED
@@ -1,7 +1,2 @@
1
- export default {
2
- testEnvironment: 'jsdom',
3
- transform: {
4
- "^.+\\.js$": "babel-jest"
5
- }
6
- // ...existing config...
7
- };
1
+ // Re-export the configuration from the config directory
2
+ export { default } from './config/jest.config.js';
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.0",
7
- "main": "chessboard.js",
6
+ "version": "2.3.2",
7
+ "main": "src/index.js",
8
8
  "type": "module",
9
9
  "scripts": {
10
- "test": "jest"
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
- import resolve from '@rollup/plugin-node-resolve';
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 "./chessboard.piece.js";
2
- import Square from "./chessboard.square.js";
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 "./chessboard.piece.js";
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
- this.element.removeChild(this.piece.element);
59
- const piece = this.piece;
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 piece;
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
- this.element.appendChild(piece.element);
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
+ };