Nous allons développer une application de démineur avec les foncsionnalités suivantes :
• Une grille de jeu générée dynamiquement en 10x10. • Placement aléatoire de 10 mines. • Calcul et affichage du nombre de mines autour de chaque case. • Les utilisateurs peuvent révéler des cases en cliquant dessus ou marquer des mines en maintenant appuyé. • La partie se termine si une mine est révélée ; la victoire est atteinte lorsque toutes les cases non minées sont révélées.
import { alertAction } from '@tools.HarmonyUI';
@Entry
@Component
struct MineSweeper {
// État de la grille de jeu
@State private gridData: Tile[][] = [];
// Nombre total de mines
@State private totalMines: number = 10;
// Ensembles des cases révélées et marquées
@State private uncoveredTiles: Set<string> = new Set();
@State private flaggedTiles: Set<string> = new Set();
// Taille et marge des cases
@State private tileSize: number = 60;
@State private tilePadding: number = 2;
// Indicateurs de fin de partie
@State private gameEnded: boolean = false;
// Initialisation lors de l'apparition du composant
aboutToAppear(): void {
this.resetGame();
}
// Réinitialiser le jeu
private resetGame() {
this.gameEnded = false;
this.uncoveredTiles.clear();
this.flaggedTiles.clear();
this.createGrid();
}
// Générer la grille
private createGrid() {
this.gridData = Array.from({ length: 10 }, (_, row) =>
Array.from({ length: 10 }, (_, col) => new Tile(row, col))
);
this.setRandomMines();
this.computeAdjacentMines();
}
// Placer les mines aléatoirement
private setRandomMines() {
let placedMines = 0;
while (placedMines < this.totalMines) {
const x = Math.floor(Math.random() * 10);
const y = Math.floor(Math.random() * 10);
if (!this.gridData[x][y].isMine) {
this.gridData[x][y].isMine = true;
placedMines++;
}
}
}
// Calculer les mines adjacentes pour chaque case
private computeAdjacentMines() {
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (!this.gridData[i][j].isMine) {
this.gridData[i][j].adjacentMines = this.countNearbyMines(i, j);
}
}
}
}
// Compter les mines autour d'une case donnée
private countNearbyMines(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;
const newRow = row + dx;
const newCol = col + dy;
if (newRow >= 0 && newRow < 10 && newCol >= 0 && newCol < 10 && this.gridData[newRow][newCol].isMine) {
count++;
}
}
}
return count;
}
// Révéler une case
private uncoverTile(row: number, col: number) {
if (this.gameEnded || this.uncoveredTiles.has(`${row},${col}`)) return;
const key = `${row},${col}`;
this.uncoveredTiles.add(key);
if (this.gridData[row][col].isMine) {
this.displayLoseMessage();
} else if (this.gridData[row][col].adjacentMines === 0) {
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
if (dx === 0 && dy === 0) continue;
const newRow = row + dx;
const newCol = col + dy;
if (newRow >= 0 && newRow < 10 && newCol >= 0 && newCol < 10) {
this.uncoverTile(newRow, newCol);
}
}
}
}
if (this.checkWinCondition()) this.displayWinMessage();
}
// Afficher un message de défaite
private displayLoseMessage() {
this.gameEnded = true;
alertAction.showDialog({
title: 'Défaite !',
buttons: [{ text: 'Recommencer', color: '#4CAF50' }]
}).then(() => this.resetGame());
}
// Afficher un message de victoire
private displayWinMessage() {
this.gameEnded = true;
alertAction.showDialog({
title: 'Victoire !',
buttons: [{ text: 'Nouvelle Partie', color: '#FF9800' }]
}).then(() => this.resetGame());
}
// Vérifier si le joueur a gagné
private checkWinCondition(): boolean {
const safeTilesCount = Array.from(this.uncoveredTiles).filter(
(tile) => !this.gridData[parseInt(tile.split(',')[0])][parseInt(tile.split(',')[1])].isMine
).length;
return safeTilesCount === 90;
}
build() {
Column({ space: 10 }) {
Button('Nouvelle Partie').onClick(() => this.resetGame());
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.gridData, (row: Tile[], rowIndex: number) => {
ForEach(row, (tile: Tile, colIndex: number) => {
Stack() {
Text(this.getDisplayValue(tile))
.width(`${this.tileSize}px`)
.height(`${this.tileSize}px`)
.margin(`${this.tilePadding}px`)
.fontSize(`${this.tileSize / 2}px`)
.textAlign(TextAlign.Center)
.backgroundColor(this.uncoveredTiles.has(`${rowIndex},${colIndex}`) ? Color.White : Color.Gray)
.fontColor(this.uncoveredTiles.has(`${rowIndex},${colIndex}`) ? Color.Black : Color.White)
.borderRadius(5)
.parallelGesture(GestureGroup(GestureMode.Exclusive,
TapGesture({ count: 1, fingers: 1 })
.onAction(() => this.uncoverTile(rowIndex, colIndex)),
LongPressGesture({ repeat: true })
.onAction(() => this.flaggedTiles.add(`${rowIndex},${colIndex}`))
));
Text('Drapeau')
.visibility(this.flaggedTiles.has(`${rowIndex},${colIndex}`) ? Visibility.Visible : Visibility.Hidden)
.onClick(() => this.flaggedTiles.delete(`${rowIndex},${colIndex}`));
}
});
});
}
.width(`${(this.tileSize + this.tilePadding * 2) * 10}px`);
}
.backgroundColor(Color.LightGray)
.width('100%')
.height('100%');
}
private getDisplayValue(tile: Tile): string {
return this.uncoveredTiles.has(`${tile.row},${tile.column}`)
? tile.isMine ? 'M' : tile.adjacentMines.toString()
: '';
}
}
// Classe représentant une tuile
@ObservedV2
class Tile {
row: number;
column: number;
isMine: boolean = false;
adjacentMines: number = 0;
constructor(row: number, column: number) {
this.row = row;
this.column = column;
}
}
Étapes de réalisation
Étape 1 : Définir l'état du jeu Créer les variables d'état nécessaires pour représenter la grille, les mines, les cases révélées et marquées.
struct MineSweeper {
@State private gridData: Tile[][] = [];
@State private totalMines: number = 10;
@State private uncoveredTiles: Set<string> = new Set();
@State private flaggedTiles: Set<string> = new Set();
}
Étape 2 : Initialiser le jeu Nettoyer les données existantes et générer une nouvelle grille avant chaque partie.
aboutToAppear(): void {
this.resetGame();
}
private resetGame() {
this.gameEnded = false;
this.uncoveredTiles.clear();
this.flaggedTiles.clear();
this.createGrid();
}
Étape 3 : Générer la grille et placer les mines Construire une grille de 10x10 cases et positionner aléatoirement les mines.
private createGrid() {
this.gridData = Array.from({ length: 10 }, (_, row) =>
Array.from({ length: 10 }, (_, col) => new Tile(row, col))
);
this.setRandomMines();
this.computeAdjacentMines();
}
Étape 4 : Gérer les interactions utilisateur Implémenter la logique pour révéler des cases et marquer des mines via des gestes.
private uncoverTile(row: number, col: number) {
// Logique de révélation...
}
build() {
// Utiliser Flex et ForEach pour itérer sur la grille...
}
Étape 5 : Afficher les résultats du jeu Afficher un message de victoire ou de défaite selon l'état final du jeu.
private displayLoseMessage() {
// Message de défaite...
}
private displayWinMessage() {
// Message de victoire...
}
Étape 6 : Définir la classe Tile Créer une classe pour stocker les informations relatives à chaque case.
@ObservedV2
class Tile {
// Attributs de la tuile...
}