diff --git a/docs/README.md b/docs/README.md
index e69de29bb2d..779f741f6e1 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -0,0 +1,101 @@
+## 기능 목록
+- [x] 게임 시작
+ - [x] 게임 시작 메시지 출력
+- [x] 다리 길이 설정
+ - [x] 다리 길이 입력요청 메시지 출력
+ - [x] 다리 길이 입력
+ - [x] 입력값 숫자 여부 검증
+ - [x] 입력 숫자 3 이상 20 이하 숫자인지 검증
+- [x] 다리 생성
+ - [x] 길이에 따른 랜덤 다리 생성
+- [x] 플레이어 이동
+ - [x] 이동할 칸 입력
+ - [x] 입력값이 U 혹은 D 인지 검증
+ - [x] 이동 가능한 칸인 경우 O 표시
+ - [x] 이동 불가능한 칸인 경우 X 표시
+ - [x] 현황 출력
+- [x] 게임 성공 여부 판단
+ - [x] 이동 불가능한 칸으로 이동한 경우 실패
+ - [x] 모두 이동 가능한 칸으로만 이동한 경우 성공
+ - [x] 성공 메시지 출력
+ - [x] 총 시도 횟수 출력
+- [x] 실패시 게임 재시작 여부 판단
+ - [x] 게임 재시작 여부 입력
+ - [x] 입력값 R 혹은 Q 인지 검증
+- [x] 게임 재시작
+ - [x] 게임 시도 횟수 증가
+- [x] 게임 종료
+
+## 구현 클래스 목록
+- ComponentFactory
+ - bridgeGameManager()
+
+- InputProvider
+ - bridgeSize()
+ - moveCommand()
+ - retryCommand()
+
+- InputValidator
+ - validateBridgeSize()
+ - validateMoving()
+ - validateRetryCommand()
+
+- InputView
+ - readBridgeSize()
+ - readMoving()
+ - readGameCommand()
+
+- OutputView
+ - printMap()
+ - printResult()
+ - printGameStart()
+ - printBridgeSizeRequest()
+ - printMovingRequest()
+ - printRetryRequest()
+
+- BridgeGameManager
+ - playGame()
+
+- Application
+ - main()
+
+- BridgeGame
+ - move()
+ - retry()
+ - makeBridge()
+ - findGameResult()
+
+- BridgeMaker
+ - makeBridge()
+
+- BridgeRandomNumberGenerator
+ - generate()
+
+- Bridge
+ - getCurrentElement()
+ - compareToInput()
+ - clearIndex()
+
+- GameResult
+ - addResultStatus()
+ - toString()
+ - clearAndAddCount()
+ - findTryCount()
+
+- TryCount
+ - nextCount()
+ - toString()
+
+- BridgeStorage
+ - saveBridge()
+ - findBridge()
+ - saveGameResult()
+ - findGameResult()
+
+## 구현 열거형 목록
+- GameMessage
+- RetryCommand
+- ErrorMessage
+- MoveCommand
+- BridgeSize
+- BridgeType
diff --git a/src/main/java/bridge/Application.java b/src/main/java/bridge/Application.java
index 5cb72dfd3de..1e4e15593d0 100644
--- a/src/main/java/bridge/Application.java
+++ b/src/main/java/bridge/Application.java
@@ -1,8 +1,11 @@
package bridge;
+import bridge.factory.ComponentFactory;
+
public class Application {
public static void main(String[] args) {
- // TODO: 프로그램 구현
+ final ComponentFactory componentFactory = new ComponentFactory();
+ componentFactory.bridgeGameManager().playGame();
}
}
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..11e7508f67a 100644
--- a/src/main/java/bridge/BridgeMaker.java
+++ b/src/main/java/bridge/BridgeMaker.java
@@ -1,15 +1,19 @@
package bridge;
+import bridge.constant.BridgeType;
+
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
/**
* 다리의 길이를 입력 받아서 다리를 생성해주는 역할을 한다.
*/
public class BridgeMaker {
-
+ private static final int START_INDEX = 0;
private final BridgeNumberGenerator bridgeNumberGenerator;
- public BridgeMaker(BridgeNumberGenerator bridgeNumberGenerator) {
+ public BridgeMaker(final BridgeNumberGenerator bridgeNumberGenerator) {
this.bridgeNumberGenerator = bridgeNumberGenerator;
}
@@ -17,7 +21,14 @@ public BridgeMaker(BridgeNumberGenerator bridgeNumberGenerator) {
* @param size 다리의 길이
* @return 입력받은 길이에 해당하는 다리 모양. 위 칸이면 "U", 아래 칸이면 "D"로 표현해야 한다.
*/
- public List makeBridge(int size) {
- return null;
+ public List makeBridge(final int size) {
+ return IntStream.range(START_INDEX, size)
+ .mapToObj(i -> getBridgeDirection())
+ .collect(Collectors.toList());
+ }
+
+ private String getBridgeDirection() {
+ final int randomNumber = bridgeNumberGenerator.generate();
+ return BridgeType.getDirectionByClassifier(randomNumber);
}
}
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/BridgeSize.java b/src/main/java/bridge/constant/BridgeSize.java
new file mode 100644
index 00000000000..48c2d7f3446
--- /dev/null
+++ b/src/main/java/bridge/constant/BridgeSize.java
@@ -0,0 +1,15 @@
+package bridge.constant;
+
+public enum BridgeSize {
+ MIN_BRIDGE_SIZE(3),
+ MAX_BRIDGE_SIZE(20);
+ private final int size;
+
+ BridgeSize(int size) {
+ this.size = size;
+ }
+
+ public int size() {
+ return this.size;
+ }
+}
diff --git a/src/main/java/bridge/constant/BridgeType.java b/src/main/java/bridge/constant/BridgeType.java
new file mode 100644
index 00000000000..76ec1ac43fb
--- /dev/null
+++ b/src/main/java/bridge/constant/BridgeType.java
@@ -0,0 +1,30 @@
+package bridge.constant;
+
+import java.util.Arrays;
+
+public enum BridgeType {
+ U(1),
+ D(0);
+
+ private final Integer classifier;
+
+ BridgeType(final Integer classifier) {
+ this.classifier = classifier;
+ }
+
+ public static String getDirectionByClassifier(final int classifier) {
+ return Arrays.stream(BridgeType.values())
+ .filter(bridgeType -> bridgeType.classifier.equals(classifier))
+ .findAny()
+ .map(Enum::toString)
+ .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.INVALID_CLASSIFIER.toString()));
+ }
+
+ public static boolean isNotMoveCommand(final String input) {
+ return !input.equals(U.toString()) && !input.equals(D.toString());
+ }
+
+ public boolean isUp() {
+ return this == U;
+ }
+}
diff --git a/src/main/java/bridge/constant/ErrorMessage.java b/src/main/java/bridge/constant/ErrorMessage.java
new file mode 100644
index 00000000000..d5bb0447b5e
--- /dev/null
+++ b/src/main/java/bridge/constant/ErrorMessage.java
@@ -0,0 +1,22 @@
+package bridge.constant;
+
+public enum ErrorMessage {
+ NOT_NUMERIC_INPUT("숫자만 입력 가능합니다."),
+ INVALID_MOVE_COMMAND(String.format("이동 방향 입력값은 %s 혹은 %s 입니다.", BridgeType.D, BridgeType.U)),
+ INVALID_CLASSIFIER("다리의 숫자 값이 올바르지 않습니다."),
+ INVALID_BRIDGE_SIZE(String.format("다리의 길이는 %d 이상, %d 이하입니다.", BridgeSize.MIN_BRIDGE_SIZE.size(), BridgeSize.MAX_BRIDGE_SIZE.size())),
+ INVALID_RETRY_COMMAND(String.format("재시도 여부 입력값은 %s 혹은 %s 입니다.", RetryCommand.R, RetryCommand.Q)),
+ NO_ELEMENT_LEFT("리스트의 원소가 없습니다.");
+
+ private static final String ERROR_PREFIX = "[ERROR] ";
+ private final String message;
+
+ ErrorMessage(final String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return ERROR_PREFIX + this.message;
+ }
+}
diff --git a/src/main/java/bridge/constant/GameMessage.java b/src/main/java/bridge/constant/GameMessage.java
new file mode 100644
index 00000000000..03002920e02
--- /dev/null
+++ b/src/main/java/bridge/constant/GameMessage.java
@@ -0,0 +1,21 @@
+package bridge.constant;
+
+public enum GameMessage {
+ START_GAME("다리 건너기 게임을 시작합니다.\n"),
+ BRIDGE_SIZE_REQUEST("다리의 길이를 입력해주세요."),
+ FINAL_RESULT("최종 게임 결과"),
+ SUCCESS("게임 성공 여부: 성공\n총 시도한 횟수: %s"),
+ MOVE_DIRECTION_REQUEST(String.format("이동할 칸을 선택해주세요. (위: %s, 아래: %s)", BridgeType.U, BridgeType.D)),
+ RETRY_REQUEST(String.format("게임을 다시 시도할지 여부를 입력해주세요. (재시도: %s, 종료: %s)", RetryCommand.R, RetryCommand.Q));
+
+ private final String message;
+
+ GameMessage(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return this.message;
+ }
+}
diff --git a/src/main/java/bridge/constant/ResultStatus.java b/src/main/java/bridge/constant/ResultStatus.java
new file mode 100644
index 00000000000..960065a19a8
--- /dev/null
+++ b/src/main/java/bridge/constant/ResultStatus.java
@@ -0,0 +1,31 @@
+package bridge.constant;
+
+public enum ResultStatus {
+ O("O"),
+ X("X"),
+ CONTINUE("O"),
+ NONE(" ");
+
+ private final String symbol;
+
+ ResultStatus(final String symbol) {
+ this.symbol = symbol;
+ }
+
+ @Override
+ public String toString() {
+ return this.symbol;
+ }
+
+ public boolean isContinue() {
+ return this == CONTINUE;
+ }
+
+ public boolean isSuccess() {
+ return this == O;
+ }
+
+ public boolean isCorrect() {
+ return isContinue() || isSuccess();
+ }
+}
diff --git a/src/main/java/bridge/constant/RetryCommand.java b/src/main/java/bridge/constant/RetryCommand.java
new file mode 100644
index 00000000000..583afdce7cb
--- /dev/null
+++ b/src/main/java/bridge/constant/RetryCommand.java
@@ -0,0 +1,13 @@
+package bridge.constant;
+
+public enum RetryCommand {
+ R, Q;
+
+ public static boolean isNotRetryCommand(final String input) {
+ return !input.equals(R.toString()) && !input.equals(Q.toString());
+ }
+
+ public boolean isRetry() {
+ return this == R;
+ }
+}
diff --git a/src/main/java/bridge/domain/Bridge.java b/src/main/java/bridge/domain/Bridge.java
new file mode 100644
index 00000000000..9be5f977eb4
--- /dev/null
+++ b/src/main/java/bridge/domain/Bridge.java
@@ -0,0 +1,66 @@
+package bridge.domain;
+
+import bridge.constant.*;
+
+import java.util.Collections;
+import java.util.List;
+
+public final class Bridge {
+ private static final int NO_LEFT_ELEMENT = 0;
+ private static final int INITIAL_INDEX = 0;
+ private final List directions;
+ private int currentIndex;
+
+ public Bridge(final List directions) {
+ validateSize(directions);
+ this.directions = Collections.unmodifiableList(directions);
+ this.currentIndex = INITIAL_INDEX;
+ }
+
+ private void validateSize(final List directions) {
+ final int size = directions.size();
+ if (size < BridgeSize.MIN_BRIDGE_SIZE.size() || size > BridgeSize.MAX_BRIDGE_SIZE.size()) {
+ throw new IllegalArgumentException(ErrorMessage.INVALID_BRIDGE_SIZE.toString());
+ }
+ }
+
+ public BridgeType getCurrentElement() {
+ validateIndex(currentIndex);
+ return BridgeType.valueOf(directions.get(currentIndex++));
+ }
+
+ private void validateIndex(final int currentIndex) {
+ if (directions.size() <= currentIndex) {
+ throw new IllegalArgumentException(ErrorMessage.NO_ELEMENT_LEFT.toString());
+ }
+ }
+
+ private boolean hasNoRemainElement() {
+ return directions.size() - currentIndex == NO_LEFT_ELEMENT;
+ }
+
+ public ResultStatus compareToInput(final BridgeType input, final BridgeType answer) {
+ if (hasNoRemainElement()) {
+ return checkSuccess(input, answer);
+ }
+ return checkContinue(input, answer);
+ }
+
+ private ResultStatus checkContinue(final BridgeType input, final BridgeType answer) {
+ if (input == answer) {
+ return ResultStatus.CONTINUE;
+ }
+ return ResultStatus.X;
+ }
+
+ private ResultStatus checkSuccess(final BridgeType input, final BridgeType answer) {
+ if (input == answer) {
+ return ResultStatus.O;
+ }
+ return ResultStatus.X;
+ }
+
+ public void clearIndex() {
+ currentIndex = INITIAL_INDEX;
+ }
+}
diff --git a/src/main/java/bridge/domain/GameResult.java b/src/main/java/bridge/domain/GameResult.java
new file mode 100644
index 00000000000..b392e353dc7
--- /dev/null
+++ b/src/main/java/bridge/domain/GameResult.java
@@ -0,0 +1,74 @@
+package bridge.domain;
+
+import bridge.constant.BridgeType;
+import bridge.constant.ResultStatus;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class GameResult {
+ private static final String LINE_DELIMITER = "\n";
+ private static final String START_TAG = "[ ";
+ private static final String END_TAG = " ]";
+ private static final String SEPARATOR = " | ";
+ private final List upperResult;
+ private final List lowerResult;
+ private final TryCount tryCount;
+
+ public GameResult() {
+ this.upperResult = new ArrayList<>();
+ this.lowerResult = new ArrayList<>();
+ this.tryCount = new TryCount();
+ }
+
+ public ResultStatus addResultStatus(final Bridge bridge, final BridgeType input) {
+ final BridgeType answer = bridge.getCurrentElement();
+ final ResultStatus resultStatus = bridge.compareToInput(input, answer);
+
+ classifyResultStatus(answer, resultStatus);
+ return resultStatus;
+ }
+
+ private void classifyResultStatus(final BridgeType answer, final ResultStatus resultStatus) {
+ if (resultStatus.isCorrect()) {
+ addByAnswerCase(answer, resultStatus, ResultStatus.NONE);
+ return;
+ }
+ addByAnswerCase(answer, ResultStatus.NONE, resultStatus);
+ }
+
+ private void addByAnswerCase(final BridgeType answer, final ResultStatus resultStatus, final ResultStatus oppositeStatus) {
+ if (answer.isUp()) {
+ upperResult.add(resultStatus);
+ lowerResult.add(oppositeStatus);
+ return;
+ }
+ upperResult.add(oppositeStatus);
+ lowerResult.add(resultStatus);
+ }
+
+ @Override
+ public String toString() {
+ return toLineString(upperResult)
+ + LINE_DELIMITER
+ + toLineString(lowerResult);
+ }
+
+ private String toLineString(final List line) {
+ return START_TAG + line.stream()
+ .map(ResultStatus::toString)
+ .collect(Collectors.joining(SEPARATOR))
+ + END_TAG;
+ }
+
+ public void clearAndAddCount() {
+ upperResult.clear();
+ lowerResult.clear();
+ tryCount.nextCount();
+ }
+
+ public TryCount findTryCount() {
+ return this.tryCount;
+ }
+}
diff --git a/src/main/java/bridge/domain/TryCount.java b/src/main/java/bridge/domain/TryCount.java
new file mode 100644
index 00000000000..47daa454056
--- /dev/null
+++ b/src/main/java/bridge/domain/TryCount.java
@@ -0,0 +1,19 @@
+package bridge.domain;
+
+public class TryCount {
+ private static final int INITIAL_COUNT = 1;
+ private Integer count;
+
+ public TryCount() {
+ this.count = INITIAL_COUNT;
+ }
+
+ public void nextCount() {
+ this.count++;
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(count);
+ }
+}
diff --git a/src/main/java/bridge/factory/ComponentFactory.java b/src/main/java/bridge/factory/ComponentFactory.java
new file mode 100644
index 00000000000..8577674c9ac
--- /dev/null
+++ b/src/main/java/bridge/factory/ComponentFactory.java
@@ -0,0 +1,51 @@
+package bridge.factory;
+
+import bridge.service.BridgeGame;
+import bridge.BridgeMaker;
+import bridge.BridgeNumberGenerator;
+import bridge.BridgeRandomNumberGenerator;
+import bridge.io.InputProvider;
+import bridge.io.InputValidator;
+import bridge.io.InputView;
+import bridge.io.OutputView;
+import bridge.manager.BridgeGameManager;
+import bridge.storage.BridgeStorage;
+
+public class ComponentFactory {
+
+ public BridgeGameManager bridgeGameManager() {
+ return new BridgeGameManager(inputProvider(), outputView(), bridgeGame());
+ }
+
+ private BridgeGame bridgeGame() {
+ return new BridgeGame(bridgeStorage(), bridgeMaker());
+ }
+
+ private OutputView outputView() {
+ return new OutputView();
+ }
+
+ private InputProvider inputProvider() {
+ return new InputProvider(inputView());
+ }
+
+ private InputValidator inputValidator() {
+ return new InputValidator();
+ }
+
+ private InputView inputView() {
+ return new InputView(inputValidator());
+ }
+
+ private BridgeStorage bridgeStorage() {
+ return new BridgeStorage();
+ }
+
+ private BridgeMaker bridgeMaker() {
+ return new BridgeMaker(bridgeNumberGenerator());
+ }
+
+ private BridgeNumberGenerator bridgeNumberGenerator() {
+ return new BridgeRandomNumberGenerator();
+ }
+}
diff --git a/src/main/java/bridge/io/InputProvider.java b/src/main/java/bridge/io/InputProvider.java
new file mode 100644
index 00000000000..1967ce54074
--- /dev/null
+++ b/src/main/java/bridge/io/InputProvider.java
@@ -0,0 +1,36 @@
+package bridge.io;
+
+import bridge.constant.BridgeType;
+import bridge.constant.RetryCommand;
+
+import java.util.function.Supplier;
+
+public class InputProvider {
+ private final InputView inputView;
+
+ public InputProvider(final InputView inputView) {
+ this.inputView = inputView;
+ }
+
+ public int bridgeSize() {
+ return read(() -> Integer.parseInt(inputView.readBridgeSize()));
+ }
+
+ public BridgeType moveCommand() {
+ return read(() -> BridgeType.valueOf(inputView.readMoving()));
+ }
+
+ public RetryCommand retryCommand() {
+ return read(() -> RetryCommand.valueOf(inputView.readGameCommand()));
+ }
+
+ private T read(final Supplier supplier) {
+ while (true) {
+ try {
+ return supplier.get();
+ } catch (final IllegalArgumentException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+ }
+}
diff --git a/src/main/java/bridge/io/InputValidator.java b/src/main/java/bridge/io/InputValidator.java
new file mode 100644
index 00000000000..783c1f8a3e4
--- /dev/null
+++ b/src/main/java/bridge/io/InputValidator.java
@@ -0,0 +1,39 @@
+package bridge.io;
+
+import bridge.constant.BridgeType;
+import bridge.constant.ErrorMessage;
+import bridge.constant.RetryCommand;
+
+public class InputValidator {
+ private static final Character START_NUMBER = '0';
+ private static final Character END_NUMBER = '9';
+ public void validateBridgeSize(final String input) {
+ validateNumeric(input);
+ }
+
+ private void validateNumeric(final String input) {
+ if (hasNonNumber(input)) {
+ throw new IllegalArgumentException(ErrorMessage.NOT_NUMERIC_INPUT.toString());
+ }
+ }
+
+ private boolean hasNonNumber(final String input) {
+ return !input.chars().allMatch(this::isNumber);
+ }
+
+ private boolean isNumber(final int character) {
+ return START_NUMBER <= character && character <= END_NUMBER;
+ }
+
+ public void validateMoving(final String input) {
+ if (BridgeType.isNotMoveCommand(input)) {
+ throw new IllegalArgumentException(ErrorMessage.INVALID_MOVE_COMMAND.toString());
+ }
+ }
+
+ public void validateRetryCommand(final String input) {
+ if (RetryCommand.isNotRetryCommand(input)) {
+ throw new IllegalArgumentException(ErrorMessage.INVALID_RETRY_COMMAND.toString());
+ }
+ }
+}
diff --git a/src/main/java/bridge/io/InputView.java b/src/main/java/bridge/io/InputView.java
new file mode 100644
index 00000000000..5cacaad3276
--- /dev/null
+++ b/src/main/java/bridge/io/InputView.java
@@ -0,0 +1,41 @@
+package bridge.io;
+
+import camp.nextstep.edu.missionutils.Console;
+
+/**
+ * 사용자로부터 입력을 받는 역할을 한다.
+ */
+public class InputView {
+ private final InputValidator inputValidator;
+
+ public InputView(final InputValidator inputValidator) {
+ this.inputValidator = inputValidator;
+ }
+
+ /**
+ * 다리의 길이를 입력받는다.
+ */
+ public String readBridgeSize() {
+ final String input = Console.readLine();
+ inputValidator.validateBridgeSize(input);
+ return input;
+ }
+
+ /**
+ * 사용자가 이동할 칸을 입력받는다.
+ */
+ public String readMoving() {
+ final String input = Console.readLine();
+ inputValidator.validateMoving(input);
+ return input;
+ }
+
+ /**
+ * 사용자가 게임을 다시 시도할지 종료할지 여부를 입력받는다.
+ */
+ public String readGameCommand() {
+ final String input = Console.readLine();
+ inputValidator.validateRetryCommand(input);
+ return input;
+ }
+}
diff --git a/src/main/java/bridge/io/OutputView.java b/src/main/java/bridge/io/OutputView.java
new file mode 100644
index 00000000000..2e7e20c3b2b
--- /dev/null
+++ b/src/main/java/bridge/io/OutputView.java
@@ -0,0 +1,46 @@
+package bridge.io;
+
+import bridge.constant.GameMessage;
+import bridge.domain.GameResult;
+
+/**
+ * 사용자에게 게임 진행 상황과 결과를 출력하는 역할을 한다.
+ */
+public class OutputView {
+
+ /**
+ * 현재까지 이동한 다리의 상태를 정해진 형식에 맞춰 출력한다.
+ *
+ * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+ */
+ public void printMap(final GameResult gameResult) {
+ System.out.println(gameResult);
+ }
+
+ /**
+ * 게임의 최종 결과를 정해진 형식에 맞춰 출력한다.
+ *
+ * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+ */
+ public void printResult(final GameResult gameResult) {
+ System.out.println(GameMessage.FINAL_RESULT);
+ printMap(gameResult);
+ System.out.println(String.format(GameMessage.SUCCESS.toString(), gameResult.findTryCount()));
+ }
+
+ public void printGameStart() {
+ System.out.println(GameMessage.START_GAME);
+ }
+
+ public void printBridgeSizeRequest() {
+ System.out.println(GameMessage.BRIDGE_SIZE_REQUEST);
+ }
+
+ public void printMovingRequest() {
+ System.out.println(GameMessage.MOVE_DIRECTION_REQUEST);
+ }
+
+ public void printRetryRequest() {
+ System.out.println(GameMessage.RETRY_REQUEST);
+ }
+}
diff --git a/src/main/java/bridge/manager/BridgeGameManager.java b/src/main/java/bridge/manager/BridgeGameManager.java
new file mode 100644
index 00000000000..895349309a8
--- /dev/null
+++ b/src/main/java/bridge/manager/BridgeGameManager.java
@@ -0,0 +1,73 @@
+package bridge.manager;
+
+import bridge.service.BridgeGame;
+import bridge.constant.BridgeType;
+import bridge.constant.ResultStatus;
+import bridge.constant.RetryCommand;
+import bridge.io.OutputView;
+import bridge.io.InputProvider;
+
+public class BridgeGameManager {
+ private final InputProvider inputProvider;
+ private final OutputView outputView;
+ private final BridgeGame bridgeGame;
+
+ public BridgeGameManager(final InputProvider inputProvider, final OutputView outputView, final BridgeGame bridgeGame) {
+ this.inputProvider = inputProvider;
+ this.outputView = outputView;
+ this.bridgeGame = bridgeGame;
+ }
+
+ public void playGame() {
+ outputView.printGameStart();
+ play();
+ }
+
+ private void play() {
+ makeBridge();
+ boolean isRunning = true;
+ while (isRunning) {
+ isRunning = moveAndCheckRetry();
+ if (isRunning) {
+ bridgeGame.retry();
+ }
+ }
+ outputView.printResult(bridgeGame.findGameResult());
+ }
+
+ private boolean moveAndCheckRetry() {
+ while (true) {
+ final ResultStatus resultStatus = tryMove();
+ if (!resultStatus.isContinue()) {
+ return getRetryStatus(resultStatus);
+ }
+ outputView.printMap(bridgeGame.findGameResult());
+ }
+ }
+
+ private boolean getRetryStatus(final ResultStatus resultStatus) {
+ if (resultStatus.isSuccess()) {
+ return false;
+ }
+ outputView.printMap(bridgeGame.findGameResult());
+ return checkRetry();
+ }
+
+ private boolean checkRetry() {
+ outputView.printRetryRequest();
+ final RetryCommand retryCommand = inputProvider.retryCommand();
+ return retryCommand.isRetry();
+ }
+
+ private ResultStatus tryMove() {
+ outputView.printMovingRequest();
+ final BridgeType bridgeType = inputProvider.moveCommand();
+ return bridgeGame.move(bridgeType);
+ }
+
+ private void makeBridge() {
+ outputView.printBridgeSizeRequest();
+ final int bridgeSize = inputProvider.bridgeSize();
+ bridgeGame.makeBridge(bridgeSize);
+ }
+}
diff --git a/src/main/java/bridge/service/BridgeGame.java b/src/main/java/bridge/service/BridgeGame.java
new file mode 100644
index 00000000000..718e27fc7b5
--- /dev/null
+++ b/src/main/java/bridge/service/BridgeGame.java
@@ -0,0 +1,54 @@
+package bridge.service;
+
+import bridge.BridgeMaker;
+import bridge.constant.BridgeType;
+import bridge.constant.ResultStatus;
+import bridge.domain.Bridge;
+import bridge.domain.GameResult;
+import bridge.storage.BridgeStorage;
+
+/**
+ * 다리 건너기 게임을 관리하는 클래스
+ */
+public class BridgeGame {
+ private final BridgeStorage bridgeStorage;
+ private final BridgeMaker bridgeMaker;
+
+ public BridgeGame(final BridgeStorage bridgeStorage, final BridgeMaker bridgeMaker) {
+ this.bridgeStorage = bridgeStorage;
+ this.bridgeMaker = bridgeMaker;
+ }
+
+
+ /**
+ * 사용자가 칸을 이동할 때 사용하는 메서드
+ *
+ * 이동을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+ */
+ public ResultStatus move(final BridgeType bridgeType) {
+ final Bridge bridge = bridgeStorage.findBridge();
+ final GameResult gameResult = bridgeStorage.findGameResult();
+
+ return gameResult.addResultStatus(bridge, bridgeType);
+ }
+
+ /**
+ * 사용자가 게임을 다시 시도할 때 사용하는 메서드
+ *
+ * 재시작을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+ */
+ public void retry() {
+ bridgeStorage.findGameResult().clearAndAddCount();
+ bridgeStorage.findBridge().clearIndex();
+ }
+
+ public void makeBridge(final int bridgeSize) {
+ final Bridge bridge = new Bridge(bridgeMaker.makeBridge(bridgeSize));
+ bridgeStorage.saveBridge(bridge);
+ bridgeStorage.saveGameResult(new GameResult());
+ }
+
+ public GameResult findGameResult() {
+ return bridgeStorage.findGameResult();
+ }
+}
diff --git a/src/main/java/bridge/storage/BridgeStorage.java b/src/main/java/bridge/storage/BridgeStorage.java
new file mode 100644
index 00000000000..ac906ef995f
--- /dev/null
+++ b/src/main/java/bridge/storage/BridgeStorage.java
@@ -0,0 +1,25 @@
+package bridge.storage;
+
+import bridge.domain.Bridge;
+import bridge.domain.GameResult;
+
+public class BridgeStorage {
+ private GameResult gameResult;
+ private Bridge bridge;
+
+ public void saveBridge(final Bridge bridge) {
+ this.bridge = bridge;
+ }
+
+ public Bridge findBridge() {
+ return this.bridge;
+ }
+
+ public void saveGameResult(final GameResult gameResult) {
+ this.gameResult = gameResult;
+ }
+
+ public GameResult findGameResult() {
+ return this.gameResult;
+ }
+}
diff --git a/src/test/java/bridge/ApplicationTest.java b/src/test/java/bridge/ApplicationTest.java
index 1a163ec0a2a..6a7df4f16ac 100644
--- a/src/test/java/bridge/ApplicationTest.java
+++ b/src/test/java/bridge/ApplicationTest.java
@@ -39,6 +39,24 @@ class ApplicationTest extends NsTest {
}, 1, 0, 1);
}
+ @Test
+ void 재시도_기능_테스트() {
+ assertRandomNumberInRangeTest(() -> {
+ run("3", "U", "D", "D", "R", "U", "D", "U");
+ assertThat(output()).contains(
+ "최종 게임 결과",
+ "[ 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/BridgeMakerTest.java b/src/test/java/bridge/BridgeMakerTest.java
new file mode 100644
index 00000000000..16badf0e44e
--- /dev/null
+++ b/src/test/java/bridge/BridgeMakerTest.java
@@ -0,0 +1,25 @@
+package bridge;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("BridgeMaker의")
+class BridgeMakerTest {
+ private final BridgeMaker bridgeMaker = new BridgeMaker(() -> 0);
+
+ @Test
+ @DisplayName("다리 만들기가 수행되는가")
+ void makeBridge() {
+ //given
+
+ //when
+ final List bridge = bridgeMaker.makeBridge(3);
+
+ //then
+ assertThat(bridge).containsExactly("D", "D", "D");
+ }
+}
diff --git a/src/test/java/bridge/domain/BridgeTest.java b/src/test/java/bridge/domain/BridgeTest.java
new file mode 100644
index 00000000000..be5e0049450
--- /dev/null
+++ b/src/test/java/bridge/domain/BridgeTest.java
@@ -0,0 +1,136 @@
+package bridge.domain;
+
+import bridge.constant.BridgeType;
+import bridge.constant.ResultStatus;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.*;
+
+@DisplayName("Bridge의")
+class BridgeTest {
+
+ @Nested
+ @DisplayName("사이즈 검증 중")
+ class Validate {
+ @Test
+ @DisplayName("3 이하의 size의 리스트가 들어올시 예외가 던져지는가")
+ void validateSizeLessThan3() {
+ assertThatThrownBy(() -> new Bridge(List.of("U", "U")))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ @DisplayName("3 이하의 size의 리스트가 들어올시 예외가 던져지는가")
+ void validateSizeGreaterThan20() {
+ assertThatThrownBy(() -> new Bridge(
+ List.of("U", "U", "U", "U", "U",
+ "U", "U", "U", "U", "U",
+ "U", "U", "U", "U", "U",
+ "U", "U", "U", "U", "U",
+ "U")))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+ }
+
+ @Nested
+ @DisplayName("현재 원소 조회 중")
+ class GetCurrentElement {
+ @Test
+ @DisplayName("횟수 내 조회가 수행되는가")
+ void getCurrentElement() {
+ //given
+ final Bridge bridge = new Bridge(List.of("U", "D", "U"));
+
+ //when
+ final BridgeType U1 = bridge.getCurrentElement();
+ final BridgeType D = bridge.getCurrentElement();
+ final BridgeType U2 = bridge.getCurrentElement();
+
+ //then
+ assertThat(List.of(U1, D, U2)).containsExactly(BridgeType.U, BridgeType.D, BridgeType.U);
+ }
+
+ @Test
+ @DisplayName("현재 원소 조회 횟수 초과시 예외가 던져지는가")
+ void getCurrentElementException() {
+ //given
+ final Bridge bridge = new Bridge(List.of("U", "D", "U"));
+
+ final BridgeType U1 = bridge.getCurrentElement();
+ final BridgeType D = bridge.getCurrentElement();
+ final BridgeType U2 = bridge.getCurrentElement();
+
+ //when
+ //then
+ assertThatThrownBy(bridge::getCurrentElement)
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+ }
+
+ @Nested
+ @DisplayName("입력값 비교 중")
+ class CompareToInput {
+ @Test
+ @DisplayName("정답과 다른 입력값 비교가 수행되는가")
+ void compareToInput1() {
+ //given
+ final Bridge bridge = new Bridge(List.of("U", "D", "U"));
+
+ //when
+ final ResultStatus resultStatus = bridge.compareToInput(BridgeType.U, BridgeType.D);
+
+ //then
+ assertThat(resultStatus).isEqualTo(ResultStatus.X);
+ }
+
+ @Test
+ @DisplayName("정답과 동일한 입력값 비교가 수행되는가")
+ void compareToInput2() {
+ //given
+ final Bridge bridge = new Bridge(List.of("U", "D", "U"));
+
+ //when
+ final ResultStatus resultStatus = bridge.compareToInput(BridgeType.U, BridgeType.U);
+
+ //then
+ assertThat(resultStatus).isEqualTo(ResultStatus.CONTINUE);
+ }
+
+ @Test
+ @DisplayName("정답과 동일한 입력값 비교가 수행되는가")
+ void compareToInput3() {
+ //given
+ final Bridge bridge = new Bridge(List.of("U", "D", "U"));
+
+ //when
+ final ResultStatus resultStatus1 = bridge.compareToInput(BridgeType.U, BridgeType.U);
+ final ResultStatus resultStatus2 = bridge.compareToInput(BridgeType.U, BridgeType.U);
+ final ResultStatus resultStatus3 = bridge.compareToInput(BridgeType.D, BridgeType.U);
+
+ //then
+ assertThat(resultStatus3).isEqualTo(ResultStatus.X);
+ }
+ }
+
+ @Test
+ @DisplayName("Bridge의")
+ void clearIndex() {
+ //given
+ final Bridge bridge = new Bridge(List.of("U", "D", "U"));
+ bridge.getCurrentElement();
+
+ //when
+ bridge.clearIndex();
+
+ //then
+ final BridgeType U = bridge.getCurrentElement();
+ assertThat(U).isEqualTo(BridgeType.U);
+ }
+}
diff --git a/src/test/java/bridge/io/InputValidatorTest.java b/src/test/java/bridge/io/InputValidatorTest.java
new file mode 100644
index 00000000000..1fd02afa5d9
--- /dev/null
+++ b/src/test/java/bridge/io/InputValidatorTest.java
@@ -0,0 +1,34 @@
+package bridge.io;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class InputValidatorTest {
+
+ private final InputValidator inputValidator = new InputValidator();
+
+ @Test
+ void validateBridgeSizeBlank() {
+ assertThatThrownBy(() -> inputValidator.validateBridgeSize(" "))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void validateBridgeSizeCharacter() {
+ assertThatThrownBy(() -> inputValidator.validateBridgeSize("s"))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void validateMoving() {
+ assertThatThrownBy(() -> inputValidator.validateMoving("A"))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void validateRetryCommand() {
+ assertThatThrownBy(() -> inputValidator.validateRetryCommand("A"))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+}
diff --git a/src/test/java/bridge/service/BridgeGameTest.java b/src/test/java/bridge/service/BridgeGameTest.java
new file mode 100644
index 00000000000..2bc91bc1baf
--- /dev/null
+++ b/src/test/java/bridge/service/BridgeGameTest.java
@@ -0,0 +1,113 @@
+package bridge.service;
+
+import bridge.BridgeMaker;
+import bridge.constant.BridgeType;
+import bridge.constant.ResultStatus;
+import bridge.domain.Bridge;
+import bridge.domain.GameResult;
+import bridge.domain.TryCount;
+import bridge.storage.BridgeStorage;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("BridgeGame의")
+class BridgeGameTest {
+ private final BridgeStorage bridgeStorage = new BridgeStorage();
+ private final BridgeGame bridgeGame = new BridgeGame(bridgeStorage, new BridgeMaker(() -> 1));
+
+ @Nested
+ @DisplayName("유저의 이동 시도 중")
+ class Move {
+ @Test
+ @DisplayName("유저의 이동 시도가 수행되는가")
+ void moveD() {
+ //given
+ bridgeGame.makeBridge(3);
+
+ //when
+ final ResultStatus resultStatus = bridgeGame.move(BridgeType.D);
+
+ //then
+ assertThat(resultStatus).isEqualTo(ResultStatus.X);
+ }
+
+ @Test
+ @DisplayName("유저의 이동 시도가 수행되는가")
+ void moveU() {
+ //given
+ bridgeGame.makeBridge(3);
+
+ //when
+ final ResultStatus resultStatus = bridgeGame.move(BridgeType.U);
+
+ //then
+ assertThat(resultStatus).isEqualTo(ResultStatus.CONTINUE);
+ }
+
+ @Test
+ @DisplayName("유저의 이동 시도가 수행되는가")
+ void moveUUU() {
+ //given
+ bridgeGame.makeBridge(3);
+
+ //when
+ final ResultStatus resultStatus1 = bridgeGame.move(BridgeType.U);
+ final ResultStatus resultStatus2 = bridgeGame.move(BridgeType.U);
+ final ResultStatus resultStatus3 = bridgeGame.move(BridgeType.U);
+
+ //then
+ assertThat(resultStatus3).isEqualTo(ResultStatus.O);
+ }
+ }
+
+ @Test
+ @DisplayName("재시도 로직이 수행되는가")
+ void retry() {
+ //given
+ bridgeGame.makeBridge(3);
+ bridgeGame.move(BridgeType.U);
+
+ //when
+ bridgeGame.retry();
+
+ //then
+ final GameResult gameResult = bridgeGame.findGameResult();
+ final TryCount tryCount = gameResult.findTryCount();
+ assertThat(tryCount).hasToString(Integer.toString(2));
+ }
+
+ @Test
+ @DisplayName("다리 생성이 수행되는가")
+ void makeBridge() {
+ //given
+
+ //when
+ bridgeGame.makeBridge(3);
+
+ //then
+ final Bridge bridge = bridgeStorage.findBridge();
+ final BridgeType bt1 = bridge.getCurrentElement();
+ final BridgeType bt2 = bridge.getCurrentElement();
+ final BridgeType bt3 = bridge.getCurrentElement();
+ assertThat(List.of(bt1, bt2, bt3)).containsExactly(BridgeType.U, BridgeType.U, BridgeType.U);
+ }
+
+ @Test
+ @DisplayName("결과 조회가 수행되는가")
+ void findGameResult() {
+ //given
+ final GameResult expected = new GameResult();
+ bridgeStorage.saveGameResult(expected);
+
+ //when
+ final GameResult gameResult = bridgeGame.findGameResult();
+
+ //then
+ assertThat(gameResult).isEqualTo(expected);
+ }
+}
diff --git a/src/test/java/bridge/storage/BridgeStorageTest.java b/src/test/java/bridge/storage/BridgeStorageTest.java
new file mode 100644
index 00000000000..c4384d8fee6
--- /dev/null
+++ b/src/test/java/bridge/storage/BridgeStorageTest.java
@@ -0,0 +1,71 @@
+package bridge.storage;
+
+import bridge.domain.Bridge;
+import bridge.domain.GameResult;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("BridgeStorage의")
+class BridgeStorageTest {
+ private final BridgeStorage bridgeStorage = new BridgeStorage();
+
+ @Test
+ @DisplayName("Bridge 저장이 수행되는가")
+ void saveBridge() {
+ //given
+ final Bridge bridge = new Bridge(List.of("D", "U", "D"));
+
+ //when
+ bridgeStorage.saveBridge(bridge);
+
+ //then
+ final Bridge expected = bridgeStorage.findBridge();
+ assertThat(bridge).isEqualTo(expected);
+ }
+
+ @Test
+ @DisplayName("Bridge 조회가 수행되는가")
+ void findBridge() {
+ //given
+ final Bridge expected = new Bridge(List.of("D", "U", "D"));
+ bridgeStorage.saveBridge(expected);
+
+ //when
+ final Bridge bridge = bridgeStorage.findBridge();
+
+ //then
+ assertThat(bridge).isEqualTo(expected);
+ }
+
+ @Test
+ @DisplayName("GameResult 저장이 수행되는가")
+ void saveGameResult() {
+ //given
+ final GameResult gameResult = new GameResult();
+
+ //when
+ bridgeStorage.saveGameResult(gameResult);
+
+ //then
+ final GameResult expected = bridgeStorage.findGameResult();
+ assertThat(gameResult).isEqualTo(expected);
+ }
+
+ @Test
+ @DisplayName("GameResult 조회가 수행되는가")
+ void findGameResult() {
+ //given
+ final GameResult expected = new GameResult();
+ bridgeStorage.saveGameResult(expected);
+
+ //when
+ final GameResult gameResult = bridgeStorage.findGameResult();
+
+ //then
+ assertThat(gameResult).isEqualTo(expected);
+ }
+}