HarmonyOS NEXT Development Case: 24-Point Calculation Game
zhongcx

zhongcx @zhongcx

About: The best time to plant a tree was 10 years ago, the second-best time is now.

Joined:
Mar 6, 2025

HarmonyOS NEXT Development Case: 24-Point Calculation Game

Publish Date: May 11
0 0

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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]
  }
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Comments 0 total

    Add comment