Skip to content

Commit

Permalink
[스티치] 자동차 경주 게임 미션 코드리뷰 제출합니다. (#93)
Browse files Browse the repository at this point in the history
* feat : 문자열 덧셈 계산기에서 null과 공백 문자열을 0으로 처리하는 기능 구현

1. null과 공백 문자열을 0으로 처리하는 기능을 calculate 메소드에 구현.
2. null과 공백 문자열을 0으로 처리하는 테스트 구현.

* feat : 문자열 덧셈 계산기에서 콤마와 콜론 구분자로 나누어서 더하는 기능 구현

1. 콤마와 콜론 구분자로 나누어서 더하는 기능을 calculate 메소드에 추가.
2. 콤마와 콜론 구분자로 나누어서 더하는 테스트 구현.

* feat : 문자열 덧셈 계산기에서 커스텀 구분자로 나누어서 더하는 기능 구현

1. 커스텀 구분자로 나누어서 더하는 기능을 calculate 메소드에 추가.
2. 커스텀 구분자로 나누어서 더하는 테스트 구현.
3. 리팩토링 진행.

* feat : 문자열 덧셈 계산기에서 숫자 이외의 값 또는 음수에 대해 RuntimeException throw 구현

1. 음수에 대해 RuntimeException throw을 calculate 메소드에 추가.
2. 숫자 이외의 값 또는 음수에 대해 RuntimeException throw 테스트 구현.
3. 리팩토링 진행.

* docs : README.md에 기능 구현 목록 작성

* feat : 자동차의 이름을 쉼표(,)로 나누는 기능 구현

1. StringUtil 클래스에 자동차의 이름을 쉼표(,)로 나누는 메소드 구현.
2. 자동차의 이름을 쉼표(,)로 나누는 테스트 구현.

* feat : 자동차 이름의 길이가 5자를 넘지 않는지 확인하는 기능 구현

1. Name 객체에 자동차 이름의 길이가 5자를 넘지 않는지 확인하는 메소드 구현.
2. 자동차 이름의 길이가 5자를 넘지 않는지 확인하는 테스트 구현.

* feat : 이동 횟수 입력에 대한 유효성을 검증하는 기능 구현

1. MovementNumber 객체에 이동 횟수 입력이 숫자인지 검증하는 메소드 구현.
2. 이동 횟수 입력이 양수인지 검증하는 메소드 구현.
3. 이동 횟수 입력에 대한 유효성을 검증하는 테스트 구현.

* feat : 0에서 9까지의 Random 값을 생성하는 기능 구현

1. RandomGenerator 클래스에 0에서 9까지의 Random 값을 생성하는 메소드 구현.
2. 0에서 9까지의 Random 값을 생성하는 테스트 구현.
3. package 구조 변경.

* feat : Random 값에 따라 전진 여부를 결정하는 기능 구현

1. MovementStrategy 클래스에 Random 값에 따라 전진 여부를 결정하는 메소드 구현.
2. Random 값에 따라 전진 여부를 결정하는 테스트 구현.

* refactor : MovementStrategy 클래스 삭제

* feat : Random 값에 따라 전진 여부를 결정하는 기능 구현

1. Car 객체에 Random 값에 따라 전진 여부를 결정하는 메소드 구현.
2. Random 값에 따라 전진 여부를 결정하는 테스트 구현.

* docs : README.md의 기능 구현 목록 수정

1. 자동차들의 이름 중복에 대한 추가적인 유효성 검사 기능 목록 작성.
2. 자동차의 관리와 이동에 대한 추가적인 기능 목록 작성.

* feat : 자동차가 전진하는 경우 위치를 1 증가시키는 기능 구현

1. Car 객체에 자동차가 전진하는 경우 위치를 1 증가시키는 메소드 구현.
2. 자동차가 전진하는 경우 위치를 1 증가시키는 테스트 구현.
3. 테스트 코드 리팩토링.

* refactor : Car 객체의 테스트 코드 수정

1. Car 객체의 isMove 메소드의 범위지정자를 private으로 변경함에 따른 테스트 코드 수정.

* feat : 자동차의 중복된 이름이 존재하는지 여부를 확인하는 기능 구현

1. Cars 객체에 자동차의 중복된 이름이 존재하는지 여부를 확인하는 메소드 구현.
2. 자동차의 중복된 이름이 존재하는지 여부를 확인하는 테스트 구현.
3. 테스트 코드 리팩토링.
4. 프로덕션 코드의 예외 처리 메시지 추가.

* feat : 자동차의 위치를 문자로 변환하는 기능 구현

1. StringUtil 클래스에 자동차의 위치를 문자로 변환하는 메소드 구현.
2. 자동차의 위치를 문자로 변환하는 테스트 구현.

* feat : 우승한 위치의 자동차인지 확인하는 기능 구현

1. Car 객체에 우승한 위치의 자동차인지 확인하는 메소드 구현.
2. 우승한 위치의 자동차인지 확인하는 테스트 구현.

* docs : README.md에 기능 구현 목록 수정

* feat : 우승한 자동차들을 반환해주는 기능 구현

1. RacingGame 객체에 우승한 자동차들을 반환해주는 메소드 구현.
2. 우승한 자동차들을 반환해주는 테스트 구현.

* refactor : stringCalculator 패키지 추가 및 StringCalculator 명명 수정

1. stringCalculator 패키지를 추가하고 해당 패키지 안에 StringCalculator 이동.

* refactor : StringCalculator 테스트 코드 리팩토링

1. ParameterizedTest를 활용하여 테스트 코드 리팩토링.

* refactor : racingGame 패키지 명명 수정

* feat : 잘못된 위치를 생성하는지 확인하는 기능 구현

1. Position 객체에 잘못된 위치를 생성하는지 확인하는 메소드 구현.
2. 잘못된 위치를 생성하는지 확인하는 테스트 구현.

* refactor : Position 객체를 추가함으로 인한 전체 코드 리팩토링

1. Position 객체를 추가함으로 인한 전체 코드 리팩토링.

* feat : 입력받은 자동차의 이름을 Cars 객체로 만드는 CarsFactory 구현

1. 입력받은 자동차의 이름을 Cars 객체로 만드는 CarsFactory 클래스와 메소드 구현.
2. 입력받은 자동차의 이름을 Cars 객체로 만드는 테스트 구현.

* feat : 자동차 이름이 null 또는 공백인지 확인하는 기능 구현

1. Name 객체에 자동차 이름이 null 또는 공백인지 확인하는 메소드 구현.
2. 자동차 이름이 null 또는 공백인지 확인하는 테스트 구현.

* feat : 우승한 자동차의 이름을 이어붙이는 기능 구현

1. 우승한 자동차의 이름을 이어붙이는 메소드 구현.
2. String 배열을 사용한 부분을 List로 변경.
3. RacingGame 객체를 Result로 명명 수정.
4. 전체적인 리팩토링 진행.

* feat : 게임을 진행한 후 결과를 반환하는 기능 구현

1. Result 객체에서 게임을 한 번 진행한 후 결과를 반환하는 메소드 구현.
2. 게임을 한 번 진행한 후 결과를 반환하는 테스트 구현.

* refactor : 게임을 진행한 후 결과를 반환하는 기능 수정

1. StringUtil 클래스의 메소드를 이름과 위치 상태를 함께 변환하여 반환하도록 리팩토링 진행.

* feat : 입력과 출력을 담당할 view 객체 생성

1. 입력을 담당할 InputView 객체와 출력을 담당할 OutputView 객체를 구현.
2. Result 객체의 package 수정 및 내부 코드 리팩토링.

* feat : 프로그램 동작을 위한 추가적인 클래스 구현

1. 프로그램의 동작을 위한 Application 클래스 구현.
2. RacingGame을 담당하는 컨트롤러 구현.

* refactor : 테스트 코드의 반복을 제거하기 위한 리팩토링

1. 테스트 코드에서의 반복을 제거하기 위한 Car 객체 생성자 추가.

* refactor : 전체 코드 컨벤션 체크 및 리팩토링 진행

* refactor : 역할이 적은 RacingGame 객체를 제거

1. 역할이 너무 적은 RacingGame 객체를 제거 후 Application 클래스 수정.

* refactor : 1차 피드백 리팩토링

1. Application 클래스의 attemptMove 메소드 책임 재분배.
2. Cars 객체의 attemptMove 메소드 구현.
3. 불필요한 stream 제거.
4. RandomGenerator seed 값 추가.
5. OutputView에 String.format 적용.
6. Pattern 코드의 Compliant한 예제를 참고하여 수정.

* refactor : Position의 캐싱 구현

1. PositionCache 클래스를 통해 Position의 객체 중복을 cache 처리.
2. 그에 따른 모든 Position 생성자 코드가 들어간 코드 수정.

* feat : 생성된 랜덤한 수를 감싸는 RandomNo 객체 구현

1. 생성된 랜덤한 수의 유효성을 확인하기 위한 RandomNo 객체 구현.
2. 테스트 코드 일부 수정.

* feat : RandomNo의 캐싱 구현

1. RandomNo 클래스를 통해 RandomNo의 객체 중복을 cache 처리.
2. 그에 따른 모든 RandomNo 생성자 코드가 들어간 코드 수정.

* refactor : Optional 사용과 결과 출력 부분 수정

1. Optional의 잘못된 사용 수정.
2. 결과 출력을 위한 Result를 Map과 같은 형식으로 저장.

* refactor : 2차 피드백 리팩토링

1. cache 클래스 제거.

* refactor : 전략 패턴 적용

1. MovableStrategy Interface 구현.
2. MovableStrategy의 프로덕션 코드에서 활용할 RandomMovableStrategy 구현.
3. MovableStrategy의 테스트 코드에서 활용할 PlannedMovableStrategy 구현.

* refactor : cars 객체 테스트 코드 수정
  • Loading branch information
lxxjn0 authored Feb 17, 2020
1 parent c7f9a56 commit 0c5c940
Show file tree
Hide file tree
Showing 30 changed files with 1,089 additions and 1 deletion.
65 changes: 64 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,68 @@
# java-racingcar

자동차 경주 게임 미션 저장소

## 기능 요구사항

- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.

- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.

- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.

- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.

- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 -
멈춘다.

- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.

## 기능 구현 목록

- 자동차의 이름을 한 줄로 입력받는 기능

- 입력받은 자동차의 이름을 쉼표(,)로 나누는 기능

- 이름이 5자를 넘지 않는지 확인하는 기능

- 중복된 이름이 존재하는지 여부를 확인하는 기능

- 몇 번의 이동을 할 것인지를 입력받는 기능

- 이동 횟수 입력에 대한 유효성을 검증하는 기능

- 전진하는 조건을 생성하는 기능

- 0에서 9까지의 random 값을 생성하는 기능

- random 값이 4 이상일 경우 전진하는 기능

- random 값이 3 이하인 경우 멈추는 기능

- 주어진 횟수에 따라 자동차를 전진하는 기능

- 자동차가 전진하는 경우 위치를 1 증가시키는 기능

- 여러대의 자동차를 관리하는 기능

- 자동차들을 이동시키는 기능

- 매번 이동이 끝난 후 자동차의 이름과 이동상황을 출력하는 기능

- 우승자를 출력하는 기능

- 자동차가 우승한 위치에 있는지 확인하는 기능

- 우승한 자동차(들)을 반환해주는 기능

- 우승한 자동차(들)을 출력하는 기능

## 기여자

- [라흐]()

- [스티치]()

## 우아한테크코스 코드리뷰
* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)

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

