diff --git a/README.md b/README.md index aae5d58a34e..1a83d90fc76 100644 --- a/README.md +++ b/README.md @@ -60,24 +60,48 @@ ~~~ ## TODO -- [ ] 입력 +- [x] 입력 - 가격 입력 - 당첨 번호 입력 -- [ ] 출력 +- [x] 출력 - 구매 개수 출력 - 발행된 로또 출력 - 결과 출력 - 수익률 출력 -- [ ] 로또 발행 +- [x] 로또 발행 - 가격에 맞는 개수만큼 로또를 생성 -- [ ] 로또 +- [x] 로또 - 6개의 숫자를 가지고 있음 - 6개 숫자 생성 방법 - 자동 : Collections.shuffle() - 수동 : 입력값 - 정렬 - Collections.sort() -- [ ] 결과 +- [x] 결과 - 로또와 당첨 번호와 비교하여 당첨 금액 리턴 -- [ ] 수익률 +- [x] 수익률 - 결과의 당첨금액을 계산하여 수익률 리턴 + +--- + +# 🚀 4단계 - 로또(수동) + +--- + +## 기능 요구사항 +현재 로또 생성기는 자동 생성 기능만 제공한다. 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야 한다. +입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다. + + +## TODO +- [x] 수동 생성 기능 + - Strategy 생성 : 생성 시 로또 번호를 입력 받아 로또를 생성 + - LottoMarket 생성 시 strategy를 입력 받는 생성자 추가 +- [x] 로또 구매 + - 가격, 수동 로또 개수를 갖는 생성자 : 자동 로또 개수를 계산 + - 수동 로또의 개수 검증 및 테스트 메소드 구현 +- [ ] 입력 + - 수동 로또 개수 입력 + - 수동 로또 번호 입력 +- [ ] 출력 + - 로또 구매 개수 : 수동 / 자동 각 출력 diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 79212cfc6c5..ab724c4b225 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -1,17 +1,27 @@ -import lotto.domain.LottoMarket; -import lotto.domain.LottoPrice; -import lotto.domain.LottoResult; -import lotto.domain.WinningNumbers; +import lotto.domain.*; +import lotto.domain.strategy.AutoLottoNumberStrategy; +import lotto.domain.strategy.ManualLottoNumberStrategy; import lotto.io.InputView; import lotto.io.PrintView; public class Main { public static void main(String[] args) { - LottoPrice lottoPrice = new LottoPrice(InputView.inputPurchasePrice()); - int numberOfLotto = lottoPrice.getNumberOfLotto(); - PrintView.printNumberOfLotto(numberOfLotto); + LottoPrice lottoPrice = new LottoPrice(InputView.inputPurchasePrice(), InputView.inputNumberOfManualLotto()); + int numberOfAutoLotto = lottoPrice.getNumberOfAutoLotto(); + int numberOfManualLotto = lottoPrice.getNumberOfManualLotto(); + + LottoMarket lottoMarket = new LottoMarket(numberOfAutoLotto, new AutoLottoNumberStrategy()); + + if(numberOfManualLotto > 0) { + PrintView.printAutoLottoGuide(); + } + + for(int i = 0; i < numberOfManualLotto; i++) { + lottoMarket.addLotto(new ManualLottoNumberStrategy(InputView.inputLottoNumber())); + } + + PrintView.printNumberOfLotto(numberOfManualLotto, numberOfAutoLotto); - LottoMarket lottoMarket = new LottoMarket(numberOfLotto); PrintView.printLottos(lottoMarket.getLottos()); WinningNumbers winningNumbers = new WinningNumbers(InputView.inputWinningNumbers(), InputView.inputBonusNumber()); diff --git a/src/main/java/lotto/domain/LottoMarket.java b/src/main/java/lotto/domain/LottoMarket.java index 261f7683006..b2f74be4971 100644 --- a/src/main/java/lotto/domain/LottoMarket.java +++ b/src/main/java/lotto/domain/LottoMarket.java @@ -1,6 +1,5 @@ package lotto.domain; -import lotto.domain.strategy.AutoLottoNumberStrategy; import lotto.domain.strategy.LottoNumberStrategy; import java.util.List; @@ -8,14 +7,16 @@ public class LottoMarket { private final Lottos lottos = new Lottos(); - public LottoMarket(int numberOfLotto) { - LottoNumberStrategy strategy = new AutoLottoNumberStrategy(); - + public LottoMarket(int numberOfLotto, LottoNumberStrategy strategy) { for(int i = 0; i < numberOfLotto; i++) { lottos.add(new Lotto(strategy)); } } + public void addLotto(LottoNumberStrategy strategy) { + lottos.add(new Lotto(strategy)); + } + public List getLottos() { return lottos.getLottos(); } diff --git a/src/main/java/lotto/domain/LottoNumbers.java b/src/main/java/lotto/domain/LottoNumbers.java index a68523f8608..568348c6504 100644 --- a/src/main/java/lotto/domain/LottoNumbers.java +++ b/src/main/java/lotto/domain/LottoNumbers.java @@ -1,12 +1,27 @@ package lotto.domain; +import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; +import static lotto.domain.strategy.LottoNumberStrategy.*; + public class LottoNumbers { private final List lottoNumbers; public LottoNumbers(List lottoNumbers) { + if(new HashSet<>(lottoNumbers).size() != LOTTO_NUMBER_COUNT) { + throw new IllegalArgumentException("번호는 중복될 수 없습니다."); + } + + if(lottoNumbers.size() != LOTTO_NUMBER_COUNT) { + throw new IllegalArgumentException("번호는 " + LOTTO_NUMBER_COUNT + "개를 입력해야 합니다."); + } + + if(lottoNumbers.stream().anyMatch(x -> x < LOTTO_NUMBER_MIN || x > LOTTO_NUMBER_MAX)) { + throw new IllegalArgumentException("번호는 " + LOTTO_NUMBER_MIN + " 미만 " + LOTTO_NUMBER_MAX + " 초과인 수를 입력할 수 없습니다."); + } + this.lottoNumbers = lottoNumbers.stream() .sorted() .collect(Collectors.toList()); diff --git a/src/main/java/lotto/domain/LottoPrice.java b/src/main/java/lotto/domain/LottoPrice.java index 1ac558ef388..9b915242c1f 100644 --- a/src/main/java/lotto/domain/LottoPrice.java +++ b/src/main/java/lotto/domain/LottoPrice.java @@ -3,19 +3,43 @@ public class LottoPrice { private static final int LOTTO_PURCHASE = 1000; private final int price; + private final int numberOfAutoLotto; + private final int numberOfManualLotto; public LottoPrice(int price) { if(price < LOTTO_PURCHASE) { throw new IllegalArgumentException("1000원 이상의 금액을 입력해야 합니다."); } this.price = price; + this.numberOfAutoLotto = price / LOTTO_PURCHASE; + this.numberOfManualLotto = 0; + } + + public LottoPrice(int price, int numberOfInputLotto) { + if(price < LOTTO_PURCHASE) { + throw new IllegalArgumentException("1000원 이상의 금액을 입력해야 합니다."); + } + + int maxNumberOfLotto = price / LOTTO_PURCHASE; + + if(maxNumberOfLotto < numberOfInputLotto) { + throw new IllegalArgumentException("수동 로또는 " + maxNumberOfLotto + "장 까지만 구매할 수 있습니다."); + } + + this.price = price; + this.numberOfAutoLotto = price / LOTTO_PURCHASE - numberOfInputLotto; + this.numberOfManualLotto = numberOfInputLotto; } public int getPrice() { return price; } - public int getNumberOfLotto() { - return price / LOTTO_PURCHASE; + public int getNumberOfAutoLotto() { + return numberOfAutoLotto; + } + + public int getNumberOfManualLotto() { + return numberOfManualLotto; } } diff --git a/src/main/java/lotto/domain/WinningNumbers.java b/src/main/java/lotto/domain/WinningNumbers.java index f158d5131f0..11e1108d971 100644 --- a/src/main/java/lotto/domain/WinningNumbers.java +++ b/src/main/java/lotto/domain/WinningNumbers.java @@ -1,24 +1,15 @@ package lotto.domain; import java.util.Arrays; -import java.util.Set; import java.util.stream.Collectors; import static lotto.domain.strategy.LottoNumberStrategy.*; public class WinningNumbers { - private final Set winningNumbers; + private final LottoNumbers winningNumbers; private final int bonusNumber; - public WinningNumbers(Set winningNumbers, int bonusNumber) { - if(winningNumbers.size() != LOTTO_NUMBER_COUNT) { - throw new IllegalArgumentException("당첨 번호는 " + LOTTO_NUMBER_COUNT + "개를 입력해야 합니다."); - } - - if(winningNumbers.stream().anyMatch(x -> x < LOTTO_NUMBER_MIN || x > LOTTO_NUMBER_MAX)) { - throw new IllegalArgumentException("당첨 번호는 " + LOTTO_NUMBER_MIN + " 미만 " + LOTTO_NUMBER_MAX + " 초과인 수를 입력할 수 없습니다."); - } - + public WinningNumbers(LottoNumbers winningNumbers, int bonusNumber) { if(bonusNumber < LOTTO_NUMBER_MIN || bonusNumber > LOTTO_NUMBER_MAX) { throw new IllegalArgumentException("보너스 번호는 " + LOTTO_NUMBER_MIN + " 미만 " + LOTTO_NUMBER_MAX + " 초과인 수를 입력할 수 없습니다."); } @@ -28,13 +19,13 @@ public WinningNumbers(Set winningNumbers, int bonusNumber) { } public WinningNumbers(String[] winningNumbers, int bonusNumber) { - this(Arrays.stream(winningNumbers) + this(new LottoNumbers(Arrays.stream(winningNumbers) .map(Integer::parseInt) - .collect(Collectors.toSet()), bonusNumber); + .collect(Collectors.toList())), bonusNumber); } public Long getMatchedCount(Lotto lotto) { - return winningNumbers.stream() + return winningNumbers.getLottoNumbers().stream() .filter(lotto::isMatched) .count(); } diff --git a/src/main/java/lotto/domain/strategy/ManualLottoNumberStrategy.java b/src/main/java/lotto/domain/strategy/ManualLottoNumberStrategy.java new file mode 100644 index 00000000000..ce8373707c6 --- /dev/null +++ b/src/main/java/lotto/domain/strategy/ManualLottoNumberStrategy.java @@ -0,0 +1,24 @@ +package lotto.domain.strategy; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class ManualLottoNumberStrategy implements LottoNumberStrategy{ + private final List numbers; + + public ManualLottoNumberStrategy(List numbers) { + this.numbers = numbers; + } + + public ManualLottoNumberStrategy(String[] numbers) { + this(Arrays.stream(numbers) + .map(Integer::parseInt) + .collect(Collectors.toList())); + } + + @Override + public List generateLottoNumber() { + return this.numbers; + } +} diff --git a/src/main/java/lotto/io/InputView.java b/src/main/java/lotto/io/InputView.java index bb7e9214cea..9d34723e33d 100644 --- a/src/main/java/lotto/io/InputView.java +++ b/src/main/java/lotto/io/InputView.java @@ -10,6 +10,11 @@ public static int inputPurchasePrice() { return Integer.parseInt(scanner.nextLine()); } + public static int inputNumberOfManualLotto() { + System.out.println("수동으로 구매할 로또 수를 입력해 주세요."); + return Integer.parseInt(scanner.nextLine()); + } + public static String[] inputWinningNumbers() { System.out.println("지난 주 당첨 번호를 입력해 주세요."); return scanner.nextLine().replace(" ", "").split(","); @@ -19,4 +24,8 @@ public static int inputBonusNumber() { System.out.println("보너스 볼을 입력해 주세요."); return Integer.parseInt(scanner.nextLine()); } + + public static String[] inputLottoNumber() { + return scanner.nextLine().replace(" ", "").split(","); + } } diff --git a/src/main/java/lotto/io/PrintView.java b/src/main/java/lotto/io/PrintView.java index 7bfbd31659d..ce76853d9c2 100644 --- a/src/main/java/lotto/io/PrintView.java +++ b/src/main/java/lotto/io/PrintView.java @@ -10,8 +10,12 @@ public class PrintView { static StringBuilder stringBuilder = new StringBuilder(); - public static void printNumberOfLotto(int number) { - System.out.println(number + "개를 구매했습니다."); + public static void printAutoLottoGuide() { + System.out.println("수동으로 구매할 번호를 입력해주세요."); + } + + public static void printNumberOfLotto(int manualLotto, int autoLotto) { + System.out.println("수동으로 "+ manualLotto +"장, 자동으로 " + autoLotto + "개를 구매했습니다."); } public static void printLottos(List lottos) { diff --git a/src/test/java/lotto/domain/LottoMarketTest.java b/src/test/java/lotto/domain/LottoMarketTest.java index 59d9b9d2b54..d55c1ab78f0 100644 --- a/src/test/java/lotto/domain/LottoMarketTest.java +++ b/src/test/java/lotto/domain/LottoMarketTest.java @@ -2,13 +2,15 @@ import org.junit.jupiter.api.Test; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; public class LottoMarketTest { @Test void 입력한_개수만큼_로또를_발행한다() { int numberOfLotto = 1; - LottoMarket lottoMarket = new LottoMarket(numberOfLotto); + LottoMarket lottoMarket = new LottoMarket(numberOfLotto, () -> List.of(1, 2, 3, 4, 5, 6)); assertThat(lottoMarket.getLottos()).hasSize(numberOfLotto); } } diff --git a/src/test/java/lotto/domain/LottoPriceTest.java b/src/test/java/lotto/domain/LottoPriceTest.java index 9c4e81b1988..0856e01e5e2 100644 --- a/src/test/java/lotto/domain/LottoPriceTest.java +++ b/src/test/java/lotto/domain/LottoPriceTest.java @@ -1,7 +1,11 @@ package lotto.domain; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; class LottoPriceTest { @@ -11,4 +15,19 @@ class LottoPriceTest { .isThrownBy(() -> new LottoPrice(0)) .withMessageMatching("1000원 이상의 금액을 입력해야 합니다."); } + + @ParameterizedTest + @ValueSource(ints = {1, 3, 10}) + @DisplayName("자동 로또 개수 = 금액 / 1000 - 수동 로또 개수") + void 자동_로또_개수_검증(int number) { + assertThat(new LottoPrice(10000, number).getNumberOfAutoLotto()).isEqualTo(10000 / 1000 - number); + } + + @ParameterizedTest + @ValueSource(ints = {10000, 3000, 1000}) + void 수동_로또는_총_생성_개수를_넘을_수_없다(int price) { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> new LottoPrice(price, price / 1000 + 1)) + .withMessageMatching("수동 로또는 " + price / 1000 + "장 까지만 구매할 수 있습니다."); + } } diff --git a/src/test/java/lotto/domain/LottoResultTest.java b/src/test/java/lotto/domain/LottoResultTest.java index d142aec9b13..50068918025 100644 --- a/src/test/java/lotto/domain/LottoResultTest.java +++ b/src/test/java/lotto/domain/LottoResultTest.java @@ -1,6 +1,6 @@ package lotto.domain; -import lotto.domain.strategy.LottoNumberStrategy; +import lotto.domain.strategy.ManualLottoNumberStrategy; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -13,13 +13,13 @@ import static org.assertj.core.api.Assertions.assertThat; public class LottoResultTest { - private final WinningNumbers winningNumbers = new WinningNumbers(Set.of(1, 2, 3, 4, 5, 6), 11); - private static final Lotto twoMatchedLotto = new Lotto(getInputLottoNumberStrategy(List.of(1, 2, 7, 8, 9, 10))); - private static final Lotto threeMatchedLotto = new Lotto(getInputLottoNumberStrategy(List.of(1, 2, 3, 7, 8, 9))); - private static final Lotto fourMatchedLotto = new Lotto(getInputLottoNumberStrategy(List.of(1, 2, 3, 4, 8, 9))); - private static final Lotto fiveMatchedLotto = new Lotto(getInputLottoNumberStrategy(List.of(1, 2, 3, 4, 5, 9))); - private static final Lotto fiveMatchedWithBonusLotto = new Lotto(getInputLottoNumberStrategy(List.of(1, 2, 3, 4, 5, 11))); - private static final Lotto sixMatchedLotto = new Lotto(getInputLottoNumberStrategy(List.of(1, 2, 3, 4, 5, 6))); + private final WinningNumbers winningNumbers = new WinningNumbers(new LottoNumbers(List.of(1, 2, 3, 4, 5, 6)), 11); + private static final Lotto twoMatchedLotto = new Lotto(new ManualLottoNumberStrategy(List.of(1, 2, 7, 8, 9, 10))); + private static final Lotto threeMatchedLotto = new Lotto(new ManualLottoNumberStrategy(List.of(1, 2, 3, 7, 8, 9))); + private static final Lotto fourMatchedLotto = new Lotto(new ManualLottoNumberStrategy(List.of(1, 2, 3, 4, 8, 9))); + private static final Lotto fiveMatchedLotto = new Lotto(new ManualLottoNumberStrategy(List.of(1, 2, 3, 4, 5, 9))); + private static final Lotto fiveMatchedWithBonusLotto = new Lotto(new ManualLottoNumberStrategy(List.of(1, 2, 3, 4, 5, 11))); + private static final Lotto sixMatchedLotto = new Lotto(new ManualLottoNumberStrategy(List.of(1, 2, 3, 4, 5, 6))); private LottoPrice lottoPrice; @ParameterizedTest @@ -65,8 +65,4 @@ private static Stream getLottoNumbersAndExpectedPrize() { , Arguments.of(sixMatchedLotto, 2000000000) ); } - - private static LottoNumberStrategy getInputLottoNumberStrategy(List inputLottoNumber) { - return () -> inputLottoNumber; - } } diff --git a/src/test/java/lotto/domain/WinningNumbersTest.java b/src/test/java/lotto/domain/WinningNumbersTest.java index 7c2e4815f79..7d6adbb3d92 100644 --- a/src/test/java/lotto/domain/WinningNumbersTest.java +++ b/src/test/java/lotto/domain/WinningNumbersTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.Test; +import java.util.List; import java.util.Set; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -10,28 +11,28 @@ public class WinningNumbersTest { @Test void 여섯개의_번호를_입력하지_않으면_오류_발생() { assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> new WinningNumbers(Set.of(1, 2, 3, 4, 5), 6)) - .withMessageMatching("당첨 번호는 6개를 입력해야 합니다."); + .isThrownBy(() -> new WinningNumbers(new LottoNumbers(List.of(1, 2, 3, 4, 5)), 6)) + .withMessageMatching("번호는 6개를 입력해야 합니다."); } @Test void 당첨_번호는_1_미만_이면_오류_발생() { assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> new WinningNumbers(Set.of(0, 1, 2, 3, 4, 5), 6)) - .withMessageMatching("당첨 번호는 1 미만 45 초과인 수를 입력할 수 없습니다."); + .isThrownBy(() -> new WinningNumbers(new LottoNumbers(List.of(0, 1, 2, 3, 4, 5)), 6)) + .withMessageMatching("번호는 1 미만 45 초과인 수를 입력할 수 없습니다."); } @Test void 당첨_번호는_45_초과_이면_오류_발생() { assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> new WinningNumbers(Set.of(46, 1, 2, 3, 4, 5), 6)) - .withMessageMatching("당첨 번호는 1 미만 45 초과인 수를 입력할 수 없습니다."); + .isThrownBy(() -> new WinningNumbers(new LottoNumbers(List.of(46, 1, 2, 3, 4, 5)), 6)) + .withMessageMatching("번호는 1 미만 45 초과인 수를 입력할 수 없습니다."); } @Test void 보너스_번호는_45_초과_이면_오류_발생() { assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> new WinningNumbers(Set.of(1, 2, 3, 4, 5, 6), 0)) - .withMessageMatching("당첨 번호는 1 미만 45 초과인 수를 입력할 수 없습니다."); + .isThrownBy(() -> new WinningNumbers(new LottoNumbers(List.of(1, 2, 3, 4, 5, 6)), 0)) + .withMessageMatching("보너스 번호는 1 미만 45 초과인 수를 입력할 수 없습니다."); } }