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

[1단계 - 자동차 경주] 페드로(류형욱) 미션 제출합니다. #700

Merged
merged 47 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
1af483a
feat(Car): Car 클래스 생성
hw0603 Feb 14, 2024
a9649af
feat(InputView): 자동차 이름 입력받는 기능 추가
hw0603 Feb 14, 2024
b7fb784
feat(validator): 자동차 이름 검증 메서드 구현
hw0603 Feb 14, 2024
c08faf0
test(validatortest): 자동차 이름 검증 테스트 코드 작성
hw0603 Feb 14, 2024
120727d
feat(validator): 이동 횟수 검증 구현
hw0603 Feb 14, 2024
3a08581
test(validator): 이동 횟수 검증 테스트 코드 작성
hw0603 Feb 14, 2024
3d5d290
feat(validator): 자동차 이름 검증 메소드 구현
hw0603 Feb 14, 2024
aab85e4
feat(inputview): 이동 횟수 입력 기능 구현
hw0603 Feb 14, 2024
b5de4a8
feat(car): Car 클래스 생성자 추가
hw0603 Feb 14, 2024
985d7fb
feat(gamecontroller): 사용자 입력 및 자동차 목록 생성
hw0603 Feb 14, 2024
40b29de
feat(randomNumberGenerator): 난수 생성 클래스 구현
hw0603 Feb 14, 2024
98b6ff2
test(randomNumberGenerator): 생성된 난수 범위의 테스트 코드 작성
hw0603 Feb 14, 2024
b996fbd
fix(validatorTest): 접근제어자 수정에 따른 테스트 코드 수정
hw0603 Feb 14, 2024
f2ddecc
feat(outputView): 자동차의 이름과 위치를 출력하는 메서드 작성
hw0603 Feb 14, 2024
21295d5
feat(gameController): 한 라운드를 진행하는 메서드 구현
hw0603 Feb 14, 2024
bbe6946
feat(car): 자동차 위치를 반환하는 게터 추가
hw0603 Feb 14, 2024
bc07afb
feat(gameController): 입력된 횟수만큼 라운드 진행 메서드 추가
hw0603 Feb 14, 2024
51d1513
fix(gameController): carList 재 선언 문제 수정
hw0603 Feb 14, 2024
7fad050
fix(inputView): 안내 문구 출력 시 개행문자 추가
hw0603 Feb 14, 2024
09142da
refactor(outputView): 출력 메서드 오버로딩
hw0603 Feb 14, 2024
7d3e8fd
feat(gameController): 우승자 판별 및 출력 메서드 구현
hw0603 Feb 14, 2024
b38fdd2
feat(application): 메인 메서드 구현
hw0603 Feb 14, 2024
25dde4a
feat(gameController): 우승자가 없는 경우 구현
hw0603 Feb 15, 2024
ec092a5
docs(readme): 요구사항 명세 추가
hw0603 Feb 15, 2024
882f1a2
refactor(gameController): 최대 이동 위치과 우승자를 출력하는 메서드 추출
hw0603 Feb 15, 2024
5c4fd7f
refactor(car): 랜덤값을 인자로 받아 스스로 움직일지를 판단하도록 수정
hw0603 Feb 15, 2024
78b57f6
refactor(car): 이름과 현재 위치를 반환하는toString() 구현
hw0603 Feb 15, 2024
395796c
refactor(gameController): 메서드 및 변수명 수정
hw0603 Feb 15, 2024
155a10e
refactor(carGroup): Car 클래스 래핑
hw0603 Feb 15, 2024
adfb397
style(gameController): 변수명 수정
hw0603 Feb 15, 2024
85b1003
fix(gameController): finish 호출 위치 변경
hw0603 Feb 15, 2024
deb6027
test(carGroupTest): 우승자 판단 테스트 코드 추가
hw0603 Feb 15, 2024
2d9da41
test(carTest): 조건에 따른 이동 여부 테스트 코드 추가
hw0603 Feb 15, 2024
117c03e
fix(carGroup): 움직인 자동차만 우승자가 될 수 있도록 수정
hw0603 Feb 15, 2024
15e7ea7
refactor(car): 이동 기준값 상수 추출
hw0603 Feb 15, 2024
1250237
refactor(nameParser): 입력된 이름을 구분해 주는 클래스 추가
hw0603 Feb 15, 2024
9295c4d
test(nameParserTest): 이름들을 구분하는 테스트 코드 추가
hw0603 Feb 15, 2024
bdf228b
refactor(randomNumberGenerator): 랜덤 범위 상수 포장
hw0603 Feb 15, 2024
91e1b2c
refactor(outputView): print() 오버로딩 제거
hw0603 Feb 15, 2024
0f96d10
style(gameController): 플래그 변수명 수정
hw0603 Feb 15, 2024
1c30133
style(GameController): 사용자 입력 받는 메소드 이름 변경
hw0603 Feb 15, 2024
6481876
refactor(Car): Car 에서 직접 우승 여부를 처리하도록 변경
hw0603 Feb 17, 2024
2053c2f
refactor(gameController): 재 입력에 사용되는 플래그 변수명 변경
hw0603 Feb 17, 2024
b430a92
refactor(validator): 클래스 명 변경
hw0603 Feb 17, 2024
77a8224
refactor(outputView): OutputView와 CarGroup의 의존성 제거
hw0603 Feb 17, 2024
d934a27
refactor(outputView): OutputView와 Car의 의존성 제거
hw0603 Feb 17, 2024
522a095
refactor(car): 사용하지 않는 toString() 오버라이딩 삭제
hw0603 Feb 17, 2024
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
49 changes: 49 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## 주요 기능

1. 사용자가 자동차 이름 입력
2. 시도할 횟수 입력
3. 입력된 횟수만큼 각 자동차를 이동
4. 이동 종료 후 최종 우승자 출력

---

### 자동차 이름 입력

- [x] 이름 입력 안내 문구 출력
- [x] 사용자로부터 이름 입력받는 기능
- [x] 이름을 쉼표 기준으로 구분하여 저장하는 기능

### 시도할 횟수 입력

- [x] 횟수 입력 안내 문구를 출력
- [x] 사용자로부터 이동할 횟수를 입력받는 기능

### 자동차 이동(게임 진행)

- [x] "실행 결과" 문구 출력
- [x] 각 자동차별 1자리 난수 생성 기능
- [x] 난수값이 4 이상인지 확인하는 기능
- [x] 난수값이 4 이상인 자동차의 위치를 1 증가시키는 기능
- [x] 각 라운드 별 실행 결과(자동차 위치) 출력하는 기능
- [x] 입력된 횟수만큼 라운드를 반복하는 기능

### 우승자 출력

- [x] 우승자(가장 멀리 이동한 자동차)를 판별하는 기능
- 모든 자동차들의 이동 거리가 0인 경우에는 우승자는 발생하지 않는다.
- [x] “우승한 자동차가 없습니다” 출력~
- [x] 우승자 출력 기능
- [x] 공동 우승자의 경우 ","로 구분하여 출력해야 함

---

## 예외처리

- 자동차 이름 입력 시 유효한 입력이 아니라면 재 시도
- 입력된 이름들의 유효성 검증
- [x] 이름은 5자 이하만 가능
- [x] 중복된 이름 설정 불가
- [x] [a-zA-Z]+
- 이동 횟수 입력 시 유효한 입력이 아니라면 재 시도
- 이동할 횟수의 유효성 검증
- [x] 자연수가 아닌 입력이 전달되는 경우 예외 처리
13 changes: 13 additions & 0 deletions src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package racingcar;

import java.io.IOException;
import racingcar.controller.GameController;