import java.util.ArrayList;
import java.util.List;

import com.woowacourse.racingGame.domain.Cars;
import com.woowacourse.racingGame.domain.CarsFactory;
import com.woowacourse.racingGame.domain.MovementNumber;
import com.woowacourse.racingGame.domain.RandomMovableStrategy;
import com.woowacourse.racingGame.domain.Result;
import com.woowacourse.racingGame.view.InputView;
import com.woowacourse.racingGame.view.OutputView;

public class Application {
private static InputView inputview;
private static OutputView outputView;

public static void main(String[] args) {
inputview = new InputView();
outputView = new OutputView();

final Cars cars = generateCars();
final MovementNumber movementNumber = receiveInputMovementNumber();

// NOTE : Log처럼 기록을 출력하는 것이 맞는지 여부 물어보기.
final List<Result> results = playRacingGame(cars, movementNumber);

outputView.printRacingGameResult(results);
outputView.printWinners(getFinalResult(results, movementNumber));
}

private static Cars generateCars() {
try {
final String carName = inputview.inputCarName();
return CarsFactory.generate(carName, new RandomMovableStrategy());
} catch (IllegalArgumentException e) {
outputView.printExceptionMessage(e.getMessage());
return generateCars();
}
}

private static MovementNumber receiveInputMovementNumber() {
try {
final String movementNumber = inputview.inputMovementNumber();
return new MovementNumber(movementNumber);
} catch (IllegalArgumentException e) {
outputView.printExceptionMessage(e.getMessage());
return receiveInputMovementNumber();
}
}

private static List<Result> playRacingGame(final Cars cars, final MovementNumber movementNumber) {
List<Result> results = new ArrayList<>();

for (int i = 0; i < movementNumber.getMovementNumber(); i++) {
attemptMove(cars);
results.add(new Result(cars));
}
return results;
}

private static void attemptMove(final Cars cars) {
cars.attemptMove();
}

private static Result getFinalResult(final List<Result> results, final MovementNumber movementNumber) {
return results.get(movementNumber.getMovementNumber() - 1);
}
}
55 changes: 55 additions & 0 deletions src/main/java/com/woowacourse/racingGame/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.woowacourse.racingGame.domain;

