-
Notifications
You must be signed in to change notification settings - Fork 94
[강민혁] 자동차 경주 미션 제출 #119
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: kangminhyuk1111
Are you sure you want to change the base?
[강민혁] 자동차 경주 미션 제출 #119
Changes from all commits
7cd5e05
0b7f359
41431dd
f0604c4
f44297a
8c634cd
f607f3e
b803dce
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,45 @@ | ||
# 1단계 - 움직이는 자동차 | ||
## 기능 요구사항 | ||
### 자동차 기본 속성 | ||
- 자동차는 고유한 이름을 가진다. | ||
- 이름을 가지는 Car 객체를 통해 관리 | ||
|
||
### 자동차 이동 로직 | ||
자동차는 다음 규칙에 따라 이동한다 | ||
- 0에서 9 사이의 랜덤 값을 생성한다. | ||
- 랜덤 값이 4 이상일 경우 자동차는 1칸 전진한다. | ||
- 랜덤 값이 3 이하일 경우 자동차는 현재 위치에 멈춘다. | ||
|
||
# 2단계 - 우승 자동차 구하기 | ||
## 기능 요구사항 | ||
### 자동차 참여 기능 | ||
- n대의 자동차가 게임에 참여할 수 있어야 함 | ||
- 자동차는 고유한 이름을 가진다. | ||
|
||
### 게임 진행 과정 | ||
- 주어진 횟수 동안 게임이 진행됨 | ||
- 매 라운드마다 모든 자동차는 전진하거나 정지함 | ||
Comment on lines
+19
to
+21
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. 위와 같은 맥락으로 횟수로 0, -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. 명시된 조건 이외에 것은 일부러 생략을 했었는데요, 신경 써야할 부분인 것 같습니다. |
||
|
||
### 우승자 선별 과정 | ||
- 게임 완료 후, 가장 높은 점수를 가진 자동차가 우승 | ||
- 공동 우승자 선별 가능 | ||
- 가장 position의 값이 큰 자동차가 우승 | ||
|
||
# 3단계 - 게임 실행 | ||
## 기능 요구사항 | ||
### 자동차 이름 | ||
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. | ||
- 게임 내에서 같은 차량의 이름을 갖는 경우 Exception 발생 | ||
- 앞 뒤 빈 공백은 제거해야 한다. | ||
|
||
### 반복 횟수 | ||
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. | ||
- 사용자의 입력을 통해 상호작용한다. 그렇기 때문에 예외 처리가 필요하다 양수인 정수만 가능. | ||
|
||
### 우승자 판별 | ||
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. | ||
- 우승자는 findWinners() 라는 메서드로 판별한다. | ||
|
||
### 전진 기준 | ||
- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다. | ||
- 1단계 요구사항과 동일함. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package racingcar; | ||
|
||
import racingcar.controller.RacingController; | ||
import racingcar.ui.InputView; | ||
import racingcar.util.DefaultRandomNumberGenerator; | ||
|
||
import java.util.List; | ||
|
||
public class RacingCarMain { | ||
public static void main(String[] args) { | ||
RacingController racingController = new RacingController(new DefaultRandomNumberGenerator()); | ||
|
||
racingController.playGame(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package racingcar.controller; | ||
|
||
import racingcar.domain.Car; | ||
import racingcar.domain.Cars; | ||
import racingcar.domain.RacingGame; | ||
import racingcar.domain.vo.Name; | ||
import racingcar.domain.vo.Repetition; | ||
import racingcar.ui.InputView; | ||
import racingcar.ui.OutputView; | ||
import racingcar.util.RandomNumberGenerator; | ||
|
||
import java.util.List; | ||
|
||
public class RacingController { | ||
|
||
private final RandomNumberGenerator randomNumberGenerator; | ||
|
||
public RacingController(final RandomNumberGenerator randomNumberGenerator) { | ||
this.randomNumberGenerator = randomNumberGenerator; | ||
} | ||
|
||
public void playGame() { | ||
final Repetition repetition = initializeRoundCount(); | ||
final Cars cars = initializeCars(); | ||
|
||
final RacingGame game = new RacingGame(cars, randomNumberGenerator); | ||
|
||
executeGame(game, repetition); | ||
|
||
displayWinners(game); | ||
} | ||
|
||
private Repetition initializeRoundCount() { | ||
return new Repetition(InputView.inputRacingCount()); | ||
} | ||
|
||
private Cars initializeCars() { | ||
return createCars(InputView.inputCarNames()); | ||
} | ||
|
||
private void executeGame(final RacingGame game, final Repetition repetition) { | ||
OutputView.printGameStart(); | ||
|
||
for (int i = 0; i < repetition.value(); i++) { | ||
game.moveCars(); | ||
OutputView.printRoundResult(game.getCars()); | ||
} | ||
} | ||
|
||
private static void displayWinners(RacingGame game) { | ||
OutputView.printWinners(game.getCars()); | ||
} | ||
|
||
private Cars createCars(List<String> carNames) { | ||
return new Cars(carNames.stream() | ||
.map(name -> new Car(new Name(name))) | ||
.toList()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package racingcar.domain; | ||
|
||
import racingcar.domain.vo.Name; | ||
import racingcar.domain.vo.Position; | ||
import racingcar.util.RandomNumberGenerator; | ||
|
||
public class Car { | ||
private static final int MOVING_STANDARD = 4; | ||
|
||
private final Name name; | ||
private Position position; | ||
|
||
public Car(final Name name) { | ||
this.name = name; | ||
this.position = new Position(); | ||
} | ||
|
||
public String getName() { | ||
return name.value(); | ||
} | ||
|
||
public int getPosition() { | ||
return position.value(); | ||
} | ||
|
||
public void move(final RandomNumberGenerator randomNumberGenerator) { | ||
if (randomNumberGenerator.generate() >= MOVING_STANDARD) { | ||
position = position.forward(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package racingcar.domain; | ||
|
||
import racingcar.domain.vo.Winners; | ||
import racingcar.exception.ApplicationError; | ||
import racingcar.exception.ApplicationException; | ||
import racingcar.util.RandomNumberGenerator; | ||
|
||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
public record Cars(List<Car> value) { | ||
|
||
public Cars { | ||
validateDuplicateNames(value); | ||
} | ||
|
||
public void moveCars(RandomNumberGenerator randomNumberGenerator) { | ||
for (Car car : value) { | ||
car.move(randomNumberGenerator); | ||
} | ||
} | ||
|
||
public Winners findWinners() { | ||
int maxPosition = value.stream() | ||
.mapToInt(Car::getPosition) | ||
.max() | ||
.orElse(0); | ||
|
||
return new Winners(value.stream() | ||
.filter(car -> car.getPosition() == maxPosition) | ||
.toList()); | ||
} | ||
|
||
private void validateDuplicateNames(List<Car> value) { | ||
Set<String> uniqueNames = value.stream() | ||
.map(Car::getName) | ||
.collect(Collectors.toSet()); | ||
|
||
if (uniqueNames.size() < value.size()) { | ||
throw new ApplicationException(ApplicationError.DUPLICATE_NAME); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package racingcar.domain; | ||
|
||
import racingcar.domain.vo.Winners; | ||
import racingcar.util.RandomNumberGenerator; | ||
|
||
import java.util.List; | ||
|
||
public class RacingGame { | ||
private final Cars cars; | ||
private final RandomNumberGenerator randomNumberGenerator; | ||
|
||
public RacingGame(final Cars cars, final RandomNumberGenerator randomNumberGenerator) { | ||
this.cars = cars; | ||
this.randomNumberGenerator = randomNumberGenerator; | ||
} | ||
|
||
public void moveCars() { | ||
cars.moveCars(randomNumberGenerator); | ||
} | ||
|
||
public Winners getWinners() { | ||
return cars.findWinners(); | ||
} | ||
|
||
public List<Car> getCars() { | ||
return cars.value(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package racingcar.domain.vo; | ||
|
||
import racingcar.exception.ApplicationError; | ||
import racingcar.exception.ApplicationException; | ||
|
||
public record Name(String value) { | ||
|
||
public Name { | ||
validateName(value); | ||
} | ||
|
||
private void validateName(final String name) { | ||
if (isNullOrEmpty(name)) { | ||
throw new ApplicationException(ApplicationError.INVALID_NAME_EMPTY); | ||
} | ||
|
||
if (isTooLong(name)) { | ||
throw new ApplicationException(ApplicationError.INVALID_NAME_TOO_LONG); | ||
} | ||
} | ||
|
||
private boolean isNullOrEmpty(String name) { | ||
return name == null || name.trim().isEmpty(); | ||
} | ||
|
||
private boolean isTooLong(String name) { | ||
return name.trim().length() > 5; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package racingcar.domain.vo; | ||
|
||
import racingcar.exception.ApplicationError; | ||
import racingcar.exception.ApplicationException; | ||
|
||
public record Position(int value) { | ||
private static final int MIN_VALUE = 0; | ||
|
||
public Position() { | ||
this(MIN_VALUE); | ||
} | ||
|
||
public Position { | ||
validatePosition(value); | ||
} | ||
|
||
public Position forward() { | ||
return new Position(value + 1); | ||
} | ||
|
||
private void validatePosition(int value) { | ||
if (isNegative(value)) { | ||
throw new ApplicationException(ApplicationError.INVALID_POSITION_NEGATIVE); | ||
} | ||
} | ||
|
||
private boolean isNegative(int value) { | ||
return value < MIN_VALUE; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package racingcar.domain.vo; | ||
|
||
import racingcar.exception.ApplicationError; | ||
import racingcar.exception.ApplicationException; | ||
|
||
public record Repetition(int value) { | ||
|
||
public static final int MIN_REPETITION = 0; | ||
|
||
public Repetition { | ||
validateRepetition(value); | ||
} | ||
|
||
private void validateRepetition(int repetition) { | ||
if (isUnderMinimumRepetition(repetition)) { | ||
throw new ApplicationException(ApplicationError.INVALID_REPETITION_NEGATIVE); | ||
} | ||
} | ||
|
||
private boolean isUnderMinimumRepetition(int repetition) { | ||
return repetition < MIN_REPETITION; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package racingcar.domain.vo; | ||
|
||
import racingcar.domain.Car; | ||
|
||
import java.util.List; | ||
|
||
public record Winners(List<Car> value) { | ||
public int countWinners() { | ||
return value.size(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package racingcar.exception; | ||
|
||
public enum ApplicationError { | ||
|
||
/* name exception */ | ||
INVALID_NAME_EMPTY("이름은 빈 값이 작성될 수 없습니다."), | ||
INVALID_NAME_TOO_LONG("이름은 5자 이하만 가능합니다."), | ||
|
||
/* position exception */ | ||
INVALID_POSITION_NEGATIVE("위치는 음수가 될 수 없습니다."), | ||
|
||
/* repetition exception */ | ||
INVALID_REPETITION_NEGATIVE("반복 횟수는 최소 0 이상의 양수를 입력해주세요."), | ||
|
||
/* cars exception */ | ||
DUPLICATE_NAME("중복된 이름이 존재합니다."); | ||
|
||
private final String description; | ||
|
||
ApplicationError(String description) { | ||
this.description = description; | ||
} | ||
|
||
public String getDescription() { | ||
return description; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package racingcar.exception; | ||
|
||
public class ApplicationException extends RuntimeException { | ||
public ApplicationException(ApplicationError error) { | ||
super(error.getDescription()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package racingcar.ui; | ||
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. 패키지 이름을 ui로 지어주신 이유가 있을까요? 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를 주었고 실제로 보여지고 작성되는 부분은 ui로 작성했습니다. 혹시 어떤 패키징 방식이 일반적일까요? |
||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Scanner; | ||
|
||
public class InputView { | ||
|
||
private static final Scanner scanner = new Scanner(System.in); | ||
|
||
private InputView() { | ||
|
||
} | ||
|
||
public static List<String> inputCarNames() { | ||
System.out.println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)."); | ||
String input = scanner.nextLine(); | ||
return parseCarNames(input); | ||
} | ||
|
||
public static int inputRacingCount() { | ||
System.out.println("시도할 회수는 몇회인가요?"); | ||
return scanner.nextInt(); | ||
} | ||
|
||
private static List<String> parseCarNames(final String input) { | ||
return Arrays.stream(input.split(",")).toList(); | ||
} | ||
} |
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.
"자동차는 고유한 이름을 가진다."라는 조건이 있는데요.
현재 어플리케이션에서 자동차 이름의 input으로 "a, a, b, c, d"를 입력한다면 어떤 결과가 나올까요?
위와 같이 이름에 공백이 있는 경우 trim 후 이름으로 사용하는것도 고려해보시면 좋을것같아요. (사용성 측면에서요😁)
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.
중복과 공백을 고려하여 예외 처리가 필요할 것 같습니다.