Skip to content
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

feature(step4): 자동차 경주(우승자) 기능 구현 #6056

Merged
merged 14 commits into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,26 @@
- [x] 입력 횟수가 숫자가 아닌 경우 예외를 발생시킨다.
- [x] 자동차 댓수가 숫자가 아닌 경우 예외를 발생시킨다.
- [x] 음수를 입력한 경우 예외를 발생시킨다.

## 🚀 4단계 - 자동차 경주(우승자)
- 각 자동차에 이름을 부여할 수 있다. 자동차 이름은 5자를 초과할 수 없다.
- 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분한다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한명 이상일 수 있다.

### 입력 값 검증
- [x] 이름은 1자 이상 5자 이하이다.
- [x] 이름이 비어있거나 공백일 경우 예외를 발생시킨다.
- [x] 이름이 6자 이상일 경우 예외를 발생시킨다.

### 자동차 객체
- [x] 자동차의 이름을 부여한다.
- [x] 자동차 이름은 쉼표를 기준으로 구분한다.
- [x] 자동차 이름이 1자이상 5자이하인지 검증한다.

### 비즈니스 로직
- [x] 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- [x] 자동차 경주가 끝난 후 쉼표로 구분하여 우승자를 출력한다.

### 결과 출력
- [x] "a, b가 최종 우승했습니다."로 출력한다.
20 changes: 0 additions & 20 deletions src/main/java/Main.java

This file was deleted.

47 changes: 47 additions & 0 deletions src/main/java/racingcar/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package racingcar;

import racingcar.model.Car;
import racingcar.model.RacingGame;
import racingcar.util.InputValidator;
import racingcar.view.InputView;
import racingcar.view.OutputView;

import java.util.List;

import static racingcar.message.ErrorMessage.INVALID_NAME;
import static racingcar.message.Message.*;

public class Main {
public static void main(String[] args) {

final InputView inputView = new InputView();

final List<String> names = getValidCarNames(inputView);
final int attemptCount = getValidAttemptCount(inputView);

final RacingGame racingGame = RacingGame.createRacingGame(names);

final RacingGame finalResult = racingGame.race(attemptCount);

final List<Car> winners = finalResult.getWinners();
OutputView.printWinners(winners);
}

private static List<String> getValidCarNames(InputView inputView) {
while (true) {
try {
inputView.printMessage(CAR_NAME_MESSAGE);
return InputValidator.validateNames(inputView.readInput());

} catch (IllegalArgumentException e) {
OutputView.print(INVALID_NAME);
}
}
}

private static int getValidAttemptCount(InputView inputView) {
inputView.printMessage(ATTEMPT_COUNT_MESSAGE);

return InputValidator.validatePositiveNumber(inputView.readInput());
}
}
3 changes: 3 additions & 0 deletions src/main/java/racingcar/message/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package racingcar.message;

