diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..a9a54a14f37 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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을 발생시킨 후 애플리케이션은 종료되어야 한다. \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e7242..09d2378c0da 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -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(); } } diff --git a/src/main/java/racingcar/controller/RaceGameController.java b/src/main/java/racingcar/controller/RaceGameController.java new file mode 100644 index 00000000000..ef41398ce55 --- /dev/null +++ b/src/main/java/racingcar/controller/RaceGameController.java @@ -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 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 winnerList = referee.getWinner(carList); + outputView.printWinner(winnerList); + } + + private void playMoveCar(Car car) { + int randomNumber = RandomNumber.generateRandomNumber(); + if (randomNumber >= Car.MOVE_FORWARD_THRESHOLD) { + car.moveForward(); + } + } +} diff --git a/src/main/java/racingcar/model/Car.java b/src/main/java/racingcar/model/Car.java new file mode 100644 index 00000000000..ecca24ba54b --- /dev/null +++ b/src/main/java/racingcar/model/Car.java @@ -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()); + } +} diff --git a/src/main/java/racingcar/model/RandomNumber.java b/src/main/java/racingcar/model/RandomNumber.java new file mode 100644 index 00000000000..1d09b5056e7 --- /dev/null +++ b/src/main/java/racingcar/model/RandomNumber.java @@ -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); + } +} diff --git a/src/main/java/racingcar/model/Referee.java b/src/main/java/racingcar/model/Referee.java new file mode 100644 index 00000000000..10862a42c69 --- /dev/null +++ b/src/main/java/racingcar/model/Referee.java @@ -0,0 +1,11 @@ +package racingcar.model; + +import java.util.Collections; +import java.util.List; + +public class Referee { + public List getWinner(List carList) { + int maxStatus = Collections.max(carList.stream().map(Car::getStatus).toList()); + return carList.stream().filter(car -> car.getStatus() == maxStatus).toList(); + } +} diff --git a/src/main/java/racingcar/validator/InputValidator.java b/src/main/java/racingcar/validator/InputValidator.java new file mode 100644 index 00000000000..289603a138c --- /dev/null +++ b/src/main/java/racingcar/validator/InputValidator.java @@ -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 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 nameList) { + if (nameList.size() > nameList.stream().distinct().count()) { + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 00000000000..a2b344e0b4f --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -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 getCarNameList() { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + String input = Console.readLine(); + List carNameList = Arrays.asList(input.split(",")); + InputValidator.verifyCarNames(carNameList); + return carNameList; + } + + public int getRoundTime() { + System.out.println("시도할 회수는 몇회인가요?"); + String input = Console.readLine(); + return Integer.parseInt(input); + } +} diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 00000000000..8103507b175 --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -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 carList) { + carList.forEach(car -> { + System.out.println(car.getName() + " : " + car.getStatusBar()); + }); + System.out.println(); + } + + public void printWinner(List winnerList) { + System.out.print("최종 우승자 : "); + System.out.println(winnerList.stream().map(Car::getName).collect(Collectors.joining(", "))); + } +} diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 764ba4c627e..1d1480a3a94 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -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[]{}); diff --git a/src/test/java/racingcar/validator/InputValidatorTest.java b/src/test/java/racingcar/validator/InputValidatorTest.java new file mode 100644 index 00000000000..911d24b76ba --- /dev/null +++ b/src/test/java/racingcar/validator/InputValidatorTest.java @@ -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); + } +} diff --git a/src/test/java/racingcar/view/InputViewTest.java b/src/test/java/racingcar/view/InputViewTest.java new file mode 100644 index 00000000000..b26b1956e4c --- /dev/null +++ b/src/test/java/racingcar/view/InputViewTest.java @@ -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 result = inputView.getCarNameList(); + + assertThat(result).isEqualTo(Arrays.asList("pobi", "woni", "jun")); + } +}