Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[자동차 경주] 한선규 미션 제출합니다. #1458

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
94 changes: 93 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,93 @@
# java-racingcar-precourse
프로그램 설명
초간단 자동자 경주 게임.
사용자가 입력한 자동차 이름과 이동 횟수를 이용하여 경주를 진행한다.
경주가 끝난 후 우승자를 판단한다.
경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)
pobi,woni,jun
시도할 횟수는 몇 회인가요?
5

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

pobi : --
woni : -
jun : --

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

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

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

최종 우승자 : pobi, jun
기능 요구사항
입출력 관련
입력
📌 자동차 이름을 입력 받는다.
📌 자동자 이름은 쉼포(,)를 기준으로 구분한다.
📌 자동차 이름은 5 이하의 길이만 가능하다.
자동차 이름은 1 이상의 길이만 가능하다.
자동차 이름은 중복되지 않는다.
자동차 이름은 공백이 없는 문자열이어야 한다.
자동차 이름은 대소문자를 구분한다.
자동차 이름은 앞뒤 공백은 없어야 한다.
자동차는 최대 10대 까지만 가능하다.
pobi,woni,jun
📌 이동 횟수를 입력 받는다.
이동 횟수는 1 이상의 정수만 가능하다.
이동 횟수는 0 이하의 정수는 입력할 수 없다.
이동 횟수는 '2147483647 이하의 정수(int 범위)' 만 가능하다.
이동 횟수는 공백이 없는 문자열이어야 한다.
이동 횟수는 숫자로만 구성된 문자열이어야 한다.
5
📌 사용자 입력이 잘못된 경우 IllegalArgumentException을 발생시킨다.
출력
📌 경기 진행 사항 출력

📌 자동차 이름과 이동거리를 출력한다.
📌 이동거리는 **'-'**로 표시한다.
📌 자동차 이름과 이동거리는 **" : "**로 구분한다.
pobi : --
woni : ----
jun : ---
📌 우숭자 출력

📌 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다.
📌 우승자는 한 명 이상일 수 있다.
📌 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
최종 우승자 : pobi
경주 관련
📌 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
사용자에게 입력 받은 횟수만큼 경주를 진행한다.
경주가 끝난 후 우승자를 판단한다.
사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.

기능 목록
이번 프로젝트에서는 MVC 패턴을 적용했습니다. 그 이유는 '1주차 계산기 프로젝트' 에 비해 출력해야하는 내용들이 많아지고, 출력하는 내용들이 복잡해졌기 때문입니다. 또한, 객체가 많아짐에 다라 객체간의 관계가 복잡해지고, 객체간의 의존성이 높아졌기 때문에 MVC 패턴을 적용했습니다.

이번 프로젝트에서는 TDD 개발 방법론을 적용 했습니다.

테스트 코드 작성 TDD (유닛테스트)
자동자 Model 관련 테스트 코드 작성
경기 Model 관련 테스트 코드 작성
RaceService 관련 테스트 코드 작성 (삭제)
자동자 Model 생성
경기 Model 생성
비즈니르 로직을 위한 Service 클래스 생성 (삭제)
RandomNumberGenerator 구현체 작성
Model 리펙토링
Service 리펙토링 (삭제)
View 생성 (입력, 출력)
Controller 생성
에러 정의 Enum 및 Exception 생성
통합 테스트 코드 작성
리펙토링
16 changes: 14 additions & 2 deletions src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package racingcar;

import racingcar.controller.RacingCarController;
import racingcar.util.RandomNumberGenerator;
import racingcar.util.ThreadLocalRandomNumberGenerator;
import racingcar.view.ConsoleInputProvider;
import racingcar.view.RacingView;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
RacingView racingView = new RacingView(new ConsoleInputProvider());
RandomNumberGenerator randomNumberGenerator = new ThreadLocalRandomNumberGenerator();

RacingCarController racingCarController = new RacingCarController(racingView, randomNumberGenerator);
racingCarController.run();

racingView.viewClose();
}
}
}
28 changes: 28 additions & 0 deletions src/main/java/racingcar/Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package racingcar;

public enum Config {
NUMBER_OF_CAR_LIMIT(10),
RANDOM_NUMBER_BEGIN(0),
RANDOM_NUMBER_END(9),
MOVE_MINIMUM_VALUE(4)
;

private final Object value;


Config(int value) {
this.value = value;
}

public String getString() {
return value.toString();
}

public int getValue() {
if (value instanceof Integer) {
return (int) value;
} else {
throw new IllegalStateException("This config is not an integer");
}
}
}
60 changes: 60 additions & 0 deletions src/main/java/racingcar/controller/RacingCarController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package racingcar.controller;

import java.util.ArrayList;
import java.util.List;
import racingcar.dto.UserInputDTO;
import racingcar.entity.Car;
import racingcar.entity.Race;
import racingcar.exception.ExceptionUtils;
import racingcar.exception.GeneralError;
import racingcar.util.RandomNumberGenerator;
import racingcar.view.RacingView;

