diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..b8e5536554b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,41 @@ +# Model +## Amount +- [x] 로또 구입 금액을 저장하는 객체 +- [x] 예외 처리 : 잘못된 입력값이 들어왔을 때 IllegalArgumentException을 발생시킨다. + - [x] 입력값이 1,000원으로 나누어 떨어지지 않았을 때 IllegalArgumentException를 발생시킨다. + - [x] 당첨 번호 숫자가 1 ~ 45 사이의 숫자가 아닌 경우 IllegalArgumentException를 발생시킨다. + + +## MyLottos +- [x] 로또들을 구입 금액에 맞게 구매하는 객체 +- [x] 로또 번호들을 오름차순으로 정렬한다. + +## WinningNumbers +- [x] 당첨 번호, 보너스 번호를 저장하는 객체 +- [x] 예외 처리 : 잘못된 입력값이 들어왔을 때 IllegalArgumentException을 발생시킨다. + - [x] 보너스 번호가 당첨 번호와 중복될 경우 IllegalArgumentException를 발생시킨다. + - [x] 당첨 번호들 중 1보다 작거나 45보다 큰 수가 있을 때 IllegalArgumentException을 발생시킨다. + - [x] 보너스 번호가 1보다 작거나 45보다 클 때 IllegalArgumentException을 발생시킨다. + +## Result +- [x] 당첨 번호, 보너스 번호와 구매한 로또들을 비교해서 결과를 계산하는 객체 +- [x] WinningNumbers와 Lotto를 비교해서 맞은 개수를 계산 +- [x] bonusNumber와 Lotto를 비교해서 bonusNumber가 존재하는지 확인 +- [x] 맞은 개수와 bonuse가 맞았는지를 통해서 로또가 몇 등인지 확인 +- [x] 해당하는 등수의 상금을 더하기 +- [x] 수익률을 계산하기 + +# View +## OutputView +- [x] 구매한 로또들의 번호를 출력 +- [x] 당첨 통계를 출력 + + +## InputView +- [x] 로또 구입 금액을 입력 +- [x] 당첨 번호를 입력 +- [x] 보너스 번호를 입력 +- [x] 예외 처리 : 잘못된 입력값이 들어왔을 때 IllegalArgumentException을 발생시킨다. + - [x] 숫자가 아닌 값을 입력했을 때 + + diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922ba44..bd00694781f 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,10 @@ package lotto; +import lotto.controller.Controller; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + Controller controller = new Controller(); + controller.run(); } } diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java index 519793d1f73..453bcb27801 100644 --- a/src/main/java/lotto/Lotto.java +++ b/src/main/java/lotto/Lotto.java @@ -1,9 +1,11 @@ package lotto; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class Lotto { - private final List numbers; + private List numbers; public Lotto(List numbers) { validate(numbers); @@ -11,10 +13,22 @@ public Lotto(List numbers) { } private void validate(List numbers) { + validateArgumentIsNotOversized(numbers); + validateNumbersAreUnique(numbers); + } + private void validateArgumentIsNotOversized(List numbers) { if (numbers.size() != 6) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("로또 번호는 6개여야 합니다."); + } + } + private void validateNumbersAreUnique(List numbers) { + Set numbersSet = new HashSet<>(numbers); + if (numbersSet.size() != numbers.size()) { + throw new IllegalArgumentException("로또 번호는 모두 다른 숫자들로 구성되어야 합니다."); } } - // TODO: 추가 기능 구현 + public List getNumbers() { + return numbers; + } } diff --git a/src/main/java/lotto/controller/Controller.java b/src/main/java/lotto/controller/Controller.java new file mode 100644 index 00000000000..9cf21f96287 --- /dev/null +++ b/src/main/java/lotto/controller/Controller.java @@ -0,0 +1,55 @@ +package lotto.controller; + +import java.util.List; +import java.util.ArrayList; + +import lotto.model.Amount; +import lotto.model.MyLottos; +import lotto.model.Result; +import lotto.model.WinningNumbers; +import lotto.view.InputView; +import lotto.view.OutputView; + +public class Controller { + enum ProgressState { + START, + INPUT_PURCHASE_AMOUNT, + INPUT_WINNING_NUMBERS, + INPUT_BONUS_NUMBER, + DONE + }; + public void run() { + ProgressState progressState = ProgressState.START; + Amount amount = new Amount(0); + MyLottos myLottos = new MyLottos(0); + WinningNumbers winningNumbers; + List winningNumberList = new ArrayList<>(); + int bonusNumber; + + + while (progressState != ProgressState.DONE) { + try { + if (progressState == ProgressState.START) { + int purchaseAmount = InputView.getPurchaseAmount(); + amount = new Amount(purchaseAmount); + myLottos = new MyLottos(purchaseAmount); + OutputView.printMyLottosNumbers(myLottos); + progressState = ProgressState.INPUT_PURCHASE_AMOUNT; + } + if (progressState == ProgressState.INPUT_PURCHASE_AMOUNT) { + winningNumberList = InputView.getWinningNumbers(); + progressState = ProgressState.INPUT_WINNING_NUMBERS; + } + if (progressState == ProgressState.INPUT_WINNING_NUMBERS) { + bonusNumber = InputView.getBonusNumber(); + winningNumbers = new WinningNumbers(winningNumberList, bonusNumber); + Result result = new Result(winningNumbers, myLottos, amount); + OutputView.printTotalStatus(result); + progressState = ProgressState.DONE; + } + } catch (IllegalArgumentException e) { + System.out.println("[ERROR] " + e.getMessage()); + } + } + } +} diff --git a/src/main/java/lotto/model/Amount.java b/src/main/java/lotto/model/Amount.java new file mode 100644 index 00000000000..881156886fe --- /dev/null +++ b/src/main/java/lotto/model/Amount.java @@ -0,0 +1,20 @@ +package lotto.model; + +public class Amount { + private int value; + + public Amount(int value) { + validateArgumentIsDividedTo1000(value); + this.value = value; + } + + private void validateArgumentIsDividedTo1000(int value) { + if (value % 1000 != 0) { + throw new IllegalArgumentException("1,000원 단위의 금액을 입력해주세요."); + } + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/lotto/model/MyLottos.java b/src/main/java/lotto/model/MyLottos.java new file mode 100644 index 00000000000..aee45f2f788 --- /dev/null +++ b/src/main/java/lotto/model/MyLottos.java @@ -0,0 +1,39 @@ +package lotto.model; + +import lotto.Lotto; + +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; + +import camp.nextstep.edu.missionutils.Randoms; + +public class MyLottos { + private int count; + private List myLottos = new ArrayList<>(); + + public MyLottos(int value) { + // create count using amount value + this.count = value / 1000; + // count 만큼의 로또 발행 + for (int i = 0; i < count; i++) { + myLottos.add(makeLotto()); + } + } + + private Lotto makeLotto() { + List numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6); + List lottoNumbers = new ArrayList<>(numbers); + Collections.sort(lottoNumbers); + Lotto lotto = new Lotto(lottoNumbers); + + return lotto; + } + + public List getLottos() { + return myLottos; + } + public int getLottoCount() { + return count; + } +} diff --git a/src/main/java/lotto/model/Result.java b/src/main/java/lotto/model/Result.java new file mode 100644 index 00000000000..288347957f5 --- /dev/null +++ b/src/main/java/lotto/model/Result.java @@ -0,0 +1,84 @@ +package lotto.model; + +import java.util.Set; +import java.util.List; + +import lotto.Lotto; + +import java.util.HashSet; + +public class Result { + int[] rank; + long totalWinningPrize; + double rateOfReturn; + + public Result(WinningNumbers winningNumbers, MyLottos myLottos, Amount amount) { + rank = new int[5 + 1]; + totalWinningPrize = 0; + for (Lotto myLotto : myLottos.getLottos()) { + int matchCount = getMatchCount(winningNumbers.getWinningNumbers(), myLotto.getNumbers()); + boolean doesLottoContainBonusNumber = checkIfLottoContainsBonusNumber(winningNumbers.getBonusNumber(), myLotto.getNumbers()); + computeRankAndWinningPrize(matchCount, doesLottoContainBonusNumber); + computeRateOfReturn(amount.getValue()); + } + } + private int getMatchCount(List winningNumbers, List lottoNumbers) { + int count = 0; + Set winningNumbersSet = new HashSet<>(winningNumbers); + for (int lottoNumber : lottoNumbers) { + if (winningNumbersSet.contains(lottoNumber)) { + count ++; + } + } + + return count; + } + private boolean checkIfLottoContainsBonusNumber(int bonusNumber, List myLottoNumbers) { + boolean isContained = false; + for (int lottoNumber : myLottoNumbers) { + if (bonusNumber == lottoNumber) { + isContained = true; + break; + } + } + + return isContained; + } + private void computeRankAndWinningPrize(int matchCount, boolean doesLottoContainBonusNumber) { + if (matchCount == 6) { + rank[1] ++; + totalWinningPrize += 2_000_000_000L; + return; + } + if (matchCount == 5 && doesLottoContainBonusNumber) { + rank[2] ++; + totalWinningPrize += 30_000_000L; + return; + } + if (matchCount == 5) { + rank[3] ++; + totalWinningPrize += 1_500_000L; + return; + } + if (matchCount == 4) { + rank[4] ++; + totalWinningPrize += 50_000L; + return; + } + if (matchCount == 3) { + rank[5] ++; + totalWinningPrize += 5_000L; + return; + } + } + private void computeRateOfReturn(int value) { + this.rateOfReturn = (this.totalWinningPrize / (double) value) * 100; + } + + public int[] getRank() { + return rank; + } + public double getRateOfReturn() { + return rateOfReturn; + } +} diff --git a/src/main/java/lotto/model/WinningNumbers.java b/src/main/java/lotto/model/WinningNumbers.java new file mode 100644 index 00000000000..251077e8451 --- /dev/null +++ b/src/main/java/lotto/model/WinningNumbers.java @@ -0,0 +1,54 @@ +package lotto.model; + +import java.util.List; + +import lotto.Lotto; + +public class WinningNumbers { + private Lotto winningLotto; + private int bonusNumber; + + public WinningNumbers(List winningNumbers, int bonusNumber) { + + this.winningLotto = new Lotto(winningNumbers); + validate(winningNumbers, bonusNumber); + + this.bonusNumber = bonusNumber; + + } + + + private void validate(List winningNumbers, int bonusNumber) { + validateWinningNumbersOnlyContainNumberBetween1To45(winningNumbers); + validateWinningNumbersNotContainBonusNumber(winningNumbers, bonusNumber); + validateBonusNumberIsBetween1To45(bonusNumber); + + } + private void validateWinningNumbersOnlyContainNumberBetween1To45(List winningNumbers) { + for (int number : winningNumbers) { + if (number < 1 || number > 45) { + throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 합니다."); + } + } + } + private void validateWinningNumbersNotContainBonusNumber(List winningNumbers, int bonusNumber) { + for (int number : winningNumbers) { + if (bonusNumber == number) { + throw new IllegalArgumentException("보너스 번호는 당첨 번호와 달라야 합니다."); + } + } + } + private void validateBonusNumberIsBetween1To45(int bonusNumber) { + if (bonusNumber < 1 || bonusNumber > 45) { + throw new IllegalArgumentException("보너스 번호는 1부터 45 사이의 숫자여야 합니다."); + } + } + + public List getWinningNumbers() { + return winningLotto.getNumbers(); + } + + public int getBonusNumber() { + return bonusNumber; + } +} diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java new file mode 100644 index 00000000000..2426ea463bf --- /dev/null +++ b/src/main/java/lotto/view/InputView.java @@ -0,0 +1,46 @@ +package lotto.view; + +import java.util.List; +import java.util.ArrayList; +import camp.nextstep.edu.missionutils.Console; + + +public class InputView { + public static int getPurchaseAmount() { + System.out.println("구입금액을 입력해 주세요."); + String inputString = Console.readLine(); + int purchaseAmount; + try { + purchaseAmount = Integer.valueOf(inputString); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 합니다."); + } + + return purchaseAmount; + } + public static List getWinningNumbers() { + System.out.println("당첨 번호를 입력해 주세요."); + String inputString = Console.readLine(); + String[] stringElements = inputString.split(","); + + List result = new ArrayList<>(); + for (String stringElement : stringElements) { + try { + result.add(Integer.valueOf(stringElement)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 합니다."); + } + } + + return result; + } + public static int getBonusNumber() { + System.out.println("보너스 번호를 입력해 주세요."); + String inputString = Console.readLine(); + try { + return Integer.valueOf(inputString); + } catch (NumberFormatException e ) { + throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 합니다."); + } + } +} diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java new file mode 100644 index 00000000000..be7edb4ffb9 --- /dev/null +++ b/src/main/java/lotto/view/OutputView.java @@ -0,0 +1,37 @@ +package lotto.view; + +import lotto.Lotto; +import lotto.model.MyLottos; +import lotto.model.Result; + +public class OutputView { + public static void printMyLottosNumbers(MyLottos myLottos) { + System.out.println(myLottos.getLottoCount() + "개를 구매했습니다."); + for (Lotto myLotto : myLottos.getLottos()) { + printMyOneLottoNumbers(myLotto); + } + } + private static void printMyOneLottoNumbers(Lotto myLotto) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int myLottoNumber: myLotto.getNumbers()) { + sb.append(myLottoNumber).append(", "); + } + sb.deleteCharAt(sb.length() - 1); + sb.deleteCharAt(sb.length() - 1); + sb.append("]").append("\n"); + System.out.print(sb); + } + public static void printTotalStatus(Result result) { + int[] rank = result.getRank(); + double rateOfReturn = result.getRateOfReturn(); + System.out.println("당첨 통계"); + System.out.println("---"); + System.out.println("3개 일치 (5,000원) - " + rank[5] + "개"); + System.out.println("4개 일치 (50,000원) - " + rank[4] + "개"); + System.out.println("5개 일치 (1,500,000원) - " + rank[3] + "개"); + System.out.println("5개 일치, 보너스 볼 일치 (30,000,000원) - " + rank[2] + "개"); + System.out.println("6개 일치 (2,000,000,000원) - " + rank[1] + "개"); + System.out.printf("총 수익률은 %.1f%%입니다.", rateOfReturn); + } +} diff --git a/src/test/java/lotto/ApplicationTest.java b/src/test/java/lotto/ApplicationTest.java index a15c7d1f522..aa3d8a35d6f 100644 --- a/src/test/java/lotto/ApplicationTest.java +++ b/src/test/java/lotto/ApplicationTest.java @@ -10,7 +10,7 @@ import static org.assertj.core.api.Assertions.assertThat; class ApplicationTest extends NsTest { - private static final String ERROR_MESSAGE = "[ERROR]"; + private static final String ERROR_MESSAGE = "[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."; @Test void 기능_테스트() {