├── .gitignore ├── README.md ├── app.js ├── app.json ├── app.wxss └── pages ├── images └── icon │ ├── emoticon_happy.png │ └── emoticon_sad.png └── index ├── index.js ├── index.wxml └── index.wxss /.gitignore: -------------------------------------------------------------------------------- 1 | .DB_Store 2 | *.swp 3 | *.log 4 | public/ 5 | .deploy*/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 微信小程序这几天很火,9号正式发布,今天19号过了10天,到现在略有降温的趋势。个大厂商对微信小程序的态度很不看好,最激进的要数[罗辑思维],直接下架了自己的[得到APP]。滴滴、京东等APP也只保留了最精简的功能,某些担心过于完善的功能会抢夺APP本身的用户。不过这也印证了小程序的初衷,小而精、用完即走。(并不否认腾讯微信平台确实流氓的事实) 2 | 3 | >PS:墨迹天气也开发了微信小程序版本,并且在1月9号第一时间发布,欢迎使用。 4 | 5 | 作为技术人员,怎能不亲手试试小程序开发呢?扫雷 Minesweeper 6 | 7 | ## 实现思路: 8 | 9 | * 如果不了解扫雷的游戏规则,请自行 Google 10 | * 绘制一张 10*10 的地图,并且标注地雷和非地雷,并且需要用数字表示每个点周围地雷的数量 11 | * 用 mineMap[x][y] 表示一个点位 12 | * mineMap[x][y] < 0 (代码中 == -1) 初始状态 13 | * 0 < mineMap[x][y] < 9 该点周围地雷的数量 14 | * mineMap[x][y] == 9 表示该点是地雷 15 | * mineMap[x][y] > 9 (代码中 == 10)表示该点插旗 16 | 17 | ## Github 18 | * https://github.com/jixiaod/wechat-app-minesweeper 19 | 20 | ## 如何跑起来? 21 | * 下载&安装微信小程序开发工具 22 | * 配置 wechat-app-minesweeper 代码目录到开发工具即可 23 | 24 | ## 玩法 25 | * 需要注意的是,由于没有鼠标右键标记Flag,简单做了 Flag 切换,开启后,方可标记Flag 26 | * 最初的想法是通过 tap 和 longtap 来区分扫雷和Flag,但是开发工具不能区分这俩个事件,只能作罢 27 | 28 | ![](http://blog.100dos.com/images/wechat-minesweeper.jpeg) 29 | 30 | 31 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | //app.js 2 | App({ 3 | onLaunch: function () {} 4 | }) 5 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":[ 3 | "pages/index/index" 4 | ], 5 | "window":{ 6 | "backgroundTextStyle":"light", 7 | "navigationBarBackgroundColor": "#fff", 8 | "navigationBarTitleText": "Minesweeper", 9 | "navigationBarTextStyle":"black" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | .container { 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: space-between; 8 | padding: 200rpx 0; 9 | box-sizing: border-box; 10 | } 11 | -------------------------------------------------------------------------------- /pages/images/icon/emoticon_happy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jixiaod/wechat-app-minesweeper/b29f11f5fdfff090beb2dc0dd755b0781eba3c57/pages/images/icon/emoticon_happy.png -------------------------------------------------------------------------------- /pages/images/icon/emoticon_sad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jixiaod/wechat-app-minesweeper/b29f11f5fdfff090beb2dc0dd755b0781eba3c57/pages/images/icon/emoticon_sad.png -------------------------------------------------------------------------------- /pages/index/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | var app = getApp() 4 | 5 | Page({ 6 | 7 | data: { 8 | mineMap: {}, 9 | timesGo: 0, 10 | minesLeft: 0 11 | }, 12 | 13 | mineMap: {}, 14 | mineMapMapping: {}, 15 | rowCount: 8, 16 | colCount: 8, 17 | mineCount: 8, 18 | minMineCount: 8, 19 | maxMineCount: 20, 20 | minesLeft: 0, 21 | timesGo: 0, 22 | timeInterval: null, 23 | flagOn: false, 24 | flags: 0, 25 | endOfTheGame: false, 26 | safeMinesGo: 0, 27 | 28 | onLoad: function() { 29 | 30 | this.setData({ 31 | minesLeft: 0, 32 | timesGo: 0 33 | }); 34 | this.drawMineField(); 35 | this.setData({ 36 | buttionText: 'START' 37 | }) 38 | 39 | }, 40 | 41 | setGame: function() { 42 | 43 | this.drawMineField(); 44 | this.createMinesMap(); 45 | this.setMinesLeft(); 46 | this.timeGoReset(); 47 | this.timeGoClock(); 48 | this.endOfTheGame = false; 49 | this.safeMinesGo = 0; 50 | this.setData({ 51 | buttionText: 'RESTART' 52 | }) 53 | 54 | }, 55 | 56 | setMinesLeft: function() { 57 | this.minesLeft = this.mineCount; 58 | this.setData({minesLeft: this.minesLeft}); 59 | }, 60 | 61 | timeGoClock: function() { 62 | var self = this; 63 | this.timeInterval = setInterval(function () { 64 | // console.log(self.data.timesGo); 65 | self.timesGo = self.timesGo + 1; 66 | self.setData({timesGo: self.timesGo}); 67 | 68 | }, 1000); 69 | }, 70 | 71 | timeGoStop: function() { 72 | 73 | clearInterval(this.timeInterval); 74 | }, 75 | 76 | timeGoReset: function() { 77 | clearInterval(this.timeInterval); 78 | this.timesGo = 0; 79 | this.setData({timesGo: this.timesGo}); 80 | }, 81 | 82 | createMinesMap: function() { 83 | 84 | var tmpMineMap = {}; 85 | // initalize mine map with 0. 86 | for (var row = 0; row < this.rowCount; row++) { 87 | 88 | tmpMineMap[row] = []; 89 | for (var col = 0; col < this.colCount; col++) { 90 | 91 | tmpMineMap[row][col] = 0; 92 | } 93 | } 94 | //console.log(tmpMineMap); 95 | 96 | // laying mines with 9 97 | this.mineCount = this.rangeRandom(this.minMineCount, this.maxMineCount); 98 | 99 | var tmpCount = this.mineCount; 100 | //console.log("Mine count: ", tmpCount); 101 | while (tmpCount > 0) { 102 | 103 | var row = this.rangeRandom(0, this.rowCount - 1); 104 | var col = this.rangeRandom(0, this.colCount - 1); 105 | 106 | if (tmpMineMap[row][col] != 9) { 107 | 108 | tmpMineMap[row][col] = 9; 109 | tmpCount--; 110 | } 111 | } 112 | 113 | // calculate numbers around mines. 114 | for (var row = 0; row < this.rowCount; row++) { 115 | for (var col = 0; col < this.colCount; col++) { 116 | var startRow = row - 1; 117 | var startCol = col - 1; 118 | //console.log("check====== r" +startRow +"c"+startCol ); 119 | for (var r = row-1; r < row+2; r++) { 120 | for (var c = col-1; c < col+2; c++) { 121 | //console.log("go: r"+r+":c"+c); 122 | if (c >= 0 && c < this.colCount 123 | && r >= 0 && r < this.rowCount 124 | && !(r === row && c === col) 125 | && tmpMineMap[r][c] == 9 126 | && tmpMineMap[row][col] != 9) { 127 | tmpMineMap[row][col]++; 128 | } 129 | } 130 | } 131 | } 132 | } 133 | this.mineMapMapping = tmpMineMap; 134 | }, 135 | 136 | drawMineField: function() { 137 | 138 | var tmpMineMap = {}; 139 | for (var row = 0; row < this.rowCount; row++) { 140 | 141 | tmpMineMap[row] = []; 142 | for (var col = 0; col < this.colCount; col++) { 143 | 144 | tmpMineMap[row][col] = -1; 145 | } 146 | } 147 | this.mineMap = tmpMineMap; 148 | //console.log(this.mineMap); 149 | 150 | this.setData({ 151 | mineMap: this.mineMap 152 | }) 153 | 154 | }, 155 | 156 | demining: function(event) { 157 | 158 | if (JSON.stringify(this.mineMapMapping) == "{}") return; 159 | 160 | 161 | var x = parseInt(event.target.dataset.x); 162 | var y = parseInt(event.target.dataset.y); 163 | var value = parseInt(event.target.dataset.value); 164 | //console.log("value:" + value +" x:"+x +" y:"+y); 165 | 166 | //flag this field as mine. 167 | if (this.flagOn) { 168 | 169 | this.flag(x, y, value); 170 | return; 171 | } 172 | 173 | // if field has been opened, return. 174 | if (value > 0) return; 175 | 176 | var valueMapping = this.mineMapMapping[x][y]; 177 | //console.log(this.mineMapMapping); 178 | //console.log(valueMapping); 179 | 180 | if (valueMapping < 9) { 181 | this.mineMap[x][y] = valueMapping; 182 | this.setData({mineMap: this.mineMap}); 183 | this.safeMinesGo++; 184 | console.log("Safe mine go: " + this.safeMinesGo); 185 | if ((this.safeMinesGo + this.mineCount) == (this.rowCount * this.colCount)) { 186 | this.success(); 187 | } 188 | } 189 | 190 | // When digg the mine. 191 | if (valueMapping == 9) { 192 | this.failed(); 193 | } 194 | 195 | // Open the fields with 0 mines arround. 196 | if (valueMapping == 0) { 197 | 198 | this.openZeroArround(x, y); 199 | this.setData({mineMap:this.mineMap}); 200 | } 201 | }, 202 | 203 | success: function() { 204 | 205 | wx.showToast({ 206 | title: 'Good Job !', 207 | image: '../images/icon/emoticon_happy.png', 208 | duration: 3000 209 | }) 210 | this.timeGoStop(); 211 | this.endOfTheGame = true; 212 | }, 213 | 214 | failed: function() { 215 | wx.showToast({ 216 | title: 'Bomb !!!', 217 | image: '../images/icon/emoticon_sad.png', 218 | mask: true, 219 | duration: 3000 220 | }) 221 | 222 | this.showAll(); 223 | this.timeGoStop(); 224 | this.endOfTheGame = true; 225 | }, 226 | 227 | // Open the fields arround 0 field recursively. 228 | openZeroArround: function(row, col) { 229 | //console.log("click" + row + " " + col) 230 | for (var r = (row-1); r < (row+2); r++) { 231 | for (var c = (col-1); c < (col+2); c++) { 232 | //console.log("go: r"+r+":c"+c); 233 | if (r >= 0 && r < this.rowCount 234 | && c >= 0 && c < this.colCount 235 | && !(r === row && c === col) 236 | && this.mineMap[r][c] < 0) { 237 | 238 | this.mineMap[r][c] = this.mineMapMapping[r][c]; 239 | this.safeMinesGo++; 240 | 241 | if (this.mineMapMapping[r][c] == 0) { 242 | this.openZeroArround(r, c); 243 | } 244 | 245 | } 246 | } 247 | } 248 | console.log("Safe mine go: " + this.safeMinesGo); 249 | if ((this.safeMinesGo + this.mineCount) == (this.rowCount * this.colCount)) { 250 | this.success(); 251 | } 252 | 253 | }, 254 | 255 | flagSwitch: function(e) { 256 | 257 | if (e.detail.value) { 258 | 259 | this.flagOn = true; 260 | } else { 261 | 262 | this.flagOn = false; 263 | } 264 | }, 265 | 266 | flag: function(x, y, value) { 267 | 268 | if (value > 0 && value < 10) return; 269 | 270 | // if flaged already, set the original state. 271 | if (value == 10) { 272 | 273 | this.pullUpFlag(x, y); 274 | return; 275 | } 276 | 277 | if (this.minesLeft <= 0) return; 278 | 279 | this.minesLeft = this.minesLeft - 1; 280 | this.mineMap[x][y] = 10; 281 | 282 | this.setData({mineMap: this.mineMap, minesLeft: this.minesLeft}); 283 | }, 284 | 285 | pullUpFlag: function(x, y) { 286 | 287 | if (this.minesLeft < this.mineCount) { 288 | this.minesLeft = this.minesLeft + 1; 289 | } 290 | this.mineMap[x][y] = -1; 291 | this.setData({mineMap: this.mineMap, minesLeft: this.minesLeft}); 292 | }, 293 | 294 | rangeRandom: function(x, y) { 295 | var z = y - x + 1; 296 | return Math.floor(Math.random() * z + x); 297 | }, 298 | 299 | showAll: function() { 300 | this.mineMap = this.mineMapMapping; 301 | this.setData({mineMap: this.mineMap}); 302 | } 303 | 304 | }); 305 | 306 | 307 | 308 | -------------------------------------------------------------------------------- /pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Mines Left: {{minesLeft}} 4 | Used Time: {{timesGo}} 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | - 点击 START 开始游戏,RESTART 重新开始\n- Switch 切换标志插旗 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | width: 750rpx; 3 | height: 100%; 4 | font-family: -apple-system-font, Helvetica Neue, Helvetica, sans-serif; 5 | } 6 | 7 | .wrapper { 8 | padding: 5px; 9 | } 10 | 11 | .flex-container { 12 | padding: 0; 13 | margin: 0 auto; 14 | list-style: none; 15 | display: flex; 16 | flex-flow: row; 17 | justify-content: space-around; 18 | line-height:1em; 19 | width: 90%; 20 | } 21 | 22 | .flex-item { 23 | background: #E6E9ED; 24 | margin: 1px; 25 | color: white; 26 | font-weight: bold; 27 | font-size: 14px; 28 | text-align: center; 29 | flex: 1 0 auto; 30 | height: 90rpx; 31 | width: 90rpx; 32 | font-family: "Lucida Console", Monaco, monospace 33 | } 34 | 35 | .open { 36 | background: #3BAFDA; 37 | } 38 | 39 | .mine { 40 | background: #E9573F; 41 | } 42 | 43 | .flag { 44 | 45 | background: #F6BB42; 46 | } 47 | 48 | .flex-item:before { 49 | content: ''; 50 | display: block; 51 | float: left; 52 | padding-top:100%; 53 | } 54 | 55 | .header { 56 | display: flex; 57 | font-size: 14px; 58 | line-height: 2.5em; 59 | text-align: center; 60 | } 61 | 62 | .header .label { 63 | flex: 1; 64 | } 65 | 66 | .footer { 67 | padding-top: 50rpx; 68 | display: flex; 69 | justify-content: space-around; 70 | align-items: center; 71 | } 72 | 73 | .button-hover { 74 | background-color: LightGrey; 75 | } 76 | 77 | .tips { 78 | margin-top: 20px; 79 | padding: 0 0 0 20px; 80 | font-size: 62.5%; 81 | color: #8d8d8d; 82 | } 83 | --------------------------------------------------------------------------------