Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b6bd6c1
docs: 기능 명세서 분석과 구현 계획 작성
binary-ho Jan 18, 2023
ba9199f
feat: 무작위 숫자 Picker 추후 테스트를 위해 추상화 적용
binary-ho Jan 18, 2023
ee05c9e
feat: 자동차 전진 가능 여부 체크 메서드 추가
binary-ho Jan 18, 2023
e842c6c
feat: 자동차 레이싱 기능 추가
binary-ho Jan 18, 2023
3b96277
feat: 자동차 결과 출력 기능 추가
binary-ho Jan 18, 2023
81b9daa
feat: 자동차 현재 위치 출력 기능 추가
binary-ho Jan 18, 2023
1b4a1a2
test: 자동차 테스트 코드 작성.
binary-ho Jan 18, 2023
6af340a
feat: 레이싱 기능 구현
binary-ho Jan 18, 2023
3a34a65
feat: 회차별 레이싱 결과 출력 기능 구현
binary-ho Jan 18, 2023
0db0af5
feat: 회차별 레이싱 결과 출력 기능 구현
binary-ho Jan 18, 2023
0a8c3dc
Test: 레이싱 테스트 코드 작성.
binary-ho Jan 18, 2023
bdb0ae6
feat: 자동차 이름 입력 받기 구현
binary-ho Jan 18, 2023
33947b7
feat: 게임 횟수 입력 받기 구현
binary-ho Jan 18, 2023
e91d7ee
Test: 레이싱 결과 전체 출력 테스트 추가.
binary-ho Jan 18, 2023
cf868d7
feat: 자동차 이름 리스트와 Number Picker로 자동차 리스트를 만드는 객체 CarFactory 구현
binary-ho Jan 18, 2023
9056e18
Test: CarFactory 객체 생성 테스트 작성.
binary-ho Jan 18, 2023
5e191b5
refactor: CarFactory 인스턴스화 방지
binary-ho Jan 18, 2023
3a649be
chore: Number Picker를 final로 변경
binary-ho Jan 18, 2023
129e231
feat: 게임 시작 요청을 받아 게임을 진행하는 CarRacingController 구현
binary-ho Jan 18, 2023
e607b0f
feat: CarRacingService 구현, CarRacingController는 입력만 받는다.
binary-ho Jan 19, 2023
b231c20
refactor: CarRacingController 인스턴스화 방지
binary-ho Jan 19, 2023
688c845
refactor: CarFactory 삭제
binary-ho Jan 19, 2023
14abfba
refactor: CarRacingService에 NumberPicker 주입하도록 변경
binary-ho Jan 19, 2023
94c03af
chore: CarRacingService가 전달 받는 NumberPicker 인자 이름 변경
binary-ho Jan 19, 2023
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
115 changes: 115 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# 기능 명세와 분석

코드를 짜기 전 기능 명세를 분석하고, 필요한 메시지와 객체를 생각해 보았습니다. <br>
순서는 아래와 같습니다
1. 기능 명세
2. 명세에 따라 필요한 메시지 분석
3. 메시지 묶기
4. 묶인 기준으로 책임 가져갈 객체 정하기


## 1. 기능 명세
문제에서 주어진 명세는 아래와 같습니다.
- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
- 아래의 프로그래밍 실행 결과 예시와 동일하게 입력과 출력이 이루어져야 한다.

## 2. 명세에 따라 필요한 메시지 분석
객체 보다 행동을 먼저 생각했습니다. 결국 객체들이 해야할 행동과 (지게 될 책임들), 주고 받을 메시지들을 먼저 생각했습니다.


#### 2.1 **주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.**
- 주어진 횟수를 저장한다.
- n 대의 자동차 정보를 저장한다.
- 자동차는 전진할 수 있다.
- 자동차는 멈출 수 있다.


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


#### 2.3 **자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.**
- 자동차 이름을 쉼표로 구분하여 출력한다.
- 자동차 이름을 저장할 수 있다. 제한 5자.


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

#### 2.5 **전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.**
- 0에서 9사이의 무작위 값을 구할 수 있다.
- 구한 무작위 값이 4이상일 때 전진한다.


#### 2.6 **자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.**
- 자동차의 진행 상태를 알 수 있다.
- 우승자를 알 수 있다 + 출력


#### 2.7 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
- 우승자 출력시 쉼표 구분 처리


