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
- Early Termination: Stops searching when first solution is found
- Memoization: Caches intermediate results for better performance
- Threshold Handling: Uses epsilon comparison for floating-point equality
- 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.
Top comments (0)