diff --git a/README.md b/README.md index 9594b6c872..1f9daa8a48 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,33 @@ 로또 미션 저장소 # [미션 리드미](https://github.com/talmood/private-mission-README/tree/main/%EB%AF%B8%EC%85%98%203%20-%20%EB%A1%9C%EB%98%90) + +# 미션 1 - 로또 +## 기능 요구 사항 +- 로또 구입 금액을 입력 받는다. + - 로또 구입 금액은 숫자여야 한다. + - 로또 한 장의 가격은 1000원이다. + - 최소 구입 금액은 1000원이다. + - 로또 구입 금액은 1000원 단위여야 한다. +- 로또 구입 금액만큼 로또를 발급한다. + - 로또 한 장의 가격은 1000원이다. +- 로또 1장은 6개의 랜덤 숫자를 가진다. + - 로또 숫자는 1부터 45까지의 숫자이다. + - 로또 숫자는 중복되지 않는다. + - 로또 숫자는 오름차순으로 정렬되어 있다. +- 지난주 당첨 번호를 입력 받는다. + - 지난주 당첨 번호는 1부터 45까지의 숫자이다. + - 지난주 당첨 번호는 중복되지 않는다. + - 지난주 당첨 번호는 오름차순으로 정렬되어 있다. +- 지난 주 보너스 볼을 입력 받는다. + - 보너스 볼은 1부터 45까지의 숫자이다. + - 보너스 볼은 지난주 당첨 번호와 중복되지 않는다. + - 보너스 볼은 오름차순으로 정렬되어 있다. +- 당첨 통계를 출력한다. + - 3개 일치, 4개 일치, 5개 일치, 5개 일치 + 보너스 볼 일치, 6개 일치의 당첨 통계를 출력한다. + - 당첨 통계는 로또 당첨 금액을 기준으로 한다. + - 당첨 통계는 로또 당첨 개수를 기준으로 한다. + - 당첨 통계는 로또 당첨 금액과 로또 당첨 개수를 출력한다. +- 총 수익률을 계산한다. + - 총 수익률은 (총 당첨 금액 - 로또 구입 금액) / 로또 구입 금액으로 계산한다. + - 총 수익률은 소수점 둘째 자리까지 출력한다. (소수점 셋째 자리에서 버림 처리한다.) \ No newline at end of file diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java new file mode 100644 index 0000000000..fabcca4dfa --- /dev/null +++ b/src/main/java/lotto/Application.java @@ -0,0 +1,10 @@ +package lotto; + +import lotto.controller.LottoMachine; + +public class Application { + + public static void main(String[] args) { + LottoMachine.run(); + } +} diff --git a/src/main/java/lotto/constrant/LottoConstant.java b/src/main/java/lotto/constrant/LottoConstant.java new file mode 100644 index 0000000000..33b33ae3e1 --- /dev/null +++ b/src/main/java/lotto/constrant/LottoConstant.java @@ -0,0 +1,10 @@ +package lotto.constrant; + +public class LottoConstant { + private LottoConstant() { + } + public static final int LOTTO_PRICE = 1000; + public static final int LOTTO_NUMBER_SIZE = 6; + public static final int MIN_LOTTO_NUMBER = 1; + public static final int MAX_LOTTO_NUMBER = 45; +} diff --git a/src/main/java/lotto/controller/LottoGenerator.java b/src/main/java/lotto/controller/LottoGenerator.java new file mode 100644 index 0000000000..a478306dc6 --- /dev/null +++ b/src/main/java/lotto/controller/LottoGenerator.java @@ -0,0 +1,46 @@ +package lotto.controller; + +import lotto.domain.LottoNumber; +import lotto.domain.LottoNumbers; +import lotto.domain.PurchasedLotto; + +import java.util.*; + +public class LottoGenerator { + + private static final int LOTTO_NUMBER_SIZE = 6; + + private final List candidateLottoNumbers; + + public LottoGenerator() { + this.candidateLottoNumbers = initializeCandidateNumbers(); + } + + private List initializeCandidateNumbers() { + final List lottoNumbers = new ArrayList<>(); + for (int i = 1; i <= 45; i++) { + lottoNumbers.add(i); + } + return lottoNumbers; + } + + public List generatePurchasedLottos(final int purchaseCount) { + final List purchasedLottos = new ArrayList<>(); + for (int i = 0; i < purchaseCount; i++) { + purchasedLottos.add(generatePurchasedLotto()); + } + return purchasedLottos; + } + + private PurchasedLotto generatePurchasedLotto() { + final List shuffledNumbers = new ArrayList<>(candidateLottoNumbers); + Collections.shuffle(shuffledNumbers); + + final List lottoNumbers = shuffledNumbers.stream() + .limit(LOTTO_NUMBER_SIZE) + .map(LottoNumber::from) + .toList(); + + return PurchasedLotto.from(LottoNumbers.from(lottoNumbers)); + } +} diff --git a/src/main/java/lotto/controller/LottoMachine.java b/src/main/java/lotto/controller/LottoMachine.java new file mode 100644 index 0000000000..7a7e15a362 --- /dev/null +++ b/src/main/java/lotto/controller/LottoMachine.java @@ -0,0 +1,54 @@ +package lotto.controller; + +import lotto.domain.PurchasedLotto; +import lotto.domain.WinningLotto; +import lotto.domain.prize.PrizeResult; +import lotto.request.LastWeekWinningLottoRequest; +import lotto.request.LottoPurchaseRequest; +import lotto.view.InputView; +import lotto.view.ResultView; + +import java.util.List; + +public class LottoMachine { + + private LottoMachine() { + } + + public static void run() { + final int purchaseCount = inputPurchaseAmount(); + printPurchaseAmount(purchaseCount); + final List purchasedLottos = generateLottoNumbers(purchaseCount); + + final WinningLotto winningLotto = inputLastWeekWinningNumbers(); + + printWinningStatistics(purchasedLottos, winningLotto); + } + + private static int inputPurchaseAmount() { + final LottoPurchaseRequest lottoPurchaseRequest = InputView.inputPurchaseAmount(); + return lottoPurchaseRequest.calculatePurchaseCount(); + } + + private static List generateLottoNumbers(final int purchaseCount) { + final LottoGenerator lottoGenerator = new LottoGenerator(); + final List purchasedLottos = lottoGenerator.generatePurchasedLottos(purchaseCount); + ResultView.printPurchasedLottos(purchasedLottos); + return purchasedLottos; + } + + private static WinningLotto inputLastWeekWinningNumbers() { + final LastWeekWinningLottoRequest lastWeekWinningLottoRequest = InputView.inputLastWeekWinningNumbers(); + return lastWeekWinningLottoRequest.generateWinningLotto(); + } + + private static void printPurchaseAmount(final int purchaseAmount) { + ResultView.printPurchaseAmount(purchaseAmount); + } + + private static void printWinningStatistics(final List purchasedLottos, final WinningLotto winningLotto) { + final PrizeResult prizeResult = PrizeResult.from(purchasedLottos, winningLotto); + ResultView.printWinningStatistics(prizeResult); + } + +} diff --git a/src/main/java/lotto/domain/LottoNumber.java b/src/main/java/lotto/domain/LottoNumber.java new file mode 100644 index 0000000000..157193c0b4 --- /dev/null +++ b/src/main/java/lotto/domain/LottoNumber.java @@ -0,0 +1,43 @@ +package lotto.domain; + +import java.util.Objects; + +import static lotto.constrant.LottoConstant.MAX_LOTTO_NUMBER; +import static lotto.constrant.LottoConstant.MIN_LOTTO_NUMBER; + +public class LottoNumber { + + private final int number; + + private LottoNumber(final int number) { + validate(number); + this.number = number; + } + + public static LottoNumber from(final int number) { + return new LottoNumber(number); + } + + private static void validate(final int number) { + if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) { + throw new IllegalArgumentException("로또 번호는 1부터 45까지의 숫자만 가능합니다."); + } + } + + public int getNumber() { + return number; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LottoNumber that = (LottoNumber) o; + return number == that.number; + } + + @Override + public int hashCode() { + return Objects.hash(number); + } +} diff --git a/src/main/java/lotto/domain/LottoNumbers.java b/src/main/java/lotto/domain/LottoNumbers.java new file mode 100644 index 0000000000..8ef3d28455 --- /dev/null +++ b/src/main/java/lotto/domain/LottoNumbers.java @@ -0,0 +1,48 @@ +package lotto.domain; + +import java.util.List; +import java.util.Set; + +import static lotto.constrant.LottoConstant.LOTTO_NUMBER_SIZE; + +public class LottoNumbers { + + private final List numbers; + + private LottoNumbers(final List numbers) { + validateSize(numbers); + validateDuplicate(numbers); + this.numbers = numbers; + } + + public static LottoNumbers from(final List numbers) { + return new LottoNumbers(numbers); + } + + private void validateSize(final List numbers) { + if (numbers.size() != LOTTO_NUMBER_SIZE) { + throw new IllegalArgumentException("로또 번호는 6개여야 합니다."); + } + } + + private void validateDuplicate(final List numbers) { + final Set lottoNumberSet = Set.copyOf(numbers); + if (lottoNumberSet.size() != LOTTO_NUMBER_SIZE) { + throw new IllegalArgumentException("로또 번호는 중복될 수 없습니다."); + } + } + + public List getNumbers() { + return List.copyOf(numbers); + } + + public int calculateMatchCounts(final List winningNumbers) { + return (int) numbers.stream() + .filter(winningNumbers::contains) + .count(); + } + + public boolean containsBonusNumber(final LottoNumber bonusNumber) { + return numbers.contains(bonusNumber); + } +} diff --git a/src/main/java/lotto/domain/PurchasedLotto.java b/src/main/java/lotto/domain/PurchasedLotto.java new file mode 100644 index 0000000000..98ceb04a50 --- /dev/null +++ b/src/main/java/lotto/domain/PurchasedLotto.java @@ -0,0 +1,27 @@ +package lotto.domain; + +import java.util.Objects; + +public class PurchasedLotto { + + private final LottoNumbers lottoNumbers; + + private PurchasedLotto(final LottoNumbers lottoNumbers) { + validate(lottoNumbers); + this.lottoNumbers = lottoNumbers; + } + + public static PurchasedLotto from(final LottoNumbers lottoNumbers) { + return new PurchasedLotto(lottoNumbers); + } + + private static void validate(final LottoNumbers lottoNumbers) { + if (Objects.isNull(lottoNumbers)) { + throw new IllegalArgumentException("lotto numbers must not be null"); + } + } + + public LottoNumbers getLottoNumbers() { + return lottoNumbers; + } +} diff --git a/src/main/java/lotto/domain/WinningLotto.java b/src/main/java/lotto/domain/WinningLotto.java new file mode 100644 index 0000000000..ff8069097c --- /dev/null +++ b/src/main/java/lotto/domain/WinningLotto.java @@ -0,0 +1,32 @@ +package lotto.domain; + +import java.util.List; + +public class WinningLotto { + private final LottoNumbers lottoNumbers; + private final LottoNumber bonusNumber; + + private WinningLotto(final LottoNumbers lottoNumbers, final LottoNumber bonusNumber) { + validate(lottoNumbers, bonusNumber); + this.lottoNumbers = lottoNumbers; + this.bonusNumber = bonusNumber; + } + + public static WinningLotto of(final LottoNumbers lottoNumbers, final LottoNumber bonusNumber) { + return new WinningLotto(lottoNumbers, bonusNumber); + } + + public static void validate(final LottoNumbers lottoNumbers, final LottoNumber bonusNumber) { + if (lottoNumbers.getNumbers().contains(bonusNumber)) { + throw new IllegalArgumentException("보너스 번호는 당첨 번호와 중복될 수 없습니다."); + } + } + + public List getLottoNumbers() { + return List.copyOf(lottoNumbers.getNumbers()); + } + + public LottoNumber getBonusNumber() { + return bonusNumber; + } +} diff --git a/src/main/java/lotto/domain/prize/Prize.java b/src/main/java/lotto/domain/prize/Prize.java new file mode 100644 index 0000000000..b7ff27e3ca --- /dev/null +++ b/src/main/java/lotto/domain/prize/Prize.java @@ -0,0 +1,40 @@ +package lotto.domain.prize; + +public enum Prize { + NONE(0, false, 0), + FIFTH(3, false, 5000), + FOURTH(4, false, 50000), + THIRD(5, false, 1500000), + SECOND(5, true, 30000000), + FIRST(6, false, 2000000000); + + private final int matchCount; + private final boolean matchBonus; + private final int money; + + Prize(final int matchCount, final boolean matchBonus, final int money) { + this.matchCount = matchCount; + this.matchBonus = matchBonus; + this.money = money; + } + + public static Prize of(final int matchCount, final boolean matchBonus) { + if (matchCount == SECOND.matchCount && matchBonus) { + return SECOND; + } + for (final Prize prize : values()) { + if (prize.matchCount == matchCount) { + return prize; + } + } + return NONE; + } + + public int getMatchCount() { + return matchCount; + } + + public int getMoney() { + return money; + } +} diff --git a/src/main/java/lotto/domain/prize/PrizeResult.java b/src/main/java/lotto/domain/prize/PrizeResult.java new file mode 100644 index 0000000000..1463dfd647 --- /dev/null +++ b/src/main/java/lotto/domain/prize/PrizeResult.java @@ -0,0 +1,65 @@ +package lotto.domain.prize; + +import lotto.constrant.LottoConstant; +import lotto.domain.LottoNumbers; +import lotto.domain.PurchasedLotto; +import lotto.domain.WinningLotto; + +import java.util.Arrays; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public class PrizeResult { + private final Map prizeCounts; + + private PrizeResult() { + prizeCounts = new EnumMap<>(Prize.class); + initializeCounts(); + } + + private void initializeCounts() { + Arrays.stream(Prize.values()) + .forEach(prize -> prizeCounts.put(prize, 0)); + } + + public static PrizeResult from(final List purchasedLottos, final WinningLotto winningLotto) { + final PrizeResult prizeResult = new PrizeResult(); + purchasedLottos.forEach(purchasedLotto -> prizeResult.calculateAndRecordPrize(purchasedLotto.getLottoNumbers(), winningLotto)); + return prizeResult; + } + + private void calculateAndRecordPrize(final LottoNumbers lottoNumbers, final WinningLotto winningLotto) { + final int matchCount = lottoNumbers.calculateMatchCounts(winningLotto.getLottoNumbers()); + final boolean isContainBonusNumber = lottoNumbers.containsBonusNumber(winningLotto.getBonusNumber()); + + final Prize prize = Prize.of(matchCount, isContainBonusNumber); + prizeCounts.put(prize, prizeCounts.get(prize) + 1); + } + + public Map getPrizeCounts() { + return prizeCounts; + } + + public double calculateEarningRate() { + final long totalPrize = calculateTotalPrize(); + final long totalPurchaseAmount = calculateTotalPurchaseAmount(); + final double earningRate = (double) totalPrize / totalPurchaseAmount; + return Math.floor(earningRate * 100) / 100.0; + } + + + private long calculateTotalPrize() { + return prizeCounts.entrySet().stream() + .mapToInt(entry -> entry.getKey().getMoney() * entry.getValue()) + .sum(); + } + + private long calculateTotalPurchaseAmount() { + final long totalPurchaseCount = prizeCounts.values().stream() + .mapToLong(Integer::longValue) + .sum(); + + return totalPurchaseCount * LottoConstant.LOTTO_PRICE; + } +} diff --git a/src/main/java/lotto/request/LastWeekWinningLottoRequest.java b/src/main/java/lotto/request/LastWeekWinningLottoRequest.java new file mode 100644 index 0000000000..45b04d216a --- /dev/null +++ b/src/main/java/lotto/request/LastWeekWinningLottoRequest.java @@ -0,0 +1,60 @@ +package lotto.request; + +import lotto.domain.LottoNumber; +import lotto.domain.LottoNumbers; +import lotto.domain.WinningLotto; +import lotto.utils.CommaNumberSplitter; +import lotto.utils.PatternUtil; + +import java.util.List; +import java.util.Objects; + +public class LastWeekWinningLottoRequest { + + private static final String INPUT_PATTERN = "^\\d+(\\s*,\\s*\\d+)*$"; + private static final String ERROR_LOTTO_INPUT_PATTERN = "[ERROR] 로또 번호는 ,로 구분된 숫자여야 합니다."; + + private static final String BONUS_INPUT_PATTERN = "^\\d+$"; + private static final String ERROR_BONUS_INPUT_PATTERN = "[ERROR] 보너스 볼은 숫자로 입력해야 합니다."; + + private final List lottoNumbers; + private final int bonusNumber; + + private LastWeekWinningLottoRequest(final List lottoNumbers, final int bonusNumber) { + this.lottoNumbers = lottoNumbers; + this.bonusNumber = bonusNumber; + } + + public static LastWeekWinningLottoRequest from(final String lastWeekWinningNumbers, final String bonusNumber) { + validateInputPattern(lastWeekWinningNumbers); + validateBonusInputPattern(bonusNumber); + return new LastWeekWinningLottoRequest(parseLottoNumbers(lastWeekWinningNumbers), Integer.parseInt(bonusNumber)); + } + + private static void validateInputPattern(final String lastWeekWinningNumbers) { + if (!PatternUtil.isMatched(lastWeekWinningNumbers, INPUT_PATTERN)) { + throw new IllegalArgumentException(ERROR_LOTTO_INPUT_PATTERN); + } + } + + private static void validateBonusInputPattern(final String bonusNumber) { + if (Objects.isNull(bonusNumber) || !bonusNumber.matches(BONUS_INPUT_PATTERN)) { + throw new IllegalArgumentException(ERROR_BONUS_INPUT_PATTERN); + } + } + + private static List parseLottoNumbers(final String lastWeekWinningNumbers) { + return CommaNumberSplitter.split(lastWeekWinningNumbers); + } + + + public WinningLotto generateWinningLotto() { + final LottoNumbers generatedLottoNumbers = LottoNumbers.from(this.lottoNumbers.stream() + .map(LottoNumber::from) + .toList()); + + final LottoNumber generatedBonusNumber = LottoNumber.from(this.bonusNumber); + + return WinningLotto.of(generatedLottoNumbers, generatedBonusNumber); + } +} diff --git a/src/main/java/lotto/request/LottoPurchaseRequest.java b/src/main/java/lotto/request/LottoPurchaseRequest.java new file mode 100644 index 0000000000..fe684578ac --- /dev/null +++ b/src/main/java/lotto/request/LottoPurchaseRequest.java @@ -0,0 +1,51 @@ +package lotto.request; + +import lotto.utils.PatternUtil; + +import static lotto.constrant.LottoConstant.LOTTO_PRICE; + +public class LottoPurchaseRequest { + private static final String PURCHASE_AMOUNT_REGEX = "^[0-9]*$"; + + private static final String ERROR_PURCHASE_AMOUNT_NOT_A_NUMBER = "[ERROR] 구입 금액은 숫자여야 합니다."; + private static final String ERROR_PURCHASE_AMOUNT_TOO_LOW = "[ERROR] 구입 금액은 %d원 이상이어야 합니다."; + private static final String ERROR_PURCHASE_AMOUNT_NOT_MULTIPLE_OF_LOTTO_PRICE = "[ERROR] 로또 구입 금액은 %d원 단위여야 합니다."; + + + private final int purchaseAmount; + + private LottoPurchaseRequest(final int purchaseAmount) { + this.purchaseAmount = purchaseAmount; + } + + public static LottoPurchaseRequest from(final String purchaseAmount) { + validate(purchaseAmount); + return new LottoPurchaseRequest(Integer.parseInt(purchaseAmount)); + } + + private static void validate (final String purchaseAmount) { + validateInputPattern(purchaseAmount); + validatePurchaseAmount(Integer.parseInt(purchaseAmount)); + } + + private static void validateInputPattern(final String purchaseAmount) { + if (!PatternUtil.isMatched(purchaseAmount, PURCHASE_AMOUNT_REGEX)) { + throw new IllegalArgumentException(ERROR_PURCHASE_AMOUNT_NOT_A_NUMBER); + } + } + + private static void validatePurchaseAmount(final int purchaseAmount) { + if (purchaseAmount < LOTTO_PRICE) { + throw new IllegalArgumentException(ERROR_PURCHASE_AMOUNT_TOO_LOW.formatted(LOTTO_PRICE)); + } + + if (purchaseAmount % LOTTO_PRICE != 0) { + throw new IllegalArgumentException(ERROR_PURCHASE_AMOUNT_NOT_MULTIPLE_OF_LOTTO_PRICE.formatted(LOTTO_PRICE)); + } + } + + public int calculatePurchaseCount() { + return purchaseAmount / LOTTO_PRICE; + } + +} diff --git a/src/main/java/lotto/utils/CommaNumberSplitter.java b/src/main/java/lotto/utils/CommaNumberSplitter.java new file mode 100644 index 0000000000..e85fb7fb12 --- /dev/null +++ b/src/main/java/lotto/utils/CommaNumberSplitter.java @@ -0,0 +1,22 @@ +package lotto.utils; + +import java.util.Arrays; +import java.util.List; + +public class CommaNumberSplitter { + private static final String DELIMITER = ","; + + private CommaNumberSplitter() { + } + + public static List split(final String str) { + if (StringUtil.isBlank(str)) { + throw new IllegalArgumentException("split string must not be blank"); + } + + return Arrays.stream(str.split(DELIMITER)) + .map(String::trim) + .map(Integer::parseInt) + .toList(); + } +} diff --git a/src/main/java/lotto/utils/Console.java b/src/main/java/lotto/utils/Console.java new file mode 100644 index 0000000000..5d88af5929 --- /dev/null +++ b/src/main/java/lotto/utils/Console.java @@ -0,0 +1,29 @@ +package lotto.utils; + +import java.util.Scanner; + +public class Console { + + private static Scanner scanner; + + private Console() { + } + + public static String readLine() { + return getInstance().nextLine(); + } + + public static void close() { + if (scanner != null) { + scanner.close(); + scanner = null; + } + } + + private static Scanner getInstance() { + if (scanner == null) { + scanner = new Scanner(System.in); + } + return scanner; + } +} diff --git a/src/main/java/lotto/utils/PatternUtil.java b/src/main/java/lotto/utils/PatternUtil.java new file mode 100644 index 0000000000..5065eaf261 --- /dev/null +++ b/src/main/java/lotto/utils/PatternUtil.java @@ -0,0 +1,16 @@ +package lotto.utils; + +import java.util.Objects; +import java.util.regex.Pattern; + +public abstract class PatternUtil { + private PatternUtil() { + } + + public static boolean isMatched(final String str, final String pattern) { + if (Objects.isNull(str) || Objects.isNull(pattern)) { + return false; + } + return Pattern.matches(pattern, str); + } +} diff --git a/src/main/java/lotto/utils/StringUtil.java b/src/main/java/lotto/utils/StringUtil.java new file mode 100644 index 0000000000..9d35270722 --- /dev/null +++ b/src/main/java/lotto/utils/StringUtil.java @@ -0,0 +1,13 @@ +package lotto.utils; + +import java.util.Objects; + +public abstract class StringUtil { + + private StringUtil() { + } + + public static boolean isBlank(final String str) { + return Objects.isNull(str) || str.trim().isEmpty(); + } +} diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java new file mode 100644 index 0000000000..16d1c21313 --- /dev/null +++ b/src/main/java/lotto/view/InputView.java @@ -0,0 +1,28 @@ +package lotto.view; + +import lotto.request.LastWeekWinningLottoRequest; +import lotto.request.LottoPurchaseRequest; +import lotto.utils.Console; + +public class InputView { + + private static final String INPUT_PURCHASE_AMOUNT_MESSAGE = "구입금액을 입력해 주세요."; + private static final String INPUT_LAST_WEEK_WINNING_NUMBERS_MESSAGE = "지난 주 당첨 번호를 입력해 주세요."; + private static final String INPUT_BONUS_BALL_MESSAGE = "보너스 볼을 입력해 주세요."; + + private InputView() { + } + + public static LottoPurchaseRequest inputPurchaseAmount() { + System.out.println(INPUT_PURCHASE_AMOUNT_MESSAGE); + return LottoPurchaseRequest.from(Console.readLine()); + } + + public static LastWeekWinningLottoRequest inputLastWeekWinningNumbers() { + System.out.println(INPUT_LAST_WEEK_WINNING_NUMBERS_MESSAGE); + final String lastWeekWinningNumbers = Console.readLine(); + System.out.println(INPUT_BONUS_BALL_MESSAGE); + final String bonusNumber = Console.readLine(); + return LastWeekWinningLottoRequest.from(lastWeekWinningNumbers, bonusNumber); + } +} diff --git a/src/main/java/lotto/view/ResultView.java b/src/main/java/lotto/view/ResultView.java new file mode 100644 index 0000000000..ff30bb15e8 --- /dev/null +++ b/src/main/java/lotto/view/ResultView.java @@ -0,0 +1,57 @@ +package lotto.view; + +import lotto.domain.LottoNumber; +import lotto.domain.PurchasedLotto; +import lotto.domain.prize.Prize; +import lotto.domain.prize.PrizeResult; +import lotto.view.printer.PrizePrinterFactory; +import lotto.view.printer.PrizePrinter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ResultView { + + private static final String PURCHASE_AMOUNT_MESSAGE = "%d개를 구매했습니다."; + + private static final String WINNING_STATISTICS_MESSAGE = "당첨 통계"; + private static final String WINNING_STATISTICS_SEPARATOR = "---------"; + private static final String TOTAL_EARNING_RATE = "총 수익률은 %.2f입니다."; + + private ResultView() { + } + + public static void printPurchaseAmount(final int purchaseAmount) { + System.out.println(String.format(PURCHASE_AMOUNT_MESSAGE, purchaseAmount)); + } + + public static void printPurchasedLottos(final List purchasedLottos) { + purchasedLottos.forEach(ResultView::printPurchasedLotto); + } + + private static void printPurchasedLotto(final PurchasedLotto purchasedLotto) { + final List lottoNumbers = new ArrayList<>(purchasedLotto.getLottoNumbers().getNumbers().stream() + .map(LottoNumber::getNumber) + .toList()); + + Collections.sort(lottoNumbers); + System.out.println(lottoNumbers); + } + + public static void printWinningStatistics(final PrizeResult prizeResult) { + System.out.println(WINNING_STATISTICS_MESSAGE); + System.out.println(WINNING_STATISTICS_SEPARATOR); + + prizeResult.getPrizeCounts().forEach((prize, count) -> { + if (prize == Prize.NONE) { + return; + } + + final PrizePrinter prizePrinter = PrizePrinterFactory.generatePrinter(prize); + prizePrinter.print(prize.getMatchCount(), prize.getMoney(), count); + }); + + System.out.println(String.format(TOTAL_EARNING_RATE, prizeResult.calculateEarningRate())); + } +} \ No newline at end of file diff --git a/src/main/java/lotto/view/printer/DefaultPrizePrinter.java b/src/main/java/lotto/view/printer/DefaultPrizePrinter.java new file mode 100644 index 0000000000..e90fee9069 --- /dev/null +++ b/src/main/java/lotto/view/printer/DefaultPrizePrinter.java @@ -0,0 +1,11 @@ +package lotto.view.printer; + +public class DefaultPrizePrinter implements PrizePrinter { + + private static final String WINNING_STATISTICS_FORMAT = "%d개 일치 (%d원)- %d개"; + + @Override + public void print(final int matchCount, final int money, final int count) { + System.out.println(String.format(WINNING_STATISTICS_FORMAT, matchCount, money, count)); + } +} \ No newline at end of file diff --git a/src/main/java/lotto/view/printer/PrizePrinter.java b/src/main/java/lotto/view/printer/PrizePrinter.java new file mode 100644 index 0000000000..fc5ac37d82 --- /dev/null +++ b/src/main/java/lotto/view/printer/PrizePrinter.java @@ -0,0 +1,5 @@ +package lotto.view.printer; + +public interface PrizePrinter { + void print(int matchCount, int money, int count); +} \ No newline at end of file diff --git a/src/main/java/lotto/view/printer/PrizePrinterFactory.java b/src/main/java/lotto/view/printer/PrizePrinterFactory.java new file mode 100644 index 0000000000..a864a2652f --- /dev/null +++ b/src/main/java/lotto/view/printer/PrizePrinterFactory.java @@ -0,0 +1,20 @@ +package lotto.view.printer; + +import lotto.domain.prize.Prize; + +import java.util.EnumMap; +import java.util.Map; + +public class PrizePrinterFactory { + private static final Map printers = new EnumMap<>(Prize.class); + + private PrizePrinterFactory() {} + + static { + printers.put(Prize.SECOND, new SecondPrizePrinter()); + } + + public static PrizePrinter generatePrinter(final Prize prize) { + return printers.getOrDefault(prize, new DefaultPrizePrinter()); + } +} \ No newline at end of file diff --git a/src/main/java/lotto/view/printer/SecondPrizePrinter.java b/src/main/java/lotto/view/printer/SecondPrizePrinter.java new file mode 100644 index 0000000000..c0c55c2324 --- /dev/null +++ b/src/main/java/lotto/view/printer/SecondPrizePrinter.java @@ -0,0 +1,11 @@ +package lotto.view.printer; + +public class SecondPrizePrinter implements PrizePrinter { + + private static final String BONUS_BALL_FORMAT = "%d개 일치, 보너스 볼 일치 (%d원) - %d개"; + + @Override + public void print(final int matchCount, final int money, final int count) { + System.out.println(String.format(BONUS_BALL_FORMAT, matchCount, money, count)); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/LottoNumberTest.java b/src/test/java/lotto/domain/LottoNumberTest.java new file mode 100644 index 0000000000..bfc9ccff0b --- /dev/null +++ b/src/test/java/lotto/domain/LottoNumberTest.java @@ -0,0 +1,31 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +class LottoNumberTest { + + @ParameterizedTest + @DisplayName("로또 번호 생성") + @ValueSource(ints = {1, 45}) + void createLottoNumber(int number) { + // when + LottoNumber lottoNumber = LottoNumber.from(number); + + // then + assertEquals(number, lottoNumber.getNumber()); + } + + @ParameterizedTest + @DisplayName("로또 번호 생성 실패") + @ValueSource(ints = {0, 46}) + void createLottoNumberFail(int number) { + // when & then + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> LottoNumber.from(number)); + assertEquals("로또 번호는 1부터 45까지의 숫자만 가능합니다.", exception.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/LottoNumbersTest.java b/src/test/java/lotto/domain/LottoNumbersTest.java new file mode 100644 index 0000000000..2008a5d79b --- /dev/null +++ b/src/test/java/lotto/domain/LottoNumbersTest.java @@ -0,0 +1,68 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class LottoNumbersTest { + + @Test + @DisplayName("로또 번호 생성") + void createLottoNumbers() { + // given + List numbers = Arrays.asList( + LottoNumber.from(1), + LottoNumber.from(2), + LottoNumber.from(3), + LottoNumber.from(4), + LottoNumber.from(5), + LottoNumber.from(6) + ); + + // when + LottoNumbers lottoNumbers = LottoNumbers.from(numbers); + + // then + assertEquals(numbers, lottoNumbers.getNumbers()); + } + + @Test + @DisplayName("로또 번호 생성 실패: 로또 번호가 6개가 아닌 경우") + void createLottoNumbersFailSize() { + // given + List numbers = Arrays.asList( + LottoNumber.from(1), + LottoNumber.from(2), + LottoNumber.from(3), + LottoNumber.from(4), + LottoNumber.from(5) + ); + + // when & then + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> LottoNumbers.from(numbers)); + assertEquals("로또 번호는 6개여야 합니다.", exception.getMessage()); + } + + @Test + @DisplayName("로또 번호 생성 실패: 로또 번호가 중복되는 경우") + void createLottoNumbersFailDuplicate() { + // given + List numbers = Arrays.asList( + LottoNumber.from(1), + LottoNumber.from(2), + LottoNumber.from(3), + LottoNumber.from(4), + LottoNumber.from(5), + LottoNumber.from(5) + ); + + // when & then + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> LottoNumbers.from(numbers)); + assertEquals("로또 번호는 중복될 수 없습니다.", exception.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/PurchasedLottoTest.java b/src/test/java/lotto/domain/PurchasedLottoTest.java new file mode 100644 index 0000000000..8d1892671d --- /dev/null +++ b/src/test/java/lotto/domain/PurchasedLottoTest.java @@ -0,0 +1,44 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class PurchasedLottoTest { + + @Test + @DisplayName("로또 번호 생성") + void createPurchasedLotto() { + // given + List numbers = Arrays.asList( + LottoNumber.from(1), + LottoNumber.from(2), + LottoNumber.from(3), + LottoNumber.from(4), + LottoNumber.from(5), + LottoNumber.from(6) + ); + LottoNumbers lottoNumbers = LottoNumbers.from(numbers); + + // when + PurchasedLotto purchasedLotto = PurchasedLotto.from(lottoNumbers); + + // then + assertEquals(lottoNumbers, purchasedLotto.getLottoNumbers()); + } + + @Test + @DisplayName("로또 번호 생성 실패: 로또 번호가 null인 경우") + void createPurchasedLottoFailNull() { + // given + LottoNumbers lottoNumbers = null; + + // when & then + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> PurchasedLotto.from(lottoNumbers)); + assertEquals("lotto numbers must not be null", exception.getMessage()); + } +} \ No newline at end of file