Battleship AI for Papergames

Advanced AI for Battleship on papergames.io with strategic weapon selection, Bayesian inference, and probability visualization

// ==UserScript==
// @name         Battleship AI for Papergames
// @namespace    github.io/longkidkoolstar
// @version      4.1.9
// @description  Advanced AI for Battleship on papergames.io with strategic weapon selection, Bayesian inference, and probability visualization
// @author       longkidkoolstar
// @match        https://papergames.io/*
// @grant        GM.setValue
// @grant        GM.getValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Game state variables for probability calculations
    let confirmedHits = []; // Still needed for probability bonuses
    let visualizationEnabled = true; // Toggle for probability visualization
    
    // Auto queue variables
    let isAutoQueueOn = false;
    let autoQueueToggleButton = null;
    
    // Ship tracking system
    let remainingShips = [5, 4, 3, 3, 2]; // Standard Battleship ships: Carrier, Battleship, Cruiser, Submarine, Destroyer
    let sunkShips = [];
    let totalHitsOnBoard = 0;
    let totalSunkCells = 0;
    
    // Weapon system variables
    let availableWeapons = {
        default: true,
        missile: 0,
        fragmentBomb: 0,
        nuclearBomb: 0
    };

    // Weapon detection system
    function detectAvailableWeapons() {
        const weaponButtons = document.querySelectorAll('.weapon-button');
        console.log(`Found ${weaponButtons.length} weapon buttons`);
        
        availableWeapons = {
            default: true,
            missile: 0,
            fragmentBomb: 0,
            nuclearBomb: 0
        };
        
        weaponButtons.forEach((button, index) => {
            const img = button.querySelector('img');
            const badge = button.querySelector('.badge');
            const isDisabled = button.hasAttribute('disabled');
            
            console.log(`Button ${index}: img=${!!img}, badge=${!!badge}, disabled=${isDisabled}`);
            
            if (img && badge) {
                const weaponType = img.getAttribute('alt');
                const count = parseInt(badge.textContent) || 0;
                
                console.log(`  Weapon type: ${weaponType}, count: ${count}, badge text: '${badge.textContent}'`);
                
                switch(weaponType) {
                    case 'missile':
                        availableWeapons.missile = isDisabled ? 0 : count;
                        console.log(`  Set missile count to: ${availableWeapons.missile}`);
                        break;
                    case 'fragment-bomb':
                        availableWeapons.fragmentBomb = isDisabled ? 0 : count;
                        break;
                    case 'nuclear-bomb':
                        availableWeapons.nuclearBomb = isDisabled ? 0 : count;
                        break;
                    case 'default':
                        availableWeapons.default = true;
                        break;
                }
            } else {
                console.log(`  Button ${index} missing img or badge`);
            }
        });
        
        console.log('Final available weapons:', availableWeapons);
        return availableWeapons;
    }
    
    // Strategic weapon selection system
    function selectOptimalWeapon(targetRow, targetCol, board, probabilityScores) {
        detectAvailableWeapons();
        
        const gameProgress = (totalSunkCells + totalHitsOnBoard) / 17;
        const hasConfirmedHits = confirmedHits.length > 0;
        
        console.log(`=== WEAPON SELECTION DEBUG ===`);
        console.log(`Target: [${targetRow},${targetCol}], Game Progress: ${gameProgress.toFixed(2)}, Has Hits: ${hasConfirmedHits}`);
        console.log(`Available weapons:`, availableWeapons);
        
        // Nuclear bomb strategy - maximum area coverage
        if (availableWeapons.nuclearBomb > 0) {
            console.log(`Checking nuclear bomb...`);
            if (shouldUseNuclearBomb(targetRow, targetCol, board, probabilityScores, gameProgress)) {
                console.log(`NUCLEAR BOMB SELECTED!`);
                return 'nuclear-bomb';
            }
        }
        
        // Fragment bomb strategy - high probability clusters
        if (availableWeapons.fragmentBomb > 0) {
            console.log(`Checking fragment bomb...`);
            if (shouldUseFragmentBomb(targetRow, targetCol, board, probabilityScores, gameProgress)) {
                console.log(`FRAGMENT BOMB SELECTED!`);
                return 'fragment-bomb';
            }
        }
        
        // Missile strategy - confirmed hits and surrounding area
        if (availableWeapons.missile > 0) {
            console.log(`Checking missile (count: ${availableWeapons.missile})...`);
            if (shouldUseMissile(targetRow, targetCol, board, hasConfirmedHits, gameProgress)) {
                console.log(`MISSILE SELECTED!`);
                return 'missile';
            } else {
                console.log(`Missile not selected - conditions not met`);
            }
        } else {
            console.log(`No missiles available (count: ${availableWeapons.missile})`);
        }
        
        // Default to single shot
        console.log(`Defaulting to single shot`);
        return 'default';
    }
    
    // Nuclear bomb strategy: 3x3 area with corners
    function shouldUseNuclearBomb(targetRow, targetCol, board, probabilityScores, gameProgress) {
        // Early game: use only for maximum coverage in completely unexplored areas
        if (gameProgress < 0.2) {
            const coverageScore = calculateNuclearCoverage(targetRow, targetCol, board);
            return coverageScore >= 8; // At least 8 unknown cells in pattern (nearly full coverage)
        }
        
        // Mid-late game: use only when extremely high probability cluster detected
        if (gameProgress >= 0.2) {
            const clusterScore = calculateClusterProbability(targetRow, targetCol, probabilityScores, 'nuclear');
            return clusterScore >= 20; // Very high combined probability in nuclear pattern
        }
        
        return false;
    }
    
    // Fragment bomb strategy: 4 hits in cross pattern
    function shouldUseFragmentBomb(targetRow, targetCol, board, probabilityScores, gameProgress) {
        // Best for confirmed hits to clear surrounding area efficiently
        if (confirmedHits.length > 0) {
            const nearHit = isNearConfirmedHit(targetRow, targetCol, 1); // More precise targeting
            if (nearHit) {
                const coverageScore = calculateFragmentCoverage(targetRow, targetCol, board);
                return coverageScore >= 4; // Require full coverage for efficiency
            }
        }
        
        // High probability cross pattern - more restrictive
        const clusterScore = calculateClusterProbability(targetRow, targetCol, probabilityScores, 'fragment');
        return clusterScore >= 16; // Higher threshold for probability-based usage
    }
    
    // Missile strategy: 5 hits in plus pattern
    function shouldUseMissile(targetRow, targetCol, board, hasConfirmedHits, gameProgress) {
        const coverageScore = calculateMissileCoverage(targetRow, targetCol, board);
        console.log(`Missile evaluation at [${targetRow},${targetCol}]: coverage=${coverageScore}, hasHits=${hasConfirmedHits}, progress=${gameProgress.toFixed(2)}`);
        
        // Perfect for finishing off damaged ships
        if (hasConfirmedHits) {
            const nearHit = isNearConfirmedHit(targetRow, targetCol, 1);
            if (nearHit && coverageScore >= 3) {
                console.log(`Missile selected: Near confirmed hit with coverage ${coverageScore}`);
                return true; // Use missile near hits with good coverage
            }
        }
        
        // Early game exploration with excellent coverage
        if (gameProgress < 0.4 && coverageScore >= 4) {
            console.log(`Missile selected: Early game exploration with coverage ${coverageScore}`);
            return true; // Use missiles for early exploration with full coverage
        }
        
        // Question mark targeting: missiles are effective for question marks
        const centerCell = getCellByCoordinates(targetRow, targetCol);
        if (centerCell && hasQuestionMark(centerCell) && coverageScore >= 3) {
            console.log(`Missile selected: Question mark targeting with coverage ${coverageScore}`);
            return true; // Use missile on question marks with good coverage
        }
        
        // High value areas: use missiles when they provide maximum benefit
        if (coverageScore >= 5) {
            console.log(`Missile selected: Maximum coverage area with coverage ${coverageScore}`);
            return true; // Only use missiles when they can hit all 5 cells
        }
        
        return false;
    }
    
    // Helper functions for weapon coverage calculations
    function calculateNuclearCoverage(row, col, board) {
        // Nuclear bomb pattern: center + 4 adjacent + 4 corners
        const positions = [
            [row, col], // center
            [row-1, col], [row+1, col], [row, col-1], [row, col+1], // adjacent
            [row-1, col-1], [row-1, col+1], [row+1, col-1], [row+1, col+1] // corners
        ];
        
        return positions.filter(([r, c]) => 
            r >= 0 && r < 10 && c >= 0 && c < 10 && 
            (board[r][c] === 'available' || board[r][c] === 'question')
        ).length;
    }
    
    function calculateFragmentCoverage(row, col, board) {
        // Fragment bomb pattern: center + 3 bombs above
        const positions = [
            [row, col], // center
            [row-1, col], [row-2, col], [row-3, col] // 3 above
        ];
        
        return positions.filter(([r, c]) => 
            r >= 0 && r < 10 && c >= 0 && c < 10 && 
            (board[r][c] === 'available' || board[r][c] === 'question')
        ).length;
    }
    
    function calculateMissileCoverage(row, col, board) {
        // Missile pattern: center + 4 adjacent (plus shape)
        const positions = [
            [row, col], // center
            [row-1, col], [row+1, col], [row, col-1], [row, col+1] // adjacent
        ];
        
        console.log(`=== MISSILE COVERAGE DEBUG at [${row},${col}] ===`);
        positions.forEach(([r, c]) => {
            if (r >= 0 && r < 10 && c >= 0 && c < 10) {
                console.log(`  Position [${r},${c}]: ${board[r][c]}`);
            } else {
                console.log(`  Position [${r},${c}]: out of bounds`);
            }
        });
        
        const validTargets = positions.filter(([r, c]) => 
            r >= 0 && r < 10 && c >= 0 && c < 10 && 
            (board[r][c] === 'available' || board[r][c] === 'question')
        );
        
        console.log(`  Valid targets: ${validTargets.length}`);
        return validTargets.length;
    }
    
    function calculateClusterProbability(row, col, probabilityScores, weaponType) {
        let positions = [];
        
        switch(weaponType) {
            case 'nuclear':
                positions = [
                    [row, col], [row-1, col], [row+1, col], [row, col-1], [row, col+1],
                    [row-1, col-1], [row-1, col+1], [row+1, col-1], [row+1, col+1]
                ];
                break;
            case 'fragment':
                positions = [[row, col], [row-1, col], [row-2, col], [row-3, col]];
                break;
            case 'missile':
                positions = [[row, col], [row-1, col], [row+1, col], [row, col-1], [row, col+1]];
                break;
        }
        
        return positions.reduce((total, [r, c]) => {
            if (r >= 0 && r < 10 && c >= 0 && c < 10 && probabilityScores[r] && probabilityScores[r][c]) {
                return total + probabilityScores[r][c];
            }
            return total;
        }, 0);
    }
    
    function isNearConfirmedHit(row, col, maxDistance) {
        return confirmedHits.some(hit => {
            const distance = Math.abs(hit.row - row) + Math.abs(hit.col - col);
            return distance <= maxDistance;
        });
    }
    
    // Weapon execution system
    function selectAndUseWeapon(weaponType) {
        console.log(`=== WEAPON EXECUTION DEBUG ===`);
        console.log(`Attempting to select weapon: ${weaponType}`);
        
        const weaponButtons = document.querySelectorAll('.weapon-button');
        console.log(`Found ${weaponButtons.length} weapon buttons for selection`);
        
        let weaponFound = false;
        let weaponSelected = false;
        
        weaponButtons.forEach((button, index) => {
            const img = button.querySelector('img');
            if (img) {
                const currentWeapon = img.getAttribute('alt');
                console.log(`Button ${index}: weapon type = ${currentWeapon}`);
                
                // Remove current selection
                button.classList.remove('is-selected');
                
                // Select the desired weapon
                if (currentWeapon === weaponType) {
                    weaponFound = true;
                    button.classList.add('is-selected');
                    button.click();
                    console.log(`Successfully selected weapon: ${weaponType}`);
                    weaponSelected = true;
                    return true;
                }
            } else {
                console.log(`Button ${index}: no img found`);
            }
        });
        
        if (!weaponFound) {
            console.log(`ERROR: Weapon type '${weaponType}' not found in available buttons`);
        }
        
        return weaponSelected;
    }
    
    // Helper function to get cell coordinates
    function getCellCoordinates(cellElement) {
        // First try data attributes
        let row = parseInt(cellElement.getAttribute('data-row'));
        let col = parseInt(cellElement.getAttribute('data-col'));
        
        // If data attributes don't exist, try class name pattern
        if (isNaN(row) || isNaN(col)) {
            const classNames = cellElement.className.match(/cell-(\d+)-(\d+)/);
            if (classNames && classNames.length >= 3) {
                row = parseInt(classNames[1]);
                col = parseInt(classNames[2]);
            } else {
                // Fallback: try extracting from any numeric classes
                const numbers = cellElement.className.match(/\d+/g);
                if (numbers && numbers.length >= 2) {
                    row = parseInt(numbers[0]);
                    col = parseInt(numbers[1]);
                }
            }
        }
        
        return [row || 0, col || 0];
    }
    
    // Enhanced function to analyze the current board state for probability calculations
    function analyzeBoardState() {
        const board = Array(10).fill().map(() => Array(10).fill('unknown'));
        let hitCount = 0;
        let missCount = 0;
        let destroyedCount = 0;
        let availableCount = 0;

        // Specifically analyze the opponent's board
        const opponentBoard = document.querySelector('.opponent app-battleship-board table');
        if (!opponentBoard) {
            console.log('Warning: Cannot find opponent board for analysis');
            return board;
        }

        opponentBoard.querySelectorAll('td[class*="cell-"]').forEach(cell => {
            const [row, col] = cell.className.match(/\d+/g).map(Number);

            // Check for previously tried cell (miss)
            if (cell.querySelector('svg.intersection.no-hit')) {
                board[row][col] = 'miss';
                missCount++;
            }
            // Check for hit
            else if (cell.querySelector('.hit.fire')) {
                board[row][col] = 'hit';
                hitCount++;
            }
            // Check for destroyed ship
            else if (cell.querySelector('.magictime.opacityIn.ship-cell.circle-dark')) {
                board[row][col] = 'destroyed';
                destroyedCount++;
            }
            // Normal untried cell
            else if (cell.querySelector('svg.intersection:not(.no-hit)')) {
                board[row][col] = 'available';
                availableCount++;
            }
        });

        // Log board analysis for debugging
        console.log(`Board Analysis - Hits: ${hitCount}, Misses: ${missCount}, Destroyed: ${destroyedCount}, Available: ${availableCount}`);
        
        return board;
    }

    // Enhanced function to get all available cells with probability-based scoring
    function getAvailableCells() {
        const cells = [];
        const board = analyzeBoardState();
        let questionMarkCount = 0;

        // Specifically target the opponent's board
        const opponentBoard = document.querySelector('.opponent app-battleship-board table');
        if (!opponentBoard) {
            console.log('Error: Cannot find opponent board for cell analysis');
            return [];
        }

        opponentBoard.querySelectorAll('td[class*="cell-"]').forEach(cell => {
            // Consider both regular untried cells and question mark cells
            const isRegularCell = cell.classList.contains('null') && cell.querySelector('svg.intersection:not(.no-hit)');
            const isQuestionMark = hasQuestionMark(cell);
            
            if (isRegularCell || isQuestionMark) {
                const [row, col] = cell.className.match(/\d+/g).map(Number);
                let score = calculateProbabilityScore(row, col, board);
                
                if (isQuestionMark) {
                    questionMarkCount++;
                    console.log(`Question mark found at [${row},${col}] with score: ${score}`);
                }
                
                cells.push({ cell, score, row, col, isQuestionMark });
            }
        });

        // Sort cells by probability score (highest first)
        const sortedCells = cells.sort((a, b) => b.score - a.score);
        
        // Log top candidates for debugging
        console.log(`Found ${cells.length} available cells (${questionMarkCount} question marks)`);
        if (sortedCells.length > 0) {
            const topCells = sortedCells.slice(0, 3);
            console.log('Top 3 probability targets:', topCells.map(c => `[${c.row},${c.col}]:${c.score.toFixed(1)}${c.isQuestionMark ? '(?)' : ''}`).join(', '));
        }
        
        // Update probability visualization
        updateProbabilityVisualization(board);
        
        return sortedCells.map(item => item.cell);
    }

    // Function to create and update probability visualization overlay
    function updateProbabilityVisualization(board) {
        const opponentBoard = document.querySelector('.opponent app-battleship-board table');
        if (!opponentBoard || !visualizationEnabled) return;

        // Remove existing probability overlays
        document.querySelectorAll('.probability-overlay').forEach(overlay => overlay.remove());

        let maxScore = 0;
        const cellScores = [];

        // Calculate scores for all cells and find maximum
        opponentBoard.querySelectorAll('td[class*="cell-"]').forEach(cell => {
            const [row, col] = cell.className.match(/\d+/g).map(Number);
            const score = calculateProbabilityScore(row, col, board);
            cellScores.push({ cell, score, row, col });
            if (score > maxScore) maxScore = score;
        });

        // Create probability overlays for each cell
        cellScores.forEach(({ cell, score, row, col }) => {
            // Skip cells that are already hit, missed, or destroyed
            if (score === 0) return;

            const overlay = document.createElement('div');
            overlay.className = 'probability-overlay';
            overlay.style.cssText = `
                position: absolute;
                bottom: 2px;
                right: 2px;
                background: rgba(0, 0, 0, 0.7);
                color: white;
                font-size: 10px;
                font-weight: bold;
                padding: 1px 3px;
                border-radius: 3px;
                pointer-events: none;
                z-index: 1000;
                font-family: monospace;
            `;
            
            // Color code based on probability (green = high, yellow = medium, red = low)
            const intensity = maxScore > 0 ? score / maxScore : 0;
            if (intensity > 0.7) {
                overlay.style.background = 'rgba(0, 150, 0, 0.8)';
            } else if (intensity > 0.4) {
                overlay.style.background = 'rgba(200, 150, 0, 0.8)';
            } else {
                overlay.style.background = 'rgba(150, 0, 0, 0.8)';
            }
            
            overlay.textContent = score.toFixed(1);
            
            // Position the overlay relative to the cell
            cell.style.position = 'relative';
            cell.appendChild(overlay);
        });

        console.log(`Probability visualization updated. Max score: ${maxScore.toFixed(1)}`);
    }

    // Ship sizes in standard Battleship
    const SHIP_SIZES = [5, 4, 3, 3, 2]; // Carrier, Battleship, Cruiser, Submarine, Destroyer
    
    // Function to update ship tracking based on board analysis
    function updateShipTracking(board) {
        let currentHits = 0;
        let currentSunk = 0;
        
        // Count current hits and sunk cells
        for (let row = 0; row < 10; row++) {
            for (let col = 0; col < 10; col++) {
                if (board[row][col] === 'hit') currentHits++;
                if (board[row][col] === 'destroyed') currentSunk++;
            }
        }
        
        // If sunk cells increased, a ship was destroyed
        if (currentSunk > totalSunkCells) {
            const newSunkCells = currentSunk - totalSunkCells;
            
            // Try to determine which ship was sunk based on size
            // Find the largest remaining ship that matches the sunk size
            for (let i = 0; i < remainingShips.length; i++) {
                if (remainingShips[i] === newSunkCells) {
                    sunkShips.push(remainingShips[i]);
                    remainingShips.splice(i, 1);
                    console.log(`Ship of size ${newSunkCells} sunk! Remaining ships:`, remainingShips);
                    break;
                }
            }
            
            totalSunkCells = currentSunk;
            confirmedHits = []; // Clear hits when ship is sunk
        }
        
        totalHitsOnBoard = currentHits;
        
        return {
            remainingShips: remainingShips.slice(),
            sunkShips: sunkShips.slice(),
            totalHits: currentHits,
            totalSunk: currentSunk
        };
    }
    
    // Enhanced probability calculation based on ship placement possibilities and density
    function calculateProbabilityScore(row, col, board) {
        let totalProbability = 0;
        let densityBonus = 0;
        
        // Update ship tracking first
        const shipInfo = updateShipTracking(board);
        
        // Only calculate probabilities for remaining ships
        remainingShips.forEach(shipSize => {
            let shipPlacements = 0;
            
            // Check horizontal placements
            for (let startCol = Math.max(0, col - shipSize + 1); startCol <= Math.min(9, col); startCol++) {
                if (startCol + shipSize <= 10) {
                    if (canPlaceShip(row, startCol, shipSize, 'horizontal', board)) {
                        shipPlacements += 1;
                    }
                }
            }
            
            // Check vertical placements
            for (let startRow = Math.max(0, row - shipSize + 1); startRow <= Math.min(9, row); startRow++) {
                if (startRow + shipSize <= 10) {
                    if (canPlaceShip(startRow, col, shipSize, 'vertical', board)) {
                        shipPlacements += 1;
                    }
                }
            }
            
            // Weight larger ships more heavily as they're harder to place
        // Apply Bayesian inference - adjust probability based on game state
        const bayesianWeight = calculateBayesianWeight(shipSize, shipPlacements, board);
        totalProbability += shipPlacements * bayesianWeight;
        });
        
        // Calculate density bonus - cells in areas with more possible ship placements
        const surroundingPositions = [
            [row-2, col], [row-1, col-1], [row-1, col], [row-1, col+1],
            [row, col-2], [row, col-1], [row, col+1], [row, col+2],
            [row+1, col-1], [row+1, col], [row+1, col+1], [row+2, col]
        ];
        
        surroundingPositions.forEach(([r, c]) => {
            if (r >= 0 && r < 10 && c >= 0 && c < 10) {
                if (board[r] && (board[r][c] === 'unknown' || board[r][c] === 'available')) {
                    densityBonus += 0.5; // Small bonus for each available nearby cell
                }
            }
        });
        
        // Bonus for cells adjacent to confirmed hits (but not already hit)
        const adjacentCells = [
            [row-1, col], [row+1, col], [row, col-1], [row, col+1]
        ];
        
        let hitBonus = 0;
        adjacentCells.forEach(([r, c]) => {
            if (r >= 0 && r < 10 && c >= 0 && c < 10) {
                if (board[r] && board[r][c] === 'hit') {
                    hitBonus += 100; // Very large bonus for adjacent cells
                }
            }
        });
        
        // Add bonus for cells 2 away from hits (potential ship continuation)
        const nearbyPositions = [
            [row-2, col], [row+2, col], [row, col-2], [row, col+2]
        ];
        
        nearbyPositions.forEach(([r, c]) => {
            if (r >= 0 && r < 10 && c >= 0 && c < 10) {
                if (board[r] && board[r][c] === 'hit') {
                    hitBonus += 20; // Smaller bonus for cells 2 away
                }
            }
        });
        
        // Check if this cell has a question mark and add bonus score
        const cell = getCellByCoordinates(row, col);
        if (cell && hasQuestionMark(cell)) {
            const qmValue = evaluateQuestionMarkValue(cell);
            console.log(`Question mark detected at [${row},${col}] - qmValue: ${qmValue}, adding bonus: ${qmValue * 2 + 25}`);
            totalProbability += qmValue * 2; // Double the question mark value
            totalProbability += 25; // Additional base bonus for question marks to ensure higher priority
        }
        
        // Add parity bonus for hunt mode (checkerboard pattern)
        // This is most effective when no ships have been hit yet
        if (totalHitsOnBoard === 0 && remainingShips.length === 5) {
            // Use parity pattern - prefer cells where (row + col) % 2 === 0
            // This ensures we hit every ship of size 2 or larger
            if ((row + col) % 2 === 0) {
                totalProbability += 10; // Significant bonus for parity cells
            }
        }
        
        // Endgame optimization - when few ships remain, be more aggressive
        const endgameBonus = calculateEndgameBonus(row, col, board);
        
        return totalProbability + hitBonus + densityBonus + endgameBonus;
    }
    
    // Endgame optimization function
    function calculateEndgameBonus(row, col, board) {
        let bonus = 0;
        const remainingShipCount = remainingShips.length;
        const smallestShip = remainingShips.length > 0 ? Math.min(...remainingShips) : 2;
        
        // When only 1-2 ships remain, focus on isolated areas
        if (remainingShipCount <= 2) {
            // Check if this cell is in an isolated area (good for finding last ships)
            let isolationScore = 0;
            const checkRadius = 2;
            
            for (let r = row - checkRadius; r <= row + checkRadius; r++) {
                for (let c = col - checkRadius; c <= col + checkRadius; c++) {
                    if (r >= 0 && r < 10 && c >= 0 && c < 10 && board[r] && board[r][c]) {
                        if (board[r][c] === 'available' || board[r][c] === 'unknown') {
                            isolationScore++;
                        }
                    }
                }
            }
            
            // Prefer areas with more available cells (potential ship hiding spots)
            bonus += isolationScore * 2;
        }
        
        // When only the smallest ships remain, use different parity
        if (remainingShipCount <= 3 && smallestShip === 2) {
            // For destroyer hunting, any parity works, but prefer corners and edges
            if (row === 0 || row === 9 || col === 0 || col === 9) {
                bonus += 5; // Edge bonus
            }
            if ((row === 0 || row === 9) && (col === 0 || col === 9)) {
                bonus += 3; // Corner bonus
            }
        }
        
        // When many ships are sunk, increase aggression in unexplored areas
        if (sunkShips.length >= 3) {
            // Count nearby misses - avoid areas with many misses
            let nearbyMisses = 0;
            for (let r = row - 1; r <= row + 1; r++) {
                for (let c = col - 1; c <= col + 1; c++) {
                    if (r >= 0 && r < 10 && c >= 0 && c < 10 && board[r] && board[r][c] === 'miss') {
                        nearbyMisses++;
                    }
                }
            }
            
            // Penalize cells near many misses
            bonus -= nearbyMisses * 3;
        }
        
        return bonus;
    }
    
    // Bayesian inference for probability weighting
    function calculateBayesianWeight(shipSize, shipPlacements, board) {
        let baseWeight = shipSize / 3; // Original weighting
        
        // Prior probability adjustments based on ship size
        const shipSizeMultiplier = {
            5: 1.5, // Carrier is hardest to place
            4: 1.3, // Battleship
            3: 1.1, // Cruiser/Submarine
            2: 0.9  // Destroyer is easiest to place
        };
        
        baseWeight *= (shipSizeMultiplier[shipSize] || 1.0);
        
        // Likelihood adjustments based on current board state
        const gameProgress = (totalSunkCells + totalHitsOnBoard) / 17; // Total ship cells = 17
        
        // Early game: prefer larger ships (they're more likely to be hit first)
        if (gameProgress < 0.3) {
            if (shipSize >= 4) {
                baseWeight *= 1.2;
            }
        }
        // Mid game: balanced approach
        else if (gameProgress < 0.7) {
            baseWeight *= 1.0; // No adjustment
        }
        // Late game: focus on remaining ships
        else {
            // If this is one of the few remaining ships, increase its weight
            if (remainingShips.includes(shipSize)) {
                const rarityBonus = 5.0 / remainingShips.length; // More rare = higher weight
                baseWeight *= (1.0 + rarityBonus);
            }
        }
        
        // Posterior probability: adjust based on observed hit patterns
        if (totalHitsOnBoard > 0) {
            // If we have hits, ships near hits are more likely
            // This is handled in the hit bonus, but we can adjust the base weight too
            if (shipPlacements > 0) {
                baseWeight *= 1.1; // Small bonus for ships that can be placed
            }
        }
        
        // Constraint satisfaction: heavily penalize impossible placements
        if (shipPlacements === 0) {
            return 0; // Impossible placement
        }
        
        return baseWeight;
    }
    
    // Enhanced function to check if a ship can be placed at a specific position with pattern recognition
    function canPlaceShip(startRow, startCol, shipSize, orientation, board) {
        // First check basic placement validity
        for (let i = 0; i < shipSize; i++) {
            const checkRow = orientation === 'vertical' ? startRow + i : startRow;
            const checkCol = orientation === 'horizontal' ? startCol + i : startCol;
            
            // Check bounds
            if (checkRow < 0 || checkRow >= 10 || checkCol < 0 || checkCol >= 10) {
                return false;
            }
            
            // Check if cell is already hit, missed, or destroyed
            if (board[checkRow] && (board[checkRow][checkCol] === 'miss' || board[checkRow][checkCol] === 'destroyed')) {
                return false;
            }
        }
        
        // Advanced pattern recognition: Check ship spacing constraints
        // Most Battleship variants don't allow ships to touch each other
        for (let i = 0; i < shipSize; i++) {
            const shipRow = orientation === 'vertical' ? startRow + i : startRow;
            const shipCol = orientation === 'horizontal' ? startCol + i : startCol;
            
            // Check all 8 adjacent cells for destroyed ships (diagonal touching rule)
            const adjacentPositions = [
                [shipRow-1, shipCol-1], [shipRow-1, shipCol], [shipRow-1, shipCol+1],
                [shipRow, shipCol-1],                          [shipRow, shipCol+1],
                [shipRow+1, shipCol-1], [shipRow+1, shipCol], [shipRow+1, shipCol+1]
            ];
            
            for (const [adjRow, adjCol] of adjacentPositions) {
                if (adjRow >= 0 && adjRow < 10 && adjCol >= 0 && adjCol < 10) {
                    if (board[adjRow] && board[adjRow][adjCol] === 'destroyed') {
                        // Check if this destroyed cell could be part of our current ship
                        let isPartOfCurrentShip = false;
                        for (let j = 0; j < shipSize; j++) {
                            const currentShipRow = orientation === 'vertical' ? startRow + j : startRow;
                            const currentShipCol = orientation === 'horizontal' ? startCol + j : startCol;
                            if (adjRow === currentShipRow && adjCol === currentShipCol) {
                                isPartOfCurrentShip = true;
                                break;
                            }
                        }
                        
                        // If it's not part of our ship, this placement violates spacing rules
                        if (!isPartOfCurrentShip) {
                            return false;
                        }
                    }
                }
            }
        }
        
        // Check for consistency with existing hits
        // If there are hits that should be part of this ship, ensure they align
        let hitsInShip = 0;
        for (let i = 0; i < shipSize; i++) {
            const checkRow = orientation === 'vertical' ? startRow + i : startRow;
            const checkCol = orientation === 'horizontal' ? startCol + i : startCol;
            
            if (board[checkRow] && board[checkRow][checkCol] === 'hit') {
                hitsInShip++;
            }
        }
        
        // If this ship placement would include hits, it's more likely to be correct
        // This is handled in the probability calculation as a bonus
        
        return true;
    }



