上周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分就会重新进行游戏。
这一版本的脚本算法简单,基本逻辑是把大的数字放在右下方,但是并不是最优的
看github上有个使用alpha-beta depth first search来自动化游戏:https://github.com/ovolve/2048-AI/blob/master/js/ai.js
可以修改代码使用这个算法
村长大好人,直接贴出代码👍🏻👍🏻
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
最基础版的
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit