From 85bdd8094b09528ace3e550c8ebcfadf9edd0ee2 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Mon, 7 Apr 2025 15:54:43 +0900 Subject: [PATCH 01/11] =?UTF-8?q?[feat]=20=EB=8F=84=EB=A9=94=EC=9D=B8=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/Car.java | 29 ++++++++++ src/main/java/racingcar/domain/RaceTrack.java | 54 +++++++++++++++++++ .../java/racingcar/domain/RacingGame.java | 40 ++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 src/main/java/racingcar/domain/Car.java create mode 100644 src/main/java/racingcar/domain/RaceTrack.java create mode 100644 src/main/java/racingcar/domain/RacingGame.java diff --git a/src/main/java/racingcar/domain/Car.java b/src/main/java/racingcar/domain/Car.java new file mode 100644 index 0000000..1630313 --- /dev/null +++ b/src/main/java/racingcar/domain/Car.java @@ -0,0 +1,29 @@ +package racingcar.domain; + +import racingcar.dto.CarStatus; + +public class Car { + + private final String name; + private int position; + + public Car(String name) { + this.name = name; + } + + public void move() { + position++; + } + + public String getName() { + return name; + } + + public int getPosition() { + return position; + } + + public CarStatus getCarStatus() { + return new CarStatus(name, position); + } +} diff --git a/src/main/java/racingcar/domain/RaceTrack.java b/src/main/java/racingcar/domain/RaceTrack.java new file mode 100644 index 0000000..4c89798 --- /dev/null +++ b/src/main/java/racingcar/domain/RaceTrack.java @@ -0,0 +1,54 @@ +package racingcar.domain; + +import camp.nextstep.edu.missionutils.Randoms; +import racingcar.dto.CarStatus; +import racingcar.dto.GameResult; + +import java.util.ArrayList; +import java.util.List; + +public class RaceTrack { + + private final List cars = new ArrayList<>(); + + public RaceTrack(List carNames) { + for(String carName : carNames) { + cars.add(new Car(carName)); + } + } + + public void moveAllCars() { + for(Car car : cars) { + if(shouldMove()) { + car.move(); + } + } + } + + public GameResult determineWinner() { + int maxPosition = findMaxPosition(); + List winnerNames = cars.stream() + .filter(car -> car.getPosition() == maxPosition) + .map(Car::getName) + .toList(); + + return new GameResult(winnerNames); + } + + private boolean shouldMove() { + return Randoms.pickNumberInRange(0, 9) >= 4; + } + + private int findMaxPosition() { + return cars.stream() + .mapToInt(Car::getPosition) + .max() + .orElse(0); + } + + public List getCarStatuses() { + return cars.stream() + .map(Car::getCarStatus) + .toList(); + } +} diff --git a/src/main/java/racingcar/domain/RacingGame.java b/src/main/java/racingcar/domain/RacingGame.java new file mode 100644 index 0000000..b032d9b --- /dev/null +++ b/src/main/java/racingcar/domain/RacingGame.java @@ -0,0 +1,40 @@ +package racingcar.domain; + + +import racingcar.dto.GameResult; +import racingcar.common.config.RacingGameConfig; +import racingcar.dto.RoundResult; + +public class RacingGame { + + private final RaceTrack raceTrack; + private final int totalRound; + private int currentRound; + + public RacingGame(RacingGameConfig config) { + this.raceTrack = new RaceTrack(config.getCarNames()); + this.totalRound = config.getTotalRound(); + this.currentRound = 0; + } + + public RoundResult executeRound() { + validateCurrentRound(); + raceTrack.moveAllCars(); + currentRound++; + return new RoundResult(raceTrack.getCarStatuses()); + } + + public GameResult determineWinner() { + return raceTrack.determineWinner(); + } + + private void validateCurrentRound() { + if(currentRound >= totalRound) { + throw new IllegalStateException("모든 라운드가 진행되었습니다."); + } + } + + public int getTotalRound() { + return totalRound; + } +} From 67a7b98a6d6b952964e459b7e456c559f20dd71a Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Mon, 7 Apr 2025 15:56:12 +0900 Subject: [PATCH 02/11] =?UTF-8?q?[feat]=20AppConfig=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/Application.java | 7 +++- .../racingcar/common/config/AppConfig.java | 40 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/main/java/racingcar/common/config/AppConfig.java diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e..9ede99f 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,12 @@ package racingcar; +import racingcar.common.config.AppConfig; +import racingcar.controller.RacingGameController; + public class Application { + public static void main(String[] args) { - // TODO: 프로그램 구현 + RacingGameController controller = AppConfig.getInstance().getRacingGameController(); + controller.run(); } } diff --git a/src/main/java/racingcar/common/config/AppConfig.java b/src/main/java/racingcar/common/config/AppConfig.java new file mode 100644 index 0000000..589f73b --- /dev/null +++ b/src/main/java/racingcar/common/config/AppConfig.java @@ -0,0 +1,40 @@ +package racingcar.common.config; + +import racingcar.common.parser.InputParser; +import racingcar.controller.RacingGameController; +import racingcar.view.InputView; +import racingcar.view.OutputView; + +public final class AppConfig { + + private static final AppConfig INSTANCE = new AppConfig(); + private final RacingGameController racingGameController; + + private AppConfig() { + this.racingGameController = new RacingGameController( + inputView(), + outputView(), + parser() + ); + } + + public static AppConfig getInstance() { + return INSTANCE; + } + + public RacingGameController getRacingGameController() { + return racingGameController; + } + + private InputView inputView() { + return new InputView(); + } + + private OutputView outputView() { + return new OutputView(); + } + + private InputParser parser() { + return new InputParser(); + } +} From c85532e4cae07def54211ac9ca0d131538b33d1b Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Mon, 7 Apr 2025 15:56:57 +0900 Subject: [PATCH 03/11] =?UTF-8?q?[feat]=20InputView,=20OutputView=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/view/InputView.java | 16 +++++++++ src/main/java/racingcar/view/OutputView.java | 36 ++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/main/java/racingcar/view/InputView.java create mode 100644 src/main/java/racingcar/view/OutputView.java diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 0000000..0c4ef7b --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,16 @@ +package racingcar.view; + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + + public String printCarNames() { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + return Console.readLine(); + } + + public String printRaceCount() { + System.out.println("시도할 회수는 몇회인가요?"); + return Console.readLine(); + } +} diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 0000000..c324774 --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -0,0 +1,36 @@ +package racingcar.view; + +import racingcar.dto.CarStatus; +import racingcar.dto.GameResult; +import racingcar.dto.RoundResult; + +import java.util.List; + +public class OutputView { + + public void printExecuteResult() { + System.out.println("실행 결과"); + } + + public void printCurrentRoundResult(RoundResult result) { + List carStatuses = result.getCarStatuses(); + + for (CarStatus carStatus : carStatuses) { + String name = carStatus.name(); + int position = carStatus.position(); + System.out.println(name + " : " + getPosition(position)); + } + System.out.println(); + } + + + public void printWinner(GameResult result) { + System.out.println("최종 우승자"); + List carStatuses = result.getWinnerNames(); + System.out.println(String.join(", ", carStatuses)); + } + + private String getPosition(int position) { + return "-".repeat(Math.max(0, position)); + } +} From 977c755be706207f409886a4fe0bc25a5aa5ca30 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Mon, 7 Apr 2025 15:57:39 +0900 Subject: [PATCH 04/11] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=9E=84=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=EB=A1=9C=EC=A7=81=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../racingcar/application/GameExecutor.java | 29 ++++++++++++++++ .../java/racingcar/application/GameSetUp.java | 34 +++++++++++++++++++ .../application/RacingGameFacade.java | 25 ++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 src/main/java/racingcar/application/GameExecutor.java create mode 100644 src/main/java/racingcar/application/GameSetUp.java create mode 100644 src/main/java/racingcar/application/RacingGameFacade.java diff --git a/src/main/java/racingcar/application/GameExecutor.java b/src/main/java/racingcar/application/GameExecutor.java new file mode 100644 index 0000000..1a78c59 --- /dev/null +++ b/src/main/java/racingcar/application/GameExecutor.java @@ -0,0 +1,29 @@ +package racingcar.application; + +import racingcar.domain.RacingGame; +import racingcar.dto.GameResult; +import racingcar.dto.RoundResult; +import racingcar.view.OutputView; + +public class GameExecutor { + + private final OutputView outputView; + + public GameExecutor(OutputView outputView) { + this.outputView = outputView; + } + + public void executeGame(RacingGame racingGame) { + outputView.printExecuteResult(); + + for(int i = 0; i < racingGame.getTotalRound(); i++) { + RoundResult result = racingGame.executeRound(); + outputView.printCurrentRoundResult(result); + } + } + + public void announceWinner(RacingGame racingGame) { + GameResult result = racingGame.determineWinner(); + outputView.printWinner(result); + } +} diff --git a/src/main/java/racingcar/application/GameSetUp.java b/src/main/java/racingcar/application/GameSetUp.java new file mode 100644 index 0000000..5519301 --- /dev/null +++ b/src/main/java/racingcar/application/GameSetUp.java @@ -0,0 +1,34 @@ +package racingcar.application; + +import racingcar.common.parser.InputParser; +import racingcar.common.config.RacingGameConfig; +import racingcar.view.InputView; + +import java.util.List; + +public class GameSetUp { + + private final InputView inputView; + private final InputParser inputParser; + + public GameSetUp(InputView inputView, InputParser inputParser) { + this.inputView = inputView; + this.inputParser = inputParser; + } + + public RacingGameConfig createRacingGameConfig() { + List carNames = setUpCarNames(); + int totalRound = setUpTotalRound(); + return new RacingGameConfig(carNames, totalRound); + } + + private List setUpCarNames() { + String carNamesInput = inputView.printCarNames(); + return inputParser.parseCarNames(carNamesInput); + } + + private int setUpTotalRound() { + String totalRoundInput = inputView.printRaceCount(); + return inputParser.parseTotalRound(totalRoundInput); + } +} diff --git a/src/main/java/racingcar/application/RacingGameFacade.java b/src/main/java/racingcar/application/RacingGameFacade.java new file mode 100644 index 0000000..e9dc030 --- /dev/null +++ b/src/main/java/racingcar/application/RacingGameFacade.java @@ -0,0 +1,25 @@ +package racingcar.application; + +import racingcar.common.config.RacingGameConfig; +import racingcar.domain.RacingGame; + +public class RacingGameFacade { + + private final GameSetUp gameSetUp; + private final GameExecutor gameExecutor; + + public RacingGameFacade( + GameSetUp gameSetUp, + GameExecutor gameExecutor + ) { + this.gameSetUp = gameSetUp; + this.gameExecutor = gameExecutor; + } + + public void playGame() { + RacingGameConfig config = gameSetUp.createRacingGameConfig(); + RacingGame racingGame = new RacingGame(config); + gameExecutor.executeGame(racingGame); + gameExecutor.announceWinner(racingGame); + } +} From cd01a252ff9065a953ad5e23d7a5dbcf79650271 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Mon, 7 Apr 2025 15:57:53 +0900 Subject: [PATCH 05/11] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=9E=84=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EB=B0=8F=20=EB=9D=BC=EC=9A=B4=EB=93=9C=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EA=B4=80=EB=A0=A8=20DTO=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/dto/CarStatus.java | 7 +++++++ src/main/java/racingcar/dto/GameResult.java | 17 +++++++++++++++++ src/main/java/racingcar/dto/RoundResult.java | 17 +++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 src/main/java/racingcar/dto/CarStatus.java create mode 100644 src/main/java/racingcar/dto/GameResult.java create mode 100644 src/main/java/racingcar/dto/RoundResult.java diff --git a/src/main/java/racingcar/dto/CarStatus.java b/src/main/java/racingcar/dto/CarStatus.java new file mode 100644 index 0000000..327c447 --- /dev/null +++ b/src/main/java/racingcar/dto/CarStatus.java @@ -0,0 +1,7 @@ +package racingcar.dto; + +public record CarStatus( + String name, + int position + ) { +} diff --git a/src/main/java/racingcar/dto/GameResult.java b/src/main/java/racingcar/dto/GameResult.java new file mode 100644 index 0000000..0c112ee --- /dev/null +++ b/src/main/java/racingcar/dto/GameResult.java @@ -0,0 +1,17 @@ +package racingcar.dto; + +import java.util.ArrayList; +import java.util.List; + +public class GameResult { + + private final List winnerNames = new ArrayList<>(); + + public GameResult(List winnerNames) { + this.winnerNames.addAll(winnerNames); + } + + public List getWinnerNames() { + return winnerNames; + } +} diff --git a/src/main/java/racingcar/dto/RoundResult.java b/src/main/java/racingcar/dto/RoundResult.java new file mode 100644 index 0000000..7ae0b8e --- /dev/null +++ b/src/main/java/racingcar/dto/RoundResult.java @@ -0,0 +1,17 @@ +package racingcar.dto; + +import java.util.ArrayList; +import java.util.List; + +public class RoundResult { + + private final List carStatuses = new ArrayList<>(); + + public RoundResult(List carStatuses) { + this.carStatuses.addAll(carStatuses); + } + + public List getCarStatuses() { + return carStatuses; + } +} From e465266fc2828a4baeed931d9014e51464f185db Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Mon, 7 Apr 2025 15:58:10 +0900 Subject: [PATCH 06/11] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=9E=84=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=9E=85=EB=A0=A5=20=ED=8C=8C=EC=8B=B1?= =?UTF-8?q?=20=EC=9C=A0=ED=8B=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/config/RacingGameConfig.java | 37 +++++++++++++++++++ .../racingcar/common/parser/InputParser.java | 15 ++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/main/java/racingcar/common/config/RacingGameConfig.java create mode 100644 src/main/java/racingcar/common/parser/InputParser.java diff --git a/src/main/java/racingcar/common/config/RacingGameConfig.java b/src/main/java/racingcar/common/config/RacingGameConfig.java new file mode 100644 index 0000000..6a334fd --- /dev/null +++ b/src/main/java/racingcar/common/config/RacingGameConfig.java @@ -0,0 +1,37 @@ +package racingcar.common.config; + +import java.util.ArrayList; +import java.util.List; + +public class RacingGameConfig { + + private final List carNames = new ArrayList<>(); + private final int totalRound; + + public RacingGameConfig(List carNames, int totalRound) { + validateCarNames(carNames); + validateTotalRound(totalRound); + this.carNames.addAll(carNames); + this.totalRound = totalRound; + } + + public List getCarNames() { + return carNames; + } + + public int getTotalRound() { + return totalRound; + } + + private void validateCarNames(List carNames) { + if(carNames == null || carNames.isEmpty()) { + throw new IllegalArgumentException("차 이름은 최소 1개 이상이어야 합니다."); + } + } + + private void validateTotalRound(int totalRound) { + if(totalRound <= 0) { + throw new IllegalArgumentException("시도 횟수는 최소 1회 이상이어야 합니다"); + } + } +} diff --git a/src/main/java/racingcar/common/parser/InputParser.java b/src/main/java/racingcar/common/parser/InputParser.java new file mode 100644 index 0000000..9b452c4 --- /dev/null +++ b/src/main/java/racingcar/common/parser/InputParser.java @@ -0,0 +1,15 @@ +package racingcar.common.parser; + +import java.util.Arrays; +import java.util.List; + +public class InputParser { + + public List parseCarNames(String input) { + return Arrays.asList(input.split(",")); + } + + public int parseTotalRound(String input) { + return Integer.parseInt(input); + } +} From bab51aa934690dcda5f04d42029de4fbe6b0b76a Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Mon, 7 Apr 2025 15:58:17 +0900 Subject: [PATCH 07/11] =?UTF-8?q?[feat]=20RacingGameController=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RacingGameController.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/racingcar/controller/RacingGameController.java diff --git a/src/main/java/racingcar/controller/RacingGameController.java b/src/main/java/racingcar/controller/RacingGameController.java new file mode 100644 index 0000000..07493f6 --- /dev/null +++ b/src/main/java/racingcar/controller/RacingGameController.java @@ -0,0 +1,28 @@ +package racingcar.controller; + + +import racingcar.application.GameExecutor; +import racingcar.application.GameSetUp; +import racingcar.application.RacingGameFacade; +import racingcar.common.parser.InputParser; +import racingcar.view.InputView; +import racingcar.view.OutputView; + +public class RacingGameController { + + private final RacingGameFacade racingGameFacade; + + public RacingGameController( + InputView inputView, + OutputView outputView, + InputParser inputParser + ) { + GameSetUp gameSetUp = new GameSetUp(inputView, inputParser); + GameExecutor gameExecutor = new GameExecutor(outputView); + racingGameFacade = new RacingGameFacade(gameSetUp, gameExecutor); + } + + public void run() { + racingGameFacade.playGame(); + } +} From e8cfd7372ade4b213b07ffe51d005a7d327159b4 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Mon, 7 Apr 2025 16:25:03 +0900 Subject: [PATCH 08/11] =?UTF-8?q?[feat]=20=EA=B3=B5=ED=86=B5=20=EC=83=81?= =?UTF-8?q?=EC=88=98=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80:?= =?UTF-8?q?=20ErrorMessages,=20GameConstants,=20UIMessages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/config/RacingGameConfig.java | 6 ++++-- .../common/constant/ErrorMessages.java | 16 ++++++++++++++++ .../common/constant/GameConstants.java | 14 ++++++++++++++ .../racingcar/common/constant/UIConstants.java | 17 +++++++++++++++++ .../racingcar/common/parser/InputParser.java | 18 ++++++++++++++++-- src/main/java/racingcar/domain/RaceTrack.java | 4 +++- src/main/java/racingcar/domain/RacingGame.java | 3 ++- src/main/java/racingcar/view/InputView.java | 5 +++-- src/main/java/racingcar/view/OutputView.java | 11 ++++++----- 9 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 src/main/java/racingcar/common/constant/ErrorMessages.java create mode 100644 src/main/java/racingcar/common/constant/GameConstants.java create mode 100644 src/main/java/racingcar/common/constant/UIConstants.java diff --git a/src/main/java/racingcar/common/config/RacingGameConfig.java b/src/main/java/racingcar/common/config/RacingGameConfig.java index 6a334fd..8d6ba0c 100644 --- a/src/main/java/racingcar/common/config/RacingGameConfig.java +++ b/src/main/java/racingcar/common/config/RacingGameConfig.java @@ -1,5 +1,7 @@ package racingcar.common.config; +import racingcar.common.constant.ErrorMessages; + import java.util.ArrayList; import java.util.List; @@ -25,13 +27,13 @@ public int getTotalRound() { private void validateCarNames(List carNames) { if(carNames == null || carNames.isEmpty()) { - throw new IllegalArgumentException("차 이름은 최소 1개 이상이어야 합니다."); + throw new IllegalArgumentException(ErrorMessages.EMPTY_CAR_NAMES); } } private void validateTotalRound(int totalRound) { if(totalRound <= 0) { - throw new IllegalArgumentException("시도 횟수는 최소 1회 이상이어야 합니다"); + throw new IllegalArgumentException(ErrorMessages.INVALID_TOTAL_ROUND); } } } diff --git a/src/main/java/racingcar/common/constant/ErrorMessages.java b/src/main/java/racingcar/common/constant/ErrorMessages.java new file mode 100644 index 0000000..cf592f7 --- /dev/null +++ b/src/main/java/racingcar/common/constant/ErrorMessages.java @@ -0,0 +1,16 @@ +package racingcar.common.constant; + +public class ErrorMessages { + + public static final String INVALID_CAR_NAMES = "자동차 이름은 쉼표(,)로 구분된 문자열이어야 합니다."; + public static final String INVALID_ROUND_INPUT = "시도 횟수는 숫자로 입력해야 합니다."; + + public static final String EMPTY_CAR_NAMES = "차 이름은 최소 1개 이상이어야 합니다."; + public static final String INVALID_TOTAL_ROUND = "시도 횟수는 최소 1회 이상이어야 합니다"; + + public static final String ALL_ROUNDS_COMPLETED = "모든 라운드가 진행되었습니다."; + + private ErrorMessages() { + + } +} diff --git a/src/main/java/racingcar/common/constant/GameConstants.java b/src/main/java/racingcar/common/constant/GameConstants.java new file mode 100644 index 0000000..088bb41 --- /dev/null +++ b/src/main/java/racingcar/common/constant/GameConstants.java @@ -0,0 +1,14 @@ +package racingcar.common.constant; + +public class GameConstants { + + public static final int MIN_MOVE_THRESHOLD = 4; + public static final int RANDOM_MIN = 0; + public static final int RANDOM_MAX = 9; + + public static final String CAR_NAME_DELIMITER = ","; + + private GameConstants() { + + } +} diff --git a/src/main/java/racingcar/common/constant/UIConstants.java b/src/main/java/racingcar/common/constant/UIConstants.java new file mode 100644 index 0000000..75c8066 --- /dev/null +++ b/src/main/java/racingcar/common/constant/UIConstants.java @@ -0,0 +1,17 @@ +package racingcar.common.constant; + +public class UIConstants { + + public static final String CAR_NAMES_PROMPT = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"; + public static final String RACE_COUNT_PROMPT = "시도할 회수는 몇회인가요?"; + public static final String EXECUTE_RESULT = "실행 결과"; + public static final String WINNER_ANNOUNCEMENT = "최종 우승자"; + + public static final String POSITION_INDICATOR = "-"; + public static final String WINNER_DELIMITER = ", "; + public static final String DELIMITER_COLON = " : "; + + private UIConstants() { + + } +} diff --git a/src/main/java/racingcar/common/parser/InputParser.java b/src/main/java/racingcar/common/parser/InputParser.java index 9b452c4..3841aa4 100644 --- a/src/main/java/racingcar/common/parser/InputParser.java +++ b/src/main/java/racingcar/common/parser/InputParser.java @@ -1,15 +1,29 @@ package racingcar.common.parser; +import racingcar.common.constant.GameConstants; + import java.util.Arrays; import java.util.List; +import static racingcar.common.constant.ErrorMessages.*; + public class InputParser { public List parseCarNames(String input) { - return Arrays.asList(input.split(",")); + try { + return Arrays.stream(input.split(GameConstants.CAR_NAME_DELIMITER)) + .toList(); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(INVALID_CAR_NAMES); + } } public int parseTotalRound(String input) { - return Integer.parseInt(input); + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new NumberFormatException(INVALID_ROUND_INPUT); + } } } + diff --git a/src/main/java/racingcar/domain/RaceTrack.java b/src/main/java/racingcar/domain/RaceTrack.java index 4c89798..0974ce5 100644 --- a/src/main/java/racingcar/domain/RaceTrack.java +++ b/src/main/java/racingcar/domain/RaceTrack.java @@ -7,6 +7,8 @@ import java.util.ArrayList; import java.util.List; +import static racingcar.common.constant.GameConstants.*; + public class RaceTrack { private final List cars = new ArrayList<>(); @@ -36,7 +38,7 @@ public GameResult determineWinner() { } private boolean shouldMove() { - return Randoms.pickNumberInRange(0, 9) >= 4; + return Randoms.pickNumberInRange(RANDOM_MIN, RANDOM_MAX) >= MIN_MOVE_THRESHOLD; } private int findMaxPosition() { diff --git a/src/main/java/racingcar/domain/RacingGame.java b/src/main/java/racingcar/domain/RacingGame.java index b032d9b..0eaa860 100644 --- a/src/main/java/racingcar/domain/RacingGame.java +++ b/src/main/java/racingcar/domain/RacingGame.java @@ -1,6 +1,7 @@ package racingcar.domain; +import racingcar.common.constant.ErrorMessages; import racingcar.dto.GameResult; import racingcar.common.config.RacingGameConfig; import racingcar.dto.RoundResult; @@ -30,7 +31,7 @@ public GameResult determineWinner() { private void validateCurrentRound() { if(currentRound >= totalRound) { - throw new IllegalStateException("모든 라운드가 진행되었습니다."); + throw new IllegalStateException(ErrorMessages.ALL_ROUNDS_COMPLETED); } } diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java index 0c4ef7b..2376bef 100644 --- a/src/main/java/racingcar/view/InputView.java +++ b/src/main/java/racingcar/view/InputView.java @@ -1,16 +1,17 @@ package racingcar.view; import camp.nextstep.edu.missionutils.Console; +import racingcar.common.constant.UIConstants; public class InputView { public String printCarNames() { - System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + System.out.println(UIConstants.CAR_NAMES_PROMPT); return Console.readLine(); } public String printRaceCount() { - System.out.println("시도할 회수는 몇회인가요?"); + System.out.println(UIConstants.RACE_COUNT_PROMPT); return Console.readLine(); } } diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java index c324774..6611433 100644 --- a/src/main/java/racingcar/view/OutputView.java +++ b/src/main/java/racingcar/view/OutputView.java @@ -1,5 +1,6 @@ package racingcar.view; +import racingcar.common.constant.UIConstants; import racingcar.dto.CarStatus; import racingcar.dto.GameResult; import racingcar.dto.RoundResult; @@ -9,7 +10,7 @@ public class OutputView { public void printExecuteResult() { - System.out.println("실행 결과"); + System.out.println(UIConstants.EXECUTE_RESULT); } public void printCurrentRoundResult(RoundResult result) { @@ -18,19 +19,19 @@ public void printCurrentRoundResult(RoundResult result) { for (CarStatus carStatus : carStatuses) { String name = carStatus.name(); int position = carStatus.position(); - System.out.println(name + " : " + getPosition(position)); + System.out.println(name + UIConstants.DELIMITER_COLON + getPosition(position)); } System.out.println(); } public void printWinner(GameResult result) { - System.out.println("최종 우승자"); + System.out.println(UIConstants.WINNER_ANNOUNCEMENT); List carStatuses = result.getWinnerNames(); - System.out.println(String.join(", ", carStatuses)); + System.out.println(String.join(UIConstants.WINNER_DELIMITER, carStatuses)); } private String getPosition(int position) { - return "-".repeat(Math.max(0, position)); + return UIConstants.POSITION_INDICATOR.repeat(Math.max(0, position)); } } From 7486d65a8bc55279b354d666ed41e856df0252d9 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Mon, 7 Apr 2025 16:34:55 +0900 Subject: [PATCH 09/11] =?UTF-8?q?[refactor]=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=ED=98=95=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/view/OutputView.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java index 6611433..edc3453 100644 --- a/src/main/java/racingcar/view/OutputView.java +++ b/src/main/java/racingcar/view/OutputView.java @@ -26,9 +26,9 @@ public void printCurrentRoundResult(RoundResult result) { public void printWinner(GameResult result) { - System.out.println(UIConstants.WINNER_ANNOUNCEMENT); - List carStatuses = result.getWinnerNames(); - System.out.println(String.join(UIConstants.WINNER_DELIMITER, carStatuses)); + List winners = result.getWinnerNames(); + String winnerNames = String.join(UIConstants.WINNER_DELIMITER, winners); + System.out.println(UIConstants.WINNER_ANNOUNCEMENT + UIConstants.DELIMITER_COLON + winnerNames); } private String getPosition(int position) { From 3d6d87ae47d3a489d0f9a8b9fca2b1de9606bd1b Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Mon, 7 Apr 2025 16:43:26 +0900 Subject: [PATCH 10/11] =?UTF-8?q?[feat]=20=EC=9E=90=EB=8F=99=EC=B0=A8=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EA=B8=B8=EC=9D=B4=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/racingcar/common/constant/ErrorMessages.java | 3 +++ .../java/racingcar/common/constant/GameConstants.java | 1 + src/main/java/racingcar/domain/Car.java | 10 ++++++++++ 3 files changed, 14 insertions(+) diff --git a/src/main/java/racingcar/common/constant/ErrorMessages.java b/src/main/java/racingcar/common/constant/ErrorMessages.java index cf592f7..01db85e 100644 --- a/src/main/java/racingcar/common/constant/ErrorMessages.java +++ b/src/main/java/racingcar/common/constant/ErrorMessages.java @@ -2,10 +2,13 @@ public class ErrorMessages { + // 입력 에러 public static final String INVALID_CAR_NAMES = "자동차 이름은 쉼표(,)로 구분된 문자열이어야 합니다."; public static final String INVALID_ROUND_INPUT = "시도 횟수는 숫자로 입력해야 합니다."; + // 도메인 에러 public static final String EMPTY_CAR_NAMES = "차 이름은 최소 1개 이상이어야 합니다."; + public static final String INVALID_CAR_NAME_LENGTH = "자동차 이름은 5자 이하여야 합니다."; public static final String INVALID_TOTAL_ROUND = "시도 횟수는 최소 1회 이상이어야 합니다"; public static final String ALL_ROUNDS_COMPLETED = "모든 라운드가 진행되었습니다."; diff --git a/src/main/java/racingcar/common/constant/GameConstants.java b/src/main/java/racingcar/common/constant/GameConstants.java index 088bb41..115f6d9 100644 --- a/src/main/java/racingcar/common/constant/GameConstants.java +++ b/src/main/java/racingcar/common/constant/GameConstants.java @@ -5,6 +5,7 @@ public class GameConstants { public static final int MIN_MOVE_THRESHOLD = 4; public static final int RANDOM_MIN = 0; public static final int RANDOM_MAX = 9; + public static final int MAX_NAME_LENGTH = 5; public static final String CAR_NAME_DELIMITER = ","; diff --git a/src/main/java/racingcar/domain/Car.java b/src/main/java/racingcar/domain/Car.java index 1630313..5222451 100644 --- a/src/main/java/racingcar/domain/Car.java +++ b/src/main/java/racingcar/domain/Car.java @@ -2,12 +2,16 @@ import racingcar.dto.CarStatus; +import static racingcar.common.constant.ErrorMessages.*; +import static racingcar.common.constant.GameConstants.*; + public class Car { private final String name; private int position; public Car(String name) { + validateNameLength(name); this.name = name; } @@ -15,6 +19,12 @@ public void move() { position++; } + private void validateNameLength(String name) { + if (name.length() > MAX_NAME_LENGTH) { + throw new IllegalArgumentException(INVALID_CAR_NAME_LENGTH); + } + } + public String getName() { return name; } From 67958bae2791e10cd033110db4cda5224dbb7b24 Mon Sep 17 00:00:00 2001 From: SeungGyu Date: Mon, 7 Apr 2025 17:01:16 +0900 Subject: [PATCH 11/11] [docs] update README.md --- README.md | 183 ++++++++++++------------------------------------------ 1 file changed, 38 insertions(+), 145 deletions(-) diff --git a/README.md b/README.md index 5c937d3..be7d9fd 100644 --- a/README.md +++ b/README.md @@ -1,163 +1,56 @@ -# 미션 - 자동차 경주 -## 🔍 진행 방식 +## 프로젝트 구조 -- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. -- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. -- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. +### 도메인 (Domain) +레이싱 카 게임의 핵심 비즈니스 로직입니다. -## 📮 미션 제출 방법 +- **Car**: 이름과 위치를 가진 단일 자동차를 표현 +- **RaceTrack**: 자동차 컬렉션과 이동을 관리 +- **RacingGame**: 게임 흐름, 라운드, 최종 결과를 제어 -- 미션 구현을 완료한 후 GitHub을 통해 제출해야 한다. - - GitHub을 활용한 제출 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고해 제출한다. -- GitHub에 미션을 제출한 후 [우아한테크코스 지원](https://apply.techcourse.co.kr) 사이트에 접속하여 프리코스 과제를 제출한다. - - 자세한 방법은 [제출 가이드](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse#제출-가이드) 참고 - - **Pull Request만 보내고 지원 플랫폼에서 과제를 제출하지 않으면 최종 제출하지 않은 것으로 처리되니 주의한다.** +### 애플리케이션 (Application) +게임 흐름을 조율하고 도메인과 프레젠테이션을 연결합니다. -## 🚨 과제 제출 전 체크 리스트 - 0점 방지 +- **GameSetUp**: 초기 게임 설정을 처리 +- **GameExecutor**: 게임 실행과 결과 발표를 관리 +- **RacingGameFacade**: 게임 운영을 위한 단순화된 메서드 제공 -- 기능 구현을 모두 정상적으로 했더라도 **요구 사항에 명시된 출력값 형식을 지키지 않을 경우 0점으로 처리**한다. -- 기능 구현을 완료한 뒤 아래 가이드에 따라 테스트를 실행했을 때 모든 테스트가 성공하는지 확인한다. -- **테스트가 실패할 경우 0점으로 처리**되므로, 반드시 확인 후 제출한다. +### 컨트롤러 (Controller) +애플리케이션 계층과 뷰 간의 상호작용을 조정합니다. -### 테스트 실행 가이드 +- **RacingGameController**: 레이싱 게임 실행의 진입점 -- 터미널에서 `java -version`을 실행하여 Java 버전이 17인지 확인한다. - Eclipse 또는 IntelliJ IDEA와 같은 IDE에서 Java 17로 실행되는지 확인한다. -- 터미널에서 Mac 또는 Linux 사용자의 경우 `./gradlew clean test` 명령을 실행하고, - Windows 사용자의 경우 `gradlew.bat clean test` 또는 `./gradlew.bat clean test` 명령을 실행할 때 모든 테스트가 아래와 같이 통과하는지 확인한다. +### 뷰 (View) +사용자 인터페이스 관련 기능을 처리합니다. -``` -BUILD SUCCESSFUL in 0s -``` +- **InputView**: 자동차 이름과 레이스 횟수에 대한 사용자 입력 관리 +- **OutputView**: 레이스 결과와 우승자 표시 ---- +### DTO (Data Transfer Objects) +계층 간 안전하게 데이터를 전달하기 위한 객체입니다. -## 🚀 기능 요구 사항 +- **CarStatus**: 자동차의 이름과 위치에 관한 정보 +- **RoundResult**: 단일 라운드 레이싱 결과 +- **GameResult**: 우승자 이름을 포함한 최종 게임 결과 -초간단 자동차 경주 게임을 구현한다. +### 공통 (Common) +유틸리티 클래스와 상수입니다. -- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. -- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. -- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. -- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. -- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. -- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. -- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다. -- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다. +- **AppConfig**: 애플리케이션 설정 및 의존성 주입 +- **RacingGameConfig**: 게임 관련 설정 +- **InputParser**: 사용자 입력의 파싱 및 유효성 검사 +- **Constants**: 오류 메시지, 게임 규칙, UI 텍스트를 위한 다양한 상수 -### 입출력 요구 사항 +## 설계 결정 -#### 입력 +### 싱글톤 패턴 +- AppConfig는 애플리케이션 전체 의존성 관리를 위해 싱글톤 패턴 사용 -- 경주 할 자동차 이름(이름은 쉼표(,) 기준으로 구분) +### 퍼사드 패턴 +- 컨트롤러가 내부 복잡도를 몰라도 되도록 단순화된 API를 제공한다는 점에서 Facade 패턴 -``` -pobi,woni,jun -``` +### 전략 패턴 +- 이동 로직은 RaceTrack에 캡슐화되어 다양한 이동 전략을 허용함 -- 시도할 회수 - -``` -5 -``` - -#### 출력 - -- 각 차수별 실행 결과 - -``` -pobi : -- -woni : ---- -jun : --- -``` - -- 단독 우승자 안내 문구 - -``` -최종 우승자 : pobi -``` - -- 공동 우승자 안내 문구 - -``` -최종 우승자 : pobi, jun -``` - -#### 실행 결과 예시 - -``` -경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분) -pobi,woni,jun -시도할 회수는 몇회인가요? -5 - -실행 결과 -pobi : - -woni : -jun : - - -pobi : -- -woni : - -jun : -- - -pobi : --- -woni : -- -jun : --- - -pobi : ---- -woni : --- -jun : ---- - -pobi : ----- -woni : ---- -jun : ----- - -최종 우승자 : pobi, jun -``` - ---- - -## 🎯 프로그래밍 요구 사항 - -- JDK 17 버전에서 실행 가능해야 한다. **JDK 17에서 정상적으로 동작하지 않을 경우 0점 처리한다.** -- 프로그램 실행의 시작점은 `Application`의 `main()`이다. -- `build.gradle` 파일을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다. -- [Java 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java) 가이드를 준수하며 프로그래밍한다. -- 프로그램 종료 시 `System.exit()`를 호출하지 않는다. -- 프로그램 구현이 완료되면 `ApplicationTest`의 모든 테스트가 성공해야 한다. **테스트가 실패할 경우 0점 처리한다.** -- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다. - -### 추가된 요구 사항 - -- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. - - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. -- 3항 연산자를 쓰지 않는다. -- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. -- JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다. - - 테스트 도구 사용법이 익숙하지 않다면 `test/java/study`를 참고하여 학습한 후 테스트를 구현한다. - -### 라이브러리 - -- JDK에서 제공하는 Random 및 Scanner API 대신 `camp.nextstep.edu.missionutils`에서 제공하는 `Randoms` 및 `Console` API를 사용하여 구현해야 한다. - - Random 값 추출은 `camp.nextstep.edu.missionutils.Randoms`의 `pickNumberInRange()`를 활용한다. - - 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다. - -#### 사용 예시 - -- 0에서 9까지의 정수 중 한 개의 정수 반환 - -```java -Randoms.pickNumberInRange(0,9); -``` - ---- - -## ✏️ 과제 진행 요구 사항 - -- 미션은 [java-racingcar-6](https://github.com/woowacourse-precourse/java-racingcar-6) 저장소를 Fork & Clone해 시작한다. -- **기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리**해 추가한다. -- **Git의 커밋 단위는 앞 단계에서 `docs/README.md`에 정리한 기능 목록 단위**로 추가한다. - - [커밋 메시지 컨벤션](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 가이드를 참고해 커밋 메시지를 작성한다. -- 과제 진행 및 제출 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고한다. +### 불변성 +- DTO는 스레드 안전성과 예측 가능성을 위해 불변으로 설계됨