// Simplified handleAttackResult for probability-based system
function handleAttackResult(cell) {
    // Extract row and column from cell class name
    const [row, col] = getCellCoordinates(cell);

    // Check if this was a question mark that got resolved
    const wasQuestionMark = hasQuestionMark(cell);
    
    // Check for hit (including fire hit and skull hit)
    if (cell.querySelector('.hit.fire') || isHitWithSkull(cell)) {
        const hitCoord = {
            row: row,
            col: col
        };
        confirmedHits.push(hitCoord);
        console.log('Confirmed hit at:', hitCoord);
        
        if (wasQuestionMark) {
            console.log(`Question mark at [${row},${col}] resolved to HIT`);
        }
    } 
    // Check for miss (including no-hit intersection)
    else if (cell.querySelector('.miss') || cell.querySelector('svg.intersection.no-hit')) {
        console.log('Miss on cell:', getCellCoordinates(cell));
        
        if (wasQuestionMark) {
            console.log(`Question mark at [${row},${col}] resolved to MISS`);
        }
    }
    // If it's still a question mark after clicking, log this for debugging
    else if (wasQuestionMark) {
        console.log(`Question mark at [${row},${col}] was clicked but still appears as question mark`);
    }

    // Check if ship was sunk and clear hits if so
    if (cell.querySelector('.magictime.opacityIn.ship-cell.circle-dark')) {
        console.log('Ship sunk! Removing sunk ship hits from confirmed hits.');
        confirmedHits = [];
    }
}

