diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..54caf7bf823 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,34 @@ +# 로또 + +## 기능 목록 +- [V] 랜덤 하게 생성한 로또 번호와 사용자가 임의로 선정한 당첨 번호를 비교하여 + 당첨된 등수와 수익률을 계산한다. + - [V] 사용자로부터 로또 구입 금액을 입력 받는다. + - [V] 입력 받은 금액에 따라 랜덤하게 6개의 숫자를 생성하여 로또 번호를 생성한다.(1~45범위, 중복X, 오름차순 정렬) + - [V] 사용자로부터 당첨 번호와 보너스 번호를 입력 받은 후, 생성한 로또 번호와 비교하여 등수를 출력한다. + - [V] 수익률을 계산하여 출력한다.(소수점 둘떄 자리 반올림) + -[]입력값 예외처리 + -[V]로또 번호 중복 예외처리 + -[V]로또 번호 범위 벗어남 예외처리 + -[] 보너스 번호 예외처리 +## 기능 요구 사항 +- 로또 번호의 숫자 범위는 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원 + +### 추가된 요구 사항 +-[V] 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. +- 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다. +-[V] else 예약어를 쓰지 않는다. +- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. +- else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. +-[V] Java Enum을 적용한다. +-[] 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다. + - 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다. + - 단위 테스트 작성이 익숙하지 않다면 `test/java/lotto/LottoTest`를 참고하여 학습한 후 테스트를 구현한다. \ No newline at end of file diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922ba44..7a123e1cfb7 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,13 @@ package lotto; +import lotto.controller.LottoController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + LottoController.setPrice(); + LottoController.setBuyLottoNumberPrint(); + LottoController.setPrizeNumberInput(); + LottoController.winningStatistic(); + LottoController.PerformanceCalculation(); } } diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java deleted file mode 100644 index 519793d1f73..00000000000 --- a/src/main/java/lotto/Lotto.java +++ /dev/null @@ -1,20 +0,0 @@ -package lotto; - -import java.util.List; - -public class Lotto { - private final List numbers; - - public Lotto(List numbers) { - validate(numbers); - this.numbers = numbers; - } - - private void validate(List numbers) { - if (numbers.size() != 6) { - throw new IllegalArgumentException(); - } - } - - // TODO: 추가 기능 구현 -} diff --git a/src/main/java/lotto/constants/ErrorMessage.java b/src/main/java/lotto/constants/ErrorMessage.java new file mode 100644 index 00000000000..b5692342b09 --- /dev/null +++ b/src/main/java/lotto/constants/ErrorMessage.java @@ -0,0 +1,21 @@ +package lotto.constants; + +public enum ErrorMessage { + DUPLICATION("[ERROR] 값이 중복되었습니다"), + ISNOTINTEGER("[ERROR] 숫자를 입력해주세요"), + SIXNUMBER("[ERROR] 번호 6개를 입력해주세요"), + OUTFRANGE("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."), + ONENUMBER("[ERROR] 숫자 번호 1개를 입력해주세요"), + NOTTHOUSAND("[ERROR] 1000원 단위로 입력해 주세요"); + + + private final String message; + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/lotto/controller/LottoController.java b/src/main/java/lotto/controller/LottoController.java new file mode 100644 index 00000000000..8c415ab7049 --- /dev/null +++ b/src/main/java/lotto/controller/LottoController.java @@ -0,0 +1,50 @@ +package lotto.controller; + +import lotto.model.Lotto; +import lotto.model.LottoNumberMaker; +import lotto.model.LottoPercentageCalculation; +import lotto.model.constants.LottoPrize; +import lotto.view.LottoInput; +import lotto.view.LottoOutput; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.Arrays.asList; +import static lotto.model.constants.LottoPrize.*; + +public class LottoController { + private static final LottoNumberMaker lottoNumberMaker = new LottoNumberMaker(); + private static final LottoInput lottoInput = new LottoInput(); + private static final LottoOutput lottoOutput = new LottoOutput(); + private static final LottoPercentageCalculation lottoPercentageCalculation = new LottoPercentageCalculation(); + public static void setPrice() { + lottoNumberMaker.checkInt(); + } + +// static List LottoPrizelist= asList(FIFTH_PRIZE,FOURTH_PRIZE,THIRD_PRIZE,SECOND_PRIZE,FIRST_PRIZE); + public static void setBuyLottoNumberPrint() { + lottoOutput.buyLottoNumberPrint(lottoNumberMaker.getLottoNumber()); + } + + public static void setPrizeNumberInput() { + Lotto lotto = new Lotto(lottoInput.prizeNumberInput()); + lotto.checkSame(lottoInput.bonusNumberInput(),lottoNumberMaker.getLottoNumber()); + } + + public static void winningStatistic() { + List lottoPrizes = new ArrayList<>(); + for(LottoPrize lottoPrize: LottoPrize.values()){ + lottoPrizes.add(lottoPrize.getText()+lottoPrize.getWinCount()+lottoPrize.getUnit()); + } + LottoOutput.seeWinningStatstic(lottoPrizes); + } + + public static void PerformanceCalculation() { + lottoOutput.seePercentage(lottoPercentageCalculation.percentageCalculation(LottoPrize.values(),lottoNumberMaker.getBuyPrice())); + } + + public String askPrice() { + return lottoInput.askPrice(); + } +} \ No newline at end of file diff --git a/src/main/java/lotto/model/Lotto.java b/src/main/java/lotto/model/Lotto.java new file mode 100644 index 00000000000..caf4b180fc6 --- /dev/null +++ b/src/main/java/lotto/model/Lotto.java @@ -0,0 +1,79 @@ +package lotto.model; + +import lotto.model.constants.LottoPrize; + +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; + +import static lotto.constants.ErrorMessage.DUPLICATION; +import static lotto.constants.ErrorMessage.SIXNUMBER; +import static lotto.model.constants.LottoPrize.*; +public class Lotto { + private final List numbers; + + public Lotto(List numbers) { + validate(numbers); + duplicate(numbers); + this.numbers = numbers; + } + + private void duplicate(List numbers) { + List duplication = numbers.stream() + .distinct() + .toList(); + if (duplication.size() != numbers.size()) { + throw new IllegalArgumentException(DUPLICATION.getMessage()); + } + } + + private void validate(List numbers) { + if (numbers.size() != 6) { + throw new IllegalArgumentException(SIXNUMBER.getMessage()); + } + } + public void checkSame(Integer bonusNumber, List> lottoNumber) { + bonusDuplication(bonusNumber); + for (List integers : lottoNumber) { + List Result = integers.stream() + .filter(i -> this.numbers.stream().anyMatch(Predicate.isEqual(i))) + .toList(); + long bonusResult = integers.stream() + .filter(bonusNumber::equals).count(); + CheckPrize(Result.size(), bonusResult).addWinCount(); + + } + } + + private void bonusDuplication(Integer bonusNumber) { + List Result = this.numbers.stream() + .filter(i-> !Objects.equals(i, bonusNumber)) + .toList(); + if(Result.size()!=6){ + throw new IllegalArgumentException(DUPLICATION.getMessage()); + } + + } + + public LottoPrize CheckPrize(int result, long bonusResult){ + if(result==6){ + return FIRST_PRIZE; + } + if(result==5&&bonusResult==1){ + return SECOND_PRIZE; + } + if(result==5&&bonusResult==0){ + return THIRD_PRIZE; + } + if(result==4){ + return FOURTH_PRIZE; + } + if(result==3){ + return FIFTH_PRIZE; + } + if(result<3){ + return LOOSE; + } + throw new IllegalArgumentException("존재 하지 않는 순위입니다."); + } +} diff --git a/src/main/java/lotto/model/LottoNumberMaker.java b/src/main/java/lotto/model/LottoNumberMaker.java new file mode 100644 index 00000000000..ff81279d9d9 --- /dev/null +++ b/src/main/java/lotto/model/LottoNumberMaker.java @@ -0,0 +1,67 @@ +package lotto.model; + +import camp.nextstep.edu.missionutils.Randoms; +import lotto.controller.LottoController; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static lotto.constants.ErrorMessage.ISNOTINTEGER; +import static lotto.constants.ErrorMessage.NOTTHOUSAND; + +public class LottoNumberMaker { + private static int buyPrice; + private static final List> numbers = new ArrayList<>(); + private static final LottoController lottoController = new LottoController(); + + public static int getBuyPrice() { + return buyPrice; + } + + public void makeLottoNumber(Integer count) { + buyPrice = count; + checkThousand(count); + IntStream.range(0, count / 1000) + .forEach(i -> numbers.add(makeRandomNumber())); + } + + private void checkThousand(Integer count) { + if(count%1000!=0){ + throw new IllegalArgumentException(NOTTHOUSAND.getMessage()); + } + return; + } + + private List makeRandomNumber() { + return Randoms.pickUniqueNumbersInRange(1, 45, 6).stream() + .sorted() + .collect(Collectors.toList()); + } + + public void checkInt() { + while (true) { + try { + int number = checkCount(); + makeLottoNumber(number); + break; + } catch (IllegalArgumentException e) { + System.out.println(ISNOTINTEGER.getMessage()); + System.out.println(NOTTHOUSAND.getMessage()); + } + } + } + + private int checkCount() { + try { + return Integer.parseInt(lottoController.askPrice()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ISNOTINTEGER.getMessage()); + } + } + + public List> getLottoNumber() { + return numbers; + } +} diff --git a/src/main/java/lotto/model/LottoPercentageCalculation.java b/src/main/java/lotto/model/LottoPercentageCalculation.java new file mode 100644 index 00000000000..b0175b6d7be --- /dev/null +++ b/src/main/java/lotto/model/LottoPercentageCalculation.java @@ -0,0 +1,14 @@ +package lotto.model; + +import lotto.model.constants.LottoPrize; + +public class LottoPercentageCalculation { + + public double percentageCalculation(LottoPrize[] values, int lottoPrice) { + double profit = 0; + for(LottoPrize lottoPrize: values){ + profit+=lottoPrize.getWinCount()*lottoPrize.getPrizeMoney(); + } + return (profit/lottoPrice)*100; + } +} diff --git a/src/main/java/lotto/model/constants/LottoPrize.java b/src/main/java/lotto/model/constants/LottoPrize.java new file mode 100644 index 00000000000..9aa9e48fd8f --- /dev/null +++ b/src/main/java/lotto/model/constants/LottoPrize.java @@ -0,0 +1,39 @@ +package lotto.model.constants; + +public enum LottoPrize { + FIRST_PRIZE(0,"6개 일치 (2,000,000,000원) - ","개",2000000000), + SECOND_PRIZE(0,"5개 일치, 보너스 볼 일치 (30,000,000원) - ","개", 30000000), + THIRD_PRIZE(0,"5개 일치 (1,500,000원) - ","개", 1500000), + FOURTH_PRIZE(0,"4개 일치 (50,000원) - ","개", 50000), + FIFTH_PRIZE(0,"3개 일치 (5,000원) - ","개", 5000), + LOOSE(0,"","개", 0); + + + private int winCount; + private final String text; + private final String unit; + private int prizeMoney; + + LottoPrize(int winCount, String text, String unit, int prizemoney) { + this.winCount = winCount; + this.text = text; + this.unit = unit; + this.prizeMoney = prizemoney; + } + public int getWinCount() { + return winCount; + } + public String getText() { + return text; + } + public String getUnit() { + return unit; + } + public int getPrizeMoney() { + return prizeMoney; + } + + public void addWinCount() { + this.winCount ++; + } +} diff --git a/src/main/java/lotto/view/ConstantsMessage.java b/src/main/java/lotto/view/ConstantsMessage.java new file mode 100644 index 00000000000..39c5413214c --- /dev/null +++ b/src/main/java/lotto/view/ConstantsMessage.java @@ -0,0 +1,21 @@ +package lotto.view; + +public enum ConstantsMessage { + ASK_BUY_PRICE("구입 금액을 입력해 주세요."), + BUY_RESULT_MESSAGE("개를 구매했습니다."), + ASK_PRIZE_NUMBER("당첨 번호를 입력해 주세요."), + ASK_BONUS_NUMBER("보너스 번호를 입력해 주세요."), + WIN_RESULT_MESSAGE("당첨통계"), + lINE_SEPARATION_MESSAGE("---"), + PERCENT_FRONT_MESSAGE("총 수익률은 "), + PERCENT_BACK_MESSAGE("%입니다."); + + private final String message; + + ConstantsMessage(String message) { + this.message = message; + } + public String getMessage() { + return message; + } +} diff --git a/src/main/java/lotto/view/LottoInput.java b/src/main/java/lotto/view/LottoInput.java new file mode 100644 index 00000000000..215259bcbe0 --- /dev/null +++ b/src/main/java/lotto/view/LottoInput.java @@ -0,0 +1,62 @@ +package lotto.view; + +import camp.nextstep.edu.missionutils.Console; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static lotto.constants.ErrorMessage.*; +import static lotto.view.ConstantsMessage.*; + +public class LottoInput { + public String askPrice() { + System.out.println(ASK_BUY_PRICE.getMessage()); + String input = Console.readLine(); + return input; + + } + + public List prizeNumberInput() { + while (true) { + try { + printNewLine(); + System.out.println(ASK_PRIZE_NUMBER.getMessage()); + return changeInt(Arrays.asList(Console.readLine().split(","))); + }catch (IllegalArgumentException e){ + System.out.println(OUTFRANGE.getMessage()); + } + } + } + public Integer bonusNumberInput() { + try{ + printNewLine(); + System.out.println(ASK_BONUS_NUMBER.getMessage()); + String input = Console.readLine(); + + return Integer.parseInt(input); + }catch (NumberFormatException e){ + throw new IllegalArgumentException(ONENUMBER.getMessage()); + } + + } + private List changeInt(List prizeNumbers) { + try{ + List numbers = prizeNumbers.stream() + .map(Integer::parseInt) + .filter(i->i>0&&i<45) + .toList(); + if(numbers.size() != prizeNumbers.size()){ + throw new NumberFormatException(); + } + return numbers; + }catch (NumberFormatException e){ + throw new IllegalArgumentException(OUTFRANGE.getMessage()); + } + + } + + private void printNewLine() { + System.out.println(); + } +} diff --git a/src/main/java/lotto/view/LottoOutput.java b/src/main/java/lotto/view/LottoOutput.java new file mode 100644 index 00000000000..b2618432d5f --- /dev/null +++ b/src/main/java/lotto/view/LottoOutput.java @@ -0,0 +1,35 @@ +package lotto.view; + +import camp.nextstep.edu.missionutils.Console; + + +import java.util.List; + +import static lotto.view.ConstantsMessage.*; + +public class LottoOutput { + + + + public void buyLottoNumberPrint(List> lottoNumber) { + printNewLine(); + System.out.println(lottoNumber.size()+BUY_RESULT_MESSAGE.getMessage()); + lottoNumber.forEach(System.out::println); + } + + public static void seeWinningStatstic(List lottoPrizes) { + System.out.println(WIN_RESULT_MESSAGE.getMessage()); + System.out.println(lINE_SEPARATION_MESSAGE.getMessage()); + for(String lottoPrize : lottoPrizes){ + System.out.println(lottoPrize); + } + + } + private void printNewLine() { + System.out.println(); + } + + public void seePercentage(double percentage) { + System.out.println(PERCENT_FRONT_MESSAGE.getMessage()+percentage+PERCENT_BACK_MESSAGE.getMessage()); + } +} diff --git a/src/test/java/lotto/ApplicationTest.java b/src/test/java/lotto/ApplicationTest.java index a15c7d1f522..45a9cd8be04 100644 --- a/src/test/java/lotto/ApplicationTest.java +++ b/src/test/java/lotto/ApplicationTest.java @@ -54,6 +54,14 @@ class ApplicationTest extends NsTest { }); } + @Test + void 로또번호_예외_테스트() { + assertSimpleTest(() -> { + runException("1, 2, 3, 4, 5, 51"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + @Override public void runMain() { Application.main(new String[]{}); diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 9f5dfe7eb83..50d257fe80f 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/LottoTest.java @@ -1,5 +1,7 @@ package lotto; +import lotto.model.Lotto; +import lotto.model.LottoNumberMaker; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -22,6 +24,11 @@ void createLottoByDuplicatedNumber() { assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 5))) .isInstanceOf(IllegalArgumentException.class); } + @DisplayName("로또 번호가 6개 미만이면 예외가 발생한다.") + @Test + void createLottoByMinSize() { + assertThatThrownBy(() -> new Lotto(List.of(1))) + .isInstanceOf(IllegalArgumentException.class); + } - // 아래에 추가 테스트 작성 가능 } \ No newline at end of file