diff --git a/README.md b/README.md index 15bb106b5..40c04cdb7 100644 --- a/README.md +++ b/README.md @@ -1 +1,19 @@ # javascript-lotto-precourse +# 로또 +## 기능 요구 사항 +- [X] 로또 구입 금액 입력 받기 +- [X] 입력 구입 금액 유효성 검사하기 +- [X] 1 ~ 45 숫자중 6개 중복없이 선택하기 +- [X] 당첨 번호, 보너스 번호 입력받기 +- [X] 발행한 로또 수량 및 번호 출력하기 +- [X] 사용자가 구입한 로또번호, 당첨번호 비교하기 +- [X] 당첨 내역 및 수익률 계산하기 +- [X] 당첨 내역 및 수익률 출력하기 +- [X] 잘못된 값 입력에 Error발생시키기 + +## 프로그래밍 요구 사항 +- [X] 함수의 길이가 15라인을 넘어가지 않도록 구현한다. +- [X] else를 지양한다. +- [ ] 구현한 기능에 대한 단위 테스트를 작성한다. +- [X] Lotto 클래스에 numbers 이외의 필드를 추가할 수 없다. +- [X] numbers의 접근 제어자인 #은 변경할수 없다. \ No newline at end of file diff --git a/src/App.js b/src/App.js index 091aa0a5d..9b39f96cf 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,15 @@ +import LottoManager from "./LottoManager.js"; + class App { - async run() {} + async run() { + const lottoManager = new LottoManager(); + await lottoManager.getPurchaseAmout(); + lottoManager.generateLottos(); + await lottoManager.getWinNumbers(); + await lottoManager.getBonusNumber(); + lottoManager.getGameResult(); + lottoManager.printGameResult(); + } } -export default App; +export default App; \ No newline at end of file diff --git a/src/Lotto.js b/src/Lotto.js index cb0b1527e..adf605982 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -1,18 +1,28 @@ +import { Console } from "@woowacourse/mission-utils"; + class Lotto { #numbers; constructor(numbers) { this.#validate(numbers); this.#numbers = numbers; + Console.print(`[${this.#numbers.join(", ")}]`); + // 구매한 로또 번호 출력은 누구 책임인가? 이게 잘못됐을지도? 그러면 매니저에서 받아서 프린트 } #validate(numbers) { if (numbers.length !== 6) { throw new Error("[ERROR] 로또 번호는 6개여야 합니다."); } + const numberSet = new Set(numbers); + if (numberSet.size !== numbers.length) { + throw new Error("[ERROR] 로또 번호는 중복될 수 없습니다."); + } } - // TODO: 추가 기능 구현 + getNumbers() { + return this.#numbers; + } } export default Lotto; diff --git a/src/LottoManager.js b/src/LottoManager.js new file mode 100644 index 000000000..233745d05 --- /dev/null +++ b/src/LottoManager.js @@ -0,0 +1,178 @@ +import { Console, MissionUtils } from "@woowacourse/mission-utils"; +import Lotto from "./Lotto.js"; + +class LottoManager { + static winningPriceMap = { + "1등": 2000000000, + "2등": 30000000, + "3등": 1500000, + "4등": 50000, + "5등": 5000 + } + static result = { + "1등": 0, + "2등": 0, + "3등": 0, + "4등": 0, + "5등": 0, + "winningPrice": 0, + } + + constructor() { + this.lottos = []; + this.spentMoney = 0; + this.winNumbers = null; + this.bonusNumber = null; + this.result = { ...LottoManager.result }; + this.winningRate = 0 + } + + // 깊이가 좀 깊은가? + // 구입 금액 입력 받기 + async getPurchaseAmout() { + while (true) { + const input = await Console.readLineAsync("구입금액을 입력해 주세요.\n") + try { + this.validateSpentMoney(input); + this.spentMoney = Number(input); + break; + } catch (e) { + Console.print(e.message); + continue; + } + } + } + + // 구입 금액 유효성 검사 + validateSpentMoney(price) { + const parsedPrice = Number(price); + if (!Number.isInteger(parsedPrice) || parsedPrice % 1000 !== 0 || parsedPrice <= 0) throw new Error("[ERROR] 구입 금액은 1000원 단위의 숫자로 입력해 주세요."); + } + + // 로또 생성 + generateLottos() { + const printNums = this.spentMoney / 1000; + Console.print(`${printNums}개를 구매했습니다.`); + + for (let i = 0; i < printNums; i++) { + const combination = MissionUtils.Random.pickUniqueNumbersInRange(1, 45, 6).sort((a, b) => a - b); + this.lottos.push(new Lotto(combination)); + } + } + // 당첨 번호 입력 받기 + async getWinNumbers() { + while (true) { + const input = await Console.readLineAsync("당첨 번호를 입력해 주세요.\n") + try { + this.validateWinNumbers(input); + this.winNumbers = input.split(",").map(Number); + break; + } catch (e) { + Console.print(e.message); + continue; + } + } + } + + // 당첨 번호 유효성 검사 + validateWinNumbers(numbers) { + const numTokens = numbers.split(",").map(Number); + const isLengthValid = numTokens.length === 6; + if (!isLengthValid) throw new Error("[ERROR] 당첨 번호는 6개여야 합니다."); + + const isAllinRange = numTokens.every(num => Number.isInteger(num) && num >= 1 && num <= 45); + if (!isAllinRange) throw new Error("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."); + + const isNotDuplicated = new Set(numTokens).size === numTokens.length; + if (!isNotDuplicated) throw new Error("[ERROR] 당첨 번호는 중복될 수 없습니다."); + } + + // 보너스 번호 입력 받기 + async getBonusNumber() { + while (true) { + const input = await Console.readLineAsync("보너스 번호를 입력해 주세요.\n") + try { + this.validateBonusNumber(input); + this.bonusNumber = Number(input); + break; + } catch (e) { + Console.print(e.message); + continue; + } + } + } + + // 보너스 번호 유효성 검사 + validateBonusNumber(number) { + const num = Number(number); + + if (!Number.isInteger(num)) throw new Error("[ERROR] 보너스 번호는 정수여야 합니다."); + if (num < 1 || num > 45) throw new Error("[ERROR] 보너스 번호는 1~45 사이여야 합니다."); + if (this.winNumbers.includes(num)) throw new Error("[ERROR] 보너스 번호는 기존 당첨 번호와 중복될 수 없습니다."); + } + + // 게임 결과 계산 + getGameResult() { + // 최종적으로 몇게임이 몇등인지 알아야함 + for(let i = 0; i < this.lottos.length; i++) { + const curLotto = this.lottos[i]; + this.getLottoResult(curLotto); // 한 로또의 결과 계산 + } + this.getPriceResult(); + } + + // 한 로또의 결과 계산 + getLottoResult(lotto) { + const lottoNumbers = lotto.getNumbers(); + let matchCount = 0; + let isBonusMatched = false; + + for (let number of lottoNumbers) { + if (this.winNumbers.includes(number)) matchCount++; + if (number === this.bonusNumber) isBonusMatched = true; + } + this.updateResult(matchCount, isBonusMatched); + } + + // 결과 업데이트 + updateResult(matchCount, isBonusMatched) { + const rankMap = { + 6: "1등", + 5: isBonusMatched ? "2등" : "3등", + 4: "4등", + 3: "5등" + }; + + const rank = rankMap[matchCount]; + if (!rank) return; // 낙첨 + + this.result[rank]++; + } + + + // 상금 결과 계산 + getPriceResult() { + const entries = Object.entries(this.result); // [ ["1등", 0], ["2등", 2], ...] + const totalPrice = entries.reduce((acc, [key, value]) => { + if (key === "winningPrice") return acc; + acc += LottoManager.winningPriceMap[key] * value; + return acc; + }, 0) + + this.result.winningPrice = Number(totalPrice); + this.winningRate = (totalPrice / this.spentMoney) * 100; + } + + // 게임 결과 출력 + printGameResult() { + Console.print("당첨 통계"); + Console.print("---"); + Console.print(`3개 일치 (5,000원) - ${this.result["5등"]}개`); + Console.print(`4개 일치 (50,000원) - ${this.result["4등"]}개`); + Console.print(`5개 일치 (1,500,000원) - ${this.result["3등"]}개`); + Console.print(`5개 일치, 보너스 볼 일치 (30,000,000원) - ${this.result["2등"]}개`); + Console.print(`6개 일치 (2,000,000,000원) - ${this.result["1등"]}개`); + Console.print(`총 수익률은 ${Number(this.winningRate.toFixed(2))}%입니다.`); + } +} +export default LottoManager; \ No newline at end of file