리엑트 네이티브 토이 프로젝트 - 꿈자리 로또 추첨기

Bosees·2022년 2월 20일
0

Mobile_Programming

목록 보기
1/1

intro


최근 MZ세대에 대한 미래 불확실성에 대해 많은 얘기가 오간다. MZ세대. 밀레니얼과 z 세대의 합성어로 x 세대와는 달리, 인터넷 기기 사용이나 모바일 서비스, 최신 트랜드등을 잘 이해하고 남들이 아닌 차별화된 내가 주체가 되는 성향을 보인 세대라고 표현한다.

하지만 이러한 MZ세대는 경제적 조건과 주어진 환경에 대해 상대적 박탈감을 많이 느끼기도 하는 세대이다. 여러 신조어 중 워라벨, 파이어족, 욜로족 등이 생겨버린 이유도 다른 사람이 아닌 내가 주체가 되어 나는 어떠한 삶을 살아가야 하는가에 대해 고민할 수밖에 없는 상황을 줬기 때문이라고 생각하고. 실제로 워라벨, 파이어족, 욜로족에 대한 관념을 100퍼센트 이해하고 그걸 따르는 사람이 있지만 그저 상황이 좋지 않기 때문에 자기합리화를 선택해 자신을 어떠한 개념에 종속시키려고 하는 사람도 있다. 이렇듯 MZ세대라는 이유가 아주 복잡한 경계선에 서 있고 어떠한 선택도 할 수 있으므로 많은 얘기가 오고 가는 것 같다.

MZ세대에 미래의 대한 불확실성에 대해 크게 두 가지 정도 추려보았다.

  • 부동산 시세 폭등으로 인한 집값 상승 때문에 이제는 너무나 커져 버린 꿈. 내 집 마련.
  • 치솟는 물가와 반대되는 낮은 임금 상승 폭.

MZ세대의 수많은 키워드 중 경제적 여건과 관련된 가장 큰 키워드인 내 집 마련에 꿈, 그리고 낮은 임금 상승 폭을 주제로 답답한 현실에 조금이나마 위안을 얻을 수 있는 앱을 만들고자 "꿈자리와 관련된 로또 추출기 앱을 만들자고 생각했다."

실제로 지속되는 코로나와 낮은 고용, 임금 등의 여파로 인해 로또 판매량은 굉장히 높아졌다고 한다.

꿈자리 로또 추첨기 - 드림로또

컨셉은 아주 간단하다. 어제 내가 꾼 꿈의 내용을 앱에 기록해 그 꿈과 관련된 로또 6개의 번호를 추출하는 앱이다.

먼저 무엇을 고려해야할까?

개발환경

  • React-native
  • Visula studio code
  • android app studio

이 세 가지면 충분하고, 그다음으로 로직과 관련한 내용은

  • 꿈을 꾼 날짜와 꿈의 내용을 잘 계산해 고정적인 꿈자리 번호를 추출하는 것
  • 꿈자리 번호가 겹치지 않도록 집합 자료구조인 Set 자료구조를 활용할 것.
  • 1~45의 숫자가 나와야 할 것.
  • 어떠한 꿈자리 내용이 들어와도 6개의 번호를 생성할 것.
  • 꿈자리가 상세할수록 복잡도가 늘어나야 할 것.
  • 꿈자리가 없는 사람도 사용할 수 있도록 무작위 번호를 추출할 것.

등이 있었다.

작업 시작


먼저 react-native 팀에서 제공하는 보일러 플레이트를 다운 받아보자.

npx react-native init [프로젝트 명]

그리고 package.json의 의존성 라이브러리 추가하자.

리엑트 네이티브의 네비게이션 스택을 쌓을 수 있도록 해주는 react-navigation 설치

npm install @react-navigation/native @react-navigation/native-stack react-native safe-area-context react-native-screens

폴더의 구조는 크게 /src 폴더를 기점으로 진행해 볼려고한다. src 폴더 안에는

  • components - 컴포넌트를 정리할 폴더.
  • navigation - 리엑트 네이티브 스택을 정리할 네비게이션 폴더.
  • screen - 유저에게 보여지는 View단.
  • common - 컬러,폰트 등 글로벌 셋업을 할 수 있는 폴더.

