-
Notifications
You must be signed in to change notification settings - Fork 56
[자동차 경주] 염수환 미션 제출합니다. #56
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
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,93 @@ | ||
| # kotlin-racingcar-precourse | ||
| --- | ||
|
|
||
| ## 기능 구현 목록 | ||
|
|
||
| - 경주할 자동차 이름과 시도할 횟수 입출력 구현 | ||
| - 쉼표를 기준으로 구분하며 이름이 5자 이하인지 | ||
| - 몇 번의 이동을 할 것인지 입력 | ||
| - 잘못된 값을 입력할 경우 에러 메시지 출력 | ||
|
|
||
| - Random 값 추출 후 전진 및 정지 구현 | ||
| - 0에서 9사이의 숫자 중 4 이상일 경우 전진 | ||
| - 전진 상태를 자동차 각각 '-'로 나타냄 | ||
| - 입력했던 이동 횟수를 반복 | ||
|
|
||
| - 최종 우승자를 안내 구현 | ||
| - 최종 우승자가 여러 명일 경우 공동 우승자를 모두 출력 | ||
|
|
||
| --- | ||
|
|
||
| ## 프로젝트 구조 및 역할 | ||
|
|
||
| - **1. Application.kt** | ||
| - 프로그램 실행 및 흐름 제어 | ||
| - 입력, 검증, 게임 실행, 출력 흐름 제어를 담당 | ||
|
|
||
| - **2. InputView.kt** | ||
| - 자동차 이름과 시도할 횟수를 입력받는 로직 담당 클래스 | ||
|
|
||
| - **3. InputValidator.kt** | ||
| - 사용자의 입력값을 검증하는 유효성 검사 전담 클래스 | ||
| - 자동차 이름이 빈 문자열이 아닌지 | ||
| - 자동차 이름이 5자 이하인지 | ||
| - 자동차 이름이 중복되지 않았는지 | ||
| - 시도 횟수가 양수인지 | ||
|
|
||
| - **4. Car.kt** | ||
| - 자동차 한 대를 표현하는 모델 클래스 | ||
| - 자동차 이름과 위치를 속성으로 가진다 | ||
| - 이동 규칙에 따라 자동차의 위치를 이동 시키는 ``move``함수 포함 | ||
| - 자동차의 현재 위치 상태를 나타내주는 ``carStatus``함수 포함 | ||
|
|
||
| - **5. MoveRule.kt** | ||
| - 자동차가 전진할지 정지할지를 결정하는 이동 규칙 클래스 | ||
| - 랜덤 값이 4이상이면 전진하는 ``canMove``함수 포함 | ||
|
|
||
| - **6. RacingGame.kt** | ||
| - 자동차 경주 게임의 핵심 비즈니스 로직을 담당하는 클래스 | ||
| - 입력받은 자동차 이름으로 여러 개의 ``Car`` 객체를 생성 | ||
| - 매 시도마다 모든 자동차에 대해 이동 규칙을 적용 | ||
| - 게임이 끝난 후에는 우승자 목록을 반환하는 ``resultWinner``함수 포함 | ||
|
|
||
| - **7. OutputResult.kt** | ||
| - 콘솔 출력 전용 클래스 | ||
| - 출력 형식을 관리 | ||
|
|
||
| --- | ||
|
|
||
| ## 학습한 내용 | ||
|
|
||
| - **`map { it.trim() }`** | ||
| - 매핑할 때 `trim()`을 사용하여 공백을 제거 | ||
|
|
||
| - **`require()** | ||
| -** 함수의 인자가 올바른 조건을 만족하는지 확인**할 때 사용 | ||
| - `require(조건) {예외 발생 시 메시지}`에서 조건이 true면 그대로 진행하고 false면 예외 발생 | ||
| - if문보다 짧고 명확하게 사용할 수 있기 때문에 `require()`를 사용 | ||
| - **'함수 인자의 유효성 검사다'**라는 의도가 코드에 들어난다는 장점도 존재 | ||
|
|
||
| - **`forEach`** | ||
| - `cars.forEach { println(it.carStatus()) }`와 같이 사용 | ||
| - **리스트의 각 요소를 순회**하는 역할 | ||
| - 현재 자동차(`it`)의 상태를 문자열로 반환하는 `carStatus()`를 호출하고, 그 결과를 콘솔에 출력 | ||
|
|
||
| - **`filter`** | ||
| - `cars.filter { it.position == maxPosition }`와 같이 사용 | ||
| - 가장 멀리 이동한 자동차만 걸러주는 역할 | ||
|
|
||
| - **`data class`** | ||
| - **값(데이터)** 자체가 중요한 객체에 쓰는 클래스 키워드 | ||
| - 이 프로젝트에서 사용 이유(`Car`클래스) | ||
| - **도메인 모델**로서 이름, 위치가 곧 정체성인 **값 객체**에 딱 맞음 | ||
| - `toString()`/`equals`가 자동이라 비교, 출력이 쉬움 | ||
|
|
||
| - **`object`** | ||
| - 단 하나의 존재하는 **싱글톤 객체**를 만드는 키워드 | ||
| - **전역에서 한 번만**만들어지며 **상태가 거의 없거나 유틸리티 성격**에 적합 | ||
| - 클래스가 아니라 **객체** 그 자체라서 `new`가 필요 없음 | ||
|
|
||
| - **`private`** | ||
| - **가시성(접근 제한) 한정자** | ||
| - **파일/클래스 내부에서만** 접근 가능하게 숨김 | ||
| - `private val rule: MoveRule`와 같이 이동 규칙은 게임 내부에서만 쓰는 도구이며 외부에서 규칙을 변경하지 못하게 하기 위해서 사용 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,33 @@ | ||
| package racingcar | ||
|
|
||
| fun main() { | ||
| // TODO: 프로그램 구현 | ||
| // 차 이름, 시도 횟수 입력 | ||
| val inputName = InputView.inputCarNames() | ||
| val inputTryCount = InputView.inputTryCount() | ||
|
|
||
| // 입력 받은 정보가 올바른지 검증 | ||
| val carNames = InputValidator.validateCarNames(inputName) | ||
| val tryNumber = InputValidator.validateTryCount(inputTryCount) | ||
|
|
||
| // 자동차이름과 이동상태를 매핑 | ||
| val cars = carNames.map { Car(it) } | ||
|
|
||
| // 자동차 객체 리스트 생성 | ||
| val game = RacingGame(cars, MoveRule()) | ||
|
|
||
| println("\n실행 결과") | ||
| //입력 횟수만큼 게임 반복 실행 | ||
| repeat(tryNumber) { | ||
| game.play() | ||
| OutputResult.printCars(cars) | ||
| println() | ||
| } | ||
|
|
||
| // 최종 우승자 출력 | ||
| val winners = game.resultWinner() | ||
| OutputResult.printWinners(winners) | ||
|
|
||
|
|
||
| } | ||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package racingcar | ||
|
|
||
| data class Car( | ||
| val name: String, | ||
| var position: Int = 0 | ||
| ) { | ||
| fun move(rule: MoveRule) { | ||
| if (rule.canMove()) position++ | ||
| } | ||
|
Comment on lines
+7
to
+9
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 carStatus(): String = "$name : ${"-".repeat(position)}" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package racingcar | ||
|
|
||
| object InputValidator { | ||
|
|
||
| fun validateCarNames(input: String): List<String> { | ||
| // 쉼표 기준으로 잘라 공백 제거 후 매핑 | ||
| val names = input.split(",").map { it.trim() } | ||
|
|
||
| require(names.all { it.isNotEmpty() && it.length <= 5 }) { | ||
| "자동차 이름은 1자 이상, 5자 이하만 가능합니다." | ||
| } | ||
|
|
||
| return names | ||
| } | ||
|
|
||
| fun validateTryCount(input: String): Int { | ||
| val count = input.toIntOrNull() ?: throw IllegalArgumentException("시도 횟수는 숫자여야 합니다.") | ||
| require(count > 0) { "시도 횟수는 1회 이상이어야 합니다." } | ||
| return count | ||
| } | ||
| } |
|
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,15 @@ | ||
| package racingcar | ||
|
|
||
| import camp.nextstep.edu.missionutils.Console | ||
|
|
||
| object InputView { | ||
| fun inputCarNames(): String { | ||
|
Comment on lines
+5
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. InputView는 있는데, 따로 OutputView를 안 만드신 이유가 있으실까요? |
||
| println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,) 기준으로 구분)") | ||
| return Console.readLine() | ||
| } | ||
|
|
||
| fun inputTryCount(): String { | ||
| println("시도할 횟수는 몇 회인가요?") | ||
| return Console.readLine() | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package racingcar | ||
|
|
||
| import camp.nextstep.edu.missionutils.Randoms | ||
|
|
||
| class MoveRule { | ||
| fun canMove(): Boolean { | ||
| val number = Randoms.pickNumberInRange(0, 9) | ||
| return number >= 4 | ||
|
Comment on lines
+7
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, 그리고 4를 상수화하였으면 더 가독성 좋은 코드가 되었을 것 같아요! |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package racingcar | ||
|
|
||
| object OutputResult { | ||
| fun printCars(cars: List<Car>) { | ||
| // forEach : 리스트의 각 요소를 순회 | ||
| cars.forEach { println(it.carStatus()) } | ||
| } | ||
|
|
||
| fun printWinners(winners: List<String>) { | ||
| print("최종 우승자 : ${winners.joinToString(", ")}") | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package racingcar | ||
|
|
||
| class RacingGame( | ||
| private val cars: List<Car>, | ||
| private val rule: MoveRule | ||
| ) { | ||
| fun play() { | ||
| cars.forEach { it.move(rule) } | ||
| } | ||
|
|
||
| fun resultWinner(): List<String> { | ||
| // 가장 멀리 이동한 자동차의 값을 반환 | ||
| val maxPosition = cars.maxOf { it. position } | ||
| // 가장 멀리 이동한 자동차의 이름만 매핑 | ||
| return cars.filter { it.position == maxPosition }.map { it.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.
커밋 단위가 크다고 여겨집니다! 한 기능에 한 커밋씩으로 나눠보면 어떨까요?
깜빡하고 커밋을 하지 않고 많은 구현을 하셨을 때 fork 같은 깃 클라이언트를 사용하면, 다시 파일별로 선택해서 커밋하실 수도 있어요!