JS/VanillaJS

[VanillaJS] 바닐라JS의 setTimeOut을 이용한 간단한 로또추첨기 만들기

kth990303 2021. 10. 11. 12:27
반응형

요즘 리액트를 만지다가 기본 바닐라js의 중요성을 다시 한 번 깨닫게 됐다.

마침 리액트 프로젝트를 같이 하고 있는 친구가 중간고사 기간이라 프로젝트를 잠시 중단하겠다고 선언했기 때문에, 나도 그 기간동안 리액트의 기본이 되는 바닐라js를 다시 한 번 공부하는 시간을 가지는 중이다.

 

그렇게 해서 만들어본게 간단한 로또추첨기이다.

최종 결과물

추첨 결과에 해당되는 6개의 볼이 1초씩 간격을 두고 차례대로 뜨며,

마지막으로 보너스에 해당되는 볼이 텀을 두고 뜨게 하는 프로그램이다.

 

어렵지 않아 초심자들이 바닐라js 감을 익히기에 좋을 듯하여 코드작성과정을 포스팅하려 한다.

개발환경은 vscode이다.


알고리즘

1. 1~45까지의 숫자 중 랜덤하게 7개의 숫자를 뽑는다. 이 때, 7개의 숫자는 중복돼선 안된다.

2. 7개의 숫자 중 1~6번째까지의 숫자는 추첨결과 볼로, 7번째 숫자는 보너스로 포함한다.

3. 추첨 결과에 해당하는 6개의 볼은 오름차순으로 나타내준다.

4. 10 미만의 볼은 빨간색, 20미만의 볼은 주황색, 30 미만의 볼은 노란색, 40미만의 볼은 파란색, 그 이상은 초록색을 띄게 한다.


기본 틀을 잡아주자

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>로또 추첨기</title>
  <style>
    .ball{
      display: inline-block;
      border: 1px solid black;
      border-radius: 20px;
      width: 40px;
      height: 40px;
      line-height: 40px;
      font-size: 20px;
      font-weight: bold;
      text-align: center;
      margin-right: 10px;
    }
  </style>
</head>
<body>
  <div id="result">추첨 결과는?</div>
  <div id="bonus">보너스: </div>
  <script></script>
</body>
</html>

먼저 doc를 입력하고 tab버튼을 눌러주면 VSCode가 자동완성으로 html 틀을 잡아준다.

<! 까지 입력하고 tab을 누르면 DOCTYPE도 완성해준다.

여기서 html lang의 값을 en (영어)가 아닌 ko(한국어)로 수정해주자.

 

여기에 css를 위와 같이 추가해주자.

css에 대한 설명은 아래 코드를 참고하자.

<style>
    .ball{
      display: inline-block;	// 해당 숫자들이 가로로 뜨도록 함.
      border: 1px solid black;	// 해당 숫자들을 둘러싸는 원 둘레의 굵기, 색깔을 정해줌.
      border-radius: 20px;	// 해당 숫자들을 둘러싸는 원의 반지름
      width: 40px;	// 해당 숫자의 너비
      height: 40px;	// 해당 숫자의 높이. line-height로도 지정해주면 이건 없어도 큰 체감은 안드는듯?
      line-height: 40px;	// 해당 숫자의 높이
      font-size: 20px;	// 숫자의 크기
      font-weight: bold;	// 숫자를 굵게
      text-align: center;	// 가운데정렬
      margin-right: 10px;	// 볼과 볼 사이의 간격 지정
    }
  </style>

추첨결과와 보너스에 해당하는 숫자 7개를 랜덤하게 만들어주자.

이제 자바스크립트 코드를 추가할 시간이다.

script 태그 안에 코드를 조금씩 조금씩 추가해주자.

<script>
    const candidate=Array(45).fill().map((data, idx)=>idx+1);
    const shuffle=[];
    
    while(shuffle.length<7){
      const random=Math.floor(Math.random()*candidate.length);
      shuffle.push(candidate.splice(random, 1)[0]);
    }
    
    const winBalls=shuffle.slice(0, 6).sort((a,b)=>a-b);
    const bonus=shuffle[6];
    console.log(winBalls, bonus);
 </script>

먼저 candidate 배열부터 살펴보자.

const candidate=Array(45).fill().map((data, idx)=>idx+1);

Array(45)로 배열을 생성해주자. java로 치면 new int[45] 와 같이 45칸 짜리 배열을 선언해준 것이다.

Array(45).fill()까지 하면 공간할당까지 완료된 것이다.

우리는 1~45까지의 숫자를 담을 것이므로 map을 이용하여 1부터 45까지 차례대로 담아줄 것이다.

 

