<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>俄罗斯方块</title>
<style>
body {
background: #202028;
color: #fff;
font-family: sans-serif;
display: flex;
justify-content: center;
}
.game-container {
position: relative;
margin-top: 20px;
}
canvas {
border: 2px solid #fff;
}
#game {
background: #000;
}
#preview {
background: #000;
margin-left: 20px;
}
.score-box {
position: absolute;
left: -120px;
top: 0;
width: 100px;
}
.controls {
position: absolute;
right: -120px;
top: 0;
width: 100px;
}
button {
background: #306;
color: #fff;
border: 0;
padding: 10px;
margin: 5px;
cursor: pointer;
}
.game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
background: rgba(0,0,0,0.8);
padding: 20px;
}
</style>
</head>
<body>
<div class="game-container">
<canvas id="game" width="300" height="600"></canvas>
<canvas id="preview" width="120" height="120"></canvas>
<div class="score-box">
<div>得分:<span id="score">0</span></div>
</div>
<div class="controls">
<button id="pause">暂停</button>
</div>
<div id="startScreen">
<button id="start">开始</button>
</div>
<div id="gameOver" class="game-over" style="display:none;">
<div>最终得分:<span id="finalScore">0</span></div>
<button id="restart">重新开始</button>
</div>
</div>
<script>
const SHAPES = [
[[1,1,1,1]], // I
[[1,1,1],[0,1,0]], // T
[[1,1,1],[1,0,0]], // L
[[1,1,1],[0,0,1]], // J
[[1,1],[1,1]], // O
[[1,1,0],[0,1,1]], // S
[[0,1,1],[1,1,0]] // Z
];
const COLORS = ['#0ff', '#f0f', '#ff0', '#0f0', '#f00', '#00f', '#f90'];
class Tetris {
constructor() {
this.canvas = document.getElementById('game');
this.ctx = this.canvas.getContext('2d');
this.preview = document.getElementById('preview').getContext('2d');
this.cellSize = 30;
this.rows = 20;
this.cols = 10;
this.board = Array(this.rows).fill().map(() => Array(this.cols).fill(0));
this.current = null;
this.next = null;
this.score = 0;
this.gameOver = false;
this.paused = false;
this.interval = null;
document.addEventListener('keydown', (e) => this.handleInput(e));
document.getElementById('start').addEventListener('click', () => this.start());
document.getElementById('restart').addEventListener('click', () => this.reset());
document.getElementById('pause').addEventListener('click', () => this.togglePause());
}
start() {
document.getElementById('startScreen').style.display = 'none';
this.reset();
this.interval = setInterval(() => this.update(), 1000);
}
reset() {
this.board = Array(this.rows).fill().map(() => Array(this.cols).fill(0));
this.score = 0;
this.gameOver = false;
this.paused = false;
document.getElementById('gameOver').style.display = 'none';
this.spawnNew();
this.draw();
}
spawnNew() {
this.current = this.next || this.createPiece();
this.next = this.createPiece();
if (this.checkCollision(this.current.x, this.current.y, this.current.shape)) {
this.gameOver = true;
clearInterval(this.interval);
document.getElementById('gameOver').style.display = 'block';
document.getElementById('finalScore').textContent = this.score;
}
}
createPiece() {
const type = Math.floor(Math.random() * SHAPES.length);
return {
x: Math.floor(this.cols/2) - Math.floor(SHAPES[type][0].length/2),
y: 0,
shape: SHAPES[type],
color: COLORS[type]
};
}
draw() {
// 清空画布
this.ctx.fillStyle = '#000';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// 绘制网格
this.ctx.strokeStyle = '#333';
for (let i = 0; i <= this.rows; i++) {
this.ctx.beginPath();
this.ctx.moveTo(0, i * this.cellSize);
this.ctx.lineTo(this.canvas.width, i * this.cellSize);
this.ctx.stroke();
}
for (let i = 0; i <= this.cols; i++) {
this.ctx.beginPath();
this.ctx.moveTo(i * this.cellSize, 0);
this.ctx.lineTo(i * this.cellSize, this.canvas.height);
this.ctx.stroke();
}
// 绘制当前方块
this.drawPiece(this.current, this.ctx);
// 绘制已落下的方块
this.board.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
this.ctx.fillStyle = value;
this.ctx.fillRect(x * this.cellSize, y * this.cellSize, this.cellSize-1, this.cellSize-1);
}
});
});
// 绘制预览
this.preview.clearRect(0, 0, 120, 120);
this.drawPiece({x:1, y:1, shape:this.next.shape, color:this.next.color}, this.preview);
// 更新分数显示
document.getElementById('score').textContent = this.score;
}
drawPiece(piece, ctx) {
ctx.fillStyle = piece.color;
piece.shape.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
ctx.fillRect(
(piece.x + x) * this.cellSize,
(piece.y + y) * this.cellSize,
this.cellSize-1,
this.cellSize-1
);
}
});
});
}
checkCollision(x, y, shape) {
return shape.some((row, dy) =>
row.some((value, dx) =>
value && (
x + dx < 0 ||
x + dx >= this.cols ||
y + dy >= this.rows ||
(y + dy >= 0 && this.board[y + dy][x + dx])
)
)
);
}
rotate() {
const rotated = this.current.shape[0].map((_, i) =>
this.current.shape.map(row => row[i]).reverse()
);
if (!this.checkCollision(this.current.x, this.current.y, rotated)) {
this.current.shape = rotated;
}
}
move(dx, dy) {
if (!this.checkCollision(this.current.x + dx, this.current.y + dy, this.current.shape)) {
this.current.x += dx;
this.current.y += dy;
return true;
}
return false;
}
hardDrop() {
while (this.move(0, 1)) {}
this.placePiece();
}
placePiece() {
this.current.shape.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
this.board[this.current.y + y][this.current.x + x] = this.current.color;
}
});
});
this.clearLines();
this.spawnNew();
}
clearLines() {
let lines = 0;
for (let y = this.rows - 1; y >= 0; y--) {
if (this.board[y].every(cell => cell)) {
this.board.splice(y, 1);
this.board.unshift(Array(this.cols).fill(0));
lines++;
y++;
}
}
if (lines) {
this.score += lines * 100;
}
}
update() {
if (this.gameOver || this.paused) return;
if (!this.move(0, 1)) {
this.placePiece();
}
this.draw();
}
handleInput(e) {
if (this.gameOver || this.paused) return;
e.preventDefault();
switch(e.keyCode) {
case 37: // 左
this.move(-1, 0);
break;
case 39: // 右
this.move(1, 0);
break;
case 38: // 上
this.rotate();
break;
case 40: // 下
this.move(0, 1);
break;
case 32: // 空格
this.hardDrop();
break;
}
this.draw();
}
togglePause() {
this.paused = !this.paused;
document.getElementById('pause').textContent = this.paused ? '继续' : '暂停';
}
}
new Tetris();
</script>
</body>
</html>