안녕하십니까 이번 포스팅에선 드디어 소스를 한번 파헤쳐 보겠습니다.

먼저 WinMain의 소스를 봅시다.

한 줄씩 분석해봅시다.

- #include <time.h>

time 함수를 사용하기 위해 선언한 헤더파일입니다. time함수는 랜덤으로 지뢰를 배치할 때 사용됩니다.

이 소스코드에서 선언된 함수들은 전부 앞 포스팅에 설명드렸으므로 넘어가겠습니다.

- HWND buttonHwnd[81];

버튼의 핸들을 저장시킬 배열을 선언합니다. 이렇게 선언된 핸들형 배열은 나중에 버튼이 생성될 때 각각의 버튼 핸들들이 저장됩니다. 핸들을 저장시키는 이유는 나중에 버튼을 삭제할 때 이 버튼의 핸들이 필요하기 때문입니다.

- int arr[9][9];

지뢰가 담긴 타일들을 생성할 때 쓰입니다. Createlandmine과 Createlandmine2함수에 사용됩니다.

- int buttonValue[81];

지뢰가 담긴 타일들이 생성되었을 때 이 타일들의 값들을 1차원 배열로 저장시키기 위해 선언하였습니다.

- int OpenClose_Tile[81];

타일이 눌러진 타일인지 아니면 아직 눌러지지 않은 타일인지의 여부를 저장시키기 위해 선언하였습니다.

- int flagTile[81];

타일이 깃발이 꽃혀져 있는 타일인지 아닌지의 여부를 저장시키기 위해 선언하였습니다. 깃발이 꽃혀져 있는 타일은 눌러지지 않아야 겠지요?

- HINSTANCE Global_hInstance;

WinMain의 인자인 hInstance를 전역변수로 사용하기 위해 선언되었습니다.

- HWND Global_hWnd;

WndProc의 인자인 hWnd를 전역변수로 사용하기 위해 선언하였습니다.

이제 WinMain의 코드를 살펴봅시다.

- Global_hInstance = hInstance;

hInstance의 값을 전역변수로 사용하기 위해 Global_hInstance에 저장시킵니다.

- srand((int)time(NULL));

난수 값을 생성하기 위해 시드를 설정해주는 코드입니다. 이것은 앞 포스팅에 설명드렸으니 넘어갑시다.

- Createlandmine();
  Createlandmine2();

지뢰를 랜덤으로 배치시키고 그 지뢰를 이용하여 주변 타일들을 배치시켜 9*9의 타일을 만듭니다. 즉 이부분이 이제 우리가 게임할 때 쓰일 타일입니다.

- wndclass.hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(IDI_ICON1));

  wndclass.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);

우리가 아까 만든 아이콘 리소스와 메뉴 리소스를 사용하기 위한 코드입니다.

- hwnd = CreateWindow(L"a",L"지뢰 찾기",WS_OVERLAPPEDWINDOW | WS_VISIBLE,73,73,240,280,NULL, NULL,hInstance,NULL);'

지뢰 찾기 윈도우 창을 생성시킵니다. 생성되는 위치는 (73,73), 크기는 가로 240, 세로 280입니다.

이 외에는 별 달라진 것이 없으므로 이제 WndProc함수로 넘어갑시다.

양이 너무 많아 나눠서 설명드리겠습니다.

- HDC hdc,hdcsrc;

HDC 형의 변수 2개를 선언합니다. 이 변수들은 윈도우 창에 비트맵을 출력시킬 때 쓰입니다.

- PAINTSTRUCT ps;

BeginPaint함수로 DC를 얻어오기 위한 구조체 변수입니다.

- HBITMAP Bitmap,landmine,Number1,Number2,Number3,Number4,Number5;
  HBITMAP Number6,Number7,Number8,space;

각각의 비트맵 리소스를 저장시키기 위해 HBITMAP형의 변수들을 선언하였습니다. 단Bitmap변수는 DeleteObject함수를 사용하기 위해 선언하였습니다.

