Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 입력 기능
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

마크다운 파일로 구현할 것들을 정리해주신 것 좋습니다!

경주할 자동차 이름 입력 받기 (쉼표 구분)
시도할 횟수 입력 받기
# 유효성 검사 기능
자동차 이름이 공백인 경우 IllegalArgumentException 발생
자동차 이름이 5자 초과인 경우 IllegalArgumentException 발생
시도 횟수가 숫자가 아닌 경우 IllegalArgumentException 발생
시도 횟수가 0 이하인 경우 IllegalArgumentException 발생
# 자동차 기능
자동차 이름과 위치(position) 관리
0~9 무작위 값이 4 이상이면 전진
# 게임 진행 기능
입력한 횟수만큼 라운드 반복 실행
매 라운드 결과 출력 (이름 + 진행 막대)
# 우승자 계산 및 출력 기능
최대 위치값을 가진 자동차를 우승자로 선정
공동 우승자 쉼표로 구분하여 출력
5 changes: 3 additions & 2 deletions src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
RacingGame game = new RacingGame();
game.run();
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

코드를 커밋하실 때, 클래스 파일 마지막 줄에는 개행 한 줄 추가하는 것이 권장되고 있습니다
자료 참고 부탁드립니다..!

23 changes: 23 additions & 0 deletions src/main/java/racingcar/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package racingcar;

public class Car {
private final String name;
private int position;

public Car(String name) {
this.name = name;
this.position = 0;
}

public void move() {
position++;
}

public String getName() {
return name;
}

public int getPosition() {
return position;
}
}
30 changes: 30 additions & 0 deletions src/main/java/racingcar/InputHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package racingcar;

import camp.nextstep.edu.missionutils.Console;
import java.util.ArrayList;
import java.util.List;

public class InputHandler {

public List<Car> readCars() {
System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
String input = Console.readLine();
String[] names = input.split(",", -1);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

끝에 인자로 -1 을 넣어준 이유가 있을까요??

return parseCars(names);
}

private List<Car> parseCars(String[] names) {
List<Car> cars = new ArrayList<>();
for (String name : names) {
Validator.validateCarName(name.trim());
cars.add(new Car(name.trim()));
}
return cars;
}
Comment on lines +16 to +23
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

trim() 메소드 활용해주신 것 좋습니다!
입력할 때 "aa, bb, cc" 이런 식으로 중간에 공백 하나씩 넣어서 입력해줄 수도 있거든요


public int readAttemptCount() {
System.out.println("시도할 회수는 몇회인가요?");
String input = Console.readLine();
return Validator.validateAndParseAttemptCount(input);
}
}
29 changes: 29 additions & 0 deletions src/main/java/racingcar/OutputHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package racingcar;

import java.util.List;
import java.util.stream.Collectors;

public class OutputHandler {

public void printRoundResult(List<Car> cars) {
for (Car car : cars) {
System.out.println(car.getName() + " : " + buildProgressBar(car.getPosition()));
}
System.out.println();
}

private String buildProgressBar(int position) {
StringBuilder sb = new StringBuilder();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

StringBuilder 를 사용한 이유가 있을까요??

for (int i = 0; i < position; i++) {
sb.append("-");
}
return sb.toString();
}
Comment on lines +17 to +21
Copy link
Copy Markdown

@JanooGwan JanooGwan Apr 5, 2026

Choose a reason for hiding this comment

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

작성해주신 코드도 하나의 방법이지만,
repeat() 메소드를 활용하면 이를 좀 더 깔끔하게 처리할 수 있습니다!

Suggested change
for (int i = 0; i < position; i++) {
sb.append("-");
}
return sb.toString();
}
return "-".repeat(position);


public void printWinners(List<Car> winners) {
String names = winners.stream()
.map(Car::getName)
.collect(Collectors.joining(", "));
System.out.println("최종 우승자 : " + names);
}
}
Comment on lines +23 to +29
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

스트림 활용해서 잘 해주셨네요! 좋습니다

67 changes: 67 additions & 0 deletions src/main/java/racingcar/RacingGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package racingcar;

import camp.nextstep.edu.missionutils.Randoms;
import java.util.List;
import java.util.stream.Collectors;

