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