HarmonyOS NEXT Development Case: Tic-Tac-Toe Game Implementation

This article demonstrates a Tic-Tac-Toe game implementation using HarmonyOS NEXT's ArkUI framework, showcasing key features like reactive UI, animation handling, and game logic implementation. 1. Core Class Design 1.1 Cell Class - Game Piece Representation // Import prompt dialog module import { promptAction } from '@kit.ArkUI'; // Utilize framework features like property tracking @ObservedV2 class Cell { // Define piece type: 0 for empty, 1 for black, 2 for white @Trace user: number = 0; // Piece identifier (e.g., "A", "B") name: string = ""; // Position coordinates x: number = 0; y: number = 0; // Piece dimensions width: number = 100; height: number = 100; // Constructor initializes piece state constructor(name: string, x: number, y: number, user: number) { this.user = user; this.name = name; this.x = x; this.y = y; } // Animation offset values @Trace animX: number = 0; @Trace animY: number = 0; // Calculate center coordinates getCenterX() { return this.x - this.width / 2; } getCenterY() { return this.y - this.height / 2; } // Handle piece movement animation moveAnimation(animationTime: number, toCell: Cell, callback?: () => void) { animateToImmediately({ duration: animationTime, iterations: 1, curve: Curve.Linear, onFinish: () => { animateToImmediately({ duration: 0, iterations: 1, curve: Curve.Linear, onFinish: () => { callback?.(); } }, () => { this.animX = 0; this.animY = 0; const temp = this.user; this.user = toCell.user; toCell.user = temp; }); } }, () => { this.animX = toCell.x - this.x; this.animY = toCell.y - this.y; }); } } 1.2 Connection Class - Piece Relationships class Connection { startName: string; endName: string; startX: number; startY: number; endX: number; endY: number; constructor(start: Cell, end: Cell) { this.startName = start.name; this.endName = end.name; this.startX = start.x; this.startY = start.y; this.endX = end.x; this.endY = end.y; } } 2. Game Implementation Logic 2.1 Main Game Component @Entry @Component struct TwoSonChessGame { @State isAnimationRunning: boolean = false; @State cells: Cell[] = []; @State connections: Connection[] = []; @State currentPlayer: number = 1; // Initialize game board aboutToAppear(): void { const cellA = new Cell("A", 180, 180, 2); const cellB = new Cell("B", 540, 180, 1); const cellC = new Cell("C", 360, 360, 0); const cellD = new Cell("D", 180, 540, 1); const cellE = new Cell("E", 540, 540, 2); this.cells.push(cellA, cellB, cellC, cellD, cellE); this.connections.push( new Connection(cellA, cellB), new Connection(cellA, cellC), new Connection(cellA, cellD), new Connection(cellB, cellC), new Connection(cellC, cellD), new Connection(cellC, cellE), new Connection(cellD, cellE) ); } // Game control methods resetGame() { this.currentPlayer = 1; this.cells[0].user = 2; this.cells[1].user = 1; this.cells[2].user = 0; this.cells[3].user = 1; this.cells[4].user = 2; } // Move validation and execution move(cell: Cell) { if (this.isCellValid(cell)) { const targetIndex = this.checkValidMove(cell); if (targetIndex !== -1) { this.isAnimationRunning = true; cell.moveAnimation(300, this.cells[targetIndex], () => { this.isAnimationRunning = false; this.moveCompleted(); }); } } } // Post-move handling moveCompleted() { this.currentPlayer = this.currentPlayer === 1 ? 2 : 1; if (this.isGameOver()) { const winner = this.currentPlayer === 1 ? 'White Wins' : 'Black Wins'; promptAction.showDialog({ title: 'Game Over', message: winner, buttons: [{ text: 'Restart', color: '#ffa500' }] }).then(() => this.resetGame()); } else if (this.currentPlayer === 2) { this.aiMove(); } } // AI implementation aiMove() { const whiteCells = this.cells.filter(cell => cell.user === 2 && this.checkValidMove(cell) !== -1); if (whiteCells.length === 1) { this.move(whiteCells[0]); } else if (whiteCells.length === 2) { const moveIndex = this.chooseBestMove(whiteCells); this.move(whiteCells[moveIndex]); } } } 3. UI Implementation build() { Column({ space: 10 }) { Stack() { // Draw connection lines ForEach(this.connections, (conn: Connection) => { Line() .width(5).height(5) .startPoint([`${conn.startX}lpx`, `${conn.startY}lpx`]) .endPoint([`${conn.endX}lpx`, `${conn.endY}lpx`]) .stroke(Color.Black) .fill(Color

May 11, 2025 - 02:12
 0
HarmonyOS NEXT Development Case: Tic-Tac-Toe Game Implementation

Image description

This article demonstrates a Tic-Tac-Toe game implementation using HarmonyOS NEXT's ArkUI framework, showcasing key features like reactive UI, animation handling, and game logic implementation.

1. Core Class Design

1.1 Cell Class - Game Piece Representation

// Import prompt dialog module
import { promptAction } from '@kit.ArkUI';

// Utilize framework features like property tracking
@ObservedV2
class Cell {
  // Define piece type: 0 for empty, 1 for black, 2 for white
  @Trace user: number = 0;
  // Piece identifier (e.g., "A", "B")
  name: string = "";
  // Position coordinates
  x: number = 0;
  y: number = 0;
  // Piece dimensions
  width: number = 100;
  height: number = 100;

  // Constructor initializes piece state
  constructor(name: string, x: number, y: number, user: number) {
    this.user = user;
    this.name = name;
    this.x = x;
    this.y = y;
  }

  // Animation offset values
  @Trace animX: number = 0;
  @Trace animY: number = 0;

