일단 전반적인 구조는 만들었지만, 내가 원하는 방향이 아닌것도 있고, 문제의 요구조건을 완벽하게 만족하지 못하고 있다.
테스트를 통해 변경이 필요해보이는 부분들에 대해서 체크했다.
수정 부분에 대한 계획
•
도전 횟수를 누적하여 “n번째만에 성공하셨네요.”와 같은 식으로 도전 횟수를 나타내 줘야 한다.
◦
도전 횟수는 전역 변수로 선언해놔야 특정 반복문에서 탈출 할 때 초기화되지 않고 유지 할 수 있다.
▪
int loseCnt로 선언하자.
•
성공, 실패 여부를 output()에서 결정하지 말고 calculate()에서 결정하도록 변경해야 한다.
◦
기존 output()으로 전달되던 String[] resultStr에 의해서 판별했지만 불필요한 선택같았다.
▪
boolean playerWon이라는 플래그 변수를 전역으로 추가해두고 calculate()에서 논리를 할당하자.
•
이후 output()에선 플래그가 true일 경우에만 성공으로 프로그램을 빠져나오도록 만들자
•
플래그가 false인 경우 실패로 계속해서 반복하도록 만들어야 한다. →계속 loseCnt가 누적될것
•
플레이어의 입력은 123, 591처럼 한번에 입력하는 것은 상관없는데, 출력부분이 거슬린다.
•
타자가 공을 1개 시도하고, Strike or Ball 결과가 한번 나오고 다음 2번째 공을 시도, 결과 출력. 이런 방식으로 나누어야 한다. 현재는 시도 시도 시도 , 결과 결과 결과 순서로 출력되다 보니 게임의 현실성이 떨어진다. 물론 입력부터 별도로 받아서 (1개 던지고, 타자가 치고, 결과 나오고) 이 방식으로 접근하고 싶긴하다.
◦
“투수(player)가 첫번째 공을 던집니다” → “타자(cpu)가 공을 바라봅니다” → “Strike!!” or “Ball”
◦
과 같은 순서가 되야 한다. 그럼 위 비지니스 로직이 움직이기 위하여 필요한 필요한 기능 추가하여 재기획 해보았다.
▪
CPU는 미리 랜덤 변수 3개 저장(안보여도 됨)
•
→player스캐너 인풋
•
→playerInput[n]에 입력값 할당
•
→“투수(player)가 첫번째 공을 던집니다” 문구 출력
•
→ “타자(cpu)가 공을 바라봅니다” 문구 출력
•
→ playerInput[n]과 cpuInput[n]의 비교
•
→결과에 따라 “Strike!!” or “Ball” 출력
•
→ playerInput[n] == cpuInput[n] 인 경우 result[n]에 “S” 할당
•
→첫 스캐너 인풋으로 이동하여 두번째 공[1]에 대한 처리, 또 종료 후 세번째 공[2]에 대한 처리 반복
◦
→result[] 배열 내 값이 S,S,S 인 경우 성공으로 boolean playerWon이라는 플래그 변수에 “True” 할당으로 tryAgain() 에서 재시도에 대한 물음
◦
→result[] 배열 내 값이 B가 1개라도 있는 경우 실패로 boolean playerWon이라는 플래그 변수에 “False” 할당으로 tryAgain() 에서 묻지 않고 “선수 교체”와 같은 말로 투수(player)의 재도전 (* 대신 CPU의 공 숫자는 변하면 안된다. 1개씩 맞추면서 추측 할 수 있도록 유도해야 한다. == 첫 난수 발생시 전역 변수에 저장되고 초기화 되면 안된다는 뜻이다. 성공 후 게임을 완전히 새로 할 경우에만 새로 생성하도록)
해당 부분은 생각보다 많은 로직을 수정해야 하는 것으로 보여졌다. 기본적으로 처음에 설계에서
input→ cpuInput과 비교 → 출력순서로 진행했으면 좋았을 텐데, 너무 많은 코드를 진행한 상황에서 분리하기가 번거로운 문제가 있다. 이 프로그램만 잡고 있으면 안되기 때문에 천천히 구현하기로 하고 다음 문제로 넘어갈땐 좀 설계를 명확하게 하고 들어가야겠다고 생각했다.
따라서 우선 도전 횟수 보여지는것 추가와 성공 실패 여부에 따른 랜덤값 유지 또는 초기화를 추가했고, 전반적으로 테스트에 문제가 없는지를 확인했다. 위 처럼 마지막 각 1회마다 인풋-계산-출력 부분은 추후에 구현하기로 하고 우선 당장 디버깅을 하기 위해 Gradle의 문제를 살펴보고 다음 예제로 넘어가보고자 한다.
우선 당장 해결해야하는 런타임 오류
현재 재시작 되는 부분에 있어서 Thread.sleep(); 을 사용하는데 아래 코드 처럼 여러번 호출하다보니 스레드간에 서로 간섭되고 있는 문제가 발생하고 있다. InterruptedException에 에 대한 예외 처리를 진행해야 한다. Thread.sleep()을 사용하는 부분에서 InterruptedException을 잡아 처리해야 한다.
void run() throws InterruptedException {
while (true) {
showTopMenu();
Thread.sleep(2000);
int[] playerNum = inputPlayer();
Thread.sleep(2000);
int[] cpuNum = inputCPU();
Thread.sleep(2000);
calculate(cpuNum, playerNum);
Thread.sleep(2000);
output();
if (!tryAgain()) {
break;
}
}
}
Java
복사
InterruptedException 에 try, catch문으로 예외를 잡자
void run() throws InterruptedException {
while (true) {
showTopMenu();
try {
Thread.sleep(2000);
int[] playerNum = inputPlayer();
Thread.sleep(2000);
int[] cpuNum = inputCPU();
Thread.sleep(2000);
calculate(cpuNum, playerNum);
Thread.sleep(2000);
output();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 인터럽트 상태를 복구
System.err.println("InterruptedException occurred: " + e.getMessage());
}
if (!tryAgain()) {
break;
}
}
}
Java
복사
이처럼 catch 문에서 interrupt()를 통해 인터럽트 상태를 복구하는 예외 처리문을 처리했다. 한번 다시 테스트해본다.
역시나 에러가 발생한다.
ChatGPT 멘토를 통해서 해당 문제에 대한 진단과 해결방법을 물어보았다.
이처럼 try catch문을 추가했는데 아직도 다시 플레이 할 때 문제가 발생해
Execution failed for task ':Main.main()'.
Process 'command '/usr/local/Cellar/openjdk@17/17.0.8/libexec/openjdk.jdk/Contents/Home/bin/java'' finished with non-zero exit value 1
•
Try:
Run with --stacktrace option to get the stack trace.
Run with --info or --debug option to get more log output.
Run with --scan to get full insights.
해당 오류 메시지는 자바 프로그램이 실행 중에 예외 상황이 발생하여 비정상적으로 종료되었음을 나타냅니다. 이 메시지만으로는 정확한 원인을 찾기 어려울 수 있습니다. 따라서 해당 오류 메시지 뒤에 나오는 스택 트레이스(stack trace)를 확인하여 어떤 부분에서 예외가 발생했는지 파악해야 합니다.
스택 트레이스는 어떤 메서드에서 시작되었으며, 어떤 메서드를 거쳐 예외가 발생했는지를 나타내주는 정보입니다. 아래와 같이 "Run with --stacktrace option to get the stack trace."라는 메시지를 보고 대상 프로그램을 스택 트레이스 옵션과 함께 실행해보세요.
터미널 또는 명령 프롬프트에서 다음과 같이 입력하여 실행해보십시오:
shCopy code
./gradlew run --stacktrace
Plain Text
복사
또는 Gradle이 아닌 직접 자바 파일을 실행한다면 다음과 같이 입력해보십시오:
shCopy code
java -cp <클래스 파일 경로> JavaPrac2week.Main --stacktrace
Plain Text
복사
위 명령을 통해 어떤 부분에서 예외가 발생했는지 더 자세한 정보를 얻을 수 있을 것입니다. 만약 스택 트레이스 정보를 확인하고 여전히 문제가 해결되지 않는다면, 스택 트레이스 정보를 공유해주시면 더 정확한 도움을 드릴 수 있을 것입니다.
여기서 계속해서 문제가 발생했다.
Gradle로 오류 로그를 상세히 보고싶었을 뿐인데
갑자기 JDK를 못찾겠다는것?? 나는 17버전을 사용하고 있었고, 프로젝트도 그렇게 설정했었다.
Gradle의 설정이 잘못되었나 살펴보았다. 역시나 17버전으로 모두 되어있다.
내 환경 변수를 살펴보았다. 역시나 17버전을 홈과 패스로 잡고 있다.
터미널에서 java —version 을 통해서도 17버전이 나왔다. 무엇이 문제인가.
터미널을 모두 종료하고 다시 java —version 을 입력했다. 어? 11버전으로 바뀌었다.
나는 수십번 뭔가 잘못되었나 계속 찾아보고 당연히 source ~/.zshrc로 변동 사항을 즉시 적용하곤했다. 그래도 터미널을 종료하고 다시 키면 11버전으로 돌아갔다. 이런 상황은 처음이다.
ChatGPT와 이것때문에 하루 종일 찾았다. 결국엔 어떤 환경 변수 설정이 계속해서 17을 11로 덮어서 적용시키고 있는 문제로 요약했다. 그 원인을 찾아야 한다. 계속해서 컴퓨터에 여러 JDK를 설치했엇고 지웠고를 반복하기도했었고, 어떤것은 인터넷에서 dmg를 받거나, 어떤것은 Homebrew로 받거나 해서 정리가 안되서 그럴수도 있었다.
StackOverflow에도 질문을 남겼다. 그러나 대부분 답변은 내가 이미 시도했던 것들을 물어봤고, 코딩에 대한 물음이 아니라 의미가 없었다. 내 컴퓨터의 문제기 때문에 그들은 정확히 알수가 없었다.
불현듯 갑자기 그럼 JDK를 모두 찾아내자는 마음에 보이는 JDK를 Homebrew 17버전을 제외하고 모두 제거했다. 그런데도 안된다. 이젠 MacOS와 시스템의 설정들 까지 다 뒤집어봤다. 거기서도 JDK 11버전과 관련된 내용을 찾을 수 없었다. 그럼 어디남아있을까.
불현듯 ~/.zshrc 의 마지막 부분이 생각났다. SDKMAN? 이게 뭐였지 하고 경로를 타고 들어갔다.
지금은 휴지통에 있지만 이것이 마지막 줄에서 계속해서 덮어쓰던 JDK였다. 뒤에 JDK라는 문구가 없어서 뭔지 정확하게 몰랐었던 것이다.. 이것을 찾느라 몇시간을 소비했다. 하지만 추후 프로젝트를 진행하고 하려는데 원하는 JDK가 계속 설치 되지 않고 11로 덮어진다 생각하면 끔찍하다 급해서 막 찾지도 못했을 것이다.
//------------------- ~/.zshrc via VI editor ----------------
# MYSQL DB PATHS
export PATH="/usr/local/Cellar/mysql/8.0.33_3/bin:$PATH"
# MYSQL DB HOME
export MYSQL_HOME=$/usr/local/Cellar/mysql/8.0.33_3/bin
# MONGO DB Paths
export PATH="/usr/local/opt/mongodb-community@5.0/bin:$PATH"
# MONGO DB HOME
export MONGO_HOME=$/usr/local/Cellar/mongodb-community@5.0/5.0.11/bin
# TOMCAT
export PATH="$PATH:/Users/inyongkim/Documents/apache-tomcat-9.0.68/bin"
export CATALINA_HOME=$/Users/inyongkim/Documents/apache-tomcat-9.0.68
export JAVA_HOME=/usr/local/Cellar/openjdk@17/17.0.8/libexec/openjdk.jdk/Contents/Home
export PATH=$JAVA_HOME/bin:$PATH
[[ -d ~/.rbenv ]] && \
export PATH=${HOME}/.rbenv/bin:${PATH} && \
eval "$(rbenv init -)"
#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!!
export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
source /usr/local/opt/chruby/share/chruby/chruby.sh
source /usr/local/opt/chruby/share/chruby/auto.sh
Java
복사
이렇게 마지막 부분에 JDK인지 모르게 암살자처럼 숨어있었다. 나는 저게 ruby설치할때 같이 딸려온 환경변수인줄 알았다. 다행히 이부분을 수정하고 이제 터미널을 재시작해도 내가 환경변수로 설정한 JDK가 정확하게 나타난다. 혹시나 이렇게 뭔가 변경되지 않고 덮어진다면 다른것을 설치하느라 추가된 환경 변수들을 신경써야 한다는 것을 배웠다. 알고 있었는데 모르는 것이 설치되었으면 파악했어야 한다. 내 실수로 시간을 버렸다.
이제 다시 이어서
현재 발생하는 오류에 대해서 원인을 파악하고자 Gradle의 오류 로그를 보고싶었던 것이다.
thread에 발생하고 있는 예외는 NoSuchElementException으로 이 부분을 개발자가 알았으니 못입력하도록 막거나, 그런 예외가 발생 했을 때 처리를 해줘야 한다.
inputPlayer() 메서드에서 입력을 받을 때, 입력이 없는 경우 적절한 안내 메시지를 출력하고 값이 없을 때 반복문으로 재 입력을 유도했다.
int[] inputPlayer() {
showPlayerTurn();
while (true) {
inputNumOrigin = sc.nextLine();
System.out.println("입력된 값: " + inputNumOrigin); // 입력 값을 확인하기 위해 출력
if (inputNumOrigin.isEmpty()) {
System.out.println("값을 입력해주세요.");
continue;
}
inputNumOriginParseInt = Integer.parseInt(inputNumOrigin);
if (inputNumOriginParseInt < 0 || inputNumOriginParseInt > 999) {
showPlayerMistake1();
} else {
inputNum1 = inputNumOriginParseInt / 100;
inputNum2 = (inputNumOriginParseInt / 10) % 10;
inputNum3 = inputNumOriginParseInt % 10;
System.out.println("inputNum1: " + inputNum1);
System.out.println("inputNum2: " + inputNum2);
System.out.println("inputNum3: " + inputNum3);
if (inputNum1 == inputNum2 || inputNum1 == inputNum3 || inputNum2 == inputNum3) {
showPlayerMistake2();
} else {
break;
}
}
}
int[] playerNum = new int[3];
playerNum[0] = inputNum1;
playerNum[1] = inputNum2;
playerNum[2] = inputNum3;
return playerNum;
}
Java
복사
이런식으로 입력이 없는 상황에 대해서 유효성체크를 진행하도록 했다.