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
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,60 @@
# javascript-planetlotto-precourse

## 기능 체크리스트

#### 로또 구입

- [x] 구입 금액 입력
- [x] 구입 금액에 맞게 로또 개수 발행
- [x] 500 단위로 발행하되, 거스름돈 계산 수행

#### 로또 발행

- [x] 한 장당 1 ~ 30 사이 중복되지 않는 5개 번호 랜덤 발행
- [x] 번호 오름차순 정렬
- [x] 구매 개수 및 로또 번호 목록 출력

#### 당첨 번호 및 보너스 입력

- [x] 당첨 번호를 쉼표를 구분하여 입력
- [x] 입력된 번호는 5개
- [x] 각 번호는 1 ~ 30 사이의 정수
- [x] 당첨 번호 이외 보너스 번호 입력

#### 당첨 결과 계산

- [x] 구매한 모든 로또 번호와 당첨 번호 비교
- [x] 일치 개수 + 보너스 번호 포함 여부 계산
- [x] 일치 개수와 보너스 일치 여부 기준으로 등수 판별
- [x] 등수 갯수 집계
- [x] 총 상금 합산

#### 추가 기능

- [x] 거스름돈 계산
- [] 총 상금을 기반으로 로또 교환 여부 작성
- [] Y or N 로 입력 받은 후 N 입력 시 프로그램 종료

## 예외 체크리스트

#### 구입 금액 관련 예외

- [x] 구입 금액이 숫자가 아님
- [x] 500원 미만으로 입력했을 때

#### 당첨 번호 입력 관련 예외

- [x] 쉼표로 구분되지 않은 입력 -> '1 2 3 4 5'
- [x] 숫자 외 문자 포함
- [x] 숫자 5개 미만
- [x] 숫자 5개 초과
- [x] 중복된 번호 존재
- [x] 숫자 범위 초과 (1 미만 or 30 초과)
- [x] 공백 또는 빈 입력

#### 보너스 번호 입력 관련 예외

- [x] 숫자가 아닌 입력
- [x] 범위 초과 (1 미만 or 30 초과)
- [x] 당첨 번호와 중복
- [x] 공백 또는 빈 입력
7 changes: 6 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import LottoController from "./controller/lottoController.js";

class App {
async run() {}
async run() {
const lottoController = new LottoController();
await lottoController.controlLotto();
}
}

export default App;
45 changes: 45 additions & 0 deletions src/constants/lotto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
export const LOTTO_PRICE = 500;
export const LOTTO_MIN_NUMBER = 1;
export const LOTTO_MAX_NUMBER = 30;
export const LOTTO_SIZE = 5;
export const LOTTO_DELIMITER = ",";
export const RANK_RULE = {
RANKS: [
{
name: "FIRST",
matchCount: 5,
bonus: false,
prize: 100000000,
},
{
name: "SECOND",
matchCount: 4,
bonus: true,
prize: 10000000,
},
{
name: "THIRD",
matchCount: 4,
bonus: false,
prize: 1500000,
},
{
name: "FOURTH",
matchCount: 3,
bonus: true,
prize: 500000,
},
{
name: "FIFTH",
matchCount: 2,
bonus: true,
prize: 5000,
},
{
name: "MISS",
matchCount: 0,
bonus: false,
prize: 0,
},
],
};
9 changes: 9 additions & 0 deletions src/constants/message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const ERROR_MESSAGE = {
EMPTY_INPUT: "입력값이 비어 있습니다.",
INVALID_PURCHASE_MIN: "구입 금액은 500원 이상이어야 합니다.",
INVALID_LOTTO_NUMBER: "로또 번호는 1부터 30 사이의 숫자여야 합니다.",
INVALID_LOTTO_SIZE: "로또 번호는 5개여야 합니다.",
DUPLICATED_NUMBER: "로또 번호는 중복된 숫자가 발생할 수 없습니다.",
DUPLICATED_LOTTO_AND_BONUS_NUMBER:
"로또 번호와 보너스 번호는 중복될 수 없습니다.",
};
84 changes: 84 additions & 0 deletions src/controller/LottoController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import WinningLotto from "../model/WinningLotto.js";
import LottoService from "../service/LottoService.js";
import LottoValidator from "../service/LottoValidator.js";
import { InputView, OutputView } from "../view.js";

class LottoController {
#inputView;
#outputView;
#validator;
#lottoService;
constructor() {
this.#inputView = InputView;
this.#outputView = OutputView;
this.#validator = new LottoValidator();
this.#lottoService = new LottoService();
}

async controlLotto() {
const amount = await this.#inputAmount();

const { change, lottos } = this.#lottoService.publish(amount);
this.#printPurchaseResult(amount, change, lottos);

const winningNumbers = await this.#inputWinningNumbers();
const bonusNumber = await this.#inputBonusNumber(winningNumbers);

const winningLotto = new WinningLotto(winningNumbers, bonusNumber);
const { resultMap } = this.#lottoService.fillMapResult(
lottos,
winningLotto
);

this.#outputView.printResult(resultMap);
}

