diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..ab4c1bb2fb6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,67 @@ +## 기능 목록 + +### input +- [x] 다리 길이를 입력 받는다. + - 3~20 사이의 숫자이다. +- [x] 이동할 칸을 입력 받는다. + - 위 칸 -> U, 아래 칸 -> D +- [x] 재시작 여부를 입력 받는다. + - 재시작 -> R, 종료 -> Q + +### inputValidator +- [x] 다리 길이 validate + - 숫자이다. + - 3~20사이의 숫자이다. +- [x] 이동할 칸 validate + - 대문자 영어이다. + - U or D이다. +- [x] 재시작 validate + - 대문자 영어이다. + - R or Q이다. + +### output +1. 다리 화면 출력한다. +- [x] 이동한 칸을 건널 수 있다면 -> O 건널 수 없다면 X +- [x] 결과 출력한다. +- [x] 에러 출력한다. +- [x] 진행 메시지 출력한다. + +### bridge +1. 다리 길이 만큼 다리를 저장한다. +- [x] List 위 칸 -> U, 아래 칸 -> D + - 재시작해도 처음에 만든 다리로 재사용한다. + +### BridgeMaker +- [x] 다리를 만든다. + - 랜덤 숫자 1 -> 위 칸, 0 -> 아래 칸 + +### BridgeMap +- [x] 게임 화면을 만든다. + +### BridgeStageRecord +- [x] 게임 진행 결과를 기록한다. + +## 상수 처리 + +### BridgeMapElement +-[x] 게임 화면 요소 + +### ErrorMessage +-[x] 에러 메시지 + +### GameCommand +-[x] 게임 명령어 + +### GameProgressMessage +-[x] 게임 진행 메시지 + +### GameResult +-[x] 게임 결정 + +## 에러 처리 +- [x] 에러 발생시 [ERROR] 단어가 포함 되어있어야 한다. +- [x] 그 부분 부터 다시 입력 받는다. + +- [x] 다리 길이는 3~20 사이의 숫자이다. +- [x] 이동할 칸 명령어는 'U', 'D' 이다. +- [x] 재시작 여부 명령어는 'R', 'Q' 이다. \ No newline at end of file diff --git a/src/main/java/bridge/Application.java b/src/main/java/bridge/Application.java index 5cb72dfd3de..758e9c0976e 100644 --- a/src/main/java/bridge/Application.java +++ b/src/main/java/bridge/Application.java @@ -1,8 +1,11 @@ package bridge; +import bridge.controller.BridgeGameController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + BridgeGameController bridgeGameController = new BridgeGameController(); + bridgeGameController.startGame(); } } diff --git a/src/main/java/bridge/BridgeGame.java b/src/main/java/bridge/BridgeGame.java deleted file mode 100644 index 834c1c8362b..00000000000 --- a/src/main/java/bridge/BridgeGame.java +++ /dev/null @@ -1,23 +0,0 @@ -package bridge; - -/** - * 다리 건너기 게임을 관리하는 클래스 - */ -public class BridgeGame { - - /** - * 사용자가 칸을 이동할 때 사용하는 메서드 - *

