안녕하십니까? 이번 포스팅에서는 지뢰찾기 구현을 위해 제가 만든 함수들을 살펴보겠습니다. 원래는 한꺼번에 설명하려고 했습니다만 워낙 소스코드가 길다보니 따로 빼서 설명하는 것이 좀더 나을 것 같아 먼저 설명드리겠습니다.

지뢰찾기에 쓰인 함수들은 다음과 같습니다.

void Createlandmine(void); - 지뢰배치함수

void Createlandmine2(void);  - 지뢰를 이용하여 기타 타일을 배치하는 함수

void CreateButton(void);  - 버튼을 생성하는 함수

void sp_check(void);  - 주변에 지뢰가 없는 타일(공백타일)을 체크하는 함수

void victoryCheck(void); - 승리조건에 도달했는가를 체크하는 함수

Createlandmine와 Createlandmine2함수는 앞 포스팅에서 설명드렸으니 넘어가겠습니다. 자 이제 차례대로 함수를 살펴 봅시다.

- void CreateButton(void)

함수명 그대로 버튼을 만드는데 쓰이는 함수입니다. 

- int i,j,k=0;

  int m = 0;

  int x=30,y=30;

for문에 쓰일 i,j 변수와 OpenClose_Tile, flagTile에 쓰일 인덱스변수 k를 선언하였습니다. OpenClose_Tile은 눌러진 타일인지, 안눌러진 타일인지의 여부를 저장하는 정수타입 전역 변수 배열 입니다. flagTile은 깃발이 있는지 없는지의 여부를 저장하는 정수타입 전역 변수 배열입니다.

m은 buttonHwnd의 인덱스 변수 겸 메뉴 이름입니다. buttonHwnd은 버튼들의 핸들을 저장시키는 핸들 타입 전역 변수 배열입니다.

x와 y는 버튼을 생성시킬 때의 좌표를 뜻합니다.

- for(i = 0; i <9 ;i++)

for(j = 0; j <9 ;j++)

초급지뢰찾기는 9*9 형태이므로 이 형태로 버튼을 만들어 출력하기 위해 2중 for문을 넣었습니다.

- buttonHwnd[m] = CreateWindow(L"button",NULL,WS_CHILD | WS_VISIBLE | BS_BITMAP,x,y,18,18,Global_hWnd, (HMENU) m,Global_hInstance,NULL);

CreateWindow함수로 버튼을 생성하여 buttonHwnd에 저장시킵니다. 이 때 버튼의 좌표는 아까 선언한 x와 y입니다. 크기는 18*18이이고 이름은 m입니다. 인자들을 보면 Global_hWnd와 Global_hInstance가 들어있는 것을 볼 수 있는데 Global_hWnd는 WndProc함수의 hWnd인자를 사용하기 위해 선언한 전역변수이고 Global_hInstance는 WinMain의 hInstance인자를 사용하기 위한 전역변수입니다.

그다음 눈에 띄는 인자는 바로 BS_BITMAP 입니다. MSDN의 설명을 봅시다.

버튼에 비트맵을 표시한다는 옵션이지요. 버튼에 원하는 색상을 주기 위해 이 옵션을 넣었습니다.

- SendMessage(buttonHwnd[m], BM_SETIMAGE,IMAGE_BITMAP,(LPARAM)LoadBitmap(Global_hInstance, MAKEINTRESOURCE(Tile)));

새로운 함수가 나왔습니다. 이 함수를 살펴봅시다.

말그대로 메세지를 보내는 함수입니다. 인자는 WndProc의 인자와 똑같습니다.

정리하면 이 SendMessage함수는 hWnd값의 윈도우 창에 메세지를 보내는 것입니다. 그러면 받은 hWnd값의 윈도우 창은 WndProc에서 받은 메세지를 처리하게 됩니다.

여기서 hWnd는 각 버튼의 핸들입니다. Msg로는 BM_SETIMAGE가 들어갔는데 이 메세지는 버튼의 이미지를 설정하는 메세지라고 보시면 됩니다. 세번째와 네번째 인자는 메세지의 추가정보 이지요. 먼저 wParam에는 IMAGE_BITMAP이 들어갔습니다. 앞서 메세지에서 BM_SETIMAGE인자를 넣었는데 그냥 이미지하면 커서인지, 아이콘인지, 비트맵인지 구별을 못하므로 비트맵 이미지를 버튼에 설정하기 위해 세번째 인자에 IMAGE_BITMAP가 들어갔습니다. 

