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

[로또] 강이현 미션 제출 합니다. #1368

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 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
64 changes: 63 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,63 @@
# java-lotto-precourse
# 로또 발매기
# 프로젝트 개요

사용자에게 로또를 발급하는 로또 발매기를 구현했습니다. 로또는 개당 1,000원으로, 사용자가 입력한 금액만큼 로또를 발급합니다. 이후 사용자가 구입한 로또의 당첨 통계를 계산하여 결과를 제공합니다.

# 주요 기능

- 로또 번호의 숫자 범위는 1~45까지이다.
- 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]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
- `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다.

# 요구사항 정의서

| 단위 | 기능 | 세부사항 | 구현여부 |
| --- | --- | --- | --- |
| 로또 번호 검증기 | 숫자의 범위를 검증한다 | 양의 정수 1 이상 45 이하이다 | |
| | 중복 검사를 한다 | | |
| | 로또 번호 6개를 검증한다 | 범위 및 중복 검사를 한다 | |
| | 보너스 번호 1개를 검증한다 | 범위 검사를 한다 | |
| 로또 | 번호를 6개 가지고 있다 | 로또 번호를 검증한다 | ✅ DEFAULT |
| 로또 번호 생성기 | 로또 번호 6개를 생성한다 | 로또 번호를 검증한다 | |
| 로또 영수증 | 구매한 로또 개수가 표시된다 | | |
| | 각 로또의 번호가 표시된다 | 로또 번호는 오름차순으로 정렬하여 보여준다 | |
| 당첨 번호 | 당첨 번호를 가지고 있다 | 로또 번호를 검증한다 | |
| | 보너스 번호를 가지고 있다 | 보너스 번호를 검증한다 | |
| 로또 당첨 계산기 | 몇 개의 번호가 일치하는지 계산한다 | 일치하는 갯수가 3개 이하이면 0을 반환한다 | |
| | 수익률을 계산한다 | 소수점 둘째 자리에서 반올림한다 | |
| 입력값 | 로또 구입 금액을 입력 받는다 | 1,000원 단위로 입력한다 | |
| | | 최소 금액은 1,000원이다 | |
| | 당첨 번호를 입력 받는다 | 쉼표를 기준으로 구분한다 | |
| | | 로또 번호를 검증한다 | |
| | 보너스 번호를 입력 받는다 | 보너스 번호를 검증한다 | |
| 출력값 | 당첨 내역을 보여준다 | | |
| | 수익률을 보여준다 | | |

# 예외 상황

| 단위 | 실패 케이스 | 예외 | 구현여부 |
| --- | --- | --- | --- |
| 로또 | 번호가 6개가 아닌 경우 | `IllegalArgumentException` | |
| 로또 번호 검증기 | 로또 번호가 1 이상 45 이하의 범주를 벗어난 경우 | | |
| | 중복된 숫자가 있는 경우 | | |
| 로또 구입 금액 입력 | 로또 구입 금액이 1,000원 이하인 경우 | | |
| | 숫자가 아닌 값을 입력하는 경우 | | |
| | 무제한으로 살 수 있는가? | | |
| 당첨 번호 입력 | 당첨 번호가 6개가 아닌 경우 | | |
| | 당첨 번호 입력 시 쉼표가 아닌 구분자를 사용하는 경우 | | |
| | 번호가 1 이상 45 이하의 범주를 벗어난 경우 | | |
| | 중복된 숫자가 있는 경우 | | |
| 보너스 번호 입력 | 번호가 1개가 아닌 경우 | | |
| | 번호가 1 이상 45 이하의 범주를 벗어난 경우 | | |
14 changes: 13 additions & 1 deletion src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package lotto;

import java.util.List;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
InputView.requestAmount();

int totalAmount = InputView.getAmount();
List<Lotto> lotteries = LottoGenerator.generate(totalAmount);

OutputView.printReceipt(new LottoReceipt(lotteries));

InputView.requestWinningNumber();
WinningNumber winningNumber = InputView.getWinningNumber();

OutputView.printResult(winningNumber, lotteries, totalAmount);
}
}
64 changes: 64 additions & 0 deletions src/main/java/lotto/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package lotto;

import camp.nextstep.edu.missionutils.Console;
import java.util.ArrayList;
import java.util.List;

public class InputView {

private static final String REQUEST_AMOUNT = "구입금액을 입력해 주세요.";
private static final String REQUEST_WINNING_NUMBER = "당첨 번호를 입력해 주세요.";
private static final String REQUEST_BONUS_NUMBER = "보너스 번호를 입력해 주세요.";

private static int amount;
private static WinningNumber winningNumber;

public static String readLine() {
return Console.readLine();
}

public static void requestAmount() {
while (true) {
try {
System.out.println(REQUEST_AMOUNT);
String inputAmount = readLine();
amount = Integer.parseInt(inputAmount);
break;
} catch (IllegalArgumentException e) {
System.out.println("[ERROR] 금액에는 숫자만 입력해야 해요.");
}
}
}

public static void requestWinningNumber() {
System.out.println(REQUEST_WINNING_NUMBER);
String inputWinningNumber = readLine();
String[] inputNumbers = inputWinningNumber.split(",");

try {
List<Integer> numbers = new ArrayList<>();
for (String inputNumber : inputNumbers) {
int number = Integer.parseInt(inputNumber);
numbers.add(number);
}

System.out.println();
System.out.println(REQUEST_BONUS_NUMBER);
String inputBonusNumber = readLine();
int bonusNumber = Integer.parseInt(inputBonusNumber);

winningNumber = new WinningNumber(new Lotto(numbers), bonusNumber);

} catch (NumberFormatException e) {
throw new IllegalArgumentException("[ERROR] 숫자 형식이 아닙니다.");
}
}

public static int getAmount() {
return amount;
}

public static WinningNumber getWinningNumber() {
return winningNumber;
}
}
23 changes: 20 additions & 3 deletions src/main/java/lotto/Lotto.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,27 @@ public Lotto(List<Integer> numbers) {
}

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

public boolean hasSameNumber(int bonusNumber) {
return numbers.contains(bonusNumber);
}

public int findMatchCount(Lotto userLotto) {
int count = 0;
for (Integer number : userLotto.numbers) {
if (this.numbers.contains(number)) {
count++;
}

}

return count;
}

// TODO: 추가 기능 구현
@Override
public String toString() {
return numbers.toString();
}
}
27 changes: 27 additions & 0 deletions src/main/java/lotto/LottoGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package lotto;

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

public class LottoGenerator {

public static List<Lotto> generate(int totalAmount) {
if (totalAmount < 1000) {
throw new IllegalArgumentException("[ERROR] 구입 금액은 1,000원 이상이어야 해요.");
}

List<Lotto> result = new ArrayList<>();
int quantity = totalAmount / 1000;

while (quantity-- > 0) {
List<Integer> numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6);
List<Integer> mutableNumbers = new ArrayList<>(numbers);
Collections.sort(mutableNumbers);
result.add(new Lotto(mutableNumbers));
}

return result;
}
}
52 changes: 52 additions & 0 deletions src/main/java/lotto/LottoNumberValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package lotto;

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

public class LottoNumberValidator {

private static final int MINIMUM = 1;
private static final int MAXIMUM = 45;
private static final int LOTTO_NUMBERS_REQUIRED = 6;

public static void validateDuplicateNumber(List<Integer> numbers) {
Set<Integer> checker = new HashSet<>();
numbers
.forEach(number -> {
if (!checker.add(number)) {
throw new IllegalArgumentException("[ERROR] 중복된 번호가 있어요.");
}
});
}

public static void validateRange(int number) {
if (number < MINIMUM || number > MAXIMUM) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 1에서 45까지의 숫자로만 이루어져야해요.");
}
}

public static void validateRange(List<Integer> numbers) {
numbers.forEach(LottoNumberValidator::validateRange);
}

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

public static void validateNumbers(List<Integer> numbers) {
validateNumbersRequired(numbers);
validateDuplicateNumber(numbers);
validateRange(numbers);
}

public static void validateBonusNumber(Lotto lotto, int bonusNumber) {
validateRange(bonusNumber);
if (lotto.hasSameNumber(bonusNumber)) {
throw new IllegalArgumentException("[ERROR] 보너스 번호는 로또 번호와 중복될 수 없어요.");
}

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

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

public class LottoReceipt {

private int quantity;
private List<Lotto> lotteries;


public LottoReceipt(List<Lotto> lotteries) {
this.lotteries = new ArrayList<>(lotteries);
quantity = this.lotteries.size();
}

public int getQuantity() {
return quantity;
}

public List<Lotto> getLotteries() {
return lotteries;
}
}
56 changes: 56 additions & 0 deletions src/main/java/lotto/LottoResultCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package lotto;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class LottoResultCalculator {

private final static int BONUS_NUMBER_MATCH_COUNT = 5;
private final static int FIRST_PLACE_PRIZE = 2000000000;
private final static int SECOND_PLACE_PRIZE = 30000000;
private final static int THIRD_PLACE_PRIZE = 1500000;
private final static int FORTH_PLACE_PRIZE = 50000;
private final static int FIFTH_PLACE_PRIZE = 5000;

private WinningNumber winningNumber;


public LottoResultCalculator(WinningNumber winningNumber) {
this.winningNumber = winningNumber;
}

public Rank calculateRanking(Lotto userLotto) {
int matchCount = winningNumber.findMatchCount(userLotto);
boolean matchedBonusNumber = matchCount == BONUS_NUMBER_MATCH_COUNT && winningNumber.hasMatchingBonusNumberWith(userLotto);

return Rank.findRankByMatchCount(matchCount, matchedBonusNumber);
}

public Map<Rank, Integer> calculateRanking(List<Lotto> lotteries) {
Map<Rank, Integer> statistics = new HashMap<>();
lotteries.forEach(lotto -> {
Rank rank = calculateRanking(lotto);
statistics.put(rank, statistics.getOrDefault(rank, 0) + 1);
});

return statistics;
}

public double calculateRateOfReturn(Map<Rank, Integer> results, int totalAmount) {
int totalRevenue = 0;

totalRevenue += results.getOrDefault(Rank.FIRST_PLACE, 0) * FIRST_PLACE_PRIZE;
totalRevenue += results.getOrDefault(Rank.SECOND_PLACE, 0) * SECOND_PLACE_PRIZE;
totalRevenue += results.getOrDefault(Rank.THIRD_PLACE, 0) * THIRD_PLACE_PRIZE;
totalRevenue += results.getOrDefault(Rank.FORTH_PLACE, 0) * FORTH_PLACE_PRIZE;
totalRevenue += results.getOrDefault(Rank.FIFTH_PLACE, 0) * FIFTH_PLACE_PRIZE;

if (totalAmount > 0) {
double rateOfReturn = ((double) totalRevenue / totalAmount) * 100;
return Math.round(rateOfReturn * 100.0) / 100.0;
}

return 0;
}
}
38 changes: 38 additions & 0 deletions src/main/java/lotto/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package lotto;

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

public class OutputView {

private final static String PRINT_QUANTITY = "개를 구매했습니다.";
private final static String PRINT_RESULT_STATISTICS = "당첨 통계\n";
private final static String PRINT_LINE = "---\n";

public static void printReceipt(LottoReceipt lottoReceipt) {
System.out.println(lottoReceipt.getQuantity() + PRINT_QUANTITY);
for (Lotto lottery : lottoReceipt.getLotteries()) {
System.out.println(lottery);
}

}

public static void printResult(WinningNumber winningNumber, List<Lotto> lotteries, int totalAmount) {
LottoResultCalculator lottoResultCalculator = new LottoResultCalculator(winningNumber);
Map<Rank, Integer> rankingResult = lottoResultCalculator.calculateRanking(lotteries);
double rateOfReturn = lottoResultCalculator.calculateRateOfReturn(rankingResult, totalAmount);

StringBuilder output = new StringBuilder();
output.append(PRINT_RESULT_STATISTICS);
output.append(PRINT_LINE);

output.append(String.format("3개 일치 (5,000원) - %d개\n", rankingResult.getOrDefault(Rank.FIFTH_PLACE, 0)));
output.append(String.format("4개 일치 (50,000원) - %d개\n", rankingResult.getOrDefault(Rank.FORTH_PLACE, 0)));
output.append(String.format("5개 일치 (1,500,000원) - %d개\n", rankingResult.getOrDefault(Rank.THIRD_PLACE, 0)));
output.append(String.format("5개 일치, 보너스 볼 일치 (30,000,000원) - %d개\n", rankingResult.getOrDefault(Rank.SECOND_PLACE, 0)));
output.append(String.format("6개 일치 (2,000,000,000원) - %d개\n", rankingResult.getOrDefault(Rank.FIRST_PLACE, 0)));

System.out.println(output);
System.out.println("총 수익률은 " + rateOfReturn + "%입니다.");
}
}
Loading