From 22fe2b2c0ae3275ba7c360aa540d2801249739c9 Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Fri, 24 Oct 2025 13:51:45 +0900 Subject: [PATCH 01/21] docs: add feature list to README --- README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/README.md b/README.md index d0286c859f..94228038c7 100644 --- a/README.md +++ b/README.md @@ -1 +1,39 @@ # java-racingcar-precourse + +## 기능 목록 + +### 1. 자동차 이름 입력 및 검증 +- [ ] 자동차 이름은 쉼표(,) 기준으로 구분 +- [ ] 이름은 최대 5자 +- [ ] 잘못된 입력 시 `IllegalArgumentException` 발생 + +### 2. 자동차 이동 로직 +- [ ] 0~9 사이 랜덤 값 >= 4일 때 전진 +- [ ] 4 이하인 경우 정지 + +### 3. 위치(Position) VO +- [ ] 초기 위치 = 0 +- [ ] 이동 시 불변 객체 반환 +- [ ] 위치 출력 시 `-` 문자를 사용하여 표현 + +### 4. Car 클래스 +- [ ] 자동차 이름(Name VO)과 위치(Position VO) 관리 +- [ ] move() 메서드에서 MoveStrategy를 통해 전진 여부 결정 + +### 5. Cars 컬렉션 +- [ ] 여러 대의 Car를 관리 +- [ ] 각 라운드마다 모든 자동차 이동 수행 +- [ ] 우승자 계산 기능 + +### 6. 게임 진행 +- [ ] 시도할 횟수만큼 라운드 반복 +- [ ] 각 라운드 결과 출력 +- [ ] 최종 우승자 출력 + +### 7. 입력/출력 +- [ ] 사용자 입력 처리 + - 자동차 이름 + - 시도 횟수 +- [ ] 실행 결과 출력 + - 각 자동차의 위치 + - 우승자가 여러 명일 경우 ','로 구분하여 공동 우승 표시 From 0434fe15efa4a90fafe17f812cc53c8ac274c643 Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Fri, 24 Oct 2025 14:06:25 +0900 Subject: [PATCH 02/21] test: add Name value object tests --- src/test/java/racingcar/domain/NameTest.java | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/test/java/racingcar/domain/NameTest.java diff --git a/src/test/java/racingcar/domain/NameTest.java b/src/test/java/racingcar/domain/NameTest.java new file mode 100644 index 0000000000..26d7026a16 --- /dev/null +++ b/src/test/java/racingcar/domain/NameTest.java @@ -0,0 +1,37 @@ +package racingcar.domain; + +import org.junit.jupiter.api.DisplayName; +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.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class NameTest { + + @ParameterizedTest + @ValueSource(strings = { "pobi", "jun", "woni" }) + @DisplayName("이름 생성하기") + void createName(String validName) { + Name name = new Name(validName); + assertEquals(validName, name.getValue()); + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" ", " "}) + @DisplayName("빈 이름 또는 NULL 값이면 예외가 발생한다") + void validateEmptyName(String invalidName) { + assertThatThrownBy(() -> new Name(invalidName)) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @ValueSource(strings = { "abcdef", "123456" }) + @DisplayName("5자를 초과하는 이름이면 예외가 발생한다") + void validateNameLength(String invalidName) { + assertThatThrownBy(() -> new Name(invalidName)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file From 2a2aa2523b196aef7340cb6efab7641770516023 Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Fri, 24 Oct 2025 14:16:31 +0900 Subject: [PATCH 03/21] feat: add Name value object for car naming --- src/main/java/racingcar/domain/Name.java | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/racingcar/domain/Name.java diff --git a/src/main/java/racingcar/domain/Name.java b/src/main/java/racingcar/domain/Name.java new file mode 100644 index 0000000000..49f250d84d --- /dev/null +++ b/src/main/java/racingcar/domain/Name.java @@ -0,0 +1,33 @@ +package racingcar.domain; + +public class Name { + + private static final int MAX_NAME_LENGTH = 5; + private static final String EMPTY_NAME_MESSAGE = "자동차 이름은 빈 값일 수 없습니다."; + private static final String LENGTH_EXCEED_MESSAGE = "자동차 이름은 5자 이하만 가능합니다."; + + private final String value; + + public Name(String value) { + validate(value); + this.value = value.trim(); + } + + private void validate(String value) { + if (isNullOrEmpty(value)) { + throw new IllegalArgumentException(EMPTY_NAME_MESSAGE); + } + if (value.trim().length() > MAX_NAME_LENGTH) { + throw new IllegalArgumentException(LENGTH_EXCEED_MESSAGE); + } + } + + public String getValue() { + return value; + } + + private static boolean isNullOrEmpty(String str) { + return str == null || str.isBlank(); + } + +} From aa22576ae8ea99b0c6c91fe0d198973e35a6e354 Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Fri, 24 Oct 2025 14:17:24 +0900 Subject: [PATCH 04/21] docs: check off completed Name VO tasks --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 94228038c7..a4917e7a97 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ ## 기능 목록 ### 1. 자동차 이름 입력 및 검증 -- [ ] 자동차 이름은 쉼표(,) 기준으로 구분 -- [ ] 이름은 최대 5자 -- [ ] 잘못된 입력 시 `IllegalArgumentException` 발생 +- [X] 자동차 이름은 쉼표(,) 기준으로 구분 +- [X] 이름은 최대 5자 +- [X] 잘못된 입력 시 `IllegalArgumentException` 발생 ### 2. 자동차 이동 로직 - [ ] 0~9 사이 랜덤 값 >= 4일 때 전진 From 283b4657ec1947c500976f5cc6c86fc97e11721b Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 05:27:31 +0900 Subject: [PATCH 05/21] test: add Position value object tests --- .../java/racingcar/domain/PositionTest.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/test/java/racingcar/domain/PositionTest.java diff --git a/src/test/java/racingcar/domain/PositionTest.java b/src/test/java/racingcar/domain/PositionTest.java new file mode 100644 index 0000000000..9014858cf1 --- /dev/null +++ b/src/test/java/racingcar/domain/PositionTest.java @@ -0,0 +1,41 @@ +package racingcar.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PositionTest { + + @Test + @DisplayName("초기 위치는 0이다") + void createInitialPosition() { + Position position = new Position(); + assertEquals(0, position.getValue()); + } + + @Test + @DisplayName("이동 조건 미달이면 위치가 증가하지 않는다") + void stayWhenConditionNotMet() { + Position position = new Position(); + Position newPosition = position.move(3); + assertEquals(0, newPosition.getValue()); + } + + @Test + @DisplayName("이동 조건 충족이면 위치가 증가한다") + void moveWhenConditionMet() { + Position position = new Position(); + Position newPosition = position.move(4); + assertEquals(1, newPosition.getValue()); + } + + @Test + @DisplayName("여러 번 전진할 수 있다") + void moveMultipleTimes() { + Position position = new Position(); + Position movedPosition = position.move().move().move(); + assertEquals(3, movedPosition.getValue()); + } + +} \ No newline at end of file From 3e6a24f27b42aecdb8989e83c84ec4ab7b5fd393 Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 05:42:02 +0900 Subject: [PATCH 06/21] feat: add Position value object for car naming --- src/main/java/racingcar/domain/Position.java | 34 +++++++++++++++++++ .../java/racingcar/domain/PositionTest.java | 16 +++------ 2 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 src/main/java/racingcar/domain/Position.java diff --git a/src/main/java/racingcar/domain/Position.java b/src/main/java/racingcar/domain/Position.java new file mode 100644 index 0000000000..ce5de0ce33 --- /dev/null +++ b/src/main/java/racingcar/domain/Position.java @@ -0,0 +1,34 @@ +package racingcar.domain; + +public class Position { + + private static final int INITIAL_POSITION = 0; + private static final int MOVE_DISTANCE = 1; + private static final String NEGATIVE_POSITION_MESSAGE = "위치는 음수일 수 없습니다."; + + private final int value; + + public Position() { + this(INITIAL_POSITION); + } + + private Position(int value) { + validate(value); + this.value = value; + } + + public Position move() { + return new Position(value + MOVE_DISTANCE); + } + + public int getValue() { + return value; + } + + private void validate(int value) { + if (value < 0) { + throw new IllegalArgumentException(NEGATIVE_POSITION_MESSAGE); + } + } + +} \ No newline at end of file diff --git a/src/test/java/racingcar/domain/PositionTest.java b/src/test/java/racingcar/domain/PositionTest.java index 9014858cf1..90c0d82169 100644 --- a/src/test/java/racingcar/domain/PositionTest.java +++ b/src/test/java/racingcar/domain/PositionTest.java @@ -15,19 +15,11 @@ void createInitialPosition() { } @Test - @DisplayName("이동 조건 미달이면 위치가 증가하지 않는다") - void stayWhenConditionNotMet() { + @DisplayName("전진하면 위치가 1 증가한다") + void moveForward() { Position position = new Position(); - Position newPosition = position.move(3); - assertEquals(0, newPosition.getValue()); - } - - @Test - @DisplayName("이동 조건 충족이면 위치가 증가한다") - void moveWhenConditionMet() { - Position position = new Position(); - Position newPosition = position.move(4); - assertEquals(1, newPosition.getValue()); + Position movedPosition = position.move(); + assertEquals(1, movedPosition.getValue()); } @Test From 154ce9692881117118b6aa611264b9793796e07e Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 05:42:43 +0900 Subject: [PATCH 07/21] docs: check off completed Position VO tasks --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a4917e7a97..df8b096b5d 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ - [ ] 4 이하인 경우 정지 ### 3. 위치(Position) VO -- [ ] 초기 위치 = 0 -- [ ] 이동 시 불변 객체 반환 +- [X] 초기 위치 = 0 +- [X] 이동 시 불변 객체 반환 - [ ] 위치 출력 시 `-` 문자를 사용하여 표현 ### 4. Car 클래스 From d659a11e3465ad78a834361f88f22636fdb36e55 Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 05:47:39 +0900 Subject: [PATCH 08/21] test: add Car value object tests --- src/test/java/racingcar/domain/CarTest.java | 45 +++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/test/java/racingcar/domain/CarTest.java diff --git a/src/test/java/racingcar/domain/CarTest.java b/src/test/java/racingcar/domain/CarTest.java new file mode 100644 index 0000000000..aa9c256888 --- /dev/null +++ b/src/test/java/racingcar/domain/CarTest.java @@ -0,0 +1,45 @@ +package racingcar.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CarTest { + + @Test + @DisplayName("자동차는 이름과 위치를 가진다") + void createCar() { + Car car = new Car("pobi"); + assertEquals("pobi", car.getName()); + assertEquals(0, car.getPosition()); + } + + @Test + @DisplayName("랜덤 값이 4 미만이면 이동하지 않는다") + void stayWhenNumberLessThanFour() { + Car car = new Car("pobi"); + car.move(3); + assertEquals(0, car.getPosition()); + } + + @Test + @DisplayName("랜덤 값이 4 이상이면 전진한다") + void moveWhenNumberGreaterThanOrEqualToFour() { + Car car = new Car("pobi"); + car.move(4); + assertEquals(1, car.getPosition()); + } + + @Test + @DisplayName("여러 번 이동할 수 있다") + void moveMultipleTimes() { + Car car = new Car("pobi"); + car.move(5); + car.move(6); + car.move(3); + car.move(7); + assertEquals(3, car.getPosition()); + } + +} \ No newline at end of file From 07dbde40220a4b79aef86b934c22b861f56b69fc Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 06:01:28 +0900 Subject: [PATCH 09/21] feat: add Car Entity --- src/main/java/racingcar/domain/Car.java | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/racingcar/domain/Car.java diff --git a/src/main/java/racingcar/domain/Car.java b/src/main/java/racingcar/domain/Car.java new file mode 100644 index 0000000000..24b218f625 --- /dev/null +++ b/src/main/java/racingcar/domain/Car.java @@ -0,0 +1,21 @@ +package racingcar.domain; + +public class Car { + + private final Name name; + private Position position; + + public Car(String nameValue) { + name = new Name(nameValue); + position = new Position(); + } + + public int getPosition() { + return position.getValue(); + } + + public String getName() { + return name.getValue(); + } + +} From ab4df655757e99f21fd1cb01d7432107804dbe5c Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 06:09:11 +0900 Subject: [PATCH 10/21] feat: add Car domain with move strategy pattern --- src/main/java/racingcar/domain/Car.java | 8 ++++++++ .../java/racingcar/strategy/MoveStrategy.java | 6 ++++++ .../strategy/RandomNumberMoveStrategy.java | 16 +++++++++++++++ src/test/java/racingcar/domain/CarTest.java | 20 +++++++++++++------ 4 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 src/main/java/racingcar/strategy/MoveStrategy.java create mode 100644 src/main/java/racingcar/strategy/RandomNumberMoveStrategy.java diff --git a/src/main/java/racingcar/domain/Car.java b/src/main/java/racingcar/domain/Car.java index 24b218f625..bac171c11c 100644 --- a/src/main/java/racingcar/domain/Car.java +++ b/src/main/java/racingcar/domain/Car.java @@ -1,5 +1,7 @@ package racingcar.domain; +import racingcar.strategy.MoveStrategy; + public class Car { private final Name name; @@ -10,6 +12,12 @@ public Car(String nameValue) { position = new Position(); } + public void move(MoveStrategy strategy) { + if (strategy.isMovable()) { + position = position.move(); + } + } + public int getPosition() { return position.getValue(); } diff --git a/src/main/java/racingcar/strategy/MoveStrategy.java b/src/main/java/racingcar/strategy/MoveStrategy.java new file mode 100644 index 0000000000..c48e5425c0 --- /dev/null +++ b/src/main/java/racingcar/strategy/MoveStrategy.java @@ -0,0 +1,6 @@ +package racingcar.strategy; + +@FunctionalInterface +public interface MoveStrategy { + boolean isMovable(); +} \ No newline at end of file diff --git a/src/main/java/racingcar/strategy/RandomNumberMoveStrategy.java b/src/main/java/racingcar/strategy/RandomNumberMoveStrategy.java new file mode 100644 index 0000000000..ab2bcc1eda --- /dev/null +++ b/src/main/java/racingcar/strategy/RandomNumberMoveStrategy.java @@ -0,0 +1,16 @@ +package racingcar.strategy; + +import camp.nextstep.edu.missionutils.Randoms; + +public class RandomNumberMoveStrategy implements MoveStrategy { + + private static final int MIN_NUMBER = 0; + private static final int MAX_NUMBER = 9; + private static final int MOVE_THRESHOLD = 4; + + @Override + public boolean isMovable() { + int randomNumber = Randoms.pickNumberInRange(MIN_NUMBER, MAX_NUMBER); + return randomNumber >= MOVE_THRESHOLD; + } +} \ No newline at end of file diff --git a/src/test/java/racingcar/domain/CarTest.java b/src/test/java/racingcar/domain/CarTest.java index aa9c256888..82eed0a5ad 100644 --- a/src/test/java/racingcar/domain/CarTest.java +++ b/src/test/java/racingcar/domain/CarTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import racingcar.strategy.MoveStrategy; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -19,7 +20,8 @@ void createCar() { @DisplayName("랜덤 값이 4 미만이면 이동하지 않는다") void stayWhenNumberLessThanFour() { Car car = new Car("pobi"); - car.move(3); + MoveStrategy notMovableStrategy = () -> false; + car.move(notMovableStrategy); assertEquals(0, car.getPosition()); } @@ -27,7 +29,8 @@ void stayWhenNumberLessThanFour() { @DisplayName("랜덤 값이 4 이상이면 전진한다") void moveWhenNumberGreaterThanOrEqualToFour() { Car car = new Car("pobi"); - car.move(4); + MoveStrategy movableStrategy = () -> true; + car.move(movableStrategy); assertEquals(1, car.getPosition()); } @@ -35,10 +38,15 @@ void moveWhenNumberGreaterThanOrEqualToFour() { @DisplayName("여러 번 이동할 수 있다") void moveMultipleTimes() { Car car = new Car("pobi"); - car.move(5); - car.move(6); - car.move(3); - car.move(7); + + MoveStrategy movableStrategy = () -> true; + MoveStrategy notMovableStrategy = () -> false; + + car.move(movableStrategy); + car.move(notMovableStrategy); + car.move(movableStrategy); + car.move(movableStrategy); + assertEquals(3, car.getPosition()); } From 681cbf04e5fb1e209085b468845ce6300bb305f8 Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 06:15:27 +0900 Subject: [PATCH 11/21] test: add missing test cases for Position --- src/test/java/racingcar/domain/PositionTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/java/racingcar/domain/PositionTest.java b/src/test/java/racingcar/domain/PositionTest.java index 90c0d82169..6ad0c7bc5c 100644 --- a/src/test/java/racingcar/domain/PositionTest.java +++ b/src/test/java/racingcar/domain/PositionTest.java @@ -30,4 +30,18 @@ void moveMultipleTimes() { assertEquals(3, movedPosition.getValue()); } + @Test + @DisplayName("초기 위치를 문자열로 변환하면 빈 문자열이다") + void displayInitialPosition() { + Position position = new Position(); + assertEquals("", position.displayPosition()); + } + + @Test + @DisplayName("위치를 문자열로 변환하면 '-'가 위치 값만큼 반복된다") + void displayPosition() { + Position position = new Position().move().move().move(); + assertEquals("---", position.displayPosition()); + } + } \ No newline at end of file From c7aa5ff82f1fbbed31efd1d30eb529061107f485 Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 06:18:40 +0900 Subject: [PATCH 12/21] feat: add position display function --- src/main/java/racingcar/domain/Position.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/racingcar/domain/Position.java b/src/main/java/racingcar/domain/Position.java index ce5de0ce33..2040f5260f 100644 --- a/src/main/java/racingcar/domain/Position.java +++ b/src/main/java/racingcar/domain/Position.java @@ -4,6 +4,7 @@ public class Position { private static final int INITIAL_POSITION = 0; private static final int MOVE_DISTANCE = 1; + private static final String POSITION_SYMBOL = "-"; private static final String NEGATIVE_POSITION_MESSAGE = "위치는 음수일 수 없습니다."; private final int value; @@ -25,6 +26,10 @@ public int getValue() { return value; } + public String displayPosition() { + return POSITION_SYMBOL.repeat(value); + } + private void validate(int value) { if (value < 0) { throw new IllegalArgumentException(NEGATIVE_POSITION_MESSAGE); From 92cf43880caea800fab7dead16790b483d7f0048 Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 06:22:18 +0900 Subject: [PATCH 13/21] test: add missing test cases for Car --- src/test/java/racingcar/domain/CarTest.java | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/test/java/racingcar/domain/CarTest.java b/src/test/java/racingcar/domain/CarTest.java index 82eed0a5ad..0012f0fd36 100644 --- a/src/test/java/racingcar/domain/CarTest.java +++ b/src/test/java/racingcar/domain/CarTest.java @@ -50,4 +50,24 @@ void moveMultipleTimes() { assertEquals(3, car.getPosition()); } + @Test + @DisplayName("초기 상태를 문자열로 출력하면 이름 : 으로 표시된다") + void displayInitialCarStatus() { + Car car = new Car("pobi"); + assertEquals("pobi : ", car.displayCar()); + } + + @Test + @DisplayName("자동차 상태를 이름 : - 으로 출력할 수 있다") + void displayCarStatus() { + Car car = new Car("pobi"); + MoveStrategy movableStrategy = () -> true; + + car.move(movableStrategy); + car.move(movableStrategy); + car.move(movableStrategy); + + assertEquals("pobi : ---", car.displayCar()); + } + } \ No newline at end of file From 0ac885e28eaec9c2e5799f81f20fe9d5d33aa1cd Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 06:25:17 +0900 Subject: [PATCH 14/21] feat: add Car display function --- src/main/java/racingcar/domain/Car.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/racingcar/domain/Car.java b/src/main/java/racingcar/domain/Car.java index bac171c11c..a9ebecc70a 100644 --- a/src/main/java/racingcar/domain/Car.java +++ b/src/main/java/racingcar/domain/Car.java @@ -26,4 +26,8 @@ public String getName() { return name.getValue(); } + public String displayCar() { + return name.getValue() + " : " + position.displayPosition(); + } + } From fff61b10afd69f9cc34be6a75b42ed70e23920b9 Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 06:34:30 +0900 Subject: [PATCH 15/21] test: add CarsTest with winner finding logic --- src/test/java/racingcar/domain/CarsTest.java | 61 ++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/test/java/racingcar/domain/CarsTest.java diff --git a/src/test/java/racingcar/domain/CarsTest.java b/src/test/java/racingcar/domain/CarsTest.java new file mode 100644 index 0000000000..fd171925a9 --- /dev/null +++ b/src/test/java/racingcar/domain/CarsTest.java @@ -0,0 +1,61 @@ +package racingcar.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import racingcar.strategy.MoveStrategy; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class CarsTest { + + @Test + @DisplayName("자동차 이름 리스트로 Cars를 생성할 수 있다") + void createCars() { + List names = List.of("pobi", "woni", "jun"); + Cars cars = new Cars(names); + + assertEquals(3, cars.getSize()); + } + + @Test + @DisplayName("가장 멀리 이동한 자동차가 우승자이다 - 단일") + void findWinner() { + List names = List.of("pobi", "woni", "jun"); + Cars cars = new Cars(names); + MoveStrategy movableStrategy = () -> true; + MoveStrategy notMovableStrategy = () -> false; + + cars.moveAll(movableStrategy); + cars.moveAll(notMovableStrategy); + cars.moveAll(notMovableStrategy); + + List winners = cars.getWinner(); + + assertEquals(1, winners.size()); + assertTrue(winners.contains("pobi")); + assertFalse(winners.contains("woni")); + assertFalse(winners.contains("jun")); + } + + @Test + @DisplayName("가장 멀리 이동한 자동차가 우승자이다 - 여러명") + void findWinners() { + List names = List.of("pobi", "woni", "jun"); + Cars cars = new Cars(names); + MoveStrategy movableStrategy = () -> true; + + cars.moveAll(movableStrategy); + cars.moveAll(movableStrategy); + cars.moveAll(movableStrategy); + + List winners = cars.getWinners(); + + assertEquals(3, winners.size()); + assertTrue(winners.contains("pobi")); + assertTrue(winners.contains("woni")); + assertTrue(winners.contains("jun")); + } + +} \ No newline at end of file From ea4fb64563d8fb265ffb5396d69a5be3a75e0c00 Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 06:45:16 +0900 Subject: [PATCH 16/21] feat: add Cars collection for managing multiple cars --- src/main/java/racingcar/domain/Cars.java | 41 ++++++++++++++++++++ src/test/java/racingcar/domain/CarsTest.java | 40 ------------------- 2 files changed, 41 insertions(+), 40 deletions(-) create mode 100644 src/main/java/racingcar/domain/Cars.java diff --git a/src/main/java/racingcar/domain/Cars.java b/src/main/java/racingcar/domain/Cars.java new file mode 100644 index 0000000000..6299145825 --- /dev/null +++ b/src/main/java/racingcar/domain/Cars.java @@ -0,0 +1,41 @@ +package racingcar.domain; + +import racingcar.strategy.RandomNumberMoveStrategy; + +import java.util.List; + +public class Cars { + + private final List cars; + + public Cars(List names) { + this.cars = names.stream() + .map(Car::new) + .toList(); + } + + public int getSize() { + return cars.size(); + } + + public void moveAll() { + for (Car car : cars) { + car.move(new RandomNumberMoveStrategy()); + } + } + + public List getWinners() { + int maxPosition = findMaxPosition(); + return cars.stream() + .filter(car -> car.getPosition() == maxPosition) + .map(Car::getName) + .toList(); + } + + private int findMaxPosition() { + return cars.stream() + .mapToInt(Car::getPosition) + .max() + .orElse(0); + } +} diff --git a/src/test/java/racingcar/domain/CarsTest.java b/src/test/java/racingcar/domain/CarsTest.java index fd171925a9..73f3dd2075 100644 --- a/src/test/java/racingcar/domain/CarsTest.java +++ b/src/test/java/racingcar/domain/CarsTest.java @@ -2,7 +2,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import racingcar.strategy.MoveStrategy; import java.util.List; @@ -19,43 +18,4 @@ void createCars() { assertEquals(3, cars.getSize()); } - @Test - @DisplayName("가장 멀리 이동한 자동차가 우승자이다 - 단일") - void findWinner() { - List names = List.of("pobi", "woni", "jun"); - Cars cars = new Cars(names); - MoveStrategy movableStrategy = () -> true; - MoveStrategy notMovableStrategy = () -> false; - - cars.moveAll(movableStrategy); - cars.moveAll(notMovableStrategy); - cars.moveAll(notMovableStrategy); - - List winners = cars.getWinner(); - - assertEquals(1, winners.size()); - assertTrue(winners.contains("pobi")); - assertFalse(winners.contains("woni")); - assertFalse(winners.contains("jun")); - } - - @Test - @DisplayName("가장 멀리 이동한 자동차가 우승자이다 - 여러명") - void findWinners() { - List names = List.of("pobi", "woni", "jun"); - Cars cars = new Cars(names); - MoveStrategy movableStrategy = () -> true; - - cars.moveAll(movableStrategy); - cars.moveAll(movableStrategy); - cars.moveAll(movableStrategy); - - List winners = cars.getWinners(); - - assertEquals(3, winners.size()); - assertTrue(winners.contains("pobi")); - assertTrue(winners.contains("woni")); - assertTrue(winners.contains("jun")); - } - } \ No newline at end of file From 423b4b043dedd11beeeaf491f9fd0a555cc35d3e Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 06:50:30 +0900 Subject: [PATCH 17/21] feat: add InputView for car names and round count --- src/main/java/racingcar/view/InputView.java | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/main/java/racingcar/view/InputView.java diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 0000000000..cd97e41938 --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,27 @@ +package racingcar.view; + +import camp.nextstep.edu.missionutils.Console; + +import java.util.Arrays; +import java.util.List; + +public class InputView { + + private static final String CAR_NAMES_INPUT_MESSAGE = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"; + private static final String ROUND_COUNT_INPUT_MESSAGE = "시도할 횟수는 몇 회인가요?"; + private static final String DELIMITER = ","; + + public List inputCarNames() { + System.out.println(CAR_NAMES_INPUT_MESSAGE); + String input = Console.readLine(); + return Arrays.stream(input.split(DELIMITER)) + .map(String::trim) + .toList(); + } + + public int inputRoundCount() { + System.out.println(ROUND_COUNT_INPUT_MESSAGE); + String input = Console.readLine(); + return Integer.parseInt(input); + } +} \ No newline at end of file From a2165ca7ff4d8c7f7aefd23c69daa0823a0c584b Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 06:56:30 +0900 Subject: [PATCH 18/21] feat: add OutputView for car winners --- src/main/java/racingcar/view/OutputView.java | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/racingcar/view/OutputView.java diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 0000000000..7d0ade4f63 --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -0,0 +1,24 @@ +package racingcar.view; + +import racingcar.domain.Car; + +import java.util.List; + +public class OutputView { + + private static final String RESULT_OUTPUT_MESSAGE = "\n실행 결과"; + private static final String WINNER_OUTPUT_MESSAGE = "최종 우승자 : "; + private static final String DELIMITER = ", "; + + public void printRoundResults(List cars) { + System.out.println(RESULT_OUTPUT_MESSAGE); + cars.forEach(car -> System.out.println(car.displayCar())); + System.out.println(); + } + + public void printWinners(List winners) { + String winner = String.join(DELIMITER, winners); + System.out.println(WINNER_OUTPUT_MESSAGE + winner); + } + +} \ No newline at end of file From de2ee3c80fdf8919816c90850533d8290b8963eb Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 07:00:36 +0900 Subject: [PATCH 19/21] feat: add RacingGame controller --- .../java/racingcar/controller/RacingGame.java | 40 +++++++++++++++++++ src/main/java/racingcar/domain/Cars.java | 4 ++ 2 files changed, 44 insertions(+) create mode 100644 src/main/java/racingcar/controller/RacingGame.java diff --git a/src/main/java/racingcar/controller/RacingGame.java b/src/main/java/racingcar/controller/RacingGame.java new file mode 100644 index 0000000000..073701ba62 --- /dev/null +++ b/src/main/java/racingcar/controller/RacingGame.java @@ -0,0 +1,40 @@ +package racingcar.controller; + +import racingcar.domain.Cars; +import racingcar.view.InputView; +import racingcar.view.OutputView; + +import java.util.List; + +public class RacingGame { + + private final InputView inputView; + private final OutputView outputView; + + public RacingGame() { + this.inputView = new InputView(); + this.outputView = new OutputView(); + } + + public void run() { + List carNames = inputView.inputCarNames(); + int roundCount = inputView.inputRoundCount(); + + Cars cars = new Cars(carNames); + + playRounds(cars, roundCount); + showWinners(cars); + } + + private void playRounds(Cars cars, int roundCount) { + for (int i = 0; i < roundCount; i++) { + cars.moveAll(); + outputView.printRoundResults(cars.getCars()); + } + } + + private void showWinners(Cars cars) { + List winners = cars.getWinners(); + outputView.printWinners(winners); + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/domain/Cars.java b/src/main/java/racingcar/domain/Cars.java index 6299145825..2ec543adec 100644 --- a/src/main/java/racingcar/domain/Cars.java +++ b/src/main/java/racingcar/domain/Cars.java @@ -18,6 +18,10 @@ public int getSize() { return cars.size(); } + public List getCars() { + return cars; + } + public void moveAll() { for (Car car : cars) { car.move(new RandomNumberMoveStrategy()); From dbfcd2502ed3b2d61b8167a362ff741ca2fba9e8 Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 07:02:25 +0900 Subject: [PATCH 20/21] feat: add racing game run method --- src/main/java/racingcar/Application.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e724..ed2d63c3de 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,10 @@ package racingcar; +import racingcar.controller.RacingGame; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + RacingGame racingGame = new RacingGame(); + racingGame.run(); } } From 2800aab34bd602f8a4f5276b91c12553333ee3da Mon Sep 17 00:00:00 2001 From: Huiyeongkim Date: Sun, 26 Oct 2025 07:07:38 +0900 Subject: [PATCH 21/21] docs: check off completed tasks --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index df8b096b5d..2ca5b7ed9c 100644 --- a/README.md +++ b/README.md @@ -8,32 +8,32 @@ - [X] 잘못된 입력 시 `IllegalArgumentException` 발생 ### 2. 자동차 이동 로직 -- [ ] 0~9 사이 랜덤 값 >= 4일 때 전진 -- [ ] 4 이하인 경우 정지 +- [X] 0~9 사이 랜덤 값 >= 4일 때 전진 +- [X] 4 이하인 경우 정지 ### 3. 위치(Position) VO - [X] 초기 위치 = 0 - [X] 이동 시 불변 객체 반환 -- [ ] 위치 출력 시 `-` 문자를 사용하여 표현 +- [X] 위치 출력 시 `-` 문자를 사용하여 표현 ### 4. Car 클래스 -- [ ] 자동차 이름(Name VO)과 위치(Position VO) 관리 -- [ ] move() 메서드에서 MoveStrategy를 통해 전진 여부 결정 +- [X] 자동차 이름(Name VO)과 위치(Position VO) 관리 +- [X] move() 메서드에서 MoveStrategy를 통해 전진 여부 결정 ### 5. Cars 컬렉션 -- [ ] 여러 대의 Car를 관리 -- [ ] 각 라운드마다 모든 자동차 이동 수행 -- [ ] 우승자 계산 기능 +- [X] 여러 대의 Car를 관리 +- [X] 각 라운드마다 모든 자동차 이동 수행 +- [X] 우승자 계산 기능 ### 6. 게임 진행 -- [ ] 시도할 횟수만큼 라운드 반복 -- [ ] 각 라운드 결과 출력 -- [ ] 최종 우승자 출력 +- [X] 시도할 횟수만큼 라운드 반복 +- [X] 각 라운드 결과 출력 +- [X] 최종 우승자 출력 ### 7. 입력/출력 -- [ ] 사용자 입력 처리 +- [X] 사용자 입력 처리 - 자동차 이름 - 시도 횟수 -- [ ] 실행 결과 출력 +- [X] 실행 결과 출력 - 각 자동차의 위치 - 우승자가 여러 명일 경우 ','로 구분하여 공동 우승 표시