diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922ba4..63d194b593 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,15 @@ package lotto; +import lotto.controller.LottoController; +import lotto.domain.LottoFactory; +import lotto.domain.LottoMachine; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + LottoFactory lottoFactory = new LottoFactory(); + LottoMachine lottoMachine = new LottoMachine(lottoFactory); + LottoController lottoController = new LottoController(lottoMachine); + + lottoController.start(); } } diff --git a/src/main/java/lotto/controller/LottoController.java b/src/main/java/lotto/controller/LottoController.java new file mode 100644 index 0000000000..070182fae8 --- /dev/null +++ b/src/main/java/lotto/controller/LottoController.java @@ -0,0 +1,31 @@ +package lotto.controller; + +import lotto.domain.*; + +import java.util.List; + +import static lotto.view.Input.*; +import static lotto.view.Output.*; + +public class LottoController { + + private final LottoMachine lottoMachine; + + public LottoController(LottoMachine lottoMachine) { + this.lottoMachine = lottoMachine; + } + + public void start() { + try { + LottoMoney money = new LottoMoney(insertLottoMoney()); + Lottos lottos = lottoMachine.createLottos(money, insertManualLottoCount()); + printLottos(lottos); + List winningNumbers = lottoMachine.createWinningNumbers(insertNormalWinningNumbers(), insertBonusWinningNumber()); + LottoResult lottoResult = lottoMachine.computeLottoResult(lottos, winningNumbers); + printLottoResult(lottoResult, money); + } catch (IllegalArgumentException error) { + printErrorMessage(error.getMessage()); + } + } + +} diff --git a/src/main/java/lotto/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java new file mode 100644 index 0000000000..f2a10ba8f9 --- /dev/null +++ b/src/main/java/lotto/domain/Lotto.java @@ -0,0 +1,35 @@ +package lotto.domain; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.List; +import java.util.stream.Collectors; + +public class Lotto { + private final int MINIMUM_LOTTO_NUMBER = 1; + private final int MAXIMUM_LOTTO_NUMBER = 45; + private final int LOTTO_TOTAL_COUNT = 6; + + private final List numbers; + + public Lotto(List numbers) { + this.numbers = numbers.stream() + .sorted() + .collect(Collectors.toList()); + } + + public Lotto() { + this.numbers = Randoms.pickUniqueNumbersInRange(MINIMUM_LOTTO_NUMBER, MAXIMUM_LOTTO_NUMBER, LOTTO_TOTAL_COUNT) + .stream() + .sorted() + .collect(Collectors.toList()); + } + + public List getNumbers() { + return this.numbers; + } + + public boolean checkContainWinningNumber(Integer winningNumber) { + return numbers.contains(winningNumber); + } +} diff --git a/src/main/java/lotto/domain/LottoFactory.java b/src/main/java/lotto/domain/LottoFactory.java new file mode 100644 index 0000000000..f8ba6aeac5 --- /dev/null +++ b/src/main/java/lotto/domain/LottoFactory.java @@ -0,0 +1,14 @@ +package lotto.domain; + +import java.util.List; + +public class LottoFactory { + + public Lotto createManualLotto(List numbers) { + return new Lotto(numbers); + } + + public Lotto createAutoLotto() { + return new Lotto(); + } +} diff --git a/src/main/java/lotto/domain/LottoMachine.java b/src/main/java/lotto/domain/LottoMachine.java new file mode 100644 index 0000000000..5bb61cf250 --- /dev/null +++ b/src/main/java/lotto/domain/LottoMachine.java @@ -0,0 +1,123 @@ +package lotto.domain; + +import lotto.domain.enumeration.NumberType; +import lotto.domain.enumeration.Ranking; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static lotto.domain.enumeration.Ranking.*; +import static lotto.view.Input.insertLottoNumbers; +import static lotto.view.Output.printInsertManualLottoNumbersRequest; + +public class LottoMachine { + private final LottoFactory lottoFactory; + + public LottoMachine(LottoFactory lottoFactory) { + this.lottoFactory = lottoFactory; + } + + public Lottos createLottos(LottoMoney money, int manualLottoCount) { + List manualLottos = createManualLottos(manualLottoCount); + List autoLottos = createAutoLottos(money.getTotalLottoCount() - manualLottos.size()); + + return new Lottos(manualLottos, autoLottos); + } + + private List createManualLottos(int manualLottoCount) { + List manualLottos = new ArrayList<>(); + printInsertManualLottoNumbersRequest(); + for (int i = 0; i < manualLottoCount; i++) { + List numbers = insertLottoNumbers(); + Lotto manualLotto = lottoFactory.createManualLotto(numbers); + manualLottos.add(manualLotto); + } + + return manualLottos; + } + + private List createAutoLottos(int autoLottoCount) { + List autoLottos = new ArrayList<>(); + for (int i = 0; i < autoLottoCount; i++) { + Lotto autoLotto = lottoFactory.createAutoLotto(); + autoLottos.add(autoLotto); + } + + return autoLottos; + } + + public List createWinningNumbers(List normalNumbers, Integer bonusNumber) { + List totalWinningNumbers = new ArrayList<>(); + List normalWinningNumbers = createNormalWinningNumbers(normalNumbers); + WinningNumber bonusWinningNumber = createBonusNumber(bonusNumber); + + totalWinningNumbers.addAll(normalWinningNumbers); + totalWinningNumbers.add(bonusWinningNumber); + + return totalWinningNumbers; + } + + private List createNormalWinningNumbers(List normalNumbers) { + List result = new ArrayList<>(); + for (Integer number : normalNumbers) { + WinningNumber normalNumber = new WinningNumber(number, NumberType.NORMAL); + result.add(normalNumber); + } + return result; + } + + private WinningNumber createBonusNumber(Integer bonusNumber) { + return new WinningNumber(bonusNumber, NumberType.BOUNS); + } + + public LottoResult computeLottoResult(Lottos lottos, List winningNumbers) { + Map winningInfo = new HashMap<>(); + + for (Lotto lotto : lottos.getLottos()) { + Ranking ranking = determineRanking(lotto, winningNumbers); + winningInfo.put(ranking, winningInfo.getOrDefault(ranking, 0) + 1); + } + return new LottoResult(winningInfo); + } + + private Ranking determineRanking(Lotto lotto, List winningNumbers) { + int equalNormalNumberCount = getEqualCount(lotto, winningNumbers); + boolean isBonusEqual = checkBonusEqual(lotto, winningNumbers); + + return Ranking.create(equalNormalNumberCount, isBonusEqual); + } + + private int getEqualCount(Lotto lotto, List winningNumbers) { + return (int) winningNumbers.stream() + .map(WinningNumber::getNumber) + .filter(lotto::checkContainWinningNumber) + .count(); + } + + private boolean checkBonusEqual(Lotto lotto, List winningNumbers) { + return lotto.getNumbers() + .contains( + winningNumbers.stream() + .filter(WinningNumber::isBonus) + .map(WinningNumber::getNumber) + .findFirst().get() + ); + } + + public static Double calculateProfit(LottoResult lottoResult, LottoMoney money) { + Map winningInfo = lottoResult.getWinningInfo(); + Long value = sum(winningInfo); + + return (double) value / (double) money.getPrice(); + } + + private static Long sum(Map winningInfo) { + return winningInfo.getOrDefault(FIFTH,0) * FIFTH.getPrizeMoney() + + winningInfo.getOrDefault(FORTH, 0) * FORTH.getPrizeMoney() + + winningInfo.getOrDefault(THIRD, 0) * THIRD.getPrizeMoney() + + winningInfo.getOrDefault(SECOND, 0) * SECOND.getPrizeMoney() + + winningInfo.getOrDefault(FIRST, 0) * FIRST.getPrizeMoney(); + } +} diff --git a/src/main/java/lotto/domain/LottoMoney.java b/src/main/java/lotto/domain/LottoMoney.java new file mode 100644 index 0000000000..bdf137a5e9 --- /dev/null +++ b/src/main/java/lotto/domain/LottoMoney.java @@ -0,0 +1,30 @@ +package lotto.domain; + +public class LottoMoney { + private final String MONEY_MUST_OVER_1000 = "구매 금액은 1000원 이상을 입력해주세요."; + private final String MONEY_UNIT_IS_1000 = "구매 금액은 1000원 단위로 입력해주세요."; + private final int LOTTO_PRICE_UNIT = 1000; + private final int price; + + public LottoMoney(int price) { + validatePrice(price); + this.price = price; + } + + private void validatePrice(int price) { + if (price < LOTTO_PRICE_UNIT) { + throw new IllegalArgumentException(MONEY_MUST_OVER_1000); + } + if (price % LOTTO_PRICE_UNIT != 0) { + throw new IllegalArgumentException(MONEY_UNIT_IS_1000); + } + } + + public int getPrice() { + return price; + } + + public int getTotalLottoCount() { + return price / LOTTO_PRICE_UNIT; + } +} diff --git a/src/main/java/lotto/domain/LottoResult.java b/src/main/java/lotto/domain/LottoResult.java new file mode 100644 index 0000000000..89d8c68461 --- /dev/null +++ b/src/main/java/lotto/domain/LottoResult.java @@ -0,0 +1,17 @@ +package lotto.domain; + +import lotto.domain.enumeration.Ranking; + +import java.util.Map; + +public class LottoResult { + private final Map winningInfo; + + public LottoResult(Map winningInfo) { + this.winningInfo = winningInfo; + } + + public Map getWinningInfo() { + return this.winningInfo; + } +} diff --git a/src/main/java/lotto/domain/Lottos.java b/src/main/java/lotto/domain/Lottos.java new file mode 100644 index 0000000000..fab3d49e72 --- /dev/null +++ b/src/main/java/lotto/domain/Lottos.java @@ -0,0 +1,32 @@ +package lotto.domain; + +import java.util.ArrayList; +import java.util.List; + +public class Lottos { + private List lottos; + private Integer manualLottoCount; + private Integer autoLottoCount; + + public Lottos(List manualLottos, List autoLottos) { + List lottos = new ArrayList<>(); + lottos.addAll(manualLottos); + lottos.addAll(autoLottos); + + this.lottos = lottos; + this.manualLottoCount = manualLottos.size(); + this.autoLottoCount = autoLottos.size(); + } + + public int getManualLottoCount() { + return manualLottoCount; + } + + public int getAutoLottoCount() { + return autoLottoCount; + } + + public List getLottos() { + return lottos; + } +} diff --git a/src/main/java/lotto/domain/WinningNumber.java b/src/main/java/lotto/domain/WinningNumber.java new file mode 100644 index 0000000000..f89bc3200b --- /dev/null +++ b/src/main/java/lotto/domain/WinningNumber.java @@ -0,0 +1,22 @@ +package lotto.domain; + +import lotto.domain.enumeration.NumberType; + +public class WinningNumber { + private final Integer number; + private final NumberType type; + + public WinningNumber(Integer number, NumberType type) { + this.number = number; + this.type = type; + } + + public Integer getNumber() { + return number; + } + + public boolean isBonus() { + return type == NumberType.BOUNS; + } + +} diff --git a/src/main/java/lotto/domain/enumeration/NumberType.java b/src/main/java/lotto/domain/enumeration/NumberType.java new file mode 100644 index 0000000000..a0c0254b52 --- /dev/null +++ b/src/main/java/lotto/domain/enumeration/NumberType.java @@ -0,0 +1,6 @@ +package lotto.domain.enumeration; + +public enum NumberType { + BOUNS, + NORMAL +} diff --git a/src/main/java/lotto/domain/enumeration/Ranking.java b/src/main/java/lotto/domain/enumeration/Ranking.java new file mode 100644 index 0000000000..a5b6acb155 --- /dev/null +++ b/src/main/java/lotto/domain/enumeration/Ranking.java @@ -0,0 +1,46 @@ +package lotto.domain.enumeration; + +import java.util.Arrays; + +public enum Ranking { + + FIRST(2_000_000_000L,6,false), + SECOND(30_000_000L, 5, true), + THIRD(1_500_000L, 5, false), + FORTH(50_000L, 4, false), + FIFTH(5_000L, 3, false), + NON_WINNER(0L, 0, false); + + private final Long prizeMoney; + private final int equalCount; + private final boolean containBonus; + private static final int CHECK_SECOND_OR_THIRD_CONDITION = 5; + + Ranking(Long prizeMoney, int equalCount, boolean containBonus) { + this.equalCount = equalCount; + this.prizeMoney = prizeMoney; + this.containBonus = containBonus; + } + + public static Ranking create(int equalCount, boolean isBonusContain) { + if (equalCount == CHECK_SECOND_OR_THIRD_CONDITION) { + return getSecondOrThird(isBonusContain); + } + + return Arrays.stream(Ranking.values()) + .filter(ranking -> ranking.equalCount == equalCount) + .findFirst() + .orElse(NON_WINNER); + } + + private static Ranking getSecondOrThird(boolean isBonusContain) { + if (isBonusContain){ + return Ranking.SECOND; + } + return Ranking.THIRD; + } + + public Long getPrizeMoney() { + return this.prizeMoney; + } +} \ No newline at end of file diff --git a/src/main/java/lotto/view/Input.java b/src/main/java/lotto/view/Input.java new file mode 100644 index 0000000000..25b3e886e6 --- /dev/null +++ b/src/main/java/lotto/view/Input.java @@ -0,0 +1,87 @@ +package lotto.view; + +import camp.nextstep.edu.missionutils.Console; + +import java.util.*; +import java.util.stream.Collectors; + +import static java.lang.Integer.parseInt; + +public class Input { + private static final String MONEY_REQUEST = "구입금액을 입력해 주세요."; + private static final String MANUAL_LOTTO_COUNT_REQUEST = "수동으로 구매할 로또 수를 입력해 주세요."; + private static final String INPUT_IS_NOT_NUMBER = "문자열이 아닌 숫자(정수)를 입력해주세요."; + private static final String DUPLICATE_LOTTO_NUMBERS = "로또 번호 중 중복된 번호가 있습니다."; + private static final int LOTTO_MIN_NUMBER = 1; + private static final int LOTTO_MAX_NUMBER = 45; + private static final String LOTTO_NUMBER_OUT_OF_RANGE = "로또 번호는 1부터 45사이의 숫자여야 합니다."; + private static final String BONUS_WINNING_NUMBER_REQUEST = "보너스 볼을 입력해 주세요."; + private static final String NORMAL_WINNING_NUMBERS_REQUEST = "지난 주 당첨 번호를 입력해 주세요."; + + public static int insertLottoMoney() { + System.out.println(MONEY_REQUEST); + String lottoMoney = Console.readLine(); + + return checkNumber(lottoMoney); + } + + public static int insertManualLottoCount() { + System.out.println(MANUAL_LOTTO_COUNT_REQUEST); + String manualLottoCount = Console.readLine(); + + return validateNumber(manualLottoCount); + } + + public static List insertLottoNumbers() { + String lottoNumbersInput = Console.readLine(); + List lottoNumbers = Arrays.asList(lottoNumbersInput.split("\\s*,\\s*")); + validateLottoNumbers(lottoNumbers); + + return lottoNumbers.stream() + .map(Integer::parseInt) + .collect(Collectors.toList()); + } + + public static List insertNormalWinningNumbers() { + System.out.println(NORMAL_WINNING_NUMBERS_REQUEST); + return insertLottoNumbers(); + } + + public static Integer insertBonusWinningNumber() { + System.out.println(BONUS_WINNING_NUMBER_REQUEST); + String lottoNumber = Console.readLine(); + + return validateNumber(lottoNumber); + } + + private static void validateLottoNumbers(List lottoNumbers) { + lottoNumbers.forEach(Input::validateNumber); + validateDuplicateLottoNumbers(lottoNumbers); + } + + private static void validateDuplicateLottoNumbers(List lottoNumbers) { + Set uniqueLottoNumbers = new HashSet<>(lottoNumbers); + if (uniqueLottoNumbers.size() != lottoNumbers.size()) { + throw new IllegalArgumentException(DUPLICATE_LOTTO_NUMBERS); + } + } + + private static int validateNumber(String input) { + return checkRange(checkNumber(input)); + } + + private static int checkNumber(String input) { + if (!input.chars().allMatch(Character::isDigit)) { + throw new IllegalArgumentException(INPUT_IS_NOT_NUMBER); + } + + return parseInt(input); + } + + private static int checkRange(int input) { + if (input < LOTTO_MIN_NUMBER || input > LOTTO_MAX_NUMBER) { + throw new IllegalArgumentException(LOTTO_NUMBER_OUT_OF_RANGE); + } + return input; + } +} diff --git a/src/main/java/lotto/view/Output.java b/src/main/java/lotto/view/Output.java new file mode 100644 index 0000000000..1aa38669f8 --- /dev/null +++ b/src/main/java/lotto/view/Output.java @@ -0,0 +1,78 @@ +package lotto.view; + +import lotto.domain.Lotto; +import lotto.domain.LottoMoney; +import lotto.domain.LottoResult; +import lotto.domain.Lottos; +import lotto.domain.enumeration.Ranking; + +import java.util.List; +import java.text.NumberFormat; +import java.util.Map; + +import static lotto.domain.LottoMachine.calculateProfit; +import static lotto.domain.enumeration.Ranking.*; + +public class Output { + private static final String MANUAL_LOTTO_NUMBERS_REQUEST = "수동으로 구매할 번호를 입력해 주세요."; + private static final String RESULT_ANNOUNCEMENT = "당첨 통계"; + private static final String RESULT_ANNOUNCEMENT_LINE = "---------"; + + private static final NumberFormat numberFormat = NumberFormat.getInstance(); + + public static void printErrorMessage(String errorMessage) { + System.out.println("[ERROR] " + errorMessage); + } + + public static void printInsertManualLottoNumbersRequest() { + System.out.println(MANUAL_LOTTO_NUMBERS_REQUEST); + } + + public static void printLottos(Lottos lottos) { + printLottosCount(lottos); + printLottoNumbers(lottos); + } + + private static void printLottosCount(Lottos lottos) { + int manualLottoCount = lottos.getManualLottoCount(); + int autoLottoCount = lottos.getAutoLottoCount(); + System.out.printf("수동으로 %d장, 자동으로 %d개를 구매했습니다.\n", manualLottoCount, autoLottoCount); + } + + private static void printLottoNumbers(Lottos lottos) { + List lottoList = lottos.getLottos(); + + lottoList.stream() + .map(Lotto::getNumbers) + .forEach(System.out::println); + } + + public static void printLottoResult(LottoResult lottoResult, LottoMoney money) { + printLottoResultStartFormat(); + printLottoWinningResult(lottoResult); + printProfit(lottoResult, money); + } + + private static void printProfit(LottoResult lottoResult, LottoMoney money) { + Double profit = calculateProfit(lottoResult, money); + System.out.printf("총 수익률은 %.2f입니다.", profit); + } + + private static void printLottoWinningResult(LottoResult lottoResult) { + String FIFTH_STATS = String.format("3개 일치 (%s원) - %s개", numberFormat.format(FIFTH.getPrizeMoney()), lottoResult.getWinningInfo().getOrDefault(FIFTH, 0)); + String FORTH_STATS = String.format("4개 일치 (%s원) - %s개", numberFormat.format(FORTH.getPrizeMoney()), lottoResult.getWinningInfo().getOrDefault(FORTH, 0)); + String THIRD_STATS = String.format("5개 일치 (%s원) - %s개", numberFormat.format(THIRD.getPrizeMoney()), lottoResult.getWinningInfo().getOrDefault(THIRD, 0)); + String SECOND_STATS = String.format("5개 일치, 보너스 볼 일치 (%s원) - %s개", numberFormat.format(SECOND.getPrizeMoney()), lottoResult.getWinningInfo().getOrDefault(SECOND, 0)); + String FIRST_STATS = String.format("6개 일치 (%s원) - %s개", numberFormat.format(FIRST.getPrizeMoney()), lottoResult.getWinningInfo().getOrDefault(FIRST, 0)); + System.out.println(FIFTH_STATS); + System.out.println(FORTH_STATS); + System.out.println(THIRD_STATS); + System.out.println(SECOND_STATS); + System.out.println(FIRST_STATS); + } + + private static void printLottoResultStartFormat() { + System.out.println(RESULT_ANNOUNCEMENT); + System.out.println(RESULT_ANNOUNCEMENT_LINE); + } +}