diff --git a/docs/README.md b/docs/README.md index e69de29..a74bb69 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,17 @@ +# 입력 기능 +경주할 자동차 이름 입력 받기 (쉼표 구분) +시도할 횟수 입력 받기 +# 유효성 검사 기능 +자동차 이름이 공백인 경우 IllegalArgumentException 발생 +자동차 이름이 5자 초과인 경우 IllegalArgumentException 발생 +시도 횟수가 숫자가 아닌 경우 IllegalArgumentException 발생 +시도 횟수가 0 이하인 경우 IllegalArgumentException 발생 +# 자동차 기능 +자동차 이름과 위치(position) 관리 +0~9 무작위 값이 4 이상이면 전진 +# 게임 진행 기능 +입력한 횟수만큼 라운드 반복 실행 +매 라운드 결과 출력 (이름 + 진행 막대) +# 우승자 계산 및 출력 기능 +최대 위치값을 가진 자동차를 우승자로 선정 +공동 우승자 쉼표로 구분하여 출력 \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e..66181f2 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -2,6 +2,7 @@ public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + RacingGame game = new RacingGame(); + game.run(); } -} +} \ No newline at end of file diff --git a/src/main/java/racingcar/Car.java b/src/main/java/racingcar/Car.java new file mode 100644 index 0000000..0a40eb7 --- /dev/null +++ b/src/main/java/racingcar/Car.java @@ -0,0 +1,23 @@ +package racingcar; + +public class Car { + private final String name; + private int position; + + public Car(String name) { + this.name = name; + this.position = 0; + } + + public void move() { + position++; + } + + public String getName() { + return name; + } + + public int getPosition() { + return position; + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/InputHandler.java b/src/main/java/racingcar/InputHandler.java new file mode 100644 index 0000000..b2f1b81 --- /dev/null +++ b/src/main/java/racingcar/InputHandler.java @@ -0,0 +1,30 @@ +package racingcar; + +import camp.nextstep.edu.missionutils.Console; +import java.util.ArrayList; +import java.util.List; + +public class InputHandler { + + public List readCars() { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + String input = Console.readLine(); + String[] names = input.split(",", -1); + return parseCars(names); + } + + private List parseCars(String[] names) { + List cars = new ArrayList<>(); + for (String name : names) { + Validator.validateCarName(name.trim()); + cars.add(new Car(name.trim())); + } + return cars; + } + + public int readAttemptCount() { + System.out.println("시도할 회수는 몇회인가요?"); + String input = Console.readLine(); + return Validator.validateAndParseAttemptCount(input); + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/OutputHandler.java b/src/main/java/racingcar/OutputHandler.java new file mode 100644 index 0000000..b26ebd1 --- /dev/null +++ b/src/main/java/racingcar/OutputHandler.java @@ -0,0 +1,29 @@ +package racingcar; + +import java.util.List; +import java.util.stream.Collectors; + +public class OutputHandler { + + public void printRoundResult(List cars) { + for (Car car : cars) { + System.out.println(car.getName() + " : " + buildProgressBar(car.getPosition())); + } + System.out.println(); + } + + private String buildProgressBar(int position) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < position; i++) { + sb.append("-"); + } + return sb.toString(); + } + + public void printWinners(List winners) { + String names = winners.stream() + .map(Car::getName) + .collect(Collectors.joining(", ")); + System.out.println("최종 우승자 : " + names); + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/RacingGame.java b/src/main/java/racingcar/RacingGame.java new file mode 100644 index 0000000..82c8c80 --- /dev/null +++ b/src/main/java/racingcar/RacingGame.java @@ -0,0 +1,67 @@ +package racingcar; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.List; +import java.util.stream.Collectors; + +public class RacingGame { + + private static final int MOVE_THRESHOLD = 4; + + private final InputHandler inputHandler; + private final OutputHandler outputHandler; + + public RacingGame() { + this.inputHandler = new InputHandler(); + this.outputHandler = new OutputHandler(); + } + + public void run() { + List cars = inputHandler.readCars(); + int attemptCount = inputHandler.readAttemptCount(); + + System.out.println(); + System.out.println("실행 결과"); + + playRounds(cars, attemptCount); + + outputHandler.printWinners(findWinners(cars)); + } + + private void playRounds(List cars, int attemptCount) { + for (int i = 0; i < attemptCount; i++) { + playOneRound(cars); + outputHandler.printRoundResult(cars); + } + } + + private void playOneRound(List cars) { + for (Car car : cars) { + moveIfPossible(car); + } + } + + private void moveIfPossible(Car car) { + if (isMovable()) { + car.move(); + } + } + + private boolean isMovable() { + return Randoms.pickNumberInRange(0, 9) >= MOVE_THRESHOLD; + } + + private List findWinners(List cars) { + int maxPosition = findMaxPosition(cars); + return cars.stream() + .filter(car -> car.getPosition() == maxPosition) + .collect(Collectors.toList()); + } + + private int findMaxPosition(List cars) { + return cars.stream() + .mapToInt(Car::getPosition) + .max() + .orElse(0); + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/Validator.java b/src/main/java/racingcar/Validator.java new file mode 100644 index 0000000..07f6b25 --- /dev/null +++ b/src/main/java/racingcar/Validator.java @@ -0,0 +1,27 @@ +package racingcar; + +public class Validator { + + private static final int MAX_NAME_LENGTH = 5; + + public static void validateCarName(String name) { + if (name.isBlank()) { + throw new IllegalArgumentException("자동차 이름은 공백일 수 없습니다."); + } + if (name.length() > MAX_NAME_LENGTH) { + throw new IllegalArgumentException("자동차 이름은 5자 이하만 가능합니다: " + name); + } + } + + public static int validateAndParseAttemptCount(String input) { + try { + int count = Integer.parseInt(input.trim()); + if (count <= 0) { + throw new IllegalArgumentException("시도 횟수는 1 이상이어야 합니다."); + } + return count; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("시도 횟수는 숫자여야 합니다."); + } + } +} \ No newline at end of file diff --git a/src/test/java/racingcar/RacingGameTest.java b/src/test/java/racingcar/RacingGameTest.java new file mode 100644 index 0000000..32d7ea0 --- /dev/null +++ b/src/test/java/racingcar/RacingGameTest.java @@ -0,0 +1,59 @@ +package racingcar; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +class RacingGameTest { + + @Test + @DisplayName("자동차가 이동 후 위치가 1 증가한다") + void carMoveTest() { + Car car = new Car("pobi"); + car.move(); + assertThat(car.getPosition()).isEqualTo(1); + } + + @Test + @DisplayName("이동하지 않으면 위치는 0이다") + void carNoMoveTest() { + Car car = new Car("woni"); + assertThat(car.getPosition()).isEqualTo(0); + } + + @Test + @DisplayName("자동차 이름이 5자를 초과하면 예외가 발생한다") + void invalidCarNameLengthTest() { + assertThatThrownBy(() -> Validator.validateCarName("toolongname")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("자동차 이름이 공백이면 예외가 발생한다") + void blankCarNameTest() { + assertThatThrownBy(() -> Validator.validateCarName(" ")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("시도 횟수가 숫자가 아니면 예외가 발생한다") + void invalidAttemptCountFormatTest() { + assertThatThrownBy(() -> Validator.validateAndParseAttemptCount("abc")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("시도 횟수가 0 이하이면 예외가 발생한다") + void invalidAttemptCountRangeTest() { + assertThatThrownBy(() -> Validator.validateAndParseAttemptCount("0")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("5자 이하의 유효한 이름은 예외가 발생하지 않는다") + void validCarNameTest() { + assertThatCode(() -> Validator.validateCarName("pobi")) + .doesNotThrowAnyException(); + } +} \ No newline at end of file