<!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;
touch-action: manipulation;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
color: white;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
overflow-x: hidden;
}
.header {
text-align: center;
margin: 10px 0 20px;
width: 100%;
}
h1 {
font-size: 2.2rem;
margin-bottom: 5px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.game-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
max-width: 1000px;
width: 100%;
}
.game-board {
flex: 0 0 auto;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 10px;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.5);
padding: 10px;
position: relative;
}
canvas {
display: block;
background-color: #111;
border-radius: 5px;
}
.side-panel {
flex: 1;
min-width: 200px;
display: flex;
flex-direction: column;
gap: 20px;
}
.panel-section {
background-color: rgba(0, 0, 0, 0.7);
border-radius: 10px;
padding: 15px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
}
h2 {
font-size: 1.5rem;
margin-bottom: 10px;
text-align: center;
color: #fdbb2d;
}
.next-piece {
display: flex;
justify-content: center;
align-items: center;
height: 120px;
}
#nextCanvas {
background-color: #111;
border-radius: 5px;
}
.score-display {
text-align: center;
font-size: 1.8rem;
font-weight: bold;
margin: 10px 0;
}
.controls {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: auto auto auto;
gap: 10px;
margin-top: 20px;
}
.control-btn {
background: linear-gradient(135deg, #3498db, #2980b9);
border: none;
border-radius: 10px;
color: white;
font-size: 1.2rem;
font-weight: bold;
padding: 12px 5px;
cursor: pointer;
box-shadow: 0 4px 0 #1f5b85, 0 6px 8px rgba(0, 0, 0, 0.3);
transition: all 0.1s;
user-select: none;
display: flex;
justify-content: center;
align-items: center;
min-height: 60px;
}
.control-btn:active {
transform: translateY(4px);
box-shadow: 0 0 0 #1f5b85, 0 2px 4px rgba(0, 0, 0, 0.3);
}
.rotate-btn {
background: linear-gradient(135deg, #9b59b6, #8e44ad);
grid-column: 2;
}
.down-btn {
background: linear-gradient(135deg, #e74c3c, #c0392b);
grid-column: 2;
grid-row: 2;
}
.left-btn {
background: linear-gradient(135deg, #2ecc71, #27ae60);
grid-column: 1;
grid-row: 2;
}
.right-btn {
background: linear-gradient(135deg, #f39c12, #d35400);
grid-column: 3;
grid-row: 2;
}
.pause-btn {
background: linear-gradient(135deg, #34495e, #2c3e50);
grid-column: 1;
grid-row: 3;
}
.restart-btn {
background: linear-gradient(135deg, #e67e22, #d35400);
grid-column: 3;
grid-row: 3;
}
.game-info {
display: flex;
flex-direction: column;
gap: 10px;
}
.info-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.level-display {
font-size: 1.5rem;
font-weight: bold;
text-align: center;
margin: 10px 0;
color: #fdbb2d;
}
.instructions {
margin-top: 20px;
text-align: center;
font-size: 0.9rem;
opacity: 0.8;
}
.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;
border-radius: 10px;
z-index: 10;
}
.game-over h2 {
font-size: 2.5rem;
color: #e74c3c;
margin-bottom: 20px;
}
.start-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.85);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 10px;
z-index: 10;
}
.start-btn {
background: linear-gradient(135deg, #2ecc71, #27ae60);
border: none;
border-radius: 10px;
color: white;
font-size: 1.5rem;
font-weight: bold;
padding: 15px 30px;
margin-top: 20px;
cursor: pointer;
box-shadow: 0 4px 0 #1e8449, 0 6px 8px rgba(0, 0, 0, 0.3);
transition: all 0.1s;
}
.start-btn:active {
transform: translateY(4px);
box-shadow: 0 0 0 #1e8449, 0 2px 4px rgba(0, 0, 0, 0.3);
}
@media (max-width: 768px) {
.game-container {
flex-direction: column;
align-items: center;
}
.side-panel {
width: 100%;
max-width: 400px;
}
.controls {
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: auto auto auto;
}
.control-btn {
font-size: 1rem;
padding: 10px 5px;
min-height: 50px;
}
h1 {
font-size: 1.8rem;
}
}
@media (max-width: 480px) {
.game-board {
padding: 5px;
}
canvas {
width: 100% !important;
height: auto !important;
}
.control-btn {
font-size: 0.9rem;
padding: 8px 2px;
min-height: 45px;
}
h1 {
font-size: 1.5rem;
}
h2 {
font-size: 1.3rem;
}
.score-display {
font-size: 1.5rem;
}
}
</style>
</head>
<body>
<div class="header">
<h1>俄罗斯方块</h1>
<p>触屏版 - 自适应手机和平板</p>
</div>
<div class="game-container">
<div class="game-board">
<canvas id="tetris" width="300" height="600"></canvas>
<div class="game-over" id="gameOver">
<h2>游戏结束!</h2>
<div class="score-display">得分: <span id="finalScore">0</span></div>
<button class="start-btn" id="restartBtn">重新开始</button>
</div>
<div class="start-screen" id="startScreen">
<h2>俄罗斯方块</h2>
<p>准备好开始游戏了吗?</p>
<button class="start-btn" id="startBtn">开始游戏</button>
</div>
</div>
<div class="side-panel">
<div class="panel-section">
<h2>下一个方块</h2>
<div class="next-piece">
<canvas id="nextCanvas" width="120" height="120"></canvas>
</div>
</div>
<div class="panel-section">
<h2>游戏信息</h2>
<div class="score-display">
<span id="score">0</span>
</div>
<div class="level-display">
等级: <span id="level">1</span>
</div>
<div class="game-info">
<div class="info-item">
<span>已消除行数:</span>
<span id="lines">0</span>
</div>
<div class="info-item">
<span>最高分:</span>
<span id="highScore">0</span>
</div>
</div>
</div>
<div class="panel-section">
<h2>游戏控制</h2>
<div class="controls">
<button class="control-btn rotate-btn">旋转</button>
<button class="control-btn left-btn">左移</button>
<button class="control-btn right-btn">右移</button>
<button class="control-btn down-btn">下移</button>
<button class="control-btn pause-btn">暂停</button>
<button class="control-btn restart-btn">重新开始</button>
</div>
<div class="instructions">
<p>点击按钮或使用键盘方向键控制</p>
</div>
</div>
</div>
</div>
<script>
// 游戏常量
const COLS = 10;
const ROWS = 20;
const BLOCK_SIZE = 30;
const COLORS = [
'cyan', 'blue', 'orange', 'yellow', 'green', 'purple', 'red'
];
// 方块形状定义
const SHAPES = [
// I
[
[0, 0, 0, 0],
[1, 1, 1, 1],
[0, 0, 0, 0],
[0, 0, 0, 0]
],
// J
[
[2, 0, 0],
[2, 2, 2],
[0, 0, 0]
],
// L
[
[0, 0, 3],
[3, 3, 3],
[0, 0, 0]
],
// O
[
[4, 4],
[4, 4]
],
// S
[
[0, 5, 5],
[5, 5, 0],
[0, 0, 0]
],
// T
[
[0, 6, 0],
[6, 6, 6],
[0, 0, 0]
],
// Z
[
[7, 7, 0],
[0, 7, 7],
[0, 0, 0]
]
];
// 游戏变量
let canvas, ctx, nextCanvas, nextCtx;
let board = [];
let currentPiece, nextPiece;
let score = 0;
let level = 1;
let lines = 0;
let highScore = localStorage.getItem('tetrisHighScore') || 0;
let gameOver = false;
let isPaused = false;
let dropTime = 0;
let dropInterval = 1000; // 初始下落速度(毫秒)
// 初始化游戏
function init() {
canvas = document.getElementById('tetris');
ctx = canvas.getContext('2d');
nextCanvas = document.getElementById('nextCanvas');
nextCtx = nextCanvas.getContext('2d');
// 设置画布尺寸
canvas.width = COLS * BLOCK_SIZE;
canvas.height = ROWS * BLOCK_SIZE;
// 初始化游戏板
createBoard();
// 创建第一个方块
currentPiece = createPiece();
nextPiece = createPiece();
// 更新显示
updateHighScore();
// 游戏循环
requestAnimationFrame(gameLoop);
// 添加事件监听器
setupEventListeners();
}
// 创建游戏板
function createBoard() {
board = [];
for (let y = 0; y < ROWS; y++) {
board[y] = [];
for (let x = 0; x < COLS; x++) {
board[y][x] = 0;
}
}
}
// 创建随机方块
function createPiece() {
const shapeId = Math.floor(Math.random() * SHAPES.length);
return {
shape: SHAPES[shapeId],
color: COLORS[shapeId],
x: Math.floor(COLS / 2) - Math.floor(SHAPES[shapeId][0].length / 2),
y: 0
};
}
// 绘制游戏板
function drawBoard() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制已固定的方块
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] - 1]);
}
}
}
// 绘制当前下落的方块
if (currentPiece) {
drawPiece(currentPiece);
}
// 绘制网格线
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
ctx.lineWidth = 0.5;
for (let x = 0; x <= COLS; x++) {
ctx.beginPath();
ctx.moveTo(x * BLOCK_SIZE, 0);
ctx.lineTo(x * BLOCK_SIZE, ROWS * BLOCK_SIZE);
ctx.stroke();
}
for (let y = 0; y <= ROWS; y++) {
ctx.beginPath();
ctx.moveTo(0, y * BLOCK_SIZE);
ctx.lineTo(COLS * BLOCK_SIZE, y * BLOCK_SIZE);
ctx.stroke();
}
}
// 绘制方块
function drawPiece(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]) {
drawBlock(ctx, piece.x + x, piece.y + y, piece.color);
}
}
}
}
// 绘制单个方块
function drawBlock(context, x, y, color) {
context.fillStyle = color;
context.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
context.strokeStyle = 'rgba(255, 255, 255, 0.5)';
context.lineWidth = 1;
context.strokeRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
// 添加3D效果
context.fillStyle = 'rgba(255, 255, 255, 0.2)';
context.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, 3);
context.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, 3, BLOCK_SIZE);
context.fillStyle = 'rgba(0, 0, 0, 0.2)';
context.fillRect(x * BLOCK_SIZE + BLOCK_SIZE - 3, y * BLOCK_SIZE, 3, BLOCK_SIZE);
context.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE + BLOCK_SIZE - 3, BLOCK_SIZE, 3);
}
// 绘制下一个方块预览
function drawNextPiece() {
nextCtx.clearRect(0, 0, nextCanvas.width, nextCanvas.height);
// 计算居中位置
const offsetX = (nextCanvas.width - nextPiece.shape[0].length * BLOCK_SIZE) / 2;
const offsetY = (nextCanvas.height - nextPiece.shape.length * BLOCK_SIZE) / 2;
for (let y = 0; y < nextPiece.shape.length; y++) {
for (let x = 0; x < nextPiece.shape[y].length; x++) {
if (nextPiece.shape[y][x]) {
nextCtx.fillStyle = nextPiece.color;
nextCtx.fillRect(
offsetX + x * BLOCK_SIZE,
offsetY + y * BLOCK_SIZE,
BLOCK_SIZE,
BLOCK_SIZE
);
nextCtx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
nextCtx.lineWidth = 1;
nextCtx.strokeRect(
offsetX + x * BLOCK_SIZE,
offsetY + y * BLOCK_SIZE,
BLOCK_SIZE,
BLOCK_SIZE
);
}
}
}
}
// 移动方块
function movePiece(dx, dy) {
if (isPaused || gameOver) return;
currentPiece.x += dx;
currentPiece.y += dy;
if (isCollision()) {
currentPiece.x -= dx;
currentPiece.y -= dy;
// 如果是向下移动发生碰撞,则固定方块
if (dy > 0) {
lockPiece();
removeLines();
currentPiece = nextPiece;
nextPiece = createPiece();
// 检查游戏是否结束
if (isCollision()) {
gameOver = true;
document.getElementById('gameOver').style.display = 'flex';
document.getElementById('finalScore').textContent = score;
// 更新最高分
if (score > highScore) {
highScore = score;
localStorage.setItem('tetrisHighScore', highScore);
updateHighScore();
}
}
}
return false;
}
return true;
}
// 旋转方块
function rotatePiece() {
if (isPaused || gameOver) return;
const originalShape = currentPiece.shape;
const rows = originalShape.length;
const cols = originalShape[0].length;
// 创建新的旋转后的形状
const rotated = [];
for (let x = 0; x < cols; x++) {
rotated[x] = [];
for (let y = 0; y < rows; y++) {
rotated[x][y] = originalShape[rows - 1 - y][x];
}
}
currentPiece.shape = rotated;
// 如果旋转后发生碰撞,则恢复原来的形状
if (isCollision()) {
currentPiece.shape = originalShape;
}
}
// 检查碰撞
function isCollision() {
for (let y = 0; y < currentPiece.shape.length; y++) {
for (let x = 0; x < currentPiece.shape[y].length; x++) {
if (currentPiece.shape[y][x]) {
const newX = currentPiece.x + x;
const newY = currentPiece.y + y;
if (
newX < 0 ||
newX >= COLS ||
newY >= ROWS ||
(newY >= 0 && board[newY][newX])
) {
return true;
}
}
}
}
return false;
}
// 固定方块到游戏板
function lockPiece() {
for (let y = 0; y < currentPiece.shape.length; y++) {
for (let x = 0; x < currentPiece.shape[y].length; x++) {
if (currentPiece.shape[y][x]) {
const boardY = currentPiece.y + y;
if (boardY >= 0) { // 确保不会在顶部之外固定方块
board[boardY][currentPiece.x + x] = currentPiece.shape[y][x];
}
}
}
}
}
// 移除完整的行
function removeLines() {
let linesRemoved = 0;
for (let y = ROWS - 1; y >= 0; y--) {
let isLineComplete = true;
for (let x = 0; x < COLS; x++) {
if (!board[y][x]) {
isLineComplete = false;
break;
}
}
if (isLineComplete) {
// 移除该行
for (let yy = y; yy > 0; yy--) {
for (let x = 0; x < COLS; x++) {
board[yy][x] = board[yy - 1][x];
}
}
// 清空顶行
for (let x = 0; x < COLS; x++) {
board[0][x] = 0;
}
linesRemoved++;
y++; // 重新检查当前行,因为上面的行已经下移
}
}
if (linesRemoved > 0) {
// 更新分数
updateScore(linesRemoved);
}
}
// 更新分数
function updateScore(linesRemoved) {
// 根据一次消除的行数计算得分
const points = [0, 40, 100, 300, 1200];
score += points[linesRemoved] * level;
// 更新已消除行数
lines += linesRemoved;
// 每消除10行提升一个等级
level = Math.floor(lines / 10) + 1;
// 根据等级调整下落速度
dropInterval = Math.max(100, 1000 - (level - 1) * 100);
// 更新显示
document.getElementById('score').textContent = score;
document.getElementById('level').textContent = level;
document.getElementById('lines').textContent = lines;
}
// 更新最高分显示
function updateHighScore() {
document.getElementById('highScore').textContent = highScore;
}
// 游戏主循环
function gameLoop(timestamp) {
if (!isPaused && !gameOver) {
// 控制方块下落速度
if (timestamp - dropTime > dropInterval) {
movePiece(0, 1);
dropTime = timestamp;
}
drawBoard();
drawNextPiece();
}
requestAnimationFrame(gameLoop);
}
// 设置事件监听器
function setupEventListeners() {
// 键盘控制
document.addEventListener('keydown', (e) => {
if (gameOver) return;
switch(e.key) {
case 'ArrowLeft':
movePiece(-1, 0);
break;
case 'ArrowRight':
movePiece(1, 0);
break;
case 'ArrowDown':
movePiece(0, 1);
break;
case 'ArrowUp':
rotatePiece();
break;
case ' ':
// 硬降(直接落到底部)
while(movePiece(0, 1)) {}
break;
case 'p':
togglePause();
break;
}
});
// 按钮控制
document.querySelector('.left-btn').addEventListener('click', () => movePiece(-1, 0));
document.querySelector('.right-btn').addEventListener('click', () => movePiece(1, 0));
document.querySelector('.down-btn').addEventListener('click', () => movePiece(0, 1));
document.querySelector('.rotate-btn').addEventListener('click', () => rotatePiece());
document.querySelector('.pause-btn').addEventListener('click', () => togglePause());
document.querySelector('.restart-btn').addEventListener('click', () => resetGame());
// 开始按钮
document.getElementById('startBtn').addEventListener('click', () => {
document.getElementById('startScreen').style.display = 'none';
resetGame();
});
// 重新开始按钮
document.getElementById('restartBtn').addEventListener('click', () => {
document.getElementById('gameOver').style.display = 'none';
resetGame();
});
}
// 切换暂停状态
function togglePause() {
isPaused = !isPaused;
document.querySelector('.pause-btn').textContent = isPaused ? '继续' : '暂停';
}
// 重置游戏
function resetGame() {
createBoard();
currentPiece = createPiece();
nextPiece = createPiece();
score = 0;
level = 1;
lines = 0;
gameOver = false;
isPaused = false;
dropInterval = 1000;
document.getElementById('score').textContent = score;
document.getElementById('level').textContent = level;
document.getElementById('lines').textContent = lines;
document.querySelector('.pause-btn').textContent = '暂停';
}
// 页面加载完成后初始化游戏
window.addEventListener('load', init);
</script>
</body>
</html>
index.html
index.html