Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
44328a4
docs(readme): 기능 목록 초안 작성
Hanbeeen Nov 3, 2025
1d1ff17
chore(Constants): 상수 및 문자열 중앙집중 정의
Hanbeeen Nov 3, 2025
e117516
chore(ErrorMessages): 에러 메시지 정의
Hanbeeen Nov 3, 2025
07f9ec7
refactor(ErrorMessages): 에러 메시지 Prefix 분리
Hanbeeen Nov 3, 2025
415eab0
test(LottoTest): Lotto 유효성 및 오름차순 정렬 단위 테스트 추가
Hanbeeen Nov 3, 2025
71602e0
refactor(domain): Lotto 및 LottoTest 디렉토리 domain 패키지로 변경
Hanbeeen Nov 3, 2025
4af22b9
refactor(ErrorMessages): 에러 메시지 enum으로 수정
Hanbeeen Nov 3, 2025
2cea0c5
refactor(ErrorMessages): PREFIX 접근제어 public으로 수정
Hanbeeen Nov 3, 2025
31d15b8
feat(LottoValidator): Lotto 크기, 범위, 중복 검증 구현
Hanbeeen Nov 3, 2025
b241d31
feat(Lotto): Lotto 클래스 생성자에 정렬 및 불변 보관 구현
Hanbeeen Nov 3, 2025
e3e4dba
refactor(support): 상수 및 메시지 클래스 분리
Hanbeeen Nov 3, 2025
c5126de
feat(Constants): 꽝 매직넘버 추가
Hanbeeen Nov 3, 2025
e2e71bb
test(RankTest): Rank 매핑 및 상금 일치 테스트 추가
Hanbeeen Nov 3, 2025
d16509d
feat(Rank): Rank enum (등수, 상금, 판정) 구현
Hanbeeen Nov 3, 2025
d4823b1
test(BonusValidatorTest): BonusValidator 범위 및 중복 테스트 추가
Hanbeeen Nov 3, 2025
ea764d3
feat(BonusValidator): 범위 및 중복 검증 추가
Hanbeeen Nov 3, 2025
0e704ef
chore(BonusValidatorTest): 미사용 import 제거
Hanbeeen Nov 3, 2025
e181f2b
test(MatchResultTest): getRank 매핑 및 미당첨 테스트 추가
Hanbeeen Nov 3, 2025
d5eb99c
feat(MatchResult): 매치 결과 getRank 추가
Hanbeeen Nov 3, 2025
3ed7751
test(WinningNumbersTest): 당첨번호 크기/범위/중복 및 보너스번호 범위/중복 테스트 추가
Hanbeeen Nov 3, 2025
b4da0cc
feat(WinningNumbers): 당첨번호 생성 시 유효성 검사, 번호 비교 로직 추가
Hanbeeen Nov 3, 2025
6c4d20e
chore(LottoTest): LottoValidatorTest를 별도로 만들지 않고 LottoTest에 함께 구현함을 명시
Hanbeeen Nov 3, 2025
a095f12
test(ResultCalculatorTest): 등수별 개수/총 당첨금/수익률 및 수익률 반올림 테스트 추가
Hanbeeen Nov 3, 2025
de37859
feat(WinningStatistics): 등수별 개수/총 당첨금/수익률 보관 및 포매팅 로직 추가
Hanbeeen Nov 3, 2025
7041009
feat(ResultCalculator): 결과 집계 및 수익률 계산 로직 추가
Hanbeeen Nov 3, 2025
2acf58c
chore(ErrorMessages): 로또 최저 발행 수량 에러 메시지 추가
Hanbeeen Nov 3, 2025
015591a
feat(LottoNumberGenerator): 로또 단일 번호 생성 구현
Hanbeeen Nov 3, 2025
2e3b367
feat(LottoGenerator): 로또 단일 및 다수 생성 구현
Hanbeeen Nov 3, 2025
752658d
feat(Purchaser): 로또 구매 금액 단위 검증 및 발행 구현
Hanbeeen Nov 3, 2025
9c59f47
feat(InputView): 입력 구현
Hanbeeen Nov 3, 2025
455fa5e
feat(OutputView): 출력 구현
Hanbeeen Nov 3, 2025
3a5c3fa
feat: controller
Hanbeeen Nov 3, 2025
49eee4d
feat: Application
Hanbeeen Nov 3, 2025
5f463f5
feat(OutputView): 에러 출력 구현
Hanbeeen Nov 3, 2025
a44d75e
refactor(LottoController): 상수 및 메시지 변경
Hanbeeen Nov 3, 2025
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 @@
# java-lotto-precourse

