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

[자동차 경주] 배현진 미션 제출합니다. #218

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2d3f6ed
feat: edit gitignore
pearhyunjin Oct 31, 2023
4b95fc4
docs: write a list of features in README.md
pearhyunjin Nov 1, 2023
147b7a6
feat: write essential constant in constant file
pearhyunjin Nov 1, 2023
873e478
feat: create car class
pearhyunjin Nov 1, 2023
9ac9b99
docs: edit the feature list in README.md
pearhyunjin Nov 1, 2023
65c564a
feat: create Racing class
pearhyunjin Nov 1, 2023
b482ea5
feat: edit main function with Car class and Racing class
pearhyunjin Nov 1, 2023
ec18d48
docs: edit the feature list in README.md
pearhyunjin Nov 1, 2023
a31b07a
feat: implement racing car movement
pearhyunjin Nov 1, 2023
7445b64
docs: edit the feature list in README.md
pearhyunjin Nov 1, 2023
c6db72f
feat: edit input carname and number of execution
pearhyunjin Nov 1, 2023
83454f2
feat: edit constant name
pearhyunjin Nov 1, 2023
fecac66
feat: implement find and print winner
pearhyunjin Nov 1, 2023
9fe0bdb
docs: edit the feature list in README.md
pearhyunjin Nov 1, 2023
58d7331
feat: create Exception file and edit Input file
pearhyunjin Nov 1, 2023
059a8af
fix: delete no use code
pearhyunjin Nov 1, 2023
f7e330f
docs: edit the feature list in README.md
pearhyunjin Nov 1, 2023
2342982
fix: delete unnecessary comments
pearhyunjin Nov 1, 2023
b30c915
docs: edit the feature list in README.md
pearhyunjin Nov 1, 2023
cad52f1
feat: create CarRacing class file to divide function
pearhyunjin Nov 1, 2023
312c494
feat: create test code
pearhyunjin Nov 1, 2023
e67e089
fix: edit test code
pearhyunjin Nov 1, 2023
067bed2
fix: edit test code
pearhyunjin 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
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ build/
!**/src/main/**
!**/src/test/**

### Local configuration file ###
local.properties

### Kotlin ###
*.class

### Log ###
*.log

### Android Studio ###
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json
*.hprof

### macOS ###
.DS_Store

Expand Down
53 changes: 53 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 미션 - 자동차 경주

## 기능 목록

- [x] 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- [x] 몇 번의 이동을 할지 사용자로부터 입력받는다.
- [x] 입력받은 횟수만큼 각 자동차는 전진하거나 멈춘다.
- [x] 자동차에 부여할 이름을 사용자로부터 입력받는다.
- [x] 자동차 이름은 쉼표를 기준으로 구분한다.
- [x] 자동차 이름은 5자 이하만 가능하다.
- [x] 전진하는 자동차 출력할때 이름 함께 출력한다.
- [x] 0~9 사이에서 구한 무작위 값이 4이상일 경우 전진한다.
- [x] 자동차 게임 완료
- [x] 최종 우승자를 알려준다.
- [x] 우승자 한 명 이상일 수 있다.
- [x] 우승자 여럿일 경우 쉼표로 구분한다.
- [x] 사용자 입력값이 잘못되었을 경우 IllegalArgumentException을 발생시킨다.
- [x] IllegalArgumentException 발생하면 애플리케이션 종료된다.
- [x] 이름을 잘못 입력한 경우
- [x] 입력값이 없는 경우
- [x] 5자 이상을 입력한 경우
- [x] 쉼표 이외의 기호를 입력한 경우
- [x] 중복된 이름을 입력한 경우
- [x] 횟수를 잘못 입력한 경우
- [x] 숫자가 아닌 값을 입력한 경우
- [x] 입력값이 없는 경우

## 프로그래밍 요구 사항

- [x] Kotlin 1.9.0에서 실행 가능해야 한다.
- [x] Java 코드가 아닌 Kotlin 코드로만 구현해야 한다.
- [x] 프로그램 실행의 시작점은 `Application`의 `main()`이다.
- [x] `build.gradle(.kts)`을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다.
- [x] [Kotlin 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/main/styleguide/kotlin) 가이드를 준수하며 프로그래밍한다.
- [x] 프로그램 종료 시 System.exit()를 호출하지 않는다.
- [x] 프로그램 구현이 완료되면 ApplicationTest의 모든 테스트가 성공해야 한다.
- [x] 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다.
- [x] indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- [ ] 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- [ ] JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
- [x] `camp.nextstep.edu.missionutils`에서 제공하는 `Randoms` 및 `Console` API를 사용하여 구현해야 한다.
- [x] Random 값 추출은 `camp.nextstep.edu.missionutils.Randoms`의 `pickNumberInRange()`를 활용한다.
- [x] 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다.

## 과제 진행 요구 사항

- [x] 미션은 [kotlin-racingcar-6](https://github.com/woowacourse-precourse/kotlin-racingcar-6) 저장소를 Fork & Clone해 시작한다.
- [x] 기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리해 추가한다.
- [x] Git의 커밋 단위는 앞 단계에서 `docs/README.md`에 정리한 기능 목록 단위로 추가한다.
- [x] [커밋 메시지 컨벤션](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 가이드를 참고해 커밋 메시지를 작성한다.
- 과제 진행 및 제출 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고한다.
- [x] 요구 사항에 명시된 출력값 형식을 지키도록 한다.
- [x] 기능 구현을 완료한 뒤 테스트 성공하는지 확인한다.
19 changes: 19 additions & 0 deletions src/main/kotlin/domain/Car.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package domain

class Car(private var name: String) {
Copy link

Choose a reason for hiding this comment

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

name을 가변 변수로 설정하신 이유가 있을까요?👀

Copy link
Author

Choose a reason for hiding this comment

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

처음에 다른 방식으로 이용해 보려고 시도하던 중 사용했던 것이 그대로 남아있는것 같습니다! 그때그때 상황에 맞춰 코드 짜기 바쁜 모습을 반성하게되네요.. 앞으로 더 신경 써보겠습니다🥲

var moveCount = 0

fun moveForward(): Int {
moveCount++
return moveCount

Choose a reason for hiding this comment

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

결과를 return하신 이유가 있으신건가요???

}

fun moveStop(): Int {
return moveCount
}
Comment on lines +11 to +13

Choose a reason for hiding this comment

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

moveCount 프로퍼티의 getter를 활용하면 해당 함수가 불필요할것 같아요!!


// 해결하는데 오래 걸린 부분 -> 객체가 저장된 위치를 반환하고 있었기 때문에 객체의 정보를 받기 위해서 사용
override fun toString(): String {
return "${name}"
}
}
62 changes: 62 additions & 0 deletions src/main/kotlin/domain/CarRacing.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package domain

import camp.nextstep.edu.missionutils.Randoms

class CarRacing {
fun startRacingPlay(cars: List<Car>, racing: Int) {
Copy link

Choose a reason for hiding this comment

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

CarRacing이라는 클래스 이름으로 의미를 충분히 알 수 있으므로 함수에는 Racing이라는 단어를 빼서 CarRacing.startPlay()로 호출하면 더 간결하게 표현할 수 있을 것 같습니다. 1주차 공통 피드백에서 '축약하지 않는다' 부분을 다시 한 번 읽어보시면 좋을 것 같아요!

println()
println(Constant.RESULT_TEXT)
for (playNum in 0..<racing) {
Copy link

Choose a reason for hiding this comment

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

저도 이렇게 쓰다가 이번에 repeat() 라는 함수를 알게되어서 사용해봤는데 더 편하고 읽기쉽더라구요.

repeat(racingl) {
   racingPlay(cars)
   println()
}

racingPlay(cars)
println()
}
winnerJudge(cars)
}

private fun racingPlay(car: List<Car>) {
car.forEach { name -> moveForwardOrStop(name) }
}

private fun moveForwardOrStop(name: Car) {
val moveCount = createPlayRandomNum()
if (moveCount >= Constant.FORWARD_CONDITION_STANDARD) {
moveForward(name)
} else {
moveStop(name)
}
}

private fun createPlayRandomNum(): Int =
Randoms.pickNumberInRange(Constant.FORWARD_CONDITION_MIN, Constant.FORWARD_CONDITION_MAX)
Copy link

Choose a reason for hiding this comment

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

단일 표현식 함수를 중괄호 빼고 표현하니까 더 간결해보이네요. 배워갑니다.👍


private fun moveForward(name: Car) {
val moveCount = name.moveForward()
val named = name.toString()
println("$named : ${move(moveCount)}")
}

private fun moveStop(name: Car) {
val moveCount = name.moveStop()
println("$name : ${move(moveCount)}")
}

private fun move(moveCount: Int): String = Constant.FORWARD_NOTATION.repeat(moveCount)

private fun winnerJudge(cars: List<Car>) {

Choose a reason for hiding this comment

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

객체지향 프로그래밍을 저도 잘 이해하지 못하고있어서 틀린 답변일 수도 있으나, 현재 CarRaing의 클래스에서는 경주를 하는 것을 판단되어서 우승자를 가리는 로직은 다른 클래스에서 관리를하는게 어떨까요?

var moveCountMap: MutableMap<String, Int> = mutableMapOf()
Copy link

Choose a reason for hiding this comment

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

이번 2주차 공통 피드백에서 '변수 이름에 자료형은 사용하지 않는다'라는 피드백이 있었습니다. 사실 저도 이때까지 어겨왔던 규칙이라...3주차에는 변수명을 좀 더 고심해서 짤 예정입니다.

cars.forEach { car -> moveCountMap[car.toString()] = car.moveCount }
val map = moveCountMap.toList().sortedByDescending { it.second }.toMap() as MutableMap
val max = map.values.max()
val maxKey: MutableList<String> = mutableListOf()
for ((key, value) in map) {
if (value == max) {
maxKey.add(key)
}
}
outputWinner(maxKey.joinToString(", "))
}

private fun outputWinner(max: String) {
println("${Constant.FINAL_WINNER}${max}")
}
}
23 changes: 23 additions & 0 deletions src/main/kotlin/domain/Constant.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package domain

object Constant {
const val INPUT_CAR_NAME_TEXT = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"
const val INPUT_TRY_NUM_TEXT = "시도할 횟수는 몇 회인가요?"
const val RESULT_TEXT = "실행 결과"
const val FINAL_WINNER = "최종 우승자 : "

const val FORWARD_NOTATION = "-"
const val NAME_DIVISION_NOTATION = ","

const val FORWARD_CONDITION_MIN = 0
const val FORWARD_CONDITION_MAX = 9
const val FORWARD_CONDITION_STANDARD = 4
const val CAR_NAME_MAX = 5
const val MIN_NUM_RANGE_ASCII = 48
const val MAX_NUM_RANGE_ASCII = 57

const val WRONG_NAME_LENGTH_EXCEPTION = "잘못된 길이의 입력값 입니다. 5자 이하로 입력해주세요"
const val WRONG_NOTATION_EXCEPTION = "잘못된 표기 방식입니다. 쉼표(,)를 이용해 주세요"
const val NO_EXIST_INPUT_EXCEPTION = "입력값이 존재하지 않습니다."
const val WRONG_NUM_EXCEPTION = "잘못된 입력 형식입니다. 숫자를 입력해주세요."
}
44 changes: 44 additions & 0 deletions src/main/kotlin/domain/Exception.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package domain

import domain.Constant.CAR_NAME_MAX
import domain.Constant.MAX_NUM_RANGE_ASCII
import domain.Constant.MIN_NUM_RANGE_ASCII
import domain.Constant.NO_EXIST_INPUT_EXCEPTION
import domain.Constant.WRONG_NAME_LENGTH_EXCEPTION
import domain.Constant.WRONG_NOTATION_EXCEPTION
import domain.Constant.WRONG_NUM_EXCEPTION

class Exception {
fun wrongNameException(inputName: String) {
if (inputName.isEmpty()) throw IllegalArgumentException(NO_EXIST_INPUT_EXCEPTION)

Choose a reason for hiding this comment

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

피드백 보셔서 이미 아시겠지만 require함수를 사용하면 좋을것같습니다!

}

fun nameLengthException(inputName: String) {
val name = inputName.split(",")
for (index in name.indices) {
if (name[index].length > CAR_NAME_MAX) throw IllegalArgumentException(WRONG_NAME_LENGTH_EXCEPTION)
}
}

fun nameNotationException(inputName: String) {
if (!inputName.contains(",")) throw IllegalArgumentException(WRONG_NOTATION_EXCEPTION)
}

fun wrongNumException(inputNum: String) {
if (!isNum(inputNum)) throw IllegalArgumentException(WRONG_NUM_EXCEPTION)
}

fun isNum(answer: String): Boolean {
answer.forEach { char ->
val charConvertToInt = char.digitToIntOrNull()
val charConvertToCode = char.code
if (charConvertToCode > MAX_NUM_RANGE_ASCII || charConvertToCode < MIN_NUM_RANGE_ASCII) {
throw IllegalArgumentException(WRONG_NUM_EXCEPTION)
}
if (charConvertToInt == null) {
throw IllegalArgumentException(NO_EXIST_INPUT_EXCEPTION)
}
}
return true
}
}
21 changes: 21 additions & 0 deletions src/main/kotlin/domain/Input.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package domain

import camp.nextstep.edu.missionutils.Console
import domain.Constant.NAME_DIVISION_NOTATION

class Input {

Choose a reason for hiding this comment

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

input이 입력을 받는 곳인줄알았는데 검증을 하는 곳인것 같아서 이름을 Validator등의 검증을 나타내는 단어를 사용하면 어떨까요?

private val exception = Exception()
fun inputName(): List<String> {
val inputCarName = Console.readLine()
exception.wrongNameException(inputCarName)
exception.nameLengthException(inputCarName)
exception.nameNotationException(inputCarName)
return inputCarName.split(NAME_DIVISION_NOTATION).map { it.trim() }
}

fun inputExecutionNumber(): Int {
val inputExecutionNum = Console.readLine()
exception.wrongNumException(inputExecutionNum)
return inputExecutionNum.toInt()
}
}
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 domain.Car
import domain.CarRacing
import domain.Constant
import domain.Constant.INPUT_CAR_NAME_TEXT
import domain.Input

fun main() {
// TODO: 프로그램 구현
val carRacing = CarRacing()
Copy link

@jihyeonbaem jihyeonbaem Nov 5, 2023

Choose a reason for hiding this comment

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

문맥에 따라 코드에 공백을 넣는건 어떨까요?

val input = Input()
println(INPUT_CAR_NAME_TEXT)

Choose a reason for hiding this comment

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

ui의 로직분리를 하라고 했으니 ui로직을 담당하는 곳을 따로 만들어서 관리하면 좋을것같아요!

val cars = input.inputName().map { Car(it) }
cars.forEach { car -> car.hashCode() }
println(Constant.INPUT_TRY_NUM_TEXT)
val racingNum = input.inputExecutionNumber()
carRacing.startRacingPlay(cars, racingNum)
}
23 changes: 23 additions & 0 deletions src/test/kotlin/domain/InputTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package domain

Choose a reason for hiding this comment

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

제가 코드리뷰를 깜빡하고 있었네요 죄송합니다. 테스트 코드에 대한 리뷰부터 먼저남길게요. 1주차 해설강의 마지막부분에 테스트코드부분나오는데 그부분 참고하시면 대략적인 감이 잡히실것 같구요, 유튜브 우하한테크코스 채널에 테스트코드 관련 테크톡이 엄청많거든요 그것도 보시면 도움이 많이 되실것같습니다.

Copy link
Author

Choose a reason for hiding this comment

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

감사합니다! 1주차 해설강의를 보긴했는데 테크톡도 참고해봐야겠네요

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

class InputTest {
Copy link

Choose a reason for hiding this comment

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

InputTest인 만큼 레이싱 횟수 입력에 대한 테스트도 같이 작성해보시는건 어떨까요? 또 자동차가 전진할 때나 우승자가 나올 때, 우승자가 2명 이상일 때 등 작은 단위로 나눠서 테스트를 더 만들어보시면 좋을 것 같습니다.🙌

@Test
fun car입력값5자이하() {
val exception = Exception()
val result = "hyunjin"
assertThrows<IllegalArgumentException>{
exception.nameLengthException(result)
}
}

@Test
fun 쉼표로구분() {
val input = "bae.hyun/jin%bae*hyun^jin"
val result = input.split(",")
assertThat(result).contains("bae.hyun/jin%bae*hyun^jin")
}
}