[JAVA] GUI 네모네모 로직 게임
오늘은 이틀정도에 걸쳐 만들어본 네모네모 로직 게임을 가져왔다.
네모로직이라는 게임은 주어진 숫자에 맞추어 칸을 색칠하는 게임이다.
각 열과 행에 적힌 숫자의 순서대로 연속된 색칠이 있어야 한다. (ex. 2 3 => ㅁㅁ (두칸 칠하고 띄우고) ㅁㅁㅁ(세칸칠))
이 띄우는 간격은 알 수 없으며 열과 행이 모두 일치할 수 있도록 잘 계산하여 넣어야한다.
이 이상의 자세한건 별도 검색을 해보도록하고, 대략적인 핵심 코드들을 간략하게 설명해보겠다.
폴더는 아래와 같이 간단히 정리해두었다.

res/maps의 txt 파일을 불러와 정답을 셋팅하는 용도로 사용한다.
이미지로 해보려 했으나.. 캡쳐 방식에 따라 정답이 아닌 배열로 설정되는 경향이 있기에
나의 기술로는 아직 불안정한 시스템이라는 판단 하, 손수 0과 1로 바꾸어 저장했다..
일단 모든 과정은 test_40x35.txt 파일을 통해 시연된다. (다른거도 해보니까 되긴함)
1. 가져올 파일명 기입

현재 단계에서는 maps에 저장된 파일 이름을 .txt를 제외하고 직접 입력하도록 해두었다.
maps 데이터를 배열화하여 하는 방식도 생각했지만
어쨌든 직접 값을 변경해야하는 단계라 생략했다.
나중에는 배열화하고 파일이 추가되면 배열을 늘리거나 남은 칸에 넣어주고
GUI 내에서 선택하는 방향까지 해보려고한다.
아무튼 어떤 파일을 실행할지 입력했다면
2. 파일 경로 지정

해당 파일명을 MapDatas 클래스에 불러들여 파일 경로를 완성한다.
3. 배열 크기 설정
설정된 파일의 이름에 적힌 40x35라는 col x row 정보를 얻어온다.
_00x00. 을 기점으로 숫자만 들고 올 것이기 때문에 charAt을 이용하여
파일명의 숫자 위치 값을 저장한다.
4. 배열 크기 기반 텍스트 배열화

먼저, 지정 경로의 파일을 InputStream으로 불러와 BufferedReader에 넣어둔다.
그 후, answerText에 String으로 누적 저장하고, 행 구분을 위해 "\r\n"을 붙여준다 (추후 정답 비교에도 사용)
그렇게 완성된 answerText를 map[][]배열에 한 글자씩 저장해준다.
5. 문제 배열 생성
이제 정답을 맞출 수 있도록 문제를 구성해야한다.
왼쪽과 위쪽에 각각 출력해주기위해 각 행과 열의 1 값(색칠)을 카운팅해준다.
- 왼쪽 (추후 화면 출력 시 유리하기 위해 바로 오른쪽으로 정렬)

- 위쪽 (추후 화면 출력 시 유리하기 위해 바로 아래쪽으로 정렬)

생각해보면 애초에 끝에서부터 비교하고 저장했으면 굳이 정렬안해도 될거같은데..?
나중에 생각해보도록하자.. 지쳤어요..
6. 전체 화면 크기 지정
문제와 그림판이 모두 화면에 알맞게 뜨게 하기 위해 전체 사이즈를 정립한다
대충 이런느낌으로 되도록
(작게보기)(열 숫 자)
(행 숫 자)(그 림 판)

위쪽의 경우 행, 왼쪽의 경우 열이 너무 적으면 미리보기도 너무 작아지기에
최소 10의 사이즈를 가지도록 설정했다. (tileSize는 16픽셀로 지정했다 -> 그대로하면 1픽셀이라 현미경 써야함)
7. 게임 화면 출력 설정
- 먼저 가로세로 행열 구분 선을 그어준다. (5칸마다 굵게 보이도록 1픽셀 작게 1번 더그어줌)

- 그림판 클릭 시 변화를 출력할 수 있도록 rect를 그려준다.
- 그려주는 김에 좌측 상단에 작게볼 수 있도록 미니맵?을 같이 그려준다.

여기까지하면 이정도 화면이 나온다.

8. 마우스 이벤트
이제 마우스 동작에 대한 설정을 해준다.

