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}`) ? '

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}`) ? '