// Function to get adjacent cells with optional radius for probability calculations
function getAdjacentCells(cell, radius = 1) {
    const [row, col] = getCellCoordinates(cell);
    let adjacentCells = [];

    if (radius === 1) {
        // Standard adjacent cells (up, down, left, right)
        adjacentCells.push(
            getCellByCoordinates(row-1, col),
            getCellByCoordinates(row+1, col),
            getCellByCoordinates(row, col-1),
            getCellByCoordinates(row, col+1)
        );
    } else {
        // For larger radius, get all cells within the specified distance
        for (let dRow = -radius; dRow <= radius; dRow++) {
            for (let dCol = -radius; dCol <= radius; dCol++) {
                if (dRow === 0 && dCol === 0) continue; // Skip the center cell
                
                const newRow = row + dRow;
                const newCol = col + dCol;

                // Check if the new position is within bounds
                if (newRow >= 0 && newRow < 10 && newCol >= 0 && newCol < 10) {
                    const adjacentCell = getCellByCoordinates(newRow, newCol);
                    if (adjacentCell) {
                        adjacentCells.push(adjacentCell);
                    }
                }
            }
        }
    }

    // Filter out null cells and already attacked cells, but include question marks
    return adjacentCells.filter(cell => {
        if (!cell) return false;
        
        // Include question mark cells as valid targets
        if (hasQuestionMark(cell)) {
            return true;
        }
        
        // Include regular null cells that haven't been attacked
        return cell.classList.contains('null') && cell.querySelector('svg.intersection:not(.no-hit)');
    });
}

// Function to determine orientation based on confirmed hits
function determineOrientation(hitsToCheck) {
    // If no hits are provided, use the confirmed hits
    if (!hitsToCheck) {
        hitsToCheck = confirmedHits.slice();
    }

    // Need at least 2 hits to determine orientation
    if (!hitsToCheck || hitsToCheck.length < 2) {
        console.log('Not enough hits to determine orientation');
        return null;
    }

    // Create copies of the arrays to avoid modifying the original
    const sortedByRow = [...hitsToCheck].sort((a, b) => a.row - b.row);
    const sortedByCol = [...hitsToCheck].sort((a, b) => a.col - b.col);

    // Check if all hits are in the same column (vertical orientation)
    let sameColumn = true;
    for (let i = 1; i < sortedByCol.length; i++) {
        if (sortedByCol[i].col !== sortedByCol[0].col) {
            sameColumn = false;
            break;
        }
    }

    // Check if all hits are in the same row (horizontal orientation)
    let sameRow = true;
    for (let i = 1; i < sortedByRow.length; i++) {
        if (sortedByRow[i].row !== sortedByRow[0].row) {
            sameRow = false;
            break;
        }
    }

    if (sameColumn) {
        console.log('Determined vertical orientation');
        return 'vertical';
    }
    if (sameRow) {
        console.log('Determined horizontal orientation');
        return 'horizontal';
    }

    console.log('Could not determine orientation');
    return null;
}

    // Function to simulate a click on a cell and handle results
    function attackCell(cell) {
        if (cell) {
            cell.click();  // Simulate clicking the cell
            console.log('Attacked cell:', cell);
            
            // After attack, check if it was a hit
            setTimeout(() => handleAttackResult(cell), 1000);  // Slight delay to allow DOM to update
        }
    }

    let lastAttackTime = 0; // Track the last attack time

    // Function to check if a cell is a confirmed hit with skull
    function isHitWithSkull(cell) {
        return cell.querySelector('.hit.skull') !== null;
    }

    // Function to check if a cell has a question mark
    function hasQuestionMark(cell) {
        // First check if the cell has been resolved to a hit or miss
        const isHit = cell.querySelector('.hit.fire') || cell.querySelector('.hit.skull');
        const isMiss = cell.querySelector('.miss') || cell.querySelector('svg.intersection.no-hit');
        const isDestroyed = cell.querySelector('.magictime.opacityIn.ship-cell.circle-dark');
        
        // If the cell has been resolved, it's no longer a question mark
        if (isHit || isMiss || isDestroyed) {
            return false;
        }
        
        const hasGift = cell.querySelector('.gift.animated.tin-in') !== null;
        const hasGiftTaken = cell.querySelector('.gift-taken') !== null;
        const result = hasGift || hasGiftTaken;
        
        if (result) {
            const [row, col] = getCellCoordinates(cell);
            console.log(`Question mark found at [${row},${col}] - hasGift: ${hasGift}, hasGiftTaken: ${hasGiftTaken}`);
        }
        
        return result;
    }

    // Function to evaluate the strategic value of a question mark
    function evaluateQuestionMarkValue(cell) {
        if (!hasQuestionMark(cell)) return 0;

        const [row, col] = getCellCoordinates(cell);
        let value = 5; // Base value for question marks

        // Check surrounding cells for hits or misses to determine strategic value
        const surroundingCells = getSurroundingCells(row, col);
        let hitCount = 0;
        let missCount = 0;

        surroundingCells.forEach(coords => {
            const surroundingCell = getCellByCoordinates(coords.row, coords.col);
            if (surroundingCell) {
                if (isHitWithSkull(surroundingCell)) {
                    hitCount++;
                    value += 3; // Increase value if near a hit
                } else if (surroundingCell.querySelector('.miss')) {
                    missCount++;
                    value -= 1; // Decrease value if near a miss
                }
            }
        });

        // If the question mark is surrounded by many misses, it's less valuable
        if (missCount > 2) value -= 3;

        // If the question mark is near hits, it's more valuable
        if (hitCount > 0) value += hitCount * 2;

        // Check if the question mark is in a strategic position (center or edges)
        if ((row > 2 && row < 7) && (col > 2 && col < 7)) {
            value += 2; // Center positions are more valuable
        }

        // Check if the question mark is aligned with confirmed hits
        if (confirmedHits.length >= 2) {
            const shipOrientation = determineOrientation(confirmedHits);
            let isAligned = false;

            if (shipOrientation && shipOrientation === 'horizontal') {
                // Check if question mark is in the same row as any confirmed hit
                for (const hit of confirmedHits) {
                    if (hit.row === row) {
                        isAligned = true;
                        break;
                    }
                }
            } else if (shipOrientation && shipOrientation === 'vertical') {
                // Check if question mark is in the same column as any confirmed hit
                for (const hit of confirmedHits) {
                    if (hit.col === col) {
                        isAligned = true;
                        break;
                    }
                }
            }

            // If aligned with confirmed hits, give it a significant bonus
            if (isAligned) {
                value += 8;
                console.log(`Question mark at [${row},${col}] is aligned with ship orientation, adding bonus value`);
            }
        }

        return value;
    }

    // Helper function to get cell coordinates (removed duplicate - using the one above that checks data attributes first)

    // Helper function to get surrounding cells (8 directions)
    function getSurroundingCells(row, col) {
        const directions = [
            [-1, -1], [-1, 0], [-1, 1],
            [0, -1],           [0, 1],
            [1, -1],  [1, 0],  [1, 1]
        ];

        return directions.map(([dx, dy]) => {
            const newRow = row + dx;
            const newCol = col + dy;
            if (newRow >= 0 && newRow < 10 && newCol >= 0 && newCol < 10) {
                return { row: newRow, col: newCol };
            }
            return null;
        }).filter(coords => coords !== null);
    }

    // Helper function to get cell by coordinates
    function getCellByCoordinates(row, col) {
        // Find the opponent board first
        const opponentBoard = document.querySelector('.opponent app-battleship-board table');
        if (!opponentBoard) return null;
        
        // Search through all cells to find the one with matching coordinates
        const cells = opponentBoard.querySelectorAll('td[class*="cell-"]');
        for (const cell of cells) {
            const [cellRow, cellCol] = getCellCoordinates(cell);
            if (cellRow === row && cellCol === col) {
                return cell;
            }
        }
        return null;
    }

    // Function to check if the game is in a ready state for an attack
    function isGameReady() {
        let opponentBoard = document.querySelector('.opponent app-battleship-board table');  // Opponent's board ID
        return opponentBoard && !opponentBoard.classList.contains('inactive'); // Adjust the class as per game state
    }

