Skip to content
Open
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;