diff --git a/README.md b/README.md index 4fc0ae874..8b62fa7cc 100644 --- a/README.md +++ b/README.md @@ -1 +1,37 @@ # kotlin-lotto-precourse +## 입력 +`camp.nextstep.edu.missionutils.Console`의 `readLine()을 활용 +- [ ] `구입금액을 입력해 주세요.` 로또 구입 금액을 입력받는다. +- [ ] `당첨 번호를 입력해 주세요.` 당첨 번호를 입력 받는다. 쉼표를 기준으로 구분한다. +- [ ] `보너스 번호를 입력해 주세요.` 보너스 번호를 입력받는다. + +## Lotto +- 변수1 `번호 리스트` +- 변수2 `당첨 금액` +- 함수1 번호 정렬해서 번호 리스트에 저장 +- 함수2 번호가 몇 개 맞는지 체크해서 저장 + +## LottoController +- [ ] 로또 발매 (1,000원당 1장). `camp.nextstep.edu.missionutils.Randoms`의 `pickUniqueNumbersInRange()`를 활용. +- [ ] 수익률 계산 + +## LottoConstant +- 1등: 6개 번호 일치 / 2,000,000,000원 +- 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 +- 3등: 5개 번호 일치 / 1,500,000원 +- 4등: 4개 번호 일치 / 50,000원 +- 5등: 3개 번호 일치 / 5,000원 + +## 출력 +- [ ] `$num개를 구매했습니다.` 발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다. +- [ ] `당첨 통계` 당첨 내역을 출력한다. +- [ ] `총 수익률 $profitRate` 수익률을 출력한다. + +## 예외 +- [ ] 구입 금액은 숫자만 가능하다. +- [ ] 로또 구입 금액이 1,000원 단위로 나누어 떨어지지 않는 경우 예외 처리한다. +- [ ] 당첨 번호는 숫자여야 한다. +- [ ] 당첨 번호는 6개여야 한다. +- [ ] 보너스 번호는 숫자여야 한다. +- [ ] 보너스 번호는 1개여야 한다. +- [ ] 로또 번호는 1~45 사이의 숫자여야 한다. \ No newline at end of file diff --git a/src/main/kotlin/lotto/Application.kt b/src/main/kotlin/lotto/Application.kt index 151821c9c..523b8fab9 100644 --- a/src/main/kotlin/lotto/Application.kt +++ b/src/main/kotlin/lotto/Application.kt @@ -1,5 +1,13 @@ package lotto +import lotto.controller.LottoController +import lotto.view.Input +import lotto.view.Output + fun main() { - // TODO: 프로그램 구현 + val input: Input = Input() + val output: Output = Output() + val lottoController: LottoController = LottoController(input, output) + + lottoController.runMachine() } diff --git a/src/main/kotlin/lotto/Lotto.kt b/src/main/kotlin/lotto/Lotto.kt deleted file mode 100644 index b97abc385..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) { "[ERROR] 로또 번호는 6개여야 합니다." } - } - - // TODO: 추가 기능 구현 -} diff --git a/src/main/kotlin/lotto/constant/Error.kt b/src/main/kotlin/lotto/constant/Error.kt new file mode 100644 index 000000000..aa80ce682 --- /dev/null +++ b/src/main/kotlin/lotto/constant/Error.kt @@ -0,0 +1,9 @@ +package lotto.constant + +object Error { + const val NOT_NUMBER = "[ERROR] 숫자를 입력해주세요. %s은 숫자가 아닙니다." + const val UNIT_OF_PRICE = "[ERROR] %d원 단위로 입력해주세요." + const val LOTTO_RANGE = "[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다." + const val LOTTO_SIZE = "[ERROR] 당첨 번호는 6개의 숫자로 입력해주세요." + const val LOTTO_DUPLICATE = "[ERROR] 번호는 중복되면 안됩니다." +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/constant/Message.kt b/src/main/kotlin/lotto/constant/Message.kt new file mode 100644 index 000000000..fc09f72a9 --- /dev/null +++ b/src/main/kotlin/lotto/constant/Message.kt @@ -0,0 +1,15 @@ +package lotto.constant + +object Message { + const val INFO_GET_PURCHASE_AMOUNT = "구입금액을 입력해 주세요." + const val INFO_GET_LOTTO_NUMBERS = "당첨 번호를 입력해 주세요." + const val INFO_GET_LOTTO_BONUS_NUMBERS = "보너스 번호를 입력해주세요." + + const val COMMA = "," + + const val TICKET_PRICE = 1000 + + + const val LOTTO_RESULT_MESSAGE = "당첨 통계\n---" + const val LOTTO_RATE_OF_RETURN = "총 수익률은 %f%입니다." +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/controller/LottoController.kt b/src/main/kotlin/lotto/controller/LottoController.kt new file mode 100644 index 000000000..e110f183d --- /dev/null +++ b/src/main/kotlin/lotto/controller/LottoController.kt @@ -0,0 +1,69 @@ +package lotto.controller + +import camp.nextstep.edu.missionutils.Randoms.pickUniqueNumbersInRange +import lotto.constant.Message.TICKET_PRICE +import lotto.model.Lotto +import lotto.model.LottoTotalMatchResult +import lotto.validation.InputValidation +import lotto.view.Input +import lotto.view.Output + +class LottoController(val input: Input, val output: Output) { + private val lottos: MutableList = mutableListOf() + private val validator = InputValidation() + + private fun getLottoTicketCount(amount: Int): Int { + return amount / TICKET_PRICE + } + + private fun createRandomLotto(): Lotto { + return Lotto(pickUniqueNumbersInRange(1, 45, 6).sorted()) + } + + private fun createResult(): List { + val numbers = input.getLottoNumbers() + val bonusNumbers = input.getLottoBonusNumber() + val resultNumbers: MutableList = mutableListOf() + + repeat(numbers.size) { + resultNumbers.add(numbers[it]) + } + resultNumbers.add(bonusNumbers) + + return validator.lottoNumbersNotDuplicate(resultNumbers.toList()) + } + + private fun calculateTotalMatchResult(result: List): LottoTotalMatchResult { + val totalMatchResult = LottoTotalMatchResult() + repeat(lottos.size) { idx -> + val matchResult = lottos[idx].calculateMatchResult(result) + when (matchResult.matchNumbersCount) { + 6 -> totalMatchResult.prizeCount1 += 1 + 5 -> if (matchResult.isMatchBonus) totalMatchResult.prizeCount2 += 1 else totalMatchResult.prizeCount3 += 1 + 4 -> totalMatchResult.prizeCount4 += 1 + 3 -> totalMatchResult.prizeCount5 += 1 + } + } + return totalMatchResult + } + + private fun calculateRateOfReturn(totalMatchResult: LottoTotalMatchResult, amount: Int): Float { + return String.format("%.1f", totalMatchResult.getTotalPrize() / amount).toFloat() + } + + fun runMachine() { + val amount = input.getPurchaseAmount() + val count = getLottoTicketCount(amount) + repeat(count) { + val lotto: Lotto = createRandomLotto() + lottos.add(lotto) + } + + output.printLottoNumbers(lottos) + + val result = createResult() + val totalMatchResult = calculateTotalMatchResult(result) + val rateOfReturn = calculateRateOfReturn(totalMatchResult, amount) + output.printTotalLottoPrize(totalMatchResult, rateOfReturn) + } +} \ 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..d039b25fc --- /dev/null +++ b/src/main/kotlin/lotto/model/Lotto.kt @@ -0,0 +1,27 @@ +package lotto.model + +import lotto.validation.InputValidation + +class Lotto(private val numbers: List) { + private val validator = InputValidation() + + init { + validator.lottoNumbers(numbers) + } + + fun getLottoNumbers(): List { + return numbers + } + + fun calculateMatchResult(resultNumbers: List): LottoMatchResult { + val numbersForCheck = numbers.toSet() + val lottoResult = LottoMatchResult() + + repeat(resultNumbers.size - 1) { idx -> + if (resultNumbers[idx] in numbersForCheck) lottoResult.matchNumbersCount += 1 + } + if (resultNumbers.last() in numbersForCheck) lottoResult.isMatchBonus = true + + return lottoResult + } +} diff --git a/src/main/kotlin/lotto/model/LottoMatchResult.kt b/src/main/kotlin/lotto/model/LottoMatchResult.kt new file mode 100644 index 000000000..bb7e74401 --- /dev/null +++ b/src/main/kotlin/lotto/model/LottoMatchResult.kt @@ -0,0 +1,6 @@ +package lotto.model + +data class LottoMatchResult( + var matchNumbersCount: Int = 0, + var isMatchBonus: Boolean = false +) \ No newline at end of file diff --git a/src/main/kotlin/lotto/model/LottoTotalMatchResult.kt b/src/main/kotlin/lotto/model/LottoTotalMatchResult.kt new file mode 100644 index 000000000..47b1cefe5 --- /dev/null +++ b/src/main/kotlin/lotto/model/LottoTotalMatchResult.kt @@ -0,0 +1,16 @@ +package lotto.model + +data class LottoTotalMatchResult( + var prizeCount1: Int = 0, + var prizeCount2: Int = 0, + var prizeCount3: Int = 0, + var prizeCount4: Int = 0, + var prizeCount5: Int = 0 +) { + private fun getPrize1(): Int = prizeCount1 * 2000000000 + private fun getPrize2(): Int = prizeCount2 * 30000000 + private fun getPrize3(): Int = prizeCount3 * 1500000 + private fun getPrize4(): Int = prizeCount4 * 50000 + private fun getPrize5(): Int = prizeCount5 * 5000 + fun getTotalPrize(): Float = (getPrize1() + getPrize2() + getPrize3() + getPrize4() + getPrize5()).toFloat() +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/validation/InputValidation.kt b/src/main/kotlin/lotto/validation/InputValidation.kt new file mode 100644 index 000000000..12e92296d --- /dev/null +++ b/src/main/kotlin/lotto/validation/InputValidation.kt @@ -0,0 +1,50 @@ +package lotto.validation + +import lotto.constant.Error.LOTTO_DUPLICATE +import lotto.constant.Error.LOTTO_RANGE +import lotto.constant.Error.LOTTO_SIZE +import lotto.constant.Error.NOT_NUMBER +import lotto.constant.Error.UNIT_OF_PRICE +import lotto.constant.Message.COMMA + +class InputValidation { + fun typeInt(input: String): Int { + return input.toIntOrNull() ?: throw IllegalArgumentException(NOT_NUMBER.format(input)) + } + + fun unitOfPrice(price: Int, unit: Int): Int { + if (price % unit == 0) + return price + throw IllegalArgumentException(UNIT_OF_PRICE.format(unit)) + } + + fun lottoNumbersDelimiter(input: String): List { + return input.split(COMMA).map { + typeInt(it) + } + } + + fun lottoNumberRange(number: Int): Int { + require(number in 1..45) { LOTTO_RANGE } + return number + } + + private fun lottoNumbersSize(numbers: List): List { + require(numbers.size == 6) { LOTTO_SIZE } + return numbers + } + + fun lottoNumbersNotDuplicate(numbers: List): List { + require(numbers.toSet().size == numbers.size) { LOTTO_DUPLICATE } + return numbers + } + + fun lottoNumbers(numbers: List): List { + return numbers.map { + lottoNumberRange(it) + }.also { + lottoNumbersSize(it) + lottoNumbersNotDuplicate(it) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/view/Input.kt b/src/main/kotlin/lotto/view/Input.kt new file mode 100644 index 000000000..22573af54 --- /dev/null +++ b/src/main/kotlin/lotto/view/Input.kt @@ -0,0 +1,45 @@ +package lotto.view + +import camp.nextstep.edu.missionutils.Console +import lotto.constant.Message.INFO_GET_LOTTO_BONUS_NUMBERS +import lotto.constant.Message.INFO_GET_LOTTO_NUMBERS +import lotto.constant.Message.INFO_GET_PURCHASE_AMOUNT +import lotto.constant.Message.TICKET_PRICE +import lotto.validation.InputValidation + +class Input { + private val inputValidation = InputValidation() + + fun getPurchaseAmount(): Int { + println(INFO_GET_PURCHASE_AMOUNT) + val value = readLine().let { + inputValidation.typeInt(it).also { amount -> + inputValidation.unitOfPrice(amount, TICKET_PRICE) + } + } + println() + return value + } + + fun getLottoNumbers(): List { + println(INFO_GET_LOTTO_NUMBERS) + val value = readLine().let { + inputValidation.lottoNumbers(inputValidation.lottoNumbersDelimiter(it)) + } + println() + return value + } + + fun getLottoBonusNumber(): Int { + println(INFO_GET_LOTTO_BONUS_NUMBERS) + val value = readLine().let { + inputValidation.typeInt(it).also { number -> + inputValidation.lottoNumberRange(number) + } + } + println() + return value + } + + private fun readLine(): String = Console.readLine() +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/view/Output.kt b/src/main/kotlin/lotto/view/Output.kt new file mode 100644 index 000000000..1497da6a2 --- /dev/null +++ b/src/main/kotlin/lotto/view/Output.kt @@ -0,0 +1,26 @@ +package lotto.view + +import lotto.constant.Message.LOTTO_RATE_OF_RETURN +import lotto.constant.Message.LOTTO_RESULT_MESSAGE +import lotto.model.Lotto +import lotto.model.LottoTotalMatchResult + +class Output { + fun printLottoNumbers(lottos: List) { + println("${lottos.size}개를 구매했습니다.") + repeat(lottos.size) { idx -> + println(lottos[idx].getLottoNumbers()) + } + println() + } + + fun printTotalLottoPrize(totalMatchResult: LottoTotalMatchResult, rateOfReturn: Float) { + println(LOTTO_RESULT_MESSAGE) + println("3개 일치 (5,000원) - ${totalMatchResult.prizeCount5}개") + println("4개 일치 (50,000원) - ${totalMatchResult.prizeCount4}개") + println("5개 일치 (1,500,000원) - ${totalMatchResult.prizeCount3}개") + println("5개 일치, 보너스 볼 일치 (30,000,000원) - ${totalMatchResult.prizeCount2}개") + println("6개 일치 (2,000,000,000원) - ${totalMatchResult.prizeCount1}개") + println(LOTTO_RATE_OF_RETURN.format(rateOfReturn)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/lotto/LottoTest.kt b/src/test/kotlin/lotto/LottoTest.kt index 122fae572..3703b1d62 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 @@ -20,4 +21,17 @@ class LottoTest { } // TODO: 추가 기능 구현에 따른 테스트 코드 작성 + @Test + fun `로또 번호가 1보다 작은 숫자면 예외가 발생한다`() { + assertThrows { + Lotto(listOf(0, 2, 3, 4, 5, 6)) + } + } + + @Test + fun `로또 번호가 45보다 큰 숫자면 예외가 발생한다`() { + assertThrows { + Lotto(listOf(1, 2, 3, 4, 5, 46)) + } + } }