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

[자동차 경주] 김민지 미션 제출합니다. #2367

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e537f0f
docs(README): 기능 목록 작성
Lightieey Nov 1, 2023
9a06e7f
feat(InputView): readLine 구현
Lightieey Nov 1, 2023
a258c66
feat(OutputView): 스켈레톤 코드 작성
Lightieey Nov 1, 2023
5a72a67
feat(RaceGameController): 스켈레톤 코드 작성
Lightieey Nov 1, 2023
d0de479
docs(README): 기능 목록에 RandomNumber 모델 추가
Lightieey Nov 1, 2023
49e21f1
feat(Car): 스켈레톤 코드 작성
Lightieey Nov 1, 2023
ed6e062
feat(RandomNumber): 랜덤 숫자 생성 기능 구현
Lightieey Nov 1, 2023
90c4189
feat(Referee): 스켈레톤 코드 작성
Lightieey Nov 1, 2023
82c4e6f
docs(README): MVC 패턴 적용 목표 달성 체크
Lightieey Nov 1, 2023
2d55fda
feat(InputView): 자동차 이름 입력 뷰 구현
Lightieey Nov 1, 2023
bfbe311
feat(InputView): 자동차 이름 입력 뷰 예외 처리 구현
Lightieey Nov 1, 2023
882d39d
feat(InputView): 게임 시도 횟수 입력 뷰 구현
Lightieey Nov 1, 2023
454a19e
fix(Car): status type 수정
Lightieey Nov 1, 2023
1125096
feat(Car): 전진 기준값 및 getter 추가
Lightieey Nov 1, 2023
a7e0283
fix(RandomNumber): 랜덤 숫자 생성 함수 static으로 변경
Lightieey Nov 1, 2023
50737c1
feat(OutputView): 라운드 실행 결과 출력 함수 구현
Lightieey Nov 1, 2023
b602c70
feat(RaceGameController): 시도 횟수만큼 경주 진행 구현
Lightieey Nov 1, 2023
0e475f5
docs(README): checklist update
Lightieey Nov 1, 2023
a1c8a3f
fix(Car): status int type으로 변경
Lightieey Nov 1, 2023
6bc4446
feat(Referee): 최종 우승자 계산 기능 추가
Lightieey Nov 1, 2023
8bb1ba7
feat(OutputView): 최종 우승자 출력 기능 구현
Lightieey Nov 1, 2023
e3f5869
feat(RaceGameController): 최종 우승자 출력 부분 추가
Lightieey Nov 1, 2023
43cf8a2
fix(RaceGameController): 자동차 전진 기준값 체크 오류 수정
Lightieey Nov 1, 2023
f8ab401
fix(RaceGameController): 결과 출력 메시지 추가
Lightieey Nov 1, 2023
5451e2c
style(global): formatting with IDE auto Align
Lightieey Nov 1, 2023
ce290b3
chore(InputViewTest): move folder path
Lightieey Nov 1, 2023
8a73403
fix(InputValidator): 중복 이름 검증 로직 오류 수정
Lightieey Nov 1, 2023
f233ba6
test(InputValidatorTest): 이름 길이 테스트 및 중복 이름 테스트 추가
Lightieey Nov 1, 2023
9501a34
chore(Test): move folder path
Lightieey Nov 1, 2023
d448fa3
test(ApplicationTest): 우승자 여러 명일 때 테스트 추가
Lightieey Nov 1, 2023
bbd228d
fix(OutputView): 우승자 여러 명 출력 구분자 수정
Lightieey Nov 1, 2023
33193a9
refactor(InputValidatorTest): 필요 없는 import 제거
Lightieey Nov 1, 2023
98a7486
docs(README): checklist update
Lightieey Nov 1, 2023
901d22b
refactor(RaceGameController): 랜덤 숫자 생성 후 자동차 전진 로직 분리
Lightieey Nov 1, 2023
c64f81e
refactor(InputViewTest): 시도 횟수 입력 실패 테스트 삭제
Lightieey Nov 1, 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
45 changes: 45 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## 목표
- [x] MVC 패턴 적용해보기
- [x] Model - Car, Referee, RandomNumber
- [x] View - InputView, OutputView
- [x] Controller - RaceGameController


