HarmonyOS NEXT Development Case Study: Whac-A-Mole Game
This article demonstrates how to implement a classic "Whac-A-Mole" game using HarmonyOS NEXT's ArkUI framework. The implementation showcases various UI components, animation handling, and state management capabilities. Core Code Implementation 1. Hamster Component (Game Character) import { curves, window } from '@kit.ArkUI' @Component struct Hamster { @Prop cellWidth: number // Cell width for layout calculations build() { Stack() { // Stack layout container // Body Text() .width(`${this.cellWidth / 2}lpx`) .height(`${this.cellWidth / 3 * 2}lpx`) .backgroundColor("#b49579") .borderRadius({ topLeft: '50%', topRight: '50%' }) .borderColor("#2a272d") .borderWidth(1) // Mouth Ellipse() .width(`${this.cellWidth / 4}lpx`) .height(`${this.cellWidth / 5}lpx`) .fillOpacity(1) .fill("#e7bad7") .stroke("#563e3f") .strokeWidth(1) .margin({ top: `${this.cellWidth / 6}lpx`) // Left eye Ellipse() .width(`${this.cellWidth / 9}lpx`) .height(`${this.cellWidth / 6}lpx`) .fillOpacity(1) .fill("#313028") .stroke("#2e2018") .strokeWidth(1) .margin({ bottom: `${this.cellWidth / 3}lpx`, right: `${this.cellWidth / 6}lpx`) // Right eye Ellipse() .width(`${this.cellWidth / 9}lpx`) .height(`${this.cellWidth / 6}lpx`) .fillOpacity(1) .fill("#313028") .stroke("#2e2018") .strokeWidth(1) .margin({ bottom: `${this.cellWidth / 3}lpx`, left: `${this.cellWidth / 6}lpx`) // Eye highlights Ellipse() .width(`${this.cellWidth / 20}lpx`) .height(`${this.cellWidth / 15}lpx`) .fillOpacity(1) .fill("#fefbfa") .margin({ bottom: `${this.cellWidth / 2.5}lpx`, right: `${this.cellWidth / 6}lpx`) Ellipse() .width(`${this.cellWidth / 20}lpx`) .height(`${this.cellWidth / 15}lpx`) .fillOpacity(1) .fill("#fefbfa") .margin({ bottom: `${this.cellWidth / 2.5}lpx`, left: `${this.cellWidth / 6}lpx`) }.width(`${this.cellWidth}lpx`).height(`${this.cellWidth}lpx`) } } 2. Game Cell Management @ObservedV2 class Cell { @Trace scaleOptions: ScaleOptions = { x: 1, y: 1 }; @Trace isSelected: boolean = false cellWidth: number selectTime: number = 0 constructor(cellWidth: number) { this.cellWidth = cellWidth } setSelectedTrueTime() { this.selectTime = Date.now() this.isSelected = true } checkTime(stayDuration: number) { if (this.isSelected && Date.now() - this.selectTime >= stayDuration) { this.selectTime = 0 this.isSelected = false } } } 3. Game Timer Implementation class MyTextTimerModifier implements ContentModifier { applyContent(): WrappedBuilder { return wrapBuilder(buildTextTimer) } } @Builder function buildTextTimer(config: TextTimerConfiguration) { Column() { Stack({ alignContent: Alignment.Center }) { Circle({ width: 150, height: 150 }) .fill(config.started ? (config.isCountDown ? 0xFF232323 : 0xFF717171) : 0xFF929292) Column() { Text(config.isCountDown ? "Countdown" : "Elapsed Time").fontColor(Color.White) Text( (config.isCountDown ? "Remaining: " : "Elapsed: ") + (config.isCountDown ? Math.max(config.count/1000 - config.elapsedTime/100, 0).toFixed(0) : (config.elapsedTime/100).toFixed(0)) + "s" ).fontColor(Color.White) } } } } Key Implementation Points 1. Responsive Layout System Uses lpx units for dynamic sizing Flexible layout calculations based on cell dimensions Adaptive component positioning using margin properties 2. Animation System Spring animation curve implementation: curves.springCurve(10, 1, 228, 30) Scale transformations for hit effects Timed visibility control for game elements 3. Game State Management @ObservedV2 decorator for observable classes @trace for state tracking Fisher-Yates shuffle algorithm for random position selection: for (let i = 0; i

