diff --git a/README.md b/README.md index 491aece1..b981d675 100644 --- a/README.md +++ b/README.md @@ -1 +1,40 @@ -# java-racingcar-precourse \ No newline at end of file +# java-racingcar-precourse + +# 기능 요구 사항 +초간단 자동차 경주 게임을 구현한다. +- 주어진 횟수 동안 n대의 자동차는 1칸씩 전진 또는 멈출 수 있다. +- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. + - 단 중복을 허용하지 않는다. +- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. +- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. +- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. +- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. +- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다. +- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. +- Exception 이 아닌 IllegalArgumentException , IllegalStateException 등과 같은 명확한 유형을 처리한다 + +# 기능 요구 사항 +- 경주 시작 + - **자동차 이름 입력 받기** (이름은 쉼표 기준으로 구분) + - 경주 인원이 한 명일 때 + - IllegalStateException, [ERROR]로 시작하는 에러 메세지 출력, 다시 입력을 받는다. + - "경주는 두 명 이상일 때 할 수 있습니다." + - 경주 인원이 2명이상일 때 + - 이름 중복 검사, 이름 제한 검사 + - 이름이 중복값일 때, 자동차 이름이 5글자 이상일 떄 + - IllegalArgumentException, [ERROR]로 시작하는 에러 메세지 출력, 다시 입력을 받는다. + - "중복된 자동차의 이름이 있습니다." + - "자동차 이름은 5글자 이하만 가능합니다." + - 검사 통과했을 때 + - **시도 횟수 입력 받기** + - 숫자가 아닐 때, 2미만일 때 + - IllegalArgumentException, [ERROR]로 시작하는 에러 메세지 출력, 다시 입력을 받는다. + - "숫자만 입력할 수 있습니다." + - "정수만 입력할 수 있습니다." + - "2이상의 숫자만 입력할 수 있습니다." + - 검사 통과했을 때 + - 주사위 굴리기 + - 4미만일 때 + - doUnderNum(int diceNum); + - 4이상일 때 + - doAboveNum(int diceNum); diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 00000000..fdae3a29 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,12 @@ +import io.Input; +import io.Output; +import repository.CarRepository; +import service.Race; +import validate.Validator; + +public class Application { + public static void main(String[] args) { + Race race = new Race(new Input(), new Output(), new Validator(), new CarRepository()); + race.execute(); + } +} diff --git a/src/main/java/domain/Car.java b/src/main/java/domain/Car.java new file mode 100644 index 00000000..7519c037 --- /dev/null +++ b/src/main/java/domain/Car.java @@ -0,0 +1,31 @@ +package domain; + +import java.util.Random; + +public class Car { + private String name; + private int location = 0; + + public Car(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public int getLocation() { + return location; + } + + public void move(){ + if (getRandomNum() >= 4){ + location += 1; + } + } + + private int getRandomNum(){ + Random random = new Random(); + return random.nextInt(9); + } +} diff --git a/src/main/java/io/Input.java b/src/main/java/io/Input.java new file mode 100644 index 00000000..9378d2a9 --- /dev/null +++ b/src/main/java/io/Input.java @@ -0,0 +1,21 @@ +package io; + +import java.util.Scanner; + +public class Input { + private Scanner scanner = new Scanner(System.in); + + public String getCarName(){ + System.out.println("경주할 자동차 이름을 입력하세요. (이름은 쉼표(,) 기준으로 구분)"); + return scanner.nextLine(); + } + + public String getRaceCount(){ + System.out.println("시도할 횟수는 몇회인가요?"); + return scanner.nextLine(); + } + + public String deleteSpace(String raceInput){ + return raceInput.replace(" ", ""); + } +} diff --git a/src/main/java/io/Output.java b/src/main/java/io/Output.java new file mode 100644 index 00000000..b8af3fc5 --- /dev/null +++ b/src/main/java/io/Output.java @@ -0,0 +1,26 @@ +package io; + +import domain.Car; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class Output { + public void printRace(String carName, int progress){ + String progressBar = "-".repeat(progress); + System.out.println(carName + " : " + progressBar); + } + + public void printWinner(List winner){ + String winnerNames = winner.stream() + .map(Car::getName) + .collect(Collectors.joining(", ")); + + System.out.println("최종 우승자 : " + winnerNames); + } + + public void print(String any){ + System.out.println(any); + } +} + diff --git a/src/main/java/repository/CarRepository.java b/src/main/java/repository/CarRepository.java new file mode 100644 index 00000000..5c09b059 --- /dev/null +++ b/src/main/java/repository/CarRepository.java @@ -0,0 +1,22 @@ +package repository; + +import domain.Car; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class CarRepository { + private static final Map carList = new LinkedHashMap<>(); + + public List findAllCar(){ + return new ArrayList<>(carList.values()); + } + + public void saveCar(Car car){ + carList.put(car.getName(), car); + } + + public void findByName(){ + } +} diff --git a/src/main/java/service/Race.java b/src/main/java/service/Race.java new file mode 100644 index 00000000..b1f18ea3 --- /dev/null +++ b/src/main/java/service/Race.java @@ -0,0 +1,109 @@ +package service; + +import domain.Car; +import io.Input; +import io.Output; +import java.util.ArrayList; +import java.util.List; +import repository.CarRepository; +import validate.Validator; + +public class Race { + + private int raceNum; + private String raceInput; + private static final int CAR_NAME_NUM = 5; + private static final char INPUT_SYMBOL = ','; + private final Input input; + private final Output output; + private final Validator validator; + + private final CarRepository carRepository; + + public Race(Input input, Output output, Validator validator, CarRepository carRepository) { + this.input = input; + this.output = output; + this.validator = validator; + this.carRepository = carRepository; + } + + public void execute() { + getCarName(); + getRaceCount(); + + List carList = carRepository.findAllCar(); + + output.print("실행 결과"); + raceStart(carList); + + List winnerList = getWinner(); + output.printWinner(winnerList); + } + + private void getCarName(){ + while (true) { + try { + raceInput = input.getCarName(); + validator.validateRaceInput(input.deleteSpace(raceInput), INPUT_SYMBOL, CAR_NAME_NUM); + registCar(raceInput); + break; + } catch (IllegalArgumentException | IllegalStateException e) { + output.print(e.getMessage()); + } + } + } + + private void getRaceCount(){ + while (true) { + try { + String stringRaceNum = input.getRaceCount(); + validator.validateRaceCount(stringRaceNum); + raceNum = Integer.parseInt(stringRaceNum); + break; + } catch (IllegalArgumentException | IllegalStateException e) { + output.print(e.getMessage()); + } + } + } + public void registCar(String raceInput) { + String[] carList = raceInput.split(Character.toString(INPUT_SYMBOL)); + for (String stringCar : carList) { + Car car = new Car(stringCar); + carRepository.saveCar(car); + } + } + + public void raceStart(List carList) { + for (int i = 0; i < raceNum; i++) { + carList.stream().forEach(car -> { + car.move(); + output.printRace(car.getName(), car.getLocation()); + }); + output.print(""); + } + } + + public List getWinner() { + List winnerList = new ArrayList<>(); + List carList = carRepository.findAllCar(); + int maxLocation = findMaxLocation(); + for (Car car : carList) { + if (car.getLocation() == maxLocation) { + winnerList.add(car); + } + } + return winnerList; + } + + private int findMaxLocation() { + int maxLocation = 0; + List carList = carRepository.findAllCar(); + for (Car car : carList) { + int carLocation = car.getLocation(); + if (maxLocation < carLocation) { + maxLocation = carLocation; + } + } + return maxLocation; + } +} diff --git a/src/main/java/validate/Validator.java b/src/main/java/validate/Validator.java new file mode 100644 index 00000000..73c8a3c7 --- /dev/null +++ b/src/main/java/validate/Validator.java @@ -0,0 +1,61 @@ +package validate; + +import java.util.Arrays; + +public class Validator { + + public boolean validateRaceInput(String raceInput, char symbol, int carNameNum){ + validateCarCount(raceInput, symbol); + validateDuplicateCarName(raceInput, symbol); + validateUnderCarNum(raceInput, symbol, carNameNum); + return true; + } + private void validateCarCount(String raceInput, char symbol) { + long carCount = raceInput.chars() + .filter(letter -> letter == symbol) + .count(); + + if (carCount < 1){ + throw new IllegalStateException("[ERROR] 경주는 두 명 이상일 때 할 수 있습니다."); + } + } + + private void validateDuplicateCarName(String raceInput, char symbol) { + String[] carList = raceInput.split(Character.toString(symbol)); + long distinctCarCount = Arrays.stream(carList).distinct().count(); + long totalCarCount = carList.length; + if(distinctCarCount != totalCarCount){ + throw new IllegalArgumentException("[ERROR] 중복된 자동차의 이름이 있습니다."); + } + } + + private void validateUnderCarNum(String raceInput, char symbol, int carNameNum) { + String[] carList = raceInput.split(Character.toString(symbol)); + for (String carName : carList){ + if(carName.length() > carNameNum){ + throw new IllegalArgumentException("[ERROR] 자동차 이름은 5글자 이하만 가능합니다. 이름 초과 : " + carName); + } + } + } + + public boolean validateRaceCount(String raceNum) { + validateIsNum(raceNum); + validateUnderRaceNum(raceNum); + return true; + } + + private void validateIsNum(String raceNum) { + for (char c : raceNum.toCharArray()){ + if(!Character.isDigit(c)){ + throw new IllegalArgumentException("[ERROR] 숫자만 입력할 수 있습니다."); + } + } + } + + private void validateUnderRaceNum(String raceNum) { + int intRaceNum = Integer.parseInt(raceNum); + if (intRaceNum < 2){ + throw new IllegalArgumentException("[ERROR] 2이상의 숫자만 입력할 수 있습니다."); + } + } +} diff --git a/src/test/java/validate/ValidatorTest.java b/src/test/java/validate/ValidatorTest.java new file mode 100644 index 00000000..fe6259bb --- /dev/null +++ b/src/test/java/validate/ValidatorTest.java @@ -0,0 +1,73 @@ +package validate; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class ValidatorTest { + Validator validator = new Validator(); + char symbol = ','; + int carNameNum = 5; + @Test + void 경주_인원() { + //given + String raceInput = "a"; + //when + Throwable exception = assertThrows(IllegalStateException.class, () ->{ + validator.validateRaceInput(raceInput, symbol, carNameNum); + }); + //then + assertThat("[ERROR] 경주는 두 명 이상일 때 할 수 있습니다.").isEqualTo(exception.getMessage()); + } + + @Test + void 이름_중복_검사(){ + //given + String raceInput = "a,a,b"; + //when + Throwable exception = assertThrows(IllegalArgumentException.class, () ->{ + validator.validateRaceInput(raceInput, symbol, carNameNum); + }); + //then + assertThat("[ERROR] 중복된 자동차의 이름이 있습니다.").isEqualTo(exception.getMessage()); + } + + @Test + void 이름_글자수_검사(){ + //given + String raceInput = "aaaaa,bbbbbb"; + //when + Throwable exception = assertThrows(IllegalArgumentException.class, () ->{ + validator.validateRaceInput(raceInput, symbol, carNameNum); + }); + //then + assertThat("[ERROR] 자동차 이름은 5글자 이하만 가능합니다. 이름 초과 : " + "bbbbbb").isEqualTo(exception.getMessage()); + } + + @Test + void 시도_횟수_숫자() { + //given + String raceNum = "a"; + //when + Throwable exception = assertThrows(IllegalArgumentException.class, () ->{ + validator.validateRaceCount(raceNum); + }); + //then + assertThat("[ERROR] 숫자만 입력할 수 있습니다.").isEqualTo(exception.getMessage()); + } + + @Test + void 시도_횟수_2미만() { + //given + String raceNum = "1"; + //when + Throwable exception = assertThrows(IllegalArgumentException.class, () ->{ + validator.validateRaceCount(raceNum); + }); + //then + assertThat("[ERROR] 2이상의 숫자만 입력할 수 있습니다.").isEqualTo(exception.getMessage()); + } +} \ No newline at end of file