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

[로또] 김준형 미션 제출합니다. #2

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7b124f9
docs(readme): 기능 목록 정리
Sep 10, 2024
7f62de5
feat(input-view,lotto-game): 로또 구입 금액 입력 받는 문구 출력
Sep 10, 2024
cdc32e0
feat(dto,validator): 로또 구입 금액 입력 받기
Sep 10, 2024
a4f0fe4
refactor(constant,controller): constant 리팩토링, controller 반복문 추가
Sep 10, 2024
464b629
feat(lotto-game-controller): 발행한 로또 수량 출력
Sep 11, 2024
6af8026
feat(lotto, lottos, output-view): 랜덤한 로또 번호 생성
Sep 11, 2024
39c81a6
feat(lotto-game-controller,input-view): 로또 당첨 번호 입력 받는 문구
Sep 11, 2024
eff5166
feat(controller, lotto): 로또 당첨 번호 입력 받기
Sep 11, 2024
5b15a01
feat(controller, input-view): 보너스 번호 입력 받는 문구 출력
Sep 11, 2024
d85ea88
feat(bonus-number,winnig-lotto): 보너스 번호 입력 받기
Sep 11, 2024
d6983fd
feat(controller, output-view): 당첨 통계 문구 출력
Sep 11, 2024
53f08af
feat(controller, rank, match-number-service): 당첨 통계 출력
Sep 13, 2024
f6823c8
refactor(controller, result-dto): dto 구성 및 controller 로직 리팩토링
Sep 13, 2024
7e0e396
feat(controller, rank, output-view): 수익률 출력
Sep 13, 2024
a7d120e
refactor(view): viewMessage 상수처리
Sep 13, 2024
1fbf18a
fix(random-number-generator): immutableList -> muttableList
Sep 13, 2024
4b6ca76
refactor(controller, random-lotto-dto): 랜덤 로또 출력 dto 로직 분기
Sep 13, 2024
6acc91b
refactor(result-service): result-service 로직 분기
Sep 13, 2024
7845f28
refactor(controller): 중복된 로직 합치기
Sep 13, 2024
4193acb
refactor: 역할의 책임에 맞게 분배 및 구조 변경
Sep 13, 2024
1321b7e
refactor(controller): 리팩토링- 필드 선언 값 줄이기
Sep 13, 2024
86af535
test(domain): domain 단위 테스트 코드 작성
Sep 13, 2024
8584553
test(validator): validator 단위 테스트 코드 작성
Sep 13, 2024
450c45c
refactor(controller): Map -> EnumMap 변경
Sep 13, 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
98 changes: 98 additions & 0 deletions docs/README.md
digitpic marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# 기능 목록 정리
## 1. 로또 구입 입력
- [x] 로또 구입 금액을 입력받는 문구 출력
- `"구입금액을 입력해 주세요."` 문구 출력


- [x] 로또 구입 금액을 입력 받는다.
- 예외 처리
- `정수`를 입력받아야한다.
- 최소금액 : `1,000원`
- `1,000원 단위`로 떨어져야한다.
---
## 2. 로또 수량 및 번호 출력
- [x] 발행한 로또 수량을 출력한다.
- `"n개를 구매했습니다."` 형식으로 출력
- 금액을 `로또 수량`으로 변환하여 출력한다.


- [x] 로또 수량만큼 로또 번호를 출력한다.
- 예시 형식 : `[8, 21, 23, 41, 42, 43]`
- 로또 번호는 `오름차순`으로 정렬하여 보여준다.
- 로또 번호는 `1~45`까지이다.
- `중복되지 않는` 6개의 숫자를 뽑는다.
- 컴퓨터가 로또 수량만큼 `랜덤 로또를 생성`한다.
---
## 3. 당첨 번호 입력
- [x] 당첨 번호를 입력하라는 문구 출력
- `"당첨 번호를 입력해 주세요."` 문구 출력


- [x] 당첨 번호를 입력 받는다.
- 예시 형식 : `1,2,3,4,5,6`
- 번호는 `쉼표(,)`를 기준으로 구분한다.
- 예외 처리
- `숫자`로 입력 받아야 한다.
- `6개`의 숫자를 입력 받아야 한다.
- `중복`이 있어서는 안된다
- 숫자의 범위는 `1~45` 사이여야 한다.
- `","`가 아닌 다른 기호가 있으면 안된다.
---
## 4. 보너스 번호 입력
- [x] 보너스 번호를 입력하라는 문구 출력
- `보너스 번호를 입력해 주세요.` 문구 출력


- [x] 보너스 번호를 입력 받는다.
- 예시 형식 : `7`
- 예외 처리
- `숫자`로 입력 받아야 한다.
- 당첨번호와 `중복`이 있어서는 안된다
- 숫자의 범위는 `1~45` 사이여야 한다.
---
## 5. 최종 결과
- [x] 당첨 통계 문구를 출력한다.
- `"당첨 통계"`
- `"---"`


