Skip to content
80 changes: 79 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,79 @@
# java-racingcar-precourse
# 2주차 자동차 경주

## 📌 구현할 기능 목록

- [x] 자동차 객체 구현

- [x] 자동차 리스트 및 실행 횟수 입력

- [x] 전진 여부 결정 메서드

- [x] 이동 결과 출력 메서드

- [x] 게임 진행

- [x] 게임 결과 계산 및 출력

<br>

---

## 기능 요구사항

- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.

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

- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.

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

- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.

- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.

- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.

- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다.

<br>

---

## 입출력 및 예외 처리 요구사항

### 입력

- 경주할 자동차 이름(이름은 쉼표(,) 기준으로 구분)

- 시도할 횟수

### 출력

- 차수별 실행 결과

- 단독 우승자 안내 문구 또는 공동 우승자 안내 문구

### 예외

- 잘못된 입력은 `IllegalArgumentException`을 반환

- 자동차 목록에 대한 잘못된 입력은 `잘못된 입력입니다.`, 이름에 대한 잘못된 입력은 `자동차 이름은 5자 이하만 가능합니다.`로 에러메시지를 반환한다.

- 시도 횟수에 대한 잘못된 입력은 `잘못된 입력입니다. 경주를 시도할 횟수를 입력해주세요.`로 에러메시지를 반환한다.

<br>

---

## 라이브러리

- `camp.nextstep.edu.missionutils`에서 제공하는`Randoms` lc `Console` API를 사용하여 구현해야 한다.

- Random 값 추출은 `camp.nextstep.edu.missionutils.Randoms`의 `pickNumberInRange()`를 활용한다.

```java
Randoms.pickNumberInRange(0, 9);
```

- 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다.
11 changes: 11 additions & 0 deletions src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
package racingcar;

import racingcar.car.Car;
import racingcar.game.Game;
import racingcar.game.InputException;

import java.util.ArrayList;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
ArrayList<Car> carList = new ArrayList<>();
InputException.getCarName(carList);
int tryCount = InputException.getTryCount();

Game.start(carList, tryCount);
}
}
19 changes: 19 additions & 0 deletions src/main/java/racingcar/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package racingcar;

public enum ErrorMessage {
INVALID_INPUT("잘못된 입력입니다"),
CAR_NAME("자동차 이름은 5자 이하만 가능합니다."),
TRY_COUNT("잘못된 입력입니다. 경주를 시도할 횟수를 입력해주세요."),
DUPLICATE_NAME("자동차 이름은 중복될 수 없습니다.");

private final String message;

ErrorMessage(String message) {
this.message = message;
}

@Override
public String toString() {
return message;
}
}
34 changes: 34 additions & 0 deletions src/main/java/racingcar/car/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package racingcar.car;

public class Car implements Comparable<Car>{
private String name;
private int moveCount;

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

public void move() {
if (MoveDecision.byRandom()) {
this.moveCount++;
}
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getMoveCount() {
return moveCount;
}

@Override
public int compareTo(Car car) {
return Integer.compare(car.getMoveCount(), this.moveCount);
}
}
9 changes: 9 additions & 0 deletions src/main/java/racingcar/car/MoveDecision.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package racingcar.car;

import camp.nextstep.edu.missionutils.Randoms;

public class MoveDecision {
public static boolean byRandom() {
return Randoms.pickNumberInRange(0, 9) >= 4;
}
}
45 changes: 45 additions & 0 deletions src/main/java/racingcar/game/Game.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package racingcar.game;

import racingcar.car.Car;

import java.util.ArrayList;
import java.util.Collections;

public class Game {
public static void start(ArrayList<Car> carList, int n) {
System.out.println("\n실행 결과");
for (int i = 0; i < n; i++) {
progress(carList);
printProgress(carList);
}

getResult(carList);
}

private static void progress(ArrayList<Car> carList) {
for (Car c : carList) {
c.move();
}
}

private static void printProgress(ArrayList<Car> carList) {
for (Car c : carList) {
System.out.println(c.getName() + " : " + "-".repeat(c.getMoveCount()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

repeat으로 깔끔하게 결과를 출력하셨네요 좋아요🚀

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

가독성을 높일 수 있는 방법을 계속 고민해봐야겠어요 🥳

매번 꼼꼼히 리뷰해주셔서 감사해요~~~!!

}
System.out.println();
}

private static void getResult(ArrayList<Car> carList) {
Collections.sort(carList);
int max = carList.getFirst().getMoveCount();
ArrayList<String> winnerList = new ArrayList<>();
for (Car c : carList) {
if (c.getMoveCount() == max)
winnerList.add(c.getName());
else break;
}

String result = String.join(",", winnerList);
System.out.println("최종 우승자 : " + result);
}
}
46 changes: 46 additions & 0 deletions src/main/java/racingcar/game/InputException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package racingcar.game;

import camp.nextstep.edu.missionutils.Console;
import racingcar.ErrorMessage;
import racingcar.car.Car;

import java.util.ArrayList;
import java.util.HashSet;

public class InputException {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

InputException이라는 클래스명에서 입력 오류를 처리할 것이라는 기대를 하게 되는데, 입력을 받는 것까지 처리해서 혼동이 올 수 있어보여요
분리하면 좋을 것 같습니다!

@mingdodev mingdodev Nov 4, 2024

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

어쩐지 클래스 이름을 지을 때 어떻게 지을지 고민하게 되더라고요 분리해달라고 클래스에서 보내는 신호였구나....!!
이름을 어떻게 바꿀지만 고민했는데 기능 분리가 더 올바른 방법이었네요!!

public static void getCarName(ArrayList<Car> carList) {
System.out.println("경주할 자동차 이름을 입력하세요. (이름은 쉼표(,) 기준으로 구분)");

try {
String[] carInput = Console.readLine().split(",");
validateCarList(carInput);
for (String s : carInput) {
validateCarName(s);
carList.add(new Car(s));
}
} catch (Exception e) {
throw new IllegalArgumentException(ErrorMessage.INVALID_INPUT.toString());
}
}
public static void validateCarList(String[] carInput) {
HashSet<String> carNameSet = new HashSet<>();
for (String s : carInput) {
if (!carNameSet.add(s)) {
throw new IllegalArgumentException(ErrorMessage.DUPLICATE_NAME.toString());
}
}
Comment on lines +26 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

저는 중복검사를 매번 데이터가 들어갈 때마다 Stream API의 anyMatch로 검사했는데요!
HashSet이 시간복잡도는 더 낫지만 중복검사용 데이터를 따로 저장해야 한다는 측면에서 두 방식의 장단점이 있는것 같습니다!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Stream API를 사용하는 방법이 있었네요!
HashSet을 따로 만드는 것이 좀 부자연스럽게 느껴졌는데, 다음에는 anyMatch를 사용해봐야겠어요!

}
public static void validateCarName(String s) {
if (s.length() > 5)
throw new IllegalArgumentException(ErrorMessage.CAR_NAME.toString());
}
public static int getTryCount() {
System.out.println("시도할 횟수는 몇 회인가요?");

try {
return Integer.parseInt(Console.readLine());
} catch (NumberFormatException e) {
throw new IllegalArgumentException(ErrorMessage.TRY_COUNT.toString());
}
}
}
8 changes: 8 additions & 0 deletions src/test/java/racingcar/ApplicationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ class ApplicationTest extends NsTest {
);
}

@Test
void 자동차_이름은_중복될_수_없다() {
assertSimpleTest(()->
assertThatThrownBy(() -> runException("pobi,pobi,eddy", "1"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Override
public void runMain() {
Application.main(new String[]{});
Expand Down