#### 2.8 **사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.**
- 입력에 대한 예외 처리가 가능하다.
- 에러 메시지를 출력 가능하고 형식이 있다.
- 예외 처리 이후 입력을 이어서 받을 수 있다.


#### 2.9 **아래의 프로그래밍 실행 결과 예시와 동일하게 입력과 출력이 이루어져야 한다.**
- 입출력에 정해진 형식이 있다.

## 3. 구현할 기능
크게 2가지 영역으로 묶었습니다. 이곳에 있는 기능들을 구현하게 될 것입니다.

### 3.1 입력

1. 사용자는 이동 횟수를 입력할 수 있다.
2. 사용자는 자동차 이름을 입력할 수 있다.
3. 자동차 이름에는 5자의 글자수 제한이 있다.
4. n대의 자동차 정보(이름)을 저장한다.
5. 입력된 이동 횟수를 저장한다.
6. 입력에는 정해진 형식이 있다.
7. 입력에 대한 예외 처리가 가능하다.
8. 에러 메시지를 출력 가능하고 형식이 있다.
9. 예외 처리 이후 입력을 이어서 받을 수 있다.

### 3.2 경주

1. 자동차에 이름을 부여할 수 있다.
3. 자동차는 0에서 9사이의 무작위 값을 구할 수 있다.
4. 구한 무작위 값이 4이상일 때 전진한다.
6. 자동차의 진행 상태를 기록할 수 있다.
9. 자동차들의 전진 상태를 출력할 수 있다.
7. 우승자를 알 수 있다.
8. 우승자를 정해진 형식 출력할 수 있다.

# 4. 구현할 객체

1. **자동차 객체**
a. 이름을 저장할 수 있다.
b. 레이싱을 진행할 수 있다.
- 무작위 숫자를 얻을 수 있다. → 랜덤 추상화 필요할 수도 있다.
- 숫자 값에 따라 전진 처리 → position의 변경

c. 진행 상태를 저장할 수 있다.
d. 자신의 진행 상태를 출력할 수 있다.
1. **레이싱 객체**
a. 잔여 게임 횟수가 있는 경우 레이싱을 진행할 수 있다.
b. 자동차들에게 게임을 진행시킬 수 있다.
c. 순서에 맞게 회차별 진행 결과를 출력할 수 있다.
d. 우승 자동차를 체크할 수 있다.
e. 우승 자동차에 대한 중복 체크가 가능하다.
1. **인풋 객체**
a. 자동차 이름을 입력 받을 수 있다.
b. 자동차 이름 입력의 다양한 예외를 처리할 수 있다.
c. 횟수를 입력 받을 수 있다.
d. 횟수에 대한 다양한 예외 처리가 가능하다.
7 changes: 5 additions & 2 deletions src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package racingcar;

import static racingcar.CarRacingController.initCarRacingGame;

public class Application {

public static void main(String[] args) {
// TODO 구현 진행
initCarRacingGame();
}
}
}
38 changes: 34 additions & 4 deletions src/main/java/racingcar/Car.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,42 @@
package racingcar;

public class Car {

private final String name;
private int position = 0;
private int position;
private final NumberPicker numberPicker;
private final StringBuilder currentPositionString;

public Car(String name) {
public Car(String name, NumberPicker randoms) {
this.name = name;
position = 0;
currentPositionString = new StringBuilder(name);
currentPositionString.append(" : ");
this.numberPicker = randoms;
}

public void race() {
if (!checkCanMoveForward()) {
return;
}
position += 1;
currentPositionString.append('-');
}

private boolean checkCanMoveForward() {
int randomNumber = numberPicker.pickNumber();
return randomNumber >= 4;
}

// 추가 기능 구현
}
public void printPosition() {
System.out.println(currentPositionString);
}

public int getPosition() {
return position;
}

public String getName() {
return name;
}
}
19 changes: 19 additions & 0 deletions src/main/java/racingcar/CarRacingController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package racingcar;

import java.util.List;

public class CarRacingController {

private CarRacingController() {
throw new AssertionError();
}

public static void initCarRacingGame() {
Input input = new Input();
List<String> carNamesList = input.getCarNamesList();
int raceGameCount = input.getRacingGameCount();

NumberPicker rangedRandomNumberPicker = new RangedRandomNumberPicker(0, 9);
CarRacingService.getInstance().carRacingGame(carNamesList, raceGameCount, rangedRandomNumberPicker);
}
}
29 changes: 29 additions & 0 deletions src/main/java/racingcar/CarRacingService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package racingcar;

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

