最近自己写了一个俄罗斯方块的小程序,基本处理完了所有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 })