untitled

Pasted Sep 16, 2026, 9:19 PM1 file948 lines

Scan to open on your phone

Point a camera at the QR code - it opens this exact paste, no app needed.

https://codepastes.com/zzgthayt4cqbedbn
untitled
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>30-Column Block Drop</title>

<style>
    * {
        box-sizing: border-box;
    }

    body {
        margin: 0;
        min-height: 100vh;
        background: #111;
        color: white;
        font-family: Arial, sans-serif;
        display: flex;
        justify-content: center;
        align-items: center;
    }

    #game {
        display: flex;
        gap: 25px;
        align-items: flex-start;
    }

    canvas {
        background: #080808;
        border: 3px solid #444;
        image-rendering: pixelated;
    }

    #board {
        width: 600px;
        height: 400px;
    }

    #side {
        width: 180px;
        background: #1b1b1b;
        border: 2px solid #333;
        padding: 18px;
        border-radius: 8px;
    }

    h1 {
        font-size: 20px;
        margin-top: 0;
    }

    .stat {
        margin: 15px 0;
        font-size: 18px;
    }

    #next {
        width: 120px;
        height: 120px;
        display: block;
        margin: 10px auto 20px;
        background: #080808;
        border: 2px solid #333;
    }

    button {
        width: 100%;
        padding: 10px;
        background: #333;
        color: white;
        border: 1px solid #555;
        border-radius: 5px;
        cursor: pointer;
        font-size: 15px;
    }

    button:hover {
        background: #444;
    }

    .controls {
        font-size: 13px;
        line-height: 1.7;
        color: #bbb;
    }

    #gameOver {
        position: fixed;
        inset: 0;
        display: none;
        justify-content: center;
        align-items: center;
        background: rgba(0,0,0,.75);
    }

    #gameOverBox {
        background: #202020;
        padding: 30px;
        text-align: center;
        border: 2px solid #555;
        border-radius: 10px;
    }

    #gameOverBox h2 {
        margin-top: 0;
    }
</style>
</head>

<body>

<div id="game">

    <canvas id="board" width="600" height="400"></canvas>

    <div id="side">
        <h1>30-COLUMN DROP</h1>

        <div class="stat">
            Score: <span id="score">0</span>
        </div>

        <div class="stat">
            Lines: <span id="lines">0</span>
        </div>

        <div class="stat">
            Level: <span id="level">1</span>
        </div>

        <h3>Next</h3>
        <canvas id="next" width="120" height="120"></canvas>

        <button onclick="restartGame()">Restart</button>

        <div class="controls">
            <br>
            ← → Move<br>
            ↓ Soft drop<br>
            ↑ Rotate<br>
            SPACE Hard drop<br>
            P Pause
        </div>
    </div>

</div>

<div id="gameOver">
    <div id="gameOverBox">
        <h2>GAME OVER</h2>
        <p>Score: <span id="finalScore">0</span></p>
        <button onclick="restartGame()">Play Again</button>
    </div>
</div>

<script>

// ============================================================
// SETTINGS
// ============================================================

const COLS = 30;
const ROWS = 20;

const canvas = document.getElementById("board");
const ctx = canvas.getContext("2d");

const nextCanvas = document.getElementById("next");
const nextCtx = nextCanvas.getContext("2d");

const CELL_W = canvas.width / COLS;
const CELL_H = canvas.height / ROWS;

// ============================================================
// COLORS
// ============================================================

const COLORS = {
    I: "#00e5ff",
    O: "#ffe600",
    T: "#b000ff",
    S: "#00d060",
    Z: "#ff3030",
    J: "#3060ff",
    L: "#ff8c00"
};

// ============================================================
// PIECES
// ============================================================

const PIECES = {

    I: [
        [1,1,1,1]
    ],

    O: [
        [1,1],
        [1,1]
    ],

    T: [
        [0,1,0],
        [1,1,1]
    ],

    S: [
        [0,1,1],
        [1,1,0]
    ],

    Z: [
        [1,1,0],
        [0,1,1]
    ],

    J: [
        [1,0,0],
        [1,1,1]
    ],

    L: [
        [0,0,1],
        [1,1,1]
    ]
};

const TYPES = Object.keys(PIECES);

// ============================================================
// GAME STATE
// ============================================================

let board;
let current;
let nextPiece;

let score = 0;
let lines = 0;
let level = 1;

let dropCounter = 0;
let lastTime = 0;

let gameOver = false;
let paused = false;

// ============================================================
// CREATE BOARD
// ============================================================

function createBoard() {

    return Array.from(
        { length: ROWS },
        () => Array(COLS).fill(null)
    );

}

// ============================================================
// RANDOM PIECE
// ============================================================