- int i,j,m=0;

for문에 사용할 i,j변수와 특정 배열의 인덱스로 쓰일 변수 m을 선언하였습니다.

- int x=30,y=30;

비트맵을 출력시킬 때의 x좌표와 y좌표를 저장시키기 위해 사용하였습니다.

- Global_hWnd = hWnd;

WndProc의 인자인 hWnd를 전역변수로 사용하기 위해 Global_hWnd에 저장시킵니다.

다음은 switch문 코드를 봅시다.

- case WM_CREATE:
   CreateButton();
   break;

WM_CREATE 메세지를 받았을 경우 버튼을 생성합니다. 대체적으로 WM_CREATE메세지는 한 번만 발생하므로 버튼도 한 번만 생성됩니다.

- case WM_CONTEXTMENU

WM_CONTEXTMENU메세지를 받았을 경우의 처리입니다. MSDN에서 이 WM_CONTEXTMENU메세지의 설명을 봅시다.

풀이하면 윈도우 창에서 마우스 오른쪽 버튼을 눌렀을 경우 발생하는 메세지라고 되어있습니다. 그런데 말입니다. 이와 같은 동작을 할 경우 발생하는 메세지가 또 하나 있지요. 그것은 바로 WM_RBUTTONUP 혹은 WM_RBUTTONDOWN입니다.

보통 윈도우창에서 오른쪽마우스 처리를 한다면 대부분 WM_RBUTTONUP메세지 혹은 WM_RBUTTONDOWN메세지에서 처리를 합니다. 그런데 왜 저는 WM_CONTEXTMENU메세지로 처리를 하였을까요?

WM_RBUTTONUP이나 WM_RBUTTONDOWN 메세지는 다른 윈도우 창에서는 발생하지 않습니다. 이것이 무슨 말이냐 하면 이 WndProc함수가 처리되는 하나의 큰 윈도우 창에서만 발생을 하고 그 외의 윈도우 창, 예를 들면 자식 윈도우 창 같은 곳에서는 발생을 하지 않습니다.

우리는 버튼을 만들 때 CreateWindow함수로 만들었습니다. 즉 클래스가 Button인 윈도우 창이지요. 이렇게 생성된 button 윈도우에서 오른쪽 마우스를 누를 경우 WndProc에서는 WM_RBUTTONUP 혹은 WM_RBUTTONDOWN 메세지가 발생하지 않습니다. 실제로 브레이크를 걸로 디버깅을 해보시면 오른쪽마우스를 눌러도 메세지가 발생하지 않습니다. 그래서 이를 막기 위해 다른 윈도우창에서도 우측 마우스를 눌렀을 때 발생하는 메세지인 WM_CONTEXTMENU 메세지를 사용하였습니다.

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

우측 마우스를 눌렀을 경우 어떤 버튼에서 우측마우스를 눌렀는지 검사하기 위해 for문을 사용하였습니다. 즉 모든 버튼을 체크한다는 뜻이지요.

-  if(buttonHwnd[i] == (HWND)wParam && flagTile[i] == 1)
     SendMessage((HWND)wParam, BM_SETIMAGE,IMAGE_BITMAP,(LPARAM)LoadBitmap(Global_hInstance, MAKEINTRESOURCE(Flag)));
     flagTile[i] = 0;

버튼핸들과 우측 마우스를 누른 버튼의 핸들이 같고 그 버튼이 깃발이 꽃혀져 있지 않은 버튼일 경우의 처리입니다. flagTile에서 값이 1인 것은 깃발이 꽃혀져 있지 않다는 것을 의미합니다. WM_CONTEXTMENU메세지에서 wParam인자는 누른 윈도우 창의 핸들을 의미하므로 wParam인자를 HWND형으로 바꾸어 버튼 핸들과 비교합니다.

