diff --git a/examples/chess.wasm/CMakeLists.txt b/examples/chess.wasm/CMakeLists.txt index 06a625ec..4488134d 100644 --- a/examples/chess.wasm/CMakeLists.txt +++ b/examples/chess.wasm/CMakeLists.txt @@ -25,7 +25,7 @@ if (WHISPER_WASM_SINGLE_FILE) TARGET ${TARGET} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/bin/libchess.js - ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/chess.wasm/chess.js + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/chess.wasm/js/chess.js ) endif() @@ -40,6 +40,16 @@ set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \ ${EXTRA_FLAGS} \ ") + +add_custom_command( + TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/chessboardjs-1.0.0 + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/chess.wasm/ + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/jquery-3.7.1.min.js + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/chess.wasm/js/ + ) # # chess.wasm # @@ -47,4 +57,4 @@ set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \ set(TARGET chess.wasm) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../helpers.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/helpers.js @ONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../helpers.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/js/helpers.js @ONLY) diff --git a/examples/chess.wasm/chessboardjs-1.0.0/CHANGELOG.md b/examples/chess.wasm/chessboardjs-1.0.0/CHANGELOG.md new file mode 100644 index 00000000..c0c6b2fe --- /dev/null +++ b/examples/chess.wasm/chessboardjs-1.0.0/CHANGELOG.md @@ -0,0 +1,32 @@ +# chessboard.js Change Log + +All notable changes to this project will be documented in this file. + +## [1.0.0] - 2019-06-11 +- Orientation methods now return current orientation. [Issue #64] +- Drop support for IE8 +- Do not check for `window.JSON` (Error #1004) +- Rename `ChessBoard` to `Chessboard` (`ChessBoard` is still supported, however) +- id query selectors are now supported as the first argument to `Chessboard()` +- Remove Error #1002 +- Format code according to [StandardJS] +- Bump minimum jQuery version to 1.8.3 +- Throttle piece drag functions + +## [0.3.0] - 2013-08-10 +- Added `appearSpeed` animation config property +- Added `onSnapbackEnd` event +- Added `onMoveEnd` event + +## [0.2.0] - 2013-08-05 +- Added `onMouseoverSquare` and `onMouseoutSquare` events +- Added `onSnapEnd` event +- Added square code as CSS class on the squares +- Added [chess.js] integration examples + +## [0.1.0] - 2013-05-21 +- Initial release + +[chess.js]:https://github.com/jhlywa/chess.js +[Issue #64]:https://github.com/oakmac/chessboardjs/issues/64 +[StandardJS]:https://standardjs.com/ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/LICENSE.md b/examples/chess.wasm/chessboardjs-1.0.0/LICENSE.md new file mode 100644 index 00000000..20b7d615 --- /dev/null +++ b/examples/chess.wasm/chessboardjs-1.0.0/LICENSE.md @@ -0,0 +1,20 @@ +Copyright 2019 Chris Oakman + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/chess.wasm/chessboardjs-1.0.0/README.md b/examples/chess.wasm/chessboardjs-1.0.0/README.md new file mode 100644 index 00000000..60c8997e --- /dev/null +++ b/examples/chess.wasm/chessboardjs-1.0.0/README.md @@ -0,0 +1,82 @@ +# chessboard.js + +chessboard.js is a JavaScript chessboard component. It depends on [jQuery]. + +Please see [chessboardjs.com] for documentation and examples. + +## What is chessboard.js? + +chessboard.js is a JavaScript chessboard component with a flexible "just a +board" API that + +chessboard.js is a standalone JavaScript Chess Board. It is designed to be "just +a board" and expose a powerful API so that it can be used in different ways. +Here's a non-exhaustive list of things you can do with chessboard.js: + +- Use chessboard.js to show game positions alongside your expert commentary. +- Use chessboard.js to have a tactics website where users have to guess the best + move. +- Integrate chessboard.js and [chess.js] with a PGN database and allow people to + search and playback games (see [Example 5000]) +- Build a chess server and have users play their games out using the + chessboard.js board. + +chessboard.js is flexible enough to handle any of these situations with relative +ease. + +## What can chessboard.js **not** do? + +The scope of chessboard.js is limited to "just a board." This is intentional and +makes chessboard.js flexible for handling a multitude of chess-related problems. + +This is a common source of confusion for new users. [remove?] + +Specifically, chessboard.js does not understand anything about how the game of +chess is played: how a knight moves, who's turn is it, is White in check?, etc. + +Fortunately, the powerful [chess.js] library deals with exactly this sort of +problem domain and plays nicely with chessboard.js's flexible API. Some examples +of chessboard.js combined with chess.js: 5000, 5001, 5002 + +Please see the powerful [chess.js] library for an API to deal with these sorts +of questions. + + +This logic is distinct from the logic of the board. Please see the powerful +[chess.js] library for this aspect of your application. + + + +Here is a list of things that chessboard.js is **not**: + +- A chess engine +- A legal move validator +- A PGN parser + +chessboard.js is designed to work well with any of those things, but the idea +behind chessboard.js is that the logic that controls the board should be +independent of those other problems. + +## Docs and Examples + +- Docs - +- Examples - + +## Developer Tools + +```sh +# create a build in the build/ directory +npm run build + +# re-build the website +npm run website +``` + +## License + +[MIT License](LICENSE.md) + +[jQuery]:https://jquery.com/ +[chessboardjs.com]:http://chessboardjs.com +[chess.js]:https://github.com/jhlywa/chess.js +[Example 5000]:http://chessboardjs.com/examples#5000 diff --git a/examples/chess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.css b/examples/chess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.css new file mode 100644 index 00000000..8de95f47 --- /dev/null +++ b/examples/chess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.css @@ -0,0 +1,54 @@ +/*! chessboard.js v1.0.0 | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */ + +.clearfix-7da63 { + clear: both; +} + +.board-b72b1 { + border: 2px solid #404040; + box-sizing: content-box; +} + +.square-55d63 { + float: left; + position: relative; + + /* disable any native browser highlighting */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.white-1e1d7 { + background-color: #f0d9b5; + color: #b58863; +} + +.black-3c85d { + background-color: #b58863; + color: #f0d9b5; +} + +.highlight1-32417, .highlight2-9c5d2 { + box-shadow: inset 0 0 3px 3px yellow; +} + +.notation-322f9 { + cursor: default; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + position: absolute; +} + +.alpha-d2270 { + bottom: 1px; + right: 3px; +} + +.numeric-fc462 { + top: 2px; + left: 2px; +} diff --git a/examples/chess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.min.css b/examples/chess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.min.css new file mode 100644 index 00000000..73f844a8 --- /dev/null +++ b/examples/chess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.min.css @@ -0,0 +1,2 @@ +/*! chessboard.js v1.0.0 | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */ +.clearfix-7da63{clear:both}.board-b72b1{border:2px solid #404040;box-sizing:content-box}.square-55d63{float:left;position:relative;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.white-1e1d7{background-color:#f0d9b5;color:#b58863}.black-3c85d{background-color:#b58863;color:#f0d9b5}.highlight1-32417,.highlight2-9c5d2{box-shadow:inset 0 0 3px 3px #ff0}.notation-322f9{cursor:default;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;position:absolute}.alpha-d2270{bottom:1px;right:3px}.numeric-fc462{top:2px;left:2px} \ No newline at end of file diff --git a/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bB.png b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bB.png new file mode 100644 index 00000000..be3007dd Binary files /dev/null and b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bB.png differ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bK.png b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bK.png new file mode 100644 index 00000000..de9880ce Binary files /dev/null and b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bK.png differ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bN.png b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bN.png new file mode 100644 index 00000000..e31a6d02 Binary files /dev/null and b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bN.png differ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bP.png b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bP.png new file mode 100644 index 00000000..afa0c9d4 Binary files /dev/null and b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bP.png differ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bQ.png b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bQ.png new file mode 100644 index 00000000..4649bb8b Binary files /dev/null and b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bQ.png differ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bR.png b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bR.png new file mode 100644 index 00000000..c7eb127a Binary files /dev/null and b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bR.png differ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wB.png b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wB.png new file mode 100644 index 00000000..70e0e140 Binary files /dev/null and b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wB.png differ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wK.png b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wK.png new file mode 100644 index 00000000..bbf56649 Binary files /dev/null and b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wK.png differ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wN.png b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wN.png new file mode 100644 index 00000000..237250c1 Binary files /dev/null and b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wN.png differ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wP.png b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wP.png new file mode 100644 index 00000000..5f9315c7 Binary files /dev/null and b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wP.png differ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wQ.png b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wQ.png new file mode 100644 index 00000000..c3dfc15e Binary files /dev/null and b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wQ.png differ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wR.png b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wR.png new file mode 100644 index 00000000..cc697603 Binary files /dev/null and b/examples/chess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wR.png differ diff --git a/examples/chess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.js b/examples/chess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.js new file mode 100644 index 00000000..0939efce --- /dev/null +++ b/examples/chess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.js @@ -0,0 +1,1817 @@ +// 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 = '
' + + 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 + // ------------------------------------------------------------------------- + + 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 += '
' + 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 (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 = '' + + 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 diff --git a/examples/chess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.min.js b/examples/chess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.min.js new file mode 100644 index 00000000..73ea2870 --- /dev/null +++ b/examples/chess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.min.js @@ -0,0 +1,2 @@ +/*! chessboard.js v1.0.0 | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */ +!function(){"use strict";var z=window.jQuery,F="abcdefgh".split(""),r=20,A="…",W="1.8.3",e="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR",G=pe(e),n=200,t=200,o=60,a=30,i=100,H={};function V(e,r,n){function t(){o=0,a&&(a=!1,s())}var o=0,a=!1,i=[],s=function(){o=window.setTimeout(t,r),e.apply(n,i)};return function(e){i=arguments,o?a=!0:s()}}function Z(){return"xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx".replace(/x/g,function(e){return(16*Math.random()|0).toString(16)})}function _(e){return JSON.parse(JSON.stringify(e))}function s(e){var r=e.split(".");return{major:parseInt(r[0],10),minor:parseInt(r[1],10),patch:parseInt(r[2],10)}}function ee(e,r){for(var n in r)if(r.hasOwnProperty(n))for(var t="{"+n+"}",o=r[n];-1!==e.indexOf(t);)e=e.replace(t,o);return e}function re(e){return"string"==typeof e}function ne(e){return"function"==typeof e}function p(e){return"number"==typeof e&&isFinite(e)&&Math.floor(e)===e}function c(e){return"fast"===e||"slow"===e||!!p(e)&&0<=e}function te(e){if(!re(e))return!1;var r=e.split("-");return 2===r.length&&(oe(r[0])&&oe(r[1]))}function oe(e){return re(e)&&-1!==e.search(/^[a-h][1-8]$/)}function u(e){return re(e)&&-1!==e.search(/^[bw][KQRNBP]$/)}function ae(e){if(!re(e))return!1;var r=(e=function(e){return e.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")}(e=e.replace(/ .+$/,""))).split("/");if(8!==r.length)return!1;for(var n=0;n<8;n++)if(8!==r[n].length||-1!==r[n].search(/[^kqrnbpKQRNBP1]/))return!1;return!0}function ie(e){if(!z.isPlainObject(e))return!1;for(var r in e)if(e.hasOwnProperty(r)&&(!oe(r)||!u(e[r])))return!1;return!0}function se(){return typeof window.$&&z.fn&&z.fn.jquery&&function(e,r){e=s(e),r=s(r);var n=1e5*e.major*1e5+1e5*e.minor+e.patch;return 1e5*r.major*1e5+1e5*r.minor+r.patch<=n}(z.fn.jquery,W)}function pe(e){if(!ae(e))return!1;for(var r,n=(e=e.replace(/ .+$/,"")).split("/"),t={},o=8,a=0;a<8;a++){for(var i=n[a].split(""),s=0,p=0;p';for(var i=0;i<8;i++){var s=n[i]+t;r+='
',f.showNotation&&(("white"===e&&1===t||"black"===e&&8===t)&&(r+='
'+n[i]+"
"),0===i&&(r+='
'+t+"
")),r+="
",o="white"===o?"black":"white"}r+='
',o="white"===o?"black":"white","white"===e?t-=1:t+=1}return ee(r,H)}(p,f.showNotation)),T(),f.sparePieces&&("white"===p?(t.html(x("black")),o.html(x("white"))):(t.html(x("white")),o.html(x("black"))))}function k(e){var r=_(c),n=_(e);ce(r)!==ce(n)&&(ne(f.onChange)&&f.onChange(r,n),c=e)}function E(e,r){for(var n in w)if(w.hasOwnProperty(n)){var t=w[n];if(e>=t.left&&e=t.top&&r (http://chrisoakman.com/)", + "name": "@chrisoakman/chessboardjs", + "description": "JavaScript chessboard widget", + "homepage": "https://chessboardjs.com", + "license": "MIT", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "git://github.com/oakmac/chessboardjs.git" + }, + "files": ["dist/"], + "dependencies": { + "jquery": ">=3.4.1" + }, + "devDependencies": { + "csso": "3.5.1", + "fs-plus": "3.1.1", + "kidif": "1.1.0", + "mustache": "2.3.0", + "standard": "10.0.2", + "uglify-js": "3.6.0" + }, + "scripts": { + "build": "standard lib/chessboard.js && node scripts/build.js", + "standard": "standard --fix lib/*.js website/js/*.js", + "website": "node scripts/website.js" + } +} diff --git a/examples/chess.wasm/index-tmpl.html b/examples/chess.wasm/index-tmpl.html index e16d7908..6cb16fd0 100644 --- a/examples/chess.wasm/index-tmpl.html +++ b/examples/chess.wasm/index-tmpl.html @@ -24,6 +24,7 @@ overflow-x: scroll; } +
@@ -71,6 +72,14 @@
[The recognized voice commands will be displayed here]
+

+
+ + + +
Debug output: @@ -101,7 +110,7 @@ - + - + diff --git a/examples/chess.wasm/jquery-3.7.1.min.js b/examples/chess.wasm/jquery-3.7.1.min.js new file mode 100644 index 00000000..7f37b5d9 --- /dev/null +++ b/examples/chess.wasm/jquery-3.7.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0