import java.util.Objects;

public class Car {
private final MovableStrategy movableStrategy;
private final Name name;
private Position position;

public Car(Name name, MovableStrategy movableStrategy) {
this.position = Position.ZERO;
this.name = name;
this.movableStrategy = movableStrategy;
}

Car(Name name, Position position, MovableStrategy movableStrategy) {
this.position = position;
this.name = name;
this.movableStrategy = movableStrategy;
}

public void attemptMoveThrough() {
if (movableStrategy.isMovable()) {
position = position.increaseByMovingUnit();
}
}

// NOTE : 불변객체의 이점?
public boolean isSamePosition(final int winnerPosition) {
return position.getPosition() == winnerPosition;
}

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

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

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Car car = (Car)o;
return name.equals(car.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}
}
54 changes: 54 additions & 0 deletions src/main/java/com/woowacourse/racingGame/domain/Cars.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.woowacourse.racingGame.domain;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

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

public Cars(final List<Car> cars) {
checkDuplicate(cars);
this.cars = cars;
}

private void checkDuplicate(final List<Car> cars) {
if (isDuplicateExist(cars)) {
throw new IllegalArgumentException("중복된 이름이 존재합니다.");
}
}

private boolean isDuplicateExist(final List<Car> cars) {
long distinctCarSize = Arrays.stream(cars.toArray())
.distinct()
.count();
return cars.size() != distinctCarSize;
}

