diff --git a/README.md b/README.md index 61e6744e6f..444fe1e5d4 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,15 @@ ## 기능 구현 목록 * domain - * LottoGenerator - * LottoChecker + * LottoFactory - 로또 생성 담당 + * LottoGame - 당첨 복권 & 수익률 계산 + * Lottos + * Lotto - 로또 복권 하나 + * LottoNumber - 기본 로또 번호 + * WinningLotto - Lotto + 보너스번호 + * Money - 금액에 관련된 책임 수행 + * LottoResult - 게임의 결과 + * Rank - 당첨 순위 & 당첨금에 대한 역할 * view - * InputView - * ResultView \ No newline at end of file + * InputView - 입력 담당 + * ResultView - 출력 담당 \ No newline at end of file diff --git a/src/main/java/lotto/Main.java b/src/main/java/lotto/Main.java index a97b1f8f15..e973c95cb6 100644 --- a/src/main/java/lotto/Main.java +++ b/src/main/java/lotto/Main.java @@ -1,8 +1,9 @@ package lotto; -import lotto.domain.Lotto; import lotto.domain.LottoGame; import lotto.domain.LottoResult; +import lotto.domain.Money; +import lotto.domain.WinningLotto; import lotto.view.InputView; import lotto.view.ResultView; @@ -10,11 +11,10 @@ public class Main { public static void main(String[] args) { long price = InputView.initLottoPrice(); LottoGame lottoGame = new LottoGame(price); - ResultView.printLottos(lottoGame.lottos()); - - Lotto winningLotto = new Lotto(InputView.initWinningLotto()); - LottoResult result = lottoGame.check(winningLotto); - ResultView.printResult(result, lottoGame); + ResultView.printLottos(lottoGame); + WinningLotto winningLotto = new WinningLotto(InputView.initWinningLotto(), InputView.initBonusNumber()); + LottoResult result = lottoGame.findWinner(winningLotto); + ResultView.printResult(result, new Money(price)); } } diff --git a/src/main/java/lotto/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java index e8cadb2063..8e592aeadc 100644 --- a/src/main/java/lotto/domain/Lotto.java +++ b/src/main/java/lotto/domain/Lotto.java @@ -1,28 +1,48 @@ package lotto.domain; import java.util.*; +import java.util.stream.Collectors; public class Lotto { public static final int LOTTO_NUMBER_SIZE = 6; - private final Set numbers; + private final Set numbers; public Lotto() { - this(new TreeSet<>(LottoFactory.generateLotto())); + this(LottoFactory.generateLotto()); } public Lotto(String value) { this(splitAndParseInt(value)); } - private static Set splitAndParseInt(String value) { + public Lotto(Integer... numbers) { + this(Arrays.asList(numbers)); + } + + public Lotto(List numbers) { + this(convert(numbers)); + } + + public Lotto(Set numbers) { + validate(numbers); + this.numbers = Collections.unmodifiableSet(numbers); + } + + private static Set convert(List numbers) { + return numbers.stream() + .map(LottoNumber::from) + .collect(Collectors.toSet()); + } + + private static List splitAndParseInt(String value) { String[] split = getSplit(value); - Set numbers = strToIntSet(split); + List numbers = strToIntSet(split); return numbers; } - private static Set strToIntSet(String[] split) { - Set numbers = new TreeSet<>(); + private static List strToIntSet(String[] split) { + List numbers = new ArrayList<>(); for (String s : split) { numbers.add(Integer.parseInt(s)); } @@ -33,39 +53,24 @@ private static String[] getSplit(String value) { return value.split(","); } - public Lotto(Integer... numbers) { - this(new TreeSet<>(Arrays.asList(numbers))); - } - - public Lotto(Set numbers) { - validate(numbers); - this.numbers = Collections.unmodifiableSet(numbers); - } - - private void validate(Set numbers) { + private void validate(Set numbers) { if (numbers.size() != LOTTO_NUMBER_SIZE) { throw new IllegalArgumentException(); } - - boolean invalidNumber = numbers.stream() - .anyMatch(n -> n < 1 || n > 45); - if (invalidNumber) { - throw new IllegalArgumentException(); - } } public int match(Lotto winningLotto) { int count = 0; - for (Integer number : numbers) { - if (winningLotto.numbers().contains(number)) { + for (LottoNumber number : numbers) { + if (winningLotto.contains(number)) { count++; } } return count; } - private Set numbers() { - return numbers; + public boolean contains(LottoNumber number) { + return numbers.contains(number); } @Override diff --git a/src/main/java/lotto/domain/LottoGame.java b/src/main/java/lotto/domain/LottoGame.java index 160af474ab..6dc73f3325 100644 --- a/src/main/java/lotto/domain/LottoGame.java +++ b/src/main/java/lotto/domain/LottoGame.java @@ -1,6 +1,6 @@ package lotto.domain; -import java.util.List; +import java.util.stream.Collectors; public class LottoGame { private final Lottos lottos; @@ -19,15 +19,18 @@ public LottoGame(Lottos lottos, Money money) { this.money = money; } - public List lottos() { - return lottos.values(); + public LottoResult findWinner(WinningLotto winningLotto) { + return lottos.findResult(winningLotto); } - public LottoResult check(Lotto winningLotto) { - return lottos.findResult(winningLotto); + public int lottoCount() { + return lottos.size(); } - public double rateOfReturn(LottoResult result) { - return money.rateOfReturn(result.getTotal()); + @Override + public String toString() { + return lottos.values().stream() + .map(Lotto::toString) + .collect(Collectors.joining("\n")); } } diff --git a/src/main/java/lotto/domain/LottoNumber.java b/src/main/java/lotto/domain/LottoNumber.java new file mode 100644 index 0000000000..3c7a481da0 --- /dev/null +++ b/src/main/java/lotto/domain/LottoNumber.java @@ -0,0 +1,53 @@ +package lotto.domain; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.IntStream; + +public class LottoNumber { + public static final int MIN_NUMBER = 1; + public static final int MAX_NUMBER = 45; + + private static final Map CACHE = new HashMap<>(); + + static { + IntStream.rangeClosed(MIN_NUMBER, MAX_NUMBER) + .forEach(i -> CACHE.put(i, new LottoNumber(i))); + } + + private final int lottoNumber; + + private LottoNumber(int lottoNumber) { + this.lottoNumber = lottoNumber; + } + + public static LottoNumber from(int lottoNumber) { + validate(lottoNumber); + return CACHE.get(lottoNumber); + } + + private static void validate(int lottoNumber) { + if (lottoNumber < MIN_NUMBER || lottoNumber > MAX_NUMBER) { + throw new IllegalArgumentException(); + } + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + LottoNumber that = (LottoNumber) object; + return lottoNumber == that.lottoNumber; + } + + @Override + public int hashCode() { + return Objects.hashCode(lottoNumber); + } + + @Override + public String toString() { + return String.valueOf(lottoNumber); + } +} diff --git a/src/main/java/lotto/domain/LottoResult.java b/src/main/java/lotto/domain/LottoResult.java index f79e85165c..75c6cfa66b 100644 --- a/src/main/java/lotto/domain/LottoResult.java +++ b/src/main/java/lotto/domain/LottoResult.java @@ -12,8 +12,7 @@ public LottoResult() { } } - public void addMatch(int match) { - Rank rank = Rank.from(match); + public void addMatch(Rank rank) { lottoResult.put(rank, lottoResult.get(rank) + 1); } @@ -26,4 +25,8 @@ public long getTotal() { .mapToLong(entry -> entry.getKey().prize() * entry.getValue()) .sum(); } + + public double profitRate(Money money) { + return getTotal() / money.getMoney(); + } } diff --git a/src/main/java/lotto/domain/Lottos.java b/src/main/java/lotto/domain/Lottos.java index 088a09b941..10c4288c57 100644 --- a/src/main/java/lotto/domain/Lottos.java +++ b/src/main/java/lotto/domain/Lottos.java @@ -17,12 +17,15 @@ public List values() { return lottos; } - public LottoResult findResult(Lotto winningLotto) { + public LottoResult findResult(WinningLotto winningLotto) { LottoResult result = new LottoResult(); for (Lotto lotto : lottos) { - int match = lotto.match(winningLotto); - result.addMatch(match); + result.addMatch(winningLotto.match(lotto)); } return result; } + + public int size() { + return lottos.size(); + } } diff --git a/src/main/java/lotto/domain/Matchable.java b/src/main/java/lotto/domain/Matchable.java deleted file mode 100644 index 0cde7a5764..0000000000 --- a/src/main/java/lotto/domain/Matchable.java +++ /dev/null @@ -1,5 +0,0 @@ -package lotto.domain; - -public interface Matchable { - boolean isMatch(int matchCount); -} diff --git a/src/main/java/lotto/domain/Money.java b/src/main/java/lotto/domain/Money.java index 1aa8e8786d..2c4cd64c0f 100644 --- a/src/main/java/lotto/domain/Money.java +++ b/src/main/java/lotto/domain/Money.java @@ -20,7 +20,7 @@ public int buyCount() { return (int) (money / LOTTO_PRICE); } - public double rateOfReturn(long getTotal) { - return Math.round((double) (getTotal / money) * 100) / 100.0; + public double getMoney() { + return money; } } diff --git a/src/main/java/lotto/domain/Rank.java b/src/main/java/lotto/domain/Rank.java index d6c63f3ea7..fe414feec7 100644 --- a/src/main/java/lotto/domain/Rank.java +++ b/src/main/java/lotto/domain/Rank.java @@ -4,6 +4,7 @@ public enum Rank { FIRST(6, 2_000_000_000), + SECOND(5, 30_000_000), THIRD(5, 1_500_000), FOURTH(4, 50_000), FIFTH(3, 5_000), @@ -29,10 +30,18 @@ public boolean isMatch(int match) { return this.match == match; } - public static Rank from(int matchCount) { + public static Rank from(int matchCount, boolean matchBonus) { return Arrays.stream(values()) .filter(rank -> rank.isMatch(matchCount)) .findFirst() + .map(rank -> bonusCheck(rank, matchBonus)) .orElse(NONE); } + + private static Rank bonusCheck(Rank rank, boolean matchBonus) { + if (rank == Rank.THIRD && matchBonus) { + return Rank.SECOND; + } + return rank; + } } diff --git a/src/main/java/lotto/domain/WinningLotto.java b/src/main/java/lotto/domain/WinningLotto.java new file mode 100644 index 0000000000..35d138f97d --- /dev/null +++ b/src/main/java/lotto/domain/WinningLotto.java @@ -0,0 +1,30 @@ +package lotto.domain; + +public class WinningLotto { + private final Lotto winningLotto; + private final LottoNumber bonusNumber; + + public WinningLotto(String lotto, int bonusNumber) { + this(new Lotto(lotto), bonusNumber); + } + + public WinningLotto(Lotto lotto, int bonusNumber) { + this(lotto, LottoNumber.from(bonusNumber)); + } + + public WinningLotto(Lotto lotto, LottoNumber bonusNumber) { + validateBonusNumber(lotto, bonusNumber); + this.winningLotto = lotto; + this.bonusNumber = bonusNumber; + } + + private void validateBonusNumber(Lotto lotto, LottoNumber bonusNumber) { + if (lotto.contains(bonusNumber)) { + throw new IllegalArgumentException(); + } + } + + public Rank match(Lotto lotto) { + return Rank.from(winningLotto.match(lotto), lotto.contains(bonusNumber)); + } +} diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java index 426b1075f7..43359db3ff 100644 --- a/src/main/java/lotto/view/InputView.java +++ b/src/main/java/lotto/view/InputView.java @@ -14,4 +14,9 @@ public static String initWinningLotto() { System.out.println("지난 주 당첨 번호를 입력해 주세요."); return SCANNER.nextLine(); } + + public static int initBonusNumber() { + System.out.println("보너스 볼을 입력해 주세요."); + return SCANNER.nextInt(); + } } diff --git a/src/main/java/lotto/view/ResultView.java b/src/main/java/lotto/view/ResultView.java index 946aef11e6..5d6c2cb898 100644 --- a/src/main/java/lotto/view/ResultView.java +++ b/src/main/java/lotto/view/ResultView.java @@ -1,32 +1,32 @@ package lotto.view; -import lotto.domain.Lotto; import lotto.domain.LottoGame; import lotto.domain.LottoResult; +import lotto.domain.Money; import lotto.domain.Rank; -import java.util.List; - public class ResultView { - public static void printLottos(List lottos) { - System.out.println(lottos.size() + "개를 구매했습니다."); - for (Lotto lotto : lottos) { - System.out.println(lotto); - } + public static void printLottos(LottoGame lottoGame) { + System.out.println(lottoGame.lottoCount() + "개를 구매했습니다."); + System.out.println(lottoGame); } - public static void printResult(LottoResult result, LottoGame lottoGame) { + public static void printResult(LottoResult result, Money money) { System.out.println("당첨 통계"); System.out.println("---------"); for (Rank rank : Rank.values()) { + if (rank == Rank.SECOND) { + System.out.println(rank.match() + "개 일치, 보너스 볼 일치 (" + rank.prize() + "원) - " + result.getCount(rank) + "개"); + continue; + } System.out.println(rank.match() + "개 일치 (" + rank.prize() + "원) - " + result.getCount(rank) + "개"); } - printRateOfReturn(result, lottoGame); + printRateOfReturn(result, money); } - private static void printRateOfReturn(LottoResult result, LottoGame lottoGame) { - double rate = lottoGame.rateOfReturn(result); + private static void printRateOfReturn(LottoResult result, Money money) { + double rate = result.profitRate(money); String message = String.format("총 수익률은 %.2f입니다.", rate); if (rate < 1.0) { message += "(기준이 1이기 때문에 결과적으로 손해라는 의미임)"; diff --git a/src/test/java/lotto/domain/LottoGameTest.java b/src/test/java/lotto/domain/LottoGameTest.java index 364fdc243d..d8a018f437 100644 --- a/src/test/java/lotto/domain/LottoGameTest.java +++ b/src/test/java/lotto/domain/LottoGameTest.java @@ -12,25 +12,6 @@ class LottoGameTest { @Test @DisplayName("입력된 금액만큼의 로또가 생성되어야 한다") void generate_lotto() { - assertThat(new LottoGame(14000).lottos()).hasSize(14); - } - - @Test - @DisplayName("2개 생성 - 1등, 4등") - void winningResult() { - Lottos lottos = new Lottos(List.of(new Lotto(1, 2, 3, 4, 5, 6), new Lotto(1, 2, 3, 9, 10, 11))); - Money money = new Money(2000); - assertThat(new LottoGame(lottos, money).check(new Lotto(1, 2, 3, 4, 5, 6)).getCount(Rank.FIRST)).isEqualTo(1); - assertThat(new LottoGame(lottos, money).check(new Lotto(1, 2, 3, 4, 5, 6)).getCount(Rank.FIFTH)).isEqualTo(1); - } - - @Test - @DisplayName("2_000원으로 10_000원을 벌면 수익률은 5이다") - void rateOfReturn() { - Lottos lottos = new Lottos(List.of(new Lotto(1, 2, 3, 4, 5, 6), new Lotto(1, 2, 3, 9, 10, 11))); - Money money = new Money(2000); - LottoGame lottoGame = new LottoGame(lottos, money); - LottoResult result = new LottoGame(lottos, money).check(new Lotto(1, 2, 3, 20, 23, 45)); - assertThat(lottoGame.rateOfReturn(result)).isEqualTo(5.0); + assertThat(new LottoGame(14000).lottoCount()).isEqualTo(14); } } \ No newline at end of file diff --git a/src/test/java/lotto/domain/LottoNumberTest.java b/src/test/java/lotto/domain/LottoNumberTest.java new file mode 100644 index 0000000000..5410d2dd66 --- /dev/null +++ b/src/test/java/lotto/domain/LottoNumberTest.java @@ -0,0 +1,22 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +class LottoNumberTest { + + @Test + @DisplayName("같은 값을 가지면, 동일한 로또 번호이다") + void init_equals() { + assertThat(LottoNumber.from(45)).isEqualTo(LottoNumber.from(45)); + } + + @Test + @DisplayName("로또번호는 1부터 45사이의 값을 가져야한다") + void init_lottoNumber() { + assertThatThrownBy(() -> LottoNumber.from(46)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> LottoNumber.from(0)).isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/LottoResultTest.java b/src/test/java/lotto/domain/LottoResultTest.java index 79b84b0e9b..5613a52851 100644 --- a/src/test/java/lotto/domain/LottoResultTest.java +++ b/src/test/java/lotto/domain/LottoResultTest.java @@ -19,7 +19,7 @@ void result_init() { @DisplayName("6개가 일치하면, FIRST 랭크가 올라간다") void result_first() { LottoResult result = new LottoResult(); - result.addMatch(6); + result.addMatch(Rank.FIRST); assertThat(result.getCount(Rank.FIRST)).isEqualTo(1); } @@ -27,7 +27,7 @@ void result_first() { @DisplayName("일치하지 않으면, None이 매칭된다") void result_none() { LottoResult result = new LottoResult(); - result.addMatch(0); + result.addMatch(Rank.NONE); assertThat(result.getCount(Rank.NONE)).isEqualTo(1); } @@ -35,7 +35,17 @@ void result_none() { @DisplayName("3개 일치 시 당청금은 5000원이다") void getTotal() { LottoResult result = new LottoResult(); - result.addMatch(3); + result.addMatch(Rank.FIFTH); assertThat(result.getTotal()).isEqualTo(5000); } + + @Test + @DisplayName("수익률 계산 - 구매 2_000, 당첨금 10_000, 수익률 5") + void profitRate() { + LottoResult result = new LottoResult(); + result.addMatch(Rank.FIFTH); + result.addMatch(Rank.FIFTH); + double rate = result.profitRate(new Money(2000)); + assertThat(rate).isEqualTo(5); + } } \ No newline at end of file diff --git a/src/test/java/lotto/domain/LottoTest.java b/src/test/java/lotto/domain/LottoTest.java index 6e96e0443c..72777fd356 100644 --- a/src/test/java/lotto/domain/LottoTest.java +++ b/src/test/java/lotto/domain/LottoTest.java @@ -14,12 +14,6 @@ void lotto_generate() { assertThatThrownBy(() -> new Lotto(1, 2, 3, 4, 5, 6, 7)).isInstanceOf(IllegalArgumentException.class); } - @Test - @DisplayName("로또는 1부터 45까지의 숫자를 가진다") - void lotto_number_limit() { - assertThatThrownBy(() -> new Lotto(1, 2, 3, 4, 5, 60)).isInstanceOf(IllegalArgumentException.class); - } - @Test @DisplayName("일치한 숫자만큼 count가 발생한다") void match() { diff --git a/src/test/java/lotto/domain/LottosTest.java b/src/test/java/lotto/domain/LottosTest.java index 4046bfc01f..38a290df10 100644 --- a/src/test/java/lotto/domain/LottosTest.java +++ b/src/test/java/lotto/domain/LottosTest.java @@ -16,10 +16,13 @@ void size() { } @Test - @DisplayName("LottoResult") + @DisplayName("1등 당첨") void findResult() { - Lottos lottos = new Lottos(List.of(new Lotto(1, 2, 3, 4, 5, 6) - , new Lotto(1, 2, 3, 9, 10, 11))); - assertThat(lottos.findResult(new Lotto(1, 2, 3, 4, 5, 6))).isNotNull(); + Lottos userLottos = new Lottos(List.of(new Lotto(1, 2, 3, 4, 5, 6))); + WinningLotto winningLotto = new WinningLotto(new Lotto(1, 2, 3, 4, 5, 6), 7); + + LottoResult result = userLottos.findResult(winningLotto); + + assertThat(result.getCount(Rank.FIRST)).isEqualTo(1); } } \ No newline at end of file diff --git a/src/test/java/lotto/domain/WinningLottoTest.java b/src/test/java/lotto/domain/WinningLottoTest.java new file mode 100644 index 0000000000..0a8650a5bc --- /dev/null +++ b/src/test/java/lotto/domain/WinningLottoTest.java @@ -0,0 +1,15 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +class WinningLottoTest { + + @Test + @DisplayName("보너스 번호는 당첨번호와 달라양한다") + void bonusNumberCheck_duplicate() { + assertThatThrownBy(() -> new WinningLotto(new Lotto(1, 2, 3, 4, 5, 6), 6)).isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file