map의 인자로는 map((data, idx)=>{}) 순임이 결정돼있다. 만약 data만 이용할 것이라면 map((data)=>{}) 와 같이 작성해도 되지만, index만 이용하고 싶다고 map((idx)=>{}) 와 같이 이용한다면 idx자리에 인덱스가 아닌 element(data)값이 들어가므로 주의하자. 참고로 인자(파라미터) 변수명은 변경해도 상관없다.

const shuffle=[];
while(shuffle.length<7){
  const random=Math.floor(Math.random()*candidate.length);
  shuffle.push(candidate.splice(random, 1)[0]);
}

candidate는 1부터 45까지 순서대로 볼이 들어있으므로 shuffle에는 1~45까지 중 7개를 랜덤하게 담아볼 것이다.

Math.random() 함수는 0~1까지 랜덤한 값을 호출한다.

Math.random() * candidate.length 를 이용하면 candidate의 길이는 45이므로 0~45까지 랜덤한 값을 호출할 것이다.

다만, 이 값은 소수도 가능하므로 Math.floor 함수로 소수점 버림을 해준다.

const random 값에는 그럼 1~45까지 중 랜덤한 숫자 하나가 들어갈 것이다.

 

이 random값을 바로 shuffle에 집어넣어도 된다.

근데 그럴 경우 다른 숫자들과 중복을 검사하기가 까다로워진다.

물론 일일이 shuffle에 숫자가 들어있는지 확인해주는 작업을 거치면 되긴 하지만, shuffle을 일일이 탐색하는 시간복잡도 O(N) 작업을 거치는 것보다 더 좋은 방법이 있다

 

바로 candidate.splice(random, 1)을 이용하는 것이다.

splice 함수는 배열의 원소를 삭제 및 교체할 때 사용되는 함수이다. arr.splice(idx, length, element)와 같이 작성하면 arr 배열의 idx부터 idx+length-1까지의 원소들을 element로 교체해준다. element가 비어있으면 그냥 삭제를 진행한다. 위에선 candidate.splice(random, 1)로 했으므로 candidate 배열에서 random 인덱스에 해당하는 값을 삭제해주고, 그 값을 배열에 담아 반환해줄 것이다. 따라서,shuffle에 push할 때 배열이기 때문에 [0]을 붙여준 것이다.

 

이렇게 하면 shuffle에 들어간 값이 candidate에선 삭제가 된 상황이므로 중복될 일이 없다.

이 과정을 shuffle의 길이가 7이될 때까지 반복해준다.


추첨 결과에 해당하는 볼은 오름차순 정렬해주자.

const winBalls=shuffle.slice(0, 6).sort((a,b)=>a-b);
const bonus=shuffle[6];
console.log(winBalls, bonus);

shuffle.slice(0, 6)에서 slice 함수로 추첨결과에 해당하는 볼을 뽑아주자. slice(begin, end) 함수는 begin ~ end-1 (end는 미포함)까지의 data를 선택해준다. sort((a,b)=>a-b); 와 같이 적어주어 오름차순 정렬해준다.

이는 a-b의 값이 음수일 경우 a를 앞으로 보내주는 js 성질 때문에 가능한 것이다.

a-b가 음수일 경우 a<b이므로 a가 앞으로 가면 작은 값이 앞으로 가는 오름차순 정렬이 가능해진다.

참고로 sort((a,b)=>{return a-b}); 에서 =>화살표함수 특징 때문에 중괄호와 return이 생략된 것이지, 실제로 return하는 코드이다.

 

참고로 C++에 익숙하다면 return a<b; 와 같이 작성할 수도 있는데, js에선 이렇게 작성하면 인식하지 못해서 결과가 제대로 나오지 않는다.

이제 여기까지는 성공한 셈이다.

이제 화면에 띄워주자.


화면에 띄워주는 코드 작성

*여기서부턴 보는 것만으론 이해가 힘들 수 있습니다. 직접 해보셔야 이해하기 쉬울 것입니다.*

const $result=document.querySelector('#result');
const $bonus=document.querySelector('#bonus');
const color=['red', 'orange', 'yellow', 'blue', 'green'];

우선 추첨결과에 해당하는 6개 볼을 띄워줄 태그랑, 보너스를 띄워줄 태그를 querySelector로 선택해주자.

color는 위와 같이 배열로 설정해주자.

참고로 $ 또한 변수명에 포함된 것이다. $가 특수한 기능이 있는 것은 아니고, 태그를 의미하는 변수임을 알아보기 쉽게 변수명을 $로 시작한 것이다.

만약 querySelector에 대해 잘 모른다면 아래 포스팅을 참고하자.

