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

[자동차 경주] 강예인 미션 제출합니다. #96

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
8ef8220
docs(READ.md): 구현할 기능 목록 작성
kangyein9892 Oct 28, 2024
da0f00c
feat(Car.kt): 이름과 위치를 다루는 자동차 model 구현
kangyein9892 Oct 28, 2024
5e9f929
feat(RacingCar.kt): 자동차를 움직일 수 있는지 확인하여 움직이는 기능, 우승자를 찾는 기능 구현
kangyein9892 Oct 28, 2024
5dd9ffe
feat(RacingCarView.kt): 입력 기능 및 출력 기능 구현
kangyein9892 Oct 28, 2024
2e4d5d1
feat(ValidatorUtil.kt): 자동차와 시도횟수를 검사하는 Validator 구현
kangyein9892 Oct 28, 2024
e0e8465
feat(RacingCarPresenter.kt): 자동차 이름과 시도횟수를 받아 처리하는 Presenter 추가
kangyein9892 Oct 28, 2024
42c7a2a
feat(Application.kt): 프로그램 시작점 구현
kangyein9892 Oct 28, 2024
7ebdbd7
refactor(Car.kt): Car 데이터와 동작에 집중할 수 있도록 수정
kangyein9892 Oct 28, 2024
12bf3a6
chore(RacingCar.kt): 최소 이동 조건 숫자 상수 사용하도록 수정
kangyein9892 Oct 28, 2024
2dacc8c
chore(ApplicationTest.kt): ApplicationTest 삭제
kangyein9892 Oct 28, 2024
6e62086
test(CarInputTest.kt): 자동차 입력 예외 테스트 기능 구현
kangyein9892 Oct 28, 2024
6955fd9
test(CarInputTest.kt): 시도 횟수 입력 예외 테스트 기능 구현
kangyein9892 Oct 28, 2024
862f7be
refactor(Application.kt): 콜론(,) 상수화
kangyein9892 Oct 28, 2024
2a3cb28
chore(RoundInputTest.kt) 사용 안 하는 import문 삭제
kangyein9892 Oct 28, 2024
84e1999
test(RacingCarTest.kt) 자동차 이동 및 레이싱 라운드 테스트
kangyein9892 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
139 changes: 139 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,140 @@
# kotlin-racingcar-precourse


## 📋 과제 진행 요구 사항

- 미션은 자동차 경주 저장소를 포크하고 클론하는 것으로 시작한다.
- 기능을 구현하기 전 README.md에 구현할 기능 목록을 정리해 추가한다.
- Git의 커밋 단위는 앞 단계에서 README.md에 정리한 기능 목록 단위로 추가한다.
- AngularJS Git Commit Message Conventions을 참고해 커밋 메시지를 작성한다.
- 자세한 과제 진행 방법은 프리코스 진행 가이드 문서를 참고한다.

## 기능 요구 사항

초간단 자동차 경주 게임을 구현한다.

- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.

### 입출력 요구 사항

**입력**

- 경주할 자동차 이름(이름은 쉼표(,) 기준으로 구분)
```
pobi,woni,jun
```

- 시도할 횟수
```
5
```

**출력**

- 차수별 실행 결과
```
pobi : --
woni : ----
jun : ---
```

- 단독 우승자 안내 문구
```
최종 우승자 : pobi
```

- 공동 우승자 안내 문구
```
최종 우승자 : pobi, jun
```

**실행 결과 예시**

```
경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)
pobi,woni,jun
시도할 횟수는 몇 회인가요?
5

실행 결과
pobi : -
woni :
jun : -

pobi : --
woni : -
jun : --

pobi : ---
woni : --
jun : ---

pobi : ----
woni : ---
jun : ----

pobi : -----
woni : ----
jun : -----

최종 우승자 : pobi, jun
```

## 프로그래밍 요구 사항 1

- Kotlin 1.9.24에서 실행 가능해야 한다.
- Java 코드가 아닌 Kotlin 코드로만 구현해야 한다.
- 프로그램 실행의 시작점은 Application의 main()이다.
- build.gradle.kts 파일은 변경할 수 없으며, 제공된 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.
- 프로그램 종료 시 System.exit() 또는 exitProcess()를 호출하지 않는다.
- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다.
- 코틀린 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 Kotlin Style Guide를 원칙으로 한다.
-
## 프로그래밍 요구 사항 2
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- JUnit 5와 AssertJ를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다.
- 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다.
- JUnit 5 User Guide
- AssertJ User Guide
- AssertJ Exception Assertions
- Guide to JUnit 5 Parameterized Tests

