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
76 changes: 75 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,75 @@
# kotlin-racingcar-precourse
# [2주차] 자동차 경주 🚗
초간단 자동차 경주 게임을 구현한다.

## 기능 목록 📝
#### 1. 애플리케이션을 실행하면 안내 문구를 출력하고, 자동차 이름과 시도할 횟수를 입력받는다.
```
경주할 자동차 이름을 입력하세요.(이름은 쉼표(,)기준으로 구분)
pobi,woni,jun
시도할 횟수는 몇 회인가요?
5
```
#### 2. 쉼표(,)를 기준으로, 자동차 이름을 분리한다.
#### 3. 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우 전진하고 4 미만일 경우 멈춘다.
#### 4. 실행 결과 첫 출력시 '실행 결과' 문구를 출력하고, 한 횟수가 끝날 때마다 결과를 출력한다.
```
실행 결과
pobi : -
woni :
jun : -

```
#### 5. 게임을 완료한 후 누가 우승했는지 출력하고, 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
```
최종 우승자 : pobi, jun
```
#### 6. 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException 을 발생시킨 후 애플리케이션은 종료된다.
- 자동차 이름이 5자를 초과인 경우
- 쉼표로 구분한 각 이름이 비어있거나 공백일 경우 (예: pobi,,jun)
- 시도할 횟수가 1 미만의 숫자인 경우 (또는 숫자가 아닌 경우)

#### 7. 기능 목록이 정상 작동하는지 테스트 코드로 확인한다.


## 설계 🧩
#### main
```
racingcar
├── controller
│ └── RaceController.kt
├── domain
│ ├── Car.kt
│ └── Race.kt
├── port
│ ├── InputPort.kt
│ └── OutputPort.kt
├── util
│ ├── InputParser.kt
│ ├── InputValidator.kt
│ └── RandomUtils.kt
├── view
│ ├── InputView.kt
│ └── OutputView.kt
└── Application.kt (main 함수)
```
#### test
```
racingcar
├── controller
│ └── RaceControllerTest.kt
├── domain
│ ├── CarTest.kt
│ └── RaceTest.kt
└── util
│ ├── InputParserTest.kt
│ └── InputValidatorTest.kt
└── ApplicationTest.kt
```
15 changes: 14 additions & 1 deletion src/main/kotlin/racingcar/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
package racingcar

import racingcar.controller.RaceController
import racingcar.util.InputParser
import racingcar.util.InputValidator
import racingcar.view.InputView
import racingcar.view.OutputView

fun main() {
// TODO: 프로그램 구현
val inputPort = InputView
val outputPort = OutputView
val inputParser = InputParser
val inputValidator = InputValidator

val raceController = RaceController(inputPort, outputPort, inputParser, inputValidator)

raceController.run()
}
41 changes: 41 additions & 0 deletions src/main/kotlin/racingcar/controller/RaceController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package racingcar.controller

import racingcar.domain.Car
import racingcar.domain.Race
import racingcar.port.InputPort
import racingcar.port.OutputPort
import racingcar.util.InputParser
import racingcar.util.InputValidator

class RaceController(
private val inputPort: InputPort,
private val outputPort: OutputPort,
private val inputParser: InputParser,
private val inputValidator: InputValidator
) {
fun run() {
val readCarNames: String? = inputPort.readCarNames()
val readTryCount: String? = inputPort.readTryCount()

val carNames = inputParser.parseCarNames(readCarNames ?: "")
inputValidator.validateCarNames(carNames)
val cars: List<Car> = carNames.map { Car(it) }
val tryCount = inputValidator.validateTryCount(readTryCount)

val race = Race(cars)

outputPort.printFirstResult()
repeat(tryCount) {
race.runOneRound()

cars.forEach { car ->
outputPort.printRoundResult(car)
}

outputPort.printBlankLine()
}
Comment on lines +28 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

controller에서 출력의 책임을 가지고 있는 것처럼 느껴져요!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

controller에서 출력의 책임을 가지고 있는 것처럼 느껴져요!

출력의 책임을 좀 더 분리하는 게 좋겠네요! 감사합니다 !


val winners = race.findWinners()
outputPort.printFinalWinner(winners)
}
}
12 changes: 12 additions & 0 deletions src/main/kotlin/racingcar/domain/Car.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package racingcar.domain

data class Car(val name: String) {
var position = 0
internal set

fun move(randomInt: Int) {
if (randomInt >= 4) {
position++
}
}
}
28 changes: 28 additions & 0 deletions src/main/kotlin/racingcar/domain/Race.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package racingcar.domain

import racingcar.util.RandomUtils

