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 |