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

May 11, 2025 - 01:11
 0
HarmonyOS NEXT Development Case: 2048 Game Implementation

Image description

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

  1. Reactive State Management:
  2. @ObservedV2 enables automatic UI updates
  3. @Trace tracks individual cell value changes
  4. State-driven UI rendering

  5. Gesture Control System:

  6. Comprehensive swipe detection

  7. Directional movement handling

  8. Touch coordinate tracking

  9. Game Logic:

  10. Tile merging algorithms

  11. Score calculation

  12. Random tile generation

  13. Game reset functionality

  14. Visual Design:

  15. Dynamic color scheme

  16. Adaptive font sizing

  17. Responsive grid layout

  18. Smooth animations

  19. Architecture:

  20. Separation of game logic and UI

  21. Helper methods for code organization

  22. Clean state management

  23. Lifecycle methods for game control

Technical Highlights

  1. Swipe Detection:
.gesture(
  SwipeGesture({ direction: SwipeDirection.All })
    .onAction((event) => {
      const dx = event.offsetX;
      const dy = event.offsetY;
      // Handle direction based on dominant axis
    })
)
  1. Dynamic Styling:
.backgroundColor(this.colors[value ? Math.floor(Math.log2(value)) : 0])
.fontSize(`${value >= 100 ? cellSize/3 : cellSize/2}px`)
  1. 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]);
  1. State Preservation:
aboutToAppear() {
  this.resetGame();
}

Usage Guide

  1. Swipe in any direction to move tiles
  2. Matching numbers merge and increase score
  3. Game ends when board fills with no possible merges
  4. Click Restart to begin new game
  5. 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.