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

[자동차 경주] 이영주 미션 제출합니다. #200

Open
wants to merge 56 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
d7fa23b
[docs] 자동차 이름 입력받는 기능목록 작성
2zerozu Oct 30, 2023
62d8e9e
[docs] 이동 횟수 입력받는 기능목록 작성
2zerozu Oct 30, 2023
69bb4d0
[docs] 경주 진행 기능목록 작성
2zerozu Oct 30, 2023
a034705
[docs] 우승자 프린트하는 기능목록 작성
2zerozu Oct 30, 2023
ae257d9
[chore] TODO 제거
2zerozu Oct 30, 2023
5c0858e
[docs] 자동차 이름 입력받는 기능명세 수정
2zerozu Oct 30, 2023
88efae5
[chore] 에러메세지 추가
2zerozu Oct 30, 2023
ddae3d5
[chore] 게임메세지 추가
2zerozu Oct 30, 2023
0806e7f
[feat] String을 Map으로 바꾸는 컨버터함수 구현
2zerozu Oct 30, 2023
30306ae
[chore] RacingCarInput 오브젝트 추가
2zerozu Oct 30, 2023
bdac5cc
[feat] 중복된 자동차 이름을 검사하는 함수 구현
2zerozu Oct 30, 2023
0a6a0a0
[feat] 최소 자동차 개수 검사하는 함수 구현
2zerozu Oct 30, 2023
eb84aec
[feat] 자동차 이름 길이 검사하는 함수 구현
2zerozu Oct 30, 2023
7101b28
[chore] 유효성 검사함수 모아둔 함수 추가
2zerozu Oct 30, 2023
8531843
[feat] 사용자에게 자동차이름 입력받는 함수 구현
2zerozu Oct 30, 2023
4c983b2
[chore] 자동차 최소 개수 상수명 수정
2zerozu Oct 30, 2023
fc060d5
[chore] MoveCountInput 오브젝트 생성
2zerozu Oct 30, 2023
3478780
[chore] print를 println으로 수정
2zerozu Oct 30, 2023
1945093
[feat] 유저의 입력을 받는 함수 추가
2zerozu Oct 30, 2023
bf228fc
[refactor] try-catch문을 runCatching문으로 수정
2zerozu Oct 30, 2023
b55f501
[chore] 사용 안 하는 import 제거
2zerozu Oct 30, 2023
9572783
[feat] 입력된 횟수의 유효성을 검사하는 함수 구현
2zerozu Oct 30, 2023
76a4712
[feat] String을 Int로 바꾸는 컨버터 함수 추가
2zerozu Oct 30, 2023
517189c
[feat] 이동횟수를 입력받는 함수 구현
2zerozu Oct 30, 2023
32068c6
[docs] 기능목록 map을 data class로 변경
2zerozu Oct 30, 2023
ddecf6b
[refactor] map으로 구현한 자동차이름, 포지션 data class로 수정
2zerozu Oct 30, 2023
2ed53d3
[feat] Car 데이터클래스 추가
2zerozu Oct 30, 2023
2ec1c28
[docs] 경주 현황 프린트하는 기능목록 추가
2zerozu Oct 30, 2023
f31958c
[style] EOL 추가
2zerozu Oct 30, 2023
f40a18c
[refactor] Car 데이터클래스에서 클래스로 수정
2zerozu Oct 30, 2023
b76ff8e
[feat] 현재 레이스 현황을 보여주는 함수 구현
2zerozu Oct 30, 2023
e3caf02
[feat] 승자를 출력하는 함수 구현
2zerozu Oct 30, 2023
27e7cfc
[style] 공백 제거
2zerozu Oct 30, 2023
a32bcfa
[feat] 랜덤수가 4 이상일 경우에 position을 증가시키는 함수 구현
2zerozu Oct 30, 2023
e3e267a
[feat] 자동차 경주 함수 구현
2zerozu Oct 30, 2023
5ae1d9c
[feat] 승자를 찾는 함수 구현
2zerozu Oct 30, 2023
baa149b
[refactor] RaceManager object를 class로 변경
2zerozu Oct 30, 2023
2feb315
[refactor] RaceManager 내부 상태 관리로 변경
2zerozu Oct 30, 2023
d2f358c
[feat] RacingGame 클래스 생성 및 생성자 인자 추가
2zerozu Oct 30, 2023
94e196a
[feat] 입력된 숫자만큼 경주를 진행하고 프린트하는 함수 구현
2zerozu Oct 30, 2023
a9d74f1
[feat] RacingGame클래스 start 함수 구현
2zerozu Oct 30, 2023
fcf17c9
[feat] GameRunner object 및 run 함수 구현
2zerozu Oct 30, 2023
0a22dc7
[feat] GameRunner run 함수 호출
2zerozu Oct 30, 2023
63ba631
[docs] 기능목록 수정
2zerozu Nov 1, 2023
b25cb4a
[chore] RacingCarInputTest 클래스 생성
2zerozu Nov 1, 2023
f9c2853
[chore] validate 함수 public으로 전환
2zerozu Nov 1, 2023
fc44391
[test] 자동차 글자수 테스트코드 작성
2zerozu Nov 1, 2023
a7902cf
[test] 자동차 개수 테스트코드 작성
2zerozu Nov 1, 2023
1139df3
[test] 중복된 자동차 이름 테스트코드 작성
2zerozu Nov 1, 2023
f90058b
[test] MovingCountInputTest 클래스 생성
2zerozu Nov 1, 2023
0b668ad
[refactor] getMoveCount에 파라미터 추가
2zerozu Nov 1, 2023
a546f6c
[chore] throwable message 추가
2zerozu Nov 1, 2023
9d3c449
[test] 이동 횟수 범위 테스트코드 작성
2zerozu Nov 1, 2023
f607319
[test] 이동 횟수 타입 테스트코드 작성
2zerozu Nov 1, 2023
19ef379
[test] 자동차 변수명 수정
2zerozu Nov 1, 2023
4531a19
[chore] 띄어쓰기 추가
2zerozu Nov 1, 2023
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
19 changes: 19 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## 기능 목록

