diff --git a/docs/README.md b/docs/README.md index e69de29bb..179dab697 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,9 @@ +# 미션 - 로또 +## 구현 할 기능 목록 + +1. 사용자로 부터 로또 구입 금액을 입력받는다 +2. 입력 받은 로또 구입 금액이 1000원으로 나누어 떨어지면 몫을 출력하고 컴퓨터가 앞서 계산한 몫만큼 임의의 수 6개로 이루어진 로또를 발행한다. +3. 사용자로부터 당첨번호를(중복되지 않는 숫자 6개를 구분자 ,와 함께) 입력 받는다. +4. 사용자로부터 보너스 번호 1개를 입력받는다. +5. 컴퓨터를 통해 발행된 로또들과 사용자로부터 입력받은 당첨 번호와 보너스번호를 비교해 5등부터 1등까지의 개수를 계산한다. +6. 수익율을 계산한다. \ No newline at end of file diff --git a/src/main/kotlin/lotto/Application.kt b/src/main/kotlin/lotto/Application.kt index d7168761c..a676be40f 100644 --- a/src/main/kotlin/lotto/Application.kt +++ b/src/main/kotlin/lotto/Application.kt @@ -1,5 +1,12 @@ package lotto fun main() { - TODO("프로그램 구현") + try { + LottoGame().start() + } catch (exception: IllegalArgumentException) { + + } } + + + diff --git a/src/main/kotlin/lotto/Lotto.kt b/src/main/kotlin/lotto/Lotto.kt index 5ca00b4e4..4a4880eb1 100644 --- a/src/main/kotlin/lotto/Lotto.kt +++ b/src/main/kotlin/lotto/Lotto.kt @@ -3,7 +3,16 @@ package lotto class Lotto(private val numbers: List) { init { require(numbers.size == 6) + require(isDuplicateNum()) { println(IS_DUPLICATE_NUM) } + require(checkNotIn1to45()) { println(NOT_IN_ONE_TO_FOURTY_FIVE) } } - // TODO: 추가 기능 구현 + private fun isDuplicateNum(): Boolean = numbers.size == numbers.distinct().size + private fun checkNotIn1to45(): Boolean = numbers.all { it in 1..45 } + + companion object { + const val IS_DUPLICATE_NUM = "[ERROR] 로또 번호는 중복되는 숫자가 없어야 합니다." + const val NOT_IN_ONE_TO_FOURTY_FIVE = "[EROOR] 로또 번호는 1부터 45 사이의 숫자여야 합니다." + } } + diff --git a/src/main/kotlin/lotto/LottoGame.kt b/src/main/kotlin/lotto/LottoGame.kt new file mode 100644 index 000000000..9f573846d --- /dev/null +++ b/src/main/kotlin/lotto/LottoGame.kt @@ -0,0 +1,65 @@ +package lotto + +import camp.nextstep.edu.missionutils.Randoms +import lotto.view.Input +import lotto.view.Output +import java.math.BigDecimal +import java.math.BigInteger + +class LottoGame { + private var matchCountList = mutableListOf(0, 0, 0, 0, 0) + + fun start() { + val money = Input().inputMoney().toInt() + val lottoCount = getLottoCount(money) + val lottoList = getLotto(lottoCount) + Output().outputLottos(lottoList) + + val winningNum = Input().inputWinningNum() + val bonusNum = Input().inputBonusNum() + + lottoList.forEach { checkLottoAndWinningNumMatch(it, winningNum, bonusNum) } + + Output().outputMatchCount(matchCountList) + + val profitability = getProfitability(money) + Output().outputProfitability(profitability) + + } + + + private fun getLottoCount(money: Int): Int = money / 1000 + + private fun getLotto(lottoCount: Int): List> { + val lottoList = List(lottoCount) { + Randoms.pickUniqueNumbersInRange(1, 45, 6).sorted() + } + lottoList.forEach { Lotto(it) } + return lottoList + } + + private fun checkLottoAndWinningNumMatch(lotto: List, winningNum: String, bonusNum: String) { + val winningNumList = winningNum.split(",").map { it.toInt() }.toSet() + val bonusNumToInt = bonusNum.toInt() + val match = lotto.intersect(winningNumList).size + + when (match) { + 3 -> matchCountList[0]++ + 4 -> matchCountList[1]++ + 5 -> if (lotto.contains(bonusNumToInt)) matchCountList[3]++ else matchCountList[2]++ + 6 -> matchCountList[4]++ + } + } + + private fun getProfitability(money: Int): BigDecimal { + val priceList = listOf(5000, 50000, 1500000, 30000000, 2000000000) + var sum = BigInteger("0") + for (i in matchCountList.indices) { + val temp = BigInteger(matchCountList[i].toString()) * BigInteger(priceList[i].toString()) + sum += temp + } + + val tempProfitability = BigDecimal(sum).divide(BigDecimal(money.toString())) + return tempProfitability.multiply(BigDecimal("100")).setScale(1, BigDecimal.ROUND_HALF_UP) + } +} diff --git a/src/main/kotlin/lotto/domain/BonusNum.kt b/src/main/kotlin/lotto/domain/BonusNum.kt new file mode 100644 index 000000000..670ef526b --- /dev/null +++ b/src/main/kotlin/lotto/domain/BonusNum.kt @@ -0,0 +1,21 @@ +package lotto.domain + +import lotto.Lotto + +class BonusNum(private val bonusNum: String) { + init { + require(isNum()) { println(IS_NOT_NUM) } + require(checkNotIn1to45()) { println(NOT_IN_ONE_TO_FOURTY_FIVE) } + } + + private fun isNum(): Boolean = bonusNum.all { number -> number.isDigit() } + private fun checkNotIn1to45(): Boolean = bonusNum.toInt() in 1..45 + + + + companion object { + private const val IS_NOT_NUM = "[ERROR] 숫자로 입력해야 합니다." + private const val NOT_IN_ONE_TO_FOURTY_FIVE = "[EROOR] 1부터 45 사이의 숫자여야 합니다." + } +} + diff --git a/src/main/kotlin/lotto/domain/Money.kt b/src/main/kotlin/lotto/domain/Money.kt new file mode 100644 index 000000000..434b62c2a --- /dev/null +++ b/src/main/kotlin/lotto/domain/Money.kt @@ -0,0 +1,17 @@ +package lotto.domain + +class Money(private val money: String) { + init { + require(isNum()) { println(IS_NOT_NUM) } + require(isMultiplesOfThousand()) { println(IS_NOT_MULTIPLES_OF_THOUSAND) } + } + + private fun isNum(): Boolean = money.all { number -> number.isDigit() } + + private fun isMultiplesOfThousand(): Boolean = money.toInt() % 1000 == 0 + + companion object { + private const val IS_NOT_NUM = "[ERROR] 금액은 숫자여야 합니다." + private const val IS_NOT_MULTIPLES_OF_THOUSAND = "[ERROR] 구입 금액은 1000원으로 나누어 떨어져야 합니다." + } +} diff --git a/src/main/kotlin/lotto/domain/WinningNum.kt b/src/main/kotlin/lotto/domain/WinningNum.kt new file mode 100644 index 000000000..7f8e11e69 --- /dev/null +++ b/src/main/kotlin/lotto/domain/WinningNum.kt @@ -0,0 +1,16 @@ +package lotto.domain + +class WinningNum(private val winningNum: String) { + init { + require(isValidFormat()) { println(IS_VALID_FORMAT) } + } + + private fun isValidFormat(): Boolean = + winningNum.split(",").map { it.trim() }.all { it.matches(Regex("""^\d+(,\d+)*$""")) } + + + companion object { + private const val IS_VALID_FORMAT = "[ERROR] 당첨 번호 입력 시 콤마와 숫자로 이루어진 문자열로 입력해야 합니다." + } +} + diff --git a/src/main/kotlin/lotto/view/Input.kt b/src/main/kotlin/lotto/view/Input.kt new file mode 100644 index 000000000..2268cd97c --- /dev/null +++ b/src/main/kotlin/lotto/view/Input.kt @@ -0,0 +1,32 @@ +package lotto.view + +import camp.nextstep.edu.missionutils.Console +import lotto.Lotto +import lotto.domain.BonusNum +import lotto.domain.Money +import lotto.domain.WinningNum + +class Input { + fun inputMoney(): String { + println("구입할 금액을 입력해 주세요.") + val money = Console.readLine() + Money(money) + return money + } + + fun inputWinningNum(): String { + println("\n당첨 번호를 입력해 주세요.") + val winningNum = Console.readLine() + WinningNum(winningNum) + Lotto(winningNum.split(",").map { it.toInt() }.toList()) + return winningNum + } + + fun inputBonusNum(): String { + println("\n보너스 번호를 입력해 주세요.") + val bonusNum = Console.readLine() + BonusNum(bonusNum) + return bonusNum + } +} + diff --git a/src/main/kotlin/lotto/view/Output.kt b/src/main/kotlin/lotto/view/Output.kt new file mode 100644 index 000000000..d04fe09f5 --- /dev/null +++ b/src/main/kotlin/lotto/view/Output.kt @@ -0,0 +1,27 @@ +package lotto.view + +import java.math.BigDecimal + +class Output { + + fun outputLottos(lottoList: List>) { + println() + println("${lottoList.size}개를 구매했습니다.") + lottoList.forEach { println(it) } + } + + fun outputMatchCount(matchCountList: MutableList) { + println("\n당첨 통계") + println("---") + println("3개 일치 (5,000원) - ${matchCountList[0]}개") + println("4개 일치 (50,000원) - ${matchCountList[1]}개") + println("5개 일치 (1,500,000원) - ${matchCountList[2]}개") + println("5개 일치, 보너스 볼 일치 (30,000,000원) - ${matchCountList[3]}개") + println("6개 일치 (2,000,000,000원) - ${matchCountList[4]}개") + } + + fun outputProfitability(profitability : BigDecimal){ + println("총 수익률은 $profitability%입니다.") + } +} + diff --git a/src/test/kotlin/lotto/BonusNumTest.kt b/src/test/kotlin/lotto/BonusNumTest.kt new file mode 100644 index 000000000..0b1760604 --- /dev/null +++ b/src/test/kotlin/lotto/BonusNumTest.kt @@ -0,0 +1,23 @@ +package lotto + +import lotto.domain.BonusNum +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + + +class BonusNumTest { + @Test + fun `보너스 번호 입력 시 숫자 아닌 경우 예외 발생`() { + assertThrows { + BonusNum("10j") + } + } + + @Test + fun `보너스 번호 입력 시 1부터 45의 범위에 속하지 않는 경우 예외 발생`() { + assertThrows { + BonusNum("48") + } + } + +} diff --git a/src/test/kotlin/lotto/LottoTest.kt b/src/test/kotlin/lotto/LottoTest.kt index 11d85ac2c..cd4df68c7 100644 --- a/src/test/kotlin/lotto/LottoTest.kt +++ b/src/test/kotlin/lotto/LottoTest.kt @@ -12,7 +12,6 @@ class LottoTest { } } - // TODO: 이 테스트가 통과할 수 있게 구현 코드 작성 @Test fun `로또 번호에 중복된 숫자가 있으면 예외가 발생한다`() { assertThrows { @@ -20,5 +19,11 @@ class LottoTest { } } - // 아래에 추가 테스트 작성 가능 + @Test + fun `로또 번호가 1부터 45 사이의 숫자가 아닐 경우 예외 발생 `() { + assertThrows { + Lotto(listOf(1, 2, 3, 4, 5, 48)) + } + } + } diff --git a/src/test/kotlin/lotto/MoneyTest.kt b/src/test/kotlin/lotto/MoneyTest.kt new file mode 100644 index 000000000..6d377ba58 --- /dev/null +++ b/src/test/kotlin/lotto/MoneyTest.kt @@ -0,0 +1,23 @@ +package lotto + +import lotto.domain.Money +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + + +class MoneyTest { + + @Test + fun `금액이 1000원으로 나누어 떨어지지 않을 경우 테스트`() { + val money = "1100" + assertThrows { Money(money) } + } + + @Test + fun `금액이 숫자가 아닐 경우 테스트`() { + val money = "1100k" + assertThrows { Money(money) } + } + + +} diff --git a/src/test/kotlin/lotto/WinningNumTest.kt b/src/test/kotlin/lotto/WinningNumTest.kt new file mode 100644 index 000000000..bb4896218 --- /dev/null +++ b/src/test/kotlin/lotto/WinningNumTest.kt @@ -0,0 +1,16 @@ +package lotto + +import lotto.domain.WinningNum +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + + +class WinningNumTest { + @Test + fun `당첨 번호 입력시 숫자와 콤마로 이루어진 문자열이 아닐 경우 예외 발생`() { + assertThrows { + WinningNum("4;7,5,3,9,10") + } + } + +}