class Race(val cars: List<Car>) {
fun runOneRound() {
for (car in cars) {
car.move(RandomUtils.getRandomInt())
}
}

private fun findFinalMaxPosition(cars: List<Car>): Int {
return cars.maxOf { it.position }
}

fun findWinners(): List<Car> {
val maxPosition = findFinalMaxPosition(cars)
val winners: MutableList<Car> = mutableListOf()

for (car in cars) {
if (car.position == maxPosition) {
winners += car
}
}

return winners
}
Comment on lines +12 to +27
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maxOffilter를 사용해서 간결하게 작성할 수 있을 것 같습니다!

fun findWinners(): List<Car> {
    val maxPosition = cars.maxOf { it.position }
    return cars.filter { it.position == maxPosition }
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

maxOffilter를 사용해서 간결하게 작성할 수 있을 것 같습니다!

fun findWinners(): List<Car> {
    val maxPosition = cars.maxOf { it.position }
    return cars.filter { it.position == maxPosition }
}

filter 가 있었네요! 감사합니다 !!

}
6 changes: 6 additions & 0 deletions src/main/kotlin/racingcar/port/InputPort.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package racingcar.port

interface InputPort {
fun readCarNames(): String?
fun readTryCount(): String?
}
Comment on lines +3 to +6
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

null을 허용하도록 하신 이유가 궁금해요!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Console.readLine()에 빈 값을 입력해도 ""가 입력되서 굳이 nullable하게 처리하지 않아도 괜찮을 것 같습니다.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

null을 허용하도록 하신 이유가 궁금해요!

Console.readLine 에서 사용자가 입력을 하지 않고, null 을 반환하면 읽어오는 순간에 바로 오류가 날까봐 널을 허용했어요! 대신 받아오는 과정에서 널일 경우 공백으로 처리해서 오류를 내보는 방식으로 작성했습니다!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Console.readLine()에 빈 값을 입력해도 ""가 입력되서 굳이 nullable하게 처리하지 않아도 괜찮을 것 같습니다.

터미널에서 Ctrl+D 나 Ctrl+Z 를 입력해서 사용자가 직접 입력의 끝을 알리면 null 로 들어온다고 하더라고요! 혹시나 해서 넣은 부분입니다!

10 changes: 10 additions & 0 deletions src/main/kotlin/racingcar/port/OutputPort.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package racingcar.port

import racingcar.domain.Car

interface OutputPort {
fun printFirstResult()
fun printRoundResult(car: Car)
fun printBlankLine()
fun printFinalWinner(winners: List<Car>)
}
Comment on lines +5 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.

인터페이스를 만드신 이유가 궁금해요!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

인터페이스를 만드신 이유가 궁금해요!

RaceController 테스트를 위해 만들었어요! 단순 입력과 출력은 테스트의 필요성을 느끼지 못해서 따로 테스트를 만들지 않았습니다. 따라서 전체적인 경주 진행을 알아보는데 전체적인 출력보다는 필요한 부분만 출력하면 될 것으로 여겨져서 만들었습니다!

9 changes: 9 additions & 0 deletions src/main/kotlin/racingcar/util/InputParser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package racingcar.util

object InputParser {
const val COMMA = ','
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

delimeter를 따로 자료형으로 저장하는거 좋네요!
나중에 관리에도 좋아보여요. 하나 배워가요


fun parseCarNames(input: String): List<String> {
return input.split(COMMA)
}
}
24 changes: 24 additions & 0 deletions src/main/kotlin/racingcar/util/InputValidator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package racingcar.util

object InputValidator {
fun validateCarNames(readCarNames: List<String>) {
for (carName in readCarNames) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

for문보다 forEach로 람다식 활용하는것은 어떨까요!?
저도 많이 사용해보진 않았지만, forEach가 조금도 코틀린스럽고, 간결한 것 같아서 의견 드립니다!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

for문보다 forEach로 람다식 활용하는것은 어떨까요!? 저도 많이 사용해보진 않았지만, forEach가 조금도 코틀린스럽고, 간결한 것 같아서 의견 드립니다!

foreach 가 더 가독성이 좋아보일 것 같기도 하네요! 감사합니다!

if (carName.length > 5) {
throw IllegalArgumentException("${carName}이 5자를 초과합니다.")
}
if (carName.isBlank()) {
throw IllegalArgumentException("입력받은 자동차 이름이 공백입니다.")
}
}
}

fun validateTryCount(readTryCount: String?): Int {
val tryCount = readTryCount?.toIntOrNull() ?: throw IllegalArgumentException("입력받은 수가 유효하지 않은 값입니다.")

if (tryCount < 0) {
throw IllegalArgumentException("입력받은 수가 음수입니다.")
}

return tryCount
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/racingcar/util/RandomUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package racingcar.util

import camp.nextstep.edu.missionutils.Randoms

object RandomUtils {
fun getRandomInt(): Int {
return Randoms.pickNumberInRange(0, 9)
}
Comment on lines +6 to +8
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

0과 9를 상수로 선언하면 좋을 것 같습니다!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

0과 9를 상수로 선언하면 좋을 것 같습니다!

작은 부분이라 신경을 못 썼는데, 감사합니다!!

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

import camp.nextstep.edu.missionutils.Console
import racingcar.port.InputPort

object InputView : InputPort {
override fun readCarNames(): String? {
println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,)기준으로 구분)")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

입력과 출력이 같이되고있어서 단일 책임 원칙에 어긋나는 것 같습니다!
print를 outputView로 분리하면 어떨까요?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

이번 주 공통피드백에서도 언급은 됐지만, 상수 정의하는것도 고민해보셔야 할 것 같습니다!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

입력과 출력이 같이되고있어서 단일 책임 원칙에 어긋나는 것 같습니다! print를 outputView로 분리하면 어떨까요?

controller 를 좀 더 깔끔히 작성하고 싶어서 고민했던 부분인데, 확실히 단일 책임 원칙엔 어긋나는 것 같습니다..! 감사합니다

return Console.readLine()
}

override fun readTryCount(): String? {
println("시도할 횟수는 몇 회인가요?")
return Console.readLine()
}
}
29 changes: 29 additions & 0 deletions src/main/kotlin/racingcar/view/OutputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package racingcar.view

import racingcar.domain.Car
import racingcar.port.OutputPort

object OutputView : OutputPort {
const val BAR = "-"

override fun printFirstResult() {
printBlankLine()
println("실행 결과")
}

override fun printRoundResult(car: Car) {
val positionBar = BAR.repeat(car.position)

println("${car.name} : $positionBar")
}

override fun printBlankLine() {
println()
}

override fun printFinalWinner(winners: List<Car>) {
val winnersName = winners.joinToString(", ") { it.name }

println("최종 우승자 : $winnersName")
}
}
Loading