In this article, we are going to implement a 2048 Game using React Native. The 2048 game is a popular sliding puzzle game that involves combining tiles with the same number to reach the tile with the number 2048. Players can move the tiles in four directions: up, down, left, or right.
Prerequisite
Preview of final output: Let us have a look at how the final output will look like.

Approach
In this app basically we added a 4x4 board and also added all functionalities required in the 2048 game.
Functionalities and logic of the app
- The Game2048 component initializes the game board as a state variable using the useState hook.
- The initializeGame function sets up the initial state of the board with two randomly placed tiles containing either the number 2 or 4.
- The PanGestureHandler is used to detect swipe gestures on the game board.
- The onSwipeEvent function is triggered when a swipe gesture ends (State.END). It determines the direction of the swipe based on the horizontal and vertical translations and calls the handleSwipe function accordingly.
- The handleSwipe function triggers the appropriate movement function (moveUp, moveDown, moveLeft, or moveRight) based on the detected swipe direction.
- The movement functions iterate through the board to move and merge tiles in the specified direction. The merging logic follows the rules of the 2048 game.
- If any movement occurs, a new tile is added to the board using the addNewTile function.
- The isGameOver function checks if there are any possible moves left on the board. If not, it triggers a game over alert with an option to restart the game.
Steps to Create React Native Application
Step 1: Create a react native application by using this command in the command prompt
React-native init Game2048Step 2: After initiating the project, install the react-native-gesture-handler package because Gesture handlers are components that handle touch interactions and gestures, such as tapping, swiping, pinching, and rotating on the screen.
npm i react-native-gesture-handleProject Structure:
The updated dependencies in package.json file will look like:
"dependencies": {
"@expo/vector-icons": "^13.0.0",
"react-native-elements": "0.18.5",
"react-native-gesture-handler": "~2.9.0"
}
Example: Write the below source code into the file.
//App.js
import React from 'react';
import GameLogic from './GameLogic';
import GameStyle from './GameStyle';
const Game2048 = () => {
const { board, initializeGame, handleSwipe } = GameLogic();
return (
<GameStyle board={board}
handleSwipe={handleSwipe}
initializeGame={initializeGame} />
);
};
export default Game2048;
//GameLogic.js
import React, { useState, useEffect } from 'react';
import { Alert } from 'react-native';
const BOARD_SIZE = 4;
const GameLogic = () => {
const [board, setBoard] = useState(Array.from({ length: BOARD_SIZE },
() => Array(BOARD_SIZE).fill(0)));
const initializeGame = () => {
const newBoard = Array.from({ length: BOARD_SIZE },
() => Array(BOARD_SIZE).fill(0));
addNewTile(newBoard);
addNewTile(newBoard);
setBoard(newBoard);
};
const addNewTile = (newBoard) => {
const emptyTiles = [];
for (let i = 0; i < BOARD_SIZE; i++) {
for (let j = 0; j < BOARD_SIZE; j++) {
if (newBoard[i][j] === 0) {
emptyTiles.push({ row: i, col: j });
}
}
}
if (emptyTiles.length > 0) {
const { row, col } = emptyTiles[Math.floor(Math.random() * emptyTiles.length)];
newBoard[row][col] = Math.random() < 0.9 ? 2 : 4;
}
};
const handleSwipe = (direction) => {
const newBoard = [...board];
let moved = false;
switch (direction) {
case 'UP':
moved = moveUp(newBoard);
break;
case 'DOWN':
moved = moveDown(newBoard);
break;
case 'LEFT':
moved = moveLeft(newBoard);
break;
case 'RIGHT':
moved = moveRight(newBoard);
break;
}
if (moved) {
addNewTile(newBoard);
setBoard(newBoard);
}
if (isGameOver(newBoard)) {
Alert.alert('Game Over', 'No more moves left!',
[{ text: 'Restart', onPress: initializeGame }]);
}
};
const moveUp = (newBoard) => {
let moved = false;
for (let col = 0; col < BOARD_SIZE; col++) {
for (let row = 1; row < BOARD_SIZE; row++) {
if (newBoard[row][col] !== 0) {
let currentRow = row;
while (currentRow > 0 && newBoard[currentRow - 1][col] === 0) {
newBoard[currentRow - 1][col] = newBoard[currentRow][col];
newBoard[currentRow][col] = 0;
currentRow--;
moved = true;
}
if (
currentRow > 0 &&
newBoard[currentRow - 1][col] === newBoard[currentRow][col]
) {
newBoard[currentRow - 1][col] *= 2;
newBoard[currentRow][col] = 0;
moved = true;
}
}
}
}
return moved;
};
const moveDown = (newBoard) => {
let moved = false;
for (let col = 0; col < BOARD_SIZE; col++) {
for (let row = BOARD_SIZE - 2; row >= 0; row--) {
if (newBoard[row][col] !== 0) {
let currentRow = row;
while(currentRow < BOARD_SIZE - 1 && newBoard[currentRow + 1][col]===0)
{
newBoard[currentRow + 1][col] = newBoard[currentRow][col];
newBoard[currentRow][col] = 0;
currentRow++;
moved = true;
}
if (
currentRow < BOARD_SIZE - 1 &&
newBoard[currentRow + 1][col] === newBoard[currentRow][col]
) {
newBoard[currentRow + 1][col] *= 2;
newBoard[currentRow][col] = 0;
moved = true;
}
}
}
}
return moved;
};
const moveLeft = (newBoard) => {
let moved = false;
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 1; col < BOARD_SIZE; col++) {
if (newBoard[row][col] !== 0) {
let currentCol = col;
while (currentCol > 0 && newBoard[row][currentCol - 1] === 0) {
newBoard[row][currentCol - 1] = newBoard[row][currentCol];
newBoard[row][currentCol] = 0;
currentCol--;
moved = true;
}
if (
currentCol > 0 &&
newBoard[row][currentCol - 1] === newBoard[row][currentCol]
) {
newBoard[row][currentCol - 1] *= 2;
newBoard[row][currentCol] = 0;
moved = true;
}
}
}
}
return moved;
};
const moveRight = (newBoard) => {
let moved = false;
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = BOARD_SIZE - 2; col >= 0; col--) {
if (newBoard[row][col] !== 0) {
let currentCol = col;
while(currentCol < BOARD_SIZE - 1 && newBoard[row][currentCol + 1]===0)
{
newBoard[row][currentCol + 1] = newBoard[row][currentCol];
newBoard[row][currentCol] = 0;
currentCol++;
moved = true;
}
if (
currentCol < BOARD_SIZE - 1 &&
newBoard[row][currentCol + 1] === newBoard[row][currentCol]
) {
newBoard[row][currentCol + 1] *= 2;
newBoard[row][currentCol] = 0;
moved = true;
}
}
}
}
return moved;
};
const isGameOver = (newBoard) => {
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
if (
newBoard[row][col] === 0 ||
(row > 0 && newBoard[row][col] === newBoard[row - 1][col]) ||
(row < BOARD_SIZE - 1 && newBoard[row][col] === newBoard[row + 1][col]) ||
(col > 0 && newBoard[row][col] === newBoard[row][col - 1]) ||
(col < BOARD_SIZE - 1 && newBoard[row][col] === newBoard[row][col + 1])
) {
return false;
}
}
}
return true;
};
useEffect(() => {
initializeGame();
}, []);
return {
board,
initializeGame,
handleSwipe,
};
};
export default GameLogic;
//GameStyle.js
import React from 'react';
import { View, Text, StyleSheet, Dimensions } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
const BOARD_SIZE = 4;
const GameStyle = ({ board, handleSwipe, initializeGame }) => {
const getTileColor = (value) => {
switch (value) {
case 2:
return '#EEE4DA';
case 4:
return '#EDE0C8';
case 8:
return '#F2B179';
case 16:
return '#F59563';
case 32:
return '#F67C5F';
case 64:
return '#F65E3B';
case 128:
return '#EDCF72';
case 256:
return '#EDCC61';
case 512:
return '#EDC850';
case 1024:
return '#EDC53F';
case 2048:
return '#EDC22E';
default:
return '#BBF99A';
}
};
const onSwipeEvent = (event) => {
if (event.nativeEvent.state === State.END) {
const { translationX, translationY } = event.nativeEvent;
const dx = Math.abs(translationX);
const dy = Math.abs(translationY);
if (dx > dy) {
if (translationX > 0) {
handleSwipe('RIGHT');
} else {
handleSwipe('LEFT');
}
} else {
if (translationY > 0) {
handleSwipe('DOWN');
} else {
handleSwipe('UP');
}
}
}
};
return (
<View style={styles.container}>
<View style={styles.Heading}>
<Text style={{ fontSize: 50, fontWeight: 'bold', color: 'green' }}>
GeekforGeeks
</Text>
<Text style={{
; paddingLeft: 70,
fontSize: 30,
fontWeight: 'bold',
color: 'black'
}}>
2048 Game
</Text>
</View >
<PanGestureHandler onGestureEvent={onSwipeEvent}>
<View style={styles.board}>
{board.map((row, rowIndex) => (
<View key={rowIndex} style={styles.row}>
{row.map((tile, colIndex) => (
<View key={colIndex}
style={[styles.tile, {
backgroundColor: getTileColor(tile)
}]}>
<Text style={styles.tileText}>
{tile !== 0 ? tile : ''}
</Text>
</View>
))}
</View>
))}
</View>
</PanGestureHandler>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
Heading: {
marginBottom: 30,
marginTop: -30,
},
board: {
flexDirection: 'column',
},
row: {
flexDirection: 'row',
},
tile: {
width: Dimensions.get('window').width / BOARD_SIZE - 10,
height: Dimensions.get('window').width / BOARD_SIZE - 10,
margin: 5,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 5,
},
tileText: {
fontSize: 20,
fontWeight: 'bold',
color: '#776E65',
},
});
export default GameStyle;
Step to run the Project:
Step 1: Depending on your operating system, type the following command in terminal
- For android:
React-native run-android- For IOS:
React-native run-iosOutput: