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}`) ? '🚩' : ''}`)
.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)
}
});
});
}
.width(`${(this.cellSize + this.cellMargin * 2) * 10}lpx`);
}
.backgroundColor(Color.Orange)
.width('100%')
.height('100%');
}
}
Key Technical Features
-
State Management:
- Uses
@ObservedV2
decorator for automatic state tracking - Implements
@Trace
for flag state synchronization - Manages game state with
@State
properties
- Uses
-
Game Logic:
- Recursive cell revealing algorithm
- Mine placement randomization
- Victory detection (90 safe cells revealed)
-
UI Features:
- Responsive grid layout with Flex container
- Multi-gesture support (tap & long-press)
- Dynamic cell styling based on game state
- Animated flag markers (🚩)
-
Performance:
- Efficient cell rendering with ForEach
- Optimized state updates using ArkUI's reactive system
This implementation demonstrates how to build complex game logic while leveraging HarmonyOS NEXT's declarative UI capabilities and reactive programming model. The code follows modern ArkUI development patterns and can be extended with additional features like difficulty levels or score tracking.
Top comments (0)