diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..777dc1d0be8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,66 @@ +## 기능 구현 목록 + +**입력 & 출력** +- [x] 게임 시작 안내 출력 +- [x] 다리 길이 입력 (3 ~ 20사이 숫자) +- [x] 이동할 칸 입력 (U / D) +- [x] 재시도 입력 (R / Q) + +**다리 생성** +- 입력한 다리 길이만큼 다리 생성 + - [x] 0(아래), 1(위)형식으로 다리 요소 랜덤 생성 + - [x] 0➝D / 1➝U 변환 + +**게임 진행** +- [x] 생성된 다리와 사용자의 현입력 비교 + - [x] 이동 가능한 경우 계속 진행 + - [x] 이동 불가능한 경우 재시작 / 종료 +- [x] 재시작하는 경우 시도 횟수 증가 + +**게임 진행 내용 저장** +- [x] 사용자의 입력이 맞는지 저장한다 + - 맞을 경우 O, 틀릴 경우 X 저장 + +**결과 출력** +- 다리 출력 + - [x] `[`로 시작, `]`로 끝, 구분자는 `|` + - [x] 이동 가능한 경우 O / 불가능한 경우 X 출력 +- [x] 게임 성공 여부 +- [x] 총 시도 횟수 + +**예외 처리** +- 이동할 칸 입력 + - [x] U/D 이외의 입력이 들어올 경우 +- 재시도 입력 + - [x] R/Q 이외의 입력이 들어올 경우 +- 다리 길이 입력 + - [x] 숫자가 아닌 입력이 들어온 경우 + - [x] 범위 이외의 숫자가 들어온 경우 + +### 테스트 목록 + +**BridgeMaker** +- [x] 다리 길이 입력시 길이에 맞는 다리를 생성하는가 + +**InputValidator** +- [x] 움직임 입력 시 U/D 이외의 문자를 입력하면 예외가 발생하는가 +- [x] 다리 길이를 입력할 경우 문자를 입력하면 예외가 발생하는가 + + +**RetryCommand** +- [x] R, Q를 입력할 경우 올바른 상수를 가져오는가 +- [x] 재시작 입력 시 R/Q 이외의 문자를 입력하면 예외가 발생하는가 + +**BridgeSize** +- [x] 다리 길이를 입력할 경우 3~20 이외의 숫자를 입력하면 예외가 발생하는가 + +**BridgeDirection** +- [x] 0 = D, 1 = U로 변환을 하는가 + +**BridgeGame** +- [ ] 게임 진행 시 사용자가 입력한 내용과 다리의 내용이 일치하는지 확인 +- [ ] 게임 재시작 시 재시작 카운트 증가 +- [ ] 게임 재시작 시 사용자 위치, 다리 건넌 기록 초기화 + +**Bridge** +- [x] 해당 방향으로 다리를 건너갈 수 있는지 확인 \ No newline at end of file diff --git a/src/main/java/bridge/Application.java b/src/main/java/bridge/Application.java index 5cb72dfd3de..29ccf2dd4b0 100644 --- a/src/main/java/bridge/Application.java +++ b/src/main/java/bridge/Application.java @@ -1,8 +1,11 @@ package bridge; +import bridge.controller.BridgeController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + BridgeController bridgeController = new BridgeController(); + bridgeController.start(); } -} +} \ No newline at end of file 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/BridgeMaker.java b/src/main/java/bridge/BridgeMaker.java index 27e9f2cfa7f..e3ef6aa8a0f 100644 --- a/src/main/java/bridge/BridgeMaker.java +++ b/src/main/java/bridge/BridgeMaker.java @@ -1,6 +1,11 @@ package bridge; +import bridge.constant.BridgeDirection; + +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 다리의 길이를 입력 받아서 다리를 생성해주는 역할을 한다. @@ -18,6 +23,13 @@ public BridgeMaker(BridgeNumberGenerator bridgeNumberGenerator) { * @return 입력받은 길이에 해당하는 다리 모양. 위 칸이면 "U", 아래 칸이면 "D"로 표현해야 한다. */ public List makeBridge(int size) { - return null; + List bridge = new ArrayList<>(); + IntStream.range(0, size) + .forEach(i -> bridge.add(bridgeNumberGenerator.generate())); + return bridge.stream() + .map(BridgeDirection::convertDirection) + .map(BridgeDirection::getDirection) + .collect(Collectors.toList() + ); } } 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/BridgeDirection.java b/src/main/java/bridge/constant/BridgeDirection.java new file mode 100644 index 00000000000..413a5cb117a --- /dev/null +++ b/src/main/java/bridge/constant/BridgeDirection.java @@ -0,0 +1,33 @@ +package bridge.constant; + +import java.util.Arrays; + +public enum BridgeDirection { + + DOWN(0, "D"), + UP(1, "U"); + + private final int generateValue; + private final String direction; + + + BridgeDirection(int generateValue, String direction) { + this.generateValue = generateValue; + this.direction = direction; + } + + public static BridgeDirection convertDirection(int generateValue) { + return Arrays.stream(BridgeDirection.values()) + .filter(value -> value.generateValue == generateValue) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.NOT_BRIDGE_ELEMENT.toString())); + } + + public static boolean isNotDirectionCommand(String direction) { + return !(direction.equals(UP.direction) || direction.equals(DOWN.direction)); + } + + public String getDirection() { + return direction; + } +} diff --git a/src/main/java/bridge/constant/BridgeElement.java b/src/main/java/bridge/constant/BridgeElement.java new file mode 100644 index 00000000000..40ed88e2bca --- /dev/null +++ b/src/main/java/bridge/constant/BridgeElement.java @@ -0,0 +1,19 @@ +package bridge.constant; + +public enum BridgeElement { + BRIDGE_START("[ "), + BRIDGE_END(" ]"), + BRIDGE_DELIMITER(" | "); + + + private final String element; + + BridgeElement(String element) { + this.element = element; + } + + @Override + public String toString() { + return this.element; + } +} diff --git a/src/main/java/bridge/constant/ExceptionMessage.java b/src/main/java/bridge/constant/ExceptionMessage.java new file mode 100644 index 00000000000..86b8174ca15 --- /dev/null +++ b/src/main/java/bridge/constant/ExceptionMessage.java @@ -0,0 +1,23 @@ +package bridge.constant; + +public enum ExceptionMessage { + + NOT_BRIDGE_ELEMENT("다리 건너기 게임을 시작합니다."), + MOVING_RESULT_NONE("해당 방향으로 움직일 수 없습니다."), + INCORRECT_MOVING("U 또는 D로만 이동할 수 있습니다."), + INCORRECT_RETRY("R 또는 Q만 입력할 수 있습니다."), + NOT_INTEGER("숫자만 입력할 수 있습니다."), + INCORRECT_RANGE("다리 길이는 3부터 20 사이의 숫자여야 합니다."),; + + private static final String PREFIX = "[ERROR] "; + private final String message; + + ExceptionMessage(String message) { + this.message = message; + } + + @Override + public String toString() { + return PREFIX + message; + } +} \ No newline at end of file diff --git a/src/main/java/bridge/constant/GameClearMessage.java b/src/main/java/bridge/constant/GameClearMessage.java new file mode 100644 index 00000000000..dc67c4c7175 --- /dev/null +++ b/src/main/java/bridge/constant/GameClearMessage.java @@ -0,0 +1,20 @@ +package bridge.constant; + +public enum GameClearMessage { + + GAME_CLEAR("성공"), + GAME_FAILED("실패"); + + private final String message; + + GameClearMessage(String message) { + this.message = message; + } + + public static String getGameClearMessage(boolean isClear) { + if(isClear) { + return GAME_CLEAR.message; + } + return GAME_FAILED.message; + } +} diff --git a/src/main/java/bridge/constant/GameStatus.java b/src/main/java/bridge/constant/GameStatus.java new file mode 100644 index 00000000000..8ba108f9960 --- /dev/null +++ b/src/main/java/bridge/constant/GameStatus.java @@ -0,0 +1,53 @@ +package bridge.constant; + +import bridge.domain.BridgeSize; + +public enum GameStatus { + + PROGRESS, + MOVING_FAILED, + GAME_SUCCESS, + RESTART, + GAME_QUIT; + + public static GameStatus checkStatus(RetryCommand command) { + if (command.isQuit()){ + return GameStatus.GAME_QUIT; + } + return GameStatus.RESTART; + } + + public static GameStatus checkStatus(boolean canMoving, BridgeSize bridgeSize, int position) { + if (!canMoving) { + return GameStatus.MOVING_FAILED; + } + return checkGameStatus(bridgeSize, position); + } + + private static GameStatus checkGameStatus(BridgeSize bridgeSize, int position) { + if (bridgeSize.isSame(position)) { + return GameStatus.GAME_SUCCESS; + } + return GameStatus.PROGRESS; + } + + public static GameStatus init() { + return GameStatus.PROGRESS; + } + + public boolean isProgress() { + return this == PROGRESS; + } + + public boolean isNotEnd() { + return this != GAME_QUIT && this != GameStatus.GAME_SUCCESS; + } + + public boolean isFailed() { + return this == GameStatus.MOVING_FAILED; + } + + public boolean isClear() { + return this == GameStatus.GAME_SUCCESS; + } +} diff --git a/src/main/java/bridge/constant/MovingResult.java b/src/main/java/bridge/constant/MovingResult.java new file mode 100644 index 00000000000..b270ccbce44 --- /dev/null +++ b/src/main/java/bridge/constant/MovingResult.java @@ -0,0 +1,28 @@ +package bridge.constant; + +import java.util.Arrays; + +public enum MovingResult { + + MOVING_SUCCESS("O", true), + MOVING_FAILED("X", false); + + private final String display; + private final boolean success; + + MovingResult(String display, boolean success) { + this.display = display; + this.success = success; + } + + public static MovingResult calculateDisplay(boolean isSuccess) { + return Arrays.stream(MovingResult.values()) + .filter(movingResult -> movingResult.success == isSuccess) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.MOVING_RESULT_NONE.toString())); + } + + public String getDisplay() { + return this.display; + } +} diff --git a/src/main/java/bridge/constant/OutputMessage.java b/src/main/java/bridge/constant/OutputMessage.java new file mode 100644 index 00000000000..4009f849622 --- /dev/null +++ b/src/main/java/bridge/constant/OutputMessage.java @@ -0,0 +1,23 @@ +package bridge.constant; + +public enum OutputMessage { + + START_MESSAGE("다리 건너기 게임을 시작합니다."), + LENGTH_MESSAGE("다리의 길이를 입력해주세요."), + MOVE_LOCATION("이동할 칸을 선택해주세요. (위: U, 아래: D)"), + RETRY_MESSAGE("게임을 다시 시도할지 여부를 입력해주세요. (재시도: R, 종료: Q)"), + GAME_END_MESSAGE("최종 게임 결과"), + RETRY_NUMBERS("총 시도한 횟수: %d\n"), + GAME_STATUS("게임 성공 여부: %s\n"); + + private final String message; + + OutputMessage(String message) { + this.message = message; + } + + @Override + public String toString() { + return this.message; + } +} \ No newline at end of file diff --git a/src/main/java/bridge/constant/RetryCommand.java b/src/main/java/bridge/constant/RetryCommand.java new file mode 100644 index 00000000000..05cc042858c --- /dev/null +++ b/src/main/java/bridge/constant/RetryCommand.java @@ -0,0 +1,35 @@ +package bridge.constant; + +import java.util.Arrays; +import java.util.Objects; + +public enum RetryCommand { + + RETRY("R"), + QUIT("Q"); + + private final String command; + + RetryCommand(String command) { + this.command = command; + } + + public static RetryCommand getCommand(String command) { + return Arrays.stream(RetryCommand.values()) + .filter(value -> value.command.equals(command)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.INCORRECT_RETRY.toString())); + } + + public boolean isRetry() { + return Objects.equals(this.command, RETRY.command); + } + + public boolean isQuit() { + return Objects.equals(this.command, QUIT.command); + } + @Override + public String toString() { + return this.command; + } +} diff --git a/src/main/java/bridge/controller/BridgeController.java b/src/main/java/bridge/controller/BridgeController.java new file mode 100644 index 00000000000..d05d3c44a24 --- /dev/null +++ b/src/main/java/bridge/controller/BridgeController.java @@ -0,0 +1,52 @@ +package bridge.controller; + +import bridge.domain.BridgeSize; +import bridge.service.BridgeGame; +import bridge.view.InputView; +import bridge.view.OutputView; + +public class BridgeController { + + private final OutputView outputView = new OutputView(); + private final InputView inputView = new InputView(); + + private BridgeGame bridgeGame; + + public void start() { + bridgeGame = startGame(); + playGame(); + printGameResult(); + } + + private BridgeGame startGame() { + outputView.printGameStart(); + BridgeSize bridgeSize = inputView.readBridgeSize(); + return new BridgeGame(bridgeSize); + } + + private void playGame() { + while (bridgeGame.isNotEnd()) { + progress(); + handleFailed(); + } + } + + private void progress() { + while (bridgeGame.isProgress()) { + bridgeGame.move(inputView.readMoving()); + outputView.printMap(bridgeGame.getBridgeMaps()); + } + } + + private void handleFailed() { + if (bridgeGame.isFailed()) { + bridgeGame.retry(inputView.readGameCommand()); + } + } + + private void printGameResult() { + outputView.printEndMessage(); + outputView.printMap(bridgeGame.getBridgeMaps()); + outputView.printResult(bridgeGame.getGameRecorder(), bridgeGame.getRetryCount()); + } +} \ No newline at end of file diff --git a/src/main/java/bridge/domain/Bridge.java b/src/main/java/bridge/domain/Bridge.java new file mode 100644 index 00000000000..2a14b8fe342 --- /dev/null +++ b/src/main/java/bridge/domain/Bridge.java @@ -0,0 +1,18 @@ +package bridge.domain; + +import java.util.ArrayList; +import java.util.List; + +public class Bridge { + + private final List bridge; + + public Bridge(List bridge) { + this.bridge = new ArrayList<>(bridge); + } + + public boolean isCorrectMoving(String moving, int position) { + String correctMoving = bridge.get(position); + return correctMoving.equals(moving); + } +} diff --git a/src/main/java/bridge/domain/BridgeMaps.java b/src/main/java/bridge/domain/BridgeMaps.java new file mode 100644 index 00000000000..2ac1e4ce814 --- /dev/null +++ b/src/main/java/bridge/domain/BridgeMaps.java @@ -0,0 +1,57 @@ +package bridge.domain; + +import bridge.constant.BridgeDirection; +import bridge.constant.BridgeElement; +import bridge.constant.MovingResult; +import bridge.constant.RetryCommand; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; + +public class BridgeMaps { + + private static final String BLANK = " "; + + private final List upper = new ArrayList<>(); + private final List lower = new ArrayList<>(); + + public void addResult(String userInput, boolean isSuccess) { + setUpper(userInput, isSuccess); + setLower(userInput, isSuccess); + } + + private void setUpper(String userInput, boolean isSuccess) { + if(userInput.equals(BridgeDirection.UP.getDirection())) { + MovingResult correctMessage = MovingResult.calculateDisplay(isSuccess); + upper.add(correctMessage.getDisplay()); + lower.add(BLANK); + } + } + + private void setLower(String userInput, boolean success) { + if(userInput.equals(BridgeDirection.DOWN.getDirection())) { + MovingResult resultMessage = MovingResult.calculateDisplay(success); + lower.add(resultMessage.getDisplay()); + upper.add(BLANK); + } + } + + public void clear(RetryCommand command) { + if (command.isRetry()){ + upper.clear(); + lower.clear(); + } + } + + @Override + public String toString() { + return setBridge(upper) + "\n" + setBridge(lower); + } + + private String setBridge(List bridge) { + StringJoiner stringJoiner = new StringJoiner(BridgeElement.BRIDGE_DELIMITER.toString(), BridgeElement.BRIDGE_START.toString(), BridgeElement.BRIDGE_END.toString()); + bridge.forEach(stringJoiner::add); + return stringJoiner.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/bridge/domain/BridgeSize.java b/src/main/java/bridge/domain/BridgeSize.java new file mode 100644 index 00000000000..26073f0ed4d --- /dev/null +++ b/src/main/java/bridge/domain/BridgeSize.java @@ -0,0 +1,31 @@ +package bridge.domain; + +import bridge.constant.ExceptionMessage; + +public class BridgeSize { + + private static final int MIN_SIZE = 3; + private static final int MAX_SIZE = 20; + + private final int bridgeSize; + + public BridgeSize(int bridgeSize) { + validateIsCorrectRange(bridgeSize); + this.bridgeSize = bridgeSize; + } + + private void validateIsCorrectRange(int bridgeSize) { + if (!(MIN_SIZE <= bridgeSize && bridgeSize <= MAX_SIZE)) { + ExceptionMessage exceptionMessage = ExceptionMessage.INCORRECT_RANGE; + throw new IllegalArgumentException(exceptionMessage.toString()); + } + } + + public boolean isSame(int size) { + return bridgeSize == size; + } + + public int getBridgeSize() { + return bridgeSize; + } +} diff --git a/src/main/java/bridge/domain/GameRecorder.java b/src/main/java/bridge/domain/GameRecorder.java new file mode 100644 index 00000000000..6653b57e62c --- /dev/null +++ b/src/main/java/bridge/domain/GameRecorder.java @@ -0,0 +1,60 @@ +package bridge.domain; + +import bridge.constant.GameClearMessage; +import bridge.constant.GameStatus; +import bridge.constant.RetryCommand; + +public class GameRecorder { + + private static final int DEFAULT_POSITION = 0; + private final BridgeSize bridgeSize; + + private int position; + private GameStatus gameStatus; + + public GameRecorder(BridgeSize bridgeSize) { + this.bridgeSize = bridgeSize; + this.gameStatus = GameStatus.PROGRESS; + this.position = DEFAULT_POSITION; + } + + public void movePosition() { + position++; + } + + public void checkProgress(boolean canMoving) { + gameStatus = GameStatus.checkStatus(canMoving, bridgeSize, position); + } + + public void checkGameStatus(RetryCommand restartCommand) { + gameStatus = GameStatus.checkStatus(restartCommand); + if (gameStatus == GameStatus.RESTART) { + initGame(); + } + } + + private void initGame() { + position = DEFAULT_POSITION; + gameStatus = GameStatus.init(); + } + + public int getPosition() { + return position; + } + + public boolean isProgress() { + return gameStatus.isProgress(); + } + + public boolean isNotEnd() { + return gameStatus.isNotEnd(); + } + + public boolean isFailed() { + return gameStatus.isFailed(); + } + + public String getGameClearMessage() { + return GameClearMessage.getGameClearMessage(gameStatus.isClear()); + } +} \ No newline at end of file diff --git a/src/main/java/bridge/domain/RetryCount.java b/src/main/java/bridge/domain/RetryCount.java new file mode 100644 index 00000000000..5743b19b4be --- /dev/null +++ b/src/main/java/bridge/domain/RetryCount.java @@ -0,0 +1,18 @@ +package bridge.domain; + +import bridge.constant.RetryCommand; + +public class RetryCount { + + private int tryCount = 1; + + public void increaseTryCount(RetryCommand command) { + if (command.isRetry()){ + tryCount++; + } + } + + public int getRetryCount() { + return tryCount; + } +} diff --git a/src/main/java/bridge/service/BridgeGame.java b/src/main/java/bridge/service/BridgeGame.java new file mode 100644 index 00000000000..f3187d4eb70 --- /dev/null +++ b/src/main/java/bridge/service/BridgeGame.java @@ -0,0 +1,70 @@ +package bridge.service; + +import bridge.BridgeMaker; +import bridge.BridgeRandomNumberGenerator; +import bridge.constant.RetryCommand; +import bridge.domain.*; + +/** + * 다리 건너기 게임을 관리하는 클래스 + */ +public class BridgeGame { + + private final Bridge bridge; + private final GameRecorder gameRecorder; + private final BridgeMaps bridgeMaps = new BridgeMaps(); + private final RetryCount retryCount = new RetryCount(); + + public BridgeGame(BridgeSize bridgeSize) { + BridgeMaker bridgeMaker = new BridgeMaker(new BridgeRandomNumberGenerator()); + this.bridge = new Bridge(bridgeMaker.makeBridge(bridgeSize.getBridgeSize())); + gameRecorder = new GameRecorder(bridgeSize); + } + + /** + * 사용자가 칸을 이동할 때 사용하는 메서드 + *

