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); + } +}