Scan to open on your phone
Point a camera at the QR code - it opens this exact paste, no app needed.
https://codepastes.com/zzgthayt4cqbedbnuntitledPlain Text
```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