您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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 })();