自动进行2048游戏

in hive-180932 •  3 days ago 

上周Succinct新加了2048的小游戏。这个小游戏大概是十年前最火的小游戏。玩法也简单,就是把2个相同数字的方块结合,变成相加的数字,最终获得2048就获胜了

我大概15-20分钟可以完成2048,获得20个星星。但是每天也没有很多时间来玩游戏,想着写个脚本自动进行游戏

没找到直接过关的漏洞,所以就想到利用console输入脚本来自动进行游戏

把需求给chatgpt,修改几处代码,就完成了初版的自动脚本:

(async () => {
    // Helper for random delays
    const randomDelay = async (min, max) => {
        return new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * (max - min + 1) + min)));
    };

    // Get grid state
    const getGrid = () => {
        try {
            const container = document.querySelector('#game-container > div > div.jsx-2883dceedf364fd2.absolute.left-0.top-0.h-full.w-full');
            if (!container) {
                console.log('Container not found');
                return null;
            }

            const grid = Array(16).fill(0);
            const tiles = Array.from(container.children).filter(tile => 
                tile.textContent && 
                !tile.classList.contains('tile-new')
            );

            tiles.forEach(tile => {
                const value = parseInt(tile.textContent);
                const style = window.getComputedStyle(tile);
                const topPercent = parseFloat(style.top.replace('px', ''));
                const leftPercent = parseFloat(style.left.replace('px', ''));
                
                const containerStyle = window.getComputedStyle(container);
                const containerHeight = parseFloat(containerStyle.height.replace('px', ''));
                const containerWidth = parseFloat(containerStyle.width.replace('px', ''));
                
                const row = Math.round((topPercent / containerHeight) * 4);
                const col = Math.round((leftPercent / containerWidth) * 4);
                
                if (row >= 0 && row < 4 && col >= 0 && col < 4) {
                    const index = row * 4 + col;
                    grid[index] = value;
                }
            });

            // Debug print grid
            let gridStr = '';
            for (let i = 0; i < 4; i++) {
                const row = grid.slice(i * 4, (i + 1) * 4).map(v => v.toString().padStart(4, ' '));
                gridStr += row.join(' | ') + '\n';
                if (i < 3) gridStr += '-'.repeat(29) + '\n';
            }
            console.log(gridStr);

            return grid;
        } catch (error) {
            console.error('Error reading grid:', error);
            return null;
        }
    };

    // Get current score
    const getScore = () => {
        try {
            const scoreElement = document.querySelector('body > div.fixed.inset-0.flex.flex-col.overflow-hidden.bg-cover.bg-center > main > div.pointer-events-none.absolute.inset-0.flex.px-4.py-1 > div > div > div.relative.flex.w-full.flex-1.flex-col.items-start.justify-center > div > div > div > div.jsx-2883dceedf364fd2.mb-4.flex.items-center.justify-between > div.jsx-2883dceedf364fd2.flex.space-x-2 > div:nth-child(1) > div.jsx-2883dceedf364fd2.text-xl.font-bold.text-succinct-pink');
            return scoreElement ? parseInt(scoreElement.textContent) : 0;
        } catch (error) {
            console.error('Error reading score:', error);
            return 0;
        }
    };

    // Press key with proper delays
    const pressKey = async (direction) => {
        console.log(`Moving: ${direction}`);
        const event = new KeyboardEvent('keydown', {
            key: direction,
            code: direction,
            keyCode: {
                'ArrowUp': 38,
                'ArrowDown': 40,
                'ArrowLeft': 37,
                'ArrowRight': 39
            }[direction],
            which: {
                'ArrowUp': 38,
                'ArrowDown': 40,
                'ArrowLeft': 37,
                'ArrowRight': 39
            }[direction],
            bubbles: true,
            cancelable: true
        });
        document.dispatchEvent(event);
        await randomDelay(150, 300);
    };

    // Check for game over
    const isGameOver = () => {
        const gameOverElement = document.querySelector('#game-container > div.jsx-2883dceedf364fd2.duration-400.absolute.inset-0.z-10.flex.flex-col.items-center.justify-center.rounded.bg-white\\/90.backdrop-blur-sm.transition-all > div.jsx-2883dceedf364fd2.mb-1.text-4xl.font-extrabold.text-succinct-pink');
        return gameOverElement && gameOverElement.textContent.includes('Game Over!');
    };

    // Handle game over actions
    const handleGameOver = async () => {
        const score = getScore();
        console.log(`Game Over! Final score: ${score}`);
        
        await randomDelay(1000, 2000); // Wait for animation

        try {
            if (score > 20000) {
                console.log('Score > 20000, clicking Confirm Score...');
                const confirmButton = document.querySelector('#game-container > div.jsx-2883dceedf364fd2.duration-400.absolute.inset-0.z-10.flex.flex-col.items-center.justify-center.rounded.bg-white\\/90.backdrop-blur-sm.transition-all > div:nth-child(3) > button > div > span');
                if (confirmButton) {
                    confirmButton.click();
                    await randomDelay(500, 1000);
                }
            } else {
                console.log('Score <= 20000, clicking Try Again...');
                const tryAgainButton = document.querySelector('#game-container > div.jsx-2883dceedf364fd2.duration-400.absolute.inset-0.z-10.flex.flex-col.items-center.justify-center.rounded.bg-white\\/90.backdrop-blur-sm.transition-all > div.jsx-2883dceedf364fd2.flex.flex-col.gap-3 > button.jsx-2883dceedf364fd2.rounded.bg-succinct-pink.px-6.py-2.font-bold.text-white.shadow-sm.transition-all.hover\\:bg-succinct-pink\\/80.active\\:scale-95');
                if (tryAgainButton) {
                    tryAgainButton.click();
                    await randomDelay(500, 1000);
                }
            }
        } catch (error) {
            console.error('Error handling game over:', error);
        }
    };

    // Simple pattern-based strategy
    class GameStrategy {
        constructor() {
            this.patterns = [
                ['ArrowRight', 'ArrowDown'],
                ['ArrowDown', 'ArrowRight'],
                ['ArrowRight', 'ArrowDown', 'ArrowRight'],
                ['ArrowDown', 'ArrowRight', 'ArrowDown']
            ];
            this.currentPattern = 0;
            this.patternIndex = 0;
            this.lastMoves = [];
            this.stuckCount = 0;
        }

        async executeMove(grid) {
            // Check if we're stuck
            if (this.isStuck(grid)) {
                this.stuckCount++;
                if (this.stuckCount >= 2) {
                    await this.performRecoverySequence();
                    return;
                }
            } else {
                this.stuckCount = 0;
            }

            // Get next move from pattern
            const move = this.getNextMove(grid);
            await pressKey(move);
            this.lastMoves.push(move);
            if (this.lastMoves.length > 5) this.lastMoves.shift();
        }

        isStuck(grid) {
            if (this.lastMoves.length < 2) return false;
            return this.lastMoves.slice(-2).every(move => 
                move === this.lastMoves[this.lastMoves.length - 1]
            );
        }

        async performRecoverySequence() {
            console.log('Performing recovery sequence...');
            const recoveryMoves = ['ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown'];
            
            for (const move of recoveryMoves) {
                await pressKey(move);
                await randomDelay(200, 400);
            }
            
            this.switchPattern();
            this.stuckCount = 0;
            this.lastMoves = [];
        }

        getNextMove(grid) {
            // Switch pattern if needed
            if (this.patternIndex >= this.patterns[this.currentPattern].length) {
                this.switchPattern();
            }

            // Get move from current pattern
            const move = this.patterns[this.currentPattern][this.patternIndex];
            this.patternIndex++;
            return move;
        }

        switchPattern() {
            this.currentPattern = (this.currentPattern + 1) % this.patterns.length;
            this.patternIndex = 0;
        }
    }

    // Main game loop
    console.log('Starting 2048 game with pattern-based strategy...');
    
    const strategy = new GameStrategy();
    let moveCount = 0;
    let highestTile = 0;
    let lastGridState = null;
    let sameStateCount = 0;
    let consecutiveGames = 0;

    while (true) {
        if (isGameOver()) {
            await handleGameOver();
            consecutiveGames++;
            
            // Reset game state
            moveCount = 0;
            highestTile = 0;
            lastGridState = null;
            sameStateCount = 0;
            strategy.stuckCount = 0;
            strategy.lastMoves = [];
            
            // Wait before starting new game
            await randomDelay(1500, 2500);
            continue;
        }

        const grid = getGrid();
        if (!grid) {
            await randomDelay(500, 1000);
            continue;
        }

        // Check for completely stuck state
        const currentGridStr = JSON.stringify(grid);
        if (currentGridStr === lastGridState) {
            sameStateCount++;
            if (sameStateCount >= 3) {
                console.log('Completely stuck, performing full reset...');
                await pressKey('ArrowUp');
                await randomDelay(200, 400);
                await pressKey('ArrowLeft');
                await randomDelay(200, 400);
                sameStateCount = 0;
                continue;
            }
        } else {
            sameStateCount = 0;
            lastGridState = currentGridStr;
        }

        // Track highest tile
        const currentHighest = Math.max(...grid);
        if (currentHighest > highestTile) {
            console.log(`New highest tile: ${currentHighest}`);
            highestTile = currentHighest;
        }

        // Execute next move
        await strategy.executeMove(grid);
        moveCount++;
        
        // Longer delay between moves
        await randomDelay(200, 400);
    }
})();

脚本会自动游戏,如果分数达不到20000分就会重新进行游戏。
image.png

这一版本的脚本算法简单,基本逻辑是把大的数字放在右下方,但是并不是最优的

看github上有个使用alpha-beta depth first search来自动化游戏:https://github.com/ovolve/2048-AI/blob/master/js/ai.js

可以修改代码使用这个算法

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  
  ·  2 days ago 

村长大好人,直接贴出代码👍🏻👍🏻

  ·  2 days ago 

最基础版的