<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML5 俄罗斯方块</title>
<style>
body {
background: #202028;
color: #fff;
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
overflow: hidden;
}
h1 {
margin-top: 0;
font-size: 2em;
}
.game-container {
position: relative;
border: 2px solid #fff;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
canvas {
display: block;
background-color: #000;
}
.score-board {
margin-top: 10px;
font-size: 1.5em;
}
.controls-hint {
margin-top: 15px;
font-size: 0.9em;
color: #aaa;
text-align: center;
}
/* 游戏结束时的遮罩层 */
#game-over {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.85);
padding: 20px;
text-align: center;
border: 1px solid #fff;
width: 200px;
}
button {
background: #fff;
color: #000;
border: none;
padding: 10px 20px;
font-size: 1em;
cursor: pointer;
margin-top: 10px;
}
button:hover {
background: #ccc;
}
</style>
</head>
<body>
<h1>俄罗斯方块</h1>
<div class="game-container">
<canvas id="tetris" width="240" height="400"></canvas>
<div id="game-over">
<h2>游戏结束</h2>
<p>得分: <span id="final-score">0</span></p>
<button onclick="resetGame()">重新开始</button>
</div>
</div>
<div class="score-board">
得分: <span id="score">0</span>
</div>
<div class="controls-hint">
← → : 移动 | ↑ : 旋转 | ↓ : 加速下落
</div>
<script>
const canvas = document.getElementById('tetris');
const context = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const finalScoreElement = document.getElementById('final-score');
const gameOverDiv = document.getElementById('game-over');
// 放大倍数,让每个像素块更大 (20x12 的网格,每个块放大20倍)
context.scale(20, 20);
// 消除满行的逻辑
function arenaSweep() {
let rowCount = 1;
outer: for (let y = arena.length - 1; y > 0; --y) {
for (let x = 0; x < arena[y].length; ++x) {
if (arena[y][x] === 0) {
continue outer;
}
}
// 移除满行并填充新行
const row = arena.splice(y, 1)[0].fill(0);
arena.unshift(row);
++y;
player.score += rowCount * 10;
rowCount *= 2;
}
}
// 碰撞检测
function collide(arena, player) {
const [m, o] = [player.matrix, player.pos];
for (let y = 0; y < m.length; ++y) {
for (let x = 0; x < m[y].length; ++x) {
if (m[y][x] !== 0 &&
(arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) {
return true;
}
}
}
return false;
}
// 创建矩阵(游戏区域或方块)
function createMatrix(w, h) {
const matrix = [];
while (h--) {
matrix.push(new Array(w).fill(0));
}
return matrix;
}
// 定义所有方块形状
function createPiece(type) {
if (type === 'I') {
return [
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
];
} else if (type === 'L') {
return [
[0, 2, 0],
[0, 2, 0],
[0, 2, 2],
];
} else if (type === 'J') {
return [
[0, 3, 0],
[0, 3, 0],
[3, 3, 0],
];
} else if (type === 'O') {
return [
[4, 4],
[4, 4],
];
} else if (type === 'Z') {
return [
[5, 5, 0],
[0, 5, 5],
[0, 0, 0],
];
} else if (type === 'S') {
return [
[0, 6, 6],
[6, 6, 0],
[0, 0, 0],
];
} else if (type === 'T') {
return [
[0, 7, 0],
[7, 7, 7],
[0, 0, 0],
];
}
}
// 绘制函数
function draw() {
// 清空画布
context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height);
drawMatrix(arena, {x: 0, y: 0});
drawMatrix(player.matrix, player.pos);
}
// 绘制矩阵(方块或场地)
function drawMatrix(matrix, offset) {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
// 定义颜色
const colors = [
null,
'#FF0D72', // I - 红粉
'#0DC2FF', // L - 蓝
'#0DFF72', // J - 绿
'#F538FF', // O - 紫
'#FF8E0D', // Z - 橙
'#FFE138', // S - 黄
'#3877FF', // T - 深蓝
];
context.fillStyle = colors[value];
context.fillRect(x + offset.x, y + offset.y, 1, 1);
// 给方块加一点立体边框效果
context.lineWidth = 0.05;
context.strokeStyle = 'white';
context.strokeRect(x + offset.x, y + offset.y, 1, 1);
}
});
});
}
// 合并方块到场地中
function merge(arena, player) {
player.matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
arena[y + player.pos.y][x + player.pos.x] = value;
}
});
});
}
// 旋转矩阵
function rotate(matrix, dir) {
for (let y = 0; y < matrix.length; ++y) {
for (let x = 0; x < y; ++x) {
[
matrix[x][y],
matrix[y][x],
] = [
matrix[y][x],
matrix[x][y],
];
}
}
if (dir > 0) {
matrix.forEach(row => row.reverse());
} else {
matrix.reverse();
}
}
// 玩家下落逻辑
function playerDrop() {
player.pos.y++;
if (collide(arena, player)) {
player.pos.y--;
merge(arena, player);
playerReset();
arenaSweep();
updateScore();
}
dropCounter = 0;
}
// 玩家移动逻辑
function playerMove(offset) {
player.pos.x += offset;
if (collide(arena, player)) {
player.pos.x -= offset;
}
}
// 玩家重置(生成新方块或游戏结束)
function playerReset() {
const pieces = 'ILJOTSZ';
player.matrix = createPiece(pieces[pieces.length * Math.random() | 0]);
player.pos.y = 0;
player.pos.x = (arena[0].length / 2 | 0) -
(player.matrix[0].length / 2 | 0);
// 如果生成新方块时就碰撞,则游戏结束
if (collide(arena, player)) {
gameOver();
}
}
// 玩家旋转逻辑
function playerRotate(dir) {
const pos = player.pos.x;
let offset = 1;
rotate(player.matrix, dir);
// 墙踢(Wall kick)简单实现:防止旋转出墙外
while (collide(arena, player)) {
player.pos.x += offset;
offset = -(offset + (offset > 0 ? 1 : -1));
if (offset > player.matrix[0].length) {
rotate(player.matrix, -dir);
player.pos.x = pos;
return;
}
}
}
// 游戏结束处理
function gameOver() {
isGameOver = true;
finalScoreElement.innerText = player.score;
gameOverDiv.style.display = 'block';
}
// 重置游戏
function resetGame() {
arena.forEach(row => row.fill(0));
player.score = 0;
updateScore();
gameOverDiv.style.display = 'none';
isGameOver = false;
playerReset();
update();
}
// 更新分数显示
function updateScore() {
scoreElement.innerText = player.score;
}
// 游戏主循环变量
let dropCounter = 0;
let dropInterval = 1000; // 下落间隔(毫秒)
let lastTime = 0;
let isGameOver = false;
// 游戏主循环
function update(time = 0) {
if (isGameOver) return;
const deltaTime = time - lastTime;
lastTime = time;
dropCounter += deltaTime;
if (dropCounter > dropInterval) {
playerDrop();
}
draw();
requestAnimationFrame(update);
}
// 游戏区域 (12宽 x 20高)
const arena = createMatrix(12, 20);
// 玩家状态
const player = {
pos: {x: 0, y: 0},
matrix: null,
score: 0,
};
// 键盘监听
document.addEventListener('keydown', event => {
if (isGameOver) return;
if (event.keyCode === 37) { // Left
playerMove(-1);
} else if (event.keyCode === 39) { // Right
playerMove(1);
} else if (event.keyCode === 40) { // Down
playerDrop();
} else if (event.keyCode === 38) { // Up (Rotate)
playerRotate(1);
}
});
// 启动游戏
playerReset();
updateScore();
update();
</script>
</body>
</html>
index.html
md
README.md
index.html