Skip to content
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
# kotlin-racingcar-precourse

## 🎯 기능 목록

- [x] 경주할 자동차 이름을 입력받는다.
- [x] 이름은 쉼표(,)를 기준으로 구분한다.
- [x] 이름은 5자 이하만 가능하다.
- [x] 잘못된 이름 입력 시 `IllegalArgumentException`을 발생시킨다.
- [x] 시도할 횟수를 입력받는다.
- [x] 입력값은 숫자여야 한다.
- [x] 잘못된 횟수 입력 시 `IllegalArgumentException`을 발생시킨다.
- [x] 입력된 횟수만큼 경주를 진행한다.
- [x] 각 자동차는 0에서 9 사이의 무작위 값을 기준으로 4 이상일 경우 전진(1칸)한다.
- [x] 각 차수의 실행 결과를 출력한다. (예: `pobi : --`)
- [x] 경주가 끝난 후 최종 우승자를 결정한다.
- [x] 가장 많이 전진한 자동차가 우승한다.
- [x] 공동 우승자가 있을 수 있다.
- [x] 최종 우승자를 출력한다.
- [x] 공동 우승 시 쉼표(,)로 구분하여 출력한다. (예: `최종 우승자 : pobi, jun`)
94 changes: 93 additions & 1 deletion src/main/kotlin/racingcar/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,97 @@
package racingcar

import camp.nextstep.edu.missionutils.Console
import camp.nextstep.edu.missionutils.Randoms

fun main() {
// TODO: 프로그램 구현
RacingGame().start()
}

// 자동차의 상태(이름, 위치)를 관리하는 데이터 클래스
data class Car(val name: String, var position: Int = 0)

class RacingGame {
fun start() {
val carNames = getCarNames()
val tryCount = getTryCount()
val cars = carNames.map { Car(it) } // 이름 목록을 Car 객체 리스트로 변환

runRace(cars, tryCount)
printWinners(cars) // 우승자 선정 및 출력 로직 호출
}

private fun getCarNames(): List<String> {
println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)")
val input = Console.readLine() ?: ""
val names = input.split(",").map { it.trim() }

validateCarNames(names)
return names
}

private fun validateCarNames(names: List<String>) {
if (names.any { it.isBlank() }) {
throw IllegalArgumentException("자동차 이름은 공백이거나 비어있을 수 없습니다.")
}
if (names.any { it.length > 5 }) {
throw IllegalArgumentException("자동차 이름은 5자 이하만 가능합니다.")
}
}

private fun getTryCount(): Int {
println("시도할 횟수는 몇 회인가요?")
val input = Console.readLine() ?: ""
val count = input.toIntOrNull()
?: throw IllegalArgumentException("시도 횟수는 숫자여야 합니다.")

if (count <= 0) {
throw IllegalArgumentException("시도 횟수는 1 이상이어야 합니다.")
}
return count
}

// 경주 실행의 메인 루프
private fun runRace(cars: List<Car>, tryCount: Int) {
println("\n실행 결과")
repeat(tryCount) {
playRound(cars)
println() // 각 라운드 실행 후 한 줄 띄움
}
}

// 단일 라운드 실행 (모든 차 이동 시도 및 결과 출력)
private fun playRound(cars: List<Car>) {
cars.forEach { car ->
tryMove(car)
}
printRoundResult(cars)
}

// 개별 자동차의 이동 시도
private fun tryMove(car: Car) {
val randomValue = Randoms.pickNumberInRange(0, 9)
if (randomValue >= 4) { // 4 이상일 경우 전진
car.position++
}
}

// 현재 라운드의 결과 출력
private fun printRoundResult(cars: List<Car>) {
cars.forEach { car ->
println("${car.name} : ${"-".repeat(car.position)}")
}
}

// 최종 우승자 선정 및 출력
private fun printWinners(cars: List<Car>) {
// 가장 많이 전진한 위치(maxPosition)를 찾는다.
val maxPosition = cars.maxOfOrNull { it.position } ?: 0

// maxPosition과 동일한 위치를 가진 자동차들(우승자)의 이름만 추출한다.
val winners = cars.filter { it.position == maxPosition }
.map { it.name }

// 우승자 목록을 쉼표(,)로 연결하여 출력한다.
println("최종 우승자 : ${winners.joinToString(", ")}")
}
}
28 changes: 24 additions & 4 deletions src/test/kotlin/racingcar/ApplicationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import camp.nextstep.edu.missionutils.test.NsTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource

class ApplicationTest : NsTest() {
@Test
Expand All @@ -19,10 +21,28 @@ class ApplicationTest : NsTest() {
)
}

@Test
fun `예외 테스트`() {
@ParameterizedTest
@ValueSource(strings = [
"pobi,javaji", // 5자 초과
"pobi,,woni", // 이름이 비어있음
"pobi, ,woni", // 이름이 공백
"" // 전체 입력이 비어있음
])
fun `잘못된 자동차 이름 입력 시 예외 테스트`(inputNames: String) {
assertSimpleTest {
assertThrows<IllegalArgumentException> {
runException(inputNames, "1") // 두 번째 입력 "1"은 도달하기 전에 예외 발생
}
}
}

@ParameterizedTest
@ValueSource(strings = ["a", " ", "", "0", "-1"])
fun `잘못된 시도 횟수 입력 시 예외 테스트`(tryCount: String) {
assertSimpleTest {
assertThrows<IllegalArgumentException> { runException("pobi,javaji", "1") }
assertThrows<IllegalArgumentException> {
runException("pobi,woni", tryCount) // 유효한 이름, 잘못된 횟수
}
}
}

Expand All @@ -34,4 +54,4 @@ class ApplicationTest : NsTest() {
private const val MOVING_FORWARD: Int = 4
private const val STOP: Int = 3
}
}
}