#printPurchaseResult(amount, change, lottos) {
const lottoArray = [];
for (let lotto of lottos) {
lottoArray.push(lotto.getNumbers());
}
this.#outputView.printPurchasedLottos(lottoArray);
if (change !== 0) {
this.#outputView.printChangeMessage(amount, change);
}
}

async #inputAmount() {
while (true) {
try {
const amount = await this.#inputView.askAmount();
this.#validator.validateAmount(amount);
return amount;
} catch (error) {
this.#outputView.printErrorMessage(error.message);
}
}
}

async #inputWinningNumbers() {
while (true) {
try {
const winningNumbers = await this.#inputView.askWinningLotto();
this.#validator.validateWinningNumbers(winningNumbers);
return winningNumbers;
} catch (error) {
this.#outputView.printErrorMessage(error.message);
}
}
}

async #inputBonusNumber(winningNumbers) {
while (true) {
try {
const bonusNumber = await this.#inputView.askBonusNumber();
this.#validator.validateBonusNumber(winningNumbers, bonusNumber);
return bonusNumber;
} catch (error) {
this.#outputView.printErrorMessage(error.message);
}
}
}
}

export default LottoController;
14 changes: 14 additions & 0 deletions src/factory/RankFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Rank from "../model/Rank.js";

class RankFactory {
static createRank(matchCount, bonusMatch) {
if (matchCount === 5) return Rank.FIRST;
if (matchCount === 4 && bonusMatch) return Rank.SECOND;
if (matchCount === 4) return Rank.THIRD;
if (matchCount === 3 && bonusMatch) return Rank.FOURTH;
if (matchCount === 2 && bonusMatch) return Rank.FIFTH;
if (matchCount === 0) return Rank.MISS;
}
}

export default RankFactory;
40 changes: 40 additions & 0 deletions src/model/Lotto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { LOTTO_SIZE } from "../constants/lotto.js";
import { ERROR_MESSAGE } from "../constants/message.js";
import { hasNoDuplicates } from "../utils/validator.js";

class Lotto {
#numbers;

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

#validate(numbers) {
if (numbers.length !== LOTTO_SIZE) {
throw new Error(ERROR_MESSAGE.INVALID_LOTTO_SIZE);
}

if (!hasNoDuplicates(numbers)) {
throw new DefaultError(ERROR_MESSAGE.DUPLICATED_NUMBER);
}
}

getNumbers() {
return [...this.#numbers];
}

matchCount(winningNumbers) {
const matchNumbers = this.#numbers.filter((number) =>
winningNumbers.includes(number)
);

return matchNumbers.length;
}

contains(number) {
return this.#numbers.includes(number);
}
}

export default Lotto;
39 changes: 39 additions & 0 deletions src/model/LottoResult.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { RANK_RULE } from "../constants/lotto.js";
import RankFactory from "../factory/RankFactory.js";

class LottoResult {
#counts;
constructor() {
this.#counts = this.#initCounts();
}

#initCounts() {
const counts = new Map();
RANK_RULE.RANKS.forEach((rankRule) => {
const rank = RankFactory.createRank(rankRule.matchCount, rankRule.bonus);
counts.set(rank, 0);
});

return counts;
}

add(rank) {
this.#counts.set(rank, this.#counts.get(rank) + 1);
}

getCountOf(rank) {
return this.#counts.get(rank) ?? 0;
}

getTotalPrize() {
let sum = 0;

for (const [rank, count] of this.#counts.entries()) {
sum += rank.prize * count;
}

return sum;
}
}

export default LottoResult;
30 changes: 30 additions & 0 deletions src/model/Rank.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { RANK_RULE } from "../constants/lotto.js";

class Rank {
#name;
#matchCount;
#bonus;
#prize;
constructor({ name, matchCount, bonus, prize }) {
this.#name = name;
this.#matchCount = matchCount;
this.#bonus = bonus;
this.#prize = prize;
}

static initializeRanks() {
const normalRanks = this.#createNormalRanks();

normalRanks.forEach((rank) => {
Rank[rank.#name] = rank;
});
}

static #createNormalRanks() {
return RANK_RULE.RANKS.map((rule) => new Rank(rule));
}
}

Rank.initializeRanks();

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

class WinningLotto {
#winningNumbers;
#bonusNumber;

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

#validate(winningNumbers, bonusNumber) {
if (winningNumbers.includes(bonusNumber)) {
throw new Error(ERROR_MESSAGE.DUPLICATED_LOTTO_AND_BONUS_NUMBER);
}
}

match(lotto) {
const matchCount = lotto.matchCount(this.#winningNumbers);
const bonusMatch = lotto.contains(this.#bonusNumber);

return { matchCount, bonusMatch };
}
}

export default WinningLotto;
Loading