diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e724..4ddc2b6743 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,16 @@ package racingcar; +import racingcar.controller.CarRace; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + CarRace carRace = new CarRace(); + + carRace.receiveCarNamesInput(); + carRace.receiveCarRaceAttemptCountInput(); + carRace.makeCarObjects(); + carRace.doRace(); + carRace.setWinnerCarNames(); + carRace.printWinnerCarNames(); } } diff --git a/src/main/java/racingcar/controller/CarRace.java b/src/main/java/racingcar/controller/CarRace.java new file mode 100644 index 0000000000..68abaa059a --- /dev/null +++ b/src/main/java/racingcar/controller/CarRace.java @@ -0,0 +1,90 @@ +package racingcar.controller; + +import racingcar.model.Car; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Scanner; +import java.util.stream.Collectors; +import java.util.NoSuchElementException; + +public class CarRace { + private final List cars = new ArrayList<>(); + private final Scanner scanner = new Scanner(System.in); + + private List carNames; + private int raceAttemptCount; + private List winnerCarNames; + + public void receiveCarNamesInput() { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + String value = scanner.nextLine().trim(); + + if (value.isEmpty()) { + throw new IllegalArgumentException("경주할 자동차의 이름을 반드시 입력해주세요."); + } + + carNames = Arrays.stream(value.split(",")) + .map(String::trim) + .collect(Collectors.toList()); + + if (carNames.stream().anyMatch(name -> name.length() > 5)) { + throw new IllegalArgumentException("자동차 이름은 5글자 이하로 작성해주세요."); + } + } + + public void receiveCarRaceAttemptCountInput() { + System.out.println("시도할 횟수는 몇 회인가요?"); + raceAttemptCount = scanner.nextInt(); + } + + public void makeCarObjects() { + for (int i = 0; i < carNames.size(); i++) { + cars.add(new Car(i, carNames.get(i))); + } + } + + private void moveCars() { + for (Car car : cars) { + car.tryMove(); + } + } + + public void doRace() { + System.out.println("\n실행 결과"); + for (int i = 0; i < raceAttemptCount; i++) { + moveCars(); + printRaceResult(); + } + } + + private void printRaceResult() { + for (Car car : cars) { + System.out.println(car.getCarName() + " : " + "-".repeat(car.getPosition())); + } + System.out.println(); + } + + public void setWinnerCarNames() { + int maxPosition = cars.stream() + .max(Comparator.comparingInt(Car::getPosition)) + .orElseThrow(NoSuchElementException::new) + .getPosition(); + + winnerCarNames = cars.stream() + .filter(c -> c.getPosition() == maxPosition) + .map(Car::getCarName) + .collect(Collectors.toList()); + } + + public void printWinnerCarNames() { + System.out.print("최종 우승자 : "); + System.out.println(String.join(", ", winnerCarNames)); + } + + public void setCarNames(List names) { + this.carNames = names; + } +} diff --git a/src/main/java/racingcar/model/Car.java b/src/main/java/racingcar/model/Car.java new file mode 100644 index 0000000000..fb6884c37f --- /dev/null +++ b/src/main/java/racingcar/model/Car.java @@ -0,0 +1,45 @@ +package racingcar.model; + +public class Car { + int position = 0; + private int carNumber; + private String carName; + + public Car() { + this.position = 0; + } + + public Car(int carNumber, String carName) { + this.carNumber = carNumber; + this.carName = carName; + } + + public int getRandom() { + double randomValue = Math.random(); + return (int) (randomValue * 9); + } + + public boolean getStatus() { + return getRandom() > 3; + } + + public void tryMove() { + if(getStatus()) { + this.position++; + } + } + + public int getPosition() { + return position; + } + + + public String getCarName() { + return carName; + } + + public int getCarNumber() { + return carNumber; + } + +} diff --git a/src/test/java/racingcar/controller/CarRaceTest.java b/src/test/java/racingcar/controller/CarRaceTest.java new file mode 100644 index 0000000000..b387cf5c43 --- /dev/null +++ b/src/test/java/racingcar/controller/CarRaceTest.java @@ -0,0 +1,110 @@ +package racingcar.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import racingcar.model.Car; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class CarRaceTest { + + private CarRace carRace; + + @BeforeEach + void setUp() { + carRace = new CarRace(); + } + + @Test + @DisplayName("자동차 이름을 올바르게 입력받을 경우 carNames 리스트가 초기화된다.") + void receiveCarNamesInput_shouldParseNamesCorrectly() { + // given + List names = List.of("pobi", "woni", "jun"); + + // when + carRace.setCarNames(names); + carRace.makeCarObjects(); + + // then + try { + var field = CarRace.class.getDeclaredField("cars"); + field.setAccessible(true); + List cars = (List) field.get(carRace); + assertEquals(3, cars.size()); + assertEquals("pobi", cars.get(0).getCarName()); + } catch (Exception e) { + fail("리플렉션 접근 실패: " + e.getMessage()); + } + } + + @Test + @DisplayName("자동차 객체를 올바르게 생성해야 한다.") + void makeCarObjects_shouldCreateCars() { + carRace = new CarRace(); + + var names = List.of("pobi", "woni", "jun"); + + try { + var field = CarRace.class.getDeclaredField("carNames"); + field.setAccessible(true); + field.set(carRace, names); + } catch (Exception e) { + fail("리플렉션 설정 실패: " + e.getMessage()); + } + + // when + carRace.makeCarObjects(); + + // then + try { + var field = CarRace.class.getDeclaredField("cars"); + field.setAccessible(true); + List cars = (List) field.get(carRace); + assertEquals(3, cars.size()); + assertEquals("pobi", cars.get(0).getCarName()); + } catch (Exception e) { + fail("리플렉션 접근 실패: " + e.getMessage()); + } + } + + @Test + @DisplayName("우승자 이름을 올바르게 설정해야 한다.") + void setWinnerCarNames_shouldFindCorrectWinners() { + try { + var names = List.of("a", "b", "c"); + var field = CarRace.class.getDeclaredField("carNames"); + field.setAccessible(true); + field.set(carRace, names); + carRace.makeCarObjects(); + + // 강제로 position 설정 + var carsField = CarRace.class.getDeclaredField("cars"); + carsField.setAccessible(true); + List cars = (List) carsField.get(carRace); + + // a는 2칸, b는 1칸, c는 0칸 + var posField = Car.class.getDeclaredField("position"); + posField.setAccessible(true); + posField.setInt(cars.get(0), 2); // a + posField.setInt(cars.get(1), 1); // b + posField.setInt(cars.get(2), 0); // c + + // when + carRace.setWinnerCarNames(); + + // then + var winnerField = CarRace.class.getDeclaredField("winnerCarNames"); + winnerField.setAccessible(true); + List winners = (List) winnerField.get(carRace); + + assertEquals(List.of("a"), winners); + + } catch (Exception e) { + fail("테스트 실행 중 오류: " + e.getMessage()); + } + } +} + diff --git a/src/test/java/racingcar/model/CarTest.java b/src/test/java/racingcar/model/CarTest.java new file mode 100644 index 0000000000..f50b218df1 --- /dev/null +++ b/src/test/java/racingcar/model/CarTest.java @@ -0,0 +1,46 @@ +package racingcar.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CarTest { + + @Test + @DisplayName("자동차 이름과 번호가 올바르게 설정되어야 한다.") + void createCar() { + Car car = new Car(1, "pobi"); + assertEquals("pobi", car.getCarName()); + assertEquals(0, car.getPosition()); + } + + @RepeatedTest(10) + @DisplayName("랜덤값이 3 이하이면 이동하지 않는다.") + void tryMove_shouldNotMoveWhenRandomIsLessThanOrEqualTo3() { + Car car = new Car(1, "test"); + + // 10번 정도 실행해 랜덤 결과의 평균적 이동을 확인 + int before = car.getPosition(); + car.tryMove(); + int after = car.getPosition(); + + // 랜덤이라 정확히 0 또는 1 단정 불가하지만, 0 이상이어야 함 + assertTrue(after >= before); + } + + @Test + @DisplayName("자동차의 위치(position)는 canMove가 true일 때 증가한다.") + void positionIncreasesWhenCanMoveIsTrue() { + Car car = new Car(1, "hello"); + int before = car.getPosition(); + + // 강제로 여러 번 이동 시도 + for (int i = 0; i < 20; i++) { + car.tryMove(); + } + + assertTrue(car.getPosition() >= before); + } +}