Skip to content
Open
125 changes: 125 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# 로또

> 본 미션은 하단과 같은 흐름을 가지는 로또 구매부터 결과 출력까지의 과정을 프로그래밍 하는 것이다.

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

## 구현할 기능 목록 순서

```
- 로또 구입을 위한 금액을 입력 받는다.
- 구매한 로또 개수를 출력한다.
- 구매 개수만큼 n개의 유한한 자동 로또를 생성한다.
- 당첨번호 6개를 입력한다.
- 보너스 번호를 입력한다.
- 구매한 로또 번호들과 당첨 번호들을 비교한다.
- 당첨(등수) 결과를 얻는다.
- 당첨 결과와 받은 금액을 통해 수익률을 얻는다.
- 당첨 결과들과 수익률을 출력한다.
```

## 역할에 따른 기능

```
⚠️ 기존에 알고 있는 복권사업 도메인과 다를 수 있음.

* 로또구매자
- 로또를 사기 위해 돈을 지불한다.
- 발행 결과를 듣는다.
- 당첨 번호를 듣는다.
- 당첨 통계를 듣는다.

* 로또판매자
- 구매자의 돈을 받는다.
- 금액 판별기에 돈을 입력한다.
- 금액 판별기를 통해 발행 가능 여부를 얻는다.
- 발행이 가능하면 자동 로또번호 생성기에 발행 개수 n개를 입력한다.
- 발행 불가능하면, 로또를 발행하지 않는다.
- 자동 로또번호 생성기로 발행된 n개의 번호들을 기록장치에 기록한다.
- 구매자에게 발행된 개수와 번호들을 알린다.
- 로또운영위의 당첨번호를 기다린다.
- 구매자에게 당첨번호를 알린다.
- 당첨번호를 수령하면, 결과 판별기에게 당첨번호를 입력한다.
- 결과 판별기로부터 결과를 얻는다.
- 받은 금액과 판별 결과와 결과 분석기에 입력한다.
- 결과 분석 완료를 듣는다.
- 최종 결과를 구매자에게 알린다.

* 금액판별기
- 금액을 입력받는다.
- 유효한 금액인지 확인한다.
- 유효한 금액이면 발행 가능 개수를 판매자에게 알린다.
- 유효하지 않은 금액이면 발행 불가능함을 판매자에게 알린다.
* 자동 로또번호 생성기
- 발행 개수를 입력받는다.
- 중복되지 않는 6개의 숫자를 생성한다. (1 ~ 45)

* 기록 장치
- 발행된 로또 개수(n)와(과) 각 로또 번호들을 기록한다.
- 결과판별기가 발행 개수와 로또 번호를 요청하면 이를 제공한다.
- 결과판별기가 당첨 결과를 도출하면 이를 기록한다.
- 결과분석기로부터 수익률을 받으면 이를 기록한다.

* 로또운영위
- 당첨 번호를 만든다.
- 6개의 숫자를 입력한다. (1 ~ 45)
- 보너스 숫자를 1개 입력한다. (1 ~ 45)
- 당첨 번호의 유효성을 확인한다.
- 유효한 당첨 번호이면 공개한다.

* 결과판별기
- 당첨 번호를 얻는다.
- 기록 장치에 발행 개수와 각 번호들을 요청한다.
- 당첨 번호와 생성 번호를 비교한다.
- 판별 결과를 도출한다.
- 판별 결과를 판매자에게 알린다.

* 결과분석기
- 판매자에게 금액과 결과지를 받는다.
- 최종 결과에 대한 수익률을 계산한다. (소수점 둘째 자리에서 반올림)
- 기록장치에 수익률을 알린다.
- 판매자에게 작업완료를 알린다.
```

## 예외처리 상황

```
- 구매자가 금액을 지불할 때, 1000으로 나누어 떨어지지 않으면, 예외처리한다.
- 구매자가 금액을 지불할 때, 양수가 아니면 예외처리한다.
- 구매자가 금액을 지불할 때, Number.MAX_VALUE 보다 큰 입력이면 예외처리한다.
- 당첨번호를 입력할 때, 중복된 숫자를 입력하면 예외처리한다.
- 당첨번호를 입력할 때, 6개 숫자를 입력하지 않으면 예외처리한다.
- 당첨번호를 입력할 때, 1 ~ 45 사이의 수가 아니면 예외처리한다.
- 당첨번호를 입력할 때, 구분자로 ,가 입력되지 않으면 예외처리한다.
- 보너스번호를 입력할 때, 중복된 숫자를 입력하면 예외처리한다.
- 보너스번호를 입력할 때, 1개의 숫자를 입력하지 않으면 예외처리한다.
- 보너스번호를 입력할 때, 1 ~ 45 사이의 수가 아니면 예외처리한다.
```
9 changes: 8 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import LottoFlow from './controller/LottoFlow.js';

class App {
async play() {}
#lottoFlow;

async play() {
this.#lottoFlow = new LottoFlow();
await this.#lottoFlow.makeLotto();
}
}

export default App;
21 changes: 18 additions & 3 deletions src/Lotto.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
class Lotto {
#numbers;

constructor(numbers) {
#bonusNumber;

#ticketList;

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

#validate(numbers) {
if (numbers.length !== 6) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
throw new Error('[ERROR] 로또 번호는 6개여야 합니다.');
}
const check = new Set(numbers);
if ([...check].length !== 6) {
throw new Error('[ERROR] 로또 번호는 중복이 불가능 합니다.');
}
}

// TODO: 추가 기능 구현
confirmNumber(bonusNumber) {
if (bonusNumber < 1 || bonusNumber > 45) {
throw new Error('[ERROR] 로또 번호는 1에서 45 사이 숫자만 가능합니다.');
}

this.#bonusNumber = bonusNumber;
}
}