## 기능 목록

### 입력
- [x] 경주에 참여할 자동차 이름을 입력으로 받는다.
- [x] 자동차가 1개일 경우 - 쉼표 없음
- [x] 자동차가 여러개일 경우 - 쉼표로 구분 (e.g. "pobi,woni,jun")
- 조건
- [x] 이름은 5자 이하만 가능
- [x] 중복 이름 불가
- [x] 몇 번의 이동을 할 것인지 입력으로 받는다.

### 경주
- [x] 1부터 9까지의 무작위 값을 생성한다.
- [x] 무작위 값이 4 이상이면 전진한다.
- [x] 입력받은 숫자만큼 반복한다.


### 심판
- [x] 제일 많이 이동한 자동차가 우승이다.
- [x] 우승자는 여러 명일 수 있다.


### 출력
- [x] 우승자를 출력한다.
- [x] 여러명일 경우 - 쉼표로 구분


## 기능 요구 사항

주어진 횟수 동안 n대의 자동차가 전진 또는 멈추는 초간단 자동차 경주 게임이다.

- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.
9 changes: 8 additions & 1 deletion src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package racingcar;

import racingcar.controller.RaceGameController;
import racingcar.model.Referee;
import racingcar.view.InputView;
import racingcar.view.OutputView;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
RaceGameController raceGameController = new RaceGameController(new InputView(), new OutputView(),
new Referee());
raceGameController.play();
}
}
41 changes: 41 additions & 0 deletions src/main/java/racingcar/controller/RaceGameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package racingcar.controller;

import java.util.List;
import racingcar.model.Car;
import racingcar.model.RandomNumber;
import racingcar.model.Referee;
import racingcar.view.InputView;
import racingcar.view.OutputView;

public class RaceGameController {
private final InputView inputView;
private final OutputView outputView;
private final Referee referee;

public RaceGameController(InputView inputView, OutputView outputView, Referee referee) {
this.inputView = inputView;
this.outputView = outputView;
this.referee = referee;
}

public void play() {
List<Car> carList = inputView.getCarNameList().stream().map(Car::new).toList();
int roundTime = inputView.getRoundTime();

outputView.printResultMessage();
while (roundTime-- > 0) {
carList.forEach(this::playMoveCar);
outputView.printRoundResult(carList);
}

List<Car> winnerList = referee.getWinner(carList);
outputView.printWinner(winnerList);
}

private void playMoveCar(Car car) {
int randomNumber = RandomNumber.generateRandomNumber();
if (randomNumber >= Car.MOVE_FORWARD_THRESHOLD) {
car.moveForward();
}
}
}
27 changes: 27 additions & 0 deletions src/main/java/racingcar/model/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package racingcar.model;

public class Car {
public static final int MOVE_FORWARD_THRESHOLD = 4;
private final String name;
private int status;

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

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

public int getStatus() {
return this.status;
}

public void moveForward() {
this.status++;
}

public String getStatusBar() {
return "-".repeat(getStatus());
}
}
12 changes: 12 additions & 0 deletions src/main/java/racingcar/model/RandomNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package racingcar.model;

import static camp.nextstep.edu.missionutils.Randoms.pickNumberInRange;

public class RandomNumber {
private static final int MIN_NUMBER = 0;
private static final int MAX_NUMBER = 9;

public static int generateRandomNumber() {
return pickNumberInRange(MIN_NUMBER, MAX_NUMBER);
}
}
11 changes: 11 additions & 0 deletions src/main/java/racingcar/model/Referee.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package racingcar.model;

import java.util.Collections;
import java.util.List;

public class Referee {
public List<Car> getWinner(List<Car> carList) {
int maxStatus = Collections.max(carList.stream().map(Car::getStatus).toList());
return carList.stream().filter(car -> car.getStatus() == maxStatus).toList();
}
}
26 changes: 26 additions & 0 deletions src/main/java/racingcar/validator/InputValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package racingcar.validator;

