본문 바로가기
JavaScript

[JavaScript] 우테코 로또 게임 만들기

by 도전하는 린치핀 2024. 1. 31.

본 과제는 우테코에서 프리코스에서 진행한 자바스크립트를 활용한 로또 게임 제작하는 프로그래밍 과제를 기반으로 진행하였습니다.

1. 과제 내용

 

과제는 우테코에서 진행한 자바스크립트를 활용하여 간단한 로또 게임을 만드는 프로그래밍 과제로 기능 자체는 간단했지만 아래의 요구사항을 따르며 프로그래밍을 진행해야 했다. 

특히 기능을 구현하기 전 기능 목록을 작성하고 기능 단위로 커밋을 하는 방식으로 진행해야 했다.

1-1. 기능 요구사항

  • 로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다.
    •  
- 로또 번호의 숫자 범위는 1~45까지이다.
- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
- 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다.
    - 1등: 6개 번호 일치 / 2,000,000,000원
    - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
    - 3등: 5개 번호 일치 / 1,500,000원
    - 4등: 4개 번호 일치 / 50,000원
    - 5등: 3개 번호 일치 / 5,000원

 

  • 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
  • 로또 1장의 가격은 1,000원이다.
  • 당첨 번호와 보너스 번호를 입력받는다.
  • 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
  • 사용자가 잘못된 값을 입력할 경우 throw문을 사용해 예외를 발생시킨다. 그런 다음, "[ERROR]"로 시작하는 에러 메시지를 출력하고 해당 부분부터 입력을 다시 받는다.
  • 로또 구입 금액은 1000원 단위로 입력 받으며 1000원으로 나누어 떨어지지 않는 경우 예외처리한다.
  • 당첨 번호를 입력받을 때 번호는 쉼표(,)를 기준으로 구분한다.
    • ex) 1,2,3,4,5,6
  • 보너스 번호를 입력받을 때 보너스 번호는 당첨 번호와 겹치지 않아야 한다.
  • 발행한 로또 수량 및 번호를 출력하며, 로또 번호는 오름차순으로 정렬하여 보여준다.
    • - 로또 번호의 숫자 범위는 1~45까지이다.
      - 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
      - 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
      - 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다.
          - 1등: 6개 번호 일치 / 2,000,000,000원
          - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
          - 3등: 5개 번호 일치 / 1,500,000원
          - 4등: 4개 번호 일치 / 50,000원
          - 5등: 3개 번호 일치 / 5,000원
    • 8개를 구매했습니다. [8, 21, 23, 41, 42, 43] [3, 5, 11, 16, 32, 38] [7, 11, 16, 35, 36, 44] [1, 8, 11, 31, 41, 42] [13, 14, 16, 38, 42, 45] [7, 11, 30, 40, 42, 43] [2, 13, 22, 32, 38, 45] [1, 3, 5, 14, 22, 45]
  • 당첨 내역을 출력한다.
    3개 일치 (5,000원) - 1개
    4개 일치 (50,000원) - 0개
    5개 일치 (1,500,000원) - 0개
    5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
    6개 일치 (2,000,000,000원) - 0개​
  • 수익률은 소수점 둘째 자리에서 반올림한다 (ex. 100.0%, 51.5%, 1000000.0%)
    총 수익률은 62.5%입니다.​
  • 예외 상황 시 에러 문구를 출력해야 한다. 단 에러 문구는 "[ERROR]"로 시작해야 한다.

 

 * 전체 실행 결과 예시

구입금액을 입력해 주세요.
8000

8개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[1, 3, 5, 14, 22, 45]

당첨 번호를 입력해 주세요.
1,2,3,4,5,6

보너스 번호를 입력해 주세요.
7

당첨 통계
---
3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
총 수익률은 62.5%입니다.

 

1-2. 프로그래밍 요구사항

  • 프로그램 실행의 시작점은 App.js의 play 메서드이다. 아래와 같이 프로그램을 실행시킬 수 있어야 한다.
  • 클래스를 최대한 활용하고, MVC 패턴을 통해 클래스별로 역할을 부여하고 나누어야한다.
    • 예를 들어 입력받는 클래스, 연산하는 클래스 등
  • 수업에 배운 문법 각각 한번은 꼭 사용해야한다.
    • 3가지 함수 작성방법, 실무에서 쓰는 유용한 ES6+ 문법들
  • 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다.
  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
    • 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
    • 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
  • 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
    • 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
  • else를 지양한다.
    • 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
    • 때로는 if/else, switch문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다.
  • 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(Console.readLineAsync, Console.print) 로직에 대한 단위 테스트는 제외한다.
    • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
    • 단위 테스트 작성이 익숙하지 않다면 __tests__/LottoTest.js를 참고하여 학습한 후 테스트를 구현한다.
  • @woowacourse/mission-utils에서 제공하는 Random  Console API를 사용하여 구현해야 한다.
    • Random 값 추출은 Random.pickUniqueNumbersInRange()를 활용한다.
    • 사용자의 값을 입력 받고 출력하기 위해서는 Console.readLineAsync, Console.print를 활용한다.

 

1-3. 과제 진행 요구사항

  • 기능을 구현하기 전 docs/README.md에 구현할 기능 목록을 정리해 추가한다.
  • Git의 커밋 단위는 앞 단계에서 docs/README.md에 정리한 기능 목록 단위로 추가한다.

 

2. 구현 과정

나 같은 경우에는 MVC 패턴을 생각해서 프로그래밍을 진행한 것이 아니라 전체적인 로직의 플로우를 따라가면서 스크립트를 분리하고 나누며 과제를 진행하였다.

따라서 먼저, 구현해야 하는 목록들을 로직의 순서대로 정리해보았다.

 

2-1. 구현 목록

