Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[로또] 송재훈 미션 제출합니다. #1341

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
42ffc1f
docs: README 작성
SongJaeHoonn Nov 4, 2024
c313972
refactor: Lotto 도메인 패키지로 이동
SongJaeHoonn Nov 4, 2024
81d4d9f
refactor: TODO 라인 삭제
SongJaeHoonn Nov 4, 2024
8c0afe4
refactor: 로또 구입 금액 입력 view 구현
SongJaeHoonn Nov 4, 2024
4f9ddf7
refactor: 로또 당첨 번호 입력 view 구현
SongJaeHoonn Nov 4, 2024
58b8706
refactor: 보너스 번호 입력 view 구현
SongJaeHoonn Nov 4, 2024
45302f5
refactor: inputView 반환 타입 추가
SongJaeHoonn Nov 4, 2024
3a89f3e
docs: 기능 구현 목록 수정
SongJaeHoonn Nov 4, 2024
fe58d84
docs: 기능 구현 목록 수정
SongJaeHoonn Nov 4, 2024
d8def1f
docs: 기능 구현 목록 수정
SongJaeHoonn Nov 4, 2024
5b71c68
refactor: 입력된 로또 번호가 1부터 45 사이의 숫자인지 검증
SongJaeHoonn Nov 4, 2024
9b29e27
feat: 생성된 로또 번호를 오름차순으로 정렬
SongJaeHoonn Nov 4, 2024
0e4d803
feat: 로또 번호 반환 메서드 추가
SongJaeHoonn Nov 4, 2024
54a1086
feat: 로또 번호 생성 구현
SongJaeHoonn Nov 4, 2024
946a3bc
feat: 로또 구매 로직 구현
SongJaeHoonn Nov 4, 2024
cc9d9b7
feat: 로또 상금 상수화
SongJaeHoonn Nov 4, 2024
ee6e167
feat: 당첨 번호 비교 로직 작성
SongJaeHoonn Nov 4, 2024
557eacb
feat: 출력 로직 작성 완료
SongJaeHoonn Nov 4, 2024
6f961fa
refactor: 불필요한 개행 제거
SongJaeHoonn Nov 4, 2024
8b8f6a5
docs: 기능 구현 목록 수정
SongJaeHoonn Nov 4, 2024
ad2d35a
feat: 수익률 계산 로직 작성
SongJaeHoonn Nov 4, 2024
2e52378
docs: 기능 구현 목록 수정
SongJaeHoonn Nov 4, 2024
287835e
refactor: 패키지 전체 이동
SongJaeHoonn Nov 4, 2024
c39e642
feat: 입력값 파싱 로직 구현
SongJaeHoonn Nov 4, 2024
6988870
docs: 기능 구현 목록 수정
SongJaeHoonn Nov 4, 2024
e0fba18
refactor: 컴포넌트 클래스를 싱글톤으로 변환
SongJaeHoonn Nov 4, 2024
5bdab53
refactor: 컴포넌트 클래스를 싱글톤으로 변환
SongJaeHoonn Nov 4, 2024
8b4713f
refactor: 로또 번호 파싱 로직 수정
SongJaeHoonn Nov 4, 2024
a23a05f
refactor: 보너스 번호 파싱 로직 수정
SongJaeHoonn Nov 4, 2024
4089a62
fix: 로또 번호 검증 로직 수정
SongJaeHoonn Nov 4, 2024
72d893f
fix: 상금 수정
SongJaeHoonn Nov 4, 2024
53b8ab7
refactor: 당첨 통계 로직 수정
SongJaeHoonn Nov 4, 2024
104ade1
feat: run 호출 로직 작성
SongJaeHoonn Nov 4, 2024
e2c4c99
feat: runner 구현
SongJaeHoonn Nov 4, 2024
c252b03
feat: runner 메서드 분리
SongJaeHoonn Nov 4, 2024
a838b33
feat: 로또 천 원 단위 금액 검증 로직 작성
SongJaeHoonn Nov 4, 2024
d5b2a94
refactor: 로또 검증 클래스 분리
SongJaeHoonn Nov 4, 2024
a6e8855
refactor: 보너스 번호 검증 로직 작성
SongJaeHoonn Nov 4, 2024
16458f1
docs: 기능 구현 목록 수정
SongJaeHoonn Nov 4, 2024
329c285
fix: runner 클래스 수정
SongJaeHoonn Nov 4, 2024
882fd43
feat: 로또 번호 중복 검사 로직 구현
SongJaeHoonn Nov 4, 2024
4d703e7
feat: 테스트 로직 작성
SongJaeHoonn Nov 4, 2024
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
105 changes: 105 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,106 @@
# java-lotto-precourse