import java.util.List;

public class InputValidator {
private static final int MAX_CAR_NAME_LENGTH = 5;

public static void verifyCarNames(List<String> nameList) {
for (String name : nameList) {
validateCarNameLength(name);
}
validateDuplicateCarName(nameList);
}

private static void validateCarNameLength(String name) {
if (name.length() < 1 || name.length() > MAX_CAR_NAME_LENGTH) {
throw new IllegalArgumentException();
}
}

private static void validateDuplicateCarName(List<String> nameList) {
if (nameList.size() > nameList.stream().distinct().count()) {
throw new IllegalArgumentException();
}
}
}
22 changes: 22 additions & 0 deletions src/main/java/racingcar/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package racingcar.view;

import camp.nextstep.edu.missionutils.Console;
import java.util.Arrays;
import java.util.List;
import racingcar.validator.InputValidator;

public class InputView {
public List<String> getCarNameList() {
System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
String input = Console.readLine();
List<String> carNameList = Arrays.asList(input.split(","));
InputValidator.verifyCarNames(carNameList);
return carNameList;
}

public int getRoundTime() {
System.out.println("시도할 회수는 몇회인가요?");
String input = Console.readLine();
return Integer.parseInt(input);
}
}
25 changes: 25 additions & 0 deletions src/main/java/racingcar/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package racingcar.view;


import java.util.List;
import java.util.stream.Collectors;
import racingcar.model.Car;

public class OutputView {
public void printResultMessage() {
System.out.println();
System.out.println("실행 결과");
}

public void printRoundResult(List<Car> carList) {
carList.forEach(car -> {
System.out.println(car.getName() + " : " + car.getStatusBar());
});
System.out.println();
}

public void printWinner(List<Car> winnerList) {
System.out.print("최종 우승자 : ");
System.out.println(winnerList.stream().map(Car::getName).collect(Collectors.joining(", ")));
}
}
11 changes: 11 additions & 0 deletions src/test/java/racingcar/ApplicationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ class ApplicationTest extends NsTest {
);
}

@Test
void 우승자_여러명_처리() {
assertRandomNumberInRangeTest(
() -> {
run("pobi,woni,minji", "1");
assertThat(output()).contains("pobi : -", "woni : ", "minji : -", "최종 우승자 : pobi, minji");
},
MOVING_FORWARD, STOP, MOVING_FORWARD
);
}

@Override
public void runMain() {
Application.main(new String[]{});
Expand Down
27 changes: 27 additions & 0 deletions src/test/java/racingcar/validator/InputValidatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package racingcar.validator;

import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.Arrays;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

public class InputValidatorTest {

@DisplayName("getCarNameList must be fail when input name has invalid length")
@ParameterizedTest
@ValueSource(strings = {"", "banana"})
void getCarNameListMustBeFailWhenNameLengthIsInvalid(String name) {
assertThatThrownBy(() -> InputValidator.verifyCarNames(Arrays.asList("pobi", name)))
.isInstanceOf(IllegalArgumentException.class);
}

@DisplayName("getCarNameList must be fail when input has duplicated name")
@Test
void getCarNameListMustBeFailWhenContainsDuplicateName() {
assertThatThrownBy(() -> InputValidator.verifyCarNames(Arrays.asList("pobi", "pobi", "jun")))
.isInstanceOf(IllegalArgumentException.class);
}
}
33 changes: 33 additions & 0 deletions src/test/java/racingcar/view/InputViewTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package racingcar.view;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class InputViewTest {

private InputView inputView;

@BeforeEach
void setUp() {
inputView = new InputView();
}

@DisplayName("getCarNameList must be success")
@Test
void getCarNameListMustBeSuccess() {
String input = "pobi,woni,jun";
InputStream in = new ByteArrayInputStream(input.getBytes());
System.setIn(in);

List<String> result = inputView.getCarNameList();

assertThat(result).isEqualTo(Arrays.asList("pobi", "woni", "jun"));
}
}