function randomPiece() {

    const type =
        TYPES[Math.floor(Math.random() * TYPES.length)];

    return {
        type: type,
        shape: PIECES[type].map(row => [...row]),
        x: Math.floor(COLS / 2) - 2,
        y: 0
    };

}

// ============================================================
// RESET
// ============================================================

function restartGame() {

    board = createBoard();

    score = 0;
    lines = 0;
    level = 1;

    gameOver = false;
    paused = false;

    nextPiece = randomPiece();
    spawnPiece();

    document.getElementById("gameOver").style.display = "none";

    updateUI();
    draw();
}

// ============================================================
// SPAWN
// ============================================================

function spawnPiece() {

    current = nextPiece;
    nextPiece = randomPiece();

    current.x =
        Math.floor(COLS / 2) -
        Math.floor(current.shape[0].length / 2);

    current.y = 0;

    drawNext();

    if (collision(current)) {
        endGame();
    }
}

// ============================================================
// COLLISION
// ============================================================

function collision(piece) {

    for (let y = 0; y < piece.shape.length; y++) {

        for (let x = 0; x < piece.shape[y].length; x++) {

            if (!piece.shape[y][x]) continue;

            const boardX = piece.x + x;
            const boardY = piece.y + y;

            if (
                boardX < 0 ||
                boardX >= COLS ||
                boardY >= ROWS
            ) {
                return true;
            }

            if (
                boardY >= 0 &&
                board[boardY][boardX]
            ) {
                return true;
            }
        }
    }

    return false;
}

// ============================================================
// MERGE PIECE
// ============================================================

function merge() {

    current.shape.forEach((row, y) => {

        row.forEach((value, x) => {

            if (value) {

                const boardY = current.y + y;
                const boardX = current.x + x;

                if (
                    boardY >= 0 &&
                    boardY < ROWS &&
                    boardX >= 0 &&
                    boardX < COLS
                ) {
                    board[boardY][boardX] =
                        current.type;
                }
            }

        });

    });

}

// ============================================================
// CLEAR LINES
// ============================================================

function clearLines() {

    let cleared = 0;

    outer:
    for (let y = ROWS - 1; y >= 0; y--) {

        for (let x = 0; x < COLS; x++) {

            if (!board[y][x]) {
                continue outer;
            }
        }

        board.splice(y, 1);

        board.unshift(
            Array(COLS).fill(null)
        );

        cleared++;
        y++;
    }

    if (cleared > 0) {

        lines += cleared;

        // Scoring scales with the number of lines cleared.
        const points = [
            0,
            100,
            300,
            500,
            800
        ];

        score += points[cleared] * level;

        level =
            Math.floor(lines / 10) + 1;

        updateUI();
    }
}

// ============================================================
// MOVE
// ============================================================

function move(dx) {

    if (paused || gameOver) return;

    current.x += dx;

    if (collision(current)) {
        current.x -= dx;
    }

}

// ============================================================
// DROP
// ============================================================

function drop() {

    if (paused || gameOver) return;

    current.y++;

    if (collision(current)) {

        current.y--;

        merge();
        clearLines();
        spawnPiece();
    }

    dropCounter = 0;
}

// ============================================================
// HARD DROP
// ============================================================

function hardDrop() {

    if (paused || gameOver) return;

    let distance = 0;

    while (!collision(current)) {

        current.y++;
        distance++;
    }

    current.y--;

    score += distance * 2;

    merge();
    clearLines();
    spawnPiece();

    updateUI();
}

// ============================================================
// ROTATION
// ============================================================

function rotate(matrix) {

    const result =
        matrix[0].map((_, index) =>
            matrix.map(row => row[index])
        );

    return result.map(row => row.reverse());
}

function rotatePiece() {

    if (paused || gameOver) return;

    const oldShape = current.shape;

    current.shape = rotate(current.shape);

    // Basic wall-kick behavior
    const originalX = current.x;

    if (collision(current)) {

        current.x++;

        if (collision(current)) {

            current.x = originalX - 1;

            if (collision(current)) {
                current.x = originalX;
                current.shape = oldShape;
            }
        }
    }
}

// ============================================================
// GHOST PIECE
// ============================================================

function getGhostY() {

    let ghostY = current.y;

    while (true) {

        ghostY++;

        const testPiece = {
            ...current,
            y: ghostY
        };

        if (collision(testPiece)) {
            return ghostY - 1;
        }
    }
}

// ============================================================
// DRAW BOARD
// ============================================================

function draw() {

    ctx.clearRect(
        0,
        0,
        canvas.width,
        canvas.height
    );

    drawGrid();

    // Locked blocks
    for (let y = 0; y < ROWS; y++) {

        for (let x = 0; x < COLS; x++) {

            if (board[y][x]) {

                drawBlock(
                    ctx,
                    x,
                    y,
                    COLORS[board[y][x]],
                    CELL_W,
                    CELL_H
                );
            }
        }
    }

    if (!gameOver) {

        // Ghost
        const ghostY = getGhostY();

        drawPiece(
            current,
            ghostY,
            true
        );

        // Current
        drawPiece(
            current,
            current.y,
            false
        );
    }

}

// ============================================================
// GRID
// ============================================================

function drawGrid() {

    ctx.strokeStyle = "#181818";
    ctx.lineWidth = 1;

    for (let x = 0; x <= COLS; x++) {

        ctx.beginPath();

        ctx.moveTo(
            x * CELL_W,
            0
        );

        ctx.lineTo(
            x * CELL_W,
            canvas.height
        );

        ctx.stroke();
    }

    for (let y = 0; y <= ROWS; y++) {

        ctx.beginPath();

        ctx.moveTo(
            0,
            y * CELL_H
        );

        ctx.lineTo(
            canvas.width,
            y * CELL_H
        );

        ctx.stroke();
    }
}

// ============================================================
// DRAW BLOCK
// ============================================================

function drawBlock(
    context,
    x,
    y,
    color,
    width,
    height,
    ghost = false
) {

    if (ghost) {

        context.strokeStyle = color;
        context.lineWidth = 2;

        context.strokeRect(
            x * width + 2,
            y * height + 2,
            width - 4,
            height - 4
        );

        return;
    }

    context.fillStyle = color;

    context.fillRect(
        x * width + 1,
        y * height + 1,
        width - 2,
        height - 2
    );

    // Highlight
    context.fillStyle = "rgba(255,255,255,.18)";

    context.fillRect(
        x * width + 2,
        y * height + 2,
        width - 4,
        3
    );

}

// ============================================================
// DRAW PIECE
// ============================================================

function drawPiece(
    piece,
    yPosition,
    ghost
) {

    piece.shape.forEach((row, y) => {

        row.forEach((value, x) => {

            if (!value) return;

            const px = piece.x + x;
            const py = yPosition + y;

            if (py < 0) return;

            drawBlock(
                ctx,
                px,
                py,
                COLORS[piece.type],
                CELL_W,
                CELL_H,
                ghost
            );
        });

    });

}

// ============================================================
// NEXT PIECE
// ============================================================

function drawNext() {

    nextCtx.clearRect(
        0,
        0,
        nextCanvas.width,
        nextCanvas.height
    );

    const blockSize = 25;

    const shape = nextPiece.shape;

    const offsetX =
        (120 - shape[0].length * blockSize) / 2;

    const offsetY =
        (120 - shape.length * blockSize) / 2;

    shape.forEach((row, y) => {

        row.forEach((value, x) => {

            if (!value) return;

            drawBlock(
                nextCtx,
                offsetX / blockSize + x,
                offsetY / blockSize + y,
                COLORS[nextPiece.type],
                blockSize,
                blockSize
            );
        });

    });

}

// ============================================================
// UI
// ============================================================

function updateUI() {

    document.getElementById("score")
        .textContent = score;

    document.getElementById("lines")
        .textContent = lines;

    document.getElementById("level")
        .textContent = level;

}

// ============================================================
// GAME OVER
// ============================================================

function endGame() {

    gameOver = true;

    document.getElementById("finalScore")
        .textContent = score;

    document.getElementById("gameOver")
        .style.display = "flex";
}

// ============================================================
// PAUSE
// ============================================================

function togglePause() {

    if (gameOver) return;

    paused = !paused;
}

// ============================================================
// CONTROLS
// ============================================================

document.addEventListener("keydown", event => {

    if (
        [
            "ArrowLeft",
            "ArrowRight",
            "ArrowDown",
            "ArrowUp",
            " ",
            "p",
            "P"
        ].includes(event.key)
    ) {
        event.preventDefault();
    }

    switch (event.key) {

        case "ArrowLeft":
            move(-1);
            break;

        case "ArrowRight":
            move(1);
            break;

        case "ArrowDown":
            drop();
            score += 1;
            updateUI();
            break;

        case "ArrowUp":
            rotatePiece();
            break;

        case " ":
            hardDrop();
            break;

        case "p":
        case "P":
            togglePause();
            break;
    }

});

// ============================================================
// GAME LOOP
// ============================================================

function getDropInterval() {

    return Math.max(
        80,
        800 - (level - 1) * 70
    );

}

function update(time = 0) {

    const deltaTime =
        time - lastTime;

    lastTime = time;

    if (!paused && !gameOver) {

        dropCounter += deltaTime;

        if (
            dropCounter >
            getDropInterval()
        ) {
            drop();
        }
    }

    draw();

    requestAnimationFrame(update);
}

// ============================================================
// START
// ============================================================

restartGame();
update();

</script>

</body>
</html>
```

17,225 chars · codepastes.com