From 8e5bf53224daf3b51c892f6ffffba17b71c1f8de Mon Sep 17 00:00:00 2001 From: wukunyu264 Date: Wed, 13 Aug 2025 16:54:23 +0800 Subject: [PATCH 1/3] Update chessboard-1.0.0.js redos --- .../chessboardjs-1.0.0/js/chessboard-1.0.0.js | 2519 +++++++++-------- 1 file changed, 1260 insertions(+), 1259 deletions(-) diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.js b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.js index 0939efce3f0..eb763a1efb4 100644 --- a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.js +++ b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.js @@ -1,268 +1,268 @@ -// 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 +// 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 - } - + '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 + 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 + var squares = move.split('-') + if (squares.length !== 2) return false + + return validSquare(squares[0]) && validSquare(squares[1]) + } - return validSquare(squares[0]) && validSquare(squares[1]) + function validSquare (square) { + return isString(square) && square.search(/^[a-h][1-8]$/) !== -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({})) } - - 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 + + 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({})) } - - 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 + + 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 = 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) { + 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 + + 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 @@ -274,62 +274,62 @@ 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() - } - + + 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 + if (!validFen(fen)) return false // cut off any move, castling, etc info from the end // we're only interested in position information @@ -341,32 +341,32 @@ var currentRow = 8 for (var i = 0; i < 8; i++) { var row = rows[i].split('') - var colIdx = 0 + 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 { + var numEmptySquares = parseInt(row[j], 10) + colIdx = colIdx + numEmptySquares + } else { // piece - var square = COLUMNS[colIdx] + currentRow + var square = COLUMNS[colIdx] + currentRow position[square] = fenToPieceCode(row[j]) - colIdx = colIdx + 1 + colIdx = colIdx + 1 } } - - currentRow = currentRow - 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 + if (!validPositionObject(obj)) return false var fen = '' @@ -376,329 +376,329 @@ var square = COLUMNS[j] + currentRow // piece exists - if (obj.hasOwnProperty(square)) { - fen = fen + pieceCodeToFen(obj[square]) + if (obj.hasOwnProperty(square)) { + fen = fen + pieceCodeToFen(obj[square]) } else { // empty space - fen = fen + '1' + fen = fen + '1' } } - if (i !== 7) { - fen = fen + '/' - } + if (i !== 7) { + fen = fen + '/' + } - currentRow = currentRow - 1 + currentRow = currentRow - 1 } - // squeeze the empty numbers together - fen = squeezeFenEmptySquares(fen) - + // 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 = '
' - - if (hasSparePieces) { - html += '
' - } - - html += '
' - - if (hasSparePieces) { - html += '
' - } - - html += '
' - - 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 + + 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 = '
' + + if (hasSparePieces) { + html += '
' + } + + html += '
' + + if (hasSparePieces) { + html += '
' + } + + html += '
' + + 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 - // ------------------------------------------------------------------------- + // ------------------------------------------------------------------------- + // 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 + config.hasOwnProperty('showErrors') !== true || + config.showErrors === false ) { return } - var errorText = 'Chessboard Error ' + code + ': ' + msg + var errorText = 'Chessboard Error ' + code + ': ' + msg // print to console if ( - config.showErrors === 'console' && + config.showErrors === 'console' && typeof console === 'object' && typeof console.log === 'function' ) { @@ -710,7 +710,7 @@ } // alert errors - if (config.showErrors === 'alert') { + if (config.showErrors === 'alert') { if (obj) { errorText += '\n\n' + JSON.stringify(obj) } @@ -718,176 +718,176 @@ 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 + // 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) + var containerWidth = parseInt($container.width(), 10) - // defensive, prevent infinite loop - if (!containerWidth || containerWidth <= 0) { + // defensive, prevent infinite loop + if (!containerWidth || containerWidth <= 0) { return 0 } - // pad one pixel + // pad one pixel var boardWidth = containerWidth - 1 - while (boardWidth % 8 !== 0 && boardWidth > 0) { - boardWidth = boardWidth - 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++) { + // 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() + squareElsIds[square] = square + '-' + uuid() } } - // spare pieces - var pieces = 'KQRNBP'.split('') - for (i = 0; i < pieces.length; i++) { + // 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 += '
' - for (var j = 0; j < 8; j++) { - var square = alpha[j] + row - - html += '
' - - if (config.showNotation) { - // alpha notation - if ((orientation === 'white' && row === 1) || - (orientation === 'black' && row === 8)) { - html += '
' + alpha[j] + '
' - } - - // numeric notation - if (j === 0) { - html += '
' + row + '
' - } - } - - html += '
' // end .square - - squareColor = (squareColor === 'white') ? 'black' : 'white' - } - html += '
' - - squareColor = (squareColor === 'white') ? 'black' : 'white' - - if (orientation === 'white') { - row = row - 1 - } else { - row = row + 1 - } - } - - return interpolateTemplate(html, CSS) - } + 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 += '
' + for (var j = 0; j < 8; j++) { + var square = alpha[j] + row + + html += '
' + + if (config.showNotation) { + // alpha notation + if ((orientation === 'white' && row === 1) || + (orientation === 'black' && row === 8)) { + html += '
' + alpha[j] + '
' + } + + // numeric notation + if (j === 0) { + html += '
' + row + '
' + } + } + + html += '
' // end .square + + squareColor = (squareColor === 'white') ? 'black' : 'white' + } + html += '
' + + 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 (isFunction(config.pieceTheme)) { + return config.pieceTheme(piece) } - if (isString(config.pieceTheme)) { - return interpolateTemplate(config.pieceTheme, {piece: 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.') + // NOTE: this should never happen + error(8272, 'Unable to build image source for config.pieceTheme.') return '' } - function buildPieceHTML (piece, hidden, id) { + function buildPieceHTML (piece, hidden, id) { var html = '' - return interpolateTemplate(html, CSS) + return interpolateTemplate(html, CSS) } - function buildSparePiecesHTML (color) { + function buildSparePiecesHTML (color) { var pieces = ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP'] if (color === 'black') { pieces = ['bK', 'bQ', 'bR', 'bB', 'bN', 'bP'] @@ -895,153 +895,153 @@ var html = '' for (var i = 0; i < pieces.length; i++) { - html += buildPieceHTML(pieces[i], false, sparePiecesElsIds[pieces[i]]) + html += buildPieceHTML(pieces[i], false, sparePiecesElsIds[pieces[i]]) } return html } - // ------------------------------------------------------------------------- - // Animations - // ------------------------------------------------------------------------- + // ------------------------------------------------------------------------- + // 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 + // 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({ + $('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 original piece from source square + $srcSquare.find('.' + CSS.piece).remove() - // remove the animated piece - $animatedPiece.remove() + function onFinishAnimation1 () { + // add the "real" piece to the destination square + $destSquare.append(buildPieceHTML(piece)) - // run complete function - if (isFunction(completeFn)) { + // remove the animated piece + $animatedPiece.remove() + + // run complete function + if (isFunction(completeFn)) { completeFn() } } - // animate the piece to the destination square + // animate the piece to the destination square var opts = { - duration: config.moveSpeed, - complete: onFinishAnimation1 + duration: config.moveSpeed, + complete: onFinishAnimation1 } - $animatedPiece.animate(destSquarePosition, opts) + $animatedPiece.animate(destSquarePosition, opts) } function animateSparePieceToSquare (piece, dest, completeFn) { - var srcOffset = $('#' + sparePiecesElsIds[piece]).offset() - var $destSquare = $('#' + squareElsIds[dest]) - var destOffset = $destSquare.offset() + var srcOffset = $('#' + sparePiecesElsIds[piece]).offset() + var $destSquare = $('#' + squareElsIds[dest]) + var destOffset = $destSquare.offset() - // create the animate piece + // create the animate piece var pieceId = uuid() - $('body').append(buildPieceHTML(piece, true, pieceId)) - var $animatedPiece = $('#' + pieceId) - $animatedPiece.css({ + $('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)) + // 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() + // remove the animated piece + $animatedPiece.remove() - // run complete function - if (isFunction(completeFn)) { + // run complete function + if (isFunction(completeFn)) { completeFn() } } - // animate the piece to the destination square + // animate the piece to the destination square var opts = { - duration: config.moveSpeed, - complete: onFinishAnimation2 + duration: config.moveSpeed, + complete: onFinishAnimation2 } - $animatedPiece.animate(destOffset, opts) - } - - // execute an array of animations - function doAnimations (animations, oldPos, newPos) { - if (animations.length === 0) return + $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)) + 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) + 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 + } + + // 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 + // make copies of both pos1 = deepCopy(pos1) pos2 = deepCopy(pos2) var animations = [] var squaresMovedTo = {} - // remove pieces that are the same in both positions + // remove pieces that are the same in both positions for (var i in pos2) { if (!pos2.hasOwnProperty(i)) continue @@ -1051,12 +1051,12 @@ } } - // find all the "move" animations - for (i in pos2) { + // find all the "move" animations + for (i in pos2) { if (!pos2.hasOwnProperty(i)) continue var closestPiece = findClosestPiece(pos1, pos2[i], i) - if (closestPiece) { + if (closestPiece) { animations.push({ type: 'move', source: closestPiece, @@ -1068,11 +1068,11 @@ delete pos2[i] squaresMovedTo[i] = true } - } - - // "add" animations - for (i in pos2) { - if (!pos2.hasOwnProperty(i)) continue + } + + // "add" animations + for (i in pos2) { + if (!pos2.hasOwnProperty(i)) continue animations.push({ type: 'add', @@ -1082,14 +1082,14 @@ 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 + // "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', @@ -1103,64 +1103,64 @@ return animations } - // ------------------------------------------------------------------------- - // Control Flow - // ------------------------------------------------------------------------- - + // ------------------------------------------------------------------------- + // Control Flow + // ------------------------------------------------------------------------- + function drawPositionInstant () { - // clear the board - $board.find('.' + CSS.piece).remove() + // clear the board + $board.find('.' + CSS.piece).remove() - // add the pieces - for (var i in currentPosition) { - if (!currentPosition.hasOwnProperty(i)) continue + // add the pieces + for (var i in currentPosition) { + if (!currentPosition.hasOwnProperty(i)) continue - $('#' + squareElsIds[i]).append(buildPieceHTML(currentPosition[i])) + $('#' + squareElsIds[i]).append(buildPieceHTML(currentPosition[i])) } } function drawBoard () { - $board.html(buildBoardHTML(currentOrientation, squareSize, config.showNotation)) + $board.html(buildBoardHTML(currentOrientation, squareSize, config.showNotation)) drawPositionInstant() - if (config.sparePieces) { - if (currentOrientation === 'white') { - $sparePiecesTop.html(buildSparePiecesHTML('black')) - $sparePiecesBottom.html(buildSparePiecesHTML('white')) + if (config.sparePieces) { + if (currentOrientation === 'white') { + $sparePiecesTop.html(buildSparePiecesHTML('black')) + $sparePiecesBottom.html(buildSparePiecesHTML('white')) } else { - $sparePiecesTop.html(buildSparePiecesHTML('white')) - $sparePiecesBottom.html(buildSparePiecesHTML('black')) + $sparePiecesTop.html(buildSparePiecesHTML('white')) + $sparePiecesBottom.html(buildSparePiecesHTML('black')) } } } - + function setCurrentPosition (position) { - var oldPos = deepCopy(currentPosition) + var oldPos = deepCopy(currentPosition) var newPos = deepCopy(position) var oldFen = objToFen(oldPos) var newFen = objToFen(newPos) - // do nothing if no change in position + // do nothing if no change in position if (oldFen === newFen) return - // run their onChange function - if (isFunction(config.onChange)) { - config.onChange(oldPos, newPos) + // run their onChange function + if (isFunction(config.onChange)) { + config.onChange(oldPos, newPos) } - // update state - currentPosition = position + // update state + currentPosition = position } function isXYOnSquare (x, y) { - for (var i in squareElsOffsets) { - if (!squareElsOffsets.hasOwnProperty(i)) continue + for (var i in squareElsOffsets) { + if (!squareElsOffsets.hasOwnProperty(i)) continue - var s = squareElsOffsets[i] - if (x >= s.left && - x < s.left + squareSize && + var s = squareElsOffsets[i] + if (x >= s.left && + x < s.left + squareSize && y >= s.top && - y < s.top + squareSize) { + y < s.top + squareSize) { return i } } @@ -1168,246 +1168,246 @@ return 'offboard' } - // records the XY coords of every square into memory + // records the XY coords of every square into memory function captureSquareOffsets () { - squareElsOffsets = {} + squareElsOffsets = {} - for (var i in squareElsIds) { - if (!squareElsIds.hasOwnProperty(i)) continue + for (var i in squareElsIds) { + if (!squareElsIds.hasOwnProperty(i)) continue - squareElsOffsets[i] = $('#' + squareElsIds[i]).offset() + squareElsOffsets[i] = $('#' + squareElsIds[i]).offset() } } function removeSquareHighlights () { - $board - .find('.' + CSS.square) - .removeClass(CSS.highlight1 + ' ' + CSS.highlight2) + $board + .find('.' + CSS.square) + .removeClass(CSS.highlight1 + ' ' + CSS.highlight2) } function snapbackDraggedPiece () { - // there is no "snapback" for spare pieces - if (draggedPieceSource === 'spare') { + // there is no "snapback" for spare pieces + if (draggedPieceSource === 'spare') { trashDraggedPiece() return } removeSquareHighlights() - // animation complete + // animation complete function complete () { drawPositionInstant() - $draggedPiece.css('display', 'none') - - // run their onSnapbackEnd function - if (isFunction(config.onSnapbackEnd)) { - config.onSnapbackEnd( - draggedPiece, - draggedPieceSource, - deepCopy(currentPosition), - currentOrientation + $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() + // get source square position + var sourceSquarePosition = $('#' + squareElsIds[draggedPieceSource]).offset() - // animate the piece to the target square + // animate the piece to the target square var opts = { - duration: config.snapbackSpeed, + duration: config.snapbackSpeed, complete: complete } - $draggedPiece.animate(sourceSquarePosition, opts) + $draggedPiece.animate(sourceSquarePosition, opts) - // set state - isDragging = false + // set state + isDragging = false } function trashDraggedPiece () { removeSquareHighlights() - // remove the source piece - var newPosition = deepCopy(currentPosition) - delete newPosition[draggedPieceSource] + // remove the source piece + var newPosition = deepCopy(currentPosition) + delete newPosition[draggedPieceSource] setCurrentPosition(newPosition) - // redraw the position + // redraw the position drawPositionInstant() - // hide the dragged piece - $draggedPiece.fadeOut(config.trashSpeed) + // hide the dragged piece + $draggedPiece.fadeOut(config.trashSpeed) - // set state - isDragging = false + // set state + isDragging = false } function dropDraggedPieceOnSquare (square) { removeSquareHighlights() - // update position - var newPosition = deepCopy(currentPosition) - delete newPosition[draggedPieceSource] - newPosition[square] = draggedPiece + // update position + var newPosition = deepCopy(currentPosition) + delete newPosition[draggedPieceSource] + newPosition[square] = draggedPiece setCurrentPosition(newPosition) - // get target square information - var targetSquarePosition = $('#' + squareElsIds[square]).offset() + // get target square information + var targetSquarePosition = $('#' + squareElsIds[square]).offset() - // animation complete - function onAnimationComplete () { + // animation complete + function onAnimationComplete () { drawPositionInstant() - $draggedPiece.css('display', 'none') + $draggedPiece.css('display', 'none') - // execute their onSnapEnd function - if (isFunction(config.onSnapEnd)) { - config.onSnapEnd(draggedPieceSource, square, draggedPiece) + // execute their onSnapEnd function + if (isFunction(config.onSnapEnd)) { + config.onSnapEnd(draggedPieceSource, square, draggedPiece) } } - // snap the piece to the target square + // snap the piece to the target square var opts = { - duration: config.snapSpeed, - complete: onAnimationComplete + duration: config.snapSpeed, + complete: onAnimationComplete } - $draggedPiece.animate(targetSquarePosition, opts) + $draggedPiece.animate(targetSquarePosition, opts) - // set state - isDragging = false + // 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) { + // 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 + // set state + isDragging = true + draggedPiece = piece + draggedPieceSource = source - // if the piece came from spare pieces, location is offboard + // if the piece came from spare pieces, location is offboard if (source === 'spare') { - draggedPieceLocation = 'offboard' + draggedPieceLocation = 'offboard' } else { - draggedPieceLocation = source + draggedPieceLocation = source } - // capture the x, y coords of all squares in memory + // capture the x, y coords of all squares in memory captureSquareOffsets() - // create the dragged piece - $draggedPiece.attr('src', buildPieceImgSrc(piece)).css({ + // create the dragged piece + $draggedPiece.attr('src', buildPieceImgSrc(piece)).css({ display: '', position: 'absolute', - left: x - squareSize / 2, - top: y - squareSize / 2 + 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') + // 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 + // put the dragged piece over the mouse cursor + $draggedPiece.css({ + left: x - squareSize / 2, + top: y - squareSize / 2 }) - // get location + // get location var location = isXYOnSquare(x, y) - // do nothing if the location has not changed - if (location === draggedPieceLocation) return + // 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) + // 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) + // add highlight to new square + if (validSquare(location)) { + $('#' + squareElsIds[location]).addClass(CSS.highlight2) } - // run onDragMove - if (isFunction(config.onDragMove)) { - config.onDragMove( + // run onDragMove + if (isFunction(config.onDragMove)) { + config.onDragMove( location, - draggedPieceLocation, - draggedPieceSource, - draggedPiece, - deepCopy(currentPosition), - currentOrientation + draggedPieceLocation, + draggedPieceSource, + draggedPiece, + deepCopy(currentPosition), + currentOrientation ) } - // update state - draggedPieceLocation = location + // update state + draggedPieceLocation = location } function stopDraggedPiece (location) { - // determine what the action should be + // determine what the action should be var action = 'drop' - if (location === 'offboard' && config.dropOffBoard === 'snapback') { + if (location === 'offboard' && config.dropOffBoard === 'snapback') { action = 'snapback' } - if (location === 'offboard' && config.dropOffBoard === 'trash') { + 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) + // 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 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 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 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 + // 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 oldPosition = deepCopy(currentPosition) - var result = config.onDrop( - draggedPieceSource, + var result = config.onDrop( + draggedPieceSource, location, - draggedPiece, + draggedPiece, newPosition, oldPosition, - currentOrientation + currentOrientation ) if (result === 'snapback' || result === 'trash') { action = result } } - // do it! + // do it! if (action === 'snapback') { snapbackDraggedPiece() } else if (action === 'trash') { @@ -1417,55 +1417,55 @@ } } - // ------------------------------------------------------------------------- - // Public Methods - // ------------------------------------------------------------------------- + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- - // clear the board - widget.clear = function (useAnimation) { + // 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 the widget from the page + widget.destroy = function () { + // remove markup + $container.html('') + $draggedPiece.remove() - // remove event handlers - $container.unbind() - } + // remove event handlers + $container.unbind() + } - // shorthand method to get the current FEN + // shorthand method to get the current FEN widget.fen = function () { return widget.position('fen') } - // flip orientation - widget.flip = function () { + // 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 + } + + // 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 + // 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 + // collect the moves into an object var moves = {} for (var i = 0; i < arguments.length; i++) { - // any "false" to this function means no animations + // any "false" to this function means no animations if (arguments[i] === false) { useAnimation = false continue } - // skip invalid arguments - if (!validMove(arguments[i])) { + // skip invalid arguments + if (!validMove(arguments[i])) { error(2826, 'Invalid move passed to the move method.', arguments[i]) continue } @@ -1474,162 +1474,162 @@ moves[tmp[0]] = tmp[1] } - // calculate position from moves - var newPos = calculatePositionFromMoves(currentPosition, moves) + // calculate position from moves + var newPos = calculatePositionFromMoves(currentPosition, moves) - // update the board + // update the board widget.position(newPos, useAnimation) - // return the new position object + // return the new position object return newPos } widget.orientation = function (arg) { - // no arguments, return the current orientation + // no arguments, return the current orientation if (arguments.length === 0) { - return currentOrientation + return currentOrientation } - // set to white or black + // set to white or black if (arg === 'white' || arg === 'black') { - currentOrientation = arg + currentOrientation = arg drawBoard() - return currentOrientation + return currentOrientation } - // flip orientation + // flip orientation if (arg === 'flip') { - currentOrientation = currentOrientation === 'white' ? 'black' : 'white' + currentOrientation = currentOrientation === 'white' ? 'black' : 'white' drawBoard() - return currentOrientation + return currentOrientation } error(5482, 'Invalid value passed to the orientation method.', arg) - } - - widget.position = function (position, useAnimation) { - // no arguments, return the current position + } + + widget.position = function (position, useAnimation) { + // no arguments, return the current position if (arguments.length === 0) { - return deepCopy(currentPosition) + return deepCopy(currentPosition) } - // get position as FEN - if (isString(position) && position.toLowerCase() === 'fen') { - return objToFen(currentPosition) - } - - // start position - if (isString(position) && position.toLowerCase() === 'start') { + // 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)) { + // convert FEN to position object + if (validFen(position)) { position = fenToObj(position) } - // validate position object - if (!validPositionObject(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 + // 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 + // instant update setCurrentPosition(position) drawPositionInstant() } - } - - widget.resize = function () { - // calulate the new square size - squareSize = calculateSquareSize() + } + + widget.resize = function () { + // calulate the new square size + squareSize = calculateSquareSize() - // set board width - $board.css('width', squareSize * 8 + 'px') + // set board width + $board.css('width', squareSize * 8 + 'px') - // set drag piece size - $draggedPiece.css({ - height: squareSize, - width: squareSize + // set drag piece size + $draggedPiece.css({ + height: squareSize, + width: squareSize }) - // spare pieces - if (config.sparePieces) { - $container - .find('.' + CSS.sparePieces) - .css('paddingLeft', squareSize + boardBorderSize + 'px') + // spare pieces + if (config.sparePieces) { + $container + .find('.' + CSS.sparePieces) + .css('paddingLeft', squareSize + boardBorderSize + 'px') } - // redraw the board + // redraw the board drawBoard() - } - - // set the starting position - widget.start = function (useAnimation) { + } + + // 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) - } - + } + + // ------------------------------------------------------------------------- + // 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 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 + // 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], + currentPosition[square], e.changedTouches[0].pageX, e.changedTouches[0].pageY ) } - function mousedownSparePiece (evt) { - // do nothing if sparePieces is not enabled - if (!config.sparePieces) return + 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) + beginDraggingPiece('spare', piece, evt.pageX, evt.pageY) } function touchstartSparePiece (e) { - // do nothing if sparePieces is not enabled - if (!config.sparePieces) return + // do nothing if sparePieces is not enabled + if (!config.sparePieces) return var piece = $(this).attr('data-piece') @@ -1642,176 +1642,177 @@ ) } - 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 + function mousemoveWindow (evt) { + if (isDragging) { + updateDraggedPiece(evt.pageX, evt.pageY) + } + } + + var throttledMousemoveWindow = throttle(mousemoveWindow, config.dragThrottleRate) - // prevent screen from scrolling - evt.preventDefault() + function touchmoveWindow (evt) { + // do nothing if we are not dragging a piece + if (!isDragging) return - updateDraggedPiece(evt.originalEvent.changedTouches[0].pageX, - evt.originalEvent.changedTouches[0].pageY) + // 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) + 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 + 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) + // 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 + 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') + // get the square + var square = $(evt.currentTarget).attr('data-square') - // NOTE: this should never happen; defensive - if (!validSquare(square)) return + // NOTE: this should never happen; defensive + if (!validSquare(square)) return - // get the piece on this square + // get the piece on this square var piece = false - if (currentPosition.hasOwnProperty(square)) { - piece = currentPosition[square] + if (currentPosition.hasOwnProperty(square)) { + piece = currentPosition[square] } - // execute their function - config.onMouseoverSquare(square, piece, deepCopy(currentPosition), currentOrientation) + // 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 + 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 - // get the square - var square = $(evt.currentTarget).attr('data-square') + // exit if they did not provide an onMouseoutSquare function + if (!isFunction(config.onMouseoutSquare)) return - // NOTE: this should never happen; defensive - if (!validSquare(square)) return + // get the square + var square = $(evt.currentTarget).attr('data-square') - // get the piece on this 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] + if (currentPosition.hasOwnProperty(square)) { + piece = currentPosition[square] } - // execute their function - config.onMouseoutSquare(square, piece, deepCopy(currentPosition), currentOrientation) + // execute their function + config.onMouseoutSquare(square, piece, deepCopy(currentPosition), currentOrientation) } - // ------------------------------------------------------------------------- - // Initialization - // ------------------------------------------------------------------------- - + // ------------------------------------------------------------------------- + // 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 + // 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) + // 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) + if (config.sparePieces) { + $sparePiecesTop = $container.find('.' + CSS.sparePiecesTop) + $sparePiecesBottom = $container.find('.' + CSS.sparePiecesBottom) } - // create the drag piece + // 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 + $('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'] - + + // ------------------------------------------------------------------------- + // 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 + window['Chessboard']['fenToObj'] = fenToObj + window['Chessboard']['objToFen'] = objToFen })() // end anonymous wrapper + From 58d5120a60e6141a55f962e7d1a390c97982646d Mon Sep 17 00:00:00 2001 From: wukunyu264 Date: Wed, 13 Aug 2025 17:06:03 +0800 Subject: [PATCH 2/3] Create package.json --- js-tests/package.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 js-tests/package.json diff --git a/js-tests/package.json b/js-tests/package.json new file mode 100644 index 00000000000..1f957e426c7 --- /dev/null +++ b/js-tests/package.json @@ -0,0 +1,12 @@ +{ + "name": "whispercpp-js-tests", + "private": true, + "type": "commonjs", + "scripts": { + "test": "mocha \"*.spec.js\"" + }, + "devDependencies": { + "mocha": "^10.6.0", + "chai": "^4.4.1" + } +} From 4c885f33f363dc62e193c9d8f5a2200134ab6b31 Mon Sep 17 00:00:00 2001 From: wukunyu264 Date: Wed, 13 Aug 2025 17:07:05 +0800 Subject: [PATCH 3/3] Add files via upload --- js-tests/fen-redos.spec.js | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 js-tests/fen-redos.spec.js diff --git a/js-tests/fen-redos.spec.js b/js-tests/fen-redos.spec.js new file mode 100644 index 00000000000..a00fd46089e --- /dev/null +++ b/js-tests/fen-redos.spec.js @@ -0,0 +1,41 @@ +/* eslint-env mocha */ +const { expect } = require('chai'); +const { performance } = require('perf_hooks'); +const path = require('path'); + +// —— 最小浏览器 & jQuery stub,避免 DOM 依赖 —— +global.window = global; +global.document = {}; +global.jQuery = function(){}; +global.$ = global.jQuery; + + +const target = path.join( + __dirname, + '..', + 'examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.js' +); + +// 载入真实实现(会把 Chessboard 挂到全局) +require(target); +const Chessboard = global.Chessboard; + +describe('FEN sanitize ReDoS in whisper.cpp (fen = fen.replace(/ .+$/, \'\'))', function () { + this.timeout(60_000); + + it('should complete within 2 seconds', function () { + const N = 100000; + const attack = ' '.repeat(N) + '\n@'; + + const t0 = performance.now(); + try { Chessboard.fenToObj(attack); } catch (_) {} + const ms = performance.now() - t0; + + + expect(ms).to.be.lessThan(2_000); + }); +}); + + + +