├── .dockerignore ├── .gitattributes ├── .gitignore ├── Dockerfile ├── README.md ├── VARS.js ├── package-lock.json ├── package.json ├── server.js ├── tampermonkey script ├── content │ ├── UserGui.js │ ├── chessboard.css │ ├── chessboard.js │ ├── chesspieces │ │ ├── bB.svg │ │ ├── bK.svg │ │ ├── bN.svg │ │ ├── bP.svg │ │ ├── bQ.svg │ │ ├── bR.svg │ │ ├── wB.svg │ │ ├── wK.svg │ │ ├── wN.svg │ │ ├── wP.svg │ │ ├── wQ.svg │ │ └── wR.svg │ ├── jquery.js │ ├── lozza.js │ ├── stockfish-2018.js │ ├── stockfish-5.js │ └── stockfish-multi-thread │ │ ├── stockfish.js │ │ ├── stockfish.wasm │ │ └── stockfish.worker.js └── smart-chess.js └── utils ├── engine.js ├── engine └── engine_path └── process.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tampermonkey script 3 | utils/engine/* 4 | */stockfish* -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.exe 3 | *.bin -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | WORKDIR /cas 3 | COPY package*.json ./ 4 | RUN npm install 5 | COPY . . 6 | EXPOSE 5000 7 | CMD [ "npm start" ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smart Chess Bot: The Ultimate Chess Analysis System 2 | 3 | ## Overview 4 | 5 | Smart Chess Bot is an advanced chess analysis system designed to elevate your gameplay to new heights. Leveraging state-of-the-art algorithms and cutting-edge technology, our system provides unparalleled insights into chess positions, empowering players to make informed and strategic moves. 6 | 7 | ## Features 8 | 9 | - **Intelligent Analysis**: Our system employs sophisticated algorithms to analyze chess positions and recommend optimal moves. 10 | - **Multiple Chess Engines**: Choose from a selection of JavaScript UCI chess engines, including Lozza Engine, Stockfish 5 Engine, and Stockfish 2018 Engine, each offering varying speeds and maximum Elo ratings. 11 | - **Browser Compatibility**: Compatible with popular browsers such as Chrome, Firefox, Edge, and Opera, ensuring accessibility for players across platforms. 12 | - **User-friendly Interface**: Enjoy a seamless and intuitive user experience, making it easy to integrate our system into your chess strategy. 13 | 14 | ## Getting Started 15 | 16 | To start using Smart Chess Bot, follow these simple steps: 17 | 18 | 1. **Installation**: Install the TamperMonkey extension for your browser from [here](https://www.tampermonkey.net/). 19 | 2. **TamperMonkey Script**: Add the TamperMonkey script available [here](https://greasyfork.org/en/scripts/460147-c-a-s-chess-assistance-system) to enable Smart Chess Bot functionality. 20 | 3. **Allow Pop-ups**: Ensure that pop-ups are allowed in your browser settings to enable the full functionality of the system. 21 | 4. **Optional: Node Server Setup**: To run the chess engine locally on your system, follow these steps: 22 | 23 | - **Install Node Modules**: Run `npm install` in your terminal to install the necessary dependencies. 24 | 25 | - **Download Stockfish Engine**: [Download](https://stockfishchess.org/download/) 26 | 27 | - **Stockfish Engine Path**: Place the Stockfish engine in the following path: `utils/engine`. 28 | 29 | - **TamperMonkey Script**: Set engine name in the script settings. 30 | 31 | - **Run Server Command**: npm start server.js 32 | 33 | 34 | ## Screenshots 35 | 36 | ![Screenshot 1](https://user-images.githubusercontent.com/80095684/219965567-57ced9ff-5dbe-4b4d-969c-e6c42d83c5b8.png) 37 | ![Screenshot 2](https://user-images.githubusercontent.com/80095684/219965566-224f8acb-38d1-4ff8-9b29-e223c1361273.png) 38 | ![Screenshot 3](https://user-images.githubusercontent.com/80095684/219969063-498cb06b-f97c-4e68-a1cf-e8de3f1a677f.png) 39 | -------------------------------------------------------------------------------- /VARS.js: -------------------------------------------------------------------------------- 1 | const VARS={ 2 | PORT : 5000, 3 | LICHESS_API : "https://lichess.org/api/cloud-eval", 4 | DEPTH_MODE:0, 5 | MOVETIME_MODE:1 6 | } 7 | 8 | 9 | module.exports={VARS} -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "server", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "cors": "^2.8.5", 13 | "express": "^4.17.3", 14 | "nodemon": "^3.1.9", 15 | "request": "^2.88.2" 16 | } 17 | }, 18 | "node_modules/accepts": { 19 | "version": "1.3.8", 20 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 21 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 22 | "dependencies": { 23 | "mime-types": "~2.1.34", 24 | "negotiator": "0.6.3" 25 | }, 26 | "engines": { 27 | "node": ">= 0.6" 28 | } 29 | }, 30 | "node_modules/ajv": { 31 | "version": "6.12.6", 32 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 33 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 34 | "dependencies": { 35 | "fast-deep-equal": "^3.1.1", 36 | "fast-json-stable-stringify": "^2.0.0", 37 | "json-schema-traverse": "^0.4.1", 38 | "uri-js": "^4.2.2" 39 | }, 40 | "funding": { 41 | "type": "github", 42 | "url": "https://github.com/sponsors/epoberezkin" 43 | } 44 | }, 45 | "node_modules/anymatch": { 46 | "version": "3.1.3", 47 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 48 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 49 | "license": "ISC", 50 | "dependencies": { 51 | "normalize-path": "^3.0.0", 52 | "picomatch": "^2.0.4" 53 | }, 54 | "engines": { 55 | "node": ">= 8" 56 | } 57 | }, 58 | "node_modules/array-flatten": { 59 | "version": "1.1.1", 60 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 61 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 62 | }, 63 | "node_modules/asn1": { 64 | "version": "0.2.6", 65 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", 66 | "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", 67 | "dependencies": { 68 | "safer-buffer": "~2.1.0" 69 | } 70 | }, 71 | "node_modules/assert-plus": { 72 | "version": "1.0.0", 73 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 74 | "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", 75 | "engines": { 76 | "node": ">=0.8" 77 | } 78 | }, 79 | "node_modules/asynckit": { 80 | "version": "0.4.0", 81 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 82 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 83 | }, 84 | "node_modules/aws-sign2": { 85 | "version": "0.7.0", 86 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 87 | "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", 88 | "engines": { 89 | "node": "*" 90 | } 91 | }, 92 | "node_modules/aws4": { 93 | "version": "1.12.0", 94 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", 95 | "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" 96 | }, 97 | "node_modules/balanced-match": { 98 | "version": "1.0.2", 99 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 100 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 101 | "license": "MIT" 102 | }, 103 | "node_modules/bcrypt-pbkdf": { 104 | "version": "1.0.2", 105 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 106 | "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", 107 | "dependencies": { 108 | "tweetnacl": "^0.14.3" 109 | } 110 | }, 111 | "node_modules/binary-extensions": { 112 | "version": "2.3.0", 113 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 114 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 115 | "license": "MIT", 116 | "engines": { 117 | "node": ">=8" 118 | }, 119 | "funding": { 120 | "url": "https://github.com/sponsors/sindresorhus" 121 | } 122 | }, 123 | "node_modules/body-parser": { 124 | "version": "1.20.1", 125 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", 126 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", 127 | "dependencies": { 128 | "bytes": "3.1.2", 129 | "content-type": "~1.0.4", 130 | "debug": "2.6.9", 131 | "depd": "2.0.0", 132 | "destroy": "1.2.0", 133 | "http-errors": "2.0.0", 134 | "iconv-lite": "0.4.24", 135 | "on-finished": "2.4.1", 136 | "qs": "6.11.0", 137 | "raw-body": "2.5.1", 138 | "type-is": "~1.6.18", 139 | "unpipe": "1.0.0" 140 | }, 141 | "engines": { 142 | "node": ">= 0.8", 143 | "npm": "1.2.8000 || >= 1.4.16" 144 | } 145 | }, 146 | "node_modules/brace-expansion": { 147 | "version": "1.1.11", 148 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 149 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 150 | "license": "MIT", 151 | "dependencies": { 152 | "balanced-match": "^1.0.0", 153 | "concat-map": "0.0.1" 154 | } 155 | }, 156 | "node_modules/braces": { 157 | "version": "3.0.3", 158 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 159 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 160 | "license": "MIT", 161 | "dependencies": { 162 | "fill-range": "^7.1.1" 163 | }, 164 | "engines": { 165 | "node": ">=8" 166 | } 167 | }, 168 | "node_modules/bytes": { 169 | "version": "3.1.2", 170 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 171 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 172 | "engines": { 173 | "node": ">= 0.8" 174 | } 175 | }, 176 | "node_modules/call-bind": { 177 | "version": "1.0.2", 178 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 179 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 180 | "dependencies": { 181 | "function-bind": "^1.1.1", 182 | "get-intrinsic": "^1.0.2" 183 | }, 184 | "funding": { 185 | "url": "https://github.com/sponsors/ljharb" 186 | } 187 | }, 188 | "node_modules/caseless": { 189 | "version": "0.12.0", 190 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 191 | "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" 192 | }, 193 | "node_modules/chokidar": { 194 | "version": "3.6.0", 195 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 196 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 197 | "license": "MIT", 198 | "dependencies": { 199 | "anymatch": "~3.1.2", 200 | "braces": "~3.0.2", 201 | "glob-parent": "~5.1.2", 202 | "is-binary-path": "~2.1.0", 203 | "is-glob": "~4.0.1", 204 | "normalize-path": "~3.0.0", 205 | "readdirp": "~3.6.0" 206 | }, 207 | "engines": { 208 | "node": ">= 8.10.0" 209 | }, 210 | "funding": { 211 | "url": "https://paulmillr.com/funding/" 212 | }, 213 | "optionalDependencies": { 214 | "fsevents": "~2.3.2" 215 | } 216 | }, 217 | "node_modules/combined-stream": { 218 | "version": "1.0.8", 219 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 220 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 221 | "dependencies": { 222 | "delayed-stream": "~1.0.0" 223 | }, 224 | "engines": { 225 | "node": ">= 0.8" 226 | } 227 | }, 228 | "node_modules/concat-map": { 229 | "version": "0.0.1", 230 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 231 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 232 | "license": "MIT" 233 | }, 234 | "node_modules/content-disposition": { 235 | "version": "0.5.4", 236 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 237 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 238 | "dependencies": { 239 | "safe-buffer": "5.2.1" 240 | }, 241 | "engines": { 242 | "node": ">= 0.6" 243 | } 244 | }, 245 | "node_modules/content-type": { 246 | "version": "1.0.5", 247 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 248 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 249 | "engines": { 250 | "node": ">= 0.6" 251 | } 252 | }, 253 | "node_modules/cookie-signature": { 254 | "version": "1.0.6", 255 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 256 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 257 | }, 258 | "node_modules/cors": { 259 | "version": "2.8.5", 260 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 261 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 262 | "license": "MIT", 263 | "dependencies": { 264 | "object-assign": "^4", 265 | "vary": "^1" 266 | }, 267 | "engines": { 268 | "node": ">= 0.10" 269 | } 270 | }, 271 | "node_modules/dashdash": { 272 | "version": "1.14.1", 273 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 274 | "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", 275 | "dependencies": { 276 | "assert-plus": "^1.0.0" 277 | }, 278 | "engines": { 279 | "node": ">=0.10" 280 | } 281 | }, 282 | "node_modules/debug": { 283 | "version": "2.6.9", 284 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 285 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 286 | "dependencies": { 287 | "ms": "2.0.0" 288 | } 289 | }, 290 | "node_modules/delayed-stream": { 291 | "version": "1.0.0", 292 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 293 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 294 | "engines": { 295 | "node": ">=0.4.0" 296 | } 297 | }, 298 | "node_modules/depd": { 299 | "version": "2.0.0", 300 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 301 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 302 | "engines": { 303 | "node": ">= 0.8" 304 | } 305 | }, 306 | "node_modules/destroy": { 307 | "version": "1.2.0", 308 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 309 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 310 | "engines": { 311 | "node": ">= 0.8", 312 | "npm": "1.2.8000 || >= 1.4.16" 313 | } 314 | }, 315 | "node_modules/ecc-jsbn": { 316 | "version": "0.1.2", 317 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 318 | "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", 319 | "dependencies": { 320 | "jsbn": "~0.1.0", 321 | "safer-buffer": "^2.1.0" 322 | } 323 | }, 324 | "node_modules/ee-first": { 325 | "version": "1.1.1", 326 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 327 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 328 | }, 329 | "node_modules/encodeurl": { 330 | "version": "1.0.2", 331 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 332 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 333 | "engines": { 334 | "node": ">= 0.8" 335 | } 336 | }, 337 | "node_modules/escape-html": { 338 | "version": "1.0.3", 339 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 340 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 341 | }, 342 | "node_modules/etag": { 343 | "version": "1.8.1", 344 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 345 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 346 | "engines": { 347 | "node": ">= 0.6" 348 | } 349 | }, 350 | "node_modules/express": { 351 | "version": "4.18.2", 352 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", 353 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", 354 | "dependencies": { 355 | "accepts": "~1.3.8", 356 | "array-flatten": "1.1.1", 357 | "body-parser": "1.20.1", 358 | "content-disposition": "0.5.4", 359 | "content-type": "~1.0.4", 360 | "cookie": "0.5.0", 361 | "cookie-signature": "1.0.6", 362 | "debug": "2.6.9", 363 | "depd": "2.0.0", 364 | "encodeurl": "~1.0.2", 365 | "escape-html": "~1.0.3", 366 | "etag": "~1.8.1", 367 | "finalhandler": "1.2.0", 368 | "fresh": "0.5.2", 369 | "http-errors": "2.0.0", 370 | "merge-descriptors": "1.0.1", 371 | "methods": "~1.1.2", 372 | "on-finished": "2.4.1", 373 | "parseurl": "~1.3.3", 374 | "path-to-regexp": "0.1.7", 375 | "proxy-addr": "~2.0.7", 376 | "qs": "6.11.0", 377 | "range-parser": "~1.2.1", 378 | "safe-buffer": "5.2.1", 379 | "send": "0.18.0", 380 | "serve-static": "1.15.0", 381 | "setprototypeof": "1.2.0", 382 | "statuses": "2.0.1", 383 | "type-is": "~1.6.18", 384 | "utils-merge": "1.0.1", 385 | "vary": "~1.1.2" 386 | }, 387 | "engines": { 388 | "node": ">= 0.10.0" 389 | } 390 | }, 391 | "node_modules/express/node_modules/cookie": { 392 | "version": "0.5.0", 393 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 394 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", 395 | "engines": { 396 | "node": ">= 0.6" 397 | } 398 | }, 399 | "node_modules/extend": { 400 | "version": "3.0.2", 401 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 402 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 403 | }, 404 | "node_modules/extsprintf": { 405 | "version": "1.3.0", 406 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 407 | "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", 408 | "engines": [ 409 | "node >=0.6.0" 410 | ] 411 | }, 412 | "node_modules/fast-deep-equal": { 413 | "version": "3.1.3", 414 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 415 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 416 | }, 417 | "node_modules/fast-json-stable-stringify": { 418 | "version": "2.1.0", 419 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 420 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 421 | }, 422 | "node_modules/fill-range": { 423 | "version": "7.1.1", 424 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 425 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 426 | "license": "MIT", 427 | "dependencies": { 428 | "to-regex-range": "^5.0.1" 429 | }, 430 | "engines": { 431 | "node": ">=8" 432 | } 433 | }, 434 | "node_modules/finalhandler": { 435 | "version": "1.2.0", 436 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 437 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 438 | "dependencies": { 439 | "debug": "2.6.9", 440 | "encodeurl": "~1.0.2", 441 | "escape-html": "~1.0.3", 442 | "on-finished": "2.4.1", 443 | "parseurl": "~1.3.3", 444 | "statuses": "2.0.1", 445 | "unpipe": "~1.0.0" 446 | }, 447 | "engines": { 448 | "node": ">= 0.8" 449 | } 450 | }, 451 | "node_modules/forever-agent": { 452 | "version": "0.6.1", 453 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 454 | "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", 455 | "engines": { 456 | "node": "*" 457 | } 458 | }, 459 | "node_modules/forwarded": { 460 | "version": "0.2.0", 461 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 462 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 463 | "engines": { 464 | "node": ">= 0.6" 465 | } 466 | }, 467 | "node_modules/fresh": { 468 | "version": "0.5.2", 469 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 470 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 471 | "engines": { 472 | "node": ">= 0.6" 473 | } 474 | }, 475 | "node_modules/fsevents": { 476 | "version": "2.3.3", 477 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 478 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 479 | "hasInstallScript": true, 480 | "license": "MIT", 481 | "optional": true, 482 | "os": [ 483 | "darwin" 484 | ], 485 | "engines": { 486 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 487 | } 488 | }, 489 | "node_modules/function-bind": { 490 | "version": "1.1.1", 491 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 492 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 493 | }, 494 | "node_modules/get-intrinsic": { 495 | "version": "1.2.0", 496 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", 497 | "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", 498 | "dependencies": { 499 | "function-bind": "^1.1.1", 500 | "has": "^1.0.3", 501 | "has-symbols": "^1.0.3" 502 | }, 503 | "funding": { 504 | "url": "https://github.com/sponsors/ljharb" 505 | } 506 | }, 507 | "node_modules/getpass": { 508 | "version": "0.1.7", 509 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 510 | "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", 511 | "dependencies": { 512 | "assert-plus": "^1.0.0" 513 | } 514 | }, 515 | "node_modules/glob-parent": { 516 | "version": "5.1.2", 517 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 518 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 519 | "license": "ISC", 520 | "dependencies": { 521 | "is-glob": "^4.0.1" 522 | }, 523 | "engines": { 524 | "node": ">= 6" 525 | } 526 | }, 527 | "node_modules/har-schema": { 528 | "version": "2.0.0", 529 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 530 | "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", 531 | "engines": { 532 | "node": ">=4" 533 | } 534 | }, 535 | "node_modules/har-validator": { 536 | "version": "5.1.5", 537 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", 538 | "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", 539 | "deprecated": "this library is no longer supported", 540 | "dependencies": { 541 | "ajv": "^6.12.3", 542 | "har-schema": "^2.0.0" 543 | }, 544 | "engines": { 545 | "node": ">=6" 546 | } 547 | }, 548 | "node_modules/has": { 549 | "version": "1.0.3", 550 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 551 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 552 | "dependencies": { 553 | "function-bind": "^1.1.1" 554 | }, 555 | "engines": { 556 | "node": ">= 0.4.0" 557 | } 558 | }, 559 | "node_modules/has-flag": { 560 | "version": "3.0.0", 561 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 562 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 563 | "license": "MIT", 564 | "engines": { 565 | "node": ">=4" 566 | } 567 | }, 568 | "node_modules/has-symbols": { 569 | "version": "1.0.3", 570 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 571 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 572 | "engines": { 573 | "node": ">= 0.4" 574 | }, 575 | "funding": { 576 | "url": "https://github.com/sponsors/ljharb" 577 | } 578 | }, 579 | "node_modules/http-errors": { 580 | "version": "2.0.0", 581 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 582 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 583 | "dependencies": { 584 | "depd": "2.0.0", 585 | "inherits": "2.0.4", 586 | "setprototypeof": "1.2.0", 587 | "statuses": "2.0.1", 588 | "toidentifier": "1.0.1" 589 | }, 590 | "engines": { 591 | "node": ">= 0.8" 592 | } 593 | }, 594 | "node_modules/http-signature": { 595 | "version": "1.2.0", 596 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 597 | "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", 598 | "dependencies": { 599 | "assert-plus": "^1.0.0", 600 | "jsprim": "^1.2.2", 601 | "sshpk": "^1.7.0" 602 | }, 603 | "engines": { 604 | "node": ">=0.8", 605 | "npm": ">=1.3.7" 606 | } 607 | }, 608 | "node_modules/iconv-lite": { 609 | "version": "0.4.24", 610 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 611 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 612 | "dependencies": { 613 | "safer-buffer": ">= 2.1.2 < 3" 614 | }, 615 | "engines": { 616 | "node": ">=0.10.0" 617 | } 618 | }, 619 | "node_modules/ignore-by-default": { 620 | "version": "1.0.1", 621 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 622 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", 623 | "license": "ISC" 624 | }, 625 | "node_modules/inherits": { 626 | "version": "2.0.4", 627 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 628 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 629 | }, 630 | "node_modules/ipaddr.js": { 631 | "version": "1.9.1", 632 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 633 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 634 | "engines": { 635 | "node": ">= 0.10" 636 | } 637 | }, 638 | "node_modules/is-binary-path": { 639 | "version": "2.1.0", 640 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 641 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 642 | "license": "MIT", 643 | "dependencies": { 644 | "binary-extensions": "^2.0.0" 645 | }, 646 | "engines": { 647 | "node": ">=8" 648 | } 649 | }, 650 | "node_modules/is-extglob": { 651 | "version": "2.1.1", 652 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 653 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 654 | "license": "MIT", 655 | "engines": { 656 | "node": ">=0.10.0" 657 | } 658 | }, 659 | "node_modules/is-glob": { 660 | "version": "4.0.3", 661 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 662 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 663 | "license": "MIT", 664 | "dependencies": { 665 | "is-extglob": "^2.1.1" 666 | }, 667 | "engines": { 668 | "node": ">=0.10.0" 669 | } 670 | }, 671 | "node_modules/is-number": { 672 | "version": "7.0.0", 673 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 674 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 675 | "license": "MIT", 676 | "engines": { 677 | "node": ">=0.12.0" 678 | } 679 | }, 680 | "node_modules/is-typedarray": { 681 | "version": "1.0.0", 682 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 683 | "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" 684 | }, 685 | "node_modules/isstream": { 686 | "version": "0.1.2", 687 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 688 | "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" 689 | }, 690 | "node_modules/jsbn": { 691 | "version": "0.1.1", 692 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 693 | "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" 694 | }, 695 | "node_modules/json-schema": { 696 | "version": "0.4.0", 697 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", 698 | "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" 699 | }, 700 | "node_modules/json-schema-traverse": { 701 | "version": "0.4.1", 702 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 703 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 704 | }, 705 | "node_modules/json-stringify-safe": { 706 | "version": "5.0.1", 707 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 708 | "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" 709 | }, 710 | "node_modules/jsprim": { 711 | "version": "1.4.2", 712 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", 713 | "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", 714 | "dependencies": { 715 | "assert-plus": "1.0.0", 716 | "extsprintf": "1.3.0", 717 | "json-schema": "0.4.0", 718 | "verror": "1.10.0" 719 | }, 720 | "engines": { 721 | "node": ">=0.6.0" 722 | } 723 | }, 724 | "node_modules/media-typer": { 725 | "version": "0.3.0", 726 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 727 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 728 | "engines": { 729 | "node": ">= 0.6" 730 | } 731 | }, 732 | "node_modules/merge-descriptors": { 733 | "version": "1.0.1", 734 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 735 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 736 | }, 737 | "node_modules/methods": { 738 | "version": "1.1.2", 739 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 740 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 741 | "engines": { 742 | "node": ">= 0.6" 743 | } 744 | }, 745 | "node_modules/mime": { 746 | "version": "1.6.0", 747 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 748 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 749 | "bin": { 750 | "mime": "cli.js" 751 | }, 752 | "engines": { 753 | "node": ">=4" 754 | } 755 | }, 756 | "node_modules/mime-db": { 757 | "version": "1.52.0", 758 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 759 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 760 | "engines": { 761 | "node": ">= 0.6" 762 | } 763 | }, 764 | "node_modules/mime-types": { 765 | "version": "2.1.35", 766 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 767 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 768 | "dependencies": { 769 | "mime-db": "1.52.0" 770 | }, 771 | "engines": { 772 | "node": ">= 0.6" 773 | } 774 | }, 775 | "node_modules/minimatch": { 776 | "version": "3.1.2", 777 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 778 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 779 | "license": "ISC", 780 | "dependencies": { 781 | "brace-expansion": "^1.1.7" 782 | }, 783 | "engines": { 784 | "node": "*" 785 | } 786 | }, 787 | "node_modules/ms": { 788 | "version": "2.0.0", 789 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 790 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 791 | }, 792 | "node_modules/negotiator": { 793 | "version": "0.6.3", 794 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 795 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 796 | "engines": { 797 | "node": ">= 0.6" 798 | } 799 | }, 800 | "node_modules/nodemon": { 801 | "version": "3.1.9", 802 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", 803 | "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", 804 | "license": "MIT", 805 | "dependencies": { 806 | "chokidar": "^3.5.2", 807 | "debug": "^4", 808 | "ignore-by-default": "^1.0.1", 809 | "minimatch": "^3.1.2", 810 | "pstree.remy": "^1.1.8", 811 | "semver": "^7.5.3", 812 | "simple-update-notifier": "^2.0.0", 813 | "supports-color": "^5.5.0", 814 | "touch": "^3.1.0", 815 | "undefsafe": "^2.0.5" 816 | }, 817 | "bin": { 818 | "nodemon": "bin/nodemon.js" 819 | }, 820 | "engines": { 821 | "node": ">=10" 822 | }, 823 | "funding": { 824 | "type": "opencollective", 825 | "url": "https://opencollective.com/nodemon" 826 | } 827 | }, 828 | "node_modules/nodemon/node_modules/debug": { 829 | "version": "4.4.0", 830 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 831 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 832 | "license": "MIT", 833 | "dependencies": { 834 | "ms": "^2.1.3" 835 | }, 836 | "engines": { 837 | "node": ">=6.0" 838 | }, 839 | "peerDependenciesMeta": { 840 | "supports-color": { 841 | "optional": true 842 | } 843 | } 844 | }, 845 | "node_modules/nodemon/node_modules/ms": { 846 | "version": "2.1.3", 847 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 848 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 849 | "license": "MIT" 850 | }, 851 | "node_modules/normalize-path": { 852 | "version": "3.0.0", 853 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 854 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 855 | "license": "MIT", 856 | "engines": { 857 | "node": ">=0.10.0" 858 | } 859 | }, 860 | "node_modules/oauth-sign": { 861 | "version": "0.9.0", 862 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 863 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", 864 | "engines": { 865 | "node": "*" 866 | } 867 | }, 868 | "node_modules/object-assign": { 869 | "version": "4.1.1", 870 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 871 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 872 | "license": "MIT", 873 | "engines": { 874 | "node": ">=0.10.0" 875 | } 876 | }, 877 | "node_modules/object-inspect": { 878 | "version": "1.12.3", 879 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 880 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", 881 | "funding": { 882 | "url": "https://github.com/sponsors/ljharb" 883 | } 884 | }, 885 | "node_modules/on-finished": { 886 | "version": "2.4.1", 887 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 888 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 889 | "dependencies": { 890 | "ee-first": "1.1.1" 891 | }, 892 | "engines": { 893 | "node": ">= 0.8" 894 | } 895 | }, 896 | "node_modules/parseurl": { 897 | "version": "1.3.3", 898 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 899 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 900 | "engines": { 901 | "node": ">= 0.8" 902 | } 903 | }, 904 | "node_modules/path-to-regexp": { 905 | "version": "0.1.7", 906 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 907 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 908 | }, 909 | "node_modules/performance-now": { 910 | "version": "2.1.0", 911 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 912 | "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" 913 | }, 914 | "node_modules/picomatch": { 915 | "version": "2.3.1", 916 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 917 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 918 | "license": "MIT", 919 | "engines": { 920 | "node": ">=8.6" 921 | }, 922 | "funding": { 923 | "url": "https://github.com/sponsors/jonschlinkert" 924 | } 925 | }, 926 | "node_modules/proxy-addr": { 927 | "version": "2.0.7", 928 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 929 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 930 | "dependencies": { 931 | "forwarded": "0.2.0", 932 | "ipaddr.js": "1.9.1" 933 | }, 934 | "engines": { 935 | "node": ">= 0.10" 936 | } 937 | }, 938 | "node_modules/psl": { 939 | "version": "1.9.0", 940 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", 941 | "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" 942 | }, 943 | "node_modules/pstree.remy": { 944 | "version": "1.1.8", 945 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 946 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", 947 | "license": "MIT" 948 | }, 949 | "node_modules/punycode": { 950 | "version": "2.3.0", 951 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", 952 | "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", 953 | "engines": { 954 | "node": ">=6" 955 | } 956 | }, 957 | "node_modules/qs": { 958 | "version": "6.11.0", 959 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 960 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 961 | "dependencies": { 962 | "side-channel": "^1.0.4" 963 | }, 964 | "engines": { 965 | "node": ">=0.6" 966 | }, 967 | "funding": { 968 | "url": "https://github.com/sponsors/ljharb" 969 | } 970 | }, 971 | "node_modules/range-parser": { 972 | "version": "1.2.1", 973 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 974 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 975 | "engines": { 976 | "node": ">= 0.6" 977 | } 978 | }, 979 | "node_modules/raw-body": { 980 | "version": "2.5.1", 981 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 982 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 983 | "dependencies": { 984 | "bytes": "3.1.2", 985 | "http-errors": "2.0.0", 986 | "iconv-lite": "0.4.24", 987 | "unpipe": "1.0.0" 988 | }, 989 | "engines": { 990 | "node": ">= 0.8" 991 | } 992 | }, 993 | "node_modules/readdirp": { 994 | "version": "3.6.0", 995 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 996 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 997 | "license": "MIT", 998 | "dependencies": { 999 | "picomatch": "^2.2.1" 1000 | }, 1001 | "engines": { 1002 | "node": ">=8.10.0" 1003 | } 1004 | }, 1005 | "node_modules/request": { 1006 | "version": "2.88.2", 1007 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", 1008 | "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", 1009 | "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", 1010 | "dependencies": { 1011 | "aws-sign2": "~0.7.0", 1012 | "aws4": "^1.8.0", 1013 | "caseless": "~0.12.0", 1014 | "combined-stream": "~1.0.6", 1015 | "extend": "~3.0.2", 1016 | "forever-agent": "~0.6.1", 1017 | "form-data": "~2.3.2", 1018 | "har-validator": "~5.1.3", 1019 | "http-signature": "~1.2.0", 1020 | "is-typedarray": "~1.0.0", 1021 | "isstream": "~0.1.2", 1022 | "json-stringify-safe": "~5.0.1", 1023 | "mime-types": "~2.1.19", 1024 | "oauth-sign": "~0.9.0", 1025 | "performance-now": "^2.1.0", 1026 | "qs": "~6.5.2", 1027 | "safe-buffer": "^5.1.2", 1028 | "tough-cookie": "~2.5.0", 1029 | "tunnel-agent": "^0.6.0", 1030 | "uuid": "^3.3.2" 1031 | }, 1032 | "engines": { 1033 | "node": ">= 6" 1034 | } 1035 | }, 1036 | "node_modules/request/node_modules/form-data": { 1037 | "version": "2.3.3", 1038 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 1039 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 1040 | "dependencies": { 1041 | "asynckit": "^0.4.0", 1042 | "combined-stream": "^1.0.6", 1043 | "mime-types": "^2.1.12" 1044 | }, 1045 | "engines": { 1046 | "node": ">= 0.12" 1047 | } 1048 | }, 1049 | "node_modules/request/node_modules/qs": { 1050 | "version": "6.5.3", 1051 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", 1052 | "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", 1053 | "engines": { 1054 | "node": ">=0.6" 1055 | } 1056 | }, 1057 | "node_modules/safe-buffer": { 1058 | "version": "5.2.1", 1059 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1060 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1061 | "funding": [ 1062 | { 1063 | "type": "github", 1064 | "url": "https://github.com/sponsors/feross" 1065 | }, 1066 | { 1067 | "type": "patreon", 1068 | "url": "https://www.patreon.com/feross" 1069 | }, 1070 | { 1071 | "type": "consulting", 1072 | "url": "https://feross.org/support" 1073 | } 1074 | ] 1075 | }, 1076 | "node_modules/safer-buffer": { 1077 | "version": "2.1.2", 1078 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1079 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1080 | }, 1081 | "node_modules/semver": { 1082 | "version": "7.6.3", 1083 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", 1084 | "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", 1085 | "license": "ISC", 1086 | "bin": { 1087 | "semver": "bin/semver.js" 1088 | }, 1089 | "engines": { 1090 | "node": ">=10" 1091 | } 1092 | }, 1093 | "node_modules/send": { 1094 | "version": "0.18.0", 1095 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 1096 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 1097 | "dependencies": { 1098 | "debug": "2.6.9", 1099 | "depd": "2.0.0", 1100 | "destroy": "1.2.0", 1101 | "encodeurl": "~1.0.2", 1102 | "escape-html": "~1.0.3", 1103 | "etag": "~1.8.1", 1104 | "fresh": "0.5.2", 1105 | "http-errors": "2.0.0", 1106 | "mime": "1.6.0", 1107 | "ms": "2.1.3", 1108 | "on-finished": "2.4.1", 1109 | "range-parser": "~1.2.1", 1110 | "statuses": "2.0.1" 1111 | }, 1112 | "engines": { 1113 | "node": ">= 0.8.0" 1114 | } 1115 | }, 1116 | "node_modules/send/node_modules/ms": { 1117 | "version": "2.1.3", 1118 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1119 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1120 | }, 1121 | "node_modules/serve-static": { 1122 | "version": "1.15.0", 1123 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 1124 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 1125 | "dependencies": { 1126 | "encodeurl": "~1.0.2", 1127 | "escape-html": "~1.0.3", 1128 | "parseurl": "~1.3.3", 1129 | "send": "0.18.0" 1130 | }, 1131 | "engines": { 1132 | "node": ">= 0.8.0" 1133 | } 1134 | }, 1135 | "node_modules/setprototypeof": { 1136 | "version": "1.2.0", 1137 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1138 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1139 | }, 1140 | "node_modules/side-channel": { 1141 | "version": "1.0.4", 1142 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 1143 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 1144 | "dependencies": { 1145 | "call-bind": "^1.0.0", 1146 | "get-intrinsic": "^1.0.2", 1147 | "object-inspect": "^1.9.0" 1148 | }, 1149 | "funding": { 1150 | "url": "https://github.com/sponsors/ljharb" 1151 | } 1152 | }, 1153 | "node_modules/simple-update-notifier": { 1154 | "version": "2.0.0", 1155 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", 1156 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", 1157 | "license": "MIT", 1158 | "dependencies": { 1159 | "semver": "^7.5.3" 1160 | }, 1161 | "engines": { 1162 | "node": ">=10" 1163 | } 1164 | }, 1165 | "node_modules/sshpk": { 1166 | "version": "1.17.0", 1167 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", 1168 | "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", 1169 | "dependencies": { 1170 | "asn1": "~0.2.3", 1171 | "assert-plus": "^1.0.0", 1172 | "bcrypt-pbkdf": "^1.0.0", 1173 | "dashdash": "^1.12.0", 1174 | "ecc-jsbn": "~0.1.1", 1175 | "getpass": "^0.1.1", 1176 | "jsbn": "~0.1.0", 1177 | "safer-buffer": "^2.0.2", 1178 | "tweetnacl": "~0.14.0" 1179 | }, 1180 | "bin": { 1181 | "sshpk-conv": "bin/sshpk-conv", 1182 | "sshpk-sign": "bin/sshpk-sign", 1183 | "sshpk-verify": "bin/sshpk-verify" 1184 | }, 1185 | "engines": { 1186 | "node": ">=0.10.0" 1187 | } 1188 | }, 1189 | "node_modules/statuses": { 1190 | "version": "2.0.1", 1191 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1192 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1193 | "engines": { 1194 | "node": ">= 0.8" 1195 | } 1196 | }, 1197 | "node_modules/supports-color": { 1198 | "version": "5.5.0", 1199 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1200 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1201 | "license": "MIT", 1202 | "dependencies": { 1203 | "has-flag": "^3.0.0" 1204 | }, 1205 | "engines": { 1206 | "node": ">=4" 1207 | } 1208 | }, 1209 | "node_modules/to-regex-range": { 1210 | "version": "5.0.1", 1211 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1212 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1213 | "license": "MIT", 1214 | "dependencies": { 1215 | "is-number": "^7.0.0" 1216 | }, 1217 | "engines": { 1218 | "node": ">=8.0" 1219 | } 1220 | }, 1221 | "node_modules/toidentifier": { 1222 | "version": "1.0.1", 1223 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1224 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1225 | "engines": { 1226 | "node": ">=0.6" 1227 | } 1228 | }, 1229 | "node_modules/touch": { 1230 | "version": "3.1.1", 1231 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", 1232 | "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", 1233 | "license": "ISC", 1234 | "bin": { 1235 | "nodetouch": "bin/nodetouch.js" 1236 | } 1237 | }, 1238 | "node_modules/tough-cookie": { 1239 | "version": "2.5.0", 1240 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 1241 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 1242 | "dependencies": { 1243 | "psl": "^1.1.28", 1244 | "punycode": "^2.1.1" 1245 | }, 1246 | "engines": { 1247 | "node": ">=0.8" 1248 | } 1249 | }, 1250 | "node_modules/tunnel-agent": { 1251 | "version": "0.6.0", 1252 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1253 | "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", 1254 | "dependencies": { 1255 | "safe-buffer": "^5.0.1" 1256 | }, 1257 | "engines": { 1258 | "node": "*" 1259 | } 1260 | }, 1261 | "node_modules/tweetnacl": { 1262 | "version": "0.14.5", 1263 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1264 | "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" 1265 | }, 1266 | "node_modules/type-is": { 1267 | "version": "1.6.18", 1268 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1269 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1270 | "dependencies": { 1271 | "media-typer": "0.3.0", 1272 | "mime-types": "~2.1.24" 1273 | }, 1274 | "engines": { 1275 | "node": ">= 0.6" 1276 | } 1277 | }, 1278 | "node_modules/undefsafe": { 1279 | "version": "2.0.5", 1280 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 1281 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", 1282 | "license": "MIT" 1283 | }, 1284 | "node_modules/unpipe": { 1285 | "version": "1.0.0", 1286 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1287 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1288 | "engines": { 1289 | "node": ">= 0.8" 1290 | } 1291 | }, 1292 | "node_modules/uri-js": { 1293 | "version": "4.4.1", 1294 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1295 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1296 | "dependencies": { 1297 | "punycode": "^2.1.0" 1298 | } 1299 | }, 1300 | "node_modules/utils-merge": { 1301 | "version": "1.0.1", 1302 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1303 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1304 | "engines": { 1305 | "node": ">= 0.4.0" 1306 | } 1307 | }, 1308 | "node_modules/uuid": { 1309 | "version": "3.4.0", 1310 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 1311 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", 1312 | "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", 1313 | "bin": { 1314 | "uuid": "bin/uuid" 1315 | } 1316 | }, 1317 | "node_modules/vary": { 1318 | "version": "1.1.2", 1319 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1320 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1321 | "engines": { 1322 | "node": ">= 0.8" 1323 | } 1324 | }, 1325 | "node_modules/verror": { 1326 | "version": "1.10.0", 1327 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1328 | "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", 1329 | "engines": [ 1330 | "node >=0.6.0" 1331 | ], 1332 | "dependencies": { 1333 | "assert-plus": "^1.0.0", 1334 | "core-util-is": "1.0.2", 1335 | "extsprintf": "^1.2.0" 1336 | } 1337 | }, 1338 | "node_modules/verror/node_modules/core-util-is": { 1339 | "version": "1.0.2", 1340 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 1341 | "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" 1342 | } 1343 | } 1344 | } 1345 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "cors": "^2.8.5", 4 | "express": "^4.17.3", 5 | "nodemon": "^3.1.9", 6 | "request": "^2.88.2" 7 | }, 8 | "name": "server", 9 | "version": "1.0.0", 10 | "main": "server.js", 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "start": "nodemon server.js" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "description": "" 19 | } 20 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const { ChessEngine } = require("./utils/engine") 4 | const { VARS } = require("./VARS") 5 | const cors = require("cors") 6 | 7 | try { 8 | app.listen(VARS.PORT, () => console.log(`Listening on port ${VARS.PORT}`)) 9 | app.use(cors()) 10 | } catch (error) { 11 | console.log("Server is already running !!") 12 | } 13 | 14 | 15 | 16 | // VARS 17 | const chessEngine = new ChessEngine() 18 | var counter = 0 19 | 20 | 21 | 22 | 23 | 24 | app.get("/getBestMove", (req, res) => { 25 | var fen = req.query.fen || "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" 26 | var depth = req.query.depth || 10 27 | var movetime = req.query.movetime || 500 28 | 29 | var turn = req.query.turn || "w" 30 | //var engine_type = req.query.engine_type || VARS.ENGINE_TYPES[0] 31 | var engine_name = req.query.engine_name || "stockfish.exe" 32 | var engine_mode = req.query.engine_mode || 0 33 | 34 | 35 | if (depth > 20) { 36 | depth = 20 37 | } 38 | 39 | counter++ 40 | console.log("\n#" + counter + " turn updated to: " + (turn === 'w' ? 'White' : 'Black')) 41 | 42 | 43 | chessEngine.start(counter, engine_mode, turn, depth, movetime, engine_name, fen).then((result) => { 44 | 45 | const parsedResult = { 46 | fen: result.fen, 47 | bestMove: result.bestMove, 48 | ponder: result.ponder, 49 | turn: result.turn, 50 | depth: depth, 51 | movetime: movetime, 52 | score: depth, 53 | provider: engine_name 54 | } 55 | 56 | 57 | console.log("#" + result.id + " best move: " + parsedResult.bestMove) 58 | return res.send({ success: true, data: parsedResult }) 59 | }).catch((error) => { 60 | console.log("Error: " + error) 61 | return res.send({ success: false, data: error.message }) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /tampermonkey script/content/UserGui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * usergui.js 3 | * v1.0.1 4 | * https://github.com/AugmentedWeb/UserGui 5 | * Apache 2.0 licensed 6 | */ 7 | 8 | class UserGui { 9 | constructor() { 10 | const grantArr = GM_info?.script?.grant; 11 | 12 | if(typeof grantArr == "object") { 13 | if(!grantArr.includes("GM_xmlhttpRequest")) { 14 | prompt(`${this.#projectName} needs GM_xmlhttpRequest!\n\nPlease add this to your userscript's header...`, "// @grant GM_xmlhttpRequest"); 15 | } 16 | 17 | if(!grantArr.includes("GM_getValue")) { 18 | prompt(`${this.#projectName} needs GM_getValue!\n\nPlease add this to your userscript's header...`, "// @grant GM_getValue"); 19 | } 20 | 21 | if(!grantArr.includes("GM_setValue")) { 22 | prompt(`${this.#projectName} needs GM_setValue!\n\nPlease add this to your userscript's header...`, "// @grant GM_setValue"); 23 | } 24 | } 25 | } 26 | 27 | #projectName = "UserGui"; 28 | window = undefined; 29 | document = undefined; 30 | iFrame = undefined; 31 | settings = { 32 | "window" : { 33 | "title" : "Smart Chess Bot", 34 | "name" : "userscript-gui", 35 | "external" : false, 36 | "centered" : false, 37 | "size" : { 38 | "width" : 300, 39 | "height" : 500, 40 | "dynamicSize" : true 41 | } 42 | }, 43 | "gui" : { 44 | "centeredItems" : false, 45 | "internal" : { 46 | "darkCloseButton" : false, 47 | "style" : ` 48 | body { 49 | background-color: #ffffff; 50 | overflow: hidden; 51 | width: 100% !important; 52 | } 53 | 54 | form { 55 | padding: 10px; 56 | } 57 | 58 | #gui { 59 | height: fit-content; 60 | } 61 | 62 | .rendered-form { 63 | padding: 10px; 64 | } 65 | 66 | #header { 67 | padding: 10px; 68 | cursor: move; 69 | z-index: 10; 70 | background-color: #2196F3; 71 | color: #fff; 72 | height: fit-content; 73 | } 74 | 75 | .header-item-container { 76 | display: flex; 77 | justify-content: space-between; 78 | align-items: center; 79 | } 80 | 81 | .left-title { 82 | font-size: 14px; 83 | font-weight: bold; 84 | padding: 0; 85 | margin: 0; 86 | } 87 | 88 | #button-close-gui { 89 | vertical-align: middle; 90 | 91 | } 92 | 93 | div .form-group { 94 | margin-bottom: 15px; 95 | } 96 | 97 | #resizer { 98 | width: 10px; 99 | height: 10px; 100 | cursor: se-resize; 101 | position: absolute; 102 | bottom: 0; 103 | right: 0; 104 | } 105 | 106 | .formbuilder-button { 107 | width: fit-content; 108 | } 109 | ` 110 | }, 111 | "external" : { 112 | "popup" : true, 113 | "style" : ` 114 | .rendered-form { 115 | padding: 10px; 116 | } 117 | div .form-group { 118 | margin-bottom: 15px; 119 | } 120 | ` 121 | } 122 | }, 123 | "messages" : { 124 | "blockedPopups" : () => alert(`The GUI (graphical user interface) failed to open!\n\nPossible reason: The popups are blocked.\n\nPlease allow popups for this site. (${window.location.hostname})`) 125 | } 126 | }; 127 | 128 | // This error page will be shown if the user has not added any pages 129 | #errorPage = (title, code) => ` 130 | 154 |
155 |
156 |

${title}

157 | ${code} 158 |

${this.#projectName} error message

159 |
160 |
`; 161 | 162 | // The user can add multiple pages to their GUI. The pages are stored in this array. 163 | #guiPages = [ 164 | { 165 | "name" : "default_no_content_set", 166 | "content" : this.#errorPage("Content missing", "Gui.setContent(html, tabName);") 167 | } 168 | ]; 169 | 170 | // The userscript manager's xmlHttpRequest is used to bypass CORS limitations (To load Bootstrap) 171 | async #bypassCors(externalFile) { 172 | const res = await new Promise(resolve => { 173 | GM_xmlhttpRequest({ 174 | method: "GET", 175 | url: externalFile, 176 | onload: resolve 177 | }); 178 | }); 179 | 180 | return res.responseText; 181 | } 182 | 183 | // Returns one tab (as HTML) for the navigation tabs 184 | #createNavigationTab(page) { 185 | const name = page.name; 186 | 187 | if(name == undefined) { 188 | console.error(`[${this.#projectName}] Gui.addPage(html, name) <- name missing!`); 189 | return undefined; 190 | } else { 191 | const modifiedName = name.toLowerCase().replaceAll(' ', '').replace(/[^a-zA-Z0-9]/g, '') + Math.floor(Math.random() * 1000000000); 192 | 193 | const content = page.content; 194 | const indexOnArray = this.#guiPages.map(x => x.name).indexOf(name); 195 | const firstItem = indexOnArray == 0 ? true : false; 196 | 197 | return { 198 | "listItem" : ` 199 | 202 | `, 203 | "panelItem" : ` 204 |
${content}
205 | ` 206 | }; 207 | } 208 | } 209 | 210 | // Make tabs function without bootstrap.js (CSP might block bootstrap and make the GUI nonfunctional) 211 | #initializeTabs() { 212 | const handleTabClick = e => { 213 | const target = e.target; 214 | const contentID = target.getAttribute("data-bs-target"); 215 | 216 | target.classList.add("active"); 217 | this.document.querySelector(contentID).classList.add("active"); 218 | 219 | [...this.document.querySelectorAll(".nav-link")].forEach(tab => { 220 | if(tab != target) { 221 | const contentID = tab.getAttribute("data-bs-target"); 222 | 223 | tab.classList.remove("active"); 224 | this.document.querySelector(contentID).classList.remove("active"); 225 | } 226 | }); 227 | } 228 | 229 | [...this.document.querySelectorAll(".nav-link")].forEach(tab => { 230 | tab.addEventListener("click", handleTabClick); 231 | }); 232 | } 233 | 234 | // Will determine if a navbar is needed, returns either a regular GUI, or a GUI with a navbar 235 | #getContent() { 236 | // Only one page has been set, no navigation tabs will be created 237 | if(this.#guiPages.length == 1) { 238 | return this.#guiPages[0].content; 239 | } 240 | // Multiple pages has been set, dynamically creating the navigation tabs 241 | else if(this.#guiPages.length > 1) { 242 | const tabs = (list, panels) => ` 243 | 246 |
247 | ${panels} 248 |
249 | `; 250 | 251 | let list = ``; 252 | let panels = ``; 253 | 254 | this.#guiPages.forEach(page => { 255 | const data = this.#createNavigationTab(page); 256 | 257 | if(data != undefined) { 258 | list += data.listItem + '\n'; 259 | panels += data.panelItem + '\n'; 260 | } 261 | }); 262 | 263 | return tabs(list, panels); 264 | } 265 | } 266 | 267 | // Returns the GUI's whole document as string 268 | async #createDocument() { 269 | const bootstrapStyling = await this.#bypassCors("https://raw.githubusercontent.com/AugmentedWeb/UserGui/main/resources/bootstrap.css"); 270 | 271 | const externalDocument = ` 272 | 273 | 274 | 275 | ${this.settings.window.title} 276 | 288 | 289 | 290 | ${this.#getContent()} 291 | 292 | 293 | `; 294 | 295 | const internalDocument = ` 296 | 297 | 298 | 299 | 311 | 312 | 313 |
314 | 322 |
323 | ${this.#getContent()} 324 |
325 |
326 |
327 | 328 | 329 | `; 330 | 331 | if(this.settings.window.external) { 332 | return externalDocument; 333 | } else { 334 | return internalDocument; 335 | } 336 | } 337 | 338 | // The user will use this function to add a page to their GUI, with their own HTML (Bootstrap 5) 339 | addPage(tabName, htmlString) { 340 | if(this.#guiPages[0].name == "default_no_content_set") { 341 | this.#guiPages = []; 342 | } 343 | 344 | this.#guiPages.push({ 345 | "name" : tabName, 346 | "content" : htmlString 347 | }); 348 | } 349 | 350 | #getCenterScreenPosition() { 351 | const guiWidth = this.settings.window.size.width; 352 | const guiHeight = this.settings.window.size.height; 353 | 354 | const x = (screen.width - guiWidth) / 2; 355 | const y = (screen.height - guiHeight) / 2; 356 | 357 | return { "x" : x, "y": y }; 358 | } 359 | 360 | #getCenterWindowPosition() { 361 | const guiWidth = this.settings.window.size.width; 362 | const guiHeight = this.settings.window.size.height; 363 | 364 | const x = (window.innerWidth - guiWidth) / 2; 365 | const y = (window.innerHeight - guiHeight) / 2; 366 | 367 | return { "x" : x, "y": y }; 368 | } 369 | 370 | #initializeInternalGuiEvents(iFrame) { 371 | // - The code below will consist mostly of drag and resize implementations 372 | // - iFrame window <-> Main window interaction requires these to be done 373 | // - Basically, iFrame document's event listeners make the whole iFrame move on the main window 374 | 375 | // Sets the iFrame's size 376 | function setFrameSize(x, y) { 377 | iFrame.style.width = `${x}px`; 378 | iFrame.style.height = `${y}px`; 379 | } 380 | 381 | // Gets the iFrame's size 382 | function getFrameSize() { 383 | const frameBounds = iFrame.getBoundingClientRect(); 384 | 385 | return { "width" : frameBounds.width, "height" : frameBounds.height }; 386 | } 387 | 388 | // Sets the iFrame's position relative to the main window's document 389 | function setFramePos(x, y) { 390 | iFrame.style.left = `${x}px`; 391 | iFrame.style.top = `${y}px`; 392 | } 393 | 394 | // Gets the iFrame's position relative to the main document 395 | function getFramePos() { 396 | const frameBounds = iFrame.getBoundingClientRect(); 397 | 398 | return { "x": frameBounds.x, "y" : frameBounds.y }; 399 | } 400 | 401 | // Gets the frame body's offsetHeight 402 | function getInnerFrameSize() { 403 | const innerFrameElem = iFrame.contentDocument.querySelector("#gui"); 404 | 405 | return { "x": innerFrameElem.offsetWidth, "y" : innerFrameElem.offsetHeight }; 406 | } 407 | 408 | // Sets the frame's size to the innerframe's size 409 | const adjustFrameSize = () => { 410 | const innerFrameSize = getInnerFrameSize(); 411 | 412 | setFrameSize(innerFrameSize.x, innerFrameSize.y); 413 | } 414 | 415 | // Variables for draggable header 416 | let dragging = false, 417 | dragStartPos = { "x" : 0, "y" : 0 }; 418 | 419 | // Variables for resizer 420 | let resizing = false, 421 | mousePos = { "x" : undefined, "y" : undefined }, 422 | lastFrame; 423 | 424 | function handleResize(isInsideFrame, e) { 425 | if(mousePos.x == undefined && mousePos.y == undefined) { 426 | mousePos.x = e.clientX; 427 | mousePos.y = e.clientY; 428 | 429 | lastFrame = isInsideFrame; 430 | } 431 | 432 | const deltaX = mousePos.x - e.clientX, 433 | deltaY = mousePos.y - e.clientY; 434 | 435 | const frameSize = getFrameSize(); 436 | const allowedSize = frameSize.width - deltaX > 160 && frameSize.height - deltaY > 90; 437 | 438 | if(isInsideFrame == lastFrame && allowedSize) { 439 | setFrameSize(frameSize.width - deltaX, frameSize.height - deltaY); 440 | } 441 | 442 | mousePos.x = e.clientX; 443 | mousePos.y = e.clientY; 444 | 445 | lastFrame = isInsideFrame; 446 | } 447 | 448 | function handleDrag(isInsideFrame, e) { 449 | const bR = iFrame.getBoundingClientRect(); 450 | 451 | const windowWidth = window.innerWidth, 452 | windowHeight = window.innerHeight; 453 | 454 | let x, y; 455 | 456 | if(isInsideFrame) { 457 | x = getFramePos().x += e.clientX - dragStartPos.x; 458 | y = getFramePos().y += e.clientY - dragStartPos.y; 459 | } else { 460 | x = e.clientX - dragStartPos.x; 461 | y = e.clientY - dragStartPos.y; 462 | } 463 | 464 | // Check out of bounds: left 465 | if(x <= 0) { 466 | x = 0 467 | } 468 | 469 | // Check out of bounds: right 470 | if(x + bR.width >= windowWidth) { 471 | x = windowWidth - bR.width; 472 | } 473 | 474 | // Check out of bounds: top 475 | if(y <= 0) { 476 | y = 0; 477 | } 478 | 479 | // Check out of bounds: bottom 480 | if(y + bR.height >= windowHeight) { 481 | y = windowHeight - bR.height; 482 | } 483 | 484 | setFramePos(x, y); 485 | } 486 | 487 | // Dragging start (iFrame) 488 | this.document.querySelector("#header").addEventListener('mousedown', e => { 489 | e.preventDefault(); 490 | 491 | dragging = true; 492 | 493 | dragStartPos.x = e.clientX; 494 | dragStartPos.y = e.clientY; 495 | }); 496 | 497 | // Resizing start 498 | this.document.querySelector("#resizer").addEventListener('mousedown', e => { 499 | e.preventDefault(); 500 | 501 | resizing = true; 502 | }); 503 | 504 | // While dragging or resizing (iFrame) 505 | this.document.addEventListener('mousemove', e => { 506 | if(dragging) 507 | handleDrag(true, e); 508 | 509 | if(resizing) 510 | handleResize(true, e); 511 | }); 512 | 513 | // While dragging or resizing (Main window) 514 | document.addEventListener('mousemove', e => { 515 | if(dragging) 516 | handleDrag(false, e); 517 | 518 | if(resizing) 519 | handleResize(false, e); 520 | }); 521 | 522 | // Stop dragging and resizing (iFrame) 523 | this.document.addEventListener('mouseup', e => { 524 | e.preventDefault(); 525 | 526 | dragging = false; 527 | resizing = false; 528 | }); 529 | 530 | // Stop dragging and resizing (Main window) 531 | document.addEventListener('mouseup', e => { 532 | dragging = false; 533 | resizing = false; 534 | }); 535 | 536 | 537 | 538 | 539 | const guiObserver = new MutationObserver(adjustFrameSize); 540 | const guiElement = this.document.querySelector("#gui"); 541 | 542 | guiObserver.observe(guiElement, { 543 | childList: true, 544 | subtree: true, 545 | attributes: true 546 | }); 547 | 548 | adjustFrameSize(); 549 | } 550 | 551 | async #openExternalGui(readyFunction) { 552 | const noWindow = this.window?.closed; 553 | 554 | if(noWindow || this.window == undefined) { 555 | let pos = ""; 556 | let windowSettings = ""; 557 | 558 | if(this.settings.window.centered && this.settings.gui.external.popup) { 559 | const centerPos = this.#getCenterScreenPosition(); 560 | pos = `left=${centerPos.x}, top=${centerPos.y}`; 561 | } 562 | 563 | if(this.settings.gui.external.popup) { 564 | windowSettings = `width=${this.settings.window.size.width}, height=${this.settings.window.size.height}, ${pos}`; 565 | } 566 | 567 | // Create a new window for the GUI 568 | this.window = window.open("", this.settings.windowName, windowSettings); 569 | 570 | if(!this.window) { 571 | this.settings.messages.blockedPopups(); 572 | return; 573 | } 574 | 575 | // Write the document to the new window 576 | this.window.document.open(); 577 | this.window.document.write(await this.#createDocument()); 578 | this.window.document.close(); 579 | 580 | if(!this.settings.gui.external.popup) { 581 | this.window.document.body.style.width = `${this.settings.window.size.width}px`; 582 | 583 | if(this.settings.window.centered) { 584 | const centerPos = this.#getCenterScreenPosition(); 585 | 586 | this.window.document.body.style.position = "absolute"; 587 | this.window.document.body.style.left = `${centerPos.x}px`; 588 | this.window.document.body.style.top = `${centerPos.y}px`; 589 | } 590 | } 591 | 592 | // Dynamic sizing (only height & window.outerHeight no longer works on some browsers...) 593 | this.window.resizeTo( 594 | this.settings.window.size.width, 595 | this.settings.window.size.dynamicSize 596 | ? this.window.document.body.offsetHeight + (this.window.outerHeight - this.window.innerHeight) 597 | : this.settings.window.size.height 598 | ); 599 | 600 | this.document = this.window.document; 601 | 602 | this.#initializeTabs(); 603 | 604 | // Call user's function 605 | if(typeof readyFunction == "function") { 606 | readyFunction(); 607 | } 608 | 609 | window.onbeforeunload = () => { 610 | // Close the GUI if parent window closes 611 | this.close(); 612 | } 613 | } 614 | 615 | else { 616 | // Window was already opened, bring the window back to focus 617 | this.window.focus(); 618 | } 619 | } 620 | 621 | async #openInternalGui(readyFunction) { 622 | if(this.iFrame) { 623 | return; 624 | } 625 | 626 | const fadeInSpeedMs = 250; 627 | 628 | let left = 0, top = 0; 629 | 630 | if(this.settings.window.centered) { 631 | const centerPos = this.#getCenterWindowPosition(); 632 | 633 | left = centerPos.x; 634 | top = centerPos.y; 635 | } 636 | 637 | const iframe = document.createElement("iframe"); 638 | iframe.srcdoc = await this.#createDocument(); 639 | iframe.style = ` 640 | position: fixed; 641 | top: ${top}px; 642 | left: ${left}px; 643 | width: ${this.settings.window.size.width}; 644 | height: ${this.settings.window.size.height}; 645 | border: 0; 646 | opacity: 0; 647 | transition: all ${fadeInSpeedMs/1000}s; 648 | border-radius: 5px; 649 | box-shadow: rgb(0 0 0 / 6%) 10px 10px 10px; 650 | z-index: 2147483647; 651 | `; 652 | 653 | const waitForBody = setInterval(() => { 654 | if(document?.body) { 655 | clearInterval(waitForBody); 656 | 657 | // Prepend the GUI to the document's body 658 | document.body.prepend(iframe); 659 | 660 | iframe.contentWindow.onload = () => { 661 | // Fade-in implementation 662 | setTimeout(() => iframe.style["opacity"] = "1", fadeInSpeedMs/2); 663 | setTimeout(() => iframe.style["transition"] = "none", fadeInSpeedMs + 500); 664 | 665 | this.window = iframe.contentWindow; 666 | this.document = iframe.contentDocument; 667 | this.iFrame = iframe; 668 | 669 | this.#initializeInternalGuiEvents(iframe); 670 | this.#initializeTabs(); 671 | 672 | readyFunction(); 673 | } 674 | } 675 | }, 100); 676 | } 677 | 678 | // Determines if the window is to be opened externally or internally 679 | open(readyFunction) { 680 | if(this.settings.window.external) { 681 | this.#openExternalGui(readyFunction); 682 | } else { 683 | this.#openInternalGui(readyFunction); 684 | } 685 | } 686 | 687 | // Closes the GUI if it exists 688 | close() { 689 | if(this.settings.window.external) { 690 | if(this.window) { 691 | this.window.close(); 692 | } 693 | } else { 694 | if(this.iFrame) { 695 | this.iFrame.remove(); 696 | this.iFrame = undefined; 697 | } 698 | } 699 | } 700 | 701 | saveConfig() { 702 | let config = []; 703 | 704 | if(this.document) { 705 | [...this.document.querySelectorAll(".form-group")].forEach(elem => { 706 | const inputElem = elem.querySelector("[name]"); 707 | 708 | const name = inputElem.getAttribute("name"), 709 | data = this.getData(name); 710 | 711 | if(data) { 712 | config.push({ "name" : name, "value" : data }); 713 | } 714 | }); 715 | } 716 | 717 | GM_setValue("config", config); 718 | } 719 | 720 | loadConfig() { 721 | const config = this.getConfig(); 722 | 723 | if(this.document && config) { 724 | config.forEach(elemConfig => { 725 | this.setData(elemConfig.name, elemConfig.value); 726 | }) 727 | } 728 | } 729 | 730 | getConfig() { 731 | return GM_getValue("config"); 732 | } 733 | 734 | resetConfig() { 735 | const config = this.getConfig(); 736 | 737 | if(config) { 738 | GM_setValue("config", []); 739 | } 740 | } 741 | 742 | dispatchFormEvent(name) { 743 | const type = name.split("-")[0].toLowerCase(); 744 | const properties = this.#typeProperties.find(x => type == x.type); 745 | const event = new Event(properties.event); 746 | 747 | const field = this.document.querySelector(`.field-${name}`); 748 | field.dispatchEvent(event); 749 | } 750 | 751 | setPrimaryColor(hex) { 752 | const styles = ` 753 | #header { 754 | background-color: ${hex} !important; 755 | } 756 | .nav-link { 757 | color: ${hex} !important; 758 | } 759 | .text-primary { 760 | color: ${hex} !important; 761 | } 762 | `; 763 | 764 | const styleSheet = document.createElement("style") 765 | styleSheet.innerText = styles; 766 | this.document.head.appendChild(styleSheet); 767 | } 768 | 769 | // Creates an event listener a GUI element 770 | event(name, event, eventFunction) { 771 | this.document.querySelector(`.field-${name}`).addEventListener(event, eventFunction); 772 | } 773 | 774 | // Disables a GUI element 775 | disable(name) { 776 | [...this.document.querySelector(`.field-${name}`).children].forEach(childElem => { 777 | childElem.setAttribute("disabled", "true"); 778 | }); 779 | } 780 | 781 | // Enables a GUI element 782 | enable(name) { 783 | [...this.document.querySelector(`.field-${name}`).children].forEach(childElem => { 784 | if(childElem.getAttribute("disabled")) { 785 | childElem.removeAttribute("disabled"); 786 | } 787 | }); 788 | } 789 | 790 | // Gets data from types: TEXT FIELD, TEXTAREA, DATE FIELD & NUMBER 791 | getValue(name) { 792 | return this.document.querySelector(`.field-${name}`).querySelector(`[id=${name}]`).value; 793 | } 794 | 795 | // Sets data to types: TEXT FIELD, TEXT AREA, DATE FIELD & NUMBER 796 | setValue(name, newValue) { 797 | this.document.querySelector(`.field-${name}`).querySelector(`[id=${name}]`).value = newValue; 798 | 799 | this.dispatchFormEvent(name); 800 | } 801 | 802 | // Gets data from types: RADIO GROUP 803 | getSelection(name) { 804 | return this.document.querySelector(`.field-${name}`).querySelector(`input[name=${name}]:checked`).value; 805 | } 806 | 807 | // Sets data to types: RADIO GROUP 808 | setSelection(name, newOptionsValue) { 809 | this.document.querySelector(`.field-${name}`).querySelector(`input[value=${newOptionsValue}]`).checked = true; 810 | 811 | this.dispatchFormEvent(name); 812 | } 813 | 814 | // Gets data from types: CHECKBOX GROUP 815 | getChecked(name) { 816 | return [...this.document.querySelector(`.field-${name}`).querySelectorAll(`input[name*=${name}]:checked`)] 817 | .map(checkbox => checkbox.value); 818 | } 819 | 820 | // Sets data to types: CHECKBOX GROUP 821 | setChecked(name, checkedArr) { 822 | const checkboxes = [...this.document.querySelector(`.field-${name}`).querySelectorAll(`input[name*=${name}]`)] 823 | 824 | checkboxes.forEach(checkbox => { 825 | if(checkedArr.includes(checkbox.value)) { 826 | checkbox.checked = true; 827 | } 828 | }); 829 | 830 | this.dispatchFormEvent(name); 831 | } 832 | 833 | // Gets data from types: FILE UPLOAD 834 | getFiles(name) { 835 | return this.document.querySelector(`.field-${name}`).querySelector(`input[id=${name}]`).files; 836 | } 837 | 838 | // Gets data from types: SELECT 839 | getOption(name) { 840 | const selectedArr = [...this.document.querySelector(`.field-${name} #${name}`).selectedOptions].map(({value}) => value); 841 | 842 | return selectedArr.length == 1 ? selectedArr[0] : selectedArr; 843 | } 844 | 845 | // Sets data to types: SELECT 846 | setOption(name, newOptionsValue) { 847 | if(typeof newOptionsValue == 'object') { 848 | newOptionsValue.forEach(optionVal => { 849 | this.document.querySelector(`.field-${name}`).querySelector(`option[value=${optionVal}]`).selected = true; 850 | }); 851 | } else { 852 | this.document.querySelector(`.field-${name}`).querySelector(`option[value=${newOptionsValue}]`).selected = true; 853 | } 854 | 855 | this.dispatchFormEvent(name); 856 | } 857 | 858 | #typeProperties = [ 859 | { 860 | "type": "button", 861 | "event": "click", 862 | "function": { 863 | "get" : null, 864 | "set" : null 865 | } 866 | }, 867 | { 868 | "type": "radio", 869 | "event": "change", 870 | "function": { 871 | "get" : n => this.getSelection(n), 872 | "set" : (n, nV) => this.setSelection(n, nV) 873 | } 874 | }, 875 | { 876 | "type": "checkbox", 877 | "event": "change", 878 | "function": { 879 | "get" : n => this.getChecked(n), 880 | "set" : (n, nV) => this.setChecked(n, nV) 881 | } 882 | }, 883 | { 884 | "type": "date", 885 | "event": "change", 886 | "function": { 887 | "get" : n => this.getValue(n), 888 | "set" : (n, nV) => this.setValue(n, nV) 889 | } 890 | }, 891 | { 892 | "type": "file", 893 | "event": "change", 894 | "function": { 895 | "get" : n => this.getFiles(n), 896 | "set" : null 897 | } 898 | }, 899 | { 900 | "type": "number", 901 | "event": "input", 902 | "function": { 903 | "get" : n => this.getValue(n), 904 | "set" : (n, nV) => this.setValue(n, nV) 905 | } 906 | }, 907 | { 908 | "type": "select", 909 | "event": "change", 910 | "function": { 911 | "get" : n => this.getOption(n), 912 | "set" : (n, nV) => this.setOption(n, nV) 913 | } 914 | }, 915 | { 916 | "type": "text", 917 | "event": "input", 918 | "function": { 919 | "get" : n => this.getValue(n), 920 | "set" : (n, nV) => this.setValue(n, nV) 921 | } 922 | }, 923 | { 924 | "type": "textarea", 925 | "event": "input", 926 | "function": { 927 | "get" : n => this.getValue(n), 928 | "set" : (n, nV) => this.setValue(n, nV) 929 | } 930 | }, 931 | ]; 932 | 933 | // The same as the event() function, but automatically determines the best listener type for the element 934 | // (e.g. button -> listen for "click", textarea -> listen for "input") 935 | smartEvent(name, eventFunction) { 936 | if(name.includes("-")) { 937 | const type = name.split("-")[0].toLowerCase(); 938 | const properties = this.#typeProperties.find(x => type == x.type); 939 | 940 | if(typeof properties == "object") { 941 | this.event(name, properties.event, eventFunction); 942 | 943 | } else { 944 | console.warn(`${this.#projectName}'s smartEvent function did not find any matches for the type "${type}". The event could not be made.`); 945 | } 946 | 947 | } else { 948 | console.warn(`The input name "${name}" is invalid for ${this.#projectName}'s smartEvent. The event could not be made.`); 949 | } 950 | } 951 | 952 | // Will automatically determine the suitable function for data retrivial 953 | // (e.g. file select -> use getFiles() function) 954 | getData(name) { 955 | if(name.includes("-")) { 956 | const type = name.split("-")[0].toLowerCase(); 957 | const properties = this.#typeProperties.find(x => type == x.type); 958 | 959 | if(typeof properties == "object") { 960 | const getFunction = properties.function.get; 961 | 962 | if(typeof getFunction == "function") { 963 | return getFunction(name); 964 | 965 | } else { 966 | console.error(`${this.#projectName}'s getData function can't be used for the type "${type}". The data can't be taken.`); 967 | } 968 | 969 | } else { 970 | console.warn(`${this.#projectName}'s getData function did not find any matches for the type "${type}". The event could not be made.`); 971 | } 972 | 973 | } else { 974 | console.warn(`The input name "${name}" is invalid for ${this.#projectName}'s getData function. The event could not be made.`); 975 | } 976 | } 977 | 978 | // Will automatically determine the suitable function for data retrivial (e.g. checkbox -> use setChecked() function) 979 | setData(name, newData) { 980 | if(name.includes("-")) { 981 | const type = name.split("-")[0].toLowerCase(); 982 | const properties = this.#typeProperties.find(x => type == x.type); 983 | 984 | if(typeof properties == "object") { 985 | const setFunction = properties.function.set; 986 | 987 | if(typeof setFunction == "function") { 988 | return setFunction(name, newData); 989 | 990 | } else { 991 | console.error(`${this.#projectName}'s setData function can't be used for the type "${type}". The data can't be taken.`); 992 | } 993 | 994 | } else { 995 | console.warn(`${this.#projectName}'s setData function did not find any matches for the type "${type}". The event could not be made.`); 996 | } 997 | 998 | } else { 999 | console.warn(`The input name "${name}" is invalid for ${this.#projectName}'s setData function. The event could not be made.`); 1000 | } 1001 | } 1002 | }; -------------------------------------------------------------------------------- /tampermonkey script/content/chessboard.css: -------------------------------------------------------------------------------- 1 | /*! chessboard.js v1.0.0 | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */ 2 | 3 | .clearfix-7da63 { 4 | clear: both; 5 | } 6 | 7 | .board-b72b1 { 8 | border: 2px solid #404040; 9 | box-sizing: content-box; 10 | } 11 | 12 | .square-55d63 { 13 | float: left; 14 | position: relative; 15 | 16 | /* disable any native browser highlighting */ 17 | -webkit-touch-callout: none; 18 | -webkit-user-select: none; 19 | -khtml-user-select: none; 20 | -moz-user-select: none; 21 | -ms-user-select: none; 22 | user-select: none; 23 | } 24 | 25 | .white-1e1d7 { 26 | background-color: #f0d9b5; 27 | color: #b58863; 28 | } 29 | 30 | .black-3c85d { 31 | background-color: #b58863; 32 | color: #f0d9b5; 33 | } 34 | 35 | .highlight1-32417, .highlight2-9c5d2 { 36 | box-shadow: inset 0 0 3px 3px yellow; 37 | } 38 | 39 | .notation-322f9 { 40 | cursor: default; 41 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 42 | font-size: 14px; 43 | position: absolute; 44 | } 45 | 46 | .alpha-d2270 { 47 | bottom: 1px; 48 | right: 3px; 49 | } 50 | 51 | .numeric-fc462 { 52 | top: 2px; 53 | left: 2px; 54 | } 55 | -------------------------------------------------------------------------------- /tampermonkey script/content/chessboard.js: -------------------------------------------------------------------------------- 1 | // chessboard.js v1.0.0 2 | // https://github.com/oakmac/chessboardjs/ 3 | // 4 | // Copyright (c) 2019, Chris Oakman 5 | // Released under the MIT license 6 | // https://github.com/oakmac/chessboardjs/blob/master/LICENSE.md 7 | 8 | // start anonymous scope 9 | ;(function () { 10 | 'use strict' 11 | 12 | var $ = window['jQuery'] 13 | 14 | // --------------------------------------------------------------------------- 15 | // Constants 16 | // --------------------------------------------------------------------------- 17 | 18 | var COLUMNS = 'abcdefgh'.split('') 19 | var DEFAULT_DRAG_THROTTLE_RATE = 20 20 | var ELLIPSIS = '…' 21 | var MINIMUM_JQUERY_VERSION = '1.8.3' 22 | var RUN_ASSERTS = false 23 | var START_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR' 24 | var START_POSITION = fenToObj(START_FEN) 25 | 26 | // default animation speeds 27 | var DEFAULT_APPEAR_SPEED = 200 28 | var DEFAULT_MOVE_SPEED = 200 29 | var DEFAULT_SNAPBACK_SPEED = 60 30 | var DEFAULT_SNAP_SPEED = 30 31 | var DEFAULT_TRASH_SPEED = 100 32 | 33 | // use unique class names to prevent clashing with anything else on the page 34 | // and simplify selectors 35 | // NOTE: these should never change 36 | var CSS = {} 37 | CSS['alpha'] = 'alpha-d2270' 38 | CSS['black'] = 'black-3c85d' 39 | CSS['board'] = 'board-b72b1' 40 | CSS['chessboard'] = 'chessboard-63f37' 41 | CSS['clearfix'] = 'clearfix-7da63' 42 | CSS['highlight1'] = 'highlight1-32417' 43 | CSS['highlight2'] = 'highlight2-9c5d2' 44 | CSS['notation'] = 'notation-322f9' 45 | CSS['numeric'] = 'numeric-fc462' 46 | CSS['piece'] = 'piece-417db' 47 | CSS['row'] = 'row-5277c' 48 | CSS['sparePieces'] = 'spare-pieces-7492f' 49 | CSS['sparePiecesBottom'] = 'spare-pieces-bottom-ae20f' 50 | CSS['sparePiecesTop'] = 'spare-pieces-top-4028b' 51 | CSS['square'] = 'square-55d63' 52 | CSS['white'] = 'white-1e1d7' 53 | 54 | // --------------------------------------------------------------------------- 55 | // Misc Util Functions 56 | // --------------------------------------------------------------------------- 57 | 58 | function throttle (f, interval, scope) { 59 | var timeout = 0 60 | var shouldFire = false 61 | var args = [] 62 | 63 | var handleTimeout = function () { 64 | timeout = 0 65 | if (shouldFire) { 66 | shouldFire = false 67 | fire() 68 | } 69 | } 70 | 71 | var fire = function () { 72 | timeout = window.setTimeout(handleTimeout, interval) 73 | f.apply(scope, args) 74 | } 75 | 76 | return function (_args) { 77 | args = arguments 78 | if (!timeout) { 79 | fire() 80 | } else { 81 | shouldFire = true 82 | } 83 | } 84 | } 85 | 86 | // function debounce (f, interval, scope) { 87 | // var timeout = 0 88 | // return function (_args) { 89 | // window.clearTimeout(timeout) 90 | // var args = arguments 91 | // timeout = window.setTimeout(function () { 92 | // f.apply(scope, args) 93 | // }, interval) 94 | // } 95 | // } 96 | 97 | function uuid () { 98 | return 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function (c) { 99 | var r = (Math.random() * 16) | 0 100 | return r.toString(16) 101 | }) 102 | } 103 | 104 | function deepCopy (thing) { 105 | return JSON.parse(JSON.stringify(thing)) 106 | } 107 | 108 | function parseSemVer (version) { 109 | var tmp = version.split('.') 110 | return { 111 | major: parseInt(tmp[0], 10), 112 | minor: parseInt(tmp[1], 10), 113 | patch: parseInt(tmp[2], 10) 114 | } 115 | } 116 | 117 | // returns true if version is >= minimum 118 | function validSemanticVersion (version, minimum) { 119 | version = parseSemVer(version) 120 | minimum = parseSemVer(minimum) 121 | 122 | var versionNum = (version.major * 100000 * 100000) + 123 | (version.minor * 100000) + 124 | version.patch 125 | var minimumNum = (minimum.major * 100000 * 100000) + 126 | (minimum.minor * 100000) + 127 | minimum.patch 128 | 129 | return versionNum >= minimumNum 130 | } 131 | 132 | function interpolateTemplate (str, obj) { 133 | for (var key in obj) { 134 | if (!obj.hasOwnProperty(key)) continue 135 | var keyTemplateStr = '{' + key + '}' 136 | var value = obj[key] 137 | while (str.indexOf(keyTemplateStr) !== -1) { 138 | str = str.replace(keyTemplateStr, value) 139 | } 140 | } 141 | return str 142 | } 143 | 144 | if (RUN_ASSERTS) { 145 | console.assert(interpolateTemplate('abc', {a: 'x'}) === 'abc') 146 | console.assert(interpolateTemplate('{a}bc', {}) === '{a}bc') 147 | console.assert(interpolateTemplate('{a}bc', {p: 'q'}) === '{a}bc') 148 | console.assert(interpolateTemplate('{a}bc', {a: 'x'}) === 'xbc') 149 | console.assert(interpolateTemplate('{a}bc{a}bc', {a: 'x'}) === 'xbcxbc') 150 | console.assert(interpolateTemplate('{a}{a}{b}', {a: 'x', b: 'y'}) === 'xxy') 151 | } 152 | 153 | // --------------------------------------------------------------------------- 154 | // Predicates 155 | // --------------------------------------------------------------------------- 156 | 157 | function isString (s) { 158 | return typeof s === 'string' 159 | } 160 | 161 | function isFunction (f) { 162 | return typeof f === 'function' 163 | } 164 | 165 | function isInteger (n) { 166 | return typeof n === 'number' && 167 | isFinite(n) && 168 | Math.floor(n) === n 169 | } 170 | 171 | function validAnimationSpeed (speed) { 172 | if (speed === 'fast' || speed === 'slow') return true 173 | if (!isInteger(speed)) return false 174 | return speed >= 0 175 | } 176 | 177 | function validThrottleRate (rate) { 178 | return isInteger(rate) && 179 | rate >= 1 180 | } 181 | 182 | function validMove (move) { 183 | // move should be a string 184 | if (!isString(move)) return false 185 | 186 | // move should be in the form of "e2-e4", "f6-d5" 187 | var squares = move.split('-') 188 | if (squares.length !== 2) return false 189 | 190 | return validSquare(squares[0]) && validSquare(squares[1]) 191 | } 192 | 193 | function validSquare (square) { 194 | return isString(square) && square.search(/^[a-h][1-8]$/) !== -1 195 | } 196 | 197 | if (RUN_ASSERTS) { 198 | console.assert(validSquare('a1')) 199 | console.assert(validSquare('e2')) 200 | console.assert(!validSquare('D2')) 201 | console.assert(!validSquare('g9')) 202 | console.assert(!validSquare('a')) 203 | console.assert(!validSquare(true)) 204 | console.assert(!validSquare(null)) 205 | console.assert(!validSquare({})) 206 | } 207 | 208 | function validPieceCode (code) { 209 | return isString(code) && code.search(/^[bw][KQRNBP]$/) !== -1 210 | } 211 | 212 | if (RUN_ASSERTS) { 213 | console.assert(validPieceCode('bP')) 214 | console.assert(validPieceCode('bK')) 215 | console.assert(validPieceCode('wK')) 216 | console.assert(validPieceCode('wR')) 217 | console.assert(!validPieceCode('WR')) 218 | console.assert(!validPieceCode('Wr')) 219 | console.assert(!validPieceCode('a')) 220 | console.assert(!validPieceCode(true)) 221 | console.assert(!validPieceCode(null)) 222 | console.assert(!validPieceCode({})) 223 | } 224 | 225 | function validFen (fen) { 226 | if (!isString(fen)) return false 227 | 228 | // cut off any move, castling, etc info from the end 229 | // we're only interested in position information 230 | fen = fen.replace(/ .+$/, '') 231 | 232 | // expand the empty square numbers to just 1s 233 | fen = expandFenEmptySquares(fen) 234 | 235 | // FEN should be 8 sections separated by slashes 236 | var chunks = fen.split('/') 237 | if (chunks.length !== 8) return false 238 | 239 | // check each section 240 | for (var i = 0; i < 8; i++) { 241 | if (chunks[i].length !== 8 || 242 | chunks[i].search(/[^kqrnbpKQRNBP1]/) !== -1) { 243 | return false 244 | } 245 | } 246 | 247 | return true 248 | } 249 | 250 | if (RUN_ASSERTS) { 251 | console.assert(validFen(START_FEN)) 252 | console.assert(validFen('8/8/8/8/8/8/8/8')) 253 | console.assert(validFen('r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R')) 254 | console.assert(validFen('3r3r/1p4pp/2nb1k2/pP3p2/8/PB2PN2/p4PPP/R4RK1 b - - 0 1')) 255 | console.assert(!validFen('3r3z/1p4pp/2nb1k2/pP3p2/8/PB2PN2/p4PPP/R4RK1 b - - 0 1')) 256 | console.assert(!validFen('anbqkbnr/8/8/8/8/8/PPPPPPPP/8')) 257 | console.assert(!validFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/')) 258 | console.assert(!validFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBN')) 259 | console.assert(!validFen('888888/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR')) 260 | console.assert(!validFen('888888/pppppppp/74/8/8/8/PPPPPPPP/RNBQKBNR')) 261 | console.assert(!validFen({})) 262 | } 263 | 264 | function validPositionObject (pos) { 265 | if (!$.isPlainObject(pos)) return false 266 | 267 | for (var i in pos) { 268 | if (!pos.hasOwnProperty(i)) continue 269 | 270 | if (!validSquare(i) || !validPieceCode(pos[i])) { 271 | return false 272 | } 273 | } 274 | 275 | return true 276 | } 277 | 278 | if (RUN_ASSERTS) { 279 | console.assert(validPositionObject(START_POSITION)) 280 | console.assert(validPositionObject({})) 281 | console.assert(validPositionObject({e2: 'wP'})) 282 | console.assert(validPositionObject({e2: 'wP', d2: 'wP'})) 283 | console.assert(!validPositionObject({e2: 'BP'})) 284 | console.assert(!validPositionObject({y2: 'wP'})) 285 | console.assert(!validPositionObject(null)) 286 | console.assert(!validPositionObject('start')) 287 | console.assert(!validPositionObject(START_FEN)) 288 | } 289 | 290 | function isTouchDevice () { 291 | return 'ontouchstart' in document.documentElement 292 | } 293 | 294 | function validJQueryVersion () { 295 | return typeof window.$ && 296 | $.fn && 297 | $.fn.jquery && 298 | validSemanticVersion($.fn.jquery, MINIMUM_JQUERY_VERSION) 299 | } 300 | 301 | // --------------------------------------------------------------------------- 302 | // Chess Util Functions 303 | // --------------------------------------------------------------------------- 304 | 305 | // convert FEN piece code to bP, wK, etc 306 | function fenToPieceCode (piece) { 307 | // black piece 308 | if (piece.toLowerCase() === piece) { 309 | return 'b' + piece.toUpperCase() 310 | } 311 | 312 | // white piece 313 | return 'w' + piece.toUpperCase() 314 | } 315 | 316 | // convert bP, wK, etc code to FEN structure 317 | function pieceCodeToFen (piece) { 318 | var pieceCodeLetters = piece.split('') 319 | 320 | // white piece 321 | if (pieceCodeLetters[0] === 'w') { 322 | return pieceCodeLetters[1].toUpperCase() 323 | } 324 | 325 | // black piece 326 | return pieceCodeLetters[1].toLowerCase() 327 | } 328 | 329 | // convert FEN string to position object 330 | // returns false if the FEN string is invalid 331 | function fenToObj (fen) { 332 | if (!validFen(fen)) return false 333 | 334 | // cut off any move, castling, etc info from the end 335 | // we're only interested in position information 336 | fen = fen.replace(/ .+$/, '') 337 | 338 | var rows = fen.split('/') 339 | var position = {} 340 | 341 | var currentRow = 8 342 | for (var i = 0; i < 8; i++) { 343 | var row = rows[i].split('') 344 | var colIdx = 0 345 | 346 | // loop through each character in the FEN section 347 | for (var j = 0; j < row.length; j++) { 348 | // number / empty squares 349 | if (row[j].search(/[1-8]/) !== -1) { 350 | var numEmptySquares = parseInt(row[j], 10) 351 | colIdx = colIdx + numEmptySquares 352 | } else { 353 | // piece 354 | var square = COLUMNS[colIdx] + currentRow 355 | position[square] = fenToPieceCode(row[j]) 356 | colIdx = colIdx + 1 357 | } 358 | } 359 | 360 | currentRow = currentRow - 1 361 | } 362 | 363 | return position 364 | } 365 | 366 | // position object to FEN string 367 | // returns false if the obj is not a valid position object 368 | function objToFen (obj) { 369 | if (!validPositionObject(obj)) return false 370 | 371 | var fen = '' 372 | 373 | var currentRow = 8 374 | for (var i = 0; i < 8; i++) { 375 | for (var j = 0; j < 8; j++) { 376 | var square = COLUMNS[j] + currentRow 377 | 378 | // piece exists 379 | if (obj.hasOwnProperty(square)) { 380 | fen = fen + pieceCodeToFen(obj[square]) 381 | } else { 382 | // empty space 383 | fen = fen + '1' 384 | } 385 | } 386 | 387 | if (i !== 7) { 388 | fen = fen + '/' 389 | } 390 | 391 | currentRow = currentRow - 1 392 | } 393 | 394 | // squeeze the empty numbers together 395 | fen = squeezeFenEmptySquares(fen) 396 | 397 | return fen 398 | } 399 | 400 | if (RUN_ASSERTS) { 401 | console.assert(objToFen(START_POSITION) === START_FEN) 402 | console.assert(objToFen({}) === '8/8/8/8/8/8/8/8') 403 | console.assert(objToFen({a2: 'wP', 'b2': 'bP'}) === '8/8/8/8/8/8/Pp6/8') 404 | } 405 | 406 | function squeezeFenEmptySquares (fen) { 407 | return fen.replace(/11111111/g, '8') 408 | .replace(/1111111/g, '7') 409 | .replace(/111111/g, '6') 410 | .replace(/11111/g, '5') 411 | .replace(/1111/g, '4') 412 | .replace(/111/g, '3') 413 | .replace(/11/g, '2') 414 | } 415 | 416 | function expandFenEmptySquares (fen) { 417 | return fen.replace(/8/g, '11111111') 418 | .replace(/7/g, '1111111') 419 | .replace(/6/g, '111111') 420 | .replace(/5/g, '11111') 421 | .replace(/4/g, '1111') 422 | .replace(/3/g, '111') 423 | .replace(/2/g, '11') 424 | } 425 | 426 | // returns the distance between two squares 427 | function squareDistance (squareA, squareB) { 428 | var squareAArray = squareA.split('') 429 | var squareAx = COLUMNS.indexOf(squareAArray[0]) + 1 430 | var squareAy = parseInt(squareAArray[1], 10) 431 | 432 | var squareBArray = squareB.split('') 433 | var squareBx = COLUMNS.indexOf(squareBArray[0]) + 1 434 | var squareBy = parseInt(squareBArray[1], 10) 435 | 436 | var xDelta = Math.abs(squareAx - squareBx) 437 | var yDelta = Math.abs(squareAy - squareBy) 438 | 439 | if (xDelta >= yDelta) return xDelta 440 | return yDelta 441 | } 442 | 443 | // returns the square of the closest instance of piece 444 | // returns false if no instance of piece is found in position 445 | function findClosestPiece (position, piece, square) { 446 | // create array of closest squares from square 447 | var closestSquares = createRadius(square) 448 | 449 | // search through the position in order of distance for the piece 450 | for (var i = 0; i < closestSquares.length; i++) { 451 | var s = closestSquares[i] 452 | 453 | if (position.hasOwnProperty(s) && position[s] === piece) { 454 | return s 455 | } 456 | } 457 | 458 | return false 459 | } 460 | 461 | // returns an array of closest squares from square 462 | function createRadius (square) { 463 | var squares = [] 464 | 465 | // calculate distance of all squares 466 | for (var i = 0; i < 8; i++) { 467 | for (var j = 0; j < 8; j++) { 468 | var s = COLUMNS[i] + (j + 1) 469 | 470 | // skip the square we're starting from 471 | if (square === s) continue 472 | 473 | squares.push({ 474 | square: s, 475 | distance: squareDistance(square, s) 476 | }) 477 | } 478 | } 479 | 480 | // sort by distance 481 | squares.sort(function (a, b) { 482 | return a.distance - b.distance 483 | }) 484 | 485 | // just return the square code 486 | var surroundingSquares = [] 487 | for (i = 0; i < squares.length; i++) { 488 | surroundingSquares.push(squares[i].square) 489 | } 490 | 491 | return surroundingSquares 492 | } 493 | 494 | // given a position and a set of moves, return a new position 495 | // with the moves executed 496 | function calculatePositionFromMoves (position, moves) { 497 | var newPosition = deepCopy(position) 498 | 499 | for (var i in moves) { 500 | if (!moves.hasOwnProperty(i)) continue 501 | 502 | // skip the move if the position doesn't have a piece on the source square 503 | if (!newPosition.hasOwnProperty(i)) continue 504 | 505 | var piece = newPosition[i] 506 | delete newPosition[i] 507 | newPosition[moves[i]] = piece 508 | } 509 | 510 | return newPosition 511 | } 512 | 513 | // TODO: add some asserts here for calculatePositionFromMoves 514 | 515 | // --------------------------------------------------------------------------- 516 | // HTML 517 | // --------------------------------------------------------------------------- 518 | 519 | function buildContainerHTML (hasSparePieces) { 520 | var html = '
' 521 | 522 | if (hasSparePieces) { 523 | html += '
' 524 | } 525 | 526 | html += '
' 527 | 528 | if (hasSparePieces) { 529 | html += '
' 530 | } 531 | 532 | html += '
' 533 | 534 | return interpolateTemplate(html, CSS) 535 | } 536 | 537 | // --------------------------------------------------------------------------- 538 | // Config 539 | // --------------------------------------------------------------------------- 540 | 541 | function expandConfigArgumentShorthand (config) { 542 | if (config === 'start') { 543 | config = {position: deepCopy(START_POSITION)} 544 | } else if (validFen(config)) { 545 | config = {position: fenToObj(config)} 546 | } else if (validPositionObject(config)) { 547 | config = {position: deepCopy(config)} 548 | } 549 | 550 | // config must be an object 551 | if (!$.isPlainObject(config)) config = {} 552 | 553 | return config 554 | } 555 | 556 | // validate config / set default options 557 | function expandConfig (config) { 558 | // default for orientation is white 559 | if (config.orientation !== 'black') config.orientation = 'white' 560 | 561 | // default for showNotation is true 562 | if (config.showNotation !== false) config.showNotation = true 563 | 564 | // default for draggable is false 565 | if (config.draggable !== true) config.draggable = false 566 | 567 | // default for dropOffBoard is 'snapback' 568 | if (config.dropOffBoard !== 'trash') config.dropOffBoard = 'snapback' 569 | 570 | // default for sparePieces is false 571 | if (config.sparePieces !== true) config.sparePieces = false 572 | 573 | // draggable must be true if sparePieces is enabled 574 | if (config.sparePieces) config.draggable = true 575 | 576 | // default piece theme is wikipedia 577 | if (!config.hasOwnProperty('pieceTheme') || 578 | (!isString(config.pieceTheme) && !isFunction(config.pieceTheme))) { 579 | config.pieceTheme = 'img/chesspieces/wikipedia/{piece}.png' 580 | } 581 | 582 | // animation speeds 583 | if (!validAnimationSpeed(config.appearSpeed)) config.appearSpeed = DEFAULT_APPEAR_SPEED 584 | if (!validAnimationSpeed(config.moveSpeed)) config.moveSpeed = DEFAULT_MOVE_SPEED 585 | if (!validAnimationSpeed(config.snapbackSpeed)) config.snapbackSpeed = DEFAULT_SNAPBACK_SPEED 586 | if (!validAnimationSpeed(config.snapSpeed)) config.snapSpeed = DEFAULT_SNAP_SPEED 587 | if (!validAnimationSpeed(config.trashSpeed)) config.trashSpeed = DEFAULT_TRASH_SPEED 588 | 589 | // throttle rate 590 | if (!validThrottleRate(config.dragThrottleRate)) config.dragThrottleRate = DEFAULT_DRAG_THROTTLE_RATE 591 | 592 | return config 593 | } 594 | 595 | // --------------------------------------------------------------------------- 596 | // Dependencies 597 | // --------------------------------------------------------------------------- 598 | 599 | // check for a compatible version of jQuery 600 | function checkJQuery () { 601 | if (!validJQueryVersion()) { 602 | var errorMsg = 'Chessboard Error 1005: Unable to find a valid version of jQuery. ' + 603 | 'Please include jQuery ' + MINIMUM_JQUERY_VERSION + ' or higher on the page' + 604 | '\n\n' + 605 | 'Exiting' + ELLIPSIS 606 | window.alert(errorMsg) 607 | return false 608 | } 609 | 610 | return true 611 | } 612 | 613 | // return either boolean false or the $container element 614 | function checkContainerArg (containerElOrString) { 615 | if (containerElOrString === '') { 616 | var errorMsg1 = 'Chessboard Error 1001: ' + 617 | 'The first argument to Chessboard() cannot be an empty string.' + 618 | '\n\n' + 619 | 'Exiting' + ELLIPSIS 620 | window.alert(errorMsg1) 621 | return false 622 | } 623 | 624 | // convert containerEl to query selector if it is a string 625 | if (isString(containerElOrString) && 626 | containerElOrString.charAt(0) !== '#') { 627 | containerElOrString = '#' + containerElOrString 628 | } 629 | 630 | // containerEl must be something that becomes a jQuery collection of size 1 631 | var $container = $(containerElOrString) 632 | if ($container.length !== 1) { 633 | var errorMsg2 = 'Chessboard Error 1003: ' + 634 | 'The first argument to Chessboard() must be the ID of a DOM node, ' + 635 | 'an ID query selector, or a single DOM node.' + 636 | '\n\n' + 637 | 'Exiting' + ELLIPSIS 638 | window.alert(errorMsg2) 639 | return false 640 | } 641 | 642 | return $container 643 | } 644 | 645 | // --------------------------------------------------------------------------- 646 | // Constructor 647 | // --------------------------------------------------------------------------- 648 | 649 | function constructor (containerElOrString, config) { 650 | // first things first: check basic dependencies 651 | if (!checkJQuery()) return null 652 | var $container = checkContainerArg(containerElOrString) 653 | if (!$container) return null 654 | 655 | // ensure the config object is what we expect 656 | config = expandConfigArgumentShorthand(config) 657 | config = expandConfig(config) 658 | 659 | // DOM elements 660 | var $board = null 661 | var $draggedPiece = null 662 | var $sparePiecesTop = null 663 | var $sparePiecesBottom = null 664 | 665 | // constructor return object 666 | var widget = {} 667 | 668 | // ------------------------------------------------------------------------- 669 | // Stateful 670 | // ------------------------------------------------------------------------- 671 | 672 | var boardBorderSize = 2 673 | var currentOrientation = 'white' 674 | var currentPosition = {} 675 | var draggedPiece = null 676 | var draggedPieceLocation = null 677 | var draggedPieceSource = null 678 | var isDragging = false 679 | var sparePiecesElsIds = {} 680 | var squareElsIds = {} 681 | var squareElsOffsets = {} 682 | var squareSize = 16 683 | 684 | // ------------------------------------------------------------------------- 685 | // Validation / Errors 686 | // ------------------------------------------------------------------------- 687 | 688 | function error (code, msg, obj) { 689 | // do nothing if showErrors is not set 690 | if ( 691 | config.hasOwnProperty('showErrors') !== true || 692 | config.showErrors === false 693 | ) { 694 | return 695 | } 696 | 697 | var errorText = 'Chessboard Error ' + code + ': ' + msg 698 | 699 | // print to console 700 | if ( 701 | config.showErrors === 'console' && 702 | typeof console === 'object' && 703 | typeof console.log === 'function' 704 | ) { 705 | console.log(errorText) 706 | if (arguments.length >= 2) { 707 | console.log(obj) 708 | } 709 | return 710 | } 711 | 712 | // alert errors 713 | if (config.showErrors === 'alert') { 714 | if (obj) { 715 | errorText += '\n\n' + JSON.stringify(obj) 716 | } 717 | window.alert(errorText) 718 | return 719 | } 720 | 721 | // custom function 722 | if (isFunction(config.showErrors)) { 723 | config.showErrors(code, msg, obj) 724 | } 725 | } 726 | 727 | function setInitialState () { 728 | currentOrientation = config.orientation 729 | 730 | // make sure position is valid 731 | if (config.hasOwnProperty('position')) { 732 | if (config.position === 'start') { 733 | currentPosition = deepCopy(START_POSITION) 734 | } else if (validFen(config.position)) { 735 | currentPosition = fenToObj(config.position) 736 | } else if (validPositionObject(config.position)) { 737 | currentPosition = deepCopy(config.position) 738 | } else { 739 | error( 740 | 7263, 741 | 'Invalid value passed to config.position.', 742 | config.position 743 | ) 744 | } 745 | } 746 | } 747 | 748 | // ------------------------------------------------------------------------- 749 | // DOM Misc 750 | // ------------------------------------------------------------------------- 751 | 752 | // calculates square size based on the width of the container 753 | // got a little CSS black magic here, so let me explain: 754 | // get the width of the container element (could be anything), reduce by 1 for 755 | // fudge factor, and then keep reducing until we find an exact mod 8 for 756 | // our square size 757 | function calculateSquareSize () { 758 | var containerWidth = parseInt($container.width(), 10) 759 | 760 | // defensive, prevent infinite loop 761 | if (!containerWidth || containerWidth <= 0) { 762 | return 0 763 | } 764 | 765 | // pad one pixel 766 | var boardWidth = containerWidth - 1 767 | 768 | while (boardWidth % 8 !== 0 && boardWidth > 0) { 769 | boardWidth = boardWidth - 1 770 | } 771 | 772 | return boardWidth / 8 773 | } 774 | 775 | // create random IDs for elements 776 | function createElIds () { 777 | // squares on the board 778 | for (var i = 0; i < COLUMNS.length; i++) { 779 | for (var j = 1; j <= 8; j++) { 780 | var square = COLUMNS[i] + j 781 | squareElsIds[square] = square + '-' + uuid() 782 | } 783 | } 784 | 785 | // spare pieces 786 | var pieces = 'KQRNBP'.split('') 787 | for (i = 0; i < pieces.length; i++) { 788 | var whitePiece = 'w' + pieces[i] 789 | var blackPiece = 'b' + pieces[i] 790 | sparePiecesElsIds[whitePiece] = whitePiece + '-' + uuid() 791 | sparePiecesElsIds[blackPiece] = blackPiece + '-' + uuid() 792 | } 793 | } 794 | 795 | // ------------------------------------------------------------------------- 796 | // Markup Building 797 | // ------------------------------------------------------------------------- 798 | 799 | function buildBoardHTML (orientation) { 800 | if (orientation !== 'black') { 801 | orientation = 'white' 802 | } 803 | 804 | var html = '' 805 | 806 | // algebraic notation / orientation 807 | var alpha = deepCopy(COLUMNS) 808 | var row = 8 809 | if (orientation === 'black') { 810 | alpha.reverse() 811 | row = 1 812 | } 813 | 814 | var squareColor = 'white' 815 | for (var i = 0; i < 8; i++) { 816 | html += '
' 817 | for (var j = 0; j < 8; j++) { 818 | var square = alpha[j] + row 819 | 820 | html += '
' 825 | 826 | if (config.showNotation) { 827 | // alpha notation 828 | if ((orientation === 'white' && row === 1) || 829 | (orientation === 'black' && row === 8)) { 830 | html += '
' + alpha[j] + '
' 831 | } 832 | 833 | // numeric notation 834 | if (j === 0) { 835 | html += '
' + row + '
' 836 | } 837 | } 838 | 839 | html += '
' // end .square 840 | 841 | squareColor = (squareColor === 'white') ? 'black' : 'white' 842 | } 843 | html += '
' 844 | 845 | squareColor = (squareColor === 'white') ? 'black' : 'white' 846 | 847 | if (orientation === 'white') { 848 | row = row - 1 849 | } else { 850 | row = row + 1 851 | } 852 | } 853 | 854 | return interpolateTemplate(html, CSS) 855 | } 856 | 857 | function buildPieceImgSrc (piece) { 858 | if (isFunction(config.pieceTheme)) { 859 | return config.pieceTheme(piece) 860 | } 861 | 862 | if (isString(config.pieceTheme)) { 863 | return interpolateTemplate(config.pieceTheme, {piece: piece}) 864 | } 865 | 866 | // NOTE: this should never happen 867 | error(8272, 'Unable to build image source for config.pieceTheme.') 868 | return '' 869 | } 870 | 871 | function buildPieceHTML (piece, hidden, id) { 872 | var html = '' 886 | 887 | return interpolateTemplate(html, CSS) 888 | } 889 | 890 | function buildSparePiecesHTML (color) { 891 | var pieces = ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP'] 892 | if (color === 'black') { 893 | pieces = ['bK', 'bQ', 'bR', 'bB', 'bN', 'bP'] 894 | } 895 | 896 | var html = '' 897 | for (var i = 0; i < pieces.length; i++) { 898 | html += buildPieceHTML(pieces[i], false, sparePiecesElsIds[pieces[i]]) 899 | } 900 | 901 | return html 902 | } 903 | 904 | // ------------------------------------------------------------------------- 905 | // Animations 906 | // ------------------------------------------------------------------------- 907 | 908 | function animateSquareToSquare (src, dest, piece, completeFn) { 909 | // get information about the source and destination squares 910 | var $srcSquare = $('#' + squareElsIds[src]) 911 | var srcSquarePosition = $srcSquare.offset() 912 | var $destSquare = $('#' + squareElsIds[dest]) 913 | var destSquarePosition = $destSquare.offset() 914 | 915 | // create the animated piece and absolutely position it 916 | // over the source square 917 | var animatedPieceId = uuid() 918 | $('body').append(buildPieceHTML(piece, true, animatedPieceId)) 919 | var $animatedPiece = $('#' + animatedPieceId) 920 | $animatedPiece.css({ 921 | display: '', 922 | position: 'absolute', 923 | top: srcSquarePosition.top, 924 | left: srcSquarePosition.left 925 | }) 926 | 927 | // remove original piece from source square 928 | $srcSquare.find('.' + CSS.piece).remove() 929 | 930 | function onFinishAnimation1 () { 931 | // add the "real" piece to the destination square 932 | $destSquare.append(buildPieceHTML(piece)) 933 | 934 | // remove the animated piece 935 | $animatedPiece.remove() 936 | 937 | // run complete function 938 | if (isFunction(completeFn)) { 939 | completeFn() 940 | } 941 | } 942 | 943 | // animate the piece to the destination square 944 | var opts = { 945 | duration: config.moveSpeed, 946 | complete: onFinishAnimation1 947 | } 948 | $animatedPiece.animate(destSquarePosition, opts) 949 | } 950 | 951 | function animateSparePieceToSquare (piece, dest, completeFn) { 952 | var srcOffset = $('#' + sparePiecesElsIds[piece]).offset() 953 | var $destSquare = $('#' + squareElsIds[dest]) 954 | var destOffset = $destSquare.offset() 955 | 956 | // create the animate piece 957 | var pieceId = uuid() 958 | $('body').append(buildPieceHTML(piece, true, pieceId)) 959 | var $animatedPiece = $('#' + pieceId) 960 | $animatedPiece.css({ 961 | display: '', 962 | position: 'absolute', 963 | left: srcOffset.left, 964 | top: srcOffset.top 965 | }) 966 | 967 | // on complete 968 | function onFinishAnimation2 () { 969 | // add the "real" piece to the destination square 970 | $destSquare.find('.' + CSS.piece).remove() 971 | $destSquare.append(buildPieceHTML(piece)) 972 | 973 | // remove the animated piece 974 | $animatedPiece.remove() 975 | 976 | // run complete function 977 | if (isFunction(completeFn)) { 978 | completeFn() 979 | } 980 | } 981 | 982 | // animate the piece to the destination square 983 | var opts = { 984 | duration: config.moveSpeed, 985 | complete: onFinishAnimation2 986 | } 987 | $animatedPiece.animate(destOffset, opts) 988 | } 989 | 990 | // execute an array of animations 991 | function doAnimations (animations, oldPos, newPos) { 992 | if (animations.length === 0) return 993 | 994 | var numFinished = 0 995 | function onFinishAnimation3 () { 996 | // exit if all the animations aren't finished 997 | numFinished = numFinished + 1 998 | if (numFinished !== animations.length) return 999 | 1000 | drawPositionInstant() 1001 | 1002 | // run their onMoveEnd function 1003 | if (isFunction(config.onMoveEnd)) { 1004 | config.onMoveEnd(deepCopy(oldPos), deepCopy(newPos)) 1005 | } 1006 | } 1007 | 1008 | for (var i = 0; i < animations.length; i++) { 1009 | var animation = animations[i] 1010 | 1011 | // clear a piece 1012 | if (animation.type === 'clear') { 1013 | $('#' + squareElsIds[animation.square] + ' .' + CSS.piece) 1014 | .fadeOut(config.trashSpeed, onFinishAnimation3) 1015 | 1016 | // add a piece with no spare pieces - fade the piece onto the square 1017 | } else if (animation.type === 'add' && !config.sparePieces) { 1018 | $('#' + squareElsIds[animation.square]) 1019 | .append(buildPieceHTML(animation.piece, true)) 1020 | .find('.' + CSS.piece) 1021 | .fadeIn(config.appearSpeed, onFinishAnimation3) 1022 | 1023 | // add a piece with spare pieces - animate from the spares 1024 | } else if (animation.type === 'add' && config.sparePieces) { 1025 | animateSparePieceToSquare(animation.piece, animation.square, onFinishAnimation3) 1026 | 1027 | // move a piece from squareA to squareB 1028 | } else if (animation.type === 'move') { 1029 | animateSquareToSquare(animation.source, animation.destination, animation.piece, onFinishAnimation3) 1030 | } 1031 | } 1032 | } 1033 | 1034 | // calculate an array of animations that need to happen in order to get 1035 | // from pos1 to pos2 1036 | function calculateAnimations (pos1, pos2) { 1037 | // make copies of both 1038 | pos1 = deepCopy(pos1) 1039 | pos2 = deepCopy(pos2) 1040 | 1041 | var animations = [] 1042 | var squaresMovedTo = {} 1043 | 1044 | // remove pieces that are the same in both positions 1045 | for (var i in pos2) { 1046 | if (!pos2.hasOwnProperty(i)) continue 1047 | 1048 | if (pos1.hasOwnProperty(i) && pos1[i] === pos2[i]) { 1049 | delete pos1[i] 1050 | delete pos2[i] 1051 | } 1052 | } 1053 | 1054 | // find all the "move" animations 1055 | for (i in pos2) { 1056 | if (!pos2.hasOwnProperty(i)) continue 1057 | 1058 | var closestPiece = findClosestPiece(pos1, pos2[i], i) 1059 | if (closestPiece) { 1060 | animations.push({ 1061 | type: 'move', 1062 | source: closestPiece, 1063 | destination: i, 1064 | piece: pos2[i] 1065 | }) 1066 | 1067 | delete pos1[closestPiece] 1068 | delete pos2[i] 1069 | squaresMovedTo[i] = true 1070 | } 1071 | } 1072 | 1073 | // "add" animations 1074 | for (i in pos2) { 1075 | if (!pos2.hasOwnProperty(i)) continue 1076 | 1077 | animations.push({ 1078 | type: 'add', 1079 | square: i, 1080 | piece: pos2[i] 1081 | }) 1082 | 1083 | delete pos2[i] 1084 | } 1085 | 1086 | // "clear" animations 1087 | for (i in pos1) { 1088 | if (!pos1.hasOwnProperty(i)) continue 1089 | 1090 | // do not clear a piece if it is on a square that is the result 1091 | // of a "move", ie: a piece capture 1092 | if (squaresMovedTo.hasOwnProperty(i)) continue 1093 | 1094 | animations.push({ 1095 | type: 'clear', 1096 | square: i, 1097 | piece: pos1[i] 1098 | }) 1099 | 1100 | delete pos1[i] 1101 | } 1102 | 1103 | return animations 1104 | } 1105 | 1106 | // ------------------------------------------------------------------------- 1107 | // Control Flow 1108 | // ------------------------------------------------------------------------- 1109 | 1110 | function drawPositionInstant () { 1111 | // clear the board 1112 | $board.find('.' + CSS.piece).remove() 1113 | 1114 | // add the pieces 1115 | for (var i in currentPosition) { 1116 | if (!currentPosition.hasOwnProperty(i)) continue 1117 | 1118 | $('#' + squareElsIds[i]).append(buildPieceHTML(currentPosition[i])) 1119 | } 1120 | } 1121 | 1122 | function drawBoard () { 1123 | $board.html(buildBoardHTML(currentOrientation, squareSize, config.showNotation)) 1124 | drawPositionInstant() 1125 | 1126 | if (config.sparePieces) { 1127 | if (currentOrientation === 'white') { 1128 | $sparePiecesTop.html(buildSparePiecesHTML('black')) 1129 | $sparePiecesBottom.html(buildSparePiecesHTML('white')) 1130 | } else { 1131 | $sparePiecesTop.html(buildSparePiecesHTML('white')) 1132 | $sparePiecesBottom.html(buildSparePiecesHTML('black')) 1133 | } 1134 | } 1135 | } 1136 | 1137 | function setCurrentPosition (position) { 1138 | var oldPos = deepCopy(currentPosition) 1139 | var newPos = deepCopy(position) 1140 | var oldFen = objToFen(oldPos) 1141 | var newFen = objToFen(newPos) 1142 | 1143 | // do nothing if no change in position 1144 | if (oldFen === newFen) return 1145 | 1146 | // run their onChange function 1147 | if (isFunction(config.onChange)) { 1148 | config.onChange(oldPos, newPos) 1149 | } 1150 | 1151 | // update state 1152 | currentPosition = position 1153 | } 1154 | 1155 | function isXYOnSquare (x, y) { 1156 | for (var i in squareElsOffsets) { 1157 | if (!squareElsOffsets.hasOwnProperty(i)) continue 1158 | 1159 | var s = squareElsOffsets[i] 1160 | if (x >= s.left && 1161 | x < s.left + squareSize && 1162 | y >= s.top && 1163 | y < s.top + squareSize) { 1164 | return i 1165 | } 1166 | } 1167 | 1168 | return 'offboard' 1169 | } 1170 | 1171 | // records the XY coords of every square into memory 1172 | function captureSquareOffsets () { 1173 | squareElsOffsets = {} 1174 | 1175 | for (var i in squareElsIds) { 1176 | if (!squareElsIds.hasOwnProperty(i)) continue 1177 | 1178 | squareElsOffsets[i] = $('#' + squareElsIds[i]).offset() 1179 | } 1180 | } 1181 | 1182 | function removeSquareHighlights () { 1183 | $board 1184 | .find('.' + CSS.square) 1185 | .removeClass(CSS.highlight1 + ' ' + CSS.highlight2) 1186 | } 1187 | 1188 | function snapbackDraggedPiece () { 1189 | // there is no "snapback" for spare pieces 1190 | if (draggedPieceSource === 'spare') { 1191 | trashDraggedPiece() 1192 | return 1193 | } 1194 | 1195 | removeSquareHighlights() 1196 | 1197 | // animation complete 1198 | function complete () { 1199 | drawPositionInstant() 1200 | $draggedPiece.css('display', 'none') 1201 | 1202 | // run their onSnapbackEnd function 1203 | if (isFunction(config.onSnapbackEnd)) { 1204 | config.onSnapbackEnd( 1205 | draggedPiece, 1206 | draggedPieceSource, 1207 | deepCopy(currentPosition), 1208 | currentOrientation 1209 | ) 1210 | } 1211 | } 1212 | 1213 | // get source square position 1214 | var sourceSquarePosition = $('#' + squareElsIds[draggedPieceSource]).offset() 1215 | 1216 | // animate the piece to the target square 1217 | var opts = { 1218 | duration: config.snapbackSpeed, 1219 | complete: complete 1220 | } 1221 | $draggedPiece.animate(sourceSquarePosition, opts) 1222 | 1223 | // set state 1224 | isDragging = false 1225 | } 1226 | 1227 | function trashDraggedPiece () { 1228 | removeSquareHighlights() 1229 | 1230 | // remove the source piece 1231 | var newPosition = deepCopy(currentPosition) 1232 | delete newPosition[draggedPieceSource] 1233 | setCurrentPosition(newPosition) 1234 | 1235 | // redraw the position 1236 | drawPositionInstant() 1237 | 1238 | // hide the dragged piece 1239 | $draggedPiece.fadeOut(config.trashSpeed) 1240 | 1241 | // set state 1242 | isDragging = false 1243 | } 1244 | 1245 | function dropDraggedPieceOnSquare (square) { 1246 | removeSquareHighlights() 1247 | 1248 | // update position 1249 | var newPosition = deepCopy(currentPosition) 1250 | delete newPosition[draggedPieceSource] 1251 | newPosition[square] = draggedPiece 1252 | setCurrentPosition(newPosition) 1253 | 1254 | // get target square information 1255 | var targetSquarePosition = $('#' + squareElsIds[square]).offset() 1256 | 1257 | // animation complete 1258 | function onAnimationComplete () { 1259 | drawPositionInstant() 1260 | $draggedPiece.css('display', 'none') 1261 | 1262 | // execute their onSnapEnd function 1263 | if (isFunction(config.onSnapEnd)) { 1264 | config.onSnapEnd(draggedPieceSource, square, draggedPiece) 1265 | } 1266 | } 1267 | 1268 | // snap the piece to the target square 1269 | var opts = { 1270 | duration: config.snapSpeed, 1271 | complete: onAnimationComplete 1272 | } 1273 | $draggedPiece.animate(targetSquarePosition, opts) 1274 | 1275 | // set state 1276 | isDragging = false 1277 | } 1278 | 1279 | function beginDraggingPiece (source, piece, x, y) { 1280 | // run their custom onDragStart function 1281 | // their custom onDragStart function can cancel drag start 1282 | if (isFunction(config.onDragStart) && 1283 | config.onDragStart(source, piece, deepCopy(currentPosition), currentOrientation) === false) { 1284 | return 1285 | } 1286 | 1287 | // set state 1288 | isDragging = true 1289 | draggedPiece = piece 1290 | draggedPieceSource = source 1291 | 1292 | // if the piece came from spare pieces, location is offboard 1293 | if (source === 'spare') { 1294 | draggedPieceLocation = 'offboard' 1295 | } else { 1296 | draggedPieceLocation = source 1297 | } 1298 | 1299 | // capture the x, y coords of all squares in memory 1300 | captureSquareOffsets() 1301 | 1302 | // create the dragged piece 1303 | $draggedPiece.attr('src', buildPieceImgSrc(piece)).css({ 1304 | display: '', 1305 | position: 'absolute', 1306 | left: x - squareSize / 2, 1307 | top: y - squareSize / 2 1308 | }) 1309 | 1310 | if (source !== 'spare') { 1311 | // highlight the source square and hide the piece 1312 | $('#' + squareElsIds[source]) 1313 | .addClass(CSS.highlight1) 1314 | .find('.' + CSS.piece) 1315 | .css('display', 'none') 1316 | } 1317 | } 1318 | 1319 | function updateDraggedPiece (x, y) { 1320 | // put the dragged piece over the mouse cursor 1321 | $draggedPiece.css({ 1322 | left: x - squareSize / 2, 1323 | top: y - squareSize / 2 1324 | }) 1325 | 1326 | // get location 1327 | var location = isXYOnSquare(x, y) 1328 | 1329 | // do nothing if the location has not changed 1330 | if (location === draggedPieceLocation) return 1331 | 1332 | // remove highlight from previous square 1333 | if (validSquare(draggedPieceLocation)) { 1334 | $('#' + squareElsIds[draggedPieceLocation]).removeClass(CSS.highlight2) 1335 | } 1336 | 1337 | // add highlight to new square 1338 | if (validSquare(location)) { 1339 | $('#' + squareElsIds[location]).addClass(CSS.highlight2) 1340 | } 1341 | 1342 | // run onDragMove 1343 | if (isFunction(config.onDragMove)) { 1344 | config.onDragMove( 1345 | location, 1346 | draggedPieceLocation, 1347 | draggedPieceSource, 1348 | draggedPiece, 1349 | deepCopy(currentPosition), 1350 | currentOrientation 1351 | ) 1352 | } 1353 | 1354 | // update state 1355 | draggedPieceLocation = location 1356 | } 1357 | 1358 | function stopDraggedPiece (location) { 1359 | // determine what the action should be 1360 | var action = 'drop' 1361 | if (location === 'offboard' && config.dropOffBoard === 'snapback') { 1362 | action = 'snapback' 1363 | } 1364 | if (location === 'offboard' && config.dropOffBoard === 'trash') { 1365 | action = 'trash' 1366 | } 1367 | 1368 | // run their onDrop function, which can potentially change the drop action 1369 | if (isFunction(config.onDrop)) { 1370 | var newPosition = deepCopy(currentPosition) 1371 | 1372 | // source piece is a spare piece and position is off the board 1373 | // if (draggedPieceSource === 'spare' && location === 'offboard') {...} 1374 | // position has not changed; do nothing 1375 | 1376 | // source piece is a spare piece and position is on the board 1377 | if (draggedPieceSource === 'spare' && validSquare(location)) { 1378 | // add the piece to the board 1379 | newPosition[location] = draggedPiece 1380 | } 1381 | 1382 | // source piece was on the board and position is off the board 1383 | if (validSquare(draggedPieceSource) && location === 'offboard') { 1384 | // remove the piece from the board 1385 | delete newPosition[draggedPieceSource] 1386 | } 1387 | 1388 | // source piece was on the board and position is on the board 1389 | if (validSquare(draggedPieceSource) && validSquare(location)) { 1390 | // move the piece 1391 | delete newPosition[draggedPieceSource] 1392 | newPosition[location] = draggedPiece 1393 | } 1394 | 1395 | var oldPosition = deepCopy(currentPosition) 1396 | 1397 | var result = config.onDrop( 1398 | draggedPieceSource, 1399 | location, 1400 | draggedPiece, 1401 | newPosition, 1402 | oldPosition, 1403 | currentOrientation 1404 | ) 1405 | if (result === 'snapback' || result === 'trash') { 1406 | action = result 1407 | } 1408 | } 1409 | 1410 | // do it! 1411 | if (action === 'snapback') { 1412 | snapbackDraggedPiece() 1413 | } else if (action === 'trash') { 1414 | trashDraggedPiece() 1415 | } else if (action === 'drop') { 1416 | dropDraggedPieceOnSquare(location) 1417 | } 1418 | } 1419 | 1420 | // ------------------------------------------------------------------------- 1421 | // Public Methods 1422 | // ------------------------------------------------------------------------- 1423 | 1424 | // clear the board 1425 | widget.clear = function (useAnimation) { 1426 | widget.position({}, useAnimation) 1427 | } 1428 | 1429 | // remove the widget from the page 1430 | widget.destroy = function () { 1431 | // remove markup 1432 | $container.html('') 1433 | $draggedPiece.remove() 1434 | 1435 | // remove event handlers 1436 | $container.unbind() 1437 | } 1438 | 1439 | // shorthand method to get the current FEN 1440 | widget.fen = function () { 1441 | return widget.position('fen') 1442 | } 1443 | 1444 | // flip orientation 1445 | widget.flip = function () { 1446 | return widget.orientation('flip') 1447 | } 1448 | 1449 | // move pieces 1450 | // TODO: this method should be variadic as well as accept an array of moves 1451 | widget.move = function () { 1452 | // no need to throw an error here; just do nothing 1453 | // TODO: this should return the current position 1454 | if (arguments.length === 0) return 1455 | 1456 | var useAnimation = true 1457 | 1458 | // collect the moves into an object 1459 | var moves = {} 1460 | for (var i = 0; i < arguments.length; i++) { 1461 | // any "false" to this function means no animations 1462 | if (arguments[i] === false) { 1463 | useAnimation = false 1464 | continue 1465 | } 1466 | 1467 | // skip invalid arguments 1468 | if (!validMove(arguments[i])) { 1469 | error(2826, 'Invalid move passed to the move method.', arguments[i]) 1470 | continue 1471 | } 1472 | 1473 | var tmp = arguments[i].split('-') 1474 | moves[tmp[0]] = tmp[1] 1475 | } 1476 | 1477 | // calculate position from moves 1478 | var newPos = calculatePositionFromMoves(currentPosition, moves) 1479 | 1480 | // update the board 1481 | widget.position(newPos, useAnimation) 1482 | 1483 | // return the new position object 1484 | return newPos 1485 | } 1486 | 1487 | widget.orientation = function (arg) { 1488 | // no arguments, return the current orientation 1489 | if (arguments.length === 0) { 1490 | return currentOrientation 1491 | } 1492 | 1493 | // set to white or black 1494 | if (arg === 'white' || arg === 'black') { 1495 | currentOrientation = arg 1496 | drawBoard() 1497 | return currentOrientation 1498 | } 1499 | 1500 | // flip orientation 1501 | if (arg === 'flip') { 1502 | currentOrientation = currentOrientation === 'white' ? 'black' : 'white' 1503 | drawBoard() 1504 | return currentOrientation 1505 | } 1506 | 1507 | error(5482, 'Invalid value passed to the orientation method.', arg) 1508 | } 1509 | 1510 | widget.position = function (position, useAnimation) { 1511 | // no arguments, return the current position 1512 | if (arguments.length === 0) { 1513 | return deepCopy(currentPosition) 1514 | } 1515 | 1516 | // get position as FEN 1517 | if (isString(position) && position.toLowerCase() === 'fen') { 1518 | return objToFen(currentPosition) 1519 | } 1520 | 1521 | // start position 1522 | if (isString(position) && position.toLowerCase() === 'start') { 1523 | position = deepCopy(START_POSITION) 1524 | } 1525 | 1526 | // convert FEN to position object 1527 | if (validFen(position)) { 1528 | position = fenToObj(position) 1529 | } 1530 | 1531 | // validate position object 1532 | if (!validPositionObject(position)) { 1533 | error(6482, 'Invalid value passed to the position method.', position) 1534 | return 1535 | } 1536 | 1537 | // default for useAnimations is true 1538 | if (useAnimation !== false) useAnimation = true 1539 | 1540 | if (useAnimation) { 1541 | // start the animations 1542 | var animations = calculateAnimations(currentPosition, position) 1543 | doAnimations(animations, currentPosition, position) 1544 | 1545 | // set the new position 1546 | setCurrentPosition(position) 1547 | } else { 1548 | // instant update 1549 | setCurrentPosition(position) 1550 | drawPositionInstant() 1551 | } 1552 | } 1553 | 1554 | widget.resize = function () { 1555 | // calulate the new square size 1556 | squareSize = calculateSquareSize() 1557 | 1558 | // set board width 1559 | $board.css('width', squareSize * 8 + 'px') 1560 | 1561 | // set drag piece size 1562 | $draggedPiece.css({ 1563 | height: squareSize, 1564 | width: squareSize 1565 | }) 1566 | 1567 | // spare pieces 1568 | if (config.sparePieces) { 1569 | $container 1570 | .find('.' + CSS.sparePieces) 1571 | .css('paddingLeft', squareSize + boardBorderSize + 'px') 1572 | } 1573 | 1574 | // redraw the board 1575 | drawBoard() 1576 | } 1577 | 1578 | // set the starting position 1579 | widget.start = function (useAnimation) { 1580 | widget.position('start', useAnimation) 1581 | } 1582 | 1583 | // ------------------------------------------------------------------------- 1584 | // Browser Events 1585 | // ------------------------------------------------------------------------- 1586 | 1587 | function stopDefault (evt) { 1588 | evt.preventDefault() 1589 | } 1590 | 1591 | function mousedownSquare (evt) { 1592 | // do nothing if we're not draggable 1593 | if (!config.draggable) return 1594 | 1595 | // do nothing if there is no piece on this square 1596 | var square = $(this).attr('data-square') 1597 | if (!validSquare(square)) return 1598 | if (!currentPosition.hasOwnProperty(square)) return 1599 | 1600 | beginDraggingPiece(square, currentPosition[square], evt.pageX, evt.pageY) 1601 | } 1602 | 1603 | function touchstartSquare (e) { 1604 | // do nothing if we're not draggable 1605 | if (!config.draggable) return 1606 | 1607 | // do nothing if there is no piece on this square 1608 | var square = $(this).attr('data-square') 1609 | if (!validSquare(square)) return 1610 | if (!currentPosition.hasOwnProperty(square)) return 1611 | 1612 | e = e.originalEvent 1613 | beginDraggingPiece( 1614 | square, 1615 | currentPosition[square], 1616 | e.changedTouches[0].pageX, 1617 | e.changedTouches[0].pageY 1618 | ) 1619 | } 1620 | 1621 | function mousedownSparePiece (evt) { 1622 | // do nothing if sparePieces is not enabled 1623 | if (!config.sparePieces) return 1624 | 1625 | var piece = $(this).attr('data-piece') 1626 | 1627 | beginDraggingPiece('spare', piece, evt.pageX, evt.pageY) 1628 | } 1629 | 1630 | function touchstartSparePiece (e) { 1631 | // do nothing if sparePieces is not enabled 1632 | if (!config.sparePieces) return 1633 | 1634 | var piece = $(this).attr('data-piece') 1635 | 1636 | e = e.originalEvent 1637 | beginDraggingPiece( 1638 | 'spare', 1639 | piece, 1640 | e.changedTouches[0].pageX, 1641 | e.changedTouches[0].pageY 1642 | ) 1643 | } 1644 | 1645 | function mousemoveWindow (evt) { 1646 | if (isDragging) { 1647 | updateDraggedPiece(evt.pageX, evt.pageY) 1648 | } 1649 | } 1650 | 1651 | var throttledMousemoveWindow = throttle(mousemoveWindow, config.dragThrottleRate) 1652 | 1653 | function touchmoveWindow (evt) { 1654 | // do nothing if we are not dragging a piece 1655 | if (!isDragging) return 1656 | 1657 | // prevent screen from scrolling 1658 | evt.preventDefault() 1659 | 1660 | updateDraggedPiece(evt.originalEvent.changedTouches[0].pageX, 1661 | evt.originalEvent.changedTouches[0].pageY) 1662 | } 1663 | 1664 | var throttledTouchmoveWindow = throttle(touchmoveWindow, config.dragThrottleRate) 1665 | 1666 | function mouseupWindow (evt) { 1667 | // do nothing if we are not dragging a piece 1668 | if (!isDragging) return 1669 | 1670 | // get the location 1671 | var location = isXYOnSquare(evt.pageX, evt.pageY) 1672 | 1673 | stopDraggedPiece(location) 1674 | } 1675 | 1676 | function touchendWindow (evt) { 1677 | // do nothing if we are not dragging a piece 1678 | if (!isDragging) return 1679 | 1680 | // get the location 1681 | var location = isXYOnSquare(evt.originalEvent.changedTouches[0].pageX, 1682 | evt.originalEvent.changedTouches[0].pageY) 1683 | 1684 | stopDraggedPiece(location) 1685 | } 1686 | 1687 | function mouseenterSquare (evt) { 1688 | // do not fire this event if we are dragging a piece 1689 | // NOTE: this should never happen, but it's a safeguard 1690 | if (isDragging) return 1691 | 1692 | // exit if they did not provide a onMouseoverSquare function 1693 | if (!isFunction(config.onMouseoverSquare)) return 1694 | 1695 | // get the square 1696 | var square = $(evt.currentTarget).attr('data-square') 1697 | 1698 | // NOTE: this should never happen; defensive 1699 | if (!validSquare(square)) return 1700 | 1701 | // get the piece on this square 1702 | var piece = false 1703 | if (currentPosition.hasOwnProperty(square)) { 1704 | piece = currentPosition[square] 1705 | } 1706 | 1707 | // execute their function 1708 | config.onMouseoverSquare(square, piece, deepCopy(currentPosition), currentOrientation) 1709 | } 1710 | 1711 | function mouseleaveSquare (evt) { 1712 | // do not fire this event if we are dragging a piece 1713 | // NOTE: this should never happen, but it's a safeguard 1714 | if (isDragging) return 1715 | 1716 | // exit if they did not provide an onMouseoutSquare function 1717 | if (!isFunction(config.onMouseoutSquare)) return 1718 | 1719 | // get the square 1720 | var square = $(evt.currentTarget).attr('data-square') 1721 | 1722 | // NOTE: this should never happen; defensive 1723 | if (!validSquare(square)) return 1724 | 1725 | // get the piece on this square 1726 | var piece = false 1727 | if (currentPosition.hasOwnProperty(square)) { 1728 | piece = currentPosition[square] 1729 | } 1730 | 1731 | // execute their function 1732 | config.onMouseoutSquare(square, piece, deepCopy(currentPosition), currentOrientation) 1733 | } 1734 | 1735 | // ------------------------------------------------------------------------- 1736 | // Initialization 1737 | // ------------------------------------------------------------------------- 1738 | 1739 | function addEvents () { 1740 | // prevent "image drag" 1741 | $('body').on('mousedown mousemove', '.' + CSS.piece, stopDefault) 1742 | 1743 | // mouse drag pieces 1744 | $board.on('mousedown', '.' + CSS.square, mousedownSquare) 1745 | $container.on('mousedown', '.' + CSS.sparePieces + ' .' + CSS.piece, mousedownSparePiece) 1746 | 1747 | // mouse enter / leave square 1748 | $board 1749 | .on('mouseenter', '.' + CSS.square, mouseenterSquare) 1750 | .on('mouseleave', '.' + CSS.square, mouseleaveSquare) 1751 | 1752 | // piece drag 1753 | var $window = $(window) 1754 | $window 1755 | .on('mousemove', throttledMousemoveWindow) 1756 | .on('mouseup', mouseupWindow) 1757 | 1758 | // touch drag pieces 1759 | if (isTouchDevice()) { 1760 | $board.on('touchstart', '.' + CSS.square, touchstartSquare) 1761 | $container.on('touchstart', '.' + CSS.sparePieces + ' .' + CSS.piece, touchstartSparePiece) 1762 | $window 1763 | .on('touchmove', throttledTouchmoveWindow) 1764 | .on('touchend', touchendWindow) 1765 | } 1766 | } 1767 | 1768 | function initDOM () { 1769 | // create unique IDs for all the elements we will create 1770 | createElIds() 1771 | 1772 | // build board and save it in memory 1773 | $container.html(buildContainerHTML(config.sparePieces)) 1774 | $board = $container.find('.' + CSS.board) 1775 | 1776 | if (config.sparePieces) { 1777 | $sparePiecesTop = $container.find('.' + CSS.sparePiecesTop) 1778 | $sparePiecesBottom = $container.find('.' + CSS.sparePiecesBottom) 1779 | } 1780 | 1781 | // create the drag piece 1782 | var draggedPieceId = uuid() 1783 | $('body').append(buildPieceHTML('wP', true, draggedPieceId)) 1784 | $draggedPiece = $('#' + draggedPieceId) 1785 | 1786 | // TODO: need to remove this dragged piece element if the board is no 1787 | // longer in the DOM 1788 | 1789 | // get the border size 1790 | boardBorderSize = parseInt($board.css('borderLeftWidth'), 10) 1791 | 1792 | // set the size and draw the board 1793 | widget.resize() 1794 | } 1795 | 1796 | // ------------------------------------------------------------------------- 1797 | // Initialization 1798 | // ------------------------------------------------------------------------- 1799 | 1800 | setInitialState() 1801 | initDOM() 1802 | addEvents() 1803 | 1804 | // return the widget object 1805 | return widget 1806 | } // end constructor 1807 | 1808 | // TODO: do module exports here 1809 | window['Chessboard'] = constructor 1810 | 1811 | // support legacy ChessBoard name 1812 | window['ChessBoard'] = window['Chessboard'] 1813 | 1814 | // expose util functions 1815 | window['Chessboard']['fenToObj'] = fenToObj 1816 | window['Chessboard']['objToFen'] = objToFen 1817 | })() // end anonymous wrapper 1818 | -------------------------------------------------------------------------------- /tampermonkey script/content/chesspieces/bB.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tampermonkey script/content/chesspieces/bK.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tampermonkey script/content/chesspieces/bN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 11 | 14 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tampermonkey script/content/chesspieces/bP.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tampermonkey script/content/chesspieces/bQ.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tampermonkey script/content/chesspieces/bR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 11 | 14 | 17 | 20 | 23 | 26 | 29 | 32 | 35 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tampermonkey script/content/chesspieces/wB.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tampermonkey script/content/chesspieces/wK.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tampermonkey script/content/chesspieces/wN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 11 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tampermonkey script/content/chesspieces/wP.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tampermonkey script/content/chesspieces/wQ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tampermonkey script/content/chesspieces/wR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 11 | 14 | 16 | 19 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tampermonkey script/content/stockfish-multi-thread/stockfish.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Stockfish copyright T. Romstad, M. Costalba, J. Kiiski, G. Linscott 3 | * and other contributors. 4 | * 5 | * Released under the GNU General Public License v3. 6 | * 7 | * Compiled to JavaScript and WebAssembly by Niklas Fiekas 8 | * using Emscripten. 9 | * 10 | * https://github.com/niklasf/stockfish.wasm 11 | */ 12 | 13 | var Stockfish = (function() { 14 | var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; 15 | if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename; 16 | return ( 17 | function(Stockfish) { 18 | Stockfish = Stockfish || {}; 19 | 20 | 21 | function d(){k.buffer!=l&&n(k.buffer);return ba}function t(){k.buffer!=l&&n(k.buffer);return ca}function v(){k.buffer!=l&&n(k.buffer);return da}function y(){k.buffer!=l&&n(k.buffer);return ea}function fa(){k.buffer!=l&&n(k.buffer);return ha}null;var z;z||(z=typeof Stockfish !== 'undefined' ? Stockfish : {});var ia,ja;z.ready=new Promise(function(a,b){ia=a;ja=b}); 22 | (function(){function a(){var g=e.shift();if(!b&&void 0!==g){if("quit"===g)return z.terminate();var m=z.ccall("uci_command","number",["string"],[g]);m&&e.unshift(g);h=m?2*h:1;setTimeout(a,h)}}var b=!1,c=[];z.print=function(g){0===c.length?console.log(g):setTimeout(function(){for(var m=0;m=c);){var h=a[b++];if(!h)break;if(h&128){var g=a[b++]&63;if(192==(h&224))e+=String.fromCharCode((h&31)<<6|g);else{var m=a[b++]&63;h=224==(h&240)?(h&15)<<12|g<<6|m:(h&7)<<18|g<<12|m<<6|a[b++]&63;65536>h?e+=String.fromCharCode(h):(h-=65536,e+=String.fromCharCode(55296|h>>10,56320|h&1023))}}else e+=String.fromCharCode(h)}return e}function Q(a){return a?wa(t(),a,void 0):""} 30 | function xa(a,b,c,e){if(0=g){var m=a.charCodeAt(++h);g=65536+((g&1023)<<10)|m&1023}if(127>=g){if(c>=e)break;b[c++]=g}else{if(2047>=g){if(c+1>=e)break;b[c++]=192|g>>6}else{if(65535>=g){if(c+2>=e)break;b[c++]=224|g>>12}else{if(c+3>=e)break;b[c++]=240|g>>18;b[c++]=128|g>>12&63}b[c++]=128|g>>6&63}b[c++]=128|g&63}}b[c]=0}} 31 | function ya(a){for(var b=0,c=0;c=e&&(e=65536+((e&1023)<<10)|a.charCodeAt(++c)&1023);127>=e?++b:b=2047>=e?b+2:65535>=e?b+3:b+4}return b}function za(a){var b=ya(a)+1,c=R(b);xa(a,d(),c,b);return c}function Aa(a,b){d().set(a,b)}var l,ba,ca,da,ea,ha; 32 | function n(a){l=a;z.HEAP8=ba=new Int8Array(a);z.HEAP16=new Int16Array(a);z.HEAP32=da=new Int32Array(a);z.HEAPU8=ca=new Uint8Array(a);z.HEAPU16=new Uint16Array(a);z.HEAPU32=ea=new Uint32Array(a);z.HEAPF32=new Float32Array(a);z.HEAPF64=ha=new Float64Array(a)}var Ba=z.INITIAL_MEMORY||67108864; 33 | if(F)k=z.wasmMemory,l=z.buffer;else if(z.wasmMemory)k=z.wasmMemory;else if(k=new WebAssembly.Memory({initial:Ba/65536,maximum:32768,shared:!0}),!(k.buffer instanceof SharedArrayBuffer))throw N("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag"),E&&console.log("(on node you may need: --experimental-wasm-threads --experimental-wasm-bulk-memory and also use a recent version)"), 34 | Error("bad memory");k&&(l=k.buffer);Ba=l.byteLength;n(l);var Ca,Da=[],Ea=[],Fa=[],Ga=[];F||Ea.push({Ia:function(){Ha()}});function Ia(){var a=z.preRun.shift();Da.unshift(a)}var S=0,Ja=null,T=null;z.preloadedImages={};z.preloadedAudios={};function M(a){if(z.onAbort)z.onAbort(a);F&&console.error("Pthread aborting at "+Error().stack);N(a);ua=!0;a=new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");ja(a);throw a;} 35 | function Ka(){var a=U;return String.prototype.startsWith?a.startsWith("data:application/octet-stream;base64,"):0===a.indexOf("data:application/octet-stream;base64,")}var U="stockfish.wasm";Ka()||(U=oa(U));function La(){var a=U;try{if(a==U&&P)return new Uint8Array(P);if(I)return I(a);throw"both async and sync fetching of the wasm failed";}catch(b){M(b)}} 36 | function Ma(){return P||!na&&!D||"function"!==typeof fetch?Promise.resolve().then(function(){return La()}):fetch(U,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+U+"'";return a.arrayBuffer()}).catch(function(){return La()})}var Oa={11752:function(){throw"Canceled!";},12164:function(a,b){setTimeout(function(){Na(a,b)},0)}}; 37 | function Pa(a){for(;0=a||a>d().length||a&1||0>b)return-28;if(0==b)return 0;2147483647<=b&&(b=Infinity);var c=Atomics.load(v(),V>>2),e=0;if(c==a&&Atomics.compareExchange(v(),V>>2,c,0)==c&&(--b,e=1,0>=b))return 1;a=Atomics.notify(v(),a>>2,b);if(0<=a)return a+e;throw"Atomics.notify returned an unexpected value "+a;} 38 | z._emscripten_futex_wake=Qa;function Ra(a){if(F)throw"Internal Error! cleanupThread() can only ever be called from main application thread!";if(!a)throw"Internal Error! Null pthread_ptr in cleanupThread!";v()[a+12>>2]=0;(a=A.ga[a])&&A.ta(a.worker)} 39 | var A={fa:[],ia:[],La:function(){for(var a=0;1>a;++a)A.za()},Ma:function(){for(var a=W(228),b=0;57>b;++b)y()[a/4+b]=0;v()[a+12>>2]=a;b=a+152;v()[b>>2]=b;var c=W(512);for(b=0;128>b;++b)y()[c/4+b]=0;Atomics.store(y(),a+100>>2,c);Atomics.store(y(),a+40>>2,a);Sa(a,!D,1);Ta(a)},Na:function(){A.receiveObjectTransfer=A.Pa;A.setThreadStatus=A.Ra;A.threadCancel=A.Ta;A.threadExit=A.Ua},ga:{},Fa:[],Ra:function(){},Da:function(){for(;0>2,a),Atomics.store(y(),b+0>>2,1),Atomics.store(y(),b+56>>2,1),Atomics.store(y(),b+60>>2,0),A.Da(),Qa(b+0,2147483647),Sa(0,0,0),F&&postMessage({cmd:"exit"}))},Ta:function(){A.Da();var a=X();Atomics.store(y(),a+4>>2,-1);Atomics.store(y(),a+0>>2,1);Qa(a+0,2147483647);Sa(0,0,0);postMessage({cmd:"cancelDone"})},Ea:function(){for(var a in A.ga){var b=A.ga[a];b&&b.worker&&A.ta(b.worker)}A.ga={};for(a=0;a>2];v()[a.ha+100>>2]=0;Va(b);Va(a.ha)}a.ha=0;a.xa&&a.ja&&Va(a.ja);a.ja=0;a.worker&&(a.worker.ea=null)}},ta:function(a){A.Qa(function(){delete A.ga[a.ea.ha];A.fa.push(a);A.ia.splice(A.ia.indexOf(a),1);A.ya(a.ea);a.ea=void 0})},Qa:function(a){v()[Wa>>2]=0;try{a()}finally{v()[Wa>>2]=1}},Pa:function(){},Ba:function(a,b){a.onmessage=function(c){var e=c.data,h=e.cmd;a.ea&&(A.Ga=a.ea.ha);if(e.targetThread&&e.targetThread!=X()){var g= 42 | A.ga[e.lb];g?g.worker.postMessage(c.data,e.transferList):console.error('Internal error! Worker sent a message "'+h+'" to target pthread '+e.targetThread+", but that thread no longer exists!")}else if("processQueuedMainThreadWork"===h)Xa();else if("spawnThread"===h)Ya(c.data);else if("cleanupThread"===h)Ra(e.thread);else if("killThread"===h){c=e.thread;if(F)throw"Internal Error! killThread() can only ever be called from main application thread!";if(!c)throw"Internal Error! Null pthread_ptr in killThread!"; 43 | v()[c+12>>2]=0;c=A.ga[c];c.worker.terminate();A.ya(c);A.ia.splice(A.ia.indexOf(c.worker),1);c.worker.ea=void 0}else if("cancelThread"===h){c=e.thread;if(F)throw"Internal Error! cancelThread() can only ever be called from main application thread!";if(!c)throw"Internal Error! Null pthread_ptr in cancelThread!";A.ga[c].worker.postMessage({cmd:"cancel"})}else if("loaded"===h)a.loaded=!0,b&&b(a),a.na&&(a.na(),delete a.na);else if("print"===h)qa("Thread "+e.threadId+": "+e.text);else if("printErr"===h)N("Thread "+ 44 | e.threadId+": "+e.text);else if("alert"===h)alert("Thread "+e.threadId+": "+e.text);else if("exit"===h)a.ea&&Atomics.load(y(),a.ea.ha+64>>2)&&A.ta(a);else if("exitProcess"===h)try{Za(e.returnCode)}catch(m){if(m instanceof L)return;throw m;}else"cancelDone"===h?A.ta(a):"objectTransfer"!==h&&("setimmediate"===c.data.target?a.postMessage(c.data):N("worker sent an unknown command "+h));A.Ga=void 0};a.onerror=function(c){N("pthread sent an error! "+c.filename+":"+c.lineno+": "+c.message)};E&&(a.on("message", 45 | function(c){a.onmessage({data:c})}),a.on("error",function(c){a.onerror(c)}),a.on("exit",function(){}));a.postMessage({cmd:"load",urlOrBlob:z.mainScriptUrlOrBlob||_scriptDir,wasmMemory:k,wasmModule:ta})},za:function(){var a=oa("stockfish.worker.js");A.fa.push(new Worker(a))},Ja:function(){0==A.fa.length&&(A.za(),A.Ba(A.fa[0]));return 0=a||a>d().length||a&1)return-28;if(na){if(Atomics.load(v(),a>>2)!=b)return-6;var e=performance.now();c=e+c;for(Atomics.exchange(v(),V>>2,a);;){e=performance.now();if(e>c)return Atomics.exchange(v(),V>>2,0),-73;e=Atomics.exchange(v(),V>>2,0);if(0==e)break;Xa();if(Atomics.load(v(),a>>2)!=b)return-6;Atomics.exchange(v(),V>>2,a)}return 0}a=Atomics.wait(v(),a>>2,b,c);if("timed-out"===a)return-73;if("not-equal"===a)return-6;if("ok"===a)return 0;throw"Atomics.wait returned an unexpected value "+ 49 | a;}function Z(a,b){for(var c=arguments.length-2,e=ib(),h=R(8*c),g=h>>3,m=0;m>2]=b,v()[e.ra+4>>2]=c);if(e.Ca||!e.bb)e.Ca&&(e=e.Ca),a=!1,e.qa&&e.qa.pa&&(a=e.qa.pa.getParameter(2978),a=0===a[0]&&0===a[1]&&a[2]===e.width&&a[3]===e.height),e.width=b,e.height=c,a&&e.qa.pa.viewport(0,0,b,c);else{if(e.ra){e=v()[e.ra+8>>2];a=a?Q(a):"";var h=ib(),g=R(12),m=0;if(a){m=ya(a)+1;var p=W(m);xa(a,t(),p,m);m=p}v()[g>>2]=m;v()[g+4>>2]=b;v()[g+8>>2]=c;pb(0,e,657457152,0,m,g);Y(h);return 1}return-4}return 0} 51 | function qb(a,b,c){return F?Z(5,1,a,b,c):ob(a,b,c)}function rb(a){var b=a.getExtension("ANGLE_instanced_arrays");b&&(a.vertexAttribDivisor=function(c,e){b.vertexAttribDivisorANGLE(c,e)},a.drawArraysInstanced=function(c,e,h,g){b.drawArraysInstancedANGLE(c,e,h,g)},a.drawElementsInstanced=function(c,e,h,g,m){b.drawElementsInstancedANGLE(c,e,h,g,m)})} 52 | function sb(a){var b=a.getExtension("OES_vertex_array_object");b&&(a.createVertexArray=function(){return b.createVertexArrayOES()},a.deleteVertexArray=function(c){b.deleteVertexArrayOES(c)},a.bindVertexArray=function(c){b.bindVertexArrayOES(c)},a.isVertexArray=function(c){return b.isVertexArrayOES(c)})}function tb(a){var b=a.getExtension("WEBGL_draw_buffers");b&&(a.drawBuffers=function(c,e){b.drawBuffersWEBGL(c,e)})} 53 | function ub(a){a||(a=vb);if(!a.Ka){a.Ka=!0;var b=a.pa;rb(b);sb(b);tb(b);b.cb=b.getExtension("EXT_disjoint_timer_query");b.ib=b.getExtension("WEBGL_multi_draw");(b.getSupportedExtensions()||[]).forEach(function(c){0>c.indexOf("lose_context")&&0>c.indexOf("debug")&&b.getExtension(c)})}}var vb,wb=["default","low-power","high-performance"],xb={}; 54 | function yb(){if(!zb){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"===typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:la||"./this.program"},b;for(b in xb)a[b]=xb[b];var c=[];for(b in a)c.push(b+"="+a[b]);zb=c}return zb}var zb; 55 | function Ab(a,b){if(F)return Z(6,1,a,b);var c=0;yb().forEach(function(e,h){var g=b+c;h=v()[a+4*h>>2]=g;for(g=0;g>0]=e.charCodeAt(g);d()[h>>0]=0;c+=e.length+1});return 0}function Bb(a,b){if(F)return Z(7,1,a,b);var c=yb();v()[a>>2]=c.length;var e=0;c.forEach(function(h){e+=h.length+1});v()[b>>2]=e;return 0}function Cb(a){return F?Z(8,1,a):0}function Eb(a,b,c,e){if(F)return Z(9,1,a,b,c,e);a=cb.fb(a);b=cb.eb(a,b,c);v()[e>>2]=b;return 0} 56 | function Fb(a,b,c,e,h){if(F)return Z(10,1,a,b,c,e,h)}function Gb(a,b,c,e){if(F)return Z(11,1,a,b,c,e);for(var h=0,g=0;g>2],p=v()[b+(8*g+4)>>2],u=0;u>2]=h;return 0} 57 | function Ya(a){if(F)throw"Internal Error! spawnThread() can only ever be called from main application thread!";var b=A.Ja();if(void 0!==b.ea)throw"Internal error!";if(!a.sa)throw"Internal error, no pthread ptr!";A.ia.push(b);for(var c=W(512),e=0;128>e;++e)v()[c+4*e>>2]=0;var h=a.ja+a.ka;e=A.ga[a.sa]={worker:b,ja:a.ja,ka:a.ka,xa:a.xa,ha:a.sa};var g=e.ha>>2;Atomics.store(y(),g+16,a.detached);Atomics.store(y(),g+25,c);Atomics.store(y(),g+10,e.ha);Atomics.store(y(),g+20,a.ka);Atomics.store(y(),g+19,h); 58 | Atomics.store(y(),g+26,a.ka);Atomics.store(y(),g+28,h);Atomics.store(y(),g+29,a.detached);c=Hb()+40;Atomics.store(y(),g+43,c);b.ea=e;var m={cmd:"run",start_routine:a.Sa,arg:a.ma,threadInfoStruct:a.sa,stackBase:a.ja,stackSize:a.ka};b.na=function(){m.time=performance.now();b.postMessage(m,a.Za)};b.loaded&&(b.na(),delete b.na)} 59 | function Ib(a,b){if(!a)return N("pthread_join attempted on a null thread pointer!"),71;if(F&&X()==a)return N("PThread "+a+" is attempting to join to itself!"),16;if(!F&&Jb()==a)return N("Main thread "+a+" is attempting to join to itself!"),16;if(v()[a+12>>2]!==a)return N("pthread_join attempted on thread "+a+", which does not point to a valid thread, or does not exist anymore!"),71;if(Atomics.load(y(),a+64>>2))return N("Attempted to join thread "+a+", which was already detached!"),28;for(gb();;){var c= 60 | Atomics.load(y(),a+0>>2);if(1==c)return c=Atomics.load(y(),a+4>>2),b&&(v()[b>>2]=c),Atomics.store(y(),a+64>>2,1),F?postMessage({cmd:"cleanupThread",thread:a}):Ra(a),0;if(F){var e=X();if(e&&!Atomics.load(y(),e+56>>2)&&2==Atomics.load(y(),e+0>>2))throw"Canceled!";}F||Xa();hb(a+0,c,F?100:1)}}function Kb(a){return 0===a%4&&(0!==a%100||0===a%400)}function Lb(a,b){for(var c=0,e=0;e<=b;c+=a[e++]);return c}var Mb=[31,29,31,30,31,30,31,31,30,31,30,31],Nb=[31,28,31,30,31,30,31,31,30,31,30,31]; 61 | function Ob(a,b){for(a=new Date(a.getTime());0e-a.getDate())b-=e-a.getDate()+1,a.setDate(1),11>c?a.setMonth(c+1):(a.setMonth(0),a.setFullYear(a.getFullYear()+1));else{a.setDate(a.getDate()+b);break}}return a} 62 | function Pb(a,b,c,e){function h(f,q,x){for(f="number"===typeof f?f.toString():f||"";f.lengthDb?-1:0=m(x,f)?0>=m(q,f)?f.getFullYear()+1:f.getFullYear():f.getFullYear()-1}var r=v()[e+40>>2];e={Xa:v()[e>>2],Wa:v()[e+4>>2],ua:v()[e+8>>2],oa:v()[e+12>>2],la:v()[e+16>>2],da:v()[e+20>>2],va:v()[e+24>>2],wa:v()[e+28>>2],mb:v()[e+ 64 | 32>>2],Va:v()[e+36>>2],Ya:r?Q(r):""};c=Q(c);r={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var w in r)c=c.replace(new RegExp(w,"g"),r[w]);var aa="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "), 65 | sa="January February March April May June July August September October November December".split(" ");r={"%a":function(f){return aa[f.va].substring(0,3)},"%A":function(f){return aa[f.va]},"%b":function(f){return sa[f.la].substring(0,3)},"%B":function(f){return sa[f.la]},"%C":function(f){return g((f.da+1900)/100|0,2)},"%d":function(f){return g(f.oa,2)},"%e":function(f){return h(f.oa,2," ")},"%g":function(f){return u(f).toString().substring(2)},"%G":function(f){return u(f)},"%H":function(f){return g(f.ua, 66 | 2)},"%I":function(f){f=f.ua;0==f?f=12:12f.ua?"AM":"PM"},"%S":function(f){return g(f.Xa,2)},"%t":function(){return"\t"},"%u":function(f){return f.va||7},"%U":function(f){var q=new Date(f.da+1900,0,1),x=0===q.getDay()?q:Ob(q,7-q.getDay());f=new Date(f.da+1900,f.la,f.oa);return 0> 67 | m(x,f)?g(Math.ceil((31-x.getDate()+(Lb(Kb(f.getFullYear())?Mb:Nb,f.getMonth()-1)-31)+f.getDate())/7),2):0===m(x,q)?"01":"00"},"%V":function(f){var q=new Date(f.da+1901,0,4),x=p(new Date(f.da+1900,0,4));q=p(q);var O=Ob(new Date(f.da+1900,0,1),f.wa);return 0>m(O,x)?"53":0>=m(q,O)?"01":g(Math.ceil((x.getFullYear()m(x,f)?g(Math.ceil((31-x.getDate()+(Lb(Kb(f.getFullYear())?Mb:Nb,f.getMonth()-1)-31)+f.getDate())/7),2):0===m(x,q)?"01":"00"},"%y":function(f){return(f.da+1900).toString().substring(2)},"%Y":function(f){return f.da+1900},"%z":function(f){f=f.Va;var q=0<=f;f=Math.abs(f)/60;return(q?"+":"-")+String("0000"+(f/60*100+f%60)).slice(-4)},"%Z":function(f){return f.Ya},"%%":function(){return"%"}};for(w in r)0<=c.indexOf(w)&&(c=c.replace(new RegExp(w,"g"),r[w](e)));w= 69 | Qb(c);if(w.length>b)return 0;Aa(w,a);return w.length-1}F||A.La();var Rb=[null,function(a,b){if(F)return Z(1,1,a,b)},db,eb,fb,qb,Ab,Bb,Cb,Eb,Fb,Gb];function Qb(a){var b=Array(ya(a)+1);xa(a,b,0,b.length);return b} 70 | var Vb={c:function(a,b,c,e){M("Assertion failed: "+Q(a)+", at: "+[b?Q(b):"unknown filename",c,e?Q(e):"unknown function"])},i:db,q:eb,r:fb,E:function(a,b){if(a==b)postMessage({cmd:"processQueuedMainThreadWork"});else if(F)postMessage({targetThread:a,cmd:"processThreadQueue"});else{a=(a=A.ga[a])&&a.worker;if(!a)return;a.postMessage({cmd:"processThreadQueue"})}return 1},b:function(){M()},v:function(a,b){if(0===a)a=Date.now();else if(1===a||4===a)a=ab();else return v()[Sb()>>2]=28,-1;v()[b>>2]=a/1E3| 71 | 0;v()[b+4>>2]=a%1E3*1E6|0;return 0},n:function(a,b,c){lb.length=0;var e;for(c>>=2;e=t()[b++];)(e=105>e)&&c&1&&c++,lb.push(e?fa()[c++>>1]:v()[c]),++c;return Oa[a].apply(null,lb)},z:gb,m:function(){},f:hb,e:Qa,g:ab,u:function(a,b,c){t().copyWithin(a,b,b+c)},A:function(a,b,c){kb.length=b;c>>=3;for(var e=0;ea?Oa[-a-1]:Rb[a]).apply(null,kb)},d:function(a){a>>>=0;var b=t().length;if(a<=b||2147483648=c;c*=2){var e=b*(1+.2/c);e=Math.min(e,a+100663296); 72 | e=Math.max(16777216,a,e);0>>16);n(k.buffer);var h=1;break a}catch(g){}h=void 0}if(h)return!0}return!1},C:function(a,b,c){return nb(a)?ob(a,b,c):qb(a,b,c)},l:function(){},D:function(a,b){b>>=2;var c=v()[b+6];b={alpha:!!v()[b],depth:!!v()[b+1],stencil:!!v()[b+2],antialias:!!v()[b+3],premultipliedAlpha:!!v()[b+4],preserveDrawingBuffer:!!v()[b+5],powerPreference:wb[c],failIfMajorPerformanceCaveat:!!v()[b+7],Oa:v()[b+ 73 | 8],hb:v()[b+9],Aa:v()[b+10],Ha:v()[b+11],jb:v()[b+12],kb:v()[b+13]};a=nb(a);if(!a||b.Ha)b=0;else if(a=a.getContext("webgl",b)){c=W(8);v()[c+4>>2]=X();var e={gb:c,attributes:b,version:b.Oa,pa:a};a.canvas&&(a.canvas.qa=e);("undefined"===typeof b.Aa||b.Aa)&&ub(e);b=c}else b=0;return b},x:Ab,y:Bb,h:function(a){Za(a)},j:Cb,p:Eb,s:Fb,o:Gb,t:function(){A.Ma()},a:k||z.wasmMemory,k:function(a,b,c,e){if("undefined"===typeof SharedArrayBuffer)return N("Current environment does not support SharedArrayBuffer, pthreads are not available!"), 74 | 6;if(!a)return N("pthread_create called with a null thread pointer!"),28;var h=[];if(F&&0===h.length)return Tb(687865856,a,b,c,e);var g=0,m=0;if(b&&-1!=b){var p=v()[b>>2];p+=81920;g=v()[b+8>>2];m=0!==v()[b+12>>2]}else p=2097152;(b=0==g)?g=Ub(16,p):(g-=p,assert(0r;++r)y()[(u>>2)+r]=0;v()[a>>2]=u;v()[u+12>>2]=u;a=u+152;v()[a>>2]=a;c={ja:g,ka:p,xa:b,detached:m,Sa:c,sa:u,ma:e,Za:h};F?(c.ab="spawnThread",postMessage(c,h)):Ya(c);return 0},B:function(a,b){return Ib(a,b)},w:function(a, 75 | b,c,e){return Pb(a,b,c,e)}}; 76 | (function(){function a(h,g){z.asm=h.exports;Ca=z.asm.ca;ta=g;if(!F){var m=A.fa.length;A.fa.forEach(function(p){A.Ba(p,function(){if(!--m&&(S--,z.monitorRunDependencies&&z.monitorRunDependencies(S),0==S&&(null!==Ja&&(clearInterval(Ja),Ja=null),T))){var u=T;T=null;u()}})})}}function b(h){a(h.instance,h.module)}function c(h){return Ma().then(function(g){return WebAssembly.instantiate(g,e)}).then(h,function(g){N("failed to asynchronously prepare wasm: "+g);M(g)})}var e={a:Vb};F||(assert(!F,"addRunDependency cannot be used in a pthread worker"), 77 | S++,z.monitorRunDependencies&&z.monitorRunDependencies(S));if(z.instantiateWasm)try{return z.instantiateWasm(e,a)}catch(h){return N("Module.instantiateWasm callback failed with error: "+h),!1}(function(){return P||"function"!==typeof WebAssembly.instantiateStreaming||Ka()||"function"!==typeof fetch?c(b):fetch(U,{credentials:"same-origin"}).then(function(h){return WebAssembly.instantiateStreaming(h,e).then(b,function(g){N("wasm streaming compile failed: "+g);N("falling back to ArrayBuffer instantiation"); 78 | return c(b)})})})().catch(ja);return{}})();var Ha=z.___wasm_call_ctors=function(){return(Ha=z.___wasm_call_ctors=z.asm.F).apply(null,arguments)};z._main=function(){return(z._main=z.asm.G).apply(null,arguments)};var W=z._malloc=function(){return(W=z._malloc=z.asm.H).apply(null,arguments)},Va=z._free=function(){return(Va=z._free=z.asm.I).apply(null,arguments)};z._uci_command=function(){return(z._uci_command=z.asm.J).apply(null,arguments)}; 79 | var Hb=z._emscripten_get_global_libc=function(){return(Hb=z._emscripten_get_global_libc=z.asm.K).apply(null,arguments)},Sb=z.___errno_location=function(){return(Sb=z.___errno_location=z.asm.L).apply(null,arguments)};z.___em_js__initPthreadsJS=function(){return(z.___em_js__initPthreadsJS=z.asm.M).apply(null,arguments)}; 80 | var X=z._pthread_self=function(){return(X=z._pthread_self=z.asm.N).apply(null,arguments)},Ua=z.___pthread_tsd_run_dtors=function(){return(Ua=z.___pthread_tsd_run_dtors=z.asm.O).apply(null,arguments)};z._emscripten_current_thread_process_queued_calls=function(){return(z._emscripten_current_thread_process_queued_calls=z.asm.P).apply(null,arguments)}; 81 | var Ta=z._emscripten_register_main_browser_thread_id=function(){return(Ta=z._emscripten_register_main_browser_thread_id=z.asm.Q).apply(null,arguments)},Jb=z._emscripten_main_browser_thread_id=function(){return(Jb=z._emscripten_main_browser_thread_id=z.asm.R).apply(null,arguments)},Na=z.__emscripten_do_dispatch_to_thread=function(){return(Na=z.__emscripten_do_dispatch_to_thread=z.asm.S).apply(null,arguments)},Tb=z._emscripten_sync_run_in_main_thread_4=function(){return(Tb=z._emscripten_sync_run_in_main_thread_4= 82 | z.asm.T).apply(null,arguments)},Xa=z._emscripten_main_thread_process_queued_calls=function(){return(Xa=z._emscripten_main_thread_process_queued_calls=z.asm.U).apply(null,arguments)},jb=z._emscripten_run_in_main_runtime_thread_js=function(){return(jb=z._emscripten_run_in_main_runtime_thread_js=z.asm.V).apply(null,arguments)},pb=z.__emscripten_call_on_thread=function(){return(pb=z.__emscripten_call_on_thread=z.asm.W).apply(null,arguments)}; 83 | z._emscripten_tls_init=function(){return(z._emscripten_tls_init=z.asm.X).apply(null,arguments)}; 84 | var Sa=z.__emscripten_thread_init=function(){return(Sa=z.__emscripten_thread_init=z.asm.Y).apply(null,arguments)},ib=z.stackSave=function(){return(ib=z.stackSave=z.asm.Z).apply(null,arguments)},Y=z.stackRestore=function(){return(Y=z.stackRestore=z.asm._).apply(null,arguments)},R=z.stackAlloc=function(){return(R=z.stackAlloc=z.asm.$).apply(null,arguments)},$a=z._emscripten_stack_set_limits=function(){return($a=z._emscripten_stack_set_limits=z.asm.aa).apply(null,arguments)},Ub=z._memalign=function(){return(Ub= 85 | z._memalign=z.asm.ba).apply(null,arguments)},Wa=z.__emscripten_allow_main_runtime_queued_calls=25580,V=z.__emscripten_main_thread_futex=1088592; 86 | z.ccall=function(a,b,c,e){var h={string:function(r){var w=0;if(null!==r&&void 0!==r&&0!==r){var aa=(r.length<<2)+1,sa=w=R(aa);xa(r,t(),sa,aa)}return w},array:function(r){var w=R(r.length);Aa(r,w);return w}},g=va(a),m=[];a=0;if(e)for(var p=0;p>2]=za(la);for(var m=1;m>2)+m]=za(c[m-1]);v()[(g>>2)+h]=0;try{var p=e(h,g);Za(p,!0)}catch(u){u instanceof L||("unwind"==u?noExitRuntime=!0:((c=u)&&"object"===typeof u&&u.stack&&(c=[u,u.stack]),N("exception thrown: "+c),ma(1,u)))}finally{}}if(!F){if(z.postRun)for("function"==typeof z.postRun&& 89 | (z.postRun=[z.postRun]);z.postRun.length;)c=z.postRun.shift(),Ga.unshift(c);Pa(Ga)}}}a=a||ka;if(!(0 { 8 | return new Promise((resolve, reject) => { 9 | const engines_path = path.resolve(process.cwd(), 'utils/engine'); 10 | const engine_path = path.resolve(engines_path, engine_name); 11 | 12 | if (!fs.existsSync(engine_path)) { 13 | reject("Engine not found: " + engine_name); 14 | } 15 | 16 | console.log("Using engine: " + engine_name); 17 | 18 | const engine = spawn(engine_path, { 19 | shell: true, 20 | cwd: engines_path 21 | }); 22 | 23 | engine.stdin.write(`${command}\n`); 24 | engine.stdin.write(`${engineCmd}\n`); 25 | 26 | 27 | 28 | 29 | engine.stdout.on('data', (chunk) => { 30 | const result = chunk.toString(); 31 | if (result.includes('bestmove')) { 32 | engine.kill(); 33 | 34 | const depth = result.match(/info\sdepth\s\d+/); 35 | const seldepth = result.match(/seldepth\s\d+/); 36 | const bestmove = result.match(/bestmove\s\w+/); 37 | const ponder = result.match(/ponder\s\w+/); 38 | 39 | resolve({ 40 | depth: depth ? Number(depth[0].match(/\d+/)[0]) : null, 41 | seldepth: seldepth ? Number(seldepth[0].match(/\d+/)[0]) : null, 42 | bestmove: bestmove ? bestmove[0].replace('bestmove ', '') : '', 43 | possible_human_move: ponder ? ponder[0].replace('ponder ', '') : '', 44 | }); 45 | } 46 | }); 47 | 48 | engine.on('error', (err) => { 49 | reject(err); 50 | }); 51 | 52 | engine.stderr.on('data', (data) => { 53 | reject(data); 54 | }); 55 | }); 56 | } 57 | 58 | module.exports = { 59 | executeEngine, 60 | } 61 | --------------------------------------------------------------------------------