您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
专为2048.linux.do设计的高性能AI
当前为
// ==UserScript== // @name linuxDo 2048 AI玩家 Plus // @namespace http://tampermonkey.net/ // @version 2.0 // @description 专为2048.linux.do设计的高性能AI // @author littleleo // @match https://2048.linux.do/* // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; // Prevent password manager interference document.addEventListener('keydown', function(e) { e.stopImmediatePropagation(); }, true); const AI_VERSION = "v2.0"; const TARGET_SCORE = 250000; // =================================================================================== // WEB WORKER CODE: The AI's brain with new 8192-merge risk detection. // =================================================================================== const workerCode = ` self.onmessage = function(e) { const { board } = e.data; const bestMove = getNextMove(board); self.postMessage({ bestMove: bestMove }); }; // The entire AI logic (brain) is placed inside the worker. function simulateMove(board, direction) { let moved = false; const tempBoard = JSON.parse(JSON.stringify(board)); function slide(row) { const arr = row.filter(val => val); const missing = 4 - arr.length; const zeros = Array(missing).fill(0); return arr.concat(zeros); } function combine(row) { for (let i = 0; i < 3; i++) { if (row[i] !== 0 && row[i] === row[i + 1]) { row[i] *= 2; row[i + 1] = 0; } } return row; } function operate(row) { let newRow = slide(row); newRow = combine(newRow); newRow = slide(newRow); return newRow; } const originalBoardState = JSON.stringify(tempBoard); switch (direction) { case 'left': for (let r = 0; r < 4; r++) tempBoard[r] = operate(tempBoard[r]); break; case 'right': for (let r = 0; r < 4; r++) tempBoard[r] = operate(tempBoard[r].reverse()).reverse(); break; case 'up': for (let c = 0; c < 4; c++) { let column = [tempBoard[0][c], tempBoard[1][c], tempBoard[2][c], tempBoard[3][c]]; const newColumn = operate(column); for (let r = 0; r < 4; r++) tempBoard[r][c] = newColumn[r]; } break; case 'down': for (let c = 0; c < 4; c++) { let column = [tempBoard[3][c], tempBoard[2][c], tempBoard[1][c], tempBoard[0][c]]; const newColumn = operate(column).reverse(); for (let r = 0; r < 4; r++) tempBoard[r][c] = newColumn[r]; } break; } if(JSON.stringify(originalBoardState) !== JSON.stringify(tempBoard)) { moved = true; Object.assign(board, tempBoard); } return moved; } function getGameStage(board) { let maxTile = 0; let emptyCells = 0; for (let r = 0; r < 4; r++) { for (let c = 0; c < 4; c++) { if (board[r][c] > maxTile) maxTile = board[r][c]; if (board[r][c] === 0) emptyCells++; } } if (emptyCells <= 3) return 'survival'; if (maxTile >= 8192) return 'endgame'; // New endgame stage if (maxTile >= 2048) return 'late'; if (maxTile >= 512) return 'middle'; return 'early'; } function evaluateByStage(b) { const stage = getGameStage(b); let score = 0; let emptyCount = 0, smoothness = 0, monotonicity = 0, maxTile = 0; let maxTilePos = {r: 0, c: 0}; let potentialMerges = 0; let islandPenalty = 0; const weights = [[15, 14, 13, 12], [8, 9, 10, 11], [7, 6, 5, 4], [0, 1, 2, 3]]; for (let r = 0; r < 4; r++) { for (let c = 0; c < 4; c++) { const tileValue = b[r][c]; if (tileValue === 0) { emptyCount++; } else { if (tileValue > maxTile) { maxTile = tileValue; maxTilePos = {r, c}; } score += Math.log2(tileValue) * weights[r][c]; if (c < 3) { const rightVal = b[r][c+1]; if (rightVal !== 0) { if (rightVal === tileValue) potentialMerges++; smoothness -= Math.abs(Math.log2(tileValue) - Math.log2(rightVal)); } } if (r < 3) { const downVal = b[r+1][c]; if (downVal !== 0) { if (downVal === tileValue) potentialMerges++; smoothness -= Math.abs(Math.log2(tileValue) - Math.log2(downVal)); } } if (tileValue <= 4) { if (c > 0 && c < 3 && b[r][c-1] > tileValue * 4 && b[r][c+1] > tileValue * 4) islandPenalty++; if (r > 0 && r < 3 && b[r-1][c] > tileValue * 4 && b[r+1][c] > tileValue * 4) islandPenalty++; } } } } let monoTotals = { up: 0, down: 0, left: 0, right: 0 }; for (let i = 0; i < 4; i++) { for (let j = 0; j < 3; j++) { if (b[i][j] >= b[i][j+1]) monoTotals.right++; if (b[i][j] <= b[i][j+1]) monoTotals.left++; } for (let j = 0; j < 3; j++) { if (b[j][i] >= b[j+1][i]) monoTotals.down++; if (b[j][i] <= b[j+1][i]) monoTotals.up++; } } monotonicity = Math.max(monoTotals.right, monoTotals.left) + Math.max(monoTotals.up, monoTotals.down); let cornerBonus = 0; if (maxTilePos.r === 3 && maxTilePos.c === 0) { cornerBonus = Math.log2(maxTile) * 10; } else if (maxTilePos.r === 3) { cornerBonus = Math.log2(maxTile) * 5; } switch (stage) { case 'survival': score += emptyCount * 20 + potentialMerges * 10 - islandPenalty * 5; break; case 'endgame': // In endgame, prioritize separation and structure above all case 'late': score += emptyCount * 5 + smoothness * 2.0 + monotonicity * 2.5 + cornerBonus * 2.0 - islandPenalty * 10; break; case 'middle': score += emptyCount * 3.0 + smoothness * 1.5 + monotonicity * 1.5 + cornerBonus - islandPenalty * 5; break; case 'early': score += emptyCount * 2.0 + smoothness * 1.0 + monotonicity * 1.0; break; } return score; } /** * NEW v8.0: The main decision function with 8192-merge risk assessment. */ function getNextMove(board) { const directions = { up: 'ArrowUp', right: 'ArrowRight', down: 'ArrowDown', left: 'ArrowLeft' }; let moveScores = []; for (const dirKey in directions) { const simBoard = JSON.parse(JSON.stringify(board)); if (simulateMove(simBoard, dirKey)) { const moveScore = evaluateByStage(simBoard); moveScores.push({ direction: directions[dirKey], score: moveScore, board: simBoard }); } } if (moveScores.length === 0) return 'ArrowRight'; // Should not happen if game is not over // Sort moves by score, descending moveScores.sort((a, b) => b.score - a.score); // --- 8192 MERGE RISK ASSESSMENT --- const initial8192Count = board.flat().filter(v => v === 8192).length; if (initial8192Count >= 2) { // Find the first move that does NOT merge the 8192s for (const move of moveScores) { const final8192Count = move.board.flat().filter(v => v === 8192).length; if (final8192Count >= initial8192Count) { return move.direction; // This move is safe, choose it. } } // If all moves merge 8192s (desperate situation), take the best one anyway. } return moveScores[0].direction; } `; // ===================================================================== // MAIN SCRIPT: Manages the worker, UI, and game interaction. // ===================================================================== let moveSpeed = 100; let isAiRunning = false; let gameLoopTimeout = null; let gamesPlayed = 0; let highestScore = 0; let highestTile = 0; const workerBlob = new Blob([workerCode], { type: 'application/javascript' }); const workerUrl = URL.createObjectURL(workerBlob); const aiWorker = new Worker(workerUrl); aiWorker.onmessage = async function(e) { const { bestMove } = e.data; updateStatsUI(false); // Update stats continuously if (!await attemptMove(bestMove)) { const fallbackMoves = ['ArrowRight', 'ArrowDown', 'ArrowLeft', 'ArrowUp'].filter(m => m !== bestMove); for (const move of fallbackMoves) { if (await attemptMove(move)) break; } } if (isAiRunning) { gameLoopTimeout = setTimeout(executeThinkCycle, moveSpeed); } }; async function attemptMove(direction) { const stateBefore = JSON.stringify(window.canvasGame.board); document.body.dispatchEvent(new KeyboardEvent('keydown', { key: direction, bubbles: true })); return new Promise(resolve => { const TIMEOUT_FOR_INVALID_MOVE = 500; const startTime = Date.now(); const checkInterval = setInterval(() => { if (JSON.stringify(window.canvasGame.board) !== stateBefore) { clearInterval(checkInterval); resolve(true); } else if (Date.now() - startTime > TIMEOUT_FOR_INVALID_MOVE) { clearInterval(checkInterval); resolve(false); } }, 50); }); } function executeThinkCycle() { if (!isAiRunning) return; if (window.canvasGame.gameOver) { isAiRunning = false; updateStatsUI(true); // Final update const btn = document.getElementById('auto-play-btn'); if(btn) { btn.disabled = false; btn.style.backgroundColor = '#27ae60'; btn.innerHTML = '▶ 启动AI'; } return; } aiWorker.postMessage({ board: window.canvasGame.board }); } // NEW v8.0: Professional control panel function createControlPanel() { if (document.getElementById('ai-control-panel')) return; const panel = document.createElement('div'); panel.id = 'ai-control-panel'; panel.style.cssText = `position: fixed; top: 20px; right: 20px; background: rgba(255, 255, 255, 0.95); padding: 15px; border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.25); z-index: 9999; font-family: Arial, sans-serif; min-width: 250px; border: 1px solid #ddd;`; panel.innerHTML = ` <h3 style="margin-top:0; color: #776e65; border-bottom: 1px solid #eee; padding-bottom: 10px;">2048 AI ${AI_VERSION}</h3> <div style="margin-bottom:15px;"> <button id="auto-play-btn" style="padding:10px 15px; width:100%; background:#27ae60; color:white; border:none; border-radius:4px; cursor:pointer; font-weight:bold; font-size:16px;"> ▶ 启动AI </button> </div> <div id="ai-stats" style="font-size:14px; line-height:1.8; margin-bottom:15px; background:#f9f9f9; padding:10px; border-radius:5px;"> <div>游戏次数: <span id="games-count" style="float:right;">0</span></div> <div>最高分数: <span id="high-score" style="float:right;">0</span></div> <div>最大方块: <span id="max-tile" style="float:right;">0</span></div> <div style="font-weight:bold;">目标进度: <span id="target-progress" style="float:right; color:#e74c3c;">0%</span></div> </div> <div style="margin-bottom:15px;"> <label style="display:block; margin-bottom:12px;"> <div style="margin-bottom:5px; font-weight:bold;">速度控制:</div> <input type="range" id="speed-slider" min="50" max="500" value="${moveSpeed}" style="width:100%;"> <div style="text-align:center; font-size:14px;"><span id="speed-value">${moveSpeed}ms/步</span></div> </label> </div>`; document.body.appendChild(panel); // Bind events const autoPlayBtn = document.getElementById('auto-play-btn'); autoPlayBtn.onclick = function() { if (!isAiRunning) { if (window.canvasGame.gameOver) { alert("游戏已结束! 请先开始新游戏。"); return; } isAiRunning = true; this.disabled = true; this.style.backgroundColor = '#7f8c8d'; this.innerHTML = 'AI运行中...'; if(gamesPlayed === 0 && window.canvasGame.score === 0) gamesPlayed++; executeThinkCycle(); } }; const speedSlider = document.getElementById('speed-slider'); speedSlider.oninput = function() { moveSpeed = parseInt(this.value); document.getElementById('speed-value').textContent = `${moveSpeed}ms/步`; }; } function updateStatsUI(isFinal) { const score = window.canvasGame.score || 0; const maxTileVal = Math.max(...window.canvasGame.board.flat()); if (score > highestScore) highestScore = score; if (maxTileVal > highestTile) highestTile = maxTileVal; if (isFinal) gamesPlayed++; const progress = Math.min(100, (score / TARGET_SCORE * 100)).toFixed(1); document.getElementById('games-count').textContent = gamesPlayed; document.getElementById('high-score').textContent = highestScore.toLocaleString(); document.getElementById('max-tile').textContent = highestTile; document.getElementById('target-progress').textContent = `${progress}%`; } window.addEventListener('load', () => { setTimeout(createControlPanel, 1000); }); })();