Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6c29f53
docs: README 작성 및 기능 구현 목록 작성
simhokyung Jan 10, 2026
0a5a938
feat: InputView, OutputView 입출력 구성방식 확인 및 초기 패키지 구성
simhokyung Jan 10, 2026
a0e3b20
feat(support): 구입금액 입력받은거 검증 구현
simhokyung Jan 10, 2026
5f0ea1e
feat(support): 당첨번호 입력받은거 검증
simhokyung Jan 10, 2026
abdc6ce
feat(support): 보너스 번호 입력받은거 검증
simhokyung Jan 10, 2026
0df1a52
feat(domain): Lotto 도메인 구현
simhokyung Jan 10, 2026
528c42d
feat(domain): Lottos 도메인 구현
simhokyung Jan 10, 2026
68e1109
feat(domain): LottoGenerator 구현
simhokyung Jan 10, 2026
c0c5ad2
feat(domain): Rank(Enum) 구현
simhokyung Jan 10, 2026
b20bc54
feat(domain): WinningNumbers 도메인 구현
simhokyung Jan 10, 2026
9629659
feat(domain): WinCountCalculator 구현(로또 당첨 횟수 계산)
simhokyung Jan 10, 2026
3c7ae1c
feat(controller): 로또 생성 기능 구현
simhokyung Jan 10, 2026
50266b6
feat(domain): WinningDetailsCalculator 구현
simhokyung Jan 10, 2026
515ea93
fix(domain): WinningDetailsCalculator 수정
simhokyung Jan 10, 2026
bd5f1bc
fix(support): validateFiveHundred 메서드 수정
simhokyung Jan 10, 2026
32a288f
docs: 기능 구현 목록 갱신
simhokyung Jan 10, 2026
1a317ee
refactor(domain) : Lotto 도메인에서 validate 부분 메서드로 추출하여 가독성 향상
simhokyung Jan 10, 2026
70df603
refactor(domain) : Lotto 도메인에 상수를 도입하여 매직넘버 제거
simhokyung Jan 10, 2026
6afa413
refactor(domain) : WinningNumbers 도메인 리팩토링(가독성 향상 및 검증 추가)
simhokyung Jan 10, 2026
69a38a6
refactor(domain) : Rank(Enum) 도메인 필드 및 메서드 리팩토링
simhokyung Jan 10, 2026
2a71450
refactor(domain) : WinningNumbers 도메인에 기능 추가
simhokyung Jan 10, 2026
2344523
refactor(domain) : WinCountCalculator 도메인 기능 수정
simhokyung Jan 10, 2026
ea6f595
fix(domain) : 도메인 이름 수정
simhokyung Jan 10, 2026
089a75c
refactor(support) : BonusNumber 매직 넘버 제거(상수 도입)
simhokyung Jan 10, 2026
8c6b792
refactor(support) : WinningNumber 매직넘버 제거
simhokyung Jan 10, 2026
1eb2f1c
fix(support) : validator에서 [ERROR] 메시지 제거
simhokyung Jan 10, 2026
b9926b3
fix(controller) : MainController 이름 변경
simhokyung Jan 10, 2026
d086e54
refactor(all) : 모든 코드 정렬(ctrl+alt+l)
simhokyung Jan 10, 2026
9398a86
test : Rank기능 테스트 완료
simhokyung Jan 10, 2026
3cbc676
test : WinningNumbers 기능 테스트 완료
simhokyung Jan 10, 2026
2fecc15
docs: 기능 구현 목록 갱신
simhokyung Jan 10, 2026
9b26bb6
docs: 기능 목록 구현 갱신
simhokyung Jan 10, 2026
3711281
docs: 기능 목록 구현 갱신
simhokyung Jan 10, 2026
82ce9e5
docs: 도전 과제 -> 도전 목표로 이름 수정
simhokyung Jan 10, 2026
33ea6cc
docs: 기능 구현 목록 갱신
simhokyung Jan 10, 2026
aa1de42
docs: 기능 구현 목록 갱신
simhokyung Jan 10, 2026
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
107 changes: 106 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,106 @@
# java-planetlotto-precourse
# 💻 우테코 8기 최종 - 행성 로또
Console을 이용하여 우테코 로또 발매기인 행성 로또를 구현한다.
구입 금액에 맞춰 로또를 발행하고, 당첨 내역을 출력한다.

---

## **📥 입출력 명세**

### ▫ 입력
1. 로또 구입 금액을 입력 받는다.
2. 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다.
3. 보너스 번호를 입력 받는다.

### ▫ 출력

1. 발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다.
2. 당첨 내역을 출력한다.
3. 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 "[ERROR]"로 시작해야 한다.


### ▫ 실행 예시

```
구입금액을 입력해 주세요.
1000

2개를 구매했습니다.
[8, 11, 13, 21, 22]
[1, 3, 6, 14, 22]

당첨 번호를 입력해 주세요.
1, 2, 3, 4, 5

보너스 번호 번호를 입력해 주세요.
6

당첨 통계
---
5개 일치 (100,000,000원) - 0개
4개 일치, 보너스 번호 일치 (10,000,000원) - 0개
4개 일치 (1,500,000원) - 0개
3개 일치, 보너스 번호 일치 (500,000원) - 0개
2개 일치, 보너스 번호 일치 (5,000원) - 1개
0개 일치 (0원) - 1개
```
---

