// ==UserScript==
// @name 2048 Ultimate Cheat Suite
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Complete cheat suite for 2048 with auto-solver (corner/greedy/Monte Carlo), speed control, heatmap, and inspector. NOW WORKS with play2048.co!
// @author GameModder
// @match https://play2048.co/*
// @match https://*.play2048.co/*
// @grant none
// @license CC-BY-NC-4.0
// @run-at document-end
// ==/UserScript==
/*
This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License.
To view a copy of this license, visit https://creativecommons.org/licenses/by-nc/4.0/
You are free to share and adapt this code, but:
- You must give appropriate credit.
- You may not use this work for commercial purposes.
*/
(function() {
'use strict';
// Wait for page to load, then check for iframe or direct game
let checkInterval = setInterval(() => {
if (document.readyState === 'complete') {
clearInterval(checkInterval);
setTimeout(findGame, 500);
}
}, 100);
function findGame() {
// Debug: Log what we're finding
console.log('2048 Cheat: Looking for game... document.readyState =', document.readyState);
// Check for various possible container selectors
let gameContainer = document.querySelector('.game-container') ||
document.querySelector('[class*="game"]') ||
document.querySelector('.container');
if (gameContainer) {
console.log('2048 Cheat: Game container found in main window!', gameContainer.className);
// For play2048.co, use DOM-based approach since game manager isn't exposed
initCheat(null, window, document);
return;
}
// Also check for tiles directly (might indicate game is loaded)
let tiles = document.querySelector('[class*="tile-position"]');
if (tiles) {
console.log('2048 Cheat: Found tiles, game is loaded!');
initCheat(null, window, document);
return;
}
// If not found, search in iframes
console.log('2048 Cheat: Searching for game in iframes...');
let iframes = document.querySelectorAll('iframe');
console.log('2048 Cheat: Found ' + iframes.length + ' iframes');
for (let iframe of iframes) {
try {
let iframeWindow = iframe.contentWindow;
let iframeDoc = iframe.contentDocument || iframeWindow.document;
// Check if iframe has game container
let hasGameContainer = iframeDoc.querySelector('.game-container') ||
iframeDoc.querySelector('[class*="game"]');
if (hasGameContainer) {
console.log('2048 Cheat: Game found in iframe!', iframe, hasGameContainer.className);
initCheat(null, iframeWindow, iframeDoc);
return;
}
} catch (e) {
// Cross-origin iframe, skip it
console.log('2048 Cheat: Skipping cross-origin iframe');
continue;
}
}
// Retry after a delay if game not found yet
let attempts = (findGame.attempts || 0) + 1;
findGame.attempts = attempts;
if (attempts < 10) {
console.log('2048 Cheat: Game not found, retrying... (attempt ' + attempts + '/10)');
setTimeout(findGame, 1000);
} else {
console.log('2048 Cheat: Failed to find game after 10 attempts');
console.log('2048 Cheat: Debug - All elements with "game" in class:', document.querySelectorAll('[class*="game"]'));
console.log('2048 Cheat: Debug - All divs:', document.querySelectorAll('div'));
}
}
function initCheat(originalGameManager, gameWindow, gameDocument) {
// Check if we have a game manager or need to use DOM-based approach
let useDOMMode = !originalGameManager;
if (useDOMMode) {
console.log('2048 Cheat: Using DOM-based control mode');
} else {
console.log('2048 Cheat: Using game manager mode');
}
console.log('2048 Cheat: Initializing with game manager', originalGameManager);
let moveHistory = [];
let currentHistoryIndex = -1;
let autoSolverInterval = null;
let currentStrategy = 'corner';
let gameSpeed = 1;
let originalAddRandomTile = null;
let forcedTileValue = null; // 2, 4, or null for random
let forcedTilePosition = null; // {x, y} or null for random
let scoreMultiplier = 1;
let lockedTile = null;
// DOM-based control functions for play2048.co
function simulateKeyPress(keyCode) {
let key, code;
switch(keyCode) {
case 0: key = 'ArrowLeft'; code = 'ArrowLeft'; break; // Left
case 1: key = 'ArrowDown'; code = 'ArrowDown'; break; // Down
case 2: key = 'ArrowRight'; code = 'ArrowRight'; break; // Right
case 3: key = 'ArrowUp'; code = 'ArrowUp'; break; // Up
}
let event = new KeyboardEvent('keydown', {
key: key,
code: code,
keyCode: keyCode === 0 ? 37 : keyCode === 1 ? 40 : keyCode === 2 ? 39 : 38,
which: keyCode === 0 ? 37 : keyCode === 1 ? 40 : keyCode === 2 ? 39 : 38,
bubbles: true,
cancelable: true
});
gameDocument.dispatchEvent(event);
}
function readBoardFromDOM() {
let board = Array(4).fill(0).map(() => Array(4).fill(0));
for (let row = 1; row <= 4; row++) {
for (let col = 1; col <= 4; col++) {
let className = 'tile-position-' + col + '-' + row;
let tiles = gameDocument.getElementsByClassName(className);
if (tiles.length > 0) {
let tile = tiles[tiles.length - 1];
let valueElement = tile.querySelector('.tile-inner');
if (valueElement) {
let value = parseInt(valueElement.textContent);
if (!isNaN(value)) {
board[row - 1][col - 1] = value;
}
}
}
}
}
return board;
}
function isGameOver() {
return gameDocument.querySelector('.game-over') !== null;
}
function isGameWon() {
return gameDocument.querySelector('.game-won') !== null;
}
function getCurrentScore() {
let scoreContainer = gameDocument.querySelector('.score-container');
if (scoreContainer) {
let scoreText = scoreContainer.textContent.trim();
let match = scoreText.match(/\d+/);
return match ? parseInt(match[0]) : 0;
}
return 0;
}
// Intercept game manager methods
function interceptGameManager() {
if (!originalGameManager) return;
// Store original methods
originalAddRandomTile = originalGameManager.addRandomTile.bind(originalGameManager);
// Override addRandomTile for spawner control
originalGameManager.addRandomTile = function() {
if (forcedTileValue || forcedTilePosition) {
let availableCells = originalGameManager.grid.availableCells();
if (availableCells.length > 0) {
let position = forcedTilePosition && availableCells.some(c => c.x === forcedTilePosition.x && c.y === forcedTilePosition.y)
? forcedTilePosition
: availableCells[Math.floor(Math.random() * availableCells.length)];
let value = forcedTileValue || (Math.random() < 0.9 ? 2 : 4);
let TileConstructor = gameWindow.Tile || Tile;
let tile = new TileConstructor(position, value);
originalGameManager.grid.insertTile(tile);
}
} else {
originalAddRandomTile();
}
};
// Override move to record history
let originalMove = originalGameManager.move.bind(originalGameManager);
originalGameManager.move = function(direction) {
// Save state before move
if (currentHistoryIndex === moveHistory.length - 1) {
moveHistory.push(captureGameState());
currentHistoryIndex++;
}
originalMove(direction);
// Apply score multiplier
if (scoreMultiplier !== 1) {
originalGameManager.score = Math.floor(originalGameManager.score * scoreMultiplier);
originalGameManager.actuator.updateScore(originalGameManager.score);
}
};
}
// Capture current game state
function captureGameState() {
return {
grid: serializeGrid(originalGameManager.grid),
score: originalGameManager.score,
over: originalGameManager.over,
won: originalGameManager.won
};
}
// Serialize grid to JSON
function serializeGrid(grid) {
let cells = [];
grid.cells.forEach(column => {
let columnData = [];
column.forEach(cell => {
columnData.push(cell ? { position: cell.position, value: cell.value } : null);
});
cells.push(columnData);
});
return { size: grid.size, cells: cells };
}
// Restore game state
function restoreGameState(state) {
originalGameManager.grid.cells = [];
for (let x = 0; x < state.grid.size; x++) {
let column = [];
for (let y = 0; y < state.grid.size; y++) {
let cellData = state.grid.cells[x][y];
let TileConstructor = gameWindow.Tile || (typeof Tile !== 'undefined' ? Tile : null);
column.push(cellData && TileConstructor ? new TileConstructor(cellData.position, cellData.value) : null);
}
originalGameManager.grid.cells.push(column);
}
originalGameManager.score = state.score;
originalGameManager.over = state.over;
originalGameManager.won = state.won;
originalGameManager.actuate();
}
// Helper: Check if move is possible (DOM mode)
function canMoveDOM(board, direction) {
// Simplified check - just see if there are any matching adjacent tiles or empty spaces
for (let y = 0; y < 4; y++) {
for (let x = 0; x < 4; x++) {
if (board[y][x] === 0) return true; // Has empty space
}
}
// Check for matching adjacent tiles
for (let y = 0; y < 4; y++) {
for (let x = 0; x < 3; x++) {
if (board[y][x] !== 0 && board[y][x] === board[y][x+1]) return true;
}
}
for (let x = 0; x < 4; x++) {
for (let y = 0; y < 3; y++) {
if (board[y][x] !== 0 && board[y][x] === board[y+1][x]) return true;
}
}
return false;
}
// Auto-solver strategies
const Strategies = {
// Greedy: Choose move with best immediate score gain
greedy: function() {
if (useDOMMode) {
// In DOM mode, use corner strategy as fallback
return Strategies.corner();
}
let bestMove = null;
let bestScore = -1;
[0, 1, 2, 3].forEach(direction => {
let clonedManager = cloneGameManager();
let initialScore = clonedManager.score;
clonedManager.move(direction);
let scoreGain = clonedManager.score - initialScore;
if (scoreGain > bestScore) {
bestScore = scoreGain;
bestMove = direction;
}
});
return bestMove !== null ? bestMove : 0;
},
// Corner strategy: Keep highest tile in corner
corner: function() {
// Prefer moves that keep tiles in bottom-right corner
// Priority: Down > Right > Left > Up
let movePreference = [1, 2, 0, 3]; // down, right, left, up
if (useDOMMode) {
// Simple heuristic: try moves in order
for (let direction of movePreference) {
return direction; // Just try in order
}
return 0;
} else {
for (let direction of movePreference) {
if (canMove(direction)) {
return direction;
}
}
return 0; // Fallback
}
},
// Monte Carlo: Simulate random games and pick best average outcome
monteCarlo: function() {
if (useDOMMode) {
// In DOM mode, use corner strategy as fallback
return Strategies.corner();
}
let simulations = 50; // Number of simulations per move
let scores = [[], [], [], []];
[0, 1, 2, 3].forEach(direction => {
if (!canMove(direction)) return;
for (let i = 0; i < simulations; i++) {
let clonedManager = cloneGameManager();
clonedManager.move(direction);
// Random playout
let moves = 0;
while (!clonedManager.over && moves < 10) {
let randomDir = Math.floor(Math.random() * 4);
if (canMoveOnGrid(clonedManager.grid, randomDir)) {
clonedManager.move(randomDir);
moves++;
}
}
scores[direction].push(clonedManager.score);
}
});
// Find direction with best average score
let bestMove = 0;
let bestAvg = -1;
scores.forEach((dirScores, dir) => {
if (dirScores.length > 0) {
let avg = dirScores.reduce((a, b) => a + b, 0) / dirScores.length;
if (avg > bestAvg) {
bestAvg = avg;
bestMove = dir;
}
}
});
return bestMove;
}
};
// Helper: Check if move is possible
function canMove(direction) {
if (useDOMMode) {
return canMoveDOM(getBoardArray(), direction);
}
return canMoveOnGrid(originalGameManager.grid, direction);
}
function canMoveOnGrid(grid, direction) {
let vector = originalGameManager.getVector(direction);
let moved = false;
grid.eachCell((x, y, tile) => {
if (tile) {
let positions = originalGameManager.findFarthestPosition({ x, y }, vector);
let next = grid.cellContent(positions.next);
if (next && next.value === tile.value) {
moved = true;
} else if (positions.farthest.x !== x || positions.farthest.y !== y) {
moved = true;
}
}
});
return moved;
}
// Clone game manager for simulation
function cloneGameManager() {
let clone = Object.create(Object.getPrototypeOf(originalGameManager));
clone.size = originalGameManager.size;
clone.startTiles = originalGameManager.startTiles;
clone.score = originalGameManager.score;
clone.over = originalGameManager.over;
clone.won = originalGameManager.won;
clone.keepPlaying = originalGameManager.keepPlaying;
// Clone grid
clone.grid = { size: originalGameManager.grid.size, cells: [] };
originalGameManager.grid.cells.forEach((column, x) => {
clone.grid.cells[x] = [];
column.forEach((cell, y) => {
clone.grid.cells[x][y] = cell ? { position: cell.position, value: cell.value } : null;
});
});
// Copy methods
clone.getVector = originalGameManager.getVector.bind(clone);
clone.findFarthestPosition = originalGameManager.findFarthestPosition.bind(clone);
clone.movesAvailable = originalGameManager.movesAvailable.bind(clone);
clone.move = function(direction) {
let vector = this.getVector(direction);
let traversals = originalGameManager.buildTraversals.call(this, vector);
let moved = false;
traversals.x.forEach(x => {
traversals.y.forEach(y => {
let tile = this.grid.cells[x][y];
if (tile) {
let positions = this.findFarthestPosition({ x, y }, vector);
let next = this.grid.cells[positions.next.x] ? this.grid.cells[positions.next.x][positions.next.y] : null;
if (next && next.value === tile.value) {
this.grid.cells[positions.next.x][positions.next.y] = { position: positions.next, value: tile.value * 2 };
this.grid.cells[x][y] = null;
this.score += tile.value * 2;
moved = true;
} else if (positions.farthest.x !== x || positions.farthest.y !== y) {
this.grid.cells[positions.farthest.x][positions.farthest.y] = tile;
this.grid.cells[x][y] = null;
moved = true;
}
}
});
});
return moved;
};
return clone;
}
// Auto-solver loop
function runAutoSolver() {
if (useDOMMode) {
if (!isGameOver()) {
let move = Strategies[currentStrategy]();
simulateKeyPress(move);
}
} else {
if (!originalGameManager.over) {
let move = Strategies[currentStrategy]();
originalGameManager.inputManager.emit('move', move);
}
}
}
// Board editor: Set grid to specific layout
function setBoard(layout) {
if (useDOMMode) {
showNotification('Board editor not available in DOM mode');
return;
}
originalGameManager.grid.cells = [];
for (let x = 0; x < 4; x++) {
let column = [];
for (let y = 0; y < 4; y++) {
let value = layout[y][x];
let TileConstructor = gameWindow.Tile || (typeof Tile !== 'undefined' ? Tile : null);
column.push(value && TileConstructor ? new TileConstructor({ x, y }, value) : null);
}
originalGameManager.grid.cells.push(column);
}
originalGameManager.actuate();
showNotification('Board updated!');
}
// Get current board as 2D array
function getBoardArray() {
if (useDOMMode) {
return readBoardFromDOM();
}
let board = [];
for (let y = 0; y < 4; y++) {
board[y] = [];
for (let x = 0; x < 4; x++) {
let tile = originalGameManager.grid.cells[x][y];
board[y][x] = tile ? tile.value : 0;
}
}
return board;
}
// Calculate heatmap (which tiles are blocking merges)
function calculateHeatmap() {
let heatmap = Array(4).fill(0).map(() => Array(4).fill(0));
let board = getBoardArray();
// Check horizontal adjacency
for (let y = 0; y < 4; y++) {
for (let x = 0; x < 3; x++) {
if (board[y][x] !== 0 && board[y][x+1] !== 0 && board[y][x] !== board[y][x+1]) {
heatmap[y][x]++;
heatmap[y][x+1]++;
}
}
}
// Check vertical adjacency
for (let x = 0; x < 4; x++) {
for (let y = 0; y < 3; y++) {
if (board[y][x] !== 0 && board[y+1][x] !== 0 && board[y][x] !== board[y+1][x]) {
heatmap[y][x]++;
heatmap[y+1][x]++;
}
}
}
return heatmap;
}
// Show notification
function showNotification(message) {
let notif = document.createElement('div');
notif.className = 'cheat-notification';
notif.textContent = message;
document.body.appendChild(notif);
setTimeout(() => notif.classList.add('show'), 10);
setTimeout(() => {
notif.classList.remove('show');
setTimeout(() => notif.remove(), 300);
}, 2000);
}
// Create GUI - always in main document, not iframe
function createGUI() {
let gui = document.createElement('div');
gui.id = 'cheat-gui';
gui.innerHTML = `
<div class="cheat-header">
<span>🎮 2048 Ultimate Cheat</span>
<button id="cheat-minimize">−</button>
</div>
<div class="cheat-content">
<!-- Auto-Solver Section -->
<div class="cheat-section">
<div class="cheat-section-header" data-section="autosolver">
<span>▼ 🤖 Auto-Solver</span>
</div>
<div class="cheat-section-content" id="section-autosolver">
<div class="cheat-row">
<label>Strategy:</label>
<select id="strategy-select">
<option value="corner" selected>Corner Strategy</option>
<option value="greedy">Greedy (Best Score)</option>
<option value="monteCarlo">Monte Carlo</option>
</select>
</div>
<div class="cheat-row">
<label>Speed (moves/sec):</label>
<input type="range" id="solver-speed" min="1" max="10" value="2">
<span id="speed-value">2</span>
</div>
<button id="start-solver" class="cheat-btn">▶ Start Auto-Solver</button>
<button id="stop-solver" class="cheat-btn" style="display:none;">⏸ Stop Auto-Solver</button>
<button id="step-once" class="cheat-btn">⏭ Step Once</button>
</div>
</div>
<!-- Tile Spawner Control Section -->
<div class="cheat-section">
<div class="cheat-section-header" data-section="spawner">
<span>▶ 🎲 Tile Spawner Control</span>
</div>
<div class="cheat-section-content" id="section-spawner" style="display:none;">
<div class="cheat-row">
<label>Force next tile value:</label>
<select id="force-tile-value">
<option value="random">Random (90% 2, 10% 4)</option>
<option value="2">Always 2</option>
<option value="4">Always 4</option>
</select>
</div>
<div class="cheat-row">
<label>Force position:</label>
<button id="pick-position" class="cheat-btn">📍 Click Grid to Pick</button>
<span id="position-display">Random</span>
</div>
<button id="clear-force" class="cheat-btn">🔄 Reset to Random</button>
</div>
</div>
<!-- Board Editor Section -->
<div class="cheat-section">
<div class="cheat-section-header" data-section="editor">
<span>▶ 🏗 Board Editor</span>
</div>
<div class="cheat-section-content" id="section-editor" style="display:none;">
<div class="board-editor" id="board-editor"></div>
<div class="cheat-row">
<button id="apply-board" class="cheat-btn">✅ Apply to Game</button>
<button id="clear-board" class="cheat-btn">🗑 Clear Board</button>
</div>
<div class="cheat-row">
<button id="load-current" class="cheat-btn">📥 Load Current</button>
<button id="preset-corner" class="cheat-btn">🎯 Corner Setup</button>
</div>
</div>
</div>
<!-- History & Replay Section -->
<div class="cheat-section">
<div class="cheat-section-header" data-section="history">
<span>▶ ⏮ Undo / Replay</span>
</div>
<div class="cheat-section-content" id="section-history" style="display:none;">
<div class="cheat-row">
<button id="undo-move" class="cheat-btn">⬅ Undo</button>
<button id="redo-move" class="cheat-btn">➡ Redo</button>
</div>
<div class="cheat-row">
<span id="history-info">History: 0 moves</span>
</div>
<button id="clear-history" class="cheat-btn">🗑 Clear History</button>
</div>
</div>
<!-- State Inspector Section -->
<div class="cheat-section">
<div class="cheat-section-header" data-section="inspector">
<span>▶ 🔍 State Inspector</span>
</div>
<div class="cheat-section-content" id="section-inspector" style="display:none;">
<button id="show-heatmap" class="cheat-btn">🔥 Show Heatmap</button>
<button id="hide-heatmap" class="cheat-btn" style="display:none;">❌ Hide Heatmap</button>
<div id="heatmap-display"></div>
<div class="cheat-row">
<label>Empty Cells:</label>
<span id="empty-cells">0</span>
</div>
<div class="cheat-row">
<label>Highest Tile:</label>
<span id="highest-tile">0</span>
</div>
<div class="cheat-row">
<label>Mergeable Pairs:</label>
<span id="mergeable-pairs">0</span>
</div>
</div>
</div>
<!-- Experimental Section -->
<div class="cheat-section">
<div class="cheat-section-header" data-section="experimental">
<span>▶ ⚗ Experimental</span>
</div>
<div class="cheat-section-content" id="section-experimental" style="display:none;">
<div class="cheat-row">
<label>Score Multiplier:</label>
<input type="number" id="score-multiplier" min="1" max="10" value="1" step="0.5">
</div>
<div class="cheat-row">
<label>Set Score:</label>
<input type="number" id="set-score" min="0" step="100" value="0">
<button id="apply-score" class="cheat-btn">Set</button>
</div>
<button id="lock-highest" class="cheat-btn">🔒 Lock Highest Tile</button>
<button id="unlock-highest" class="cheat-btn" style="display:none;">🔓 Unlock Highest</button>
</div>
</div>
</div>
`;
document.body.appendChild(gui);
// Add styles
let style = document.createElement('style');
style.textContent = `
#cheat-gui {
position: fixed;
top: 20px;
right: 20px;
width: 320px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
font-family: 'Clear Sans', 'Helvetica Neue', Arial, sans-serif;
z-index: 999999;
color: white;
}
.cheat-header {
padding: 15px;
font-weight: bold;
font-size: 16px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
border-bottom: 2px solid rgba(255,255,255,0.2);
}
#cheat-minimize {
background: rgba(255,255,255,0.2);
border: none;
color: white;
width: 24px;
height: 24px;
border-radius: 4px;
cursor: pointer;
font-size: 18px;
line-height: 18px;
}
#cheat-minimize:hover {
background: rgba(255,255,255,0.3);
}
.cheat-content {
max-height: 600px;
overflow-y: auto;
}
.cheat-section {
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.cheat-section-header {
padding: 12px 15px;
cursor: pointer;
user-select: none;
transition: background 0.2s;
}
.cheat-section-header:hover {
background: rgba(255,255,255,0.1);
}
.cheat-section-content {
padding: 10px 15px;
background: rgba(0,0,0,0.1);
}
.cheat-row {
margin: 10px 0;
display: flex;
align-items: center;
gap: 10px;
}
.cheat-row label {
flex: 1;
font-size: 13px;
}
.cheat-btn {
background: rgba(255,255,255,0.2);
border: none;
color: white;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
width: 100%;
margin: 5px 0;
}
.cheat-btn:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-1px);
}
.cheat-btn:active {
transform: translateY(0);
}
input[type="range"] {
flex: 1;
}
input, select {
background: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.3);
color: white;
padding: 6px 10px;
border-radius: 4px;
font-size: 13px;
}
select option {
background: #667eea;
color: white;
}
.board-editor {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 5px;
margin: 10px 0;
}
.board-cell {
aspect-ratio: 1;
background: rgba(255,255,255,0.2);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 14px;
font-weight: bold;
}
.board-cell:hover {
background: rgba(255,255,255,0.3);
}
.cheat-notification {
position: fixed;
bottom: 20px;
right: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px 25px;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
transform: translateX(400px);
transition: transform 0.3s ease;
z-index: 1000000;
font-family: 'Clear Sans', Arial, sans-serif;
}
.cheat-notification.show {
transform: translateX(0);
}
#heatmap-display {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 2px;
margin: 10px 0;
}
.heatmap-cell {
aspect-ratio: 1;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: bold;
}
`;
document.head.appendChild(style);
// Make draggable
makeDraggable(gui);
// Set up event listeners
setupEventListeners();
}
// Make element draggable
function makeDraggable(element) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
let header = element.querySelector('.cheat-header');
header.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
element.style.top = (element.offsetTop - pos2) + "px";
element.style.left = (element.offsetLeft - pos1) + "px";
element.style.right = 'auto';
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
}
}
// Set up all event listeners
function setupEventListeners() {
// Collapsible sections
document.querySelectorAll('.cheat-section-header').forEach(header => {
header.addEventListener('click', function() {
let section = this.getAttribute('data-section');
let content = document.getElementById('section-' + section);
let arrow = this.querySelector('span');
if (content.style.display === 'none') {
content.style.display = 'block';
arrow.textContent = arrow.textContent.replace('▶', '▼');
} else {
content.style.display = 'none';
arrow.textContent = arrow.textContent.replace('▼', '▶');
}
});
});
// Minimize button
document.getElementById('cheat-minimize').addEventListener('click', function() {
let content = document.querySelector('.cheat-content');
if (content.style.display === 'none') {
content.style.display = 'block';
this.textContent = '−';
} else {
content.style.display = 'none';
this.textContent = '+';
}
});
// Auto-solver controls
document.getElementById('strategy-select').addEventListener('change', function() {
currentStrategy = this.value;
showNotification('Strategy: ' + this.options[this.selectedIndex].text);
});
document.getElementById('solver-speed').addEventListener('input', function() {
gameSpeed = parseInt(this.value);
document.getElementById('speed-value').textContent = gameSpeed;
if (autoSolverInterval) {
clearInterval(autoSolverInterval);
autoSolverInterval = setInterval(runAutoSolver, 1000 / gameSpeed);
}
});
document.getElementById('start-solver').addEventListener('click', function() {
if (!autoSolverInterval) {
autoSolverInterval = setInterval(runAutoSolver, 1000 / gameSpeed);
this.style.display = 'none';
document.getElementById('stop-solver').style.display = 'block';
showNotification('Auto-solver started with ' + currentStrategy + ' strategy');
}
});
document.getElementById('stop-solver').addEventListener('click', function() {
if (autoSolverInterval) {
clearInterval(autoSolverInterval);
autoSolverInterval = null;
this.style.display = 'none';
document.getElementById('start-solver').style.display = 'block';
showNotification('Auto-solver stopped');
}
});
document.getElementById('step-once').addEventListener('click', function() {
runAutoSolver();
showNotification('Executed one move');
});
// Tile spawner controls
document.getElementById('force-tile-value').addEventListener('change', function() {
forcedTileValue = this.value === 'random' ? null : parseInt(this.value);
showNotification('Next tiles: ' + (forcedTileValue || 'Random'));
});
document.getElementById('pick-position').addEventListener('click', function() {
showNotification('Click on any tile to set spawn position');
let tiles = document.querySelectorAll('.tile-container .tile, .grid-container .grid-cell');
let clickHandler = function(e) {
// Calculate grid position from click
let rect = document.querySelector('.grid-container').getBoundingClientRect();
let x = Math.floor((e.clientX - rect.left) / (rect.width / 4));
let y = Math.floor((e.clientY - rect.top) / (rect.height / 4));
if (x >= 0 && x < 4 && y >= 0 && y < 4) {
forcedTilePosition = { x, y };
document.getElementById('position-display').textContent = `(${x}, ${y})`;
showNotification(`Position set to (${x}, ${y})`);
}
tiles.forEach(t => t.removeEventListener('click', clickHandler));
};
tiles.forEach(t => t.addEventListener('click', clickHandler));
});
document.getElementById('clear-force').addEventListener('click', function() {
forcedTileValue = null;
forcedTilePosition = null;
document.getElementById('force-tile-value').value = 'random';
document.getElementById('position-display').textContent = 'Random';
showNotification('Reset to random spawning');
});
// Board editor
createBoardEditor();
document.getElementById('apply-board').addEventListener('click', function() {
let board = getBoardFromEditor();
setBoard(board);
});
document.getElementById('clear-board').addEventListener('click', function() {
let board = Array(4).fill(0).map(() => Array(4).fill(0));
updateBoardEditor(board);
});
document.getElementById('load-current').addEventListener('click', function() {
let board = getBoardArray();
updateBoardEditor(board);
showNotification('Loaded current board');
});
document.getElementById('preset-corner').addEventListener('click', function() {
let board = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 2],
[128, 64, 32, 16]
];
updateBoardEditor(board);
showNotification('Loaded corner strategy preset');
});
// History controls
document.getElementById('undo-move').addEventListener('click', function() {
if (currentHistoryIndex > 0) {
currentHistoryIndex--;
restoreGameState(moveHistory[currentHistoryIndex]);
updateHistoryInfo();
showNotification('Undo move');
}
});
document.getElementById('redo-move').addEventListener('click', function() {
if (currentHistoryIndex < moveHistory.length - 1) {
currentHistoryIndex++;
restoreGameState(moveHistory[currentHistoryIndex]);
updateHistoryInfo();
showNotification('Redo move');
}
});
document.getElementById('clear-history').addEventListener('click', function() {
moveHistory = [captureGameState()];
currentHistoryIndex = 0;
updateHistoryInfo();
showNotification('History cleared');
});
// Inspector controls
let heatmapInterval = null;
document.getElementById('show-heatmap').addEventListener('click', function() {
this.style.display = 'none';
document.getElementById('hide-heatmap').style.display = 'block';
heatmapInterval = setInterval(updateInspector, 500);
updateInspector();
});
document.getElementById('hide-heatmap').addEventListener('click', function() {
this.style.display = 'none';
document.getElementById('show-heatmap').style.display = 'block';
if (heatmapInterval) {
clearInterval(heatmapInterval);
heatmapInterval = null;
}
document.getElementById('heatmap-display').innerHTML = '';
});
// Experimental controls
document.getElementById('score-multiplier').addEventListener('change', function() {
scoreMultiplier = parseFloat(this.value);
showNotification('Score multiplier: ' + scoreMultiplier + 'x');
});
document.getElementById('apply-score').addEventListener('click', function() {
let score = parseInt(document.getElementById('set-score').value);
originalGameManager.score = score;
originalGameManager.actuator.updateScore(score);
showNotification('Score set to ' + score);
});
document.getElementById('lock-highest').addEventListener('click', function() {
// Find highest tile
let maxValue = 0;
let maxPos = null;
originalGameManager.grid.eachCell((x, y, tile) => {
if (tile && tile.value > maxValue) {
maxValue = tile.value;
maxPos = { x, y };
}
});
if (maxPos) {
lockedTile = maxPos;
this.style.display = 'none';
document.getElementById('unlock-highest').style.display = 'block';
showNotification('Locked tile ' + maxValue + ' at (' + maxPos.x + ', ' + maxPos.y + ')');
}
});
document.getElementById('unlock-highest').addEventListener('click', function() {
lockedTile = null;
this.style.display = 'none';
document.getElementById('lock-highest').style.display = 'block';
showNotification('Tile unlocked');
});
}
// Create board editor grid
function createBoardEditor() {
let editor = document.getElementById('board-editor');
let editorData = Array(4).fill(0).map(() => Array(4).fill(0));
for (let y = 0; y < 4; y++) {
for (let x = 0; x < 4; x++) {
let cell = document.createElement('div');
cell.className = 'board-cell';
cell.dataset.x = x;
cell.dataset.y = y;
cell.textContent = '';
cell.addEventListener('click', function() {
let currentValue = editorData[y][x];
let values = [0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192];
let nextIndex = (values.indexOf(currentValue) + 1) % values.length;
editorData[y][x] = values[nextIndex];
this.textContent = editorData[y][x] || '';
this.style.background = getTileColor(editorData[y][x]);
});
editor.appendChild(cell);
}
}
window.editorData = editorData;
}
function updateBoardEditor(board) {
window.editorData = board;
let cells = document.querySelectorAll('.board-cell');
let index = 0;
for (let y = 0; y < 4; y++) {
for (let x = 0; x < 4; x++) {
cells[index].textContent = board[y][x] || '';
cells[index].style.background = getTileColor(board[y][x]);
index++;
}
}
}
function getBoardFromEditor() {
return window.editorData;
}
function getTileColor(value) {
let colors = {
0: 'rgba(255,255,255,0.2)',
2: '#eee4da',
4: '#ede0c8',
8: '#f2b179',
16: '#f59563',
32: '#f67c5f',
64: '#f65e3b',
128: '#edcf72',
256: '#edcc61',
512: '#edc850',
1024: '#edc53f',
2048: '#edc22e',
4096: '#3c3a32',
8192: '#3c3a32'
};
return colors[value] || '#3c3a32';
}
function updateHistoryInfo() {
document.getElementById('history-info').textContent =
`History: ${currentHistoryIndex}/${moveHistory.length - 1} moves`;
}
function updateInspector() {
// Calculate stats
let emptyCells = 0;
let highestTile = 0;
let mergeablePairs = 0;
let board = getBoardArray();
// Count empty cells and find highest tile
for (let y = 0; y < 4; y++) {
for (let x = 0; x < 4; x++) {
if (board[y][x] === 0) emptyCells++;
if (board[y][x] > highestTile) highestTile = board[y][x];
}
}
// Count mergeable pairs
for (let y = 0; y < 4; y++) {
for (let x = 0; x < 3; x++) {
if (board[y][x] !== 0 && board[y][x] === board[y][x+1]) mergeablePairs++;
}
}
for (let x = 0; x < 4; x++) {
for (let y = 0; y < 3; y++) {
if (board[y][x] !== 0 && board[y][x] === board[y+1][x]) mergeablePairs++;
}
}
document.getElementById('empty-cells').textContent = emptyCells;
document.getElementById('highest-tile').textContent = highestTile;
document.getElementById('mergeable-pairs').textContent = mergeablePairs;
// Display heatmap
let heatmap = calculateHeatmap();
let heatmapDisplay = document.getElementById('heatmap-display');
heatmapDisplay.innerHTML = '';
for (let y = 0; y < 4; y++) {
for (let x = 0; x < 4; x++) {
let cell = document.createElement('div');
cell.className = 'heatmap-cell';
cell.textContent = board[y][x] || '';
let heat = heatmap[y][x];
let alpha = Math.min(heat / 4, 1);
cell.style.background = `rgba(255, 0, 0, ${alpha * 0.5})`;
heatmapDisplay.appendChild(cell);
}
}
}
// Initialize
if (!useDOMMode) {
interceptGameManager();
moveHistory.push(captureGameState());
}
createGUI();
console.log('🎮 2048 Ultimate Cheat Suite loaded!');
console.log('Mode: ' + (useDOMMode ? 'DOM-based (play2048.co)' : 'Game Manager'));
console.log('Features: Auto-solver' + (useDOMMode ? '' : ', Tile control, Board editor, Undo/Redo') + ', Inspector, and more!');
showNotification('2048 Cheat Suite Loaded! 🎮');
}
})();