Skip to content

Instantly share code, notes, and snippets.

@mgroves
Created March 22, 2025 19:53
Show Gist options
  • Save mgroves/e9bc10e9b4ecff767be4faa28c9c3063 to your computer and use it in GitHub Desktop.
Save mgroves/e9bc10e9b4ecff767be4faa28c9c3063 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Block Drop</title>
<style>
* {
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
body {
margin: 0;
background: #111;
color: #fff;
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: center;
touch-action: manipulation;
}
canvas {
background: #222;
margin-top: 10px;
touch-action: none;
border: 2px solid #444;
}
.controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin: 10px;
}
button {
font-size: 1.2em;
padding: 10px;
width: 100px;
height: 60px;
border: none;
border-radius: 10px;
background: #444;
color: white;
}
button:active {
background: #666;
}
</style>
</head>
<body>
<h1>Block Drop</h1>
<canvas id="game"></canvas>
<div class="controls">
<button onclick="move(-1)">Left</button>
<button onclick="rotate()">Rotate</button>
<button onclick="move(1)">Right</button>
<button onclick="drop()">Drop</button>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const gridWidth = 10;
const gridHeight = 20;
const cellSize = 24;
canvas.width = gridWidth * cellSize;
canvas.height = gridHeight * cellSize;
const colors = ['#f00', '#0f0', '#00f', '#ff0', '#f0f', '#0ff', '#aaa'];
const tetrominoes = [
[[1, 1, 1, 1]],
[[1, 1], [1, 1]],
[[0, 1, 0], [1, 1, 1]],
[[1, 0, 0], [1, 1, 1]],
[[0, 0, 1], [1, 1, 1]],
[[1, 1, 0], [0, 1, 1]],
[[0, 1, 1], [1, 1, 0]]
];
let grid = Array.from({length: gridHeight}, () => Array(gridWidth).fill(0));
let piece, pos, color;
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function beep(freq = 440, dur = 0.1) {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.type = 'square';
osc.frequency.value = freq;
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
osc.start();
osc.stop(audioCtx.currentTime + dur);
}
function newPiece() {
const index = Math.floor(Math.random() * tetrominoes.length);
piece = tetrominoes[index];
color = index + 1;
pos = {x: 3, y: 0};
if (collide()) {
alert('Game Over!');
grid = Array.from({length: gridHeight}, () => Array(gridWidth).fill(0));
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
grid.forEach((row, y) =>
row.forEach((val, x) => {
if (val) drawCell(x, y, colors[val - 1]);
}));
piece.forEach((row, y) =>
row.forEach((val, x) => {
if (val) drawCell(x + pos.x, y + pos.y, colors[color - 1]);
}));
}
function drawCell(x, y, fill) {
ctx.fillStyle = fill;
ctx.fillRect(x * cellSize, y * cellSize, cellSize - 1, cellSize - 1);
}
function collide(offsetX = 0, offsetY = 0, testPiece = piece) {
return testPiece.some((row, y) =>
row.some((val, x) => {
if (val) {
const nx = x + pos.x + offsetX;
const ny = y + pos.y + offsetY;
return nx < 0 || nx >= gridWidth || ny >= gridHeight || (ny >= 0 && grid[ny][nx]);
}
return false;
})
);
}
function merge() {
piece.forEach((row, y) =>
row.forEach((val, x) => {
if (val) grid[y + pos.y][x + pos.x] = color;
}));
beep(220);
clearLines();
newPiece();
}
function clearLines() {
for (let y = gridHeight - 1; y >= 0; y--) {
if (grid[y].every(v => v)) {
grid.splice(y, 1);
grid.unshift(Array(gridWidth).fill(0));
beep(880);
y++;
}
}
}
function rotate() {
const rotated = piece[0].map((_, i) => piece.map(row => row[i]).reverse());
if (!collide(0, 0, rotated)) {
piece = rotated;
beep(660);
}
}
function move(dir) {
if (!collide(dir, 0)) {
pos.x += dir;
beep(550);
}
}
function drop() {
if (!collide(0, 1)) {
pos.y++;
} else {
merge();
}
}
let dropCounter = 0;
let lastTime = 0;
function update(time = 0) {
const delta = time - lastTime;
lastTime = time;
dropCounter += delta;
if (dropCounter > 500) {
drop();
dropCounter = 0;
}
draw();
requestAnimationFrame(update);
}
newPiece();
update();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment