Kros的博客 Kros的博客
首页
  • CSS
  • 工具
  • Vue
  • js
  • Vue3
  • 算法
  • 折腾笔记
一言
  • 分类
  • 标签
  • 归档
码云

Kros

凡心所向,素履以往,生如逆旅,一苇以航
首页
  • CSS
  • 工具
  • Vue
  • js
  • Vue3
  • 算法
  • 折腾笔记
一言
  • 分类
  • 标签
  • 归档
码云
  • 拼图小游戏

  • 井字棋

  • 贪吃蛇

  • 扫雷

    • 扫雷规则
    • 封装扫雷相关类
    • 具体使用与页面渲染
    • 后续优化
  • 项目
  • 扫雷
kros
2025-09-03

封装扫雷相关类

为更快、更简单实现扫雷的基础功能,这里先封装一个扫雷相关类,类中实现扫雷的基础数据和操作。

# 定义格子

定义格子属性:坐标x、y,当前格子值(类、空白、数字),是否已揭露,是否标记

interface ICell {
  x: number;
  y: number;
  value: number;
  isRevealed: boolean;
  isFlagged: boolean;
}
1
2
3
4
5
6
7

# 定义扫雷类

因为棋子是复杂类型,所以棋盘数组使用reactive创建响应式保证Cell内部属性修改触发渲染更新。同样为保证棋盘能再次更新我在reactive创建时包裹了一层**{ list: }**,我们使用时只需更新board.list即可。

export class MineSweeper {
  private board: Reactive<{ list: ICell[][] }>;
  private gameOver: boolean;
  private size: number;
  private mineList: number[];
	
	// 其它方法属性
	// ....
}
1
2
3
4
5
6
7
8
9

# 初始化雷区

  1. 按传入的size生成size * size的二维数组ICell
  2. 从0到size * size中生成随机数,以mineList记录保证不重复,将上面记录的随机数转换成坐标填入到board.list中,即生成地雷
  3. 根据生成的地雷mineList列表,我们累计计算周围的数字并填入到board.list中,即生成地雷周围数字
  4. 以上步骤即生成完整的雷区,需注意:随机数为一维,使用时需转换成二维坐标 实现代码如下:
private createBoard(): ICell[][] {
    const board = Array.from({ length: this.size }, (_, x) =>
      Array.from({ length: this.size }, (_, y) => ({
        x,
        y,
        value: 0,
        isRevealed: false,
        isFlagged: false,
      }))
    );

    // 添加雷
    for(let i = 0; i < this.size; ) {
      const randomIndex = Math.floor(Math.random() * this.size * this.size);
      if (!this.mineList.includes(randomIndex)) {
        this.mineList.push(randomIndex);
        board[Math.floor(randomIndex / this.size)][randomIndex % this.size].value = -1;
        i++
      }
    }
    
    // 添加数字
    for (const mineIndex of this.mineList) {
      const mineX = Math.floor(mineIndex / this.size);
      const mineY = mineIndex % this.size;
      
      board[mineX][mineY].value = -1;

      for (let x = Math.max(0, mineX - 1); x <= Math.min(this.size - 1, mineX + 1); x++) {
        for (let y = Math.max(0, mineY - 1); y <= Math.min(this.size - 1, mineY + 1); y++) {
          if (board[x][y].value !== -1) {
            board[x][y].value++;
          }
        }
      }
    }
    return board;
  }
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

# 展开格子算法

如果点中的格子为空白时需展开相邻所有的空白和数字格子,实现代码如下:

