Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5c25a14
docs: README.md 기능 정의 및 구조 정리
b-jm Oct 25, 2025
0af5fef
feat: 입력 안내 메시지를 상수로 관리하는 InputMessage 클래스 생성
b-jm Oct 25, 2025
18c44e3
feat(Input): 자동차 이름 입력 기능 추가
b-jm Oct 25, 2025
bd594b8
refactor(Input): 자동차 이름 구분 로직 제거 및 문자열 반환으로 수정
b-jm Oct 25, 2025
5a255e1
feat(Input): 시도 횟수 입력 기능 추가
b-jm Oct 25, 2025
fc55172
feat: 예외 메시지를 상수로 관리하는 ErrorMessage 클래스 추가
b-jm Oct 25, 2025
8058942
feat(Controller): 전체 실행 흐름 제어를 위한 RacingController 생성
b-jm Oct 25, 2025
5e86d42
feat(Application): 프로그램 실행을 위한 main 메서드 구현
b-jm Oct 25, 2025
aac16b5
feat(Rounds): 사용자 입력 기반 시도 횟수 생성 및 검증 로직 구현
b-jm Oct 25, 2025
3bccb15
feat(Rounds): 시도 횟수가 양수인지 검증하는 메서드 추가
b-jm Oct 25, 2025
22e3d66
refactor(Rounds): 메서드명을 반환 타입에 맞게 변경
b-jm Oct 25, 2025
077cc8e
feat(Cars): 자동차 이름 입력 및 쉼표(,) 기준 문자열 분리 로직 구현
b-jm Oct 25, 2025
a091380
feat(Cars): 자동차 수 및 이름 중복 검증 로직 추가
b-jm Oct 25, 2025
fbe39ab
feat(Cars): 자동차 이름으로 Car 객체 리스트 생성 기능 추가
b-jm Oct 25, 2025
fa04de8
feat(Car): 자동차 이름 길이(5자 이하) 검증 로직 추가
b-jm Oct 25, 2025
0d8af5c
feat(Car): 자동차 이름의 공백 및 빈 문자열 검증 로직 추가
b-jm Oct 25, 2025
c958d26
feat(Output): 실행 결과 제목 출력 기능 추가
b-jm Oct 25, 2025
278de7c
feat(NumberGenerator): 랜덤 숫자 생성 인터페이스 및 구현체 추가
b-jm Oct 26, 2025
ecffe44
feat(Cars): 모든 자동차를 이동시키는 로직 구현
b-jm Oct 26, 2025
49e8653
refactor(Cars): NumberGenerator를 생성자 주입 방식으로 변경
b-jm Oct 26, 2025
986ef72
feat(Cars): 우승자 판별 및 최대 이동 거리 계산 로직 추가
b-jm Oct 27, 2025
5bbc314
feat(Car): 이동, 결과 문자열 생성 및 우승자 판별 기능 추가
b-jm Oct 27, 2025
cfb2464
feat(OutputMessage): 우승자 출력용 상수 추가
b-jm Oct 27, 2025
1d9c0f0
feat(RacingController): 자동차 경주 전체 실행 및 결과 출력 로직 구현
b-jm Oct 27, 2025
c30d5a4
feat(Race): 자동차 경주 라운드 진행 및 우승자 조회 기능 구현
b-jm Oct 27, 2025
8e5ab64
refactor(Rounds): BigInteger 사용 제거 및 int 기반 라운드 파싱 로직 단순화
b-jm Oct 27, 2025
9a559e5
feat(RaceService): 경주 실행 및 우승자 조회 서비스 로직 구현
b-jm Oct 27, 2025
55d520f
feat(Output): 경주 결과 및 우승자 출력 기능 구현
b-jm Oct 27, 2025
ea0edc1
docs: 기능 구현 상태 업데이트
b-jm Oct 27, 2025
c97a3be
fix(Car): 자동차 이동 조건 수정 (>= → >)
b-jm Oct 27, 2025
21d5ff0
feat(Test): 테스트 코드 추가
b-jm Oct 27, 2025
582426b
test 추가
b-jm 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
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,59 @@
# java-racingcar-precourse


## 구현할 기능 목록

### controller
- [x] 전체 게임의 실행 흐름을 관리한다.
- [x] 사용자 입력을 통해 자동차 이름과 시도 횟수를 전달받는다.
- [x] 입력받은 데이터를 기반으로 경주를 시작한다.
- [x] 경주 결과와 우승자를 출력한다.

