Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[숫자 야구 게임] 박준현 미션 제출합니다. #252

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## 🚀 기능 요구 사항


[x] 1부터 9까지 서로 다른 수로 이루어진 3자리의 수여야 한다.
[x] 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱
[x] 상대방의 역할을 컴퓨터가 하며, 1에서 9까지 서로 다른 임의의 수 3개를 선택한다.
[x] 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료
[x] 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.
[x] 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다.
10 changes: 9 additions & 1 deletion src/main/kotlin/baseball/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
package baseball

import baseball.controller.Controller
import baseball.view.InputView
import baseball.view.OutputView

fun main() {
TODO("프로그램 구현")
val inputView = InputView()
val outputView = OutputView()
val controller = Controller(inputView, outputView)

controller.process()
}
57 changes: 57 additions & 0 deletions src/main/kotlin/baseball/controller/Controller.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package baseball.controller

import baseball.model.Inning
import baseball.model.PlayingNumber
import baseball.model.Score
import baseball.view.InputView
import baseball.view.OutputView

private const val START_CODE = 1
private const val END_CODE = 2

class Controller(
private val inputView: InputView,
private val outputView: OutputView
) {

fun process() {
do {
playInning()
} while (shouldContinueGame())
}

private fun playInning() {
outputView.printStartMessage()
val computerNumber = PlayingNumber.pitchBall()
val inning = Inning(computerNumber)

while (true) {
val userNumber = inputView.readNumbers()
val score = inning.matchUp(userNumber)

if (score.isEnd()) {
outputView.printEndMessage()
break
}
printResult(score)
}
}

private fun printResult(score: Score) {
if (score.isNothing()) {
outputView.printNothing()
return
}
outputView.printResult(ball = score.balls, strike = score.strikes)
}

private fun shouldContinueGame(): Boolean {
val restartCode = inputView.readRestart()

return when (restartCode) {
START_CODE -> true
END_CODE -> false
else -> throw IllegalArgumentException("$START_CODE 또는 ${END_CODE}만 입력해주세요")
}
}
}
14 changes: 14 additions & 0 deletions src/main/kotlin/baseball/model/Inning.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package baseball.model

class Inning(private val pitcher: PlayingNumber) {

fun matchUp(userNumber: String): Score {
val playingNumber = PlayingNumber.from(userNumber)

val strikes = playingNumber.countStrike(pitcher)
val balls = playingNumber.countBall(pitcher)

return Score(strikes, balls)
}

}
62 changes: 62 additions & 0 deletions src/main/kotlin/baseball/model/PlayingNumber.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package baseball.model

import camp.nextstep.edu.missionutils.Randoms

data class PlayingNumber(val numbers: List<Int>) {

init {
validateSize(numbers)
validateNoDuplicateDigits(numbers)
validateInRange(numbers)
}

fun countStrike(playingNumber: PlayingNumber): Int = numbers.zip(playingNumber.numbers).count { (a, b) -> a == b }

fun countBall(playingNumber: PlayingNumber): Int = numbers.filter { it in playingNumber.numbers }.size - countStrike(playingNumber)

private fun validateSize(numbers: List<Int>) {
require(numbers.size == DIGIT_COUNT) {
"${DIGIT_COUNT}자리 숫자만 입력해주세요."
}
}

private fun validateNoDuplicateDigits(numbers: List<Int>) {
val uniqueDigits = numbers.toSet()

require(uniqueDigits.size == DIGIT_COUNT) {
"서로 다른 숫자들만 입력해주세요."
}
}

private fun validateInRange(numbers: List<Int>) {
require(numbers.all { it in MIN_NUMBER..MAX_NUMBER }) {
"${MIN_NUMBER}~${MAX_NUMBER} 사이의 자연수만 입력해주세요."
}
}

companion object {

private const val DIGIT_COUNT = 3
private const val MIN_NUMBER = 1
private const val MAX_NUMBER = 9

fun from(playingNumber: String): PlayingNumber {
val numbers: List<Int> = playingNumber.map { Character.getNumericValue(it) }

return PlayingNumber(numbers)
}

fun pitchBall(): PlayingNumber {
val uniqueNumbers: MutableSet<Int> = linkedSetOf()

while (uniqueNumbers.size < DIGIT_COUNT) {
val randomNumber: Int = Randoms.pickNumberInRange(MIN_NUMBER, MAX_NUMBER)
uniqueNumbers.add(randomNumber)
}

return PlayingNumber(uniqueNumbers.toList())
}

}

}
40 changes: 40 additions & 0 deletions src/main/kotlin/baseball/model/Score.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package baseball.model


