-
Notifications
You must be signed in to change notification settings - Fork 8
[자동차 경주] 박호건 미션 제출합니다. #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6dc8888
788844c
d6d3a0a
a5dbfed
67119bb
8c40bdf
0f4b8cf
ef937ae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,103 @@ | ||
| # java-racingcar-precourse | ||
| # java-racingCar-precourse | ||
|
|
||
|
|
||
| > 콩 번째로 하는 우테코의 콩 번째 미션 \ | ||
| > 콩 번째로 하는 우테코의 콩 번째 미션 | ||
|
|
||
| <details> | ||
| <summary>과제 세부 내용</summary> | ||
|
|
||
| ## 과제 내용 | ||
| 초간단 자동차 경주 게임을 구현한다. | ||
|
|
||
| - 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. | ||
| - 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. | ||
| - 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. | ||
| - 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. | ||
| - 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. | ||
| - 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. | ||
| - 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다. | ||
| - 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다. | ||
|
|
||
| ### 입출력 | ||
| - 입력 | ||
| - 경주 할 자동차 이름 | ||
| - 시도할 횟수 | ||
| - 출력 | ||
| - 각 차수별 실행 결과 | ||
| - 우승자 안내 문구 | ||
|
|
||
| ex) | ||
|
|
||
| ``` | ||
| 경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분) | ||
| pobi,woni,jun | ||
| 시도할 회수는 몇회인가요? | ||
| 5 | ||
|
|
||
| 실행 결과 | ||
| pobi : - | ||
| woni : | ||
| jun : - | ||
|
|
||
| pobi : -- | ||
| woni : - | ||
| jun : -- | ||
|
|
||
| pobi : --- | ||
| woni : -- | ||
| jun : --- | ||
|
|
||
| pobi : ---- | ||
| woni : --- | ||
| jun : ---- | ||
|
|
||
| pobi : ----- | ||
| woni : ---- | ||
| jun : ----- | ||
|
|
||
| 최종 우승자 : pobi, jun | ||
| ``` | ||
|
|
||
| </details> | ||
|
|
||
| ## 코드 흐름 | ||
| - 사용자의 이름 및 시도 횟수를 입력받는다. 이름의 유효성을 검증한다. | ||
| - 랜덤으로 자동차의 전진 횟수를 더한다. | ||
| - 최종 우승자를 출력한다. | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant View | ||
| participant Controller | ||
| participant Model | ||
|
|
||
| Controller->>View: 입력 대기 | ||
| View->>Controller: 이름, 시도횟수 반환 | ||
| Controller->>Model: 이름, 시도횟수 전달 | ||
| Model->>Controller: 게임 생성 | ||
| loop 시도횟수만큼 반복 | ||
| Controller->>Model: 실행 대기 | ||
| Model->>Controller: 실행 결과 반환 | ||
| Controller->>View: 실행 결과 전달 | ||
| View->>View: 결과 출력 | ||
| View->>Controller: - | ||
| end | ||
| Controller->>Model: 우승자 요청 | ||
| Model->>Controller: 우승자 반환 | ||
| Controller->>View: 우승자 전달 | ||
| View->>View: 우승자 출력 | ||
| View->>Controller: - | ||
| Controller->>Controller: 프로그램 종료 | ||
|
|
||
| ``` | ||
|
|
||
| ## 구현 기능 목록 | ||
| - 입출력 | ||
| - [ ] 사용자 이름 입력 | ||
| - [ ] 시도 횟수 입력 | ||
| - [ ] 차수별 실행 결과 출력 | ||
| - [ ] 최종 우승자 출력 | ||
| - 자동차 전진 | ||
| - [ ] 여러 자동차의 상태 관리 | ||
| - [ ] 랜덤 추출 기능 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package racingcar; | ||
|
|
||
| import racingcar.controller.RacingcarController; | ||
| import racingcar.service.CarControlService; | ||
| import racingcar.service.RandomService; | ||
| import racingcar.view.InputView; | ||
| import racingcar.view.OutputView; | ||
| import racingcar.view.provider.InputProvider; | ||
| import racingcar.view.provider.WoowaInputProvider; | ||
|
|
||
| public class AppConfig { | ||
| public InputProvider inputProvider() { | ||
| return new WoowaInputProvider(); | ||
| } | ||
|
|
||
| public InputView inputView() { | ||
| return new InputView(inputProvider()); | ||
| } | ||
|
|
||
| public OutputView outputView() { | ||
| return new OutputView(); | ||
| } | ||
|
|
||
| public CarControlService carControlService() { | ||
| return new CarControlService(); | ||
| } | ||
|
|
||
| public RandomService randomService() { | ||
| return new RandomService(); | ||
| } | ||
|
|
||
| public RacingcarController racingcarController() { | ||
| return new RacingcarController( | ||
| inputView(), | ||
| outputView(), | ||
| carControlService(), | ||
| randomService() | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,16 @@ | ||
| package racingcar; | ||
|
|
||
| import racingcar.controller.RacingcarController; | ||
|
|
||
| public class Application { | ||
| static AppConfig appConfig; | ||
| static RacingcarController racingcarController; | ||
|
|
||
| public static void main(String[] args) { | ||
| // TODO: 프로그램 구현 | ||
| appConfig = new AppConfig(); | ||
| racingcarController = appConfig.racingcarController(); | ||
|
|
||
| racingcarController.run(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package racingcar.controller; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.IntStream; | ||
| import racingcar.model.dto.CarMovementDto; | ||
| import racingcar.service.CarControlService; | ||
| import racingcar.service.RandomService; | ||
| import racingcar.utils.Validator; | ||
| import racingcar.view.InputView; | ||
| import racingcar.view.OutputView; | ||
|
|
||
| public class RacingcarController { | ||
| InputView inputView; | ||
| OutputView outputView; | ||
| CarControlService carControlService; | ||
| RandomService randomService; | ||
|
|
||
| private int trials; | ||
|
|
||
| public RacingcarController(InputView inputView, OutputView outputView, | ||
| CarControlService carControlService, RandomService randomService) { | ||
| this.inputView = inputView; | ||
| this.outputView = outputView; | ||
| this.carControlService = carControlService; | ||
| this.randomService = randomService; | ||
| } | ||
|
|
||
| public void run() { | ||
| initialize(); | ||
| execute(); | ||
| getResult(); | ||
| } | ||
|
|
||
| private void initialize() { | ||
| String names = inputView.getCarName(); | ||
| trials = inputView.getTrialNumber(); | ||
| Validator.naturalNumberCheck(trials); | ||
| carControlService.initialize(names); | ||
| outputView.newLine(); | ||
| } | ||
|
|
||
| private void execute() { | ||
| outputView.printResultGuide(); | ||
| IntStream.range(0, trials).forEach((t) -> { | ||
| List<CarMovementDto> progress = carControlService.playTurn(randomService::willCarMove); | ||
| outputView.printPlayerResult(progress); | ||
| outputView.newLine(); | ||
| }); | ||
| } | ||
|
|
||
| private void getResult() { | ||
| List<String> winner = carControlService.getWinner(); | ||
| outputView.printWinner(winner); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package racingcar.model; | ||
|
|
||
| import racingcar.model.dto.CarMovementDto; | ||
|
|
||
| public class Car { | ||
| private final String name; | ||
| private int position; | ||
|
|
||
| public Car(String name) { | ||
| this.name = name; | ||
| position = 0; | ||
| } | ||
|
|
||
| public CarMovementDto getData() { | ||
| return new CarMovementDto(name, position); | ||
| } | ||
|
|
||
| public void move() { | ||
| position++; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package racingcar.model; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Comparator; | ||
| import java.util.List; | ||
| import java.util.function.Supplier; | ||
| import racingcar.model.dto.CarMovementDto; | ||
|
|
||
| public class Race { | ||
| private int turn; | ||
| private final List<Car> cars; | ||
|
|
||
| private Race() { | ||
| cars = new ArrayList<>(); | ||
| } | ||
|
|
||
| public static Race init(List<String> cars) { | ||
| Race race = new Race(); | ||
| race.cars.addAll(cars.stream().map(Car::new).toList()); | ||
| race.turn = 0; | ||
| return race; | ||
| } | ||
|
|
||
| public void advance(Supplier<Boolean> movementFunction) { | ||
| cars.forEach((car) -> { | ||
| if (movementFunction.get()) { | ||
| car.move(); | ||
| } | ||
| }); | ||
| turn++; | ||
| } | ||
|
|
||
| public List<CarMovementDto> getResult() { | ||
| return cars.stream().map((Car::getData)).toList(); | ||
| } | ||
|
|
||
| public List<String> getWinner() { | ||
| int maxValue = cars.stream() | ||
| .map(car -> car.getData().movement()) | ||
| .max(Comparator.naturalOrder()).orElse(0); | ||
|
|
||
| return cars.stream() | ||
| .filter(car -> car.getData().movement() == maxValue) | ||
| .map(car -> car.getData().name()) | ||
| .toList(); | ||
| } | ||
|
|
||
| public int getTurn() { | ||
| return turn; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package racingcar.model.dto; | ||
|
|
||
| public record CarMovementDto(String name, int movement) { | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) { | ||
| return true; | ||
| } | ||
| if (o == null || getClass() != o.getClass()) { | ||
| return false; | ||
| } | ||
| CarMovementDto that = (CarMovementDto) o; | ||
| return movement == that.movement && name.equals(that.name); | ||
| } | ||
| } |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금 보니까 서비스가 상호작용하는 모델이 race밖에 없고 race가 car의 함수를 다 커버하는것 같은데, 그러면 race가 모델보다는 서비스에 가깝지 않나유??
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 책임을 분리하기 위함입니다. 서비스 레이어가 필요한 이유는 여러 모델을 중계하기 위함도 있을 수 있지만 컨트롤러의 비지니스 로직과 데이터와 이에 대한 상태를 담아두는 모델을 분리하는 것 역시 있다고 생각합니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package racingcar.service; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.function.Supplier; | ||
| import racingcar.model.Race; | ||
| import racingcar.model.dto.CarMovementDto; | ||
| import racingcar.utils.Validator; | ||
|
|
||
| public class CarControlService { | ||
| private Race race; | ||
|
|
||
| private static final int MIN_CHAR_LENGTH = 1; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Static final min max값은 카 컨트롤 서비스보단 validator랑 관련이 있는거같은데 validator에 넣으시는게 나을듯 유틸 클래스는 stateless해야하지만 상수값은 가질 수 잇다구 검색하면 나옵니다
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 상수가 몇 개 없어 상수 파일을 만들지는 않았지만 그것 이외의 문제가 되지는 않다고 생각합니다. (static final로 선언하여 상수화시켰기에 이 부분은 stateless합니다. ) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호 저는 validator의 stringRangeCheck가 이름만 관리하는걸로 생각해서 상수를 내부에 집어넣는걸 코멘트했었는데 호건씨 사용처쪽으로 생각하시면 해당 방안이 더 적합하다구 생각합니다!! |
||
| private static final int MAX_CHAR_LENGTH = 5; | ||
| private static final String COMMA = ","; | ||
|
|
||
| public void initialize(String names) { | ||
| List<String> nameList = parseNames(names); | ||
| validateName(nameList); | ||
| this.race = Race.init(nameList); | ||
| } | ||
|
|
||
| private List<String> parseNames(String names) { | ||
| return Arrays.stream(names.trim().split(COMMA)).toList(); | ||
| } | ||
|
|
||
| public List<CarMovementDto> playTurn(Supplier<Boolean> movementFunction) { | ||
| race.advance(movementFunction); | ||
| return race.getResult(); | ||
| } | ||
|
|
||
| public List<String> getWinner() { | ||
| return race.getWinner(); | ||
| } | ||
|
|
||
| private void validateName(List<String> nameList) { | ||
| nameList.forEach((name) -> Validator.stringRangeCheck(name, MIN_CHAR_LENGTH, MAX_CHAR_LENGTH)); | ||
| Validator.duplicationCheck(nameList); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package racingcar.service; | ||
|
|
||
| import camp.nextstep.edu.missionutils.Randoms; | ||
|
|
||
| public class RandomService { | ||
| private static final int LOWER_BOUND = 0; | ||
| private static final int UPPER_BOUND = 9; | ||
| private static final int THRESHOLD = 4; | ||
|
|
||
| public boolean willCarMove() { | ||
| return Randoms.pickNumberInRange(LOWER_BOUND, UPPER_BOUND) >= THRESHOLD; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package racingcar.utils; | ||
|
|
||
| public enum MessageConstants { | ||
| INPUT_CAR_NAME_GUIDE("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"), | ||
| INPUT_TRIAL_NUMBER_GUIDE("시도할 회수는 몇회인가요?"), | ||
| OUTPUT_EXECUTE_RESULT_GUIDE("실행 결과"), | ||
| OUTPUT_FINAL_WINNER_GUIDE("최종 우승자 : "), | ||
| OUTPUT_CAR_MOVEMENT("%s : "); | ||
|
|
||
| private final String message; | ||
|
|
||
| MessageConstants(String message) { | ||
| this.message = message; | ||
| } | ||
|
|
||
| public String getMessage() { | ||
| return message; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제 개인적인 생각으론 저 런 메소드는 메인에 빼는게 더 좋을거같아요 컨트롤러는 사용자의 조작에 따라 처리하는 놈이라 시스템 전체 실행에는 관련이 약하지 않나용??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
initialize메서드 말씀하시는거죠? 이 메서드는 단순히 "프로그램 초기화를 위한 것"이라기 보다는 "자동차 경주를 준비"하는 개념에 가깝다고 생각해요! 필연적으로 뷰와 모델을 매개하기에 컨트롤러의 역할이라고 바라봤습니다