DEV Community

HarmonyOS
HarmonyOS

Posted on

Building 2048 with ArkTS 2

Read the original article:Building 2048 with ArkTS 2

Let’s build with Arkts

Introduction

Welcome back! In the first part, we created the game UI. Now, it is time to build the game logic. If you haven’t seen part 1, check it out here.

Game

  • First, create a viewmodel directory and the “Game.ets” file.

  • We will create an observed class to hold our game state and logic.

Let’s start by putting our board data.

@Observed
export default class Game {
  board: number[] = [2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 0, 0, 0, 0, 0]
}
Enter fullscreen mode Exit fullscreen mode

Modify “Index.ets” to use Game.

@State game: Game = new Game() // replace mock data with Game object

// update ForEach to use the Game
ForEach(this.game.board, (num: number) => {
  GridItem() {
    GameTile({ num: num })
  }
})
Enter fullscreen mode Exit fullscreen mode
Modify Index.ets

Game board with all tiles

As you know, the game starts with two 2's on the board. We should initialize our game board with two 2s in random locations.

  • Since we hold our board data in an array, we can select two random indices to put initial values.
private _getRandomIndex(max: number = 15) {
  return Math.round(Math.random() * max);
}
Enter fullscreen mode Exit fullscreen mode
Select a random index
  • Initialize the game board:
board: number[] = this._init()

private _init() {
  // create an empty array
  const newList = Array<number>(16).fill(0)
  //select two random indexes to put initial 2s
  const indexes: Set<number> = new Set<number>()
  while (indexes.size < 2) {
    indexes.add(this._getRandomIndex())
  }
  indexes.forEach((num) => {
    newList[num] = 2
  })

  return newList;
}
Enter fullscreen mode Exit fullscreen mode
Initialize the game

Our game should start with two 2s in random places.

Initial Game with two 2s

Swipe and merge

We will implement swipe and merge operations for all four directions.

Let’s start with the left swipe:

swipeLeft() {
  // reshape flat list as board
  let board2D: number[][] = [];
  for (let i = 0; i < this.board.length; i += 4) {
    board2D.push(this.board.slice(i, i + 4));
  }

  // swipe and merge each row
  for (let i = 0; i < board2D.length; i++) {
    board2D[i] = this._swipeAndMerge(board2D[i])
  }

  // flatten board as list
  let newBoard = board2D.flat()
  this.board = newBoard;
}
Enter fullscreen mode Exit fullscreen mode
Left swipe operation
  • Step 1: Reshape the board data as a 2D array to make operations on each row.

  • Step 2: Use the _swipeAndMerge function to swipe and merge.

  • Step 3: Flatten the 2d array and update the board.

Since swipe and merge is simply a mathematical operation, we will not dive into details about this operation.

private _isEmpty(line: number[]) {
  return line.every((value) => value == 0)
}

private _swipeAndMerge(line: number[]) {
  // return line if empty
  if (this._isEmpty(line)) {
    return line
  }

  // first clear zeros
  const noZeros = line.filter((value) => value != 0)

  // merge if two near elements are same, if three are same first two should merge
  // 2,2 -> 4
  // 2,2,2 -> 4,2
  const newLine: number[] = []
  for (let i = 0; i < noZeros.length; i++) {
    // if last element, push to new newLine
    if (i == noZeros.length - 1) {
      newLine.push(noZeros[i])
    } else if (noZeros[i] == noZeros[i + 1]) { // check if two elements should merge
      newLine.push(noZeros[i] * 2)
      i++;
    } else { // no merge, push element
      newLine.push(noZeros[i])
    }
  }

  // fill new line with 0's to make its length 4
  if (newLine.length < 4) {
    newLine.push(...new Array(4 - newLine.length).fill(0))
  }

  return newLine
}
Enter fullscreen mode Exit fullscreen mode

We will use the same logic for the right swipe:

The only difference is that we will rotate the 2d array 180 degrees to be able to use the _swipeAndMerge function, then we will rotate it back 180 degrees to get the correct result

private _rotateRight90(matrix: number[][]) {
  return matrix[0].map((_, index) => matrix.map(row => row[index]).reverse())
}

private _rotateLeft90(matrix: number[][]) {
  return matrix[0].map((_, index) => matrix.map(row => row[row.length - 1 - index]));
}

swipeRight() {
  // reshape flat list as board
  let board2D: number[][] = [];
  for (let i = 0; i < this.board.length; i += 4) {
    board2D.push(this.board.slice(i, i + 4));
  }

  // rotate 180 to make it compatible with the merge function
  board2D = this._rotateRight90(this._rotateRight90(board2D))

  // swipe and merge
  for (let i = 0; i < board2D.length; i++) {
    board2D[i] = this._swipeAndMerge(board2D[i])
  }

  // rotate 180 back
  board2D = this._rotateLeft90(this._rotateLeft90(board2D))

  // flatten board as list
  let newBoard = board2D.flat()
  this.board = newBoard;
}
Enter fullscreen mode Exit fullscreen mode
Swipe right operation

We will use the same logic for swipe-up and swipe-down operations. This time we will use a 90-degree rotation.

swipeUp() {
  // reshape flat list as board
  let board2D: number[][] = [];
  for (let i = 0; i < this.board.length; i += 4) {
    board2D.push(this.board.slice(i, i + 4));
  }

  // rotate 90 to make it compatible with the merge function
  board2D = this._rotateLeft90(board2D)

  // swipe and merge
  for (let i = 0; i < board2D.length; i++) {
    board2D[i] = this._swipeAndMerge(board2D[i])
  }

  // rotate 90 back
  board2D = this._rotateRight90(board2D)

  // flatten board as list
  let newBoard = board2D.flat()
  this.board = newBoard;
}

Enter fullscreen mode Exit fullscreen mode
Swipe up operation
  • Time to modify the “Index.ets” to use swipe operations.
.gesture(PanGesture({ distance: 50 }).onActionEnd((event) => {
  const verticalDistance = event.offsetY;
  const horizontalDistance = event.offsetX;

  if (Math.abs(verticalDistance) > Math.abs(horizontalDistance)) { // vertical movement
    if (verticalDistance < 0) { // swipe up
      this.game.swipeUp()
    } else { // swipe down
      this.game.swipeDown()
    }
  } else { // horizontal movement
    if (horizontalDistance < 0) { // swipe left
      this.game.swipeLeft()
    } else { // swipe right
      this.game.swipeRight()
    }
  }
}))
Enter fullscreen mode Exit fullscreen mode
Modify Index.ets

Now you can move tiles in all four directions. The final result should look like this.

Game with swipe and merge operations

Conclusion

Even in the darkest times, one should see the light. We have used the mathematician that lives inside us to create our game logic. We will implement adding new tile functionality and win-lose logic in Part 3. See you soon. :)

Edit: Part 3 is here.

Written by Mr.Karaaslan

Top comments (0)