diff --git a/docs/README.md b/docs/README.md index e69de29bb..a0b76bd19 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,34 @@ +# 기능 구현 목록 + +- 로또 구입 금액을 입력한다. + - 1,000원 단위로 떨어지지 않는 값을 입력할 경우 예외 처리 + - 1,000원 이하를 입력할 경우 예외 처리 + - 숫자를 입력하지 않을 경우 예외 처리 + +- 구입 금액에 해당하는 만큼 로또를 발행하며, 이 때 숫자의 범위는 1부터 45이내다. + - 발행한 로또 수량과 번호를 출력한다. 이 때, 번호는 오름차순으로 정렬 + +- 당첨 번호를 입력한다. + - 번호를 쉼표 기준으로 입력하지 않을 경우 예외 처리 + - 1~45 사이의 값을 입력하지 않을 경우 예외 처리 + - 숫자를 입력하지 않을 경우 예외 처리 + - 값이 중복될 경우 예외 처리 + - 번호를 6개 입력하지 않았을 경우 예외 처리 + +- 보너스 번호를 입력한다. + - 1~45 사이의 값을 입력하지 않을 경우 예외 처리 + - 값이 당첨 번호와 중복될 경우 예외 처리 + - 숫자를 입력하지 않을 경우 예외 처리 + +- 당첨 번호와 로또의 숫자를 비교하여 당첨된 로또의 개수를 계산한다. + - 1등: 6개 번호 일치 / 2,000,000,000원 + - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 + - 3등: 5개 번호 일치 / 1,500,000원 + - 4등: 4개 번호 일치 / 50,000원 + - 5등: 3개 번호 일치 / 5,000원 + +- 구매 금액과 당첨 금액을 비교하여 수익률을 계산한다. 이 때, 수익률은 소수점 둘째 자리에서 반올림한다. + + +### 주의사항 +- 사용자가 잘못된 값을 입력할 경우 에러 메세지를 출력한 후 입력을 다시 받는다. \ No newline at end of file diff --git a/src/main/kotlin/lotto/Application.kt b/src/main/kotlin/lotto/Application.kt index d7168761c..0be8a7726 100644 --- a/src/main/kotlin/lotto/Application.kt +++ b/src/main/kotlin/lotto/Application.kt @@ -1,5 +1,5 @@ package lotto fun main() { - TODO("프로그램 구현") + LottoGame().play() } diff --git a/src/main/kotlin/lotto/CompareLotto.kt b/src/main/kotlin/lotto/CompareLotto.kt new file mode 100644 index 000000000..65a319365 --- /dev/null +++ b/src/main/kotlin/lotto/CompareLotto.kt @@ -0,0 +1,57 @@ +package lotto + +import lotto.model.LottoType + +class CompareLotto { + private val resultPrice = listOf( + "3개 일치 (5,000원) - ", + "4개 일치 (50,000원) - ", + "5개 일치 (1,500,000원) - ", + "5개 일치, 보너스 볼 일치 (30,000,000원) - ", + "6개 일치 (2,000,000,000원) - " + ) + + fun compareLotto( + lottos: List, + lottoNumber: List, + bonusNumber: Int + ): MutableList { + val winningLottosNumber = MutableList(LottoType.entries.size) { 0 } + + lottos.forEach { lotto -> + val index = when (lotto.compareLottoNumber(lottoNumber)) { + in 0..2 -> null + 3 -> LottoType.THREE + 4 -> LottoType.FOUR + 5 -> if (lotto.containBonusNumber(bonusNumber)) LottoType.FIVE_BONUS else LottoType.FIVE + 6 -> LottoType.SIX + else -> throw IllegalArgumentException("유효하지 않은 비교 개수입니다.") + } + index?.also { + winningLottosNumber[it.ordinal]++ + } + } + + return winningLottosNumber + } + + fun calculateRateOfReturn(winningLottosNumber: MutableList, price: Int): Double { + var profit = 0.0 + val priceList = listOf(5000, 50000, 1500000, 30000000, 2000000000) + winningLottosNumber.forEachIndexed { index, count -> + profit += priceList[index] * count + } + return profit / price * 100 + } + + fun printResult(winningLottosNumber: MutableList) { + winningLottosNumber.forEachIndexed { index, count -> + println("${resultPrice[index]}${count}개") + } + } + + fun printRateOfReturnResult(rateOfReturn: Double) { + val num = String.format("%.1f", rateOfReturn) + println("총 수익률은 ${num}%입니다.") + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/Input.kt b/src/main/kotlin/lotto/Input.kt new file mode 100644 index 000000000..27e1c7a9d --- /dev/null +++ b/src/main/kotlin/lotto/Input.kt @@ -0,0 +1,32 @@ +package lotto + +import camp.nextstep.edu.missionutils.Console.readLine +import lotto.exception.BonusValidation +import lotto.exception.ErrorConstants +import lotto.exception.LottoValidation +import lotto.exception.PriceValidation + +class Input { + + fun inputPurchasePrice(): Int { + println("구입금액을 입력해 주세요.") + val price = readLine() + PriceValidation(price) + return price.toInt() + } + + fun inputLottoNumber(): List { + println("당첨 번호를 입력해 주세요.") + val number = readLine() + LottoValidation(number) + return number.split(",").map { it.toInt() } + } + + fun inputLottoBonusNumber(lotto: List): Int { + println("보너스 번호를 입력해 주세요.") + val bonusNumber = readLine() + BonusValidation(lotto, bonusNumber) + return bonusNumber.toInt() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/Lotto.kt b/src/main/kotlin/lotto/Lotto.kt index 5ca00b4e4..11da770cc 100644 --- a/src/main/kotlin/lotto/Lotto.kt +++ b/src/main/kotlin/lotto/Lotto.kt @@ -5,5 +5,19 @@ class Lotto(private val numbers: List) { require(numbers.size == 6) } - // TODO: 추가 기능 구현 + private fun sortLotto(): List { + return numbers.sorted() + } + + fun compareLottoNumber(lottoNumber: List): Int { + return numbers.intersect(lottoNumber.toSet()).size + } + + fun containBonusNumber(bonusNumber: Int): Boolean { + return numbers.contains(bonusNumber) + } + + fun printLotto() { + println("${sortLotto()}") + } } diff --git a/src/main/kotlin/lotto/LottoGame.kt b/src/main/kotlin/lotto/LottoGame.kt new file mode 100644 index 000000000..fa4c6d9a1 --- /dev/null +++ b/src/main/kotlin/lotto/LottoGame.kt @@ -0,0 +1,61 @@ +package lotto + +class LottoGame { + + fun play() { + val input = Input() + val lottoManager = LottoManager() + val compareLotto = CompareLotto() + + val price = getPrice(input) + lottoManager.createLottoes(price) + lottoManager.printLottoes(price) + + val lottoNumbers = getLottoNumber(input) + val bonusNumber = getBonusNumber(input, lottoNumbers) + + val lottoResult = compareLotto.compareLotto(lottoManager.lottoes, lottoNumbers, bonusNumber) + val rateOfReturnResult = compareLotto.calculateRateOfReturn(lottoResult, price) + compareLotto.printResult(lottoResult) + compareLotto.printRateOfReturnResult(rateOfReturnResult) + } + + private fun getPrice(input: Input): Int { + var price: Int + while (true) { + try { + price = input.inputPurchasePrice() + break + } catch (e: IllegalArgumentException) { + println(e.message) + } + } + return price + } + + private fun getLottoNumber(input: Input): List { + var lotto: List + while (true) { + try { + lotto = input.inputLottoNumber() + break + } catch (e: IllegalArgumentException) { + println(e.message) + } + } + return lotto + } + + private fun getBonusNumber(input: Input, lottoNumbers: List): Int { + var bonus: Int + while (true) { + try { + bonus = input.inputLottoBonusNumber(lottoNumbers) + break + } catch (e: IllegalArgumentException) { + println(e.message) + } + } + return bonus + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/LottoManager.kt b/src/main/kotlin/lotto/LottoManager.kt new file mode 100644 index 000000000..2876fc4eb --- /dev/null +++ b/src/main/kotlin/lotto/LottoManager.kt @@ -0,0 +1,30 @@ +package lotto + +import camp.nextstep.edu.missionutils.Randoms + +class LottoManager { + + private val _lottoes = mutableListOf() + val lottoes: List + get() = _lottoes + + fun createLottoes(price: Int) { + val lottoNumbers = price / 1000 + repeat(lottoNumbers) { + _lottoes.add(Lotto(getLotto())) + } + } + + private fun getLotto(): MutableList { + return Randoms.pickUniqueNumbersInRange(1, 45, 6) + } + + fun printLottoes(price: Int) { + val lottoNumbers = price / 1000 + println("${lottoNumbers}개를 구매했습니다.") + lottoes.forEach { lotto -> + lotto.printLotto() + } + println() + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/exception/BonusValidation.kt b/src/main/kotlin/lotto/exception/BonusValidation.kt new file mode 100644 index 000000000..a8d7aaae1 --- /dev/null +++ b/src/main/kotlin/lotto/exception/BonusValidation.kt @@ -0,0 +1,32 @@ +package lotto.exception + +class BonusValidation(private val lotto: List, private val bonus: String) { + + init { + validateBonusRange() + validateBonusNumber() + validateBonusDuplicate() + } + + private fun validateBonusRange() { + require(bonus.toInt() in 1..45) { + ErrorConstants.INVALID_LOTTO_RANGE_ERROR + } + } + + private fun validateBonusNumber() { + try { + bonus.toInt() + } catch (e: NumberFormatException) { + throw IllegalArgumentException(ErrorConstants.INVALID_PRICE_FORMAT_ERROR) + } + } + + private fun validateBonusDuplicate() { + repeat(lotto.size) { + require(lotto[it] != bonus.toInt()) { + ErrorConstants.DUPLICATE_LOTTO_ERROR + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/exception/ErrorConstants.kt b/src/main/kotlin/lotto/exception/ErrorConstants.kt new file mode 100644 index 000000000..b810eb4c2 --- /dev/null +++ b/src/main/kotlin/lotto/exception/ErrorConstants.kt @@ -0,0 +1,10 @@ +package lotto.exception + +object ErrorConstants { + const val INVALID_PRICE_FORMAT_ERROR = "[ERROR] 숫자만 입력 가능합니다." + const val INVALID_PRICE_UNIT_ERROR = "[ERROR] 1,000원 단위의 값을 입력해주세요." + const val INVALID_PRICE_RANGE_ERROR = "[ERROR] 1,000원 이상의 값을 입력해주세요." + const val INVALID_LOTTO_FORMAT_ERROR = "[ERROR] 번호를 쉼표 기준으로 입력해주세요." + const val INVALID_LOTTO_RANGE_ERROR = "[ERROR] 1~45 사이의 숫자만 가능합니다." + const val DUPLICATE_LOTTO_ERROR = "[ERROR] 중복된 숫자는 입력할 수 없습니다." +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/exception/LottoValidation.kt b/src/main/kotlin/lotto/exception/LottoValidation.kt new file mode 100644 index 000000000..b218abf4d --- /dev/null +++ b/src/main/kotlin/lotto/exception/LottoValidation.kt @@ -0,0 +1,42 @@ +package lotto.exception + +class LottoValidation(private val lotto: String) { + private val input: List = lotto.split(",") + + init { + validateLottoComma() + validateLottoRange() + validateLottoNumber() + validateLottoDuplication() + } + + private fun validateLottoComma() { + require(lotto.contains(",")) { + ErrorConstants.INVALID_LOTTO_FORMAT_ERROR + } + } + + private fun validateLottoRange() { + repeat(input.size) { + require(input[it].toInt() in 1..45) { + ErrorConstants.INVALID_LOTTO_RANGE_ERROR + } + } + } + + private fun validateLottoNumber() { + repeat(input.size) { + try { + input[it].toInt() + } catch (e: NumberFormatException) { + throw IllegalArgumentException(ErrorConstants.INVALID_PRICE_FORMAT_ERROR) + } + } + } + + private fun validateLottoDuplication() { + require(input.toSet().size == input.size) { + ErrorConstants.DUPLICATE_LOTTO_ERROR + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/exception/PriceValidation.kt b/src/main/kotlin/lotto/exception/PriceValidation.kt new file mode 100644 index 000000000..4a178ff47 --- /dev/null +++ b/src/main/kotlin/lotto/exception/PriceValidation.kt @@ -0,0 +1,28 @@ +package lotto.exception + +class PriceValidation(private val price: String) { + + init { + validatePriceDigit() + validatePriceUnit() + validatePricePositive() + } + + private fun validatePriceDigit() { + require(price.toIntOrNull() != null) { + ErrorConstants.INVALID_PRICE_FORMAT_ERROR + } + } + + private fun validatePriceUnit() { + require(price.toInt() % 1000 == 0) { + ErrorConstants.INVALID_PRICE_UNIT_ERROR + } + } + + private fun validatePricePositive() { + require(price.toInt() >= 1000) { + ErrorConstants.INVALID_PRICE_RANGE_ERROR + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/model/LottoType.kt b/src/main/kotlin/lotto/model/LottoType.kt new file mode 100644 index 000000000..fcc67b746 --- /dev/null +++ b/src/main/kotlin/lotto/model/LottoType.kt @@ -0,0 +1,5 @@ +package lotto.model + +enum class LottoType { + THREE, FOUR, FIVE, FIVE_BONUS, SIX +} \ No newline at end of file