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: 추가 기능 구현
}