// Function to introduce a delay in milliseconds
function waitForAttack(delay) {
    return new Promise(resolve => setTimeout(resolve, delay));
}


// Adjusted performAttack to add a 2-second delay and check board state
async function performAttack(currentElementValue) {
    const now = Date.now();

    // Check if enough time has passed since the last attack (2 seconds)
    if (now - lastAttackTime < 2000) {
        console.log("Waiting for 2 seconds before the next attack.");
        return;
    }

    // Only perform an attack if the game is ready
    if (!isGameReady()) {
        console.log("Game is not ready. Waiting...");
        return;
    }

    console.log('Performing attack based on current element value:', currentElementValue);

    // Wait 2 seconds before the next attack
    await waitForAttack(2000);

    // Select cell to attack based on hunt or target mode
    let cell = huntMode ? huntModeAttack() : targetModeAttack();
    if (cell) {
        attackCell(cell);
        lastAttackTime = now; // Update the last attack time
    } else {
        console.log("No cell available to attack.");
    }
}


    GM.getValue('username').then(function(username) {
        if (!username) {
            username = prompt('Please enter your Papergames username:');
            GM.setValue('username', username);
        }
    });

// Simplified probability-based attack system
function updateBoard() {
    console.log("=== AI Turn Started ===");
    
    // Update probability visualization first
    const board = analyzeBoardState();
    updateProbabilityVisualization(board);
    
    GM.getValue("username").then(function(username) {
        var profileOpener = [...document.querySelectorAll(".text-truncate.cursor-pointer")].find(
            opener => opener.textContent.trim() === username
        );

        var chronometer = document.querySelector("app-chronometer");
        var numberElement = profileOpener.parentNode ? profileOpener.parentNode.querySelectorAll("span")[4] : null;

        var currentElement = chronometer || numberElement;
        console.log("Current Element:", currentElement);

        // Check for error message first
        checkForErrorAndRefresh();

        // Use pure probability-based targeting
        const bestCell = findBestProbabilityCell();
        if (bestCell) {
            console.log("Selected optimal cell based on probability calculations");
            
            // Get cell coordinates for weapon selection
            const [row, col] = getCellCoordinates(bestCell);
            
            // Build probability scores matrix for weapon selection
            const probabilityScores = {};
            for (let r = 0; r < 10; r++) {
                probabilityScores[r] = {};
                for (let c = 0; c < 10; c++) {
                    probabilityScores[r][c] = calculateProbabilityScore(r, c, board);
                }
            }
            
            // Select optimal weapon before attacking
            const optimalWeapon = selectOptimalWeapon(row, col, board, probabilityScores);
            console.log(`Using weapon: ${optimalWeapon}`);
            
            // Select and use the optimal weapon
            selectAndUseWeapon(optimalWeapon);
            
            // Small delay to ensure weapon selection is processed
            setTimeout(() => {
                attackCell(bestCell);
            }, 100);
            return;
        }

        // Fallback if no cells available
        console.log("No valid cells found to attack!");
        performAttack(currentElement.textContent);
    });
}