https://kth990303.tistory.com/168

 

[VanillaJS] html 태그를 선택하는 querySelector에 대해 알아보자

사실 프론트 프레임워크를 다룬다면 querySelector는 거의 쓰이지 않지만, 바닐라js를 다룬다면 꽤나 자주 보게 될 querySelector에 대해 알아보자. 프론트 프레임워크를 배울거라고 querySelector 안배우겠

kth990303.tistory.com

이제 공을 1초 간격으로 뽑는 코드를 작성해보자.

// 공뽑기
for(let i=0;i<6;i++){
  setTimeout(()=>{
    showBall(winBalls[i], $result, color[Math.floor(winBalls[i]/10)]);
  }, (i+1)*1000);
}
// 보너스 뽑기
setTimeout(()=>{
  showBall(bonus, $bonus, color[Math.floor(bonus/10)]);
}, 7000);

아까 winBalls 배열에 6개의 숫자를 랜덤하게 담았으므로 여기서 뽑아주고, bonus는 따로 const bonus에 저장해둔 것을 이용한다.

 

setTimeOut 함수는 비동기 함수로, setTimeOut(()=>{function}, time); 에서 time 밀리초 이후에 function 작업을 실시할 수 있게 해준다. 밀리초 단위임에 주의하자. 참고로 비동기 함수란, 코드가 위에서부터 아래로 순서대로 진행되는 것이 아닌, 다른 순서로 진행되게 하는 함수임을 의미한다. (정확한 의미는 아니지만, 이해하기 쉽게 말했다.)

 

추첨결과에 해당되는 6개의 볼을 뽑고 나서 보너스를 뽑아주므로 보너스는 7000밀리초 이후에 진행되게 하였다.

 

색깔 지정은 color[Math.floor(숫자/10)]로 해주었는데 혹시 잘 이해가 되는가? 

아까 color 배열을 다시 한 번 살펴보자.

const color=['red', 'orange', 'yellow', 'blue', 'green'];

0번째 인덱스: (1~9의 숫자는 red),

1번째 인덱스: (10~19 숫자는 orange), ... 와 같이 담아주었다.

즉, (볼에 해당하는 숫자/10)에서 소수점을 버린 숫자를 인덱스로 담고 있는 color 배열이기 때문에 위와 같이 축약해서 코드를 작성할 수 있었던 것이다.

어렵다면 다른 방법으로, if문으로 if(number<10) color='red'와 같이 진행해도 상관없다.

 

setTimeOut에 적힌 snowBall함수를 이제 살펴볼 것이다. snowBall 함수는 공을 그려주는 작업을 해주는 함수이다.

const showBall=(number, $target, ballColor)=>{
  const $ball=document.createElement('div');
  $ball.className='ball';
  $ball.textContent=number;
  $ball.style.backgroundColor=ballColor;
  $target.append($ball);
}

createElement로 <div class='ball'></div> 코드를 생성해준다.

 

textContent로 공의 값을 적어준다. 위에서 ball 클래스에 해당하는 css 작업을 해주었기 때문에 textContent로 공의 값을 작성하기만 해도 주변에 원 둘레가 생겨나면서 공처럼 보이게 해준다.

 

$ball.style.backgroundColor 로 공의 색깔을 지정해준다. 1~9는 빨간색, 10~19는 노란색, ... 으로 변환해주는 작업을 아까 위에서 해주었기 때문에 우리는 ballColor를 대입해주기만 하면 된다.

 

또한, 여기서 $target이란, 추첨 결과 6개에 해당되는 볼 태그인지, 보너스 볼 태그에 그려줄 것인지 선택해주는 것이다.

여기까지 하면 완성!

 

 

사진 첨부한 것이라 1초 간격으로 진행되는 게 안보이긴 하다.

실제로는 추첨 결과가 1초 텀을 두고 하나씩 뜬다.


전체 코드는 아래 깃헙주소에서 볼 수 있다.

https://github.com/kth990303/TH-s-Web/blob/master/es2021/lotto.html

 

GitHub - kth990303/TH-s-Web: 웹프로그래밍 공부

웹프로그래밍 공부. Contribute to kth990303/TH-s-Web development by creating an account on GitHub.

github.com

간단한 로또추첨기이지만,

직접 만들어보면서 하지 않으면 입문자에겐 이해가 어려울 수 있다.

반드시 직접 만들어보면서 감을 익혀보자.

 

나도 이번 기회에 slice, splice, map을 활용해볼 수 있어서 좋은 시간이었던 것 같다.


도움 받은 곳: 조현영님의 ES2021 강좌, 모질라 공식 자바스크립트 문서

 

반응형