chesscademy 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1742 @@
1
+ /*!
2
+ * chessboard.js v0.3.0
3
+ *
4
+ * Copyright 2013 Chris Oakman
5
+ * Released under the MIT license
6
+ * http://chessboardjs.com/license
7
+ *
8
+ * Date: 10 Aug 2013
9
+ */
10
+
11
+ // start anonymous scope
12
+ ;(function() {
13
+ 'use strict';
14
+
15
+ //------------------------------------------------------------------------------
16
+ // Chess Util Functions
17
+ //------------------------------------------------------------------------------
18
+ var COLUMNS = 'abcdefgh'.split('');
19
+
20
+ function validMove(move) {
21
+ // move should be a string
22
+ if (typeof move !== 'string') return false;
23
+
24
+ // move should be in the form of "e2-e4", "f6-d5"
25
+ var tmp = move.split('-');
26
+ if (tmp.length !== 2) return false;
27
+
28
+ return (validSquare(tmp[0]) === true && validSquare(tmp[1]) === true);
29
+ }
30
+
31
+ function validSquare(square) {
32
+ if (typeof square !== 'string') return false;
33
+ return (square.search(/^[a-h][1-8]$/) !== -1);
34
+ }
35
+
36
+ function validPieceCode(code) {
37
+ if (typeof code !== 'string') return false;
38
+ return (code.search(/^[bw][KQRNBP]$/) !== -1);
39
+ }
40
+
41
+ // TODO: this whole function could probably be replaced with a single regex
42
+ function validFen(fen) {
43
+ if (typeof fen !== 'string') return false;
44
+
45
+ // cut off any move, castling, etc info from the end
46
+ // we're only interested in position information
47
+ fen = fen.replace(/ .+$/, '');
48
+
49
+ // FEN should be 8 sections separated by slashes
50
+ var chunks = fen.split('/');
51
+ if (chunks.length !== 8) return false;
52
+
53
+ // check the piece sections
54
+ for (var i = 0; i < 8; i++) {
55
+ if (chunks[i] === '' ||
56
+ chunks[i].length > 8 ||
57
+ chunks[i].search(/[^kqrbnpKQRNBP1-8]/) !== -1) {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ return true;
63
+ }
64
+
65
+ function validPositionObject(pos) {
66
+ if (typeof pos !== 'object') return false;
67
+
68
+ for (var i in pos) {
69
+ if (pos.hasOwnProperty(i) !== true) continue;
70
+
71
+ if (validSquare(i) !== true || validPieceCode(pos[i]) !== true) {
72
+ return false;
73
+ }
74
+ }
75
+
76
+ return true;
77
+ }
78
+
79
+ // convert FEN piece code to bP, wK, etc
80
+ function fenToPieceCode(piece) {
81
+ // black piece
82
+ if (piece.toLowerCase() === piece) {
83
+ return 'b' + piece.toUpperCase();
84
+ }
85
+
86
+ // white piece
87
+ return 'w' + piece.toUpperCase();
88
+ }
89
+
90
+ // convert bP, wK, etc code to FEN structure
91
+ function pieceCodeToFen(piece) {
92
+ var tmp = piece.split('');
93
+
94
+ // white piece
95
+ if (tmp[0] === 'w') {
96
+ return tmp[1].toUpperCase();
97
+ }
98
+
99
+ // black piece
100
+ return tmp[1].toLowerCase();
101
+ }
102
+
103
+ // convert FEN string to position object
104
+ // returns false if the FEN string is invalid
105
+ function fenToObj(fen) {
106
+ if (validFen(fen) !== true) {
107
+ return false;
108
+ }
109
+
110
+ // cut off any move, castling, etc info from the end
111
+ // we're only interested in position information
112
+ fen = fen.replace(/ .+$/, '');
113
+
114
+ var rows = fen.split('/');
115
+ var position = {};
116
+
117
+ var currentRow = 8;
118
+ for (var i = 0; i < 8; i++) {
119
+ var row = rows[i].split('');
120
+ var colIndex = 0;
121
+
122
+ // loop through each character in the FEN section
123
+ for (var j = 0; j < row.length; j++) {
124
+ // number / empty squares
125
+ if (row[j].search(/[1-8]/) !== -1) {
126
+ var emptySquares = parseInt(row[j], 10);
127
+ colIndex += emptySquares;
128
+ }
129
+ // piece
130
+ else {
131
+ var square = COLUMNS[colIndex] + currentRow;
132
+ position[square] = fenToPieceCode(row[j]);
133
+ colIndex++;
134
+ }
135
+ }
136
+
137
+ currentRow--;
138
+ }
139
+
140
+ return position;
141
+ }
142
+
143
+ // position object to FEN string
144
+ // returns false if the obj is not a valid position object
145
+ function objToFen(obj) {
146
+ if (validPositionObject(obj) !== true) {
147
+ return false;
148
+ }
149
+
150
+ var fen = '';
151
+
152
+ var currentRow = 8;
153
+ for (var i = 0; i < 8; i++) {
154
+ for (var j = 0; j < 8; j++) {
155
+ var square = COLUMNS[j] + currentRow;
156
+
157
+ // piece exists
158
+ if (obj.hasOwnProperty(square) === true) {
159
+ fen += pieceCodeToFen(obj[square]);
160
+ }
161
+
162
+ // empty space
163
+ else {
164
+ fen += '1';
165
+ }
166
+ }
167
+
168
+ if (i !== 7) {
169
+ fen += '/';
170
+ }
171
+
172
+ currentRow--;
173
+ }
174
+
175
+ // squeeze the numbers together
176
+ // haha, I love this solution...
177
+ fen = fen.replace(/11111111/g, '8');
178
+ fen = fen.replace(/1111111/g, '7');
179
+ fen = fen.replace(/111111/g, '6');
180
+ fen = fen.replace(/11111/g, '5');
181
+ fen = fen.replace(/1111/g, '4');
182
+ fen = fen.replace(/111/g, '3');
183
+ fen = fen.replace(/11/g, '2');
184
+
185
+ return fen;
186
+ }
187
+
188
+ window['ChessBoard'] = window['ChessBoard'] || function(containerElOrId, cfg) {
189
+ 'use strict';
190
+
191
+ cfg = cfg || {};
192
+
193
+ //------------------------------------------------------------------------------
194
+ // Constants
195
+ //------------------------------------------------------------------------------
196
+
197
+ var MINIMUM_JQUERY_VERSION = '1.7.0',
198
+ START_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR',
199
+ START_POSITION = fenToObj(START_FEN);
200
+
201
+ // use unique class names to prevent clashing with anything else on the page
202
+ // and simplify selectors
203
+ var CSS = {
204
+ alpha: 'alpha-d2270',
205
+ black: 'black-3c85d',
206
+ board: 'board-b72b1',
207
+ chessboard: 'chessboard-63f37',
208
+ clearfix: 'clearfix-7da63',
209
+ highlight1: 'highlight1-32417',
210
+ highlight2: 'highlight2-9c5d2',
211
+ notation: 'notation-322f9',
212
+ numeric: 'numeric-fc462',
213
+ piece: 'piece-417db',
214
+ row: 'row-5277c',
215
+ sparePieces: 'spare-pieces-7492f',
216
+ sparePiecesBottom: 'spare-pieces-bottom-ae20f',
217
+ sparePiecesTop: 'spare-pieces-top-4028b',
218
+ square: 'square-55d63',
219
+ white: 'white-1e1d7'
220
+ };
221
+
222
+ //------------------------------------------------------------------------------
223
+ // Module Scope Variables
224
+ //------------------------------------------------------------------------------
225
+
226
+ // DOM elements
227
+ var containerEl,
228
+ boardEl,
229
+ draggedPieceEl,
230
+ sparePiecesTopEl,
231
+ sparePiecesBottomEl;
232
+
233
+ // constructor return object
234
+ var widget = {};
235
+
236
+ //------------------------------------------------------------------------------
237
+ // Stateful
238
+ //------------------------------------------------------------------------------
239
+
240
+ var ANIMATION_HAPPENING = false,
241
+ BOARD_BORDER_SIZE = 2,
242
+ CURRENT_ORIENTATION = 'white',
243
+ CURRENT_POSITION = {},
244
+ SQUARE_SIZE,
245
+ DRAGGED_PIECE,
246
+ DRAGGED_PIECE_LOCATION,
247
+ DRAGGED_PIECE_SOURCE,
248
+ DRAGGING_A_PIECE = false,
249
+ SPARE_PIECE_ELS_IDS = {},
250
+ SQUARE_ELS_IDS = {},
251
+ SQUARE_ELS_OFFSETS;
252
+
253
+ //------------------------------------------------------------------------------
254
+ // JS Util Functions
255
+ //------------------------------------------------------------------------------
256
+
257
+ // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
258
+ function createId() {
259
+ return 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function(c) {
260
+ var r = Math.random() * 16 | 0;
261
+ return r.toString(16);
262
+ });
263
+ }
264
+
265
+ function deepCopy(thing) {
266
+ return JSON.parse(JSON.stringify(thing));
267
+ }
268
+
269
+ function parseSemVer(version) {
270
+ var tmp = version.split('.');
271
+ return {
272
+ major: parseInt(tmp[0], 10),
273
+ minor: parseInt(tmp[1], 10),
274
+ patch: parseInt(tmp[2], 10)
275
+ };
276
+ }
277
+
278
+ // returns true if version is >= minimum
279
+ function compareSemVer(version, minimum) {
280
+ version = parseSemVer(version);
281
+ minimum = parseSemVer(minimum);
282
+
283
+ var versionNum = (version.major * 10000 * 10000) +
284
+ (version.minor * 10000) + version.patch;
285
+ var minimumNum = (minimum.major * 10000 * 10000) +
286
+ (minimum.minor * 10000) + minimum.patch;
287
+
288
+ return (versionNum >= minimumNum);
289
+ }
290
+
291
+ //------------------------------------------------------------------------------
292
+ // Validation / Errors
293
+ //------------------------------------------------------------------------------
294
+
295
+ function error(code, msg, obj) {
296
+ // do nothing if showErrors is not set
297
+ if (cfg.hasOwnProperty('showErrors') !== true ||
298
+ cfg.showErrors === false) {
299
+ return;
300
+ }
301
+
302
+ var errorText = 'ChessBoard Error ' + code + ': ' + msg;
303
+
304
+ // print to console
305
+ if (cfg.showErrors === 'console' &&
306
+ typeof console === 'object' &&
307
+ typeof console.log === 'function') {
308
+ console.log(errorText);
309
+ if (arguments.length >= 2) {
310
+ console.log(obj);
311
+ }
312
+ return;
313
+ }
314
+
315
+ // alert errors
316
+ if (cfg.showErrors === 'alert') {
317
+ if (obj) {
318
+ errorText += '\n\n' + JSON.stringify(obj);
319
+ }
320
+ console.log(errorText);
321
+ return;
322
+ }
323
+
324
+ // custom function
325
+ if (typeof cfg.showErrors === 'function') {
326
+ cfg.showErrors(code, msg, obj);
327
+ }
328
+ }
329
+
330
+ // check dependencies
331
+ function checkDeps() {
332
+ // if containerId is a string, it must be the ID of a DOM node
333
+ if (typeof containerElOrId === 'string') {
334
+ // cannot be empty
335
+ if (containerElOrId === '') {
336
+ console.log('ChessBoard Error 1001: ' +
337
+ 'The first argument to ChessBoard() cannot be an empty string.' +
338
+ '\n\nExiting...');
339
+ return false;
340
+ }
341
+
342
+ // make sure the container element exists in the DOM
343
+ var el = document.getElementById(containerElOrId);
344
+ if (! el) {
345
+ console.log('ChessBoard Error 1002: Element with id "' +
346
+ containerElOrId + '" does not exist in the DOM.' +
347
+ '\n\nExiting...');
348
+ return false;
349
+ }
350
+
351
+ // set the containerEl
352
+ containerEl = $(el);
353
+ }
354
+
355
+ // else it must be something that becomes a jQuery collection
356
+ // with size 1
357
+ // ie: a single DOM node or jQuery object
358
+ else {
359
+ containerEl = $(containerElOrId);
360
+
361
+ if (containerEl.length !== 1) {
362
+ console.log('ChessBoard Error 1003: The first argument to ' +
363
+ 'ChessBoard() must be an ID or a single DOM node.' +
364
+ '\n\nExiting...');
365
+ return false;
366
+ }
367
+ }
368
+
369
+ // JSON must exist
370
+ if (! window.JSON ||
371
+ typeof JSON.stringify !== 'function' ||
372
+ typeof JSON.parse !== 'function') {
373
+ console.log('ChessBoard Error 1004: JSON does not exist. ' +
374
+ 'Please include a JSON polyfill.\n\nExiting...');
375
+ return false;
376
+ }
377
+
378
+ // check for a compatible version of jQuery
379
+ if (! (typeof window.$ && $.fn && $.fn.jquery &&
380
+ compareSemVer($.fn.jquery, MINIMUM_JQUERY_VERSION) === true)) {
381
+ console.log('ChessBoard Error 1005: Unable to find a valid version ' +
382
+ 'of jQuery. Please include jQuery ' + MINIMUM_JQUERY_VERSION + ' or ' +
383
+ 'higher on the page.\n\nExiting...');
384
+ return false;
385
+ }
386
+
387
+ return true;
388
+ }
389
+
390
+ function validAnimationSpeed(speed) {
391
+ if (speed === 'fast' || speed === 'slow') {
392
+ return true;
393
+ }
394
+
395
+ if ((parseInt(speed, 10) + '') !== (speed + '')) {
396
+ return false;
397
+ }
398
+
399
+ return (speed >= 0);
400
+ }
401
+
402
+ // validate config / set default options
403
+ function expandConfig() {
404
+ if (typeof cfg === 'string' || validPositionObject(cfg) === true) {
405
+ cfg = {
406
+ position: cfg
407
+ };
408
+ }
409
+
410
+ // default for orientation is white
411
+ if (cfg.orientation !== 'black') {
412
+ cfg.orientation = 'white';
413
+ }
414
+ CURRENT_ORIENTATION = cfg.orientation;
415
+
416
+ // default for showNotation is true
417
+ if (cfg.showNotation !== false) {
418
+ cfg.showNotation = true;
419
+ }
420
+
421
+ // default for draggable is false
422
+ if (cfg.draggable !== true) {
423
+ cfg.draggable = false;
424
+ }
425
+
426
+ // default for dropOffBoard is 'snapback'
427
+ if (cfg.dropOffBoard !== 'trash') {
428
+ cfg.dropOffBoard = 'snapback';
429
+ }
430
+
431
+ // default for sparePieces is false
432
+ if (cfg.sparePieces !== true) {
433
+ cfg.sparePieces = false;
434
+ }
435
+
436
+ // draggable must be true if sparePieces is enabled
437
+ if (cfg.sparePieces === true) {
438
+ cfg.draggable = true;
439
+ }
440
+
441
+ // default piece theme is wikipedia
442
+ if (cfg.hasOwnProperty('pieceTheme') !== true ||
443
+ (typeof cfg.pieceTheme !== 'string' &&
444
+ typeof cfg.pieceTheme !== 'function')) {
445
+ cfg.pieceTheme = '/img/chesspieces/wikipedia/{piece}.png';
446
+ }
447
+
448
+ // animation speeds
449
+ if (cfg.hasOwnProperty('appearSpeed') !== true ||
450
+ validAnimationSpeed(cfg.appearSpeed) !== true) {
451
+ cfg.appearSpeed = 200;
452
+ }
453
+ if (cfg.hasOwnProperty('moveSpeed') !== true ||
454
+ validAnimationSpeed(cfg.moveSpeed) !== true) {
455
+ cfg.moveSpeed = 200;
456
+ }
457
+ if (cfg.hasOwnProperty('snapbackSpeed') !== true ||
458
+ validAnimationSpeed(cfg.snapbackSpeed) !== true) {
459
+ cfg.snapbackSpeed = 50;
460
+ }
461
+ if (cfg.hasOwnProperty('snapSpeed') !== true ||
462
+ validAnimationSpeed(cfg.snapSpeed) !== true) {
463
+ cfg.snapSpeed = 25;
464
+ }
465
+ if (cfg.hasOwnProperty('trashSpeed') !== true ||
466
+ validAnimationSpeed(cfg.trashSpeed) !== true) {
467
+ cfg.trashSpeed = 100;
468
+ }
469
+
470
+ // make sure position is valid
471
+ if (cfg.hasOwnProperty('position') === true) {
472
+ if (cfg.position === 'start') {
473
+ CURRENT_POSITION = deepCopy(START_POSITION);
474
+ }
475
+
476
+ else if (validFen(cfg.position) === true) {
477
+ CURRENT_POSITION = fenToObj(cfg.position);
478
+ }
479
+
480
+ else if (validPositionObject(cfg.position) === true) {
481
+ CURRENT_POSITION = deepCopy(cfg.position);
482
+ }
483
+
484
+ else {
485
+ error(7263, 'Invalid value passed to config.position.', cfg.position);
486
+ }
487
+ }
488
+
489
+ return true;
490
+ }
491
+
492
+ //------------------------------------------------------------------------------
493
+ // DOM Misc
494
+ //------------------------------------------------------------------------------
495
+
496
+ // calculates square size based on the width of the container
497
+ // got a little CSS black magic here, so let me explain:
498
+ // get the width of the container element (could be anything), reduce by 1 for
499
+ // fudge factor, and then keep reducing until we find an exact mod 8 for
500
+ // our square size
501
+ function calculateSquareSize() {
502
+ var containerWidth = parseInt(containerEl.css('width'), 10);
503
+
504
+ // defensive, prevent infinite loop
505
+ if (! containerWidth || containerWidth <= 0) {
506
+ return 0;
507
+ }
508
+
509
+ // pad one pixel
510
+ var boardWidth = containerWidth - 1;
511
+
512
+ while (boardWidth % 8 !== 0 && boardWidth > 0) {
513
+ boardWidth--;
514
+ }
515
+
516
+ return (boardWidth / 8);
517
+ }
518
+
519
+ // create random IDs for elements
520
+ function createElIds() {
521
+ // squares on the board
522
+ for (var i = 0; i < COLUMNS.length; i++) {
523
+ for (var j = 1; j <= 8; j++) {
524
+ var square = COLUMNS[i] + j;
525
+ SQUARE_ELS_IDS[square] = square + '-' + createId();
526
+ }
527
+ }
528
+
529
+ // spare pieces
530
+ var pieces = 'KQRBNP'.split('');
531
+ for (var i = 0; i < pieces.length; i++) {
532
+ var whitePiece = 'w' + pieces[i];
533
+ var blackPiece = 'b' + pieces[i];
534
+ SPARE_PIECE_ELS_IDS[whitePiece] = whitePiece + '-' + createId();
535
+ SPARE_PIECE_ELS_IDS[blackPiece] = blackPiece + '-' + createId();
536
+ }
537
+ }
538
+
539
+ //------------------------------------------------------------------------------
540
+ // Markup Building
541
+ //------------------------------------------------------------------------------
542
+
543
+ function buildBoardContainer() {
544
+ var html = '<div class="' + CSS.chessboard + '">';
545
+
546
+ if (cfg.sparePieces === true) {
547
+ html += '<div class="' + CSS.sparePieces + ' ' +
548
+ CSS.sparePiecesTop + '"></div>';
549
+ }
550
+
551
+ html += '<div class="' + CSS.board + '"></div>';
552
+
553
+ if (cfg.sparePieces === true) {
554
+ html += '<div class="' + CSS.sparePieces + ' ' +
555
+ CSS.sparePiecesBottom + '"></div>';
556
+ }
557
+
558
+ html += '</div>';
559
+
560
+ return html;
561
+ }
562
+
563
+ /*
564
+ var buildSquare = function(color, size, id) {
565
+ var html = '<div class="' + CSS.square + ' ' + CSS[color] + '" ' +
566
+ 'style="width: ' + size + 'px; height: ' + size + 'px" ' +
567
+ 'id="' + id + '">';
568
+
569
+ if (cfg.showNotation === true) {
570
+
571
+ }
572
+
573
+ html += '</div>';
574
+
575
+ return html;
576
+ };
577
+ */
578
+
579
+ function buildBoard(orientation) {
580
+ if (orientation !== 'black') {
581
+ orientation = 'white';
582
+ }
583
+
584
+ var html = '';
585
+
586
+ // algebraic notation / orientation
587
+ var alpha = deepCopy(COLUMNS);
588
+ var row = 8;
589
+ if (orientation === 'black') {
590
+ alpha.reverse();
591
+ row = 1;
592
+ }
593
+
594
+ var squareColor = 'white';
595
+ for (var i = 0; i < 8; i++) {
596
+ html += '<div class="' + CSS.row + '">';
597
+ for (var j = 0; j < 8; j++) {
598
+ var square = alpha[j] + row;
599
+
600
+ html += '<div class="' + CSS.square + ' ' + CSS[squareColor] + ' ' +
601
+ 'square-' + square + '" ' +
602
+ 'style="width: ' + SQUARE_SIZE + 'px; height: ' + SQUARE_SIZE + 'px" ' +
603
+ 'id="' + SQUARE_ELS_IDS[square] + '" ' +
604
+ 'data-square="' + square + '">';
605
+
606
+ if (cfg.showNotation === true) {
607
+ // alpha notation
608
+ if ((orientation === 'white' && row === 1) ||
609
+ (orientation === 'black' && row === 8)) {
610
+ html += '<div class="' + CSS.notation + ' ' + CSS.alpha + '">' +
611
+ alpha[j] + '</div>';
612
+ }
613
+
614
+ // numeric notation
615
+ if (j === 0) {
616
+ html += '<div class="' + CSS.notation + ' ' + CSS.numeric + '">' +
617
+ row + '</div>';
618
+ }
619
+ }
620
+
621
+ html += '</div>'; // end .square
622
+
623
+ squareColor = (squareColor === 'white' ? 'black' : 'white');
624
+ }
625
+ html += '<div class="' + CSS.clearfix + '"></div></div>';
626
+
627
+ squareColor = (squareColor === 'white' ? 'black' : 'white');
628
+
629
+ if (orientation === 'white') {
630
+ row--;
631
+ }
632
+ else {
633
+ row++;
634
+ }
635
+ }
636
+
637
+ return html;
638
+ }
639
+
640
+ var imgCache = {}
641
+ function cacheImages() {
642
+ var pieces = ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP', 'bK', 'bQ', 'bR', 'bB', 'bN', 'bP'];
643
+ pieces.forEach(function(piece) {
644
+ var img = new Image()
645
+ img.onload = function() {
646
+ imgCache[piece] = getBase64Image(img)
647
+ }
648
+ img.src = buildPieceImgSrc(piece)
649
+ })
650
+
651
+ function getBase64Image(img) {
652
+ var canvas = document.createElement("canvas");
653
+ canvas.width = img.width;
654
+ canvas.height = img.height;
655
+ var ctx = canvas.getContext("2d");
656
+ ctx.drawImage(img, 0, 0);
657
+ var dataURL = canvas.toDataURL("image/png");
658
+ return dataURL;
659
+ }
660
+ }
661
+
662
+ function buildPieceImgSrc(piece) {
663
+ if(imgCache[piece]) return imgCache[piece]
664
+ else return getUrl(piece)
665
+
666
+ function getUrl(piece) {
667
+ if (typeof cfg.pieceTheme === 'function') {
668
+ return cfg.pieceTheme(piece);
669
+ }
670
+
671
+ if (typeof cfg.pieceTheme === 'string') {
672
+ return cfg.pieceTheme.replace(/{piece}/g, piece);
673
+ }
674
+
675
+ // NOTE: this should never happen
676
+ error(8272, 'Unable to build image source for cfg.pieceTheme.');
677
+ return '';
678
+ }
679
+ }
680
+
681
+ function buildPiece(piece, hidden, id) {
682
+ var html = '<img src="' + buildPieceImgSrc(piece) + '" ';
683
+ if (id && typeof id === 'string') {
684
+ html += 'id="' + id + '" ';
685
+ }
686
+ html += 'alt="" ' +
687
+ 'class="' + CSS.piece + '" ' +
688
+ 'data-piece="' + piece + '" ' +
689
+ 'style="width: ' + SQUARE_SIZE + 'px;' +
690
+ 'height: ' + SQUARE_SIZE + 'px;';
691
+ if (hidden === true) {
692
+ html += 'display:none;';
693
+ }
694
+ html += '" />';
695
+
696
+ return html;
697
+ }
698
+
699
+ function buildSparePieces(color) {
700
+ var pieces = ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP'];
701
+ if (color === 'black') {
702
+ pieces = ['bK', 'bQ', 'bR', 'bB', 'bN', 'bP'];
703
+ }
704
+
705
+ var html = '';
706
+ for (var i = 0; i < pieces.length; i++) {
707
+ html += buildPiece(pieces[i], false, SPARE_PIECE_ELS_IDS[pieces[i]]);
708
+ }
709
+
710
+ return html;
711
+ }
712
+
713
+ //------------------------------------------------------------------------------
714
+ // Animations
715
+ //------------------------------------------------------------------------------
716
+
717
+ function animateSquareToSquare(src, dest, piece, completeFn) {
718
+ // get information about the source and destination squares
719
+ var srcSquareEl = $('#' + SQUARE_ELS_IDS[src]);
720
+ var srcSquarePosition = srcSquareEl.offset();
721
+ var destSquareEl = $('#' + SQUARE_ELS_IDS[dest]);
722
+ var destSquarePosition = destSquareEl.offset();
723
+
724
+ // create the animated piece and absolutely position it
725
+ // over the source square
726
+ var animatedPieceId = createId();
727
+ $('body').append(buildPiece(piece, true, animatedPieceId));
728
+ var animatedPieceEl = $('#' + animatedPieceId);
729
+ animatedPieceEl.css({
730
+ display: '',
731
+ position: 'absolute',
732
+ top: srcSquarePosition.top,
733
+ left: srcSquarePosition.left
734
+ });
735
+
736
+ // remove original piece from source square
737
+ srcSquareEl.find('.' + CSS.piece).remove();
738
+
739
+ // on complete
740
+ var complete = function() {
741
+ // add the "real" piece to the destination square
742
+ destSquareEl.append(buildPiece(piece));
743
+
744
+ // remove the animated piece
745
+ animatedPieceEl.remove();
746
+
747
+ // run complete function
748
+ if (typeof completeFn === 'function') {
749
+ completeFn();
750
+ }
751
+ };
752
+
753
+ // animate the piece to the destination square
754
+ var opts = {
755
+ duration: cfg.moveSpeed,
756
+ complete: complete
757
+ };
758
+ animatedPieceEl.animate(destSquarePosition, opts);
759
+ }
760
+
761
+ function animateSparePieceToSquare(piece, dest, completeFn) {
762
+ var srcOffset = $('#' + SPARE_PIECE_ELS_IDS[piece]).offset();
763
+ var destSquareEl = $('#' + SQUARE_ELS_IDS[dest]);
764
+ var destOffset = destSquareEl.offset();
765
+
766
+ // create the animate piece
767
+ var pieceId = createId();
768
+ $('body').append(buildPiece(piece, true, pieceId));
769
+ var animatedPieceEl = $('#' + pieceId);
770
+ animatedPieceEl.css({
771
+ display: '',
772
+ position: 'absolute',
773
+ left: srcOffset.left,
774
+ top: srcOffset.top
775
+ });
776
+
777
+ // on complete
778
+ var complete = function() {
779
+ // add the "real" piece to the destination square
780
+ destSquareEl.find('.' + CSS.piece).remove();
781
+ destSquareEl.append(buildPiece(piece));
782
+
783
+ // remove the animated piece
784
+ animatedPieceEl.remove();
785
+
786
+ // run complete function
787
+ if (typeof completeFn === 'function') {
788
+ completeFn();
789
+ }
790
+ };
791
+
792
+ // animate the piece to the destination square
793
+ var opts = {
794
+ duration: cfg.moveSpeed,
795
+ complete: complete
796
+ };
797
+ animatedPieceEl.animate(destOffset, opts);
798
+ }
799
+
800
+ // execute an array of animations
801
+ function doAnimations(a, oldPos, newPos) {
802
+ ANIMATION_HAPPENING = true;
803
+
804
+ var numFinished = 0;
805
+ function onFinish() {
806
+ numFinished++;
807
+
808
+ // exit if all the animations aren't finished
809
+ if (numFinished !== a.length) return;
810
+
811
+ drawPositionInstant();
812
+ ANIMATION_HAPPENING = false;
813
+
814
+ // run their onMoveEnd function
815
+ if (cfg.hasOwnProperty('onMoveEnd') === true &&
816
+ typeof cfg.onMoveEnd === 'function') {
817
+ cfg.onMoveEnd(deepCopy(oldPos), deepCopy(newPos));
818
+ }
819
+ }
820
+
821
+ for (var i = 0; i < a.length; i++) {
822
+ // clear a piece
823
+ if (a[i].type === 'clear') {
824
+ $('#' + SQUARE_ELS_IDS[a[i].square] + ' .' + CSS.piece)
825
+ .fadeOut(cfg.trashSpeed, onFinish);
826
+ }
827
+
828
+ // add a piece (no spare pieces)
829
+ if (a[i].type === 'add' && cfg.sparePieces !== true) {
830
+ $('#' + SQUARE_ELS_IDS[a[i].square])
831
+ .append(buildPiece(a[i].piece, true))
832
+ .find('.' + CSS.piece)
833
+ .fadeIn(cfg.appearSpeed, onFinish);
834
+ }
835
+
836
+ // add a piece from a spare piece
837
+ if (a[i].type === 'add' && cfg.sparePieces === true) {
838
+ animateSparePieceToSquare(a[i].piece, a[i].square, onFinish);
839
+ }
840
+
841
+ // move a piece
842
+ if (a[i].type === 'move') {
843
+ animateSquareToSquare(a[i].source, a[i].destination, a[i].piece,
844
+ onFinish);
845
+ }
846
+ }
847
+ }
848
+
849
+ // returns the distance between two squares
850
+ function squareDistance(s1, s2) {
851
+ s1 = s1.split('');
852
+ var s1x = COLUMNS.indexOf(s1[0]) + 1;
853
+ var s1y = parseInt(s1[1], 10);
854
+
855
+ s2 = s2.split('');
856
+ var s2x = COLUMNS.indexOf(s2[0]) + 1;
857
+ var s2y = parseInt(s2[1], 10);
858
+
859
+ var xDelta = Math.abs(s1x - s2x);
860
+ var yDelta = Math.abs(s1y - s2y);
861
+
862
+ if (xDelta >= yDelta) return xDelta;
863
+ return yDelta;
864
+ }
865
+
866
+ // returns an array of closest squares from square
867
+ function createRadius(square) {
868
+ var squares = [];
869
+
870
+ // calculate distance of all squares
871
+ for (var i = 0; i < 8; i++) {
872
+ for (var j = 0; j < 8; j++) {
873
+ var s = COLUMNS[i] + (j + 1);
874
+
875
+ // skip the square we're starting from
876
+ if (square === s) continue;
877
+
878
+ squares.push({
879
+ square: s,
880
+ distance: squareDistance(square, s)
881
+ });
882
+ }
883
+ }
884
+
885
+ // sort by distance
886
+ squares.sort(function(a, b) {
887
+ return a.distance - b.distance;
888
+ });
889
+
890
+ // just return the square code
891
+ var squares2 = [];
892
+ for (var i = 0; i < squares.length; i++) {
893
+ squares2.push(squares[i].square);
894
+ }
895
+
896
+ return squares2;
897
+ }
898
+
899
+ // returns the square of the closest instance of piece
900
+ // returns false if no instance of piece is found in position
901
+ function findClosestPiece(position, piece, square) {
902
+ // create array of closest squares from square
903
+ var closestSquares = createRadius(square);
904
+
905
+ // search through the position in order of distance for the piece
906
+ for (var i = 0; i < closestSquares.length; i++) {
907
+ var s = closestSquares[i];
908
+
909
+ if (position.hasOwnProperty(s) === true && position[s] === piece) {
910
+ return s;
911
+ }
912
+ }
913
+
914
+ return false;
915
+ }
916
+
917
+ // calculate an array of animations that need to happen in order to get
918
+ // from pos1 to pos2
919
+ function calculateAnimations(pos1, pos2) {
920
+ // make copies of both
921
+ pos1 = deepCopy(pos1);
922
+ pos2 = deepCopy(pos2);
923
+
924
+ var animations = [];
925
+ var squaresMovedTo = {};
926
+
927
+ // remove pieces that are the same in both positions
928
+ for (var i in pos2) {
929
+ if (pos2.hasOwnProperty(i) !== true) continue;
930
+
931
+ if (pos1.hasOwnProperty(i) === true && pos1[i] === pos2[i]) {
932
+ delete pos1[i];
933
+ delete pos2[i];
934
+ }
935
+ }
936
+
937
+ // find all the "move" animations
938
+ for (var i in pos2) {
939
+ if (pos2.hasOwnProperty(i) !== true) continue;
940
+
941
+ var closestPiece = findClosestPiece(pos1, pos2[i], i);
942
+ if (closestPiece !== false) {
943
+ animations.push({
944
+ type: 'move',
945
+ source: closestPiece,
946
+ destination: i,
947
+ piece: pos2[i]
948
+ });
949
+
950
+ delete pos1[closestPiece];
951
+ delete pos2[i];
952
+ squaresMovedTo[i] = true;
953
+ }
954
+ }
955
+
956
+ // add pieces to pos2
957
+ for (var i in pos2) {
958
+ if (pos2.hasOwnProperty(i) !== true) continue;
959
+
960
+ animations.push({
961
+ type: 'add',
962
+ square: i,
963
+ piece: pos2[i]
964
+ })
965
+
966
+ delete pos2[i];
967
+ }
968
+
969
+ // clear pieces from pos1
970
+ for (var i in pos1) {
971
+ if (pos1.hasOwnProperty(i) !== true) continue;
972
+
973
+ // do not clear a piece if it is on a square that is the result
974
+ // of a "move", ie: a piece capture
975
+ if (squaresMovedTo.hasOwnProperty(i) === true) continue;
976
+
977
+ animations.push({
978
+ type: 'clear',
979
+ square: i,
980
+ piece: pos1[i]
981
+ });
982
+
983
+ delete pos1[i];
984
+ }
985
+
986
+ return animations;
987
+ }
988
+
989
+ //------------------------------------------------------------------------------
990
+ // Control Flow
991
+ //------------------------------------------------------------------------------
992
+
993
+ function drawPositionInstant() {
994
+ // clear the board
995
+ boardEl.find('.' + CSS.piece).remove();
996
+
997
+ // add the pieces
998
+ for (var i in CURRENT_POSITION) {
999
+ if (CURRENT_POSITION.hasOwnProperty(i) !== true) continue;
1000
+ if (DRAGGING_A_PIECE && DRAGGED_PIECE_SOURCE == i) continue;
1001
+ $('#' + SQUARE_ELS_IDS[i]).append(buildPiece(CURRENT_POSITION[i]));
1002
+ }
1003
+ }
1004
+
1005
+ function drawBoard() {
1006
+ boardEl.html(buildBoard(CURRENT_ORIENTATION));
1007
+ drawPositionInstant();
1008
+
1009
+ if (cfg.sparePieces === true) {
1010
+ if (CURRENT_ORIENTATION === 'white') {
1011
+ sparePiecesTopEl.html(buildSparePieces('black'));
1012
+ sparePiecesBottomEl.html(buildSparePieces('white'));
1013
+ }
1014
+ else {
1015
+ sparePiecesTopEl.html(buildSparePieces('white'));
1016
+ sparePiecesBottomEl.html(buildSparePieces('black'));
1017
+ }
1018
+ }
1019
+ }
1020
+
1021
+ // given a position and a set of moves, return a new position
1022
+ // with the moves executed
1023
+ function calculatePositionFromMoves(position, moves) {
1024
+ position = deepCopy(position);
1025
+
1026
+ for (var i in moves) {
1027
+ if (moves.hasOwnProperty(i) !== true) continue;
1028
+
1029
+ // skip the move if the position doesn't have a piece on the source square
1030
+ if (position.hasOwnProperty(i) !== true) continue;
1031
+
1032
+ var piece = position[i];
1033
+ delete position[i];
1034
+ position[moves[i]] = piece;
1035
+ }
1036
+
1037
+ return position;
1038
+ }
1039
+
1040
+ function setCurrentPosition(position) {
1041
+ var oldPos = deepCopy(CURRENT_POSITION);
1042
+ var newPos = deepCopy(position);
1043
+ var oldFen = objToFen(oldPos);
1044
+ var newFen = objToFen(newPos);
1045
+
1046
+ // do nothing if no change in position
1047
+ if (oldFen === newFen) return;
1048
+
1049
+ // run their onChange function
1050
+ if (cfg.hasOwnProperty('onChange') === true &&
1051
+ typeof cfg.onChange === 'function') {
1052
+ cfg.onChange(oldPos, newPos);
1053
+ }
1054
+
1055
+ // update state
1056
+ CURRENT_POSITION = position;
1057
+ }
1058
+
1059
+ function isXYOnSquare(x, y) {
1060
+ for (var i in SQUARE_ELS_OFFSETS) {
1061
+ if (SQUARE_ELS_OFFSETS.hasOwnProperty(i) !== true) continue;
1062
+
1063
+ var s = SQUARE_ELS_OFFSETS[i];
1064
+ if (x >= s.left && x < s.left + SQUARE_SIZE &&
1065
+ y >= s.top && y < s.top + SQUARE_SIZE) {
1066
+ return i;
1067
+ }
1068
+ }
1069
+
1070
+ return 'offboard';
1071
+ }
1072
+
1073
+ // records the XY coords of every square into memory
1074
+ function captureSquareOffsets() {
1075
+ SQUARE_ELS_OFFSETS = {};
1076
+
1077
+ for (var i in SQUARE_ELS_IDS) {
1078
+ if (SQUARE_ELS_IDS.hasOwnProperty(i) !== true) continue;
1079
+
1080
+ SQUARE_ELS_OFFSETS[i] = $('#' + SQUARE_ELS_IDS[i]).offset();
1081
+ }
1082
+ }
1083
+
1084
+ function removeSquareHighlights() {
1085
+ boardEl.find('.' + CSS.square)
1086
+ .removeClass(CSS.highlight1 + ' ' + CSS.highlight2);
1087
+ }
1088
+
1089
+ function snapbackDraggedPiece() {
1090
+ // there is no "snapback" for spare pieces
1091
+ if (DRAGGED_PIECE_SOURCE === 'spare') {
1092
+ trashDraggedPiece();
1093
+ return;
1094
+ }
1095
+
1096
+ removeSquareHighlights();
1097
+
1098
+ // animation complete
1099
+ function complete() {
1100
+ drawPositionInstant();
1101
+ draggedPieceEl.css('display', 'none');
1102
+
1103
+ // run their onSnapbackEnd function
1104
+ if (cfg.hasOwnProperty('onSnapbackEnd') === true &&
1105
+ typeof cfg.onSnapbackEnd === 'function') {
1106
+ cfg.onSnapbackEnd(DRAGGED_PIECE, DRAGGED_PIECE_SOURCE,
1107
+ deepCopy(CURRENT_POSITION), CURRENT_ORIENTATION);
1108
+ }
1109
+ }
1110
+
1111
+ // get source square position
1112
+ var sourceSquarePosition =
1113
+ $('#' + SQUARE_ELS_IDS[DRAGGED_PIECE_SOURCE]).offset();
1114
+
1115
+ // animate the piece to the target square
1116
+ var opts = {
1117
+ duration: cfg.snapbackSpeed,
1118
+ complete: complete
1119
+ };
1120
+ draggedPieceEl.animate(sourceSquarePosition, opts);
1121
+
1122
+ // set state
1123
+ DRAGGING_A_PIECE = false;
1124
+ }
1125
+
1126
+ function trashDraggedPiece() {
1127
+ removeSquareHighlights();
1128
+
1129
+ // remove the source piece
1130
+ var newPosition = deepCopy(CURRENT_POSITION);
1131
+ delete newPosition[DRAGGED_PIECE_SOURCE];
1132
+ setCurrentPosition(newPosition);
1133
+
1134
+ // redraw the position
1135
+ drawPositionInstant();
1136
+
1137
+ // hide the dragged piece
1138
+ draggedPieceEl.fadeOut(cfg.trashSpeed);
1139
+
1140
+ // set state
1141
+ DRAGGING_A_PIECE = false;
1142
+ }
1143
+
1144
+ function dropDraggedPieceOnSquare(square) {
1145
+ removeSquareHighlights();
1146
+
1147
+ // update position
1148
+ var newPosition = deepCopy(CURRENT_POSITION);
1149
+ delete newPosition[DRAGGED_PIECE_SOURCE];
1150
+ newPosition[square] = DRAGGED_PIECE;
1151
+ setCurrentPosition(newPosition);
1152
+
1153
+ // get target square information
1154
+ var targetSquarePosition = $('#' + SQUARE_ELS_IDS[square]).offset();
1155
+
1156
+ // animation complete
1157
+ var complete = function() {
1158
+ drawPositionInstant();
1159
+ draggedPieceEl.css('display', 'none');
1160
+
1161
+ // execute their onSnapEnd function
1162
+ if (cfg.hasOwnProperty('onSnapEnd') === true &&
1163
+ typeof cfg.onSnapEnd === 'function') {
1164
+ cfg.onSnapEnd(DRAGGED_PIECE_SOURCE, square, DRAGGED_PIECE);
1165
+ }
1166
+ };
1167
+
1168
+ // snap the piece to the target square
1169
+ var opts = {
1170
+ duration: cfg.snapSpeed,
1171
+ complete: complete
1172
+ };
1173
+ draggedPieceEl.animate(targetSquarePosition, opts);
1174
+
1175
+ // set state
1176
+ DRAGGING_A_PIECE = false;
1177
+ }
1178
+
1179
+ function beginDraggingPiece(source, piece, x, y) {
1180
+ // run their custom onDragStart function
1181
+ // their custom onDragStart function can cancel drag start
1182
+ if (typeof cfg.onDragStart === 'function' &&
1183
+ cfg.onDragStart(source, piece,
1184
+ deepCopy(CURRENT_POSITION), CURRENT_ORIENTATION) === false) {
1185
+ return;
1186
+ }
1187
+
1188
+ // set state
1189
+ DRAGGING_A_PIECE = true;
1190
+ DRAGGED_PIECE = piece;
1191
+ DRAGGED_PIECE_SOURCE = source;
1192
+
1193
+ // if the piece came from spare pieces, location is offboard
1194
+ if (source === 'spare') {
1195
+ DRAGGED_PIECE_LOCATION = 'offboard';
1196
+ }
1197
+ else {
1198
+ DRAGGED_PIECE_LOCATION = source;
1199
+ }
1200
+
1201
+ // capture the x, y coords of all squares in memory
1202
+ captureSquareOffsets();
1203
+
1204
+ // create the dragged piece
1205
+ draggedPieceEl.attr('src', buildPieceImgSrc(piece))
1206
+ .css({
1207
+ display: '',
1208
+ position: 'absolute',
1209
+ left: x - (SQUARE_SIZE / 2),
1210
+ top: y - (SQUARE_SIZE / 2)
1211
+ });
1212
+
1213
+ if (source !== 'spare') {
1214
+ // highlight the source square and hide the piece
1215
+ $('#' + SQUARE_ELS_IDS[source]).addClass(CSS.highlight1)
1216
+ .find('.' + CSS.piece).css('display', 'none');
1217
+ }
1218
+ }
1219
+
1220
+ function updateDraggedPiece(x, y) {
1221
+ // put the dragged piece over the mouse cursor
1222
+ draggedPieceEl.css({
1223
+ left: x - (SQUARE_SIZE / 2),
1224
+ top: y - (SQUARE_SIZE / 2)
1225
+ });
1226
+
1227
+ // get location
1228
+ var location = isXYOnSquare(x, y);
1229
+
1230
+ // do nothing if the location has not changed
1231
+ if (location === DRAGGED_PIECE_LOCATION) return;
1232
+
1233
+ // remove highlight from previous square
1234
+ if (validSquare(DRAGGED_PIECE_LOCATION) === true) {
1235
+ $('#' + SQUARE_ELS_IDS[DRAGGED_PIECE_LOCATION])
1236
+ .removeClass(CSS.highlight2);
1237
+ }
1238
+
1239
+ // add highlight to new square
1240
+ if (validSquare(location) === true) {
1241
+ $('#' + SQUARE_ELS_IDS[location]).addClass(CSS.highlight2);
1242
+ }
1243
+
1244
+ // run onDragMove
1245
+ if (typeof cfg.onDragMove === 'function') {
1246
+ cfg.onDragMove(location, DRAGGED_PIECE_LOCATION,
1247
+ DRAGGED_PIECE_SOURCE, DRAGGED_PIECE,
1248
+ deepCopy(CURRENT_POSITION), CURRENT_ORIENTATION);
1249
+ }
1250
+
1251
+ // update state
1252
+ DRAGGED_PIECE_LOCATION = location;
1253
+ }
1254
+
1255
+ function stopDraggedPiece(location) {
1256
+ // determine what the action should be
1257
+ var action = 'drop';
1258
+ if (location === 'offboard' && cfg.dropOffBoard === 'snapback') {
1259
+ action = 'snapback';
1260
+ }
1261
+ if (location === 'offboard' && cfg.dropOffBoard === 'trash') {
1262
+ action = 'trash';
1263
+ }
1264
+
1265
+ // run their onDrop function, which can potentially change the drop action
1266
+ if (cfg.hasOwnProperty('onDrop') === true &&
1267
+ typeof cfg.onDrop === 'function') {
1268
+ var newPosition = deepCopy(CURRENT_POSITION);
1269
+
1270
+ // source piece is a spare piece and position is off the board
1271
+ //if (DRAGGED_PIECE_SOURCE === 'spare' && location === 'offboard') {...}
1272
+ // position has not changed; do nothing
1273
+
1274
+ // source piece is a spare piece and position is on the board
1275
+ if (DRAGGED_PIECE_SOURCE === 'spare' && validSquare(location) === true) {
1276
+ // add the piece to the board
1277
+ newPosition[location] = DRAGGED_PIECE;
1278
+ }
1279
+
1280
+ // source piece was on the board and position is off the board
1281
+ if (validSquare(DRAGGED_PIECE_SOURCE) === true && location === 'offboard') {
1282
+ // remove the piece from the board
1283
+ delete newPosition[DRAGGED_PIECE_SOURCE];
1284
+ }
1285
+
1286
+ // source piece was on the board and position is on the board
1287
+ if (validSquare(DRAGGED_PIECE_SOURCE) === true &&
1288
+ validSquare(location) === true) {
1289
+ // move the piece
1290
+ delete newPosition[DRAGGED_PIECE_SOURCE];
1291
+ newPosition[location] = DRAGGED_PIECE;
1292
+ }
1293
+
1294
+ var oldPosition = deepCopy(CURRENT_POSITION);
1295
+
1296
+ var result = cfg.onDrop(DRAGGED_PIECE_SOURCE, location, DRAGGED_PIECE,
1297
+ newPosition, oldPosition, CURRENT_ORIENTATION);
1298
+ if (result === 'snapback' || result === 'trash') {
1299
+ action = result;
1300
+ }
1301
+ }
1302
+
1303
+ // do it!
1304
+ if (action === 'snapback') {
1305
+ snapbackDraggedPiece();
1306
+ }
1307
+ else if (action === 'trash') {
1308
+ trashDraggedPiece();
1309
+ }
1310
+ else if (action === 'drop') {
1311
+ dropDraggedPieceOnSquare(location);
1312
+ }
1313
+ }
1314
+
1315
+ //------------------------------------------------------------------------------
1316
+ // Public Methods
1317
+ //------------------------------------------------------------------------------
1318
+
1319
+ // clear the board
1320
+ widget.clear = function(useAnimation) {
1321
+ widget.position({}, useAnimation);
1322
+ };
1323
+
1324
+ /*
1325
+ // get or set config properties
1326
+ // TODO: write this, GitHub Issue #1
1327
+ widget.config = function(arg1, arg2) {
1328
+ // get the current config
1329
+ if (arguments.length === 0) {
1330
+ return deepCopy(cfg);
1331
+ }
1332
+ };
1333
+ */
1334
+
1335
+ // remove the widget from the page
1336
+ widget.destroy = function() {
1337
+ // remove markup
1338
+ containerEl.html('');
1339
+ draggedPieceEl.remove();
1340
+
1341
+ // remove event handlers
1342
+ containerEl.unbind();
1343
+ };
1344
+
1345
+ // shorthand method to get the current FEN
1346
+ widget.fen = function() {
1347
+ return widget.position('fen');
1348
+ };
1349
+
1350
+ // flip orientation
1351
+ widget.flip = function() {
1352
+ widget.orientation('flip');
1353
+ };
1354
+
1355
+ /*
1356
+ // TODO: write this, GitHub Issue #5
1357
+ widget.highlight = function() {
1358
+
1359
+ };
1360
+ */
1361
+ widget.cache = cacheImages
1362
+ // move pieces
1363
+ widget.move = function() {
1364
+ // no need to throw an error here; just do nothing
1365
+ if (arguments.length === 0) return;
1366
+
1367
+ var useAnimation = true;
1368
+
1369
+ // collect the moves into an object
1370
+ var moves = {};
1371
+ for (var i = 0; i < arguments.length; i++) {
1372
+ // any "false" to this function means no animations
1373
+ if (arguments[i] === false) {
1374
+ useAnimation = false;
1375
+ continue;
1376
+ }
1377
+
1378
+ // skip invalid arguments
1379
+ if (validMove(arguments[i]) !== true) {
1380
+ error(2826, 'Invalid move passed to the move method.', arguments[i]);
1381
+ continue;
1382
+ }
1383
+
1384
+ var tmp = arguments[i].split('-');
1385
+ moves[tmp[0]] = tmp[1];
1386
+ }
1387
+
1388
+ // calculate position from moves
1389
+ var newPos = calculatePositionFromMoves(CURRENT_POSITION, moves);
1390
+
1391
+ // update the board
1392
+ widget.position(newPos, useAnimation);
1393
+
1394
+ // return the new position object
1395
+ return newPos;
1396
+ };
1397
+
1398
+ widget.orientation = function(arg) {
1399
+ // no arguments, return the current orientation
1400
+ if (arguments.length === 0) {
1401
+ return CURRENT_ORIENTATION;
1402
+ }
1403
+
1404
+ // set to white or black
1405
+ if (arg === 'white' || arg === 'black') {
1406
+ CURRENT_ORIENTATION = arg;
1407
+ drawBoard();
1408
+ return;
1409
+ }
1410
+
1411
+ // flip orientation
1412
+ if (arg === 'flip') {
1413
+ CURRENT_ORIENTATION = (CURRENT_ORIENTATION === 'white') ? 'black' : 'white';
1414
+ drawBoard();
1415
+ return;
1416
+ }
1417
+
1418
+ error(5482, 'Invalid value passed to the orientation method.', arg);
1419
+ };
1420
+
1421
+ widget.position = function(position, useAnimation) {
1422
+ // no arguments, return the current position
1423
+ if (arguments.length === 0) {
1424
+ return deepCopy(CURRENT_POSITION);
1425
+ }
1426
+
1427
+ // get position as FEN
1428
+ if (typeof position === 'string' && position.toLowerCase() === 'fen') {
1429
+ return objToFen(CURRENT_POSITION);
1430
+ }
1431
+
1432
+ // default for useAnimations is true
1433
+ if (useAnimation !== false) {
1434
+ useAnimation = true;
1435
+ }
1436
+
1437
+ // start position
1438
+ if (typeof position === 'string' && position.toLowerCase() === 'start') {
1439
+ position = deepCopy(START_POSITION);
1440
+ }
1441
+
1442
+ // convert FEN to position object
1443
+ if (validFen(position) === true) {
1444
+ position = fenToObj(position);
1445
+ }
1446
+
1447
+ // validate position object
1448
+ if (validPositionObject(position) !== true) {
1449
+ error(6482, 'Invalid value passed to the position method.', position);
1450
+ return;
1451
+ }
1452
+
1453
+ if (useAnimation === true) {
1454
+ // start the animations
1455
+ doAnimations(calculateAnimations(CURRENT_POSITION, position),
1456
+ CURRENT_POSITION, position);
1457
+
1458
+ // set the new position
1459
+ setCurrentPosition(position);
1460
+ }
1461
+ // instant update
1462
+ else {
1463
+ setCurrentPosition(position);
1464
+ drawPositionInstant();
1465
+ }
1466
+ };
1467
+
1468
+ widget.resize = function() {
1469
+ // calulate the new square size
1470
+ SQUARE_SIZE = calculateSquareSize();
1471
+
1472
+ // set board width
1473
+ boardEl.css('width', (SQUARE_SIZE * 8) + 'px');
1474
+
1475
+ // set drag piece size
1476
+ draggedPieceEl.css({
1477
+ height: SQUARE_SIZE,
1478
+ width: SQUARE_SIZE
1479
+ });
1480
+
1481
+ // spare pieces
1482
+ if (cfg.sparePieces === true) {
1483
+ containerEl.find('.' + CSS.sparePieces)
1484
+ .css('paddingLeft', (SQUARE_SIZE + BOARD_BORDER_SIZE) + 'px');
1485
+ }
1486
+
1487
+ // redraw the board
1488
+ drawBoard();
1489
+ };
1490
+
1491
+ // set the starting position
1492
+ widget.start = function(useAnimation) {
1493
+ widget.position('start', useAnimation);
1494
+ };
1495
+
1496
+ //------------------------------------------------------------------------------
1497
+ // Browser Events
1498
+ //------------------------------------------------------------------------------
1499
+
1500
+ function isTouchDevice() {
1501
+ return ('ontouchstart' in document.documentElement);
1502
+ }
1503
+
1504
+ // reference: http://www.quirksmode.org/js/detect.html
1505
+ function isMSIE() {
1506
+ return (navigator && navigator.userAgent &&
1507
+ navigator.userAgent.search(/MSIE/) !== -1);
1508
+ }
1509
+
1510
+ function stopDefault(e) {
1511
+ e.preventDefault();
1512
+ }
1513
+
1514
+ function mousedownSquare(e) {
1515
+ // do nothing if we're not draggable
1516
+ if (cfg.draggable !== true) return;
1517
+
1518
+ var square = $(this).attr('data-square');
1519
+
1520
+ // no piece on this square
1521
+ if (validSquare(square) !== true ||
1522
+ CURRENT_POSITION.hasOwnProperty(square) !== true) {
1523
+ return;
1524
+ }
1525
+
1526
+ beginDraggingPiece(square, CURRENT_POSITION[square], e.pageX, e.pageY);
1527
+ }
1528
+
1529
+ function touchstartSquare(e) {
1530
+ // do nothing if we're not draggable
1531
+ if (cfg.draggable !== true) return;
1532
+
1533
+ var square = $(this).attr('data-square');
1534
+
1535
+ // no piece on this square
1536
+ if (validSquare(square) !== true ||
1537
+ CURRENT_POSITION.hasOwnProperty(square) !== true) {
1538
+ return;
1539
+ }
1540
+
1541
+ e = e.originalEvent;
1542
+ beginDraggingPiece(square, CURRENT_POSITION[square],
1543
+ e.changedTouches[0].pageX, e.changedTouches[0].pageY);
1544
+ }
1545
+
1546
+ function mousedownSparePiece(e) {
1547
+ // do nothing if sparePieces is not enabled
1548
+ if (cfg.sparePieces !== true) return;
1549
+
1550
+ var piece = $(this).attr('data-piece');
1551
+
1552
+ beginDraggingPiece('spare', piece, e.pageX, e.pageY);
1553
+ }
1554
+
1555
+ function touchstartSparePiece(e) {
1556
+ // do nothing if sparePieces is not enabled
1557
+ if (cfg.sparePieces !== true) return;
1558
+
1559
+ var piece = $(this).attr('data-piece');
1560
+
1561
+ e = e.originalEvent;
1562
+ beginDraggingPiece('spare', piece,
1563
+ e.changedTouches[0].pageX, e.changedTouches[0].pageY);
1564
+ }
1565
+
1566
+ function mousemoveWindow(e) {
1567
+ // do nothing if we are not dragging a piece
1568
+ if (DRAGGING_A_PIECE !== true) return;
1569
+
1570
+ updateDraggedPiece(e.pageX, e.pageY);
1571
+ }
1572
+
1573
+ function touchmoveWindow(e) {
1574
+ // do nothing if we are not dragging a piece
1575
+ if (DRAGGING_A_PIECE !== true) return;
1576
+
1577
+ // prevent screen from scrolling
1578
+ e.preventDefault();
1579
+
1580
+ updateDraggedPiece(e.originalEvent.changedTouches[0].pageX,
1581
+ e.originalEvent.changedTouches[0].pageY);
1582
+ }
1583
+
1584
+ function mouseupWindow(e) {
1585
+ // do nothing if we are not dragging a piece
1586
+ if (DRAGGING_A_PIECE !== true) return;
1587
+
1588
+ // get the location
1589
+ var location = isXYOnSquare(e.pageX, e.pageY);
1590
+
1591
+ stopDraggedPiece(location);
1592
+ }
1593
+
1594
+ function touchendWindow(e) {
1595
+ // do nothing if we are not dragging a piece
1596
+ if (DRAGGING_A_PIECE !== true) return;
1597
+
1598
+ // get the location
1599
+ var location = isXYOnSquare(e.originalEvent.changedTouches[0].pageX,
1600
+ e.originalEvent.changedTouches[0].pageY);
1601
+
1602
+ stopDraggedPiece(location);
1603
+ }
1604
+
1605
+ function mouseenterSquare(e) {
1606
+ // do not fire this event if we are dragging a piece
1607
+ // NOTE: this should never happen, but it's a safeguard
1608
+ if (DRAGGING_A_PIECE !== false) return;
1609
+
1610
+ if (cfg.hasOwnProperty('onMouseoverSquare') !== true ||
1611
+ typeof cfg.onMouseoverSquare !== 'function') return;
1612
+
1613
+ // get the square
1614
+ var square = $(e.currentTarget).attr('data-square');
1615
+
1616
+ // NOTE: this should never happen; defensive
1617
+ if (validSquare(square) !== true) return;
1618
+
1619
+ // get the piece on this square
1620
+ var piece = false;
1621
+ if (CURRENT_POSITION.hasOwnProperty(square) === true) {
1622
+ piece = CURRENT_POSITION[square];
1623
+ }
1624
+
1625
+ // execute their function
1626
+ cfg.onMouseoverSquare(square, piece, deepCopy(CURRENT_POSITION),
1627
+ CURRENT_ORIENTATION);
1628
+ }
1629
+
1630
+ function mouseleaveSquare(e) {
1631
+ // do not fire this event if we are dragging a piece
1632
+ // NOTE: this should never happen, but it's a safeguard
1633
+ if (DRAGGING_A_PIECE !== false) return;
1634
+
1635
+ if (cfg.hasOwnProperty('onMouseoutSquare') !== true ||
1636
+ typeof cfg.onMouseoutSquare !== 'function') return;
1637
+
1638
+ // get the square
1639
+ var square = $(e.currentTarget).attr('data-square');
1640
+
1641
+ // NOTE: this should never happen; defensive
1642
+ if (validSquare(square) !== true) return;
1643
+
1644
+ // get the piece on this square
1645
+ var piece = false;
1646
+ if (CURRENT_POSITION.hasOwnProperty(square) === true) {
1647
+ piece = CURRENT_POSITION[square];
1648
+ }
1649
+
1650
+ // execute their function
1651
+ cfg.onMouseoutSquare(square, piece, deepCopy(CURRENT_POSITION),
1652
+ CURRENT_ORIENTATION);
1653
+ }
1654
+
1655
+ //------------------------------------------------------------------------------
1656
+ // Initialization
1657
+ //------------------------------------------------------------------------------
1658
+
1659
+ function addEvents() {
1660
+ // prevent browser "image drag"
1661
+ $('body').on('mousedown mousemove', '.' + CSS.piece, stopDefault);
1662
+
1663
+ // mouse drag pieces
1664
+ boardEl.on('mousedown', '.' + CSS.square, mousedownSquare);
1665
+ containerEl.on('mousedown', '.' + CSS.sparePieces + ' .' + CSS.piece,
1666
+ mousedownSparePiece);
1667
+
1668
+ // mouse enter / leave square
1669
+ boardEl.on('mouseenter', '.' + CSS.square, mouseenterSquare);
1670
+ boardEl.on('mouseleave', '.' + CSS.square, mouseleaveSquare);
1671
+
1672
+ // IE doesn't like the events on the window object, but other browsers
1673
+ // perform better that way
1674
+ if (isMSIE() === true) {
1675
+ // IE-specific prevent browser "image drag"
1676
+ document.ondragstart = function() { return false; };
1677
+
1678
+ $('body').on('mousemove', mousemoveWindow);
1679
+ $('body').on('mouseup', mouseupWindow);
1680
+ }
1681
+ else {
1682
+ $(window).on('mousemove', mousemoveWindow);
1683
+ $(window).on('mouseup', mouseupWindow);
1684
+ }
1685
+
1686
+ // touch drag pieces
1687
+ if (isTouchDevice() === true) {
1688
+ boardEl.on('touchstart', '.' + CSS.square, touchstartSquare);
1689
+ containerEl.on('touchstart', '.' + CSS.sparePieces + ' .' + CSS.piece,
1690
+ touchstartSparePiece);
1691
+ $(window).on('touchmove', touchmoveWindow);
1692
+ $(window).on('touchend', touchendWindow);
1693
+ }
1694
+ }
1695
+
1696
+ function initDom() {
1697
+ // build board and save it in memory
1698
+ cacheImages();
1699
+ containerEl.html(buildBoardContainer());
1700
+ boardEl = containerEl.find('.' + CSS.board);
1701
+
1702
+ if (cfg.sparePieces === true) {
1703
+ sparePiecesTopEl = containerEl.find('.' + CSS.sparePiecesTop);
1704
+ sparePiecesBottomEl = containerEl.find('.' + CSS.sparePiecesBottom);
1705
+ }
1706
+
1707
+ // create the drag piece
1708
+ var draggedPieceId = createId();
1709
+ $('body').append(buildPiece('wP', true, draggedPieceId));
1710
+ draggedPieceEl = $('#' + draggedPieceId);
1711
+
1712
+ // get the border size
1713
+ BOARD_BORDER_SIZE = parseInt(boardEl.css('borderLeftWidth'), 10);
1714
+
1715
+ // set the size and draw the board
1716
+ widget.resize();
1717
+ }
1718
+
1719
+ function init() {
1720
+ if (checkDeps() !== true ||
1721
+ expandConfig() !== true) return;
1722
+
1723
+ // create unique IDs for all the elements we will create
1724
+ createElIds();
1725
+
1726
+ initDom();
1727
+ addEvents();
1728
+ }
1729
+
1730
+ // go time
1731
+ init();
1732
+
1733
+ // return the widget object
1734
+ return widget;
1735
+
1736
+ }; // end window.ChessBoard
1737
+
1738
+ // expose util functions
1739
+ window.ChessBoard.fenToObj = fenToObj;
1740
+ window.ChessBoard.objToFen = objToFen;
1741
+
1742
+ })(); // end anonymous wrapper