Tic Tac Toe AI for papergames

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

目前为 2023-10-07 提交的版本。查看 最新版本

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址