diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..361b135a982 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,20 @@ +기능 구현 목록 + +## 기능 구현 목록 + +기능 목록 + +- 로또를 구매 한다 + - 구입 금액을 입력 + - 음수, 0, 또는 1,000으로 나누어 떨어지지 않는 값을 입력하면 예외 발생 + - 구매한 로또를 출력함 +- 당첨 번호를 입력한다. + - 당첨 번호는 6자리를, 보너스 번호는 1자리를 입력 + - 당첨 번호가 서로 중복될 경우 예외 발생 + - 보너스 번호가 당첨 번호와 중복될 경우 예외 발생 + - 로또 숫자들이 정해진 범위 (1 ~ 45)를 벗어나면 예외 발생 +- 결과를 출력한다. + - 당첨 통계 + - n개 일치 (%d원) - %d개 + - 수익률 + - 총 수익률은 %f원 입니다. \ No newline at end of file diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922ba44..35042a5e789 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -2,6 +2,7 @@ public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + LottoController lottoController = new LottoController(); + lottoController.run(); } } diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java deleted file mode 100644 index 519793d1f73..00000000000 --- 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/LottoController.java b/src/main/java/lotto/LottoController.java new file mode 100644 index 00000000000..06a8e352c55 --- /dev/null +++ b/src/main/java/lotto/LottoController.java @@ -0,0 +1,84 @@ +package lotto; + +import java.math.BigDecimal; +import java.util.List; +import lotto.domain.lotto.LottoService; +import lotto.domain.lotto.entity.Lotto; +import lotto.domain.lotto.entity.LottoAnswer; +import lotto.domain.lotto.entity.LottoResultCount; +import lotto.domain.lotto.entity.Lottos; +import lotto.domain.lotto.generator.RandomLottoGenerator; +import lotto.domain.lotto.money.Money; +import lotto.exception.RetryExceptionHandler; +import lotto.view.InputView; +import lotto.view.OutputView; + +public class LottoController { + public static final BigDecimal PERCENT_DECIMAL = new BigDecimal(100); + private final InputView inputView = new InputView(); + private final OutputView outputView = new OutputView(); + private final RetryExceptionHandler handler = new RetryExceptionHandler(); + private final LottoService lottoService = new LottoService(new RandomLottoGenerator()); + + void run() { + //로또 구입 + Money purchaseMoney = getPurchaseMoney(); + Lottos lottos = purchaseLotto(purchaseMoney); + printPurchasedLotto(lottos); + + //정답 생성 + LottoAnswer answer = getLottoAnswer(); + + //결과 출력 + LottoResultCount results = lottos.getResults(answer); + printResult(results); + printRevenu(purchaseMoney, results); + } + + private void printRevenu(Money purchaseMoney, LottoResultCount results) { + BigDecimal initMoney = purchaseMoney.toBigDecimal(); + BigDecimal totalPrize = results.getTotalPrize(); + outputView.printRevenue(totalPrize.divide(initMoney).multiply(PERCENT_DECIMAL)); + } + + private void printResult(LottoResultCount results) { + outputView.printResults(results); + } + + private Money getPurchaseMoney() { + return handler.get(() -> { + int purchaseMoney = inputView.getPurchaseMoney(); + return Money.nonZeroMoney(purchaseMoney); + }); + + } + + private Lottos purchaseLotto(Money purchaseMoney) { + return lottoService.purchaseLottos(purchaseMoney); + } + + private LottoAnswer getLottoAnswer() { + Lotto lotto = getLottoUsingInput(); + return generateLottoAnswer(lotto); + } + + private LottoAnswer generateLottoAnswer(Lotto lotto) { + return handler.get(() -> { + int bonusNumber = inputView.getBonusNumber(); + return new LottoAnswer(lotto, bonusNumber); + } + ); + } + + private Lotto getLottoUsingInput() { + return handler.get(() -> { + List lottoNumbers = inputView.getLottoNumbers(); + return new Lotto(lottoNumbers); + } + ); + } + + private void printPurchasedLotto(Lottos lottos) { + outputView.printPurchasedLotto(lottos); + } +} diff --git a/src/main/java/lotto/domain/lotto/LottoService.java b/src/main/java/lotto/domain/lotto/LottoService.java new file mode 100644 index 00000000000..e7f492b0d60 --- /dev/null +++ b/src/main/java/lotto/domain/lotto/LottoService.java @@ -0,0 +1,33 @@ +package lotto.domain.lotto; + +import java.util.ArrayList; +import java.util.List; +import lotto.domain.lotto.entity.Lotto; +import lotto.domain.lotto.entity.Lottos; +import lotto.domain.lotto.generator.LottoGenerator; +import lotto.domain.lotto.money.Cash; +import lotto.domain.lotto.money.Money; + +public class LottoService { + + public static final int LOTTO_PRICE = 1000; + private final LottoGenerator lottoGenerator; + + public LottoService(LottoGenerator lottoGenerator) { + this.lottoGenerator = lottoGenerator; + } + + public Lottos purchaseLottos(Money money) { + List lottos = new ArrayList<>(); + Cash cash = money.toCash(); + while (cash.canPurchase(LOTTO_PRICE)) { + lottos.add(generateLotto()); + cash.spend(LOTTO_PRICE); + } + return new Lottos(lottos); + } + + public Lotto generateLotto() { + return lottoGenerator.generate(); + } +} diff --git a/src/main/java/lotto/domain/lotto/entity/Lotto.java b/src/main/java/lotto/domain/lotto/entity/Lotto.java new file mode 100644 index 00000000000..5a3098b1efc --- /dev/null +++ b/src/main/java/lotto/domain/lotto/entity/Lotto.java @@ -0,0 +1,62 @@ +package lotto.domain.lotto.entity; + +import java.util.List; +import lotto.exception.LottoException; + +public class Lotto { + private final List numbers; + + public Lotto(List numbers) { + validate(numbers); + this.numbers = numbers.stream() + .map(LottoNumber::new) + .sorted() + .toList(); + } + + private void validate(List numbers) { + validateSize(numbers); + validateDuplication(numbers); + } + + private void validateDuplication(List numbers) { + int distinctSize = (int) numbers.stream() + .distinct() + .count(); + if (distinctSize != numbers.size()) { + throw LottoException.LOTTO_SIZE_EXCEPTION.makeException(); + } + } + + private static void validateSize(List numbers) { + if (numbers.size() != 6) { + throw new IllegalArgumentException(); + } + } + + // TODO: 추가 기능 구현 + public int getSameNumberCount(Lotto lotto) { + return (int) this.numbers.stream() + .filter(lotto.numbers::contains) + .count(); + } + + public boolean hasNumber(LottoNumber number) { + return this.numbers.contains(number); + } + + public List getNumbers() { + return numbers.stream() + .map(LottoNumber::getNumber) + .toList(); + } + + + @Override + public String toString() { + return numbers.stream() + .map(LottoNumber::getNumber) + .toList() + .toString(); + } +} diff --git a/src/main/java/lotto/domain/lotto/entity/LottoAnswer.java b/src/main/java/lotto/domain/lotto/entity/LottoAnswer.java new file mode 100644 index 00000000000..15e904a5024 --- /dev/null +++ b/src/main/java/lotto/domain/lotto/entity/LottoAnswer.java @@ -0,0 +1,28 @@ +package lotto.domain.lotto.entity; + +import lotto.exception.LottoException; + +public class LottoAnswer { + private final Lotto lotto; + private final LottoNumber bonusNumber; + + public LottoAnswer(Lotto lotto, int bonusNumber) { + this.lotto = lotto; + this.bonusNumber = new LottoNumber(bonusNumber); + validate(); + } + + private void validate() { + if (this.lotto.hasNumber(this.bonusNumber)) { + throw LottoException.LOTTO_DUPLICATED_BONUS_NUMBER.makeException(); + } + } + + public int getSameNumberCount(Lotto lotto) { + return lotto.getSameNumberCount(this.lotto); + } + + public boolean matchesBonusNumber(Lotto lotto) { + return lotto.hasNumber(this.bonusNumber); + } +} diff --git a/src/main/java/lotto/domain/lotto/entity/LottoNumber.java b/src/main/java/lotto/domain/lotto/entity/LottoNumber.java new file mode 100644 index 00000000000..fc9a4eca909 --- /dev/null +++ b/src/main/java/lotto/domain/lotto/entity/LottoNumber.java @@ -0,0 +1,48 @@ +package lotto.domain.lotto.entity; + +import java.util.Objects; +import lotto.exception.LottoException; + +public class LottoNumber implements Comparable { + + public static final int LOTTO_RANGE_MAX = 45; + public static final int LOTTO_RANGE_MIN = 1; + private final int number; + + public LottoNumber(int number) { + validateNumberRange(number); + this.number = number; + } + + private void validateNumberRange(int number) { + if (number < LOTTO_RANGE_MIN || number > LOTTO_RANGE_MAX) { + throw LottoException.LOTTO_NUMBER_OUT_RANGE.makeException(); + } + } + + @Override + public int compareTo(LottoNumber o) { + return this.number - o.number; + } + + 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/lotto/entity/LottoResult.java b/src/main/java/lotto/domain/lotto/entity/LottoResult.java new file mode 100644 index 00000000000..7472d9df0c7 --- /dev/null +++ b/src/main/java/lotto/domain/lotto/entity/LottoResult.java @@ -0,0 +1,71 @@ +package lotto.domain.lotto.entity; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.function.BiPredicate; + +public enum LottoResult { + LOSE(new BigDecimal(0), 0, MatchBonusNumber.IGNORE), + FIFTH(new BigDecimal(5_000), 3, MatchBonusNumber.IGNORE), + FOURTH(new BigDecimal(50_000), 4, MatchBonusNumber.IGNORE), + THIRD(new BigDecimal(1_500_000), 5, MatchBonusNumber.NOT_MATCH), + SECOND(new BigDecimal(30_000_000), 5, MatchBonusNumber.MATCH), + FIRST(new BigDecimal(2_000_000_000), 6, MatchBonusNumber.IGNORE), + ; + + private enum MatchBonusNumber { + MATCH(LottoAnswer::matchesBonusNumber), + NOT_MATCH(((lottoAnswer, lotto) -> !lottoAnswer.matchesBonusNumber(lotto))), + IGNORE((lottoAnswer, lotto) -> true), + ; + + private final BiPredicate matchBonusNumber; + + MatchBonusNumber(BiPredicate matchBonusNumber) { + this.matchBonusNumber = matchBonusNumber; + } + + public boolean checkBonusNumber(LottoAnswer lottoAnswer, Lotto lotto) { + return this.matchBonusNumber.test(lottoAnswer, lotto); + } + } + + private final BigDecimal prize; + private final int sameCount; + private final MatchBonusNumber matchBonusNumber; + + LottoResult(BigDecimal prize, int sameCount, MatchBonusNumber matchBonusNumber) { + this.prize = prize; + this.sameCount = sameCount; + this.matchBonusNumber = matchBonusNumber; + } + + public static LottoResult getResult(LottoAnswer lottoAnswer, Lotto lotto) { + return Arrays.stream(LottoResult.values()) + .filter(result -> result.matchesSameNumberCount(lottoAnswer, lotto)) + .filter(result -> result.checkBonusNumber(lottoAnswer, lotto)) + .findFirst() + .orElse(LOSE); + } + + private boolean matchesSameNumberCount(LottoAnswer lottoAnswer, Lotto lotto) { + int sameNumberCount = lottoAnswer.getSameNumberCount(lotto); + return this.sameCount == sameNumberCount; + } + + private boolean checkBonusNumber(LottoAnswer lottoAnswer, Lotto lotto) { + return this.matchBonusNumber.checkBonusNumber(lottoAnswer, lotto); + } + + public BigDecimal getPrize() { + return prize; + } + + public BigDecimal getTotalPrize(int count) { + return this.prize.multiply(new BigDecimal(count)); + } + + public int getSameCount() { + return sameCount; + } +} diff --git a/src/main/java/lotto/domain/lotto/entity/LottoResultCount.java b/src/main/java/lotto/domain/lotto/entity/LottoResultCount.java new file mode 100644 index 00000000000..fc12827d0f7 --- /dev/null +++ b/src/main/java/lotto/domain/lotto/entity/LottoResultCount.java @@ -0,0 +1,30 @@ +package lotto.domain.lotto.entity; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class LottoResultCount { + private final Map lottoResultCount; + + public LottoResultCount(Map lottoResultCount) { + this.lottoResultCount = lottoResultCount; + } + + public Map getCounts() { + return Collections.unmodifiableMap(this.lottoResultCount); + } + + + public BigDecimal getTotalPrize() { + BigDecimal result = BigDecimal.ZERO; + List list = this.lottoResultCount.entrySet().stream() + .map(entry -> entry.getKey().getTotalPrize(entry.getValue())) + .toList(); + for (BigDecimal decimal : list) { + result = result.add(decimal); + } + return result; + } +} diff --git a/src/main/java/lotto/domain/lotto/entity/Lottos.java b/src/main/java/lotto/domain/lotto/entity/Lottos.java new file mode 100644 index 00000000000..fa86d286eb0 --- /dev/null +++ b/src/main/java/lotto/domain/lotto/entity/Lottos.java @@ -0,0 +1,43 @@ +package lotto.domain.lotto.entity; + +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; + +public class Lottos { + private final List lottos; + + public Lottos(List lottos) { + this.lottos = lottos; + } + + public int getSize() { + return this.lottos.size(); + } + + public List getLottos() { + return Collections.unmodifiableList(this.lottos); + } + + public LottoResultCount getResults(LottoAnswer lottoAnswer) { + EnumMap lottoResults = initResults(); + this.lottos.stream() + .map(lotto -> LottoResult.getResult(lottoAnswer, lotto)) + .filter(result -> result != LottoResult.LOSE) + .forEach(result -> putInLottoResults(lottoResults, result)); + return new LottoResultCount(lottoResults); + } + + private static EnumMap initResults() { + EnumMap results = new EnumMap<>(LottoResult.class); + Arrays.stream(LottoResult.values()) + .filter(result -> result != LottoResult.LOSE) + .forEach(result -> results.put(result, 0)); + return results; + } + + private static void putInLottoResults(EnumMap lottoResults, LottoResult result) { + lottoResults.put(result, lottoResults.get(result) + 1); + } +} diff --git a/src/main/java/lotto/domain/lotto/generator/LottoGenerator.java b/src/main/java/lotto/domain/lotto/generator/LottoGenerator.java new file mode 100644 index 00000000000..2ba52ddc13c --- /dev/null +++ b/src/main/java/lotto/domain/lotto/generator/LottoGenerator.java @@ -0,0 +1,17 @@ +package lotto.domain.lotto.generator; + +import java.util.List; +import lotto.domain.lotto.entity.Lotto; + +public abstract class LottoGenerator { + protected static final int LOTTO_NUMBER_START = 1; + protected static final int LOTTO_NUMBER_END = 45; + protected static final int LOTTO_SIZE = 6; + + protected abstract List pickLottoNumbers(); + + public Lotto generate() { + List lottoNumbers = pickLottoNumbers(); + return new Lotto(lottoNumbers); + } +} diff --git a/src/main/java/lotto/domain/lotto/generator/RandomLottoGenerator.java b/src/main/java/lotto/domain/lotto/generator/RandomLottoGenerator.java new file mode 100644 index 00000000000..b31e01e71da --- /dev/null +++ b/src/main/java/lotto/domain/lotto/generator/RandomLottoGenerator.java @@ -0,0 +1,11 @@ +package lotto.domain.lotto.generator; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.List; + +public class RandomLottoGenerator extends LottoGenerator { + @Override + protected List pickLottoNumbers() { + return Randoms.pickUniqueNumbersInRange(LOTTO_NUMBER_START, LOTTO_NUMBER_END, LOTTO_SIZE); + } +} diff --git a/src/main/java/lotto/domain/lotto/money/Cash.java b/src/main/java/lotto/domain/lotto/money/Cash.java new file mode 100644 index 00000000000..40bbc159ff8 --- /dev/null +++ b/src/main/java/lotto/domain/lotto/money/Cash.java @@ -0,0 +1,29 @@ +package lotto.domain.lotto.money; + +import lotto.exception.LottoException; + +public class Cash { + private int cash; + + public Cash(int cash) { + validateCash(cash); + this.cash = cash; + } + + private void validateCash(int cash) { + if (cash < 0) { + throw LottoException.MONEY_INVALID_VALUE.makeException(); + } + } + + public boolean canPurchase(int price) { + return this.cash >= price; + } + + public void spend(int price) { + if (this.cash < price) { + throw LottoException.CANT_SPEND_MONEY.makeException(); + } + this.cash -= price; + } +} diff --git a/src/main/java/lotto/domain/lotto/money/Money.java b/src/main/java/lotto/domain/lotto/money/Money.java new file mode 100644 index 00000000000..fd81507d6e6 --- /dev/null +++ b/src/main/java/lotto/domain/lotto/money/Money.java @@ -0,0 +1,41 @@ +package lotto.domain.lotto.money; + +import java.math.BigDecimal; +import lotto.exception.LottoException; + +public class Money { + private final int money; + + public Money(int money) { + validateMoney(money); + this.money = money; + } + + public static Money nonZeroMoney(int money) { + validateNonzero(money); + return new Money(money); + } + + private static void validateNonzero(int money) { + if (money == 0) { + throw LottoException.MONEY_INVALID_VALUE.makeException(); + } + } + + private void validateMoney(int money) { + if (money < 0) { + throw LottoException.MONEY_INVALID_VALUE.makeException(); + } + if (money % 1_000 != 0) { + throw LottoException.MONEY_INVALID_VALUE.makeException(); + } + } + + public Cash toCash() { + return new Cash(this.money); + } + + public BigDecimal toBigDecimal() { + return new BigDecimal(this.money); + } +} diff --git a/src/main/java/lotto/exception/LottoException.java b/src/main/java/lotto/exception/LottoException.java new file mode 100644 index 00000000000..8b7e8252602 --- /dev/null +++ b/src/main/java/lotto/exception/LottoException.java @@ -0,0 +1,29 @@ +package lotto.exception; + +public enum LottoException { + LOTTO_DUPLICATED_BONUS_NUMBER("보너스 번호가 중복 되었습니다"), + LOTTO_NUMBER_OUT_RANGE("로또 숫자의 범위가 1~45의 범위를 벗어났습니다"), + LOTTO_SIZE_EXCEPTION("로또 숫자의 개수가 6개가 아닙니다."), + + MONEY_INVALID_VALUE("잘못된 값의 금액입니다."), + CANT_SPEND_MONEY("돈을 사용할 수 없습니다"), + + INPUT_NUMBER_FORMAT("숫자를 입력해 주세요"), + INPUT_INVALID_FORMAT("잘못된 형식의 입력입니다."), + ; + + private static final String PREFIX = "[ERROR] "; + private final String message; + + LottoException(String message) { + this.message = message; + } + + public String getMessage() { + return PREFIX + message; + } + + public IllegalArgumentException makeException() { + return new IllegalArgumentException(getMessage()); + } +} diff --git a/src/main/java/lotto/exception/RetryExceptionHandler.java b/src/main/java/lotto/exception/RetryExceptionHandler.java new file mode 100644 index 00000000000..9863e441003 --- /dev/null +++ b/src/main/java/lotto/exception/RetryExceptionHandler.java @@ -0,0 +1,20 @@ +package lotto.exception; + +import java.util.function.Supplier; +import lotto.view.io.Printer; + +public class RetryExceptionHandler { + private final Printer printer = new Printer(); + + public T get(Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + printer.printMessage(e.getMessage()); + } finally { + printer.printMessage(""); + } + } + } +} diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java new file mode 100644 index 00000000000..6f656219dff --- /dev/null +++ b/src/main/java/lotto/view/InputView.java @@ -0,0 +1,26 @@ +package lotto.view; + +import java.util.List; +import lotto.view.io.Printer; +import lotto.view.io.Reader; + +public class InputView { + public static final String MESSAGE_PURCHASE_MONEY = "구입금액을 입력해 주세요."; + private final Reader reader = new Reader(); + private final Printer printer = new Printer(); + + public int getPurchaseMoney() { + printer.printMessage(MESSAGE_PURCHASE_MONEY); + return reader.getInteger(); + } + + public List getLottoNumbers() { + printer.printMessage("당첨 번호를 입력해 주세요."); + return reader.getIntegers(","); + } + + public int getBonusNumber() { + printer.printMessage("보너스 번호를 입력해 주세요."); + return reader.getInteger(); + } +} diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java new file mode 100644 index 00000000000..ffde341fa89 --- /dev/null +++ b/src/main/java/lotto/view/OutputView.java @@ -0,0 +1,46 @@ +package lotto.view; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import lotto.domain.lotto.entity.Lotto; +import lotto.domain.lotto.entity.LottoResultCount; +import lotto.domain.lotto.entity.Lottos; +import lotto.view.dto.LottoResultDTO; +import lotto.view.io.Printer; + +public class OutputView { + public static final String FORMAT_REVENUE = "총 수익률은 %s%%입니다."; + public static final String FORMAT_PURCHASE = "%d개를 구매했습니다."; + public static final String FORMAT_LOTTO_NUMBER = "[%d, %d, %d, %d, %d, %d]"; + private static final DecimalFormat DECIMAL_FORMAT_MONEY = new DecimalFormat("###,##0"); + private static final DecimalFormat DECIMAL_FORMAT_REVENUE = new DecimalFormat("###,##0.0"); + public static final String TITLE_RESULT = "당첨 통계"; + public static final String MESSAGE_SEPERATOR = "---"; + private final Printer printer = new Printer(); + + public void printPurchasedLotto(Lottos lottos) { + printer.printMessageUsingFormat(FORMAT_PURCHASE, lottos.getSize()); + + lottos.getLottos().stream() + .map(Lotto::getNumbers) + .forEach(list -> + printer.printListUsingFormat(FORMAT_LOTTO_NUMBER, list)); + } + + public void printResults(LottoResultCount results) { + printer.printMessage(TITLE_RESULT); + printer.printMessage(MESSAGE_SEPERATOR); + results.getCounts().forEach((result, value) -> { + LottoResultDTO resultDTO = LottoResultDTO.from(result); + printer.printMessageUsingFormat( + resultDTO.getDescriptionFormat() + " - %d개", + resultDTO.getMatchingCount(), + DECIMAL_FORMAT_MONEY.format(resultDTO.getPrize()), + value); + }); + } + + public void printRevenue(BigDecimal revenue) { + printer.printMessageUsingFormat(FORMAT_REVENUE, DECIMAL_FORMAT_REVENUE.format(revenue)); + } +} diff --git a/src/main/java/lotto/view/dto/LottoResultDTO.java b/src/main/java/lotto/view/dto/LottoResultDTO.java new file mode 100644 index 00000000000..9a5755cb617 --- /dev/null +++ b/src/main/java/lotto/view/dto/LottoResultDTO.java @@ -0,0 +1,42 @@ +package lotto.view.dto; + +import lotto.domain.lotto.entity.LottoResult; + +public class LottoResultDTO { + private static final String MATCH_COUNT_FORMAT = "%d개 일치"; + private static final String BONUS_BALL_MESSAGE = ", 보너스 볼 일치"; + private static final String PRICE_MESSAGE = " (%s원)"; + private final String descriptionFormat; + private final int prize; + private final int matchingCount; + + private LottoResultDTO(String descriptionFormat, int prize, int matchingCount) { + this.descriptionFormat = descriptionFormat; + this.prize = prize; + this.matchingCount = matchingCount; + } + + public static LottoResultDTO from(LottoResult result) { + if (result.equals(LottoResult.SECOND)) { + return new LottoResultDTO(MATCH_COUNT_FORMAT + BONUS_BALL_MESSAGE + PRICE_MESSAGE, + result.getPrize().intValue(), + result.getSameCount()); + } + + return new LottoResultDTO(MATCH_COUNT_FORMAT + PRICE_MESSAGE, + result.getPrize().intValue(), + result.getSameCount()); + } + + public String getDescriptionFormat() { + return descriptionFormat; + } + + public int getPrize() { + return prize; + } + + public int getMatchingCount() { + return matchingCount; + } +} diff --git a/src/main/java/lotto/view/io/Printer.java b/src/main/java/lotto/view/io/Printer.java new file mode 100644 index 00000000000..9faa9d9aa45 --- /dev/null +++ b/src/main/java/lotto/view/io/Printer.java @@ -0,0 +1,19 @@ +package lotto.view.io; + +import java.util.List; + +public class Printer { + public void printMessage(String message) { + System.out.println(message); + } + + public void printMessageUsingFormat(String format, Object... args) { + System.out.printf(format, args); + System.out.println(); + } + + public void printListUsingFormat(String format, List argsList) { + Object[] args = argsList.stream().toArray(); + printMessageUsingFormat(format, args); + } +} diff --git a/src/main/java/lotto/view/io/Reader.java b/src/main/java/lotto/view/io/Reader.java new file mode 100644 index 00000000000..e81164d09ff --- /dev/null +++ b/src/main/java/lotto/view/io/Reader.java @@ -0,0 +1,36 @@ +package lotto.view.io; + +import camp.nextstep.edu.missionutils.Console; +import java.util.Arrays; +import java.util.List; +import lotto.exception.LottoException; + +public class Reader { + public int getInteger() { + String input = Console.readLine(); + return parseInt(input); + } + + public List getIntegers(String delimiter) { + String input = Console.readLine(); + validateNotEndDelimiter(input, delimiter); + return Arrays.stream(input.split(delimiter)) + .map(this::parseInt) + .toList(); + } + + private void validateNotEndDelimiter(String input, String delimiter) { + if (input.substring(input.length() - 1).equals(delimiter)) { + throw LottoException.INPUT_INVALID_FORMAT.makeException(); + } + } + + private int parseInt(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw LottoException.INPUT_NUMBER_FORMAT.makeException(); + } + } + +} diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 9f5dfe7eb83..4cfb8059a5f 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/LottoTest.java @@ -1,11 +1,15 @@ package lotto; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.stream.Stream; +import lotto.domain.lotto.entity.Lotto; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; class LottoTest { @DisplayName("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.") @@ -24,4 +28,18 @@ void createLottoByDuplicatedNumber() { } // 아래에 추가 테스트 작성 가능 + @DisplayName("[EXCEPTION] 로또 숫자의 범위를 넘어가는 숫자가 사용되면 예외가 발생한다.") + @ParameterizedTest(name = "{0}의 숫자 리스트를 사용하면 예외가 발생") + @MethodSource("outRangeLottoNumbers") + void 숫자_범위_테스트(List integers) { + Assertions.assertThatIllegalArgumentException() + .isThrownBy(() -> new Lotto(integers)); + } + + static Stream> outRangeLottoNumbers() { + return Stream.of( + List.of(0, 1, 2, 3, 4, 5), + List.of(1, 2, 3, 4, 5, 46) + ); + } } \ No newline at end of file diff --git a/src/test/java/lotto/domain/lotto/LottoResultTest.java b/src/test/java/lotto/domain/lotto/LottoResultTest.java new file mode 100644 index 00000000000..a4fa0524b33 --- /dev/null +++ b/src/test/java/lotto/domain/lotto/LottoResultTest.java @@ -0,0 +1,69 @@ +package lotto.domain.lotto; + +import java.util.List; +import java.util.stream.Stream; +import lotto.domain.lotto.entity.Lotto; +import lotto.domain.lotto.entity.LottoAnswer; +import lotto.domain.lotto.entity.LottoResult; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class LottoResultTest { + + @Nested + @DisplayName("로또 결과 확인") + class 결과_테스트 { + + @ParameterizedTest(name = "결과 {0} 테스트") + @MethodSource("lottoAndResult") + @DisplayName("[SUCCESS] 로또 정답과 로또를 비교한 결과를 확인한다.") + void 정상_결과_테스트(Lotto lotto, LottoResult expectedResult) { + LottoAnswer lottoAnswer = + new LottoAnswer(new Lotto(List.of(1, 2, 3, 4, 5, 6)), 7); + Assertions.assertThat(LottoResult.getResult(lottoAnswer, lotto)) + .isEqualTo(expectedResult); + } + + static Stream lottoAndResult() { + return Stream.of( + Arguments.of( + new Lotto(List.of(1, 2, 3, 4, 5, 6)), + LottoResult.FIRST + ), + Arguments.of( + new Lotto(List.of(1, 2, 3, 4, 5, 7)), + LottoResult.SECOND + ), + Arguments.of( + new Lotto(List.of(1, 2, 3, 4, 5, 8)), + LottoResult.THIRD + ), + Arguments.of( + new Lotto(List.of(1, 2, 3, 4, 7, 8)), + LottoResult.FOURTH + ), + Arguments.of( + new Lotto(List.of(1, 2, 3, 7, 8, 9)), + LottoResult.FIFTH + ), + Arguments.of( + new Lotto(List.of(1, 2, 7, 8, 9, 10)), + LottoResult.LOSE + ), + Arguments.of( + new Lotto(List.of(1, 7, 8, 9, 10, 11)), + LottoResult.LOSE + ), + Arguments.of( + new Lotto(List.of(7, 8, 9, 10, 11, 12)), + LottoResult.LOSE + ) + ); + } + } + +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/lotto/entity/LottoAnswerTest.java b/src/test/java/lotto/domain/lotto/entity/LottoAnswerTest.java new file mode 100644 index 00000000000..ad1f24d0d42 --- /dev/null +++ b/src/test/java/lotto/domain/lotto/entity/LottoAnswerTest.java @@ -0,0 +1,25 @@ +package lotto.domain.lotto.entity; + +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class LottoAnswerTest { + + @Nested + @DisplayName("객체 생성 테스트") + class 객체_생성_테스트 { + + @DisplayName("보너스 번호가 로또 번호와 중복되면 예외가 발생한다.") + @Test + void 보너스_번호_중복() { + Assertions.assertThatIllegalArgumentException() + .isThrownBy(() -> new LottoAnswer( + new Lotto(List.of(1, 2, 3, 4, 5, 6)), + 5 + )); + } + } +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/lotto/entity/LottoResultCountTest.java b/src/test/java/lotto/domain/lotto/entity/LottoResultCountTest.java new file mode 100644 index 00000000000..31a1e0213c7 --- /dev/null +++ b/src/test/java/lotto/domain/lotto/entity/LottoResultCountTest.java @@ -0,0 +1,27 @@ +package lotto.domain.lotto.entity; + +import java.util.EnumMap; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class LottoResultCountTest { + + @Nested + @DisplayName("학습 테스트 - EnumMap의 초기화 상태 확인") + class 학습_테스트 { + + @Test + @DisplayName("EnumMap을 생성하고 아무 값도 넣지 않으면 아무 것도 들어있지 않다.") + void enumMap_초기_상태() { + EnumMap enumMap = new EnumMap<>(LottoResult.class); + enumMap.entrySet() + .forEach( + entry -> { + System.out.println(entry.getKey() + ": " + entry.getValue()); + } + ); + } + } + +} \ No newline at end of file