Greasy Fork is available in English.

Chess.com Stockfish Bot

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

  1. // ==UserScript==
  2. // @name Chess.com Stockfish Bot
  3. // @namespace BottleOrg Scripts
  4. // @version 1.7.0
  5. // @description Chess.com Stockfish Bot with Enhanced Auto-Match using Stockfish Online API
  6. // @author [REDACTED] - Rightful Owner(qbaonguyen050@gmail.com) + ChatGPT-4o Reasoning, GPT-3o mini advanced reasoning, a bit of Gemini 2.5 Pro
  7. // @license Chess.com Bot/Cheat by BottleOrg(qbaonguyen050@gmail.com)
  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. let myVars = document.myVars = {
  74. autoMove: false,
  75. autoRun: false,
  76. autoMatch: false,
  77. delay: 0.1,
  78. hasAutoMatched: false,
  79. gameEnded: false
  80. };
  81. let myFunctions = document.myFunctions = {};
  82. const currentStockfishVersion = "Stockfish API";
  83. let uiElementsLoaded = false;
  84. const stockfishAPI_URI = "https://stockfish.online/api/s/v2.php";
  85. let stop_b = 0, stop_w = 0;
  86. let board;
  87.  
  88. // Rescan board to generate FEN string
  89. myFunctions.rescan = function() {
  90. console.log("Rescanning board...");
  91. const boardElement = document.querySelector('chess-board, wc-chess-board');
  92. if (!boardElement) {
  93. console.warn("No board element found. Using default FEN.");
  94. return "rnbqkbnr/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
  95. }
  96. const pieces = $(boardElement).find(".piece").map(function() {
  97. return this.className;
  98. }).get();
  99. if (!pieces.length) {
  100. console.warn("No pieces found. Using default FEN.");
  101. return "rnbqkbnr/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
  102. }
  103. let boardArray = Array(64).fill('');
  104. pieces.forEach(piece => {
  105. const classes = piece.split(' ');
  106. const squareClass = classes.find(c => c.startsWith('square-'));
  107. const pieceClass = classes.find(c => /^[wb][prnbqk]$/.test(c));
  108. if (squareClass && pieceClass) {
  109. const squareNum = squareClass.replace('square-', '');
  110. const file = parseInt(squareNum[0]) - 1;
  111. const rank = parseInt(squareNum[1]) - 1;
  112. const square = (7 - rank) * 8 + file;
  113. if (square >= 0 && square < 64) {
  114. const pieceChar = {
  115. 'wp': 'P', 'bp': 'p', 'wr': 'R', 'br': 'r',
  116. 'wn': 'N', 'bn': 'n', 'wb': 'B', 'bb': 'b',
  117. 'wq': 'Q', 'bq': 'q', 'wk': 'K', 'bk': 'k'
  118. }[pieceClass];
  119. boardArray[square] = pieceChar;
  120. }
  121. }
  122. });
  123. let fen = '';
  124. for (let i = 0; i < 64; i++) {
  125. if (i % 8 === 0 && i > 0) fen += '/';
  126. const piece = boardArray[i];
  127. if (!piece) {
  128. let emptyCount = 1;
  129. while (i + 1 < 64 && !boardArray[i + 1] && (i + 1) % 8 !== 0) {
  130. emptyCount++;
  131. i++;
  132. }
  133. fen += emptyCount;
  134. } else {
  135. fen += piece;
  136. }
  137. }
  138. const turn = $('.coordinates').children().first().text() === "1" ? 'b' : 'w';
  139. const castling = ((stop_w ? '' : 'KQ') + (stop_b ? '' : 'kq')) || '-';
  140. fen += ` ${turn} ${castling} - 0 1`;
  141. console.log("Generated FEN:", fen);
  142. return fen;
  143. };
  144.  
  145. myFunctions.color = function(dat) {
  146. console.log("myFunctions.color CALLED with:", dat);
  147. const bestmoveUCI = dat.split(' ')[1];
  148. console.log("Extracted bestmove UCI:", bestmoveUCI);
  149. if (myVars.autoMove) myFunctions.movePiece(bestmoveUCI);
  150. else myFunctions.highlightMove(bestmoveUCI);
  151. isThinking = false;
  152. myFunctions.spinner();
  153. };
  154.  
  155. myFunctions.highlightMove = function(bestmoveUCI) {
  156. const res1 = bestmoveUCI.substring(0, 2);
  157. const res2 = bestmoveUCI.substring(2, 4);
  158. $(board).prepend(`<div class="highlight square-${res2}" style="background-color: rgb(235, 97, 80); opacity: 0.71;"></div>`)
  159. .children(':first').delay(1800).queue(function() { $(this).remove(); });
  160. $(board).prepend(`<div class="highlight square-${res1}" style="background-color: rgb(235, 97, 80); opacity: 0.71;"></div>`)
  161. .children(':first').delay(1800).queue(function() { $(this).remove(); });
  162. console.log("Highlighted:", bestmoveUCI);
  163. };
  164.  
  165. myFunctions.movePiece = function(bestmoveUCI) {
  166. console.log("movePiece CALLED with:", bestmoveUCI);
  167. if (!board || !board.game) {
  168. console.error("Board or board.game not initialized!");
  169. return;
  170. }
  171. const fromSquare = bestmoveUCI.substring(0, 2);
  172. const toSquare = bestmoveUCI.substring(2, 4);
  173. const legalMoves = board.game.getLegalMoves();
  174. console.log("Legal moves:", legalMoves);
  175. const foundMove = legalMoves.find(move => move.from === fromSquare && move.to === toSquare);
  176. if (foundMove) {
  177. console.log("Executing move:", foundMove);
  178. board.game.move({ ...foundMove, promotion: 'q', animate: true, userGenerated: true });
  179. console.log("Move executed:", bestmoveUCI);
  180. } else {
  181. console.warn("No legal move found for:", bestmoveUCI);
  182. }
  183. };
  184.  
  185. myFunctions.reloadChessEngine = function() {
  186. console.log("Reload not needed for API.");
  187. };
  188.  
  189. myFunctions.loadChessEngine = function() {
  190. console.log("Using Stockfish API.");
  191. if (uiElementsLoaded) $('#engineVersionText')[0].innerHTML = "Engine: <strong>Stockfish API</strong>";
  192. };
  193.  
  194. myFunctions.fetchBestMoveFromAPI = function(fen, depth) {
  195. const apiURL = `${stockfishAPI_URI}?fen=${encodeURIComponent(fen)}&depth=${depth}`;
  196. console.log(`Fetching from: ${apiURL}`);
  197. GM_xmlhttpRequest({
  198. method: "GET",
  199. url: apiURL,
  200. onload: function(response) {
  201. if (response.status === 200) {
  202. try {
  203. const jsonResponse = JSON.parse(response.responseText);
  204. if (jsonResponse.success) {
  205. console.log("API Response:", jsonResponse);
  206. myFunctions.color(jsonResponse.bestmove);
  207. } else {
  208. console.error("API failed:", jsonResponse);
  209. isThinking = false;
  210. myFunctions.spinner();
  211. }
  212. } catch (e) {
  213. console.error("API parse error:", e);
  214. isThinking = false;
  215. myFunctions.spinner();
  216. }
  217. } else {
  218. console.error("API error:", response.status);
  219. isThinking = false;
  220. myFunctions.spinner();
  221. }
  222. },
  223. onerror: function(error) {
  224. console.error("API request error:", error);
  225. isThinking = false;
  226. myFunctions.spinner();
  227. }
  228. });
  229. };
  230.  
  231. myFunctions.startNewGame = function() {
  232. console.log("Starting new game...");
  233. const modalNewGameButton = $('.game-over-modal-content .game-over-buttons-component .cc-button-component:not([aria-label="Rematch"])');
  234. if (modalNewGameButton.length) {
  235. modalNewGameButton[0].click();
  236. console.log("Clicked New <x> min button from game-over modal.");
  237. myVars.hasAutoMatched = true;
  238. return;
  239. }
  240. const newGameButton = $('.game-over-buttons-component .cc-button-component:not([aria-label="Rematch"])');
  241. if (newGameButton.length) {
  242. newGameButton[0].click();
  243. console.log("Clicked New <x> min button from game-over.");
  244. myVars.hasAutoMatched = true;
  245. return;
  246. }
  247. const guestButton = $('#guest-button.authentication-intro-guest');
  248. if (guestButton.length) {
  249. guestButton[0].click();
  250. console.log("Clicked Play as Guest.");
  251. setTimeout(() => {
  252. const playButton = $('.cc-button-component.cc-button-primary.cc-button-xx-large.cc-button-full');
  253. if (playButton.length) {
  254. playButton[0].click();
  255. console.log("Clicked Play button after guest prompt.");
  256. myVars.hasAutoMatched = true;
  257. } else {
  258. console.error("Play button not found after guest prompt!");
  259. }
  260. }, 500);
  261. return;
  262. }
  263. const newGameTab = $('[data-tab="newGame"]');
  264. if (newGameTab.length) {
  265. newGameTab[0].click();
  266. console.log("Clicked New Game tab.");
  267. setTimeout(() => {
  268. const playButton = $('.cc-button-component.cc-button-primary.cc-button-xx-large.cc-button-full');
  269. if (playButton.length) {
  270. playButton[0].click();
  271. console.log("Clicked Play button.");
  272. myVars.hasAutoMatched = true;
  273. } else {
  274. console.error("Play button not found!");
  275. }
  276. }, 500);
  277. } else {
  278. console.error("New Game tab not found!");
  279. }
  280. };
  281.  
  282. myFunctions.declineRematch = function() {
  283. const declineButton = $('.cc-button-component.cc-button-secondary[aria-label="Decline"], .cc-button-component.cc-button-secondary:contains("Decline")');
  284. if (declineButton.length) {
  285. declineButton[0].click();
  286. console.log("Declined rematch.");
  287. return true;
  288. } else {
  289. console.log("No rematch decline button found.");
  290. return false;
  291. }
  292. };
  293.  
  294. let lastValue = 11, MAX_DEPTH = 15, MIN_DEPTH = 1;
  295.  
  296. myFunctions.runChessEngine = function(depth) {
  297. depth = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, depth));
  298. const fen = myFunctions.rescan();
  299. console.log(`Analyzing FEN: ${fen}, Depth: ${depth}`);
  300. isThinking = true;
  301. myFunctions.spinner();
  302. myFunctions.fetchBestMoveFromAPI(fen, depth);
  303. lastValue = depth;
  304. updateDepthDisplay();
  305. };
  306.  
  307. function updateDepthDisplay() {
  308. if (uiElementsLoaded && $('#depthText')[0]) {
  309. $('#depthText')[0].innerHTML = `Depth: <strong>${lastValue}</strong>`;
  310. }
  311. }
  312.  
  313. myFunctions.incrementDepth = function(delta) {
  314. lastValue = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, lastValue + delta));
  315. updateDepthDisplay();
  316. };
  317.  
  318. myFunctions.autoRun = function() {
  319. if (board && board.game && board.game.getTurn() === board.game.getPlayingAs()) {
  320. myFunctions.runChessEngine(lastValue);
  321. }
  322. };
  323.  
  324. document.onkeydown = function(e) {
  325. switch (e.keyCode) {
  326. case 81: myFunctions.runChessEngine(1); break; // Q
  327. case 87: myFunctions.runChessEngine(2); break; // W
  328. case 69: myFunctions.runChessEngine(3); break; // E
  329. case 82: myFunctions.runChessEngine(4); break; // R
  330. case 84: myFunctions.runChessEngine(5); break; // T
  331. case 89: myFunctions.runChessEngine(6); break; // Y
  332. case 85: myFunctions.runChessEngine(7); break; // U
  333. case 73: myFunctions.runChessEngine(8); break; // I
  334. case 79: myFunctions.runChessEngine(9); break; // O
  335. case 80: myFunctions.runChessEngine(10); break; // P
  336. case 65: myFunctions.runChessEngine(11); break; // A
  337. case 83: myFunctions.runChessEngine(12); break; // S
  338. case 68: myFunctions.runChessEngine(13); break; // D
  339. case 70: myFunctions.runChessEngine(14); break; // F
  340. case 71: myFunctions.runChessEngine(15); break; // G
  341. case 187: myFunctions.incrementDepth(1); break; // +
  342. case 189: myFunctions.incrementDepth(-1); break; // -
  343. }
  344. };
  345.  
  346. myFunctions.spinner = function() {
  347. if (uiElementsLoaded && $('#overlay')[0]) {
  348. $('#overlay')[0].style.display = isThinking ? 'block' : 'none';
  349. }
  350. };
  351.  
  352. let dynamicStyles = null;
  353. function addAnimation(rule) {
  354. if (!dynamicStyles) {
  355. dynamicStyles = document.createElement('style');
  356. document.head.appendChild(dynamicStyles);
  357. }
  358. dynamicStyles.sheet.insertRule(rule, dynamicStyles.sheet.cssRules.length);
  359. }
  360.  
  361. // Add extra UI styles for a "chef's kiss" look with LED hover effects
  362. function addUIStyles() {
  363. const style = document.createElement('style');
  364. style.innerHTML = `
  365. .chess-ui-panel {
  366. width: 280px;
  367. background: linear-gradient(135deg, #ffe6e6, #fff5f5);
  368. border: 2px solid #ffcccc;
  369. border-radius: 12px;
  370. box-shadow: 4px 4px 16px rgba(0,0,0,0.3);
  371. animation: fadeIn 1s ease-out;
  372. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  373. user-select: none;
  374. }
  375. /* Header for dragging */
  376. .chess-ui-header {
  377. background: #ffcccc;
  378. border-bottom: 1px solid #ffb3b3;
  379. border-top-left-radius: 10px;
  380. border-top-right-radius: 10px;
  381. padding: 8px;
  382. cursor: move;
  383. font-weight: bold;
  384. color: #800000;
  385. text-align: center;
  386. }
  387. .chess-ui-body {
  388. padding: 10px;
  389. }
  390. .chess-ui-panel button {
  391. background: #e91e63;
  392. border: none;
  393. color: white;
  394. padding: 8px 12px;
  395. margin: 4px 2px;
  396. border-radius: 5px;
  397. cursor: pointer;
  398. transition: box-shadow 0.3s ease, transform 0.2s ease;
  399. }
  400. .chess-ui-panel button:hover {
  401. box-shadow: 0 0 8px #ff69b4;
  402. transform: scale(1.05);
  403. }
  404. .chess-ui-panel input[type="checkbox"],
  405. .chess-ui-panel input[type="number"],
  406. .chess-ui-panel input[type="text"] {
  407. margin: 5px 0;
  408. padding: 4px;
  409. border: 1px solid #ffcccc;
  410. border-radius: 4px;
  411. }
  412. @keyframes fadeIn {
  413. from { opacity: 0; transform: translateY(-10px); }
  414. to { opacity: 1; transform: translateY(0); }
  415. }
  416. `;
  417. document.head.appendChild(style);
  418. }
  419.  
  420. // Make only the header draggable so the panel doesn't stretch or disappear.
  421. function makeDraggable(panel, header) {
  422. let offsetX, offsetY;
  423. header.addEventListener('mousedown', dragMouseDown);
  424. function dragMouseDown(e) {
  425. e.preventDefault();
  426. const rect = panel.getBoundingClientRect();
  427. offsetX = e.clientX - rect.left;
  428. offsetY = e.clientY - rect.top;
  429. document.addEventListener('mousemove', elementDrag);
  430. document.addEventListener('mouseup', closeDragElement);
  431. }
  432. function elementDrag(e) {
  433. e.preventDefault();
  434. panel.style.left = (e.clientX - offsetX) + "px";
  435. panel.style.top = (e.clientY - offsetY) + "px";
  436. }
  437. function closeDragElement() {
  438. document.removeEventListener('mousemove', elementDrag);
  439. document.removeEventListener('mouseup', closeDragElement);
  440. }
  441. }
  442.  
  443. let loaded = false;
  444. myFunctions.loadEx = function() {
  445. try {
  446. console.log("Attempting to load UI...");
  447. board = document.querySelector('chess-board, wc-chess-board');
  448. addUIStyles();
  449. // Create panel structure with a header and a body
  450. const panel = document.createElement('div');
  451. panel.className = "chess-ui-panel";
  452. panel.style.position = 'fixed';
  453. panel.style.top = '10px';
  454. panel.style.right = '10px';
  455. panel.style.zIndex = '10000';
  456. panel.innerHTML = `
  457. <div class="chess-ui-header">Stockfish Bot</div>
  458. <div class="chess-ui-body">
  459. <p id="depthText">Depth: <strong>${lastValue}</strong></p>
  460. <button id="depthMinus">-</button>
  461. <button id="depthPlus">+</button>
  462. <p style="font-size: 12px;">Keys: Q-G (1-15), +/-</p>
  463. <p id="engineVersionText">Engine: Stockfish API 😘</p>
  464. <label><input type="checkbox" id="autoRun"> Auto Run</label><br>
  465. <label><input type="checkbox" id="autoMove"> Auto Move</label><br>
  466. <label><input type="checkbox" id="autoMatch"> Auto-Match</label><br>
  467. <label>Min Delay (s): <input type="number" id="timeDelayMin" min="0.1" value="0.1" step="0.1" style="width: 60px;"></label><br>
  468. <label>Max Delay (s): <input type="number" id="timeDelayMax" min="0.1" value="1" step="0.1" style="width: 60px;"></label><br>
  469. <label>Custom Command: <input type="text" id="customCmd" placeholder="Type here..."></label>
  470. </div>
  471. `;
  472. document.body.appendChild(panel);
  473. // Make only the header draggable.
  474. const header = panel.querySelector('.chess-ui-header');
  475. makeDraggable(panel, header);
  476.  
  477. setTimeout(() => {
  478. $('#depthPlus').off('click').on('click', () => myFunctions.incrementDepth(1));
  479. $('#depthMinus').off('click').on('click', () => myFunctions.incrementDepth(-1));
  480. $('#autoMatch').on('change', () => {
  481. myVars.autoMatch = $('#autoMatch')[0].checked;
  482. if (myVars.autoMatch && !myVars.hasAutoMatched) {
  483. myFunctions.startNewGame();
  484. }
  485. });
  486. console.log("Event listeners bound.");
  487. }, 100);
  488.  
  489. const spinCont = document.createElement('div');
  490. spinCont.id = 'overlay';
  491. spinCont.style.cssText = 'display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);';
  492. panel.appendChild(spinCont);
  493. const spinr = document.createElement('div');
  494. spinr.style.cssText = "height: 64px; width: 64px; animation: rotate 0.8s infinite linear; border: 5px solid firebrick; border-right-color: transparent; border-radius: 50%;";
  495. spinCont.appendChild(spinr);
  496. addAnimation(`@keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }`);
  497.  
  498. loaded = true;
  499. uiElementsLoaded = true;
  500. console.log("UI loaded successfully.");
  501. myFunctions.loadChessEngine();
  502. checkForUpdate();
  503. } catch (error) {
  504. console.error("loadEx error:", error);
  505. }
  506. };
  507.  
  508. function other(delay) {
  509. const endTime = Date.now() + delay;
  510. const timer = setInterval(() => {
  511. if (Date.now() >= endTime) {
  512. myFunctions.autoRun();
  513. canGo = true;
  514. clearInterval(timer);
  515. }
  516. }, 10);
  517. }
  518.  
  519. const waitForChessBoard = setInterval(() => {
  520. if (!loaded) {
  521. myFunctions.loadEx();
  522. } else {
  523. if (!board) board = document.querySelector('chess-board, wc-chess-board');
  524. myVars.autoRun = $('#autoRun')[0].checked;
  525. myVars.autoMove = $('#autoMove')[0].checked;
  526. myVars.autoMatch = $('#autoMatch')[0].checked;
  527. const minDel = parseFloat($('#timeDelayMin')[0].value) || 0.1;
  528. const maxDel = parseFloat($('#timeDelayMax')[0].value) || 1;
  529. myVars.delay = Math.random() * (maxDel - minDel) + minDel;
  530. myFunctions.spinner();
  531. myTurn = board && board.game && board.game.getTurn() === board.game.getPlayingAs();
  532. updateDepthDisplay();
  533.  
  534. const gameOver = document.querySelector('.game-over-message-component') || document.querySelector('.game-result');
  535. if (gameOver && !myVars.gameEnded) {
  536. console.log("Game ended detected (chat or result).");
  537. myVars.gameEnded = true;
  538. if (myVars.autoMatch) {
  539. myFunctions.declineRematch();
  540. setTimeout(() => {
  541. myVars.hasAutoMatched = false;
  542. myFunctions.startNewGame();
  543. }, 1000);
  544. }
  545. } else if (!gameOver && myVars.gameEnded) {
  546. myVars.gameEnded = false;
  547. }
  548.  
  549. const gameOverModal = $('.game-over-modal-content');
  550. if (myVars.autoMatch && gameOverModal.length && !myVars.hasAutoMatched) {
  551. console.log("Game over modal detected.");
  552. const newGameButton = gameOverModal.find('.game-over-buttons-component .cc-button-component:not([aria-label="Rematch"])');
  553. if (newGameButton.length) {
  554. newGameButton[0].click();
  555. console.log("Clicked New <x> min button from game-over modal in interval.");
  556. myVars.hasAutoMatched = true;
  557. } else {
  558. console.error("New <x> min button not found in game-over modal!");
  559. }
  560. }
  561.  
  562. if (myVars.autoRun && canGo && !isThinking && myTurn) {
  563. canGo = false;
  564. other(myVars.delay * 1000);
  565. }
  566. }
  567. }, 500);
  568.  
  569. setTimeout(() => {
  570. if (!loaded) myFunctions.loadEx();
  571. }, 2000);
  572. }
  573.  
  574. let isThinking = false, canGo = true, myTurn = false, board;
  575. window.addEventListener("load", () => main());