阅读 95

javascript 写一个简易扫雷游戏(不用canvas)

游戏逻辑:

  1. 首先随机在格子内生成若干个雷;

  2. 当玩家点击一个格子时: (1)如果是雷,那么展示出所有的雷,游戏结束; (2)如果不是雷:

  • 如果周围9宫格内有雷,则显示出雷的个数;

  • 如果自己周围没有雷,则不显示数字

先生成基本的样子

样子如下,目前点击后格子会变成黄色 image.png

class MineSweep {     constructor() {         // 这样也可以让用户赋值,但会更复杂一些         this.gridNumPerRow = 10         this.mineNum = 10     }     init() {         this.generateMines()         this.generateBoard()         console.log(this.generateMines())     }     /**      *  生成扫雷基本的格子,直接用div,借助css grid      */     generateBoard() {         const $wrapper = document.createElement('div')         $wrapper.classList.add('board-wrapper')         document.body.appendChild($wrapper)         let $grid;         for (let i = 0; i < this.gridNumPerRow * this.gridNumPerRow; i++) {             $grid = document.createElement('div')             $grid.classList.add('grid')             $wrapper.appendChild($grid)             $grid.onclick = this.handleClickGrid         }     }     /**      *  格子被点击后的处理       */     handleClickGrid() {         e.target.style.backgroundColor = 'yellow'     }               /**      *  随机生成雷的位置      *  要点是要注意不能有重复位置的雷      */     generateMines() {         // 这个函数重点是考虑如何生成不重复的雷,我刚开始想的是用把[row, column] 数组存入结果数组,         // 但如果这样的话,比对是否和前面有重复不方便,所以后来改成行用一个数组,列用一个数组,这样我们可以用includes直接判断是否数字有重复         const rows = []         const columns = []         let generatedRow         let generatedColumn         for (let i = 0; i < 10; i++) {             // 如果有重复,则继续生成             do {                 generatedRow = parseInt(Math.random() * 10)                 generatedColumn = parseInt(Math.random() * 10)             }             while (rows.includes(generatedRow) && columns.includes(generatedColumn))             // 已拿到未重复的数字对,放入数组中             rows.push(generatedRow)             columns.push(generatedColumn)         }         return {             rows,             columns         }     } } 复制代码

css

.board-wrapper {     width: 400px;     display: grid;     background-color: green;     gap: 0;     grid-template: repeat(10, 1fr) / repeat(10, 1fr); } .grid {     background-color: rgb(233, 233, 233);     width: 40px;     height: 40px;     border: 1px solid rgb(96, 96, 96); } 复制代码

现在已经有了基本的样子,考虑点击后显示数字/雷/空白

实现点击格子后出现内容

  • 思路1:让数字、雷元素一开始就已经加入再对应格子中,只不过其对应的div有个class是hide,所以它不可见。点击后这些内容可见,点到雷后,所有格子hide取消。

  • 思路2: 给每个格子添加属性value, -1表示自己是雷,其他数字表示周围雷的个数。只要这个属性添加好后,直接遍历,给每个格子添加对应innerHTML即可;

  • 思路3:   如何计算格子周围的雷的数量?假设要计算(3,3)格子周围的雷的数量,那么需要check周围8个格子,要计算(3,4)格子周围雷数量的话,周围格子有的和(3,3)是重叠的,又要check一次 。从另一个角度想,雷的数量比较少,我们可以直接遍历有雷的格子,把它周围的格子num++就可以了。

  • 思路4: 用一个含有100个数字的数组来存储每个格子的value。(本来想过用二维数组,后来觉得不需要,因为用0-99就可以区分这100个格子,而不是一定要用坐标来区分。不过这样的问题是:我们计算雷的个数时要用到坐标,坐标和0-99怎么转化?)

  • 思路5: 考虑把前面生成雷的函数由行列数组改为数字,例如生成88代表第88个格子是雷。然后88对应的九宫格其他格子为:78 87 89 98,也很好算。

所以再理一下:

  1. 用0-99存放雷的格子

  2. 新建一个数组用来存放value,长度为100,默认值都为0;

  3. 遍历雷数组,将雷自己的格子设置为-1,将每个雷周围的格子value++

  4. 遍历grid元素,如果元素value为-1,插入一个图片代表雷,如果value为0,什么也不做,如果value为其他,放入数字。并为每个雷元素放入对应class(例如hasMine),所有内部元素加上hide