/**
   * 递归揭示格子
   * @param x 
   * @param y 
   * @returns 
   */
  private recursionRevealCell(x: number, y: number) {
    if (this.gameOver || this.board.list[y][x].isRevealed) return;
    this.board.list[y][x].isRevealed = true;
    // 递归处理
    if (this.board.list[y][x].value === 0) {
      for (let i = -1; i <= 1; i++) {
        for (let j = -1; j <= 1; j++) {
          if (i === 0 && j === 0) continue;
          const newX = x + i;
          const newY = y + j;
          if (newX >= 0 && newX < this.size && newY >= 0 && newY < this.size) {
            this.recursionRevealCell(newX, newY);
          }
        }
      }
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 完整代码

import type { Reactive } from "vue";
import { reactive } from "vue";

interface ICell {
  x: number;
  y: number;
  value: number;
  isRevealed: boolean;
  isFlagged: boolean;
}

export class MineSweeper {
  private board: Reactive<{ list: ICell[][] }>;
  private gameOver: boolean;
  private size: number;
  private mineList: number[];

  get boardList(): Reactive<ICell[][]> {
    return this.board.list;
  }

  constructor(size: number) {
    this.size = size;
    this.mineList = [];
    this.board = reactive({ list: this.createBoard()});
    this.gameOver = false;
  }

  /**
   * 创建游戏棋盘
   * @returns 棋盘
   */
  private createBoard(): ICell[][] {
    const board = Array.from({ length: this.size }, (_, x) =>
      Array.from({ length: this.size }, (_, y) => ({
        x,
        y,
        value: 0,
        isRevealed: false,
        isFlagged: false,
      }))
    );

    // 添加雷
    for(let i = 0; i < this.size; ) {
      const randomIndex = Math.floor(Math.random() * this.size * this.size);
      if (!this.mineList.includes(randomIndex)) {
        this.mineList.push(randomIndex);
        board[Math.floor(randomIndex / this.size)][randomIndex % this.size].value = -1;
        i++
      }
    }
    
    // 添加数字
    for (const mineIndex of this.mineList) {
      const mineX = Math.floor(mineIndex / this.size);
      const mineY = mineIndex % this.size;
      
      board[mineX][mineY].value = -1;

      for (let x = Math.max(0, mineX - 1); x <= Math.min(this.size - 1, mineX + 1); x++) {
        for (let y = Math.max(0, mineY - 1); y <= Math.min(this.size - 1, mineY + 1); y++) {
          if (board[x][y].value !== -1) {
            board[x][y].value++;
          }
        }
      }
    }
    return board;
  }

  /**
   * 揭示格子
   * @param x 
   * @param y 
   * @returns 
   */
  public revealCell(x: number, y: number): void {
    if (this.gameOver || this.board.list[y][x].isRevealed) return;
    if (this.board.list[y][x].value === -1) {
      this.gameOver = true;
      for(let i = 0; i < this.mineList.length; i++) {
        const mineX = Math.floor(this.mineList[i] / this.size);
        const mineY = this.mineList[i] % this.size;
        this.board.list[mineX][mineY].isRevealed = true;
      }
      this.gameOver = true;
      setTimeout(() => {
        alert('游戏失败!!!!')
      }, 200)
      return;
    }
    this.recursionRevealCell(x, y);

    if (this.checkWin()) {
      this.gameOver = true
      setTimeout(() => {
        alert('游戏胜利!!!!')
      }, 200)
    }
  }

  /**
   * 递归揭示格子
   * @param x 
   * @param y 
   * @returns 
   */
  private recursionRevealCell(x: number, y: number) {
    if (this.gameOver || this.board.list[y][x].isRevealed) return;
    this.board.list[y][x].isRevealed = true;
    // 递归处理
    if (this.board.list[y][x].value === 0) {
      for (let i = -1; i <= 1; i++) {
        for (let j = -1; j <= 1; j++) {
          if (i === 0 && j === 0) continue;
          const newX = x + i;
          const newY = y + j;
          if (newX >= 0 && newX < this.size && newY >= 0 && newY < this.size) {
            this.recursionRevealCell(newX, newY);
          }
        }
      }
    }
  }

  private checkWin() {
    return this.boardList.reduce((res, row) => {
      res.push(...row.filter(cell => !cell.isRevealed));
      return res;
    }, []).length === this.mineList.length;
  }

  public flagCell(x: number, y: number): void {
    if (this.gameOver || this.board.list[y][x].isRevealed) return;
    this.board.list[y][x].isFlagged = !this.board.list[y][x].isFlagged;
  }

  public resetGame(): void {
    this.mineList = [];
    this.board.list = this.createBoard();
    this.gameOver = false;
  }
}
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
上次更新: 2025/09/05, 8:09:00
扫雷规则
具体使用与页面渲染

← 扫雷规则 具体使用与页面渲染→

最近更新
01
Find the next perfect square
09-05
02
Regex validate PIN code
09-05
03
Find the odd int
09-05
更多文章>
Theme by Vdoing | Copyright © 2020-2025 kros king
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
icon-heart-o icon-heart icon-infinity icon-pause icon-play link next prev