안녕하십니까 이번 포스팅에선 저번 포스팅에서 만든 지뢰찾기에 기능을 추가해보겠습니다. 먼저 바뀐 소스코드를 봅시다.

바뀐 소스코드만 찍어서 올렸습니다. 하나씩 살펴봅시다.

- int time_value = 0;

지뢰찾기의 시간을 저장시키기 위한 변수를 선언하였습니다.

- BOOL start = FALSE;

BOOL형인 start변수를 선언하고 FALSE값으로 초기화 시켰습니다. 이 변수는 지뢰찾기를 시작했을 때 타이머가 작동하도록 하기 위해 쓰였습니다.

- int landmine_value = 10;

사용자가 우측 마우스를 눌러 지뢰임을 표시할 때 지뢰의 개수를 나타내기 위해 선언하였습니다.

- wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); -> 원본코드

 wndclass.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH); -> 수정코드

윈도우창의 배경색을 바꿔주었습니다. LTGRAY_BRUSH는 밝은 회색이란 뜻입니다.

이제 WndProc함수를 살펴봅시다.

- HBITMAP Number6,Number7,Number8,space; -> 원본코드

  HBITMAP Number6,Number7,Number8,space,landmine_count,timer; -> 수정코드

비트맵을 출력시키기 위해 HBITMAP형 변수 landmine_count와 timer를 선언하였습니다. 이 변수는 각각

비트맵 이미지를 저장시킵니다.

- RECT rt = {55,200,100,250};
  RECT rt2 = {150,200,200,250};

처음 보는 자료형인 RECT가 나왔습니다. 이 RECT의 정의를 한 번 봅시다.

구조체이군요. 각 멤버들은 왼쪽,높이,오른쪽,아래 를 나타냅니다. 이것이 무슨 말이냐면 사각형의 왼쪽 위 모서리 좌표와 오른쪽 아래 모서리 좌표를 저장시킨다는 뜻입니다. 즉 사각형 모양의 영역을 지정할 때 쓰이는 구조체입니다. 이 구조체를 선언하는 이유는 나중에 설명드리겠습니다.

아무튼 RECT의 구조체를 2개 선언하여 각각 초기값을 줍니다. 이러면 rt에는 (55,200)에서 (100,250) 까지의 사각형 영역을 나타내고 rt2에는 (150,200)에서 (200,250) 까지의 사각형 영역을 나타냅니다.

- TCHAR time_count[150]=L"";

지뢰찾기에서는 몇초가 지나갔다 혹은 지뢰 몇개가 남아있다는 것을 숫자로 사용자에게 보여주는데 이 값들은 시시각각 변합니다. 그러므로 %d형으로 값을 출력시켜야 합니다. 즉 wsprintf함수를 사용해야 합니다. 그래서 이 wsprintf함수를 사용하기 위해 TCHAR 형 변수를 선언하였습니다.

자 이제 WM_CONTEXTMENU를 살펴봅시다.

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

  if(buttonHwnd[i] == (HWND)wParam && flagTile[i] == 1)
     SendMessage((HWND)wParam, BM_SETIMAGE,IMAGE_BITMAP,(LPARAM)LoadBitmap(Global_hInstance, MAKEINTRESOURCE(Flag)));
     flagTile[i] = 0;
     landmine_value--;
     InvalidateRect(hWnd,&rt2,TRUE); -> 수정코드

수정코드를 잘 보시면 landmine_value--; 가 적혀있습니다. 오리지널 지뢰찾기에서는 사용자가 우측 마우스를 눌러 지뢰가 있다는 것을 표기하면 지뢰의 개수가 하나 줄어든 값을 보여줍니다.

이렇게 말이지요. 이것을 구현하기 위해 지뢰의 개수를 나타내는 변수인  landmine_value값을 하나 감소시킵니다. 이렇게 값을 하나 감소시키고 나면 감소시킨 값을 사용자에게 보여주어야 겠지요? 이를 위해 InvalidateRect함수를 사용하였습니다. 정의를 한 번 봅시다.

