自己动手开发小程序版俄罗斯方块

发布时间 2023-12-07 18:19:13作者: 。活着。

最近自己写了一个俄罗斯方块的小程序,基本处理完了所有BUG,尽量做到了代码简洁、功能完善,目前感觉唯一不太完美的地方是下键的短按和长按效果,完整源码如下:

 

WXML(使用了weui组件库的图标)

 1 <view class='container'>
 2   <view class="flex output">
 3     <view>分数:{{score}}</view>
 4     <view>等级:{{speed}}</view>
 5     <view>最高分:{{maxScore}}</view>
 6   </view>
 7   <view class='map'>
 8     <view wx:for="{{newMap}}" wx:for-item="rows" class='flex'>
 9       <view wx:for="{{rows}}" class='block block{{item}}'></view>
10     </view>
11   </view>
12   <view class="flex control">
13     <view>
14       <view class="flex button">
15         <mp-icon type="field" icon="back2" color="#FF0000" size="{{80}}" data-dir="left" bindlongpress="longPress" bindtap="horTap" bindtouchend="touchEnd" />
16         <mp-icon extClass="icon-right" type="field" icon="back2" color="#FF0000" size="{{80}}" data-dir="right" bindlongpress="longPress" bindtap="horTap" bindtouchend="touchEnd" />
17       </view>
18       <view class="center">
19         <mp-icon extClass="icon-down" type="field" icon="back2" color="#FF0000" size="{{80}}" data-dir="down" bindlongpress="longPress" bindtap="downTap" bindtouchend="touchEnd" />
20       </view>
21     </view>
22     <view>
23       <view class="center">
24         <mp-icon type="field" icon="{{icon}}" color="#FF0000" size="{{60}}" bindtap="play" />
25       </view>
26       <view>
27         <view wx:for="{{newNextMap}}" wx:for-item="rows" class='flex'>
28           <view wx:for="{{rows}}" class='block-next block{{item}}'></view>
29         </view>
30       </view>
31     </view>
32     <view>
33       <mp-icon type="field" icon="refresh" color="#FF0000" size="{{100}}" bindtap="rotateBlock" />
34     </view>
35   </view>

 

WXSS

 1 .map {
 2   margin-top: 30rpx;
 3 }
 4 .flex {
 5   display: flex;
 6 }
 7 .output {
 8   width: 90%;
 9   justify-content: space-between;
10 }
11 .block {
12   width: 50rpx;
13   height: 50rpx;
14   border: 1px solid #eee;
15   background: #000;
16 }
17 .block1 {
18   background: green;
19 }
20 .control {
21   margin-top: 20rpx;
22   justify-content: space-between;
23   align-items: center;
24 }
25 .center {
26   text-align: center;
27 }
28 .button {
29   justify-content: space-between
30 }
31 .icon-right {
32   transform: rotate(180deg);
33 }
34 .icon-down {
35   transform: rotate(-90deg);
36 }
37 .block-next {
38   width: 30rpx;
39   height: 30rpx;
40   border: 1px solid #eee;
41 }

 