public class ErrorMessage {

public static final String INVALID_NUMBER = "숫자를 입력해야 합니다.";
public static final String POSITIVE_NUMBER = "양수를 입력해야 합니다.";
public static final String INVALID_NAME = "닉네임은 1자 이상 5자 이하여야 합니다. 다시 입력해주세요.";
public static final String INVALID_CAR_NAME_LENGTH = "자동차 이름은 5자 이하만 가능합니다.";

private ErrorMessage() {
}
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/racingcar/message/Message.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package racingcar.message;

public class Message {

public static final String RESULT_MESSAGE = "실행 결과";
public static final String CAR_COUNT_MESSAGE = "자동차 대수는 몇 대 인가요?";
public static final String ATTEMPT_COUNT_MESSAGE = "시도할 회수는 몇 회 인가요?";
public static final String CAR_NAME_MESSAGE = "경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분).";
public static final String WINNING_MESSAGE = "가 최종 우승했습니다.";

private Message() {
}
Expand Down
34 changes: 24 additions & 10 deletions src/main/java/racingcar/model/Car.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,48 @@
package racingcar.model;

import static racingcar.view.OutputView.NAME_POSITION_SEPERATOR;
Copy link
Member

Choose a reason for hiding this comment

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

MVC 패턴을 의식하셔서 model, view 패키지를 나누셨을거라 생각되는데요
이미 OutputView에서 Car 모델을 참조하고 있기에 model 패키지와 view 패키지는 순환참조하게 되었네요.
다음 단계의 주제이니 의존성 방향을 정리해 보시면 좋을 것 같아요.


public class Car {
public final class Car {

private final CarName name;
private final Position position;

private static final int MOVE_CONDITION_NUMBER = 4;

public Car() {
this.position = new Position();
public Car(CarName name) {
this(name, new Position(0));
}

public Car(int initialPosition) {
this.position = new Position(initialPosition);
public Car(CarName name, Position initialPosition) {
this.name = name;
this.position = initialPosition;
}

public void move(int randomValue) {
public Car move(int randomValue) {
if (canMove(randomValue)) {
position.incrementPosition();
return new Car(this.name, this.position.incrementPosition());
}
return this;
}

private static boolean canMove(int randomValue) {
private boolean canMove(int randomValue) {
return randomValue >= MOVE_CONDITION_NUMBER;
}

public boolean isAtPosition(Position other) {
return position.isSameAs(other);
}

public void printPosition() {
System.out.println(position);
public CarName getName() {
return name;
}

public int getPosition() {
return position.getPosition();
}

@Override
public String toString() {
return name + NAME_POSITION_SEPERATOR + position;
}
}
14 changes: 0 additions & 14 deletions src/main/java/racingcar/model/CarGenerator.java

This file was deleted.

48 changes: 48 additions & 0 deletions src/main/java/racingcar/model/CarName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package racingcar.model;

import static racingcar.message.ErrorMessage.INVALID_CAR_NAME_LENGTH;
import static racingcar.message.ErrorMessage.INVALID_NAME;

public class CarName {

private final String name;
Comment on lines +6 to +8
Copy link
Member

Choose a reason for hiding this comment

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

객체 분리 👍


private static final int MAXIMUM_NAME_LENGTH = 5;

public CarName(String value) {
validate(value);
this.name = value;
}

private void validate(String value) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException(INVALID_NAME);
}

if (value.length() > MAXIMUM_NAME_LENGTH) {
throw new IllegalArgumentException(INVALID_CAR_NAME_LENGTH);
}
}

public String getName() {
return name;
}

@Override
public String toString() {
return name;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CarName)) return false;
CarName carName = (CarName) o;
return name.equals(carName.name);
}

@Override
public int hashCode() {
return name.hashCode();
}
}
17 changes: 11 additions & 6 deletions src/main/java/racingcar/model/Position.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
package racingcar.model;

import static racingcar.view.OutputView.MOVE;

public class Position {

private int position;
private static final String DELIMITER = "-";
private final int position;

public Position() {
this.position = 0;
this(0);
}

public Position(int initialPosition) {
this.position = initialPosition;
}

public void incrementPosition() {
position += 1;
public Position incrementPosition() {
return new Position(this.position + 1);
Copy link
Member

Choose a reason for hiding this comment

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

불변 객체 💯

}

public boolean isSameAs(Position other) {
return this.position == other.position;
}

public int getPosition() {
return position;
}

@Override
public String toString() {
return DELIMITER.repeat(position);
return MOVE.repeat(position);
}
}
87 changes: 74 additions & 13 deletions src/main/java/racingcar/model/RacingGame.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,89 @@
import racingcar.view.OutputView;

import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Collectors;

public class RacingGame {

List<Car> cars;
private final List<Car> cars;

public RacingGame(int carCount) {
this.cars = CarGenerator.createCars(carCount);
private RacingGame(List<Car> cars) {
this.cars = cars;
}

public void race(int attemptCount) {
public static RacingGame createRacingGame(List<String> names) {
List<Car> cars = createCars(names);

return new RacingGame(cars);
}

public static RacingGame ofCars(List<Car> cars) {
return new RacingGame(cars);
}

private static List<Car> createCars(List<String> names) {
return names.stream()
.map(name -> new Car(new CarName(name)))
.collect(Collectors.toList());
}

public boolean hasCarCount(int expectedCount) {
return cars.size() == expectedCount;
}

public boolean areAllCarsAtPosition(Position position) {
return cars.stream().allMatch(car -> car.isAtPosition(position));
}

public RacingGame race(int attemptCount) {
OutputView.print(Message.RESULT_MESSAGE);

IntStream.range(0, attemptCount)
.forEach(i -> {
moveCars();
OutputView.print("");
});
List<Car> updatedCars = getCars(attemptCount);

return new RacingGame(updatedCars);
}

private List<Car> getCars(int attemptCount) {
List<Car> updatedCars = cars;

updatedCars = executeRace(attemptCount, updatedCars);
return updatedCars;
}

private List<Car> executeRace(int attemptCount, List<Car> updatedCars) {
for (int i = 0; i < attemptCount; i++) {
updatedCars = move(updatedCars);
OutputView.print("");
}
return updatedCars;
}

private List<Car> move(List<Car> currentCars) {
List<Car> movedCars = moveCars(currentCars);

movedCars.forEach(System.out::println);

return movedCars;
}

private List<Car> moveCars(List<Car> currentCars) {
return currentCars.stream()
.map(car -> car.move(RandomNumberGenerator.generateNumber()))
.collect(Collectors.toList());
}

public List<Car> getWinners() {
int maxPosition = findMaxPosition();

return cars.stream()
.filter(car -> car.isAtPosition(new Position(maxPosition)))
.collect(Collectors.toList());
}

private void moveCars() {
cars.forEach(car -> car.move(RandomNumberGenerator.generateNumber()));
cars.forEach(Car::printPosition);
private int findMaxPosition() {
return cars.stream()
.mapToInt(Car::getPosition)
.max()
.orElse(0);
}
}
Loading