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
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
# kotlin-racingcar-precourse
# 자동차 경주
## 기능 목록
1. 입력
- 자동차 이름 입력
- 시도할 횟수 입력

2. 경주 준비
- 이름 쉼표 단위 분리
- 이름 5자 이하 확인

3. 경주 시작
- 무작위 값 부여
- 정해진 횟수만큼 반복

4. 경주 결과
- 우승자 결정

5. 출력
- 중간 과정 출력
- 우승자 출력

### 스스로 판단한 사항
- 이름으로 공백 입력할 경우 예외 발생
- 시도 횟수 0 이하인 경우 예외 발생
- 중복된 이름 사용할 경우 예외 발생
7 changes: 5 additions & 2 deletions src/main/kotlin/racingcar/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package racingcar

import racingcar.controller.RacingController

fun main() {
// TODO: 프로그램 구현
}
val racingController = RacingController()
racingController.run()
}
27 changes: 27 additions & 0 deletions src/main/kotlin/racingcar/controller/RacingController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package racingcar.controller

import racingcar.model.Car
import racingcar.model.RacingGame
import racingcar.view.InputView
import racingcar.view.OutputView

class RacingController(
private val inputView: InputView = InputView(),
private val outputView: OutputView = OutputView()
Comment on lines +9 to +10
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InputView, OutputView는 프로젝트 내에서 여러 번 할당되지도 않고 상태도 갖고 있지 않기에 object 클래스로 선언하여 static 처럼 쓰시는 게 좋을 거 같습니다!

) {
fun run() {
val names = inputView.readCarNames()
val count = inputView.readRoundCount()

val cars = names.map { Car(it) }
val game = RacingGame(cars)

outputView.printGameStart()
repeat(count) {
game.playRound()
outputView.printRoundResult(cars)
}

outputView.printWinner(game.judgeResult())
}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/racingcar/model/Car.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package racingcar.model

import camp.nextstep.edu.missionutils.Randoms

class Car(val name: String) {
var position: Int = 0

fun move() {
if (Randoms.pickNumberInRange(0, 9) >= 4) position++
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/racingcar/model/RacingGame.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package racingcar.model

import racingcar.view.OutputView

class RacingGame(private val cars: List<Car>) {
fun playRound() {
cars.forEach { it.move() }
}

fun judgeResult(): List<String> {
val maxPosition = cars.maxOf { it.position }
val winners = cars.filter { it.position == maxPosition }.map { it.name }

return winners
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/racingcar/model/validator/CarValidator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package racingcar.model.validator

object CarValidator {
fun parse(input: String): List<String> {
val names = input.split(",").map { it.trim() }
validateCarName(names)

return names
}

private fun validateCarName(names: List<String>) {
require(names.all { it.isNotBlank() }) { throw IllegalArgumentException("자동차 이름은 비어 있을 수 없습니다.") }
require(names.all { it.length <= 5 }) { throw IllegalArgumentException("자동차 이름은 5자 이하만 가능합니다.") }
require(names.distinct().size == names.size) { throw IllegalArgumentException("자동차 이름은 중복될 수 없습니다.") }
}
}
Comment on lines +3 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CarValidator에 validate 말고도 parse도 있네요. parse를 따로 parser 클래스에 두시거나 Car 클래스에 두시는 것도 좋았을 것 같습니다.

11 changes: 11 additions & 0 deletions src/main/kotlin/racingcar/model/validator/RoundCountValidator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package racingcar.model.validator

object RoundCountValidator {
fun validate(countInput: String): Int {
val number = countInput.toIntOrNull()
?: throw IllegalArgumentException("시도 횟수는 숫자여야 합니다.")
require(number > 0) { "시도 횟수는 1 이상이어야 합니다." }

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

import camp.nextstep.edu.missionutils.Console
import racingcar.model.validator.CarValidator
import racingcar.model.validator.RoundCountValidator

class InputView {
fun readCarNames(): List<String> {
println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)")
val carsInput = Console.readLine()

return CarValidator.parse(carsInput)
}
Comment on lines +8 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 지적받은 사항인데, InputView에서는 parse와 같은 다른 처리를 두지 않고 입력만 처리하면 좋을 것 같습니다!


fun readRoundCount(): Int {
println("시도할 횟수는 몇 회인가요?")
val countInput = Console.readLine()

return RoundCountValidator.validate(countInput)
}
}
20 changes: 20 additions & 0 deletions src/main/kotlin/racingcar/view/OutputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package racingcar.view

import racingcar.model.Car

class OutputView {
fun printGameStart() {
println("실행 결과")
}

fun printRoundResult(cars: List<Car>) {
cars.forEach { car ->
println("${car.name} : ${"-".repeat(car.position)}")
}
println("")
}

fun printWinner(names: List<String>) {
println("최종 우승자 : ${names.joinToString(", ")}")
}
}
70 changes: 69 additions & 1 deletion src/test/kotlin/racingcar/ApplicationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,80 @@ class ApplicationTest : NsTest() {
}

@Test
fun `예외 테스트`() {
fun `이름 5자 초과 예외 테스트`() {
assertSimpleTest {
assertThrows<IllegalArgumentException> { runException("pobi,javaji", "1") }
}
}

@Test
fun `이름 입력 시 공백 테스트`() {
assertSimpleTest {
assertThrows<IllegalArgumentException> {
runException("pobi, ,jun", "1")
}
}
}

@Test
fun `라운드 횟수에 숫자가 아닌 값 입력 테스트`() {
assertSimpleTest {
assertThrows<IllegalArgumentException> {
runException("pobi,woni", "two")
}
}
}

@Test
fun `라운드 반복 테스트`() {
assertRandomNumberInRangeTest(
{
run("pobi,woni", "2")
assertThat(output())
.contains(
"실행 결과",
"pobi : -",
"woni : ",
"pobi : --",
"woni : -"
)
},
MOVING_FORWARD, STOP, MOVING_FORWARD, MOVING_FORWARD
)
}

@Test
fun `라운드 횟수 0 입력 테스트`() {
assertSimpleTest {
assertThrows<IllegalArgumentException> {
runException("pobi,woni", "0")
}
}
}

@Test
fun `공동 우승자 테스트`() {
assertRandomNumberInRangeTest(
{
run("pobi,woni", "1")
assertThat(output()).contains("최종 우승자 : pobi, woni")
},
MOVING_FORWARD, MOVING_FORWARD
)
}

@Test
fun `아무도 전진 못한 경우 테스트`() {
assertRandomNumberInRangeTest(
{
run("pobi,woni", "1")
assertThat(output()).contains("pobi : ", "woni : ", "최종 우승자 : pobi, woni")
},
STOP, STOP
)
}


override fun runMain() {
main()
}
Expand Down