Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[로또] 강지유 미션 제출합니다. #390

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
223 changes: 223 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,224 @@
# javascript-lotto-precourse

## 구현할 기능 목록

1. 사용자 입력 처리
- 구입 금액
- 당첨 번호
- 보너스 번호
2. 로또 생성
- 구입 금액에 따라 구매 가능한 로또의 개수 계산
- 로또 개수에 따라 로또 번호 자동 생성, 오름차순으로 정렬
3. 당첨 확인
- 구매한 로또와 당첨 번호 비교
- 일치하는 번호 개수 확인
- 보너스 번호 일치 여부 확인
- 당첨 등수 판정
4. 결과 출력
- 당첨 내역 출력(각 등수별 당첨 개수)
- 수익률 계산

## 예외 케이스

### 구입금액

1. 숫자가 아닐 때
2. 실수일 때
3. 공백일 때
4. 1000원으로 나누어 떨어지지 않을 때

### 로또 번호

1. 숫자가 아닐 때
2. 실수일 때
3. 공백일 때
4. 1 ~ 45번까지의 숫자가 아닐 때
5. 중복 번호 없어야함

---

---

## 기능 요구 사항

간단한 로또 발매기를 구현한다.

- 로또 번호의 숫자 범위는 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원이다.
- 당첨 번호와 보너스 번호를 입력받는다.
- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
- 사용자가 잘못된 값을 입력할 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 해당 지점부터 다시 입력을 받는다.

### 입출력 요구 사항

#### 입력

- 로또 구입 금액을 입력 받는다. 구입 금액은 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다.

```
14000
```

- 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다.

```
1,2,3,4,5,6
```

- 보너스 번호를 입력 받는다.

```
7
```

#### 출력

- 발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다.

```
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%, 1,000,000.0%)

```
총 수익률은 62.5%입니다.
```

- 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 "[ERROR]"로 시작해야 한다.

```
[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.
```

#### 실행 결과 예시

```
구입금액을 입력해 주세요.
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

- Node.js 20.17.0 버전에서 실행 가능해야 한다.
- 프로그램 실행의 시작점은 App.js의 run()이다.
- package.json 파일은 변경할 수 없으며, 제공된 라이브러리와 스타일 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.
- 프로그램 종료 시 process.exit()를 호출하지 않는다.
- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다.
- 자바스크립트 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 JavaScript Style Guide를 원칙으로 한다.

## 프로그래밍 요구 사항 2

- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 3항 연산자를 쓰지 않는다.
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- Jest를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다.
- 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다.
- Using Matchers
- Testing Asynchronous Code
- Jest로 파라미터화 테스트하기: test.each(), describe.each()

## 프로그래밍 요구 사항 3

- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
- 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
- else를 지양한다.
- 때로는 if/else, when문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- 구현한 기능에 대한 단위 테스트를 작성한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
- 단위 테스트 작성이 익숙하지 않다면 LottoTest를 참고하여 학습한 후 테스트를 작성한다.

### 라이브러리

- @woowacourse/mission-utils에서 제공하는 Random 및 Console API를 사용하여 구현해야 한다.
- Random 값 추출은 Random. pickUniqueNumbersInRange()를 활용한다.
- 사용자의 값을 입력 및 출력하려면 Console.readLineAsync()와 Console.print()를 활용한다.

#### 사용 예시

- 1에서 45 사이의 중복되지 않은 정수 6개 반환

```
MissionUtils.Random.pickUniqueNumbersInRange(1, 45, 6);
```

### Lotto 클래스

- 제공된 Lotto 클래스를 사용하여 구현해야 한다.
- Lotto에 numbers 이외의 필드(인스턴스 변수)를 추가할 수 없다.
- numbers의 접근 제어자인 #은 변경할 수 없다.
- Lotto의 패키지를 변경할 수 있다.

```
class Lotto {
#numbers;

constructor(numbers) {
this.#validate(numbers);
this.#numbers = numbers;
}

#validate(numbers) {
if (numbers.length !== 6) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
}
}

// TODO: 추가 기능 구현
}
```
46 changes: 45 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
import LottoChecker from "./LottoChecker.js";
import LottoCreator from "./LottoCreator.js";
import OutputView from "./OutputView.js";
import UserInput from "./UserInput.js";
import { MissionUtils } from "@woowacourse/mission-utils";

class App {
async run() {}
#userInput;
#lottoCreator;
#lottoChecker;
#outputView;

constructor() {
this.#userInput = new UserInput();
this.#lottoCreator = new LottoCreator();
this.#lottoChecker = new LottoChecker();
this.#outputView = new OutputView();
}

async run() {
const amount = await this.#userInput.getUserInput("purchaseAmount");
const lottoCount = this.#lottoCreator.getLottoCount(amount);

MissionUtils.Console.print(`\n${lottoCount}개를 구매했습니다.`);

const lottos = this.#lottoCreator.createLotto(lottoCount);
this.#lottoCreator.printLottos(lottos);

const winningNumbers = (await this.#userInput.getUserInput("winningNumber"))
.split(",")
.map((number) => Number(number.trim()));
const bonusNumber = Number(
await this.#userInput.getUserInput("bonusNumber")
);

const checkedLottos = this.#lottoChecker.checkWinning(
lottos,
winningNumbers,
bonusNumber
);

this.#outputView.printWinningStatistics(checkedLottos);

const totalPrize = this.#lottoChecker.calculateTotalPrize();
this.#outputView.printProfitRate(totalPrize, lottoCount);
}
}

export default App;
14 changes: 12 additions & 2 deletions src/Lotto.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@ class Lotto {

constructor(numbers) {
this.#validate(numbers);
this.#numbers = numbers;
this.#numbers = numbers.sort((a, b) => a - b);
}

#validate(numbers) {
if (numbers.length !== 6) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
}

if (!numbers.every((number) => number >= 1 && number <= 45)) {
throw new Error("[ERROR] 로또 번호는 1 ~ 45 사이의 숫자여야 합니다.");
}

if (new Set(numbers).size !== 6) {
throw new Error("[ERROR] 로또 번호는 중복되지 않아야 합니다.");
}
}