## 📄기능 구현 목록

### 1) 입력 흐름

- [x] InputView, OutputView 입출력 구성방식 확인 및 초기 패키지 구성
- [x] 구입금액 입력받은거 검증
- [x] 당첨번호 입력받은거 검증
- [x] 보너스 번호 입력받은거 검증

### 2) 주요 기능
- [x] Lotto 도메인 구현
- [x] Lottos 도메인 구현
- [x] LottoGenerator 구현
- [x] Rank(Enum) 구현
- [x] WinningNumbers 도메인 구현
- [x] 로또 생성 기능 구현
- [x] WinCountCalculator 구현(로또 당첨 횟수 계산)
- [x] 로또 당첨 내역 기능 구현


### 3) 예외 및 주의사항 체크
- [x] 로또 번호의 숫자 범위는 1~30까지이다.
- [x] 1개의 로또를 발행할 때 중복되지 않는 5개의 숫자를 뽑는다.
- [x] 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
- [x] 로또 1장의 가격은 500원이다.
- [x] 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
- [x] 구입 금액은 500원 단위로 입력 받으며 500원으로 나누어 떨어지지 않는 경우 예외 처리한다.
- [x] 로또 번호는 오름차순으로 정렬하여 보여준다.
- [x] 에러 문구는 "[ERROR]"로 시작해야 한다.
- [x] 입력/출력 역할은 제공된 InputView, OutputView에서 수행하며 기존 메서드를 수정, 삭제할 수 없다.
- [x] OutputView에 있는 printErrorMessage 사용하기

### 4) 도전 목표
- 기본 요구 사항을 모두 충족한 후, 아래 중 하나를 선택하여 도전하세요. 단, 도전 과제 수행 여부와 관계없이 기본 기능은 반드시 작동해야 합니다.
- 도전 방향:
- [x] 리팩터링: 작동은 그대로 유지하면서 코드 품질을 높이는 방향을 목표로 한다.
- 상세 구현 목표:
- [x] 매직 넘버를 사용하지 않는다.
- [x] 도메인에서 활용할 수 있는 기능이 있으면 최대한 활용한다.
- [x] private, static 의미에 맞게 사용하기
- [x] 이중 검증(validate) 구현
- [x] 모든 코드 정렬
- 상세 과정은 커밋 메시지를 확인하세요.


### 5) 프로그래밍 요구사항 체크
- [x] 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다.
- [x] 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- [x] 기본으로 제공되는 테스트가 통과해야 한다.



### 6) 테스트 기능 목록 체크
- [x] 기능테스트: 전체 기능 구현: ("1000", "1,2,3,4,5", "6") 입력 시 실행예시에 맞게 출력
- [x] 예외테스트: 구입금액("500j") 입력 시 에러발생
- [x] 에러 테스트: 로또 번호의 개수가 5개가 넘어가면 예외가 발생
- [x] 에러 테스트: 로또 번호에 중복된 숫자가 있으면 예외가 발생
- [x] Rank 기능 테스트
- [x] WinningNumbers 기능 테스트
4 changes: 3 additions & 1 deletion src/main/java/planetlotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package planetlotto;

import planetlotto.controller.LottoController;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
new LottoController().run();
}
}
76 changes: 76 additions & 0 deletions src/main/java/planetlotto/controller/LottoController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package planetlotto.controller;

import planetlotto.domain.*;
import planetlotto.support.validator.BonusNumber;
import planetlotto.support.validator.PurchaseAmount;
import planetlotto.support.validator.WinningNumber;
import planetlotto.view.InputView;
import planetlotto.view.OutputView;

import java.util.List;
import java.util.Map;

public class LottoController {
private final LottoGenerator lottoGenerator = new LottoGenerator();
private final CountByRank countByRank = new CountByRank();

public void run() {
int purchaseAmount = readPurchaseAmountWithRetry();

Lottos lottos = lottoGenerator.generate(purchaseAmount / 500);
OutputView.printPurchasedLottos(lottos.getLottosAs2DList());

List<Integer> winning = readWinningNumbersWithRetry();

int bonusNumber = readBonusNumberWithRetry();

WinningNumbers winningNumbers = new WinningNumbers(winning, bonusNumber);

Map<Rank, Integer> ranks = ResultCalculator.tallyRanks(lottos, winningNumbers);

Map<Integer, Integer> countsByrank = countByRank.calculate(ranks);

OutputView.printResult(countsByrank);


}


private int readPurchaseAmountWithRetry() {
while (true) {
try {
int purchaseAmount = InputView.askAmount();
PurchaseAmount.validate(purchaseAmount);
return purchaseAmount;
} catch (IllegalArgumentException e) {
OutputView.printErrorMessage(e.getMessage());
}
}
}

private List<Integer> readWinningNumbersWithRetry() {
while (true) {
try {
List<Integer> winning = InputView.askWinningLotto();
WinningNumber.validate(winning); //입력 검증

return winning;
} catch (IllegalArgumentException e) {
OutputView.printErrorMessage(e.getMessage());
}
}
}

private int readBonusNumberWithRetry() {
while (true) {
try {
int bonusNumber = InputView.askBonusNumber();
BonusNumber.validate(bonusNumber); //입력 검증
return bonusNumber;

} catch (IllegalArgumentException e) {
OutputView.printErrorMessage(e.getMessage());
}
}
}
}
20 changes: 20 additions & 0 deletions src/main/java/planetlotto/domain/CountByRank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package planetlotto.domain;

