HTML 俄罗斯方块游戏
2026-01-25 19:32:43
发布于:浙江
以下内容为AI生成,作者转载
HTML版
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>俄罗斯方块</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
font-family: 'Microsoft YaHei', 'Arial', sans-serif;
}
body {
background-color: #141428;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
overflow: hidden;
color: #dcdcff;
}
#game-container {
position: relative;
width: 100%;
max-width: 850px;
display: flex;
flex-direction: column;
align-items: center;
}
#title {
font-size: 48px;
color: #dcdcff;
text-shadow: 0 0 10px rgba(220, 220, 255, 0.5);
margin-bottom: 20px;
letter-spacing: 2px;
}
#game-area {
display: flex;
gap: 30px;
justify-content: center;
align-items: flex-start;
margin-bottom: 20px;
}
#game-board {
position: relative;
background-color: #28283c;
border: 3px solid #32323c;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
}
#sidebar {
width: 200px;
display: flex;
flex-direction: column;
gap: 20px;
}
.panel {
background-color: #28283c;
border: 2px solid #32323c;
border-radius: 8px;
padding: 15px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
.panel h3 {
color: #dcdcff;
margin-bottom: 10px;
font-size: 24px;
text-align: center;
border-bottom: 1px solid #404050;
padding-bottom: 5px;
}
#next-block-canvas {
background-color: #1a1a2e;
border: 2px solid #404050;
display: block;
margin: 0 auto 10px;
}
#stats {
font-size: 20px;
}
.stat-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
padding: 5px 0;
border-bottom: 1px solid #32323c;
}
.stat-label {
color: #a0a0c0;
}
.stat-value {
color: #ffffff;
font-weight: bold;
}
#controls {
font-size: 16px;
}
.control-item {
margin-bottom: 8px;
padding: 5px 0;
border-bottom: 1px dashed #404050;
}
.key {
display: inline-block;
background-color: #32323c;
color: #ffffff;
padding: 2px 8px;
border-radius: 4px;
margin-right: 5px;
font-family: monospace;
border: 1px solid #505060;
}
#controls-footer {
font-size: 14px;
color: #8080a0;
text-align: center;
margin-top: 15px;
}
#game-over {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.85);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
}
#game-over h2 {
font-size: 60px;
color: #ff3232;
text-shadow: 0 0 15px rgba(255, 50, 50, 0.7);
margin-bottom: 20px;
}
#final-score {
font-size: 36px;
color: #ffffff;
margin-bottom: 30px;
}
#restart-button {
background-color: #ffaa00;
color: #000000;
border: none;
padding: 15px 40px;
font-size: 24px;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
transition: all 0.2s;
box-shadow: 0 0 15px rgba(255, 170, 0, 0.5);
}
#restart-button:hover {
background-color: #ffcc44;
transform: scale(1.05);
}
#pause-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 5;
}
#pause-text {
font-size: 60px;
color: #ffff00;
text-shadow: 0 0 15px rgba(255, 255, 0, 0.7);
margin-bottom: 30px;
}
#instructions {
text-align: center;
color: #a0a0c0;
font-size: 16px;
max-width: 800px;
line-height: 1.6;
margin-top: 20px;
}
@media (max-width: 768px) {
#game-area {
flex-direction: column;
align-items: center;
}
#sidebar {
width: 100%;
max-width: 400px;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}
.panel {
flex: 1;
min-width: 180px;
}
#title {
font-size: 36px;
}
}
</style>
</head>
<body>
<div id="game-container">
<h1 id="title">俄罗斯方块</h1>
<div id="game-area">
<canvas id="game-board" width="300" height="600"></canvas>
<div id="sidebar">
<div class="panel">
<h3>下一个</h3>
<canvas id="next-block-canvas" width="150" height="150"></canvas>
</div>
<div class="panel" id="stats">
<h3>游戏状态</h3>
<div class="stat-row">
<span class="stat-label">分数:</span>
<span id="score-value" class="stat-value">0</span>
</div>
<div class="stat-row">
<span class="stat-label">等级:</span>
<span id="level-value" class="stat-value">1</span>
</div>
<div class="stat-row">
<span class="stat-label">消除行:</span>
<span id="lines-value" class="stat-value">0</span>
</div>
</div>
<div class="panel" id="controls">
<h3>操作说明</h3>
<div class="control-item">
<span class="key">← →</span>
<span class="key">A D</span> 左右移动
</div>
<div class="control-item">
<span class="key">↑</span>
<span class="key">W</span> 旋转
</div>
<div class="control-item">
<span class="key">↓</span>
<span class="key">S</span> 加速下落
</div>
<div class="control-item">
<span class="key">空格</span> 直接落下
</div>
<div class="control-item">
<span class="key">P</span> 暂停游戏
</div>
<div class="control-item">
<span class="key">R</span> 重新开始
</div>
<div class="control-item">
<span class="key">F11</span> 全屏切换
</div>
<div id="controls-footer">
按任意方向键开始游戏
</div>
</div>
</div>
</div>
<div id="game-over">
<h2>游戏结束!</h2>
<div id="final-score">最终分数: <span id="final-score-value">0</span></div>
<button id="restart-button">按 R 键重新开始</button>
</div>
<div id="pause-overlay">
<div id="pause-text">游戏暂停</div>
<div style="color: white; font-size: 24px;">按 P 键继续</div>
</div>
<div id="instructions">
俄罗斯方块游戏 | 使用方向键或WASD控制方块移动和旋转 | 空格键快速落下方块
</div>
</div>
<script>
// 游戏常量
const GRID_WIDTH = 10;
const GRID_HEIGHT = 20;
const BLOCK_SIZE = 30;
const BOARD_WIDTH = GRID_WIDTH * BLOCK_SIZE;
const BOARD_HEIGHT = GRID_HEIGHT * BLOCK_SIZE;
// 颜色定义
const COLORS = {
BLACK: '#000000',
WHITE: '#ffffff',
RED: '#ff3232',
GREEN: '#32ff32',
BLUE: '#3264ff',
CYAN: '#00ffff',
MAGENTA: '#ff00ff',
YELLOW: '#ffff00',
ORANGE: '#ffa500',
GRAY: '#808080',
DARK_GRAY: '#32323c',
BACKGROUND: '#141428',
GRID: '#28283c',
TEXT: '#dcdcff'
};
// 方块形状定义 (7种经典形状)
const SHAPES = [
// 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 SHAPE_COLORS = [
COLORS.CYAN, // I
COLORS.YELLOW, // O
COLORS.MAGENTA, // T
COLORS.GREEN, // S
COLORS.RED, // Z
COLORS.BLUE, // J
COLORS.ORANGE // L
];
// 游戏变量
let gameBoard = [];
let currentPiece = null;
let nextPiece = null;
let score = 0;
let level = 1;
let linesCleared = 0;
let gameOver = false;
let paused = false;
let fallSpeed = 500; // 初始下落速度(毫秒)
let lastFallTime = 0;
// 闪烁效果相关变量 - 修改了闪烁速度
let blinkingLines = [];
let blinkTimer = 0;
let blinkInterval = 150; // 闪烁间隔改为150毫秒(原来是100毫秒)
let blinkVisible = true;
let blinkCount = 0;
const maxBlinks = 6; // 闪烁3次(每次闪烁包含显示和隐藏)
// 获取DOM元素
const gameBoardCanvas = document.getElementById('game-board');
const gameBoardCtx = gameBoardCanvas.getContext('2d');
const nextBlockCanvas = document.getElementById('next-block-canvas');
const nextBlockCtx = nextBlockCanvas.getContext('2d');
const scoreValue = document.getElementById('score-value');
const levelValue = document.getElementById('level-value');
const linesValue = document.getElementById('lines-value');
const gameOverScreen = document.getElementById('game-over');
const finalScoreValue = document.getElementById('final-score-value');
const restartButton = document.getElementById('restart-button');
const pauseOverlay = document.getElementById('pause-overlay');
const title = document.getElementById('title');
// 初始化游戏板
function initBoard() {
gameBoard = [];
for (let y = 0; y < GRID_HEIGHT; y++) {
gameBoard[y] = [];
for (let x = 0; x < GRID_WIDTH; x++) {
gameBoard[y][x] = 0;
}
}
}
// 方块类
class Tetromino {
constructor(x, y, shapeIdx) {
this.x = x;
this.y = y;
this.shapeIdx = shapeIdx;
this.shape = SHAPES[shapeIdx];
this.color = SHAPE_COLORS[shapeIdx];
this.rotation = 0;
}
// 获取旋转后的形状
getRotatedShape() {
let shape = this.shape;
for (let i = 0; i < this.rotation % 4; i++) {
shape = this.rotateMatrix(shape);
}
return shape;
}
// 旋转矩阵
rotateMatrix(matrix) {
const rows = matrix.length;
const cols = matrix[0].length;
const rotated = Array(cols).fill().map(() => Array(rows).fill(0));
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
rotated[c][rows - 1 - r] = matrix[r][c];
}
}
return rotated;
}
// 获取方块的所有位置
getPositions() {
const positions = [];
const shape = this.getRotatedShape();
for (let y = 0; y < shape.length; y++) {
for (let x = 0; x < shape[y].length; x++) {
if (shape[y][x]) {
positions.push({x: this.x + x, y: this.y + y});
}
}
}
return positions;
}
}
// 创建新方块
function createNewPiece() {
const shapeIdx = Math.floor(Math.random() * SHAPES.length);
const shape = SHAPES[shapeIdx];
const startX = Math.floor(GRID_WIDTH / 2) - Math.floor(shape[0].length / 2);
return new Tetromino(startX, 0, shapeIdx);
}
// 检查移动是否有效
function isValidMove(piece, xOffset = 0, yOffset = 0, rotation = 0) {
const testPiece = new Tetromino(piece.x + xOffset, piece.y + yOffset, piece.shapeIdx);
testPiece.rotation = (piece.rotation + rotation) % 4;
const positions = testPiece.getPositions();
for (const pos of positions) {
// 检查是否超出边界
if (pos.x < 0 || pos.x >= GRID_WIDTH || pos.y >= GRID_HEIGHT) {
return false;
}
// 检查是否与已有方块重叠
if (pos.y >= 0 && gameBoard[pos.y][pos.x]) {
return false;
}
}
return true;
}
// 锁定方块到游戏板
function lockPiece() {
const positions = currentPiece.getPositions();
for (const pos of positions) {
if (pos.y >= 0) {
gameBoard[pos.y][pos.x] = currentPiece.color;
}
}
// 检查是否有完整的行
checkLines();
// 生成新方块
currentPiece = nextPiece;
nextPiece = createNewPiece();
// 检查游戏是否结束
if (!isValidMove(currentPiece)) {
gameOver = true;
finalScoreValue.textContent = score;
gameOverScreen.style.display = 'flex';
}
}
// 检查并标记要消除的行
function checkLines() {
const linesToClear = [];
for (let y = 0; y < GRID_HEIGHT; y++) {
let full = true;
for (let x = 0; x < GRID_WIDTH; x++) {
if (!gameBoard[y][x]) {
full = false;
break;
}
}
if (full) {
linesToClear.push(y);
}
}
// 如果有要消除的行,启动闪烁效果
if (linesToClear.length > 0) {
blinkingLines = linesToClear;
blinkTimer = 0;
blinkCount = 0;
blinkVisible = true;
// 注意:我们不在这里立即清除行,而是在闪烁完成后清除
// 清除行的逻辑现在在updateBlink函数中
}
}
// 清除行并更新分数
function clearLines(lines) {
// 按从下到上的顺序消除行
lines.sort((a, b) => b - a);
for (const line of lines) {
// 移除该行
gameBoard.splice(line, 1);
// 在顶部添加新行
gameBoard.unshift(new Array(GRID_WIDTH).fill(0));
}
// 更新分数和等级
updateScore(lines.length);
}
// 更新分数和等级
function updateScore(linesClearedCount) {
// 计分规则:1行=100分,2行=200分,3行=500分,4行=1000分
const points = [100, 200, 500, 1000];
score += points[Math.min(linesClearedCount - 1, 3)];
linesCleared += linesClearedCount;
level = Math.floor(linesCleared / 10) + 1;
// 更新显示
scoreValue.textContent = score;
levelValue.textContent = level;
linesValue.textContent = linesCleared;
// 提高下落速度(最高等级15)
fallSpeed = Math.max(50, 500 - (level - 1) * 30);
}
// 更新闪烁效果
function updateBlink(deltaTime) {
if (blinkingLines.length > 0) {
blinkTimer += deltaTime;
// 闪烁间隔到了,切换可见状态
if (blinkTimer >= blinkInterval) {
blinkTimer = 0;
blinkVisible = !blinkVisible;
blinkCount++;
// 如果闪烁次数达到最大值,清除行
if (blinkCount >= maxBlinks) {
const linesToClear = [...blinkingLines];
clearLines(linesToClear);
blinkingLines = [];
blinkCount = 0;
}
}
}
}
// 绘制游戏板
function drawBoard() {
// 绘制背景
gameBoardCtx.fillStyle = COLORS.GRID;
gameBoardCtx.fillRect(0, 0, BOARD_WIDTH, BOARD_HEIGHT);
// 绘制网格线
gameBoardCtx.strokeStyle = COLORS.DARK_GRAY;
gameBoardCtx.lineWidth = 1;
// 垂直线
for (let x = 0; x <= GRID_WIDTH; x++) {
gameBoardCtx.beginPath();
gameBoardCtx.moveTo(x * BLOCK_SIZE, 0);
gameBoardCtx.lineTo(x * BLOCK_SIZE, BOARD_HEIGHT);
gameBoardCtx.stroke();
}
// 水平线
for (let y = 0; y <= GRID_HEIGHT; y++) {
gameBoardCtx.beginPath();
gameBoardCtx.moveTo(0, y * BLOCK_SIZE);
gameBoardCtx.lineTo(BOARD_WIDTH, y * BLOCK_SIZE);
gameBoardCtx.stroke();
}
// 绘制已锁定的方块(排除正在闪烁的行)
for (let y = 0; y < GRID_HEIGHT; y++) {
// 如果这一行正在闪烁且当前不可见,跳过绘制
if (blinkingLines.includes(y) && !blinkVisible) {
continue;
}
for (let x = 0; x < GRID_WIDTH; x++) {
if (gameBoard[y][x]) {
drawBlock(x, y, gameBoard[y][x]);
}
}
}
// 绘制当前下落的方块
if (!gameOver && currentPiece) {
const positions = currentPiece.getPositions();
for (const pos of positions) {
if (pos.y >= 0) {
drawBlock(pos.x, pos.y, currentPiece.color);
}
}
}
// 绘制边框
gameBoardCtx.strokeStyle = COLORS.DARK_GRAY;
gameBoardCtx.lineWidth = 3;
gameBoardCtx.strokeRect(0, 0, BOARD_WIDTH, BOARD_HEIGHT);
}
// 绘制单个方块
function drawBlock(x, y, color) {
const px = x * BLOCK_SIZE;
const py = y * BLOCK_SIZE;
// 绘制方块主体
gameBoardCtx.fillStyle = color;
gameBoardCtx.fillRect(px, py, BLOCK_SIZE, BLOCK_SIZE);
// 绘制方块边框
gameBoardCtx.strokeStyle = COLORS.BLACK;
gameBoardCtx.lineWidth = 1;
gameBoardCtx.strokeRect(px, py, BLOCK_SIZE, BLOCK_SIZE);
// 绘制高光效果
gameBoardCtx.strokeStyle = COLORS.WHITE;
gameBoardCtx.lineWidth = 2;
gameBoardCtx.beginPath();
gameBoardCtx.moveTo(px + 2, py + 2);
gameBoardCtx.lineTo(px + BLOCK_SIZE - 2, py + 2);
gameBoardCtx.lineTo(px + BLOCK_SIZE - 2, py + BLOCK_SIZE - 2);
gameBoardCtx.stroke();
}
// 绘制下一个方块预览
function drawNextBlock() {
nextBlockCtx.clearRect(0, 0, nextBlockCanvas.width, nextBlockCanvas.height);
// 绘制背景
nextBlockCtx.fillStyle = COLORS.BACKGROUND;
nextBlockCtx.fillRect(0, 0, nextBlockCanvas.width, nextBlockCanvas.height);
if (!nextPiece) return;
const shape = nextPiece.shape;
const blockSize = 25;
const offsetX = (nextBlockCanvas.width - shape[0].length * blockSize) / 2;
const offsetY = (nextBlockCanvas.height - shape.length * blockSize) / 2;
for (let y = 0; y < shape.length; y++) {
for (let x = 0; x < shape[y].length; x++) {
if (shape[y][x]) {
const px = offsetX + x * blockSize;
const py = offsetY + y * blockSize;
// 绘制方块主体
nextBlockCtx.fillStyle = nextPiece.color;
nextBlockCtx.fillRect(px, py, blockSize, blockSize);
// 绘制方块边框
nextBlockCtx.strokeStyle = COLORS.BLACK;
nextBlockCtx.lineWidth = 1;
nextBlockCtx.strokeRect(px, py, blockSize, blockSize);
// 绘制高光效果
nextBlockCtx.strokeStyle = COLORS.WHITE;
nextBlockCtx.lineWidth = 1;
nextBlockCtx.beginPath();
nextBlockCtx.moveTo(px + 2, py + 2);
nextBlockCtx.lineTo(px + blockSize - 2, py + 2);
nextBlockCtx.lineTo(px + blockSize - 2, py + blockSize - 2);
nextBlockCtx.stroke();
}
}
}
}
// 游戏主循环
let lastTimestamp = 0;
function gameLoop(timestamp) {
const deltaTime = timestamp - lastTimestamp;
lastTimestamp = timestamp;
if (!paused && !gameOver) {
// 方块自动下落
if (timestamp - lastFallTime > fallSpeed) {
if (isValidMove(currentPiece, 0, 1)) {
currentPiece.y++;
} else {
lockPiece();
}
lastFallTime = timestamp;
}
// 更新闪烁效果
updateBlink(deltaTime);
}
// 绘制游戏
drawBoard();
drawNextBlock();
// 继续游戏循环
requestAnimationFrame(gameLoop);
}
// 初始化游戏
function initGame() {
initBoard();
currentPiece = createNewPiece();
nextPiece = createNewPiece();
score = 0;
level = 1;
linesCleared = 0;
gameOver = false;
paused = false;
fallSpeed = 500;
blinkingLines = [];
blinkCount = 0;
// 更新显示
scoreValue.textContent = score;
levelValue.textContent = level;
linesValue.textContent = linesCleared;
// 隐藏游戏结束画面
gameOverScreen.style.display = 'none';
pauseOverlay.style.display = 'none';
// 绘制初始状态
drawBoard();
drawNextBlock();
// 开始游戏循环
lastFallTime = performance.now();
lastTimestamp = performance.now();
requestAnimationFrame(gameLoop);
}
// 处理键盘输入
function handleKeyDown(e) {
// 防止默认行为(如页面滚动)
if ([37, 38, 39, 40, 32, 65, 68, 83, 87].includes(e.keyCode)) {
e.preventDefault();
}
// 重新开始游戏
if (e.key === 'r' || e.key === 'R') {
initGame();
return;
}
// 暂停/继续游戏
if (e.key === 'p' || e.key === 'P') {
paused = !paused;
pauseOverlay.style.display = paused ? 'flex' : 'none';
return;
}
// 全屏切换
if (e.key === 'F11') {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
return;
}
// 如果游戏结束或暂停,不处理其他按键
if (gameOver || paused || !currentPiece) return;
switch (e.key) {
case 'ArrowLeft':
case 'a':
case 'A':
if (isValidMove(currentPiece, -1, 0)) {
currentPiece.x--;
}
break;
case 'ArrowRight':
case 'd':
case 'D':
if (isValidMove(currentPiece, 1, 0)) {
currentPiece.x++;
}
break;
case 'ArrowDown':
case 's':
case 'S':
if (isValidMove(currentPiece, 0, 1)) {
currentPiece.y++;
}
break;
case 'ArrowUp':
case 'w':
case 'W':
if (isValidMove(currentPiece, 0, 0, 1)) {
currentPiece.rotation = (currentPiece.rotation + 1) % 4;
}
break;
case ' ':
// 硬降落 - 直接落到底部
while (isValidMove(currentPiece, 0, 1)) {
currentPiece.y++;
}
lockPiece();
break;
}
// 立即重绘
drawBoard();
}
// 添加事件监听器
document.addEventListener('keydown', handleKeyDown);
restartButton.addEventListener('click', initGame);
// 窗口大小变化时调整布局
window.addEventListener('resize', () => {
// 在移动设备上,调整游戏区域大小
if (window.innerWidth < 768) {
const scale = Math.min(
(window.innerWidth - 40) / 850,
1
);
gameBoardCanvas.style.transform = `scale(${scale})`;
nextBlockCanvas.style.transform = `scale(${scale})`;
} else {
gameBoardCanvas.style.transform = 'scale(1)';
nextBlockCanvas.style.transform = 'scale(1)';
}
});
// 初始化游戏
initGame();
// 添加点击标题切换主题的小彩蛋
title.addEventListener('click', () => {
const colors = ['#dcdcff', '#ffcc00', '#00ffcc', '#ff66cc', '#66ff66'];
const currentColor = title.style.color || COLORS.TEXT;
const currentIndex = colors.indexOf(currentColor);
const nextIndex = (currentIndex + 1) % colors.length;
title.style.color = colors[nextIndex];
title.style.textShadow = `0 0 10px ${colors[nextIndex]}80`;
});
</script>
</body>
</html>
这个程序无需任何依赖。
或者,你可以用这个 Python版:
Python 俄罗斯方块
这里空空如也
















有帮助,赞一个