diff --git a/README.md b/README.md index 91744e0bd0..2921ba240a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,84 @@ # java-racingcar 자동차 경주 게임 미션 저장소 + + +## 기능 요구사항 + +- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. +- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. +- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. +- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. +- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다. +- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. + + + +## 필요한 기능 + +1. 문자열을 입력받아 car 객체를 생성 + + - 문자열을 입력받는다. + + - 문자열을 나눈다. + + - car 객체들을 생성한다 + - 일급컬렉션에 담아준다. + +2. 반복회수를 입력받아 레이싱을 진행하고, 상태를 출력한다. + - 반복회수 숫자를 입력받는다. + - 반복회수마다 Car를 이동시킨다. + - Car는 랜덤 숫자를 생성하고, 그 숫자가 4 이상일 경우 한칸 전진한다. + - Car객체의 State를 출력한다. + +3. 우승자를 구하고 출력한다. + - Car의 일급 컬렉션은 모든 Car에 대해 최대 이동 거리를 구한다. + - 최대 이동거리에 속하는 Car 객체를 구한다. + - Car객체의 Name을 출력한다. + +## 가능한 예외 + +#### 1번케이스 + +- 쉼표로 구분되지 않는 경우. +- 구분된 문자열의 길이가 5를 초과하는 경우. +- Car 이름이 공백인 경우 + +#### 2번케이스 + +- 반복회수가 숫자가 아닌 경우. +- 반복회수가 0이거나 음수인 경우. + + + + + +# 문자열 계산기 + + + +## 요구사항 + +- 문자열을 계산하는 계산기 (사칙연산 우선순위는 무시한다.) +- if문을 사용하지 않고 구현 + + + +## 기능 + +- 문자열을 입력 받으면 `Space`를 기준으로 분리해준다. ex) `3 + 2 * 4 / 10` + - 적절한 입력이 아닐 경우 예외처리 후 재입력한다. +- 분리한 연산자와 숫자들을 이용해서 계산해준다. + - 연산자를 Map의 key로 연산은 Value로 구현해준다. + - 연산자와 숫자를 넣으면 map에서 해당 연산자(value)의 연산메소드를 이용해서 연산 결과를 반환해준다. + +- + +## 해결과정 + +- https://dublin-java.tistory.com/38 + + + ## 우아한테크코스 코드리뷰 -* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) \ No newline at end of file +* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) diff --git a/build.gradle b/build.gradle index 1b85b74cb3..f7caa79fc1 100644 --- a/build.gradle +++ b/build.gradle @@ -11,4 +11,6 @@ repositories { dependencies { testCompile('org.junit.jupiter:junit-jupiter:5.4.2') testCompile('org.assertj:assertj-core:3.11.1') + + testCompile('junit:junit:4.12') } \ No newline at end of file diff --git a/src/main/java/cal/Calculator.java b/src/main/java/cal/Calculator.java new file mode 100644 index 0000000000..e605350565 --- /dev/null +++ b/src/main/java/cal/Calculator.java @@ -0,0 +1,21 @@ +package cal; + + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; + +final public class Calculator { + private static Map> operators = new HashMap<>(); + + static { + operators.put("+", (num1, num2) -> num1 + num2); + operators.put("-", (num1, num2) -> num1 - num2); + operators.put("*", (num1, num2) -> num1 * num2); + operators.put("/", (num1, num2) -> num1 / num2); + } + + public static double calculate(String operator, double num1, double num2) { + return operators.get(operator).apply(num1, num2); + } +} diff --git a/src/main/java/cal/TextCalculator.java b/src/main/java/cal/TextCalculator.java new file mode 100644 index 0000000000..e0ccc5da05 --- /dev/null +++ b/src/main/java/cal/TextCalculator.java @@ -0,0 +1,38 @@ +package cal; + +final public class TextCalculator { + private final static String DEFAULT_DELIMITER = " "; + private final String text; + + public TextCalculator(final String text) { + this.text = text.trim(); + } + + public double calculate() { + String tokens[] = split(DEFAULT_DELIMITER); + double result = toDouble(tokens[0]); + + for (int i = 1; i < tokens.length; i += 2) { + String operator = tokens[i]; + double number = toDouble(tokens[i + 1]); + result = calculate(operator, result, number); + } + return result; + } + + private String[] split(String delimiter) { + return text.split(delimiter); + } + + private double calculate(String operator, double result, double number) { + return Calculator.calculate(operator, result, number); + } + + private double toDouble(String value) { + try { + return Double.parseDouble(value); + } catch (Exception e) { + throw new IllegalArgumentException("적절한 입력이 아닙니다."); + } + } +} diff --git a/src/main/java/cal/TextCalculatorMain.java b/src/main/java/cal/TextCalculatorMain.java new file mode 100644 index 0000000000..7b3e1da012 --- /dev/null +++ b/src/main/java/cal/TextCalculatorMain.java @@ -0,0 +1,11 @@ +package cal; + +import java.util.Scanner; + +public class TextCalculatorMain { + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + TextCalculator textCalculator = new TextCalculator(scanner.nextLine()); + System.out.println(textCalculator.calculate()); + } +} diff --git a/src/main/java/empty.txt b/src/main/java/empty.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/main/java/racing/Main.java b/src/main/java/racing/Main.java new file mode 100644 index 0000000000..3fe09f6be4 --- /dev/null +++ b/src/main/java/racing/Main.java @@ -0,0 +1,8 @@ +package racing; + +public class Main { + public static void main(String[] args) { + RacingGame racingGame = new RacingGame(); + racingGame.run(); + } +} diff --git a/src/main/java/racing/RacingGame.java b/src/main/java/racing/RacingGame.java new file mode 100644 index 0000000000..37f61667da --- /dev/null +++ b/src/main/java/racing/RacingGame.java @@ -0,0 +1,65 @@ +package racing; + +import racing.domain.Car; +import racing.domain.RacingCars; +import racing.domain.RepeatNumber; +import racing.view.ConsoleMessages; +import racing.view.InputView; +import racing.view.OutputView; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class RacingGame { + public void run() { + RacingCars racingCars = new RacingCars(generateCars()); + + startRace(racingCars, inputRepeatNumber()); + + OutputView.printWinners(racingCars.getWinners()); + } + + private List generateCars() { + try { + return getCarNames().stream() + .map(name -> new Car(name)) + .collect(Collectors.toList()); + } catch (Exception e) { + System.err.println(e.getMessage()); + return generateCars(); + } + } + + private List getCarNames() { + List splitNames = Arrays.asList(InputView.inputCarNames().split(",")); + + if (splitNames.isEmpty()){ + throw new IllegalArgumentException(ConsoleMessages.ERR_CAR_BLANK_NAME.getMessage()); + } + return splitNames; + } + + private static RepeatNumber inputRepeatNumber() { + return toRepeatNumber(InputView.inputRepeatNumber()); + } + + + private static RepeatNumber toRepeatNumber(String number) { + try { + return new RepeatNumber(number); + } catch (NumberFormatException e) { + System.err.println(ConsoleMessages.ERR_REPEAT_NUMBER.getMessage()); + } catch (IllegalArgumentException e) { + System.err.println(e.getMessage()); + } + return inputRepeatNumber(); + } + + private void startRace(RacingCars racingCars, RepeatNumber repeatNumber) { + for (int i = 0; i < repeatNumber.getNumber(); i++) { + racingCars.race(); + OutputView.printStatus(racingCars.getRaceStatus()); + } + } +} diff --git a/src/main/java/racing/domain/Car.java b/src/main/java/racing/domain/Car.java new file mode 100644 index 0000000000..8794b376bb --- /dev/null +++ b/src/main/java/racing/domain/Car.java @@ -0,0 +1,73 @@ +package racing.domain; + +import racing.view.ConsoleMessages; + +public class Car implements Comparable { + private static final int DEFAULT_DISTANCE = 1; + + private final String name; + private int distance; + + public Car(final String name) { + this(name, DEFAULT_DISTANCE); + } + + public Car(final String name, final int distance) { + this.name = validName(name); + this.distance = distance; + } + + private String validName(String name) { + String trimmedName = trimName(name); + + checkBlankName(trimmedName); + + checkNameLength(trimmedName); + + return trimmedName; + } + + private String trimName(String name) { + return name.trim(); + } + + private void checkBlankName(String trimmedName) { + if (trimmedName.isEmpty()) { + throw new IllegalArgumentException(ConsoleMessages.ERR_CAR_BLANK_NAME.getMessage()); + } + } + + private void checkNameLength(String trimmedName) { + if (trimmedName.length() > Rules.MAX_CAR_NAME) { + throw new IllegalArgumentException(ConsoleMessages.ERR_CAR_NAME.getMessage()); + } + } + + public int move(int number) { + if (isMove(number)) { + distance++; + } + return distance; + } + + private boolean isMove(int number) { + return number >= Rules.MIN_MOVABLE_NUMBER; + } + + public boolean isMatchDistance(Car car) { + return this.distance == car.distance; + } + + public String getName() { + return name; + } + + public int getDistance() { + return distance; + } + + @Override + public int compareTo(Car car) { + return this.distance - car.distance; + } +} diff --git a/src/main/java/racing/domain/RaceStatusDto.java b/src/main/java/racing/domain/RaceStatusDto.java new file mode 100644 index 0000000000..a135e7b3f9 --- /dev/null +++ b/src/main/java/racing/domain/RaceStatusDto.java @@ -0,0 +1,16 @@ +package racing.domain; + +import java.util.ArrayList; +import java.util.List; + +public class RaceStatusDto { + private final List cars; + + public RaceStatusDto(List cars) { + this.cars = new ArrayList<>(cars); + } + + public List getCars() { + return cars; + } +} diff --git a/src/main/java/racing/domain/RacingCars.java b/src/main/java/racing/domain/RacingCars.java new file mode 100644 index 0000000000..28ebebb177 --- /dev/null +++ b/src/main/java/racing/domain/RacingCars.java @@ -0,0 +1,30 @@ +package racing.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class RacingCars { + private final List cars; + + public RacingCars(List cars) { + this.cars = new ArrayList<>(cars); + } + + public void race() { + cars.forEach(car -> car.move(Rules.generateRandomNumber())); + } + + public RaceStatusDto getRaceStatus() { + return new RaceStatusDto(cars); + } + + public List getWinners() { + Car winnerCar = Collections.max(cars); + return cars.stream() + .filter(car -> car.isMatchDistance(winnerCar)) + .map(Car::getName) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/racing/domain/RepeatNumber.java b/src/main/java/racing/domain/RepeatNumber.java new file mode 100644 index 0000000000..f84b3f4f17 --- /dev/null +++ b/src/main/java/racing/domain/RepeatNumber.java @@ -0,0 +1,21 @@ +package racing.domain; + +import racing.view.ConsoleMessages; + +public class RepeatNumber { + private int number; + + public RepeatNumber(int number) { + if (number <= 0) + throw new IllegalArgumentException(ConsoleMessages.ERR_INVALID_REPEAT_NUMBER.getMessage()); + this.number = number; + } + + public RepeatNumber(String number) { + this(Integer.parseInt(number)); + } + + public int getNumber() { + return number; + } +} diff --git a/src/main/java/racing/domain/Rules.java b/src/main/java/racing/domain/Rules.java new file mode 100644 index 0000000000..4040d18afa --- /dev/null +++ b/src/main/java/racing/domain/Rules.java @@ -0,0 +1,14 @@ +package racing.domain; + +import java.util.Random; + +public class Rules { + public static final int MAX_CAR_NAME = 5; + public static final int MIN_MOVABLE_NUMBER = 4; + public static final int RANDOM_NUMBER_RANGE = 10; + + public static int generateRandomNumber() { + Random random = new Random(); + return random.nextInt(RANDOM_NUMBER_RANGE); + } +} diff --git a/src/main/java/racing/view/ConsoleMessages.java b/src/main/java/racing/view/ConsoleMessages.java new file mode 100644 index 0000000000..38c19ab2f0 --- /dev/null +++ b/src/main/java/racing/view/ConsoleMessages.java @@ -0,0 +1,23 @@ +package racing.view; + +public enum ConsoleMessages { + INPUT_CAR_NAME("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,)기준으로구분)"), + INPUT_REPEAT_NUMBER("시도할 횟수는 몇 회인가요?"), + ERR_REPEAT_NUMBER("반복 횟수는 숫자만 가능합니다."), + ERR_IDENTIFIER_NAME("이름은 쉼표를 기준으로 구분합니다."), + ERR_CAR_NAME("레이서의 이름은 5자 이하여야 합니다."), + ERR_CAR_BLANK_NAME("레이서의 이름은 적어도 하나의 공백이 아닌 문자가 있어야 합니다."), + ERR_INVALID_REPEAT_NUMBER("횟수는 1 이상이어야 합니다."), + OUTPUT_WINNER_NAME("%s가 최종 우승했습니다."); + + private String message; + + private ConsoleMessages(String message){ + this.message = message; + } + + public String getMessage(){ + return message; + } + +} diff --git a/src/main/java/racing/view/InputView.java b/src/main/java/racing/view/InputView.java new file mode 100644 index 0000000000..1ca5cc7d04 --- /dev/null +++ b/src/main/java/racing/view/InputView.java @@ -0,0 +1,32 @@ +package racing.view; + + +import java.util.Scanner; + +public class InputView { + public static String inputCarNames() { + System.out.println(ConsoleMessages.INPUT_CAR_NAME.getMessage()); + String carNames = new Scanner(System.in).nextLine(); + + try { + checkIdentifier(carNames); + return carNames; + } catch (Exception e) { + System.err.println(e.getMessage()); + return inputCarNames(); + } + } + + public static String inputRepeatNumber() { + System.out.println(ConsoleMessages.INPUT_REPEAT_NUMBER.getMessage()); + return new Scanner(System.in).nextLine(); + } + + protected static void checkIdentifier(String carNames) { + if (!carNames.contains(",")) { + throw new IllegalArgumentException(ConsoleMessages.ERR_IDENTIFIER_NAME.getMessage()); + } + } + + +} diff --git a/src/main/java/racing/view/OutputView.java b/src/main/java/racing/view/OutputView.java new file mode 100644 index 0000000000..e975bfae5b --- /dev/null +++ b/src/main/java/racing/view/OutputView.java @@ -0,0 +1,36 @@ +package racing.view; + +import racing.domain.Car; +import racing.domain.RaceStatusDto; + +import java.util.List; + +public class OutputView { + public static void printStatus(RaceStatusDto raceStatusDto) { + List cars = raceStatusDto.getCars(); + + System.out.println(printStatus(cars).toString()); + } + + private static StringBuilder printStatus(List cars) { + StringBuilder sb = new StringBuilder(); + for (Car car : cars) { + sb.append(car.getName()); + sb.append(" : "); + printDistanceStatus(sb, car); + sb.append("\n"); + } + return sb; + } + + private static void printDistanceStatus(StringBuilder sb, Car car) { + for (int i = 0; i < car.getDistance(); i++) { + sb.append("-"); + } + } + + public static void printWinners(List winners) { + String winnerNames = String.join(",", winners); + System.out.println(String.format(ConsoleMessages.OUTPUT_WINNER_NAME.getMessage(), winnerNames)); + } +} diff --git a/src/test/java/cal/TextCalculatorTest.java b/src/test/java/cal/TextCalculatorTest.java new file mode 100644 index 0000000000..e719ee557d --- /dev/null +++ b/src/test/java/cal/TextCalculatorTest.java @@ -0,0 +1,58 @@ +package cal; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class TextCalculatorTest { + @Test + public void 덧셈() { + assertEquals(3.0, new TextCalculator("1 + 2").calculate(), 0.00); + } + + @Test + public void 뺄셈() { + assertEquals(2.0, new TextCalculator("3 - 1").calculate(), 0.0); + } + + @Test + public void 곱셈() { + assertEquals(9, new TextCalculator("3 * 3").calculate(), 0.0); + } + + @Test + public void 나눗셈() { + assertEquals(10.0, new TextCalculator("100 / 10").calculate(), 0.0); + } + + @Test + public void 여러가지_연산() { + assertEquals(10.0, new TextCalculator("2 + 3 * 4 / 2").calculate(), 0.00); + } + + @Test(expected = IllegalArgumentException.class) + public void 숫자가아님() { + new TextCalculator("2 + o").calculate(); + } + + @Test(expected = IllegalArgumentException.class) + public void 첫문자가_숫자가아님() { + new TextCalculator("+ 3 * 4 / 2").calculate(); + } + + @Test(expected = NullPointerException.class) + public void 연속_숫자() { + new TextCalculator(" 3 4 2").calculate(); + } + + @Test + public void 음수() { + assertEquals(0, new TextCalculator("2 + -2").calculate(), 0.0); + } + + @Test(expected = IllegalArgumentException.class) + public void 공백_잘못됨() { + new TextCalculator("2+3 * 4 / 2").calculate(); + } + +} \ No newline at end of file diff --git a/src/test/java/empty.txt b/src/test/java/empty.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/test/java/racing/domain/CarTest.java b/src/test/java/racing/domain/CarTest.java new file mode 100644 index 0000000000..58433912fa --- /dev/null +++ b/src/test/java/racing/domain/CarTest.java @@ -0,0 +1,34 @@ +package racing.domain; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class CarTest { + @Test(expected = IllegalArgumentException.class) + public void 이름_길이_체크_1() { + Car car = new Car("abcdef"); + } + + @Test(expected = IllegalArgumentException.class) + public void 이름_길이_체크_2() { + Car car = new Car("abcdef", 3); + } + + @Test(expected = IllegalArgumentException.class) + public void 이름_공백_체크() { + Car car = new Car(""); + } + + @Test(expected = NullPointerException.class) + public void 이름_NULL_체크3() { + Car car = new Car(null); + } + + @Test + public void 이동_제어_테스트() { + Car car = new Car("abc"); + assertEquals(2, car.move(Rules.MIN_MOVABLE_NUMBER)); + assertEquals(2, car.move(Rules.MIN_MOVABLE_NUMBER - 1)); + } +} diff --git a/src/test/java/racing/domain/RacingCarsTest.java b/src/test/java/racing/domain/RacingCarsTest.java new file mode 100644 index 0000000000..01004b753f --- /dev/null +++ b/src/test/java/racing/domain/RacingCarsTest.java @@ -0,0 +1,28 @@ +package racing.domain; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + + +public class RacingCarsTest { + + @Test + public void 우승자_확인() { + List cars = new ArrayList<>(); + + cars.add(new Car("a",1)); + cars.add(new Car("b", 3)); + cars.add(new Car("c", 3)); + + List winners = Arrays.asList("b","c"); + + RacingCars rc = new RacingCars(cars); + + assertEquals(winners,rc.getWinners()); + } +} diff --git a/src/test/java/racing/domain/RepeatNumberTest.java b/src/test/java/racing/domain/RepeatNumberTest.java new file mode 100644 index 0000000000..3df2ac3aa8 --- /dev/null +++ b/src/test/java/racing/domain/RepeatNumberTest.java @@ -0,0 +1,20 @@ +package racing.domain; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class RepeatNumberTest { + + @Test(expected = IllegalArgumentException.class) + public void ZERO_보다_작음_체크() { + + RepeatNumber number = new RepeatNumber(0); + } + + @Test + public void 정수변환_체크() { + RepeatNumber number = new RepeatNumber("3"); + + assertEquals(3, number.getNumber()); + } +} diff --git a/src/test/java/racing/view/InputViewTest.java b/src/test/java/racing/view/InputViewTest.java new file mode 100644 index 0000000000..737fe773fe --- /dev/null +++ b/src/test/java/racing/view/InputViewTest.java @@ -0,0 +1,29 @@ +package racing.view; + +import org.junit.After; +import org.junit.Test; + +import java.io.ByteArrayInputStream; + +import static org.junit.Assert.*; + +public class InputViewTest { + + @Test(expected = IllegalArgumentException.class) + public void 구분자_쉼표_에러_확인() { + InputView.checkIdentifier("andole:baedi"); + } + + @Test + public void 정상_구분자() { + String inputString = "andole,baedi"; + ByteArrayInputStream input = new ByteArrayInputStream(inputString.getBytes()); + System.setIn(input); + assertEquals(inputString, InputView.inputCarNames()); + } + + @After + public void tearDown() throws Exception { + System.setIn(System.in); + } +}