Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Step4] 자동차 경주 #1529

Open
wants to merge 6 commits into
base: be-student
Choose a base branch
from
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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
# kotlin-racingcar
# kotlin-racingcar

## 기능목록

- [x] 자동차 이름 입력받는다
- [x] 우승자를 선정한다
26 changes: 26 additions & 0 deletions scripts/git-hooks/pre-commit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh

######## KTLINT-GRADLE HOOK START ########

CHANGED_FILES="$(git --no-pager diff --name-status --no-color --cached | awk '$1 != "D" && $2 ~ /\.kts|\.kt/ { print $2}')"

if [ -z "$CHANGED_FILES" ]; then
echo "No Kotlin staged files."
exit 0
fi;

echo "Running ktlint over these files:"
echo "$CHANGED_FILES"

./gradlew --quiet ktlintFormat -PinternalKtlintGitFilter="$CHANGED_FILES"

echo "Completed ktlint run."

echo "$CHANGED_FILES" | while read -r file; do
if [ -f $file ]; then
git add $file
fi
done

echo "Completed ktlint hook."
######## KTLINT-GRADLE HOOK END ########
2 changes: 1 addition & 1 deletion src/main/kotlin/calculator/OperatorCalculatorState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class OperatorCalculatorState(private val number: Int) : CalculatorState {
val operator = Operator.from(value)
return OperandCalculatorState(number, operator)
}

override fun result(): Int {
return number
}
Expand Down
14 changes: 11 additions & 3 deletions src/main/kotlin/racingcar/Car.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package racingcar

