diff --git a/README.md b/README.md index 15bb106..07ed1f2 100644 --- a/README.md +++ b/README.md @@ -1 +1,53 @@ # javascript-lotto-precourse +✅ 입출력 +- 로또 구입 금액 입력(1000단위) ✅ +- 당첨 번호 입력 ✅ +- 보너스 번호 입력 ✅ +- 발행한 로또 개수 출력 ✅ +- 결과 출력 + +✅ 전처리 +- 당첨 번호 쉼표 기준 구분(앞 뒤로 공백제거, 숫자로 변환) ✅ +- 발행한 로또 개수 구하기 ✅ + +✅ 1등 ~ 5등 검사 +- (로또 개수만큼) 중복되지 않는 숫자 6개 뽑기, 오름차순 정렬 ✅ +- 1등 조건 검사, 개수 카운트 ✅ +- 2등 조건 검사, 개수 카운트 ✅ +- 3등 조건 검사, 개수 카운트 ✅ +- 4등 조건 검사, 개수 카운트 ✅ +- 5등 조건 검사, 개수 카운트 ✅ + +✅ 수익률 계산 +- 수익률 계산 (벌어들인 금액/구매 금액), 소수점 둘째 자리 반올림 ✅ + +✅ 예외처리 +- 입력 오류 : 입력 금액이 1000으로 나누어 떨어지지 않는 경우 ✅ +- 입력 오류 : 로또 구매 금액이 숫자가 경우 ✅ +- 입력 오류 : 숫자가 아니거나, 쉼표가 아닌 것이 입력된 경우 ✅ +- 입력 오류 : 숫자가 중복되는 경우 ✅ +- 입력 오류 : 로또 번호가 범위를 벗어나는 경우 ✅ +- 입력 오류 : 로또 번호가 6개가 아닌 경우 ✅ +- 입력 오류 : 보너스 번호가 이미 입력한 번호와 중복되는 경우 ✅ +- 입력 오류 : 보너스 번호가 숫자가 아닌 경우 ✅ + +# 클래스 설계 +- Lotto 클래스 : 입력된 로또 번호 검증 및 관리 +- LottoGame 클래스 : 게임진행, 로또발행, 당첨내역, 예외처리 +- App 클래스 : 입출력 담당 + +# 코드 흐름 +- App 로또 금액 입력받기 + -> LottoGame 로또 금액만큼 로또 발행 요청 + -> Lotto 로또 발행 (외부에서 수정 불가) + -> LottoGame 발행된 로또 저장 + -> App 로또출력 + +- App 로또 번호, 보너스 입력받기 + -> LottoGame 로또 번호 전처리(,제거) + -> Lotto 유저 로또 검증, 발행 + -> LottoGame 유저 로또 저장 + -> LottoGame 로또 번호 당첨 여부 카운트 + -> App 당첨여부출력 + +# MVC 구조 diff --git a/src/App.js b/src/App.js index 091aa0a..211c6eb 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,86 @@ +import { Console } from "@woowacourse/mission-utils"; +import LottoGame from "../src/LottoGame.js"; + class App { - async run() {} + async run() { + try { + const lottoGame = new LottoGame(); + + //구입금액 입력 + const inputMoney = await this.getInputMoney(); + const ticketCount = inputMoney / 1000; + + //구입한 로또 출력 + Console.print(`${ticketCount}개를 구매했습니다.`); + const winningLotto = lottoGame.creatLotto(ticketCount); + for (let lotto in winningLotto) { + Console.print(`[${winningLotto[lotto].join(", ")}]`); + } + + //당첨번호 입력 + const userLotto = await this.getUserLotto(lottoGame); + + //보너스번호 입력 + const inputBonus = await this.getBonusNumber(userLotto); + + //결과 출력 + const rank = lottoGame.returnRank(userLotto, winningLotto, inputBonus); + Console.print("당첨 통계"); + Console.print("---"); + Console.print(`3개 일치 (5,000원) - ${rank.fifth}개`); + Console.print(`4개 일치 (50,000원) - ${rank.fourth}개`); + Console.print(`5개 일치 (1,500,000원) - ${rank.third}개`); + Console.print(`5개 일치, 보너스 볼 일치 (30,000,000원) - ${rank.second}개`); + Console.print(`6개 일치 (2,000,000,000원) - ${rank.first}개`); + const earningRate = lottoGame.earningRate(inputMoney); + Console.print(`총 수익률은 ${earningRate}%입니다.`); + + } catch (error) { + Console.print(error.message); + return Promise.reject(error); + } + } + + // 재귀적으로 구입금액 입력 받기 + async getInputMoney() { + try { + const inputMoney = await Console.readLineAsync('구입금액을 입력해 주세요.'); + const moneyNum = Number(inputMoney); + if (isNaN(moneyNum) || inputMoney % 1000 !== 0 || !Number.isInteger(moneyNum)) { + throw new Error("[ERROR] IllegalArgumentException"); + } + return inputMoney; + } catch (error) { + Console.print(error.message); + return this.getInputMoney(); // 재귀적으로 다시 호출 + } + } + + // 재귀적으로 당첨번호 입력 받기 + async getUserLotto(lottoGame) { + try { + const inputNumbers = await Console.readLineAsync('당첨 번호를 입력해 주세요.'); + const userLotto = lottoGame.preprocessUserLotto(inputNumbers); + return userLotto; + } catch (error) { + Console.print(error.message); + return this.getUserLotto(lottoGame); // 재귀적으로 다시 호출 + } + } + + // 재귀적으로 보너스 번호 입력 받기 + async getBonusNumber(userLotto) { + try { + const inputBonus = await Console.readLineAsync('보너스 번호를 입력해 주세요.'); + if (userLotto.includes(Number(inputBonus))||isNaN(inputBonus)) { + throw new Error("[ERROR] 보너스 번호는 입력하지 않은 숫자이어야 합니다."); + } + return inputBonus; + } catch (error) { + Console.print(error.message); + return this.getBonusNumber(userLotto); // 재귀적으로 다시 호출 + } + } } export default App; diff --git a/src/Lotto.js b/src/Lotto.js index cb0b152..6471e32 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -1,5 +1,8 @@ +import { Random } from "@woowacourse/mission-utils"; + +//로또 번호 저장, 검증, 반환 class Lotto { - #numbers; + #numbers; //받은 로또 번호리스트 constructor(numbers) { this.#validate(numbers); @@ -12,7 +15,9 @@ class Lotto { } } - // TODO: 추가 기능 구현 + getNumbers(){ + return this.#numbers + } } export default Lotto; diff --git a/src/LottoGame.js b/src/LottoGame.js new file mode 100644 index 0000000..e59efbd --- /dev/null +++ b/src/LottoGame.js @@ -0,0 +1,76 @@ +import { Console, Random } from "@woowacourse/mission-utils"; +import Lotto from "../src/Lotto.js"; + +class LottoGame{ + constructor(){ + this.winningLottos = [] + this.counts = {first : 0, second : 0, third: 0, fourth: 0, fifth: 0}; + } + + //로또 발행 + creatLotto(num){ + for(let i = 0; i < num; i++){ + let numbers = Random.pickUniqueNumbersInRange(1, 45, 6); //중복되지 않는 숫자 6개 + this.winningLottos.push(new Lotto(numbers).getNumbers()); + } + this.winningLottos.sort((a,b) => a - b); + return this.winningLottos + } + + // userLotto 전처리 + preprocessUserLotto(userNumbers) { + userNumbers = userNumbers.split(",").map(num => num.trim()).map(num => Number(num)); + const userLotto = new Lotto(userNumbers); + return userLotto.getNumbers().sort((a,b) => a - b); + } + + // 일치하는 숫자 개수 계산 + calculateMatchingCount(userLotto, winningLotto) { + return userLotto.filter(num => winningLotto.includes(num)).length; + } + + // 등수 판별 + getRank(matchingCount, bonusMatch) { + switch (matchingCount) { + case 6: return 'first'; + case 5: return bonusMatch ? 'third' : 'second'; + case 4: return 'fourth'; + case 3: return 'fifth'; + default: return null; + } + } + + // 등수 반환 + returnRank(userLotto, winningLottos, bonusNumber){ + let matchingCount = 0; + let bonusMatch = userLotto.includes(bonusNumber); + + for (let i = 0; i < winningLottos.length; i++) { + // 일치하는 번호 개수 계산 + matchingCount = this.calculateMatchingCount(userLotto, winningLottos[i]); + + // 등수 판별 + let rank = this.getRank(matchingCount, bonusMatch); + if (rank) { + this.counts[rank] += 1; + } + } + + return this.counts; + } + + //수익률 계산 (벌어들인 금액/구매 금액) + earningRate(money){ + const earnings = (this.counts.fifth * 5000) + + (this.counts.fourth * 50000) + + (this.counts.second * 1500000) + + (this.counts.third * 30000000) + + (this.counts.first * 2000000000); + + const earningsRate = (earnings / money) * 100 ; + const roundedearningsRate = parseFloat(earningsRate.toFixed(1)); + return roundedearningsRate; + } +} + +export default LottoGame; \ No newline at end of file