鸿蒙开发案例:扫雷

a24.gif

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

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

实现步骤

步骤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 {
  // 定义方块的各种属性...
}

完整代码

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 = '';
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,640评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,254评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,011评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,755评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,774评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,610评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,352评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,257评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,717评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,894评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,021评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,735评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,354评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,936评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,054评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,224评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,974评论 2 355

推荐阅读更多精彩内容