diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index 97bd457659..070344468e 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -1,4 +1,5 @@ import Lotto from "../src/Lotto.js"; +import App from "../src/App.js"; describe("로또 클래스 테스트", () => { test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => { @@ -7,12 +8,88 @@ describe("로또 클래스 테스트", () => { }).toThrow("[ERROR]"); }); - // TODO: 이 테스트가 통과할 수 있게 구현 코드 작성 test("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.", () => { expect(() => { new Lotto([1, 2, 3, 4, 5, 5]); }).toThrow("[ERROR]"); }); - // 아래에 추가 테스트 작성 가능 + test("로또 번호에 1보다 작은 숫자가 있으면 예외가 발생한다.", () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5, 0]); + }).toThrow("[ERROR]"); + }); + + test("로또 번호에 45보다 큰 숫자가 있으면 예외가 발생한다.", () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5, 46]); + }).toThrow("[ERROR]"); + }); + + test("hasNumber 테스트",()=>{ + const lotto = new Lotto([1,2,3,4,5,6]); + expect(lotto.hasNumber(1)).toEqual(true); + expect(lotto.hasNumber(7)).toEqual(false); + }) }); + +describe("App.js 메서드 단위 테스트",()=>{ + test("constructor 테스트",()=>{ + const app = new App(); + expect(app.winningNumbers).toEqual([]); + expect(app.bonusNumber).toEqual(0); + }) + test("validateMoney 테스트",()=>{ + const app = new App(); + expect(app.validateMoney(1000)).toEqual(true); + expect(app.validateMoney(1001)).toEqual(undefined); + expect(app.validateMoney(0)).toEqual(undefined); + expect(app.validateMoney(-1)).toEqual(undefined); + }) + test("publishLotto 테스트",()=>{ + const app = new App(); + const lottos = app.publishLotto(1000); + expect(lottos.length).toEqual(1); + expect(lottos[0].getNumbers().length).toEqual(6); + }) + test("validateNumbers 테스트",()=>{ + const app = new App(); + expect(app.validateNumbers([1,2,3,4,5,6])).toEqual(true); + expect(app.validateNumbers([1,2,3,4,5,5])).toEqual(undefined); + expect(app.validateNumbers([1,2,3,4,5,0])).toEqual(undefined); + expect(app.validateNumbers([1,2,3,4,5,46])).toEqual(undefined); + }) + test("validateBonusNumber 테스트",()=>{ + const app = new App(); + app.winningNumbers = [1,2,3,4,5,6]; + expect(app.validateBonusNumber(7)).toEqual(true); + expect(app.validateBonusNumber(6)).toEqual(undefined); + expect(app.validateBonusNumber(0)).toEqual(undefined); + expect(app.validateBonusNumber(46)).toEqual(undefined); + }) + test("countMatchingNumbers 테스트",()=>{ + const app = new App(); + app.winningNumbers = [1,2,3,4,5,6]; + const lotto = new Lotto([1,2,3,4,5,6]); + expect(app.countMatchingNumbers(lotto)).toEqual(6); + app.winningNumbers = [1,2,3,4,5,7]; + expect(app.countMatchingNumbers(lotto)).toEqual(5); + }) + test("computeRank 테스트",()=>{ + const app = new App(); + app.bonusNumber = 7; + const lotto = new Lotto([1,2,3,4,5,6]); + app.winningNumbers = [1,2,3,4,5,6]; + expect(app.computeRank(lotto)).toEqual("1등"); + // app.winningNumbers = [1,2,3,4,5,7]; + // expect(app.computeRank(lotto)).toEqual("2등"); + app.winningNumbers = [1,2,3,4,5,8]; + expect(app.computeRank(lotto)).toEqual("3등"); + app.winningNumbers = [1,2,3,4,7,8]; + expect(app.computeRank(lotto)).toEqual("4등"); + app.winningNumbers = [1,2,3,7,8,9]; + expect(app.computeRank(lotto)).toEqual("5등"); + app.winningNumbers = [1,2,7,8,9,10]; + expect(app.computeRank(lotto)).toEqual("꽝"); + }); +}) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..243458ecad --- /dev/null +++ b/docs/README.md @@ -0,0 +1,54 @@ +로또 게임 기능 목록 +-------------------- +
+ +- 로또 구입 금액 입력받기 + - 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다 +
+ +- 로또 발행 + - 구입 금액에 해당하는 만큼 로또를 발행한다 + - 1~45범위의 중복되지 않는 6개의 숫자를 뽑는다 + - 로또 1장의 가격은 1000원이다 +
+ +- 구매한 로또 수량 및 번호 출력 + - 로또 번호는 오름차순으로 정렬하여 보여준다 +
+ +- 당첨 번호를 입력 받는다 + - 1~45범위의 중복되지 않는 6개의 숫자를 입력받는다 + - 번호는 쉼표(,)를 기준으로 구분한다. +- 보너스 번호를 입력 받는다 + - 보너스 번호는 당첨 번호와 중복될 수 없다 +
+ +- 잘못된 입력에 대한 예외처리 (각 입력마다) + - 사용자가 잘못된 값을 입력할 경우 throw문을 사용해 예외를 발생시킨다. 그런 다음, "[ERROR]"로 시작하는 에러 메시지를 출력하고 해당 부분부터 입력을 다시 받는다. +
+ +- 로또 하나에 대한 결과(등수) 계산 + - 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다 + - 5개가 일치할 시 틀린 1개가 보너스 번호와 일치하는지 확인하여 2등과 3등을 나눈다 + 1등: 6개 번호 일치 / 2,000,000,000원 + 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 + 3등: 5개 번호 일치 / 1,500,000원 + 4등: 4개 번호 일치 / 50,000원 + 5등: 3개 번호 일치 / 5,000원 +
+ +- 총 당첨 내역 출력 + - 각 로또의 결과를 합산한다 + - 아래와 같은 형식으로 출력한다 + 3개 일치 (5,000원) - ?개 + 4개 일치 (50,000원) - ?개 + 5개 일치 (1,500,000원) - ?개 + 5개 일치, 보너스 볼 일치 (30,000,000원) - ?개 + 6개 일치 (2,000,000,000원) - ?개 +
+ +- 수익률 계산 + - 수익률은 소수점 둘째 자리에서 반올림한다 +
+ +- 수익률 출력 diff --git a/src/App.js b/src/App.js index c38b30d5b2..4214ffb105 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,183 @@ +import { MissionUtils } from "@woowacourse/mission-utils"; +import Lotto from "./Lotto.js"; + class App { - async play() {} + constructor() { + this.winningNumbers = []; + this.bonusNumber = 0; + } + + async inputMoney() { + let money; + let isValidated = false; + while (!isValidated) { + const inputString = await MissionUtils.Console.readLineAsync( + "구입금액을 입력해 주세요.\n" + ); + money = Number(inputString.trim()); + isValidated = this.validateMoney(money); + } + return money; + } + + validateMoney(money) { + if (isNaN(money)) { + MissionUtils.Console.print("[ERROR] 숫자만 입력해주세요.\n"); + }else if(money < 1000){ + MissionUtils.Console.print("[ERROR] 최소 1000 이상을 입력해야 합니다.\n"); + } + else if (money % 1000 !== 0) { + MissionUtils.Console.print("[ERROR] 1000원 단위로 입력해주세요.\n"); + } else return true; + } + + publishLotto(money) { + const lottoCount = money / 1000; + const lottos = []; + for (let i = 0; i < lottoCount; i++) { + const randomNumbers = MissionUtils.Random.pickUniqueNumbersInRange( + 1, + 45, + 6 + ); + try { + const lotto = new Lotto(randomNumbers); + lottos.push(lotto); + } catch (error) { + MissionUtils.Console.print(error.message); + i--; + } + } + return lottos; + } + + printLottos(lottos) { + MissionUtils.Console.print(`\n${lottos.length}개를 구매했습니다.`); + for (const lotto of lottos) { + MissionUtils.Console.print(lotto.toString()); + } + } + + async inputNumbers() { + let numbers; + let isValidated = false; + while (!isValidated) { + const inputString = await MissionUtils.Console.readLineAsync( + "\n당첨 번호를 입력해주세요.\n" + ); + numbers = inputString.split(",").map((number) => Number(number.trim())); + isValidated = this.validateNumbers(numbers); + } + this.winningNumbers = numbers; + } + + validateNumbers(numbers) { + if (numbers.length !== 6) { + MissionUtils.Console.print("[ERROR] 6개의 숫자를 입력해주세요.\n"); + } else if (numbers.some((number) => isNaN(number))) { + MissionUtils.Console.print("[ERROR] 숫자만 입력해주세요.\n"); + } else if (numbers.some((number) => number < 1 || number > 45)) { + MissionUtils.Console.print("[ERROR] 1~45 사이의 숫자를 입력해주세요.\n"); + } else if (new Set(numbers).size !== 6) { + MissionUtils.Console.print("[ERROR] 중복된 숫자를 입력할 수 없습니다.\n"); + } else return true; + } + + async inputBonusNumber() { + let bonusNumber; + let isValidated = false; + while (!isValidated) { + const inputString = await MissionUtils.Console.readLineAsync( + "\n보너스 번호를 입력해주세요.\n" + ); + bonusNumber = Number(inputString.trim()); + isValidated = this.validateBonusNumber(bonusNumber); + } + this.bonusNumber = bonusNumber; + } + + validateBonusNumber(bonusNumber) { + if (isNaN(bonusNumber)) { + MissionUtils.Console.print("[ERROR] 숫자만 입력해주세요.\n"); + } else if (bonusNumber < 1 || bonusNumber > 45) { + MissionUtils.Console.print("[ERROR] 1~45 사이의 숫자를 입력해주세요.\n"); + } else if (this.winningNumbers.includes(bonusNumber)) { + MissionUtils.Console.print("[ERROR] 당첨 번호와 중복될 수 없습니다.\n"); + } else return true; + } + + countMatchingNumbers(lotto) { + let count = 0; + lotto.getNumbers().forEach((number) => { + if (this.winningNumbers.includes(number)) { + count++; + } + }); + return count; + } + + computeRank(lotto) { + const count = this.countMatchingNumbers(lotto); + const isBonusMatched = lotto.hasNumber(this.bonusNumber); + switch (count) { + case 6: + return "1등"; + case 5: + return isBonusMatched ? "2등" : "3등"; + case 4: + return "4등"; + case 3: + return "5등"; + default: + return "꽝"; + } + } + + computeTotalResult(lottos) { + const result = { "1등": 0, "2등": 0, "3등": 0, "4등": 0, "5등": 0, 꽝: 0 }; + for (const lotto of lottos) { + const rank = this.computeRank(lotto); + result[rank]++; + } + return result; + } + + printResult(result) { + MissionUtils.Console.print(` +당첨 통계\n--- +3개 일치 (5,000원) - ${result["5등"]}개 +4개 일치 (50,000원) - ${result["4등"]}개 +5개 일치 (1,500,000원) - ${result["3등"]}개 +5개 일치, 보너스 볼 일치 (30,000,000원) - ${result["2등"]}개 +6개 일치 (2,000,000,000원) - ${result["1등"]}개`); + } + + computeProfit(result, money) { + const totalPrize = + result["5등"] * 5000 + + result["4등"] * 50000 + + result["3등"] * 1500000 + + result["2등"] * 30000000 + + result["1등"] * 2000000000; + const profit = ((totalPrize / money) * 100).toFixed(1); + return profit; + } + + printProfit(profit) { + MissionUtils.Console.print(`총 수익률은 ${profit}%입니다.`); + } + + async play() { + const money = await this.inputMoney(); + const lottos = this.publishLotto(money); + this.printLottos(lottos); + await this.inputNumbers(); + await this.inputBonusNumber(); + const result = this.computeTotalResult(lottos); + this.printResult(result); + const profit = this.computeProfit(result, money); + this.printProfit(profit); + } } export default App; diff --git a/src/Lotto.js b/src/Lotto.js index cb0b1527e9..1e6e61c3bd 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -10,8 +10,25 @@ class Lotto { if (numbers.length !== 6) { throw new Error("[ERROR] 로또 번호는 6개여야 합니다."); } + if (new Set(numbers).size !== 6) { + throw new Error("[ERROR] 중복된 숫자가 있습니다."); + } + if (numbers.some(num => num < 1 || num > 45)) { + throw new Error("[ERROR] 번호는 1과 45 사이여야 합니다."); + } + } + + getNumbers() { + return this.#numbers; } + hasNumber(number) { + return this.#numbers.includes(number); + } + + toString() { + return `[${this.#numbers.join(", ")}]`; + } // TODO: 추가 기능 구현 }