linuxDo 2048 AI玩家 Plus

专为2048.linux.do设计的高性能AI

目前為 2025-07-16 提交的版本,檢視 最新版本

// ==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';
    
    // ======================== 核心配置 ========================
    const MOVE_INTERVAL = 150; // 默认移动延迟(ms)
    const TARGET_SCORE = 250000; // 25万分目标
    const AI_VERSION = "v2.0";
    let moveSpeed = MOVE_INTERVAL;
    
    // 高级策略系统
    const strategies = {
        default: {
            weights: [
                [6, 5, 4, 3],
                [5, 4, 3, 2],
                [4, 3, 2, 1],
                [3, 2, 1, 0]
            ],
            emptyWeight: 2.7
        },
        aggressive: {
            weights: [
                [10, 8, 6, 4],
                [8, 6, 4, 2],
                [6, 4, 2, 1],
                [4, 2, 1, 0]
            ],
            emptyWeight: 2.0
        },
        expert: {
            weights: [
                [15, 14, 13, 12],
                [8,  9,  10, 11],
                [7,  6,  5,  4],
                [0,  1,  2,  3]
            ],
            emptyWeight: 4.0
        },
        linuxdo: {
            weights: [
                [16, 15, 14, 13],
                [9, 10, 11, 12],
                [8, 7, 6, 5],
                [1, 2, 3, 4]
            ],
            emptyWeight: 4.5
        }
    };
    
    let currentStrategy = 'linuxdo';
    let isAiRunning = false;
    let isThinking = false;
    let gameLoopInterval = null;
    
    // 游戏统计
    let gamesPlayed = 0;
    let highestScore = 0;
    let highestTile = 0;
    let targetAchieved = false;

    // ======================== AI核心算法 ========================
    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;
                    moved = true;
                }
            }
            return row;
        }

        function operate(row) {
            row = slide(row);
            row = combine(row);
            return slide(row);
        }

        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++) {
                    const 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++) {
                    const 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(board) !== JSON.stringify(tempBoard)) {
            moved = true;
            Object.assign(board, tempBoard);
        }
        return moved;
    }

    function detectGameStage(board) {
        const tiles = board.flat();
        const maxTile = Math.max(...tiles.filter(Boolean));
        const emptyCount = 16 - tiles.filter(Boolean).length;
        const smallTiles = tiles.filter(t => t > 0 && t <= 64).length;
        
        if (maxTile >= 8192) return 'endgame';
        if (maxTile >= 4096) return 'lategame';
        if (emptyCount > 6 || smallTiles > 8) return 'earlygame';
        return 'midgame';
    }

    function enhancedEvaluate(b) {
        const strategy = strategies[currentStrategy];
        const stage = detectGameStage(b);
        let score = 0;
        let emptyCount = 0;
        let smoothness = 0;
        let maxTile = 0;
        let mergePossible = 0;
        let cornerBonus = 0;
        
        // 基础数据收集
        for (let r = 0; r < 4; r++) {
            for (let c = 0; c < 4; c++) {
                if (b[r][c] === 0) {
                    emptyCount++;
                } else {
                    const tileValue = b[r][c];
                    if (tileValue > maxTile) maxTile = tileValue;
                    
                    // 位置权重
                    score += Math.log2(tileValue) * strategy.weights[r][c];
                    
                    // 角落大值奖励
                    if ((r === 0 || r === 3) && (c === 0 || c === 3) && tileValue >= 1024) {
                        cornerBonus += tileValue * 0.5;
                    }
                    
                    // 平滑度评估
                    if (c < 3 && b[r][c+1] !== 0) {
                        const diff = Math.abs(Math.log2(tileValue) - Math.log2(b[r][c+1]));
                        smoothness -= diff;
                        if (diff === 0) mergePossible++;
                    }
                    if (r < 3 && b[r+1][c] !== 0) {
                        const diff = Math.abs(Math.log2(tileValue) - Math.log2(b[r+1][c]));
                        smoothness -= diff;
                        if (diff === 0) mergePossible++;
                    }
                }
            }
        }
        
        // 阶段特定策略
        if (stage === 'earlygame') {
            score += emptyCount * 4.5;
        } else if (stage === 'midgame') {
            score += emptyCount * 3.0;
        } else if (stage === 'lategame') {
            score += maxTile * 2.5;
            score += cornerBonus;
            score += avoidLargeTileMerge(b, maxTile) * 3.0;
        } else {
            score += avoidLargeTileMerge(b, maxTile) * 5.0;
        }
        
        // 通用评估因素
        score += smoothness * 1.5;
        score += mergePossible * 2.0;
        score += emptyCount * strategy.emptyWeight;
        
        return score;
    }
    
    function avoidLargeTileMerge(board, maxTile) {
        if (maxTile < 4096) return 0;
        
        let riskScore = 0;
        const largeTiles = [];
        
        for (let r = 0; r < 4; r++) {
            for (let c = 0; c < 4; c++) {
                if (board[r][c] >= 2048) {
                    largeTiles.push({r, c, value: board[r][c]});
                }
            }
        }
        
        for (let i = 0; i < largeTiles.length; i++) {
            for (let j = i + 1; j < largeTiles.length; j++) {
                const tileA = largeTiles[i];
                const tileB = largeTiles[j];
                
                if (tileA.value === tileB.value) {
                    const distR = Math.abs(tileA.r - tileB.r);
                    const distC = Math.abs(tileA.c - tileB.c);
                    
                    if ((distR === 1 && distC === 0) || (distR === 0 && distC === 1)) {
                        riskScore -= 50000;
                    } else if (distR === 0 || distC === 0) {
                        riskScore -= 20000;
                    }
                }
            }
        }
        
        return riskScore;
    }

    function getNextMove(board) {
        const directions = {up: 'ArrowUp', right: 'ArrowRight', down: 'ArrowDown', left: 'ArrowLeft'};
        let bestScore = -Infinity;
        let bestDirection = 'ArrowRight';

        for (const dirKey in directions) {
            const simBoard = JSON.parse(JSON.stringify(board));
            if (simulateMove(simBoard, dirKey)) {
                const moveScore = enhancedEvaluate(simBoard);
                if (moveScore > bestScore) {
                    bestScore = moveScore;
                    bestDirection = directions[dirKey];
                }
            }
        }
        return bestDirection;
    }

    // ======================== 游戏执行引擎 ========================
    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 startTime = Date.now();
            const checkInterval = setInterval(() => {
                if (JSON.stringify(window.canvasGame.board) !== stateBefore) {
                    clearInterval(checkInterval);
                    resolve(true);
                } else if (Date.now() - startTime > 500) {
                    clearInterval(checkInterval);
                    resolve(false);
                }
            }, 50);
        });
    }

    async function executeThinkCycle() {
        if (isThinking || !isAiRunning) return;
        
        // 游戏结束检测
        if (window.canvasGame.gameOver || (window.canvasGame.victory && !window.canvasGame.keepPlaying)) {
            const score = window.canvasGame.score;
            const maxTile = window.canvasGame.maxTile;
            
            console.log(`🎉 游戏结束! 得分: ${score} | 最大方块: ${maxTile}`);
            
            // 更新统计
            gamesPlayed++;
            if (score > highestScore) {
                highestScore = score;
                if (score >= TARGET_SCORE) {
                    targetAchieved = true;
                    console.log(`🏆 达成目标分数: ${TARGET_SCORE}`);
                }
            }
            if (maxTile > highestTile) highestTile = maxTile;
            
            // 更新UI
            updateStatsUI();
            
            // 停止AI
            isAiRunning = false;
            clearInterval(gameLoopInterval);
            
            // 启用按钮
            const autoPlayBtn = document.getElementById('auto-play-btn');
            if (autoPlayBtn) {
                autoPlayBtn.disabled = false;
                autoPlayBtn.style.backgroundColor = '#27ae60';
                autoPlayBtn.textContent = 'AI自动游戏';
            }
            
            return;
        }

        isThinking = true;

        try {
            const board = window.canvasGame.board;
            const bestMove = getNextMove(board);
            
            if (!await attemptMove(bestMove)) {
                // 备用移动方案
                const fallbackMoves = ['ArrowRight', 'ArrowDown', 'ArrowLeft', 'ArrowUp'];
                for(const move of fallbackMoves) {
                    if (await attemptMove(move)) break;
                }
            }
        } catch (e) {
            console.error("AI执行错误:", e);
        } finally {
            isThinking = false;
        }
    }
    
    // ======================== 用户界面与控制 ========================
    function createControlPanel() {
        const panel = document.createElement('div');
        panel.id = 'ai-control-panel';
        panel.style.cssText = `
            position: absolute;
            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>
            <div style="margin-bottom:15px;">
                <label style="display:block; margin-bottom:12px;">
                    <div style="margin-bottom:5px; font-weight:bold;">游戏策略:</div>
                    <select id="strategy-select" style="width:100%; padding:8px; border-radius:4px; border:1px solid #ddd;">
                        ${Object.keys(strategies).map(strat => 
                            `<option value="${strat}" ${strat === currentStrategy ? 'selected' : ''}>
                             ${strat.charAt(0).toUpperCase() + strat.slice(1)}
                             </option>`
                        ).join('')}
                    </select>
                </label>
            </div>
            <div style="color:#777; font-size:12px; text-align:center; margin-top:10px; padding-top:10px; border-top:1px solid #eee;">
                目标分数: ${TARGET_SCORE.toLocaleString()}
            </div>
        `;
        
        return panel;
    }

    function updateStatsUI() {
        if (!document.getElementById('games-count')) return;
        
        document.getElementById('games-count').textContent = gamesPlayed;
        document.getElementById('high-score').textContent = highestScore;
        document.getElementById('max-tile').textContent = highestTile || 0;
        
        const progress = Math.min(100, Math.round((highestScore / TARGET_SCORE) * 100));
        const progressEl = document.getElementById('target-progress');
        progressEl.textContent = `${progress}%`;
        progressEl.style.color = progress > 90 ? '#27ae60' : 
                               progress > 50 ? '#f39c12' : '#e74c3c';
        progressEl.style.fontWeight = progress > 80 ? 'bold' : 'normal';
    }
    
    function toggleAI() {
        const button = document.getElementById('auto-play-btn');
        
        if (!button) {
            console.error("未找到AI按钮!");
            return;
        }
        
        if (isAiRunning) {
            // 停止AI
            isAiRunning = false;
            clearInterval(gameLoopInterval);
            button.style.backgroundColor = '#27ae60';
            button.textContent = '▶ 启动AI';
            console.log('AI已停止');
        } else {
            // 检查游戏状态
            if (window.canvasGame.gameOver || (window.canvasGame.victory && !window.canvasGame.keepPlaying)) {
                alert("游戏已结束! 请先点击'新游戏'按钮");
                return;
            }
            
            // 启动AI
            isAiRunning = true;
            button.style.backgroundColor = '#e74c3c';
            button.textContent = '■ 停止AI';
            console.log('AI开始运行...');
            gameLoopInterval = setInterval(executeThinkCycle, moveSpeed);
        }
    }
    
    function handleSpeedChange() {
        moveSpeed = parseInt(this.value);
        document.getElementById('speed-value').textContent = moveSpeed + 'ms/步';
        if (isAiRunning) {
            clearInterval(gameLoopInterval);
            gameLoopInterval = setInterval(executeThinkCycle, moveSpeed);
        }
    }
        // ======================== 初始化函数 ========================
    function initializeAI() {
        // 等待游戏对象加载
        if (typeof window.canvasGame === 'undefined' || !window.canvasGame.board) {
            console.log("等待游戏加载...");
            setTimeout(initializeAI, 500);
            return;
        }
        
        // 确保游戏容器存在
        const gameContainer = document.querySelector('.game-container');
        if (!gameContainer) {
            console.log("等待游戏容器...");
            setTimeout(initializeAI, 500);
            return;
        }
        
        // 检查是否已添加控制面板
        if (document.getElementById('ai-control-panel')) {
            console.log("AI控制面板已存在");
            return;
        }
        
        // 创建并添加控制面板
        const panel = createControlPanel();
        gameContainer.appendChild(panel);
        console.log("AI控制面板已添加");
        
        // 添加事件监听
        document.getElementById('auto-play-btn').addEventListener('click', toggleAI);
        document.getElementById('speed-slider').addEventListener('input', handleSpeedChange);
        document.getElementById('strategy-select').addEventListener('change', function() {
            currentStrategy = this.value;
            console.log(`策略切换为: ${currentStrategy}`);
        });
        
        // 添加新游戏监听
        document.querySelector('.new-game-btn')?.addEventListener('click', () => {
            if (isAiRunning) {
                isAiRunning = false;
                clearInterval(gameLoopInterval);
                const button = document.getElementById('auto-play-btn');
                if (button) {
                    button.style.backgroundColor = '#27ae60';
                    button.textContent = '▶ 启动AI';
                }
                console.log("新游戏开始,AI已停止");
            }
        });
        
        // 初始化统计
        updateStatsUI();
        console.log(`2048 AI ${AI_VERSION} 初始化完成`);
    }

    // ======================== 启动脚本 ========================
    (function() {
        // 防止重复初始化
        if (window.AI_LOADED) return;
        window.AI_LOADED = true;
        
        console.log(`2048 AI ${AI_VERSION} 加载中,目标分数: ${TARGET_SCORE.toLocaleString()}`);
        
        // 确保页面完全加载
        if (document.readyState === 'complete') {
            initializeAI();
        } else {
            window.addEventListener('load', function() {
                setTimeout(initializeAI, 1000); // 额外等待确保完全加载
            });
        }
        
        // 添加键盘快捷键: Alt+A 切换AI
        document.addEventListener('keydown', function(e) {
            if (e.altKey && e.key.toLowerCase() === 'a') {
                const button = document.getElementById('auto-play-btn');
                if (button) {
                    button.click();
                    e.preventDefault();
                }
            }
        });
    })();
})();