diff --git a/README.md b/README.md index d0286c859f..f472898ede 100644 --- a/README.md +++ b/README.md @@ -1 +1,45 @@ # java-racingcar-precourse + +## 기능 요구 사항 +1. 경주할 **자동차 이름**과 **시도할 횟수**를 입력받는다. + +> **[입력 형식]** +경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분) +chae,morol +시도할 횟수는 몇 회인가요? +3 + + 1-1. 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 종료되어야 한다. + + +2. 각 자동차마다 **0에서 9** 사이의 난수를 생성한다. +3. 생성된 난수가 **4 이상**이면 해당 자동차를 **전진**시킨다. +4. 모든 자동차의 1회 시도가 끝나면, 자동차 이름과 함께 **누적 전진**한 칸만큼 "-"를 출력한다. +> **[출력 형식]** +chae : --- +morol : -- + +*(입력받은 시도 횟수만큼 **2, 3, 4**의 과정을 반복한다.)* + + +5. 경주한 자동차 중 최종적으로 **전진한 값**이 가장 높은 자동차를 우승자로 선정한다. + 5-1. 여러 자동차가 동시에 가장 높은 값을 가졌을 경우 **공동 우승자**로 선정한다. + + +6. 우승한 자동차의 이름을 **우승자 안내** 문구와 함께 출력한다. +> **[출력 형식]** +최종 우승자 : chae, morol + + +## 예외 케이스 + +**[자동차 이름 입력]** +- 쉼표가 포함되지 않은 문자열 +- 구분한 결과가 5자 초과 +- 비어 있는 값 + +**[시도할 횟수 입력]** +- 0 또는 음수 +- 정수가 아닌 모든 수 +- 문자, 문자열 등 숫자가 아닌 값 +- 비어 있는 값 \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e724..43d9ba1695 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,15 @@ package racingcar; +import racingcar.controller.RacingGameController; +import racingcar.model.RacingGame; +import racingcar.view.RacingGameView; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + RacingGameView view = new RacingGameView(); + RacingGame model = new RacingGame(); + RacingGameController controller = new RacingGameController(model, view); + + controller.runGame(); } -} +} \ No newline at end of file diff --git a/src/main/java/racingcar/controller/RacingGameController.java b/src/main/java/racingcar/controller/RacingGameController.java new file mode 100644 index 0000000000..b237dc811c --- /dev/null +++ b/src/main/java/racingcar/controller/RacingGameController.java @@ -0,0 +1,52 @@ +package racingcar.controller; + +import racingcar.model.RacingGame; +import racingcar.view.RacingGameView; +import camp.nextstep.edu.missionutils.Console; + +import java.util.List; + +public class RacingGameController { + private final RacingGame racingGame; + private final RacingGameView racingGameView; + + public RacingGameController(RacingGame racingGame, RacingGameView racingGameView) { + this.racingGame = racingGame; + this.racingGameView = racingGameView; + } + + public void runGame() { + initializeGame(); + playGame(); + announceWinners(); + } + + private void initializeGame() { + racingGameView.printCarNameInputPrompt(); + String carNames = Console.readLine(); + racingGameView.printRoundCountPrompt(); + int roundCount = getRoundCount(); + + racingGame.initialize(carNames, roundCount); + } + + private int getRoundCount() { + try { + return Integer.parseInt(Console.readLine()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(); + } + } + + private void playGame() { + for (int round = 0; round < racingGame.getRoundCount(); round++) { + racingGame.moveAllCars(); + racingGameView.printRoundResult(racingGame.getCarStates()); + } + } + + private void announceWinners() { + List winners = racingGame.getWinners(); + racingGameView.printWinners(winners); + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/model/Car.java b/src/main/java/racingcar/model/Car.java new file mode 100644 index 0000000000..0ba3d23058 --- /dev/null +++ b/src/main/java/racingcar/model/Car.java @@ -0,0 +1,32 @@ +package racingcar.model; + +import camp.nextstep.edu.missionutils.Randoms; + +public class Car { + private static final int MOVE_THRESHOLD = 4; + private static final int RANDOM_BOUND = 9; + + private final String name; + private int position; + + public Car(String name) { + Validator.validateCarName(name); + this.name = name; + this.position = 0; + } + + public void move() { + Validator.validateCarPosition(position); + if (Randoms.pickNumberInRange(0, RANDOM_BOUND) >= MOVE_THRESHOLD) { + position++; + } + } + + public String getName() { + return name; + } + + public int getPosition() { + return position; + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/model/RacingGame.java b/src/main/java/racingcar/model/RacingGame.java new file mode 100644 index 0000000000..979047b564 --- /dev/null +++ b/src/main/java/racingcar/model/RacingGame.java @@ -0,0 +1,54 @@ +package racingcar.model; + +import java.util.List; +import java.util.stream.Collectors; + +public class RacingGame { + private List cars; + private int roundCount; + + public void initialize(String carNames, int roundCount) { + validate(carNames, roundCount); + this.cars = createCars(carNames); + this.roundCount = roundCount; + } + + private void validate(String carNames, int roundCount) { + Validator.validateInputString(carNames); + Validator.validateRoundCount(roundCount); + } + + private List createCars(String carNames) { + return List.of(carNames.split(",")) + .stream() + .map(String::trim) + .map(Car::new) + .collect(Collectors.toList()); + } + + public void moveAllCars() { + cars.forEach(Car::move); + } + + public List getCarStates() { + return cars.stream() + .map(car -> car.getName() + " : " + "-".repeat(car.getPosition())) + .collect(Collectors.toList()); + } + + public List getWinners() { + int maxPosition = cars.stream() + .mapToInt(Car::getPosition) + .max() + .orElse(0); + + return cars.stream() + .filter(car -> car.getPosition() == maxPosition) + .map(Car::getName) + .collect(Collectors.toList()); + } + + public int getRoundCount() { + return roundCount; + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/model/Validator.java b/src/main/java/racingcar/model/Validator.java new file mode 100644 index 0000000000..d9030bb045 --- /dev/null +++ b/src/main/java/racingcar/model/Validator.java @@ -0,0 +1,29 @@ +package racingcar.model; + +public class Validator { + private static final int MAX_NAME_LENGTH = 5; + + public static void validateInputString(String carNames) { + if (!carNames.contains(",")) { + throw new IllegalArgumentException(); + } + } + + public static void validateCarName(String carName) { + if (carName.isEmpty() || carName.length() > MAX_NAME_LENGTH) { + throw new IllegalArgumentException(); + } + } + + public static void validateRoundCount(int roundCount) { + if (roundCount <= 0) { + throw new IllegalArgumentException(); + } + } + + public static void validateCarPosition(int position) { + if (position < 0) { + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/racingcar/view/RacingGameView.java b/src/main/java/racingcar/view/RacingGameView.java new file mode 100644 index 0000000000..57eb388a8a --- /dev/null +++ b/src/main/java/racingcar/view/RacingGameView.java @@ -0,0 +1,22 @@ +package racingcar.view; + +import java.util.List; + +public class RacingGameView { + public void printCarNameInputPrompt() { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + } + + public void printRoundCountPrompt() { + System.out.println("시도할 횟수는 몇 회인가요?"); + } + + public void printRoundResult(List carStates) { + carStates.forEach(System.out::println); + System.out.println(); + } + + public void printWinners(List winners) { + System.out.println("최종 우승자 : " + String.join(", ", winners)); + } +} \ No newline at end of file diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 1d35fc33fe..284c743bc3 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -31,6 +31,38 @@ class ApplicationTest extends NsTest { ); } + @Test + void 이름_길이_초과_예외_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,woni,javaji,longname,tooLongName", "1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 빈_이름_예외_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,,woni", "1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 음수_시도_횟수_예외_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,woni", "-1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 시도_횟수_예외_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,woni", "abc")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + @Override public void runMain() { Application.main(new String[]{});