diff --git a/README.md b/README.md index 32ba6986..559ef305 100644 --- a/README.md +++ b/README.md @@ -41,3 +41,16 @@ ### 결과출력 > ![1_결과출력](https://user-images.githubusercontent.com/29879110/155278106-3f5717ed-1885-43ba-a2ec-dcb4f10c6c4a.JPG)
+ +## Mission3 - 수동구매 기능 추가 +### 기능요구사항 +- [x] 현재 로또 생성기는 자동 생성 기능만 제공한다. 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야 한다. +- [x] 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다. +### 프로그래밍 요구사항 +- [x] 예외가 발생하는 부분에 대해 자바 Exception을 적용해 예외처리한다. +- [x] 사용자가 입력한 값에 대한 예외 처리를 철저히 한다. +- [x] 상속과 인터페이스를 통해 구현을 간결히 할 수 없는지 고민해 본다. + +### 결과출력 +> ![1_결과출력](https://user-images.githubusercontent.com/29879110/155676112-fd7ccf8e-9a3e-48c1-9473-20863e490827.JPG) +
\ No newline at end of file diff --git a/src/main/java/lotto/controller/LottoGame.java b/src/main/java/lotto/controller/LottoGame.java index 0f2aad8f..8c4a9b73 100644 --- a/src/main/java/lotto/controller/LottoGame.java +++ b/src/main/java/lotto/controller/LottoGame.java @@ -1,15 +1,14 @@ package lotto.controller; -import lotto.domain.LottoPaper; -import lotto.domain.LottoStore; -import lotto.domain.WinningStrategy; +import lotto.domain.*; import lotto.view.InputView; import lotto.view.OutputView; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.IntStream; public class LottoGame { @@ -19,74 +18,53 @@ public void start() { InputView.init(); int purchaseAmount = InputView.getPurchaseAmount(); - LottoPaper lottoPaper = purchase(purchaseAmount); + int manualLottoCount = InputView.getManualLottoCount(); - List winningStrategies = checkWinning(lottoPaper); + LottoPaper lottoPaper = purchase( + purchaseAmount, + manualLottoCount, + getManualNumbers(InputView.getManualLottoNumbers(manualLottoCount))); - calculateWinningStats(winningStrategies, purchaseAmount); + calculateWinningStats(checkWinning(lottoPaper), purchaseAmount); InputView.close(); } - private LottoPaper purchase(int purchaseAmount) { - LottoStore lottoStore = new LottoStore(); - LottoPaper lottoPaper = lottoStore.purchase(purchaseAmount); + private LottoPaper purchase(int purchaseAmount, int manualLottoCount, Map> numbers) { + LottoPaper lottoPaper = new LottoStore().purchase(purchaseAmount, manualLottoCount, numbers); - OutputView.printPurchaseCount(lottoPaper.getLottoSize()); + OutputView.printPurchaseCount(manualLottoCount, lottoPaper.getLottoSize() - manualLottoCount); OutputView.printLottoPaper(lottoPaper.showLottoPaper()); return lottoPaper; } private List checkWinning(LottoPaper lottoPaper) { - String inputWinningNumbers = InputView.getRequiredWinningNumber(); - String inputBonusNumber = InputView.getBonusBallNumber(); + List winningNumbers = getNumbers(InputView.getRequiredWinningNumber()); + int bonusNumber = getBonusNumber(InputView.getBonusBallNumber(winningNumbers)); - List winningNumbers = getWinningNumbers(inputWinningNumbers); - int bonusNumber = getBonusNumber(inputBonusNumber); - - List correctNumberCounts = lottoPaper.judgeWinning(winningNumbers); - List hasBonusNumbers = lottoPaper.hasBonusNumbers(bonusNumber); - - return IntStream.range(0, correctNumberCounts.size()) - .mapToObj(index -> convertMatchNumberToWinningStrategy(correctNumberCounts.get(index), hasBonusNumbers.get(index))) - .collect(Collectors.toList()); + return lottoPaper.checkWinning(new WinningLotto(winningNumbers, bonusNumber)); } - private List getWinningNumbers(String inputWinningNumbers) { - return Arrays.stream(inputWinningNumbers.split(NUMBER_DELIMITER)) + private List getNumbers(String inputNumbers) { + return Arrays.stream(inputNumbers.split(NUMBER_DELIMITER)) .mapToInt(Integer::parseInt) .boxed() .collect(Collectors.toList()); } - private int getBonusNumber(String inputBonusNumber) { - return Integer.parseInt(inputBonusNumber); - } + private Map> getManualNumbers(Map inputNumbers) { + Map> manualNumbers = new HashMap<>(); - private WinningStrategy convertMatchNumberToWinningStrategy(int matchNumber, boolean hasBonusNumber) { - if (matchNumber == WinningStrategy.ZERO_MATCHES.getCorrectNumber()) { - return WinningStrategy.ZERO_MATCHES; - } - if (matchNumber == WinningStrategy.ONE_MATCHES.getCorrectNumber()) { - return WinningStrategy.ONE_MATCHES; - } - if (matchNumber == WinningStrategy.TWO_MATCHES.getCorrectNumber()) { - return WinningStrategy.TWO_MATCHES; - } - if (matchNumber == WinningStrategy.THREE_MATCHES.getCorrectNumber()) { - return WinningStrategy.THREE_MATCHES; + for (int i = 0; i < inputNumbers.size(); i++) { + manualNumbers.put(i, getNumbers(inputNumbers.get(i))); } - if (matchNumber == WinningStrategy.FOUR_MATCHES.getCorrectNumber()) { - return WinningStrategy.FOUR_MATCHES; - } - if (matchNumber == WinningStrategy.FIVE_MATCHES.getCorrectNumber() && hasBonusNumber) { - return WinningStrategy.FIVE_WITH_BONUS_MATCHES; - } - if (matchNumber == WinningStrategy.FIVE_MATCHES.getCorrectNumber()) { - return WinningStrategy.FIVE_MATCHES; - } - return WinningStrategy.SIX_MATCHES; + + return manualNumbers; + } + + private int getBonusNumber(String inputBonusNumber) { + return Integer.parseInt(inputBonusNumber); } private void calculateWinningStats(List winningStrategies, int purchaseAmount) { diff --git a/src/main/java/lotto/domain/AutoLotto.java b/src/main/java/lotto/domain/AutoLotto.java new file mode 100644 index 00000000..cfdb9889 --- /dev/null +++ b/src/main/java/lotto/domain/AutoLotto.java @@ -0,0 +1,27 @@ +package lotto.domain; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class AutoLotto extends Lotto { + private static final int PICK_NUMBER_LENGTH = 6; + + public AutoLotto(List numbers) { + super(numbers); + } + + @Override + public List createLotto(List numbers) { + return pickSixNumbers(numbers); + } + + private List pickSixNumbers(List numbers) { + Collections.shuffle(numbers); + + return numbers.stream() + .limit(PICK_NUMBER_LENGTH) + .sorted() + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/lotto/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java index ae747a5d..6457e383 100644 --- a/src/main/java/lotto/domain/Lotto.java +++ b/src/main/java/lotto/domain/Lotto.java @@ -1,35 +1,23 @@ package lotto.domain; -import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; -public class Lotto { +public abstract class Lotto { + private final List numbers; public Lotto(List numbers) { - this.numbers = pickSixNumbers(numbers); + this.numbers = createLotto(numbers); } - private List pickSixNumbers(List numbers) { - int pickNumberLength = 6; - - Collections.shuffle(numbers); - - return numbers.stream() - .limit(pickNumberLength) - .sorted() - .collect(Collectors.toList()); - } + public abstract List createLotto(List numbers); public String showLottoNumbers() { return numbers.toString(); } - public int getCorrectNumberCount(List winningNumbers) { - return winningNumbers.stream() - .mapToInt(this::correctNumber) - .sum(); + public boolean hasNumber(int number) { + return numbers.contains(number); } private int correctNumber(int number) { @@ -39,7 +27,11 @@ private int correctNumber(int number) { return 0; } - public boolean hasBonusNumber(int bonusNumber) { - return numbers.contains(bonusNumber); + public MatchLottoResult match(WinningLotto winningLotto) { + int matchCount = (int)numbers.stream() + .filter(value -> winningLotto.hasNumber(value)) + .count(); + + return new MatchLottoResult(matchCount, numbers.contains(winningLotto.getBonusNumber())); } } diff --git a/src/main/java/lotto/domain/LottoPaper.java b/src/main/java/lotto/domain/LottoPaper.java index dbacc6d5..7d3792ce 100644 --- a/src/main/java/lotto/domain/LottoPaper.java +++ b/src/main/java/lotto/domain/LottoPaper.java @@ -25,16 +25,12 @@ public String showLottoPaper() { return sb.toString(); } - public List judgeWinning(List winningNumbers) { + public List checkWinning(WinningLotto winningLotto) { return lottos.stream() - .mapToInt(lotto -> lotto.getCorrectNumberCount(winningNumbers)) - .boxed() - .collect(Collectors.toList()); - } - - public List hasBonusNumbers(int bonusNumber) { - return lottos.stream() - .map(lotto -> lotto.hasBonusNumber(bonusNumber)) + .map(lotto -> lotto.match(winningLotto)) + .map(result -> WinningStrategy.convertMatchNumberToWinningStrategy( + result.getMatchNumberCount(), + result.hasBonus())) .collect(Collectors.toList()); } } diff --git a/src/main/java/lotto/domain/LottoStore.java b/src/main/java/lotto/domain/LottoStore.java index 633e4ce0..533e4c40 100644 --- a/src/main/java/lotto/domain/LottoStore.java +++ b/src/main/java/lotto/domain/LottoStore.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; public class LottoStore { private static final int LOTTO_PRICE = 1_000; @@ -24,23 +25,27 @@ private List getAllLottoNumber() { return this.allLottoNumber; } - private int getLottoCount(int purchaseAmount) { - return purchaseAmount / LOTTO_PRICE; - } - - public LottoPaper purchase(int purchaseAmount) { + public LottoPaper purchase(int purchaseAmount, int manualLottoCount, Map> numbers) { int lottoCount = getLottoCount(purchaseAmount); - List lottos = createLottos(lottoCount); + List lottos = createLottos(manualLottoCount, lottoCount - manualLottoCount, numbers); return new LottoPaper(lottos); } - private List createLottos(int lottoCount) { + private int getLottoCount(int purchaseAmount) { + return purchaseAmount / LOTTO_PRICE; + } + + private List createLottos(int manualLottoCount, int autoLottoCount, Map> numbers) { List lottos = new ArrayList<>(); - for (int i = 0; i < lottoCount; i++) { - lottos.add(new Lotto(allLottoNumber)); + for (int i = 0; i < manualLottoCount; i++) { + lottos.add(new ManualLotto(numbers.get(i))); + } + + for (int i = 0; i < autoLottoCount; i++) { + lottos.add(new AutoLotto(allLottoNumber)); } return lottos; diff --git a/src/main/java/lotto/domain/ManualLotto.java b/src/main/java/lotto/domain/ManualLotto.java new file mode 100644 index 00000000..522b8e60 --- /dev/null +++ b/src/main/java/lotto/domain/ManualLotto.java @@ -0,0 +1,18 @@ +package lotto.domain; + +import java.util.List; +import java.util.stream.Collectors; + +public class ManualLotto extends Lotto { + + public ManualLotto(List numbers) { + super(numbers); + } + + @Override + public List createLotto(List numbers) { + return numbers.stream() + .sorted() + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/lotto/domain/MatchLottoResult.java b/src/main/java/lotto/domain/MatchLottoResult.java new file mode 100644 index 00000000..dabcdc13 --- /dev/null +++ b/src/main/java/lotto/domain/MatchLottoResult.java @@ -0,0 +1,19 @@ +package lotto.domain; + +public class MatchLottoResult { + private int matchNumberCount; + private boolean hasBonus; + + public MatchLottoResult(int matchNumberCount, boolean hasBonus) { + this.matchNumberCount = matchNumberCount; + this.hasBonus = hasBonus; + } + + public int getMatchNumberCount() { + return matchNumberCount; + } + + public boolean hasBonus() { + return hasBonus; + } +} diff --git a/src/main/java/lotto/domain/WinningLotto.java b/src/main/java/lotto/domain/WinningLotto.java new file mode 100644 index 00000000..ebcbf57a --- /dev/null +++ b/src/main/java/lotto/domain/WinningLotto.java @@ -0,0 +1,25 @@ +package lotto.domain; + +import java.util.List; +import java.util.stream.Collectors; + +public class WinningLotto extends Lotto { + + private final int bonusNumber; + + public WinningLotto(List numbers, int bonusNumber) { + super(numbers); + this.bonusNumber = bonusNumber; + } + + @Override + public List createLotto(List numbers) { + return numbers.stream() + .sorted() + .collect(Collectors.toList()); + } + + public int getBonusNumber() { + return bonusNumber; + } +} diff --git a/src/main/java/lotto/domain/WinningStrategy.java b/src/main/java/lotto/domain/WinningStrategy.java index 3726749c..86f4515e 100644 --- a/src/main/java/lotto/domain/WinningStrategy.java +++ b/src/main/java/lotto/domain/WinningStrategy.java @@ -26,4 +26,29 @@ public int getWinningPrice() { public int getCorrectNumber() { return this.correctNumber; } + + public static WinningStrategy convertMatchNumberToWinningStrategy(int matchNumber, boolean hasBonusNumber) { + if (matchNumber == WinningStrategy.ZERO_MATCHES.getCorrectNumber()) { + return WinningStrategy.ZERO_MATCHES; + } + if (matchNumber == WinningStrategy.ONE_MATCHES.getCorrectNumber()) { + return WinningStrategy.ONE_MATCHES; + } + if (matchNumber == WinningStrategy.TWO_MATCHES.getCorrectNumber()) { + return WinningStrategy.TWO_MATCHES; + } + if (matchNumber == WinningStrategy.THREE_MATCHES.getCorrectNumber()) { + return WinningStrategy.THREE_MATCHES; + } + if (matchNumber == WinningStrategy.FOUR_MATCHES.getCorrectNumber()) { + return WinningStrategy.FOUR_MATCHES; + } + if (matchNumber == WinningStrategy.FIVE_MATCHES.getCorrectNumber() && hasBonusNumber) { + return WinningStrategy.FIVE_WITH_BONUS_MATCHES; + } + if (matchNumber == WinningStrategy.FIVE_MATCHES.getCorrectNumber()) { + return WinningStrategy.FIVE_MATCHES; + } + return WinningStrategy.SIX_MATCHES; + } } diff --git a/src/main/java/lotto/view/InputValidator.java b/src/main/java/lotto/view/InputValidator.java new file mode 100644 index 00000000..9a584484 --- /dev/null +++ b/src/main/java/lotto/view/InputValidator.java @@ -0,0 +1,42 @@ +package lotto.view; + +import java.util.List; + +public class InputValidator { + private static final String NOT_NUMBER_REGEX = "[0-9]+"; + private static final int LESS_PURCHASE_AMOUNT = 1000; + + public static boolean validateInputAllNumber(String input) { + if (!input.matches(NOT_NUMBER_REGEX) || Integer.parseInt(input) < LESS_PURCHASE_AMOUNT) { + throw new IllegalArgumentException("숫자만 입력가능하며 1000원 이상으로 입력해주세요."); + } + + return true; + } + + public static boolean validateInputManualLottoCount(String input) { + if (!input.matches(NOT_NUMBER_REGEX)) { + throw new IllegalArgumentException("숫자만 입력가능합니다."); + } + + return true; + } + + public static boolean validateIsNumber(String input) { + try { + Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("숫자만 입력가능합니다."); + } + + return true; + } + + public static boolean validateDuplicateWinningNumber(List winningNumbers, String bonusNumber) { + if (winningNumbers.contains(Integer.parseInt(bonusNumber))) { + throw new IllegalArgumentException("보너스 번호는 당첨번호와 중복될 수 없습니다."); + } + + return true; + } +} diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java index f3662845..ad561e0a 100644 --- a/src/main/java/lotto/view/InputView.java +++ b/src/main/java/lotto/view/InputView.java @@ -1,20 +1,53 @@ package lotto.view; -import java.util.Scanner; +import java.util.*; public class InputView { private static Scanner sc; - private static String PURCHASE_AMOUNT_MESSAGE = "구입금액을 입력해 주세요."; - private static String REQUIRED_WINNING_NUMBER_MESSAGE = "당첨 번호를 입력해 주세요."; - private static String BONUS_BALL_NUMBER_MESSAGE = "보너스 볼을 입력해 주세요."; + private static final String PURCHASE_AMOUNT_MESSAGE = "구입금액을 입력해 주세요."; + private static final String MANUAL_LOTTO_COUNT_MESSAGE = "수동으로 구매할 로또 수를 입력해 주세요."; + private static final String MANUAL_LOTTO_NUMBERS_MESSAGE = "수동으로 구매할 번호를 입력해 주세요."; + private static final String REQUIRED_WINNING_NUMBER_MESSAGE = "당첨 번호를 입력해 주세요."; + private static final String BONUS_BALL_NUMBER_MESSAGE = "보너스 볼을 입력해 주세요."; public static int getPurchaseAmount() { - System.out.println(PURCHASE_AMOUNT_MESSAGE); - int purchaseAmount = sc.nextInt(); - sc.nextLine(); + try { + System.out.println(PURCHASE_AMOUNT_MESSAGE); + String input = sc.nextLine(); + if (InputValidator.validateInputAllNumber(input)) { + return Integer.parseInt(input); + } + } catch (IllegalArgumentException ne) { + System.err.println(ne.getMessage()); + } - return purchaseAmount; + return getPurchaseAmount(); + } + + public static int getManualLottoCount() { + try { + System.out.println(MANUAL_LOTTO_COUNT_MESSAGE); + String input = sc.nextLine(); + if (InputValidator.validateInputManualLottoCount(input)) { + return Integer.parseInt(input); + } + } catch (IllegalArgumentException ne) { + System.err.println(ne.getMessage()); + } + + return getManualLottoCount(); + } + + public static Map getManualLottoNumbers(int manualLottoCount) { + System.out.println(MANUAL_LOTTO_NUMBERS_MESSAGE); + Map manualLottoNumbers = new HashMap<>(); + + for (int i = 0; i < manualLottoCount; i++) { + manualLottoNumbers.put(i, sc.nextLine()); + } + + return manualLottoNumbers; } public static String getRequiredWinningNumber() { @@ -22,9 +55,19 @@ public static String getRequiredWinningNumber() { return sc.nextLine(); } - public static String getBonusBallNumber() { - System.out.println(BONUS_BALL_NUMBER_MESSAGE); - return sc.nextLine(); + public static String getBonusBallNumber(List winningNumbers) { + try { + System.out.println(BONUS_BALL_NUMBER_MESSAGE); + String bonusBall = sc.nextLine(); + InputValidator.validateIsNumber(bonusBall); + InputValidator.validateDuplicateWinningNumber(winningNumbers, bonusBall); + + return bonusBall; + } catch (IllegalArgumentException e) { + System.err.println(e.getMessage()); + } + + return getBonusBallNumber(winningNumbers); } public static void init() { diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java index f6276cb5..2a23efe8 100644 --- a/src/main/java/lotto/view/OutputView.java +++ b/src/main/java/lotto/view/OutputView.java @@ -6,7 +6,7 @@ public class OutputView { - private static final String PURCHASED_COUNT_MESSAGE = "개를 구매했습니다."; + private static final String PURCHASED_COUNT_MESSAGE = "수동으로 %d장, 자동으로 %d개를 구매했습니다."; private static final String WINNING_STATS = "당첨 통계"; private static final String DIVISION_LINE = "---------"; private static final String CORRECT_THREE_NUMBER = "3개 일치 (5000원) - %d개"; @@ -16,8 +16,8 @@ public class OutputView { private static final String CORRECT_SIX_NUMBER = "6개 일치 (2000000000원) - %d개"; private static final String TOTAL_PROFIT_MESSAGE = "총 수익률은 %.2f%%입니다."; - public static void printPurchaseCount(int lottoSize) { - System.out.println(lottoSize + PURCHASED_COUNT_MESSAGE); + public static void printPurchaseCount(int manualLottoCount, int autoLottoCount) { + System.out.printf(PURCHASED_COUNT_MESSAGE + System.lineSeparator(),manualLottoCount, autoLottoCount); } public static void printLottoPaper(String lottoPaper) { diff --git a/src/test/java/lotto/domain/LottoPaperTest.java b/src/test/java/lotto/domain/LottoPaperTest.java index 8a86198c..d5eecc1e 100644 --- a/src/test/java/lotto/domain/LottoPaperTest.java +++ b/src/test/java/lotto/domain/LottoPaperTest.java @@ -1,24 +1,32 @@ package lotto.domain; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.Arrays; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; +import java.util.stream.IntStream; import static org.assertj.core.api.Assertions.assertThat; class LottoPaperTest { - private int purchaseAmount = 500_000; + private int purchaseAmount = 14_000; + private int manualLottoCount = 3; private LottoPaper lottoPaper; + private List allNumberStream = IntStream.range(1, 46).boxed().collect(Collectors.toList()); @BeforeEach void setup() { + Map> allNumbers = new HashMap<>(); + allNumbers.put(0, IntStream.range(1, 46).boxed().collect(Collectors.toList())); + allNumbers.put(1, IntStream.range(1, 46).boxed().collect(Collectors.toList())); + allNumbers.put(2, IntStream.range(1, 46).boxed().collect(Collectors.toList())); + LottoStore lottoStore = new LottoStore(); - lottoPaper = lottoStore.purchase(purchaseAmount); + lottoPaper = lottoStore.purchase(purchaseAmount, manualLottoCount, allNumbers); } @Test @@ -31,25 +39,4 @@ void purchaseTest() { // then assertThat(lottoPaper.getLottoSize()).isEqualTo(purchaseAmount / 1000); } - - @Test - @DisplayName("당첨번호를 전달하면 각 로또가 몇 개 당첨되었는지 리스트로 리턴한다") - void judgeWinningTest() { - // given - String winningString = lottoPaper.showLottoPaper().split(System.lineSeparator())[0]; - String[] splitNumbers = winningString.replaceAll(" ", "") - .replaceAll("\\[", "") - .replaceAll("\\]", "") - .split(","); - List winningNumbers = Arrays.stream(splitNumbers) - .mapToInt(number -> Integer.parseInt(number)) - .boxed() - .collect(Collectors.toList()); - - // when - List correctNumberCounts = lottoPaper.judgeWinning(winningNumbers); - - // then - assertThat(correctNumberCounts.contains(6)).isEqualTo(true); - } }