From 2449fb29c95ae4ce7adce837c5c769a867dcf50d Mon Sep 17 00:00:00 2001 From: HareuBang Date: Sat, 10 Jan 2026 13:06:23 +0900 Subject: [PATCH 01/12] chore: run npm install --- package-lock.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index 328e25a1..8b17a6cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2985,6 +2986,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001669", "electron-to-chromium": "^1.5.41", From 7d794c6308b784385603d5eed5f91bd18fe04e11 Mon Sep 17 00:00:00 2001 From: HareuBang Date: Sat, 10 Jan 2026 13:14:54 +0900 Subject: [PATCH 02/12] =?UTF-8?q?docs:=20README.md=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=97=90=20=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index b168a180..e08ecf4b 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ # javascript-planetlotto-precourse + +## 기능 목록 + +- [ ] 구입 금액에 해당하는 만큼 발행할 로또 수량을 정한다. +- [ ] 로또 수량만큼 중복되지 않은 5개의 숫자로 구성된 로또를 발행한다. +- [ ] 당첨 번호와 발행된 로또를 비교한다. +- [ ] 4개의 번호가 일치하면 보너스 번호와 비교한다. +- [ ] 비교된 결과값을 바탕으로 당첨 내역을 계산한다. +- [ ] 사용자가 잘못된 값을 입력하면 에러 메시지와 Error를 발생시키고 재입력을 받는다. From e9fa10e56a050995464303657548d0e1fcd32d66 Mon Sep 17 00:00:00 2001 From: HareuBang Date: Sat, 10 Jan 2026 13:25:33 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=EA=B5=AC=EC=9E=85=20=EA=B8=88?= =?UTF-8?q?=EC=95=A1=EC=97=90=20=ED=95=B4=EB=8B=B9=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A7=8C=ED=81=BC=EC=9D=98=20=EC=88=98=EB=9F=89=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/LottoMachine.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/model/LottoMachine.js diff --git a/src/model/LottoMachine.js b/src/model/LottoMachine.js new file mode 100644 index 00000000..7d1cd3ef --- /dev/null +++ b/src/model/LottoMachine.js @@ -0,0 +1,17 @@ +export default class LottoMachine { + #quantity; + + constructor(purchase) { + this.#validatePurchase(purchase); + + this.#quantity = purchase / 500; + } + + #validatePurchase(purchase) { + if (purchase % 500 !== 0) throw new DomainError("INVALID_PURCHASE"); + } + + getQuantity() { + return this.#quantity; + } +} From 7c718afca68647a2942842a849434ffb1ef00abe Mon Sep 17 00:00:00 2001 From: HareuBang Date: Sat, 10 Jan 2026 13:26:44 +0900 Subject: [PATCH 04/12] =?UTF-8?q?docs:=20=EA=B5=AC=EC=9E=85=20=EA=B8=88?= =?UTF-8?q?=EC=95=A1=EC=97=90=20=ED=95=B4=EB=8B=B9=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A7=8C=ED=81=BC=20=EC=88=98=EB=9F=89=20=EA=B3=84=EC=82=B0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e08ecf4b..0d06332c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## 기능 목록 -- [ ] 구입 금액에 해당하는 만큼 발행할 로또 수량을 정한다. +- [x] 구입 금액에 해당하는 만큼 발행할 로또 수량을 정한다. - [ ] 로또 수량만큼 중복되지 않은 5개의 숫자로 구성된 로또를 발행한다. - [ ] 당첨 번호와 발행된 로또를 비교한다. - [ ] 4개의 번호가 일치하면 보너스 번호와 비교한다. From 2083c92c04b5e78273df4d234c4402d2432bf326 Mon Sep 17 00:00:00 2001 From: HareuBang Date: Sat, 10 Jan 2026 13:37:45 +0900 Subject: [PATCH 05/12] =?UTF-8?q?test:=20LottoMachine=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EA=B5=AC=EC=9E=85=20=EA=B0=80=EA=B2=A9=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EC=99=80=20=EC=88=98=EB=9F=89=20=EA=B3=84=EC=82=B0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/LottoMachineTest.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 __tests__/LottoMachineTest.js diff --git a/__tests__/LottoMachineTest.js b/__tests__/LottoMachineTest.js new file mode 100644 index 00000000..59a64f57 --- /dev/null +++ b/__tests__/LottoMachineTest.js @@ -0,0 +1,13 @@ +import LottoMachine from "../src/model/LottoMachine.js"; + +describe("LottoMachine 클래스 테스트", () => { + test("로또 가격만큼 나누어 떨어지지 않으면 에러를 발생한다.", () => { + expect(() => { + new LottoMachine(600); + }).toThrow("INVALID_PURCHASE"); + }); + + test("로또 가격만큼 발행할 로또 수량을 계산한다.", () => { + expect(new LottoMachine(500).getQuantity()).toBe(1); + }); +}); From 7798a87147afe19d72878512e5410bb7ef5f5983 Mon Sep 17 00:00:00 2001 From: HareuBang Date: Sat, 10 Jan 2026 13:38:54 +0900 Subject: [PATCH 06/12] =?UTF-8?q?fix:=20DomainError=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=20=EB=B3=B4=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/LottoMachine.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/model/LottoMachine.js b/src/model/LottoMachine.js index 7d1cd3ef..ba9fdc1a 100644 --- a/src/model/LottoMachine.js +++ b/src/model/LottoMachine.js @@ -1,5 +1,8 @@ +// import { DomainError } from "../error/ApplicationError.js"; + export default class LottoMachine { #quantity; + #random; constructor(purchase) { this.#validatePurchase(purchase); @@ -8,10 +11,18 @@ export default class LottoMachine { } #validatePurchase(purchase) { - if (purchase % 500 !== 0) throw new DomainError("INVALID_PURCHASE"); + if (purchase % 500 !== 0) throw new Error("INVALID_PURCHASE"); } getQuantity() { return this.#quantity; } + + issue() { + const issueLotto = []; + + while (issueLotto.length < this.#quantity) {} + + return issueLotto; + } } From 703840e7c958c3565933f1511470b098d75e122d Mon Sep 17 00:00:00 2001 From: HareuBang Date: Sat, 10 Jan 2026 13:58:58 +0900 Subject: [PATCH 07/12] =?UTF-8?q?test:=20Lotto=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/LottoTest.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 __tests__/LottoTest.js diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js new file mode 100644 index 00000000..d5d68084 --- /dev/null +++ b/__tests__/LottoTest.js @@ -0,0 +1,31 @@ +import Lotto from "../src/model/Lotto.js"; + +describe("Lotto 클래스 테스트", () => { + test("중복된 숫자가 있으면 에러를 발생한다.", () => { + expect(() => { + new Lotto([1, 2, 3, 4, 4]); + }).toThrow("NUMBERS_DUPLICATION"); + }); + + test("1 ~ 30 사이의 숫자 범위에 들지 않으면 에러를 발생한다.", () => { + expect(() => { + new Lotto([1, 2, 3, 4, 31]); + }).toThrow("NUMBERIC_RANG"); + }); + + test("로또를 오름차순으로 반환한다.", () => { + expect(new Lotto([30, 1, 5, 3, 22]).getNumbers()).toEqual([ + 1, 3, 5, 22, 30, + ]); + }); + + test("비교할 로또와 같은 숫자들을 가지면 true를 반환한다.", () => { + expect( + new Lotto([1, 4, 8, 29, 10]).isEqual([1, 4, 8, 10, 29]) + ).toBeTruthy(); + }); + + test("비교할 로또와 다른 숫자들을 가지면 false를 반환한다.", () => { + expect(new Lotto([1, 4, 7, 10, 29]).isEqual([1, 4, 7, 10, 5])).toBeFalsy(); + }); +}); From cfd0775112dc6facbcd9e7002bc494863ef79b9c Mon Sep 17 00:00:00 2001 From: HareuBang Date: Sat, 10 Jan 2026 13:59:34 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20Lotto=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/Lotto.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/model/Lotto.js diff --git a/src/model/Lotto.js b/src/model/Lotto.js new file mode 100644 index 00000000..fbd6abb2 --- /dev/null +++ b/src/model/Lotto.js @@ -0,0 +1,25 @@ +export default class Lotto { + #numbers; + + constructor(numbers) { + this.#validateNumbers(numbers); + + this.#numbers = numbers; + } + + #validateNumbers(numbers) { + if (new Set(numbers).size !== numbers.length) + throw new Error("NUMBERS_DUPLICATION"); + + const isNumberRang = numbers.every((number) => 1 <= number && number <= 30); + if (!isNumberRang) throw new Error("NUMBERIC_RANG"); + } + + getNumbers() { + return this.#numbers.sort((a, b) => a - b); + } + + isEqual(compareLotto) { + return this.#numbers.every((number) => compareLotto.includes(number)); + } +} From c087a8b14bf62e187b1f403acc67bdb0891fdf8d Mon Sep 17 00:00:00 2001 From: HareuBang Date: Sat, 10 Jan 2026 14:18:50 +0900 Subject: [PATCH 09/12] =?UTF-8?q?test:=20LottoMachine=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=EB=A1=9C=20=EC=83=9D=EC=84=B1=EC=9E=90=20?= =?UTF-8?q?=EC=A3=BC=EC=9E=85=20=EB=B3=80=EA=B2=BD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B8=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/LottoMachineTest.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/__tests__/LottoMachineTest.js b/__tests__/LottoMachineTest.js index 59a64f57..21ba3aad 100644 --- a/__tests__/LottoMachineTest.js +++ b/__tests__/LottoMachineTest.js @@ -2,12 +2,14 @@ import LottoMachine from "../src/model/LottoMachine.js"; describe("LottoMachine 클래스 테스트", () => { test("로또 가격만큼 나누어 떨어지지 않으면 에러를 발생한다.", () => { + const purchase = 600; expect(() => { - new LottoMachine(600); + new LottoMachine({ purchase }); }).toThrow("INVALID_PURCHASE"); }); test("로또 가격만큼 발행할 로또 수량을 계산한다.", () => { - expect(new LottoMachine(500).getQuantity()).toBe(1); + const purchase = 1000; + expect(new LottoMachine({ purchase }).getQuantity()).toBe(2); }); }); From 44d1017393b2f2161099fa3b8227d6794656b58d Mon Sep 17 00:00:00 2001 From: HareuBang Date: Sat, 10 Jan 2026 14:20:49 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20=EC=88=98=EB=9F=89=EB=A7=8C?= =?UTF-8?q?=ED=81=BC=20=EB=A1=9C=EB=98=90=20=EB=B0=9C=ED=96=89=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/LottoMachine.js | 31 ++++++++++++++++++++++------ src/utils/LottoFactory.js | 7 +++++++ src/utils/randomPickUniqueNumbers.js | 6 ++++++ 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 src/utils/LottoFactory.js create mode 100644 src/utils/randomPickUniqueNumbers.js diff --git a/src/model/LottoMachine.js b/src/model/LottoMachine.js index ba9fdc1a..17a34fb7 100644 --- a/src/model/LottoMachine.js +++ b/src/model/LottoMachine.js @@ -1,28 +1,47 @@ // import { DomainError } from "../error/ApplicationError.js"; +import randomPickUniqueNumbers from "../utils/randompickUniqueNumbers.js"; +import LottoFactory from "../utils/LottoFactory.js"; + export default class LottoMachine { #quantity; - #random; - - constructor(purchase) { + #lottoFactory; + #randomFn; + + constructor({ + purchase, + lottoFactory = LottoFactory, + randomFn = randomPickUniqueNumbers, + } = {}) { this.#validatePurchase(purchase); this.#quantity = purchase / 500; + this.#lottoFactory = lottoFactory; + this.#randomFn = randomFn; } #validatePurchase(purchase) { if (purchase % 500 !== 0) throw new Error("INVALID_PURCHASE"); } + #checkAvailable(issueLottos, newLotto) { + return issueLottos.every((lotto) => lotto.isEqual(newLotto)); + } + getQuantity() { return this.#quantity; } issue() { - const issueLotto = []; + const issueLottos = []; + + while (issueLottos.length < this.#quantity) { + const newLotto = this.#lottoFactory.createLotto(this.#randomFn()); + const isAvailable = this.#checkAvailable(issueLottos, newLotto); - while (issueLotto.length < this.#quantity) {} + if (isAvailable) issueLottos.push(newLotto); + } - return issueLotto; + return issueLottos; } } diff --git a/src/utils/LottoFactory.js b/src/utils/LottoFactory.js new file mode 100644 index 00000000..3211eddb --- /dev/null +++ b/src/utils/LottoFactory.js @@ -0,0 +1,7 @@ +import Lotto from "../model/Lotto.js"; + +export default class LottoFactory { + static createLotto(numbers) { + return new Lotto(numbers); + } +} diff --git a/src/utils/randomPickUniqueNumbers.js b/src/utils/randomPickUniqueNumbers.js new file mode 100644 index 00000000..81cd4088 --- /dev/null +++ b/src/utils/randomPickUniqueNumbers.js @@ -0,0 +1,6 @@ +import { MissionUtils } from "@woowacourse/mission-utils"; + +const randomPickUniqueNumbers = () => + MissionUtils.Random.pickUniqueNumbersInRange(1, 30, 5); + +export default randomPickUniqueNumbers; From 9408a69dda17e5036a86d652522b0a891a1c1729 Mon Sep 17 00:00:00 2001 From: HareuBang Date: Sat, 10 Jan 2026 14:24:48 +0900 Subject: [PATCH 11/12] =?UTF-8?q?docs:=20=EB=A1=9C=EB=98=90=20=EC=88=98?= =?UTF-8?q?=EB=9F=89=EB=A7=8C=ED=81=BC=20=EB=A1=9C=EB=98=90=20=EB=B0=9C?= =?UTF-8?q?=ED=96=89=20=EA=B8=B0=EB=8A=A5=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d06332c..22e5b5aa 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## 기능 목록 - [x] 구입 금액에 해당하는 만큼 발행할 로또 수량을 정한다. -- [ ] 로또 수량만큼 중복되지 않은 5개의 숫자로 구성된 로또를 발행한다. +- [x] 로또 수량만큼 중복되지 않은 5개의 숫자로 구성된 로또를 발행한다. - [ ] 당첨 번호와 발행된 로또를 비교한다. - [ ] 4개의 번호가 일치하면 보너스 번호와 비교한다. - [ ] 비교된 결과값을 바탕으로 당첨 내역을 계산한다. From 1c78022e15b6649fc2e56bbe22ce7a5727329867 Mon Sep 17 00:00:00 2001 From: HareuBang Date: Sat, 10 Jan 2026 16:53:09 +0900 Subject: [PATCH 12/12] =?UTF-8?q?feat:=20=ED=96=89=EC=84=B1=20=EB=A1=9C?= =?UTF-8?q?=EB=98=90=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 14 +++++++- src/application/LottoController.js | 52 +++++++++++++++++++++++++++ src/application/LottoService.js | 31 +++++++++++++++++ src/constants/errorMessage.js | 7 ++++ src/error/ApplicationError.js | 17 +++++++++ src/model/Lotto.js | 7 ++-- src/model/LottoMachine.js | 12 +++---- src/model/LottoWinning.js | 53 ++++++++++++++++++++++++++++ src/model/LottoWinningRank.js | 56 ++++++++++++++++++++++++++++++ src/model/WinnersStatistics.js | 20 +++++++++++ 10 files changed, 259 insertions(+), 10 deletions(-) create mode 100644 src/application/LottoController.js create mode 100644 src/application/LottoService.js create mode 100644 src/constants/errorMessage.js create mode 100644 src/error/ApplicationError.js create mode 100644 src/model/LottoWinning.js create mode 100644 src/model/LottoWinningRank.js create mode 100644 src/model/WinnersStatistics.js diff --git a/src/App.js b/src/App.js index 091aa0a5..c69f514a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,17 @@ +import { InputView } from "./view.js"; +import { OutputView } from "./view.js"; +import LottoController from "./application/LottoController.js"; +import LottoService from "./application/LottoService.js"; + class App { - async run() {} + async run() { + const lottoController = new LottoController({ + InputView, + OutputView, + LottoService, + }); + await lottoController.run(); + } } export default App; diff --git a/src/application/LottoController.js b/src/application/LottoController.js new file mode 100644 index 00000000..92c3793a --- /dev/null +++ b/src/application/LottoController.js @@ -0,0 +1,52 @@ +import { DomainError, InvalidWinningError } from "../error/ApplicationError.js"; + +export default class LottoController { + #inputView; + #outputView; + #LottoService; + + constructor({ InputView, OutputView, LottoService } = {}) { + this.#inputView = InputView; + this.#outputView = OutputView; + this.#LottoService = LottoService; + } + + async #handlingError(fn) { + while (true) { + try { + return await fn(); + } catch (error) { + const message = `[ERROR] ${error.message}`; + + this.#outputView.printErrorMessage(message); + + if (error instanceof DomainError) continue; + if (error instanceof InvalidWinningError) this.#askWinningLotto(); + + throw error; + } + } + } + + async #askWinningLotto() { + return await this.#handlingError(() => this.#inputView.askWinningLotto()); + } + + async run() { + const amount = await this.#handlingError(() => this.#inputView.askAmount()); + const winningLotto = this.#askWinningLotto(); + const bonus = await this.#handlingError(() => + this.#inputView.askBonusNumber() + ); + + const service = await this.#handlingError(() => this.#LottoService()); + const { issueLottos, rankingCounts } = service.run( + amount, + winningLotto, + bonus + ); + + this.#outputView.printPurchasedLottos(issueLottos); + this.#outputView.printResult(rankingCounts); + } +} diff --git a/src/application/LottoService.js b/src/application/LottoService.js new file mode 100644 index 00000000..bbddc294 --- /dev/null +++ b/src/application/LottoService.js @@ -0,0 +1,31 @@ +import LottoMachine from "../model/LottoMachine.js"; +import LottoWinning from "../model/LottoWinning.js"; +import LottoWinningRank from "../model/LottoWinningRank.js"; +import WinnersStatistics from "../model/WinnersStatistics.js"; + +export default class LottoService { + constructor() {} + + run(purchase, winningNumbers, bonus) { + const lottoMachine = new LottoMachine(purchase); + const issueLottos = lottoMachine.issue(); + + const lottoWinning = new LottoWinning({ + winningNumbers, + bonus, + lottoWinningRank: LottoWinningRank, + }); + + const winningRankings = issueLottos.map((lotto) => + lottoWinning.evaluate(lotto.getNumbers()) + ); + + const winnersStatistics = new WinnersStatistics(winningRankings); + + const rankings = LottoWinningRank.getRanks(); + + const rankingCounts = winnersStatistics.getStatistics(rankings); + + return { issueLottos, rankingCounts }; + } +} diff --git a/src/constants/errorMessage.js b/src/constants/errorMessage.js new file mode 100644 index 00000000..1dce2922 --- /dev/null +++ b/src/constants/errorMessage.js @@ -0,0 +1,7 @@ +const ERROR_TYPES = { + NUMBERS_DUPLICATION: "중복되는 숫자가 있습니다.", + NUMBERIC_RANG: "숫자 범위에서 벗어났습니다.", + INVALID_PURCHASE: "로또 가격에 해당하는 가격을 입력해주세요.", +}; + +export default ERROR_TYPES; diff --git a/src/error/ApplicationError.js b/src/error/ApplicationError.js new file mode 100644 index 00000000..370eb4ec --- /dev/null +++ b/src/error/ApplicationError.js @@ -0,0 +1,17 @@ +class ApplicationError extends Error { + constructor(message) { + super(message); + } +} + +export class DomainError extends ApplicationError { + constructor(message) { + super(`${message}`); + } +} + +export class InvalidWinningError extends ApplicationError { + constructor(message) { + super(`${message}`); + } +} diff --git a/src/model/Lotto.js b/src/model/Lotto.js index fbd6abb2..e9e1eff2 100644 --- a/src/model/Lotto.js +++ b/src/model/Lotto.js @@ -1,3 +1,6 @@ +import { DomainError } from "../error/ApplicationError.js"; +import ERROR_TYPES from "../constants/errorMessage.js"; + export default class Lotto { #numbers; @@ -9,10 +12,10 @@ export default class Lotto { #validateNumbers(numbers) { if (new Set(numbers).size !== numbers.length) - throw new Error("NUMBERS_DUPLICATION"); + throw new DomainError(ERROR_TYPES.NUMBERS_DUPLICATION); const isNumberRang = numbers.every((number) => 1 <= number && number <= 30); - if (!isNumberRang) throw new Error("NUMBERIC_RANG"); + if (!isNumberRang) throw new DomainError(ERROR_TYPES.NUMBERIC_RANG); } getNumbers() { diff --git a/src/model/LottoMachine.js b/src/model/LottoMachine.js index 17a34fb7..dd4477bb 100644 --- a/src/model/LottoMachine.js +++ b/src/model/LottoMachine.js @@ -1,8 +1,9 @@ -// import { DomainError } from "../error/ApplicationError.js"; - import randomPickUniqueNumbers from "../utils/randompickUniqueNumbers.js"; import LottoFactory from "../utils/LottoFactory.js"; +import { DomainError } from "../error/ApplicationError.js"; +import ERROR_TYPES from "../constants/errorMessage.js"; + export default class LottoMachine { #quantity; #lottoFactory; @@ -21,17 +22,14 @@ export default class LottoMachine { } #validatePurchase(purchase) { - if (purchase % 500 !== 0) throw new Error("INVALID_PURCHASE"); + if (purchase % 500 !== 0) + throw new DomainError(ERROR_TYPES.INVALID_PURCHASE); } #checkAvailable(issueLottos, newLotto) { return issueLottos.every((lotto) => lotto.isEqual(newLotto)); } - getQuantity() { - return this.#quantity; - } - issue() { const issueLottos = []; diff --git a/src/model/LottoWinning.js b/src/model/LottoWinning.js new file mode 100644 index 00000000..db429a1a --- /dev/null +++ b/src/model/LottoWinning.js @@ -0,0 +1,53 @@ +import ERROR_TYPES from "../constants/errorMessage.js"; +import { DomainError, InvalidWinningError } from "../error/ApplicationError.js"; + +export default class LottoWinning { + #winningNumbers; + #bonus; + #lottoWinningRank; + + constructor({ winningNumbers, bonus, lottoWinningRank } = {}) { + this.#validateBonus(bonus); + + this.#winningNumbers = winningNumbers; + this.#bonus = bonus; + this.#lottoWinningRank = lottoWinningRank; + } + + validateWinningNumbers(winningNumbers) { + if (new Set(winningNumbers).size !== winningNumbers.length) + throw new InvalidWinningError(ERROR_TYPES.NUMBERS_DUPLICATION); + + const isNumberRang = winningNumbers.every( + (number) => 1 <= number && number <= 30 + ); + if (!isNumberRang) throw new InvalidWinningError(ERROR_TYPES.NUMBERIC_RANG); + } + + #validateBonus(bonus) { + const isNumberRange = 1 <= bonus && bonus <= 30; + if (!isNumberRange) + throw new DomainError("1 ~ 30 사이의 숫자 범위에 해당하지 않습니다."); + + if (this.#winningNumbers.includes(bonus)) { + throw new DomainError("당첨 번호에 중복되는 보너스 번호입니다."); + } + } + + #matchCount(lottoNumbers) { + return lottoNumbers.filter((number) => + this.#winningNumbers.includes(number) + ); + } + + #matchBonus(lottoNumbers, matchCount) { + return matchCount === 5 && lottoNumbers.includes(this.#bonus); + } + + evaluate(lottoNumbers) { + const matchCount = this.#matchCount(lottoNumbers); + const isMatchBonus = this.#matchBonus(lottoNumbers, matchCount); + + return this.#lottoWinningRank.evaluate(matchCount, isMatchBonus); + } +} diff --git a/src/model/LottoWinningRank.js b/src/model/LottoWinningRank.js new file mode 100644 index 00000000..b8d7884c --- /dev/null +++ b/src/model/LottoWinningRank.js @@ -0,0 +1,56 @@ +export default class LottoWinningRank { + #rank; + #matchCount; + #isMatchBonus; + #prize; + + constructor(rank, matchCount, isMatchBonus, prize) { + this.#rank = rank; + this.#matchCount = matchCount; + this.#isMatchBonus = isMatchBonus; + this.#prize = prize; + } + + getRank() { + return this.#rank; + } + + getMatchCount() { + return this.#matchCount; + } + + getIsMatchBonus() { + return this.#isMatchBonus; + } + + getPrize() { + return this.#prize; + } + + getRanks() { + return [ + LottoWinningRank.FIRST.getRank(), + LottoWinningRank.SECOND.getRank(), + LottoWinningRank.THIRD.getRank(), + LottoWinningRank.FOURTH.getRank(), + LottoWinningRank.FIFTH.getRank(), + LottoWinningRank.NONE.getRank(), + ]; + } + + static FIRST = new LottoWinningRank(1, 5, false, 100000000); + static SECOND = new LottoWinningRank(2, 4, true, 10000000); + static THIRD = new LottoWinningRank(3, 4, false, 1500000); + static FOURTH = new LottoWinningRank(4, 3, true, 500000); + static FIFTH = new LottoWinningRank(5, 2, true, 5000); + static NONE = new LottoWinningRank(0, 0, false, 0); + + static of(matchCount, isMatchBonus) { + if (matchCount === 5) return LottoWinningRank.FIRST; + if (matchCount === 4 && isMatchBonus) return LottoWinningRank.SECOND; + if (matchCount === 4) return LottoWinningRank.THIRD; + if (matchCount === 3 && isMatchBonus) return LottoWinningRank.FOURTH; + if (matchCount === 2 && isMatchBonus) return LottoWinningRank.FIFTH; + return LottoWinningRank.NONE; + } +} diff --git a/src/model/WinnersStatistics.js b/src/model/WinnersStatistics.js new file mode 100644 index 00000000..64cca4ea --- /dev/null +++ b/src/model/WinnersStatistics.js @@ -0,0 +1,20 @@ +export default class WinnersStatistics { + #ranks; + + constructor(ranks) { + this.#ranks = ranks; + } + + #count(ranking) { + return this.#ranks.filter((rank) => rank.getRank() === ranking).length; + } + + getStatistics(ranks) { + const rankingCounts = new Map(); + + for (const rank of ranks) { + const count = this.#count(rank); + rankingCounts.set(rank, count); + } + } +}