1. 경주 할 자동차 이름을 입력받는다
1. 자동차 이름은 쉼표로 구분한다
2. 구분한 것을 리스트에 저장한다
3. 입력된 자동차 이름이 5자 초과이면 `IllegalArgumentException` 발생
4. 입력된 자동차 개수가 1개 이하면 `IllegalArgumentException` 발생 (경주는 둘 이상부터 가능)

2. 몇 번의 이동을 할 것인지 입력받는다
1. 입력된 숫자의 수가 0 이하라면 `IllegalArgumentException` 발생
2. 숫자가 아니라면 `IllegalArgumentException` 발생 (`NumberFormatException`)

3. 입력받은 숫자만큼 반복하여 경주를 진행한다
1. class에 자동차 이름과 전진 수를 저장한다
2. 무작위 값이 4 이상일 경우 전진 수를 ++ 해준다
3. 경주 현황을 프린트한다

4. 우승자를 프린트한다
1. 우승자가 여러 명일 경우 쉼표를 이용하여 나타내준다
2 changes: 1 addition & 1 deletion src/main/kotlin/racingcar/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package racingcar

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

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

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

object Converter {
fun convertStringToCars(input: String): List<Car> = input.split(",").map { Car(it.trim()) }

fun convertStringToInt(input: String) = input.toInt()
}
9 changes: 9 additions & 0 deletions src/main/kotlin/racingcar/ErrorMessage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package racingcar

object ErrorMessage {
const val CAR_NAME_LENGTH_OVER_ERROR = "입력된 자동차 이름이 5자 초과입니다."
const val CAR_MINIMUM_COUNT_ERROR = "입력된 자동차의 개수가 1개 이하입니다."
const val DUPLICATE_CAR_NAME_ERROR = "중복되는 자동차 이름이 있습니다."
const val NUMBER_FORMAT_ERROR = "입력된 수가 숫자가 아닙니다."
const val INVALID_ATTEMPT_COUNT_ERROR = "입력된 횟수가 0 이하입니다."
}
8 changes: 8 additions & 0 deletions src/main/kotlin/racingcar/GameMessage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package racingcar

