diff --git a/docs/README.md b/docs/README.md index e69de29b..7aeb1507 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,30 @@ +## 구현해야 하는 기능 목록 + +### 자동차 +- [x] 클래스 기본 틀 작성(변수, 생성자 등) +- [x] 랜덤 수(0~9) 반환 메소드 +- [x] 자동차를 한 칸 전진하는 메소드 + +### 입력 +- [x] 자동차 이름 & 입력 횟수 입력 +- [x] 입력받은 데이터 전달 + +### 출력 +- [x] 라운드 진행 시마다 경기 진행 상황 출력 +- [x] 경주 종료 후 최종 결과 및 우승자 출력 + +### 예외 처리 +- [x] 자동차 이름 관련 +- [x] 시도 횟수 관련 + + +### 고민 사항 +- 자동차 정보를 입력받는 DTO를 어떻게 설계하는 것이 좋을까? + - 입력 텍스트 그대로(poni,woni,jun)를 DTO에 넣기 + - 이름을 분리하여 List에 넣기 + - 이름을 분리한 것을 바탕으로 Car 객체들로 변환하고, List에 넣기 +- Util 패키지 관련 + - 객체들을 new로 생성해서 사용하는 것이 좋은가? + - 아니면 객체를 별도로 생성하지 않고 객체 내부 메소드들을 모두 static 메소드로 만들어서 쓰는 것이 나은가? +- 시도 횟수를 입력할 때, 숫자가 아닌 다른 형태의 값을 입력했을 경우 + - CheckTryCount 라는 별도의 클래스에서 이를 검증할 방법은 없을까?(현재 코드는 sc.nextInt() 하는 시점에 try-catch를 통해 예외처리 하고 있음) \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e7..278693d5 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,16 @@ package racingcar; +import racingcar.controller.RacingController; +import racingcar.controller.dto.RaceInfoResponse; +import racingcar.util.RaceManager; +import racingcar.view.InputView; +import racingcar.view.OutputView; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + RacingController racingController = new RacingController(new InputView(), new OutputView(), new RaceManager()); + + RaceInfoResponse raceInfo = racingController.getInfosBeforeRaceStart(); + racingController.raceStart(raceInfo); } } diff --git a/src/main/java/racingcar/controller/RacingController.java b/src/main/java/racingcar/controller/RacingController.java new file mode 100644 index 00000000..cff6b5bb --- /dev/null +++ b/src/main/java/racingcar/controller/RacingController.java @@ -0,0 +1,58 @@ +package racingcar.controller; + +import racingcar.controller.dto.RaceInfoRequest; +import racingcar.controller.dto.RaceInfoResponse; +import racingcar.util.TryCountValidator; +import racingcar.util.RaceManager; +import racingcar.util.StringUtils; +import racingcar.model.Car; +import racingcar.view.InputView; +import racingcar.view.OutputView; + +import java.util.List; + +public class RacingController { + private final InputView inputView; + private final OutputView outputView; + private final RaceManager raceManager; + + public RacingController(InputView inputView, OutputView outputView, RaceManager raceManager) { + this.inputView = inputView; + this.outputView = outputView; + this.raceManager = raceManager; + } + + public RaceInfoResponse getInfosBeforeRaceStart() { + outputView.getCars(); + String racerStr = inputView.getStringInput(); + + outputView.getTryCount(); + String tryCnt = inputView.getStringInput(); + TryCountValidator.checkTryCount(tryCnt); + int tryCount = Integer.parseInt(tryCnt); + + RaceInfoRequest raceInfoInput = new RaceInfoRequest(racerStr, tryCount); + + List carNames = StringUtils.splitByComma(raceInfoInput.carNames()); + + return new RaceInfoResponse(carNames, tryCount); + } + + public void raceStart(RaceInfoResponse raceInfo) { + List cars = StringUtils.makeCarsUsingStrings(raceInfo.carNames()); + + outputView.printRaceStart(); + + for(int i = 0; i < raceInfo.gameCount(); ++i) { + raceManager.movingCars(cars); + outputView.printRaceStatus(cars); + } + + finishRace(cars); + } + + private void finishRace(List cars) { + List winners = raceManager.findWinners(cars); + outputView.printRaceFinalStatus(winners); + } +} diff --git a/src/main/java/racingcar/controller/dto/RaceInfoRequest.java b/src/main/java/racingcar/controller/dto/RaceInfoRequest.java new file mode 100644 index 00000000..5b6baaee --- /dev/null +++ b/src/main/java/racingcar/controller/dto/RaceInfoRequest.java @@ -0,0 +1,7 @@ +package racingcar.controller.dto; + +public record RaceInfoRequest( + String carNames, + int gameCount +) { +} diff --git a/src/main/java/racingcar/controller/dto/RaceInfoResponse.java b/src/main/java/racingcar/controller/dto/RaceInfoResponse.java new file mode 100644 index 00000000..eecc44e2 --- /dev/null +++ b/src/main/java/racingcar/controller/dto/RaceInfoResponse.java @@ -0,0 +1,9 @@ +package racingcar.controller.dto; + +import java.util.List; + +public record RaceInfoResponse( + List carNames, + int gameCount +) { +} diff --git a/src/main/java/racingcar/model/Car.java b/src/main/java/racingcar/model/Car.java new file mode 100644 index 00000000..7e6b271c --- /dev/null +++ b/src/main/java/racingcar/model/Car.java @@ -0,0 +1,32 @@ +package racingcar.model; + +import racingcar.util.CarNameValidator; + +public class Car { + private static final int CAN_MOVE_STANDARD = 4; + + private int location; + private String name; + + public Car(String name) { + CarNameValidator.checkName(name); + this.name = name; + this.location = 0; + } + + public void move() { + if(isAbleToMove()) ++location; + } + + private boolean isAbleToMove() { + return (int)(Math.random() * 10) >= CAN_MOVE_STANDARD; + } + + public int getLocation() { + return location; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/racingcar/util/CarNameValidator.java b/src/main/java/racingcar/util/CarNameValidator.java new file mode 100644 index 00000000..40d4061d --- /dev/null +++ b/src/main/java/racingcar/util/CarNameValidator.java @@ -0,0 +1,26 @@ +package racingcar.util; + +public class CarNameValidator { + private static final int NAME_MAX_LENGTH = 5; + + public static void isNull(String name) { + if(name == null) + throw new IllegalArgumentException("이름에는 null 값이 들어갈 수 없습니다."); + } + + public static void isBlank(String name) { + if(name.isBlank()) + throw new IllegalArgumentException("이름은 공백일 수 없습니다."); + } + + public static void isMoreThanMaxLength(String name) { + if(name.length() > NAME_MAX_LENGTH) + throw new IllegalArgumentException("이름은 5자를 넘을 수 없습니다."); + } + + public static void checkName(String name) { + isNull(name); + isBlank(name); + isMoreThanMaxLength(name); + } +} diff --git a/src/main/java/racingcar/util/RaceManager.java b/src/main/java/racingcar/util/RaceManager.java new file mode 100644 index 00000000..386482df --- /dev/null +++ b/src/main/java/racingcar/util/RaceManager.java @@ -0,0 +1,20 @@ +package racingcar.util; + +import racingcar.model.Car; + +import java.util.ArrayList; +import java.util.List; + +public class RaceManager { + public void movingCars(List cars) { + for(var c : cars) c.move(); + } + + public List findWinners(List cars) { + int maxLocation = cars.stream().mapToInt(Car::getLocation).max().orElse(0); + + List winners = cars.stream().filter(c -> c.getLocation() == maxLocation).map(Car::getName).toList(); + + return winners; + } +} diff --git a/src/main/java/racingcar/util/StringUtils.java b/src/main/java/racingcar/util/StringUtils.java new file mode 100644 index 00000000..5a4bf883 --- /dev/null +++ b/src/main/java/racingcar/util/StringUtils.java @@ -0,0 +1,20 @@ +package racingcar.util; + +import racingcar.model.Car; + +import java.util.Arrays; +import java.util.List; + +public class StringUtils { + public static List splitByComma(String str) { + return Arrays.stream(str.split(",")).toList(); + } + + public static String NumToSticks(int count) { + return "-".repeat(count); + } + + public static List makeCarsUsingStrings(List strs) { + return strs.stream().map(Car::new).toList(); + } +} diff --git a/src/main/java/racingcar/util/TryCountValidator.java b/src/main/java/racingcar/util/TryCountValidator.java new file mode 100644 index 00000000..2534f841 --- /dev/null +++ b/src/main/java/racingcar/util/TryCountValidator.java @@ -0,0 +1,21 @@ +package racingcar.util; + +public class TryCountValidator { + public static void isNumber(String tryCnt) { + try { + Integer.parseInt(tryCnt); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("시도 횟수에 문자를 입력할 수 없습니다."); + } + } + + public static void isPositive(int tryCnt) { + if(tryCnt < 0) + throw new IllegalArgumentException("시도 횟수는 자연수여야 합니다."); + } + + public static void checkTryCount(String tryCnt) { + isNumber(tryCnt); + isPositive(Integer.parseInt(tryCnt)); + } +} diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 00000000..304489e0 --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,11 @@ +package racingcar.view; + +import java.util.Scanner; + +public class InputView { + private final Scanner sc = new Scanner(System.in); + + public String getStringInput() { + return sc.nextLine(); + } +} diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 00000000..1b449c07 --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -0,0 +1,31 @@ +package racingcar.view; + +import racingcar.util.StringUtils; +import racingcar.model.Car; + +import java.util.List; + +public class OutputView { + public void getCars() { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)\n"); + } + + public void getTryCount() { + System.out.println("시도할 회수는 몇회인가요?\n"); + } + + public void printRaceStart() { + System.out.println("\n실행 결과"); + } + + public void printRaceStatus(List cars) { + for(var c : cars) { + System.out.println(c.getName() + " : " + StringUtils.NumToSticks(c.getLocation())); + } + System.out.print('\n'); + } + + public void printRaceFinalStatus(List winners) { + System.out.print("최종 우승자 : " + String.join(", ", winners)); + } +}