Building a Tic Tac Toe Game in React Native
Introduction
In this tutorial, we'll build a simple Tic Tac Toe game using React Native. This project will help you understand the basics of React Native, including components, state management, and event handling.
Prerequisites
Basic knowledge of JavaScript and React.
Node.js and npm installed on your machine.
React Native environment setup. You can follow the official React Native guide for setting up your environment.
Step 1: Setting Up the Project
First, create a new React Native project using the following command:
npx react-native init Tic-Tac-Toe
Navigate into the project directory:
cd Tic-Tac-Toe
Step 2: react-native-vector-icons
Installation
Install the package via npm:
npm install --save react-native-vector-icons
Depending on the platform you're targeting (iOS/Android/Windows), follow the appropriate setup instructions.
If you're planning to useFontAwesome 5or6 icons, refer to these guides:FontAwesome 5|FontAwesome 6
iOS Setup
To use the bundled icons on iOS, follow these steps:
Navigate to
node_module
s/react-native-vector-icons
and drag theFonts
folder (or select specific fonts) into your Xcode project.Make sure your app is checked under "Add to targets," and if adding the whole folder, check "Creategroups."
Not familiar with Xcode? Check out thisarticle.
Edit
Info.pl
ist
and add a property called Fonts providedby application (or UIAppFonts if Xcode autocomplete is not working):List of all available fonts to copy & paste in Info.plist
<key>UIAppFonts</key> <array> <string>AntDesign.ttf</string> <string>Entypo.ttf</string> <string>EvilIcons.ttf</string> <string>Feather.ttf</string> <string>FontAwesome.ttf</string> <string>FontAwesome5_Brands.ttf</string> <string>FontAwesome5_Regular.ttf</string> <string>FontAwesome5_Solid.ttf</string> <string>FontAwesome6_Brands.ttf</string> <string>FontAwesome6_Regular.ttf</string> <string>FontAwesome6_Solid.ttf</string> <string>Foundation.ttf</string> <string>Ionicons.ttf</string> <string>MaterialIcons.ttf</string> <string>MaterialCommunityIcons.ttf</string> <string>SimpleLineIcons.ttf</string> <string>Octicons.ttf</string> <string>Zocial.ttf</string> <string>Fontisto.ttf</string> </array>Above step might look something Like this:
n Xcode, select your project in the navigator, choose your app's target, go to the Build Phases tab, and under Copy Bundle Resources, add the copied fonts.
When using auto linking, it will automatically add all fonts to the Build Phases, Copy Pods Resources. Which will end up in your bundle. To avoid that, create a
react-native.config.js
file at the root of your react-native project with:module.exports = { dependencies: { 'react-native-vector-icons': { platforms: { ios: null, }, }, }, };
Note: Recompile your project after adding new fonts.
Note 2: If you're getting problems withduplicate outputs file
for fonts on ios build, try running cd ios && pod install
after the react-native.config.js
configuration.
Android Setup
Option: With Gradle (recommended)
To make font management smoother on Android, use this method:
Edit
android/app/build.gradle
(NOTandroid/build.gradle
) and add:apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle")
To customize the fonts being copied, use:
project.ext.vectoricons = [ iconFontNames: [ 'MaterialIcons.ttf', 'EvilIcons.ttf' ] // Specify font files ] apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle")
Option: Manual Integration
To manually integrate the library, follow these steps:
- Copy the contents from the
Fonts
folder and paste them intoandroid/app/src/main/assets/fonts
(ensure the folder name is lowercase, i.e.,fonts
).
Integrating Library forgetImageSource
Support
The following steps are optional and are only necessary if you intend to utilize the Icon.getImageSource
function.
Edit the
android/settings.gradle
file as shown below:rootProject.name = 'MyApp' include ':app' + include ':react-native-vector-icons' + project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
Edit the
android/app/build.gradle
(located in the app folder) as shown below:apply plugin: 'com.android.application' android { ... } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" // From node_modules + implementation project(':react-native-vector-icons') }
Edit your
MainApplication.java
(located deep withinandroid/app/src/main/java/...
) as shown below (note that there aretwo
places to edit):package com.myapp; + import com.oblador.vectoricons.VectorIconsPackage; .... @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage() + , new VectorIconsPackage() ); }
Please note that this optional step is necessary only if your react-native app doesn't support auto-linking; otherwise, you can skip this.
Step 3: Install @types/react-navigate-vector-Icon:
Installation
npm install --save @types/react-n
ative-v
ector-icons
Summary
This package contains type definitions for react-native-vector-icons (github.com/oblador/react-native-vector-icons).
Step 4 : Creating the Game Board
Create a new component for the game board. Create a file named App.js
in the src directory
Inside src directory create a Component folder and make Icon.tsx
App.tsx code:
import React, { useState } from 'react';
import {
FlatList,
Pressable,
SafeAreaView,
StatusBar,
StyleSheet,
Text,
View,
} from 'react-native';
import Snackbar from 'react-native-snackbar';
import Icons from './components/Icons';
function App(): JSX.Element {
const [isCross, setIsCross] = useState<boolean>(false);
const [gameWinner, setGameWinner] = useState<string>('');
const [gameState, setGameState] = useState<string[]>(new Array(9).fill('empty'));
const reloadGame = () => {
setIsCross(false);
setGameWinner('');
setGameState(new Array(9).fill('empty'));
};
const checkIsWinner = (state: string[]) => {
const winningPatterns = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], // rows
[0, 3, 6], [1, 4, 7], [2, 5, 8], // columns
[0, 4, 8], [2, 4, 6] // diagonals
];
for (let pattern of winningPatterns) {
const [a, b, c] = pattern;
if (state[a] !== 'empty' && state[a] === state[b] && state[a] === state[c]) {
setGameWinner(`${state[a]} won the game! 🥳`);
return true;
}
}
if (!state.includes('empty')) {
setGameWinner('Draw game... ⌛️');
return true;
}
return false;
};
const onChangeItem = (itemNumber: number) => {
if (gameWinner) {
Snackbar.show({
text: gameWinner,
backgroundColor: '#FFFFFF',
textColor: "#FFFFFF"
});
return;
}
if (gameState[itemNumber] === 'empty') {
const newGameState = [...gameState];
newGameState[itemNumber] = isCross ? 'cross' : 'circle';
setGameState(newGameState);
if (!checkIsWinner(newGameState)) {
setIsCross(!isCross);
}
} else {
Snackbar.show({
text: "Position is already filled",
backgroundColor: "red",
textColor: "#FFF"
});
}
};
return (
<SafeAreaView style={styles.container}>
<StatusBar />
{gameWinner ? (
<View style={[styles.playerInfo, styles.winnerInfo]}>
<Text style={styles.winnerTxt}>{gameWinner}</Text>
</View>
) : (
<View style={[styles.playerInfo, isCross ? styles.playerX : styles.playerO]}>
<Text style={styles.gameTurnTxt}>
Player {isCross ? 'X' : 'O'}'s Turn
</Text>
</View>
)}
<FlatList
numColumns={3}
data={gameState}
style={styles.grid}
renderItem={({ item, index }) => (
<Pressable
key={index}
style={styles.card}
onPress={() => onChangeItem(index)}
>
<Icons name={item} />
</Pressable>
)}
keyExtractor={(item, index) => index.toString()}
/>
<Pressable style={styles.gameBtn} onPress={reloadGame}>
<Text style={styles.gameBtnText}>
{gameWinner ? 'Start new game' : 'Reload the game'}
</Text>
</Pressable>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingHorizontal: 16,
},
playerInfo: {
height: 56,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 4,
paddingVertical: 8,
marginVertical: 12,
marginHorizontal: 14,
shadowOffset: { width: 1, height: 1 },
shadowColor: '#333',
shadowOpacity: 0.2,
shadowRadius: 1.5,
},
gameTurnTxt: {
fontSize: 20,
color: '#FFFFFF',
fontWeight: '600',
},
playerX: {
backgroundColor: '#38CC77',
},
playerO: {
backgroundColor: '#F7CD2E',
},
grid: {
margin: 12,
},
card: {
height: 100,
width: '33.33%',
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderColor: '#FFF',
},
winnerInfo: {
borderRadius: 8,
backgroundColor: '#38CC77',
shadowOpacity: 0.1,
},
winnerTxt: {
fontSize: 20,
color: '#FFFFFF',
fontWeight: '600',
textTransform: 'capitalize',
},
gameBtn: {
alignItems: 'center',
padding: 10,
borderRadius: 8,
marginHorizontal: 36,
backgroundColor: '#8D3DAF',
marginBottom: 450,
},
gameBtnText: {
fontSize: 18,
color: '#FFFFFF',
fontWeight: '500',
},
});
export default App;
Step 3: Using the Board Component
Now, use the src
component in your main Icon.tsx
file.
Icon.tsx
code:
import React from 'react'
import { PropsWithChildren } from 'react'
import Icon from 'react-native-vector-icons/FontAwesome'
type IconsProps=PropsWithChildren<{
name: string;
}>
const Icons=({name}:IconsProps)=> {
switch (name) {
case 'circle':
return <Icon name="circle-thin" size={38} color="#000000"/>
break;
case 'cross':
return <Icon name="times" size={38} color="#FFFFFF"/>
break;
default:
return <Icon name="pincle" size={38} color="#FFFFFF"/>
}
}
export default Icons
Step 4: Running the App
Run your app on an emulator or a physical device using the following command:
npx react-native run-android
or
npx react-native run-ios
Conclusion
Congratulations! You've built a simple Tic Tac Toe game using React Native. This tutorial covered the basics of React Native components, state management, and event handling. You can further enhance this game by adding more features, such as a score tracker, different difficulty levels, or even a multiplayer mode.
For more advanced React Native tutorials and resources, check out the official React Native documentation.