diff --git a/docs/README.md b/docs/README.md index e69de29bb2..72a7d3c3fd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,40 @@ +## 기능 리스트 + +- [ ] 가격 -> 개수 반환 +- [ ] 로또 발행 +- [ ] 총 일치 수, 금액, 수익률 계산 +- [ ] 정답로또 입력받기 + - validation + - 보너스점수 +- [ ] ㅇ + + +PurchaseService +- (금액) -> 개수 // thr + +Enum Prize +- 출력문구 matches bonus reward + +LottoService + + +LottoBundle +- 로또 수 +- LottoStatus list +- +- Lotto 정답로또 +- 보너스번호 +- + +LottoStatus +- Lotto +- 일치 수 +- 보너스 여부 +- (정답로또, 보너스) -> 일치 수 , 보너스 여부 +- + +Lotto +- 랜덤로또 발행 +- 정답로또 생성 +- + diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922ba4..bcf5546bb0 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,10 @@ package lotto; +import lotto.lotto.LottoService; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + LottoService lottoService = new LottoService(); + lottoService.run(); } } diff --git a/src/main/java/lotto/lotto/Bonus.java b/src/main/java/lotto/lotto/Bonus.java new file mode 100644 index 0000000000..f3cf40dac0 --- /dev/null +++ b/src/main/java/lotto/lotto/Bonus.java @@ -0,0 +1,18 @@ +package lotto.lotto; + +public class Bonus { + private final int bonusNumber; + + private Bonus(int bonusNumber) { + this.bonusNumber = bonusNumber; + } + + public static Bonus from(int num, Lotto lotto) { + lotto.validateBonusNumber(num); + return new Bonus(num); + } + + public int getBonusNumber() { + return bonusNumber; + } +} diff --git a/src/main/java/lotto/lotto/Lotto.java b/src/main/java/lotto/lotto/Lotto.java new file mode 100644 index 0000000000..1ed7ca7c35 --- /dev/null +++ b/src/main/java/lotto/lotto/Lotto.java @@ -0,0 +1,82 @@ +package lotto.lotto; + + +import lotto.lotto.lottoCreator.LottoCreator; +import lotto.lotto.lottoCreator.RandomLottoCreator; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; + +public class Lotto { + private final static int LOTTO_NUM_LENGTH = 6; + private final static int LOTTO_MIN_NUM = 1; + private final static int LOTTO_MAX_NUM = 45; + private final List numbers; + + public Lotto(List numbers) { + this.numbers = numbers; + } + + public static Lotto from(LottoCreator lottoCreator) { + return lottoCreator.getLotto( + LOTTO_MIN_NUM, + LOTTO_MAX_NUM, + LOTTO_NUM_LENGTH + ); + } + + public Prize comparePrize(Lotto winningLotto, int bonus) { + List list = new ArrayList<>(); + list.addAll(numbers); + list.addAll(winningLotto.numbers); + int matches = list.size() - (new HashSet<>(list)).size(); + boolean isBonus = numbers.contains(bonus); + + Prize prize = Prize.NONE; + for (Prize p : Prize.values()) { + if ( + p.isPrized() && + p.getMatchNum() <= matches && + p.getGrade() <= prize.getGrade() + ) { + if (p.isBonus() && !isBonus) // 보너스 true인 경우 isBonus 확인한다. + continue; + prize = p; + } + } + return prize; + } + + public void validateBonusNumber(int bonus) { + validateBonusDuplicateWithLotto(bonus); + validateBonusRange(bonus); + } + + public boolean contains(int num) { + return numbers.contains(num); + } + + @Override + public String toString() { + return numbers.toString(); + } + + public List getNumbers() { + return numbers; + } + + + private void validateBonusDuplicateWithLotto(int bonus) { + if (numbers.contains(bonus)) + throw new IllegalArgumentException("[ERROR] 보너스 번호와 당첨 번호에 중복이 있습니다."); + + } + + private void validateBonusRange(int bonus) { + if (bonus < LOTTO_MIN_NUM || bonus > LOTTO_MAX_NUM) + throw new IllegalArgumentException("[ERROR] 보너스 번호와 당첨 번호에 중복이 있습니다."); + } +} diff --git a/src/main/java/lotto/lotto/LottoBundle.java b/src/main/java/lotto/lotto/LottoBundle.java new file mode 100644 index 0000000000..fb2607e207 --- /dev/null +++ b/src/main/java/lotto/lotto/LottoBundle.java @@ -0,0 +1,125 @@ +package lotto.lotto; + +import lotto.lotto.lottoCreator.LottoCreator; +import lotto.lotto.model.StatusOutputModel; +import lotto.purchase.PurchaseService; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class LottoBundle { + private final List lottos; + private final Lotto winningLotto; + private final Bonus bonus; + + private LottoBundle(Builder builder) { + this.lottos = builder.lottos; + this.winningLotto = builder.winningLotto; + this.bonus = builder.bonus; + } + + public static class Builder { + private List lottos = new ArrayList<>(); + private Lotto winningLotto; + private Integer bonusNumber; + private Bonus bonus; + + public Builder() {} + + /** + * lottoCreators만큼 새로운 로또 생성하기 + */ + public Builder createLottos(List lottoCreators) { + for (LottoCreator creator : lottoCreators) { + Lotto lotto = Lotto.from(creator); + lottos.add(lotto); + } + return this; + } + + /** + * 당첨번호 저장하기 + */ + public Builder winningLotto(LottoCreator creator) { + Lotto lotto = Lotto.from(creator); + this.winningLotto = lotto; + if (bonusNumber != null) + bonus = Bonus.from(bonusNumber, winningLotto); + return this; + } + + /** + * 보너스번호 저장하기 + */ + public Builder bonus(int bonusNumber) { + this.bonusNumber = bonusNumber; + if (winningLotto != null) + this.bonus = Bonus.from(bonusNumber, winningLotto); + return this; + } + + private void validateBonusNumber(List numbers, int num){ + if (numbers.contains(num)) + throw new IllegalArgumentException("[ERROR] 보너스 번호와 당첨 번호에 중복이 있습니다."); + } + + public LottoBundle build() { + return new LottoBundle(this); + } + } + + /** + * 로또 출력하기 + */ + public List> getLottosNumList() { + List> lottoNumbers = new ArrayList<>(); + for (Lotto lotto : lottos) + lottoNumbers.add(lotto.getNumbers()); + return lottoNumbers; + } + + /** + * 당첨 통계 출력하기 + */ + public StatusOutputModel getResultStatus() { + Map prizeMap = getPrizeStat(); + int totalreward = getTotalReward(prizeMap); + double rewardPercent = PurchaseService.getRewardPercent(lottos.size(), totalreward); + + StringBuilder sb = new StringBuilder(); + for (Prize prize : Prize.values()) { + if (!prize.isPrized()) continue; + sb.append(prize.getToString()); + sb.append( + String.format(" (%,d원) - %d개", + prize.getReward(), + prizeMap.get(prize) + )); + sb.append("\n"); + } + sb.append(String.format("총 수익률은 %.1f%%입니다.", rewardPercent)); + + return new StatusOutputModel(sb.toString()); + } + + private Map getPrizeStat() { + Map prizeMap = Prize.getInitializedMap(); + for (Lotto lotto : lottos) { + Prize prize = lotto.comparePrize(winningLotto, bonus.getBonusNumber()); + if (prize.isPrized()) + prizeMap.put(prize, prizeMap.get(prize) + 1); + } + return prizeMap; + } + + private int getTotalReward(Map prizeMap) { + int totalReward = 0; + for (Prize prize : Prize.values()) { + if (prizeMap.get(prize) != 0) + totalReward += prize.getReward() * prizeMap.get(prize); + } + return totalReward; + } + +} diff --git a/src/main/java/lotto/lotto/LottoService.java b/src/main/java/lotto/lotto/LottoService.java new file mode 100644 index 0000000000..e873d80e04 --- /dev/null +++ b/src/main/java/lotto/lotto/LottoService.java @@ -0,0 +1,94 @@ +package lotto.lotto; + +import lotto.lotto.lottoCreator.LottoCreator; +import lotto.lotto.lottoCreator.ManualLottoCreator; +import lotto.lotto.lottoCreator.RandomLottoCreator; +import lotto.lotto.model.LottoOutputModel; +import lotto.purchase.PurchaseService; +import lotto.view.UserInput; +import lotto.view.UserOutput; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class LottoService { + + public void run() { + LottoBundle.Builder lottoBundleBuilder = new LottoBundle.Builder(); + + // 구입금액 입력받기 + // todo : 구입금액 객체 관리 + int lottoNum = PurchaseService.getLottoNum(getNum(UserInput.getCostInput())); + int manualNum = getNum(UserInput.getManualNumInput()); + if (lottoNum < manualNum) + throw new IllegalArgumentException("[ERROR] 구입 가능한 로또 수를 초과하였습니다."); + + // 자동 로또 + List creators = Stream.generate(RandomLottoCreator::from) + .limit(lottoNum - manualNum) + .collect(Collectors.toList()); + // 수동 로또 + if (manualNum != 0) { + List strList = UserInput.getManualLottoInput(manualNum); + for (String input : strList) + creators.add(ManualLottoCreator.from(getNumList(input))); + } + + lottoBundleBuilder.createLottos(creators); + LottoBundle bundle = lottoBundleBuilder.build(); + + // 로또 출력하기 + UserOutput.printLottos( + new LottoOutputModel( + lottoNum - manualNum, manualNum, bundle.getLottosNumList() + )); + + // 당첨번호 입력받기 + lottoBundleBuilder.winningLotto( + ManualLottoCreator.from(getNumList(UserInput.getLottoInput()))); + + // 보너스번호 입력받기 + lottoBundleBuilder.bonus(getNum(UserInput.getBonusInput())); + bundle = lottoBundleBuilder.build(); + + // 당첨 통계 출력하기 + UserOutput.printResult(bundle.getResultStatus()); + } + + + public int getNum(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException ne) { + throw new IllegalArgumentException("[ERROR] 정수를 입력해주세요."); + } + } + + public List getNumList(String input) { + List strList = new ArrayList<>( + Arrays.asList( + input.split(",") + ) + ); + + // "," 개수를 이용해서 배열의 길이가 올바르게 입력되었는지 확인한다. + int commas = input.length() - input.replace(String.valueOf(","), "").length(); + if (commas + 1 != strList.size()) + throw new IllegalArgumentException("[ERROR] 정수와 ,로 이루어진 배열을 입력해주세요."); + + List numList = strList.stream() + .map(s -> { + try { + return Integer.parseInt(s); + } catch (NumberFormatException ne) { + throw new IllegalArgumentException("[ERROR] 정수와 ,로 이루어진 배열을 입력해주세요."); + } + }).collect(Collectors.toList()); + + return numList; + } + +} diff --git a/src/main/java/lotto/lotto/Prize.java b/src/main/java/lotto/lotto/Prize.java new file mode 100644 index 0000000000..3a0366408c --- /dev/null +++ b/src/main/java/lotto/lotto/Prize.java @@ -0,0 +1,62 @@ +package lotto.lotto; + +import java.util.EnumMap; +import java.util.Map; + +public enum Prize { + FIFTH(true, 5, 3, false, 5000, "3개 일치"), + FOURTH(true, 4, 4, false, 50000, "4개 일치"), + THIRD(true, 3, 5, false, 1500000, "5개 일치"), + SECOND(true, 2, 5, true, 30000000, "5개 일치, 보너스 볼 일치"), + FIRST(true, 1, 6, false, 2000000000, "6개 일치"), + NONE(false, 99999, 0, false, 0, ""); + + private final boolean prized; + private final int grade; + private final int matchNum; + private final boolean bonus; + private final int reward; + private final String toString; + + Prize(boolean prized, int grade, int matchNum, boolean bonus, int reward, String toString) { + this.prized = prized; + this.grade = grade; + this.matchNum = matchNum; + this.bonus = bonus; + this.reward = reward; + this.toString = toString; + } + + public static Map getInitializedMap() { + Map prizeMap = new EnumMap(Prize.class); + for( Prize key : Prize.values() ){ + prizeMap.put(key, 0); + } + return prizeMap; + } + + public boolean isPrized() { + return prized; + } + + public int getGrade() { + return grade; + } + + public int getMatchNum() { + return matchNum; + } + + public boolean isBonus() { + return bonus; + } + + public int getReward() { + return reward; + } + + public String getToString() { + return toString; + } + +} diff --git a/src/main/java/lotto/lotto/lottoCreator/LottoCreator.java b/src/main/java/lotto/lotto/lottoCreator/LottoCreator.java new file mode 100644 index 0000000000..7c8b03062b --- /dev/null +++ b/src/main/java/lotto/lotto/lottoCreator/LottoCreator.java @@ -0,0 +1,8 @@ +package lotto.lotto.lottoCreator; + +import camp.nextstep.edu.missionutils.Randoms; +import lotto.lotto.Lotto; + +public interface LottoCreator { + public Lotto getLotto(int min, int max, int num); +} diff --git a/src/main/java/lotto/lotto/lottoCreator/ManualLottoCreator.java b/src/main/java/lotto/lotto/lottoCreator/ManualLottoCreator.java new file mode 100644 index 0000000000..3627cf4127 --- /dev/null +++ b/src/main/java/lotto/lotto/lottoCreator/ManualLottoCreator.java @@ -0,0 +1,43 @@ +package lotto.lotto.lottoCreator; + +import camp.nextstep.edu.missionutils.Randoms; +import lotto.lotto.Lotto; + +import java.util.HashSet; +import java.util.List; + +public class ManualLottoCreator implements LottoCreator { + private List numbers; + + private ManualLottoCreator(List numbers) { + this.numbers = numbers; + } + + public static ManualLottoCreator from(List numbers) { + return new ManualLottoCreator(numbers); + } + + @Override + public Lotto getLotto(int min, int max, int num) { + validateLength(num); + validateDuplication(); + validateNumRange(min,max); + return new Lotto(numbers); + } + + private void validateLength(int length) { + if (numbers.size() != length) + throw new IllegalArgumentException("[ERROR] 숫자의 개수가 올바르지 않습니다."); + } + + private void validateDuplication() { + if (numbers.size() != (new HashSet<>(numbers)).size()) + throw new IllegalArgumentException("[ERROR] 숫자에 중복이 없어야 합니다."); + } + + private void validateNumRange(int min, int max) { + for (int n : numbers) + if (n < min || n > max) + throw new IllegalArgumentException("[ERROR] 올바른 범위 내의 숫자를 입력해주세요."); + } +} diff --git a/src/main/java/lotto/lotto/lottoCreator/RandomLottoCreator.java b/src/main/java/lotto/lotto/lottoCreator/RandomLottoCreator.java new file mode 100644 index 0000000000..10db4ea7ae --- /dev/null +++ b/src/main/java/lotto/lotto/lottoCreator/RandomLottoCreator.java @@ -0,0 +1,25 @@ +package lotto.lotto.lottoCreator; + +import camp.nextstep.edu.missionutils.Randoms; +import lotto.lotto.Lotto; + +import java.util.Comparator; +import java.util.List; + +public class RandomLottoCreator implements LottoCreator{ + private static final LottoCreator instance = new RandomLottoCreator(); + + private RandomLottoCreator() { + } + + public static LottoCreator from() { + return instance; + } + + @Override + public Lotto getLotto(int min, int max, int num){ + List numbers = Randoms.pickUniqueNumbersInRange(min,max,num); + numbers.sort(Comparator.naturalOrder()); + return new Lotto(numbers); + } +} diff --git a/src/main/java/lotto/lotto/model/LottoOutputModel.java b/src/main/java/lotto/lotto/model/LottoOutputModel.java new file mode 100644 index 0000000000..def54bb764 --- /dev/null +++ b/src/main/java/lotto/lotto/model/LottoOutputModel.java @@ -0,0 +1,27 @@ +package lotto.lotto.model; + +import java.util.List; + +public class LottoOutputModel { + private final int autoCount; + private final int manualCount; + private final List> lottoNumbers; + + public LottoOutputModel(int autoCount, int manualCount, List> lottoNumbers) { + this.autoCount = autoCount; + this.manualCount = manualCount; + this.lottoNumbers = lottoNumbers; + } + + public int getAutoCount() { + return autoCount; + } + + public int getManualCount() { + return manualCount; + } + + public List> getLottoNumbers() { + return lottoNumbers; + } +} diff --git a/src/main/java/lotto/lotto/model/StatusOutputModel.java b/src/main/java/lotto/lotto/model/StatusOutputModel.java new file mode 100644 index 0000000000..5a75d0124b --- /dev/null +++ b/src/main/java/lotto/lotto/model/StatusOutputModel.java @@ -0,0 +1,13 @@ +package lotto.lotto.model; + +public class StatusOutputModel { + private String resultStr; + + public StatusOutputModel(String resultStr) { + this.resultStr = resultStr; + } + + public String getResultStr() { + return resultStr; + } +} diff --git a/src/main/java/lotto/purchase/PurchaseService.java b/src/main/java/lotto/purchase/PurchaseService.java new file mode 100644 index 0000000000..3f01ee8d4d --- /dev/null +++ b/src/main/java/lotto/purchase/PurchaseService.java @@ -0,0 +1,35 @@ +package lotto.purchase; + +public class PurchaseService { + + private final static int PRICE_PER_LOTTO = 1000; + + private PurchaseService() { + } + + /** + * 텍스트 입력을 lotto 수로 변환 + */ + public static int getLottoNum(int cost) { + validatePositiveNum(cost); + validateDivisible(cost); + return cost / PRICE_PER_LOTTO; + } + + /** + * 총 로또 수익률을 퍼센트 단위로 반환 + */ + public static double getRewardPercent(int num, int reward){ + return (double) reward / (num * PRICE_PER_LOTTO) * 100; + } + + private static void validatePositiveNum(int num) { + if (num <=0 ) + throw new IllegalArgumentException("[ERROR] 1 이상의 양수를 입력해주세요"); + } + + private static void validateDivisible(int num) { + if (num % PRICE_PER_LOTTO != 0) + throw new IllegalArgumentException("[ERROR] 가격의 배수에 해당하는 가격을 입력해주세요"); + } +} diff --git a/src/main/java/lotto/view/UserInput.java b/src/main/java/lotto/view/UserInput.java new file mode 100644 index 0000000000..2073ca0716 --- /dev/null +++ b/src/main/java/lotto/view/UserInput.java @@ -0,0 +1,42 @@ +package lotto.view; + +import camp.nextstep.edu.missionutils.Console; + +import java.util.ArrayList; +import java.util.List; + +public class UserInput { + + private UserInput() { + } + + public static String getCostInput() { + System.out.println("구입금액을 입력해 주세요."); + return Console.readLine().trim(); + } + + public static String getManualNumInput() { + System.out.println("\n수동으로 구매할 로또 수를 입력해 주세요."); + return Console.readLine().trim(); + } + + public static List getManualLottoInput(int manualNum) { + System.out.println("\n수동으로 구매할 번호를 입력해 주세요."); + List list = new ArrayList<>(); + for (int i = 0; i < manualNum; i++) { + list.add(Console.readLine().trim()); + } + return list; + } + + public static String getLottoInput() { + System.out.println("당첨 번호를 입력해 주세요."); + return Console.readLine().trim(); + } + + public static String getBonusInput() { + System.out.println("보너스 번호를 입력해 주세요."); + return Console.readLine().trim(); + } + +} diff --git a/src/main/java/lotto/view/UserOutput.java b/src/main/java/lotto/view/UserOutput.java new file mode 100644 index 0000000000..90fd3e7bde --- /dev/null +++ b/src/main/java/lotto/view/UserOutput.java @@ -0,0 +1,30 @@ +package lotto.view; + +import lotto.lotto.model.LottoOutputModel; +import lotto.lotto.model.StatusOutputModel; + +import java.util.List; + +public class UserOutput { + + private UserOutput() { + } + + public static void printLottos(LottoOutputModel model) { + StringBuilder sb = new StringBuilder(); + sb.append("\n수동으로 "); + sb.append(model.getManualCount()) + .append("개, 자동으로 "); + sb.append(model.getAutoCount()) + .append("개를 구매했습니다.\n"); + for (List list : model.getLottoNumbers()) + sb.append(list).append("\n"); + System.out.println(sb); + } + + public static void printResult(StatusOutputModel model) { + System.out.println("\n당첨 통계\n" + + "---"); + System.out.println(model.getResultStr()); + } +}