0基础编写微信小程序-2048小游戏
博客班级 | https://edu.cnblogs.com/campus/zjcsxy/SE2020/ |
作业要求 | https://edu.cnblogs.com/campus/zjcsxy/SE2020/homework/11334 |
作业目标 |
|
作业源代码 | https://github.com/risedMer/2048-miniprogram |
学号 | 31801120 |
姓名 | 梅景添 |
院系 | 浙大城市学院计算机系 |
前言:软件工程的第一次作业,主要难点在于2048的计算算法,本文主要讲解微信小程序版本的2048小游戏编写,含源代码和注解。
最终版本的地址:https://github.com/risedMer/2048-miniprogram
开发工具:微信开发者工具
=====================================
2020.10.21更新
在原有的小程序基础上新增了三个界面
界面一:开始游戏界面,使游戏不再已进入就启动
界面二:关于界面,关于程序开发的基础信息
界面三:最高分界面,可查看历史最高分
新增界面的内容不涉及有技术的算法操作,仅在样式以及界面上增加内容,故代码补贴出,完整代码见底部github链接
=======================================
效果演示
全局配置
app.json:
enablePullDownRefresh务必设置为false,以禁止小程序下拉刷新
其余三个的值可依个人喜好自行设置,注:backgroundTextStyle仅支持dark/light
具体配置可参见官方文档: https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html
1 { 2 "pages": [ 3 "pages/start/start", 4 "pages/index/index", 5 "pages/maxScore/maxScore", 6 "pages/about/about" 7 ], 8 "window": { 9 "backgroundTextStyle": "light", //小程序导航栏标题高亮 10 "navigationBarBackgroundColor": "#000000", //小程序导航栏颜色(16进制) 11 "navigationBarTitleText": "Mer\'s 2048", //小程序导航栏标题 12 "enablePullDownRefresh": false //禁止小程序页面下拉刷新 13 }, 14 "sitemapLocation": "sitemap36.json" 15 }
app.js
小程序启动时从缓存中读取日期信息
1 App({ 2 onLaunch: function () { 3 var logs = wx.getStorageSync(\'logs\') || [] 4 logs.unshift(Date.now()) 5 wx.setStorageSync(\'logs\', logs) 6 } 7 })
app.wxss
全局样式配置文件
1 .container { 2 height: 100%; 3 display: flex; 4 flex-direction: column; 5 align-items: center; 6 justify-content: space-between; 7 padding: 200rpx 0; 8 box-sizing: border-box; 9 }
小程序核心部分
以上内容为小程序的全局配置,用于小程序的整体配置,以下内容进入正题,主要分为3块内容:
1. 2048的页面布局(index.wxml)
2. 2048的样式文件(index.wxss)
3. 2048的程序运行逻辑,也是该小程序中最为核心且最为复杂的一部分(index.js)
(还有一个index.json文件,由于该小程序未引入其他自定义组件,故该文件不影响小程序的运行,呈默认状态{ “usingComponents”: {}})
index.wxml
<view class = "action_cavas" bindtouchstart = "tapStart" bindtouchmove = "tapMove" bindtouchend = "tapEnd"> <view class = "score"> <view class = "title">2048</view> <view class = "scoredetail"> <view class = "scoredesc">历史最高</view> <view class = "scorenumber">{{maxscore}}</view> </view> </view> <view class = "bc_cavas"> <view class = "bc" wx:for = "{{numbers}}" wx:for-item = "row" > <view wx:for = "{{row}}" class = "bc_ bc_{{item}}">{{item}}</view> </view> </view> </view> <modal class = "modal" hidden = "{{modalHidden}}" bindconfirm = "modalChange" bindcancel = "modalCancle"> <view>游戏结束</view> </modal>
index.wxss
1 .score { 2 display: flex; 3 } 4 5 .title{ 6 flex:1; 7 height: 150rpx; 8 line-height: 150rpx; 9 background:#eec22e; 10 margin: 80rpx 20rpx 40rpx 40rpx; 11 text-align: center; 12 font-size: 1.5rem; 13 color: white; 14 border-radius: 10rpx; 15 } 16 17 18 .scoredetail{ 19 flex: 1; 20 height: 150rpx; 21 background:#eee4da; 22 margin: 80rpx 20rpx 40rpx 20rpx; 23 text-align: center; 24 border-radius: 10rpx; 25 } 26 27 .scoredetail:last-child{ 28 margin-right: 40rpx; 29 } 30 31 .scoredesc{ 32 font-size: 0.8rem; 33 line-height: 60rpx; 34 } 35 .scorenumber{ 36 line-height: 70rpx; 37 } 38 39 .bc_{ 40 height: 152rpx; 41 width: 152rpx; 42 margin: 6rpx 6rpx; 43 line-height: 152rpx; 44 text-align: center; 45 font-size: 60rpx; 46 color: #fff7eb; 47 } 48 .bc_0{ 49 color:#ccc0b2; 50 background: #ccc0b2; 51 } 52 .bc_2 53 { 54 color: #7c736a; 55 background: #eee4da; 56 } 57 .bc_4 58 { 59 color: #7c736a; 60 background: #ece0c8; 61 } 62 .bc_8 63 { 64 color: #fff7eb; 65 background: #f2b179; 66 } 67 .bc_16 68 { 69 color:#fff7eb; 70 background:#f59563; 71 } 72 .bc_32 73 { 74 color:#fff7eb; 75 background:#f57c5f; 76 } 77 .bc_64 78 { 79 color:#fff7eb; 80 background:#f65d3b; 81 } 82 .bc_128 83 { 84 color:#fff7eb; 85 background:#edce71; 86 } 87 .bc_256 88 { 89 color:#fff7eb; 90 background:#edcc61; 91 } 92 .bc_512 93 { 94 color:#fff7eb; 95 background:#ecc850; 96 } 97 .bc_1024 98 { 99 color:#fff7eb; 100 background:#edc53f; 101 } 102 .bc_2048 103 { 104 color:#fff7eb; 105 background:#eec22e; 106 } 107 .bc{ 108 display: flex; 109 } 110 111 .bc_cavas{ 112 display: flex; 113 height: 670rpx; 114 background-color: #b8af9e; 115 justify-content: center; 116 align-content: center; 117 flex-wrap:wrap; 118 margin: 10rpx 40rpx; 119 border-radius: 10rpx; 120 } 121 122 .action_cavas { 123 width:100%; 124 height: 100%; 125 } 126 127 .intro{ 128 display: flex; 129 margin: 0 60rpx; 130 font-size: small; 131 color: #fff7eb; 132 justify-content:flex-end; 133 }
index.js
该部分代码略长,下面将其分开详细讲解
data部分,2048的数据初始化界面
1 data: { 2 score: 0, //当前得分 3 maxscore: 0, //最高分在界面上方显示 4 startx: 0, //坐标初始化为0 5 starty: 0, 6 endx: 0, 7 endy: 0, 8 direction: \'\', 9 numbers: [[],[],[],[]], //该嵌套数组用于2048的游戏布局4*4网格,先置空,在接下来的onLoad块中将其初始化 10 modalHidden: true, 11 }
onLoad:function()
小程序启动后进入第一个页面首先加载的函数,此处涉及到小程序页面的生命周期,详情可见以下链接:
https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/page-life-cycle.html
1 onLoad: function() { 2 var maxscore = wx.getStorageSync(\'maxscore\') //从缓存中读取最大值 3 if(!maxscore) maxscore = 0 //若缓存中没有最大值的记录,则将其置为0 4 let num = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]] 5 let j = 0 //该处循环为4*4网格的初始化,由于实在想不到其他简易的初始化方案,于是选择了random的随机数,并嵌套了几层尝试让初始化的界面不会有很多数字 6 for(;j < 4;j++) { 7 num[0][j] = 2 * (Math.floor((Math.random() * Math.floor(Math.random() * 4)))) 8 num[1][j] = 2 * (Math.floor((Math.random() * 2))) 9 num[2][j] = 2 * (Math.floor((Math.random() * 1))) 10 num[3][j] = 2 * (Math.floor((Math.random() * 2))) 11 num[j][0] = 2 * (Math.floor((Math.random() * Math.floor(Math.random() * 4)))) 12 num[j][2] = 2 * (Math.floor((Math.random() * 1))) 13 num[j][1] = 2 * (Math.floor((Math.random() * Math.floor(Math.random() * 4)))) 14 num[j][3] = 2 * (Math.floor((Math.random() * 1))) 15 } 16 //将值读取后置给data 17 this.setData({ 18 maxscore:maxscore, 19 numbers:num 20 }) 21 }
将玩游戏的过程中产生的最高分存入缓存,并更新游戏界面最上方的最高分分值
1 storeScore:function(){ 2 console.log(this.data.maxscore, this.data.score) 3 if(this.data.maxscore < this.data.score){ 4 this.setData({ 5 maxscore:this.data.score 6 }) 7 wx.setStorageSync(\'maxscore\', this.data.maxscore) 8 } 9 },
触摸屏幕后的动作判定,根据手指在屏幕的初始触点和离开屏幕后的结尾触点计算出是向左、向右、向上、向下四个方向的操作并将值赋给一个dir变量
1 tapStart: function(event){ 2 this.setData({ 3 startx: event.touches[0].pageX, 4 starty: event.touches[0].pageY 5 }) 6 }, 7 8 tapMove: function(event){ 9 this.setData({ 10 endx: event.touches[0].pageX, 11 endy: event.touches[0].pageY 12 }) 13 }, 14 tapEnd: function(event){ 15 var heng = (this.data.endx) ? (this.data.endx - this.data.startx) : 0; 16 var shu = (this.data.endy) ? (this.data.endy - this.data.starty) : 0; 17 console.log(heng, shu); 18 if(Math.abs(heng) > 5 || Math.abs(shu) > 5){ 19 var direction = (Math.abs(heng) > Math.abs(shu)) ? this.computeDir(1, heng):this.computeDir(0, shu); 20 this.setData({ 21 startx:0, 22 starty:0, 23 endx:0, 24 endy:0 25 }) 26 this.mergeAll(direction) && this.randInsert(); 27 } 28 }, 29 computeDir: function(heng, num){ 30 if(heng) return (num > 0) ? \'right\' : \'left\'; 31 return (num > 0) ? \'bottom\' : \'top\'; 32 },
mergeAll函数通过一个switch的操作将手指在屏幕的操作调动起来,来进入各个动作操作的函数,让方块进行合并,计算数值
1 mergeAll: function(dir){ 2 this.checkGame(); 3 switch(dir){ 4 case \'left\': 5 return this.mergeleft(); 6 break; 7 case \'right\': 8 return this.mergeright(); 9 break; 10 case \'top\': 11 return this.mergetop(); 12 break; 13 case \'bottom\': 14 return this.mergebottom(); 15 break; 16 default: 17 } 18 },
以下函数为将方块进行合并的过程,原理均一致故只展示向左滑动时合并方块的过程
只要会一个方向的数值处理,其他方向和该操作一致,不同处仅符号的变化和x,y轴的变化
1 mergeleft: function(){ 2 var change = false; 3 var arr = this.data.numbers; 4 for(var i = 0; i < 4; i++){ 5 for(var j = 0; j < 3; j++){ 6 if(arr[i][j] == 0) continue; 7 for(var k = 1; k < 4-j; k++){ 8 if(arr[i][j] != 0 && arr[i][j+k] != 0){ 9 if(arr[i][j] != arr[i][j+k]) break; 10 arr[i][j] = arr[i][j] *2; 11 arr[i][j+k] = 0; 12 change = true; 13 if(this.data.score < arr[i][j]){ 14 this.setData({score:arr[i][j]}) 15 } 16 break; 17 } 18 } 19 } 20 for(var j = 0; j < 3; j++){ 21 if(arr[i][j] == 0){ 22 for(var k = 1; k < 4-j; k++){ 23 if(arr[i][j+k] != 0){ 24 arr[i][j] = arr[i][j+k]; 25 arr[i][j+k] = 0; 26 change = true; 27 break; 28 } 29 } 30 } 31 } 32 } 33 this.setData({ 34 numbers:arr 35 }) 36 this.storeScore() 37 return change 38 },
随机插入函数
在每次滑动后随机向一个为0的格子内插入一个数值2的方格
1 randInsert: function(){ 2 var arr = this.data.numbers 3 var num = Math.random() < 0.8 ? 2 : 4 4 var zeros = []; 5 for(var i = 0; i < 4; i++){ 6 for(var j = 0; j < 4; j++){ 7 if(arr[i][j] == 0){ 8 zeros.push([i, j]); 9 } 10 } 11 } 12 var position = zeros[Math.floor(Math.random() * zeros.length)]; 13 arr[position[0]][position[1]] = num 14 this.setData({ 15 numbers:arr 16 }) 17 },
游戏检查函数
该部分函数的主要作用就是检查游戏是否结束,若每个格子均已有非零数字填充,且相邻的格子无法进行合并,则进入modalCancle函数,弹出游戏结束窗口
1 checkGame: function(){ 2 var arr = this.data.numbers 3 for(var i = 0; i < 4; i++){ 4 for(var j = 0; j < 4; j++){ 5 if(arr[i][j] == 0) 6 return; 7 } 8 } 9 for(var i = 0; i < 3; i++){ 10 for(var j = 0; j < 3; j++) 11 if(arr[i][j] == arr[i+1][j] || arr[i][j] == arr[i][j+1]) 12 return; 13 } 14 for(var j = 0; j < 3; j++) { 15 if(arr[3][j] == arr[3][j+1]) 16 return; 17 if(arr[j][3] == arr[j+1][3]) 18 return; 19 } 20 this.setData({ 21 modalHidden: false, 22 }) 23 },
若游戏结束则弹出弹窗询问取消或重新开始
(重现了一次初始化4*4网格的蛇皮操作)
1 modalChange:function(){ 2 let num = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]] 3 let j = 0 4 for(;j < 4;j++) { 5 num[0][j] = 2 * (Math.floor((Math.random() * Math.floor(Math.random() * 4)))) 6 num[1][j] = 2 * (Math.floor((Math.random() * 2))) 7 num[2][j] = 2 * (Math.floor((Math.random() * 1))) 8 num[3][j] = 2 * (Math.floor((Math.random() * 2))) 9 num[j][0] = 2 * (Math.floor((Math.random() * Math.floor(Math.random() * 4)))) 10 num[j][2] = 2 * (Math.floor((Math.random() * 1))) 11 num[j][1] = 2 * (Math.floor((Math.random() * Math.floor(Math.random() * 4)))) 12 num[j][3] = 2 * (Math.floor((Math.random() * 1))) 13 } 14 this.setData({ 15 score: 0, 16 numbers: num, 17 modalHidden: true, 18 }) 19 }, 20 modalCancle:function(){ 21 this.setData({ 22 modalHidden: true, 23 }) 24 }
以上即本次作业的全部代码,所有代码均已完整上传至github,链接如下:
https://github.com/risedMer/2048-miniprogram
若存在不足之处请在评论区随意指出,本人愿虚心求教将此代码更进一步的更新完善。
=============================
2020-10-18