Skip to content

Commit

Permalink
[1단계 - 자동차 경주 구현] 저문(유정훈) 미션 제출합니다. (#519)
Browse files Browse the repository at this point in the history
* test: 페어와 JUnit 학습하기

* docs: 기능 목록 초안 정리

* feat: 입력받는 기능 추가

* feat: 쉼표를 기준으로 이름을 받아 자동차를 생성하는 기능 추가

* feat: 자동차 이름 길이에 대한 예외처리

* test: 자동차 이름 길이 예외처리에 대한 테스트

* feat: 시도 횟수는 양수로만 가능하도록 예외처리

* feat: 랜덤 값을 받아 자동자 위치를 이동하는 기능 추가

* test: 값에 따른 자동차 위치를 이동하는 테스트

* feat: 최종 우승 자동차를 확인하는 기능 추가

* feat: 최종 결과를 가져오는 기능 추가

* feat: 각 자동차별 경주 실행결과 출력 기능 추가

* feat: 최종 우승 자동차 출력 기능 추가

* feat: 컨트롤러 프로그램 실행 로직 추가

* docs: 기능 목록 정리 최신화

* feat: 자동차 경주 시도 횟수는 1~100으로 제한하는 기능 추가

* refactor: 예외 메시지 추가 및 메소드 분리

* style: 전체적인 코드 스타일 수정

* refactor: 하드 코딩된 값을 제거

* test: CarGroup에 대한 테스트

* test: Car에 대한 테스트

* test: RacingResult에 대한 테스트

* docs: 기능 목록 정리 최신화

* style: 전체적인 코드 스타일 수정

* refactor: 예외 발생 시 그 부분부터 다시 입력을 받는 기능 수정

* refactor: 테스트 코드 접근자 제거

* refactor: RandomNumberGenerator를 NumberGenerator를 implement하도록 구현

* refactor: CarTest에서의 Car를 전진하도록 하는 메서드의 설계 변경으로 인한 코드 수정

* refactor: RacingGame객체의 생성을 controller로 이전

* refactor: RacingGame의 race메서드 로직을 CarGroup으로 이전

* refactor: CarGroup의 setUp메소드가 List를 반환하도록 수정

* refactor: Car클래스 getter위치 변경

* refactor: getter와 혼동될 수 있는 메서드 네이밍 변경

* refactor: Car에서 move의 매개변수를 boolean타입으로 변경

* refactor: 정규표현식 성능 개선을 위해 메서드 변경

* refactor: NumberGenerator인터페이스 삭제 후 RandomNumberGenerator만으로 운영되도록 변경

* refactor: CarTest의 test로직 변경에 따라 DisplayName변경
  • Loading branch information
jeomxon committed Feb 12, 2023
1 parent 5cbd219 commit 9601a85
Show file tree
Hide file tree
Showing 17 changed files with 613 additions and 0 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,42 @@

자동차 경주 미션 저장소

## 기능 목록 정리
- ### 입력받는 기능
- [x] 자동차 이름을 입력받는 기능
- 예외처리
- [x] 쉼표를 기준으로 구분하여 입력을 받아야 한다.
- [x] 자동차 이름에는 중복을 허용하지 않는다.
- [x] 자동차 이동 시도횟수를 입력받는 기능
- 예외처리
- [x] 시도 횟수가 숫자인지 검증
- [x] 최대 시도 횟수를 1~100회로 제한
- ### 출력하는 기능
- [x] 실행결과 문구를 출력하는 기능
- [x] 자동차별 이동 횟수를 출력하는 기능
- [x] 최종 우승자를 출력하는 기능

- ### 메인 기능
- 자동차 경주 게임
- [x] 전체 자동차가 경주하는 기능

- 전체 자동차
- 예외처리
- [x] 전체 자동차 이름에 중복이 있으면 예외처리
- [x] 전체 자동차를 2~50대로 제한

- 자동차
- [x] 자동차는 이름을 가진다.
- [x] 랜덤 값을 받아 전진 혹은 위치 유지
- 예외처리
- [x] 자동차의 이름은 1~5자만 가능하다.

- 경주 결과
- [x] 최종 우승 자동차
- [x] 경주 기록 결과

- [x] 0~9사이의 랜덤 값을 생성하는 기능

## 우아한테크코스 코드리뷰

- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
13 changes: 13 additions & 0 deletions src/main/java/racing/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package racing;

import java.io.IOException;

import racing.controller.RacingGameController;

public class Application {

public static void main(String[] args) throws IOException {
RacingGameController racingGameController = new RacingGameController();
racingGameController.run();
}
}
38 changes: 38 additions & 0 deletions src/main/java/racing/controller/RacingGameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package racing.controller;

import java.io.IOException;

import racing.domain.RacingGame;
import racing.handler.InputHandler;
import racing.view.OutputView;

public class RacingGameController {

private final InputHandler inputHandler;
private final OutputView outputView;

public RacingGameController() {
this.inputHandler = new InputHandler();
this.outputView = new OutputView();
}

public void run() throws IOException {
String[] carNames = inputHandler.readCars();
int movingTrial = inputHandler.readMovingTrial();

RacingGame racingGame = new RacingGame(carNames);

outputView.printNotice();
raceWithHistory(movingTrial, racingGame);
outputView.printWinner(racingGame.produceRacingResult().pickWinner());
}

private void raceWithHistory(int movingTrial, RacingGame racingGame) {
//TODO: 인덱스를 쓰지 않는데 개선할 방법
for (int i = 0; i < movingTrial; i++) {
racingGame.race();

outputView.printRacingResult(racingGame.produceRacingResult().getHistory());
}
}
}
38 changes: 38 additions & 0 deletions src/main/java/racing/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package racing.domain;

public class Car {

private static final int INITIAL_VALUE = 0;
private static final int MINIMUM_LENGTH_OF_CAR_NAME = 1;
private static final int MAXIMUM_LENGTH_OF_CAR_NAME = 5;
private static final String LENGTH_OF_CAR_NAME_ERROR = "[ERROR] 자동차이름의 길이는 1-5자까지 가능합니다.";

private final String name;
private int position;

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

public void move(boolean isMovable) {
if (isMovable) {
this.position++;
}
}

public String getName() {
return name;
}

public int getPosition() {
return position;
}

private void validateLengthOfName(String name) {
if (name.length() < MINIMUM_LENGTH_OF_CAR_NAME || name.length() > MAXIMUM_LENGTH_OF_CAR_NAME) {
throw new IllegalArgumentException(LENGTH_OF_CAR_NAME_ERROR);
}
}
}
50 changes: 50 additions & 0 deletions src/main/java/racing/domain/CarGroup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package racing.domain;

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

//TODO: ERROR 패키지 분리
public class CarGroup {

private static final String DUPLICATED_CAR_NAME_ERROR = "[ERROR] 자동차 이름에는 중복이 허용되지 않습니다.";
private static final String RANGE_OF_CAR_GROUP_ERROR = "[ERROR] 자동차 대수는 2-50대 사이입니다.";
private static final int MINIMUM_NUMBER_OF_CARS = 2;
private static final int MAXIMUM_NUMBER_OF_CARS = 50;

private final List<Car> cars;

public CarGroup(String[] names){
validateDuplicatedName(names);
validateNumberOfCars(names);
this.cars = setUp(names);
}

public void race(boolean isMovable) {
for (Car car : cars) {
car.move(isMovable);
}
}

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

private List<Car> setUp(String[] names) {
return Arrays.stream(names)
.map(Car::new)
.collect(Collectors.toList());
}

private void validateDuplicatedName(String[] names) {
if (names.length != Arrays.stream(names).distinct().count()) {
throw new IllegalArgumentException(DUPLICATED_CAR_NAME_ERROR);
}
}

private void validateNumberOfCars(String[] names) {
if (names.length < MINIMUM_NUMBER_OF_CARS || names.length > MAXIMUM_NUMBER_OF_CARS){
throw new IllegalArgumentException(RANGE_OF_CAR_GROUP_ERROR);
}
}
}
35 changes: 35 additions & 0 deletions src/main/java/racing/domain/RacingGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package racing.domain;

import java.util.LinkedHashMap;
import java.util.Map;

public class RacingGame {

private static final int MOVABLE_CONDITION = 4;

private final CarGroup carGroup;
private final RandomNumberGenerator numberGenerator;

public RacingGame(String[] names) {
this.carGroup = new CarGroup(names);
this.numberGenerator = new RandomNumberGenerator();
}

//TODO: 테스트
public void race() {
carGroup.race(isMovable());
}

public RacingResult produceRacingResult() {
Map<String, Integer> history = new LinkedHashMap<>();
for (Car car : carGroup.getCars()) {
history.put(car.getName(), car.getPosition());
}

return new RacingResult(history);
}

private boolean isMovable() {
return (numberGenerator.generate() >= MOVABLE_CONDITION);
}
}
29 changes: 29 additions & 0 deletions src/main/java/racing/domain/RacingResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package racing.domain;

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

public class RacingResult {

private final Map<String, Integer> history;

public RacingResult(Map<String, Integer> history) {
this.history = history;
}

public Map<String, Integer> getHistory() {
return history;
}

public List<String> pickWinner() {
Integer maxValue = Collections.max(history.values());

return history.entrySet()
.stream()
.filter(entry -> entry.getValue().equals(maxValue))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
}
8 changes: 8 additions & 0 deletions src/main/java/racing/domain/RandomNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package racing.domain;

public class RandomNumberGenerator {

public int generate(){
return (int)(Math.random() * 10);
}
}
60 changes: 60 additions & 0 deletions src/main/java/racing/handler/InputHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package racing.handler;

import java.io.IOException;
import java.util.regex.Pattern;

import racing.view.InputView;

public class InputHandler {

private static final String COMMA = ",";
private static final Pattern REGEX = Pattern.compile("^[0-9]+$");
private static final int MINIMUM_LENGTH_OF_MOVING_TRIAL = 1;
private static final int MAXIMUM_LENGTH_OF_MOVING_TRIAL = 100;
private static final String MOVING_TRIAL_NOT_INTEGER_ERROR = "[ERROR] 시도할 횟수는 숫자만 가능합니다.";
private static final String MOVING_TRIAL_RANGE_ERROR = "[ERROR] 시도할 횟수의 범위는 1이상 100이하만 가능합니다.";

private final InputView inputView;

public InputHandler() {
this.inputView = new InputView();
}

public String[] readCars() throws IOException {
try {
String inputName = inputView.readCarNames();
String[] names = inputName.split(COMMA);

return names;
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
return readCars();
}
}

public int readMovingTrial() throws IOException {
try {
String input = inputView.readMovingTrial();
validateInteger(input);
int movingTrial = Integer.parseInt(input);
validateTrialRange(movingTrial);

return movingTrial;
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
return readMovingTrial();
}
}

private void validateTrialRange(int movingTrial) {
if (movingTrial < MINIMUM_LENGTH_OF_MOVING_TRIAL || movingTrial > MAXIMUM_LENGTH_OF_MOVING_TRIAL) {
throw new IllegalArgumentException(MOVING_TRIAL_RANGE_ERROR);
}
}

private void validateInteger(String movingTrial) {
if (!REGEX.matcher(movingTrial).matches()) {
throw new IllegalArgumentException(MOVING_TRIAL_NOT_INTEGER_ERROR);
}
}
}
14 changes: 14 additions & 0 deletions src/main/java/racing/util/InputUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package racing.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class InputUtil {

public static String readLine() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));

return bufferedReader.readLine();
}
}
23 changes: 23 additions & 0 deletions src/main/java/racing/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package racing.view;

import java.io.IOException;

import racing.util.InputUtil;

public class InputView {

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

public String readCarNames() throws IOException {
System.out.println(CAR_NAME_INPUT_MESSAGE);

return InputUtil.readLine();
}

public String readMovingTrial() throws IOException {
System.out.println(MOVING_TRIAL_INPUT_MESSAGE);

return InputUtil.readLine();
}
}
26 changes: 26 additions & 0 deletions src/main/java/racing/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package racing.view;

import java.util.List;
import java.util.Map;

public class OutputView {
private static final String RESULT_MESSAGE = "\n실행 결과";
private static final String WINNER_MESSAGE = "%s가 최종 우승했습니다.";

public void printNotice() {
System.out.println(RESULT_MESSAGE);
}

public void printRacingResult(Map<String, Integer> history) {
for (String name : history.keySet()) {
Integer positionValue = history.get(name);
System.out.println(name + " : " + "-".repeat(positionValue));
}
System.out.println();
}

public void printWinner(List<String> winners) {
String winnerNames = winners.toString();
System.out.printf(WINNER_MESSAGE, winnerNames.substring(1, winnerNames.length()-1));
}
}
Loading

0 comments on commit 9601a85

Please sign in to comment.