Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0a48381
docs: README에 기능 목록 추가
ssilverrain Oct 25, 2025
f406be6
feat: 기본 패키지 구성 및 초기 구조 설정
ssilverrain Oct 26, 2025
1b5db61
feat: 자동차 이름 입력 기능 구현
ssilverrain Oct 26, 2025
f5076c9
feat: 자동차 이름 유효성 검증 추가
ssilverrain Oct 26, 2025
a167729
feat: 시도 횟수 입력 기능 구현
ssilverrain Oct 26, 2025
a2bcc91
feat: 시도 횟수 입력값 검증 추가
ssilverrain Oct 26, 2025
26cb5a1
feat: Car 클래스 생성 및 이동 기능 구현
ssilverrain Oct 27, 2025
99cea7e
feat: RandomNumberGenerator 유틸 클래스 추가
ssilverrain Oct 27, 2025
a669b7f
feat: Cars 일급 컬렉션 구현 및 이동 로직 추가
ssilverrain Oct 27, 2025
ac90e8c
feat: 사용자 입력으로 자동차 목록 초기화
ssilverrain Oct 27, 2025
9c65ce3
refactor: 자동차 이름 입력 반환 타입을 List로 변경
ssilverrain Oct 27, 2025
8ac5b6c
refactor: RacingController에 final 필드 적용 및 OutputView 추가
ssilverrain Oct 27, 2025
f4d9fa9
feat: 이동 결과 출력 기능 구현
ssilverrain Oct 27, 2025
cd0a0d7
feat: 전체 시도 횟수만큼 라운드 반복 기능 추가
ssilverrain Oct 27, 2025
4df4983
feat: 최종 우승자 판별 로직 구현
ssilverrain Oct 27, 2025
a6f80f4
feat: 최종 우승자 출력 기능 구현
ssilverrain Oct 27, 2025
947235b
refactor: 자동차 상태 문자열 생성을 Car로 이동
ssilverrain Oct 27, 2025
3a7eb77
test: 자동차 경주 입력 예외 및 정상 흐름 테스트 추가함
ssilverrain Oct 27, 2025
86efdb4
refactor: 우승자 판별 로직 메서드 분리로 가독성 개선
ssilverrain Oct 27, 2025
efc8369
fix: 빈 이름만 입력된 경우 예외 처리 추가함
ssilverrain Oct 27, 2025
c4b3375
refactor: Validator를 util에서 domain 패키지로 이동
ssilverrain Oct 27, 2025
117d6f5
refactor: Car 클래스에서 이름 검증 로직 내부화
ssilverrain Oct 27, 2025
1ec5cd8
feat: Cars 클래스에 중복 이름 검증 기능 추가
ssilverrain Oct 27, 2025
fe3f342
refactor: InputView에 기본 입력 형식 검증 추가
ssilverrain Oct 27, 2025
72ee058
refactor: 시도 횟수를 값 객체로 캡슐화
ssilverrain Oct 27, 2025
7c8c3cd
refactor: Controller를 흐름 제어에만 집중하도록 개선
ssilverrain Oct 27, 2025
4ab1ec6
remove: 사용하지 않는 Validator 클래스 제거
ssilverrain 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
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,52 @@
# java-racingcar-precourse
# 🚗 자동차 경주 – 기능 요구 사항 (커밋 단위)

## 1. 입력 처리
- [ ] 자동차 이름 입력받기
- [ ] 콘솔에서 한 줄 입력으로 이름 목록을 받는다 (예: `pobi,woni,jun`).
- [ ] 이름 구분자는 쉼표(,)만 허용한다.
- [ ] 각 이름은 5자 이하여야 한다 → 아니면 `IllegalArgumentException`.
- [ ] 빈 이름(예: `pobi,,jun`) 또는 공백만 입력된 이름이 있으면 `IllegalArgumentException`.
- [ ] 이동 횟수 입력받기
- [ ] 콘솔에서 한 줄 입력으로 정수를 받는다 (예: `5`).
- [ ] 정수가 아닌 값이면 `IllegalArgumentException`.
- [ ] 음수/0 등 유효하지 않은 시도 횟수면 `IllegalArgumentException`.

