diff --git a/README.md b/README.md
index b168a180..8c8c546b 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,61 @@
-# javascript-planetlotto-precourse
+## 1️⃣ 과제 개요
+
+**우아한 테크코스 프리코스 최종 테스트**
+
+**과제명 : 행성 로또**
+
+**기간 : 01.10 (4시간)**
+
+**작성자 : 윤돌**
+
+
+
+## 2️⃣ 기능 목록
+
+**(0) 기본 구조 세팅**
+
+**(1) 로또 구입 금액 입력 기능 구현**
+
+**(2) 구입 금액 검증 로직 구현**
+
+- 숫자가 아닌 경우 예외처리
+- 0보다 작거나 같을 경우 예외처리
+- 500 단위로 떨어지지 않을 경우 예외처리
+
+**(3) 로또 발행 기능 구현**
+
+**(4) 저장 및 조회 가능한 LottoBundle 클래스 생성**
+
+**(5) 발행된 로또 번호 출력 기능 구현**
+
+**(6) 당첨 번호 입력 기능 구현**
+
+**(7) 당첨 번호 검증 로직 구현**
+
+- 5개가 아닌 경우 예외처리
+- 중복될 경우 예외처리
+- 숫자가 아닐 경우 예외처리
+- 1~30 사이가 아닐 경우 예외처리
+
+**(8) 보너스 번호 입력 기능 구현**
+
+**(9) 보너스 번호 검증 기능 구현**
+
+- 숫자가 아닐 경우 예외처리
+- 1~30 사이가 아닐 경우 예외처리
+
+**(10) 규칙 및 상금 정의**
+
+**(11) 당첨 결과 계산 기능 구현**
+
+**(12) 결과 구현(결과 집계 및 수익률 계산)**
+
+**(13) 결과 출력**
+
+**(14) 통합 테스트 확인**
+
+**(15) 단일 테스트 추가 및 확인**
+
+**(16) 추가 기능 구현**
+
+
diff --git a/__tests__/LottoJudgeTest.js b/__tests__/LottoJudgeTest.js
new file mode 100644
index 00000000..4f683638
--- /dev/null
+++ b/__tests__/LottoJudgeTest.js
@@ -0,0 +1,41 @@
+import LottoJudge from "../src/service/LottoJudge";
+import { PRIZES } from "../src/util/Rank";
+
+describe("로또 판단하는 클래스 테스트", () => {
+ test("로또 판단하는 judge 함수 정상 작동하는지 확인", () => {
+ expect(() => {
+ const lottoJudge = new LottoJudge([1, 2, 3, 4, 5], 6);
+ return lottoJudge
+ .judge([1, 2, 3, 4, 5])
+ .toStictEqual({ rank: 1, prize: PRIZES[1] });
+ });
+
+ expect(() => {
+ const lottoJudge = new LottoJudge([1, 2, 3, 4, 5], 6);
+ return lottoJudge
+ .judge([1, 2, 3, 4, 6])
+ .toStictEqual({ rank: 2, prize: PRIZES[2] });
+ });
+
+ expect(() => {
+ const lottoJudge = new LottoJudge([1, 2, 3, 4, 5], 6);
+ return lottoJudge
+ .judge([1, 2, 3, 4, 8])
+ .toStictEqual({ rank: 3, prize: PRIZES[3] });
+ });
+
+ expect(() => {
+ const lottoJudge = new LottoJudge([1, 2, 3, 4, 5], 6);
+ return lottoJudge
+ .judge([1, 2, 3, 8, 9])
+ .toStictEqual({ rank: 4, prize: PRIZES[4] });
+ });
+
+ expect(() => {
+ const lottoJudge = new LottoJudge([1, 2, 3, 4, 5], 6);
+ return lottoJudge
+ .judge([1, 2, 8, 9, 10])
+ .toStictEqual({ rank: 5, prize: PRIZES[5] });
+ });
+ });
+});
diff --git a/src/App.js b/src/App.js
index 091aa0a5..81a8d609 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,5 +1,51 @@
+import LottoBundle from "./model/LottoBundle.js";
+import LottoMachine from "./model/LottoMachine.js";
+import LottoJudge from "./service/LottoJudge.js";
+import Result from "./service/Result.js";
+import Validator from "./util/Validate.js";
+import { InputView, OutputView } from "./view.js";
+
class App {
- async run() {}
+ async run() {
+ try {
+ const purchaseAmount = await InputView.askAmount();
+ Validator.validatePurchaseAmount(purchaseAmount);
+
+ const lottoMachine = new LottoMachine(purchaseAmount);
+ const lottoBundle = new LottoBundle(lottoMachine.getLottos());
+
+ console.log("");
+ OutputView.printPurchasedLottos(lottoBundle.getAll());
+ console.log("");
+
+ const winningNumbers = await InputView.askWinningLotto();
+ Validator.validateWinningNumbers(winningNumbers);
+ console.log("");
+
+ const bonusNumber = await InputView.askBonusNumber();
+ Validator.validateBonusNumber(bonusNumber);
+ console.log("");
+
+ const lottoJudge = new LottoJudge(winningNumbers, bonusNumber);
+ const result = new Result();
+
+ result.calculate(lottoBundle, lottoJudge);
+ const rankCounts = result.getRankCounts();
+
+ OutputView.printResult(
+ new Map([
+ [1, rankCounts[1]],
+ [2, rankCounts[2]],
+ [3, rankCounts[3]],
+ [4, rankCounts[4]],
+ [5, rankCounts[5]],
+ [0, rankCounts[6]],
+ ])
+ );
+ } catch (error) {
+ OutputView.printErrorMessage(error.message);
+ }
+ }
}
export default App;
diff --git a/src/model/LottoBundle.js b/src/model/LottoBundle.js
new file mode 100644
index 00000000..5e201902
--- /dev/null
+++ b/src/model/LottoBundle.js
@@ -0,0 +1,21 @@
+class LottoBundle {
+ #lottoBundle;
+
+ constructor(lottoBundle) {
+ this.#lottoBundle = lottoBundle;
+ }
+
+ size() {
+ return this.#lottoBundle.length;
+ }
+
+ getAll() {
+ return [...this.#lottoBundle];
+ }
+
+ forEach(callback) {
+ this.#lottoBundle.forEach(callback);
+ }
+}
+
+export default LottoBundle;
diff --git a/src/model/LottoMachine.js b/src/model/LottoMachine.js
new file mode 100644
index 00000000..5f50dbea
--- /dev/null
+++ b/src/model/LottoMachine.js
@@ -0,0 +1,31 @@
+import { MissionUtils } from "@woowacourse/mission-utils";
+
+class LottoMachine {
+ #lottoBundle;
+
+ constructor(purchaseAmount) {
+ this.lottoCount = purchaseAmount / 500;
+ this.#lottoBundle = this.#generateLottos();
+ }
+
+ #generateLottos() {
+ const lottoBundle = [];
+ for (let i = 0; i < this.lottoCount; i++) {
+ const numbers = this.#generateRandomNumbers();
+ lottoBundle.push(Array(numbers));
+ }
+
+ return lottoBundle;
+ }
+
+ #generateRandomNumbers() {
+ const numbers = MissionUtils.Random.pickUniqueNumbersInRange(1, 30, 5);
+ return numbers.sort((a, b) => a - b);
+ }
+
+ getLottos() {
+ return this.#lottoBundle;
+ }
+}
+
+export default LottoMachine;
diff --git a/src/service/LottoJudge.js b/src/service/LottoJudge.js
new file mode 100644
index 00000000..c982935c
--- /dev/null
+++ b/src/service/LottoJudge.js
@@ -0,0 +1,19 @@
+import { getRank } from "../util/Rank.js";
+
+class LottoJudge {
+ constructor(winningNumbers, bonusNumber) {
+ this.winningNumbers = winningNumbers;
+ this.bonusNumber = bonusNumber;
+ }
+
+ judge(lotto) {
+ const matchCount = this.winningNumbers.filter((it) =>
+ lotto[0].includes(it)
+ ).length;
+ const hasBonus = lotto[0].includes(this.bonusNumber);
+
+ return getRank(matchCount, hasBonus);
+ }
+}
+
+export default LottoJudge;
diff --git a/src/service/Result.js b/src/service/Result.js
new file mode 100644
index 00000000..cf5b2129
--- /dev/null
+++ b/src/service/Result.js
@@ -0,0 +1,22 @@
+class Result {
+ constructor() {
+ this.rankCounts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 };
+ }
+
+ addRank(rank) {
+ if (rank) this.rankCounts[rank.rank]++;
+ }
+
+ calculate(lottoBundle, lottoJudge) {
+ lottoBundle.forEach((lotto) => {
+ const rank = lottoJudge.judge(lotto);
+ this.addRank(rank);
+ });
+ }
+
+ getRankCounts() {
+ return this.rankCounts;
+ }
+}
+
+export default Result;
diff --git a/src/util/Rank.js b/src/util/Rank.js
new file mode 100644
index 00000000..bdcdb988
--- /dev/null
+++ b/src/util/Rank.js
@@ -0,0 +1,32 @@
+export const RANKS = {
+ FIRST: 1,
+ SECOND: 2,
+ THIRD: 3,
+ FOURTH: 4,
+ FIFTH: 5,
+ SIXTH: 6,
+};
+
+export const PRIZES = {
+ [RANKS.FIRST]: 100000000,
+ [RANKS.SECOND]: 10000000,
+ [RANKS.THIRD]: 1500000,
+ [RANKS.FOURTH]: 500000,
+ [RANKS.FIFTH]: 5000,
+ [RANKS.SIXTH]: 0,
+};
+
+export function getRank(matchCount, hasBonus) {
+ if (matchCount === 5)
+ return { rank: RANKS.FIRST, prize: PRIZES[RANKS.FIRST] };
+ if (matchCount === 4 && hasBonus)
+ return { rank: RANKS.SECOND, prize: PRIZES[RANKS.SECOND] };
+ if (matchCount === 4)
+ return { rank: RANKS.THIRD, prize: PRIZES[RANKS.THIRD] };
+ if (matchCount === 3)
+ return { rank: RANKS.FOURTH, prize: PRIZES[RANKS.FOURTH] };
+ if (matchCount === 2)
+ return { rank: RANKS.FIFTH, prize: PRIZES[RANKS.FIFTH] };
+
+ return { rank: RANKS.SIXTH, prize: PRIZES[RANKS.FIFTH] };
+}
diff --git a/src/util/Validate.js b/src/util/Validate.js
new file mode 100644
index 00000000..0fd7e08a
--- /dev/null
+++ b/src/util/Validate.js
@@ -0,0 +1,44 @@
+const Validator = {
+ validatePurchaseAmount(input) {
+ const amount = Number(input);
+
+ if (amount <= 0) {
+ throw new Error("구입 금액은 0보다 커야 합니다.");
+ }
+
+ if (amount % 500 !== 0) {
+ throw new Error("구입 금액은 500원 단위로 입력해야 합니다.");
+ }
+ },
+
+ validateWinningNumbers(winningNumbers) {
+ if (winningNumbers.length !== 5) {
+ throw new Error("로또 번호는 5개여야 합니다.");
+ }
+
+ const uniqueNumbers = new Set(winningNumbers);
+ if (uniqueNumbers.size !== winningNumbers.length) {
+ throw new Error("로또 번호는 중복될 수 없습니다.");
+ }
+
+ winningNumbers.forEach((num) => {
+ if (num < 1 || num > 30) {
+ throw new Error("로또 번호는 1부터 30 사이의 숫자여야 합니다.");
+ }
+ });
+ },
+
+ validateBonusNumber(bonusNumber) {
+ const number = Number(bonusNumber);
+
+ if (isNaN(number)) {
+ throw new Error("[ERROR] 보너스 번호는 숫자여야 합니다.");
+ }
+
+ if (number < 1 || number > 45) {
+ throw new Error("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
+ }
+ },
+};
+
+export default Validator;