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

## 프로젝트 구현 기능 목록

### 1. 상수 정의

- 로또 기본 상수 정의 (번호 범위, 개수, 가격)
- 당첨 등급별 상금 상수 정의

### 2. Lotto 클래스 (로또 번호 검증 및 관리)

- 로또 번호는 5개여야 한다.
- 로또 번호는 1~30 사이여야 한다.
- 로또 번호는 중복되지 않아야 한다.
- 로또 번호는 정수여야 한다.
- 로또 번호를 오름차순으로 정렬하여 반환한다.

### 3. Rank 클래스 (당첨 등급 판별)

- 일치 개수와 보너스 번호 일치 여부로 등급을 판별한다.
- 5개 일치: 1등 (100,000,000원)
- 4개 일치 + 보너스 번호 일치: 2등 (10,000,000원)
- 4개 일치: 3등 (1,500,000원)
- 3개 일치 + 보너스 번호 일치: 4등 (500,000원)
- 2개 일치 + 보너스 번호 일치: 5등 (5,000원)
- 0개 일치: (0원)

### 4. WinningNumber 클래스 (당첨 번호 관리)

- 당첨 번호는 로또 번호 검증 규칙을 따른다.
- 보너스 번호는 정수여야 한다.
- 보너스 번호는 1~30 사이여야 한다.
- 보너스 번호는 당첨 번호와 중복되지 않아야 한다.

### 5. LottoMatcher 클래스 (당첨 확인)

- 구매한 로또와 당첨 번호를 비교하여 일치 개수를 센다.
- 보너스 번호 일치 여부를 확인한다.
- 일치 결과를 바탕으로 당첨 등급을 반환한다.

### 6. LottoMachine 클래스 (로또 발행)

- 구매 금액은 최소 1,000원 이상이어야 한다.
- 구매 금액은 1,000원 단위여야 한다.
- 구매 금액에 맞는 개수만큼 로또를 발행한다.
- 로또 번호는 1~45 사이에서 중복 없이 6개 무작위로 생성한다.

### 7. LottoStatistics 클래스 (통계)

- 등급별 당첨 개수를 집계한다.
- 총 당첨 금액을 계산한다.

### 8. LottoController 클래스 (전체 흐름 통합)

- 구매 금액 입력 및 검증 (예외 발생 시 재입력)
- 로또 발행 및 출력
- 당첨 번호 및 보너스 번호 입력 및 검증 (예외 발생 시 재입력)
- 당첨 확인 및 통계 계산
- 결과 출력
- 예외 발생 시 "[ERROR]"로 시작하는 메시지 출력
- 예외 발생 시 해당 지점부터 다시 입력받기
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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 controller = new LottoController();
await controller.run();
}
}

export default App;
12 changes: 12 additions & 0 deletions src/constants/lottoConstants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const LOTTO_CONFIG = {
PRICE: 500,
NUMBER_COUNT: 5,
MIN_NUMBER: 1,
MAX_NUMBER: 30,
};

export const STATISTICS_CONFIG = {
RETURN_RATE_MULTIPLIER: 500,
RETURN_RATE_DIVISOR: 10,
DECIMAL_PLACES: 1,
};
17 changes: 17 additions & 0 deletions src/constants/rankConstants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const RANK = {
FIRST: { name: '1등', match: 5, bonus: false, amount: 100_000_000 },
SECOND: { name: '2등', match: 4, bonus: true, amount: 10_000_000 },
THIRD: { name: '3등', match: 4, bonus: false, amount: 1_500_000 },
FOURTH: { name: '4등', match: 3, bonus: true, amount: 500_000 },
FIFTH: { name: '5등', match: 2, bonus: true, amount: 5_000 },
ZERO: { name: '0등', match: 0, bonus: false, amount: 0 },
};

export const LOTTO_RANKS = [
RANK.FIRST,
RANK.SECOND,
RANK.THIRD,
RANK.FOURTH,
RANK.FIFTH,
RANK.ZERO,
];
115 changes: 115 additions & 0 deletions src/controller/LottoController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { LOTTO_CONFIG } from '../constants/lottoConstants.js';
import InputView from '../view/InputView.js';
import OutputView from '../view/OutputView.js';
import LottoMachine from '../domain/LottoMachine.js';
import WinningNumber from '../domain/WinningNumber.js';
import LottoMatcher from '../domain/LottoMatcher.js';
import LottoStatistics from '../domain/LottoStatistics.js';
import PurchaseAmount from '../domain/PurchaseAmount.js';

class LottoController {
#inputView;
#outputView;

constructor() {
this.#inputView = new InputView();
this.#outputView = new OutputView();
}

async run() {
const { purchaseAmount, lottos } = await this.#purchaseLottos();
const winningNumber = await this.#getWinningNumber();
const results = this.#checkWinning(lottos, winningNumber);
this.#printResults(results, purchaseAmount);
}

async #purchaseLottos() {
while (true) {
try {
const amount = await this.#inputPurchaseAmount();
const lottos = this.#issueLottos(amount);
this.#printLottos(lottos);
return { purchaseAmount: amount, lottos };
} catch (error) {
this.#outputView.printError(error.message);
}
}
}