---

## 2. 자동차 상태 초기화
- [ ] 입력된 이름들로 자동차 리스트를 생성한다.
- [ ] 각 자동차의 초기 이동거리 = 0 으로 설정한다.

---

## 3. 라운드 진행 (시도 횟수만큼 반복)
- [ ] 매 라운드에서 모든 자동차에 대해 다음을 수행한다.
- [ ] 무작위 정수 0~9 하나를 뽑는다 (`Randoms.pickNumberInRange(0, 9)` 사용).
- [ ] 값이 4 이상이면 해당 자동차의 이동거리를 +1 한다.
- [ ] 값이 3 이하면 이동하지 않는다.
- [ ] 라운드 종료 시 결과 출력
- [ ] 각 자동차에 대해 `이름 : ----` 형식으로 현재까지의 이동거리를 막대(`-`)로 출력한다.
- [ ] 출력은 자동차 입력 순서를 유지한다.
- [ ] 매 라운드 출력 사이에 빈 줄 없이 예시와 동일한 형식으로 누적 결과를 보여준다.
- 예)
```
pobi : --
woni : ---
jun : -
```

---

## 4. 우승자 판별 및 출력
- [ ] 모든 라운드 종료 후 최대 이동거리를 구한다.
- [ ] 최대 이동거리와 같아진 자동차들을 우승자 목록으로 만든다.
- [ ] 우승자가 여러 명일 수 있으며, 이름을 쉼표(,) 로 연결해 출력한다.
- [ ] 출력 형식은 정확히 다음과 같다.
- [ ] 단독 우승: `최종 우승자 : pobi`
- [ ] 공동 우승: `최종 우승자 : pobi, jun`

---

## 5. 예외 처리
- [ ] 입력 형식이 잘못된 경우 즉시 `IllegalArgumentException` 발생.
- [ ] 예외 발생 후 애플리케이션은 종료한다.
5 changes: 4 additions & 1 deletion src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package racingcar;

import racingcar.controller.RacingController;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
RacingController racingController = new RacingController();
racingController.start();
}
}
34 changes: 34 additions & 0 deletions src/main/java/racingcar/controller/RacingController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package racingcar.controller;

import racingcar.domain.AttemptCount;
import racingcar.domain.Car;
import racingcar.domain.Cars;
import racingcar.view.InputView;
import racingcar.view.OutputView;

import java.util.List;

public class RacingController {
private final InputView inputView = new InputView();
private final OutputView outputView = new OutputView();

public void start(){
List<String> carNames = inputView.readCarNames();
int attemptCountInput = inputView.readAttemptCount();

AttemptCount attemptCount = new AttemptCount(attemptCountInput);

List<Car> carList = carNames.stream()
.map(Car::new)
.toList();

Cars cars = new Cars(carList);

outputView.printStartMessage();
for (int i = 0; i < attemptCount.getValue(); i++) {
cars.moveAll();
outputView.printRoundResult(cars);
}
outputView.printWinners(cars.getWinners());
}
}
22 changes: 22 additions & 0 deletions src/main/java/racingcar/domain/AttemptCount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package racingcar.domain;

public class AttemptCount {
private static final int MIN_COUNT = 1;

private final int value;

public AttemptCount(int value) {
validateCount(value);
this.value = value;
}

private void validateCount(int count) {
if (count < MIN_COUNT) {
throw new IllegalArgumentException("시도 횟수는 1 이상이어야 합니다.");
}
}

public int getValue() {
return value;
}
}
35 changes: 35 additions & 0 deletions src/main/java/racingcar/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package racingcar.domain;

