#react-native dogcatoe tic-tac-toe game app with a cat vs dog theme sounds- Apk con una temática de gato contra perro con sonidos.
Introduction - Introduction
App.tsx
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';;
import Sound from 'react-native-sound';
let globalSound: Sound | null = null; // Variable global para rastrear la instancia de sonido
function App(): React.JSX.Element {
const [isCat, setIsCat] = useState<boolean>(false)
const [gameWinner, setGameWinner] = useState<string>('')
const [gameState, setGameState] = useState(new Array(9).fill('empty',0,9)) // llenar con 'empty' ,desde la posición 0, hasta la posición 9 sin incluirlo
// Cargar archivos de audio
const catSound = new Sound('cat.mp3', Sound.MAIN_BUNDLE, (error) => {
if (error) {
console.log('error al cargar cat.mp3', error);
return;
}
});
const dogSound = new Sound('dog.mp3', Sound.MAIN_BUNDLE, (error) => {
if (error) {
console.log('error al cargar dog.mp3', error);
return;
}
});
const gameSound = new Sound('game.mp3', Sound.MAIN_BUNDLE, (error) => {
if (error) {
console.log('error al cargar dog.mp3', error);
return;
}
});
const reloadGame = () => {
if (globalSound)
globalSound.stop();
setIsCat(false)
setGameWinner('')
setGameState(new Array(9).fill('empty',0,9))
}
const checkIsWinner = () => {
globalSound = gameSound;
globalSound.play();
// checking winner of the game
if (
gameState[0] === gameState[1] &&
gameState[0] === gameState[2] &&
gameState[0] !== 'empty'
) {
setGameWinner(`${gameState[0]} won the game! 🥳`);
if (gameState[0] === 'cat') {
if(globalSound)
globalSound.stop();
globalSound = catSound;
catSound.play();
} else if (gameState[0] === 'dog') {
if(globalSound)
globalSound.stop();
globalSound = dogSound;
dogSound.play();
}
} else if (
gameState[3] !== 'empty' &&
gameState[3] === gameState[4] &&
gameState[4] === gameState[5]
) {
setGameWinner(`${gameState[3]} won the game! 🥳`);
if (gameState[3] === 'cat') {
if(globalSound)
globalSound.stop();
globalSound = catSound;
catSound.play();
} else if (gameState[3] === 'dog') {
if(globalSound)
globalSound.stop();
globalSound = dogSound;
dogSound.play();
}
} else if (
gameState[6] !== 'empty' &&
gameState[6] === gameState[7] &&
gameState[7] === gameState[8]
) {
setGameWinner(`${gameState[6]} won the game! 🥳`);
if (gameState[6] === 'cat') {
if(globalSound)
globalSound.stop();
globalSound = catSound;
catSound.play();
} else if (gameState[6] === 'dog') {
if(globalSound)
globalSound.stop();
globalSound = dogSound;
dogSound.play();
}
} else if (
gameState[0] !== 'empty' &&
gameState[0] === gameState[3] &&
gameState[3] === gameState[6]
) {
setGameWinner(`${gameState[0]} won the game! 🥳`);
if (gameState[0] === 'cat') {
if(globalSound)
globalSound.stop();
globalSound = catSound;
catSound.play();
} else if (gameState[0] === 'dog') {
if(globalSound)
globalSound.stop();
globalSound = dogSound;
dogSound.play();
}
} else if (
gameState[1] !== 'empty' &&
gameState[1] === gameState[4] &&
gameState[4] === gameState[7]
) {
setGameWinner(`${gameState[1]} won the game! 🥳`);
if (gameState[1] === 'cat') {
if(globalSound)
globalSound.stop();
globalSound = catSound;
catSound.play();
} else if (gameState[1] === 'dog') {
if(globalSound)
globalSound.stop();
globalSound = dogSound;
dogSound.play();
}
} else if (
gameState[2] !== 'empty' &&
gameState[2] === gameState[5] &&
gameState[5] === gameState[8]
) {
setGameWinner(`${gameState[2]} won the game! 🥳`);
if (gameState[2] === 'cat') {
if(globalSound)
globalSound.stop();
globalSound = catSound;
catSound.play();
} else if (gameState[2] === 'dog') {
if(globalSound)
globalSound.stop();
globalSound = dogSound;
dogSound.play();
}
} else if (
gameState[0] !== 'empty' &&
gameState[0] === gameState[4] &&
gameState[4] === gameState[8]
) {
setGameWinner(`${gameState[0]} won the game! 🥳`);
if (gameState[0] === 'cat') {
if(globalSound)
globalSound.stop();
globalSound = catSound;
catSound.play();
} else if (gameState[0] === 'dog') {
if(globalSound)
globalSound.stop();
globalSound = dogSound;
dogSound.play();
}
} else if (
gameState[2] !== 'empty' &&
gameState[2] === gameState[4] &&
gameState[4] === gameState[6]
) {
setGameWinner(`${gameState[2]} won the game! 🥳`);
if (gameState[2] === 'cat') {
if(globalSound)
globalSound.stop();
globalSound = catSound;
catSound.play();
} else if (gameState[2] === 'dog') {
if(globalSound)
globalSound.stop();
globalSound = dogSound;
dogSound.play();
}
} else if (!gameState.includes('empty', 0)) { // para comenzar la busqueda desde el indice 0
setGameWinner('Draw game... ⌛️');
}
}
const onChangeItem = (itemNumber: number) => {
if(globalSound){
globalSound.stop();
globalSound = gameSound;
gameSound.play();
}
if(gameWinner){
return Snackbar.show({
text: gameWinner,
backgroundColor: '#000000',
textColor: '#FFFFFF'
})
}
if(gameState[itemNumber]=== 'empty'){
gameState[itemNumber] = isCat ? 'cat' : 'dog'
setIsCat(!isCat)
} else {
Snackbar.show({
text: "Position is already filled",
backgroundColor: "red",
textColor: "#FFF"
})
}
checkIsWinner()
}
return (
<SafeAreaView >
<StatusBar />
{gameWinner ? (
<View style={[styles.playerInfo, styles.winnerInfo]}>
<Text style={styles.winnerTxt}>{gameWinner}</Text>
</View>
) : (
<View
style={[
styles.playerInfo,
isCat ? styles.playerCat : styles.playerDog
]}>
<Text style={styles.gameTurnTxt}>
Player {isCat ? '😺' : '🐶'}'s Turn
</Text>
</View>
)}
{ /* Game Grid */ }
<FlatList
numColumns={3}
data={gameState}
style={styles.grid}
renderItem={({item, index}) => (
<Pressable
key={index}
style={styles.card}
onPress={() => onChangeItem(index)}
>
<Icons name={item} />
</Pressable>
)}
/>
{/* game action */}
<Pressable
style={styles.gameBtn}
onPress={reloadGame}
>
<Text style={styles.gameBtnText}>
{gameWinner ? 'Start new game ' : 'reload the game'}
</Text>
</Pressable>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
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',
},
playerCat: {
backgroundColor: '#EC4849',
},
playerDog: {
backgroundColor: '#26ae60',
},
grid: {
margin: 12,
},
card: {
height: 100,
width: '33.33%',
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderColor: '#333',
},
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',
},
gameBtnText: {
fontSize: 18,
color: '#FFFFFF',
fontWeight: '500',
},
});
export default App;
Este código es para una aplicación de juego de tres en raya con una temática de gato contra perro.
Importa varios componentes de React Native como FlatList, Pressable, Text, etc para construir la IU.
La aplicación rastrea el estado del juego en la variable de estado gameState que se inicializa con todos los valores 'vacíos'.
Cuando se presiona una celda, el método onChangeItem actualiza el gameState estableciendo la celda en 'gato' o 'perro' según el turno que sea.
El método checkIsWinner verifica si hay un ganador en cada turno buscando tres en raya. Si hay un ganador, el estado gameWinner se actualiza.
También hay lógica para reproducir sonidos cuando se presionan las casillas o cuando hay un ganador usando la API Sound.
La IU muestra de quién es el turno, renderiza el tablero usando FlatList y tiene un botón de recarga para reiniciar el juego.
En general, tiene la lógica y los componentes necesarios para crear un juego interactivo de tres en raya con sonidos y retroalimentación visual.
This code is for a tic-tac-toe game app with a cat vs dog theme.
It imports various React Native components like FlatList, Pressable, Text, etc to build the UI.
The app keeps track of the game state in the gameState state variable which is initialized to all 'empty' values.
When a cell is pressed, the onChangeItem method updates the gameState by setting the cell to either 'cat' or 'dog' based on whose turn it is.
The checkIsWinner method checks if there is a winner every turn by checking for three in a rows. If there is a winner, the gameWinner state is updated.
There is also logic to play sounds when tiles are pressed or when there is a winner using the Sound API.
The UI shows whose turn it is, renders the board using a FlatList, and has a reload button to reset the game.
Overall, it has the logic and components to create an interactive tic-tac-toe game with sounds and visual feedback.
Icons.tsx
import React from 'react';
import type { PropsWithChildren } from 'react';
import Icon from 'react-native-vector-icons/FontAwesome5';
type IconsProps = PropsWithChildren<{ name: string }>;
const Icons = ({ name }: IconsProps) => {
switch (name) {
case 'cat':
return <Icon name="cat" size={50} color="#EC4849" />;
case 'dog':
return <Icon name="dog" size={50} color="#26ae60" />;
default:
return <Icon name="pen" size={50} color="#0D0D0D" />;
}
};
export default Icons;
Este código define un componente reutilizable de Icono que puede renderizar diferentes iconos de FontAwesome.
Importa React y el tipo PropsWithChildren de React. También importa el componente Icon desde la librería react-native-vector-icons.
Luego define un tipo llamado IconsProps que extiende PropsWithChildren y tiene una propiedad name que es un string. Esta será la interfaz de props para el componente Icons.
El componente funcional Icons toma props de tipo IconsProps. Desestructura sólo la propiedad name de esos props.
Dentro del componente hay una sentencia switch en la prop name. Dependiendo del nombre del icono pasado, devolverá un componente Icon desde react-native-vector-icons con algunas props predefinidas diferentes.
Finalmente, el componente se exporta como exportación por defecto. Así otros archivos pueden importar y usar el componente reutilizable Icons.
This code defines a reusable Icon component that can render different FontAwesome icons.
It imports React and the PropsWithChildren type from React. It also imports the Icon component from the react-native-vector-icons library.
It then defines a type called IconsProps which extends PropsWithChildren and has a name property that is a string. This will be the props interface for the Icons component.
The Icons functional component takes in props of type IconsProps. It destructures just the name property from those props.
Inside the component there is a switch statement on the name prop. Depending on the icon name passed in, it will return an Icon component from react-native-vector-icons with some different predefined props.
Finally, the component is exported as the default export. So other files can import and use the reusable Icons component.
Librerias - Libraries
Español:
react-native-vector-icons
Provee iconos y soporte de fuentes.
Para instalar:
npm install react-native-vector-icons
react-native-snackbar
Muestra notificaciones snackbar.
Para instalar:
npm install react-native-snackbar
react-native-sound
Reproduce archivos de sonido.
Para instalar:
npm install react-native-sound
El comando para instalar es npm install <nombre-librería>
para las 3 librerías. Esto las instalará localmente y las agregará como dependencias en el archivo package.json.
Adicionalmente, después de instalarlas con NPM, puede ser necesario hacer alguna configuración nativa. Las instrucciones de instalación se proporcionan en la documentación de cada una.
Inglés:
react-native-vector-icons
Provides icons and font support.
To install:
npm install react-native-vector-icons
react-native-snackbar
Displays snackbar notifications.
To install:
npm install react-native-snackbar
react-native-sound
Plays sound files.
To install:
npm install react-native-sound
The command to install is npm install <library-name>
for all 3 libraries. This will install them locally and add them as dependencies in the package.json file.
Additionally some native configuration may be needed after installing these libraries with NPM. The installation instructions are provided in their documentation.
github
youtube
Update App.tsx
import React, { useState, useEffect } from 'react';
import {
FlatList,
Pressable,
SafeAreaView,
StatusBar,
StyleSheet,
Text,
View,
} from 'react-native';
import Snackbar from 'react-native-snackbar';
import Icons from './components/Icons';;
import Sound from 'react-native-sound';
let globalSound: Sound | null = null; // Variable global para rastrear la instancia de sonido
function App(): React.JSX.Element {
const [isCat, setIsCat] = useState<boolean>(false)
const [gameWinner, setGameWinner] = useState<string>('')
const [gameState, setGameState] = useState(new Array(9).fill('empty',0,9)) // llenar con 'empty' ,desde la posición 0, hasta la posición 9 sin incluirlo
const [catSound, setCatSound] = useState<Sound | null>(null);
const [dogSound, setDogSound] = useState<Sound | null>(null);
const [gameSound, setGameSound] = useState<Sound | null>(null);
const [globalSound, setGlobalSound] = useState<Sound | null>(null);
useEffect(() => {
const loadSounds = async () => {
try {
const catSoundInstance = new Sound('cat.mp3', Sound.MAIN_BUNDLE);
const dogSoundInstance = new Sound('dog.mp3', Sound.MAIN_BUNDLE);
const gameSoundInstance = new Sound('game.mp3', Sound.MAIN_BUNDLE);
setCatSound(catSoundInstance);
setDogSound(dogSoundInstance);
setGameSound(gameSoundInstance);
} catch (error) {
console.log('Error al cargar los sonidos', error);
}
};
loadSounds();
}, []);
useEffect(() => {
return () => {
catSound?.release();
dogSound?.release();
gameSound?.release();
};
}, [catSound, dogSound, gameSound]);
const reloadGame = () => {
globalSound?.stop();
setIsCat(false)
setGameWinner('')
setGameState(new Array(9).fill('empty',0,9));
// Iniciar la reproducción de gameSound cuando se reinicia el juego
setGlobalSound(gameSound);
gameSound?.play();
}
const checkIsWinner = () => {
// checking winner of the game
globalSound?.stop();
if (
gameState[0] === gameState[1] &&
gameState[0] === gameState[2] &&
gameState[0] !== 'empty'
) {
setGameWinner(`${gameState[0]} won the game! 🥳`);
if (gameState[0] === 'cat' && catSound) {
setGlobalSound(catSound);
catSound.play();
} else if (gameState[0] === 'dog' && dogSound) {
setGlobalSound(dogSound);
dogSound.play();
}
} else if (
gameState[3] !== 'empty' &&
gameState[3] === gameState[4] &&
gameState[4] === gameState[5]
) {
setGameWinner(`${gameState[3]} won the game! 🥳`);
if (gameState[3] === 'cat' && catSound) {
setGlobalSound(catSound);
catSound.play();
} else if (gameState[3] === 'dog' && dogSound) {
setGlobalSound(dogSound);
dogSound.play();
}
} else if (
gameState[6] !== 'empty' &&
gameState[6] === gameState[7] &&
gameState[7] === gameState[8]
) {
setGameWinner(`${gameState[6]} won the game! 🥳`);
if (gameState[6] === 'cat' && catSound) {
setGlobalSound(catSound);
catSound.play();
} else if (gameState[6] === 'dog' && dogSound) {
setGlobalSound(dogSound);
dogSound.play();
}
} else if (
gameState[0] !== 'empty' &&
gameState[0] === gameState[3] &&
gameState[3] === gameState[6]
) {
setGameWinner(`${gameState[0]} won the game! 🥳`);
if (gameState[0] === 'cat' && catSound) {
setGlobalSound(catSound);
catSound.play();
} else if (gameState[0] === 'dog' && dogSound) {
setGlobalSound(dogSound);
dogSound.play();
}
} else if (
gameState[1] !== 'empty' &&
gameState[1] === gameState[4] &&
gameState[4] === gameState[7]
) {
setGameWinner(`${gameState[1]} won the game! 🥳`);
if (gameState[1] === 'cat' && catSound) {
setGlobalSound(catSound);
catSound.play();
} else if (gameState[1] === 'dog' && dogSound) {
setGlobalSound(dogSound);
dogSound.play();
}
} else if (
gameState[2] !== 'empty' &&
gameState[2] === gameState[5] &&
gameState[5] === gameState[8]
) {
setGameWinner(`${gameState[2]} won the game! 🥳`);
if (gameState[2] === 'cat' && catSound) {
setGlobalSound(catSound);
catSound.play();
} else if (gameState[2] === 'dog' && dogSound) {
setGlobalSound(dogSound);
dogSound.play();
}
} else if (
gameState[0] !== 'empty' &&
gameState[0] === gameState[4] &&
gameState[4] === gameState[8]
) {
setGameWinner(`${gameState[0]} won the game! 🥳`);
if (gameState[0] === 'cat' && catSound) {
setGlobalSound(catSound);
catSound.play();
} else if (gameState[0] === 'dog' && dogSound) {
setGlobalSound(dogSound);
dogSound.play();
}
} else if (
gameState[2] !== 'empty' &&
gameState[2] === gameState[4] &&
gameState[4] === gameState[6]
) {
setGameWinner(`${gameState[2]} won the game! 🥳`);
if (gameState[2] === 'cat' && catSound) {
setGlobalSound(catSound);
catSound.play();
} else if (gameState[2] === 'dog' && dogSound) {
setGlobalSound(dogSound);
dogSound.play();
}
} else if (!gameState.includes('empty', 0)) { // para comenzar la busqueda desde el indice 0
setGameWinner('Draw game... ⌛️');
}
}
const onChangeItem = (itemNumber: number) => {
if(gameWinner){
if ((
gameState[0] === 'cat' || gameState[1] === 'cat' || gameState[2] === 'cat' ||
gameState[3] === 'cat' || gameState[4] === 'cat' || gameState[5] === 'cat' ||
gameState[6] === 'cat' || gameState[7] === 'cat' || gameState[8] === 'cat') && catSound
) {
setGlobalSound(catSound);
catSound.play();
} else if ((
gameState[0] === 'dog' || gameState[1] === 'dog' || gameState[2] === 'dog' ||
gameState[3] === 'dog' || gameState[4] === 'dog' || gameState[5] === 'dog' ||
gameState[6] === 'dog' || gameState[7] === 'dog' || gameState[8] === 'dog') && dogSound
) {
setGlobalSound(dogSound);
dogSound.play();
}
return Snackbar.show({
text: gameWinner,
backgroundColor: '#000000',
textColor: '#FFFFFF'
})
}
if(gameState[itemNumber]=== 'empty'){
gameState[itemNumber] = isCat ? 'cat' : 'dog'
setIsCat(!isCat)
} else {
Snackbar.show({
text: "Position is already filled",
backgroundColor: "red",
textColor: "#FFF"
})
}
checkIsWinner()
}
return (
<SafeAreaView >
<StatusBar />
{gameWinner ? (
<View style={[styles.playerInfo, styles.winnerInfo]}>
<Text style={styles.winnerTxt}>{gameWinner}</Text>
</View>
) : (
<View
style={[
styles.playerInfo,
isCat ? styles.playerCat : styles.playerDog
]}>
<Text style={styles.gameTurnTxt}>
Player {isCat ? '😺' : '🐶'}'s Turn
</Text>
</View>
)}
{ /* Game Grid */ }
<FlatList
numColumns={3}
data={gameState}
style={styles.grid}
renderItem={({item, index}) => (
<Pressable
key={index}
style={styles.card}
onPress={() => onChangeItem(index)}
>
<Icons name={item} />
</Pressable>
)}
/>
{/* game action */}
<Pressable
style={styles.gameBtn}
onPress={reloadGame}
>
<Text style={styles.gameBtnText}>
{gameWinner ? 'Start new game ' : 'reload the game'}
</Text>
</Pressable>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
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',
},
playerCat: {
backgroundColor: '#EC4849',
},
playerDog: {
backgroundColor: '#26ae60',
},
grid: {
margin: 12,
},
card: {
height: 100,
width: '33.33%',
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderColor: '#333',
},
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',
},
gameBtnText: {
fontSize: 18,
color: '#FFFFFF',
fontWeight: '500',
},
});
export default App;
Declaración de Estados con
useState
: Se definen cuatro estados conuseState
, cada uno representando una instancia de sonido diferente:catSound
para el sonido del gato.dogSound
para el sonido del perro.gameSound
para el sonido de fondo del juego.globalSound
para el sonido que se está reproduciendo actualmente.
Cada uno de estos estados tiene un valor inicial de null
y una función para actualizar ese estado (setCatSound
, setDogSound
, etc.).
State Declaration with
useState
: Four states are declared usinguseState
, each representing a different sound instance:catSound
for the cat sound.dogSound
for the dog sound.gameSound
for the background game sound.globalSound
for the currently playing sound.
Primer useEffect: Este useEffect se ejecuta una vez cuando el componente se monta. Dentro de él, se define una función async llamada
loadSounds
que crea nuevas instancias de sonido y las carga en el estado correspondiente utilizando las funcionessetCatSound
,setDogSound
ysetGameSound
. No se reproduce ningún sonido al cargarlos, lo cual es una práctica recomendada para evitar sorpresas al usuario.- First useEffect: This useEffect runs once when the component mounts. Inside of it, an asynchronous function
loadSounds
is defined that creates new sound instances and loads them into the corresponding state using thesetCatSound
,setDogSound
, andsetGameSound
functions. No sound is played upon loading, which is a recommended practice to avoid surprising the user.
- First useEffect: This useEffect runs once when the component mounts. Inside of it, an asynchronous function
useEffect(() => {
const loadSounds = async () => {
try {
const catSoundInstance = new Sound('cat.mp3', Sound.MAIN_BUNDLE);
const dogSoundInstance = new Sound('dog.mp3', Sound.MAIN_BUNDLE);
const gameSoundInstance = new Sound('game.mp3', Sound.MAIN_BUNDLE);
setCatSound(catSoundInstance);
setDogSound(dogSoundInstance);
setGameSound(gameSoundInstance);
} catch (error) {
console.log('Error al cargar los sonidos', error);
}
};
loadSounds();
}, []);
Segundo
useEffect
: EsteuseEffect
se ejecuta cuando el componente se desmonta o cuando las dependencias (catSound
,dogSound
,gameSound
) cambian. Su función de limpieza libera los recursos de los sonidos para prevenir fugas de memoria.3.Second
useEffect
: ThisuseEffect
runs when the component unmounts or when the dependencies (catSound
,dogSound
,gameSound
) change. Its cleanup function releases the resources of the sounds to prevent memory leaks.useEffect(() => { return () => { catSound?.release(); dogSound?.release(); gameSound?.release(); }; }, [catSound, dogSound, gameSound]);