diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index b9ed0456a3..4fa223893a 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,10 @@ package racingcar; public class Application { + public static void main(String[] args) { - // TODO 구현 진행 + Game game = Game.getInstance(); + game.play(); + game.end(); } } diff --git a/src/main/java/racingcar/Car.java b/src/main/java/racingcar/Car.java index ab3df94921..9e4d03b74e 100644 --- a/src/main/java/racingcar/Car.java +++ b/src/main/java/racingcar/Car.java @@ -1,12 +1,42 @@ package racingcar; +import java.util.Objects; + public class Car { - private final String name; - private int position = 0; + + private final CarName name; + + private Position position; public Car(String name) { - this.name = name; + this.name = CarName.from(name); + this.position = new Position(0); + } + + public void moveIfPowerEnough(Power power) { + if (power.isEnough()) { + position = position.proceed(); + } + } + + public String getName() { + return name.getValue(); + } + + public int getPosition() { + return position.getValue(); } - // 추가 기능 구현 + @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); + } } diff --git a/src/main/java/racingcar/CarName.java b/src/main/java/racingcar/CarName.java new file mode 100644 index 0000000000..4a444f19d0 --- /dev/null +++ b/src/main/java/racingcar/CarName.java @@ -0,0 +1,40 @@ +package racingcar; + +import java.util.Objects; + +public class CarName { + + private final String value; + + private CarName(String value) { + this.value = value; + } + + public static CarName from(String name) { + validate(name); + return new CarName(name); + } + + private static void validate(String name) { + if (name.isEmpty() || name.length() > 5) { + throw new IllegalArgumentException("이름은 5자 이하만 가능합니다."); + } + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CarName carName = (CarName) o; + return Objects.equals(value, carName.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/src/main/java/racingcar/Cars.java b/src/main/java/racingcar/Cars.java new file mode 100644 index 0000000000..c15cd51563 --- /dev/null +++ b/src/main/java/racingcar/Cars.java @@ -0,0 +1,47 @@ +package racingcar; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class Cars { + + private final List cars; + + public Cars(List cars) { + this.cars = cars; + } + + public void race() { + for (Car car : cars) { + int value = Randoms.pickNumberInRange(1, 9); + Power power = new Power(value); + car.moveIfPowerEnough(power); + } + } + + public Map fetchCurrentPositions() { + Map currentPositions = new LinkedHashMap<>(); + + for (Car car : cars) { + currentPositions.put(car.getName(), car.getPosition()); + } + + return currentPositions; + } + + public List fetchWinners() { + int maxPosition = cars.stream() + .mapToInt(Car::getPosition) + .max() + .orElse(0); + + return cars.stream() + .filter(car -> car.getPosition() == maxPosition) + .map(Car::getName) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/racingcar/CarsMaker.java b/src/main/java/racingcar/CarsMaker.java new file mode 100644 index 0000000000..263b431ad2 --- /dev/null +++ b/src/main/java/racingcar/CarsMaker.java @@ -0,0 +1,35 @@ +package racingcar; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class CarsMaker { + + public static final String DELIMITER = ","; + + private CarsMaker() { + } + + public static Cars make(String input) { + String[] names = input.split(DELIMITER); + validateDuplication(names); + + List cars = Arrays.stream(names) + .map(String::trim) + .map(Car::new) + .collect(Collectors.toList()); + + return new Cars(cars); + } + + private static void validateDuplication(String[] names) { + long count = Arrays.stream(names) + .distinct() + .count(); + + if (count != names.length) { + throw new IllegalArgumentException("이름이 중복되었습니다."); + } + } +} diff --git a/src/main/java/racingcar/Game.java b/src/main/java/racingcar/Game.java new file mode 100644 index 0000000000..45a565abc5 --- /dev/null +++ b/src/main/java/racingcar/Game.java @@ -0,0 +1,85 @@ +package racingcar; + +import racingcar.view.InputView; +import racingcar.view.OutputView; + +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +public class Game { + + private final Cars cars; + private final int number; + + private boolean ongoing; + + public Game(Cars cars, int number) { + this.cars = cars; + this.number = number; + } + + public static Game getInstance() { + return repeat(Game::makeGame); + } + + public static Game makeGame() { + String names = InputView.readNames(); + Cars cars = CarsMaker.make(names); + + int number = InputView.readNumber(); + + return new Game(cars, number); + } + + public void play() { + repeat(this::race); + } + + private void race() { + if (ongoing) { + throw new IllegalStateException("게임이 실행중입니다."); + } + + ongoing = true; + + for (int i = 0; i < number; i++) { + cars.race(); + Map currentPositions = cars.fetchCurrentPositions(); + OutputView.printCurrentPositions(currentPositions); + } + } + + public void end() { + repeat(this::quit); + } + + private void quit() { + if (!ongoing) { + throw new IllegalStateException("게임이 실행되지 않았습니다."); + } + + ongoing = false; + + List winners = cars.fetchWinners(); + OutputView.printWinner(winners); + } + + private static T repeat(Supplier target) { + try { + return target.get(); + } catch (IllegalArgumentException e) { + OutputView.printErrorMsg(e); + return repeat(target); + } + } + + private void repeat(Runnable target) { + try { + target.run(); + } catch (IllegalArgumentException e) { + OutputView.printErrorMsg(e); + repeat(target); + } + } +} diff --git a/src/main/java/racingcar/Position.java b/src/main/java/racingcar/Position.java new file mode 100644 index 0000000000..d9cf63bed3 --- /dev/null +++ b/src/main/java/racingcar/Position.java @@ -0,0 +1,35 @@ +package racingcar; + +import java.util.Objects; + +public class Position { + + public static final int DISTANCE_UNIT = 1; + + private final int value; + + public Position(int value) { + this.value = value; + } + + public Position proceed() { + return new Position(value + DISTANCE_UNIT); + } + + public int getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Position position = (Position) o; + return value == position.value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/src/main/java/racingcar/Power.java b/src/main/java/racingcar/Power.java new file mode 100644 index 0000000000..ed1699e9b2 --- /dev/null +++ b/src/main/java/racingcar/Power.java @@ -0,0 +1,16 @@ +package racingcar; + +public class Power { + + private static final int THRESHOLD = 4; + + private final int value; + + public Power(int value) { + this.value = value; + } + + public boolean isEnough() { + return value >= THRESHOLD; + } +} diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 0000000000..da7602e575 --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,56 @@ +package racingcar.view; + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + + private InputView() { + } + + public static String readNames() { + try { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + String input = Console.readLine(); + return validateNames(input); + } catch (IllegalArgumentException e) { + OutputView.printErrorMsg(e); + return readNames(); + } + } + + private static String validateNames(String input) { + if (input.contains(",")) { + return input; + } + + throw new IllegalArgumentException("이름은 쉼표로 구분되어야 합니다."); + } + + public static int readNumber() { + try { + System.out.println("시도할 횟수는 몇회인가요?"); + String input = Console.readLine(); + return validateNumber(input); + } catch (IllegalArgumentException e) { + OutputView.printErrorMsg(e); + return readNumber(); + } + } + + private static int validateNumber(String input) { + try { + int number = Integer.parseInt(input); + return validatePositive(number); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("양의 정수만 입력 가능합니다."); + } + } + + private static int validatePositive(int number) { + if (number <= 0) { + throw new IllegalArgumentException("양의 정수만 입력 가능합니다."); + } + + return number; + } +} diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 0000000000..e15d621faa --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -0,0 +1,33 @@ +package racingcar.view; + +import java.util.List; +import java.util.Map; + +public class OutputView { + + private static final String ERROR_PREFIX = "[ERROR]"; + private static final String BLANK = " "; + + public static void printErrorMsg(Exception e) { + System.out.println(ERROR_PREFIX + BLANK + e.getMessage()); + } + + public static void printResultNotification() { + System.out.println(); + System.out.println("실행 결과"); + } + + public static void printCurrentPositions(Map currentPositions) { + currentPositions.forEach((key, value) -> { + StringBuilder positionBar = new StringBuilder(); + for (int i = 0; i < value; i++) { + positionBar.append("-"); + } + System.out.println(key + " : " + positionBar); + }); + } + + public static void printWinner(List winners) { + System.out.println("최종 우승자 : " + String.join(", ", winners)); + } +}