我们的扫雷游戏将具备以下功能:

• 动态生成10x10的游戏面板。
• 放置10个随机地雷。
• 计算并显示每个方块周围的地雷数量。
• 用户可以通过点击来揭示方块,长按来标记地雷。
• 当揭示到地雷时,游戏结束;当所有非雷方块都被揭示时,游戏胜利。

完整代码

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 = '';
  }
}

实现步骤

步骤1:定义游戏状态

首先,我们需要定义游戏的状态变量,包括游戏面板数据、地雷数量、已揭示方块集合、标记为地雷的方块集合等。

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;
}

步骤2:初始化游戏

在组件即将显示时初始化游戏,包括清空状态变量、生成游戏面板、放置地雷等。

aboutToAppear(): void {
  this.initializeGame();
}

private initializeGame() {
  this.isGameOver = false;
  this.startTime = Date.now();
  this.revealedCells.clear();
  this.flaggedCells.clear();
  this.generateBoard();
}

步骤3:生成游戏面板与放置地雷

游戏面板由10x10的方块组成,随机放置10个地雷,并计算每个方块周围的地雷数量。

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();
}

步骤4:处理用户交互

用户可以通过点击方块来揭示其内容,也可以通过长按来标记地雷。

private revealCell(row: number, col: number) {
  // 处理揭示方块的逻辑...
}

build() {
  // 使用Flex布局和ForEach遍历游戏面板,添加点击和长按手势...
}

步骤5:显示游戏结果

当玩家揭示到地雷时,游戏结束;当所有非雷方块都被揭示时,游戏胜利。

private showGameOverDialog() {
  // 显示游戏结束对话框...
}

private showVictoryDialog() {
  // 显示胜利对话框...
}

步骤6:定义Cell类

最后,定义一个Cell类来存储每个方块的信息。

@ObservedV2
class Cell {
  // 定义方块的各种属性...
}

zhongcx
1 声望3 粉丝