HarmonyOS NEXT Development Case: 2048 Game Implementation
This article demonstrates a 2048 game implementation using HarmonyOS NEXT, featuring reactive state management and gesture controls. Core Implementation 1. Game Cell Class // Decorator indicating observable state tracking @ObservedV2 class Cell { // Track value changes with decorator @Trace value: number; // Initialize cell value to 0 constructor() { this.value = 0; } } 2. Main Game Component @Entry @Component struct Game2048 { // Game state variables @State board: Cell[][] = []; // Game board cells @State score: number = 0; // Current score @State cellSize: number = 200; // Cell dimension @State cellMargin: number = 5; // Cell spacing @State screenStartX: number = 0; // Touch start X @State screenStartY: number = 0; // Touch start Y @State lastScreenX: number = 0; // Touch end X @State lastScreenY: number = 0; // Touch end Y // Color scheme for different values colors: string[] = [ '#CCCCCC', // 0 - Gray '#FFC107', // 2 - Yellow '#FF9800', // 4 - Orange '#FF5722', // 8 - Dark Orange '#F44336', // 16 - Red '#9C27B0', // 32 - Purple '#3F51B5', // 64 - Indigo '#00BCD4', // 128 - Cyan '#009688', // 256 - Teal '#4CAF50', // 512 - Green '#8BC34A', // 1024 - Light Green '#CDDC39', // 2048 - Lime '#FFEB3B', // 4096 - Light Yellow '#795548', // 8192 - Brown '#607D8B', // 16384 - Dark Gray '#9E9E9E', // 32768 - Gray '#000000' // Above - Black ]; // Lifecycle method aboutToAppear() { this.resetGame(); } // Initialize game board initBoard() { if (this.board.length === 0) { for (let i = 0; i cell.value = 0)); } } // Add new tiles to board addRandomTiles(count: number) { let emptyCells: {row: number, col: number}[] = []; this.board.forEach((row, i) => { row.forEach((cell, j) => { if (cell.value === 0) emptyCells.push({row: i, col: j}); }); }); for (let i = 0; i cell.value !== 0).map(cell => cell.value); let merged = new Array(4).fill(false); for (let j = 0; j { Text(`${cell.value || ''}`) .size(`${this.cellSize}px`) .margin(`${this.cellMargin}px`) .fontSize(this.calculateFontSize(cell.value)) .backgroundColor(this.getCellColor(cell.value)) .fontColor(cell.value ? '#fff' : '#000'); }); } .width(`${(this.cellSize + this.cellMargin * 2) * 4}px`); Button('Restart').onClick(() => this.resetGame()); } .gesture( SwipeGesture({ direction: SwipeDirection.All }) .onAction(this.handleSwipe.bind(this)) ); } // Helper methods private resetGame() { this.score = 0; this.initBoard(); this.addRandomTiles(2); } private handleSwipe() { const dx = this.lastScreenX - this.screenStartX; const dy = this.lastScreenY - this.screenStartY; if (Math.abs(dx) > Math.abs(dy)) { dx > 0 ? this.slideRight() : this.slideLeft(); } else { dy > 0 ? this.slideDown() : this.slideUp(); } this.addRandomTiles(1); } private calculateFontSize(value: number): string { return `${value >= 100 ? this.cellSize / 3 : this.cellSize / 2}px`; } private getCellColor(value: number): string { return this.colors[value ? Math.floor(Math.log2(value)) : 0]; } } Key Features Reactive State Management: @ObservedV2 enables automatic UI updates @Trace tracks individual cell value changes State-driven UI rendering Gesture Control System: Comprehensive swipe detection Directional movement handling Touch coordinate tracking Game Logic: Tile merging algorithms Score calculation Random tile generation Game reset functionality Visual Design: Dynamic color scheme Adaptive font sizing Responsive grid layout Smooth animations Architecture: Separation of game logic and UI Helper methods for code organization Clean state management Lifecycle methods for game control Technical Highlights Swipe Detection: .gesture( SwipeGesture({ direction: SwipeDirection.All }) .onAction((event) => { const dx = event.offsetX; const dy = event.offsetY; // Handle direction based on dominant axis }) ) Dynamic Styling: .backgroundColor(this.colors[value ? Math.floor(Math.log2(value)) : 0]) .fontSize(`${value >= 100 ? cellSize/3 : cellSize/2}px`) Efficient Board Updates: // Example for left slide let temp = row.filter(cell => cell.value !== 0).map(cell => cell.value); while (temp.length cell.value = temp[j]); State Preservation: aboutToAppear() { this.resetGame(); } Usage Guide Swipe in any direction to move tiles Matching numbers merge and increase score Game ends when board fills with no possible merges Click Restart to begin new game Observe color changes for different values This implementation demonstrates HarmonyOS NEXT's capabilities in handl