마지막 인자에선 LoadBitmap함수가 쓰였습니다. 말 그대로 이함수는 비트맵을 불러오는 함수인데 LoadIcon과 맥락이 비슷하다고 보시면 됩니다.  인자로는 MAKEINTRESOURCE(Tile)이 쓰였습니다. 즉 비트맵리소스를 가져오신다고 보시면 됩니다. 이 리소스는 나중에 지뢰찾기 코드를 설명할 때 자세히 설명드리겠습니다.

마지막으로 LoadBitmap함수는 반환값이 HBITMAP이므로 (LPARAM)으로 강제형변환을 해주었습니다.

정리하면 이 sendMessage함수는 버튼의 BS_BITMAP 옵션을 사용할 때 호출되는 BM_SETIMAGE에 추가정보를 주기 위해 사용되었다고 보시면 됩니다. 이렇게 보낸 BM_SETIMAGE메세지에 의해 버튼은 비트맵 이미지를 받아 출력하게 됩니다.

다음으로 넘어갑시다.

- OpenClose_Tile[k] = 1;

  flagTile[k] = 1;

초기의 OpenClose_Tile, flagTile의 인덱스들의 값을 1로 설정합니다. 즉 OpenClose_Tile의 1은 버튼이 눌려지지 않은 상태임을 나타내고 flagTile의 1은 깃발이 꽃혀있지 않다는 것을 의미합니다.

- k = k+1;

  m += 1;

다음 인덱스를 지정하기 위해 변수들을 1씩 증가시킵니다.

- x += 18;

다음 버튼을 만들기 위해 x의 좌표를 버튼의 크기만큼 증가시킵니다. 이렇게하면 버튼이 오른쪽으로 하나씩 생성됩니다.

- x = 30;

  y += 18;

한줄의 버튼을 다 만들었다면 다음 줄의 버튼을 만들기 위해 x를 처음 버튼위치인 30으로 초기화 시키고, y를 버튼의 크기만큼 증가시킵니다.

최종적으로 이 CreateButton함수가 처리되고 나면 각각의 버튼 이름이 (0~80)인 버튼 81개가 생성됩니다.

다음 함수로 넘어갑시다.

- void sp_check(void)

이 함수는 모든 타일을 검사하여 타일이 9값이고 눌러진 상태일경우, 즉 주변에 지뢰가 없는 타일의 버튼이 벗겨진 경우 그 타일의 주변의 버튼을 없애는 기능을 합니다.

기존 지뢰찾기에서는 누르면 소리가 나면서 버튼이 크게 벗겨지지요? 그것을 구현한 겁니다.

- int i;

81개의 타일을 검사하기 위한 인덱스 변수를 선언합니다.

- for(i=0;i<81;i++)

81개의 타일을 검사하기 위해 선언하였습니다.

- if(buttonValue[i] == 9 && OpenClose_Tile[i] == 0)

buttonValue의 값이 9이고 OpenClose_Tile의 값이 0일 경우 if문을 실행합니다. 여기서 buttonValue는 Createlandmine2함수로 만들어진 arr[9][9]에 들어있는 값을 한 배열에 저장하기 위해 선언한 정수형 전역 변수 배열 입니다.

OpenClose_Tile의 값은 1과 0으로 나뉘어 지는데 1은 버튼이 안눌러진 상태이고 0은 버튼이 눌러진 상태를 의미합니다. 

즉 정리하면 현재 타일이 버튼이 벗겨진 상태의 9값 타일 인 경우를 의미합니다.

이 다음부터는 전 포스팅에서 얘기했던 Createlandmine2의 알고리즘을 이용하여 주변 타일을 삭제하고 OpenClose_Tile의 값을 0으로 지정해주는 코드입니다. 그러므로 일부분만 설명드리겠습니다.

- if(i <= 8)

switch(i)

case 0:

if(DestroyWindow(buttonHwnd[i+1]))

OpenClose_Tile[i+1] = 0;

if(DestroyWindow(buttonHwnd[i+9]))

OpenClose_Tile[i+9] = 0;

if(DestroyWindow(buttonHwnd[i+10]))

OpenClose_Tile[i+10] = 0;

break;

case 8:

.......

break;

default:

break;

