Chess.com Stockfish Bot

Chess.com Stockfish Bot with Auto-Match using Stockfish Online API

目前为 2025-02-28 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Chess.com Stockfish Bot
  3. // @namespace BottleOrg Scripts
  4. // @version 1.6.2.12
  5. // @description Chess.com Stockfish Bot with Auto-Match using Stockfish Online API
  6. // @author Quoc Bao Nguyen & Deepseek V3 AI & Gemini 2.0 AI & Grok 3 (xAI)
  7. // @license Chess.com Bot/Cheat by BottleOrg(me Quoc Bao Nguyen)
  8. // @match https://www.chess.com/play/*
  9. // @match https://www.chess.com/game/*
  10. // @match https://www.chess.com/puzzles/*
  11. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM_xmlhttpRequest
  15. // @grant GM_getResourceText
  16. // @grant GM_registerMenuCommand
  17. // @require https://greasyfork.org/scripts/445697/code/index.js
  18. // @require https://code.jquery.com/jquery-3.6.0.min.js
  19. // @run-at document-start
  20. // CHANGELOGS:
  21. // 1.6.2.4: Initial fix for depth jumping from 11 to 100, integrated Stockfish API response parsing (bestmove b7b6), fixed AI not moving with L key by refining movePiece and rescan.
  22. // 1.6.2.5: Improved board detection, fixed rescan FEN generation (still had issues), enhanced move execution logging, addressed "Chessboard not found" errors.
  23. // 1.6.2.6: Fixed UI not loading by ensuring UI creation outside board check, corrected rescan piece parsing to avoid TypeError, improved board detection reliability.
  24. // 1.6.2.7: Fixed UI glitch with better CSS (z-index: 10000, padding, borders), ensured moves execute with Auto Move enabled, refined event binding timing.
  25. // 1.6.2.8: Fixed "always loading" by resetting isThinking in all API outcomes, updated rescan to use UCI-style square notation, capped depth at 15 in code but not fully in keys.
  26. // 1.6.2.9: Corrected rescan to properly map Chess.com squares (11-88) to FEN (0-63), fixed inverted board issue, ensured moves align with getLegalMoves.
  27. // 1.6.2.10: Capped MAX_DEPTH at 15, removed key bindings above 15 (H-M, ,), updated UI to reflect "Q-G (1-15)", fixed API depth limit errors.
  28. // 1.6.2.11: Added "Auto-Match" checkbox, triggered new game on game end by clicking "New Game" tab and "Play" button, used game-over detection.
  29. // 1.6.2.12: Modified "Auto-Match" to trigger only once on enable (not on game end), added hasAutoMatched flag, moved logic to checkbox change event.
  30. // ==/UserScript==
  31.  
  32. const currentVersion = '1.6.2.12';
  33.  
  34. function main() {
  35. var myVars = document.myVars = { autoMove: false, autoRun: false, autoMatch: false, delay: 0.1, hasAutoMatched: false };
  36. var myFunctions = document.myFunctions = {};
  37. var currentStockfishVersion = "Stockfish API";
  38. var uiElementsLoaded = false;
  39. const stockfishAPI_URI = "https://stockfish.online/api/s/v2.php";
  40.  
  41. var stop_b = 0, stop_w = 0, s_br = 0, s_br2 = 0, s_wr = 0, s_wr2 = 0;
  42.  
  43. myFunctions.rescan = function() {
  44. console.log("Rescanning board...");
  45. var boardElement = document.querySelector('chess-board, wc-chess-board');
  46. if (!boardElement) {
  47. console.warn("No board element found. Using default FEN.");
  48. return "rnbqkbnr/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
  49. }
  50. var pieces = $(boardElement).find(".piece").map(function() { return this.className; }).get();
  51. if (!pieces.length) {
  52. console.warn("No pieces found. Using default FEN.");
  53. return "rnbqkbnr/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
  54. }
  55. var boardArray = Array(64).fill('');
  56. pieces.forEach(piece => {
  57. var classes = piece.split(' ');
  58. var squareClass = classes.find(c => c.startsWith('square-'));
  59. var pieceClass = classes.find(c => /^[wb][prnbqk]$/.test(c));
  60. if (squareClass && pieceClass) {
  61. var squareNum = squareClass.replace('square-', '');
  62. var file = parseInt(squareNum[0]) - 1;
  63. var rank = parseInt(squareNum[1]) - 1;
  64. var square = (7 - rank) * 8 + file;
  65. if (square >= 0 && square < 64) {
  66. var pieceChar = {'wp': 'P', 'bp': 'p', 'wr': 'R', 'br': 'r', 'wn': 'N', 'bn': 'n',
  67. 'wb': 'B', 'bb': 'b', 'wq': 'Q', 'bq': 'q', 'wk': 'K', 'bk': 'k'}[pieceClass];
  68. boardArray[square] = pieceChar;
  69. }
  70. }
  71. });
  72. var fen = '';
  73. for (var i = 0; i < 64; i++) {
  74. if (i % 8 === 0 && i > 0) fen += '/';
  75. var piece = boardArray[i];
  76. if (!piece) {
  77. var emptyCount = 1;
  78. while (i + 1 < 64 && !boardArray[i + 1] && (i + 1) % 8 !== 0) {
  79. emptyCount++;
  80. i++;
  81. }
  82. fen += emptyCount;
  83. } else {
  84. fen += piece;
  85. }
  86. }
  87. var turn = $('.coordinates').children().first().text() === "1" ? 'b' : 'w';
  88. var castling = (stop_w ? '' : 'KQ') + (stop_b ? '' : 'kq') || '-';
  89. fen += ` ${turn} ${castling} - 0 1`;
  90. console.log("Generated FEN:", fen);
  91. return fen;
  92. };
  93.  
  94. myFunctions.color = function(dat) {
  95. console.log("myFunctions.color CALLED with:", dat);
  96. const bestmoveUCI = dat.split(' ')[1];
  97. console.log("Extracted bestmove UCI:", bestmoveUCI);
  98. if (myVars.autoMove) myFunctions.movePiece(bestmoveUCI);
  99. else myFunctions.highlightMove(bestmoveUCI);
  100. isThinking = false;
  101. myFunctions.spinner();
  102. };
  103.  
  104. myFunctions.highlightMove = function(bestmoveUCI) {
  105. var res1 = bestmoveUCI.substring(0, 2), res2 = bestmoveUCI.substring(2, 4);
  106. $(board).prepend(`<div class="highlight square-${res2}" style="background-color: rgb(235, 97, 80); opacity: 0.71;"></div>`)
  107. .children(':first').delay(1800).queue(function() { $(this).remove(); });
  108. $(board).prepend(`<div class="highlight square-${res1}" style="background-color: rgb(235, 97, 80); opacity: 0.71;"></div>`)
  109. .children(':first').delay(1800).queue(function() { $(this).remove(); });
  110. console.log("Highlighted:", bestmoveUCI);
  111. };
  112.  
  113. myFunctions.movePiece = function(bestmoveUCI) {
  114. console.log("movePiece CALLED with:", bestmoveUCI);
  115. if (!board || !board.game) {
  116. console.error("Board or board.game not initialized!");
  117. return;
  118. }
  119. const fromSquare = bestmoveUCI.substring(0, 2);
  120. const toSquare = bestmoveUCI.substring(2, 4);
  121. const legalMoves = board.game.getLegalMoves();
  122. console.log("Legal moves:", legalMoves);
  123. let foundMove = legalMoves.find(move => move.from === fromSquare && move.to === toSquare);
  124. if (foundMove) {
  125. console.log("Executing move:", foundMove);
  126. board.game.move({ ...foundMove, promotion: 'q', animate: true, userGenerated: true });
  127. console.log("Move executed:", bestmoveUCI);
  128. } else {
  129. console.warn("No legal move found for:", bestmoveUCI);
  130. }
  131. };
  132.  
  133. myFunctions.reloadChessEngine = function() { console.log("Reload not needed for API."); };
  134. myFunctions.loadChessEngine = function() {
  135. console.log("Using Stockfish API.");
  136. if (uiElementsLoaded) $('#engineVersionText')[0].innerHTML = "Engine: <strong>Stockfish API</strong>";
  137. };
  138.  
  139. myFunctions.fetchBestMoveFromAPI = function(fen, depth) {
  140. const apiURL = `${stockfishAPI_URI}?fen=${encodeURIComponent(fen)}&depth=${depth}`;
  141. console.log(`Fetching from: ${apiURL}`);
  142. GM_xmlhttpRequest({
  143. method: "GET",
  144. url: apiURL,
  145. onload: function(response) {
  146. if (response.status === 200) {
  147. try {
  148. const jsonResponse = JSON.parse(response.responseText);
  149. if (jsonResponse.success) {
  150. console.log("API Response:", jsonResponse);
  151. myFunctions.color(jsonResponse.bestmove);
  152. } else {
  153. console.error("API failed:", jsonResponse);
  154. isThinking = false;
  155. myFunctions.spinner();
  156. }
  157. } catch (e) {
  158. console.error("API parse error:", e);
  159. isThinking = false;
  160. myFunctions.spinner();
  161. }
  162. } else {
  163. console.error("API error:", response.status);
  164. isThinking = false;
  165. myFunctions.spinner();
  166. }
  167. },
  168. onerror: function(error) {
  169. console.error("API request error:", error);
  170. isThinking = false;
  171. myFunctions.spinner();
  172. }
  173. });
  174. };
  175.  
  176. myFunctions.startNewGame = function() {
  177. console.log("Starting new game...");
  178. const newGameTab = document.querySelector('[data-tab="newGame"]');
  179. if (newGameTab) {
  180. newGameTab.click();
  181. console.log("Clicked New Game tab.");
  182. setTimeout(() => {
  183. const playButton = document.querySelector('.cc-button-component.cc-button-primary.cc-button-xx-large.cc-button-full');
  184. if (playButton) {
  185. playButton.click();
  186. console.log("Clicked Play button.");
  187. myVars.hasAutoMatched = true; // Mark as done
  188. } else {
  189. console.error("Play button not found!");
  190. }
  191. }, 500);
  192. } else {
  193. console.error("New Game tab not found!");
  194. }
  195. };
  196.  
  197. var lastValue = 11, MAX_DEPTH = 15, MIN_DEPTH = 1;
  198.  
  199. myFunctions.runChessEngine = function(depth) {
  200. depth = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, depth));
  201. var fen = myFunctions.rescan();
  202. console.log(`Analyzing FEN: ${fen}, Depth: ${depth}`);
  203. isThinking = true;
  204. myFunctions.spinner();
  205. myFunctions.fetchBestMoveFromAPI(fen, depth);
  206. lastValue = depth;
  207. updateDepthDisplay();
  208. };
  209.  
  210. function updateDepthDisplay() {
  211. if (uiElementsLoaded && $('#depthText')[0]) $('#depthText')[0].innerHTML = `Depth: <strong>${lastValue}</strong>`;
  212. }
  213.  
  214. myFunctions.incrementDepth = function(delta) {
  215. lastValue = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, lastValue + delta));
  216. updateDepthDisplay();
  217. };
  218.  
  219. myFunctions.autoRun = function() {
  220. if (board && board.game && board.game.getTurn() === board.game.getPlayingAs()) {
  221. myFunctions.runChessEngine(lastValue);
  222. }
  223. };
  224.  
  225. document.onkeydown = function(e) {
  226. switch (e.keyCode) {
  227. case 81: myFunctions.runChessEngine(1); break; // Q
  228. case 87: myFunctions.runChessEngine(2); break; // W
  229. case 69: myFunctions.runChessEngine(3); break; // E
  230. case 82: myFunctions.runChessEngine(4); break; // R
  231. case 84: myFunctions.runChessEngine(5); break; // T
  232. case 89: myFunctions.runChessEngine(6); break; // Y
  233. case 85: myFunctions.runChessEngine(7); break; // U
  234. case 73: myFunctions.runChessEngine(8); break; // I
  235. case 79: myFunctions.runChessEngine(9); break; // O
  236. case 80: myFunctions.runChessEngine(10); break; // P
  237. case 65: myFunctions.runChessEngine(11); break; // A
  238. case 83: myFunctions.runChessEngine(12); break; // S
  239. case 68: myFunctions.runChessEngine(13); break; // D
  240. case 70: myFunctions.runChessEngine(14); break; // F
  241. case 71: myFunctions.runChessEngine(15); break; // G
  242. case 187: myFunctions.incrementDepth(1); break; // +
  243. case 189: myFunctions.incrementDepth(-1); break;// -
  244. }
  245. };
  246.  
  247. myFunctions.spinner = function() {
  248. if (uiElementsLoaded && $('#overlay')[0]) {
  249. $('#overlay')[0].style.display = isThinking ? 'block' : 'none';
  250. }
  251. };
  252.  
  253. let dynamicStyles = null;
  254. function addAnimation(body) {
  255. if (!dynamicStyles) {
  256. dynamicStyles = document.createElement('style');
  257. document.head.appendChild(dynamicStyles);
  258. }
  259. dynamicStyles.sheet.insertRule(body, dynamicStyles.length);
  260. }
  261.  
  262. var loaded = false;
  263. myFunctions.loadEx = function() {
  264. try {
  265. console.log("Attempting to load UI...");
  266. board = document.querySelector('chess-board, wc-chess-board');
  267. var div = document.createElement('div');
  268. div.innerHTML = `
  269. <div style="margin: 8px; padding: 10px; background: white; border: 1px solid #000; border-radius: 5px;">
  270. <p id="depthText">Depth: <strong>${lastValue}</strong></p>
  271. <button id="depthMinus">-</button>
  272. <button id="depthPlus">+</button>
  273. <p style="font-size: 12px;">Keys: Q-G (1-15), +/-</p>
  274. <p id="engineVersionText">Engine: Stockfish API</p>
  275. <label><input type="checkbox" id="autoRun"> Auto Run</label><br>
  276. <label><input type="checkbox" id="autoMove"> Auto Move</label><br>
  277. <label><input type="checkbox" id="autoMatch"> Auto-Match</label><br>
  278. <label>Min Delay (s): <input type="number" id="timeDelayMin" min="0.1" value="0.1" step="0.1" style="width: 60px;"></label><br>
  279. <label>Max Delay (s): <input type="number" id="timeDelayMax" min="0.1" value="1" step="0.1" style="width: 60px;"></label>
  280. </div>`;
  281. div.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 10000;';
  282. document.body.appendChild(div);
  283.  
  284. setTimeout(() => {
  285. $('#depthPlus').off('click').on('click', () => myFunctions.incrementDepth(1));
  286. $('#depthMinus').off('click').on('click', () => myFunctions.incrementDepth(-1));
  287. $('#autoMatch').on('change', () => {
  288. myVars.autoMatch = $('#autoMatch')[0].checked;
  289. if (myVars.autoMatch && !myVars.hasAutoMatched) {
  290. myFunctions.startNewGame();
  291. }
  292. });
  293. console.log("Event listeners bound.");
  294. }, 100);
  295.  
  296. var spinCont = document.createElement('div');
  297. spinCont.id = 'overlay';
  298. spinCont.style.cssText = 'display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);';
  299. div.appendChild(spinCont);
  300. var spinr = document.createElement('div');
  301. spinr.style.cssText = "height: 64px; width: 64px; animation: rotate 0.8s infinite linear; border: 5px solid firebrick; border-right-color: transparent; border-radius: 50%;";
  302. spinCont.appendChild(spinr);
  303. addAnimation(`@keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }`);
  304.  
  305. loaded = true;
  306. uiElementsLoaded = true;
  307. console.log("UI loaded successfully.");
  308. myFunctions.loadChessEngine();
  309. } catch (error) {
  310. console.error("loadEx error:", error);
  311. }
  312. };
  313.  
  314. function other(delay) {
  315. var endTime = Date.now() + delay;
  316. var timer = setInterval(() => {
  317. if (Date.now() >= endTime) {
  318. myFunctions.autoRun();
  319. canGo = true;
  320. clearInterval(timer);
  321. }
  322. }, 10);
  323. }
  324.  
  325. const waitForChessBoard = setInterval(() => {
  326. if (!loaded) {
  327. myFunctions.loadEx();
  328. } else {
  329. if (!board) board = document.querySelector('chess-board, wc-chess-board');
  330. myVars.autoRun = $('#autoRun')[0].checked;
  331. myVars.autoMove = $('#autoMove')[0].checked;
  332. myVars.autoMatch = $('#autoMatch')[0].checked;
  333. let minDel = parseFloat($('#timeDelayMin')[0].value) || 0.1;
  334. let maxDel = parseFloat($('#timeDelayMax')[0].value) || 1;
  335. myVars.delay = Math.random() * (maxDel - minDel) + minDel;
  336. myFunctions.spinner();
  337. myTurn = board && board.game && board.game.getTurn() === board.game.getPlayingAs();
  338. updateDepthDisplay();
  339.  
  340. // Check for game end (for logging/debugging only, no auto-match here)
  341. const gameOver = document.querySelector('.game-over-message-component') || document.querySelector('.game-result');
  342. if (gameOver) {
  343. console.log("Game ended detected.");
  344. }
  345.  
  346. if (myVars.autoRun && canGo && !isThinking && myTurn) {
  347. canGo = false;
  348. other(myVars.delay * 1000);
  349. }
  350. }
  351. }, 500);
  352.  
  353. setTimeout(() => {
  354. if (!loaded) myFunctions.loadEx();
  355. }, 2000);
  356. }
  357.  
  358. var isThinking = false, canGo = true, myTurn = false, board;
  359.  
  360. window.addEventListener("load", () => main());