Skip to content

[강민혁] 자동차 경주 미션 제출 #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

Open
wants to merge 8 commits into
base: kangminhyuk1111
Choose a base branch
from
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 1단계 - 움직이는 자동차
## 기능 요구사항
### 자동차 기본 속성
- 자동차는 고유한 이름을 가진다.
- 이름을 가지는 Car 객체를 통해 관리
Comment on lines +3 to +5
Copy link

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 후 이름으로 사용하는것도 고려해보시면 좋을것같아요. (사용성 측면에서요😁)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

중복과 공백을 고려하여 예외 처리가 필요할 것 같습니다.


### 자동차 이동 로직
자동차는 다음 규칙에 따라 이동한다
- 0에서 9 사이의 랜덤 값을 생성한다.
- 랜덤 값이 4 이상일 경우 자동차는 1칸 전진한다.
- 랜덤 값이 3 이하일 경우 자동차는 현재 위치에 멈춘다.

# 2단계 - 우승 자동차 구하기
## 기능 요구사항
### 자동차 참여 기능
- n대의 자동차가 게임에 참여할 수 있어야 함
- 자동차는 고유한 이름을 가진다.

### 게임 진행 과정
- 주어진 횟수 동안 게임이 진행됨
- 매 라운드마다 모든 자동차는 전진하거나 정지함
Comment on lines +19 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위와 같은 맥락으로 횟수로 0, -1이 들어가면 어떤 결과가 나올까요?
미션 요구사항에 명시된 조건 외에도 제공하는 기능에 대한 예외 케이스들을 추가로 고려해보시면 좋을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

명시된 조건 이외에 것은 일부러 생략을 했었는데요, 신경 써야할 부분인 것 같습니다.


### 우승자 선별 과정
- 게임 완료 후, 가장 높은 점수를 가진 자동차가 우승
- 공동 우승자 선별 가능
- 가장 position의 값이 큰 자동차가 우승

# 3단계 - 게임 실행
## 기능 요구사항
### 자동차 이름
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
- 게임 내에서 같은 차량의 이름을 갖는 경우 Exception 발생
- 앞 뒤 빈 공백은 제거해야 한다.

### 반복 횟수
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 사용자의 입력을 통해 상호작용한다. 그렇기 때문에 예외 처리가 필요하다 양수인 정수만 가능.

### 우승자 판별
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
- 우승자는 findWinners() 라는 메서드로 판별한다.

### 전진 기준
- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.
- 1단계 요구사항과 동일함.
15 changes: 15 additions & 0 deletions src/main/java/racingcar/RacingCarMain.java
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();
}
}
59 changes: 59 additions & 0 deletions src/main/java/racingcar/controller/RacingController.java
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());
}
}
31 changes: 31 additions & 0 deletions src/main/java/racingcar/domain/Car.java
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();
}
}
}
44 changes: 44 additions & 0 deletions src/main/java/racingcar/domain/Cars.java
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);
}
}
}
28 changes: 28 additions & 0 deletions src/main/java/racingcar/domain/RacingGame.java
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();
}
}
29 changes: 29 additions & 0 deletions src/main/java/racingcar/domain/vo/Name.java
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;
}
}
30 changes: 30 additions & 0 deletions src/main/java/racingcar/domain/vo/Position.java
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;
}
}
23 changes: 23 additions & 0 deletions src/main/java/racingcar/domain/vo/Repetition.java
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;
}
}
11 changes: 11 additions & 0 deletions src/main/java/racingcar/domain/vo/Winners.java
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();
}
}
27 changes: 27 additions & 0 deletions src/main/java/racingcar/exception/ApplicationError.java
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;
}
}
7 changes: 7 additions & 0 deletions src/main/java/racingcar/exception/ApplicationException.java
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());
}
}
29 changes: 29 additions & 0 deletions src/main/java/racingcar/ui/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package racingcar.ui;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

패키지 이름을 ui로 지어주신 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The 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();
}
}
Loading