diff --git a/docs/README.md b/docs/README.md index e69de29bb..c0ee1256b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,64 @@ +# 구현할 기능 목록 + +### 게임 시작 + +- 자동차 이름 입력 +- 시도 횟수 입력 +- 경주 중 현황 출력 +- 우승자들 결정 +- 우승자들 출력 + +### 자동차 이름 입력 + +- 경주 자동차 이름 입력 메시지 출력(`경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)`) +- 쉼표(,)를 기준으로 구분 +- 자동차 이름으로 객체 생성 후 `List`에 담아서 반환 +- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료 + +### 시도 횟수 입력 + +- 시도 횟수 입력 메시지 출력(`시도할 횟수는 몇 회인가요?`) +- 입력받은 횟수 반환 +- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료 + +### 경주 중 현황 + +- 경기 결과 메시지 출력(`실행 결과`) +- 횟수별 경기 진행 +- 횟수별 경주 중 현황 출력 + +### 경기 진행 + +- 매개 변수로 받은 자동차 목록을 순회 +- `pickNumberInRange()`를 활용하여 0에서 9 사이에서 무작위 값을 구하기 + +### 자동차 전진 또는 멈춤 + +- 매개 변수로 받은 랜덤 숫자가 4 이상일 경우는 전진, 아니면 멈춤 + +### 경주 중 현황 출력 + +- 매개 변수로 받은 자동차 목록을 순회하며 자동차 전진의 결과를 출력, 전진 거리는 `-`으로 표시(ex. `pobi : ---`) + +### 우승자들 결정 + +- 매개 변수로 받은 자동차 목록 중 자동차 주행 거리의 최대값을 구하기 +- 자동차 목록에서 최대값과 주행거리가 같은 자동차 이름을 `List`에 담아서 반환 + +### 우승자들 출력 + +- 우승자 메시지 출력(`최종 우승자 :`) +- 매개 변수로 받은 우승자들 목록 출력 +- 우승자는 한 명 이상일 수 있음 + +--- + +# 객체 + +### 자동차 + +- `name` : String 타입으로 자동차 이름 + - 이름은 1자 이상, 5자 이하만 가능 +- `distance` : 자동차가 간 거리를 `-`로 표현한 String +- `distanceLength` : `distance`의 길이 +- `drive()` : `distance`에 `-` 추가 \ No newline at end of file diff --git a/src/main/kotlin/racingcar/Application.kt b/src/main/kotlin/racingcar/Application.kt index 0d8f3a79d..53a9c2faa 100644 --- a/src/main/kotlin/racingcar/Application.kt +++ b/src/main/kotlin/racingcar/Application.kt @@ -1,5 +1,68 @@ package racingcar +import camp.nextstep.edu.missionutils.Console +import camp.nextstep.edu.missionutils.Randoms + fun main() { - // TODO: 프로그램 구현 + startGame(Console::readLine, Randoms::pickNumberInRange) +} + +fun startGame(readLine: () -> String, pickNumberInRange: (Int, Int) -> Int) { + val carList = inputCarName(readLine) + val tryNumber = inputTryNumber(readLine) + + printRacing(carList, tryNumber, pickNumberInRange) + + val winnerNameList = decideWinnerNameList(carList) + printWinner(winnerNameList) } + +fun inputCarName(readLine: () -> String): List { + println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)") + return readLine().split(",").map { Car(it.trim()) } +} + +fun inputTryNumber(readLine: () -> String): Int { + println("시도할 횟수는 몇 회인가요?") + return readLine().apply { + require(this.isNotEmpty() && Regex("[0-9]+").matches(this)) { "시도할 횟수는 숫자만 입력해야만 합니다." } + }.toInt() +} + +fun printRacing(carList: List, tryNumber: Int, pickNumberInRange: (Int, Int) -> Int) { + println("\n실행 결과") + + for (round in 0 until tryNumber) { + raceCar(carList, pickNumberInRange) + printCurrentRace(carList) + } +} + +fun raceCar(carList: List, pickNumberInRange: (Int, Int) -> Int) { + for (car in carList) { + val randomNumber = pickNumberInRange(0, 9) + driveCar(car, randomNumber) + } +} + +fun driveCar(car: Car, randomNumber: Int) { + if (randomNumber >= 4) { + car.drive() + } +} + +fun printCurrentRace(carList: List) { + for (car in carList) { + println("${car.name} : ${car.distance}") + } + println() +} + +fun decideWinnerNameList(carList: List): List { + val max = carList.maxOf { it.distanceLength } + return carList.filter { it.distanceLength == max }.map { it.name } +} + +fun printWinner(winnerList: List) { + print("최종 우승자 : ${winnerList.joinToString(", ")}") +} \ No newline at end of file diff --git a/src/main/kotlin/racingcar/Car.kt b/src/main/kotlin/racingcar/Car.kt new file mode 100644 index 000000000..b55fcce3b --- /dev/null +++ b/src/main/kotlin/racingcar/Car.kt @@ -0,0 +1,34 @@ +package racingcar + +class Car(name: String) { + val name: String + private var _distance: String + val distance: String + get() = this._distance + val distanceLength: Int + get() = distance.length + + init { + require(name.length in 1 until 6) { "이름은 1자 이상, 5자 이하만 가능합니다." } + this.name = name + this._distance = "" + } + + fun drive() { + this._distance = _distance.plus('-') + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Car) return false + if (name != other.name) return false + if (distance != other.distance) return false + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + distance.hashCode() + return result + } +} \ No newline at end of file diff --git a/src/test/kotlin/racingcar/CarTest.kt b/src/test/kotlin/racingcar/CarTest.kt new file mode 100644 index 000000000..bef6dbf16 --- /dev/null +++ b/src/test/kotlin/racingcar/CarTest.kt @@ -0,0 +1,20 @@ +package racingcar + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class CarTest { + @Test + fun `name 1자 미만 5자 초과 예외 처리`() { + assertThrows { Car("123456") } + } + + @Test + fun `drive 실행 시 distance 1증가`() { + val car = Car("test") + val initDistanceLength = car.distanceLength + car.drive() + assertThat(car.distanceLength).isEqualTo(initDistanceLength + 1) + } +} \ No newline at end of file diff --git a/src/test/kotlin/racingcar/RacingGameTest.kt b/src/test/kotlin/racingcar/RacingGameTest.kt new file mode 100644 index 000000000..c05b265fe --- /dev/null +++ b/src/test/kotlin/racingcar/RacingGameTest.kt @@ -0,0 +1,147 @@ +package racingcar + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.io.ByteArrayOutputStream +import java.io.OutputStream +import java.io.PrintStream + +class RacingGameTest { + private lateinit var standardOut: PrintStream + private lateinit var captor: OutputStream + + @BeforeEach + fun init() { + standardOut = System.out + captor = ByteArrayOutputStream() + System.setOut(PrintStream(captor)) + } + + @AfterEach + fun printOutput() { + System.setOut(standardOut) + println(output()) + } + + fun output(): String { + return captor.toString().trim { it <= ' ' } + } + + @Test + fun `inputCarName 정상 입력`() { + val input = "a,b,c" + val carList = input.split(",").map { Car(it.trim()) } + assertThat(inputCarName { input }).isEqualTo(carList) + } + + @Test + fun `inputCarName 빈칸 입력 예외 발생`() { + val input = "" + assertThrows { inputCarName { input } } + } + + @Test + fun `inputCarName 5자 이상 입력 예외 발생`() { + val input = "hello,abc,wrongName" + assertThrows { inputCarName { input } } + } + + @Test + fun `inputTryNumber 정상 입력`() { + val input = "5" + assertThat(inputTryNumber { input }).isEqualTo(5) + } + + @Test + fun `inputTryNumber 빈칸 입력 예외 발생`() { + val input = "" + assertThrows { inputTryNumber { input } } + } + + @Test + fun `inputTryNumber 문자 입력 예외 발생`() { + val input = "a" + assertThrows { inputTryNumber { input } } + } + + @Test + fun `printRacing 결과 출력`() { + val carList = listOf(Car("a"), Car("b"), Car("c")) + val tryNumber = 1 + val pickNumberInRange = { _: Int, _: Int -> 4 } + printRacing(carList, tryNumber, pickNumberInRange) + for (car in carList) { + val expectedOutput = "${car.name} : ${"-".repeat(tryNumber)}" + assertThat(output()).contains(expectedOutput) + } + } + + @Test + fun `raceCar 정상 처리`() { + val carList = listOf(Car("a"), Car("b"), Car("c")) + val pickNumberInRange = { _: Int, _: Int -> 4 } + raceCar(carList, pickNumberInRange) + for (car in carList) { + assertThat(car.distanceLength).isEqualTo(1) + } + } + + @Test + fun `driveCar 랜덤 숫자 4이상 정상 처리`() { + val car = Car("a") + val randomNumber = 4 + driveCar(car, randomNumber) + assertThat(car.distanceLength).isEqualTo(1) + } + + @Test + fun `driveCar 랜덤 숫자 4미만 정상 처리`() { + val car = Car("a") + val randomNumber = 2 + driveCar(car, randomNumber) + assertThat(car.distanceLength).isEqualTo(0) + } + + @Test + fun `printCurrentRace 결과 출력`() { + val carList = listOf(Car("a"), Car("b"), Car("c")) + printCurrentRace(carList) + assertThat(output()).contains("a : ", "b : ", "c :") + } + + @Test + fun `decideWinnerNameList 한 명 정상 처리`() { + val carList = listOf(Car("a"), Car("b"), Car("c")) + carList[0].drive() + val expectList = listOf("a") + assertThat(decideWinnerNameList(carList)).isEqualTo(expectList) + } + + @Test + fun `decideWinnerNameList 여러 명 정상 처리`() { + val carList = listOf(Car("a"), Car("b"), Car("c")) + carList[0].drive() + carList[1].drive() + val expectList = listOf("a", "b") + assertThat(decideWinnerNameList(carList)).isEqualTo(expectList) + } + + @Test + fun `printWinner 한 명 결과 출력`() { + val winnerList = listOf("a") + printWinner(winnerList) + val expectedOutput = "최종 우승자 : a" + assertThat(output()).contains(expectedOutput) + } + + @Test + fun `printWinner 여러 명 결과 출력`() { + val winnerList = listOf("a", "b") + printWinner(winnerList) + val expectedOutput = "최종 우승자 : a, b" + assertThat(output()).contains(expectedOutput) + } +} \ No newline at end of file