<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>俄罗斯方块</title>
<style>
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: #333;
color: white;
font-family: Arial, sans-serif;
}
.game-container {
display: flex;
gap: 20px;
}
canvas {
border: 2px solid #fff;
}
.info-panel {
width: 150px;
}
.controls {
margin-top: 20px;
}
.controls button {
padding: 10px 20px;
margin: 5px;
font-size: 16px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="game-container">
<canvas id="tetris" width="300" height="600"></canvas>
<div class="info-panel">
<h2>俄罗斯方块</h2>
<p>分数: <span id="score">0</span></p>
<p>等级: <span id="level">1</span></p>
<p>下一个:</p>
<canvas id="nextPiece" width="120" height="120"></canvas>
<div class="controls">
<button id="startBtn">开始游戏</button>
<button id="pauseBtn" disabled>暂停</button>
<button id="restartBtn" disabled>重新开始</button>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('tetris');
const ctx = canvas.getContext('2d');
const nextCanvas = document.getElementById('nextPiece');
const nextCtx = nextCanvas.getContext('2d');
const scoreElement = document.getElementById('score');
const levelElement = document.getElementById('level');
const ROW = 20;
const COL = 10;
const SQ = 30;
const VACANT = 'black';
const SPEED = 500;
const LINES_PER_LEVEL = 10;
const Z = [
[[1,1,0],
[0,1,1],
[0,0,0]],
[[0,0,1],
[0,1,1],
[0,1,0]]
];
const S = [
[[0,1,1],
[1,1,0],
[0,0,0]],
[[1,0,0],
[1,1,0],
[0,1,0]]
];
const T = [[[1,1,1],[0,1,0],[0,0,0]]],
O = [[[1,1],[1,1]]],
L = [[[1,0,0],[1,1,1],[0,0,0]]],
I = [[[1,1,1,1]]],
J = [[[0,0,1],[1,1,1],[0,0,0]]];
const PIECES = [
[Z, 'red'],
[S, 'green'],
[T, 'yellow'],
[O, 'blue'],
[L, 'purple'],
[I, 'cyan'],
[J, 'orange']
];
let board = [];
let score = 0;
let level = 1;
let lines = 0;
let dropStart = Date.now();
let piece;
let nextPiece;
let isPaused = false;
let isRunning = false;
let rAF;
function initBoard() {
for(let r = 0; r < ROW; r++) {
board[r] = [];
for(let c = 0; c < COL; c++) {
board[r][c] = VACANT;
}
}
}
function drawSquare(x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x*SQ, y*SQ, SQ, SQ);
ctx.strokeStyle = '#333';
ctx.strokeRect(x*SQ, y*SQ, SQ, SQ);
}
function drawBoard() {
for(let r = 0; r < ROW; r++) {
for(let c = 0; c < COL; c++) {
drawSquare(c, r, board[r][c]);
}
}
}
class Piece {
constructor(tetromino, color) {
this.tetromino = tetromino;
this.color = color;
this.tetrominoN = 0;
this.activeTetromino = this.tetromino[this.tetrominoN];
this.x = 3;
this.y = -2;
}
draw() {
for(let r = 0; r < this.activeTetromino.length; r++) {
for(let c = 0; c < this.activeTetromino.length; c++) {
if(this.activeTetromino[r][c]) {
drawSquare(this.x + c, this.y + r, this.color);
}
}
}
}
unDraw() {
for(let r = 0; r < this.activeTetromino.length; r++) {
for(let c = 0; c < this.activeTetromino.length; c++) {
if(this.activeTetromino[r][c]) {
drawSquare(this.x + c, this.y + r, VACANT);
}
}
}
}
moveDown() {
if(!this.collision(0, 1, this.activeTetromino)) {
this.unDraw();
this.y++;
this.draw();
} else {
if(this.y < 0) {
gameOver();
return;
}
this.lock();
piece = nextPiece;
nextPiece = randomPiece();
drawNextPiece();
}
}
moveLeft() {
if(!this.collision(-1, 0, this.activeTetromino)) {
this.unDraw();
this.x--;
this.draw();
}
}
moveRight() {
if(!this.collision(1, 0, this.activeTetromino)) {
this.unDraw();
this.x++;
this.draw();
}
}
rotate() {
if(this.y < 0) {
return;
}
let nextPattern = this.tetromino[(this.tetrominoN + 1) % this.tetromino.length];
let kick = 0;
if(this.collision(0, 0, nextPattern)) {
if(this.x > COL/2) {
kick = -1;
} else {
kick = 1;
}
if(!this.collision(kick, 0, nextPattern)) {
this.x += kick;
} else {
return;
}
}
this.unDraw();
this.tetrominoN = (this.tetrominoN + 1) % this.tetromino.length;
this.activeTetromino = this.tetromino[this.tetrominoN];
this.draw();
}
collision(x, y, piece) {
for(let r = 0; r < piece.length; r++) {
for(let c = 0; c < piece.length; c++) {
if(!piece[r][c]) continue;
let newX = this.x + c + x;
let newY = this.y + r + y;
if(newX < 0 || newX >= COL || newY >= ROW) {
return true;
}
if(newY >= 0 && board[newY][newX] !== VACANT) {
return true;
}
}
}
return false;
}
lock() {
for(let r = 0; r < this.activeTetromino.length; r++) {
for(let c = 0; c < this.activeTetromino.length; c++) {
if(!this.activeTetromino[r][c]) continue;
if(this.y + r < 0) {
gameOver();
return;
}
board[this.y + r][this.x + c] = this.color;
}
}
clearLines();
}
}
function randomPiece() {
let r = Math.floor(Math.random() * PIECES.length);
return new Piece(PIECES[r][0], PIECES[r][1]);
}
function clearLines() {
let linesCleared = 0;
for(let r = ROW - 1; r >= 0; r--) {
if(board[r].every(cell => cell !== VACANT)) {
board.splice(r, 1);
board.unshift(new Array(COL).fill(VACANT));
linesCleared++;
r++;
}
}
if(linesCleared > 0) {
updateScore(linesCleared);
}
}
function updateScore(linesCleared) {
const points = [0, 40, 100, 300, 1200][linesCleared] * level;
score += points;
lines += linesCleared;
if(lines >= LINES_PER_LEVEL) {
level++;
lines -= LINES_PER_LEVEL;
}
scoreElement.textContent = score;
levelElement.textContent = level;
}
function drawNextPiece() {
nextCtx.clearRect(0, 0, nextCanvas.width, nextCanvas.height);
for(let r = 0; r < nextPiece.tetromino[0].length; r++) {
for(let c = 0; c < nextPiece.tetromino[0].length; c++) {
if(nextPiece.tetromino[0][r][c]) {
nextCtx.fillStyle = nextPiece.color;
nextCtx.fillRect(c*30, r*30, 30, 30);
nextCtx.strokeStyle = '#333';
nextCtx.strokeRect(c*30, r*30, 30, 30);
}
}
}
}
function gameOver() {
cancelAnimationFrame(rAF);
isRunning = false;
document.getElementById('startBtn').disabled = false;
document.getElementById('pauseBtn').disabled = true;
document.getElementById('restartBtn').disabled = false;
ctx.fillStyle = 'rgba(0,0,0,0.75)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = '40px Arial';
ctx.fillText('游戏结束!', 30, canvas.height/2 - 20);
ctx.font = '20px Arial';
ctx.fillText('按R键重新开始', 50, canvas.height/2 + 20);
}
function init() {
initBoard();
score = 0;
level = 1;
lines = 0;
scoreElement.textContent = score;
levelElement.textContent = level;
piece = randomPiece();
nextPiece = randomPiece();
drawNextPiece();
drawBoard();
piece.draw();
}
function gameLoop() {
if(isPaused || !isRunning) return;
let now = Date.now();
let delta = now - dropStart;
if(delta > SPEED / level) {
piece.moveDown();
dropStart = Date.now();
}
drawBoard();
piece.draw();
rAF = requestAnimationFrame(gameLoop);
}
document.getElementById('startBtn').addEventListener('click', () => {
if(!isRunning) {
init();
isRunning = true;
isPaused = false;
document.getElementById('startBtn').disabled = true;
document.getElementById('pauseBtn').disabled = false;
document.getElementById('restartBtn').disabled = false;
dropStart = Date.now();
gameLoop();
}
});
document.getElementById('pauseBtn').addEventListener('click', () => {
if(isRunning) {
isPaused = !isPaused;
document.getElementById('pauseBtn').textContent = isPaused ? '继续' : '暂停';
if(!isPaused) {
dropStart = Date.now();
gameLoop();
}
}
});
document.getElementById('restartBtn').addEventListener('click', () => {
cancelAnimationFrame(rAF);
init();
isRunning = true;
isPaused = false;
document.getElementById('pauseBtn').textContent = '暂停';
dropStart = Date.now();
gameLoop();
});
document.addEventListener('keydown', (event) => {
if(!isRunning || isPaused) return;
if(event.keyCode === 37) {
piece.moveLeft();
dropStart = Date.now();
} else if(event.keyCode === 38) {
if(piece.y >= 0) {
piece.rotate();
dropStart = Date.now();
}
} else if(event.keyCode === 39) {
piece.moveRight();
dropStart = Date.now();
} else if(event.keyCode === 40) {
piece.moveDown();
} else if(event.keyCode === 80) {
document.getElementById('pauseBtn').click();
}
});
</script>
</body>
</html>