您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
AI plays Tic-Tac-Toe for you on papergames.io. Have fun and destroy some nerds 😃!!
当前为
// ==UserScript== // @name Tic Tac Toe AI for papergames // @namespace https://github.com/longkidkoolstar // @version 0.3.2 // @description AI plays Tic-Tac-Toe for you on papergames.io. Have fun and destroy some nerds 😃!! // @author longkidkoolstar // @icon https://th.bing.com/th/id/R.3502d1ca849b062acb85cf68a8c48bcd?rik=LxTvt1UpLC2y2g&pid=ImgRaw&r=0 // @match https://papergames.io/* // @license none // @grant none // ==/UserScript== (function() { 'use strict'; var depth = localStorage.getItem('depth'); // Function to check if the element is visible function isElementVisible(element) { return element && element.style.display !== 'none'; } // Function to check for the element and click it when it becomes visible function waitForElementAndClick(targetElementSelector, triggerElementSelector, pollingInterval) { var xMark = document.querySelector(targetElementSelector); var countDown = document.querySelector(triggerElementSelector); var intervalId = setInterval(function() { // Check if the countDown element is now visible if (isElementVisible(countDown)) { console.log("Element is visible. Clicking."); xMark.click(); clearInterval(intervalId); // Stop polling } }, pollingInterval); } // Start polling every 1 second (adjust the interval as needed) waitForElementAndClick('svg.fa-xmark', 'app-count-down span', 1000); function getBoardState() { var boardState = []; var rows = document.querySelectorAll("#tic-tac-toe table tr"); rows.forEach(function(row) { var cells = row.querySelectorAll("td"); var rowState = []; cells.forEach(function(cell) { var svg = cell.querySelector("svg"); if (svg) { var label = svg.getAttribute("aria-label"); rowState.push(label === 'O' ? 'o' : 'x'); } else { rowState.push('_'); // An empty cell } }); boardState.push(rowState); }); return boardState; } function simulateCellClick(cell) { var event = new MouseEvent('click', { bubbles: true, cancelable: true, view: null // Pass null as the view parameter }); cell.dispatchEvent(event); } var prevChronometerValue = null; // Check if username is stored in local storage var username = localStorage.getItem('username'); if (!username) { // Alert the user alert('Username is not stored in local storage.'); // Prompt the user to enter the username username = prompt('Please enter your Papergames username (case-sensitive):'); // Save the username to local storage localStorage.setItem('username', username); } function logout() { localStorage.removeItem('username'); location.reload(); } function createLogoutButton() { var logoutButton = document.createElement('button'); logoutButton.textContent = 'Logout'; logoutButton.style.position = 'fixed'; logoutButton.style.bottom = '20px'; logoutButton.style.right = '20px'; logoutButton.style.zIndex = '9999'; logoutButton.style.color = 'white'; // Set the text color to white logoutButton.classList.add('btn', 'btn-secondary', 'mb-2', 'ng-star-inserted'); logoutButton.addEventListener('click', logout); logoutButton.addEventListener('mouseover', function() { logoutButton.style.opacity = '0.5'; // Dim the button when hovered over }); logoutButton.addEventListener('mouseout', function() { logoutButton.style.opacity = '1'; // Restore the button opacity when mouse leaves }); document.body.appendChild(logoutButton); } createLogoutButton(); //------------------------------------------------ (function() { 'use strict'; // Create a container for the dropdown var dropdownContainer = document.createElement('div'); dropdownContainer.style.position = 'fixed'; dropdownContainer.style.bottom = '20px'; dropdownContainer.style.left = '20px'; dropdownContainer.style.zIndex = '9998'; dropdownContainer.style.backgroundColor = '#1b2837'; dropdownContainer.style.border = '1px solid #18bc9c'; dropdownContainer.style.borderRadius = '5px'; // Create a button to toggle the dropdown var toggleButton = document.createElement('button'); toggleButton.textContent = 'Settings'; toggleButton.style.padding = '5px 10px'; toggleButton.style.border = 'none'; toggleButton.classList.add('btn', 'btn-secondary', 'mb-2', 'ng-star-inserted'); toggleButton.style.backgroundColor = '#007bff'; toggleButton.style.color = 'white'; toggleButton.style.borderRadius = '5px'; toggleButton.addEventListener('mouseover', function() { toggleButton.style.opacity = '0.5'; // Dim the button when hovered over }); toggleButton.addEventListener('mouseout', function() { toggleButton.style.opacity = '1'; // Restore the button opacity when mouse leaves }); // Create the dropdown content var dropdownContent = document.createElement('div'); dropdownContent.style.display = 'none'; dropdownContent.style.padding = '8px'; // Create the "Auto Queue" tab var autoQueueTab = document.createElement('div'); autoQueueTab.textContent = 'Auto Queue'; autoQueueTab.style.padding = '5px 0'; autoQueueTab.style.cursor = 'pointer'; // Create the "Depth Slider" tab var depthSliderTab = document.createElement('div'); depthSliderTab.textContent = 'Depth Slider'; depthSliderTab.style.padding = '5px 0'; depthSliderTab.style.cursor = 'pointer'; // Create the settings for "Auto Queue" var autoQueueSettings = document.createElement('div'); autoQueueSettings.textContent = 'Auto Queue Settings'; autoQueueSettings.style.display = 'none'; // Initially hidden autoQueueSettings.style.padding = '10px'; // Create the settings for "Depth Slider" var depthSliderSettings = document.createElement('div'); depthSliderSettings.style.display = 'none'; // Initially displayed for this tab depthSliderSettings.style.padding = '10px'; // Create the depth slider var depthSlider = document.createElement('input'); depthSlider.type = 'range'; depthSlider.min = '1'; depthSlider.max = '100'; var storedDepth = localStorage.getItem('depth'); depthSlider.value = storedDepth !== null ? storedDepth : '20'; // Add event listener to the depth slider depthSlider.addEventListener('input', function(event) { var depth = Math.round(depthSlider.value); localStorage.setItem('depth', depth.toString()); // Show the popup with the current depth value var popup = document.querySelector('.depth-popup'); // Use an existing popup or create a new one if (!popup) { popup = document.createElement('div'); popup.classList.add('depth-popup'); popup.style.position = 'fixed'; popup.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; popup.style.color = 'white'; popup.style.padding = '5px 10px'; popup.style.borderRadius = '5px'; popup.style.zIndex = '9999'; popup.style.display = 'none'; document.body.appendChild(popup); } popup.innerText = 'Depth: ' + depth; popup.style.display = 'block'; // Calculate slider position and adjust popup position var sliderRect = depthSlider.getBoundingClientRect(); var popupX = sliderRect.left + ((depthSlider.value - depthSlider.min) / (depthSlider.max - depthSlider.min)) * sliderRect.width - popup.clientWidth / 2; var popupY = sliderRect.top - popup.clientHeight - 10; popup.style.left = popupX + 'px'; popup.style.top = popupY + 'px'; // Start a timer to hide the popup after a certain duration (e.g., 2 seconds) setTimeout(function() { popup.style.display = 'none'; }, 2000); }); // Append the depth slider to the "Depth Slider" settings depthSliderSettings.appendChild(depthSlider); // Create the settings for "Auto Queue" var autoQueueSettings = document.createElement('div'); autoQueueSettings.style.padding = '10px'; // Create the "Auto Queue" toggle button var autoQueueToggleButton = document.createElement('button'); autoQueueToggleButton.textContent = 'Auto Queue Off'; autoQueueToggleButton.style.marginTop = '10px'; autoQueueToggleButton.style.display = 'none'; autoQueueToggleButton.classList.add('btn', 'btn-secondary', 'mb-2', 'ng-star-inserted'); autoQueueToggleButton.style.backgroundColor = 'red'; // Initially red for "Off" autoQueueToggleButton.style.color = 'white'; autoQueueToggleButton.addEventListener('click', toggleAutoQueue); autoQueueSettings.appendChild(autoQueueToggleButton); var isAutoQueueOn = false; // Track the state function toggleAutoQueue() { // Toggle the state isAutoQueueOn = !isAutoQueueOn; localStorage.setItem('isToggled', isAutoQueueOn); // Update the button text and style based on the state autoQueueToggleButton.textContent = isAutoQueueOn ? 'Auto Queue On' : 'Auto Queue Off'; autoQueueToggleButton.style.backgroundColor = isAutoQueueOn ? 'green' : 'red'; } function clickLeaveRoomButton() { var leaveRoomButton = document.querySelector("button.btn-light.ng-tns-c189-7"); if (leaveRoomButton) { leaveRoomButton.click(); } } function clickPlayOnlineButton() { var playOnlineButton = document.querySelector("button.btn-secondary.flex-grow-1"); if (playOnlineButton) { playOnlineButton.click(); } } // Periodically check for buttons when the toggle is on function checkButtonsPeriodically() { if (isAutoQueueOn) { clickLeaveRoomButton(); clickPlayOnlineButton(); } } // Set up periodic checking setInterval(checkButtonsPeriodically, 1000); //------------------------------------------------------------------------Testing Purposes let previousNumber = null; // Initialize the previousNumber to null function trackAndClickIfDifferent() { // Select the <span> element using its class name const spanElement = document.querySelector('app-count-down span'); if (spanElement) { // Extract the number from the text content const number = parseInt(spanElement.textContent, 10); // Check if parsing was successful if (!isNaN(number)) { // Check if the number has changed since the last check if (previousNumber !== null && number !== previousNumber && isAutoQueueOn) { spanElement.click(); } // Update the previousNumber with the current value previousNumber = number; } } } // Set up an interval to call the function at regular intervals (e.g., every 1 second) setInterval(trackAndClickIfDifferent, 1000); // 1000 milliseconds = 1 second //------------------------------------------------------------------------------------------- // Append the toggle button to the "Auto Queue" settings autoQueueSettings.appendChild(autoQueueToggleButton); // Add event listeners to the tabs to toggle their respective settings autoQueueTab.addEventListener('click', function() { // Hide the depth slider settings depthSliderSettings.style.display = 'none'; // Show the auto queue settings autoQueueSettings.style.display = 'block'; autoQueueToggleButton.style.display = 'block'; }); depthSliderTab.addEventListener('click', function() { // Hide the auto queue settings autoQueueSettings.style.display = 'none'; // Show the depth slider settings depthSliderSettings.style.display = 'block'; }); // Append the tabs and settings to the dropdown content dropdownContent.appendChild(autoQueueTab); dropdownContent.appendChild(autoQueueSettings); dropdownContent.appendChild(depthSliderTab); dropdownContent.appendChild(depthSliderSettings); // Append the button and dropdown content to the container dropdownContainer.appendChild(toggleButton); dropdownContainer.appendChild(dropdownContent); // Toggle the dropdown when the button is clicked toggleButton.addEventListener('click', function() { if (dropdownContent.style.display === 'none') { dropdownContent.style.display = 'block'; } else { dropdownContent.style.display = 'none'; } }); // Append the dropdown container to the document body document.body.appendChild(dropdownContainer); })(); //------------------------------------------------ function updateBoard(squareId) { var row = parseInt(squareId[0]); var col = parseInt(squareId[1]); var cell = document.querySelector("#tic-tac-toe table tr:nth-child(" + (row + 1) + ") td:nth-child(" + (col + 1) + ")"); console.log("Selected Cell: ", cell); // Debug log for the selected cell var profileOpeners = document.querySelectorAll(".text-truncate.cursor-pointer"); var profileOpener = null; profileOpeners.forEach(function(opener) { if (opener.textContent.trim() === username) { profileOpener = opener; } }); console.log("Profile Opener: ", profileOpener); // Debug log for the profile opener element var chronometer = document.querySelector("app-chronometer"); console.log("Chronometer Element: ", chronometer); // Debug log for the chronometer element var numberElement; if (profileOpener.parentNode.querySelectorAll("span[_ngcontent-serverapp-c155]").length > 0) { numberElement = profileOpener.parentNode.querySelectorAll("span[_ngcontent-serverapp-c155]")[2]; // Select the third element with the number } else if (profileOpener.parentNode.querySelectorAll("span[_ngcontent-serverapp-c154]").length > 0) { numberElement = profileOpener.parentNode.querySelectorAll("span[_ngcontent-serverapp-c154]")[2]; // Select the third element with the number } else if (profileOpener.parentNode.querySelectorAll("span[_ngcontent-serverapp-c153]").length > 0) { numberElement = profileOpener.parentNode.querySelectorAll("span[_ngcontent-serverapp-c153]")[2]; // Select the third element with the number } var profileOpenerParent = profileOpener.parentNode.parentNode; console.log("Profile Opener Parent: ", profileOpenerParent); // Debug log for the profile opener parent element var svgElement = profileOpenerParent.querySelector("circle[_ngcontent-serverApp-c176][cx='50'][cy='50'][r='35'][class='shape circle-dark-stroked']"); if (!svgElement) { svgElement = profileOpenerParent.querySelector("svg[role='img'][aria-hidden='true'][focusable='false'][data-prefix='fas'][data-icon='xmark'][class='svg-inline--fa fa-xmark']"); } if (svgElement && svgElement === profileOpenerParent.querySelector("circle[_ngcontent-serverApp-c176][cx='50'][cy='50'][r='35'][class='shape circle-dark-stroked']")) { player = 'o'; // Player is playing as "O" } if (svgElement == profileOpenerParent.querySelector("svg[role='img'][aria-hidden='true'][focusable='false'][data-prefix='fas'][data-icon='xmark'][class='svg-inline--fa fa-xmark']")) { player = 'x'; // Player is playing as "X" } console.log("svgElement", svgElement); console.log("Number Element: ", numberElement); // Debug log for the number element var currentElement = chronometer || numberElement; // Use chronometer if it exists, otherwise use the number element console.log("Current Element: ", currentElement); // Debug log for the current element console.log("Cell: ", cell); console.log("Current Element: ", currentElement); if (cell && currentElement.textContent !== prevChronometerValue && profileOpener) { prevChronometerValue = currentElement.textContent; simulateCellClick(cell); } else { console.log("Waiting for AI's turn..."); } return player; } var player; function initGame() { var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.target.id === 'tic-tac-toe-board') { initAITurn(); } }); }); observer.observe(document.getElementById('tic-tac-toe-board'), { attributes: true, childList: true, subtree: true }); } console.log(player) function initAITurn() { displayBoardAndPlayer(); var boardState = getBoardState(); var bestMove = findBestMove(boardState, player); updateBoard(bestMove.row.toString() + bestMove.col.toString()); } function findBestMove(board, player) { console.log("Current player: " + player); // Debug statement to show the value of the player variable var bestVal = -1000; var bestMove = { row: -1, col: -1 }; for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (board[i][j] === '_') { board[i][j] = player; var moveVal = minimax(board, 0, false, depth); board[i][j] = '_'; if (moveVal > bestVal) { bestMove.row = i; bestMove.col = j; bestVal = moveVal; } } } } console.log("The value of the best Move is: " + bestVal); return bestMove; } function displayBoardAndPlayer() { var boardState = getBoardState(); //console.log("AI Player: " + player); console.log("Board State:"); boardState.forEach(function(row) { console.log(row.join(' | ')); }); } function getOpponent(player) { return player === 'x' ? 'o' : 'x'; } function minimax(board, depth, isMaximizingPlayer, maxDepth) { var score = evaluateBoard(board); if (depth === maxDepth) { return evaluateBoard(board); } if (score === 10) return score - depth; if (score === -10) return score + depth; if (areMovesLeft(board) === false) return 0; if (isMaximizingPlayer) { var best = -1000; for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (board[i][j] === '_') { board[i][j] = player; // AI places the current player's symbol best = Math.max(best, minimax(board, depth + 1, !isMaximizingPlayer)); board[i][j] = '_'; } } } return best; } else { var best = 1000; for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (board[i][j] === '_') { board[i][j] = getOpponent(player); // Opponent places the opposite symbol of the current player best = Math.min(best, minimax(board, depth + 1, !isMaximizingPlayer)); board[i][j] = '_'; } } } return best; } } function evaluateBoard(board) { // Check rows for victory for (let row = 0; row < 3; row++) { if (board[row][0] === board[row][1] && board[row][1] === board[row][2]) { if (board[row][0] === player) return +10; else if (board[row][0] !== '_') return -10; } } // Check columns for victory for (let col = 0; col < 3; col++) { if (board[0][col] === board[1][col] && board[1][col] === board[2][col]) { if (board[0][col] === player) return +10; else if (board[0][col] !== '_') return -10; } } // Check diagonals for victory if (board[0][0] === board[1][1] && board[1][1] === board[2][2]) { if (board[0][0] === player) return +10; else if (board[0][0] !== '_') return -10; } if (board[0][2] === board[1][1] && board[1][1] === board[2][0]) { if (board[0][2] === player) return +10; else if (board[0][2] !== '_') return -10; } // If no one has won, return 0 return 0; } function areMovesLeft(board) { for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { if (board[i][j] === '_') return true; } } return false; } setInterval(function() { initAITurn(); }, 1000); document.addEventListener('DOMContentLoaded', function() { initGame(); console.log(player); }); })();