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