diff --git a/README.md b/README.md
index b168a180..c5ff29d3 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,137 @@
-# javascript-planetlotto-precourse
+# ๐ช javascript-planetlotto-precourse
+์ฐํ
์ฝ ๋ก๋ ๋ฐ๋งค๊ธฐ์ธ ํ์ฑ ๋ก๋๋ฅผ ๊ตฌํํ๋ค.
+
+---
+## ๐ ๊ตฌํ ๊ธฐ๋ฅ ๋ชฉ๋ก
+
+### 1. ๋ก๋ ๊ตฌ์
๊ธ์ก ์
๋ ฅ ๋ฐ๊ธฐ
+- [x] `๊ตฌ์
๊ธ์ก์ ์
๋ ฅํด ์ฃผ์ธ์.` ์ถ๋ ฅ
+- [x] ์๋ชป๋ ์
๋ ฅ๊ฐ์ผ ๊ฒฝ์ฐ throw๋ฌธ์ ํตํด ์์ธ ๋ฐ์
+
+ - [์์ธ] ์
๋ ฅ๊ฐ์ด ์์ ๊ฒฝ์ฐ
+ - [์์ธ] ์ซ์๊ฐ ์๋ ๊ฐ์ ์
๋ ฅํ์ ๊ฒฝ์ฐ (๋ฌธ์, ๊ธฐํธ)
+ - [์์ธ] ์์๋ฅผ ์
๋ ฅํ์ ๊ฒฝ์ฐ
+ - [์์ธ] ์์ ๋๋ 0์ ์
๋ ฅํ์ ๊ฒฝ์ฐ
+ - [์์ธ] ๊ณต๋ฐฑ์ด ์์ ๊ฒฝ์ฐ
+ - [์์ธ] 500์์ผ๋ก ๋๋์ด ๋จ์ด์ง์ง ์๋ ๊ฒฝ์ฐ
+
+
+> ํ
์คํธ ๋ชฉ๋ก
+> - [ ] ์
๋ ฅ๊ฐ์ด 500์ ๋จ์์ผ ๋ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฅ๋๋๊ฐ?
+> - [ ] ๊ตฌ์
๊ธ์ก์ด 500์ ๋จ์๊ฐ ์๋ ๋ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] ์
๋ ฅ๊ฐ์ด ์์ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] ์ซ์๊ฐ ์๋ ์
๋ ฅ(๋ฌธ์, ๊ธฐํธ) ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] ์์ ์
๋ ฅ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] ์์ ๋๋ 0 ์
๋ ฅ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] ๊ณต๋ฐฑ ์
๋ ฅ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+
+
+### 2. ๋ฐํํ ๋ก๋ ์๋ ์ถ๋ ฅํ๊ธฐ
+- [x] ๋ฐํํ ๋ก๋ ์๋ ๊ตฌํ๊ธฐ (๋ก๋ ๊ตฌ์
๊ธ์ก / 500)
+- [ ] `2๊ฐ๋ฅผ ๊ตฌ๋งคํ์ต๋๋ค.` ์ถ๋ ฅ
+
+
+> ํ
์คํธ ๋ชฉ๋ก
+> - [ ] ๊ตฌ์
๊ธ์ก์ ๋ฐ๋ผ ๋ก๋ ์๋์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ณ์ฐ๋๋ค.
+
+
+### 3. ๋ฐํํ ๋ก๋ ๋ฒํธ ์ถ๋ ฅํ๊ธฐ
+- [ ] ๋ก๋ ๋ฐํํ๊ธฐ
+
+ - ์ค๋ณต๋์ง ์๋ 5๊ฐ์ ์ซ์ ๋ฝ๊ธฐ (Random ๊ฐ ์ถ์ถ์ย `Random. pickUniqueNumbersInRange()` ํ์ฉ)
+ - ๋ก๋ ๋ฒํธ๋ ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๋ ฌ
+ - ๋ฐํํ ๋ก๋ ์๋๋งํผ ๋ฐ๋ณต
+
+
+> ํ
์คํธ ๋ชฉ๋ก
+> - [ ] ํ ์ฅ์ ๋ก๋ ๋ฒํธ๊ฐ 5๊ฐ์ธ์ง ํ์ธํ๋ค.
+> - [ ] 5๊ฐ์ ๋ฒํธ ์ค ์ค๋ณต๋ ์ซ์๊ฐ ์๋์ง ํ์ธํ๋ค.
+> - [ ] 5๊ฐ์ ๋ฒํธ๊ฐ ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๋ ฌ๋๋์ง ํ์ธํ๋ค.
+> - [ ] ๋ชจ๋ ์ซ์๊ฐ 1~30 ๋ฒ์์ธ์ง ํ์ธํ๋ค.
+> - [ ] ๋ฐํ๋ ๋ก๋ ์๋์ด ๊ตฌ์
์๋๊ณผ ์ผ์นํ๋๊ฐ?
+
+
+### 4. ๋น์ฒจ ๋ฒํธ ์
๋ ฅ ๋ฐ๊ธฐ
+- [x] `๋น์ฒจ ๋ฒํธ๋ฅผ ์
๋ ฅํด ์ฃผ์ธ์.` ์ถ๋ ฅ
+- [x] ์ผํ(,)๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ฒํธ ๊ตฌ๋ถํ๊ธฐ
+- [x] ์๋ชป๋ ์
๋ ฅ๊ฐ์ผ ๊ฒฝ์ฐ throw๋ฌธ์ ํตํด ์์ธ ๋ฐ์
+
+ - [์์ธ] ์
๋ ฅ๊ฐ์ด ์์ ๊ฒฝ์ฐ
+ - [์์ธ] ๊ณต๋ฐฑ์ด ์์ ๊ฒฝ์ฐ
+ - [์์ธ] 1 ~ 30 ๋ฒ์์ ์ ์๊ฐ ์๋ ๊ฒฝ์ฐ
+ - [์์ธ] ์ค๋ณต๋๋ ์ซ์๊ฐ ์์ ๊ฒฝ์ฐ
+ - [์์ธ] 5๊ฐ๊ฐ ์๋ ๊ฒฝ์ฐ
+
+
+> ํ
์คํธ ๋ชฉ๋ก
+> - [ ] ์
๋ ฅ๊ฐ์ด ์์ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] ๋ฌธ์, ๊ธฐํธ, ์์ ์
๋ ฅ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค. (*์ผํ ์ ์ธ)
+> - [ ] ๊ณต๋ฐฑ ์
๋ ฅ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] 1 ~ 30 ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ซ์ ์
๋ ฅ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] ์ค๋ณต๋ ์ซ์ ์
๋ ฅ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] ๋น์ฒจ ๋ฒํธ์ ๊ฐ์๊ฐ 5๊ฐ๊ฐ ์๋ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+
+
+### 5. ๋ณด๋์ค ๋ฒํธ ์
๋ ฅ ๋ฐ๊ธฐ
+- [x] `๋ณด๋์ค ๋ฒํธ๋ฅผ ์
๋ ฅํด ์ฃผ์ธ์.` ์ถ๋ ฅ
+- [x] ์๋ชป๋ ์
๋ ฅ๊ฐ์ผ ๊ฒฝ์ฐ throw๋ฌธ์ ํตํด ์์ธ ๋ฐ์
+
+ - [์์ธ] ์
๋ ฅ๊ฐ์ด ์์ ๊ฒฝ์ฐ
+ - [์์ธ] ์ซ์๊ฐ ์๋ ๊ฐ์ ์
๋ ฅํ์ ๊ฒฝ์ฐ (๋ฌธ์, ๊ธฐํธ)
+ - [์์ธ] ์์๋ฅผ ์
๋ ฅํ์ ๊ฒฝ์ฐ
+ - [์์ธ] ์์๋ฅผ ์
๋ ฅํ์ ๊ฒฝ์ฐ
+ - [์์ธ] ๊ณต๋ฐฑ์ด ์์ ๊ฒฝ์ฐ
+ - [์์ธ] 1 ~ 30 ๋ฒ์์ ์ซ์๊ฐ ์๋ ๊ฒฝ์ฐ
+ - [์์ธ] ๋น์ฒจ ๋ฒํธ์ ์ค๋ณต๋ ๊ฒฝ์ฐ
+
+
+> ํ
์คํธ ๋ชฉ๋ก
+> - [ ] ์ ํจํ ์ซ์ ์
๋ ฅ ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฅ๋๋์ง ํ์ธํ๋ค.
+> - [ ] ์
๋ ฅ๊ฐ์ด ์์ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] ์ซ์๊ฐ ์๋ ์
๋ ฅ(๋ฌธ์, ๊ธฐํธ) ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] ์์ ์
๋ ฅ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] ๊ณต๋ฐฑ ์
๋ ฅ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] 1 ~ 30 ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ซ์ ์
๋ ฅ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+> - [ ] ๋น์ฒจ ๋ฒํธ์ ์ค๋ณต๋๋ ์ซ์ ์
๋ ฅ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
+
+
+### 6. ๋น์ฒจ ๋ด์ญ ์ถ๋ ฅํ๊ธฐ
+- [x] ์ฌ์ฉ์๊ฐ ๊ตฌ๋งคํ ๋ก๋ ๋ฒํธ์ ๋น์ฒจ ๋ฒํธ ๋น๊ตํ๊ธฐ
+- [ ] ๋น์ฒจ ๋ด์ญ ์ถ๋ ฅํ๊ธฐ
+
+
+> ํ
์คํธ ๋ชฉ๋ก
+> - [ ] ๊ฐ ๋ก๋ ๋ฒํธ์ ๋น์ฒจ ๋ฒํธ ์ผ์น ๊ฐ์๋ฅผ ์ ํํ ๊ณ์ฐํ๋ค.
+> - [ ] 5๊ฐ ๋ฒํธ ์ผ์น ์ 1๋ฑ์ผ๋ก ๊ณ์ฐ๋๋ค.
+> - [ ] 4๊ฐ ๋ฒํธ ์ผ์น + ๋ณด๋์ค ๋ฒํธ ํฌํจ ์ 2๋ฑ์ผ๋ก ๊ณ์ฐ๋๋ค.
+> - [ ] 4๊ฐ ๋ฒํธ ์ผ์น ์ 3๋ฑ์ผ๋ก ๊ณ์ฐ๋๋ค.
+> - [ ] 3๊ฐ ๋ฒํธ ์ผ์น + ๋ณด๋์ค ๋ฒํธ ์ผ์น ์ 4๋ฑ์ผ๋ก ๊ณ์ฐ๋๋ค.
+> - [ ] 2๊ฐ ๋ฒํธ ์ผ์น + ๋ณด๋์ค ๋ฒํธ ์ผ์ ์ 5๋ฑ์ผ๋ก ๊ณ์ฐ๋๋ค.
+> - [ ] ์ผ์น ๊ฐ์์ ๋ฐ๋ผ ํต๊ณ ๊ฐ์ฒด๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๊ฐฑ์ ๋๋ค.
+
+
+### 7. ์๋ฌ ๋ฉ์์ง ์ถ๋ ฅ ํ ์ฌ์
๋ ฅ ๋ฐ๊ธฐ
+- [x] ์ฌ์ฉ์๊ฐ ์๋ชป๋ ๊ฐ์ ์
๋ ฅํ ๊ฒฝ์ฐ [ERROR]๋ก ์์ํ๋ ๋ฉ์์ง๋ฅผ ์ถ๋ ฅ
+- [x] ํด๋น ์
๋ ฅ ๋จ๊ณ๋ถํฐ ๋ค์ ์
๋ ฅ ๋ฐ๊ธฐ
+
+
+---
+## ๊ธฐ๋ฅ ์๊ตฌ ์ฌํญ
+๋ณธ ํ๋ก๊ทธ๋จ์ ๋ชฉ์ ์ ๊ธฐ๋ฅ ๊ตฌํ์ ๊ทธ์น๋ ๊ฒ์ด ์๋๋ผ ํ๋ฆฌ์ฝ์ค์์ ํ์ตํ ๊ฐ๋ฐ ๋ฐฉ์(๋ฌธ์ ๋ถํด, ์ค๊ณ, TDD)์ด ์ฝ๋์ ๋๋ฌ๋๋ ๊ฒ์ ์๋ค.
+
+- [x] ์
๋ ฅ/์ถ๋ ฅ ์ญํ ์ ์ ๊ณต๋ InputView, OutputView์์ ์ํํ๋ฉฐ ๊ธฐ์กด ๋ฉ์๋๋ฅผ ์์ , ์ญ์ ํ ์ ์๋ค.
+
+- [x] ๋ก๋ ๋ฒํธ์ ์ซ์ ๋ฒ์๋ 1~30๊น์ง์ด๋ค.
+- [x] 1๊ฐ์ ๋ก๋๋ฅผ ๋ฐํํ ๋ ์ค๋ณต๋์ง ์๋ 5๊ฐ์ ์ซ์๋ฅผ ๋ฝ๋๋ค.
+- [x] ๋น์ฒจ ๋ฒํธ ์ถ์ฒจ ์ ์ค๋ณต๋์ง ์๋ ์ซ์ 5๊ฐ์ ๋ณด๋์ค ๋ฒํธ 1๊ฐ๋ฅผ ๋ฝ๋๋ค.
+- [x] ๋น์ฒจ์ 1๋ฑ๋ถํฐ 5๋ฑ๊น์ง ์๋ค. ๋น์ฒจ ๊ธฐ์ค๊ณผ ๊ธ์ก์ ์๋์ ๊ฐ๋ค.
+- [x] 1๋ฑ: 5๊ฐ ๋ฒํธ ์ผ์น / 100,000,000์
+- [x] 2๋ฑ: 4๊ฐ ๋ฒํธ + ๋ณด๋์ค ๋ฒํธ ์ผ์น / 10,000,000์
+- [x] 3๋ฑ: 4๊ฐ ๋ฒํธ ์ผ์น / 1,500,000์
+- [x] 4๋ฑ: 3๊ฐ ๋ฒํธ ์ผ์น + ๋ณด๋์ค ๋ฒํธ ์ผ์น / 500,000์
+- [x] 5๋ฑ: 2๊ฐ ๋ฒํธ ์ผ์น + ๋ณด๋์ค ๋ฒํธ ์ผ์น / 5,000์
+- [x] ๋ก๋ ๊ตฌ์
๊ธ์ก์ ์
๋ ฅํ๋ฉด ๊ตฌ์
๊ธ์ก์ ํด๋นํ๋ ๋งํผ ๋ก๋๋ฅผ ๋ฐํํด์ผ ํ๋ค.
+- [x] ๋ก๋ 1์ฅ์ ๊ฐ๊ฒฉ์ 500์์ด๋ค.
+- [x] ๋น์ฒจ ๋ฒํธ์ ๋ณด๋์ค ๋ฒํธ๋ฅผ ์
๋ ฅ๋ฐ๋๋ค.
+- [ ] ์ฌ์ฉ์๊ฐ ๊ตฌ๋งคํ ๋ก๋ ๋ฒํธ์ ๋น์ฒจ ๋ฒํธ๋ฅผ ๋น๊ตํ์ฌ ๋น์ฒจ ๋ด์ญ์ ์ถ๋ ฅํ๊ณ ๋ก๋ ๊ฒ์์ ์ข
๋ฃํ๋ค.
+- [x] ์ฌ์ฉ์๊ฐ ์๋ชป๋ ๊ฐ์ ์
๋ ฅํ ๊ฒฝ์ฐ "[ERROR]"๋ก ์์ํ๋ ๋ฉ์์ง์ ํจ๊ป Error๋ฅผ ๋ฐ์์ํค๊ณ ํด๋น ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํ ๋ค์ ํด๋น ์ง์ ๋ถํฐ ๋ค์ ์
๋ ฅ์ ๋ฐ๋๋ค.
\ No newline at end of file
diff --git a/src/App.js b/src/App.js
index 091aa0a5..c12ddd21 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,5 +1,57 @@
+import { InputView, OutputView } from "./view.js";
+import { InputValidator } from "./validator/InputValidator.js";
+import LottoMachine from "./model/LottoMachine.js";
+import { create2DArray } from "./util/create2DArray.js";
+
class App {
- async run() {}
+ async run() {
+ const purchaseAmount = await this.getPurchaseAmount();
+ const lottoMachine = new LottoMachine(Number(purchaseAmount));
+ const lottos = create2DArray(lottoMachine.get2DArray());
+ OutputView.printPurchasedLottos(lottos);
+
+ const winningNumbers = await this.getWinningNumbers();
+ const bonusNumber = await this.getBonusNumber(winningNumbers);
+
+ const result = lottoMachine.calculateResult(winningNumbers, bonusNumber);
+ OutputView.printResult(result);
+ }
+
+ async getPurchaseAmount() {
+ while (true) {
+ try {
+ const purchaseAmount = await InputView.askAmount();
+ InputValidator.validatePurchaseAmount(purchaseAmount);
+ return purchaseAmount;
+ } catch(e) {
+ OutputView.printErrorMessage(e.message);
+ }
+ }
+ }
+
+ async getWinningNumbers() {
+ while (true) {
+ try {
+ const input = await InputView.askWinningLotto();
+ const winningNumbers = InputValidator.validateWinningNumbers(input);
+ return winningNumbers;
+ } catch (e) {
+ OutputView.printErrorMessage(e.message);
+ }
+ }
+ }
+
+ async getBonusNumber(winningNumbers) {
+ while (true) {
+ try {
+ const input = await InputView.askBonusNumber();
+ const bonusNumber = InputValidator.validateBonusNumber(input, winningNumbers);
+ return bonusNumber;
+ } catch (e) {
+ OutputView.printErrorMessage(e.message);
+ }
+ }
+ }
}
export default App;
diff --git a/src/constant/lotto.js b/src/constant/lotto.js
new file mode 100644
index 00000000..db263f50
--- /dev/null
+++ b/src/constant/lotto.js
@@ -0,0 +1,7 @@
+export const LOTTO_CONFIG = Object.freeze({
+ RANDOM_MIN: 1,
+ RANDOM_MAX: 30,
+ RANDOM_COUNT: 5,
+ LOTTO_PRICE: 500,
+ DELIMITER: ',',
+});
\ No newline at end of file
diff --git a/src/constant/message.js b/src/constant/message.js
new file mode 100644
index 00000000..194f2eb0
--- /dev/null
+++ b/src/constant/message.js
@@ -0,0 +1,29 @@
+export const ERROR_MESSAGE = Object.freeze({
+ OUT_OF_RANGE: '๋ก๋ ๋ฒํธ๋ 1๋ถํฐ 30์ ์ซ์์ฌ์ผ ํฉ๋๋ค.',
+ INVALID_COUNT: '๋ก๋ ๋ฒํธ๋ 5๊ฐ์ฌ์ผ ํฉ๋๋ค.',
+ DUPLICATE_NUMBER: "๋ก๋ ๋ฒํธ๋ ์ค๋ณต๋ ์ ์์ต๋๋ค.",
+
+ PURCHASE: {
+ EMPTY_INPUT: '๊ตฌ์
๊ธ์ก์ ์
๋ ฅํด ์ฃผ์ธ์.',
+ NOT_NUMBER: '๊ตฌ์
๊ธ์ก์ ์ซ์์ฌ์ผ ํฉ๋๋ค.',
+ NOT_INTEGER: '๊ตฌ์
๊ธ์ก์ ์ ์์ฌ์ผ ํฉ๋๋ค.',
+ NOT_POSITIVE: '๊ตฌ์
๊ธ์ก์ ์์๊ฐ ๋ ์ ์์ต๋๋ค.',
+ NOT_MULTIPLE_UNIT: '๊ตฌ์
๊ธ์ก์ 500์ ๋จ์๋ก ์
๋ ฅํด์ผ ํฉ๋๋ค.',
+ },
+
+ WINNING_NUMBERS: {
+ OUT_OF_RANGE: '๋น์ฒจ ๋ฒํธ๋ 1๋ถํฐ 30์ ์ซ์์ฌ์ผ ํฉ๋๋ค.',
+ INVALID_COUNT: '๋น์ฒจ ๋ฒํธ๋ 5๊ฐ์ฌ์ผ ํฉ๋๋ค.',
+ DUPLICATE_NUMBER: "๋น์ฒจ ๋ฒํธ๋ ์ค๋ณต๋ ์ ์์ต๋๋ค.",
+ },
+
+ BONUS: {
+ EMPTY_INPUT: '๋ณด๋์ค ๋ฒํธ๋ฅผ ์
๋ ฅํด ์ฃผ์ธ์.',
+ NOT_NUMBER: '๋ณด๋์ค ๋ฒํธ๋ ์ซ์์ฌ์ผ ํฉ๋๋ค.',
+ NOT_INTEGER: '๋ณด๋์ค ๋ฒํธ๋ ์ ์์ฌ์ผ ํฉ๋๋ค.',
+ OUT_OF_RANGE: '๋ณด๋์ค ๋ฒํธ๋ 1๋ถํฐ 30์ ์ซ์์ฌ์ผ ํฉ๋๋ค.',
+ DUPLICATE_WITH_WINNING: '๋ณด๋์ค ๋ฒํธ๋ ๋น์ฒจ ๋ฒํธ์ ์ค๋ณต๋ ์ ์์ต๋๋ค.',
+ }
+});
+
+
\ No newline at end of file
diff --git a/src/model/Lotto.js b/src/model/Lotto.js
new file mode 100644
index 00000000..938c8bed
--- /dev/null
+++ b/src/model/Lotto.js
@@ -0,0 +1,31 @@
+import { validate } from "../util/validate.js";
+import { ERROR_MESSAGE } from "../constant/message.js";
+
+class Lotto {
+ #numbers;
+
+ constructor(numbers) {
+ this.#validate(numbers);
+ this.#numbers = numbers.slice().sort((a, b) => a - b);
+ }
+
+ #validate(numbers) {
+ if (!validate.hasValidCount(numbers)) {
+ throw new Error(ERROR_MESSAGE.INVALID_COUNT);
+ }
+
+ if (validate.hasDuplicate(numbers)) {
+ throw new Error(ERROR_MESSAGE.DUPLICATE_NUMBER);
+ }
+
+ if (!validate.areNumbersInRange(numbers)) {
+ throw new Error(ERROR_MESSAGE.OUT_OF_RANGE);
+ }
+ }
+
+ getNumbers() {
+ return this.#numbers;
+ }
+}
+
+export default Lotto;
\ No newline at end of file
diff --git a/src/model/LottoMachine.js b/src/model/LottoMachine.js
new file mode 100644
index 00000000..002bf9b6
--- /dev/null
+++ b/src/model/LottoMachine.js
@@ -0,0 +1,93 @@
+import Lotto from './Lotto.js';
+import { getRandomLottoNumbers } from '../util/random.js';
+import { LOTTO_CONFIG } from '../constant/lotto.js';
+
+class LottoMachine {
+ #lottos = [];
+ #purchaseAmount;
+ #lottoCount;
+
+ constructor(purchaseAmount) {
+ this.#purchaseAmount = purchaseAmount;
+ this.#lottoCount = Math.floor(purchaseAmount / LOTTO_CONFIG.LOTTO_PRICE);
+ this.#generateLotto();
+ }
+
+ #generateLotto() {
+ for (let i = 0; i < this.#lottoCount; i += 1) {
+ const numbers = getRandomLottoNumbers();
+ this.#lottos.push(new Lotto(numbers));
+ }
+ }
+
+ get2DArray(r, c) {
+ const lottoArr = [];
+ for (let i = 0 ; i < r ; i++) {
+ const temp = [];
+ this.#generateLotto();
+ result.push(temp);
+ }
+ return lottoArr;
+}
+
+ getLottoCount() {
+ return this.#lottoCount;
+ }
+
+ getLottos() {
+ return this.#lottos;
+ }
+
+ calculateResult(winningNumbers, bonusNumber) {
+ const result = this.#initResult();
+ this.#lottos.forEach(lotto => {
+ const matchCount = this.#countMatches(lotto, winningNumbers);
+ const hasBonus = this.#hasBonus(lotto, bonusNumber);
+ this.#updateResult(result, matchCount, hasBonus);
+ });
+ return result;
+ }
+
+ #initResult() {
+ return {
+ ZERO: 0,
+ FIRST: 0,
+ SECOND: 0,
+ THIRD: 0,
+ FOURTH: 0,
+ FIFTH: 0,
+ };
+ }
+
+ #countMatches(lotto, winningNumbers) {
+ const numbers = lotto.getNumbers();
+ const matchedNumbers = numbers.filter(num => winningNumbers.includes(num));
+ return matchedNumbers.length;
+ }
+
+ #hasBonus(lotto, bonusNumber) {
+ return lotto.getNumbers().includes(bonusNumber);
+ }
+
+ #updateResult(result, matchCount, hasBonus) {
+ if (matchCount === 5) result.FIRST += 1;
+ else if (matchCount === 4 && hasBonus) result.SECOND += 1;
+ else if (matchCount === 4) result.THIRD += 1;
+ else if (matchCount === 3 && hasBonus) result.FOURTH += 1;
+ else if (matchCount === 2 && hasBonus) result.FIFTH += 1;
+ else if (matchCount === 0) result.ZERO +=1;
+ }
+
+ buildStatistics(result) {
+ return [
+ { match: 0, prize: 0, count: result.ZERO },
+ { match: 2, prize: 5000, count: result.FIFTH, bonus: true },
+ { match: 3, prize: 500000, count: result.FOURTH, bonus: true },
+ { match: 4, prize: 1500000, count: result.THIRD },
+ { match: 4, prize: 10000000, count: result.SECOND, bonus: true },
+ { match: 5, prize: 100000000, count: result.FIRST },
+ ];
+ }
+}
+
+export default LottoMachine;
\ No newline at end of file
diff --git a/src/util/create2DArray.js b/src/util/create2DArray.js
new file mode 100644
index 00000000..205247f2
--- /dev/null
+++ b/src/util/create2DArray.js
@@ -0,0 +1,10 @@
+export function create2DArray(lottos) {
+ const lottoArr = [];
+
+ lottos.forEach((lotto) => {
+ lotto.getNumbers();
+ lottoArr.push();
+ });
+
+ return lottoArr;
+}
\ No newline at end of file
diff --git a/src/util/parser.js b/src/util/parser.js
new file mode 100644
index 00000000..2cfb2298
--- /dev/null
+++ b/src/util/parser.js
@@ -0,0 +1,7 @@
+import { LOTTO_CONFIG } from "../constant/lotto.js";
+
+export function parseLottoNumbers(input) {
+ const splitted = input.split(LOTTO_CONFIG.DELIMITER);
+ const numbers = splitted.map(n => Number(n.trim()));
+ return numbers;
+}
\ No newline at end of file
diff --git a/src/util/random.js b/src/util/random.js
new file mode 100644
index 00000000..ccb21292
--- /dev/null
+++ b/src/util/random.js
@@ -0,0 +1,9 @@
+import { MissionUtils } from '@woowacourse/mission-utils';
+import { LOTTO_CONFIG } from '../constant/lotto.js';
+
+export const getRandomLottoNumbers = () =>
+ MissionUtils.Random.pickUniqueNumbersInRange(
+ LOTTO_CONFIG.RANDOM_MIN,
+ LOTTO_CONFIG.RANDOM_MAX,
+ LOTTO_CONFIG.RANDOM_COUNT
+ );
\ No newline at end of file
diff --git a/src/util/validate.js b/src/util/validate.js
new file mode 100644
index 00000000..58dbc169
--- /dev/null
+++ b/src/util/validate.js
@@ -0,0 +1,43 @@
+import { LOTTO_CONFIG } from "../constant/lotto.js";
+
+export const validate = {
+ isNumber(input) {
+ const number = Number(input);
+ return !Number.isNaN(number);
+ },
+
+ isInteger(input) {
+ const number = Number(input);
+ return Number.isInteger(number);
+ },
+
+ isEmpty(input = '') {
+ return String(input).trim() === '';
+ },
+
+ isPositiveNumber(input) {
+ const number = Number(input);
+ return number > 0;
+ },
+
+ hasDuplicate(numbers) {
+ return new Set(numbers).size !== numbers.length;
+ },
+
+ hasValidCount(numbers) {
+ return numbers.length === LOTTO_CONFIG.RANDOM_COUNT;
+ },
+
+ areNumbersInRange(numbers) {
+ const isValidNumber = (num) =>
+ Number.isInteger(num) &&
+ num >= LOTTO_CONFIG.RANDOM_MIN &&
+ num <= LOTTO_CONFIG.RANDOM_MAX;
+
+ return numbers.every(isValidNumber);
+ },
+
+ isInRange(number) {
+ return number >= LOTTO_CONFIG.RANDOM_MIN && number <= LOTTO_CONFIG.RANDOM_MAX;
+ }
+};
\ No newline at end of file
diff --git a/src/validator/InputValidator.js b/src/validator/InputValidator.js
new file mode 100644
index 00000000..b0acb1d9
--- /dev/null
+++ b/src/validator/InputValidator.js
@@ -0,0 +1,60 @@
+import { ERROR_MESSAGE } from '../constant/message.js';
+import { validate } from '../util/validate.js';
+
+export const InputValidator = {
+ validatePurchaseAmount(input) {
+ if (validate.isEmpty(input)) {
+ throw new Error(ERROR_MESSAGE.PURCHASE.EMPTY_INPUT);
+ }
+ if (!validate.isNumber(input)) {
+ throw new Error(ERROR_MESSAGE.PURCHASE.NOT_NUMBER);
+ }
+ if (!validate.isInteger(input)) {
+ throw new Error(ERROR_MESSAGE.PURCHASE.NOT_INTEGER);
+ }
+ if (!validate.isPositiveNumber(input)) {
+ throw new Error(ERROR_MESSAGE.PURCHASE.NOT_POSITIVE);
+ }
+ if (Number(input) % 500 !== 0) {
+ throw new Error(ERROR_MESSAGE.PURCHASE.NOT_MULTIPLE_UNIT);
+ }
+
+ return input;
+ },
+
+ validateWinningNumbers(numbers) {
+ if (!validate.hasValidCount(numbers)) {
+ throw new Error(ERROR_MESSAGE.WINNING_NUMBERS.INVALID_COUNT);
+ }
+ if (!validate.areNumbersInRange(numbers)) {
+ throw new Error(ERROR_MESSAGE.WINNING_NUMBERS.OUT_OF_RANGE);
+ }
+ if (validate.hasDuplicate(numbers)) {
+ throw new Error(ERROR_MESSAGE.WINNING_NUMBERS.DUPLICATE_NUMBER);
+ }
+
+ return numbers;
+ },
+
+ validateBonusNumber(input, winningNumbers) {
+ if (validate.isEmpty(input)) {
+ throw new Error(ERROR_MESSAGE.BONUS.EMPTY_INPUT);
+ }
+ if (!validate.isNumber(input)) {
+ throw new Error(ERROR_MESSAGE.BONUS.NOT_NUMBER);
+ }
+ if (!validate.isInteger(input)) {
+ throw new Error(ERROR_MESSAGE.BONUS.NOT_INTEGER);
+ }
+ const number = Number(input);
+
+ if (!validate.isInRange(number)) {
+ throw new Error(ERROR_MESSAGE.BONUS.OUT_OF_RANGE);
+ }
+ if (winningNumbers.includes(number)) {
+ throw new Error(ERROR_MESSAGE.BONUS.DUPLICATE_WITH_WINNING);
+ }
+
+ return number;
+ }
+};
\ No newline at end of file