diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922..26951e7 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,14 @@ package lotto; +import camp.nextstep.edu.missionutils.Console; +import camp.nextstep.edu.missionutils.Randoms; +import lotto.controller.LottoController; + +import java.util.List; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + LottoController lottoController = new LottoController(); + lottoController.lottoStart(); } } diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java deleted file mode 100644 index 519793d..0000000 --- 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(); - } - } - - // TODO: 추가 기능 구현 -} diff --git a/src/main/java/lotto/controller/LottoController.java b/src/main/java/lotto/controller/LottoController.java new file mode 100644 index 0000000..ed7555e --- /dev/null +++ b/src/main/java/lotto/controller/LottoController.java @@ -0,0 +1,36 @@ +package lotto.controller; + +import lotto.domain.*; +import lotto.view.InputView; +import lotto.view.OutputView; + +import java.util.Map; + +public class LottoController { + + public void lottoStart() { + try { + int pruchaseAmount = InputView.getPurchaseAmount(); + LottoGenerator lottoGenerator = new LottoGenerator(pruchaseAmount); + Lottos lottos = new Lottos(lottoGenerator.generateLottos()); + printLottosInfromation(lottoGenerator, lottos); + WinningLotto winningLotto = new WinningLotto(InputView.getLottoNumber(), InputView.getLottoBonusNumber()); + Map winningDetails = WinningStatistics.getWinningDetails(lottos, winningLotto); + printWinningInformation(winningDetails, pruchaseAmount); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + + private void printLottosInfromation(LottoGenerator lottoGenerator, Lottos lottos) { + OutputView.printHowManyLottosUserPurchased(lottoGenerator.getLottoQuantity()); + OutputView.printLottos(lottos); + } + + private void printWinningInformation(Map winningDetails, int purchaseAmount) { + OutputView.printWinningStatistics(); + OutputView.printWinningDetails(winningDetails); + long winningAmount = WinningStatistics.getWinningAmount(winningDetails); + OutputView.printLottoYield(WinningStatistics.getLottoYield(winningAmount, purchaseAmount)); + } +} diff --git a/src/main/java/lotto/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java new file mode 100644 index 0000000..4db7fff --- /dev/null +++ b/src/main/java/lotto/domain/Lotto.java @@ -0,0 +1,34 @@ +package lotto.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 (!isSizeSix(numbers) || isDuplicate(numbers)) { + throw new IllegalStateException(); + } + } + + private boolean isSizeSix(List numbers) { + return numbers.size() == 6; + } + + //중복체크 + private boolean isDuplicate(List numbers) { + Set duplicateChecker = new HashSet<>(numbers); + return duplicateChecker.size() != 6; + } + + public List getNumbers() { + return numbers; + } +} diff --git a/src/main/java/lotto/domain/LottoGenerator.java b/src/main/java/lotto/domain/LottoGenerator.java new file mode 100644 index 0000000..8608efe --- /dev/null +++ b/src/main/java/lotto/domain/LottoGenerator.java @@ -0,0 +1,59 @@ +package lotto.domain; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class LottoGenerator { + + private static final int LOTTO_PRICE = 1_000; + private static final int LOTTO_NUMBER_LOWER_LOMIT = 1; + private static final int LOTTO_NUMBER_UPPER_LIMIT = 45; + private static final int LOTTO_NUMBER_QUANTITY = 6; + private static final int ZERO = 0; + private static final String MONEY_SHOULD_BE_DIVIDED_BY_ONE_THOUSAND = "[ERROR] 구입 금액은 1,000원 단위로만 받을 수 있습니다."; + + private final List lottos = new ArrayList<>(); + private final int lottoQuantity; + + public LottoGenerator(int money) { + validateMoney(money); + lottoQuantity = money / LOTTO_PRICE; + } + + private void validateMoney(int money) { + if (isZeroOrNegativeNumber(money) || !isDividedByOneThousand(money)) { + throw new IllegalStateException(MONEY_SHOULD_BE_DIVIDED_BY_ONE_THOUSAND); + } + } + + private boolean isZeroOrNegativeNumber(int money) { + return money <= ZERO; + } + + private boolean isDividedByOneThousand(int money) { + return money % LOTTO_PRICE == ZERO; + } + public List generateLottos() { + for (int i = 0; i < lottoQuantity; i++) { + Lotto lotto = generateLotto(); + lottos.add(lotto); + } + return lottos; + } + private Lotto generateLotto() { + List randomNumbers = new ArrayList<>( + Randoms.pickUniqueNumbersInRange(LOTTO_NUMBER_LOWER_LOMIT, LOTTO_NUMBER_UPPER_LIMIT, + LOTTO_NUMBER_QUANTITY)); + randomNumbers.sort(Comparator.naturalOrder()); + return new Lotto(randomNumbers); + } + + public int getLottoQuantity() { + return lottoQuantity; + } + + +} diff --git a/src/main/java/lotto/domain/Lottos.java b/src/main/java/lotto/domain/Lottos.java new file mode 100644 index 0000000..1c4204e --- /dev/null +++ b/src/main/java/lotto/domain/Lottos.java @@ -0,0 +1,15 @@ +package lotto.domain; + +import java.util.List; + +public class Lottos { + private List lottos; + + public Lottos(List lottos) { + this.lottos = lottos; + } + + public List getLottos() { + return lottos; + } +} diff --git a/src/main/java/lotto/domain/WinningLotto.java b/src/main/java/lotto/domain/WinningLotto.java new file mode 100644 index 0000000..d374acc --- /dev/null +++ b/src/main/java/lotto/domain/WinningLotto.java @@ -0,0 +1,74 @@ +package lotto.domain; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class WinningLotto { + + private static final String WINNING_NUMBERS_ARE_BETWEEN_ONE_AND_FORTY_FIVE = "[ERROR] 당첨 번호는 1부터 45 사이의 숫자여야 합니다."; + private static final String WINNING_NUMBERS_MUST_BE_SIX_DIFFERENT_NUMBERS = "[ERROR] 당첨 번호는 서로 다른 6개의 수여야 합니다."; + private static final String BONUS_NUMBER_IS_BETWEEN_ONE_AND_FORTY_FIVE = "[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다."; + private static final String WINNING_NUMBERS_CONTAIN_BONUS_NUMBER = "[ERROR] 당첨 번호와 보너스 번호가 중복됩니다."; + private static final int LOTTO_NUMBER_LOWER_LIMIT = 1; + private static final int LOTTO_NUMBER_UPPER_LIMIT = 45; + private static final int LOTTO_NUMBERS_SIZE = 6; + + private final List winningNumbers; + private final int bonusNumber; + + public WinningLotto(List winningNumbers, int bonusNumber) { + validateWinningNumbers(winningNumbers); + validateBonusNumber(bonusNumber); + validateDuplicate(winningNumbers, bonusNumber); + this.winningNumbers = winningNumbers; + this.bonusNumber = bonusNumber; + } + + private void validateWinningNumbers(List winningNumbers) { + if (!isSixDifferentNumbers(winningNumbers)) { + throw new IllegalArgumentException(WINNING_NUMBERS_MUST_BE_SIX_DIFFERENT_NUMBERS); + } + if (!isBetweenOneAndFortyFive(winningNumbers)) { + throw new IllegalArgumentException(WINNING_NUMBERS_ARE_BETWEEN_ONE_AND_FORTY_FIVE); + } + } + + private boolean isBetweenOneAndFortyFive(List winningNumbers) { + for (int winningNumber : winningNumbers) { + if (winningNumber < LOTTO_NUMBER_LOWER_LIMIT || winningNumber > LOTTO_NUMBER_UPPER_LIMIT) { + return false; + } + } + return true; + } + + private boolean isSixDifferentNumbers(List winningNumbers) { + Set duplicateChecker = new HashSet<>(winningNumbers); + return duplicateChecker.size() == LOTTO_NUMBERS_SIZE; + } + + private void validateBonusNumber(int bonusNumber) { + if (!isBetweenOneAndFortyFive(bonusNumber)) { + throw new IllegalArgumentException(BONUS_NUMBER_IS_BETWEEN_ONE_AND_FORTY_FIVE); + } + } + + private boolean isBetweenOneAndFortyFive(int bonusNumber) { + return bonusNumber >= LOTTO_NUMBER_LOWER_LIMIT && bonusNumber <= LOTTO_NUMBER_UPPER_LIMIT; + } + + private void validateDuplicate(List winningNumbers, int bonusNumber) { + if (winningNumbers.contains(bonusNumber)) { + throw new IllegalArgumentException(WINNING_NUMBERS_CONTAIN_BONUS_NUMBER); + } + } + + public List getWinningNumbers() { + return winningNumbers; + } + + public int getBonusNumber() { + return bonusNumber; + } +} \ No newline at end of file diff --git a/src/main/java/lotto/domain/WinningRank.java b/src/main/java/lotto/domain/WinningRank.java new file mode 100644 index 0000000..d2a41a7 --- /dev/null +++ b/src/main/java/lotto/domain/WinningRank.java @@ -0,0 +1,38 @@ +package lotto.domain; + +import java.util.Arrays; + +public enum WinningRank { + LAST_PLACE(0, false, 0), + FIFTH_PLACE(3, false, 5_000), + FOURTH_PLACE(4, false, 50_000), + THIRD_PLACE(5, false, 1_500_000), + SECOND_PLACE(5, true, 30_000_000), + FIRST_PLACE(6, false, 2_000_000_000); + + private final int matchingCount; + private final boolean containsBonusNumber; + private final int winningPrice; + + WinningRank(int matchingCount, boolean containsBonusNumber, int winningPrice) { + this.matchingCount = matchingCount; + this.containsBonusNumber = containsBonusNumber; + this.winningPrice = winningPrice; + } + + public static WinningRank findWinningRank(int matchingCount, boolean containsBonusNumber) { + return Arrays.stream(values()) + .filter(winningRank -> winningRank.matchingCount == matchingCount) + .filter(winningRank -> winningRank.containsBonusNumber == containsBonusNumber) + .findFirst() + .orElse(WinningRank.LAST_PLACE); + } + + public int getMatchingCount() { + return matchingCount; + } + + public int getWinningPrice() { + return winningPrice; + } +} diff --git a/src/main/java/lotto/domain/WinningStatistics.java b/src/main/java/lotto/domain/WinningStatistics.java new file mode 100644 index 0000000..b6c0285 --- /dev/null +++ b/src/main/java/lotto/domain/WinningStatistics.java @@ -0,0 +1,58 @@ +package lotto.domain; + +import java.util.Arrays; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public class WinningStatistics { + + private static final int AT_LEAST_THIRD_PLACE = 5; + private static final int PERCENTAGE = 100; + private static final int INITIAL_VALUE = 0; + + public static Map getWinningDetails(Lottos lottos, WinningLotto winningLotto) { + Map winningDetails = generateWinningDetails(); + for (Lotto lotto : lottos.getLottos()) { + int matchingCount = compareNumbersWithWinningNumbers(lotto, winningLotto); + boolean containsBonusNumber = compareNumbersWithBonusNumber(lotto, winningLotto, matchingCount); + WinningRank winningRank = WinningRank.findWinningRank(matchingCount, containsBonusNumber); + winningDetails.replace(winningRank, winningDetails.get(winningRank) + 1); + } + return winningDetails; + } + + public static Map generateWinningDetails() { + Map winningDetails = new EnumMap<>(WinningRank.class); + Arrays.stream(WinningRank.values()).forEach(winningRank -> winningDetails.put(winningRank, INITIAL_VALUE)); + return winningDetails; + } + + private static int compareNumbersWithWinningNumbers(Lotto lotto, WinningLotto winningLotto) { + List numbers = lotto.getNumbers(); + List winningNumbers = winningLotto.getWinningNumbers(); + return (int) numbers.stream() + .filter(winningNumbers::contains) + .count(); + } + + private static boolean compareNumbersWithBonusNumber(Lotto lotto, WinningLotto winningLotto, int matchingCount) { + if (matchingCount != AT_LEAST_THIRD_PLACE) { + return false; + } + List numbers = lotto.getNumbers(); + int bonusNumber = winningLotto.getBonusNumber(); + return numbers.contains(bonusNumber); + } + + public static long getWinningAmount(Map winningDetails) { + return winningDetails.entrySet().stream() + .mapToLong(entry -> (long) entry.getKey().getWinningPrice() * entry.getValue()) + .sum(); + } + + public static double getLottoYield(long winningAmount, int money) { + double lottoYield = PERCENTAGE + (double) (winningAmount - money) / money * PERCENTAGE; + return Math.round(lottoYield * 10) / 10.0; + } +} \ No newline at end of file diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java new file mode 100644 index 0000000..5ac6131 --- /dev/null +++ b/src/main/java/lotto/view/InputView.java @@ -0,0 +1,45 @@ +package lotto.view; + +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 LOTTO_NUMBER_MESSAGE = "당첨 번호를 입력해 주세요"; + private static final String LOTTO_BONUS_NUMBER_MESSAGE = "보너스 번호를 입력해주세요"; + private static final String SEPARATOR_VALUE = ","; + private static final String NOT_NUMBER = "[ERROR] 숫자가 아닌 값이 입력됐습니다."; + + public static int getPurchaseAmount() { + System.out.println(PURCHASE_AMOUNT_MESSAGE); + try { + return Integer.parseInt(Console.readLine()); + } catch (NumberFormatException numberFormatException) { + throw new IllegalStateException(NOT_NUMBER); + } + } + + public static List getLottoNumber() { + System.out.println(LOTTO_NUMBER_MESSAGE); + try { + return Arrays.stream(Console.readLine().split(SEPARATOR_VALUE)) + .map(Integer::parseInt) + .collect(Collectors.toList()); + } catch (NumberFormatException numberFormatException) { + throw new IllegalStateException(NOT_NUMBER); + } + } + + public static int getLottoBonusNumber() { + System.out.println(LOTTO_BONUS_NUMBER_MESSAGE); + try { + return Integer.parseInt(Console.readLine()); + } catch (NumberFormatException numberFormatException) { + throw new IllegalStateException(NOT_NUMBER); + } + } +} diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java new file mode 100644 index 0000000..e385434 --- /dev/null +++ b/src/main/java/lotto/view/OutputView.java @@ -0,0 +1,66 @@ +package lotto.view; + +import lotto.domain.Lottos; +import lotto.domain.WinningRank; + +import java.text.DecimalFormat; +import java.util.Map; + +public class OutputView { + + private static final String HOW_MANY_LOTTO_USER_PURCHASED_MESSAGE = "%d개를 구매했습니다.\n"; + private static final String WINNING_STATISTICS_MESSAGE = "당첨 통계\n---"; + private static final String WINNING_DETAILS_MESSAGE = "%d개 일치 (%s원) - %d개\n"; + private static final String WINNING_DETAILS_WITH_BONUS_MESSAGE = "%d개 일치, 보너스 볼 일치 (%s원) - %d개\n"; + private static final String LOTTO_YIELD_MESSAGE = "총 수익률은 %.1f%%입니다.\n"; + private static final String SEPARATOR_PATTERN = "###,###"; + + public static void printHowManyLottosUserPurchased(int lottoQuantity) { + System.out.printf(HOW_MANY_LOTTO_USER_PURCHASED_MESSAGE, lottoQuantity); + } + + public static void printLottos(Lottos lottos) { + lottos.getLottos().stream() + .forEach(lotto -> System.out.println(lotto.getNumbers().toString())); + } + + public static void printWinningStatistics() { + System.out.println(WINNING_STATISTICS_MESSAGE); + } + + public static void printWinningDetails(Map winningDetails) { + winningDetails.entrySet().stream() + .filter(entry -> entry.getKey() != WinningRank.SECOND_PLACE) + .forEach(entry -> { + if (entry.getKey() == WinningRank.SECOND_PLACE) { + printWinningDetailsWithBonus(entry); + return; + } + printWinningDetailsWithoutBonus(entry); + }); + + } + + private static void printWinningDetailsWithBonus(Map.Entry entry) { + System.out.printf(WINNING_DETAILS_WITH_BONUS_MESSAGE, + entry.getKey().getMatchingCount(), + getFormattingPrice(entry.getKey().getWinningPrice()), + entry.getValue()); + } + + private static void printWinningDetailsWithoutBonus(Map.Entry entry) { + System.out.printf(WINNING_DETAILS_MESSAGE, + entry.getKey().getMatchingCount(), + getFormattingPrice(entry.getKey().getWinningPrice()), + entry.getValue()); + } + + private static String getFormattingPrice(int winningPrice) { + DecimalFormat df = new DecimalFormat(SEPARATOR_PATTERN); + return df.format(winningPrice); + } + + public static void printLottoYield(double lottoYield) { + System.out.printf(LOTTO_YIELD_MESSAGE, lottoYield); + } +} diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 0f3af0f..d2bca39 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/LottoTest.java @@ -1,5 +1,6 @@ package lotto; +import lotto.domain.Lotto; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test;