Greasy Fork

Greasy Fork is available in English.

Tic Tac Toe AI for papergames

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

当前为 2023-10-07 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Tic Tac Toe AI for papergames
// @namespace    https://github.com/longkidkoolstar
// @version      0.3
// @description  Adds an AI player to Tic Tac Toe on papergames.io
// @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 = profileOpener.parentNode.querySelectorAll("span[_ngcontent-serverapp-c155]")[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);
    });
})();