이 if문이 참이라는 것은 깃발이 꽃혀져 있지 않은 버튼에 우측마우스를 눌렀다는 뜻이므로 SendMessage함수를 사용하여 해당하는 버튼의 이미지를 flag가 세워진 이미지 리소스(Flag)로 변경하도록 하였습니다. SendMessage함수는 앞 포스팅에서 함수를 설명할 때 설명드렸으므로 넘어가겠습니다.

깃발 세운 이미지로 버튼을 바꾸었으면 해당 flagTile의 값을 0으로 설정합니다.
    
- else if(buttonHwnd[i] == (HWND)wParam && flagTile[i] == 0)
     SendMessage((HWND)wParam, BM_SETIMAGE,IMAGE_BITMAP,(LPARAM)LoadBitmap(Global_hInstance, MAKEINTRESOURCE(QM)));
     flagTile[i] = 2;

이번 if문은 해당하는 버튼의 flagTile값이 0일 경우입니다. flagTile값이 0이라는 것은 해당 버튼이 깃발이 꽃혀져 있는 상태임을 의미합니다. 오리지널 지뢰찾기에서는 깃발이 표시된 버튼에 우측마우스를 누르면 ?가 적힌 이미지로 바뀝니다. 그것을 재현하기 위해 버튼의 이미지를 물음표가 적힌 이미지 리소스(QM)로 바꿔줍니다. 그리고 해당 flagTile의 값을 2로 설정합니다.

- else if(buttonHwnd[i] == (HWND)wParam && flagTile[i] == 2)
     SendMessage((HWND)wParam, BM_SETIMAGE,IMAGE_BITMAP,(LPARAM)LoadBitmap(Global_hInstance, MAKEINTRESOURCE(Tile)));
     flagTile[i] = 1;

마지막 if문은 해당하는 버튼의 flagTile값이 2일 경우입니다. 값이 2라는 것은 ?가 적힌 버튼이라는 것을 의미하므로 원상태로 복구시키기 위해 Tile 리소스를 사용하여 원래 상태의 이미지로 바꿉니다. 그리고 해당하는 flagTile의 값을 원래 상태의 값인 1로 설정합니다.

이제 WM_COMMAND 메세지 부분을 살펴봅시다. 일단 코드를 보여드리겠습니다.

하나씩 살펴봅시다.

WM_COMMAND메세지는 메뉴를 누를 때 발생하는 메세지입니다. 우리가 만든 버튼 윈도우도 하나의 메뉴로 인식하므로 버튼을 눌렀을 때 이 메세지가 발생하게 됩니다.

- switch(wParam)
    default:

어떤 메뉴를 눌렀냐에 따라 처리를 다르게 하기 위해 사용하였습니다. 일단 우리가 만든 메뉴 리소스(새게임, 통계 ..... )에 있는 메뉴처리는 제외하였습니다. 그럼 남는 메뉴는 무엇일까요? 바로 우리가 만든 버튼 메뉴이겠지요. 그런데 우리가 만든 버튼 개수만 해도 81개가 됩니다. 그걸 일일이 case문으로 해주기엔 좀 버거우므로 default로 하여 case로 지정한 것 외의 나머지 버튼을 처리하도록 하였습니다.

- if(flagTile[wParam] == 1)
      DestroyWindow(buttonHwnd[wParam]);
      OpenClose_Tile[wParam] = 0;

버튼을 누르면 해당하는 버튼의 이름이 wParam에 들어있다는 것은 다 아실 겁니다 .이를 이용하여 누른 버튼의 flagTile값이 1일 경우를 if문으로 작성해주었습니다. flagTile의 값이 1이라는 것은 정상적인 타일을 의미합니다. 즉 정상적인 타일을 눌렀을 경우에는 DestroyWindow함수를 사용하여 해당하는 버튼의 핸들을 지웁니다. 핸들이 지워지면 그 핸들에 해당하는 윈도우 창은 사라지게 됩니다. 즉 버튼을 벗겨내는 것을 재현한 것이지요. 그리고 해당하는 버튼의 OpenClose_Tile값을 0으로 주어 이 버튼은 벗겨진 버튼이라는 것을 정의하였습니다.