export default Lotto;
41 changes: 41 additions & 0 deletions src/controller/LottoFlow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import InputView from '../views/InputView.js';
import OutputView from '../views/OutputView.js';
import Lotto from '../Lotto.js';
import Seller from '../models/Seller.js';
import ResultAnalyzer from '../models/ResultAnalyzer.js';

class LottoFlow {
#seller;

#lotto;

#resultAnalizer;

constructor() {
this.#seller = new Seller();
}

async makeLotto() {
const money = await InputView.InputMoney();
this.#seller.setAmount(money);
const { winningNumber, bonusNumber, ticketList } =
await this.#seller.makeWinnigNumber();
await this.processLotto(winningNumber, bonusNumber, ticketList);
}
async processLotto(winningNumber, bonusNumber, ticketList) {
this.#lotto = new Lotto(winningNumber, ticketList);
await this.#lotto.confirmNumber(bonusNumber);
this.endLotto(winningNumber, bonusNumber, ticketList);
}
endLotto(winningNumber, bonusNumber, ticketList) {
this.#resultAnalizer = new ResultAnalyzer(
winningNumber,
bonusNumber,
ticketList,
);
const result = this.#resultAnalizer.findResult();
OutputView.printResult(result);
}
}

export default LottoFlow;
19 changes: 19 additions & 0 deletions src/models/LottoMachine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Random } from '@woowacourse/mission-utils';

class LottoMachine {
constructor() {
this.ticket = [];
}

makeTicket() {
while (this.ticket.length !== 6) {
const number = Random.pickNumberInRange(1, 45);
this.ticket.push(number);
const validTicket = new Set(this.ticket);
this.ticket = [...validTicket];
}
return this.ticket;
}
}

export default LottoMachine;
70 changes: 70 additions & 0 deletions src/models/ResultAnalyzer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
class ResultAnalyzer {
#winningNumber;

#bonusNumber;

#ticketList;

#prize;

#profit;

#reward;

constructor(winningNumber, bonusNumber, ticketList) {
this.#winningNumber = winningNumber;
this.#bonusNumber = bonusNumber;
this.#ticketList = ticketList;
this.#prize = {
3: 0,
4: 0,
5: 0,
bonus: 0,
6: 0,
};
this.#reward = [5000, 50000, 1500000, 30000000, 2000000000];
this.#profit = 0;
}

findResult() {
this.#ticketList.forEach((ticket) => {
this.countCorrect(ticket);
});
this.calculateProfit();
const result = {
prize: this.#prize,
profit: this.#profit,
};
return result;
}

countCorrect(ticket) {
let count = 0;
ticket.forEach((number) => {
if (this.#winningNumber.includes(number)) {
count += 1;
}
});
if (count === 5 && ticket.includes(this.#bonusNumber)) {
this.#prize.bonus += 1;
}
if (count > 2) {
this.#prize[count] += 1;
}
}

calculateProfit() {
const prizeNumber = Object.values(this.#prize);
const profit = prizeNumber.map(
(number, index) => number * this.#reward[index],
);
const sum = profit.reduce(
(accumulator, currentValue) => accumulator + currentValue,
0,
);
const pay = this.#ticketList.length * 1000;
this.#profit = ((sum / pay) * 100).toFixed(1);
}
}

export default ResultAnalyzer;
44 changes: 44 additions & 0 deletions src/models/Seller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import InputView from '../views/InputView.js';
import OutputView from '../views/OutputView.js';
import LottoMachine from './LottoMachine.js';

class Seller {
#lottoMachine;

#winningNumber;

#bonusNumber;

constructor() {
this.ticketList = [];
this.result = [];
}

setAmount(money) {
const amount = money / 1000;
OutputView.printAmount(amount);
this.setLottoTicket(amount);
}

setLottoTicket(amount) {
for (let order = 0; order < amount; order += 1) {
this.#lottoMachine = new LottoMachine();
const ticket = this.#lottoMachine.makeTicket();
this.ticketList.push(ticket);
}
OutputView.printTickets(this.ticketList);
}

async makeWinnigNumber() {
this.#winningNumber = await InputView.InputWinningNumber();
this.#bonusNumber = await InputView.InputBonusNumber();
const NumberSet = {
winningNumber: this.#winningNumber,
bonusNumber: this.#bonusNumber,
ticketList: this.ticketList,
};
return NumberSet;
}
}

export default Seller;
Empty file added src/test.js
Empty file.
9 changes: 9 additions & 0 deletions src/utils/validate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const validate = {
isValidateMoney(money) {
if (money % 1000 !== 0) {
throw new Error('[ERROR] 잘못된 입력입니다.');
}
},
};

export default validate;
30 changes: 30 additions & 0 deletions src/views/InputView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Console } from '@woowacourse/mission-utils';
import validate from '../utils/validate.js';

const InputView = {
async InputMoney() {
const money = parseInt(
await Console.readLineAsync('구매금액을 입력해 주세요.\n'),
10,
);
validate.isValidateMoney(money);
return money;
},
async InputWinningNumber() {
const winningInput = await Console.readLineAsync(
'\n당첨 번호를 입력해 주세요.\n',
);
const winningArray = winningInput.split(',');
const winningNumber = winningArray.map((number) => parseInt(number, 10));
return winningNumber;
},
async InputBonusNumber() {
const bonusNumber = parseInt(
await Console.readLineAsync('\n보너스 번호를 입력해 주세요.\n'),
10,
);
return bonusNumber;
},
};

export default InputView;
Loading