游戏俄罗斯方块实现
一个经典游戏:俄罗斯方块
# 原理
下面拆解一下要实现这个游戏需要考虑的问题
# 怎么表示棋盘?
- 棋盘的长方形区域可以用
m*n
的二维数组表示, 棋盘初始化数值为0
如果[m,n]
处被占用则为1
- 棋盘分为两部分:下落的小方块和底部已经堆叠好的部分
- 渲染棋盘方法就取出堆叠部分和下落的小方块,在对应地方渲染为
1
# 怎么表示小方块? 小方块的移动、旋转怎么实现
- 小方块可以用二维数组实现
- 移动:记录小方块的位置,小方块移动就改变方块的位置,移动时需要注意检测边界和底部堆叠的碰撞
- 旋转:二维数组矩阵旋转即可,旋转时也需要注意检测边界和底部堆叠的碰撞
# 怎么计算消除
- 可以直接遍历棋盘的每行,只要每行坑位都为 1 就消除
- 消除操作直接从堆叠部分干掉应该消除的行,然后重新渲染棋盘
# 代码实现
下面就是体力活儿 👇🏻
<!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>几何⚡️俄罗斯方块</title>
<style>
h2 {
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>
<h2>俄罗斯方块</h2>
<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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
createShape()
: 创建一个随机的俄罗斯方块形状,并将其设置为当前形状。还会初始化当前形状的行和列。
drawBoard()
: 在游戏面板上绘制当前的方块状态和已放置的方块。通过遍历游戏面板的二维数组 boardGrid,根据数组中的值来确定是否绘制方块。
drawCurrentShape()
: 在游戏面板上绘制当前的方块形状。遍历当前形状的二维数组,根据数组中的值来确定绘制方块的位置。
checkCollision()
: 检查当前的方块是否与已放置的方块或游戏边界发生碰撞。遍历当前形状的二维数组,检查当前方块的每个单元格是否与已放置的方块或边界发生碰撞。
mergeShape()
: 将当前方块合并到已放置方块的游戏面板中。遍历当前形状的二维数组,将当前方块的每个单元格的值设置为 1,表示已放置方块。
clearRows()
: 检查游戏面板的每一行是否已满。如果某一行已满,则将该行删除,并在顶部添加新的空行。同时,增加玩家的分数。
updateScore()
: 更新分数显示。将分数的值更新到分数元素中。
moveDown()
: 将当前方块向下移动一行。如果发生碰撞,则将当前方块合并到游戏面板中,并检查是否有已满的行需要清除。如果当前方块无法再向下移动,则生成一个新的随机方块。
moveLeft()
: 将当前方块向左移动一列。如果发生碰撞,则撤销移动操作。
moveRight()
: 将当前方块向右移动一列。如果发生碰撞,则撤销移动操作。
rotateShape()
: 旋转当前方块的形状。通过交换二维数组的行和列来实现方块的旋转。如果旋转后发生碰撞,则撤销旋转操作。
gameOver()
: 游戏结束。显示游戏结束的提示框,并重置游戏。
resetGame()
: 重置游戏状态。将分数、游戏面板和已放置方块的二维数组重置为初始状态,然后创建一个新的随机方块。
handleKeyPress(event)
: 处理按键事件。根据按下的按键来调用相应的移动或旋转方法,并重新绘制游戏面板和当前形状。
startGame()
: 启动游戏。在游戏开始时,创建一个新的随机方块,并以一定的时间间隔不断向下移动方块。同时,监听键盘按键事件。