Skip to content

김나현 - 자동차 경주 게임 #7

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 15 commits into
base: nahyun0121
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package racingcar;

import racingcar.controller.RacingController;

public class Application {
public static void main(String[] args) {
// TODO 구현 진행
RacingController racingController = new RacingController();
racingController.run();
}
}
12 changes: 0 additions & 12 deletions src/main/java/racingcar/Car.java

This file was deleted.

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

import racingcar.controller.util.ExceptionHandler;
import racingcar.dto.CarStatusDTO;
import racingcar.model.RacingGame;
import racingcar.model.RandomNumberGenerator;
import racingcar.view.InputView;
import racingcar.view.OutputView;

import java.util.List;

public class RacingController {
private final InputView inputView = new InputView();
private final OutputView outputView = new OutputView();
private final RacingGame racingGame = new RacingGame(new RandomNumberGenerator());

public void run() {
try {
repeatUntilGetLegalAnswer(this::enrollCarToRace);
repeatUntilGetLegalAnswer(this::moveCarsByCount);
showWinners();
} catch (Exception e) {
e.printStackTrace();
outputView.printErrorMessage(e.getMessage());
}
}

private void repeatUntilGetLegalAnswer(Runnable runnable) {
ExceptionHandler.retryForIllegalArgument(runnable, outputView::printErrorMessage);
}

private void enrollCarToRace() {
List<String> carNames = inputView.inputCarNames();
racingGame.enrollCars(carNames);
}

private void moveCarsByCount() {
int moveCount = inputView.inputMoveCount();
List<CarStatusDTO> carStatuses = racingGame.repeatMovingCars(moveCount); // moveCount만큼 반복 이동
outputView.printGameResult(carStatuses);
}

private void showWinners() {
List<String> winnerNames = racingGame.findWinners();
outputView.printWinners(winnerNames);
}
}
19 changes: 19 additions & 0 deletions src/main/java/racingcar/controller/util/ExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package racingcar.controller.util;

import java.util.function.Consumer;

public class ExceptionHandler {
private ExceptionHandler() {
}

public static void retryForIllegalArgument(Runnable runnable, Consumer<String> exceptionMessageHandling) {
while (true) {
try {
runnable.run();
return;
} catch (IllegalArgumentException e) {
exceptionMessageHandling.accept(e.getMessage());
}
}
}
}
27 changes: 27 additions & 0 deletions src/main/java/racingcar/dto/CarDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package racingcar.dto;

public class CarDTO {
private final String name;
private final int position;

public CarDTO(String name, int position) {
this.name = name;
this.position = position;
}

public String getName() {
return name;
}

public int getPosition() {
return position;
}

@Override
public String toString() {
return "CarDTO{" +
"name='" + name + '\'' +
", position=" + position +
'}';
}
}
15 changes: 15 additions & 0 deletions src/main/java/racingcar/dto/CarStatusDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package racingcar.dto;

import java.util.List;

public class CarStatusDTO {
private final List<CarDTO> cars;

public CarStatusDTO(List<CarDTO> cars) {
this.cars = cars;
}

public List<CarDTO> getCars() {
return cars;
}
}
6 changes: 6 additions & 0 deletions src/main/java/racingcar/model/NumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package racingcar.model;

@FunctionalInterface // 1개의 추상 메소드를 갖는 인터페이스
public interface NumberGenerator {
int make();
}
52 changes: 52 additions & 0 deletions src/main/java/racingcar/model/RacingGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package racingcar.model;

import racingcar.dto.CarDTO;
import racingcar.dto.CarStatusDTO;
import racingcar.model.domain.Car;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class RacingGame {
private final List<Car> racingCars = new ArrayList<>();
private final NumberGenerator numberGenerator;

public RacingGame(NumberGenerator numberGenerator) {
this.numberGenerator = numberGenerator;
}

public void enrollCars(List<String> carNames) {
carNames.forEach(name -> racingCars.add(new Car(name)));
}

public List<CarStatusDTO> repeatMovingCars(int moveCount) {
List<CarStatusDTO> carStatuses = new ArrayList<>();
for (int count = 0; count < moveCount; count++)
carStatuses.add(new CarStatusDTO(moveCars()));
return carStatuses;
}

public List<CarDTO> moveCars() {
racingCars.forEach(car -> car.move(numberGenerator.make()));
return racingCars.stream()
.map(Car::to)
.collect(Collectors.toList());
}

public List<String> findWinners() {
List<String> winners = new ArrayList<>();
for (Car car : racingCars) {
if (findAnyHeadCar().compareTo(car) == 0)
winners.add(car.getName());
}
return winners;
}

private Car findAnyHeadCar() {
List<Car> sortedCars = new ArrayList<>(racingCars);
Collections.sort(sortedCars); // Car 클래스의 compareTo()로 인해 position에 따라 많이 전진한 순대로 정렬
return sortedCars.get(0);
}
}
11 changes: 11 additions & 0 deletions src/main/java/racingcar/model/RandomNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package racingcar.model;

import camp.nextstep.edu.missionutils.Randoms;

public class RandomNumberGenerator implements NumberGenerator {
Copy link

@splguyjr splguyjr Mar 21, 2023

Choose a reason for hiding this comment

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

NumberGenerator 인터페이스에서 make()를 선언하고
RandomNumberGenerator에서 이를 받아 정의하는 형태로 되어있는데
이러한 형태가 가지는 장점이 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

지금 시점에서는 큰 장점이 없는 것 같지만, 다른 숫자생성기가 만들어져야 하는 상황과 같이 앞으로 다른 기능이 추가되어야 할 상황이 온다면, NumberGenerator 인터페이스를 통해 다중 상속이라든지 여러 숫자생성기 클래스에서 기본이 되는 틀이 된다든지 하는 점에서 이 인터페이스를 잘 활용할 수 있을 것 같습니다!

Copy link
Author

Choose a reason for hiding this comment

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

그리고 더 찾아보니 대규모 프로젝트 개발 시 일관되고 정형화된 개발을 위한 표준화가 가능하다는 장점도 있다고 합니다.

Choose a reason for hiding this comment

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

SOLID의 5가지 원칙중에서 OCP에 해당하는 것 같네요.
하나 배워갑니다!


@Override
public int make() {
return Randoms.pickNumberInRange(0, 9);
}
}
42 changes: 42 additions & 0 deletions src/main/java/racingcar/model/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package racingcar.model.domain;