JS

  1 Page({
  2   data: {
  3     //地图大小
  4     mapSize: [18, 10],
  5     //地图
  6     map: [],
  7     newMap: [],
  8     nextMap: [],
  9     newNextMap: [],
 10     //方块
 11     block: [],
 12     nextBlock: [],
 13     //游戏状态
 14     icon: '',
 15     status: '',
 16     timer: null,
 17     quickTimer: null,
 18     speed: 1,
 19     score: 0,
 20     maxScore: 0,
 21     //方块位置
 22     blockPos: [0, 0],
 23     //七种方块
 24     blocks: [
 25       [[1, 0], [1, 1], [1, 2], [2, 1]],
 26       [[1, 1], [2, 1], [3, 1], [3, 2]],
 27       [[1, 2], [2, 2], [3, 1], [3, 2]],
 28       [[1, 0], [1, 1], [2, 1], [2, 2]],
 29       [[1, 1], [1, 2], [2, 0], [2, 1]],
 30       [[1, 1], [1, 2], [2, 1], [2, 2]],
 31       [[0, 1], [1, 1], [2, 1], [3, 1]]
 32     ]
 33   },
 34   onLoad(options) {
 35     let maxScore = 0
 36     if (wx.getStorageSync("tetrisScore")) {
 37       maxScore = wx.getStorageSync("tetrisScore")
 38     }
 39     //初始化地图
 40     let map = this.initMap(this.data.mapSize)
 41     let nextMap = this.initMap([4, 4])
 42     //随机一个方块
 43     let nextBlock = this.data.blocks[Math.floor(Math.random() * 7)]
 44     this.setData({
 45       map: map,
 46       newMap: map,
 47       nextMap: nextMap,
 48       newNextMap: nextMap,
 49       nextBlock: nextBlock,
 50       icon: 'play',
 51       status: 'stop',
 52       score: 0,
 53       speed: 1,
 54       maxScore: maxScore
 55     })
 56   },
 57   //初始化地图
 58   initMap: function (size) {
 59     let map = []
 60     for (let i = 0; i < size[0]; i++) {
 61       let rows = []
 62       for (let j = 0; j < size[1]; j++) {
 63         rows.push(0)
 64       }
 65       map.push(rows)
 66     }
 67     return map
 68   },
 69   //初始化方块
 70   initBlock: function () {
 71     //随机方向
 72     let block = this.rotateFun(this.data.nextBlock, Math.floor(Math.random() * 4))
 73     //移到中间
 74     this.moveFun(block, [-3, 3], 0)
 75     //生成下一个方块
 76     let nextBlock = this.data.blocks[Math.floor(Math.random() * 7)]
 77     //更新小图
 78     let newNextMap = JSON.parse(JSON.stringify(this.data.nextMap))
 79     for (let item of nextBlock) {
 80       newNextMap[item[0]][item[1]] = 1
 81     }
 82     this.setData({
 83       block: block,
 84       nextBlock: nextBlock,
 85       newNextMap: newNextMap
 86     })
 87   },
 88   play: function () {
 89     let icon, status = this.data.status
 90     if (status == 'play') {
 91       icon = 'play'
 92       status = 'pause'
 93       clearInterval(this.data.timer)
 94     } else {
 95       if (status == 'stop') {
 96         this.initBlock()
 97       }
 98       this.fallFun()
 99       icon = 'pause'
100       status = 'play'
101     }
102     this.setData({
103       icon: icon,
104       status: status
105     })
106   },
107   //下落方法
108   fallFun: function () {
109     let that = this
110     this.data.timer = setInterval(function () {
111       that.moveFun(that.data.block, that.posFun(that.data.blockPos, 'down'), 1)
112     }, (21 - that.data.speed) * 50)
113   },
114   //左、右短按
115   horTap: function (e) {
116     if (this.data.status == 'play') {
117       this.moveFun(this.data.block, this.posFun(this.data.blockPos, e.currentTarget.dataset.dir))
118     }
119   },
120   //下短按
121   downTap: function () {
122     if (this.data.status == 'play') {
123       let that = this
124       let i = 0
125       let timer = setInterval(function () {
126         that.moveFun(that.data.block, that.posFun(that.data.blockPos, 'down'))
127         i++
128         if (i >= 5) clearInterval(timer)
129       }, 10)
130     }
131   },
132   //长按
133   longPress: function (e) {
134     if (this.data.status == 'play') {
135       let that = this
136       this.data.quickTimer = setInterval(function () {
137         that.moveFun(that.data.block, that.posFun(that.data.blockPos, e.currentTarget.dataset.dir))
138       }, 10)
139     }
140   },
141   //按键手指移开
142   touchEnd: function (e) {
143     clearInterval(this.data.quickTimer)
144   },
145   //计算移动后坐标
146   posFun: function (pos, dir) {
147     let newPos = []
148     switch (dir) {
149       case 'left':
150         newPos = [pos[0], pos[1] - 1]
151         break;
152       case 'right':
153         newPos = [pos[0], pos[1] + 1]
154         break;
155       case 'down':
156         newPos = [pos[0] + 1, pos[1]]
157         break;
158     }
159     return newPos
160   },
161   //移动方法,type:0初始移到中间,1正常下落
162   moveFun: function (block, pos, type) {
163     let m = pos[0], n = pos[1]
164     let mapSize = this.data.mapSize
165     let maxX = m, reLoop = true
166     let map = JSON.parse(JSON.stringify(this.data.map))
167     for (let index = 0; index < block.length; index++) {
168       let i = block[index][0] + m, j = block[index][1] + n
169       //超出边界,或已有方块,取消移动
170       if (i >= mapSize[0] || j < 0 || j >= mapSize[1] || (i >= 0 && map[i][j] == 1) == 1) {
171         if (type == 1) {
172           clearInterval(this.data.timer)
173           //游戏结束
174           if (map[0][j] == 1) {
175             let scroe = this.data.score
176             wx.showModal({
177               title: '游戏结束',
178               content: '得分:' + scroe,
179               showCancel: false,
180             })
181             if (scroe > this.data.maxScore) wx.setStorageSync("tetrisScore", scroe)
182             this.onLoad()
183             return false
184           }
185           //消除
186           let newMap = this.data.newMap
187           loop: for (let a = mapSize[0] - 1; a >= 0; a--) {
188             for (let val of newMap[a]) {
189               if (val == 0) continue loop
190             }
191             newMap.splice(a, 1)
192           }
193           let len = mapSize[0] - newMap.length
194           for (let a = 0; a < len; a++) {
195             let row = []
196             for (let b = 0; b < mapSize[1]; b++) {
197               row.push(0)
198             }
199             newMap.unshift(row)
200           }
201           //初始化方块
202           let score = this.data.score
203           if (len > 0) score += (len * 100 - 50)
204           let speed = parseInt(score / 1000) + 1
205           if (speed > 20) speed = 20
206           this.setData({
207             map: newMap,
208             score: score,
209             speed: speed
210           })
211           this.initBlock()
212           this.fallFun()
213         }
214         return false
215       }
216       if (type == 0) {
217         if (reLoop) {
218           //计算方块最大横坐标
219           if (i > maxX) maxX = i
220           //开始下一次循环
221           if (index == block.length - 1) {
222             index = -1
223             reLoop = false
224           }
225           continue
226         }
227         if (maxX > m) i -= maxX
228       }
229       //移动到新位置
230       if (i >= 0) map[i][j] = 1
231     }
232     this.setData({
233       blockPos: [(maxX > m ? m - maxX : m), n],
234       newMap: map
235     })
236     return true
237   },
238   //按键转动
239   rotateBlock: function (e) {
240     if (this.data.status == 'play') {
241       let block = this.rotateFun(this.data.block, 1)
242       if (this.moveFun(block, this.data.blockPos)) {
243         this.setData({
244           block: block
245         })
246       }
247     }
248   },
249   //顺时针旋转方块
250   rotateFun: function (block, m) {
251     if (m > 0) {
252       let newBlock = []
253       for (let i in block) {
254         newBlock.push(this.rotateRe(block[i], m))
255       }
256       return newBlock
257     }
258     return block
259   },
260   rotateRe: function (arr, m) {
261     arr = [arr[1], 3 - arr[0]]
262     if (m == 1)
263       return arr
264     return this.rotateRe(arr, m - 1)
265   },
266   onUnload() {
267     clearInterval(this.data.timer)
268   }
269 })