- [x] 최종 결과를 계산하고 출력한다.
- 에시 형식 :
```
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%입니다.
```
- 컴퓨터가 생성한 `랜덤 로또`와 사용자가 입력한 `로또`의 `일치하는 숫자 갯수`를 구한다
- `보너스 번호`가 포함되었는지 여부를 구한다.
- `일치하는 갯수`와 `보너스 포함` 조건에 일치하는 사용자의 `등수`를 확인한다
- 사용자가 당첨된 `등수`의 총 갯수를 구한다.
- `수익률`을 계산한다
- ( 총 당첨된 금액 / 로또 구입 금액 ) * 100
- 수익률은 `소수점 둘째 자리`에서 `반올림`한다
- 에시 형식대로 출력하고 로또 게임 종료
---
# 예외 처리
- [x] 예외 상황시 에러 문구를 출력해야한다.
- "[ERROR] ~"로 시작하며 문구 출력


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


- [x] Validator은 Model에서 진행한다.

---
# 테스트 코드
- [x] `JUnit 5`와 `AssertJ`를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.


- [x] `도메인 로직`에 `단위 테스트`를 구현해야 한다.
- 단, `UI(System.out, System.in, Scanner)` 로직은 제외한다.
- `핵심 로직을 구현하는 코드`와 `UI`를 담당하는 `로직을 분리`해 구현한다.
- 단위 테스트 작성이 익숙하지 않다면 test/java/lotto/LottoTest를 참고하여 학습한 후 테스트를 구현한다.
5 changes: 4 additions & 1 deletion src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package lotto;

import lotto.controller.LottoGameController;

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

This file was deleted.

24 changes: 24 additions & 0 deletions src/main/java/lotto/constant/LottoConfig.java
digitpic marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package lotto.constant;

