Skip to content

[자동차 경주] 이지섭 미션 제출합니다.#57

Open
Soldbone wants to merge 19 commits into
woowacourse-precourse:mainfrom
Soldbone:Soldbone
Open

[자동차 경주] 이지섭 미션 제출합니다.#57
Soldbone wants to merge 19 commits into
woowacourse-precourse:mainfrom
Soldbone:Soldbone

Conversation

@Soldbone
Copy link
Copy Markdown

📝 기능 목록

경주 생성

  • 자동차 이름 입력 받기

    예외 처리
    • 쉼표가 아닌 문자 중 알파벳이 아닌 문자가 존재하는 경우 예외 발생
    • 자동차 이름이 5자를 초과하는 경우 예외 발생
    • 자동차 이름이 비어 있는 경우 예외 발생
  • 시도할 횟수 입력 받기

    예외 처리
    • 시도 횟수가 비어 있는 경우 0으로 처리. 예외 발생시키지 않음
    • 숫자가 아닌 경우 예외 발생
  • 경주에 참여하는 자동차와 경주 생성

경주 진행

  • 현재 차수가 입력 받은 시도 횟수보다 작은지 확인
    • 시도 횟수를 초과하는 경우 우승자 선정
  • 자동차마다 0~9 사이의 무작위 값 추출
    • 추출된 값이 4 이상인 경우 움직임 데이터 1 증가
  • 자동차마다 해당 차수의 실행 결과 출력
  • 우승자를 List로 반환

경주 결과 출력

  • 최종 우승자 출력

🔃플로우 차트

---
title: 자동차 경주
---
flowchart TD
subgraph INIT[경주 생성]
inputCarName[자동차 이름 입력] -->|입력 조건 예외 처리| inputRound[시도할 횟수 입력]
inputRound --> createCar[경주와 경주의 자동차 생성]
end INIT --> Game

    subgraph Game[경주 진행]
        initCurrentRound[현재 차수 = 1]
        initCurrentRound --> isRoundNotFinished
        isRoundNotFinished{현재 차수 <= 입력된 시도 횟수}
        isRoundNotFinished -- YES --> extractRandomInt
        isRoundNotFinished -- NO -----> winnerSelection["움직임 데이터가 가장 큰 우승자(들) 선정"]
        winnerSelection --> END

        extractRandomInt["자동차마다 0~9 사이의 값 추출"]
        extractRandomInt --> isCanMove

        isCanMove{추출된 값 >= 4}
        isCanMove -- YES --> move
        isCanMove -- NO --> printRound

        move[움직임 데이터 1 증가]
        move --> printRound

        printRound[자동차마다 차수별 실행 결과 출력]
        printRound --> isRoundNotFinished
    end Game --> printResult

    subgraph printResult[결과 출력하기]
        printWinner[최종 우승자 출력]
    end
Loading

Soldbone and others added 19 commits October 26, 2025 18:51
경주 생성과 관련된 기능 추가:
- 입력 및 입력 예외처리
- 경주에 참여하는 자동차와 경주 생성
다음과 같은 이름으로 함수화:
- getInputCarName(): 자동차 이름 입력 및 예외처리
- splitCarName(name): 입력 받은 이름에서 각 자동차 이름 추출 및 예외처리
- getInputRound(): 차수 입력 및 예외처리
작은 오류 수정:
- 초기 차수 수정 과정에서 잘못 수정한 Car.position 초기값 복구
기능 분리
- RacingGame.play() 하위 로직을 함수로 세분화
- RacingGame.play()에서는 경주만 진행하도록 수정
- RacingGame.getWinners() 추가로 RacingGame은 데이터만 제공
- 최종 우승자 출력은 printOutput(List<Car>)에서 담당하도록 분리
테스트를 위한 가시성 수정자 변경:
- 기능 테스트의 대상이 되는 주요 입출력 함수들을 public으로 전환
기능 분리:
- Car 클래스 생성 기능 분리

기능 수정:
- getInputRound()의 반환 타입을 String으로 변경
- getInputCarNames()에서 공백에 따른 에러 메시지 일관성을 위해 require() 순서 조정
- RacingGame.printRoundResult()의 map()을 forEach()로 변경

변수명 변경:
- getInputCarNames()의 hasNonCommaSpecialChar를 hasInvalidChar로 변경
- map()에서 it가 Car인 경우 중 일부를 car로 표기
다음과 같은 기능들의 테스트 코드 수정:
- getInputRound()의 반환값을 String으로 수정
- stringToInt() 테스트 추가 및 splitCarName()과 InputProcessingTest로 묶음
- getInputCarNames()의 require() 순서가 달라짐에 따라 공백 관련 테스트 추가
InputView 추가:
- main/kotlin/racingcar에 view 디렉토리 및 InputView.kt 추가
- 입력에 포함되었던 유효 입력 검사 기능을 축출하여 축소
Application.kt의 불필요한 Console import 제거
validateCarNamesInput() 함수에서 자동차 이름 입력 검증을 담당하도록 수정
로직 변경 사항에 따라 수정:
- InputView를 통해 입력을 받도록 수정
- 입력 시 검증을 하지 않도록 변경됨에 따라 입력 검증하는 테스트를 제거
실수로 반영되지 않은 사항 반영:
- 차수 입력 기능 테스트 중 inputView 누락 수정
- 예외 처리 테스트 제거
InputTest.kt에서 사용하지 않는 assertThrows에 대한 import 제거

