diff --git a/README.md b/README.md index 8ab62a9..a8eb9d8 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,7 @@ public class BridgeMaker { ### BridgeRandomNumberGenerator 클래스 -- Random 값 추출은 제공된 `bridge.BridgeRandomNumberGenerator`의 `generate()`를 활용한다. +- Random 값 추출은 제공된 `BridgeRandomNumberGenerator`의 `generate()`를 활용한다. - `BridgeRandomNumberGenerator`, `BridgeNumberGenerator` 클래스의 코드는 변경할 수 없다. #### 사용 예시 diff --git a/docs/README.md b/docs/README.md index e69de29..2880a53 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,54 @@ +# 요구사항 명세서 +## [카테고리] +1. 사용자 +2. 다리 생성기 +3. 이동 검사 +4. 시스템 + +## [카테고리 별 요구사항 내용] +### 1. 사용자 +- 다리의 길이를 입력한다. +- 이동할 칸을 입력한다. +- 게임 재시작 여부를 입력한다. + + +### 2. 다리 생성기 +- 입력한 길이의 다리를 생성한다. +- 다리는 각 칸은 위, 아래 두 칸으로 둘 중 하나의 칸만 건널 수 있도록 해야한다. + + +### 3. 이동 검사 +- 사용자가 이동할 칸을 검사한다. + + +### 4. 시스템 +- 게임을 시작한다. +- 게임 재시작 여부를 체크한다. +- 최종 게임 결과를 출력하고 게임을 종료한다. + + +# 기능 목록 +### 1. 입력 기능 +- 사용자가 입력하는 다리의 길이를 입력 받는 기능 +- 사용자가 입력하는 이동할 칸을 입력 받는 기능 +- 사용자가 입력하는 게임 재시작 여부를 입력 받는 기능 + + +### 2. 출력 기능 +- 사용자가 선택한 칸 출력 기능 +- 최종 게임 결과 출력 기능 + + +### 3. 게임 로직 기능 +- 입력 받은 길이의 다리 생성 기능 +- 이동할 수 있는 칸인지 검사하는 기능 +- 게임 시작/ 재시작 기능 +- 시도한 횟수를 세는 기능 + + +### 4. 검증 기능 +- 입력 검증 + - 사용자가 입력한 다리의 길이가 3 이상 20 이하 정수인지 검증 + - 사용자가 입력한 다리의 길이가 숫자 1개인지 검증 + - 사용자가 입력한 이동할 칸이 U와 D 중 하나의 문자인지 검증 + - 사용자가 입력한 재시작 코드가 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 5cb72df..8452337 100644 --- a/src/main/java/bridge/Application.java +++ b/src/main/java/bridge/Application.java @@ -1,8 +1,11 @@ package bridge; +import bridge.controller.BridgeGame; + public class Application { public static void main(String[] args) { // TODO: 프로그램 구현 + new BridgeGame().run(); } } diff --git a/src/main/java/bridge/BridgeGame.java b/src/main/java/bridge/BridgeGame.java deleted file mode 100644 index 834c1c8..0000000 --- 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 deleted file mode 100644 index 27e9f2c..0000000 --- a/src/main/java/bridge/BridgeMaker.java +++ /dev/null @@ -1,23 +0,0 @@ -package bridge; - -import java.util.List; - -/** - * 다리의 길이를 입력 받아서 다리를 생성해주는 역할을 한다. - */ -public class BridgeMaker { - - private final BridgeNumberGenerator bridgeNumberGenerator; - - public BridgeMaker(BridgeNumberGenerator bridgeNumberGenerator) { - this.bridgeNumberGenerator = bridgeNumberGenerator; - } - - /** - * @param size 다리의 길이 - * @return 입력받은 길이에 해당하는 다리 모양. 위 칸이면 "U", 아래 칸이면 "D"로 표현해야 한다. - */ - public List makeBridge(int size) { - return null; - } -} diff --git a/src/main/java/bridge/InputView.java b/src/main/java/bridge/InputView.java deleted file mode 100644 index c3911c8..0000000 --- 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 69a433a..0000000 --- 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/controller/BridgeGame.java b/src/main/java/bridge/controller/BridgeGame.java new file mode 100644 index 0000000..4872847 --- /dev/null +++ b/src/main/java/bridge/controller/BridgeGame.java @@ -0,0 +1,121 @@ +package bridge.controller; + +import bridge.model.RestartStatus; +import bridge.model.bridge.BridgeMaker; +import bridge.model.bridge.BridgeRandomNumberGenerator; +import bridge.model.result.CompareResult; +import bridge.model.result.FinalResult; +import bridge.model.result.MoveResult; +import bridge.model.user.UserSquare; +import bridge.service.BridgeService; + +import java.util.List; + +/** + * 다리 건너기 게임을 관리하는 클래스 + */ +public class BridgeGame { + private static BridgeService bridgeService; + + private static BridgeMaker bridgeMaker; + + private static int count; + + private static CompareResult compare; + + private static RestartStatus restartStatus; + + private static FinalResult finalResult; + + public BridgeGame() { + bridgeMaker = new BridgeMaker(new BridgeRandomNumberGenerator()); + bridgeService = new BridgeService(); + count = 0; + } + + public void run() { + final List bridge = createBridge(); + playGame(bridge); + endGame(); + } + + private List createBridge() { + return bridgeService.createBridge(bridgeMaker); + } + + private void playGame(final List bridge) { + do { + List moveResult = createResultHolder(); + count += 1; + for (final String bridgeSquare : bridge) { + move(bridgeSquare, moveResult, bridge); + + if (isDifferent()) { + break; + } + } + gameOver(); + } while (!isQuit()); + } + + private void endGame() { + bridgeService.end(count, finalResult); + } + + private static List createResultHolder() { + MoveResult.initResultHolder(); + return MoveResult.getMoveResult(); + } + + /** + * 사용자가 칸을 이동할 때 사용하는 메서드 + *

+ * 이동을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. + */ + public void move(final String bridgeSquare, List moveResult, final List bridge) { + final UserSquare userSquare = new UserSquare(); + final boolean eachResult = bridgeService.checkMove(userSquare.getSquare(), bridgeSquare); + + compare = CompareResult.getCompareBy(eachResult); + bridgeService.move(moveResult, eachResult, bridge); + retry(eachResult); + } + + private static void gameOver() { + if (isSuccess()) { + restartStatus = RestartStatus.QUIT; + finalResult = FinalResult.SUCCESS; + } + + if (isFail()) { + finalResult = FinalResult.FAIL; + } + } + + /** + * 사용자가 게임을 다시 시도할 때 사용하는 메서드 + *

+ * 재시작을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. + */ + public void retry(final boolean eachResult) { + if (eachResult == false) { + restartStatus = bridgeService.retry(); + } + } + + private boolean isDifferent() { + return compare == CompareResult.DIFF; + } + + private boolean isQuit() { + return restartStatus == RestartStatus.QUIT; + } + + private static boolean isSuccess() { + return compare == CompareResult.SAME; + } + + private static boolean isFail() { + return compare == CompareResult.DIFF && restartStatus == RestartStatus.QUIT; + } +} diff --git a/src/main/java/bridge/exception/HasWhiteSpaceException.java b/src/main/java/bridge/exception/HasWhiteSpaceException.java new file mode 100644 index 0000000..995c4eb --- /dev/null +++ b/src/main/java/bridge/exception/HasWhiteSpaceException.java @@ -0,0 +1,7 @@ +package bridge.exception; + +public class HasWhiteSpaceException extends IllegalArgumentException{ + public HasWhiteSpaceException(final String message) { + super(message); + } +} diff --git a/src/main/java/bridge/exception/InvalidInputRestartStatusException.java b/src/main/java/bridge/exception/InvalidInputRestartStatusException.java new file mode 100644 index 0000000..dfe0f9c --- /dev/null +++ b/src/main/java/bridge/exception/InvalidInputRestartStatusException.java @@ -0,0 +1,7 @@ +package bridge.exception; + +public class InvalidInputRestartStatusException extends IllegalArgumentException{ + public InvalidInputRestartStatusException(final String message) { + super(message); + } +} diff --git a/src/main/java/bridge/exception/InvalidInputSquareException.java b/src/main/java/bridge/exception/InvalidInputSquareException.java new file mode 100644 index 0000000..b567a6c --- /dev/null +++ b/src/main/java/bridge/exception/InvalidInputSquareException.java @@ -0,0 +1,7 @@ +package bridge.exception; + +public class InvalidInputSquareException extends IllegalArgumentException{ + public InvalidInputSquareException(final String message) { + super(message); + } +} diff --git a/src/main/java/bridge/exception/NotInRangeBridgeSizeException.java b/src/main/java/bridge/exception/NotInRangeBridgeSizeException.java new file mode 100644 index 0000000..c849d93 --- /dev/null +++ b/src/main/java/bridge/exception/NotInRangeBridgeSizeException.java @@ -0,0 +1,7 @@ +package bridge.exception; + +public class NotInRangeBridgeSizeException extends IllegalArgumentException{ + public NotInRangeBridgeSizeException(final String message) { + super(message); + } +} diff --git a/src/main/java/bridge/exception/NotNumericException.java b/src/main/java/bridge/exception/NotNumericException.java new file mode 100644 index 0000000..6893764 --- /dev/null +++ b/src/main/java/bridge/exception/NotNumericException.java @@ -0,0 +1,7 @@ +package bridge.exception; + +public class NotNumericException extends IllegalArgumentException{ + public NotNumericException(final String message) { + super(message); + } +} diff --git a/src/main/java/bridge/model/RestartStatus.java b/src/main/java/bridge/model/RestartStatus.java new file mode 100644 index 0000000..34530c8 --- /dev/null +++ b/src/main/java/bridge/model/RestartStatus.java @@ -0,0 +1,37 @@ +package bridge.model; + +import java.util.Arrays; + +import static bridge.utils.ExceptionMessage.Input_Exception.INVALID_INPUT_RESTART_STATUS_EXCEPTION; + +public enum RestartStatus { + RESTART("R"), + QUIT("Q"); + + private static final String QUIT_SIGN = "Q"; + private static final String RESTART_SIGN = "R"; + private final String signal; + + RestartStatus(String signal) { + this.signal = signal; + } + + public static RestartStatus getRestartStatusBy(final String signal) { + validateCharacter(signal); + + return Arrays.stream(values()) + .filter(RestartStatus -> RestartStatus.signal.equals(signal)) + .findFirst() + .orElseGet(() -> null); + } + + private static void validateCharacter(final String signal) { + if (isInvalidCharacter(signal)) { + throw new IllegalArgumentException(INVALID_INPUT_RESTART_STATUS_EXCEPTION.getMessage()); + } + } + + private static boolean isInvalidCharacter(final String signal) { + return !signal.equals(RESTART_SIGN) && !signal.equals(QUIT_SIGN); + } +} diff --git a/src/main/java/bridge/model/bridge/BridgeMaker.java b/src/main/java/bridge/model/bridge/BridgeMaker.java new file mode 100644 index 0000000..0523d59 --- /dev/null +++ b/src/main/java/bridge/model/bridge/BridgeMaker.java @@ -0,0 +1,40 @@ +package bridge.model.bridge; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static bridge.utils.ExceptionMessage.Input_Exception.INVALID_BRIDGE_RANGE_EXCEPTION; + +/** + * 다리의 길이를 입력 받아서 다리를 생성해주는 역할을 한다. + */ +public class BridgeMaker { + + private static final int MIN_SIZE = 3; + private static final int MAX_SIZE = 20; + private final BridgeNumberGenerator bridgeNumberGenerator; + + public BridgeMaker(BridgeNumberGenerator bridgeNumberGenerator) { + this.bridgeNumberGenerator = bridgeNumberGenerator; + } + + /** + * @param size 다리의 길이 + * @return 입력받은 길이에 해당하는 다리 모양. 위 칸이면 "U", 아래 칸이면 "D"로 표현해야 한다. + */ + public List makeBridge(final int size) { + validateBridgeRange(size); + + return IntStream.range(0, size) + .mapToObj(i -> Direct.getDirectBy(bridgeNumberGenerator.generate())) + .map(String::valueOf) + .collect(Collectors.toList()); + } + + private void validateBridgeRange(final int size) { + if (size < MIN_SIZE || size > MAX_SIZE) { + throw new IllegalArgumentException(INVALID_BRIDGE_RANGE_EXCEPTION.getMessage()); + } + } +} diff --git a/src/main/java/bridge/BridgeNumberGenerator.java b/src/main/java/bridge/model/bridge/BridgeNumberGenerator.java similarity index 74% rename from src/main/java/bridge/BridgeNumberGenerator.java rename to src/main/java/bridge/model/bridge/BridgeNumberGenerator.java index 56187b7..693f135 100644 --- a/src/main/java/bridge/BridgeNumberGenerator.java +++ b/src/main/java/bridge/model/bridge/BridgeNumberGenerator.java @@ -1,4 +1,4 @@ -package bridge; +package bridge.model.bridge; @FunctionalInterface public interface BridgeNumberGenerator { diff --git a/src/main/java/bridge/BridgeRandomNumberGenerator.java b/src/main/java/bridge/model/bridge/BridgeRandomNumberGenerator.java similarity index 92% rename from src/main/java/bridge/BridgeRandomNumberGenerator.java rename to src/main/java/bridge/model/bridge/BridgeRandomNumberGenerator.java index 4c9cb53..da4a925 100644 --- a/src/main/java/bridge/BridgeRandomNumberGenerator.java +++ b/src/main/java/bridge/model/bridge/BridgeRandomNumberGenerator.java @@ -1,4 +1,4 @@ -package bridge; +package bridge.model.bridge; import camp.nextstep.edu.missionutils.Randoms; diff --git a/src/main/java/bridge/model/bridge/Direct.java b/src/main/java/bridge/model/bridge/Direct.java new file mode 100644 index 0000000..8f2d581 --- /dev/null +++ b/src/main/java/bridge/model/bridge/Direct.java @@ -0,0 +1,21 @@ +package bridge.model.bridge; + +import java.util.Arrays; + +public enum Direct { + U(1), + D(0); + + public final int directNumber; + + Direct(int directNumber) { + this.directNumber = directNumber; + } + + public static Direct getDirectBy(final int directNumber) { + return Arrays.stream(values()) + .filter(Direct -> Direct.directNumber == directNumber) + .findFirst() + .orElseGet(() -> null); + } +} diff --git a/src/main/java/bridge/model/result/CompareResult.java b/src/main/java/bridge/model/result/CompareResult.java new file mode 100644 index 0000000..0873737 --- /dev/null +++ b/src/main/java/bridge/model/result/CompareResult.java @@ -0,0 +1,21 @@ +package bridge.model.result; + +import java.util.Arrays; + +public enum CompareResult { + SAME(true), + DIFF(false); + + private final boolean isSame; + + CompareResult(boolean isSame) { + this.isSame = isSame; + } + + public static CompareResult getCompareBy(final boolean EachResult) { + return Arrays.stream(values()) + .filter(Compare -> Compare.isSame == EachResult) + .findFirst() + .orElseGet(() -> null); + } +} diff --git a/src/main/java/bridge/model/result/FinalResult.java b/src/main/java/bridge/model/result/FinalResult.java new file mode 100644 index 0000000..b40511b --- /dev/null +++ b/src/main/java/bridge/model/result/FinalResult.java @@ -0,0 +1,6 @@ +package bridge.model.result; + +public enum FinalResult { + SUCCESS, + FAIL; +} diff --git a/src/main/java/bridge/model/result/MoveResult.java b/src/main/java/bridge/model/result/MoveResult.java new file mode 100644 index 0000000..da074d7 --- /dev/null +++ b/src/main/java/bridge/model/result/MoveResult.java @@ -0,0 +1,22 @@ +package bridge.model.result; + +import java.util.ArrayList; +import java.util.List; + +public class MoveResult { + + private static List moveResult; + private MoveResult() { + } + + public static void initResultHolder() { + moveResult = null; + } + + public static List getMoveResult() { + if (moveResult == null) { + moveResult = new ArrayList<>(); + } + return moveResult; + } +} diff --git a/src/main/java/bridge/model/user/UserSquare.java b/src/main/java/bridge/model/user/UserSquare.java new file mode 100644 index 0000000..803dea5 --- /dev/null +++ b/src/main/java/bridge/model/user/UserSquare.java @@ -0,0 +1,38 @@ +package bridge.model.user; + +import bridge.utils.BridgeConstant; +import bridge.view.InputView; + +import static bridge.utils.ExceptionMessage.Input_Exception.INVALID_INPUT_SQUARE_EXCEPTION; + +public class UserSquare { + private String square; + + public UserSquare() { + while (true) { + try { + final String moving = InputView.readMoving(); + validateCharacter(moving); + this.square = moving; + + break; + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + + private static void validateCharacter(final String moving) { + if (isInvalidCharacter(moving)) { + throw new IllegalArgumentException(INVALID_INPUT_SQUARE_EXCEPTION.getMessage()); + } + } + + private static boolean isInvalidCharacter(final String moving) { + return !moving.equals(BridgeConstant.UP_SQUARE) && !moving.equals(BridgeConstant.DOWN_SQUARE); + } + + public String getSquare() { + return square; + } +} diff --git a/src/main/java/bridge/service/BridgeService.java b/src/main/java/bridge/service/BridgeService.java new file mode 100644 index 0000000..239d0c0 --- /dev/null +++ b/src/main/java/bridge/service/BridgeService.java @@ -0,0 +1,46 @@ +package bridge.service; + +import bridge.model.RestartStatus; +import bridge.model.bridge.BridgeMaker; +import bridge.model.result.FinalResult; +import bridge.view.InputView; +import bridge.view.OutputView; + +import java.util.List; + +public class BridgeService { + public List createBridge(final BridgeMaker bridgeMaker) { + while (true) { + try { + OutputView.printGameStartMessage(); + + return bridgeMaker.makeBridge(InputView.readBridgeSize()); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + + public boolean checkMove(final String bridgeSquare, final String userSquare) { + return userSquare.equals(bridgeSquare); + } + + public void move(List moveResult, final boolean eachResult, final List bridge) { + moveResult.add(eachResult); + OutputView.printMap(bridge, moveResult); + } + + public RestartStatus retry() { + while (true) { + try { + return RestartStatus.getRestartStatusBy(InputView.readGameCommand()); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + + public void end(final int count, final FinalResult finalResult) { + OutputView.printResult(count, finalResult); + } +} diff --git a/src/main/java/bridge/utils/BridgeConstant.java b/src/main/java/bridge/utils/BridgeConstant.java new file mode 100644 index 0000000..5ec8614 --- /dev/null +++ b/src/main/java/bridge/utils/BridgeConstant.java @@ -0,0 +1,6 @@ +package bridge.utils; + +public abstract class BridgeConstant { + public static final String DOWN_SQUARE = "D"; + public static final String UP_SQUARE = "U"; +} diff --git a/src/main/java/bridge/utils/ExceptionMessage.java b/src/main/java/bridge/utils/ExceptionMessage.java new file mode 100644 index 0000000..9b66097 --- /dev/null +++ b/src/main/java/bridge/utils/ExceptionMessage.java @@ -0,0 +1,24 @@ +package bridge.utils; + +public interface ExceptionMessage { + + String ERROR = "[ERROR] "; + + enum Input_Exception { + INVALID_BRIDGE_RANGE_EXCEPTION("다리 길이는 3부터 20 사이의 숫자여야 합니다."), + INVALID_INPUT_SQUARE_EXCEPTION("윗 칸은 [U], 아래 칸은 [D]를 눌러주세요."), + INVALID_INPUT_RESTART_STATUS_EXCEPTION("재시작 [R], 종료 [Q]를 눌러주세요."), + HAS_WHITESPACE_EXCEPTION("공백을 입력할 수 없습니다."), + IS_NOT_NUMERIC_EXCEPTION("정수만 입력 가능합니다."); + + private final String message; + + Input_Exception(String message) { + this.message = message; + } + + public String getMessage() { + return ERROR + message; + } + } +} diff --git a/src/main/java/bridge/view/InputView.java b/src/main/java/bridge/view/InputView.java new file mode 100644 index 0000000..ee6d048 --- /dev/null +++ b/src/main/java/bridge/view/InputView.java @@ -0,0 +1,84 @@ +package bridge.view; + +import camp.nextstep.edu.missionutils.Console; + +import static bridge.utils.ExceptionMessage.Input_Exception.HAS_WHITESPACE_EXCEPTION; +import static bridge.utils.ExceptionMessage.Input_Exception.IS_NOT_NUMERIC_EXCEPTION; + +/** + * 사용자로부터 입력을 받는 역할을 한다. + */ +public class InputView { + + private static final String INPUT_BRIDGE_LENGTH_MESSAGE = "다리의 길이를 입력해주세요."; + private static final String INPUT_USER_SQUARE_MESSAGE = "이동할 칸을 선택해주세요. (위: U, 아래: D)"; + private static final String INPUT_RESTART_MESSAGE = "게임을 다시 시도할지 여부를 입력해주세요. (재시도: R, 종료: Q)"; + + private static void printInputLengthMessage() { + System.out.println(INPUT_BRIDGE_LENGTH_MESSAGE); + } + + private static void printInputDirectMessage() { + System.out.println(INPUT_USER_SQUARE_MESSAGE); + } + + private static void printInputRestartStatusMessage() { + System.out.println(INPUT_RESTART_MESSAGE); + } + + /** + * 다리의 길이를 입력받는다. + */ + public static int readBridgeSize() { + printInputLengthMessage(); + + final String length = Console.readLine(); + + validateHasWhiteSpace(length); + validateIsNumeric(length); + + return Integer.parseInt(length); + } + + /** + * 사용자가 이동할 칸을 입력받는다. + */ + public static String readMoving() { + printInputDirectMessage(); + + final String direct = Console.readLine(); + validateHasWhiteSpace(direct); + + return direct; + } + + /** + * 사용자가 게임을 다시 시도할지 종료할지 여부를 입력받는다. + */ + public static String readGameCommand() { + printInputRestartStatusMessage(); + + final String command = Console.readLine(); + validateHasWhiteSpace(command); + + return command; + } + + private static void validateHasWhiteSpace(final String input) { + if (hasWhiteSpace(input)) { + throw new IllegalArgumentException(HAS_WHITESPACE_EXCEPTION.getMessage()); + } + } + + private static boolean hasWhiteSpace(final String input) { + return input.chars().anyMatch(Character::isWhitespace); + } + + private static void validateIsNumeric(final String input) { + try { + Integer.valueOf(input); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(IS_NOT_NUMERIC_EXCEPTION.getMessage()); + } + } +} diff --git a/src/main/java/bridge/view/OutputView.java b/src/main/java/bridge/view/OutputView.java new file mode 100644 index 0000000..754cd3b --- /dev/null +++ b/src/main/java/bridge/view/OutputView.java @@ -0,0 +1,111 @@ +package bridge.view; + +import bridge.model.result.FinalResult; + +import java.util.List; + +import static bridge.utils.BridgeConstant.DOWN_SQUARE; +import static bridge.utils.BridgeConstant.UP_SQUARE; + +/** + * 사용자에게 게임 진행 상황과 결과를 출력하는 역할을 한다. + */ +public class OutputView { + private static final String GAME_START_MESSAGE = "다리 건너기 게임을 시작합니다."; + private static final String GAME_OVER_MESSAGE = "최종 게임 결과"; + private static final String OPEN_BRACKETS = "["; + private static final String CLOSE_BRACKETS = "]"; + private static final String SEPARATOR = "|"; + private static final String COLLECT_SIGN = " O "; + private static final String GAP = " "; + private static final String NEW_LINE = "\n"; + private static final String WRONG_SIGN = " X "; + private static final String SUCCESS = "성공"; + private static final String FAIL = "실패"; + private static final String GAME_SUCCESS_WHETHER = "게임 성공 여부: "; + private static final String TYR_COUNT = "총 시도한 횟수: "; + private static final int START_NUMBER = 0; + private static String finalSquares; + + public static void printGameStartMessage() { + System.out.println(GAME_START_MESSAGE + NEW_LINE); + } + + + /** + * 현재까지 이동한 다리의 상태를 정해진 형식에 맞춰 출력한다. + *

+ * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. + */ + public static void printMap(final List bridge, final List moveResult) { + StringBuilder upSquares = new StringBuilder(OPEN_BRACKETS); + StringBuilder downSquares = new StringBuilder(OPEN_BRACKETS); + + for (int i = 0; i < moveResult.size(); i++) { + if (i > START_NUMBER) { + upSquares.append(SEPARATOR); + downSquares.append(SEPARATOR); + } + + if (isUpDirectSame(bridge, moveResult, i)) { + upSquares.append(COLLECT_SIGN); + downSquares.append(GAP); + } + if (isUpDirectNotSame(bridge, moveResult, i)) { + upSquares.append(GAP); + downSquares.append(WRONG_SIGN); + } + if (isDownDirectSame(bridge, moveResult, i)) { + upSquares.append(GAP); + downSquares.append(COLLECT_SIGN); + } + if (isDownDirectNotSame(bridge, moveResult, i)) { + upSquares.append(WRONG_SIGN); + downSquares.append(GAP); + } + } + + upSquares.append(CLOSE_BRACKETS + NEW_LINE); + downSquares.append(CLOSE_BRACKETS + NEW_LINE); + + finalSquares = upSquares.append(downSquares).toString(); + System.out.println(finalSquares); + } + + private static boolean isDownDirectNotSame(final List bridge, final List moveResult, final int i) { + return bridge.get(i).equals(DOWN_SQUARE) && moveResult.get(i) == false; + } + + private static boolean isUpDirectNotSame(final List bridge, final List moveResult, final int i) { + return bridge.get(i).equals(UP_SQUARE) && moveResult.get(i) == false; + } + + private static boolean isDownDirectSame(final List bridge, final List moveResult, final int i) { + return bridge.get(i).equals(DOWN_SQUARE) && moveResult.get(i) == true; + } + + private static boolean isUpDirectSame(final List bridge, final List moveResult, final int i) { + return bridge.get(i).equals(UP_SQUARE) && moveResult.get(i) == true; + } + + /** + * 게임의 최종 결과를 정해진 형식에 맞춰 출력한다. + *

+ * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다. + */ + public static void printResult(final int count, final FinalResult finalResult) { + StringBuilder finalMessage = new StringBuilder(); + String result = SUCCESS; + + if (finalResult == FinalResult.FAIL) { + result = FAIL; + } + + finalMessage.append(GAME_OVER_MESSAGE + NEW_LINE) + .append(finalSquares + NEW_LINE) + .append(GAME_SUCCESS_WHETHER + result + NEW_LINE) + .append(TYR_COUNT + count + NEW_LINE); + + System.out.println(finalMessage); + } +} diff --git a/src/test/java/bridge/ApplicationTest.java b/src/test/java/bridge/ApplicationTest.java index 1a163ec..bcca907 100644 --- a/src/test/java/bridge/ApplicationTest.java +++ b/src/test/java/bridge/ApplicationTest.java @@ -1,14 +1,17 @@ package bridge; +import bridge.model.bridge.BridgeMaker; +import bridge.model.bridge.BridgeNumberGenerator; +import camp.nextstep.edu.missionutils.test.NsTest; +import org.junit.jupiter.api.Test; + +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.util.Lists.newArrayList; -import camp.nextstep.edu.missionutils.test.NsTest; -import java.util.List; -import org.junit.jupiter.api.Test; - class ApplicationTest extends NsTest { private static final String ERROR_MESSAGE = "[ERROR]"; diff --git a/src/test/java/bridge/model/RestartStatusTest.java b/src/test/java/bridge/model/RestartStatusTest.java new file mode 100644 index 0000000..7b803df --- /dev/null +++ b/src/test/java/bridge/model/RestartStatusTest.java @@ -0,0 +1,54 @@ +package bridge.model; + +import bridge.model.bridge.BridgeMaker; +import bridge.model.bridge.BridgeNumberGenerator; +import bridge.model.bridge.BridgeRandomNumberGenerator; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static bridge.utils.ExceptionMessage.Input_Exception.INVALID_BRIDGE_RANGE_EXCEPTION; +import static bridge.utils.ExceptionMessage.Input_Exception.INVALID_INPUT_RESTART_STATUS_EXCEPTION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RestartStatusTest { + @Test + @DisplayName("올바르지 않은 문자를 입력하면 예외가 발생한다.") + void throwException_InvalidCharacter() { + // given + String signal = "G"; + + // then + assertThatThrownBy(() -> RestartStatus.getRestartStatusBy(signal)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INVALID_INPUT_RESTART_STATUS_EXCEPTION.getMessage()); + } + + @Test + @DisplayName("게임을 재시작한다.") + void restart() { + // given + String signal = "R"; + + // when + RestartStatus restartStatus = RestartStatus.getRestartStatusBy(signal); + + // then + assertThat(RestartStatus.RESTART).isEqualTo(restartStatus); + } + + @Test + @DisplayName("게임을 종료한다.") + void quit() { + // given + String signal = "Q"; + + // when + RestartStatus restartStatus = RestartStatus.getRestartStatusBy(signal); + + // then + assertThat(RestartStatus.QUIT).isEqualTo(restartStatus); + } +} \ No newline at end of file diff --git a/src/test/java/bridge/model/bridge/BridgeMakerTest.java b/src/test/java/bridge/model/bridge/BridgeMakerTest.java new file mode 100644 index 0000000..a84bcbf --- /dev/null +++ b/src/test/java/bridge/model/bridge/BridgeMakerTest.java @@ -0,0 +1,45 @@ +package bridge.model.bridge; + +import bridge.utils.ExceptionMessage; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; + +import static bridge.utils.ExceptionMessage.Input_Exception.INVALID_BRIDGE_RANGE_EXCEPTION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BridgeMakerTest { + @ParameterizedTest + @ValueSource(ints = {2, 21}) + @DisplayName("범위에 맞지 않은 다리 길이를 입력하지 않으면 예외가 발생한다.") + void throwException_NotInRangeBridgeSize(int size) { + // given + BridgeNumberGenerator bridgeNumberGenerator = new BridgeRandomNumberGenerator(); + BridgeMaker bridgeMaker = new BridgeMaker(bridgeNumberGenerator); + + // then + assertThatThrownBy(() -> bridgeMaker.makeBridge(size)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INVALID_BRIDGE_RANGE_EXCEPTION.getMessage()); + } + + @Test + @DisplayName("다리 길이를 입력받아 다리를 생성한다.") + void makeBridge() { + // given + BridgeNumberGenerator bridgeNumberGenerator = new BridgeRandomNumberGenerator(); + BridgeMaker bridgeMaker = new BridgeMaker(bridgeNumberGenerator); + int size = 3; + + // when + List bridge = bridgeMaker.makeBridge(size); + + // then + assertThat(bridge.size()).isEqualTo(size); + } +} \ No newline at end of file diff --git a/src/test/java/bridge/service/BridgeServiceTest.java b/src/test/java/bridge/service/BridgeServiceTest.java new file mode 100644 index 0000000..b0d8015 --- /dev/null +++ b/src/test/java/bridge/service/BridgeServiceTest.java @@ -0,0 +1,77 @@ +package bridge.service; + +import bridge.model.result.MoveResult; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class BridgeServiceTest { + @BeforeEach + void beforeEach() { + MoveResult.initResultHolder(); + } + + @Test + @DisplayName("모두 정답이면 끝까지 이동할 수 있다.") + void moveEnd() { + // given + List caseA = List.of("U", "U", "U"); + List bridge = List.of("U", "U", "U"); + BridgeService bridgeService = new BridgeService(); + + // when + List moveResult = MoveResult.getMoveResult(); + moveProcess(bridgeService, moveResult, caseA, bridge); + + // then + assertThat(moveResult.size()).isEqualTo(3); + } + + @Test + @DisplayName("중간에 틀리면 중간까지 이동할 수 있다.") + void moveMiddle() { + // given + List caseA = List.of("U", "U", "U"); + List bridge = List.of("U", "D", "U"); + BridgeService bridgeService = new BridgeService(); + + // when + List moveResult = MoveResult.getMoveResult(); + moveProcess(bridgeService, moveResult, caseA, bridge); + + // then + assertThat(moveResult.size()).isEqualTo(2); + } + + @Test + @DisplayName("처음부터 틀리면 이동할 수 없다.") + void notMove() { + // given + List caseA = List.of("U", "U", "U"); + List bridge = List.of("D", "U", "U"); + BridgeService bridgeService = new BridgeService(); + + // when + List moveResult = MoveResult.getMoveResult(); + moveProcess(bridgeService, moveResult, caseA, bridge); + + // then + assertThat(moveResult.size()).isEqualTo(1); + } + + // 어플리케이션 시나리오 + private static void moveProcess(BridgeService bridgeService, List moveResult, List caseA, List bridge) { + for (int i = 0; i < bridge.size(); i++) { + boolean eachResult = bridgeService.checkMove(bridge.get(i), caseA.get(i)); + bridgeService.move(moveResult, eachResult, bridge); + + if (eachResult == false) { + break; + } + } + } +} \ No newline at end of file