async #inputPurchaseAmount() {
const input = await this.#inputView.readPurchaseAmount();
const purchaseAmount = new PurchaseAmount(input);
return purchaseAmount.getValue();
}

#issueLottos(amount) {
const machine = new LottoMachine();
return machine.createLottos(amount);
}

#printLottos(lottos) {
this.#outputView.printLottos(lottos);
}

async #getWinningNumber() {
while (true) {
try {
const numbersInput = await this.#inputView.readWinningNumbers();
const numbers = this.#parseNumbers(numbersInput);

const bonusInput = await this.#inputView.readBonusNumber();
const bonus = this.#parseBonus(bonusInput);

return new WinningNumber(numbers, bonus);
} catch (error) {
this.#outputView.printError(error.message);
}
}
}

#parseNumbers(input) {
const numbers = input
.split(',')
.map((num) =>
this.#validateNumber(
num.trim(),
'[ERROR] 당첨 번호는 숫자여야 합니다!',
),
);

if (numbers.length !== LOTTO_CONFIG.NUMBER_COUNT) {
throw new Error('[ERROR] 로또 번호는 5개여야 합니다!');
}

return numbers;
}

#parseBonus(input) {
return this.#validateNumber(
input.trim(),
'[ERROR] 보너스 번호는 숫자여야 합니다!',
);
}

#checkWinning(lottos, winningNumber) {
return lottos.map((lotto) => {
const matcher = new LottoMatcher(lotto, winningNumber);
return matcher.determineRank();
});
}

#printResults(results, purchaseAmount) {
const statistics = new LottoStatistics(results);
this.#outputView.printStatistics(statistics, purchaseAmount);
}

#validateNumber(value, errorMessage) {
const parsed = Number(value);
if (Number.isNaN(parsed)) {
throw new Error(errorMessage);
}
return parsed;
}
}

export default LottoController;
66 changes: 66 additions & 0 deletions src/domain/Lotto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { LOTTO_CONFIG } from '../constants/lottoConstants.js';

class Lotto {
#numbers;

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

#validate(numbers) {
this.#validateType(numbers);
this.#validateLength(numbers);
this.#validateDuplicate(numbers);
this.#validateRange(numbers);
}

#validateType(numbers) {
if (numbers.some((num) => !Number.isInteger(num))) {
throw new Error('[ERROR] 로또 번호는 숫자로만 입력 가능합니다!');
}
}

#validateLength(numbers) {
if (numbers.length !== LOTTO_CONFIG.NUMBER_COUNT) {
throw new Error('[ERROR] 로또 번호는 5개여야 합니다!');
}
}

#validateDuplicate(numbers) {
if (new Set(numbers).size !== numbers.length) {
throw new Error('[ERROR] 중복된 숫자가 있으면 안됩니다!');
}
}

#validateRange(numbers) {
if (
numbers.some(
(num) => num < LOTTO_CONFIG.MIN_NUMBER || num > LOTTO_CONFIG.MAX_NUMBER,
)
) {
throw new Error('[ERROR] 로또 번호는 1~45 사이여야 합니다!');
}
}

countMatchesWith(otherLotto) {
let count = 0;
for (const number of this.#numbers) {
if (otherLotto.hasNumber(number)) {
count++;
}
}
return count;
}

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

formatSorted() {
const sorted = [...this.#numbers].sort((a, b) => a - b);
return `[${sorted.join(', ')}]`;
}
}

export default Lotto;
39 changes: 39 additions & 0 deletions src/domain/LottoMachine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Random } from '@woowacourse/mission-utils';
import Lotto from './Lotto.js';
import { LOTTO_CONFIG } from '../constants/lottoConstants.js';

class LottoMachine {
createLottos(purchaseAmount) {
this.#validatePurchaseAmount(purchaseAmount);

const count = purchaseAmount / LOTTO_CONFIG.PRICE;
const lottos = [];

for (let i = 0; i < count; i++) {
lottos.push(this.#createLotto());
}

return lottos;
}

#validatePurchaseAmount(amount) {
if (amount < LOTTO_CONFIG.PRICE) {
throw new Error('[ERROR] 구입 금액은 최소 500원 부터입니다!');
}

if (amount % LOTTO_CONFIG.PRICE !== 0) {
throw new Error('[ERROR] 구입 금액이 500원 단위가 아닙니다!');
}
}

#createLotto() {
const numbers = Random.pickUniqueNumbersInRange(
LOTTO_CONFIG.MIN_NUMBER,
LOTTO_CONFIG.MAX_NUMBER,
LOTTO_CONFIG.NUMBER_COUNT,
);
return new Lotto(numbers);
}
}

export default LottoMachine;
17 changes: 17 additions & 0 deletions src/domain/LottoMatcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Rank from './Rank.js';

class LottoMatcher {
#matchCount;
#hasBonus;

constructor(lotto, winningNumber) {
this.#matchCount = winningNumber.countMatchesWith(lotto);
this.#hasBonus = winningNumber.isBonusMatch(lotto);
}

determineRank() {
return Rank.from(this.#matchCount, this.#hasBonus);
}
}

export default LottoMatcher;
Loading