## 👀 미션 상세

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

로또 번호의 숫자 범위는 1~45까지이다.<br>
1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.<br>
당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.<br>
당첨은 1등부터 5등까지 있다.<br>
당첨 기준과 금액은 아래와 같다.
- 1등: 6개 번호 일치 / 2,000,000,000원
- 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
- 3등: 5개 번호 일치 / 1,500,000원
- 4등: 4개 번호 일치 / 50,000원
- 5등: 3개 번호 일치 / 5,000원

로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.<br>
로또 1장의 가격은 1,000원이다.<br>
당첨 번호와 보너스 번호를 입력받는다.<br>
사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.<br>

사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
Exception이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다.


### 입력
1. 로또 구입 금액을 입력 받는다. 구입 금액은 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다.
```
14000
```
2. 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다.
```
1,2,3,4,5,6
```
3. 보너스 번호를 입력 받는다.
```
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개
```
- 수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%)
```
총 수익률은 62.5%입니다.
```
예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 "[ERROR]"로 시작해야 한다.
```
[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.
```


## 🌈 기능 구현 목록

- [x] **로또 구입 금액 입력 받기**
- [x] 1,000원 단위로 입력된 금액인지 확인
- [x] 입력 금액이 1,000원으로 나누어 떨어지지 않는지 검증

- [x] **로또 번호 생성 및 발행**
- [x] 입력된 금액에 따라 발행할 로또 수량 계산
- [x] 로또 번호 범위(1~45) 내에서 중복되지 않는 6개의 번호 생성
- [x] 생성된 로또 번호를 오름차순으로 정렬
- [x] 발행된 로또 수량과 각각의 로또 번호를 출력

- [x] **당첨 번호와 보너스 번호 입력 받기**
- [x] 당첨 번호 6개를 쉼표(,)로 구분하여 입력 받기
- [x] 입력된 번호들이 1~45 범위 내의 숫자인지 검증
- [x] 보너스 번호가 1~45 범위 내의 숫자인지 검증
- [x] 당첨 번호와 중복되지 않는지 확인
- [x] 입력값이 유효하지 않는지 검증

- [x] **로또 당첨 확인**
- [x] 사용자가 구매한 각 로또 번호와 당첨 번호를 비교
- [x] 당첨 기준에 따라 1등부터 5등까지 당첨 여부와 개수를 판별

- [x] **당첨 내역 출력**
- [x] 각 등수별 일치하는 로또 개수를 출력
- [x] 당첨 금액을 각 등수별로 출력 형식에 맞게 표시

- [x] **수익률 계산**
- [x] 사용자가 구매한 로또 총 금액과 당첨 금액을 비교하여 수익률 계산
- [x] 수익률을 소수점 둘째 자리에서 반올림하여 출력

- [x] **예외 처리**
- [x] 입력 값이 잘못되었을 때 `IllegalArgumentException`을 발생시키고 `[ERROR]` 메시지를 출력
- [x] 잘못된 값이 입력될 경우 해당 부분부터 입력을 다시 받도록 구현
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.runner.Runner;

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

This file was deleted.

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

import camp.nextstep.edu.missionutils.Randoms;
import lotto.domain.Lotto;

import java.util.List;

public class LottoGenerator {

private static LottoGenerator instance;

private LottoGenerator() {

}

public static LottoGenerator getInstance() {
if (instance == null) {
instance = new LottoGenerator();
}
return instance;
}

public Lotto generate() {
List<Integer> LottoNumbers = Randoms.pickUniqueNumbersInRange(1, 45, 6);
return Lotto.of(LottoNumbers);
}
}
55 changes: 55 additions & 0 deletions src/main/java/lotto/application/LottoService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package lotto.application;

import lotto.domain.Lotto;
import lotto.global.LottoPrize;

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

