diff --git a/README-HS.md b/README-HS.md new file mode 100644 index 0000000..d67a7df --- /dev/null +++ b/README-HS.md @@ -0,0 +1,24 @@ +# 프로그래밍 요구사항 +1. depth 2까지 가능 +2. else, switch, 3항 연산자 사용 불가 +3. 함수 또는 메소드는 한 가지 일만 하도록 구현 +4. 모든 기능 단위 테스트 존재 필수 (System.out, System.in 로직 제외) +5. 모든 원시 값과 문자열 포장 +6. 일급 컬렉션 사용 + +--- + +# 구현 기능 +1. 게임 설명 출력 +2. 자동차 이름 입력 + 1. 이름이 5자 미만인지 검증 + 2. 자동차 이름이 쉼표로 구분되어 있는지 검증 +3. 시도 횟수 질문 출력 +4. 시도 횟수 입력 + 1. 숫자 입력 했는지 검증 +5. 게임 시작 (시도 횟수 만큼 반복) + 1. random 값 생성 (0부터 9사이) + 2. random 값이 4 이상일 경우 전진 + 3. 실행 결과 출력 +5. 게임 결과, 최종 우승자 출력 + \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f80bbf..d2880ba 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/RacingCarApplication.java b/src/main/java/RacingCarApplication.java new file mode 100644 index 0000000..04ea943 --- /dev/null +++ b/src/main/java/RacingCarApplication.java @@ -0,0 +1,17 @@ +import racingcar.RacingCarGame; +import racingcar.model.RacingCars; +import racingcar.scanner.RacingCarScanner; + +public class RacingCarApplication { + public static void main(String[] args) { + RacingCarScanner scanner = new RacingCarScanner(); + + RacingCars cars = new RacingCars(scanner.inputCarNames()); + + RacingCarGame game = new RacingCarGame(cars); + game.play(scanner.inputRepeat()); + game.winner(); + + + } +} diff --git a/src/main/java/racingcar/RacingCarGame.java b/src/main/java/racingcar/RacingCarGame.java new file mode 100644 index 0000000..075f848 --- /dev/null +++ b/src/main/java/racingcar/RacingCarGame.java @@ -0,0 +1,46 @@ +package racingcar; + +import racingcar.model.RacingCar; +import racingcar.model.RacingCars; +import racingcar.view.ResultView; + +import java.util.Random; + +public class RacingCarGame { + private static final int FORWARD_STANDARD = 4; + private static final int MAX_NUM = 9; + + private final RacingCars cars; + private final Random random = new Random(); + + public RacingCarGame(RacingCars cars) { + this.cars = cars; + } + + public void play(int repeatNum) { + for(int i=0; i= FORWARD_STANDARD){ + car.forward(); + } + } + + public void winner() { + ResultView.displayRacingCars(cars); + ResultView.printWinner(cars.getWinnerName()); + } +} + diff --git a/src/main/java/racingcar/model/RacingCar.java b/src/main/java/racingcar/model/RacingCar.java new file mode 100644 index 0000000..621f5a5 --- /dev/null +++ b/src/main/java/racingcar/model/RacingCar.java @@ -0,0 +1,27 @@ +package racingcar.model; + +public class RacingCar { + private String name; + private int location; + + public RacingCar(String name) { + this.name = name; + this.location = 0; + } + + public void forward() { + location++; + } + + public String getName(){ + return name; + } + + public int getLocation() { + return location; + } + + public String formatRacingCar() { + return name + " : " + "-".repeat(location); + } +} diff --git a/src/main/java/racingcar/model/RacingCars.java b/src/main/java/racingcar/model/RacingCars.java new file mode 100644 index 0000000..8efb2df --- /dev/null +++ b/src/main/java/racingcar/model/RacingCars.java @@ -0,0 +1,38 @@ +package racingcar.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class RacingCars { + private List cars; + + public RacingCars(String[] carNames){ + cars = new ArrayList<>(); + + for (String carName : carNames) { + RacingCar car = new RacingCar(carName); + cars.add(car); + } + } + + public List getCars() { + return cars; + } + + public String getWinnerName() { + int winnerLocation = cars.stream() + .mapToInt(RacingCar::getLocation) + .max() + .orElse(0); + + + return cars.stream() + .filter(car -> car.getLocation() == winnerLocation) + .map(RacingCar::getName) + .collect(Collectors.joining(", ")); + } + + + +} diff --git a/src/main/java/racingcar/scanner/RacingCarScanner.java b/src/main/java/racingcar/scanner/RacingCarScanner.java new file mode 100644 index 0000000..f370fb7 --- /dev/null +++ b/src/main/java/racingcar/scanner/RacingCarScanner.java @@ -0,0 +1,56 @@ +package racingcar.scanner; + +import racingcar.validation.InputValidator; +import racingcar.view.ErrorView; +import racingcar.view.InputView; + +import java.util.Scanner; + +public class RacingCarScanner { + private final Scanner scanner; + private final InputValidator validator; + + public RacingCarScanner(){ + this.scanner = new Scanner(System.in); + this.validator = new InputValidator(); + } + + public String[] inputCarNames(){ + while (true) { + InputView.inputCarName(); + String input = scanner.nextLine(); + + if (validator.isEmptyInput(input)) { + continue; + } + + String[] inputArr = splitComma(input); + if (validator.isValidCarName(inputArr)){ + return inputArr; + } + + ErrorView.invalidInputCarName(); + } + } + + + public int inputRepeat() { + while(true){ + InputView.inputRepeatNum(); + String repeatString = scanner.nextLine(); + + if (validator.isEmptyInput(repeatString)){ + continue; + } + + if (validator.isNumberInput(repeatString)){ + return Integer.parseInt(repeatString); + } + } + } + + + public String[] splitComma(String input) { + return input.replaceAll(" ", "").split(","); + } +} diff --git a/src/main/java/racingcar/validation/InputValidator.java b/src/main/java/racingcar/validation/InputValidator.java new file mode 100644 index 0000000..3e49a7a --- /dev/null +++ b/src/main/java/racingcar/validation/InputValidator.java @@ -0,0 +1,39 @@ +package racingcar.validation; + +import racingcar.view.ErrorView; + +import java.util.Arrays; + +public class InputValidator { + public static final int LIMIT_NAME_LENGTH = 5; + + public boolean isValidCarName(String[] inputArr) { + return !isEmptyArray(inputArr) && Arrays.stream(inputArr).allMatch(this::isLessThanFiveCharacters); + } + + public boolean isEmptyArray(String[] inputArr) { + return inputArr.length == 0; + } + + public boolean isLessThanFiveCharacters(String input){ + return input.length() < LIMIT_NAME_LENGTH; + } + + public boolean isEmptyInput(String input) { + if (input.isEmpty()){ + ErrorView.invalidInputEmpty(); + return true; + } + return false; + } + + + public boolean isNumberInput(String input) { + if (!input.matches("\\d+")) { + ErrorView.invalidInputRepeat(); + return false; + } + + return true; + } +} diff --git a/src/main/java/racingcar/view/ErrorView.java b/src/main/java/racingcar/view/ErrorView.java new file mode 100644 index 0000000..eea5c40 --- /dev/null +++ b/src/main/java/racingcar/view/ErrorView.java @@ -0,0 +1,19 @@ +package racingcar.view; + +public class ErrorView { + public static final String INVALID_INPUT_CAR_NAME_MESSAGE = "이름은 5자 이하로 입력 가능합니다. 다시 입력해주세요."; + public static final String INVALID_INPUT_EMPTY_MESSAGE = "공백은 입력할 수 없습니다. 다시 입력해주세요."; + public static final String INVALID_INPUT_REPEAT_MESSAGE = "시도 횟수는 숫자만 입력 가능합니다."; + + public static void invalidInputCarName(){ + System.out.println(INVALID_INPUT_CAR_NAME_MESSAGE); + } + + public static void invalidInputEmpty(){ + System.out.println(INVALID_INPUT_EMPTY_MESSAGE); + } + + public static void invalidInputRepeat(){ + System.out.println(INVALID_INPUT_REPEAT_MESSAGE); + } +} diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 0000000..6702243 --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,14 @@ +package racingcar.view; + +public class InputView { + public static final String INPUT_CAR_NAME_MESSAGE = "경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)."; + public static final String INPUT_REPEAT_NUM_MESSAGE = "시도할 회수는 몇회인가요?"; + + public static void inputCarName() { + System.out.println(INPUT_CAR_NAME_MESSAGE); + } + + public static void inputRepeatNum() { + System.out.println(INPUT_REPEAT_NUM_MESSAGE); + } +} diff --git a/src/main/java/racingcar/view/ResultView.java b/src/main/java/racingcar/view/ResultView.java new file mode 100644 index 0000000..7fe653b --- /dev/null +++ b/src/main/java/racingcar/view/ResultView.java @@ -0,0 +1,25 @@ +package racingcar.view; + +import racingcar.model.RacingCar; +import racingcar.model.RacingCars; + +public class ResultView { + public static void displayRacingCars(RacingCars cars){ + for (RacingCar car : cars.getCars()){ + displayRacingCar(car); + } + printNewLine(); + } + + public static void displayRacingCar(RacingCar car){ + System.out.println(car.formatRacingCar()); + } + + public static void printWinner(String winnerName){ + System.out.println(winnerName + "가 최종 우승했습니다."); + } + + public static void printNewLine(){ + System.out.println(); + } +} diff --git a/src/test/java/racingcar/RacingCarGameTest.java b/src/test/java/racingcar/RacingCarGameTest.java new file mode 100644 index 0000000..b11c522 --- /dev/null +++ b/src/test/java/racingcar/RacingCarGameTest.java @@ -0,0 +1,49 @@ +package racingcar; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import racingcar.model.RacingCar; +import racingcar.model.RacingCars; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RacingCarGameTest { + + private RacingCars cars; + + @BeforeEach + void setUp(){ + cars = new RacingCars(new String[]{"red", "blue"}); + } + + + @Test + @DisplayName("랜덤 값이 4 이상이여서 전진") + void raceMore4(){ + // given + RacingCarGame game = new RacingCarGame(cars); + RacingCar car1 = cars.getCars().get(0); + + // when + game.race(car1, 4); + + // then + assertEquals(car1.getLocation(), 1); + } + + @Test + @DisplayName("랜덤 값이 4 이하여서 전진 안함") + void raceLessThan4(){ + // given, when + RacingCarGame game = new RacingCarGame(cars); + RacingCar car1 = cars.getCars().get(0); + + // when + game.race(car1, 3); + + // then + assertEquals(car1.getLocation(), 0); + } + +} diff --git a/src/test/java/racingcar/model/RacingCarTest.java b/src/test/java/racingcar/model/RacingCarTest.java new file mode 100644 index 0000000..d2d16bc --- /dev/null +++ b/src/test/java/racingcar/model/RacingCarTest.java @@ -0,0 +1,24 @@ +package racingcar.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class RacingCarTest { + + @Test + @DisplayName("자동차 이름과 위치 문자열로 반환") + void formatRacingCar(){ + // given + RacingCar car = new RacingCar("red"); + car.forward(); + + // when + String response = car.formatRacingCar(); + + // then + assertEquals(response, "red : -"); + } + +} \ No newline at end of file diff --git a/src/test/java/racingcar/model/RacingCarsTest.java b/src/test/java/racingcar/model/RacingCarsTest.java new file mode 100644 index 0000000..a86d31a --- /dev/null +++ b/src/test/java/racingcar/model/RacingCarsTest.java @@ -0,0 +1,61 @@ +package racingcar.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RacingCarsTest { + private RacingCars cars; + + @BeforeEach + void setUp(){ + cars = new RacingCars(new String[]{"red", "blue"}); + } + + + @Test + @DisplayName("레이싱카 이름으로 생성자에서 리스트 객체 생성") + void racingCars(){ + // given, when + List response = cars.getCars(); + + // then + assertEquals(response.size(), 2); + assertEquals(response.get(0).getName(), "red"); + assertEquals(response.get(1).getName(), "blue"); + } + + + @Test + @DisplayName("우승자 이름 반환") + void getWinnerName(){ + // given + RacingCar car1 = cars.getCars().get(0); + car1.forward(); + + // when + String response = cars.getWinnerName(); + + // then + assertEquals(response, car1.getName()); + } + + @Test + @DisplayName("우승자 이름 반환 시 우승자 다수일 경우") + void getMultipleWinnerName(){ + RacingCar car1 = cars.getCars().get(0); + RacingCar car2 = cars.getCars().get(1); + car1.forward(); + car2.forward(); + + // when + String response = cars.getWinnerName(); + + // then + assertEquals(response, car1.getName() + ", " + car2.getName()); + + } +} diff --git a/src/test/java/racingcar/scanner/RacingCarScannerTest.java b/src/test/java/racingcar/scanner/RacingCarScannerTest.java new file mode 100644 index 0000000..b74176b --- /dev/null +++ b/src/test/java/racingcar/scanner/RacingCarScannerTest.java @@ -0,0 +1,55 @@ +package racingcar.scanner; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class RacingCarScannerTest { + + private RacingCarScanner scanner; + + @BeforeEach + void setUp(){ + scanner = new RacingCarScanner(); + } + + @Test + @DisplayName("자동차 이름 쉼표로 구분") + void splitComma(){ + // given, when + String[] response = scanner.splitComma("red,blue,green"); + + // then + assertArrayEquals(response, new String[]{"red", "blue", "green"}); + } + + @Test + @DisplayName("자동차 이름 쉼표로 구분 시 공백 포함되어 있을 경우") + void splitCommaContainsSpace(){ + // given, when + String[] response = scanner.splitComma("red, blue, green"); + + // then + assertArrayEquals(response, new String[]{"red", "blue", "green"}); + } + + + @Test + @DisplayName("자동차 이름 쉼표로 구분 시 쉼표가 없는 경우") + void splitCommaNotContainsComma(){ + // given, when + String[] response = scanner.splitComma("red@b"); + + // then + assertArrayEquals(response, new String[]{"red@b"}); + } + + + + + + + +} diff --git a/src/test/java/racingcar/validation/InputValidatorTest.java b/src/test/java/racingcar/validation/InputValidatorTest.java new file mode 100644 index 0000000..2071dbf --- /dev/null +++ b/src/test/java/racingcar/validation/InputValidatorTest.java @@ -0,0 +1,143 @@ +package racingcar.validation; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class InputValidatorTest { + + private InputValidator validator; + + @BeforeEach + void setUp(){ + validator = new InputValidator(); + } + + @Test + @DisplayName("자동차 이름을 담은 배열이 빈값으로 false 리턴") + void isValidCarName_EmptyArray(){ + // given, when + boolean response = validator.isValidCarName(new String[]{}); + + // then + assertFalse(response); + } + + @Test + @DisplayName("자동차 이름을 담은 배열이 빈값이 아니고 5자 미만으로 true 리턴") + void isValidCarName_NotEmptyArray(){ + // given, when + boolean response = validator.isValidCarName(new String[]{"red"}); + + // then + assertTrue(response); + } + + @Test + @DisplayName("자동차 이름들 중 5자 이상이 포함되어 있어서 false 리턴") + void isValidCarName_MoreFiveCharacters(){ + // given, when + boolean response = validator.isValidCarName(new String[]{"red", "purple", "green"}); + + // then + assertFalse(response); + } + + + @Test + @DisplayName("자동차 이름들 중 5자 미만으로만 구성되어 있어 true 리턴") + void isValidCarName_lessThanFiveCharacters(){ + // given, when + boolean response = validator.isValidCarName(new String[]{"red", "blue", "pink"}); + + // then + assertTrue(response); + } + + + @Test + @DisplayName("자동차 이름을 담은 배열이 빈값으로 true 리턴") + void isEmptyArray(){ + // given, when + boolean response = validator.isEmptyArray(new String[]{}); + + // then + assertTrue(response); + } + + @Test + @DisplayName("자동차 이름을 담은 배열이 빈값이 아니여서 false 리턴") + void isNotEmptyArray(){ + // given, when + boolean response = validator.isEmptyArray(new String[]{"red"}); + + // then + assertFalse(response); + } + + + @Test + @DisplayName("자동차 이름이 5자 이상인지 확인 후 true 리턴") + void isLessThanFiveCharactersTrue(){ + // given, when + boolean response = validator.isLessThanFiveCharacters("red"); + + // then + assertTrue(response); + } + + @Test + @DisplayName("자동차 이름이 5자 이상이여서 false 리턴") + void isLessThanFiveCharactersFalse(){ + // given, when + boolean response = validator.isLessThanFiveCharacters("purple"); + + // then + assertFalse(response); + } + + + @Test + @DisplayName("입력값이 빈값으로 true 반환") + void isEmptyInputTrue(){ + // given, when + boolean response = validator.isEmptyInput(""); + + // then + assertTrue(response); + } + + + @Test + @DisplayName("입력값이 빈값이 아니여서 false 반환") + void isEmptyInputFalse(){ + // given, when + boolean response = validator.isEmptyInput("red"); + + // then + assertFalse(response); + } + + @Test + @DisplayName("반복횟수 입력 시 숫자 문자열 입력으로 true 반환") + void isNumberInput(){ + // given, when + boolean response = validator.isNumberInput("3456"); + + // then + assertTrue(response); + } + + @Test + @DisplayName("반복횟수 입력 시 숫자 아닌 문자열 입력으로 false 반환") + void isNotNumberInput(){ + // given, when + boolean response = validator.isNumberInput("테스트"); + + // then + assertFalse(response); + } + +} \ No newline at end of file