서비스가 커진다면 부족한 구조이지만 로또 앱을 만들기에는 이 4가지면 충분하다.

맨 처음 해야하는 index.jsx 와 app.jsx 셋팅

app.js

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import MainNavigator from "./src/navigation/navigation";

const App = () => {
  return (
    <NavigationContainer >
      <MainNavigator />
    </NavigationContainer>
  );
};

export default App;

그리고 네비게이션 셋팅

import * as React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

//Screen
import { Splash } from "../screen/Splash";
import { Home } from "../screen/Home";

const defaultScreenOption = {
    headerShown: false,
    gestureEnabled: false
}

const Stack = createNativeStackNavigator();

const Navigation = () => {
    return (
        <Stack.Navigator screenOptions={defaultScreenOption}>
            <Stack.Screen name="Splash" component={Splash}/>
            <Stack.Screen name="Home" component={Home} />
        </Stack.Navigator>
    );
};

export default Navigation;

처음은 짤막하게 앱에 타이틀을 보여줄 수 있도록 Splash 화면을 보여줄수 있도록 구성하였고. 한 1초정도 지나면 바로 Home화면으로 넘어갈 수 있게 스택을 구성하였다.

그리고 앱에 대표 컬러를 변수로 활용하기 위해서 common/styles.jsx 를 생성.

const PASTEL_PUPPLE = "#D9D7F1";
const PASTEL_YELLOW = "#FFFDDE";
const PASTEL_GREEN = "#E7FBBE";
const PASTEL_PINK = "#FFCBCB";

export {
    PASTEL_GREEN,
    PASTEL_PINK,
    PASTEL_YELLOW,
    PASTEL_PUPPLE,
}

이 컬러는 미적 감각이 좋지않은 나를 위해 색상을 조합해주는 사이트에서 인기도가 높은 파스텔톤의 컬러를 채택하였다.

마지막으로 가장 중요한 로또를 생성하는 알고리즘을 가지고 있는 홈화면이다.

import React, { useState, useEffect } from "react";
import { SafeAreaView, View, Alert, Text, StyleSheet, TextInput, Button, BackHandler } from "react-native";
import { PASTEL_PUPPLE, PASTEL_YELLOW, PASTEL_PINK } from "../common/color";
import { Ball } from "../components/ball";