public class LottoService {

private static final int LOTTO_PRICE = 1000;
private static LottoService instance;
private final LottoGenerator lottoGenerator;

private LottoService(LottoGenerator lottoGenerator) {
this.lottoGenerator = lottoGenerator;
}

public static LottoService getInstance(LottoGenerator lottoGenerator) {
if (instance == null) {
instance = new LottoService(lottoGenerator);
}
return instance;
}

public List<Lotto> buyLottos(int price) {
LottoValidator.validatePurchasePrice(price);
List<Lotto> lottos = new ArrayList<>();

for(int i = 0; i < price / LOTTO_PRICE; i++) {
lottos.add(lottoGenerator.generate());
}

return lottos;
}

public LottoPrize calculatePrize(Lotto myLotto, Lotto lottoResult, int bonusNumber) {
LottoValidator.validateBonusNumber(bonusNumber, lottoResult.getNumbers());
long matchLottoCount = myLotto.getNumbers().stream()
.filter(lottoResult.getNumbers()::contains)
.count();

boolean matchBonusNumber = myLotto.getNumbers().contains(bonusNumber);

return LottoPrize.from(matchLottoCount, matchBonusNumber);
}

public Double calculateProfitRate(List<LottoPrize> results, int purchaseQuantity) {
int totalPrize = results.stream()
.map(result -> result.getPrizeMoney().replace(",", ""))
.mapToInt(Integer::parseInt)
.sum();
return (double) totalPrize / purchaseQuantity;
}
}
27 changes: 27 additions & 0 deletions src/main/java/lotto/application/LottoValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package lotto.application;

import java.util.List;

public class LottoValidator {

private static final int LOTTO_PRICE = 1000;
private static final int MIN_NUMBER = 1;
private static final int MAX_NUMBER = 45;


public static void validatePurchasePrice(int price) {
if (price <= 0 || price % LOTTO_PRICE != 0) {
throw new IllegalArgumentException("[ERROR] 금액은 1,000원 단위로 입력해야 합니다.");
}
}

public static void validateBonusNumber(int bonusNumber, List<Integer> winningNumbers) {
if (bonusNumber < MIN_NUMBER || bonusNumber > MAX_NUMBER) {
throw new IllegalArgumentException("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다.");
}

if (winningNumbers.contains(bonusNumber)) {
throw new IllegalArgumentException("[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다.");
}
}
}
51 changes: 51 additions & 0 deletions src/main/java/lotto/domain/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package lotto.domain;

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

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

public Lotto(List<Integer> numbers) {
validateQuantity(numbers);
validateNumberRange(numbers);
validateDuplicate(numbers);
this.numbers = numbers;
sortNumbers(this.numbers);
}

public static Lotto of(List<Integer> numbers) {
return new Lotto(numbers);
}

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

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

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

private void sortNumbers(List<Integer> numbers) {
Collections.sort(numbers);
}

private void validateDuplicate(List<Integer> numbers) {
Set<Integer> uniqueNumbers = new HashSet<>(numbers);
if (uniqueNumbers.size() != numbers.size()) {
throw new IllegalArgumentException("[ERROR] 로또 번호에 중복된 숫자가 있으면 안됩니다.");
}
}
}
45 changes: 45 additions & 0 deletions src/main/java/lotto/global/LottoPrize.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package lotto.global;

public enum LottoPrize {

FIRST(6, false, "2,000,000,000"),
SECOND(5, true, "30,000,000"),
THIRD(5, false, "1,500,000"),
FOURTH(4, false, "50,000"),
FIFTH(3, false, "5,000"),
NONE(0, false, "0");

private final int matchNumberCount;
private final boolean matchBonusNumber;
private final String prizeMoney;

LottoPrize(int matchNumberCount, boolean matchBonusNumber, String prizeMoney) {
this.matchNumberCount = matchNumberCount;
this.matchBonusNumber = matchBonusNumber;
this.prizeMoney = prizeMoney;
}

public int getMatchNumberCount() {
return matchNumberCount;
}

public String getPrizeMoney() {
return prizeMoney;
}

public static LottoPrize from(long matchNumberCount, boolean matchBonusNumber) {
for (LottoPrize prize : values()) {
if (prize.matchNumberCount == matchNumberCount && prize.matchBonusNumber == matchBonusNumber) {
return prize;
}
}
return NONE;
}

public String getMatchDescription() {
if (matchBonusNumber) {
return matchNumberCount + "개 일치, 보너스 볼 일치";
}
return matchNumberCount + "개 일치";
}
}
23 changes: 23 additions & 0 deletions src/main/java/lotto/parser/InputParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package lotto.parser;

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

public class InputParser {

public static int parseLottoPrice(String lottoPrice) {
return Integer.parseInt(lottoPrice);
}

public static List<Integer> parseLottoNumber(String lottoNumber) {
List<String> lottoNumbers = Arrays.asList(lottoNumber.split(","));
return lottoNumbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
}

public static int parseBonusNumber(String bonusNumber) {
return Integer.parseInt(bonusNumber);
}
}
Loading