HarmonyOS NEXT Development Case: Encircle the Neuro Cat

This article demonstrates how to implement a "Neuro Cat" game using HarmonyOS NEXT and ArkUI. The goal is to trap the cat within a hexagonal grid by placing walls strategically. Below is the code implementation with detailed explanations and translated annotations. Core Code Implementation 1. Import Dependencies import { promptAction } from '@kit.ArkUI'; // Import prompt dialog component 2. Define the Cell Class @ObservedV2 // Observer decorator to monitor state changes class Cell { @Trace value: number = 0; // Cell value: 0 (empty), 1 (wall) x: number = 0; // X-coordinate of the cell y: number = 0; // Y-coordinate of the cell leftNeighborIndex: number | undefined = undefined; // Left neighbor index rightNeighborIndex: number | undefined = undefined; // Right neighbor index leftTopNeighborIndex: number | undefined = undefined; // Left-top neighbor index rightTopNeighborIndex: number | undefined = undefined; // Right-top neighbor index leftBottomNeighborIndex: number | undefined = undefined; // Left-bottom neighbor index rightBottomNeighborIndex: number | undefined = undefined; // Right-bottom neighbor index constructor(value: number, x: number, y: number) { this.value = value; // Initialize cell value this.x = x; // Initialize X-coordinate this.y = y; // Initialize Y-coordinate } } 3. Main Component Implementation @Entry // Entry decorator for the root component @Component // Component decorator struct Index { @State gridCells: Cell[] = []; // Array storing all cells cellWidth: number = 70; // Cell width borderPieceWidth: number = -10; // Border piece width pieceSize: number = 65; // Piece size @State catPositionIndex: number = 40; // Cat's current position index // Initialize the game board aboutToAppear(): void { this.initializeBoard(); this.startGame(); } // Find movable neighbors for a cell findNeighbors(cell: Cell): Cell[] { let neighbors: Cell[] = []; if (cell.leftNeighborIndex !== undefined && this.gridCells[cell.leftNeighborIndex].value === 0) { neighbors.push(this.gridCells[cell.leftNeighborIndex]); // Left neighbor } if (cell.rightNeighborIndex !== undefined && this.gridCells[cell.rightNeighborIndex].value === 0) { neighbors.push(this.gridCells[cell.rightNeighborIndex]); // Right neighbor } if (cell.leftTopNeighborIndex !== undefined && this.gridCells[cell.leftTopNeighborIndex].value === 0) { neighbors.push(this.gridCells[cell.leftTopNeighborIndex]); // Left-top neighbor } if (cell.rightTopNeighborIndex !== undefined && this.gridCells[cell.rightTopNeighborIndex].value === 0) { neighbors.push(this.gridCells[cell.rightTopNeighborIndex]); // Right-top neighbor } if (cell.leftBottomNeighborIndex !== undefined && this.gridCells[cell.leftBottomNeighborIndex].value === 0) { neighbors.push(this.gridCells[cell.leftBottomNeighborIndex]); // Left-bottom neighbor } if (cell.rightBottomNeighborIndex !== undefined && this.gridCells[cell.rightBottomNeighborIndex].value === 0) { neighbors.push(this.gridCells[cell.rightBottomNeighborIndex]); // Right-bottom neighbor } return neighbors; } // Initialize the 9x9 hexagonal grid initializeBoard() { this.gridCells = []; for (let rowIndex = 0; rowIndex this.startGame()); } else { const nextMove = this.selectNextMove(emptyNeighbors); this.catPositionIndex = nextMove.x * 9 + nextMove.y; if (nextMove.x === 0 || nextMove.x === 8 || nextMove.y === 0 || nextMove.y === 8) { promptAction.showDialog({ title: "'You Lose!'," buttons: [{ text: 'Restart', color: '#ffa500' }] }).then(() => this.startGame()); } } } // Select the next move using a heuristic approach selectNextMove(emptyNeighbors: Cell[]): Cell { let closestToEdge: Cell | null = null; let minDistanceToEdge = Number.MAX_VALUE; for (const neighbor of emptyNeighbors) { const distance = Math.min(neighbor.x, 8 - neighbor.x, neighbor.y, 8 - neighbor.y); if (distance { Stack() { Text().width(`${this.pieceSize}lpx`).height(`${this.pieceSize}lpx`) .backgroundColor(item.value === 0 ? "#b4b4b4" : "#ff8d5a") .borderRadius('50%') } .margin({ left: `${(index + 9) % 18 === 0 ? this.cellWidth / 2 : 0}lpx` }) }) } // Cat and interactive elements Flex({ wrap: FlexWrap.Wrap }) { ForEach(this.gridCells, (item: Cell, index: number) => { Stack() { Text('Cat') .width(`${this.pieceSize}lpx`) .fontColor(Color.White) .visibility(this.catPositionIndex === index ? Visibility.Visible : Visibility.None) } .onClick(() => { if (item.value === 0 && index !== this.catPositionIndex)