object GameMessage {
const val INPUT_RACING_CAR_NAME_MESSAGE = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"
const val INPUT_NUMBER_ATTEMPTS_MESSAGE = "시도할 횟수는 몇 회인가요?"
const val GAME_RESULT_MESSAGE = "실행 결과"
const val WINNER_MESSAGE = "최종 우승자 : "
}
9 changes: 9 additions & 0 deletions src/main/kotlin/racingcar/GameRunner.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package racingcar

object GameRunner {
fun run() {
val raceManager = RaceManager()
val racingGame = RacingGame(RacingCarInput, MoveCountInput, raceManager, RacePrinter)
racingGame.start()
}
}
26 changes: 26 additions & 0 deletions src/main/kotlin/racingcar/MoveCountInput.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package racingcar

import racingcar.Converter.convertStringToInt
import racingcar.ErrorMessage.INVALID_ATTEMPT_COUNT_ERROR
import racingcar.ErrorMessage.NUMBER_FORMAT_ERROR

object MoveCountInput {
private const val MIN_ATTEMPT_COUNT = 0

fun getMoveCount(userInput: String): Int {
return runCatching {
convertStringToInt(userInput).apply { validateAttempts(this) }
}.getOrElse { throwable ->
when (throwable) {
is NumberFormatException -> throw IllegalArgumentException(NUMBER_FORMAT_ERROR)
else -> throw IllegalArgumentException(throwable.message)
}
}
}

private fun validateAttempts(attempts: Int) {
if (attempts <= MIN_ATTEMPT_COUNT) {
throw IllegalArgumentException(INVALID_ATTEMPT_COUNT_ERROR)
}
}
}
35 changes: 35 additions & 0 deletions src/main/kotlin/racingcar/RaceManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package racingcar

import camp.nextstep.edu.missionutils.Randoms

class RaceManager {
private lateinit var cars: List<Car>

fun setup(cars: List<Car>) {
this.cars = cars
}

fun race(): List<Car> {
for (car in cars) {
moveIfPossible(car)
}
return cars
}

private fun moveIfPossible(car: Car) {
if (Randoms.pickNumberInRange(RANDOM_START_NUMBER, RANDOM_END_NUMBER) >= MOVE_MIN_NUMBER) {
car.move()
}
}

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

companion object {
private const val RANDOM_START_NUMBER = 0
private const val RANDOM_END_NUMBER = 9
private const val MOVE_MIN_NUMBER = 4
}
}
21 changes: 21 additions & 0 deletions src/main/kotlin/racingcar/RacePrinter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package racingcar

import racingcar.GameMessage.WINNER_MESSAGE

object RacePrinter {
private const val DASH = "-"
private const val COLON = " : "
private const val COMMA = ", "

fun showCurrentRace(cars: List<Car>) {
cars.map { car ->
println(car.name + COLON + DASH.repeat(car.position))
}
println()
}

fun showWinners(winners: List<Car>) {
val winnerNames = winners.joinToString(COMMA) { it.name }
println(WINNER_MESSAGE + winnerNames)
}
}
56 changes: 56 additions & 0 deletions src/main/kotlin/racingcar/RacingCarInput.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package racingcar

import racingcar.Converter.convertStringToCars
import racingcar.ErrorMessage.CAR_MINIMUM_COUNT_ERROR
import racingcar.ErrorMessage.CAR_NAME_LENGTH_OVER_ERROR
import racingcar.ErrorMessage.DUPLICATE_CAR_NAME_ERROR
import racingcar.GameMessage.INPUT_RACING_CAR_NAME_MESSAGE
import racingcar.UserInput.getUserInput

