广告位联系
返回顶部
分享到

Vue2+JS实现扫雷小游戏的代码

JavaScript 来源:互联网 作者:酷站 发布时间:2022-06-18 23:03:29 人浏览
摘要

实现步骤 1、场景布局实现 布局就是经典的方格布局,对于场景的美观度可以自行找几个配色网站作为参考。 出现问题: 先初始化一个二维数组对应方块坐标,然后依次渲染or直接通过

实现步骤

1、场景布局实现

布局就是经典的方格布局,对于场景的美观度可以自行找几个配色网站作为参考。

出现问题: 先初始化一个二维数组对应方块坐标,然后依次渲染 or 直接通过预期的行、列数渲染空白方块

区别: 直接初始化二维数组,可以对坐标进行一些属性操作,例如标记、是否为地雷等等,之后操作的时候会方便很多,缺点在初始化的时候需要进行大量的计算工作(因为在点开一个安全坐标时需要显示周围的地雷个数,还要考虑边缘情况),而渲染空白方块就可以在点击坐标的时候再去做计算,并且在点击的时候只需要计算该方块的属性。

这里我选择了渲染空白方块的形式。

代码实现

使用了 element-ui组件

template

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

<div class="layout">

  <div class="row" v-for="row in layoutConfig.row" :key="row">

    <div

      class="cell"

      :style="{ width:  edgeLength, height: edgeLength }"

      v-for="col in layoutConfig.cell"

      :key="col">

      <div

        class="block"

        @click="open(row, col, $event)"

        @contextmenu.prevent="sign(row, col, $event)"

      >

        // 这里的逻辑现在可以暂时不用管,只需要先做好布局

        <template v-if="areaSign[`${row}-${col}`] === 'fail'">

          <img src="../../assets/svg/fail.svg" alt="">

        </template>

        <template v-else-if="areaSign[`${row}-${col}`] === 'tag'">

          <img src="../../assets/svg/Flag.svg" alt="">

        </template>

        <template v-else-if="areaSign[`${row}-${col}`] === 'normal'">

        </template>

        <template v-else>

          {{areaSign[`${row}-${col}`]}}

        </template>

      </div>

    </div>

  </div>

</div>

style:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

<style scoped lang="less">

.container {

  display: flex;

  justify-content: center;

  align-items: center;

  flex-direction: column;

  margin-top: 100px;

  .typeChoose {

    display: flex;

    justify-content: center;

    align-items: center;

    width: 100%;

    margin-bottom: 20px;

    .item {

      margin: 0 10px;

    }

  }

  .layout {

    width: 500px;

    height: 500px;

    .row {

      display: flex;

      justify-content: center;

      align-items: center;

      .cell {

        border: 1px solid #735E30;

        caret-color: transparent;

        cursor: pointer;

        line-height: 50px;

        .block {

          height: 100%;

          background: #292E17;

        }

        .block:hover {

          background: #96875F;

        }

        .opened {

          height: 100%;

          background: #E8E0D8;

        }

      }

    }

  }

}

</style>

2、初始化事件

生成地雷随机二维数组

因为布局已经通过空白方块生成了,所以我们只需要关心生成随机的地雷坐标就可以了

代码实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

/*

 *  type: 当前模式的地雷个数(自己定义数量)

 *  mineList: 地雷坐标数组

 *  layoutConfig: {

 *    row: 布局的行数

 *    col: 布局的列数

 *  }

 */  

 

// 生成随机地雷坐标数组

initMineListRange () {

  while (this.mineList.length < this.type) {

    this.initMineItem()

  }

},

// 生成单个地雷坐标并且放入地雷坐标数组(mineList)中

initMineItem () {

  const position = this.initPositionRange([1, this.layoutConfig.row], [1, this.layoutConfig.cell])

  if (!this.hasPositionIn(position, this.mineList)) {

    this.mineList.push(position)

  }

},

