-
Notifications
You must be signed in to change notification settings - Fork 116
[그리디] 김민욱 Java:자동차 경주 미션 3,4 단계 제출합니다. #177
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: hapdaypy
Are you sure you want to change the base?
Changes from all commits
9fee122
1077534
5c22eae
4480a21
b566e31
159e617
1c4dfe5
7877f6c
11a0d07
82e46fb
059c10a
9dd00ba
03accdd
23abbbe
cb031fe
bfc8e29
2fa8c64
e1ad114
de9a01b
3f90b89
f8eb16b
c79e89f
0783491
00c6296
63eb12b
6f1f03f
f0535c2
480d3f6
a3aae79
7ff14c6
4e5c715
beec64e
2383aca
f84be8a
16f3a09
c0ba151
f7a77ae
0c1f264
2e6d2cc
916bc1b
75fbb12
1baf558
507359d
5b4a919
ffa3dcd
d5bb220
cccecbc
1d18214
5a2864e
8830bf9
4f4fbc2
a98da71
39e70e3
03f95ee
56a6d53
8feb016
7622804
cd4eef1
dac335b
e631f1d
ebf44d7
015f2c6
9f5a01b
6a68d7f
bff56d8
3e5cc6c
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 |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # 🏎️ 자동차 경주 게임 (Car Racing) | ||
|
|
||
| ## 🚀 기능 구현 목록 | ||
|
|
||
| ### 1. 입력 및 데이터 처리 | ||
| - [ ] **자동차 이름 입력**: 쉼표(`,`)를 기준으로 구분하여 경주에 참여할 자동차 이름을 입력받는다. | ||
| - [ ] **데이터 변환**: 입력된 문자열을 분리하여 `Car` 객체 리스트로 생성한다. | ||
| - [ ] **시도 횟수 입력**: 전체 자동차가 이동을 시도할 총 횟수를 입력받는다. | ||
|
|
||
| ### 2. 레이싱 로직 | ||
| - [ ] **전진 조건 확인**: 매 라운드마다 각 자동차별로 무작위 값을 생성하여 전진 여부를 결정한다. | ||
| - [ ] **위치 업데이트**: 전진 조건을 만족하는 경우 자동차의 위치를 $1$ 씩 증가시킨다. | ||
| - [ ] **우승자 판별**: 모든 라운드 종료 후 전진 거리가 가장 긴 자동차를 우승자로 선정한다. (공동 우승 가능) | ||
|
|
||
| ### 3. 출력 | ||
| - [ ] **라운드 결과**: 매 라운드 종료 시점의 자동차별 이름과 전진 상태(`-`)를 출력한다. | ||
| - [ ] **최종 우승자**: 경주 종료 후 최종 우승자의 이름을 출력한다. (공동 우승 시 쉼표로 구분) | ||
|
|
||
| --- | ||
|
|
||
| ## ⚠️ 예외 처리 규칙 (Exception Handling) | ||
| 잘못된 값 입력 시 `IllegalArgumentException`을 발생시키며, 프로그램은 즉시 종료되거나 에러 메시지를 출력해야 한다. | ||
|
|
||
| ### [자동차 이름 관련] | ||
| 1. **형식 오류**: 알파벳과 한글 이외의 문자(특수문자, 숫자 등)가 포함된 경우. | ||
| 2. **공백 포함**: 이름 내부에 공백이 있거나, 입력값이 공백으로만 구성된 경우. | ||
| 3. **입력 부재**: 자동차 이름을 입력하지 않고 진행하려 하는 경우. | ||
| 4. **중복 발생**: 동일한 이름을 가진 자동차가 리스트 내에 중복으로 존재하는 경우. | ||
|
|
||
| ### [시도 횟수 관련] | ||
| 1. **타입 오류**: 숫자 이외의 문자, 특수기호, 공백이 포함된 경우. | ||
| 2. **범위 오류**: 입력값이 $0$ 이하의 정수인 경우 (최소 $1$ 회 이상 필요). | ||
| 3. **미입력**: 시도 횟수를 입력하지 않고 엔터를 입력한 경우. | ||
|
|
||
| --- | ||
|
|
||
| ## 💻 프로그램 진행 방식 | ||
| 1. **[Step 1]** `InputView`를 통한 자동차 이름 및 시도 횟수 입력. | ||
| 2. **[Step 2]** 입력 데이터 검증 및 `List<Car>` 객체 생성. | ||
| 3. **[Step 3]** 설정된 횟수만큼 경주 실행 및 실시간 결과 출력. | ||
| 4. **[Step 4]** 최종 우승자 연산 및 `OutputView`를 통한 결과 발표. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package racing; | ||
| import racing.controller.Controller; | ||
|
|
||
| public class Application { | ||
| public static void main(String[] args) { | ||
| Controller controller = new Controller(); | ||
| controller.run(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package racing.controller; | ||
|
|
||
| import racing.view.InputView; | ||
|
|
||
| import racing.domain.Cars; | ||
| import racing.domain.Race; | ||
| import racing.domain.Car; | ||
| import racing.view.OutputView; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import racing.domain.RandomMoveStrategy; | ||
|
|
||
| public class Controller { | ||
| public void run() { | ||
| // [1] 자동차 이름 및 시도 횟수 입력 받기 | ||
| OutputView.printInputCarNameMessage(); | ||
| String carNameInput = InputView.inputCarName(); | ||
|
|
||
| OutputView.printInputTrialCountMessage(); | ||
| int trialNumber = InputView.inputTrialNumberCount(); | ||
| // [2] 데이터 변환: 문자열 -> 자동차 객체 리스트 | ||
| List<String> carList = InputView.parse(carNameInput); | ||
| List<Car> raceCarList = carList.stream() | ||
| .map(Car::new) | ||
| .collect(Collectors.toList()); | ||
| Cars cars = new Cars(raceCarList); | ||
|
|
||
| Race race = new Race(cars,trialNumber,new RandomMoveStrategy()); | ||
| //[3] 레이씽 경기 시작 | ||
| OutputView.println(); | ||
| OutputView.printExecutionResultMessage(); | ||
|
|
||
| while (race.hasMoreRounds()) { | ||
| race.playRound(); | ||
| OutputView.printRoundResult(cars.getCarList()); | ||
|
|
||
| } | ||
| //[4] 결과 출력 | ||
| OutputView.printWinners(race.getWinners()); | ||
|
|
||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package racing.domain; | ||
|
|
||
| public class Car { | ||
| private final String name; | ||
| private int position; | ||
|
|
||
| public Car(String name) { | ||
| validateName(name); | ||
| this.name = name; | ||
| this.position = 0; | ||
| } | ||
|
|
||
| private void validateName(String name) { | ||
| if (name == null || name.isBlank()) { | ||
| throw new IllegalArgumentException("[ERROR] 자동차 이름은 1자 이상이어야 합니다."); | ||
| } | ||
| if (name.length() > 5) { | ||
| throw new IllegalArgumentException("[ERROR] 자동차 이름은 5자 이하여야 합니다."); | ||
| } | ||
| if (name.contains(" ")) { | ||
| throw new IllegalArgumentException("[ERROR] 자동차 이름에 공백을 포함할 수 없습니다."); | ||
| } | ||
| if (!name.matches("^[a-zA-Z0-9가-힣]*$")) { | ||
| throw new IllegalArgumentException("[ERROR] 자동차 이름에 특수문자를 포함할 수 없습니다."); | ||
| } | ||
| } | ||
|
|
||
| public void move() { | ||
| this.position++; | ||
| } | ||
|
|
||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| public int getPosition() { | ||
| return position; | ||
| } | ||
|
|
||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package racing.domain; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.List; | ||
|
|
||
| public class Cars { | ||
| private final List<Car> cars; | ||
|
|
||
| public Cars(List<Car> cars) { | ||
| this.cars = cars; | ||
| validateDuplicate(); | ||
| } | ||
|
|
||
| private void validateDuplicate() { | ||
| long distinctCount = cars.stream() | ||
| .map(Car::getName) | ||
| .distinct() | ||
| .count(); | ||
| if (distinctCount != cars.size()) { | ||
| throw new IllegalArgumentException("[ERROR] 중복된 자동차 이름이 존재합니다."); | ||
| } | ||
| } | ||
| public void moveAll(MoveStrategy moveStrategy){ | ||
| for (Car car : cars) { | ||
| if (moveStrategy.isMovable()) { | ||
| car.move(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // 불변성 보장: 외부에서 리스트를 수정하지 못하도록 읽기 전용 뷰 반환 | ||
| public List<Car> getCarList() { | ||
| return Collections.unmodifiableList(cars); | ||
| } | ||
|
Comment on lines
+31
to
+34
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. 제 리뷰를 반영해 주셨군요 ~~! 👍
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,5 @@ | ||
| package racing.domain; | ||
|
|
||
| public interface MoveStrategy { | ||
| boolean isMovable(); | ||
| } | ||
|
Comment on lines
+3
to
+5
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. 👏👏
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,45 @@ | ||
| package racing.domain; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| public class Race { | ||
| private final Cars cars; | ||
| private final int totalRounds; | ||
| private int currentRound; | ||
| private final MoveStrategy moveStrategy; | ||
|
|
||
| public Race(Cars cars, int totalRounds, MoveStrategy moveStrategy) { | ||
| if (totalRounds <= 0) { | ||
| throw new IllegalArgumentException("[ERROR] 시도 횟수는 1 이상이어야 합니다."); | ||
| } | ||
| this.cars = cars; | ||
| this.totalRounds = totalRounds; | ||
| this.currentRound = 0; | ||
| this.moveStrategy = moveStrategy; | ||
| } | ||
|
Comment on lines
+6
to
+20
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.
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. 💯 👍 |
||
|
|
||
| public boolean hasMoreRounds() { | ||
| return currentRound < totalRounds; | ||
| } | ||
|
|
||
| public void playRound() { | ||
| if (!hasMoreRounds()) { | ||
| throw new IllegalStateException("[ERROR] 이미 모든 라운드가 종료되었습니다."); | ||
| } | ||
| cars.moveAll(moveStrategy); | ||
| currentRound++; | ||
| } | ||
|
|
||
| public List<String> getWinners() { | ||
| int maxPosition = cars.getCarList().stream() | ||
| .mapToInt(Car::getPosition) | ||
| .max() | ||
| .orElse(0); | ||
|
|
||
| return cars.getCarList().stream() | ||
| .filter(car -> car.getPosition() == maxPosition) | ||
| .map(Car::getName) | ||
| .collect(Collectors.toList()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package racing.domain; | ||
|
|
||
| import java.util.Random; | ||
|
|
||
| public class RandomMoveStrategy implements MoveStrategy { | ||
| private static final int RANDOM_RANGE = 10; | ||
| private static final int MOVE_THRESHOLD = 4; | ||
| private final Random random; | ||
|
|
||
| public RandomMoveStrategy() { | ||
| this(new Random()); | ||
| } | ||
|
|
||
| public RandomMoveStrategy(Random random) { | ||
| this.random = random; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isMovable() { | ||
| return random.nextInt(RANDOM_RANGE) >= MOVE_THRESHOLD; | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package racing.view; | ||
|
|
||
| import racing.domain.Car; | ||
|
|
||
| import java.util.Scanner; | ||
|
|
||
|
|
||
|
|
||
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
|
|
||
|
|
||
| public class InputView { | ||
| private static final Scanner SCANNER = new Scanner(System.in); | ||
|
|
||
| public static String inputCarName() { | ||
| String carName = SCANNER.nextLine(); | ||
| validateCarNameFormat(carName); | ||
| return carName; | ||
| } | ||
|
|
||
| public static int inputTrialNumberCount() { | ||
| String input = SCANNER.nextLine(); | ||
| return parseTrialCount(input); | ||
| } | ||
|
Comment on lines
+25
to
+28
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. 입력의 책임을 갖는 +) 이렇게 입력 부분에서 출력 문을 분리해 이동시키면 어떤 장점이 있는거 같으신가요?
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. 아무래도 지금 controller의 역할이 살짝 빈약하다는 생각이 됩니다. 적극적이기 controller 가 아닌 듯한 느낌을 받았습니다. 출력문을 분리해서 이동하시키게 된다면, 프로그램의 규모가 커지더라도, 수정이 비교적 수월해진다는 장점이 있습니다. 적극적인 controller 로 리팩토링을 하겠습니다! 제가 지금 코드를 봐도 outputView 가 domain 여기저기 흩어져 있는 것이 보이네요 ㅜ 이렇게된다면 outputView가 많아졌을 때, 어디서 리팩토링을 해야할지 못 찾을 수 있겠다는 생각을 했습니다. |
||
|
|
||
| private static void validateCarNameFormat(String input) { | ||
| if (input == null || input.isBlank()) { | ||
| throw new IllegalArgumentException("[ERROR] 입력값이 없습니다."); | ||
| } | ||
| String noSpaceInput = input.replace(" ", ""); | ||
| if (noSpaceInput.contains(",,")) { | ||
| throw new IllegalArgumentException("[ERROR] 쉼표가 연속으로 입력되었습니다."); | ||
| } | ||
| if (input.startsWith(",") || input.endsWith(",")) { | ||
| throw new IllegalArgumentException("[ERROR] 입력값의 시작이나 끝에 쉼표가 있습니다."); | ||
| } | ||
| } | ||
|
Comment on lines
+30
to
+41
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. 여기 자동차 이름 입력을 검사하기위해 if 문으로 여러 케이스를 검증해 주시고 계신데요, 여기서도 Car 도메인 내부에서 자동차 이름을 검증했을때 처럼 정규표현식을 쓸 수 있었을 것 같은데,
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. view에서 검증하는 것과 domain에서 금증을 해야하는 기능을 분리했습니다! inputView 같은 경우에는 숫자와 , 로 잘 되어있는지와 같은 입력된 형식에 대해서만을 검증을 하고 car 도메인에서는 정규식을 통해서 올바른 자동차의 이름을 검증하기 위해 다른 클래스로 분리하여 검증을 시도했습니다 ! |
||
|
|
||
| private static int parseTrialCount(String input) { | ||
| try { | ||
| return Integer.parseInt(input); | ||
| } catch (NumberFormatException e) { | ||
| throw new IllegalArgumentException("[ERROR] 시도 횟수는 정수 형의 숫자여야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| public static List<String> parse(String input) { | ||
| return Arrays.stream(input.split(",")) | ||
| .map(String::trim) // 양끝 공백 제거 | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package racing.view; | ||
|
|
||
| import racing.domain.Car; | ||
| import java.util.List; | ||
|
|
||
| public class OutputView { | ||
| private static final String INPUT_CAR_NAME_MESSAGE = "경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)."; | ||
| private static final String INPUT_CAR_COUNT_MESSAGE = "시도할 회수는 몇회인가요?"; | ||
| private static final String EXECUTION_RESULT_MESSAGE = "실행 결과"; | ||
|
|
||
| public static void printInputCarNameMessage() { | ||
| System.out.println(INPUT_CAR_NAME_MESSAGE); | ||
| } | ||
|
|
||
| public static void printInputTrialCountMessage() { | ||
| System.out.println(INPUT_CAR_COUNT_MESSAGE); | ||
| } | ||
|
|
||
| public static void printExecutionResultMessage() { | ||
| System.out.println(EXECUTION_RESULT_MESSAGE); | ||
| } | ||
|
|
||
| public static void printRoundResult(List<Car> cars) { | ||
| for (Car car : cars) { | ||
| System.out.println(car.getName() + " : " + "-".repeat(car.getPosition())); | ||
| } | ||
| System.out.println(""); | ||
|
|
||
| } | ||
|
|
||
| public static void printWinners(List<String> winnerNames) { | ||
| if (winnerNames == null || winnerNames.isEmpty()) { | ||
| return; | ||
| } | ||
| String result = String.join(", ", winnerNames); | ||
| System.out.println(result + "가 최종 우승했습니다."); | ||
| } | ||
| public static void println() { | ||
| System.out.println(); | ||
| } | ||
|
|
||
| } |
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.
Cars 일급 컬렉션에 대한 이야기는 이 코멘트를 참고해 주세요!
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.
넵 !