Read the original article:Building 2048 with ArkTS 2
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]
}
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 })
}
})
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);
}
- 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;
}
Our game should start with two 2s in random places.
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;
}
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
}
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;
}
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;
}
- 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()
}
}
}))
Now you can move tiles in all four directions. The final result should look like this.
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.




Top comments (0)