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

[로또 게임] 이정우 미션 제출합니다. #2099

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
67 changes: 67 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
## 💲로또 게임

---

### **🚀 User Flow**

---

1. 로또 티켓 구매에 사용할 금액 입력
2. 구매 금액에 맞는 개수 만큼 로또 티켓을 생성
3. 당첨 번호와 보너스 번호를 입력
4. 로또 티켓과 사용자 입력 당첨 번호를 비교하여 결과를 계산
5. 각 등수별 일치 번호 수 및 수익을 포함한 당첨 내역 결과 출력
6. 총 수익률 계산

### **🚀 기능 구현**

---

### **입력 기능**
- 로또 티켓 구매 금액을 입력
- 당첨 번호와 보너스 번호를 입력

### **출력 기능**
- 구매한 로또 티켓 수와 각 티켓의 번호를 표시
- 한장당 1부터 45까지 서로 다른 6개의 임의의 수를 생성
- 각 등수별 일치하는 번호의 수와 수익을 보여주는 당첨 결과를 표시
- 투자 수익률을 백분율로 계산하고 표시

### **서비스 기능**
- 구매 금액에 따라 개수 만큼 로또 티켓을 생성
- 사용자의 티켓과 당첨 번호를 비교하여 당첨 확인
- 일치하는 번호에 따라 수익과 수익률을 계산

### **예외 처리 (사용자 입력값 검증)**
- 티켓 구매
- 입력 받은 티켓 구매 금액이 1000원 단위가 아닌 경우
- 당첨 번호
- 당첨 번호가 6개가 아닌 경우
- 보너스 번호가 1개가 아닌 경우
- 당첨 번호에 중복값 존재
- 당첨 번호가 1~45 사이의 값이 아닌 경우
- 보너스 번호가 1~45 사이의 값이 아닌 경우

### **추가 요구 사항**
- 메소드는 15라인을 넘지 않아야
- else와 switch/case 문을 사용하지 않음
- 로또 등수와 결과 관리를 위해 Java Enum을 적용
- 도메인 로직에 단위 테스트를 구현
- 단, UI(System.out, System.in, Scanner) 로직은 제외
- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현

### **클래스 정리**

controller
- LottoController
domain
- Lotto
- LottoNumbers
- LottoAmount
- Ranking
- Results
view
- InputView
- OutputView

Application(컨트롤러 실행)
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: 프로그램 구현
LottoController controller = new LottoController();
controller.play();
}
}
101 changes: 101 additions & 0 deletions src/main/java/lotto/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package lotto;

import camp.nextstep.edu.missionutils.Console;

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

public class InputView {
private static final String PURCHASE_AMOUNT_MESSAGE = "구입금액을 입력해 주세요.";
private static final String WINNING_NUMBERS_MESSAGE = "\n당첨 번호를 입력해 주세요.";
private static final String BONUS_NUMBER_MESSAGE = "\n보너스 번호를 입력해 주세요.";

public static int inputPurchaseAmount() {
while (true) {
System.out.println(PURCHASE_AMOUNT_MESSAGE);
String input = Console.readLine();
try {
int amount = Integer.parseInt(input);
validateAmount(amount);
return amount;
} catch (NumberFormatException e) {
System.out.println("[ERROR] 구입 금액은 숫자여야 합니다.");
// continue the loop for another attempt
}
}
}

public static List<Integer> inputWinningNumbers() {
while (true) {
System.out.println(WINNING_NUMBERS_MESSAGE);
String input = Console.readLine();
try {
List<Integer> winningNumbers = parseToIntList(input);
validateWinningNumbers(winningNumbers);
return winningNumbers;
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
// continue the loop for another attempt
}
}
}

public static int inputBonusNumber() {
while (true) {
System.out.println(BONUS_NUMBER_MESSAGE);
String input = Console.readLine();
try {
int bonusNumber = parseToInt(input);
validateBonusNumber(bonusNumber);
return bonusNumber;
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
// continue the loop for another attempt
}
}
}

private static int parseToInt(String input) {
try {
return Integer.parseInt(input.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("[ERROR] 숫자를 올바르게 입력해 주세요.");
}
}

private static List<Integer> parseToIntList(String input) {
try {
return Arrays.stream(input.split(","))
.map(String::trim)
.map(Integer::parseInt)
.collect(Collectors.toList());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("[ERROR] 당첨 번호를 올바르게 입력해 주세요.");
}
}

private static void validateAmount(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("[ERROR] 구입 금액은 0원 이상이어야 합니다.");
}
if (amount % 1000 != 0) {
throw new IllegalArgumentException("[ERROR] 구입 금액은 1,000원 단위이어야 합니다.");
}
}

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

private static void validateBonusNumber(int bonusNumber) {
if (bonusNumber < 1 || bonusNumber > 45) {
throw new IllegalArgumentException("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다.");
}
}
}
42 changes: 36 additions & 6 deletions src/main/java/lotto/Lotto.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,50 @@
package lotto;

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

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

