diff --git a/README.md b/README.md index b168a180..8c8c546b 100644 --- a/README.md +++ b/README.md @@ -1 +1,61 @@ -# javascript-planetlotto-precourse +## 1️⃣ 과제 개요 + +**우아한 테크코스 프리코스 최종 테스트** + +**과제명 : 행성 로또** + +**기간 : 01.10 (4시간)** + +**작성자 : 윤돌** + +
+ +## 2️⃣ 기능 목록 + +**(0) 기본 구조 세팅** + +**(1) 로또 구입 금액 입력 기능 구현** + +**(2) 구입 금액 검증 로직 구현** + +- 숫자가 아닌 경우 예외처리 +- 0보다 작거나 같을 경우 예외처리 +- 500 단위로 떨어지지 않을 경우 예외처리 + +**(3) 로또 발행 기능 구현** + +**(4) 저장 및 조회 가능한 LottoBundle 클래스 생성** + +**(5) 발행된 로또 번호 출력 기능 구현** + +**(6) 당첨 번호 입력 기능 구현** + +**(7) 당첨 번호 검증 로직 구현** + +- 5개가 아닌 경우 예외처리 +- 중복될 경우 예외처리 +- 숫자가 아닐 경우 예외처리 +- 1~30 사이가 아닐 경우 예외처리 + +**(8) 보너스 번호 입력 기능 구현** + +**(9) 보너스 번호 검증 기능 구현** + +- 숫자가 아닐 경우 예외처리 +- 1~30 사이가 아닐 경우 예외처리 + +**(10) 규칙 및 상금 정의** + +**(11) 당첨 결과 계산 기능 구현** + +**(12) 결과 구현(결과 집계 및 수익률 계산)** + +**(13) 결과 출력** + +**(14) 통합 테스트 확인** + +**(15) 단일 테스트 추가 및 확인** + +**(16) 추가 기능 구현** + +
diff --git a/__tests__/LottoJudgeTest.js b/__tests__/LottoJudgeTest.js new file mode 100644 index 00000000..4f683638 --- /dev/null +++ b/__tests__/LottoJudgeTest.js @@ -0,0 +1,41 @@ +import LottoJudge from "../src/service/LottoJudge"; +import { PRIZES } from "../src/util/Rank"; + +describe("로또 판단하는 클래스 테스트", () => { + test("로또 판단하는 judge 함수 정상 작동하는지 확인", () => { + expect(() => { + const lottoJudge = new LottoJudge([1, 2, 3, 4, 5], 6); + return lottoJudge + .judge([1, 2, 3, 4, 5]) + .toStictEqual({ rank: 1, prize: PRIZES[1] }); + }); + + expect(() => { + const lottoJudge = new LottoJudge([1, 2, 3, 4, 5], 6); + return lottoJudge + .judge([1, 2, 3, 4, 6]) + .toStictEqual({ rank: 2, prize: PRIZES[2] }); + }); + + expect(() => { + const lottoJudge = new LottoJudge([1, 2, 3, 4, 5], 6); + return lottoJudge + .judge([1, 2, 3, 4, 8]) + .toStictEqual({ rank: 3, prize: PRIZES[3] }); + }); + + expect(() => { + const lottoJudge = new LottoJudge([1, 2, 3, 4, 5], 6); + return lottoJudge + .judge([1, 2, 3, 8, 9]) + .toStictEqual({ rank: 4, prize: PRIZES[4] }); + }); + + expect(() => { + const lottoJudge = new LottoJudge([1, 2, 3, 4, 5], 6); + return lottoJudge + .judge([1, 2, 8, 9, 10]) + .toStictEqual({ rank: 5, prize: PRIZES[5] }); + }); + }); +}); diff --git a/src/App.js b/src/App.js index 091aa0a5..81a8d609 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,51 @@ +import LottoBundle from "./model/LottoBundle.js"; +import LottoMachine from "./model/LottoMachine.js"; +import LottoJudge from "./service/LottoJudge.js"; +import Result from "./service/Result.js"; +import Validator from "./util/Validate.js"; +import { InputView, OutputView } from "./view.js"; + class App { - async run() {} + async run() { + try { + const purchaseAmount = await InputView.askAmount(); + Validator.validatePurchaseAmount(purchaseAmount); + + const lottoMachine = new LottoMachine(purchaseAmount); + const lottoBundle = new LottoBundle(lottoMachine.getLottos()); + + console.log(""); + OutputView.printPurchasedLottos(lottoBundle.getAll()); + console.log(""); + + const winningNumbers = await InputView.askWinningLotto(); + Validator.validateWinningNumbers(winningNumbers); + console.log(""); + + const bonusNumber = await InputView.askBonusNumber(); + Validator.validateBonusNumber(bonusNumber); + console.log(""); + + const lottoJudge = new LottoJudge(winningNumbers, bonusNumber); + const result = new Result(); + + result.calculate(lottoBundle, lottoJudge); + const rankCounts = result.getRankCounts(); + + OutputView.printResult( + new Map([ + [1, rankCounts[1]], + [2, rankCounts[2]], + [3, rankCounts[3]], + [4, rankCounts[4]], + [5, rankCounts[5]], + [0, rankCounts[6]], + ]) + ); + } catch (error) { + OutputView.printErrorMessage(error.message); + } + } } export default App; diff --git a/src/model/LottoBundle.js b/src/model/LottoBundle.js new file mode 100644 index 00000000..5e201902 --- /dev/null +++ b/src/model/LottoBundle.js @@ -0,0 +1,21 @@ +class LottoBundle { + #lottoBundle; + + constructor(lottoBundle) { + this.#lottoBundle = lottoBundle; + } + + size() { + return this.#lottoBundle.length; + } + + getAll() { + return [...this.#lottoBundle]; + } + + forEach(callback) { + this.#lottoBundle.forEach(callback); + } +} + +export default LottoBundle; diff --git a/src/model/LottoMachine.js b/src/model/LottoMachine.js new file mode 100644 index 00000000..5f50dbea --- /dev/null +++ b/src/model/LottoMachine.js @@ -0,0 +1,31 @@ +import { MissionUtils } from "@woowacourse/mission-utils"; + +class LottoMachine { + #lottoBundle; + + constructor(purchaseAmount) { + this.lottoCount = purchaseAmount / 500; + this.#lottoBundle = this.#generateLottos(); + } + + #generateLottos() { + const lottoBundle = []; + for (let i = 0; i < this.lottoCount; i++) { + const numbers = this.#generateRandomNumbers(); + lottoBundle.push(Array(numbers)); + } + + return lottoBundle; + } + + #generateRandomNumbers() { + const numbers = MissionUtils.Random.pickUniqueNumbersInRange(1, 30, 5); + return numbers.sort((a, b) => a - b); + } + + getLottos() { + return this.#lottoBundle; + } +} + +export default LottoMachine; diff --git a/src/service/LottoJudge.js b/src/service/LottoJudge.js new file mode 100644 index 00000000..c982935c --- /dev/null +++ b/src/service/LottoJudge.js @@ -0,0 +1,19 @@ +import { getRank } from "../util/Rank.js"; + +class LottoJudge { + constructor(winningNumbers, bonusNumber) { + this.winningNumbers = winningNumbers; + this.bonusNumber = bonusNumber; + } + + judge(lotto) { + const matchCount = this.winningNumbers.filter((it) => + lotto[0].includes(it) + ).length; + const hasBonus = lotto[0].includes(this.bonusNumber); + + return getRank(matchCount, hasBonus); + } +} + +export default LottoJudge; diff --git a/src/service/Result.js b/src/service/Result.js new file mode 100644 index 00000000..cf5b2129 --- /dev/null +++ b/src/service/Result.js @@ -0,0 +1,22 @@ +class Result { + constructor() { + this.rankCounts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 }; + } + + addRank(rank) { + if (rank) this.rankCounts[rank.rank]++; + } + + calculate(lottoBundle, lottoJudge) { + lottoBundle.forEach((lotto) => { + const rank = lottoJudge.judge(lotto); + this.addRank(rank); + }); + } + + getRankCounts() { + return this.rankCounts; + } +} + +export default Result; diff --git a/src/util/Rank.js b/src/util/Rank.js new file mode 100644 index 00000000..bdcdb988 --- /dev/null +++ b/src/util/Rank.js @@ -0,0 +1,32 @@ +export const RANKS = { + FIRST: 1, + SECOND: 2, + THIRD: 3, + FOURTH: 4, + FIFTH: 5, + SIXTH: 6, +}; + +export const PRIZES = { + [RANKS.FIRST]: 100000000, + [RANKS.SECOND]: 10000000, + [RANKS.THIRD]: 1500000, + [RANKS.FOURTH]: 500000, + [RANKS.FIFTH]: 5000, + [RANKS.SIXTH]: 0, +}; + +export function getRank(matchCount, hasBonus) { + if (matchCount === 5) + return { rank: RANKS.FIRST, prize: PRIZES[RANKS.FIRST] }; + if (matchCount === 4 && hasBonus) + return { rank: RANKS.SECOND, prize: PRIZES[RANKS.SECOND] }; + if (matchCount === 4) + return { rank: RANKS.THIRD, prize: PRIZES[RANKS.THIRD] }; + if (matchCount === 3) + return { rank: RANKS.FOURTH, prize: PRIZES[RANKS.FOURTH] }; + if (matchCount === 2) + return { rank: RANKS.FIFTH, prize: PRIZES[RANKS.FIFTH] }; + + return { rank: RANKS.SIXTH, prize: PRIZES[RANKS.FIFTH] }; +} diff --git a/src/util/Validate.js b/src/util/Validate.js new file mode 100644 index 00000000..0fd7e08a --- /dev/null +++ b/src/util/Validate.js @@ -0,0 +1,44 @@ +const Validator = { + validatePurchaseAmount(input) { + const amount = Number(input); + + if (amount <= 0) { + throw new Error("구입 금액은 0보다 커야 합니다."); + } + + if (amount % 500 !== 0) { + throw new Error("구입 금액은 500원 단위로 입력해야 합니다."); + } + }, + + validateWinningNumbers(winningNumbers) { + if (winningNumbers.length !== 5) { + throw new Error("로또 번호는 5개여야 합니다."); + } + + const uniqueNumbers = new Set(winningNumbers); + if (uniqueNumbers.size !== winningNumbers.length) { + throw new Error("로또 번호는 중복될 수 없습니다."); + } + + winningNumbers.forEach((num) => { + if (num < 1 || num > 30) { + throw new Error("로또 번호는 1부터 30 사이의 숫자여야 합니다."); + } + }); + }, + + validateBonusNumber(bonusNumber) { + const number = Number(bonusNumber); + + if (isNaN(number)) { + throw new Error("[ERROR] 보너스 번호는 숫자여야 합니다."); + } + + if (number < 1 || number > 45) { + throw new Error("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."); + } + }, +}; + +export default Validator;