HarmonyOS NEXT Development Case: 24-Point Calculation Game

This article demonstrates a 24-point calculation game implementation on HarmonyOS NEXT platform, showcasing core features including dynamic UI interaction, mathematical operations, and recursive solution finding algorithms. 1. Core Class Design 1.1 Cell Class - Game Cell Management @ObservedV2 class Cell { @Trace value: number // Tracked numeric value @Trace displayValue: string // Tracked display value @Trace isVisible: boolean // Visibility state @Trace xPosition: number // X-axis position @Trace yPosition: number // Y-axis position columnIndex: number // Column index rowIndex: number // Row index constructor(rowIndex: number, columnIndex: number) { this.rowIndex = rowIndex this.columnIndex = columnIndex this.xPosition = 0 this.yPosition = 0 this.value = 0 this.displayValue = '' this.isVisible = true } setDefaultValue(value: number) { this.value = value this.displayValue = `${value}` this.isVisible = true } performOperation(otherCell: Cell, operationName: string) { switch (operationName) { case "+": this.value = otherCell.value + this.value break case "-": this.value = otherCell.value - this.value break case "×": this.value = otherCell.value * this.value break case "÷": if (this.value === 0) { promptAction.showToast({ message: 'Divisor cannot be zero', bottom: 400 }) return false } this.value = otherCell.value / this.value break } otherCell.isVisible = false this.displayValue = `${this.value >= 0 ? '' : '-'}${this.convertToFraction(Math.abs(this.value))}` return true } // Fraction conversion implementation private convertToFraction(decimal: number): string { const tolerance = 1.0E-6 const maxIterations = 1000 let [h1, h2, k1, k2] = [1, 0, 0, 1] let current = decimal let iteration = 0 do { const integer = Math.floor(current) const [hNext, kNext] = [ integer * h1 + h2, integer * k1 + k2 ] ;[h1, h2, k1, k2] = [hNext, h1, kNext, k1] current = 1 / (current - integer) iteration++ } while ( Math.abs(decimal - h1 / k1) > decimal * tolerance && iteration = maxIterations) return `${decimal}` const gcd = this.calculateGCD(h1, k1) return `${h1/gcd}${k1/gcd !== 1 ? `/${k1/gcd}` : ''}` } private calculateGCD(a: number, b: number): number { return b === 0 ? a : this.calculateGCD(b, a % b) } } 1.2 Solution Finder Class class JudgePointSolution { private solutions: string[] = [] private readonly epsilon = 1e-6 private readonly operations = [ (a: number, b: number) => a + b, (a: number, b: number) => a * b, (a: number, b: number) => a - b, (a: number, b: number) => a / b, ] findSolutions(numbers: number[]): string[] { this.solutions = [] this.depthFirstSearch(numbers, '') return this.solutions } private depthFirstSearch(current: number[], path: string) { if (this.solutions.length > 0) return if (current.length === 1) { if (Math.abs(current[0] - 24) { const value = Math.floor(Math.random() * 13) + 1 cell.setDefaultValue(value) }) // ... rest of initialization } build() { Column({ space: 20 }) { // Solution display Text(this.solver.findSolutions(this.cells.map(c => c.value))) .visibility(this.showSolution ? Visibility.Visible : Visibility.Hidden) // Game grid Grid() { ForEach(this.cells, (cell, index) => Text(cell.displayValue) .onClick(() => this.handleCellClick(index)) ) } // Control buttons ButtonGroup() { Button('New Game').onClick(() => this.initializeGame()) Button('Solution').onClick(() => this.showSolution = !this.showSolution) } } } private handleCellClick(index: number) { if (this.selectedCell === -1) { this.selectedCell = index } else { this.executeOperation(this.selectedCell, index) this.selectedCell = -1 } } private executeOperation(source: number, target: number) { // ... operation execution logic } } 3. Key Features 3.1 Fraction Representation Implements continuous fraction approximation algorithm for precise decimal-to-fraction conversion, essential for accurate calculation display. 3.2 Recursive Solution Search Utilizes depth-first search with backtracking to explore all possible operation combinations, ensuring comprehensive solution finding. 3.3 Reactive UI Updates Leverages HarmonyOS ArkUI's reactive programming model with @ObservedV2 and @trace decorators for efficient state management. 4. Optimization Strategies Early Termination: Stops searching when first solution is found Memoization: Caches intermediate results for

May 11, 2025 - 02:46
 0
HarmonyOS NEXT Development Case: 24-Point Calculation Game

Image description

This article demonstrates a 24-point calculation game implementation on HarmonyOS NEXT platform, showcasing core features including dynamic UI interaction, mathematical operations, and recursive solution finding algorithms.

1. Core Class Design

1.1 Cell Class - Game Cell Management

@ObservedV2
class Cell {
  @Trace value: number // Tracked numeric value
  @Trace displayValue: string // Tracked display value
  @Trace isVisible: boolean // Visibility state
  @Trace xPosition: number // X-axis position
  @Trace yPosition: number // Y-axis position
  columnIndex: number // Column index
  rowIndex: number // Row index

  constructor(rowIndex: number, columnIndex: number) {
    this.rowIndex = rowIndex
    this.columnIndex = columnIndex
    this.xPosition = 0
    this.yPosition = 0
    this.value = 0
    this.displayValue = ''
    this.isVisible = true
  }

  setDefaultValue(value: number) {
    this.value = value
    this.displayValue = `${value}`
    this.isVisible = true
  }

  performOperation(otherCell: Cell, operationName: string) {
    switch (operationName) {
      case "+":
        this.value = otherCell.value + this.value
        break
      case "-":
        this.value = otherCell.value - this.value
        break
      case "×":
        this.value = otherCell.value * this.value
        break
      case "÷":
        if (this.value === 0) {
          promptAction.showToast({ message: 'Divisor cannot be zero', bottom: 400 })
          return false
        }
        this.value = otherCell.value / this.value
        break
    }
    otherCell.isVisible = false
    this.displayValue = `${this.value >= 0 ? '' : '-'}${this.convertToFraction(Math.abs(this.value))}`
    return true
  }

  // Fraction conversion implementation
  private convertToFraction(decimal: number): string {
    const tolerance = 1.0E-6
    const maxIterations = 1000
    let [h1, h2, k1, k2] = [1, 0, 0, 1]
    let current = decimal
    let iteration = 0

    do {
      const integer = Math.floor(current)
      const [hNext, kNext] = [
        integer * h1 + h2,
        integer * k1 + k2
      ]
      ;[h1, h2, k1, k2] = [hNext, h1, kNext, k1]
      current = 1 / (current - integer)
      iteration++
    } while (
      Math.abs(decimal - h1 / k1) > decimal * tolerance &&
      iteration < maxIterations
    )

    if (iteration >= maxIterations) return `${decimal}`

    const gcd = this.calculateGCD(h1, k1)
    return `${h1/gcd}${k1/gcd !== 1 ? `/${k1/gcd}` : ''}`
  }

  private calculateGCD(a: number, b: number): number {
    return b === 0 ? a : this.calculateGCD(b, a % b)
  }
}

1.2 Solution Finder Class

class JudgePointSolution {
  private solutions: string[] = []
  private readonly epsilon = 1e-6
  private readonly operations = [
    (a: number, b: number) => a + b,
    (a: number, b: number) => a * b,
    (a: number, b: number) => a - b,
    (a: number, b: number) => a / b,
  ]

  findSolutions(numbers: number[]): string[] {
    this.solutions = []
    this.depthFirstSearch(numbers, '')
    return this.solutions
  }

  private depthFirstSearch(current: number[], path: string) {
    if (this.solutions.length > 0) return

    if (current.length === 1) {
      if (Math.abs(current[0] - 24) < this.epsilon) {
        this.solutions.push(path)
      }
      return
    }

    for (let i = 0; i < current.length; i++) {
      for (let j = i + 1; j < current.length; j++) {
        const remaining = current.filter((_, idx) => idx !== i && idx !== j)

        for (let op = 0; op < 4; op++) {
          const newPath = path ? `${path}, ` : ''
          const newValue = this.operations[op](current[i], current[j])

          remaining.push(newValue)
          this.depthFirstSearch(remaining, `${newPath}(${current[i]}${this.getSymbol(op)}${current[j]})`)
          remaining.pop()

          if (op > 1) { // Handle commutative operations
            const swappedValue = this.operations[op](current[j], current[i])
            remaining.push(swappedValue)
            this.depthFirstSearch(remaining, `${newPath}(${current[j]}${this.getSymbol(op)}${current[i]})`)
            remaining.pop()
          }
        }
      }
    }
  }

  private getSymbol(opIndex: number): string {
    return ['+', '×', '-', '÷'][opIndex]
  }
}

2. UI Implementation

2.1 Game Component Structure

@Entry
@Component
struct GameInterface {
  @State cells: Cell[] = [
    new Cell(0, 0), new Cell(0, 1),
    new Cell(1, 0), new Cell(1, 1)
  ]
  @State selectedCell: number = -1
  @State selectedOp: number = -1
  @State showSolution: boolean = false

  private cellSize: number = 250
  private solver = new JudgePointSolution()

  aboutToAppear(): void {
    this.initializeGame()
  }

  private initializeGame() {
    this.cells.forEach(cell => {
      const value = Math.floor(Math.random() * 13) + 1
      cell.setDefaultValue(value)
    })
    // ... rest of initialization
  }

  build() {
    Column({ space: 20 }) {
      // Solution display
      Text(this.solver.findSolutions(this.cells.map(c => c.value)))
        .visibility(this.showSolution ? Visibility.Visible : Visibility.Hidden)

      // Game grid
      Grid() {
        ForEach(this.cells, (cell, index) => 
          Text(cell.displayValue)
            .onClick(() => this.handleCellClick(index))
        )
      }

      // Control buttons
      ButtonGroup() {
        Button('New Game').onClick(() => this.initializeGame())
        Button('Solution').onClick(() => this.showSolution = !this.showSolution)
      }
    }
  }

  private handleCellClick(index: number) {
    if (this.selectedCell === -1) {
      this.selectedCell = index
    } else {
      this.executeOperation(this.selectedCell, index)
      this.selectedCell = -1
    }
  }

  private executeOperation(source: number, target: number) {
    // ... operation execution logic
  }
}

3. Key Features

3.1 Fraction Representation

Implements continuous fraction approximation algorithm for precise decimal-to-fraction conversion, essential for accurate calculation display.

3.2 Recursive Solution Search

Utilizes depth-first search with backtracking to explore all possible operation combinations, ensuring comprehensive solution finding.

3.3 Reactive UI Updates

Leverages HarmonyOS ArkUI's reactive programming model with @ObservedV2 and @trace decorators for efficient state management.

4. Optimization Strategies

  1. Early Termination: Stops searching when first solution is found
  2. Memoization: Caches intermediate results for better performance
  3. Threshold Handling: Uses epsilon comparison for floating-point equality
  4. Animation Optimization: Implements smooth transition effects using HarmonyOS animation APIs

This implementation demonstrates effective use of HarmonyOS NEXT's capabilities in building complex mathematical games while maintaining clean architecture and responsive UI design.