이 함수는 특정 영역을 무효화 영역으로 만들어주는 함수입니다. 앞 포스팅에서 설명하였지만 무효화 영역이 발생하면 윈도우에서는 WM_PAINT메세지가 호출됩니다. 즉 이 함수는 WM_PAINT메세지를 발생시키기 위해 사용하는 함수인데 WM_PAINT메세지를 발생시킴으로써 보여지는 landmine_value값을 감소시킨 landmine_value값으로 다시 그리는 것이지요. 인자들을 하나씩 살펴봅시다.

* hWnd

무효화 영역을 발생시킬 대상 핸들입니다.

* RECT *lpRect

RECT 구조체는 앞에서 설명드렸지요? 영역을 나타내는 구조체입니다. 즉 이 인자부분은 무효화시킬 영역을 의미합니다.

*BOOL bErase

이 인자는 영역을 무효화시킬 때 그 영역의 배경을 지우고 무효화 시키느냐 혹은 배경을 지우지 않고 무효화 시키느냐를 결정합니다. TRUE값으로 주면 배경을 지우고 무효화시키며 FALSE를 주면 배경을 지우지 않고 무효화 시킵니다. 특별한 상황이 아니면 대부분 TRUE값을 줍니다.

우리는  InvalidateRect(hWnd,&rt2,TRUE); 코드를 작성함으로써 rt2의 영역을 무효화시킵니다. 나중에 나오겠지만 이 rt2의 영역은 지뢰 개수를 나타내는 숫자 부분 영역입니다.  즉 이 영역을 무효화 시키고 WM_PAINT메세지를 호출하여 달라진 지뢰 개수 값으로 다시 그린다고 보시면 됩니다.

-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; -> 원본코드

- 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;
     landmine_value++;
     InvalidateRect(hWnd,&rt2,TRUE); -> 수정코드

이 코드 부분은 사용자가 깃발이 있는 타일에 오른쪽 마우스를 눌러 ?타일로 바꾸는 부분입니다. 깃발타일이 사라졌으므로  landmine_value값을 하나 증가시키고 다시 그리기 위해 InvalidateRect함수를 호출합니다.

다음은 WM_COMMAND메세지를 봅시다.

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

- if(flagTile[wParam] == 1)
      if(!start)
          start = TRUE;
          SetTimer(hWnd,1,1000,NULL);
      DestroyWindow(buttonHwnd[wParam]);
      OpenClose_Tile[wParam] = 0;

버튼을 눌렀을 때의 코드입니다. 오리지널 지뢰찾기에서는 정상적인 버튼을 눌러서 버튼이 벗겨졌을 경우 타이머가 작동합니다. 우리도 그것을 구현하기 위해 버튼을 처음 눌렀을 경우 SetTimer함수를 호출하도록 코드를 작성하였습니다. start변수에는 FALSE가 초기값으로 들어있었고 FALSE의 부정은 TRUE이므로 버튼을 처음 눌렀다면 if(!start)에서 참으로 인식하여 SetTimer함수를 호출합니다. 그리고 start변수에 TRUE값을 주어 다음에 버튼을 누를 경우 if문이 실행되지 않도록 해줍니다.

setTimer함수를 한 번 살펴봅시다.

함수 명 그대로 타이머를 설정해주는 함수입니다. 인자를 살펴 봅시다.

*hWnd

타이머와 관련된 핸들입니다. 보통 WndProc의 인자인 핸들이 인자로 들어갑니다.

*nIDEvent

타이머의 이름을 의미하는 인자입니다. 1을 줄 경우 그 타이머의 이름은 1이 됩니다. 이렇게 지정된 타이머의 이름은 KillTimer함수를 사용할 때 쓰입니다.

*uElapse

타이머의 주기를 설정하는 부분입니다. 원래 이 SetTimer함수가 호출되고 난 후에는 일정 주기마다 WM_TIMER메세지가 발생하는데 그 일정주기를 설정해주는 부분입니다. 단위는 ms(밀리 세컨드)이기 때문에 값을 1000으로 주면 1초마다 WM_TIMER메세지가 발생하도록 할 수 있습니다.

*lpTimerFunc

WM_TIMER메세지가 발생할 때마다 호출할 콜백 함수를 지정하는 부분입니다. 그림을 보시면 자료형이 TIMERPROC라고 되어있는데 이 자료형은 WndProc와 같은 CALLBACK 함수임을 뜻합니다. 우리는 사용할 필요가 없으므로 NULL값을 줍시다.

