From 4a345879d15ca6cf43a3ec8d25f75294f24404d1 Mon Sep 17 00:00:00 2001 From: HJ-Rich Date: Sat, 9 Mar 2024 21:29:39 +0900 Subject: [PATCH 1/9] =?UTF-8?q?docs:=202=EB=8B=A8=EA=B3=84=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=AC=B8=EC=84=9C=20=EB=B3=B4=EA=B0=95.=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20=EC=A0=84=20=ED=9B=84=20?= =?UTF-8?q?=EB=B9=84=EA=B5=90=20=EB=B0=8F=20=EC=B2=B4=ED=81=AC=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B2=B4=ED=81=AC=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 31478a687ab..9c69d78e9cf 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,10 @@ --- -# 2단계 +# 2단계 - 문자열 덧셈 계산기를 통한 TDD 실습 -## 문자열 덧셈 계산기를 통한 TDD 실습 +
+ 요구사항 보기 ### 기능 요구사항 @@ -89,6 +90,7 @@ ### 리팩터링 ```java +// as-is public static int splitAndSum(String input) { if (isNullOrEmpty(input)) { return DEFAULT_VALUE_FOR_EMPTY_INPUT; @@ -110,9 +112,28 @@ public static int splitAndSum(String input) { - null 또는 빈 문자열 체크 -> 구분자 획득 -> split -> sum - public으로 열린 splitAndSum 메서드에서는 위 동작 흐름만 나타날 수 있게 리팩터링 해보자 -- [ ] 구분자 획득 - - [ ] 커스텀 구분자 정규식에 - - [ ] 일치하면 커스텀 구분자를 반환 - - [ ] 일치하지 않으면 기본 구분자를 반환 -- [ ] split and sum - - [ ] 전달 받은 구분자로 split, parse, sum +- [x] split + - [x] 커스텀 구분자 정규식에 + - [x] 일치하면 커스텀 구분자로 split한 문자열 배열을 반환 + - [x] 일치하지 않으면 기본 구분자로 split한 문자열 배열을 반환 +- [x] parseInt and sum + - [x] 문자열 배열을 전달받아 Stream API로 아래 메서드를 활용하여 mapToInt한 결과를 sum하여 반환 + - [x] `StringAddCalculator#parsePositiveSingleNumber`: 파싱한 결과가 음수이면 예외 반환, 0 또는 양수이면 숫자 반환 + - [x] `StringAddCalculator#parseSingleNumber`: 파싱한 결과가 숫자면 반환, 아닐 경우 RuntimeException 던지기 + +```java +// to-be +public static int splitAndSum(final String input) { + if (isNullOrEmpty(input)) { + return DEFAULT_VALUE_FOR_EMPTY_INPUT; + } + + final String[] splitInput = splitByDelimiter(input); + + return parseIntAndSum(splitInput); +} +``` + +
+ +--- From 866577548b05ff532f63f10d59542c3688c2b2b3 Mon Sep 17 00:00:00 2001 From: HJ-Rich Date: Sun, 10 Mar 2024 05:39:32 +0900 Subject: [PATCH 2/9] =?UTF-8?q?docs:=203=EA=B3=84=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=AC=B8=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index 9c69d78e9cf..41ba1741142 100644 --- a/README.md +++ b/README.md @@ -137,3 +137,26 @@ public static int splitAndSum(final String input) { --- + +# 3단계 - 자동차 경주 + +## 기능 요구사항 + +- 초간단 자동차 경주 게임을 구현한다. +- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. +- 사용자는 몇 대의 자동차로 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. +- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4이상일 경우이다. +- 자동차의 상태를 화면에 출력한다. 어느 시점에 출력할 것인지에 대한 제약은 없다. + +## 힌트 + +- 값을 입력 받는 API는 Scanner를 이용한다. +- 랜덤 값은 자바 java.util.Random 클래스의 nextInt(10) 메소드를 활용한다. + +## 요구사항 분리 + +- [ ] `자동차 대수는 몇 대 인가요?` -> 양의 정수 하나를 입력 받음 +- [ ] `시도할 회수는 몇 회 인가요?` -> 양의 정수 하나를 입력 받음 +- [ ] 개행 및 `실행 결과` 출력 +- [ ] 0-9사이 Random 값이 4이상인 경우 전진하는 메서드 구현 +- [ ] 입력 받은 시도 횟수 만큼 개행으로 구분하며 각 자동차의 경과를 출력 From 74a52161c78f7b8d4b45a3860cda0a30ab81e3f4 Mon Sep 17 00:00:00 2001 From: HJ-Rich Date: Sun, 10 Mar 2024 06:14:00 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=EC=9E=90=EB=8F=99=EC=B0=A8=20?= =?UTF-8?q?=EC=88=98=EC=99=80=20=EC=8B=9C=EB=8F=84=20=ED=9A=9F=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20=EC=9D=98=EB=AF=B8=ED=95=98=EB=8A=94=20=EA=B0=92=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +-- .../racingcar/domain/vo/NumberOfCars.java | 21 ++++++++++++ .../racingcar/domain/vo/NumberOfTrials.java | 21 ++++++++++++ .../racingcar/domain/vo/NumberOfCarsTest.java | 34 +++++++++++++++++++ .../domain/vo/NumberOfTrialsTest.java | 34 +++++++++++++++++++ 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 src/main/java/racingcar/domain/vo/NumberOfCars.java create mode 100644 src/main/java/racingcar/domain/vo/NumberOfTrials.java create mode 100644 src/test/java/racingcar/domain/vo/NumberOfCarsTest.java create mode 100644 src/test/java/racingcar/domain/vo/NumberOfTrialsTest.java diff --git a/README.md b/README.md index 41ba1741142..67b6fa1558e 100644 --- a/README.md +++ b/README.md @@ -155,8 +155,8 @@ public static int splitAndSum(final String input) { ## 요구사항 분리 -- [ ] `자동차 대수는 몇 대 인가요?` -> 양의 정수 하나를 입력 받음 -- [ ] `시도할 회수는 몇 회 인가요?` -> 양의 정수 하나를 입력 받음 +- [x] `자동차 대수는 몇 대 인가요?` -> 양의 정수 하나를 입력 받음 +- [x] `시도할 회수는 몇 회 인가요?` -> 양의 정수 하나를 입력 받음 - [ ] 개행 및 `실행 결과` 출력 - [ ] 0-9사이 Random 값이 4이상인 경우 전진하는 메서드 구현 - [ ] 입력 받은 시도 횟수 만큼 개행으로 구분하며 각 자동차의 경과를 출력 diff --git a/src/main/java/racingcar/domain/vo/NumberOfCars.java b/src/main/java/racingcar/domain/vo/NumberOfCars.java new file mode 100644 index 00000000000..9bdd41c172f --- /dev/null +++ b/src/main/java/racingcar/domain/vo/NumberOfCars.java @@ -0,0 +1,21 @@ +package racingcar.domain.vo; + +public class NumberOfCars { + private final int value; + + private NumberOfCars(final int value) { + this.value = value; + } + + public static NumberOfCars from(final int numberOfCar) { + if (numberOfCar < 1) { + throw new RuntimeException("자동차는 1대 이상이어야 합니다"); + } + + return new NumberOfCars(numberOfCar); + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/racingcar/domain/vo/NumberOfTrials.java b/src/main/java/racingcar/domain/vo/NumberOfTrials.java new file mode 100644 index 00000000000..800cb77d39c --- /dev/null +++ b/src/main/java/racingcar/domain/vo/NumberOfTrials.java @@ -0,0 +1,21 @@ +package racingcar.domain.vo; + +public class NumberOfTrials { + private final int value; + + private NumberOfTrials(final int value) { + this.value = value; + } + + public static NumberOfTrials from(final int numberOfTrial) { + if (numberOfTrial < 1) { + throw new RuntimeException("시도 횟수는 1회 이상이어야 합니다"); + } + + return new NumberOfTrials(numberOfTrial); + } + + public int getValue() { + return value; + } +} diff --git a/src/test/java/racingcar/domain/vo/NumberOfCarsTest.java b/src/test/java/racingcar/domain/vo/NumberOfCarsTest.java new file mode 100644 index 00000000000..7e90c1f3367 --- /dev/null +++ b/src/test/java/racingcar/domain/vo/NumberOfCarsTest.java @@ -0,0 +1,34 @@ +package racingcar.domain.vo; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; + +@DisplayName("자동차 수 값 객체 테스트") +class NumberOfCarsTest { + @DisplayName("자동차 수가 1대 미만일 경우 예외가 발생한다") + @ParameterizedTest(name = "{0}") + @ValueSource(ints = {-100, -1, 0}) + void exceptionShouldBeThrownForLessThan1Car(final int numberOfCars) { + // given & when + final RuntimeException actual = catchThrowableOfType( + () -> NumberOfCars.from(numberOfCars), RuntimeException.class); + + // then + assertThat(actual).hasMessage("자동차는 1대 이상이어야 합니다"); + } + + @DisplayName("자동차 수가 1대 이상일 경우 예외가 발생하지 않는다") + @ParameterizedTest(name = "{0}") + @ValueSource(ints = {1, 10, 100}) + void exceptionShouldBeThrownForMoreThan1Car(final int numberOfCars) { + // given & when + final NumberOfCars actual = NumberOfCars.from(numberOfCars); + + // then + assertThat(actual.getValue()).isEqualTo(numberOfCars); + } +} diff --git a/src/test/java/racingcar/domain/vo/NumberOfTrialsTest.java b/src/test/java/racingcar/domain/vo/NumberOfTrialsTest.java new file mode 100644 index 00000000000..73ad1aa089a --- /dev/null +++ b/src/test/java/racingcar/domain/vo/NumberOfTrialsTest.java @@ -0,0 +1,34 @@ +package racingcar.domain.vo; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; + +@DisplayName("시도 횟수 값 객체 테스트") +class NumberOfTrialsTest { + @DisplayName("시도 횟수가 1 미만일 경우 예외가 발생한다") + @ParameterizedTest(name = "{0}") + @ValueSource(ints = {-100, -1, 0}) + void exceptionShouldBeThrownForLessThan1Trial(final int numberOfTrials) { + // given & when + final RuntimeException actual = catchThrowableOfType( + () -> NumberOfTrials.from(numberOfTrials), RuntimeException.class); + + // then + assertThat(actual).hasMessage("시도 횟수는 1회 이상이어야 합니다"); + } + + @DisplayName("시도 횟수가 1 이상일 경우 예외가 발생하지 않는다") + @ParameterizedTest(name = "{0}") + @ValueSource(ints = {1, 10, 100}) + void exceptionShouldBeThrownForMoreThan1Trial(final int numberOfTrials) { + // given & when + final NumberOfTrials actual = NumberOfTrials.from(numberOfTrials); + + // then + assertThat(actual.getValue()).isEqualTo(numberOfTrials); + } +} From 26830d5e04c60f6f639b7f3be5e366fe0ab37ab8 Mon Sep 17 00:00:00 2001 From: HJ-Rich Date: Sun, 10 Mar 2024 06:18:22 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EC=9E=85=EB=A0=A5=EB=B0=9B?= =?UTF-8?q?=EC=9D=84=20=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B5=AC=ED=98=84=EC=B2=B4=20=EA=B5=AC=ED=98=84,?= =?UTF-8?q?=20=EC=9E=85=EB=A0=A5=EA=B0=92=EC=9D=84=20=EC=A0=84=EB=8B=AC?= =?UTF-8?q?=ED=95=A0=20RacingCarInput=20DTO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../racingcar/domain/dto/RacingCarInput.java | 22 ++++++++++ .../presentation/ConsoleRacingCarClient.java | 42 +++++++++++++++++++ .../presentation/RacingCarClient.java | 7 ++++ 3 files changed, 71 insertions(+) create mode 100644 src/main/java/racingcar/domain/dto/RacingCarInput.java create mode 100644 src/main/java/racingcar/presentation/ConsoleRacingCarClient.java create mode 100644 src/main/java/racingcar/presentation/RacingCarClient.java diff --git a/src/main/java/racingcar/domain/dto/RacingCarInput.java b/src/main/java/racingcar/domain/dto/RacingCarInput.java new file mode 100644 index 00000000000..e9458ff16fe --- /dev/null +++ b/src/main/java/racingcar/domain/dto/RacingCarInput.java @@ -0,0 +1,22 @@ +package racingcar.domain.dto; + +import racingcar.domain.vo.NumberOfCars; +import racingcar.domain.vo.NumberOfTrials; + +public class RacingCarInput { + private final NumberOfCars numberOfCars; + private final NumberOfTrials numberOfTrials; + + public RacingCarInput(final NumberOfCars numberOfCars, final NumberOfTrials numberOfTrials) { + this.numberOfCars = numberOfCars; + this.numberOfTrials = numberOfTrials; + } + + public NumberOfCars getNumberOfCars() { + return numberOfCars; + } + + public NumberOfTrials getNumberOfTrials() { + return numberOfTrials; + } +} diff --git a/src/main/java/racingcar/presentation/ConsoleRacingCarClient.java b/src/main/java/racingcar/presentation/ConsoleRacingCarClient.java new file mode 100644 index 00000000000..71c66053fe1 --- /dev/null +++ b/src/main/java/racingcar/presentation/ConsoleRacingCarClient.java @@ -0,0 +1,42 @@ +package racingcar.presentation; + +import racingcar.domain.dto.RacingCarInput; +import racingcar.domain.vo.NumberOfCars; +import racingcar.domain.vo.NumberOfTrials; + +import java.util.Scanner; + +public class ConsoleRacingCarClient implements RacingCarClient { + private final Scanner scanner = new Scanner(System.in); + + @Override + public RacingCarInput getRacingCarInput() { + final NumberOfCars numberOfCars = getNumberOfCars(); + final NumberOfTrials numberOfTrials = getNumberOfTrials(); + scanner.close(); + + return new RacingCarInput(numberOfCars, numberOfTrials); + } + + public NumberOfCars getNumberOfCars() { + System.out.println("자동차 대수는 몇 대 인가요?"); + final String input = scanner.nextLine(); + try { + final int parsed = Integer.parseInt(input); + return NumberOfCars.from(parsed); + } catch (NumberFormatException e) { + throw new RuntimeException(String.format("자동차 대수를 양의 숫자로 입력해주세요 : %s", input), e); + } + } + + public NumberOfTrials getNumberOfTrials() { + System.out.println("시도할 회수는 몇 회 인가요?"); + final String input = scanner.nextLine(); + try { + final int parsed = Integer.parseInt(input); + return NumberOfTrials.from(parsed); + } catch (NumberFormatException e) { + throw new RuntimeException(String.format("시도 횟수를 양의 숫자로 입력해주세요 : %s", input), e); + } + } +} diff --git a/src/main/java/racingcar/presentation/RacingCarClient.java b/src/main/java/racingcar/presentation/RacingCarClient.java new file mode 100644 index 00000000000..1df141e6119 --- /dev/null +++ b/src/main/java/racingcar/presentation/RacingCarClient.java @@ -0,0 +1,7 @@ +package racingcar.presentation; + +import racingcar.domain.dto.RacingCarInput; + +public interface RacingCarClient { + RacingCarInput getRacingCarInput(); +} From 011b986fc3450b0358af284d42bdd08673028c16 Mon Sep 17 00:00:00 2001 From: HJ-Rich Date: Mon, 11 Mar 2024 03:17:47 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20boolean=EC=9D=84=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=98=EB=8A=94=20=ED=95=A8=EC=88=98=ED=98=95=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EB=B0=8F=2060%?= =?UTF-8?q?=EB=A1=9C=20true=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B5=AC=ED=98=84=EC=B2=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../java/racingcar/domain/CarMoveGenerator.java | 6 ++++++ .../domain/SixtyPercentAdvanceGenerator.java | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 src/main/java/racingcar/domain/CarMoveGenerator.java create mode 100644 src/main/java/racingcar/domain/SixtyPercentAdvanceGenerator.java diff --git a/README.md b/README.md index 67b6fa1558e..9e97789b4bf 100644 --- a/README.md +++ b/README.md @@ -158,5 +158,5 @@ public static int splitAndSum(final String input) { - [x] `자동차 대수는 몇 대 인가요?` -> 양의 정수 하나를 입력 받음 - [x] `시도할 회수는 몇 회 인가요?` -> 양의 정수 하나를 입력 받음 - [ ] 개행 및 `실행 결과` 출력 -- [ ] 0-9사이 Random 값이 4이상인 경우 전진하는 메서드 구현 +- [x] 0-9사이 Random 값이 4이상인 경우 전진하는 메서드 구현 - [ ] 입력 받은 시도 횟수 만큼 개행으로 구분하며 각 자동차의 경과를 출력 diff --git a/src/main/java/racingcar/domain/CarMoveGenerator.java b/src/main/java/racingcar/domain/CarMoveGenerator.java new file mode 100644 index 00000000000..d4231e21fd2 --- /dev/null +++ b/src/main/java/racingcar/domain/CarMoveGenerator.java @@ -0,0 +1,6 @@ +package racingcar.domain; + +@FunctionalInterface +public interface CarMoveGenerator { + boolean advance(); +} diff --git a/src/main/java/racingcar/domain/SixtyPercentAdvanceGenerator.java b/src/main/java/racingcar/domain/SixtyPercentAdvanceGenerator.java new file mode 100644 index 00000000000..3dfbfa38bc0 --- /dev/null +++ b/src/main/java/racingcar/domain/SixtyPercentAdvanceGenerator.java @@ -0,0 +1,14 @@ +package racingcar.domain; + +import java.util.Random; + +public class SixtyPercentAdvanceGenerator implements CarMoveGenerator { + private static final int UPPER_BOUND_EXCLUSIVE = 10; + private static final int ADVANCE_STANDARD = 4; + private final Random random = new Random(); + + @Override + public boolean advance() { + return random.nextInt(UPPER_BOUND_EXCLUSIVE) >= ADVANCE_STANDARD; + } +} From 9a6465f40096ca6418e370cd49e9bdb05b3861ee Mon Sep 17 00:00:00 2001 From: HJ-Rich Date: Mon, 11 Mar 2024 03:18:52 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=EC=B5=9C=EC=B4=88=20=ED=8F=AC?= =?UTF-8?q?=EC=A7=80=EC=85=98=201,=20advance=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EC=97=90=20true=EB=A5=BC=20=EC=A0=84=EB=8B=AC=ED=95=98?= =?UTF-8?q?=EB=A9=B4=20=ED=8F=AC=EC=A7=80=EC=85=98=EC=9D=B4=201=EC=94=A9?= =?UTF-8?q?=20=EC=A6=9D=EA=B0=80=ED=95=98=EB=8A=94=20Car=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=84=A0=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/domain/Car.java | 15 +++++++ src/test/java/racingcar/domain/CarTest.java | 50 +++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/main/java/racingcar/domain/Car.java create mode 100644 src/test/java/racingcar/domain/CarTest.java diff --git a/src/main/java/racingcar/domain/Car.java b/src/main/java/racingcar/domain/Car.java new file mode 100644 index 00000000000..f22b7f12d29 --- /dev/null +++ b/src/main/java/racingcar/domain/Car.java @@ -0,0 +1,15 @@ +package racingcar.domain; + +public class Car { + private int position = 1; + + public void advance(final boolean advance) { + if (advance) { + this.position++; + } + } + + public int getPosition() { + return position; + } +} diff --git a/src/test/java/racingcar/domain/CarTest.java b/src/test/java/racingcar/domain/CarTest.java new file mode 100644 index 00000000000..333a6e254af --- /dev/null +++ b/src/test/java/racingcar/domain/CarTest.java @@ -0,0 +1,50 @@ +package racingcar.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("Car 테스트") +class CarTest { + @DisplayName("자동차 최초 생성 시 포지션은 1이다") + @Test + void carHasPosition1AfterCreation() { + // given + final Car car = new Car(); + + // when + final int actual = car.getPosition(); + + // then + assertThat(actual).isOne(); + } + + @DisplayName("advance 메서드에 true를 전달하면 position이 1 증가한다") + @Test + void positionIncreasesWhenTruePassed() { + // given + final Car car = new Car(); + car.advance(true); + + // when + final int actual = car.getPosition(); + + // then + assertThat(actual).isEqualTo(2); + } + + @DisplayName("advance 메서드에 false를 전달하면 position은 그대로 1이다") + @Test + void positionRemainsSameWhenFalsePassed() { + // given + final Car car = new Car(); + car.advance(false); + + // when + final int actual = car.getPosition(); + + // then + assertThat(actual).isEqualTo(1); + } +} From 8d8e1dd1604546b021c089b1a2c39c3ea7a1b115 Mon Sep 17 00:00:00 2001 From: HJ-Rich Date: Mon, 11 Mar 2024 03:19:39 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20Car=EB=93=A4=EC=9D=84=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=ED=95=98=EB=8A=94=20=EC=9D=BC=EA=B8=89=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=20Cars=EC=84=A0=EC=96=B8.=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=ED=98=95=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=EB=A5=BC=20=EC=9D=B4=EC=9A=A9=ED=95=B4=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=20=EC=9E=90=EB=8F=99=EC=B0=A8=EB=A5=BC=20=EC=A0=84?= =?UTF-8?q?=EC=A7=84=20=EC=8B=9C=EB=8F=84=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/domain/Cars.java | 24 ++++++ src/test/java/racingcar/domain/CarsTest.java | 84 ++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/main/java/racingcar/domain/Cars.java create mode 100644 src/test/java/racingcar/domain/CarsTest.java diff --git a/src/main/java/racingcar/domain/Cars.java b/src/main/java/racingcar/domain/Cars.java new file mode 100644 index 00000000000..f24f167f088 --- /dev/null +++ b/src/main/java/racingcar/domain/Cars.java @@ -0,0 +1,24 @@ +package racingcar.domain; + +import java.util.ArrayList; +import java.util.List; + +public class Cars { + private final List cars; + + public Cars(final List cars) { + if (cars == null || cars.isEmpty()) { + throw new RuntimeException("자동차 목록이 비어있습니다"); + } + + this.cars = new ArrayList<>(cars); + } + + public void tryAdvance(final CarMoveGenerator carMoveGenerator) { + cars.forEach(car -> car.advance(carMoveGenerator.advance())); + } + + public List getCars() { + return cars; + } +} diff --git a/src/test/java/racingcar/domain/CarsTest.java b/src/test/java/racingcar/domain/CarsTest.java new file mode 100644 index 00000000000..96275510eb0 --- /dev/null +++ b/src/test/java/racingcar/domain/CarsTest.java @@ -0,0 +1,84 @@ +package racingcar.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("Cars 테스트") +class CarsTest { + @DisplayName("null 또는 빈 리스트로 Cars 생성하려 하면 예외가 발생한다") + @ParameterizedTest(name = "{0}") + @NullAndEmptySource + void exceptionShouldBeThrownForNullOrEmpty(final List nullOrEmpty) { + // given & when + final RuntimeException actual = catchThrowableOfType(() -> new Cars(nullOrEmpty), RuntimeException.class); + + // then + assertThat(actual).hasMessage("자동차 목록이 비어있습니다"); + } + + @DisplayName("1 이상의 길이를 가진 리스트로 Cars를 생성할 수 있다") + @Test + void creationSuccessForMoreThanOneSizeOfCarList() { + // given + final List validCars = new ArrayList<>(); + final Car car = new Car(); + validCars.add(car); + + // when + final Cars actual = new Cars(validCars); + + // then + assertAll( + () -> assertThat(actual).isNotNull(), + () -> assertThat(actual.getCars()).hasSize(1) + ); + } + + @DisplayName("tryAdvance 메서드를 통해 내부의 Car들에 대해 전진 시도할 수 있다 [전진 성공]") + @Test + void tryAdvanceForContainingCarsSuccessCase() { + // given + final Cars cars = new Cars(List.of(new Car(), new Car(), new Car())); + final CarMoveGenerator mustMoveGenerator = () -> true; + + // when + cars.tryAdvance(mustMoveGenerator); + + // then + final List actual = cars.getCars() + .stream() + .map(Car::getPosition) + .collect(Collectors.toList()); + + assertThat(actual).containsExactly(2, 2, 2); + } + + @DisplayName("tryAdvance 메서드를 통해 내부의 Car들에 대해 전진 시도할 수 있다 [전진 실패]") + @Test + void tryAdvanceForContainingCarsFailCase() { + // given + final Cars cars = new Cars(List.of(new Car(), new Car(), new Car())); + final CarMoveGenerator mustMoveGenerator = () -> false; + + // when + cars.tryAdvance(mustMoveGenerator); + + // then + final List actual = cars.getCars() + .stream() + .map(Car::getPosition) + .collect(Collectors.toList()); + + assertThat(actual).containsExactly(1, 1, 1); + } +} From b90d91410c00f46fe6944cb94292b8fc24f2c00d Mon Sep 17 00:00:00 2001 From: HJ-Rich Date: Mon, 11 Mar 2024 03:20:48 +0900 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20RacingCar=20=EA=B2=8C=EB=B0=8D?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9E=85=EC=B6=9C=EB=A0=A5?= =?UTF-8?q?=EC=9D=84=20=EB=8B=B4=EB=8B=B9=ED=95=A0=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EB=B0=8F=20=EC=BD=98=EC=86=94?= =?UTF-8?q?=EC=9A=A9=20=EA=B5=AC=ED=98=84=EC=B2=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ingCarInput.java => RacingCarRequest.java} | 4 +- .../racingcar/domain/dto/RacingCarResult.java | 17 ++++++++ .../presentation/ConsoleRacingCarClient.java | 41 +++++++++++++++---- .../presentation/RacingCarClient.java | 9 +++- 4 files changed, 58 insertions(+), 13 deletions(-) rename src/main/java/racingcar/domain/dto/{RacingCarInput.java => RacingCarRequest.java} (78%) create mode 100644 src/main/java/racingcar/domain/dto/RacingCarResult.java diff --git a/src/main/java/racingcar/domain/dto/RacingCarInput.java b/src/main/java/racingcar/domain/dto/RacingCarRequest.java similarity index 78% rename from src/main/java/racingcar/domain/dto/RacingCarInput.java rename to src/main/java/racingcar/domain/dto/RacingCarRequest.java index e9458ff16fe..03603ead420 100644 --- a/src/main/java/racingcar/domain/dto/RacingCarInput.java +++ b/src/main/java/racingcar/domain/dto/RacingCarRequest.java @@ -3,11 +3,11 @@ import racingcar.domain.vo.NumberOfCars; import racingcar.domain.vo.NumberOfTrials; -public class RacingCarInput { +public class RacingCarRequest { private final NumberOfCars numberOfCars; private final NumberOfTrials numberOfTrials; - public RacingCarInput(final NumberOfCars numberOfCars, final NumberOfTrials numberOfTrials) { + public RacingCarRequest(final NumberOfCars numberOfCars, final NumberOfTrials numberOfTrials) { this.numberOfCars = numberOfCars; this.numberOfTrials = numberOfTrials; } diff --git a/src/main/java/racingcar/domain/dto/RacingCarResult.java b/src/main/java/racingcar/domain/dto/RacingCarResult.java new file mode 100644 index 00000000000..e4ff49df8b5 --- /dev/null +++ b/src/main/java/racingcar/domain/dto/RacingCarResult.java @@ -0,0 +1,17 @@ +package racingcar.domain.dto; + +import racingcar.domain.Car; + +import java.util.List; + +public class RacingCarResult { + private final List cars; + + public RacingCarResult(final List cars) { + this.cars = cars; + } + + public List getCars() { + return cars; + } +} diff --git a/src/main/java/racingcar/presentation/ConsoleRacingCarClient.java b/src/main/java/racingcar/presentation/ConsoleRacingCarClient.java index 71c66053fe1..5f91dd26366 100644 --- a/src/main/java/racingcar/presentation/ConsoleRacingCarClient.java +++ b/src/main/java/racingcar/presentation/ConsoleRacingCarClient.java @@ -1,42 +1,65 @@ package racingcar.presentation; -import racingcar.domain.dto.RacingCarInput; +import racingcar.domain.Car; +import racingcar.domain.dto.RacingCarRequest; +import racingcar.domain.dto.RacingCarResult; import racingcar.domain.vo.NumberOfCars; import racingcar.domain.vo.NumberOfTrials; +import java.util.List; import java.util.Scanner; public class ConsoleRacingCarClient implements RacingCarClient { + private static final String LINE_SEPARATOR = System.lineSeparator(); + private static final String PROGRESS_CHARACTER = "-"; + private static final String MESSAGE_FOR_CAR_INPUT_REQUEST = "자동차 대수는 몇 대 인가요?"; + private static final String MESSAGE_FOR_TRIAL_INPUT_REQUEST = "시도할 회수는 몇 회 인가요?"; + private static final String MESSAGE_FOR_INVALID_CAR_INPUT = "자동차 대수를 양의 숫자로 입력해주세요 : %s"; + private static final String MESSAGE_FOR_INVALID_TRIAL_INPUT = "시도 횟수를 양의 숫자로 입력해주세요 : %s"; + private static final String RESULT_HEADER = "%s실행 결과%s"; private final Scanner scanner = new Scanner(System.in); @Override - public RacingCarInput getRacingCarInput() { + public RacingCarRequest getRacingCarInput() { final NumberOfCars numberOfCars = getNumberOfCars(); final NumberOfTrials numberOfTrials = getNumberOfTrials(); scanner.close(); - return new RacingCarInput(numberOfCars, numberOfTrials); + return new RacingCarRequest(numberOfCars, numberOfTrials); } - public NumberOfCars getNumberOfCars() { - System.out.println("자동차 대수는 몇 대 인가요?"); + private NumberOfCars getNumberOfCars() { + System.out.println(MESSAGE_FOR_CAR_INPUT_REQUEST); final String input = scanner.nextLine(); try { final int parsed = Integer.parseInt(input); return NumberOfCars.from(parsed); } catch (NumberFormatException e) { - throw new RuntimeException(String.format("자동차 대수를 양의 숫자로 입력해주세요 : %s", input), e); + throw new RuntimeException(String.format(MESSAGE_FOR_INVALID_CAR_INPUT, input), e); } } - public NumberOfTrials getNumberOfTrials() { - System.out.println("시도할 회수는 몇 회 인가요?"); + private NumberOfTrials getNumberOfTrials() { + System.out.println(MESSAGE_FOR_TRIAL_INPUT_REQUEST); final String input = scanner.nextLine(); try { final int parsed = Integer.parseInt(input); return NumberOfTrials.from(parsed); } catch (NumberFormatException e) { - throw new RuntimeException(String.format("시도 횟수를 양의 숫자로 입력해주세요 : %s", input), e); + throw new RuntimeException(String.format(MESSAGE_FOR_INVALID_TRIAL_INPUT, input), e); } } + + @Override + public void showResultHeader() { + System.out.printf(RESULT_HEADER, LINE_SEPARATOR, LINE_SEPARATOR); + } + + @Override + public void showRacingCarResult(final RacingCarResult racingCarResult) { + final List cars = racingCarResult.getCars(); + cars.forEach(car -> System.out.println(PROGRESS_CHARACTER.repeat(car.getPosition()))); + + System.out.println(); + } } diff --git a/src/main/java/racingcar/presentation/RacingCarClient.java b/src/main/java/racingcar/presentation/RacingCarClient.java index 1df141e6119..9b5fe567d10 100644 --- a/src/main/java/racingcar/presentation/RacingCarClient.java +++ b/src/main/java/racingcar/presentation/RacingCarClient.java @@ -1,7 +1,12 @@ package racingcar.presentation; -import racingcar.domain.dto.RacingCarInput; +import racingcar.domain.dto.RacingCarRequest; +import racingcar.domain.dto.RacingCarResult; public interface RacingCarClient { - RacingCarInput getRacingCarInput(); + RacingCarRequest getRacingCarInput(); + + void showResultHeader(); + + void showRacingCarResult(RacingCarResult result); } From 45a61dba88d336d57fa35433d3d0414efea3b2bf Mon Sep 17 00:00:00 2001 From: HJ-Rich Date: Mon, 11 Mar 2024 03:21:53 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20RacingCar=20=EA=B2=8C=EC=9E=84=20?= =?UTF-8?q?=EC=95=A0=ED=94=8C=EB=A6=AC=EC=BC=80=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- src/main/java/racingcar/Main.java | 17 ++++++ .../racingcar/application/RacingCarGame.java | 55 +++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/main/java/racingcar/Main.java create mode 100644 src/main/java/racingcar/application/RacingCarGame.java diff --git a/README.md b/README.md index 9e97789b4bf..b4cf6540b81 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,6 @@ public static int splitAndSum(final String input) { - [x] `자동차 대수는 몇 대 인가요?` -> 양의 정수 하나를 입력 받음 - [x] `시도할 회수는 몇 회 인가요?` -> 양의 정수 하나를 입력 받음 -- [ ] 개행 및 `실행 결과` 출력 +- [x] 개행 및 `실행 결과` 출력 - [x] 0-9사이 Random 값이 4이상인 경우 전진하는 메서드 구현 -- [ ] 입력 받은 시도 횟수 만큼 개행으로 구분하며 각 자동차의 경과를 출력 +- [x] 입력 받은 시도 횟수 만큼 개행으로 구분하며 각 자동차의 경과를 출력 diff --git a/src/main/java/racingcar/Main.java b/src/main/java/racingcar/Main.java new file mode 100644 index 00000000000..8018cd4d013 --- /dev/null +++ b/src/main/java/racingcar/Main.java @@ -0,0 +1,17 @@ +package racingcar; + +import racingcar.application.RacingCarGame; +import racingcar.domain.CarMoveGenerator; +import racingcar.domain.SixtyPercentAdvanceGenerator; +import racingcar.presentation.ConsoleRacingCarClient; +import racingcar.presentation.RacingCarClient; + +public class Main { + public static void main(String[] args) { + final RacingCarClient racingCarClient = new ConsoleRacingCarClient(); + final CarMoveGenerator randomCarMoveGenerator = new SixtyPercentAdvanceGenerator(); + + final RacingCarGame racingCarGame = new RacingCarGame(racingCarClient, randomCarMoveGenerator); + racingCarGame.play(); + } +} diff --git a/src/main/java/racingcar/application/RacingCarGame.java b/src/main/java/racingcar/application/RacingCarGame.java new file mode 100644 index 00000000000..8490024c12a --- /dev/null +++ b/src/main/java/racingcar/application/RacingCarGame.java @@ -0,0 +1,55 @@ +package racingcar.application; + +import racingcar.domain.Car; +import racingcar.domain.CarMoveGenerator; +import racingcar.domain.Cars; +import racingcar.domain.dto.RacingCarRequest; +import racingcar.domain.dto.RacingCarResult; +import racingcar.domain.vo.NumberOfCars; +import racingcar.domain.vo.NumberOfTrials; +import racingcar.presentation.RacingCarClient; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class RacingCarGame { + private final RacingCarClient racingCarClient; + private final CarMoveGenerator carMoveGenerator; + + public RacingCarGame(final RacingCarClient racingCarClient, final CarMoveGenerator carMoveGenerator) { + this.racingCarClient = racingCarClient; + this.carMoveGenerator = carMoveGenerator; + } + + public void play() { + final RacingCarRequest racingCarInput = racingCarClient.getRacingCarInput(); + final Cars cars = createCars(racingCarInput); + + final NumberOfTrials numberOfTrials = racingCarInput.getNumberOfTrials(); + + racingCarClient.showResultHeader(); + playRounds(numberOfTrials, cars); + } + + private Cars createCars(final RacingCarRequest racingCarRequest) { + final NumberOfCars numberOfCars = racingCarRequest.getNumberOfCars(); + final List car = createCar(numberOfCars.getValue()); + + return new Cars(car); + } + + private List createCar(final int value) { + return IntStream.rangeClosed(1, value) + .mapToObj(i -> new Car()) + .collect(Collectors.toList()); + } + + private void playRounds(final NumberOfTrials numberOfTrials, final Cars cars) { + for (int i = 0; i < numberOfTrials.getValue(); i++) { + cars.tryAdvance(this.carMoveGenerator); + final RacingCarResult racingCarResult = new RacingCarResult(cars.getCars()); + racingCarClient.showRacingCarResult(racingCarResult); + } + } +}