-
Notifications
You must be signed in to change notification settings - Fork 56
[자동차 경주] 김선아 미션 제출합니다. #42
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
base: main
Are you sure you want to change the base?
Changes from all commits
81fdce7
abbd4b6
142e9be
3a55345
babf823
ffbf7c3
18205b7
007151d
4b7e120
40f2bc2
d23d278
d287083
69377b6
9040022
7e2c0c3
f0d50fe
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 |
|---|---|---|
| @@ -1 +1,75 @@ | ||
| # kotlin-racingcar-precourse | ||
| # [2주차] 자동차 경주 🚗 | ||
| 초간단 자동차 경주 게임을 구현한다. | ||
|
|
||
| ## 기능 목록 📝 | ||
| #### 1. 애플리케이션을 실행하면 안내 문구를 출력하고, 자동차 이름과 시도할 횟수를 입력받는다. | ||
| ``` | ||
| 경주할 자동차 이름을 입력하세요.(이름은 쉼표(,)기준으로 구분) | ||
| pobi,woni,jun | ||
| 시도할 횟수는 몇 회인가요? | ||
| 5 | ||
| ``` | ||
| #### 2. 쉼표(,)를 기준으로, 자동차 이름을 분리한다. | ||
| #### 3. 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우 전진하고 4 미만일 경우 멈춘다. | ||
| #### 4. 실행 결과 첫 출력시 '실행 결과' 문구를 출력하고, 한 횟수가 끝날 때마다 결과를 출력한다. | ||
| ``` | ||
| 실행 결과 | ||
| pobi : - | ||
| woni : | ||
| jun : - | ||
|
|
||
| ``` | ||
| #### 5. 게임을 완료한 후 누가 우승했는지 출력하고, 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다. | ||
| ``` | ||
| 최종 우승자 : pobi, jun | ||
| ``` | ||
| #### 6. 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException 을 발생시킨 후 애플리케이션은 종료된다. | ||
| - 자동차 이름이 5자를 초과인 경우 | ||
| - 쉼표로 구분한 각 이름이 비어있거나 공백일 경우 (예: pobi,,jun) | ||
| - 시도할 횟수가 1 미만의 숫자인 경우 (또는 숫자가 아닌 경우) | ||
|
|
||
| #### 7. 기능 목록이 정상 작동하는지 테스트 코드로 확인한다. | ||
|
|
||
|
|
||
| ## 설계 🧩 | ||
| #### main | ||
| ``` | ||
| racingcar | ||
| ├── controller | ||
| │ └── RaceController.kt | ||
| │ | ||
| ├── domain | ||
| │ ├── Car.kt | ||
| │ └── Race.kt | ||
| │ | ||
| ├── port | ||
| │ ├── InputPort.kt | ||
| │ └── OutputPort.kt | ||
| │ | ||
| ├── util | ||
| │ ├── InputParser.kt | ||
| │ ├── InputValidator.kt | ||
| │ └── RandomUtils.kt | ||
| │ | ||
| ├── view | ||
| │ ├── InputView.kt | ||
| │ └── OutputView.kt | ||
| │ | ||
| └── Application.kt (main 함수) | ||
| ``` | ||
| #### test | ||
| ``` | ||
| racingcar | ||
| ├── controller | ||
| │ └── RaceControllerTest.kt | ||
| │ | ||
| ├── domain | ||
| │ ├── CarTest.kt | ||
| │ └── RaceTest.kt | ||
| │ | ||
| └── util | ||
| │ ├── InputParserTest.kt | ||
| │ └── InputValidatorTest.kt | ||
| │ | ||
| └── ApplicationTest.kt | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,18 @@ | ||
| package racingcar | ||
|
|
||
| import racingcar.controller.RaceController | ||
| import racingcar.util.InputParser | ||
| import racingcar.util.InputValidator | ||
| import racingcar.view.InputView | ||
| import racingcar.view.OutputView | ||
|
|
||
| fun main() { | ||
| // TODO: 프로그램 구현 | ||
| val inputPort = InputView | ||
| val outputPort = OutputView | ||
| val inputParser = InputParser | ||
| val inputValidator = InputValidator | ||
|
|
||
| val raceController = RaceController(inputPort, outputPort, inputParser, inputValidator) | ||
|
|
||
| raceController.run() | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package racingcar.controller | ||
|
|
||
| import racingcar.domain.Car | ||
| import racingcar.domain.Race | ||
| import racingcar.port.InputPort | ||
| import racingcar.port.OutputPort | ||
| import racingcar.util.InputParser | ||
| import racingcar.util.InputValidator | ||
|
|
||
| class RaceController( | ||
| private val inputPort: InputPort, | ||
| private val outputPort: OutputPort, | ||
| private val inputParser: InputParser, | ||
| private val inputValidator: InputValidator | ||
| ) { | ||
| fun run() { | ||
| val readCarNames: String? = inputPort.readCarNames() | ||
| val readTryCount: String? = inputPort.readTryCount() | ||
|
|
||
| val carNames = inputParser.parseCarNames(readCarNames ?: "") | ||
| inputValidator.validateCarNames(carNames) | ||
| val cars: List<Car> = carNames.map { Car(it) } | ||
| val tryCount = inputValidator.validateTryCount(readTryCount) | ||
|
|
||
| val race = Race(cars) | ||
|
|
||
| outputPort.printFirstResult() | ||
| repeat(tryCount) { | ||
| race.runOneRound() | ||
|
|
||
| cars.forEach { car -> | ||
| outputPort.printRoundResult(car) | ||
| } | ||
|
|
||
| outputPort.printBlankLine() | ||
| } | ||
|
|
||
| val winners = race.findWinners() | ||
| outputPort.printFinalWinner(winners) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package racingcar.domain | ||
|
|
||
| data class Car(val name: String) { | ||
| var position = 0 | ||
| internal set | ||
|
|
||
| fun move(randomInt: Int) { | ||
| if (randomInt >= 4) { | ||
| position++ | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package racingcar.domain | ||
|
|
||
| import racingcar.util.RandomUtils | ||
|
|
||
| class Race(val cars: List<Car>) { | ||
| fun runOneRound() { | ||
| for (car in cars) { | ||
| car.move(RandomUtils.getRandomInt()) | ||
| } | ||
| } | ||
|
|
||
| private fun findFinalMaxPosition(cars: List<Car>): Int { | ||
| return cars.maxOf { it.position } | ||
| } | ||
|
|
||
| fun findWinners(): List<Car> { | ||
| val maxPosition = findFinalMaxPosition(cars) | ||
| val winners: MutableList<Car> = mutableListOf() | ||
|
|
||
| for (car in cars) { | ||
| if (car.position == maxPosition) { | ||
| winners += car | ||
| } | ||
| } | ||
|
|
||
| return winners | ||
| } | ||
|
Comment on lines
+12
to
+27
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.
fun findWinners(): List<Car> {
val maxPosition = cars.maxOf { it.position }
return cars.filter { it.position == maxPosition }
}
Author
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.
filter 가 있었네요! 감사합니다 !! |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package racingcar.port | ||
|
|
||
| interface InputPort { | ||
| fun readCarNames(): String? | ||
| fun readTryCount(): String? | ||
| } | ||
|
Comment on lines
+3
to
+6
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.
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. Console.readLine()에 빈 값을 입력해도 ""가 입력되서 굳이 nullable하게 처리하지 않아도 괜찮을 것 같습니다.
Author
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.
Console.readLine 에서 사용자가 입력을 하지 않고, null 을 반환하면 읽어오는 순간에 바로 오류가 날까봐 널을 허용했어요! 대신 받아오는 과정에서 널일 경우 공백으로 처리해서 오류를 내보는 방식으로 작성했습니다!
Author
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.
터미널에서 Ctrl+D 나 Ctrl+Z 를 입력해서 사용자가 직접 입력의 끝을 알리면 null 로 들어온다고 하더라고요! 혹시나 해서 넣은 부분입니다! |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package racingcar.port | ||
|
|
||
| import racingcar.domain.Car | ||
|
|
||
| interface OutputPort { | ||
| fun printFirstResult() | ||
| fun printRoundResult(car: Car) | ||
| fun printBlankLine() | ||
| fun printFinalWinner(winners: List<Car>) | ||
| } | ||
|
Comment on lines
+5
to
+10
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. 인터페이스를 만드신 이유가 궁금해요!
Author
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.
RaceController 테스트를 위해 만들었어요! 단순 입력과 출력은 테스트의 필요성을 느끼지 못해서 따로 테스트를 만들지 않았습니다. 따라서 전체적인 경주 진행을 알아보는데 전체적인 출력보다는 필요한 부분만 출력하면 될 것으로 여겨져서 만들었습니다! |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package racingcar.util | ||
|
|
||
| object InputParser { | ||
| const val COMMA = ',' | ||
|
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. delimeter를 따로 자료형으로 저장하는거 좋네요! |
||
|
|
||
| fun parseCarNames(input: String): List<String> { | ||
| return input.split(COMMA) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package racingcar.util | ||
|
|
||
| object InputValidator { | ||
| fun validateCarNames(readCarNames: List<String>) { | ||
| for (carName in readCarNames) { | ||
|
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. for문보다 forEach로 람다식 활용하는것은 어떨까요!?
Author
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.
foreach 가 더 가독성이 좋아보일 것 같기도 하네요! 감사합니다! |
||
| if (carName.length > 5) { | ||
| throw IllegalArgumentException("${carName}이 5자를 초과합니다.") | ||
| } | ||
| if (carName.isBlank()) { | ||
| throw IllegalArgumentException("입력받은 자동차 이름이 공백입니다.") | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fun validateTryCount(readTryCount: String?): Int { | ||
| val tryCount = readTryCount?.toIntOrNull() ?: throw IllegalArgumentException("입력받은 수가 유효하지 않은 값입니다.") | ||
|
|
||
| if (tryCount < 0) { | ||
| throw IllegalArgumentException("입력받은 수가 음수입니다.") | ||
| } | ||
|
|
||
| return tryCount | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package racingcar.util | ||
|
|
||
| import camp.nextstep.edu.missionutils.Randoms | ||
|
|
||
| object RandomUtils { | ||
| fun getRandomInt(): Int { | ||
| return Randoms.pickNumberInRange(0, 9) | ||
| } | ||
|
Comment on lines
+6
to
+8
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. 0과 9를 상수로 선언하면 좋을 것 같습니다!
Author
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.
작은 부분이라 신경을 못 썼는데, 감사합니다!! |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package racingcar.view | ||
|
|
||
| import camp.nextstep.edu.missionutils.Console | ||
| import racingcar.port.InputPort | ||
|
|
||
| object InputView : InputPort { | ||
| override fun readCarNames(): String? { | ||
| println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,)기준으로 구분)") | ||
|
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. 입력과 출력이 같이되고있어서 단일 책임 원칙에 어긋나는 것 같습니다! 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. 이번 주 공통피드백에서도 언급은 됐지만, 상수 정의하는것도 고민해보셔야 할 것 같습니다!
Author
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.
controller 를 좀 더 깔끔히 작성하고 싶어서 고민했던 부분인데, 확실히 단일 책임 원칙엔 어긋나는 것 같습니다..! 감사합니다 |
||
| return Console.readLine() | ||
| } | ||
|
|
||
| override fun readTryCount(): String? { | ||
| println("시도할 횟수는 몇 회인가요?") | ||
| return Console.readLine() | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package racingcar.view | ||
|
|
||
| import racingcar.domain.Car | ||
| import racingcar.port.OutputPort | ||
|
|
||
| object OutputView : OutputPort { | ||
| const val BAR = "-" | ||
|
|
||
| override fun printFirstResult() { | ||
| printBlankLine() | ||
| println("실행 결과") | ||
| } | ||
|
|
||
| override fun printRoundResult(car: Car) { | ||
| val positionBar = BAR.repeat(car.position) | ||
|
|
||
| println("${car.name} : $positionBar") | ||
| } | ||
|
|
||
| override fun printBlankLine() { | ||
| println() | ||
| } | ||
|
|
||
| override fun printFinalWinner(winners: List<Car>) { | ||
| val winnersName = winners.joinToString(", ") { it.name } | ||
|
|
||
| println("최종 우승자 : $winnersName") | ||
| } | ||
| } |
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.
controller에서 출력의 책임을 가지고 있는 것처럼 느껴져요!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.
출력의 책임을 좀 더 분리하는 게 좋겠네요! 감사합니다 !