Tic Tac Toe AI for papergames

AI plays Tic-Tac-Toe for you on papergames.io. Have fun and destroy some nerds 😃!!

安装此脚本
作者推荐脚本

您可能也喜欢Connect 4 AI for papergames

安装此脚本
  1. // ==UserScript==
  2. // @name Tic Tac Toe AI for papergames
  3. // @namespace https://github.com/longkidkoolstar
  4. // @version 3.0
  5. // @description AI plays Tic-Tac-Toe for you on papergames.io. Have fun and destroy some nerds 😃!!
  6. // @author longkidkoolstar
  7. // @icon https://th.bing.com/th/id/R.3502d1ca849b062acb85cf68a8c48bcd?rik=LxTvt1UpLC2y2g&pid=ImgRaw&r=0
  8. // @match https://papergames.io/*
  9. // @license none
  10. // @grant GM.getValue
  11. // @grant GM.setValue
  12. // @grant GM.deleteValue
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. var depth;
  19.  
  20. // Function to check if the element is visible
  21. function isElementVisible(element) {
  22. return element && element.style.display !== 'none';
  23. }
  24.  
  25. // Function to check for the element and click it when it becomes visible
  26. function waitForElementAndClick(targetElementSelector, triggerElementSelector, pollingInterval) {
  27. var xMark = document.querySelector(targetElementSelector);
  28. var countDown = document.querySelector(triggerElementSelector);
  29.  
  30. var intervalId = setInterval(function() {
  31. // Check if the countDown element is now visible
  32. if (isElementVisible(countDown)) {
  33. console.log("Element is visible. Clicking.");
  34. xMark.click();
  35. clearInterval(intervalId); // Stop polling
  36. }
  37. }, pollingInterval);
  38. }
  39.  
  40. // Start polling every 1 second (adjust the interval as needed)
  41. waitForElementAndClick('svg.fa-xmark', 'app-count-down span', 1000);
  42.  
  43. function getBoardState() {
  44. var boardState = [];
  45. var gridItems = document.querySelectorAll('.grid.s-3x3 .grid-item');
  46. for (var i = 0; i < 3; i++) {
  47. var row = [];
  48. for (var j = 0; j < 3; j++) {
  49. var cell = gridItems[i * 3 + j];
  50. var svg = cell.querySelector('svg');
  51. if (svg) {
  52. var label = svg.getAttribute('aria-label');
  53. if (label.toLowerCase().includes('x')) {
  54. row.push('x');
  55. } else if (label.toLowerCase().includes('o') || label.toLowerCase().includes('circle')) {
  56. row.push('o');
  57. } else {
  58. row.push('_');
  59. }
  60. } else {
  61. row.push('_'); // An empty cell
  62. }
  63. }
  64. boardState.push(row);
  65. }
  66. return boardState;
  67. }
  68.  
  69. function simulateCellClick(row, col) {
  70. var gridItems = document.querySelectorAll('.grid.s-3x3 .grid-item');
  71. var cell = gridItems[row * 3 + col];
  72. if (cell) {
  73. var event = new MouseEvent('click', {
  74. bubbles: true,
  75. cancelable: true,
  76. //view: window
  77. });
  78. cell.dispatchEvent(event);
  79. }
  80. }
  81.  
  82. var prevChronometerValue = null;
  83.  
  84. // Check if username is stored in GM storage
  85. GM.getValue('username').then(function(username) {
  86. if (!username) {
  87. // Alert the user
  88. alert('Username is not stored in GM storage.');
  89.  
  90. // Prompt the user to enter the username
  91. username = prompt('Please enter your Papergames username (case-sensitive):');
  92.  
  93. // Save the username to GM storage
  94. GM.setValue('username', username);
  95. }
  96. });
  97.  
  98. function logout() {
  99. GM.deleteValue('username');
  100. location.reload();
  101. }
  102.  
  103. function createLogoutButton() {
  104. var logoutButton = document.createElement('button');
  105. logoutButton.textContent = 'Logout';
  106. logoutButton.style.position = 'fixed';
  107. logoutButton.style.bottom = '20px';
  108. logoutButton.style.right = '20px';
  109. logoutButton.style.zIndex = '9999';
  110. logoutButton.style.color = 'white'; // Set the text color to white
  111. logoutButton.classList.add('btn', 'btn-secondary', 'mb-2', 'ng-star-inserted');
  112. logoutButton.addEventListener('click', logout);
  113. logoutButton.addEventListener('mouseover', function() {
  114. logoutButton.style.opacity = '0.5'; // Dim the button when hovered over
  115. });
  116. logoutButton.addEventListener('mouseout', function() {
  117. logoutButton.style.opacity = '1'; // Restore the button opacity when mouse leaves
  118. });
  119. document.body.appendChild(logoutButton);
  120. }
  121. createLogoutButton();
  122.  
  123. //------------------------------------------------
  124.  
  125. (function() {
  126. 'use strict';
  127.  
  128. // Create a container for the dropdown
  129. var dropdownContainer = document.createElement('div');
  130. dropdownContainer.style.position = 'fixed';
  131. dropdownContainer.style.bottom = '20px';
  132. dropdownContainer.style.left = '20px';
  133. dropdownContainer.style.zIndex = '9998';
  134. dropdownContainer.style.backgroundColor = '#1b2837';
  135. dropdownContainer.style.border = '1px solid #18bc9c';
  136. dropdownContainer.style.borderRadius = '5px';
  137.  
  138. // Create a button to toggle the dropdown
  139. var toggleButton = document.createElement('button');
  140. toggleButton.textContent = 'Settings';
  141. toggleButton.style.padding = '5px 10px';
  142. toggleButton.style.border = 'none';
  143. toggleButton.classList.add('btn', 'btn-secondary', 'mb-2', 'ng-star-inserted');
  144. toggleButton.style.backgroundColor = '#007bff';
  145. toggleButton.style.color = 'white';
  146. toggleButton.style.borderRadius = '5px';
  147. toggleButton.addEventListener('mouseover', function() {
  148. toggleButton.style.opacity = '0.5'; // Dim the button when hovered over
  149. });
  150. toggleButton.addEventListener('mouseout', function() {
  151. toggleButton.style.opacity = '1'; // Restore the button opacity when mouse leaves
  152. });
  153.  
  154. // Create the dropdown content
  155. var dropdownContent = document.createElement('div');
  156. dropdownContent.style.display = 'none';
  157. dropdownContent.style.padding = '8px';
  158.  
  159. // Create the "Auto Queue" tab
  160. var autoQueueTab = document.createElement('div');
  161. autoQueueTab.textContent = 'Auto Queue';
  162. autoQueueTab.style.padding = '5px 0';
  163. autoQueueTab.style.cursor = 'pointer';
  164.  
  165. // Create the "Depth Slider" tab
  166. var depthSliderTab = document.createElement('div');
  167. depthSliderTab.textContent = 'Depth Slider';
  168. depthSliderTab.style.padding = '5px 0';
  169. depthSliderTab.style.cursor = 'pointer';
  170.  
  171. // Create the settings for "Auto Queue"
  172. var autoQueueSettings = document.createElement('div');
  173. autoQueueSettings.textContent = 'Auto Queue Settings';
  174. autoQueueSettings.style.display = 'none'; // Initially hidden
  175. autoQueueSettings.style.padding = '10px';
  176.  
  177. // Create the settings for "Depth Slider"
  178. var depthSliderSettings = document.createElement('div');
  179. depthSliderSettings.style.display = 'none'; // Initially displayed for this tab
  180. depthSliderSettings.style.padding = '10px';
  181.  
  182. // Create the depth slider
  183. var depthSlider = document.createElement('input');
  184. depthSlider.type = 'range';
  185. depthSlider.min = '1';
  186. depthSlider.max = '100';
  187. GM.getValue('depth').then(function(storedDepth) {
  188. depthSlider.value = storedDepth !== null ? storedDepth : '100';
  189. });
  190.  
  191. // Add event listener to the depth slider
  192. depthSlider.addEventListener('input', function(event) {
  193. var depth = Math.round(depthSlider.value);
  194. GM.setValue('depth', depth.toString());
  195.  
  196. // Show the popup with the current depth value
  197. var popup = document.querySelector('.depth-popup'); // Use an existing popup or create a new one
  198. if (!popup) {
  199. popup = document.createElement('div');
  200. popup.classList.add('depth-popup');
  201. popup.style.position = 'fixed';
  202. popup.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
  203. popup.style.color = 'white';
  204. popup.style.padding = '5px 10px';
  205. popup.style.borderRadius = '5px';
  206. popup.style.zIndex = '9999';
  207. popup.style.display = 'none';
  208. document.body.appendChild(popup);
  209. }
  210.  
  211. popup.innerText = 'Depth: ' + depth;
  212. popup.style.display = 'block';
  213.  
  214. // Calculate slider position and adjust popup position
  215. var sliderRect = depthSlider.getBoundingClientRect();
  216. var popupX = sliderRect.left + ((depthSlider.value - depthSlider.min) / (depthSlider.max - depthSlider.min)) * sliderRect.width - popup.clientWidth / 2;
  217. var popupY = sliderRect.top - popup.clientHeight - 10;
  218.  
  219. popup.style.left = popupX + 'px';
  220. popup.style.top = popupY + 'px';
  221.  
  222. // Start a timer to hide the popup after a certain duration (e.g., 2 seconds)
  223. setTimeout(function() {
  224. popup.style.display = 'none';
  225. }, 2000);
  226. });
  227.  
  228. // Append the depth slider to the "Depth Slider" settings
  229. depthSliderSettings.appendChild(depthSlider);
  230.  
  231. // Create the settings for "Auto Queue"
  232. var autoQueueSettings = document.createElement('div');
  233. autoQueueSettings.style.padding = '10px';
  234.  
  235. // Create the "Auto Queue" toggle button
  236. var autoQueueToggleButton = document.createElement('button');
  237. autoQueueToggleButton.textContent = 'Auto Queue Off';
  238. autoQueueToggleButton.style.marginTop = '10px';
  239. autoQueueToggleButton.style.display = 'none';
  240. autoQueueToggleButton.classList.add('btn', 'btn-secondary', 'mb-2', 'ng-star-inserted');
  241. autoQueueToggleButton.style.backgroundColor = 'red'; // Initially red for "Off"
  242. autoQueueToggleButton.style.color = 'white';
  243. autoQueueToggleButton.addEventListener('click', toggleAutoQueue);
  244.  
  245. autoQueueSettings.appendChild(autoQueueToggleButton);
  246.  
  247. var isAutoQueueOn = false; // Track the state
  248.  
  249. function toggleAutoQueue() {
  250. // Toggle the state
  251. isAutoQueueOn = !isAutoQueueOn;
  252. GM.setValue('isToggled', isAutoQueueOn);
  253.  
  254. // Update the button text and style based on the state
  255. autoQueueToggleButton.textContent = isAutoQueueOn ? 'Auto Queue On' : 'Auto Queue Off';
  256. autoQueueToggleButton.style.backgroundColor = isAutoQueueOn ? 'green' : 'red';
  257. }
  258.  
  259. function clickLeaveRoomButton() {
  260. var leaveRoomButton = document.querySelector("button.btn-light.ng-tns-c189-7");
  261. if (leaveRoomButton) {
  262. leaveRoomButton.click();
  263. }
  264. }
  265.  
  266. function clickPlayOnlineButton() {
  267. var playOnlineButton = document.querySelector("button.btn-secondary.flex-grow-1");
  268. if (playOnlineButton) {
  269. playOnlineButton.click();
  270. }
  271. }
  272.  
  273. // Periodically check for buttons when the toggle is on
  274. function checkButtonsPeriodically() {
  275. if (isAutoQueueOn) {
  276. clickLeaveRoomButton();
  277. clickPlayOnlineButton();
  278. }
  279. }
  280.  
  281. // Set up periodic checking
  282. setInterval(checkButtonsPeriodically, 1000);
  283.  
  284. //------------------------------------------------------------------------Testing Purposes
  285.  
  286. let previousNumber = null; // Initialize the previousNumber to null
  287.  
  288. function trackAndClickIfDifferent() {
  289. // Select the <span> element using its class name
  290. const spanElement = document.querySelector('app-count-down span');
  291.  
  292. if (spanElement) {
  293. // Extract the number from the text content
  294. const number = parseInt(spanElement.textContent, 10);
  295.  
  296. // Check if parsing was successful
  297. if (!isNaN(number)) {
  298. // Check if the number has changed since the last check
  299. if (previousNumber !== null && number !== previousNumber && isAutoQueueOn) {
  300. spanElement.click();
  301. }
  302.  
  303. // Update the previousNumber with the current value
  304. previousNumber = number;
  305. }
  306. }
  307. }
  308.  
  309. // Set up an interval to call the function at regular intervals (e.g., every 1 second)
  310. setInterval(trackAndClickIfDifferent, 1000); // 1000 milliseconds = 1 second
  311.  
  312. //-------------------------------------------------------------------------------------------
  313.  
  314. // Append the toggle button to the "Auto Queue" settings
  315. autoQueueSettings.appendChild(autoQueueToggleButton);
  316.  
  317. // Add event listeners to the tabs to toggle their respective settings
  318. autoQueueTab.addEventListener('click', function() {
  319. // Hide the depth slider settings
  320. depthSliderSettings.style.display = 'none';
  321. // Show the auto queue settings
  322. autoQueueSettings.style.display = 'block';
  323. autoQueueToggleButton.style.display = 'block';
  324. });
  325.  
  326. depthSliderTab.addEventListener('click', function() {
  327. // Hide the auto queue settings
  328. autoQueueSettings.style.display = 'none';
  329. // Show the depth slider settings
  330. depthSliderSettings.style.display = 'block';
  331. });
  332.  
  333. // Append the tabs and settings to the dropdown content
  334. dropdownContent.appendChild(autoQueueTab);
  335. dropdownContent.appendChild(autoQueueSettings);
  336. dropdownContent.appendChild(depthSliderTab);
  337. dropdownContent.appendChild(depthSliderSettings);
  338.  
  339. // Append the button and dropdown content to the container
  340. dropdownContainer.appendChild(toggleButton);
  341. dropdownContainer.appendChild(dropdownContent);
  342.  
  343. // Toggle the dropdown when the button is clicked
  344. toggleButton.addEventListener('click', function() {
  345. if (dropdownContent.style.display === 'none') {
  346. dropdownContent.style.display = 'block';
  347. } else {
  348. dropdownContent.style.display = 'none';
  349. }
  350. });
  351.  
  352. // Append the dropdown container to the document body
  353. document.body.appendChild(dropdownContainer);
  354. })();
  355.  
  356. //------------------------------------------------
  357.  
  358. function updateBoard(squareId) {
  359. var row = parseInt(squareId[0]);
  360. var col = parseInt(squareId[1]);
  361. GM.getValue("username").then(function(username) {
  362. var profileOpeners = document.querySelectorAll(".text-truncate.cursor-pointer");
  363. var profileOpener = null;
  364. profileOpeners.forEach(function(opener) {
  365. if (opener.textContent.trim() === username) {
  366. profileOpener = opener;
  367. }
  368. });
  369. if (!profileOpener) {
  370. console.error("Profile opener not found");
  371. return;
  372. }
  373. var chronometer = document.querySelector("app-chronometer");
  374. var numberElement = profileOpener.parentNode ? profileOpener.parentNode.querySelectorAll("span")[4] : null;
  375. var profileOpenerParent = profileOpener.parentNode ? profileOpener.parentNode.parentNode : null;
  376. var svgElement = profileOpenerParent.querySelector("circle[class*='circle-dark-stroked']");
  377. if (!svgElement) {
  378. svgElement = profileOpenerParent.querySelector("svg[class*='fa-xmark']");
  379. }
  380.  
  381. if (svgElement && svgElement.closest("circle[class*='circle-dark-stroked']")) {
  382. player = 'o'; // Player is playing as "O"
  383. } else if (svgElement && svgElement.closest("svg[class*='fa-xmark']")) {
  384. player = 'x'; // Player is playing as "X"
  385. }
  386.  
  387. var currentElement = chronometer || numberElement;
  388.  
  389. if (currentElement.textContent !== prevChronometerValue && profileOpener) {
  390. prevChronometerValue = currentElement.textContent;
  391. simulateCellClick(row, col);
  392. } else {
  393. console.log("Waiting for AI's turn...");
  394. }
  395. });
  396. return player;
  397. }
  398. function logBoardState() {
  399. // Attempt to log various variables and elements for debugging
  400. try {
  401. // Log row and col based on a hardcoded squareId for debugging
  402. var squareId = "00"; // Change this as needed for different squares
  403. var row = parseInt(squareId[0]);
  404. var col = parseInt(squareId[1]);
  405. console.log("Row:", row, "Col:", col);
  406. // Log username from GM storage
  407. GM.getValue("username").then(function(username) {
  408. console.log("Username from GM storage:", username);
  409. // Log profile openers
  410. var profileOpeners = document.querySelectorAll(".text-truncate.cursor-pointer");
  411. console.log("Profile Openers:", profileOpeners);
  412. var profileOpener = null;
  413. profileOpeners.forEach(function(opener) {
  414. if (opener.textContent.trim() === username) {
  415. profileOpener = opener;
  416. }
  417. });
  418. console.log("Profile Opener:", profileOpener);
  419. // Log chronometer element
  420. var chronometer = document.querySelector("app-chronometer");
  421. console.log("Chronometer:", chronometer);
  422. // Log number element
  423. var numberElement = profileOpener ? profileOpener.parentNode.querySelectorAll("span")[4] : null;
  424. console.log("Number Element:", numberElement);
  425. // Log profile opener parent
  426. var profileOpenerParent = profileOpener ? profileOpener.parentNode.parentNode : null;
  427. console.log("Profile Opener Parent:", profileOpenerParent);
  428. // Log SVG element
  429. var svgElement = profileOpenerParent ? profileOpenerParent.querySelector("circle[class*='circle-dark-stroked']") : null;
  430. if (!svgElement && profileOpenerParent) {
  431. svgElement = profileOpenerParent.querySelector("svg[class*='fa-xmark']");
  432. }
  433. console.log("SVG Element:", svgElement);
  434. // Determine and log the player
  435. var player = null;
  436. if (svgElement && svgElement.closest("circle[class*='circle-dark-stroked']")) {
  437. player = 'o'; // Player is playing as "O"
  438. } else if (svgElement && svgElement.closest("svg[class*='fa-xmark']")) {
  439. player = 'x'; // Player is playing as "X"
  440. }
  441. console.log("Player:", player);
  442. // Log current element
  443. var currentElement = chronometer || numberElement;
  444. console.log("Current Element:", currentElement);
  445. console.log("Logging complete for this iteration.\n");
  446. });
  447. } catch (error) {
  448. console.error("Error in logBoardState:", error);
  449. }
  450. }
  451. // Call logBoardState every 5 seconds
  452. setInterval(logBoardState, 5000);
  453.  
  454. var player;
  455.  
  456. function initGame() {
  457. var observer = new MutationObserver(function(mutations) {
  458. mutations.forEach(function(mutation) {
  459. if (mutation.target.id === 'tic-tac-toe-board') {
  460. initAITurn();
  461. }
  462. });
  463. });
  464.  
  465. observer.observe(document.getElementById('tic-tac-toe-board'), { attributes: true, childList: true, subtree: true });
  466. }
  467.  
  468.  
  469. function initAITurn() {
  470. displayBoardAndPlayer();
  471. var boardState = getBoardState();
  472. var bestMove = findBestMove(boardState, player);
  473. updateBoard(bestMove.row.toString() + bestMove.col.toString());
  474. }
  475.  
  476. function findBestMove(board, player) {
  477. console.log("Current player: " + player); // Debug statement to show the value of the player variable
  478.  
  479. var bestVal = -1000;
  480. var bestMove = { row: -1, col: -1 };
  481.  
  482. for (var i = 0; i < 3; i++) {
  483. for (var j = 0; j < 3; j++) {
  484. if (board[i][j] === '_') {
  485. board[i][j] = player;
  486. var moveVal = minimax(board, 0, false, depth);
  487. board[i][j] = '_';
  488.  
  489. if (moveVal > bestVal) {
  490. bestMove.row = i;
  491. bestMove.col = j;
  492. bestVal = moveVal;
  493. }
  494. }
  495. }
  496. }
  497.  
  498. console.log("The value of the best Move is: " + bestVal);
  499. return bestMove;
  500. }
  501.  
  502. function displayBoardAndPlayer() {
  503. var boardState = getBoardState();
  504. console.log("Board State:");
  505. boardState.forEach(function(row) {
  506. console.log(row.join(' | '));
  507. });
  508. }
  509.  
  510. function getOpponent(player) {
  511. return player === 'x' ? 'o' : 'x';
  512. }
  513.  
  514. function minimax(board, depth, isMaximizingPlayer, maxDepth) {
  515. var score = evaluateBoard(board);
  516.  
  517. if (depth === maxDepth) {
  518. return evaluateBoard(board);
  519. }
  520.  
  521. if (score === 10)
  522. return score - depth;
  523.  
  524. if (score === -10)
  525. return score + depth;
  526.  
  527. if (areMovesLeft(board) === false)
  528. return 0;
  529.  
  530. if (isMaximizingPlayer) {
  531. var best = -1000;
  532.  
  533. for (var i = 0; i < 3; i++) {
  534. for (var j = 0; j < 3; j++) {
  535. if (board[i][j] === '_') {
  536. board[i][j] = player;
  537. best = Math.max(best, minimax(board, depth + 1, !isMaximizingPlayer));
  538. board[i][j] = '_';
  539. }
  540. }
  541. }
  542. return best;
  543. } else {
  544. var best = 1000;
  545.  
  546. for (var i = 0; i < 3; i++) {
  547. for (var j = 0; j < 3; j++) {
  548. if (board[i][j] === '_') {
  549. board[i][j] = getOpponent(player);
  550. best = Math.min(best, minimax(board, depth + 1, !isMaximizingPlayer));
  551. board[i][j] = '_';
  552. }
  553. }
  554. }
  555. return best;
  556. }
  557. }
  558.  
  559. function evaluateBoard(board) {
  560. // Check rows for victory
  561. for (let row = 0; row < 3; row++) {
  562. if (board[row][0] === board[row][1] && board[row][1] === board[row][2]) {
  563. if (board[row][0] === player) return +10;
  564. else if (board[row][0] !== '_') return -10;
  565. }
  566. }
  567.  
  568. // Check columns for victory
  569. for (let col = 0; col < 3; col++) {
  570. if (board[0][col] === board[1][col] && board[1][col] === board[2][col]) {
  571. if (board[0][col] === player) return +10;
  572. else if (board[0][col] !== '_') return -10;
  573. }
  574. }
  575.  
  576. // Check diagonals for victory
  577. if (board[0][0] === board[1][1] && board[1][1] === board[2][2]) {
  578. if (board[0][0] === player) return +10;
  579. else if (board[0][0] !== '_') return -10;
  580. }
  581.  
  582. if (board[0][2] === board[1][1] && board[1][1] === board[2][0]) {
  583. if (board[0][2] === player) return +10;
  584. else if (board[0][2] !== '_') return -10;
  585. }
  586.  
  587. // If no one has won, return 0
  588. return 0;
  589. }
  590.  
  591. function areMovesLeft(board) {
  592. for (let i = 0; i < 3; i++) {
  593. for (let j = 0; j < 3; j++) {
  594. if (board[i][j] === '_') return true;
  595. }
  596. }
  597. return false;
  598. }
  599.  
  600. setInterval(function() {
  601. initAITurn();
  602. }, 1000);
  603.  
  604. document.addEventListener('DOMContentLoaded', function() {
  605. initGame();
  606. });
  607. })();