@alepot55/chessboardjs 1.0.0

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