### domain
- [x] 자동차를 생성한다.(**Cars**)
- [x] 자동차 이름을 쉼표(,)를 기준으로 구분한다.
- [x] 자동차 이름이 5자 이하인지 확인한다.
- [x] 자동차 이름이 비어 있거나 공백이 포함되어 있지 않은지 확인한다.

- [x] 경주를 위한 유효성을 검증한다.
- [x] 경주에 참여하는 자동차가 둘 이상인지 확인한다.
- [x] 중복된 자동차 이름이 존재하지 않는지 검사한다.


- [x] 0~9 사이의 랜덤 값을 생성한다.(**RandomNumberGenerator**)


- [x] 자동차의 이동을 수행한다.(**Car**)
- [x] 랜덤 값이 4 이상일 경우 자동차를 전진시킨다.


- [x] 시도 횟수의 유효성을 검증한다.(**Rounds**)
- [x] 시도할 횟수는 숫자여야 한다.
- [x] 시도할 횟수는 양수여야 한다.


- [x] 경주 결과를 저장하고 우승자를 판별한다.
- [x] 가장 많이 전진한 자동차를 우승자로 결정한다.
- [x] 여러 명의 우승자가 있을 경우 모두 표시한다.

### service
- [x] 각 라운드마다 자동차들의 이동을 반복 수행한다.
- [x] 시도 횟수에 따라 경주를 자동으로 종료한다.
- [x] 매 라운드의 진행 결과를 누적 저장한다.
- [x] 우승자를 계산한다.

### constant
- [x] Input 관련 안내 메시지 관리한다.
- [x] Output 관련 출력 메시지 관리한다.
- [x] Error 관련 예외 메시지 관리한다.

### view
- [x] 자동차 이름을 입력받는다.


- [x] 시도할 횟수를 입력받는다.


- [x] 경주 진행 결과를 출력한다.
- [x] 각 라운드별 자동차의 이동 결과를 출력한다.
- [x] 최종 우승자를 출력한다.
6 changes: 5 additions & 1 deletion src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package racingcar;

import racingcar.controller.RacingController;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현

new RacingController().run();

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

