Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
62 changes: 61 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,61 @@
# javascript-planetlotto-precourse
## 1️⃣ 과제 개요

**우아한 테크코스 프리코스 최종 테스트**

**과제명 : 행성 로또**

**기간 : 01.10 (4시간)**

**작성자 : 윤돌**

<br>

## 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) 추가 기능 구현**

<br>
41 changes: 41 additions & 0 deletions __tests__/LottoJudgeTest.js
Original file line number Diff line number Diff line change
@@ -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] });
});
});
});
48 changes: 47 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -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;
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;
31 changes: 31 additions & 0 deletions src/model/LottoMachine.js
Original file line number Diff line number Diff line change
@@ -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;
19 changes: 19 additions & 0 deletions src/service/LottoJudge.js
Original file line number Diff line number Diff line change
@@ -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;
22 changes: 22 additions & 0 deletions src/service/Result.js
Original file line number Diff line number Diff line change
@@ -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;
32 changes: 32 additions & 0 deletions src/util/Rank.js
Original file line number Diff line number Diff line change
@@ -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] };
}
44 changes: 44 additions & 0 deletions src/util/Validate.js
Original file line number Diff line number Diff line change
@@ -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;