diff --git a/README.md b/README.md index fc275557..4868d3ff 100644 --- a/README.md +++ b/README.md @@ -1 +1,30 @@ -# java-lotto 게임 +# java-Lotto 게임 + +# 구현 기능 목록 + +- [x] 구입금액 입력받기 + - [x] 1000원 미만 입력 시 처리 + - [x] 1000의 배수 아닌 값 입력 시 처리 + - [x] 숫자가 아닌 값 (문자열) + - [x] 정상값 처리 + - [x] 공백 처리 +- [x] 구매 개수 구하기 +- [x] 랜덤 로또번호 구매 개수만큼 만들기 + - [x] defaultNumberSet 1번만 생성되도록 변경 + - [x] RandomLottoTest 상수 리팩토링 + - [x] PurchaseCount의 1000 접근 +- [x] 지난 주 당첨 번호 입력받기 + - [x] WinningNumbers 멤버변수 ArrayList 클래스 확인 + - [x] 숫자 개수 6개 확인 + - [x] 숫자가 아닌 값 포함 + - [x] 범위 (1~45) 확인 + - [x] 공백 처리 +- [x] 보너스 볼 입력받기 +- [x] 당첨 통계 + - [x] 당첨 조건을 enum 처리 + - [x] 일치 개수 찾기 + - [x] 5개 일치 시 보너스 볼 일치 여부 확인 + - [x] 로또 당첨 개수 구하기 + - [x] 당첨값의 합 구하기 + - [x] 수익률 구하기 + - [x] 결과 출력 \ No newline at end of file diff --git a/build.gradle b/build.gradle index 092a1c08..538e48d2 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,13 @@ repositories { dependencies { testImplementation('org.junit.jupiter:junit-jupiter:5.6.0') testImplementation('org.assertj:assertj-core:3.15.0') + + compileOnly 'org.projectlombok:lombok:1.18.20' + annotationProcessor 'org.projectlombok:lombok:1.18.20' + + testCompileOnly 'org.projectlombok:lombok:1.18.20' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.20' + } test { diff --git a/src/main/java/empty.txt b/src/main/java/empty.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/lotto/LottoApplication.java b/src/main/java/lotto/LottoApplication.java new file mode 100644 index 00000000..23498223 --- /dev/null +++ b/src/main/java/lotto/LottoApplication.java @@ -0,0 +1,33 @@ +package lotto; + +import lotto.controller.LottoController; +import lotto.domain.dto.LottoResult; +import lotto.domain.dto.PurchaseInput; +import lotto.domain.dto.PurchaseResult; +import lotto.domain.dto.WinningLottoInput; +import lotto.exception.ExceptionMessage; +import lotto.view.ConsoleInputView; +import lotto.view.ConsoleOutputView; +import lotto.view.View; + +public class LottoApplication { + + public static void main(String[] args) { + View view = new View(new ConsoleInputView(), new ConsoleOutputView()); + LottoController lottoController = new LottoController(); + + try { + PurchaseInput purchaseInput = view.getPurchasePrice(); + PurchaseResult purchaseResult = lottoController.purchase(purchaseInput); + view.printLottoPurchaseResult(purchaseResult); + + WinningLottoInput winningLottoInput = view.getWinningLottoAndBonus(); + LottoResult lottoResult = lottoController.calculateResult(purchaseResult, winningLottoInput); + view.printLottoStatistics(lottoResult); + } catch (NumberFormatException e) { + view.printException(ExceptionMessage.NON_NUMBER_INPUT.getMessage()); + } catch (IllegalArgumentException e) { + view.printException(e.getMessage()); + } + } +} diff --git a/src/main/java/lotto/controller/LottoController.java b/src/main/java/lotto/controller/LottoController.java new file mode 100644 index 00000000..608ca7eb --- /dev/null +++ b/src/main/java/lotto/controller/LottoController.java @@ -0,0 +1,24 @@ +package lotto.controller; + +import lotto.domain.dto.LottoResult; +import lotto.domain.dto.PurchaseInput; +import lotto.domain.dto.PurchaseResult; +import lotto.domain.dto.WinningLottoInput; +import lotto.service.LottoService; + +public class LottoController { + + private final LottoService lottoService; + + public LottoController() { + this.lottoService = new LottoService(); + } + + public PurchaseResult purchase(PurchaseInput purchaseInput) throws IllegalArgumentException { + return lottoService.purchase(purchaseInput); + } + + public LottoResult calculateResult(PurchaseResult purchaseResult, WinningLottoInput winningLottoInput) throws IllegalArgumentException { + return lottoService.calculateResult(purchaseResult, winningLottoInput); + } +} diff --git a/src/main/java/lotto/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java new file mode 100644 index 00000000..04f0a8a3 --- /dev/null +++ b/src/main/java/lotto/domain/Lotto.java @@ -0,0 +1,51 @@ +package lotto.domain; + +import lombok.Getter; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static lotto.exception.ExceptionMessage.DUPLICATE_LOTTO_NUMBER_INPUT_FOR_LOTTO; +import static lotto.exception.ExceptionMessage.INVALID_LENGTH_INPUT_FOR_LOTTO; + +public class Lotto { + + public static final int PRICE = 1000; + public static final int LOTTO_NUMBER_SIZE = 6; + + @Getter + private final List lottoNumbers; + + public Lotto(List numbers) throws IllegalArgumentException { + validate(numbers); + + numbers.sort(Integer::compare); + this.lottoNumbers = Collections.unmodifiableList(numbers.stream() + .map(LottoNumber::new) + .collect(Collectors.toList())); + } + + private void validate(List numbers) throws IllegalArgumentException { + if (numbers.size() != Lotto.LOTTO_NUMBER_SIZE) { + throw new IllegalArgumentException(INVALID_LENGTH_INPUT_FOR_LOTTO.getMessage()); + } + if (numbers.size() != numbers.stream().distinct().count()) { + throw new IllegalArgumentException(DUPLICATE_LOTTO_NUMBER_INPUT_FOR_LOTTO.getMessage()); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Lotto lotto = (Lotto) o; + return Objects.equals(lottoNumbers, lotto.lottoNumbers); + } + + @Override + public int hashCode() { + return Objects.hash(lottoNumbers); + } +} diff --git a/src/main/java/lotto/domain/LottoMatcher.java b/src/main/java/lotto/domain/LottoMatcher.java new file mode 100644 index 00000000..600fbf25 --- /dev/null +++ b/src/main/java/lotto/domain/LottoMatcher.java @@ -0,0 +1,31 @@ +package lotto.domain; + +public class LottoMatcher { + + private final WinningLotto winningLotto; + private final LottoSet lottoSet; + + public LottoMatcher(WinningLotto winningLotto, LottoSet lottoSet) { + this.winningLotto = winningLotto; + this.lottoSet = lottoSet; + } + + public PrizeCount countPrizes() { + PrizeCount prizeCount = new PrizeCount(); + for (Lotto lotto : lottoSet.getLottoSet()) { + Prize prize = Prize.getMatchPrize(getMatchNumbersCount(lotto), isBonusMatch(lotto)); + prizeCount.addPrize(prize); + } + return prizeCount; + } + + private int getMatchNumbersCount(Lotto targetLotto) { + return (int) targetLotto.getLottoNumbers().stream() + .filter(lottoNumber -> winningLotto.getLottoNumbers().contains(lottoNumber)) + .count(); + } + + private boolean isBonusMatch(Lotto targetLotto) { + return targetLotto.getLottoNumbers().contains(winningLotto.getBonusNumber()); + } +} \ No newline at end of file diff --git a/src/main/java/lotto/domain/LottoNumber.java b/src/main/java/lotto/domain/LottoNumber.java new file mode 100644 index 00000000..c4512fd6 --- /dev/null +++ b/src/main/java/lotto/domain/LottoNumber.java @@ -0,0 +1,53 @@ +package lotto.domain; + +import lombok.Getter; + +import java.util.Objects; + +import static lotto.exception.ExceptionMessage.OUT_OF_BOUND_INPUT_FOR_LOTTO_NUMBER; + +public class LottoNumber { + + public static final int LOWER_BOUND = 1; + public static final int UPPER_BOUND = 45; + + @Getter + private final int lottoNumber; + + public LottoNumber(int lottoNumber) throws IllegalArgumentException { + validate(lottoNumber); + + this.lottoNumber = lottoNumber; + } + + private void validate(int lottoNumber) throws IllegalArgumentException { + if (isOutOfBound(lottoNumber)) { + throw new IllegalArgumentException(OUT_OF_BOUND_INPUT_FOR_LOTTO_NUMBER.getMessage()); + } + } + + private boolean isOutOfBound(int lottoNumber) { + return lottoNumber < LOWER_BOUND || lottoNumber > UPPER_BOUND; + } + + public boolean isGreaterThan(LottoNumber winningNumber) { + return this.getLottoNumber() > winningNumber.getLottoNumber(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LottoNumber that = (LottoNumber) o; + return lottoNumber == that.lottoNumber; + } + + @Override + public int hashCode() { + return Objects.hash(lottoNumber); + } +} diff --git a/src/main/java/lotto/domain/LottoSet.java b/src/main/java/lotto/domain/LottoSet.java new file mode 100644 index 00000000..41be7d6f --- /dev/null +++ b/src/main/java/lotto/domain/LottoSet.java @@ -0,0 +1,29 @@ +package lotto.domain; + +import lombok.Getter; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class LottoSet { + + @Getter + private final Set lottoSet; + + public LottoSet(Set lottoSet) { + this.lottoSet = Collections.unmodifiableSet(lottoSet); + } + + public LottoSet(PurchaseCount purchaseCount) { + this(generateRandomLottoSet(purchaseCount)); + } + + private static Set generateRandomLottoSet(PurchaseCount purchaseCount) { + Set lottoSet = new HashSet<>(); + while (lottoSet.size() < purchaseCount.getPurchaseCount()) { + lottoSet.add(new RandomLotto()); + } + return lottoSet; + } +} diff --git a/src/main/java/lotto/domain/LottoStatistics.java b/src/main/java/lotto/domain/LottoStatistics.java new file mode 100644 index 00000000..3ee78ff0 --- /dev/null +++ b/src/main/java/lotto/domain/LottoStatistics.java @@ -0,0 +1,22 @@ +package lotto.domain; + +import lombok.Builder; +import lombok.Getter; + +public class LottoStatistics { + + @Getter + private final PrizeCount prizeCount; + private final PurchaseCount purchaseCount; + + @Builder + public LottoStatistics(PrizeCount prizeCount, PurchaseCount purchaseCount) { + this.prizeCount = prizeCount; + this.purchaseCount = purchaseCount; + } + + public double calculateProfitRate() { + return (double) Prize.sumOfPrizeMoney(prizeCount) + / (purchaseCount.getPurchaseCount() * Lotto.PRICE); + } +} \ No newline at end of file diff --git a/src/main/java/lotto/domain/Prize.java b/src/main/java/lotto/domain/Prize.java new file mode 100644 index 00000000..0584789f --- /dev/null +++ b/src/main/java/lotto/domain/Prize.java @@ -0,0 +1,55 @@ +package lotto.domain; + +import lombok.Getter; + +@Getter +public enum Prize { + + FIRST(6, false, 2000000000), + SECOND(5, true, 30000000), + THIRD(5, false, 1500000), + FOURTH(4, false, 50000), + FIFTH(3, false, 5000), + LOSE(2, false, 0); + + private final int matchNumbersCount; + private final boolean isBonus; + private final long prizeMoney; + + Prize(int matchNumbersCount, boolean isBonus, long prizeMoney) { + this.matchNumbersCount = matchNumbersCount; + this.isBonus = isBonus; + this.prizeMoney = prizeMoney; + } + + public static Prize getMatchPrize(int matchNumbersCount, boolean isBonus) { + if (matchNumbersCount <= LOSE.matchNumbersCount) { + return LOSE; + } + if (matchNumbersCount == FIFTH.matchNumbersCount) { + return FIFTH; + } + if (matchNumbersCount == FOURTH.matchNumbersCount) { + return FOURTH; + } + if (matchNumbersCount == FIRST.matchNumbersCount) { + return FIRST; + } + return dissolveSecondOrThird(isBonus); + } + + private static Prize dissolveSecondOrThird(boolean isBonus) { + if (isBonus) { + return Prize.SECOND; + } + return Prize.THIRD; + } + + public static long sumOfPrizeMoney(PrizeCount prizeCount) { + return prizeCount.getFirst() * FIRST.prizeMoney + + prizeCount.getSecond() * SECOND.prizeMoney + + prizeCount.getThird() * THIRD.prizeMoney + + prizeCount.getFourth() * FOURTH.prizeMoney + + prizeCount.getFifth() * FIFTH.prizeMoney; + } +} diff --git a/src/main/java/lotto/domain/PrizeCount.java b/src/main/java/lotto/domain/PrizeCount.java new file mode 100644 index 00000000..0c4546d9 --- /dev/null +++ b/src/main/java/lotto/domain/PrizeCount.java @@ -0,0 +1,35 @@ +package lotto.domain; + +import lombok.Getter; + +@Getter +public class PrizeCount { + + private int first; + private int second; + private int third; + private int fourth; + private int fifth; + + public void addPrize(Prize prize) { + if (prize == Prize.FIRST) { + first++; + return; + } + if (prize == Prize.SECOND) { + second++; + return; + } + if (prize == Prize.THIRD) { + third++; + return; + } + if (prize == Prize.FOURTH) { + fourth++; + return; + } + if (prize == Prize.FIFTH) { + fifth++; + } + } +} diff --git a/src/main/java/lotto/domain/PurchaseCount.java b/src/main/java/lotto/domain/PurchaseCount.java new file mode 100644 index 00000000..9096169e --- /dev/null +++ b/src/main/java/lotto/domain/PurchaseCount.java @@ -0,0 +1,35 @@ +package lotto.domain; + +import lombok.Builder; +import lombok.Getter; + +import static lotto.exception.ExceptionMessage.LESS_THAN_LOTTO_PRICE_INPUT_FOR_PURCHASE_MONEY; +import static lotto.exception.ExceptionMessage.NON_MULTIPLE_OF_LOTTO_PRICE_INPUT_FOR_PURCHASE_MONEY; + +public class PurchaseCount { + + private static final int MINIMUM_INPUT = 1000; + + @Getter + private final Integer purchaseCount; + + @Builder + public PurchaseCount(int purchasePrice) { + validate(purchasePrice); + + this.purchaseCount = purchasePrice / Lotto.PRICE; + } + + private void validate(int purchasePrice) { + if (purchasePrice < MINIMUM_INPUT) { + throw new IllegalArgumentException(LESS_THAN_LOTTO_PRICE_INPUT_FOR_PURCHASE_MONEY.getMessage()); + } + if (notMultipleOfLottoPrice(purchasePrice)) { + throw new IllegalArgumentException(NON_MULTIPLE_OF_LOTTO_PRICE_INPUT_FOR_PURCHASE_MONEY.getMessage()); + } + } + + private boolean notMultipleOfLottoPrice(int input) { + return input % Lotto.PRICE != 0; + } +} diff --git a/src/main/java/lotto/domain/RandomLotto.java b/src/main/java/lotto/domain/RandomLotto.java new file mode 100644 index 00000000..1a79222c --- /dev/null +++ b/src/main/java/lotto/domain/RandomLotto.java @@ -0,0 +1,26 @@ +package lotto.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.IntStream; + +public class RandomLotto extends Lotto { + + private static final int INDEX_LOWER_BOUND = 0; + private static final int INDEX_UPPER_BOUND = 6; + private static final List DEFAULT_NUMBERS = Collections.unmodifiableList(new ArrayList() {{ + IntStream.range(LottoNumber.LOWER_BOUND, LottoNumber.UPPER_BOUND) + .forEach(this::add); + }}); + + public RandomLotto() { + super(generateRandomLottoNumbers()); + } + + private static List generateRandomLottoNumbers() { + List defaultNumbers = new ArrayList<>(DEFAULT_NUMBERS); + Collections.shuffle(defaultNumbers); + return new ArrayList<>(defaultNumbers.subList(INDEX_LOWER_BOUND, INDEX_UPPER_BOUND)); + } +} diff --git a/src/main/java/lotto/domain/WinningLotto.java b/src/main/java/lotto/domain/WinningLotto.java new file mode 100644 index 00000000..6ca10ae5 --- /dev/null +++ b/src/main/java/lotto/domain/WinningLotto.java @@ -0,0 +1,17 @@ +package lotto.domain; + +import lombok.Builder; +import lombok.Getter; +import lotto.domain.dto.WinningLottoInput; + +public class WinningLotto extends Lotto { + + @Getter + private final LottoNumber bonusNumber; + + @Builder + public WinningLotto(WinningLottoInput winningLottoInput) throws IllegalArgumentException { + super(winningLottoInput.getNumbers()); + this.bonusNumber = new LottoNumber(winningLottoInput.getBonus()); + } +} diff --git a/src/main/java/lotto/domain/dto/LottoResult.java b/src/main/java/lotto/domain/dto/LottoResult.java new file mode 100644 index 00000000..127e4326 --- /dev/null +++ b/src/main/java/lotto/domain/dto/LottoResult.java @@ -0,0 +1,15 @@ +package lotto.domain.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lotto.domain.PrizeCount; + +@Getter +@RequiredArgsConstructor +@Builder +public class LottoResult { + + private final PrizeCount prizeCount; + private final double profitRate; +} diff --git a/src/main/java/lotto/domain/dto/PurchaseInput.java b/src/main/java/lotto/domain/dto/PurchaseInput.java new file mode 100644 index 00000000..3e70ff23 --- /dev/null +++ b/src/main/java/lotto/domain/dto/PurchaseInput.java @@ -0,0 +1,13 @@ +package lotto.domain.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@Builder +@RequiredArgsConstructor +public class PurchaseInput { + + private final Integer price; +} diff --git a/src/main/java/lotto/domain/dto/PurchaseResult.java b/src/main/java/lotto/domain/dto/PurchaseResult.java new file mode 100644 index 00000000..40efb485 --- /dev/null +++ b/src/main/java/lotto/domain/dto/PurchaseResult.java @@ -0,0 +1,16 @@ +package lotto.domain.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lotto.domain.LottoSet; +import lotto.domain.PurchaseCount; + +@Getter +@RequiredArgsConstructor +@Builder +public class PurchaseResult { + + private final PurchaseCount purchaseCount; + private final LottoSet lottoSet; +} diff --git a/src/main/java/lotto/domain/dto/WinningLottoInput.java b/src/main/java/lotto/domain/dto/WinningLottoInput.java new file mode 100644 index 00000000..65fd3eb3 --- /dev/null +++ b/src/main/java/lotto/domain/dto/WinningLottoInput.java @@ -0,0 +1,16 @@ +package lotto.domain.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +@Getter +@Builder +@RequiredArgsConstructor +public class WinningLottoInput { + + private final List numbers; + private final Integer bonus; +} diff --git a/src/main/java/lotto/exception/ExceptionMessage.java b/src/main/java/lotto/exception/ExceptionMessage.java new file mode 100644 index 00000000..1218cb0b --- /dev/null +++ b/src/main/java/lotto/exception/ExceptionMessage.java @@ -0,0 +1,19 @@ +package lotto.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lotto.domain.Lotto; + +@RequiredArgsConstructor +public enum ExceptionMessage { + + NON_NUMBER_INPUT("숫자를 입력해주세요."), + LESS_THAN_LOTTO_PRICE_INPUT_FOR_PURCHASE_MONEY("구입금액은 " + Lotto.PRICE + " 이상의 값을 입력해주세요."), + NON_MULTIPLE_OF_LOTTO_PRICE_INPUT_FOR_PURCHASE_MONEY("구입금액은 " + Lotto.PRICE + "의 배수로 입력해주세요."), + INVALID_LENGTH_INPUT_FOR_LOTTO("로또 번호는 6개를 입력해주세요"), + DUPLICATE_LOTTO_NUMBER_INPUT_FOR_LOTTO("로또 번호는 중복되지 않는 6개의 숫자를 입력해주세요"), + OUT_OF_BOUND_INPUT_FOR_LOTTO_NUMBER("로또 번호는 1 이상 45 이하의 값을 입력해주세요."); + + @Getter + private final String message; +} diff --git a/src/main/java/lotto/service/LottoService.java b/src/main/java/lotto/service/LottoService.java new file mode 100644 index 00000000..fedd9024 --- /dev/null +++ b/src/main/java/lotto/service/LottoService.java @@ -0,0 +1,33 @@ +package lotto.service; + +import lotto.domain.*; +import lotto.domain.dto.LottoResult; +import lotto.domain.dto.PurchaseInput; +import lotto.domain.dto.PurchaseResult; +import lotto.domain.dto.WinningLottoInput; + +public class LottoService { + + public PurchaseResult purchase(PurchaseInput purchaseInput) throws IllegalArgumentException { + PurchaseCount purchaseCount = new PurchaseCount(purchaseInput.getPrice()); + LottoSet lottoSet = new LottoSet(purchaseCount); + + return PurchaseResult.builder() + .purchaseCount(purchaseCount) + .lottoSet(lottoSet) + .build(); + } + + public LottoResult calculateResult(PurchaseResult purchaseResult, WinningLottoInput winningLottoInput) throws IllegalArgumentException { + LottoMatcher lottoMatcher = new LottoMatcher( + new WinningLotto(winningLottoInput), purchaseResult.getLottoSet() + ); + PrizeCount prizeCount = lottoMatcher.countPrizes(); + LottoStatistics lottoStatistics = new LottoStatistics(prizeCount, purchaseResult.getPurchaseCount()); + + return LottoResult.builder() + .prizeCount(prizeCount) + .profitRate(lottoStatistics.calculateProfitRate()) + .build(); + } +} diff --git a/src/main/java/lotto/util/NumberValidateUtils.java b/src/main/java/lotto/util/NumberValidateUtils.java new file mode 100644 index 00000000..2348d907 --- /dev/null +++ b/src/main/java/lotto/util/NumberValidateUtils.java @@ -0,0 +1,12 @@ +package lotto.util; + +import java.util.regex.Pattern; + +public class NumberValidateUtils { + + private static final String NUMBER_REGEX = "^[0-9]*$"; + + public static boolean isNonNegativeInteger(String input) { + return Pattern.matches(NUMBER_REGEX, input); + } +} diff --git a/src/main/java/lotto/view/ConsoleInputView.java b/src/main/java/lotto/view/ConsoleInputView.java new file mode 100644 index 00000000..97660a45 --- /dev/null +++ b/src/main/java/lotto/view/ConsoleInputView.java @@ -0,0 +1,31 @@ +package lotto.view; + +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.stream.Collectors; + +public class ConsoleInputView implements InputView { + + private static final String LOTTO_NUMBERS_INPUT_DELIMITER = ","; + + private final Scanner scanner = new Scanner(System.in); + + public Integer getPurchaseCost() throws NumberFormatException { + String input = scanner.nextLine().trim(); + return Integer.parseInt(input); + } + + public List getWinningLottoNumbers() throws NumberFormatException { + String input = scanner.nextLine(); + return Arrays.stream(input.split(LOTTO_NUMBERS_INPUT_DELIMITER)) + .map(String::trim) + .map(Integer::new) + .collect(Collectors.toList()); + } + + public Integer getWinningLottoBonus() throws NumberFormatException { + String input = scanner.nextLine().trim(); + return Integer.parseInt(input); + } +} diff --git a/src/main/java/lotto/view/ConsoleOutputView.java b/src/main/java/lotto/view/ConsoleOutputView.java new file mode 100644 index 00000000..2e6f5698 --- /dev/null +++ b/src/main/java/lotto/view/ConsoleOutputView.java @@ -0,0 +1,86 @@ +package lotto.view; + +import lotto.domain.*; +import lotto.domain.dto.LottoResult; + +public class ConsoleOutputView implements OutputView { + + private static final String ERROR_HEADER = "[ERROR] "; + + private void print(String contents) { + System.out.println(contents); + } + + public void printLottoCount(PurchaseCount purchaseCount) { + int count = purchaseCount.getPurchaseCount(); + print(count + "개를 구매했습니다."); + } + + public void printLottoSet(LottoSet lottoSet) { + for (Lotto lotto : lottoSet.getLottoSet()) { + printLotto(lotto); + } + } + + private void printLotto(Lotto lotto) { + String result = "["; + for (LottoNumber lottoNumber : lotto.getLottoNumbers()) { + result += lottoNumber.getLottoNumber() + ", "; + } + result = removeCommaAtTheEnd(result) + "]"; + + print(result); + } + + private String removeCommaAtTheEnd(String str) { + return str.substring(0, str.length() - 2); + } + + public void askPurchaseCost() { + print("구입금액을 입력해 주세요."); + } + + public void askWinningLottoNumbers() { + print("지난 주 당첨 번호를 입력해 주세요."); + } + + public void askWinningLottoBonus() { + print("보너스 볼을 입력해 주세요."); + } + + public void printLottoStatistic(LottoResult lottoStatistics) { + String result = "당첨 통계\n---------\n" + + generatePrizeResultMessage(Prize.FIFTH, lottoStatistics.getPrizeCount().getFifth()) + + generatePrizeResultMessage(Prize.FOURTH, lottoStatistics.getPrizeCount().getFourth()) + + generatePrizeResultMessage(Prize.THIRD, lottoStatistics.getPrizeCount().getThird()) + + generatePrizeResultMessage(Prize.SECOND, lottoStatistics.getPrizeCount().getSecond()) + + generatePrizeResultMessage(Prize.FIRST, lottoStatistics.getPrizeCount().getFirst()) + + generateProfitRateMessage(lottoStatistics.getProfitRate()); + print(result); + } + + private String generatePrizeResultMessage(Prize prize, int prizeCount) { + return generatePrizeResultMessage(prize) + prizeCount + "개\n"; + } + + private String generatePrizeResultMessage(Prize prize) { + StringBuilder sb = new StringBuilder(); + sb.append(prize.getMatchNumbersCount()) + .append("개 일치"); + if (prize.isBonus()) { + sb.append(", 보너스 볼 일치 "); + } + sb.append("(") + .append(prize.getPrizeMoney()) + .append("원)- "); + return sb.toString(); + } + + private String generateProfitRateMessage(double profitRate) { + return "총 수익률은 " + profitRate + "입니다."; + } + + public void printException(String message) { + print(ERROR_HEADER + message); + } +} diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java new file mode 100644 index 00000000..c08ea3c0 --- /dev/null +++ b/src/main/java/lotto/view/InputView.java @@ -0,0 +1,11 @@ +package lotto.view; + +import java.util.List; + +public interface InputView { + Integer getPurchaseCost(); + + List getWinningLottoNumbers(); + + Integer getWinningLottoBonus(); +} diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java new file mode 100644 index 00000000..0f36ab55 --- /dev/null +++ b/src/main/java/lotto/view/OutputView.java @@ -0,0 +1,21 @@ +package lotto.view; + +import lotto.domain.LottoSet; +import lotto.domain.PurchaseCount; +import lotto.domain.dto.LottoResult; + +public interface OutputView { + void askPurchaseCost(); + + void printLottoCount(PurchaseCount purchaseCount); + + void printLottoSet(LottoSet LottoSet); + + void askWinningLottoNumbers(); + + void askWinningLottoBonus(); + + void printLottoStatistic(LottoResult lottoResult); + + void printException(String message); +} diff --git a/src/main/java/lotto/view/View.java b/src/main/java/lotto/view/View.java new file mode 100644 index 00000000..dcb18988 --- /dev/null +++ b/src/main/java/lotto/view/View.java @@ -0,0 +1,53 @@ +package lotto.view; + +import lombok.Builder; +import lombok.RequiredArgsConstructor; +import lotto.domain.dto.PurchaseInput; +import lotto.domain.dto.PurchaseResult; +import lotto.domain.dto.LottoResult; +import lotto.domain.dto.WinningLottoInput; + +import java.util.List; + +@RequiredArgsConstructor +@Builder +public class View { + + private final InputView inputView; + private final OutputView outputView; + + public PurchaseInput getPurchasePrice() throws NumberFormatException { + outputView.askPurchaseCost(); + Integer purchaseCountInput = inputView.getPurchaseCost(); + + return PurchaseInput.builder() + .price(purchaseCountInput) + .build(); + } + + public WinningLottoInput getWinningLottoAndBonus() throws NumberFormatException { + outputView.askWinningLottoNumbers(); + List numbers = inputView.getWinningLottoNumbers(); + + outputView.askWinningLottoBonus(); + Integer bonus = inputView.getWinningLottoBonus(); + + return WinningLottoInput.builder() + .numbers(numbers) + .bonus(bonus) + .build(); + } + + public void printLottoStatistics(LottoResult lottoResult) { + outputView.printLottoStatistic(lottoResult); + } + + public void printException(String message) { + outputView.printException(message); + } + + public void printLottoPurchaseResult(PurchaseResult purchaseResult) { + outputView.printLottoCount(purchaseResult.getPurchaseCount()); + outputView.printLottoSet(purchaseResult.getLottoSet()); + } +} diff --git a/src/test/java/empty.txt b/src/test/java/empty.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/src/test/java/lotto/domain/LottoMatcherTest.java b/src/test/java/lotto/domain/LottoMatcherTest.java new file mode 100644 index 00000000..14d2ef47 --- /dev/null +++ b/src/test/java/lotto/domain/LottoMatcherTest.java @@ -0,0 +1,30 @@ +package lotto.domain; + +import lotto.fixture.TestLottoSet; +import lotto.fixture.TestWinningLotto; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LottoMatcherTest { + + @Test + @DisplayName("로또의 당첨 개수를 구한다") + void testCountPrizesOfLottoSet() { + //given + TestLottoSet lottoSet = new TestLottoSet(); + TestWinningLotto winningLotto = new TestWinningLotto(); + + //when + LottoMatcher lottoMatcher = new LottoMatcher(winningLotto, lottoSet); + PrizeCount prizeCount = lottoMatcher.countPrizes(); + + //then + assertThat(prizeCount.getFirst()).isEqualTo(1); + assertThat(prizeCount.getSecond()).isEqualTo(1); + assertThat(prizeCount.getThird()).isEqualTo(2); + assertThat(prizeCount.getFourth()).isEqualTo(2); + assertThat(prizeCount.getFifth()).isEqualTo(2); + } +} diff --git a/src/test/java/lotto/domain/LottoNumberTest.java b/src/test/java/lotto/domain/LottoNumberTest.java new file mode 100644 index 00000000..d3842c1d --- /dev/null +++ b/src/test/java/lotto/domain/LottoNumberTest.java @@ -0,0 +1,60 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class LottoNumberTest { + + @Test + @DisplayName("주어진 1~45 숫자로 LottoNumber 객체를 생성한다") + void testGenerateLottoNumberWithInteger() { + // given + List inputNumbers = new ArrayList<>(); + inputNumbers.add(1); + inputNumbers.add(2); + inputNumbers.add(44); + inputNumbers.add(45); + List lottoNumbers = new ArrayList<>(); + + // when + for (Integer inputNumber : inputNumbers) { + lottoNumbers.add(new LottoNumber(inputNumber)); + } + + // then + for (int i = 0; i { + new LottoNumber(inputNumber); + }); + } + + @Test + @DisplayName("45보다 큰 숫자로 LottoNumber 생성 시 예외를 던진다") + void testGenerateLottoNumberWithIntegerGreaterThanUpperBound() { + //given + int inputNumber = 46; + + //when, then + assertThrows(IllegalArgumentException.class, ()-> { + new LottoNumber(inputNumber); + }); + } +} diff --git a/src/test/java/lotto/domain/LottoSetTest.java b/src/test/java/lotto/domain/LottoSetTest.java new file mode 100644 index 00000000..d8ccc269 --- /dev/null +++ b/src/test/java/lotto/domain/LottoSetTest.java @@ -0,0 +1,23 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LottoSetTest { + + @Test + @DisplayName("개수를 입력받으면 개수만큼 랜덤 로또를 생성한다") + void testGenerateRandomLottoSet() { + // given + int targetSize = 10; + PurchaseCount purchaseCount = new PurchaseCount(targetSize * Lotto.PRICE); + + // when + LottoSet lottoSet = new LottoSet(purchaseCount); + + // then + assertThat(lottoSet.getLottoSet().size()).isEqualTo(targetSize); + } +} diff --git a/src/test/java/lotto/domain/LottoStatisticsTest.java b/src/test/java/lotto/domain/LottoStatisticsTest.java new file mode 100644 index 00000000..e56c92fa --- /dev/null +++ b/src/test/java/lotto/domain/LottoStatisticsTest.java @@ -0,0 +1,31 @@ +package lotto.domain; + +import lotto.fixture.TestLottoSet; +import lotto.fixture.TestWinningLotto; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LottoStatisticsTest { + + + @Test + @DisplayName("로또 구매 개수와 당첨 로또 개수를 입력받으면 수익률을 반환한다") + void testLottoStatistics() { + //given + LottoMatcher lottoMatcher = new LottoMatcher(new TestWinningLotto(), new TestLottoSet()); + PrizeCount prizeCount = lottoMatcher.countPrizes(); + long prizeSum = Prize.sumOfPrizeMoney(prizeCount); + int purchaseMoney = 18000; + double result = (double) prizeSum / purchaseMoney; + + // when + LottoStatistics lottoStatistics = new LottoStatistics( + prizeCount, new PurchaseCount(purchaseMoney) + ); + + // then + assertThat(lottoStatistics.calculateProfitRate()).isEqualTo(result); + } +} diff --git a/src/test/java/lotto/domain/LottoTest.java b/src/test/java/lotto/domain/LottoTest.java new file mode 100644 index 00000000..79b1c7a9 --- /dev/null +++ b/src/test/java/lotto/domain/LottoTest.java @@ -0,0 +1,147 @@ +package lotto.domain; + +import lotto.domain.dto.WinningLottoInput; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class LottoTest { + + @Test + @DisplayName("숫자가 6개인지 확인한다") + void testWinningNumberSize() { + // given + List winningNumberInput = Arrays.stream(new int[]{1, 2, 3, 4, 5, 6}) + .boxed() + .collect(Collectors.toList()); + Integer bonusNumberInput = 10; + + // when + WinningLotto winningLotto = new WinningLotto( + new WinningLottoInput(winningNumberInput, bonusNumberInput) + ); + int size = winningLotto.getLottoNumbers().size(); + + // then + assertThat(size).isEqualTo(Lotto.LOTTO_NUMBER_SIZE); + } + + @Test + @DisplayName("45 초과인 값 포함할 때 예외를 던진다") + void testInputGreaterThanUpperBoundary() { + // given + List input = Arrays.stream(new int[]{1, 2, 3, 4, 5, 46}).boxed().collect(Collectors.toList()); + Integer bonus = 10; + WinningLottoInput winningLottoInput = WinningLottoInput.builder() + .numbers(input) + .bonus(bonus) + .build(); + + // when, then + assertThrows(IllegalArgumentException.class, () -> { + new WinningLotto(winningLottoInput); + }); + } + + @Test + @DisplayName("1 미만인 값 포함할 때 예외를 던진다") + void testInputSmallerThanLowerBoundary() { + // given + List input = Arrays.stream(new int[]{0, 2, 3, 4, 5, 6}).boxed().collect(Collectors.toList()); + Integer bonus = 10; + WinningLottoInput winningLottoInput = WinningLottoInput.builder() + .numbers(input) + .bonus(bonus) + .build(); + + // when, then + assertThrows(IllegalArgumentException.class, () -> { + new WinningLotto(winningLottoInput); + }); + } + + @Test + @DisplayName("6개 이하의 숫자가 입력됐을 때 예외를 던진다") + void testInputLengthLessThanStandard() { + // given + List input = Arrays.stream(new int[]{1, 2, 3, 4, 5}).boxed().collect(Collectors.toList()); + Integer bonus = 10; + WinningLottoInput winningLottoInput = WinningLottoInput.builder() + .numbers(input) + .bonus(bonus) + .build(); + + // when, then + assertThrows(IllegalArgumentException.class, () -> { + new WinningLotto(winningLottoInput); + }); + } + + @Test + @DisplayName("6개 이상의 숫자가 입력됐을 때 예외를 던진다") + void testInputLengthGreaterThanStandard() { + // given + List input = Arrays.stream(new int[]{1, 2, 3, 4, 5, 6, 7}).boxed().collect(Collectors.toList()); + Integer bonus = 10; + WinningLottoInput winningLottoInput = WinningLottoInput.builder() + .numbers(input) + .bonus(bonus) + .build(); + + // when, then + assertThrows(IllegalArgumentException.class, () -> { + new WinningLotto(winningLottoInput); + }); + } + + @Test + @DisplayName("중복된 숫자가 입력됐을 때 예외를 던진다") + void testInputWithDuplicatedNumbers() { + // given + List input = Arrays.stream(new int[]{1, 2, 3, 3, 5, 6}).boxed().collect(Collectors.toList()); + Integer bonus = 10; + WinningLottoInput winningLottoInput = WinningLottoInput.builder() + .numbers(input) + .bonus(bonus) + .build(); + + // when, then + assertThrows(IllegalArgumentException.class, () -> { + new WinningLotto(winningLottoInput); + }); + } + + @Test + void testEqualityOfLottos() { + // given + List lottoNumbersOne = new ArrayList<>(); + lottoNumbersOne.add(1); + lottoNumbersOne.add(30); + lottoNumbersOne.add(22); + lottoNumbersOne.add(5); + lottoNumbersOne.add(42); + lottoNumbersOne.add(17); + + List lottoNumbersTwo = new ArrayList<>(); + lottoNumbersTwo.add(17); + lottoNumbersTwo.add(22); + lottoNumbersTwo.add(5); + lottoNumbersTwo.add(30); + lottoNumbersTwo.add(1); + lottoNumbersTwo.add(42); + + // when + Lotto one = new Lotto(lottoNumbersOne); + Lotto two = new Lotto(lottoNumbersTwo); + + // then + assertThat(one).isEqualTo(two); + } +} diff --git a/src/test/java/lotto/domain/PrizeTest.java b/src/test/java/lotto/domain/PrizeTest.java new file mode 100644 index 00000000..eee318ed --- /dev/null +++ b/src/test/java/lotto/domain/PrizeTest.java @@ -0,0 +1,113 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PrizeTest { + + @Test + @DisplayName("숫자가 6개 일치할 때 1등") + void firstPrize() { + //given + int matchNumbersCount = 6; + + //when, then + assertThat(Prize.getMatchPrize(matchNumbersCount, true)) + .isEqualTo(Prize.FIRST); + assertThat(Prize.getMatchPrize(matchNumbersCount, false)) + .isEqualTo(Prize.FIRST); + } + + @Test + @DisplayName("숫자가 5개 일치하고 보너스번호도 일치할 때 2등") + void secondPrize() { + //given + int matchNumbersCount = 5; + boolean isBonus = true; + + //when + Prize prize = Prize.getMatchPrize(matchNumbersCount, isBonus); + + //then + assertThat(prize).isEqualTo(Prize.SECOND); + } + + @Test + @DisplayName("숫자가 5개 일치하고 보너스 번호가 일치하지 않을 때 3등") + void thirdPrize() { + //given + int matchNumbersCount = 5; + boolean isBonus = false; + + //when, then + assertThat(Prize.getMatchPrize(matchNumbersCount, isBonus)) + .isEqualTo(Prize.THIRD); + } + + @Test + @DisplayName("숫자가 4개 일치할 때 4등") + void fourthPrize() { + //given + int matchNumbersCount = 4; + + //when, then + assertThat(Prize.getMatchPrize(matchNumbersCount, false)) + .isEqualTo(Prize.FOURTH); + assertThat(Prize.getMatchPrize(matchNumbersCount, true)) + .isEqualTo(Prize.FOURTH); + } + + @Test + @DisplayName("숫자가 3개 일치할 때 5등") + void fifthPrize() { + //given + int matchNumbersCount = 3; + + //when, then + assertThat(Prize.getMatchPrize(matchNumbersCount, false)) + .isEqualTo(Prize.FIFTH); + assertThat(Prize.getMatchPrize(matchNumbersCount, true)) + .isEqualTo(Prize.FIFTH); + } + + @Test + @DisplayName("숫자가 2개 일치할 때 꽝") + void loseMatchesTwo() { + //given + int matchNumbersCount = 2; + + //when, then + assertThat(Prize.getMatchPrize(matchNumbersCount, false)) + .isEqualTo(Prize.LOSE); + assertThat(Prize.getMatchPrize(matchNumbersCount, true)) + .isEqualTo(Prize.LOSE); + } + + @Test + @DisplayName("숫자가 1개 일치할 때 꽝") + void loseMatchesOne() { + //given + int matchNumbersCount = 1; + + //when, then + assertThat(Prize.getMatchPrize(matchNumbersCount, false)) + .isEqualTo(Prize.LOSE); + assertThat(Prize.getMatchPrize(matchNumbersCount, true)) + .isEqualTo(Prize.LOSE); + } + + @Test + @DisplayName("숫자가 0개 일치할 때 꽝") + void lose() { + //given + int matchNumbersCount = 0; + + //when, then + assertThat(Prize.getMatchPrize(matchNumbersCount, false)) + .isEqualTo(Prize.LOSE); + assertThat(Prize.getMatchPrize(matchNumbersCount, true)) + .isEqualTo(Prize.LOSE); + } +} diff --git a/src/test/java/lotto/domain/PurchaseCountTest.java b/src/test/java/lotto/domain/PurchaseCountTest.java new file mode 100644 index 00000000..f8a959ab --- /dev/null +++ b/src/test/java/lotto/domain/PurchaseCountTest.java @@ -0,0 +1,47 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class PurchaseCountTest { + + @Test + @DisplayName("구매금액 입력값이 1000 미만이면 예외를 던진다") + void testInputValueUnder1000() { + // given + int input = 800; + + // when, then + assertThrows(IllegalArgumentException.class, () -> { + new PurchaseCount(input); + }); + } + + @Test + @DisplayName("구매금액 입력값이 1000 이상이면 정상처리된다") + void testInputValueNotUnder1000() { + // given + int input = 2000; + + // when + PurchaseCount purchaseCount = new PurchaseCount(input); + + // then + assertThat(purchaseCount.getPurchaseCount()).isEqualTo(input / Lotto.PRICE); + } + + @Test + @DisplayName("구매금액 입력값이 1000의 배수가 아니면 예외를 던진다") + void testInputValueNotMultiple1000() { + // given + int input = 1234; + + // when, then + assertThrows(IllegalArgumentException.class, () ->{ + new PurchaseCount(input); + }); + } +} diff --git a/src/test/java/lotto/fixture/TestLottoSet.java b/src/test/java/lotto/fixture/TestLottoSet.java new file mode 100644 index 00000000..c8608495 --- /dev/null +++ b/src/test/java/lotto/fixture/TestLottoSet.java @@ -0,0 +1,43 @@ +package lotto.fixture; + +import lotto.domain.Lotto; +import lotto.domain.LottoSet; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.stream.Collectors; + +public class TestLottoSet extends LottoSet { + + public TestLottoSet() { + super(generateLottoSet()); + } + + public static HashSet generateLottoSet() { + HashSet lottos = new HashSet<>(); + // 1등 + lottos.add(new Lotto(Arrays.stream(new int[]{1, 2, 3, 4, 5, 6}).boxed().collect(Collectors.toList()))); + + // 2등 + lottos.add(new Lotto(Arrays.stream(new int[]{1, 2, 3, 4, 5, 7}).boxed().collect(Collectors.toList()))); + + // 3등 + lottos.add(new Lotto(Arrays.stream(new int[]{1, 2, 3, 4, 5, 10}).boxed().collect(Collectors.toList()))); + lottos.add(new Lotto(Arrays.stream(new int[]{2, 3, 4, 5, 6, 10}).boxed().collect(Collectors.toList()))); + + // 4등 + lottos.add(new Lotto(Arrays.stream(new int[]{1, 2, 3, 4, 10, 11}).boxed().collect(Collectors.toList()))); + lottos.add(new Lotto(Arrays.stream(new int[]{1, 3, 5, 6, 10, 11}).boxed().collect(Collectors.toList()))); + + // 5등 + lottos.add(new Lotto(Arrays.stream(new int[]{2, 4, 6, 10, 11, 12}).boxed().collect(Collectors.toList()))); + lottos.add(new Lotto(Arrays.stream(new int[]{3, 4, 5, 10, 11, 12}).boxed().collect(Collectors.toList()))); + + // 꽝 + lottos.add(new Lotto(Arrays.stream(new int[]{1, 12, 13, 14, 15, 16}).boxed().collect(Collectors.toList()))); + lottos.add(new Lotto(Arrays.stream(new int[]{11, 12, 13, 14, 15, 16}).boxed().collect(Collectors.toList()))); + + return lottos; + } + +} diff --git a/src/test/java/lotto/fixture/TestWinningLotto.java b/src/test/java/lotto/fixture/TestWinningLotto.java new file mode 100644 index 00000000..3791bcb6 --- /dev/null +++ b/src/test/java/lotto/fixture/TestWinningLotto.java @@ -0,0 +1,23 @@ +package lotto.fixture; + +import lotto.domain.WinningLotto; +import lotto.domain.dto.WinningLottoInput; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class TestWinningLotto extends WinningLotto { + + private static final List winningNumberInput = Arrays.stream(new int[]{1, 2, 3, 4, 5, 6}) + .boxed() + .collect(Collectors.toList()); + private static final Integer bonusNumberInput = 7; + + public TestWinningLotto() { + super(WinningLottoInput.builder() + .numbers(winningNumberInput) + .bonus(bonusNumberInput) + .build()); + } +}