import racingcar.dto.CarDTO;

public class Car implements Comparable<Car> {
private final String name;
private int position = 0;

public Car(String name) {
validateName(name);
this.name = name;
}

private void validateName(String name) {
if (name.length() > 5)
throw new IllegalArgumentException("자동차 이름이 다섯 글자를 초과합니다.");
}

public void move(int number) {
validateNumber(number);
if (number >= 4)
position++;
}

public void validateNumber(int number) {
if (number < 0 || number > 9)
throw new IllegalArgumentException("전진하는 조건은 0에서 9 사이의 숫자일 때입니다.");
}

public String getName() {
return name;
}

public CarDTO to() {
return new CarDTO(name, position);
}

@Override
public int compareTo(Car anotherCar) {
return anotherCar.position - this.position;
}
}
21 changes: 21 additions & 0 deletions src/main/java/racingcar/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package racingcar.view;

import camp.nextstep.edu.missionutils.Console;
import racingcar.view.utils.FormatParser;
import racingcar.view.utils.NumberParser;

import java.util.List;

public class InputView {
public List<String> inputCarNames() {
System.out.println("경주할 자동차 이름들을 입력하세요. (이름은 쉼표(,)로 구분함)");
String line = Console.readLine(); // 콘솔로부터 값 읽기
return FormatParser.split(line, ","); // ',' 단위로 자른 공백 제거한 입력한 자동차 이름들의 목록
}

public int inputMoveCount() {
System.out.println("몇 번 시도할지 입력하세요. (숫자)");
String line = Console.readLine();
return NumberParser.parseDigit(line); // 입력값을 정수로 변환
}
}
34 changes: 34 additions & 0 deletions src/main/java/racingcar/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package racingcar.view;

import racingcar.dto.CarDTO;
import racingcar.dto.CarStatusDTO;
import racingcar.view.utils.FormatParser;

import java.util.List;

public class OutputView {
public void printGameResult(List<CarStatusDTO> carStatuses) { // 경주 결과 자동차들 각각 상태 출력
System.out.println("실행 결과");
carStatuses.forEach(this::printCarStatuses);
}

private void printCarStatuses(CarStatusDTO carStatus) {
List<CarDTO> cars = carStatus.getCars();
cars.forEach(this::printCar);
System.out.println();
}

private void printCar(CarDTO carDTO) {
System.out.printf("%s : %s" + System.lineSeparator(), carDTO.getName(),
FormatParser.make(carDTO.getPosition(), "-")); // lineSeparator: 개행문자
}

public void printWinners(List<String> winnerNames) {
System.out.printf("최종 우승자 : %s" + System.lineSeparator(),
FormatParser.join(winnerNames, ", "));
}

public void printErrorMessage(String message) {
System.out.printf("[ERROR] %s" + System.lineSeparator(), message);
}
}
53 changes: 53 additions & 0 deletions src/main/java/racingcar/view/utils/FormatParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package racingcar.view.utils;

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

public class FormatParser {
private FormatParser() {
}

public static List<String> split(String line, String delimiter) {
validateWrongFormat(line);
String[] values = line.split(delimiter); // 문자열 배열
List<String> parsedValue = Arrays.stream(values)
.map(String::trim) // trim: 문자열 공백 제거
.collect(Collectors.toList());
validateEmpty(parsedValue);
return parsedValue;
}
private static void validateWrongFormat(String line) {
if (hasWrongFormat(line)) {
throw new IllegalArgumentException("입력값의 형식이 잘못되었습니다.");
}
}

private static boolean hasWrongFormat(String line) {
if (line.isEmpty()) {
return true;
}
return line.startsWith(",") || line.endsWith(",");
}

private static void validateEmpty(List<String> values) {
if (hasEmptyValue(values))
throw new IllegalArgumentException("입력값에 빈 문자열이 있습니다.");
}

private static boolean hasEmptyValue(List<String> values) {
return values.stream()
.anyMatch(String::isEmpty); // anyMatch(): 최소한 한 개의 요소가 주어진 조건에 만족하는지 조사하는 함수 & '::': 메소드 레퍼런스. 람다식을 더욱 간결하게 해준다.
}

public static String join(List<String> values, String delimiter) {
return String.join(delimiter, values);
}

public static String make(int count, String unit) {
String units = "";
for (int i = 0; i < count; i++)
units = units + unit.charAt(0);
return units;
}
}
21 changes: 21 additions & 0 deletions src/main/java/racingcar/view/utils/NumberParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package racingcar.view.utils;

public class NumberParser {
private NumberParser() {}

public static int parseDigit(String value) {
int number = parseInteger(value); // 입력값을 정수로 변환
if (number <= 0)
throw new IllegalArgumentException("입력값이 자연수가 아닙니다.");

return number;
}

public static int parseInteger(String value) {
try {
return Integer.parseInt(value); // 숫자형의 문자열을 인자 값으로 받으면 십진수의 Integer 형으로 변환해줌
} catch (NumberFormatException e) {
throw new IllegalArgumentException("입력값이 숫자가 아닙니다.");
}
}
}
Loading