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

[자동차 경주] 김지원 미션 제출합니다. #113

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
39d13af
docs(README): Create README for feature documentation
kimgwon Oct 28, 2024
459c603
feat(Input): Create readLine() function for user input
kimgwon Oct 28, 2024
9724b77
feat(Input): create carNames() function for user input
kimgwon Oct 28, 2024
7f10610
refactor(Input): Modify readLine() to private for encapsulation
kimgwon Oct 28, 2024
c020e31
feat(Input): Create raceCount() function for user input
kimgwon Oct 28, 2024
90f01d3
feat(InputController): Create InputController to validate user input
kimgwon Oct 28, 2024
a528a23
refactor(InputController): Rename function getRaceCount() to getRaceR…
kimgwon Oct 28, 2024
d1de4e2
refactor(Input): Rename function raceCount() to raceRound()
kimgwon Oct 28, 2024
368f57b
feat(Car): Create Car data class with move function
kimgwon Oct 28, 2024
de750f2
feat(Race): Create Race data class
kimgwon Oct 28, 2024
6abe7e4
feat(RaceController): Create makeCar() function to race
kimgwon Oct 28, 2024
480a280
feat(RaceController): Create moveCar() function to move car
kimgwon Oct 28, 2024
8338351
feat(RaceController): Create getWinner() function to find race winner
kimgwon Oct 28, 2024
a317bc7
feat(Output): Add infoGetCarNames() function to get car names
kimgwon Oct 28, 2024
4cdf575
feat(Output): Add infoGetRound() function to get race round
kimgwon Oct 28, 2024
cdea6cf
feat(Output): Add showRoundResult() function to show each round result
kimgwon Oct 28, 2024
caec248
feat(Output): Add showRaceResult() function to show race final result
kimgwon Oct 28, 2024
41e4ee4
feat(RaceController): Create start() function to initiate race and di…
kimgwon Oct 28, 2024
5ce289c
feat(Application): Add raceController.start() to start race
kimgwon Oct 28, 2024
0e6350e
refactor(InputValidation): Split validation logic and remove InputCon…
kimgwon Oct 28, 2024
dbcda58
refactor(Application): Move input, output constructor to main() and i…
kimgwon Oct 28, 2024
0fd5e5c
refactor(Race): Move addCars(), maxMoving() to Race class
kimgwon Oct 28, 2024
77aad89
refactor: Extract common constants to separate file
kimgwon Oct 28, 2024
0909df1
test(ApplicationTest): Create test
kimgwon Oct 28, 2024
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
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,47 @@
# kotlin-racingcar-precourse
## 기능 요구 사항
### 사용자 입력
- [ ] `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 이용한다.
- [ ] 자동차 이름을 입력 받는다.
- [ ] 자동차 이름은 쉼표를 기준으로 구분한다.
- [ ] 이름은 1 ~ 5자이다.
- [ ] 횟수를 입력받는다.

### 출력
- [ ] 이름 입력 요청 시, '경주할 자동차 이름을 입력하세요.' 문구를 출력한다.
- [ ] 횟수 입력 요청 시, '시도할 횟수는 몇 회인가요?' 문구를 출력한다.
- [ ] 실행 결과 출력 시, '실행 결과' 문구를 출력한다.
- [ ] 매 라운드 실행 결과를 출력한다.
- [ ] 최종 우승자를 출력 시, '최종 우승자 : ${user}'를 출력한다.
- [ ] 우승자가 여러명이라면, 쉼표로 구분하여 출력한다.


### 자동차: Car
- [ ] 자동차 객체는 `name(이름)`, `moving(이동 칸)`을 변수로 가지고 있다.

### 자동차 이동: Car - move()
- [ ] 4 이상이면 한 칸 이동한다.

### 자동차 경주: Race
- [ ] 참여하는 자동차의 정보 `cars: List<Car>`를 변수로 갖는다.

### 우승자 결정: Race - getWinner()
- [ ] 횟수동안 가장 많이 이동한 자동차가 우승한다.
- [ ] 우승자는 여러명일 수 있다.


### 자동차 생성: CarController - makeCar()
- [ ] 사용자에게 이름을 입력받으면 자동차를 하나씩 생성하고, `cars`로 만든다.

### 자동차 이동: CarController - moveCar()
- [ ] `camp.nextstep.edu.missionutils.Randoms`의 `pickNumberInRange()`로 나온 값을 Car-move()로 호출한다.


### 테스트
- [ ] 자동차 이름이 입력되지 않으면 오류가 발생한다.
- [ ] 자동차 이름이 6자 이상이면 오류가 발생한다.
- [ ] 횟수는 숫자만 가능하다.
- [ ] 횟수가 입력되지 않으면 오류가 발생한다.
- [ ] 우승자가 한 명일 경우
- [ ] 우승자가 여러명일 경우
- [ ] 4 이상이면 한 칸 이동한다.
12 changes: 10 additions & 2 deletions src/main/kotlin/racingcar/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
package racingcar

import racingcar.controller.RaceController
import racingcar.view.Input
import racingcar.view.Output

fun main() {
// TODO: 프로그램 구현
}
val input = Input()
val output = Output()

val raceController = RaceController(input.getCarNames(), input.getRaceRound(), output)
raceController.start()
}
8 changes: 8 additions & 0 deletions src/main/kotlin/racingcar/constant/Error.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package racingcar.constant

object Error {
val CAR_NAME_RANGE = 1..5
val NOT_VALID_CAR_NAME_LENGTH =
"자동차 이름은 ${CAR_NAME_RANGE.first} 이상 ${CAR_NAME_RANGE.last} 이하로 입력해주세요. %s의 이름은 %d자 입니다."
const val NOT_VALID_ROUND_TYPE = "경주 횟수는 숫자만 입력 가능합니다. %s은 숫자가 아닙니다."
}
18 changes: 18 additions & 0 deletions src/main/kotlin/racingcar/constant/Message.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package racingcar.constant

object Message {
const val INFO_GET_CAR_NAMES = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"
const val INFO_GET_ROUND = "시도할 횟수는 몇 회인가요?"
const val INFO_ROUND_RESULT = "실행 결과"

const val COMMA = ","

val RANDOM_VALUE_RANGE = 0..9
const val CAR_MOVING_MIN_NUMBER = 4

const val ROUND_RESULT_FORMAT = "%s : "
const val MOVING_SYMBOL = "-"

const val RACE_RESULT_FORMAT = "최종 우승자 : %s"
const val RACE_WINNER_DELIMITER = ", "
}
32 changes: 32 additions & 0 deletions src/main/kotlin/racingcar/controller/RaceController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package racingcar.controller

import camp.nextstep.edu.missionutils.Randoms.pickNumberInRange
import racingcar.constant.Message.RANDOM_VALUE_RANGE
import racingcar.model.Car
import racingcar.model.Race
import racingcar.view.Output

class RaceController(carNames: List<String>, private val round: Int, private val output: Output) {
private val race = Race()

init {
race.addCar(carNames)
}

private fun moveCar() =
race.cars.forEach { it.move(pickNumberInRange(RANDOM_VALUE_RANGE.first, RANDOM_VALUE_RANGE.last)) }

private fun getWinner(): List<Car> {
val maxMoving = race.maxMoving()
return race.cars.filter { car -> car.moving == maxMoving }
}

fun start() {
repeat(round) {
moveCar()
output.showRoundResult(race.cars)
}

output.showRaceResult(getWinner())
}
}
12 changes: 12 additions & 0 deletions src/main/kotlin/racingcar/model/Car.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package racingcar.model

import racingcar.constant.Message.CAR_MOVING_MIN_NUMBER

data class Car(
val name: String,
var moving: Int = 0
) {
fun move(value: Int) {
if (value >= CAR_MOVING_MIN_NUMBER) moving++
}
}
8 changes: 8 additions & 0 deletions src/main/kotlin/racingcar/model/Race.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package racingcar.model

data class Race(
val cars: MutableList<Car> = mutableListOf()
) {
fun addCar(carNames: List<String>) = carNames.forEach { name -> cars.add(Car(name)) }
fun maxMoving() = cars.maxOf { it.moving }
}
13 changes: 13 additions & 0 deletions src/main/kotlin/racingcar/validation/InputValidation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package racingcar.validation

import racingcar.constant.Error.CAR_NAME_RANGE
import racingcar.constant.Error.NOT_VALID_CAR_NAME_LENGTH
import racingcar.constant.Error.NOT_VALID_ROUND_TYPE

class InputValidation {
fun carName(name: String) =
require(name.length in CAR_NAME_RANGE) { NOT_VALID_CAR_NAME_LENGTH.format(name, name.length) }

fun raceRound(round: String) =
round.toIntOrNull() ?: throw IllegalArgumentException(NOT_VALID_ROUND_TYPE.format(round))
}
28 changes: 28 additions & 0 deletions src/main/kotlin/racingcar/view/Input.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package racingcar.view

import camp.nextstep.edu.missionutils.Console
import racingcar.constant.Message.COMMA
import racingcar.constant.Message.INFO_GET_CAR_NAMES
import racingcar.constant.Message.INFO_GET_ROUND
import racingcar.validation.InputValidation

class Input {
private val inputValidation = InputValidation()

fun getCarNames(): List<String> {
println(INFO_GET_CAR_NAMES)
return readLine()
.split(COMMA)
.map {
inputValidation.carName(it)
it.trim()
}
}

fun getRaceRound(): Int {
println(INFO_GET_ROUND)
return readLine().also { inputValidation.raceRound(it) }.toInt()
}

private fun readLine(): String = Console.readLine()
}
26 changes: 26 additions & 0 deletions src/main/kotlin/racingcar/view/Output.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package racingcar.view

import racingcar.constant.Message.INFO_ROUND_RESULT
import racingcar.constant.Message.MOVING_SYMBOL
import racingcar.constant.Message.RACE_RESULT_FORMAT
import racingcar.constant.Message.RACE_WINNER_DELIMITER
import racingcar.constant.Message.ROUND_RESULT_FORMAT
import racingcar.model.Car

class Output {
fun showRoundResult(cars: List<Car>) {
println(INFO_ROUND_RESULT)

cars.forEach { car ->
print(ROUND_RESULT_FORMAT.format(car.name))
repeat(car.moving) {
print(MOVING_SYMBOL)
}
println()
}
println()
}

fun showRaceResult(cars: List<Car>) =
println(RACE_RESULT_FORMAT.format(cars.joinToString(RACE_WINNER_DELIMITER) { it.name }))
}
31 changes: 31 additions & 0 deletions src/test/kotlin/racingcar/ApplicationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import camp.nextstep.edu.missionutils.test.Assertions.assertRandomNumberInRangeT
import camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest
import camp.nextstep.edu.missionutils.test.NsTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

class ApplicationTest : NsTest() {
@Test
@DisplayName("우승자가 한 명일 경우")
fun `기능 테스트`() {
assertRandomNumberInRangeTest(
{
Expand All @@ -20,12 +22,41 @@ class ApplicationTest : NsTest() {
}

@Test
@DisplayName("우승자가 여러 명일 경우")
fun returnMultipleWinnersWhenSameScore() {
assertRandomNumberInRangeTest(
{
run("pobi,woni", "1")
assertThat(output()).contains("pobi : -", "woni : -", "최종 우승자 : pobi, woni")
},
MOVING_FORWARD, MOVING_FORWARD
)
}

@Test
@DisplayName("자동차 이름이 입력되어야 한다.")
fun throwExceptionWhenCarNameIsEmpty() {
assertSimpleTest {
assertThrows<IllegalArgumentException> { runException("", "1") }
}
}

@Test
@DisplayName("자동차 이름이 6자 이상이면 예외가 발생해야 한다.")
fun `예외 테스트`() {
assertSimpleTest {
assertThrows<IllegalArgumentException> { runException("pobi,javaji", "1") }
}
}

@Test
@DisplayName("레이싱 횟수는 숫자만 가능하다.")
fun throwExceptionWhenRoundIsNotNumber() {
assertSimpleTest {
assertThrows<IllegalArgumentException> { runException("pobi,woni", "a") }
}
}

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