## 라이브러리

- camp.nextstep.edu.missionutils에서 제공하는 Randoms 및 Console API를 사용하여 구현해야 한다.
- Random 값 추출은 camp.nextstep.edu.missionutils.Randoms의 pickNumberInRange()를 활용한다.
- 사용자가 입력하는 값은 camp.nextstep.edu.missionutils.Console의 readLine()을 활용한다.

### 사용 예시
- 0에서 0까지의 정수 중 한 개의 정수 반환
```
Randoms.pickNumberInRange(0, 9)
```

## 구현할 기능 목록
1. 자동차 이름을 입력받는다.
- 쉼표를 기준으로 분리한다.
- 차의 이름과 전진하고 그의 횟수를 다룰 수 있도록 한다.
- 차의 이름이 5자 이상이면 IllegalArgumentException을 발생시킨다.
- 차의 이름이 빈 문자열이면 IllegalArgumentException을 발생시킨다.
- 차의 이름이 중복되면 IllegalArgumentException을 발생시킨다.
2. 시도 횟수를 입력받는다.
- 시도 횟수가 1 미만이면 IllegalArgumentException을 발생시킨다.
- 시도 횟수가 Int로 바꿀 수 없으면 IllegalArgumentException을 발생시킨다.
3. 무작위 값을 구해서 시도횟수 만큼 각각 자동차를 움직이는 기회를 갖도록 한다.
- 4 이상일 경우에 움직일 수 있도록 진행한다.
- 시도하는 횟수 동안 자동차의 이동을 출력한다.
- 각각의 자동차가 총 움직인 횟수를 구한다.
4. 자동차가 총 움직인 횟수를 비교하여 우승자를 구한다.
- 우승자를 출력한다.
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.presenter.RacingCarPresenter
import racingcar.util.ConstantsUtil.DELIMITER_COMMA
import racingcar.util.ConstantsUtil.MESSAGE_INPUT_CAR_NAME
import racingcar.util.ConstantsUtil.MESSAGE_INPUT_ROUND
import racingcar.view.RacingCarView
import racingcar.view.RacingCarViewImpl

fun main() {
// TODO: 프로그램 구현
val view: RacingCarView = RacingCarViewImpl()
val presenter = RacingCarPresenter(view)

val carNames = view.getUserInput(MESSAGE_INPUT_CAR_NAME).split(DELIMITER_COMMA)
val round = view.getUserInput(MESSAGE_INPUT_ROUND)

presenter.startRacingCar(carNames, round)
}
13 changes: 13 additions & 0 deletions src/main/kotlin/racingcar/model/Car.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package racingcar.model

import racingcar.util.ConstantsUtil.CAR_INITIAL_POSITION

data class Car(
val name: String,
var position: Int = CAR_INITIAL_POSITION
) {

fun move() {
position++
}
}
27 changes: 27 additions & 0 deletions src/main/kotlin/racingcar/model/RacingCar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package racingcar.model

import camp.nextstep.edu.missionutils.Randoms
import racingcar.util.ConstantsUtil.MAX_RANDOM_NUMBER
import racingcar.util.ConstantsUtil.MIN_RANDOM_NUMBER
import racingcar.util.ConstantsUtil.MOVE_MIN_NUMBER

class RacingCar(
private val cars: List<Car>
) {
fun playRound() {
cars.forEach { car ->
if (isMove()) {
car.move()
}
}
}

private fun isMove(): Boolean {
return Randoms.pickNumberInRange(MIN_RANDOM_NUMBER, MAX_RANDOM_NUMBER) >= MOVE_MIN_NUMBER
}

fun findWinners(): List<Car> {
val maxPosition = cars.maxOf { it.position }
return cars.filter { it.position == maxPosition }
}
}
43 changes: 43 additions & 0 deletions src/main/kotlin/racingcar/presenter/RacingCarPresenter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package racingcar.presenter

import racingcar.model.Car
import racingcar.model.RacingCar
import racingcar.util.ValidatorUtil
import racingcar.util.ValidatorUtil.validateCarLength
import racingcar.util.ValidatorUtil.validateCarName
import racingcar.util.ValidatorUtil.validateCarsNames
import racingcar.util.ValidatorUtil.validateRoundRange
import racingcar.util.ValidatorUtil.validateRoundType
import racingcar.view.RacingCarView

