diff --git a/README.md b/README.md index 62fa463c..96bd7ecf 100644 --- a/README.md +++ b/README.md @@ -1 +1,79 @@ # kotlin-racingcar-precourse + +초간단 자동차 경주 게임을 구현한다. + +# 구현 기능 목록 + +- 몇 번 이동할 것인지 횟수 입력 기능 +- 자동차의 이름 입력 기능 + - 쉼표(`,`) 기준으로 한 번에 n 대의 이름 입력 받기 + - 해당 기능에서 자동차 갯수 확인 + - 각각의 이름은 5자 이하 +- 자동차의 전진 기능 + - 전진 조건은 0~9 사이 무작위 값이 4 이상일 경우 +- 각 실행 결과 출력 기능 +- 우승자 출력 기능 + - 우승자는 한 명 이상일 수 있음 + - 쉼표(`,`)로 우승자 여러 명 출력 +- 잘못된 입력값에 대해 IllegalArgumentException 발생 기능 + +# 프로그래밍 요구사항 + +- **`indent depth` 2 이하로 구현할 것** +- 함수는 한 가지 일만 담당하게끔 구현 +- 위의 기능 목록에 대해 각각을 테스트할 것 +- 외부 라이브러리 사용 불가 +- `Kotlin Style Guide` 원칙 + +# 라이브러리 +- `camp.nextstep.edu.missionutils` + - `Randoms.pickNumberInRange()` + - `Console.readLine()` + +# 실행 결과 예시 +``` +경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분) +pobi,woni,jun +시도할 횟수는 몇 회인가요? +5 + +실행 결과 +pobi : - +woni : +jun : - + +pobi : -- +woni : - +jun : -- + +pobi : --- +woni : -- +jun : --- + +pobi : ---- +woni : --- +jun : ---- + +pobi : ----- +woni : ---- +jun : ----- + +최종 우승자 : pobi, jun +``` + +--- + +# 슈도 코드 작성 + +```kotlin +val carMap: Map +val attemptNumber: Int + +for (attemptNumber) { + randomDice(carMap) + printRaceResult(carMap) // carMap의 Int 이용 +} + +val winner = getWinner(carMap) // carMap의 Int 이용 최댓값 추출 +printWinner(winner) +``` diff --git a/src/main/kotlin/racingcar/Application.kt b/src/main/kotlin/racingcar/Application.kt index 0d8f3a79..38bbc9c0 100644 --- a/src/main/kotlin/racingcar/Application.kt +++ b/src/main/kotlin/racingcar/Application.kt @@ -1,5 +1,82 @@ package racingcar +import camp.nextstep.edu.missionutils.Console.readLine +import camp.nextstep.edu.missionutils.Randoms.pickNumberInRange +import kotlin.collections.set + +val isCarNameValidLength = { carName: String -> carName.length <= 5 } +val isMoveForwardValid = { pickNumberInRange(0, 9) >= 4 } +val isWinnerValid = { carMap: MutableMap, count: Int -> count == carMap.values.maxOrNull() } + fun main() { - // TODO: 프로그램 구현 + + promptForCarNames() + val carList = processCarNames(readLine()) + val carMap = initializeCarMap(carList) + + promptForAttemptNumber() + val attemptNumber = processAttemptNumber(readLine()) + + printAllRaceResults(carMap, attemptNumber) + + printWinners(carMap) +} + +fun promptForCarNames() = println(Strings.MESSAGE_INPUT_CAR_NAME) +fun promptForAttemptNumber() = println(Strings.MESSAGE_INPUT_ATTEMPT_NUMBER) + +fun processCarNames(input: String): List { + val carList = input.split(",").map { it.trim() } + require(carList.all { it.isNotBlank() }) { Strings.MESSAGE_EXCEPTION_INPUT_CAR_NAMES } + return carList +} + +fun processAttemptNumber(input: String): Int { + require(input.toIntOrNull() != null) { Strings.MESSAGE_EXCEPTION_INPUT_ATTEMPT_NUMBER } + return input.toInt() } + + +fun initializeCarMap(carList: List): MutableMap { + val carMap = mutableMapOf() + + for (carName in carList) { + validateCarName(carName) + carMap.put(carName, 0) + } + + return carMap +} + +fun printAllRaceResults(carMap: MutableMap, attemptNumber: Int) { + println(Strings.MESSAGE_OUTPUT_RESULT) + + for (i in 0 until attemptNumber) { + printRaceResult(carMap) + println() + } +} + +fun printRaceResult(carMap: MutableMap) { + carMap.forEach { key, value -> + moveForwardIfValid(carMap, key, value) + + println("$key : " + "-".repeat(carMap[key]!!)) + } +} + +fun moveForwardIfValid(carMap: MutableMap, key: String, value: Int) { + if (isMoveForwardValid()) { + carMap[key] = value + 1 + } +} + +fun printWinners(carMap: MutableMap) = println( + Strings.MESSAGE_OUTPUT_WINNER + filterWinners(carMap).joinToString(", ") +) + +fun filterWinners(carMap: MutableMap) = carMap.filterValues { isWinnerValid(carMap, it) }.keys + +fun validateCarName(carName: String) { + require(isCarNameValidLength(carName)) { Strings.MESSAGE_EXCEPTION_CAR_NAME_LENGTH } +} \ No newline at end of file diff --git a/src/main/kotlin/racingcar/Strings.kt b/src/main/kotlin/racingcar/Strings.kt new file mode 100644 index 00000000..27609fba --- /dev/null +++ b/src/main/kotlin/racingcar/Strings.kt @@ -0,0 +1,11 @@ +package racingcar + +object Strings { + const val MESSAGE_INPUT_CAR_NAME = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)" + const val MESSAGE_INPUT_ATTEMPT_NUMBER = "시도할 횟수는 몇 회인가요?" + const val MESSAGE_OUTPUT_RESULT = "실행 결과" + const val MESSAGE_OUTPUT_WINNER = "최종 우승자 : " + const val MESSAGE_EXCEPTION_CAR_NAME_LENGTH = "자동차 이름은 5자 이하만 가능합니다." + const val MESSAGE_EXCEPTION_INPUT_CAR_NAMES = "자동차 이름은 값이 없거나 공백이 아니여야 합니다." + const val MESSAGE_EXCEPTION_INPUT_ATTEMPT_NUMBER = "입력 횟수는 정수값이여야 합니다." +} \ No newline at end of file diff --git a/src/test/kotlin/racingcar/ApplicationTest.kt b/src/test/kotlin/racingcar/ApplicationTest.kt index 3c601c8e..2f28e393 100644 --- a/src/test/kotlin/racingcar/ApplicationTest.kt +++ b/src/test/kotlin/racingcar/ApplicationTest.kt @@ -9,7 +9,7 @@ import org.junit.jupiter.api.assertThrows class ApplicationTest : NsTest() { @Test - fun `기능 테스트`() { + fun `통합 테스트`() { assertRandomNumberInRangeTest( { run("pobi,woni", "1") @@ -20,9 +20,73 @@ class ApplicationTest : NsTest() { } @Test - fun `예외 테스트`() { + fun `통합 테스트 3명 이상`() { + assertRandomNumberInRangeTest( + { + run("pobi,woni,jun", "5") + assertThat(output()).contains("pobi : -", "woni : ", "jun : ", "최종 우승자 : pobi") + }, + MOVING_FORWARD, STOP + ) + } + + @Test + fun `통합 테스트 2명 이상의 우승자`() { + assertRandomNumberInRangeTest( + { + run("pobi,woni,jun,a,b", "3") + assertThat(output()).contains( + "pobi : -", + "woni : ", + "jun : ", + "a : ", + "b : ", + "최종 우승자 : pobi, woni, jun, a, b" + ) + }, + MOVING_FORWARD + ) + } + + @Test + fun `통합 테스트 공백 제거`() { + assertRandomNumberInRangeTest( + { + run(" a , b , c", "3") + assertThat(output()).contains( + "a : -", + "b : -", + "c : -", + "최종 우승자 : a, b, c" + ) + }, + MOVING_FORWARD + ) + } + + @Test + fun `단위 테스트 공백 제거`() { + val input = " tesla , audi , bmw " + val result = processCarNames(input) + + assertThat(result).containsExactly("tesla", "audi", "bmw") + } + + @Test + fun `예외 테스트 자동차 이름 5자 초과`() { + assertSimpleTest { + val exception = assertThrows { runException("pobi,javaji", "1") } + + exception.printStackTrace() + } + } + + @Test + fun `예외 테스트 공백으로 이뤄진 자동차 이름`() { assertSimpleTest { - assertThrows { runException("pobi,javaji", "1") } + val exception = assertThrows { runException(" , ,", "1") } + + exception.printStackTrace() } }