diff --git a/src/main/java/RacingCarApplication.java b/src/main/java/RacingCarApplication.java new file mode 100644 index 00000000..72de2511 --- /dev/null +++ b/src/main/java/RacingCarApplication.java @@ -0,0 +1,10 @@ +import controller.RacingGameController; +import domain.RandomNumberGenerateStrategy; + +public class RacingCarApplication { + + public static void main(String[] args) { + final RacingGameController controller = new RacingGameController(); + controller.run(new RandomNumberGenerateStrategy()); + } +} diff --git a/src/main/java/controller/RacingGameController.java b/src/main/java/controller/RacingGameController.java new file mode 100644 index 00000000..18905e09 --- /dev/null +++ b/src/main/java/controller/RacingGameController.java @@ -0,0 +1,34 @@ +package controller; + +import domain.Cars; +import domain.CarsMapper; +import domain.NumberGenerateStrategy; +import domain.RacingGame; +import java.util.List; +import view.InputView; +import view.OutputView; + +public class RacingGameController { + + public void run(final NumberGenerateStrategy strategy) { + final List carNames = InputView.readCarNames(); + final Cars cars = CarsMapper.mapByCarNames(strategy, carNames); + final RacingGame racingGame = new RacingGame(cars); + + final int attemptNumber = InputView.readAttemptNumber(); + + OutputView.printResult(); + race(racingGame, attemptNumber); + + final Cars winningCars = racingGame.findWinningCars(); + OutputView.printResultOfWinningCars(winningCars); + } + + private void race(final RacingGame racingGame, + final int attemptNumber) { + for (int i = 0; i < attemptNumber; i++) { + racingGame.race(); + OutputView.printCarsInfo(racingGame.getCars()); + } + } +} diff --git a/src/main/java/domain/Car.java b/src/main/java/domain/Car.java new file mode 100644 index 00000000..c1e5a77e --- /dev/null +++ b/src/main/java/domain/Car.java @@ -0,0 +1,40 @@ +package domain; + +public class Car { + + private final NumberGenerateStrategy strategy; + private final CarName name; + private int position; + + public Car(final NumberGenerateStrategy strategy, + final String name) { + this(strategy, name, 0); + } + + public Car(final NumberGenerateStrategy strategy, + final String name, + final int position) { + this.strategy = strategy; + this.name = CarName.from(name); + this.position = position; + } + + public void move() { + final int value = strategy.generate(); + if (value >= 4) { + position++; + } + } + + public boolean isPositionSame(final int position) { + return this.position == position; + } + + public String getName() { + return name.value(); + } + + public int getPosition() { + return position; + } +} diff --git a/src/main/java/domain/CarName.java b/src/main/java/domain/CarName.java new file mode 100644 index 00000000..c2c92486 --- /dev/null +++ b/src/main/java/domain/CarName.java @@ -0,0 +1,24 @@ +package domain; + +public record CarName( + String value +) { + + private static final int MAX_LENGTH = 5; + + + public static CarName from(final String name) { + validateName(name); + return new CarName(name); + } + + private static void validateName(final String name) { + if (name.isBlank()) { + throw new IllegalArgumentException("[ERROR] 자동차 이름은 공백일 수 없습니다."); + } + + if (name.length() > MAX_LENGTH) { + throw new IllegalArgumentException("[ERROR] 자동차 이름은 5자 이하여야 합니다."); + } + } +} diff --git a/src/main/java/domain/Cars.java b/src/main/java/domain/Cars.java new file mode 100644 index 00000000..4a66e053 --- /dev/null +++ b/src/main/java/domain/Cars.java @@ -0,0 +1,33 @@ +package domain; + +import java.util.List; + +public class Cars { + + private final List cars; + + public Cars(final List cars) { + this.cars = cars; + } + + public void move() { + cars.forEach(Car::move); + } + + public List findCarsByPosition(final int position) { + return cars.stream() + .filter(car -> car.isPositionSame(position)) + .toList(); + } + + public int findMaxPosition() { + return cars.stream() + .mapToInt(Car::getPosition) + .max() + .orElse(0); + } + + public List getCars() { + return List.copyOf(cars); + } +} diff --git a/src/main/java/domain/CarsMapper.java b/src/main/java/domain/CarsMapper.java new file mode 100644 index 00000000..d29b32f7 --- /dev/null +++ b/src/main/java/domain/CarsMapper.java @@ -0,0 +1,15 @@ +package domain; + +import java.util.List; +import java.util.stream.Collectors; + +public class CarsMapper { + + public static Cars mapByCarNames(final NumberGenerateStrategy strategy, + final List carNames) { + final List cars = carNames.stream() + .map(carName -> new Car(strategy, carName)) + .collect(Collectors.toList()); + return new Cars(cars); + } +} diff --git a/src/main/java/domain/NumberGenerateStrategy.java b/src/main/java/domain/NumberGenerateStrategy.java new file mode 100644 index 00000000..b8675210 --- /dev/null +++ b/src/main/java/domain/NumberGenerateStrategy.java @@ -0,0 +1,6 @@ +package domain; + +public interface NumberGenerateStrategy { + + int generate(); +} diff --git a/src/main/java/domain/RacingGame.java b/src/main/java/domain/RacingGame.java new file mode 100644 index 00000000..8eecbc31 --- /dev/null +++ b/src/main/java/domain/RacingGame.java @@ -0,0 +1,26 @@ +package domain; + +import java.util.List; + +public class RacingGame { + + private final Cars cars; + + public RacingGame(final Cars cars) { + this.cars = cars; + } + + public void race() { + cars.move(); + } + + public Cars findWinningCars() { + final int maxPosition = cars.findMaxPosition(); + final List winningCars = cars.findCarsByPosition(maxPosition); + return new Cars(winningCars); + } + + public Cars getCars() { + return cars; + } +} diff --git a/src/main/java/domain/RandomNumberGenerateStrategy.java b/src/main/java/domain/RandomNumberGenerateStrategy.java new file mode 100644 index 00000000..64a44df9 --- /dev/null +++ b/src/main/java/domain/RandomNumberGenerateStrategy.java @@ -0,0 +1,12 @@ +package domain; + +import java.util.Random; + +public class RandomNumberGenerateStrategy implements NumberGenerateStrategy { + + @Override + public int generate() { + final Random random = new Random(); + return random.nextInt(10); + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..cf90f555 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,22 @@ +package view; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; + +public class InputView { + + private static final Scanner scanner = new Scanner(System.in); + + public static List readCarNames() { + System.out.println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)."); + final String carNames = scanner.nextLine(); + return new ArrayList<>(Arrays.asList(carNames.split(","))); + } + + public static int readAttemptNumber() { + System.out.println("시도할 회수는 몇회인가요?"); + return scanner.nextInt(); + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000..99848da6 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,38 @@ +package view; + +import domain.Car; +import domain.Cars; +import java.util.List; + +public class OutputView { + + public static void printResult() { + System.out.println("실행 결과"); + } + + public static void printCarsInfo(final Cars cars) { + for (final Car car : cars.getCars()) { + final String name = car.getName(); + final String position = getPositionFormat(car.getPosition()); + System.out.printf("%s : %s\n", name, position); + } + System.out.println(); + } + + private static String getPositionFormat(final int position) { + final StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < position; i++) { + stringBuilder.append("-"); + } + return stringBuilder.toString(); + } + + public static void printResultOfWinningCars(final Cars winningCars) { + final List cars = winningCars.getCars(); + final List carNames = cars.stream() + .map(Car::getName) + .toList(); + final String winningCarNames = String.join(", ", carNames); + System.out.printf("%s가 최종 우승했습니다.\n", winningCarNames); + } +} diff --git a/src/test/java/domain/CarNameTest.java b/src/test/java/domain/CarNameTest.java new file mode 100644 index 00000000..2b0c0a6d --- /dev/null +++ b/src/test/java/domain/CarNameTest.java @@ -0,0 +1,43 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayName("자동차 이름 테스트") +class CarNameTest { + + @Test + void 자동차_이름_생성() { + // given + final String carName = "코코닥"; + + // when & then + assertThatCode(() -> CarName.from(carName)).doesNotThrowAnyException(); + } + + @Test + void 자동차_이름의_길이가_5보다_크면_생성에_실패한다() { + // given + final String carName = "A".repeat(6); + + // when & then + assertThatThrownBy(() -> CarName.from(carName)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 자동차 이름은 5자 이하여야 합니다."); + } + + @Test + void 자동차_이름은_공백일_수_없다() { + // given + final String carName = " "; + + // when & then + assertThatThrownBy(() -> CarName.from(carName)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 자동차 이름은 공백일 수 없습니다."); + } +} diff --git a/src/test/java/domain/CarTest.java b/src/test/java/domain/CarTest.java new file mode 100644 index 00000000..694d6484 --- /dev/null +++ b/src/test/java/domain/CarTest.java @@ -0,0 +1,82 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(ReplaceUnderscores.class) +class CarTest { + + @Test + void 자동차는_4이상일때_움직인다() { + // given + NumberGenerateStrategy strategy = new TestNumberGenerateStrategy( + new ArrayList<>(Arrays.asList(4)) + ); + final Car car = new Car(strategy, "코코닥"); + + // when + car.move(); + + // then + assertThat(car.getPosition()).isEqualTo(1); + } + + @Test + void 자동차는_4미만일때_움직이지_않는다() { + // given + NumberGenerateStrategy strategy = new TestNumberGenerateStrategy( + new ArrayList<>(Arrays.asList(3)) + ); + final Car car = new Car(strategy, "코코닥"); + + // when + car.move(); + + // then + assertThat(car.getPosition()).isEqualTo(0); + } + + @ParameterizedTest + @CsvSource({ + "8, true", + "4, false" + }) + void 임의의_위치가_자동차의_현재_위치와_같은지_판단할_수_있다(final int position, final boolean expected) { + // given + final Car car = new Car(new RandomNumberGenerateStrategy(), + "코코닥", + 8); + + // when + final boolean actual = car.isPositionSame(position); + + // then + assertThat(actual).isEqualTo(expected); + } + + static class TestNumberGenerateStrategy implements NumberGenerateStrategy { + + private final List numbers; + + public TestNumberGenerateStrategy(final List numbers) { + this.numbers = new ArrayList<>(numbers); + } + + @Override + public int generate() { + if (numbers.isEmpty()) { + throw new IllegalStateException("더 이상 숫자를 생성할 수 없습니다."); + } + return numbers.remove(0); + } + } +} diff --git a/src/test/java/domain/CarsTest.java b/src/test/java/domain/CarsTest.java new file mode 100644 index 00000000..d5514553 --- /dev/null +++ b/src/test/java/domain/CarsTest.java @@ -0,0 +1,70 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import domain.CarTest.TestNumberGenerateStrategy; +import java.util.List; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(ReplaceUnderscores.class) +class CarsTest { + + @Test + void 자동차들을_일괄적으로_움직일수_있다() { + // given + final int movePower = 5; + final int stopPower = 1; + final List cars = List.of( + new Car(new TestNumberGenerateStrategy(List.of(movePower)), "코코닥"), + new Car(new TestNumberGenerateStrategy(List.of(stopPower)), "로이스") + ); + final Cars racingCars = new Cars(cars); + + // when + racingCars.move(); + + // then + assertThat(racingCars.getCars()) + .extracting("position") + .containsExactly(1, 0); + } + + @Test + void 자동차들의_최대_위치를_찾을_수_있다() { + // given + final List cars = List.of( + new Car(new RandomNumberGenerateStrategy(), "코코닥", 12), + new Car(new RandomNumberGenerateStrategy(), "로이스", 4), + new Car(new RandomNumberGenerateStrategy(), "루카", 12) + ); + final Cars racingCars = new Cars(cars); + + // when + int maxPosition = racingCars.findMaxPosition(); + + // then + assertThat(maxPosition).isEqualTo(12); + } + + @Test + void 특정_위치에_있는_자동차들을_찾을_수_있다() { + // given + final List cars = List.of( + new Car(new RandomNumberGenerateStrategy(), "코코닥", 4), + new Car(new RandomNumberGenerateStrategy(), "로이스", 4), + new Car(new RandomNumberGenerateStrategy(), "루카", 3) + ); + final Cars racingCars = new Cars(cars); + + // when + List carsWithPosition = racingCars.findCarsByPosition(4); + + // then + assertThat(carsWithPosition) + .extracting("name") + .containsExactly("코코닥", "로이스"); + } +}