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

Step3 - 자동차 경주 #6042

Merged
merged 18 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
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
58 changes: 49 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,49 @@
# 자동차 경주 게임
## 진행 방법
* 자동차 경주 게임 요구사항을 파악한다.
* 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다.
* 코드 리뷰 피드백에 대한 개선 작업을 하고 다시 PUSH한다.
* 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.

## 온라인 코드 리뷰 과정
* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)
# 3단계 자동차 경주

---
## 기능 요구사항
- [x] 초간단 자동차 경주 게임을 구현한다.
- [x] 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- [x] 사용자는 몇 대의 자동차로 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- [x] 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4이상일 경우이다.
- [x] 자동차의 상태를 화면에 출력한다. 어느 시점에 출력할 것인지에 대한 제약은 없다.

## 프로그래밍 요구사항
- [x] 모든 로직에 단위 테스트를 구현한다. 단, UI(System.out, System.in) 로직은 제외
- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
- UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
- [x] 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 이 과정의 Code Style은 [intellij idea Code Style. Java](https://www.jetbrains.com/help/idea/code-style-java.html)을 따른다.
- intellij idea Code Style. Java을 따르려면 code formatting 단축키(Windows : Ctrl + Alt + L. Mac : ⌥ (Option) + ⌘ (Command) + L.)를 사용한다.
- [x] else 예약어를 쓰지 않는다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.

## 기능 목록 및 commit 로그 요구사항
- [x] 기능을 구현하기 전에 README.md 파일에 구현할 기능 목록을 정리해 추가한다.
- [x] git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다.
- 참고문서: [AngularJS Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153)

---
## 기능 구현 목록
- [x] 0에서 9 사이 random 값을 생성한다.
- [x] random 값이 4이상일 경우에 전진하고, 그렇지 않으면 정지한다.
- [x] 자동차 대수와 시도할 회수를 순서대로 입력받는 안내문구를 출력하고 입력받는다.
```
자동차 대수는 몇 대 인가요?
3
시도할 회수는 몇 회 인가요?
5

```
- [x] 주어진 횟수 동안 n대의 자동차는 전진하거나 멈출 수 있으며, 횟수마다 실행 결과를 출력한다.
```
실행 결과
-
-
-

--
-
--
```
19 changes: 19 additions & 0 deletions src/main/java/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import java.util.Random;

public class Car {
private static final Random random = new Random();
private static final int moveThreshold = 4;
private int position;

public void move() {
if (canMove()) position++;
}

private boolean canMove() {
return random.nextInt(10) >= moveThreshold;
}

public int getPosition() {
return position;
}
}
37 changes: 37 additions & 0 deletions src/main/java/CarRace.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import java.util.ArrayList;
import java.util.List;

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

public void ready() {
InputView input = new InputView();
int carCount = input.scanPositiveInt("자동차 대수는 몇 대 인가요?");
int raceCount = input.scanPositiveInt("시도할 회수는 몇 회 인가요?");

for (int i = 0; i < carCount; i++) {
cars.add(new Car());
}

result.printStart();
for (int i = 0; i < raceCount; i++) {
race();
show();
}
}

private void race() {
for (Car car : cars) {
car.move();
}
}

private void show() {
for (Car car : cars) {
result.printDash(car.getPosition());
}
result.printFinish();
}

}
21 changes: 21 additions & 0 deletions src/main/java/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import java.util.Scanner;

public class InputView {
private final Scanner scanner = new Scanner(System.in);

public int scanPositiveInt(String message) {
System.out.println(message);
int value = scanner.nextInt();
scanner.nextLine();
validatePositiveInt(value);
return value;
}

private void validatePositiveInt(int value) {
if (value <= 0) {
String errorMessage = String.format("입력 값은 양수여야 합니다. value:%d", value);
throw new IllegalArgumentException(errorMessage);
}
}

}
19 changes: 19 additions & 0 deletions src/main/java/ResultView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
public class ResultView {
private final StringBuilder builder = new StringBuilder();

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

public void printDash(int count) {
while(count-->0){
System.out.print("-");
}
System.out.println();
}

public void printFinish() {
System.out.println();
}
}
77 changes: 77 additions & 0 deletions src/test/java/CarRaceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.List;

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

class CarRaceTest {
private CarRace carRace;

@BeforeEach
void setUp() {
carRace = new CarRace();
}

@ParameterizedTest
@CsvSource(delimiter = ',', value = {"3,5", "4,5"})
@DisplayName("자동차 대수와 시도할 회수를 순서대로 입력받는 안내문구를 출력하고 입력받는다.")
void printGuideMessage(int carNumber, int tryCount) {
String in = String.format("%d%n%d%n", carNumber, tryCount);
System.setIn(new ByteArrayInputStream(in.getBytes())); //setIn() 이후에 new Scanner(System.in)을 호출해야 동작함.
OutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));

carRace.ready();

List<String> expected = List.of("자동차 대수는 몇 대 인가요?", "시도할 회수는 몇 회 인가요?");
assertThat(out.toString()).contains(expected);
}

@ParameterizedTest
@CsvSource(delimiter = ',', value = {"-3,0", "0,0", "3,-1"})
@DisplayName("자동차 대수와 시도할 회수는 양수여야한다.")
void throwIfInputPositive(int carNumber, int tryCount) {
String in = String.format("%d%n%d%n", carNumber, tryCount);
System.setIn(new ByteArrayInputStream(in.getBytes()));

assertThatThrownBy(carRace::ready)
.isInstanceOf(IllegalArgumentException.class);
}

@ParameterizedTest
@CsvSource(delimiter = ',', value = {"2,2", "4,5", "1,3"})
@DisplayName("주어진 횟수 동안 자동차는 전진하거나 멈출 수 있으며, 횟수마다 실행 결과를 출력한다.")
void printStartResult(int carNumber, int tryCount) {
String in = String.format("%s%n%s%n", carNumber, tryCount);
System.setIn(new ByteArrayInputStream(in.getBytes()));
OutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));

carRace.ready();

assertThat(out.toString()).containsPattern("실행 결과\\R((-*\\R)*\\R)*");
}

// @RepeatedTest(value = 30)
// @DisplayName("0에서 9 사이 random 값을 생성한다.")
// void createRandomBetween() {
// assertThat(racingCar.createRandom()).isBetween(0, 9);
// }

// @ParameterizedTest
// @CsvSource(delimiter = ':', value = { "0:false", "4:true", "9:true" })
// @DisplayName("random 값이 4 이상이면 전진하고, 그렇지 않으면 정지한다.")
// void determineMoveOrNot(int input, boolean expected) {
// assertThat(racingCar.isGoingForward(input)).isEqualTo(expected);
// }


}