diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 00000000..b8642540 --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,8 @@ +import controller.LottoController; +public class Main { + public static void main(String[] args) { + LottoController lottoController = new LottoController(); + lottoController.lottoStart(); + + } +} diff --git a/src/main/java/controller/LottoController.java b/src/main/java/controller/LottoController.java new file mode 100644 index 00000000..331a22db --- /dev/null +++ b/src/main/java/controller/LottoController.java @@ -0,0 +1,30 @@ +package controller; + +import model.*; +import view.InputView; +import view.ResultView; + +import java.util.List; + +public class LottoController { + + public void lottoStart() { + + int price = InputView.getPrice(); + int manualCount = InputView.getManualCount(); + List manualLottos = InputView.getManualLottos(manualCount); + BuyLotto buyLotto = new BuyLotto(price, manualCount, manualLottos); + LottoGenerator lottoGenerator = new LottoGenerator(); + List lottos = lottoGenerator.generateLottos(buyLotto.calculateAutoLottoCount(), manualLottos); + + ResultView.printLottos(manualCount, lottos); + + WinLotto winLotto = new WinLotto(InputView.getWinLotto(), lottos, InputView.getBonusBall()); + winLotto.updateWinningStates(lottos); + ResultView.printStatics(winLotto.getWinningStates()); + + ProfitCalculator profitCalculator = new ProfitCalculator(); + ResultView.printProfit(profitCalculator.calculateProfit(winLotto.getWinPrize(), price)); + + } +} diff --git a/src/main/java/model/BuyLotto.java b/src/main/java/model/BuyLotto.java new file mode 100644 index 00000000..abdc14e6 --- /dev/null +++ b/src/main/java/model/BuyLotto.java @@ -0,0 +1,23 @@ +package model; + +import java.util.List; + +public class BuyLotto { + private LottoMoney totalPrice; + private int manualLottoCount; + private List manualLottoList; + + public BuyLotto(int totalPrice, int manualLottoCount, List manualLottoList) { + this.totalPrice = new LottoMoney(String.valueOf(totalPrice)); + this.manualLottoCount = manualLottoCount; + this.manualLottoList = manualLottoList; + } + + public int calculateAutoLottoCount() { + return totalPrice.getAmount() / Constant.LOTTO_PRICE - manualLottoCount; + } + + public List getManualLottoList() { + return manualLottoList; + } +} diff --git a/src/main/java/model/Constant.java b/src/main/java/model/Constant.java new file mode 100644 index 00000000..a0cac96a --- /dev/null +++ b/src/main/java/model/Constant.java @@ -0,0 +1,59 @@ +package model; + +public class Constant { + + public static final int LOTTO_PRICE = 1000; + public static final int LOTTO_SIZE = 6; + public static final int MAX_LOTTO_NUMBER = 45; + public static final int MIN_LOTTO_NUMBER = 1; + public static final int THREE_COUNT = 3; + public static final int FOUR_COUNT = 4; + public static final int FIVE_COUNT = 5; + public static final int SIX_COUNT = 6; + public static final int ZERO_COUNT = 0; + + public static final int FIFTH_PRICE = 5000; + public static final int FOURTH_PRICE = 50000; + public static final int THIRD_PRICE = 1500000; + public static final int SECOND_PRICE = 30000000; + public static final int FIRST_PRICE = 2000000000; + + public static final String SEPARATOR = ","; + + // InputView + + public static final String PRICE_QUERY = "구입금액을 입력해 주세요."; + public static final String WIN_LOTTO_QUERY = "지난 주 당첨 번호를 입력해 주세요."; + public static final String BONUS_QUERY = "보너스 볼을 입력해 주세요"; + public static final String MANUAL_COUNT_QUERY = "수동으로 구매할 로또 수를 입력해 주세요."; + public static final String MANUAL_LOTTO_QUERY = "수동으로 구매할 번호를 입력해 주세요."; + + + // ResultView + public static final String WIN_RESULT = "당첨 통계"; + public static final String LINES = "---------"; + public static final String LOTTO_BUY_RESULT_1 = "수동으로 "; + public static final String LOTTO_BUY_RESULT_2 = "장 자동으로"; + public static final String LOTTO_BUY_RESULT_3 = "개를 구매했습니다."; + public static final String PROFIT_RESULT_1 = "총 수익률은 "; + public static final String PROFIT_RESULT_2 = "입니다."; + public static final String STATICS_RESULT_1 = "개 일치"; + public static final String STATICS_RESULT_2 = " 보너스볼 일치"; + public static final String STATICS_RESULT_3 = "원"; + public static final String STATICS_RESULT_4 = "개"; + public static final String OPEN_PARENTHESES = "("; + public static final String CLOSE_PARENTHESES = ")"; + public static final String DASH = "-"; + public static final String BLANK = " "; + + //exceptions + public static final String RANGE_EXCEPTION = "로또 번호는 1부터 45 사이여야 합니다."; + public static final String SIZE_EXCEPTION = "로또 번호는 6개입니다."; + public static final String DUPLICATE_EXCEPTION = "로또 번호는 중복이 불가합니다."; + public static final String VALUE_EXCEPTION = "로또 번호는 숫자여야 합니다."; + public static final String PRICE_VALUE_EXCEPTION = "가격은 숫자여야 합니다."; + public static final String BONUS_DUPLICATE_EXCEPTION = "보너스 볼은 중복이 불가합니다."; + public static final String LOTTO_PRICE_EXCEPTION = "로또는 천원 단위로만 구입이 가능합니다."; + + +} diff --git a/src/main/java/model/Lotto.java b/src/main/java/model/Lotto.java new file mode 100644 index 00000000..980a71cf --- /dev/null +++ b/src/main/java/model/Lotto.java @@ -0,0 +1,41 @@ +package model; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Lotto { + private List numbers = new ArrayList<>(); + + public Lotto(List numbers) { + validateSize(numbers); + validateDuplicate(numbers); + this.numbers = new ArrayList<>(numbers); + } + + @Override + public String toString() { + return numbers.toString(); + } + + public List getNumbers() { + return new ArrayList<>(numbers); + } + + private void validateSize(List numbers) { + if (numbers.size() != Constant.SIX_COUNT) { + throw new IllegalArgumentException(Constant.SIZE_EXCEPTION); + } + } + + private void validateDuplicate(List numbers) { + Set numberSet = new HashSet<>(); + for (LottoNumber number : numbers) { + numberSet.add(number.getNumber()); + } + if (numberSet.size() != Constant.SIX_COUNT) { + throw new IllegalArgumentException(Constant.DUPLICATE_EXCEPTION); + } + } +} diff --git a/src/main/java/model/LottoGenerator.java b/src/main/java/model/LottoGenerator.java new file mode 100644 index 00000000..e66ec33a --- /dev/null +++ b/src/main/java/model/LottoGenerator.java @@ -0,0 +1,48 @@ +package model; + +import java.util.*; +import java.util.stream.Collectors; + +public class LottoGenerator { + private List numberList; + + public LottoGenerator() { + numberList = new ArrayList<>(); + for (int i = Constant.MIN_LOTTO_NUMBER; i <= Constant.MAX_LOTTO_NUMBER; i++) { + numberList.add(i); + } + } + + public List generateLottos(int autoLottoCount, List manualLottoList) { + List lottos = convertManualLottoList(manualLottoList); + + for (int i = 0; i < autoLottoCount; i++) { + lottos.add(new Lotto(generateRandomNumbers())); + } + + return lottos; + } + + private List convertManualLottoList(List manualLottoList) { + return manualLottoList.stream() + .map(this::parseLottoNumbers) + .collect(Collectors.toList()); + } + + private Lotto parseLottoNumbers(String lotto) { + List numbers = Arrays.stream(lotto.split(Constant.SEPARATOR)) + .map(String::trim) + .map(LottoNumber::new) + .collect(Collectors.toList()); + return new Lotto(numbers); + } + + private List generateRandomNumbers() { + Collections.shuffle(numberList); + return numberList.subList(Constant.ZERO_COUNT, Constant.LOTTO_SIZE) + .stream() + .map(n -> new LottoNumber(String.valueOf(n))) + .sorted(Comparator.comparingInt(LottoNumber::getNumber)) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/model/LottoMoney.java b/src/main/java/model/LottoMoney.java new file mode 100644 index 00000000..5023c1d1 --- /dev/null +++ b/src/main/java/model/LottoMoney.java @@ -0,0 +1,25 @@ +package model; + +public class LottoMoney { + private final int amount; + + public LottoMoney(String inputAmount) { + try { + int amount = Integer.parseInt(inputAmount); + validateAmount(amount); + this.amount = amount; + } catch (NumberFormatException e) { + throw new IllegalArgumentException(Constant.PRICE_VALUE_EXCEPTION, e); + } + } + + private void validateAmount(int amount) { + if (amount % Constant.LOTTO_PRICE != 0 || amount < Constant.LOTTO_PRICE) { + throw new IllegalArgumentException(Constant.LOTTO_PRICE_EXCEPTION); + } + } + + public int getAmount() { + return amount; + } +} diff --git a/src/main/java/model/LottoNumber.java b/src/main/java/model/LottoNumber.java new file mode 100644 index 00000000..d596470b --- /dev/null +++ b/src/main/java/model/LottoNumber.java @@ -0,0 +1,30 @@ +package model; + +public class LottoNumber { + private final int number; + + public LottoNumber(String number) { + try { + int parsedNumber = Integer.parseInt(number); + validateRange(parsedNumber); + this.number = parsedNumber; + } catch (NumberFormatException e) { + throw new IllegalArgumentException(Constant.VALUE_EXCEPTION, e); + } + } + + private void validateRange(int number) { + if (number < Constant.MIN_LOTTO_NUMBER || number > Constant.MAX_LOTTO_NUMBER) { + throw new IllegalArgumentException(Constant.RANGE_EXCEPTION); + } + } + + public int getNumber() { + return number; + } + + @Override + public String toString() { + return String.valueOf(number); + } +} diff --git a/src/main/java/model/LottoPrize.java b/src/main/java/model/LottoPrize.java new file mode 100644 index 00000000..65b6a3f8 --- /dev/null +++ b/src/main/java/model/LottoPrize.java @@ -0,0 +1,32 @@ +package model; + +public enum LottoPrize { + FIFTH(Constant.THREE_COUNT, Constant.FIFTH_PRICE, false), + FOURTH(Constant.FOUR_COUNT, Constant.FOURTH_PRICE, false), + THIRD(Constant.FIVE_COUNT, Constant.THIRD_PRICE, false), + SECOND(Constant.FIVE_COUNT, Constant.SECOND_PRICE, true), + FIRST(Constant.SIX_COUNT, Constant.FIRST_PRICE, false); + + private final int matchCount; + private final int prizeAmount; + private final boolean hasBonus; + + LottoPrize(int matchCount, int prizeAmount, boolean hasBonus) { + this.matchCount = matchCount; + this.prizeAmount = prizeAmount; + this.hasBonus = hasBonus; + } + + public int getMatchCount() { + return matchCount; + } + + public int getPrizeAmount() { + return prizeAmount; + } + + public boolean hasBonus() { + return hasBonus; + } + +} diff --git a/src/main/java/model/ProfitCalculator.java b/src/main/java/model/ProfitCalculator.java new file mode 100644 index 00000000..b1be680f --- /dev/null +++ b/src/main/java/model/ProfitCalculator.java @@ -0,0 +1,9 @@ +package model; + +public class ProfitCalculator { + + public double calculateProfit(int winPrice, int totalPrice) { + return (double) winPrice / totalPrice; + } + +} diff --git a/src/main/java/model/WinLotto.java b/src/main/java/model/WinLotto.java new file mode 100644 index 00000000..4426aab4 --- /dev/null +++ b/src/main/java/model/WinLotto.java @@ -0,0 +1,68 @@ +package model; + +import java.util.*; + +public class WinLotto { + private List winnerLotto = new ArrayList<>(); + private List userLottos; + private Map winningStates = new LinkedHashMap<>(); + private int winPrize; + private LottoNumber bonusBall; + + public WinLotto(String inputWinnerLotto, List userLottos, String bonusBall) { + Arrays.stream(inputWinnerLotto.split(Constant.SEPARATOR)) + .forEach(e -> winnerLotto.add(new LottoNumber(e.trim()))); + this.userLottos = userLottos; + this.winPrize = Constant.ZERO_COUNT; + LottoNumber tempBonusBall = new LottoNumber(bonusBall); + if (winnerLotto.stream().anyMatch(winnerNumber -> winnerNumber.getNumber() == tempBonusBall.getNumber())) { + throw new IllegalArgumentException(Constant.BONUS_DUPLICATE_EXCEPTION); + } + this.bonusBall = new LottoNumber(bonusBall); + for (LottoPrize prize : LottoPrize.values()) { + winningStates.put(prize, Constant.ZERO_COUNT); + } + } + + private int compareHowManyNumberSame(Lotto lotto) { + return (int) lotto.getNumbers().stream() + .filter(number -> winnerLotto.stream().anyMatch(winnerNumber -> winnerNumber.getNumber() == number.getNumber())) + .count(); + } + + public int getWinPrize() { + return winPrize; + } + + public Map getWinningStates() { + return winningStates; + } + + public void updateWinningStates(List userLottos) { + userLottos.forEach(userLotto -> { + int matchedNumbers = compareHowManyNumberSame(userLotto); + boolean containsBonus = userLotto.getNumbers().stream().anyMatch(number -> number.getNumber() == bonusBall.getNumber()); + + if (matchedNumbers == Constant.SIX_COUNT) { + updateWinningState(LottoPrize.FIRST); + } + if (matchedNumbers == Constant.FIVE_COUNT && containsBonus) { + updateWinningState(LottoPrize.SECOND); + } + if (matchedNumbers == Constant.FIVE_COUNT && !containsBonus) { + updateWinningState(LottoPrize.THIRD); + } + if (matchedNumbers == Constant.FOUR_COUNT) { + updateWinningState(LottoPrize.FOURTH); + } + if (matchedNumbers == Constant.THREE_COUNT) { + updateWinningState(LottoPrize.FIFTH); + } + }); + } + + private void updateWinningState(LottoPrize prize) { + winningStates.put(prize, winningStates.get(prize) + 1); + winPrize += prize.getPrizeAmount(); + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..b7386e84 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,51 @@ +package view; + +import model.Constant; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class InputView { + + public static int getPrice() { + System.out.println(Constant.PRICE_QUERY); + return readInt(); + } + + public static int getManualCount() { + System.out.println(Constant.MANUAL_COUNT_QUERY); + return readInt(); + } + + public static List getManualLottos(int manualCount) { + System.out.println(Constant.MANUAL_LOTTO_QUERY); + List manualLottos = new ArrayList(); + for (int i = 0; i < manualCount; i++) { + manualLottos.add(readString()); + } + return manualLottos; + } + + public static String getWinLotto() { + System.out.println(Constant.WIN_LOTTO_QUERY); + return readString(); + } + + public static String getBonusBall() { + System.out.println(Constant.BONUS_QUERY); + return readString(); + } + + private static int readInt() { + Scanner scanner = new Scanner(System.in); + return scanner.nextInt(); + } + + private static String readString() { + Scanner scanner = new Scanner(System.in); + return scanner.nextLine(); + } + + +} diff --git a/src/main/java/view/ResultView.java b/src/main/java/view/ResultView.java new file mode 100644 index 00000000..8c35e54f --- /dev/null +++ b/src/main/java/view/ResultView.java @@ -0,0 +1,42 @@ +package view; + +import model.Constant; +import model.Lotto; +import model.LottoPrize; + +import java.util.List; +import java.util.Map; + +public class ResultView { + + public static void printLottos(int manualCount, List lottos) { + + System.out.println(Constant.LOTTO_BUY_RESULT_1 + manualCount + Constant.LOTTO_BUY_RESULT_2 + Constant.BLANK + (lottos.size() - manualCount) + Constant.LOTTO_BUY_RESULT_3); + lottos.stream().forEach(lotto -> System.out.println(lotto)); + System.out.println(Constant.BLANK); + } + + + public static void printStatics(Map winningStates) { + System.out.println(Constant.BLANK); + System.out.println(Constant.WIN_RESULT); + System.out.println(Constant.LINES); + + winningStates.forEach((prize, count) -> { + String message = prize.getMatchCount() + Constant.STATICS_RESULT_1; + if (prize.hasBonus()) { + message += Constant.SEPARATOR + Constant.STATICS_RESULT_2; + } + message += Constant.OPEN_PARENTHESES; + message += prize.getPrizeAmount() + Constant.STATICS_RESULT_3 + Constant.CLOSE_PARENTHESES + Constant.DASH + Constant.BLANK + count + Constant.STATICS_RESULT_4; + System.out.println(message); + }); + } + + + public static void printProfit(double profit) { + System.out.println(Constant.PROFIT_RESULT_1 + String.format("%.2f", profit) + Constant.PROFIT_RESULT_2); + } + + +} diff --git a/src/test/java/LottoTest.java b/src/test/java/LottoTest.java new file mode 100644 index 00000000..70cc4e34 --- /dev/null +++ b/src/test/java/LottoTest.java @@ -0,0 +1,92 @@ +import model.Lotto; +import model.LottoMoney; +import model.LottoNumber; +import model.WinLotto; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class LottoTest { + @Test + void lottoRangeTest() { + String testNum = "46"; + assertThatThrownBy(() -> new LottoNumber(testNum)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("로또 번호는 1부터 45 사이여야 합니다."); + } + + + @Test + void lottoMoneyTest() { + String price = "1001"; + assertThatThrownBy(() -> new LottoMoney(price)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("로또는 천원 단위로만 구입이 가능합니다."); + } + + @Test + void lottoListTest() { + List numbers = List.of(1, 2, 3, 4, 5); + + List lottoNumbers = numbers.stream() + .map(number -> String.valueOf(number)) + .map(LottoNumber::new) + .collect(Collectors.toList()); + + assertThatThrownBy(() -> new Lotto(lottoNumbers)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("로또 번호는 6개입니다."); + } + + @Test + void lottoDuplicateNumbersTest() { + List numbersWithDuplicates = List.of(1, 2, 3, 4, 4, 6); + + List lottoNumbers = numbersWithDuplicates.stream() + .map(number -> String.valueOf(number)) + .map(LottoNumber::new) + .collect(Collectors.toList()); + + assertThatThrownBy(() -> new Lotto(lottoNumbers)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("로또 번호는 중복이 불가합니다."); + } + + @Test + void isLottoNumber() { + String invalidNumber = "십"; + + assertThatThrownBy(() -> new LottoNumber(invalidNumber)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("로또 번호는 숫자여야 합니다."); + } + + @Test + void isLottoPriceNumber() { + String invalidAmount = "만원"; + + assertThatThrownBy(() -> new LottoMoney(invalidAmount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("가격은 숫자여야 합니다."); + } + + @Test + void testBonusBallDuplicated() { + // given + String inputWinnerLotto = "1, 2, 3, 4, 5, 6"; + List userLottos = new ArrayList<>(); + String bonusBall = "6"; + + // when & then + Exception exception = assertThrows(IllegalArgumentException.class, () -> new WinLotto(inputWinnerLotto, userLottos, bonusBall)); + assertThat(exception.getMessage()).contains("보너스 볼은 중복이 불가합니다."); + } + + +} diff --git a/src/test/java/WinLottoTest.java b/src/test/java/WinLottoTest.java new file mode 100644 index 00000000..4c67f3f2 --- /dev/null +++ b/src/test/java/WinLottoTest.java @@ -0,0 +1,73 @@ +import model.Lotto; +import model.LottoNumber; +import model.WinLotto; +import model.LottoPrize; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class WinLottoTest { + + private WinLotto winLotto; + private final String winningNumbers = "1, 2, 3, 4, 5, 6"; + private final String bonusBall = "7"; + + @BeforeEach + void setUp() { + winLotto = new WinLotto(winningNumbers, List.of(), bonusBall); + } + + private Lotto createLotto(String numbers) { + List lottoNumbers = Arrays.stream(numbers.split(",")) + .map(String::trim) + .map(Integer::parseInt) + .map(number -> String.valueOf(number)) + .map(LottoNumber::new) + .collect(Collectors.toList()); + return new Lotto(lottoNumbers); + } + + + @Test + void testFirstPrize() { + Lotto userLotto = createLotto("1, 2, 3, 4, 5, 6"); + winLotto.updateWinningStates(List.of(userLotto)); + assertEquals(1, winLotto.getWinningStates().get(LottoPrize.FIRST)); + } + + @Test + void testSecondPrize() { + Lotto userLotto = createLotto("1, 2, 3, 4, 5, 7"); + winLotto.updateWinningStates(List.of(userLotto)); + assertEquals(1, winLotto.getWinningStates().get(LottoPrize.SECOND)); + } + + @Test + void testThirdPrize() { + Lotto userLotto = createLotto("1, 2, 3, 4, 5, 8"); + winLotto.updateWinningStates(List.of(userLotto)); + assertEquals(1, winLotto.getWinningStates().get(LottoPrize.THIRD)); + } + + @Test + void testFourthPrize() { + Lotto userLotto = createLotto("1, 2, 3, 4, 8, 9"); + winLotto.updateWinningStates(List.of(userLotto)); + assertEquals(1, winLotto.getWinningStates().get(LottoPrize.FOURTH)); + } + + @Test + void testFifthPrize() { + Lotto userLotto = createLotto("1, 2, 3, 7, 8, 9"); + winLotto.updateWinningStates(List.of(userLotto)); + assertEquals(1, winLotto.getWinningStates().get(LottoPrize.FIFTH)); + } +}