public class ErrorMessage {

public static final String ERROR_CAR_NAME_EMPTY = "자동차 이름은 비어 있을 수 없습니다.";
public static final String ERROR_CAR_NAME_TOO_LONG = "자동차 이름은 5자 이하만 가능합니다.";
public static final String ERROR_CAR_NAME_BLANK = "자동차 이름은 빈 값을 입력할 수 없습니다.";

public static final String ERROR_CAR_COUNT_TOO_SMALL = "경주에 참여할 자동차는 최소 2대 이상이어야 합니다.";
public static final String ERROR_CAR_NAME_DUPLICATE = "자동차 이름은 중복될 수 없습니다.";

public static final String ERROR_ROUND_NOT_POSITIVE = "시도할 횟수를 숫자로 입력해주세요.";
public static final String ERROR_ROUND_NOT_NUMBER = "시도할 횟수를 양수로 입력해주세요.";

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

public class InputMessage {

public static final String CAR_NAMES_INPUT_MESSAGE = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)";
public static final String TRY_COUNT_INPUT_MESSAGE = "시도할 회수는 몇회인가요?";

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

public class OutputMessage {

public static final String RESULT_TITLE = "\n실행 결과";
public static final String WINNER_MESSAGE = "최종 우승자 : ";

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

import racingcar.domain.*;
import racingcar.service.RaceService;
import racingcar.util.RandomNumberGenerator;
import racingcar.view.Input;
import racingcar.view.Output;

import java.util.List;

public class RacingController {
private final Input input;
private final Output output;
private final NumberGenerator numberGenerator;

public RacingController() {
this.input = new Input();
this.output = new Output();
this.numberGenerator = new RandomNumberGenerator();
}

public void run() {
Cars cars = setupCars();
Rounds rounds = setupRounds();

Race race = new Race(cars, rounds);
RaceService raceService = new RaceService(race);

output.printResultTitle();
int roundCount = race.getRoundCount();

for (int i = 0; i < roundCount; i++) {
raceService.runSingleRound();
output.printCarsStatus(race.getCurrentCars());
}

List<Car> winners = raceService.getWinners();
output.printWinners(winners);
}

private Cars setupCars() {
String rawNames = input.inputCarNames();

return new Cars(rawNames, numberGenerator);
}

private Rounds setupRounds() {
String rawCount = input.inputTryCount();

return new Rounds(rawCount);
}

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

import static racingcar.constant.ErrorMessage.*;

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

private final String name;
private int position = 0;

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

private void validate(String name) {
validateNameLength(name);
validateNoBlank(name);
validateNoEmpty(name);
}

private void validateNameLength(String name) {
if (name.length() >= MAX_NAME_LENGTH) {
throw new IllegalArgumentException(ERROR_CAR_NAME_TOO_LONG);
}
}

private void validateNoBlank(String name) {
if (name.isBlank()) {
throw new IllegalArgumentException(ERROR_CAR_NAME_BLANK);
}
}

private void validateNoEmpty(String name) {
if (name.isEmpty()) {
throw new IllegalArgumentException(ERROR_CAR_NAME_EMPTY);
}
}

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

public String toResultString() {
return name + " : " + "-".repeat(position);
}

public boolean isWinner(int maxPosition) {
return position == maxPosition;
}

public String getName() {
return name;
}

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

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static racingcar.constant.ErrorMessage.*;

public class Cars {
private static final int MINIMUMSIZE_CARS_COUNT = 2;

private List<Car> cars;

private final NumberGenerator numberGenerator;

public Cars(String rawNames, NumberGenerator numberGenerator) {
List<String> carNames = parseNames(rawNames);
validateList(carNames);
this.cars = carNames.stream()
.map(name -> new Car(name))
.toList();
this.numberGenerator = numberGenerator;
}

private List<String> parseNames(String rawNames) {
return Arrays.stream(rawNames.split(",")).toList();
}

private void validateList(List<String> names) {
validateMinimumSize(names);
validateDuplicates(names);
}

private void validateMinimumSize(List<String> names) {
if (names.size() < MINIMUMSIZE_CARS_COUNT) {
throw new IllegalArgumentException(ERROR_CAR_COUNT_TOO_SMALL);
}
}

private void validateDuplicates(List<String> names) {
Set<String> uniqueNames = new HashSet<>(names);
if (uniqueNames.size() != names.size()) {
throw new IllegalArgumentException(ERROR_CAR_NAME_DUPLICATE);
}
}

public void moveAllCars() {
for (Car car : cars) {
int randomNumber = numberGenerator.pickNumber();

car.move(randomNumber);
}
}

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

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

private int findMaxPosition() {
return cars.stream()
.mapToInt(Car::getPosition)
.max()
.orElse(0);
}
}
6 changes: 6 additions & 0 deletions src/main/java/racingcar/domain/NumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package racingcar.domain;

public interface NumberGenerator {

int pickNumber();
}
29 changes: 29 additions & 0 deletions src/main/java/racingcar/domain/Race.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package racingcar.domain;

import java.util.List;

public class Race {
private final Cars cars;
private final Rounds rounds;

public Race(Cars cars, Rounds rounds) {
this.cars = cars;
this.rounds = rounds;
}

public void runSingleRound() {
cars.moveAllCars();
}

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

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

public int getRoundCount() {
return rounds.getCount();
}
}
31 changes: 31 additions & 0 deletions src/main/java/racingcar/domain/Rounds.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package racingcar.domain;

import static racingcar.constant.ErrorMessage.*;

public class Rounds {

private final int count;

public Rounds(String rawCount) {
this.count = parseToInt(rawCount);
validatePositive(count);
}

private int parseToInt(String rawCount) {
try {
return Integer.parseInt(rawCount);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(ERROR_ROUND_NOT_POSITIVE);
}
}

private void validatePositive(int count) {
if (count <= 0) {
throw new IllegalArgumentException(ERROR_ROUND_NOT_NUMBER);
}
}

public int getCount() {
return count;
}
}
23 changes: 23 additions & 0 deletions src/main/java/racingcar/service/RaceService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package racingcar.service;

import racingcar.domain.Car;
import racingcar.domain.Race;

import java.util.List;

public class RaceService {

private final Race race;

public RaceService(Race race) {
this.race = race;
}

public void runSingleRound() {
race.runSingleRound();
}

public List<Car> getWinners() {
return race.findWinners();
}
}
Loading