diff --git a/docs/README.md b/docs/README.md index e69de29bb..ad30c01bb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,39 @@ +# 기능목록 + +1. 구입 금액 입력 구문 출력 +2. 구입 금액 입력 + - 예외 `IllegalArgumentException` + - 구입 금액이 정수가 아닐 경우(문구 : [ERROR] 구입 금액은 정수여야 합니다.) + - 구입 금액이 1000원 단위가 아닐 경우(문구 : [ERROR] 구입 금액은 1000원 단위여야 합니다.) +3. 구입 개수 계산 +4. 구입 개수 출력 +5. 구입 개수에 맞게 로또 발행 +6. 발행된 로또 출력 +7. 당첨 번호 입력 구문 출력 +8. 당첨 번호 입력 + - 당첨 번호를 쉼표를 구분자로 나누어 문자열 배열로 저장 + - 나눠진 각 문자열 예외 `IllegalArgumentException` + - 숫자가 아닐 경우(문구 : [ERROR] 당첨 번호는 숫자여야 합니다.) + - 1 ~ 45 가 아닐 경우(문구 : [ERROR] 당첨 번호는 1 ~ 45 사이여야 합니다.) + - 당첨 번호가 6개가 아닐 경우(문구 : [ERROR] 당첨 번호는 6개여야 합니다.) + - 당첨 번호가 중복될 경우(문구 : [ERROR] 당첨 번호는 중복되지 않아야 합니다.) + - 예외사항이 아니면 정수 배열로 변경 후 Lotto 클래스의 생성자에 멤버변수로 전달 +9. 보너스 번호 입력 구문 출력 +10. 보너스 번호 입력 + - 예외 `IllegalArgumentException` + - 정수가 아닐 경우(문구 : [ERROR] 보너스 번호는 정수여야 합니다.) + - 1 ~ 45 가 아닐 경우(문구 : [ERROR] 보너스 번호는 1 ~ 45 사이여야 합니다.) + - 당첨 번호들과 중복될 경우(문구 : [ERROR] 보너스 번호는 당첨 번호와 중복되지 않아야 합니다.) +11. 발행된 로또들을 순회하며 당첨 번호와 몇 개가 일치하는지 확인 + - 3,4,6개가 일치한다면, 일치 개수에 따라 5칸짜리 정수 배열에 할당 + - 0번째 인덱스 : 3개 일치 개수 + - 1번째 인덱스 : 4개 일치 개수 + - 2번째 인덱스 : 0 + - 3번째 인덱스 : 0 + - 4번째 인덱스 : 6개 일치 개수 + - 5개가 일치한다면, 보너스 번호와 일치하는지 확인 + - 일치한다면 3번째 인덱스에 1 증가 + - 일치하지 않는다면 2번째 인덱스에 1 증가 +12. 수익률 계산 + - 수익률 = 당첨 금액 / 구입 금액 * 100 +13. 당첨 통계 및 수익률 출력 \ No newline at end of file diff --git a/src/main/kotlin/lotto/Application.kt b/src/main/kotlin/lotto/Application.kt index d7168761c..3db9bcb38 100644 --- a/src/main/kotlin/lotto/Application.kt +++ b/src/main/kotlin/lotto/Application.kt @@ -1,5 +1,15 @@ package lotto fun main() { - TODO("프로그램 구현") + LottoUI().printBuyPrice() + val buyPrice = LottoUI().inputBuyPrice() + LottoUI().printBuyLottoCount(buyPrice) + val buyCount = buyPrice.toInt() / 1000 + val lottos = LottoService().buyLotto(buyCount) + LottoUI().printLottoNumbers(lottos) + LottoUI().printWinningNumbers() + val winningLotto = LottoUI().inputWinningNumbers() + LottoUI().printBonusNumber() + val bonusNumber = LottoUI().inputBonusNumber(winningLotto) + LottoUI().printResult(lottos, winningLotto, bonusNumber) } diff --git a/src/main/kotlin/lotto/Lotto.kt b/src/main/kotlin/lotto/Lotto.kt index 5ca00b4e4..62f28a80b 100644 --- a/src/main/kotlin/lotto/Lotto.kt +++ b/src/main/kotlin/lotto/Lotto.kt @@ -3,7 +3,18 @@ package lotto class Lotto(private val numbers: List) { init { require(numbers.size == 6) + require(numbers.distinct().size == 6) } - // TODO: 추가 기능 구현 + fun getNumbers(): List { + return numbers + } + + fun matchCount(winningLotto: Lotto): Any { + return numbers.intersect(winningLotto.getNumbers().toSet()).size + } + + fun matchBonusNumber(bonusNumber: Int): Boolean { + return numbers.contains(bonusNumber) + } } diff --git a/src/main/kotlin/lotto/LottoService.kt b/src/main/kotlin/lotto/LottoService.kt new file mode 100644 index 000000000..437ac45b7 --- /dev/null +++ b/src/main/kotlin/lotto/LottoService.kt @@ -0,0 +1,78 @@ +package lotto + +import camp.nextstep.edu.missionutils.Randoms +import lotto.enumeration.Bonus +import lotto.enumeration.Buy +import lotto.enumeration.Winning + +class LottoService { + fun buyLotto(buyCount: Int): List { + val buyLottos = mutableListOf() + repeat(buyCount) { + buyLottos.add(Lotto(lottoMaker())) + } + return buyLottos + } + + + private fun lottoMaker(): List { + return Randoms.pickUniqueNumbersInRange(1, 45, 6) + } + + fun checkInvalidBuyPrice(buyPrice: String) { + when { + buyPrice.toIntOrNull() == null -> throw IllegalArgumentException(Buy.ERROR_NOT_INTEGER.value) + buyPrice.toInt() % 1000 != 0 -> throw IllegalArgumentException(Buy.ERROR_NOT_THOUSAND.value) + buyPrice.toInt() == 0 -> throw IllegalArgumentException(Buy.ERROR_NOT_THOUSAND.value) + } + } + + fun checkInvalidWinningNumbers(winningNumbers: List) { + when { + winningNumbers.map { it.toIntOrNull() } + .contains(null) -> throw IllegalArgumentException(Winning.ERROR_NOT_INTEGER.value) + + winningNumbers.map { it.toInt() } + .any { it !in 1..45 } -> throw IllegalArgumentException(Winning.ERROR_NOT_RANGE.value) + + winningNumbers.size != 6 -> throw IllegalArgumentException(Winning.ERROR_NOT_SIX.value) + winningNumbers.map { it.toInt() } + .distinct().size != 6 -> throw IllegalArgumentException(Winning.ERROR_NOT_UNIQUE.value) + } + } + + fun checkInvalidBonusNumber(bonusNumber: String, winningLotto: Lotto) { + when { + bonusNumber.toIntOrNull() == null -> throw IllegalArgumentException(Bonus.ERROR_NOT_INTEGER.value) + bonusNumber.toInt() !in 1..45 -> throw IllegalArgumentException(Bonus.ERROR_NOT_RANGE.value) + bonusNumber.toInt() in winningLotto.getNumbers() -> throw IllegalArgumentException(Bonus.ERROR_NOT_UNIQUE.value) + } + } + + fun LottoResult(lottos: List, winningLotto: Lotto, bonusNumber: Int): List { + val result = mutableListOf(0, 0, 0, 0, 0, 0) + lottos.forEach { + when (it.matchCount(winningLotto)) { + 3 -> result[0]++ + 4 -> result[1]++ + 5 -> { + if (it.matchBonusNumber(bonusNumber)) { + result[3]++ + } else { + result[2]++ + } + } + 6 -> result[4]++ + else -> result[5]++ + } + } + return result + } + + fun calculateEarnRate(result: List): Any { + val total = result.sum() + val rate = + (result[0] * 5000 + result[1] * 50000 + result[2] * 1500000 + result[3] * 30000000 + result[4] * 2000000000).toDouble() / (total * 1000) * 100 + return "%.2f".format(rate).toDouble() + } +} diff --git a/src/main/kotlin/lotto/LottoUI.kt b/src/main/kotlin/lotto/LottoUI.kt new file mode 100644 index 000000000..dda32eddd --- /dev/null +++ b/src/main/kotlin/lotto/LottoUI.kt @@ -0,0 +1,89 @@ +package lotto + +import camp.nextstep.edu.missionutils.Console +import lotto.enumeration.Bonus +import lotto.enumeration.Buy +import lotto.enumeration.Winning +import lotto.enumeration.WinningResult + +class LottoUI { + fun printBuyPrice() { + println(Buy.PRICE_INPUT.value) + } + + fun inputBuyPrice(): String { + val buyPrice = Console.readLine() + try { + LottoService().checkInvalidBuyPrice(buyPrice) + } catch (e: IllegalArgumentException) { + println(e.message) + printBuyPrice() + return inputBuyPrice() + } + return buyPrice + } + + fun printBuyLottoCount(buyPrice: String) { + println() + print(buyPrice.toInt() / 1000) + println(Buy.HOW_MANY.value) + } + + fun printLottoNumbers(lottos: List) { + lottos.forEach { + println("${it.getNumbers()}") + } + } + + fun printWinningNumbers() { + println() + println(Winning.NUMBER_INPUT.value) + } + + fun inputWinningNumbers(): Lotto { + val winningNumbers = Console.readLine().split(",") + try { + LottoService().checkInvalidWinningNumbers(winningNumbers) + } catch (e: IllegalArgumentException) { + println(e.message) + printWinningNumbers() + return inputWinningNumbers() + } + return Lotto(winningNumbersToInt(winningNumbers)) + } + + private fun winningNumbersToInt(winningNumbers: List): List { + return winningNumbers.map { it.toInt() } + } + + fun printBonusNumber() { + println() + println(Bonus.NUMBER_INPUT.value) + } + + fun inputBonusNumber(winningLotto: Lotto): Int { + val bonusNumber = Console.readLine() + try { + LottoService().checkInvalidBonusNumber(bonusNumber, winningLotto) + } catch (e: IllegalArgumentException) { + println(e.message) + printBonusNumber() + return inputBonusNumber(winningLotto) + } + return bonusNumber.toInt() + } + + fun printResult(lottos: List, winningLotto: Lotto, bonusNumber: Int) { + println() + println(WinningResult.STATISTIC.value) + val result: List = LottoService().LottoResult(lottos, winningLotto, bonusNumber) + println("${WinningResult.THREE_COUNT.value}${result[0]}${WinningResult.UNIT.value}") + println("${WinningResult.FOUR_COUNT.value}${result[1]}${WinningResult.UNIT.value}") + println("${WinningResult.FIVE_COUNT.value}${result[2]}${WinningResult.UNIT.value}") + println("${WinningResult.FIVE_COUNT_WITH_BONUS.value}${result[3]}${WinningResult.UNIT.value}") + println("${WinningResult.SIX_COUNT.value}${result[4]}${WinningResult.UNIT.value}") + val earnRate = LottoService().calculateEarnRate(result) + println("${WinningResult.ALL_EARN_RATE.value}${earnRate}${WinningResult.END_PERCENT.value}") + } + +} diff --git a/src/main/kotlin/lotto/enumeration/Bonus.kt b/src/main/kotlin/lotto/enumeration/Bonus.kt new file mode 100644 index 000000000..9d4e0510e --- /dev/null +++ b/src/main/kotlin/lotto/enumeration/Bonus.kt @@ -0,0 +1,8 @@ +package lotto.enumeration + +enum class Bonus(val value: String) { + NUMBER_INPUT("보너스 번호를 입력해 주세요."), + ERROR_NOT_INTEGER("[ERROR] 보너스 번호는 정수여야 합니다."), + ERROR_NOT_RANGE("[ERROR] 보너스 번호는 1 ~ 45 사이여야 합니다."), + ERROR_NOT_UNIQUE("[ERROR] 보너스 번호는 당첨 번호와 중복되지 않아야 합니다.") +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/enumeration/Buy.kt b/src/main/kotlin/lotto/enumeration/Buy.kt new file mode 100644 index 000000000..955b73599 --- /dev/null +++ b/src/main/kotlin/lotto/enumeration/Buy.kt @@ -0,0 +1,8 @@ +package lotto.enumeration + +enum class Buy(val value: String) { + PRICE_INPUT("구입금액을 입력해 주세요."), + ERROR_NOT_INTEGER("[ERROR] 구입 금액은 정수여야 합니다."), + ERROR_NOT_THOUSAND("[ERROR] 구입 금액은 1000원 단위여야 합니다."), + HOW_MANY("개를 구매했습니다.") +} diff --git a/src/main/kotlin/lotto/enumeration/Winning.kt b/src/main/kotlin/lotto/enumeration/Winning.kt new file mode 100644 index 000000000..9fa25ae32 --- /dev/null +++ b/src/main/kotlin/lotto/enumeration/Winning.kt @@ -0,0 +1,9 @@ +package lotto.enumeration + +enum class Winning(val value: String) { + NUMBER_INPUT("당첨 번호를 입력해 주세요."), + ERROR_NOT_INTEGER("[ERROR] 당첨 번호는 정수여야 합니다."), + ERROR_NOT_RANGE("[ERROR] 당첨 번호는 1 ~ 45 사이여야 합니다."), + ERROR_NOT_SIX("[ERROR] 당첨 번호는 6개여야 합니다."), + ERROR_NOT_UNIQUE("[ERROR] 당첨 번호는 중복되지 않아야 합니다.") +} diff --git a/src/main/kotlin/lotto/enumeration/WinningResult.kt b/src/main/kotlin/lotto/enumeration/WinningResult.kt new file mode 100644 index 000000000..7eeb8642e --- /dev/null +++ b/src/main/kotlin/lotto/enumeration/WinningResult.kt @@ -0,0 +1,13 @@ +package lotto.enumeration + +enum class WinningResult(val value: String) { + STATISTIC("당첨 통계\n---"), + THREE_COUNT("3개 일치 (5,000원) - "), + FOUR_COUNT("4개 일치 (50,000원) - "), + FIVE_COUNT("5개 일치 (1,500,000원) - "), + FIVE_COUNT_WITH_BONUS("5개 일치, 보너스 볼 일치 (30,000,000원) - "), + SIX_COUNT("6개 일치 (2,000,000,000원) - "), + UNIT("개"), + ALL_EARN_RATE("총 수익률은 "), + END_PERCENT("%입니다."); +} diff --git a/src/test/kotlin/lotto/LottoServiceTest.kt b/src/test/kotlin/lotto/LottoServiceTest.kt new file mode 100644 index 000000000..71de7d058 --- /dev/null +++ b/src/test/kotlin/lotto/LottoServiceTest.kt @@ -0,0 +1,68 @@ +package lotto + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class LottoServiceTest { + + @Test + fun `구입 개수에 맞게 로또 발행`() { + var lotto = LottoService().buyLotto(3) + assertEquals(3, lotto.size) + lotto = LottoService().buyLotto(5) + assertEquals(5, lotto.size) + } + + @Test + fun `구입 금액 입력 에러 발생 테스트`() { + assertThrows { + LottoService().checkInvalidBuyPrice("abc") + } + assertThrows { + LottoService().checkInvalidBuyPrice("1001") + } + assertThrows { + LottoService().checkInvalidBuyPrice("0") + } + assertThrows { + LottoService().checkInvalidBuyPrice("-1") + } + LottoService().checkInvalidBuyPrice("1000") + } + + @Test + fun `당첨 번호 입력 에러 발생 테스트`() { + assertThrows { + LottoService().checkInvalidWinningNumbers("a,b,c,d,5,6".split(",")) + } + assertThrows { + LottoService().checkInvalidWinningNumbers("100,2,3,4,50,60".split(",")) + } + assertThrows { + LottoService().checkInvalidWinningNumbers("1,2,3,4,5".split(",")) + } + assertThrows { + LottoService().checkInvalidWinningNumbers("1,2,3,4,5,6,7".split(",")) + } + assertThrows { + LottoService().checkInvalidWinningNumbers("1,2,6,4,5,6".split(",")) + } + LottoService().checkInvalidWinningNumbers("1,2,3,4,5,6".split(",")) + } + + @Test + fun `보너스 번호 입력 에러 발생 테스트`() { + val lottoNumbers = Lotto("1,2,3,4,5,6".split(",").map { it.toInt() }) + assertThrows { + LottoService().checkInvalidBonusNumber("a", lottoNumbers) + } + assertThrows { + LottoService().checkInvalidBonusNumber("100", lottoNumbers) + } + assertThrows { + LottoService().checkInvalidBonusNumber("1", lottoNumbers) + } + LottoService().checkInvalidBonusNumber("7", lottoNumbers) + } +} \ No newline at end of file