import camp.nextstep.edu.missionutils.Console

class InputView {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

일반적인 형식의 class로 만드시는 것보다 object class 형식으로 정의하시면 더 보기 좋은 코드가 될 거 같습니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

InputView만 따로 view 폴더에 빼신 이유가 궁금합니다.

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.

InputView만 따로 view 폴더에 빼신 이유가 궁금합니다.

과제 마지막에 MVC를 조금이라도 적용해보려다 보니 입력 기능만 따로 빼게 되었습니다

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.

일반적인 형식의 class로 만드시는 것보다 object class 형식으로 정의하시면 더 보기 좋은 코드가 될 거 같습니다!

찾아보니 싱글톤 객체라는 개념이 있네요! 감사합니다.

}

fun getWinners(): List<Car> {
check(this.isFinished()) { "게임이 아직 시작되지 않았습니다." }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

check를 통해 모든 라운드가 끝났는지 검사하시는 것 같습니다. 그런데, 이미 play 함수에서 isFinished를 사용해서 반복문을 검사하고 있는데 이 부분이 또 필요한 지 모르겠습니다.

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.

check를 통해 모든 라운드가 끝났는지 검사하시는 것 같습니다. 그런데, 이미 play 함수에서 isFinished를 사용해서 반복문을 검사하고 있는데 이 부분이 또 필요한 지 모르겠습니다.

RacingGame.getWinners() 메서드를 RacingGame.play()를 호출하기 이전에 호출하는 경우가 존재할 수 있다고 생각했습니다.
생각해보니 굳이 한번 더 체크하기보다는 널이나 빈 리스트를 반환해도 될 것 같네요.

0
}

fun printOutput(winners: List<Car>?) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

InputView는 만드셨는데 OutputView는 따로 만드시지 않았네요. printOutput에서 우승자 출력을 결정하시는데 OutputView.kt를 따로 만드셔서 분리하시는 게 좋을 거 같습니다~

Copy link
Copy Markdown

@BaekCCI BaekCCI left a comment

Choose a reason for hiding this comment

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

플로우 차트를 설계해서 구현하신 점이 인상적이네요!

전체적으로 로직이 하나의 파일에 모여 있어서 다소 복잡해 보이는 것 같습니다.
비슷한 역할이나 처리 단위를 가진 함수들을 object나 class로 묶어서 별도의 파일로 분리하면
구조가 명확해지고 코드 가독성도 좋아질 것 같습니다.

2주차 고생하셨습니다!!

Comment on lines +40 to +44
fun stringToInt(roundInput: String): Int = if (roundInput.isNotBlank()) {
roundInput.toIntOrNull() ?: throw IllegalArgumentException("숫자를 입력해주세요.")
} else {
0
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

빈 값을 입력하면 0으로 반환하도록 처리하셨는데,
이렇게 되면 게임 결과 계산 시 모든 유저가 우승으로 처리될 것 같습니다.
해당 케이스를 에러로 처리하지 않고 0을 반환하도록 하신 이유가 궁금합니다!

또한 가독성을 위해서 명시적으로 return을 사용하는 형태로 바꾸면 더 읽기 쉬울 것 같습니다!

Suggested change
fun stringToInt(roundInput: String): Int = if (roundInput.isNotBlank()) {
roundInput.toIntOrNull() ?: throw IllegalArgumentException("숫자를 입력해주세요.")
} else {
0
}
fun stringToInt(roundInput: String): Int {
if (roundInput.isNotBlank()) {
return roundInput.toIntOrNull() ?: throw IllegalArgumentException("숫자를 입력해주세요.")
}
return 0
}

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.

빈 값을 입력하면 0으로 반환하도록 처리하셨는데, 이렇게 되면 게임 결과 계산 시 모든 유저가 우승으로 처리될 것 같습니다. 해당 케이스를 에러로 처리하지 않고 0을 반환하도록 하신 이유가 궁금합니다!

이건 제 요구사항 분석 과정에서 잘못된 판단이 들어간 것 같습니다. 말씀하신 대로 round 입력이 0이거나 들어오지 않을 경우에는 모종의 이유로 게임 개최는 했지만 진행할 수 없는 상황이라고 판단하고 모든 유저가 우승자가 되도록 구현했습니다.

지금 생각해보니 IllegalArgumentException을 발생시키거나 우승자가 없도록 하는 것이 더 올바른 구현일 것 같네요.

fun play() {
println()
println("실행 결과")
while (!this.isFinished()) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

반복문이 돌 때마다 isFinished()로 비교하는 것보다
repeat(round)나 for 문을 활용하면 불필요한 비교 연산을 줄일 수 있을 것 같습니다.
그렇게 하면 currentRound 변수도 없어져서 코드가 좀 더 단순해질 것 같아요!

또한 클래스 내부 메서드 호출 시에 this를 반복적으로 사용하셨는데,
스코프가 명확한 상황에서는 this를 생략해도 되기 때문에
제거하면 코드가 조금 더 깔끔해질 것 같습니다.

Copy link
Copy Markdown

@joon0447 joon0447 left a comment

Choose a reason for hiding this comment

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

안녕하세요! 2주차 과제 고생 많으셨습니다!


import camp.nextstep.edu.missionutils.Console

class InputView {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

InputView만 따로 view 폴더에 빼신 이유가 궁금합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants