-
Notifications
You must be signed in to change notification settings - Fork 200
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
base: main
Are you sure you want to change the base?
Changes from all commits
2d3f6ed
4b95fc4
147b7a6
873e478
9ac9b99
65c564a
b482ea5
ec18d48
a31b07a
7445b64
c6db72f
83454f2
fecac66
9fe0bdb
58d7331
059a8af
f7e330f
2342982
b30c915
cad52f1
312c494
e67e089
067bed2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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] 기능 구현을 완료한 뒤 테스트 성공하는지 확인한다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package domain | ||
|
||
class Car(private var name: String) { | ||
var moveCount = 0 | ||
|
||
fun moveForward(): Int { | ||
moveCount++ | ||
return moveCount | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 결과를 return하신 이유가 있으신건가요??? |
||
} | ||
|
||
fun moveStop(): Int { | ||
return moveCount | ||
} | ||
Comment on lines
+11
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moveCount 프로퍼티의 getter를 활용하면 해당 함수가 불필요할것 같아요!! |
||
|
||
// 해결하는데 오래 걸린 부분 -> 객체가 저장된 위치를 반환하고 있었기 때문에 객체의 정보를 받기 위해서 사용 | ||
override fun toString(): String { | ||
return "${name}" | ||
} | ||
} |
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CarRacing이라는 클래스 이름으로 의미를 충분히 알 수 있으므로 함수에는 Racing이라는 단어를 빼서 |
||
println() | ||
println(Constant.RESULT_TEXT) | ||
for (playNum in 0..<racing) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 이렇게 쓰다가 이번에
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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>) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 객체지향 프로그래밍을 저도 잘 이해하지 못하고있어서 틀린 답변일 수도 있으나, 현재 CarRaing의 클래스에서는 경주를 하는 것을 판단되어서 우승자를 가리는 로직은 다른 클래스에서 관리를하는게 어떨까요? |
||
var moveCountMap: MutableMap<String, Int> = mutableMapOf() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}") | ||
} | ||
} |
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 = "잘못된 입력 형식입니다. 숫자를 입력해주세요." | ||
} |
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
} |
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
} | ||
} |
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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 문맥에 따라 코드에 공백을 넣는건 어떨까요? |
||
val input = Input() | ||
println(INPUT_CAR_NAME_TEXT) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package domain | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제가 코드리뷰를 깜빡하고 있었네요 죄송합니다. 테스트 코드에 대한 리뷰부터 먼저남길게요. 1주차 해설강의 마지막부분에 테스트코드부분나오는데 그부분 참고하시면 대략적인 감이 잡히실것 같구요, 유튜브 우하한테크코스 채널에 테스트코드 관련 테크톡이 엄청많거든요 그것도 보시면 도움이 많이 되실것같습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
@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") | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name을 가변 변수로 설정하신 이유가 있을까요?👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
처음에 다른 방식으로 이용해 보려고 시도하던 중 사용했던 것이 그대로 남아있는것 같습니다! 그때그때 상황에 맞춰 코드 짜기 바쁜 모습을 반성하게되네요.. 앞으로 더 신경 써보겠습니다🥲