一、需要实现的功能
俄罗斯方块游戏功能比较简单,旨在借助该游戏的制作过程来帮助大家学习JavaScript在前端中的应用。
在游戏开始的时候,会出现一个空的游戏区域,以及一个正在下落的方块。使用键盘方向键来控制方块的旋转和移动,目标是为了将方块填满游戏区域的整行,使其消除。当整行被填满时,该整行将被清除并获得积分。当方块堆叠到游戏区域的顶部时无法继续移动游戏结束。(下面是游戏的演示图)
二、需求功能细化
1、游戏区域
是由许多格子组成矩形区域。初始状态下游戏区域是空的。
2、方块
由四个小方块组成,随机生成不同的形状,每个小方块可以向下、向左、向右移动,以及顺时针旋转。
3、游戏规则
方块是从游戏区域的顶部开始下落,玩家可以控制方块的旋转和移动,直到方块无法继续下落或旋转。
当方块下落到游戏区域的底部或落在其他得方块上时,将固定在该位置,成为游戏区域的一部分。
当方块固定在游戏区域时,程序检查是否有整行被填满。如果有,清除这些行,并给玩家增加相应积分。
新的方块继续从顶部生成,直到方块堆叠到游戏区域的顶部游戏结束。
可以通过增加方块的下落速度、方块的形状复杂度等来逐渐增加游戏难度。
三、H5页面设计
1、创建html文件
首先创建一个命名为tetris.html的文件,通过编辑器(我用的EditPlus)打开。在HTML文档的头部,使用<!DOCTYPE>声明HTML文档类型,确保浏览器以正确的方式渲染网页内容。同时,设置utf-8编码,以确保浏览器能够正确地解析和显示中文字符。
下面这段 html 代码定义了俄罗斯方块游戏的基本布局。
<body>
<h1>俄罗斯方块游戏</h1>
<div id="tetris">
<div id="game-board"></div>
<div id="score">Score: <span id="score-value">0</span></div>
</div>
</body>
<h1>俄罗斯方块游戏</h1>:这是一个 <h1> 标题标签,用于显示该页面的主题内容“俄罗斯方块游戏”,一个页面只能一个h1标签。
<div id="tetris">:这是一个 <div> 容器,用于包裹整个游戏区域。
<div id="game-board"></div>:这是一个空的 <div> 容器,它的 id 属性为 “game-board”。这个容器将用于显示俄罗斯方块游戏的主要游戏区域。
<div id="score">Score: <span id="score-value">0</span></div>:这是一个用于显示积分的 <div> 容器,它的 id 属性为 “score”。其中包含文本 "Score: ",以及一个 <span> 元素用于显示实际的积分值。初始得分为 0,它被包含在 id 属性为 “score-value” 的 <span> 元素中。
这段 HTML 代码定义了一个基本的俄罗斯方块游戏界面,包含游戏标题、游戏区域和积分显示。通过 JavaScript 代码的结合,将实现游戏逻辑和交互功能。
三、CSS样式设置
上面html已经准备好了,下面我们来设置下页面样式吧。下面这段 CSS 代码为俄罗斯方块游戏提供了样式和布局:
<style>
h1 {
font-size: 19px;
text-align: center;
}
#tetris {
width: 240px;
margin: 0 auto;
background-color: #d5d5d5;
border-radius: 10px;
padding: 25px;
}
#game-board {
width: 200px;
height: 400px;
border: 4px solid #4b6014;
position: relative;
border-radius: 10px;
background-color: #f4f126;
margin: 0 auto;
}
#score {
text-align: center;
margin-top: 10px;
}
.block {
width: 20px;
height: 20px;
position: absolute;
background-color: #000;
border: 1px solid #3a3a3a;
box-sizing: border-box;
}
</style>
这些样式规则的含义如下:
h1:设置 <h1> 标签的字体大小为 19 像素,居中对齐。
#tetris:设置俄罗斯方块游戏容器的宽度为 240 像素,水平居中对齐,背景色为 #d5d5d5,边框半径为 10 像素,内边距为 25 像素。
#game-board:设置游戏面板容器的宽度为 200 像素,高度为 400 像素,添加 4 像素宽度、颜色为 #4b6014 的实线边框,相对定位,边框半径为 10 像素,背景色为 #f4f126,水平居中对齐。
#score:设置分数容器的文本居中对齐,上外边距为 10 像素。
.block:设置方块元素的宽度和高度为 20 像素,绝对定位,背景色为 #000,边框为 1 像素宽度、颜色为 #3a3a3a 的实线边框,使用盒模型的 “border-box” 进行盒子尺寸计算。
我们看下效果,怎么样是不是感觉好多了:
四、代码逻辑,游戏实现
上面我们搭建了好了页面,下面我们通过js代码来实现我们游戏的功能。先看代码:
<script>
document.addEventListener('DOMContentLoaded', () => {
function createShape() {
const shapes = [
[[1, 1, 1, 1]],
[[1, 1], [1, 1]],
[[1, 1, 0], [0, 1, 1]],
[[0, 1, 1], [1, 1, 0]],
[[1, 1, 1], [0, 1, 0]],
[[1, 1, 1], [1, 0, 0]],
[[1, 1, 1], [0, 0, 1]]
];
const randomIndex = Math.floor(Math.random() * shapes.length);
const shape = shapes[randomIndex];
currentShape = shape;
currentRow = 0;
currentCol = Math.floor(cols / 2) - Math.floor(shape[0].length / 2);
}
function drawBoard() {
board.innerHTML = '';
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
if (boardGrid[row][col]) {
const block = document.createElement('div');
block.className = 'block';
block.style.top = row * blockSize + 'px';
block.style.left = col * blockSize + 'px';
board.appendChild(block);
}
}
}
}
function drawCurrentShape() {
for (let row = 0; row < currentShape.length; row++) {
for (let col = 0; col < currentShape[row].length; col++) {
if (currentShape[row][col]) {
const block = document.createElement('div');
block.className = 'block';
block.style.top = (currentRow + row) * blockSize + 'px';
block.style.left = (currentCol + col) * blockSize + 'px';
board.appendChild(block);
}
}
}
}
function checkCollision() {
for (let row = 0; row < currentShape.length; row++) {
for (let col = 0; col < currentShape[row].length; col++) {
if (currentShape[row][col]) {
const newRow = currentRow + row;
const newCol = currentCol + col;
if (
newRow >= rows ||
newCol < 0 ||
newCol >= cols ||
boardGrid[newRow][newCol]
) {
return true;
}
}
}
}
return false;
}
function mergeShape() {
for (let row = 0; row < currentShape.length; row++) {
for (let col = 0; col < currentShape[row].length; col++) {
if (currentShape[row][col]) {
const newRow = currentRow + row;
const newCol = currentCol + col;
boardGrid[newRow][newCol] = 1;
}
}
}
}
function clearRows() {
for (let row = rows - 1; row >= 0; row--) {
if (boardGrid[row].every((cell) => cell)) {
boardGrid.splice(row, 1);
boardGrid.unshift(new Array(cols).fill(0));
score++;
}
}
}
function updateScore() {
scoreValue.textContent = score;
}
function moveDown() {
currentRow++;
if (checkCollision()) {
currentRow--;
mergeShape();
clearRows();
updateScore();
createShape();
if (checkCollision()) {
gameOver();
}
}
}
function moveLeft() {
currentCol--;
if (checkCollision()) {
currentCol++;
}
}
function moveRight() {
currentCol++;
if (checkCollision()) {
currentCol--;
}
}
function rotateShape() {
const rotatedShape = currentShape[0].map((_, colIndex) =>
currentShape.map((row) => row[colIndex]).reverse()
);
const prevShape = currentShape;
currentShape = rotatedShape;
if (checkCollision()) {
currentShape = prevShape;
}
}
function gameOver() {
alert('Game Over');
resetGame();
}
function resetGame() {
score = 0;
boardGrid = Array.from(Array(rows), () => new Array(cols).fill(0));
updateScore();
createShape();
}
function handleKeyPress(event) {
switch (event.key) {
case 'ArrowDown':
moveDown();
break;
case 'ArrowLeft':
moveLeft();
break;
case 'ArrowRight':
moveRight();
break;
case 'ArrowUp':
rotateShape();
break;
}
drawBoard();
drawCurrentShape();
}
function startGame() {
createShape();
setInterval(() => {
moveDown();
drawBoard();
drawCurrentShape();
}, 500);
document.addEventListener('keydown', handleKeyPress);
}
startGame();
});
</script>
接下来对代码进行解析:
1、变量定义
const board = document.getElementById('game-board');
const scoreValue = document.getElementById('score-value');
const blockSize = 20;
const rows = 20;
const cols = 10;
let score = 0;
let boardGrid = Array.from(Array(rows), () => new Array(cols).fill(0));
let currentShape;
let currentRow;
let currentCol;
board: 表示游戏区域的 DOM 元素,用于显示方块的容器。
scoreValue: 显示当前积分的 DOM 元素,用于更新积分显示。
blockSize: 方块的大小,即每个小方块的宽度和高度。
rows: 游戏区域的行数,表示游戏区域的垂直方向上方块的数量。
cols: 游戏区域的列数,表示游戏区域的水平方向上方块的数量。
score: 当前的积分,表示玩家在游戏中获得的积分。
boardGrid: 表示游戏区域的二维数组,用于记录方块的位置和状态。
currentShape: 当前正在移动的方块的形状,以二维数组的形式表示。
currentRow: 当前正在移动的方块的所在行数。
currentCol: 当前正在移动的方块的所在列数。
这些变量在游戏过程中被用于存储和追踪游戏的状态和数据。通过这些变量,游戏能够实时更新和管理游戏状态。
2、函数功能
createShape(): 创建一个随机的俄罗斯方块形状,并将其设置为当前形状。还会初始化当前形状的行和列。
drawBoard(): 在游戏面板上绘制当前的方块状态和已放置的方块。通过遍历游戏面板的二维数组 boardGrid,根据数组中的值来确定是否绘制方块。
drawCurrentShape(): 在游戏面板上绘制当前的方块形状。遍历当前形状的二维数组,根据数组中的值来确定绘制方块的位置。
checkCollision(): 检查当前的方块是否与已放置的方块或游戏边界发生碰撞。遍历当前形状的二维数组,检查当前方块的每个单元格是否与已放置的方块或边界发生碰撞。
mergeShape(): 将当前方块合并到已放置方块的游戏面板中。遍历当前形状的二维数组,将当前方块的每个单元格的值设置为1,表示已放置方块。
clearRows(): 检查游戏面板的每一行是否已满。如果某一行已满,则将该行删除,并在顶部添加新的空行。同时,增加玩家的分数。
updateScore(): 更新分数显示。将分数的值更新到分数元素中。
moveDown(): 将当前方块向下移动一行。如果发生碰撞,则将当前方块合并到游戏面板中,并检查是否有已满的行需要清除。如果当前方块无法再向下移动,则生成一个新的随机方块。
moveLeft(): 将当前方块向左移动一列。如果发生碰撞,则撤销移动操作。
moveRight(): 将当前方块向右移动一列。如果发生碰撞,则撤销移动操作。
rotateShape(): 旋转当前方块的形状。通过交换二维数组的行和列来实现方块的旋转。如果旋转后发生碰撞,则撤销旋转操作。
gameOver(): 游戏结束。显示游戏结束的提示框,并重置游戏。
resetGame(): 重置游戏状态。将分数、游戏面板和已放置方块的二维数组重置为初始状态,然后创建一个新的随机方块。
handleKeyPress(event): 处理按键事件。根据按下的按键来调用相应的移动或旋转方法,并重新绘制游戏面板和当前形状。
startGame(): 启动游戏。在游戏开始时,创建一个新的随机方块,并以一定的时间间隔不断向下移动方块。同时,监听键盘按键事件。
DOMContentLoaded 事件监听器:在 HTML 文档加载完成后执行代码。这是游戏逻辑的入口点。
通过这些函数功能实现了俄罗斯方块游戏的核心逻辑,包括生成随机方块、移动方块、检测碰撞、合并方块、清除满行等。同时,它还提供了游戏的初始化、分数计算、游戏结束和重置等功能。
五、完整源代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>俄罗斯方块游戏 - mmt3.com</title>
<style>
h1 {
font-size: 19px;
text-align: center;
}
#tetris {
width: 240px;
margin: 0 auto;
background-color: #d5d5d5;
border-radius: 10px;
padding: 25px;
}
#game-board {
width: 200px;
height: 400px;
border: 4px solid #4b6014;
position: relative;
border-radius: 10px;
background-color: #f4f126;
margin: 0 auto;
}
#score {
text-align: center;
margin-top: 10px;
}
.block {
width: 20px;
height: 20px;
position: absolute;
background-color: #000;
border: 1px solid #3a3a3a;
box-sizing: border-box;
}
</style>
</head>
<body>
<h1>俄罗斯方块游戏</h1>
<div id="tetris">
<div id="game-board"></div>
<div id="score">Score: <span id="score-value">0</span></div>
</div>
</body>
<script>
document.addEventListener('DOMContentLoaded', () => {
const board = document.getElementById('game-board');
const scoreValue = document.getElementById('score-value');
const blockSize = 20;
const rows = 20;
const cols = 10;
let score = 0;
let boardGrid = Array.from(Array(rows), () => new Array(cols).fill(0));
let currentShape;
let currentRow;
let currentCol;
function createShape() {
const shapes = [
[[1, 1, 1, 1]],
[[1, 1], [1, 1]],
[[1, 1, 0], [0, 1, 1]],
[[0, 1, 1], [1, 1, 0]],
[[1, 1, 1], [0, 1, 0]],
[[1, 1, 1], [1, 0, 0]],
[[1, 1, 1], [0, 0, 1]]
];
const randomIndex = Math.floor(Math.random() * shapes.length);
const shape = shapes[randomIndex];
currentShape = shape;
currentRow = 0;
currentCol = Math.floor(cols / 2) - Math.floor(shape[0].length / 2);
}
function drawBoard() {
board.innerHTML = '';
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
if (boardGrid[row][col]) {
const block = document.createElement('div');
block.className = 'block';
block.style.top = row * blockSize + 'px';
block.style.left = col * blockSize + 'px';
board.appendChild(block);
}
}
}
}
function drawCurrentShape() {
for (let row = 0; row < currentShape.length; row++) {
for (let col = 0; col < currentShape[row].length; col++) {
if (currentShape[row][col]) {
const block = document.createElement('div');
block.className = 'block';
block.style.top = (currentRow + row) * blockSize + 'px';
block.style.left = (currentCol + col) * blockSize + 'px';
board.appendChild(block);
}
}
}
}
function checkCollision() {
for (let row = 0; row < currentShape.length; row++) {
for (let col = 0; col < currentShape[row].length; col++) {
if (currentShape[row][col]) {
const newRow = currentRow + row;
const newCol = currentCol + col;
if (
newRow >= rows ||
newCol < 0 ||
newCol >= cols ||
boardGrid[newRow][newCol]
) {
return true;
}
}
}
}
return false;
}
function mergeShape() {
for (let row = 0; row < currentShape.length; row++) {
for (let col = 0; col < currentShape[row].length; col++) {
if (currentShape[row][col]) {
const newRow = currentRow + row;
const newCol = currentCol + col;
boardGrid[newRow][newCol] = 1;
}
}
}
}
function clearRows() {
for (let row = rows - 1; row >= 0; row--) {
if (boardGrid[row].every((cell) => cell)) {
boardGrid.splice(row, 1);
boardGrid.unshift(new Array(cols).fill(0));
score++;
}
}
}
function updateScore() {
scoreValue.textContent = score;
}
function moveDown() {
currentRow++;
if (checkCollision()) {
currentRow--;
mergeShape();
clearRows();
updateScore();
createShape();
if (checkCollision()) {
gameOver();
}
}
}
function moveLeft() {
currentCol--;
if (checkCollision()) {
currentCol++;
}
}
function moveRight() {
currentCol++;
if (checkCollision()) {
currentCol--;
}
}
function rotateShape() {
const rotatedShape = currentShape[0].map((_, colIndex) =>
currentShape.map((row) => row[colIndex]).reverse()
);
const prevShape = currentShape;
currentShape = rotatedShape;
if (checkCollision()) {
currentShape = prevShape;
}
}
function gameOver() {
alert('Game Over');
resetGame();
}
function resetGame() {
score = 0;
boardGrid = Array.from(Array(rows), () => new Array(cols).fill(0));
updateScore();
createShape();
}
function handleKeyPress(event) {
switch (event.key) {
case 'ArrowDown':
moveDown();
break;
case 'ArrowLeft':
moveLeft();
break;
case 'ArrowRight':
moveRight();
break;
case 'ArrowUp':
rotateShape();
break;
}
drawBoard();
drawCurrentShape();
}
function startGame() {
createShape();
setInterval(() => {
moveDown();
drawBoard();
drawCurrentShape();
}, 500);
document.addEventListener('keydown', handleKeyPress);
}
startGame();
});
</script>
</html>
以上是俄罗斯方块游戏的完整代码,可以直接复制过去,或者点击demo链接访问。