diff --git a/README.md b/README.md index d0286c859f..46a0edabaf 100644 --- a/README.md +++ b/README.md @@ -1 +1,60 @@ # java-racingcar-precourse + +--- + +# 기능 설계서 + +## Domain + +1. Car + - 각 자동차에 대한 이름, 전진 횟수(점수)를 저장한다. + - 점수를 변경한다. +2. CarList + - 참여한 자동차를 List 형태로 관리한다. + - 최종 우승자를 찾는다. +3. Game + - 참여한 CarList, 게임 횟수를 관리한다. + - 진행 횟수가 남아있는지 판단한다. + +## Model + +1. GameService + - 게임을 진행한다. + - 진행 횟수가 남아있다면 전진하고 현재 상태 출력 신호를 보낸다. + - 랜덤값을 구해 전진할 수 있는지 판단하고 점수 변경 신호를 보낸다. +2. CarDTO + - 중간 진행 과정 출력을 위해 Car의 정보를 전달하는 객체 +3. OutputPort + - GameService 진행 중에 직접 View를 호출하는 것을 방지하기 위해 데이터를 View에 전달하는 역할을 한다. +4. Validator + - 입력 받은 자동차 이름의 형식이 올바른지 판단한다. + - 입력 받은 전진 횟수의 형식이 올바른지 판단한다. + +## View + +1. getCarName : 자동차 이름 목록 입력 받기 +2. getRoundCount : 경주 횟수 입력 받기 +3. printStatusHeader : 중간 과정 머리말 출력하기(”실행 결과”) +4. printRaceStatus : 진행 과정 출력하기 +5. formatOutput : 자동차의 이름과 현재 전진 횟수를 출력 형식에 맞게 변환하기 +6. printFinalResult : 최종 우승자 출력하기 + +--- + +# 프로그램 흐름도 + +(경주 환경 설정) +1. 사용자로부터 자동차 이름 목록을 입력 받는다. +2. 입력 받은 이름 목록이 적절한 형식인지 검증한다. +3. 입력 받은 목록을 바탕으로 Car 인스턴스를 생성하고 CarList로 관리한다. +4. 사용자로부터 경주 횟수를 입력 받는다. +5. 입력 받은 경주 횟수가 적절한 형식인지 검증한다. +6. CarList와 경주 횟수를 바탕으로 Game 인스턴스를 생성한다. + +(경주 진행) + +7. 경주 횟수 동안 반복한다. (경주 횟수가 남아있다면) + 1. 전진한다.(랜덤값 판단, 점수 변경) + 2. 현재 상태를 출력한다. +8. 우승자를 찾는다. +9. 최종 결과를 출력한다. \ No newline at end of file diff --git a/src/main/java/racingcar/AppConfig.java b/src/main/java/racingcar/AppConfig.java new file mode 100644 index 0000000000..8a666a37da --- /dev/null +++ b/src/main/java/racingcar/AppConfig.java @@ -0,0 +1,14 @@ +package racingcar; + +public class AppConfig { + + final View view = new View(); + + public OutputPort outputPort(){ + return new OutputPort(view); + } + + public Controller controller(){ + return new Controller(view, new Validator(), outputPort()); + } +} diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e724..6cababf6a6 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -3,5 +3,8 @@ public class Application { public static void main(String[] args) { // TODO: 프로그램 구현 + AppConfig appConfig = new AppConfig(); + Controller controller = appConfig.controller(); + controller.run(); } } diff --git a/src/main/java/racingcar/Car.java b/src/main/java/racingcar/Car.java new file mode 100644 index 0000000000..89f8a32028 --- /dev/null +++ b/src/main/java/racingcar/Car.java @@ -0,0 +1,24 @@ +package racingcar; + +public class Car { + private String name; + private int score; + + public Car(String name) { + this.name = name; + score = 0; + } + + public void plusScore(){ + score++; + } + + public CarDTO createCarDTO(){ + return new CarDTO(this.name, this.score); + } + + @Override + public String toString(){ + return name + ":" + score; + } +} diff --git a/src/main/java/racingcar/CarDTO.java b/src/main/java/racingcar/CarDTO.java new file mode 100644 index 0000000000..a0a7e56db9 --- /dev/null +++ b/src/main/java/racingcar/CarDTO.java @@ -0,0 +1,4 @@ +package racingcar; + +public record CarDTO(String name, int score) { +} diff --git a/src/main/java/racingcar/CarList.java b/src/main/java/racingcar/CarList.java new file mode 100644 index 0000000000..0e89223189 --- /dev/null +++ b/src/main/java/racingcar/CarList.java @@ -0,0 +1,38 @@ +package racingcar; + +import java.util.ArrayList; + +public class CarList { + private ArrayList carArrayList; + private int carNumber; + + public CarList(String[] carList) { + carArrayList = new ArrayList<>(); + for (String string : carList) { + carArrayList.add(new Car(string)); + } + carNumber = carList.length; + } + + public int getCarNumber(){ + return carNumber; + } + + public void moveForward(int currentCarIndex) { + carArrayList.get(currentCarIndex).plusScore(); + } + + public ArrayList printCurrentStatus(){ + ArrayList carListDTO = new ArrayList<>(); + for (Car car : carArrayList) { + carListDTO.add(car.createCarDTO()); + } + return carListDTO; + } + + @Override + public String toString(){ + return carArrayList.toString(); + } + +} diff --git a/src/main/java/racingcar/Controller.java b/src/main/java/racingcar/Controller.java new file mode 100644 index 0000000000..a2f95723ce --- /dev/null +++ b/src/main/java/racingcar/Controller.java @@ -0,0 +1,27 @@ +package racingcar; + +import java.util.ArrayList; + +public class Controller { + View view; + Validator validator; + OutputPort outputPort; + + public Controller(View view, Validator validator, OutputPort outputPort) { + this.view = view; + this.validator = validator; + this.outputPort = outputPort; + } + + public void run(){ + String[] carNameList = validator.validateCarName(view.getCarName()); + CarList carList = new CarList(carNameList); + int roundCount = validator.validateRoundCount(view.getRoundCount()); + Game game = new Game(carList, roundCount); + GameService gameService = new GameService(game, outputPort); + view.printStatusHeader(); + gameService.runGame(); + ArrayList carDTOS = carList.printCurrentStatus(); + view.printFinalResult(gameService.findWinnerList(carDTOS)); + } +} diff --git a/src/main/java/racingcar/Game.java b/src/main/java/racingcar/Game.java new file mode 100644 index 0000000000..6e12456e5f --- /dev/null +++ b/src/main/java/racingcar/Game.java @@ -0,0 +1,66 @@ +package racingcar; + +import java.util.ArrayList; + +public class Game { + private CarList carList; + private int roundCount; + private int currentRound; + + public Game(CarList carList, int roundCount) { + this.carList = carList; + this.roundCount = roundCount; + currentRound = 0; + } + + public boolean checkRemainRound(){ + currentRound++; + if (currentRound > roundCount) { + return false; + } + return true; + } + + public boolean hasNextCar(int currentCarIndex){ + if(currentCarIndex printCurrentStatus(){ + return carList.printCurrentStatus(); + } + + public ArrayList findWinner(ArrayList carDTOS){ + int maxScore = findMaxScore(carDTOS); + ArrayList winnerNameList = new ArrayList<>(); + for (CarDTO car : carDTOS) { + addWinnerName(car,maxScore,winnerNameList); + } + return winnerNameList; + } + + public int findMaxScore(ArrayList carDTOS){ + int maxScore = 0; + for (CarDTO car : carDTOS) { + maxScore = Math.max(maxScore, car.score()); + } + return maxScore; + } + + public void addWinnerName(CarDTO car, int maxScore, ArrayList winnerNameList) { + if (car.score() == maxScore) { + winnerNameList.add(car.name()); + } + } + + @Override + public String toString(){ + return carList.toString() + "," + roundCount + "," + currentRound; + } +} diff --git a/src/main/java/racingcar/GameService.java b/src/main/java/racingcar/GameService.java new file mode 100644 index 0000000000..726f2e86fa --- /dev/null +++ b/src/main/java/racingcar/GameService.java @@ -0,0 +1,44 @@ +package racingcar; + +import java.util.ArrayList; + +import static camp.nextstep.edu.missionutils.Randoms.pickNumberInRange; + +public class GameService { + Game game; + OutputPort outputPort; + + public GameService(Game game,OutputPort outputPort) { + this.game = game; + this.outputPort = outputPort; + } + + public void runGame(){ + while(game.checkRemainRound()){ + proceedRound(true); + outputPort.printRaceStatus(game.printCurrentStatus()); + } + } + + public void proceedRound(boolean movingResult){ + int currentCarIndex = 0; + while(game.hasNextCar(currentCarIndex)){ + movingResult = decideMoving(); + if (!movingResult) return; + game.moveForward(currentCarIndex++); + } + } + + public ArrayList findWinnerList(ArrayList carDTOS){ + return game.findWinner(carDTOS); + } + + public boolean decideMoving(){ + int result = pickNumberInRange(0,9); + if (result >= 4) { + return true; + } + return false; + } + +} diff --git a/src/main/java/racingcar/OutputPort.java b/src/main/java/racingcar/OutputPort.java new file mode 100644 index 0000000000..c65a7c9ef0 --- /dev/null +++ b/src/main/java/racingcar/OutputPort.java @@ -0,0 +1,15 @@ +package racingcar; + +import java.util.ArrayList; + +public class OutputPort { + View view; + + public OutputPort(View view) { + this.view = view; + } + + public void printRaceStatus(ArrayList carListDTO){ + view.printRaceStatus(carListDTO); + } +} diff --git a/src/main/java/racingcar/Validator.java b/src/main/java/racingcar/Validator.java new file mode 100644 index 0000000000..5077e53480 --- /dev/null +++ b/src/main/java/racingcar/Validator.java @@ -0,0 +1,33 @@ +package racingcar; + +public class Validator { + public String[] validateCarName(String userInput) { + try{ + String[] nameList = userInput.split(","); + if(!checkNameLength(nameList)){ + throw new IllegalArgumentException(); + } + return nameList; + }catch(Exception e) { + throw new IllegalArgumentException(); + } + } + + public boolean checkNameLength(String[] nameList) { + for (String name : nameList) { + if(name.length()>5) return false; + if(name.isEmpty()) return false; + } + return true; + } + + public int validateRoundCount(String userInput) { + try { + int roundCount = Integer.parseInt(userInput); + if(roundCount<0) throw new IllegalArgumentException(); + return roundCount; + } catch (Exception e) { + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/racingcar/View.java b/src/main/java/racingcar/View.java new file mode 100644 index 0000000000..675feac39f --- /dev/null +++ b/src/main/java/racingcar/View.java @@ -0,0 +1,45 @@ +package racingcar; + +import java.util.ArrayList; + +import static camp.nextstep.edu.missionutils.Console.readLine; + +public class View { + public String getCarName(){ + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + return readLine(); + } + + public String getRoundCount() { + System.out.println("시도할 횟수는 몇 회인가요?"); + return readLine(); + } + + public void printStatusHeader(){ + System.out.println("\n실행 결과"); + } + + public void printRaceStatus(ArrayList carListDTO){ + for (CarDTO carDTO : carListDTO) { + System.out.print(formatOutput(carDTO)); + } + System.out.print("\n"); + } + + public String formatOutput(CarDTO carDTO) { + StringBuilder sb = new StringBuilder(); + sb.append(carDTO.name()).append(" : "); + for (int i = 0; i < carDTO.score(); i++) { + sb.append("-"); + } + sb.append("\n"); + return String.valueOf(sb); + } + + + public void printFinalResult(ArrayList winnerNameList) { + String result = "최종 우승자 : "+String.join(", ",winnerNameList); + System.out.println(result); + + } +} diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 1d35fc33fe..b24287bbd3 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -1,16 +1,162 @@ package racingcar; import camp.nextstep.edu.missionutils.test.NsTest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.List; + 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; +import static org.assertj.core.api.Assertions.*; class ApplicationTest extends NsTest { private static final int MOVING_FORWARD = 4; private static final int STOP = 3; + View view; + Validator validator; + OutputPort outputPort; + + + @BeforeEach + void setUp() { + view = new View(); + validator = new Validator(); + outputPort = new OutputPort(view); + } + +// @Test +// void validateCarName(){ +// assertSimpleTest(() -> { +// String TestInput = "pobi,woni,jun\n"; +// System.setIn(new ByteArrayInputStream(TestInput.getBytes())); +// String test = view.getCarName(); +// assertThat(validator.validateCarName(test)).containsExactly("pobi", "woni", "jun"); +// }); +// } +// +// +// @Test +// void getRoundCount(){ +// assertSimpleTest(() -> { +// String TestInput = "5\n"; +// System.setIn(new ByteArrayInputStream(TestInput.getBytes())); +// String test = view.getRoundCount(); +// assertThat(validator.validateRoundCount(test)).isEqualTo(5); +// }); +// } +// +// @Test +// void makeCarList(){ +// assertSimpleTest(() -> { +// String[] nameList = {"pobi", "woni", "jun"}; +// CarList carList = new CarList(nameList); +// assertThat(carList.toString()).isEqualTo("[pobi:0, woni:0, jun:0]"); +// }); +// } +// +// @Test +// void makeGame(){ +// assertSimpleTest(() -> { +// String[] nameList = {"pobi", "woni", "jun"}; +// CarList carList = new CarList(nameList); +// Game game = new Game(carList, 5); +// assertThat(game.toString()).isEqualTo("[pobi:0, woni:0, jun:0],5,0"); +// }); +// } + +// @Test +// void proceedRoundTrue(){ +// assertSimpleTest(() -> { +// String[] nameList = {"pobi", "woni"}; +// CarList carList = new CarList(nameList); +// Game game = new Game(carList, 1); +// GameService gameService = new GameService(game,outputPort); +// gameService.proceedRound(true); +// assertThat(game.toString()).isEqualTo("[pobi:1, woni:1],1,0"); +// }); +// } +// +// @Test +// void proceedRoundFalse(){ +// assertSimpleTest(() -> { +// String[] nameList = {"pobi", "woni"}; +// CarList carList = new CarList(nameList); +// Game game = new Game(carList, 1); +// GameService gameService = new GameService(game,outputPort); +// gameService.proceedRound(false); +// assertThat(game.toString()).isEqualTo("[pobi:0, woni:0],1,0"); +// }); +// } +// +// @Test +// void runGameTrue(){ +// assertSimpleTest(() -> { +// String[] nameList = {"pobi", "woni"}; +// CarList carList = new CarList(nameList); +// Game game = new Game(carList, 2); +// GameService gameService = new GameService(game,outputPort); +// gameService.runGame(); +// assertThat(game.toString()).isEqualTo("[pobi:2, woni:2],2,3"); +// }); +// } + +// @Test +// void makeCarListDTO(){ +// assertSimpleTest(() -> { +// String[] nameList = {"pobi", "woni"}; +// CarList carList = new CarList(nameList); +// Game game = new Game(carList, 2); +// GameService gameService = new GameService(game,outputPort); +// gameService.runGame(); +// assertThat(game.printCurrentStatus()) +// .extracting(CarDTO::name,CarDTO::score) +// .containsExactly( +// tuple("pobi",2), +// tuple("woni",2) +// ); +// }); +// } +// +// @Test +// void findWinner(){ +// assertSimpleTest(() -> { +// String[] nameList = {"pobi", "woni", "jun"}; +// CarList carList = new CarList(nameList); +// Game game = new Game(carList, 3); +// GameService gameService = new GameService(game,outputPort); +// +// +// assertThat(game.findWinner(new ArrayList( +// List.of( +// new CarDTO("pobi", 2), +// new CarDTO("woni", 0), +// new CarDTO("jun", 2) +// )))).containsExactly("pobi", "jun"); +// }); +// } +// +// @Test +// void printFinalWinnerMulti(){ +// assertSimpleTest(() -> { +// ArrayList winnerNameList = new ArrayList<>(List.of("pobi", "jun")); +// view.printFinalResult(winnerNameList); +// assertThat(output()).isEqualTo("최종 우승자 : pobi, jun"); +// }); +// } +// +// @Test +// void printFinalWinnerOnly(){ +// assertSimpleTest(() -> { +// ArrayList winnerNameList = new ArrayList<>(List.of("pobi")); +// view.printFinalResult(winnerNameList); +// assertThat(output()).isEqualTo("최종 우승자 : pobi"); +// }); +// } +// + @Test void 기능_테스트() {