May 11, 2025 - 02:12
 0
HarmonyOS NEXT Development Case: Encircle the Neuro Cat

Image description

This article demonstrates how to implement a "Neuro Cat" game using HarmonyOS NEXT and ArkUI. The goal is to trap the cat within a hexagonal grid by placing walls strategically. Below is the code implementation with detailed explanations and translated annotations.

Core Code Implementation

1. Import Dependencies

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

2. Define the Cell Class

@ObservedV2 // Observer decorator to monitor state changes
class Cell {
  @Trace value: number = 0; // Cell value: 0 (empty), 1 (wall)
  x: number = 0; // X-coordinate of the cell
  y: number = 0; // Y-coordinate of the cell
  leftNeighborIndex: number | undefined = undefined; // Left neighbor index
  rightNeighborIndex: number | undefined = undefined; // Right neighbor index
  leftTopNeighborIndex: number | undefined = undefined; // Left-top neighbor index
  rightTopNeighborIndex: number | undefined = undefined; // Right-top neighbor index
  leftBottomNeighborIndex: number | undefined = undefined; // Left-bottom neighbor index
  rightBottomNeighborIndex: number | undefined = undefined; // Right-bottom neighbor index

  constructor(value: number, x: number, y: number) {
    this.value = value; // Initialize cell value
    this.x = x; // Initialize X-coordinate
    this.y = y; // Initialize Y-coordinate
  }
}

3. Main Component Implementation

@Entry // Entry decorator for the root component
@Component // Component decorator
struct Index {
  @State gridCells: Cell[] = []; // Array storing all cells
  cellWidth: number = 70; // Cell width
  borderPieceWidth: number = -10; // Border piece width
  pieceSize: number = 65; // Piece size
  @State catPositionIndex: number = 40; // Cat's current position index

  // Initialize the game board
  aboutToAppear(): void {
    this.initializeBoard();
    this.startGame();
  }

  // Find movable neighbors for a cell
  findNeighbors(cell: Cell): Cell[] {
    let neighbors: Cell[] = [];
    if (cell.leftNeighborIndex !== undefined && this.gridCells[cell.leftNeighborIndex].value === 0) {
      neighbors.push(this.gridCells[cell.leftNeighborIndex]); // Left neighbor
    }
    if (cell.rightNeighborIndex !== undefined && this.gridCells[cell.rightNeighborIndex].value === 0) {
      neighbors.push(this.gridCells[cell.rightNeighborIndex]); // Right neighbor
    }
    if (cell.leftTopNeighborIndex !== undefined && this.gridCells[cell.leftTopNeighborIndex].value === 0) {
      neighbors.push(this.gridCells[cell.leftTopNeighborIndex]); // Left-top neighbor
    }
    if (cell.rightTopNeighborIndex !== undefined && this.gridCells[cell.rightTopNeighborIndex].value === 0) {
      neighbors.push(this.gridCells[cell.rightTopNeighborIndex]); // Right-top neighbor
    }
    if (cell.leftBottomNeighborIndex !== undefined && this.gridCells[cell.leftBottomNeighborIndex].value === 0) {
      neighbors.push(this.gridCells[cell.leftBottomNeighborIndex]); // Left-bottom neighbor
    }
    if (cell.rightBottomNeighborIndex !== undefined && this.gridCells[cell.rightBottomNeighborIndex].value === 0) {
      neighbors.push(this.gridCells[cell.rightBottomNeighborIndex]); // Right-bottom neighbor
    }
    return neighbors;
  }