자 이제 WM_PAINT메세지를 살펴봅시다.

- SetBkColor(hdc,RGB(0,100,200));
  SetTextColor(hdc,RGB(255,255,255));

출력할 글자의 배경색과 색깔을 지정해주기 위해 사용하였습니다.

- wsprintf(time_count,L"%d",time_value);
  TextOut(hdc,55,200,time_count,lstrlen(time_count));
  wsprintf(time_count,L"%d",landmine_value);
  TextOut(hdc,150,200,time_count,lstrlen(time_count));

wsprintf함수를 사용하여 배열에 데이터를 출력시킵니다. 첫번째 wsprintf함수는 time_count에 time_value값을 출력시켰습니다. 이 time_value값은 타이머의 값을 의미합니다. 그리고 TextOut함수로 hdc에 time_count배열에 들어있는 값을 출력시킵니다. 이렇게 되면 결과적으로 (%d,time_value) 값이 화면에 나타나게 됩니다.

두번 째 wsprintf함수도 마찬가지입니다. 이 함수는 남은 지뢰 개수를 배열에 저장시키고 TextOut함수로 화면에 출력시킵니다.

한가지 주의해야 하는 부분은 TextOut함수를 호출할 때 글자를 출력할 좌표를 지정하게 되는데 이 좌표는 RECT로 선언된 rt구조체의 영역 안에 포함되어 있어야 합니다. 그래야 나중에 무효화처리되어 WM_PAINT에서 다시 그릴때 정상적으로 그려지게 됩니다. 즉 time_value를 출력시킬 때의 좌표는 rt 영역 안이어야 하며 landmine_value를 출력시킬 때의 좌표는 rt2 영역 안이어야 합니다.

- timer = LoadBitmap(Global_hInstance, MAKEINTRESOURCE(Timer));
  landmine_count = LoadBitmap(Global_hInstance,MAKEINTRESOURCE(LM_COUNT));

앞에서 선언한 HBITMAP형 timer와 landmine_count변수에 비트맵을 저장시키는 코드입니다.

- hdcsrc = CreateCompatibleDC(hdc);
   Bitmap = (HBITMAP)SelectObject(hdcsrc,timer);
   BitBlt(hdc,25,192,24,24,hdcsrc,NULL,NULL,SRCCOPY);
   DeleteObject(Bitmap);
   Bitmap = (HBITMAP)SelectObject(hdcsrc,landmine_count);
   BitBlt(hdc,175,192,24,24,hdcsrc,NULL,NULL,SRCCOPY);
   DeleteObject(Bitmap);
   DeleteDC(hdcsrc);

비트맵이 저장된 timer와 landmine_count를 BitBlt함수로 출력시키는 부분입니다.

이제 WM_TIMER메세지를 살펴봅시다.

- time_value++;
  InvalidateRect(hWnd,&rt,TRUE);

WM_TIMER메세지가 발생할 때마다 time_value값을 하나씩 증가시킵니다. 앞의 SetTimer함수에서 주기를 1000으로 주었으므로 1초마다 WM_TIMER메세지가 발생하고 이로 인해 time_value값이 하나 증가합니다. 그리고 값이 증가하였으니 InvalidateRect함수를 호출하여 무효화 영역을 그려 WM_PAINT메세지를 발생시키도록 하였습니다. 이 InvalidateRect함수에 쓰인 rt구조체는 타이머 값이 출력되는 영역을 의미하지요. 즉 타이머의 값을 다시 그려주기 위해 사용되었습니다.

마지막으로 WM_DESTORY메세지를 봅시다.

- KillTimer(hWnd,1);

SetTimer함수로 설정된 타이머는 따로 삭제를 해주지 않으면 윈도우 창이 종료된 후에도 계속 남아있어 메모리 누수가 발생하게 됩니다. 이를 방지하기 위해 KillTimer함수를 사용하여 만든 타이머를 지웁니다. 인자는 SetTimer를 호출헀을 때의 핸들과 이름입니다.

이제 결과물을 한 번 봅시다.

네 이것으로 지뢰찾기 다듬기 1을 마치겠습니다.

Posted by englishmath
,