diff --git a/src/main/java/RaceController.java b/src/main/java/RaceController.java new file mode 100644 index 0000000..c997240 --- /dev/null +++ b/src/main/java/RaceController.java @@ -0,0 +1,42 @@ +import domain.Attempts; +import domain.RacingGame; +import dto.ResultDto; +import view.InputView; +import view.OutputView; + +import java.io.IOException; +import java.util.List; + +public class RaceController { + private static final InputView inputView = new InputView(System.in); + + public static void main(String[] args) { + final RacingGame racingGame = repeatUntilGetValidCarNames(); + final Attempts attempts = repeatUntilGetValidAttempts(); + + List raceProcess = racingGame.race(attempts); + + OutputView.printRaceProcess(raceProcess); + OutputView.printResult(racingGame.getWinners()); + } + + private static RacingGame repeatUntilGetValidCarNames() { + try { + final String[] carNames = inputView.readCarNames(); + return new RacingGame(carNames); + } catch (IOException | IllegalArgumentException e) { + OutputView.printExceptionMessage(e); + return repeatUntilGetValidCarNames(); + } + } + + private static Attempts repeatUntilGetValidAttempts() { + try { + final Integer numberOfAttempts = inputView.readNumberOfAttempts(); + return new Attempts(numberOfAttempts); + } catch (IOException | IllegalArgumentException e) { + OutputView.printExceptionMessage(e); + return repeatUntilGetValidAttempts(); + } + } +} \ No newline at end of file diff --git a/src/main/java/RacingMain.java b/src/main/java/RacingMain.java deleted file mode 100644 index 4394287..0000000 --- a/src/main/java/RacingMain.java +++ /dev/null @@ -1,7 +0,0 @@ -public class RacingMain { - - public static void main(String[] args) { - // TODO: MVC 패턴을 기반으로 자동차 경주 미션 구현해보기 - System.out.println("Hello, World!"); - } -} diff --git a/src/main/java/domain/Attempts.java b/src/main/java/domain/Attempts.java new file mode 100644 index 0000000..210508e --- /dev/null +++ b/src/main/java/domain/Attempts.java @@ -0,0 +1,44 @@ +package domain; + +import java.util.Objects; + +public class Attempts { + private static final int MIN_ATTEMPTS = 1; + private static final int MAX_ATTEMPTS = 10; + + private int numberOfAttempts; + + + public Attempts(final int numberOfAttempts) { + if (isNumberOfAttemptsOutOfRange(numberOfAttempts)) { + throw new IllegalArgumentException(MIN_ATTEMPTS + "부터 " + MAX_ATTEMPTS + " 이하의 숫자를 입력하세요.\n"); + } + + this.numberOfAttempts = numberOfAttempts; + } + + public boolean isEnd() { + return numberOfAttempts == 0; + } + + public void decrease() { + numberOfAttempts--; + } + + private boolean isNumberOfAttemptsOutOfRange(final int numberOfAttempts) { + return numberOfAttempts < MIN_ATTEMPTS || numberOfAttempts > MAX_ATTEMPTS; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Attempts attempts = (Attempts) o; + return numberOfAttempts == attempts.numberOfAttempts; + } + + @Override + public int hashCode() { + return Objects.hash(numberOfAttempts); + } +} \ No newline at end of file diff --git a/src/main/java/domain/Car.java b/src/main/java/domain/Car.java new file mode 100644 index 0000000..6d2a4a1 --- /dev/null +++ b/src/main/java/domain/Car.java @@ -0,0 +1,64 @@ +package domain; + +import java.util.Objects; + +public class Car { + private final String name; + private int position; + + public Car(final String name) { + validate(name); + position = 0; + this.name = name; + } + + Car(final String name, final int position) { + this(name); + this.position = position; + } + + public void move(final int number) { + if (number >= 4) { + position++; + } + } + + public boolean isPositionedAt(final int position) { + return this.position == position; + } + + public String getName() { + return name; + } + + public int getPosition() { + return position; + } + + private void validate(final String name) { + if (isNameLengthOutOfRange(name)) { + throw new IllegalArgumentException("1이상 5이하의 이름을 입력하세요.\n"); + } + } + + private boolean isNameLengthOutOfRange(final String name) { + return name.length() < 1 || name.length() > 5; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Car car = (Car) o; + return Objects.equals(name, car.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, position); + } +} \ No newline at end of file diff --git a/src/main/java/domain/CarGroup.java b/src/main/java/domain/CarGroup.java new file mode 100644 index 0000000..d56006d --- /dev/null +++ b/src/main/java/domain/CarGroup.java @@ -0,0 +1,56 @@ +package domain; + +import dto.CarDto; +import dto.ResultDto; +import utils.PowerGenerator; +import utils.RandomPowerGenerator; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class CarGroup { + private final List carGroup; + private final PowerGenerator powerGenerator; + + public CarGroup(final String[] carNames) { + this.carGroup = Arrays.stream(carNames) + .map(Car::new) + .collect(Collectors.toList()); + this.powerGenerator = new RandomPowerGenerator(); + } + + CarGroup(final String[] carNames, final PowerGenerator powerGenerator) { + this.carGroup = Arrays.stream(carNames) + .map(Car::new) + .collect(Collectors.toList()); + this.powerGenerator = powerGenerator; + } + + public ResultDto move() { + for (Car car : carGroup) { + car.move(powerGenerator.getNumber()); + } + return ResultDto.toDto(carGroup); + } + + public List getWinners() { + final int positionMax = getPositionMax(); + + if (positionMax == 0) { + return List.of(); + } + + return carGroup.stream() + .filter(car -> car.isPositionedAt(positionMax)) + .map(CarDto::toDto) + .toList(); + } + + private int getPositionMax() { + return carGroup.stream() + .mapToInt(Car::getPosition) + .max() + .orElse(0); + } +} \ No newline at end of file diff --git a/src/main/java/domain/RacingGame.java b/src/main/java/domain/RacingGame.java new file mode 100644 index 0000000..1a6748b --- /dev/null +++ b/src/main/java/domain/RacingGame.java @@ -0,0 +1,35 @@ +package domain; + +import dto.CarDto; +import dto.ResultDto; +import utils.PowerGenerator; + +import java.util.ArrayList; +import java.util.List; + +public class RacingGame { + private final CarGroup carGroup; + + public RacingGame(final String[] carNames) { + this.carGroup = new CarGroup(carNames); + } + + RacingGame(final String[] carNames, final PowerGenerator powerGenerator) { + this.carGroup = new CarGroup(carNames, powerGenerator); + } + + public List race(final Attempts attempts) { + List result = new ArrayList<>(); + + while (!attempts.isEnd()) { + result.add(carGroup.move()); + attempts.decrease(); + } + + return result; + } + + public List getWinners() { + return carGroup.getWinners(); + } +} \ No newline at end of file diff --git a/src/main/java/dto/CarDto.java b/src/main/java/dto/CarDto.java new file mode 100644 index 0000000..ec32e23 --- /dev/null +++ b/src/main/java/dto/CarDto.java @@ -0,0 +1,11 @@ +package dto; + +import domain.Car; + +public record CarDto(String name, int position) { + + public static CarDto toDto(final Car car) { + return new CarDto(car.getName(), car.getPosition()); + } + +} \ No newline at end of file diff --git a/src/main/java/dto/ResultDto.java b/src/main/java/dto/ResultDto.java new file mode 100644 index 0000000..800b398 --- /dev/null +++ b/src/main/java/dto/ResultDto.java @@ -0,0 +1,15 @@ +package dto; + +import domain.Car; + +import java.util.List; + +public record ResultDto(List carGroup) { + + public static ResultDto toDto(final List carGroup) { + return new ResultDto(carGroup.stream() + .map(CarDto::toDto) + .toList()); + } + +} \ No newline at end of file diff --git a/src/main/java/utils/PowerGenerator.java b/src/main/java/utils/PowerGenerator.java new file mode 100644 index 0000000..dc94b89 --- /dev/null +++ b/src/main/java/utils/PowerGenerator.java @@ -0,0 +1,5 @@ +package utils; + +public interface PowerGenerator { + int getNumber(); +} \ No newline at end of file diff --git a/src/main/java/utils/RandomPowerGenerator.java b/src/main/java/utils/RandomPowerGenerator.java new file mode 100644 index 0000000..cc4f5fc --- /dev/null +++ b/src/main/java/utils/RandomPowerGenerator.java @@ -0,0 +1,11 @@ +package utils; + +import java.util.Random; + +public class RandomPowerGenerator implements PowerGenerator { + private static final Random random = new Random(); + + public int getNumber() { + return random.nextInt(10); + } +} \ No newline at end of file diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 0000000..5d3fba3 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,55 @@ +package view; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.regex.Pattern; + +public class InputView { + private static final String CAR_NAME_DELIMITER = ","; + private final BufferedReader reader; + + public InputView(final InputStream inputStream) { + this.reader = new BufferedReader(new InputStreamReader(inputStream)); + } + + public String[] readCarNames() throws IOException { + System.out.println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)."); + final String userInput = removeBlank(reader.readLine()); + + validateCarNamesFormat(userInput); + + return userInput.split(CAR_NAME_DELIMITER); + } + + public Integer readNumberOfAttempts() throws IOException { + System.out.println("시도할 회수는 몇회인가요?"); + final String userInput = removeBlank(reader.readLine()); + + validateNumberOfAttempts(userInput); + + return Integer.parseInt(userInput); + } + + private void validateCarNamesFormat(final String names) { + if (!Pattern.matches("^[A-z0-9,]+$", names)) { + throw new IllegalArgumentException("형식에 맞게 다시 입력하세요.\n"); + } + } + + private void validateNumberOfAttempts(final String text) { + try { + Integer.parseInt(text); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("1부터 10 이하의 숫자를 입력하세요.\n"); + } + } + + private String removeBlank(final String text) { + if (text == null) { + return null; + } + return text.replaceAll(" ", ""); + } +} \ No newline at end of file diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 0000000..91dc90e --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,45 @@ +package view; + +import dto.CarDto; +import dto.ResultDto; + +import java.util.List; +import java.util.stream.Collectors; + +public class OutputView { + private static final String CAR_POSITION_MARK = "-"; + private static final String WINNERS_NAME_DELIMITER = ", "; + + public static void printRaceProcess(final List raceProcess) { + System.out.println("\n실행 결과"); + raceProcess.forEach(OutputView::printCarsPositionSingleAttempt); + } + + public static void printResult(final List winners) { + if (winners.size() == 0) { + System.out.println("우승자가 없습니다."); + return; + } + + final List winnerNames = winners.stream() + .map(CarDto::name) + .collect(Collectors.toList()); + + final String result = String.join(WINNERS_NAME_DELIMITER, winnerNames).concat("가 최종 우승했습니다."); + System.out.println(result); + } + + public static void printExceptionMessage(final Exception ex) { + System.out.println(ex.getMessage()); + } + + private static void printCarsPositionSingleAttempt(final ResultDto singleAttemptResult) { + singleAttemptResult.carGroup().forEach(carDto -> { + String carName = carDto.name(); + int position = carDto.position(); + System.out.println(carName + " : " + CAR_POSITION_MARK.repeat(position)); + } + ); + System.out.println(); + } +} \ No newline at end of file