diff --git a/src/README.md b/src/README.md index 84b3d4a2c..f174ed6b7 100644 --- a/src/README.md +++ b/src/README.md @@ -3,8 +3,9 @@ ## 프로젝트 개요 로또 구매 및 당첨 통계 계산 프로그램을 구현했습니다. -사용자는 구입 금액과 지난 주 당첨 번호를 입력할 수 있으며, 구입 금액에 해당하는 수만큼 로또를 자동으로 발급받습니다. -발급된 로또와 당첨 번호를 비교하여 당첨 통계를 계산하고, 총 수익률을 출력합니다. +사용자는 구입 금액을 입력하고, 수동으로 구매할 로또 개수와 번호를 입력할 수 있습니다. +남은 수량만큼은 자동으로 발급되며, 지난 주 당첨 번호와 보너스 볼을 입력해 당첨 결과를 계산합니다. +구매한 로또와 당첨 번호를 비교하여 당첨 통계를 출력하고, 총 수익률을 계산합니다. ## 기술 스택 @@ -26,25 +27,33 @@ - 로또 번호가 6개가 아니면 예외를 발생시킨다. - 로또 번호가 중복되면 예외를 발생시킨다. - 당첨 번호와 비교하여 일치 개수를 계산한다. +- 특정 번호를 포함하는지 확인한다. ### Lottos - 여러 장의 로또를 관리한다. - 구매한 로또 개수를 반환한다. - 로또 목록을 출력용 번호 리스트로 변환한다. -- 당첨 번호를 기준으로 당첨 통계를 생성한다. +- 로또 목록을 반환한다. ### PurchaseAmount - 구입 금액을 관리한다. - 구입 금액이 1000원 미만이면 예외를 발생시킨다. - 구입 금액이 1000원 단위가 아니면 예외를 발생시킨다. -- 구입 금액으로 구매 가능한 로또 개수를 계산한다. +- 수동 구매 수를 제외한 자동 구매 개수를 계산한다. + +### ManualLottoCount + +- 수동 구매 로또 개수를 관리한다. +- 수동 구매 수가 0 미만이면 예외를 발생시킨다. +- 수동 구매 수가 총 구매 가능 로또 수를 초과하면 예외를 발생시킨다. ### LottoShop - 로또 구매를 담당한다. -- 구입 금액에 해당하는 개수만큼 로또를 생성한다. +- 수동 로또와 자동 로또를 함께 구매한다. +- 자동 로또를 생성하고 수동 로또와 병합한다. ### NumberGenerator @@ -52,32 +61,48 @@ - `RandomNumberGenerator`는 1부터 45 사이의 숫자 중 6개를 무작위로 생성한다. - `TestNumberGenerator`는 테스트에서 원하는 번호를 고정으로 생성한다. +### BonusBall + +- 보너스 볼 번호를 관리한다. +- 보너스 볼이 없으면 예외를 발생시킨다. + +### WinningLotto + +- 지난 주 당첨 번호와 보너스 볼을 함께 관리한다. +- 보너스 볼이 당첨 번호와 중복되면 예외를 발생시킨다. +- 구매한 로또의 당첨 등수를 판별한다. + ### Rank - 당첨 등수를 관리한다. -- 일치 개수에 따라 3등, 4등, 5등, 6등을 판별한다. +- 일치 개수와 보너스 볼 일치 여부에 따라 등수를 판별한다. - 각 등수에 해당하는 당첨 금액을 관리한다. ### WinningStatistics - 등수별 당첨 개수를 관리한다. -- 등수별 당첨 개수를 증가시킨다. +- 구매한 로또 목록과 당첨 번호를 기준으로 당첨 통계를 생성한다. +- 등수별 당첨 개수를 반환한다. - 총 당첨금을 계산한다. - 구입 금액을 기준으로 수익률을 계산한다. ### WinningResult - 당첨 통계 출력에 필요한 데이터를 관리한다. -- 일치 개수, 당첨 금액, 당첨 개수를 담아 출력 계층으로 전달한다. +- `WinningStatistics`를 출력용 결과 목록으로 변환한다. +- 당첨 메시지와 당첨 개수를 담아 출력 계층으로 전달한다. ### InputView - 구입 금액을 입력받는다. +- 수동으로 구매할 로또 수를 입력받는다. +- 수동으로 구매할 번호를 입력받는다. - 지난 주 당첨 번호를 쉼표(,)를 기준으로 구분하여 입력받는다. +- 보너스 볼을 입력받는다. ### OutputView -- 구매 결과 헤더를 출력한다. +- 수동 구매 수와 자동 구매 수를 함께 출력한다. - 발급된 로또 번호를 출력한다. - 당첨 통계를 출력한다. - 총 수익률을 출력한다. @@ -85,7 +110,6 @@ ### LottoController - 로또 게임의 전체 진행을 담당한다. -- 구입 금액 입력, 로또 구매, 당첨 번호 입력, 당첨 통계 계산, 결과 출력을 순서대로 연결한다. ## 테스트 @@ -100,34 +124,39 @@ ### LottoShopTest -- 구입 금액만큼 로또를 구매하는지 테스트한다. +- 구입 금액에 맞게 자동 로또가 생성되는지 테스트한다. +- 수동 로또와 자동 로또가 함께 반환되는지 테스트한다. ### LottoNumberTest -- 로또 번호가 1보다 작으면 예외가 발생하는지 테스트한다. -- 로또 번호가 45보다 크면 예외가 발생하는지 테스트한다. +- 로또 번호가 범위를 벗어나면 예외가 발생하는지 테스트한다. ### PurchaseAmountTest - 구입 금액이 1000원 미만이면 예외가 발생하는지 테스트한다. - 구입 금액이 1000원 단위가 아니면 예외가 발생하는지 테스트한다. -- 구입 금액으로 구매 가능한 로또 개수를 계산하는지 테스트한다. + +### ManualLottoCountTest + +- 수동 구매 수가 0 미만이면 예외가 발생하는지 테스트한다. +- 수동 구매 수가 총 구매 가능 수를 초과하면 예외가 발생하는지 테스트한다. ### WinningStatisticsTest -- 등수를 추가하면 당첨 개수가 증가하는지 테스트한다. +- 당첨 결과별 개수를 집계하는지 테스트한다. - 총 당첨금을 계산하는지 테스트한다. - 수익률을 계산하는지 테스트한다. ## 설계 의도 -로또 번호와 구입 금액을 각각 `LottoNumber`, `PurchaseAmount` 로 포장했습니다. -이를 통해 로또 번호 범위 검증과 구입 금액 검증 책임을 객체가 관리하도록 했습니다. +로또 번호, 구입 금액, 수동 구매 수를 각각 `LottoNumber`, `PurchaseAmount`, `ManualLottoCount`로 포장했습니다. +이를 통해 원시값이 직접 흩어지지 않도록 하고, 각 값에 대한 검증 책임을 객체가 관리하도록 했습니다. 또한 `List`를 `Lottos`로 감싸는 방식으로 로또 목록에 대한 책임을 분리했습니다. -당첨 결과는 `Rank`와 `WinningStatistics`를 통해 계산하도록 했습니다. -`Rank`는 일치 개수에 따른 등수와 당첨 금액을 관리하고, `WinningStatistics`는 등수별 개수와 총 당첨금, 수익률 계산을 담당하도록 구성했습니다. +당첨 결과는 `WinningLotto`, `Rank`, `WinningStatistics`를 통해 계산하도록 했습니다. +`WinningLotto`는 당첨 번호와 보너스 볼을 함께 관리하고, `Rank`는 일치 개수와 보너스 볼 일치 여부에 따른 등수 및 당첨 금액을 관리합니다. +`WinningStatistics`는 등수별 개수와 총 당첨금, 수익률 계산을 담당하도록 구성했습니다. 입력과 출력은 `InputView`, `OutputView`로 분리했습니다. -또한 출력에 필요한 데이터는 `WinningResult`로 별도 전달하여 뷰가 도메인 객체를 직접 해석하지 않도록 구성했습니다. +또한 출력에 필요한 데이터는 `WinningResult`로 변환하여 뷰가 도메인을 직접 알지 못하도록 구성했습니다. diff --git a/src/main/java/controller/LottoController.java b/src/main/java/controller/LottoController.java index a3271aa7e..7ef84cce8 100644 --- a/src/main/java/controller/LottoController.java +++ b/src/main/java/controller/LottoController.java @@ -1,10 +1,19 @@ package controller; -import domain.*; +import domain.BonusBall; +import domain.Lotto; +import domain.LottoNumber; +import domain.LottoShop; +import domain.Lottos; +import domain.ManualLottoCount; +import domain.PurchaseAmount; +import domain.WinningLotto; +import domain.WinningStatistics; +import dto.WinningResult; import view.InputView; import view.OutputView; -import java.util.List; +import java.util.function.Supplier; public class LottoController { private final InputView inputView; @@ -18,24 +27,49 @@ public LottoController(InputView inputView, OutputView outputView, LottoShop lot } public void run() { - PurchaseAmount purchaseAmount = new PurchaseAmount(inputView.readAmount()); - Lottos lottos = lottoShop.purchase(purchaseAmount); + PurchaseAmount purchaseAmount = retryUntilValid(() -> new PurchaseAmount(inputView.readAmount())); + ManualLottoCount manualLottoCount = retryUntilValid(() -> new ManualLottoCount(inputView.readManualCount(), purchaseAmount)); + Lottos lottos = retryUntilValid(() -> purchaseLottos(purchaseAmount, manualLottoCount)); - outputView.printResultHeader(lottos.size()); - outputView.printLottos(lottos.toNumberLists()); - - List winningNumbers = inputView.readWinningNumbers(); - Lotto winningLotto = new Lotto(toLottoNumbers(winningNumbers)); + printPurchaseResult(lottos, manualLottoCount); + WinningLotto winningLotto = readWinningLotto(); WinningStatistics winningStatistics = WinningStatistics.from(lottos, winningLotto); - outputView.printWinningStatistics(winningStatistics.winningResults()); + printWinningResult(winningStatistics, purchaseAmount); + } + + private Lottos purchaseLottos(PurchaseAmount purchaseAmount, ManualLottoCount manualLottoCount) { + Lottos manualLottos = Lottos.from(inputView.readManualNumbers(manualLottoCount.count())); + return lottoShop.purchase(purchaseAmount, manualLottos); + } + + private void printPurchaseResult(Lottos lottos, ManualLottoCount manualLottoCount) { + int autoCount = lottos.size() - manualLottoCount.count(); + outputView.printResultHeader(manualLottoCount.count(), autoCount); + outputView.printLottos(lottos.toNumberLists()); + } + + private WinningLotto readWinningLotto() { + Lotto winningLotto = retryUntilValid(() -> Lotto.from(inputView.readWinningNumbers())); + return retryUntilValid(() -> { + BonusBall bonusBall = new BonusBall(new LottoNumber(inputView.readBonusBall())); + return new WinningLotto(winningLotto, bonusBall); + }); + } + + private void printWinningResult(WinningStatistics winningStatistics, PurchaseAmount purchaseAmount) { + outputView.printWinningStatistics(WinningResult.from(winningStatistics)); outputView.printProfitRate(winningStatistics.calculateProfitRate(purchaseAmount)); } - private List toLottoNumbers(List numbers) { - return numbers.stream() - .map(LottoNumber::new) - .toList(); + private T retryUntilValid(Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + } + } } } diff --git a/src/main/java/domain/BonusBall.java b/src/main/java/domain/BonusBall.java new file mode 100644 index 000000000..5f7e78b78 --- /dev/null +++ b/src/main/java/domain/BonusBall.java @@ -0,0 +1,13 @@ +package domain; + +public record BonusBall(LottoNumber number) { + public BonusBall { + validate(number); + } + + private void validate(LottoNumber number) { + if (number == null) { + throw new IllegalArgumentException("보너스 볼은 필수입니다."); + } + } +} diff --git a/src/main/java/domain/Lotto.java b/src/main/java/domain/Lotto.java index c143b43bd..460352d73 100644 --- a/src/main/java/domain/Lotto.java +++ b/src/main/java/domain/Lotto.java @@ -1,6 +1,7 @@ package domain; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; public class Lotto { @@ -9,7 +10,13 @@ public class Lotto { public Lotto(List numbers) { validate(numbers); - this.numbers = new ArrayList<>(numbers); + this.numbers = new ArrayList<>(sortNumbers(numbers)); + } + + public static Lotto from(List numbers) { + return new Lotto(numbers.stream() + .map(LottoNumber::new) + .toList()); } public int countMatch(Lotto winningLotto) { @@ -22,22 +29,28 @@ public List getNumbers() { return List.copyOf(numbers); } - private boolean contains(LottoNumber lottoNumber) { + public boolean contains(LottoNumber lottoNumber) { return numbers.contains(lottoNumber); } - private void validate(List numbers) { + private static void validate(List numbers) { validateLottoSize(numbers); validateDuplicate(numbers); } - private void validateLottoSize(List numbers) { + private List sortNumbers(List numbers) { + return numbers.stream() + .sorted(Comparator.comparingInt(LottoNumber::number)) + .toList(); + } + + private static void validateLottoSize(List numbers) { if (numbers.size() != LOTTO_SIZE) { throw new IllegalArgumentException("로또 숫자의 갯수는 6개입니다."); } } - private void validateDuplicate(List numbers) { + private static void validateDuplicate(List numbers) { long distinctCount = numbers.stream().distinct().count(); if (distinctCount != LOTTO_SIZE) { throw new IllegalArgumentException("로또 번호는 중복될 수 없습니다."); diff --git a/src/main/java/domain/LottoNumber.java b/src/main/java/domain/LottoNumber.java index 8d3943ae2..dd131d62c 100644 --- a/src/main/java/domain/LottoNumber.java +++ b/src/main/java/domain/LottoNumber.java @@ -8,11 +8,11 @@ public record LottoNumber(int number) { validate(number); } - private void validate(int number) { + private static void validate(int number) { validateRange(number); } - private void validateRange(int number) { + private static void validateRange(int number) { if (number < MIN_NUMBER || number > MAX_NUMBER) { throw new IllegalArgumentException("로또 번호는 1부터 45 사이여야 합니다."); } diff --git a/src/main/java/domain/LottoShop.java b/src/main/java/domain/LottoShop.java index 5c24e299e..5bc975c56 100644 --- a/src/main/java/domain/LottoShop.java +++ b/src/main/java/domain/LottoShop.java @@ -12,15 +12,23 @@ public LottoShop(NumberGenerator numberGenerator) { this.numberGenerator = numberGenerator; } - public Lottos purchase(PurchaseAmount purchaseAmount) { - return new Lottos(createLottos(purchaseAmount)); + public Lottos purchase(PurchaseAmount purchaseAmount, Lottos manualLottos) { + int autoLottoCount = purchaseAmount.calculateAutoLottoCount(manualLottos.size()); + List autoLottos = createAutoLottos(autoLottoCount); + return new Lottos(mergeLottos(manualLottos.lottoToList(), autoLottos)); } - private List createLottos(PurchaseAmount purchaseAmount) { + private List createAutoLottos(int autoLottoCount) { List lottos = new ArrayList<>(); - for (int i = 0; i < purchaseAmount.calculateLottoCount(); i++) { + for (int i = 0; i < autoLottoCount; i++) { lottos.add(new Lotto(numberGenerator.generate())); } return lottos; } + + private List mergeLottos(List manualLottos, List autoLottos) { + List mergedLottos = new ArrayList<>(manualLottos); + mergedLottos.addAll(autoLottos); + return mergedLottos; + } } diff --git a/src/main/java/domain/Lottos.java b/src/main/java/domain/Lottos.java index 3412e3926..94a0d9138 100644 --- a/src/main/java/domain/Lottos.java +++ b/src/main/java/domain/Lottos.java @@ -10,6 +10,12 @@ public Lottos(List lottos) { this.lottos = new ArrayList<>(lottos); } + public static Lottos from(List> lottos) { + return new Lottos(lottos.stream() + .map(Lotto::from) + .toList()); + } + public int size() { return lottos.size(); } diff --git a/src/main/java/domain/ManualLottoCount.java b/src/main/java/domain/ManualLottoCount.java new file mode 100644 index 000000000..025da317d --- /dev/null +++ b/src/main/java/domain/ManualLottoCount.java @@ -0,0 +1,23 @@ +package domain; + +public class ManualLottoCount { + private final int count; + + public ManualLottoCount(int count, PurchaseAmount purchaseAmount) { + validate(count, purchaseAmount); + this.count = count; + } + + private void validate(int count, PurchaseAmount purchaseAmount) { + if (count < 0) { + throw new IllegalArgumentException("수동 구매 수는 0 이상이어야 합니다."); + } + if (count > purchaseAmount.calculateLottoCount()) { + throw new IllegalArgumentException("수동 로또 수는 총 로또 수를 넘을 수 없습니다."); + } + } + + public int count() { + return count; + } +} diff --git a/src/main/java/domain/PurchaseAmount.java b/src/main/java/domain/PurchaseAmount.java index f88362af6..60d89cd39 100644 --- a/src/main/java/domain/PurchaseAmount.java +++ b/src/main/java/domain/PurchaseAmount.java @@ -19,4 +19,8 @@ private void validateAmount(int amount) { public int calculateLottoCount() { return amount / LOTTO_PRICE; } + + public int calculateAutoLottoCount(int manualCount) { + return calculateLottoCount() - manualCount; + } } diff --git a/src/main/java/domain/Rank.java b/src/main/java/domain/Rank.java index 44f2d0cd1..a31885f1b 100644 --- a/src/main/java/domain/Rank.java +++ b/src/main/java/domain/Rank.java @@ -1,27 +1,38 @@ package domain; -import java.util.Arrays; - public enum Rank { - THREE_MATCH(3, 5000), - FOUR_MATCH(4, 50000), - FIVE_MATCH(5, 1500000), - SIX_MATCH(6, 2000000000), - MISS(0, 0); + THREE_MATCH(5000, "3개 일치"), + FOUR_MATCH(50000, "4개 일치"), + FIVE_MATCH(1500000, "5개 일치"), + FIVE_BONUS_MATCH(30000000, "5개 일치, 보너스 볼 일치"), + SIX_MATCH(2000000000, "6개 일치"), + MISS(0, "꽝"); - private final int matchCount; private final int prizeMoney; + private final String displayName; - Rank(int matchCount, int prizeMoney) { - this.matchCount = matchCount; + Rank(int prizeMoney, String displayName) { this.prizeMoney = prizeMoney; + this.displayName = displayName; } - public static Rank from(int matchCount) { - return Arrays.stream(values()) - .filter(rank -> rank.matchCount == matchCount) - .findFirst() - .orElse(MISS); + public static Rank from(int matchCount, boolean bonusBallMatched) { + if (matchCount == 6) { + return SIX_MATCH; + } + if (matchCount == 5 && bonusBallMatched) { + return FIVE_BONUS_MATCH; + } + if (matchCount == 5) { + return FIVE_MATCH; + } + if (matchCount == 4) { + return FOUR_MATCH; + } + if (matchCount == 3) { + return THREE_MATCH; + } + return MISS; } public boolean isWinning() { @@ -32,7 +43,7 @@ public int getPrizeMoney() { return prizeMoney; } - public int getMatchCount() { - return matchCount; + public String getDisplayName() { + return displayName; } } diff --git a/src/main/java/domain/WinningLotto.java b/src/main/java/domain/WinningLotto.java new file mode 100644 index 000000000..c5a3ff958 --- /dev/null +++ b/src/main/java/domain/WinningLotto.java @@ -0,0 +1,25 @@ +package domain; + +public class WinningLotto { + + private final Lotto winningLotto; + private final BonusBall bonusBall; + + public WinningLotto(Lotto winningLotto, BonusBall bonusBall) { + validateWinningLotto(winningLotto, bonusBall); + this.winningLotto = winningLotto; + this.bonusBall = bonusBall; + } + + public Rank match(Lotto lotto) { + int matchCount = lotto.countMatch(this.winningLotto); + boolean bonusBallMatch = lotto.contains(bonusBall.number()); + return Rank.from(matchCount, bonusBallMatch); + } + + private void validateWinningLotto(Lotto winningLotto, BonusBall bonusBall) { + if (winningLotto.contains(bonusBall.number())) { + throw new IllegalArgumentException("보너스 볼은 당첨 번호와 중복될 수 없습니다."); + } + } +} diff --git a/src/main/java/domain/WinningStatistics.java b/src/main/java/domain/WinningStatistics.java index 69e593a08..984edd4fd 100644 --- a/src/main/java/domain/WinningStatistics.java +++ b/src/main/java/domain/WinningStatistics.java @@ -1,25 +1,21 @@ package domain; -import dto.WinningResult; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; +import java.util.EnumMap; import java.util.Map; public class WinningStatistics { private final Map statistics; public WinningStatistics() { - statistics = new HashMap<>(); + statistics = new EnumMap<>(Rank.class); initialize(); } - public static WinningStatistics from(Lottos lottos, Lotto winningLotto) { + public static WinningStatistics from(Lottos lottos, WinningLotto winningLotto) { WinningStatistics statistics = new WinningStatistics(); lottos.lottoToList().stream() - .map(lotto -> Rank.from(lotto.countMatch(winningLotto))) + .map(winningLotto::match) .forEach(statistics::add); return statistics; @@ -37,21 +33,6 @@ private void add(Rank rank) { statistics.put(rank, rankCount); } - public List winningResults() { - return Arrays.stream(Rank.values()) - .filter(Rank::isWinning) - .map(this::toWinningResult) - .toList(); - } - - private WinningResult toWinningResult(Rank rank) { - return new WinningResult( - rank.getMatchCount(), - rank.getPrizeMoney(), - statistics.get(rank) - ); - } - public int countOf(Rank rank) { return statistics.get(rank); } diff --git a/src/main/java/dto/WinningResult.java b/src/main/java/dto/WinningResult.java index f6ace10a3..4e595b0c8 100644 --- a/src/main/java/dto/WinningResult.java +++ b/src/main/java/dto/WinningResult.java @@ -1,4 +1,20 @@ package dto; -public record WinningResult(int matchCount, int prizeMoney, int count) { +import domain.Rank; +import domain.WinningStatistics; + +import java.util.Arrays; +import java.util.List; + +public record WinningResult(String message, int count) { + public static List from(WinningStatistics winningStatistics) { + return Arrays.stream(Rank.values()) + .filter(Rank::isWinning) + .map(rank -> createMessage(rank, winningStatistics.countOf(rank))) + .toList(); + } + + private static WinningResult createMessage(Rank rank, int count) { + return new WinningResult(rank.getDisplayName() + " (" + rank.getPrizeMoney() + "원)", count); + } } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index f0e6eb60d..cac7c8d49 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,5 +1,6 @@ package view; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Scanner; @@ -12,13 +13,32 @@ public int readAmount() { return Integer.parseInt(scanner.nextLine()); } + public int readManualCount() { + System.out.println("수동으로 구매할 로또 수를 입력해 주세요."); + return Integer.parseInt(scanner.nextLine()); + } + + public List> readManualNumbers(int count) { + System.out.println("수동으로 구매할 번호를 입력해 주세요."); + List> manualNumbers = new ArrayList<>(); + for (int i = 0; i < count; i++) { + manualNumbers.add(parseLottoNumbers(scanner.nextLine())); + } + return manualNumbers; + } + public List readWinningNumbers() { System.out.println("지난 주 당첨 번호를 입력해 주세요."); String input = scanner.nextLine(); - return parseWinningNumbers(input); + return parseLottoNumbers(input); + } + + public int readBonusBall() { + System.out.println("보너스 볼을 입력해 주세요."); + return Integer.parseInt(scanner.nextLine()); } - private List parseWinningNumbers(String input) { + private List parseLottoNumbers(String input) { return Arrays.stream(input.split(",")) .map(String::trim) .map(Integer::parseInt) diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index f57d71ee0..6ce34d8e7 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -6,13 +6,13 @@ public class OutputView { - public void printResultHeader(int count) { - System.out.println(count + "개를 구매했습니다."); + public void printResultHeader(int manualCount, int autoCount) { + System.out.println("수동으로 " + manualCount + "장, 자동으로 " + autoCount + "개를 구매했습니다."); } public void printLottos(List> lottoNumbers) { for (List numbers : lottoNumbers) { - System.out.println(sortLotto(numbers)); + System.out.println(numbers); } } @@ -21,12 +21,6 @@ public void printWinningStatistics(List winningResults) { printRankCount(winningResults); } - private List sortLotto(List lotto) { - return lotto.stream() - .sorted() - .toList(); - } - private void printStatisticsHeader() { System.out.println(); System.out.println("당첨 통계"); @@ -35,11 +29,15 @@ private void printStatisticsHeader() { private void printRankCount(List winningResults) { for (WinningResult winningResult : winningResults) { - System.out.println(winningResult.matchCount() + "개 일치" + " (" + winningResult.prizeMoney() + "원)-" + winningResult.count() + "개"); + System.out.println(winningResult.message() + " - " + winningResult.count() + "개"); } } public void printProfitRate(double profitRate) { System.out.println("총 수익률은 " + String.format("%.2f", profitRate) + "입니다."); } + + public void printErrorMessage(String message) { + System.out.println("[ERROR] " + message); + } } diff --git a/src/test/java/domain/LottoShopTest.java b/src/test/java/domain/LottoShopTest.java index d3ebedb1a..3af690d38 100644 --- a/src/test/java/domain/LottoShopTest.java +++ b/src/test/java/domain/LottoShopTest.java @@ -11,7 +11,7 @@ class LottoShopTest { @Test - void 구입금액만큼_로또를_생성한다() { + void 구입금액에_맞게_자동_로또를_생성한다() { NumberGenerator numberGenerator = new TestNumberGenerator(List.of( new LottoNumber(1), new LottoNumber(2), new LottoNumber(3), new LottoNumber(4), @@ -19,26 +19,37 @@ class LottoShopTest { )); LottoShop lottoShop = new LottoShop(numberGenerator); - Lottos lottos = lottoShop.purchase(new PurchaseAmount(3000)); + Lottos lottos = lottoShop.purchase(new PurchaseAmount(3000), new Lottos(List.of())); assertThat(lottos.toNumberLists()).hasSize(3); + assertThat(lottos.toNumberLists()).containsExactly( + List.of(1, 2, 3, 4, 5, 6), + List.of(1, 2, 3, 4, 5, 6), + List.of(1, 2, 3, 4, 5, 6) + ); } @Test - void 생성된_번호로_로또가_구성된다() { + void 수동_로또와_자동_로또를_함께_반환한다() { NumberGenerator numberGenerator = new TestNumberGenerator(List.of( - new LottoNumber(1), new LottoNumber(2), - new LottoNumber(3), new LottoNumber(4), - new LottoNumber(5), new LottoNumber(6) + new LottoNumber(7), new LottoNumber(8), + new LottoNumber(9), new LottoNumber(10), + new LottoNumber(11), new LottoNumber(12) )); LottoShop lottoShop = new LottoShop(numberGenerator); - Lottos lottos = lottoShop.purchase(new PurchaseAmount(3000)); + Lottos manualLottos = new Lottos(List.of( + new Lotto(List.of( + new LottoNumber(1), new LottoNumber(2), new LottoNumber(3), + new LottoNumber(4), new LottoNumber(5), new LottoNumber(6) + )) + )); + + Lottos lottos = lottoShop.purchase(new PurchaseAmount(2000), manualLottos); assertThat(lottos.toNumberLists()).containsExactly( List.of(1, 2, 3, 4, 5, 6), - List.of(1, 2, 3, 4, 5, 6), - List.of(1, 2, 3, 4, 5, 6) + List.of(7, 8, 9, 10, 11, 12) ); } } diff --git a/src/test/java/domain/ManualLottoCountTest.java b/src/test/java/domain/ManualLottoCountTest.java new file mode 100644 index 000000000..576f63526 --- /dev/null +++ b/src/test/java/domain/ManualLottoCountTest.java @@ -0,0 +1,26 @@ +package domain; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ManualLottoCountTest { + + @Test + void 수동_구매_수가_0_미만이면_예외가_발생한다() { + PurchaseAmount purchaseAmount = new PurchaseAmount(5000); + + assertThatThrownBy(() -> new ManualLottoCount(-1, purchaseAmount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("수동 구매 수는 0 이상이어야 합니다."); + } + + @Test + void 수동_구매_수가_총_구매_가능_수를_초과하면_예외가_발생한다() { + PurchaseAmount purchaseAmount = new PurchaseAmount(4000); + + assertThatThrownBy(() -> new ManualLottoCount(5, purchaseAmount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("수동 로또 수는 총 로또 수를 넘을 수 없습니다."); + } +} diff --git a/src/test/java/domain/WinningStatisticsTest.java b/src/test/java/domain/WinningStatisticsTest.java index ca6c0a4cc..78987d4cc 100644 --- a/src/test/java/domain/WinningStatisticsTest.java +++ b/src/test/java/domain/WinningStatisticsTest.java @@ -8,17 +8,19 @@ import static org.assertj.core.api.Assertions.assertThat; class WinningStatisticsTest { - private static final Lotto WINNING_LOTTO = createLotto(1, 2, 3, 4, 5, 6); + private static final WinningLotto WINNING_LOTTO = new WinningLotto(createLotto(1, 2, 3, 4, 5, 6), new BonusBall(new LottoNumber(7))); private static final Lotto THREE_MATCH_LOTTO = createLotto(1, 2, 3, 7, 8, 9); private static final Lotto FOUR_MATCH_LOTTO = createLotto(1, 2, 3, 4, 10, 11); private static final Lotto FIVE_MATCH_LOTTO = createLotto(1, 2, 3, 4, 5, 12); + private static final Lotto FIVE_BONUS_MATCH_LOTTO = createLotto(1, 2, 3, 4, 5, 7); @Test void 당첨_결과별_개수를_집계한다() { Lottos lottos = new Lottos(List.of( THREE_MATCH_LOTTO, FOUR_MATCH_LOTTO, - FIVE_MATCH_LOTTO + FIVE_MATCH_LOTTO, + FIVE_BONUS_MATCH_LOTTO )); WinningStatistics winningStatistics = WinningStatistics.from(lottos, WINNING_LOTTO); @@ -26,6 +28,7 @@ class WinningStatisticsTest { assertThat(winningStatistics.countOf(Rank.THREE_MATCH)).isEqualTo(1); assertThat(winningStatistics.countOf(Rank.FOUR_MATCH)).isEqualTo(1); assertThat(winningStatistics.countOf(Rank.FIVE_MATCH)).isEqualTo(1); + assertThat(winningStatistics.countOf(Rank.FIVE_BONUS_MATCH)).isEqualTo(1); } @Test