// 生成一个在给定范围内的随机坐标

initPositionRange ([xStart, xEnd], [yStart, yEnd]) {

  return [this.numRange(xStart, xEnd), this.numRange(yStart, yEnd)]

},

// 生成一个在给定范围内的随机整数

numRange (start, end) {

  return Math.floor((Math.random() * (end - start + 1))) + start

},

// 判断参数中的 position 是否已经存在与 参数中的 positionList 中

hasPositionIn (position, positionList) {

  console.assert(position.length === 2, 'position length < 2, not a position item')

  return positionList.some(p => {

    return p[0] === position[0] && p[1] === position[1]

  })

}

3、游戏动作(action)

指的是游戏中的一些操作以及某个操作导致的一系列变化

点击方块

分析:点击方块之后会出现三种情况

  • 该方块的九宫格范围内没有地雷
  • 该方块的九宫格方位内有地雷
  • 踩雷了(game over)

对应这三种情况需要分别有不同的表现形式

第一种情况:(方块的九宫格范围内没有地雷)

这种情况只需要将该方块的样式改为点击过的样式即可(class="opened")

第二种情况:(方块的九宫格方位内有地雷)

修改样式为opened,并且需要计算周围的地雷数量(需要考虑边缘情况,即当前坐标是否在边缘)

第三种情况:(踩雷)

修改样式为opened, 并且展示地雷,提示用户游戏结束

代码实现

因为在点击之前该方块是空白对象,所以需要一个对象来存储该方块的属性或者状态(areaSign)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

/*

*  areaSign: Object  key: 坐标('1-2') value: 状态

*  gameProcess:当前游戏是否处于进行状态

*  statusEnum: 枚举 方块状态枚举值(fail,normal,tag)

*/

 

// 方块点击事件 (传入坐标以及点击事件对象)

 open (rowIndex, colIndex, e) {

   // 判断当前游戏是否

   if (!this.gameProcess) {

     this.gameEndConfirm()

     return

   }

   // 判断当前坐标是否被标记,被标记则不能被点开

   if (this.getAreaSignValueWithPosition(rowIndex, colIndex) === statusEnum.tag) {

     this.confirmMessageBox('该区域已经被标记,请选择其他区域点击')

     return

   }

    

   e.target.className = 'opened'

   if (this.hasTouchMine([rowIndex, colIndex])) {

     // 踩雷

     this.mineTouched([rowIndex, colIndex])

   } else {

     // 第一、二种情况

     this.safeTouched([rowIndex, colIndex])

   }

 },

 // 通过传入的坐标判断是否存在地雷坐标数组中

 hasTouchMine ([xPosition, yPosition]) {

   return this.hasPositionIn([xPosition, yPosition], this.mineList)

 },

 mineTouched (position) {

   this.setSvg(position, statusEnum.fail)

   // 游戏失败提示

   this.gameProcess = false

   this.gameEndConfirm()

 },

 safeTouched (position) {

   this.setTips(position)

 },

 // 把传入坐标通过判断是否有雷设置对应提示

 setTips (position) {

   const total = this.positionAroundMineTotal(position)

   this.$set(this.areaSign, `${position[0]}-${position[1]}`, total || '')

 },

 // 把传入坐标设置为对应状态的svg图标

 setSvg (position, type) {

   this.$set(this.areaSign, `${position[0]}-${position[1]}`, type)

 },

 // 传入坐标与地雷坐标数组判断是否其周围存在雷

 positionAroundMineTotal (position) {

   const aroundPositionList = this.getAroundPosition(position[0], position[1])

   return aroundPositionList.filter(item => this.hasTouchMine(item)).length

 },

 // 获取传入坐标的周围九宫格坐标

 getAroundPosition (xPosition, yPosition) {

   const aroundPositionList = [

     [xPosition - 1, yPosition - 1],

     [xPosition - 1, yPosition],

     [xPosition - 1, yPosition + 1],

     [xPosition, yPosition - 1],

     [xPosition, yPosition + 1],

     [xPosition + 1, yPosition - 1],

     [xPosition + 1, yPosition],

     [xPosition + 1, yPosition + 1]

   ]

   return aroundPositionList.filter(position => isInRange(position[0]) && isInRange(position[1]))

   // 判断传入数字是否在对应范围中

   function isInRange (num, range = [1, 10]) {

     return num >= range[0] && num <= range[1]

   }

 }

