Skip to content
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
ce8c73c
docs(README): 기능 요구사항 README 작성
rlatneorp Oct 23, 2025
05f734d
feat(input): input 메서드 구현
rlatneorp Oct 23, 2025
36f53af
feat(processCarNaming): 자동차 이름 분류
rlatneorp Oct 24, 2025
40055bc
fix(processCarNaming): 자동차 이름 분류 함수 수정
rlatneorp Oct 24, 2025
7891178
docs(README): 예외처리 항목 추가
rlatneorp Oct 24, 2025
9981224
feat(validateNames): 차 이름 판별 함수 생성
rlatneorp Oct 24, 2025
2f77b20
refacor(validateNames): 자동차 이름 판별 함수 수정
rlatneorp Oct 24, 2025
721c898
feat(processor): 레이싱 경주에 필요한 함수 생성
rlatneorp Oct 24, 2025
5ed9cfc
feat(processor): 레이싱 경주에 필요한 함수 생성
rlatneorp Oct 26, 2025
8b7878d
fix(수정): 함수 이름 변경
rlatneorp Oct 26, 2025
cd7ad15
feat(수정): 레이싱 관련 함수 생성
rlatneorp Oct 26, 2025
ea8e372
fix(수정): 레이싱 관련 함수 수정
rlatneorp Oct 26, 2025
49d6ab2
feat(bar): - 모양 생성 함수
rlatneorp Oct 26, 2025
50d2d04
refactor(validate): 예외발생 수정
rlatneorp Oct 27, 2025
83a4636
refactor(time): 횟수 세팅하는 함수 수정
rlatneorp Oct 27, 2025
27d7447
refactor(judgeFourMoreRole): 횟수 세팅하는 함수 수정
rlatneorp Oct 27, 2025
40a6a4c
refactor(carNameSetting): 자동차 이름 생성 예외적용
rlatneorp Oct 27, 2025
72871a6
fix(error): 굳이 필요없는 예외발생 삭제
rlatneorp Oct 27, 2025
a7b1a95
fix: 안 쓰는 함수 삭제 및 컨벤션 다듬기
rlatneorp Oct 27, 2025
ae67b87
test(추가): test 예외 코드 추가
rlatneorp Oct 27, 2025
82112e6
fix(delete): 안 쓰는 함수 삭제
rlatneorp Oct 27, 2025
cb5c76a
docs(fix): README.md에 함수 사용 이유 추가 뒤 수정
rlatneorp Oct 27, 2025
f5b722e
docs(fix): README.md 오타 수정
rlatneorp Oct 27, 2025
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
125 changes: 125 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,126 @@
# kotlin-racingcar-precourse
## 기능 요구 사항 리스트

### 입력

자동차 이름 입력

- 쉼표로 구분
- 5자 이하만 가능

시도할 횟수 입력

- 정수로 입력

### 처리

0에서 9까지 무작위 값을 구한 후 4 이상일 경우만 전진

4 미만이면 정지 후 다음 턴

우승자는 한 명 이상 가능

우승자가 나올때 까지 진행

### 출력

차수별 실행 결과 출력

-를 이용하여 진척도 나타내기

최종 우승자 출력

- 공동인 경우 , 쉼표를 이용하여 구분

### 예외발생

- 사용자의 입력값이 잘못될 경우 IllegalArgumentException 예외 발생 후 종료
1. 이름이 5자 초과일 경우
2. 이름을 구분 지을때 쉼표가 아닌 다른 문자가 적힐 경우
3. 몇 번 이동할지 정할때 숫자가 0이하일 경우
4. 아무런 입력이 없을 때
5. 이름이 알파벳이 아닐때

## 작성한 코드의 사용 이유

### 1. 자동차 이름 입력 및 저장

```kotlin
fun carNameSetting() {
val carNames = processCarNaming() // input().split(",")
validateNamesLength(carNames)
validateNamesType(carNames)

for (carName in carNames) {
cars.add(carName)
carRunStatus[carName] = 0
}
}
```

- `validate`~ 함수들은 자동차 이름을 설정할 때 예외처리를 위함

### 2. 자동차 이름 유효성 검사

```kotlin
fun validateNamesLength(names: List<String>) {
if (!names.all { it.length <= 5 }) {
throw IllegalArgumentException("이름은 5자 이하여야 합니다.")
}
if (!names.all { it.isNotEmpty() }) {
throw IllegalArgumentException("이름을 비워둘 수 없습니다.")
}
}
```

