diff --git a/README.md b/README.md index 491aece1..b484383e 100644 --- a/README.md +++ b/README.md @@ -1 +1,48 @@ -# java-racingcar-precourse \ No newline at end of file +# java-racingcar-precourse + +## 구현 기능 목록 + +### Model + +#### RacingCar + +- 레이싱카 객체 +- 변수 + - 자동차 이름 + - 현재 위치 (=전진한 회수) +- 메서드 + - 현재 위치 출력 : "자동차 이름 : (현재 위치만큼 - 출력)" 의 조건으로 출력 + - 전진 : 전진할지 말지 판단하는 메서드, 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우 전진 + + +#### RacingCarGame + +- 레이싱카게임 객체 +- 변수 + - 게임에 참가하는 레이싱카 객체 리스트 + - 시도할 회수 (몇번의 이동을 할 것인지) + - 현재까지 시도한 회수 +- 메서드 + - 회수 진행 : 1번의 전진 회차를 진행 + - 우승자 판단 : 모든 시도 회수가 끝난 후 우승자를 판단하여 반환 + - 게임 종료 판단 : 모든 회차를 진행하여 게임이 종료됐는지 판단 + +### View + +#### GameInterface + +- 출력 및 입력 기능을 담당하는 게임 인터페이스 객체 +- 출력 기능 + - 전진 회수 진행 시, 현재 레이싱카들의 전진 상태 출력 + - 최종 우승자 결과 출력 + - 오류 메세지 출력 +- 입력 기능 + - 게임에 참가할 n개의 자동차 입력 + - 시도할 회수 입력 + +### Controller + +#### GameExecutor + +- 앱 최초 실행 시 main 함수에서 호출하는 exec 메서드 구현 +- model과 view를 연결하는 기능 \ No newline at end of file diff --git a/src/main/java/app/RacingCarApplication.java b/src/main/java/app/RacingCarApplication.java new file mode 100644 index 00000000..fba9f9d6 --- /dev/null +++ b/src/main/java/app/RacingCarApplication.java @@ -0,0 +1,12 @@ +package app; + +import app.controller.GameExecutor; +import app.model.RacingCarGame; +import app.view.GameInterface; + +public class RacingCarApplication { + public static void main(String[] args) { + GameExecutor gameExecutor = new GameExecutor(new GameInterface(), new RacingCarGame()); + gameExecutor.exec(); + } +} diff --git a/src/main/java/app/controller/GameExecutor.java b/src/main/java/app/controller/GameExecutor.java new file mode 100644 index 00000000..3ee8d511 --- /dev/null +++ b/src/main/java/app/controller/GameExecutor.java @@ -0,0 +1,56 @@ +package app.controller; + +import app.model.RacingCarGame; +import app.view.GameInterface; + +public class GameExecutor { + private GameInterface gameInterface; + private RacingCarGame racingCarGame; + + public GameExecutor(GameInterface gameInterface, RacingCarGame racingCarGame) { + this.gameInterface = gameInterface; + this.racingCarGame = racingCarGame; + } + + public void step1() { + while (racingCarGame.getCarList() == null) { + try { + racingCarGame.setCarList(gameInterface.getRacingCarNames()); + } catch (IllegalArgumentException e) { + gameInterface.printErrorMessage(e.getMessage()); + } + } + } + + public void step2() { + while (racingCarGame.getAttemptCnt() == 0) { + try { + racingCarGame.setAttemptCnt(gameInterface.getAttemptCount()); + } catch (NumberFormatException e) { + gameInterface.printErrorMessage("숫자만 입력해주세요."); + } catch (IllegalArgumentException e) { + gameInterface.printErrorMessage(e.getMessage()); + } + } + } + + public void step3() { + gameInterface.printMessage("\n실행 결과"); + while (!racingCarGame.isFinished()) { + racingCarGame.proceed(); + gameInterface.printAttemptResult(racingCarGame.getCarList()); + } + } + + public void step4() { + gameInterface.printWinner(racingCarGame.getWinnerList()); + } + + public void exec() { + step1(); // 1. 자동차 이름 입력 + step2(); // 2. 시도할 회수 입력 + step3(); // 3. 게임 진행 (경주) + step4(); // 4. 최종 우승자 출력 + } + +} diff --git a/src/main/java/app/model/RacingCar.java b/src/main/java/app/model/RacingCar.java new file mode 100644 index 00000000..fa2ae560 --- /dev/null +++ b/src/main/java/app/model/RacingCar.java @@ -0,0 +1,31 @@ +package app.model; + +import java.util.Random; + +public class RacingCar { + private String name; + private int location; + + public RacingCar(String name) { + this.name = name; + this.location = 0; + } + + public void printCurLocation() { + System.out.println(this.name + " : " + "-".repeat(location)); + } + + public void moveForward() { // 전진할지 말지 판단한 후, 전진하면 location 값 +1 + if (new Random().nextInt(10) >= 4) { + this.location++; + } + } + + public String getName() { + return name; + } + + public int getLocation() { + return location; + } +} diff --git a/src/main/java/app/model/RacingCarGame.java b/src/main/java/app/model/RacingCarGame.java new file mode 100644 index 00000000..8554e82e --- /dev/null +++ b/src/main/java/app/model/RacingCarGame.java @@ -0,0 +1,65 @@ +package app.model; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class RacingCarGame { + private int attemptCnt; // 시도할 회수 + private int curCnt; // 현재까지 시도한 회수 + private List carList; + + public RacingCarGame() { + this.curCnt = 0; + this.attemptCnt = 0; + } + + public int getAttemptCnt() { + return attemptCnt; + } + + public void setAttemptCnt(String attemptCnt) { + int num = Integer.parseInt(attemptCnt); + if (num < 1) throw new IllegalArgumentException("시도 회수는 1 이상이어야 합니다."); + this.attemptCnt = num; + } + + public int getCurCnt() { + return curCnt; + } + + public void setCurCnt(int curCnt) { + this.curCnt = curCnt; + } + + public List getCarList() { + return carList; + } + + public void setCarList(String carNames) { + String[] carNameList = carNames.split(","); + checkCarNameValidation(carNameList); + + this.carList = Arrays.stream(carNameList).map(RacingCar::new).collect(Collectors.toList()); + } + + public void checkCarNameValidation(String[] carNameList) { + Arrays.stream(carNameList).filter(name -> name.length() > 5 || name.length() < 1).findAny().ifPresent(name -> { + throw new IllegalArgumentException("자동차 이름은 1자리 이상 5자리 이하만 가능합니다."); + }); + } + + public void proceed() { + carList.forEach(RacingCar::moveForward); + this.curCnt++; + } + + public List getWinnerList() { + int maxLocation = carList.stream().mapToInt(RacingCar::getLocation).max().orElse(0); + return carList.stream().filter(car -> car.getLocation() == maxLocation).collect(Collectors.toList()); + } + + public boolean isFinished() { + return this.attemptCnt == this.curCnt; + } +} diff --git a/src/main/java/app/view/GameInterface.java b/src/main/java/app/view/GameInterface.java new file mode 100644 index 00000000..7edd7f67 --- /dev/null +++ b/src/main/java/app/view/GameInterface.java @@ -0,0 +1,40 @@ +package app.view; + +import app.model.RacingCar; + +import java.util.List; +import java.util.Scanner; +import java.util.stream.Collectors; + +public class GameInterface { + private Scanner scanner = new Scanner(System.in); + + public String getRacingCarNames() { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + return scanner.nextLine(); + } + + public String getAttemptCount() { + System.out.println("시도할 회수는 몇회인가요?"); + return scanner.nextLine(); + } + + public void printAttemptResult(List carList) { + carList.stream().forEach(RacingCar::printCurLocation); + System.out.println(); + } + + public void printWinner(List winnerList) { + System.out.println("최종 우승자 : " + winnerList.stream() + .map(RacingCar::getName) + .collect(Collectors.joining(", "))); + } + + public void printMessage(String message) { + System.out.println(message); + } + + public void printErrorMessage(String message) { + System.out.println("[Error] " + message); + } +} diff --git a/src/test/java/app/model/RacingCarGameTest.java b/src/test/java/app/model/RacingCarGameTest.java new file mode 100644 index 00000000..36d979a0 --- /dev/null +++ b/src/test/java/app/model/RacingCarGameTest.java @@ -0,0 +1,78 @@ +package app.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +public class RacingCarGameTest { + + @Test + @DisplayName("5자리 이상 자동차 이름이 입력됐을 경우") + void test1() { + String inputCarNames = "가나,다라마바,사아자차카타,파하"; + + assertThatThrownBy(() -> { + RacingCarGame racingCarGame = new RacingCarGame(); + racingCarGame.setCarList(inputCarNames); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("자동차 이름은 1자리 이상 5자리 이하만 가능합니다."); + } + + @Test + @DisplayName("자동차 이름에 공백이 입력됐을 경우") + void test2() { + String inputCarNames1 = ""; + + assertThatThrownBy(() -> { + RacingCarGame racingCarGame = new RacingCarGame(); + racingCarGame.setCarList(inputCarNames1); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("자동차 이름은 1자리 이상 5자리 이하만 가능합니다."); + + String inputCarNames2 = "가나,다라마,,바사"; + + assertThatThrownBy(() -> { + RacingCarGame racingCarGame = new RacingCarGame(); + racingCarGame.setCarList(inputCarNames2); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("자동차 이름은 1자리 이상 5자리 이하만 가능합니다."); + } + + @Test + @DisplayName("시도 회수에 숫자 외 문자가 입력됐을 경우") + void test3() { + String input = "asd@34"; + + assertThatThrownBy(() -> { + RacingCarGame racingCarGame = new RacingCarGame(); + racingCarGame.setAttemptCnt(input); + }) + .isInstanceOf(NumberFormatException.class); + } + + @Test + @DisplayName("시도 회수에 1 미만 숫자가 입력됐을 경우") + void test4() { + String input1 = "-1"; + + assertThatThrownBy(() -> { + RacingCarGame racingCarGame = new RacingCarGame(); + racingCarGame.setAttemptCnt(input1); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("시도 회수는 1 이상이어야 합니다."); + + String input2 = "0"; + + assertThatThrownBy(() -> { + RacingCarGame racingCarGame = new RacingCarGame(); + racingCarGame.setAttemptCnt(input2); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("시도 회수는 1 이상이어야 합니다."); + } +}