data class Score(
val strikes: Int,
val balls: Int
) {

init {
validateCount(strikes, balls)
validateRange(strikes, balls)
}

fun isEnd(): Boolean {
return strikes == MAX_NUMBER && balls == MIN_NUMBER
}

fun isNothing(): Boolean {
return strikes == MIN_NUMBER && balls == MIN_NUMBER
}

private fun validateCount(strike: Int, ball: Int) {
require(strike + ball in MIN_NUMBER..MAX_NUMBER) {
"스트라이크와 볼의 합계는 ${MIN_NUMBER}와 $MAX_NUMBER 사이에 위치해야합니다."
}
}

private fun validateRange(strike: Int, ball: Int) {
require(strike >= MIN_NUMBER && ball >= MIN_NUMBER) {
"스트라이크와 볼은 ${MIN_NUMBER}보다 커야 합니다."
}
}

companion object {

private const val MAX_NUMBER = 3
private const val MIN_NUMBER = 0

}
}
21 changes: 21 additions & 0 deletions src/main/kotlin/baseball/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package baseball.view


import camp.nextstep.edu.missionutils.Console

private const val RESTART_NUMBER = 1
private const val END_NUMBER = 2

class InputView {
fun readNumbers(): String {
print("숫자를 입력해주세요 : ")

return Console.readLine()
}

fun readRestart(): Int {
println("게임을 새로 시작하려면 ${RESTART_NUMBER}, 종료하려면 ${END_NUMBER}를 입력하세요.")

return Console.readLine().toInt()
}
}
24 changes: 24 additions & 0 deletions src/main/kotlin/baseball/view/OutputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package baseball.view


private const val END_STRIKE = 3

class OutputView {

fun printStartMessage() {
println("숫자 야구 게임을 시작합니다.")
}

fun printEndMessage() {
println("${END_STRIKE}스트라이크")
println("${END_STRIKE}개의 숫자를 모두 맞히셨습니다! 게임 종료")
}

fun printNothing() {
println("낫싱")
}

fun printResult(ball: Int, strike: Int) {
println("${ball}${strike}스트라이크")
}
}
70 changes: 70 additions & 0 deletions src/test/kotlin/baseball/model/InningTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package baseball.model

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll


class InningTest {

private lateinit var inning: Inning

@BeforeEach
fun setUp() {
// given
val pitcherNumber = PlayingNumber.from(THREE_STRIKES)
inning = Inning(pitcherNumber)
}

@Test
@DisplayName("모든 숫자가 스트라이크인 경우")
fun playWhenAllStrikesThenIsEndTrue() {
// when
val score = inning.matchUp(THREE_STRIKES)

// then
assertAll(
assertThat(score.isEnd())::isTrue,
{ assertThat(score.strikes).isEqualTo(3) },
{ assertThat(score.balls).isEqualTo(0) }
)
}

@Test
@DisplayName("1스트라이크, 2볼인 경우")
fun play() {
// when
val score = inning.matchUp(ONE_STRIKE_TWO_BALLS)

// then
assertAll(
{ assertThat(score.strikes).isEqualTo(1) },
{ assertThat(score.balls).isEqualTo(2) }
)

}

@Test
@DisplayName("모든 숫자가 낫싱인경우")
fun play_whenNothingThenIsNothingTrue() {
// when
val score = inning.matchUp(NOTHING)

// then
assertAll(
assertThat(score.isNothing())::isTrue,
{ assertThat(score.strikes).isEqualTo(0) },
{ assertThat(score.balls).isEqualTo(0) }
)
}

companion object {

private const val THREE_STRIKES = "123"
private const val ONE_STRIKE_TWO_BALLS = "321"
private const val NOTHING = "456"
}

}
Loading