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
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
# javascript-lotto-precourse
## 프로젝트 개요
- 사용자로부터 입력받은 구매 금액만큼 로또를 발행하여, 사용자가 입력한 당첨 번호 및 보너스 번호를 바탕으로 로또의 총 당첨 내역 및 수익률을 출력하는 프로젝트
## 서비스 프로세스 흐름
1. 사용자로부터 로또 구입 금액을 입력 받는다.
2. 그 금액만큼 로또를 발행한다.
3. 당첨 번호를 입력 받는다.
4. 보너스 번호를 입력 받는다.
5. 각 로또를 순회하며 당첨 내역을 기록한다.
6. 기록된 당첨 내역을 바탕으로 당첨 통계 및 수익률을 출력한다.
## 구현할 기능 목록
- [ ] 메인 함수(`App.run()`)
- [ ] `Lotto` 메서드
- `validate` 메서드 내용 확장
- [ ] 당첨 번호, 보너스 번호를 인자로 받아 비교 후 등수를 반환하는 메서드 (calculateRank)
- [ ] 로또 구입 금액을 입력받는 함수 (setupLottoBudget)
- 예외 케이스 1) 사용자가 정수를 입력하지 않는 경우
- 예외 케이스 2) 입력 받은 금액이 1,000원 단위로 나누어 떨어지지 않는 경우
- [ ] 중복되지 않는 숫자 6개를 생성해 로또를 발행하는 함수 (createLotto)
- [ ] 입력된 금액만큼 복권을 발행하는 함수 (purchaseLotto)
- [ ] 당첨 번호를 입력받는 함수 (setupWinningNum)
- 예외 케이스 1) 사용자가 입력한 값을 쉼표 기준으로 구분했을 때, 원소 개수가 6개가 아닌 경우
- 예외 케이스 2) 원소 중 정수가 아닌 값이 있는 경우
- 예외 케이스 3) 원소 중 1 ~ 45 범위를 벗어난 값이 있는 경우
- 예외 케이스 4) 당첨 번호 중 중복되는 값이 있는 경우
- [ ] 보너스 번호를 입력받는 함수(setupBonusNum)
- 예외 케이스 1) 정수가 아닌 값을 입력받은 경우
- 예외 케이스 2) 입력받은 값이 1 ~ 45 범위를 벗어난 경우
- 예외 케이스 3) 당첨 번호 중 보너스 번호와 중복되는 값이 있는 경우
- [ ] 구매한 로또들의 당첨 내역을 기록하는 함수 (checkLotteriesResult)
- [ ] 기록된 당첨 내역을 바탕으로 수익률을 계산하고 이들을 출력하는 함수(calculateProfit)
138 changes: 137 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,141 @@
import { Console, Random } from "@woowacourse/mission-utils";
import Lotto from "./Lotto.js";

const WINNER_PRIZE = [0, 2_000_000_000, 30_000_000, 1_500_000, 50_000, 5_000];


class App {
async run() {}
async run() {
const budget = await setupLottoBudget();
const lotteries = purchaseLotto(budget);
const winningNumbers = await setupWinningNum();
const bonusNumber = await setupBonusNum(winningNumbers);
const rankCount = checkLotteriesResult(lotteries, winningNumbers, bonusNumber);
calculateProfit(budget, rankCount);
}
}

async function setupLottoBudget() {
let input = await Console.readLineAsync("구입 금액을 입력해 주세요.\n");
if (Number.isNaN(input) || Number.isNaN(Number(input))) {
throw new Error("[ERROR] 구입 금액은 정수여야 합니다.");
}
input = Number(input);
if (input <= 0 || input % 1000 !== 0) {
throw new Error("[ERROR] 구입 금액은 1,000원 단위여야 합니다.");
}
Console.print("");
return input;
}

function createLotto() {
return new Lotto(Random.pickUniqueNumbersInRange(1, 45, 6).sort((a, b) => a - b));
}

function purchaseLotto(budget) {
const quantity = budget / 1000;
Console.print(`${quantity}개를 구매했습니다.\n`);
const lotteries = new Array(quantity);
for (let i = 0; i < quantity; i++) {
lotteries[i] = createLotto();
Console.print(lotteries[i].toString());
}
Console.print("");
return lotteries;
}

