diff --git a/README.md b/README.md index 5fa2560b46..2bf07edbc3 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ # java-lotto-precourse +# 로또 게임 +이번 프로젝트는 로또 번호를 구매하고 당첨 번호를 입력하여 당첨 통계를 확인하는 간단한 프로그램이다. + +## 구현 할 기능 목록 +1. 로또 구매 기능 +2. 당첨 번호 입력 받기 +3. 보너스 번호 입력 받기 +4. 구매한 로또 번호와 당첨 번호 비교 +5. 당첨 통계 출력 +6. 총 수익률 계산 \ No newline at end of file diff --git a/src/main/java/domain/Lotto.java b/src/main/java/domain/Lotto.java new file mode 100644 index 0000000000..4509f9c6f8 --- /dev/null +++ b/src/main/java/domain/Lotto.java @@ -0,0 +1,42 @@ +package domain; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Lotto { + private final List numbers; + + public Lotto(List numbers) { + validate(numbers); + this.numbers = numbers; + } + + private void validate(List numbers) { + if (numbers.size() != 6) { + throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다."); + } + if (invalidNumber(numbers)) { + throw new IllegalArgumentException("[ERROR] 로또 숫자 범위는 1~45까지입니다."); + } + if (noDuplicates(numbers)) { + throw new IllegalArgumentException("[ERROR] 중복되지 않는 6개의 숫자를 뽑아야합니다."); + } + } + private boolean invalidNumber(List numbers) { + for(int number : numbers) { + if(number < 1 || number > 45) { + return true; + } + } + return false; + } + private boolean noDuplicates(List numbers) { + Set set = new HashSet<>(numbers); + return set.size() != numbers.size(); + } + public List getNumbers() { + return numbers; + } + +} \ No newline at end of file diff --git a/src/main/java/domain/Prize.java b/src/main/java/domain/Prize.java new file mode 100644 index 0000000000..f6f24073e5 --- /dev/null +++ b/src/main/java/domain/Prize.java @@ -0,0 +1,25 @@ +package domain; + +public enum Prize { + FIRST(6, 2000000000), + SECOND(5, 30000000), + THIRD(5, 1500000), + FOURTH(4, 50000), + FIFTH(3, 5000); + + private final int matchCount; + private final int prizeAmount; + + Prize(int matchCount, int prizeAmount) { + this.matchCount = matchCount; + this.prizeAmount = prizeAmount; + } + + public int getMatchCount() { + return matchCount; + } + + public int getPrizeAmount() { + return prizeAmount; + } +} diff --git a/src/main/java/domain/Rank.java b/src/main/java/domain/Rank.java new file mode 100644 index 0000000000..b8225bfd79 --- /dev/null +++ b/src/main/java/domain/Rank.java @@ -0,0 +1,81 @@ +package domain; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Rank { + private static final int LOTTO_PRICE = 1000; + private List purchasedLottos = new ArrayList<>(); + private Map winnings = new HashMap<>(); + + public Rank() { + for (Prize prize : Prize.values()) { + winnings.put(prize, 0); + } + } + + public List purchaseLotto(int amount) { + int numberOfLottos = amount / LOTTO_PRICE; + for (int i = 0; i < numberOfLottos; i++) { + List numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6); + purchasedLottos.add(new Lotto(numbers)); + } + return purchasedLottos; + } + + public void checkWinning(List purchasedLottos, List winningNumbers, int bonusNumber) { + for (Lotto lotto : purchasedLottos) { + int matchCount = countMatches(lotto.getNumbers(), winningNumbers); + boolean bonusMatched = lotto.getNumbers().contains(bonusNumber); + updateWinnings(matchCount, bonusMatched); + } + } + + private int countMatches(List lottoNumbers, List winningNumbers) { + int count = 0; + for (int number : lottoNumbers) { + if (winningNumbers.contains(number)) { + count++; + } + } + return count; + } + + private void updateWinnings(int matchCount, boolean bonusMatched) { + if (matchCount == Prize.FIRST.getMatchCount()) { + winnings.put(Prize.FIRST, winnings.get(Prize.FIRST) + 1); + return; + } + if (matchCount == Prize.SECOND.getMatchCount() && bonusMatched) { + winnings.put(Prize.SECOND, winnings.get(Prize.SECOND) + 1); + return; + } + if (matchCount == Prize.THIRD.getMatchCount() && !bonusMatched) { + winnings.put(Prize.THIRD, winnings.get(Prize.THIRD) + 1); + return; + } + if (matchCount == Prize.FOURTH.getMatchCount()) { + winnings.put(Prize.FOURTH, winnings.get(Prize.FOURTH) + 1); + return; + } + if (matchCount == Prize.FIFTH.getMatchCount()) { + winnings.put(Prize.FIFTH, winnings.get(Prize.FIFTH) + 1); + } + } + + public Map getWinnings() { + return winnings; + } + + public int calculateTotalWinnings() { + int total = 0; + for (Prize prize : Prize.values()) { + total += winnings.get(prize) * prize.getPrizeAmount(); + } + return total; + } +} diff --git a/src/main/java/lotto/InputValidator.java b/src/main/java/lotto/InputValidator.java new file mode 100644 index 0000000000..9fd8a9cf0d --- /dev/null +++ b/src/main/java/lotto/InputValidator.java @@ -0,0 +1,13 @@ +package lotto; + +public class InputValidator { + public static void validatePurchase(String input){ + if(!input.matches("\\d+")){ + throw new IllegalArgumentException("[ERROR] 구입금액은 숫자여아합니다."); + } + int amount = Integer.parseInt(input); + if(amount < 1000 || amount % 1000 != 0){ + throw new IllegalArgumentException("[ERROR] 구입금액은 1,000원 단위여야 합니다."); + } + } +} diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java deleted file mode 100644 index 88fc5cf12b..0000000000 --- a/src/main/java/lotto/Lotto.java +++ /dev/null @@ -1,20 +0,0 @@ -package lotto; - -import java.util.List; - -public class Lotto { - private final List numbers; - - public Lotto(List numbers) { - validate(numbers); - this.numbers = numbers; - } - - private void validate(List numbers) { - if (numbers.size() != 6) { - throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다."); - } - } - - // TODO: 추가 기능 구현 -} diff --git a/src/main/java/lotto/LottoController.java b/src/main/java/lotto/LottoController.java new file mode 100644 index 0000000000..dc0ab040ee --- /dev/null +++ b/src/main/java/lotto/LottoController.java @@ -0,0 +1,32 @@ +package lotto; + +import domain.Lotto; +import domain.Rank; +import view.LottoView; + +import java.util.List; + +public class LottoController { + private final LottoView view; + private final Rank rank; + + public LottoController(LottoView view) { + this.view = view; + this.rank = new Rank(); + } + + public void start() { + int amount = view.getPurchaseAmount(); + List purchasedLottos = rank.purchaseLotto(amount); + view.displayPurchasedLottos(purchasedLottos); + + view.setPurchasedLottos(purchasedLottos); + + List winningNumbers = view.getWinningNumbers().getNumbers(); + int bonusNumber = view.getBonusNumber(); + + rank.checkWinning(purchasedLottos, winningNumbers, bonusNumber); + view.displayStatistics(rank.getWinnings()); + view.displayReturnRate(amount, rank.calculateTotalWinnings()); + } +} diff --git a/src/main/java/view/LottoView.java b/src/main/java/view/LottoView.java new file mode 100644 index 0000000000..263e68bee1 --- /dev/null +++ b/src/main/java/view/LottoView.java @@ -0,0 +1,104 @@ +package view; + +import camp.nextstep.edu.missionutils.Console; +import domain.Lotto; +import domain.Prize; +import lotto.InputValidator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class LottoView { + private List purchasedLottos; + + public LottoView() { + this.purchasedLottos = new ArrayList<>(); + } + + public void setPurchasedLottos(List purchasedLottos) { + this.purchasedLottos = purchasedLottos; + } + + public int getPurchaseAmount() { + while (true) { + try { + System.out.print("구입금액을 입력해 주세요: "); + String input = Console.readLine(); + InputValidator.validatePurchase(input); + return Integer.parseInt(input); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + + public void displayPurchasedLottos(List purchasedLottos) { + System.out.println(purchasedLottos.size() + "개를 구매했습니다."); + for (Lotto lotto : purchasedLottos) { + System.out.println(lotto.getNumbers()); + } + } + + public Lotto getWinningNumbers() { + List winningNumbers = new ArrayList<>(); + while (true) { + try { + System.out.print("당첨 번호를 입력해 주세요: "); + String input = Console.readLine(); + String[] numbers = input.split(","); + winningNumbers.clear(); + for (String number : numbers) { + winningNumbers.add(Integer.parseInt(number.trim())); + } + return new Lotto(winningNumbers); + } catch (NumberFormatException e) { + System.out.println("[ERROR] 숫자를 입력해야 합니다."); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + + public int getBonusNumber() { + int bonusNumber; + while (true) { + try { + System.out.print("보너스 번호를 입력해 주세요: "); + String input = Console.readLine(); + bonusNumber = Integer.parseInt(input.trim()); + + validateBonusNumber(bonusNumber); + return bonusNumber; + } catch (NumberFormatException e) { + System.out.println("[ERROR] 숫자를 입력해야 합니다."); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + + private void validateBonusNumber(int bonusNumber) { + if (bonusNumber < 1 || bonusNumber > 45) { + throw new IllegalArgumentException("[ERROR] 보너스 번호는 1~45 사이의 숫자여야 합니다."); + } + for (Lotto lotto : purchasedLottos) { + if (lotto.getNumbers().contains(bonusNumber)) { + throw new IllegalArgumentException("[ERROR] 보너스 번호는 로또 번호와 중복될 수 없습니다."); + } + } + } + + public void displayStatistics(Map winnings) { + System.out.println("당첨 통계"); + System.out.println("---"); + for (Map.Entry entry : winnings.entrySet()) { + System.out.println(entry.getKey().name() + " (" + entry.getKey().getPrizeAmount() + "원) - " + entry.getValue() + "개"); + } + } + + public void displayReturnRate(int totalSpent, int totalWinnings) { + double returnRate = totalSpent == 0 ? 0 : (double) totalWinnings / totalSpent * 100; + System.out.println("총 수익률은 " + returnRate + "%입니다."); + } +} diff --git a/src/test/java/lotto/InputValidatorTest.java b/src/test/java/lotto/InputValidatorTest.java new file mode 100644 index 0000000000..3886a50833 --- /dev/null +++ b/src/test/java/lotto/InputValidatorTest.java @@ -0,0 +1,26 @@ +package lotto; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class InputValidatorTest { + @Test + void 구입금액_숫자_유효성_검사() { + assertThatThrownBy(() -> InputValidator.validatePurchase("abc")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 구입 금액은 숫자여야 합니다."); + + assertThatThrownBy(() -> InputValidator.validatePurchase("-1000")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 구입 금액은 0보다 커야 합니다."); + + assertThatThrownBy(() -> InputValidator.validatePurchase("0")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 구입 금액은 0보다 커야 합니다."); + } + + @Test + void 구입금액_정상값_테스트() { + InputValidator.validatePurchase("1000"); +} diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 309f4e50ae..756cfc381a 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/LottoTest.java @@ -1,5 +1,6 @@ package lotto; +import domain.Lotto; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,6 +21,10 @@ class LottoTest { assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 5))) .isInstanceOf(IllegalArgumentException.class); } - - // TODO: 추가 기능 구현에 따른 테스트 코드 작성 + @Test + void 빈_리스트_입력시_예외가_발생한다() { + assertThatThrownBy(() -> new Lotto(List.of())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 로또 번호는 6개여야 합니다."); + } }