diff --git a/README.md b/README.md index 8ab62a91992..8d0b64dba0a 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,7 @@ public class BridgeMaker { ### BridgeRandomNumberGenerator 클래스 -- Random 값 추출은 제공된 `bridge.BridgeRandomNumberGenerator`의 `generate()`를 활용한다. +- Random 값 추출은 제공된 `bridge.utils.BridgeRandomNumberGenerator`의 `generate()`를 활용한다. - `BridgeRandomNumberGenerator`, `BridgeNumberGenerator` 클래스의 코드는 변경할 수 없다. #### 사용 예시 diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..f91c6c226b5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,97 @@ +## 기능 목록 +- [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] 게임 종료 + +## 구현 클래스 목록 +- BridgeGame + - move() + - retry() + - getGameResultMap() result map 문자열 반환 + +- BridgeGameManager + - playBridgeGame() 게임 실행 + +- BridgeMaker + - makeBridge() + +- BridgeNumberGenerator + - generate() + +- BridgeRandomNumberGenerator + - generate() + +- InputView + - readBridgeSize() + - readMoving() + - readGameCommand() + +- InputValidator + - validateBridgeSizeInput() 다리 길이 입력값 검증 + +- OutputView + - printGameStart() + - printBridgeSizeRequest() + - printMap() + - printResult() + - printMovingRequest() 이동할 칸 입력 요청 출력 + +- ComponentFactory + - bridgeGameManager() BridgeGameManager 생성 + +- Bridge + - create() Bridge 객체 생성 팩토리 메서드 + +- GameResult + - addResultStatus() 결과 상태 추가 + - getFirstBridgeElement() 다리의 첫번째 요소 조회 + - getBridgeLeftSize() 다리 잔여 사이즈 조회 + +- MoveResultMapper + - mapToMoveResult() MoveResult로 매핑 + +- TryCount + - increment() 시도 횟수 증가 + - toString() 시도 횟수 문자열로 반환 + +## 열거형 목록 +- GameMessage + - 게임 메시지 + +- BridgeType + - 다리의 윗 칸과 아래칸 + +- ErrorMessage + - 에러 메시지 + +- MoveResult + - 이동 결과 + +- GameResultStatus + - 결과 상태 + +- RetryCommand + - 재시도 입력값 diff --git a/src/main/java/bridge/Application.java b/src/main/java/bridge/Application.java index 5cb72dfd3de..11c5d6831b3 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().playBridgeGame(); } } 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
deleted file mode 100644
index 27e9f2cfa7f..00000000000
--- 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
- * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
- */
- public void printMap() {
- }
-
- /**
- * 게임의 최종 결과를 정해진 형식에 맞춰 출력한다.
- *
- * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
- */
- public void printResult() {
- }
-}
diff --git a/src/main/java/bridge/constant/BridgeType.java b/src/main/java/bridge/constant/BridgeType.java
new file mode 100644
index 00000000000..9c2777319da
--- /dev/null
+++ b/src/main/java/bridge/constant/BridgeType.java
@@ -0,0 +1,22 @@
+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()));
+ }
+}
diff --git a/src/main/java/bridge/constant/ErrorMessage.java b/src/main/java/bridge/constant/ErrorMessage.java
new file mode 100644
index 00000000000..3688a6e7fea
--- /dev/null
+++ b/src/main/java/bridge/constant/ErrorMessage.java
@@ -0,0 +1,24 @@
+package bridge.constant;
+
+public enum ErrorMessage {
+ NOT_NUMERIC_INPUT("숫자만 입력 가능합니다."),
+ BLANK_INPUT("입력값이 비어있습니다."),
+ INVALID_CLASSIFIER("다리의 숫자 값이 올바르지 않습니다."),
+ INVALID_MOVE_DIRECTION("이동 방향 입력값은 %s 혹은 %s 입니다."),
+ NO_ELEMENT_LEFT("리스트의 원소가 없습니다."),
+ INVALID_RETRY_COMMAND("재시도 여부 입력값은 %s 혹은 %s 입니다."),
+ INVALID_BRIDGE_SIZE("다리의 길이는 %d 이상, %d 이하입니다."),
+ INVALID_COMMAND_LENGTH("커맨드의 길이는 %d 이여야 합니다.");
+
+ private final String message;
+ private static final String ERROR_PREFIX = "[ERROR] ";
+
+ ErrorMessage(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..d14c7edd671
--- /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("다리의 길이를 입력해주세요."),
+ MOVE_DIRECTION_REQUEST("이동할 칸을 선택해주세요. (위: %s, 아래: %s)"),
+ SUCCESS("게임 성공 여부: 성공\n총 시도한 횟수: %s"),
+ RETRY_REQUEST("게임을 다시 시도할지 여부를 입력해주세요. (재시도: %s, 종료: %s)"),
+ FINAL_RESULT("최종 게임 결과");
+
+ 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/GameResultStatus.java b/src/main/java/bridge/constant/GameResultStatus.java
new file mode 100644
index 00000000000..92eaaebed63
--- /dev/null
+++ b/src/main/java/bridge/constant/GameResultStatus.java
@@ -0,0 +1,18 @@
+package bridge.constant;
+
+public enum GameResultStatus {
+ O("O"),
+ X("X"),
+ NONE(" ");
+
+ private final String symbol;
+
+ GameResultStatus(final String symbol) {
+ this.symbol = symbol;
+ }
+
+ @Override
+ public String toString() {
+ return this.symbol;
+ }
+}
diff --git a/src/main/java/bridge/constant/MoveResult.java b/src/main/java/bridge/constant/MoveResult.java
new file mode 100644
index 00000000000..9a789f68603
--- /dev/null
+++ b/src/main/java/bridge/constant/MoveResult.java
@@ -0,0 +1,5 @@
+package bridge.constant;
+
+public enum MoveResult {
+ CONTINUE, FAILED, SUCCESS;
+}
diff --git a/src/main/java/bridge/constant/RetryCommand.java b/src/main/java/bridge/constant/RetryCommand.java
new file mode 100644
index 00000000000..dc13ab4c3c3
--- /dev/null
+++ b/src/main/java/bridge/constant/RetryCommand.java
@@ -0,0 +1,5 @@
+package bridge.constant;
+
+public enum RetryCommand {
+ R, Q
+}
diff --git a/src/main/java/bridge/domain/Bridge.java b/src/main/java/bridge/domain/Bridge.java
new file mode 100644
index 00000000000..723935ba5d1
--- /dev/null
+++ b/src/main/java/bridge/domain/Bridge.java
@@ -0,0 +1,34 @@
+package bridge.domain;
+
+import bridge.constant.BridgeType;
+import bridge.constant.ErrorMessage;
+
+import java.util.Collections;
+import java.util.List;
+
+public final class Bridge {
+ private final List
+ * 이동을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+ */
+ public MoveResult move(final String movingInput) {
+ validateMovingInput(movingInput);
+ final BridgeType inputBridgeType = BridgeType.valueOf(movingInput);
+ final BridgeType answerBridgeType = bridge.getNextElement(currentIndex++);
+
+ final MoveResult moveResult = moveResultMapper.mapToMoveResult(inputBridgeType, answerBridgeType, bridge.getLeftSize(currentIndex));
+ addGameResultStatus(moveResult, answerBridgeType);
+ return moveResult;
+ }
+
+ /**
+ * 사용자가 게임을 다시 시도할 때 사용하는 메서드
+ *
+ * 재시작을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+ */
+ public boolean retry(final String retryInput) {
+ validateRetryInput(retryInput);
+ if (retryInput.equals(RetryCommand.R.toString())) {
+ clearAll();
+ return true;
+ }
+ return false;
+ }
+
+ private void clearAll() {
+ initCurrentIndex();
+ upperGameResult.clearResult();
+ lowerGameResult.clearResult();
+ }
+
+ private void addGameResultStatus(final MoveResult moveResult, final BridgeType answerBridgeType) {
+ if (moveResult == MoveResult.FAILED) {
+ addToProperGameResult(answerBridgeType, GameResultStatus.X, false);
+ return;
+ }
+ addToProperGameResult(answerBridgeType, GameResultStatus.O, true);
+ }
+
+ private void addToProperGameResult(final BridgeType answerBridgeType, final GameResultStatus gameResultStatus, final boolean isSuccess) {
+ if (answerBridgeType.equals(BridgeType.U)) {
+ addResultStatus(isSuccess, gameResultStatus, GameResultStatus.NONE);
+ return;
+ }
+ addResultStatus(isSuccess, GameResultStatus.NONE, gameResultStatus);
+ }
+
+ private void addResultStatus(final boolean isSuccess, final GameResultStatus resultStatus, final GameResultStatus oppositeStatus) {
+ if (isSuccess) {
+ upperGameResult.addResultStatus(resultStatus);
+ lowerGameResult.addResultStatus(oppositeStatus);
+ return;
+ }
+ upperGameResult.addResultStatus(oppositeStatus);
+ lowerGameResult.addResultStatus(resultStatus);
+ }
+
+ public String getGameResultMap() {
+ return this.upperGameResult + LINE_DELIMITER + this.lowerGameResult;
+ }
+
+ private void initCurrentIndex() {
+ this.currentIndex = INITIAL_INDEX;
+ }
+
+ private void validateRetryInput(final String input) {
+ if (!input.equals(RetryCommand.R.toString()) && !input.equals(RetryCommand.Q.toString())) {
+ throw new IllegalArgumentException(String.format(ErrorMessage.INVALID_RETRY_COMMAND.toString(), RetryCommand.R, RetryCommand.Q));
+ }
+ }
+
+ private void validateMovingInput(final String input) {
+ if (!input.equals(BridgeType.U.toString()) && !input.equals(BridgeType.D.toString())) {
+ throw new IllegalArgumentException(String.format(ErrorMessage.INVALID_MOVE_DIRECTION.toString(), BridgeType.U, BridgeType.D));
+ }
+ }
+}
diff --git a/src/main/java/bridge/domain/GameResult.java b/src/main/java/bridge/domain/GameResult.java
new file mode 100644
index 00000000000..c13d6369092
--- /dev/null
+++ b/src/main/java/bridge/domain/GameResult.java
@@ -0,0 +1,33 @@
+package bridge.domain;
+
+import bridge.constant.GameResultStatus;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class GameResult {
+ private static final String START_TAG = "[ ";
+ private static final String END_TAG = " ]";
+ private static final String SEPARATOR = " | ";
+ private final List
+ * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+ */
+ public void printMap(final String gameResultMap) {
+ System.out.println(gameResultMap);
+ }
+
+ /**
+ * 게임의 최종 결과를 정해진 형식에 맞춰 출력한다.
+ *
+ * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
+ */
+ public void printResult(final TryCount tryCount, final String gameResultMap) {
+ System.out.println(GameMessage.FINAL_RESULT);
+ printMap(gameResultMap);
+ System.out.println(String.format(GameMessage.SUCCESS.toString(), tryCount));
+ }
+
+ public void printMovingRequest() {
+ System.out.println(String.format(GameMessage.MOVE_DIRECTION_REQUEST.toString(), BridgeType.U, BridgeType.D));
+ }
+
+ public void printRetryRequest() {
+ System.out.println(String.format(GameMessage.RETRY_REQUEST.toString(), RetryCommand.R, RetryCommand.Q));
+ }
+
+ public void printErrorMessage(final String message) {
+ System.out.println(message);
+ }
+}
diff --git a/src/main/java/bridge/manager/BridgeGameManager.java b/src/main/java/bridge/manager/BridgeGameManager.java
new file mode 100644
index 00000000000..74108a36eea
--- /dev/null
+++ b/src/main/java/bridge/manager/BridgeGameManager.java
@@ -0,0 +1,79 @@
+package bridge.manager;
+
+import bridge.constant.MoveResult;
+import bridge.domain.BridgeGame;
+import bridge.domain.TryCount;
+import bridge.io.InputView;
+import bridge.io.OutputView;
+import bridge.factory.BridgeMaker;
+
+public class BridgeGameManager {
+ private final InputView inputView;
+ private final OutputView outputView;
+ private final BridgeMaker bridgeMaker;
+ private TryCount tryCount;
+
+ public BridgeGameManager(final InputView inputView, final OutputView outputView, final BridgeMaker bridgeMaker) {
+ this.inputView = inputView;
+ this.outputView = outputView;
+ this.bridgeMaker = bridgeMaker;
+ this.tryCount = TryCount.create();
+ }
+
+ public void playBridgeGame() {
+ outputView.printGameStart();
+ try {
+ playUntilGameEnd();
+ } catch (IllegalArgumentException e) {
+ outputView.printErrorMessage(e.getMessage());
+ }
+ }
+
+ private void playUntilGameEnd() {
+ boolean isGameRunning = true;
+ final BridgeGame bridgeGame = makeBridgeGameWithSize();
+ while (isGameRunning) {
+ isGameRunning = moveAndGetRetryStatus(bridgeGame);
+ if (isGameRunning) {
+ tryCount = tryCount.next();
+ }
+ }
+ outputView.printResult(tryCount, bridgeGame.getGameResultMap());
+ }
+
+ private BridgeGame makeBridgeGameWithSize() {
+ outputView.printBridgeSizeRequest();
+ final int bridgeSize = inputView.readBridgeSize();
+ return new BridgeGame(bridgeMaker.makeBridge(bridgeSize));
+ }
+
+ private boolean moveAndGetRetryStatus(final BridgeGame bridgeGame) {
+ while (true) {
+ final MoveResult moveResult = tryMove(bridgeGame);
+ if (moveResult != MoveResult.CONTINUE) {
+ return getRetryStatus(bridgeGame, moveResult);
+ }
+ outputView.printMap(bridgeGame.getGameResultMap());
+ }
+ }
+
+ private MoveResult tryMove(final BridgeGame bridgeGame) {
+ outputView.printMovingRequest();
+ final String movingInput = inputView.readMoving();
+ return bridgeGame.move(movingInput);
+ }
+
+ private boolean getRetryStatus(final BridgeGame bridgeGame, final MoveResult moveResult) {
+ if (moveResult == MoveResult.SUCCESS) {
+ return false;
+ }
+ outputView.printMap(bridgeGame.getGameResultMap());
+ return checkRetry(bridgeGame);
+ }
+
+ private boolean checkRetry(final BridgeGame bridgeGame) {
+ outputView.printRetryRequest();
+ final String retryInput = inputView.readGameCommand();
+ return bridgeGame.retry(retryInput);
+ }
+}
diff --git a/src/main/java/bridge/BridgeNumberGenerator.java b/src/main/java/bridge/utils/BridgeNumberGenerator.java
similarity index 79%
rename from src/main/java/bridge/BridgeNumberGenerator.java
rename to src/main/java/bridge/utils/BridgeNumberGenerator.java
index 56187b71d2d..94a44d6bfcf 100644
--- a/src/main/java/bridge/BridgeNumberGenerator.java
+++ b/src/main/java/bridge/utils/BridgeNumberGenerator.java
@@ -1,4 +1,4 @@
-package bridge;
+package bridge.utils;
@FunctionalInterface
public interface BridgeNumberGenerator {
diff --git a/src/main/java/bridge/BridgeRandomNumberGenerator.java b/src/main/java/bridge/utils/BridgeRandomNumberGenerator.java
similarity index 94%
rename from src/main/java/bridge/BridgeRandomNumberGenerator.java
rename to src/main/java/bridge/utils/BridgeRandomNumberGenerator.java
index 4c9cb53e03a..f54f3073733 100644
--- a/src/main/java/bridge/BridgeRandomNumberGenerator.java
+++ b/src/main/java/bridge/utils/BridgeRandomNumberGenerator.java
@@ -1,4 +1,4 @@
-package bridge;
+package bridge.utils;
import camp.nextstep.edu.missionutils.Randoms;
diff --git a/src/test/java/bridge/ApplicationTest.java b/src/test/java/bridge/ApplicationTest.java
index 1a163ec0a2a..ca2cc40c4cb 100644
--- a/src/test/java/bridge/ApplicationTest.java
+++ b/src/test/java/bridge/ApplicationTest.java
@@ -5,6 +5,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.util.Lists.newArrayList;
+import bridge.factory.BridgeMaker;
+import bridge.utils.BridgeNumberGenerator;
import camp.nextstep.edu.missionutils.test.NsTest;
import java.util.List;
import org.junit.jupiter.api.Test;