Spaces:
Sleeping
Sleeping
| // chessboard.js v1.0.0 | |
| // https://github.com/oakmac/chessboardjs/ | |
| // | |
| // Copyright (c) 2019, Chris Oakman | |
| // Released under the MIT license | |
| // https://github.com/oakmac/chessboardjs/blob/master/LICENSE.md | |
| // start anonymous scope | |
| ;(function () { | |
| 'use strict' | |
| var $ = window['jQuery'] | |
| // --------------------------------------------------------------------------- | |
| // Constants | |
| // --------------------------------------------------------------------------- | |
| var COLUMNS = 'abcdefgh'.split('') | |
| var DEFAULT_DRAG_THROTTLE_RATE = 20 | |
| var ELLIPSIS = '…' | |
| var MINIMUM_JQUERY_VERSION = '1.8.3' | |
| var RUN_ASSERTS = false | |
| var START_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR' | |
| var START_POSITION = fenToObj(START_FEN) | |
| // default animation speeds | |
| var DEFAULT_APPEAR_SPEED = 200 | |
| var DEFAULT_MOVE_SPEED = 200 | |
| var DEFAULT_SNAPBACK_SPEED = 60 | |
| var DEFAULT_SNAP_SPEED = 30 | |
| var DEFAULT_TRASH_SPEED = 100 | |
| // use unique class names to prevent clashing with anything else on the page | |
| // and simplify selectors | |
| // NOTE: these should never change | |
| var CSS = {} | |
| CSS['alpha'] = 'alpha-d2270' | |
| CSS['black'] = 'black-3c85d' | |
| CSS['board'] = 'board-b72b1' | |
| CSS['chessboard'] = 'chessboard-63f37' | |
| CSS['clearfix'] = 'clearfix-7da63' | |
| CSS['highlight1'] = 'highlight1-32417' | |
| CSS['highlight2'] = 'highlight2-9c5d2' | |
| CSS['notation'] = 'notation-322f9' | |
| CSS['numeric'] = 'numeric-fc462' | |
| CSS['piece'] = 'piece-417db' | |
| CSS['row'] = 'row-5277c' | |
| CSS['sparePieces'] = 'spare-pieces-7492f' | |
| CSS['sparePiecesBottom'] = 'spare-pieces-bottom-ae20f' | |
| CSS['sparePiecesTop'] = 'spare-pieces-top-4028b' | |
| CSS['square'] = 'square-55d63' | |
| CSS['white'] = 'white-1e1d7' | |
| // --------------------------------------------------------------------------- | |
| // Misc Util Functions | |
| // --------------------------------------------------------------------------- | |
| function throttle (f, interval, scope) { | |
| var timeout = 0 | |
| var shouldFire = false | |
| var args = [] | |
| var handleTimeout = function () { | |
| timeout = 0 | |
| if (shouldFire) { | |
| shouldFire = false | |
| fire() | |
| } | |
| } | |
| var fire = function () { | |
| timeout = window.setTimeout(handleTimeout, interval) | |
| f.apply(scope, args) | |
| } | |
| return function (_args) { | |
| args = arguments | |
| if (!timeout) { | |
| fire() | |
| } else { | |
| shouldFire = true | |
| } | |
| } | |
| } | |
| // function debounce (f, interval, scope) { | |
| // var timeout = 0 | |
| // return function (_args) { | |
| // window.clearTimeout(timeout) | |
| // var args = arguments | |
| // timeout = window.setTimeout(function () { | |
| // f.apply(scope, args) | |
| // }, interval) | |
| // } | |
| // } | |
| function uuid () { | |
| return 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function (c) { | |
| var r = (Math.random() * 16) | 0 | |
| return r.toString(16) | |
| }) | |
| } | |
| function deepCopy (thing) { | |
| return JSON.parse(JSON.stringify(thing)) | |
| } | |
| function parseSemVer (version) { | |
| var tmp = version.split('.') | |
| return { | |
| major: parseInt(tmp[0], 10), | |
| minor: parseInt(tmp[1], 10), | |
| patch: parseInt(tmp[2], 10) | |
| } | |
| } | |
| // returns true if version is >= minimum | |
| function validSemanticVersion (version, minimum) { | |
| version = parseSemVer(version) | |
| minimum = parseSemVer(minimum) | |
| var versionNum = (version.major * 100000 * 100000) + | |
| (version.minor * 100000) + | |
| version.patch | |
| var minimumNum = (minimum.major * 100000 * 100000) + | |
| (minimum.minor * 100000) + | |
| minimum.patch | |
| return versionNum >= minimumNum | |
| } | |
| function interpolateTemplate (str, obj) { | |
| for (var key in obj) { | |
| if (!obj.hasOwnProperty(key)) continue | |
| var keyTemplateStr = '{' + key + '}' | |
| var value = obj[key] | |
| while (str.indexOf(keyTemplateStr) !== -1) { | |
| str = str.replace(keyTemplateStr, value) | |
| } | |
| } | |
| return str | |
| } | |
| if (RUN_ASSERTS) { | |
| console.assert(interpolateTemplate('abc', {a: 'x'}) === 'abc') | |
| console.assert(interpolateTemplate('{a}bc', {}) === '{a}bc') | |
| console.assert(interpolateTemplate('{a}bc', {p: 'q'}) === '{a}bc') | |
| console.assert(interpolateTemplate('{a}bc', {a: 'x'}) === 'xbc') | |
| console.assert(interpolateTemplate('{a}bc{a}bc', {a: 'x'}) === 'xbcxbc') | |
| console.assert(interpolateTemplate('{a}{a}{b}', {a: 'x', b: 'y'}) === 'xxy') | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Predicates | |
| // --------------------------------------------------------------------------- | |
| function isString (s) { | |
| return typeof s === 'string' | |
| } | |
| function isFunction (f) { | |
| return typeof f === 'function' | |
| } | |
| function isInteger (n) { | |
| return typeof n === 'number' && | |
| isFinite(n) && | |
| Math.floor(n) === n | |
| } | |
| function validAnimationSpeed (speed) { | |
| if (speed === 'fast' || speed === 'slow') return true | |
| if (!isInteger(speed)) return false | |
| return speed >= 0 | |
| } | |
| function validThrottleRate (rate) { | |
| return isInteger(rate) && | |
| rate >= 1 | |
| } | |
| function validMove (move) { | |
| // move should be a string | |
| if (!isString(move)) return false | |
| // move should be in the form of "e2-e4", "f6-d5" | |
| var squares = move.split('-') | |
| if (squares.length !== 2) return false | |
| return validSquare(squares[0]) && validSquare(squares[1]) | |
| } | |
| function validSquare (square) { | |
| return isString(square) && square.search(/^[a-h][1-8]$/) !== -1 | |
| } | |
| if (RUN_ASSERTS) { | |
| console.assert(validSquare('a1')) | |
| console.assert(validSquare('e2')) | |
| console.assert(!validSquare('D2')) | |
| console.assert(!validSquare('g9')) | |
| console.assert(!validSquare('a')) | |
| console.assert(!validSquare(true)) | |
| console.assert(!validSquare(null)) | |
| console.assert(!validSquare({})) | |
| } | |
| function validPieceCode (code) { | |
| return isString(code) && code.search(/^[bw][KQRNBP]$/) !== -1 | |
| } | |
| if (RUN_ASSERTS) { | |
| console.assert(validPieceCode('bP')) | |
| console.assert(validPieceCode('bK')) | |
| console.assert(validPieceCode('wK')) | |
| console.assert(validPieceCode('wR')) | |
| console.assert(!validPieceCode('WR')) | |
| console.assert(!validPieceCode('Wr')) | |
| console.assert(!validPieceCode('a')) | |
| console.assert(!validPieceCode(true)) | |
| console.assert(!validPieceCode(null)) | |
| console.assert(!validPieceCode({})) | |
| } | |
| function validFen (fen) { | |
| if (!isString(fen)) return false | |
| // cut off any move, castling, etc info from the end | |
| // we're only interested in position information | |
| fen = fen.replace(/ .+$/, '') | |
| // expand the empty square numbers to just 1s | |
| fen = expandFenEmptySquares(fen) | |
| // FEN should be 8 sections separated by slashes | |
| var chunks = fen.split('/') | |
| if (chunks.length !== 8) return false | |
| // check each section | |
| for (var i = 0; i < 8; i++) { | |
| if (chunks[i].length !== 8 || | |
| chunks[i].search(/[^kqrnbpKQRNBP1]/) !== -1) { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| if (RUN_ASSERTS) { | |
| console.assert(validFen(START_FEN)) | |
| console.assert(validFen('8/8/8/8/8/8/8/8')) | |
| console.assert(validFen('r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R')) | |
| console.assert(validFen('3r3r/1p4pp/2nb1k2/pP3p2/8/PB2PN2/p4PPP/R4RK1 b - - 0 1')) | |
| console.assert(!validFen('3r3z/1p4pp/2nb1k2/pP3p2/8/PB2PN2/p4PPP/R4RK1 b - - 0 1')) | |
| console.assert(!validFen('anbqkbnr/8/8/8/8/8/PPPPPPPP/8')) | |
| console.assert(!validFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/')) | |
| console.assert(!validFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBN')) | |
| console.assert(!validFen('888888/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR')) | |
| console.assert(!validFen('888888/pppppppp/74/8/8/8/PPPPPPPP/RNBQKBNR')) | |
| console.assert(!validFen({})) | |
| } | |
| function validPositionObject (pos) { | |
| if (!$.isPlainObject(pos)) return false | |
| for (var i in pos) { | |
| if (!pos.hasOwnProperty(i)) continue | |
| if (!validSquare(i) || !validPieceCode(pos[i])) { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| if (RUN_ASSERTS) { | |
| console.assert(validPositionObject(START_POSITION)) | |
| console.assert(validPositionObject({})) | |
| console.assert(validPositionObject({e2: 'wP'})) | |
| console.assert(validPositionObject({e2: 'wP', d2: 'wP'})) | |
| console.assert(!validPositionObject({e2: 'BP'})) | |
| console.assert(!validPositionObject({y2: 'wP'})) | |
| console.assert(!validPositionObject(null)) | |
| console.assert(!validPositionObject('start')) | |
| console.assert(!validPositionObject(START_FEN)) | |
| } | |
| function isTouchDevice () { | |
| return 'ontouchstart' in document.documentElement | |
| } | |
| function validJQueryVersion () { | |
| return typeof window.$ && | |
| $.fn && | |
| $.fn.jquery && | |
| validSemanticVersion($.fn.jquery, MINIMUM_JQUERY_VERSION) | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Chess Util Functions | |
| // --------------------------------------------------------------------------- | |
| // convert FEN piece code to bP, wK, etc | |
| function fenToPieceCode (piece) { | |
| // black piece | |
| if (piece.toLowerCase() === piece) { | |
| return 'b' + piece.toUpperCase() | |
| } | |
| // white piece | |
| return 'w' + piece.toUpperCase() | |
| } | |
| // convert bP, wK, etc code to FEN structure | |
| function pieceCodeToFen (piece) { | |
| var pieceCodeLetters = piece.split('') | |
| // white piece | |
| if (pieceCodeLetters[0] === 'w') { | |
| return pieceCodeLetters[1].toUpperCase() | |
| } | |
| // black piece | |
| return pieceCodeLetters[1].toLowerCase() | |
| } | |
| // convert FEN string to position object | |
| // returns false if the FEN string is invalid | |
| function fenToObj (fen) { | |
| if (!validFen(fen)) return false | |
| // cut off any move, castling, etc info from the end | |
| // we're only interested in position information | |
| fen = fen.replace(/ .+$/, '') | |
| var rows = fen.split('/') | |
| var position = {} | |
| var currentRow = 8 | |
| for (var i = 0; i < 8; i++) { | |
| var row = rows[i].split('') | |
| var colIdx = 0 | |
| // loop through each character in the FEN section | |
| for (var j = 0; j < row.length; j++) { | |
| // number / empty squares | |
| if (row[j].search(/[1-8]/) !== -1) { | |
| var numEmptySquares = parseInt(row[j], 10) | |
| colIdx = colIdx + numEmptySquares | |
| } else { | |
| // piece | |
| var square = COLUMNS[colIdx] + currentRow | |
| position[square] = fenToPieceCode(row[j]) | |
| colIdx = colIdx + 1 | |
| } | |
| } | |
| currentRow = currentRow - 1 | |
| } | |
| return position | |
| } | |
| // position object to FEN string | |
| // returns false if the obj is not a valid position object | |
| function objToFen (obj) { | |
| if (!validPositionObject(obj)) return false | |
| var fen = '' | |
| var currentRow = 8 | |
| for (var i = 0; i < 8; i++) { | |
| for (var j = 0; j < 8; j++) { | |
| var square = COLUMNS[j] + currentRow | |
| // piece exists | |
| if (obj.hasOwnProperty(square)) { | |
| fen = fen + pieceCodeToFen(obj[square]) | |
| } else { | |
| // empty space | |
| fen = fen + '1' | |
| } | |
| } | |
| if (i !== 7) { | |
| fen = fen + '/' | |
| } | |
| currentRow = currentRow - 1 | |
| } | |
| // squeeze the empty numbers together | |
| fen = squeezeFenEmptySquares(fen) | |
| return fen | |
| } | |
| if (RUN_ASSERTS) { | |
| console.assert(objToFen(START_POSITION) === START_FEN) | |
| console.assert(objToFen({}) === '8/8/8/8/8/8/8/8') | |
| console.assert(objToFen({a2: 'wP', 'b2': 'bP'}) === '8/8/8/8/8/8/Pp6/8') | |
| } | |
| function squeezeFenEmptySquares (fen) { | |
| return fen.replace(/11111111/g, '8') | |
| .replace(/1111111/g, '7') | |
| .replace(/111111/g, '6') | |
| .replace(/11111/g, '5') | |
| .replace(/1111/g, '4') | |
| .replace(/111/g, '3') | |
| .replace(/11/g, '2') | |
| } | |
| function expandFenEmptySquares (fen) { | |
| return fen.replace(/8/g, '11111111') | |
| .replace(/7/g, '1111111') | |
| .replace(/6/g, '111111') | |
| .replace(/5/g, '11111') | |
| .replace(/4/g, '1111') | |
| .replace(/3/g, '111') | |
| .replace(/2/g, '11') | |
| } | |
| // returns the distance between two squares | |
| function squareDistance (squareA, squareB) { | |
| var squareAArray = squareA.split('') | |
| var squareAx = COLUMNS.indexOf(squareAArray[0]) + 1 | |
| var squareAy = parseInt(squareAArray[1], 10) | |
| var squareBArray = squareB.split('') | |
| var squareBx = COLUMNS.indexOf(squareBArray[0]) + 1 | |
| var squareBy = parseInt(squareBArray[1], 10) | |
| var xDelta = Math.abs(squareAx - squareBx) | |
| var yDelta = Math.abs(squareAy - squareBy) | |
| if (xDelta >= yDelta) return xDelta | |
| return yDelta | |
| } | |
| // returns the square of the closest instance of piece | |
| // returns false if no instance of piece is found in position | |
| function findClosestPiece (position, piece, square) { | |
| // create array of closest squares from square | |
| var closestSquares = createRadius(square) | |
| // search through the position in order of distance for the piece | |
| for (var i = 0; i < closestSquares.length; i++) { | |
| var s = closestSquares[i] | |
| if (position.hasOwnProperty(s) && position[s] === piece) { | |
| return s | |
| } | |
| } | |
| return false | |
| } | |
| // returns an array of closest squares from square | |
| function createRadius (square) { | |
| var squares = [] | |
| // calculate distance of all squares | |
| for (var i = 0; i < 8; i++) { | |
| for (var j = 0; j < 8; j++) { | |
| var s = COLUMNS[i] + (j + 1) | |
| // skip the square we're starting from | |
| if (square === s) continue | |
| squares.push({ | |
| square: s, | |
| distance: squareDistance(square, s) | |
| }) | |
| } | |
| } | |
| // sort by distance | |
| squares.sort(function (a, b) { | |
| return a.distance - b.distance | |
| }) | |
| // just return the square code | |
| var surroundingSquares = [] | |
| for (i = 0; i < squares.length; i++) { | |
| surroundingSquares.push(squares[i].square) | |
| } | |
| return surroundingSquares | |
| } | |
| // given a position and a set of moves, return a new position | |
| // with the moves executed | |
| function calculatePositionFromMoves (position, moves) { | |
| var newPosition = deepCopy(position) | |
| for (var i in moves) { | |
| if (!moves.hasOwnProperty(i)) continue | |
| // skip the move if the position doesn't have a piece on the source square | |
| if (!newPosition.hasOwnProperty(i)) continue | |
| var piece = newPosition[i] | |
| delete newPosition[i] | |
| newPosition[moves[i]] = piece | |
| } | |
| return newPosition | |
| } | |
| // TODO: add some asserts here for calculatePositionFromMoves | |
| // --------------------------------------------------------------------------- | |
| // HTML | |
| // --------------------------------------------------------------------------- | |
| function buildContainerHTML (hasSparePieces) { | |
| var html = '<div class="{chessboard}">' | |
| if (hasSparePieces) { | |
| html += '<div class="{sparePieces} {sparePiecesTop}"></div>' | |
| } | |
| html += '<div class="{board}"></div>' | |
| if (hasSparePieces) { | |
| html += '<div class="{sparePieces} {sparePiecesBottom}"></div>' | |
| } | |
| html += '</div>' | |
| return interpolateTemplate(html, CSS) | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Config | |
| // --------------------------------------------------------------------------- | |
| function expandConfigArgumentShorthand (config) { | |
| if (config === 'start') { | |
| config = {position: deepCopy(START_POSITION)} | |
| } else if (validFen(config)) { | |
| config = {position: fenToObj(config)} | |
| } else if (validPositionObject(config)) { | |
| config = {position: deepCopy(config)} | |
| } | |
| // config must be an object | |
| if (!$.isPlainObject(config)) config = {} | |
| return config | |
| } | |
| // validate config / set default options | |
| function expandConfig (config) { | |
| // default for orientation is white | |
| if (config.orientation !== 'black') config.orientation = 'white' | |
| // default for showNotation is true | |
| if (config.showNotation !== false) config.showNotation = true | |
| // default for draggable is false | |
| if (config.draggable !== true) config.draggable = false | |
| // default for dropOffBoard is 'snapback' | |
| if (config.dropOffBoard !== 'trash') config.dropOffBoard = 'snapback' | |
| // default for sparePieces is false | |
| if (config.sparePieces !== true) config.sparePieces = false | |
| // draggable must be true if sparePieces is enabled | |
| if (config.sparePieces) config.draggable = true | |
| // default piece theme is wikipedia | |
| if (!config.hasOwnProperty('pieceTheme') || | |
| (!isString(config.pieceTheme) && !isFunction(config.pieceTheme))) { | |
| config.pieceTheme = 'img/chesspieces/wikipedia/{piece}.png' | |
| } | |
| // animation speeds | |
| if (!validAnimationSpeed(config.appearSpeed)) config.appearSpeed = DEFAULT_APPEAR_SPEED | |
| if (!validAnimationSpeed(config.moveSpeed)) config.moveSpeed = DEFAULT_MOVE_SPEED | |
| if (!validAnimationSpeed(config.snapbackSpeed)) config.snapbackSpeed = DEFAULT_SNAPBACK_SPEED | |
| if (!validAnimationSpeed(config.snapSpeed)) config.snapSpeed = DEFAULT_SNAP_SPEED | |
| if (!validAnimationSpeed(config.trashSpeed)) config.trashSpeed = DEFAULT_TRASH_SPEED | |
| // throttle rate | |
| if (!validThrottleRate(config.dragThrottleRate)) config.dragThrottleRate = DEFAULT_DRAG_THROTTLE_RATE | |
| return config | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Dependencies | |
| // --------------------------------------------------------------------------- | |
| // check for a compatible version of jQuery | |
| function checkJQuery () { | |
| if (!validJQueryVersion()) { | |
| var errorMsg = 'Chessboard Error 1005: Unable to find a valid version of jQuery. ' + | |
| 'Please include jQuery ' + MINIMUM_JQUERY_VERSION + ' or higher on the page' + | |
| '\n\n' + | |
| 'Exiting' + ELLIPSIS | |
| window.alert(errorMsg) | |
| return false | |
| } | |
| return true | |
| } | |
| // return either boolean false or the $container element | |
| function checkContainerArg (containerElOrString) { | |
| if (containerElOrString === '') { | |
| var errorMsg1 = 'Chessboard Error 1001: ' + | |
| 'The first argument to Chessboard() cannot be an empty string.' + | |
| '\n\n' + | |
| 'Exiting' + ELLIPSIS | |
| window.alert(errorMsg1) | |
| return false | |
| } | |
| // convert containerEl to query selector if it is a string | |
| if (isString(containerElOrString) && | |
| containerElOrString.charAt(0) !== '#') { | |
| containerElOrString = '#' + containerElOrString | |
| } | |
| // containerEl must be something that becomes a jQuery collection of size 1 | |
| var $container = $(containerElOrString) | |
| if ($container.length !== 1) { | |
| var errorMsg2 = 'Chessboard Error 1003: ' + | |
| 'The first argument to Chessboard() must be the ID of a DOM node, ' + | |
| 'an ID query selector, or a single DOM node.' + | |
| '\n\n' + | |
| 'Exiting' + ELLIPSIS | |
| window.alert(errorMsg2) | |
| return false | |
| } | |
| return $container | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Constructor | |
| // --------------------------------------------------------------------------- | |
| function constructor (containerElOrString, config) { | |
| // first things first: check basic dependencies | |
| if (!checkJQuery()) return null | |
| var $container = checkContainerArg(containerElOrString) | |
| if (!$container) return null | |
| // ensure the config object is what we expect | |
| config = expandConfigArgumentShorthand(config) | |
| config = expandConfig(config) | |
| // DOM elements | |
| var $board = null | |
| var $draggedPiece = null | |
| var $sparePiecesTop = null | |
| var $sparePiecesBottom = null | |
| // constructor return object | |
| var widget = {} | |
| // ------------------------------------------------------------------------- | |
| // Stateful | |
| // ------------------------------------------------------------------------- | |
| var boardBorderSize = 2 | |
| var currentOrientation = 'white' | |
| var currentPosition = {} | |
| var draggedPiece = null | |
| var draggedPieceLocation = null | |
| var draggedPieceSource = null | |
| var isDragging = false | |
| var sparePiecesElsIds = {} | |
| var squareElsIds = {} | |
| var squareElsOffsets = {} | |
| var squareSize = 16 | |
| // ------------------------------------------------------------------------- | |
| // Validation / Errors | |
| // ------------------------------------------------------------------------- | |
| function error (code, msg, obj) { | |
| // do nothing if showErrors is not set | |
| if ( | |
| config.hasOwnProperty('showErrors') !== true || | |
| config.showErrors === false | |
| ) { | |
| return | |
| } | |
| var errorText = 'Chessboard Error ' + code + ': ' + msg | |
| // print to console | |
| if ( | |
| config.showErrors === 'console' && | |
| typeof console === 'object' && | |
| typeof console.log === 'function' | |
| ) { | |
| console.log(errorText) | |
| if (arguments.length >= 2) { | |
| console.log(obj) | |
| } | |
| return | |
| } | |
| // alert errors | |
| if (config.showErrors === 'alert') { | |
| if (obj) { | |
| errorText += '\n\n' + JSON.stringify(obj) | |
| } | |
| window.alert(errorText) | |
| return | |
| } | |
| // custom function | |
| if (isFunction(config.showErrors)) { | |
| config.showErrors(code, msg, obj) | |
| } | |
| } | |
| function setInitialState () { | |
| currentOrientation = config.orientation | |
| // make sure position is valid | |
| if (config.hasOwnProperty('position')) { | |
| if (config.position === 'start') { | |
| currentPosition = deepCopy(START_POSITION) | |
| } else if (validFen(config.position)) { | |
| currentPosition = fenToObj(config.position) | |
| } else if (validPositionObject(config.position)) { | |
| currentPosition = deepCopy(config.position) | |
| } else { | |
| error( | |
| 7263, | |
| 'Invalid value passed to config.position.', | |
| config.position | |
| ) | |
| } | |
| } | |
| } | |
| // ------------------------------------------------------------------------- | |
| // DOM Misc | |
| // ------------------------------------------------------------------------- | |
| // calculates square size based on the width of the container | |
| // got a little CSS black magic here, so let me explain: | |
| // get the width of the container element (could be anything), reduce by 1 for | |
| // fudge factor, and then keep reducing until we find an exact mod 8 for | |
| // our square size | |
| function calculateSquareSize () { | |
| var containerWidth = parseInt($container.width(), 10) | |
| // defensive, prevent infinite loop | |
| if (!containerWidth || containerWidth <= 0) { | |
| return 0 | |
| } | |
| // pad one pixel | |
| var boardWidth = containerWidth - 1 | |
| while (boardWidth % 8 !== 0 && boardWidth > 0) { | |
| boardWidth = boardWidth - 1 | |
| } | |
| return boardWidth / 8 | |
| } | |
| // create random IDs for elements | |
| function createElIds () { | |
| // squares on the board | |
| for (var i = 0; i < COLUMNS.length; i++) { | |
| for (var j = 1; j <= 8; j++) { | |
| var square = COLUMNS[i] + j | |
| squareElsIds[square] = square + '-' + uuid() | |
| } | |
| } | |
| // spare pieces | |
| var pieces = 'KQRNBP'.split('') | |
| for (i = 0; i < pieces.length; i++) { | |
| var whitePiece = 'w' + pieces[i] | |
| var blackPiece = 'b' + pieces[i] | |
| sparePiecesElsIds[whitePiece] = whitePiece + '-' + uuid() | |
| sparePiecesElsIds[blackPiece] = blackPiece + '-' + uuid() | |
| } | |
| } | |
| // ------------------------------------------------------------------------- | |
| // Markup Building | |
| // ------------------------------------------------------------------------- | |
| function buildBoardHTML (orientation) { | |
| if (orientation !== 'black') { | |
| orientation = 'white' | |
| } | |
| var html = '' | |
| // algebraic notation / orientation | |
| var alpha = deepCopy(COLUMNS) | |
| var row = 8 | |
| if (orientation === 'black') { | |
| alpha.reverse() | |
| row = 1 | |
| } | |
| var squareColor = 'white' | |
| for (var i = 0; i < 8; i++) { | |
| html += '<div class="{row}">' | |
| for (var j = 0; j < 8; j++) { | |
| var square = alpha[j] + row | |
| html += '<div class="{square} ' + CSS[squareColor] + ' ' + | |
| 'square-' + square + '" ' + | |
| 'style="width:' + squareSize + 'px;height:' + squareSize + 'px;" ' + | |
| 'id="' + squareElsIds[square] + '" ' + | |
| 'data-square="' + square + '">' | |
| if (config.showNotation) { | |
| // alpha notation | |
| if ((orientation === 'white' && row === 1) || | |
| (orientation === 'black' && row === 8)) { | |
| html += '<div class="{notation} {alpha}">' + alpha[j] + '</div>' | |
| } | |
| // numeric notation | |
| if (j === 0) { | |
| html += '<div class="{notation} {numeric}">' + row + '</div>' | |
| } | |
| } | |
| html += '</div>' // end .square | |
| squareColor = (squareColor === 'white') ? 'black' : 'white' | |
| } | |
| html += '<div class="{clearfix}"></div></div>' | |
| squareColor = (squareColor === 'white') ? 'black' : 'white' | |
| if (orientation === 'white') { | |
| row = row - 1 | |
| } else { | |
| row = row + 1 | |
| } | |
| } | |
| return interpolateTemplate(html, CSS) | |
| } | |
| function buildPieceImgSrc (piece) { | |
| if (isFunction(config.pieceTheme)) { | |
| return config.pieceTheme(piece) | |
| } | |
| if (isString(config.pieceTheme)) { | |
| return interpolateTemplate(config.pieceTheme, {piece: piece}) | |
| } | |
| // NOTE: this should never happen | |
| error(8272, 'Unable to build image source for config.pieceTheme.') | |
| return '' | |
| } | |
| function buildPieceHTML (piece, hidden, id) { | |
| var html = '<img src="' + buildPieceImgSrc(piece) + '" ' | |
| if (isString(id) && id !== '') { | |
| html += 'id="' + id + '" ' | |
| } | |
| html += 'alt="" ' + | |
| 'class="{piece}" ' + | |
| 'data-piece="' + piece + '" ' + | |
| 'style="width:' + squareSize + 'px;' + 'height:' + squareSize + 'px;' | |
| if (hidden) { | |
| html += 'display:none;' | |
| } | |
| html += '" />' | |
| return interpolateTemplate(html, CSS) | |
| } | |
| function buildSparePiecesHTML (color) { | |
| var pieces = ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP'] | |
| if (color === 'black') { | |
| pieces = ['bK', 'bQ', 'bR', 'bB', 'bN', 'bP'] | |
| } | |
| var html = '' | |
| for (var i = 0; i < pieces.length; i++) { | |
| html += buildPieceHTML(pieces[i], false, sparePiecesElsIds[pieces[i]]) | |
| } | |
| return html | |
| } | |
| // ------------------------------------------------------------------------- | |
| // Animations | |
| // ------------------------------------------------------------------------- | |
| function animateSquareToSquare (src, dest, piece, completeFn) { | |
| // get information about the source and destination squares | |
| var $srcSquare = $('#' + squareElsIds[src]) | |
| var srcSquarePosition = $srcSquare.offset() | |
| var $destSquare = $('#' + squareElsIds[dest]) | |
| var destSquarePosition = $destSquare.offset() | |
| // create the animated piece and absolutely position it | |
| // over the source square | |
| var animatedPieceId = uuid() | |
| $('body').append(buildPieceHTML(piece, true, animatedPieceId)) | |
| var $animatedPiece = $('#' + animatedPieceId) | |
| $animatedPiece.css({ | |
| display: '', | |
| position: 'absolute', | |
| top: srcSquarePosition.top, | |
| left: srcSquarePosition.left | |
| }) | |
| // remove original piece from source square | |
| $srcSquare.find('.' + CSS.piece).remove() | |
| function onFinishAnimation1 () { | |
| // add the "real" piece to the destination square | |
| $destSquare.append(buildPieceHTML(piece)) | |
| // remove the animated piece | |
| $animatedPiece.remove() | |
| // run complete function | |
| if (isFunction(completeFn)) { | |
| completeFn() | |
| } | |
| } | |
| // animate the piece to the destination square | |
| var opts = { | |
| duration: config.moveSpeed, | |
| complete: onFinishAnimation1 | |
| } | |
| $animatedPiece.animate(destSquarePosition, opts) | |
| } | |
| function animateSparePieceToSquare (piece, dest, completeFn) { | |
| var srcOffset = $('#' + sparePiecesElsIds[piece]).offset() | |
| var $destSquare = $('#' + squareElsIds[dest]) | |
| var destOffset = $destSquare.offset() | |
| // create the animate piece | |
| var pieceId = uuid() | |
| $('body').append(buildPieceHTML(piece, true, pieceId)) | |
| var $animatedPiece = $('#' + pieceId) | |
| $animatedPiece.css({ | |
| display: '', | |
| position: 'absolute', | |
| left: srcOffset.left, | |
| top: srcOffset.top | |
| }) | |
| // on complete | |
| function onFinishAnimation2 () { | |
| // add the "real" piece to the destination square | |
| $destSquare.find('.' + CSS.piece).remove() | |
| $destSquare.append(buildPieceHTML(piece)) | |
| // remove the animated piece | |
| $animatedPiece.remove() | |
| // run complete function | |
| if (isFunction(completeFn)) { | |
| completeFn() | |
| } | |
| } | |
| // animate the piece to the destination square | |
| var opts = { | |
| duration: config.moveSpeed, | |
| complete: onFinishAnimation2 | |
| } | |
| $animatedPiece.animate(destOffset, opts) | |
| } | |
| // execute an array of animations | |
| function doAnimations (animations, oldPos, newPos) { | |
| if (animations.length === 0) return | |
| var numFinished = 0 | |
| function onFinishAnimation3 () { | |
| // exit if all the animations aren't finished | |
| numFinished = numFinished + 1 | |
| if (numFinished !== animations.length) return | |
| drawPositionInstant() | |
| // run their onMoveEnd function | |
| if (isFunction(config.onMoveEnd)) { | |
| config.onMoveEnd(deepCopy(oldPos), deepCopy(newPos)) | |
| } | |
| } | |
| for (var i = 0; i < animations.length; i++) { | |
| var animation = animations[i] | |
| // clear a piece | |
| if (animation.type === 'clear') { | |
| $('#' + squareElsIds[animation.square] + ' .' + CSS.piece) | |
| .fadeOut(config.trashSpeed, onFinishAnimation3) | |
| // add a piece with no spare pieces - fade the piece onto the square | |
| } else if (animation.type === 'add' && !config.sparePieces) { | |
| $('#' + squareElsIds[animation.square]) | |
| .append(buildPieceHTML(animation.piece, true)) | |
| .find('.' + CSS.piece) | |
| .fadeIn(config.appearSpeed, onFinishAnimation3) | |
| // add a piece with spare pieces - animate from the spares | |
| } else if (animation.type === 'add' && config.sparePieces) { | |
| animateSparePieceToSquare(animation.piece, animation.square, onFinishAnimation3) | |
| // move a piece from squareA to squareB | |
| } else if (animation.type === 'move') { | |
| animateSquareToSquare(animation.source, animation.destination, animation.piece, onFinishAnimation3) | |
| } | |
| } | |
| } | |
| // calculate an array of animations that need to happen in order to get | |
| // from pos1 to pos2 | |
| function calculateAnimations (pos1, pos2) { | |
| // make copies of both | |
| pos1 = deepCopy(pos1) | |
| pos2 = deepCopy(pos2) | |
| var animations = [] | |
| var squaresMovedTo = {} | |
| // remove pieces that are the same in both positions | |
| for (var i in pos2) { | |
| if (!pos2.hasOwnProperty(i)) continue | |
| if (pos1.hasOwnProperty(i) && pos1[i] === pos2[i]) { | |
| delete pos1[i] | |
| delete pos2[i] | |
| } | |
| } | |
| // find all the "move" animations | |
| for (i in pos2) { | |
| if (!pos2.hasOwnProperty(i)) continue | |
| var closestPiece = findClosestPiece(pos1, pos2[i], i) | |
| if (closestPiece) { | |
| animations.push({ | |
| type: 'move', | |
| source: closestPiece, | |
| destination: i, | |
| piece: pos2[i] | |
| }) | |
| delete pos1[closestPiece] | |
| delete pos2[i] | |
| squaresMovedTo[i] = true | |
| } | |
| } | |
| // "add" animations | |
| for (i in pos2) { | |
| if (!pos2.hasOwnProperty(i)) continue | |
| animations.push({ | |
| type: 'add', | |
| square: i, | |
| piece: pos2[i] | |
| }) | |
| delete pos2[i] | |
| } | |
| // "clear" animations | |
| for (i in pos1) { | |
| if (!pos1.hasOwnProperty(i)) continue | |
| // do not clear a piece if it is on a square that is the result | |
| // of a "move", ie: a piece capture | |
| if (squaresMovedTo.hasOwnProperty(i)) continue | |
| animations.push({ | |
| type: 'clear', | |
| square: i, | |
| piece: pos1[i] | |
| }) | |
| delete pos1[i] | |
| } | |
| return animations | |
| } | |
| // ------------------------------------------------------------------------- | |
| // Control Flow | |
| // ------------------------------------------------------------------------- | |
| function drawPositionInstant () { | |
| // clear the board | |
| $board.find('.' + CSS.piece).remove() | |
| // add the pieces | |
| for (var i in currentPosition) { | |
| if (!currentPosition.hasOwnProperty(i)) continue | |
| $('#' + squareElsIds[i]).append(buildPieceHTML(currentPosition[i])) | |
| } | |
| } | |
| function drawBoard () { | |
| $board.html(buildBoardHTML(currentOrientation, squareSize, config.showNotation)) | |
| drawPositionInstant() | |
| if (config.sparePieces) { | |
| if (currentOrientation === 'white') { | |
| $sparePiecesTop.html(buildSparePiecesHTML('black')) | |
| $sparePiecesBottom.html(buildSparePiecesHTML('white')) | |
| } else { | |
| $sparePiecesTop.html(buildSparePiecesHTML('white')) | |
| $sparePiecesBottom.html(buildSparePiecesHTML('black')) | |
| } | |
| } | |
| } | |
| function setCurrentPosition (position) { | |
| var oldPos = deepCopy(currentPosition) | |
| var newPos = deepCopy(position) | |
| var oldFen = objToFen(oldPos) | |
| var newFen = objToFen(newPos) | |
| // do nothing if no change in position | |
| if (oldFen === newFen) return | |
| // run their onChange function | |
| if (isFunction(config.onChange)) { | |
| config.onChange(oldPos, newPos) | |
| } | |
| // update state | |
| currentPosition = position | |
| } | |
| function isXYOnSquare (x, y) { | |
| for (var i in squareElsOffsets) { | |
| if (!squareElsOffsets.hasOwnProperty(i)) continue | |
| var s = squareElsOffsets[i] | |
| if (x >= s.left && | |
| x < s.left + squareSize && | |
| y >= s.top && | |
| y < s.top + squareSize) { | |
| return i | |
| } | |
| } | |
| return 'offboard' | |
| } | |
| // records the XY coords of every square into memory | |
| function captureSquareOffsets () { | |
| squareElsOffsets = {} | |
| for (var i in squareElsIds) { | |
| if (!squareElsIds.hasOwnProperty(i)) continue | |
| squareElsOffsets[i] = $('#' + squareElsIds[i]).offset() | |
| } | |
| } | |
| function removeSquareHighlights () { | |
| $board | |
| .find('.' + CSS.square) | |
| .removeClass(CSS.highlight1 + ' ' + CSS.highlight2) | |
| } | |
| function snapbackDraggedPiece () { | |
| // there is no "snapback" for spare pieces | |
| if (draggedPieceSource === 'spare') { | |
| trashDraggedPiece() | |
| return | |
| } | |
| removeSquareHighlights() | |
| // animation complete | |
| function complete () { | |
| drawPositionInstant() | |
| $draggedPiece.css('display', 'none') | |
| // run their onSnapbackEnd function | |
| if (isFunction(config.onSnapbackEnd)) { | |
| config.onSnapbackEnd( | |
| draggedPiece, | |
| draggedPieceSource, | |
| deepCopy(currentPosition), | |
| currentOrientation | |
| ) | |
| } | |
| } | |
| // get source square position | |
| var sourceSquarePosition = $('#' + squareElsIds[draggedPieceSource]).offset() | |
| // animate the piece to the target square | |
| var opts = { | |
| duration: config.snapbackSpeed, | |
| complete: complete | |
| } | |
| $draggedPiece.animate(sourceSquarePosition, opts) | |
| // set state | |
| isDragging = false | |
| } | |
| function trashDraggedPiece () { | |
| removeSquareHighlights() | |
| // remove the source piece | |
| var newPosition = deepCopy(currentPosition) | |
| delete newPosition[draggedPieceSource] | |
| setCurrentPosition(newPosition) | |
| // redraw the position | |
| drawPositionInstant() | |
| // hide the dragged piece | |
| $draggedPiece.fadeOut(config.trashSpeed) | |
| // set state | |
| isDragging = false | |
| } | |
| function dropDraggedPieceOnSquare (square) { | |
| removeSquareHighlights() | |
| // update position | |
| var newPosition = deepCopy(currentPosition) | |
| delete newPosition[draggedPieceSource] | |
| newPosition[square] = draggedPiece | |
| setCurrentPosition(newPosition) | |
| // get target square information | |
| var targetSquarePosition = $('#' + squareElsIds[square]).offset() | |
| // animation complete | |
| function onAnimationComplete () { | |
| drawPositionInstant() | |
| $draggedPiece.css('display', 'none') | |
| // execute their onSnapEnd function | |
| if (isFunction(config.onSnapEnd)) { | |
| config.onSnapEnd(draggedPieceSource, square, draggedPiece) | |
| } | |
| } | |
| // snap the piece to the target square | |
| var opts = { | |
| duration: config.snapSpeed, | |
| complete: onAnimationComplete | |
| } | |
| $draggedPiece.animate(targetSquarePosition, opts) | |
| // set state | |
| isDragging = false | |
| } | |
| function beginDraggingPiece (source, piece, x, y) { | |
| // run their custom onDragStart function | |
| // their custom onDragStart function can cancel drag start | |
| if (isFunction(config.onDragStart) && | |
| config.onDragStart(source, piece, deepCopy(currentPosition), currentOrientation) === false) { | |
| return | |
| } | |
| // set state | |
| isDragging = true | |
| draggedPiece = piece | |
| draggedPieceSource = source | |
| // if the piece came from spare pieces, location is offboard | |
| if (source === 'spare') { | |
| draggedPieceLocation = 'offboard' | |
| } else { | |
| draggedPieceLocation = source | |
| } | |
| // capture the x, y coords of all squares in memory | |
| captureSquareOffsets() | |
| // create the dragged piece | |
| $draggedPiece.attr('src', buildPieceImgSrc(piece)).css({ | |
| display: '', | |
| position: 'absolute', | |
| left: x - squareSize / 2, | |
| top: y - squareSize / 2 | |
| }) | |
| if (source !== 'spare') { | |
| // highlight the source square and hide the piece | |
| $('#' + squareElsIds[source]) | |
| .addClass(CSS.highlight1) | |
| .find('.' + CSS.piece) | |
| .css('display', 'none') | |
| } | |
| } | |
| function updateDraggedPiece (x, y) { | |
| // put the dragged piece over the mouse cursor | |
| $draggedPiece.css({ | |
| left: x - squareSize / 2, | |
| top: y - squareSize / 2 | |
| }) | |
| // get location | |
| var location = isXYOnSquare(x, y) | |
| // do nothing if the location has not changed | |
| if (location === draggedPieceLocation) return | |
| // remove highlight from previous square | |
| if (validSquare(draggedPieceLocation)) { | |
| $('#' + squareElsIds[draggedPieceLocation]).removeClass(CSS.highlight2) | |
| } | |
| // add highlight to new square | |
| if (validSquare(location)) { | |
| $('#' + squareElsIds[location]).addClass(CSS.highlight2) | |
| } | |
| // run onDragMove | |
| if (isFunction(config.onDragMove)) { | |
| config.onDragMove( | |
| location, | |
| draggedPieceLocation, | |
| draggedPieceSource, | |
| draggedPiece, | |
| deepCopy(currentPosition), | |
| currentOrientation | |
| ) | |
| } | |
| // update state | |
| draggedPieceLocation = location | |
| } | |
| function stopDraggedPiece (location) { | |
| // determine what the action should be | |
| var action = 'drop' | |
| if (location === 'offboard' && config.dropOffBoard === 'snapback') { | |
| action = 'snapback' | |
| } | |
| if (location === 'offboard' && config.dropOffBoard === 'trash') { | |
| action = 'trash' | |
| } | |
| // run their onDrop function, which can potentially change the drop action | |
| if (isFunction(config.onDrop)) { | |
| var newPosition = deepCopy(currentPosition) | |
| // source piece is a spare piece and position is off the board | |
| // if (draggedPieceSource === 'spare' && location === 'offboard') {...} | |
| // position has not changed; do nothing | |
| // source piece is a spare piece and position is on the board | |
| if (draggedPieceSource === 'spare' && validSquare(location)) { | |
| // add the piece to the board | |
| newPosition[location] = draggedPiece | |
| } | |
| // source piece was on the board and position is off the board | |
| if (validSquare(draggedPieceSource) && location === 'offboard') { | |
| // remove the piece from the board | |
| delete newPosition[draggedPieceSource] | |
| } | |
| // source piece was on the board and position is on the board | |
| if (validSquare(draggedPieceSource) && validSquare(location)) { | |
| // move the piece | |
| delete newPosition[draggedPieceSource] | |
| newPosition[location] = draggedPiece | |
| } | |
| var oldPosition = deepCopy(currentPosition) | |
| var result = config.onDrop( | |
| draggedPieceSource, | |
| location, | |
| draggedPiece, | |
| newPosition, | |
| oldPosition, | |
| currentOrientation | |
| ) | |
| if (result === 'snapback' || result === 'trash') { | |
| action = result | |
| } | |
| } | |
| // do it! | |
| if (action === 'snapback') { | |
| snapbackDraggedPiece() | |
| } else if (action === 'trash') { | |
| trashDraggedPiece() | |
| } else if (action === 'drop') { | |
| dropDraggedPieceOnSquare(location) | |
| } | |
| } | |
| // ------------------------------------------------------------------------- | |
| // Public Methods | |
| // ------------------------------------------------------------------------- | |
| // clear the board | |
| widget.clear = function (useAnimation) { | |
| widget.position({}, useAnimation) | |
| } | |
| // remove the widget from the page | |
| widget.destroy = function () { | |
| // remove markup | |
| $container.html('') | |
| $draggedPiece.remove() | |
| // remove event handlers | |
| $container.unbind() | |
| } | |
| // shorthand method to get the current FEN | |
| widget.fen = function () { | |
| return widget.position('fen') | |
| } | |
| // flip orientation | |
| widget.flip = function () { | |
| return widget.orientation('flip') | |
| } | |
| // move pieces | |
| // TODO: this method should be variadic as well as accept an array of moves | |
| widget.move = function () { | |
| // no need to throw an error here; just do nothing | |
| // TODO: this should return the current position | |
| if (arguments.length === 0) return | |
| var useAnimation = true | |
| // collect the moves into an object | |
| var moves = {} | |
| for (var i = 0; i < arguments.length; i++) { | |
| // any "false" to this function means no animations | |
| if (arguments[i] === false) { | |
| useAnimation = false | |
| continue | |
| } | |
| // skip invalid arguments | |
| if (!validMove(arguments[i])) { | |
| error(2826, 'Invalid move passed to the move method.', arguments[i]) | |
| continue | |
| } | |
| var tmp = arguments[i].split('-') | |
| moves[tmp[0]] = tmp[1] | |
| } | |
| // calculate position from moves | |
| var newPos = calculatePositionFromMoves(currentPosition, moves) | |
| // update the board | |
| widget.position(newPos, useAnimation) | |
| // return the new position object | |
| return newPos | |
| } | |
| widget.orientation = function (arg) { | |
| // no arguments, return the current orientation | |
| if (arguments.length === 0) { | |
| return currentOrientation | |
| } | |
| // set to white or black | |
| if (arg === 'white' || arg === 'black') { | |
| currentOrientation = arg | |
| drawBoard() | |
| return currentOrientation | |
| } | |
| // flip orientation | |
| if (arg === 'flip') { | |
| currentOrientation = currentOrientation === 'white' ? 'black' : 'white' | |
| drawBoard() | |
| return currentOrientation | |
| } | |
| error(5482, 'Invalid value passed to the orientation method.', arg) | |
| } | |
| widget.position = function (position, useAnimation) { | |
| // no arguments, return the current position | |
| if (arguments.length === 0) { | |
| return deepCopy(currentPosition) | |
| } | |
| // get position as FEN | |
| if (isString(position) && position.toLowerCase() === 'fen') { | |
| return objToFen(currentPosition) | |
| } | |
| // start position | |
| if (isString(position) && position.toLowerCase() === 'start') { | |
| position = deepCopy(START_POSITION) | |
| } | |
| // convert FEN to position object | |
| if (validFen(position)) { | |
| position = fenToObj(position) | |
| } | |
| // validate position object | |
| if (!validPositionObject(position)) { | |
| error(6482, 'Invalid value passed to the position method.', position) | |
| return | |
| } | |
| // default for useAnimations is true | |
| if (useAnimation !== false) useAnimation = true | |
| if (useAnimation) { | |
| // start the animations | |
| var animations = calculateAnimations(currentPosition, position) | |
| doAnimations(animations, currentPosition, position) | |
| // set the new position | |
| setCurrentPosition(position) | |
| } else { | |
| // instant update | |
| setCurrentPosition(position) | |
| drawPositionInstant() | |
| } | |
| } | |
| widget.resize = function () { | |
| // calulate the new square size | |
| squareSize = calculateSquareSize() | |
| // set board width | |
| $board.css('width', squareSize * 8 + 'px') | |
| // set drag piece size | |
| $draggedPiece.css({ | |
| height: squareSize, | |
| width: squareSize | |
| }) | |
| // spare pieces | |
| if (config.sparePieces) { | |
| $container | |
| .find('.' + CSS.sparePieces) | |
| .css('paddingLeft', squareSize + boardBorderSize + 'px') | |
| } | |
| // redraw the board | |
| drawBoard() | |
| } | |
| // set the starting position | |
| widget.start = function (useAnimation) { | |
| widget.position('start', useAnimation) | |
| } | |
| // ------------------------------------------------------------------------- | |
| // Browser Events | |
| // ------------------------------------------------------------------------- | |
| function stopDefault (evt) { | |
| evt.preventDefault() | |
| } | |
| function mousedownSquare (evt) { | |
| // do nothing if we're not draggable | |
| if (!config.draggable) return | |
| // do nothing if there is no piece on this square | |
| var square = $(this).attr('data-square') | |
| if (!validSquare(square)) return | |
| if (!currentPosition.hasOwnProperty(square)) return | |
| beginDraggingPiece(square, currentPosition[square], evt.pageX, evt.pageY) | |
| } | |
| function touchstartSquare (e) { | |
| // do nothing if we're not draggable | |
| if (!config.draggable) return | |
| // do nothing if there is no piece on this square | |
| var square = $(this).attr('data-square') | |
| if (!validSquare(square)) return | |
| if (!currentPosition.hasOwnProperty(square)) return | |
| e = e.originalEvent | |
| beginDraggingPiece( | |
| square, | |
| currentPosition[square], | |
| e.changedTouches[0].pageX, | |
| e.changedTouches[0].pageY | |
| ) | |
| } | |
| function mousedownSparePiece (evt) { | |
| // do nothing if sparePieces is not enabled | |
| if (!config.sparePieces) return | |
| var piece = $(this).attr('data-piece') | |
| beginDraggingPiece('spare', piece, evt.pageX, evt.pageY) | |
| } | |
| function touchstartSparePiece (e) { | |
| // do nothing if sparePieces is not enabled | |
| if (!config.sparePieces) return | |
| var piece = $(this).attr('data-piece') | |
| e = e.originalEvent | |
| beginDraggingPiece( | |
| 'spare', | |
| piece, | |
| e.changedTouches[0].pageX, | |
| e.changedTouches[0].pageY | |
| ) | |
| } | |
| function mousemoveWindow (evt) { | |
| if (isDragging) { | |
| updateDraggedPiece(evt.pageX, evt.pageY) | |
| } | |
| } | |
| var throttledMousemoveWindow = throttle(mousemoveWindow, config.dragThrottleRate) | |
| function touchmoveWindow (evt) { | |
| // do nothing if we are not dragging a piece | |
| if (!isDragging) return | |
| // prevent screen from scrolling | |
| evt.preventDefault() | |
| updateDraggedPiece(evt.originalEvent.changedTouches[0].pageX, | |
| evt.originalEvent.changedTouches[0].pageY) | |
| } | |
| var throttledTouchmoveWindow = throttle(touchmoveWindow, config.dragThrottleRate) | |
| function mouseupWindow (evt) { | |
| // do nothing if we are not dragging a piece | |
| if (!isDragging) return | |
| // get the location | |
| var location = isXYOnSquare(evt.pageX, evt.pageY) | |
| stopDraggedPiece(location) | |
| } | |
| function touchendWindow (evt) { | |
| // do nothing if we are not dragging a piece | |
| if (!isDragging) return | |
| // get the location | |
| var location = isXYOnSquare(evt.originalEvent.changedTouches[0].pageX, | |
| evt.originalEvent.changedTouches[0].pageY) | |
| stopDraggedPiece(location) | |
| } | |
| function mouseenterSquare (evt) { | |
| // do not fire this event if we are dragging a piece | |
| // NOTE: this should never happen, but it's a safeguard | |
| if (isDragging) return | |
| // exit if they did not provide a onMouseoverSquare function | |
| if (!isFunction(config.onMouseoverSquare)) return | |
| // get the square | |
| var square = $(evt.currentTarget).attr('data-square') | |
| // NOTE: this should never happen; defensive | |
| if (!validSquare(square)) return | |
| // get the piece on this square | |
| var piece = false | |
| if (currentPosition.hasOwnProperty(square)) { | |
| piece = currentPosition[square] | |
| } | |
| // execute their function | |
| config.onMouseoverSquare(square, piece, deepCopy(currentPosition), currentOrientation) | |
| } | |
| function mouseleaveSquare (evt) { | |
| // do not fire this event if we are dragging a piece | |
| // NOTE: this should never happen, but it's a safeguard | |
| if (isDragging) return | |
| // exit if they did not provide an onMouseoutSquare function | |
| if (!isFunction(config.onMouseoutSquare)) return | |
| // get the square | |
| var square = $(evt.currentTarget).attr('data-square') | |
| // NOTE: this should never happen; defensive | |
| if (!validSquare(square)) return | |
| // get the piece on this square | |
| var piece = false | |
| if (currentPosition.hasOwnProperty(square)) { | |
| piece = currentPosition[square] | |
| } | |
| // execute their function | |
| config.onMouseoutSquare(square, piece, deepCopy(currentPosition), currentOrientation) | |
| } | |
| // ------------------------------------------------------------------------- | |
| // Initialization | |
| // ------------------------------------------------------------------------- | |
| function addEvents () { | |
| // prevent "image drag" | |
| $('body').on('mousedown mousemove', '.' + CSS.piece, stopDefault) | |
| // mouse drag pieces | |
| $board.on('mousedown', '.' + CSS.square, mousedownSquare) | |
| $container.on('mousedown', '.' + CSS.sparePieces + ' .' + CSS.piece, mousedownSparePiece) | |
| // mouse enter / leave square | |
| $board | |
| .on('mouseenter', '.' + CSS.square, mouseenterSquare) | |
| .on('mouseleave', '.' + CSS.square, mouseleaveSquare) | |
| // piece drag | |
| var $window = $(window) | |
| $window | |
| .on('mousemove', throttledMousemoveWindow) | |
| .on('mouseup', mouseupWindow) | |
| // touch drag pieces | |
| if (isTouchDevice()) { | |
| $board.on('touchstart', '.' + CSS.square, touchstartSquare) | |
| $container.on('touchstart', '.' + CSS.sparePieces + ' .' + CSS.piece, touchstartSparePiece) | |
| $window | |
| .on('touchmove', throttledTouchmoveWindow) | |
| .on('touchend', touchendWindow) | |
| } | |
| } | |
| function initDOM () { | |
| // create unique IDs for all the elements we will create | |
| createElIds() | |
| // build board and save it in memory | |
| $container.html(buildContainerHTML(config.sparePieces)) | |
| $board = $container.find('.' + CSS.board) | |
| if (config.sparePieces) { | |
| $sparePiecesTop = $container.find('.' + CSS.sparePiecesTop) | |
| $sparePiecesBottom = $container.find('.' + CSS.sparePiecesBottom) | |
| } | |
| // create the drag piece | |
| var draggedPieceId = uuid() | |
| $('body').append(buildPieceHTML('wP', true, draggedPieceId)) | |
| $draggedPiece = $('#' + draggedPieceId) | |
| // TODO: need to remove this dragged piece element if the board is no | |
| // longer in the DOM | |
| // get the border size | |
| boardBorderSize = parseInt($board.css('borderLeftWidth'), 10) | |
| // set the size and draw the board | |
| widget.resize() | |
| } | |
| // ------------------------------------------------------------------------- | |
| // Initialization | |
| // ------------------------------------------------------------------------- | |
| setInitialState() | |
| initDOM() | |
| addEvents() | |
| // return the widget object | |
| return widget | |
| } // end constructor | |
| // TODO: do module exports here | |
| window['Chessboard'] = constructor | |
| // support legacy ChessBoard name | |
| window['ChessBoard'] = window['Chessboard'] | |
| // expose util functions | |
| window['Chessboard']['fenToObj'] = fenToObj | |
| window['Chessboard']['objToFen'] = objToFen | |
| })() // end anonymous wrapper | |