您需要先安装一个扩展,例如 篡改猴、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 a new, stable simulation engine. // =================================================================================== const workerCode = ` self.onmessage = function(e) { const { board } = e.data; const bestMove = getNextMove(board); self.postMessage({ bestMove: bestMove }); }; /** * NEW v8.1: A rewritten, robust, and easy-to-understand simulation function. * This is the stable core of the AI. */ function simulateMove(board, direction) { const tempBoard = JSON.parse(JSON.stringify(board)); let moved = false; 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) { const originalJson = JSON.stringify(row); let newRow = slide(row); newRow = combine(newRow); newRow = slide(newRow); if (JSON.stringify(newRow) !== originalJson) { moved = true; } return newRow; } if (direction === 'left' || direction === 'right') { for (let r = 0; r < 4; r++) { let row = tempBoard[r]; if (direction === 'right') row.reverse(); row = operate(row); if (direction === 'right') row.reverse(); tempBoard[r] = row; } } else if (direction === 'up' || direction === 'down') { for (let c = 0; c < 4; c++) { let col = [tempBoard[0][c], tempBoard[1][c], tempBoard[2][c], tempBoard[3][c]]; if (direction === 'down') col.reverse(); col = operate(col); if (direction === 'down') col.reverse(); for (let r = 0; r < 4; r++) { tempBoard[r][c] = col[r]; } } } if (moved) { 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'; 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++; else monoTotals.left++; } for (let j = 0; j < 3; j++) { if (b[j][i] >= b[j+1][i]) monoTotals.down++; else 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': 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; } 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)) { moveScores.push({ direction: directions[dirKey], score: evaluateByStage(simBoard), board: simBoard }); } } if (moveScores.length === 0) return null; // No valid moves moveScores.sort((a, b) => b.score - a.score); const initial8192Count = board.flat().filter(v => v === 8192).length; if (initial8192Count >= 2) { 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 } } } 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) { if (!isAiRunning) return; const { bestMove } = e.data; updateStatsUI(); if (bestMove === null) { // No valid moves left endGame(); return; } if (!await attemptMove(bestMove)) { const fallbackMoves = ['ArrowRight', 'ArrowDown', 'ArrowLeft', 'ArrowUp'].filter(m => m !== bestMove); for (const move of fallbackMoves) { if (await attemptMove(move)) break; } } 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) { endGame(); return; } aiWorker.postMessage({ board: window.canvasGame.board }); } function endGame() { if (!isAiRunning) return; // Prevent multiple calls isAiRunning = false; clearTimeout(gameLoopTimeout); gamesPlayed++; updateStatsUI(); const btn = document.getElementById('auto-play-btn'); if(btn) { btn.disabled = false; btn.style.backgroundColor = '#27ae60'; btn.innerHTML = '▶ 启动AI'; } console.log("🎉 Game Over! Final Score: " + window.canvasGame.score); } 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: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 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> <label style="display:block; margin-bottom:12px;"> <div style="margin-bottom:5px; font-weight:bold;">速度控制 (ms/步):</div> <input type="range" id="speed-slider" min="50" max="500" value="${moveSpeed}" style="width:100%;"> <div style="text-align:center; font-size:12px; color:#776e65"><span id="speed-value">${moveSpeed}</span></div> </label> </div>`; document.body.appendChild(panel); 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'; // Changed to a stop button executeThinkCycle(); } else { isAiRunning = false; clearTimeout(gameLoopTimeout); this.disabled = false; this.style.backgroundColor = '#27ae60'; this.innerHTML = '▶ 启动AI'; } }; document.getElementById('speed-slider').oninput = function() { moveSpeed = parseInt(this.value); document.getElementById('speed-value').textContent = moveSpeed; }; } function updateStatsUI() { const score = window.canvasGame.score || 0; const maxTileVal = Math.max(0, ...window.canvasGame.board.flat()); if (score > highestScore) highestScore = score; if (maxTileVal > highestTile) highestTile = maxTileVal; 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); }); })();