Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5405b2d
docs(README): 기능 정리 및 예외 조건 등 요구사항 분석 README 추가
Oct 27, 2025
9644837
feat(domain): Car 클래스 구현
Oct 27, 2025
7c0a3c0
feat(domain): Cars 일급 컬렉션 도입 및 리스트 관련 로직 캡슐화
Oct 27, 2025
5d3890d
feat(domain): MovePolicy 인터페이스 정의로 이동 조건 추상화
Oct 27, 2025
a92b8d7
feat(util): 랜덤값 생성기 RandomGenerator 추가
Oct 27, 2025
ad439ca
feat(domain): RandomMovePolicy로 이동 조건 구체화
Oct 27, 2025
4e9b658
feat(domain): WinnerCalculator로 우승 자동차 판단 구현
Oct 27, 2025
dc4acfa
feat(service): RacingService 클래스 구현 및 게임 로직 제공
Oct 27, 2025
5f3238c
feat(view): 사용자에게 입출력 받기 위한 메세지 리소스 추가
Oct 27, 2025
3f5ce98
feat(view): 사용자의 입출력 처리를 위한 view 클래스 구현
Oct 27, 2025
1026c07
feat(controller): RacingController 클래스 구현 및 게임 실행 흐름 관리
Oct 27, 2025
3527baa
feat(error): IllegalArgumentException 발생 시 나오는 에러 메세지 추가
Oct 27, 2025
02a37a4
feat: 프로그램 실행을 위한 진입점 추가
Oct 27, 2025
d43a9f7
test(domain): Car 클래스 단위 테스트 추가
Oct 27, 2025
47b57ee
test(domain): Cars 클래스 단위 테스트 추가
Oct 27, 2025
abbe596
test(domain): WinnerCalculator 클래스 단위 테스트 추가
Oct 27, 2025
67936d1
test(domain): RandomMovePolicy 클래스 단위 테스트 추가
Oct 27, 2025
6cf6583
test(service): RacingService 클래스 단위 테스트 추가
Oct 27, 2025
05ad46f
chore(CHCANGELOG): 작성한 커밋 메세지 전체 내용 추가
Oct 27, 2025
3ab8974
fix(dto): dip 규칙 준수를 위해 car 객체에 대한 dto 추가
Oct 27, 2025
463939e
fix(dto): dip 규칙 준수를 위해 cars 객체에 대한 dto 추가
Oct 27, 2025
21b6292
fix(mapper): Mapper 클래스를 통한 객체 > dto 변환 로직 추가
Oct 27, 2025
1f0ba7f
fix(view): 객체 아닌 dto로 받아 출력하도록 수정
Oct 27, 2025
62ceabb
fix(controller): 객체 dto 변환 후 view에 전달하는 로직 추가
Oct 27, 2025
3030e37
docs(CHANGELOG): view의 domain 참조로 인한 dip 문제 해결 커밋 로직 추가
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
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
- fix(controller): 객체 dto 변환 후 view에 전달하는 로직 추가 (62ceabb)
- fix(view): 객체 아닌 dto로 받아 출력하도록 수정 (1f0ba7f)
- fix(mapper): Mapper 클래스를 통한 객체 > dto 변환 로직 추가 (21b6292)
- fix(dto): dip 규칙 준수를 위해 cars 객체에 대한 dto 추가 (463939e)
- fix(dto): dip 규칙 준수를 위해 car 객체에 대한 dto 추가 (3ab8974)
- chore(CHCANGELOG): 작성한 커밋 메세지 전체 내용 추가 (05ad46f)
- test(service): RacingService 클래스 단위 테스트 추가 (6cf6583)
- test(domain): RandomMovePolicy 클래스 단위 테스트 추가 (67936d1)
- test(domain): WinnerCalculator 클래스 단위 테스트 추가 (abbe596)
- test(domain): Cars 클래스 단위 테스트 추가 (47b57ee)
- test(domain): Car 클래스 단위 테스트 추가 (d43a9f7)
- feat: 프로그램 실행을 위한 진입점 추가 (02a37a4)
- feat(error): IllegalArgumentException 발생 시 나오는 에러 메세지 추가 (3527baa)
- feat(controller): RacingController 클래스 구현 및 게임 실행 흐름 관리 (1026c07)
- feat(view): 사용자의 입출력 처리를 위한 view 클래스 구현 (3f5ce98)
- feat(view): 사용자에게 입출력 받기 위한 메세지 리소스 추가 (5f3238c)
- feat(service): RacingService 클래스 구현 및 게임 로직 제공 (dc4acfa)
- feat(domain): WinnerCalculator로 우승 자동차 판단 구현 (4e9b658)
- feat(domain): RandomMovePolicy로 이동 조건 구체화 (ad439ca)
- feat(util): 랜덤값 생성기 RandomGenerator 추가 (a92b8d7)
- feat(domain): MovePolicy 인터페이스 정의로 이동 조건 추상화 (5d3890d)
- feat(domain): Cars 일급 컬렉션 도입 및 리스트 관련 로직 캡슐화 (7c0a3c0)
- feat(domain): Car 클래스 구현 (9644837)
- docs(README): 기능 정리 및 예외 조건 등 요구사항 분석 README 추가 (5405b2d)
161 changes: 161 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,162 @@
# java-racingcar-precourse

