1.扫雷需求分析
- 初始化部分
- 扩散算法(DFS搜索)
- 鼠标左键右键事件处理部分
- 游戏成功与失败部分
- 游戏进程部分(开始,结束,暂停,继续等)
2.初始化部分
- 初始化基础数据(initDefault)
初始化雷数、雷盘行列数、雷坐标存储数组、标识存储数、总格数、时间、标识雷数、雷盘width/height、显示时间和剩余雷数的初样式
//初始化一些默认值 function initDefault () { landmineCon.style.width = rowNum * 20 + rowNum + 1 + 'px'; landmineCon.style.width = colNum * 20 + colNum + 1 +'px'; time.children[0].innerHTML = ''; time.children[1].innerHTML = ''; mineCount.innerHTML = mineNum; mineXPos = []; mineYPos = []; identifyMinePosX = []; identifyMinePosY = []; mineIdentifyCount = 0; gameTime = 0; grids = colNum * rowNum; isStarted = false; } 复制代码
- 随机生成雷的坐标(randomMinePos)
这里主要的难点在于怎么样保证生成的雷不同。这里我使用的方式是,将雷的坐标x,y转化成'xy'存到arr数组中,每次随机生成一个雷,就调用arr.indexOf('xy')查询是否出现。
//随机生成雷的位置 function randomMinePos () { var arr = []; for (var i = 0; i<mineNum; i++){ var x = Math.floor(Math.random()*rowNum), y = Math.floor(Math.random()*colNum); while (arr.indexOf(''+x+y) !== -1) { x = Math.floor(Math.random()*rowNum); y = Math.floor(Math.random()*colNum); } arr.push(''+x+y); mineXPos.push(x); mineYPos.push(y); } } 复制代码
- 动态初始化ul(initLi)
因为游戏存在三个难度,每个难度的雷盘是不一样大的,因此每次选择难度后,需要重新生成一次雷盘。
但是源于考虑reflow的影响: - 使用DocumentFragment一次插入 - 如果当前的li个数小于需求的li个数,在基础上生成 - 如果当前的li个数小于需求的li个数,直接全部删除,重新添加
//动态插入li function initLi () { var len = mineGrid.length, grids = colNum * rowNum; if (len < grids) { var fragment = createFragment(); for (var i = 0; i<grids - len; i++) { var li = createLi(); fragment.appendChild(li); } mineUl.appendChild(fragment); }else if (len > grids) { mineUl.innerHTML = ''; var fragment = createFragment(); for (var i = 0; i<grids; i++) { var li = createLi(); fragment.appendChild(li); } mineUl.appendChild(fragment); } } 复制代码
- 初始化mineMap和visited数组(initMineMap)
- 初始化的mineMap数组为0
- mapIsVis全部赋值为false,代表没有被遍历过
- 先把雷赋予mineMap数组(雷用"#"表示)
//生成雷盘 function initMineMap () { //初始化雷盘 for (var i = 0; i<rowNum; i++) { mineMap[i] = new Array(colNum); mapIsVis[i] = new Array(colNum); for (var j = 0; j<colNum; j++){ mineMap[i][j] = 0; //雷盘开始格子全部初始化为0 mapIsVis[i][j] = false;//开始全没有被访问过 mineGrid[reaerchIndexId(i, j)].innerHTML = ''; mineGrid[reaerchIndexId(i, j)].style.backgroundColor = 'rgb(192,192,192)'; } } //添加地雷 for (var i = 0; i<mineNum; i++){ mineMap[mineXPos[i]][mineYPos[i]] = '#';//#代表地雷 } } 复制代码
- 计算mineMap数组(calcMineMap): 计算mineMap数组
//计算mineMap function calcMineMap() { //计算每个格子附近的雷数 for (var i = 0; i<mineNum; i++) { for (var j = 0; j<8; j++){ rx = mineXPos[i] + xx[j]; ry = mineYPos[i] + yy[j]; if (rx < 0 || rx >= rowNum || ry < 0 || ry >= colNum || mineMap[rx][ry] === '#') { continue; }else { mineMap[rx][ry] ++; } } } } 复制代码
- 为元素绑定事件(initAddEvent)
//绑定事件 function initAddEvent() { mineUl.addEventListener('click', mineGame, false); mineUl.addEventListener('contextmenu', identify, false); newgame.addEventListener('click', newGame, false); continueBtn.addEventListener('click', continueGame, false); pause.addEventListener('click', startOrPause, false); } 复制代码
3.扩散算法(DFS)
深度搜素(本质上是一种递归)
深度搜索一定注意把搜索过的点mapIsVis = true; 如果后面在需要注意回溯
4.鼠标左键右键事件处理部分
- 鼠标左键点击(mineGame)
- 如果mineMap为0,DFS
- 如果mineMap为#,gameOver
- 如果mineMap>0, 只显示就可以
- 鼠标右键点击(identify): 一次点击标🚩
当标识旗子数目达到mineNum时,进行检查,如果全部标识正确,gameSuccess(); 如果存在错误利用model提示。
- 二次点击标❓
- 三次点击取消
由于剩余雷数与旗子个数有关系,因此每一次样式由旗子改编为其他值,都要注意修正当前的标识旗子总数和剩余雷数
5.游戏成功与失败部分
- 游戏成功(gameSuccess)
游戏有两种胜利方式:
- 雷全部被标识出来(testIdentify)
- 只剩雷没有被点击(比较grids === mineNum)
- 游戏失败(gameOver)
当点击到的位置mineMap为#
6.游戏进程部分
- 游戏开始(startOrPause)
当点击笑脸之后,游戏才真正开始。
实现原理:给鼠标左右键事件处理函数套一个锁,之后笑脸点击之后才被打开。
- 游戏暂停(startOrPause)
- 游戏继续(continueGame)
- 游戏重新开始(newGame)
- 返回主菜单(returnMainMenu)
因为存在计时问题,所以都应该注意定时器的清除与重新建立