From e29e619d2c2746a340d5a9d92cf01699bb44457b Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 18:18:15 +0900 Subject: [PATCH 01/28] =?UTF-8?q?docs(readme):=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 15bb106b5..5cd495dfb 100644 --- a/README.md +++ b/README.md @@ -1 +1,33 @@ # javascript-lotto-precourse + +## 기능 +### 입력 +- [ ] 로또 구입 금액 입력 + - [ ] 양수가 아닌 경우 예외 + - [ ] `1000`으로 나누어 떨어지지 않는 경우 예외 +- [ ] 당첨 번호 입력 + - [ ] 쉼표로 구분된 6개의 숫자 입력 + - [ ] `1`~`45` 범위를 벗어나거나 중복인 경우 예외 +- [ ] 보너스 번호 입력 + - [ ] 당첨 번호와 중복인 경우 예외 + +### 로또 발행 +- [ ] `구입 금액 / 1000`개의 로또 발행 +- [ ] 각 로또는 `1`~`45` 범위의 중복되지 않은 6개의 숫자로 구성 +- [ ] 오름차순 정렬 + +### 당첨 결과 계산 +- [ ] 로또의 당첨 번호 일치 개수에 따른 등수 판별 + - 1등: 6개 번호 일치 / 2,000,000,000원 + - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 + - 3등: 5개 번호 일치 / 1,500,000원 + - 4등: 4개 번호 일치 / 50,000원 + - 5등: 3개 번호 일치 / 5,000원 +- [ ] 수익률 계산 + - [ ] 소수점 둘째 자리 반올림 + +### 출력 +- [ ] 구매한 로또 번호 출력 +- [ ] 당첨 통계 출력 +- [ ] 수익률 출력 +- [ ] 예외 발생 시 `[ERROR]`로 시작하는 에러 메시지 출력 \ No newline at end of file From 4638a597200ea36b372f1a6e1697ba3a6f180337 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 19:41:45 +0900 Subject: [PATCH 02/28] =?UTF-8?q?feat(inputView):=20=EB=A1=9C=EB=98=90=20?= =?UTF-8?q?=EA=B5=AC=EC=9E=85=20=EA=B8=88=EC=95=A1=20=EC=9E=85=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/view/inputView.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 src/view/inputView.js diff --git a/README.md b/README.md index 5cd495dfb..b175ac9ce 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## 기능 ### 입력 -- [ ] 로또 구입 금액 입력 +- [x] 로또 구입 금액 입력 - [ ] 양수가 아닌 경우 예외 - [ ] `1000`으로 나누어 떨어지지 않는 경우 예외 - [ ] 당첨 번호 입력 diff --git a/src/view/inputView.js b/src/view/inputView.js new file mode 100644 index 000000000..ec69a25ca --- /dev/null +++ b/src/view/inputView.js @@ -0,0 +1,10 @@ +import { Console } from "@woowacourse/mission-utils"; + +const inputView = { + async readPurchaseAmount() { + const input = await Console.readLineAsync("구입금액을 입력해 주세요.\n"); + return input; + }, +} + +export default inputView; \ No newline at end of file From f1ce297fab3ca06caa0e88a0580a39fe1fdb4a36 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 19:48:23 +0900 Subject: [PATCH 03/28] =?UTF-8?q?feat(validator):=20=EB=A1=9C=EB=98=90=20?= =?UTF-8?q?=EA=B5=AC=EC=9E=85=20=EA=B8=88=EC=95=A1=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 숫자가 아닌 경우 예외 - 정수가 아닌 경우 예외 - 양수가 아닌 경우 예외 - 1000으로 나누어 떨어지지 않는 경우 예외 --- README.md | 6 ++++-- src/model/validator.js | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 src/model/validator.js diff --git a/README.md b/README.md index b175ac9ce..7eb0bec23 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,10 @@ ## 기능 ### 입력 - [x] 로또 구입 금액 입력 - - [ ] 양수가 아닌 경우 예외 - - [ ] `1000`으로 나누어 떨어지지 않는 경우 예외 + - [x] 숫자가 아닌 경우 예외 + - [x] 정수가 아닌 경우 예외 + - [x] 양수가 아닌 경우 예외 + - [x] `1000`으로 나누어 떨어지지 않는 경우 예외 - [ ] 당첨 번호 입력 - [ ] 쉼표로 구분된 6개의 숫자 입력 - [ ] `1`~`45` 범위를 벗어나거나 중복인 경우 예외 diff --git a/src/model/validator.js b/src/model/validator.js new file mode 100644 index 000000000..96207946b --- /dev/null +++ b/src/model/validator.js @@ -0,0 +1,18 @@ +const validator = { + validatePurchaseAmount(purchaseAmount) { + if (Number.isNaN(purchaseAmount)) { + throw new Error("[ERROR] 구입 금액은 숫자여야 합니다."); + } + if (!Number.isInteger(purchaseAmount)) { + throw new Error("[ERROR] 구입 금액은 정수여야 합니다."); + } + if (purchaseAmount <= 0) { + throw new Error("[ERROR] 구입 금액은 0보다 커야 합니다."); + } + if (purchaseAmount % 1000 !== 0) { + throw new Error("[ERROR] 구입 금액은 1000원 단위여야 합니다."); + } + }, +} + +export default validator; \ No newline at end of file From db4b97b7ce9d9eb98635f6652f062f779ed1b079 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 19:48:31 +0900 Subject: [PATCH 04/28] =?UTF-8?q?test(validator):=20=EB=A1=9C=EB=98=90=20?= =?UTF-8?q?=EA=B5=AC=EC=9E=85=20=EA=B8=88=EC=95=A1=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/validator.test.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 __tests__/validator.test.js diff --git a/__tests__/validator.test.js b/__tests__/validator.test.js new file mode 100644 index 000000000..e59e9905b --- /dev/null +++ b/__tests__/validator.test.js @@ -0,0 +1,26 @@ +import validator from "../src/model/validator.js"; + +describe("Validator.validatePurchaseAmount()", () => { + test("정상 입력(1000원 단위의 양의 정수)일 경우 에러가 발생하지 않는다.", () => { + expect(() => validator.validatePurchaseAmount(1000)).not.toThrow(); + expect(() => validator.validatePurchaseAmount(8000)).not.toThrow(); + }); + + test("숫자가 아닐 경우 에러가 발생한다.", () => { + expect(() => validator.validatePurchaseAmount(NaN)).toThrow("[ERROR] 구입 금액은 숫자여야 합니다."); + }); + + test("정수가 아닐 경우 에러가 발생한다.", () => { + expect(() => validator.validatePurchaseAmount(1000.5)).toThrow("[ERROR] 구입 금액은 정수여야 합니다."); + }); + + test("0 이하일 경우 에러가 발생한다.", () => { + expect(() => validator.validatePurchaseAmount(0)).toThrow("[ERROR] 구입 금액은 0보다 커야 합니다."); + expect(() => validator.validatePurchaseAmount(-5000)).toThrow("[ERROR] 구입 금액은 0보다 커야 합니다."); + }); + + test("1000원 단위가 아닐 경우 에러가 발생한다.", () => { + expect(() => validator.validatePurchaseAmount(1500)).toThrow("[ERROR] 구입 금액은 1000원 단위여야 합니다."); + expect(() => validator.validatePurchaseAmount(123456)).toThrow("[ERROR] 구입 금액은 1000원 단위여야 합니다."); + }); +}); \ No newline at end of file From bd7b97f4d7c1b5cb77cf02a24449836cdb9491bb Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 19:50:00 +0900 Subject: [PATCH 05/28] =?UTF-8?q?feat(lottoController):=20=EB=A1=9C?= =?UTF-8?q?=EB=98=90=20=EA=B5=AC=EC=9E=85=20=EA=B8=88=EC=95=A1=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 7 ++++++- src/controller/LottoController.js | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/controller/LottoController.js diff --git a/src/App.js b/src/App.js index 091aa0a5d..c121a4315 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,10 @@ +import LottoController from "./controller/LottoController.js"; + class App { - async run() {} + async run() { + const controller = new LottoController(); + await controller.run(); + } } export default App; diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js new file mode 100644 index 000000000..e89493d7a --- /dev/null +++ b/src/controller/LottoController.js @@ -0,0 +1,17 @@ +import inputView from "../view/inputView.js"; +import validator from "../model/validator.js"; + +class LottoController { + async run() { + const purchaseAmount = await this.getPurchaseAmount(); + } + + async getPurchaseAmount() { + const input = await inputView.readPurchaseAmount(); + const purchaseAmount = Number(input); + validator.validatePurchaseAmount(purchaseAmount); + return purchaseAmount; + } +} + +export default LottoController; \ No newline at end of file From 0a776949db665fc03cd10ad2eaf1744d2b9e9ec1 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 19:51:13 +0900 Subject: [PATCH 06/28] =?UTF-8?q?feat(inputView):=20=EB=8B=B9=EC=B2=A8=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9E=85=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/view/inputView.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7eb0bec23..414bec0a8 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ - [x] 정수가 아닌 경우 예외 - [x] 양수가 아닌 경우 예외 - [x] `1000`으로 나누어 떨어지지 않는 경우 예외 -- [ ] 당첨 번호 입력 +- [x] 당첨 번호 입력 - [ ] 쉼표로 구분된 6개의 숫자 입력 - [ ] `1`~`45` 범위를 벗어나거나 중복인 경우 예외 - [ ] 보너스 번호 입력 diff --git a/src/view/inputView.js b/src/view/inputView.js index ec69a25ca..2a2d963c2 100644 --- a/src/view/inputView.js +++ b/src/view/inputView.js @@ -5,6 +5,11 @@ const inputView = { const input = await Console.readLineAsync("구입금액을 입력해 주세요.\n"); return input; }, + + async readWinningNumbers() { + const input = await Console.readLineAsync("당첨 번호를 입력해 주세요.\n"); + return input; + }, } export default inputView; \ No newline at end of file From 44238f9da9338692e6280a29deaa5cffca7f4fe7 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 20:00:30 +0900 Subject: [PATCH 07/28] =?UTF-8?q?feat(validator):=20=EB=8B=B9=EC=B2=A8=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 번호가 6개가 아닌 경우 예외 - 중복된 번호가 있는 경우 예외 - 1~45 범위를 벗어나는 경우 예외 --- README.md | 5 +++-- src/model/validator.js | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 414bec0a8..8ae0b5f98 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ - [x] 양수가 아닌 경우 예외 - [x] `1000`으로 나누어 떨어지지 않는 경우 예외 - [x] 당첨 번호 입력 - - [ ] 쉼표로 구분된 6개의 숫자 입력 - - [ ] `1`~`45` 범위를 벗어나거나 중복인 경우 예외 + - [x] 쉼표로 구분된 6개의 숫자 입력 + - [x] 중복된 번호가 있는 경우 예외 + - [x] `1`~`45` 범위를 벗어나는 경우 예외 - [ ] 보너스 번호 입력 - [ ] 당첨 번호와 중복인 경우 예외 diff --git a/src/model/validator.js b/src/model/validator.js index 96207946b..a47a5c7b5 100644 --- a/src/model/validator.js +++ b/src/model/validator.js @@ -13,6 +13,26 @@ const validator = { throw new Error("[ERROR] 구입 금액은 1000원 단위여야 합니다."); } }, + + validateWinningNumbers(winningNumbers) { + if (winningNumbers.length !== 6) { + throw new Error("[ERROR] 당첨 번호는 6개여야 합니다."); + } + + const hasDuplicate = new Set(winningNumbers).size !== winningNumbers.length; + if (hasDuplicate) { + throw new Error("[ERROR] 중복된 당첨 번호가 있습니다."); + } + + winningNumbers.forEach((num) => { + if (!Number.isInteger(num)) { + throw new Error("[ERROR] 당첨 번호는 숫자여야 합니다."); + } + if (num < 1 || num > 45) { + throw new Error("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."); + } + }); + }, } export default validator; \ No newline at end of file From a0f531829e1b83173ef59e4049b2d32ee3ff28ca Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 20:01:49 +0900 Subject: [PATCH 08/28] =?UTF-8?q?test(validator):=20=EB=8B=B9=EC=B2=A8=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/validator.test.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/__tests__/validator.test.js b/__tests__/validator.test.js index e59e9905b..465914fe0 100644 --- a/__tests__/validator.test.js +++ b/__tests__/validator.test.js @@ -23,4 +23,33 @@ describe("Validator.validatePurchaseAmount()", () => { expect(() => validator.validatePurchaseAmount(1500)).toThrow("[ERROR] 구입 금액은 1000원 단위여야 합니다."); expect(() => validator.validatePurchaseAmount(123456)).toThrow("[ERROR] 구입 금액은 1000원 단위여야 합니다."); }); +}); + +describe("Validator.validateWinningNumbers()", () => { + test("정상 입력일 경우 에러가 발생하지 않는다.", () => { + expect(() => validator.validateWinningNumbers([1, 5, 12, 23, 34, 45])).not.toThrow(); + }); + + test("번호가 6개가 아닐 경우 에러가 발생한다.", () => { + expect(() => validator.validateWinningNumbers([1, 2, 3, 4, 5])).toThrow("[ERROR] 당첨 번호는 6개여야 합니다."); + expect(() => validator.validateWinningNumbers([1, 2, 3, 4, 5, 6, 7])).toThrow("[ERROR] 당첨 번호는 6개여야 합니다."); + }); + + test("중복된 번호가 있을 경우 에러가 발생한다.", () => { + expect(() => validator.validateWinningNumbers([1, 2, 3, 3, 4, 5])).toThrow("[ERROR] 중복된 당첨 번호가 있습니다."); + }); + + test("숫자가 아닌 값이 있을 경우 에러가 발생한다.", () => { + expect(() => validator.validateWinningNumbers([1, 2, "3", 4, 5, 6])).toThrow("[ERROR] 당첨 번호는 숫자여야 합니다."); + expect(() => validator.validateWinningNumbers([1, 2, null, 4, 5, 6])).toThrow("[ERROR] 당첨 번호는 숫자여야 합니다."); + }); + + test("1 미만의 번호가 있을 경우 에러가 발생한다.", () => { + expect(() => validator.validateWinningNumbers([0, 2, 3, 4, 5, 6])).toThrow("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."); + expect(() => validator.validateWinningNumbers([-1, 2, 3, 4, 5, 6])).toThrow("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."); + }); + + test("45 초과의 번호가 있을 경우 에러가 발생한다.", () => { + expect(() => validator.validateWinningNumbers([1, 2, 3, 4, 5, 46])).toThrow("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."); + }); }); \ No newline at end of file From a31916edb5f431d9d9a571a26a633c3840adaa55 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 20:05:58 +0900 Subject: [PATCH 09/28] =?UTF-8?q?feat(lottoController):=20=EB=8B=B9?= =?UTF-8?q?=EC=B2=A8=20=EB=B2=88=ED=98=B8=20=EC=9E=85=EB=A0=A5=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/LottoController.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index e89493d7a..f3f11a988 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -4,6 +4,7 @@ import validator from "../model/validator.js"; class LottoController { async run() { const purchaseAmount = await this.getPurchaseAmount(); + const winningNumbers = await this.getWinningNumbers(); } async getPurchaseAmount() { @@ -12,6 +13,13 @@ class LottoController { validator.validatePurchaseAmount(purchaseAmount); return purchaseAmount; } + + async getWinningNumbers() { + const input = await inputView.readWinningNumbers(); + const winningNumbers = input.split(",").map((num) => Number(num.trim())); + validator.validateWinningNumbers(winningNumbers); + return winningNumbers; + } } export default LottoController; \ No newline at end of file From 5fe2bc1c5fb6bcde0eb9956d74ff94efe18dc7a2 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 20:19:39 +0900 Subject: [PATCH 10/28] =?UTF-8?q?refactor(validator):=20=EB=8B=B9=EC=B2=A8?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8=20=EA=B2=80=EC=A6=9D=20=EC=88=9C=EC=84=9C?= =?UTF-8?q?=20=EC=A1=B0=EC=A0=95=EC=9C=BC=EB=A1=9C=20=EC=A1=B0=EA=B8=B0=20?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- __tests__/validator.test.js | 8 ++++---- src/model/validator.js | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8ae0b5f98..c51c4ad7f 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ - [x] `1000`으로 나누어 떨어지지 않는 경우 예외 - [x] 당첨 번호 입력 - [x] 쉼표로 구분된 6개의 숫자 입력 - - [x] 중복된 번호가 있는 경우 예외 - [x] `1`~`45` 범위를 벗어나는 경우 예외 + - [x] 중복된 번호가 있는 경우 예외 - [ ] 보너스 번호 입력 - [ ] 당첨 번호와 중복인 경우 예외 diff --git a/__tests__/validator.test.js b/__tests__/validator.test.js index 465914fe0..83cbf48eb 100644 --- a/__tests__/validator.test.js +++ b/__tests__/validator.test.js @@ -35,10 +35,6 @@ describe("Validator.validateWinningNumbers()", () => { expect(() => validator.validateWinningNumbers([1, 2, 3, 4, 5, 6, 7])).toThrow("[ERROR] 당첨 번호는 6개여야 합니다."); }); - test("중복된 번호가 있을 경우 에러가 발생한다.", () => { - expect(() => validator.validateWinningNumbers([1, 2, 3, 3, 4, 5])).toThrow("[ERROR] 중복된 당첨 번호가 있습니다."); - }); - test("숫자가 아닌 값이 있을 경우 에러가 발생한다.", () => { expect(() => validator.validateWinningNumbers([1, 2, "3", 4, 5, 6])).toThrow("[ERROR] 당첨 번호는 숫자여야 합니다."); expect(() => validator.validateWinningNumbers([1, 2, null, 4, 5, 6])).toThrow("[ERROR] 당첨 번호는 숫자여야 합니다."); @@ -52,4 +48,8 @@ describe("Validator.validateWinningNumbers()", () => { test("45 초과의 번호가 있을 경우 에러가 발생한다.", () => { expect(() => validator.validateWinningNumbers([1, 2, 3, 4, 5, 46])).toThrow("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."); }); + + test("중복된 번호가 있을 경우 에러가 발생한다.", () => { + expect(() => validator.validateWinningNumbers([1, 2, 3, 3, 4, 5])).toThrow("[ERROR] 중복된 당첨 번호가 있습니다."); + }); }); \ No newline at end of file diff --git a/src/model/validator.js b/src/model/validator.js index a47a5c7b5..bf8af6e8b 100644 --- a/src/model/validator.js +++ b/src/model/validator.js @@ -19,11 +19,6 @@ const validator = { throw new Error("[ERROR] 당첨 번호는 6개여야 합니다."); } - const hasDuplicate = new Set(winningNumbers).size !== winningNumbers.length; - if (hasDuplicate) { - throw new Error("[ERROR] 중복된 당첨 번호가 있습니다."); - } - winningNumbers.forEach((num) => { if (!Number.isInteger(num)) { throw new Error("[ERROR] 당첨 번호는 숫자여야 합니다."); @@ -32,6 +27,11 @@ const validator = { throw new Error("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."); } }); + + const hasDuplicate = new Set(winningNumbers).size !== winningNumbers.length; + if (hasDuplicate) { + throw new Error("[ERROR] 중복된 당첨 번호가 있습니다."); + } }, } From c3070446887990b7163ae3afcf861cb091b13706 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 20:20:04 +0900 Subject: [PATCH 11/28] =?UTF-8?q?feat(inputView):=20=EB=B3=B4=EB=84=88?= =?UTF-8?q?=EC=8A=A4=20=EB=B2=88=ED=98=B8=20=EC=9E=85=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/view/inputView.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c51c4ad7f..77acb13f2 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ - [x] 쉼표로 구분된 6개의 숫자 입력 - [x] `1`~`45` 범위를 벗어나는 경우 예외 - [x] 중복된 번호가 있는 경우 예외 -- [ ] 보너스 번호 입력 +- [x] 보너스 번호 입력 - [ ] 당첨 번호와 중복인 경우 예외 ### 로또 발행 diff --git a/src/view/inputView.js b/src/view/inputView.js index 2a2d963c2..4b7115df5 100644 --- a/src/view/inputView.js +++ b/src/view/inputView.js @@ -10,6 +10,11 @@ const inputView = { const input = await Console.readLineAsync("당첨 번호를 입력해 주세요.\n"); return input; }, + + async readBonusNumber() { + const input = await Console.readLineAsync("보너스 번호를 입력해 주세요.\n"); + return input; + }, } export default inputView; \ No newline at end of file From f9cc4a3cf34c54a486aba69f66c19d3ea0fa3839 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 20:22:23 +0900 Subject: [PATCH 12/28] =?UTF-8?q?feat(validator):=20=EB=B3=B4=EB=84=88?= =?UTF-8?q?=EC=8A=A4=20=EB=B2=88=ED=98=B8=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 1~45 범위를 벗어나는 경우 예외 - 당첨 번호와 중복인 경우 예외 --- README.md | 3 ++- src/model/validator.js | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 77acb13f2..19076abda 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ - [x] `1`~`45` 범위를 벗어나는 경우 예외 - [x] 중복된 번호가 있는 경우 예외 - [x] 보너스 번호 입력 - - [ ] 당첨 번호와 중복인 경우 예외 + - [x] `1`~`45` 범위를 벗어나는 경우 예외 + - [x] 당첨 번호와 중복인 경우 예외 ### 로또 발행 - [ ] `구입 금액 / 1000`개의 로또 발행 diff --git a/src/model/validator.js b/src/model/validator.js index bf8af6e8b..9c5a07a5d 100644 --- a/src/model/validator.js +++ b/src/model/validator.js @@ -33,6 +33,15 @@ const validator = { throw new Error("[ERROR] 중복된 당첨 번호가 있습니다."); } }, + + validateBonusNumber(bonus, winningNumbers) { + if (bonus < 1 || bonus > 45) { + throw new Error("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다."); + } + if (winningNumbers.includes(bonus)) { + throw new Error("[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다."); + } + }, } export default validator; \ No newline at end of file From 93aec44f1e8143558f28a547547c624ed3d9e7e7 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 20:23:03 +0900 Subject: [PATCH 13/28] =?UTF-8?q?test(validator):=20=EB=B3=B4=EB=84=88?= =?UTF-8?q?=EC=8A=A4=20=EB=B2=88=ED=98=B8=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/validator.test.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/__tests__/validator.test.js b/__tests__/validator.test.js index 83cbf48eb..985a13313 100644 --- a/__tests__/validator.test.js +++ b/__tests__/validator.test.js @@ -52,4 +52,34 @@ describe("Validator.validateWinningNumbers()", () => { test("중복된 번호가 있을 경우 에러가 발생한다.", () => { expect(() => validator.validateWinningNumbers([1, 2, 3, 3, 4, 5])).toThrow("[ERROR] 중복된 당첨 번호가 있습니다."); }); +}); + +describe("Validator.validateBonusNumber()", () => { + test("정상 입력일 경우 에러가 발생하지 않는다.", () => { + const winningNumbers = [1, 2, 3, 4, 5, 6]; + expect(() => validator.validateBonusNumber(7, winningNumbers)).not.toThrow(); + expect(() => validator.validateBonusNumber(45, winningNumbers)).not.toThrow(); + }); + + test("보너스 번호가 1 미만일 경우 에러가 발생한다.", () => { + const winningNumbers = [10, 20, 30, 40, 41, 42]; + expect(() => validator.validateBonusNumber(0, winningNumbers)) + .toThrow("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다."); + expect(() => validator.validateBonusNumber(-5, winningNumbers)) + .toThrow("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다."); + }); + + test("보너스 번호가 45 초과일 경우 에러가 발생한다.", () => { + const winningNumbers = [1, 2, 3, 4, 5, 6]; + expect(() => validator.validateBonusNumber(46, winningNumbers)) + .toThrow("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다."); + expect(() => validator.validateBonusNumber(100, winningNumbers)) + .toThrow("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다."); + }); + + test("보너스 번호가 당첨 번호와 중복될 경우 에러가 발생한다.", () => { + const winningNumbers = [7, 8, 9, 10, 11, 12]; + expect(() => validator.validateBonusNumber(9, winningNumbers)) + .toThrow("[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다."); + }); }); \ No newline at end of file From 0a5009c42c5d3fd00c4536876bb2141cba21b569 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 20:26:15 +0900 Subject: [PATCH 14/28] =?UTF-8?q?feat(lottoController):=20=EB=B3=B4?= =?UTF-8?q?=EB=84=88=EC=8A=A4=20=EB=B2=88=ED=98=B8=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/LottoController.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index f3f11a988..9b9a1356d 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -5,6 +5,7 @@ class LottoController { async run() { const purchaseAmount = await this.getPurchaseAmount(); const winningNumbers = await this.getWinningNumbers(); + const bonusNumber = await this.getBonusNumber(winningNumbers); } async getPurchaseAmount() { @@ -20,6 +21,13 @@ class LottoController { validator.validateWinningNumbers(winningNumbers); return winningNumbers; } + + async getBonusNumber(winningNumbers) { + const input = await inputView.readBonusNumber(); + const bonusNumber = Number(input); + validator.validateBonusNumber(bonusNumber, winningNumbers); + return bonusNumber; + } } export default LottoController; \ No newline at end of file From a8ffde0946311a1592c421450eb4254dbe47501d Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 21:11:58 +0900 Subject: [PATCH 15/28] =?UTF-8?q?feat(lottogenerator):=20=EB=A1=9C?= =?UTF-8?q?=EB=98=90=20=EB=B0=9C=ED=96=89=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구입 금액/1000개의 로또 발행 - 각 로또는 1~45 범위의 중복되지 않은 6개의 랜덤 숫자로 구성 - 번호 오름차순 정렬 - LottoController에서 로또 발행 로직 연동 --- README.md | 6 +++--- src/controller/LottoController.js | 3 +++ src/{ => model}/Lotto.js | 2 -- src/model/lottoGenerator.js | 12 ++++++++++++ 4 files changed, 18 insertions(+), 5 deletions(-) rename src/{ => model}/Lotto.js (89%) create mode 100644 src/model/lottoGenerator.js diff --git a/README.md b/README.md index 19076abda..a43fd5126 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ - [x] 당첨 번호와 중복인 경우 예외 ### 로또 발행 -- [ ] `구입 금액 / 1000`개의 로또 발행 -- [ ] 각 로또는 `1`~`45` 범위의 중복되지 않은 6개의 숫자로 구성 -- [ ] 오름차순 정렬 +- [x] `구입 금액 / 1000`개의 로또 발행 +- [x] 각 로또는 `1`~`45` 범위의 중복되지 않은 6개의 랜덤 숫자로 구성 +- [x] 번호 오름차순 정렬 ### 당첨 결과 계산 - [ ] 로또의 당첨 번호 일치 개수에 따른 등수 판별 diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index 9b9a1356d..8611c6590 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -1,11 +1,14 @@ import inputView from "../view/inputView.js"; import validator from "../model/validator.js"; +import generateLottos from "../model/lottoGenerator.js"; class LottoController { async run() { const purchaseAmount = await this.getPurchaseAmount(); const winningNumbers = await this.getWinningNumbers(); const bonusNumber = await this.getBonusNumber(winningNumbers); + + const lottos = generateLottos(purchaseAmount); } async getPurchaseAmount() { diff --git a/src/Lotto.js b/src/model/Lotto.js similarity index 89% rename from src/Lotto.js rename to src/model/Lotto.js index cb0b1527e..6d86b165a 100644 --- a/src/Lotto.js +++ b/src/model/Lotto.js @@ -11,8 +11,6 @@ class Lotto { throw new Error("[ERROR] 로또 번호는 6개여야 합니다."); } } - - // TODO: 추가 기능 구현 } export default Lotto; diff --git a/src/model/lottoGenerator.js b/src/model/lottoGenerator.js new file mode 100644 index 000000000..4609874ac --- /dev/null +++ b/src/model/lottoGenerator.js @@ -0,0 +1,12 @@ +import { Random } from "@woowacourse/mission-utils"; +import Lotto from "./Lotto.js"; + +export function generateLottos(purchaseAmount) { + const count = purchaseAmount / 1000; + return Array.from({ length: count }, () => { + const numbers = Random.pickUniqueNumbersInRange(1, 45, 6).sort((a, b) => a - b); + return new Lotto(numbers); + }); +} + +export default generateLottos; \ No newline at end of file From a52a12571c4023294e3a004139034d3a03657766 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 21:12:14 +0900 Subject: [PATCH 16/28] =?UTF-8?q?test(lottogenerator):=20=EB=A1=9C?= =?UTF-8?q?=EB=98=90=20=EB=B0=9C=ED=96=89=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/LottoGenerator.test.js | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 __tests__/LottoGenerator.test.js diff --git a/__tests__/LottoGenerator.test.js b/__tests__/LottoGenerator.test.js new file mode 100644 index 000000000..71ec1c8d1 --- /dev/null +++ b/__tests__/LottoGenerator.test.js @@ -0,0 +1,38 @@ +import { Random } from "@woowacourse/mission-utils"; +import { generateLottos } from "../src/model/lottoGenerator.js"; +import Lotto from "../src/model/Lotto.js"; + +jest.mock("@woowacourse/mission-utils", () => ({ + Random: { + pickUniqueNumbersInRange: jest.fn(), + }, +})); + +describe("generateLottos()", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test("구입 금액에 따라 Lotto 인스턴스를 생성한다.", () => { + Random.pickUniqueNumbersInRange.mockReturnValue([1, 2, 3, 4, 5, 6]); + const purchaseAmount = 3000; + + const lottos = generateLottos(purchaseAmount); + + expect(lottos).toHaveLength(3); + lottos.forEach((lotto) => { + expect(lotto).toBeInstanceOf(Lotto); + }); + }); + + test("각 로또는 1~45 범위의 중복되지 않은 6개의 오름차순 숫자로 구성된다.", () => { + Random.pickUniqueNumbersInRange.mockReturnValue([45, 1, 12, 8, 30, 5]); + + const lottos = generateLottos(1000); + const numbers = lottos[0].getNumbers ? lottos[0].getNumbers() : undefined; + + if (numbers) { + expect(numbers).toEqual([1, 5, 8, 12, 30, 45]); + } + }); +}); \ No newline at end of file From 3ed828834810027da18be1eebac82e7965badb01 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 21:45:55 +0900 Subject: [PATCH 17/28] =?UTF-8?q?feat(calculateresult):=20=EB=8B=B9?= =?UTF-8?q?=EC=B2=A8=20=EA=B2=B0=EA=B3=BC=20=EA=B3=84=EC=82=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PRIZE_TABLE 상수 추가로 조건, 등수, 상금 정의 - calculateResult에 당첨 결과 기능 추가 - lottoController에 calculateResult 연결 --- README.md | 2 +- src/controller/LottoController.js | 2 ++ src/model/calculateResult.js | 21 +++++++++++++++++++++ src/model/constants.js | 7 +++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/model/calculateResult.js create mode 100644 src/model/constants.js diff --git a/README.md b/README.md index a43fd5126..f2f666e8c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ - [x] 번호 오름차순 정렬 ### 당첨 결과 계산 -- [ ] 로또의 당첨 번호 일치 개수에 따른 등수 판별 +- [x] 로또 번호에 따른 당첨 결과 계산 - 1등: 6개 번호 일치 / 2,000,000,000원 - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 - 3등: 5개 번호 일치 / 1,500,000원 diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index 8611c6590..688b2f266 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -1,6 +1,7 @@ import inputView from "../view/inputView.js"; import validator from "../model/validator.js"; import generateLottos from "../model/lottoGenerator.js"; +import calculateResult from "../model/calculateResult.js"; class LottoController { async run() { @@ -9,6 +10,7 @@ class LottoController { const bonusNumber = await this.getBonusNumber(winningNumbers); const lottos = generateLottos(purchaseAmount); + const result = calculateResult(lottos, winningNumbers, bonusNumber); } async getPurchaseAmount() { diff --git a/src/model/calculateResult.js b/src/model/calculateResult.js new file mode 100644 index 000000000..80d692fc7 --- /dev/null +++ b/src/model/calculateResult.js @@ -0,0 +1,21 @@ +import { PRIZE_TABLE } from "./constants.js"; + +export default function calculateResult(lottos, winningNumbers, bonusNumber) { + const result = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }; + + lottos.forEach((lotto) => { + const lottoNumbers = lotto.getNumbers(); + const matchCount = lottoNumbers.filter((num) => + winningNumbers.includes(num) + ).length; + const hasBonus = lottoNumbers.includes(bonusNumber); + + const prize = PRIZE_TABLE.find( + (prize) => prize.match === matchCount && (!!prize.bonus === hasBonus) + ); + + if (prize) result[prize.rank]++; + }); + + return result; +} \ No newline at end of file diff --git a/src/model/constants.js b/src/model/constants.js new file mode 100644 index 000000000..0f19f3d22 --- /dev/null +++ b/src/model/constants.js @@ -0,0 +1,7 @@ +export const PRIZE_TABLE = [ + { match: 6, bonus: false, rank: 1, amount: 2_000_000_000 }, + { match: 5, bonus: true, rank: 2, amount: 30_000_000 }, + { match: 5, bonus: false, rank: 3, amount: 1_500_000 }, + { match: 4, bonus: false, rank: 4, amount: 50_000 }, + { match: 3, bonus: false, rank: 5, amount: 5_000 }, +]; \ No newline at end of file From fdc331c5fbc3d8eb0e7e3b5d58b7b85b8894b280 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 21:46:05 +0900 Subject: [PATCH 18/28] =?UTF-8?q?test(calculateresult):=20=EB=8B=B9?= =?UTF-8?q?=EC=B2=A8=20=EA=B2=B0=EA=B3=BC=20=EA=B3=84=EC=82=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/calculateResult.test.js | 63 +++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 __tests__/calculateResult.test.js diff --git a/__tests__/calculateResult.test.js b/__tests__/calculateResult.test.js new file mode 100644 index 000000000..f4c8ec1ea --- /dev/null +++ b/__tests__/calculateResult.test.js @@ -0,0 +1,63 @@ +import calculateResult from "../src/model/calculateResult.js"; + +const createLotto = (numbers) => ({ + getNumbers: () => numbers, +}); + +describe("calculateResult", () => { + test("6개 번호가 모두 일치하면 1등 1개를 반환한다.", () => { + const lottos = [createLotto([1, 2, 3, 4, 5, 6])]; + const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7); + + expect(result).toEqual({ 1: 1, 2: 0, 3: 0, 4: 0, 5: 0 }); + }); + + test("5개 번호 + 보너스 번호가 일치하면 2등 1개를 반환한다.", () => { + const lottos = [createLotto([1, 2, 3, 4, 5, 7])]; + const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7); + + expect(result).toEqual({ 1: 0, 2: 1, 3: 0, 4: 0, 5: 0 }); + }); + + test("5개 번호가 일치하면 3등 1개를 반환한다.", () => { + const lottos = [createLotto([1, 2, 3, 4, 5, 9])]; + const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7); + + expect(result).toEqual({ 1: 0, 2: 0, 3: 1, 4: 0, 5: 0 }); + }); + + test("4개 번호가 일치하면 4등 1개를 반환한다.", () => { + const lottos = [createLotto([1, 2, 3, 4, 10, 11])]; + const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7); + + expect(result).toEqual({ 1: 0, 2: 0, 3: 0, 4: 1, 5: 0 }); + }); + + test("3개 번호가 일치하면 5등 1개를 반환한다.", () => { + const lottos = [createLotto([1, 2, 3, 10, 11, 12])]; + const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7); + + expect(result).toEqual({ 1: 0, 2: 0, 3: 0, 4: 0, 5: 1 }); + }); + + test("2개 이하로 일치하면 등수에 포함되지 않는다.", () => { + const lottos = [createLotto([1, 2, 10, 11, 12, 13])]; + const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7); + + expect(result).toEqual({ 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }); + }); + + test("여러 장의 로또 결과가 등수별로 누적된다.", () => { + const lottos = [ + createLotto([1, 2, 3, 4, 5, 6]), // 1등 + createLotto([1, 2, 3, 4, 5, 7]), // 2등 + createLotto([1, 2, 3, 4, 5, 9]), // 3등 + createLotto([1, 2, 3, 4, 10, 11]), // 4등 + createLotto([1, 2, 3, 10, 11, 12]), // 5등 + ]; + + const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7); + + expect(result).toEqual({ 1: 1, 2: 1, 3: 1, 4: 1, 5: 1 }); + }); +}); \ No newline at end of file From 4360733089c8685e0b65a6d4ea1e366b9610af43 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 21:46:21 +0900 Subject: [PATCH 19/28] =?UTF-8?q?refactor(calculateresult):=20=EB=A1=9C?= =?UTF-8?q?=EB=98=90=20=EA=B2=B0=EA=B3=BC=20=EA=B3=84=EC=82=B0=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=ED=95=A8=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - calculateResult() -> initResult(), analyzeLotto(), findPrize()로 분리 --- src/model/calculateResult.js | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/model/calculateResult.js b/src/model/calculateResult.js index 80d692fc7..000c9203d 100644 --- a/src/model/calculateResult.js +++ b/src/model/calculateResult.js @@ -1,21 +1,30 @@ import { PRIZE_TABLE } from "./constants.js"; export default function calculateResult(lottos, winningNumbers, bonusNumber) { - const result = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }; + const result = initResult(); lottos.forEach((lotto) => { - const lottoNumbers = lotto.getNumbers(); - const matchCount = lottoNumbers.filter((num) => - winningNumbers.includes(num) - ).length; - const hasBonus = lottoNumbers.includes(bonusNumber); - - const prize = PRIZE_TABLE.find( - (prize) => prize.match === matchCount && (!!prize.bonus === hasBonus) - ); - + const { matchCount, hasBonus } = analyzeLotto(lotto, winningNumbers, bonusNumber); + const prize = findPrize(matchCount, hasBonus); if (prize) result[prize.rank]++; }); return result; +} + +function initResult() { + return { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }; +} + +function analyzeLotto(lotto, winningNumbers, bonusNumber) { + const lottoNumbers = lotto.getNumbers(); + const matchCount = lottoNumbers.filter((num) => winningNumbers.includes(num)).length; + const hasBonus = lottoNumbers.includes(bonusNumber); + return { matchCount, hasBonus }; +} + +function findPrize(matchCount, hasBonus) { + return PRIZE_TABLE.find( + (p) => p.match === matchCount && (!!p.bonus === hasBonus) + ); } \ No newline at end of file From e1c70f235fc76aa8c88bf9e4227a283d449b7f51 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 22:11:52 +0900 Subject: [PATCH 20/28] =?UTF-8?q?feat(calculateprofit):=20=EC=88=98?= =?UTF-8?q?=EC=9D=B5=EB=A5=A0=20=EA=B3=84=EC=82=B0=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- src/controller/LottoController.js | 2 ++ src/model/calculateProfit.js | 12 ++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 src/model/calculateProfit.js diff --git a/README.md b/README.md index f2f666e8c..91637efeb 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ - 3등: 5개 번호 일치 / 1,500,000원 - 4등: 4개 번호 일치 / 50,000원 - 5등: 3개 번호 일치 / 5,000원 -- [ ] 수익률 계산 - - [ ] 소수점 둘째 자리 반올림 +- [x] 수익률 계산 + - [x] 소수점 둘째 자리 반올림 ### 출력 - [ ] 구매한 로또 번호 출력 diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index 688b2f266..729148fbe 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -2,6 +2,7 @@ import inputView from "../view/inputView.js"; import validator from "../model/validator.js"; import generateLottos from "../model/lottoGenerator.js"; import calculateResult from "../model/calculateResult.js"; +import calculateProfit from "../model/calculateProfit.js"; class LottoController { async run() { @@ -11,6 +12,7 @@ class LottoController { const lottos = generateLottos(purchaseAmount); const result = calculateResult(lottos, winningNumbers, bonusNumber); + const profitRate = calculateProfit(result, purchaseAmount); } async getPurchaseAmount() { diff --git a/src/model/calculateProfit.js b/src/model/calculateProfit.js new file mode 100644 index 000000000..72de38f3d --- /dev/null +++ b/src/model/calculateProfit.js @@ -0,0 +1,12 @@ +import { PRIZE_TABLE } from "./constants.js"; + +export default function calculateProfit(result, purchaseAmount) { + let totalPrize = 0; + Object.keys(result).forEach((rank) => { + const count = result[rank]; + const prize = PRIZE_TABLE.find((p) => p.rank === Number(rank)); + if (prize) totalPrize += prize.amount * count; + }); + const rate = (totalPrize / purchaseAmount) * 100; + return Number(rate.toFixed(1)); +} \ No newline at end of file From dfaaf2c58c7c63d89049d0deece6ee7a41d971a5 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 22:12:06 +0900 Subject: [PATCH 21/28] =?UTF-8?q?test(calculateprofit):=20=EC=88=98?= =?UTF-8?q?=EC=9D=B5=EB=A5=A0=20=EA=B3=84=EC=82=B0=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/calculateProfit.test.js | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 __tests__/calculateProfit.test.js diff --git a/__tests__/calculateProfit.test.js b/__tests__/calculateProfit.test.js new file mode 100644 index 000000000..65aed3743 --- /dev/null +++ b/__tests__/calculateProfit.test.js @@ -0,0 +1,54 @@ +import calculateProfit from "../src/model/calculateProfit.js"; + +describe("calculateProfit", () => { + test("당첨 내역이 없으면 수익률은 0%", () => { + const result = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }; + const purchaseAmount = 10000; + const rate = calculateProfit(result, purchaseAmount); + + expect(rate).toBe(0); + }); + + test("1등 1개면 200,000,000%", () => { + const result = { 1: 1, 2: 0, 3: 0, 4: 0, 5: 0 }; + const purchaseAmount = 1000; + const rate = calculateProfit(result, purchaseAmount); + + const expected = Number(((2000000000 / 1000) * 100).toFixed(1)); + expect(rate).toBe(expected); + }); + + test("3등 2개면 (1,500,000 × 2) / 10,000 * 100 = 30,000%", () => { + const result = { 1: 0, 2: 0, 3: 2, 4: 0, 5: 0 }; + const purchaseAmount = 10000; + const rate = calculateProfit(result, purchaseAmount); + + expect(rate).toBe(30000); + }); + + test("5등 5개면 (5,000 × 5) / 5,000 * 100 = 500%", () => { + const result = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 5 }; + const purchaseAmount = 5000; + const rate = calculateProfit(result, purchaseAmount); + + expect(rate).toBe(500); + }); + + test("1등 1명, 3등 2명, 5등 3명일 때 총 상금은 2,003,015,000원이다.", () => { + const result = { 1: 1, 2: 0, 3: 2, 4: 0, 5: 3 }; + const purchaseAmount = 10000; + const expected = Number(((2003015000 / purchaseAmount) * 100).toFixed(1)); + const rate = calculateProfit(result, purchaseAmount); + + expect(rate).toBe(expected); + }); + + test("결과값은 소수점 한 자리까지 반올림된다.", () => { + const result = { 1: 0, 2: 1, 3: 0, 4: 0, 5: 0 }; + const purchaseAmount = 3333; + const rate = calculateProfit(result, purchaseAmount); + + const expected = Number(((30000000 / 3333) * 100).toFixed(1)); + expect(rate).toBe(expected); + }); +}); \ No newline at end of file From f3e0dbad158b364dc46f95b73f12cec2f2e4d002 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 22:12:47 +0900 Subject: [PATCH 22/28] =?UTF-8?q?feat(lotto):=20getNumbers=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/Lotto.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/model/Lotto.js b/src/model/Lotto.js index 6d86b165a..87d6db3f2 100644 --- a/src/model/Lotto.js +++ b/src/model/Lotto.js @@ -11,6 +11,10 @@ class Lotto { throw new Error("[ERROR] 로또 번호는 6개여야 합니다."); } } + + getNumbers() { + return this.#numbers; + } } export default Lotto; From 4950744e5f996058a4f7c766d7414d1b76d28fab Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 22:15:14 +0900 Subject: [PATCH 23/28] =?UTF-8?q?feat(outputView):=20=EA=B5=AC=EB=A7=A4?= =?UTF-8?q?=ED=95=9C=20=EB=A1=9C=EB=98=90=20=EB=B2=88=ED=98=B8=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/controller/LottoController.js | 5 ++++- src/view/outputView.js | 12 ++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 src/view/outputView.js diff --git a/README.md b/README.md index 91637efeb..92ac1a979 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ - [x] 소수점 둘째 자리 반올림 ### 출력 -- [ ] 구매한 로또 번호 출력 +- [x] 구매한 로또 번호 출력 - [ ] 당첨 통계 출력 - [ ] 수익률 출력 - [ ] 예외 발생 시 `[ERROR]`로 시작하는 에러 메시지 출력 \ No newline at end of file diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index 729148fbe..ca963c4c0 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -1,4 +1,5 @@ import inputView from "../view/inputView.js"; +import outputView from "../view/outputView.js"; import validator from "../model/validator.js"; import generateLottos from "../model/lottoGenerator.js"; import calculateResult from "../model/calculateResult.js"; @@ -7,10 +8,12 @@ import calculateProfit from "../model/calculateProfit.js"; class LottoController { async run() { const purchaseAmount = await this.getPurchaseAmount(); + const lottos = generateLottos(purchaseAmount); + outputView.printPurchasedLottos(lottos); + const winningNumbers = await this.getWinningNumbers(); const bonusNumber = await this.getBonusNumber(winningNumbers); - const lottos = generateLottos(purchaseAmount); const result = calculateResult(lottos, winningNumbers, bonusNumber); const profitRate = calculateProfit(result, purchaseAmount); } diff --git a/src/view/outputView.js b/src/view/outputView.js new file mode 100644 index 000000000..62805e0ae --- /dev/null +++ b/src/view/outputView.js @@ -0,0 +1,12 @@ +import { Console } from "@woowacourse/mission-utils"; + +const outputView = { + printPurchasedLottos(lottos) { + Console.print(`\n${lottos.length}개를 구매했습니다.`); + lottos.forEach((lotto) => { + Console.print(`[${lotto.getNumbers().join(", ")}]`); + }); + }, +}; + +export default outputView; \ No newline at end of file From d242a24cda75ab729509055e2c0c8969128780f1 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 22:23:36 +0900 Subject: [PATCH 24/28] =?UTF-8?q?feat(outputView):=20=EB=8B=B9=EC=B2=A8=20?= =?UTF-8?q?=ED=86=B5=EA=B3=84,=20=EC=88=98=EC=9D=B5=EB=A5=A0=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- src/controller/LottoController.js | 2 ++ src/view/outputView.js | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 92ac1a979..87b87d38f 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,6 @@ ### 출력 - [x] 구매한 로또 번호 출력 -- [ ] 당첨 통계 출력 -- [ ] 수익률 출력 +- [x] 당첨 통계 출력 +- [x] 수익률 출력 - [ ] 예외 발생 시 `[ERROR]`로 시작하는 에러 메시지 출력 \ No newline at end of file diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index ca963c4c0..a9455ac81 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -16,6 +16,8 @@ class LottoController { const result = calculateResult(lottos, winningNumbers, bonusNumber); const profitRate = calculateProfit(result, purchaseAmount); + + outputView.printResult(result, profitRate); } async getPurchaseAmount() { diff --git a/src/view/outputView.js b/src/view/outputView.js index 62805e0ae..880992d3e 100644 --- a/src/view/outputView.js +++ b/src/view/outputView.js @@ -1,4 +1,5 @@ import { Console } from "@woowacourse/mission-utils"; +import { PRIZE_TABLE } from "../model/constants.js"; const outputView = { printPurchasedLottos(lottos) { @@ -7,6 +8,23 @@ const outputView = { Console.print(`[${lotto.getNumbers().join(", ")}]`); }); }, + + printResult(result, profitRate) { + Console.print("\n당첨 통계"); + Console.print("----------"); + + PRIZE_TABLE.forEach(({ match, bonus, amount, rank }) => { + let label = `${match}개 일치`; + if (bonus) { + label += ", 보너스 볼 일치"; + } + label += ` (${amount.toLocaleString()}원) - ${result[rank]}개`; + + Console.print(label); + }); + + Console.print(`총 수익률은 ${profitRate}%입니다.`); + }, }; export default outputView; \ No newline at end of file From 1f1ff85832c8c32c2733324ef11a624a1b553144 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 22:29:36 +0900 Subject: [PATCH 25/28] =?UTF-8?q?refactor(outputview):=20printResult=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - printResult() -> printStatistics(), printProfitRate() --- src/view/outputView.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/view/outputView.js b/src/view/outputView.js index 880992d3e..2055c2790 100644 --- a/src/view/outputView.js +++ b/src/view/outputView.js @@ -12,7 +12,11 @@ const outputView = { printResult(result, profitRate) { Console.print("\n당첨 통계"); Console.print("----------"); + this.printStatistics(result); + this.printProfitRate(profitRate); + }, + printStatistics(result) { PRIZE_TABLE.forEach(({ match, bonus, amount, rank }) => { let label = `${match}개 일치`; if (bonus) { @@ -22,7 +26,9 @@ const outputView = { Console.print(label); }); - + }, + + printProfitRate(profitRate) { Console.print(`총 수익률은 ${profitRate}%입니다.`); }, }; From 528000feea660c35fa7fdb7f913d9460887aa8b2 Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 23:00:57 +0900 Subject: [PATCH 26/28] =?UTF-8?q?fix(lotto):=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EA=B0=80=20=ED=86=B5=EA=B3=BC=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/LottoTest.js | 5 +---- src/model/Lotto.js | 5 +++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index 409aaf69b..b6dd22eb8 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -1,4 +1,4 @@ -import Lotto from "../src/Lotto"; +import Lotto from "../src/model/Lotto"; describe("로또 클래스 테스트", () => { test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => { @@ -7,12 +7,9 @@ describe("로또 클래스 테스트", () => { }).toThrow("[ERROR]"); }); - // TODO: 테스트가 통과하도록 프로덕션 코드 구현 test("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.", () => { expect(() => { new Lotto([1, 2, 3, 4, 5, 5]); }).toThrow("[ERROR]"); }); - - // TODO: 추가 기능 구현에 따른 테스트 코드 작성 }); diff --git a/src/model/Lotto.js b/src/model/Lotto.js index 87d6db3f2..fdbc6353f 100644 --- a/src/model/Lotto.js +++ b/src/model/Lotto.js @@ -10,6 +10,11 @@ class Lotto { if (numbers.length !== 6) { throw new Error("[ERROR] 로또 번호는 6개여야 합니다."); } + + const uniqueNumbers = new Set(numbers); + if (uniqueNumbers.size !== numbers.length) { + throw new Error("[ERROR] 로또 번호에 중복된 숫자가 있습니다."); + } } getNumbers() { From ef270ff5d799beae27fbf783a7d77a76c6249f3e Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 23:01:28 +0900 Subject: [PATCH 27/28] =?UTF-8?q?fix(lottocontroller):=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC=EA=B0=80=20=EC=A0=9C=EB=8C=80?= =?UTF-8?q?=EB=A1=9C=20=EB=90=98=EB=8F=84=EB=A1=9D=20try,=20catch=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/LottoController.js | 40 +++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js index a9455ac81..4d928a279 100644 --- a/src/controller/LottoController.js +++ b/src/controller/LottoController.js @@ -1,3 +1,4 @@ +import { Console } from "@woowacourse/mission-utils"; import inputView from "../view/inputView.js"; import outputView from "../view/outputView.js"; import validator from "../model/validator.js"; @@ -21,24 +22,39 @@ class LottoController { } async getPurchaseAmount() { - const input = await inputView.readPurchaseAmount(); - const purchaseAmount = Number(input); - validator.validatePurchaseAmount(purchaseAmount); - return purchaseAmount; + try { + const input = await inputView.readPurchaseAmount(); + const purchaseAmount = Number(input); + validator.validatePurchaseAmount(purchaseAmount); + return purchaseAmount; + } catch (error) { + Console.print(error.message); + return this.getPurchaseAmount(); + } } async getWinningNumbers() { - const input = await inputView.readWinningNumbers(); - const winningNumbers = input.split(",").map((num) => Number(num.trim())); - validator.validateWinningNumbers(winningNumbers); - return winningNumbers; + try { + const input = await inputView.readWinningNumbers(); + const winningNumbers = input.split(",").map((num) => Number(num.trim())); + validator.validateWinningNumbers(winningNumbers); + return winningNumbers; + } catch (error) { + Console.print(error.message); + return this.getWinningNumbers(); + } } async getBonusNumber(winningNumbers) { - const input = await inputView.readBonusNumber(); - const bonusNumber = Number(input); - validator.validateBonusNumber(bonusNumber, winningNumbers); - return bonusNumber; + try { + const input = await inputView.readBonusNumber(); + const bonusNumber = Number(input); + validator.validateBonusNumber(bonusNumber, winningNumbers); + return bonusNumber; + } catch (error) { + Console.print(error.message); + return this.getBonusNumber(winningNumbers); + } } } From e985655b313aaf843faaa7d4dd0d85dacfd16d2f Mon Sep 17 00:00:00 2001 From: nunomi0 Date: Mon, 3 Nov 2025 23:09:04 +0900 Subject: [PATCH 28/28] =?UTF-8?q?docs(readme):=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=86=8C=EA=B0=9C,=20=EC=8B=A4=ED=96=89=20=EC=98=88=EC=8B=9C,?= =?UTF-8?q?=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EA=B5=AC=EC=A1=B0,?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC,=20=EA=B3=A0?= =?UTF-8?q?=EB=A0=A4=ED=95=9C=20=EB=B6=80=EB=B6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 87b87d38f..fdf702f01 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,75 @@ # javascript-lotto-precourse +로또 구매부터 당첨 결과 확인까지 전 과정을 시뮬레이션하는 JavaScript 애플리케이션입니다. + +## 기능 소개 + +1. **로또 구매**: 1,000원 단위로 로또를 구매합니다. +2. **자동 번호 생성**: 1~45 사이의 중복되지 않는 6개의 번호를 자동으로 생성합니다. +3. **당첨 확인**: 구매한 로또와 당첨 번호를 비교하여 당첨 결과를 확인합니다. +4. **수익률 계산**: 총 수익률을 계산하여 표시합니다. + +## 실행 예시 + +``` +구입금액을 입력해 주세요. +8000 + +8개를 구매했습니다. +[8, 21, 23, 41, 42, 43] +[3, 5, 11, 16, 32, 38] +[7, 11, 16, 35, 36, 44] +[1, 8, 11, 31, 41, 42] +[13, 14, 16, 38, 42, 45] +[7, 11, 30, 40, 42, 43] +[2, 13, 22, 32, 38, 45] +[1, 3, 5, 14, 22, 45] + +당첨 번호를 입력해 주세요. +1,2,3,4,5,6 + +보너스 번호를 입력해 주세요. +7 + +당첨 통계 +---------- +3개 일치 (5,000원) - 1개 +4개 일치 (50,000원) - 0개 +5개 일치 (1,500,000원) - 0개 +5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 +6개 일치 (2,000,000,000원) - 0개 +총 수익률은 62.5%입니다. +``` + +## 프로젝트 구조 + +``` +src/ +├── App.js # 애플리케이션 진입점 +├── index.js # 프로그램 실행 +├── controller/ +│ └── LottoController.js # 로또 게임 로직 제어 +├── model/ +│ ├── Lotto.js # 로또 클래스 +│ ├── lottoGenerator.js # 로또 생성 로직 +│ ├── calculateResult.js # 당첨 결과 계산 +│ ├── calculateProfit.js # 수익률 계산 +│ ├── validator.js # 입력 검증 +│ └── constants.js # 상수 정의 +└── view/ + ├── inputView.js # 사용자 입력 처리 + └── outputView.js # 결과 출력 처리 + +__tests__/ +├── ApplicationTest.js # 통합 테스트 +├── LottoTest.js # 로또 클래스 테스트 +├── validator.test.js # 유효성 검증 테스트 +├── LottoGenerator.test.js # 로또 생성 테스트 +├── calculateResult.test.js # 결과 계산 테스트 +└── calculateProfit.test.js # 수익률 계산 테스트 +``` + +## 구현 기능 목록 -## 기능 ### 입력 - [x] 로또 구입 금액 입력 - [x] 숫자가 아닌 경우 예외 @@ -34,4 +103,36 @@ - [x] 구매한 로또 번호 출력 - [x] 당첨 통계 출력 - [x] 수익률 출력 -- [ ] 예외 발생 시 `[ERROR]`로 시작하는 에러 메시지 출력 \ No newline at end of file +- [x] 예외 발생 시 `[ERROR]`로 시작하는 에러 메시지 출력 + +## 예외 처리 + +모든 예외 상황에서 `[ERROR]`로 시작하는 에러 메시지를 출력하고, 올바른 값을 재입력받습니다. + +### 구입 금액 예외 +- 숫자가 아닌 값 입력 시: `[ERROR] 구입 금액은 숫자여야 합니다.` +- 정수가 아닌 값 입력 시: `[ERROR] 구입 금액은 정수여야 합니다.` +- 0 이하의 값 입력 시: `[ERROR] 구입 금액은 0보다 커야 합니다.` +- 1,000원 단위가 아닌 경우: `[ERROR] 구입 금액은 1000원 단위여야 합니다.` + +### 당첨 번호 예외 +- 6개가 아닌 경우: `[ERROR] 당첨 번호는 6개여야 합니다.` +- 숫자가 아닌 값 포함 시: `[ERROR] 당첨 번호는 숫자여야 합니다.` +- 1~45 범위를 벗어난 경우: `[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.` +- 중복된 번호가 있는 경우: `[ERROR] 중복된 당첨 번호가 있습니다.` + +### 보너스 번호 예외 +- 1~45 범위를 벗어난 경우: `[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다.` +- 당첨 번호와 중복인 경우: `[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다.` + +### 로또 클래스 예외 +- 번호가 6개가 아닌 경우: `[ERROR] 로또 번호는 6개여야 합니다.` +- 중복된 번호가 있는 경우: `[ERROR] 로또 번호에 중복된 숫자가 있습니다.` + +## 설계 시 고려한 부분 + +1. **MVC 패턴**: Controller, Model, View를 분리하여 관심사의 분리 원칙을 준수합니다. +2. **단일 책임 원칙**: 각 모듈은 하나의 책임만 가집니다. +3. **입력 검증**: 모든 사용자 입력은 validator를 통해 검증됩니다. +4. **에러 처리**: 예외 발생 시 적절한 에러 메시지를 표시합니다. +5. **테스트**: 단위 테스트와 통합 테스트를 통해 코드의 안정성을 보장합니다. \ No newline at end of file