Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,59 @@
# javascript-lotto-precourse
## 1️⃣ 과제 개요

**우아한 테크코스 프리코스 3주차 과제**

**과제명 : 로또**

**기간 : 10.28 ~ 11.03 (3주차)**

**작성자 : 윤돌**

<br>

## 2️⃣ 기능 목록

**(0) 기본 구조 세팅**

**(1) 로또 구입 금액 입력 기능 구현**

**(2) 구입 금액 검증 로직 구현**

- 숫자가 아닌 경우 예외처리
- 0보다 작거나 같을 경우 예외처리
- 1000 단위로 떨어지지 않을 경우 예외처리

**(3) 로또 발행 기능 구현**

**(4) 저장 및 조회 가능한 LottoBundle 클래스 생성**

**(5) 발행된 로또 번호 출력 기능 구현**

**(6) 당첨 번호 입력 기능 구현**

**(7) 당첨 번호 검증 로직 구현**

- 6개가 아닌 경우 예외처리
- 중복될 경우 예외처리
- 숫자가 아닐 경우 예외처리
- 1~45 사이가 아닐 경우 예외처리

**(8) 보너스 번호 입력 기능 구현**

**(9) 보너스 번호 검증 기능 구현**

- 숫자가 아닐 경우 예외처리
- 1~45 사이가 아닐 경우 예외처리

**(10) 규칙 및 상금 정의**

**(11) 당첨 결과 계산 기능 구현**

**(12) 결과 구현(결과 집계 및 수익률 계산)**

**(13) 결과 출력**

**(14) 통합 테스트 확인**

**(15) 단일 테스트 추가 및 확인**

<br>
12 changes: 12 additions & 0 deletions __tests__/BonusNumberTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Validator from "../src/util/Validator";

describe("validateBonusNumber", () => {
test("숫자가 아니면 예외가 발생한다.", () => {
expect(() => Validator.validateBonusNumber("abc")).toThrow("[ERROR]");
});

test("1 ~ 45 범위를 벗어나면 예외가 발생한다.", () => {
expect(() => Validator.validateBonusNumber("0")).toThrow("[ERROR]");
expect(() => Validator.validateBonusNumber("46")).toThrow("[ERROR]");
});
});
7 changes: 7 additions & 0 deletions __tests__/LottoJudgeTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import LottoJudge from "../src/service/LottoJudge";

describe("로또 판단하는 클래스 테스트", () => {
test("보너스 번호가 당첨 번호에 포함되어 있으면 에러를 던진다.", () => {
expect(() => new LottoJudge([1, 2, 3, 4, 5, 6], 6)).toThrow("[ERROR]");
});
});
12 changes: 11 additions & 1 deletion __tests__/LottoTest.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Lotto from "../src/Lotto";
import Lotto from "../src/model/Lotto";

