diff --git a/README.md b/README.md index 5fa2560b46..d6f9ffd5e0 100644 --- a/README.md +++ b/README.md @@ -1 +1,44 @@ # java-lotto-precourse +### 간단한 로또 발매기 구현 +- 로또 구입 금액을 입력 받는다. 로또 1장의 가격은 1,000원이다. 구입 금액에 해당하는 만큼 로또를 발행해야 한다. + - 구입 금액은 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다. + > 구입금액을 입력해 주세요. + 8000 +- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. + - 로또 번호의 숫자 범위는 1~45까지이다. +- 발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다. + > 8개를 구매했습니다. + [8, 21, 23, 41, 42, 43] + [3, 5, 11, 16, 32, 38] + [7, 11, 16, 35, 36, 44] + [1, 8, 11, 31, 41, 42] + [13, 14, 16, 38, 42, 45] + [7, 11, 30, 40, 42, 43] + [2, 13, 22, 32, 38, 45] + [1, 3, 5, 14, 22, 45] +- 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다. +- 보너스 번호를 입력 받는다. + - 당첨 번호는 중복되지 않는 숫자 6개이고, 보너스 번호 1개도 중복되지 않아야 한다. + > 당첨 번호를 입력해 주세요. + 1,2,3,4,5,6 + 보너스 번호를 입력해 주세요. + 7 +- 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다. + - 1등: 6개 번호 일치 / 2,000,000,000원 + - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 + - 3등: 5개 번호 일치 / 1,500,000원 + - 4등: 4개 번호 일치 / 50,000원 + - 5등: 3개 번호 일치 / 5,000원 +- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다. +- 수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%) + > 당첨 통계 + `---` + 3개 일치 (5,000원) - 1개 + 4개 일치 (50,000원) - 0개 + 5개 일치 (1,500,000원) - 0개 + 5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 + 6개 일치 (2,000,000,000원) - 0개 + 총 수익률은 62.5%입니다. +- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. + - Exception이 아닌 IllegalArgumentException, IllegalStateException 등과 같은 명확한 유형을 처리한다. + > [ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다. \ No newline at end of file diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922ba4..fc8d59d9db 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,160 @@ package lotto; +import camp.nextstep.edu.missionutils.Console; + +import java.util.ArrayList; +import java.util.List; + public class Application { + private static final int LOTTO_PRICE = 1000; + public static void main(String[] args) { - // TODO: 프로그램 구현 + int purchaseAmount = getPurchaseAmount(); + int numberOfLottos = purchaseAmount / LOTTO_PRICE; + System.out.println("\n" + numberOfLottos + "개를 구매했습니다."); + + List lottos = new ArrayList<>(); + for (int i = 0; i < numberOfLottos; i++) { + lottos.add(Lotto.generateRandomLotto()); + } + + for (Lotto lotto : lottos) { + System.out.println(lotto); + } + + List winningNumbers = inputWinningNumbers(); + int bonusNumber = inputBonusNumber(winningNumbers); + printWinningStatistics(lottos, winningNumbers, bonusNumber, purchaseAmount); + } + + private static int getPurchaseAmount() { + while (true) { + try { + System.out.println("구입금액을 입력해 주세요."); + String input = Console.readLine(); + int amount = Integer.parseInt(input); + + if (amount % LOTTO_PRICE != 0) { + throw new IllegalArgumentException(); + } + return amount; + } catch (NumberFormatException e) { + System.out.println("[ERROR] 구입 금액은 숫자로 입력해야 합니다."); + } catch (IllegalArgumentException e) { + System.out.println("[ERROR] 구입 금액은 1,000원 단위로 입력해야 합니다."); + } + } + } + + private static List inputWinningNumbers() { + List winningNumbers = new ArrayList<>(); + while (winningNumbers.size() < 6) { + try { + System.out.println("\n당첨 번호를 입력해 주세요."); + String winningInput = Console.readLine(); + winningNumbers = parseNumbers(winningInput); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + return winningNumbers; + } + + private static int inputBonusNumber(List winningNumbers) { + while (true) { + try { + System.out.println("\n보너스 번호를 입력해 주세요."); + int bonusNumber = Integer.parseInt(Console.readLine()); + validateBonusNumber(winningNumbers, bonusNumber); + return bonusNumber; // 유효한 보너스 번호가 입력된 경우 반환 + } catch (NumberFormatException e) { + System.out.println("[ERROR] 보너스 번호는 숫자로 입력해야 합니다."); + } + } + } + + private static List parseNumbers(String input) { + String[] numbers = input.split(","); + List winningNumbers = new ArrayList<>(); + + for (String number : numbers) { + int num = Integer.parseInt(number.trim()); + validateLottoNumber(num); // 각 번호에 대해 유효성 검사 + winningNumbers.add(num); + } + return winningNumbers; + } + + private static void validateLottoNumber(int number) { + if (number < 1 || number > 45) { + throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."); + } + } + + private static void validateBonusNumber(List winningNumbers, int bonusNumber) { + if (bonusNumber < 1 || bonusNumber > 45) { + throw new IllegalArgumentException("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다."); + } + if (winningNumbers.contains(bonusNumber)) { + throw new IllegalArgumentException("[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다."); + } + } + + private static void printWinningStatistics(List lottos, List winningNumbers, int bonusNumber, int purchaseAmount) { + int[] counts = new int[5]; // [3개, 4개, 5개, 5개+보너스, 6개] + int totalPrize = 0; + + for (Lotto lotto : lottos) { + int matchCount = countMatches(lotto.getNumbers(), winningNumbers); + boolean bonusMatch = lotto.getNumbers().contains(bonusNumber); + + totalPrize += calculatePrize(counts, matchCount, bonusMatch); + } + + // 출력 + System.out.println("\n당첨 통계"); + System.out.println("---"); + printPrizeCounts(counts); + + double profitRate = (double) totalPrize / purchaseAmount * 100; + System.out.printf("총 수익률은 %.1f%%입니다.\n", profitRate); + } + + private static int calculatePrize(int[] counts, int matchCount, boolean bonusMatch) { + if (matchCount == 6) { + counts[4]++; + return 2000000000; + } + if (matchCount == 5 && bonusMatch) { + counts[3]++; + return 30000000; + } + if (matchCount == 5) { + counts[2]++; + return 1500000; + } + if (matchCount == 4) { + counts[1]++; + return 50000; + } + if (matchCount == 3) { + counts[0]++; + return 5000; + } + return 0; // 당첨되지 않은 경우 + } + + private static void printPrizeCounts(int[] counts) { + System.out.printf("3개 일치 (5,000원) - %d개\n", counts[0]); + System.out.printf("4개 일치 (50,000원) - %d개\n", counts[1]); + System.out.printf("5개 일치 (1,500,000원) - %d개\n", counts[2]); + System.out.printf("5개 일치, 보너스 볼 일치 (30,000,000원) - %d개\n", counts[3]); + System.out.printf("6개 일치 (2,000,000,000원) - %d개\n", counts[4]); + } + + private static int countMatches(List userNumbers, List winningNumbers) { + return (int) userNumbers.stream() + .filter(winningNumbers::contains) + .count(); } } diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java index 88fc5cf12b..8fb2fa84e4 100644 --- a/src/main/java/lotto/Lotto.java +++ b/src/main/java/lotto/Lotto.java @@ -1,8 +1,17 @@ package lotto; +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; public class Lotto { + private static final int LOTTO_NUMBER_COUNT = 6; + private static final int MIN_NUMBER = 1; + private static final int MAX_NUMBER = 45; + private final List numbers; public Lotto(List numbers) { @@ -11,10 +20,26 @@ public Lotto(List numbers) { } private void validate(List numbers) { - if (numbers.size() != 6) { + if (numbers.size() != LOTTO_NUMBER_COUNT) { throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다."); } + Set uniqueNumbers = new HashSet<>(numbers); + if (uniqueNumbers.size() != LOTTO_NUMBER_COUNT) { + throw new IllegalArgumentException("[ERROR] 로또 번호는 중복될 수 없습니다."); + } + } + + public static Lotto generateRandomLotto() { + List randomNumbers = Randoms.pickUniqueNumbersInRange(MIN_NUMBER, MAX_NUMBER, LOTTO_NUMBER_COUNT); + return new Lotto(randomNumbers.stream().sorted().collect(Collectors.toList())); + } + + public List getNumbers() { + return numbers; } - // TODO: 추가 기능 구현 + @Override + public String toString() { + return numbers.toString(); + } } diff --git a/src/test/java/lotto/ApplicationTest.java b/src/test/java/lotto/ApplicationTest.java index a15c7d1f52..d7c54267d5 100644 --- a/src/test/java/lotto/ApplicationTest.java +++ b/src/test/java/lotto/ApplicationTest.java @@ -51,6 +51,8 @@ class ApplicationTest extends NsTest { assertSimpleTest(() -> { runException("1000j"); assertThat(output()).contains(ERROR_MESSAGE); + runException("1500"); + assertThat(output()).contains(ERROR_MESSAGE); }); } diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 309f4e50ae..d502afaf2a 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/LottoTest.java @@ -21,5 +21,9 @@ class LottoTest { .isInstanceOf(IllegalArgumentException.class); } - // TODO: 추가 기능 구현에 따른 테스트 코드 작성 + @Test + void 로또_번호의_개수가_6개보다_작으면_예외가_발생한다() { + assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5))) + .isInstanceOf(IllegalArgumentException.class); + } }