diff --git a/docs/README.md b/docs/README.md index e69de29..0c0fe4c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,6 @@ +- 로또 1개 클래스 +- 로또 n개 묶음 클래스 +- 로또 발행기? (입력받은 가격만큼 생성, 가격 안 맞으면 오류) +- 당첨번호 입력 (6자리인지, 중복 없는지, 1~45의 숫자인지) +- 당첨내역 확인 (몇 개가 일치하는지.. 어떻게하지) +- 수익률 diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922..a0862b0 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,11 @@ package lotto; +import lotto.controller.LottoController; + public class Application { public static void main(String[] args) { // TODO: 프로그램 구현 + LottoController lottoController = new LottoController(); + lottoController.lottoStart(); } } diff --git a/src/main/java/lotto/View/InputView.java b/src/main/java/lotto/View/InputView.java new file mode 100644 index 0000000..62249be --- /dev/null +++ b/src/main/java/lotto/View/InputView.java @@ -0,0 +1,43 @@ +package lotto.View; + +import org.kokodak.Console; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class InputView { + + private static final String INVALID_VALUE = "[ERROR] 올바른 값을 입력하세요."; + + public static int getPurchaseAmount() { + System.out.println("구입금액을 입력해 주세요."); + try { + return Integer.parseInt(Console.readLine()); + } catch (NumberFormatException numberFormatException) { + throw new IllegalArgumentException(INVALID_VALUE); + } + } + +// 찾아볼필요 ㅇ + public static List getWinningNums() { + System.out.println("당첨 번호를 입력해 주세요."); + try { + return Arrays.stream(Console.readLine().split(",")) + .map(Integer::parseInt) + .collect(Collectors.toList()); + } catch (NumberFormatException numberFormatException) { + throw new IllegalArgumentException(INVALID_VALUE); + } + + } + + public static int getBonusNum() { + System.out.println("보너스 번호를 입력해 주세요."); + try { + return Integer.parseInt(Console.readLine()); + } catch (NumberFormatException numberFormatException) { + throw new IllegalArgumentException(INVALID_VALUE); + } + } +} diff --git a/src/main/java/lotto/View/OutputView.java b/src/main/java/lotto/View/OutputView.java new file mode 100644 index 0000000..8e3ef56 --- /dev/null +++ b/src/main/java/lotto/View/OutputView.java @@ -0,0 +1,59 @@ +package lotto.View; + +import lotto.domain.Lottos; +import lotto.service.WinningRank; + +import java.text.DecimalFormat; +import java.util.Map; + +public class OutputView { + + private static final String WINNING_DETAILS_MESSAGE = "%d개 일치 (%s원) - %d개\n"; + private static final String WINNING_DETAILS_MESSAGE_WITH_BONUS_MASSAGE = "%d개 일치, 보너스 볼 일치 (%s원) - %d개\n"; + + public static void printHowManyLottoUserPurchased(int lottoQuantity) { + System.out.println(lottoQuantity + "개를 구매했습니다."); + } + + public static void printLottos(Lottos lottos) { + lottos.getLottos() + .forEach(lotto -> System.out.println(lotto.getNumbers().toString())); + } + + public static void printWinningDetails(Map winningDetails) { + System.out.println("당첨 통계"); + System.out.println("---"); + winningDetails.entrySet().stream() + .filter(entry->entry.getKey()!=WinningRank.LAST_PLACE) + .forEach(entry->{ + if (entry.getKey() == WinningRank.SECOND_PLACE){ + printWinningDetailsWithBonus(entry); + } + printWinningDetails(entry); + }); + } + + public static void printProfitRate(double profitRate) { + System.out.printf("총 수익률은 %.1f%%입니다.\n", profitRate); + } + + + private static void printWinningDetails(Map.Entry entry) { + System.out.printf(WINNING_DETAILS_MESSAGE, + entry.getKey().getMatchCount(), + getFormattingPrice(entry.getKey().getPrize()), + entry.getValue()); + } + + private static void printWinningDetailsWithBonus(Map.Entry entry) { + System.out.printf(WINNING_DETAILS_MESSAGE_WITH_BONUS_MASSAGE, + entry.getKey().getMatchCount(), + getFormattingPrice(entry.getKey().getPrize()), + entry.getValue()); + } + + private static String getFormattingPrice(int prize) { + DecimalFormat df = new DecimalFormat("###,###"); + return df.format(prize); + } +} diff --git a/src/main/java/lotto/controller/LottoController.java b/src/main/java/lotto/controller/LottoController.java new file mode 100644 index 0000000..5145636 --- /dev/null +++ b/src/main/java/lotto/controller/LottoController.java @@ -0,0 +1,33 @@ +package lotto.controller; + +import lotto.View.InputView; +import lotto.View.OutputView; +import lotto.domain.*; +import lotto.service.LottoGenerator; +import lotto.service.WinningDetails; +import lotto.service.WinningRank; + +import java.util.Map; + +public class LottoController { + + public void lottoStart() { + try { + int money = InputView.getPurchaseAmount(); //금액 입력 + LottoGenerator lottoGenerator = new LottoGenerator(money); + Lottos lottos = new Lottos(lottoGenerator.generateLottos()); //개수만큼 로또 생성 + + OutputView.printHowManyLottoUserPurchased(lottoGenerator.getLottoQuantity()); + OutputView.printLottos(lottos); //구매 개수랑 출력 + + LottoWinningNumber lottoWinningNumber = new LottoWinningNumber(InputView.getWinningNums(), InputView.getBonusNum()); + Map winningDetails = WinningDetails.getWinningDetails(lottos, lottoWinningNumber); + + OutputView.printWinningDetails(winningDetails); + long profit = WinningDetails.getProfit(winningDetails); + OutputView.printProfitRate(WinningDetails.getProfitRate(profit, money)); //당첨결과, 수익률 + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/domain/Lotto.java similarity index 80% rename from src/main/java/lotto/Lotto.java rename to src/main/java/lotto/domain/Lotto.java index 519793d..5d57dfd 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; @@ -17,4 +17,8 @@ private void validate(List numbers) { } // TODO: 추가 기능 구현 + + public List getNumbers() { + return numbers; + } } diff --git a/src/main/java/lotto/domain/LottoWinningNumber.java b/src/main/java/lotto/domain/LottoWinningNumber.java new file mode 100644 index 0000000..52e248e --- /dev/null +++ b/src/main/java/lotto/domain/LottoWinningNumber.java @@ -0,0 +1,54 @@ +package lotto.domain; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class LottoWinningNumber { + + private final List winningNums; + private final int bonusNum; + + + public LottoWinningNumber(List winningNums, int bonusNum) { +// 에러 체크 + validateDuplicateNum(winningNums, bonusNum); + isBetween1and45(winningNums, bonusNum); + + this.winningNums = winningNums; + this.bonusNum = bonusNum; + } + + public List getWinningNums() { + return winningNums; + } + + public int getBonusNum() { + return bonusNum; + } + + // 중복 번호 체크 + private void validateDuplicateNum(List winningNum, int bonusNum) { + Set duplicateCheck = new HashSet<>(winningNum); + if (winningNum.size() != duplicateCheck.size()) { + throw new IllegalArgumentException("[ERROR] 당첨 번호는 서로 다른 6개의 숫자여야 합니다."); + } + if (winningNum.contains(bonusNum)) { + throw new IllegalArgumentException("[ERROR] 당첨 번호와 보너스 번호가 중복됩니다."); + } + } + +// 1~45 사이의 번호 + private void isBetween1and45(List winningNums, int bonusNum) { + for (int num : winningNums) { + if (num < 1 || num > 45) { + throw new IllegalArgumentException("[ERROR] 당첨 번호는 1부터 45 사이의 숫자여야 합니다."); + } + } + if (bonusNum < 1 || bonusNum > 45) { + throw new IllegalArgumentException("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다."); + } + } + + +} diff --git a/src/main/java/lotto/domain/Lottos.java b/src/main/java/lotto/domain/Lottos.java new file mode 100644 index 0000000..51c9860 --- /dev/null +++ b/src/main/java/lotto/domain/Lottos.java @@ -0,0 +1,18 @@ +package lotto.domain; + +import lotto.domain.Lotto; + +import java.util.List; + +public class Lottos { + + private List lottos; + + public Lottos(List lottos) { + this.lottos = lottos; + } + + public List getLottos() { + return lottos; + } +} diff --git a/src/main/java/lotto/service/LottoGenerator.java b/src/main/java/lotto/service/LottoGenerator.java new file mode 100644 index 0000000..40a7800 --- /dev/null +++ b/src/main/java/lotto/service/LottoGenerator.java @@ -0,0 +1,54 @@ +package lotto.service; + + +import lotto.domain.Lotto; +import org.kokodak.Randoms; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + + + +public class LottoGenerator { + + private static final int LOTTO_PRICE = 1000; //로또 1개 1000원 + + private final List lottos = new ArrayList<>(); + private final int lottoQuantity; //로또 개수(양) + + public LottoGenerator(int money) { + validateMoney(money); + lottoQuantity = money/LOTTO_PRICE; + } + +// 금액만큼 로또 생성 + public List generateLottos() { + for (int i = 0; i < lottoQuantity; i++) { + lottos.add(generateLotto()); + } + return lottos; + } + +// 구입 로또 개수 + public int getLottoQuantity() { + return lottoQuantity; + } + + + +// 금액확인 + private void validateMoney(int money) { + if (money <= 0 || money % LOTTO_PRICE != 0){ + throw new IllegalArgumentException("[ERROR] 로또 구입 단위는 1,000원 단위입니다."); + } + } + +// 로또 1개 생성 + private Lotto generateLotto() { + List numbers = new ArrayList<>(Randoms.pickUniqueNumbersInRange(1, 45, 6)); + numbers.sort(Comparator.naturalOrder()); //오름차순 + return new Lotto(numbers); + } + +} diff --git a/src/main/java/lotto/service/WinningDetails.java b/src/main/java/lotto/service/WinningDetails.java new file mode 100644 index 0000000..51b191b --- /dev/null +++ b/src/main/java/lotto/service/WinningDetails.java @@ -0,0 +1,69 @@ +package lotto.service; + +import lotto.domain.Lotto; +import lotto.domain.LottoWinningNumber; +import lotto.domain.Lottos; + +import java.util.Arrays; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public class WinningDetails { + + public static Map getWinningDetails(Lottos lottos, LottoWinningNumber lottoWinningNumber) { + Map winningDetails = generateWinningDetails(); + for (Lotto lotto : lottos.getLottos()) { + int matchCount = compareWithWinningNums(lotto, lottoWinningNumber); + boolean containsBonusNum = compareWithBonusNum(lotto, lottoWinningNumber, matchCount); + WinningRank winningRank = WinningRank.findWinningRank(matchCount, containsBonusNum); + winningDetails.replace(winningRank, winningDetails.get(winningRank) + 1); + } + return winningDetails; + } + +// 찾아볼필요가.. + public static long getProfit(Map winningDetails) { + return winningDetails.entrySet() + .stream() + .mapToLong(entry -> (long) entry.getKey().getPrize() * entry.getValue()) + .sum(); + } + +// 수익률 구하는 방법이 뭐지 + public static double getProfitRate(long profit, int money) { + double profitRate = 100 + (double) (profit - money) / money * 100; + return Math.round(profitRate * 10) * 10.0; + } + + + + + private static Map generateWinningDetails() { + Map winningDetails = new EnumMap<>(WinningRank.class); + Arrays.stream(WinningRank.values()) + .forEach(winningRank -> winningDetails.put(winningRank, 0)); + return winningDetails; + } + + +// 로또 1개와 당첨번호 비교, 일치하는 개수 찾기(스트림 필터) + private static int compareWithWinningNums(Lotto lotto, LottoWinningNumber lottoWinningNumber) { + List nums = lotto.getNumbers(); + List winningNums = lottoWinningNumber.getWinningNums(); + return (int) nums.stream() + .filter(winningNums::contains) + .count(); + } + +// 5개 일치 시에만 보너스 번호 필요 + private static boolean compareWithBonusNum(Lotto lotto, LottoWinningNumber lottoWinningNumber, int matchCount) { + if (matchCount != 5) { + return false; + } + List nums = lotto.getNumbers(); + int bonusNum = lottoWinningNumber.getBonusNum(); + return nums.contains(bonusNum); //있으면 true 없으면 false return + } + +} diff --git a/src/main/java/lotto/service/WinningRank.java b/src/main/java/lotto/service/WinningRank.java new file mode 100644 index 0000000..9ba1ade --- /dev/null +++ b/src/main/java/lotto/service/WinningRank.java @@ -0,0 +1,40 @@ +package lotto.service; + +import java.util.Arrays; + +public enum WinningRank { +// (일치 개수, 보너스볼 일치 여부, 상금) + FIRST_PLACE(6, false, 2_000_000_000), + SECOND_PLACE(5, true, 30_000_000), + THIRD_PLACE(5, false, 1_500_000), + FOURTH_PLACE(4, false, 50_000), + FIFTH_PLACE(3, false, 5_000), + LAST_PLACE(0, false, 0); + + private final int matchCount; + private final boolean containsBonusNum; + private final int prize; + + WinningRank(int matchCount, boolean containsBonusNum, int prize) { + this.matchCount = matchCount; + this.containsBonusNum = containsBonusNum; + this.prize = prize; + } + +// 등수 찾기 + public static WinningRank findWinningRank(int matchCount, boolean containsBonusNum) { + return Arrays.stream(values()) + .filter(winningRank -> winningRank.matchCount == matchCount) + .filter(winningRank -> winningRank.containsBonusNum == containsBonusNum) + .findFirst() + .orElse(WinningRank.LAST_PLACE); + } + + public int getMatchCount() { + return matchCount; + } + + public int getPrize() { + return prize; + } +} diff --git a/src/test/java/lotto/ApplicationTest.java b/src/test/java/lotto/ApplicationTest.java index 404cbe1..b4906eb 100644 --- a/src/test/java/lotto/ApplicationTest.java +++ b/src/test/java/lotto/ApplicationTest.java @@ -1,13 +1,14 @@ package lotto; +import org.junit.jupiter.api.Test; +import org.kokodak.test.NsTest; + +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; import static org.kokodak.test.Assertions.assertRandomUniqueNumbersInRangeTest; import static org.kokodak.test.Assertions.assertSimpleTest; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.kokodak.test.NsTest; - class ApplicationTest extends NsTest { private static final String ERROR_MESSAGE = "[ERROR]"; diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 14ed50f..8f9a149 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/LottoTest.java @@ -3,6 +3,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; + +import lotto.domain.Lotto; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test;