diff --git a/src/main/java/LottoApplication.java b/src/main/java/LottoApplication.java new file mode 100644 index 00000000..4c6163f3 --- /dev/null +++ b/src/main/java/LottoApplication.java @@ -0,0 +1,8 @@ +import controller.LottoController; + +public class LottoApplication { + public static void main(String[] args) { + LottoController controller = new LottoController(); + controller.run(); + } +} diff --git a/src/main/java/controller/LottoController.java b/src/main/java/controller/LottoController.java new file mode 100644 index 00000000..3d43b9a3 --- /dev/null +++ b/src/main/java/controller/LottoController.java @@ -0,0 +1,44 @@ +package controller; + +import domain.*; +import view.InputView; +import view.OutputView; + +import java.util.List; + +public class LottoController { + public void run() { + int purchaseAmount = InputView.getPurchaseAmount(); + int manualLottoCount = InputView.getManualLottoCount(); + List lottos = InputView.getManualLottos(manualLottoCount); + + int autoLottoCount = purchaseAmount / Lotto.LOTTO_PRICE - manualLottoCount; + lottos.addAll(LottoMachine.generateLottos(autoLottoCount)); + OutputView.printLottos(lottos, manualLottoCount, autoLottoCount); + + Lotto winningLotto = InputView.getWinningLottoNumbers(); + LottoNumber bonusNumber = InputView.getBonusNumber(); + + LottoResult result = getLottoResult(lottos, winningLotto, bonusNumber); + OutputView.printResult(result, purchaseAmount); + } + + private LottoResult getLottoResult(final List lottos, final Lotto winningLotto, final LottoNumber bonusNumber) { + List prizes = lottos.stream() + .map(lotto -> getLottoPrize(lotto, winningLotto, bonusNumber)) + .toList(); + return new LottoResult(prizes); + } + + private LottoPrize getLottoPrize(final Lotto lotto, final Lotto winningLotto, final LottoNumber bonusNumber) { + int matchCount = countMatchingNumbers(lotto, winningLotto); + boolean isMatchBonus = lotto.contains(bonusNumber); + return LottoPrize.matchPrize(matchCount, isMatchBonus); + } + + private int countMatchingNumbers(final Lotto lotto, final Lotto winningLotto) { + return (int) lotto.numbers().stream() + .filter(winningLotto.numbers()::contains) + .count(); + } +} diff --git a/src/main/java/domain/Lotto.java b/src/main/java/domain/Lotto.java new file mode 100644 index 00000000..e304e194 --- /dev/null +++ b/src/main/java/domain/Lotto.java @@ -0,0 +1,44 @@ +package domain; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public record Lotto(List numbers) { + public static final int LOTTO_NUMBER_COUNT = 6; + public static final int LOTTO_PRICE = 1000; + + public Lotto { + validateNumbers(numbers); + Collections.sort(numbers); + } + + private void validateNumbers(final List numbers) { + if (numbers.size() != LOTTO_NUMBER_COUNT) { + throw new IllegalArgumentException("로또 숫자는 6개여야 합니다."); + } + if (numbers.stream().distinct().count() != LOTTO_NUMBER_COUNT) { + throw new IllegalArgumentException("로또 숫자는 중복 되지 않아야 합니다."); + } + } + + public static Lotto generate() { + List numbers = Stream.generate(LottoNumber::generate) + .distinct() + .limit(LOTTO_NUMBER_COUNT) + .collect(Collectors.toList()); + return new Lotto(numbers); + } + + public boolean contains(final LottoNumber number) { + return numbers.contains(number); + } + + @Override + public String toString() { + return numbers.stream() + .map(LottoNumber::toString) + .collect(Collectors.joining(", ", "[", "]")); + } +} diff --git a/src/main/java/domain/LottoMachine.java b/src/main/java/domain/LottoMachine.java new file mode 100644 index 00000000..da02949c --- /dev/null +++ b/src/main/java/domain/LottoMachine.java @@ -0,0 +1,16 @@ +package domain; + +import domain.Lotto; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class LottoMachine { + + public static List generateLottos(final int count) { + return Stream.generate(Lotto::generate) + .limit(count) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/domain/LottoNumber.java b/src/main/java/domain/LottoNumber.java new file mode 100644 index 00000000..4358319e --- /dev/null +++ b/src/main/java/domain/LottoNumber.java @@ -0,0 +1,33 @@ +package domain; + +import java.util.Random; + +public record LottoNumber(int number) implements Comparable { + private static final int MIN_NUMBER = 1; + private static final int MAX_NUMBER = 45; + private static final Random random = new Random(); + + public LottoNumber { + valdateNumber(number); + } + + private void valdateNumber(final int number) { + if (number < MIN_NUMBER || number > MAX_NUMBER) { + throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 값이어야 합니다."); + } + } + + public static LottoNumber generate() { + return new LottoNumber(random.nextInt(MAX_NUMBER - MIN_NUMBER + 1) + MIN_NUMBER); + } + + @Override + public String toString() { + return String.valueOf(number); + } + + @Override + public int compareTo(final LottoNumber other) { + return Integer.compare(this.number, other.number); + } +} diff --git a/src/main/java/domain/LottoPrize.java b/src/main/java/domain/LottoPrize.java new file mode 100644 index 00000000..e3ade005 --- /dev/null +++ b/src/main/java/domain/LottoPrize.java @@ -0,0 +1,42 @@ +package domain; + +import java.util.Arrays; + +public enum LottoPrize { + MISS(0, 0, false), + FIFTH(3, 5_000, false), + FOURTH(4, 50_000, false), + THIRD(5, 1_500_000, false), + SECOND(5, 30_000_000, true), + FIRST(6, 2_000_000_000, false); + + private final int matchCount; + private final int prize; + private final boolean isMatchBonus; + + LottoPrize(final int matchCount, final int prize, final boolean isMatchBonus) { + this.matchCount = matchCount; + this.prize = prize; + this.isMatchBonus = isMatchBonus; + } + + public static LottoPrize matchPrize(final int matchCount, final boolean isMatchBonus) { + return Arrays.stream(LottoPrize.values()) + .filter(prize -> prize.matchCount == matchCount) + .filter(prize -> prize.isMatchBonus == isMatchBonus) + .findFirst() + .orElse(MISS); + } + + public int getMatchCount() { + return matchCount; + } + + public int getPrize() { + return prize; + } + + public boolean isMatchBonus() { + return isMatchBonus; + } +} diff --git a/src/main/java/domain/LottoResult.java b/src/main/java/domain/LottoResult.java new file mode 100644 index 00000000..ad498888 --- /dev/null +++ b/src/main/java/domain/LottoResult.java @@ -0,0 +1,27 @@ +package domain; + +import java.util.List; + +public class LottoResult { + private final List prizes; + + public LottoResult(final List prizes) { + this.prizes = prizes; + } + + public int getTotalPrize() { + return prizes.stream() + .mapToInt(LottoPrize::getPrize) + .sum(); + } + + public double getEarningRate(final int purchaseAmount) { + return (double) getTotalPrize() / purchaseAmount; + } + + public int getCountByPrize(final LottoPrize prize) { + return (int) prizes.stream() + .filter(p -> p == prize) + .count(); + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..e012360e --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,53 @@ +package view; + +import domain.Lotto; +import domain.LottoNumber; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class InputView { + private static final Scanner scanner = new Scanner(System.in); + + public static int getPurchaseAmount() { + System.out.println("구입 금액을 입력해 주세요."); + return Integer.parseInt(scanner.nextLine()); + } + + public static int getManualLottoCount() { + System.out.println("수동으로 구매할 로또 수를 입력해 주세요."); + return Integer.parseInt(scanner.nextLine()); + } + + public static List getManualLottos(final int count) { + System.out.println("수동으로 구매할 번호를 입력해 주세요."); + return IntStream.range(0, count) + .mapToObj(i -> getLottoNumbers()) + .collect(Collectors.toList()); + } + + public static Lotto getWinningLottoNumbers() { + System.out.println("지난 주 당첨 번호를 입력해 주세요."); + return getLottoNumbers(); + } + + private static Lotto getLottoNumbers() { + String input = scanner.nextLine(); + List numbers = Arrays.stream(input.split(",")) + .map(String::trim) + .map(Integer::parseInt) + .map(LottoNumber::new) + .collect(Collectors.toList()); + return new Lotto(numbers); + } + + public static LottoNumber getBonusNumber() { + System.out.println("보너스 볼을 입력해 주세요."); + int number = Integer.parseInt(scanner.nextLine()); + return new LottoNumber(number); + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000..d27f05c5 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,37 @@ +package view; + +import domain.Lotto; +import domain.LottoPrize; +import domain.LottoResult; + +import java.util.List; + +public class OutputView { + public static void printLottos(final List lottos, final int manualLottoCount, final int autoLottoCount) { + System.out.printf("수동으로 %d장, 자동으로 %d개를 구매했습니다.\n", manualLottoCount, autoLottoCount); + lottos.stream().skip(0).limit(manualLottoCount).forEach(System.out::println); + lottos.stream().skip(manualLottoCount).limit(autoLottoCount).forEach(System.out::println); + } + + public static void printResult(final LottoResult result, final int purchaseAmount) { + System.out.println("당첨 통계"); + System.out.println("---------"); + for (LottoPrize prize : LottoPrize.values()) { + int count = result.getCountByPrize(prize); + printPrizeStatics(prize, count); + } + double earningRate = result.getEarningRate(purchaseAmount); + System.out.printf("총 수익률은 %.2f입니다.%n", earningRate); + } + + private static void printPrizeStatics(final LottoPrize prize, final int statics) { + if (prize == LottoPrize.MISS) { + return; + } + if (prize.isMatchBonus()) { + System.out.printf("%d개 일치, 보너스 볼 일치(%d원) - %d개%n", prize.getMatchCount(), prize.getPrize(), statics); + return; + } + System.out.printf("%d개 일치 (%d원)- %d개%n", prize.getMatchCount(), prize.getPrize(), statics); + } +} diff --git a/src/test/java/domain/LottoMachineTest.java b/src/test/java/domain/LottoMachineTest.java new file mode 100644 index 00000000..709bf24b --- /dev/null +++ b/src/test/java/domain/LottoMachineTest.java @@ -0,0 +1,18 @@ +package domain; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LottoMachineTest { + + @Test + void generateLottos() { + int count = 5; + List lottos = LottoMachine.generateLottos(count); + assertThat(lottos.size()).isEqualTo(count); + assertThat(lottos.stream().distinct().count()).isEqualTo(count); + } +} diff --git a/src/test/java/domain/LottoNumberTest.java b/src/test/java/domain/LottoNumberTest.java new file mode 100644 index 00000000..0f3531c4 --- /dev/null +++ b/src/test/java/domain/LottoNumberTest.java @@ -0,0 +1,47 @@ +import domain.LottoNumber; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class LottoNumberTest { + + @Test + void validateNumber() { + // 유효한 숫자인 경우 + assertThat(new LottoNumber(1)).isNotNull(); + assertThat(new LottoNumber(45)).isNotNull(); + + // 유효하지 않은 숫자인 경우 + assertThatThrownBy(() -> new LottoNumber(0)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("로또 번호는 1부터 45 사이의 값이어야 합니다."); + assertThatThrownBy(() -> new LottoNumber(46)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("로또 번호는 1부터 45 사이의 값이어야 합니다."); + } + + @Test + void generate() { + LottoNumber number = LottoNumber.generate(); + assertThat(number.number()).isBetween(1, 45); + } + + @Test + void toString_test() { + LottoNumber number = new LottoNumber(5); + assertThat(number.toString()).isEqualTo("5"); + } + + @Test + void compareTo() { + LottoNumber num1 = new LottoNumber(1); + LottoNumber num2 = new LottoNumber(2); + LottoNumber num3 = new LottoNumber(3); + + assertThat(num1.compareTo(num2)).isNegative(); + assertThat(num2.compareTo(num1)).isPositive(); + assertThat(num2.compareTo(num3)).isNegative(); + assertThat(num2.compareTo(num2)).isZero(); + } +} diff --git a/src/test/java/domain/LottoTest.java b/src/test/java/domain/LottoTest.java new file mode 100644 index 00000000..f35e54b3 --- /dev/null +++ b/src/test/java/domain/LottoTest.java @@ -0,0 +1,57 @@ +package domain; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class LottoTest { + + @Test + void validateNumbers() { + // 로또 숫자가 6개인 경우 + List validNumbers = Arrays.asList( + new LottoNumber(1), new LottoNumber(2), new LottoNumber(3), + new LottoNumber(4), new LottoNumber(5), new LottoNumber(6) + ); + assertThat(new Lotto(validNumbers)).isNotNull(); + + // 로또 숫자가 6개가 아닌 경우 + List invalidNumbers = Arrays.asList( + new LottoNumber(1), new LottoNumber(2), new LottoNumber(3), + new LottoNumber(4), new LottoNumber(5) + ); + assertThatThrownBy(() -> new Lotto(invalidNumbers)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("로또 숫자는 6개여야 합니다."); + + // 로또 숫자에 중복이 있는 경우 + List duplicatedNumbers = Arrays.asList( + new LottoNumber(1), new LottoNumber(2), new LottoNumber(3), + new LottoNumber(4), new LottoNumber(5), new LottoNumber(5) + ); + assertThatThrownBy(() -> new Lotto(duplicatedNumbers)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("로또 숫자는 중복 되지 않아야 합니다."); + } + + @Test + void generate() { + Lotto lotto = Lotto.generate(); + assertThat(lotto.numbers().size()).isEqualTo(6); + assertThat(lotto.numbers().stream().distinct().count()).isEqualTo(6); + } + + @Test + void contains() { + Lotto lotto = new Lotto(Arrays.asList( + new LottoNumber(1), new LottoNumber(2), new LottoNumber(3), + new LottoNumber(4), new LottoNumber(5), new LottoNumber(6) + )); + assertThat(lotto.contains(new LottoNumber(1))).isTrue(); + assertThat(lotto.contains(new LottoNumber(7))).isFalse(); + } +}