diff --git a/README.md b/README.md index d0286c859f..6ac03a26dd 100644 --- a/README.md +++ b/README.md @@ -1 +1,39 @@ # java-racingcar-precourse + +# 기능 목록 + +- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. +- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. +- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. +- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. +- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. +- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. +- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다. +- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다. + +# 구현할 기능 + +## 자동차 + +- [x] 주어진 횟수 동안 자동차의 전진/정지 기능 +- [x] 자동차에 이름 부여 기능 +- [x] 0에서 9까지의 무작위 값 구하기 및 4 이상인 경우 전진 +- [x] 예외 처리 + - [x] 이름 없는 경우 + - [x] 자동차 이름이 5글자를 초과한 경우 + +## 입력 + +- [x] 쉼표(,)를 기준으로 구분된 5자 이하의 자동차 이름 입력 +- [x] 이동 횟수 입력 +- [x] 잘못된 값을 입력한 경우 예외 처리 + - [x] 같은 이름의 자동차가 들어오는 경우 + - [x] 자동차 이름이 5글자를 초과한 경우 + - [x] 이름이 없는 경우 → 오류 출력 + - [x] 시도하는 횟수 입력이 올바르지 않은 경우 + +## 출력 + +- [x] 자동차 출력 (이름과 전진한 정도) +- [x] 우승자 출력 + - [x] 여러명인 경우 쉼표(,)로 구분하여 출력 \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e724..210caa3618 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,46 @@ package racingcar; +import camp.nextstep.edu.missionutils.Console; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + String carNameStrings = Console.readLine(); + + // view 생성 + Output view = new Output(); + + // model 생성 + List cars = new ArrayList<>(); + Set carNamesSet = new HashSet<>(); + for (String carName : carNameStrings.split(",", -1)) { + if (carNamesSet.contains(carName)) { + throw new IllegalArgumentException(); + } + cars.add(new Car(carName)); + carNamesSet.add(carName); + } + + // controller 생성 + RacingCarController controller = new RacingCarController(view, cars); + + System.out.println("시도할 횟수는 몇 회인가요?"); + String playTimeString = Console.readLine(); + + int playTime = -1; + try { + playTime = Integer.parseInt(playTimeString); + } catch (Exception e) { + throw new IllegalArgumentException(); + } + if (playTime < 0) { + throw new IllegalArgumentException(); + } + + controller.play(playTime); } } diff --git a/src/main/java/racingcar/Car.java b/src/main/java/racingcar/Car.java new file mode 100644 index 0000000000..38abd2306f --- /dev/null +++ b/src/main/java/racingcar/Car.java @@ -0,0 +1,43 @@ +package racingcar; + +import camp.nextstep.edu.missionutils.Randoms; + +public class Car { + private Integer location; + private String name; + + public Car(String name) { + location = 0; + name = name.trim(); + if (name.isEmpty() || name.length() > 5) { + throw new IllegalArgumentException(); + } + this.name = name; + } + + public Car() { + location = 0; + throw new IllegalArgumentException(); + } + + private void move() { + location++; + } + + public void randomMove() { + int randomValue = Randoms.pickNumberInRange(0,9); + if (randomValue >= 4) { + this.move(); + } else { + // 정지 + } + } + + public Integer getLocation() { + return location; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/racingcar/Output.java b/src/main/java/racingcar/Output.java new file mode 100644 index 0000000000..f55db86896 --- /dev/null +++ b/src/main/java/racingcar/Output.java @@ -0,0 +1,27 @@ +package racingcar; + +import java.util.List; + +public class Output { + private static final String ONE_STEP = "-"; + + public void printResultString() { + System.out.println("실행 결과"); + } + + public void printCars(List cars) { + for (Car car : cars) { + System.out.print(car.getName()); + System.out.print(" : "); + System.out.println(ONE_STEP.repeat(car.getLocation())); + } + System.out.println(); + } + + public void printWinners(List winners) { + System.out.print("최종 우승자 : "); + for (String winner : winners) { + System.out.print(winner); + } + } +} diff --git a/src/main/java/racingcar/RacingCarController.java b/src/main/java/racingcar/RacingCarController.java new file mode 100644 index 0000000000..6cbdb46d91 --- /dev/null +++ b/src/main/java/racingcar/RacingCarController.java @@ -0,0 +1,43 @@ +package racingcar; + +import java.util.ArrayList; +import java.util.List; + +public class RacingCarController { + + private Output output; + private List cars; + + public RacingCarController(Output output, List cars) { + this.output = output; + this.cars = cars; + } + + public void play(int time) { + output.printResultString(); + for (int i = 0; i < time; i++) { + for (Car car : cars) { + car.randomMove(); + } + output.printCars(cars); + } + List winners = findWinner(); + output.printWinners(winners); + } + + public List findWinner() { + List winners = new ArrayList<>(); + int maxLocation = 0; + + for (Car car : cars) { + if (maxLocation < car.getLocation()) { + winners.clear(); + winners.add(car.getName()); + } else if (maxLocation == car.getLocation()) { + winners.add(car.getName()); + } + } + + return winners; + } +} diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 1d35fc33fe..24e9c0a41f 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -12,6 +12,54 @@ class ApplicationTest extends NsTest { private static final int MOVING_FORWARD = 4; private static final int STOP = 3; + @Test + void 중복_이름_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,pobi", "1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 뒤_빈_이름_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,", "1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 앞_빈_이름_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException(",pobi", "1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 가운데_빈_이름_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,,hello", "1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 시도_횟수_비정상_알파벳_예외_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,hello", "a")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 시도_횟수_음수_예외_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,hello", "-1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + @Test void 기능_테스트() { assertRandomNumberInRangeTest( @@ -24,13 +72,69 @@ class ApplicationTest extends NsTest { } @Test - void 예외_테스트() { + void 글자_5자_초과_예외_테스트() { assertSimpleTest(() -> assertThatThrownBy(() -> runException("pobi,javaji", "1")) .isInstanceOf(IllegalArgumentException.class) ); } + // 출력 테스트 + @Test + void 자동차_1개_출력_테스트() { + assertRandomNumberInRangeTest( + () -> { + run("pobi", "2"); + assertThat(output()).contains("pobi : --"); + }, + MOVING_FORWARD, STOP + ); + } + + @Test + void 자동차_2개_출력_테스트() { + assertRandomNumberInRangeTest( + () -> { + run("pobi,woni", "1"); + assertThat(output()).contains("pobi : -", "woni : "); + }, + MOVING_FORWARD, STOP + ); + } + + @Test + void 자동차_3개_출력_테스트() { + assertRandomNumberInRangeTest( + () -> { + run("pobi,woni,hehe", "3"); + assertThat(output()).contains("pobi : -", "woni : ", "hehe : --"); + }, + MOVING_FORWARD, STOP + ); + } + + @Test + void 전체_출력_테스트() { + assertRandomNumberInRangeTest( + () -> { + run("pobi,woni,hehe", "3"); + assertThat(output()).contains("pobi : -", "woni : ", "hehe : --", "최종 우승자 : hehe"); + }, + MOVING_FORWARD, STOP + ); + } + + @Test + void 공동_우승_출력_테스트() { + assertRandomNumberInRangeTest( + () -> { + run("pobi,woni,hehe", "1"); + assertThat(output()).contains("pobi : -", "woni : -", "hehe : ", "최종 우승자 : pobi, woni"); + }, + MOVING_FORWARD, STOP + ); + } + @Override public void runMain() { Application.main(new String[]{}); diff --git a/src/test/java/racingcar/CarTest.java b/src/test/java/racingcar/CarTest.java new file mode 100644 index 0000000000..afcb3e855e --- /dev/null +++ b/src/test/java/racingcar/CarTest.java @@ -0,0 +1,54 @@ +package racingcar; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.Test; + +public class CarTest { + + @Test + void createCarWithName() { + // 자동차는 초기 위치가 0 + Car car = new Car("car1"); + assertEquals(car.getLocation(), 0); + assertEquals(car.getName(), "car1"); + } + + @Test + void createCarWithWrongName() { + // 자동차의 이름은 5자 이하 + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> new Car("haeseung")); + } + + @Test + void createCarWithoutName() { + // 자동차 이름이 없는 경우 오류 출력 + // 없는 경우 + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> new Car()); + // 공백 + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> new Car("")); + // 스페이스바 + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> new Car(" ")); + // 탭 + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> new Car(" ")); + } + + @Test + void randomMoveCar() { + // 자동차 확률적으로 전진/정지 + Car car = new Car("car1"); + int preLocation = car.getLocation(); + car.randomMove(); + int curLocation = car.getLocation(); + assertThat(curLocation).isBetween(preLocation, curLocation); + } + +}