diff --git a/README.md b/README.md index 5fa2560b46..39e2df4fad 100644 --- a/README.md +++ b/README.md @@ -1 +1,50 @@ # java-lotto-precourse +## 간단한 로또 발매기를 구현한다. + +### 학습 목표🤔 + +- 관련 함수를 묶어 클래스를 만들고, 객체들이 협력하여 하나의 큰 기능을 수행하도록 한다. +- 클래스와 함수에 대한 단위 테스트를 통해 의도한 대로 정확하게 작동하는 영역을 확보한다. +- 2주 차 공통 피드백을 최대한 반영한다. + + +### 기능 목록🎰 + +- [ ] 로또 구입 금액을 입력받는다. +- [ ] 로또를 발행한다. + - 구입 금액에 해당하는 만큼 발행한다. + - 로또 1장의 가격은 1,000원이다. + - 1에서 45 사이의 중복되지 않은 정수 6개를 반환한다. +- [ ] 당첨 번호를 입력받는다. + - 로또 번호의 숫자 범위는 1~45까지이다. + - 중복되지 않는 숫자 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원 +- [ ] 수익률을 구한다. + - 사용자가 구매한 로또 번호와 당첨 번호를 비교해야 한다. +- [ ] 수익률을 출력한다. +- [ ] 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. + - `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다. + +### 예외 처리🔎 + +#### 구입금액 + +- [ ] 구입 금액이 1,000원으로 나누어 떨어지지 않는 경우 + +#### 로또 번호 + +- [ ] 로또 번호의 숫자 범위를 벗어난 경우 +- [ ] ,로 구분하지 않았을 경우 + +#### 당첨번호 +- [ ] 당첨 번호에 있는 보너스 번호를 입력한 경우 diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922ba4..194005187f 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,22 @@ package lotto; +import lotto.controller.LottoController; +import lotto.domain.GenerateLottos; +import lotto.service.LottoService; +import lotto.utils.Parsing; +import lotto.utils.Validator; +import lotto.view.InputView; +import lotto.view.OutputView; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + InputView inputView = new InputView(); + OutputView outputView = new OutputView(); + GenerateLottos generateLottos = new GenerateLottos(); + + LottoService lottoService = new LottoService(new Validator(),new Parsing(), generateLottos); + LottoController lottoController = new LottoController(inputView, outputView, lottoService); + + lottoController.run(); } } diff --git a/src/main/java/lotto/controller/LottoController.java b/src/main/java/lotto/controller/LottoController.java new file mode 100644 index 0000000000..49971d57a9 --- /dev/null +++ b/src/main/java/lotto/controller/LottoController.java @@ -0,0 +1,41 @@ +package lotto.controller; + +import lotto.domain.Lotto; + +import lotto.domain.Winning; +import lotto.service.LottoService; +import lotto.utils.Parsing; +import lotto.view.InputView; +import lotto.view.OutputView; + +import java.util.List; + +import static java.lang.System.in; + +public class LottoController { + InputView inputView; + OutputView outputView; + LottoService service; + + public LottoController(InputView inputView, OutputView outputView, LottoService service){ + this.inputView = inputView; + this.outputView = outputView; + this.service = service; + } + public void run(){ + outputView.purchasePrint(); + String purchaseInput = inputView.purchaseInput(); + int lottoQuantity = service.lottoQuantity(purchaseInput); + outputView.quantityPrint(lottoQuantity); + List lottos = service.issueLottos(lottoQuantity); + outputView.lottosPrint(lottos); + outputView.winningNumberPrint(); + String winningNumberInput = inputView.winningNumberInput(); + outputView.bonusNumberPrint(); + String bonusNumberInput = inputView.bonusNumberInput(); + List results = Winning.FIRST.findWinningDetail(lottos, winningNumberInput, bonusNumberInput); + outputView.resultPrint(results); + double profit = Winning.FIRST.profitRate(purchaseInput); + outputView.profitPrint(profit); + } +} diff --git a/src/main/java/lotto/domain/GenerateLottos.java b/src/main/java/lotto/domain/GenerateLottos.java new file mode 100644 index 0000000000..478b300e31 --- /dev/null +++ b/src/main/java/lotto/domain/GenerateLottos.java @@ -0,0 +1,24 @@ +package lotto.domain; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.List; + +public class GenerateLottos{ + List lottos = new ArrayList<>(); + + public Lotto generateLotto() { + List sixRandomNumbers = Randoms.pickUniqueNumbersInRange(1, 45, 6); + return new Lotto(sixRandomNumbers); + } + + public List addLotto(Lotto lotto){ + lottos.add(lotto); + return lottos; + } + + public List getLottos(){ + return lottos; + } +} diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/domain/Lotto.java similarity index 53% rename from src/main/java/lotto/Lotto.java rename to src/main/java/lotto/domain/Lotto.java index 88fc5cf12b..8ded4ba669 100644 --- a/src/main/java/lotto/Lotto.java +++ b/src/main/java/lotto/domain/Lotto.java @@ -1,6 +1,8 @@ -package lotto; +package lotto.domain; +import java.util.HashSet; import java.util.List; +import camp.nextstep.edu.missionutils.Randoms; public class Lotto { private final List numbers; @@ -14,7 +16,12 @@ private void validate(List numbers) { if (numbers.size() != 6) { throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다."); } + if (new HashSet<>(numbers).size() != numbers.size()) { + throw new IllegalArgumentException("[ERROR] 로또 번호에 중복된 숫자가 있습니다."); + } } - // TODO: 추가 기능 구현 + public List getLotto(){ + return numbers; + } } diff --git a/src/main/java/lotto/domain/Winning.java b/src/main/java/lotto/domain/Winning.java new file mode 100644 index 0000000000..20c5ff6c15 --- /dev/null +++ b/src/main/java/lotto/domain/Winning.java @@ -0,0 +1,107 @@ +package lotto.domain; + +import lotto.utils.Parsing; + +import java.util.ArrayList; +import java.util.List; + +public enum Winning { + FIFTH(3,"5,000",0), + FOURTH(4,"50,000",0), + THIRD(5,"1,500,000", 0), + SECOND(5,"30,000,000", 0), + FIRST(6,"2,000,000,000", 0); + + private int numberOfMatches; + private String prizeMoney; + private int numberOfLottos; + + Winning(int numberOfMatches, String prizeMoney, int numberOfLottos){ + this.numberOfMatches = numberOfMatches; + this.prizeMoney = prizeMoney; + this.numberOfLottos = numberOfLottos; + } + + public int getNumberOfMatches() { + return numberOfMatches; + } + + public String getPrizeMoney() { + return prizeMoney; + } + + public int getNumberOfLottos() { + return numberOfLottos; + } + + public List findWinningDetail(List lottos, String winningNumberInput, String bonusNumberInput) { + Parsing parsing = new Parsing(); + int getvalidNumber = 0; + int getvalidBonus = 0; + List getWinningNumber = parsing.stringToIntegerArray(winningNumberInput); + for(Lotto lotto : lottos){ + getvalidNumber = compareLottoAndWinning(lotto.getLotto(),getWinningNumber); + getvalidBonus = compareLottoAndBonus(lotto.getLotto(),parsing.stringToInteger(bonusNumberInput)); + if (3<= getvalidNumber){ + increaseNumberOfLottos(getvalidNumber,getvalidBonus); + } + } + + List winningResults = new ArrayList<>(); + for (Winning winning : Winning.values()) { + winningResults.add(winning); + } + return winningResults; + } + + public int compareLottoAndWinning(List lotto, List getWinningNumber){ + int matchingNumber = 0; + for(int i=0; i<6; i++){ + boolean hasWinningNumber = lotto.contains(getWinningNumber.get(i)); + if(hasWinningNumber){ + matchingNumber += 1; + } + } + return matchingNumber; + } + + public int compareLottoAndBonus(List lotto, int bonusNumberInput){ + int matchingNumber = 0; + for(int i=0; i<6; i++){ + boolean hasWinningNumber = lotto.contains(bonusNumberInput); + if(hasWinningNumber){ + matchingNumber += 1; + } + } + return matchingNumber; + } + + public void increaseNumberOfLottos(int getvalidNumber, int getvalidBonus){ + for (Winning winning : Winning.values()) { + if (winning.numberOfMatches == 5 && getvalidNumber == 5 && getvalidBonus==1) { + winning.numberOfLottos += 1; + break; + } + + if (winning.numberOfMatches == getvalidNumber) { + winning.numberOfLottos += 1; + break; + } + } + + } + + public double profitRate(String purchaseInput) { + int totalPrize = 0; + + for (Winning result : Winning.values()) { + int prizeAmount = Integer.parseInt(result.getPrizeMoney().replace(",", "")); + totalPrize += prizeAmount * result.getNumberOfLottos(); + } + Parsing parsing = new Parsing(); + int purchase = parsing.stringToInteger(purchaseInput); + double profitRate = (double) totalPrize / purchase * 100; + return Math.round(profitRate * 100) / 100.0; + } + +} diff --git a/src/main/java/lotto/service/LottoService.java b/src/main/java/lotto/service/LottoService.java new file mode 100644 index 0000000000..3a5dd61e80 --- /dev/null +++ b/src/main/java/lotto/service/LottoService.java @@ -0,0 +1,43 @@ +package lotto.service; + +import lotto.domain.Lotto; +import lotto.domain.GenerateLottos; +import lotto.utils.Parsing; +import lotto.utils.Validator; + +import java.util.List; + +public class LottoService { + Validator validator; + Parsing parsing; + GenerateLottos generateLottos; + + public LottoService(Validator validator, Parsing parsing, GenerateLottos generateLottos){ + this.validator = validator; + this.parsing = parsing; + this.generateLottos = generateLottos; + } + + + //로또 발행 수량 + public int lottoQuantity(String purchaseInput){ + if(!validator.isPositiveInteger(purchaseInput)){ + throw new IllegalArgumentException("[ERROR] 양의 정수를 입력해주세요."); + } + + int purchase = parsing.stringToInteger(purchaseInput); + + if(!validator.isDivisibleBy1000(purchase)){ + throw new IllegalArgumentException("[ERROR] 1,000원 단위로 입력해주세요."); + } + return purchase/1000; + } + + //수량만큼 로또 발행 + public List issueLottos(int lottoQuantity){ + for(int i=0; i stringToIntegerArray(String winningNumberInput){ + String[] separated = winningNumberInput.split(","); + List getIntegerArray = new ArrayList<>(); + for(String str:separated){ + getIntegerArray.add(Integer.parseInt(str)); + } + return getIntegerArray; + } +} diff --git a/src/main/java/lotto/utils/Validator.java b/src/main/java/lotto/utils/Validator.java new file mode 100644 index 0000000000..029e758097 --- /dev/null +++ b/src/main/java/lotto/utils/Validator.java @@ -0,0 +1,15 @@ +package lotto.utils; + +public class Validator { + + public boolean isPositiveInteger(String purchaseInput){ + return purchaseInput.matches("^[1-9]\\d*$"); + } + + public boolean isDivisibleBy1000(int purchase){ + if(purchase%100 == 0){ + return true; + } + return false; + } +} diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java new file mode 100644 index 0000000000..24b386e8cf --- /dev/null +++ b/src/main/java/lotto/view/InputView.java @@ -0,0 +1,15 @@ +package lotto.view; +import camp.nextstep.edu.missionutils.Console; +public class InputView { + + public String purchaseInput(){ + return Console.readLine(); + } + + public String winningNumberInput(){ + return Console.readLine(); + } + public String bonusNumberInput(){ + return Console.readLine(); + } +} diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java new file mode 100644 index 0000000000..8e69782311 --- /dev/null +++ b/src/main/java/lotto/view/OutputView.java @@ -0,0 +1,54 @@ +package lotto.view; +import lotto.domain.Lotto; +import lotto.domain.Winning; + +import java.util.Collections; +import java.util.List; + +public class OutputView { + private final String PURCHASE_OUTPUT = "구입금액을 입력해 주세요."; + private final String QUANTITY_OUTPUT="개를 구매했습니다."; + private final String WINNINGNUMBER_OUTPUT="당첨 번호를 입력해 주세요."; + private final String BONUSNUMBER_OUTPUT="보너스 번호를 입력해 주세요."; + + public void purchasePrint(){ + System.out.println(PURCHASE_OUTPUT); + } + + public void quantityPrint(int lottoQuantity){ + System.out.println("\n"+lottoQuantity+QUANTITY_OUTPUT); + } + + public void lottosPrint(List lottos){ + for(Lotto lotto : lottos){ + Collections.sort(lotto.getLotto()); + System.out.println(lotto.getLotto()); + } + } + + public void winningNumberPrint(){ + System.out.println("\n"+WINNINGNUMBER_OUTPUT); + } + + public void bonusNumberPrint(){ + System.out.println("\n"+BONUSNUMBER_OUTPUT); + } + + public void resultPrint(List results) { + System.out.println("\n"+"당첨통계"+"\n"+"---"); + for (Winning result : Winning.values()) { + if (result == Winning.SECOND) { + System.out.printf("%d개 일치, 보너스 볼 일치 (%s원) - %d개\n", result.getNumberOfMatches(), result.getPrizeMoney(), result.getNumberOfLottos()); + continue; + } + + System.out.printf("%d개 일치 (%s원) - %d개\n", + result.getNumberOfMatches(), result.getPrizeMoney(), result.getNumberOfLottos()); + } + } + + public void profitPrint(double profit){ + System.out.printf("총 수익률은 %.1f%%입니다.", profit); + } + +} diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 309f4e50ae..502627de98 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; @@ -21,5 +22,4 @@ class LottoTest { .isInstanceOf(IllegalArgumentException.class); } - // TODO: 추가 기능 구현에 따른 테스트 코드 작성 }