diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..3ee733ded75 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,58 @@ +## 다리 건너기 기능 구현 목록 + +**입력 & 출력** +- [x] 게임 시작 안내 출력 +- [x] 다리 길이 입력 (3 ~ 20사이 숫자) +- [x] 이동할 칸 입력 (U / D) +- [x] 재시도 입력 (R / Q) + +**다리 생성** +- 입력한 다리 길이만큼 다리 생성 + - [x] 0(아래), 1(위)형식으로 다리 요소 랜덤 생성 + - [x] 0➝D / 1➝U 변환 + +**게임 진행** +- [x] 생성된 다리와 사용자의 입력 비교 + - [x] 이동 가능한 경우 계속 진행 + - [x] 이동 불가능한 경우 재시작 / 종료 +- [x] 재시작하는 경우 시도 횟수 증가 +현 +**게임 진행 내용 저장** +- [x] 사용자의 입력이 맞는지 저장한다 + - 맞을 경우 O, 틀릴 경우 X 저장 + +**결과 출력** +- 다리 출력 + - [x] `[`로 시작, `]`로 끝, 구분자는 `|` + - [x] 이동 가능한 경우 O / 불가능한 경우 X 출력 +- [x] 게임 성공 여부 +- [x] 총 시도 횟수 + +**예외 처리** +- 이동할 칸 입력 + - [x] U/D 이외의 입력이 들어올 경우 +- 재시도 입력 + - [x] R/Q 이외의 입력이 들어올 경우 +- 다리 길이 입력 + - [x] 숫자가 아닌 입력이 들어온 경우 + - [x] 범위 이외의 숫자가 들어온 경우 + +### 테스트 목록 + +**BridgeMaker** +- [x] 다리 길이 입력시 길이에 맞는 다리를 생성하는가 + +**InputValidator** +- [x] 움직임 입력 시 U/D 이외의 문자를 입력하면 예외가 발생하는가 +- [x] 재시작 입력 시 R/Q 이외의 문자를 입력하면 예외가 발생하는가 +- [x] 다리 길이를 입력할 경우 문자를 입력하면 예외가 발생하는가 +- [x] 다리 길이를 입력할 경우 3~20 이외의 숫자를 입력하면 예외가 발생하는가 + +**LocationConverter** +- [x] 0 = D, 1 = U로 변환을 하는가 + +**BridgeGame** +- [x] 게임 진행 시 사용자가 입력한 내용과 다리의 내용이 일치하는지 확인 +- [x] 정답 계산 이후 사용자 위치 움직였는지 확인 +- [x] 게임 재시작 시 재시작 카운트 증가 +- [x] 게임 재시작 시 사용자 위치, 다리 건넌 기록 초기화 \ No newline at end of file diff --git a/src/main/java/bridge/Application.java b/src/main/java/bridge/Application.java index 5cb72dfd3de..fe1a8545862 100644 --- a/src/main/java/bridge/Application.java +++ b/src/main/java/bridge/Application.java @@ -1,8 +1,13 @@ package bridge; +import bridge.controller.BridgeController; +import bridge.view.InputView; +import bridge.view.OutputView; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + BridgeController bridgeController = new BridgeController(new OutputView(), new InputView()); + bridgeController.start(); } } 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..567561e7403 100644
--- a/src/main/java/bridge/BridgeMaker.java
+++ b/src/main/java/bridge/BridgeMaker.java
@@ -1,5 +1,8 @@
package bridge;
+import bridge.model.BridgeConverter;
+
+import java.util.ArrayList;
import java.util.List;
/**
@@ -18,6 +21,10 @@ public BridgeMaker(BridgeNumberGenerator bridgeNumberGenerator) {
* @return 입력받은 길이에 해당하는 다리 모양. 위 칸이면 "U", 아래 칸이면 "D"로 표현해야 한다.
*/
public List
- * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
- */
- public void printMap() {
- }
-
- /**
- * 게임의 최종 결과를 정해진 형식에 맞춰 출력한다.
- *
- * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
- */
- public void printResult() {
- }
-}
diff --git a/src/main/java/bridge/controller/BridgeController.java b/src/main/java/bridge/controller/BridgeController.java
new file mode 100644
index 00000000000..b6d1f3b5e59
--- /dev/null
+++ b/src/main/java/bridge/controller/BridgeController.java
@@ -0,0 +1,65 @@
+package bridge.controller;
+
+import bridge.BridgeMaker;
+import bridge.BridgeRandomNumberGenerator;
+import bridge.model.AnswerTable;
+import bridge.model.Bridge;
+import bridge.model.Constant;
+import bridge.model.GameStatus;
+import bridge.service.BridgeGame;
+import bridge.view.InputView;
+import bridge.view.OutputView;
+
+public class BridgeController {
+
+ private final OutputView outputView;
+ private final InputView inputView;
+ private final AnswerTable answerTable = new AnswerTable();
+ private final GameStatus gameStatus = new GameStatus();
+
+ public BridgeController(OutputView outputView, InputView inputView) {
+ this.outputView = outputView;
+ this.inputView = inputView;
+ }
+
+ public void start() {
+ try {
+ Bridge bridge = makeBridge();
+ progressGame(bridge);
+ printResult();
+ } catch (IllegalArgumentException exception) {
+ outputView.printExceptionMessage(exception.getMessage());
+ }
+ }
+
+ private Bridge makeBridge() {
+ outputView.printGameStart();
+ BridgeMaker bridgeMaker = new BridgeMaker(new BridgeRandomNumberGenerator());
+ return new Bridge(bridgeMaker.makeBridge(inputView.readBridgeSize()));
+ }
+
+ private void progressGame(Bridge bridge) {
+ BridgeGame bridgeGame = new BridgeGame(bridge, gameStatus, answerTable);
+ while (gameStatus.getPosition() < bridge.size()) {
+ boolean isCorrect = bridgeGame.move(inputView.readMoving());
+ outputView.printMap(answerTable);
+ if(!isCorrect && !retry(bridgeGame)) {
+ gameStatus.setGameResultFailed();
+ break;
+ }
+ }
+ }
+
+ private boolean retry(BridgeGame bridgeGame) {
+ String userInput = inputView.readGameCommand();
+ if (userInput.equals(Constant.RETRY.toString())) {
+ bridgeGame.retry();
+ return true;
+ }
+ return false;
+ }
+
+ private void printResult() {
+ outputView.printResult(answerTable, gameStatus);
+ }
+}
diff --git a/src/main/java/bridge/model/AnswerTable.java b/src/main/java/bridge/model/AnswerTable.java
new file mode 100644
index 00000000000..ec2d1d5adb5
--- /dev/null
+++ b/src/main/java/bridge/model/AnswerTable.java
@@ -0,0 +1,59 @@
+package bridge.model;
+
+import bridge.view.ExceptionMessage;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class AnswerTable {
+
+ private final List
+ * 이동을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+ */
+ public boolean move(String moving) {
+ int position = gameStatus.getPosition();
+ boolean isCorrect = bridgeComparison.checkMoving(moving, position);
+ answerTable.setAnswerTable(moving, isCorrect);
+ gameStatus.movePosition();
+ return isCorrect;
+ }
+
+ /**
+ * 사용자가 게임을 다시 시도할 때 사용하는 메서드
+ *
+ * 재시작을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+ */
+ public void retry() {
+ gameStatus.increaseRetryCount();
+ gameStatus.clear();
+ answerTable.clear();
+ }
+}
diff --git a/src/main/java/bridge/view/BridgeElement.java b/src/main/java/bridge/view/BridgeElement.java
new file mode 100644
index 00000000000..b63d7df7a16
--- /dev/null
+++ b/src/main/java/bridge/view/BridgeElement.java
@@ -0,0 +1,19 @@
+package bridge.view;
+
+public enum BridgeElement {
+ BRIDGE_START("[ "),
+ BRIDGE_END(" ]"),
+ BRIDGE_DELIMITER(" | ");
+
+
+ private final String element;
+
+ BridgeElement(String element) {
+ this.element = element;
+ }
+
+ @Override
+ public String toString() {
+ return this.element;
+ }
+}
diff --git a/src/main/java/bridge/view/ExceptionMessage.java b/src/main/java/bridge/view/ExceptionMessage.java
new file mode 100644
index 00000000000..41ee20a3208
--- /dev/null
+++ b/src/main/java/bridge/view/ExceptionMessage.java
@@ -0,0 +1,21 @@
+package bridge.view;
+
+public enum ExceptionMessage {
+
+ INCORRECT_MOVING("U 또는 D로만 이동할 수 있습니다."),
+ INCORRECT_RETRY("R 또는 Q만 입력할 수 있습니다."),
+ NOT_INTEGER("숫자만 입력할 수 있습니다."),
+ INCORRECT_RANGE("다리 길이는 3부터 20 사이의 숫자여야 합니다."),
+ MOVING_RESULT_NONE("해당 방향으로 움직일 수 없습니다..");
+
+ private static final String prefix = "[ERROR] ";
+ private final String message;
+ ExceptionMessage(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return prefix + message;
+ }
+}
diff --git a/src/main/java/bridge/view/InputValidator.java b/src/main/java/bridge/view/InputValidator.java
new file mode 100644
index 00000000000..2d85c9d4902
--- /dev/null
+++ b/src/main/java/bridge/view/InputValidator.java
@@ -0,0 +1,51 @@
+package bridge.view;
+
+import bridge.model.Constant;
+
+import java.util.regex.Pattern;
+
+public class InputValidator {
+
+ private static final String NUMBER_REGEXP = "^\\d*$";
+ private static final int MAX_BRIDGE_LENGTH = 20;
+ private static final int MIN_BRIDGE_LENGTH = 1;
+
+ public void validateMoving(String input) {
+ validateIsMovingInput(input);
+ }
+
+ private void validateIsMovingInput(String input) {
+ if (!(input.equals(Constant.LOWER_CHAR.toString()) || input.equals(Constant.UPPER_CHAR.toString()))) {
+ throw new IllegalArgumentException(ExceptionMessage.INCORRECT_MOVING.toString());
+ }
+ }
+
+ public void validateRetry(String input) {
+ validateIsRetryInput(input);
+ }
+
+ private void validateIsRetryInput(String input) {
+ if (!(input.equals(Constant.RETRY.toString()) || input.equals(Constant.QUIT.toString()))) {
+ throw new IllegalArgumentException(ExceptionMessage.INCORRECT_RETRY.toString());
+ }
+ }
+
+ public void validateBridgeLength(String input) {
+ validateIsNumber(input);
+ validateIsCorrectRange(Integer.parseInt(input));
+ }
+
+ private void validateIsNumber(String input) {
+ if (!Pattern.matches(NUMBER_REGEXP, input)) {
+ ExceptionMessage exceptionMessage = ExceptionMessage.NOT_INTEGER;
+ throw new IllegalArgumentException(exceptionMessage.toString());
+ }
+ }
+
+ private void validateIsCorrectRange(int input) {
+ if (!(MIN_BRIDGE_LENGTH <= input && input <= MAX_BRIDGE_LENGTH)) {
+ ExceptionMessage exceptionMessage = ExceptionMessage.INCORRECT_RANGE;
+ throw new IllegalArgumentException(exceptionMessage.toString());
+ }
+ }
+}
diff --git a/src/main/java/bridge/view/InputView.java b/src/main/java/bridge/view/InputView.java
new file mode 100644
index 00000000000..fae5d17cb46
--- /dev/null
+++ b/src/main/java/bridge/view/InputView.java
@@ -0,0 +1,43 @@
+package bridge.view;
+
+import camp.nextstep.edu.missionutils.Console;
+
+/**
+ * 사용자로부터 입력을 받는 역할을 한다.
+ */
+public class InputView {
+
+ private final OutputView outputView = new OutputView();
+ private final InputValidator inputValidator = new InputValidator();
+
+ /**
+ * 다리의 길이를 입력받는다.
+ */
+ public int readBridgeSize() {
+ outputView.printLengthMessage();
+ String userInput = Console.readLine();
+ inputValidator.validateBridgeLength(userInput);
+ outputView.printNewLine();
+ return Integer.parseInt(userInput);
+ }
+
+ /**
+ * 사용자가 이동할 칸을 입력받는다.
+ */
+ public String readMoving() {
+ outputView.printMoveLocation();
+ String input = Console.readLine();
+ inputValidator.validateMoving(input);
+ return input;
+ }
+
+ /**
+ * 사용자가 게임을 다시 시도할지 종료할지 여부를 입력받는다.
+ */
+ public String readGameCommand() {
+ outputView.printRetryMessage();
+ String input = Console.readLine();
+ inputValidator.validateRetry(input);
+ return input;
+ }
+}
diff --git a/src/main/java/bridge/view/OutputMessage.java b/src/main/java/bridge/view/OutputMessage.java
new file mode 100644
index 00000000000..d641dd579c8
--- /dev/null
+++ b/src/main/java/bridge/view/OutputMessage.java
@@ -0,0 +1,23 @@
+package bridge.view;
+
+public enum OutputMessage {
+
+ START_MESSAGE("다리 건너기 게임을 시작합니다."),
+ LENGTH_MESSAGE("다리의 길이를 입력해주세요."),
+ MOVE_LOCATION("이동할 칸을 선택해주세요. (위: U, 아래: D)"),
+ RETRY_MESSAGE("게임을 다시 시도할지 여부를 입력해주세요. (재시도: R, 종료: Q)"),
+ GAME_END_MESSAGE("최종 게임 결과"),
+ GAME_STATUS("게임 성공 여부: %s\n"),
+ RETRY_NUMBERS("총 시도한 횟수: %d\n");
+
+ private final String message;
+
+ OutputMessage(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return this.message;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/bridge/view/OutputView.java b/src/main/java/bridge/view/OutputView.java
new file mode 100644
index 00000000000..1c251845a73
--- /dev/null
+++ b/src/main/java/bridge/view/OutputView.java
@@ -0,0 +1,68 @@
+package bridge.view;
+
+import bridge.model.AnswerTable;
+import bridge.model.GameStatus;
+
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * 사용자에게 게임 진행 상황과 결과를 출력하는 역할을 한다.
+ */
+public class OutputView {
+
+ private static final String NEW_LINE = "";
+
+ /**
+ * 현재까지 이동한 다리의 상태를 정해진 형식에 맞춰 출력한다.
+ *
+ * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+ */
+ public void printMap(AnswerTable answerTable) {
+ printBridge(answerTable.getUpper());
+ printBridge(answerTable.getLower());
+ printNewLine();
+ }
+
+ /**
+ * 게임의 최종 결과를 정해진 형식에 맞춰 출력한다.
+ *
+ * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+ */
+ public void printResult(AnswerTable answerTable, GameStatus gameStatus) {
+ System.out.println(OutputMessage.GAME_END_MESSAGE);
+ printMap(answerTable);
+ System.out.printf((OutputMessage.GAME_STATUS.toString()), gameStatus.getGameResult());
+ System.out.printf(OutputMessage.RETRY_NUMBERS.toString(), gameStatus.getRetry());
+ }
+
+ public void printGameStart() {
+ System.out.println(OutputMessage.START_MESSAGE);
+ printNewLine();
+ }
+
+ public void printLengthMessage() {
+ System.out.println(OutputMessage.LENGTH_MESSAGE);
+ }
+ public void printMoveLocation() {
+ System.out.println(OutputMessage.MOVE_LOCATION);
+ }
+
+ public void printRetryMessage() {
+ System.out.println(OutputMessage.RETRY_MESSAGE);
+ }
+
+ public void printBridge(List