- switch(buttonValue[wParam])
       case 0:
        for(i=0;i<81;i++)
          if(buttonValue[i] == 0)
             DestroyWindow(buttonHwnd[i]);
        MessageBox(hWnd,L"지뢰를 밟으셨습니다.",
          L"콰콰쾅",MB_ICONERROR | MB_OK);
        PostQuitMessage(0);
        break;
       case 9:
        for(i=0;i<9;i++)
           sp_check();
        break;

버튼을 지우고 나면 이제 밑에 깔린 타일의 값에 따라 처리륻 다르게 해야겠지요? 이를 위해 switch문을 사용하였습니다. switch의 인자로는 buttonValue배열이 사용되었습니다. buttonValue는 해당하는 버튼의 타일값이 저장되어있으므로 그 타일값에 따라 처리를 해주시면 됩니다.

먼저 타일값이 0일 경우입니다. 타일값이 0이라는 것은 지뢰를 의미하지요. 즉 지뢰를 밟았을 경우입니다. 이 때는 게임오버가 된 것이니 그 게임오버를 구현해주셔야 합니다. 먼저 for문을 이용하여 모든 버튼을 검사합니다. 그리고 지뢰타일이 있는 버튼을 전부 찾아내어 삭제시킵니다. 그리고 메세지박스함수를 이용하여 지뢰를 밟았다는 메세지를 띄우고 PostQuitMessage함수를 사용하여 윈도우를 종료시킵니다.

case가 9일 경우의 처리입니다. 즉 누른 버튼의 타일 값이 빈타일일 경우를 의미합니다. 빈타일은 주변에 지뢰가 없는 타일을 의미하는데 오리지널 지뢰찾기에서 빈타일을 눌렀을 경우

1. 빈 타일의 주변 타일들을 전부 연다. (주변 버튼을 전부 삭제한다)

2. 1번의 과정에서 생긴 빈타일들의 주변 타일들을 전부 연다.

이 두과정을 거치게 됩니다.

우리는 이 과정을 구현해주기 위해 그냥 간단하게 모든 타일을 체크하여 타일의 값이 9이고 그 타일의 버튼이 없는 경우(벗겨진 경우)를 찾아 그 주변의 타일을 전부 벗겨내는 함수(sp_check)를 사용하였습니다.

다만 함수를 한번만 사용하면 위의 2번 과정을 제대로 수행하기가 어렵기 때문에 sp_check를 여러번 사용하여 2번 과정을 수행할 수 있도록 하였습니다. 물론 지뢰찾기의 맵이 커지면 sp_check도 그만큼 더 많이 수행해야 합니다. sp_check 함수는 앞 포스팅에서 설명드렸으니 기억이 안나시는 분은 다시 한번 보시는 것을 추천합니다.

그 외의 타일 값들은 별 조작 없이 그냥 버튼만 없애면 되므로 생략하였습니다.

- victoryCheck();

이렇게 하나의 버튼을 눌렀을 때의 처리를 다 끝내고 나면 승리를 체크하는 함수인 victoryCheck 함수를 실행합니다. 이 함수는 버튼이 총 10개가 남아있으면 승리 라는 메세지를 출력시키고 남은 10개의 버튼을 삭제시킵니다. 버튼이 총 10개가 남아있을 려면 지뢰인 버튼을 누르지 않아야 하므로 오리지널 지뢰찾기와 승리조건이 똑같아집니다.

다음은 윈도우 창에 출력시키는 부분은 WM_PAINT 메시지를 살펴봅시다. 일단 코드를 먼저 봅시다.

-  hdc = BeginPaint(hWnd,&ps);

dc를 얻어내서 hdc에 저장시킵니다.

