Skip to content
24 changes: 24 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 기능 목록
+ 각 기능에 대한 예외사항과 핸들링도 기재

- [ ] 게임 관리 - GameManager class
- [ ] 게임 시작 - start()
- [ ] 로또 게임 구현 - LottoGame class
- [ ] 6개의 숫자 생성 **(Computer)** - generateLottoNumber()
- [ ] 로또 구입 금액 입력 받기 **(Me)** - receiveUserMoneyInput()
- [ ] **예외사항** : 1000원으로 나누어 떨어지지 않는 경우 -> `IllegalArgumentException`
- [ ] 당첨 번호 입력 받기 **(Me)** - receiveUserNumberInput()
- [ ] **예외사항** : 쉼표를 기준으로 구분하지 않은 경우 -> `IllegalArgumentException`
- [ ] **예외사항** : 로또 번호의 숫자 범위 밖의 경우 (1 ~ 45) -> `IllegalArgumentException`
- [ ] **예외사항** : 숫자가 중복되는 경우 -> `IllegalArgumentException`
- [ ] 보너스 번호 입력 받기 **(Me)** - receiveUserBonusInput()
- [ ] **예외사항** : 로또 번호의 숫자 범위 밖의 경우 (1 ~ 45) -> `IllegalArgumentException`
- [ ] **예외사항** : 숫자가 중복되는 경우 -> `IllegalArgumentException`
- [ ] 발행한 로또 수량 및 번호를 오름차순으로 출력 - issuanceLotto()
- [ ] 당첨 내역 출력 - printWinningDetails()
- [ ] 수익률 계산 (소수점 둘째 자리 반올림) - calculateRateOfProfit()

-------

* 당첨 번호와 보너스 번호를 입력하는 기능을 분리하는게 좋을까?
* 가독성을 위해 분리하는 방향으로 선택
3 changes: 2 additions & 1 deletion src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

import java.util.List;

public class GameManager {
private final LottoInputView lottoInputView;
private final LottoService lottoService;
private final LottoOutputView lottoOutputView;

public GameManager() {
this.lottoService = new LottoService();
this.lottoOutputView = new LottoOutputView();
this.lottoInputView = new LottoInputView();
}

/**
* 게임 시작
*/
public void start() {
int money = lottoInputView.receiveUserMoneyInput();
List<Lotto> lottoList = lottoService.generateLottoNumber(money / 1000);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

숫자를 바로 하드코딩 하는 방식은 좋지 않은걸로 알고있는데 이 점에 대해선 어떻게 생각하시나용??

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magic number를 사용하는 방법으로 바꿔보겠습니다!

lottoOutputView.printLotto(lottoList);

List<Integer> winningNumbers = lottoInputView.receiveWinningNumberInput();
int bonusNumber = lottoInputView.receiveBonusNumberInput(winningNumbers);

LottoResult result = lottoService.checkWinningResult(lottoList, winningNumbers, bonusNumber);
lottoOutputView.printWinningStatistics(result);

double profitRate = lottoService.calculateProfitRate(money, result);
lottoOutputView.printProfitRate(profitRate);
}
}
14 changes: 13 additions & 1 deletion src/main/java/lotto/Lotto.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,17 @@ private void validate(List<Integer> numbers) {
}
}

// TODO: 추가 기능 구현
/**
*
*/
public boolean contains(int number) {
return numbers.contains(number);
}

/**
*
*/
public int matchCount(List<Integer> winningNumbers) {
return (int) numbers.stream().filter(winningNumbers::contains).count(); //numbers에 있는 int를 loop 돌려서 winningNumbers에 포함되어있다면 개수를 셈
}
}
82 changes: 82 additions & 0 deletions src/main/java/lotto/LottoInputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package lotto;

import camp.nextstep.edu.missionutils.Console;

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

public class LottoInputView {

/**
* 로또 구입 금액 입력 받기
*/
public int receiveUserMoneyInput() {
System.out.println("구입금액을 입력해 주세요.");
String input = Console.readLine();
validateMoneyInput(input);
return Integer.parseInt(input);
}

private void validateMoneyInput(String input) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예외처리를 클래스를 만들어 따로 빼는 방식에 대해 생각해보면 좋을것 같습니다!!

try {
int money = Integer.parseInt(input);
if (money % 1000 != 0) {
throw new IllegalArgumentException("[ERROR] 구입 금액은 1,000원 단위여야 합니다.");
}
} catch (NumberFormatException e) { //숫자가 아닐 경우
throw new IllegalArgumentException("[ERROR] 올바른 숫자를 입력하세요.");
}
}

/**
* 당첨 번호 입력 받기
*/
public List<Integer> receiveWinningNumberInput() {
System.out.println("당첨 번호를 입력해 주세요.");
String input = Console.readLine();
List<Integer> numbers = parseNumbers(input);
validateNumbers(numbers);
return numbers;
}

private void validateNumbers(List<Integer> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException("[ERROR] 당첨 번호는 6개여야 합니다.");
}
if (numbers.stream().distinct().count() != 6) {
throw new IllegalArgumentException("[ERROR] 당첨 번호에 중복된 숫자가 있습니다.");
}
if (numbers.stream().anyMatch(n -> n < 1 || n > 45)) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}
}

/**
* 입력 정수 리스트로 변환
*/
private List<Integer> parseNumbers(String input) {
return Arrays.stream(input.split(","))
.map(String::trim) //스페이스 제거
.map(Integer::parseInt) //정수 변환
.collect(Collectors.toList());
}


