diff --git a/README.md b/README.md index d0286c859f..49f9616d61 100644 --- a/README.md +++ b/README.md @@ -1 +1,92 @@ # java-racingcar-precourse + +## 자동차 경주 게임 + +초간단 자동차 경주 게임을 구현한다. + +주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. + +각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. + +자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. + +사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. + +전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. + +경주가 끝나면 가장 멀리 이동한 자동차(들)가 우승하며, 우승자가 여러 명이면 쉼표(,)로 구분하여 출력한다. + +사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`를 발생시키고, 애플리케이션은 종료되어야 한다. + + +--- + + +# 기능 구현 목록 + +## View + +### 1. 입력 (InputVew) +- [x] 경주할 자동차 이름을 입력받는다 + - [x] 입력 요청 문구를 출력한다 + - [x] 입력된 문자열을 `,` 기준으로 분리한다 + - [x] 입력된 원시 문자열(List)을 반환한다 + +- [x] 시도할 횟수를 입력받는다 + - [x] 입력 요청 문구를 출력한다 + - [x] 입력 문자열을 반환한다 + +### 2. 출력 (OutputView) +- [x] 각 턴마다 자동차 이름과 이동 거리를 출력한다 +- [x] 최종 우승자 목록을 전달 받아 출력한다 + - [x] 우승자가 다수일 경우 `,`로 구분하여 출력한다 + + +## Model + +### 1. 검증 기능 (Validator) +- 입력 형식 및 비즈니스 규칙에 따른 유효성 검증을 담당한다 +- [x] 문자열 입력값 검증 + - [x] null, 빈 문자열, 공백 문자열 예외 발생 + - [x] 자동차 이름이 없는 경우 예외 발생 + - [x] 이름이 1자 미만 5자 초과일 경우 예외 발생 + - [x] `,`로 구분된 이름 목록에 공백 요소 존재 . 예외 발생 +- [x] 자동차 이름 중복일 경우 예외 발생 +- [x] 시도 횟수 입력값 검증 + - [x] 숫자가 아닐 경우 예외 발생 + - [x] 0 이하의 숫자일 경우 예외 발생 + +### 2. 도메인 핵심 로직 (Model) +- [x] Car 클래스 + - [x] 자동차 이름과 현재 위치를 가진다 + - [x] 이동 조건 충족 시 위치를 1 증가시킨다 + - [x] 상태를 조회할 수 있다 + - [x] 객체 자신의 유효성을 검증한다 + +- [x] Cars 일급 컬렉션 + - [x] private final List + - [x] 정적 팩토리 메서드로 생성한다 + - [x] 모든 자동차를 한 번씩 전진시킨다 + - [x] 최대 이동 거리를 반환한다 + - [x] 우승자 목록을 반환한다 + - [x] Car 목록의 이름 중복을 검증한다 + +- [x] ValueGenerator 인터페이스 + - 랜덤값을 구한다 + - [x] 구현체는 Randoms.pickNumberInRange(0,9)를 사용해 0~9의 정수를 반환한다 + +- [x] RacingGame 클래스 + - 게임 전체 진행을 관리한다 + - [x] 매 라운드마다 이동을 실행한다 + - [x] 매 라운드마다 결과를 저장하고 반환한다 + - [x] 게임이 종료된 후 최종 우승자를 계산한다 + + +## Controller + +- [x] 입력 → 검증 → 모델 실행 → 결과 출력의 전체 흐름을 제어 + - InputView로 입력을 받는다 + - Validator로 형식을 검증한다 + - 유효한 입력값을 사용해 Cars, RacingGame을 초기화한다 + - RacingGame의 결과를 OutputView에 전당해 출력한다 +- 애플리케이션 진입점에서 실행된다 diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e724..ba5cc9c6ef 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,18 @@ package racingcar; +import racingcar.controller.GameController; +import racingcar.model.generator.RandomValueGenerator; +import racingcar.model.generator.ValueGenerator; +import racingcar.view.InputView; +import racingcar.view.OutputView; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + InputView inputView = new InputView(); + OutputView outputView = new OutputView(); + ValueGenerator generator = new RandomValueGenerator(); + + GameController controller = new GameController(inputView, outputView, generator); + controller.run(); } } diff --git a/src/main/java/racingcar/controller/GameController.java b/src/main/java/racingcar/controller/GameController.java new file mode 100644 index 0000000000..a2b70a9dcc --- /dev/null +++ b/src/main/java/racingcar/controller/GameController.java @@ -0,0 +1,38 @@ +package racingcar.controller; + +import racingcar.model.car.Cars; +import racingcar.model.game.RacingGame; +import racingcar.model.generator.ValueGenerator; +import racingcar.model.vo.RoundLimit; +import racingcar.view.InputView; +import racingcar.view.OutputView; + +import java.util.List; + +public class GameController { + + private final InputView inputView; + private final OutputView outputView; + private final ValueGenerator generator; + + public GameController(InputView inputView, OutputView outputView, ValueGenerator generator) { + this.inputView = inputView; + this.outputView = outputView; + this.generator = generator; + } + + public void run() { + List names = inputView.readCarNames(); + String attemptInput = inputView.readAttemptCount(); + + Cars cars = Cars.of(names); + RoundLimit roundLimit = RoundLimit.of(attemptInput); + RacingGame game = RacingGame.of(cars, roundLimit); + outputView.printResultHeader(); + for (int i = 0; i < roundLimit.value(); i++) { + game.playRound(generator); + outputView.printRoundResult(game.currentRoundSnapshots()); + } + outputView.printWinners(game.findWinners()); + } +} diff --git a/src/main/java/racingcar/model/Validator.java b/src/main/java/racingcar/model/Validator.java new file mode 100644 index 0000000000..2b6c565f15 --- /dev/null +++ b/src/main/java/racingcar/model/Validator.java @@ -0,0 +1,15 @@ +package racingcar.model; + +import racingcar.model.constant.ExceptionMessages; + +public class Validator { + + public void validateInputExists(String input) { + if (input == null) { + throw new IllegalArgumentException(ExceptionMessages.INPUT_NULL.get()); + } + if (input.isBlank()) { + throw new IllegalArgumentException(ExceptionMessages.INPUT_BLANK.get()); + } + } +} diff --git a/src/main/java/racingcar/model/car/Car.java b/src/main/java/racingcar/model/car/Car.java new file mode 100644 index 0000000000..ce75e91fbf --- /dev/null +++ b/src/main/java/racingcar/model/car/Car.java @@ -0,0 +1,36 @@ +package racingcar.model.car; + +import racingcar.model.generator.ValueGenerator; +import racingcar.model.vo.CarName; +import racingcar.model.vo.Position; + +public class Car { + + private final CarName name; + private final Position position; + + public Car(String name) { + this.name = new CarName(name); + this.position = new Position(); + } + + public void move(ValueGenerator generator) { + position.move(generator.getValue()); + } + + public String name() { + return name.value(); + } + + public int position() { + return position.value(); + } + + public boolean isWinner(int maxPosition) { + return position.value() == maxPosition; + } + + public CarStatus snapshot() { + return new CarStatus(name.value(), position.value()); + } +} diff --git a/src/main/java/racingcar/model/car/CarStatus.java b/src/main/java/racingcar/model/car/CarStatus.java new file mode 100644 index 0000000000..919e6956a8 --- /dev/null +++ b/src/main/java/racingcar/model/car/CarStatus.java @@ -0,0 +1,3 @@ +package racingcar.model.car; + +public record CarStatus(String name, int position) { } diff --git a/src/main/java/racingcar/model/car/Cars.java b/src/main/java/racingcar/model/car/Cars.java new file mode 100644 index 0000000000..4e4a96d7fb --- /dev/null +++ b/src/main/java/racingcar/model/car/Cars.java @@ -0,0 +1,68 @@ +package racingcar.model.car; + +import racingcar.model.constant.ExceptionMessages; +import racingcar.model.generator.ValueGenerator; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class Cars { + + private final List cars; + + private Cars(List cars) { + validateDuplicateNames(cars); + this.cars = cars; + } + + public static Cars of(List names) { + if (names == null || names.isEmpty()) { + throw new IllegalArgumentException(ExceptionMessages.INPUT_EMPTY.get()); + } + + List carList = names.stream() + .map(Car::new) + .collect(Collectors.toUnmodifiableList()); + + return new Cars(carList); + } + + private void validateDuplicateNames(List cars) { + Set uniqueNames = cars.stream() + .map(Car::name) + .collect(Collectors.toSet()); + if (uniqueNames.size() != cars.size()) { + throw new IllegalArgumentException(ExceptionMessages.DUPLICATE_NAME.get()); + } + } + + public void moveAll(ValueGenerator generator) { + cars.forEach(car -> car.move(generator)); + } + + public int maxPosition() { + return cars.stream() + .mapToInt(Car::position) + .max() + .orElse(0); + } + + public List findWinners() { + int maxPosition = maxPosition(); + return cars.stream() + .filter(car -> car.isWinner(maxPosition)) + .map(Car::name) + .collect(Collectors.toUnmodifiableList()); + } + + public List snapshots() { + return cars.stream() + .map(Car::snapshot) + .collect(Collectors.toList()); + } + + public List cars() { + return List.copyOf(cars); + } +} diff --git a/src/main/java/racingcar/model/constant/ExceptionMessages.java b/src/main/java/racingcar/model/constant/ExceptionMessages.java new file mode 100644 index 0000000000..8603dcb28a --- /dev/null +++ b/src/main/java/racingcar/model/constant/ExceptionMessages.java @@ -0,0 +1,26 @@ +package racingcar.model.constant; + +public enum ExceptionMessages { + + INPUT_EMPTY("입력값이 비어있습니다."), + INPUT_NULL("입력값이 존재하지 않습니다."), + INPUT_BLANK("입력값에 공백만 포함될 수 없습니다."), + + INVALID_NAME_EMPTY("자동차 이름은 비어있을 수 없습니다."), + INVALID_NAME_LENGTH("자동차 이름은 1자 이상 5자 이하만 가능합니다."), + DUPLICATE_NAME("자동차 이름은 중복될 수 없습니다."), + + INVALID_NUMBER_FORMAT("시도 횟수는 숫자여야 합니다."), + INVALID_NUMBER_RANGE("시도 횟수는 1 이상의 정수여야 합니다."), + EXCEEDED_ROUND_LIMIT("라운드 한도를 초과했습니다."); + + private final String message; + + ExceptionMessages(String message) { + this.message = message; + } + + public String get() { + return message; + } +} diff --git a/src/main/java/racingcar/model/game/RacingGame.java b/src/main/java/racingcar/model/game/RacingGame.java new file mode 100644 index 0000000000..210ee774d7 --- /dev/null +++ b/src/main/java/racingcar/model/game/RacingGame.java @@ -0,0 +1,57 @@ +package racingcar.model.game; + +import racingcar.model.car.CarStatus; +import racingcar.model.car.Cars; +import racingcar.model.constant.ExceptionMessages; +import racingcar.model.generator.ValueGenerator; +import racingcar.model.vo.RoundLimit; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class RacingGame { + + private final Cars cars; + private final RoundLimit roundLimit; + private final List> roundSnapshots = new ArrayList<>(); + + private RacingGame(Cars cars, RoundLimit roundLimit) { + this.cars = cars; + this.roundLimit = roundLimit; + } + + public static RacingGame of(Cars cars, RoundLimit roundLimit) { + return new RacingGame(cars, roundLimit); + } + + public void playRound(ValueGenerator generator) { + if (!roundLimit.hasRemaining(roundSnapshots.size())) { + throw new IllegalStateException(ExceptionMessages.EXCEEDED_ROUND_LIMIT.get()); + } + cars.moveAll(generator); + roundSnapshots.add(cars.snapshots()); + } + + public List currentRoundSnapshots() { + if (roundSnapshots.isEmpty()) { + return List.of(); + } + List lastRound = roundSnapshots.get(roundSnapshots.size() - 1); + return lastRound.stream() + .map(status -> new CarStatus(status.name(), status.position())) + .collect(Collectors.toList()); + } + + public List> allRoundSnapshots() { + return roundSnapshots.stream() + .map(round -> round.stream() + .map(status -> new CarStatus(status.name(), status.position())) + .collect(Collectors.toList())) + .collect(Collectors.toList()); + } + + public List findWinners() { + return cars.findWinners(); + } +} diff --git a/src/main/java/racingcar/model/generator/RandomValueGenerator.java b/src/main/java/racingcar/model/generator/RandomValueGenerator.java new file mode 100644 index 0000000000..acc337589f --- /dev/null +++ b/src/main/java/racingcar/model/generator/RandomValueGenerator.java @@ -0,0 +1,14 @@ +package racingcar.model.generator; + +import camp.nextstep.edu.missionutils.Randoms; + +public class RandomValueGenerator implements ValueGenerator { + + private static final int MIN = 0; + private static final int MAX = 9; + + @Override + public int getValue() { + return Randoms.pickNumberInRange(MIN, MAX); + } +} diff --git a/src/main/java/racingcar/model/generator/ValueGenerator.java b/src/main/java/racingcar/model/generator/ValueGenerator.java new file mode 100644 index 0000000000..1a79d8ca22 --- /dev/null +++ b/src/main/java/racingcar/model/generator/ValueGenerator.java @@ -0,0 +1,6 @@ +package racingcar.model.generator; + +@FunctionalInterface +public interface ValueGenerator { + int getValue(); +} diff --git a/src/main/java/racingcar/model/vo/CarName.java b/src/main/java/racingcar/model/vo/CarName.java new file mode 100644 index 0000000000..e81b495bc8 --- /dev/null +++ b/src/main/java/racingcar/model/vo/CarName.java @@ -0,0 +1,21 @@ +package racingcar.model.vo; + +import racingcar.model.constant.ExceptionMessages; + +public record CarName(String value) { + private static final int NAME_MAX_LENGTH = 5; + + public CarName { + if (value == null || value.isBlank()) { + throw new IllegalArgumentException(ExceptionMessages.INVALID_NAME_EMPTY.get()); + } + if (value.length() > NAME_MAX_LENGTH) { + throw new IllegalArgumentException(ExceptionMessages.INVALID_NAME_LENGTH.get()); + } + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/racingcar/model/vo/Position.java b/src/main/java/racingcar/model/vo/Position.java new file mode 100644 index 0000000000..423e1a5232 --- /dev/null +++ b/src/main/java/racingcar/model/vo/Position.java @@ -0,0 +1,20 @@ +package racingcar.model.vo; + +public class Position { + private int value; + private static final int MOVE_THRESHOLD = 4; + + public Position() { + this.value = 0; + } + + public int value() { + return value; + } + + public void move(int randomValue) { + if (randomValue >= MOVE_THRESHOLD) { + value++; + } + } +} diff --git a/src/main/java/racingcar/model/vo/RoundLimit.java b/src/main/java/racingcar/model/vo/RoundLimit.java new file mode 100644 index 0000000000..c3bf1425d1 --- /dev/null +++ b/src/main/java/racingcar/model/vo/RoundLimit.java @@ -0,0 +1,34 @@ +package racingcar.model.vo; + +import racingcar.model.constant.ExceptionMessages; + +public final class RoundLimit { + + private final int value; + + private RoundLimit(int value) { + if (value <= 0) { + throw new IllegalArgumentException(ExceptionMessages.INVALID_NUMBER_RANGE.get()); + } + this.value = value; + } + + public static RoundLimit of(String input) { + if (input == null || input.isBlank()) { + throw new IllegalArgumentException(ExceptionMessages.INPUT_EMPTY.get()); + } + try { + return new RoundLimit(Integer.parseInt(input)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ExceptionMessages.INVALID_NUMBER_FORMAT.get()); + } + } + + public boolean hasRemaining(int currentRound) { + return currentRound < value; + } + + public int value() { + return value; + } +} diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 0000000000..28827d30af --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,25 @@ +package racingcar.view; + +import camp.nextstep.edu.missionutils.Console; +import java.util.Arrays; +import java.util.List; + +public class InputView { + + private static final String INPUT_CAR_NAMES_MESSAGE = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"; + private static final String INPUT_ATTEMPTS_MESSAGE = "시도할 횟수는 몇 회인가요?"; + + public List readCarNames() { + System.out.println(INPUT_CAR_NAMES_MESSAGE); + String input = Console.readLine(); + + return Arrays.stream(input.split(",")) + .map(String::trim) + .toList(); + } + + public String readAttemptCount() { + System.out.println(INPUT_ATTEMPTS_MESSAGE); + return Console.readLine(); + } +} diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 0000000000..b2f273c1ad --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -0,0 +1,26 @@ +package racingcar.view; + +import racingcar.model.car.CarStatus; + +import java.util.List; + +public class OutputView { + + private static final String RESULT_HEADER = "실행 결과"; + private static final String WINNER_ANNOUNCEMENT = "최종 우승자 : "; + + public void printResultHeader() { + System.out.println(RESULT_HEADER); + } + + public void printRoundResult(List snapshots) { + for (CarStatus status : snapshots) { + System.out.println(status.name() + " : " + "-".repeat(status.position())); + } + System.out.println(); + } + + public void printWinners(List winners) { + System.out.println(WINNER_ANNOUNCEMENT + String.join(", ", winners)); + } +} diff --git a/src/test/java/racingcar/model/ValidatorTest.java b/src/test/java/racingcar/model/ValidatorTest.java new file mode 100644 index 0000000000..1a444cb6a2 --- /dev/null +++ b/src/test/java/racingcar/model/ValidatorTest.java @@ -0,0 +1,30 @@ +package racingcar.model; + +import org.junit.jupiter.api.Test; +import racingcar.model.constant.ExceptionMessages; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ValidatorTest { + + private final Validator validator = new Validator(); + + @Test + void 입력값이_null이면_예외() { + assertThatThrownBy(() -> validator.validateInputExists(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessages.INPUT_NULL.get()); + } + + @Test + void 입력값이_공백이면_예외() { + assertThatThrownBy(() -> validator.validateInputExists(" ")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ExceptionMessages.INPUT_BLANK.get()); + } + + @Test + void 입력값이_정상이라면_예외가_발생하지_않는다() { + validator.validateInputExists("pobi"); // 예외 발생하지 않아야 함 + } +} diff --git a/src/test/java/racingcar/model/car/CarTest.java b/src/test/java/racingcar/model/car/CarTest.java new file mode 100644 index 0000000000..05fadaad98 --- /dev/null +++ b/src/test/java/racingcar/model/car/CarTest.java @@ -0,0 +1,42 @@ +package racingcar.model.car; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CarTest { + + @ParameterizedTest + @ValueSource(ints = {4, 5, 6, 9}) + void 랜덤값이_4_이상이면_전진한다(int value) { + Car car = new Car("pobi"); + car.move(() -> value); + assertThat(car.position()).isEqualTo(1); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1, 2, 3}) + void 랜덤값이_4_미만이면_이동하지_않는다(int value) { + Car car = new Car("pobi"); + car.move(() -> value); + assertThat(car.position()).isZero(); + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" "}) + void 이름이_null_또는_공백이면_예외(String input) { + assertThatThrownBy(() -> new Car(input)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 이름이_5자초과면_예외() { + assertThatThrownBy(() -> new Car("abcdef")) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/racingcar/model/car/CarsTest.java b/src/test/java/racingcar/model/car/CarsTest.java new file mode 100644 index 0000000000..ee2821f5a7 --- /dev/null +++ b/src/test/java/racingcar/model/car/CarsTest.java @@ -0,0 +1,83 @@ +package racingcar.model.car; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import racingcar.model.generator.ValueGenerator; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CarsTest { + + private List names; + + @BeforeEach + void setUp() { + names = List.of("pobi", "woni", "jun"); + } + + @Test + void 중복된_이름이_있으면_예외() { + List duplicateNames = List.of("pobi", "pobi"); + assertThatThrownBy(() -> Cars.of(duplicateNames)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 빈_리스트면_예외() { + assertThatThrownBy(() -> Cars.of(List.of())) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void null_입력이면_예외() { + assertThatThrownBy(() -> Cars.of(null)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 모든_자동차가_한번씩_전진한다() { + Cars cars = Cars.of(names); + ValueGenerator alwaysMove = () -> 9; + cars.moveAll(alwaysMove); + assertThat(cars.cars()) + .extracting(Car::position) + .containsExactly(1, 1, 1); + } + + @Test + void 최대_이동_거리를_반환한다() { + Cars cars = Cars.of(names); + ValueGenerator generator = () -> 9; + cars.moveAll(generator); // 모두 한 번 전진 + cars.moveAll(generator); // 두 번 전진 + + assertThat(cars.maxPosition()).isEqualTo(2); + } + + @Test + void 공동_우승자가_존재할_수_있다() { + Cars cars = Cars.of(List.of("pobi", "woni")); + cars.moveAll(() -> 9); + List winners = cars.findWinners(); + assertThat(winners).containsExactlyInAnyOrder("pobi", "woni"); + } + + @Test + void 가장_먼_위치의_자동차가_우승자이다() { + Cars cars = Cars.of(names); + ValueGenerator moveOnlyPobi = new ValueGenerator() { + private int count = 0; + @Override + public int getValue() { + return count++ == 0 ? 9 : 0; + } + }; + cars.moveAll(moveOnlyPobi); + List winners = cars.findWinners(); + assertThat(winners) + .containsExactly("pobi"); + } +} diff --git a/src/test/java/racingcar/model/game/RacingGameTest.java b/src/test/java/racingcar/model/game/RacingGameTest.java new file mode 100644 index 0000000000..eae069d3ba --- /dev/null +++ b/src/test/java/racingcar/model/game/RacingGameTest.java @@ -0,0 +1,64 @@ +package racingcar.model.game; + +import org.junit.jupiter.api.Test; +import racingcar.model.car.CarStatus; +import racingcar.model.car.Cars; +import racingcar.model.vo.RoundLimit; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RacingGameTest { + + @Test + void 주어진_횟수만큼_경주를_진행한다() { + Cars cars = Cars.of(List.of("pobi", "woni")); + RacingGame game = RacingGame.of(cars, RoundLimit.of("3")); + for (int i = 0; i < 3; i++) { + game.playRound(() -> 5); + } + List> results = game.allRoundSnapshots(); + assertThat(results).hasSize(3); + assertThat(results.get(2).get(0).position()).isGreaterThanOrEqualTo(1); + } + + @Test + void 시도_횟수가_0이면_예외() { + assertThatThrownBy(() -> RoundLimit.of("0")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 시도_횟수가_음수면_예외() { + assertThatThrownBy(() -> RoundLimit.of("-1")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 라운드_결과는_깊은_복사이다() { + Cars cars = Cars.of(List.of("pobi")); + RacingGame game = RacingGame.of(cars, RoundLimit.of("1")); + + game.playRound(() -> 9); + + List> firstCall = game.allRoundSnapshots(); + List> secondCall = game.allRoundSnapshots(); + + assertThat(firstCall).isNotSameAs(secondCall); + assertThat(firstCall.get(0)).isNotSameAs(secondCall.get(0)); + assertThat(firstCall.get(0).get(0)) + .usingRecursiveComparison() + .isEqualTo(secondCall.get(0).get(0)); // 값은 동일해야 함 + } + + @Test + void 최종_우승자를_반환한다() { + Cars cars = Cars.of(List.of("pobi", "woni")); + RacingGame game = RacingGame.of(cars, RoundLimit.of("1")); + game.playRound(() -> 9); + List winners = game.findWinners(); + assertThat(winners).containsExactlyInAnyOrder("pobi", "woni"); + } +}