Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
16d099f
docs: 구현 사항 리스트 업
lepitaaar Nov 3, 2025
6d5ffe0
feat: 로또 번호가 중복일때 예외 발생
lepitaaar Nov 3, 2025
f784fad
feat: 랜덤 번호인 로또를 구매할 수 있다
lepitaaar Nov 3, 2025
758e4ef
feat: 사용자는 돈을 가진다
lepitaaar Nov 3, 2025
db6601c
test: 돈 테스트 코드 작성
lepitaaar Nov 3, 2025
ca5af59
test: 로또 테스트 코드 작성
lepitaaar Nov 3, 2025
26f3ba0
feat: 구입 금액으로 부터 구매할 수 있는 모든 로또를 구매한다
lepitaaar Nov 3, 2025
247032c
test: 구매할 돈 만큼 로또를 구매한다
lepitaaar Nov 3, 2025
c737724
feat: Integer 자료형에서 LottoNumber로 변경
lepitaaar Nov 3, 2025
7f636a0
test: LottoTest 자료형 리팩토링
lepitaaar Nov 3, 2025
61797cc
test: equal override 메소드 테스트
lepitaaar Nov 3, 2025
1de6171
feat: 같은 로또 번호 개수 새기
lepitaaar Nov 3, 2025
b77733d
fix: 중복된 숫자 존재시 오류 발생
lepitaaar Nov 3, 2025
d4917a7
feat: 겹치는 숫자를 구할 수 있다.
lepitaaar Nov 3, 2025
173f7cc
test: 겹치는 숫자를 구한다 테스트 코드 작성
lepitaaar Nov 3, 2025
891d94b
feat: 로또 당첨 금액 구현
lepitaaar Nov 3, 2025
e1403a0
feat: 수익률을 구할 수 있다
lepitaaar Nov 3, 2025
a946eeb
test: 수익률이 올바르게 나오는지 테스트 코드 작성
lepitaaar Nov 3, 2025
b8f0015
feat: 숫자 범위 검증 추가
lepitaaar Nov 3, 2025
02c241f
feat: 구매할 금액이 천원 단위로 끝나는지 검증
lepitaaar Nov 3, 2025
a50a04e
feat: 입력 뷰 구현
lepitaaar Nov 3, 2025
098c7b9
refactor: LottoNumber Number 상속 제거
lepitaaar Nov 3, 2025
d9a35df
refactor: 리워드 전략을 ENUM으로 리팩토링
lepitaaar Nov 3, 2025
ed8548e
refactor: Integer 자료형 long으로 변경
lepitaaar Nov 3, 2025
b25d2fa
feat: 출력 view 구현
lepitaaar Nov 3, 2025
14efcab
feat: controller 구현
lepitaaar Nov 3, 2025
f5cfe87
refactor: 매직넘버 분리
lepitaaar Nov 3, 2025
534e379
test: 테스트 로직 분리
lepitaaar Nov 3, 2025
0528c77
fix: 수익률 계산공식 수정
lepitaaar Nov 3, 2025
b115684
fix: 양수 검증 로직 추가
lepitaaar 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
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,66 @@
# java-lotto-precourse

간단한 로또 발매기를 구현한다.

- [ ] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
- [ ] 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
- [ ] 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다.
```
1등: 6개 번호 일치 / 2,000,000,000원
2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
3등: 5개 번호 일치 / 1,500,000원
4등: 4개 번호 일치 / 50,000원
5등: 3개 번호 일치 / 5,000원
```
- [ ] 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
- [ ] 로또 1장의 가격은 1,000원이다.
- [ ] 당첨 번호와 보너스 번호를 입력받는다.
- [ ] 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
- 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다.
- 로또 번호의 숫자 범위는 1~45까지이다.
- 당첨 번호의 개수는 6개이다.
- 로또 번호는 중복되지 않는다.
- Exception이 아닌 IllegalArgumentException, IllegalStateException 등과 같은 명확한 유형을 처리한다.
- 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 "[ERROR]"로 시작해야 한다.
- ex) [ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.

# View (입출력 요구 사항)

## 입력
로또 구입 금액을 입력 받는다. 구입 금액은 1,000원 단위로 입력 받는다.

``14000``

당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다.

``1,2,3,4,5,6``

보너스 번호를 입력 받는다.

``7``

## 출력
발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다.
```
8개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[1, 3, 5, 14, 22, 45]
```

```
당첨 내역을 출력한다.
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%입니다.
```
수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%)
79 changes: 77 additions & 2 deletions src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,82 @@
package lotto;