public class Application {
public static void main(String[] args) throws IOException {
GameController controller = new GameController();
controller.init();
controller.play();
controller.finish();
}
}
85 changes: 85 additions & 0 deletions src/main/java/racingcar/controller/GameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package racingcar.controller;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import racingcar.model.Car;
import racingcar.model.CarGroup;
import racingcar.utils.NameParser;
import racingcar.utils.InputValidator;
import racingcar.view.InputView;
import racingcar.view.OutputView;

public class GameController {
private final CarGroup carGroup = new CarGroup();
private List<String> names;
private int moveCount;

public void init() throws IOException {
readCarNames();
readMoveCount();
initCars(names);
}

private void readCarNames() throws IOException {
boolean isValid = false;
while (!isValid) {
isValid = doReadCarNames();
}
}

private boolean doReadCarNames() throws IOException {
try {
OutputView.printlnInputName();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

페드로는 OutputViewInputView 를 어떤 기준으로 분리하셨나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

단순히 콘솔에 출력하는 부분은 모두 OutputView에서, 사용자의 입력을 받아야 하는 부분은 InputView에서 담당하는 것으로 생각했습니다.

names = NameParser.parse(InputView.inputNames());
InputValidator.validateCarName(names);
} catch (IllegalArgumentException e) {
OutputView.printException(e.getMessage());
return false;
}
return true;
}

private void readMoveCount() throws IOException {
boolean isValid = false;
while (!isValid) {
isValid = doReadMoveCount();
}
Comment on lines +45 to +47
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분이 좋은 힌트가 될 수 있을 것 같아요!
isValid는 어떤걸 의미하나요?
doReadMoveCount는 해당 값을 충족시켜주나요?

Comment on lines +43 to +47
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readMoveCount가 아무 값을 반환해주지 않으니
moveCount가 어디 저장되는지 확인하려면 내부 구현을 살펴봐야겠네요.

}

private boolean doReadMoveCount() throws IOException {
try {
OutputView.printlnInputMoveCount();
moveCount = InputView.inputMoveCount();
InputValidator.validateMoveCount(moveCount);
} catch (IllegalArgumentException e) {
OutputView.printException(e.getMessage());
return false;
}
return true;
}

private void initCars(List<String> carNames) {
for (String name : carNames) {
carGroup.add(new Car(name));
}
}

public void play() {
OutputView.printResultDescription();
for (int i = 0; i < moveCount; i++) {
Map<String, Integer> raceResponse = carGroup.race();
OutputView.printPosition(raceResponse);
}
}

public void finish() {
List<String> winners = carGroup.findWinners();
if (winners.isEmpty()) {
OutputView.printNoWinner();
return;
}
Comment on lines +78 to +81
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우승자가 없는 경우까지 디테일 하네요ㅎㅎ


OutputView.printWinnerList(winners);
}
}
35 changes: 35 additions & 0 deletions src/main/java/racingcar/model/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package racingcar.model;

public class Car {
private static final int MOVE_THRESHOLD = 4;
private final String name;
private int position;

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

public String getName() {
return name;
}

public int getPosition() {
return position;
}

public int move(int randomNumber) {
if (randomNumber >= MOVE_THRESHOLD) {
position++;
}
return position;
}

public boolean isMoved() {
return position > 0;
}

public boolean isSamePosition(int position) {
return this.position == position;
}
}
40 changes: 40 additions & 0 deletions src/main/java/racingcar/model/CarGroup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package racingcar.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import racingcar.utils.RandomNumberGenerator;