// Manual function to refresh probability visualization
function refreshProbabilityVisualization() {
    const board = analyzeBoardState();
    updateProbabilityVisualization(board);
    console.log("Probability visualization manually refreshed");
}

// Expose functions to global scope for manual triggering
window.refreshProbabilityVisualization = refreshProbabilityVisualization;
window.toggleProbabilityVisualization = function() {
    visualizationEnabled = !visualizationEnabled;
    if (visualizationEnabled) {
        console.log('Probability visualization enabled');
        refreshProbabilityVisualization();
    } else {
        console.log('Probability visualization disabled');
        // Remove all existing overlays
        const overlays = document.querySelectorAll('.probability-overlay');
        overlays.forEach(overlay => overlay.remove());
    }
    return visualizationEnabled;
};

// Function to find the cell with the highest probability score
function findBestProbabilityCell() {
    const board = analyzeBoardState();
    const opponentBoard = document.querySelector('.opponent app-battleship-board table');
    if (!opponentBoard) {
        console.log('Cannot find opponent board');
        return null;
    }

    let bestCell = null;
    let bestScore = -1;

    opponentBoard.querySelectorAll('td[class*="cell-"]').forEach(cell => {
        // Only consider cells that haven't been attacked
        if (cell.classList.contains('null') && cell.querySelector('svg.intersection:not(.no-hit)') || hasQuestionMark(cell)) {
            const [row, col] = getCellCoordinates(cell);
            const score = calculateProbabilityScore(row, col, board);
            
            // console.log(`Cell [${row},${col}] probability score: ${score}`);
            
            if (score > bestScore) {
                bestScore = score;
                bestCell = cell;
            }
        }
    });

    console.log(`Best cell found with score: ${bestScore}`);
    return bestCell;
}

