Tic Tac Toe AI for papergames

Adds an AI player to Tic Tac Toe on papergames.io

目前為 2023-09-02 提交的版本,檢視 最新版本

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

QingJ © 2025

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