XingYun blog
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具

XingYun

冲!
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具
  • leaflet改变坐标原点
  • 补间动画gsap与tween
  • 文字转语音mp3文件
  • JavaScript引入
  • JavaScript高级程序设计阅读笔记
  • Javascript函数参数按值传递传递
  • JS防抖和节流
  • 手写JS常用方法
  • 手写Promise
  • JS Event Loop
  • 重排和重绘
  • em、px、rem、vh、vw 区别
  • css也需要性能优化
  • 图解 js 原型链
  • js函数参数按值传递
  • BOM浏览器对象模型
  • DOM
  • 事件
  • js对象数组sort按需排序
  • 文件上传功能技术选型和前后端实现
  • 前端图片处理
  • 让你的JavaScript代码简单又高效
  • BEM:class命名规范
  • 前端规范-CSS属性那么多(杂),怎么排序
  • TypeScript Tips
  • jsx
  • canvas基础
  • 前端日志
  • 浏览器存储
  • CSS世界阅读笔记
  • CSS揭秘阅读笔记
  • js变量命名常用规范词
  • 你不知道的JavaScript阅读笔记
  • js对象的底层数据结构
  • js判断数据类型
  • JS浮点数精度问题和解决办法
  • js作用域
  • js堆栈溢出和内存泄漏
  • 浏览器是如何渲染页面的
  • 疑难杂症和踩坑问题合集
  • 免费在线API收集
  • 原生JS实现Ajax请求
  • cookie、session、localStorage、sessionStorage的区别
  • Sass与Less
  • arrayBuffer、blob、file对象
  • TypeScript基础
  • 前端
XingYun
2023-07-20
目录

游戏俄罗斯方块实现

一个经典游戏:俄罗斯方块

# 原理

下面拆解一下要实现这个游戏需要考虑的问题

# 怎么表示棋盘?

  1. 棋盘的长方形区域可以用 m*n 的二维数组表示, 棋盘初始化数值为0 如果[m,n] 处被占用则为1
  2. 棋盘分为两部分:下落的小方块和底部已经堆叠好的部分
  3. 渲染棋盘方法就取出堆叠部分和下落的小方块,在对应地方渲染为1

# 怎么表示小方块? 小方块的移动、旋转怎么实现

  1. 小方块可以用二维数组实现
  2. 移动:记录小方块的位置,小方块移动就改变方块的位置,移动时需要注意检测边界和底部堆叠的碰撞
  3. 旋转:二维数组矩阵旋转即可,旋转时也需要注意检测边界和底部堆叠的碰撞

# 怎么计算消除

  1. 可以直接遍历棋盘的每行,只要每行坑位都为 1 就消除
  2. 消除操作直接从堆叠部分干掉应该消除的行,然后重新渲染棋盘

# 代码实现

下面就是体力活儿 👇🏻

<!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>
1
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(): 启动游戏。在游戏开始时,创建一个新的随机方块,并以一定的时间间隔不断向下移动方块。同时,监听键盘按键事件。

上次更新: 2023/06/05, 07:13:51
最近更新
01
JavaScript-test
07-20
02
二维码的原理
07-20
03
利用ChatGPT优化代码
07-20
更多文章>
Theme by Vdoing | Copyright © 2021-2023 XingYun | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式