신승미

number guessing

1 +import { useState } from "react";
2 +import { StyleSheet, ImageBackground, SafeAreaView } from "react-native";
3 +import { LinearGradient } from "expo-linear-gradient";
4 +import { useFonts } from "expo-font";
5 +import AppLoading from "expo-app-loading";
6 +
7 +import StartGameScreen from "./screens/StartGameScreen";
8 +import GameScreen from "./screens/GameScreen";
9 +import GameOverScreen from "./screens/GameOverScreen";
10 +import Colors from "./constants/colors";
11 +
12 +export default function App() {
13 + const [userNumber, setUserNumber] = useState();
14 + const [gameIsOver, setGameIsOver] = useState(true);
15 + const [guessRounds, setGuessRounds] = useState(0);
16 +
17 + const [fontsLoaded] = useFonts({
18 + "open-sans": require("./assets/fonts/OpenSans-Regular.ttf"),
19 + "open-sans-bold": require("./assets/fonts/OpenSans-Bold.ttf"),
20 + });
21 +
22 + if (!fontsLoaded) {
23 + return <AppLoading />;
24 + }
25 +
26 + function pickedNumberHandler(pickedNumber) {
27 + setUserNumber(pickedNumber);
28 + setGameIsOver(false);
29 + }
30 +
31 + function gameOverHandler(numberOfRounds) {
32 + setGameIsOver(true);
33 + setGuessRounds(numberOfRounds);
34 + }
35 +
36 + function startNewGameHandler() {
37 + setUserNumber(null);
38 + setGuessRounds(0);
39 + }
40 +
41 + let screen = <StartGameScreen onPickNumber={pickedNumberHandler} />;
42 +
43 + if (userNumber) {
44 + screen = (
45 + <GameScreen userNumber={userNumber} onGameOver={gameOverHandler} />
46 + );
47 + }
48 +
49 + if (gameIsOver && userNumber) {
50 + screen = (
51 + <GameOverScreen
52 + userNumber={userNumber}
53 + roundsNumber={guessRounds}
54 + onStartNewGame={startNewGameHandler}
55 + />
56 + );
57 + }
58 +
59 + return (
60 + <LinearGradient
61 + colors={[Colors.primary700, Colors.accent500]}
62 + style={styles.rootScreen}
63 + >
64 + <ImageBackground
65 + source={require("./assets/images/background.png")}
66 + resizeMode="cover"
67 + style={styles.rootScreen}
68 + imageStyle={styles.backgroundImage}
69 + >
70 + <SafeAreaView style={styles.rootScreen}>{screen}</SafeAreaView>
71 + </ImageBackground>
72 + </LinearGradient>
73 + );
74 +}
75 +
76 +const styles = StyleSheet.create({
77 + rootScreen: {
78 + flex: 1,
79 + },
80 + backgroundImage: {
81 + opacity: 0.15,
82 + },
83 +});
1 +{
2 + "expo": {
3 + "name": "numGame",
4 + "slug": "numGame",
5 + "version": "1.0.0",
6 + "orientation": "portrait",
7 + "icon": "./assets/icon.png",
8 + "userInterfaceStyle": "light",
9 + "splash": {
10 + "image": "./assets/splash.png",
11 + "resizeMode": "contain",
12 + "backgroundColor": "#ffffff"
13 + },
14 + "updates": {
15 + "fallbackToCacheTimeout": 0
16 + },
17 + "assetBundlePatterns": [
18 + "**/*"
19 + ],
20 + "ios": {
21 + "supportsTablet": true
22 + },
23 + "android": {
24 + "adaptiveIcon": {
25 + "foregroundImage": "./assets/adaptive-icon.png",
26 + "backgroundColor": "#FFFFFF"
27 + }
28 + },
29 + "web": {
30 + "favicon": "./assets/favicon.png"
31 + }
32 + }
33 +}
No preview for this file type
No preview for this file type
1 +module.exports = function(api) {
2 + api.cache(true);
3 + return {
4 + presets: ['babel-preset-expo'],
5 + };
6 +};
1 +import { View, Text, StyleSheet } from "react-native";
2 +
3 +import Colors from "../../constants/colors";
4 +
5 +function GuessLogItem({ roundNumber, guess }) {
6 + return (
7 + <View style={styles.listItem}>
8 + <Text style={styles.itemText}>#{roundNumber}</Text>
9 + <Text style={styles.itemText}>Opponent's Guess: {guess}</Text>
10 + </View>
11 + );
12 +}
13 +
14 +export default GuessLogItem;
15 +
16 +const styles = StyleSheet.create({
17 + listItem: {
18 + borderColor: Colors.primary800,
19 + borderWidth: 1,
20 + borderRadius: 40,
21 + padding: 12,
22 + marginVertical: 8,
23 + backgroundColor: Colors.accent500,
24 + flexDirection: "row",
25 + justifyContent: "space-between",
26 + width: "100%",
27 + elevation: 4,
28 + shadowColor: "black",
29 + shadowOffset: { width: 0, height: 0 },
30 + shadowOpacity: 0.25,
31 + shadowRadius: 3,
32 + },
33 + itemText: {
34 + fontFamily: "open-sans",
35 + },
36 +});
1 +import { View, Text, StyleSheet } from "react-native";
2 +
3 +import Colors from "../../constants/colors";
4 +
5 +function NumberContainer({ children }) {
6 + return (
7 + <View style={styles.container}>
8 + <Text style={styles.numberText}>{children}</Text>
9 + </View>
10 + );
11 +}
12 +
13 +export default NumberContainer;
14 +
15 +const styles = StyleSheet.create({
16 + container: {
17 + borderWidth: 4,
18 + borderColor: Colors.accent500,
19 + padding: 24,
20 + margin: 24,
21 + borderRadius: 8,
22 + alignItems: "center",
23 + justifyContent: "center",
24 + },
25 + numberText: {
26 + color: Colors.accent500,
27 + fontSize: 36,
28 + // fontWeight: 'bold',
29 + fontFamily: "open-sans-bold",
30 + },
31 +});
1 +import { View, StyleSheet } from "react-native";
2 +
3 +import Colors from "../../constants/colors";
4 +
5 +function Card({ children }) {
6 + return <View style={styles.card}>{children}</View>;
7 +}
8 +
9 +export default Card;
10 +
11 +const styles = StyleSheet.create({
12 + card: {
13 + justifyContent: "center",
14 + alignItems: "center",
15 + marginTop: 36,
16 + marginHorizontal: 24,
17 + padding: 16,
18 + backgroundColor: Colors.primary800,
19 + borderRadius: 8,
20 + elevation: 4,
21 + shadowColor: "black",
22 + shadowOffset: { width: 0, height: 2 },
23 + shadowRadius: 6,
24 + shadowOpacity: 0.25,
25 + },
26 +});
1 +import { Text, StyleSheet } from "react-native";
2 +
3 +import Colors from "../../constants/colors";
4 +
5 +function InstructionText({ children, style }) {
6 + return <Text style={[styles.instructionText, style]}>{children}</Text>;
7 +}
8 +
9 +export default InstructionText;
10 +
11 +const styles = StyleSheet.create({
12 + instructionText: {
13 + fontFamily: "open-sans",
14 + color: Colors.accent500,
15 + fontSize: 24,
16 + },
17 +});
1 +import { View, Text, Pressable, StyleSheet } from "react-native";
2 +
3 +import Colors from "../../constants/colors";
4 +
5 +function PrimaryButton({ children, onPress }) {
6 + return (
7 + <View style={styles.buttonOuterContainer}>
8 + <Pressable
9 + style={({ pressed }) =>
10 + pressed
11 + ? [styles.buttonInnerContainer, styles.pressed]
12 + : styles.buttonInnerContainer
13 + }
14 + onPress={onPress}
15 + android_ripple={{ color: Colors.primary600 }}
16 + >
17 + <Text style={styles.buttonText}>{children}</Text>
18 + </Pressable>
19 + </View>
20 + );
21 +}
22 +
23 +export default PrimaryButton;
24 +
25 +const styles = StyleSheet.create({
26 + buttonOuterContainer: {
27 + borderRadius: 28,
28 + margin: 4,
29 + overflow: "hidden",
30 + },
31 + buttonInnerContainer: {
32 + backgroundColor: Colors.primary500,
33 + paddingVertical: 8,
34 + paddingHorizontal: 16,
35 + elevation: 2,
36 + },
37 + buttonText: {
38 + color: "white",
39 + textAlign: "center",
40 + },
41 + pressed: {
42 + opacity: 0.75,
43 + },
44 +});
1 +import { Text, StyleSheet } from "react-native";
2 +
3 +function Title({ children }) {
4 + return <Text style={styles.title}>{children}</Text>;
5 +}
6 +
7 +export default Title;
8 +
9 +const styles = StyleSheet.create({
10 + title: {
11 + fontFamily: "open-sans-bold",
12 + fontSize: 24,
13 + // fontWeight: 'bold',
14 + color: "white",
15 + textAlign: "center",
16 + borderWidth: 2,
17 + borderColor: "white",
18 + padding: 12,
19 + },
20 +});
1 +const Colors = {
2 + primary500: "#72063c",
3 + primary600: "#640233",
4 + primary700: "#4e0329",
5 + primary800: "#3b021f",
6 + accent500: "#ddb52f",
7 +};
8 +
9 +export default Colors;
1 +{
2 + "name": "numgame",
3 + "version": "1.0.0",
4 + "main": "node_modules/expo/AppEntry.js",
5 + "scripts": {
6 + "start": "expo start",
7 + "android": "expo start --android",
8 + "ios": "expo start --ios",
9 + "web": "expo start --web",
10 + "eject": "expo eject"
11 + },
12 + "dependencies": {
13 + "expo": "~45.0.0",
14 + "expo-app-loading": "~2.0.0",
15 + "expo-font": "~10.1.0",
16 + "expo-linear-gradient": "~11.3.0",
17 + "expo-status-bar": "~1.3.0",
18 + "react": "17.0.2",
19 + "react-dom": "17.0.2",
20 + "react-native": "0.68.2",
21 + "react-native-web": "0.17.7"
22 + },
23 + "devDependencies": {
24 + "@babel/core": "^7.12.9"
25 + },
26 + "private": true
27 +}
1 +import { View, Image, Text, StyleSheet } from "react-native";
2 +
3 +import Title from "../components/ui/Title";
4 +import PrimaryButton from "../components/ui/PrimaryButton";
5 +import Colors from "../constants/colors";
6 +
7 +function GameOverScreen({ roundsNumber, userNumber, onStartNewGame }) {
8 + return (
9 + <View style={styles.rootContainer}>
10 + <Title>GAME OVER!</Title>
11 + <View style={styles.imageContainer}>
12 + <Image
13 + style={styles.image}
14 + source={require("../assets/images/success.png")}
15 + />
16 + </View>
17 + <Text style={styles.summaryText}>
18 + Your phone needed <Text style={styles.highlight}>{roundsNumber}</Text>{" "}
19 + rounds to guess the number{" "}
20 + <Text style={styles.highlight}>{userNumber}</Text>.
21 + </Text>
22 + <PrimaryButton onPress={onStartNewGame}>Start New Game</PrimaryButton>
23 + </View>
24 + );
25 +}
26 +
27 +export default GameOverScreen;
28 +
29 +const styles = StyleSheet.create({
30 + rootContainer: {
31 + flex: 1,
32 + padding: 24,
33 + justifyContent: "center",
34 + alignItems: "center",
35 + },
36 + imageContainer: {
37 + width: 300,
38 + height: 300,
39 + borderRadius: 150,
40 + borderWidth: 3,
41 + borderColor: Colors.primary800,
42 + overflow: "hidden",
43 + margin: 36,
44 + },
45 + image: {
46 + width: "100%",
47 + height: "100%",
48 + },
49 + summaryText: {
50 + fontFamily: "open-sans",
51 + fontSize: 24,
52 + textAlign: "center",
53 + marginBottom: 24,
54 + },
55 + highlight: {
56 + fontFamily: "open-sans-bold",
57 + color: Colors.primary500,
58 + },
59 +});
1 +import { useState, useEffect } from "react";
2 +import { View, StyleSheet, Alert, Text, FlatList } from "react-native";
3 +import { Ionicons } from "@expo/vector-icons";
4 +
5 +import NumberContainer from "../components/game/NumberContainer";
6 +import Card from "../components/ui/Card";
7 +import InstructionText from "../components/ui/InstructionText";
8 +import PrimaryButton from "../components/ui/PrimaryButton";
9 +import Title from "../components/ui/Title";
10 +import GuessLogItem from "../components/game/GuessLogItem";
11 +
12 +function generateRandomBetween(min, max, exclude) {
13 + const rndNum = Math.floor(Math.random() * (max - min)) + min;
14 +
15 + if (rndNum === exclude) {
16 + return generateRandomBetween(min, max, exclude);
17 + } else {
18 + return rndNum;
19 + }
20 +}
21 +
22 +let minBoundary = 1;
23 +let maxBoundary = 100;
24 +
25 +function GameScreen({ userNumber, onGameOver }) {
26 + const initialGuess = generateRandomBetween(1, 100, userNumber);
27 + const [currentGuess, setCurrentGuess] = useState(initialGuess);
28 + const [guessRounds, setGuessRounds] = useState([initialGuess]);
29 +
30 + useEffect(() => {
31 + if (currentGuess === userNumber) {
32 + onGameOver(guessRounds.length);
33 + }
34 + }, [currentGuess, userNumber, onGameOver]);
35 +
36 + useEffect(() => {
37 + minBoundary = 1;
38 + maxBoundary = 100;
39 + }, []);
40 +
41 + function nextGuessHandler(direction) {
42 + // direction => 'lower', 'greater'
43 + if (
44 + (direction === "lower" && currentGuess < userNumber) ||
45 + (direction === "greater" && currentGuess > userNumber)
46 + ) {
47 + Alert.alert("Don't lie!", "You know that this is wrong...", [
48 + { text: "Sorry!", style: "cancel" },
49 + ]);
50 + return;
51 + }
52 +
53 + if (direction === "lower") {
54 + maxBoundary = currentGuess;
55 + } else {
56 + minBoundary = currentGuess + 1;
57 + }
58 +
59 + const newRndNumber = generateRandomBetween(
60 + minBoundary,
61 + maxBoundary,
62 + currentGuess
63 + );
64 + setCurrentGuess(newRndNumber);
65 + setGuessRounds((prevGuessRounds) => [newRndNumber, ...prevGuessRounds]);
66 + }
67 +
68 + const guessRoundsListLength = guessRounds.length;
69 +
70 + return (
71 + <View style={styles.screen}>
72 + <Title>Opponent's Guess</Title>
73 + <NumberContainer>{currentGuess}</NumberContainer>
74 + <Card>
75 + <InstructionText style={styles.instructionText}>
76 + Higher or lower?
77 + </InstructionText>
78 + <View style={styles.buttonsContainer}>
79 + <View style={styles.buttonContainer}>
80 + <PrimaryButton onPress={nextGuessHandler.bind(this, "lower")}>
81 + <Ionicons name="md-remove" size={24} color="white" />
82 + </PrimaryButton>
83 + </View>
84 + <View style={styles.buttonContainer}>
85 + <PrimaryButton onPress={nextGuessHandler.bind(this, "greater")}>
86 + <Ionicons name="md-add" size={24} color="white" />
87 + </PrimaryButton>
88 + </View>
89 + </View>
90 + </Card>
91 + <View style={styles.listContainer}>
92 + {/* {guessRounds.map(guessRound => <Text key={guessRound}>{guessRound}</Text>)} */}
93 + <FlatList
94 + data={guessRounds}
95 + renderItem={(itemData) => (
96 + <GuessLogItem
97 + roundNumber={guessRoundsListLength - itemData.index}
98 + guess={itemData.item}
99 + />
100 + )}
101 + keyExtractor={(item) => item}
102 + />
103 + </View>
104 + </View>
105 + );
106 +}
107 +
108 +export default GameScreen;
109 +
110 +const styles = StyleSheet.create({
111 + screen: {
112 + flex: 1,
113 + padding: 24,
114 + },
115 + instructionText: {
116 + marginBottom: 12,
117 + },
118 + buttonsContainer: {
119 + flexDirection: "row",
120 + },
121 + buttonContainer: {
122 + flex: 1,
123 + },
124 + listContainer: {
125 + flex: 1,
126 + padding: 16,
127 + },
128 +});
1 +import { useState } from "react";
2 +import { TextInput, View, StyleSheet, Alert } from "react-native";
3 +
4 +import PrimaryButton from "../components/ui/PrimaryButton";
5 +import Title from "../components/ui/Title";
6 +import Colors from "../constants/colors";
7 +import Card from "../components/ui/Card";
8 +import InstructionText from "../components/ui/InstructionText";
9 +
10 +function StartGameScreen({ onPickNumber }) {
11 + const [enteredNumber, setEnteredNumber] = useState("");
12 +
13 + function numberInputHandler(enteredText) {
14 + setEnteredNumber(enteredText);
15 + }
16 +
17 + function resetInputHandler() {
18 + setEnteredNumber("");
19 + }
20 +
21 + function confirmInputHandler() {
22 + const chosenNumber = parseInt(enteredNumber);
23 +
24 + if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
25 + Alert.alert(
26 + "Invalid number!",
27 + "Number has to be a number between 1 and 99.",
28 + [{ text: "Okay", style: "destructive", onPress: resetInputHandler }]
29 + );
30 + return;
31 + }
32 +
33 + onPickNumber(chosenNumber);
34 + }
35 +
36 + return (
37 + <View style={styles.rootContainer}>
38 + <Title>Guess My Number</Title>
39 + <Card>
40 + <InstructionText>Enter a Number</InstructionText>
41 + <TextInput
42 + style={styles.numberInput}
43 + maxLength={2}
44 + keyboardType="number-pad"
45 + autoCapitalize="none"
46 + autoCorrect={false}
47 + onChangeText={numberInputHandler}
48 + value={enteredNumber}
49 + />
50 + <View style={styles.buttonsContainer}>
51 + <View style={styles.buttonContainer}>
52 + <PrimaryButton onPress={resetInputHandler}>Reset</PrimaryButton>
53 + </View>
54 + <View style={styles.buttonContainer}>
55 + <PrimaryButton onPress={confirmInputHandler}>Confirm</PrimaryButton>
56 + </View>
57 + </View>
58 + </Card>
59 + </View>
60 + );
61 +}
62 +
63 +export default StartGameScreen;
64 +
65 +const styles = StyleSheet.create({
66 + rootContainer: {
67 + flex: 1,
68 + marginTop: 100,
69 + alignItems: "center",
70 + },
71 + numberInput: {
72 + height: 50,
73 + width: 50,
74 + fontSize: 32,
75 + borderBottomColor: Colors.accent500,
76 + borderBottomWidth: 2,
77 + color: Colors.accent500,
78 + marginVertical: 8,
79 + fontWeight: "bold",
80 + textAlign: "center",
81 + },
82 + buttonsContainer: {
83 + flexDirection: "row",
84 + },
85 + buttonContainer: {
86 + flex: 1,
87 + },
88 +});
This diff could not be displayed because it is too large.