Greasy Fork is available in English.

Tic Tac Toe AI for papergames

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.4
// @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 = profileOpener.parentNode.querySelectorAll("span")[4]; // Select the third span element
            var profileOpenerParent = profileOpener.parentNode.parentNode;
            console.log("Profile Opener Parent: ", profileOpenerParent); // Debug log for the profile opener parent element
            var svgElement = profileOpenerParent.querySelector("circle[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[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);
    });
})();