diff --git a/src/main/java/com/racing/Main.java b/src/main/java/com/racing/Main.java index 5a201455acf..38c8897711c 100644 --- a/src/main/java/com/racing/Main.java +++ b/src/main/java/com/racing/Main.java @@ -1,48 +1,9 @@ package com.racing; -import com.racing.domain.Car; -import com.racing.ui.InputView; -import com.racing.ui.ResultView; -import com.racing.utils.CarHelper; - -import java.util.ArrayList; -import java.util.List; +import com.racing.service.RacingService; public class Main { public static void main(String[] args) { - String carNames = InputView.getCarName(); - int tryNumber = InputView.getTryNumber(); - - List validCarNames = CarHelper.splitCarName(carNames); - - List carList = createCars(validCarNames); - runRace(carList, tryNumber); - - List winners = CarHelper.determineWinners(carList); - ResultView.printWinners(winners); - } - - private static List createCars(List carNames) { - List carList = new ArrayList<>(); - - for (String carName : carNames) { - carList.add(new Car(0, carName)); - } - - return carList; - } - - private static void runRace(List carList, int tryNumber) { - ResultView.printStartMessage(); - for (int i = 0; i < tryNumber; i++) { - moveAllCars(carList); - ResultView.printRoundResult(i + 1, carList); - } - } - - private static void moveAllCars(List carList) { - for (Car car : carList) { - car.move(); - } + RacingService.startRacingGame(); } } \ No newline at end of file diff --git a/src/main/java/com/racing/domain/Car.java b/src/main/java/com/racing/domain/Car.java index 69755c6dbcd..4fac28b0623 100644 --- a/src/main/java/com/racing/domain/Car.java +++ b/src/main/java/com/racing/domain/Car.java @@ -1,11 +1,9 @@ package com.racing.domain; -import com.racing.utils.RacingHelper; - public class Car { - private int state = 0; - private String name; + private Position position; + private CarName carName; // 기본 생성자 public Car() { @@ -13,36 +11,30 @@ public Car() { } // 상태 지정 생성자 - public Car(int state, String name) { - this.state = state; - this.name = name; + public Car(int position, String carName) { + this.position = new Position(position); + this.carName = new CarName(carName); } - public void move() { - RacingHelper racingHelper = new RacingHelper(); - - if (racingHelper.shouldMove()) { - state++; + public void move(boolean isMovable) { + if (isMovable) { + position.addPosition(); } } - public String displayRacingState() { - return displayCarName() + " : " + displayState(); - } - - public String displayState() { - return "-".repeat(state); + public String displayRacingPosition() { + return carName.getCarName() + " : " + position.displayDashAsPosition(); } public String displayCarName() { - return name; + return carName.getCarName(); } - public int getState() { - return state; + public int getPosition() { + return position.getPosition(); } public boolean isDefeated(int verseState) { - return state < verseState; + return position.isBiggerThanPosition(verseState); } } diff --git a/src/main/java/com/racing/domain/CarName.java b/src/main/java/com/racing/domain/CarName.java new file mode 100644 index 00000000000..1a5388d5cdb --- /dev/null +++ b/src/main/java/com/racing/domain/CarName.java @@ -0,0 +1,32 @@ +package com.racing.domain; + +import java.util.Objects; + +public class CarName { + private final String carName; + + public CarName(String carName) { + if (carName == null || carName.trim().isEmpty()) { + throw new IllegalArgumentException("Car name cannot be empty or blank."); + } + + this.carName = carName; + } + + public String getCarName() { + return carName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CarName carName1 = (CarName) o; + return Objects.equals(carName, carName1.carName); + } + + @Override + public int hashCode() { + return Objects.hashCode(carName); + } +} diff --git a/src/main/java/com/racing/domain/Cars.java b/src/main/java/com/racing/domain/Cars.java new file mode 100644 index 00000000000..5b144526a24 --- /dev/null +++ b/src/main/java/com/racing/domain/Cars.java @@ -0,0 +1,71 @@ +package com.racing.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class Cars { + private final List items; + + public Cars() { + items = new ArrayList<>(); + } + + public Cars(List carList) { + this.items = carList; + } + + public void addCars(Car car) { + items.add(car); + } + + public List getItems() { + return Collections.unmodifiableList(this.items); + } + + private int findMaxPosition() { + int maxPosition = 0; + for (Car car : this.items) { + maxPosition = car.isDefeated(maxPosition) ? maxPosition : car.getPosition(); + } + + return maxPosition; + } + + public Cars determineWinners() { + int maxPosition = this.findMaxPosition(); + + List winnerList = this.items.stream() + .filter(car -> car.getPosition() == maxPosition) + .collect(Collectors.toList()); + + return new Cars(winnerList); + } + + public int carsSize() { + return this.items.size(); + } + + public String getCarName(int index) { + if (index < 0 || index >= items.size()) { + throw new IndexOutOfBoundsException("Invalid car index"); + } + + return items.get(index).displayCarName(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Cars cars = (Cars) o; + return Objects.equals(items, cars.items); + } + + @Override + public int hashCode() { + return Objects.hashCode(items); + } +} diff --git a/src/main/java/com/racing/domain/Position.java b/src/main/java/com/racing/domain/Position.java new file mode 100644 index 00000000000..a02a515134d --- /dev/null +++ b/src/main/java/com/racing/domain/Position.java @@ -0,0 +1,44 @@ +package com.racing.domain; + +import java.util.Objects; + +public class Position { + private int position; + + public Position() { + this(0); + } + + public Position(int position) { + this.position = position; + } + + public void addPosition() { + this.position++; + } + + public int getPosition() { + return position; + } + + public String displayDashAsPosition() { + return "-".repeat(this.position); + } + + public boolean isBiggerThanPosition(int number) { + return this.position < number; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Position position1 = (Position) o; + return position == position1.position; + } + + @Override + public int hashCode() { + return Objects.hashCode(position); + } +} diff --git a/src/main/java/com/racing/service/RacingService.java b/src/main/java/com/racing/service/RacingService.java new file mode 100644 index 00000000000..b102d86b7e9 --- /dev/null +++ b/src/main/java/com/racing/service/RacingService.java @@ -0,0 +1,55 @@ +package com.racing.service; + +import com.racing.domain.Car; +import com.racing.domain.Cars; +import com.racing.ui.InputView; +import com.racing.ui.ResultView; +import com.racing.utils.CarHelper; +import com.racing.utils.CarMovement; + +import java.util.List; + +public class RacingService { + public static void startRacingGame() { + String carNames = InputView.getCarName(); + int tryNumber = InputView.getTryNumber(); + + List validCarNames = CarHelper.splitCarName(carNames); + + Cars cars = initializeRacingCars(validCarNames); + + executeRaceRounds(cars, tryNumber); + + Cars winners = + cars.determineWinners(); + ResultView.printWinners(winners); + } + + private static Cars initializeRacingCars(List carNames) { + Cars cars = new Cars(); + + for (String carName : carNames) { + cars.addCars(new Car(0, carName)); + } + + return cars; + } + + private static void executeRaceRounds(Cars cars, int tryNumber) { + ResultView.printStartMessage(); + for (int i = 0; i < tryNumber; i++) { + advanceAllCars(cars); + + int roundNumber = i + 1; + ResultView.printRoundResult(roundNumber, cars); + } + } + + private static void advanceAllCars(Cars cars) { + CarMovement carMovement = new CarMovement(); + + for (Car car : cars.getItems()) { + car.move(carMovement.shouldMove()); + } + } +} diff --git a/src/main/java/com/racing/ui/ResultView.java b/src/main/java/com/racing/ui/ResultView.java index cd3367fa657..c8c20b679d7 100644 --- a/src/main/java/com/racing/ui/ResultView.java +++ b/src/main/java/com/racing/ui/ResultView.java @@ -1,27 +1,28 @@ package com.racing.ui; import com.racing.domain.Car; - -import java.util.List; +import com.racing.domain.Cars; public class ResultView { public static void printStartMessage() { System.out.println("실행 결과"); } - public static void printRoundResult(int round, List carList) { + public static void printRoundResult(int round, Cars cars) { System.out.println("# " + round + "회차"); - for (Car car : carList) { - System.out.println(car.displayRacingState()); + + for (Car car : cars.getItems()) { + System.out.println(car.displayRacingPosition()); } + System.out.println(); } - public static void printWinners(List winners) { + public static void printWinners(Cars winners) { System.out.print("최종 우승자: "); - int winnerSize = winners.size(); + int winnerSize = winners.carsSize(); for (int i = 0; i < winnerSize; i++) { - System.out.print(winners.get(i).displayCarName()); + System.out.print(winners.getCarName(i)); printComma(i < winnerSize - 1); } diff --git a/src/main/java/com/racing/utils/CarHelper.java b/src/main/java/com/racing/utils/CarHelper.java index 276568e2c95..4f645f815ac 100644 --- a/src/main/java/com/racing/utils/CarHelper.java +++ b/src/main/java/com/racing/utils/CarHelper.java @@ -1,7 +1,5 @@ package com.racing.utils; -import com.racing.domain.Car; - import java.util.ArrayList; import java.util.List; @@ -28,7 +26,7 @@ private static void addIfValid(String carName, List validNames) { } } - public static boolean isValidCarName(String carName) { + private static boolean isValidCarName(String carName) { if (carName.isEmpty()) { return false; } @@ -36,31 +34,4 @@ public static boolean isValidCarName(String carName) { return carName.length() < VALID_CAR_NAME; } - public static List determineWinners(List carList) { - List winners = new ArrayList<>(); - int maxState = findMaxState(carList); - - for (Car car : carList) { - addWinners(car, maxState, winners); - } - - return winners; - } - - private static void addWinners(Car car, int maxState, List winners) { - if (car.getState() == maxState) { - winners.add(car); - } - } - - private static int findMaxState(List carList) { - int maxState = 0; - for (Car car : carList) { - maxState = car.isDefeated(maxState) ? maxState : car.getState(); - - } - - return maxState; - } - } diff --git a/src/main/java/com/racing/utils/RacingHelper.java b/src/main/java/com/racing/utils/CarMovement.java similarity index 93% rename from src/main/java/com/racing/utils/RacingHelper.java rename to src/main/java/com/racing/utils/CarMovement.java index 20691509d09..9cdad0de80a 100644 --- a/src/main/java/com/racing/utils/RacingHelper.java +++ b/src/main/java/com/racing/utils/CarMovement.java @@ -5,7 +5,7 @@ /** * 레이싱에서 사용 할 헬퍼 메서드를 모아놓은 클래스 */ -public class RacingHelper implements RandomMover { +public class CarMovement implements MovementStrategy { private static final int RANDOM_NUMBER_RANGE = 10; private static final int FORWARD_MINIMUM_NUMBER = 4; diff --git a/src/main/java/com/racing/utils/RandomMover.java b/src/main/java/com/racing/utils/MovementStrategy.java similarity index 63% rename from src/main/java/com/racing/utils/RandomMover.java rename to src/main/java/com/racing/utils/MovementStrategy.java index 54d160c694d..2bf70b1ef9c 100644 --- a/src/main/java/com/racing/utils/RandomMover.java +++ b/src/main/java/com/racing/utils/MovementStrategy.java @@ -1,5 +1,5 @@ package com.racing.utils; -public interface RandomMover { +public interface MovementStrategy { public boolean shouldMove(); } diff --git a/src/test/java/racing/domain/CarNameTest.java b/src/test/java/racing/domain/CarNameTest.java new file mode 100644 index 00000000000..4ab8143311d --- /dev/null +++ b/src/test/java/racing/domain/CarNameTest.java @@ -0,0 +1,36 @@ +package racing.domain; + +import com.racing.domain.CarName; +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; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + + +public class CarNameTest { + @Test + @DisplayName("CarName 객체가 생성자를 통해 정상적으로 만들어지는지 여부") + public void positionConstructorTest() { + CarName carName = new CarName("poby"); + + assertThat(new CarName("poby")).isEqualTo(carName); + } + + @ParameterizedTest + @ValueSource(strings = {"", " "}) + @DisplayName("빈 문자열이나 공백으로 CarName 객체 생성 시 예외 발생") + public void createCarNameWithEmptyNameThrowsException(String invalidName) { + assertThatThrownBy(() -> new CarName(invalidName)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("null로 CarName 객체 생성 시 예외 발생") + public void createCarNameWithNullThrowsException() { + assertThatThrownBy(() -> new CarName(null)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/racing/domain/CarTest.java b/src/test/java/racing/domain/CarTest.java index a2a6d1fea39..acb5610ff03 100644 --- a/src/test/java/racing/domain/CarTest.java +++ b/src/test/java/racing/domain/CarTest.java @@ -7,15 +7,6 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; public class CarTest { - @Test - @DisplayName("입력한 숫자 만큼 '-'를 반복하여 반환") - public void isEquals_RepeatDash() { - Car car = new Car(6, ""); - String expected = "------"; - - assertThat(car.displayState()).isEqualTo(expected); - } - @Test @DisplayName("입력한 CarName을 반환") public void isEquals_CarName() { diff --git a/src/test/java/racing/domain/CarsTest.java b/src/test/java/racing/domain/CarsTest.java new file mode 100644 index 00000000000..be8f72482ac --- /dev/null +++ b/src/test/java/racing/domain/CarsTest.java @@ -0,0 +1,65 @@ +package racing.domain; + +import com.racing.domain.Car; +import com.racing.domain.Cars; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +public class CarsTest { + @Test + @DisplayName("Cars 객체가 정상적으로 만들어지는지 여부") + public void createCarsTest() { + Car pobyCar = new Car(5, "poby"); + Car jongCar = new Car(3, "jong"); + Car leeCar = new Car(3, "lee"); + + List oiginalCarList = Arrays.asList(pobyCar, jongCar, leeCar); + Cars cars = new Cars(oiginalCarList); + + assertThat(new Cars(Arrays.asList(pobyCar, jongCar, leeCar))).isEqualTo(cars); + } + + @Test + @DisplayName("addCars 메서드가 Car 객체를 정상적으로 추가하는지 여부") + public void addCarsTest() { + Cars cars = new Cars(); + Car newCar = new Car(4, "newCar"); + + cars.addCars(newCar); + + assertThat(cars.getItems()).hasSize(1); + assertThat(cars.getItems().get(0)).isEqualTo(newCar); + } + + @Test + @DisplayName("carsSize 메서드가 정확한 자동차 수를 반환하는지 테스트") + void carsSizeTest() { + Car car1 = new Car(3, "car1"); + Car car2 = new Car(3, "car2"); + Car car3 = new Car(3, "car3"); + Cars cars = new Cars(Arrays.asList(car1, car2, car3)); + + int size = cars.carsSize(); + + assertThat(size).isEqualTo(3); + } + + @Test + @DisplayName("getCarName 메서드 테스트") + void getCarNameTest() { + Car car1 = new Car(3, "pobi"); + Car car2 = new Car(3, "crong"); + Cars cars = new Cars(Arrays.asList(car1, car2)); + + assertThat(cars.getCarName(0)).isEqualTo("pobi"); + assertThat(cars.getCarName(1)).isEqualTo("crong"); + assertThatThrownBy(() -> cars.getCarName(2)) + .isInstanceOf(IndexOutOfBoundsException.class); + } +} diff --git a/src/test/java/racing/domain/PositionTest.java b/src/test/java/racing/domain/PositionTest.java new file mode 100644 index 00000000000..98fc6591fd5 --- /dev/null +++ b/src/test/java/racing/domain/PositionTest.java @@ -0,0 +1,47 @@ +package racing.domain; + +import com.racing.domain.Position; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PositionTest { + @Test + @DisplayName("Position 객체가 생성자를 통해 정상적으로 만들어지는지 여부") + public void positionConstructorTest() { + Position position = new Position(5); + + assertThat(new Position(5)).isEqualTo(position); + } + + @Test + @DisplayName("Position add 함수를 실행했을 때 정상적으로 1이 더해지는지 여부") + public void positionAddFuncTest() { + Position position = new Position(0); + position.addPosition(); + position.addPosition(); + + assertThat(position.getPosition()).isEqualTo(2); + } + + @Test + @DisplayName("position 값에 따라 올바른 대시 문자열을 반환하는지 여부") + public void displayDashAsPositionTest() { + Position position = new Position(3); + String result = position.displayDashAsPosition(); + + assertThat(result).isEqualTo("---"); + } + + @Test + @DisplayName("position을 올바르게 비교하여 큰 값인지 여부") + public void isBiggerThanPositionTest() { + Position position = new Position(5); + + assertThat(position.isBiggerThanPosition(6)).isTrue(); + assertThat(position.isBiggerThanPosition(5)).isFalse(); + assertThat(position.isBiggerThanPosition(4)).isFalse(); + } + +} diff --git a/src/test/java/racing/utils/CarHelperTest.java b/src/test/java/racing/utils/CarHelperTest.java index 2d44d53f765..c4768829965 100644 --- a/src/test/java/racing/utils/CarHelperTest.java +++ b/src/test/java/racing/utils/CarHelperTest.java @@ -1,11 +1,10 @@ package racing.utils; import com.racing.domain.Car; +import com.racing.domain.Cars; import com.racing.utils.CarHelper; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; import java.util.Arrays; import java.util.List; @@ -22,13 +21,6 @@ public void isEquals_SplitStringFromDelegate() { assertThat(CarHelper.splitCarName("pobi,crong,honux")).isEqualTo(expectedValue); } - @ParameterizedTest - @DisplayName("5글자 이하일 경우 true, 6글자 이상일 경우 false반환") - @CsvSource(value = {"poby:true", "crong:true", "honux:true", "testtttt:false", "qweqweqwe:false"}, delimiter = ':') - void isEquals_ShouldEqualsExpectedValueSet(String carName, boolean expected) { - assertThat(CarHelper.isValidCarName(carName)).isEqualTo(expected); - } - @Test @DisplayName("모든 이름이 유효하지 않을 때 빈 리스트 반환") public void splitAndValidateCarNames_ShouldReturnEmptyList_WhenAllNamesAreInvalid() { @@ -50,12 +42,12 @@ void isEquals_SingleWinner() { Car car1 = new Car(3, "car1"); Car car2 = new Car(2, "car2"); Car car3 = new Car(1, "car3"); - List cars = Arrays.asList(car1, car2, car3); + Cars cars = new Cars(Arrays.asList(car1, car2, car3)); - List winners = CarHelper.determineWinners(cars); + Cars winners = cars.determineWinners(); - assertThat(winners).hasSize(1); - assertThat(winners.get(0).displayCarName()).isEqualTo("car1"); + assertThat(winners.getItems()).hasSize(1); + assertThat(winners.getItems().get(0).displayCarName()).isEqualTo("car1"); } @Test @@ -64,11 +56,11 @@ void isEquals_MultipleWinner() { Car car1 = new Car(3, "car1"); Car car2 = new Car(3, "car2"); Car car3 = new Car(1, "car3"); - List cars = Arrays.asList(car1, car2, car3); + Cars cars = new Cars(Arrays.asList(car1, car2, car3)); - List winners = CarHelper.determineWinners(cars); + Cars winners = cars.determineWinners(); - assertThat(winners).hasSize(2); - assertThat(winners).extracting("name").containsExactlyInAnyOrder("car1", "car2"); + assertThat(winners.getItems()).hasSize(2); + assertThat(winners.getItems()).extracting("name").containsExactlyInAnyOrder("car1", "car2"); } } diff --git a/src/test/java/racing/utils/RacingHelperTest.java b/src/test/java/racing/utils/RacingHelperTest.java index 69c4a4075b2..18c9fdc8b7d 100644 --- a/src/test/java/racing/utils/RacingHelperTest.java +++ b/src/test/java/racing/utils/RacingHelperTest.java @@ -1,6 +1,6 @@ package racing.utils; -import com.racing.utils.RacingHelper; +import com.racing.utils.CarMovement; import com.warmup.step3.CarRacing; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -14,9 +14,9 @@ public class RacingHelperTest { @Test @DisplayName("모킹 데이터인 false를 반환 받는다.") public void isEquals_GetRandomBoolean() { - RandomMoverMock randomMover = new RandomMoverMock(); + RandomStrategyMock randomStrategy = new RandomStrategyMock(); - assertThat(randomMover.shouldMove()).isEqualTo(false); + assertThat(randomStrategy.shouldMove()).isEqualTo(false); } @Test @@ -25,7 +25,7 @@ public void isBetween_RangeOfRandomNumber() { int expectedMinNum = 0; int expectedMaxNum = 10; - assertThat(RacingHelper.getRandomNumber()).isGreaterThanOrEqualTo(expectedMinNum) + assertThat(CarMovement.getRandomNumber()).isGreaterThanOrEqualTo(expectedMinNum) .isLessThan(expectedMaxNum); } diff --git a/src/test/java/racing/utils/RandomMoverMock.java b/src/test/java/racing/utils/RandomMoverMock.java deleted file mode 100644 index 11d65fbad33..00000000000 --- a/src/test/java/racing/utils/RandomMoverMock.java +++ /dev/null @@ -1,10 +0,0 @@ -package racing.utils; - -import com.racing.utils.RandomMover; - -public class RandomMoverMock implements RandomMover { - @Override - public boolean shouldMove() { - return false; - } -} diff --git a/src/test/java/racing/utils/RandomStrategyMock.java b/src/test/java/racing/utils/RandomStrategyMock.java new file mode 100644 index 00000000000..f9d77e36a41 --- /dev/null +++ b/src/test/java/racing/utils/RandomStrategyMock.java @@ -0,0 +1,10 @@ +package racing.utils; + +import com.racing.utils.MovementStrategy; + +public class RandomStrategyMock implements MovementStrategy { + @Override + public boolean shouldMove() { + return false; + } +}