0. 操作流程图

1. 介绍
俄罗斯方块(俄语:Русская клеточная игра или Русская клеточная игра в квадрате)是一款经典的益智类游戏,其规则简单,操作简单,具有很高的趣味性。 俄罗斯方块是一款2D的益智游戏,玩家控制一个方块在一个方格内自由移动,通过控制方块的旋转、下落、以及移动,来消除周围的方块,并在此过程中获得分数。
游戏的基本规则如下:
- 方块由四个小方块组成,每个小方块都有四个角落,四条边,以及一个中心点。
- 方块可以旋转,但不能翻转。
- 方块只能在空白的格子上移动。
- 方块移动到底部时,方块会固定在底部,并开始下落。
- 方块下落时,如果它与其他方块发生碰撞,则游戏结束。
- 方块下落到底部时,它会消除周围的方块,并获得分数。
- 方块消除一行时,游戏结束。
本文将用javascript实现俄罗斯方块游戏。
2. 预览效果
3. DOM 结构
html
<div>
<div id="tetris-score">Score: 0</div>
<div>
<button onclick="startGame()">Start Game</button>
</div>
<div id="tetris-container">
<div id="tetris-board"></div>
<div id="tetris-preview">
<div id="next-piece"></div>
</div>
</div>
</div>
4. CSS 基础样式
css
#tetris-container {
display: flex;
margin-top: 20px;
position: relative;
}
#tetris-board {
width: 300px;
height: 600px;
border: 2px solid #000;
position: relative;
background-color: #f0f0f0;
}
#tetris-preview{
width: 120px;
height: 120px;
margin-left: 20px;
border: 2px solid #000;
position: relative;
background-color: #f0f0f0;
}
.rui-piece-cell{
box-sizing: border-box;
border: 1px solid #aaa;
}
#tetris-score {
font-size: 20px;
font-weight: bold;
text-align: center;
}
button {
display: block;
margin: 0 auto;
padding: 10px 20px;
font-size: 18px;
font-weight: bold;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #45a049;
}
5. 全局变量声明
javascript
const BOARD_WIDTH = 10; // 俄罗斯方块板宽
const BOARD_HEIGHT = 20; // 俄罗斯方块板高
const PREVIEW_WIDTH = 6; // 俄罗斯方块预览宽
let timer = null; // 定时器
let board = Array.from({length: BOARD_HEIGHT}, () => Array(BOARD_WIDTH).fill(0)); // 俄罗斯方块板
let currentPiece; // 当前俄罗斯方块
let nextPiece; // 下一个俄罗斯方块
let score = 0; // 得分
const TETROMINOES = [
[[1,1,1,1]], // I
[[1,1],[1,1]], // O
[[0,1,0],[1,1,1]], // T 向上
[[1,1,1],[0,1,0]], // T 向下
[[1,0,0],[1,1,1]], // L
[[0,0,1],[1,1,1]], // J
[[0,1,1],[1,1,0]], // S
[[1,1,0],[0,1,1]] // Z
];
6. 创建当前俄罗斯方块和下一个俄罗斯方块
- 如果没有下一个俄罗斯方块,则创建一个,说明游戏刚开始,下一个俄罗斯方块还未创建。
- 随机选择一个俄罗斯方块,计算俄罗斯方块出现的X坐标位置。
- 创建下一个俄罗斯方块,并对下一个俄罗斯方块进行预告绘制。
- 返回当前俄罗斯方块。
javascript
function createRandomPiece() {
// 随机选择一个俄罗斯方块
const randomIndex = Math.floor(Math.random() * TETROMINOES.length);
// 计算俄罗斯方块出现的X坐标位置
const randomX = Math.floor((BOARD_WIDTH - TETROMINOES[randomIndex][0].length) / 2);
return {
shape: TETROMINOES[randomIndex],
x: randomX,
y: 0
};
}
function createPiece() {
// 如果没有下一个俄罗斯方块,则创建一个
let piece = nextPiece || createRandomPiece();
// 随机选择下一个俄罗斯方块
nextPiece = createRandomPiece();
// 绘制下一个俄罗斯方块
drawNextPiece(nextPiece);
// 返回当前俄罗斯方块
return piece;
}
7. 绘制预告俄罗斯方块
javascript
function drawNextPiece(piece) {
// 获取展示预告俄罗斯方块的盒子
const nextPieceElement = document.getElementById('next-piece');
// 清空展示预告俄罗斯方块的盒子
nextPieceElement.innerHTML = '';
// 获取俄罗斯方块的宽和高
nextPieceWidth = piece.shape[0].length;
nextPieceHeight = piece.shape.length;
// 计算预告俄罗斯方块的左上角坐标
let left = (PREVIEW_WIDTH - nextPieceWidth) / 2 * 20;
let top = (PREVIEW_WIDTH - nextPieceHeight) / 2 * 20;
// 绘制预告俄罗斯方块
for (let row = 0; row < piece.shape.length; row++) {
for (let col = 0; col < piece.shape[row].length; col++) {
if (piece.shape[row][col]) {
const cell = document.createElement('div');
cell.style.width = '20px';
cell.style.height = '20px';
cell.style.position = 'absolute';
cell.style.left = `${col * 20 + left}px`;
cell.style.top = `${row * 20 + top}px`;
cell.style.backgroundColor = '#000';
cell.classList.add('rui-piece-cell');
nextPieceElement.appendChild(cell);
}
}
}
}
8. 绘制俄罗斯方块板
javascript
function draw() {
// 获取俄罗斯方块板的盒子
const boardElement = document.getElementById('tetris-board');
// 清空俄罗斯方块板的盒子
boardElement.innerHTML = '';
// 获取当前俄罗斯方块的形状、X坐标、Y坐标
let { shape, x:i, y:j } = currentPiece;
// 绘制俄罗斯方块板
for (let y = 0; y < BOARD_HEIGHT; y++) {
for (let x = 0; x < BOARD_WIDTH; x++) {
// 判断当前坐标是否是俄罗斯方块的一部分
let isPiece = false;
for (let row = 0; row < currentPiece.shape.length; row++) {
for (let col = 0; col < currentPiece.shape[row].length; col++) {
if (currentPiece.shape[row][col] &&
x === currentPiece.x + col &&
y === currentPiece.y + row) {
isPiece = true;
}
}
}
// 创建一个俄罗斯方块板的单元格
const cell = document.createElement('div');
// 设置单元格的样式
const cell = document.createElement('div');
cell.style.position = 'absolute';
cell.style.width = '30px';
cell.style.height = '30px';
cell.style.left = `${x * 30}px`;
cell.style.top = `${y * 30}px`;
cell.style.backgroundColor = isPiece ? '#f00' : (board[y][x] ? '#000' : '#f0f0f0');
cell.classList.add('rui-piece-cell');
boardElement.appendChild(cell);
}
}
// 绘制得分
document.getElementById('tetris-score').innerText = `Score: ${score}`;
}
9. 移动俄罗斯方块
javascript
function movePiece(dx, dy) {
// 如果当前俄罗斯方块可以移动,则移动当前俄罗斯方块
if (!collision(currentPiece.shape, currentPiece.x + dx, currentPiece.y + dy)) {
currentPiece.x += dx;
currentPiece.y += dy;
}
}
10. 旋转俄罗斯方块
javascript
function rotatePiece() {
// 旋转当前俄罗斯方块
const rotated = currentPiece.shape[0].map((_, i) =>
currentPiece.shape.map(row => row[i]).reverse()
);
// 如果旋转后的俄罗斯方块未发生碰撞,则旋转当前俄罗斯方块
if (!collision(rotated, currentPiece.x, currentPiece.y)) {
currentPiece.shape = rotated;
}
}
11. 俄罗斯方块是否碰撞
javascript
function collision(shape, x, y) {
// 判断当前俄罗斯方块是否与俄罗斯方块板发生碰撞
for (let row = 0; row < shape.length; row++) {
for (let col = 0; col < shape[row].length; col++) {
// 判断当前俄罗斯方块的某一格是否与俄罗斯方块板的某一格发生碰撞
if (shape[row][col] &&
(board[y + row] && board[y + row][x + col]) !== 0) {
return true;
}
}
}
return false;
}
12. 俄罗斯方块下落
javascript
function placePiece() {
// 放置当前俄罗斯方块
for (let row = 0; row < currentPiece.shape.length; row++) {
for (let col = 0; col < currentPiece.shape[row].length; col++) {
if (currentPiece.shape[row][col]) {
board[currentPiece.y + row][currentPiece.x + col] = 1;
}
}
}
// 清除满行
clearLines();
// 创建下一个俄罗斯方块
currentPiece = createPiece();
// 如果当前俄罗斯方块与俄罗斯方块板发生碰撞,则游戏结束
if (collision(currentPiece.shape, currentPiece.x, currentPiece.y)) {
gameOver();
}
}
13. 清除满行
javascript
function clearLines() {
// 清除满行
let linesCleared = 0;
// 遍历俄罗斯方块板,从上到下遍历每一行
for (let y = BOARD_HEIGHT - 1; y >= 0; y--) {
if (board[y].every(cell => cell !== 0)) {
board.splice(y, 1);
board.unshift(Array(BOARD_WIDTH).fill(0));
linesCleared++;
y++;
}
}
// 增加分数
score += linesCleared * 100;
}
14. 游戏结束
javascript
function gameOver() {
alert(`Game Over! Your score: ${score}`);
// 清空俄罗斯方块板
board = Array.from({length: BOARD_HEIGHT}, () => Array(BOARD_WIDTH).fill(0));
// 重置得分
score = 0;
// 创建新的俄罗斯方块
currentPiece = createPiece();
}
15. 开始游戏
javascript
function gameLoop() {
draw();
// 判断当前俄罗斯方块是否可以下落
if (!collision(currentPiece.shape, currentPiece.x, currentPiece.y + 1)) {
currentPiece.y++;
} else {
placePiece();
}
// 开启下一轮游戏
timer = setTimeout(gameLoop, 1000);
}
function startGame() {
// 判断游戏是否已经开始,开始就清除定时器
if(timer) {
clearTimeout(timer);
}
currentPiece = createPiece();
draw();
gameLoop();
}