diff --git a/.gitignore b/.gitignore index 6c01878..ec8c04f 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ out/ ### VS Code ### .vscode/ + +### CodeRabbit 무시 ### +.coderabbit.yaml diff --git a/build.gradle b/build.gradle index a89989b..15f3352 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ dependencies { java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(17) } } diff --git a/docs/README.md b/docs/README.md index e69de29..040049b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,21 @@ +1. 사용자로부터 자동차의 이름을 받기(쉼표로 구분) +- 이름은 공백 불가, 5이하 -> 그 외는 Exception +- 중복 이름 불가 +- readLine() 사용, Scanner 사용 X +- 쉼표로 구분하기 + +2. 사용자로부터 몇번의 이동을 할 지 입력 받기 +- 양의 정수로 받기 아니면 IllegalArgumentException +- + +3. 앞으로 전진하기 +- 랜덤 숫자는 0~9 사의 값 무작위로 추출 +- Randoms.pickNumber() >= 4 이상이면 distance++ +- 차수별로 실행결과 출력하기 + +4. 우승자 출력하기 +- 각 차량마다 distance 비교해서 distance가 가장 큰 자동차 고르기 +- 여러명일경우 , 로 구분하여 출력하기 + +5. 예외처리 +- 잘못된 입력 시 IllegealArgumentException으로 프로그램 종료시키기 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e644113..c1962a7 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a441313..fae0804 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip -networkTimeout=10000 -validateDistributionUrl=true +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index b740cf1..aeb74cb 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,8 +83,7 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -131,13 +130,10 @@ location of your Java installation." fi else JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." - fi fi # Increase the maximum file descriptors if we can. @@ -145,7 +141,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +149,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +198,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 25da30d..6689b85 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,92 +1,92 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 0000000..2a93316 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,11 @@ +import controller.RacingController; + +public class Application { + public static void main(String[] args) { + try { + new RacingController().run(); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/common/ErrorMessages.java b/src/main/java/common/ErrorMessages.java new file mode 100644 index 0000000..613c692 --- /dev/null +++ b/src/main/java/common/ErrorMessages.java @@ -0,0 +1,16 @@ +package common; + +public class ErrorMessages { + + public static final String NAME_REQUIRED = "[ERROR] 이름 입력은 필수입니다."; + public static final String NAME_LENGTH = "[ERROR] 이름은 5자까지 입력 가능합니다."; + public static final String NAME_EMPTY = "[ERROR] 이름은 공백일 수 없습니다."; + public static final String NAME_DUPLICATION = "[ERROR] 이름은 중복될 수 없습니다."; + public static final String NUMBER_OF_MOVES = "[ERROR] 이동 횟수는 양의 정수여야 합니다."; + + public static final String ASK_CAR_NAMES = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"; + public static final String ASK_ROUND_COUNT = "시도할 횟수는 몇 회인가요?"; + + public static final String PRINT_RESULT = "실행 결과"; + public static final String WINNER_ANNOUNCEMENT = "최종 우승자: "; +} \ No newline at end of file diff --git a/src/main/java/controller/RacingController.java b/src/main/java/controller/RacingController.java new file mode 100644 index 0000000..4e222b1 --- /dev/null +++ b/src/main/java/controller/RacingController.java @@ -0,0 +1,50 @@ +package controller; + +import common.ErrorMessages; +import domain.car.Cars; +import service.RacingGame; +import view.InputView; +import view.OutputView; + +public class RacingController { + + private final InputView inputView = new InputView(); + private final OutputView outputView = new OutputView(); + + public void run() { + + outputView.askCarNames(); + String namesRaw = inputView.readCarNames(); + Cars cars = Cars.of(namesRaw); + + outputView.askRoundCount(); + String roundsRaw = inputView.readRoundCount(); + int rounds = parsePositiveInt(roundsRaw); + + RacingGame game = new RacingGame(cars); + + outputView.printResult(); + for (int i = 0; i < rounds; i++) { + game.playOneRound(); + outputView.printRound(cars); + } + + outputView.printWinners(cars.findWinners()); + } + + private int parsePositiveInt(String raw) { + if (raw == null || raw.trim().isEmpty()) { + throw new IllegalArgumentException(ErrorMessages.NUMBER_OF_MOVES); + } + + try { + int n = Integer.parseInt(raw.trim()); + if (n <= 0) { + throw new IllegalArgumentException(ErrorMessages.NUMBER_OF_MOVES); + } + return n; + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ErrorMessages.NUMBER_OF_MOVES); + } + } +} \ No newline at end of file diff --git a/src/main/java/domain/car/Car.java b/src/main/java/domain/car/Car.java new file mode 100644 index 0000000..9d0c2ca --- /dev/null +++ b/src/main/java/domain/car/Car.java @@ -0,0 +1,48 @@ +package domain.car; + +import static common.ErrorMessages.NAME_EMPTY; +import static common.ErrorMessages.NAME_LENGTH; +import static common.ErrorMessages.NAME_REQUIRED; + +import domain.move.MovePolicy; + +public class Car { + + private int distance; + private final String name; + + private Car(String name) { + this.distance = 0; + this.name = name; + } + + public static Car of(String name) { + if (name == null) { + throw new IllegalArgumentException(NAME_REQUIRED); + } + if (name.isEmpty()) { + throw new IllegalArgumentException(NAME_EMPTY); + } + if (name.contains(" ")) { + throw new IllegalArgumentException(NAME_EMPTY); + } + if (name.length() > 5) { + throw new IllegalArgumentException(NAME_LENGTH); + } + return new Car(name); + } + + public void moveCar(MovePolicy policy) { + if (policy.canMove()) { + distance++; + } + } + + public String getName() { + return name; + } + + public int getDistance() { + return distance; + } +} \ No newline at end of file diff --git a/src/main/java/domain/car/Cars.java b/src/main/java/domain/car/Cars.java new file mode 100644 index 0000000..7559ff2 --- /dev/null +++ b/src/main/java/domain/car/Cars.java @@ -0,0 +1,65 @@ +package domain.car; + +import static common.ErrorMessages.NAME_DUPLICATION; +import static common.ErrorMessages.NAME_REQUIRED; + +import domain.move.MovePolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Cars { + + private final List values; + + private Cars(List values) { + this.values = Collections.unmodifiableList(values); + } + + public static Cars of(String raw) { + if (raw == null || raw.isEmpty()) { + throw new IllegalArgumentException(NAME_REQUIRED); + } + + String[] parts = raw.split(","); + + Set seen = new HashSet<>(); + for (String p : parts) { + if (!seen.add(p)) { + throw new IllegalArgumentException(NAME_DUPLICATION + ": " + p); + } + } + + List list = new ArrayList<>(); + for (String name : parts) { + list.add(Car.of(name)); + } + return new Cars(list); + } + + public void moveAll(MovePolicy policy) { + values.forEach(c -> c.moveCar(policy)); + } + + public List asList() { + return values; + } + + public List findWinners() { + List winners = new ArrayList<>(); + int max = 0; + for (Car car : values) { + int d = car.getDistance(); + if (d > max) { + winners.clear(); + winners.add(car); + max = d; + } else if (d == max) { + winners.add(car); + } + } + return winners; + } +} \ No newline at end of file diff --git a/src/main/java/domain/move/MovePolicy.java b/src/main/java/domain/move/MovePolicy.java new file mode 100644 index 0000000..152380f --- /dev/null +++ b/src/main/java/domain/move/MovePolicy.java @@ -0,0 +1,12 @@ +package domain.move; + +import camp.nextstep.edu.missionutils.Randoms; + +public class MovePolicy { + private static final int DEFAULT_MOVE_NUMBER = 4; + + public boolean canMove() { + int randomNumber = Randoms.pickNumberInRange(0, 9); + return randomNumber >= DEFAULT_MOVE_NUMBER; + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java deleted file mode 100644 index a17a52e..0000000 --- a/src/main/java/racingcar/Application.java +++ /dev/null @@ -1,7 +0,0 @@ -package racingcar; - -public class Application { - public static void main(String[] args) { - // TODO: 프로그램 구현 - } -} diff --git a/src/main/java/service/RacingGame.java b/src/main/java/service/RacingGame.java new file mode 100644 index 0000000..a9cc838 --- /dev/null +++ b/src/main/java/service/RacingGame.java @@ -0,0 +1,17 @@ +package service; + +import domain.car.Cars; +import domain.move.MovePolicy; + +public class RacingGame { + private final Cars cars; + private final MovePolicy policy = new MovePolicy(); + + public RacingGame(Cars cars) { + this.cars = cars; + } + + public void playOneRound() { + cars.moveAll(policy); + } +} \ No newline at end of file diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 0000000..24385b0 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,14 @@ +package view; + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + + public String readCarNames() { + return Console.readLine(); + } + + public String readRoundCount() { + return Console.readLine(); + } +} \ No newline at end of file diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 0000000..f3a4022 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,42 @@ +package view; + +import common.ErrorMessages; +import domain.car.Car; +import domain.car.Cars; +import java.util.List; + +public class OutputView { + + public void askCarNames() { + System.out.println(ErrorMessages.ASK_CAR_NAMES); + } + + public void askRoundCount() { + System.out.println(ErrorMessages.ASK_ROUND_COUNT); + } + + public void printResult() { + System.out.println(); + System.out.println(ErrorMessages.PRINT_RESULT); + } + + public void printRound(Cars cars) { + List list = cars.asList(); + for (Car car : list) { + System.out.println(car.getName() + " : " + "-".repeat(car.getDistance())); + } + System.out.println(); + } + + public void printWinners(List winners) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < winners.size(); i++) { + sb.append(winners.get(i).getName()); + if (i < winners.size() - 1) { + sb.append(", "); + } + } + String names = sb.toString(); + System.out.println(ErrorMessages.WINNER_ANNOUNCEMENT + names); + } +} \ No newline at end of file