public class CarRacingService {

private static final CarRacingService INSTANCE = new CarRacingService();

private CarRacingService() {
}

public static CarRacingService getInstance() {
return INSTANCE;
}

public void carRacingGame(List<String> carNamesList, int raceGameCount,
NumberPicker numberPicker) {
List<Car> cars = getCars(carNamesList, numberPicker);
Racing racing = new Racing(cars, raceGameCount);
racing.race();
}

private List<Car> getCars(List<String> carNamesList, NumberPicker numberPicker) {
return carNamesList.stream()
.map(name -> new Car(name, numberPicker))
.collect(Collectors.toList());
}
}
82 changes: 82 additions & 0 deletions src/main/java/racingcar/Input.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package racingcar;

import camp.nextstep.edu.missionutils.Console;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Input {

public List<String> getCarNamesList() {
System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
return getCarNamesString();
}

public int getRacingGameCount() {
System.out.println("시도할 회수는 몇회인가요?");
return getGameCountString();
}

private List<String> getCarNamesString() {
try {
return inputCarNamesString();
} catch (IllegalArgumentException error) {
System.out.println(error.getMessage());
return getCarNamesString();
}
}

private List<String> inputCarNamesString() {
String inputString = Console.readLine();
List<String> carNames = Arrays.stream(inputString.split(","))
.map(Input::getTrimString)
.collect(Collectors.toList());
if (carNames.isEmpty()) {
throw new IllegalArgumentException("[ERROR] 자동차 이름을 입력하세요.");
}
return carNames;
}

private static String getTrimString(String string) {
String trimString = string.trim();
if (trimString.isEmpty()) {
throw new IllegalArgumentException("[ERROR] 자동차 이름에 공란이 있습니다. 다시 입력해주세요.");
}
if (trimString.length() > 5) {
throw new IllegalArgumentException("[ERROR] 자동차 이름은 최대 5자 입니다. 다시 입력해주세요.");
}
return trimString;
}


private int getGameCountString() {
try {
return inputGameCountString();
} catch (IllegalArgumentException error) {
System.out.println(error.getMessage());
return getGameCountString();
}
}

private int inputGameCountString() {
String inputString = Console.readLine();
String gameCountString = inputString.trim();
if (gameCountString.isEmpty()) {
throw new IllegalArgumentException("[ERROR] 시도 횟수를 입력하세요.");
}
try {
return parseGameCount(gameCountString);
} catch (IllegalArgumentException error) {
System.out.println(error.getMessage());
return inputGameCountString();
}
}

private int parseGameCount(String string) {
try {
return Integer.parseInt(string);
} catch (NumberFormatException error) {
throw new IllegalArgumentException("[ERROR] 시도 횟수를 입력하세요.");
}
}
}
6 changes: 6 additions & 0 deletions src/main/java/racingcar/NumberPicker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package racingcar;

public interface NumberPicker {

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

import java.util.Comparator;
import java.util.List;

public class Racing {

public final List<Car> cars;
private int gameCount;
private final StringBuilder winnerString;

public Racing(List<Car> cars, int gameCount) {
this.cars = cars;
this.gameCount = gameCount;
winnerString = new StringBuilder("최종 우승자 : ");
}

public void race() {
System.out.println("실행 결과");
while (checkLeftRacing()) {
gameCount--;
raceAllCar();
printResult();
}
printWinner();
}

private boolean checkLeftRacing() {
return gameCount > 0;
}

private void raceAllCar() {
cars.forEach(Car::race);
}

private void printResult() {
cars.forEach(Car::printPosition);
System.out.println();
}

private void printWinner() {
int leadCarPosition = getLeadCarPosition();
buildWinnerString(leadCarPosition);
System.out.println(winnerString);
}

private int getLeadCarPosition() {
Car leadCar = cars.stream()
.max(Comparator.comparing(Car::getPosition))
.get();
return leadCar.getPosition();
}

private void buildWinnerString(int leadCarPosition) {
cars.forEach(car -> {
if (car.getPosition() == leadCarPosition) {
winnerString.append(car.getName()).append(", ");
}
});
winnerString.setLength(winnerString.length() - 2);
}
}
Loading