public class Car {
private static final int MAX_NAME_LENGTH = 5;
private static final int FORWARD_THRESHOLD = 4;

private final String name;
private int position = 0;

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

private void validateName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("자동차 이름은 공백일 수 없습니다.");
}
if (name.length() > MAX_NAME_LENGTH) {
throw new IllegalArgumentException("자동차 이름은 5자 이하만 가능합니다.");
}
}

public void move(int randomNumber) {
if (randomNumber >= FORWARD_THRESHOLD) position++;
}

public String getName() { return name; }

public int getPosition() { return position; }

public String status() {
return name + " : " + "-".repeat(position);
}
}
59 changes: 59 additions & 0 deletions src/main/java/racingcar/domain/Cars.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package racingcar.domain;

import racingcar.util.RandomNumberGenerator;

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

public class Cars {
private static final int RANDOM_MIN = 0;
private static final int RANDOM_MAX = 9;

private final List<Car> cars;

public Cars(List<Car> cars) {
validateNoDuplicateNames(cars);
this.cars = List.copyOf(cars);
}

private void validateNoDuplicateNames(List<Car> cars) {
long uniqueNameCount = cars.stream()
.map(Car::getName)
.distinct()
.count();

if (uniqueNameCount != cars.size()) {
throw new IllegalArgumentException("자동차 이름은 중복될 수 없습니다.");
}
}

public void moveAll() {
for (Car car : cars) {
int random = RandomNumberGenerator.generateInRange(RANDOM_MIN, RANDOM_MAX);
car.move(random);
}
}

public List<Car> getCars() {
return Collections.unmodifiableList(cars);
}

public List<Car> getWinners() {
int maxPosition = findMaxPosition();
return findCarsWithPosition(maxPosition);
}

private int findMaxPosition() {
return cars.stream()
.mapToInt(Car::getPosition)
.max()
.orElse(0);
}

private List<Car> findCarsWithPosition(int position) {
return cars.stream()
.filter(car -> car.getPosition() == position)
.toList();
}
}
11 changes: 11 additions & 0 deletions src/main/java/racingcar/util/RandomNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package racingcar.util;

import camp.nextstep.edu.missionutils.Randoms;

public final class RandomNumberGenerator {
private RandomNumberGenerator() {}

public static int generateInRange(int start, int end) {
return Randoms.pickNumberInRange(start, end);
}
}
42 changes: 42 additions & 0 deletions src/main/java/racingcar/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package racingcar.view;

import camp.nextstep.edu.missionutils.Console;

import java.util.Arrays;
import java.util.List;

public class InputView {
public List<String> readCarNames(){
System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
String input = Console.readLine();

List<String> carNames = Arrays.stream(input.split(","))
.map(String::trim)
.toList();

validateNotEmpty(carNames);

return carNames;
}

private void validateNotEmpty(List<String> carNames) {
if (carNames.isEmpty()) {
throw new IllegalArgumentException("자동차 이름을 입력해주세요.");
}

if (carNames.stream().allMatch(String::isEmpty)) {
throw new IllegalArgumentException("자동차 이름을 입력해주세요.");
}
}

public int readAttemptCount(){
System.out.println("시도할 횟수는 몇 회인가요?");
String input = Console.readLine();

try {
return Integer.parseInt(input);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("시도 횟수는 정수만 입력할 수 있습니다.");
}
}
}
25 changes: 25 additions & 0 deletions src/main/java/racingcar/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package racingcar.view;

import racingcar.domain.Car;
import racingcar.domain.Cars;

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

public class OutputView {
public void printStartMessage() {
System.out.println("\n실행 결과");
}

public void printRoundResult(Cars cars) {
cars.getCars().forEach(car -> System.out.println(car.status()));
System.out.println();
}

public void printWinners(List<Car> winners) {
String winnerNames = winners.stream()
.map(Car::getName)
.collect(Collectors.joining(", "));
System.out.println("최종 우승자 : " + winnerNames);
}
}
Loading