diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java new file mode 100644 index 000000000..9e5c50d63 --- /dev/null +++ b/src/main/java/racingcar/Application.java @@ -0,0 +1,32 @@ +package racingcar; + +import java.util.Scanner; + +import racingcar.domain.Cars; +import racingcar.domain.RacingGame; +import racingcar.domain.RandomMoveStrategy; +import racingcar.view.RacingView; + +public class Application { + public static void main(String[] args) { + RacingView racingView = new RacingView(); + + Scanner scanner = new Scanner(System.in); + + try { + String[] names = racingView.inputNames(scanner); + Cars cars = new Cars(names); + int trialCount = racingView.inputTrialCount(scanner); + + RacingGame racingGame = new RacingGame(cars, trialCount, new RandomMoveStrategy()); + + while(!racingGame.isEnd()) { + System.out.println(racingView.printRace(racingGame.race())); + } + + System.out.println(racingView.printWinners(racingGame.getWinners())); + }catch(Exception e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/racingcar/domain/Car.java b/src/main/java/racingcar/domain/Car.java new file mode 100644 index 000000000..fdadba97e --- /dev/null +++ b/src/main/java/racingcar/domain/Car.java @@ -0,0 +1,40 @@ + package racingcar.domain; + + public class Car { + private final String name; + private final Position position; + public Car(String name) { + this(name, 0); + } + + public Car(String name, int position) { + if(name.length() == 0 || name.isEmpty()) { + throw new IllegalArgumentException("차 이름은 빈값을 넣을 수 없습니다."); + } + + if(name.length() > 5) { + throw new IllegalArgumentException("차 이름의 길이를 5자를 초과할 수 없습니다."); + } + + this.name = name; + this.position = new Position(position); + } + + public void move(MoveStrategy moveStrategy) { + if(moveStrategy.movable()) { + position.add(); + } + } + + public String getName() { + return name; + } + + public Position getPosition() { + return this.position; + } + + public int getDistance() { + return position.getPosition(); + } + } diff --git a/src/main/java/racingcar/domain/Cars.java b/src/main/java/racingcar/domain/Cars.java new file mode 100644 index 000000000..4a06a7c1b --- /dev/null +++ b/src/main/java/racingcar/domain/Cars.java @@ -0,0 +1,45 @@ +package racingcar.domain; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class Cars { + private final List cars; + + public Cars(List cars) { + this.cars = cars; + } + + public Cars(String[] names) { + cars = Arrays.stream(names) + .map(Car::new) + .collect(Collectors.toList()); + } + + public Cars findWinners() { + Position maxPosition = getMaxPosition(); + + return new Cars( + cars.stream() + .filter(car -> car.getPosition().equals(maxPosition)) + .collect(Collectors.toList()) + ); + } + + public Position getMaxPosition() { + + return cars.stream().map(Car::getPosition).max( + Comparator.comparingInt(Position::getPosition) + ).orElse(new Position(0)); + } + + public List getCars() { + return cars; + } + + public void moveAll(MoveStrategy moveStrategy) { + cars.forEach(car -> car.move(moveStrategy)); + } +} diff --git a/src/main/java/racingcar/domain/MoveStrategy.java b/src/main/java/racingcar/domain/MoveStrategy.java new file mode 100644 index 000000000..aaabddc18 --- /dev/null +++ b/src/main/java/racingcar/domain/MoveStrategy.java @@ -0,0 +1,6 @@ +package racingcar.domain; + +@FunctionalInterface +public interface MoveStrategy { + boolean movable(); +} diff --git a/src/main/java/racingcar/domain/Position.java b/src/main/java/racingcar/domain/Position.java new file mode 100644 index 000000000..428eb5a54 --- /dev/null +++ b/src/main/java/racingcar/domain/Position.java @@ -0,0 +1,39 @@ +package racingcar.domain; + +import java.util.Objects; + +public class Position { + private int position; + + public Position(int position) { + if(position < 0) { + throw new IllegalArgumentException("position은 0보다 적을 수 없습니다."); + } + this.position = position; + } + + public void add() { + position++; + } + + public int getPosition() { + return position; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Position position1 = (Position)o; + return position == position1.position; + } + + @Override + public int hashCode() { + return Objects.hash(position); + } + + +} diff --git a/src/main/java/racingcar/domain/RacingGame.java b/src/main/java/racingcar/domain/RacingGame.java new file mode 100644 index 000000000..8b971d8c9 --- /dev/null +++ b/src/main/java/racingcar/domain/RacingGame.java @@ -0,0 +1,39 @@ +package racingcar.domain; + +import racingcar.domain.Cars; +import racingcar.domain.MoveStrategy; + +public class RacingGame { + private final Cars cars; + private final MoveStrategy moveStrategy; + private int trialCount; + + public RacingGame(Cars cars, int trialCount, MoveStrategy moveStrategy) { + if(trialCount < 1) { + throw new IllegalArgumentException("시도할 횟수는 1이상이어야 합니다."); + } + + this.cars = cars; + this.trialCount = trialCount; + this.moveStrategy = moveStrategy; + } + + public boolean isEnd() { + return trialCount == 0; + } + + public Cars race() { + if(trialCount == 0) { + return cars; + } + + trialCount--; + cars.moveAll(moveStrategy); + return cars; + } + + public Cars getWinners() { + return cars.findWinners(); + } + +} diff --git a/src/main/java/racingcar/domain/RandomMoveStrategy.java b/src/main/java/racingcar/domain/RandomMoveStrategy.java new file mode 100644 index 000000000..842242b6a --- /dev/null +++ b/src/main/java/racingcar/domain/RandomMoveStrategy.java @@ -0,0 +1,18 @@ +package racingcar.domain; + +import java.util.Random; + +public class RandomMoveStrategy implements MoveStrategy{ + private static final int FORWARD_NUM = 4; + private static final int MAX_BOUND = 10; + private static final Random RANDOM = new Random(); + + @Override + public boolean movable() { + return getRandomNo() >= FORWARD_NUM; + } + + private int getRandomNo() { + return RANDOM.nextInt(MAX_BOUND); + } +} diff --git a/src/main/java/racingcar/view/RacingView.java b/src/main/java/racingcar/view/RacingView.java new file mode 100644 index 000000000..7db024943 --- /dev/null +++ b/src/main/java/racingcar/view/RacingView.java @@ -0,0 +1,44 @@ +package racingcar.view; + +import java.util.Scanner; +import java.util.stream.Collectors; + +import racingcar.domain.Car; +import racingcar.domain.Cars; + +public class RacingView { + + public String printRace(Cars cars) { + + return cars.getCars() + .stream() + .map(RacingView::renderCarPosition) + .collect(Collectors.joining("\n")) + + "\n"; + } + + private static String renderCarPosition(Car car) { + return car.getName() + " : " + "-".repeat(car.getDistance() + 1); + } + + public String[] inputNames(Scanner scanner) { + System.out.println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)."); + + return scanner.nextLine().trim().split(","); + } + + public int inputTrialCount(Scanner scanner) { + System.out.println("시도할 회수는 몇회인가요?"); + + return Integer.parseInt(scanner.nextLine()); + } + + public String printWinners(Cars cars) { + Cars winners = cars.findWinners(); + String winnerNames = winners.getCars().stream().map(Car::getName).collect(Collectors.joining(", ")); + + return winnerNames + "가 최종 우승했습니다."; + } + + +} diff --git a/src/main/java/study/StringAddCalculator.java b/src/main/java/study/StringAddCalculator.java new file mode 100644 index 000000000..4ce3a2b84 --- /dev/null +++ b/src/main/java/study/StringAddCalculator.java @@ -0,0 +1,45 @@ +package study; + +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jdk.nashorn.internal.runtime.regexp.joni.Regex; + +public class StringAddCalculator { + private final static String BASIC_DELIMITER = ",|:"; + + public int splitAndSum(String str) { + if(isNullOrEmpty(str)) { + return 0; + } + + String delimiter = getDelimiter(str); + if(!delimiter.equals(BASIC_DELIMITER)) { + str = str.substring(4); + } + + return Arrays.stream(str.split(delimiter)) + .mapToInt((num) -> { + if(num.matches("\\d*")){ + return Integer.parseInt(num); + } + throw new RuntimeException(); + }).sum(); + } + + public boolean isNullOrEmpty(String str) { + return str == null || str.isEmpty(); + } + + public String getDelimiter(String str) { + Matcher m = Pattern.compile("//(.)\n(.*)").matcher(str); + + if (m.find()) { + return m.group(1) + "|" + BASIC_DELIMITER; + } + + return BASIC_DELIMITER; + } + +} diff --git a/src/test/java/racingcar/domain/CarTest.java b/src/test/java/racingcar/domain/CarTest.java new file mode 100644 index 000000000..0508d9f46 --- /dev/null +++ b/src/test/java/racingcar/domain/CarTest.java @@ -0,0 +1,34 @@ +package racingcar.domain; + +import static org.assertj.core.api.AssertionsForClassTypes.*; + +import org.junit.jupiter.api.Test; + +public class CarTest { + @Test + void create() { + assertThatThrownBy(() -> { + new Car("abcdef"); + }).isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy(() -> { + new Car(""); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void move() { + Car car = new Car("ws"); + car.move(() -> true); + + assertThat(car.getPosition()).isEqualTo(new Position(1)); + } + + @Test + void notMove() { + Car car = new Car("ws"); + car.move(() -> false); + + assertThat(car.getPosition()).isEqualTo(new Position(0)); + } +} diff --git a/src/test/java/racingcar/domain/CarsTest.java b/src/test/java/racingcar/domain/CarsTest.java new file mode 100644 index 000000000..23e48cbbd --- /dev/null +++ b/src/test/java/racingcar/domain/CarsTest.java @@ -0,0 +1,62 @@ +package racingcar.domain; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +public class CarsTest { + @Test + void create() { + Cars cars = new Cars(new String[]{"ws", "mh", "dh"}); + List carList = cars.getCars(); + + assertAll( + () -> assertThat(carList.get(0).getName()).isEqualTo("ws"), + () -> assertThat(carList.get(1).getName()).isEqualTo("mh"), + () -> assertThat(carList.get(2).getName()).isEqualTo("dh") + ); + } + + @Test + void findWinners() { + Car ws = new Car("ws", 5); + Car mh = new Car("mh", 5); + Car dh = new Car("dh", 3); + Cars cars = new Cars(Arrays.asList(ws, mh, dh)); + + assertThat(cars.findWinners().getCars()).containsExactly(ws, mh); + } + + @Test + void getMaxPosition() { + Car ws = new Car("ws", 5); + Car mh = new Car("mh", 4); + Car dh = new Car("dh", 3); + Cars cars = new Cars(Arrays.asList(ws, mh, dh)); + + assertThat(cars.getMaxPosition()).isEqualTo(new Position(5)); + } + + @Test + void moveAll() { + Car ws = new Car("ws", 5); + Car mh = new Car("mh", 4); + Car dh = new Car("dh", 3); + Cars cars = new Cars(Arrays.asList(ws, mh, dh)); + + cars.moveAll(() -> true); + List carList = cars.getCars(); + + assertAll( + () -> assertThat(carList.get(0).getPosition()).isEqualTo(new Position(6)), + () -> assertThat(carList.get(1).getPosition()).isEqualTo(new Position(5)), + () -> assertThat(carList.get(2).getPosition()).isEqualTo(new Position(4)) + ); + + } + +} diff --git a/src/test/java/racingcar/domain/PositionTest.java b/src/test/java/racingcar/domain/PositionTest.java new file mode 100644 index 000000000..064c8e3a1 --- /dev/null +++ b/src/test/java/racingcar/domain/PositionTest.java @@ -0,0 +1,22 @@ +package racingcar.domain; + +import static org.assertj.core.api.AssertionsForClassTypes.*; + +import org.junit.jupiter.api.Test; + +public class PositionTest { + @Test + void create() { + assertThatThrownBy( () -> { + new Position(-1); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void add() { + Position position = new Position(1); + position.add(); + + assertThat(position).isEqualTo(new Position(2)); + } +} diff --git a/src/test/java/racingcar/view/RacingViewTest.java b/src/test/java/racingcar/view/RacingViewTest.java new file mode 100644 index 000000000..73b50724c --- /dev/null +++ b/src/test/java/racingcar/view/RacingViewTest.java @@ -0,0 +1,63 @@ +package racingcar.view; + +import static org.assertj.core.api.AssertionsForClassTypes.*; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Scanner; + +import org.junit.jupiter.api.Test; + +import racingcar.domain.Car; +import racingcar.domain.Cars; + +public class RacingViewTest { + @Test + void printRace() { + Car ws = new Car("ws", 2); + Car mh = new Car("mh"); + Car dh = new Car("dh", 1); + Cars cars = new Cars(Arrays.asList(ws, mh, dh)); + + RacingView racingView = new RacingView(); + + assertThat(racingView.printRace(cars)).isEqualTo("ws : ---\nmh : -\ndh : --\n"); + } + + @Test + void printWinners() { + Car ws = new Car("ws", 2); + Car mh = new Car("mh"); + Car dh = new Car("dh", 2); + Cars cars = new Cars(Arrays.asList(ws, mh, dh)); + + RacingView racingView = new RacingView(); + + assertThat(racingView.printWinners(cars)).isEqualTo("ws, dh가 최종 우승했습니다."); + } + + @Test + void inputName() { + String input = "ws,mh,dh"; + InputStream in = new ByteArrayInputStream(input.getBytes()); + System.setIn(in); + Scanner scanner = new Scanner(System.in); + + RacingView racingView = new RacingView(); + + assertThat(racingView.inputNames(scanner)).containsExactly("ws", "mh", "dh"); + } + + @Test + void inputTrialCount() { + String input = "5"; + InputStream in = new ByteArrayInputStream(input.getBytes()); + System.setIn(in); + Scanner scanner = new Scanner(System.in); + + RacingView racingView = new RacingView(); + + assertThat(racingView.inputTrialCount(scanner)).isEqualTo(5); + } +} diff --git a/src/test/java/study/StringAddCalculatorTest.java b/src/test/java/study/StringAddCalculatorTest.java new file mode 100644 index 000000000..13065d8c7 --- /dev/null +++ b/src/test/java/study/StringAddCalculatorTest.java @@ -0,0 +1,86 @@ +package study; + +import static org.assertj.core.api.AssertionsForClassTypes.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class StringAddCalculatorTest { + StringAddCalculator stringAddCalculator; + + @BeforeEach + void setUp() { + stringAddCalculator = new StringAddCalculator(); + } + + @Test + void null_또는_빈문자() { + assertThat(stringAddCalculator.splitAndSum("")).isEqualTo(0); + assertThat(stringAddCalculator.splitAndSum(null)).isEqualTo(0); + } + + @Test + void 숫자_1개() { + assertThat(stringAddCalculator.splitAndSum("5")).isEqualTo(5); + assertThat(stringAddCalculator.splitAndSum("10")).isEqualTo(10); + } + + @Test + void 쉼표_구분자() { + assertThat(stringAddCalculator.splitAndSum("1,2")).isEqualTo(3); + assertThat(stringAddCalculator.splitAndSum("12,3")).isEqualTo(15); + } + + @Test + void 콜론_구분자() { + assertThat(stringAddCalculator.splitAndSum("1:2")).isEqualTo(3); + assertThat(stringAddCalculator.splitAndSum("12:3")).isEqualTo(15); + } + + @Test + void 쉼표_AND_콜론() { + assertThat(stringAddCalculator.splitAndSum("1:2:3,4")).isEqualTo(10); + assertThat(stringAddCalculator.splitAndSum("1,2:3")).isEqualTo(6); + } + + @Test + void 커스텀_구분자() { + assertThat(stringAddCalculator.splitAndSum("//!\n1!2!3")).isEqualTo(6); + assertThat(stringAddCalculator.splitAndSum("//#\n1#2#3")).isEqualTo(6); + assertThat(stringAddCalculator.splitAndSum("//;\n1;2;3")).isEqualTo(6); + } + + @Test + void 커스텀_구분자_AND_쉼표() { + assertThat(stringAddCalculator.splitAndSum("//#\n1#2,3")).isEqualTo(6); + assertThat(stringAddCalculator.splitAndSum("//;\n1;2,3")).isEqualTo(6); + } + + @Test + void 커스텀_구분자_AND_콜롬() { + assertThat(stringAddCalculator.splitAndSum("//#\n1#2:3")).isEqualTo(6); + assertThat(stringAddCalculator.splitAndSum("//;\n1;2:3")).isEqualTo(6); + } + + @Test + void 커스텀_구분자_AND_콜롬_AND_쉼표() { + assertThat(stringAddCalculator.splitAndSum("//#\n1#2:3,4")).isEqualTo(10); + assertThat(stringAddCalculator.splitAndSum("//;\n1;2:3,0")).isEqualTo(6); + } + + @Test + void 음수_포함() { + assertThatThrownBy(() -> stringAddCalculator.splitAndSum("//#\n-1:2:3")) + .isInstanceOf(RuntimeException.class); + } + + @Test + void 정수가_아닌_입력값() { + assertThatThrownBy(() -> stringAddCalculator.splitAndSum("//#\na:32b:3")) + .isInstanceOf(RuntimeException.class); + + assertThatThrownBy(() -> stringAddCalculator.splitAndSum("//#\n1a:b:3")) + .isInstanceOf(RuntimeException.class); + } + +}