diff --git a/README.md b/README.md index 8a4f22ed0..694f4632d 100644 --- a/README.md +++ b/README.md @@ -1 +1,106 @@ -# java-planetlotto-precourse +# ๐Ÿ’ป ์šฐํ…Œ์ฝ” 8๊ธฐ ์ตœ์ข… - ํ–‰์„ฑ ๋กœ๋˜ +Console์„ ์ด์šฉํ•˜์—ฌ ์šฐํ…Œ์ฝ” ๋กœ๋˜ ๋ฐœ๋งค๊ธฐ์ธ ํ–‰์„ฑ ๋กœ๋˜๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. +๊ตฌ์ž… ๊ธˆ์•ก์— ๋งž์ถฐ ๋กœ๋˜๋ฅผ ๋ฐœํ–‰ํ•˜๊ณ , ๋‹น์ฒจ ๋‚ด์—ญ์„ ์ถœ๋ ฅํ•œ๋‹ค. + +--- + +## **๐Ÿ“ฅ ์ž…์ถœ๋ ฅ ๋ช…์„ธ** + +### โ–ซ ์ž…๋ ฅ +1. ๋กœ๋˜ ๊ตฌ์ž… ๊ธˆ์•ก์„ ์ž…๋ ฅ ๋ฐ›๋Š”๋‹ค. +2. ๋‹น์ฒจ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ ๋ฐ›๋Š”๋‹ค. ๋ฒˆํ˜ธ๋Š” ์‰ผํ‘œ(,)๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ตฌ๋ถ„ํ•œ๋‹ค. +3. ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ ๋ฐ›๋Š”๋‹ค. + +### โ–ซ ์ถœ๋ ฅ + +1. ๋ฐœํ–‰ํ•œ ๋กœ๋˜ ์ˆ˜๋Ÿ‰ ๋ฐ ๋ฒˆํ˜ธ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜์—ฌ ๋ณด์—ฌ์ค€๋‹ค. +2. ๋‹น์ฒจ ๋‚ด์—ญ์„ ์ถœ๋ ฅํ•œ๋‹ค. +3. ์˜ˆ์™ธ ์ƒํ™ฉ ์‹œ ์—๋Ÿฌ ๋ฌธ๊ตฌ๋ฅผ ์ถœ๋ ฅํ•ด์•ผ ํ•œ๋‹ค. ๋‹จ, ์—๋Ÿฌ ๋ฌธ๊ตฌ๋Š” "[ERROR]"๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•œ๋‹ค. + + +### โ–ซ ์‹คํ–‰ ์˜ˆ์‹œ + +``` +๊ตฌ์ž…๊ธˆ์•ก์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. +1000 + +2๊ฐœ๋ฅผ ๊ตฌ๋งคํ–ˆ์Šต๋‹ˆ๋‹ค. +[8, 11, 13, 21, 22] +[1, 3, 6, 14, 22] + +๋‹น์ฒจ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. +1, 2, 3, 4, 5 + +๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. +6 + +๋‹น์ฒจ ํ†ต๊ณ„ +--- +5๊ฐœ ์ผ์น˜ (100,000,000์›) - 0๊ฐœ +4๊ฐœ ์ผ์น˜, ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ ์ผ์น˜ (10,000,000์›) - 0๊ฐœ +4๊ฐœ ์ผ์น˜ (1,500,000์›) - 0๊ฐœ +3๊ฐœ ์ผ์น˜, ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ ์ผ์น˜ (500,000์›) - 0๊ฐœ +2๊ฐœ ์ผ์น˜, ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ ์ผ์น˜ (5,000์›) - 1๊ฐœ +0๊ฐœ ์ผ์น˜ (0์›) - 1๊ฐœ +``` +--- + +## ๐Ÿ“„๊ธฐ๋Šฅ ๊ตฌํ˜„ ๋ชฉ๋ก + +### 1) ์ž…๋ ฅ ํ๋ฆ„ + +- [x] InputView, OutputView ์ž…์ถœ๋ ฅ ๊ตฌ์„ฑ๋ฐฉ์‹ ํ™•์ธ ๋ฐ ์ดˆ๊ธฐ ํŒจํ‚ค์ง€ ๊ตฌ์„ฑ +- [x] ๊ตฌ์ž…๊ธˆ์•ก ์ž…๋ ฅ๋ฐ›์€๊ฑฐ ๊ฒ€์ฆ +- [x] ๋‹น์ฒจ๋ฒˆํ˜ธ ์ž…๋ ฅ๋ฐ›์€๊ฑฐ ๊ฒ€์ฆ +- [x] ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ ์ž…๋ ฅ๋ฐ›์€๊ฑฐ ๊ฒ€์ฆ + +### 2) ์ฃผ์š” ๊ธฐ๋Šฅ +- [x] Lotto ๋„๋ฉ”์ธ ๊ตฌํ˜„ +- [x] Lottos ๋„๋ฉ”์ธ ๊ตฌํ˜„ +- [x] LottoGenerator ๊ตฌํ˜„ +- [x] Rank(Enum) ๊ตฌํ˜„ +- [x] WinningNumbers ๋„๋ฉ”์ธ ๊ตฌํ˜„ +- [x] ๋กœ๋˜ ์ƒ์„ฑ ๊ธฐ๋Šฅ ๊ตฌํ˜„ +- [x] WinCountCalculator ๊ตฌํ˜„(๋กœ๋˜ ๋‹น์ฒจ ํšŸ์ˆ˜ ๊ณ„์‚ฐ) +- [x] ๋กœ๋˜ ๋‹น์ฒจ ๋‚ด์—ญ ๊ธฐ๋Šฅ ๊ตฌํ˜„ + + +### 3) ์˜ˆ์™ธ ๋ฐ ์ฃผ์˜์‚ฌํ•ญ ์ฒดํฌ +- [x] ๋กœ๋˜ ๋ฒˆํ˜ธ์˜ ์ˆซ์ž ๋ฒ”์œ„๋Š” 1~30๊นŒ์ง€์ด๋‹ค. +- [x] 1๊ฐœ์˜ ๋กœ๋˜๋ฅผ ๋ฐœํ–‰ํ•  ๋•Œ ์ค‘๋ณต๋˜์ง€ ์•Š๋Š” 5๊ฐœ์˜ ์ˆซ์ž๋ฅผ ๋ฝ‘๋Š”๋‹ค. +- [x] ๋กœ๋˜ ๊ตฌ์ž… ๊ธˆ์•ก์„ ์ž…๋ ฅํ•˜๋ฉด ๊ตฌ์ž… ๊ธˆ์•ก์— ํ•ด๋‹นํ•˜๋Š” ๋งŒํผ ๋กœ๋˜๋ฅผ ๋ฐœํ–‰ํ•ด์•ผ ํ•œ๋‹ค. +- [x] ๋กœ๋˜ 1์žฅ์˜ ๊ฐ€๊ฒฉ์€ 500์›์ด๋‹ค. +- [x] ์‚ฌ์šฉ์ž๊ฐ€ ์ž˜๋ชป๋œ ๊ฐ’์„ ์ž…๋ ฅํ•  ๊ฒฝ์šฐ IllegalArgumentException๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ , "[ERROR]"๋กœ ์‹œ์ž‘ํ•˜๋Š” ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅ ํ›„ ๊ทธ ๋ถ€๋ถ„๋ถ€ํ„ฐ ์ž…๋ ฅ์„ ๋‹ค์‹œ ๋ฐ›๋Š”๋‹ค. +- [x] ๊ตฌ์ž… ๊ธˆ์•ก์€ 500์› ๋‹จ์œ„๋กœ ์ž…๋ ฅ ๋ฐ›์œผ๋ฉฐ 500์›์œผ๋กœ ๋‚˜๋ˆ„์–ด ๋–จ์–ด์ง€์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์˜ˆ์™ธ ์ฒ˜๋ฆฌํ•œ๋‹ค. +- [x] ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜์—ฌ ๋ณด์—ฌ์ค€๋‹ค. +- [x] ์—๋Ÿฌ ๋ฌธ๊ตฌ๋Š” "[ERROR]"๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•œ๋‹ค. +- [x] ์ž…๋ ฅ/์ถœ๋ ฅ ์—ญํ• ์€ ์ œ๊ณต๋œ InputView, OutputView์—์„œ ์ˆ˜ํ–‰ํ•˜๋ฉฐ ๊ธฐ์กด ๋ฉ”์„œ๋“œ๋ฅผ ์ˆ˜์ •, ์‚ญ์ œํ•  ์ˆ˜ ์—†๋‹ค. +- [x] OutputView์— ์žˆ๋Š” printErrorMessage ์‚ฌ์šฉํ•˜๊ธฐ + +### 4) ๋„์ „ ๋ชฉํ‘œ +- ๊ธฐ๋ณธ ์š”๊ตฌ ์‚ฌํ•ญ์„ ๋ชจ๋‘ ์ถฉ์กฑํ•œ ํ›„, ์•„๋ž˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜์—ฌ ๋„์ „ํ•˜์„ธ์š”. ๋‹จ, ๋„์ „ ๊ณผ์ œ ์ˆ˜ํ–‰ ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด ๊ธฐ๋ณธ ๊ธฐ๋Šฅ์€ ๋ฐ˜๋“œ์‹œ ์ž‘๋™ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +- ๋„์ „ ๋ฐฉํ–ฅ: + - [x] ๋ฆฌํŒฉํ„ฐ๋ง: ์ž‘๋™์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๋ฉด์„œ ์ฝ”๋“œ ํ’ˆ์งˆ์„ ๋†’์ด๋Š” ๋ฐฉํ–ฅ์„ ๋ชฉํ‘œ๋กœ ํ•œ๋‹ค. +- ์ƒ์„ธ ๊ตฌํ˜„ ๋ชฉํ‘œ: + - [x] ๋งค์ง ๋„˜๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. + - [x] ๋„๋ฉ”์ธ์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์œผ๋ฉด ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•œ๋‹ค. + - [x] private, static ์˜๋ฏธ์— ๋งž๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ + - [x] ์ด์ค‘ ๊ฒ€์ฆ(validate) ๊ตฌํ˜„ + - [x] ๋ชจ๋“  ์ฝ”๋“œ ์ •๋ ฌ + - ์ƒ์„ธ ๊ณผ์ •์€ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€๋ฅผ ํ™•์ธํ•˜์„ธ์š”. + + +### 5) ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ์‚ฌํ•ญ ์ฒดํฌ +- [x] ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ์—์„œ ๋‹ฌ๋ฆฌ ๋ช…์‹œํ•˜์ง€ ์•Š๋Š” ํ•œ ํŒŒ์ผ, ํŒจํ‚ค์ง€ ์ด๋ฆ„์„ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์ด๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค. +- [x] ์ž๋ฐ” ์ฝ”๋“œ ์ปจ๋ฒค์…˜์„ ์ง€ํ‚ค๋ฉด์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•œ๋‹ค. +- [x] ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณต๋˜๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•ด์•ผ ํ•œ๋‹ค. + + + +### 6) ํ…Œ์ŠคํŠธ ๊ธฐ๋Šฅ ๋ชฉ๋ก ์ฒดํฌ +- [x] ๊ธฐ๋Šฅํ…Œ์ŠคํŠธ: ์ „์ฒด ๊ธฐ๋Šฅ ๊ตฌํ˜„: ("1000", "1,2,3,4,5", "6") ์ž…๋ ฅ ์‹œ ์‹คํ–‰์˜ˆ์‹œ์— ๋งž๊ฒŒ ์ถœ๋ ฅ +- [x] ์˜ˆ์™ธํ…Œ์ŠคํŠธ: ๊ตฌ์ž…๊ธˆ์•ก("500j") ์ž…๋ ฅ ์‹œ ์—๋Ÿฌ๋ฐœ์ƒ +- [x] ์—๋Ÿฌ ํ…Œ์ŠคํŠธ: ๋กœ๋˜ ๋ฒˆํ˜ธ์˜ ๊ฐœ์ˆ˜๊ฐ€ 5๊ฐœ๊ฐ€ ๋„˜์–ด๊ฐ€๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒ +- [x] ์—๋Ÿฌ ํ…Œ์ŠคํŠธ: ๋กœ๋˜ ๋ฒˆํ˜ธ์— ์ค‘๋ณต๋œ ์ˆซ์ž๊ฐ€ ์žˆ์œผ๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒ +- [x] Rank ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ +- [x] WinningNumbers ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ \ No newline at end of file diff --git a/src/main/java/planetlotto/Application.java b/src/main/java/planetlotto/Application.java index 27d0a8f96..f77ad05db 100644 --- a/src/main/java/planetlotto/Application.java +++ b/src/main/java/planetlotto/Application.java @@ -1,7 +1,9 @@ package planetlotto; +import planetlotto.controller.LottoController; + public class Application { public static void main(String[] args) { - // TODO: ํ”„๋กœ๊ทธ๋žจ ๊ตฌํ˜„ + new LottoController().run(); } } diff --git a/src/main/java/planetlotto/controller/LottoController.java b/src/main/java/planetlotto/controller/LottoController.java new file mode 100644 index 000000000..1a992493e --- /dev/null +++ b/src/main/java/planetlotto/controller/LottoController.java @@ -0,0 +1,76 @@ +package planetlotto.controller; + +import planetlotto.domain.*; +import planetlotto.support.validator.BonusNumber; +import planetlotto.support.validator.PurchaseAmount; +import planetlotto.support.validator.WinningNumber; +import planetlotto.view.InputView; +import planetlotto.view.OutputView; + +import java.util.List; +import java.util.Map; + +public class LottoController { + private final LottoGenerator lottoGenerator = new LottoGenerator(); + private final CountByRank countByRank = new CountByRank(); + + public void run() { + int purchaseAmount = readPurchaseAmountWithRetry(); + + Lottos lottos = lottoGenerator.generate(purchaseAmount / 500); + OutputView.printPurchasedLottos(lottos.getLottosAs2DList()); + + List winning = readWinningNumbersWithRetry(); + + int bonusNumber = readBonusNumberWithRetry(); + + WinningNumbers winningNumbers = new WinningNumbers(winning, bonusNumber); + + Map ranks = ResultCalculator.tallyRanks(lottos, winningNumbers); + + Map countsByrank = countByRank.calculate(ranks); + + OutputView.printResult(countsByrank); + + + } + + + private int readPurchaseAmountWithRetry() { + while (true) { + try { + int purchaseAmount = InputView.askAmount(); + PurchaseAmount.validate(purchaseAmount); + return purchaseAmount; + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e.getMessage()); + } + } + } + + private List readWinningNumbersWithRetry() { + while (true) { + try { + List winning = InputView.askWinningLotto(); + WinningNumber.validate(winning); //์ž…๋ ฅ ๊ฒ€์ฆ + + return winning; + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e.getMessage()); + } + } + } + + private int readBonusNumberWithRetry() { + while (true) { + try { + int bonusNumber = InputView.askBonusNumber(); + BonusNumber.validate(bonusNumber); //์ž…๋ ฅ ๊ฒ€์ฆ + return bonusNumber; + + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e.getMessage()); + } + } + } +} diff --git a/src/main/java/planetlotto/domain/CountByRank.java b/src/main/java/planetlotto/domain/CountByRank.java new file mode 100644 index 000000000..2693a9eaf --- /dev/null +++ b/src/main/java/planetlotto/domain/CountByRank.java @@ -0,0 +1,20 @@ +package planetlotto.domain; + +import java.util.HashMap; +import java.util.Map; + +public class CountByRank { + + public Map calculate(Map ranks) { + Map countsByRank = new HashMap<>(); + + countsByRank.put(1, ranks.getOrDefault(Rank.FIRST, 0)); + countsByRank.put(2, ranks.getOrDefault(Rank.SECOND, 0)); + countsByRank.put(3, ranks.getOrDefault(Rank.THIRD, 0)); + countsByRank.put(4, ranks.getOrDefault(Rank.FOURTH, 0)); + countsByRank.put(5, ranks.getOrDefault(Rank.FIFTH, 0)); + countsByRank.put(0, ranks.getOrDefault(Rank.NONE, 0)); + + return countsByRank; + } +} diff --git a/src/main/java/planetlotto/domain/Lotto.java b/src/main/java/planetlotto/domain/Lotto.java new file mode 100644 index 000000000..8173a8fe4 --- /dev/null +++ b/src/main/java/planetlotto/domain/Lotto.java @@ -0,0 +1,66 @@ +package planetlotto.domain; + +import java.util.*; + +public class Lotto { + // ์ƒ์ˆ˜ + private static final int LOTTO_SIZE = 5; + private static final int MIN_NUMBER = 1; + private static final int MAX_NUMBER = 30; + + private final List numbers; // ์˜ค๋ฆ„์ฐจ์ˆœ ๋ถˆ๋ณ€ ๋ฆฌ์ŠคํŠธ + + public Lotto(List numbers) { + validate(numbers); + this.numbers = toSortedUnmodifiable(numbers); + } + + private void validate(List numbers) { + validateNull(numbers); + validateSize(numbers); + validateRange(numbers); + validateDuplicate(numbers); + } + + private static void validateNull(List numbers) { + if (numbers == null) { + throw new IllegalArgumentException("[ERROR] ๋กœ๋˜ ๋ฒˆํ˜ธ ๋ชฉ๋ก์ด null์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + } + } + + + private static void validateSize(List numbers) { + if (numbers.size() != LOTTO_SIZE) { + throw new IllegalArgumentException("[ERROR] ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” " + LOTTO_SIZE + "๊ฐœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + } + } + + private static void validateRange(List numbers) { + for (Integer n : numbers) { + if (n == null || n < MIN_NUMBER || n > MAX_NUMBER) { + throw new IllegalArgumentException("[ERROR] ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” " + MIN_NUMBER + "๋ถ€ํ„ฐ " + MAX_NUMBER + " ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + } + } + } + + private static void validateDuplicate(List numbers) { + Set set = new HashSet<>(numbers); + if (set.size() != numbers.size()) { + throw new IllegalArgumentException("[ERROR] ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” ์ค‘๋ณต๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + } + } + + + // --- ์ •๋ ฌ & ๋ถˆ๋ณ€ ๋ณด๊ด€ --- + private List toSortedUnmodifiable(List numbers) { + List copy = new ArrayList<>(numbers); + Collections.sort(copy); + return Collections.unmodifiableList(copy); + } + + + public List getNumbers() { + return numbers; + } + +} diff --git a/src/main/java/planetlotto/domain/LottoGenerator.java b/src/main/java/planetlotto/domain/LottoGenerator.java new file mode 100644 index 000000000..8563bbfed --- /dev/null +++ b/src/main/java/planetlotto/domain/LottoGenerator.java @@ -0,0 +1,18 @@ +package planetlotto.domain; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.List; + +public class LottoGenerator { + + public Lottos generate(long count) { + List lottos = new ArrayList<>(); + for (int i = 0; i < count; i++) { + List numbers = Randoms.pickUniqueNumbersInRange(1, 30, 5); + lottos.add(new Lotto(numbers)); + } + return new Lottos(lottos); + } +} diff --git a/src/main/java/planetlotto/domain/Lottos.java b/src/main/java/planetlotto/domain/Lottos.java new file mode 100644 index 000000000..24ce92060 --- /dev/null +++ b/src/main/java/planetlotto/domain/Lottos.java @@ -0,0 +1,28 @@ +package planetlotto.domain; + +import java.util.List; +import java.util.stream.Collectors; + +public class Lottos { + + public List lottos; + + public Lottos(List lottos) { + this.lottos = lottos; + } + + + public int size() { + return lottos.size(); + } + + public List> getLottosAs2DList() { + return lottos.stream() + .map(Lotto::getNumbers) // Lotto ๊ฐ์ฒด์˜ getNumbers() ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ + .collect(Collectors.toList()); + } + + public List getLottos() { + return lottos; + } +} diff --git a/src/main/java/planetlotto/domain/Rank.java b/src/main/java/planetlotto/domain/Rank.java new file mode 100644 index 000000000..0b3a7cb6d --- /dev/null +++ b/src/main/java/planetlotto/domain/Rank.java @@ -0,0 +1,50 @@ +package planetlotto.domain; + +public enum Rank { + + FIRST(5, false, 100_000_000L), + SECOND(4, true, 10_000_000L), + THIRD(4, false, 1_500_000L), + FOURTH(3, true, 500_000L), + FIFTH(2, true, 5_000L), + NONE(0, false, 0L); + + private final int matchCount; + private final boolean isBonus; + private final long prize; + + Rank(int matchCount, boolean isBonus, long prize) { + this.matchCount = matchCount; + this.isBonus = isBonus; + this.prize = prize; + } + + + public static Rank of(int matchCount, boolean isBonus) { + // ์กฐ๊ธฐ ๋ฐ˜ํ™˜์œผ๋กœ ๋ถ„๊ธฐ (else/switch/3ํ•ญ ๊ธˆ์ง€) + if (matchCount == 5) { + return FIRST; + } + if (matchCount == 4 && isBonus) { + return SECOND; + } + if (matchCount == 4) { + return THIRD; + } + if (matchCount == 3 && isBonus) { + return FOURTH; + } + if (matchCount == 2 && isBonus) { + return FIFTH; + } + return NONE; + } + + public long getPrize(){ + return prize; + } + + public int getMatchCount(){ + return matchCount; + } +} diff --git a/src/main/java/planetlotto/domain/ResultCalculator.java b/src/main/java/planetlotto/domain/ResultCalculator.java new file mode 100644 index 000000000..1e3f53cba --- /dev/null +++ b/src/main/java/planetlotto/domain/ResultCalculator.java @@ -0,0 +1,24 @@ +package planetlotto.domain; + +import java.util.*; + +public final class ResultCalculator { + + private ResultCalculator() { + } // ๊ฐ์ฒด ์ƒ์„ฑ ๊ธˆ์ง€์šฉ + + /** + * ๋ณด์œ ํ•œ ๋กœ๋˜๋“ค์„ ๋“ฑ์ˆ˜๋ณ„๋กœ ์ง‘๊ณ„ํ•œ๋‹ค. + */ + public static Map tallyRanks(Lottos lottos, WinningNumbers winning) { + Map tally = new EnumMap<>(Rank.class); + for (Lotto lotto : lottos.getLottos()) { + int matches = winning.countMatches(lotto); + boolean bonus = winning.isBonusMatched(lotto); + Rank rank = Rank.of(matches, bonus); + tally.put(rank, tally.getOrDefault(rank, 0) + 1); + } + return tally; + } + +} diff --git a/src/main/java/planetlotto/domain/WinningNumbers.java b/src/main/java/planetlotto/domain/WinningNumbers.java new file mode 100644 index 000000000..ebb95aeeb --- /dev/null +++ b/src/main/java/planetlotto/domain/WinningNumbers.java @@ -0,0 +1,100 @@ +package planetlotto.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +public class WinningNumbers { + + // ์ƒ์ˆ˜ + private static final int REQUIRED_SIZE = 5; + private static final int MIN_NUMBER = 1; + private static final int MAX_NUMBER = 30; + + private final List numbers; // ์˜ค๋ฆ„์ฐจ์ˆœ ๋ถˆ๋ณ€ ๋ฆฌ์ŠคํŠธ + private final int bonusNum; + + public WinningNumbers(List numbers, int bonusNum) { + validateNotNull(numbers); + validateSize(numbers); + validateRange(numbers); + validateUnique(numbers); + validateBonusRange(bonusNum); + validateBonusNotDuplicated(numbers, bonusNum); + + this.numbers = toSortedUnmodifiable(numbers); + this.bonusNum = bonusNum; + } + + + // --- ๊ฒ€์ฆ --- + private void validateNotNull(List numbers) { + if (numbers == null) { + throw new IllegalArgumentException("[ERROR] ๋‹น์ฒจ ๋ฒˆํ˜ธ ๋ชฉ๋ก์ด null์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + } + } + + private void validateSize(List numbers) { + if (numbers.size() != REQUIRED_SIZE) { + throw new IllegalArgumentException("[ERROR] ๋‹น์ฒจ ๋ฒˆํ˜ธ๋Š” " + REQUIRED_SIZE + "๊ฐœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + } + } + + private void validateRange(List numbers) { + for (Integer n : numbers) { + if (n == null || n < MIN_NUMBER || n > MAX_NUMBER) { + throw new IllegalArgumentException("[ERROR] ๋‹น์ฒจ ๋ฒˆํ˜ธ๋Š” " + MIN_NUMBER + "๋ถ€ํ„ฐ " + MAX_NUMBER + " ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + } + } + } + + private void validateUnique(List numbers) { + if (new HashSet<>(numbers).size() != numbers.size()) { + throw new IllegalArgumentException("[ERROR] ๋‹น์ฒจ ๋ฒˆํ˜ธ๋Š” ์ค‘๋ณต๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + } + } + + private void validateBonusRange(int bonus) { + if (bonus < MIN_NUMBER || bonus > MAX_NUMBER) { + throw new IllegalArgumentException("[ERROR] ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋Š” " + MIN_NUMBER + "๋ถ€ํ„ฐ " + MAX_NUMBER + " ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + } + } + + private void validateBonusNotDuplicated(List numbers, int bonus) { + if (numbers.contains(bonus)) { + throw new IllegalArgumentException("[ERROR] ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋Š” ๋‹น์ฒจ ๋ฒˆํ˜ธ์™€ ์ค‘๋ณต๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + } + } + + public List getNumbers() { + return numbers; + } + + public int getBonusNum() { + return bonusNum; + } + + // --- ๋ณด๊ด€ --- + private List toSortedUnmodifiable(List numbers) { + List copy = new ArrayList<>(numbers); + Collections.sort(copy); + return Collections.unmodifiableList(copy); + } + + + // --- ํŒ์ • ๋ณด์กฐ --- + public int countMatches(Lotto lotto) { + int count = 0; + for (Integer n : lotto.getNumbers()) { + if (numbers.contains(n)) { + count++; + } + } + return count; + } + + public boolean isBonusMatched(Lotto lotto) { + return lotto.getNumbers().contains(bonusNum); + } +} diff --git a/src/main/java/planetlotto/support/validator/BonusNumber.java b/src/main/java/planetlotto/support/validator/BonusNumber.java new file mode 100644 index 000000000..6946c00b8 --- /dev/null +++ b/src/main/java/planetlotto/support/validator/BonusNumber.java @@ -0,0 +1,14 @@ +package planetlotto.support.validator; + +public final class BonusNumber { + + // ์ƒ์ˆ˜ + private static final int MIN_NUMBER = 1; + private static final int MAX_NUMBER = 30; + + public static void validate(int bonus) { + if (bonus < MIN_NUMBER || bonus > MAX_NUMBER) { + throw new IllegalArgumentException("๋ณด๋„ˆ์Šค ์ˆซ์ž๋Š” " + MIN_NUMBER + "๋ถ€ํ„ฐ " + MAX_NUMBER + " ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + } + } +} diff --git a/src/main/java/planetlotto/support/validator/PurchaseAmount.java b/src/main/java/planetlotto/support/validator/PurchaseAmount.java new file mode 100644 index 000000000..49f695041 --- /dev/null +++ b/src/main/java/planetlotto/support/validator/PurchaseAmount.java @@ -0,0 +1,21 @@ +package planetlotto.support.validator; + +public final class PurchaseAmount { + public static void validate(int purchaseAmount) { + validateNegative(purchaseAmount); + validateFiveHundred(purchaseAmount); + } + + + private static void validateNegative(int amount) { + if (amount < 0) { + throw new IllegalArgumentException("๊ตฌ์ž… ๊ธˆ์•ก์€ ์Œ์ˆ˜์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + } + } + + private static void validateFiveHundred(int amount) { + if (amount % 500 != 0) { + throw new IllegalArgumentException("๊ตฌ์ž…๊ธˆ์•ก์€ 500์› ๋‹จ์œ„๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + } + } +} diff --git a/src/main/java/planetlotto/support/validator/WinningNumber.java b/src/main/java/planetlotto/support/validator/WinningNumber.java new file mode 100644 index 000000000..6b03ed3cd --- /dev/null +++ b/src/main/java/planetlotto/support/validator/WinningNumber.java @@ -0,0 +1,40 @@ +package planetlotto.support.validator; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public final class WinningNumber { + + // ์ƒ์ˆ˜ + private static final int LOTTO_SIZE = 5; + private static final int MIN_NUMBER = 1; + private static final int MAX_NUMBER = 30; + + public static void validate(List WinningNumbers) { + validateDuplicate(WinningNumbers); + validateSize(WinningNumbers); + validateRange(WinningNumbers); + } + + private static void validateDuplicate(List result) { + Set set = new HashSet<>(result); + if (set.size() != result.size()) { + throw new IllegalArgumentException("๋‹น์ฒจ๋ฒˆํ˜ธ์— ์ค‘๋ณต๋˜๋Š” ์ˆซ์ž๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค."); + } + } + + private static void validateSize(List result) { + if (result.size() != LOTTO_SIZE) { + throw new IllegalArgumentException("๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” " + LOTTO_SIZE + "๊ฐœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + } + } + + private static void validateRange(List result) { + for (int number : result) { + if (number < MIN_NUMBER || number > MAX_NUMBER) { + throw new IllegalArgumentException("๋กœ๋˜ ๋ฒˆํ˜ธ์˜ ์ˆซ์ž ๋ฒ”์œ„๋Š” 1~30๊นŒ์ง€์ž…๋‹ˆ๋‹ค."); + } + } + } +} diff --git a/src/test/java/planetlotto/domain/RankTest.java b/src/test/java/planetlotto/domain/RankTest.java new file mode 100644 index 000000000..3e7fb367c --- /dev/null +++ b/src/test/java/planetlotto/domain/RankTest.java @@ -0,0 +1,57 @@ +package planetlotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +class RankTest { + + @Test + @DisplayName("์ผ์น˜ 5๊ฐœ๋ฉด 1๋“ฑ์ด๋ฉฐ ์ƒ๊ธˆ์€ 100_000_000์›") + void firstPrize() { + Rank rank = Rank.of(5, false); + assertThat(rank).isEqualTo(Rank.FIRST); + assertThat(rank.getPrize()).isEqualTo(100_000_000L); + assertThat(rank.getMatchCount()).isEqualTo(5); + } + + @Test + @DisplayName("์ผ์น˜ 4๊ฐœ+๋ณด๋„ˆ์Šค๋ฉด 2๋“ฑ์ด๋ฉฐ ์ƒ๊ธˆ์€ 10_000_000์›") + void secondPrize() { + Rank rank = Rank.of(4, true); + assertThat(rank).isEqualTo(Rank.SECOND); + assertThat(rank.getPrize()).isEqualTo(10_000_000L); + assertThat(rank.getMatchCount()).isEqualTo(4); + } + + @Test + @DisplayName("์ผ์น˜ 4๊ฐœ(๋ณด๋„ˆ์ŠคX)๋Š” 3๋“ฑ์ด๋ฉฐ ์ƒ๊ธˆ์€ 1_500_000์›") + void thirdPrize() { + Rank rank = Rank.of(4, false); + assertThat(rank).isEqualTo(Rank.THIRD); + assertThat(rank.getPrize()).isEqualTo(1_500_000L); + assertThat(rank.getMatchCount()).isEqualTo(4); + } + + @Test + @DisplayName("์ผ์น˜ 3๊ฐœ(๋ณด๋„ˆ์Šคo)๋Š” 4๋“ฑ(500_000์›), 2๊ฐœ(๋ณด๋„ˆ์Šคo)๋Š” 5๋“ฑ(5_000์›)") + void fourthAndFifthPrize() { + assertThat(Rank.of(3, true)).isEqualTo(Rank.FOURTH); + assertThat(Rank.of(3, true).getPrize()).isEqualTo(500_000L); + + assertThat(Rank.of(2, true)).isEqualTo(Rank.FIFTH); + assertThat(Rank.of(2, true).getPrize()).isEqualTo(5_000L); + } + + @Test + @DisplayName("๊ทธ ์™ธ์˜ ์กฐํ•ฉ์€ NONE(0์›)") + void nonePrize() { + assertThat(Rank.of(2, false)).isEqualTo(Rank.NONE); + assertThat(Rank.of(0, false)).isEqualTo(Rank.NONE); + assertThat(Rank.of(1, true)).isEqualTo(Rank.NONE); + assertThat(Rank.of(2, false)).isEqualTo(Rank.NONE); + assertThat(Rank.NONE.getPrize()).isEqualTo(0L); + assertThat(Rank.NONE.getMatchCount()).isEqualTo(0); + } +} \ No newline at end of file diff --git a/src/test/java/planetlotto/domain/WinningNumbersTest.java b/src/test/java/planetlotto/domain/WinningNumbersTest.java new file mode 100644 index 000000000..4dec38c2b --- /dev/null +++ b/src/test/java/planetlotto/domain/WinningNumbersTest.java @@ -0,0 +1,85 @@ +package planetlotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +class WinningNumbersTest { + + @Test + @DisplayName("๋‹น์ฒจ ๋ฒˆํ˜ธ๋Š” ์ •ํ™•ํžˆ 5๊ฐœ๊ฐ€ ์•„๋‹ˆ๋ฉด ์˜ˆ์™ธ") + void sizeMustBeFive() { + assertThatThrownBy(() -> new WinningNumbers(List.of(1,2,3,4,5,6), 7)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("[ERROR]"); + + assertThatThrownBy(() -> new WinningNumbers(List.of(1,2,3,4,5,6,7), 8)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("[ERROR]"); + } + + @Test + @DisplayName("๋‹น์ฒจ ๋ฒˆํ˜ธ ๊ฐ ์ˆซ์ž๋Š” 1~30 ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋ฉด ์˜ˆ์™ธ") + void winningNumbersMustBeInRange() { + assertThatThrownBy(() -> new WinningNumbers(List.of(0,2,3,4,5), 7)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("[ERROR]"); + + assertThatThrownBy(() -> new WinningNumbers(List.of(2,3,4,5,31), 7)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("[ERROR]"); + } + + @Test + @DisplayName("๋‹น์ฒจ ๋ฒˆํ˜ธ์— ์ค‘๋ณต์ด ์žˆ์œผ๋ฉด ์˜ˆ์™ธ") + void winningNumbersMustBeUnique() { + assertThatThrownBy(() -> new WinningNumbers(List.of(1,1,3,4,5), 7)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("[ERROR]"); + } + + @Test + @DisplayName("๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋Š” 1~30 ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋ฉด ์˜ˆ์™ธ") + void bonusMustBeInRange() { + assertThatThrownBy(() -> new WinningNumbers(List.of(1,2,3,4,6), 0)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("[ERROR]"); + + assertThatThrownBy(() -> new WinningNumbers(List.of(1,2,3,4,5), 31)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("[ERROR]"); + } + + @Test + @DisplayName("๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๊ฐ€ ๋‹น์ฒจ ๋ฒˆํ˜ธ์™€ ์ค‘๋ณต์ด๋ฉด ์˜ˆ์™ธ") + void bonusMustNotDuplicateWinningNumbers() { + assertThatThrownBy(() -> new WinningNumbers(List.of(1,2,3,4,5), 5)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("[ERROR]"); + } + + @Test + @DisplayName("๋‚ด๋ถ€ ๋‹น์ฒจ ๋ฒˆํ˜ธ๋Š” ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ๋ณด๊ด€๋˜๋ฉฐ ๋ถˆ๋ณ€์ด๋‹ค") + void internalNumbersAreSortedAndUnmodifiable() { + WinningNumbers w = new WinningNumbers(List.of(1,30,10,3,7), 8); + assertThat(w.getNumbers()).containsExactly(1,3,7,10,30); + assertThatThrownBy(() -> w.getNumbers().add(99)) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + @DisplayName("์ผ์น˜ ๊ฐœ์ˆ˜์™€ ๋ณด๋„ˆ์Šค ์ผ์น˜ ์—ฌ๋ถ€๋ฅผ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ๋‹ค") + void canCountMatchesAndBonus() { + WinningNumbers w = new WinningNumbers(List.of(1,2,3,4,5), 7); + Lotto lotto1 = new Lotto(List.of(1,2,3,10,11)); // 3๊ฐœ ์ผ์น˜ + Lotto lotto2 = new Lotto(List.of(2,3,4,5,7)); // 5๊ฐœ + ๋ณด๋„ˆ์Šค + + assertThat(w.countMatches(lotto1)).isEqualTo(3); + assertThat(w.countMatches(lotto2)).isEqualTo(4); + assertThat(w.isBonusMatched(lotto2)).isTrue(); + + } +} \ No newline at end of file