DEV Community

Cover image for HarmonyOS NEXT Development Case: Guess the Ball Game
zhongcx
zhongcx

Posted on

HarmonyOS NEXT Development Case: Guess the Ball Game

Image description

This article demonstrates a simple "Guess the Ball" game implementation using HarmonyOS NEXT, featuring state management with decorators and animation control.

Core Implementation

1. Cup Class with State Tracking

// Use decorator for object change tracking
@ObservedV2
class Cup {
  // Track property changes with decorators
  @Trace positionX: number; // X-axis position
  @Trace positionY: number; // Y-axis position
  @Trace containsBall: boolean; // Whether contains the ball
  @Trace isRevealed: boolean; // Whether cup is opened

  // Constructor to initialize cup states
  constructor(hasBall: boolean) {
    this.positionX = 0;
    this.positionY = 0;
    this.containsBall = hasBall;
    this.isRevealed = true;
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Game Component Structure

@Entry
@Component
struct ThreeCupGame {
  // Game state variables
  @State gameCups: Cup[] = [
    new Cup(true),
    new Cup(false),
    new Cup(false)
  ];
  @State cupWidth: number = 200;
  @State cupSpacing: number = 10;
  @State animationDurationMs: number = 140;
  @State isGameAnimating: boolean = false;
  @State mixingCount: number = 5;
  @State currentMixingCount: number = 0;

  // Game initialization
  startGame() {
    this.currentMixingCount--;
    const cupPairs = [[0, 1], [0, 2], [1, 2]];
    const selectedPair = cupPairs[Math.floor(Math.random() * cupPairs.length)];
    this.moveCups(selectedPair[0], selectedPair[1]);
  }

  // Cup movement logic
  moveCups(cupIndex1: number, cupIndex2: number) {
    const direction: number = Math.random() < 0.5 ? -1 : 1;
    const distanceFactor: number = Math.abs(cupIndex1 - cupIndex2);
    const adjustedDistanceFactor: number = distanceFactor === 1 ? 2 : 1;

    // Animation sequence for first cup
    animateToImmediately({
      delay: 0,
      duration: this.animationDurationMs
    }, () => {
      this.gameCups[cupIndex1].positionY = -direction * (this.cupWidth + this.cupSpacing * 2) / adjustedDistanceFactor
    });

    // Animation sequence for second cup
    animateToImmediately({
      delay: 0,
      duration: this.animationDurationMs
    }, () => {
      this.gameCups[cupIndex2].positionY = direction * (this.cupWidth + this.cupSpacing * 2) / adjustedDistanceFactor
    });

    // Animation completion handler
    animateToImmediately({
      delay: this.animationDurationMs * 2,
      duration: this.animationDurationMs,
      onFinish: () => {
        this.swapBalls(cupIndex1, cupIndex2)
      }
    }, () => {
      this.gameCups[cupIndex2].positionX = -(this.cupWidth + this.cupSpacing * 2) * distanceFactor
      this.gameCups[cupIndex2].positionY = 0
    });
  }

  // Ball swapping logic
  swapBalls(cupIndex1: number, cupIndex2: number) {
    [this.gameCups[cupIndex1].containsBall, this.gameCups[cupIndex2].containsBall] = 
      [this.gameCups[cupIndex2].containsBall, this.gameCups[cupIndex1].containsBall];

    if (this.currentMixingCount <= 0) {
      this.isGameAnimating = false;
    } else {
      setTimeout(() => this.startGame(), 10);
    }
  }

  // UI Construction
  build() {
    Column({ space: 20 }) {
      Text('Guess the Ball Game')
        .fontSize(24)
        .margin({ top: 20 });

      // Animation speed control
      Counter() {
        Text(`Current Speed: ${this.animationDurationMs}ms`)
          .fontColor(Color.Black)
          .fontSize('26lpx');
      }.onInc(() => this.animationDurationMs += 10)
       .onDec(() => this.animationDurationMs = Math.max(10, this.animationDurationMs - 10));

      // Mixing count control
      Counter() {
        Text(`Mixes per Round: ${this.mixingCount}`)
          .fontColor(Color.Black)
          .fontSize('26lpx');
      }.onInc(() => this.mixingCount += 1)
       .onDec(() => this.mixingCount = Math.max(1, this.mixingCount - 1));

      // Cups layout
      Row() {
        ForEach(this.gameCups, (cup: Cup) => {
          Text(cup.isRevealed ? (cup.containsBall ? 'Ball' : 'Empty') : '')
            .size(`${this.cupWidth}lpx`)
            .margin(`${this.cupSpacing}lpx`)
            .backgroundColor(Color.Orange)
            .translate({ x: `${cup.positionX}lpx`, y: `${cup.positionY}lpx` })
            .onClick(() => {
              if (!this.isGameAnimating) cup.isRevealed = true;
            });
        });
      }.height('720lpx').backgroundColor(Color.Gray);

      // Start button
      Button('Start Game').onClick(() => {
        if (!this.isGameAnimating) {
          this.currentMixingCount = this.mixingCount;
          this.isGameAnimating = true;
          this.gameCups.forEach(cup => cup.isRevealed = false);
          this.startGame();
        }
      });
    }.size('100%');
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Features

  1. State Management:
  2. @ObservedV2 and @Trace decorators enable efficient state tracking
  3. Automatic UI updates when cup properties change

  4. Animation System:

  5. Sequential animations using animateToImmediately

  6. Configurable duration and delay parameters

  7. Smooth transition effects for cup movements

  8. Game Logic:

  9. Random cup pair selection

  10. Configurable mixing count

  11. Ball position swapping mechanism

  12. UI Controls:

  13. Interactive speed adjustment

  14. Visual feedback for cup states

  15. Responsive layout adaptation

Usage

  1. Adjust animation speed using the speed control
  2. Set desired mixing count
  3. Click "Start Game" to begin
  4. Click cups after mixing to reveal contents
  5. Adjust parameters between rounds

This implementation demonstrates HarmonyOS NEXT's capabilities in handling complex state management, smooth animations, and responsive UI interactions. The decorator-based approach provides efficient state tracking while maintaining clean code structure.

Top comments (0)