async function setupWinningNum() {
let winningNumbers = await Console.readLineAsync("당첨 번호를 입력해 주세요.\n");
winningNumbers = winningNumbers.split(",");
if (winningNumbers.length !== 6) {
throw new Error("[ERROR] 당첨 번호는 쉼표를 기준으로 구분되는 6개의 문자여야 합니다.");
}
for (let i = 0, len = winningNumbers.length; i < len; i++) {
winningNumbers[i] = validateWinningNum(winningNumbers[i]);
}
checkDuplicationWinningNum(winningNumbers);

Console.print("");
return winningNumbers;
}

function validateWinningNum(number) {
if (Number.isNaN(number) || Number.isNaN(Number(number))) {
throw new Error("[ERROR] 당첨 번호는 정수여야 합니다.");
}
number = Number(number);
if (number < 0 || number > 45) {
throw new Error("[ERROR] 당첨 번호는 1 ~ 45 사이의 정수여야 합니다.");
}
return number;
}

function checkDuplicationWinningNum(array) {
const v = new Array(46).fill(false);
for (const number of array) {
if (v[number]) {
throw new Error("[ERROR] 당첨 번호는 중복될 수 없습니다.");
}
v[number] = true;
}
}

async function setupBonusNum(winningNumbers) {
let bonusNumber = await Console.readLineAsync("보너스 번호를 입력해 주세요.\n");
if (Number.isNaN(bonusNumber) || Number.isNaN(Number(bonusNumber))) {
throw new Error("[ERROR] 보너스 번호는 정수여야 합니다.");
}
bonusNumber = validateWinningNum(bonusNumber);
if (winningNumbers.indexOf(bonusNumber) >= 0) {
throw new Error("[ERROR] 당첨 번호와 보너스 번호는 중복될 수 없습니다.");
}

Console.print("");
return bonusNumber;
}

function checkLotteriesResult(lotteries, winningNumbers, bonusNumber) {
const rankCount = new Array(6).fill(0);
for (const lotto of lotteries) {
const [matchedNumberCount, isBonusNumberExist] = lotto.checkWinningStatus(winningNumbers, bonusNumber);
const rank = calculateRank(matchedNumberCount, isBonusNumberExist);
rankCount[rank]++;
}
return rankCount;
}

function calculateRank(matchedNumberCount, isBonusNumberExist) {
switch (matchedNumberCount) {
case 3:
return 5;
case 4:
return 4;
case 5:
if (isBonusNumberExist) return 2;
return 3;
case 6:
return 1;
}
return 0;
};

function calculateProfit(budget, rankCount) {
let totalProfit = 0;
let profitRate = 0;
Console.print("당첨 통계");
Console.print("------");
Console.print(`3개 일치 (5,000원) - ${rankCount[5]}개`);
totalProfit += WINNER_PRIZE[5] * rankCount[5];
Console.print(`4개 일치 (50,000원) - ${rankCount[4]}개`);
totalProfit += WINNER_PRIZE[4] * rankCount[4];
Console.print(`5개 일치 (1,500,000원) - ${rankCount[3]}개`);
totalProfit += WINNER_PRIZE[3] * rankCount[3];
Console.print(`5개 일치, 보너스 볼 일치 (30,000,000원) - ${rankCount[2]}개`);
totalProfit += WINNER_PRIZE[2] * rankCount[2];
Console.print(`6개 일치 (2,000,000,000원) - ${rankCount[1]}개`);
totalProfit += WINNER_PRIZE[1] * rankCount[1];
profitRate = (totalProfit / budget * 100).toFixed(1);
Console.print(`총 수익률은 ${profitRate}%입니다.`);
}

export default App;
22 changes: 21 additions & 1 deletion src/Lotto.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,29 @@ class Lotto {
if (numbers.length !== 6) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
}
const v = new Array(46).fill(false);
for (const number of numbers) {
if (Number.isNaN(number) || Number.isNaN(Number(number))) {
throw new Error("[ERROR] 로또 번호는 정수여야 합니다.");
}
if (v[number]) {
throw new Error("[ERROR] 로또 번호는 중복될 수 없습니다.");
}
v[number] = true;
}
}

toString() {
return `[${this.#numbers.join(", ")}]`;
}

// TODO: 추가 기능 구현
checkWinningStatus(winningNumbers, bonusNumber) {
let matchedNumberCount = 0;
let isBonusNumberExist = false;
matchedNumberCount = this.#numbers.filter(num => winningNumbers.includes(num)).length;
isBonusNumberExist = this.#numbers.includes(bonusNumber);
return [matchedNumberCount, isBonusNumberExist];
}
}

export default Lotto;