Read the original article:Building 2048 with ArkTS 3
Introduction
Welcome back! We are one step away from our goal. If you haven't seen them, please refer to parts 1 and 2.
Adding a new tile
With every swipe operation, we will add a new tile, a 2 or a 4.
private _addItem(arr: number[]) {
// get empty indexes
const emptyIndexes = arr.map((value, index) => value == 0 ? index : -1).filter((val) => val >= 0)
// select a random index
const index = emptyIndexes[this._getRandomIndex(emptyIndexes.length - 1)]
const arrCopy = Array.from(arr);
// put 2 or 4 randomly
arrCopy[index] = [2, 4][this._getRandomIndex(1)]
return arrCopy;
}
private _boardChanged(newBoard: number[]) {
return !this.board.every((val, index) => val == newBoard[index])
}
// flatten board as list
let newBoard = board2D.flat()
// add new element if the board changed
if (this._boardChanged(newBoard)) {
newBoard = this._addItem(newBoard)
this.board = newBoard;
}
The result should look like this. After every swipe, your app should add a 2 or a 4 to a random location.
Game Status
So far, we have created a playable game, now we will implement the win-lose logic. The game ends when the player reaches 2048 or when no more moves are possible. Let’s implement the game checker.
export enum GameStatus {
running,
lose,
win
}
status: GameStatus = GameStatus.running // add status parameter
private _checkGame() {
// check if win
if (this.board.some((val) => val == GameNum._2048)) {
this.status = GameStatus.win;
}
// check if the board is full and no move exists
if (this.board.every((val) => val != GameNum._0) && !this._moveExists()) {
this.status = GameStatus.lose
}
// else game can continue
}
And the move checker:
private _moveExists() {
// 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));
}
// check each row and column
for (let row = 0; row < board2D.length; row++) {
for (let col = 0; col < board2D[row].length - 1; col++) {
if (board2D[row][col] == board2D[row][col + 1]) {
return true;
}
// if not last row, compare with next row
if (row < board2D.length - 1) {
if (board2D[row][col] == board2D[row + 1][col]) {
return true
}
}
}
}
// else
return false
}
Now, modify all the swipe functions to check if the game ends.
// add new element if the board changed
if (this._boardChanged(newBoard)) {
newBoard = this._addItem(newBoard)
this.board = newBoard;
this._checkGame(); // add this line
}
Adding the UI
To inform our users when the game status changes, we will use a custom pop-up. We will show the final result and a refresh button to restart the game. Start with the pop-up UI.
Create “WinLosePopup.ets” under the components directory. You can customize the UI as you wish.
@Component
export default struct WinLosePopup {
@Link showPopup: boolean;
@Require win: boolean;
build() {
Column() {
Text(`YOU ${this.win ? 'WON' : 'LOST'}`)
.fontColor(this.win ? Color.Green : Color.Red)
Blank().size({ height: 8 })
Button() {
SymbolGlyph($r('sys.symbol.arrow_clockwise'))
.fontColor([Color.White])
}
.size({
width: '100%',
height: 32
})
.onClick(() => {
this.showPopup = false // close the popup
})
}
.width('70%')
.backgroundColor(Color.White)
.padding(16)
}
}
We will watch the Game and show the popup when the game status changes to win or lose. Modify “Index.ets”.
@State pop: boolean = false;
@Builder
winLosePopupBuilder() {
WinLosePopup({
showPopup: this.pop,
win: this.game.status == GameStatus.win
})
}
RelativeContainer() {
// ...
}
.bindPopup(this.pop, {
builder: this.winLosePopupBuilder(),
autoCancel: false,
})
// add @Watch
@State @Watch('winLose') game: Game = new Game()
// show pop-up if the game has ended
winLose() {
this.pop = this.game.status != GameStatus.running
}
Now we have the automatic pop-up. It should open when the game ends.
// add function to change color
bgColor() {
switch (this.game.status) {
case GameStatus.running:
return $r('app.color.color_background')
case GameStatus.lose:
return Color.Red
case GameStatus.win:
return Color.Green
}
}
// add background color
RelativeContainer() {
// ...
}
.backgroundColor(this.bgColor())
Final Touch: restarting the game
We will use .bindPopup’s onStateChange attribute to restart the game after the user closes the pop-up.
refresh() {
this.board = this._init();
this.status = GameStatus.running
}
.bindPopup(this.pop, {
builder: this.winLosePopupBuilder(),
autoCancel: false,
// add this to refresh the game
onStateChange: (e) => {
if (!e.isVisible) {
this.game.refresh()
}
},
})
Conclusion
Congratulations, everyone, for not giving up! It is time to play and enjoy. Keep on building new things and learning. See you all in new adventures. :)
Fortuna Favet Fortibus
References
(https://forums.developer.huawei.com/forumPortal/en/topic/0201189245864780049?fid=0102647487706140266)
Top comments (0)