### 🔑 기능 정리

1. 사용자에게 자동차 경주할 자동차 이름들과 경주 차수를 받는다.
2. 경주 차수 동안 n대의 자동차가 조건에 따라, 전진 또는 멈춘 상황을 출력한다.
3. 게임이 끝난 후 가장 멀리 이동해 우승한 자동차를 출력한다.

---

### ✅ 구현 조건

#### 입력 조건

자동차 이름

- [ ] 경주할 자동차 이름과 경주 차수 2가지 입력 필요
- [ ] 자동차 이름은 `,` 기준으로 구분
- [ ] 자동차 이름은 1자 이상 5자 이하만 가능함
- [ ] 사용성을 고려해 자동차 이름에 공백이 있는 경우 없는 것으로 처리함

경주 차수

- [ ] 경주 차수는 숫자여야 함
- [ ] 경주 차수는 양의 정수여야 함
- [ ] 사용자가 잘못된 값을 입력한 경우 `IllegalArgumentException`을 발생 후 종료되어야 함

#### 동작 조건

- [ ] 경주는 1명이서 할 수 없음
- [ ] 입력된 경주 차수 동안 n대의 자동차는 전진 또는 멈출 수 있음
- [ ] 전진 조건: 0 ~ 9 사이에서 추출한 무작위 값이 4 이상인 경우
- [ ] 각 경주마다 각 자동차의 이동 결과를 출력
- [ ] 우승자는 한 명 이상일 수 있음
- [ ] 우승 조건: 가장 많이 전진한 자동차

#### 출력 조건

- [ ] 자동차 이름은 `,` 기준으로 구분
- [ ] 전진하는 자동차를 출력할 때마다, 전진한 자동차 이름과 이동거리를 출력 (ex `pobi : --`)
- [ ] 자동차 경주 게임 완료 후, 누가 우승했는지 출력 (ex `최종 우승자 : pobi`)
- [ ] 공동 우승자의 경우 `,`를 기준으로 출력 (ex `최종 우승자 : pobi, jun`)
- [ ] 우승자 출력 시, 입력 순서로 출력해야 함

---

### 🚨 예외(IllegalArgumentException) 발생 조건

- 잘못된 값을 입력한 경우 예외 발생
- 입력해야 하는 값: `자동차 이름`, `경주 차수`

자동차 이름

1. 자동차 이름이 공백이면 예외 발생
2. 자동차 이름은 5자 초과면 예외 발생
3. 자동차 이름 중복 입력 시 예외 발생
4. 자동차 이름 목록에 이름이 1개일 경우 예외 발생

경주 차수

1. 경주 차수에 공백 입력 시 예외 발생
2. 경주 차수에 숫자가 아닌 값 입력 시 예외 발생(`NumberFormatException`이 발생하는 경우)
3. 경주 차수에 0 입력 시 예외 발생
4. 경주 차수에 음수 입력 시 예외 발생

---

### 🧪 테스트 목록

##### **CarTest**

