分享|鸿蒙开发案例:扫雷
472
2024.10.15
2024.10.15
发布于 北京市

a7.gif

一个扫雷游戏的实现。游戏由一个10x10的网格组成,其中包含10个随机分布的地雷。游戏的目标是通过点击未标记为地雷的方块来揭示其下的数字,该数字表示周围八格中有多少个地雷。如果玩家点击了带有地雷的方块,则会触发游戏结束;如果成功揭示所有非雷区,则玩家获胜。

游戏状态包括:

• 游戏面板数据,
• 地雷总数,
• 已经揭示的方块集合,
• 被标记为地雷的方块集合,
• 方块大小和间距,
• 游戏开始时间和游戏结束标志。

主要方法包括:

• 初始化游戏,包括重置游戏状态并生成新的游戏面板;
• 生成游戏面板,并随机放置地雷;
• 计算每个非雷方块周围地雷的数量;
• 揭示方块及其相邻的空白方块;
• 处理游戏结束和胜利的情况,显示相应的对话框;
• 用于判断是否显示方块上的值的辅助函数。

此外,文件还定义了一个Cell类来表示游戏中的单个方块,其属性包括位置、是否有雷、邻近雷数、是否被标记等。游戏界面使用了HarmonyOS ArkUI的框架进行构建。

【算法分析】

  1. 随机数生成

该算法用来在游戏开始时随机地在游戏板上放置地雷。

private placeMines() {
  let placed = 0;
  while (placed < this.mineCount) {
    let x = Math.floor(Math.random() * 10);
    let y = Math.floor(Math.random() * 10);
    if (!this.gameBoard[x][y].hasMine) {
      this.gameBoard[x][y].hasMine = true;
      placed++;
    }
  }
}
  1. 邻域计算

该算法遍历每个单元格,计算出该单元格周围的地雷数量,并更新单元格的值。

private calculateNumbers() {
  for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
      if (!this.gameBoard[i][j].hasMine) {
        this.gameBoard[i][j].neighborMines = this.countNeighborMines(i, j);
        this.gameBoard[i][j].value = this.gameBoard[i][j].neighborMines.toString();
      } else {
        this.gameBoard[i][j].value = '雷';
      }
    }
  }
}

private countNeighborMines(row: number, col: number): number {
  let count = 0;
  for (let dx = -1; dx <= 1; dx++) {
    for (let dy = -1; dy <= 1; dy++) {
      if (dx === 0 && dy === 0) {
        continue;
      }
      let newRow = row + dx, newCol = col + dy;
      if (newRow >= 0 && newRow < 10 && newCol >= 0 && newCol < 10 && this.gameBoard[newRow][newCol].hasMine) {
        count++;
      }
    }
  }
  return count;
}
  1. 深度优先搜索(DFS)

当玩家点击一个没有地雷且周围也没有地雷的单元格时,递归地揭示这些单元格。

private revealCell(row: number, col: number) {
  if (this.isGameOver || this.revealedCells.has(`${row},${col}`)) {
    return;
  }
  const key = `${row},${col}`;
  this.revealedCells.add(key);
  if (this.gameBoard[row][col].hasMine) {
    this.showGameOverDialog();
  } else {
    if (this.gameBoard[row][col].neighborMines === 0) {
      for (let dx = -1; dx <= 1; dx++) {
        for (let dy = -1; dy <= 1; dy++) {
          if (dx === 0 && dy === 0) {
            continue;
          }
          let newRow = row + dx, newCol = col + dy;
          if (newRow >= 0 && newRow < 10 && newCol >= 0 && newCol < 10) {
            this.revealCell(newRow, newCol);
          }
        }
      }
    }
  }
  if (this.isVictory()) {
    this.showVictoryDialog();
  }
}

【完整代码】