public enum LottoConfig {
PRICE_UNIT(1000),
PRICE_MIN(1000),
digitpic marked this conversation as resolved.
Show resolved Hide resolved
LOTTO_COUNT(6),
RANGE_MIN(1),
RANGE_MAX(45);

private final int value;

LottoConfig(int value) {
this.value = value;
}

public int getValue() {
return value;
}

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

public enum ViewMessage {

// Input Messages
PRICE_REQUEST_MESSAGE("구입금액을 입력해 주세요."),
WINNING_LOTTO_REQUEST_MESSAGE("당첨 번호를 입력해 주세요."),
BONUS_LOTTO_REQUEST_MESSAGE("보너스 번호를 입력해 주세요."),

// Output Messages
LOTTO_COUNT_NOTICE("개를 구매했습니다."),
WINNING_STATISTICS("당첨 통계"),
SEPARATOR("---"),
TOTAL_RATE_FORMAT("총 수익률은 %.1f%%입니다.");

private final String message;

ViewMessage(String message) {
this.message = message;
}

@Override
public String toString() {
return message; // Enum 값이 호출될 때 문자열을 반환
}
}
9 changes: 9 additions & 0 deletions src/main/java/lotto/constant/exception/LottoException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package lotto.constant.exception;

import lotto.constant.exception.error.ErrorMessage;

public class LottoException extends IllegalArgumentException {
public LottoException(ErrorMessage errorMessage) {
super(errorMessage.getMessage());
}
}
24 changes: 24 additions & 0 deletions src/main/java/lotto/constant/exception/error/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package lotto.constant.exception.error;

import static lotto.constant.LottoConfig.*;
JHZLO marked this conversation as resolved.
Show resolved Hide resolved

public enum ErrorMessage {
INVALID_PRICE_INTEGER("정수로 된 금액만 입력할 수 있습니다."),
INVALID_PRICE_UNIT(PRICE_UNIT + "원 단위의 금액만 입력할 수 있습니다."),
INVALID_PRICE_MIN(PRICE_MIN + "원 미만의 금액은 입력할 수 없습니다."),
INVALID_REQUIRED_LENGTH("당첨 번호는 " + LOTTO_COUNT + "개를 입력해야 합니다."),
OUT_OF_RANGE(RANGE_MIN + "부터 " + RANGE_MAX + "의 숫자만 입력할 수 있습니다."),
INVALID_DUPLICATION("중복되는 수를 입력할 수 없습니다.");

private static final String PREFIX = "[ERROR] "; // PREFIX는 항상 같은 값을 가짐
JHZLO marked this conversation as resolved.
Show resolved Hide resolved

private final String message;

private ErrorMessage(String message) {
this.message = message;
}

public String getMessage() {
return PREFIX + message;
}
}
83 changes: 83 additions & 0 deletions src/main/java/lotto/controller/LottoGameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package lotto.controller;

import lotto.constant.exception.LottoException;
import lotto.dto.RandomLottoDto;
import lotto.domain.model.*;
JHZLO marked this conversation as resolved.
Show resolved Hide resolved
import lotto.service.ConvertingService;
import lotto.service.ResultService;
import lotto.view.InputView;
import lotto.view.OutputView;

import java.util.*;
JHZLO marked this conversation as resolved.
Show resolved Hide resolved


public class LottoGameController {
private Lotto inputWinningLotto;
private Lottos randomLottos;
private WinningLotto winningLotto;

public void run() {
runUntilNoException(inputBuyLottoRunnable());
runUntilNoException(inputLottoNumberRunnable());
runUntilNoException(inputBonusNumberRunnable());
announceResult();
JHZLO marked this conversation as resolved.
Show resolved Hide resolved
}

private void runUntilNoException(Runnable runnable) {
while (true) {
try {
runnable.run();
break;
} catch (LottoException e) {
OutputView.printResult(e.getMessage()); // 예외 메시지 출력
}
}
}
digitpic marked this conversation as resolved.
Show resolved Hide resolved

private Runnable inputBuyLottoRunnable() { // 로또 구입을 입력한다.
return () -> {
String userPrice = InputView.requestPrice();
Integer lottoCount = ConvertingService.priceToTicket(userPrice);
outputRandomLotto(lottoCount);
};
}

private void outputRandomLotto(int lottoCount) { // 랜덤으로 생성한 로또 번호를 출력한다.
OutputView.printLottoCount(lottoCount); // 로또 개수 출력
randomLottos = Lottos.create(lottoCount); // 랜덤 로또 생성
String outputLotto = RandomLottoDto.lottoToString(randomLottos.getLottos()); // toString
OutputView.printResult(outputLotto);
}

private Runnable inputLottoNumberRunnable() { // 로또 당첨 번호를 입력 받는다
return () -> {
String inputLotto = InputView.requestWinningLotto(); // 로또 당첨 번호 입력 받기
inputWinningLotto = Lotto.create(inputLotto); // 입력된 값을 Lotto 객체로 변환
};
}

private Runnable inputBonusNumberRunnable() { // 보너스 번호를 입력 받는다.
return () -> {
String inputBonus = InputView.requestBonusLotto(); // 보너스 번호 입력 받기
Integer bonus = ConvertingService.stringToInteger(inputBonus);
BonusNumber bonusNumber = new BonusNumber(bonus);
winningLotto = new WinningLotto(inputWinningLotto, bonusNumber);
};
}

private void announceResult() { // 최종 결과 출력
OutputView.printHeaderNotice();
progressStatistics();
}

private void progressStatistics() {
final ResultService resultService = new ResultService();

EnumMap<Rank, Integer> rankStatistics = new EnumMap<>(Rank.class);
rankStatistics.putAll(resultService.progressStatistics(randomLottos.getLottos(), winningLotto));
String totalRankStatus = resultService.calculateTotalRankStatus(rankStatistics);
OutputView.printResult(totalRankStatus);
float profitRate = resultService.calculateProfitRate(randomLottos.size(), rankStatistics);
OutputView.printTotalRate(profitRate);
}
}
19 changes: 19 additions & 0 deletions src/main/java/lotto/domain/RandomNumberGenerator.java
digitpic marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package lotto.domain;

import camp.nextstep.edu.missionutils.Randoms;

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

public class RandomNumberGenerator {
private final static int NUMBER_START = 1;
private final static int NUMBER_END = 45;
private final static int NUMBER_COUNT = 6;
digitpic marked this conversation as resolved.
Show resolved Hide resolved

public static List<Integer> generateNumbers() {
List<Integer> numbers = new ArrayList<>(Randoms.pickUniqueNumbersInRange(NUMBER_START, NUMBER_END, NUMBER_COUNT)); // 변경 가능한 리스트로 변환
JHZLO marked this conversation as resolved.
Show resolved Hide resolved
Collections.sort(numbers); // 정렬
return numbers;
}
}
30 changes: 30 additions & 0 deletions src/main/java/lotto/domain/model/BonusNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package lotto.domain.model;

import lotto.constant.exception.LottoException;

import static lotto.constant.LottoConfig.RANGE_MAX;
import static lotto.constant.LottoConfig.RANGE_MIN;
import static lotto.constant.exception.error.ErrorMessage.OUT_OF_RANGE;

public class BonusNumber {
private final Integer number;

public BonusNumber(int number){
validateRange(number);
this.number = number;
}

private void validateRange(int number){ // 범위를 벗어나는 숫자인지 검증
if(isNotRequiredRange(number)){
throw new LottoException(OUT_OF_RANGE);
}
}

private boolean isNotRequiredRange(int number) {
return number < RANGE_MIN.getValue() || number > RANGE_MAX.getValue();
}
digitpic marked this conversation as resolved.
Show resolved Hide resolved

public int getNumber() {
return number;
}
}
Loading