标记坐标

左键为点击方块,右键为标记坐标(第二次点击为取消标记),当该坐标为标记的时候,无法进行点击,并且当刚好标记的坐标数组和地雷数组一样时,则游戏结束,玩家胜利

代码实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

/*

*  hasWin 见下文的 vue computed

*/

sign (rowIndex, colIndex, e) {

// 判断游戏当前状态

  if (!this.gameProcess) {

    this.gameEndConfirm()

    return

  }

  if (this.getAreaSignValueWithPosition(rowIndex, colIndex) === undefined ||

    this.getAreaSignValueWithPosition(rowIndex, colIndex) === statusEnum.normal) {

    // 当前坐标 为被标记过或者以及被取消标记 触发:添加标记

    this.setSvg([rowIndex, colIndex], statusEnum.tag)

  } else if (this.getAreaSignValueWithPosition(rowIndex, colIndex) === statusEnum.tag) {

    // 当前坐标 被标记 触发:取消标记

    this.setSvg([rowIndex, colIndex], statusEnum.normal)

  }

  console.log(this.tagList, this.mineList)

  // 检测游戏是否结束

  this.gameInspector()

},

// 游戏提示

gameEndConfirm () {

  const message = this.hasWin ? '恭喜你通关,是否继续?' : '游戏失败,是否重新开始?'

  this.confirmMessageBox(message, {

    callback: () => {

      this.resetGame()

    },

    cancelCallback: () => {}

  }, 'confirm')

},

// 游戏状态检测员(判断当前游戏是否结束)

gameInspector () {

  if (this.hasWin) {

    this.gameEndConfirm()

  }

},

// 通过传入坐标返回对应格式的字符串(areaSign的key值)

getAreaSignAttrWithPosition (xPosition, yPosition) {

  return `${xPosition}-${yPosition}`

},

// 通过传入坐标返回areaSign的value值(获取该坐标的状态)

getAreaSignValueWithPosition (xPosition, yPosition) {

  return this.areaSign[this.getAreaSignAttrWithPosition(xPosition, yPosition)]

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// 被标记列表

tagList () {

  return Object.keys(this.areaSign)

    .filter(item => this.areaSign[item] === 'tag')

    .map(attrStr => attrStr.split('-').map(str => parseInt(str)))

},

// 判断所有的地雷是否已经被标记

hasSignAllMine () {

  return this.tagList.length === this.mineList.length &&

    this.tagList.every(tagPosition => this.hasPositionIn(tagPosition, this.mineList))

},

// 游戏是否胜利

hasWin () {

  return this.hasSignAllMine

}

游戏收尾

游戏失败或者胜利的时候需要重置游戏

代码实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

resetGame () {

  this.gameProcess = true

  this.areaSign = {}

  this.mineList = []

  this.resetOpenedClass()

  // 初始化游戏

  this.initMineListRange()

},

// 将class = "opened" 的元素改回 "block" 状态

resetOpenedClass () {

  document.querySelectorAll('.opened').forEach(node => {

    node.className = 'block'

  })

}

总结

扫雷的实现并不复杂,首先需要对扫雷这个游戏的机制有思路,并且可以将一些逻辑捋清楚就可以了,实现的时候再将一些边缘状态考虑一下。可以更多关注一下对于代码的封装,对于代码的提炼很重要,这样在之后继续开发或者需要修改的时候很容易上手。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://juejin.cn/post/7110113661351362596
相关文章
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计