2048 Classic
by PhantomGamer18827 lines24.3 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>2048 Classic</title>
<style>
/* CSS Variables - Premium 2048 Palette */
:root {
--bg-color: #1a1a2e;
--grid-bg: #16213e;
--cell-bg: #0f3460;
--text-dark: #e8e8e8;
--text-light: #ffffff;
/* Tile Colors - Gradient Rich Palette */
--tile-2: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--tile-4: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
--tile-8: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
--tile-16: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
--tile-32: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
--tile-64: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%);
--tile-128: linear-gradient(135deg, #fccb90 0%, #d57eeb 100%);
--tile-256: linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%);
--tile-512: linear-gradient(135deg, #fbc2eb 0%, #a6c1ee 100%);
--tile-1024: linear-gradient(135deg, #f6d365 0%, #fda085 100%);
--tile-2048: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
--tile-super: linear-gradient(135deg, #0c0c1d 0%, #2d2d44 100%);
--grid-size: 4;
--cell-size: 80px;
--cell-gap: 12px;
--border-radius: 12px;
}
/* Reset & Base */
* { margin: 0; padding: 0; box-sizing: border-box; }
body::before {
content: '';
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background:
radial-gradient(circle at 20% 80%, rgba(120, 100, 255, 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255, 100, 200, 0.1) 0%, transparent 50%);
pointer-events: none;
z-index: 0;
}
.container { max-width: 400px; width: 100%; position: relative; z-index: 1; }
/* Header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.title {
font-size: 52px;
font-weight: 900;
background: linear-gradient(135deg, #f6d365 0%, #fda085 50%, #ff6b6b 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
filter: drop-shadow(0 2px 10px rgba(253, 160, 133, 0.4));
letter-spacing: 2px;
}
.scores { display: flex; gap: 10px; }
.score-box {
background: linear-gradient(135deg, #1a1a3e 0%, #2d2b55 100%);
padding: 8px 20px;
border-radius: 12px;
text-align: center;
min-width: 80px;
border: 1px solid rgba(255,255,255,0.1);
box-shadow: 0 4px 15px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.1);
}
.score-label {
display: block;
font-size: 10px;
font-weight: 700;
color: #a0a0c0;
text-transform: uppercase;
letter-spacing: 1.5px;
}
.score-value {
display: block;
font-size: 22px;
font-weight: 800;
color: #fff;
text-shadow: 0 0 10px rgba(253, 160, 133, 0.5);
}
/* Controls */
.controls { margin-bottom: 20px; }
.btn-new-game {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
border: none;
padding: 12px 28px;
font-size: 15px;
font-weight: 700;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
letter-spacing: 1px;
}
.btn-new-game:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px rgba(102, 126, 234, 0.6);
}
.btn-new-game:active { transform: scale(0.97); }
/* Game Container */
.game-container {
position: relative;
background: linear-gradient(135deg, #1a1a3e 0%, #16213e 100%);
border-radius: 16px;
padding: var(--cell-gap);
margin-bottom: 20px;
border: 1px solid rgba(255,255,255,0.08);
box-shadow:
0 10px 40px rgba(0,0,0,0.5),
inset 0 1px 0 rgba(255,255,255,0.1);
}
.grid-background {
display: grid;
grid-template-columns: repeat(var(--grid-size), var(--cell-size));
grid-template-rows: repeat(var(--grid-size), var(--cell-size));
gap: var(--cell-gap);
}
.cell {
background: rgba(15, 52, 96, 0.6);
border-radius: var(--border-radius);
border: 1px solid rgba(255,255,255,0.05);
}
.tiles-container {
position: absolute;
top: var(--cell-gap);
left: var(--cell-gap);
width: calc(var(--grid-size) * var(--cell-size) + (var(--grid-size) - 1) * var(--cell-gap));
height: calc(var(--grid-size) * var(--cell-size) + (var(--grid-size) - 1) * var(--cell-gap));
}
/* Tiles */
.tile {
position: absolute;
width: var(--cell-size);
height: var(--cell-size);
border-radius: var(--border-radius);
display: flex;
justify-content: center;
align-items: center;
font-size: 36px;
font-weight: 800;
transition: top 0.15s ease, left 0.15s ease;
box-shadow: 0 4px 15px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.2);
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.tile-2 { background: var(--tile-2); color: #fff; }
.tile-4 { background: var(--tile-4); color: #fff; }
.tile-8 { background: var(--tile-8); color: #fff; }
.tile-16 { background: var(--tile-16); color: #fff; }
.tile-32 { background: var(--tile-32); color: #fff; }
.tile-64 { background: var(--tile-64); color: #fff; }
.tile-128 { background: var(--tile-128); color: #fff; font-size: 30px; box-shadow: 0 4px 20px rgba(213, 126, 235, 0.4), inset 0 1px 0 rgba(255,255,255,0.2); }
.tile-256 { background: var(--tile-256); color: #fff; font-size: 30px; box-shadow: 0 4px 20px rgba(250, 128, 154, 0.4), inset 0 1px 0 rgba(255,255,255,0.2); }
.tile-512 { background: var(--tile-512); color: #fff; font-size: 30px; box-shadow: 0 4px 20px rgba(166, 193, 238, 0.5), inset 0 1px 0 rgba(255,255,255,0.2); }
.tile-1024 { background: var(--tile-1024); color: #fff; font-size: 24px; box-shadow: 0 4px 25px rgba(246, 211, 101, 0.5), inset 0 1px 0 rgba(255,255,255,0.2); }
.tile-2048 { background: var(--tile-2048); color: #fff; font-size: 24px; box-shadow: 0 4px 30px rgba(255, 107, 107, 0.6), inset 0 1px 0 rgba(255,255,255,0.3); animation: glow2048 2s ease-in-out infinite; }
.tile-super { background: var(--tile-super); color: #fff; font-size: 20px; box-shadow: 0 4px 30px rgba(0,0,0,0.8), inset 0 1px 0 rgba(255,255,255,0.1); }
@keyframes glow2048 {
0%, 100% { box-shadow: 0 4px 30px rgba(255, 107, 107, 0.6), inset 0 1px 0 rgba(255,255,255,0.3); }
50% { box-shadow: 0 4px 40px rgba(255, 107, 107, 0.9), 0 0 60px rgba(255, 107, 107, 0.3), inset 0 1px 0 rgba(255,255,255,0.3); }
}
/* Tile Animations */
.tile-new { animation: appear 0.25s cubic-bezier(0.34, 1.56, 0.64, 1); }
.tile-merged { animation: pop 0.25s cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes appear {
0% { transform: scale(0); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
@keyframes pop {
0% { transform: scale(1); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
/* Instructions */
.instructions p { font-size: 14px; color: rgba(255,255,255,0.6); line-height: 1.6; }
/* Game Over Overlay */
.game-overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(15, 12, 41, 0.85);
backdrop-filter: blur(8px);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 16px;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.game-overlay.active { opacity: 1; pointer-events: auto; }
.game-overlay.win { background: rgba(255, 107, 107, 0.3); }
.game-over-text {
font-size: 48px;
font-weight: 900;
background: linear-gradient(135deg, #ff6b6b, #feca57);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 20px;
}
.game-overlay .btn-new-game { font-size: 18px; padding: 14px 28px; }
/* Responsive Design */
@media (max-width: 480px) {
:root { --cell-size: 65px; --cell-gap: 10px; }
.title { font-size: 38px; }
.score-box { padding: 6px 14px; min-width: 65px; }
.score-label { font-size: 9px; }
.score-value { font-size: 18px; }
.tile { font-size: 28px; }
.tile-128, .tile-256, .tile-512 { font-size: 24px; }
.tile-1024, .tile-2048 { font-size: 20px; }
.tile-super { font-size: 16px; }
.game-over-text { font-size: 36px; }
}
@media (max-width: 360px) {
:root { --cell-size: 55px; --cell-gap: 8px; }
.title { font-size: 30px; }
.tile { font-size: 24px; }
.tile-128, .tile-256, .tile-512 { font-size: 20px; }
.tile-1024, .tile-2048 { font-size: 18px; }
.tile-super { font-size: 14px; }
}
@media (hover: hover) {
.btn-new-game:hover { background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); }
}
/* Base overrides */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
touch-action: none;
user-select: none;
overflow: hidden;
}
.game-intro p { font-size: 14px; color: rgba(255,255,255,0.7); margin-bottom: 15px; }
</style>
</head>
<body>
<div class="container">
<header class="header">
<h1 class="title">2048 Classic</h1>
<div class="scores">
<div class="score-box">
<span class="score-label">SCORE</span>
<span class="score-value" id="score">0</span>
</div>
<div class="score-box">
<span class="score-label">BEST</span>
<span class="score-value" id="best">0</span>
</div>
</div>
</header>
<div class="game-intro">
<p>Join the tiles, get to <strong>2048!</strong></p>
</div>
<div class="controls">
<button class="btn-new-game" id="newGameBtn">New Game</button>
</div>
<div class="game-container" id="gameContainer">
<div class="grid-background" id="gridBackground"></div>
<div class="tiles-container" id="tilesContainer"></div>
</div>
<div class="instructions">
<p><strong>How to play:</strong> Use your arrow keys or swipe to move the tiles. When two tiles with the same number touch, they merge into one!</p>
</div>
</div>
<script>
/**
* 2048 Classic - Game Logic
* A pure JavaScript implementation of the popular 2048 puzzle game
*/
(function() {
'use strict';
// Game Configuration
const GRID_SIZE = 4;
const WINNING_TILE = 2048;
// DOM Elements
const gameContainer = document.getElementById('gameContainer');
const gridBackground = document.getElementById('gridBackground');
const tilesContainer = document.getElementById('tilesContainer');
const scoreElement = document.getElementById('score');
const bestElement = document.getElementById('best');
const newGameBtn = document.getElementById('newGameBtn');
// Game State
let grid = [];
let score = 0;
let best = parseInt(localStorage.getItem('2048_best')) || 0;
let isAnimating = false;
let gameOver = false;
let hasWon = false;
// Touch handling
let touchStartX = 0;
let touchStartY = 0;
let touchEndX = 0;
let touchEndY = 0;
// Initialize the game
function init() {
createGrid();
loadBestScore();
setupEventListeners();
newGame();
}
// Create the grid background cells
function createGrid() {
gridBackground.innerHTML = '';
for (let i = 0; i < GRID_SIZE * GRID_SIZE; i++) {
const cell = document.createElement('div');
cell.className = 'cell';
gridBackground.appendChild(cell);
}
}
// Load best score from localStorage
function loadBestScore() {
bestElement.textContent = best;
}
// Setup event listeners
function setupEventListeners() {
// New Game button
newGameBtn.addEventListener('click', newGame);
// Keyboard controls
document.addEventListener('keydown', handleKeyDown);
// Touch controls
gameContainer.addEventListener('touchstart', handleTouchStart, { passive: false });
gameContainer.addEventListener('touchmove', handleTouchMove, { passive: false });
gameContainer.addEventListener('touchend', handleTouchEnd, { passive: false });
// Prevent default touch behavior
document.body.addEventListener('touchmove', function(e) {
e.preventDefault();
}, { passive: false });
}
// Start a new game
function newGame() {
// Reset state
grid = Array(GRID_SIZE).fill(null).map(() => Array(GRID_SIZE).fill(0));
score = 0;
gameOver = false;
hasWon = false;
isAnimating = false;
// Clear tiles
tilesContainer.innerHTML = '';
// Remove game over overlay if exists
const overlay = document.querySelector('.game-overlay');
if (overlay) {
overlay.remove();
}
// Update score display
updateScore();
// Add initial tiles
addRandomTile();
addRandomTile();
}
// Add a random tile (2 or 4)
function addRandomTile() {
const emptyCells = [];
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (grid[r][c] === 0) {
emptyCells.push({ row: r, col: c });
}
}
}
if (emptyCells.length === 0) return false;
const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
const value = Math.random() < 0.9 ? 2 : 4;
grid[randomCell.row][randomCell.col] = value;
createTileElement(randomCell.row, randomCell.col, value, true);
return true;
}
// Create a tile DOM element
function createTileElement(row, col, value, isNew = false) {
const tile = document.createElement('div');
tile.className = `tile tile-${getTileClass(value)}`;
if (isNew) tile.classList.add('tile-new');
tile.textContent = value;
tile.dataset.row = row;
tile.dataset.col = col;
setTilePosition(tile, row, col);
tilesContainer.appendChild(tile);
// Remove animation class after animation
if (isNew) {
setTimeout(() => tile.classList.remove('tile-new'), 200);
}
return tile;
}
// Get tile class based on value
function getTileClass(value) {
if (value > 2048) return 'super';
return value;
}
// Set tile position
function setTilePosition(tile, row, col) {
const cellSize = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--cell-size'));
const cellGap = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--cell-gap'));
tile.style.top = row * (cellSize + cellGap) + 'px';
tile.style.left = col * (cellSize + cellGap) + 'px';
}
// Handle keyboard input
function handleKeyDown(e) {
if (gameOver || isAnimating) return;
let moved = false;
const key = e.key;
if (key === 'ArrowUp' || key === 'w' || key === 'W') {
moved = moveUp();
} else if (key === 'ArrowDown' || key === 's' || key === 'S') {
moved = moveDown();
} else if (key === 'ArrowLeft' || key === 'a' || key === 'A') {
moved = moveLeft();
} else if (key === 'ArrowRight' || key === 'd' || key === 'D') {
moved = moveRight();
}
if (moved) {
e.preventDefault();
isAnimating = true;
setTimeout(() => {
addRandomTile();
isAnimating = false;
if (!hasWon && checkWin()) {
hasWon = true;
showWinOverlay();
} else if (checkGameOver()) {
gameOver = true;
showGameOverOverlay();
}
}, 150);
}
}
// Touch event handlers
function handleTouchStart(e) {
if (gameOver || isAnimating) return;
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}
function handleTouchMove(e) {
e.preventDefault();
}
function handleTouchEnd(e) {
if (gameOver || isAnimating) return;
touchEndX = e.changedTouches[0].clientX;
touchEndY = e.changedTouches[0].clientY;
const deltaX = touchEndX - touchStartX;
const deltaY = touchEndY - touchStartY;
const minSwipeDistance = 30;
let moved = false;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (Math.abs(deltaX) > minSwipeDistance) {
if (deltaX > 0) {
moved = moveRight();
} else {
moved = moveLeft();
}
}
} else {
// Vertical swipe
if (Math.abs(deltaY) > minSwipeDistance) {
if (deltaY > 0) {
moved = moveDown();
} else {
moved = moveUp();
}
}
}
if (moved) {
isAnimating = true;
setTimeout(() => {
addRandomTile();
isAnimating = false;
if (!hasWon && checkWin()) {
hasWon = true;
showWinOverlay();
} else if (checkGameOver()) {
gameOver = true;
showGameOverOverlay();
}
}, 150);
}
}
// Move functions
function moveLeft() {
return moveHorizontal(-1);
}
function moveRight() {
return moveHorizontal(1);
}
function moveUp() {
return moveVertical(-1);
}
function moveDown() {
return moveVertical(1);
}
// Move tiles horizontally
function moveHorizontal(direction) {
let moved = false;
const mergedPositions = [];
for (let r = 0; r < GRID_SIZE; r++) {
const row = grid[r].slice();
const result = slideRow(row, direction, mergedPositions, r);
if (result.moved) moved = true;
for (let c = 0; c < GRID_SIZE; c++) {
grid[r][c] = result.row[c];
}
}
if (moved) {
animateTiles(mergedPositions);
}
return moved;
}
// Move tiles vertically
function moveVertical(direction) {
let moved = false;
const mergedPositions = [];
for (let c = 0; c < GRID_SIZE; c++) {
const col = [];
for (let r = 0; r < GRID_SIZE; r++) {
col.push(grid[r][c]);
}
const result = slideRow(col, direction, mergedPositions, c, true);
if (result.moved) moved = true;
for (let r = 0; r < GRID_SIZE; r++) {
grid[r][c] = result.row[r];
}
}
if (moved) {
animateTiles(mergedPositions);
}
return moved;
}
// Slide a row/column
function slideRow(row, direction, mergedPositions, index, isVertical = false) {
const original = row.slice();
// Remove zeros
let filtered = row.filter(val => val !== 0);
// Reverse if moving right or down
if (direction === 1) {
filtered.reverse();
}
// Merge adjacent equal tiles
const merged = [];
let i = 0;
while (i < filtered.length) {
if (i + 1 < filtered.length && filtered[i] === filtered[i + 1]) {
const newValue = filtered[i] * 2;
merged.push(newValue);
score += newValue;
i += 2;
} else {
merged.push(filtered[i]);
i++;
}
}
// Pad with zeros
while (merged.length < GRID_SIZE) {
merged.push(0);
}
// Reverse back if direction was right/down
if (direction === 1) {
merged.reverse();
}
// Check if moved
let moved = false;
for (let j = 0; j < GRID_SIZE; j++) {
if (original[j] !== merged[j]) {
moved = true;
break;
}
}
// Track merged positions
for (let j = 0; j < GRID_SIZE; j++) {
if (merged[j] !== 0 && merged[j] !== original[j]) {
if (merged[j] % 2 === 0) {
// Check if this was a merge
const wasMerge = (direction === -1 && j > 0 && merged[j] === merged[j - 1]) ||
(direction === 1 && j < GRID_SIZE - 1 && merged[j] === merged[j + 1]);
if (wasMerge || (merged[j] === original[j - 1] && direction === -1) ||
(merged[j] === original[j + 1] && direction === 1)) {
// This is a merge, don't add to mergedPositions again
}
}
}
}
updateScore();
return { row: merged, moved };
}
// Animate tiles
function animateTiles(mergedPositions) {
// Clear and rebuild all tiles
tilesContainer.innerHTML = '';
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (grid[r][c] !== 0) {
const tile = createTileElement(r, c, grid[r][c], false);
// Check if this position had a merge
const isMerged = mergedPositions.some(pos => pos.row === r && pos.col === c);
if (isMerged) {
tile.classList.add('tile-merged');
setTimeout(() => tile.classList.remove('tile-merged'), 200);
}
}
}
}
}
// Update score display
function updateScore() {
scoreElement.textContent = score;
if (score > best) {
best = score;
localStorage.setItem('2048_best', best);
bestElement.textContent = best;
}
}
// Check for win
function checkWin() {
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (grid[r][c] === WINNING_TILE) {
return true;
}
}
}
return false;
}
// Check for game over
function checkGameOver() {
// Check for empty cells
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (grid[r][c] === 0) return false;
}
}
// Check for possible merges
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
const value = grid[r][c];
// Check right neighbor
if (c < GRID_SIZE - 1 && grid[r][c + 1] === value) return false;
// Check bottom neighbor
if (r < GRID_SIZE - 1 && grid[r + 1][c] === value) return false;
}
}
return true;
}
// Show win overlay
function showWinOverlay() {
const overlay = document.createElement('div');
overlay.className = 'game-overlay win active';
overlay.innerHTML = `
<div class="game-over-text">You Win!</div>
<button class="btn-new-game" onclick="location.reload()">Play Again</button>
`;
gameContainer.appendChild(overlay);
}
// Show game over overlay
function showGameOverOverlay() {
const overlay = document.createElement('div');
overlay.className = 'game-overlay active';
overlay.innerHTML = `
<div class="game-over-text">Game Over!</div>
<button class="btn-new-game" onclick="location.reload()">Try Again</button>
`;
gameContainer.appendChild(overlay);
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>
</body>
</html>Game Source: 2048 Classic
Creator: PhantomGamer18
Libraries: none
Complexity: complex (827 lines, 24.3 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: 2048-classic-phantomgamer18" to link back to the original. Then publish at arcadelab.ai/publish.