public int receiveBonusNumberInput(List<Integer> winningList) {
System.out.println("보너스 번호를 입력해 주세요.");
String input = Console.readLine();
int result = Integer.parseInt(input);
validateBonusNumber(result, winningList);
return result;
}

private void validateBonusNumber(int bonusNumber, List<Integer> winningList) {
if (bonusNumber < 1 || bonusNumber > 45) {
throw new IllegalArgumentException("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다.");
}
if (winningList.contains(bonusNumber)) {
throw new IllegalArgumentException("[ERROR] 당첨 번호에 중복된 숫자가 있습니다.");
}
}
}
29 changes: 29 additions & 0 deletions src/main/java/lotto/LottoOutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package lotto;

import java.util.List;

public class LottoOutputView {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InputView, OutputView가 입출력만을 담당하면 좋을 것 같고,
InputView, OutputView가 다른 class를 의존하는 것보다, 다른 class에서 InputView, OutputView를 의존하면 더 좋을 것 같습니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 class를 의존한다는게 현재 OutputView 코드 내의 printWinningStatisticsprintLotto 에서 Lotto나 LottoResult class를 파라미터로 받고 함수 내에서 사용한다고 이해해도 될까요??

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞습니다!


public void printLotto(List<Lotto> lottoList) {
System.out.println(lottoList.size() + "개를 구매했습니다.");
for(Lotto lotto : lottoList) {
System.out.println(lotto);
}
System.out.println();
}

public void printWinningStatistics(LottoResult result) {
System.out.println("당첨 통계");
System.out.println("---");
for (LottoRank lottoRank : LottoRank.values()) {
if (lottoRank != LottoRank.NONE) {
//println (X) print(X) printf(O)
System.out.printf("%s - %d개\n", lottoRank.getDescription(), result.getCountForRank(lottoRank));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

println이 아닌 printf를 쓰신 이유가 있으실까요??

Copy link
Author

@KimGyeongLock KimGyeongLock Oct 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드에서는 System.out.println(rankDescriptions.get(i) + " - " + rankCounts.get(i) + "개");
이렇게 println을 사용할 수 있겠지만
아래 printProfitRate 함수에서 System.out.printf("총 수익률은 %.1f%%입니다.\n", profitRate);
여기서 printf를 쓴 이유는 printf 는 %.1f% 와 같이 출력 형식을 제어할 수 있습니다.
또한 가독성도 높아지는 것 같아 사용하였습니다.

}
}
}

public void printProfitRate(double profitRate) {
System.out.printf("총 수익률은 %.1f%%입니다.\n", profitRate);
}
}
37 changes: 37 additions & 0 deletions src/main/java/lotto/LottoRank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package lotto;

public enum LottoRank {
FIRST(6, 2_000_000_000, "6개 일치 (2,000,000,000원)"),
SECOND(5, 30_000_000, "5개 일치, 보너스 볼 일치 (30,000,000원)"),
THIRD(5, 1_500_000, "5개 일치 (1,500,000원)"),
FOURTH(4, 50_000, "4개 일치 (50,000원)"),
FIFTH(3, 5_000, "3개 일치 (5,000원)"),
NONE(0, 0, "");

private final int matchCount;
private final int prize;
private final String description;

LottoRank(int matchCount, int prize, String description) {
this.matchCount = matchCount;
this.prize = prize;
this.description = description;
}

public static LottoRank getRank(int matchCount, boolean bonusMatch) {
if (matchCount == 6) return FIRST;
else if (matchCount == 5 && bonusMatch) return SECOND;
else if (matchCount == 5) return THIRD;
else if (matchCount == 4) return FOURTH;
else if (matchCount == 3) return FIFTH;
return NONE;
}

public String getDescription() {
return description;
}

public long getPrize() {
return prize;
}
}
28 changes: 28 additions & 0 deletions src/main/java/lotto/LottoResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package lotto;

import java.util.HashMap;

public class LottoResult {
private final HashMap<LottoRank, Integer> rankCounts;

public LottoResult() {
this.rankCounts = new HashMap<LottoRank, Integer>();
for (LottoRank lottoRank : LottoRank.values()) {
rankCounts.put(lottoRank, 0);
}
}

public void addResult(LottoRank rank) {
rankCounts.put(rank, rankCounts.get(rank) + 1); //현재 랭크 갯수보다 하나 업
}

public int getCountForRank(LottoRank lottoRank) {
return rankCounts.get(lottoRank);
}

public long getTotalPrize() {
return rankCounts.entrySet().stream()
.mapToLong(entry -> entry.getKey().getPrize() * entry.getValue())
.sum();
}
}
36 changes: 36 additions & 0 deletions src/main/java/lotto/LottoService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package lotto;

import camp.nextstep.edu.missionutils.Randoms;

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

public class LottoService {

/**
* 6개의 숫자 생성
*/
public List<Lotto> generateLottoNumber(int count) {
List<Lotto> result = new ArrayList<>();
for(int i = 0; i < count; i++) {
List<Integer> numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6);
result.add(new Lotto(numbers));
}
return result;
}

public LottoResult checkWinningResult(List<Lotto> lottoList, List<Integer> winningNumbers, int bonusNumber) {
LottoResult result = new LottoResult();
for (Lotto lotto : lottoList) {
int matchCount = lotto.matchCount(winningNumbers);
boolean bonusMatch = lotto.contains(bonusNumber);
result.addResult(LottoRank.getRank(matchCount, bonusMatch));
}
return result;
}

public double calculateProfitRate(int money, LottoResult result) {
long totalPrize = result.getTotalPrize();
return (totalPrize * 100.0) / money;
}
}