-  landmine = LoadBitmap(Global_hInstance, MAKEINTRESOURCE(LM));
   Number1 = LoadBitmap(Global_hInstance, MAKEINTRESOURCE(N1));
   Number2 = LoadBitmap(Global_hInstance, MAKEINTRESOURCE(N2));
   Number3 = LoadBitmap(Global_hInstance, MAKEINTRESOURCE(N3));
   Number4 = LoadBitmap(Global_hInstance, MAKEINTRESOURCE(N4));
   Number5 = LoadBitmap(Global_hInstance, MAKEINTRESOURCE(N5));
   Number6 = LoadBitmap(Global_hInstance, MAKEINTRESOURCE(N6));
   Number7 = LoadBitmap(Global_hInstance, MAKEINTRESOURCE(N7));
   Number8 = LoadBitmap(Global_hInstance, MAKEINTRESOURCE(N8));
   space = LoadBitmap(Global_hInstance, MAKEINTRESOURCE(SP));

비트맵이미지를 불러와 각각의 변수에 저장시킵니다. 비트맵 리소스와 LoadBitmap함수를 사용하였습니다. LoadBitmap함수를 사용하였으므로 각 변수들의 자료형은 HBITMAP입니다.

- for(i=0;i<9;i++)
     for(j=0;j<9;j++)
        buttonValue[m] = arr[i][j];
        m++;

윈도우 창에 비트맵을 9*9형태로 출력시키기 위해 이중 for문을 사용합니다. 그리고 따로 선언해놓은 인덱스변수 m을 사용하여 2차원 배열 arr에 저장된 타일값들을 1차원 배열에 저장시킵니다. 이렇게 하면 buttonValue배열에는 9*9의 타일들의 값이 한줄로 들어가게 됩니다. 하나의 타일값을 저장시키고 나면 인덱스를 하나 증가시켜야 하므로 m++을 사용하였습니다.

- switch(arr[i][j])
      case 0:
       hdcsrc = CreateCompatibleDC(hdc);
       Bitmap = (HBITMAP)SelectObject(hdcsrc,landmine);
       BitBlt(hdc,x,y,18,18,hdcsrc,NULL,NULL,SRCCOPY);
       DeleteObject(Bitmap);
       DeleteDC(hdcsrc);
       break;

지뢰찾기 맵이 저장되어있는 arr배열의 값들에 맞게 비트맵을 출력시키기 위해 switch함수를 사용하여씁니다. 2중 for문에 의해 한 줄씩 출력이 가능하므로 이에 맞춰주기 위해 arr[i][j]를 인자로 사용하였습니다.

이제 이 arr[i][j]값에 맞는 비트맵 이미지를 출력시켜야 하는데 비트맵을 출력시키기 위해선 dc가 2개 필요합니다. 그냥 DC(화면)와 메모리 DC 말입니다. 왜냐하면 비트맵이미지를 출력시킬 때 컴퓨터는 그냥 출력하면 속도가 많이 떨어지기 때문에 약간의 준비과정을 거치기 되는데 그 준비과정을 거칠 때 필요한 것이 바로 메모리 DC이기 때문입니다.  메모리 DC에 미리 출력할 이미지를 등록해 놓고 나중에 화면에 출력할 때 메모리 DC에 있는 이미지를 복사하여 출력하게 되는 것입니다.

우리는 이 메모리 DC를 얻기 위해 CreateCompatibleDC함수를 사용하였습니다. MSDN의 설명을 봅시다.

지정된 DC(화면 DC)와 호환되는 메모리DC를 생성해주는 함수라고 되어있습니다. 인자는 지정할 DC(화면 DC)입니다.

이 함수를 사용하여 기존의 DC와 호환되는 메모리DC를 만들어 hdcsrc에 저장시킵니다.

그 다음은 SelectObject함수를 사용하여 아까 만든 메모리DC에 landmine이란 이미지를 등록시킵니다. 그리고 DeleteObject함수를 사용하기 위해 SelectObject의 반환값을 HBITMAP형태로 Bitmap에 저장시킵니다.

