diff --git a/README.md b/README.md index 5fa2560b46..5038abfe8d 100644 --- a/README.md +++ b/README.md @@ -1 +1,40 @@ # 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 등과 같은 명확한 유형을 처리한다. + +## 기능 구현 사항 +- 입력 관련 기능 + - 사용자로부터 로또 구입 금액을 입력받는다. + - 1000원으로 나누어 떨어지지 않는 경우 예외처리 + - 당첨번호 6자리를 ','를 기준으로 구분하여 입력받는다 + - 당첨번호가 6자리 미만이거나 초과할 경우 예외처리 + - 당첨번호가 1 ~ 45를 벗어나는 경우 예외처리 + - 숫자 이외의 문자를 입력받거나, 입력이 없을 시 예외처리 + - 중복되는 숫자를 입력받았을 시 예외처리 + - 보너스 번호를 입력받는다 + - 보너스 번호가 1 ~ 45를 벗어나는 경우 예외 처리 + - 숫자 이외의 문자를 입력받거나, 입력이 없을 시 예외처리 + - 입력받은 당첨 번호와 중복되는 숫자를 입력받았을 시 예외처리 +- 1 ~ 45 사이의 로또 번호를 생성하는 기능 + - 생성 후 오름차순으로 저장 + - 마찬가지로 숫자의 중복이 불가함 +- 각 생성된 로또번호를 통해 당첨 내역을 확인하는 기능 + - 각 당첨 순위는 Enum으로 명세해둠 +- 당첨금을 기준으로 수익률을 계산하는 기능 +- 요구사항에 맞게 결과를 출력하는 기능 \ No newline at end of file diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922ba4..7fc3521c92 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,10 @@ package lotto; +import static lotto.Controller.lottoController.start; + public class Application { public static void main(String[] args) { // TODO: 프로그램 구현 + start(); } } diff --git a/src/main/java/lotto/Controller/lottoController.java b/src/main/java/lotto/Controller/lottoController.java new file mode 100644 index 0000000000..b1a91d507c --- /dev/null +++ b/src/main/java/lotto/Controller/lottoController.java @@ -0,0 +1,33 @@ +package lotto.Controller; + +import lotto.domain.*; +import lotto.service.CheckLotto; +import lotto.view.Input; + +import java.util.List; +import java.util.Map; + +import static lotto.service.CalculateResult.calculateResult; +import static lotto.service.CheckLotto.checkLottoRank; +import static lotto.view.Output.*; + +public class lottoController { + public static void start(){ + try { + int putchaseAmount = Input.inputPurchaseAmount(); + List winningNumber = Input.inputWinnerNumbers(); + Lotto winningLotto = new Lotto(winningNumber); + int bonusNumber = Input.inputBonusNumber(winningNumber); + int numberOfPurchase = Input.calculateLottoAmount(putchaseAmount); + List lottos = LottoNumberGenerator.generateLottoNumbers(numberOfPurchase); + WinningLotto winningLottoWithBonusNumber = new WinningLotto(winningLotto, bonusNumber); + List resultOfLottos = checkLottoRank(winningLottoWithBonusNumber, lottos, bonusNumber); + List resultList = calculateResult(resultOfLottos); + Map resultOfLottoAsEnum = collectResult(resultList); + printPurchasedLottoNumbers(lottos); + printResult(resultOfLottoAsEnum, putchaseAmount); + }catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java deleted file mode 100644 index 88fc5cf12b..0000000000 --- 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/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java new file mode 100644 index 0000000000..d15156074d --- /dev/null +++ b/src/main/java/lotto/domain/Lotto.java @@ -0,0 +1,60 @@ +package lotto.domain; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Lotto { + private final List numbers; + + public Lotto(List numbers) { + validateLottoNumber(numbers); + this.numbers = numbers; + } + + private void validateLottoSize(List numbers) { + if (numbers.size() != 6) { + throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다."); + } + } + + // TODO: 추가 기능 구현 + private void validateLottoNumberRange(List numbers){ + numbers.stream().forEach(element->{ + if(element < 1 || element > 45){ + throw new IllegalArgumentException("[ERROR] 로또 번호는 1 ~ 45 내의 숫자여야 합니다."); + } + }); + } + + private void validateLottoNumberIsInteger(List numbers){ + numbers.stream().forEach(element->{ + if(!(element instanceof Integer)){ + throw new IllegalArgumentException("[ERROR] 로또 번호는 정수만 입력 가능합니다."); + } + }); + } + + private void validateLottoNumberIsDuplicated(List numbers){ + Set numbersSet = new HashSet<>(numbers); + if(numbersSet.size() < numbers.size()){ + throw new IllegalArgumentException("[ERROR] 중복된 로또 번호는 입력이 불가합니다."); + } + } + + private void validateLottoNumber(List numbers){ + try{ + validateLottoSize(numbers); + validateLottoNumberRange(numbers); + validateLottoNumberIsInteger(numbers); + validateLottoNumberIsDuplicated(numbers); + } + catch(IllegalArgumentException e){ + System.out.println(e.getMessage()); + } + } + + public List getLottoNumbers(){ + return numbers; + } +} \ No newline at end of file diff --git a/src/main/java/lotto/domain/LottoNumberGenerator.java b/src/main/java/lotto/domain/LottoNumberGenerator.java new file mode 100644 index 0000000000..40e0afedf8 --- /dev/null +++ b/src/main/java/lotto/domain/LottoNumberGenerator.java @@ -0,0 +1,26 @@ +package lotto.domain; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class LottoNumberGenerator { + public static Lotto generateLotto(){ + List lottoNumbers = Randoms.pickUniqueNumbersInRange(1,45,6); + Collections.sort(lottoNumbers); + + Lotto lotto = new Lotto(lottoNumbers); + return lotto; + } + + public static List generateLottoNumbers(int amountOfLotto){ + List lottos = new ArrayList<>(); + for(int i = 0 ; i < amountOfLotto ; i++){ + lottos.add(generateLotto()); + } + return lottos; + } +} diff --git a/src/main/java/lotto/domain/ResultOfLotto.java b/src/main/java/lotto/domain/ResultOfLotto.java new file mode 100644 index 0000000000..7902a6b252 --- /dev/null +++ b/src/main/java/lotto/domain/ResultOfLotto.java @@ -0,0 +1,27 @@ +package lotto.domain; + +public class ResultOfLotto { + private int numberOfMatching; + private boolean isBonusNumberCorrect; + + public ResultOfLotto(int numberOfMatching, boolean isBonusNumberCorrect) { + this.numberOfMatching = numberOfMatching; + this.isBonusNumberCorrect = isBonusNumberCorrect; + } + + public int getNumberOfMatching() { + return numberOfMatching; + } + + public void setNumberOfMatching(int numberOfMatching) { + this.numberOfMatching = numberOfMatching; + } + + public boolean isBonusNumberCorrect() { + return isBonusNumberCorrect; + } + + public void setBonusNumberCorrect(boolean bonusNumberCorrect) { + isBonusNumberCorrect = bonusNumberCorrect; + } +} diff --git a/src/main/java/lotto/domain/WinningLotto.java b/src/main/java/lotto/domain/WinningLotto.java new file mode 100644 index 0000000000..b4fe988516 --- /dev/null +++ b/src/main/java/lotto/domain/WinningLotto.java @@ -0,0 +1,20 @@ +package lotto.domain; + +import java.util.HashMap; +import java.util.Map; + +public class WinningLotto { + Map winningLottoWithBonusNumber = new HashMap<>(); + + public WinningLotto(Lotto lotto, int bonusNumber) { + winningLottoWithBonusNumber.put(lotto,bonusNumber); + } + + public Map getWinningLotto() { + return winningLottoWithBonusNumber; + } + + public void setWinningLotto(Map winningLotto) { + this.winningLottoWithBonusNumber = winningLotto; + } +} diff --git a/src/main/java/lotto/domain/WinningRank.java b/src/main/java/lotto/domain/WinningRank.java new file mode 100644 index 0000000000..afd5be7d23 --- /dev/null +++ b/src/main/java/lotto/domain/WinningRank.java @@ -0,0 +1,47 @@ +package lotto.domain; + +public enum WinningRank { + LOSE(0,0,false), + FIFTHPLACE(3,5000,false), + FOURTHPLACE(4,50000,false), + THIRDPLACE(5,1500000,false), + SECONDPLACE(5,30000000,true), + FIRSTPLACE(6,2000000000,false); + + private final int numberOfCorrectNumber; + private final int prizeMoneyAmount; + + public boolean isBonusNumberCorrect() { + return isBonusNumberCorrect; + } + + private final boolean isBonusNumberCorrect; + + WinningRank(int numberOfCorrectNumber, int prizeMoneyAmount, boolean isBonusNumberCorrect){ + this.numberOfCorrectNumber = numberOfCorrectNumber; + this.prizeMoneyAmount = prizeMoneyAmount; + this.isBonusNumberCorrect = isBonusNumberCorrect; + } + + public static WinningRank calculateLottoRank(int numberOfCorrectNumber, boolean isBonusNumberCorrect){ + WinningRank lottoWinningRank = LOSE; + for(WinningRank winningRank: WinningRank.values()){ + if(winningRank.getnumberOfCorrectNumber() == numberOfCorrectNumber){ + if(winningRank.getnumberOfCorrectNumber() == 5 && winningRank.isBonusNumberCorrect() == isBonusNumberCorrect) + lottoWinningRank = winningRank; + else + lottoWinningRank = winningRank; + } + } + return lottoWinningRank; + } + + + public int getnumberOfCorrectNumber() { + return numberOfCorrectNumber; + } + + public int getPrizeMoneyAmount(){ + return prizeMoneyAmount; + } +} diff --git a/src/main/java/lotto/service/CalculateResult.java b/src/main/java/lotto/service/CalculateResult.java new file mode 100644 index 0000000000..806cd1a7bb --- /dev/null +++ b/src/main/java/lotto/service/CalculateResult.java @@ -0,0 +1,25 @@ +package lotto.service; + +import lotto.domain.Lotto; +import lotto.domain.ResultOfLotto; +import lotto.domain.WinningRank; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static lotto.domain.WinningRank.calculateLottoRank; + +public class CalculateResult { + public static List calculateResult(List resultOfLottos){ + List resultList = new ArrayList<>(); + for (ResultOfLotto resultOfLotto: resultOfLottos) { + int matchingNumber = resultOfLotto.getNumberOfMatching(); + boolean isBonusNumberCorrect = resultOfLotto.isBonusNumberCorrect(); + WinningRank winningRank = calculateLottoRank(matchingNumber, isBonusNumberCorrect); + System.out.println(winningRank); + resultList.add(winningRank); + } + return resultList; + } +} diff --git a/src/main/java/lotto/service/CheckLotto.java b/src/main/java/lotto/service/CheckLotto.java new file mode 100644 index 0000000000..1a0e970e9b --- /dev/null +++ b/src/main/java/lotto/service/CheckLotto.java @@ -0,0 +1,46 @@ +package lotto.service; + +import lotto.domain.Lotto; +import lotto.domain.ResultOfLotto; +import lotto.domain.WinningLotto; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CheckLotto { + public static List checkLottoRank(WinningLotto winningLotto, List purchasedLottoNumbers, int bonusNumber){ + List resultOfLottos = new ArrayList<>(); + + List winningLottoNumber = new ArrayList<>(); + int winningBonusNumber = 0; + int numberOfMatching = 0; + boolean isBonusNumberCorrect = false; + for (Map.Entry entry : winningLotto.getWinningLotto().entrySet()) { + winningLottoNumber = entry.getKey().getLottoNumbers(); + winningBonusNumber = entry.getValue(); + } + for(Lotto purchasedLotto : purchasedLottoNumbers){ + List purchasedLottoNumber = purchasedLotto.getLottoNumbers(); + numberOfMatching = checkLottoNumbers(winningLottoNumber, purchasedLottoNumber); + isBonusNumberCorrect = checkBonusNumber(winningBonusNumber, bonusNumber); + ResultOfLotto resultOfLotto = new ResultOfLotto(numberOfMatching, isBonusNumberCorrect); + resultOfLottos.add(resultOfLotto); + } + + return resultOfLottos; + } + + public static boolean checkBonusNumber(int winningBonusNumber, int bonusNumber){ + return winningBonusNumber == bonusNumber; + } + + public static int checkLottoNumbers(List winningLottoNumber, List purchasedLottoNumber){ + long numberOfMatchNumber = purchasedLottoNumber.stream() + .filter(winningLottoNumber::contains) + .count(); + + return (int)numberOfMatchNumber; + } +} diff --git a/src/main/java/lotto/view/Input.java b/src/main/java/lotto/view/Input.java new file mode 100644 index 0000000000..f5da7d5a39 --- /dev/null +++ b/src/main/java/lotto/view/Input.java @@ -0,0 +1,63 @@ +package lotto.view; + +import camp.nextstep.edu.missionutils.Console; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class Input { + public static int inputPurchaseAmount() throws IllegalArgumentException { + int purchaseAmount; + System.out.println("구입금액을 입력해 주세요."); + try { + purchaseAmount = Integer.parseInt(Console.readLine()); + }catch (IllegalArgumentException e){ + throw new IllegalArgumentException("[ERROR] 구입 금액은 정수로 입력해야 합니다."); + } + validatePurchaseAmount(purchaseAmount); + return purchaseAmount; + } + + public static void validatePurchaseAmount(int purchaseAmount){ + if((purchaseAmount % 1000) != 0 && (purchaseAmount / 1000) < 1){ + throw new IllegalArgumentException("[ERROR] 구입 금액은 1000 이상의, 1000단위 정수로 입력해야 합니다"); + } + } + + public static int calculateLottoAmount(int purchaseAmount){ + return purchaseAmount/1000; + } + + public static List inputWinnerNumbers(){ + System.out.println("당첨 번호를 입력해 주세요."); + try { + String winningNumbersInString = Console.readLine(); + return Arrays.stream(winningNumbersInString.split(",")).map(element -> Integer.parseInt(element)).collect(Collectors.toList()); + }catch (IllegalArgumentException e){ + throw new IllegalArgumentException("[ERROR] 숫자를 입력해야 합니다."); + } + } + + public static int inputBonusNumber(List winningNumbers) throws IllegalArgumentException{ + System.out.println("보너스 번호를 입력해 주세요."); + int bonusNumber; + try{ + bonusNumber = Integer.parseInt(Console.readLine()); + } + catch (IllegalArgumentException e){ + throw new IllegalArgumentException("[ERROR] 보너스 번호는 정수로 입력해야 합니다."); + } + validateBonusNumber(bonusNumber, winningNumbers); + return bonusNumber; + } + + public static void validateBonusNumber(int bonusNumber, List winningNumbers) throws IllegalArgumentException{ + if(bonusNumber < 1 && bonusNumber > 45){ + throw new IllegalArgumentException("[ERROR] 보너스 번호는 1 ~ 45 내에 있는 정수입니다."); + } + if(winningNumbers.contains(bonusNumber)){ + throw new IllegalArgumentException("[ERROR] 보너스 번호는 기존 당첨번호와 중복될 수 없습니다."); + } + } +} \ No newline at end of file diff --git a/src/main/java/lotto/view/Output.java b/src/main/java/lotto/view/Output.java new file mode 100644 index 0000000000..ef56e16d23 --- /dev/null +++ b/src/main/java/lotto/view/Output.java @@ -0,0 +1,59 @@ +package lotto.view; + +import lotto.domain.Lotto; +import lotto.domain.WinningLotto; +import lotto.domain.WinningRank; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Output { + public static void printPurchasedLottoNumbers(List lottos){ + System.out.println(lottos.size() + "개를 구매했습니다."); + for(Lotto lotto: lottos){ + List lottoNumbers = lotto.getLottoNumbers(); + System.out.println(lottoNumbers); + } + } + + public static Map initializeEnumList(){ + Map resultOfLottoAsEnum = new HashMap<>(); + for(WinningRank result: WinningRank.values()){ + resultOfLottoAsEnum.put(result, 0); + } + return resultOfLottoAsEnum; + } + + public static Map collectResult(List resultList){ + Map resultOfLottoAsEnum = initializeEnumList(); + + for(WinningRank result: resultList){ + resultOfLottoAsEnum.put(result, resultOfLottoAsEnum.get(result) + 1); + } + return resultOfLottoAsEnum; + } + + public static int calculateEarningRate(Map result, int purchaseAmount){ + int totalPrizeMoney = 0; + for (Map.Entry entry : result.entrySet()) { + totalPrizeMoney += entry.getValue() * entry.getKey().getPrizeMoneyAmount(); + } + return totalPrizeMoney; + } + + public static void printResult(Map result, int purchaseAmount){ + int totalPrizeMoney = calculateEarningRate(result, purchaseAmount); + + double earningRate = totalPrizeMoney/purchaseAmount; + + System.out.println("당첨 통계"); + System.out.println("---"); + System.out.println(WinningRank.FIFTHPLACE.getnumberOfCorrectNumber() + "개 일치 (" + WinningRank.FIFTHPLACE.getPrizeMoneyAmount() +") - "+result.get(WinningRank.FIFTHPLACE)+"개 일치"); + System.out.println(WinningRank.FOURTHPLACE.getnumberOfCorrectNumber() + "개 일치 (" + WinningRank.FOURTHPLACE.getPrizeMoneyAmount() +") - "+result.get(WinningRank.FOURTHPLACE)+"개 일치"); + System.out.println(WinningRank.THIRDPLACE.getnumberOfCorrectNumber() + "개 일치 (" + WinningRank.THIRDPLACE.getPrizeMoneyAmount() +") - "+result.get(WinningRank.THIRDPLACE)+"개 일치"); + System.out.println(WinningRank.SECONDPLACE.getnumberOfCorrectNumber() + "개 일치 (" + WinningRank.SECONDPLACE.getPrizeMoneyAmount() +") - "+result.get(WinningRank.SECONDPLACE)+"개 일치"); + System.out.println(WinningRank.FIRSTPLACE.getnumberOfCorrectNumber() + "개 일치 (" + WinningRank.FIRSTPLACE.getPrizeMoneyAmount() +") - "+result.get(WinningRank.FIRSTPLACE)+"개 일치"); + System.out.println("총 수익률은" + earningRate + "% 입니다."); + } +} diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 309f4e50ae..c74fb4b676 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;