신승미

number guessing

import { useState } from "react";
import { StyleSheet, ImageBackground, SafeAreaView } from "react-native";
import { LinearGradient } from "expo-linear-gradient";
import { useFonts } from "expo-font";
import AppLoading from "expo-app-loading";
import StartGameScreen from "./screens/StartGameScreen";
import GameScreen from "./screens/GameScreen";
import GameOverScreen from "./screens/GameOverScreen";
import Colors from "./constants/colors";
export default function App() {
const [userNumber, setUserNumber] = useState();
const [gameIsOver, setGameIsOver] = useState(true);
const [guessRounds, setGuessRounds] = useState(0);
const [fontsLoaded] = useFonts({
"open-sans": require("./assets/fonts/OpenSans-Regular.ttf"),
"open-sans-bold": require("./assets/fonts/OpenSans-Bold.ttf"),
});
if (!fontsLoaded) {
return <AppLoading />;
}
function pickedNumberHandler(pickedNumber) {
setUserNumber(pickedNumber);
setGameIsOver(false);
}
function gameOverHandler(numberOfRounds) {
setGameIsOver(true);
setGuessRounds(numberOfRounds);
}
function startNewGameHandler() {
setUserNumber(null);
setGuessRounds(0);
}
let screen = <StartGameScreen onPickNumber={pickedNumberHandler} />;
if (userNumber) {
screen = (
<GameScreen userNumber={userNumber} onGameOver={gameOverHandler} />
);
}
if (gameIsOver && userNumber) {
screen = (
<GameOverScreen
userNumber={userNumber}
roundsNumber={guessRounds}
onStartNewGame={startNewGameHandler}
/>
);
}
return (
<LinearGradient
colors={[Colors.primary700, Colors.accent500]}
style={styles.rootScreen}
>
<ImageBackground
source={require("./assets/images/background.png")}
resizeMode="cover"
style={styles.rootScreen}
imageStyle={styles.backgroundImage}
>
<SafeAreaView style={styles.rootScreen}>{screen}</SafeAreaView>
</ImageBackground>
</LinearGradient>
);
}
const styles = StyleSheet.create({
rootScreen: {
flex: 1,
},
backgroundImage: {
opacity: 0.15,
},
});
{
"expo": {
"name": "numGame",
"slug": "numGame",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
}
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
No preview for this file type
No preview for this file type
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};
import { View, Text, StyleSheet } from "react-native";
import Colors from "../../constants/colors";
function GuessLogItem({ roundNumber, guess }) {
return (
<View style={styles.listItem}>
<Text style={styles.itemText}>#{roundNumber}</Text>
<Text style={styles.itemText}>Opponent's Guess: {guess}</Text>
</View>
);
}
export default GuessLogItem;
const styles = StyleSheet.create({
listItem: {
borderColor: Colors.primary800,
borderWidth: 1,
borderRadius: 40,
padding: 12,
marginVertical: 8,
backgroundColor: Colors.accent500,
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
elevation: 4,
shadowColor: "black",
shadowOffset: { width: 0, height: 0 },
shadowOpacity: 0.25,
shadowRadius: 3,
},
itemText: {
fontFamily: "open-sans",
},
});
import { View, Text, StyleSheet } from "react-native";
import Colors from "../../constants/colors";
function NumberContainer({ children }) {
return (
<View style={styles.container}>
<Text style={styles.numberText}>{children}</Text>
</View>
);
}
export default NumberContainer;
const styles = StyleSheet.create({
container: {
borderWidth: 4,
borderColor: Colors.accent500,
padding: 24,
margin: 24,
borderRadius: 8,
alignItems: "center",
justifyContent: "center",
},
numberText: {
color: Colors.accent500,
fontSize: 36,
// fontWeight: 'bold',
fontFamily: "open-sans-bold",
},
});
import { View, StyleSheet } from "react-native";
import Colors from "../../constants/colors";
function Card({ children }) {
return <View style={styles.card}>{children}</View>;
}
export default Card;
const styles = StyleSheet.create({
card: {
justifyContent: "center",
alignItems: "center",
marginTop: 36,
marginHorizontal: 24,
padding: 16,
backgroundColor: Colors.primary800,
borderRadius: 8,
elevation: 4,
shadowColor: "black",
shadowOffset: { width: 0, height: 2 },
shadowRadius: 6,
shadowOpacity: 0.25,
},
});
import { Text, StyleSheet } from "react-native";
import Colors from "../../constants/colors";
function InstructionText({ children, style }) {
return <Text style={[styles.instructionText, style]}>{children}</Text>;
}
export default InstructionText;
const styles = StyleSheet.create({
instructionText: {
fontFamily: "open-sans",
color: Colors.accent500,
fontSize: 24,
},
});
import { View, Text, Pressable, StyleSheet } from "react-native";
import Colors from "../../constants/colors";
function PrimaryButton({ children, onPress }) {
return (
<View style={styles.buttonOuterContainer}>
<Pressable
style={({ pressed }) =>
pressed
? [styles.buttonInnerContainer, styles.pressed]
: styles.buttonInnerContainer
}
onPress={onPress}
android_ripple={{ color: Colors.primary600 }}
>
<Text style={styles.buttonText}>{children}</Text>
</Pressable>
</View>
);
}
export default PrimaryButton;
const styles = StyleSheet.create({
buttonOuterContainer: {
borderRadius: 28,
margin: 4,
overflow: "hidden",
},
buttonInnerContainer: {
backgroundColor: Colors.primary500,
paddingVertical: 8,
paddingHorizontal: 16,
elevation: 2,
},
buttonText: {
color: "white",
textAlign: "center",
},
pressed: {
opacity: 0.75,
},
});
import { Text, StyleSheet } from "react-native";
function Title({ children }) {
return <Text style={styles.title}>{children}</Text>;
}
export default Title;
const styles = StyleSheet.create({
title: {
fontFamily: "open-sans-bold",
fontSize: 24,
// fontWeight: 'bold',
color: "white",
textAlign: "center",
borderWidth: 2,
borderColor: "white",
padding: 12,
},
});
const Colors = {
primary500: "#72063c",
primary600: "#640233",
primary700: "#4e0329",
primary800: "#3b021f",
accent500: "#ddb52f",
};
export default Colors;
{
"name": "numgame",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject"
},
"dependencies": {
"expo": "~45.0.0",
"expo-app-loading": "~2.0.0",
"expo-font": "~10.1.0",
"expo-linear-gradient": "~11.3.0",
"expo-status-bar": "~1.3.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-native": "0.68.2",
"react-native-web": "0.17.7"
},
"devDependencies": {
"@babel/core": "^7.12.9"
},
"private": true
}
import { View, Image, Text, StyleSheet } from "react-native";
import Title from "../components/ui/Title";
import PrimaryButton from "../components/ui/PrimaryButton";
import Colors from "../constants/colors";
function GameOverScreen({ roundsNumber, userNumber, onStartNewGame }) {
return (
<View style={styles.rootContainer}>
<Title>GAME OVER!</Title>
<View style={styles.imageContainer}>
<Image
style={styles.image}
source={require("../assets/images/success.png")}
/>
</View>
<Text style={styles.summaryText}>
Your phone needed <Text style={styles.highlight}>{roundsNumber}</Text>{" "}
rounds to guess the number{" "}
<Text style={styles.highlight}>{userNumber}</Text>.
</Text>
<PrimaryButton onPress={onStartNewGame}>Start New Game</PrimaryButton>
</View>
);
}
export default GameOverScreen;
const styles = StyleSheet.create({
rootContainer: {
flex: 1,
padding: 24,
justifyContent: "center",
alignItems: "center",
},
imageContainer: {
width: 300,
height: 300,
borderRadius: 150,
borderWidth: 3,
borderColor: Colors.primary800,
overflow: "hidden",
margin: 36,
},
image: {
width: "100%",
height: "100%",
},
summaryText: {
fontFamily: "open-sans",
fontSize: 24,
textAlign: "center",
marginBottom: 24,
},
highlight: {
fontFamily: "open-sans-bold",
color: Colors.primary500,
},
});
import { useState, useEffect } from "react";
import { View, StyleSheet, Alert, Text, FlatList } from "react-native";
import { Ionicons } from "@expo/vector-icons";
import NumberContainer from "../components/game/NumberContainer";
import Card from "../components/ui/Card";
import InstructionText from "../components/ui/InstructionText";
import PrimaryButton from "../components/ui/PrimaryButton";
import Title from "../components/ui/Title";
import GuessLogItem from "../components/game/GuessLogItem";
function generateRandomBetween(min, max, exclude) {
const rndNum = Math.floor(Math.random() * (max - min)) + min;
if (rndNum === exclude) {
return generateRandomBetween(min, max, exclude);
} else {
return rndNum;
}
}
let minBoundary = 1;
let maxBoundary = 100;
function GameScreen({ userNumber, onGameOver }) {
const initialGuess = generateRandomBetween(1, 100, userNumber);
const [currentGuess, setCurrentGuess] = useState(initialGuess);
const [guessRounds, setGuessRounds] = useState([initialGuess]);
useEffect(() => {
if (currentGuess === userNumber) {
onGameOver(guessRounds.length);
}
}, [currentGuess, userNumber, onGameOver]);
useEffect(() => {
minBoundary = 1;
maxBoundary = 100;
}, []);
function nextGuessHandler(direction) {
// direction => 'lower', 'greater'
if (
(direction === "lower" && currentGuess < userNumber) ||
(direction === "greater" && currentGuess > userNumber)
) {
Alert.alert("Don't lie!", "You know that this is wrong...", [
{ text: "Sorry!", style: "cancel" },
]);
return;
}
if (direction === "lower") {
maxBoundary = currentGuess;
} else {
minBoundary = currentGuess + 1;
}
const newRndNumber = generateRandomBetween(
minBoundary,
maxBoundary,
currentGuess
);
setCurrentGuess(newRndNumber);
setGuessRounds((prevGuessRounds) => [newRndNumber, ...prevGuessRounds]);
}
const guessRoundsListLength = guessRounds.length;
return (
<View style={styles.screen}>
<Title>Opponent's Guess</Title>
<NumberContainer>{currentGuess}</NumberContainer>
<Card>
<InstructionText style={styles.instructionText}>
Higher or lower?
</InstructionText>
<View style={styles.buttonsContainer}>
<View style={styles.buttonContainer}>
<PrimaryButton onPress={nextGuessHandler.bind(this, "lower")}>
<Ionicons name="md-remove" size={24} color="white" />
</PrimaryButton>
</View>
<View style={styles.buttonContainer}>
<PrimaryButton onPress={nextGuessHandler.bind(this, "greater")}>
<Ionicons name="md-add" size={24} color="white" />
</PrimaryButton>
</View>
</View>
</Card>
<View style={styles.listContainer}>
{/* {guessRounds.map(guessRound => <Text key={guessRound}>{guessRound}</Text>)} */}
<FlatList
data={guessRounds}
renderItem={(itemData) => (
<GuessLogItem
roundNumber={guessRoundsListLength - itemData.index}
guess={itemData.item}
/>
)}
keyExtractor={(item) => item}
/>
</View>
</View>
);
}
export default GameScreen;
const styles = StyleSheet.create({
screen: {
flex: 1,
padding: 24,
},
instructionText: {
marginBottom: 12,
},
buttonsContainer: {
flexDirection: "row",
},
buttonContainer: {
flex: 1,
},
listContainer: {
flex: 1,
padding: 16,
},
});
import { useState } from "react";
import { TextInput, View, StyleSheet, Alert } from "react-native";
import PrimaryButton from "../components/ui/PrimaryButton";
import Title from "../components/ui/Title";
import Colors from "../constants/colors";
import Card from "../components/ui/Card";
import InstructionText from "../components/ui/InstructionText";
function StartGameScreen({ onPickNumber }) {
const [enteredNumber, setEnteredNumber] = useState("");
function numberInputHandler(enteredText) {
setEnteredNumber(enteredText);
}
function resetInputHandler() {
setEnteredNumber("");
}
function confirmInputHandler() {
const chosenNumber = parseInt(enteredNumber);
if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
Alert.alert(
"Invalid number!",
"Number has to be a number between 1 and 99.",
[{ text: "Okay", style: "destructive", onPress: resetInputHandler }]
);
return;
}
onPickNumber(chosenNumber);
}
return (
<View style={styles.rootContainer}>
<Title>Guess My Number</Title>
<Card>
<InstructionText>Enter a Number</InstructionText>
<TextInput
style={styles.numberInput}
maxLength={2}
keyboardType="number-pad"
autoCapitalize="none"
autoCorrect={false}
onChangeText={numberInputHandler}
value={enteredNumber}
/>
<View style={styles.buttonsContainer}>
<View style={styles.buttonContainer}>
<PrimaryButton onPress={resetInputHandler}>Reset</PrimaryButton>
</View>
<View style={styles.buttonContainer}>
<PrimaryButton onPress={confirmInputHandler}>Confirm</PrimaryButton>
</View>
</View>
</Card>
</View>
);
}
export default StartGameScreen;
const styles = StyleSheet.create({
rootContainer: {
flex: 1,
marginTop: 100,
alignItems: "center",
},
numberInput: {
height: 50,
width: 50,
fontSize: 32,
borderBottomColor: Colors.accent500,
borderBottomWidth: 2,
color: Colors.accent500,
marginVertical: 8,
fontWeight: "bold",
textAlign: "center",
},
buttonsContainer: {
flexDirection: "row",
},
buttonContainer: {
flex: 1,
},
});
This diff could not be displayed because it is too large.