DEV Community

Cover image for HarmonyOS NEXT Development Case: Tic-Tac-Toe Game Implementation
zhongcx
zhongcx

Posted on

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

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

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

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

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.

Top comments (0)