public class CarGroup {
private final List<Car> cars = new ArrayList<>();

public void add(Car car) {
cars.add(car);
}

public Map<String, Integer> race() {
Map<String, Integer> raceResponse = new HashMap<>();
for (Car car : cars) {
int nextPosition = car.move(RandomNumberGenerator.generate());
raceResponse.put(car.getName(), nextPosition);
}

return raceResponse;
}

public List<String> findWinners() {
int maxPosition = findMaxPosition();
return cars.stream()
.filter((car -> car.isMoved() && car.isSamePosition(maxPosition)))
.map(Car::getName)
.toList();
}

private int findMaxPosition() {
return cars.stream()
.mapToInt(Car::getPosition)
.max()
.orElse(0);
Comment on lines +34 to +38
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스트림 사용👍🏻

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

import java.util.List;

public class InputValidator {
public static void validateCarName(List<String> names) {
validateDuplicateNames(names);
for (String name : names) {
validateNameLength(name);
validateNameCharacters(name);
}
}

private static void validateNameLength(String name) {
if (name.length() > 5) {
throw new IllegalArgumentException("자동차의 이름은 5글자를 초과할 수 없습니다.");
}
}

private static void validateDuplicateNames(List<String> names) {
int distinctNamesCount = (int) names.stream()
.distinct()
.count();

if (distinctNamesCount != names.size()) {
throw new IllegalArgumentException("자동차의 이름은 중복될 수 없습니다.");
}
}

private static void validateNameCharacters(String name) {
if (!name.matches("^[a-zA-Z]*$")) {
throw new IllegalArgumentException("자동차의 이름은 영어로만 이루어져야 합니다.");
}
}

public static void validateMoveCount(int moveCount) {
if (moveCount <= 0) {
throw new IllegalArgumentException("이동 횟수는 자연수여야 합니다.");
}
}
}
14 changes: 14 additions & 0 deletions src/main/java/racingcar/utils/NameParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package racingcar.utils;

import java.util.Arrays;
import java.util.List;

public class NameParser {
private static final String DELIMITER = ",";

public static List<String> parse(String rawNames) {
String[] names = rawNames.split(DELIMITER);
return Arrays.stream(names)
.toList();
}
}
12 changes: 12 additions & 0 deletions src/main/java/racingcar/utils/RandomNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package racingcar.utils;

import java.util.Random;

public class RandomNumberGenerator {
private static final int RANDOM_BOUND = 10;

public static int generate() {
Random random = new Random();
return random.nextInt(RANDOM_BOUND);
}
}
19 changes: 19 additions & 0 deletions src/main/java/racingcar/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package racingcar.view;

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

public class InputView {
private static final BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

public static String inputNames() throws IOException {
return br.readLine();
}

public static int inputMoveCount() throws IOException {
String moveCount = br.readLine();
return Integer.parseInt(moveCount);
}
}

51 changes: 51 additions & 0 deletions src/main/java/racingcar/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package racingcar.view;

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

public class OutputView {
private static final String WINNER_DESCRIPTION = "가 최종 우승했습니다.";
private static final String RESULT_DESCRIPTION = "실행 결과";
private static final String NO_WINNER_DESCRIPTION = "최대 이동 거리가 0이므로 우승한 자동차가 없습니다.";
private static final String NAME_INPUT_DESCRIPTION = "경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분).";
private static final String MOVE_COUNT_INPUT_DESCRIPTION = "시도할 회수는 몇회인가요?";

private static final String POSITION_METER = "-";

public static void printResultDescription() {
System.out.println();
System.out.println(RESULT_DESCRIPTION);
}

public static void printPosition(Map<String, Integer> carPositions) {
StringBuilder sb = new StringBuilder();
carPositions.forEach(
(name, position) -> sb.append(name).append(":").append(POSITION_METER.repeat(position)).append("\n")
);
System.out.println(sb);
}

public static void printException(String message) {
System.out.println(message);
}

public static void printWinnerList(List<String> winners) {
List<String> names = winners.stream()
.toList();

String winnerNames = String.join(", ", names);
System.out.println(winnerNames + WINNER_DESCRIPTION);
}

public static void printNoWinner() {
System.out.println(NO_WINNER_DESCRIPTION);
}

public static void printlnInputName() {
System.out.println(NAME_INPUT_DESCRIPTION);
}

public static void printlnInputMoveCount() {
System.out.println(MOVE_COUNT_INPUT_DESCRIPTION);
}
}
Loading