public void attemptMove() {
for (Car car : cars) {
car.attemptMoveThrough();
}
}

// NOTE : unmodifiable로 처리해주는 것이 의미를 가지는지 리뷰어님께 여쭤보기.
public List<Car> getCars() {
return Collections.unmodifiableList(cars);
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Cars cars = (Cars)o;
return Objects.equals(this.cars, cars.cars);
}

@Override
public int hashCode() {
return Objects.hash(cars);
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/woowacourse/racingGame/domain/CarsFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.woowacourse.racingGame.domain;

import java.util.stream.Collectors;

import com.woowacourse.racingGame.utils.StringUtil;

public class CarsFactory {
public static Cars generate(final String inputCarName, MovableStrategy movableStrategy) {
return new Cars(StringUtil.split(inputCarName).stream()
.map(Name::new)
.map(name -> new Car(name, movableStrategy))
.collect(Collectors.toList()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.woowacourse.racingGame.domain;

public interface MovableStrategy {
boolean isMovable();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.woowacourse.racingGame.domain;

public class MovementNumber {
private static final int ZERO = 0;

private final int movementNumber;

public MovementNumber(final String inputMovementNumber) {
final int parsedNumber = checkValid(inputMovementNumber);
checkPositive(parsedNumber);
this.movementNumber = parsedNumber;
}

private int checkValid(final String number) {
try {
return Integer.parseInt(number);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("입력한 이동 횟수가 숫자가 아닙니다.");
}
}

private void checkPositive(final int parsedNumber) {
if (parsedNumber <= ZERO) {
throw new IllegalArgumentException("입력한 이동 횟수가 0보다 작은 값입니다.");
}
}

public int getMovementNumber() {
return this.movementNumber;
}
}
46 changes: 46 additions & 0 deletions src/main/java/com/woowacourse/racingGame/domain/Name.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.woowacourse.racingGame.domain;

import java.util.Objects;

public class Name {
private static final int MAX_NAME_LENGTH_LIMIT = 5;

private final String name;

public Name(final String name) {
checkNullOrEmpty(name);
checkValidLength(name);
this.name = name;
}

private void checkNullOrEmpty(final String name) {
if (Objects.isNull(name) || name.isEmpty()) {
throw new IllegalArgumentException("null 또는 빈 문자열입니다.");
}
}

private void checkValidLength(final String name) {
if (name.length() > MAX_NAME_LENGTH_LIMIT) {
throw new IllegalArgumentException("이름은 5자이하만 가능합니다.");
}
}

public String getName() {
return name;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Name name = (Name)o;
return Objects.equals(this.name, name.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.woowacourse.racingGame.domain;

public class PlannedMovableStrategy implements MovableStrategy {
private Power power;

public PlannedMovableStrategy(final Power power) {
this.power = power;
}

@Override
public boolean isMovable() {
return power.isMovable();
}
}
Loading

0 comments on commit 0c5c940

Please sign in to comment.