Josh Bruce Online

2048 Game Documentation

Overview

A single-file implementation of the popular 2048 puzzle game featuring smooth animations, touch controls, and responsive design. The game challenges players to combine numbered tiles to reach the 2048 tile through strategic sliding movements.

Technical Architecture

Core Technologies

File Structure

2048.html    # Complete self-contained game

Game Implementation

Grid System

// 4x4 game grid
const GRID_SIZE = 4;
let board = [];
let score = 0;
let highScore = 0;

// Initialize empty board
function initializeBoard() {
    board = [];
    for (let i = 0; i < GRID_SIZE; i++) {
        board[i] = [];
        for (let j = 0; j < GRID_SIZE; j++) {
            board[i][j] = 0;
        }
    }
}

// Add random tile (2 or 4)
function addRandomTile() {
    let emptyCells = [];
    
    for (let i = 0; i < GRID_SIZE; i++) {
        for (let j = 0; j < GRID_SIZE; j++) {
            if (board[i][j] === 0) {
                emptyCells.push({x: i, y: j});
            }
        }
    }
    
    if (emptyCells.length > 0) {
        let randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
        let value = Math.random() < 0.9 ? 2 : 4; // 90% chance for 2, 10% for 4
        board[randomCell.x][randomCell.y] = value;
        return true;
    }
    
    return false;
}

Movement Logic

// Core sliding algorithm
function slideArray(arr) {
    // Remove zeros
    let filtered = arr.filter(val => val !== 0);
    let missing = GRID_SIZE - filtered.length;
    let zeros = Array(missing).fill(0);
    return filtered.concat(zeros);
}

// Combine adjacent equal tiles
function combineArray(arr) {
    let score = 0;
    for (let i = 0; i < GRID_SIZE - 1; i++) {
        if (arr[i] !== 0 && arr[i] === arr[i + 1]) {
            arr[i] *= 2;
            arr[i + 1] = 0;
            score += arr[i];
        }
    }
    return score;
}

// Move tiles left
function moveLeft() {
    let moved = false;
    let moveScore = 0;
    
    for (let i = 0; i < GRID_SIZE; i++) {
        let originalRow = [...board[i]];
        
        // Slide tiles left
        board[i] = slideArray(board[i]);
        
        // Combine tiles
        moveScore += combineArray(board[i]);
        
        // Slide again after combining
        board[i] = slideArray(board[i]);
        
        // Check if row changed
        if (JSON.stringify(originalRow) !== JSON.stringify(board[i])) {
            moved = true;
        }
    }
    
    if (moved) {
        score += moveScore;
        addRandomTile();
        updateDisplay();
        checkGameStatus();
    }
}

// Move tiles right (reverse, move left, reverse)
function moveRight() {
    // Reverse each row
    for (let i = 0; i < GRID_SIZE; i++) {
        board[i].reverse();
    }
    
    moveLeft();
    
    // Reverse back
    for (let i = 0; i < GRID_SIZE; i++) {
        board[i].reverse();
    }
}

// Move tiles up (transpose, move left, transpose back)
function moveUp() {
    board = transpose(board);
    moveLeft();
    board = transpose(board);
}

// Move tiles down (transpose, move right, transpose back)
function moveDown() {
    board = transpose(board);
    moveRight();
    board = transpose(board);
}

// Matrix transpose utility
function transpose(matrix) {
    return matrix[0].map((_, colIndex) => matrix.map(row => row[colIndex]));
}

Visual System

CSS Grid Layout

/* Game container */
.game-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 20px;
    font-family: 'Arial', sans-serif;
    background: #faf8ef;
    min-height: 100vh;
    box-sizing: border-box;
}

/* Game grid */
.grid-container {
    position: relative;
    width: 320px;
    height: 320px;
    background: #bbada0;
    border-radius: 8px;
    padding: 10px;
    margin: 20px 0;
}

.grid-row {
    display: flex;
    margin-bottom: 10px;
}

.grid-row:last-child {
    margin-bottom: 0;
}

.grid-cell {
    width: 65px;
    height: 65px;
    background: rgba(238, 228, 218, 0.35);
    border-radius: 4px;
    margin-right: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 28px;
    font-weight: bold;
    color: #776e65;
}

.grid-cell:last-child {
    margin-right: 0;
}

Tile Styling and Colors

/* Tile appearance based on value */
.tile {
    position: absolute;
    width: 65px;
    height: 65px;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bold;
    transition: all 0.15s ease-in-out;
    transform-origin: center;
}