This article demonstrates a 2048 game implementation using HarmonyOS NEXT, featuring reactive state management and gesture controls.
Core Implementation
1. Game Cell Class
// Decorator indicating observable state tracking
@ObservedV2
class Cell {
// Track value changes with decorator
@Trace value: number;
// Initialize cell value to 0
constructor() {
this.value = 0;
}
}
2. Main Game Component
@Entry
@Component
struct Game2048 {
// Game state variables
@State board: Cell[][] = []; // Game board cells
@State score: number = 0; // Current score
@State cellSize: number = 200; // Cell dimension
@State cellMargin: number = 5; // Cell spacing
@State screenStartX: number = 0; // Touch start X
@State screenStartY: number = 0; // Touch start Y
@State lastScreenX: number = 0; // Touch end X
@State lastScreenY: number = 0; // Touch end Y
// Color scheme for different values
colors: string[] = [
'#CCCCCC', // 0 - Gray
'#FFC107', // 2 - Yellow
'#FF9800', // 4 - Orange
'#FF5722', // 8 - Dark Orange
'#F44336', // 16 - Red
'#9C27B0', // 32 - Purple
'#3F51B5', // 64 - Indigo
'#00BCD4', // 128 - Cyan
'#009688', // 256 - Teal
'#4CAF50', // 512 - Green
'#8BC34A', // 1024 - Light Green
'#CDDC39', // 2048 - Lime
'#FFEB3B', // 4096 - Light Yellow
'#795548', // 8192 - Brown
'#607D8B', // 16384 - Dark Gray
'#9E9E9E', // 32768 - Gray
'#000000' // Above - Black
];
// Lifecycle method
aboutToAppear() {
this.resetGame();
}
// Initialize game board
initBoard() {
if (this.board.length === 0) {
for (let i = 0; i < 4; i++) {
let row: Cell[] = [];
for (let j = 0; j < 4; j++) {
row.push(new Cell());
}
this.board.push(row);
}
} else {
this.board.forEach(row => row.forEach(cell => cell.value = 0));
}
}
// Add new tiles to board
addRandomTiles(count: number) {
let emptyCells: {row: number, col: number}[] = [];
this.board.forEach((row, i) => {
row.forEach((cell, j) => {
if (cell.value === 0) emptyCells.push({row: i, col: j});
});
});
for (let i = 0; i < count; i++) {
if (emptyCells.length === 0) break;
const index = Math.floor(Math.random() * emptyCells.length);
const {row, col} = emptyCells[index];
this.board[row][col].value = Math.random() < 0.9 ? 2 : 4;
emptyCells.splice(index, 1);
}
}
// Slide movement logic (left example)
slideLeft() {
this.board.forEach((row, i) => {
let temp = row.filter(cell => cell.value !== 0).map(cell => cell.value);
let merged = new Array(4).fill(false);
for (let j = 0; j < temp.length - 1; j++) {
if (temp[j] === temp[j + 1] && !merged[j]) {
temp[j] *= 2;
this.score += temp[j];
merged[j] = true;
temp.splice(j + 1, 1);
}
}
while (temp.length < 4) temp.push(0);
row.forEach((cell, j) => cell.value = temp[j]);
});
}
// Similar implementations for slideRight(), slideUp(), slideDown()
// UI Construction
build() {
Column({ space: 10 }) {
Text(`Score: ${this.score}`)
.fontSize(24)
.margin({ top: 20 });
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.board.flat(), (cell: Cell, index: number) => {
Text(`${cell.value || ''}`)
.size(`${this.cellSize}px`)
.margin(`${this.cellMargin}px`)
.fontSize(this.calculateFontSize(cell.value))
.backgroundColor(this.getCellColor(cell.value))
.fontColor(cell.value ? '#fff' : '#000');
});
}
.width(`${(this.cellSize + this.cellMargin * 2) * 4}px`);
Button('Restart').onClick(() => this.resetGame());
}
.gesture(
SwipeGesture({ direction: SwipeDirection.All })
.onAction(this.handleSwipe.bind(this))
);
}
// Helper methods
private resetGame() {
this.score = 0;
this.initBoard();
this.addRandomTiles(2);
}
private handleSwipe() {
const dx = this.lastScreenX - this.screenStartX;
const dy = this.lastScreenY - this.screenStartY;
if (Math.abs(dx) > Math.abs(dy)) {
dx > 0 ? this.slideRight() : this.slideLeft();
} else {
dy > 0 ? this.slideDown() : this.slideUp();
}
this.addRandomTiles(1);
}
private calculateFontSize(value: number): string {
return `${value >= 100 ? this.cellSize / 3 : this.cellSize / 2}px`;
}
private getCellColor(value: number): string {
return this.colors[value ? Math.floor(Math.log2(value)) : 0];
}
}
Key Features
- Reactive State Management:
-
@ObservedV2
enables automatic UI updates -
@Trace
tracks individual cell value changes State-driven UI rendering
Gesture Control System:
Comprehensive swipe detection
Directional movement handling
Touch coordinate tracking
Game Logic:
Tile merging algorithms
Score calculation
Random tile generation
Game reset functionality
Visual Design:
Dynamic color scheme
Adaptive font sizing
Responsive grid layout
Smooth animations
Architecture:
Separation of game logic and UI
Helper methods for code organization
Clean state management
Lifecycle methods for game control
Technical Highlights
- Swipe Detection:
.gesture(
SwipeGesture({ direction: SwipeDirection.All })
.onAction((event) => {
const dx = event.offsetX;
const dy = event.offsetY;
// Handle direction based on dominant axis
})
)
- Dynamic Styling:
.backgroundColor(this.colors[value ? Math.floor(Math.log2(value)) : 0])
.fontSize(`${value >= 100 ? cellSize/3 : cellSize/2}px`)
- Efficient Board Updates:
// Example for left slide
let temp = row.filter(cell => cell.value !== 0).map(cell => cell.value);
while (temp.length < 4) temp.push(0);
row.forEach((cell, j) => cell.value = temp[j]);
- State Preservation:
aboutToAppear() {
this.resetGame();
}
Usage Guide
- Swipe in any direction to move tiles
- Matching numbers merge and increase score
- Game ends when board fills with no possible merges
- Click Restart to begin new game
- Observe color changes for different values
This implementation demonstrates HarmonyOS NEXT's capabilities in handling complex game logic, responsive touch controls, and efficient state management. The decorator-based approach ensures optimal performance while maintaining clean code structure.