i가 8보다 작거나 같을 경우입니다. 아까 CreateButton함수로 인해 버튼들의 핸들은 0~80까지의 인덱스들에 각각 저장되어있습니다. 즉 i가 8보다 작거나 같다는 것은 첫번째 줄을 의미합니다.

switch(i)는 i값에 따라 처리를 다르게 하기 위해 선언하였습니다. 아까의 Createlandmine2알고리즘으로 따지자면 j값을 의미합니다.

이 i값이 0이라면 i=0,j=0을 뜻하고 이 i값이 8이면 i=0,j=8을 뜻하고 그 외의 값(1~7)이라면 i=0, j= (1~7)까지를 나타내지요.

자 이제 처리부분을 한 번 봅시다.

DestroyWindow함수를 사용하여 핸들을 삭제합니다. 이 함수는 말그대로 인자에 해당하는 핸들을 가진 윈도우 창을 부수는 함수입니다. 즉 버튼을 삭제한다는 뜻입니다. 버튼이 삭제된다는 것은 벗겨진다는 것을 의미하지요. 그리고 버튼을 삭제한 이후 해당하는 OpenClose_Tile의 값을 0으로 줍니다. 벗겨졌으니까 OpenClose_Tile의 값을 바꿔주어야 겠지요.

여기서 궁금점이 하나 생길 수 있는데 그것은 바로 if문입니다. 이러한 if문을 준 이유는 버튼을 지우다보면 이미 버튼이 지워진 부분도 다시 버튼을 지우고 OpenClose_Tile의 값을 0으로 주기 때문에 이를 방지하기 위해 if문을 넣었습니다. if문이 실패하면 OpenClose_Tile에 값을 지정하지 않도록 하기 위해서입니다. 사실 if문이 있든 말든 동작에 지장은 없기 때문에 이 부분은 딱히 신경쓰지 않으셔도 됩니다. 편하실 대로 하십시요.

나머지 case와 default는 비슷하기 때문에 생략하였습니다.

- else if(i >= 72)

switch(i)

case 72:

......

break;

case 80:

......

break;

default:

......

break;

i가 72보다 크거나 같을 경우입니다. 즉 마지막 줄을 의미합니다. i값이 바꼈으므로 case의 값도 그에 맞게 바꿔주어야 합니다. 처리코드는 비슷하기에 생략하였습니다.

- else 

switch(i%9)

case 0:

......

break;

case 8:

......

break;

default:

......

break;

그 외의 줄을 처리하는 부분입니다. 즉 9<= i <= 71 인 부분이지요.

여기서 하나 유심히 보셔야 할부분은 switch(i%9)입니다. i값에 9를 나눈 나머지 값을 이용하여 case가 작동합니다. 이렇게 한 이유는 i값의 범위가 크다보니 일일이 case문을 작성해야하는데 너무 비효율적이라 생각하였기 때문입니다. i를 9로 나눈 나머지 값이 0이라면 첫번째 열을 의미하고, 나머지가 8이라면 마지막 열을 의미하고 그 외의 나머지 값은 중간열을 의미하므로 훨씬 간편하게 case문을 작성할 수 있습니다.

이제 마지막 함수를 살펴봅시다.

- void victoryCheck(void)

승리조건에 도달하였는지를 체크하는 함수입니다. 원리는 열려진 타일의 개수를 검사하여 열려진 타일이 10개(지뢰개수)일 경우 승리 메세지박스가 출력됩니다. 

- int i;

  int tilecount = 0;

  검사를 하기 위한 인덱스 변수 i와 타일의 개수를 저장시킬 변수 tilecount를 선언하였습니다.

- for(i=0;i<81;i++)

if(OpenClose_Tile[i] == 1)

tilecount = tilecount+1;

타일을 검사하여 열린 타일의 개수를 체크합니다.

- if(tilecount == 10)

MessageBox(Global_hWnd,L"지뢰를 다찾았습니다.  리!",

L"승리!",MB_ICONQUESTION | MB_OK);

for(i=0;i<81;i++)

if(buttonValue[i] == 0)

DestroyWindow(buttonHwnd[i]);

타일의 개수가 10개일 경우 승리 메세지박스를 띄우고 for문을 사용하여 남아있는 버튼을 전부 삭제합니다.

여기까지가 제가 작성한 함수들입니다.  다음포스팅에서는 리소스를 보여드리도록 하겠습니다.

Posted by englishmath
,