import java.util.HashMap;
import java.util.Map;

public class CountByRank {

public Map<Integer, Integer> calculate(Map<Rank, Integer> ranks) {
Map<Integer, Integer> countsByRank = new HashMap<>();

countsByRank.put(1, ranks.getOrDefault(Rank.FIRST, 0));
countsByRank.put(2, ranks.getOrDefault(Rank.SECOND, 0));
countsByRank.put(3, ranks.getOrDefault(Rank.THIRD, 0));
countsByRank.put(4, ranks.getOrDefault(Rank.FOURTH, 0));
countsByRank.put(5, ranks.getOrDefault(Rank.FIFTH, 0));
countsByRank.put(0, ranks.getOrDefault(Rank.NONE, 0));

return countsByRank;
}
}
66 changes: 66 additions & 0 deletions src/main/java/planetlotto/domain/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package planetlotto.domain;

import java.util.*;

public class Lotto {
// 상수
private static final int LOTTO_SIZE = 5;
private static final int MIN_NUMBER = 1;
private static final int MAX_NUMBER = 30;

private final List<Integer> numbers; // 오름차순 불변 리스트

public Lotto(List<Integer> numbers) {
validate(numbers);
this.numbers = toSortedUnmodifiable(numbers);
}

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

private static void validateNull(List<Integer> numbers) {
if (numbers == null) {
throw new IllegalArgumentException("[ERROR] 로또 번호 목록이 null일 수 없습니다.");
}
}


private static void validateSize(List<Integer> numbers) {
if (numbers.size() != LOTTO_SIZE) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 " + LOTTO_SIZE + "개여야 합니다.");
}
}

private static void validateRange(List<Integer> numbers) {
for (Integer n : numbers) {
if (n == null || n < MIN_NUMBER || n > MAX_NUMBER) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 " + MIN_NUMBER + "부터 " + MAX_NUMBER + " 사이여야 합니다.");
}
}
}

private static void validateDuplicate(List<Integer> numbers) {
Set<Integer> set = new HashSet<>(numbers);
if (set.size() != numbers.size()) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 중복될 수 없습니다.");
}
}


// --- 정렬 & 불변 보관 ---
private List<Integer> toSortedUnmodifiable(List<Integer> numbers) {
List<Integer> copy = new ArrayList<>(numbers);
Collections.sort(copy);
return Collections.unmodifiableList(copy);
}


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

}
18 changes: 18 additions & 0 deletions src/main/java/planetlotto/domain/LottoGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package planetlotto.domain;

import camp.nextstep.edu.missionutils.Randoms;

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

public class LottoGenerator {

public Lottos generate(long count) {
List<Lotto> lottos = new ArrayList<>();
for (int i = 0; i < count; i++) {
List<Integer> numbers = Randoms.pickUniqueNumbersInRange(1, 30, 5);
lottos.add(new Lotto(numbers));
}
return new Lottos(lottos);
}
}
28 changes: 28 additions & 0 deletions src/main/java/planetlotto/domain/Lottos.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package planetlotto.domain;

import java.util.List;
import java.util.stream.Collectors;

public class Lottos {

public List<Lotto> lottos;

public Lottos(List<Lotto> lottos) {
this.lottos = lottos;
}


public int size() {
return lottos.size();
}

public List<List<Integer>> getLottosAs2DList() {
return lottos.stream()
.map(Lotto::getNumbers) // Lotto 객체의 getNumbers() 메소드 호출
.collect(Collectors.toList());
}

public List<Lotto> getLottos() {
return lottos;
}
}
50 changes: 50 additions & 0 deletions src/main/java/planetlotto/domain/Rank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package planetlotto.domain;

public enum Rank {

FIRST(5, false, 100_000_000L),
SECOND(4, true, 10_000_000L),
THIRD(4, false, 1_500_000L),
FOURTH(3, true, 500_000L),
FIFTH(2, true, 5_000L),
NONE(0, false, 0L);

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

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


public static Rank of(int matchCount, boolean isBonus) {
// 조기 반환으로 분기 (else/switch/3항 금지)
if (matchCount == 5) {
return FIRST;
}
if (matchCount == 4 && isBonus) {
return SECOND;
}
if (matchCount == 4) {
return THIRD;
}
if (matchCount == 3 && isBonus) {
return FOURTH;
}
if (matchCount == 2 && isBonus) {
return FIFTH;
}
return NONE;
}

public long getPrize(){
return prize;
}

public int getMatchCount(){
return matchCount;
}
}
Loading