diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..897edaf2007 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,50 @@ +# πŸ’ͺ ν”„λ‘œμ νŠΈ κ°œμš” + +건널 수 μžˆλŠ” 닀리λ₯Ό μƒμ„±ν•˜κ³ , ν”Œλ ˆμ΄μ–΄κ°€ 닀리λ₯Ό κ±΄λ„ˆλŠ” κ²Œμž„μ„ κ΅¬ν˜„ν•œλ‹€. + +# πŸ“ κ΅¬ν˜„ κΈ°λŠ₯ λͺ©λ‘ + +### κ²Œμž„ μ‹œμž‘ 문ꡬλ₯Ό 좜λ ₯ν•˜λŠ” κΈ°λŠ₯ + +- [x] `닀리 κ±΄λ„ˆκΈ° κ²Œμž„μ„ μ‹œμž‘ν•©λ‹ˆλ‹€.`λ₯Ό 좜λ ₯ν•œλ‹€. + +### μžλ™μœΌλ‘œ 닀리λ₯Ό μƒμ„±ν•˜λŠ” κΈ°λŠ₯ + +- [x] λ‹€λ¦¬μ˜ 길이λ₯Ό μž…λ ₯ν•œλ‹€. + - [x] `λ‹€λ¦¬μ˜ 길이λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.`λ₯Ό 좜λ ₯ν•œλ‹€. + - [x] λ‹€λ¦¬μ˜ 길이λ₯Ό μž…λ ₯λ°›λŠ”λ‹€. + - [x] 빈 λ¬Έμžμ—΄μ΄ μ•„λ‹˜μ„ κ²€μ¦ν•œλ‹€. + - [x] 숫자 μž…λ ₯μž„μ„ κ²€μ¦ν•œλ‹€. + - [x] 3 이상 20μ΄ν•˜μž„μ„ κ²€μ¦ν•œλ‹€. +- [x] 닀리λ₯Ό μƒμ„±ν•œλ‹€. + - [x] λ‹€λ¦¬μ˜ 길이만큼 0κ³Ό 1 쀑 λ¬΄μž‘μœ„ 값을 μƒμ„±ν•œλ‹€. + - [x] 0인 경우 μ•„λž˜ μΉΈ, 1인 경우 μœ„ 칸을 건널 수 μžˆλŠ” 칸으둜 μ €μž₯ν•œλ‹€. + +### ν”Œλ ˆμ΄μ–΄κ°€ 닀리λ₯Ό μ΄λ™ν•˜λŠ” κΈ°λŠ₯ + +- [x] `이동할 칸을 μ„ νƒν•΄μ£Όμ„Έμš”. (μœ„: U, μ•„λž˜: D)`λ₯Ό 좜λ ₯ν•œλ‹€. +- [x] ν”Œλ ˆμ΄μ–΄κ°€ 이동할 칸을 μž…λ ₯ν•œλ‹€. + - [x] 빈 λ¬Έμžμ—΄μ΄ μ•„λ‹˜μ„ κ²€μ¦ν•œλ‹€. + - [x] U ν˜Ήμ€ D의 μž…λ ₯μž„μ„ κ²€μ¦ν•œλ‹€. +- [x] ν”Œλ ˆμ΄μ–΄κ°€ μ΄λ™ν•œ 칸이 이동할 수 μžˆλŠ” 칸인지 κ²€μ‚¬ν•œλ‹€. + - [x] 이동할 수 μžˆλŠ” 칸을 μ„ νƒν•œ 경우 Oλ₯Ό ν‘œμ‹œν•œλ‹€. + - [x] λͺ¨λ“  닀리λ₯Ό μ΄λ™ν•œ 경우 κ²°κ³Όλ₯Ό λ°˜ν™˜ν•œλ‹€. + - [x] 이동할 수 μ—†λŠ” 칸을 μ„ νƒν•œ 경우 + - [x] μž¬μ‹œλ„ ν˜Ήμ€ μ’…λ£Œ μ—¬λΆ€λ₯Ό μ„ νƒν•œλ‹€. + - [x] μ’…λ£Œλ₯Ό μ„ νƒν•œ 경우 κ²°κ³Όλ₯Ό λ°˜ν™˜ν•œλ‹€. + +### κ²Œμž„μ„ μ’…λ£Œν•˜λŠ” κΈ°λŠ₯ + +- [x] `μ΅œμ’… κ²Œμž„ κ²°κ³Ό`λ₯Ό 좜λ ₯ν•œλ‹€. +- [x] μ΅œμ’…μ μœΌλ‘œ λ§Œλ“€μ–΄μ§„ λ‹€λ¦¬μ˜ μƒνƒœλ₯Ό 좜λ ₯ν•œλ‹€. +- [x] `κ²Œμž„ 성곡 μ—¬λΆ€: 성곡`와 같이 κ²Œμž„ 성곡 μ—¬λΆ€λ₯Ό 좜λ ₯ν•œλ‹€. +- [x] `총 μ‹œλ„ν•œ 횟수: 2`와 같이 총 μ‹œλ„ν•œ 횟수λ₯Ό 좜λ ₯ν•œλ‹€. + +### μΉΈ 이동에 μ‹€νŒ¨ν•œ 경우 μž¬μ‹œλ„ ν˜Ήμ€ μ’…λ£Œ μ—¬λΆ€λ₯Ό μ„ νƒν•˜λŠ” κΈ°λŠ₯ + +- [x] `κ²Œμž„μ„ λ‹€μ‹œ μ‹œλ„ν• μ§€ μ—¬λΆ€λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”. (μž¬μ‹œλ„: R, μ’…λ£Œ: Q)`λ₯Ό 좜λ ₯ν•œλ‹€. +- [x] ν”Œλ ˆμ΄μ–΄λŠ” μž¬μ‹œλ„ ν˜Ήμ€ μ’…λ£Œ μ—¬λΆ€λ₯Ό μž…λ ₯ν•œλ‹€. + - [x] 빈 λ¬Έμžμ—΄μ΄ μ•„λ‹˜μ„ κ²€μ¦ν•œλ‹€. + - [x] R ν˜Ήμ€ Q의 μž…λ ₯μž„μ„ κ²€μ¦ν•œλ‹€. +- [x] μž¬μ‹œλ„λ₯Ό μ„ νƒν•œ 경우, μ²˜μŒλΆ€ν„° λ‹€μ‹œ 닀리λ₯Ό μ΄λ™ν•œλ‹€. +- [x] μ’…λ£Œλ₯Ό μ„ νƒν•œ 경우, κ²Œμž„μ„ μ’…λ£Œν•œλ‹€. diff --git a/src/main/java/bridge/Application.java b/src/main/java/bridge/Application.java index 5cb72dfd3de..a0ba72cae0d 100644 --- a/src/main/java/bridge/Application.java +++ b/src/main/java/bridge/Application.java @@ -1,8 +1,42 @@ package bridge; +import bridge.controller.BridgeController; +import bridge.controller.BridgeGame; +import bridge.controller.MoveController; +import bridge.domain.BridgeMaker; +import bridge.util.BridgeRandomNumberGenerator; +import bridge.view.InputView; +import bridge.view.OutputView; + public class Application { public static void main(String[] args) { - // TODO: ν”„λ‘œκ·Έλž¨ κ΅¬ν˜„ + InputView inputView = new InputView(); + BridgeController bridgeController = getBridgeController(inputView); + MoveController moveController = getMoveController(inputView); + BridgeGame bridgeGame = getBridgeGame(bridgeController, moveController); + bridgeGame.run(); + } + + private static BridgeController getBridgeController(InputView inputView) { + return new BridgeController( + inputView, + new BridgeMaker(new BridgeRandomNumberGenerator()) + ); + } + + private static MoveController getMoveController(InputView inputView) { + return new MoveController( + inputView, + new OutputView() + ); + } + + private static BridgeGame getBridgeGame(BridgeController bridgeController, MoveController moveController) { + return new BridgeGame( + new OutputView(), + bridgeController, + moveController + ); } } 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/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/controller/BridgeController.java b/src/main/java/bridge/controller/BridgeController.java new file mode 100644 index 00000000000..7fe48ef61c1 --- /dev/null +++ b/src/main/java/bridge/controller/BridgeController.java @@ -0,0 +1,23 @@ +package bridge.controller; + +import bridge.domain.BridgeMaker; +import bridge.util.RetryExecutor; +import bridge.view.InputView; +import java.util.List; + +public class BridgeController { + private final InputView inputView; + private final BridgeMaker bridgeMaker; + + public BridgeController(InputView inputView, BridgeMaker bridgeMaker) { + this.inputView = inputView; + this.bridgeMaker = bridgeMaker; + } + + public List prepare() { + int bridgeSize = RetryExecutor.retryUntilSuccess(() -> { + return inputView.readBridgeSize(); + }); + return bridgeMaker.makeBridge(bridgeSize); + } +} diff --git a/src/main/java/bridge/controller/BridgeGame.java b/src/main/java/bridge/controller/BridgeGame.java new file mode 100644 index 00000000000..656d496bd70 --- /dev/null +++ b/src/main/java/bridge/controller/BridgeGame.java @@ -0,0 +1,31 @@ +package bridge.controller; + +import bridge.controller.dto.MoveResult; +import bridge.view.OutputView; +import java.util.List; + +/** + * 닀리 κ±΄λ„ˆκΈ° κ²Œμž„μ„ κ΄€λ¦¬ν•˜λŠ” 클래슀 + */ +public class BridgeGame { + private final OutputView outputView; + private final BridgeController bridgeController; + private final MoveController moveController; + + public BridgeGame( + OutputView outputView, + BridgeController bridgeController, + MoveController moveController + ) { + this.outputView = outputView; + this.bridgeController = bridgeController; + this.moveController = moveController; + this.outputView.printStartMessage(); + } + + public void run() { + List bridges = bridgeController.prepare(); + MoveResult moveResult = moveController.retry(bridges); + outputView.printResult(moveResult); + } +} diff --git a/src/main/java/bridge/controller/MoveController.java b/src/main/java/bridge/controller/MoveController.java new file mode 100644 index 00000000000..270caa2c9df --- /dev/null +++ b/src/main/java/bridge/controller/MoveController.java @@ -0,0 +1,80 @@ +package bridge.controller; + +import bridge.controller.dto.Move; +import bridge.controller.dto.MoveResult; +import bridge.domain.constants.Result; +import bridge.util.RetryExecutor; +import bridge.view.InputView; +import bridge.view.OutputView; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class MoveController { + private final InputView inputView; + private final OutputView outputView; + + public MoveController(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + /** + * μ‚¬μš©μžκ°€ κ²Œμž„μ„ λ‹€μ‹œ μ‹œλ„ν•  λ•Œ μ‚¬μš©ν•˜λŠ” λ©”μ„œλ“œ + */ + public MoveResult retry(List bridges) { + int count = 1; + while (true) { + List moves = move(bridges); + Optional moveResult = createMoveResult(moves, count); + if (moveResult.isPresent()) { + return moveResult.get(); + } + count++; + } + } + + private Optional createMoveResult(List moves, int count) { + if (isSucceed(moves)) { // λκΉŒμ§€ 이동에 μ„±κ³΅ν•œ 경우 + return Optional.of(new MoveResult(moves, Result.SUCCESS, count)); + } + String retry = RetryExecutor.retryUntilSuccess(inputView::readGameCommand); + if ("Q".equals(retry)) { // μ’…λ£Œ + return Optional.of(new MoveResult(moves, Result.FAIL, count)); + } + return Optional.empty(); // μž¬μ‹œμž‘ + } + + private boolean isSucceed(List result) { + int size = result.size(); + return result.get(size - 1).success().equals("O"); + } + + /** + * μ‚¬μš©μžκ°€ 칸을 이동할 λ•Œ μ‚¬μš©ν•˜λŠ” λ©”μ„œλ“œ + */ + public List move(List bridges) { + List moves = new ArrayList<>(); + for (String bridge : bridges) { + Move move = createSingleMove(bridge); + moves.add(move); + outputView.printMap(moves); + if (move.success().equals("X")) { + return moves; + } + } + return moves; + } + + private Move createSingleMove(String bridge) { + String direction = RetryExecutor.retryUntilSuccess(inputView::readMoving); + if (cannotMove(bridge, direction)) { + return new Move(direction, "X"); + } + return new Move(direction, "O"); + } + + private boolean cannotMove(String bridge, String direction) { + return !bridge.equals(direction); + } +} diff --git a/src/main/java/bridge/controller/dto/Move.java b/src/main/java/bridge/controller/dto/Move.java new file mode 100644 index 00000000000..e01b24844a4 --- /dev/null +++ b/src/main/java/bridge/controller/dto/Move.java @@ -0,0 +1,19 @@ +package bridge.controller.dto; + +public class Move { + private String direction; // U, D + private String success; // O, X + + public Move(String direction, String success) { + this.direction = direction; + this.success = success; + } + + public String direction() { + return direction; + } + + public String success() { + return success; + } +} diff --git a/src/main/java/bridge/controller/dto/MoveResult.java b/src/main/java/bridge/controller/dto/MoveResult.java new file mode 100644 index 00000000000..33a6e4f7172 --- /dev/null +++ b/src/main/java/bridge/controller/dto/MoveResult.java @@ -0,0 +1,28 @@ +package bridge.controller.dto; + +import bridge.domain.constants.Result; +import java.util.List; + +public class MoveResult { + private List moves; + private Result success; + private int count; + + public MoveResult(List moves, Result success, int count) { + this.moves = moves; + this.success = success; + this.count = count; + } + + public List singleMoves() { + return moves; + } + + public Result success() { + return success; + } + + public int count() { + return count; + } +} diff --git a/src/main/java/bridge/BridgeMaker.java b/src/main/java/bridge/domain/BridgeMaker.java similarity index 56% rename from src/main/java/bridge/BridgeMaker.java rename to src/main/java/bridge/domain/BridgeMaker.java index 27e9f2cfa7f..b73255b111f 100644 --- a/src/main/java/bridge/BridgeMaker.java +++ b/src/main/java/bridge/domain/BridgeMaker.java @@ -1,5 +1,7 @@ -package bridge; +package bridge.domain; +import bridge.util.BridgeNumberGenerator; +import java.util.ArrayList; import java.util.List; /** @@ -18,6 +20,16 @@ public BridgeMaker(BridgeNumberGenerator bridgeNumberGenerator) { * @return μž…λ ₯받은 길이에 ν•΄λ‹Ήν•˜λŠ” 닀리 λͺ¨μ–‘. μœ„ 칸이면 "U", μ•„λž˜ 칸이면 "D"둜 ν‘œν˜„ν•΄μ•Ό ν•œλ‹€. */ public List makeBridge(int size) { - return null; + List result = new ArrayList<>(); + for (int i = 0; i < size; i++) { + int number = bridgeNumberGenerator.generate(); + if (number == 1) { // μœ„ + result.add("U"); + } + if (number == 0) { // μ•„λž˜ + result.add("D"); + } + } + return result; } } diff --git a/src/main/java/bridge/domain/constants/Result.java b/src/main/java/bridge/domain/constants/Result.java new file mode 100644 index 00000000000..9cc21ce4abe --- /dev/null +++ b/src/main/java/bridge/domain/constants/Result.java @@ -0,0 +1,16 @@ +package bridge.domain.constants; + +public enum Result { + SUCCESS("성곡"), + FAIL("μ‹€νŒ¨"); + + private final String name; + + Result(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/bridge/global/exception/CustomException.java b/src/main/java/bridge/global/exception/CustomException.java new file mode 100644 index 00000000000..759725c904b --- /dev/null +++ b/src/main/java/bridge/global/exception/CustomException.java @@ -0,0 +1,13 @@ +package bridge.global.exception; + +public class CustomException extends IllegalArgumentException { + private static final String PREFIX = "[ERROR] "; + + private CustomException(ErrorMessage errorMessage) { + super(PREFIX + errorMessage.getMessage()); + } + + public static CustomException from(ErrorMessage errorMessage) { + return new CustomException(errorMessage); + } +} diff --git a/src/main/java/bridge/global/exception/ErrorMessage.java b/src/main/java/bridge/global/exception/ErrorMessage.java new file mode 100644 index 00000000000..9bb9d7840b0 --- /dev/null +++ b/src/main/java/bridge/global/exception/ErrorMessage.java @@ -0,0 +1,19 @@ +package bridge.global.exception; + +public enum ErrorMessage { + BLANK_INPUT_ERROR("빈 λ¬Έμžμ—΄μ΄ μž…λ ₯λ˜μ—ˆμŠ΅λ‹ˆλ‹€."), + NOT_NUMBER_ERROR("μ˜¬λ°”λ₯Έ 숫자λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."), + BRIDGE_SIZE_RANGE_ERROR("닀리 κΈΈμ΄λŠ” 3λΆ€ν„° 20 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."), + INVALID_MOVING_MESSAGE("이동할 칸을 잘λͺ» μž…λ ₯ν•˜μ˜€μŠ΅λ‹ˆλ‹€."), + INVALID_GAME_COMMAND("μž¬μ‹œλ„ ν˜Ήμ€ μ’…λ£Œ μ—¬λΆ€λ₯Ό 잘λͺ» μž…λ ₯ν•˜μ˜€μŠ΅λ‹ˆλ‹€."); + + private final String message; + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return this.message; + } +} diff --git a/src/main/java/bridge/global/validator/Validator.java b/src/main/java/bridge/global/validator/Validator.java new file mode 100644 index 00000000000..d6a5fb12522 --- /dev/null +++ b/src/main/java/bridge/global/validator/Validator.java @@ -0,0 +1,32 @@ +package bridge.global.validator; + +import bridge.global.exception.CustomException; +import bridge.global.exception.ErrorMessage; + +public final class Validator { + public static int validateNumber(String message, ErrorMessage errorMessage) { + if (isNotNumber(message)) { + throw CustomException.from(errorMessage); + } + return Integer.parseInt(message); + } + + private static boolean isNotNumber(String str) { + return !str.matches("\\d+"); + } + + public static void validateRange( + int number, + int start, + int end, + ErrorMessage errorMessage + ) { + if (isInvalidRange(number, start, end)) { + throw CustomException.from(errorMessage); + } + } + + private static boolean isInvalidRange(int number, int start, int end) { + return number < start || number > end; + } +} diff --git a/src/main/java/bridge/BridgeNumberGenerator.java b/src/main/java/bridge/util/BridgeNumberGenerator.java similarity index 80% rename from src/main/java/bridge/BridgeNumberGenerator.java rename to src/main/java/bridge/util/BridgeNumberGenerator.java index 56187b71d2d..325bff3c4be 100644 --- a/src/main/java/bridge/BridgeNumberGenerator.java +++ b/src/main/java/bridge/util/BridgeNumberGenerator.java @@ -1,4 +1,4 @@ -package bridge; +package bridge.util; @FunctionalInterface public interface BridgeNumberGenerator { diff --git a/src/main/java/bridge/BridgeRandomNumberGenerator.java b/src/main/java/bridge/util/BridgeRandomNumberGenerator.java similarity index 94% rename from src/main/java/bridge/BridgeRandomNumberGenerator.java rename to src/main/java/bridge/util/BridgeRandomNumberGenerator.java index 4c9cb53e03a..917f6b4eb4f 100644 --- a/src/main/java/bridge/BridgeRandomNumberGenerator.java +++ b/src/main/java/bridge/util/BridgeRandomNumberGenerator.java @@ -1,4 +1,4 @@ -package bridge; +package bridge.util; import camp.nextstep.edu.missionutils.Randoms; diff --git a/src/main/java/bridge/util/RetryExecutor.java b/src/main/java/bridge/util/RetryExecutor.java new file mode 100644 index 00000000000..58a227d8a37 --- /dev/null +++ b/src/main/java/bridge/util/RetryExecutor.java @@ -0,0 +1,16 @@ +package bridge.util; + +import bridge.view.console.ConsoleWriter; +import java.util.function.Supplier; + +public final class RetryExecutor { + public static T retryUntilSuccess(Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + ConsoleWriter.printlnMessage(e.getMessage()); + } + } + } +} diff --git a/src/main/java/bridge/view/InputView.java b/src/main/java/bridge/view/InputView.java new file mode 100644 index 00000000000..5bf8a78309b --- /dev/null +++ b/src/main/java/bridge/view/InputView.java @@ -0,0 +1,70 @@ +package bridge.view; + +import bridge.global.exception.CustomException; +import bridge.global.exception.ErrorMessage; +import bridge.global.validator.Validator; +import bridge.view.console.ConsoleReader; +import bridge.view.console.ConsoleWriter; + +/** + * μ‚¬μš©μžλ‘œλΆ€ν„° μž…λ ₯을 λ°›λŠ” 역할을 ν•œλ‹€. + */ +public class InputView { + + /** + * λ‹€λ¦¬μ˜ 길이λ₯Ό μž…λ ₯λ°›λŠ”λ‹€. + */ + public int readBridgeSize() { + ConsoleWriter.println(); + ConsoleWriter.printlnMessage("λ‹€λ¦¬μ˜ 길이λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + return validateBridgeSize(ConsoleReader.enterMessage()); + } + + private int validateBridgeSize(String message) { + int size = Validator.validateNumber( + message, + ErrorMessage.NOT_NUMBER_ERROR + ); + Validator.validateRange(size, + 3, + 20, + ErrorMessage.BRIDGE_SIZE_RANGE_ERROR + ); + return size; + } + + /** + * μ‚¬μš©μžκ°€ 이동할 칸을 μž…λ ₯λ°›λŠ”λ‹€. + */ + public String readMoving() { + ConsoleWriter.printlnMessage("이동할 칸을 μ„ νƒν•΄μ£Όμ„Έμš”. (μœ„: U, μ•„λž˜: D)"); + return validateMoving(ConsoleReader.enterMessage()); + } + + private String validateMoving(String message) { + if (isNotMatch(message, "U") && isNotMatch(message, "D")) { + throw CustomException.from(ErrorMessage.INVALID_MOVING_MESSAGE); + } + return message; + } + + private boolean isNotMatch(String actual, String expected) { + return !actual.equals(expected); + } + + /** + * μ‚¬μš©μžκ°€ κ²Œμž„μ„ λ‹€μ‹œ μ‹œλ„ν• μ§€ μ’…λ£Œν• μ§€ μ—¬λΆ€λ₯Ό μž…λ ₯λ°›λŠ”λ‹€. + */ + public String readGameCommand() { + ConsoleWriter.println(); + ConsoleWriter.printlnMessage("κ²Œμž„μ„ λ‹€μ‹œ μ‹œλ„ν• μ§€ μ—¬λΆ€λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”. (μž¬μ‹œλ„: R, μ’…λ£Œ: Q)"); + return validateGameCommand(ConsoleReader.enterMessage()); + } + + private String validateGameCommand(String message) { + if (isNotMatch(message, "R") && isNotMatch(message, "Q")) { + throw CustomException.from(ErrorMessage.INVALID_GAME_COMMAND); + } + return message; + } +} diff --git a/src/main/java/bridge/view/OutputView.java b/src/main/java/bridge/view/OutputView.java new file mode 100644 index 00000000000..71b4d61b7ec --- /dev/null +++ b/src/main/java/bridge/view/OutputView.java @@ -0,0 +1,65 @@ +package bridge.view; + +import bridge.controller.dto.Move; +import bridge.controller.dto.MoveResult; +import bridge.view.console.ConsoleWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * μ‚¬μš©μžμ—κ²Œ κ²Œμž„ μ§„ν–‰ 상황과 κ²°κ³Όλ₯Ό 좜λ ₯ν•˜λŠ” 역할을 ν•œλ‹€. + */ +public class OutputView { + + private static final String NONE = " "; + private static final String SINGLE_MAP_FORMAT = " | %s"; + private static final String GAME_RESULT_MESSAGE = "κ²Œμž„ 성곡 μ—¬λΆ€: %s"; + private static final String TRY_COUNT_MESSAGE = "총 μ‹œλ„ν•œ 횟수: %d"; + + public void printStartMessage() { + ConsoleWriter.printlnMessage("닀리 κ±΄λ„ˆκΈ° κ²Œμž„μ„ μ‹œμž‘ν•©λ‹ˆλ‹€."); + } + + /** + * ν˜„μž¬κΉŒμ§€ μ΄λ™ν•œ λ‹€λ¦¬μ˜ μƒνƒœλ₯Ό μ •ν•΄μ§„ ν˜•μ‹μ— 맞좰 좜λ ₯ν•œλ‹€. + */ + public void printMap(List moves) { + List up = new ArrayList<>(); + List down = new ArrayList<>(); + for (Move move : moves) { + if (move.direction().equals("U")) { + up.add(move.success()); + down.add(NONE); + } + if (move.direction().equals("D")) { + up.add(NONE); + down.add(move.success()); + } + } + ConsoleWriter.printlnMessage(generateSingleMapRow(up)); + ConsoleWriter.printlnMessage(generateSingleMapRow(down)); + ConsoleWriter.println(); + } + + public String generateSingleMapRow(List map) { + int size = map.size(); + if (size == 1) { + return "[ " + map.get(0) + " ]"; + } + String head = "[ " + map.get(0); + for (int i = 1; i < size; i++) { + head += String.format(SINGLE_MAP_FORMAT, map.get(i)); + } + return head + " ]"; + } + + /** + * κ²Œμž„μ˜ μ΅œμ’… κ²°κ³Όλ₯Ό μ •ν•΄μ§„ ν˜•μ‹μ— 맞좰 좜λ ₯ν•œλ‹€. + */ + public void printResult(MoveResult result) { + ConsoleWriter.printlnMessage("μ΅œμ’… κ²Œμž„ κ²°κ³Ό"); + printMap(result.singleMoves()); + ConsoleWriter.printlnFormat(GAME_RESULT_MESSAGE, result.success().getName()); + ConsoleWriter.printlnFormat(TRY_COUNT_MESSAGE, result.count()); + } +} diff --git a/src/main/java/bridge/view/console/ConsoleReader.java b/src/main/java/bridge/view/console/ConsoleReader.java new file mode 100644 index 00000000000..78e0c8f72a6 --- /dev/null +++ b/src/main/java/bridge/view/console/ConsoleReader.java @@ -0,0 +1,24 @@ +package bridge.view.console; + +import bridge.global.exception.CustomException; +import bridge.global.exception.ErrorMessage; +import camp.nextstep.edu.missionutils.Console; + +public final class ConsoleReader { + public static String enterMessage() { + return Validator.validate(Console.readLine()); + } + + private static class Validator { + public static String validate(String message) { + validateBlankInput(message); + return message; + } + + private static void validateBlankInput(String message) { + if (message.isBlank()) { + throw CustomException.from(ErrorMessage.BLANK_INPUT_ERROR); + } + } + } +} diff --git a/src/main/java/bridge/view/console/ConsoleWriter.java b/src/main/java/bridge/view/console/ConsoleWriter.java new file mode 100644 index 00000000000..7ab7f8d6580 --- /dev/null +++ b/src/main/java/bridge/view/console/ConsoleWriter.java @@ -0,0 +1,15 @@ +package bridge.view.console; + +public final class ConsoleWriter { + public static void println() { + System.out.println(); + } + + public static void printlnMessage(String message) { + System.out.println(message); + } + + public static void printlnFormat(String message, Object... args) { + printlnMessage(String.format(message, args)); + } +} diff --git a/src/test/java/bridge/ApplicationTest.java b/src/test/java/bridge/ApplicationTest.java index 1a163ec0a2a..6ce58c196c8 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.domain.BridgeMaker; +import bridge.util.BridgeNumberGenerator; import camp.nextstep.edu.missionutils.test.NsTest; import java.util.List; import org.junit.jupiter.api.Test; @@ -26,11 +28,11 @@ class ApplicationTest extends NsTest { assertRandomNumberInRangeTest(() -> { run("3", "U", "D", "U"); assertThat(output()).contains( - "μ΅œμ’… κ²Œμž„ κ²°κ³Ό", - "[ O | | O ]", - "[ | O | ]", - "κ²Œμž„ 성곡 μ—¬λΆ€: 성곡", - "총 μ‹œλ„ν•œ 횟수: 1" + "μ΅œμ’… κ²Œμž„ κ²°κ³Ό", + "[ O | | O ]", + "[ | O | ]", + "κ²Œμž„ 성곡 μ—¬λΆ€: 성곡", + "총 μ‹œλ„ν•œ 횟수: 1" ); int upSideIndex = output().indexOf("[ O | | O ]"); diff --git a/src/test/java/bridge/domain/BridgeMakerTest.java b/src/test/java/bridge/domain/BridgeMakerTest.java new file mode 100644 index 00000000000..ab0ad2db8ff --- /dev/null +++ b/src/test/java/bridge/domain/BridgeMakerTest.java @@ -0,0 +1,46 @@ +package bridge.domain; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import bridge.util.BridgeNumberGenerator; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class BridgeMakerTest { + + @Mock + private BridgeNumberGenerator bridgeNumberGenerator; + + @InjectMocks + private BridgeMaker bridgeMaker; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void makeBridgeTestWithUp() { + when(bridgeNumberGenerator.generate()).thenReturn(1); + + List result = bridgeMaker.makeBridge(5); + + assertEquals(5, result.size()); + assertEquals("U", result.get(0)); + } + + @Test + void makeBridgeTestWithDown() { + when(bridgeNumberGenerator.generate()).thenReturn(0); + + List result = bridgeMaker.makeBridge(5); + + assertEquals(5, result.size()); + assertEquals("D", result.get(0)); + } +}