diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..ba8eda45a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,39 @@ +# πŸš— κ΅¬ν˜„ κΈ°λŠ₯ λͺ©λ‘ + +## μžλ™μ°¨ +- [x] 각 μžλ™μ°¨μ— 이름을 λΆ€μ—¬ν•  수 μžˆλ‹€. + - [x] **[μ˜ˆμ™Έμ²˜λ¦¬]** μžλ™μ°¨μ˜ 이름이 5자 초과일 경우 μ˜ˆμ™Έμ²˜λ¦¬ν•œλ‹€. +- [x] μ „μ§„ν•˜κ±°λ‚˜ 멈좜 수 μžˆλ‹€. + - [x] μ—°λ£Œκ°€ 4 이상일 경우 μ „μ§„ν•œλ‹€. + - [x] **[μ˜ˆμ™Έμ²˜λ¦¬]** μ—°λ£Œκ°€ 0 미만 9 초과의 값이 λ“€μ–΄μ˜€λ©΄ μ˜ˆμ™Έμ²˜λ¦¬ν•œλ‹€. + + +## μžλ™μ°¨ κ²½μ£Ό κ²Œμž„ +- [x] 경주에 μ°Έμ—¬ν•  μžλ™μ°¨λ“€μ„ λ“±λ‘ν•œλ‹€. + - [x] **[μ˜ˆμ™Έμ²˜λ¦¬]** μ°Έμ—¬ν•  μžλ™μ°¨λŠ” μ΅œμ†Œ 1λŒ€μ—¬μ•Όν•œλ‹€. +- [x] 이동 횟수λ₯Ό λ“±λ‘ν•œλ‹€. + - [x] **[μ˜ˆμ™Έμ²˜λ¦¬]** μ΄λ™νšŸμˆ˜κ°€ 0 μ΄ν•˜λ©΄ μ˜ˆμ™Έμ²˜λ¦¬ν•œλ‹€. +- [x] μžλ™μ°¨ κ²½μ£Όλ₯Ό μ‹œμž‘ν•œλ‹€. + - [x] 각 μ΄λ™νšŸμˆ˜λ§ˆλ‹€ 각 μžλ™μ°¨μ—κ²Œ λžœλ€ν•œ μ—°λ£Œλ₯Ό μ£Όμž…ν•œλ‹€. +- [x] μžλ™μ°¨ κ²½μ£Ό κ²Œμž„μ„ μ™„λ£Œν•œ ν›„ λˆ„κ°€ μš°μŠΉν–ˆλŠ”μ§€λ₯Ό μ•Œλ €μ€€λ‹€.(μ—¬λŸ¬λͺ…μ˜ μš°μŠΉμžκ°€ λ‚˜μ˜¬ 수 μžˆλ‹€.) + + +## μž…λ ₯ +- [x] μžλ™μ°¨ 이름은 μ‰Όν‘œ(,)λ₯Ό κΈ°μ€€μœΌλ‘œ κ΅¬λΆ„ν•˜λ©° 이름을 μž…λ ₯λ°›λŠ”λ‹€. + - [x] **[μ˜ˆμ™Έμ²˜λ¦¬]** μ‰Όν‘œλ₯Ό κΈ°μ€€μœΌλ‘œ λ‚˜λˆ΄μ„ λ•Œ μžλ™μ°¨ 이름이 0보닀 μž‘κ±°λ‚˜ 5보닀 크면 μ˜ˆμ™Έμ²˜λ¦¬ν•œλ‹€. +- [x] μ‚¬μš©μžλŠ” λͺ‡ 번의 이동을 ν•  것인지λ₯Ό μž…λ ₯ν•  수 μžˆμ–΄μ•Ό ν•œλ‹€. + - [x] **[μ˜ˆμ™Έμ²˜λ¦¬]** 이동 νšŸμˆ˜λŠ” μˆ«μžμ—¬μ•Ό ν•œλ‹€. +- [x] μ‚¬μš©μžκ°€ 잘λͺ»λœ 값을 μž…λ ₯ν•  경우 μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό 좜λ ₯ ν›„ κ·Έ λΆ€λΆ„λΆ€ν„° μž…λ ₯을 λ‹€μ‹œ λ°›λŠ”λ‹€. + + +## 좜λ ₯ +- [x] μžλ™μ°¨ 이름과 μžλ™μ°¨μ˜ ν˜„μž¬ μœ„μΉ˜λ₯Ό 좜λ ₯ν•œλ‹€. +- [x] μ΅œμ’… 우승자λ₯Ό 좜λ ₯ν•œλ‹€. + - [x] μš°μŠΉμžκ°€ μ—¬λŸ¬ λͺ…일 경우 μ‰Όν‘œ(,)λ₯Ό μ΄μš©ν•˜μ—¬ κ΅¬λΆ„ν•œλ‹€. + +--- +## λ™μž‘ μˆœμ„œ +1. 경주에 등둝할 μžλ™μ°¨λ₯Ό μž…λ ₯λ°›λŠ”λ‹€. +2. κ²½μ£Ό μ‹œλ„ 횟수λ₯Ό μž…λ ₯λ°›λŠ”λ‹€. +3. μ‹œλ„ 횟수 만큼 μžλ™μ°¨κ°€ μ „μ§„ν•˜κ±°λ‚˜ λ©ˆμΆ˜λ‹€. +4. κ²½μ£Όκ°€ μ’…λ£Œλ˜κ³  우승자λ₯Ό μ•Œλ €μ€€λ‹€. diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index b9ed0456a..242c04ce6 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -3,5 +3,7 @@ public class Application { public static void main(String[] args) { // TODO κ΅¬ν˜„ μ§„ν–‰ + MainController mainController = new MainController(); + mainController.run(); } } diff --git a/src/main/java/racingcar/Car.java b/src/main/java/racingcar/Car.java index ab3df9492..f715ae262 100644 --- a/src/main/java/racingcar/Car.java +++ b/src/main/java/racingcar/Car.java @@ -1,12 +1,51 @@ package racingcar; public class Car { + + public static final int MIN_FUEL = 0; + public static final int MAX_FUEL = 9; + public static final int MIN_MOVE_FUEL = 4; + + public static final int MAX_NAME_LENGTH = 5; + private final String name; private int position = 0; public Car(String name) { + validateName(name); this.name = name; } // μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„ + private void validateName(String name) { + if (name.length() > MAX_NAME_LENGTH) { + throw new IllegalArgumentException(Message.CAR_NAME_VALIDATION); + } + } + + + public void moveOrStop(int fuel) { + validateFuel(fuel); + if (fuel >= MIN_MOVE_FUEL) { + move(); + } + } + + private void validateFuel(int fuel) { + if (fuel < MIN_FUEL || fuel > MAX_FUEL) { + throw new IllegalArgumentException(Message.CAR_FUEL_VALIDATION); + } + } + + private void move() { + position++; + } + + public int getCurrentPosition() { + return position; + } + + public String getName() { + return name; + } } diff --git a/src/main/java/racingcar/CarGame.java b/src/main/java/racingcar/CarGame.java new file mode 100644 index 000000000..896547c5e --- /dev/null +++ b/src/main/java/racingcar/CarGame.java @@ -0,0 +1,70 @@ +package racingcar; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class CarGame { + + private final List cars; + private int moveCounts; + + public CarGame() { + cars = new ArrayList<>(); + } + + public void registerCar(List cars) { + validateCars(cars); + for (String carName : cars) { + this.cars.add(new Car(carName)); + } + } + + private void validateCars(List cars) { + if (cars.isEmpty()) { + throw new IllegalArgumentException(Message.NUMBER_OF_CARS_VALIDATION); + } + } + + public List getCars() { + return cars; + } + + public void registerMoveCounts(int moveCounts) { + validateMoveCounts(moveCounts); + this.moveCounts = moveCounts; + } + + private void validateMoveCounts(int moveCounts) { + if (moveCounts <= 0) { + throw new IllegalArgumentException(Message.MOVE_COUNTS_VALIDATION); + } + } + + public void startRacing() { + if (checkIsOver()) { + throw new IllegalStateException(Message.GAME_OVER); + } + for (Car car : cars) { + car.moveOrStop(getRandomFuel()); + } + moveCounts--; + } + + public boolean checkIsOver() { + return moveCounts <= 0; + } + + private int getRandomFuel() { + return Randoms.pickNumberInRange(Car.MIN_FUEL, Car.MAX_FUEL); + } + + public List announceWinners() { + return cars.stream() + .filter(car -> car.getCurrentPosition() == cars.stream().map(Car::getCurrentPosition) + .max(Integer::compareTo).orElse(0)) + .map(Car::getName) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/racingcar/MainController.java b/src/main/java/racingcar/MainController.java new file mode 100644 index 000000000..182d680e0 --- /dev/null +++ b/src/main/java/racingcar/MainController.java @@ -0,0 +1,57 @@ +package racingcar; + +import java.util.List; +import java.util.function.Supplier; +import racingcar.view.InputView; +import racingcar.view.OutputView; + +public class MainController { + + private final CarGame carGame; + private final InputView inputView; + private final OutputView outputView; + + public MainController() { + carGame = new CarGame(); + inputView = new InputView(); + outputView = new OutputView(); + } + + public void run() { + registerCars(); + registerMoveCounts(); + startRace(); + endRace(); + } + + private void registerCars() { + List inputCars = repeat(inputView::inputCars); + carGame.registerCar(inputCars); + } + + private T repeat(Supplier inputReader) { + try { + return inputReader.get(); + } catch (IllegalArgumentException e) { + outputView.printError(e.getMessage()); + return repeat(inputReader); + } + } + + private void registerMoveCounts() { + int moveCounts = repeat(inputView::inputMoveCounts); + carGame.registerMoveCounts(moveCounts); + } + + private void startRace() { + outputView.printRaceStart(); + while (!carGame.checkIsOver()) { + carGame.startRacing(); + outputView.printCars(carGame.getCars()); + } + } + + private void endRace() { + outputView.printWinners(carGame.announceWinners()); + } +} diff --git a/src/main/java/racingcar/Message.java b/src/main/java/racingcar/Message.java new file mode 100644 index 000000000..59008b9e6 --- /dev/null +++ b/src/main/java/racingcar/Message.java @@ -0,0 +1,17 @@ +package racingcar; + +public class Message { + + public static final String CAR_NAME_VALIDATION = "μžλ™μ°¨μ˜ 이름은 1~5μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."; + public static final String CAR_FUEL_VALIDATION = "μ—°λ£ŒλŠ” 0 이상 9 μ΄ν•˜μ˜ 값이어야 ν•©λ‹ˆλ‹€."; + public static final String NUMBER_OF_CARS_VALIDATION = "등둝할 μžλ™μ°¨λŠ” μ΅œμ†Œ 1λŒ€μ—¬μ•Ό ν•©λ‹ˆλ‹€."; + public static final String MOVE_COUNTS_VALIDATION = "μ΄λ™νšŸμˆ˜λŠ” μ΅œμ†Œ 1회 이상이어야 ν•©λ‹ˆλ‹€."; + public static final String GAME_OVER = "κ²½κΈ°κ°€ μ’…λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€."; + + public static final String INPUT_CAR_MESSAGE = "κ²½μ£Όν•  μžλ™μ°¨ 이름을 μž…λ ₯ν•˜μ„Έμš”.(이름은 μ‰Όν‘œ(,) κΈ°μ€€μœΌλ‘œ ꡬ뢄)"; + public static final String INPUT_MOVE_COUNTS_MESSAGE = "μ‹œλ„ν•  νšŒμˆ˜λŠ” λͺ‡νšŒμΈκ°€μš”?"; + public static final String INPUT_NUMBER_ERROR = "숫자만 μž…λ ₯이 κ°€λŠ₯ν•©λ‹ˆλ‹€."; + + public static final String OUTPUT_RACE_RESULT = "\nμ‹€ν–‰ κ²°κ³Ό"; + +} diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 000000000..ae0cbbfb2 --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,49 @@ +package racingcar.view; + +import camp.nextstep.edu.missionutils.Console; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import racingcar.Car; +import racingcar.Message; + +public class InputView { + + public static final String CAR_REGEX = ","; + private static final String ONLY_NUMBER_REGEX = "^[0-9]+$"; + + public List inputCars() { + System.out.println(Message.INPUT_CAR_MESSAGE); + String input = Console.readLine(); + validateInputCars(input); + String[] inputCars = input.split(CAR_REGEX); + return Arrays.stream(inputCars).collect(Collectors.toList()); + } + + private void validateInputCars(String input) { + if (input.endsWith(CAR_REGEX)) { + throw new IllegalArgumentException(Message.CAR_NAME_VALIDATION); + } + + String[] inputCars = input.split(CAR_REGEX); + for (String inputCar : inputCars) { + if (inputCar.length() > Car.MAX_NAME_LENGTH) { + throw new IllegalArgumentException(Message.CAR_NAME_VALIDATION); + } + } + } + + public int inputMoveCounts() { + System.out.println(Message.INPUT_MOVE_COUNTS_MESSAGE); + String input = Console.readLine(); + validateNumber(input); + return Integer.parseInt(input); + } + + private void validateNumber(String input) { + if (!Pattern.matches(ONLY_NUMBER_REGEX, input)) { + throw new IllegalArgumentException(Message.INPUT_NUMBER_ERROR); + } + } +} diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 000000000..5d76a7da8 --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -0,0 +1,51 @@ +package racingcar.view; + +import java.util.List; +import racingcar.Car; +import racingcar.Message; + +public class OutputView { + + public static final String CAR_FORMAT = "%s : %s\n"; + public static final String MOVE_UNIT = "-"; + public static final String DELIMITER = ", "; + public static final String ERROR_SUFFIX = "[ERROR]"; + public static final String WINNER_FORMAT = "μ΅œμ’… 우승자 : %s"; + public static final String ERROR_FORMAT = "%s %s\n"; + + public void printCars(List cars) { + for (Car car : cars) { + printCar(car); + } + printNewLine(); + } + + private void printNewLine() { + System.out.println(); + } + + public void printRaceStart() { + System.out.println(Message.OUTPUT_RACE_RESULT); + } + + private void printCar(Car car) { + System.out.printf(CAR_FORMAT, car.getName(), getMove(car.getCurrentPosition())); + } + + private String getMove(int currentPosition) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < currentPosition; i++) { + stringBuilder.append(MOVE_UNIT); + } + return stringBuilder.toString(); + } + + public void printWinners(List winners) { + String winnersName = String.join(DELIMITER, winners); + System.out.printf(WINNER_FORMAT, winnersName); + } + + public void printError(String errorMessage) { + System.out.printf(ERROR_FORMAT, ERROR_SUFFIX, errorMessage); + } +} diff --git a/src/test/java/racingcar/CarGameTest.java b/src/test/java/racingcar/CarGameTest.java new file mode 100644 index 000000000..f9ceeb48c --- /dev/null +++ b/src/test/java/racingcar/CarGameTest.java @@ -0,0 +1,78 @@ +package racingcar; + + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("CarGame 클래슀") +class CarGameTest { + private CarGame carGame; + + @BeforeEach + void init() { + carGame = new CarGame(); + } + + @Nested + @DisplayName("μžλ™μ°¨ 등둝") + class Describe_Register { + + @Test + @DisplayName("성곡 ν…ŒμŠ€νŠΈ") + void μžλ™μ°¨_등둝_성곡_ν…ŒμŠ€νŠΈ() { + //when + List cars = Arrays.asList("pobi", "woni", "jun"); + carGame.registerCar(cars); + //then + List carNames = carGame.getCars() + .stream() + .map(Car::getName) + .collect(Collectors.toList()); + + assertThat(carNames) + .contains("pobi", "woni", "jun"); + } + + @Test + @DisplayName("μ‹€νŒ¨μ‹œ μ—λŸ¬λ°œμƒ") + void μžλ™μ°¨_등둝_μ‹€νŒ¨_ν…ŒμŠ€νŠΈ() { + //when + List cars = new ArrayList<>(); + //then + assertThatThrownBy(() -> carGame.registerCar(cars)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Test + @DisplayName("μ΄λ™νšŸμˆ˜ 등둝 μ‹€νŒ¨ ν…ŒμŠ€νŠΈ") + void μ΄λ™νšŸμˆ˜_등둝_μ‹€νŒ¨_ν…ŒμŠ€νŠΈ() { + int moveCounts = 0; + + assertThatThrownBy(() -> carGame.registerMoveCounts(moveCounts)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("μžλ™μ°¨ κ²½μ£Ό 우승자 ν…ŒμŠ€νŠΈ") + void κ²½μ£Ό_우승자_ν…ŒμŠ€νŠΈ() { + //given + List cars = Arrays.asList("pobi", "woni", "jun"); + carGame.registerCar(cars); + carGame.registerMoveCounts(5); + //when + carGame.startRacing(); + //then + List winners = carGame.announceWinners(); + assertThat(winners).isSubsetOf(cars); + } +} diff --git a/src/test/java/racingcar/CarTest.java b/src/test/java/racingcar/CarTest.java new file mode 100644 index 000000000..8e666d8ae --- /dev/null +++ b/src/test/java/racingcar/CarTest.java @@ -0,0 +1,65 @@ +package racingcar; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("Car 클래슀") +class CarTest { + + @Nested + @DisplayName("μžλ™μ°¨ 이름 μœ νš¨μ„± 검사") + class Describe_Car_Init { + + @Test + @DisplayName("이름이 5자 μ΄ν•˜λ©΄ μ΄ˆκΈ°ν™” 성곡") + void 성곡_ν…ŒμŠ€νŠΈ() { + Car fives = new Car("fives"); + assertThat(fives).isNotNull(); + } + + @Test + @DisplayName("이름이 5자 초과이면 μ˜ˆμ™Έμ²˜λ¦¬ν•œλ‹€.") + void μ‹€νŒ¨_ν…ŒμŠ€νŠΈ() { + //given + assertThatThrownBy(() -> new Car("sixCar")) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + @DisplayName("μ „μ§„ ν…ŒμŠ€νŠΈ") + class Describe_MoveOrStop { + + @Test + @DisplayName("λ“€μ–΄μ˜¨ 값이 4 이상이면 μ „μ§„ν•œλ‹€.") + void μžλ™μ°¨_μ „μ§„_ν…ŒμŠ€νŠΈ() { + //given + Car pobi = new Car("pobi"); + //when + int prePosition = pobi.getCurrentPosition(); + pobi.moveOrStop(4); + //then + int currentPostion = pobi.getCurrentPosition(); + assertThat(prePosition + 1).isEqualTo(currentPostion); + } + + @Test + @DisplayName("λ“€μ–΄μ˜¨ 값이 4 미만이면 μ •μ§€ν•œλ‹€.") + void μžλ™μ°¨_μ •μ§€_ν…ŒμŠ€νŠΈ() { + //given + Car pobi = new Car("pobi"); + //when + int prePosition = pobi.getCurrentPosition(); + pobi.moveOrStop(3); + //then + int currentPostion = pobi.getCurrentPosition(); + assertThat(prePosition).isEqualTo(currentPostion); + } + } + +}