diff --git a/README.md b/README.md index 64924a267d..f339a944f8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,19 @@ # kotlin-racingcar - [o] 자동차 생성 - - [o] 자동차는 4~9 범위 사이일때 움직일 수 있다. - - [o] 자동차는 0~3 범위 사이에는 움직일 수 없다. + - [o] 자동차에 이름을 부여할 수 있다. + - [o] 자동차의 이름은 5글자를 초과할 수 없다. + - [o] 자동차의 이름이 5글자를 초과하면 예외를 발생시킨다. +- [o] 자동차의 움직임 + - [o] 자동차는 4~9 범위 사이일때 움직일 수 있다. + - [o] 자동차는 0~3 범위 사이에는 움직일 수 없다. - [o] 무작위 값은 0~9 범위 사이이다. -- [o] 클라이언트로부터 값을 입력받을 수 있다. -- [o] 실행결과를 출력한다. \ No newline at end of file +- [o] 클라이언트로부터 값을 입력받을 수 있다. + - [o] 자동차의 이름은 쉼표(,)로 구분할 수 있다. +- [o] 실행결과를 출력한다. + - [o] 매 횟수마다 출력한다. + - [o] 매 횟수마다 전진하는 자동차의 이름도 출력한다. + - [o] 최종 우승자를 출력하며, 우승자는 한명 이상이다. +- [o] 의존관계 + - [o] Domain -> View X + - [o] View -> Domain O \ No newline at end of file diff --git a/src/main/kotlin/racing/application/RacingGameService.kt b/src/main/kotlin/racing/application/RacingGameService.kt index a303b10de7..bc3cb40acd 100644 --- a/src/main/kotlin/racing/application/RacingGameService.kt +++ b/src/main/kotlin/racing/application/RacingGameService.kt @@ -1,11 +1,18 @@ package racing.application import racing.domain.RacingGame +import racing.view.InputView +import racing.view.ResultView class RacingGameService { fun main() { + val racingCarNames = InputView.requireRacingCarNames() + val numberOfGames = InputView.requireNumberOfGames() + val racingGame = RacingGame() - racingGame.start() + val winCars = racingGame.start(racingCarNames, numberOfGames) + ResultView.printRacingGameGuideText() + ResultView.printRacingGameWinner(winCars) } } diff --git a/src/main/kotlin/racing/domain/Car.kt b/src/main/kotlin/racing/domain/Car.kt index e187c38527..2baea214e3 100644 --- a/src/main/kotlin/racing/domain/Car.kt +++ b/src/main/kotlin/racing/domain/Car.kt @@ -1,10 +1,16 @@ package racing.domain -class Car(position: Int = 0) { +class Car(val name: String, position: Int = 0) { var position = position private set + init { + require(name.length <= CAR_NAME_LIMIT) { + "자동차의 이름은 ${CAR_NAME_LIMIT}글자를 초과할 수 없습니다." + } + } + fun move(num: Int) { if (num >= FORWARD_MOVE) { position++ @@ -13,5 +19,7 @@ class Car(position: Int = 0) { companion object { const val FORWARD_MOVE: Int = 4 + const val CAR_NAME_LIMIT: Int = 5 + fun produce(name: String, position: Int = 0) = Car(name, position) } } diff --git a/src/main/kotlin/racing/domain/CarFactory.kt b/src/main/kotlin/racing/domain/CarFactory.kt index 6c8dab7711..02b4296073 100644 --- a/src/main/kotlin/racing/domain/CarFactory.kt +++ b/src/main/kotlin/racing/domain/CarFactory.kt @@ -3,8 +3,8 @@ package racing.domain class CarFactory { companion object { - fun create(count: Int): Cars { - return Cars(count) + fun create(names: Array): Cars { + return Cars(names) } } } diff --git a/src/main/kotlin/racing/domain/Cars.kt b/src/main/kotlin/racing/domain/Cars.kt index 7a7ce34e94..d442f1f449 100644 --- a/src/main/kotlin/racing/domain/Cars.kt +++ b/src/main/kotlin/racing/domain/Cars.kt @@ -1,22 +1,20 @@ package racing.domain -class Cars(count: Int) : Iterable { - - private val list: List - - init { - list = List(count) { - Car() - } - } +class Cars(private val list: List) : Iterable { + constructor(names: Array) : this(names.map { Car.produce(it) }) fun count() = list.size fun race(movable: () -> Int) { list.forEach { car -> - car.move(movable.invoke()) + car.move(movable()) } } + + private fun isWinnerPosition() = list.maxOf { it.position } + + fun isWinner() = list.filter { it.position == isWinnerPosition() } + override fun iterator(): Iterator = list.iterator() } diff --git a/src/main/kotlin/racing/domain/RacingGame.kt b/src/main/kotlin/racing/domain/RacingGame.kt index 22a73c2d95..174f372bef 100644 --- a/src/main/kotlin/racing/domain/RacingGame.kt +++ b/src/main/kotlin/racing/domain/RacingGame.kt @@ -1,22 +1,19 @@ package racing.domain -import racing.ui.InputView -import racing.ui.ResultView +import racing.view.ResultView class RacingGame { - fun start() { + fun start(carNames: Array, numberOfGames: Int): List { - val carCount = InputView.requireCarsCount() - val cars = CarFactory.create(carCount) - var numberOfGames = InputView.requireNumberOfGames() + val cars = CarFactory.create(carNames) - while (numberOfGames-- > 0) { - cars.race { - RandomGenerator.generate() - } + for (i in 1..numberOfGames) { + cars.race { RandomGenerator.generate() } + ResultView.printRacingGameResult(ResultView.toRaceResult(cars)) } - ResultView.printGameResult(ResultStatistics(cars).toResult()) + val winner = Winner(cars) + return winner.win() } } diff --git a/src/main/kotlin/racing/domain/RandomGenerator.kt b/src/main/kotlin/racing/domain/RandomGenerator.kt index 9a9946e7bd..28f70d7ecd 100644 --- a/src/main/kotlin/racing/domain/RandomGenerator.kt +++ b/src/main/kotlin/racing/domain/RandomGenerator.kt @@ -3,6 +3,6 @@ package racing.domain class RandomGenerator { companion object { - fun generate(): Int = (0..9).random() + fun generate() = (0..9).random() } } diff --git a/src/main/kotlin/racing/domain/ResultStatistics.kt b/src/main/kotlin/racing/domain/ResultStatistics.kt deleted file mode 100644 index d219320c28..0000000000 --- a/src/main/kotlin/racing/domain/ResultStatistics.kt +++ /dev/null @@ -1,16 +0,0 @@ -package racing.domain - -class ResultStatistics(private val cars: Cars) { - - fun toResult(): String { - return cars.map { toMark(it) }.joinToString(separator = System.lineSeparator()) - } - - private fun toMark(car: Car): String { - return MARK.repeat(car.position) - } - - companion object { - const val MARK: String = "-" - } -} diff --git a/src/main/kotlin/racing/domain/Winner.kt b/src/main/kotlin/racing/domain/Winner.kt new file mode 100644 index 0000000000..58b0a8f4f4 --- /dev/null +++ b/src/main/kotlin/racing/domain/Winner.kt @@ -0,0 +1,6 @@ +package racing.domain + +class Winner(private val cars: Cars) { + + fun win(): List = cars.isWinner() +} diff --git a/src/main/kotlin/racing/ui/InputView.kt b/src/main/kotlin/racing/ui/InputView.kt deleted file mode 100644 index ae2f84ef99..0000000000 --- a/src/main/kotlin/racing/ui/InputView.kt +++ /dev/null @@ -1,19 +0,0 @@ -package racing.ui - -class InputView { - - companion object { - - fun requireCarsCount(): Int { - println("자동차 대수는 몇 대인가요?") - return inputNumber() - } - - fun requireNumberOfGames(): Int { - println("시도할 횟수는 몇 회인가요?") - return inputNumber() - } - - private fun inputNumber() = readln().toInt() - } -} diff --git a/src/main/kotlin/racing/ui/ResultView.kt b/src/main/kotlin/racing/ui/ResultView.kt deleted file mode 100644 index f323d39590..0000000000 --- a/src/main/kotlin/racing/ui/ResultView.kt +++ /dev/null @@ -1,11 +0,0 @@ -package racing.ui - -class ResultView { - - companion object { - fun printGameResult(result: String) { - println("실행결과") - println(result) - } - } -} diff --git a/src/main/kotlin/racing/view/InputView.kt b/src/main/kotlin/racing/view/InputView.kt new file mode 100644 index 0000000000..a9403d971b --- /dev/null +++ b/src/main/kotlin/racing/view/InputView.kt @@ -0,0 +1,23 @@ +package racing.view + +class InputView { + + companion object { + private const val SEPARATOR = "," + + fun requireRacingCarNames(): Array { + println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분") + val readString = readString() + return readString.split(SEPARATOR).toTypedArray() + } + + fun requireNumberOfGames(): Int { + println("시도할 횟수는 몇 회인가요?") + return readNum() + } + + private fun readNum() = readln().toInt() + + private fun readString() = readln() + } +} diff --git a/src/main/kotlin/racing/view/ResultView.kt b/src/main/kotlin/racing/view/ResultView.kt new file mode 100644 index 0000000000..b5e5613408 --- /dev/null +++ b/src/main/kotlin/racing/view/ResultView.kt @@ -0,0 +1,32 @@ +package racing.view + +import racing.domain.Car +import racing.domain.Cars + +class ResultView { + + companion object { + fun toRaceResult(cars: Cars): String { + return cars.map { "${it.name} : ${toMark(it)}" }.joinToString(separator = System.lineSeparator()).trimIndent() + } + + private fun toMark(car: Car): String { + return MARK.repeat(car.position) + } + + fun printRacingGameGuideText() { + println("실행결과") + } + + fun printRacingGameResult(result: String) { + println(result) + } + + fun printRacingGameWinner(cars: List) { + val winner = cars.joinToString { it.name } + println("${winner}가 최종 우승했습니다.") + } + + private const val MARK = "-" + } +} diff --git a/src/test/kotlin/racing/CarTest.kt b/src/test/kotlin/racing/CarTest.kt index 353b35322f..ff7e5339fc 100644 --- a/src/test/kotlin/racing/CarTest.kt +++ b/src/test/kotlin/racing/CarTest.kt @@ -1,7 +1,9 @@ package racing -import org.assertj.core.api.AssertionsForInterfaceTypes +import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat +import org.assertj.core.util.Arrays 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 import racing.domain.CarFactory @@ -10,28 +12,39 @@ import racing.domain.Cars class CarTest { @Test - fun `자동차 생성`() { - val cars: Cars = CarFactory.create(3) - AssertionsForInterfaceTypes.assertThat(cars.count()).isEqualTo(3) + fun `자동차를 생성할 때 자동차의 이름은 5글자 이하이다`() { + val names = Arrays.array("lee", "kkoks", "amery") + val cars: Cars = CarFactory.create(names) + assertThat(cars.count()).isEqualTo(3) + } + + @Test + fun `자동차를 생성할 때 자동차의 이름은 5글자를 초과한다`() { + val names = Arrays.array("lee", "kkokss", "amery") + assertThrows { + CarFactory.create(names) + } } @ParameterizedTest @ValueSource(ints = [4, 9]) fun `자동차 이동`(num: Int) { - val cars: Cars = CarFactory.create(3) + val names = Arrays.array("lee", "kkoks", "amery") + val cars: Cars = CarFactory.create(names) for (car in cars) { car.move(num) - AssertionsForInterfaceTypes.assertThat(car.position).isEqualTo(1) + assertThat(car.position).isEqualTo(1) } } @ParameterizedTest @ValueSource(ints = [0, 3]) fun `자동차 이동실패`(num: Int) { - val cars: Cars = CarFactory.create(3) + val names = Arrays.array("lee", "kkoks", "amery") + val cars: Cars = CarFactory.create(names) for (car in cars) { car.move(num) - AssertionsForInterfaceTypes.assertThat(car.position).isEqualTo(0) + assertThat(car.position).isEqualTo(0) } } } diff --git a/src/test/kotlin/racing/RacingGameTest.kt b/src/test/kotlin/racing/RacingGameTest.kt index 09d2fcba1a..3ee700ae1e 100644 --- a/src/test/kotlin/racing/RacingGameTest.kt +++ b/src/test/kotlin/racing/RacingGameTest.kt @@ -1,19 +1,55 @@ package racing import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat +import org.assertj.core.util.Arrays import org.junit.jupiter.api.Test +import racing.domain.Car import racing.domain.CarFactory import racing.domain.Cars -import racing.domain.ResultStatistics +import racing.domain.Winner +import racing.view.ResultView class RacingGameTest { @Test fun `실행결과 출력`() { - val cars: Cars = CarFactory.create(2) + val names = Arrays.array("lee", "kkoks") + val cars: Cars = CarFactory.create(names) for (car in cars) { car.move(4) } - assertThat(ResultStatistics(cars).toResult()).isEqualTo("-${System.lineSeparator()}-") + + val result = """ + lee : - + kkoks : - + """.trimIndent() + assertThat(ResultView.toRaceResult(cars)).isEqualTo(result) + } + + @Test + fun `Racing Game 단독 우승자 출력(차가 한대일경우)`() { + val kkoksCar = Car.produce("kkoks", 3) + val winner = Winner(Cars(listOf(kkoksCar))) + val result = winner.win() + assertThat(result).contains(kkoksCar) + } + + @Test + fun `Racing Game 단독 우승자 출력`() { + val leeCar = Car.produce("lee", 5) + val kkoksCar = Car.produce("kkoks", 3) + val winner = Winner(Cars(listOf(leeCar, kkoksCar))) + val result = winner.win() + assertThat(result).contains(leeCar) + } + + @Test + fun `Racing Game 공동 우승자 출력`() { + val leeCar = Car.produce("lee", 5) + val kkoksCar = Car.produce("kkoks", 5) + val tesyCar = Car.produce("tesy", 3) + val winner = Winner(Cars(listOf(leeCar, kkoksCar, tesyCar))) + val result = winner.win() + assertThat(result).contains(leeCar, kkoksCar) } }