## 기능 목록
### 1. 입력 처리
1.1 구매 금액 입력
- 양의 정수, 1000원 단위, 1000원으로 나누어 떨어져야 함
- < 예외 > 숫자가 아님, 음수 또는 0, 1000원 단위 아님
1.2 당첨 번호 입력
- ","로 토큰 분리, 6개 숫자, 중복 없음, 1~45 범위
- < 예외 > 개수 불일치, 공백, 빈 토큰, 중복, 범위 위배
1.3 보너스 번호 입력
- 1개 숫자, 당첨 번호와 중북 금지, 1~45 범위
- < 예외 > 당첨 번호와 중복, 범위 위배
-> 예외 시 [ERROR] 후 재입력

### 2. 로또 발행
2.1 구매 개수 만큼 로또 발행
- 입력 금액 / 1000
- 각 로또는 1~45 범위에서 중복 없이 6개 숫자를 생성
- 주어진 메서드 사용 (Randoms.pickUniqueNumbersInRange)

### 3. 출력
3.1 발행 결과
- "n개를 구매했습니다."
- 다음 줄부터 각 로또 번호를 [1, 2, 3, 4, 5, 6] 포맷으로 출력
- 로또 번호는 오름차순으로 출력
3.2 당첨 통계

당첨 통계
---
3개 일치 (5,000원) - 0개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개

위 포맷으로 출력
3.3 수익률
- "총 수익률은 00.0%입니다."
- 소수 둘째 자리 반올림

### 4. 당첨 확인 및 정산
4.1 당첨 확인
- 각 로또와 당첨 번호를 비교하여 일치 개수 및 보너스 일치 여부 계산
4.2 등수 매핑

1등: 6개 번호 일치 / 2,000,000,000원
2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
3등: 5개 번호 일치 / 1,500,000원
4등: 4개 번호 일치 / 50,000원
5등: 3개 번호 일치 / 5,000원

4.3 정산
- 총 상금 합계 계산
- 수익률 계산 (총 상금 / 총 구매액 * 100)

### 5. 예외 처리
5.1 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException 발생
5.2 "[ERROR]"로 시작하는 에러 메시지 출력 후 그 부분부터 다시 입력 받음
5.3 Exception 포괄 처리를 지양하고 명확한 유형으로 처리
4 changes: 3 additions & 1 deletion src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package lotto;

import lotto.controller.LottoController;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
new LottoController().run();
}
}
20 changes: 0 additions & 20 deletions src/main/java/lotto/Lotto.java

This file was deleted.

85 changes: 85 additions & 0 deletions src/main/java/lotto/controller/LottoController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package lotto.controller;

import lotto.domain.Lotto;
import lotto.domain.WinningNumbers;
import lotto.domain.WinningStatistics;

import lotto.random.LottoNumberGenerator;
import lotto.service.LottoGenerator;
import lotto.service.ResultCalculator;
import lotto.support.ErrorMessages;
import lotto.view.InputView;
import lotto.view.OutputView;

import java.util.List;

import static lotto.support.Constants.PRICE_PER_TICKET;

public final class LottoController {

private final InputView inputView;
private final OutputView outputView;

private final LottoGenerator generator;

public LottoController() {
this.inputView = new InputView();
this.outputView = new OutputView();

LottoNumberGenerator picker = new LottoNumberGenerator();
this.generator = new LottoGenerator(picker);
}

public void run() {
try {
long purchaseAmount = askPurchaseAmount();
List<Lotto> tickets = purchase(purchaseAmount);
outputView.printPurchased(tickets);

WinningNumbers winningNumbers = askWinningNumbers();
WinningStatistics stats = ResultCalculator.calculate(tickets, winningNumbers, purchaseAmount);

outputView.printStatistics(stats);
} finally {
inputView.close();
}
}

private long askPurchaseAmount() {
while (true) {
try {
long money = inputView.readPurchaseAmount();
validatePurchaseUnit(money);
return money;
} catch (IllegalArgumentException e) {
outputView.printError(e.getMessage());
}
}
}

private void validatePurchaseUnit(long money) {
if (money <= 0) {
throw new IllegalArgumentException(ErrorMessages.INVALID_MONEY_RANGE.message());
}
if (money % PRICE_PER_TICKET != 0) {
throw new IllegalArgumentException(ErrorMessages.INVALID_MONEY_UNIT.message());
}
}

private List<Lotto> purchase(long money) {
int count = (int) (money / PRICE_PER_TICKET);
return generator.generateLottoes(count);
}

private WinningNumbers askWinningNumbers() {
while (true) {
try {
List<Integer> mains = inputView.readWinningNumbers();
int bonus = inputView.readBonusNumber();
return new WinningNumbers(mains, bonus);
} catch (IllegalArgumentException e) {
outputView.printError(e.getMessage());
}
}
}
}
28 changes: 28 additions & 0 deletions src/main/java/lotto/domain/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package lotto.domain;

