HarmonyOS NEXT Development Case: Minesweeper

The following code demonstrates how to implement a classic Minesweeper game using HarmonyOS NEXT's ArkUI framework. This implementation includes core game logic, state management, and interactive UI components. Core Code Implementation import { promptAction } from '@kit.ArkUI'; // Import dialog display utility // Define Cell class @ObservedV2 // Enable automatic state tracking class Cell { row: number; // Cell's row index column: number; // Cell's column index hasMine: boolean = false; // Mine presence flag neighborMines: number = 0; // Number of adjacent mines @Trace isFlag: boolean = false; // Flag marking state @Trace value: string; // Display value (number or 'Mine') constructor(row: number, column: number) { this.row = row; this.column = column; this.value = ''; // Initialize empty value } } // Main game component @Entry // Entry component declaration @Component // Component declaration struct MineSweeper { @State private gameBoard: Cell[][] = []; // Game board data @State private mineCount: number = 10; // Total mines @State private revealedCells: Set = new Set(); // Revealed cells @State private flaggedCells: Set = new Set(); // Flagged cells @State private cellSize: number = 60; // Cell dimension @State private cellMargin: number = 2; // Cell spacing private startTime: number = Date.now(); // Game start timestamp @State private isGameOver: boolean = false; // Game over flag // Lifecycle method 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 this.initializeGame()); } private showVictoryDialog() { this.isGameOver = true; promptAction.showDialog({ title: 'Congratulations! You Won!', message: `Time: ${((Date.now() - this.startTime) / 1000).toFixed(3)}s`, buttons: [{ text: 'Restart', color: '#ffa500' }] }).then(() => this.initializeGame()); } private isVictory(): boolean { let revealedNonMineCount = 0; for (let i = 0; i 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}`) ? '

May 11, 2025 - 01:12
 0
HarmonyOS NEXT Development Case: Minesweeper

Image description

The following code demonstrates how to implement a classic Minesweeper game using HarmonyOS NEXT's ArkUI framework. This implementation includes core game logic, state management, and interactive UI components.

Core Code Implementation

import { promptAction } from '@kit.ArkUI'; // Import dialog display utility

// Define Cell class
@ObservedV2 // Enable automatic state tracking
class Cell {
  row: number; // Cell's row index
  column: number; // Cell's column index
  hasMine: boolean = false; // Mine presence flag
  neighborMines: number = 0; // Number of adjacent mines
  @Trace isFlag: boolean = false; // Flag marking state
  @Trace value: string; // Display value (number or 'Mine')

  constructor(row: number, column: number) {
    this.row = row;
    this.column = column;
    this.value = ''; // Initialize empty value
  }
}

// Main game component
@Entry // Entry component declaration
@Component // Component declaration
struct MineSweeper {
  @State private gameBoard: Cell[][] = []; // Game board data
  @State private mineCount: number = 10; // Total mines
  @State private revealedCells: Set<string> = new Set(); // Revealed cells
  @State private flaggedCells: Set<string> = new Set(); // Flagged cells
  @State private cellSize: number = 60; // Cell dimension
  @State private cellMargin: number = 2; // Cell spacing
  private startTime: number = Date.now(); // Game start timestamp
  @State private isGameOver: boolean = false; // Game over flag

  // Lifecycle method
  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 = ''; // Chinese character for mine
        }
      }
    }
  }

  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: 'Game Over: You Lost!',
      buttons: [{ text: 'Restart', color: '#ffa500' }]
    }).then(() => this.initializeGame());
  }

  private showVictoryDialog() {
    this.isGameOver = true;
    promptAction.showDialog({
      title: 'Congratulations! You Won!',
      message: `Time: ${((Date.now() - this.startTime) / 1000).toFixed(3)}s`,
      buttons: [{ text: 'Restart', color: '#ffa500' }]
    }).then(() => this.initializeGame());
  }

  private isVictory(): boolean {
    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 {
      return this.revealedCells.has(`${cell.row},${cell.column}`) ? 
             (cell.value === '0' ? '' : cell.value) : '';
    }
  }

  build() {
    Column({ space: 10 }) {
      Button('Restart').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}`) ? '