diff --git a/docs/README.md b/docs/README.md index e69de29bb..3245f99f1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,25 @@ +# ๐Ÿ“š 3์ฃผ์ฐจ ๋ฏธ์…˜ - ๋กœ๋˜ + +## ๊ตฌํ˜„ํ•  ๊ธฐ๋Šฅ ๋ชฉ๋ก +### ๊ณตํ†ต +- ์ž…๋ ฅ +- [x] ์ž˜๋ชป ์ž…๋ ฅ์‹œ ๋‹ค์‹œ ์ž…๋ ฅ๋ฐ›๊ธฐ +- ์ถœ๋ ฅ +- [x] ์˜ˆ์™ธ์ฒ˜๋ฆฌ(IllegalArgumentException) +- [x] ์—๋Ÿฌ ๋ฉ”์„ธ์ง€ `[ERROR]` ๋ถ™์—ฌ์„œ ์ถœ๋ ฅ + +### 1. ๋กœ๋˜ ๊ตฌ์ž…ํ•ด์„œ ๋ฐœํ–‰ํ•˜๊ธฐ +- ์ž…๋ ฅ +- [x] 1000 ๋‹จ์œ„์˜ ์ˆซ์ž +- ์ถœ๋ ฅ +- [x] ๊ตฌ๋งคํ•œ ๊ฐœ์ˆ˜๋งŒํผ ๋กœ๋˜ ๋ฐœํ–‰ + +### 2. ๋‹น์ฒจ ๋ฒˆํ˜ธ์™€ ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ ์ž…๋ ฅ๋ฐ›๊ธฐ +- ์ž…๋ ฅ +- [x] ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ์ค‘๋ณต์—†๋Š” 6๊ฐœ ์ˆซ์ž +- [x] ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ 1๊ฐœ ์ž…๋ ฅ๋ฐ›๊ธฐ + +### 3. ๋‹น์ฒจ ๊ฒฐ๊ณผ ์ถœ๋ ฅํ•˜๊ธฐ +- ์ถœ๋ ฅ +- [x] ๊ฐ ๋“ฑ์ˆ˜๋งˆ๋‹ค ๋‹น์ฒจ ๊ฐœ์ˆ˜ ์ถœ๋ ฅ +- [x] ์ˆ˜์ต๋ฅ  ๊ณ„์‚ฐ ํ›„ ์ถœ๋ ฅ \ No newline at end of file diff --git a/src/main/kotlin/lotto/Application.kt b/src/main/kotlin/lotto/Application.kt index d7168761c..4b1e159ac 100644 --- a/src/main/kotlin/lotto/Application.kt +++ b/src/main/kotlin/lotto/Application.kt @@ -1,5 +1,7 @@ package lotto +import lotto.controller.GameController + fun main() { - TODO("ํ”„๋กœ๊ทธ๋žจ ๊ตฌํ˜„") -} + GameController().start() +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/Lotto.kt b/src/main/kotlin/lotto/Lotto.kt deleted file mode 100644 index 5ca00b4e4..000000000 --- a/src/main/kotlin/lotto/Lotto.kt +++ /dev/null @@ -1,9 +0,0 @@ -package lotto - -class Lotto(private val numbers: List) { - init { - require(numbers.size == 6) - } - - // TODO: ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„ -} diff --git a/src/main/kotlin/lotto/controller/GameController.kt b/src/main/kotlin/lotto/controller/GameController.kt new file mode 100644 index 000000000..e35e157c5 --- /dev/null +++ b/src/main/kotlin/lotto/controller/GameController.kt @@ -0,0 +1,75 @@ +package lotto.controller + +import camp.nextstep.edu.missionutils.Console +import lotto.global.InformationMessage +import lotto.service.LottoService +import lotto.service.WinningService +import lotto.service.RankService + +class GameController { + private val lottoService = LottoService() + private val winningService = WinningService() + private val rankService = RankService() + + fun start() { + println(InformationMessage.PURCHASE_AMOUNT.message) + purchase() + + println("\n${InformationMessage.WINNING_NUMBER.message}") + winningNumber() + + println("\n${InformationMessage.BONUS_NUMBER.message}") + bonusNumber() + + println("\n${InformationMessage.WINNING_STATISTIC.message}") + checkLottoNumber() + } + + private fun purchase() { + try { + val input = Console.readLine() + val count = lottoService.purchaseLotto(input) + println("\n$count${InformationMessage.PURCHASE_SUCCESS.message}") + lottoService.createLotto(count) + } catch (e: IllegalArgumentException) { + println(e.message) + purchase() + } + } + + private fun winningNumber() { + try { + val input = Console.readLine() + winningService.winningNumber(input) + } catch (e: IllegalArgumentException) { + println(e.message) + winningNumber() + } + } + + private fun bonusNumber() { + try { + val input = Console.readLine() + winningService.bonusNumber(input) + } catch (e: IllegalArgumentException) { + println(e.message) + bonusNumber() + } + } + + private fun checkLottoNumber() { + val winningNumber = winningService.getLotto() + val bonusNumber = winningService.getBonus() + val lottos = lottoService.getLotto() + + val matches = rankService.rateRank(winningNumber, bonusNumber, lottos) + println(InformationMessage.place(matches)) + + calculateReturn(matches) + } + + private fun calculateReturn(matches: MutableList) { + val price = lottoService.getPrice() + println(InformationMessage.returnRate(rankService.calculateReturn(price, matches))) + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/global/Config.kt b/src/main/kotlin/lotto/global/Config.kt new file mode 100644 index 000000000..b97ba2b6f --- /dev/null +++ b/src/main/kotlin/lotto/global/Config.kt @@ -0,0 +1,7 @@ +package lotto.global + +object Config { + const val LOTTO_RANGE = 45 + const val NUMBER_DRAW = 6 + const val LOTTO_PRICE = 1000 +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/global/Error.kt b/src/main/kotlin/lotto/global/Error.kt new file mode 100644 index 000000000..9b143dccc --- /dev/null +++ b/src/main/kotlin/lotto/global/Error.kt @@ -0,0 +1,10 @@ +package lotto.global + +enum class Error(val message: String) { + PURCHASE_AMOUNT("${Config.LOTTO_PRICE}์› ๋‹จ์œ„์˜ ์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"), + NOT_NUMBER("์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"), + NUMBER_RANGE("1~${Config.LOTTO_RANGE}์˜ ์ˆซ์ž๋งŒ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"), + NUMBER_NUMBER("${Config.NUMBER_DRAW}๊ฐœ์˜ ์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"); + + fun message() = "[ERROR] $message" +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/global/InformationMessage.kt b/src/main/kotlin/lotto/global/InformationMessage.kt new file mode 100644 index 000000000..17161320f --- /dev/null +++ b/src/main/kotlin/lotto/global/InformationMessage.kt @@ -0,0 +1,20 @@ +package lotto.global + +enum class InformationMessage(val message: String) { + PURCHASE_AMOUNT("๊ตฌ์ž…๊ธˆ์•ก์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”."), + PURCHASE_SUCCESS("๊ฐœ๋ฅผ ๊ตฌ๋งคํ–ˆ์Šต๋‹ˆ๋‹ค."), + WINNING_NUMBER("๋‹น์ฒจ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”."), + BONUS_NUMBER("๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”."), + WINNING_STATISTIC("๋‹น์ฒจ ํ†ต๊ณ„\n---"); + + companion object { + fun place(numbers: MutableList) = "${WINNING_NUMBER.message}\n" + + "3๊ฐœ ์ผ์น˜ (5,000์›) - ${numbers[4]}๊ฐœ\n" + + "4๊ฐœ ์ผ์น˜ (50,000์›) - ${numbers[3]}๊ฐœ\n" + + "5๊ฐœ ์ผ์น˜ (1,500,000์›) - ${numbers[2]}๊ฐœ\n" + + "5๊ฐœ ์ผ์น˜, ๋ณด๋„ˆ์Šค ๋ณผ ์ผ์น˜ (30,000,000์›) - ${numbers[1]}๊ฐœ\n" + + "6๊ฐœ ์ผ์น˜ (2,000,000,000์›) - ${numbers[0]}๊ฐœ" + + fun returnRate(percent: Double) = "์ด ์ˆ˜์ต๋ฅ ์€ ${percent}%์ž…๋‹ˆ๋‹ค." + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/global/Prize.kt b/src/main/kotlin/lotto/global/Prize.kt new file mode 100644 index 000000000..c56be05a6 --- /dev/null +++ b/src/main/kotlin/lotto/global/Prize.kt @@ -0,0 +1,16 @@ +package lotto.global + +enum class Prize(val rank: Int, val amount: Int) { + FIRST(1, 2000000000), + SECOND(2, 30000000), + THIRD(3, 1500000), + FOURTH(4, 50000), + FIFTH(5, 5000); + + fun calculation(number: Int) = amount * number + + companion object { + private val map = Prize.values().associateBy { it.rank } + fun from(rank: Int) = Prize.values().first { it.rank == rank } + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/model/Lotto.kt b/src/main/kotlin/lotto/model/Lotto.kt new file mode 100644 index 000000000..8ac60ddb7 --- /dev/null +++ b/src/main/kotlin/lotto/model/Lotto.kt @@ -0,0 +1,13 @@ +package lotto.model + +import lotto.global.Config + +class Lotto(private val numbers: List) { + init { + require(numbers.size == Config.NUMBER_DRAW) + require(numbers.toSet().size == Config.NUMBER_DRAW) + numbers.forEach { require(it in 1..Config.LOTTO_RANGE) } + } + + fun getLotto() = numbers +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/service/LottoService.kt b/src/main/kotlin/lotto/service/LottoService.kt new file mode 100644 index 000000000..80b22ed74 --- /dev/null +++ b/src/main/kotlin/lotto/service/LottoService.kt @@ -0,0 +1,40 @@ +package lotto.service + +import camp.nextstep.edu.missionutils.Randoms +import lotto.global.Config +import lotto.global.Error +import lotto.model.Lotto + +class LottoService { + private var price = Config.LOTTO_PRICE + private var lottos: MutableList = mutableListOf() + + fun purchaseLotto(price: String): Int { + require(price.matches(Regex("^[0-9]*$"))) { Error.NOT_NUMBER.message() } + require( + (price.toDouble() / Config.LOTTO_PRICE).toString().matches(Regex("^[0-9]+.0$")) + ) { Error.PURCHASE_AMOUNT.message() } + this.price = price.toInt() + return this.price / Config.LOTTO_PRICE + } + + fun createLotto(count: Int) { + for (i in 0.. { + return Randoms.pickUniqueNumbersInRange(1, Config.LOTTO_RANGE, Config.NUMBER_DRAW) + } + + fun getPrice(): Int { + return price + } + + fun getLotto(): MutableList { + return lottos + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/service/RankService.kt b/src/main/kotlin/lotto/service/RankService.kt new file mode 100644 index 000000000..b673aacb9 --- /dev/null +++ b/src/main/kotlin/lotto/service/RankService.kt @@ -0,0 +1,47 @@ +package lotto.service + +import lotto.global.Prize +import lotto.model.Lotto +import kotlin.math.round + +class RankService { + fun rateRank( + winningNumber: MutableSet, + bonusNumber: Int, + lottos: MutableList + ): MutableList { + val matches: MutableList = mutableListOf(0, 0, 0, 0, 0) + + lottos.forEach { it: Lotto -> + when (checkSameNumber(it.getLotto(), winningNumber)) { + 3 -> matches[4]++ + 4 -> matches[3]++ + 5 -> if (!it.getLotto().contains(bonusNumber)) { + matches[2]++ + } else { + matches[1]++ + } + + 6 -> matches[0]++ + } + } + return matches + } + + fun checkSameNumber(numbers: List, winningNumber: MutableSet): Int { + var matchCount = 0 + for (number in numbers) { + if (winningNumber.contains(number)) { + matchCount++ + } + } + return matchCount + } + + fun calculateReturn(price: Int, matches: MutableList): Double { + var totalPrize = 0 + for (i in 1..5) totalPrize += Prize.from(i).calculation(matches[i - 1]) + val d = totalPrize.toDouble() / price.toDouble() * 100 + return round(d * 100) / 100 + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/service/WinningService.kt b/src/main/kotlin/lotto/service/WinningService.kt new file mode 100644 index 000000000..7e650513b --- /dev/null +++ b/src/main/kotlin/lotto/service/WinningService.kt @@ -0,0 +1,42 @@ +package lotto.service + +import lotto.global.Config +import lotto.global.Error + +class WinningService { + private var lottoNumber: MutableSet = mutableSetOf() + private var bonusNumber: Int = 0 + + fun winningNumber(winningNumber: String) { + winningNumber.split(",").forEach { + val number = try { + it.toInt() + } catch (e: NumberFormatException) { + throw IllegalArgumentException(Error.NOT_NUMBER.message()) + } + require(number in (1..Config.LOTTO_RANGE)) { Error.NUMBER_RANGE.message() } + lottoNumber.add(number) + } + require(lottoNumber.size == 6) { Error.NUMBER_NUMBER.message() } + + lottoNumber = lottoNumber.toSortedSet() + } + + fun bonusNumber(bonusNumber: String) { + val number = try { + bonusNumber.toInt() + } catch (e: NumberFormatException) { + throw IllegalArgumentException(Error.NOT_NUMBER.message()) + } + require(number in (1..Config.LOTTO_RANGE)) { Error.NUMBER_RANGE.message() } + this.bonusNumber = number + } + + fun getLotto(): MutableSet { + return lottoNumber + } + + fun getBonus(): Int { + return bonusNumber + } +} \ No newline at end of file diff --git a/src/test/kotlin/lotto/LottoTest.kt b/src/test/kotlin/lotto/LottoTest.kt index 11d85ac2c..666e080dc 100644 --- a/src/test/kotlin/lotto/LottoTest.kt +++ b/src/test/kotlin/lotto/LottoTest.kt @@ -1,5 +1,6 @@ package lotto +import lotto.model.Lotto import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows diff --git a/src/test/kotlin/lotto/service/LottoServiceTest.kt b/src/test/kotlin/lotto/service/LottoServiceTest.kt new file mode 100644 index 000000000..df4a1df38 --- /dev/null +++ b/src/test/kotlin/lotto/service/LottoServiceTest.kt @@ -0,0 +1,24 @@ +package lotto.service + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class LottoServiceTest { + @Test + fun `๋กœ๋˜ ๊ตฌ์ž… ๊ธˆ์•ก์ด 1000 ๋‹จ์œ„์˜ ์ˆซ์ž๊ฐ€ ์•„๋‹ˆ๋ฉด ERROR`() { + assertThrows { + LottoService().purchaseLotto("300") + } + assertThrows { + LottoService().purchaseLotto("3000d") + } + } + + @Test + fun `๊ตฌ์ž… ๊ธˆ์•ก์— ๋”ฐ๋ฅธ ๋กœ๋˜ ๊ฐœ์ˆ˜ ํ™•์ธ`() { + val input = "123000" + val result = 123 + assertThat(LottoService().purchaseLotto(input)).isEqualTo(result) + } +} \ No newline at end of file diff --git a/src/test/kotlin/lotto/service/RankServiceTest.kt b/src/test/kotlin/lotto/service/RankServiceTest.kt new file mode 100644 index 000000000..6536f432a --- /dev/null +++ b/src/test/kotlin/lotto/service/RankServiceTest.kt @@ -0,0 +1,28 @@ +package lotto.service + +import lotto.model.Lotto +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class RankServiceTest { + @Test + fun `๋กœ๋˜ ๋‹น์ฒจ ํ™•์ธ`() { + val input = RankService().rateRank(WINNING_NUMBER, BONUS_NUMBER, FIRST) + val result = mutableListOf(1, 0, 0, 0, 0) + assertThat(input).isEqualTo(result) + } + + @Test + fun `๋กœ๋˜ ๋‹น์ฒจ ํ™•์ธ 2๋“ฑ`() { + val input = RankService().rateRank(WINNING_NUMBER, BONUS_NUMBER, SECOND) + val result = mutableListOf(0, 1, 0, 0, 0) + assertThat(input).isEqualTo(result) + } + + companion object { + private val WINNING_NUMBER = mutableSetOf(1, 2, 3, 4, 5, 6) + private const val BONUS_NUMBER = 7 + private val FIRST = mutableListOf(Lotto(listOf(1, 2, 3, 4, 5, 6))) + private val SECOND = mutableListOf(Lotto(listOf(1, 2, 3, 4, 5, 7))) + } +} \ No newline at end of file diff --git a/src/test/kotlin/lotto/service/WinningServiceTest.kt b/src/test/kotlin/lotto/service/WinningServiceTest.kt new file mode 100644 index 000000000..b0cc2dfe8 --- /dev/null +++ b/src/test/kotlin/lotto/service/WinningServiceTest.kt @@ -0,0 +1,62 @@ +package lotto.service + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class WinningServiceTest { + @Test + fun `๋‹น์ฒจ ๋ฒˆํ˜ธ ์‰ผํ‘œ๊ฐ€ ์—†์œผ๋ฉด ERROR`() { + val input = "233" + assertThrows { + WinningService().winningNumber(input) + } + } + + @Test + fun `๋‹น์ฒจ ๋ฒˆํ˜ธ ์ž…๋ ฅ์‹œ ์ˆซ์ž๊ฐ€ ์•„๋‹ˆ๋ฉด ERROR`() { + val input = "1,d" + assertThrows { + WinningService().winningNumber(input) + } + } + + @Test + fun `๋‹น์ฒจ ๋ฒˆํ˜ธ ์ž…๋ ฅ์‹œ 1~45์˜ ์ˆซ์ž๊ฐ€ ์•„๋‹ˆ๋ฉด ERROR`() { + val input = "2,65,4" + assertThrows { + WinningService().winningNumber(input) + } + } + + @Test + fun `๋‹น์ฒจ ๋ฒˆํ˜ธ ์ž…๋ ฅ์‹œ 6๊ฐœ ์ˆซ์ž๊ฐ€ ์•„๋‹ˆ๋ฉด ERROR`() { + val input = "2,4,7,27,45" + assertThrows { + WinningService().winningNumber(input) + } + } + + @Test + fun `๋‹น์ฒจ ๋ฒˆํ˜ธ ์ž…๋ ฅ์‹œ ์ค‘๋ณต์žˆ์œผ๋ฉด ERROR`() { + val input = "1,1,3,4,5,6" + assertThrows { + WinningService().winningNumber(input) + } + } + + @Test + fun `๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ ์ž…๋ ฅ์‹œ ์ˆซ์ž๊ฐ€ ์•„๋‹ˆ๋ฉด ERROR`() { + val input = "df" + assertThrows { + WinningService().winningNumber(input) + } + } + + @Test + fun `๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ ์ž…๋ ฅ์‹œ 1~45์˜ ์ˆซ์ž๊ฐ€ ์•„๋‹ˆ๋ฉด ERROR`() { + val input = "149" + assertThrows { + WinningService().winningNumber(input) + } + } +} \ No newline at end of file