describe("로또 클래스 테스트", () => {
test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => {
Expand All @@ -15,4 +15,14 @@ describe("로또 클래스 테스트", () => {
});

// TODO: 추가 기능 구현에 따른 테스트 코드 작성
test("숫자가 아니면 예외가 발생한다.", () => {
expect(() => {
new Lotto([1, 2, 3, 4, 5, NaN]);
}).toThrow("[ERROR]");
});

test("1 ~ 45 범위를 벗어나면 예외가 발생한다.", () => {
expect(() => new Lotto([0, 2, 3, 4, 5, 6])).toThrow("[ERROR]");
expect(() => new Lotto([1, 2, 3, 4, 5, 46])).toThrow("[ERROR]");
});
});
17 changes: 17 additions & 0 deletions __tests__/PurchaseAmountTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Validator from "../src/util/Validator";

describe("구입 금액 검증 테스트", () => {
test("숫자가 아니면 예외가 발생한다.", () => {
expect(() => Validator.validatePurchaseAmount("abc")).toThrow("[ERROR]");
});

test("0 이하면 예외가 발생한다.", () => {
expect(() => Validator.validatePurchaseAmount("0")).toThrow("[ERROR]");
expect(() => Validator.validatePurchaseAmount("-1000")).toThrow("[ERROR]");
});

test("1000 단위가 아니면 예외가 발생한다.", () => {
expect(() => Validator.validatePurchaseAmount("1500")).toThrow("[ERROR]");
expect(() => Validator.validatePurchaseAmount("2999")).toThrow("[ERROR]");
});
});
38 changes: 38 additions & 0 deletions __tests__/WinningNumbersTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Validator from "../src/util/Validator";

describe("당첨 번호 검증 테스트", () => {
test("개수가 6개가 아니면 예외가 발생한다.", () => {
expect(() =>
Validator.validateWinningNumbers([1, 2, 3, 4, 5, 6, 7])
).toThrow("[ERROR]");
expect(() => Validator.validateWinningNumbers([1, 2, 3, 4, 5])).toThrow(
"[ERROR]"
);
});

test("중복이 있으면 예외가 발생한다.", () => {
expect(() => Validator.validateWinningNumbers([1, 2, 3, 4, 5, 5])).toThrow(
"[ERROR]"
);
});

test("숫자가 아니면 예외가 발생한다.", () => {
// 문자열 'a' 포함
expect(() =>
Validator.validateWinningNumbers(["a", 2, 3, 4, 5, 6])
).toThrow("[ERROR]");
// NaN 직접 포함
expect(() =>
Validator.validateWinningNumbers([NaN, 2, 3, 4, 5, 6])
).toThrow("[ERROR]");
});

test("1 ~ 45 범위를 벗어나면 예외가 발생한다.", () => {
expect(() => Validator.validateWinningNumbers([0, 2, 3, 4, 5, 6])).toThrow(
"[ERROR]"
);
expect(() => Validator.validateWinningNumbers([1, 2, 3, 4, 5, 46])).toThrow(
"[ERROR]"
);
});
});
34 changes: 33 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
import { Console } from "@woowacourse/mission-utils";
import LottoBundle from "./model/LottoBundle.js";
import Result from "./model/Result.js";
import LottoJudge from "./service/LottoJudge.js";
import LottoMachine from "./service/LottoMachine.js";
import InputView from "./view/InputView.js";
import OutputView from "./view/OutputView.js";

class App {
async run() {}
async run() {
try {
const purchaseAmount = await InputView.readPurchaseAmount();
const lottoMachine = new LottoMachine(purchaseAmount);
const lottoBundle = new LottoBundle(lottoMachine.getLottos());

OutputView.printLottoBundle(lottoBundle);

const winningNumbers = await InputView.readWinningNumbers();
OutputView.printLineBreak();
const bonusNumber = await InputView.readBonusNumber();

const lottoJudge = new LottoJudge(winningNumbers, bonusNumber);
const result = new Result();

result.calculate(lottoBundle, lottoJudge);
const rankCounts = result.getRankCounts();
const profitRate = result.getProfitRate(purchaseAmount);

OutputView.printResult(rankCounts);
OutputView.printProfitRate(profitRate);
} catch (error) {
Console.print(error.message);
}
}
}

export default App;
18 changes: 0 additions & 18 deletions src/Lotto.js

This file was deleted.

36 changes: 36 additions & 0 deletions src/model/Lotto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class Lotto {
#numbers;

constructor(numbers) {
this.#validate(numbers);
this.#numbers = numbers;
}

#validate(numbers) {
if (numbers.length !== 6) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
}

const uniqueNumbers = new Set(numbers);
if (uniqueNumbers.size !== numbers.length) {
throw new Error("[ERROR] 로또 번호는 중복될 수 없습니다.");
}

numbers.forEach((num) => {
if (isNaN(num)) {
throw new Error("[ERROR] 로또 번호는 숫자만 입력해야 합니다.");
}

if (num < 1 || num > 45) {
throw new Error("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}
});
}

// TODO: 추가 기능 구현
getNumbers() {
return this.#numbers;
}
}

export default Lotto;
21 changes: 21 additions & 0 deletions src/model/LottoBundle.js
Original file line number Diff line number Diff line change
@@ -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;
30 changes: 30 additions & 0 deletions src/model/Rank.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export const RANKS = {
FIRST: 1,
SECOND: 2,
THIRD: 3,
FOURTH: 4,
FIFTH: 5,
};

export const PRIZES = {
[RANKS.FIRST]: 2000000000,
[RANKS.SECOND]: 30000000,
[RANKS.THIRD]: 1500000,
[RANKS.FOURTH]: 50000,
[RANKS.FIFTH]: 5000,
};

export function getRank(matchCount, hasBonus) {
if (matchCount === 6)
return { rank: RANKS.FIRST, prize: PRIZES[RANKS.FIRST] };
if (matchCount === 5 && hasBonus)
return { rank: RANKS.SECOND, prize: PRIZES[RANKS.SECOND] };
if (matchCount === 5)
return { rank: RANKS.THIRD, prize: PRIZES[RANKS.THIRD] };
if (matchCount === 4)
return { rank: RANKS.FOURTH, prize: PRIZES[RANKS.FOURTH] };
if (matchCount === 3)
return { rank: RANKS.FIFTH, prize: PRIZES[RANKS.FIFTH] };

return null;
}
36 changes: 36 additions & 0 deletions src/model/Result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { PRIZES } from "./Rank.js";

class Result {
constructor() {
this.rankCounts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
}

addRank(rank) {
if (rank) this.rankCounts[rank.rank]++;
}

calculate(lottoBundle, lottoJudge) {
lottoBundle.forEach((lotto) => {
const rank = lottoJudge.judge(lotto);
this.addRank(rank);
});
}

getTotalPrize() {
return Object.entries(this.rankCounts).reduce((total, [rank, count]) => {
const prize = PRIZES[rank] ?? 0;
return total + prize * count;
}, 0);
}

getProfitRate(purchaseAmount) {
const totalPrize = this.getTotalPrize();
return ((totalPrize / purchaseAmount) * 100).toFixed(1);
}

getRankCounts() {
return this.rankCounts;
}
}

export default Result;
27 changes: 27 additions & 0 deletions src/service/LottoJudge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { getRank } from "../model/Rank.js";

class LottoJudge {
constructor(winningNumbers, bonusNumber) {
this.#validate(winningNumbers, bonusNumber);
this.winningNumbers = winningNumbers;
this.bonusNumber = bonusNumber;
}

#validate(winningNumbers, bonusNumber) {
if (winningNumbers.includes(bonusNumber)) {
throw new Error("[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다.");
}
}

judge(lotto) {
const numbers = lotto.getNumbers();
const matchCount = numbers.filter((num) =>
this.winningNumbers.includes(num)
).length;
const hasBonus = numbers.includes(this.bonusNumber);

return getRank(matchCount, hasBonus);
}
}

export default LottoJudge;
Loading