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 index 8ff596d95..2fcef65e4 100644 --- a/src/main/kotlin/lotto/LottoVendingMachine.kt +++ b/src/main/kotlin/lotto/LottoVendingMachine.kt @@ -1,9 +1,12 @@ package lotto +import lotto.domain.Lotto +import lotto.domain.Lottos + object LottoVendingMachine { private const val LOTTO_PRICE = 1000 - fun buyLotto(money: Int?): List { + fun buyLotto(money: Int?): Lottos { requireNotNull(money) { "구매 금액은 null이 아니어야 함" } @@ -11,6 +14,6 @@ object LottoVendingMachine { "구매 금액은 1000 보다 커야 함" } - return List(money / LOTTO_PRICE) { Lotto() } + return Lottos(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..bea29ec35 100644 --- a/src/main/kotlin/lotto/application/LottoApplication.kt +++ b/src/main/kotlin/lotto/application/LottoApplication.kt @@ -1,8 +1,9 @@ package lotto.application -import lotto.Lotto -import lotto.LottoResult +import lotto.domain.Lotto +import lotto.domain.LottoResult import lotto.LottoVendingMachine +import lotto.domain.LottoNumber import lotto.view.InputView import lotto.view.OutputView @@ -12,17 +13,17 @@ class LottoApplication( ) { fun run() { - outputView.showInputMoneyMessage() val money = inputView.inputMoney() val lotto = LottoVendingMachine.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()), + winningLotto = winningLotto, + bonusLottoNumber = bonusNumber, lottos = lotto, ) 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..11a698cd7 --- /dev/null +++ b/src/main/kotlin/lotto/domain/Lotto.kt @@ -0,0 +1,22 @@ +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(it) }.toSet()) + + fun countMatch(winningLotto: Lotto): Int { + return lottoNumbers.intersect(winningLotto.lottoNumbers).count() + } + + 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..b1eaf1ac4 --- /dev/null +++ b/src/main/kotlin/lotto/domain/LottoNumber.kt @@ -0,0 +1,13 @@ +package lotto.domain + +@JvmInline +value class LottoNumber(val number: Int) { + init { + require(number in START_LOTTO_NUMBER..END_LOTTO_NUMBER) + } + + companion object { + const val START_LOTTO_NUMBER = 1 + const val END_LOTTO_NUMBER = 45 + } +} diff --git a/src/main/kotlin/lotto/domain/LottoResult.kt b/src/main/kotlin/lotto/domain/LottoResult.kt new file mode 100644 index 000000000..1a5025fb8 --- /dev/null +++ b/src/main/kotlin/lotto/domain/LottoResult.kt @@ -0,0 +1,43 @@ +package lotto.domain + +class LottoResult private constructor(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 + } + + companion object { + fun makeLottoResult( + winningLotto: Lotto, + bonusLottoNumber: LottoNumber, + lottos: Lottos, + ): LottoResult { + val result = lottos.lottos.groupingBy { lotto -> + val matchCount = lotto.countMatch(winningLotto) + when (matchCount) { + 6 -> Prize.FIRST + 5 -> if (bonusLottoNumber in lotto.lottoNumbers) { + Prize.SECOND + } else { + Prize.THIRD + } + + 4 -> Prize.FOURTH + 3 -> Prize.FIFTH + else -> Prize.NONE + } + }.eachCount() + return LottoResult(result) + } + } +} diff --git a/src/main/kotlin/lotto/domain/Lottos.kt b/src/main/kotlin/lotto/domain/Lottos.kt new file mode 100644 index 000000000..83bc2e460 --- /dev/null +++ b/src/main/kotlin/lotto/domain/Lottos.kt @@ -0,0 +1,5 @@ +package lotto.domain + +class Lottos(lottos: List) { + val lottos: List = lottos.toList() +} diff --git a/src/main/kotlin/lotto/domain/Prize.kt b/src/main/kotlin/lotto/domain/Prize.kt new file mode 100644 index 000000000..2b94098ad --- /dev/null +++ b/src/main/kotlin/lotto/domain/Prize.kt @@ -0,0 +1,11 @@ +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), + ; +} diff --git a/src/main/kotlin/lotto/view/InputView.kt b/src/main/kotlin/lotto/view/InputView.kt index 0caa6776e..cbb0b1be3 100644 --- a/src/main/kotlin/lotto/view/InputView.kt +++ b/src/main/kotlin/lotto/view/InputView.kt @@ -1,13 +1,24 @@ 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..a9966005d 100644 --- a/src/main/kotlin/lotto/view/OutputView.kt +++ b/src/main/kotlin/lotto/view/OutputView.kt @@ -1,36 +1,33 @@ 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) { + println("${lottos.lottos.size}개를 구매했습니다.") } - fun showLottoCount(lotto: List) { - println("${lotto.size}개를 구매했습니다.") - } - - fun showLotto(lotto: List) { - lotto.forEach { - println(it) + fun showLotto(lottos: Lottos) { + lottos.lottos.forEach { + println("[${it.lottoNumbers.map { lottoNumber -> lottoNumber.number }.joinToString(", ")}]") } } - fun showInputWinningNumbersMessage() { - println("지난 주 당첨 번호를 입력해 주세요.") - } - fun showResult(lottoResult: LottoResult, money: Int) { println("당첨 통계") 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..10a0f4068 100644 --- a/src/test/kotlin/lotto/LottoResultTest.kt +++ b/src/test/kotlin/lotto/LottoResultTest.kt @@ -1,40 +1,121 @@ package lotto import io.kotest.matchers.shouldBe +import lotto.domain.Lotto +import lotto.domain.LottoNumber +import lotto.domain.LottoResult +import lotto.domain.Lottos +import lotto.domain.Prize import org.junit.jupiter.api.Test class LottoResultTest { @Test - internal fun `구매한 로또와 결과가 일치`() { + internal fun `6개가 일치하면 1등`() { 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 lottos = Lottos( + listOf(Lotto(1, 2, 3, 4, 5, 6)) + ) val lottoResult = LottoResult.makeLottoResult( winningLotto = winningLotto, - lottos = lottos + bonusLottoNumber = bonusLottoNumber, + lottos = lottos, + ) + + lottoResult[Prize.FIRST] shouldBe 1 + } + + @Test + internal fun `5개가 일치하고 보너스번호가 일치하면 2등`() { + val winningLotto = Lotto(1, 2, 3, 4, 5, 6) + val bonusLottoNumber = LottoNumber(7) + + val lottos = Lottos( + listOf(Lotto(1, 2, 3, 4, 5, 7)) + ) + + val lottoResult = LottoResult.makeLottoResult( + winningLotto = winningLotto, + bonusLottoNumber = bonusLottoNumber, + lottos = lottos, + ) + + lottoResult[Prize.SECOND] shouldBe 1 + } + + @Test + internal fun `5개가 일치하면 3등`() { + val winningLotto = Lotto(1, 2, 3, 4, 5, 6) + val bonusLottoNumber = LottoNumber(7) + + val lottos = Lottos( + listOf(Lotto(1, 2, 3, 4, 5, 8)) + ) + + val lottoResult = LottoResult.makeLottoResult( + winningLotto = winningLotto, + bonusLottoNumber = bonusLottoNumber, + lottos = lottos, + ) + + lottoResult[Prize.THIRD] shouldBe 1 + } + + @Test + internal fun `4개가 일치하면 4등`() { + val winningLotto = Lotto(1, 2, 3, 4, 5, 6) + val bonusLottoNumber = LottoNumber(7) + + val lottos = Lottos( + listOf(Lotto(1, 2, 3, 4, 8, 9)) + ) + + val lottoResult = LottoResult.makeLottoResult( + winningLotto = winningLotto, + bonusLottoNumber = bonusLottoNumber, + lottos = lottos, ) - 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 } + + @Test + internal fun `3개가 일치하면 5등`() { + val winningLotto = Lotto(1, 2, 3, 4, 5, 6) + val bonusLottoNumber = LottoNumber(7) + + val lottos = Lottos( + listOf(Lotto(1, 2, 3, 8, 9, 10)) + ) + + val lottoResult = LottoResult.makeLottoResult( + winningLotto = winningLotto, + bonusLottoNumber = bonusLottoNumber, + lottos = lottos, + ) + + lottoResult[Prize.FIFTH] shouldBe 1 + } + + @Test + internal fun `2개 이하로 일치하면 당첨 없음`() { + val winningLotto = Lotto(1, 2, 3, 4, 5, 6) + val bonusLottoNumber = LottoNumber(7) + + val lottos = Lottos( + listOf(Lotto(1, 2,8, 9, 10, 11)) + ) + + val lottoResult = LottoResult.makeLottoResult( + winningLotto = winningLotto, + bonusLottoNumber = bonusLottoNumber, + lottos = lottos, + ) + + lottoResult[Prize.NONE] shouldBe 1 + } + } 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/LottoVendingMachineTest.kt index 180f2cc17..f65bd1a37 100644 --- a/src/test/kotlin/lotto/LottoVendingMachineTest.kt +++ b/src/test/kotlin/lotto/LottoVendingMachineTest.kt @@ -29,7 +29,7 @@ class LottoVendingMachineTest { @Test internal fun `로또 한 장의 금액은 1000원 이다`() { - LottoVendingMachine.buyLotto(1000).size shouldBe 1 - LottoVendingMachine.buyLotto(14000).size shouldBe 14 + LottoVendingMachine.buyLotto(1000).lottos.size shouldBe 1 + LottoVendingMachine.buyLotto(14000).lottos.size shouldBe 14 } } diff --git a/src/test/kotlin/lotto/PrizeTest.kt b/src/test/kotlin/lotto/PrizeTest.kt index f29b9e8e8..66b02ce02 100644 --- a/src/test/kotlin/lotto/PrizeTest.kt +++ b/src/test/kotlin/lotto/PrizeTest.kt @@ -1,26 +1,27 @@ package lotto import io.kotest.matchers.shouldBe +import lotto.domain.Prize import org.junit.jupiter.api.Test 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 } }