class Car(val position: Int) {
constructor() : this(INITIAL_POSITION)
class Car(val name: String, val position: Int) {
init {
require(name.length in MINIMUM_CAR_NAME_LENGTH..MAXIMUM_CAR_NAME_LENGTH) {
"자동차의 이름은 ${MINIMUM_CAR_NAME_LENGTH}글자 이상 ${MAXIMUM_CAR_NAME_LENGTH}글자 이하만 가능합니다."
}
}

constructor(name: String) : this(name, INITIAL_POSITION)

fun move(power: Int): Car {
validatePower(power)
if (power >= MINIMUM_AMOUNT_TO_MOVE) {
return Car(position + 1)
return Car(name, position + 1)
}
return this
}
Expand All @@ -22,5 +28,7 @@ class Car(val position: Int) {
const val MINIMUM_AMOUNT_TO_MOVE = 4
const val MAXIMUM_MOVE_POWER = 9
const val MINIMUM_MOVE_POWER = 0
const val MINIMUM_CAR_NAME_LENGTH = 1
const val MAXIMUM_CAR_NAME_LENGTH = 5
}
}
8 changes: 6 additions & 2 deletions src/main/kotlin/racingcar/Cars.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package racingcar

class Cars(val cars: List<Car>, private val powerGenerator: PowerGenerator) {
init {
require(cars.isNotEmpty()) { "자동차는 최소 한 대 이상 존재해야 합니다." }
}

fun move(): Cars {
val movedCars = mutableListOf<Car>()
cars.forEach { movedCars.add(it.move(powerGenerator.generate())) }
return Cars(movedCars, powerGenerator)
}

companion object {
fun initializeWithNumberOfCars(numberOfCars: Int, powerGenerator: PowerGenerator): Cars {
fun initialize(numberOfCars: Int, names: List<String>, powerGenerator: PowerGenerator): Cars {
val cars = mutableListOf<Car>()
repeat(numberOfCars) {
cars.add(Car())
cars.add(Car(names[it]))
}
return Cars(cars, powerGenerator)
}
Expand Down
5 changes: 5 additions & 0 deletions src/main/kotlin/racingcar/InputView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ object InputView {
println("시도할 회수는 몇 회 인가요?")
return readln().toInt()
}

fun inputNameOfCars(): List<String> {
println("자동차 이름을 입력하세요. (이름은 쉼표(,) 기준으로 구분)")
return readln().split(",")
}
Comment on lines +15 to +18
Copy link

Choose a reason for hiding this comment

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

View에서 바로 입력 값에 대한 가공을 하셨네요. 이건 참 사람마다 의견이 나뉠 수 있는 부분이라고 생각해요. 그런데 이런 처리까지 View에서 한다면 입력 받고 split 하면서 바로 이름에 대한 사이즈 검증도 하면 되지 않나? 라는 생각이 들기도 하네요. 어떤 부분은 View에서 직접 처리하고 어떤 부분은 별도 객체에서 처리할 지 결정한 기준이 있으신가요?

Copy link
Author

Choose a reason for hiding this comment

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

"자동차 n 대가 있으면 이름이 n 개 있어야 해" 라는 로직은 비즈니스 로직쪽에 더 가까울 것 같아서, 제거했고, split 같이 단순하게 처리하기 편하게 하기 위한 것들은 그냥 view 에서 처리하는 방향으로 하려고 했어요
입력을 처리하기 위한 편의성의 여부에 따라 달라질 것 같아요

}
18 changes: 15 additions & 3 deletions src/main/kotlin/racingcar/Main.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package racingcar

fun main() {
val numberOfCars = InputView.inputNumberOfCars()
val numberOfMoves = InputView.inputNumberOfMoves()
val cars = initializeCars()

val movedCars = moveCars(cars)
val winners = RacingRule.findWinners(movedCars)
OutputView.printWinners(winners)
}

val cars = Cars.initializeWithNumberOfCars(numberOfCars, RandomPowerGenerator)
private fun moveCars(cars: Cars): Cars {
val numberOfMoves = InputView.inputNumberOfMoves()

OutputView.printResultTitle()

Expand All @@ -13,4 +18,11 @@ fun main() {
movedCars = movedCars.move()
OutputView.printResult(movedCars.cars)
}
return movedCars
}

private fun initializeCars(): Cars {
val numberOfCars = InputView.inputNumberOfCars()

Choose a reason for hiding this comment

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

이번 단계에서는 자동차 대수에 대한 입력은 받지 않습니다. 따라서 현재 전체적으로 프로그램이 제대로 동작하지 않습니다. 수정 후 다시 리뷰 요청 드립니다.

val namesOfCars = InputView.inputNameOfCars()
return Cars.initialize(numberOfCars, namesOfCars, RandomPowerGenerator)
}
5 changes: 5 additions & 0 deletions src/main/kotlin/racingcar/OutputView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ object OutputView {
}
println()
}

fun printWinners(winners: List<Car>) {
val winnerNames = winners.joinToString(", ") { it.name }
println("$winnerNames 가 최종 우승했습니다.")
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/racingcar/RacingRule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package racingcar

object RacingRule {

fun findWinners(cars: Cars): List<Car> {
val maxPosition = cars.cars.maxBy { it.position }.position
return cars.cars.filter { it.position == maxPosition }
}
}
2 changes: 0 additions & 2 deletions src/main/kotlin/racingcar/RandomPowerGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ package racingcar
import kotlin.random.Random

object RandomPowerGenerator : PowerGenerator {

private const val MAX_POWER = 10
private const val MIN_POWER = 0


override fun generate(): Int {
return Random.nextInt(MIN_POWER, MAX_POWER)
}
Expand Down
15 changes: 12 additions & 3 deletions src/test/kotlin/racingcar/CarTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,34 @@ import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource

internal class CarTest {
@Test
fun `자동차의 이름은 1자 이상이어야 한다`() {
Copy link

Choose a reason for hiding this comment

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

결과적으로 예외 처리를 하네요. 그러면 정확한 동작을 기술하면'자동차 이름이 공백("")일 경우 예외가 발생한다.' 정도로 하면 더 명확하게 테스트 내용을 이해하는데 도움이 될 것 같다는 생각이 드네요.

Copy link
Author

Choose a reason for hiding this comment

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

아하 글자수보다는 훨씬 더 직관적이겠네요 감사합니다! 👍

assertThrows<IllegalArgumentException> { Car("") }
}

@Test
fun `자동차의 이름은 5자 이하여야 한다`() {
assertThrows<IllegalArgumentException> { Car("123456") }
}

@Test
fun `자동차는 4 이상이 들어오면 움직인다`() {
val car = Car()
val car = Car("name")
val result = car.move(4)
assertThat(result.position).isOne()
}

@Test
fun `자동차는 4 미만이 들어오면 움직이지 않는다`() {
val car = Car()
val car = Car("name")
val result = car.move(3)
assertThat(result.position).isZero()
}

@ParameterizedTest
@ValueSource(ints = [-1, 10])
fun `자동차는 0 이상 9 이하의 값만 가능하다`(power: Int) {
val car = Car()
val car = Car("name")

assertThrows<IllegalArgumentException> {
car.move(power)
Expand Down
14 changes: 10 additions & 4 deletions src/test/kotlin/racingcar/CarsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@ package racingcar

import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

internal class CarsTest {

@Test
fun `자동차들은 특정 숫자 이상이 들어오면 움직인다`() {
val cars = Cars.initializeWithNumberOfCars(3, FixedPowerGenerator(4))
fun `자동차들은 1대 이상 있어야 한다`() {
assertThrows<IllegalArgumentException> { Cars.initialize(0, listOf("a", "b", "c"), FixedPowerGenerator(5)) }
}

@Test
fun `자동차들은 4 이상이 들어오면 움직인다`() {
val cars = Cars.initialize(3, listOf("a", "b", "c"), FixedPowerGenerator(4))

val result = cars.move()

assertTrue(result.cars.all { it.position == 1 })
}

@Test
fun `자동차들은 특정 숫자 미만이 들어오면 움직이지 않는다`() {
val cars = Cars.initializeWithNumberOfCars(3, FixedPowerGenerator(3))
fun `자동차들은 4 미만이 들어오면 움직이지 않는다`() {
val cars = Cars.initialize(3, listOf("a", "b", "c"), FixedPowerGenerator(3))

val result = cars.move()

Expand Down
16 changes: 16 additions & 0 deletions src/test/kotlin/racingcar/RacingRuleTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package racingcar

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

internal class RacingRuleTest {

@Test
fun `Cars 가 들어오면 우승자를 가려낼 수 있다`() {
val cars = Cars(listOf(Car("aa", 2), Car("a", 1)), FixedPowerGenerator(5))

val result = RacingRule.findWinners(cars)

assertThat(result).usingRecursiveComparison().isEqualTo(listOf(Car("aa", 2)))
}
}