Skip to content

[자동차 경주 게임] 조동현 미션 제출합니다. #348

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

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
6b78aae
docs(readme): 구현 기능 목록 작성
devHudi Dec 1, 2021
e01c9d2
feat: 경주할 자동차 이름들을 입력하는 기능 구현
devHudi Dec 1, 2021
ba96018
docs(readme): 구현된 기능 체크 표시
devHudi Dec 1, 2021
3c8841c
feat: 사용자로부터 시도할 회수를 입력 받는 기능 구현
devHudi Dec 1, 2021
caeeb51
docs(readme): 구현된 기능 체크 표시
devHudi Dec 1, 2021
f63aa8c
refactor: Name 클래스를 생성해 분리하고 View 의 inputCarNames가 Name 리스트를 반환하도록 변경
devHudi Dec 1, 2021
01597bb
docs(readme): 이름 입력 시 쉼표 하나만 입력되었을 경우의 예외 사항 추가
devHudi Dec 1, 2021
2407b77
feat: Names 일급 컬렉션을 작성하여 중복 이름 예외 처리 구현
devHudi Dec 1, 2021
718cd4a
docs(readme): 구현된 기능 체크 표시
devHudi Dec 1, 2021
8e7da54
feat: 입력된 이름 수가 2개보다 작을 경우 예외 처리 구현
devHudi Dec 1, 2021
5302dd8
docs(readme): 구현된 기능 체크 표시
devHudi Dec 1, 2021
e9e6891
docs(readme): 규모가 큰 태스크를 세부 태스크로 분리
devHudi Dec 1, 2021
04c6d21
feat: 개별 자동차 1회 전진 기능 구현
devHudi Dec 1, 2021
f5dc295
docs(readme): 구현된 기능 체크 표시
devHudi Dec 1, 2021
bc4cebb
refactor: getName 메소드 명을 toString 으로 변경
devHudi Dec 1, 2021
bc5fc39
refactor: getPosition 메소드 명을 toInt 로 변경
devHudi Dec 1, 2021
c8f86a2
feat: 개별 자동차의 진행도를 출력하는 기능 구현
devHudi Dec 1, 2021
0ff129e
docs(readme): 구현된 기능 체크 표시
devHudi Dec 1, 2021
807cea1
refactor: getMoveCount 메소드 명을 toInt 로 변경
devHudi Dec 1, 2021
6f9e26d
docs(readme): 모든 자동차 1회 전진 기능 제거
devHudi Dec 1, 2021
c2a35e4
feat: 모든 자동차가 사용자가 입력한 횟수만큼 전진하며 진행도가 출력되는 기능 개발
devHudi Dec 1, 2021
51070c9
docs(readme): 구현된 기능 체크 표시
devHudi Dec 1, 2021
55e7c3f
feat: 실행 결과 텍스트 출력
devHudi Dec 1, 2021
8d2ce72
refactor: Cars 객체 생성을 정적 팩토리 메소드 패턴으로 변경
devHudi Dec 1, 2021
deb699e
feat: 경주 마무리 시 우승자 출력 기능 구현
devHudi Dec 1, 2021
a141851
docs(readme): 구현된 기능 체크 표시
devHudi Dec 1, 2021
b001093
style(OutputView): 가독성 증가를 위해 개행 제거
devHudi Dec 1, 2021
696c059
refactor: 에러 메세지 접두사를 상수로 분리
devHudi Dec 1, 2021
4a1745a
refactor: startRace 메소드 이름을 startGame 으로 변경
devHudi Dec 1, 2021
d323d22
refactor: 경주 시작 로직을 함수로 분리
devHudi Dec 1, 2021
9582ce1
style: 가독성을 위한 빈줄 삽입 및 제거
devHudi Dec 1, 2021
01c2cf3
docs(readme): 공백이 포함된 이름을 받을 경우 예외처리 하도록 구현 기능 목록 변경
devHudi Dec 1, 2021
b473dd7
refactor: 이름에 공백 문자가 포함될 수 없도록 예외 처리 개선
devHudi Dec 1, 2021
0f6252e
docs(readme): 처리할 필요 없는 예외처리 제거
devHudi Dec 1, 2021
98b86d4
style: 클래스 처음과 끝의 빈줄 제거
devHudi Dec 2, 2021
73dd335
docs(readme): 프로젝트 소개 및 설계에 대한 내용 작성
devHudi Dec 3, 2021
4a9564b
refactor: MoveCount 클래스의 이름을 더 명확한 TryCount 로 변경
devHudi Dec 5, 2021
ce75154
docs(readme): MoveCount 를 TryCount 로 정정
devHudi Dec 5, 2021
7c8014c
docs(readme): 기능 명세 문서의 경로 변경
devHudi Dec 6, 2021
5398b6a
refactor: 매직 넘버를 상수로 분리
devHudi Dec 6, 2021
3751a7d
docs(readme): 오타 수정
devHudi Dec 7, 2021
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,4 @@ public class Car {

## 📝 License

This project is [MIT](https://github.com/woowacourse/java-racingcar-precourse/blob/master/LICENSE) licensed.
This project is [MIT](https://github.com/woowacourse/java-racingcar-precourse/blob/master/LICENSE) licensed.
145 changes: 145 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
## 🏎️ 자동차 경주 게임

> 우아한테크코스 4기 2주차 프리코스 과제입니다 :)

여러대의 자동차가 함께 경주 하여 승자를 가리는 간단한 게임입니다. 각 자동차는 고유한 이름을 가지고 있으며, 자동차는 각 전진 시도마다 랜덤하게 전진 여부가 결정됩니다. 공동 우승자가 존재할 수 있으며, 경주 중 모든 자동차가 주행하지 않는다면 모두가 공동우승 처리 됩니다.

자동차 이름과 총 시도 횟수는 사용자로부터 입력받습니다. 사용자는 아래의 조건에 맞게 정보를 입력해야합니다.

### 🏁 입력값 조건

**자동차 이름**

- 자동차 이름은 한줄에 2개 이상의 이름을 적으며, 그 구분은 `,` 문자로 한다.
- 예) `dev,hudi,dong,hyun`
- 자동차 이름은 5글자를 초과할 수 없다.
- 자동차 이름은 비어있을 수 없다. (0글자일 수 없다.)
- 자동차 이름은 공백(`" "`)을 포함할 수 없다.
- 자동차 이름은 중복될 수 없다.

**시도 횟수**

- 시도 횟수는 1이상의 정수여야 한다.

### 🚩 전진 조건

각 자동차는 전진 시도마다 0부터 9 사이의 임의의 정수를 생성하고, 생성된 정수가 4 이상일 경우 1칸 전진한다.

## 🖥️ 실행 결과

```
경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)
hudi,java,c++,woowa
시도할 회수는 몇회인가요?
10

실행결과
hudi :
java :
c++ : -
woowa : -

hudi :
java :
c++ : -
woowa : -

hudi : -
java : -
c++ : --
woowa : -

hudi : --
java : --
c++ : ---
woowa : --

hudi : ---
java : ---
c++ : ----
woowa : ---

hudi : ---
java : ---
c++ : -----
woowa : ----

hudi : ----
java : ----
c++ : -----
woowa : ----

hudi : ----
java : -----
c++ : ------
woowa : -----

hudi : -----
java : -----
c++ : ------
woowa : -----

hudi : ------
java : -----
c++ : -------
woowa : ------

최종 우승자 : c++
```



## ⚙️ 구현 기능 목록

### 경주 전

- [x] 경주할 자동차 이름들을 입력하는 기능
- [x] 입력 이름 중 5자를 초과하는 이름이 입력된 경우 예외처리
- [x] 입력 이름 중 문자의 길이가 0인 경우 (`hudi,,donghyun` 또는 `,hudi,donghyun` 등의 경우)
- [x] 공백이 포함된 이름을 입력받았을 경우 예외처리
- [x] `,` 만 입력하였을 경우의 예외처리
- [x] 같은 이름이 2개 이상 들어온 경우 예외처리
- [x] 입력된 자동차 수가 2대보다 작을 경우 (0 ~ 1대) 예외처리
- [x] 예외가 발생한 경우 에러를 출력하고, 입력을 다시 받는 기능
- [x] 시도할 회수를 입력하는 기능
- [x] 숫자가 아닌 문자를 입력받을 경우 예외처리 (공백 포함)
- [x] 양의 정수가 아닌 숫자 (0과 음수) 를 입력 받을 경우 예외처리
- [x] 예외가 발생한 경우 에러를 출력하고, 입력을 다시 받는 기능

### 경주 중

- [x] 개별 자동차 1회 전진 기능
- [x] 0 ~ 9 사이의 무작위 값을 구하고, 4 이상일 경우에만 전진한다.
- [x] 개별 자동차의 진행도를 출력하는 기능
- [x] 모든 자동차를 사용자가 입력한 횟수만큼 전진하는 기능
- [x] 모든 자동차의 진행도를 출력하는 기능

### 경주 완료

- [x] 경주가 마무리되면, 우승자를 출력하는 기능
- [x] 단독 우승자는 단일 이름만 출력되어야 함
- [x] 공동 우승자는 `,` 로 구분되어 함께 출력되어야 함



## 🏛️ 프로젝트 구조

> 큰 틀로 **MVC** 패턴을 사용하였으며, **VO** 객체의 **불변성**을 유지하기 위해 혹은 **생성자의 구현을 숨기기** 위해 일부 **정적 팩토리 메소드 패턴**을 사용하였습니다. 레이어간 데이터를 전달하기 위한 **DTO 클래스**를 정의하여 사용하였습니다.

- `controller`
- `Game` : Domain 객체와 View 를 제어하여, 게임의 전반적인 흐름 제어를 담당합니다.
- `domain`
- `Car` : 자동차
- `CarDto` : 자동차에 대한 정보를 View 레이어로 전달하기 위해 사용되는 DTO (Data Transfer Object)
- `Cars` : 경주하는 여러대의 Car 를 담고있는 일급 컬렉션
- `TryCount` : 사용자로부터 입력받은 시도 횟수를 담고 있는 객체
- `Name` : 사용자로부터 입력받은 자동차의 이름을 담고 있는 VO
- `Names` : 입력받은 모든 Name 을 담고있는 일급 컬렉션
- `Position` : 자동차의 위치 정보를 담고있는 VO
- `Winners` : Cars 중 가장 멀리 전진한 Car 를 담고있는 일급 컬렉션
- `WinnersDto` : 승자 정보를 View 레이어로 전달하기 위해 사용되는 DTO
- `view`
- `InputView` : 사용자로부터 데이터를 입력받을 때 사용되는 클래스
- `OutputView` : 사용자에게 정보를 표시하기 위해 사용되는 클래스
- `racingcar`
- `Application` : 프로그램의 Entry
51 changes: 51 additions & 0 deletions src/main/java/controller/Race.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package controller;

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

import domain.CarDto;
import domain.Cars;
import domain.TryCount;
import domain.Names;
import domain.Winners;
import domain.WinnersDto;
import view.InputView;
import view.OutputView;

public class Race {
public void startGame() {
Names names = InputView.inputCarNames();
Cars cars = Cars.from(names);

TryCount tryCount = InputView.inputTryCount();

startRace(cars, tryCount);

printWinners(cars);
}

private void startRace(Cars cars, TryCount tryCount) {
OutputView.printResultMessage();
for (int i = 0; i < tryCount.toInt(); i++) {
moveCars(cars);
printCars(cars);
}
}

public void moveCars(Cars cars) {
cars.moveAll();
}

public void printCars(Cars cars) {
List<CarDto> carDtos = cars.getCars().stream()
.map(CarDto::from)
.collect(Collectors.toList());

OutputView.printCars(carDtos);
}

public void printWinners(Cars cars) {
Winners winners = cars.getWinners();
OutputView.printWinners(WinnersDto.from(winners));
}
}
39 changes: 39 additions & 0 deletions src/main/java/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package domain;

import camp.nextstep.edu.missionutils.Randoms;

public class Car {
private static final int MINIMUM_RANDOM_NUMBER = 0;
private static final int MAXIMUM_RANDOM_NUMBER = 9;
private static final int MOVING_STANDARD_NUMBER = 4;

private final Name name;
private Position position = new Position();

public Car(Name name) {
this.name = name;
}

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

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

public boolean isSamePosition(Position position) {
return this.position.toInt() == position.toInt();
}

public void moveForward() {
if (willMove()) {
this.position = this.position.increase();
}
}

private boolean willMove() {
int randomNumber = Randoms.pickNumberInRange(MINIMUM_RANDOM_NUMBER, MAXIMUM_RANDOM_NUMBER);
return randomNumber >= MOVING_STANDARD_NUMBER;
}
}
25 changes: 25 additions & 0 deletions src/main/java/domain/CarDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package domain;

public class CarDto {
private String name;
private int position;

private CarDto(String name, int position) {
this.name = name;
this.position = position;
}

public static CarDto from(Car car) {
Name name = car.getName();
Position position = car.getPosition();
return new CarDto(name.toString(), position.toInt());
}

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

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

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

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

private Cars(List<Car> cars) {
this.cars = cars;
}

public static Cars from(Names names) {
List<Car> cars = names.getNames().stream()
.map(Car::new)
.collect(Collectors.toList());

return new Cars(cars);
}

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

public void moveAll() {
cars.forEach(Car::moveForward);
}

public Winners getWinners() {
Position maxPosition = getMaxPosition();
Winners winnerNames = new Winners(this.cars.stream()
.filter(car -> car.isSamePosition(maxPosition))
.collect(Collectors.toList())
);

return winnerNames;
}

private Position getMaxPosition() {
List<Position> positions = this.cars.stream()
.map(Car::getPosition)
.collect(Collectors.toList());

return Collections.max(positions);
}
}
56 changes: 56 additions & 0 deletions src/main/java/domain/Name.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package domain;

import java.util.Objects;

public class Name {
private static final String LENGTH_ERROR_MESSAGE = "이름은 1글자 이상, 5글자 이하를 입력해야 합니다.";
private static final String SPACE_INCLUDED_ERROR_MESSAGE = "이름에 공백이 포함될 수 없습니다.";

private static final String SPACE = " ";

private static final int MINIMUM_LENGTH = 1;
private static final int MAXIMUM_LENGTH = 5;

private final String name;

public Name(String name) {
validateLength(name);
validateSpaceIncluded(name);

this.name = name;
}

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

private void validateLength(String name) {
int nameLength = name.length();
if (nameLength < MINIMUM_LENGTH || nameLength > MAXIMUM_LENGTH) {
throw new IllegalArgumentException(LENGTH_ERROR_MESSAGE);
}
}

private void validateSpaceIncluded(String name) {
if (name.contains(SPACE)) {
throw new IllegalArgumentException(SPACE_INCLUDED_ERROR_MESSAGE);
}
}

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

@Override
public int hashCode() {
return Objects.hash(this.name);
}
}
Loading