public class RacingCarController {
final private RacingView racingView;
final private RandomNumberGenerator randomNumberGenerator;

public RacingCarController(RacingView racingView, RandomNumberGenerator randomNumberGenerator) {
this.racingView = racingView;
this.randomNumberGenerator = randomNumberGenerator;
}

public void run() {
UserInputDTO userInput = getUserInput();

List<String> carNames = List.of(userInput.carNames().split(","));
int roundCount = Integer.parseInt(userInput.roundCount());

Race race = new Race(createCars(carNames));
startRace(roundCount, race);

List<String> winners = race.getWinners();
racingView.printWinners(winners);
}

private void startRace(int roundCount, Race race) {
racingView.printRaceBegin();
for (int i = 0; i < roundCount; i++) {
race.runSingleRound(this.randomNumberGenerator);
racingView.printRoundResult(race.getRaceHistory().getLastRound());
}
}

private List<Car> createCars(List<String> carNames) {
List<Car> cars = new ArrayList<>();
for (String carName : carNames) {
cars.add(new Car(carName));
}
return cars;
}

private UserInputDTO getUserInput() {
UserInputDTO userInputDTO = new UserInputDTO(racingView.inputCarNames(), racingView.inputRoundCount());
if (!userInputDTO.isValid()) {
ExceptionUtils.throwIllegalArgException(GeneralError.EMPTY_INPUT);
}
return userInputDTO;
}


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

public class UserInputDTO {
String carNames;
String roundCount;

public UserInputDTO(String carNames, String roundCount) {
this.carNames = carNames;
this.roundCount = roundCount;
}

private UserInputDTO() {
}

public String carNames() {
return carNames;
}

public String roundCount() {
return roundCount;
}

public boolean isValid() {
return carNames != null && roundCount != null && !carNames.isEmpty() && !roundCount.isEmpty();
}
}
44 changes: 44 additions & 0 deletions src/main/java/racingcar/entity/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package racingcar.entity;

import racingcar.Config;
import racingcar.exception.CarNameValidationError;
import racingcar.exception.ExceptionUtils;

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

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

}

public String getName() {
return this.name;
}

public int getPosition() {
return this.position;
}

public void move(int randomNumber) {
if (randomNumber >= Config.MOVE_MINIMUM_VALUE.getValue()) {
this.position++;
}
}

private void validate(String name) {
if (name == null) {
ExceptionUtils.throwIllegalArgException(CarNameValidationError.NAME_NULL);
} else if (name.isEmpty()) {
ExceptionUtils.throwIllegalArgException(CarNameValidationError.NAME_EMPTY);
} else if (5 < name.length()) {
ExceptionUtils.throwIllegalArgException(CarNameValidationError.NAME_TOO_LONG);
} else if (name.contains(" ")) {
ExceptionUtils.throwIllegalArgException(CarNameValidationError.NAME_CONTAINS_SPACE);
}
}

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

import java.util.List;
import racingcar.Config;
import racingcar.exception.ExceptionUtils;
import racingcar.exception.RaceValidationError;
import racingcar.util.RandomNumberGenerator;

public class Race {
private final List<Car> cars;
private final RaceHistory raceHistory;

private Race() {
this.cars = null;
this.raceHistory = null;
}

public Race(List<Car> cars) {
this.cars = cars;
this.raceHistory = new RaceHistory();
validate(cars);
}

public void runSingleRound(RandomNumberGenerator randomNumberGenerator) {
for (Car car : this.cars) {
car.move(randomNumberGenerator.pickRandomNumberInRange(Config.RANDOM_NUMBER_BEGIN.getValue(),
Config.RANDOM_NUMBER_END.getValue()));
}
this.raceHistory.addRound(this.cars);
}

public RaceHistory getRaceHistory() {
return this.raceHistory;
}

public List<String> getWinners() {
int maxPosition = this.cars
.stream()
.mapToInt(Car::getPosition)
.max()
.orElseThrow(IllegalStateException::new);

return this.cars.stream()
.filter(car -> car.getPosition() == maxPosition)
.map(Car::getName)
.toList();
}

public List<String> getCarNames() {
return this.cars.stream().map(Car::getName).toList();
}

private void validate(List<Car> cars) {
if (cars == null) {
ExceptionUtils.throwIllegalArgException(RaceValidationError.CARS_NULL);
} else if (Config.NUMBER_OF_CAR_LIMIT.getValue() < cars.size()) {
ExceptionUtils.throwIllegalArgException(RaceValidationError.CARS_EXCEED_LIMIT);
} else if (cars.stream().map(Car::getName).distinct().count() != cars.size()) {
ExceptionUtils.throwIllegalArgException(RaceValidationError.CARS_DUPLICATE_NAME);
}
}
}
32 changes: 32 additions & 0 deletions src/main/java/racingcar/entity/RaceHistory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package racingcar.entity;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class RaceHistory {
private final List<Map<String, Integer>> history;

public RaceHistory() {
this.history = new ArrayList<>();
}

public void addRound(List<Car> cars) {
Map<String, Integer> roundState = new HashMap<>();
cars.forEach(car -> roundState.put(car.getName(), car.getPosition()));
this.history.add(roundState);
}

public Map<String, Integer> getRound(int index) {
return this.history.get(index);
}

public List<Map<String, Integer>> getRounds() {
return this.history;
}

public Map<String, Integer> getLastRound() {
return this.history.getLast();
}
}
Loading