export const Home = () => {
    const [ dream, setDream ] = useState("");
    const [ flag, setFlag ] = useState(false);
    const [ lotto, setLotto ] = useState([]);
    const [ random, setRandom ] = useState([]);
    const [ dataSum, setDateSum ] = useState(0);

const sortArray = (array) => {
    return array.sort((a, b) => a - b);
}

const randomGenerateLotto = (length) => {
    let randomArray = [];
    for(let i = 0; i < length; i++) {
        let tempArray = [];
        for (let j = 0; j < 6; j++) {
            tempArray.push(parseInt(String(Math.random() * 45 + 1)));
        }
        randomArray.push(setNumberGenerator(tempArray, dataSum));
    }
    return randomArray;
}

const setNumberGenerator = (array, sum) => {
    const set = new Set([0]);
    const tempArray = array.slice();
    let count = 0;

    while(count < 6) {
        if (set.has((tempArray[count]) % 46 )) {
            tempArray[count] += sum;
        } else {
            set.add((tempArray[count]) % 46);
            tempArray[count] = (tempArray[count]) % 46;
            count++;
        }
    }

    return sortArray(tempArray);
}

const onGenerateLotto = () => {
    const joinText = dream.split(" ").join("");
    let randomArrays = [];
    if(joinText.length <= 0) {
        randomArrays = randomGenerateLotto(5);
        setFlag(false);
        setRandom(randomArrays);
    } else {
        randomArrays = randomGenerateLotto(4);    
        let total:number = 0;
        let dreamArray = [];
        for (let i = 0; i < dream.length; i++) {
            total+= dream.charCodeAt(i);
        }

        for(let i = 2; i <= 7; i++) {
            dreamArray.push(parseInt(String((total / i) * dataSum)));
        }
        dreamArray = setNumberGenerator(dreamArray, dataSum);

        setLotto(dreamArray);
        setRandom(randomArrays);
        setFlag(true);
    }
}

const backAction = () => {
    Alert.alert("Hold on!", "앱을 종료하시겠습니까?", [
        {
        text: "취소",
        onPress: () => null,
        style: "cancel"
        },
        { text: "확인", onPress: () => BackHandler.exitApp() }
    ]);
    return true;
}

useEffect(() => {
    BackHandler.addEventListener("hardwareBackPress", backAction)
    return () => BackHandler.removeEventListener("hardwareBackPress", backAction);
}, [])

useEffect(() => {
    const date = new Date();
    let sum = 0;
    sum += date.getFullYear();
    sum += date.getMonth() + 1;
    sum += date.getDate();
    setDateSum(sum);
}, [])

return (
    <SafeAreaView style={styles.background}>
        <View style={styles.titleContainer}>
            <Text style={styles.mainTitle}>
                어떤 꿈을 꾸셨나요?
            </Text>
            <TextInput
                style={styles.dreamInput}
                onChangeText={setDream}
                value={dream}
                placeholder="자세할수록 확률이 올라갑니다."
            />
            <Button 
                color={PASTEL_PUPPLE}
                title="추출하기"
                onPress={onGenerateLotto} 
            />
            {flag ?
            <View>
                <Text style={styles.lottoTitle}> 꿈자리 로또 번호</Text>
                <View style={styles.dreamLotto}>
                    {lotto.map((number, index) => {
                        return <Ball key={index} number={number}/>
                    })}
                </View>
                <Text style={styles.lottoTitle} >랜덤 번호</Text>
                <View style={styles.otherLottoList}>
                    {random.map((list, index)=> {
                        return (
                            <View key={index} style={styles.otherLotto}>
                                {[...list].map((number, index) => {
                                    return <Ball key={index} number={number} />
                                })}
                            </View>
                        )
                    })}
                </View>
            </View>    
            :
            <>
                {random.length ? <Text style={styles.lottoTitle} >랜덤 번호</Text>: <></>}
                <View style={styles.otherLottoList}>
                    {random.map((list, index)=> {
                        return (
                            <View key={index} style={styles.otherLotto}>
                                {[...list].map((number, index) => {
                                    return <Ball key={index} number={number} />
                                })}
                            </View>
                        )
                    })}
                </View>
            </>
            }
        </View>
    </SafeAreaView>
)
}

const styles = StyleSheet.create({
    background: {
        flex: 1,
    },
    mainTitle: {
        fontSize: 25
    },
    titleContainer: {
        flex: 1,
        padding: 20,
    },
    dreamInput: {
        borderColor: "black",
        borderRadius:10,
        borderWidth: 1,
        margin: 10,
    },
    dreamLotto: {
        backgroundColor: PASTEL_YELLOW,
        flexDirection: "row",
        alignItems:"center",
        justifyContent:"center",
        borderRadius: 20,
    },
    otherLottoList: {
        backgroundColor: PASTEL_PINK,
        borderRadius: 20,
    },
    otherLotto: {
        flexDirection: "row",
        alignItems:"center",
        justifyContent:"center",
    },
    lottoTitle: {
        fontSize:20,
        margin:10,
    }
})

함수는 3가지정도로 볼 수 있는데,

  • 꿈자리 텍스트를 아스키코드로 변환해 숫자를 추출하는 함수 (onGeneratorLotto)
  • 꿈자리 로또가 아닌 랜덤 로또를 생성하기 위한 (randomGeneratorLotto)
  • 중복된 로또는 Set 자료구조를 사용해 새로운 배열로 만들어 주는 (setNumberGenerlator)

이다. 전체적으로 간략하게 설명하자면 인풋으로 들어온 텍스트를 아스키코드로 바꾸고 그 값을 날짜와 더해 1~45에 해당하는 겹치지않는 6개의 번호를 만드는 알고리즘이다.

결과물


결론

재미있는 코딩을 통해 로또에 조금이나마 신앙심을 불어넣어 1등 당첨이 됐으면 하는 바람이다. 여기서 신앙심의 대상은 cpu이다. 내 cpu를 믿어보자.

profile
블록체인 프론트엔드 개발자

0개의 댓글