-  구매 금액 입력
    - 숫자가 아닌 경우 예외처리
    - 1000원으로 나누어 떨어지지 않을 때 예외처리
    - 입력 값이 0보다 작으면 예외처리
- 입력값을 바탕으로 구매 개수 판단
- 로또 구매 개수 출력
- 구매 개수에 맞는 로또 발행
- 발행된 로또 번호 출력
- 당첨 번호 입력 받기
    - 숫자 아닌 경우 예외처리
    - 개수가 6개가 아닌 경우 예외처리
    - 숫자의 범위(1~45)가 아닌 경우 예외처리
    - 중복된 숫자가 있을 경우 예외처리
-  보너스 숫자 입력 받기
    - 숫자 아닌 경우
    - 숫자의 범위(1~45)가 아닌 경우 예외처리
    - 당첨 번호와 중복일 경우 예외처리
- 사용가에게 발행된 로또와 당첨 번호와 보너스 번호 비교
- 당첨 통계 출력
    - 각 등수별 당첨자 수 계산
    - 수익률 계산

2-2. 구현 순서

  1. 로또 게임에서 모든 로직을 관장하는 LottoController.js를 작업하여 기본적인 로직의 흐름을 작성하였다.
  2. 그 후 LottoController.js에서 사용하는 메서드를 작성하기 위해 InputView.js와 OutputView.js를 통해 입출력 데이터를 관리하였다.
  3.  입력받은 데이터를 저장하고 관리하기 위한 LottoData.js를 작성하여 기본적인 로직을 완성하였다.
  4. 이제 LottoGenerator.js를 통해 구매한 로또 번호를 랜덤으로 발행하는 로직을 완성하였다.
  5. 마지막으로 입력 받은 데이터를 CheckValidNumber.js 를 통해 유효성을 판단하는 로직을 완성하였다.

 

3. 사용한 JS 문법

해당 과제에서 핵심적인 기능을 구성하는 JS 문법(map, split, trim, Set, includes, filters 등)은 추가적인 포스팅에서 따로 작성하였다.

JS 자주 사용한 문법 포스팅 : https://rnclf1005.tistory.com/21

 

[ASAC_04/JavaScript] map / set / trim / includes / filter / reduce

1. map 활용 map() 메서드는 배열 각 요소에 대하여 주어진 함수를 수행한 결과를 모아 새로운 배열을 반환하는 메서드로 어떤 배열에 있는 모든 요소들의 값을 콜백 함수를 활용해 변경하여 만든

rnclf1005.tistory.com

 

4. 코드 리뷰 및 회고

 

사실 과제를 진행하고 기능적으로 모든 것이 완성되었다고 생각이 들었을 때는 기분이 굉굉장히히 좋았었다.

왜냐하면 프로그래밍 자체를 엄청 오랜만에 하는 것도 그렇고 자바스크립트를 처음 사용하는 상태에서 뭔가 그래도 기능적으로 모든 것을 완성했다는 것 자체가 뿌듯했던 것 같다.

 

하지만 코드 리뷰를 진행하고 나서는.......(꽤나 많이 두들겨 맞은 기분,...)

코드 리뷰에서 중점적으로 말한 것들은 객체 지향적인 프로그래밍을 하지 못한 부분들에 대해서 말씀해주셨다.

나는 처음 과제를 시작하면서 분명히 객체지향적인 무언가를 만들어야 겠다고 생각했었는데,,,,, 위에서 말한 것처럼 전체적인 로직의 플로우를 바탕으로 프로그래밍을 했다는 것 자체가 지금 생각해보면 절차적 프로그래밍이었다는 것을 깨달았다...

 

전체적으로 코드 리뷰에서 말씀하신 고쳐야 할 부분들은 아래와 같았다.

  1. 변수나 함수의 이름을 설정할 때 단수, 복수 구분을 명확히 해서 이름만으로 어떤 것을 의미하는지 알 수 있게 노력해야 한다.
  2. 어떤 로직을 작성할 때 기초를 준비하고 그곳에 하나씩 쌓기보다는 하나씩 모듈화하여 완성한 뒤 그것들을 이어 붙이는 것이 조금 더 객체 지향적인 프로그래밍이다.
    • 이러한 프로그래밍을 진행헀을 때 블랙박스 상태가 되고 테스트를 진행할 때 어려움을 겪을 수 있다. 
  3. 한 클래스 내 모든 데이터를 묶어 처리하기보다는 각 상태나 비즈니스 별로 클래스를 나누어 동작하는 것이 객체지향적인 프로그래밍이다.
  4. 또한 현재 과제에서는 컨트롤러 내부에서 모든 행위를 정의하고 동작하고 있지만 모든 행위들이 독립적으로 실행되고 정의되는 것이 아니라서 조금 아쉬웠다.
    • 위와 같은 프로그래밍을 진행했을 때 생길 수 있는 문제로는 행위를 나누어 유닛 테스트를 진행할 수 없다는 문제를 가진다.
    • 또한 어떤 한 함수에서 로직의 문제가 발생되었을 때 그것과 연결되 모든 함수들이 정상 작동하지 않는 문제가 있다.

 

결과적으로 모든 말들은 조금 더 객체지향적인 프로그래밍이 되어야 한다는 말이었다.

다음 프로그래밍 과제부터는 기능 단위로 커밋하는 습관을 들이고 이번 과제처럼 플로우를 작성 한 뒤 컨트롤러 내부에서 모든 동작을 정의한 뒤 프로그래밍을 시작하는 것이 아니라 모든 행위들을 하는 객체를 구분한 뒤 객체 단위로 프로그래밍을 진행하여 유기적으로 객체들을 연결하여 동작하는 프로그램을 만들어야 할 것 같다는 생각이 들었다...