Open In App

Create a 2048 Game using React-Native

Last Updated : 02 Aug, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

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.

2048
Preview of the app

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 Game2048

Step 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-handle

Project Structure:

struu

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.

JavaScript
//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; 
JavaScript
//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; 
JavaScript
//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-ios

Output:


Next Article

Similar Reads