Boot Camp/[우테코] 프리코스

[4주차] bridge 미션

계란💕 2022. 11. 20. 21:17

  Ex) bridge 요구 사항

<hide/>
# 미션 - 다리 건너기

## 🔍 진행 방식

- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다.
- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.
- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.

## 📮 미션 제출 방법

- 미션 구현을 완료한 후 GitHub을 통해 제출해야 한다.
    - GitHub을 활용한 제출 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고해
      제출한다.
- GitHub에 미션을 제출한 후 [우아한테크코스 지원](https://apply.techcourse.co.kr) 사이트에 접속하여 프리코스 과제를 제출한다.
    - 자세한 방법은 [제출 가이드](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse#제출-가이드) 참고
    - **Pull Request만 보내고 지원 플랫폼에서 과제를 제출하지 않으면 최종 제출하지 않은 것으로 처리되니 주의한다.**

## 🚨 과제 제출 전 체크 리스트 - 0점 방지

- 기능 구현을 모두 정상적으로 했더라도 **요구 사항에 명시된 출력값 형식을 지키지 않을 경우 0점으로 처리**한다.
- 기능 구현을 완료한 뒤 아래 가이드에 따라 테스트를 실행했을 때 모든 테스트가 성공하는지 확인한다.
- **테스트가 실패할 경우 0점으로 처리**되므로, 반드시 확인 후 제출한다.

### 테스트 실행 가이드

- 터미널에서 `java -version`을 실행하여 Java 버전이 11인지 확인한다. 또는 Eclipse 또는 IntelliJ IDEA와 같은 IDE에서 Java 11로 실행되는지 확인한다.
- 터미널에서 Mac 또는 Linux 사용자의 경우 `./gradlew clean test` 명령을 실행하고,   
  Windows 사용자의 경우  `gradlew.bat clean test` 명령을 실행할 때 모든 테스트가 아래와 같이 통과하는지 확인한다.

```
BUILD SUCCESSFUL in 0s
```

---

## 🚀 기능 요구 사항
위아래 둘 중 하나의 칸만 건널 수 있는 다리를 끝까지 건너가는 게임이다.
- 위아래 두 칸으로 이루어진 다리를 건너야 한다.
  - 다리는 왼쪽에서 오른쪽으로 건너야 한다.
  - 위아래 둘 중 하나의 칸만 건널 수 있다.
- 다리의 길이를 숫자로 입력받고 생성한다.
  - 다리를 생성할 때 위 칸과 아래 칸 중 건널 수 있는 칸은 0과 1 중 무작위 값을 이용해서 정한다.
  - 위 칸을 건널 수 있는 경우 U, 아래 칸을 건널 수 있는 경우 D값으로 나타낸다.
  - 무작위 값이 0인 경우 아래 칸, 1인 경우 위 칸이 건널 수 있는 칸이 된다.
- 다리가 생성되면 플레이어가 이동할 칸을 선택한다.
  - 이동할 때 위 칸은 대문자 U, 아래 칸은 대문자 D를 입력한다.
  - 이동한 칸을 건널 수 있다면 O로 표시한다. 건널 수 없다면 X로 표시한다.
- 다리를 끝까지 건너면 게임이 종료된다.
- 다리를 건너다 실패하면 게임을 재시작하거나 종료할 수 있다.
  - 재시작해도 처음에 만든 다리로 재사용한다.
  - 게임 결과의 총 시도한 횟수는 첫 시도를 포함해 게임을 종료할 때까지 시도한 횟수를 나타낸다.
- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
  - `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다.

### 입출력 요구 사항

#### 입력
- 자동으로 생성할 다리 길이를 입력 받는다. 3 이상 20 이하의 숫자를 입력할 수 있으며 올바른 값이 아니면 예외 처리한다.
```
3
```
- 라운드마다 플레이어가 이동할 칸을 입력 받는다. U(위 칸)와 D(아래 칸) 중 하나의 문자를 입력할 수 있으며 올바른 값이 아니면 예외 처리한다.
```
U
```
- 게임 재시작/종료 여부를 입력 받는다. R(재시작)과 Q(종료) 중 하나의 문자를 입력할 수 있으며 올바른 값이 아니면 예외 처리한다.
```
R
```

#### 출력
- 게임 시작 문구
```
다리 건너기 게임을 시작합니다.
```
- 게임 종료 문구
```
최종 게임 결과
[ O |   |   ]
[   | O | O ]

게임 성공 여부: 성공
총 시도한 횟수: 2
```
- 사용자가 이동할 때마다 다리 건너기 결과의 출력 형식은 실행 결과 예시를 참고한다.
  - 이동할 수 있는 칸을 선택한 경우 O 표시
  - 이동할 수 없는 칸을 선택한 경우 X 표시
  - 선택하지 않은 칸은 공백 한 칸으로 표시
  - 다리의 시작은 `[`, 다리의 끝은 `]`으로 표시
  - 다리 칸의 구분은 ` | `(앞뒤 공백 포함) 문자열로 구분
  - 현재까지 건넌 다리를 모두 출력
- 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 "[ERROR]"로 시작해야 한다.
```
[ERROR] 다리 길이는 3부터 20 사이의 숫자여야 합니다.
```

#### 실행 결과 예시
```
다리 건너기 게임을 시작합니다.

다리의 길이를 입력해주세요.
3

이동할 칸을 선택해주세요. (위: U, 아래: D)
U
[ O ]
[   ]

이동할 칸을 선택해주세요. (위: U, 아래: D)
U
[ O | X ]
[   |   ]

게임을 다시 시도할지 여부를 입력해주세요. (재시도: R, 종료: Q)
R
이동할 칸을 선택해주세요. (위: U, 아래: D)
U
[ O ]
[   ]

이동할 칸을 선택해주세요. (위: U, 아래: D)
D
[ O |   ]
[   | O ]

이동할 칸을 선택해주세요. (위: U, 아래: D)
D
[ O |   |   ]
[   | O | O ]

최종 게임 결과
[ O |   |   ]
[   | O | O ]

게임 성공 여부: 성공
총 시도한 횟수: 2
```

```
다리 건너기 게임을 시작합니다.

다리의 길이를 입력해주세요.
3

이동할 칸을 선택해주세요. (위: U, 아래: D)
U
[ O ]
[   ]

이동할 칸을 선택해주세요. (위: U, 아래: D)
U
[ O | X ]
[   |   ]

게임을 다시 시도할지 여부를 입력해주세요. (재시도: R, 종료: Q)
Q
최종 게임 결과
[ O | X ]
[   |   ]

게임 성공 여부: 실패
총 시도한 횟수: 1
```

---

## 🎯 프로그래밍 요구 사항

- JDK 11 버전에서 실행 가능해야 한다. **JDK 11에서 정상적으로 동작하지 않을 경우 0점 처리한다.**
- 프로그램 실행의 시작점은 `Application`의 `main()`이다.
- `build.gradle` 파일을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다.
- [Java 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java) 가이드를 준수하며 프로그래밍한다.
- 프로그램 종료 시 `System.exit()`를 호출하지 않는다.
- 프로그램 구현이 완료되면 `ApplicationTest`의 모든 테스트가 성공해야 한다. **테스트가 실패할 경우 0점 처리한다.**
- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다.
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
  - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
  - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 3항 연산자를 쓰지 않는다.
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
- else 예약어를 쓰지 않는다.
  - 힌트: if 조건절에서 값을 return 하는 방식으로 구현하면 else를 사용하지 않아도 된다.
  - else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
- 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
  - 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다.

### 추가된 요구 사항

- 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
  - 함수(또는 메서드)가 한 가지 일만 잘하도록 구현한다.
- 메서드의 파라미터 개수는 최대 3개까지만 허용한다.
- 아래 있는 `InputView`, `OutputView`, `BridgeGame`, `BridgeMaker`, `BridgeRandomNumberGenerator` 클래스의 요구사항을 참고하여 구현한다.
  - 각 클래스의 제약 사항은 아래 클래스별 세부 설명을 참고한다.
  - 이외 필요한 클래스(또는 객체)와 메서드는 자유롭게 구현할 수 있다.
  - `InputView` 클래스에서만 `camp.nextstep.edu.missionutils.Console` 의 `readLine()` 메서드를 이용해 사용자의 입력을 받을 수 있다.
  - `BridgeGame` 클래스에서 `InputView`, `OutputView` 를 사용하지 않는다.

### InputView 클래스
- 제공된 `InputView` 클래스를 활용해 구현해야 한다.
- `InputView`의 패키지는 변경할 수 있다.
- `InputView`의 메서드의 시그니처(인자, 이름)와 반환 타입은 변경할 수 있다.
- 사용자 값 입력을 위해 필요한 메서드를 추가할 수 있다.
```java
public class InputView {

    public int readBridgeSize() {
        return 0;
    }

    public String readMoving() {
        return null;
    }

    public String readGameCommand() {
        return null;
    }
}
```
### OutputView 클래스
- 제공된 `OutputView` 클래스를 활용해 구현해야 한다.
- `OutputView`의 패키지는 변경할 수 있다.
- `OutputView`의 메서드의 이름은 변경할 수 없고, 인자와 반환 타입은 필요에 따라 추가하거나 변경할 수 있다.
- 값 출력을 위해 필요한 메서드를 추가할 수 있다.
```java
public class OutputView {

    public void printMap() {
    }

    public void printResult() {
    }
}
```

### BridgeGame 클래스
- 제공된 `BridgeGame` 클래스를 활용해 구현해야 한다.
- `BridgeGame`에 필드(인스턴스 변수)를 추가할 수 있다.
- `BridgeGame`의 패키지는 변경할 수 있다.
- `BridgeGame`의 메서드의 이름은 변경할 수 없고, 인자와 반환 타입은 필요에 따라 추가하거나 변경할 수 있다.
- 게임 진행을 위해 필요한 메서드를 추가 하거나 변경할 수 있다.

```java
public class BridgeGame {

    public void move() {
    }

    public void retry() {
    }
}
```

### BridgeMaker 클래스
- 제공된 `BridgeMaker` 클래스를 활용해 구현해야 한다.
- `BridgeMaker`의 필드(인스턴스 변수)를 변경할 수 없다.
- `BridgeMaker`의 메서드의 시그니처(인자, 이름)와 반환 타입은 변경할 수 없다.
```java
public class BridgeMaker {

    public List<String> makeBridge(int size) {
        return null;
    }
}
```

### BridgeRandomNumberGenerator 클래스

- Random 값 추출은 제공된 `bridge.BridgeRandomNumberGenerator`의 `generate()`를 활용한다.
- `BridgeRandomNumberGenerator`, `BridgeNumberGenerator` 클래스의 코드는 변경할 수 없다.

#### 사용 예시

- 다리 칸을 생성하기 위한 Random 값은 아래와 같이 추출한다.

```java
int number = bridgeNumberGenerator.generate();
``` 

### 라이브러리

- [`camp.nextstep.edu.missionutils`](https://github.com/woowacourse-projects/mission-utils)에서 제공하는 `Console` API를 사용하여 구현해야 한다.
  - 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다.

---

## ✏️ 과제 진행 요구 사항

- 미션은 [java-bridge](https://github.com/woowacourse-precourse/java-bridge) 저장소를 Fork & Clone해 시작한다.
- **기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리**해 추가한다.
- **Git의 커밋 단위는 앞 단계에서 `docs/README.md`에 정리한 기능 목록 단위**로 추가한다.
    - [커밋 메시지 컨벤션](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 가이드를 참고해 커밋 메시지를 작성한다.
- 과제 진행 및 제출 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고한다.
  • 메서드 라인 10 이하
  • 모든 메서드를 파라미터 3개 이하로 구성한다..
  • 들여 쓰기 3이하: while문 안에 if 절 까지 허용
  • BridgeGame 클래스에서 input, output을 사용할 수 없다.
  • 입력은 저장된 라이브러리를 이용한다
  • Console로 입력 받는 부분은 Input 클래스에서만 사용 가능하다.

 

 

  • README
<hide/>
## 🚀기능 요구 사항 🚀

- 다리 길이를 입력 받는다.
- 길이에 맞는 다리를 생성한다. generate()
  - List<String> bridge
- U 또는 D를 입력 받아서 다리를 이동한다.
  - 한 칸씩 이동(move)할 때마다 현재의 row, col, str을 파악하면서   
  - 가능한지 아닌 지 파악한다.
  - move()할 때 마다 boolean 형태로 해서 현재의 진행 상황을 printMap()으로 출력한다.
  - 중간에 실패하면? retry() 호출해서 재시도/종료 여부를 선택한다. 
- 마지막에 통과하면 최종 게임 결과, 시도한 횟수를 출력한다.

 

 

  • InputView 
    • 이 클래스에서만 console을 통해 입력 받을 수 있다. 
<hide/>
package bridge;

import static bridge.Application.generatedBridgeStr;

import camp.nextstep.edu.missionutils.Console;
import java.util.NoSuchElementException;

/**
 * 사용자로부터 입력을 받는 역할을 한다.
 */
public class InputView {

    private static final String errorMsg = "[ERROR]";
    private OutputView outputView = new OutputView();
    private BridgeGame bridgeGame = new BridgeGame();
    private BridgeNumberGenerator bridgeNumberGenerator = new BridgeRandomNumberGenerator();
    private String changeToNum = "";
    private boolean successOrFail = false;
    private String generatedStr = "";
    private char udToOneOrZero = ' ';
    private String position = "";
    private int result;


    /**
     * 다리의 길이를 입력받는다.
     */
    public int readBridgeSize() {
        System.out.println("다리의 길이를 입력해주세요.");
        String bridgeSize = Console.readLine();
        try {
            result = Integer.parseInt(bridgeSize);
        } catch (NumberFormatException e) {
            System.out.println(errorMsg + " 숫자만 입력 가능 \n 다리의 길이를 입력해주세요.");
            String input = Console.readLine();
            throw new NoSuchElementException(errorMsg + " 숫자만 입력 가능");
        }
        return result;
    }


    public void initialSetting(String generatedBridgeStr) {

        generatedStr = generatedBridgeStr;
        successOrFail = true;
        changeToNum = "";
    }

    public void for_initialSetting() {
        System.out.println("이동할 칸을 선택해주세요. (위: U, 아래: D)");
        position = Console.readLine();
        udToOneOrZero = ' ';
        if (position.charAt(0) == 'U') {
            udToOneOrZero = '1';
        } else if (position.charAt(0) == 'D') {
            udToOneOrZero = '0';
        }
    }

    public void notMatchAndDown() {

        changeToNum += "y";
        outputView.printMap(generatedBridgeStr, changeToNum);
        successOrFail = false;

    }

    public void notMatchAndUp() {
        changeToNum += "x";
        outputView.printMap(generatedBridgeStr, changeToNum);
        successOrFail = false;

    }

    public boolean successHandler(String position) {
        if (position.equals("U")) {
            changeToNum += "1";
            successOrFail = outputView.printMap(generatedBridgeStr, changeToNum);
            return true;
        } else if (position.equals("D")) {
            changeToNum += "0";
            successOrFail = outputView.printMap(generatedBridgeStr, changeToNum);
            return true;
        }
        return false;
    }


    public boolean failureHandler() {
        char posChar = position.charAt(0);
        if (posChar == 'U') {
            notMatchAndUp();
            return true;
        } else if (posChar == 'D') {
            notMatchAndDown();
            return true;
        }
        return false;
    }

    public void recursiveFor(int bridgeSize) {
        for (int i = 0; i < bridgeSize && successOrFail;
            ++i) {  // 리스트 List<String> 같은 형태로 필요 // U 또는 D를 읽을 때마다 한번씩 출력해준다.
            for_initialSetting();
            boolean matchChar = (udToOneOrZero == generatedBridgeStr.charAt(i));
            boolean failureHandler = false;
            if (!matchChar) {
                failureHandler = failureHandler();
            }
            if (failureHandler) {
                continue;
            }
            boolean successHandler = successHandler(position);
            if (successHandler) {
                continue;
            }
            throw new IllegalArgumentException(errorMsg + " 위: U, 아래: D 만 입력 가능");
        }
    }

    /**
     * 사용자가 이동할 칸을 입력받는다. U, D
     * <p>
     * 여기서 boolean 형태로 성공 실패 여부가 필요할까?
     */
    public String readMoving(String generatedBridgeStr,
        int bridgeSize) {       // 반환 값에 x가 포함되어 있으면 실패인 경우
        initialSetting(generatedBridgeStr);
        recursiveFor(bridgeSize);
        return changeToNum;
    }


    /**
     * 사용자가 게임을 다시 시도할지 종료할지 여부를 입력받는다. 게임 실패한 경우에만 호출한다.
     */
    public boolean readGameCommand() {  // 실패한 경우에, 재시작 여부
        System.out.println("게임을 다시 시도할지 여부를 입력해주세요. (재시도: R, 종료: Q)");
        String restartOrExit = Console.readLine();
        if (restartOrExit.length() == 1 && restartOrExit.charAt(0) == 'R') {
            bridgeGame.retry();      // BridgeGame 클래스의 retry()로 넘어간다.
            return true;
        } else if (restartOrExit.length() == 1 && restartOrExit.charAt(0) == 'Q') {
            return false;
        }
        throw new IllegalArgumentException(errorMsg + " R 또는 Q만 입력 가능");
    }
}

 

 

  • BridgeNumberGenerator
    • 숫자를 생성할 수 있는 메서드를 가진 인터페이스
<hide/>
@FunctionalInterface
public interface BridgeNumberGenerator {

    int generate();
}

 

 

  • BridgeRandomNumberGenerator
    • 0, 1을 랜덤으로 만들 수 있는 클래스
    • BridgeNumberGenerator를 implement 한다.
<hide/>
public class BridgeRandomNumberGenerator implements BridgeNumberGenerator {

    private static final int RANDOM_LOWER_INCLUSIVE = 0;
    private static final int RANDOM_UPPER_INCLUSIVE = 1;

    @Override
    public int generate() {
        return Randoms.pickNumberInRange(RANDOM_LOWER_INCLUSIVE, RANDOM_UPPER_INCLUSIVE);
    }
}

 

 

  • BridgeMaker
    • SuccessHandler, FailureHandler로 나눠서 구현한다.
    • 프로젝트 할 때, 로그인 성공, 실패 처리할 때 핸들러를 이용했던 게 생각나서 구분했다.
    • 메서드 라인 조건 때문에 모둔 걸 잘게 쪼갰는데 왜 쪼개라는 건지 보니까 이해가 확 간다.
    • 메서드명으로 하여금 기능이 확실히 파악되도록 짜면 더 좋을 것 같다.
    • makeBridge() 라는 메서드는 List<String>을 반환해야하는데 {"U" , "D", "D"} 라는 문자열이 포함된 리스트를 반환하도록 메서드의 틀을 만들어 놓은 걸로 보인다. 그런데, 내가 기능 구현을 모두 마친 다음에 이거 깨달았다. 그
    • makeBridge() 
      • 다시 생각해보니, 우테코가 생각한 로직은 아래와 같지 않을까 싶다.
        • 1) Maker 클래스에서 InputView을 이용해서 사용자의 입력을 받는다. U, D, D 같은 값을 저장하고 반환
        • 2) List {"U", "D", "D"}를 가지고 generatedBridge {"U", "D", "U"}와 비교한다.
        • 3) BridgeGame 클래스에서 Output.printMap() 을 이용해서 현재 상황을 출력한다. 
          • 첫 번째 출력: [ O ]            [   ]
          • 번째 출력: [ O |    ]       [   | O ]
          •  번째 출력: [ O |    |    ]  [   | O | X ]
    • 결국, 테스트 코드는 통과할 수 있도록 조건에 맞게 makeBridge() 를 구성하고 실질적으로 다리 만드는 메서드 makeCustomBridge()를 새로 만들었다.
    • 클래스와 메서드 역할을 잘못 이해해서 makeCustomBridge() 라는 메서드를 만들었다. 입력 받은 숫자에 따라서 랜덤으로 다리를 생성하는 메서드이다.
<hide/>
/**
 * 다리의 길이를 입력 받아서 다리를 생성해주는 역할을 한다.
 */
public class BridgeMaker {

    private static String up = "";
    private static String down = "";
    private static final String errorMsg = "[ERROR]";
    private static String bridgeStr = "";
    private char tmpChar = ' ';
    private boolean isFinalIdx = false;
    private int[][] tmpArr;
    private static List<String> resultBridge = new ArrayList<>();
    private final BridgeNumberGenerator bridgeNumberGenerator;
    private  List<String> res = new ArrayList<>();


    public BridgeMaker(BridgeNumberGenerator bridgeNumberGenerator) {
        this.bridgeNumberGenerator = bridgeNumberGenerator;
    }


    public void initialSetting() {
        resultBridge = new ArrayList<>();
        up = "[";
        down = "[";
    }

    public void finalSetting() {
        up += "]";
        down += "]";
        resultBridge.add(up);
        resultBridge.add(down);
    }

    // String 00111010101  => List<String> 형태로 바꾼다.
    public List<String> changeStrToBridge(String inputBridgeStr) {
        initialSetting();
        recursiveFor(inputBridgeStr, inputBridgeStr.length());
        finalSetting();
        return resultBridge;
    }

    public void recursiveFor(String inputBridgeStr, int length){

        for (int i = 0; i < inputBridgeStr.length(); i++) {
            tmpChar = inputBridgeStr.charAt(i);
            isFinalIdx = (i == inputBridgeStr.length() - 1);
            if (tmpChar == 'x' || tmpChar == 'y') {failureHandler(tmpChar, isFinalIdx);
                break;
            }
            successHandler(tmpChar, isFinalIdx);
        }

    }

    public void upSuccess(boolean isFinalIdx) {

        if (isFinalIdx) { // 업
            up += " O ";
            down += "   ";
            return;
        }
        up += " O |";
        down += "   |";
    }

    public void downSuccess(boolean isFinalIdx) {
        if (isFinalIdx) { // 다운
            up += "   ";
            down += " O ";
            return;
        }
        up += "   |";
        down += " O |";
    }

    public void upFail(boolean isFinalIdx) {
        if (isFinalIdx) {
            up += " X ";
            down += "   ";
            return;
        }
        up += " X |";
        down += "   |";
    }

    public void downFail(boolean isFinalIdx) {
        if (isFinalIdx) {
            up += "   ";
            down += " X ";
            return;
        }
        up += "   |";
        down += " X |";
    }

    public void successHandler(char tmpChar, boolean isFinalIdx) {
        if (tmpChar == '1') {
            upSuccess(isFinalIdx);
        } else if (tmpChar == '0') {
            downSuccess(isFinalIdx);
        }
    }

    public void failureHandler(char tmpChar, boolean isFinalIdx) {
        if (tmpChar == 'x') {
            upFail(isFinalIdx);
        } else if (tmpChar == 'y') {
            downFail(isFinalIdx);
        }
    }

    public void makeArr() {

        int[][] tmpArr = new int[2][bridgeStr.length()];
        for (int i = 0; i < bridgeStr.length(); i++) {
            if (bridgeStr.charAt(i) == '0') {       //  0인 경우 아래 칸 건널 수 있다.
                tmpArr[1][i] = 1;
            } else if (bridgeStr.charAt(i) == '1') {    // 0인 경우 위 칸이 건널 수 있다.
                tmpArr[0][i] = 1;
            }
        }
    }

    public void printArr() {
        System.out.println("BridgeMaker.makeBridge() arr 타입");
        for (int[] a : tmpArr) {
            System.out.println(Arrays.toString(a));
        }
    }

    /**
     * @param size 다리의 길이
     * @return 입력받은 길이에 해당하는 다리 모양. 위 칸이면 "U", 아래 칸이면 "D"로 표현해야 한다.
     * <p>
     */
    public List<String> customMakeBridge(int size) {
        if (size < 3 || size > 20) {
            throw new IllegalArgumentException(errorMsg + " 3 이상 20 이하의 숫자만 입력 가능합니다.");
        }
        bridgeStr = "";     // while문에 들어가기 때문에  초기화 꼭 필요
        for (int i = 0; i < size; i++) {
            bridgeStr += bridgeNumberGenerator.generate();
        }
        makeArr();//      printArr();
        return changeStrToBridge(bridgeStr);
    }

    // 10110 같은 형태를 가지고 사용자가 하나씩 입력하는 값과 비교해준다.
    public String bridgeStr() {

        return bridgeStr;
    }



    public List<String> makeBridge(int size) {
        String inputBridgeStr = forRecursive(size);
        bridgeStr = "";
        res = new ArrayList<>();
        for (char c : inputBridgeStr.toCharArray()) {
            if (c == '1') {
                res.add("U"); continue;
            }
            res.add("D");
        }   return res;
    }

    public String forRecursive(int size) {
        String res = "";

        for (int i = 0; i < size; ++i) {
            res += bridgeNumberGenerator.generate();
        }
        return res;
    }

}

 

 

  • BridgeGame
    • 이 클래스 내부 각 메서드의 역할을 이해 못해서 사실상 이 클래스 없어도 잘 돌아간다..ㅜㅜ
    • 게임의 역할을 할 수 있도록 BridgeMaker내에 메서드의 반환값을 가져와서 비교하는 내용을 move()에 넣는다.
      • 성공: printResult()를 호출한 다음 게임을 끝낸다. 
      • 실패: retry()를 호출하도록 게임 재시작/ 종료를 선택한다.
      • 그러면 application에서 move()와 retry()를 어떻게 가져올지도 생각해볼 문제인 것 같다.
<hide/>
/**
 * 다리 건너기 게임을 관리하는 클래스
 */
public class BridgeGame {

    /**
     * 사용자가 칸을 이동할 때 사용하는 메서드
     * <p>
     * 이동을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
     */

    public boolean move(String generatedBridgeStr,
        String currBridgeStr) {
        int idx = currBridgeStr.length() - 1;
        if (idx < 0) {
            System.out.println("currBridgeStr : null");
            return false;
        }
        if (generatedBridgeStr.charAt(idx) == currBridgeStr.charAt(idx)) {
            return true;
        }
        return false;
    }

    /**
     * 사용자가 게임을 다시 시도할 때 사용하는 메서드
     * <p>
     * 재시작을 위해 필요한 메서드의 반환 타입(return type), 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
     */
    public void retry() {
    }
}

 

 

  • OutputView
    • 매개변수에 generatedStr은 사실상 필요없다.
    • 급하게 제출하느라 지우지는 못했다 ㅠ.ㅠ
<hide/>
/**
 * 사용자에게 게임 진행 상황과 결과를 출력하는 역할을 한다.
 */
public class OutputView {

    private BridgeNumberGenerator bridgeNumberGenerator = new BridgeRandomNumberGenerator();

    private BridgeMaker bridgeMaker = new BridgeMaker(bridgeNumberGenerator);


    /**
     * 현재까지 이동한 다리의 상태를 정해진 형식에 맞춰 출력한다.
     * <p>
     * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
     */
    public boolean printMap(String generatedBridgeStr, String currStr) {        // 현재 상황을 프린트한다.
        List<String> bridges = bridgeMaker.changeStrToBridge(currStr); // [ O | O |  ].. 같은 형태
        printBridge(bridges);
        boolean successOrFail = true;
        for (String bridge : bridges) {
            if (bridge.contains("X")) {
                successOrFail = false;
                break;
            }
        }
        return successOrFail;
    }

    /**
     * 게임의 최종 결과를 정해진 형식에 맞춰 출력한다.
     * <p>
     * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
     */
    public void printResult(List<String> bridge, boolean successYn, int gameCnt) {      
        System.out.println("최종 게임 결과");
        printBridge(bridge);
        System.out.print("게임 성공 여부: ");
        if(successYn){
            System.out.println("성공\n총 시도한 횟수: " + gameCnt );
            return;
        }
        System.out.println("실패\n총 시도한 횟수: "  + gameCnt );
    }

    public static void printBridge(List<String> bridge){
        for (String b : bridge) {
            System.out.println(b);
        }
    }
}

 

 

  • Application
<hide/>

public class Application {

    static boolean retryYn = true;
    static int totalCnt = 0;
    static InputView inputView = new InputView();
    static BridgeRandomNumberGenerator bridgeRandomNumberGenerator = new BridgeRandomNumberGenerator();
    static BridgeMaker bridgeMaker = new BridgeMaker(bridgeRandomNumberGenerator);
    static String generatedBridgeStr = "";
    static int bridgeSize = 0;
    static List<String> resultBridge = new ArrayList<>();
    static String inputBridgeStr = "";
    static boolean successYn;
    static List<String> generatedBridge = new ArrayList<>();

    public static void startBridgeGame() {
        System.out.println("다리 건너기 게임을 시작합니다.");
    }

    public static void initialSetting() {
        startBridgeGame();
        inputView = new InputView();
        // 랜덤으로 다리를 생성한다.
        bridgeSize = inputView.readBridgeSize();        // 다리 길이를 입력 받아서 저장한다.
        generatedBridge = bridgeMaker.customMakeBridge(bridgeSize);
        generatedBridgeStr = bridgeMaker.bridgeStr();        // 1011010111
        resultBridge = new ArrayList<>();
        inputBridgeStr = "";
        successYn = true;
    }

    public static void printRandomBridge() {
        System.out.println("generatedBridgeStr " + generatedBridgeStr);
        System.out.println("랜덤으로 생성한 브릿지");
        for (String b : generatedBridge) {
            System.out.println(b);
        }
    }

    public static void finalSetting() {
        // 최종 결과 출력
        OutputView outputView = new OutputView();
        resultBridge = bridgeMaker.changeStrToBridge(inputBridgeStr);
        outputView.printResult(resultBridge, successYn, totalCnt);
    }

    public static void main(String[] args) {
        initialSetting();
//        printRandomBridge();
        while (retryYn) {
            boolean successYn = recursiveBridgeGame();
            if (successYn) {
                break;
            }
        }
        finalSetting();
    }

    public static boolean recursiveBridgeGame() {
        successYn = true;
        ++totalCnt;
        inputBridgeStr = inputView.readMoving(generatedBridgeStr, bridgeSize);
        if (inputBridgeStr.contains("x") || inputBridgeStr.contains("y")) {
            successYn = false;
        }
        if (successYn) {
            return true;
        }
        retryYn = inputView.readGameCommand();
        return false;
    }
}

 

 

 

  Ex) Caused by: java.io.IOException: Pipe broken

  • 오류: IOException: Pipe broken
  • 원인: 클라이언트가 요청 받은 응답 데이터를 정해진 시간 안에 맞춰 처리하지 못하는 경우 발생한다.
    • Receiver에서 송신 받은 데이터를 정해진 시간에 처리하지 못하는 상황에서 sender가 요청을 계속 보내는 경우 
    • 데이터를 제 때 처리하지 못하는 경우
    • 네트워크가 느리거나 서버의 CPU 이슈로 속도가 느린 경우에 발생한다.
  • 해결
    • 어떤 메서드 안에서 예외 처리를 하는 부분이 있는데 그 부분을 메서드의 뒷부분으로 이동했더니 해결됐다.
    • 우테코 미션할 때마다 비슷한 오류가 많이 터졌다.
    • 해결 방법은?
      • 1) Request 후에 Response 를 기다린다. (클라이언트가 연속적으로 데이터를 보내는 게 아니라 하나의 레코드를 보낸 다음 서버에서 응답을 받은 다음 보내도록 한다.)
      • 2) Exception을 무시한다. (클라이언트가 비정상적으로 종료할 때, Broken pipe Signal이 발생하고 클라이언트의 종료를 서버에서 제어가 불가능하므로 시그널을 무시한다.)
      • 3) 클라이언트에서 연속으로 버튼 클릭하는 것을 차단하거나 예외 처리 부분에서 오류가 나지 않도록 한다.
      • 4) Timeout 값을 늘린다.
      • 5) 가용 스레드를 늘린다.

 

 

우테코 다음 일정

  • 12/14: 1차 발표 (2배수 선발)
  • 12/17: 최종 코딩테스트 13 - 18시 (5시간)
    • 장소: 선릉, 잠실 캠퍼스에서 오프라인으로 응시

 

 

4주차 회고

  • 메서드 라인 10 줄 이내로 맞추느라 오래걸렸다. 처음부터 쪼개서 만드는 연습을 해야겠다. 추가 요구 사항에 있어서 만족시키기 위해 반복하여 쪼개고 대부분의 변수는 전역 변수로 처리했다. 메서드를  나누니까 각각 어떤 역할인지 파악할 수 있어 깔끔했다.
  • 앞으로는 메서드명만 봐도 기능이 확실히 파악되도록 네이밍하면 더 좋을 것 같다.
  • else문 금지, 들여 쓰기 3 이상 금지 등 여러 조건이 있었는데 일단 코드를 모두 짜고 통과하는 것을 확인한 다음에 요구사항을 보면서 리팩토링했다. 
  • 우테코에서 기본적으로 만들어준 클래스와 메서드에 대해 이해가 잘 안되서 사실 손 가는대로 만들었다..ㅜ.ㅜ 그래서 Maker의 두 가지 메서드는 사실 거의 쓰지 않았다. 그러다 보니까 Game 클래스에 내용이 많았고 그 부분을 쪼개는데 리팩토링 시간을 다 쓴 것 같다.
    • 요구 사항과 클래스 조건에 맞춰서 새롭게 브릿지 미션을 시작해봐야겠다는 생각이 든다.
    • 다시 짠다면 Maker클래스에 있지만 Game의 역할에 가까운 코드는 분리해야겠다.
  • 이번 마지막 미션은 스스로 완성하고 싶어서 겨우겨우 완성했다. 처음에는 문제가 이해도 안 가고 글이 너무 길어서 짜증이 좀 났다 ㅎㅎ 그래서 문제 파악하는데만 며칠이 걸렸다. 마지막 날에는 완성해서 정상 동작하는 걸 확인하니까 뿌듯하고 성취감도 느꼈다. 
  • 클래스 요구사항을 만족시키지 못해서 사실 큰 기대를 하고 있지는 않다..ㅋ 그래도 공부할 수 있는 방법은 많으니까 결과에 연연하지 말아야겠다 ヽ(✿゚▽゚)ノ

'Boot Camp > [우테코] 프리코스' 카테고리의 다른 글

[3주차] lotto 미션  (1) 2022.11.16
[2주차] baseball game 미션  (0) 2022.11.09
[1주차] on-boarding 미션  (0) 2022.11.01