diff --git a/src/main/kotlin/lotto/Lotto.kt b/src/main/kotlin/lotto/Lotto.kt deleted file mode 100644 index 160d35a65..000000000 --- a/src/main/kotlin/lotto/Lotto.kt +++ /dev/null @@ -1,26 +0,0 @@ -package lotto - -class Lotto private constructor(val lottoNumbers: List) { - - init { - val lottoNumbersSize = lottoNumbers.size - require(lottoNumbersSize == LOTTO_SIZE) - require(lottoNumbersSize == lottoNumbers.toSet().size) - } - - constructor() : this((1..45).shuffled().take(6).sorted().map { LottoNumber(it) }) - - constructor(vararg number: Int) : this(number.map { LottoNumber(it) }) - - fun countMatch(winningLotto: Lotto): Int { - return lottoNumbers.intersect(winningLotto.lottoNumbers.toSet()).count() - } - - override fun toString(): String { - return "[${lottoNumbers.joinToString(", ")}]" - } - - companion object { - private const val LOTTO_SIZE = 6 - } -} diff --git a/src/main/kotlin/lotto/LottoNumber.kt b/src/main/kotlin/lotto/LottoNumber.kt deleted file mode 100644 index 430374156..000000000 --- a/src/main/kotlin/lotto/LottoNumber.kt +++ /dev/null @@ -1,12 +0,0 @@ -package lotto - -@JvmInline -value class LottoNumber(val number: Int) { - init { - require(number in 1..45) - } - - override fun toString(): String { - return number.toString() - } -} diff --git a/src/main/kotlin/lotto/LottoResult.kt b/src/main/kotlin/lotto/LottoResult.kt deleted file mode 100644 index cafe0b78d..000000000 --- a/src/main/kotlin/lotto/LottoResult.kt +++ /dev/null @@ -1,31 +0,0 @@ -package lotto - -class LottoResult private constructor(private val result: Map) : Map by result { - private val totalPrizeMoney: Int - get() { - return result.map { - it.key.money * it.value - }.sum() - } - - override fun get(key: Prize): Int { - return result[key] ?: 0 - } - - fun getRateOfReturn(money: Int): Float { - return totalPrizeMoney.toFloat() / money - } - - companion object { - fun makeLottoResult( - winningLotto: Lotto, - lottos: List, - ): LottoResult { - val result = lottos.groupingBy { lotto -> - val count = lotto.countMatch(winningLotto) - Prize.from(count) - }.eachCount() - return LottoResult(result) - } - } -} diff --git a/src/main/kotlin/lotto/LottoVendingMachine.kt b/src/main/kotlin/lotto/LottoVendingMachine.kt deleted file mode 100644 index 8ff596d95..000000000 --- a/src/main/kotlin/lotto/LottoVendingMachine.kt +++ /dev/null @@ -1,16 +0,0 @@ -package lotto - -object LottoVendingMachine { - private const val LOTTO_PRICE = 1000 - - fun buyLotto(money: Int?): List { - requireNotNull(money) { - "구매 금액은 null이 아니어야 함" - } - require(money >= LOTTO_PRICE) { - "구매 금액은 1000 보다 커야 함" - } - - return List(money / LOTTO_PRICE) { Lotto() } - } -} diff --git a/src/main/kotlin/lotto/Prize.kt b/src/main/kotlin/lotto/Prize.kt deleted file mode 100644 index c2bfe763a..000000000 --- a/src/main/kotlin/lotto/Prize.kt +++ /dev/null @@ -1,17 +0,0 @@ -package lotto - -enum class Prize(val money: Int, val count: Int) { - FIRST(2_000_000_000, 6), - SECOND(1_500_000, 5), - THIRD(50_000, 4), - FOURTH(5_000, 3), - NONE(0, 0), - ; - - companion object { - fun from(count: Int): Prize { - return entries.find { it.count == count } ?: NONE - } - } - -} diff --git a/src/main/kotlin/lotto/application/LottoApplication.kt b/src/main/kotlin/lotto/application/LottoApplication.kt index 5335339a5..c70b71b80 100644 --- a/src/main/kotlin/lotto/application/LottoApplication.kt +++ b/src/main/kotlin/lotto/application/LottoApplication.kt @@ -1,8 +1,8 @@ package lotto.application -import lotto.Lotto -import lotto.LottoResult -import lotto.LottoVendingMachine +import lotto.domain.Lotto +import lotto.domain.LottoNumber +import lotto.domain.Lottos import lotto.view.InputView import lotto.view.OutputView @@ -12,18 +12,17 @@ class LottoApplication( ) { fun run() { - outputView.showInputMoneyMessage() val money = inputView.inputMoney() - val lotto = LottoVendingMachine.buyLotto(money) + val lotto = Lottos.buyLotto(money) outputView.showLottoCount(lotto) outputView.showLotto(lotto) - outputView.showInputWinningNumbersMessage() - val winningNumbers: List = inputView.inputWinningNumbers() + val winningLotto: Lotto = inputView.inputWinningNumbers() + val bonusNumber: LottoNumber = inputView.inputBonusNumber() - val lottoResult= LottoResult.makeLottoResult( - winningLotto = Lotto(*winningNumbers.toIntArray()), - lottos = lotto, + val lottoResult = lotto.getResult( + winningLotto = winningLotto, + bonusLottoNumber = bonusNumber ) outputView.showResult(lottoResult, money) } diff --git a/src/main/kotlin/lotto/domain/Lotto.kt b/src/main/kotlin/lotto/domain/Lotto.kt new file mode 100644 index 000000000..fd3a294ba --- /dev/null +++ b/src/main/kotlin/lotto/domain/Lotto.kt @@ -0,0 +1,35 @@ +package lotto.domain + +class Lotto private constructor(val lottoNumbers: Set) { + + init { + val lottoNumbersSize = lottoNumbers.size + require(lottoNumbersSize == LOTTO_SIZE) + } + + constructor() : this( + LottoPreset.shuffled() + .take(6) + .sortedBy { it.number } + .toSet() + ) + + constructor(vararg number: Int) : this( + number.map(::LottoNumber) + .toSet() + ) + + fun countMatch(winningLotto: Lotto): Int { + return lottoNumbers.intersect(winningLotto.lottoNumbers) + .count() + } + + operator fun contains(lottoNumber: LottoNumber): Boolean { + return lottoNumber in lottoNumbers + } + + companion object { + private val LottoPreset = List(LottoNumber.END_LOTTO_NUMBER) { LottoNumber(it + 1) } + private const val LOTTO_SIZE = 6 + } +} diff --git a/src/main/kotlin/lotto/domain/LottoNumber.kt b/src/main/kotlin/lotto/domain/LottoNumber.kt new file mode 100644 index 000000000..5f6a5116b --- /dev/null +++ b/src/main/kotlin/lotto/domain/LottoNumber.kt @@ -0,0 +1,14 @@ +package lotto.domain + +@JvmInline +value class LottoNumber(val number: Int) { + init { + require(number in lottoNumberRange) + } + + companion object { + const val START_LOTTO_NUMBER = 1 + const val END_LOTTO_NUMBER = 45 + val lottoNumberRange = START_LOTTO_NUMBER..END_LOTTO_NUMBER + } +} diff --git a/src/main/kotlin/lotto/domain/LottoResult.kt b/src/main/kotlin/lotto/domain/LottoResult.kt new file mode 100644 index 000000000..057417ba9 --- /dev/null +++ b/src/main/kotlin/lotto/domain/LottoResult.kt @@ -0,0 +1,18 @@ +package lotto.domain + +class LottoResult(private val result: Map) : Map by result { + private val totalPrizeMoney: Long + get() { + return result.map { + it.key.money.toLong() * it.value + }.sum() + } + + override fun get(key: Prize): Int { + return result[key] ?: 0 + } + + fun getRateOfReturn(money: Int): Float { + return totalPrizeMoney.toFloat() / money + } +} diff --git a/src/main/kotlin/lotto/domain/Lottos.kt b/src/main/kotlin/lotto/domain/Lottos.kt new file mode 100644 index 000000000..1bb081b85 --- /dev/null +++ b/src/main/kotlin/lotto/domain/Lottos.kt @@ -0,0 +1,31 @@ +package lotto.domain + +class Lottos(lottos: List) { + val lottos: List = lottos.toList() + + fun getResult( + winningLotto: Lotto, + bonusLottoNumber: LottoNumber, + ): LottoResult { + val result = lottos.groupingBy { lotto -> + val matchCount = lotto.countMatch(winningLotto) + Prize.of(matchCount = matchCount, bonusLottoNumber = bonusLottoNumber, lotto = lotto) + }.eachCount() + return LottoResult(result) + } + + companion object { + private const val LOTTO_PRICE = 1000 + + fun buyLotto(money: Int?): Lottos { + requireNotNull(money) { + "구매 금액은 null이 아니어야 함" + } + require(money >= LOTTO_PRICE) { + "구매 금액은 1000 보다 커야 함" + } + + return Lottos(List(money / LOTTO_PRICE) { Lotto() }) + } + } +} diff --git a/src/main/kotlin/lotto/domain/Prize.kt b/src/main/kotlin/lotto/domain/Prize.kt new file mode 100644 index 000000000..50a35e37b --- /dev/null +++ b/src/main/kotlin/lotto/domain/Prize.kt @@ -0,0 +1,21 @@ +package lotto.domain + +enum class Prize(val money: Int, val count: Int) { + FIRST(2_000_000_000, 6), + SECOND(30_000_000, 5), + THIRD(1_500_000, 5), + FOURTH(50_000, 4), + FIFTH(5_000, 3), + NONE(0, 0), + ; + + companion object { + fun of(matchCount: Int, bonusLottoNumber: LottoNumber, lotto: Lotto) = when (matchCount) { + 6 -> FIRST + 5 -> if (bonusLottoNumber in lotto) SECOND else THIRD + 4 -> FOURTH + 3 -> FIFTH + else -> NONE + } + } +} diff --git a/src/main/kotlin/lotto/view/InputView.kt b/src/main/kotlin/lotto/view/InputView.kt index 0caa6776e..4f42b9847 100644 --- a/src/main/kotlin/lotto/view/InputView.kt +++ b/src/main/kotlin/lotto/view/InputView.kt @@ -1,13 +1,28 @@ package lotto.view +import lotto.domain.Lotto +import lotto.domain.LottoNumber + class InputView { fun inputMoney(): Int { + println("구입금액을 입력해 주세요.") val input = readlnOrNull()?.toIntOrNull() requireNotNull(input) return input } - fun inputWinningNumbers(): List { - return readlnOrNull()?.split(",")?.map { it.trim().toInt() } ?: emptyList() + fun inputWinningNumbers(): Lotto { + println("지난 주 당첨 번호를 입력해 주세요.") + val winningNumbers: List = readlnOrNull()?.split(",") + ?.map { + it.trim() + .toInt() + } ?: emptyList() + return Lotto(*winningNumbers.toIntArray()) + } + + fun inputBonusNumber(): LottoNumber { + println("보너스 볼을 입력해 주세요.") + return LottoNumber(readlnOrNull()?.toIntOrNull() ?: 0) } } diff --git a/src/main/kotlin/lotto/view/OutputView.kt b/src/main/kotlin/lotto/view/OutputView.kt index a4b61fe20..e5eb41963 100644 --- a/src/main/kotlin/lotto/view/OutputView.kt +++ b/src/main/kotlin/lotto/view/OutputView.kt @@ -1,26 +1,25 @@ package lotto.view -import lotto.Lotto -import lotto.LottoResult -import lotto.Prize +import lotto.domain.LottoResult +import lotto.domain.Lottos +import lotto.domain.Prize class OutputView { - fun showInputMoneyMessage() { - println("구입금액을 입력해 주세요.") + fun showLottoCount(lottos: Lottos) { + val size = lottos.lottos + .size + println("${size}개를 구매했습니다.") } - fun showLottoCount(lotto: List) { - println("${lotto.size}개를 구매했습니다.") - } - - fun showLotto(lotto: List) { - lotto.forEach { - println(it) - } - } - - fun showInputWinningNumbersMessage() { - println("지난 주 당첨 번호를 입력해 주세요.") + fun showLotto(lottos: Lottos) { + lottos.lottos + .forEach { + println( + "[${ + it.lottoNumbers.map { lottoNumber -> lottoNumber.number } + .joinToString(", ") + }]") + } } fun showResult(lottoResult: LottoResult, money: Int) { @@ -28,9 +27,14 @@ class OutputView { println("---------") Prize.entries .filter { it != Prize.NONE } - .sortedByDescending { it.count } + .reversed() .forEach { prize -> - println("${prize.count}개 일치 (${prize.money}원) - ${lottoResult[prize]}개") + val title = if (prize == Prize.SECOND) { + "${prize.count}개 일치, 보너스 볼 일치 (${prize.money}원)" + } else { + "${prize.count}개 일치 (${prize.money}원)" + } + println("$title - ${lottoResult[prize]}개") } println(String.format("총 수익률은 %.2f입니다.", lottoResult.getRateOfReturn(money))) diff --git a/src/test/kotlin/lotto/LottoNumberTest.kt b/src/test/kotlin/lotto/LottoNumberTest.kt index 310781c52..14775d969 100644 --- a/src/test/kotlin/lotto/LottoNumberTest.kt +++ b/src/test/kotlin/lotto/LottoNumberTest.kt @@ -2,6 +2,7 @@ package lotto import io.kotest.assertions.throwables.shouldNotThrow import io.kotest.assertions.throwables.shouldThrow +import lotto.domain.LottoNumber import org.junit.jupiter.api.Test class LottoNumberTest { diff --git a/src/test/kotlin/lotto/LottoResultTest.kt b/src/test/kotlin/lotto/LottoResultTest.kt index da591f4a4..5dcd3574b 100644 --- a/src/test/kotlin/lotto/LottoResultTest.kt +++ b/src/test/kotlin/lotto/LottoResultTest.kt @@ -1,40 +1,61 @@ package lotto import io.kotest.matchers.shouldBe -import org.junit.jupiter.api.Test +import lotto.domain.Lotto +import lotto.domain.LottoNumber +import lotto.domain.Lottos +import lotto.domain.Prize +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource class LottoResultTest { - - @Test - internal fun `구매한 로또와 결과가 일치`() { + @ParameterizedTest + @MethodSource("provideLottoTestCases") + internal fun `로또 결과 테스트`(testCase: TestCase) { val winningLotto = Lotto(1, 2, 3, 4, 5, 6) + val bonusLottoNumber = LottoNumber(7) - val lottos = mutableListOf().apply { - add(Lotto(8, 21, 23, 41, 42, 43)) - add(Lotto(3, 5, 11, 16, 32, 38)) - add(Lotto(7, 11, 16, 35, 36, 44)) - add(Lotto(1, 8, 11, 31, 41, 42)) - add(Lotto(13, 14, 16, 38, 42, 45)) - add(Lotto(7, 11, 30, 40, 42, 43)) - add(Lotto(2, 13, 22, 32, 38, 45)) - add(Lotto(23, 25, 33, 36, 39, 41)) - add(Lotto(1, 3, 5, 14, 22, 45)) - add(Lotto(5, 9, 38, 41, 43, 44)) - add(Lotto(2, 8, 9, 18, 19, 21)) - add(Lotto(13, 14, 18, 21, 23, 35)) - add(Lotto(17, 21, 29, 37, 42, 45)) - add(Lotto(3, 8, 27, 30, 35, 44)) - } - - val lottoResult = LottoResult.makeLottoResult( + val lottoResult = testCase.lottos.getResult( winningLotto = winningLotto, - lottos = lottos + bonusLottoNumber = bonusLottoNumber ) - lottoResult[Prize.FIRST] shouldBe 0 - lottoResult[Prize.SECOND] shouldBe 0 - lottoResult[Prize.THIRD] shouldBe 0 - lottoResult[Prize.FOURTH] shouldBe 1 - lottoResult[Prize.NONE] shouldBe 13 + lottoResult[testCase.expectedPrize] shouldBe 1 } + + data class TestCase( + val lottos: Lottos, + val expectedPrize: Prize + ) + + companion object { + @JvmStatic + fun provideLottoTestCases() = listOf( + TestCase( + lottos = Lottos(listOf(Lotto(1, 2, 3, 4, 5, 6))), + expectedPrize = Prize.FIRST + ), + TestCase( + lottos = Lottos(listOf(Lotto(1, 2, 3, 4, 5, 7))), + expectedPrize = Prize.SECOND + ), + TestCase( + lottos = Lottos(listOf(Lotto(1, 2, 3, 4, 5, 8))), + expectedPrize = Prize.THIRD + ), + TestCase( + lottos = Lottos(listOf(Lotto(1, 2, 3, 4, 8, 9))), + expectedPrize = Prize.FOURTH + ), + TestCase( + lottos = Lottos(listOf(Lotto(1, 2, 3, 8, 9, 10))), + expectedPrize = Prize.FIFTH + ), + TestCase( + lottos = Lottos(listOf(Lotto(1, 2, 8, 9, 10, 11))), + expectedPrize = Prize.NONE + ) + ) + } + } diff --git a/src/test/kotlin/lotto/LottoTest.kt b/src/test/kotlin/lotto/LottoTest.kt index 62243aa48..741c5d584 100644 --- a/src/test/kotlin/lotto/LottoTest.kt +++ b/src/test/kotlin/lotto/LottoTest.kt @@ -1,6 +1,7 @@ package lotto import io.kotest.matchers.shouldBe +import lotto.domain.Lotto import org.junit.jupiter.api.Test class LottoTest { diff --git a/src/test/kotlin/lotto/LottoVendingMachineTest.kt b/src/test/kotlin/lotto/LottosTest.kt similarity index 67% rename from src/test/kotlin/lotto/LottoVendingMachineTest.kt rename to src/test/kotlin/lotto/LottosTest.kt index 180f2cc17..69f1993ed 100644 --- a/src/test/kotlin/lotto/LottoVendingMachineTest.kt +++ b/src/test/kotlin/lotto/LottosTest.kt @@ -3,33 +3,34 @@ package lotto import io.kotest.assertions.throwables.shouldNotThrow import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe +import lotto.domain.Lottos import org.junit.jupiter.api.Test -class LottoVendingMachineTest { +class LottosTest { @Test internal fun `로또 구매 금액은 0이거나 null이 아니어야 한다`() { shouldThrow { - LottoVendingMachine.buyLotto(0) + Lottos.buyLotto(0) } shouldThrow { - LottoVendingMachine.buyLotto(null) + Lottos.buyLotto(null) } } @Test internal fun `로또 구매 금액은 1000이상이어야 한다`() { shouldThrow { - LottoVendingMachine.buyLotto(999) + Lottos.buyLotto(999) } shouldNotThrow { - LottoVendingMachine.buyLotto(1000) + Lottos.buyLotto(1000) } } @Test internal fun `로또 한 장의 금액은 1000원 이다`() { - LottoVendingMachine.buyLotto(1000).size shouldBe 1 - LottoVendingMachine.buyLotto(14000).size shouldBe 14 + Lottos.buyLotto(1000).lottos.size shouldBe 1 + Lottos.buyLotto(14000).lottos.size shouldBe 14 } } diff --git a/src/test/kotlin/lotto/PrizeTest.kt b/src/test/kotlin/lotto/PrizeTest.kt index f29b9e8e8..11998f4b5 100644 --- a/src/test/kotlin/lotto/PrizeTest.kt +++ b/src/test/kotlin/lotto/PrizeTest.kt @@ -1,26 +1,85 @@ package lotto import io.kotest.matchers.shouldBe +import lotto.domain.Lotto +import lotto.domain.LottoNumber +import lotto.domain.Prize import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource class PrizeTest { @Test internal fun `로또 당첨 금액`() { Prize.FIRST.money shouldBe 2_000_000_000 - Prize.SECOND.money shouldBe 1_500_000 - Prize.THIRD.money shouldBe 50_000 - Prize.FOURTH.money shouldBe 5_000 + Prize.SECOND.money shouldBe 30_000_000 + Prize.THIRD.money shouldBe 1_500_000 + Prize.FOURTH.money shouldBe 50_000 + Prize.FIFTH.money shouldBe 5_000 Prize.NONE.money shouldBe 0 } @Test - internal fun `일치하는 개수에 맞는 등수`() { - Prize.from(6) shouldBe Prize.FIRST - Prize.from(5) shouldBe Prize.SECOND - Prize.from(4) shouldBe Prize.THIRD - Prize.from(3) shouldBe Prize.FOURTH - Prize.from(2) shouldBe Prize.NONE - Prize.from(1) shouldBe Prize.NONE - Prize.from(0) shouldBe Prize.NONE + fun `당첨 개수`() { + Prize.FIRST.count shouldBe 6 + Prize.SECOND.count shouldBe 5 + Prize.THIRD.count shouldBe 5 + Prize.FOURTH.count shouldBe 4 + Prize.FIFTH.count shouldBe 3 + Prize.NONE.count shouldBe 0 } + + @ParameterizedTest + @MethodSource("providePrizeTestCase") + fun `Prize 케이스`(testCase: TestCase) { + val prize = Prize.of( + matchCount = testCase.matchCount, + bonusLottoNumber = testCase.bonusLottoNumber, + lotto = Lotto(1, 2, 3, 4, 5, 6) + ) + prize shouldBe testCase.expectedPrize + } + + data class TestCase( + val matchCount: Int, + val bonusLottoNumber: LottoNumber, + val expectedPrize: Prize + ) + + companion object { + @JvmStatic + fun providePrizeTestCase() = listOf( + TestCase( + matchCount = 6, + bonusLottoNumber = LottoNumber(10), + expectedPrize = Prize.FIRST + ), + TestCase( + matchCount = 5, + bonusLottoNumber = LottoNumber(1), + expectedPrize = Prize.SECOND + ), + TestCase( + matchCount = 5, + bonusLottoNumber = LottoNumber(10), + expectedPrize = Prize.THIRD + ), + TestCase( + matchCount = 4, + bonusLottoNumber = LottoNumber(10), + expectedPrize = Prize.FOURTH + ), + TestCase( + matchCount = 3, + bonusLottoNumber = LottoNumber(10), + expectedPrize = Prize.FIFTH + ), + TestCase( + matchCount = 0, + bonusLottoNumber = LottoNumber(10), + expectedPrize = Prize.NONE + ) + ) + } + }