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

[로또] 박소현 미션 제출합니다. #640

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Binary file added .DS_Store
Binary file not shown.
81 changes: 81 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# 💎 로또 💎



## ✔︎ 기능 목록
* [E] : 예외 처리 -> throw 문 사용

ex) [ERROR] 숫자가 잘못된 형식입니다.

### (1) 📝 로또 구입 금액 입력 (1,000원 단위)
> [E] 숫자가 아닌 경우
[E] 1,000원 단위가 아닌 경우

* ex)

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

### (2) 🟡 구매 로또 번호 출력
> (i) 로또 (구입 금액 / 1000)개 출력
(ii) 로또 1개당 랜덤 숫자 6개씩 출력 (중복 X, 숫자 범위 1 ~ 45)
(iii) 오름차순으로 정렬 후 출력

* ex)

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

### (3) 🟢 당첨 번호 입력 (,로 구분)
> [E] 숫자가 아닌 경우
[E] 숫자가 6개가 아닌 경우
[E] 숫자 범위가 1 ~ 45 가 아닌 경우
[E] 중복된 숫자가 있는 경우

* ex)

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

### (4) +🟢 보너스 번호 입력
> [E] 숫자가 아닌 경우
[E] 숫자 범위가 1 ~ 45 가 아닌 경우
[E] 당첨 번호(3)와 중복된 숫자가 있는 경우

* ex)

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

### (5) 📝 당첨 내역 출력
> (i) 3개 일치 -> 5,000원
(ii) 4개 일치 -> 50,000원

> (iii) 5개 일치
>> 나머지 1개가 보너스 번호가 아닌 경우 -> 1,500,000원
>> 나머지 1개가 보너스 번호인 경우 -> 30,000,000원

> (iv) 6개 일치 -> 2,000,000,000원

* ex)

당첨 통계
---
3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개

### (6) 📝 수익률 출력
> (i) (총 당첨 금액 / 구입 금액) 출력
(ii) 소수점 둘째 자리에서 반올림
(iii) 천 단위로 , 표시 (ex. 2,000,000%)

* ex)

총 수익률은 100.0%입니다.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"devDependencies": {
"@babel/core": "^7.23.0",
"@babel/preset-env": "^7.22.20",
"@babel/preset-env": "^7.23.2",
"babel-jest": "^29.7.0",
"jest": "29.6.0"
},
Expand Down
97 changes: 96 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,100 @@
import { MissionUtils } from '@woowacourse/mission-utils';
import inputView from './InputView.js';
import validation from './validation.js';
import Lotto from './Lotto.js';
import { LOTTO } from './constant.js';
import outputView from './OutputView.js';

class App {
async play() {}
async play() {
this.gameStart();
}

async gameStart() {
const purchasePrice = await inputView.purchaseInput();
validation.checkPurchasePrice(purchasePrice);

return(this.pickLottoNum(purchasePrice));
}

pickLottoNum(purchasePrice){
const lottos = [];
const lottoCount = purchasePrice / LOTTO.UNIT;

while (lottos.length < lottoCount) {
const lottoNums = arraySort(this.pickNum());
lottos.push(new Lotto(lottoNums));
}

outputView.printLottoCount(lottoCount);
lottos.forEach((lotto) => outputView.printLottoNum(lotto));

return this.checkWinningNum(lottos);
}

static arraySort(arr) {
return [...arr].sort((a, b) => a - b);
}

pickNum() {
const lottoNum = MissionUtils.Random.pickUniqueNumbersInRange(LOTTO.MIN_RANGE, LOTTO.MAX_RANGE, LOTTO.LENGTH);
return lottoNum;
}

async checkWinningNum(lottos) {
const winningNum = await inputView.winningNumInput();
const winningNums = winningNum.split(',');
validation.checkWinningNum(winningNums);

const bonusNum = await inputView.bonusNumInput();
validation.checkBonusNum(winningNums, bonusNum);

return this.countLottoResult({lottos, winningNums, bonusNum});
}

countLottoResult({lottos, winningNums, bonusNum}) {
const matchScore = [];
const hasBonusNum = [];

lottos.forEach((lotto) => {
matchScore.push(lotto.getMatchCount(winningNums));
if (lotto.hasBonusNumber(bonusNum))
hasBonusNum.push(true);
else
hasBonusNum.push(false);
});

return this.countLottoRank({matchScore, hasBonusNum});
}

countLottoRank({matchScore, hasBonusNum}) {
const rank = [0, 0, 0, 0, 0, 0];
for (let i = 0; i < matchScore.length; i++) {
if (matchScore[i] === 6) rank[1] += 1;
else if (matchScore[i] === 5 && hasBonusNum[i]) {
rank[2] += 1;
} else if (matchScore[i] === 5 && !hasBonusNum[i]) {
rank[3] += 1;
} else if (matchScore[i] === 4) {
rank[4] += 1;
} else if (matchScore[i] === 3) {
rank[5] += 1;
}
}
return this.calculateWinnings(rank);
}

calculateWinnings(rank) {
let totalWin = 0;
totalWin += rank[1] * LOTTO.FIRST_PRIZE;
totalWin += rank[2] * LOTTO.SECOND_PRIZE;
totalWin += rank[3] * LOTTO.THIRD_PRIZE;
totalWin += rank[4] * LOTTO.FOURTH_PRIZE;
totalWin += rank[5] * LOTTO.FIFTH_PRIZE;
return this.calculateRateOfReturn({rank, totalWin});
}


}

