From 6a922090854d3488809353cd0a84a94368a1ecd6 Mon Sep 17 00:00:00 2001 From: moongua404 Date: Mon, 26 May 2025 22:33:00 +0900 Subject: [PATCH 1/4] =?UTF-8?q?docs:=20=EB=A6=AC=EB=93=9C=EB=AF=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/README.md b/README.md index 5fa2560..244cad1 100644 --- a/README.md +++ b/README.md @@ -1 +1,130 @@ # java-lotto-precourse + +> 로또 맞게 해주세요...ㅠ + +
+ 과제 세부 내용 + +## 과제 내용 +로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다. +``` +- 로또 번호의 숫자 범위는 1~45까지이다. +- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. +- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다. +- 당첨은 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원 +``` +- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다. +- 로또 1장의 가격은 1,000원이다. +- 당첨 번호와 보너스 번호를 입력받는다. +- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다. +- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. + - `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다. + +### 입출력 +- 입력 + - 로또 구입 금액 + - 당첨 번호 6개 + - 보너스 번호 +- 출력 + - 발행한 로또 수량 및 번호 + - 당첨 내역 + - 수익률 + - (예외 문구) + +ex) + +``` +구입금액을 입력해 주세요. +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%입니다. +``` + +
+ +## 코드 흐름 +- 로또 구매 금액을 입력받는다. +- 로또를 번호를 생성하고 출력한다. +- 당첨 번호를 입력받는다. +- 결과를 계산한 후 출력한다. + +```mermaid +sequenceDiagram + participant View + participant Controller + participant Model + + Controller->>View: 금액 입력 요청 + View->>Controller: 구입 금액 반환 + Controller->>Model: 구입 금액 전달 + Model-->Model: 로또 생성 + Model->>Controller: 로또 번호 반환 + Controller->>View: 로또 번호 출력 + View->>Controller: - + Controller->>View: 당첨 번호 입력 요청 + View->>Controller: 당첨 번호 반환 + Controller->>Model: 당첨 번호 전달 + Model-->Model: 당첨 여부 확인 + Model->>Controller: 결과 반환 + Controller->>View: 결과 출력 + View->>Controller: - + Controller-->Controller: 프로그램 종료 + +``` + +## 구현 기능 목록 +- 입출력 + - [ ] 구입 금액 입력 + - [ ] 당첨 번호 입력 + - [ ] 보너스 번호 입력 + - [ ] 발행한 로또 수량 및 번호 + - [ ] 당첨 내역 + - [ ] 수익률 + - [ ] 예외 +- 로또 + - [ ] 로또 생성 + - [ ] 당첨 확인 + - [ ] 수익률 계산 + +## 처리할 예외 +- 나누어 떨어지지 않는 금액 (1000 단위로 떨어지지 않을 때) + - `[ERROR] 1,000원 단위로 입력해 주세요.` +- 너무 큰 구입 금액 + - `[ERROR] $입력한 금액 보다 작은 금액을 입력해 주세요.` +- 부적절한 구입 금액 (음수 입력 등 포함, 입력 예외) + - `[ERROR] 유효한 구입 금액을 입력해 주세요` +- 부적절한 로또의 범위 (1-45 밖의 숫자) + - `[ERROR] 1부터 45 사이의 값을 입력해 주세요.` +- 중복되는 번호 / 보너스 번호 + - `[ERROR] 중복되지 않은 번호를 입력해주세요.` +- 부적절한 양식 혹은 갯수 + - `[ERROR] 당첨 번호 6개를 정확히 입력해주세요. ex)1,2,3,4,5,6` + - `[ERROR] 보너스 번호 하나를 정확히 입력해주세요. ex)7` \ No newline at end of file From 68181675b469acbbaa431432c426cd8a47ce1a9e Mon Sep 17 00:00:00 2001 From: moongua404 Date: Tue, 27 May 2025 13:34:13 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=B8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84=20(service=20/=20model)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/Lotto.java | 20 ----- .../java/lotto/application/domain/Lotto.java | 42 +++++++++ .../domain/dto/WinningDataDto.java | 6 ++ .../application/domain/enums/LottoPrize.java | 55 ++++++++++++ .../ProgramTerminationException.java | 7 ++ .../application/service/LottoService.java | 90 +++++++++++++++++++ src/main/java/lotto/utils/Validator.java | 38 ++++++++ src/test/java/lotto/LottoTest.java | 1 + 8 files changed, 239 insertions(+), 20 deletions(-) delete mode 100644 src/main/java/lotto/Lotto.java create mode 100644 src/main/java/lotto/application/domain/Lotto.java create mode 100644 src/main/java/lotto/application/domain/dto/WinningDataDto.java create mode 100644 src/main/java/lotto/application/domain/enums/LottoPrize.java create mode 100644 src/main/java/lotto/application/domain/exceptions/ProgramTerminationException.java create mode 100644 src/main/java/lotto/application/service/LottoService.java create mode 100644 src/main/java/lotto/utils/Validator.java diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java deleted file mode 100644 index 88fc5cf..0000000 --- a/src/main/java/lotto/Lotto.java +++ /dev/null @@ -1,20 +0,0 @@ -package lotto; - -import java.util.List; - -public class Lotto { - private final List numbers; - - public Lotto(List numbers) { - validate(numbers); - this.numbers = numbers; - } - - private void validate(List numbers) { - if (numbers.size() != 6) { - throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다."); - } - } - - // TODO: 추가 기능 구현 -} diff --git a/src/main/java/lotto/application/domain/Lotto.java b/src/main/java/lotto/application/domain/Lotto.java new file mode 100644 index 0000000..8bc8a6b --- /dev/null +++ b/src/main/java/lotto/application/domain/Lotto.java @@ -0,0 +1,42 @@ +package lotto.application.domain; + +import java.util.List; +import lotto.utils.Validator; + +public class Lotto { + private final List numbers; + + private final static int LOTTO_SIZE = 6; + private final static int LOWER_BOUND = 1; + private final static int UPPER_BOUND = 45; + + public Lotto(List numbers) { + validate(numbers); + this.numbers = numbers; + } + + private void validate(List numbers) { + try { + Validator.fitSize(numbers, LOTTO_SIZE); + Validator.hasNoDuplication(numbers); + Validator.isInRange(numbers, LOWER_BOUND, UPPER_BOUND); + } catch (RuntimeException e) { + throw new IllegalArgumentException("[ERROR] 부적절한 로또 번호 입니다.", e); + } + } + + // TODO: 추가 기능 구현 + public boolean isDuplicate(int numbers) { + return this.numbers.contains(numbers); + } + + public int countMatch(Lotto other) { + return (int) numbers.stream() + .filter(other.numbers::contains) + .count(); + } + + public boolean isMatch(int bonus) { + return numbers.contains(bonus); + } +} \ No newline at end of file diff --git a/src/main/java/lotto/application/domain/dto/WinningDataDto.java b/src/main/java/lotto/application/domain/dto/WinningDataDto.java new file mode 100644 index 0000000..433b01c --- /dev/null +++ b/src/main/java/lotto/application/domain/dto/WinningDataDto.java @@ -0,0 +1,6 @@ +package lotto.application.domain.dto; + +import lotto.application.domain.enums.LottoPrize; + +public record WinningDataDto(LottoPrize lottoPrize, int count) { +} \ No newline at end of file diff --git a/src/main/java/lotto/application/domain/enums/LottoPrize.java b/src/main/java/lotto/application/domain/enums/LottoPrize.java new file mode 100644 index 0000000..b6a1e71 --- /dev/null +++ b/src/main/java/lotto/application/domain/enums/LottoPrize.java @@ -0,0 +1,55 @@ +package lotto.application.domain.enums; + +import java.util.List; + +public enum LottoPrize { + NOTHING("꽝", 0), + FIFTH_PRICE("3개 일치", 5_000), + FOURTH_PRICE("4개 일치", 50_000), + THIRD_PRICE("5개 일치", 1_500_000), + SECOND_PRICE("5개 일치, 보너스 볼 일치", 30_000_000), + FIRST_PRICE("6개 일치", 2_000_000_000); + + private final String condition; + private final int price; + + LottoPrize(String condition, int price) { + this.condition = condition; + this.price = price; + } + + public String getCondition() { + return condition; + } + + public int getPrice() { + return price; + } + + public static List getPrizeTypes() { + return List.of(FIFTH_PRICE, FOURTH_PRICE, THIRD_PRICE, SECOND_PRICE, FIRST_PRICE); + } + + public static LottoPrize getLottoPrize(int matchCount, boolean matchBonus) { + if (matchCount == 6) { + return getFirstOrSecondPrize(matchBonus); + } + if (matchCount == 5) { + return THIRD_PRICE; + } + if (matchCount == 4) { + return FOURTH_PRICE; + } + if (matchCount == 3) { + return FIFTH_PRICE; + } + return NOTHING; + } + + private static LottoPrize getFirstOrSecondPrize(boolean matchBonus) { + if (matchBonus) { + return SECOND_PRICE; + } + return FIRST_PRICE; + } +} \ No newline at end of file diff --git a/src/main/java/lotto/application/domain/exceptions/ProgramTerminationException.java b/src/main/java/lotto/application/domain/exceptions/ProgramTerminationException.java new file mode 100644 index 0000000..83767bd --- /dev/null +++ b/src/main/java/lotto/application/domain/exceptions/ProgramTerminationException.java @@ -0,0 +1,7 @@ +package lotto.application.domain.exceptions; + +public class ProgramTerminationException extends IllegalArgumentException { + public ProgramTerminationException() { + super("[ERROR] 예기치 않은 문제로 프로그램을 종료합니다."); + } +} diff --git a/src/main/java/lotto/application/service/LottoService.java b/src/main/java/lotto/application/service/LottoService.java new file mode 100644 index 0000000..063b7cf --- /dev/null +++ b/src/main/java/lotto/application/service/LottoService.java @@ -0,0 +1,90 @@ +package lotto.application.service; + +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.function.Supplier; +import java.util.stream.IntStream; +import lotto.application.domain.Lotto; +import lotto.application.domain.enums.LottoPrize; +import lotto.application.domain.exceptions.ProgramTerminationException; +import lotto.utils.Validator; + +public class LottoService { + public void run() { + final int price = repeat(this::getPrice); + final Lotto lotto = repeat(this::getLotto); + final int bonus = repeat(() -> getBonusNumber(lotto)); + + List lottoNumbers = IntStream.range(0, price/1000).mapToObj(i -> { + Lotto purchased = new Lotto(purchase()); + // lottoNumbers 출력 + return purchased; + }).toList(); + + printResult(lotto, bonus, lottoNumbers); + } + + private int getPrice() { + try { + int res = 3000; // 입력 받아야 함 + Validator.isDividedByThousand(res); + return res; + } catch (NoSuchElementException e) { + throw new ProgramTerminationException(); + } catch (Exception e) { + throw new IllegalArgumentException("[ERROR] 가격이 잘못 입력되었습니다."); + } + } + + private Lotto getLotto() { + try { + String res = "1,2,3,4,5,6"; + return new Lotto( + Arrays.stream(res.split(",")) + .map(String::trim) + .map(Integer::parseInt) + .toList() + ); + } catch (NoSuchElementException e) { + throw new ProgramTerminationException(); + } catch (Exception e) { + throw new IllegalArgumentException("[ERROR] 로또 번호가 잘못 입력되었습니다."); + } + } + + private int getBonusNumber(Lotto lotto) { + try { + int res = 7; + if (lotto.isDuplicate(res)) { + throw new IllegalArgumentException(); + } + return res; + } catch (NoSuchElementException e) { + throw new ProgramTerminationException(); + } catch (Exception e) { + throw new IllegalArgumentException("[ERROR] 보너스 숫자가 잘못 입력되었습니다."); + } + } + + private T repeat(Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (ProgramTerminationException e) { + throw new IllegalStateException(e.getMessage()); + } catch (RuntimeException ignored) { + } + } + } + + private List purchase() { + return List.of(1, 2, 3, 4, 5, 6); // 랜덤으로 숫자를 뽑음 + } + + private void printResult(Lotto lotto, int bonusNumber, List purchased) { + List result = purchased.stream() + .map(other -> LottoPrize.getLottoPrize(other.countMatch(lotto), other.isMatch(bonusNumber))) + .toList(); + } +} diff --git a/src/main/java/lotto/utils/Validator.java b/src/main/java/lotto/utils/Validator.java new file mode 100644 index 0000000..d1e2949 --- /dev/null +++ b/src/main/java/lotto/utils/Validator.java @@ -0,0 +1,38 @@ +package lotto.utils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Validator { + public static void fitSize(Collection collection, int size) { + if (collection.size() != size) { + throw new IllegalStateException("[ERROR] size mismatch"); + } + } + + public static void hasNoDuplication(List itemList) { + if (new HashSet<>(itemList).size() != itemList.size()) { + throw new IllegalStateException("[ERROR] Duplicated item exist."); + }; + } + + public static void isInRange(int value, int min, int max) { + if (value < min || value > max) { + throw new IllegalStateException("[ERROR] Invalid range."); + } + } + + public static void isInRange(List values, int min, int max) { + if (values.stream().allMatch(v -> v >= min && v <= max)) { + throw new IllegalStateException("[ERROR] Invalid range."); + } + } + + public static void isDividedByThousand(int value) { + if (value % 1000 != 0) { + throw new IllegalStateException("[ERROR] Invalid divided thousand."); + } + } +} diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 309f4e5..b266590 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/LottoTest.java @@ -1,5 +1,6 @@ package lotto; +import lotto.application.domain.Lotto; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From 4ebb51f9345e7e532475ca95633cd1981370f4b3 Mon Sep 17 00:00:00 2001 From: moongua404 Date: Tue, 27 May 2025 16:19:47 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=EC=9D=B8=ED=84=B0=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EB=B0=8F=20=EA=B5=AC=ED=98=84=EC=B2=B4=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/Application.java | 12 ++++ .../java/lotto/adapter/in/InputTerminal.java | 16 +++++ .../java/lotto/adapter/in/RandomLibrary.java | 12 ++++ .../domain/enums/MessageConstants.java | 21 ++++++ .../port/inport/GetLottoPropertyUseCase.java | 6 ++ .../port/inport/GetRandomUseCase.java | 7 ++ .../application/port/outport/LottoPort.java | 11 +++ .../application/service/LottoService.java | 67 +++++++++++++++---- src/main/java/lotto/utils/Validator.java | 2 +- 9 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 src/main/java/lotto/adapter/in/InputTerminal.java create mode 100644 src/main/java/lotto/adapter/in/RandomLibrary.java create mode 100644 src/main/java/lotto/application/domain/enums/MessageConstants.java create mode 100644 src/main/java/lotto/application/port/inport/GetLottoPropertyUseCase.java create mode 100644 src/main/java/lotto/application/port/inport/GetRandomUseCase.java create mode 100644 src/main/java/lotto/application/port/outport/LottoPort.java diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922..636a51d 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,19 @@ package lotto; +import lotto.adapter.in.InputTerminal; +import lotto.adapter.in.RandomLibrary; +import lotto.adapter.out.OutputTerminal; +import lotto.application.service.LottoService; + public class Application { public static void main(String[] args) { // TODO: 프로그램 구현 + LottoService lottoService = new LottoService( + new InputTerminal(), + new RandomLibrary(), + new OutputTerminal() + ); + + lottoService.run(); } } diff --git a/src/main/java/lotto/adapter/in/InputTerminal.java b/src/main/java/lotto/adapter/in/InputTerminal.java new file mode 100644 index 0000000..d4be7a0 --- /dev/null +++ b/src/main/java/lotto/adapter/in/InputTerminal.java @@ -0,0 +1,16 @@ +package lotto.adapter.in; + +import camp.nextstep.edu.missionutils.Console; +import lotto.application.port.inport.GetLottoPropertyUseCase; + +public class InputTerminal implements GetLottoPropertyUseCase { + @Override + public String getString() { + return Console.readLine(); + } + + @Override + public int getInteger() { + return Integer.parseInt(Console.readLine()); + } +} diff --git a/src/main/java/lotto/adapter/in/RandomLibrary.java b/src/main/java/lotto/adapter/in/RandomLibrary.java new file mode 100644 index 0000000..f67d6bd --- /dev/null +++ b/src/main/java/lotto/adapter/in/RandomLibrary.java @@ -0,0 +1,12 @@ +package lotto.adapter.in; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.List; +import lotto.application.port.inport.GetRandomUseCase; + +public class RandomLibrary implements GetRandomUseCase { + @Override + public List getRandomNumbers(int min, int max, int amount) { + return Randoms.pickUniqueNumbersInRange(min, max, amount); + } +} diff --git a/src/main/java/lotto/application/domain/enums/MessageConstants.java b/src/main/java/lotto/application/domain/enums/MessageConstants.java new file mode 100644 index 0000000..593ddbb --- /dev/null +++ b/src/main/java/lotto/application/domain/enums/MessageConstants.java @@ -0,0 +1,21 @@ +package lotto.application.domain.enums; + +public enum MessageConstants { + PURCHASE_GUIDE("구입금액을 입력해 주세요."), + PURCHASE_RESULT("%d개를 구매했습니다."), + WINNING_NUMBER_GUIDE("당첨 번호를 입력해 주세요."), + BONUS_NUMBER_GUIDE("보너스 번호를 입력해 주세요."), + WINNING_STATUS("당첨 통계\n---"), + RESULT_LINE("%s (%,d원) - %d개"), + RETURN_RATE("총 수익률은 %.1f%%입니다.") + + + ; + private String message; + MessageConstants(String message) { + this.message = message; + } + public String getMessage() { + return message; + } +} diff --git a/src/main/java/lotto/application/port/inport/GetLottoPropertyUseCase.java b/src/main/java/lotto/application/port/inport/GetLottoPropertyUseCase.java new file mode 100644 index 0000000..064a914 --- /dev/null +++ b/src/main/java/lotto/application/port/inport/GetLottoPropertyUseCase.java @@ -0,0 +1,6 @@ +package lotto.application.port.inport; + +public interface GetLottoPropertyUseCase { + String getString(); + int getInteger(); +} diff --git a/src/main/java/lotto/application/port/inport/GetRandomUseCase.java b/src/main/java/lotto/application/port/inport/GetRandomUseCase.java new file mode 100644 index 0000000..50643ea --- /dev/null +++ b/src/main/java/lotto/application/port/inport/GetRandomUseCase.java @@ -0,0 +1,7 @@ +package lotto.application.port.inport; + +import java.util.List; + +public interface GetRandomUseCase { + List getRandomNumbers(int min, int max, int amount); +} diff --git a/src/main/java/lotto/application/port/outport/LottoPort.java b/src/main/java/lotto/application/port/outport/LottoPort.java new file mode 100644 index 0000000..d4677b2 --- /dev/null +++ b/src/main/java/lotto/application/port/outport/LottoPort.java @@ -0,0 +1,11 @@ +package lotto.application.port.outport; + +import java.util.List; +import lotto.application.domain.enums.MessageConstants; + +public interface LottoPort { + void sendMessage(MessageConstants message); + void sendMessage(MessageConstants message, Object... params); + void sendMessage(String message); + void printLottoNumbers(List numbers); +} diff --git a/src/main/java/lotto/application/service/LottoService.java b/src/main/java/lotto/application/service/LottoService.java index 063b7cf..38f9a56 100644 --- a/src/main/java/lotto/application/service/LottoService.java +++ b/src/main/java/lotto/application/service/LottoService.java @@ -2,33 +2,57 @@ import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; +import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; import lotto.application.domain.Lotto; import lotto.application.domain.enums.LottoPrize; +import lotto.application.domain.enums.MessageConstants; import lotto.application.domain.exceptions.ProgramTerminationException; +import lotto.application.port.inport.GetLottoPropertyUseCase; +import lotto.application.port.inport.GetRandomUseCase; +import lotto.application.port.outport.LottoPort; import lotto.utils.Validator; public class LottoService { + private final GetLottoPropertyUseCase getLottoPropertyUseCase; + private final GetRandomUseCase getRandomUseCase; + private final LottoPort lottoPort; + + public LottoService(GetLottoPropertyUseCase getLottoPropertyUseCase, + GetRandomUseCase getRandomUseCase, LottoPort lottoPort) { + this.getLottoPropertyUseCase = getLottoPropertyUseCase; + this.getRandomUseCase = getRandomUseCase; + this.lottoPort = lottoPort; + } + public void run() { - final int price = repeat(this::getPrice); + final int amount = repeat(this::getAmount); final Lotto lotto = repeat(this::getLotto); final int bonus = repeat(() -> getBonusNumber(lotto)); - List lottoNumbers = IntStream.range(0, price/1000).mapToObj(i -> { + List lottoNumbers = IntStream.range(0, amount).mapToObj(i -> { Lotto purchased = new Lotto(purchase()); // lottoNumbers 출력 + return purchased; - }).toList(); + } + ).toList(); - printResult(lotto, bonus, lottoNumbers); + printResult(lotto, bonus, lottoNumbers, amount); } - private int getPrice() { + private int getAmount() { try { - int res = 3000; // 입력 받아야 함 + lottoPort.sendMessage(MessageConstants.PURCHASE_GUIDE); + int res = getLottoPropertyUseCase.getInteger(); Validator.isDividedByThousand(res); + res /= 1000; + lottoPort.sendMessage(MessageConstants.PURCHASE_RESULT, res); return res; } catch (NoSuchElementException e) { throw new ProgramTerminationException(); @@ -39,7 +63,8 @@ private int getPrice() { private Lotto getLotto() { try { - String res = "1,2,3,4,5,6"; + lottoPort.sendMessage(MessageConstants.WINNING_NUMBER_GUIDE); + String res = getLottoPropertyUseCase.getString(); return new Lotto( Arrays.stream(res.split(",")) .map(String::trim) @@ -55,7 +80,8 @@ private Lotto getLotto() { private int getBonusNumber(Lotto lotto) { try { - int res = 7; + lottoPort.sendMessage(MessageConstants.BONUS_NUMBER_GUIDE); + int res = getLottoPropertyUseCase.getInteger(); if (lotto.isDuplicate(res)) { throw new IllegalArgumentException(); } @@ -73,18 +99,33 @@ private T repeat(Supplier supplier) { return supplier.get(); } catch (ProgramTerminationException e) { throw new IllegalStateException(e.getMessage()); - } catch (RuntimeException ignored) { + } catch (RuntimeException exception) { + lottoPort.sendMessage(exception.getMessage()); } } } private List purchase() { - return List.of(1, 2, 3, 4, 5, 6); // 랜덤으로 숫자를 뽑음 + List numbers = getRandomUseCase.getRandomNumbers(1, 45, 6); + lottoPort.printLottoNumbers(numbers); + return numbers; } - private void printResult(Lotto lotto, int bonusNumber, List purchased) { - List result = purchased.stream() + private void printResult(Lotto lotto, int bonusNumber, List purchased, int amount) { + Map result = purchased.stream() .map(other -> LottoPrize.getLottoPrize(other.countMatch(lotto), other.isMatch(bonusNumber))) - .toList(); + .collect(Collectors.groupingBy( + Function.identity(), + Collectors.counting() + )); + long sum = Stream.of(LottoPrize.FIFTH_PRICE, LottoPrize.FOURTH_PRICE, LottoPrize.THIRD_PRICE, + LottoPrize.SECOND_PRICE, LottoPrize.FIRST_PRICE) + .peek(prize -> + lottoPort.sendMessage(MessageConstants.RESULT_LINE, + prize.getCondition(), + prize.getPrice(), + result.getOrDefault(prize, 0L))) + .reduce(0L, (acc, prize) -> acc + (prize.getPrice() * result.getOrDefault(prize, 0L)), Long::sum); + lottoPort.sendMessage(MessageConstants.RETURN_RATE, (float) (sum / (1000L * amount))); } } diff --git a/src/main/java/lotto/utils/Validator.java b/src/main/java/lotto/utils/Validator.java index d1e2949..17171d8 100644 --- a/src/main/java/lotto/utils/Validator.java +++ b/src/main/java/lotto/utils/Validator.java @@ -25,7 +25,7 @@ public static void isInRange(int value, int min, int max) { } public static void isInRange(List values, int min, int max) { - if (values.stream().allMatch(v -> v >= min && v <= max)) { + if (!values.stream().allMatch(v -> v >= min && v <= max)) { throw new IllegalStateException("[ERROR] Invalid range."); } } From f54b0f50ea62fd485d9a00048aeb0f445d1c8eca Mon Sep 17 00:00:00 2001 From: moongua404 Date: Tue, 27 May 2025 17:52:52 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=EB=B2=84=EA=B7=B8=20=EB=B0=8F=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/domain/enums/LottoPrize.java | 12 +- .../application/port/outport/LottoPort.java | 1 + .../application/service/LottoService.java | 29 ++--- src/test/java/lotto/ApplicationTest.java | 114 +++++++++++++++++- 4 files changed, 128 insertions(+), 28 deletions(-) diff --git a/src/main/java/lotto/application/domain/enums/LottoPrize.java b/src/main/java/lotto/application/domain/enums/LottoPrize.java index b6a1e71..10a49d0 100644 --- a/src/main/java/lotto/application/domain/enums/LottoPrize.java +++ b/src/main/java/lotto/application/domain/enums/LottoPrize.java @@ -32,7 +32,10 @@ public static List getPrizeTypes() { public static LottoPrize getLottoPrize(int matchCount, boolean matchBonus) { if (matchCount == 6) { - return getFirstOrSecondPrize(matchBonus); + return FIRST_PRICE; + } + if (matchCount == 5 && matchBonus) { + return SECOND_PRICE; } if (matchCount == 5) { return THIRD_PRICE; @@ -45,11 +48,4 @@ public static LottoPrize getLottoPrize(int matchCount, boolean matchBonus) { } return NOTHING; } - - private static LottoPrize getFirstOrSecondPrize(boolean matchBonus) { - if (matchBonus) { - return SECOND_PRICE; - } - return FIRST_PRICE; - } } \ No newline at end of file diff --git a/src/main/java/lotto/application/port/outport/LottoPort.java b/src/main/java/lotto/application/port/outport/LottoPort.java index d4677b2..39af2ad 100644 --- a/src/main/java/lotto/application/port/outport/LottoPort.java +++ b/src/main/java/lotto/application/port/outport/LottoPort.java @@ -7,5 +7,6 @@ public interface LottoPort { void sendMessage(MessageConstants message); void sendMessage(MessageConstants message, Object... params); void sendMessage(String message); + void clear(); void printLottoNumbers(List numbers); } diff --git a/src/main/java/lotto/application/service/LottoService.java b/src/main/java/lotto/application/service/LottoService.java index 38f9a56..c3790ad 100644 --- a/src/main/java/lotto/application/service/LottoService.java +++ b/src/main/java/lotto/application/service/LottoService.java @@ -31,19 +31,19 @@ public LottoService(GetLottoPropertyUseCase getLottoPropertyUseCase, } public void run() { - final int amount = repeat(this::getAmount); - final Lotto lotto = repeat(this::getLotto); - final int bonus = repeat(() -> getBonusNumber(lotto)); - - List lottoNumbers = IntStream.range(0, amount).mapToObj(i -> { - Lotto purchased = new Lotto(purchase()); - // lottoNumbers 출력 - - return purchased; + try { + final int amount = repeat(this::getAmount); + lottoPort.clear(); + List lottoNumbers = IntStream.range(0, amount).mapToObj(i -> new Lotto(purchase())).toList(); + lottoPort.clear(); + final Lotto lotto = repeat(this::getLotto); + lottoPort.clear(); + final int bonus = repeat(() -> getBonusNumber(lotto)); + lottoPort.clear(); + printResult(lotto, bonus, lottoNumbers, amount); + } catch (ProgramTerminationException e) { + lottoPort.sendMessage(e.getMessage()); } - ).toList(); - - printResult(lotto, bonus, lottoNumbers, amount); } private int getAmount() { @@ -98,7 +98,7 @@ private T repeat(Supplier supplier) { try { return supplier.get(); } catch (ProgramTerminationException e) { - throw new IllegalStateException(e.getMessage()); + throw new ProgramTerminationException(); } catch (RuntimeException exception) { lottoPort.sendMessage(exception.getMessage()); } @@ -118,6 +118,7 @@ private void printResult(Lotto lotto, int bonusNumber, List purchased, in Function.identity(), Collectors.counting() )); + lottoPort.sendMessage(MessageConstants.WINNING_STATUS); long sum = Stream.of(LottoPrize.FIFTH_PRICE, LottoPrize.FOURTH_PRICE, LottoPrize.THIRD_PRICE, LottoPrize.SECOND_PRICE, LottoPrize.FIRST_PRICE) .peek(prize -> @@ -126,6 +127,6 @@ private void printResult(Lotto lotto, int bonusNumber, List purchased, in prize.getPrice(), result.getOrDefault(prize, 0L))) .reduce(0L, (acc, prize) -> acc + (prize.getPrice() * result.getOrDefault(prize, 0L)), Long::sum); - lottoPort.sendMessage(MessageConstants.RETURN_RATE, (float) (sum / (1000L * amount))); + lottoPort.sendMessage(MessageConstants.RETURN_RATE, (float) ((double) sum / (1000L * amount) * 100)); } } diff --git a/src/test/java/lotto/ApplicationTest.java b/src/test/java/lotto/ApplicationTest.java index a15c7d1..85a4071 100644 --- a/src/test/java/lotto/ApplicationTest.java +++ b/src/test/java/lotto/ApplicationTest.java @@ -1,14 +1,18 @@ package lotto; -import camp.nextstep.edu.missionutils.test.NsTest; -import org.junit.jupiter.api.Test; - -import java.util.List; - import static camp.nextstep.edu.missionutils.test.Assertions.assertRandomUniqueNumbersInRangeTest; import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; import static org.assertj.core.api.Assertions.assertThat; +import camp.nextstep.edu.missionutils.test.NsTest; +import java.util.List; +import java.util.stream.Stream; +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; + class ApplicationTest extends NsTest { private static final String ERROR_MESSAGE = "[ERROR]"; @@ -54,8 +58,106 @@ class ApplicationTest extends NsTest { }); } + @Test + void 로또번호_중복_Error1() { + assertSimpleTest(() -> { + run("5000", "1,2,3,3,5,6", "7"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @Test + void 로또번호_중복_Error2() { + assertSimpleTest(() -> { + run("5000", "1,2,3,4,5,6", "4"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @Test + void 로또번호_범위_Error() { + assertSimpleTest(() -> { + run("5000", "0,2,3,4,5,6", "7"); + assertThat(output()).contains(ERROR_MESSAGE); + + run("5000", "1,2,3,4,5,46", "7"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @Test + void 로또번호_갯수_Error() { + assertSimpleTest(() -> { + runException("5000", "1,2,3,4,5", "7"); + assertThat(output()).contains(ERROR_MESSAGE); + + runException("5000", "1,2,3,4,5,6,7", "7"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @ParameterizedTest + @MethodSource("provideNumbersForTest") + @DisplayName("로또 당첨 결과 테스트") + void 로또_당첨_등수_확인(String purchaseAmount, String winningNumbers, String bonusNumber, String expectedOutput) { + assertRandomUniqueNumbersInRangeTest( + () -> { + run(purchaseAmount, winningNumbers, bonusNumber); + assertThat(output()).contains(expectedOutput); + }, + List.of(1, 2, 3, 4, 5, 6) + ); + } + + static Stream provideNumbersForTest() { + return Stream.of( + Arguments.of("1000", "1,2,3,43,44,45", "7", "3개 일치 (5,000원) - 1개"), + Arguments.of("1000", "1,2,3,4,44,45", "7", "4개 일치 (50,000원) - 1개"), + Arguments.of("1000", "1,2,3,4,5,45", "7", "5개 일치 (1,500,000원) - 1개"), + Arguments.of("1000", "1,2,3,4,5,45", "6", "5개 일치, 보너스 볼 일치 (30,000,000원) - 1개"), + Arguments.of("1000", "1,2,3,4,5,6", "7", "6개 일치 (2,000,000,000원) - 1개") + + ); + } + + @Test + void 수익률_반올림_테스트() { + assertRandomUniqueNumbersInRangeTest( + () -> { + run("3000", "1,2,3,4,5,6", "7"); + try { + assertThat(output()).contains(ERROR_MESSAGE); + } catch (AssertionError e) { + assertThat(output()).contains("총 수익률은 166.7%입니다."); + } + }, + List.of(1, 2, 3, 43, 44, 45), + List.of(40, 41, 42, 43, 44, 45), + List.of(40, 41, 42, 43, 44, 45) + ); + } + + @Test + @DisplayName("죽지도 않고 돌아온 정수 오버플로우") + void OverflowTest() { + assertRandomUniqueNumbersInRangeTest( + () -> { + run("2000", "1,2,3,4,5,6", "7"); + try { + assertThat(output()).contains(ERROR_MESSAGE); + } catch (AssertionError e) { + assertThat(output()).contains("6개 일치 (2,000,000,000원) - 2개"); + assertThat(output()).contains("총 수익률은 200000000.0%입니다."); + } + }, + List.of(1, 2, 3, 4, 5, 6), + List.of(1, 2, 3, 4, 5, 6), + List.of(1, 2, 3, 4, 5, 6) + ); + } + @Override public void runMain() { Application.main(new String[]{}); } -} +} \ No newline at end of file