// Function to check for error message and refresh if needed
function checkForErrorAndRefresh() {
    const errorToast = document.querySelector('.toast-error .toast-message');
    if (errorToast && errorToast.textContent.includes('The targeted frame is already played')) {
        location.reload();
    }
}
// Legacy functions removed - now using pure probability-based targeting





    // Auto queue functions
    function toggleAutoQueue() {
        // Toggle the state
        isAutoQueueOn = !isAutoQueueOn;
        GM.setValue('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('span.front.text.btn.btn-light');
        if (leaveRoomButton && leaveRoomButton.textContent.includes('Leave room')) {
            leaveRoomButton.click();
        }
    }

    function clickPlayOnlineButton() {
        var playOnlineButton = document.querySelector('span.front.text.btn.btn-secondary.btn-lg.text-start.juicy-btn-inner');
        if (playOnlineButton) {
            playOnlineButton.click();
        }
    }

    // Periodically check for buttons when the toggle is on
    function checkButtonsPeriodically() {
        if (isAutoQueueOn) {
            clickLeaveRoomButton();
            clickPlayOnlineButton();
        }
    }

    // Create toggle buttons for probability visualization and auto queue
    function createToggleButton() {
        // Check if buttons already exist
        if (document.getElementById('probability-toggle')) return;
        
        // Probability toggle button
        const probButton = document.createElement('button');
        probButton.id = 'probability-toggle';
        probButton.textContent = 'Toggle Probability View';
        probButton.style.cssText = `
            position: fixed;
            top: 10px;
            right: 10px;
            z-index: 10000;
            padding: 8px 12px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            font-weight: bold;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
        `;
        
        probButton.addEventListener('click', function() {
            const enabled = window.toggleProbabilityVisualization();
            probButton.style.background = enabled ? '#4CAF50' : '#f44336';
            probButton.textContent = enabled ? 'Hide Probability View' : 'Show Probability View';
        });
        
        // Auto queue toggle button
        autoQueueToggleButton = document.createElement('button');
        autoQueueToggleButton.id = 'auto-queue-toggle';
        autoQueueToggleButton.textContent = 'Auto Queue Off';
        autoQueueToggleButton.style.cssText = `
            position: fixed;
            top: 50px;
            right: 10px;
            z-index: 10000;
            padding: 8px 12px;
            background: red;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            font-weight: bold;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
        `;
        
        autoQueueToggleButton.addEventListener('click', toggleAutoQueue);
        
        // Load saved auto queue state
        GM.getValue('isToggled', false).then(function(savedState) {
            isAutoQueueOn = savedState;
            autoQueueToggleButton.textContent = isAutoQueueOn ? 'Auto Queue On' : 'Auto Queue Off';
            autoQueueToggleButton.style.backgroundColor = isAutoQueueOn ? 'green' : 'red';
        });
        
        document.body.appendChild(probButton);
        document.body.appendChild(autoQueueToggleButton);
    }
    
    // Initialize toggle button when DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', createToggleButton);
    } else {
        createToggleButton();
    }

// Set up periodic checking for auto queue
setInterval(checkButtonsPeriodically, 1000);

// Set interval to update the board regularly
setInterval(updateBoard, 1000); // Check every second
})();
长期地址
遇到问题?请前往 GitHub 提 Issues。