Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 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
67 changes: 39 additions & 28 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,35 +1,46 @@
HELP.md
.gradle
build/
# =====================================
# (Java / Gradle / IntelliJ 프로젝트 기준)
# =====================================

# === 빌드 & 실행 결과물 ===
/build/
out/
bin/
.gradle/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**
!**/src/test/**

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws

# === IntelliJ IDEA 설정 ===
.idea/
*.iml
*.ipr
out/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
*.iws

### VS Code ###
# === VSCode (혹시 사용할 경우) ===
.vscode/

### Mac OS ###
# === OS 자동 생성 파일 ===
.DS_Store
Thumbs.db

# === 로그 & 캐시 파일 ===
*.log
*.tmp
*.bak
*.swp

# === 컴파일 산출물 ===
*.class
*.jar
*.war
*.ear

# === 테스트 관련 ===
/test-output/
/reports/

# === 환경변수, 개인 설정 ===
*.env
*.local

# === 백업 파일 ===
*~
92 changes: 91 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,91 @@
# java-lotto-precourse
# 🎰 로또

---
## ⚙️ 구현 목록

### 입력
- [x] 구입 금액 입력 (콘솔 입력, 1000원 단위 검증)
- [x] 당첨 번호 6개 입력 (쉼표 구분)
- [x] 보너스 번호 1개 입력

### 로또 로직
- [ ] 구입 금액만큼 로또 생성
- [ ] 당첨 번호 및 보너스 번호 저장
- [ ] 구매한 로또 번호 비교
- [ ] 일치 및 보너스 여부에 따른 등수 계산
- [ ] 당첨 내역 및 수익률 계산

### 출력
- [ ] 구매한 로또 수량 및 번호 출력
- [ ] 당첨 내역 및 등수별 결과 출력
- [ ] 총 수익률 출력 (소수점 둘째 자리, nn.n%)


### 예외 처리
- [ ] 잘못된 입력 시 `[ERROR]`로 시작하는 에러 메시지 출력 후 재입력

---

## 🧪 실행 예시

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

8개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
...

당첨 번호를 입력해 주세요.
1,2,3,4,5,6
보너스 번호를 입력해 주세요.
7

당첨 통계

3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
총 수익률은 62.5%입니다.
```
---

## 🧩 단위 테스트 계획

### LottoTest
- [x] 로또 번호 개수가 6개가 아니면 예외
- [x] 로또 번호에 중복된 숫자가 있으면 예외 발생
- [x] 로또 번호가 1~45 범위를 벗어나면 예외 발생

### RankTest
- [x] 일치 개수가 6개면 1등으로 판단
- [x] 일치 개수가 5개이고 보너스 번호 일치 시 2등으로 판단
- [x] 일치 개수가 5개이고 보너스 번호 불일치 시 3등으로 판단
- [x] 일치 개수가 4개면 4등으로 판단
- [x] 일치 개수가 3개면 5등으로 판단
- [x] 일치 개수가 2개 이하일 경우 당첨 아님 처리

### LottoResultTest
- [x] 여러 로또의 당첨 결과 집계가 정확한지 확인
- [x] 수익률 계산이 정확한지 검증 (소수점 둘째 자리 반올림)

### LottoMachineTest
- [x] 구입 금액에 따라 로또가 알맞게 생성되는지 확인
- [x] 생성된 모든 로또가 중복되지 않고 유효한 번호를 가지는지 검증

---

## ✅ 피드백 기반 미니 퀘스트

### 🚗 2주차 피드백 반영 (✅ 이미 완료된 항목 제외)
- **테스트 코드 작성 및 단위 분리**
- 이번 주는 본격적으로 도메인 중심 단위 테스트를 작성한다.
- **테스트의 목적을 명확히 이해**
- “동작 확인”이 아니라 “의도 검증” 중심으로 작성
- **입력/출력(UI)과 로직 분리**
- 기존 분리 구조 유지, 로또 로직은 도메인 중심으로 구성

---
101 changes: 99 additions & 2 deletions src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,104 @@
package lotto;

import camp.nextstep.edu.missionutils.Console;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
LottoMachine lottoMachine = new LottoMachine();
OutputView outputView = new OutputView();
LottoResult lottoResult = new LottoResult();

List<Lotto> lottos;
int money;
while (true) {
try {
System.out.println("구입금액을 입력해 주세요.");
String inputMoney = Console.readLine();
money = parseNumber(inputMoney);

lottos = lottoMachine.purchaseLottos(money);

outputView.printPurchaseCount(lottos.size());
outputView.printLottos(lottos);
break;
} catch (IllegalArgumentException e) {
outputView.printError(e.getMessage());
}
}

Lotto winningLotto;
while (true) {
try {
System.out.println("\n당첨 번호를 입력해 주세요.");
String inputWinningNumbers = Console.readLine();
List<Integer> numbers = parseWinningNumbers(inputWinningNumbers);
winningLotto = new Lotto(numbers);
break;
} catch (IllegalArgumentException e) {
outputView.printError(e.getMessage());
}
}

int bonusNumber;
while (true) {
try {
System.out.println("\n보너스 번호를 입력해 주세요.");
String inputBonusNumber = Console.readLine();
bonusNumber = parseNumber(inputBonusNumber);
validateBonusNumber(winningLotto, bonusNumber);
break;
} catch (IllegalArgumentException e) {
outputView.printError(e.getMessage());
}
}

for (Lotto lotto : lottos) {
int matchCount = calculateMatchCount(lotto, winningLotto);
boolean bonusMatch = lotto.getNumbers().contains(bonusNumber);

Rank rank = Rank.determineRank(matchCount, bonusMatch);
lottoResult.addResult(rank);
}

outputView.printStatistics(lottoResult);
double rateOfReturn = lottoResult.calculateRateOfReturn(money);
outputView.printRateOfReturn(rateOfReturn);
}

private static List<Integer> parseWinningNumbers(String input) {
try {
return Stream.of(input.split(","))
.map(String::trim)
.map(Integer::parseInt)
.collect(Collectors.toList());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("[ERROR] 당첨 번호는 숫자여야 합니다.");
}
}

private static int parseNumber(String input) {
try {
return Integer.parseInt(input.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("[ERROR] 숫자를 입력해야 합니다.");
}
}

private static void validateBonusNumber(Lotto winningLotto, int bonusNumber) {
if (bonusNumber < 1 || bonusNumber > 45) {
throw new IllegalArgumentException("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다.");
}
if (winningLotto.getNumbers().contains(bonusNumber)) {
throw new IllegalArgumentException("[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다.");
}
}

private static int calculateMatchCount(Lotto userLotto, Lotto winningLotto) {
return (int) userLotto.getNumbers().stream()
.filter(winningLotto.getNumbers()::contains)
.count();
}
}
}
27 changes: 23 additions & 4 deletions src/main/java/lotto/Lotto.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
package lotto;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Lotto {
private static final int LOTTO_SIZE = 6;
private static final int MIN_NUMBER = 1;
private static final int MAX_NUMBER = 45;

private final List<Integer> numbers;

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

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

Set<Integer> unique = new HashSet<>(numbers);
if (unique.size() != LOTTO_SIZE) {
throw new IllegalArgumentException("중복된 숫자는 허용되지 않습니다");
}

boolean outOfRange = numbers.stream()
.anyMatch(n -> n < MIN_NUMBER || n > MAX_NUMBER);
if (outOfRange) {
throw new IllegalArgumentException("로또 번호는 1부터 45 사이여야 합니다");
}
}

// TODO: 추가 기능 구현
public List<Integer> getNumbers() {
return numbers;
}
}
38 changes: 38 additions & 0 deletions src/main/java/lotto/LottoMachine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package lotto;

import camp.nextstep.edu.missionutils.Randoms;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class LottoMachine {
public static final int LOTTO_PRICE = 1000;
public static final int LOTTO_MAX_PRICE = 100000;

public List<Lotto> purchaseLottos(int purchaseAmount) {
validatePurchaseAmount(purchaseAmount);
int numberOfLottos = purchaseAmount / LOTTO_PRICE;

List<Lotto> lottos = new ArrayList<>();
for (int i = 0; i < numberOfLottos; i++) {
lottos.add(createLotto());
}
return lottos;
}

private Lotto createLotto() {
List<Integer> numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6);

List<Integer> sortedNumbers = new ArrayList<>(numbers);
Collections.sort(sortedNumbers);

return new Lotto(sortedNumbers);
}

private void validatePurchaseAmount(int purchaseAmount) {
if (purchaseAmount <= 0 || purchaseAmount % LOTTO_PRICE != 0)
throw new IllegalArgumentException("[ERROR] 구입 금액은 1,000원 단위여야 합니다.");
if (purchaseAmount > LOTTO_MAX_PRICE)
throw new IllegalArgumentException("[ERROR] 1회 구매 금액은 10만 원을 초과할 수 없습니다.");
}
}
Loading