object RacingCarInput {
private const val MIN_CAR_COUNT = 1
private const val MAX_CAR_NAME_LENGTH = 5

fun getCarNames(): List<Car> {
println(INPUT_RACING_CAR_NAME_MESSAGE)
val userInput = getUserInput()
return runCatching {
convertStringToCars(userInput).apply { validate(this) }
}.getOrElse { throwable ->
throw IllegalArgumentException(throwable)
}
}

// 테스트 코드를 위해 public 으로 전환했습니다. (멍청이슈)
fun validate(cars: List<Car>) {
validateCarNameDuplicate(cars)
validateMinimumCarCount(cars)
validateCarNameLength(cars)
}

private fun validateCarNameDuplicate(cars: List<Car>) {
val carNames = cars.map { it.name }
if (carNames.size != carNames.distinct().size) {
throw IllegalArgumentException(DUPLICATE_CAR_NAME_ERROR)
}
}

private fun validateMinimumCarCount(cars: List<Car>) {
if (cars.size <= MIN_CAR_COUNT) {
throw IllegalArgumentException(CAR_MINIMUM_COUNT_ERROR)
}
}

private fun validateCarNameLength(cars: List<Car>) {
for (car in cars) {
validateLength(car.name)
}
}

private fun validateLength(carName: String) {
if (carName.length > MAX_CAR_NAME_LENGTH) {
throw IllegalArgumentException(CAR_NAME_LENGTH_OVER_ERROR)
}
}

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

import racingcar.GameMessage.GAME_RESULT_MESSAGE

class RacingGame(
private val racingCarInput: RacingCarInput,
private val moveCountInput: MoveCountInput,
private val raceManager: RaceManager,
private val racePrinter: RacePrinter
) {
fun start() {
val cars = racingCarInput.getCarNames()
showNumberInputMessage()
val count = UserInput.getUserInput().let {
moveCountInput.getMoveCount(it)
}
raceManager.setup(cars)
runRace(count)
val winners = raceManager.findWinners()
racePrinter.showWinners(winners)
}

private fun showNumberInputMessage() {
println(GameMessage.INPUT_NUMBER_ATTEMPTS_MESSAGE)
}

private fun runRace(count: Int) {
println(GAME_RESULT_MESSAGE)
repeat(count) {
val currentRace: List<Car> = raceManager.race()
racePrinter.showCurrentRace(currentRace)
}
}
}
7 changes: 7 additions & 0 deletions src/main/kotlin/racingcar/UserInput.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package racingcar

import camp.nextstep.edu.missionutils.Console

object UserInput {
fun getUserInput(): String = Console.readLine()
}
31 changes: 31 additions & 0 deletions src/test/kotlin/racingcar/MovingCountInputTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package racingcar

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

class MovingCountInputTest {

@Test
fun `입력된 이동 횟수가 0 이하라면 예외가 발생한다`() {
// given
val countInput = "0"

// then
assertThatThrownBy {
MoveCountInput.getMoveCount(countInput)
}.isInstanceOf(IllegalArgumentException::class.java)
.hasMessage("입력된 횟수가 0 이하입니다.")
}

@Test
fun `입력된 이동 횟수가 숫자가 아니라면 예외가 발생한다`() {
// given
val countInput = "a"

// then
assertThatThrownBy {
MoveCountInput.getMoveCount(countInput)
}.isInstanceOf(IllegalArgumentException::class.java)
.hasMessage("입력된 수가 숫자가 아닙니다.")
}
}
49 changes: 49 additions & 0 deletions src/test/kotlin/racingcar/RacingCarInputTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package racingcar

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

class RacingCarInputTest {

@Test
fun `입력된 자동차 이름이 5자 초과이면 예외가 발생한다`() {
// given
val racingCarNames: List<Car> = listOf(
Car("abcdef"),
Car("abcde")
)

// then
assertThatThrownBy {
RacingCarInput.validate(racingCarNames)
}.isInstanceOf(IllegalArgumentException::class.java)
.hasMessage("입력된 자동차 이름이 5자 초과입니다.")
}

@Test
fun `입력된 자동차 개수가 1개 이하면 예외가 발생한다`() {
// given
val racingCarNames: List<Car> = listOf(Car("abcde"))

// then
assertThatThrownBy {
RacingCarInput.validate(racingCarNames)
}.isInstanceOf(IllegalArgumentException::class.java)
.hasMessage("입력된 자동차의 개수가 1개 이하입니다.")
}

@Test
fun `중복된 자동차 이름이 입력되면 예외가 발생한다`() {
// given
val racingCarNames: List<Car> = listOf(
Car("abcde"),
Car("abcde")
)

// then
assertThatThrownBy {
RacingCarInput.validate(racingCarNames)
}.isInstanceOf(IllegalArgumentException::class.java)
.hasMessage("중복되는 자동차 이름이 있습니다.")
}
}