export default App;
21 changes: 21 additions & 0 deletions src/InputView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MissionUtils } from '@woowacourse/mission-utils';
import { INPUT_MSG } from './constant.js';

const inputView = {
async purchaseInput() {
const inputPurchase = await MissionUtils.Console.readLineAsync(INPUT_MSG.PURCHASE);
return inputPurchase;
},

async winningNumInput() {
const inputwinningNum = await MissionUtils.Console.readLineAsync(INPUT_MSG.WINNING_NUM);
return inputwinningNum;
},

async bonusNumInput() {
const inputbonusNum = await MissionUtils.Console.readLineAsync(INPUT_MSG.BONUS_NUM);
return inputbonusNum;
},
};

export default inputView;
22 changes: 17 additions & 5 deletions src/Lotto.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { ERROR_MSG } from './constant.js';
import validation from './validation.js';

class Lotto {
#numbers;

Expand All @@ -7,12 +10,21 @@ class Lotto {
}

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

getMatchCount(winningNum) {
let matchCount = 0;
winningNum.forEach((num) => {
if (this.#numbers.includes(num))
matchCount += 1;
});
return matchCount;
}

// TODO: 추가 기능 구현
hasBonusNumber(bonusNum) {
return this.#numbers.includes(bonusNum);
}
}

export default Lotto;
export default Lotto;
14 changes: 14 additions & 0 deletions src/OutputView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { MissionUtils } from '@woowacourse/mission-utils';
import { LOTTO, OUTPUT_MSG } from './constant.js';

const outputView = {
printLottoCount(lottoCount) {
MissionUtils.Console.print(OUTPUT_MSG.PURCHASE(lottoCount));
},

printLottoNum(numbers) {
MissionUtils.Console.print(`[${numbers.join(', ')}]`);
},
};

export default outputView;
40 changes: 40 additions & 0 deletions src/constant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export const LOTTO = {
UNIT: 1000,
LENGTH: 6,
MIN_RANGE: 1,
MAX_RANGE: 45,

FIFTH_PRIZE: 5000,
FOURTH_PRIZE: 50000,
THIRD_PRIZE: 1500000,
SECOND_PRIZE: 30000000,
FIRST_PRIZE: 2000000000,
}

export const INPUT_MSG = {
PURCHASE: '구입금액을 입력해 주세요.\n',
WINNING_NUM: '당첨 번호를 입력해 주세요.\n',
BONUS_NUM: '보너스 번호를 입력해 주세요.\n',
}

export const OUTPUT_MSG = {
PURCHASE: (ea) => `${ea}개를 구매했습니다.\n`,

TOTAL_WIN: '당첨 통계\n---\n',
FIFTH: '3개 일치 (5,000원) - ',
FOURTH: '4개 일치 (50,000원) - ',
THIRD: '5개 일치 (1,500,000원) - ',
SECOND: '5개 일치, 보너스 볼 일치 (30,000,000원) - ',
FIRST: '6개 일치 (2,000,000,000원) - ',
EA: '개\n',

TOTAL_RETURN: (rate) => `총 수익률은 ${rate}%입니다.\n`,
}

export const ERROR_MSG = {
INPUT_NAN: '[ERROR] 입력된 숫자가 잘못된 형식입니다.',
NUM_UNIT: '[ERROR] 1,000원 단위로만 입력 가능합니다.',
NUM_RANGE: '[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.',
NUM_LENGTH: '[ERROR] 로또 번호는 6개여야 합니다.',
NUM_DUPE: '[ERROR] 중복된 숫자는 입력할 수 없습니다.',
};
60 changes: 60 additions & 0 deletions src/validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { LOTTO, ERROR_MSG } from './constant.js';

const validation = {
checkPurchasePrice(input) {
if (isNaN(input))
throw new Error(ERROR_MSG.INPUT_NAN);

if (input / LOTTO.UNIT !== 0)
throw new Error(ERROR_MSG.NUM_UNIT);

return 0;
},

checkLottoNum(input) {
input.forEach((num) => {
if (num < LOTTO.MIN_RANGE || num > LOTTO.MAX_RANGE)
throw new Error(ERROR_MSG.NUM_RANGE);
});
if (input.length !== LOTTO.LENGTH)
throw new Error(ERROR_MSG.NUM_LENGTH);

if (new Set(input).size !== input.length)
throw new Error(ERROR_MSG.NUM_DUPE);

return 0;
},

checkWinningNum(input) {
input.forEach((num) => {
if (isNaN(num))
throw new Error(ERROR_MSG.INPUT_NAN);

if (num < LOTTO.MIN_RANGE || num > LOTTO.MAX_RANGE)
throw new Error(ERROR_MSG.NUM_RANGE);
});
if (input.length !== LOTTO.LENGTH)
throw new Error(ERROR_MSG.NUM_LENGTH);

if (new Set(input).size !== input.length)
throw new Error(ERROR_MSG.NUM_DUPE);

return 0;
},

checkBonusNum(winning, bonus) {
if (isNaN(bonus))
throw new Error(ERROR_MSG.INPUT_NAN);

if (bonus < LOTTO.MIN_RANGE || bonus > LOTTO.MAX_RANGE)
throw new Error(ERROR_MSG.NUM_RANGE);

const arr = winning.push(bonus);
if (new Set(arr).size !== arr.length)
throw new Error(ERROR_MSG.NUM_DUPE);

return 0;
},
};

export default validation;