  // Initialize the 9x9 hexagonal grid
  initializeBoard() {
    this.gridCells = [];
    for (let rowIndex = 0; rowIndex < 9; rowIndex++) {
      for (let columnIndex = 0; columnIndex < 9; columnIndex++) {
        this.gridCells.push(new Cell(0, rowIndex, columnIndex));
      }
    }
    // Configure neighbor indices based on hexagonal grid logic
    for (let rowIndex = 0; rowIndex < 9; rowIndex++) {
      for (let columnIndex = 0; columnIndex < 9; columnIndex++) {
        const cellIndex = rowIndex * 9 + columnIndex;
        const cell = this.gridCells[cellIndex];
        // Logic for setting neighbor indices...
        // (Omitted for brevity; see original code for details)
      }
    }
  }

  // Start or reset the game
  startGame() {
    let availableIndices: number[] = [];
    for (let i = 0; i < 81; i++) {
      this.gridCells[i].value = 0;
      if (i === 39 || i === 40 || i === 41) continue; // Exclude center cells
      availableIndices.push(i);
    }
    // Generate random walls
    for (let i = 0; i < 8; i++) {
      const randomIndex = Math.floor(Math.random() * availableIndices.length);
      this.gridCells[availableIndices[randomIndex]].value = 1;
      availableIndices.splice(randomIndex, 1);
    }
    this.catPositionIndex = 40; // Reset cat position
  }

  // Move the cat based on AI logic
  moveCat(): void {
    const neighbors = this.findNeighbors(this.gridCells[this.catPositionIndex]);
    const emptyNeighbors = neighbors.filter(neighbor => neighbor.value === 0);
    if (emptyNeighbors.length === 0) {
      promptAction.showDialog({
        title: "'You Win!',"
        buttons: [{ text: 'Restart', color: '#ffa500' }]
      }).then(() => this.startGame());
    } else {
      const nextMove = this.selectNextMove(emptyNeighbors);
      this.catPositionIndex = nextMove.x * 9 + nextMove.y;
      if (nextMove.x === 0 || nextMove.x === 8 || nextMove.y === 0 || nextMove.y === 8) {
        promptAction.showDialog({
          title: "'You Lose!',"
          buttons: [{ text: 'Restart', color: '#ffa500' }]
        }).then(() => this.startGame());
      }
    }
  }

  // Select the next move using a heuristic approach
  selectNextMove(emptyNeighbors: Cell[]): Cell {
    let closestToEdge: Cell | null = null;
    let minDistanceToEdge = Number.MAX_VALUE;
    for (const neighbor of emptyNeighbors) {
      const distance = Math.min(neighbor.x, 8 - neighbor.x, neighbor.y, 8 - neighbor.y);
      if (distance < minDistanceToEdge) {
        minDistanceToEdge = distance;
        closestToEdge = neighbor;
      }
    }
    return closestToEdge || emptyNeighbors[0];
  }

  // Build UI components
  build() {
    Column({ space: 20 }) {
      Stack() {
        // Background grid
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.gridCells, (item: Cell, index: number) => {
            Stack() {
              Text().width(`${this.pieceSize}lpx`).height(`${this.pieceSize}lpx`)
                .backgroundColor(item.value === 0 ? "#b4b4b4" : "#ff8d5a")
                .borderRadius('50%')
            }
            .margin({ left: `${(index + 9) % 18 === 0 ? this.cellWidth / 2 : 0}lpx` })
          })
        }

        // Cat and interactive elements
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.gridCells, (item: Cell, index: number) => {
            Stack() {
              Text('Cat')
                .width(`${this.pieceSize}lpx`)
                .fontColor(Color.White)
                .visibility(this.catPositionIndex === index ? Visibility.Visible : Visibility.None)
            }
            .onClick(() => {
              if (item.value === 0 && index !== this.catPositionIndex) {
                item.value = 1; // Place a wall
                this.moveCat(); // Trigger cat movement
              }
            })
          })
        }
      }

      // Restart button
      Button('Restart').onClick(() => this.startGame())
    }
    .backgroundColor("#666666")
  }
}

Key Features

  1. Hexagonal Grid Logic: The grid uses a 9x9 hexagonal layout, with neighbor indices calculated based on row parity.
  2. Cat Movement AI: The cat moves toward the edge using a Manhattan distance heuristic to escape.
  3. State Management: Leverages @ObservedV2 and @State decorators for reactive UI updates.
  4. User Interaction: Players click empty cells to place walls, triggering the cat's movement.

This implementation showcases HarmonyOS NEXT's declarative UI capabilities and reactive programming model. Developers can extend this logic to create more complex games or interactive applications.