  // Calculate center coordinates
  getCenterX() {
    return this.x - this.width / 2;
  }

  getCenterY() {
    return this.y - this.height / 2;
  }

  // Handle piece movement animation
  moveAnimation(animationTime: number, toCell: Cell, callback?: () => void) {
    animateToImmediately({
      duration: animationTime,
      iterations: 1,
      curve: Curve.Linear,
      onFinish: () => {
        animateToImmediately({
          duration: 0,
          iterations: 1,
          curve: Curve.Linear,
          onFinish: () => {
            callback?.();
          }
        }, () => {
          this.animX = 0;
          this.animY = 0;
          const temp = this.user;
          this.user = toCell.user;
          toCell.user = temp;
        });
      }
    }, () => {
      this.animX = toCell.x - this.x;
      this.animY = toCell.y - this.y;
    });
  }
}

1.2 Connection Class - Piece Relationships

class Connection {
  startName: string;
  endName: string;
  startX: number;
  startY: number;
  endX: number;
  endY: number;

  constructor(start: Cell, end: Cell) {
    this.startName = start.name;
    this.endName = end.name;
    this.startX = start.x;
    this.startY = start.y;
    this.endX = end.x;
    this.endY = end.y;
  }
}

2. Game Implementation Logic

2.1 Main Game Component

@Entry
@Component
struct TwoSonChessGame {
  @State isAnimationRunning: boolean = false;
  @State cells: Cell[] = [];
  @State connections: Connection[] = [];
  @State currentPlayer: number = 1;

  // Initialize game board
  aboutToAppear(): void {
    const cellA = new Cell("A", 180, 180, 2);
    const cellB = new Cell("B", 540, 180, 1);
    const cellC = new Cell("C", 360, 360, 0);
    const cellD = new Cell("D", 180, 540, 1);
    const cellE = new Cell("E", 540, 540, 2);
    this.cells.push(cellA, cellB, cellC, cellD, cellE);

    this.connections.push(
      new Connection(cellA, cellB),
      new Connection(cellA, cellC),
      new Connection(cellA, cellD),
      new Connection(cellB, cellC),
      new Connection(cellC, cellD),
      new Connection(cellC, cellE),
      new Connection(cellD, cellE)
    );
  }

  // Game control methods
  resetGame() {
    this.currentPlayer = 1;
    this.cells[0].user = 2;
    this.cells[1].user = 1;
    this.cells[2].user = 0;
    this.cells[3].user = 1;
    this.cells[4].user = 2;
  }

  // Move validation and execution
  move(cell: Cell) {
    if (this.isCellValid(cell)) {
      const targetIndex = this.checkValidMove(cell);
      if (targetIndex !== -1) {
        this.isAnimationRunning = true;
        cell.moveAnimation(300, this.cells[targetIndex], () => {
          this.isAnimationRunning = false;
          this.moveCompleted();
        });
      }
    }
  }

  // Post-move handling
  moveCompleted() {
    this.currentPlayer = this.currentPlayer === 1 ? 2 : 1;

    if (this.isGameOver()) {
      const winner = this.currentPlayer === 1 ? 'White Wins' : 'Black Wins';
      promptAction.showDialog({
        title: 'Game Over',
        message: winner,
        buttons: [{ text: 'Restart', color: '#ffa500' }]
      }).then(() => this.resetGame());
    } else if (this.currentPlayer === 2) {
      this.aiMove();
    }
  }

  // AI implementation
  aiMove() {
    const whiteCells = this.cells.filter(cell => 
      cell.user === 2 && this.checkValidMove(cell) !== -1);

    if (whiteCells.length === 1) {
      this.move(whiteCells[0]);
    } else if (whiteCells.length === 2) {
      const moveIndex = this.chooseBestMove(whiteCells);
      this.move(whiteCells[moveIndex]);
    }
  }
}

3. UI Implementation

build() {
  Column({ space: 10 }) {
    Stack() {
      // Draw connection lines
      ForEach(this.connections, (conn: Connection) => {
        Line()
          .width(5).height(5)
          .startPoint([`${conn.startX}lpx`, `${conn.startY}lpx`])
          .endPoint([`${conn.endX}lpx`, `${conn.endY}lpx`])
          .stroke(Color.Black)
          .fill(Color.Green);
      });

      // Render game pieces
      ForEach(this.cells, (cell: Cell) => {
        Text()
          .width(`${cell.width}lpx`)
          .height(`${cell.height}lpx`)
          .margin({ 
            left: `${cell.getCenterX()}lpx`, 
            top: `${cell.getCenterY()}lpx` 
          })
          .translate({ x: `${cell.animX}lpx`, y: `${cell.animY}lpx` })
          .backgroundColor(cell.user === 0 ? Color.Transparent : 
            (cell.user === 1 ? Color.Black : Color.White))
          .borderRadius('50%')
          .onClick(() => {
            if (!this.isAnimationRunning) this.move(cell);
          });
      });
    }
    .width('720lpx').height('720lpx')
    .backgroundColor(Color.Orange);

    // Restart button
    Button('Restart').onClick(() => {
      if (!this.isAnimationRunning) this.resetGame();
    });
  }
}

Key Features Demonstrated:

  1. Reactive UI Updates: Leveraging @ObservedV2 and @trace decorators for efficient state management
  2. Animation System: Smooth piece movement using animateToImmediately
  3. Game Logic:
    • Turn-based gameplay
    • Move validation
    • Win condition checking
    • Basic AI implementation
  4. Component Composition: Effective use of Stack, Column, and ForEach for UI organization
  5. User Interaction: Click handling with animation state management

This implementation demonstrates fundamental HarmonyOS development patterns while maintaining clean architecture and responsive gameplay experience.