diff --git a/build.gradle b/build.gradle index 15f33525..4d418501 100644 --- a/build.gradle +++ b/build.gradle @@ -22,4 +22,5 @@ java { test { useJUnitPlatform() + jvmArgs '-Xshare:off' // JVM 아규먼트 설정 } diff --git a/docs/README.md b/docs/README.md index e69de29b..ed43fe3a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,23 @@ +구현해야하는 기능 목록 + +1. 자동차 이름 입력 (쉼표로 구분) + 1-1. 이름 나누기 + 1-2. 각 이름 검증 (5글자 이하) +2. 시도할 횟수 입력 + 2-1. 횟수 검증 (10회 이하) + +3. 각 라운드 진행 + 3-1. 각 개체 별 랜덤 넘버 생성 + 3-2. 라운드 진행 + 3-3. 각 라운드 별 결과 작성 (이름 : 진행 상황) + +4. 최종 우승자 출력 + 4-1. 각 객체 별 최종 위치 확인 + 4-2. 우승자 출력 (2명 이상일 수 있음) + +유의사항 +- 사용자 입력값은 camp.nextstep.edu.missionutils.Console의 readLine() 활용 +- Random 값 추출은 camp.nextstep.edu.missionutils.Randoms의 pickNumberInRange() 활용 +- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현 +- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 구현 +- JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인할 것! \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e7..65c85b8e 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,73 @@ package racingcar; +import camp.nextstep.edu.missionutils.Console; + public class Application { public static void main(String[] args) { // TODO: 프로그램 구현 + + // 1. 이름 입력 받기 + System.out.println("경주할 자동차 이름을 입력하세요. (이름은 쉼표(,) 기준으로 구분합니다.)"); + String participations = Console.readLine(); + + // 1-1. 이름 나누기 + String[] carNames = participations.split(","); //String을 콤마를 기준으로 나누어 리스트로 변환 + int length = carNames.length; + + // 객체 배열 생성 + Car[] cars = new Car[length]; + + for (int i = 0; i < carNames.length; i++) { + cars[i] = new Car(carNames[i]); + } + for (Car car : cars) { System.out.println("참가자 : " + car.getName()); } + + // 2. 시도 횟수 입력 + System.out.println("시도할 회수는 몇 회입니까?"); + String tryCounts = Console.readLine(); // readLine은 String으로 받아야 함. (int 불가x) + + int counts = Application.validateTryCount(tryCounts); // 변수 시작 소문자로 수정 + + // 3. 게임 실행 + Game game = new Game(); + for(int i = 0; i < counts; i++){ + System.out.println(); + System.out.println( "["+ (i+1) +"번째 시도]"); + // 각 라운드 경주 진행 + game.runRound(cars); + + // 3-1. 라운드 별 결과 출력 + game.printRound(cars); + } + // 4. 게임 끝! 결과 출력 + // 4-1. + game.checkWinner(cars); + // 4-2. + game.printWinner(cars); + } + + private static final int minGameTry = 1; + private static final int maxGameTry = 10; + + // 2-1. 시도 횟수 검증(임의 규칙 생성) + public static int validateTryCount(String tryCounts){ + // 게임 횟수는 1번 이상일 것 + final int num ; // num은 한 게임에서 변하지 않으므로 final 처리 + + try { + num = Integer.parseInt(tryCounts); + } + catch(NumberFormatException e) { + throw new IllegalArgumentException("시도 횟수는 숫자여야 합니다."); + } + // 게임 횟수는 최소 1번 + if (num < minGameTry){ + throw new IllegalArgumentException("게임 횟수는 최소 1번이어야 합니다."); + } + // 게임 횟수는 10번 이하일 것 + if (num > maxGameTry){ + throw new IllegalArgumentException("게임 횟수는 10회 이하여야 합니다."); + } + return num ; } } diff --git a/src/main/java/racingcar/Car.java b/src/main/java/racingcar/Car.java new file mode 100644 index 00000000..289b2197 --- /dev/null +++ b/src/main/java/racingcar/Car.java @@ -0,0 +1,35 @@ +// 상태 : 자동차의 이름, 위치 +// 행동 : 움직인다/움직이지 않는다 + +package racingcar; +public class Car { + + private final String name; // final로 변경 + private int position = 0; // 초기화 + + // 생성자 + public Car(String name) { + this.name = validateName(name); + } + + private String validateName(String name) { + // 1. null 체크 + if (name == null) throw new IllegalArgumentException("이름을 입력해주세요."); + + // 2. 이름이 비었는지 체크(공백만으로 이루어졌는지) + String s = name.strip(); + if (s.isEmpty()) throw new IllegalArgumentException("이름은 공백만으로 이루어질 수 없습니다."); + + // 3. 길이 > 5 체크 + if (s.length() > 5) throw new IllegalArgumentException("이름은 5글자 이하여야 합니다"); + return s; + } + + public String getName() { return name; } + public int getPosition() { return position; } + + + + //4 이상이면 전진 + void moveIf(int num) { if (num >= 4) { position++; } } +} diff --git a/src/main/java/racingcar/Game.java b/src/main/java/racingcar/Game.java new file mode 100644 index 00000000..11f004a1 --- /dev/null +++ b/src/main/java/racingcar/Game.java @@ -0,0 +1,63 @@ +package racingcar; + +import camp.nextstep.edu.missionutils.Randoms; + +public class Game { // 게임 진행 클래스 + // 3-1. 랜덤 넘버 생성 + public int pickRandomNumber(){ + return Randoms.pickNumberInRange(0,9); + } + + // 3-2. 라운드 진행 + public void runRound(Car[] cars){ + // 모든 차에 대해 랜덤 수를 뽑고, 전진 여부를 판단하기 + for (Car car : cars) { + int n = pickRandomNumber(); + car.moveIf(n); + } + } + + // 3-3. 라운드 결과 출력(라운드마다 실행!) + public void printRound(Car[] cars){ + for (Car car : cars) { + // 단순히 getPosition으로 숫자만 출력하는게 아님! 현재 위치를 '-' 이용해서 출력하기 + String result = printPosition(car.getPosition()); + System.out.println(car.getName() + " : " + result); + } + } + + // SRP(단일책임원칙) : 메서드 위치 변경 (Car -> Game) + public String printPosition(int position) { // 생각해보니 buffer를 사용하지 않아도 됨... return을 String으로 변경 + return "-".repeat(position); + } + // 4. 게임 끝! + + // 4-1. 마지막 위치 확인 + // - 객체 배열 순회하면서 최종 위치 + 우승자 갱신 + public StringBuilder checkWinner(Car[] cars){ + StringBuilder winners = new StringBuilder(); // winner는 2명 이상일 수 있으므로 stringbuilder로 생성 + int maxPosition = 0; + + // <1> 1번 순회하기 (max값과 같으면 string에 추가, 더 크면 초기화) + for (Car car : cars) { + int p = car.getPosition(); // 변수화 + + // [1] max보다 크면 StringBuilder 초기화 후 우승자 후보 정보 입력 + if ((p) > maxPosition) { + maxPosition = p; + winners.setLength(0); // 초기화 + winners.append(car.getName()); + } + // [2] max와 동일하면 초기화는 하지 않고 우승자 후보 추가 입력 + else if (p == maxPosition) { + if (winners.length() > 0) { winners.append(", ");} + winners.append(car.getName()); + } + } + return winners; + } + public void printWinner(Car[] cars){ + System.out.println("최종 우승자 : " + checkWinner(cars)); + + } +} diff --git a/src/test/java/racingcar/CarTest.java b/src/test/java/racingcar/CarTest.java new file mode 100644 index 00000000..bcfc8dbb --- /dev/null +++ b/src/test/java/racingcar/CarTest.java @@ -0,0 +1,54 @@ +package racingcar; + +import org.junit.jupiter.api.Test; + +import static camp.nextstep.edu.missionutils.test.Assertions.assertRandomNumberInRangeTest; +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + + +class CarTest { + //1. 자동차 이름 입력 (쉼표로 구분) + @Test + void 자동차의_이름은_5글자_넘으면_예외발생() { + // 이름이 6자 이상인 자동차를 만들면 예외 발생 + assertThatThrownBy(() -> new Car("abcdef")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 이름이_비었으면_예외발생() { + assertThatThrownBy(() -> new Car(" ")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 이름이_null이면_예외발생() { + assertThatThrownBy(() -> new Car(null)) + .isInstanceOf(IllegalArgumentException.class); + } + +// //2번 - 시도할 횟수 입력 +// @Test +// void 게임횟수는_1미만이면_예외발생() { +// assertThatThrownBy(() -> Application.validateTryCount("0") +// .isInstanceOf(IllegalArgumentException.class)); +// } + //3. 전진 규칙 + @Test + void 자동차는_숫자가_4이상일때_전진() { //테스트는 경계값을 기준으로 설정 + Car car = new Car("test1"); + car.moveIf(4); + assertThat(car.getPosition()).isEqualTo(1); + } + + @Test + void 자동차는_숫자가_3이하면_정지() { + Car car = new Car("test1"); + car.moveIf(3); // 3 이하 → 멈춤 + assertThat(car.getPosition()).isEqualTo(0); // 자동차의 위치가 0인지 검증 + } + + +} diff --git a/src/test/java/racingcar/TryCountValidateTest.java b/src/test/java/racingcar/TryCountValidateTest.java new file mode 100644 index 00000000..cb8e4421 --- /dev/null +++ b/src/test/java/racingcar/TryCountValidateTest.java @@ -0,0 +1,20 @@ +package racingcar; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.api.Test; + +public class TryCountValidateTest { + //2번 - 시도할 횟수 입력 테스트 + @Test + void 게임횟수가_0이하면_예외처리(){ + assertThatThrownBy(() -> Application.validateTryCount("0")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 게임횟수가_10초과면_예외처리(){ + assertThatThrownBy(() -> Application.validateTryCount("11")) + .isInstanceOf(IllegalArgumentException.class); + } + +}