// TODO: 추가 기능 구현
getNumbers() {
return [...this.#numbers];
}
}

export default Lotto;
46 changes: 46 additions & 0 deletions src/LottoChecker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
class LottoChecker {
#winningResult;

constructor() {
this.#winningResult = {
3: { prize: 5000, count: 0 },
4: { prize: 50000, count: 0 },
5: { prize: 1500000, count: 0 },
5.5: { prize: 30000000, count: 0 },
6: { prize: 2000000000, count: 0 },
};
}

checkWinning(lottos, winningNumber, bonusNumber) {
lottos.forEach((lotto) => {
const matchCount = this.#countMatches(lotto.getNumbers(), winningNumber);
this.#countPrize(matchCount, lotto.getNumbers(), bonusNumber);
});
return this.#winningResult;
}

#countMatches(lottoNumbers, winningNumbers) {
return lottoNumbers.filter((number) => winningNumbers.includes(number))
.length;
}

#countPrize(matchCount, lottoNumbers, bonusNumber) {
if (matchCount === 5 && lottoNumbers.includes(bonusNumber)) {
this.#winningResult[5.5].count += 1;
return;
}

if (this.#winningResult[matchCount]) {
this.#winningResult[matchCount].count += 1;
}
}

calculateTotalPrize() {
return Object.values(this.#winningResult).reduce(
(total, { prize, count }) => total + prize * count,
0
);
}
}

export default LottoChecker;
31 changes: 31 additions & 0 deletions src/LottoCreator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { MissionUtils } from "@woowacourse/mission-utils";
import Lotto from "./Lotto.js";

const LOTTO_PRICE = 1000;

class LottoCreator {
getLottoCount(amount) {
return Math.floor(amount / LOTTO_PRICE);
}

createLotto(lottoCount) {
const lottos = [];
for (let i = 0; i < lottoCount; i++) {
const lottoNumber = MissionUtils.Random.pickUniqueNumbersInRange(
1,
45,
6
);
lottos.push(new Lotto(lottoNumber));
}
return lottos;
}

printLottos(lottos) {
lottos.forEach((lotto) => {
MissionUtils.Console.print(lotto.getNumbers());
});
}
}

export default LottoCreator;
Loading