From 0be31082bad6e92ba7d499610017a97bcd059ace Mon Sep 17 00:00:00 2001 From: Suhan Ha <10433434+chemistryx@users.noreply.github.com> Date: Tue, 30 Sep 2025 00:57:03 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=203=EB=8B=A8=EA=B3=84=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/suhan/lotto/model/DrawResult.java | 14 ++++- src/main/java/io/suhan/lotto/model/Rank.java | 56 +++++++++++++++++++ .../lotto/model/executor/DrawExecutor.java | 19 ++++++- .../lotto/model/lotto/LottoController.java | 4 +- .../suhan/lotto/model/lotto/LottoFactory.java | 1 - .../lotto/model/lotto/LottoStatistics.java | 33 ++++------- .../java/io/suhan/lotto/view/InputView.java | 6 ++ .../java/io/suhan/lotto/view/OutputView.java | 17 ++++-- .../model/executor/DrawExecutorTest.java | 26 ++++++++- 9 files changed, 140 insertions(+), 36 deletions(-) create mode 100644 src/main/java/io/suhan/lotto/model/Rank.java diff --git a/src/main/java/io/suhan/lotto/model/DrawResult.java b/src/main/java/io/suhan/lotto/model/DrawResult.java index 98d5875d..48b9a252 100644 --- a/src/main/java/io/suhan/lotto/model/DrawResult.java +++ b/src/main/java/io/suhan/lotto/model/DrawResult.java @@ -2,16 +2,24 @@ public class DrawResult { private final int matchedCount; + private final boolean bonusMatched; + private final Rank rank; - private DrawResult(int matchedCount) { + private DrawResult(int matchedCount, boolean bonusMatched) { this.matchedCount = matchedCount; + this.bonusMatched = bonusMatched; + this.rank = Rank.of(matchedCount, bonusMatched); } - public static DrawResult of(int matchedCount) { - return new DrawResult(matchedCount); + public static DrawResult of(int matchedCount, boolean bonusMatched) { + return new DrawResult(matchedCount, bonusMatched); } public int getMatchedCount() { return matchedCount; } + + public Rank getRank() { + return rank; + } } diff --git a/src/main/java/io/suhan/lotto/model/Rank.java b/src/main/java/io/suhan/lotto/model/Rank.java new file mode 100644 index 00000000..6c2da6ea --- /dev/null +++ b/src/main/java/io/suhan/lotto/model/Rank.java @@ -0,0 +1,56 @@ +package io.suhan.lotto.model; + +import io.suhan.lotto.model.lotto.Lotto; + +public enum Rank { + FIRST(Lotto.LOTTO_SIZE, false, 2000000000, "6개 일치"), + SECOND(Lotto.LOTTO_SIZE - 1, true, 30000000, "5개 일치, 보너스 볼 일치"), + THIRD(Lotto.LOTTO_SIZE - 1, false, 1500000, "5개 일치"), + FOURTH(Lotto.LOTTO_SIZE - 2, false, 50000, "4개 일치"), + FIFTH(Lotto.LOTTO_SIZE - 3, false, 5000, "3개 일치"), + NONE(0, false, 0, ""); // fallback + + private final int matchedCount; + private final boolean bonusRequired; + private final int prize; + private final String description; + + Rank(int matchedCount, boolean bonusRequired, int prize, String description) { + this.matchedCount = matchedCount; + this.bonusRequired = bonusRequired; + this.prize = prize; + this.description = description; + } + + public static Rank of(int matchedCount, boolean bonusMatched) { + if (matchedCount == Lotto.LOTTO_SIZE) { + return Rank.FIRST; + } + + if (matchedCount == Lotto.LOTTO_SIZE - 1 && bonusMatched) { + return Rank.SECOND; + } + + if (matchedCount == Lotto.LOTTO_SIZE - 1) { + return Rank.THIRD; + } + + if (matchedCount == Lotto.LOTTO_SIZE - 2) { + return Rank.FOURTH; + } + + if (matchedCount == Lotto.LOTTO_SIZE - 3) { + return Rank.FIFTH; + } + + return Rank.NONE; + } + + public int getPrize() { + return prize; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/io/suhan/lotto/model/executor/DrawExecutor.java b/src/main/java/io/suhan/lotto/model/executor/DrawExecutor.java index 30644164..3be35276 100644 --- a/src/main/java/io/suhan/lotto/model/executor/DrawExecutor.java +++ b/src/main/java/io/suhan/lotto/model/executor/DrawExecutor.java @@ -12,19 +12,26 @@ public class DrawExecutor implements Executor { private final LottoRegistry registry; private final Lotto winningLotto; + private final LottoNumber bonusNumber; private final List results; - public DrawExecutor(LottoRegistry registry, Lotto winningLotto) { + public DrawExecutor(LottoRegistry registry, Lotto winningLotto, LottoNumber bonusNumber) { this.registry = registry; this.winningLotto = winningLotto; + this.bonusNumber = bonusNumber; this.results = new ArrayList<>(); } @Override public void execute() { + if (winningLotto.getNumbers().contains(bonusNumber)) { + throw new IllegalArgumentException("보너스 번호는 당첨 번호와 중복될 수 없습니다."); + } + for (Lotto lotto : registry.getLottos()) { int matchedCount = calculateMatchedCount(lotto, winningLotto); - results.add(DrawResult.of(matchedCount)); + boolean bonusMatched = isBonusMatched(matchedCount, lotto); + results.add(DrawResult.of(matchedCount, bonusMatched)); } } @@ -35,6 +42,14 @@ private int calculateMatchedCount(Lotto lotto, Lotto winningLotto) { return numbers.size(); } + private boolean isBonusMatched(int matchedCount, Lotto lotto) { + if (matchedCount != Lotto.LOTTO_SIZE - 1) { + return false; + } + + return lotto.getNumbers().contains(bonusNumber); + } + public List getResults() { return results; } diff --git a/src/main/java/io/suhan/lotto/model/lotto/LottoController.java b/src/main/java/io/suhan/lotto/model/lotto/LottoController.java index a2120886..7c784484 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/LottoController.java +++ b/src/main/java/io/suhan/lotto/model/lotto/LottoController.java @@ -35,7 +35,9 @@ private void executePurchase(int balance) { private void executeDraw(int balance) { Lotto winningLotto = createWinningLotto(); - DrawExecutor drawExecutor = new DrawExecutor(registry, winningLotto); + LottoNumber bonusNumber = new LottoNumber(InputView.getBonusNumber()); + + DrawExecutor drawExecutor = new DrawExecutor(registry, winningLotto, bonusNumber); drawExecutor.execute(); LottoStatistics statistics = new LottoStatistics(drawExecutor.getResults()); diff --git a/src/main/java/io/suhan/lotto/model/lotto/LottoFactory.java b/src/main/java/io/suhan/lotto/model/lotto/LottoFactory.java index 2b506878..d6800433 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/LottoFactory.java +++ b/src/main/java/io/suhan/lotto/model/lotto/LottoFactory.java @@ -3,7 +3,6 @@ import io.suhan.lotto.model.NumberPool; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; diff --git a/src/main/java/io/suhan/lotto/model/lotto/LottoStatistics.java b/src/main/java/io/suhan/lotto/model/lotto/LottoStatistics.java index 611e45fe..0504ba17 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/LottoStatistics.java +++ b/src/main/java/io/suhan/lotto/model/lotto/LottoStatistics.java @@ -1,23 +1,17 @@ package io.suhan.lotto.model.lotto; import io.suhan.lotto.model.DrawResult; +import io.suhan.lotto.model.Rank; import java.util.HashMap; import java.util.List; import java.util.Map; public class LottoStatistics { - private static final Map winningsMap = Map.of( - 3, 5000, - 4, 50000, - 5, 1500000, - 6, 2000000000 - ); - - private final Map countMap; + private final Map countMap; private final long totalWinnings; public LottoStatistics(List results) { - this.countMap = calculateMatchedCounts(results); + this.countMap = calculateRankCounts(results); this.totalWinnings = calculateTotalWinnings(); } @@ -25,12 +19,12 @@ public double calculateRevenue(int totalSpent) { return (double) totalWinnings / totalSpent; } - private Map calculateMatchedCounts(List results) { - Map map = new HashMap<>(); + private Map calculateRankCounts(List results) { + Map map = new HashMap<>(); for (DrawResult result : results) { - int matchedCount = result.getMatchedCount(); - map.put(matchedCount, map.getOrDefault(matchedCount, 0L) + 1); + Rank rank = result.getRank(); + map.put(rank, map.getOrDefault(rank, 0L) + 1); } return map; @@ -39,21 +33,14 @@ private Map calculateMatchedCounts(List results) { private long calculateTotalWinnings() { long sum = 0; - for (Map.Entry entry : countMap.entrySet()) { - int matchedCount = entry.getKey(); - long count = entry.getValue(); - - sum += winningsMap.getOrDefault(matchedCount, 0) * count; + for (Map.Entry entry : countMap.entrySet()) { + sum += entry.getKey().getPrize() * entry.getValue(); } return sum; } - public Map getWinningsMap() { - return winningsMap; - } - - public Map getCountMap() { + public Map getCountMap() { return countMap; } } diff --git a/src/main/java/io/suhan/lotto/view/InputView.java b/src/main/java/io/suhan/lotto/view/InputView.java index 8e3e4d54..b8de1406 100644 --- a/src/main/java/io/suhan/lotto/view/InputView.java +++ b/src/main/java/io/suhan/lotto/view/InputView.java @@ -21,4 +21,10 @@ public static Set getWonNumbers() { .map(Integer::parseInt) .collect(Collectors.toSet()); } + + public static int getBonusNumber() { + System.out.println("\n보너스 볼을 입력해주세요."); + + return scanner.nextInt(); + } } diff --git a/src/main/java/io/suhan/lotto/view/OutputView.java b/src/main/java/io/suhan/lotto/view/OutputView.java index 28780cde..6606f16b 100644 --- a/src/main/java/io/suhan/lotto/view/OutputView.java +++ b/src/main/java/io/suhan/lotto/view/OutputView.java @@ -1,5 +1,6 @@ package io.suhan.lotto.view; +import io.suhan.lotto.model.Rank; import io.suhan.lotto.model.lotto.Lotto; import io.suhan.lotto.model.lotto.LottoStatistics; import java.util.List; @@ -15,12 +16,18 @@ public static void printStatistics(LottoStatistics statistics, int totalSpent) { System.out.println("\n당첨 통계"); System.out.println("---------"); - // 3개 ~ 6개 일치 - for (int i = 3; i <= 6; i++) { - long count = statistics.getCountMap().getOrDefault(i, 0L); - int winnings = statistics.getWinningsMap().get(i); + Rank[] ranks = Rank.values(); - System.out.printf("%d개 일치 (%d원)- %d개\n", i, winnings, count); + // print in reverse order + for (int i = ranks.length - 1; i >= 0; i--) { + Rank rank = ranks[i]; + + if (rank == Rank.NONE) { + continue; + } + + long count = statistics.getCountMap().getOrDefault(rank, 0L); + System.out.printf("%s (%d원)- %d개\n", rank.getDescription(), rank.getPrize(), count); } double revenue = statistics.calculateRevenue(totalSpent); diff --git a/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java b/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java index 03f9914b..cccef539 100644 --- a/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java +++ b/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java @@ -1,10 +1,14 @@ package io.suhan.lotto.model.executor; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + import io.suhan.lotto.model.DrawResult; import io.suhan.lotto.model.lotto.Lotto; import io.suhan.lotto.model.lotto.LottoFactory; +import io.suhan.lotto.model.lotto.LottoNumber; import io.suhan.lotto.model.lotto.LottoRegistry; import java.util.List; +import java.util.Set; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; @@ -20,7 +24,7 @@ public class DrawExecutorTest { Lotto winningLotto = LottoFactory.createLotto(); registry.add(winningLotto); - DrawExecutor executor = new DrawExecutor(registry, winningLotto); + DrawExecutor executor = new DrawExecutor(registry, winningLotto, new LottoNumber(1)); executor.execute(); List results = executor.getResults(); @@ -30,4 +34,24 @@ public class DrawExecutorTest { softly.assertThat(results.get(0).getMatchedCount()).isEqualTo(Lotto.LOTTO_SIZE); }); } + + @Test + void 보너스_번호는_당첨번호와_중복될_수_없다() { + LottoRegistry registry = new LottoRegistry(); + + Lotto lotto = LottoFactory.createLotto(); + registry.add(lotto); + + Lotto winningLotto = new Lotto(Set.of( + new LottoNumber(1), new LottoNumber(2), new LottoNumber(3), + new LottoNumber(4), new LottoNumber(5), new LottoNumber(6)) + ); + + LottoNumber bonusNumber = new LottoNumber(6); + + assertThatThrownBy(() -> { + DrawExecutor executor = new DrawExecutor(registry, winningLotto, bonusNumber); + executor.execute(); + }).isInstanceOf(IllegalArgumentException.class); + } } From e0ea09c0fe9ba16c3807155a3da0e5b0bdf604be Mon Sep 17 00:00:00 2001 From: Suhan Ha <10433434+chemistryx@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:48:58 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=204=EB=8B=A8=EA=B3=84=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/suhan/lotto/model/DrawResult.java | 2 -- src/main/java/io/suhan/lotto/model/Rank.java | 20 +++++-------- .../model/executor/PurchaseExecutor.java | 26 +++++++++++++---- .../io/suhan/lotto/model/lotto/Lotto.java | 16 +++++++++- .../lotto/model/lotto/LottoController.java | 26 ++++++++++++----- .../suhan/lotto/model/lotto/LottoFactory.java | 18 +++++++++--- .../io/suhan/lotto/model/lotto/LottoType.java | 6 ++++ .../java/io/suhan/lotto/view/InputView.java | 29 +++++++++++++++++-- .../java/io/suhan/lotto/view/OutputView.java | 7 +++-- .../model/executor/DrawExecutorTest.java | 12 ++++---- .../model/executor/PurchaseExecutorTest.java | 8 ++--- .../io/suhan/lotto/model/lotto/LottoTest.java | 4 +-- 12 files changed, 127 insertions(+), 47 deletions(-) create mode 100644 src/main/java/io/suhan/lotto/model/lotto/LottoType.java diff --git a/src/main/java/io/suhan/lotto/model/DrawResult.java b/src/main/java/io/suhan/lotto/model/DrawResult.java index 48b9a252..d234dbd5 100644 --- a/src/main/java/io/suhan/lotto/model/DrawResult.java +++ b/src/main/java/io/suhan/lotto/model/DrawResult.java @@ -2,12 +2,10 @@ public class DrawResult { private final int matchedCount; - private final boolean bonusMatched; private final Rank rank; private DrawResult(int matchedCount, boolean bonusMatched) { this.matchedCount = matchedCount; - this.bonusMatched = bonusMatched; this.rank = Rank.of(matchedCount, bonusMatched); } diff --git a/src/main/java/io/suhan/lotto/model/Rank.java b/src/main/java/io/suhan/lotto/model/Rank.java index 6c2da6ea..9a907bb7 100644 --- a/src/main/java/io/suhan/lotto/model/Rank.java +++ b/src/main/java/io/suhan/lotto/model/Rank.java @@ -3,21 +3,17 @@ import io.suhan.lotto.model.lotto.Lotto; public enum Rank { - FIRST(Lotto.LOTTO_SIZE, false, 2000000000, "6개 일치"), - SECOND(Lotto.LOTTO_SIZE - 1, true, 30000000, "5개 일치, 보너스 볼 일치"), - THIRD(Lotto.LOTTO_SIZE - 1, false, 1500000, "5개 일치"), - FOURTH(Lotto.LOTTO_SIZE - 2, false, 50000, "4개 일치"), - FIFTH(Lotto.LOTTO_SIZE - 3, false, 5000, "3개 일치"), - NONE(0, false, 0, ""); // fallback - - private final int matchedCount; - private final boolean bonusRequired; + FIRST(2000000000, "6개 일치"), + SECOND(30000000, "5개 일치, 보너스 볼 일치"), + THIRD(1500000, "5개 일치"), + FOURTH(50000, "4개 일치"), + FIFTH(5000, "3개 일치"), + NONE(0, ""); // fallback + private final int prize; private final String description; - Rank(int matchedCount, boolean bonusRequired, int prize, String description) { - this.matchedCount = matchedCount; - this.bonusRequired = bonusRequired; + Rank(int prize, String description) { this.prize = prize; this.description = description; } diff --git a/src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java b/src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java index d8815ccf..60c54ae8 100644 --- a/src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java +++ b/src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java @@ -1,25 +1,41 @@ package io.suhan.lotto.model.executor; +import io.suhan.lotto.model.lotto.Lotto; import io.suhan.lotto.model.lotto.LottoFactory; import io.suhan.lotto.model.lotto.LottoRegistry; +import io.suhan.lotto.model.lotto.LottoType; +import io.suhan.lotto.view.InputView; +import java.util.List; +import java.util.Set; public class PurchaseExecutor implements Executor { public static final int PRICE_PER_LOTTO = 1000; private final LottoRegistry registry; - private final int balance; + private int balance; + private final int manualCount; - public PurchaseExecutor(LottoRegistry registry, int balance) { + public PurchaseExecutor(LottoRegistry registry, int balance, int manualCount) { this.registry = registry; this.balance = balance; + this.manualCount = manualCount; } @Override public void execute() { - int count = getAvailableCount(balance); + List> manualNumbersList = InputView.getManualNumbers(manualCount); - for (int i = 0; i < count; i++) { - registry.add(LottoFactory.createLotto()); + for (Set numbers : manualNumbersList) { + Lotto lotto = Lotto.of(LottoType.MANUAL, LottoFactory.toLottoNumbers(numbers)); + + registry.add(lotto); + balance -= PRICE_PER_LOTTO; + } + + int autoCount = getAvailableCount(balance); + + for (int i = 0; i < autoCount; i++) { + registry.add(LottoFactory.createLotto(LottoType.AUTOMATIC)); } } diff --git a/src/main/java/io/suhan/lotto/model/lotto/Lotto.java b/src/main/java/io/suhan/lotto/model/lotto/Lotto.java index 66314e87..4a0f3fea 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/Lotto.java +++ b/src/main/java/io/suhan/lotto/model/lotto/Lotto.java @@ -9,21 +9,35 @@ public class Lotto { public static final int LOTTO_NUMBER_MIN = 1; public static final int LOTTO_NUMBER_MAX = 45; + private final LottoType type; private final Set numbers; - public Lotto(Set numbers) { + private Lotto(LottoType type, Set numbers) { if (numbers.size() != LOTTO_SIZE) { throw new IllegalArgumentException("로또 번호는 " + LOTTO_SIZE + "개여야 합니다."); } + this.type = type; this.numbers = new HashSet<>(numbers); } + public static Lotto of(Set numbers) { + return new Lotto(LottoType.AUTOMATIC, numbers); + } + + public static Lotto of(LottoType type, Set numbers) { + return new Lotto(type, numbers); + } + @Override public String toString() { return numbers.stream().sorted(Comparator.comparingInt(LottoNumber::getValue)).toList().toString(); } + public LottoType getType() { + return type; + } + public Set getNumbers() { return numbers; } diff --git a/src/main/java/io/suhan/lotto/model/lotto/LottoController.java b/src/main/java/io/suhan/lotto/model/lotto/LottoController.java index 7c784484..393d40c9 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/LottoController.java +++ b/src/main/java/io/suhan/lotto/model/lotto/LottoController.java @@ -1,11 +1,12 @@ package io.suhan.lotto.model.lotto; +import static io.suhan.lotto.model.executor.PurchaseExecutor.PRICE_PER_LOTTO; + import io.suhan.lotto.model.executor.DrawExecutor; import io.suhan.lotto.model.executor.PurchaseExecutor; import io.suhan.lotto.view.InputView; import io.suhan.lotto.view.OutputView; import java.util.Set; -import java.util.stream.Collectors; public class LottoController { private final LottoRegistry registry; @@ -18,6 +19,10 @@ public void run() { try { int balance = InputView.getBalance(); + if (balance < PRICE_PER_LOTTO) { + throw new IllegalArgumentException("금액은 " + PRICE_PER_LOTTO + "원 보다 커야 합니다."); + } + executePurchase(balance); executeDraw(balance); } catch (IllegalArgumentException e) { @@ -26,7 +31,17 @@ public void run() { } private void executePurchase(int balance) { - PurchaseExecutor purchaseExecutor = new PurchaseExecutor(registry, balance); + int manualCount = InputView.getManualCount(); + + if (manualCount < 0) { + throw new IllegalArgumentException("로또 수는 0보다 커야합니다."); + } + + if (PRICE_PER_LOTTO * manualCount > balance) { + throw new IllegalArgumentException("금액이 부족합니다."); + } + + PurchaseExecutor purchaseExecutor = new PurchaseExecutor(registry, balance, manualCount); purchaseExecutor.execute(); OutputView.printPurchaseResult(registry.getLottos()); @@ -46,11 +61,8 @@ private void executeDraw(int balance) { } private Lotto createWinningLotto() { - Set wonNumbers = InputView.getWonNumbers() - .stream() - .map(LottoNumber::new) - .collect(Collectors.toSet()); + Set wonNumbers = LottoFactory.toLottoNumbers(InputView.getWonNumbers()); - return new Lotto(wonNumbers); + return Lotto.of(wonNumbers); } } diff --git a/src/main/java/io/suhan/lotto/model/lotto/LottoFactory.java b/src/main/java/io/suhan/lotto/model/lotto/LottoFactory.java index d6800433..c439efa9 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/LottoFactory.java +++ b/src/main/java/io/suhan/lotto/model/lotto/LottoFactory.java @@ -8,12 +8,12 @@ import java.util.stream.Collectors; public class LottoFactory { - public static Lotto createLotto() { + public static Lotto createLotto(LottoType type) { NumberPool pool = NumberPool.of(Lotto.LOTTO_NUMBER_MIN, Lotto.LOTTO_NUMBER_MAX); - return createLotto(pool); + return createLotto(type, pool); } - private static Lotto createLotto(NumberPool pool) { + public static Lotto createLotto(LottoType type, NumberPool pool) { List poolNumbers = new ArrayList<>(pool.getNumbers()); // copy Collections.shuffle(poolNumbers); @@ -22,6 +22,16 @@ private static Lotto createLotto(NumberPool pool) { .map(LottoNumber::new) .collect(Collectors.toSet()); - return new Lotto(numbers); + return Lotto.of(type, numbers); + } + + public static Set toLottoNumbers(Set numbers) { + return numbers.stream() + .map(LottoNumber::new) + .collect(Collectors.toSet()); + } + + public static int getManualLottosCount(List lottos) { + return lottos.stream().filter((lotto) -> lotto.getType() == LottoType.MANUAL).toList().size(); } } diff --git a/src/main/java/io/suhan/lotto/model/lotto/LottoType.java b/src/main/java/io/suhan/lotto/model/lotto/LottoType.java new file mode 100644 index 00000000..197e061d --- /dev/null +++ b/src/main/java/io/suhan/lotto/model/lotto/LottoType.java @@ -0,0 +1,6 @@ +package io.suhan.lotto.model.lotto; + +public enum LottoType { + MANUAL, + AUTOMATIC; +} diff --git a/src/main/java/io/suhan/lotto/view/InputView.java b/src/main/java/io/suhan/lotto/view/InputView.java index b8de1406..367c9c17 100644 --- a/src/main/java/io/suhan/lotto/view/InputView.java +++ b/src/main/java/io/suhan/lotto/view/InputView.java @@ -1,6 +1,8 @@ package io.suhan.lotto.view; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Scanner; import java.util.Set; import java.util.stream.Collectors; @@ -17,9 +19,7 @@ public static int getBalance() { public static Set getWonNumbers() { System.out.println("\n지난 주 당첨 번호를 입력해 주세요."); - return Arrays.stream(scanner.next().split(",")) - .map(Integer::parseInt) - .collect(Collectors.toSet()); + return parseNumbers(scanner.next()); } public static int getBonusNumber() { @@ -27,4 +27,27 @@ public static int getBonusNumber() { return scanner.nextInt(); } + + public static int getManualCount() { + System.out.println("\n수동으로 구매할 로또 수를 입력해주세요."); + + return scanner.nextInt(); + } + + public static List> getManualNumbers(int count) { + System.out.println("\n수동으로 구매할 번호를 입력해 주세요."); + List> numbers = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + numbers.add(parseNumbers(scanner.next())); + } + + return numbers; + } + + private static Set parseNumbers(String input) { + return Arrays.stream(input.split(",")) + .map(Integer::parseInt) + .collect(Collectors.toSet()); + } } diff --git a/src/main/java/io/suhan/lotto/view/OutputView.java b/src/main/java/io/suhan/lotto/view/OutputView.java index 6606f16b..889a5258 100644 --- a/src/main/java/io/suhan/lotto/view/OutputView.java +++ b/src/main/java/io/suhan/lotto/view/OutputView.java @@ -2,17 +2,20 @@ import io.suhan.lotto.model.Rank; import io.suhan.lotto.model.lotto.Lotto; +import io.suhan.lotto.model.lotto.LottoFactory; import io.suhan.lotto.model.lotto.LottoStatistics; import java.util.List; public class OutputView { public static void printPurchaseResult(List lottos) { - System.out.println("\n" + lottos.size() + "개를 구매했습니다."); + int manualCount = LottoFactory.getManualLottosCount(lottos); + int autoCount = lottos.size() - manualCount; + + System.out.println("\n수동으로 " + manualCount + "장, 자동으로 " + autoCount + "장을 구매했습니다."); printLottos(lottos); } public static void printStatistics(LottoStatistics statistics, int totalSpent) { - System.out.println("\n당첨 통계"); System.out.println("---------"); diff --git a/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java b/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java index cccef539..6ba7f865 100644 --- a/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java +++ b/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java @@ -7,6 +7,7 @@ import io.suhan.lotto.model.lotto.LottoFactory; import io.suhan.lotto.model.lotto.LottoNumber; import io.suhan.lotto.model.lotto.LottoRegistry; +import io.suhan.lotto.model.lotto.LottoType; import java.util.List; import java.util.Set; import org.assertj.core.api.SoftAssertions; @@ -21,7 +22,7 @@ public class DrawExecutorTest { void 당첨번호와_로또를_비교할_수_있다() { LottoRegistry registry = new LottoRegistry(); - Lotto winningLotto = LottoFactory.createLotto(); + Lotto winningLotto = LottoFactory.createLotto(LottoType.AUTOMATIC); registry.add(winningLotto); DrawExecutor executor = new DrawExecutor(registry, winningLotto, new LottoNumber(1)); @@ -39,13 +40,14 @@ public class DrawExecutorTest { void 보너스_번호는_당첨번호와_중복될_수_없다() { LottoRegistry registry = new LottoRegistry(); - Lotto lotto = LottoFactory.createLotto(); + Lotto lotto = LottoFactory.createLotto(LottoType.AUTOMATIC); registry.add(lotto); - Lotto winningLotto = new Lotto(Set.of( + Set numbers = Set.of( new LottoNumber(1), new LottoNumber(2), new LottoNumber(3), - new LottoNumber(4), new LottoNumber(5), new LottoNumber(6)) - ); + new LottoNumber(4), new LottoNumber(5), new LottoNumber(6)); + + Lotto winningLotto = Lotto.of(numbers); LottoNumber bonusNumber = new LottoNumber(6); diff --git a/src/test/java/io/suhan/lotto/model/executor/PurchaseExecutorTest.java b/src/test/java/io/suhan/lotto/model/executor/PurchaseExecutorTest.java index 822482ad..7b2110d1 100644 --- a/src/test/java/io/suhan/lotto/model/executor/PurchaseExecutorTest.java +++ b/src/test/java/io/suhan/lotto/model/executor/PurchaseExecutorTest.java @@ -14,12 +14,12 @@ public class PurchaseExecutorTest { @Test void 금액에_맞는_로또를_구매할_수_있다() { LottoRegistry registry = new LottoRegistry(); - int balance = 5000; + int count = 5; + int balance = PRICE_PER_LOTTO * count; - PurchaseExecutor executor = new PurchaseExecutor(registry, balance); + PurchaseExecutor executor = new PurchaseExecutor(registry, balance, 0); executor.execute(); - int expectedSize = balance / PRICE_PER_LOTTO; - assertThat(registry.getLottos()).hasSize(expectedSize); + assertThat(registry.getLottos()).hasSize(count); } } diff --git a/src/test/java/io/suhan/lotto/model/lotto/LottoTest.java b/src/test/java/io/suhan/lotto/model/lotto/LottoTest.java index ea435a5d..529dfcd7 100644 --- a/src/test/java/io/suhan/lotto/model/lotto/LottoTest.java +++ b/src/test/java/io/suhan/lotto/model/lotto/LottoTest.java @@ -11,14 +11,14 @@ public class LottoTest { @Test void 로또는_6개의_숫자를_가진다() { - Lotto lotto = LottoFactory.createLotto(); + Lotto lotto = LottoFactory.createLotto(LottoType.AUTOMATIC); assertThat(lotto.getNumbers()).hasSize(Lotto.LOTTO_SIZE); } @Test void 로또는_중복된_숫자를_가질_수_없다() { - Lotto lotto = LottoFactory.createLotto(); + Lotto lotto = LottoFactory.createLotto(LottoType.AUTOMATIC); assertThat(lotto.getNumbers()).doesNotHaveDuplicates(); } From ade5b8282cd32d23b8b198f362574c3a89a35bfb Mon Sep 17 00:00:00 2001 From: Suhan Ha <10433434+chemistryx@users.noreply.github.com> Date: Thu, 2 Oct 2025 00:42:47 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=205=EB=8B=A8=EA=B3=84=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/suhan/lotto/model/NumberPool.java | 3 +- src/main/java/io/suhan/lotto/model/Rank.java | 52 +++++++++---------- .../io/suhan/lotto/model/lotto/Lotto.java | 3 +- .../lotto/model/lotto/LottoRegistry.java | 3 +- .../lotto/model/lotto/LottoStatistics.java | 3 +- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/main/java/io/suhan/lotto/model/NumberPool.java b/src/main/java/io/suhan/lotto/model/NumberPool.java index cccbafcf..32b82b2f 100644 --- a/src/main/java/io/suhan/lotto/model/NumberPool.java +++ b/src/main/java/io/suhan/lotto/model/NumberPool.java @@ -2,6 +2,7 @@ import io.suhan.lotto.model.lotto.Lotto; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class NumberPool { @@ -39,6 +40,6 @@ private List generateNumbersInRange(int from, int to) { } public List getNumbers() { - return numbers; + return Collections.unmodifiableList(numbers); } } diff --git a/src/main/java/io/suhan/lotto/model/Rank.java b/src/main/java/io/suhan/lotto/model/Rank.java index 9a907bb7..b00f1d31 100644 --- a/src/main/java/io/suhan/lotto/model/Rank.java +++ b/src/main/java/io/suhan/lotto/model/Rank.java @@ -1,45 +1,41 @@ package io.suhan.lotto.model; -import io.suhan.lotto.model.lotto.Lotto; +import java.util.Arrays; public enum Rank { - FIRST(2000000000, "6개 일치"), - SECOND(30000000, "5개 일치, 보너스 볼 일치"), - THIRD(1500000, "5개 일치"), - FOURTH(50000, "4개 일치"), - FIFTH(5000, "3개 일치"), - NONE(0, ""); // fallback - + FIRST(6, false, 2000000000, "6개 일치"), + SECOND(5, true, 30000000, "5개 일치, 보너스 볼 일치"), + THIRD(5, false, 1500000, "5개 일치"), + FOURTH(4, false, 50000, "4개 일치"), + FIFTH(3, false, 5000, "3개 일치"), + NONE(0, false, 0, ""); // fallback + + private final int matchedCount; + private final boolean bonusRequired; private final int prize; private final String description; - Rank(int prize, String description) { + Rank(int matchedCount, boolean bonusRequired, int prize, String description) { + this.matchedCount = matchedCount; + this.bonusRequired = bonusRequired; this.prize = prize; this.description = description; } public static Rank of(int matchedCount, boolean bonusMatched) { - if (matchedCount == Lotto.LOTTO_SIZE) { - return Rank.FIRST; - } - - if (matchedCount == Lotto.LOTTO_SIZE - 1 && bonusMatched) { - return Rank.SECOND; - } - - if (matchedCount == Lotto.LOTTO_SIZE - 1) { - return Rank.THIRD; - } - - if (matchedCount == Lotto.LOTTO_SIZE - 2) { - return Rank.FOURTH; - } + return Arrays.stream(Rank.values()) + .filter((rank) -> rank.getMatchedCount() == matchedCount) + .filter((rank) -> rank.isBonusRequired() == bonusMatched) + .findFirst() + .orElse(Rank.NONE); + } - if (matchedCount == Lotto.LOTTO_SIZE - 3) { - return Rank.FIFTH; - } + public int getMatchedCount() { + return matchedCount; + } - return Rank.NONE; + public boolean isBonusRequired() { + return bonusRequired; } public int getPrize() { diff --git a/src/main/java/io/suhan/lotto/model/lotto/Lotto.java b/src/main/java/io/suhan/lotto/model/lotto/Lotto.java index 4a0f3fea..1ca8b6ff 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/Lotto.java +++ b/src/main/java/io/suhan/lotto/model/lotto/Lotto.java @@ -1,5 +1,6 @@ package io.suhan.lotto.model.lotto; +import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Set; @@ -39,6 +40,6 @@ public LottoType getType() { } public Set getNumbers() { - return numbers; + return Collections.unmodifiableSet(numbers); } } diff --git a/src/main/java/io/suhan/lotto/model/lotto/LottoRegistry.java b/src/main/java/io/suhan/lotto/model/lotto/LottoRegistry.java index 141fe029..c50a26c8 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/LottoRegistry.java +++ b/src/main/java/io/suhan/lotto/model/lotto/LottoRegistry.java @@ -1,6 +1,7 @@ package io.suhan.lotto.model.lotto; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class LottoRegistry { @@ -15,6 +16,6 @@ public void add(Lotto lotto) { } public List getLottos() { - return lottos; + return Collections.unmodifiableList(lottos); } } diff --git a/src/main/java/io/suhan/lotto/model/lotto/LottoStatistics.java b/src/main/java/io/suhan/lotto/model/lotto/LottoStatistics.java index 0504ba17..9268743f 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/LottoStatistics.java +++ b/src/main/java/io/suhan/lotto/model/lotto/LottoStatistics.java @@ -2,6 +2,7 @@ import io.suhan.lotto.model.DrawResult; import io.suhan.lotto.model.Rank; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,6 +42,6 @@ private long calculateTotalWinnings() { } public Map getCountMap() { - return countMap; + return Collections.unmodifiableMap(countMap); } } From 9a72bef22fd25bdf57de6aa15c9b6345ee2c578a Mon Sep 17 00:00:00 2001 From: Suhan Ha <10433434+chemistryx@users.noreply.github.com> Date: Thu, 2 Oct 2025 00:43:29 +0900 Subject: [PATCH 04/12] =?UTF-8?q?fix:=20=EC=88=98=EB=8F=99=20=EA=B0=9C?= =?UTF-8?q?=EC=88=98=EA=B0=80=200=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EC=98=AC=EB=B0=94=EB=A5=B4=EA=B2=8C=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lotto/model/executor/PurchaseExecutor.java | 18 ++++++++++++------ .../lotto/model/lotto/LottoController.java | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java b/src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java index 60c54ae8..b1a1c49f 100644 --- a/src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java +++ b/src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java @@ -23,6 +23,18 @@ public PurchaseExecutor(LottoRegistry registry, int balance, int manualCount) { @Override public void execute() { + if (manualCount > 0) { + purchaseManualNumbers(); + } + + int autoCount = getAvailableCount(balance); + + for (int i = 0; i < autoCount; i++) { + registry.add(LottoFactory.createLotto(LottoType.AUTOMATIC)); + } + } + + private void purchaseManualNumbers() { List> manualNumbersList = InputView.getManualNumbers(manualCount); for (Set numbers : manualNumbersList) { @@ -31,12 +43,6 @@ public void execute() { registry.add(lotto); balance -= PRICE_PER_LOTTO; } - - int autoCount = getAvailableCount(balance); - - for (int i = 0; i < autoCount; i++) { - registry.add(LottoFactory.createLotto(LottoType.AUTOMATIC)); - } } private int getAvailableCount(int balance) { diff --git a/src/main/java/io/suhan/lotto/model/lotto/LottoController.java b/src/main/java/io/suhan/lotto/model/lotto/LottoController.java index 393d40c9..90b57171 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/LottoController.java +++ b/src/main/java/io/suhan/lotto/model/lotto/LottoController.java @@ -34,7 +34,7 @@ private void executePurchase(int balance) { int manualCount = InputView.getManualCount(); if (manualCount < 0) { - throw new IllegalArgumentException("로또 수는 0보다 커야합니다."); + throw new IllegalArgumentException("로또 수는 0 또는 양수만 입력할 수 있습니다."); } if (PRICE_PER_LOTTO * manualCount > balance) { From f65ce6998d295daabb95a3cdafa7f658a68dc8d5 Mon Sep 17 00:00:00 2001 From: Suhan Ha <10433434+chemistryx@users.noreply.github.com> Date: Thu, 2 Oct 2025 00:44:51 +0900 Subject: [PATCH 05/12] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EA=B0=80=20=EA=B0=84=ED=97=90=EC=A0=81=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=ED=95=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LottoFactory.createLotto()를 통해 무작위 생성하는 경우 사전에 정의된 보너스 번호(1)과 중복되어 테스트가 실패하는 케이스 발생 --- .../suhan/lotto/model/executor/DrawExecutorTest.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java b/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java index 6ba7f865..ca7c4fed 100644 --- a/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java +++ b/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java @@ -22,10 +22,17 @@ public class DrawExecutorTest { void 당첨번호와_로또를_비교할_수_있다() { LottoRegistry registry = new LottoRegistry(); - Lotto winningLotto = LottoFactory.createLotto(LottoType.AUTOMATIC); + Set numbers = Set.of( + new LottoNumber(1), new LottoNumber(2), new LottoNumber(3), + new LottoNumber(4), new LottoNumber(5), new LottoNumber(6)); + + Lotto winningLotto = Lotto.of(numbers); + registry.add(winningLotto); - DrawExecutor executor = new DrawExecutor(registry, winningLotto, new LottoNumber(1)); + LottoNumber bonusNumber = new LottoNumber(7); + + DrawExecutor executor = new DrawExecutor(registry, winningLotto, bonusNumber); executor.execute(); List results = executor.getResults(); From a3438d7e8882233418259b52eb27b0ce757dbc5e Mon Sep 17 00:00:00 2001 From: Suhan Ha <10433434+chemistryx@users.noreply.github.com> Date: Sun, 5 Oct 2025 14:09:31 +0900 Subject: [PATCH 06/12] =?UTF-8?q?refactor:=20LottoController=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/suhan/lotto/Main.java | 2 +- .../lotto/{model/lotto => controller}/LottoController.java | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) rename src/main/java/io/suhan/lotto/{model/lotto => controller}/LottoController.java (88%) diff --git a/src/main/java/io/suhan/lotto/Main.java b/src/main/java/io/suhan/lotto/Main.java index 5e912401..f25bfe0f 100644 --- a/src/main/java/io/suhan/lotto/Main.java +++ b/src/main/java/io/suhan/lotto/Main.java @@ -1,6 +1,6 @@ package io.suhan.lotto; -import io.suhan.lotto.model.lotto.LottoController; +import io.suhan.lotto.controller.LottoController; public class Main { public static void main(String[] args) { diff --git a/src/main/java/io/suhan/lotto/model/lotto/LottoController.java b/src/main/java/io/suhan/lotto/controller/LottoController.java similarity index 88% rename from src/main/java/io/suhan/lotto/model/lotto/LottoController.java rename to src/main/java/io/suhan/lotto/controller/LottoController.java index 90b57171..b313fead 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/LottoController.java +++ b/src/main/java/io/suhan/lotto/controller/LottoController.java @@ -1,9 +1,14 @@ -package io.suhan.lotto.model.lotto; +package io.suhan.lotto.controller; import static io.suhan.lotto.model.executor.PurchaseExecutor.PRICE_PER_LOTTO; import io.suhan.lotto.model.executor.DrawExecutor; import io.suhan.lotto.model.executor.PurchaseExecutor; +import io.suhan.lotto.model.lotto.Lotto; +import io.suhan.lotto.model.lotto.LottoFactory; +import io.suhan.lotto.model.lotto.LottoNumber; +import io.suhan.lotto.model.lotto.LottoRegistry; +import io.suhan.lotto.model.lotto.LottoStatistics; import io.suhan.lotto.view.InputView; import io.suhan.lotto.view.OutputView; import java.util.Set; From 15bd177e4269b8ae31af27016f98c72977cb86c6 Mon Sep 17 00:00:00 2001 From: Suhan Ha <10433434+chemistryx@users.noreply.github.com> Date: Sun, 5 Oct 2025 14:31:47 +0900 Subject: [PATCH 07/12] =?UTF-8?q?refactor:=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=EC=9E=90=20=EB=A7=A4=EC=A7=81=EB=84=98?= =?UTF-8?q?=EB=B2=84=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/suhan/lotto/view/InputView.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/suhan/lotto/view/InputView.java b/src/main/java/io/suhan/lotto/view/InputView.java index 367c9c17..08067d35 100644 --- a/src/main/java/io/suhan/lotto/view/InputView.java +++ b/src/main/java/io/suhan/lotto/view/InputView.java @@ -9,6 +9,7 @@ public class InputView { private static final Scanner scanner = new Scanner(System.in); + private static final String INPUT_DELIMITER = ","; public static int getBalance() { System.out.println("구입할 금액을 입력해주세요."); @@ -46,7 +47,7 @@ public static List> getManualNumbers(int count) { } private static Set parseNumbers(String input) { - return Arrays.stream(input.split(",")) + return Arrays.stream(input.split(INPUT_DELIMITER)) .map(Integer::parseInt) .collect(Collectors.toSet()); } From 381d0849eb7e7f87f5736b115f7c5c70402a701c Mon Sep 17 00:00:00 2001 From: Suhan Ha <10433434+chemistryx@users.noreply.github.com> Date: Sun, 5 Oct 2025 14:53:50 +0900 Subject: [PATCH 08/12] =?UTF-8?q?test:=20=EA=B3=B5=ED=86=B5=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EB=A1=9C=EC=A7=81=20BeforeEach=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/executor/DrawExecutorTest.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java b/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java index ca7c4fed..5c75dfef 100644 --- a/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java +++ b/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Set; import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; import org.junit.jupiter.api.Test; @@ -18,16 +19,21 @@ @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(ReplaceUnderscores.class) public class DrawExecutorTest { - @Test - void 당첨번호와_로또를_비교할_수_있다() { - LottoRegistry registry = new LottoRegistry(); + private LottoRegistry registry; + private Lotto winningLotto; + @BeforeEach + void setUp() { + registry = new LottoRegistry(); Set numbers = Set.of( new LottoNumber(1), new LottoNumber(2), new LottoNumber(3), - new LottoNumber(4), new LottoNumber(5), new LottoNumber(6)); - - Lotto winningLotto = Lotto.of(numbers); + new LottoNumber(4), new LottoNumber(5), new LottoNumber(6) + ); + winningLotto = Lotto.of(numbers); + } + @Test + void 당첨번호와_로또를_비교할_수_있다() { registry.add(winningLotto); LottoNumber bonusNumber = new LottoNumber(7); @@ -45,17 +51,9 @@ public class DrawExecutorTest { @Test void 보너스_번호는_당첨번호와_중복될_수_없다() { - LottoRegistry registry = new LottoRegistry(); - Lotto lotto = LottoFactory.createLotto(LottoType.AUTOMATIC); registry.add(lotto); - Set numbers = Set.of( - new LottoNumber(1), new LottoNumber(2), new LottoNumber(3), - new LottoNumber(4), new LottoNumber(5), new LottoNumber(6)); - - Lotto winningLotto = Lotto.of(numbers); - LottoNumber bonusNumber = new LottoNumber(6); assertThatThrownBy(() -> { From f9bce693f2f745f541b6ba48b34abc8bc5f08e7c Mon Sep 17 00:00:00 2001 From: Suhan Ha <10433434+chemistryx@users.noreply.github.com> Date: Sun, 5 Oct 2025 15:13:40 +0900 Subject: [PATCH 09/12] =?UTF-8?q?test:=20given-when-that=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C=EC=A0=81=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/suhan/lotto/model/NumberPoolTest.java | 22 +++++++++++++++---- .../model/executor/DrawExecutorTest.java | 10 +++++---- .../model/executor/PurchaseExecutorTest.java | 4 ++++ .../lotto/model/lotto/LottoNumberTest.java | 11 ++++++++-- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/test/java/io/suhan/lotto/model/NumberPoolTest.java b/src/test/java/io/suhan/lotto/model/NumberPoolTest.java index 320782ac..23145ac5 100644 --- a/src/test/java/io/suhan/lotto/model/NumberPoolTest.java +++ b/src/test/java/io/suhan/lotto/model/NumberPoolTest.java @@ -13,25 +13,39 @@ public class NumberPoolTest { @Test void 특정_범위를_가진_Pool을_생성할_수_있다() { - NumberPool pool = NumberPool.of(1, 10); + // given + int from = 1; + int to = 10; - assertThat(pool.getNumbers()).hasSize(10); + // when + NumberPool pool = NumberPool.of(from, to); + + // then + assertThat(pool.getNumbers()).hasSize(to - from + 1); } @Test void from은_to보다_클_수_없다() { + // given + int from = 2; + int to = 1; String expectedMessage = "from 값은 to 값보다 작아야 합니다."; - assertThatThrownBy(() -> NumberPool.of(2, 1)) + // when and then + assertThatThrownBy(() -> NumberPool.of(from, to)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(expectedMessage); } @Test void 범위의_크기는_LOTTO_SIZE보다_커야_한다() { + // given + int from = Lotto.LOTTO_NUMBER_MIN; + int to = Lotto.LOTTO_NUMBER_MIN + Lotto.LOTTO_SIZE - 2; String expectedMessage = "범위의 크기는 " + Lotto.LOTTO_SIZE + " 보다 커야 합니다."; - assertThatThrownBy(() -> NumberPool.of(Lotto.LOTTO_NUMBER_MIN, Lotto.LOTTO_NUMBER_MIN + Lotto.LOTTO_SIZE - 2)) + // when and then + assertThatThrownBy(() -> NumberPool.of(from, to)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(expectedMessage); } diff --git a/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java b/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java index 5c75dfef..0ce230c9 100644 --- a/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java +++ b/src/test/java/io/suhan/lotto/model/executor/DrawExecutorTest.java @@ -34,15 +34,16 @@ void setUp() { @Test void 당첨번호와_로또를_비교할_수_있다() { + // given registry.add(winningLotto); - LottoNumber bonusNumber = new LottoNumber(7); - DrawExecutor executor = new DrawExecutor(registry, winningLotto, bonusNumber); - executor.execute(); + // when + executor.execute(); List results = executor.getResults(); + // then SoftAssertions.assertSoftly((softly) -> { softly.assertThat(results).hasSize(1); softly.assertThat(results.get(0).getMatchedCount()).isEqualTo(Lotto.LOTTO_SIZE); @@ -51,11 +52,12 @@ void setUp() { @Test void 보너스_번호는_당첨번호와_중복될_수_없다() { + // given Lotto lotto = LottoFactory.createLotto(LottoType.AUTOMATIC); registry.add(lotto); - LottoNumber bonusNumber = new LottoNumber(6); + // when and then assertThatThrownBy(() -> { DrawExecutor executor = new DrawExecutor(registry, winningLotto, bonusNumber); executor.execute(); diff --git a/src/test/java/io/suhan/lotto/model/executor/PurchaseExecutorTest.java b/src/test/java/io/suhan/lotto/model/executor/PurchaseExecutorTest.java index 7b2110d1..a162a99b 100644 --- a/src/test/java/io/suhan/lotto/model/executor/PurchaseExecutorTest.java +++ b/src/test/java/io/suhan/lotto/model/executor/PurchaseExecutorTest.java @@ -13,13 +13,17 @@ public class PurchaseExecutorTest { @Test void 금액에_맞는_로또를_구매할_수_있다() { + // given LottoRegistry registry = new LottoRegistry(); int count = 5; int balance = PRICE_PER_LOTTO * count; PurchaseExecutor executor = new PurchaseExecutor(registry, balance, 0); + + // when executor.execute(); + // then assertThat(registry.getLottos()).hasSize(count); } } diff --git a/src/test/java/io/suhan/lotto/model/lotto/LottoNumberTest.java b/src/test/java/io/suhan/lotto/model/lotto/LottoNumberTest.java index 46f18e54..f2e7e062 100644 --- a/src/test/java/io/suhan/lotto/model/lotto/LottoNumberTest.java +++ b/src/test/java/io/suhan/lotto/model/lotto/LottoNumberTest.java @@ -12,15 +12,22 @@ public class LottoNumberTest { @Test void 유효한_번호를_생성할_수_있다() { - LottoNumber number = new LottoNumber(Lotto.LOTTO_NUMBER_MIN); + // given + int validNumber = Lotto.LOTTO_NUMBER_MIN; - assertThat(Lotto.LOTTO_NUMBER_MIN).isEqualTo(number.getValue()); + // when + LottoNumber lottoNumber = new LottoNumber(validNumber); + + // then + assertThat(lottoNumber.getValue()).isEqualTo(validNumber); } @Test void 번호는_범위를_벗어날_수_없다() { + // given String expectedMessage = "로또 번호는 " + Lotto.LOTTO_NUMBER_MIN + "~" + Lotto.LOTTO_NUMBER_MAX + " 사이여야 합니다."; + // when and then SoftAssertions.assertSoftly((softly) -> { softly.assertThatThrownBy(() -> new LottoNumber(Lotto.LOTTO_NUMBER_MIN - 1)) .isInstanceOf(IllegalArgumentException.class) From 3768376d8c90308d913991228f1f0497f9a6425d Mon Sep 17 00:00:00 2001 From: Suhan Ha <10433434+chemistryx@users.noreply.github.com> Date: Sun, 5 Oct 2025 20:32:47 +0900 Subject: [PATCH 10/12] =?UTF-8?q?refactor:=20=EC=9E=98=EB=AA=BB=EB=90=9C?= =?UTF-8?q?=20=EC=9E=85=EB=A0=A5=EA=B0=92=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?retry=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lotto/controller/LottoController.java | 16 +--- .../model/executor/PurchaseExecutor.java | 2 +- .../java/io/suhan/lotto/view/InputView.java | 76 ++++++++++++++++++- 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/suhan/lotto/controller/LottoController.java b/src/main/java/io/suhan/lotto/controller/LottoController.java index b313fead..ec42c126 100644 --- a/src/main/java/io/suhan/lotto/controller/LottoController.java +++ b/src/main/java/io/suhan/lotto/controller/LottoController.java @@ -22,11 +22,7 @@ public LottoController() { public void run() { try { - int balance = InputView.getBalance(); - - if (balance < PRICE_PER_LOTTO) { - throw new IllegalArgumentException("금액은 " + PRICE_PER_LOTTO + "원 보다 커야 합니다."); - } + int balance = InputView.getValidBalance(); executePurchase(balance); executeDraw(balance); @@ -36,11 +32,7 @@ public void run() { } private void executePurchase(int balance) { - int manualCount = InputView.getManualCount(); - - if (manualCount < 0) { - throw new IllegalArgumentException("로또 수는 0 또는 양수만 입력할 수 있습니다."); - } + int manualCount = InputView.getValidManualCount(); if (PRICE_PER_LOTTO * manualCount > balance) { throw new IllegalArgumentException("금액이 부족합니다."); @@ -55,7 +47,7 @@ private void executePurchase(int balance) { private void executeDraw(int balance) { Lotto winningLotto = createWinningLotto(); - LottoNumber bonusNumber = new LottoNumber(InputView.getBonusNumber()); + LottoNumber bonusNumber = InputView.getValidBonusNumber(); DrawExecutor drawExecutor = new DrawExecutor(registry, winningLotto, bonusNumber); drawExecutor.execute(); @@ -66,7 +58,7 @@ private void executeDraw(int balance) { } private Lotto createWinningLotto() { - Set wonNumbers = LottoFactory.toLottoNumbers(InputView.getWonNumbers()); + Set wonNumbers = LottoFactory.toLottoNumbers(InputView.getValidWonNumbers()); return Lotto.of(wonNumbers); } diff --git a/src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java b/src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java index b1a1c49f..1cf1ab49 100644 --- a/src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java +++ b/src/main/java/io/suhan/lotto/model/executor/PurchaseExecutor.java @@ -35,7 +35,7 @@ public void execute() { } private void purchaseManualNumbers() { - List> manualNumbersList = InputView.getManualNumbers(manualCount); + List> manualNumbersList = InputView.getValidManualNumbers(manualCount); for (Set numbers : manualNumbersList) { Lotto lotto = Lotto.of(LottoType.MANUAL, LottoFactory.toLottoNumbers(numbers)); diff --git a/src/main/java/io/suhan/lotto/view/InputView.java b/src/main/java/io/suhan/lotto/view/InputView.java index 08067d35..a8e854c8 100644 --- a/src/main/java/io/suhan/lotto/view/InputView.java +++ b/src/main/java/io/suhan/lotto/view/InputView.java @@ -1,5 +1,9 @@ package io.suhan.lotto.view; +import static io.suhan.lotto.model.executor.PurchaseExecutor.PRICE_PER_LOTTO; +import static io.suhan.lotto.model.lotto.Lotto.LOTTO_SIZE; + +import io.suhan.lotto.model.lotto.LottoNumber; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -46,9 +50,79 @@ public static List> getManualNumbers(int count) { return numbers; } + public static int getValidBalance() { + while (true) { + try { + int balance = getBalance(); + + if (balance < PRICE_PER_LOTTO) { + throw new IllegalArgumentException("금액은 " + PRICE_PER_LOTTO + "원 보다 크거나 같아야 합니다."); + } + + return balance; + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + } + + public static Set getValidWonNumbers() { + while (true) { + try { + return getWonNumbers(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + } + + public static LottoNumber getValidBonusNumber() { + while (true) { + try { + int number = getBonusNumber(); + + return new LottoNumber(number); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + } + + public static int getValidManualCount() { + while (true) { + try { + int manualCount = InputView.getManualCount(); + + if (manualCount < 0) { + throw new IllegalArgumentException("로또 수는 0 또는 양수만 입력할 수 있습니다."); + } + + return manualCount; + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + } + + public static List> getValidManualNumbers(int count) { + while (true) { + try { + return getManualNumbers(count); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + } + private static Set parseNumbers(String input) { - return Arrays.stream(input.split(INPUT_DELIMITER)) + Set numbers = Arrays.stream(input.split(INPUT_DELIMITER)) .map(Integer::parseInt) .collect(Collectors.toSet()); + + if (numbers.size() != LOTTO_SIZE) { + throw new IllegalArgumentException("로또 번호는 " + LOTTO_SIZE + "개여야 합니다."); + } + + return numbers; } } From 57ccc7adb32422322084eda095f1d13b29d09804 Mon Sep 17 00:00:00 2001 From: Suhan Ha <10433434+chemistryx@users.noreply.github.com> Date: Tue, 14 Oct 2025 15:56:18 +0900 Subject: [PATCH 11/12] =?UTF-8?q?refactor:=20LottoNumber=20Record=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/suhan/lotto/model/lotto/Lotto.java | 2 +- .../suhan/lotto/model/lotto/LottoNumber.java | 24 ++----------------- .../lotto/model/lotto/LottoNumberTest.java | 2 +- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/src/main/java/io/suhan/lotto/model/lotto/Lotto.java b/src/main/java/io/suhan/lotto/model/lotto/Lotto.java index 1ca8b6ff..84a6e894 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/Lotto.java +++ b/src/main/java/io/suhan/lotto/model/lotto/Lotto.java @@ -32,7 +32,7 @@ public static Lotto of(LottoType type, Set numbers) { @Override public String toString() { - return numbers.stream().sorted(Comparator.comparingInt(LottoNumber::getValue)).toList().toString(); + return numbers.stream().sorted(Comparator.comparingInt(LottoNumber::value)).toList().toString(); } public LottoType getType() { diff --git a/src/main/java/io/suhan/lotto/model/lotto/LottoNumber.java b/src/main/java/io/suhan/lotto/model/lotto/LottoNumber.java index 605bf203..e7d53da9 100644 --- a/src/main/java/io/suhan/lotto/model/lotto/LottoNumber.java +++ b/src/main/java/io/suhan/lotto/model/lotto/LottoNumber.java @@ -1,34 +1,14 @@ package io.suhan.lotto.model.lotto; -public class LottoNumber { - private final int value; - - public LottoNumber(int value) { +public record LottoNumber(int value) { + public LottoNumber { if (value < Lotto.LOTTO_NUMBER_MIN || value > Lotto.LOTTO_NUMBER_MAX) { throw new IllegalArgumentException("로또 번호는 " + Lotto.LOTTO_NUMBER_MIN + "~" + Lotto.LOTTO_NUMBER_MAX + " 사이여야 합니다."); } - - this.value = value; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof LottoNumber number)) return false; - return value == number.value; - } - - @Override - public int hashCode() { - return Integer.hashCode(value); } @Override public String toString() { return String.valueOf(value); } - - public int getValue() { - return value; - } } diff --git a/src/test/java/io/suhan/lotto/model/lotto/LottoNumberTest.java b/src/test/java/io/suhan/lotto/model/lotto/LottoNumberTest.java index f2e7e062..913b600c 100644 --- a/src/test/java/io/suhan/lotto/model/lotto/LottoNumberTest.java +++ b/src/test/java/io/suhan/lotto/model/lotto/LottoNumberTest.java @@ -19,7 +19,7 @@ public class LottoNumberTest { LottoNumber lottoNumber = new LottoNumber(validNumber); // then - assertThat(lottoNumber.getValue()).isEqualTo(validNumber); + assertThat(lottoNumber.value()).isEqualTo(validNumber); } @Test From 0a8659955bcd27e5e50618084bb2413f957236c3 Mon Sep 17 00:00:00 2001 From: Suhan Ha <10433434+chemistryx@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:21:53 +0900 Subject: [PATCH 12/12] =?UTF-8?q?refactor:=20=EB=82=B4=EB=B6=80=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A1=9C=EC=A7=81=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=B4=20retry=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lotto/controller/LottoController.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/suhan/lotto/controller/LottoController.java b/src/main/java/io/suhan/lotto/controller/LottoController.java index ec42c126..2bb3086a 100644 --- a/src/main/java/io/suhan/lotto/controller/LottoController.java +++ b/src/main/java/io/suhan/lotto/controller/LottoController.java @@ -45,16 +45,32 @@ private void executePurchase(int balance) { } private void executeDraw(int balance) { - Lotto winningLotto = createWinningLotto(); + Lotto winningLotto; + + while (true) { + try { + winningLotto = createWinningLotto(); + break; + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } - LottoNumber bonusNumber = InputView.getValidBonusNumber(); + while (true) { + try { + LottoNumber bonusNumber = InputView.getValidBonusNumber(); - DrawExecutor drawExecutor = new DrawExecutor(registry, winningLotto, bonusNumber); - drawExecutor.execute(); + DrawExecutor drawExecutor = new DrawExecutor(registry, winningLotto, bonusNumber); + drawExecutor.execute(); - LottoStatistics statistics = new LottoStatistics(drawExecutor.getResults()); + LottoStatistics statistics = new LottoStatistics(drawExecutor.getResults()); - OutputView.printStatistics(statistics, balance); + OutputView.printStatistics(statistics, balance); + break; + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } } private Lotto createWinningLotto() {