import lotto.reward.RewardCondition;
import lotto.view.InputView;
import lotto.view.OutputView;

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

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
Money purchaseAmount = readPurchaseAmountWithRetry();
User user = new User(purchaseAmount.getMoney());
OutputView.printPurchasedLottos(user.getBoughtLotto());

Lotto winningLotto = readWinningLottoWithRetry();
readBonusNumberWithRetry(winningLotto);

Map<RewardCondition, Integer> result = calculateResults(user.getBoughtLotto(), winningLotto);

long totalWinnings = 0;
for (Map.Entry<RewardCondition, Integer> entry : result.entrySet()) {
if (entry.getKey() != RewardCondition.MISS) {
totalWinnings += entry.getKey().getPrize() * entry.getValue();
}
}

Money userMoney = user.getMoney();
userMoney.addMoney(new Money(totalWinnings));

double roi = userMoney.getRateOfReturn();

OutputView.printStatistics(result, roi);
}

private static Money readPurchaseAmountWithRetry() {
while (true) {
try {
return new Money(InputView.readPurchaseAmount());
} catch (IllegalArgumentException e) {
System.out.println("[ERROR] " + e.getMessage());
}
}
}

private static Lotto readWinningLottoWithRetry() {
while (true) {
try {
return new Lotto(InputView.readWinningNumbers());
} catch (IllegalArgumentException e) {
System.out.println("[ERROR] " + e.getMessage());
}
}
}

private static void readBonusNumberWithRetry(Lotto winningLotto) {
while (true) {
try {
winningLotto.addLottoNumber(new LottoNumber(LottoNumberType.BONUS_NUMBER, InputView.readBonusNumber()));
return;
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}

private static Map<RewardCondition, Integer> calculateResults(List<Lotto> purchasedLottos, Lotto winningLotto) {
Map<RewardCondition, Integer> result = new EnumMap<>(RewardCondition.class);
for (RewardCondition condition : RewardCondition.values()) {
result.put(condition, 0);
}

for (Lotto lotto : purchasedLottos) {
int matchCount = lotto.getSameOrdinaryLottoNumberCount(winningLotto.getNumbers());
boolean hasBonus = lotto.getSameBonusLottoNumberCount(winningLotto.getNumbers()) >= 1;
RewardCondition reward = RewardCondition.valueOf(matchCount, hasBonus);
result.put(reward, result.get(reward) + 1);
}
return result;
}
}
}
70 changes: 64 additions & 6 deletions src/main/java/lotto/Lotto.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,78 @@
package lotto;

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

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

public Lotto(List<Integer> numbers) {
validate(numbers);
this.numbers = numbers;
List<LottoNumber> mapNumbers = new ArrayList<>(numbers.stream().map(LottoNumber::new).toList());
validate(mapNumbers);
this.numbers = mapNumbers;
}

private void validate(List<Integer> numbers) {
private void validate(List<LottoNumber> numbers) {
validateNumbersSize(numbers);
validateDuplicationNumbers(numbers);
}

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

// TODO: 추가 기능 구현
private void validateDuplicationNumbers(List<LottoNumber> numbers) {
List<LottoNumber> combined = new ArrayList<>(numbers);

if (this.numbers != null) {
combined.addAll(this.numbers);
}

long distinctCount = combined.stream()
.map(LottoNumber::getNumber)
.distinct()
.count();

if (distinctCount != combined.size()) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 중복될 수 없습니다.");
}
}
public List<LottoNumber> getNumbers() {
return this.numbers;
}

public void addLottoNumber(LottoNumber lottoNumber) {
validateDuplicationNumbers(new ArrayList<>(List.of(lottoNumber)));
numbers.add(lottoNumber);
}

