diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index 97bd457659..4f7fe3f0ac 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -1,3 +1,4 @@ +import App from '../src/App.js'; import Lotto from "../src/Lotto.js"; describe("로또 클래스 테스트", () => { @@ -14,5 +15,35 @@ describe("로또 클래스 테스트", () => { }).toThrow("[ERROR]"); }); - // 아래에 추가 테스트 작성 가능 + test("로또 번호에 숫자가 아닌 값이 있으면 예외가 발생한다.", () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5, 'a']); + }).toThrow("[ERROR]"); + }); + + test("로또 번호에 1~45 이외의 숫자가 있으면 예외가 발생한다..", () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5, 50]); + }).toThrow("[ERROR]"); + }) + + test("당첨 통계 테스트1", async () => { + const lotto = new Lotto([1, 2, 3, 4, 5, 6]); + + expect( + lotto.stats( + [ + [7, 8, 9, 10, 11, 12], // 0 + [6, 7, 8, 9, 10 , 11, 12], // 1 + [5, 6, 7, 8, 9, 10], // 2 + [4, 5, 6, 7, 8, 9], // 3 + [3, 4, 5, 6, 7, 8], // 4 + [2, 3, 4, 5, 6, 7], // 5 + bonus + [2, 3, 4, 5, 6, 8], // 5 + [1, 2, 3, 4, 5, 6], // 6 + ], + 7 + ) + ).toEqual([1, 1, 1, 1, 1]); + }) }); diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..496c3d6f1c --- /dev/null +++ b/docs/README.md @@ -0,0 +1,36 @@ +# 기능 목록 정리 + +## 입력 +### 로또 구입 금액 +- [x] 로또 구입 금액을 입력받는다. +- [x] (예외) 숫자가 아닌 경우 +- [x] (예외) 1,000 단위로 나누어 떨어지지 않는 경우 +- [x] (예외) 0원인 경우 +- [x] 잘못된 값을 입력한 경우, 에러 메시지를 출력하고 해당 부분부터 입력을 다시 받는다. + +### 당첨 번호 +- [x] 쉼표(,)를 기준으로 당첨 번호를 입력받는다. +- [x] (예외) 숫자가 아닌 경우 +- [x] (예외) 각 번호의 숫자 범위가 1~45에 포함되지 않는 경우 +- [x] (예외) 번호가 중복되는 경우 +- [x] (예외) 번호가 6개가 아닌 경우 + +### 보너스 번호 +- [x] 보너스 번호를 입력받는다 +- [x] (예외) 숫자가 아닌 경우 +- [x] (예외) 숫자 범위가 1~45에 포함되지 않는 경우 +- [x] (예외) 당첨 번호와 중복되는 경우 + + +## 출력 +- [x] 발행한 로또 수량 및 번호를 오름차순으로 출력한다. +- [x] 당첨 내역을 출력한다. +- [ ] 수익률은 소수점 둘째 자리에서 반올림한다. + +## 진행 +- [x] 구입 금액 입력 +- [x] 로또 구입 금액에 해당하는 만큼 로또 발행 +- [x] 당첨 번호 입력 +- [x] 보너스 번호 입력 +- [x] 사용자가 구매한 로또 번호와 당첨 번호를 비교 +- [x] 당첨 내역 및 수익률 출력 \ No newline at end of file diff --git a/src/App.js b/src/App.js index c38b30d5b2..a012ea5bd1 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,90 @@ +import { MissionUtils } from "@woowacourse/mission-utils"; +import Lotto from './Lotto.js'; + class App { - async play() {} + + printResult = (result) => { + MissionUtils.Console.print('당첨 통계'); + MissionUtils.Console.print('---'); + + MissionUtils.Console.print(`3개 일치 (5,000원) - ${result[0]}개`); + MissionUtils.Console.print(`4개 일치 (50,000원) - ${result[1]}개`); + MissionUtils.Console.print(`5개 일치 (1,500,000원) - ${result[2]}개`); + MissionUtils.Console.print(`5개 일치, 보너스 볼 일치 (30,000,000원) - ${result[3]}개`); + MissionUtils.Console.print(`6개 일치 (2,000,000,000원) - ${result[4]}개`); + } + + calculateLottoCount = async () => { + while(true) { + try { + const money = await MissionUtils.Console.readLineAsync('구입금액을 입력해 주세요.\n'); + + if (isNaN(money)) throw new Error("[ERROR] 구입금액은 숫자여야 합니다."); + if (parseInt(money / 1000) !== money / 1000) throw new Error("[ERROR] 구입금액은 1000원 단위여야 합니다."); + if (money == 0) throw new Error("[ERROR] 구입금액은 0원 이상이어야 합니다."); + + const count = money / 1000; + MissionUtils.Console.print(`${count}개를 구매했습니다.`); + return count; + } catch(error) { + MissionUtils.Console.print(error.message); + } + } + } + + inputBonus = async (win) => { + while(true) { + try { + const bonus = await MissionUtils.Console.readLineAsync('보너스 번호를 입력해 주세요.\n'); + if (isNaN(parseInt(bonus))) throw new Error("[ERROR] 보너스 번호는 숫자여야 합니다."); + if (1 > bonus || bonus > 45) throw new Error("[ERROR] 보너스 번호는 1~45 사이여야 합니다."); + if (win.includes(bonus)) throw new Error("[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다."); + return bonus; + } catch(error) { + MissionUtils.Console.print(error.message); + } + } + } + + printLottoNumbers = (lottoList) => { + lottoList.forEach((lottoNumbers) => { + MissionUtils.Console.print(`[${lottoNumbers.join(', ')}]`); + }); + } + + calculateTotalPrize = (stats) => { + let total = 0; + total += stats[0] * 5000; + total += stats[1] * 50000; + total += stats[2] * 1500000; + total += stats[3] * 30000000; + total += stats[4] * 2000000000; + return total; + } + earningsRate = (stats, count) => { + const totalPrize = this.calculateTotalPrize(stats); + return (totalPrize / (count * 1000) * 100).toFixed(1); + } + + createLotto = (count) => { + const lottoList = []; + while(count--) { + lottoList.push(MissionUtils.Random.pickUniqueNumbersInRange(1, 45, 6).sort((a, b) => a - b)); + } + return lottoList; + } + + async play() { + let count = await this.calculateLottoCount(); + const lottoList = this.createLotto(count); + this.printLottoNumbers(lottoList); + const win = await MissionUtils.Console.readLineAsync('당첨 번호를 입력해 주세요.\n'); + const lotto = new Lotto(win.split(',').map(v=>Number(v))); + const bonus = await this.inputBonus(win); + const result = lotto.stats(lottoList, bonus); + this.printResult(result); + MissionUtils.Console.print(`총 수익률은 ${this.earningsRate(result, count)}%입니다.`); + } } export default App; diff --git a/src/Lotto.js b/src/Lotto.js index cb0b1527e9..e7f0dc0fcc 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -7,12 +7,32 @@ class Lotto { } #validate(numbers) { + [...numbers].map((v, idx) => { + if (isNaN(parseInt(v))) throw new Error("[ERROR] 로또 번호는 숫자여야 합니다."); + if (1 > v || v > 45) throw new Error("[ERROR] 로또 번호는 1~45 사이여야 합니다."); + if (numbers.indexOf(v) !== idx) throw new Error("[ERROR] 로또 번호는 중복될 수 없습니다"); + }) if (numbers.length !== 6) { throw new Error("[ERROR] 로또 번호는 6개여야 합니다."); } } - // TODO: 추가 기능 구현 + stats(lottoList, bonus) { + const result = [0, 0, 0, 0, 0]; + lottoList.forEach((lotto) => { + switch(new Set([...lotto, ...this.#numbers]).size) { + case(9): result[0]++; break; + case(8): result[1]++; break; + case(7): { + if (lotto.includes(bonus)) result[3]++; + else result[2]++; + break; + } + case(6): result[4]++; break; + } + }) + return result; + } } export default Lotto;