Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 232 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,233 @@
# javascript-lotto-precourse

---

## 기능 요구 사항

#### 간단한 로또 발매기 구현

- 로또 번호의 숫자 범위 1~45
- 1개의 로또를 발행할 때 중복되지 않는 숫자 6개를 뽑는다.
- 당첨 번호 추첨 시 중복되지 않는 수자 6개와 보너스 번호 1개를 뽑는다. (총 7개)
- 당첨은 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%입니다.
```

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

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

## 프로그래밍 요구사항 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개 반환

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

#### Lotto 클래스

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

```javascript
class Lotto {
#numbers;
constructor(numbers) {
this.#validate(numbers);
this.#numbers = numbers;
}

#validate(numbers) {
if (numbers.length !== 6) {
throw new Error('[ERROR] 로또 번호는 6개여야 합니다.');
}
}
//TODO: 추가 기능 구현
}
```

## 💻 구현 기능 목록

#### 입력

- 로또 구매 금액
- 당첨 번호 6개 (쉼표 구분)
- 보너스 번호 1개

#### 입력 검증

###### 구매 금액 확인

- 숫자 확인, 아닌 경우 예외 처리
- 1,000원 단위 확인, 아닌 경우 예외 처리

###### 당첨 번호 확인

- 숫자 범위 확인 (1~45)
- 예외 처리 ❌
- 입력값이 없는 경우 (빈 배열)
- 숫자가 아닌 경우
- 숫자 6개 보다 적거나 많은 경우
- 6개 숫자의 중복 여부 확인

###### 보너스 번호 확인

- 숫자 범위 확인 (1~45)
- 예외 처리 ❌
- 입력값이 없는 경우
- 숫자가 아닌 경우
- 번호가 2개 이상일 경우

###### ❓

```
- 당첨 번호 추첨 시 당첨 번호와 보너스 번호는 중복되지 않아야하는데, 입력값도 그래야 하나?
- 그럴 경우 당첨번호와 보너스 번호의 중복 여부 추가 검증
- 당첨 번호 6개와 보너스 번호 중복 여부 확인, 중복일 경우 예외 처리
```

#### 로또 로직

- 무작위로 로또 번호 6개 출력 (`Random.pickUniqueNumbersInRange()`)
- 보너스 번호 출력 (`Random.pickNumberInRange(n,m)`)
- 입력받은 번호와 발행된 번호 비교
- 당첨 여부 확인 로직
- 수익률 계산 로직

#### 출력

- 구입 금액
- 구매 내역 (갯수, 해당 로또 당첨 번호)
- 입력한 당첨 번호
- 보너스 입력 번호
- 당첨 통계
- 5등부터 1등 순으로 당첨 여부 확인 내역
- 총 수익률 (소수점 둘째 자리에서 반올림)
65 changes: 59 additions & 6 deletions __tests__/LottoTest.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,71 @@
import Lotto from "../src/Lotto";
import Lotto from '../src/Lotto';

describe("로또 클래스 테스트", () => {
test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => {
describe('로또 클래스 테스트', () => {
test('로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.', () => {
expect(() => {
new Lotto([1, 2, 3, 4, 5, 6, 7]);
}).toThrow("[ERROR]");
}).toThrow('[ERROR]');
});

// TODO: 테스트가 통과하도록 프로덕션 코드 구현
test("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.", () => {
test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => {
expect(() => {
new Lotto([1, 2, 3, 4, 5, 5]);
}).toThrow("[ERROR]");
}).toThrow('[ERROR]');
});

test('로또 번호의 개수가 6개보다 적으면 예외가 발생한다.', () => {
expect(() => {
new Lotto([1, 2, 3, 4, 5]);
}).toThrow('[ERROR]');
});

test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => {
expect(() => {
new Lotto([1, 2, 3, 4, 5, 5]);
}).toThrow('[ERROR]');
});

test('로또 번호에 숫자가 아닌 값이 있으면 예외가 발생한다.', () => {
expect(() => {
new Lotto([1, 2, 'a', 4, 5, 6]);
}).toThrow('[ERROR]');
});

test('로또 번호가 1 미만이거나 45 초과인 숫자가 있으면 예외가 발생한다.', () => {
expect(() => {
new Lotto([0, 2, 3, 4, 5, 6]);
}).toThrow('[ERROR]');
expect(() => {
new Lotto([1, 2, 3, 4, 5, 46]);
}).toThrow('[ERROR]');
});

test('로또 번호에 음수나 소수가 포함되면 예외가 발생한다.', () => {
expect(() => {
new Lotto([-1, 2, 3, 4, 5, 6]);
}).toThrow('[ERROR]');
expect(() => {
new Lotto([1.5, 2, 3, 4, 5, 6]);
}).toThrow('[ERROR]');
});

// TODO: 추가 기능 구현에 따른 테스트 코드 작성

test('유효한 6개의 번호로 생성된 경우 예외가 발생하지 않는다.', () => {
expect(() => {
new Lotto([1, 2, 3, 4, 5, 6]);
}).not.toThrow();
});

test('로또 번호는 오름차순으로 정렬되어 저장된다.', () => {
const lotto = new Lotto([6, 1, 3, 5, 2, 4]);
expect(lotto.getNumbers()).toEqual([1, 2, 3, 4, 5, 6]);
});

test('당첨 번호와 일치하는 개수를 반환한다.', () => {
const lotto = new Lotto([1, 2, 3, 4, 5, 6]);
const winningNumbers = [1, 3, 5, 7, 9, 11];
expect(lotto.countMatchingNumbers(winningNumbers)).toBe(3);
});
});
23 changes: 22 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
import LottoGame from './LottoGame.js';
import InputView from './views/InputView.js';
import OutputView from './views/OutputView.js';

class App {
async run() {}
async run() {
try {
const money = await InputView.readPurchaseAmount();
const game = new LottoGame(money);

OutputView.printLottoTickets(game.getTickets());

const winningNumbers = await InputView.readWinningNumbers();
const bonuseNumber = await InputView.readBonusNumber();

const results = game.checkResult(winningNumbers, bonuseNumber);
const profitRate = game.calculateProfitRate();

OutputView.printStatistics(results, profitRate);
} catch (error) {
OutputView.printError(error.message);
}
}
}

export default App;
25 changes: 18 additions & 7 deletions src/Lotto.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
import Validator from './utils/Validator.js';

class Lotto {
#numbers;

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

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

// }

// TODO: 추가 기능 구현
countMatchingNumbers(winningNumbers) {
return this.#numbers.filter((num) => winningNumbers.includes(num)).length;
}

matchBonusNumber(bonusNumber) {
return this.#numbers.includes(bonusNumber);
}

getNumbers() {
return [...this.#numbers];
}
}

export default Lotto;
Loading