public int getSameOrdinaryLottoNumberCount(List<LottoNumber> lottoNumbers) {
List<Integer> numbers = this.numbers.stream()
.map(LottoNumber::getNumber)
.toList();

long sameCount = lottoNumbers.stream()
.map(LottoNumber::getNumber)
.filter(numbers::contains)
.count();

return (int) sameCount;
}

public int getSameBonusLottoNumberCount(List<LottoNumber> lottoNumbers) {
List<Integer> numbers = this.numbers.stream()
.filter(lottoNumber -> lottoNumber.getType() == LottoNumberType.BONUS_NUMBER)
.map(LottoNumber::getNumber)
.toList();

long sameCount = lottoNumbers.stream()
.filter(lottoNumber -> lottoNumber.getType() == LottoNumberType.BONUS_NUMBER)
.map(LottoNumber::getNumber)
.filter(numbers::contains)
.count();

return (int) sameCount;
}
}
48 changes: 48 additions & 0 deletions src/main/java/lotto/LottoNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package lotto;

import java.util.Objects;

public class LottoNumber {
private final LottoNumberType type;
private final Integer number;
private final static int MIN_RANGE_NUMBER = 1;
private final static int MAX_RANGE_NUMBER = 45;

public LottoNumber(Integer number) {
validateNumberRange(number);
this.type = LottoNumberType.ORDINARY_NUMBER;
this.number = number;
}

public LottoNumber(LottoNumberType type, Integer number) {
validateNumberRange(number);
this.type = type;
this.number = number;
}

private void validateNumberRange(int number) {
if (number < MIN_RANGE_NUMBER || number > MAX_RANGE_NUMBER) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}
}

public Integer getNumber() {
return number;
}

public LottoNumberType getType() {
return type;
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof LottoNumber lottoNumber)) return false;
return Objects.equals(this.number, lottoNumber.number);
}

@Override
public String toString() {
return this.number.toString();
}
}
6 changes: 6 additions & 0 deletions src/main/java/lotto/LottoNumberType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package lotto;

public enum LottoNumberType {
ORDINARY_NUMBER,
BONUS_NUMBER;
}
38 changes: 38 additions & 0 deletions src/main/java/lotto/Money.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package lotto;

public class Money {
private final Long initMoney;
private Long currentMoney;
static long DIVIDE_STANDARD = 1_000L;

public Money(long initMoney) {
validatePositiveInteger(initMoney);
if (initMoney % DIVIDE_STANDARD != 0) {
throw new IllegalArgumentException("[ERROR] 구입 금액은 1,000원 단위여야 합니다.");
}
this.initMoney = initMoney;
this.currentMoney = initMoney;
}

private void validatePositiveInteger(long initMoney) {
if (initMoney <= 0) throw new IllegalArgumentException("[ERROR] 돈은 0이하가 될 수 없습니다.");
}

public int getLottoBuyAvailableAmount() {
return (int) (initMoney / DIVIDE_STANDARD);
}

public void addMoney(Money money) {
currentMoney += money.getMoney();
}

public Long getMoney() {
return currentMoney;
}

public Double getRateOfReturn() {
if (currentMoney == 0) return 0.0;
double roi = (double) (currentMoney - initMoney) / initMoney * 100;
return Math.round(roi * 10.0) / 10.0;
}
}
31 changes: 31 additions & 0 deletions src/main/java/lotto/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package lotto;

import camp.nextstep.edu.missionutils.Randoms;

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

public class User {
private final List<Lotto> boughtLotto = new ArrayList<>();
private final Money money;

public User(long initMoney) {
this.money = new Money(initMoney);
this.buyLottoReceipt(this.money.getLottoBuyAvailableAmount());
}

private void buyLottoReceipt(int amount) {
for (int i = 0; i < amount; i++) {
List<Integer> randomNumbers = Randoms.pickUniqueNumbersInRange(1, 45, 6);
boughtLotto.add(new Lotto(randomNumbers));
}
}

public List<Lotto> getBoughtLotto() {
return this.boughtLotto;
}

public Money getMoney() {
return money;
}
}
Loading