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 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 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/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 directions; + + private Bridge(final List directions) { + this.directions = Collections.unmodifiableList(directions); + } + + public static Bridge create(final List directions) { + return new Bridge(directions); + } + + public BridgeType getNextElement(final int currentIndex) { + 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()); + } + } + + public int getLeftSize(final int currentIndex) { + return directions.size() - currentIndex; + } +} diff --git a/src/main/java/bridge/domain/BridgeGame.java b/src/main/java/bridge/domain/BridgeGame.java new file mode 100644 index 00000000000..4f6779aed67 --- /dev/null +++ b/src/main/java/bridge/domain/BridgeGame.java @@ -0,0 +1,108 @@ +package bridge.domain; + +import bridge.constant.*; +import bridge.factory.MoveResultMapper; + +import java.util.List; + +/** + * 다리 건너기 게임을 관리하는 클래스 + */ +public class BridgeGame { + private static final String LINE_DELIMITER = "\n"; + private static final int INITIAL_INDEX = 0; + private final GameResult upperGameResult; + private final GameResult lowerGameResult; + private final MoveResultMapper moveResultMapper; // 요구사항에 의해 한 번만 생성될 것이므로 싱글톤으로 구현하지 않음 + private final Bridge bridge; + private Integer currentIndex; + + public BridgeGame(final List bridgeDirections) { + this.bridge = Bridge.create(bridgeDirections); + this.upperGameResult = new GameResult(); + this.lowerGameResult = new GameResult(); + this.moveResultMapper = new MoveResultMapper(); + initCurrentIndex(); + } + + /** + * 사용자가 칸을 이동할 때 사용하는 메서드 + *

+ * 이동을 위해 필요한 메서드의 반환 타입(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 gameResultStatusList; + + public GameResult() { + this.gameResultStatusList = new ArrayList<>(); + } + + public void addResultStatus(final GameResultStatus gameResultStatus) { + this.gameResultStatusList.add(gameResultStatus); + } + + @Override + public String toString() { + return START_TAG + gameResultStatusList.stream() + .map(GameResultStatus::toString) + .collect(Collectors.joining(SEPARATOR)) + END_TAG; + } + + public void clearResult() { + this.gameResultStatusList.clear(); + } +} diff --git a/src/main/java/bridge/domain/TryCount.java b/src/main/java/bridge/domain/TryCount.java new file mode 100644 index 00000000000..20fe2116353 --- /dev/null +++ b/src/main/java/bridge/domain/TryCount.java @@ -0,0 +1,24 @@ +package bridge.domain; + +public final class TryCount { + private static final int INITIAL_COUNT = 1; + private static final int PLUS_UNIT = 1; + private final int count; + + private TryCount(final int count) { + this.count = count; + } + + @Override + public String toString() { + return String.valueOf(this.count); + } + + public static TryCount create() { + return new TryCount(INITIAL_COUNT); + } + + public TryCount next() { + return new TryCount(this.count + PLUS_UNIT); + } +} diff --git a/src/main/java/bridge/factory/BridgeMaker.java b/src/main/java/bridge/factory/BridgeMaker.java new file mode 100644 index 00000000000..64a691e9b58 --- /dev/null +++ b/src/main/java/bridge/factory/BridgeMaker.java @@ -0,0 +1,45 @@ +package bridge.factory; + +import bridge.utils.BridgeNumberGenerator; +import bridge.constant.BridgeType; +import bridge.constant.ErrorMessage; + +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 static final int MIN_BRIDGE_SIZE = 3; + private static final int MAX_BRIDGE_SIZE = 20; + private final BridgeNumberGenerator bridgeNumberGenerator; + + public BridgeMaker(final BridgeNumberGenerator bridgeNumberGenerator) { + this.bridgeNumberGenerator = bridgeNumberGenerator; + } + + /** + * @param size 다리의 길이 + * @return 입력받은 길이에 해당하는 다리 모양. 위 칸이면 "U", 아래 칸이면 "D"로 표현해야 한다. + */ + public List makeBridge(final int size) { + validateSize(size); + return IntStream.range(START_INDEX, size) + .mapToObj(i -> getBridgeDirection()) + .collect(Collectors.toList()); + } + + private void validateSize(final int size) { + if (size < MIN_BRIDGE_SIZE || MAX_BRIDGE_SIZE < size) { + throw new IllegalArgumentException(String.format(ErrorMessage.INVALID_BRIDGE_SIZE.toString(), MIN_BRIDGE_SIZE, MAX_BRIDGE_SIZE)); + } + } + + private String getBridgeDirection() { + final int randomNumber = bridgeNumberGenerator.generate(); + return BridgeType.getDirectionByClassifier(randomNumber); + } +} diff --git a/src/main/java/bridge/factory/ComponentFactory.java b/src/main/java/bridge/factory/ComponentFactory.java new file mode 100644 index 00000000000..46b77b65b22 --- /dev/null +++ b/src/main/java/bridge/factory/ComponentFactory.java @@ -0,0 +1,35 @@ +package bridge.factory; + +import bridge.manager.BridgeGameManager; +import bridge.utils.BridgeNumberGenerator; +import bridge.utils.BridgeRandomNumberGenerator; +import bridge.io.InputValidator; +import bridge.io.InputView; +import bridge.io.OutputView; + +public class ComponentFactory { + + public BridgeGameManager bridgeGameManager() { + return new BridgeGameManager(inputView(), outputView(), bridgeMaker()); + } + + private OutputView outputView() { + return new OutputView(); + } + + private InputView inputView() { + return new InputView(inputValidator()); + } + + private InputValidator inputValidator() { + return new InputValidator(); + } + + private BridgeMaker bridgeMaker() { + return new BridgeMaker(bridgeNumberGenerator()); + } + + private BridgeNumberGenerator bridgeNumberGenerator() { + return new BridgeRandomNumberGenerator(); + } +} diff --git a/src/main/java/bridge/factory/MoveResultMapper.java b/src/main/java/bridge/factory/MoveResultMapper.java new file mode 100644 index 00000000000..99c8855346b --- /dev/null +++ b/src/main/java/bridge/factory/MoveResultMapper.java @@ -0,0 +1,29 @@ +package bridge.factory; + +import bridge.constant.BridgeType; +import bridge.constant.MoveResult; + +public class MoveResultMapper { + private static final int NO_LEFT_ELEMENT = 0; + + public MoveResult mapToMoveResult(final BridgeType input, final BridgeType answer, final int leftSize) { + if (leftSize == NO_LEFT_ELEMENT) { + return checkSuccess(input, answer); + } + return checkContinue(input, answer); + } + + private MoveResult checkContinue(final BridgeType input, final BridgeType answer) { + if (input == answer) { + return MoveResult.CONTINUE; + } + return MoveResult.FAILED; + } + + private MoveResult checkSuccess(final BridgeType input, final BridgeType answer) { + if (input == answer) { + return MoveResult.SUCCESS; + } + return MoveResult.FAILED; + } +} diff --git a/src/main/java/bridge/io/InputValidator.java b/src/main/java/bridge/io/InputValidator.java new file mode 100644 index 00000000000..2efe4dde110 --- /dev/null +++ b/src/main/java/bridge/io/InputValidator.java @@ -0,0 +1,45 @@ +package bridge.io; + +import bridge.constant.ErrorMessage; + +public class InputValidator { + private static final Character START_NUMBER = '0'; + private static final Character END_NUMBER = '9'; + private static final Integer COMMAND_LENGTH = 1; + + public void validateBridgeSizeInput(final String input) { + validateNumeric(input); + validateNotBlank(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(int character) { + return START_NUMBER <= character && character <= END_NUMBER; + } + + private void validateNotBlank(final String input) { + if (input.isBlank()) { + throw new IllegalArgumentException(ErrorMessage.BLANK_INPUT.toString()); + } + } + + public void validateCommand(final String input) { + validateNotBlank(input); + validateCommandLength(input); + } + + private void validateCommandLength(final String input) { + if (input.length() != COMMAND_LENGTH) { + throw new IllegalArgumentException(String.format(ErrorMessage.INVALID_COMMAND_LENGTH.toString(), COMMAND_LENGTH)); + } + } +} diff --git a/src/main/java/bridge/io/InputView.java b/src/main/java/bridge/io/InputView.java new file mode 100644 index 00000000000..732f99998eb --- /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 int readBridgeSize() { + final String input = Console.readLine(); + inputValidator.validateBridgeSizeInput(input); + return Integer.parseInt(input); + } + + /** + * 사용자가 이동할 칸을 입력받는다. + */ + public String readMoving() { + final String input = Console.readLine(); + inputValidator.validateCommand(input); + return input; + } + + /** + * 사용자가 게임을 다시 시도할지 종료할지 여부를 입력받는다. + */ + public String readGameCommand() { + final String input = Console.readLine(); + inputValidator.validateCommand(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..90d117f0f23 --- /dev/null +++ b/src/main/java/bridge/io/OutputView.java @@ -0,0 +1,52 @@ +package bridge.io; + +import bridge.domain.TryCount; +import bridge.constant.BridgeType; +import bridge.constant.GameMessage; +import bridge.constant.RetryCommand; + +/** + * 사용자에게 게임 진행 상황과 결과를 출력하는 역할을 한다. + */ +public class OutputView { + + public void printGameStart() { + System.out.println(GameMessage.START_GAME); + } + + public void printBridgeSizeRequest() { + System.out.println(GameMessage.BRIDGE_SIZE_REQUEST); + } + + /** + * 현재까지 이동한 다리의 상태를 정해진 형식에 맞춰 출력한다. + *

+ * 출력을 위해 필요한 메서드의 인자(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;