public Lotto(List<Integer> numbers) {
validate(numbers);
this.numbers = numbers;
validateTotalCount(numbers);
validateNumbersRange(numbers);
validateUniqueNumbers(numbers);
this.numbers = sortNumbers(numbers);
}

private void validate(List<Integer> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException();
public List<Integer> getNumbers() {
return numbers;
}

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

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

// TODO: 추가 기능 구현
private void validateUniqueNumbers(List<Integer> numbers) {
if (new HashSet<>(numbers).size() != 6) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 중복될 수 없습니다.");
}
}

private List<Integer> sortNumbers(List<Integer> numbers) {
return numbers.stream()
.sorted()
.collect(Collectors.toList());
}

public int countMatch(List<Integer> winningNumbers) {
return (int) numbers.stream()
.filter(winningNumbers::contains)
.count();
}
}
28 changes: 28 additions & 0 deletions src/main/java/lotto/LottoAmount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package lotto;

public class LottoAmount {
public static final int LOTTO_PRICE = 1000;
private final int amount;

public LottoAmount(int amount) {
validatePrice(amount);
validateDivisible(amount);
this.amount = amount;
}

private static void validatePrice(int amount) {
if (amount < LOTTO_PRICE) {
throw new IllegalArgumentException("[ERROR] 구입 금액은 1000원 이상이어야 합니다.");
}
}

private static void validateDivisible(int amount) {
if (amount % LOTTO_PRICE != 0) {
throw new IllegalArgumentException("[ERROR] 구입 금액은 1000원 단위이어야 합니다.");
}
}

public int calculateAmount() {
return amount / LOTTO_PRICE;
}
}
28 changes: 28 additions & 0 deletions src/main/java/lotto/LottoController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package lotto;

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

public class LottoController {

public void play() {
int purchaseAmount = InputView.inputPurchaseAmount();
LottoAmount lottoAmount = new LottoAmount(purchaseAmount);
int lottoCount = lottoAmount.calculateAmount();

List<Lotto> lottos = new ArrayList<>();
for (int i = 0; i < lottoCount; i++) {
List<Integer> numbers = LottoNumbers.setRandomNumbers();
lottos.add(new Lotto(numbers));
}
OutputView.printLottos(lottos);

List<Integer> winningNumbers = InputView.inputWinningNumbers();
int bonusNumber = InputView.inputBonusNumber();
Lotto winningLotto = new Lotto(winningNumbers);

Results results = new Results(lottos, winningLotto, bonusNumber, purchaseAmount);

OutputView.printResults(results);
}
}
22 changes: 22 additions & 0 deletions src/main/java/lotto/LottoNumbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package lotto;

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

public class LottoNumbers {

private static final int COUNT_LOTTO_NUMBERS = 6;
private static final int MIN_LOTTO_NUMBER = 1;
private static final int MAX_LOTTO_NUMBER = 45;

private static List<Integer> lottoNumberList;

public static List<Integer> setRandomNumbers() {
lottoNumberList = Randoms.pickUniqueNumbersInRange(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER, COUNT_LOTTO_NUMBERS);
List<Integer> lottoTicketNumberList = new ArrayList<>(lottoNumberList);
Collections.sort(lottoTicketNumberList);
return lottoTicketNumberList;
}
}
33 changes: 33 additions & 0 deletions src/main/java/lotto/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package lotto;

import java.util.List;
import java.util.stream.Collectors;
public class OutputView {

public static void printResults(Results lottoResult) {
System.out.println("\n당첨 통계");
System.out.println("----");

for (Ranking ranking : Ranking.values()) {
if (ranking != Ranking.NONE) {
int count = lottoResult.getCount(ranking);
System.out.println(ranking.getMessage() + " - " + count + "개");
}
}

System.out.printf("총 수익률은 %.1f%%입니다.", lottoResult.calculateEarningsRate());
}

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

private static String formatLottoNumbers(List<Integer> numbers) {
return "[" + numbers.stream()
.map(String::valueOf)
.collect(Collectors.joining(", ")) + "]";
}
}
Loading