+ * 이동을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. + */ + public void move(String moving) { + boolean isCorrect = bridge.isCorrectMoving(moving, gameRecorder.getPosition()); + gameRecorder.movePosition(); + gameRecorder.checkProgress(isCorrect); + bridgeMaps.addResult(moving, isCorrect); + } + + /** + * 사용자가 게임을 다시 시도할 때 사용하는 메서드 + *

+ * 재시작을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. + */ + public void retry(RetryCommand restartCommand) { + gameRecorder.checkGameStatus(restartCommand); + bridgeMaps.clear(restartCommand); + retryCount.increaseTryCount(restartCommand); + } + + public boolean isProgress() { + return gameRecorder.isProgress(); + } + + public boolean isNotEnd() { + return gameRecorder.isNotEnd(); + } + + public boolean isFailed() { + return gameRecorder.isFailed(); + } + + public BridgeMaps getBridgeMaps() { + return bridgeMaps; + } + + public GameRecorder getGameRecorder() { + return gameRecorder; + } + + public RetryCount getRetryCount() { + return retryCount; + } +} diff --git a/src/main/java/bridge/validator/InputValidator.java b/src/main/java/bridge/validator/InputValidator.java new file mode 100644 index 00000000000..c40e69d0764 --- /dev/null +++ b/src/main/java/bridge/validator/InputValidator.java @@ -0,0 +1,23 @@ +package bridge.validator; + +import bridge.constant.BridgeDirection; +import bridge.constant.ExceptionMessage; + +import java.util.regex.Pattern; + +public class InputValidator { + + public void validateMoving(String input) { + if (BridgeDirection.isNotDirectionCommand(input)) { + throw new IllegalArgumentException(ExceptionMessage.INCORRECT_MOVING.toString()); + } + } + + public void validateIsNumber(String input) { + String NUMBER_REGEXP = "^\\d*$"; + if (!Pattern.matches(NUMBER_REGEXP, input)) { + ExceptionMessage exceptionMessage = ExceptionMessage.NOT_INTEGER; + throw new IllegalArgumentException(exceptionMessage.toString()); + } + } +} diff --git a/src/main/java/bridge/view/InputView.java b/src/main/java/bridge/view/InputView.java new file mode 100644 index 00000000000..405ffae972f --- /dev/null +++ b/src/main/java/bridge/view/InputView.java @@ -0,0 +1,64 @@ +package bridge.view; + +import bridge.constant.RetryCommand; +import bridge.domain.BridgeSize; +import bridge.validator.InputValidator; +import camp.nextstep.edu.missionutils.Console; + +/** + * 사용자로부터 입력을 받는 역할을 한다. + */ +public class InputView { + + private static final InputValidator inputValidator = new InputValidator(); + private static final OutputView outputView = new OutputView(); + + /** + * 다리의 길이를 입력받는다. + */ + public BridgeSize readBridgeSize() { + try { + String input = inputBridgeSize(); + inputValidator.validateIsNumber(input); + return new BridgeSize(Integer.parseInt(input)); + } catch (IllegalArgumentException exception) { + outputView.printExceptionMessage(exception.getMessage()); + return readBridgeSize(); + } + } + + private String inputBridgeSize() { + outputView.printLengthMessage(); + String input = Console.readLine(); + outputView.printEmpty(); + return input; + } + + /** + * 사용자가 이동할 칸을 입력받는다. + */ + public String readMoving() { + try { + outputView.printMovingDirectionMessage(); + String input = Console.readLine(); + inputValidator.validateMoving(input); + return input; + } catch (IllegalArgumentException exception) { + outputView.printExceptionMessage(exception.getMessage()); + return readMoving(); + } + } + + /** + * 사용자가 게임을 다시 시도할지 종료할지 여부를 입력받는다. + */ + public RetryCommand readGameCommand() { + try { + outputView.printRetryMessage(); + return RetryCommand.getCommand(Console.readLine()); + } catch (IllegalArgumentException exception) { + outputView.printExceptionMessage(exception.getMessage()); + return readGameCommand(); + } + } +} diff --git a/src/main/java/bridge/view/OutputView.java b/src/main/java/bridge/view/OutputView.java new file mode 100644 index 00000000000..f9f50ca5496 --- /dev/null +++ b/src/main/java/bridge/view/OutputView.java @@ -0,0 +1,62 @@ +package bridge.view; + +import bridge.constant.OutputMessage; +import bridge.domain.BridgeMaps; +import bridge.domain.GameRecorder; +import bridge.domain.RetryCount; + +/** + * 사용자에게 게임 진행 상황과 결과를 출력하는 역할을 한다. + */ +public class OutputView { + + /** + * 현재까지 이동한 다리의 상태를 정해진 형식에 맞춰 출력한다. + *

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

+ * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. + */ + public void printResult(GameRecorder gameRecorder, RetryCount tryCount) { + System.out.printf((OutputMessage.GAME_STATUS.toString()), gameRecorder.getGameClearMessage()); + System.out.printf(OutputMessage.RETRY_NUMBERS.toString(), tryCount.getRetryCount()); + } + + public void printEndMessage() { + System.out.println(OutputMessage.GAME_END_MESSAGE); + } + + public void printGameStart() { + System.out.println(OutputMessage.START_MESSAGE); + printEmpty(); + } + + public void printLengthMessage() { + System.out.println(OutputMessage.LENGTH_MESSAGE); + } + + public void printMovingDirectionMessage() { + System.out.println(OutputMessage.MOVE_LOCATION); + } + + public void printRetryMessage() { + System.out.println(OutputMessage.RETRY_MESSAGE); + } + + public void printEmpty() { + System.out.println(); + } + + public void printExceptionMessage(String message) { + System.out.println(message); + printEmpty(); + } +} diff --git a/src/test/java/bridge/ApplicationTest.java b/src/test/java/bridge/ApplicationTest.java index 1a163ec0a2a..5b965cd93b6 100644 --- a/src/test/java/bridge/ApplicationTest.java +++ b/src/test/java/bridge/ApplicationTest.java @@ -39,6 +39,25 @@ class ApplicationTest extends NsTest { }, 1, 0, 1); } + @Test + void 재시작_테스트() { + assertRandomNumberInRangeTest(() -> { + run("3", "U", "D", "D", "R", "U", "D", "U"); + assertThat(output()).contains( + "게임을 다시 시도할지 여부를 입력해주세요. (재시도: R, 종료: Q)", + "최종 게임 결과", + "[ O | | O ]", + "[ | O | ]", + "게임 성공 여부: 성공", + "총 시도한 횟수: 2" + ); + + int upSideIndex = output().indexOf("[ O | | O ]"); + int downSideIndex = output().indexOf("[ | O | ]"); + assertThat(upSideIndex).isLessThan(downSideIndex); + }, 1, 0, 1); + } + @Test void 예외_테스트() { assertSimpleTest(() -> { diff --git a/src/test/java/bridge/model/BridgeDirectionTest.java b/src/test/java/bridge/model/BridgeDirectionTest.java new file mode 100644 index 00000000000..1ec44ae0fbf --- /dev/null +++ b/src/test/java/bridge/model/BridgeDirectionTest.java @@ -0,0 +1,27 @@ +package bridge.model; + +import bridge.constant.BridgeDirection; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BridgeDirectionTest { + + @Test + void convertDOWN() { + int value = 0; + + BridgeDirection direction = BridgeDirection.convertDirection(value); + + assertThat(direction.getDirection()).isEqualTo("D"); + } + + @Test + void convertUP() { + int value = 1; + + BridgeDirection direction = BridgeDirection.convertDirection(value); + + assertThat(direction.getDirection()).isEqualTo("U"); + } +} diff --git a/src/test/java/bridge/model/BridgeMakerTest.java b/src/test/java/bridge/model/BridgeMakerTest.java new file mode 100644 index 00000000000..8e0cf517d3a --- /dev/null +++ b/src/test/java/bridge/model/BridgeMakerTest.java @@ -0,0 +1,35 @@ +package bridge.model; + +import bridge.BridgeMaker; +import bridge.BridgeNumberGenerator; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.util.Lists.newArrayList; + +public class BridgeMakerTest { + + @Test + void createBridge() { + BridgeNumberGenerator numberGenerator = new BridgeMakerTest.TestNumberGenerator(newArrayList(1, 0, 0)); + BridgeMaker bridgeMaker = new BridgeMaker(numberGenerator); + List bridge = bridgeMaker.makeBridge(3); + assertThat(bridge).containsExactly("U", "D", "D"); + } + + static class TestNumberGenerator implements BridgeNumberGenerator { + + private final List numbers; + + TestNumberGenerator(List numbers) { + this.numbers = numbers; + } + + @Override + public int generate() { + return numbers.remove(0); + } + } +} diff --git a/src/test/java/bridge/model/BridgeMapsTest.java b/src/test/java/bridge/model/BridgeMapsTest.java new file mode 100644 index 00000000000..b624a90e571 --- /dev/null +++ b/src/test/java/bridge/model/BridgeMapsTest.java @@ -0,0 +1,21 @@ +package bridge.model; + +import bridge.domain.BridgeMaps; +import org.junit.jupiter.api.Test; + + +import static org.assertj.core.api.Assertions.assertThat; + +public class BridgeMapsTest { + + @Test + void addResult() { + String direction = "U"; + boolean isCorrect = true; + BridgeMaps bridgeMaps = new BridgeMaps(); + + bridgeMaps.addResult(direction, isCorrect); + + assertThat(bridgeMaps.toString()).contains("[ O ]", "[ ]"); + } +} diff --git a/src/test/java/bridge/model/BridgeSizeTest.java b/src/test/java/bridge/model/BridgeSizeTest.java new file mode 100644 index 00000000000..93081be868a --- /dev/null +++ b/src/test/java/bridge/model/BridgeSizeTest.java @@ -0,0 +1,20 @@ +package bridge.model; + +import bridge.domain.BridgeSize; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class BridgeSizeTest { + + String ERROR_MESSAGE = "[ERROR]"; + + @Test + void movingCommandException() { + int length = 21; + + assertThatThrownBy(() -> new BridgeSize(length)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ERROR_MESSAGE); + } +} diff --git a/src/test/java/bridge/model/BridgeTest.java b/src/test/java/bridge/model/BridgeTest.java new file mode 100644 index 00000000000..e9b4908d9cd --- /dev/null +++ b/src/test/java/bridge/model/BridgeTest.java @@ -0,0 +1,22 @@ +package bridge.model; + +import bridge.domain.Bridge; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BridgeTest { + + @Test + void isCorrectMoving() { + Bridge bridge = new Bridge(List.of("U", "U", "U")); + int position = 2; + String direction = "U"; + + boolean isCorrect = bridge.isCorrectMoving(direction, position); + + assertThat(isCorrect).isTrue(); + } +} diff --git a/src/test/java/bridge/model/GameStatusMessageTest.java b/src/test/java/bridge/model/GameStatusMessageTest.java new file mode 100644 index 00000000000..a4c524c9dc0 --- /dev/null +++ b/src/test/java/bridge/model/GameStatusMessageTest.java @@ -0,0 +1,47 @@ +package bridge.model; + +import bridge.constant.GameStatus; +import bridge.constant.RetryCommand; +import bridge.domain.Bridge; +import bridge.domain.BridgeSize; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GameStatusMessageTest { + + @Test + void checkRetryStatus() { + RetryCommand retryCommand = RetryCommand.QUIT; + + GameStatus gameStatus = GameStatus.checkStatus(retryCommand); + + assertThat(gameStatus).isEqualTo(GameStatus.GAME_QUIT); + } + + @Test + void checkClearStatus() { + int position = 3; + BridgeSize bridgeSize = new BridgeSize(3); + Bridge bridge = new Bridge(List.of("U", "U", "D")); + boolean isCorrect = bridge.isCorrectMoving("D", 2); + + GameStatus gameStatus = GameStatus.checkStatus(isCorrect, bridgeSize, position); + + assertThat(gameStatus).isEqualTo(GameStatus.GAME_SUCCESS); + } + + @Test + void checkMovingFailedStatus() { + int position = 3; + BridgeSize bridgeSize = new BridgeSize(3); + Bridge bridge = new Bridge(List.of("U", "U", "D")); + boolean isCorrect = bridge.isCorrectMoving("U", 2); + + GameStatus gameStatus = GameStatus.checkStatus(isCorrect, bridgeSize, position); + + assertThat(gameStatus).isEqualTo(GameStatus.MOVING_FAILED); + } +} diff --git a/src/test/java/bridge/model/InputValidatorTest.java b/src/test/java/bridge/model/InputValidatorTest.java new file mode 100644 index 00000000000..2ab99c04764 --- /dev/null +++ b/src/test/java/bridge/model/InputValidatorTest.java @@ -0,0 +1,30 @@ +package bridge.model; + +import bridge.validator.InputValidator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class InputValidatorTest { + + String ERROR_MESSAGE = "[ERROR]"; + InputValidator inputValidator = new InputValidator(); + + @Test + void movingCommandException() { + String command = "A"; + + assertThatThrownBy(() -> inputValidator.validateMoving(command)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ERROR_MESSAGE); + } + + @Test + void bridgeLengthException() { + String command = "A"; + + assertThatThrownBy(() -> inputValidator.validateIsNumber(command)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ERROR_MESSAGE); + } +} diff --git a/src/test/java/bridge/model/RetryCommandTest.java b/src/test/java/bridge/model/RetryCommandTest.java new file mode 100644 index 00000000000..2b3419d153a --- /dev/null +++ b/src/test/java/bridge/model/RetryCommandTest.java @@ -0,0 +1,57 @@ +package bridge.model; + +import bridge.constant.RetryCommand; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class RetryCommandTest { + + String ERROR_MESSAGE = "[ERROR]"; + + @Test + void getRetryCommand() { + String command = "R"; + + RetryCommand retryCommand = RetryCommand.getCommand(command); + + assertThat(retryCommand).isEqualTo(RetryCommand.RETRY); + } + + @Test + void getQuitCommand() { + String command = "Q"; + + RetryCommand retryCommand = RetryCommand.getCommand(command); + + assertThat(retryCommand).isEqualTo(RetryCommand.QUIT); + } + + @Test + void retryCommandException() { + String command = "A"; + + assertThatThrownBy(() -> RetryCommand.getCommand(command)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ERROR_MESSAGE); + } + + @Test + void isRetryCommand() { + RetryCommand retryCommand = RetryCommand.getCommand("R"); + + boolean isRetry = retryCommand.isRetry(); + + assertThat(isRetry).isTrue(); + } + + @Test + void isNotRetryCommand() { + RetryCommand retryCommand = RetryCommand.getCommand("Q"); + + boolean isRetry = retryCommand.isRetry(); + + assertThat(isRetry).isFalse(); + } +}