* [ ] 성공: 이름 길이 1~5자 허용, 생성 가능
* [ ] 성공: 이동 조건 충족 시 위치 1 증가
* [ ] 성공: 이동 조건 미충족 시 위치 유지
* [ ] 실패: 이름이 공백이거나 5자 초과면 예외 발생 → `IllegalArgumentException`

---

##### **CarsTest**

* [ ] 성공: 여러 자동차 동시에 이동 가능
* [ ] 성공: 라운드 진행 시 이동 거리 누적
* [ ] 실패: 자동차 리스트 비어있음 → `IllegalArgumentException`
* [ ] 실패: 자동차 1대만 입력 → `IllegalArgumentException`
* [ ] 실패: 이름 중복 → `IllegalArgumentException`

---

##### **RandomMovePolicyTest**

* [ ] 성공: 랜덤값 4 이상이면 실제 정책에서 true 반환 가능

---

##### **WinnerCalculatorTest**

* [ ] 성공: 최대 이동 거리 계산 및 우승자 필터링
* [ ] 실패: 자동차 리스트 비어있음 → `IllegalArgumentException`

---

##### **RacingServiceTest**

* [ ] 성공: 자동차 이름 입력 후 trim 적용
* [ ] 성공: 양의 정수 경주 차수 설정 가능
* [ ] 성공: 여러 라운드 후 위치 누적
* [ ] 성공: 우승자 리스트 반환, 입력 순서 유지
* [ ] 실패: 자동차 이름 입력 null/공백 → `IllegalArgumentException`
* [ ] 실패: 경주 차수 0, 음수, 숫자 아님 → `IllegalArgumentException`

---

### 📑 구현 순서

#### 1. 기본 세팅

- jdk 21 버전 설정
- java style guide 설정

#### 2. 경주할 자동차 이름, 경주 차수 입력받기

- readLine() 으로 문자열 입력 받기
- 경주할 자동차 이름 및 경주 차수 입력 유효성 검사 (예외 발생 조건 참고)
- 경주할 자동차 이름의 경우 , 기준으로 분할해 입력 처리하기

#### 3. 실행 결과 출력하기

- 0~9 사이의 수를 랜덤하게 뽑고, 4 이상인 경우 전진으로 처리(그 외는 멈춤으로 처리)
- 전진 여부 확인 후 전진한 횟수 증가시키기
- 경주 차수만큼 실행 결과 출력하기

#### 4. 최종 우승자 발표하기

- 1명인 경우 그냥 출력 / 다수인 경우 , 로 구분하여 출력

---

### 📄 예시 입출력

```
경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)
pobi,woni,jun
시도할 횟수는 몇 회인가요?
3

실행 결과
pobi : -
woni :
jun : -

pobi : --
woni : -
jun : --

pobi : ---
woni : --
jun : ---

최종 우승자 : pobi, jun
```

<br/>
13 changes: 12 additions & 1 deletion src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
package racingcar;

import racingcar.controller.RacingController;
import racingcar.domain.RandomMovePolicy;
import racingcar.service.RacingService;
import racingcar.view.InputView;
import racingcar.view.OutputView;

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

import racingcar.domain.Cars;
import racingcar.dto.CarsDto;
import racingcar.mapper.CarsMapper;
import racingcar.service.RacingService;
import racingcar.view.InputView;
import racingcar.view.OutputView;

public class RacingController {

private final InputView inputView;
private final OutputView outputView;
private final RacingService racingService;

public RacingController(InputView inputView, OutputView outputView, RacingService racingService) {
this.inputView = inputView;
this.outputView = outputView;
this.racingService = racingService;
}

public void run() {
// 입력 값 받아오기 (경주할 자동차 이름, 경주 차수)
String namesInput = inputView.readCarNames();
int roundCount = inputView.readRoundCount();

// 입력값 유효성 검증
RacingService.validateInputNamesIsNotNullAndEmpty(namesInput);
RacingService.validateInputRoundCountIsPlus(roundCount);

// 자동차 이름 값 검증 및 Cars 객체 생성
racingService.initCars(namesInput);

// 경주 차수에 따른 결과 출력
outputView.printRoundResultTitle();
for (int i = 0; i < roundCount; i++) {
// 변경된 cars 받아 dto로 변환
Cars cars = racingService.playRound();
CarsDto carsDto = CarsMapper.toDto(cars);

// 경주 라운드 실행 결과 출력
outputView.printRoundResult(carsDto);
}

// 경주 승자 출력
outputView.printWinners(racingService.getWinners());
}
}
42 changes: 42 additions & 0 deletions src/main/java/racingcar/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package racingcar.domain;

