diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 00000000..5870c3e6 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,13 @@ +import model.LottoGame; +import view.InputView; +import view.OutputView; + +public class Application { + + public static void main(String[] args) { + InputView inputView = new InputView(); + OutputView outputView = new OutputView(); + LottoGame lottoGame = new LottoGame(inputView, outputView); + lottoGame.play(); + } +} diff --git a/src/main/java/model/Lotto.java b/src/main/java/model/Lotto.java new file mode 100644 index 00000000..5f2d5954 --- /dev/null +++ b/src/main/java/model/Lotto.java @@ -0,0 +1,11 @@ +package model; + +import java.util.List; + +public record Lotto(List numbers) { + + @Override + public String toString() { + return numbers.toString(); + } +} diff --git a/src/main/java/model/LottoGame.java b/src/main/java/model/LottoGame.java new file mode 100644 index 00000000..1a4a9e8b --- /dev/null +++ b/src/main/java/model/LottoGame.java @@ -0,0 +1,71 @@ +package model; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import view.InputView; +import view.OutputView; + +public class LottoGame { + + private final InputView inputView; + private final OutputView outputView; + + private final Integer LOTTO_PRICE = 1_000; + + public LottoGame(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public void play() { + Integer price = inputView.readPrice(); + Integer totalCount = price / LOTTO_PRICE; + Integer manualCount = inputView.readManualCount(); + Integer autoCount = totalCount - manualCount; + List manualLottos = inputView.readManualLottos(manualCount); + List autoLottos = RandomLottoGenerator.generateLottoNumbers(autoCount); + outputView.printMyLottos(manualLottos, autoLottos); + Lotto winningLotto = inputView.readWinningLotto(); + Integer bonusNumber = inputView.readBonusNumber(winningLotto); + List myLottos = concatMyLottos(manualLottos, autoLottos); + Map myResult = calculateResult(myLottos, winningLotto, bonusNumber); + outputView.printResult(myResult); + Integer profit = computeProfit(myResult); + outputView.printRateOfReturn(LOTTO_PRICE * totalCount, profit); + } + + private Integer computeProfit(Map result) { + return Arrays.stream(Rank.values()) + .filter(it -> it.matchCount() >= 3) + .mapToInt(it -> result.getOrDefault(it, 0) * it.price()) + .sum(); + } + + private Map calculateResult(List myLottos, Lotto winningLotto, Integer bonusNumber) { + Map result = new HashMap<>(); + myLottos.stream().forEach(lotto -> { + Integer count = computeMatchCount(lotto, winningLotto); + Boolean containsBonusNumber = lotto.numbers().contains(bonusNumber); + Rank rank = Rank.of(count, containsBonusNumber); + result.put(rank, result.getOrDefault(rank, 0) + 1); + }); + return result; + } + + private Integer computeMatchCount(Lotto myLotto, Lotto winningLotto) { + return Math.toIntExact(myLotto.numbers().stream() + .filter(winningLotto.numbers()::contains) + .count()); + } + + private List concatMyLottos(List manualLottos, List autoLottos) { + return Stream.concat( + manualLottos.stream(), + autoLottos.stream() + ).toList(); + } +} diff --git a/src/main/java/model/LottoNumber.java b/src/main/java/model/LottoNumber.java new file mode 100644 index 00000000..62d695d6 --- /dev/null +++ b/src/main/java/model/LottoNumber.java @@ -0,0 +1,9 @@ +package model; + +public record LottoNumber(int number) { + + @Override + public String toString() { + return String.valueOf(number); + } +} diff --git a/src/main/java/model/RandomLottoGenerator.java b/src/main/java/model/RandomLottoGenerator.java new file mode 100644 index 00000000..a12b9e96 --- /dev/null +++ b/src/main/java/model/RandomLottoGenerator.java @@ -0,0 +1,26 @@ +package model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class RandomLottoGenerator { + + public static List generateLottoNumbers(Integer count) { + return IntStream.range(0, count) + .mapToObj(i -> generateLottoNumber()) + .toList(); + } + + private static Lotto generateLottoNumber() { + Set lottoNumbers = IntStream.generate(() -> ThreadLocalRandom.current().nextInt(1, 46)) + .distinct() + .limit(6) + .mapToObj(it -> new LottoNumber(it)) + .collect(Collectors.toSet()); + return new Lotto(new ArrayList<>(lottoNumbers)); + } +} diff --git a/src/main/java/model/Rank.java b/src/main/java/model/Rank.java new file mode 100644 index 00000000..df40415c --- /dev/null +++ b/src/main/java/model/Rank.java @@ -0,0 +1,43 @@ +package model; + +import java.util.Arrays; + +public enum Rank { + _LAST(0, 0, false), + _5TH(5_000, 3, false), + _4TH(50_000, 4, false), + _3TH(1_500_000, 5, false), + _2TH(30_000_000, 5, true), + _1TH(2_000_000_000, 6, false), + ; + + private final int price; + private final int matchCount; + private final boolean containsBonus; + + Rank(int price, int matchCount, boolean containsBonus) { + this.price = price; + this.matchCount = matchCount; + this.containsBonus = containsBonus; + } + + public static Rank of(int matchCount, boolean containsBonus) { + return Arrays.stream(Rank.values()) + .filter(it -> it.matchCount == matchCount) + .filter(it -> matchCount == 5? it.containsBonus == containsBonus : true) + .findFirst() + .orElse(_LAST); + } + + public int price() { + return price; + } + + public int matchCount() { + return matchCount; + } + + public boolean containsBonus() { + return containsBonus; + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..29fcc5ef --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,60 @@ +package view; + +import java.util.List; +import java.util.Scanner; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import model.Lotto; +import model.LottoNumber; + +public class InputView { + + private static final Scanner SCANNER = new Scanner(System.in); + + public int readPrice() { + System.out.println("구입금액을 입력해 주세요."); + return readNextInt(); + } + + public int readBonusNumber(Lotto lotto) { + System.out.println("보너스 볼을 입력해 주세요."); + int bonusNumber = readNextInt(); + if (lotto.numbers().contains(bonusNumber)) { + throw new RuntimeException("이미 뽑힌 볼입니다."); + } + return bonusNumber; + } + + public List readManualLottos(Integer count) { + System.out.println("수동으로 구매할 번호를 입력해 주세요."); + return IntStream.range(0, count) + .mapToObj(i -> readLotto()) + .toList(); + } + + public int readManualCount() { + System.out.println("수동으로 구매할 로또 수를 입력해 주세요."); + return readNextInt(); + } + + public Lotto readWinningLotto() { + System.out.println("지난 주 당첨 번호를 입력해 주세요."); + return readLotto(); + } + + private Lotto readLotto() { + String input = SCANNER.nextLine(); + return new Lotto( + Stream.of(input.replace(" ","").split(",")) + .map(it -> new LottoNumber(Integer.parseInt(it))) + .toList() + ); + } + + private int readNextInt() { + Integer input = SCANNER.nextInt(); + SCANNER.nextLine(); + return input; + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000..324f294a --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,46 @@ +package view; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import model.Lotto; +import model.Rank; + +public class OutputView { + + public void printMyLottos(List manual, List auto) { + System.out.println(String.format("수동으로 %d장, 자동으로 %d개를 구매했습니다.", manual.size(), auto.size())); + manual.stream().forEach(System.out::println); + auto.stream().forEach(System.out::println); + } + + public void printRateOfReturn(Integer price, Integer profit) { + Double rateOfReturn = ((double) profit / price) * 100.0d; + System.out.println(String.format("총 수익률은 %.2f입니다.", rateOfReturn)); + } + + public void printResult(Map result) { + System.out.println("당첨 통계"); + System.out.println("---------"); + Arrays.stream(Rank.values()) + .filter(rank -> rank.matchCount() != 0) + .forEach(rank -> printResult(rank, result.getOrDefault(rank, 0))); + } + + private void printResult(Rank rank, Integer count) { + String bonus = getBonusText(rank); + System.out.println(String.format("%d개 일치%s(%d원)- %d개", + rank.matchCount(), + bonus, + rank.price(), + count)); + } + + private String getBonusText(Rank rank) { + if (rank.containsBonus()) { + return ", 보너스 볼 일치"; + } + return " "; + } +}