/* Progressive tile colors */
.tile-2 { background: #eee4da; color: #776e65; font-size: 28px; }
.tile-4 { background: #ede0c8; color: #776e65; font-size: 28px; }
.tile-8 { background: #f2b179; color: #f9f6f2; font-size: 28px; }
.tile-16 { background: #f59563; color: #f9f6f2; font-size: 28px; }
.tile-32 { background: #f67c5f; color: #f9f6f2; font-size: 28px; }
.tile-64 { background: #f65e3b; color: #f9f6f2; font-size: 28px; }
.tile-128 { background: #edcf72; color: #f9f6f2; font-size: 24px; }
.tile-256 { background: #edcc61; color: #f9f6f2; font-size: 24px; }
.tile-512 { background: #edc850; color: #f9f6f2; font-size: 24px; }
.tile-1024 { background: #edc53f; color: #f9f6f2; font-size: 20px; }
.tile-2048 { background: #edc22e; color: #f9f6f2; font-size: 20px; box-shadow: 0 0 10px rgba(237, 194, 46, 0.5); }

/* Super tiles (beyond 2048) */
.tile-4096 { background: #3c3a32; color: #f9f6f2; font-size: 18px; }
.tile-8192 { background: #3c3a32; color: #f9f6f2; font-size: 16px; }

Animation System

/* Tile appearance animation */
@keyframes tile-appear {
    0% {
        transform: scale(0);
    }
    100% {
        transform: scale(1);
    }
}

.tile-new {
    animation: tile-appear 0.15s ease-in-out;
}

/* Tile merge animation */
@keyframes tile-merge {
    0% {
        transform: scale(1);
    }
    50% {
        transform: scale(1.1);
    }
    100% {
        transform: scale(1);
    }
}

.tile-merged {
    animation: tile-merge 0.15s ease-in-out;
}

Input Handling

Keyboard Controls

// Keyboard event handling
document.addEventListener('keydown', function(event) {
    if (gameOver) return;
    
    switch(event.keyCode) {
        case 37: // Left arrow
            event.preventDefault();
            moveLeft();
            break;
        case 38: // Up arrow
            event.preventDefault();
            moveUp();
            break;
        case 39: // Right arrow
            event.preventDefault();
            moveRight();
            break;
        case 40: // Down arrow
            event.preventDefault();
            moveDown();
            break;
        case 82: // R key - restart
            if (event.ctrlKey || event.metaKey) {
                event.preventDefault();
                restartGame();
            }
            break;
    }
});

Touch and Swipe Controls

// Touch gesture handling
let touchStartX = 0;
let touchStartY = 0;
let touchEndX = 0;
let touchEndY = 0;

const MIN_SWIPE_DISTANCE = 30;

document.addEventListener('touchstart', function(event) {
    event.preventDefault();
    touchStartX = event.changedTouches[0].screenX;
    touchStartY = event.changedTouches[0].screenY;
}, { passive: false });

document.addEventListener('touchend', function(event) {
    event.preventDefault();
    touchEndX = event.changedTouches[0].screenX;
    touchEndY = event.changedTouches[0].screenY;
    handleSwipe();
}, { passive: false });

function handleSwipe() {
    if (gameOver) return;
    
    const deltaX = touchEndX - touchStartX;
    const deltaY = touchEndY - touchStartY;
    const absDeltaX = Math.abs(deltaX);
    const absDeltaY = Math.abs(deltaY);
    
    // Check if swipe is long enough
    if (Math.max(absDeltaX, absDeltaY) < MIN_SWIPE_DISTANCE) {
        return;
    }
    
    // Determine swipe direction
    if (absDeltaX > absDeltaY) {
        // Horizontal swipe
        if (deltaX > 0) {
            moveRight();
        } else {
            moveLeft();
        }
    } else {
        // Vertical swipe
        if (deltaY > 0) {
            moveDown();
        } else {
            moveUp();
        }
    }
}

Game State Management

Win/Lose Detection

// Check if player has won (reached 2048)
function checkWin() {
    for (let i = 0; i < GRID_SIZE; i++) {
        for (let j = 0; j < GRID_SIZE; j++) {
            if (board[i][j] === 2048) {
                return true;
            }
        }
    }
    return false;
}

// Check if game is over (no moves available)
function checkGameOver() {
    // Check for empty cells
    for (let i = 0; i < GRID_SIZE; i++) {
        for (let j = 0; j < GRID_SIZE; j++) {
            if (board[i][j] === 0) {
                return false;
            }
        }
    }
    
    // Check for possible horizontal merges
    for (let i = 0; i < GRID_SIZE; i++) {
        for (let j = 0; j < GRID_SIZE - 1; j++) {
            if (board[i][j] === board[i][j + 1]) {
                return false;
            }
        }
    }
    
    // Check for possible vertical merges
    for (let i = 0; i < GRID_SIZE - 1; i++) {
        for (let j = 0; j < GRID_SIZE; j++) {
            if (board[i][j] === board[i + 1][j]) {
                return false;
            }
        }
    }
    
    return true; // No moves available
}

// Overall game status check
function checkGameStatus() {
    if (checkWin() && !hasWon) {
        hasWon = true;
        showWinMessage();
    }
    
    if (checkGameOver()) {
        gameOver = true;
        showGameOverMessage();
    }
}

Score System

Score Calculation and Display

// Update score display
function updateScore() {
    document.getElementById('current-score').textContent = score;
    
    if (score > highScore) {
        highScore = score;
        document.getElementById('high-score').textContent = highScore;
        saveHighScore();
    }
}

// Local storage for high score persistence
function saveHighScore() {
    try {
        localStorage.setItem('2048-high-score', highScore.toString());
    } catch (e) {
        console.warn('Unable to save high score to localStorage');
    }
}

function loadHighScore() {
    try {
        const saved = localStorage.getItem('2048-high-score');
        return saved ? parseInt(saved, 10) : 0;
    } catch (e) {
        return 0;
    }
}

Visual Updates

Dynamic Grid Rendering

// Update visual representation
function updateDisplay() {
    const gridContainer = document.getElementById('grid-container');
    
    // Clear existing tiles
    const existingTiles = gridContainer.querySelectorAll('.tile');
    existingTiles.forEach(tile => tile.remove());
    
    // Add current tiles
    for (let i = 0; i < GRID_SIZE; i++) {
        for (let j = 0; j < GRID_SIZE; j++) {
            if (board[i][j] !== 0) {
                createTile(board[i][j], i, j);
            }
        }
    }
    
    updateScore();
}

// Create individual tile element
function createTile(value, row, col) {
    const tile = document.createElement('div');
    tile.className = `tile tile-${value}`;
    tile.textContent = value;
    
    // Position tile in grid
    const x = col * 75 + 10; // 65px cell + 10px margin
    const y = row * 75 + 10;
    tile.style.transform = `translate(${x}px, ${y}px)`;
    
    // Add to container
    document.getElementById('grid-container').appendChild(tile);
    
    // Trigger appearance animation
    requestAnimationFrame(() => {
        tile.classList.add('tile-new');
    });
}

Game Initialization

Setup and Reset

// Initialize new game
function initGame() {
    initializeBoard();
    score = 0;
    gameOver = false;
    hasWon = false;
    
    // Add two initial tiles
    addRandomTile();
    addRandomTile();
    
    updateDisplay();
    
    // Load high score
    highScore = loadHighScore();
    document.getElementById('high-score').textContent = highScore;
}

// Restart game
function restartGame() {
    if (confirm('Are you sure you want to restart? Your current progress will be lost.')) {
        initGame();
        hideMessages();
    }
}

// Hide win/game over messages
function hideMessages() {
    document.getElementById('win-message').style.display = 'none';
    document.getElementById('game-over-message').style.display = 'none';
}

Responsive Design

Mobile Optimization

/* Mobile-specific styles */
@media (max-width: 480px) {
    .game-container {
        padding: 10px;
    }
    
    .grid-container {
        width: 280px;
        height: 280px;
        padding: 8px;
    }
    
    .grid-cell {
        width: 56px;
        height: 56px;
        font-size: 24px;
        margin-right: 8px;
        margin-bottom: 8px;
    }
    
    .tile {
        width: 56px;
        height: 56px;
        font-size: 20px;
    }
    
    .tile-128, .tile-256, .tile-512 {
        font-size: 18px;
    }
    
    .tile-1024, .tile-2048 {
        font-size: 16px;
    }
}

/* Prevent zoom on mobile devices */
.game-container {
    touch-action: manipulation;
    user-select: none;
    -webkit-user-select: none;
}

Accessibility Features

/* High contrast mode support */
@media (prefers-contrast: high) {
    .tile {
        border: 2px solid #000;
    }
    
    .grid-cell {
        border: 1px solid #333;
    }
}

/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
    .tile {
        transition: none;
    }
    
    .tile-new,
    .tile-merged {
        animation: none;
    }
}

Performance Optimizations

Efficient Rendering

// Optimized board comparison
function boardsEqual(board1, board2) {
    for (let i = 0; i < GRID_SIZE; i++) {
        for (let j = 0; j < GRID_SIZE; j++) {
            if (board1[i][j] !== board2[i][j]) {
                return false;
            }
        }
    }
    return true;
}

// Batch DOM updates
function batchUpdateDisplay() {
    requestAnimationFrame(() => {
        updateDisplay();
    });
}

Memory Management

// Clean up event listeners on game end
function cleanup() {
    document.removeEventListener('keydown', handleKeydown);
    document.removeEventListener('touchstart', handleTouchStart);
    document.removeEventListener('touchend', handleTouchEnd);
}

Code Quality Assessment

Strengths

Areas for Enhancement

Conclusion

This 2048 implementation demonstrates excellent understanding of game mechanics, efficient algorithms, and modern web development practices. The combination of smooth animations, responsive design, and intuitive controls creates an engaging puzzle game experience that works seamlessly across all devices.

Technical Rating: 8.4/10