DEV Community

HarmonyOS
HarmonyOS

Posted on

🎮Let’s Build XoX with ArkTS🎮

Read the original article:🎮Let’s Build XoX with ArkTS🎮

Hey there, fellow developers! In this article, we’re going to build a simple Tic Tac Toe (XoX) game using ArkTS, Huawei’s modern TypeScript-based declarative UI framework.

Our goal is to learn the fundamentals of ArkTS while creating something fun and visual.

🧩 Step 1: The Model

Each box on our 3x3 grid is represented by a simple model class:

@Observed
export class Box {
  value: string

  constructor(value: string) {
    this.value = value;
  }
}
Enter fullscreen mode Exit fullscreen mode

🧠 Step 2: Utility Functions

Let’s define a utility class to handle basic helper methods:

import { Box } from "./model/Box";

export class Utils {

  //initializes 9 empty boxes.
  static generateBoxes(): Box[] {
    const list: Box[] = [];
    for (let index = 0; index < 9; index++) {
      list.push(new Box(''))
    }
    return list;
  }

  //assigns a color based on whether it’s 'X' or 'O'
  static generateColor(value: string): ResourceColor {
    if (value == 'X') {
      return Color.Blue
    } else {
      return Color.Red
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

🧱 Step 3: Creating the Game Component

Now we build the core logic of the game inside a GameBox component.

import { Box } from "../model/Box";
import { Utils } from "../Utils";

@Component
export struct GameBox{
  @State boxes: Box[] = Utils.generateBoxes()
  //with each change in pen's value checkWin method is being called
  @State @Watch('checkWin') pen: string = 'X';
  @State winner: string = '';
  @State isShow: boolean = false;

  // in any of win pattern is works, winner is being assigned and 
  // the builder showResult is called
  checkWin(){
    const winPatterns = [
      // rows
      [0, 1, 2], [3, 4, 5], [6, 7, 8],
      // columns
      [0, 3, 6], [1, 4, 7], [2, 5, 8],
      // cross
      [0, 4, 8], [2, 4, 6]
    ];

    for (let i = 0; i < winPatterns.length; i++) {
      const a = winPatterns[i][0];
      const b = winPatterns[i][1];
      const c = winPatterns[i][2];

      if (
        this.boxes[a].value !== '' &&
          this.boxes[a].value === this.boxes[b].value &&
          this.boxes[a].value === this.boxes[c].value
      ) {

        this.winner = this.boxes[a].value;
        this.isShow = true;
        return;
      }
    }

    let isDraw = true;
    for (let i = 0; i < this.boxes.length; i++) {
      if (this.boxes[i].value === '') {
        isDraw = false;
        break;
      }
    }

  // if there is no winner, this logic works and refresh the game
    if (isDraw) {
      this.refresh()
    }
  }


// pen starts as a X and boxes are being generated from stratch winner is no one 
  refresh(){
    this.pen = 'X';
    this.boxes = Utils.generateBoxes()
    this.winner = '';
    this.isShow = false;
  }

//shows the winner with popup 
  @Builder
  ShowResult(){
    Column(){
      Text(this.winner + ' is winner!').fontSize(24).fontWeight(FontWeight.Bold).padding(8)
      Button('Restart')
        .backgroundColor(Color.White)
        .fontColor(Utils.generateColor(this.winner))
        .borderColor(Utils.generateColor(this.winner))
        .borderWidth(1)
        .fontSize(24)
        .onClick(() => {
        this.refresh()
      })
    }
  }

  build() {
    RelativeContainer() {
      Column() {
        GridRow({ columns: 3 }) {
          ForEach(this.boxes, (item:Box, index: number) => {
            GridCol() {
              Row() {
                Text(item.value)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(Utils.generateColor(item.value))
                  .fontSize(28)
              }.onClick(() =>{
                if (!item.value && !this.winner) {
                  this.boxes[index] = new Box(this.pen)
                  this.pen = this.pen == 'X' ? 'O' : 'X';
                }
              })
              .height('33%').width('33%').justifyContent(FlexAlign.Center)
            }.border( { 'color': Color.Gray, 'width': 1 })
          }, (item: Box, index: number) => `${index}${JSON.stringify(item)}`)
        }
        .width('100%').height('100%')
      }
       //call popup with .bindhSheet
      .bindSheet(this.isShow, this.ShowResult(), {height: '50%', onDisappear: () => this.refresh(), showClose:false})
      .justifyContent(FlexAlign.Center)
      .border( { 'color': Color.Gray, 'width': 2 })
      .width('70%')
      .height('70%')
      .margin('15%')
    }
    .height('100%')
    .width('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

🚀 Step 4: Calling our main component in Index.ets

import { GameBox } from '../components/GameBox'

@Entry
@Component
struct Index {

  build() {
    Column(){
      GameBox()
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The final result is something like this:

✅We’ve built a fully functional Tic Tac Toe game using ArkTS, all in under 150 lines of code!

Through this small project, you learned how to:

  • Work with @State and @Watch for reactivity
  • Use @Observed to track model changes
  • Create layouts with GridRow, Column, and RelativeContainer
  • Show modals with bindSheet
  • Write clean reusable code using utilities

Thank you for reading and let me know in the comments section if you have any questions. Happy coding!

References

developer.huawei.com

Written by Hatice Akyel

Top comments (0)