<!
DOCTYPE
html>
<
html
>
<
head
>
<
title
>Tetris</
title
>
<
style
>
body {
background: #202028;
color: #fff;
font-family: sans-serif;
font-size: 2em;
text-align: center;
}
canvas {
border: 2px solid #fff;
background: #000;
}
.container {
display: flex;
justify-content: center;
gap: 20px;
align-items: center;
margin-top: 20px;
}
#score {
margin-top: 1em;
}
.instructions {
font-size: 0.6em;
line-height: 1.5;
text-align: left;
min-width: 200px;
}
</
style
>
</
head
>
<
body
>
<
div
id
=
"score"
>0</
div
>
<
div
class
=
"container"
>
<
canvas
id
=
"tetris"
width
=
"240"
height
=
"400"
></
canvas
>
<
div
>
<
div
>Next:</
div
>
<
canvas
id
=
"next"
width
=
"120"
height
=
"120"
></
canvas
>
<
div
class
=
"instructions"
>
<
div
>操作说明:</
div
>
<
ul
>
<
li
>← →方向键:左右移动</
li
>
<
li
>↑键:旋转方块</
li
>
<
li
>↓键:加速下落</
li
>
<
li
>空格键:直接落下</
li
>
</
ul
>
</
div
>
</
div
>
</
div
>
<
script
>
const canvas = document.getElementById('tetris');
const context = canvas.getContext('2d');
const nextCanvas = document.getElementById('next');
const nextContext = nextCanvas.getContext('2d');
const scoreElement = document.getElementById('score');
context.scale(20, 20);
nextContext.scale(20, 20);
const COLORS = [
'#FF0D72', // I
'#0DC2FF', // O
'#0DFF72', // T
'#F538FF', // L
'#FF8E0D', // J
'#FFE138', // S
'#3877FF' // Z
];
const SHAPES = [
[[1, 1, 1, 1]], // I
[[1, 1], [1, 1]], // O
[[1, 1, 1], [0, 1, 0]], // T
[[1, 1, 1], [1, 0, 0]], // L
[[1, 1, 1], [0, 0, 1]], // J
[[1, 1, 0], [0, 1, 1]], // S
[[0, 1, 1], [1, 1, 0]] // Z
];
let dropCounter = 0;
let dropInterval = 1000;
let lastTime = 0;
let score = 0;
let gameOver = false;
// 初始化矩阵
const matrix = createMatrix(12, 20);
const player = {
pos: {x: 5, y: 0},
matrix: null,
nextMatrix: null,
score: 0,
color: null,
nextColor: null
};
// 游戏逻辑初始化
playerReset();
updateScore();
function update(time = 0) {
if (gameOver) return;
const deltaTime = time - lastTime;
lastTime = time;
dropCounter += deltaTime;
if (dropCounter > dropInterval) {
playerDrop();
}
draw();
requestAnimationFrame(update);
}
function draw() {
context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height);
// 绘制已固定的方块
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
context.fillStyle = value;
context.fillRect(x, y, 1, 1);
}
});
});
// 绘制当前方块
drawMatrix(context, player.matrix, player.pos, player.color);
// 绘制下一个方块
nextContext.fillStyle = '#000';
nextContext.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
drawMatrix(nextContext, player.nextMatrix, {x: 1, y: 1}, player.nextColor);
}
function drawMatrix(context, matrix, offset, color) {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
context.fillStyle = color;
context.fillRect(x + offset.x, y + offset.y, 1, 1);
}
});
});
}
function collide(playerMatrix, pos) {
for (let y = 0; y <
playerMatrix.length
; ++y) {
for (let
x
=
0
; x < playerMatrix[y].length; ++x) {
if (playerMatrix[y][x] !== 0) {
if (pos.y + y >= 20 || // 超出底部
pos.x + x <
0
|| // 超出左边界
pos.x + x >= 12 || // 超出右边界
matrix[pos.y + y][pos.x + x]) { // 与其他方块重叠
return true;
}
}
}
}
return false;
}
function playerRotate() {
const matrix = player.matrix;
const newMatrix = matrix[0].map((_, i) =>
matrix.map(row => row[row.length - 1 - i])
);
if (!collide(newMatrix, player.pos)) {
player.matrix = newMatrix;
}
}
function playerMove(dir) {
player.pos.x += dir;
if (collide(player.matrix, player.pos)) {
player.pos.x -= dir;
}
}
function playerDrop() {
player.pos.y++;
if (collide(player.matrix, player.pos)) {
player.pos.y--;
merge();
playerReset();
sweep();
updateScore();
}
dropCounter = 0;
}
function merge() {
player.matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
matrix[y + player.pos.y][x + player.pos.x] = player.color;
}
});
});
}
function sweep() {
let rowCount = 0;
outer: for (let y = matrix.length - 1; y >= 0; y--) {
for (let x = 0; x <
matrix
[y].length; x++) {
if (!matrix[y][x]) continue outer;
}
const
row
=
matrix
.splice(y, 1)[0];
matrix.unshift(Array(12).fill(0));
rowCount++;
y++;
}
if (rowCount) {
score += rowCount * 100;
}
}
function updateScore() {
scoreElement.textContent
= score;
dropInterval
=
Math
.max(100, 1000 - (Math.floor(score / 1000) * 100));
}
function playerReset() {
const
pieces
=
'ILJOTSZ'
;
const
index
=
pieces
.length * Math.random() | 0;
player.matrix
= player.nextMatrix || createPiece(pieces[index]);
player.color
= player.nextColor || COLORS[index];
const
nextIndex
=
pieces
.length * Math.random() | 0;
player.nextMatrix
=
createPiece
(pieces[nextIndex]);
player.nextColor
=
COLORS
[nextIndex];
player.pos.y
=
0
;
player.pos.x = (12 / 2 | 0) - (player.matrix[0].length / 2 | 0);
if (collide(player.matrix, player.pos)) {
gameOver
=
true
;
alert('游戏结束!得分:' + score);
matrix.forEach(row => row.fill(0));
score = 0;
updateScore();
gameOver = false;
}
}
function createMatrix(w, h) {
return Array(h).fill().map(() => Array(w).fill(0));
}
function createPiece(type) {
const index = 'ILJOTSZ'.indexOf(type);
return SHAPES[index];
}
// 初始化键盘控制
document.addEventListener('keydown', event => {
if (gameOver) return;
if (event.keyCode === 37) {
playerMove(-1);
} else if (event.keyCode === 39) {
playerMove(1);
} else if (event.keyCode === 40) {
playerDrop();
} else if (event.keyCode === 38) {
playerRotate();
} else if (event.keyCode === 32) {
while(!collide(player.matrix, {x: player.pos.x, y: player.pos.y + 1})) {
player.pos.y++;
}
merge();
playerReset();
sweep();
updateScore();
}
});
update();
</
script
>
</
body
>
</
html
>