BUTTON1이 좌클릭, BUTTON3이 우클릭이다.
클릭하면 mousePressed에서 클릭한 지점의 cx,cy좌표를 e.getY(), e.getX()로 각각 받아와 저장해준다.
나중에 드래그 할때를 위해 pcs,pcy에도 동일하게 저장해준다. (이유는 뒤에 설명)
클릭을 떼면 mouseReleased에서 클릭중이지 않음을 표시하기 위해 boolean을 false로 바꿔준다.
여기있는 is0 이런거도 드래그때 필요해서 두었다. (뒤에설명)

위는 누르고있을때 계속 변경되는 좌표를 저장해 주고
아래는 내 마우스의 현재 위치를 계속 저장한다. (클릭안해도)
9. 마우스 이벤트 기반 데이터 및 출력 변경
- 마우스 움직임 감지
그냥 클릭하려면 맵이 커질때 여기가 숫자가 뭔지 헷갈리고 불편하기때문에 마우스 위치의 열과 행을 투명도 조절한 rect로 표시해 줄것이다. 앞서 설정한 mouseMoved좌표를 이용해 현재 커서 위치를 사용하는데, 나는 픽셀 단위가 아닌 tileSize 기반의 단위를 사용하기 때문에 위치값을 tileSize로 잘 나누어 사용했다. 지정구간에서만 표시가되며 지정 구간은 그림판 구역이다.

해당 조건을 설정하면 아래와같이 포인터 지점 표시가 가능하다. (편-안)

- 마우스 클릭 시 변경

뭐가 좀 긴데 요약하면 이정도다.
그림판 구간에서
마우스 좌클릭 -> 해당좌표 값 0이면 1로변경
-> 해당좌표 값 1이면 0으로 변경
마우스 우클릭 -> 해당좌표 값 0이면 2로변경
-> 해당좌표 값 2면 0으로 변경
여기서 클릭했을때 true로 바꾼 isClick을 false로 바꿔주지 않으면 0과 1의 변화를 무한으로 즐길 수 있다. (유저 친화도 0에 육박 ㄷㄷ) 그리고 여기서 is0, is1, is2가 나오는데 처음 클릭한 곳의 변경 전 값이 뭔지 체크하는 용도이다.
이 체크는 아래 드래그 설정에서 사용한다.
드래그는 사용자가 한번에 그리고 싶어할 때 사용한다. 여기서 클릭 시 저장된 pcy,pcx를 사용하는데, 드래그 한 곳의 좌표가 처음 입력한 값과 tileSize 픽셀만큼 차이가 나면 아직 누르고 있고 옆칸도 칠할거다! 라고 판단하고 또 칠을해준다.
여기서 is0,is1,is2의 중요성이 나오는데 이게 없다면 ! 만약, 0에서 1로바꾸는 클릭을하고 드래그를하면 뒤에는 1로 변경을 할 것이다. 그런데 중간에 이미 1인 녀석이 존재한다면? 그놈은 0으로 바뀌어 버릴것이다. 그래서 추가 조건이 필요했던 것이다.
(이거 되는데만 몇시간 소비..)
아무튼 이런 과정을 거치면 이제 아래와같은 출력이 가능하다.

10. 결과 도출
그럼 클리어는 어떻게 판단하는가? 일단 앞서 answerData를 입력하고 배열화 할때 dotCnt라는 것을 answer[][] == 1일때마다 ++해주었다. 그것으로 정답에 필요한 dot 수를(1의 갯수) 저장했고, 클릭 마다 내가 입력한 map[][]의 값이 1이면 drawCnt라는 녀석을 ++해주었다. 그 결과 둘의 값이 같아지면 MapDatas 클래스에서 gameEnd()를 실행하게 해놓았다.

그리고 isEnd()를 실행하고

내가 입력한 값의 String과 정답의 String을 비교하여 같은지 체크한다. 다르면 게임이 종료되지 않을 것이고, 같으면 UI클래스의 gameFinished를 true로 반환하도록 설계했다. 그 이후는 종료 화면 출력을하고 GamePanel의 gameThread를 null로 변경해주어 떠있는 창 이외 이벤트들이 일어날 수 없도록 했다.
아래는 위 코드의 시연 영상이다.
🧷 더 자세한 코드 : https://github.com/Dev-RiQ/SquareLogic.git