- * 이동을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. - */ - public void move() { - } - - /** - * 사용자가 게임을 다시 시도할 때 사용하는 메서드 - *

- * 재시작을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. - */ - public void retry() { - } -} diff --git a/src/main/java/bridge/InputView.java b/src/main/java/bridge/InputView.java deleted file mode 100644 index c3911c8a8e7..00000000000 --- a/src/main/java/bridge/InputView.java +++ /dev/null @@ -1,28 +0,0 @@ -package bridge; - -/** - * 사용자로부터 입력을 받는 역할을 한다. - */ -public class InputView { - - /** - * 다리의 길이를 입력받는다. - */ - public int readBridgeSize() { - return 0; - } - - /** - * 사용자가 이동할 칸을 입력받는다. - */ - public String readMoving() { - return null; - } - - /** - * 사용자가 게임을 다시 시도할지 종료할지 여부를 입력받는다. - */ - public String readGameCommand() { - return null; - } -} diff --git a/src/main/java/bridge/OutputView.java b/src/main/java/bridge/OutputView.java deleted file mode 100644 index 69a433a6285..00000000000 --- a/src/main/java/bridge/OutputView.java +++ /dev/null @@ -1,23 +0,0 @@ -package bridge; - -/** - * 사용자에게 게임 진행 상황과 결과를 출력하는 역할을 한다. - */ -public class OutputView { - - /** - * 현재까지 이동한 다리의 상태를 정해진 형식에 맞춰 출력한다. - *

- * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. - */ - public void printMap() { - } - - /** - * 게임의 최종 결과를 정해진 형식에 맞춰 출력한다. - *

- * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. - */ - public void printResult() { - } -} diff --git a/src/main/java/bridge/constant/BridgeMapElement.java b/src/main/java/bridge/constant/BridgeMapElement.java new file mode 100644 index 00000000000..0bd688e2cce --- /dev/null +++ b/src/main/java/bridge/constant/BridgeMapElement.java @@ -0,0 +1,29 @@ +package bridge.constant; + +public enum BridgeMapElement { + START("[ "), + SUCCESS("O"), + FAIL("X"), + DIVISION(" | "), + BLANK(" "), + END(" ]"); + + private final String value; + + BridgeMapElement(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + public static BridgeMapElement getStageWindowElement(boolean canMoveStage) { + if (canMoveStage){ + return SUCCESS; + } + return FAIL; + } + +} diff --git a/src/main/java/bridge/constant/ErrorMessage.java b/src/main/java/bridge/constant/ErrorMessage.java new file mode 100644 index 00000000000..9af68a17ce4 --- /dev/null +++ b/src/main/java/bridge/constant/ErrorMessage.java @@ -0,0 +1,26 @@ +package bridge.constant; + +public enum ErrorMessage { + + // bridge + BRIDGE_LENGTH_ERROR("다리 길이는 %s~%s 사이의 숫자입니다."), + BRIDGE_STAGE_COMMAND_ERROR("이동할 칸은 %s 혹은 %s 입니다."), + BRIDGE_RETRY_COMMAND_ERROR("게임 다시 시도 여부 명령어는 '%s' 혹은 '%s' 입니다."), + + // global + INPUT_NOT_NUMBER_ERROR("숫자가 아닙니다."), + INPUT_NOT_UPPER_ENGLISH_ERROR("대문자 영어가 아닙니다."); + + + private static final String error = "[ERROR] "; + private final String message; + + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return error + message; + } +} diff --git a/src/main/java/bridge/constant/GameCommand.java b/src/main/java/bridge/constant/GameCommand.java new file mode 100644 index 00000000000..3aa4cfc1724 --- /dev/null +++ b/src/main/java/bridge/constant/GameCommand.java @@ -0,0 +1,49 @@ +package bridge.constant; + +public enum GameCommand { + UP_STAIR("U"), + DOWN_STAIR("D"), + BRIDGE_RETRY("R"), + BRIDGE_QUITE("Q"); + + + private final static int DOWN_RANDOM_NUMBER = 0; + private final String command; + + GameCommand(String command) { + this.command = command; + } + + public String getCommand() { + return command; + } + + @Override + public String toString() { + return command; + } + + public static String bridgeStageCase(int num){ + if (num == DOWN_RANDOM_NUMBER){ + return DOWN_STAIR.getCommand(); + } + return UP_STAIR.getCommand(); + } + + public static boolean isNotBridgeStageCommand(String bridgeStage) { + return !bridgeStage.equals(UP_STAIR.getCommand()) && !bridgeStage.equals(DOWN_STAIR.getCommand()); + } + + public static boolean isBridgeStageCommandU(String bridgeStage){ + return bridgeStage.equals(UP_STAIR.getCommand()); + } + + public static boolean isNotBridgeRetryCommand(String bridgeRetry){ + return !bridgeRetry.equals(BRIDGE_RETRY.getCommand()) && !bridgeRetry.equals(BRIDGE_QUITE.getCommand()); + } + + public static boolean isBridgeRetryCommandR(String bridgeRetry){ + return bridgeRetry.equals(BRIDGE_RETRY.getCommand()); + } + +} diff --git a/src/main/java/bridge/constant/GameProgressMessage.java b/src/main/java/bridge/constant/GameProgressMessage.java new file mode 100644 index 00000000000..6830e423225 --- /dev/null +++ b/src/main/java/bridge/constant/GameProgressMessage.java @@ -0,0 +1,27 @@ +package bridge.constant; + +public enum GameProgressMessage { + + BRIDGE_GAME_START("다리 건너기 게임을 시작합니다."), + + // input + INPUT_BRIDGE_LENGTH("다리의 길이를 입력해주세요."), + INPUT_BRIDGE_STAGE("이동할 칸을 선택해주세요. (위: %s, 아래: %s)"), + INPUT_BRIDGE_RETRY("게임을 다시 시도할지 여부를 입력해주세요. (재시도: %s, 종료: %s)"), + + //output + OUTPUT_FINAL_GAME_RESULT("최종 게임 결과"), + OUTPUT_BRIDGE_SUCCESS_OR_NOT("게임 성공 여부: %s"), + OUTPUT_BRIDGE_ATTEMPT_RESULT("총 시도한 횟수: %d"); + + private final String message; + + GameProgressMessage(String message) { + this.message = message; + } + + @Override + public String toString() { + return message; + } +} diff --git a/src/main/java/bridge/constant/GameResult.java b/src/main/java/bridge/constant/GameResult.java new file mode 100644 index 00000000000..66872079b5c --- /dev/null +++ b/src/main/java/bridge/constant/GameResult.java @@ -0,0 +1,28 @@ +package bridge.constant; + +import bridge.model.BridgeStageRecord; + +public enum GameResult { + + + GAME_SUCCESS("성공"), + GAME_FAILED("실패"); + + private final String message; + + GameResult(String message) { + this.message = message; + } + + public static String gameResult(){ + if (BridgeStageRecord.clearsGame()){ + return GAME_SUCCESS.message; + } + return GAME_FAILED.message; + } + + @Override + public String toString() { + return message; + } +} diff --git a/src/main/java/bridge/controller/BridgeGameController.java b/src/main/java/bridge/controller/BridgeGameController.java new file mode 100644 index 00000000000..44d50426976 --- /dev/null +++ b/src/main/java/bridge/controller/BridgeGameController.java @@ -0,0 +1,46 @@ +package bridge.controller; + +import bridge.model.BridgeGame; +import bridge.model.BridgeStageRecord; +import bridge.view.InputView; +import bridge.view.OutputView; + +public class BridgeGameController { + + private final InputView inputView; + private final OutputView outputView; + private final BridgeGame bridgeGame; + + public BridgeGameController() { + this.inputView = new InputView(); + this.outputView = new OutputView(); + this.bridgeGame = new BridgeGame(); + } + + public void startGame(){ + makeGame(); + progressGame(); + } + + private void makeGame() { + outputView.printGameStart(); + int bridgeSize = inputView.readBridgeSize(); + bridgeGame.makeBridge(bridgeSize); + } + + private void progressGame() { + while (BridgeStageRecord.isProgressing()){ + String stage = inputView.readMoving(); + bridgeGame.move(stage); + outputView.printMap(bridgeGame.getBridgeMap()); + ifFailStageThenRetryGameOrQuit(); + } + outputView.printResult(bridgeGame.getBridgeMap()); + } + + private void ifFailStageThenRetryGameOrQuit(){ + if (BridgeStageRecord.canNotMoveNextStage()){ + bridgeGame.retry(inputView.readGameCommand()); + } + } +} diff --git a/src/main/java/bridge/model/Bridge.java b/src/main/java/bridge/model/Bridge.java new file mode 100644 index 00000000000..8cdc8ffca7c --- /dev/null +++ b/src/main/java/bridge/model/Bridge.java @@ -0,0 +1,29 @@ +package bridge.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Bridge { + private static final int DEFAULT_STAGE_NUM = 1; + private final List bridge; + public Bridge(List bridge) { + this.bridge = new ArrayList<>(bridge); + } + + public List getBridge() { + return Collections.unmodifiableList(bridge); + } + + public int stages(){ + return bridge.size(); + } + + public boolean canMoveStage(String bridgeStage, int curStage){ + return bridge.get(curStage).equals(bridgeStage); + } + + public boolean clearsAllStage(int curStage, boolean canMoveStage) { + return canMoveStage && stages() == curStage+DEFAULT_STAGE_NUM; + } +} diff --git a/src/main/java/bridge/model/BridgeGame.java b/src/main/java/bridge/model/BridgeGame.java new file mode 100644 index 00000000000..901823def4f --- /dev/null +++ b/src/main/java/bridge/model/BridgeGame.java @@ -0,0 +1,61 @@ +package bridge.model; + +import bridge.BridgeRandomNumberGenerator; +import bridge.constant.GameCommand; + +/** + * 다리 건너기 게임을 관리하는 클래스 + */ +public class BridgeGame { + + private static final boolean CLEARS_GAME = true; + private final BridgeMaker bridgeMaker; + private final BridgeMap bridgeMap; + private Bridge bridge; + + public BridgeGame() { + this.bridgeMaker = new BridgeMaker(new BridgeRandomNumberGenerator()); + this.bridgeMap = new BridgeMap(); + } + + public void makeBridge(int bridge){ + this.bridge = new Bridge(bridgeMaker.makeBridge(bridge)); + } + + + /** + * 사용자가 칸을 이동할 때 사용하는 메서드 + *

+ * 이동을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. + */ + public void move(String bridgeStageCommand) { + int curStage = BridgeStageRecord.getCurStage(); + boolean canMoveStage = bridge.canMoveStage(bridgeStageCommand, curStage); + bridgeMap.moveStair(bridgeStageCommand, canMoveStage); + checkStageStatus(canMoveStage); + BridgeStageRecord.nextStage(); + } + + /** + * 사용자가 게임을 다시 시도할 때 사용하는 메서드 + *

+ * 재시작을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. + */ + public void retry(String bridgeRetryCommand) { + if (GameCommand.isBridgeRetryCommandR(bridgeRetryCommand)){ + BridgeStageRecord.reset(); + bridgeMap.reset(); + } + } + + private void checkStageStatus(boolean canMoveStage){ + if (bridge.clearsAllStage(BridgeStageRecord.getCurStage(), canMoveStage)) { + BridgeStageRecord.changeProgressStatus(CLEARS_GAME); + } + BridgeStageRecord.changeMoveStatus(canMoveStage); + } + + public BridgeMap getBridgeMap() { + return bridgeMap; + } +} diff --git a/src/main/java/bridge/BridgeMaker.java b/src/main/java/bridge/model/BridgeMaker.java similarity index 61% rename from src/main/java/bridge/BridgeMaker.java rename to src/main/java/bridge/model/BridgeMaker.java index 27e9f2cfa7f..d802e370f82 100644 --- a/src/main/java/bridge/BridgeMaker.java +++ b/src/main/java/bridge/model/BridgeMaker.java @@ -1,6 +1,11 @@ -package bridge; +package bridge.model; + +import bridge.BridgeNumberGenerator; +import bridge.constant.GameCommand; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 다리의 길이를 입력 받아서 다리를 생성해주는 역할을 한다. @@ -18,6 +23,9 @@ public BridgeMaker(BridgeNumberGenerator bridgeNumberGenerator) { * @return 입력받은 길이에 해당하는 다리 모양. 위 칸이면 "U", 아래 칸이면 "D"로 표현해야 한다. */ public List makeBridge(int size) { - return null; + return IntStream.range(0, size) + .mapToObj(i -> bridgeNumberGenerator.generate()) + .map(GameCommand::bridgeStageCase) + .collect(Collectors.toList()); } } diff --git a/src/main/java/bridge/model/BridgeMap.java b/src/main/java/bridge/model/BridgeMap.java new file mode 100644 index 00000000000..b990f00c953 --- /dev/null +++ b/src/main/java/bridge/model/BridgeMap.java @@ -0,0 +1,68 @@ +package bridge.model; + +import bridge.constant.BridgeMapElement; +import bridge.constant.GameCommand; + +import java.util.ArrayList; +import java.util.List; + +public class BridgeMap { + + private static final int INIT_SIZE = 2; + private static final int LENGTH_ONE = 1; + + private final List upStair = new ArrayList<>(); + private final List downStair = new ArrayList<>(); + + public BridgeMap(){ + init(); + } + + private void init(){ + upStair.add(BridgeMapElement.START); + upStair.add(BridgeMapElement.END); + downStair.add(BridgeMapElement.START); + downStair.add(BridgeMapElement.END); + } + + public void reset() { + upStair.clear(); + downStair.clear(); + init(); + } + + public void moveStair(String bridgeStair, boolean canMoveStage) { + inputDivision(); + BridgeMapElement windowElement = BridgeMapElement.getStageWindowElement(canMoveStage); + + if (GameCommand.isBridgeStageCommandU(bridgeStair)) { + upStair.add(getInsertMiddle(upStair), windowElement); + downStair.add(getInsertMiddle(downStair), BridgeMapElement.BLANK); + return; + } + upStair.add(getInsertMiddle(upStair), BridgeMapElement.BLANK); + downStair.add(getInsertMiddle(downStair), windowElement); + } + + private void inputDivision(){ + if (upStair.size() == INIT_SIZE){ + return; + } + upStair.add(getInsertMiddle(upStair), BridgeMapElement.DIVISION); + downStair.add(getInsertMiddle(downStair), BridgeMapElement.DIVISION); + } + + private int getInsertMiddle(List stair){ + return stair.size() - LENGTH_ONE; + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + upStair.forEach(stringBuilder::append); + stringBuilder.append("\n"); + downStair.forEach(stringBuilder::append); + stringBuilder.append("\n"); + return stringBuilder.toString(); + } +} diff --git a/src/main/java/bridge/model/BridgeStageRecord.java b/src/main/java/bridge/model/BridgeStageRecord.java new file mode 100644 index 00000000000..aa239a35ec0 --- /dev/null +++ b/src/main/java/bridge/model/BridgeStageRecord.java @@ -0,0 +1,48 @@ +package bridge.model; + +public class BridgeStageRecord { + + private static final int firstStage = 0; + private static int curStage = firstStage; + private static boolean canMoveNextStage = true; + private static boolean clearsGame = false; + private static int totalAttemptCount = 1; + + public static int getCurStage() { + return curStage; + } + + public static void nextStage(){ + curStage++; + } + + public static void reset(){ + curStage = firstStage; + canMoveNextStage = true; + totalAttemptCount++; + } + + public static void changeMoveStatus(boolean stageStatus){ + canMoveNextStage = stageStatus; + } + + public static void changeProgressStatus(boolean stageStatus) { + clearsGame = stageStatus; + } + + public static boolean isProgressing(){ + return canMoveNextStage && !clearsGame; + } + + public static boolean canNotMoveNextStage() { + return !canMoveNextStage; + } + + public static boolean clearsGame() { + return clearsGame; + } + + public static int getTotalAttemptCount() { + return totalAttemptCount; + } +} diff --git a/src/main/java/bridge/view/InputValidator.java b/src/main/java/bridge/view/InputValidator.java new file mode 100644 index 00000000000..84be3f63ffe --- /dev/null +++ b/src/main/java/bridge/view/InputValidator.java @@ -0,0 +1,73 @@ +package bridge.view; + + +import bridge.constant.ErrorMessage; +import bridge.constant.GameCommand; + +public class InputValidator { + + private static final int MIN_SIZE = 3; + private static final int MAX_SIZE = 20; + private static final String NUMBER_REGEX = "^[0-9]*$"; + private static final String UPPER_ENGLISH_REGEX ="^[A-Z]*$"; + + public void validateBridgeSize(String bridgeSize){ + validateInputNumber(bridgeSize); + validateBridgeLength(bridgeSize); + } + + public void validateBridgeStage(String bridgeStage) { + validateInputUpperEnglish(bridgeStage); + validateBridgeStageCommand(bridgeStage); + } + + public void validateBridgeRetry(String retryCommand) { + validateInputUpperEnglish(retryCommand); + validateInputRetryCommand(retryCommand); + } + + private void validateInputNumber(String number) { + if (isNotNumeric(number)) { + throw new IllegalArgumentException(ErrorMessage.INPUT_NOT_NUMBER_ERROR.getMessage()); + } + } + + private boolean isNotNumeric(String str) { + return !str.matches(NUMBER_REGEX); + } + + private void validateBridgeLength(String number) { + int bridgeLength = Integer.parseInt(number); + if(isNotBridgeLength(bridgeLength)){ + throw new IllegalArgumentException(String.format(ErrorMessage.BRIDGE_LENGTH_ERROR.getMessage(), MIN_SIZE, MAX_SIZE)); + } + } + + private boolean isNotBridgeLength(int bridgeLength){ + return bridgeLength < MIN_SIZE || bridgeLength > MAX_SIZE; + } + + private void validateInputUpperEnglish(String bridgeStage) { + if (isNotUpperEnglish(bridgeStage)){ + throw new IllegalArgumentException(ErrorMessage.INPUT_NOT_UPPER_ENGLISH_ERROR.getMessage()); + } + } + + private boolean isNotUpperEnglish(String str){ + return !str.matches(UPPER_ENGLISH_REGEX); + } + + private void validateBridgeStageCommand(String bridgeStage) { + if(GameCommand.isNotBridgeStageCommand(bridgeStage)){ + throw new IllegalArgumentException(String.format(ErrorMessage.BRIDGE_STAGE_COMMAND_ERROR.getMessage() + , GameCommand.UP_STAIR, GameCommand.DOWN_STAIR)); + } + } + + private void validateInputRetryCommand(String retryCommand) { + if (GameCommand.isNotBridgeRetryCommand(retryCommand)){ + throw new IllegalArgumentException(String.format(ErrorMessage.BRIDGE_STAGE_COMMAND_ERROR.getMessage() + , GameCommand.BRIDGE_RETRY, GameCommand.BRIDGE_QUITE)); + } + } +} diff --git a/src/main/java/bridge/view/InputView.java b/src/main/java/bridge/view/InputView.java new file mode 100644 index 00000000000..61f2d4d2bdc --- /dev/null +++ b/src/main/java/bridge/view/InputView.java @@ -0,0 +1,91 @@ +package bridge.view; +import camp.nextstep.edu.missionutils.Console; +/** + * 사용자로부터 입력을 받는 역할을 한다. + */ +public class InputView { + + private final InputValidator inputValidator; + private final OutputView outputView; + + public InputView() { + this.inputValidator = new InputValidator(); + this.outputView = new OutputView(); + } + + /** + * 다리의 길이를 입력받는다. + */ + public int readBridgeSize() { + String input; + do { + input = inputBridgeSize(); + } while (isNotInputNull(input)); + + return Integer.parseInt(input); + } + + private String inputBridgeSize() { + outputView.printInputBridgeSize(); + String bridgeSize = Console.readLine(); + outputView.printEnter(); + try { + inputValidator.validateBridgeSize(bridgeSize); + } catch (IllegalArgumentException e){ + outputView.printErrorMessage(e.getMessage()); + return null; + } + return bridgeSize; + } + + /** + * 사용자가 이동할 칸을 입력받는다. + */ + public String readMoving() { + String input; + do { + input = inputBridgeStage(); + }while (isNotInputNull(input)); + + return input; + } + + private String inputBridgeStage() { + outputView.printInputBridgeStage(); + String bridgeStage = Console.readLine(); + try { + inputValidator.validateBridgeStage(bridgeStage); + }catch (IllegalArgumentException e){ + outputView.printErrorMessage(e.getMessage()); + return null; + } + return bridgeStage; + } + + /** + * 사용자가 게임을 다시 시도할지 종료할지 여부를 입력받는다. + */ + public String readGameCommand(){ + String input; + do{ + input = inputRetryCommand(); + }while (isNotInputNull(input)); + return input; + } + + private String inputRetryCommand() { + outputView.printInputBridgeRetry(); + String retryCommand = Console.readLine(); + try { + inputValidator.validateBridgeRetry(retryCommand); + } catch (IllegalArgumentException e){ + outputView.printErrorMessage(e.getMessage()); + return null; + } + return retryCommand; + } + + private boolean isNotInputNull(String input) { + return input == null; + } +} diff --git a/src/main/java/bridge/view/OutputView.java b/src/main/java/bridge/view/OutputView.java new file mode 100644 index 00000000000..09dd31d175d --- /dev/null +++ b/src/main/java/bridge/view/OutputView.java @@ -0,0 +1,65 @@ +package bridge.view; + +import bridge.model.BridgeMap; +import bridge.model.BridgeStageRecord; +import bridge.constant.GameCommand; +import bridge.constant.GameProgressMessage; +import bridge.constant.GameResult; + +/** + * 사용자에게 게임 진행 상황과 결과를 출력하는 역할을 한다. + */ +public class OutputView { + + /** + * 현재까지 이동한 다리의 상태를 정해진 형식에 맞춰 출력한다. + *

+ * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. + */ + public void printMap(BridgeMap bridgeMap) { + System.out.println(bridgeMap); + } + + /** + * 게임의 최종 결과를 정해진 형식에 맞춰 출력한다. + *

+ * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. + */ + public void printResult(BridgeMap bridgeMap) { + System.out.println(GameProgressMessage.OUTPUT_FINAL_GAME_RESULT); + System.out.println(bridgeMap); + System.out.println(String.format(GameProgressMessage.OUTPUT_BRIDGE_SUCCESS_OR_NOT.toString() + , GameResult.gameResult())); + System.out.println(String.format(GameProgressMessage.OUTPUT_BRIDGE_ATTEMPT_RESULT.toString() + , BridgeStageRecord.getTotalAttemptCount())); + } + + public void printErrorMessage(String errorMessage){ + System.out.println(errorMessage); + } + + public void printGameStart() { + System.out.println(GameProgressMessage.BRIDGE_GAME_START); + printEnter(); + } + + public void printInputBridgeSize() { + System.out.println(GameProgressMessage.INPUT_BRIDGE_LENGTH); + } + + public void printInputBridgeStage(){ + String message = String.format(GameProgressMessage.INPUT_BRIDGE_STAGE.toString() + , GameCommand.UP_STAIR, GameCommand.DOWN_STAIR); + System.out.println(message); + } + + public void printInputBridgeRetry(){ + String message = String.format(GameProgressMessage.INPUT_BRIDGE_RETRY.toString() + , GameCommand.BRIDGE_RETRY, GameCommand.BRIDGE_QUITE); + System.out.println(message); + } + + public void printEnter(){ + System.out.println(); + } +} diff --git a/src/test/java/bridge/ApplicationTest.java b/src/test/java/bridge/ApplicationTest.java index 1a163ec0a2a..51b833ff850 100644 --- a/src/test/java/bridge/ApplicationTest.java +++ b/src/test/java/bridge/ApplicationTest.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.util.Lists.newArrayList; +import bridge.model.BridgeMaker; import camp.nextstep.edu.missionutils.test.NsTest; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/src/test/java/bridge/BridgeGameTest.java b/src/test/java/bridge/BridgeGameTest.java new file mode 100644 index 00000000000..6e95e9dccf8 --- /dev/null +++ b/src/test/java/bridge/BridgeGameTest.java @@ -0,0 +1,26 @@ +package bridge; + +import bridge.model.BridgeGame; +import bridge.model.BridgeStageRecord; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.*; + +class BridgeGameTest { + + BridgeGame bridgeGame = new BridgeGame(); + + @Test + @DisplayName("게임 재시작 시 현재 stage와 이동 여부를 기본값으로 reset되는지 확인하다.") + void checkBridgeStageRecordReset(){ + //given + bridgeGame.retry("R"); + + //when + boolean canNotMoveNextStage = BridgeStageRecord.canNotMoveNextStage(); + + //then + assertThat(canNotMoveNextStage).isFalse(); + } + +} diff --git a/src/test/java/bridge/BridgeMakerTest.java b/src/test/java/bridge/BridgeMakerTest.java new file mode 100644 index 00000000000..e447f487777 --- /dev/null +++ b/src/test/java/bridge/BridgeMakerTest.java @@ -0,0 +1,71 @@ +package bridge; + +import bridge.model.Bridge; +import bridge.model.BridgeMaker; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.util.Lists.newArrayList; + +class BridgeMakerTest { + + BridgeNumberGenerator numberGenerator = new ApplicationTest.TestNumberGenerator(newArrayList(1, 0, 0)); + BridgeMaker bridgeMaker = new BridgeMaker(numberGenerator); + List bridge = bridgeMaker.makeBridge(3); + + + @Test + @DisplayName("다리가 'U','D'로 구성되어 있는지 확인한다.") + void BridgeComposeByUAndD(){ + assertThat(bridge).containsExactly("U", "D", "D"); + } + + @Test + @DisplayName("입력된 길이 만큼 다리가 크기가 생성이 된는지 확인한다.") + void makeBridgeSize(){ + assertThat(bridge.size()).isEqualTo(3); + } + + @Test + @DisplayName("생성된 다리가 Bridge에 저장이 되는지 확인한다.") + void saveBridge(){ + //given + Bridge saveBridge = new Bridge(bridge); + + //when + List savedBridge = saveBridge.getBridge(); + + //then + assertThat(savedBridge).isEqualTo(bridge); + } + + @Test + @DisplayName("bridge에 입력할 칸이 맞으면 true를 반환하는지 확인한다.") + void checkBridgeStage(){ + //given + Bridge saveBridge = new Bridge(bridge); + + //when + boolean stageStatus = saveBridge.canMoveStage("U", 0); + + //then + assertThat(stageStatus).isTrue(); + } + + @Test + @DisplayName("bridge를 완주했을 때 false를 반환하는지 확인한다.") + void checkBridgeStatus(){ + //given + Bridge saveBridge = new Bridge(bridge); + + //when + boolean stageStatus = saveBridge.clearsAllStage(3, true); + + //then + assertThat(stageStatus).isFalse(); + } + + +} diff --git a/src/test/java/bridge/BridgeWindowTest.java b/src/test/java/bridge/BridgeWindowTest.java new file mode 100644 index 00000000000..f926d709074 --- /dev/null +++ b/src/test/java/bridge/BridgeWindowTest.java @@ -0,0 +1,16 @@ +package bridge; + +import bridge.constant.BridgeMapElement; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.*; +class BridgeWindowTest { + + @Test + @DisplayName("입력한 칸이 실패했을 때 FAIL을 반환하는지 확인한다.") + void getBridgeWindowElement(){ + BridgeMapElement bridgeWindowElement = BridgeMapElement.getStageWindowElement(false); + assertThat(bridgeWindowElement).isEqualTo(BridgeMapElement.FAIL); + } + +} diff --git a/src/test/java/bridge/GameResultTest.java b/src/test/java/bridge/GameResultTest.java new file mode 100644 index 00000000000..26149d79391 --- /dev/null +++ b/src/test/java/bridge/GameResultTest.java @@ -0,0 +1,17 @@ +package bridge; + +import bridge.constant.GameResult; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class GameResultTest { + + @Test + @DisplayName("게임 결과 메시지를 반환하는지 확인한다.") + void getBridgeGameResult(){ + String gameResult = GameResult.gameResult(); + assertThat(gameResult).isEqualTo(GameResult.GAME_FAILED.toString()); + } +} diff --git a/src/test/java/bridge/InputValidatorTest.java b/src/test/java/bridge/InputValidatorTest.java new file mode 100644 index 00000000000..e4423d30991 --- /dev/null +++ b/src/test/java/bridge/InputValidatorTest.java @@ -0,0 +1,57 @@ +package bridge; + +import bridge.view.InputValidator; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.*; + +class InputValidatorTest { + + InputValidator inputValidator = new InputValidator(); + + @Test + @DisplayName("다리 길이가 숫자가 아니면 에러가 발생한다.") + void validateBridgeIsNumber(){ + assertThatThrownBy(() -> inputValidator.validateBridgeSize("ㅁ")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("다리 길이가 3~20사이가 아니면 에러가 발생한다.") + void validateBridgeLength(){ + assertThatThrownBy(() -> inputValidator.validateBridgeSize("21")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("이동할 칸은 대문자 영어가 아니면 에러가 발생한다.") + void validateBridgeStageUpperEnglish(){ + assertThatThrownBy(() -> inputValidator.validateBridgeStage("u")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("이동할 칸은 'U' 혹은 'D'가 아니면 에러가 발생한다.") + void validateBridgeStage(){ + assertThatThrownBy(() -> inputValidator.validateBridgeStage("T")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("재시도 여부 명령어가 대문자 영어가 아니면 에러가 발생한다.") + void validateBridgeRetryUpperEnglish(){ + assertThatThrownBy(() -> inputValidator.validateBridgeRetry("r")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("재시도 여부 명령어가 'R' 혹은 'Q'가 아니면 에러가 발생한다.") + void validateBridgeRetry(){ + assertThatThrownBy(() -> inputValidator.validateBridgeRetry("T")) + .isInstanceOf(IllegalArgumentException.class); + } + + + + +}