import static racingcar.error.ErrorMessages.CAR_NAME_SIZE_INVALID;

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

private static final int MAX_NAME_LENGTH = 5;

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

private Car(String name, int location) {
this.name = name;
this.location = location;
}

private void validateName(String name) {
if (name == null || name.isBlank() || name.length() > MAX_NAME_LENGTH) {
throw new IllegalArgumentException(CAR_NAME_SIZE_INVALID.getMessage());
}
}

public Car move(boolean isMovable) {
if (isMovable) {
return new Car(name, location + 1);
}
return this;
}

Choose a reason for hiding this comment

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

move 할 때 마다 Car와 Cars 객체를 새로 찍어서 반환한 이유가 긍금합니다

Copy link
Author

Choose a reason for hiding this comment

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

불변 객체로 만들고 싶다는 마음에 메모리는 많이 들지만 new를 사용해서 코드를 구성했습니다. 이번 요구 사항에 함수형 설계가 중점으로 나와있어서 불변 객체로 진행하여 함수가 항상 예측 가능하게 동작할 수 있도록 하였습니다. 가변 객체가 되면 외부 상태에 따라 객체의 결과가 변경될 가능성이 높다고 배워 그렇게 진행하였습니다. 좋은 질문 감사합니다😊


public String getName() {
return name;
}

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

import static racingcar.error.ErrorMessages.CARS_ARE_EMPTY;
import static racingcar.error.ErrorMessages.CARS_NAME_DUPLICATED;
import static racingcar.error.ErrorMessages.CARS_SIZE_INVALID;

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

public class Cars {
private final List<Car> cars;

public Cars(List<Car> cars) {
validateListNotNullAndEmpty(cars);
validateInputNameIsMoreThanOne(cars);
validateUniqueNames(cars);
this.cars = cars;
}

private static void validateListNotNullAndEmpty(List<Car> cars) {
if (cars == null || cars.isEmpty()) {
throw new IllegalArgumentException(CARS_ARE_EMPTY.getMessage());
}
}

private static void validateInputNameIsMoreThanOne(List<Car> cars) {
if (cars.size() < 2) {
throw new IllegalArgumentException(CARS_SIZE_INVALID.getMessage());
}
}

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

if (distinctCount != cars.size()) {
throw new IllegalArgumentException(CARS_NAME_DUPLICATED.getMessage());
}
}

public Cars moveAll(MovePolicy movePolicy) {
List<Car> moved = this.cars.stream()
.map(car -> car.move(movePolicy.canMove()))
.collect(Collectors.toList());
return new Cars(moved);
}

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

Choose a reason for hiding this comment

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

순서에 대한 안정성을 위해 Cars 객체를 따로 만든 부분이 좋은 것 같아요

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

public interface MovePolicy {
boolean canMove();
}
14 changes: 14 additions & 0 deletions src/main/java/racingcar/domain/RandomMovePolicy.java

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,14 @@
package racingcar.domain;

import racingcar.util.RandomGenerator;

public class RandomMovePolicy implements MovePolicy {
private static final int MIN = 0;
private static final int MAX = 9;
private static final int MIN_MOVE_TRIGGER = 4;

@Override
public boolean canMove() {
return RandomGenerator.generate(MIN, MAX) >= MIN_MOVE_TRIGGER;
}
}
21 changes: 21 additions & 0 deletions src/main/java/racingcar/domain/WinnerCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package racingcar.domain;

import static racingcar.error.ErrorMessages.CARS_ARE_EMPTY;

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

public class WinnerCalculator {

public static List<String> calculateWinners(Cars cars) {
int maxLocation = cars.getCars().stream()
.mapToInt(Car::getLocation)
.max()
.orElseThrow(() -> new IllegalArgumentException(CARS_ARE_EMPTY.getMessage()));

return cars.getCars().stream()
.filter(car -> car.getLocation() == maxLocation)
.map(Car::getName)
.collect(Collectors.toList());
}
}
Loading