자 이제 비트맵을 출력할 때입니다. 비트맵을 출력하기 위해 우리는 BitBlt함수를 사용하였습니다. 이 함수의 설명을 봅시다.

풀이하면 메모리 DC에 등록된 이미지를 고속으로 복사하여 화면 DC에 출력시키는 함수라고 되어 있습니다. 인자들을 하나씩 살펴봅시다.

* hdcDest

출력할 화면 DC입니다.

* nXDest

이미지를 출력할 x좌표 입니다.

* nYDest

이미지를 출력할 Y좌표입니다.

* nWidth

출력할 이미지의 가로길이 입니다.

* nHeight

출력할 이미지의 세로길이 입니다.

* hdcSrc

복사대상이 될 DC입니다. 여기서는 메모리 DC가 되겠군요.

* nXSrc

복사 대상 이미지의 x좌표입니다. 우리는 하나의 이미지를 통째로 복사할 것이므로 NULL값으로 주었습니다.

* nXSrc

복사 대상 이미지의 y좌표입니다. 우리는 하나의 이미지를 통째로 복사할 것이므로 NULL값으로 주었습니다.

* dwRop

복사할 때의 옵션입니다. 우리는 그냥 복사만 할 것이므로 SRCCOPY옵션을 주었습니다. 다른 옵션의 설명은 MSDN에 있으므로 궁금하신 분은 직접 참고하시면 됩니다.

우리는 arr[i][j]에 맞는 이미지를 하나씩 총 81번 출력할 것이므로 기본 좌표를 x,y로 두고 크기는 18*18로 두었습니다. 그리고 이렇게 하나의 출력이 끝나면 메모리 DC에 등록된 이미지를 DeleteObject함수를 사용해 해제하고, DeleteDC함수를 사용하여 메모리 DC를 삭제합니다.

참고로 기본 좌표인 x와 y는 선언할 때 30으로 두었으며 나머지 case문은 값만 다르고 다 똑같기에 설명을 생략하였습니다.

-   x += 18; 

한칸을 출력했으니 switch문을 빠져나갑니다. 이제 다음 칸을 출력시켜야겠지요? 다음칸을 출력시키기 위해서는 좌표를 (기존좌표+크기)로 바꾸어주어야 하므로 x에 18을 더합니다.

- x = 30;
  y += 18;

한 줄이 다 출력되고 나면 2중 for문에 의해 다음 줄을 출력하게 됩니다. 그러므로 다음 줄을 출력할 수 있도록 x를 기존 값으로 설정하고 y의 값을 비트맵의 크기만큼 증가시킵니다.

- EndPaint(hWnd,&ps);

모든 for문이 다 끝나고 나면 dc를 반환합니다.

네 이것으로 모든 코드 설명이 끝났습니다. 저는 여태 만들어본 프로그램 중에서 가장 어렵고 가장 재밌었던것 같습니다.

다음 포스팅에선 이제 우리가 만든 이 지뢰찾기를 좀 더 다듬어보겠습니다.

이상으로 지뢰찾기 포스팅을 마치겠습니다.

Posted by englishmath
,

안녕하십니까 이번 포스팅에선 지뢰찾기를 만들 때 쓰인 리소스를 보여드리도록 하겠습니다. 리소스를 추가하는 방법은 앞 포스팅에서 하였으므로 따로 설명드리지 않겠습니다.

먼저 메뉴 리소스입니다.

아이콘 리소스입니다.

깃발 리소스 입니다.

지뢰 리소스 입니다.

숫자부분 리소스입니다. N1에서 N8까지 각각 1~8을 맡고 있습니다.

물음표 리소스입니다.

공백타일 리소스입니다.

눌러지지 않은 기본 타일 리소스 입니다.

리소스 설명은 간단하게 끝이 났습니다.

다음포스팅에서는 본격적으로 소스를 파헤쳐 보겠습니다.

Posted by englishmath
,

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

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

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
,