@alepot55/chessboardjs 1.1.0 → 2.0.3

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/chessboard.js CHANGED
@@ -1,1207 +1,850 @@
1
- const DEFAULT_POSITION_WHITE = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
2
- const SLOW_ANIMATION = 600;
3
- const FAST_ANIMATION = 150;
4
-
5
- class ChessboardConfig {
6
-
7
- constructor(settings) {
8
-
9
- let defaults = {
10
-
11
- // ---------------------- General
12
- id_div: 'board',
13
- position: 'start',
14
- orientation: 'w',
15
- mode: 'normal',
16
- size: 'auto',
17
-
18
- // ---------------------- Moves
19
- draggable: true,
20
- hints: true,
21
- clickable: true,
22
- movableColors: 'both',
23
- moveHighlight: true,
24
- overHighlight: true,
25
- moveAnimation: 'ease',
26
- moveTime: 'fast',
27
-
28
- // ---------------------- Snapback
29
- dropOffBoard: 'snapback',
30
- snapbackTime: 'fast',
31
- snapbackAnimation: 'ease',
32
-
33
- // ---------------------- Fade
34
- fadeTime: 'fast',
35
- fadeAnimation: 'ease',
36
-
37
- // ---------------------- Pieces
38
- ratio: 0.9,
39
- piecesPath: 'https://cdn.jsdelivr.net/npm/@alepot55/chessboardjs/default_pieces',
40
-
41
- // ---------------------- Events
42
- onMove: () => true,
43
- onMoveEnd: () => true,
44
- onChange: () => true,
45
- onDragStart: () => true,
46
- onDragMove: () => true,
47
- onDrop: () => true,
48
- onSnapbackEnd: () => true,
49
-
50
- // ---------------------- Colors
51
- whiteSquare: '#f0d9b5',
52
- blackSquare: '#b58863',
53
- highlight: 'yellow',
54
- selectedSquareWhite: '#ababaa',
55
- selectedSquareBlack: '#ababaa',
56
- movedSquareWhite: '#f1f1a0',
57
- movedSquareBlack: '#e9e981',
58
- choiceSquare: 'white',
59
- coverSquare: 'black',
60
- hintColor: '#ababaa'
61
- };
62
-
63
- // ---------------------- General
64
-
65
- // id_div: string
66
- this.id_div = settings.id_div === undefined ? defaults.id_div : settings.id_div;
67
-
68
- // position: 'start', 'fen', {a1: 'wp', b2: 'bp', ...}, 'default'
69
- this.position = settings.position === undefined ? defaults.position : settings.position;
70
-
71
- // orientation: 'w', 'b'
72
- this.orientation = settings.orientation === undefined ? defaults.orientation : settings.orientation;
73
-
74
- // mode: 'normal', 'creative'
75
- this.mode = settings.mode === undefined ? defaults.mode : settings.mode;
76
-
77
- // deaggable: true, false
78
- this.draggable = settings.draggable === undefined ? defaults.draggable : settings.draggable;
79
-
80
- // dropOffBoard: 'snapback', 'trash'
81
- this.dropOffBoard = settings.dropOffBoard == undefined ? defaults.dropOffBoard : settings.dropOffBoard;
82
-
83
- // hints: true, false. se settings non contiene hints, allora hints = true
84
- this.hints = settings.hints === undefined ? defaults.hints : settings.hints;
85
-
86
- // clickable: true, false
87
- this.clickable = settings.clickable === undefined ? defaults.clickable : settings.clickable;
88
-
89
- // size: integer or 'auto'
90
- this.size = settings.size === undefined ? defaults.size : settings.size;
91
-
92
- // ---------------------- Moves
93
-
94
- // movableColors: 'white', 'black', 'both', 'none'
95
- this.movableColors = settings.movableColors === undefined ? defaults.movableColors : settings.movableColors;
96
-
97
- // moveHighlight: true, false
98
- this.moveHighlight = settings.moveHighlight === undefined ? defaults.moveHighlight : settings.moveHighlight;
99
-
100
- // overHighlight: true, false
101
- this.overHighlight = settings.overHighlight === undefined ? defaults.overHighlight : settings.overHighlight;
102
-
103
- // moveAnimation: 'linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'none'
104
- this.moveAnimation = settings.moveAnimation === undefined ? defaults.moveAnimation : settings.moveAnimation;
105
-
106
- // moveTime: integer, 'slow', 'fast'
107
- this.moveTime = settings.moveTime === undefined ? defaults.moveTime : settings.moveTime;
108
-
109
- // ---------------------- Snapback
110
-
111
- // snapbackTime: integer, 'slow', 'fast'f
112
- this.snapbackTime = settings.snapbackTime === undefined ? defaults.snapbackTime : settings.snapbackTime;
113
-
114
- // snapbackAnimation: 'linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'none'
115
- this.snapbackAnimation = settings.snapbackAnimation === undefined ? defaults.snapbackAnimation : settings.snapbackAnimation;
116
-
117
- // ---------------------- Fade
118
-
119
- // fadeTime: integer, 'slow', 'fast'
120
- this.fadeTime = settings.fadeTime === undefined ? defaults.fadeTime : settings.fadeTime;
121
-
122
- // fadeAnimation: 'linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'none'
123
- this.fadeAnimation = settings.fadeAnimation === undefined ? defaults.fadeAnimation : settings.fadeAnimation;
124
-
125
- // ---------------------- Pieces
126
-
127
- // ratio: integer
128
- settings.ratio === undefined ? this.setCSSProperty('pieceRatio', defaults.ratio) : this.setCSSProperty('pieceRatio', settings.ratio);
129
-
130
- // piecesPath: string
131
- this.piecesPath = settings.piecesPath === undefined ? defaults.piecesPath : settings.piecesPath;
132
-
133
-
134
- // ---------------------- Events
135
-
136
- // onMove: function(move)
137
- this.onMove = settings.onMove === undefined ? defaults.onMove : settings.onMove;
138
-
139
- // onMoveEnd: function(move)
140
- this.onMoveEnd = settings.onMoveEnd === undefined ? defaults.onMoveEnd : settings.onMoveEnd;
141
-
142
- // onChange: function(fen)
143
- this.onChange = settings.onChange === undefined ? defaults.onChange : settings.onChange;
144
-
145
- // onDragStart: function(from, piece)
146
- this.onDragStart = settings.onDragStart === undefined ? defaults.onDragStart : settings.onDragStart;
147
-
148
- // onDragMove: function(from, to, piece)
149
- this.onDragMove = settings.onDragMove === undefined ? defaults.onDragMove : settings.onDragMove;
150
-
151
- // onDrop: function(from, to, piece)
152
- this.onDrop = settings.onDrop === undefined ? defaults.onDrop : settings.onDrop;
153
-
154
- // onSnapbackEnd: function(from, piece)
155
- this.onSnapbackEnd = settings.onSnapbackEnd === undefined ? defaults.onSnapbackEnd : settings.onSnapbackEnd;
156
-
157
- // ---------------------- Colors
158
-
159
- // whiteSquare: string
160
- settings.whiteSquare === undefined ? this.setCSSProperty('whiteSquare', defaults.whiteSquare) : this.setCSSProperty('whiteSquare', settings.whiteSquare);
161
-
162
- // blackSquare: string
163
- settings.blackSquare === undefined ? this.setCSSProperty('blackSquare', defaults.blackSquare) : this.setCSSProperty('blackSquare', settings.blackSquare);
164
-
165
- // highlight: string
166
- settings.highlight === undefined ? this.setCSSProperty('highlightSquare', defaults.highlight) : this.setCSSProperty('highlightSquare', settings.highlight);
167
-
168
- // selectedSquareWhite: string
169
- settings.selectedSquareWhite === undefined ? this.setCSSProperty('selectedSquareWhite', defaults.selectedSquareWhite) : this.setCSSProperty('selectedSquareWhite', settings.selectedSquareWhite);
170
-
171
- // selectedSquareBlack: string
172
- settings.selectedSquareBlack === undefined ? this.setCSSProperty('selectedSquareBlack', defaults.selectedSquareBlack) : this.setCSSProperty('selectedSquareBlack', settings.selectedSquareBlack);
173
-
174
- // movedSquareWhite: string
175
- settings.movedSquareWhite === undefined ? this.setCSSProperty('movedSquareWhite', defaults.movedSquareWhite) : this.setCSSProperty('movedSquareWhite', settings.movedSquareWhite);
176
-
177
- // movedSquareBlack: string
178
- settings.movedSquareBlack === undefined ? this.setCSSProperty('movedSquareBlack', defaults.movedSquareBlack) : this.setCSSProperty('movedSquareBlack', settings.movedSquareBlack);
179
-
180
- // choiceSquare: string
181
- settings.choiceSquare === undefined ? this.setCSSProperty('choiceSquare', defaults.choiceSquare) : this.setCSSProperty('choiceSquare', settings.choiceSquare);
182
-
183
- // coverSquare: string
184
- settings.coverSquare === undefined ? this.setCSSProperty('coverSquare', defaults.coverSquare) : this.setCSSProperty('coverSquare', settings.coverSquare);
185
-
186
- // hintColor: string
187
- settings.hintColor === undefined ? this.setCSSProperty('hintColor', defaults.hintColor) : this.setCSSProperty('hintColor', settings.hintColor);
188
-
189
- // Configure modes
190
-
191
- if (this.mode === 'creative') {
192
- this.onlyLegalMoves = false;
193
- this.hints = false;
194
- } else if (this.mode === 'normal') {
195
- this.onlyLegalMoves = true;
196
- }
197
-
198
- return this;
199
- }
200
-
201
- setCSSProperty(property, value) {
202
- document.documentElement.style.setProperty('--' + property, value);
203
- }
204
-
205
- setOrientation(orientation) {
206
- this.orientation = orientation;
207
- return this;
208
- }
209
- }
210
-
211
- export class Chessboard {
212
-
213
- constructor(config) {
214
-
215
- this.config = new ChessboardConfig(config);
216
-
217
- this.pezzi = {};
218
- this.pieces = {};
219
- this.celle = {};
220
- this.squares = {};
221
- this.buildGame(this.config.position);
222
- this.initParams();
223
- this.buildBoard();
224
- this.updatePosition();
225
- }
226
-
227
- // Build
228
-
229
- buildGame(position) {
230
- if (position === 'start') {
231
- this.game = new Chess();
232
- } else if (position === 'default') {
233
- this.game = new Chess(DEFAULT_POSITION_WHITE);
234
- } else if (typeof position === 'string') {
235
- this.game = new Chess(position);
236
- } else if (typeof position === 'object') {
237
- let game = new Chess('start');
238
- for (let square in position) {
239
- game.put({ type: position[square][0], color: position[square][1] }, square);
240
- }
241
- this.game = game;
242
- } else {
243
- throw new Error('Invalid position - ' + position + ' - must be a fen string, "start", "default" or a dictionary of pieces, like {a1: "wK", b2: "bQ", ...}');
244
- }
245
- }
246
-
247
- buildBoard() {
248
- this.board = document.getElementById(this.config.id_div);
249
- if (!this.board) {
250
- throw new Error('Board id not found - ' + this.config.id_div + ' - must be a valid id of a div element');
251
- }
252
- this.resize(this.config.size);
253
- this.board.className = "board";
254
- this.buildSquares();
255
- }
256
-
257
- buildSquares() {
258
- this.squares = {};
259
- this.lastSquare = null;
260
- this.celle = {};
261
- this.pezzi = {};
262
- this.pieces = {};
263
- this.mosseIndietro = [];
264
-
265
-
266
- for (let row = 0; row < 8; row++) {
267
-
268
- this.squares[row] = {};
269
-
270
- for (let col = 0; col < 8; col++) {
271
-
272
- // Imposta l'id della cella e crea un nuovo elemento div
273
- let id = this.getSquareID(row, col)
274
- let square = document.createElement("div");
275
-
276
- this.celle[id] = square;
277
- this.squares[row][col] = square;
278
- square.id = id;
279
- this.resetSquare(id);
280
-
281
- this.board.appendChild(square);
282
- }
283
- }
284
-
285
- this.addListeners();
286
- }
287
-
288
- initParams() {
289
- this.promoting = false;
290
- this.lastSquare = null;
291
- this.history = [];
292
- this.mosseIndietro = [];
293
- this.lastSquare = null;
294
- }
295
-
296
- resize(value) {
297
- if (value === 'auto') {
298
- let size;
299
- if (this.board.offsetWidth === 0) {
300
- size = this.board.offsetHeight;
301
- } else if (this.board.offsetHeight === 0) {
302
- size = this.board.offsetWidth;
303
- } else {
304
- size = Math.min(this.board.offsetWidth, this.board.offsetHeight);
305
- }
306
- this.resize(size);
307
- } else if (typeof value !== 'number') {
308
- throw new Error('Invalid value - ' + value + ' - must be a number or "auto"');
309
- } else {
310
- document.documentElement.style.setProperty('--dimBoard', value + 'px');
311
- this.updatePosition();
312
- }
313
- }
314
-
315
- destroy() {
316
- if (!this.board) throw new Error('Board not found');
317
- this.board.innerHTML = '';
318
- this.board.className = '';
319
- }
320
-
321
-
322
- // Pieces
323
-
324
- checkPiece(piece) {
325
- if (['p', 'r', 'n', 'b', 'q', 'k'].indexOf(piece[0]) === -1 || ['w', 'b'].indexOf(piece[1]) === -1) throw new Error('Invalid piece - ' + piece + ' - must be a valid piece like "pw" or "kb"');
326
- }
327
-
328
- getPiecePath(piece) {
329
- if (typeof this.config.piecesPath === 'string') return this.config.piecesPath + '/' + piece + '.svg';
330
- else return this.config.piecesPath(piece);
331
- }
332
-
333
- piece(square) {
334
- this.checkSquare(square);
335
- let piece = this.game.get(square);
336
- return piece ? piece['type'] + piece['color'] : null;
337
- }
338
-
339
- colorPiece(square) {
340
- let piece = this.piece(square);
341
- return piece ? piece[1] : null;
342
- }
343
-
344
- traslation(elem, from, to, duration) {
345
-
346
- let piece = elem.src.split('/').pop().split('.')[0];
347
-
348
- if (duration === 'none' || duration === 0) {
349
- this.removePiece(from, piece, false);
350
- this.insert(to, piece, false);
351
- return;
352
- }
353
- else if (duration === 'slow') duration = SLOW_ANIMATION;
354
- else if (duration === 'fast') duration = FAST_ANIMATION;
355
-
356
- let startX, startY, endX, endY;
357
-
358
- if (from) {
359
- startX = this.celle[from].getBoundingClientRect().left;
360
- startY = this.celle[from].getBoundingClientRect().top;
361
- } else {
362
- startX = elem.getBoundingClientRect().left - 4;
363
- startY = elem.getBoundingClientRect().top - 4;
364
- }
365
-
366
- endX = this.celle[to].getBoundingClientRect().left;
367
- endY = this.celle[to].getBoundingClientRect().top;
368
-
369
-
370
- let x = endX - startX;
371
- let y = endY - startY;
372
- let startTime;
373
- let board = this;
374
-
375
- function translate(currentTime) {
376
- if (!startTime) {
377
- startTime = currentTime;
378
- }
379
-
380
- let timeElapsed = currentTime - startTime;
381
- let t = timeElapsed / duration;
382
- let progress = board.transitionTimingFunction(t, board.config.moveAnimation);
383
- elem.style.transform = 'translate(' + (x * progress) + 'px, ' + (y * progress) + 'px)';
384
-
385
- if (t < 1) {
386
- requestAnimationFrame(translate);
387
- } else {
388
- if (from) board.removePiece(from, piece, false);
389
- if (to) board.insert(to, board.piece(to), false);
390
- }
391
- }
392
-
393
- requestAnimationFrame(translate);
394
- }
395
-
396
- translatePiece(piece, from, to, removeTo, animate) {
397
-
398
- if (!animate) {
399
- this.removePiece(from, piece, false);
400
- this.insert(to, piece, false);
401
- return;
402
- };
403
-
404
- let elem = this.pieces[(piece, from)]['img'];
405
-
406
- if (removeTo) this.removePiece(to);
407
-
408
- return this.traslation(elem, from, to, this.config.moveTime);
409
- }
410
-
411
- snapbackPiece(square, piece, animate) {
412
-
413
- if (!animate || this.config.snapbackAnimation === 'none' || this.config.snapbackTime === 0) {
414
- this.removePiece(square, piece, false);
415
- this.insert(square, piece, false);
416
- return;
417
- }
418
-
419
- let elem = this.pieces[(piece, square)]['img'];
420
- this.traslation(elem, null, square, this.config.snapbackTime);
421
- }
422
-
423
- fadeInPiece(square) {
424
-
425
- let duration = this.config.fadeTime;
426
- if (duration === 'slow') duration = SLOW_ANIMATION;
427
- else if (duration === 'fast') duration = FAST_ANIMATION;
428
-
429
- let elem = this.pezzi[square]['img'];
430
-
431
- let startTime;
432
- let board = this;
433
-
434
- function fadeIn(currentTime) {
435
- if (!startTime) {
436
- startTime = currentTime;
437
- }
438
- let timeElapsed = currentTime - startTime;
439
- let t = timeElapsed / duration;
440
- let progress = board.transitionTimingFunction(t, board.config.fadeAnimation);
441
- elem.style.opacity = progress;
442
-
443
- if (t < 1) {
444
- requestAnimationFrame(fadeIn);
445
- }
446
- }
447
-
448
- requestAnimationFrame(fadeIn);
449
- }
450
-
451
- fadeOutPiece(square, img, remove, animate) {
452
-
453
- let duration = this.config.fadeTime;
454
- if (duration === 'slow') duration = SLOW_ANIMATION;
455
- else if (duration === 'fast') duration = FAST_ANIMATION;
456
-
457
- if (!animate) {
458
- if (remove) this.celle[square].removeChild(img);
459
- else img.style.opacity = 0;
460
- return;
461
- }
462
-
463
- let startTime;
464
- let board = this;
465
-
466
- function fadeOut(currentTime) {
467
- if (!startTime) {
468
- startTime = currentTime;
469
- }
470
- let timeElapsed = currentTime - startTime;
471
- let t = timeElapsed / duration;
472
- let progress = board.transitionTimingFunction(t, board.config.fadeAnimation);
473
- img.style.opacity = 1 - progress;
474
-
475
- if (t < 1) {
476
- requestAnimationFrame(fadeOut);
477
- } else {
478
- if (remove) board.celle[square].removeChild(img);
479
- }
480
- }
481
-
482
- requestAnimationFrame(fadeOut);
483
- }
484
-
485
- removePiece(square, piece, fade = true) {
486
-
487
- if (!this.pezzi[square]) return null;
488
- piece = piece ? piece : this.pezzi[square]['piece'];
489
- if (this.pezzi[square]['piece'] !== piece) return null;
490
-
491
- if (fade) this.fadeOutPiece(square, this.pezzi[square]['img']);
492
- else this.celle[square].removeChild(this.pezzi[square]['img']);
493
-
494
- this.pezzi[square] = null;
495
- this.pieces[(piece, square)] = null;
496
-
497
- return piece;
498
- }
499
-
500
- insert(square, piece, fade = this.config.fadeAnimation) {
501
-
502
- if (fade === 'none' || fade === 0) fade = false;
503
-
504
- this.checkPiece(piece);
505
- this.checkSquare(square);
506
-
507
- if (!piece) return;
508
- this.removePiece(square, null, false);
509
-
510
- let img = document.createElement("img");
511
- img.className = "piece";
512
- img.src = this.getPiecePath(piece);
513
- img.style.opacity = fade ? 0 : 1;
514
-
515
- let board = this;
516
- img.onmousedown = function (event) {
517
-
518
- if (!board.config.draggable) return;
519
-
520
- let recent;
521
- let from = square;
522
- let to = square;
523
- let moved = false;
524
-
525
- if (!board.canMove(from)) return;
526
-
527
- if (!board.config.clickable) board.lastSquare = null;
528
- if (board.onClick(square)) return;
529
-
530
- img.style.position = 'absolute';
531
- img.style.zIndex = 15;
532
-
533
- // Function to move the piece with the mouse pointer
534
- function moveAt(pageX, pageY) {
535
- if (!moved && !board.config.onDragStart(from, piece)) return;
536
- moved = true;
537
- img.style.left = pageX - img.offsetWidth / 2 + 'px';
538
- img.style.top = pageY - img.offsetHeight / 2 + 'px';
539
- return true;
540
- }
541
-
542
- function onMouseMove(event) {
543
-
544
- // Bug fix for spamming the mousemove event
545
- if (!piece) return;
546
-
547
- if (!moveAt(event.pageX, event.pageY)) return;
548
-
549
- // Find the square where the mouse is
550
- let x = event.clientX - board.board.getBoundingClientRect().left;
551
- let y = event.clientY - board.board.getBoundingClientRect().top;
552
-
553
- let col = Math.floor(x / (board.board.offsetWidth / 8));
554
- let row = Math.floor(y / (board.board.offsetHeight / 8));
555
- if (x < 0 || x > board.board.offsetWidth || y < 0 || y > board.board.offsetHeight) to = null;
556
- else to = board.getSquareID(row, col);
557
- board.config.onDragMove(from, to, piece);
558
-
559
- if (to !== recent) {
560
- board.highlight(to);
561
- board.dehighlight(recent);
562
- recent = to;
563
- }
564
- }
565
- document.addEventListener('mousemove', onMouseMove);
566
-
567
- // Drop the piece and remove the event listener
568
- img.onmouseup = function () {
569
- board.dehighlight(recent);
570
- document.removeEventListener('mousemove', onMouseMove);
571
- img.onmouseup = null;
572
- let drop = board.config.onDrop(from, to, piece);
573
-
574
- if ((board.config.dropOffBoard === 'trash' || drop === 'trash') && !to) {
575
- board.unmoveAllSquares();
576
- board.dehintAllSquares();
577
- board.deselect(from);
578
- board.remove(from);
579
- } else if (moved && (!board.onClick(to, false) || drop === 'snapback')) {
580
- board.snapbackPiece(from, piece, !board.promoting);
581
- board.config.onSnapbackEnd(from, piece);
582
- }
583
- };
584
-
585
- };
586
-
587
- // Prevent the image from being dragged
588
- img.ondragstart = function () {
589
- return false;
590
- };
591
-
592
- this.pezzi[square] = { 'img': img, 'piece': piece };
593
- this.pieces[(piece, square)] = { 'img': img };
594
- this.celle[square].appendChild(img);
595
-
596
- if (fade) this.fadeInPiece(square);
597
- else img.style.opacity = 1;
598
- return img;
599
- }
600
-
601
- updatePieces(animation) {
602
-
603
- let ok = {};
604
- let escaping = {};
605
- let canEscape = {};
606
- let toTranslate = [];
607
-
608
- for (let square in this.celle) {
609
- ok[square] = false;
610
- escaping[square] = false;
611
- canEscape[square] = this.pezzi[square] && this.piece(square) !== this.pezzi[square]['piece'];
612
- }
613
-
614
-
615
- for (let square in this.celle) {
616
-
617
- let pieceNew = this.piece(square);
618
- let pieceOld = this.pezzi[square] ? this.pezzi[square]['piece'] : null;
619
-
620
- if (pieceOld !== pieceNew && !ok[square]) {
621
-
622
-
623
- for (let from in this.pezzi) {
624
-
625
- let coming = this.pezzi[from] ? this.pezzi[from]['piece'] : null;
626
-
627
- if (coming && canEscape[from] && !ok[square] && from !== square && coming === pieceNew && !this.isPiece(pieceNew, from)) {
628
-
629
- // check for en passant
630
- let lastMove = this.lastMove();
631
- if (!pieceOld && lastMove && lastMove['captured'] === 'p') {
632
- let captured = 'p' + (lastMove['color'] === 'w' ? 'b' : 'w');
633
- this.removePiece(square[0] + from[1], captured);
634
- }
635
-
636
- toTranslate.push([coming, from, square]);
637
-
638
- if (!this.piece(from)) ok[from] = true;
639
- escaping[from] = true;
640
- canEscape[from] = false;
641
-
642
- ok[square] = true;
643
-
644
- break;
645
- }
646
- }
647
- }
648
- }
649
-
650
- for (let [piece, from, to] of toTranslate) {
651
- this.translatePiece(piece, from, to, !escaping[to], animation);
652
- }
653
-
654
- for (let square in this.celle) {
655
-
656
- let pieceNew = this.piece(square);
657
- let pieceOld = this.pezzi[square] ? this.pezzi[square]['piece'] : null;
658
-
659
- if (pieceOld !== pieceNew && !ok[square]) {
660
-
661
- if (!ok[square]) {
662
- // check for promotion
663
- let lastMove = this.lastMove();
664
- if (lastMove && lastMove['promotion']) {
665
- if (lastMove['to'] === square) {
666
- let piece = lastMove['promotion'] + lastMove['color'];
667
- this.translatePiece(piece, lastMove['from'], square, true, animation);
668
- ok[lastMove['from']] = true;
669
- }
670
- } else {
671
- this.removePiece(square);
672
- if (pieceNew) this.insert(square, pieceNew);
673
- }
674
- }
675
- }
676
- }
677
-
678
- this.config.onChange(this.game.fen());
679
- }
680
-
681
- opponentPiece(square) {
682
- let piece = this.piece(square);
683
- return piece && piece[1] !== this.config.orientation;
684
- }
685
-
686
- playerPiece(square) {
687
- let piece = this.piece(square);
688
- return piece && piece[1] === this.config.orientation;
689
- }
690
-
691
- isPiece(piece, square) {
692
- return this.piece(square) === piece;
693
- }
694
-
695
- remove(square, animation = true) {
696
- this.checkSquare(square);
697
- this.game.remove(square);
698
- this.removePiece(square, null, animation);
699
- }
700
-
701
-
702
- // Listeners
703
-
704
- addListeners() {
705
- if (this.mosseIndietro.length > 0) return;
706
- for (let square in this.celle) {
707
- let elem = this.celle[square];
708
- elem.addEventListener("mouseover", () => {
709
- if (!this.lastSquare) this.hintMoves(elem.id);
710
- });
711
- elem.addEventListener("mouseout", () => {
712
- if (!this.lastSquare) this.dehintMoves(elem.id);
713
- });
714
- elem.addEventListener("click", () => {
715
- if (this.config.clickable && (!this.pezzi[elem.id] || this.config.onlyLegalMoves)) this.onClick(elem.id)
716
- });
717
- elem.addEventListener("touch", () => {
718
- if (this.config.clickable) this.onClick(elem.id)
719
- });
720
- }
721
- }
722
-
723
- onClick(square, animation = this.config.moveAnimation) {
724
-
725
- if (!square || square === this.lastSquare) return;
726
-
727
- if (animation === 'none') animation = false;
728
-
729
- if (this.promoting) {
730
- this.depromoteAllSquares();
731
- this.removeAllCovers();
732
- this.promoting = false;
733
- if (square.length === 2) this.lastSquare = null;
734
- }
735
-
736
- let from = this.lastSquare;
737
- this.lastSquare = null;
738
- let move = from + square;
739
-
740
- if (from) {
741
- this.deselect(from);
742
- this.dehintAllSquares();
743
- } else if (!this.canMove(square)) return;
744
-
745
-
746
- if (from && this.canMove(from) && (!this.canMove(square) || !this.config.onlyLegalMoves)) {
747
-
748
- if (this.config.onlyLegalMoves && !this.legalMove(move)) return;
749
- if (move.length == 4 && this.promote(move)) return;
750
-
751
- if (this.config.onMove(move)) this.move(move, animation);
752
- else return;
753
-
754
- return true;
755
-
756
- } else if (this.canMove(square)) {
757
-
758
- this.select(square);
759
- this.hintMoves(square);
760
- this.lastSquare = square;
761
- }
762
- }
763
-
764
- // Hint
765
-
766
- hint(square) {
767
- this.checkSquare(square);
768
- if (!this.config.hints || !this.celle[square]) return;
769
-
770
- let hint = document.createElement("div");
771
- hint.className = "hint";
772
-
773
- if (this.colorPiece(square) && this.colorPiece(square) !== this.turn()) hint.className += " catchable";
774
-
775
- this.celle[square].appendChild(hint);
776
-
777
- }
778
-
779
- hintMoves(square) {
780
- if (!this.canMove(square)) return;
781
- let mosse = this.game.moves({ square: square, verbose: true });
782
- for (let mossa of mosse) {
783
- if (mossa['to'].length === 2) this.hint(mossa['to']);
784
- }
785
- }
786
-
787
- dehintMoves(square) {
788
- let mosse = this.game.moves({ square: square, verbose: true });
789
- for (let mossa of mosse) {
790
- if (mossa['to'].length === 2) this.dehint(mossa['to']);
791
- }
792
- }
793
-
794
- dehint(square) {
795
- if (square.length !== 2) return;
796
- if (this.config.hints) {
797
- let cella = this.celle[square];
798
- if (!cella) return;
799
- let figli = cella.childNodes;
800
-
801
- for (let i = figli.length - 1; i >= 0; i--) {
802
- if (figli[i].className.includes('hint')) {
803
- cella.removeChild(figli[i]);
804
- }
805
- }
806
- }
807
- }
808
-
809
- dehintAllSquares() {
810
- for (let casella in this.celle) {
811
- this.dehint(casella);
812
- }
813
- }
814
-
815
- // Select
816
-
817
- select(square) {
818
- this.checkSquare(square);
819
- if (!this.config.clickable) return;
820
- let elem = this.celle[square];
821
- if (this.isWhiteSquare(square)) elem.className += ' selectedSquareWhite';
822
- else elem.className += ' selectedSquareBlack';
823
- }
824
-
825
- deselect(square) {
826
- this.checkSquare(square);
827
- let elem = this.celle[square];
828
- if (this.isWhiteSquare(square)) elem.className = elem.className.replace(' selectedSquareWhite', '');
829
- else elem.className = elem.className.replace(' selectedSquareBlack', '');
830
- }
831
-
832
- deselectAllSquares() {
833
- for (let casella in this.celle) {
834
- this.deselect(casella);
835
- }
836
- }
837
-
838
- // Moves
839
-
840
- checkMove(move) {
841
- if (move.length < 4 || move.length > 5) throw new Error('Invalid move - ' + move + ' - must be a valid move like "e2e4" or "e7e8q"');
842
- let from = move.slice(0, 2);
843
- let to = move.slice(2, 4);
844
- let prom = move.length === 5 ? move[4] : null;
845
- this.checkSquare(from);
846
- this.checkSquare(to);
847
- if (prom && ['q', 'r', 'n', 'b'].indexOf(prom) === -1) throw new Error('Invalid promotion - ' + prom + ' - must be a valid piece like "q", "r", "n" or "b"');
848
- }
849
-
850
- canMove(square) {
851
- if (!this.piece(square)) return false;
852
- if (this.config.movableColors === 'none') return false;
853
- if (this.config.movableColors === 'w' && this.colorPiece(square) === 'b') return false;
854
- if (this.config.movableColors === 'b' && this.colorPiece(square) === 'w') return false;
855
- if (!this.config.onlyLegalMoves) return true;
856
- if (this.colorPiece(square) !== this.turn()) return false;
857
- return true;
858
- }
859
-
860
- move(move, animation) {
861
-
862
- this.checkMove(move);
863
-
864
- if (!this.config.onlyLegalMoves) {
865
- let piece = this.piece(move.slice(0, 2));
866
- this.game.remove(move.slice(0, 2));
867
- this.game.remove(move.slice(2, 4));
868
- this.game.put({ type: move[4] ? move[4] : piece[0], color: piece[1] }, move.slice(2, 4));
869
- return this.updatePosition(false, false);
870
- }
871
-
872
- this.unmoveAllSquares();
873
-
874
- move = this.game.move({
875
- from: move.slice(0, 2),
876
- to: move.slice(2, 4),
877
- promotion: move.length === 5 ? move[4] : null
878
- });
879
-
880
-
881
- this.history.push(move);
882
-
883
- this.updatePosition(false, animation);
884
-
885
- this.moved(move['to']);
886
- this.moved(move['from']);
887
-
888
- this.dehintAllSquares();
889
-
890
- this.config.onMoveEnd(move);
891
-
892
- return true;
893
-
894
-
895
- }
896
-
897
- moved(square) {
898
- this.checkSquare(square);
899
- if (!this.config.moveHighlight) return;
900
- let elem = this.celle[square];
901
- if (this.isWhiteSquare(square)) elem.className += ' movedSquareWhite';
902
- else elem.className += ' movedSquareBlack';
903
- }
904
-
905
- unmoved(square) {
906
- this.checkSquare(square);
907
- if (!this.config.moveHighlight) return;
908
- let elem = this.celle[square];
909
- if (this.isWhiteSquare(square)) elem.className = elem.className.replace(' movedSquareWhite', '');
910
- else elem.className = elem.className.replace(' movedSquareBlack', '');
911
- }
912
-
913
- unmoveAllSquares() {
914
- for (let casella in this.celle) {
915
- this.unmoved(casella);
916
- }
917
- return;
918
- }
919
-
920
- legalMove(mossa) {
921
- let legalMoves = this.legalMoves(mossa.slice(0, 2));
922
- for (let i in legalMoves) {
923
- if (legalMoves[i]['to'] === mossa.slice(2, 4) && (mossa.length === 4 || mossa[4] === legalMoves[i]['promotion'])) return true;
924
- }
925
-
926
- return false;
927
- }
928
-
929
- legalMoves(from = null, verb = true) {
930
- if (from) this.checkSquare(from);
931
- else return this.game.moves({ verbose: verb });
932
- return this.game.moves({ square: from, verbose: verb });
933
- }
934
-
935
- lastMove() {
936
- return this.history[this.history.length - 1];
937
- }
938
-
939
- history() {
940
- return this.history;
941
- }
942
-
943
- // State
944
-
945
- isGameOver() {
946
- if (this.game.game_over()) {
947
- if (this.game.in_checkmate()) return this.game.turn() === 'w' ? 'b' : 'w';
948
- return 'd';
949
- }
950
- return null;
951
- }
952
-
953
- turn() {
954
- return this.game.turn();
955
- }
956
-
957
- getOrientation() {
958
- return this.config.orientation;
959
- }
960
-
961
- orientation(color) {
962
- if ((color === 'w' || color === 'b') && color !== this.config.orientation) this.flip();
963
- }
964
-
965
- // Position
966
-
967
- chageFenTurn(fen, color) {
968
- let parts = fen.split(' ');
969
- parts[1] = color;
970
- return parts.join(' ');
971
- }
972
-
973
- position(position, color = null) {
974
- this.initParams();
975
- this.dehintAllSquares();
976
- this.deselectAllSquares();
977
- this.unmoveAllSquares();
978
- if (!color) color = position.split(' ')[1];
979
- let change_color = this.config.orientation !== color;
980
- this.config.setOrientation(color);
981
- this.game = new Chess(position);
982
- this.updatePosition(change_color);
983
- }
984
-
985
- flip() {
986
- let position = this.game.fen();
987
- this.position(position, this.config.orientation === 'w' ? 'b' : 'w');
988
- }
989
-
990
- playerTurn() { // Restituisce true se è il turno del giocatore
991
- return this.config.orientation === this.game.turn();
992
- }
993
-
994
- isWhiteSquare(square) {
995
- this.checkSquare(square);
996
- let letters = 'abcdefgh';
997
- return (letters.indexOf(square[0]) + parseInt(square[1])) % 2 === 0;
998
- }
999
-
1000
- isWhiteOriented() {
1001
- return this.config.orientation === 'w';
1002
- }
1003
-
1004
- updatePosition(change_color = false, animation = this.config.moveAnimation) {
1005
- if (change_color) {
1006
- this.renameSquares();
1007
- }
1008
- this.updatePieces(animation);
1009
- }
1010
-
1011
- renameSquares() {
1012
- let new_celle = {};
1013
- let new_pezzi = {};
1014
- let new_pieces = {};
1015
- for (let elem in this.celle) {
1016
- let square = this.celle[elem];
1017
- let id = square.id;
1018
- let [row, col] = this.getSquareCoord(id);
1019
- let new_row = 7 - row;
1020
- let new_col = 7 - col;
1021
- square.id = this.getSquareID(new_row, new_col);
1022
- new_celle[square.id] = square;
1023
- if (this.pezzi[id]) {
1024
- new_pezzi[square.id] = this.pezzi[id];
1025
- new_pieces[(this.pezzi[id]['piece'], square.id)] = this.pieces[(this.pezzi[id]['piece'], id)];
1026
- }
1027
-
1028
- }
1029
- this.celle = new_celle;
1030
- this.pezzi = new_pezzi;
1031
- this.pieces = new_pieces;
1032
- }
1033
-
1034
- fen() {
1035
- return this.game.fen();
1036
- }
1037
-
1038
- // Squares
1039
-
1040
- checkSquare(square) {
1041
- if (!square) return;
1042
- if (['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'].indexOf(square[0]) === -1 || ['1', '2', '3', '4', '5', '6', '7', '8'].indexOf(square[1]) === -1) throw new Error('Invalid square - ' + square + ' - must be a valid square like "a1" or "h8"');
1043
- }
1044
-
1045
- getSquareCoord(coord) {
1046
- let letters = 'abcdefgh';
1047
- if (this.isWhiteOriented()) {
1048
- return [8 - parseInt(coord[1]), letters.indexOf(coord[0])];
1049
- }
1050
- return [parseInt(coord[1]) - 1, 7 - letters.indexOf(coord[0])];
1051
- }
1052
-
1053
- resetSquare(square) {
1054
- let elem = this.celle[square];
1055
- elem.className = 'square ' + (this.isWhiteSquare(square) ? 'whiteSquare' : 'blackSquare');
1056
- }
1057
-
1058
- getSquareID(row, col) {
1059
- row = parseInt(row);
1060
- col = parseInt(col);
1061
- if (this.isWhiteOriented()) {
1062
- row = 8 - row;
1063
- col = col + 1;
1064
- } else {
1065
- row = row + 1;
1066
- col = 8 - col;
1067
- }
1068
- let letters = 'abcdefgh';
1069
- let letter = letters[col - 1];
1070
- return letter + row;
1071
- }
1072
-
1073
- removeSquares() { // Rimuove le caselle dalla Chessboard
1074
- for (let casella in this.celle) {
1075
- this.board.removeChild(this.celle[casella]);
1076
- }
1077
- this.celle = {};
1078
- }
1079
-
1080
- clear(animation = true) {
1081
- this.game.clear();
1082
- this.updatePosition(null, animation);
1083
- }
1084
-
1085
- // Highlight
1086
-
1087
- highlight(square) {
1088
- if (!square || !this.celle[square] || !this.config.overHighlight) return;
1089
- let elem = this.celle[square];
1090
- elem.className += ' highlighted';
1091
- }
1092
-
1093
- dehighlight(square) {
1094
- if (!square || !this.celle[square] || !this.config.overHighlight) return;
1095
- this.checkSquare(square);
1096
- let elem = this.celle[square];
1097
- elem.className = elem.className.replace(' highlighted', '');
1098
- }
1099
-
1100
-
1101
- // Promotion
1102
-
1103
- coverSquare(square) {
1104
- let cover = document.createElement("div");
1105
- cover.className = "square cover";
1106
- this.celle[square].appendChild(cover);
1107
- }
1108
-
1109
- removeCover(square) {
1110
- let elem = this.celle[square];
1111
- let figli = elem.childNodes;
1112
-
1113
- for (let i = figli.length - 1; i >= 0; i--) {
1114
- if (figli[i].className.includes('cover')) {
1115
- elem.removeChild(figli[i]);
1116
- }
1117
- }
1118
- }
1119
-
1120
- removeAllCovers() {
1121
- for (let casella in this.celle) {
1122
- this.removeCover(casella);
1123
- }
1124
- }
1125
-
1126
- promoteSquare(square, piece) {
1127
- let choice = document.createElement("div");
1128
- choice.className = "square choice";
1129
-
1130
- let img = document.createElement("img");
1131
- img.className = "piece choicable";
1132
- img.src = this.getPiecePath(piece);
1133
- choice.appendChild(img);
1134
-
1135
- this.celle[square].appendChild(choice);
1136
-
1137
- return choice;
1138
- }
1139
-
1140
- depromoteSquare(square) {
1141
- let elem = this.celle[square];
1142
- let figli = elem.childNodes;
1143
-
1144
- for (let i = figli.length - 1; i >= 0; i--) {
1145
- if (figli[i].className.includes('choice')) {
1146
- elem.removeChild(figli[i]);
1147
- }
1148
- }
1149
- }
1150
-
1151
- depromoteAllSquares() {
1152
- for (let casella in this.celle) {
1153
- this.depromoteSquare(casella);
1154
- }
1155
- }
1156
-
1157
- promote(mossa) {
1158
-
1159
- if (!this.config.onlyLegalMoves) return false;
1160
-
1161
- let to = mossa.slice(2, 4);
1162
- let from = mossa.slice(0, 2);
1163
- let pezzo = this.game.get(from);
1164
- let [row, col] = this.getSquareCoord(to);
1165
- let choices = ['q', 'r', 'b', 'n']
1166
-
1167
- if (pezzo['type'] !== 'p' || !(row === 0 || row === 7)) return false;
1168
-
1169
- this.promoting = true;
1170
-
1171
- for (let casella in this.celle) {
1172
- let [rowCurr, colCurr] = this.getSquareCoord(casella);
1173
-
1174
- if (col === colCurr && Math.abs(row - rowCurr) <= 3) {
1175
-
1176
- let choice = this.promoteSquare(casella, choices[Math.abs(row - rowCurr)] + pezzo['color']);
1177
- choice.addEventListener('click', () => {
1178
- this.onClick(to + choices[Math.abs(row - rowCurr)]);
1179
- });
1180
-
1181
- } else {
1182
- this.coverSquare(casella);
1183
- }
1184
- }
1185
-
1186
- this.lastSquare = from;
1187
-
1188
- return true;
1189
- }
1190
-
1191
- // Other
1192
-
1193
- transitionTimingFunction(x, type = 'ease') {
1194
- switch (type) {
1195
- case 'linear':
1196
- return x;
1197
- case 'ease':
1198
- return (x ** 2) * (3 - 2 * x);
1199
- case 'ease-in':
1200
- return x ** 2;
1201
- case 'ease-out':
1202
- return -1 * (x - 1) ** 2 + 1;
1203
- case 'ease-in-out':
1204
- return (x < 0.5) ? 2 * x ** 2 : 4 * x - 2 * x ** 2 - 1;
1205
- }
1206
- }
1207
- }
1
+ import { Chess, validateFen } from './chess.js';
2
+ import ChessboardConfig from './chessboard.config.js';
3
+ import Square from './chessboard.square.js';
4
+ import Piece from './chessboard.piece.js';
5
+ import Move from './chessboard.move.js';
6
+
7
+ class Chessboard {
8
+
9
+ standard_positions = {
10
+ 'start': 'start',
11
+ 'default': 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
12
+ }
13
+
14
+ error_messages = {
15
+ 'invalid_position': 'Invalid position - ',
16
+ 'invalid_id_div': 'Board id not found - ',
17
+ 'invalid_value': 'Invalid value - ',
18
+ 'invalid_piece': 'Invalid piece - ',
19
+ 'invalid_square': 'Invalid square - ',
20
+ 'invalid_fen': 'Invalid fen - ',
21
+ 'invalid_orientation': 'Invalid orientation - ',
22
+ 'invalid_color': 'Invalid color - ',
23
+ 'invalid_mode': 'Invalid mode - ',
24
+ 'invalid_dropOffBoard': 'Invalid dropOffBoard - ',
25
+ 'invalid_snapbackTime': 'Invalid snapbackTime - ',
26
+ 'invalid_snapbackAnimation': 'Invalid snapbackAnimation - ',
27
+ 'invalid_fadeTime': 'Invalid fadeTime - ',
28
+ 'invalid_fadeAnimation': 'Invalid fadeAnimation - ',
29
+ 'invalid_ratio': 'Invalid ratio - ',
30
+ 'invalid_piecesPath': 'Invalid piecesPath - ',
31
+ 'invalid_onMove': 'Invalid onMove - ',
32
+ 'invalid_onMoveEnd': 'Invalid onMoveEnd - ',
33
+ 'invalid_onChange': 'Invalid onChange - ',
34
+ 'invalid_onDragStart': 'Invalid onDragStart - ',
35
+ 'invalid_onDragMove': 'Invalid onDragMove - ',
36
+ 'invalid_onDrop': 'Invalid onDrop - ',
37
+ 'invalid_onSnapbackEnd': 'Invalid onSnapbackEnd - ',
38
+ 'invalid_whiteSquare': 'Invalid whiteSquare - ',
39
+ 'invalid_blackSquare': 'Invalid blackSquare - ',
40
+ 'invalid_highlight': 'Invalid highlight - ',
41
+ 'invalid_selectedSquareWhite': 'Invalid selectedSquareWhite - ',
42
+ 'invalid_selectedSquareBlack': 'Invalid selectedSquareBlack - ',
43
+ 'invalid_movedSquareWhite': 'Invalid movedSquareWhite - ',
44
+ 'invalid_movedSquareBlack': 'Invalid movedSquareBlack - ',
45
+ 'invalid_choiceSquare': 'Invalid choiceSquare - ',
46
+ 'invalid_coverSquare': 'Invalid coverSquare - ',
47
+ 'invalid_hintColor': 'Invalid hintColor - ',
48
+ }
49
+
50
+ constructor(config) {
51
+ this.config = new ChessboardConfig(config);
52
+ this.init();
53
+ }
54
+
55
+ // Build
56
+
57
+ init() {
58
+ this.initParams();
59
+ this.buildGame(this.config.position);
60
+ this.buildBoard();
61
+ this.buildSquares();
62
+ this.addListeners();
63
+ this.updateBoardPieces();
64
+ }
65
+
66
+ initParams() {
67
+ this.board = null;
68
+ this.squares = {};
69
+ this.promoting = false;
70
+ this.clicked = null;
71
+ this.movesHistory = [];
72
+ this.mosseIndietro = [];
73
+ this.clicked = null;
74
+ }
75
+
76
+ buildGame(position) {
77
+ if (typeof position === 'object') {
78
+ this.game = new Chess('start');
79
+ Object.entries(position).forEach(([square, [type, color]]) => {
80
+ this.game.put({ type, color }, square);
81
+ });
82
+ } else if (Object.values(this.standard_positions).includes(position)) {
83
+ if (position === 'start') this.game = new Chess();
84
+ else this.game = new Chess(this.standard_positions[position]);
85
+ } else if (validateFen(position)) {
86
+ this.game = new Chess(position);
87
+ } else {
88
+ throw new Error(this.error_messages['invalid_position'] + position);
89
+ }
90
+ }
91
+
92
+ buildBoard() {
93
+ this.board = document.getElementById(this.config.id_div);
94
+ if (!this.board) {
95
+ throw new Error(this.error_messages['invalid_id_div'] + this.config.id_div);
96
+ }
97
+ this.resize(this.config.size);
98
+ this.board.className = "board";
99
+ }
100
+
101
+ realCoord(row, col) {
102
+ if (this.isWhiteOriented()) row = 7 - row;
103
+ else col = 7 - col;
104
+ return [row + 1, col + 1];
105
+ }
106
+
107
+ buildSquares() {
108
+
109
+ for (let row = 0; row < 8; row++) {
110
+ for (let col = 0; col < 8; col++) {
111
+
112
+ let [square_row, square_col] = this.realCoord(row, col);
113
+ let square = new Square(square_row, square_col);
114
+ this.squares[square.getId()] = square;
115
+
116
+ this.board.appendChild(square.element);
117
+ }
118
+ }
119
+ }
120
+
121
+ removeBoard() {
122
+
123
+ this.board.innerHTML = '';
124
+ }
125
+
126
+ // Pieces
127
+
128
+ getPiecePath(piece) {
129
+ if (typeof this.config.piecesPath === 'string')
130
+ return this.config.piecesPath + '/' + piece + '.svg';
131
+ else if (typeof this.config.piecesPath === 'object')
132
+ return this.config.piecesPath[piece];
133
+ else if (typeof this.config.piecesPath === 'function')
134
+ return this.config.piecesPath(piece);
135
+ else
136
+ throw new Error(this.error_messages['invalid_piecesPath']);
137
+ }
138
+
139
+ colorPiece(square) {
140
+ let piece = this.piece(square);
141
+ return piece ? piece[1] : null;
142
+ }
143
+
144
+ movePiece(piece, to, duration, callback) {
145
+ piece.translate(to, duration, this.transitionTimingFunction, this.config.moveAnimation, callback);
146
+ }
147
+
148
+ translatePiece(move, removeTo, animate, callback = null) {
149
+
150
+ if (removeTo) this.removePieceFromSquare(move.to, false);
151
+
152
+ let change_square = () => {
153
+ move.from.removePiece();
154
+ move.to.putPiece(move.piece);
155
+ move.piece.setDrag(this.dragFunction(move.to, move.piece));
156
+ if (callback) callback();
157
+ }
158
+
159
+ let duration = animate ? this.config.moveTime : 0;
160
+
161
+ this.movePiece(move.piece, move.to, duration, change_square);
162
+
163
+ }
164
+
165
+ snapbackPiece(square, animate) {
166
+ let move = new Move(square, square);
167
+ this.translatePiece(move, false, animate);
168
+ }
169
+
170
+ convertSquare(square) {
171
+ if (square instanceof Square) return square;
172
+ if (typeof square === 'string' && this.squares[square]) return this.squares[square];
173
+ throw new Error('Invalid square value');
174
+ }
175
+
176
+ removePieceFromSquare(square, fade = true) {
177
+
178
+ square = this.convertSquare(square);
179
+ square.check();
180
+
181
+ let piece = square.piece;
182
+
183
+ if (!piece) throw Error('Square has no piece to remove.')
184
+
185
+ if (fade) piece.fadeOut(
186
+ this.config.fadeTime,
187
+ this.config.fadeAnimation,
188
+ this.transitionTimingFunction);
189
+
190
+ square.removePiece();
191
+
192
+ return piece;
193
+ }
194
+
195
+ dragFunction(square, piece) {
196
+
197
+ return (event) => {
198
+
199
+ event.preventDefault();
200
+
201
+ if (!this.config.draggable || !piece) return;
202
+ if (!this.config.onDragStart(square, piece)) return;
203
+
204
+ let prec;
205
+ let from = square;
206
+ let to = square;
207
+
208
+ const img = piece.element;
209
+
210
+ if (!this.canMove(from)) return;
211
+ if (!this.config.clickable) this.clicked = null;
212
+ if (this.onClick(from)) return;
213
+
214
+ img.style.position = 'absolute';
215
+ img.style.zIndex = 100;
216
+
217
+ const moveAt = (pageX, pageY) => {
218
+ const halfWidth = img.offsetWidth / 2;
219
+ const halfHeight = img.offsetHeight / 2;
220
+ img.style.left = `${pageX - halfWidth}px`;
221
+ img.style.top = `${pageY - halfHeight}px`;
222
+ return true;
223
+ };
224
+
225
+ const onMouseMove = (event) => {
226
+ if (!moveAt(event.pageX, event.pageY)) return;
227
+
228
+ const boardRect = this.board.getBoundingClientRect();
229
+ const { offsetWidth: boardWidth, offsetHeight: boardHeight } = this.board;
230
+ const x = event.clientX - boardRect.left;
231
+ const y = event.clientY - boardRect.top;
232
+
233
+ let newTo = null;
234
+ if (x >= 0 && x <= boardWidth && y >= 0 && y <= boardHeight) {
235
+ const col = Math.floor(x / (boardWidth / 8));
236
+ const row = Math.floor(y / (boardHeight / 8));
237
+ newTo = this.squares[this.getSquareID(row, col)];
238
+ }
239
+
240
+ to = newTo;
241
+ this.config.onDragMove(from, to, piece);
242
+
243
+ if (to !== prec) {
244
+ to?.highlight();
245
+ prec?.dehighlight();
246
+ prec = to;
247
+ }
248
+ };
249
+
250
+ const onMouseUp = () => {
251
+ prec?.dehighlight();
252
+ document.removeEventListener('mousemove', onMouseMove);
253
+ window.removeEventListener('mouseup', onMouseUp);
254
+ img.style.zIndex = 20;
255
+
256
+ const dropResult = this.config.onDrop(from, to, piece);
257
+ const isTrashDrop = !to && (this.config.dropOffBoard === 'trash' || dropResult === 'trash');
258
+
259
+ if (isTrashDrop) {
260
+ this.allSquares("unmoved");
261
+ this.allSquares('removeHint');
262
+ from.deselect();
263
+ this.remove(from);
264
+ } else if (!to || !this.onClick(to, true)) {
265
+ this.snapbackPiece(from, !this.promoting);
266
+ this.config.onSnapbackEnd(from, piece);
267
+ }
268
+ };
269
+
270
+ window.addEventListener('mouseup', onMouseUp, { once: true });
271
+ document.addEventListener('mousemove', onMouseMove);
272
+ img.addEventListener('mouseup', onMouseUp, { once: true });
273
+ }
274
+ }
275
+
276
+ convertPiece(piece) {
277
+ if (piece instanceof Piece) return piece;
278
+ if (typeof piece === 'string') {
279
+ let [type, color] = piece.split('');
280
+ return new Piece(color, type, this.getPiecePath(piece));
281
+ }
282
+ throw new Error(this.error_messages['invalid_piece'] + piece);
283
+ }
284
+
285
+ addPieceOnSquare(square, piece, fade = true) {
286
+
287
+ square.putPiece(piece);
288
+ piece.setDrag(this.dragFunction(square, piece));
289
+
290
+ if (fade) piece.fadeIn(
291
+ this.config.fadeTime,
292
+ this.config.fadeAnimation,
293
+ this.transitionTimingFunction
294
+ );
295
+
296
+ piece.visible();
297
+ }
298
+
299
+ updateBoardPieces(animation = false) {
300
+ let { updatedFlags, escapeFlags, movableFlags, pendingTranslations } = this.prepareBoardUpdateData();
301
+
302
+ this.identifyPieceTranslations(updatedFlags, escapeFlags, movableFlags, pendingTranslations);
303
+
304
+ this.executePieceTranslations(pendingTranslations, escapeFlags, animation);
305
+
306
+ this.processRemainingPieceUpdates(updatedFlags, animation);
307
+ }
308
+
309
+ prepareBoardUpdateData() {
310
+ let updatedFlags = {};
311
+ let escapeFlags = {};
312
+ let movableFlags = {};
313
+ let pendingTranslations = [];
314
+
315
+ for (let squareId in this.squares) {
316
+ let cellPiece = this.squares[squareId].piece;
317
+ let cellPieceId = cellPiece ? cellPiece.getId() : null;
318
+ updatedFlags[squareId] = this.piece(squareId) === cellPieceId;
319
+ escapeFlags[squareId] = false;
320
+ movableFlags[squareId] = cellPiece ? this.piece(squareId) !== cellPieceId : false;
321
+ }
322
+
323
+ return { updatedFlags, escapeFlags, movableFlags, pendingTranslations };
324
+ }
325
+
326
+ identifyPieceTranslations(updatedFlags, escapeFlags, movableFlags, pendingTranslations) {
327
+ Object.values(this.squares).forEach(targetSquare => {
328
+ const newPieceId = this.piece(targetSquare.id);
329
+ const newPiece = newPieceId && this.convertPiece(newPieceId);
330
+ const currentPiece = targetSquare.piece;
331
+ const currentPieceId = currentPiece ? currentPiece.getId() : null;
332
+
333
+ if (currentPieceId === newPieceId || updatedFlags[targetSquare.id]) return;
334
+
335
+ this.evaluateTranslationCandidates(
336
+ targetSquare,
337
+ newPiece,
338
+ currentPiece,
339
+ updatedFlags,
340
+ escapeFlags,
341
+ movableFlags,
342
+ pendingTranslations
343
+ );
344
+ });
345
+ }
346
+
347
+ evaluateTranslationCandidates(targetSquare, newPiece, oldPiece, updatedFlags, escapeFlags, movableFlags, pendingTranslations) {
348
+ if (!newPiece) return;
349
+ const newPieceId = newPiece.getId();
350
+
351
+ for (const sourceSquare of Object.values(this.squares)) {
352
+ if (sourceSquare.id === targetSquare.id || updatedFlags[targetSquare.id]) continue;
353
+
354
+ const sourcePiece = sourceSquare.piece;
355
+ if (!sourcePiece || !movableFlags[sourceSquare.id] || this.isPiece(newPieceId, sourceSquare.id)) continue;
356
+
357
+ if (sourcePiece.id === newPieceId) {
358
+ this.handleTranslationMovement(targetSquare, sourceSquare, oldPiece, sourcePiece, updatedFlags, escapeFlags, movableFlags, pendingTranslations);
359
+ break;
360
+ }
361
+ }
362
+ }
363
+
364
+ handleTranslationMovement(targetSquare, sourceSquare, oldPiece, currentSource, updatedFlags, escapeFlags, movableFlags, pendingTranslations) {
365
+ // Verifica il caso specifico "en passant"
366
+ let lastMove = this.lastMove();
367
+ if (!oldPiece && lastMove && lastMove['captured'] === 'p') {
368
+ this.removePieceFromSquare(this.squares[targetSquare.id[0] + sourceSquare.id[1]]);
369
+ }
370
+
371
+ pendingTranslations.push([currentSource, sourceSquare, targetSquare]);
372
+
373
+ if (!this.piece(sourceSquare.id)) updatedFlags[sourceSquare.id] = true;
374
+
375
+ escapeFlags[sourceSquare.id] = true;
376
+ movableFlags[sourceSquare.id] = false;
377
+ updatedFlags[targetSquare.id] = true;
378
+ }
379
+
380
+ executePieceTranslations(pendingTranslations, escapeFlags, animation) {
381
+ for (let [_, sourceSquare, targetSquare] of pendingTranslations) {
382
+ console.log('executing translation: ', sourceSquare.id, targetSquare.id);
383
+ let removeTarget = !escapeFlags[targetSquare.id] && targetSquare.piece;
384
+ let moveObj = new Move(sourceSquare, targetSquare);
385
+ this.translatePiece(moveObj, removeTarget, animation);
386
+ }
387
+ }
388
+
389
+ // Gestisce gli aggiornamenti residui per ogni cella che non è ancora stata correttamente aggiornata
390
+ processRemainingPieceUpdates(updatedFlags, animation) {
391
+ for (const square of Object.values(this.squares)) {
392
+ let newPieceId = this.piece(square.id);
393
+ let newPiece = newPieceId ? this.convertPiece(newPieceId) : null;
394
+ let currentPiece = square.piece;
395
+ let currentPieceId = currentPiece ? currentPiece.getId() : null;
396
+
397
+ if (currentPieceId !== newPieceId && !updatedFlags[square.id]) {
398
+ this.updateSinglePiece(square, newPiece, updatedFlags, animation);
399
+ }
400
+ }
401
+ }
402
+
403
+ // Aggiorna il pezzo in una cella specifica. Gestisce anche il caso di promozione
404
+ updateSinglePiece(square, newPiece, updatedFlags, animation) {
405
+ if (!updatedFlags[square.id]) {
406
+ let lastMove = this.lastMove();
407
+
408
+ if (lastMove?.promotion) {
409
+ if (lastMove['to'] === square.id) {
410
+
411
+ let move = new Move(this.squares[lastMove['from']], square);
412
+ this.translatePiece(move, true, animation
413
+ , () => {
414
+ move.to.removePiece();
415
+ this.addPieceOnSquare(square, newPiece);
416
+ });
417
+ }
418
+ } else {
419
+ if (square.piece) this.removePieceFromSquare(square);
420
+ if (newPiece) this.addPieceOnSquare(square, newPiece);
421
+ }
422
+ }
423
+ }
424
+
425
+ isPiece(piece, square) { return this.piece(square) === piece }
426
+
427
+ // Listeners
428
+
429
+ addListeners() {
430
+ for (const square of Object.values(this.squares)) {
431
+
432
+ let piece = square.piece;
433
+
434
+ square.element.addEventListener("mouseover", (e) => {
435
+ if (!this.clicked) this.hintMoves(square);
436
+ });
437
+ square.element.addEventListener("mouseout", (e) => {
438
+ if (!this.clicked) this.dehintMoves(square);
439
+ });
440
+
441
+ const handleClick = (e) => {
442
+ e.stopPropagation();
443
+ if (this.config.clickable && (!piece || this.config.onlyLegalMoves)) this.onClick(square)
444
+ }
445
+
446
+ square.element.addEventListener("click", handleClick);
447
+ square.element.addEventListener("touch", handleClick);
448
+ }
449
+ }
450
+
451
+ onClick(square, animation = this.config.moveAnimation) {
452
+
453
+ if (square.id === this.clicked?.id) return false;
454
+
455
+ let from = this.clicked;
456
+ this.clicked = null;
457
+
458
+ let promotion = null
459
+
460
+ if (this.promoting) {
461
+ if (this.promoting === 'none') from = null
462
+ else promotion = this.promoting;
463
+
464
+ this.promoting = false;
465
+ this.allSquares("removePromotion");
466
+ this.allSquares("removeCover");
467
+ }
468
+
469
+ if (!from) {
470
+
471
+ if (this.canMove(square)) {
472
+ square.select();
473
+ this.hintMoves(square);
474
+ this.clicked = square;
475
+ }
476
+
477
+ return false;
478
+ }
479
+
480
+ if (!this.canMove(from)) return false;
481
+
482
+ let move = new Move(from, square, promotion);
483
+
484
+ move.from.deselect();
485
+ this.allSquares("removeHint");
486
+
487
+ if (this.config.onlyLegalMoves && !move.isLegal(this.game)) return false;
488
+
489
+ if (!move.hasPromotion() && this.promote(move)) return false;
490
+
491
+ if (this.config.onMove(move)) {
492
+ this.move(move, animation);
493
+ return true;
494
+ }
495
+
496
+ return false;
497
+ }
498
+
499
+ // Hint
500
+
501
+ hint(square) {
502
+ if (!this.config.hints || !this.squares[square]) return;
503
+ this.squares[square].putHint(this.colorPiece(square) && this.colorPiece(square) !== this.turn());
504
+ }
505
+
506
+ hintMoves(square) {
507
+ if (!this.canMove(square)) return;
508
+ let mosse = this.game.moves({ square: square.id, verbose: true });
509
+ for (let mossa of mosse) {
510
+ if (mossa['to'].length === 2) this.hint(mossa['to']);
511
+ }
512
+ }
513
+
514
+ dehintMoves(square) {
515
+ let mosse = this.game.moves({ square: square.id, verbose: true });
516
+ for (let mossa of mosse) {
517
+ let to = this.squares[mossa['to']];
518
+ to.removeHint();
519
+ }
520
+ }
521
+
522
+ // Moves
523
+
524
+ canMove(square) {
525
+ if (!square.piece) return false;
526
+ if (this.config.movableColors === 'none') return false;
527
+ if (this.config.movableColors === 'w' && square.piece.color === 'b') return false;
528
+ if (this.config.movableColors === 'b' && square.piece.color === 'w') return false;
529
+ if (!this.config.onlyLegalMoves) return true;
530
+ return square.piece.color == this.turn();
531
+ }
532
+
533
+ convertMove(move) {
534
+ if (move instanceof Move) return move;
535
+ if (typeof move == 'string') {
536
+ let fromId = move.slice(0, 2);
537
+ let toId = move.slice(2, 4);
538
+ let promotion = move.slice(4, 5) ? move.slice(4, 5) : null;
539
+ return new Move(this.squares[fromId], this.squares[toId], promotion);
540
+ }
541
+ throw new Error("Invalid move format");
542
+ }
543
+
544
+ allSquares(method) {
545
+ for (const square of Object.values(this.squares)) {
546
+ square[method]();
547
+ this.squares[square.id] = square;
548
+ }
549
+ }
550
+
551
+ legalMove(move) {
552
+ let legal_moves = this.legalMoves(move.from.id);
553
+
554
+ for (let i in legal_moves) {
555
+ if (legal_moves[i]['to'] === move.to.id &&
556
+ move.promotion == legal_moves[i]['promotion'])
557
+ return true;
558
+ }
559
+
560
+ return false;
561
+ }
562
+
563
+ legalMoves(from = null, verb = true) {
564
+ if (from) return this.game.moves({ square: from, verbose: verb });
565
+ return this.game.moves({ verbose: verb });
566
+ }
567
+
568
+ // Position
569
+
570
+ chageFenTurn(fen, color) {
571
+ let parts = fen.split(' ');
572
+ parts[1] = color;
573
+ return parts.join(' ');
574
+ }
575
+
576
+ changeFenColor(fen) {
577
+ let parts = fen.split(' ');
578
+ parts[1] = parts[1] === 'w' ? 'b' : 'w';
579
+ return parts.join(' ');
580
+ }
581
+
582
+ playerTurn() {
583
+ return this.getOrientation() == this.game.turn()
584
+ }
585
+
586
+ isWhiteOriented() { return this.config.orientation === 'w' }
587
+
588
+ // Squares
589
+
590
+ getSquareID(row, col) {
591
+ row = parseInt(row);
592
+ col = parseInt(col);
593
+ if (this.isWhiteOriented()) {
594
+ row = 8 - row;
595
+ col = col + 1;
596
+ } else {
597
+ row = row + 1;
598
+ col = 8 - col;
599
+ }
600
+ let letters = 'abcdefgh';
601
+ let letter = letters[col - 1];
602
+ return letter + row;
603
+ }
604
+
605
+ removeSquares() {
606
+ for (const square of Object.values(this.squares)) {
607
+ this.board.removeChild(square.element);
608
+ square.destroy();
609
+
610
+ }
611
+ this.squares = {};
612
+ }
613
+
614
+ promote(move) {
615
+
616
+ if (!this.config.onlyLegalMoves) return false;
617
+
618
+ let to = move.to;
619
+ let from = move.from;
620
+ let pezzo = this.game.get(from.id);
621
+ let choichable = ['q', 'r', 'b', 'n']
622
+
623
+ if (pezzo['type'] !== 'p' || !(to.row === 1 || to.row === 8)) return false;
624
+
625
+ for (const square of Object.values(this.squares)) {
626
+ let distance = Math.abs(to.row - square.row);
627
+
628
+ if (to.col === square.col && distance <= 3) {
629
+
630
+ let pieceId = choichable[distance] + pezzo['color'];
631
+
632
+ square.putPromotion(
633
+ this.getPiecePath(pieceId),
634
+ () => {
635
+ this.promoting = pieceId[0]
636
+ this.clicked = from;
637
+ this.onClick(to);
638
+ }
639
+ );
640
+ } else
641
+ square.putCover(
642
+ () => {
643
+ this.promoting = 'none';
644
+ this.onClick(square);
645
+ });
646
+ }
647
+
648
+ this.clicked = from.id;
649
+
650
+ return true;
651
+ }
652
+
653
+ transitionTimingFunction(elapsed, duration, type = 'ease') {
654
+ let x = elapsed / duration;
655
+ switch (type) {
656
+ case 'linear':
657
+ return x;
658
+ case 'ease':
659
+ return (x ** 2) * (3 - 2 * x);
660
+ case 'ease-in':
661
+ return x ** 2;
662
+ case 'ease-out':
663
+ return -1 * (x - 1) ** 2 + 1;
664
+ case 'ease-in-out':
665
+ return (x < 0.5) ? 2 * x ** 2 : 4 * x - 2 * x ** 2 - 1;
666
+ }
667
+ }
668
+
669
+ // user
670
+
671
+ turn() {
672
+ return this.game.turn();
673
+ }
674
+
675
+ getOrientation() {
676
+ return this.config.orientation;
677
+ }
678
+
679
+ fen() {
680
+ return this.game.fen();
681
+ }
682
+
683
+ lastMove() {
684
+ return this.movesHistory[this.movesHistory.length - 1];
685
+ }
686
+
687
+ get history() {
688
+ return this.movesHistory;
689
+ }
690
+
691
+ history() {
692
+ return this.movesHistory;
693
+ }
694
+
695
+ get(square) {
696
+ square = this.convertSquare(square);
697
+ square.check();
698
+ let piece = square.piece;
699
+ return piece ? piece.id : null;
700
+ }
701
+
702
+ position(position, color = null) {
703
+ this.allSquares('removeHint');
704
+ this.allSquares("deselect");
705
+ this.allSquares("unmoved");
706
+ if (color && color !== this.config.orientation) {
707
+ position = this.changeFenColor(position);
708
+ this.config.orientation = color;
709
+ this.destroy();
710
+ this.init();
711
+ } else {
712
+ this.buildGame(this.config.position);
713
+ this.updateBoardPieces();
714
+ }
715
+ }
716
+
717
+ flip() {
718
+ let position = this.game.fen();
719
+ this.position(position, this.config.orientation === 'w' ? 'b' : 'w');
720
+ }
721
+
722
+ build() {
723
+ if (this.board) this.destroy();
724
+ this.init();
725
+ }
726
+
727
+ move(move, animation) {
728
+ move = this.convertMove(move);
729
+ move.check();
730
+
731
+ let from = move.from;
732
+ let to = move.to;
733
+
734
+ if (!this.config.onlyLegalMoves) {
735
+ let piece = this.piece(from.id);
736
+ this.game.remove(from.id);
737
+ this.game.remove(to.id);
738
+ this.game.put({ type: move.hasPromotion() ? move.promotion : piece[0], color: piece[1] }, to.id);
739
+ this.updateBoardPieces(animation);
740
+ } else {
741
+ this.allSquares("unmoved");
742
+
743
+ move = this.game.move({
744
+ from: from.id,
745
+ to: to.id,
746
+ promotion: move.hasPromotion() ? move.promotion : undefined
747
+ });
748
+
749
+ if (move === null) {
750
+ throw new Error("Invalid move: move could not be executed");
751
+ }
752
+
753
+ this.movesHistory.push(move);
754
+
755
+ this.updateBoardPieces(animation);
756
+
757
+ from.moved();
758
+ to.moved();
759
+ this.allSquares("removeHint");
760
+
761
+ this.config.onMoveEnd(move);
762
+ }
763
+ }
764
+
765
+ clear(animation = true) {
766
+ this.game.clear();
767
+ this.updateBoardPieces(animation);
768
+ }
769
+
770
+ insert(square, piece) {
771
+ square = this.convertSquare(square);
772
+ piece = this.convertPiece(piece);
773
+ square.check();
774
+ piece.check();
775
+ if (square.piece) this.remove(square);
776
+ this.game.put({ type: piece.type, color: piece.color }, square.id);
777
+ this.updateBoardPieces();
778
+ }
779
+
780
+ isGameOver() {
781
+ if (this.game.isGameOver()) {
782
+ if (this.game.inCheck()) return this.game.turn() === 'w' ? 'b' : 'w';
783
+ return 'd';
784
+ }
785
+ return null;
786
+ }
787
+
788
+ orientation(color) {
789
+ if ((color === 'w' || color === 'b') && color !== this.config.orientation) this.flip();
790
+ }
791
+
792
+ resize(value) {
793
+ if (value === 'auto') {
794
+ let size;
795
+ if (this.board.offsetWidth === 0) {
796
+ size = this.board.offsetHeight;
797
+ } else if (this.board.offsetHeight === 0) {
798
+ size = this.board.offsetWidth;
799
+ } else {
800
+ size = Math.min(this.board.offsetWidth, this.board.offsetHeight);
801
+ }
802
+ this.resize(size);
803
+ } else if (typeof value !== 'number') {
804
+ throw new Error(this.error_messages['invalid_value'] + value);
805
+ } else {
806
+ document.documentElement.style.setProperty('--dimBoard', value + 'px');
807
+ this.updateBoardPieces();
808
+ }
809
+ }
810
+
811
+ destroy() {
812
+ this.removeSquares();
813
+ this.removeBoard();
814
+ this.game = null;
815
+ this.clicked = null;
816
+ this.movesHistory = [];
817
+ this.mosseIndietro = [];
818
+ this.clicked = null;
819
+ this.board = null;
820
+ }
821
+
822
+ remove(square, animation = true) {
823
+ square = this.convertSquare(square);
824
+ square.check();
825
+ this.game.remove(square.id);
826
+ let piece = square.piece;
827
+ this.updateBoardPieces(animation);
828
+ return piece;
829
+ }
830
+
831
+ piece(square) {
832
+ let piece = this.game.get(square);
833
+ return piece ? piece['type'] + piece['color'] : null;
834
+ }
835
+
836
+ highlight(square) {
837
+ square = this.convertSquare(square);
838
+ square.check();
839
+ square.highlight();
840
+ }
841
+
842
+ dehighlight(square) {
843
+ square = this.convertSquare(square);
844
+ square.check();
845
+ square.dehighlight();
846
+ }
847
+
848
+ }
849
+
850
+ export default Chessboard;