From 488847e01d2753a756e47fec0f1068b93030a911 Mon Sep 17 00:00:00 2001 From: jisu-om Date: Fri, 1 Dec 2023 00:26:51 +0900 Subject: [PATCH 1/7] =?UTF-8?q?Docs:=20=EA=B5=AC=ED=98=84=ED=95=A0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..1208da54c3b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,66 @@ +# 구현할 기능 목록 +- [ ] 구입 금액을 입력받고, 구입 금액에 해당하는 만큼 로또를 발행한다. + - [ ] 로또 1장의 가격은 1,000원이다. 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다. + + +- [ ] 발행한 로또 수량 및 번호를 출력한다. + - [ ] 로또 번호는 오름차순으로 정렬하여 보여준다. + - [ ] 로또 번호의 숫자 범위는 1~45까지이다. + - [ ] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. + + +- [ ] 당첨 번호를 입력받는다. + - [ ] 1 이상 45 이하, 중복되지 않는 6개의 숫자인지 검증 + + +- [ ] 보너스 번호를 입력받는다. + - [ ] 1 이상 45 이하 이고, 당첨 번호와 중복되지 않는 숫자 여야 함 + + +- [ ] 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 게임을 종료한다. + - [ ] 당첨 내역을 출력한다. + - [ ] 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다. + - 1등: 6개 번호 일치 / 2,000,000,000원 + - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 + - 3등: 5개 번호 일치 / 1,500,000원 + - 4등: 4개 번호 일치 / 50,000원 + - 5등: 3개 번호 일치 / 5,000원 + - [ ] 수익률을 출력한다. + - [ ] 수익률은 소수점 둘째 자리에서 반올림한다. + - (ex. 100.0%, 51.5%, 1,000,000.0%) + + +- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. + + +--- +## 전체 흐름 +``` +구입금액을 입력해 주세요. +8000 + +8개를 구매했습니다. +[8, 21, 23, 41, 42, 43] +[3, 5, 11, 16, 32, 38] +[7, 11, 16, 35, 36, 44] +[1, 8, 11, 31, 41, 42] +[13, 14, 16, 38, 42, 45] +[7, 11, 30, 40, 42, 43] +[2, 13, 22, 32, 38, 45] +[1, 3, 5, 14, 22, 45] + +당첨 번호를 입력해 주세요. +1,2,3,4,5,6 + +보너스 번호를 입력해 주세요. +7 + +당첨 통계 +--- +3개 일치 (5,000원) - 1개 +4개 일치 (50,000원) - 0개 +5개 일치 (1,500,000원) - 0개 +5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 +6개 일치 (2,000,000,000원) - 0개 +총 수익률은 62.5%입니다. +``` \ No newline at end of file From aa1a0692358c007e86c6c1584daa76ec197dc784 Mon Sep 17 00:00:00 2001 From: jisu-om Date: Fri, 1 Dec 2023 00:39:31 +0900 Subject: [PATCH 2/7] =?UTF-8?q?Feat:=20=EA=B5=AC=EC=9E=85=20=EA=B8=88?= =?UTF-8?q?=EC=95=A1=20=EC=9E=85=EB=A0=A5=EB=B0=9B=EA=B3=A0=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 5 +- src/main/java/lotto/Application.java | 7 ++- .../java/lotto/controller/MainController.java | 58 +++++++++++++++++++ src/main/java/lotto/{ => domain}/Lotto.java | 2 +- .../java/lotto/domain/PurchaseAmount.java | 19 ++++++ .../java/lotto/exception/ErrorMessage.java | 18 ++++++ src/main/java/lotto/utils/Mapper.java | 4 ++ .../lotto/utils/PurchaseAmountValidator.java | 27 +++++++++ src/main/java/lotto/view/InputView.java | 22 +++++++ src/main/java/lotto/view/OutputView.java | 18 ++++++ src/test/java/lotto/LottoTest.java | 1 + 11 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 src/main/java/lotto/controller/MainController.java rename src/main/java/lotto/{ => domain}/Lotto.java (94%) create mode 100644 src/main/java/lotto/domain/PurchaseAmount.java create mode 100644 src/main/java/lotto/exception/ErrorMessage.java create mode 100644 src/main/java/lotto/utils/Mapper.java create mode 100644 src/main/java/lotto/utils/PurchaseAmountValidator.java create mode 100644 src/main/java/lotto/view/InputView.java create mode 100644 src/main/java/lotto/view/OutputView.java diff --git a/docs/README.md b/docs/README.md index 1208da54c3b..ec44f7c4c2e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # 구현할 기능 목록 -- [ ] 구입 금액을 입력받고, 구입 금액에 해당하는 만큼 로또를 발행한다. - - [ ] 로또 1장의 가격은 1,000원이다. 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다. +- [X] 구입 금액을 입력받고, 구입 금액에 해당하는 만큼 로또를 발행한다. + - [X] 로또 1장의 가격은 1,000원이다. 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다. - [ ] 발행한 로또 수량 및 번호를 출력한다. @@ -25,6 +25,7 @@ - 3등: 5개 번호 일치 / 1,500,000원 - 4등: 4개 번호 일치 / 50,000원 - 5등: 3개 번호 일치 / 5,000원 + - [ ] 수익률을 출력한다. - [ ] 수익률은 소수점 둘째 자리에서 반올림한다. - (ex. 100.0%, 51.5%, 1,000,000.0%) diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922ba44..45ef536469e 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,12 @@ package lotto; +import camp.nextstep.edu.missionutils.Console; +import lotto.controller.MainController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + MainController mainController = MainController.create(); + mainController.run(); + Console.close(); } } diff --git a/src/main/java/lotto/controller/MainController.java b/src/main/java/lotto/controller/MainController.java new file mode 100644 index 00000000000..a34499dce12 --- /dev/null +++ b/src/main/java/lotto/controller/MainController.java @@ -0,0 +1,58 @@ +package lotto.controller; + +import lotto.domain.PurchaseAmount; +import lotto.view.InputView; +import lotto.view.OutputView; + +import java.util.function.Supplier; + +public class MainController { + private final InputView inputView; + private final OutputView outputView; + //private OrderController orderController; //컨트롤러 추가하는 경우 + + private MainController(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public static MainController create() { + return new MainController(InputView.getInstance(), OutputView.getInstance()); + } + + public void run() { + PurchaseAmount purchaseAmount = createPurchaseAmount(); + + } + + private void initializeControllers() { + orderController = OrderController.of(inputView, outputView); + } + + + private PurchaseAmount createPurchaseAmount() { + return readUserInput(() -> { + long input = inputView.readPurchaseAmount(); + return PurchaseAmount.of(input); + }); + } + + private Orders createOrders() { + return readUserInput(() -> { + List items = inputView.readOrders().stream() + .map(OrderItemDto::toOrderItem) + .toList(); + return Orders.from(items); + }); + } + + private T readUserInput(Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + outputView.printError(e.getMessage()); + } + } + } +} diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/domain/Lotto.java similarity index 94% rename from src/main/java/lotto/Lotto.java rename to src/main/java/lotto/domain/Lotto.java index 519793d1f73..366657953a2 100644 --- a/src/main/java/lotto/Lotto.java +++ b/src/main/java/lotto/domain/Lotto.java @@ -1,4 +1,4 @@ -package lotto; +package lotto.domain; import java.util.List; diff --git a/src/main/java/lotto/domain/PurchaseAmount.java b/src/main/java/lotto/domain/PurchaseAmount.java new file mode 100644 index 00000000000..2d92b7a524c --- /dev/null +++ b/src/main/java/lotto/domain/PurchaseAmount.java @@ -0,0 +1,19 @@ +package lotto.domain; + +import lotto.utils.PurchaseAmountValidator; + +public class PurchaseAmount { + private final long amount; + + private PurchaseAmount(long amount) { + this.amount = amount; + } + + public static PurchaseAmount of(long amount) { + PurchaseAmountValidator.validatePositive(amount); + PurchaseAmountValidator.validateDividedByUnit(amount); + return new PurchaseAmount(amount); + } + + +} diff --git a/src/main/java/lotto/exception/ErrorMessage.java b/src/main/java/lotto/exception/ErrorMessage.java new file mode 100644 index 00000000000..da15f439c19 --- /dev/null +++ b/src/main/java/lotto/exception/ErrorMessage.java @@ -0,0 +1,18 @@ +package lotto.exception; + +public enum ErrorMessage { + ERROR_CAPTION("[ERROR] "), + NOT_NUMERIC_INPUT("숫자를 입력해 주세요."), + NOT_POSITIVE_INPUT("0 보다 큰 수를 입력해 주세요."), + INVALID_PURCHASE_AMOUNT("1000 으로 나누어 떨어지는 값을 입력해 주세요."); + + private final String message; + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return ERROR_CAPTION.message + message; + } +} diff --git a/src/main/java/lotto/utils/Mapper.java b/src/main/java/lotto/utils/Mapper.java new file mode 100644 index 00000000000..3dac0f817f8 --- /dev/null +++ b/src/main/java/lotto/utils/Mapper.java @@ -0,0 +1,4 @@ +package lotto.utils; + +public class Mapper { +} diff --git a/src/main/java/lotto/utils/PurchaseAmountValidator.java b/src/main/java/lotto/utils/PurchaseAmountValidator.java new file mode 100644 index 00000000000..ca886b2fc79 --- /dev/null +++ b/src/main/java/lotto/utils/PurchaseAmountValidator.java @@ -0,0 +1,27 @@ +package lotto.utils; + +import static lotto.exception.ErrorMessage.*; + +public class PurchaseAmountValidator { + private static final int PURCHASE_AMOUNT_UNIT = 1000; + + public static long safeParseLong(String input) { + try { + return Long.parseLong(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(NOT_NUMERIC_INPUT.getMessage()); + } + } + + public static void validatePositive(long value) { + if (value <= 0) { + throw new IllegalArgumentException(NOT_POSITIVE_INPUT.getMessage()); + } + } + + public static void validateDividedByUnit(long value) { + if (value % PURCHASE_AMOUNT_UNIT != 0) { + throw new IllegalArgumentException(INVALID_PURCHASE_AMOUNT.getMessage()); + } + } +} diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java new file mode 100644 index 00000000000..f4e4a60aa90 --- /dev/null +++ b/src/main/java/lotto/view/InputView.java @@ -0,0 +1,22 @@ +package lotto.view; + +import camp.nextstep.edu.missionutils.Console; +import lotto.utils.PurchaseAmountValidator; + +public class InputView { + private static final InputView instance = new InputView(); + private static final String ASK_PURCHASE_AMOUNT = "구입금액을 입력해 주세요."; + + private InputView() { + } + + public static InputView getInstance() { + return instance; + } + + public long readPurchaseAmount() { + System.out.println(ASK_PURCHASE_AMOUNT); + String input = Console.readLine(); + return PurchaseAmountValidator.safeParseLong(input); + } +} diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java new file mode 100644 index 00000000000..4469dad2d2d --- /dev/null +++ b/src/main/java/lotto/view/OutputView.java @@ -0,0 +1,18 @@ +package lotto.view; + +public class OutputView { + private static final OutputView instance = new OutputView(); + private static final String START_MESSAGE = ""; + + private OutputView() { + } + + public static OutputView getInstance() { + return instance; + } + + public void printError(String errorMessage) { + System.out.println(errorMessage); + } + +} diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 9f5dfe7eb83..0e4d4f73896 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/LottoTest.java @@ -1,5 +1,6 @@ package lotto; +import lotto.domain.Lotto; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From 13e8413052d2e6b09e939f57df096bf421954c3c Mon Sep 17 00:00:00 2001 From: jisu-om Date: Fri, 1 Dec 2023 01:21:46 +0900 Subject: [PATCH 3/7] =?UTF-8?q?Feat:=20=EA=B5=AC=EC=9E=85=20=EA=B8=88?= =?UTF-8?q?=EC=95=A1=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=A1=9C=EB=98=90?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=9C=ED=96=89=ED=95=98=EA=B3=A0=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 8 ++--- src/main/java/lotto/constants/Constants.java | 8 +++++ .../java/lotto/controller/MainController.java | 20 ++++++++--- src/main/java/lotto/domain/Lotto.java | 33 ++++++++++++++++-- src/main/java/lotto/domain/Lottos.java | 23 +++++++++++++ .../java/lotto/domain/PurchaseAmount.java | 8 +++-- src/main/java/lotto/dto/LottoDto.java | 6 ++++ src/main/java/lotto/dto/LottosDto.java | 6 ++++ .../java/lotto/exception/ErrorMessage.java | 5 ++- src/main/java/lotto/service/LottoMaker.java | 15 ++++++++ .../lotto/utils/LottoNumbersGenerator.java | 20 +++++++++++ src/main/java/lotto/utils/Mapper.java | 12 +++++++ .../lotto/utils/PurchaseAmountValidator.java | 5 ++- src/main/java/lotto/view/OutputView.java | 34 ++++++++++++++++++- 14 files changed, 184 insertions(+), 19 deletions(-) create mode 100644 src/main/java/lotto/constants/Constants.java create mode 100644 src/main/java/lotto/domain/Lottos.java create mode 100644 src/main/java/lotto/dto/LottoDto.java create mode 100644 src/main/java/lotto/dto/LottosDto.java create mode 100644 src/main/java/lotto/service/LottoMaker.java create mode 100644 src/main/java/lotto/utils/LottoNumbersGenerator.java diff --git a/docs/README.md b/docs/README.md index ec44f7c4c2e..e6b6eccbaa8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,10 +3,10 @@ - [X] 로또 1장의 가격은 1,000원이다. 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다. -- [ ] 발행한 로또 수량 및 번호를 출력한다. - - [ ] 로또 번호는 오름차순으로 정렬하여 보여준다. - - [ ] 로또 번호의 숫자 범위는 1~45까지이다. - - [ ] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. +- [X] 발행한 로또 수량 및 번호를 출력한다. + - [X] 로또 번호는 오름차순으로 정렬하여 보여준다. + - [X] 로또 번호의 숫자 범위는 1~45까지이다. + - [X] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. - [ ] 당첨 번호를 입력받는다. diff --git a/src/main/java/lotto/constants/Constants.java b/src/main/java/lotto/constants/Constants.java new file mode 100644 index 00000000000..15aa0e45a2b --- /dev/null +++ b/src/main/java/lotto/constants/Constants.java @@ -0,0 +1,8 @@ +package lotto.constants; + +public class Constants { + public static final int LOTTO_PRICE = 1000; + public static final int MIN_LOTTO_NUMBER = 1; + public static final int MAX_LOTTO_NUMBER = 45; + public static final int LOTTO_NUMBERS_SIZE = 6; +} diff --git a/src/main/java/lotto/controller/MainController.java b/src/main/java/lotto/controller/MainController.java index a34499dce12..b2f9dd9d43b 100644 --- a/src/main/java/lotto/controller/MainController.java +++ b/src/main/java/lotto/controller/MainController.java @@ -1,9 +1,15 @@ package lotto.controller; +import lotto.domain.Lotto; +import lotto.domain.Lottos; import lotto.domain.PurchaseAmount; +import lotto.dto.LottosDto; +import lotto.service.LottoMaker; +import lotto.utils.Mapper; import lotto.view.InputView; import lotto.view.OutputView; +import java.util.List; import java.util.function.Supplier; public class MainController { @@ -22,18 +28,22 @@ public static MainController create() { public void run() { PurchaseAmount purchaseAmount = createPurchaseAmount(); + long quantityOfLotto = purchaseAmount.getQuantityOfLotto(); + List lottosMade = LottoMaker.makeLottos(quantityOfLotto); + Lottos lottos = Lottos.from(lottosMade); + LottosDto lottosDto = Mapper.toLottosDto(lottos); + outputView.printLottos(lottosDto); } - private void initializeControllers() { - orderController = OrderController.of(inputView, outputView); - } - +// private void initializeControllers() { +// orderController = OrderController.of(inputView, outputView); +// } private PurchaseAmount createPurchaseAmount() { return readUserInput(() -> { long input = inputView.readPurchaseAmount(); - return PurchaseAmount.of(input); + return PurchaseAmount.from(input); }); } diff --git a/src/main/java/lotto/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java index 366657953a2..fd09a743daf 100644 --- a/src/main/java/lotto/domain/Lotto.java +++ b/src/main/java/lotto/domain/Lotto.java @@ -1,7 +1,11 @@ package lotto.domain; +import java.util.HashSet; import java.util.List; +import static lotto.constants.Constants.*; +import static lotto.exception.ErrorMessage.*; + public class Lotto { private final List numbers; @@ -11,10 +15,33 @@ public Lotto(List numbers) { } private void validate(List numbers) { - if (numbers.size() != 6) { - throw new IllegalArgumentException(); + validateSize(numbers); + validateDuplicates(numbers); + validateRange(numbers); + } + + private static void validateSize(List numbers) { + if (numbers.size() != LOTTO_NUMBERS_SIZE) { + throw new IllegalArgumentException(INVALID_LOTTO_NUMBERS_SIZE.getMessage()); + } + } + + private void validateDuplicates(List numbers) { + HashSet uniqueNumbers = new HashSet<>(numbers); + if (numbers.size() != uniqueNumbers.size()) { + throw new IllegalArgumentException(DUPLICATED_LOTTO_NUMBERS.getMessage()); } } - // TODO: 추가 기능 구현 + private void validateRange(List numbers) { + boolean isOutOfRange = numbers.stream() + .anyMatch(number -> number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER); + if (isOutOfRange) { + throw new IllegalArgumentException(INVALID_LOTTO_NUMBER_RANGE.getMessage()); + } + } + + public List getNumbers() { + return List.copyOf(numbers); + } } diff --git a/src/main/java/lotto/domain/Lottos.java b/src/main/java/lotto/domain/Lottos.java new file mode 100644 index 00000000000..a9c31e18ecc --- /dev/null +++ b/src/main/java/lotto/domain/Lottos.java @@ -0,0 +1,23 @@ +package lotto.domain; + +import java.util.List; + +public class Lottos { + private final List lottos; + + private Lottos(List lottos) { + this.lottos = lottos; + } + + public static Lottos from(List lottos) { + return new Lottos(lottos); + } + + public List getLottos() { + return List.copyOf(lottos); + } + + public long getQuantity() { + return lottos.size(); + } +} diff --git a/src/main/java/lotto/domain/PurchaseAmount.java b/src/main/java/lotto/domain/PurchaseAmount.java index 2d92b7a524c..5b0cc9f55fc 100644 --- a/src/main/java/lotto/domain/PurchaseAmount.java +++ b/src/main/java/lotto/domain/PurchaseAmount.java @@ -2,6 +2,8 @@ import lotto.utils.PurchaseAmountValidator; +import static lotto.constants.Constants.LOTTO_PRICE; + public class PurchaseAmount { private final long amount; @@ -9,11 +11,13 @@ private PurchaseAmount(long amount) { this.amount = amount; } - public static PurchaseAmount of(long amount) { + public static PurchaseAmount from(long amount) { PurchaseAmountValidator.validatePositive(amount); PurchaseAmountValidator.validateDividedByUnit(amount); return new PurchaseAmount(amount); } - + public long getQuantityOfLotto() { + return amount / LOTTO_PRICE; + } } diff --git a/src/main/java/lotto/dto/LottoDto.java b/src/main/java/lotto/dto/LottoDto.java new file mode 100644 index 00000000000..98f9549d60d --- /dev/null +++ b/src/main/java/lotto/dto/LottoDto.java @@ -0,0 +1,6 @@ +package lotto.dto; + +import java.util.List; + +public record LottoDto(List numbers) { +} diff --git a/src/main/java/lotto/dto/LottosDto.java b/src/main/java/lotto/dto/LottosDto.java new file mode 100644 index 00000000000..e197ea58a23 --- /dev/null +++ b/src/main/java/lotto/dto/LottosDto.java @@ -0,0 +1,6 @@ +package lotto.dto; + +import java.util.List; + +public record LottosDto(long quantity, List lottoDtos) { +} diff --git a/src/main/java/lotto/exception/ErrorMessage.java b/src/main/java/lotto/exception/ErrorMessage.java index da15f439c19..75fa23b5cdd 100644 --- a/src/main/java/lotto/exception/ErrorMessage.java +++ b/src/main/java/lotto/exception/ErrorMessage.java @@ -4,7 +4,10 @@ public enum ErrorMessage { ERROR_CAPTION("[ERROR] "), NOT_NUMERIC_INPUT("숫자를 입력해 주세요."), NOT_POSITIVE_INPUT("0 보다 큰 수를 입력해 주세요."), - INVALID_PURCHASE_AMOUNT("1000 으로 나누어 떨어지는 값을 입력해 주세요."); + INVALID_PURCHASE_AMOUNT("1000 으로 나누어 떨어지는 값을 입력해 주세요."), + INVALID_LOTTO_NUMBERS_SIZE("유효하지 않은 로또 숫자 개수 입니다."), + DUPLICATED_LOTTO_NUMBERS("중복된 로또 숫자가 있습니다."), + INVALID_LOTTO_NUMBER_RANGE("1 이상 45 이하의 숫자만 가능합니다."); private final String message; diff --git a/src/main/java/lotto/service/LottoMaker.java b/src/main/java/lotto/service/LottoMaker.java new file mode 100644 index 00000000000..9d8c2d09bbc --- /dev/null +++ b/src/main/java/lotto/service/LottoMaker.java @@ -0,0 +1,15 @@ +package lotto.service; + +import lotto.domain.Lotto; +import lotto.utils.LottoNumbersGenerator; + +import java.util.List; +import java.util.stream.Stream; + +public class LottoMaker { + public static List makeLottos(long quantity) { + return Stream.generate(() -> new Lotto(LottoNumbersGenerator.generate())) + .limit(quantity) + .toList(); + } +} diff --git a/src/main/java/lotto/utils/LottoNumbersGenerator.java b/src/main/java/lotto/utils/LottoNumbersGenerator.java new file mode 100644 index 00000000000..5732af678ce --- /dev/null +++ b/src/main/java/lotto/utils/LottoNumbersGenerator.java @@ -0,0 +1,20 @@ +package lotto.utils; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static lotto.constants.Constants.LOTTO_NUMBERS_SIZE; +import static lotto.constants.Constants.MIN_LOTTO_NUMBER; +import static lotto.constants.Constants.MAX_LOTTO_NUMBER; + +public class LottoNumbersGenerator { + public static List generate() { + List numbers = Randoms.pickUniqueNumbersInRange(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER, LOTTO_NUMBERS_SIZE); + List sortedNumbers = new ArrayList<>(numbers); + Collections.sort(sortedNumbers); + return sortedNumbers; + } +} diff --git a/src/main/java/lotto/utils/Mapper.java b/src/main/java/lotto/utils/Mapper.java index 3dac0f817f8..9995bc0246e 100644 --- a/src/main/java/lotto/utils/Mapper.java +++ b/src/main/java/lotto/utils/Mapper.java @@ -1,4 +1,16 @@ package lotto.utils; +import lotto.domain.Lottos; +import lotto.dto.LottoDto; +import lotto.dto.LottosDto; + +import java.util.List; + public class Mapper { + public static LottosDto toLottosDto(Lottos lottos) { + List lottoDtos = lottos.getLottos().stream() + .map(lotto -> new LottoDto(lotto.getNumbers())) + .toList(); + return new LottosDto(lottos.getQuantity(), lottoDtos); + } } diff --git a/src/main/java/lotto/utils/PurchaseAmountValidator.java b/src/main/java/lotto/utils/PurchaseAmountValidator.java index ca886b2fc79..add86e991cf 100644 --- a/src/main/java/lotto/utils/PurchaseAmountValidator.java +++ b/src/main/java/lotto/utils/PurchaseAmountValidator.java @@ -1,10 +1,9 @@ package lotto.utils; +import static lotto.constants.Constants.LOTTO_PRICE; import static lotto.exception.ErrorMessage.*; public class PurchaseAmountValidator { - private static final int PURCHASE_AMOUNT_UNIT = 1000; - public static long safeParseLong(String input) { try { return Long.parseLong(input); @@ -20,7 +19,7 @@ public static void validatePositive(long value) { } public static void validateDividedByUnit(long value) { - if (value % PURCHASE_AMOUNT_UNIT != 0) { + if (value % LOTTO_PRICE != 0) { throw new IllegalArgumentException(INVALID_PURCHASE_AMOUNT.getMessage()); } } diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java index 4469dad2d2d..206929fab79 100644 --- a/src/main/java/lotto/view/OutputView.java +++ b/src/main/java/lotto/view/OutputView.java @@ -1,8 +1,18 @@ package lotto.view; +import lotto.dto.LottoDto; +import lotto.dto.LottosDto; + +import java.util.stream.Collectors; + public class OutputView { private static final OutputView instance = new OutputView(); - private static final String START_MESSAGE = ""; + private static final String LOTTOS_QUANTITY_FORMAT = "%d개를 구매했습니다."; + private static final String START_LOTTO_FORMAT = "["; + private static final String END_LOTTO_FORMAT = "]"; + private static final String LOTTO_NUMBERS_DELIMITER = ", "; + + private OutputView() { } @@ -15,4 +25,26 @@ public void printError(String errorMessage) { System.out.println(errorMessage); } + public void printLottos(LottosDto lottosDto) { + printLottoQuantity(lottosDto.quantity()); + lottosDto.lottoDtos().forEach(this::printLotto); + } + public void printLottoQuantity(long quantity) { + printLine(); + System.out.printf((LOTTOS_QUANTITY_FORMAT) + "%n", quantity); + } + + private void printLine() { + System.out.println(); + } + + private void printLotto(LottoDto lottoDto) { + System.out.print(START_LOTTO_FORMAT); + String lottoNumbers = lottoDto.numbers().stream() + .map(Object::toString) + .collect(Collectors.joining(LOTTO_NUMBERS_DELIMITER)); + System.out.print(lottoNumbers); + System.out.print(END_LOTTO_FORMAT); + printLine(); + } } From 118dfb3c68a1916fdd15938b5a4d58aed334a847 Mon Sep 17 00:00:00 2001 From: jisu-om Date: Fri, 1 Dec 2023 01:43:55 +0900 Subject: [PATCH 4/7] =?UTF-8?q?Feat:=20=EB=8B=B9=EC=B2=A8=EB=B2=88?= =?UTF-8?q?=ED=98=B8,=20=EB=B3=B4=EB=84=88=EC=8A=A4=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=EB=B0=9B=EA=B3=A0=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 8 ++-- .../java/lotto/controller/MainController.java | 19 ++++++--- src/main/java/lotto/domain/WinningLotto.java | 38 ++++++++++++++++++ .../java/lotto/exception/ErrorMessage.java | 4 +- .../lotto/utils/LottoNumbersGenerator.java | 1 + .../lotto/utils/WinningLottoValidator.java | 39 +++++++++++++++++++ src/main/java/lotto/view/InputView.java | 25 ++++++++++++ 7 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 src/main/java/lotto/domain/WinningLotto.java create mode 100644 src/main/java/lotto/utils/WinningLottoValidator.java diff --git a/docs/README.md b/docs/README.md index e6b6eccbaa8..7a9695d1ea8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,12 +9,12 @@ - [X] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. -- [ ] 당첨 번호를 입력받는다. - - [ ] 1 이상 45 이하, 중복되지 않는 6개의 숫자인지 검증 +- [X] 당첨 번호를 입력받는다. + - [X] 1 이상 45 이하, 중복되지 않는 6개의 숫자인지 검증 -- [ ] 보너스 번호를 입력받는다. - - [ ] 1 이상 45 이하 이고, 당첨 번호와 중복되지 않는 숫자 여야 함 +- [X] 보너스 번호를 입력받는다. + - [X] 1 이상 45 이하 이고, 당첨 번호와 중복되지 않는 숫자 여야 함 - [ ] 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 게임을 종료한다. diff --git a/src/main/java/lotto/controller/MainController.java b/src/main/java/lotto/controller/MainController.java index b2f9dd9d43b..c3505ae536b 100644 --- a/src/main/java/lotto/controller/MainController.java +++ b/src/main/java/lotto/controller/MainController.java @@ -3,6 +3,7 @@ import lotto.domain.Lotto; import lotto.domain.Lottos; import lotto.domain.PurchaseAmount; +import lotto.domain.WinningLotto; import lotto.dto.LottosDto; import lotto.service.LottoMaker; import lotto.utils.Mapper; @@ -33,6 +34,9 @@ public void run() { Lottos lottos = Lottos.from(lottosMade); LottosDto lottosDto = Mapper.toLottosDto(lottos); outputView.printLottos(lottosDto); + Lotto winningNumbers = readWinningNumbers(); + WinningLotto winningLotto = readWinningLotto(winningNumbers); + } @@ -47,12 +51,17 @@ private PurchaseAmount createPurchaseAmount() { }); } - private Orders createOrders() { + private Lotto readWinningNumbers() { + return readUserInput(() -> { + List numbers = inputView.readWinningNumbers(); + return new Lotto(numbers); + }); + } + + private WinningLotto readWinningLotto(Lotto winningNumbers) { return readUserInput(() -> { - List items = inputView.readOrders().stream() - .map(OrderItemDto::toOrderItem) - .toList(); - return Orders.from(items); + int bonusNumber = inputView.readBonusNumber(); + return WinningLotto.of(winningNumbers, bonusNumber); }); } diff --git a/src/main/java/lotto/domain/WinningLotto.java b/src/main/java/lotto/domain/WinningLotto.java new file mode 100644 index 00000000000..89934a186ae --- /dev/null +++ b/src/main/java/lotto/domain/WinningLotto.java @@ -0,0 +1,38 @@ +package lotto.domain; + +import static lotto.constants.Constants.MAX_LOTTO_NUMBER; +import static lotto.constants.Constants.MIN_LOTTO_NUMBER; +import static lotto.exception.ErrorMessage.INVALID_BONUS_NUMBER; +import static lotto.exception.ErrorMessage.INVALID_LOTTO_NUMBER_RANGE; + +public class WinningLotto { + private final Lotto winningNumbers; + private final int bonusNumber; + + private WinningLotto(Lotto winningNumbers, int bonusNumber) { + this.winningNumbers = winningNumbers; + this.bonusNumber = bonusNumber; + } + + public static WinningLotto of(Lotto winningNumbers, int bonusNumber) { + validateBonusNumber(winningNumbers, bonusNumber); + return new WinningLotto(winningNumbers, bonusNumber); + } + + private static void validateBonusNumber(Lotto winningNumbers, int bonusNumber) { + validateRange(bonusNumber); + validateDuplicate(winningNumbers, bonusNumber); + } + + private static void validateDuplicate(Lotto winningNumbers, int bonusNumber) { + if (winningNumbers.getNumbers().contains(bonusNumber)) { + throw new IllegalArgumentException(INVALID_BONUS_NUMBER.getMessage()); + } + } + + private static void validateRange(int bonusNumber) { + if (bonusNumber < MIN_LOTTO_NUMBER || bonusNumber > MAX_LOTTO_NUMBER) { + throw new IllegalArgumentException(INVALID_LOTTO_NUMBER_RANGE.getMessage()); + } + } +} diff --git a/src/main/java/lotto/exception/ErrorMessage.java b/src/main/java/lotto/exception/ErrorMessage.java index 75fa23b5cdd..b3e349b7c19 100644 --- a/src/main/java/lotto/exception/ErrorMessage.java +++ b/src/main/java/lotto/exception/ErrorMessage.java @@ -7,7 +7,9 @@ public enum ErrorMessage { INVALID_PURCHASE_AMOUNT("1000 으로 나누어 떨어지는 값을 입력해 주세요."), INVALID_LOTTO_NUMBERS_SIZE("유효하지 않은 로또 숫자 개수 입니다."), DUPLICATED_LOTTO_NUMBERS("중복된 로또 숫자가 있습니다."), - INVALID_LOTTO_NUMBER_RANGE("1 이상 45 이하의 숫자만 가능합니다."); + INVALID_LOTTO_NUMBER_RANGE("1 이상 45 이하의 숫자만 가능합니다."), + INVALID_WINNING_NUMBERS("유효하지 않은 당첨번호 형식 입니다."), + INVALID_BONUS_NUMBER("보너스 번호는 당첨 번호와 중복되지 않는 숫자로 입력해 주세요."); private final String message; diff --git a/src/main/java/lotto/utils/LottoNumbersGenerator.java b/src/main/java/lotto/utils/LottoNumbersGenerator.java index 5732af678ce..2d25cc144e7 100644 --- a/src/main/java/lotto/utils/LottoNumbersGenerator.java +++ b/src/main/java/lotto/utils/LottoNumbersGenerator.java @@ -9,6 +9,7 @@ import static lotto.constants.Constants.LOTTO_NUMBERS_SIZE; import static lotto.constants.Constants.MIN_LOTTO_NUMBER; import static lotto.constants.Constants.MAX_LOTTO_NUMBER; +import static lotto.exception.ErrorMessage.NOT_NUMERIC_INPUT; public class LottoNumbersGenerator { public static List generate() { diff --git a/src/main/java/lotto/utils/WinningLottoValidator.java b/src/main/java/lotto/utils/WinningLottoValidator.java new file mode 100644 index 00000000000..bfcf229c4f8 --- /dev/null +++ b/src/main/java/lotto/utils/WinningLottoValidator.java @@ -0,0 +1,39 @@ +package lotto.utils; + +import org.junit.platform.commons.util.StringUtils; + +import java.util.List; + +import static lotto.exception.ErrorMessage.INVALID_WINNING_NUMBERS; +import static lotto.exception.ErrorMessage.NOT_NUMERIC_INPUT; + +public class WinningLottoValidator { + public static List safeSplit(String input, String delimiter) { + validateEmpty(input); + validateStartsOrEndsWithDelimiter(input, delimiter); + List splitInput = List.of(input.split(delimiter)); + return splitInput.stream() + .map(WinningLottoValidator::safeParseInt) + .toList(); + } + + private static void validateEmpty(String input) { + if (StringUtils.isBlank(input)) { + throw new IllegalArgumentException(INVALID_WINNING_NUMBERS.getMessage()); + } + } + + private static void validateStartsOrEndsWithDelimiter(String input, String delimiter) { + if (input.startsWith(delimiter) || input.endsWith(delimiter)) { + throw new IllegalArgumentException(INVALID_WINNING_NUMBERS.getMessage()); + } + } + + public static int safeParseInt(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(NOT_NUMERIC_INPUT.getMessage()); + } + } +} diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java index f4e4a60aa90..d45b14898de 100644 --- a/src/main/java/lotto/view/InputView.java +++ b/src/main/java/lotto/view/InputView.java @@ -1,11 +1,18 @@ package lotto.view; import camp.nextstep.edu.missionutils.Console; +import lotto.domain.WinningLotto; import lotto.utils.PurchaseAmountValidator; +import lotto.utils.WinningLottoValidator; + +import java.util.List; public class InputView { private static final InputView instance = new InputView(); private static final String ASK_PURCHASE_AMOUNT = "구입금액을 입력해 주세요."; + private static final String ASK_WINNING_NUMBERS = "당첨 번호를 입력해 주세요."; + private static final String WINNING_NUMBERS_DELIMITER = ","; + private static final String ASK_BONUS_NUMBER = "보너스 번호를 입력해 주세요."; private InputView() { } @@ -19,4 +26,22 @@ public long readPurchaseAmount() { String input = Console.readLine(); return PurchaseAmountValidator.safeParseLong(input); } + + public List readWinningNumbers() { + printLine(); + System.out.println(ASK_WINNING_NUMBERS); + String input = Console.readLine(); + return WinningLottoValidator.safeSplit(input, WINNING_NUMBERS_DELIMITER); + } + + private static void printLine() { + System.out.println(); + } + + public int readBonusNumber() { + printLine(); + System.out.println(ASK_BONUS_NUMBER); + String input = Console.readLine(); + return WinningLottoValidator.safeParseInt(input); + } } From ede6fb140b3671375da3755b2ee362711bbfd488 Mon Sep 17 00:00:00 2001 From: jisu-om Date: Fri, 1 Dec 2023 03:24:07 +0900 Subject: [PATCH 5/7] =?UTF-8?q?Feat:=20=EB=8B=B9=EC=B2=A8=EB=82=B4?= =?UTF-8?q?=EC=97=AD,=20=EC=88=98=EC=9D=B5=EB=A5=A0=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20=EC=B6=9C=EB=A0=A5=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 12 +++--- .../java/lotto/controller/MainController.java | 16 +++----- src/main/java/lotto/domain/Lotto.java | 10 +++++ src/main/java/lotto/domain/LottoRank.java | 40 +++++++++++++++++++ src/main/java/lotto/domain/Lottos.java | 11 +++++ .../java/lotto/domain/PurchaseAmount.java | 4 ++ src/main/java/lotto/domain/RankResult.java | 27 +++++++++++++ src/main/java/lotto/domain/WinningLotto.java | 24 +++-------- src/main/java/lotto/dto/RankDto.java | 6 +++ src/main/java/lotto/dto/ResultDto.java | 6 +++ src/main/java/lotto/utils/Mapper.java | 16 ++++++++ .../lotto/utils/WinningLottoValidator.java | 24 ++++++++++- src/main/java/lotto/view/OutputView.java | 32 ++++++++++++++- 13 files changed, 189 insertions(+), 39 deletions(-) create mode 100644 src/main/java/lotto/domain/LottoRank.java create mode 100644 src/main/java/lotto/domain/RankResult.java create mode 100644 src/main/java/lotto/dto/RankDto.java create mode 100644 src/main/java/lotto/dto/ResultDto.java diff --git a/docs/README.md b/docs/README.md index 7a9695d1ea8..2756477ea12 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,21 +17,21 @@ - [X] 1 이상 45 이하 이고, 당첨 번호와 중복되지 않는 숫자 여야 함 -- [ ] 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 게임을 종료한다. - - [ ] 당첨 내역을 출력한다. - - [ ] 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다. +- [X] 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 게임을 종료한다. + - [X] 당첨 내역을 출력한다. + - [X] 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다. - 1등: 6개 번호 일치 / 2,000,000,000원 - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 - 3등: 5개 번호 일치 / 1,500,000원 - 4등: 4개 번호 일치 / 50,000원 - 5등: 3개 번호 일치 / 5,000원 - - [ ] 수익률을 출력한다. - - [ ] 수익률은 소수점 둘째 자리에서 반올림한다. + - [X] 수익률을 출력한다. + - [X] 수익률은 소수점 둘째 자리에서 반올림한다. - (ex. 100.0%, 51.5%, 1,000,000.0%) -- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. +- [X] 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. --- diff --git a/src/main/java/lotto/controller/MainController.java b/src/main/java/lotto/controller/MainController.java index c3505ae536b..1d8a3fd3c82 100644 --- a/src/main/java/lotto/controller/MainController.java +++ b/src/main/java/lotto/controller/MainController.java @@ -1,10 +1,8 @@ package lotto.controller; -import lotto.domain.Lotto; -import lotto.domain.Lottos; -import lotto.domain.PurchaseAmount; -import lotto.domain.WinningLotto; +import lotto.domain.*; import lotto.dto.LottosDto; +import lotto.dto.ResultDto; import lotto.service.LottoMaker; import lotto.utils.Mapper; import lotto.view.InputView; @@ -16,7 +14,6 @@ public class MainController { private final InputView inputView; private final OutputView outputView; - //private OrderController orderController; //컨트롤러 추가하는 경우 private MainController(InputView inputView, OutputView outputView) { this.inputView = inputView; @@ -36,14 +33,11 @@ public void run() { outputView.printLottos(lottosDto); Lotto winningNumbers = readWinningNumbers(); WinningLotto winningLotto = readWinningLotto(winningNumbers); - - + RankResult rankResult = lottos.findRanks(winningLotto); + ResultDto resultDto = Mapper.toTotalRankDto(purchaseAmount, rankResult); + outputView.printResult(resultDto); } -// private void initializeControllers() { -// orderController = OrderController.of(inputView, outputView); -// } - private PurchaseAmount createPurchaseAmount() { return readUserInput(() -> { long input = inputView.readPurchaseAmount(); diff --git a/src/main/java/lotto/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java index fd09a743daf..ead2e307a8e 100644 --- a/src/main/java/lotto/domain/Lotto.java +++ b/src/main/java/lotto/domain/Lotto.java @@ -41,6 +41,16 @@ private void validateRange(List numbers) { } } + public int getMatchCount(Lotto lotto) { + return (int) numbers.stream() + .filter(lotto.numbers::contains) + .count(); + } + + public boolean contains(int number) { + return numbers.contains(number); + } + public List getNumbers() { return List.copyOf(numbers); } diff --git a/src/main/java/lotto/domain/LottoRank.java b/src/main/java/lotto/domain/LottoRank.java new file mode 100644 index 00000000000..a1ff2ab4195 --- /dev/null +++ b/src/main/java/lotto/domain/LottoRank.java @@ -0,0 +1,40 @@ +package lotto.domain; + +import java.util.Arrays; +import java.util.Optional; + +public enum LottoRank { + FIFTH(3, false,5_000), + FOURTH(4, false,50_000), + THIRD(5, false,1_500_000), + SECOND(5, true, 30_000_000), + FIRST(6, false, 2_000_000_000); + + private final int matchCount; + private final boolean requiresBonusMatch; + private final int prize; + + LottoRank(int matchCount, boolean requiresBonusMatch, int prize) { + this.matchCount = matchCount; + this.requiresBonusMatch = requiresBonusMatch; + this.prize = prize; + } + + public static Optional findByMatchResult(int matchCount, boolean bonusMatch) { + return Arrays.stream(LottoRank.values()) + .filter(rank -> rank.matchCount == matchCount && (matchCount != 5 || rank.requiresBonusMatch == bonusMatch)) + .findAny(); + } + + public int getMatchCount() { + return matchCount; + } + + public boolean isRequiresBonusMatch() { + return requiresBonusMatch; + } + + public int getPrize() { + return prize; + } +} diff --git a/src/main/java/lotto/domain/Lottos.java b/src/main/java/lotto/domain/Lottos.java index a9c31e18ecc..1bb2e07bdf3 100644 --- a/src/main/java/lotto/domain/Lottos.java +++ b/src/main/java/lotto/domain/Lottos.java @@ -1,6 +1,7 @@ package lotto.domain; import java.util.List; +import java.util.Optional; public class Lottos { private final List lottos; @@ -17,6 +18,16 @@ public List getLottos() { return List.copyOf(lottos); } + public RankResult findRanks(WinningLotto winningLotto) { + List lottoRanks = lottos.stream() + .map(lotto -> LottoRank.findByMatchResult( + lotto.getMatchCount(winningLotto.getWinningNumbers()), + lotto.contains(winningLotto.getBonusNumber()))) + .flatMap(Optional::stream) + .toList(); + return RankResult.from(lottoRanks); + } + public long getQuantity() { return lottos.size(); } diff --git a/src/main/java/lotto/domain/PurchaseAmount.java b/src/main/java/lotto/domain/PurchaseAmount.java index 5b0cc9f55fc..32e16d83a6c 100644 --- a/src/main/java/lotto/domain/PurchaseAmount.java +++ b/src/main/java/lotto/domain/PurchaseAmount.java @@ -20,4 +20,8 @@ public static PurchaseAmount from(long amount) { public long getQuantityOfLotto() { return amount / LOTTO_PRICE; } + + public long getAmount() { + return amount; + } } diff --git a/src/main/java/lotto/domain/RankResult.java b/src/main/java/lotto/domain/RankResult.java new file mode 100644 index 00000000000..d7c0caf996d --- /dev/null +++ b/src/main/java/lotto/domain/RankResult.java @@ -0,0 +1,27 @@ +package lotto.domain; + +import java.util.List; + +public class RankResult { + private final List lottoRanks; + + private RankResult(List lottoRanks) { + this.lottoRanks = lottoRanks; + } + + public static RankResult from(List lottoRanks) { + return new RankResult(lottoRanks); + } + + public long getRankCount(LottoRank rank) { + return lottoRanks.stream() + .filter(lottoRank -> lottoRank == rank) + .count(); + } + + public long getTotalPrize() { + return lottoRanks.stream() + .mapToLong(LottoRank::getPrize) + .sum(); + } +} diff --git a/src/main/java/lotto/domain/WinningLotto.java b/src/main/java/lotto/domain/WinningLotto.java index 89934a186ae..0ba8dc02464 100644 --- a/src/main/java/lotto/domain/WinningLotto.java +++ b/src/main/java/lotto/domain/WinningLotto.java @@ -1,9 +1,6 @@ package lotto.domain; -import static lotto.constants.Constants.MAX_LOTTO_NUMBER; -import static lotto.constants.Constants.MIN_LOTTO_NUMBER; -import static lotto.exception.ErrorMessage.INVALID_BONUS_NUMBER; -import static lotto.exception.ErrorMessage.INVALID_LOTTO_NUMBER_RANGE; +import lotto.utils.WinningLottoValidator; public class WinningLotto { private final Lotto winningNumbers; @@ -15,24 +12,15 @@ private WinningLotto(Lotto winningNumbers, int bonusNumber) { } public static WinningLotto of(Lotto winningNumbers, int bonusNumber) { - validateBonusNumber(winningNumbers, bonusNumber); + WinningLottoValidator.validateBonusNumber(winningNumbers, bonusNumber); return new WinningLotto(winningNumbers, bonusNumber); } - private static void validateBonusNumber(Lotto winningNumbers, int bonusNumber) { - validateRange(bonusNumber); - validateDuplicate(winningNumbers, bonusNumber); + public Lotto getWinningNumbers() { + return winningNumbers; } - private static void validateDuplicate(Lotto winningNumbers, int bonusNumber) { - if (winningNumbers.getNumbers().contains(bonusNumber)) { - throw new IllegalArgumentException(INVALID_BONUS_NUMBER.getMessage()); - } - } - - private static void validateRange(int bonusNumber) { - if (bonusNumber < MIN_LOTTO_NUMBER || bonusNumber > MAX_LOTTO_NUMBER) { - throw new IllegalArgumentException(INVALID_LOTTO_NUMBER_RANGE.getMessage()); - } + public int getBonusNumber() { + return bonusNumber; } } diff --git a/src/main/java/lotto/dto/RankDto.java b/src/main/java/lotto/dto/RankDto.java new file mode 100644 index 00000000000..ebd00bd4578 --- /dev/null +++ b/src/main/java/lotto/dto/RankDto.java @@ -0,0 +1,6 @@ +package lotto.dto; + +import lotto.domain.LottoRank; + +public record RankDto(LottoRank rank, long quantity) { +} diff --git a/src/main/java/lotto/dto/ResultDto.java b/src/main/java/lotto/dto/ResultDto.java new file mode 100644 index 00000000000..3de19456de1 --- /dev/null +++ b/src/main/java/lotto/dto/ResultDto.java @@ -0,0 +1,6 @@ +package lotto.dto; + +import java.util.List; + +public record ResultDto(List rankDtos, double profit) { +} diff --git a/src/main/java/lotto/utils/Mapper.java b/src/main/java/lotto/utils/Mapper.java index 9995bc0246e..b8e650003ec 100644 --- a/src/main/java/lotto/utils/Mapper.java +++ b/src/main/java/lotto/utils/Mapper.java @@ -1,9 +1,15 @@ package lotto.utils; +import lotto.domain.LottoRank; import lotto.domain.Lottos; +import lotto.domain.PurchaseAmount; +import lotto.domain.RankResult; import lotto.dto.LottoDto; import lotto.dto.LottosDto; +import lotto.dto.RankDto; +import lotto.dto.ResultDto; +import java.util.Arrays; import java.util.List; public class Mapper { @@ -13,4 +19,14 @@ public static LottosDto toLottosDto(Lottos lottos) { .toList(); return new LottosDto(lottos.getQuantity(), lottoDtos); } + + public static ResultDto toTotalRankDto(PurchaseAmount purchaseAmount, RankResult rankResult) { + List rankDtos = Arrays.stream(LottoRank.values()) + .map(rank -> new RankDto(rank, rankResult.getRankCount(rank))) + .toList(); + long totalPrize = rankResult.getTotalPrize(); + double profit = (double)totalPrize / purchaseAmount.getAmount() * 100; + profit = Math.round(profit * 100.0) / 100.0; + return new ResultDto(rankDtos, profit); + } } diff --git a/src/main/java/lotto/utils/WinningLottoValidator.java b/src/main/java/lotto/utils/WinningLottoValidator.java index bfcf229c4f8..6eb1a8c4890 100644 --- a/src/main/java/lotto/utils/WinningLottoValidator.java +++ b/src/main/java/lotto/utils/WinningLottoValidator.java @@ -1,11 +1,14 @@ package lotto.utils; +import lotto.domain.Lotto; import org.junit.platform.commons.util.StringUtils; import java.util.List; -import static lotto.exception.ErrorMessage.INVALID_WINNING_NUMBERS; -import static lotto.exception.ErrorMessage.NOT_NUMERIC_INPUT; +import static lotto.constants.Constants.MAX_LOTTO_NUMBER; +import static lotto.constants.Constants.MIN_LOTTO_NUMBER; +import static lotto.exception.ErrorMessage.*; +import static lotto.exception.ErrorMessage.INVALID_LOTTO_NUMBER_RANGE; public class WinningLottoValidator { public static List safeSplit(String input, String delimiter) { @@ -36,4 +39,21 @@ public static int safeParseInt(String input) { throw new IllegalArgumentException(NOT_NUMERIC_INPUT.getMessage()); } } + + public static void validateBonusNumber(Lotto winningNumbers, int bonusNumber) { + validateRange(bonusNumber); + validateDuplicate(winningNumbers, bonusNumber); + } + + private static void validateDuplicate(Lotto winningNumbers, int bonusNumber) { + if (winningNumbers.getNumbers().contains(bonusNumber)) { + throw new IllegalArgumentException(INVALID_BONUS_NUMBER.getMessage()); + } + } + + private static void validateRange(int bonusNumber) { + if (bonusNumber < MIN_LOTTO_NUMBER || bonusNumber > MAX_LOTTO_NUMBER) { + throw new IllegalArgumentException(INVALID_LOTTO_NUMBER_RANGE.getMessage()); + } + } } diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java index 206929fab79..b05c70aeb90 100644 --- a/src/main/java/lotto/view/OutputView.java +++ b/src/main/java/lotto/view/OutputView.java @@ -2,6 +2,7 @@ import lotto.dto.LottoDto; import lotto.dto.LottosDto; +import lotto.dto.ResultDto; import java.util.stream.Collectors; @@ -11,8 +12,12 @@ public class OutputView { private static final String START_LOTTO_FORMAT = "["; private static final String END_LOTTO_FORMAT = "]"; private static final String LOTTO_NUMBERS_DELIMITER = ", "; - - + private static final String RESULT_TITLE = "당첨 통계"; + private static final String RESULT_SIGNATURE = "---"; + private static final String RESULT_FORMAT = "%d개 일치 (%,d원) - %d개"; + private static final String RESULT_FORMAT_BONUS_MATCH = "%d개 일치, 보너스 볼 일치 (%,d원) - %d개"; + private static final int RANK_NEEDS_BONUS = 5; + private static final String PROFIT_FORMAT = "총 수익률은 %,.1f%%입니다."; private OutputView() { } @@ -47,4 +52,27 @@ private void printLotto(LottoDto lottoDto) { System.out.print(END_LOTTO_FORMAT); printLine(); } + + public void printResult(ResultDto resultDto) { + printLine(); + System.out.println(RESULT_TITLE); + System.out.println(RESULT_SIGNATURE); + printRankResult(resultDto); + printProfit(resultDto.profit()); + } + + private static void printRankResult(ResultDto resultDto) { + resultDto.rankDtos().forEach(rankDto -> { + String format = RESULT_FORMAT; + if (rankDto.rank().isRequiresBonusMatch()) { + format = RESULT_FORMAT_BONUS_MATCH; + } + System.out.printf((format) + "%n", + rankDto.rank().getMatchCount(), rankDto.rank().getPrize(), rankDto.quantity()); + }); + } + + private void printProfit(double profit) { + System.out.printf((PROFIT_FORMAT) + "%n", profit); + } } From c6e618dc1c4676182eb54bff6d764c8dbe8de550 Mon Sep 17 00:00:00 2001 From: jisu-om Date: Fri, 1 Dec 2023 03:36:15 +0900 Subject: [PATCH 6/7] =?UTF-8?q?Refactor(mainController):=20run=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/lotto/controller/MainController.java | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/main/java/lotto/controller/MainController.java b/src/main/java/lotto/controller/MainController.java index 1d8a3fd3c82..32915da3d1c 100644 --- a/src/main/java/lotto/controller/MainController.java +++ b/src/main/java/lotto/controller/MainController.java @@ -26,37 +26,49 @@ public static MainController create() { public void run() { PurchaseAmount purchaseAmount = createPurchaseAmount(); + Lottos lottos = createLottos(purchaseAmount); + printLottos(lottos); + WinningLotto winningLotto = createWinningLotto(); + printResult(lottos, winningLotto, purchaseAmount); + } + + private PurchaseAmount createPurchaseAmount() { + return readUserInput(() -> { + long input = inputView.readPurchaseAmount(); + return PurchaseAmount.from(input); + }); + } + + private Lottos createLottos(PurchaseAmount purchaseAmount) { long quantityOfLotto = purchaseAmount.getQuantityOfLotto(); List lottosMade = LottoMaker.makeLottos(quantityOfLotto); - Lottos lottos = Lottos.from(lottosMade); + return Lottos.from(lottosMade); + } + + private void printLottos(Lottos lottos) { LottosDto lottosDto = Mapper.toLottosDto(lottos); outputView.printLottos(lottosDto); - Lotto winningNumbers = readWinningNumbers(); - WinningLotto winningLotto = readWinningLotto(winningNumbers); - RankResult rankResult = lottos.findRanks(winningLotto); - ResultDto resultDto = Mapper.toTotalRankDto(purchaseAmount, rankResult); - outputView.printResult(resultDto); } - private PurchaseAmount createPurchaseAmount() { + private WinningLotto createWinningLotto() { + Lotto winningNumbers = createWinningNumbers(); return readUserInput(() -> { - long input = inputView.readPurchaseAmount(); - return PurchaseAmount.from(input); + int bonusNumber = inputView.readBonusNumber(); + return WinningLotto.of(winningNumbers, bonusNumber); }); } - private Lotto readWinningNumbers() { + private Lotto createWinningNumbers() { return readUserInput(() -> { List numbers = inputView.readWinningNumbers(); return new Lotto(numbers); }); } - private WinningLotto readWinningLotto(Lotto winningNumbers) { - return readUserInput(() -> { - int bonusNumber = inputView.readBonusNumber(); - return WinningLotto.of(winningNumbers, bonusNumber); - }); + private void printResult(Lottos lottos, WinningLotto winningLotto, PurchaseAmount purchaseAmount) { + RankResult rankResult = lottos.findRanks(winningLotto); + ResultDto resultDto = Mapper.toTotalRankDto(purchaseAmount, rankResult); + outputView.printResult(resultDto); } private T readUserInput(Supplier supplier) { From 5c8c9607e5a75f9a561953debc58fbbf26d9c2e9 Mon Sep 17 00:00:00 2001 From: jisu-om Date: Fri, 1 Dec 2023 03:48:35 +0900 Subject: [PATCH 7/7] =?UTF-8?q?Test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/lotto/LottoTest.java | 21 ++++++++++++++-- .../java/lotto/domain/PurchaseAmountTest.java | 24 +++++++++++++++++++ .../java/lotto/domain/WinningLottoTest.java | 19 +++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/test/java/lotto/domain/PurchaseAmountTest.java create mode 100644 src/test/java/lotto/domain/WinningLottoTest.java diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 0e4d4f73896..2522fdcdde8 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/LottoTest.java @@ -3,8 +3,12 @@ import lotto.domain.Lotto; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import java.util.List; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -19,10 +23,23 @@ void createLottoByOverSize() { @DisplayName("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.") @Test void createLottoByDuplicatedNumber() { - // TODO: 이 테스트가 통과할 수 있게 구현 코드 작성 assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 5))) .isInstanceOf(IllegalArgumentException.class); } - // 아래에 추가 테스트 작성 가능 + @ParameterizedTest(name = "[{index}] 로또 번호에 1 미만, 45 초과하는 숫자가 있으면 예외가 발생한다.") + @MethodSource("lottoNumberProvider") + void createLottoByInvalidRangeNumber(List numbers) { + assertThatThrownBy(() -> new Lotto(numbers)) + .isInstanceOf(IllegalArgumentException.class); + } + + private static Stream lottoNumberProvider() { + return Stream.of( + Arguments.of(List.of(-1, 1, 2, 3, 4, 5)), + Arguments.of(List.of(0, 1, 2, 3, 4, 5)), + Arguments.of(List.of(1, 2, 3, 4, 5, 47)), + Arguments.of(List.of(-100, 1, 2, 3, 4, 5)) + ); + } } \ No newline at end of file diff --git a/src/test/java/lotto/domain/PurchaseAmountTest.java b/src/test/java/lotto/domain/PurchaseAmountTest.java new file mode 100644 index 00000000000..8821c93a87c --- /dev/null +++ b/src/test/java/lotto/domain/PurchaseAmountTest.java @@ -0,0 +1,24 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.*; + +class PurchaseAmountTest { + @DisplayName("PurchaseAmount 정상 생성") + @Test + void create() { + PurchaseAmount purchaseAmount = PurchaseAmount.from(1000); + assertThat(purchaseAmount).isNotNull(); + } + + @ParameterizedTest(name = "[{index}] PurchaseAmount 생성 시 {0} 을 입력하면 예외 발생한다.") + @ValueSource(ints = {1, 110, 0, -1000}) + void exception(int element) { + assertThatThrownBy(() -> PurchaseAmount.from(element)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/WinningLottoTest.java b/src/test/java/lotto/domain/WinningLottoTest.java new file mode 100644 index 00000000000..8c5d941e079 --- /dev/null +++ b/src/test/java/lotto/domain/WinningLottoTest.java @@ -0,0 +1,19 @@ +package lotto.domain; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; + + +class WinningLottoTest { + @ParameterizedTest + @ValueSource(ints = {47, 0, -1, 6}) + void exception(int element) { + Lotto winningNumbers = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + Assertions.assertThatThrownBy(() -> WinningLotto.of(winningNumbers, element)) + .isInstanceOf(IllegalArgumentException.class); + } + +} \ No newline at end of file