Chess.com Stockfish Bot

Chess.com Stockfish Bot with Enhanced 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.17
  5. // @description Chess.com Stockfish Bot with Enhanced 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. // ==/UserScript==
  21.  
  22. const currentVersion = '1.6.2.17';
  23. const scriptURL = 'https://greasyfork.org/en/scripts/526240-chess-com-stockfish-bot';
  24.  
  25. function checkForUpdate() {
  26. console.log("Checking for script updates...");
  27. GM_xmlhttpRequest({
  28. method: "GET",
  29. url: scriptURL,
  30. onload: function(response) {
  31. if (response.status === 200) {
  32. const html = response.responseText;
  33. const versionMatch = html.match(/@version\s+([\d.]+)/);
  34. if (versionMatch && versionMatch[1]) {
  35. const latestVersion = versionMatch[1];
  36. console.log("Latest version found:", latestVersion);
  37. if (compareVersions(latestVersion, currentVersion) > 0) {
  38. const message = `New Version: ${latestVersion} has been uploaded. Would you like me to take you there or continue with old version ${currentVersion}? (Not recommended for stability)`;
  39. if (confirm(message)) {
  40. window.location.href = scriptURL;
  41. } else {
  42. console.log("User chose to continue with old version.");
  43. }
  44. } else {
  45. console.log("No newer version available.");
  46. }
  47. } else {
  48. console.error("Could not find version in Greasy Fork page.");
  49. }
  50. } else {
  51. console.error("Failed to fetch script page:", response.status);
  52. }
  53. },
  54. onerror: function(error) {
  55. console.error("Error checking for update:", error);
  56. }
  57. });
  58. }
  59.  
  60. function compareVersions(v1, v2) {
  61. const parts1 = v1.split('.').map(Number);
  62. const parts2 = v2.split('.').map(Number);
  63. for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
  64. const p1 = parts1[i] || 0;
  65. const p2 = parts2[i] || 0;
  66. if (p1 > p2) return 1;
  67. if (p1 < p2) return -1;
  68. }
  69. return 0;
  70. }
  71.  
  72. function main() {
  73. var myVars = document.myVars = { autoMove: false, autoRun: false, autoMatch: false, delay: 0.1, hasAutoMatched: false, gameEnded: false };
  74. var myFunctions = document.myFunctions = {};
  75. var currentStockfishVersion = "Stockfish API";
  76. var uiElementsLoaded = false;
  77. const stockfishAPI_URI = "https://stockfish.online/api/s/v2.php";
  78.  
  79. var stop_b = 0, stop_w = 0, s_br = 0, s_br2 = 0, s_wr = 0, s_wr2 = 0;
  80.  
  81. myFunctions.rescan = function() {
  82. console.log("Rescanning board...");
  83. var boardElement = document.querySelector('chess-board, wc-chess-board');
  84. if (!boardElement) {
  85. console.warn("No board element found. Using default FEN.");
  86. return "rnbqkbnr/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
  87. }
  88. var pieces = $(boardElement).find(".piece").map(function() { return this.className; }).get();
  89. if (!pieces.length) {
  90. console.warn("No pieces found. Using default FEN.");
  91. return "rnbqkbnr/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
  92. }
  93. var boardArray = Array(64).fill('');
  94. pieces.forEach(piece => {
  95. var classes = piece.split(' ');
  96. var squareClass = classes.find(c => c.startsWith('square-'));
  97. var pieceClass = classes.find(c => /^[wb][prnbqk]$/.test(c));
  98. if (squareClass && pieceClass) {
  99. var squareNum = squareClass.replace('square-', '');
  100. var file = parseInt(squareNum[0]) - 1;
  101. var rank = parseInt(squareNum[1]) - 1;
  102. var square = (7 - rank) * 8 + file;
  103. if (square >= 0 && square < 64) {
  104. var pieceChar = {'wp': 'P', 'bp': 'p', 'wr': 'R', 'br': 'r', 'wn': 'N', 'bn': 'n',
  105. 'wb': 'B', 'bb': 'b', 'wq': 'Q', 'bq': 'q', 'wk': 'K', 'bk': 'k'}[pieceClass];
  106. boardArray[square] = pieceChar;
  107. }
  108. }
  109. });
  110. var fen = '';
  111. for (var i = 0; i < 64; i++) {
  112. if (i % 8 === 0 && i > 0) fen += '/';
  113. var piece = boardArray[i];
  114. if (!piece) {
  115. var emptyCount = 1;
  116. while (i + 1 < 64 && !boardArray[i + 1] && (i + 1) % 8 !== 0) {
  117. emptyCount++;
  118. i++;
  119. }
  120. fen += emptyCount;
  121. } else {
  122. fen += piece;
  123. }
  124. }
  125. var turn = $('.coordinates').children().first().text() === "1" ? 'b' : 'w';
  126. var castling = (stop_w ? '' : 'KQ') + (stop_b ? '' : 'kq') || '-';
  127. fen += ` ${turn} ${castling} - 0 1`;
  128. console.log("Generated FEN:", fen);
  129. return fen;
  130. };
  131.  
  132. myFunctions.color = function(dat) {
  133. console.log("myFunctions.color CALLED with:", dat);
  134. const bestmoveUCI = dat.split(' ')[1];
  135. console.log("Extracted bestmove UCI:", bestmoveUCI);
  136. if (myVars.autoMove) myFunctions.movePiece(bestmoveUCI);
  137. else myFunctions.highlightMove(bestmoveUCI);
  138. isThinking = false;
  139. myFunctions.spinner();
  140. };
  141.  
  142. myFunctions.highlightMove = function(bestmoveUCI) {
  143. var res1 = bestmoveUCI.substring(0, 2), res2 = bestmoveUCI.substring(2, 4);
  144. $(board).prepend(`<div class="highlight square-${res2}" style="background-color: rgb(235, 97, 80); opacity: 0.71;"></div>`)
  145. .children(':first').delay(1800).queue(function() { $(this).remove(); });
  146. $(board).prepend(`<div class="highlight square-${res1}" style="background-color: rgb(235, 97, 80); opacity: 0.71;"></div>`)
  147. .children(':first').delay(1800).queue(function() { $(this).remove(); });
  148. console.log("Highlighted:", bestmoveUCI);
  149. };
  150.  
  151. myFunctions.movePiece = function(bestmoveUCI) {
  152. console.log("movePiece CALLED with:", bestmoveUCI);
  153. if (!board || !board.game) {
  154. console.error("Board or board.game not initialized!");
  155. return;
  156. }
  157. const fromSquare = bestmoveUCI.substring(0, 2);
  158. const toSquare = bestmoveUCI.substring(2, 4);
  159. const legalMoves = board.game.getLegalMoves();
  160. console.log("Legal moves:", legalMoves);
  161. let foundMove = legalMoves.find(move => move.from === fromSquare && move.to === toSquare);
  162. if (foundMove) {
  163. console.log("Executing move:", foundMove);
  164. board.game.move({ ...foundMove, promotion: 'q', animate: true, userGenerated: true });
  165. console.log("Move executed:", bestmoveUCI);
  166. } else {
  167. console.warn("No legal move found for:", bestmoveUCI);
  168. }
  169. };
  170.  
  171. myFunctions.reloadChessEngine = function() { console.log("Reload not needed for API."); };
  172. myFunctions.loadChessEngine = function() {
  173. console.log("Using Stockfish API.");
  174. if (uiElementsLoaded) $('#engineVersionText')[0].innerHTML = "Engine: <strong>Stockfish API</strong>";
  175. };
  176.  
  177. myFunctions.fetchBestMoveFromAPI = function(fen, depth) {
  178. const apiURL = `${stockfishAPI_URI}?fen=${encodeURIComponent(fen)}&depth=${depth}`;
  179. console.log(`Fetching from: ${apiURL}`);
  180. GM_xmlhttpRequest({
  181. method: "GET",
  182. url: apiURL,
  183. onload: function(response) {
  184. if (response.status === 200) {
  185. try {
  186. const jsonResponse = JSON.parse(response.responseText);
  187. if (jsonResponse.success) {
  188. console.log("API Response:", jsonResponse);
  189. myFunctions.color(jsonResponse.bestmove);
  190. } else {
  191. console.error("API failed:", jsonResponse);
  192. isThinking = false;
  193. myFunctions.spinner();
  194. }
  195. } catch (e) {
  196. console.error("API parse error:", e);
  197. isThinking = false;
  198. myFunctions.spinner();
  199. }
  200. } else {
  201. console.error("API error:", response.status);
  202. isThinking = false;
  203. myFunctions.spinner();
  204. }
  205. },
  206. onerror: function(error) {
  207. console.error("API request error:", error);
  208. isThinking = false;
  209. myFunctions.spinner();
  210. }
  211. });
  212. };
  213.  
  214. myFunctions.startNewGame = function() {
  215. console.log("Starting new game...");
  216. // Check for game-over "New <x> min" button in modal
  217. const modalNewGameButton = $('.game-over-modal-content .game-over-buttons-component .cc-button-component:not([aria-label="Rematch"])');
  218. if (modalNewGameButton.length) {
  219. modalNewGameButton[0].click();
  220. console.log("Clicked New <x> min button from game-over modal.");
  221. myVars.hasAutoMatched = true;
  222. return;
  223. }
  224.  
  225. // Check for game-over "New <x> min" button outside modal
  226. const newGameButton = $('.game-over-buttons-component .cc-button-component:not([aria-label="Rematch"])');
  227. if (newGameButton.length) {
  228. newGameButton[0].click();
  229. console.log("Clicked New <x> min button from game-over.");
  230. myVars.hasAutoMatched = true;
  231. return;
  232. }
  233.  
  234. // Check for guest prompt
  235. const guestButton = $('#guest-button.authentication-intro-guest');
  236. if (guestButton.length) {
  237. guestButton[0].click();
  238. console.log("Clicked Play as Guest.");
  239. setTimeout(() => {
  240. const playButton = $('.cc-button-component.cc-button-primary.cc-button-xx-large.cc-button-full');
  241. if (playButton.length) {
  242. playButton[0].click();
  243. console.log("Clicked Play button after guest prompt.");
  244. myVars.hasAutoMatched = true;
  245. } else {
  246. console.error("Play button not found after guest prompt!");
  247. }
  248. }, 500);
  249. return;
  250. }
  251.  
  252. // Fallback to sidebar "New Game" tab and "Play"
  253. const newGameTab = $('[data-tab="newGame"]');
  254. if (newGameTab.length) {
  255. newGameTab[0].click();
  256. console.log("Clicked New Game tab.");
  257. setTimeout(() => {
  258. const playButton = $('.cc-button-component.cc-button-primary.cc-button-xx-large.cc-button-full');
  259. if (playButton.length) {
  260. playButton[0].click();
  261. console.log("Clicked Play button.");
  262. myVars.hasAutoMatched = true;
  263. } else {
  264. console.error("Play button not found!");
  265. }
  266. }, 500);
  267. } else {
  268. console.error("New Game tab not found!");
  269. }
  270. };
  271.  
  272. myFunctions.declineRematch = function() {
  273. const declineButton = $('.cc-button-component.cc-button-secondary[aria-label="Decline"], .cc-button-component.cc-button-secondary:contains("Decline")');
  274. if (declineButton.length) {
  275. declineButton[0].click();
  276. console.log("Declined rematch.");
  277. return true;
  278. } else {
  279. console.log("No rematch decline button found.");
  280. return false;
  281. }
  282. };
  283.  
  284. var lastValue = 11, MAX_DEPTH = 15, MIN_DEPTH = 1;
  285.  
  286. myFunctions.runChessEngine = function(depth) {
  287. depth = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, depth));
  288. var fen = myFunctions.rescan();
  289. console.log(`Analyzing FEN: ${fen}, Depth: ${depth}`);
  290. isThinking = true;
  291. myFunctions.spinner();
  292. myFunctions.fetchBestMoveFromAPI(fen, depth);
  293. lastValue = depth;
  294. updateDepthDisplay();
  295. };
  296.  
  297. function updateDepthDisplay() {
  298. if (uiElementsLoaded && $('#depthText')[0]) $('#depthText')[0].innerHTML = `Depth: <strong>${lastValue}</strong>`;
  299. }
  300.  
  301. myFunctions.incrementDepth = function(delta) {
  302. lastValue = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, lastValue + delta));
  303. updateDepthDisplay();
  304. };
  305.  
  306. myFunctions.autoRun = function() {
  307. if (board && board.game && board.game.getTurn() === board.game.getPlayingAs()) {
  308. myFunctions.runChessEngine(lastValue);
  309. }
  310. };
  311.  
  312. document.onkeydown = function(e) {
  313. switch (e.keyCode) {
  314. case 81: myFunctions.runChessEngine(1); break; // Q
  315. case 87: myFunctions.runChessEngine(2); break; // W
  316. case 69: myFunctions.runChessEngine(3); break; // E
  317. case 82: myFunctions.runChessEngine(4); break; // R
  318. case 84: myFunctions.runChessEngine(5); break; // T
  319. case 89: myFunctions.runChessEngine(6); break; // Y
  320. case 85: myFunctions.runChessEngine(7); break; // U
  321. case 73: myFunctions.runChessEngine(8); break; // I
  322. case 79: myFunctions.runChessEngine(9); break; // O
  323. case 80: myFunctions.runChessEngine(10); break; // P
  324. case 65: myFunctions.runChessEngine(11); break; // A
  325. case 83: myFunctions.runChessEngine(12); break; // S
  326. case 68: myFunctions.runChessEngine(13); break; // D
  327. case 70: myFunctions.runChessEngine(14); break; // F
  328. case 71: myFunctions.runChessEngine(15); break; // G
  329. case 187: myFunctions.incrementDepth(1); break; // +
  330. case 189: myFunctions.incrementDepth(-1); break;// -
  331. }
  332. };
  333.  
  334. myFunctions.spinner = function() {
  335. if (uiElementsLoaded && $('#overlay')[0]) {
  336. $('#overlay')[0].style.display = isThinking ? 'block' : 'none';
  337. }
  338. };
  339.  
  340. let dynamicStyles = null;
  341. function addAnimation(body) {
  342. if (!dynamicStyles) {
  343. dynamicStyles = document.createElement('style');
  344. document.head.appendChild(dynamicStyles);
  345. }
  346. dynamicStyles.sheet.insertRule(body, dynamicStyles.length);
  347. }
  348.  
  349. var loaded = false;
  350. myFunctions.loadEx = function() {
  351. try {
  352. console.log("Attempting to load UI...");
  353. board = document.querySelector('chess-board, wc-chess-board');
  354. var div = document.createElement('div');
  355. div.innerHTML = `
  356. <div style="margin: 8px; padding: 10px; background: white; border: 1px solid #000; border-radius: 5px;">
  357. <p id="depthText">Depth: <strong>${lastValue}</strong></p>
  358. <button id="depthMinus">-</button>
  359. <button id="depthPlus">+</button>
  360. <p style="font-size: 12px;">Keys: Q-G (1-15), +/-</p>
  361. <p id="engineVersionText">Engine: Stockfish API</p>
  362. <label><input type="checkbox" id="autoRun"> Auto Run</label><br>
  363. <label><input type="checkbox" id="autoMove"> Auto Move</label><br>
  364. <label><input type="checkbox" id="autoMatch"> Auto-Match</label><br>
  365. <label>Min Delay (s): <input type="number" id="timeDelayMin" min="0.1" value="0.1" step="0.1" style="width: 60px;"></label><br>
  366. <label>Max Delay (s): <input type="number" id="timeDelayMax" min="0.1" value="1" step="0.1" style="width: 60px;"></label>
  367. </div>`;
  368. div.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 10000;';
  369. document.body.appendChild(div);
  370.  
  371. setTimeout(() => {
  372. $('#depthPlus').off('click').on('click', () => myFunctions.incrementDepth(1));
  373. $('#depthMinus').off('click').on('click', () => myFunctions.incrementDepth(-1));
  374. $('#autoMatch').on('change', () => {
  375. myVars.autoMatch = $('#autoMatch')[0].checked;
  376. if (myVars.autoMatch && !myVars.hasAutoMatched) {
  377. myFunctions.startNewGame();
  378. }
  379. });
  380. console.log("Event listeners bound.");
  381. }, 100);
  382.  
  383. var spinCont = document.createElement('div');
  384. spinCont.id = 'overlay';
  385. spinCont.style.cssText = 'display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);';
  386. div.appendChild(spinCont);
  387. var spinr = document.createElement('div');
  388. spinr.style.cssText = "height: 64px; width: 64px; animation: rotate 0.8s infinite linear; border: 5px solid firebrick; border-right-color: transparent; border-radius: 50%;";
  389. spinCont.appendChild(spinr);
  390. addAnimation(`@keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }`);
  391.  
  392. loaded = true;
  393. uiElementsLoaded = true;
  394. console.log("UI loaded successfully.");
  395. myFunctions.loadChessEngine();
  396. checkForUpdate();
  397. } catch (error) {
  398. console.error("loadEx error:", error);
  399. }
  400. };
  401.  
  402. function other(delay) {
  403. var endTime = Date.now() + delay;
  404. var timer = setInterval(() => {
  405. if (Date.now() >= endTime) {
  406. myFunctions.autoRun();
  407. canGo = true;
  408. clearInterval(timer);
  409. }
  410. }, 10);
  411. }
  412.  
  413. const waitForChessBoard = setInterval(() => {
  414. if (!loaded) {
  415. myFunctions.loadEx();
  416. } else {
  417. if (!board) board = document.querySelector('chess-board, wc-chess-board');
  418. myVars.autoRun = $('#autoRun')[0].checked;
  419. myVars.autoMove = $('#autoMove')[0].checked;
  420. myVars.autoMatch = $('#autoMatch')[0].checked;
  421. let minDel = parseFloat($('#timeDelayMin')[0].value) || 0.1;
  422. let maxDel = parseFloat($('#timeDelayMax')[0].value) || 1;
  423. myVars.delay = Math.random() * (maxDel - minDel) + minDel;
  424. myFunctions.spinner();
  425. myTurn = board && board.game && board.game.getTurn() === board.game.getPlayingAs();
  426. updateDepthDisplay();
  427.  
  428. // Check for game end
  429. const gameOver = document.querySelector('.game-over-message-component') || document.querySelector('.game-result');
  430. if (gameOver && !myVars.gameEnded) {
  431. console.log("Game ended detected (chat or result).");
  432. myVars.gameEnded = true;
  433. if (myVars.autoMatch) {
  434. myFunctions.declineRematch(); // Check for rematch once on game end
  435. setTimeout(() => {
  436. myVars.hasAutoMatched = false; // Reset to allow new game
  437. myFunctions.startNewGame(); // Attempt to start new game
  438. }, 1000); // Delay to ensure modal loads
  439. }
  440. } else if (!gameOver && myVars.gameEnded) {
  441. myVars.gameEnded = false; // Reset when game restarts
  442. }
  443.  
  444. // Check for game-over modal and auto-click "New <x> min" if Auto-Match is enabled
  445. const gameOverModal = $('.game-over-modal-content');
  446. if (myVars.autoMatch && gameOverModal.length && !myVars.hasAutoMatched) {
  447. console.log("Game over modal detected.");
  448. const newGameButton = gameOverModal.find('.game-over-buttons-component .cc-button-component:not([aria-label="Rematch"])');
  449. if (newGameButton.length) {
  450. newGameButton[0].click();
  451. console.log("Clicked New <x> min button from game-over modal in interval.");
  452. myVars.hasAutoMatched = true;
  453. } else {
  454. console.error("New <x> min button not found in game-over modal!");
  455. }
  456. }
  457.  
  458. if (myVars.autoRun && canGo && !isThinking && myTurn) {
  459. canGo = false;
  460. other(myVars.delay * 1000);
  461. }
  462. }
  463. }, 500);
  464.  
  465. setTimeout(() => {
  466. if (!loaded) myFunctions.loadEx();
  467. }, 2000);
  468. }
  469.  
  470. var isThinking = false, canGo = true, myTurn = false, board;
  471.  
  472. window.addEventListener("load", () => main());