import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct MineSweeper {
  // 游戏面板数据
  @State private gameBoard: Cell[][] = [];
  // 地雷总数
  @State private mineCount: number = 10;
  // 已经揭示的方块集合
  @State private revealedCells: Set<string> = new Set();
  // 标记为地雷的方块集合
  @State private flaggedCells: Set<string> = new Set();
  // 方块大小
  @State private cellSize: number = 60;
  // 方块之间的边距
  @State private cellMargin: number = 2;
  // 游戏开始时间
  private startTime: number = Date.now();
  // 游戏结束标志
  @State private isGameOver: boolean = false;

  // 在组件即将显示时初始化游戏
  aboutToAppear(): void {
    this.initializeGame();
  }

  // 初始化游戏
  private initializeGame() {
    this.isGameOver = false;
    this.startTime = Date.now();
    this.revealedCells.clear();
    this.flaggedCells.clear();
    this.generateBoard();
  }

  // 生成游戏面板
  private generateBoard() {
    this.gameBoard = [];
    for (let i = 0; i < 10; i++) {
      this.gameBoard.push([]);
      for (let j = 0; j < 10; j++) {
        this.gameBoard[i].push(new Cell(i, j));
      }
    }
    this.placeMines();
    this.calculateNumbers();
  }

  // 随机放置地雷
  private placeMines() {
    let placed = 0;
    while (placed < this.mineCount) {
      let x = Math.floor(Math.random() * 10);
      let y = Math.floor(Math.random() * 10);
      if (!this.gameBoard[x][y].hasMine) {
        this.gameBoard[x][y].hasMine = true;
        placed++;
      }
    }
  }

  // 计算每个方块周围的地雷数量
  private calculateNumbers() {
    for (let i = 0; i < 10; i++) {
      for (let j = 0; j < 10; j++) {
        if (!this.gameBoard[i][j].hasMine) {
          this.gameBoard[i][j].neighborMines = this.countNeighborMines(i, j);
          this.gameBoard[i][j].value = this.gameBoard[i][j].neighborMines.toString();
        } else {
          this.gameBoard[i][j].value = '雷';
        }
      }
    }
  }

  // 计算给定坐标周围地雷的数量
  private countNeighborMines(row: number, col: number): number {
    let count = 0;
    for (let dx = -1; dx <= 1; dx++) {
      for (let dy = -1; dy <= 1; dy++) {
        if (dx === 0 && dy === 0) {
          continue;
        }
        let newRow = row + dx, newCol = col + dy;
        if (newRow >= 0 && newRow < 10 && newCol >= 0 && newCol < 10 && this.gameBoard[newRow][newCol].hasMine) {
          count++;
        }
      }
    }
    return count;
  }

  // 揭示方块
  private revealCell(row: number, col: number) {
    if (this.isGameOver || this.revealedCells.has(`${row},${col}`)) {
      return;
    }

    const key = `${row},${col}`;
    this.revealedCells.add(key);

    if (this.gameBoard[row][col].hasMine) {
      this.showGameOverDialog();
    } else {
      if (this.gameBoard[row][col].neighborMines === 0) {
        for (let dx = -1; dx <= 1; dx++) {
          for (let dy = -1; dy <= 1; dy++) {
            if (dx === 0 && dy === 0) {
              continue;
            }
            let newRow = row + dx, newCol = col + dy;
            if (newRow >= 0 && newRow < 10 && newCol >= 0 && newCol < 10) {
              this.revealCell(newRow, newCol);
            }
          }
        }
      }
    }

    if (this.isVictory()) {
      this.showVictoryDialog();
    }
  }

  // 显示游戏结束对话框
  private showGameOverDialog() {
    this.isGameOver = true;
    promptAction.showDialog({
      title: '游戏结束: 游戏失败!',
      buttons: [{ text: '重新开始', color: '#ffa500' }]
    }).then(() => {
      this.initializeGame();
    });
  }

  // 显示胜利对话框
  private showVictoryDialog() {
    this.isGameOver = true;
    promptAction.showDialog({
      title: '恭喜你,游戏胜利!',
      message: `用时:${((Date.now() - this.startTime) / 1000).toFixed(3)}秒`,
      buttons: [{ text: '重新开始', color: '#ffa500' }]
    }).then(() => {
      this.initializeGame();
    });
  }

  // 判断游戏是否胜利
  private isVictory() {
    let revealedNonMineCount = 0;

    for (let i = 0; i < this.gameBoard.length; i++) {
      for (let j = 0; j < this.gameBoard[i].length; j++) {
        if (this.revealedCells.has(`${i},${j}`)) {
          revealedNonMineCount++;
        }
      }
    }

    return revealedNonMineCount == 90;
  }

  // 决定是否显示方块值
  private isShowValue(cell: Cell): string {
    if (this.isGameOver) {
      return cell.value === '0' ? '' : cell.value;
    } else {
      if (this.revealedCells.has(`${cell.row},${cell.column}`)) {
        return cell.value === '0' ? '' : cell.value;
      } else {
        return '';
      }
    }
  }

  build() {
    Column({ space: 10 }) {
      // 重置游戏按钮
      Button('重新开始').onClick(() => this.initializeGame());

      // 创建游戏面板容器
      Flex({ wrap: FlexWrap.Wrap }) {
        // 遍历每一行
        ForEach(this.gameBoard, (row: Cell[], rowIndex: number) => {
          // 遍历每一列
          ForEach(row, (cell: Cell, colIndex: number) => {
            Stack() {
              // 显示方块上的数字或雷
              Text(this.isShowValue(cell))
                .width(`${this.cellSize}lpx`)
                .height(`${this.cellSize}lpx`)
                .margin(`${this.cellMargin}lpx`)
                .fontSize(`${this.cellSize / 2}lpx`)
                .textAlign(TextAlign.Center)
                .backgroundColor(this.revealedCells.has(`${rowIndex},${colIndex}`) ?
                  (this.isShowValue(cell) === '雷' ? Color.Red : Color.White) : Color.Gray)
                .fontColor(!this.revealedCells.has(`${rowIndex},${colIndex}`) || this.isShowValue(cell) === '雷' ?
                Color.White : Color.Black)
                .borderRadius(5)
                .parallelGesture(GestureGroup(GestureMode.Exclusive,
                  TapGesture({ count: 1, fingers: 1 })
                    .onAction(() => this.revealCell(rowIndex, colIndex)),
                  LongPressGesture({ repeat: true })
                    .onAction(() => cell.isFlag = true)
                ));

              // 显示标记旗帜
              Text(`${!this.revealedCells.has(`${rowIndex},${colIndex}`) ? '旗' : ''}`)
                .width(`${this.cellSize}lpx`)
                .height(`${this.cellSize}lpx`)
                .margin(`${this.cellMargin}lpx`)
                .fontSize(`${this.cellSize / 2}lpx`)
                .textAlign(TextAlign.Center)
                .fontColor(Color.White)
                .visibility(cell.isFlag && !this.isGameOver ? Visibility.Visible : Visibility.None)
                .onClick(() => {
                  cell.isFlag = false;
                })
            }
          });
        });
      }
      .width(`${(this.cellSize + this.cellMargin * 2) * 10}lpx`);
    }
    .backgroundColor(Color.Orange)
    .width('100%')
    .height('100%');
  }
}

// 方块类
@ObservedV2
class Cell {
  // 方块所在的行
  row: number;
  // 方块所在的列
  column: number;
  // 是否有地雷
  hasMine: boolean = false;
  // 周围地雷数量
  neighborMines: number = 0;
  // 是否被标记为地雷
  @Trace isFlag: boolean = false;
  // 方块值
  @Trace value: string;

  // 构造函数
  constructor(row: number, column: number) {
    this.row = row;
    this.column = column;
    this.value = '';
  }
}
评论 (0)