-
Notifications
You must be signed in to change notification settings - Fork 120
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
[부나] 코틀린 자동차 경주 제출합니다. #47
Changes from 26 commits
4d15ac7
2fb4547
d85e64e
69fe601
ca51779
37462f6
8808d37
1d86cdd
e1f5fc1
118d2cb
b826b0e
972d412
7b51f8c
b16841c
e724948
1381ae5
d88ea80
7e42178
0edf628
1ca0cda
4d5d28b
89bfc95
fe11d89
5d67e4e
3571323
39f2cdd
3e46229
c34e4cc
890bef6
8945701
3aa878a
74f7af6
3ab052f
ce2e1bb
efd5d0c
9282f50
35dff55
e45ab6b
4f9ba23
07dcf8e
983fd98
e735d17
8a24159
d9c1cde
3250ce8
c41adbe
aaca844
385e9b7
7012826
810050b
0924fe7
1f76b4e
3783a31
f3d42d2
a2441a2
3966720
f1d7a52
5a27b6d
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,16 @@ | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
end_of_line = lf | ||
indent_size = 4 | ||
indent_style = space | ||
trim_trailing_whitespace = true | ||
insert_final_newline = false | ||
max_line_length = 120 | ||
tab_width = 4 | ||
|
||
[*.{kt,kts}] | ||
disabled_rules=import-ordering | ||
# java.* 패키지를 의존하는 경우 IntelliJ의 Orgarnize Import 기능으로는 알파벳 순서대로 import 구문을 정렬할 수 없다. | ||
# 이는 ktlint의 import-ordering 규칙과 맞지 않는다. | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
### 기능 목록 | ||
- | ||
- 입력 | ||
- 자동차 이름들 입력 | ||
- [x] 입력받은 문자열을 콤마를 기준으로 split | ||
- [x] 앞뒤 공백 제거 | ||
- [X] 각각 0 이하 6자 이상 시 예외 발생 후 재입력 | ||
- [x] 중복이 있을 시 예외 발생 후 재입력 | ||
- 시도 횟수 입력 | ||
- [x] 숫자 형태 확인 | ||
- [x] 0 이하, int 범위 초과 시 예외 발생 후 재입력 | ||
|
||
- 기능 | ||
- 전진 여부 발행 | ||
- [x] 0 ~ 9 무작위 발행 | ||
- [x] 4 이상일 경우 전진 | ||
- [x] 최종 우승자 산출 | ||
- [x] 구현한 기능 통합 | ||
|
||
- 출력 | ||
- [x] 라운드 결과 출력 | ||
- [x] 우승자 출력 | ||
|
||
경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분). | ||
pobi,woni,jun | ||
시도할 횟수는 몇 회인가요? | ||
5 | ||
|
||
실행 결과 | ||
pobi : - | ||
woni : | ||
jun : - | ||
|
||
pobi : -- | ||
woni : - | ||
jun : -- |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
rootProject.name = "kotlin-racingcar" | ||
rootProject.name = "kotlin-racingcar" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package racingcar | ||
|
||
import racingcar.controller.RacingController | ||
|
||
fun main() { | ||
val racingController = RacingController() | ||
racingController.runRacing() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package racingcar.controller | ||
|
||
import racingcar.model.Car | ||
import racingcar.service.RacingService | ||
import racingcar.utils.CAR_NAMES_REQUEST_MESSAGE | ||
import racingcar.utils.ROUNDS_RESULT_NOTIFICATION_MESSAGE | ||
import racingcar.utils.ROUND_COUNT_REQUEST_MESSAGE | ||
import racingcar.utils.Validator | ||
import racingcar.view.InputView | ||
import racingcar.view.OutputView | ||
|
||
class RacingController( | ||
private val inputView: InputView = InputView(Validator()), | ||
private val outputView: OutputView = OutputView(), | ||
private val racingService: RacingService = RacingService(), | ||
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 runRacing() { | ||
val cars = createCars(readCarNames()) | ||
val roundCount = readRoundCount() | ||
|
||
runRounds(roundCount, cars) | ||
|
||
val winners = getWinners(cars) | ||
printWinners(winners) | ||
} | ||
|
||
private fun readCarNames(): List<String> { | ||
outputView.printMessage(CAR_NAMES_REQUEST_MESSAGE) | ||
return inputView.readCarNames() | ||
} | ||
|
||
private fun readRoundCount(): Int { | ||
outputView.printMessage(ROUND_COUNT_REQUEST_MESSAGE) | ||
return inputView.readRoundCount() | ||
} | ||
|
||
private fun printRoundCountRequestMessage() = outputView.printMessage(ROUNDS_RESULT_NOTIFICATION_MESSAGE) | ||
|
||
private fun printRoundResult(cars: List<Car>) = outputView.printRoundResult(cars) | ||
|
||
private fun printWinners(winners: List<Car>) = outputView.printWinners(winners) | ||
|
||
private fun runRounds(roundCount: Int, cars: List<Car>) { | ||
printRoundCountRequestMessage() | ||
repeat(roundCount) { | ||
runRound(cars) | ||
} | ||
Comment on lines
+44
to
+46
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의 역할은 무엇인지, 다음 미션의 MVC적용을 하시면서 고민해보시면 좋을거같아요! |
||
} | ||
|
||
private fun runRound(cars: List<Car>) { | ||
moveCarsRandomly(cars) | ||
printRoundResult(cars) | ||
} | ||
|
||
private fun moveCarsRandomly(cars: List<Car>) { | ||
cars.forEach { car -> | ||
racingService.moveRandomly(car) | ||
} | ||
} | ||
|
||
private fun createCars(carNames: List<String>) = | ||
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. 반환값이 있는 경우에는 가독성 측면에서 항상 반환 타입을 작성해주는 것이 좋을까요?? 현업자의 관점에서 보셨을 때 추천하시는 방식이 있으실까요??🙂 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. 답변해주신 글을 읽어보니 공감되는 부분이 많습니다! |
||
carNames.map { carName -> | ||
racingService.createCar(carName) | ||
} | ||
|
||
private fun getWinners(cars: List<Car>): List<Car> = racingService.getWinners(cars) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package racingcar.model | ||
|
||
import racingcar.utils.Validator | ||
|
||
class Car( | ||
private val name: String, | ||
private var position: Int = 0 | ||
) : Comparable<Car> { | ||
|
||
init { | ||
val validator = Validator() | ||
validator.checkCarNameLength(name) | ||
} | ||
|
||
fun move() = ++position | ||
|
||
override fun compareTo(other: Car) = this.position - other.position | ||
|
||
override fun equals(other: Any?): Boolean { | ||
if (other is Car) { | ||
return this.position == other.position | ||
} | ||
|
||
return false | ||
} | ||
|
||
override fun toString() = name | ||
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. kotlin에서는 따로 getter함수를 생성해주지 않으셔도 됩니다. 한번 공식문서를 참고하시면 좋을거같아요! 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. 오.. 이 부분도 헷갈리던 점이었는데 명쾌한 답변 감사합니다. |
||
|
||
override fun hashCode(): Int { | ||
var result = name.hashCode() | ||
result = 31 * result + position | ||
return result | ||
} | ||
|
||
fun getPositionAsDash() = "-".repeat(position) | ||
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. 매직 리터럴, 매직 넘버를 제거하는 것이 좋아보이네요! |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package racingcar.service | ||
|
||
import racingcar.model.Car | ||
import racingcar.utils.END_RANDOM_MOVEMENT_PROBABILITY | ||
import racingcar.utils.MOVEMENT_PROBABILITY | ||
import racingcar.utils.Random | ||
import racingcar.utils.START_RANDOM_MOVEMENT_PROBABILITY | ||
|
||
class RacingService { | ||
|
||
fun createCar(carName: String) = Car(carName) | ||
|
||
fun moveRandomly(car: Car) { | ||
if (isMove()) { | ||
car.move() | ||
} | ||
} | ||
|
||
private fun isMove(): Boolean { | ||
val random = Random.pickInRange(START_RANDOM_MOVEMENT_PROBABILITY, END_RANDOM_MOVEMENT_PROBABILITY) | ||
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. 외부에서는 랜덤숫자가아닌 isMove만 노출하는군요 👍 Random클래스에 대한 강한 의존이 생겼네요! |
||
if (random < MOVEMENT_PROBABILITY) { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
fun getWinners(cars: List<Car>): List<Car> { | ||
val winnerStandard = cars.max() | ||
return cars.filter { it == winnerStandard } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package racingcar.utils | ||
|
||
object BlankRemover { | ||
fun removeBlank(texts: MutableList<String>) { | ||
for (i in texts.indices) { | ||
texts[i] = removeBlank(texts[i]) | ||
} | ||
} | ||
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. Blank를 지워주는 클래스를 만드셨네요!
|
||
|
||
private fun removeBlank(text: String): String = text.trim() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package racingcar.utils | ||
|
||
const val MIN_CAR_NAME_LENGTH = 1 | ||
const val MAX_CAR_NAME_LENGTH = 5 | ||
|
||
const val MIN_ROUND_COUNT = 1 | ||
const val MAX_ROUND_COUNT = Int.MAX_VALUE | ||
|
||
const val RANDOM_SEED = 10 | ||
|
||
const val START_RANDOM_MOVEMENT_PROBABILITY = 1 | ||
const val END_RANDOM_MOVEMENT_PROBABILITY = 10 | ||
const val MOVEMENT_PROBABILITY = 4 | ||
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. Dimens 파일에 상수를 관리하셨군요 프로그램이 점점 커질수록, 이러한 문자열이 많아져서, 관리되기가 힘들수도 있을거 같아요! 필요한 클래스에서 선언하게 된다면, |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,10 @@ | ||||||||||
package racingcar.utils | ||||||||||
|
||||||||||
import kotlin.random.Random | ||||||||||
|
||||||||||
object Random { | ||||||||||
|
||||||||||
private val random = Random(RANDOM_SEED) | ||||||||||
|
||||||||||
fun pickInRange(start: Int, end: Int) = random.nextInt(start, end + 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.
Suggested change
으로도 간단하게 대체할수 있을거 같아요! |
||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package racingcar.utils | ||
|
||
// Delimiter | ||
const val CAR_NAME_DELIMITER = "," | ||
|
||
// Error | ||
const val CAR_NAME_BOUNDARY_ERROR_MESSAGE = "자동차 이름의 길이는 $MIN_CAR_NAME_LENGTH 이상 $MAX_CAR_NAME_LENGTH 이하로 부탁이요~" | ||
const val DUPLICATED_CAR_NAME_ERROR_MESSAGE = "자동차 이름 중복 빼주세요~" | ||
|
||
const val NOT_INTEGER_TYPE_ERROR_MESSAGE = "숫자 형태로 부탁이요~" | ||
const val ROUND_COUNT_BOUNDARY_ERROR_MESSAGE = "라운드 횟수는 $MIN_ROUND_COUNT 이상 $MAX_ROUND_COUNT 이하로 부탁이요~" | ||
|
||
// Request Message | ||
const val CAR_NAMES_REQUEST_MESSAGE = "경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)." | ||
const val ROUND_COUNT_REQUEST_MESSAGE = "시도할 횟수는 몇 회인가요?" | ||
const val ROUNDS_RESULT_NOTIFICATION_MESSAGE = "\n실행 결과" | ||
const val WINNER_NOTIFICATION_MESSAGE = "최종 우승자" | ||
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,32 @@ | ||
package racingcar.utils | ||
|
||
class Validator { | ||
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. Validator을 분리하셨군요! 클래스이름과 역할이 너무 광범위해지는 단점도 있는거 같아요! 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. Validator에 대한 의존도가 높아지는 것을 고려하지 못했던 것 같습니다 🥲 |
||
fun checkCarNames(names: List<String>): List<String> { | ||
checkDuplicatedCarNames(names) | ||
return names | ||
} | ||
|
||
private fun checkDuplicatedCarNames(names: List<String>) { | ||
if (names.toSet().size != names.size) { | ||
throw IllegalArgumentException(DUPLICATED_CAR_NAME_ERROR_MESSAGE) | ||
} | ||
} | ||
|
||
fun checkRoundCount(input: String): Int { | ||
val roundCount = input.toIntOrNull() | ||
|
||
requireNotNull(roundCount) { NOT_INTEGER_TYPE_ERROR_MESSAGE } | ||
|
||
if (roundCount !in MIN_ROUND_COUNT..MAX_ROUND_COUNT) { | ||
throw IllegalArgumentException(ROUND_COUNT_BOUNDARY_ERROR_MESSAGE) | ||
} | ||
|
||
return roundCount | ||
} | ||
|
||
fun checkCarNameLength(name: String) { | ||
if (name.length !in MIN_CAR_NAME_LENGTH..MAX_CAR_NAME_LENGTH) { | ||
throw IllegalArgumentException(CAR_NAME_BOUNDARY_ERROR_MESSAGE) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package racingcar.view | ||
|
||
import racingcar.utils.BlankRemover | ||
import racingcar.utils.CAR_NAME_DELIMITER | ||
import racingcar.utils.Validator | ||
|
||
class InputView(private val validator: Validator) { | ||
|
||
fun readCarNames(): List<String> { | ||
val names = readln().split(CAR_NAME_DELIMITER).toMutableList() | ||
BlankRemover.removeBlank(names) | ||
|
||
return validator.checkCarNames(names) | ||
} | ||
|
||
fun readRoundCount() = validator.checkRoundCount(readln()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package racingcar.view | ||
|
||
import racingcar.model.Car | ||
import racingcar.utils.WINNER_NOTIFICATION_MESSAGE | ||
|
||
class OutputView { | ||
fun printMessage(message: String) = println(message) | ||
|
||
fun printRoundResult(cars: List<Car>) { | ||
cars.forEach { car -> | ||
println("$car : ${car.getPositionAsDash()}") | ||
} | ||
println() | ||
} | ||
|
||
fun printWinners(winners: List<Car>) { | ||
println("$WINNER_NOTIFICATION_MESSAGE: ${winners.joinToString(", ")}") | ||
} | ||
} |
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.
마지막에 개행문자를 넣어주세요
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.
너무 시원시원하네요😭👏
마지막에 개행 문자를 추가하는 것에 대해 의문이 있었는데 일부 환경에서는 EOF이 없으면 컴파일 에러가 발생하는군요!
추가로 .editorconfig를 수동으로 작성해서 추가해야 하는 줄 알았는데 Cmd + N 단축키로 쉽게 생성할 수 있다는 것도 알게 되었습니다!
감사합니다~🙏