  5. 点击元素时,内部元素hide取消。通过对应class来判断点中的是否是雷,是雷的话将所有hide取消。

写的过程中发现上面有一些问题,计算周围九宫格格子要负责一些,当格子在边角时的情况也需要考虑。

写到下面这个程度时,(显不隐藏格子内容),已经能正常显示出我们想要的内容。只不过点击内容还没写。

image.png

class MineSweep {     constructor() {         this.gridNumPerRow = 10         this.mineNum = 20         this.gridValues = []         this.gridHasMine = []     }     init() {         this.generateMines()         this.setGridValue()         this.generateBoard()     }     /**      *  生成扫雷基本的格子,直接用div,借助css grid      */     generateBoard() {         console.log(this.gridHasMine)         const $wrapper = document.createElement('div')         $wrapper.classList.add('board-wrapper')         document.body.appendChild($wrapper)         let $grid;         for (let i = 0; i < this.gridNumPerRow * this.gridNumPerRow; i++) {             $grid = document.createElement('div')             $grid.classList.add('grid')             $wrapper.appendChild($grid)             if (this.gridValues[i] == -1) {                 // 插入雷元素                 $grid.innerHTML = `<img src="./mine.png" class="hidden hasMine mine-img"></span>`             } else if (this.gridValues[i] > 0) {                 $grid.innerHTML = `<span>${this.gridValues[i]}</span>`             }             $grid.onclick = this.handleClickGrid         }     }     /**      *  格子被点击后的处理       */     handleClickGrid(e) {        //     }     /**      *  随机生成雷的位置      *  要点是要注意不能有重复位置的雷      */     generateMines() {         const res = []         // 用0-99代表每一个格子         let mineGrid         for (let i = 0; i < this.mineNum; i++) {             // 如果有重复,则继续生成             do {                 mineGrid = parseInt(Math.random() * 100)             }             while (res.includes(mineGrid))             // 放入结果数组中             res.push(mineGrid)         }         this.gridHasMine = res     }     /**      * 为每个格子设置对应的值,-1代表有雷,其他数字代表周围九宫格内雷的个数      */     setGridValue() {         // 设置100个默认值         const gridCount = this.gridNumPerRow * this.gridNumPerRow         for (let i = 0; i < gridCount; i++) {             this.gridValues.push(0)         }         // 用一个数组存放周围的8个格子的index,如果不存在设置为null         let arroundGrids         for (let i = 0; i < this.gridHasMine.length; i++) {             // 获取当前雷对应的格子             const currGrid = this.gridHasMine[i]             // 先将雷对应的格子设置为-1             this.gridValues[currGrid] = -1             arroundGrids = this.getAroundGrids(currGrid)             console.log(currGrid)             console.log(arroundGrids)             //将周围存在的格子value加1             // 需要注意如果周围格子已经有雷,则不需要进行加1操作             for (let i = 0; i < arroundGrids.length; i++) {                 if (arroundGrids[i] && this.gridValues[arroundGrids[i]] != -1) {                     this.gridValues[arroundGrids[i]]++                 }             }         }     }     /**      *  获取周围九宫格内格子对应的index      */     getAroundGrids(currGrid) {         let one = this.isValidGridNumber(currGrid - 11) ? currGrid - 11 : null         let two = this.isValidGridNumber(currGrid - 10) ? currGrid - 10 : null         let three = this.isValidGridNumber(currGrid - 9) ? currGrid - 9 : null         let four = this.isValidGridNumber(currGrid - 1) ? currGrid - 1 : null         let five = this.isValidGridNumber(currGrid + 1) ? currGrid + 1 : null         let six = this.isValidGridNumber(currGrid + 9) ? currGrid + 9 : null         let seven = this.isValidGridNumber(currGrid + 10) ? currGrid + 10 : null         let eight = this.isValidGridNumber(currGrid + 11) ? currGrid + 11 : null         // 没有左边格子的话1、4、6对应的都去掉         if (this.notHasLeftNeighbour(currGrid)) {             one = null             four = null             six = null         }         // 没有右边格子的话3、5、8对应的都去掉         if (this.notHasRightNeighbour(currGrid)) {             three = null             five = null             eight = null         }         return [one, two, three, four, five, six, seven, eight]     }     /**      *  判断是否左边没有格子      */     notHasLeftNeighbour(index) {         return index % 10 == 0     }     /**      *  判断是否右边没有格子      */     notHasRightNeighbour(index) {         return index % 10 == 9     }     /**      *  判断是否是存在的格子      */     isValidGridNumber(num) {         return num >= 0 && num <= 99     } } 复制代码

接下来只要实现点击事件就ok啦。最终完成的样子如下。当然还可以再做一些优化,例如点到雷后提示用户本轮游戏失败。或者再加一个重新开始按钮。不过我这个练习暂且先做到这里。 点击时间再这里,完整的代码和示范在最后

     /**      *  格子被点击后的处理       */     handleClickGrid(e) {         let $hiddenContent         // 避免只将图片部分或文字背景改变,而整个方框背景未变         if (e.target.tagName == 'IMG' || e.target.tagName == 'SPAN') {             $hiddenContent = e.target             e.target.parentNode.style.backgroundColor = 'yellow'         } else {             e.target.style.backgroundColor = 'yellow'             $hiddenContent = e.target.firstChild         }         // 为空说明value为0         if (!$hiddenContent) {             return         }         // 点到雷后显示所有隐藏内容         if ($hiddenContent.classList.contains('hasMine')) {             const $hiddens = document.getElementsByClassName('hidden')             while ($hiddens.length) {                 $hiddens[0].classList.remove('hidden')             }                          // 下面这段代码会有问题!             // for (let i = 0; i < $hiddens.length; i++) {             //     $hiddens[i].classList.remove('hidden')             // }         } else {             // 加上判断,避免多次点击同一个格子报错             if ($hiddenContent.classList.contains('hidden')) {                 $hiddenContent.classList.remove('hidden')             }         }     } 复制代码

这里需要注意的是下面这段代码

          const $hiddens = document.getElementsByClassName('hidden')             while ($hiddens.length) {                 $hiddens[0].classList.remove('hidden')             }                          // 下面这段代码会有问题!             // for (let i = 0; i < $hiddens.length; i++) {             //     $hiddens[i].classList.remove('hidden')             // } 复制代码

用class获取到的元素数组是动态的,所以随着remove,元素的数量是越来越少的,会导致结果不正确。正确做法是用while来判断数组内是否还有内容。


作者:两个月牙里的星星
链接:https://juejin.cn/post/7169981611016978468

文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