class RacingCarPresenter(
private val racingCarView: RacingCarView
) {
fun startRacingCar(
carNames: List<String>,
roundString: String
) {

validateCarsNames(carNames)
carNames.forEach { name ->
validateCarLength(name.length)
validateCarName(name)
}

validateRoundType(roundString)
val roundNumber = roundString.toInt()
validateRoundRange(roundNumber)

val cars = carNames.map { Car(it) }
val racingCar = RacingCar(cars)

repeat(roundNumber) {
racingCar.playRound()
racingCarView.displayRaceRound(cars)
}

val winners = racingCar.findWinners()
racingCarView.displayWinners(winners)

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

object ConstantsUtil {
const val CAR_INITIAL_POSITION = 0
const val MOVE_MIN_NUMBER = 4
const val CAR_NAME_MAX_LENGTH = 5
const val MIN_RANDOM_NUMBER = 0
const val MAX_RANDOM_NUMBER = 9

const val DELIMITER_COMMA = ","

const val MESSAGE_CAR_LENGTH_EXCEEDED = "자동차 이름 길이가 5자를 초과했습니다."
const val MESSAGE_CAR_NAME_BLANK = "자동차 이름 길이가 5자를 초과했습니다."
const val MESSAGE_CAR_NAME_DUPLICATE = "자동차 이름이 중복되었습니다."
const val MESSAGE_ROUND_NOT_EXCEEDED= "시도 횟수가 1 미만입니다."
const val MESSAGE_ROUND_NOT_INT= "시도 횟수가 숫자가 아닙니다."

const val MESSAGE_INPUT_CAR_NAME = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"
const val MESSAGE_INPUT_ROUND = "시도할 횟수는 몇 회인가요?"

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

import racingcar.util.ConstantsUtil.CAR_NAME_MAX_LENGTH
import racingcar.util.ConstantsUtil.MESSAGE_CAR_LENGTH_EXCEEDED
import racingcar.util.ConstantsUtil.MESSAGE_CAR_NAME_BLANK
import racingcar.util.ConstantsUtil.MESSAGE_CAR_NAME_DUPLICATE
import racingcar.util.ConstantsUtil.MESSAGE_ROUND_NOT_EXCEEDED
import racingcar.util.ConstantsUtil.MESSAGE_ROUND_NOT_INT

object ValidatorUtil {

fun validateCarLength(carLength: Int) {
require(carLength <= CAR_NAME_MAX_LENGTH){
MESSAGE_CAR_LENGTH_EXCEEDED
}
}

fun validateCarName(carName: String) {
require(carName.isNotBlank()) {
MESSAGE_CAR_NAME_BLANK
}
}

fun validateCarsNames(carNames: List<String>) {
require(carNames.size == carNames.distinct().size){
MESSAGE_CAR_NAME_DUPLICATE
}
}

fun validateRoundType(roundString: String) {
require(roundString.toIntOrNull() != null){
MESSAGE_ROUND_NOT_INT
}
}

fun validateRoundRange(roundNumber: Int){
require(roundNumber >= 1){
MESSAGE_ROUND_NOT_EXCEEDED
}
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/racingcar/view/RacingCarView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package racingcar.view

import racingcar.model.Car

interface RacingCarView {
fun getUserInput(message: String): String
fun displayRaceRound(cars: List<Car>)
fun displayWinners(cars: List<Car>)
}
29 changes: 29 additions & 0 deletions src/main/kotlin/racingcar/view/RacingCarViewImpl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package racingcar.view

import camp.nextstep.edu.missionutils.Console
import racingcar.model.Car
import racingcar.util.ConstantsUtil.MESSAGE_RACING_WINNERS_FORMAT

class RacingCarViewImpl : RacingCarView {

override fun getUserInput(message: String): String {
println(message)
return Console.readLine()
}

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

override fun displayWinners(cars: List<Car>) {
println(formatWinners(cars))
}

private fun formatWinners(cars: List<Car>): String {
val winners = cars.joinToString(", ") { it.name }
return MESSAGE_RACING_WINNERS_FORMAT.format(winners)
}
}
37 changes: 0 additions & 37 deletions src/test/kotlin/racingcar/ApplicationTest.kt

This file was deleted.

Loading