import lotto.validator.LottoValidator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Lotto {
private final List<Integer> numbers;

public Lotto(List<Integer> numbers) {
validate(numbers);
List<Integer> copy = new ArrayList<>(numbers);
Collections.sort(copy);
this.numbers = Collections.unmodifiableList(copy);
}

private void validate(List<Integer> numbers) {
LottoValidator.validateSize(numbers);
LottoValidator.validateRange(numbers);
LottoValidator.validateDuplicate(numbers);
}

public List<Integer> getNumbers() {
return numbers;
}
}
23 changes: 23 additions & 0 deletions src/main/java/lotto/domain/MatchResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package lotto.domain;

public final class MatchResult {
private final int matchedCount;
private final boolean bonusMatched;

public MatchResult(int matchedCount, boolean bonusMatched) {
this.matchedCount = matchedCount;
this.bonusMatched = bonusMatched;
}

public int getMatchedCount() {
return matchedCount;
}

public boolean isBonusMatched() {
return bonusMatched;
}

public Rank getRank() {
return Rank.findRankByMatchCountAndBonus(matchedCount, bonusMatched);
}
}
35 changes: 35 additions & 0 deletions src/main/java/lotto/domain/Rank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package lotto.domain;

import static lotto.support.Constants.*;

public enum Rank {
FIRST(6, false, PRIZE_FIRST),
SECOND(5, true, PRIZE_SECOND),
THIRD(5, false, PRIZE_THIRD),
FOURTH(4, false, PRIZE_FOURTH),
FIFTH(3, false, PRIZE_FIFTH),
OOPS(0, false, PRIZE_NONE);

private final int matchCount;
private final boolean requireBonus;
private final long prize;

Rank(int matchCount, boolean requireBonus, long prize) {
this.matchCount = matchCount;
this.requireBonus = requireBonus;
this.prize = prize;
}

public long getPrize() {
return prize;
}

public static Rank findRankByMatchCountAndBonus(int matched, boolean bonusMatched) {
if (matched == 6) return FIRST;
if (matched == 5 && bonusMatched) return SECOND;
if (matched == 5) return THIRD;
if (matched == 4) return FOURTH;
if (matched == 3) return FIFTH;
return OOPS;
}
}
45 changes: 45 additions & 0 deletions src/main/java/lotto/domain/WinningNumbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package lotto.domain;

import lotto.validator.BonusValidator;
import lotto.validator.LottoValidator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public final class WinningNumbers {
private final List<Integer> numbers;
private final int bonusNumber;

public WinningNumbers(List<Integer> numbers, int bonusNumber) {
LottoValidator.validateSize(numbers);
LottoValidator.validateRange(numbers);
LottoValidator.validateDuplicate(numbers);
BonusValidator.validateBonus(numbers, bonusNumber);

List<Integer> copy = new ArrayList<>(numbers);
Collections.sort(copy);
this.numbers = Collections.unmodifiableList(copy);
this.bonusNumber = bonusNumber;
}

public MatchResult match(Lotto lotto) {
int matchedCount = 0;
boolean bonusMatched = false;
for (int n : lotto.getNumbers()) {
if (numbers.contains(n)) {
matchedCount++;
}
}
bonusMatched = lotto.getNumbers().contains(bonusNumber);
return new MatchResult(matchedCount, bonusMatched);
}

public List<Integer> getNumbers() {
return numbers;
}

public int getBonusNumber() {
return bonusNumber;
}
}
47 changes: 47 additions & 0 deletions src/main/java/lotto/domain/WinningStatistics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package lotto.domain;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;

public final class WinningStatistics {

private final EnumMap<Rank, Integer> counts;
private final long totalPrize;
private final BigDecimal roiPercent;

public WinningStatistics(Map<Rank, Integer> counts, long totalPrize, BigDecimal roiPercent) {
this.counts = new EnumMap<>(Rank.class);
for (Rank rank : Rank.values()) {
int count = counts.getOrDefault(rank, 0);
this.counts.put(rank, count);
}
this.totalPrize = totalPrize;
this.roiPercent = roiPercent;
}

public int getCount(Rank rank) {
Integer count = counts.get(rank);
if (count == null) return 0;
return count;
}

public Map<Rank, Integer> getCounts() {
return Collections.unmodifiableMap(counts);
}

public long getTotalPrize() {
return totalPrize;
}

public BigDecimal getRoiPercent() {
return roiPercent;
}

public String formatRoiPercent() {
DecimalFormat df = new DecimalFormat("#,##0.0'%'");
return df.format(roiPercent);
}
}
13 changes: 13 additions & 0 deletions src/main/java/lotto/random/LottoNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package lotto.random;

import camp.nextstep.edu.missionutils.Randoms;

import java.util.List;

import static lotto.support.Constants.*;

public final class LottoNumberGenerator {
public List<Integer> generateUniqueLottoNumbers() {
return Randoms.pickUniqueNumbersInRange(MIN_NUMBER, MAX_NUMBER, LOTTO_SIZE);
}
}
Loading