This article demonstrates how to implement a classic "Whac-A-Mole" game using HarmonyOS NEXT's ArkUI framework. The implementation showcases various UI components, animation handling, and state management capabilities.
Core Code Implementation
1. Hamster Component (Game Character)
import { curves, window } from '@kit.ArkUI'
@Component
struct Hamster {
@Prop cellWidth: number // Cell width for layout calculations
build() {
Stack() { // Stack layout container
// Body
Text()
.width(`${this.cellWidth / 2}lpx`)
.height(`${this.cellWidth / 3 * 2}lpx`)
.backgroundColor("#b49579")
.borderRadius({ topLeft: '50%', topRight: '50%' })
.borderColor("#2a272d")
.borderWidth(1)
// Mouth
Ellipse()
.width(`${this.cellWidth / 4}lpx`)
.height(`${this.cellWidth / 5}lpx`)
.fillOpacity(1)
.fill("#e7bad7")
.stroke("#563e3f")
.strokeWidth(1)
.margin({ top: `${this.cellWidth / 6}lpx`)
// Left eye
Ellipse()
.width(`${this.cellWidth / 9}lpx`)
.height(`${this.cellWidth / 6}lpx`)
.fillOpacity(1)
.fill("#313028")
.stroke("#2e2018")
.strokeWidth(1)
.margin({ bottom: `${this.cellWidth / 3}lpx`, right: `${this.cellWidth / 6}lpx`)
// Right eye
Ellipse()
.width(`${this.cellWidth / 9}lpx`)
.height(`${this.cellWidth / 6}lpx`)
.fillOpacity(1)
.fill("#313028")
.stroke("#2e2018")
.strokeWidth(1)
.margin({ bottom: `${this.cellWidth / 3}lpx`, left: `${this.cellWidth / 6}lpx`)
// Eye highlights
Ellipse()
.width(`${this.cellWidth / 20}lpx`)
.height(`${this.cellWidth / 15}lpx`)
.fillOpacity(1)
.fill("#fefbfa")
.margin({ bottom: `${this.cellWidth / 2.5}lpx`, right: `${this.cellWidth / 6}lpx`)
Ellipse()
.width(`${this.cellWidth / 20}lpx`)
.height(`${this.cellWidth / 15}lpx`)
.fillOpacity(1)
.fill("#fefbfa")
.margin({ bottom: `${this.cellWidth / 2.5}lpx`, left: `${this.cellWidth / 6}lpx`)
}.width(`${this.cellWidth}lpx`).height(`${this.cellWidth}lpx`)
}
}
2. Game Cell Management
@ObservedV2
class Cell {
@Trace scaleOptions: ScaleOptions = { x: 1, y: 1 };
@Trace isSelected: boolean = false
cellWidth: number
selectTime: number = 0
constructor(cellWidth: number) {
this.cellWidth = cellWidth
}
setSelectedTrueTime() {
this.selectTime = Date.now()
this.isSelected = true
}
checkTime(stayDuration: number) {
if (this.isSelected && Date.now() - this.selectTime >= stayDuration) {
this.selectTime = 0
this.isSelected = false
}
}
}
3. Game Timer Implementation
class MyTextTimerModifier implements ContentModifier<TextTimerConfiguration> {
applyContent(): WrappedBuilder<[TextTimerConfiguration]> {
return wrapBuilder(buildTextTimer)
}
}
@Builder
function buildTextTimer(config: TextTimerConfiguration) {
Column() {
Stack({ alignContent: Alignment.Center }) {
Circle({ width: 150, height: 150 })
.fill(config.started ? (config.isCountDown ? 0xFF232323 : 0xFF717171) : 0xFF929292)
Column() {
Text(config.isCountDown ? "Countdown" : "Elapsed Time").fontColor(Color.White)
Text(
(config.isCountDown ? "Remaining: " : "Elapsed: ") +
(config.isCountDown ?
Math.max(config.count/1000 - config.elapsedTime/100, 0).toFixed(0) :
(config.elapsedTime/100).toFixed(0)) + "s"
).fontColor(Color.White)
}
}
}
}
Key Implementation Points
1. Responsive Layout System
- Uses
lpx
units for dynamic sizing - Flexible layout calculations based on cell dimensions
- Adaptive component positioning using margin properties
2. Animation System
- Spring animation curve implementation:
curves.springCurve(10, 1, 228, 30)
- Scale transformations for hit effects
- Timed visibility control for game elements
3. Game State Management
- @ObservedV2 decorator for observable classes
- @trace for state tracking
- Fisher-Yates shuffle algorithm for random position selection:
for (let i = 0; i < availableIndexList.length; i++) {
let index = Math.floor(Math.random() * (availableIndexList.length - i))
// Swap positions
}
4. Game Configuration
- Customizable parameters:
-
gameDuration
: Total game time -
appearanceCount
: Moles per wave -
animationInterval
: Spawn interval -
hamsterStayDuration
: Visible duration
-
Game Features
- Landscape orientation setup:
windowClass.setPreferredOrientation(window.Orientation.LANDSCAPE)
- Score tracking system
- Configurable game parameters
- Visual feedback mechanisms:
- Scale animations
- Color state indicators
- Game completion dialog:
showAlertDialog({ title: 'Game Over', message: `Score: ${currentScore}` })
This implementation demonstrates HarmonyOS NEXT's capabilities in building interactive games with complex state management and smooth animations. The component-based architecture allows for maintainable code structure while leveraging ArkUI's declarative syntax for efficient UI updates.
Developers can extend this foundation by:
- Adding sound effects
- Implementing difficulty levels
- Creating progressive animation systems
- Adding multiplayer support
- Integrating with device sensors for alternative input methods
The complete code demonstrates best practices in HarmonyOS application development, including responsive design, state management, and user interaction handling.