public class RacingGame {

private static final int MOVE_THRESHOLD = 4;

private final InputHandler inputHandler;
private final OutputHandler outputHandler;

public RacingGame() {
this.inputHandler = new InputHandler();
this.outputHandler = new OutputHandler();
}
Comment on lines +14 to +17
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

작성해주신 것처럼 내부에서 객체를 만들 수도 있지만,
외부에서 객체를 주입받도록 변경하면 결합도를 낮출 수 있습니다!

Suggested change
public RacingGame() {
this.inputHandler = new InputHandler();
this.outputHandler = new OutputHandler();
}
public RacingGame(InputHandler inputHandler, OutputHandler outputHandler) {
this.inputHandler = inputHandler;
this.outputHandler = outputHandler;
}

나중에 Spring 프레임워크를 학습하면서 배우게 되겠지만, 이것을 제어의 역전(IoC) 이라고 합니다!


public void run() {
List<Car> cars = inputHandler.readCars();
int attemptCount = inputHandler.readAttemptCount();

System.out.println();
System.out.println("실행 결과");

playRounds(cars, attemptCount);

outputHandler.printWinners(findWinners(cars));
}

private void playRounds(List<Car> cars, int attemptCount) {
for (int i = 0; i < attemptCount; i++) {
playOneRound(cars);
outputHandler.printRoundResult(cars);
}
}

private void playOneRound(List<Car> cars) {
for (Car car : cars) {
moveIfPossible(car);
}
}

private void moveIfPossible(Car car) {
if (isMovable()) {
car.move();
}
}

private boolean isMovable() {
return Randoms.pickNumberInRange(0, 9) >= MOVE_THRESHOLD;
}
Comment on lines +44 to +52
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

이 부분은 Car 객체 내부로 옮겨도 좋을 것 같다고 생각이 드는데, 어떻게 생각하시나요??


private List<Car> findWinners(List<Car> cars) {
int maxPosition = findMaxPosition(cars);
return cars.stream()
.filter(car -> car.getPosition() == maxPosition)
.collect(Collectors.toList());
}

private int findMaxPosition(List<Car> cars) {
return cars.stream()
.mapToInt(Car::getPosition)
.max()
.orElse(0);
}
}
27 changes: 27 additions & 0 deletions src/main/java/racingcar/Validator.java
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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,27 @@
package racingcar;

public class Validator {

private static final int MAX_NAME_LENGTH = 5;

public static void validateCarName(String name) {
if (name.isBlank()) {
throw new IllegalArgumentException("자동차 이름은 공백일 수 없습니다.");
}
if (name.length() > MAX_NAME_LENGTH) {
throw new IllegalArgumentException("자동차 이름은 5자 이하만 가능합니다: " + name);
}
}

public static int validateAndParseAttemptCount(String input) {
try {
int count = Integer.parseInt(input.trim());
if (count <= 0) {
throw new IllegalArgumentException("시도 횟수는 1 이상이어야 합니다.");
}
return count;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("시도 횟수는 숫자여야 합니다.");
}
}
Comment on lines +16 to +26
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

그럴 가능성은 낮겠지만, 만약에 시도 횟수에 정수가 아닌 소수가 들어가게 될 경우에는 어떻게 될까요??
이 부분도 같이 생각해보시면 좋을 듯 합니다...!

}
59 changes: 59 additions & 0 deletions src/test/java/racingcar/RacingGameTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package racingcar;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;

class RacingGameTest {

@Test
@DisplayName("자동차가 이동 후 위치가 1 증가한다")
void carMoveTest() {
Car car = new Car("pobi");
car.move();
assertThat(car.getPosition()).isEqualTo(1);
}

@Test
@DisplayName("이동하지 않으면 위치는 0이다")
void carNoMoveTest() {
Car car = new Car("woni");
assertThat(car.getPosition()).isEqualTo(0);
}

@Test
@DisplayName("자동차 이름이 5자를 초과하면 예외가 발생한다")
void invalidCarNameLengthTest() {
assertThatThrownBy(() -> Validator.validateCarName("toolongname"))
.isInstanceOf(IllegalArgumentException.class);
}

@Test
@DisplayName("자동차 이름이 공백이면 예외가 발생한다")
void blankCarNameTest() {
assertThatThrownBy(() -> Validator.validateCarName(" "))
.isInstanceOf(IllegalArgumentException.class);
}

@Test
@DisplayName("시도 횟수가 숫자가 아니면 예외가 발생한다")
void invalidAttemptCountFormatTest() {
assertThatThrownBy(() -> Validator.validateAndParseAttemptCount("abc"))
.isInstanceOf(IllegalArgumentException.class);
}

@Test
@DisplayName("시도 횟수가 0 이하이면 예외가 발생한다")
void invalidAttemptCountRangeTest() {
assertThatThrownBy(() -> Validator.validateAndParseAttemptCount("0"))
.isInstanceOf(IllegalArgumentException.class);
}

@Test
@DisplayName("5자 이하의 유효한 이름은 예외가 발생하지 않는다")
void validCarNameTest() {
assertThatCode(() -> Validator.validateCarName("pobi"))
.doesNotThrowAnyException();
}
}