- `names.all` 코틀린의 컬렉션 함수 `all`을 사용한 이유는 모든 요소가 조건`it.length <= 5`을 만족하는지 검사
- `it.isNotEmpty()`는 비어있는 문자열이 있는지 검사하기 위해 사용

### 3. 시도 횟수 입력 및 검증

```kotlin
fun timeSetting() {
val inputTime = input()
movingTime = inputTime.toIntOrNull()
?: throw IllegalArgumentException("숫자만 가능합니다.")
if (movingTime <= 0) {
throw IllegalArgumentException("횟수는 1 이상이어야 합니다.")
}
}
```

- `inputTime.toIntOrNull()` `toIntOrNull()`은 숫자가 아니면 null 을 반환하여 엘비스 연산자와 함께 예외를 발생
- `?:` 엘비스 연산자. `toIntOrNull()`이 null 을 반환할 경우 `throw` 구문이 실행되어 예외를 발생

### 4. 핵심 레이싱 로직 (전진 및 상태 누적)

```kotlin
fun judgeFourMoreRole(car: String) {
val randomNumber = pickRandomNumber()
if (randomNumber >= 4) {
val currentStatus = carRunStatus.getOrDefault(car, 0)
carRunStatus[car] = currentStatus + 1
}
}

```

- `getOrDefault` 는 맵에 Key가 없더라도 null 대신 기본값 0을 반환 받음
- `carRunStatus[car] = currentStatus + 1` 자동차의 bar 전진 상태가 누적

### 5. 최종 우승자 판별

```kotlin
fun winner() {
val maxBar = carRunStatus.values.maxOrNull() ?: 0
val winners = carRunStatus
.filter { it.value == maxBar }
.keys

println("최종 우승자 : ${winners.joinToString(", ")}")
}

```

- `maxOrNull()` 맵이 비어있을 경우 `null`을 반환할 수 있어`?: 0`으로 처리
- `.filter { it.value == maxBar }filter`를 사용해 맵의 모든 항목 중 최대거리와 같은 경우 반환
120 changes: 120 additions & 0 deletions src/main/kotlin/racingcar/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,125 @@
package racingcar

import camp.nextstep.edu.missionutils.Console
import camp.nextstep.edu.missionutils.Randoms
import kotlin.collections.all
import kotlin.text.all

var cars = mutableListOf<String>()
var movingTime: Int = 0
var carRunStatus = mutableMapOf<String, Int>()

fun main() {
// TODO: 프로그램 구현
game()
}

fun game() {
println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)")
carNameSetting()
println("시도할 횟수는 몇 회인가요?")
timeSetting()
println()
println("실행 결과")
stepControl()
winner()
}

fun input(): String {
return Console.readLine()
}

fun processCarNaming(): List<String> {
return input().split(",")
}

fun validateNamesLength(names: List<String>) {
if (!names.all { it.length <= 5 }) {
throw IllegalArgumentException("이름은 5자 이하여야 합니다.")
}
if (!names.all { it.isNotEmpty() }) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

!names.all { it.isNotEmpty() }이 이중 부정(! + isNotEmpty) 형태라
코드를 읽을 때 _"이름이 모두 빈 값이 아닌게 아니라면~"_으로 해석되어 가독성이 조금 떨어질 수 있을 것 같습니다.

동작은 동일하지만 names.any { it.isEmpty() }를 사용하면
"이름이 하나라도 빈 값이라면~" 으로 해석되어 의도가 더 명확하게 드러날 것 같습니다!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

아...이런 부분은 생각도 못했어요. all, any 등 가독성에 용이한 활용에 맞게 써야한다고 배워갑니다. 감사합니다!

throw IllegalArgumentException("이름을 비워둘 수 없습니다.")
}
}

fun validateNamesType(names: List<String>) {
for (name in names) {
validateNamesCharType(name)
}
}

private fun validateNamesCharType(name: String) {
if (!name.all { it.isLetter() }) {
throw IllegalArgumentException("이름은 알파벳만 입력 가능합니다.")
}
}
Comment on lines +51 to +55
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

해당 함수에는 private이 있는데 다른 함수에는 private이 없네요. 다른 함수에도 붙이시거나 이 함수에서 private을 제거하면 더 통일성 있는 코드가 될 거 같습니다~

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

맞아요 아직 private 활용도가 이해가 되지 않아, 몇 번 시도를 해봤지만 앞으로는 더 자세히 써야겠다 다짐하게 됩니다.
감사합니다!!


fun pickRandomNumber(): Int {
return Randoms.pickNumberInRange(0, 9)
}

fun timeSetting() {
val inputTime = input()
movingTime = inputTime.toIntOrNull()
?: throw IllegalArgumentException("숫자만 가능합니다.")
if (movingTime <= 0) {
throw IllegalArgumentException("횟수는 1 이상이어야 합니다.")
}
}

fun stepControl() {
for (i in 1..movingTime) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

for문 대신 repeat을 활용할 수도 있을 것 같습니다!

Suggested change
for (i in 1..movingTime) {
repeat(movingTime) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

우와 간편하네요. repeat...정말 좋은 거 같아요!!

racingFourMoreRole()
currentResult()
}
}

fun racingFourMoreRole() {
for (carName in cars) {
judgeFourMoreRole(carName)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

judgeFourMoreRole이라는 함수가 내용이 그리 길지 않아서 따로 함수로 빼기보다 racingFourMoreRole에서 그대로 쓰는 것도 괜찮았을 거 같습니다~

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

일단 리뷰 달아주셔서 감사하다는 말씀 드리고 싶어요!
함수를 나누자는 생각에 너무 과하게 한 거 같네요 ㅎㅎ
좋은 피드백 감사드립니다!!

}
}

fun judgeFourMoreRole(car: String) {
val randomNumber = pickRandomNumber()
if (randomNumber >= 4) {
val currentStatus = carRunStatus.getOrDefault(car, 0)
carRunStatus[car] = currentStatus + 1
}
}

fun currentResult() {
for (carName in cars) {
print("$carName : ")
barCreator(carRunStatus[carName])
}
println()
}

fun barCreator(time: Int?) {
val bar = "-"
for (i in 1..time!!) {
print(bar)
}
println()
}
Comment on lines +91 to +105
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

전진 횟수만큼 "-"를 출력할 때 repeat()을 활용하면 코드가 간결해질 것 같습니다!

Suggested change
fun currentResult() {
for (carName in cars) {
print("$carName : ")
barCreator(carRunStatus[carName])
}
println()
}
fun barCreator(time: Int?) {
val bar = "-"
for (i in 1..time!!) {
print(bar)
}
println()
}
fun currentResult() {
for (carName in cars) {
println("$carName : ${"-".repeat(carRunStatus[carName]!!)}")
}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

일단 리뷰 달아주셔서 정말 감사드린다는 말씀드리고 싶습니다!!
repeat 함수란 게 있었군요! 새롭게 배워가요!
그리고 class랑 파일로 정리해서 코드 작성하는 법에 대해 알아봐야겠어요!
함수명도 동사형으로 쓰는 게 정말 보기에 더 좋고 와닿는 거 같습니다.

정말 감사합니다!!

Comment on lines +99 to +105
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

carNameSetting에서 이미 carNames에 대해 기본값으로 0을 넣어주고 있어서 time의 nullable하지 않을 것 같아요.
차라리 carRunStatus를 순회하며 value에 접근하는 방식으로 수정하는건 어떤가요?


fun carNameSetting() {
val carNames = processCarNaming()
validateNamesLength(carNames)
validateNamesType(carNames)

for (carName in carNames) {
cars.add(carName)
carRunStatus[carName] = 0
}
}

fun winner() {
val maxBar = carRunStatus.values.maxOrNull() ?: 0
val winners = carRunStatus
.filter { it.value == maxBar }
.keys

println("최종 우승자 : ${winners.joinToString(", ")}")
}
35 changes: 35 additions & 0 deletions src/test/kotlin/racingcar/ApplicationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,41 @@ class ApplicationTest : NsTest() {
}
}

@Test
fun `자동차 이름이 비어있을 때 예외`() {
assertSimpleTest {
assertThrows<IllegalArgumentException> { runException("pobi,,woni", "1") }
}
}

@Test
fun `자동차 이름에 문자가 아닌 값이 있을 때 예외`() {
assertSimpleTest {
assertThrows<IllegalArgumentException> { runException("pobi,123", "1") }
}
}

@Test
fun `시도 횟수가 숫자가 아닐 때 예외`() {
assertSimpleTest {
assertThrows<IllegalArgumentException> { runException("pobi,woni", "a") }
}
}

@Test
fun `시도 횟수가 0 이하일 때 예외`() {
assertSimpleTest {
assertThrows<IllegalArgumentException> { runException("pobi,woni", "0") }
}
}

@Test
fun `시도 횟수가 음수일 때 예외`() {
assertSimpleTest {
assertThrows<IllegalArgumentException> { runException("pobi,woni", "-1") }
}
}

override fun runMain() {
main()
}
Expand Down