diff --git a/kotlin-baseball/src/main/kotlin/baseball/Application.kt b/kotlin-baseball/src/main/kotlin/baseball/Application.kt index 148d75c..42931f3 100644 --- a/kotlin-baseball/src/main/kotlin/baseball/Application.kt +++ b/kotlin-baseball/src/main/kotlin/baseball/Application.kt @@ -1,5 +1,9 @@ package baseball +import baseball.controller.BaseballController +import baseball.view.View + fun main() { - TODO("프로그램 구현") + val baseballController = BaseballController(View()) + baseballController.start() } diff --git a/kotlin-baseball/src/main/kotlin/baseball/controller/BaseballController.kt b/kotlin-baseball/src/main/kotlin/baseball/controller/BaseballController.kt new file mode 100644 index 0000000..c7c68e0 --- /dev/null +++ b/kotlin-baseball/src/main/kotlin/baseball/controller/BaseballController.kt @@ -0,0 +1,26 @@ +package baseball.controller + +import baseball.model.NUMBER_LENGTH +import baseball.model.Numbers +import baseball.model.StrikeBall +import baseball.model.UserContinueGame +import baseball.view.View + +class BaseballController( + private val view: View, +) { + fun start() { + view.printStartMessage() + do { + val computerNumbers = Numbers.generateRandom() + do { + val userNumbers = view.getUserNumbers() + val strikeBall = userNumbers.compare(computerNumbers) + view.printStrikeBall(strikeBall) + } while (strikeBall.isGameContinue()) + view.printEndMessage() + } while (view.getUserContinueGame() == UserContinueGame.RESTART) + } + + private fun StrikeBall.isGameContinue() = this.strike != NUMBER_LENGTH +} diff --git a/kotlin-baseball/src/main/kotlin/baseball/model/Numbers.kt b/kotlin-baseball/src/main/kotlin/baseball/model/Numbers.kt new file mode 100644 index 0000000..0723b6c --- /dev/null +++ b/kotlin-baseball/src/main/kotlin/baseball/model/Numbers.kt @@ -0,0 +1,55 @@ +package baseball.model + +import camp.nextstep.edu.missionutils.Randoms + +const val MIN_NUMBER_INCLUDE = 1 +const val MAX_NUMBER_INCLUDE = 9 +const val NUMBER_LENGTH = 3 + +data class Numbers(val numbers: List) { + companion object { + fun generateRandom() = + Numbers( + mutableListOf().apply { + while (size != NUMBER_LENGTH) { + val pickNumber = Number(Randoms.pickNumberInRange(MIN_NUMBER_INCLUDE, MAX_NUMBER_INCLUDE)) + if (pickNumber !in this) { + this.add(pickNumber) + } + } + }.toList() + ) + } + + init { + require(numbers.size == NUMBER_LENGTH) + require(numbers.distinct().size == numbers.size) + } + + fun compare(other: Numbers): StrikeBall { + val strike = getStrike(other) + val ball = getBall(other) + return StrikeBall(strike, ball) + } + + fun getStrike(other: Numbers) = + (0 until NUMBER_LENGTH) + .count { this.numbers[it] == other.numbers[it] } + + fun getBall(other: Numbers) = + (0 until NUMBER_LENGTH) + .count { i -> isBall(i, other) } + + private fun isBall(i: Int, other: Numbers) = + (0 until NUMBER_LENGTH) + .any { j -> + if (i == j) return@any false + this.numbers[i] == other.numbers[j] + } +} + +data class Number(val number: Int) { + init { + require(number in MIN_NUMBER_INCLUDE..MAX_NUMBER_INCLUDE) + } +} diff --git a/kotlin-baseball/src/main/kotlin/baseball/model/StrikeBall.kt b/kotlin-baseball/src/main/kotlin/baseball/model/StrikeBall.kt new file mode 100644 index 0000000..4308870 --- /dev/null +++ b/kotlin-baseball/src/main/kotlin/baseball/model/StrikeBall.kt @@ -0,0 +1,17 @@ +package baseball.model + +data class StrikeBall(val strike: Int, val ball: Int) { + override fun toString() = + buildString { + if (ball != 0) { + append("${ball}볼 ") + } + if (strike != 0) { + append("${strike}스트라이크 ") + } + if (strike == 0 && ball == 0) { + append("낫싱") + } + return toString() + } +} diff --git a/kotlin-baseball/src/main/kotlin/baseball/model/UserContinueGame.kt b/kotlin-baseball/src/main/kotlin/baseball/model/UserContinueGame.kt new file mode 100644 index 0000000..6e8a823 --- /dev/null +++ b/kotlin-baseball/src/main/kotlin/baseball/model/UserContinueGame.kt @@ -0,0 +1,14 @@ +package baseball.model + +enum class UserContinueGame(val value: Int) { + RESTART(1), + END(2), + ; + + companion object { + fun of(input: String): UserContinueGame { + return entries.find { it.value == input.toInt() } + ?: throw IllegalArgumentException("Input must in $entries. input: $input") + } + } +} diff --git a/kotlin-baseball/src/main/kotlin/baseball/view/View.kt b/kotlin-baseball/src/main/kotlin/baseball/view/View.kt new file mode 100644 index 0000000..1915437 --- /dev/null +++ b/kotlin-baseball/src/main/kotlin/baseball/view/View.kt @@ -0,0 +1,30 @@ +package baseball.view + +import baseball.model.NUMBER_LENGTH +import baseball.model.Number +import baseball.model.Numbers +import baseball.model.StrikeBall +import baseball.model.UserContinueGame +import camp.nextstep.edu.missionutils.Console + +class View { + fun printStartMessage() { + println("숫자 야구 게임을 시작합니다.") + } + + fun getUserNumbers(): Numbers { + print("숫자를 입력해주세요 : ") + return Numbers(Console.readLine().map { Number(it.digitToInt()) }) + } + + fun getUserContinueGame() = UserContinueGame.of(Console.readLine()) + + fun printStrikeBall(strikeBall: StrikeBall) { + println(strikeBall) + } + + fun printEndMessage() { + println("${NUMBER_LENGTH}개의 숫자를 모두 맞히셨습니다! 게임 종료") + println("게임을 새로 시작하려면 ${UserContinueGame.RESTART.value}, 종료하려면 ${UserContinueGame.END.value}를 입력하세요.") + } +}