Battleship AI for Papergames

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==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
})();