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.
Top comments (0)