2011년10월10일_소코반 게임 기초 (미완성)

 

★ 20대 때는 경험 부족으로 실수를 많이 한다.
   30대 때 가장 일을 잘 하고 운동도 잘하나 우리나라는 은퇴할 나이다. 
   -> 지금 잘 하는 시기에 놀고 있다. ㅠㅠ  30대 신입사원은 잘 안 뽑아…

★ 프로그래머는 여러 분야에 걸쳐 뭐든지 잘 하는 사람.   
   (네트워크 + F/W + UI(windows) + JAVA + H/W)
   지식도 편식하면 끝이다. 나하고 맞는 일만 찾지 마라. 
   -> 내가 하고 싶은 일만 하다 이 꼬라지 되었지...ㅠㅠ



● 전체소스코드

   1: // 게임 기초
   2: // 2011년 10월 10일 작성 
   3:  
   4: /* 윈도우즈 프로그래밍 순서 WinMain()
   5: 
   6:     WndClass정의
   7: 
   8:     클래스를 등록
   9: 
  10:     메모리상에 윈도우 생성
  11: 
  12:     윈도우를 화면에 출력
  13: 
  14:     메시지 루프 (반복)
  15: */
  16: #include <windows.h>        // 모든 API함수들의 원형과 사용하는 상수들이 정의되어 있음.
  17: #include "resource.h"
  18:  
  19: #define    BW    30
  20: #define    BH    30
  21:  
  22: LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);    //메시지처리함수
  23: HINSTANCE g_hlnst;    //핸들 인스턴스
  24: LPCTSTR lpszClass = TEXT("GameBasis");        //윈도우 제목으로 사용되는 const char * (문자열)
  25:  
  26:  
  27:  
  28: /***************************************************************************************************************************
  29: WinMain은 엔트리 포인트(시작함수)
  30: APIENTRY는 윈도우즈 표준호출규약 _stdcall(없어도 무방함)
  31: 
  32: WinMain의 인자들...
  33: ① hInstance: 프로그램의 인스턴스 핸들
  34: ② hPrevInstance: 바로 앞에 실행된 현재 프로그램의 인스턴스 핸들로 16비트와 호환성위한 것이니 무시하자. Win32는 NULL이다.
  35: ③ lpszCmdParam: 명령행으로 입력된 프로그램의 인수 (콘솔app에서 argv), 보통 실행직후에 열 파일의 경로가 전달됨.
  36: ④ nCmdShow: 프로그램이 실행될 형태이며 최소화, 보통 모양 등이 전달된다.
  37: ***************************************************************************************************************************/
  38: int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
  39: {
  40:     HWND hWnd;
  41:     MSG Message;
  42:     WNDCLASS WndClass;
  43:     g_hlnst = hInstance;
  44:  
  45:     /* 윈도우 클래스 정의 */
  46:     WndClass.cbClsExtra = 0;
  47:     WndClass.cbWndExtra = 0;
  48:     WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  49:     //WndClass.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(0xFF, 0xFF, 0xEE));
  50:     //WndClass.hbrBackground = (HBRUSH)CreateHatchBrush(HS_DIAGCROSS, RGB(100, 255, 0));
  51:     //WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  52:     WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  53:     WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  54:     WndClass.hInstance = hInstance;
  55:     WndClass.lpfnWndProc = WndProc;
  56:     WndClass.lpszClassName = lpszClass;
  57:     WndClass.lpszMenuName = NULL;
  58:     WndClass.style = CS_HREDRAW | CS_VREDRAW;
  59:     
  60:     /* 윈도우 클래스 등록 */
  61:     RegisterClass(&WndClass);
  62:  
  63:     /* 메모리상에 윈도우 생성 */
  64:     hWnd = CreateWindow(lpszClass, TEXT("소코반"), WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME,
  65:         CW_USEDEFAULT, CW_USEDEFAULT, 30 * 25 + 6, 30 * 15 + 25,        // 6과 25 더하는 이유는 타이틀고 윈도우 경계    
  66:         NULL, (HMENU)NULL, hInstance, NULL);
  67:     
  68:     /* 화면에 윈도우 표시 */
  69:     ShowWindow(hWnd, nCmdShow);
  70:  
  71:     while(GetMessage(&Message, NULL, 0, 0)) {
  72:         TranslateMessage(&Message);
  73:         DispatchMessage(&Message);
  74:     }
  75:  
  76:     return (int)Message.wParam;
  77: }
  78:  
  79: LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
  80: {
  81:     HDC hdc;    // hdc는 화면DC
  82:     HDC MemDC;    // 메모리DC
  83:     PAINTSTRUCT ps;
  84:     int iRow;    // 반복제어변수
  85:     int iCol;    // 반복제어변수
  86:     static HBITMAP hWall;         // 벽
  87:     static HBITMAP hSpace;         // 공간
  88:     static HBITMAP hHuman;         // 졸라맨 '@'
  89:     static HBITMAP hBox;         // 박스
  90:     static HBITMAP hStorage;     // 저장공간
  91:     static HBITMAP hOldBitmap;
  92:     static int iX = 11;
  93:     static int iY = 8;
  94:     static RECT rt;
  95:     static unsigned char ucStage[][15][25 + 1] =    // +1은 문자열이니 NULL추가
  96:     {    // 0123456789012345678901234                // 화면에 출력하는 그래픽을 1:1대응
  97:         { "#########################"
  98:          ,"#########################"
  99:          ,"######   ################"
 100:          ,"######O  ################"
 101:          ,"######  O################"
 102:          ,"####  O O ###############"
 103:          ,"#### # ## ###############"
 104:          ,"##   # ## ######  ..#####"
 105:          ,"## O  O    @      ..#####"
 106:          ,"###### #### # ##  ..#####"
 107:          ,"######      #############"
 108:          ,"#########################"
 109:          ,"#########################"
 110:          ,"#########################"
 111:          ,"#########################"
 112:         }
 113:     };
 114:  
 115:     switch(iMessage) {
 116:     case WM_CREATE:    
 117:         hWall = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_WALL));    // 벽 비트맵 읽어옴.
 118:         hSpace = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_SPACE));    // 빈공간 비트맵 읽어옴.
 119:         hHuman = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_HUMAN));    // 졸라맨 비트맵 읽어옴.
 120:         hBox = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BOX));        // 박스 비트맵 읽어옴.
 121:         hStorage = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_STORAGE));    // 저장공간 비트맵 읽어옴.
 122:         // GetModuleHandle(NULL) == 이 프로그램의 인스턴스 핸들
 123:         ucStage[0][iY][iX] = '@';
 124:         return 0;
 125:  
 126:     case WM_KEYDOWN:
 127:         rt.left = iX * BW;
 128:         rt.top = iY * BH;
 129:         rt.right = iX * BW + BW;
 130:         rt.bottom = iY * BH + BH;  
 131:         ucStage[0][iY][iX] = ' ';
 132:         InvalidateRect(hWnd, &rt, TRUE);
 133:         
 134:         switch(wParam) {
 135:         case VK_LEFT:
 136:             if('#' != ucStage[0][iY][iX - 1])
 137:             {
 138:                 iX = iX - 1;
 139:             }
 140:             break;
 141:         case VK_RIGHT:
 142:             if('#' != ucStage[0][iY][iX + 1])
 143:             {
 144:                 iX = iX + 1;
 145:             }
 146:             break;
 147:         case VK_UP:
 148:             if('#' != ucStage[0][iY - 1][iX])
 149:             {
 150:                 iY = iY - 1;
 151:             }
 152:             break;
 153:         case VK_DOWN:
 154:             if('#' != ucStage[0][iY + 1][iX])
 155:             {
 156:                 iY = iY + 1;
 157:             }
 158:             break;
 159:         }
 160:  
 161:         rt.left = iX * BW;
 162:         rt.top = iY * BH;
 163:         rt.right = iX * BW + BW;
 164:         rt.bottom = iY * BH + BH;  
 165:         ucStage[0][iY][iX] = '@';
 166:         InvalidateRect(hWnd, &rt, TRUE);
 167:  
 168:         return 0;
 169:  
 170:     case WM_PAINT:
 171:         hdc = BeginPaint(hWnd, &ps);
 172:         MemDC=CreateCompatibleDC(hdc);
 173:  
 174:         // 가로 10개, 세로10개 출력.
 175:         for(iRow = 0 ; 15 > iRow ; ++iRow)
 176:         {
 177:             for(iCol = 0 ; 25 > iCol ; ++iCol)
 178:             {
 179:                 switch(ucStage[0][iRow][iCol]) {
 180:                 case '#':    // 벽
 181:                     hOldBitmap=(HBITMAP)SelectObject(MemDC, hWall);
 182:                     BitBlt(hdc, iCol * BW, iRow * BH, BW, BH, MemDC, 0, 0, SRCCOPY);    //비트맵복사 MemDC -> DC
 183:                     SelectObject(MemDC,hOldBitmap);
 184:                     break;
 185:                 case '@':    // 졸라맨
 186:                     hOldBitmap=(HBITMAP)SelectObject(MemDC, hHuman);
 187:                     BitBlt(hdc, iCol * BW, iRow * BH, BW, BH, MemDC, 0, 0, SRCCOPY);    //비트맵복사 MemDC -> DC
 188:                     SelectObject(MemDC,hOldBitmap);        // 핸들 교체 말고도 MemDC를 여러 개 선언하는 법이 있음.
 189:                     break;
 190:                 case 'O':    // 박스
 191:                     hOldBitmap=(HBITMAP)SelectObject(MemDC, hBox);
 192:                     BitBlt(hdc, iCol * BW, iRow * BH, BW, BH, MemDC, 0, 0, SRCCOPY);    //비트맵복사 MemDC -> DC
 193:                     SelectObject(MemDC,hOldBitmap);        // 핸들 교체 말고도 MemDC를 여러 개 선언하는 법이 있음.
 194:                     break;
 195:                 case '.':    // 저장공간
 196:                     hOldBitmap=(HBITMAP)SelectObject(MemDC, hStorage);
 197:                     BitBlt(hdc, iCol * BW, iRow * BH, BW, BH, MemDC, 0, 0, SRCCOPY);    //비트맵복사 MemDC -> DC
 198:                     SelectObject(MemDC,hOldBitmap);        // 핸들 교체 말고도 MemDC를 여러 개 선언하는 법이 있음.
 199:                     break;
 200:                 case ' ':    // 통로
 201:                     hOldBitmap=(HBITMAP)SelectObject(MemDC, hSpace);
 202:                     BitBlt(hdc, iCol * BW, iRow * BH, BW, BH, MemDC, 0, 0, SRCCOPY);    //비트맵복사 MemDC -> DC
 203:                     SelectObject(MemDC,hOldBitmap);        // 핸들 교체 말고도 MemDC를 여러 개 선언하는 법이 있음.
 204:                     break;
 205:                 default:    // 디폴트 처리
 206:                     break;
 207:             }
 208:  
 209:                 /*
 210:                 if('#' == ucStage[0][iRow][iCol])
 211:                 {
 212:                     hOldBitmap=(HBITMAP)SelectObject(MemDC, hWall);
 213:                     BitBlt(hdc, iCol * BW, iRow * BH, BW, BH, MemDC, 0, 0, SRCCOPY);    //비트맵복사 MemDC -> DC
 214:                     SelectObject(MemDC,hOldBitmap);
 215:                 }
 216:                 else if('@' == ucStage[0][iRow][iCol])
 217:                 {
 218:                     hOldBitmap=(HBITMAP)SelectObject(MemDC, hHuman);
 219:                     BitBlt(hdc, iCol * BW, iRow * BH, BW, BH, MemDC, 0, 0, SRCCOPY);    //비트맵복사 MemDC -> DC
 220:                     SelectObject(MemDC,hOldBitmap);        // 핸들 교체 말고도 MemDC를 여러 개 선언하는 법이 있음.
 221:                 }
 222:                 */
 223:             }
 224:         }
 225:  
 226:         DeleteDC(MemDC);
 227:         EndPaint(hWnd, &ps);
 228:         return 0;
 229:  
 230:     case WM_DESTROY:
 231:         DeleteObject(hWall);    //게임 끝나면 지워야 함.
 232:         DeleteObject(hSpace);    
 233:         DeleteObject(hHuman);    
 234:         PostQuitMessage(0);
 235:         return 0;
 236:     }
 237:  
 238:     return(DefWindowProc(hWnd, iMessage, wParam, lParam));
 239: }



● 소스코드설명

  19: #define    BW    30
  20: #define    BH    30

BW는 비트맵블록(사람, 박스, 벽, 등등)의 가로 길이(Width)이고 30pixel이다. BH는 세로 길이(Height)이다.

image 

  64:     hWnd = CreateWindow(lpszClass, TEXT("소코반"), WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME,

CreateWindow()의 세 번째 인자는 윈도우의 스타일로 윈도우 모양과 동작 방식을 결정하는 여러 가지 속성을 지정한다.

image

보통 게임은 정해진 해상도에서 돌아가니 실행 중 윈도우크기를 변경시키는 일은 하지 않는다.
그래서 WS_THICKFRAME속성을 없애야 한다.
일일이 OR연산해서 지정할 수도 있지만 좀 더 쉽게 AND연산과 NOT연산을 이용해 해당 비트를 클리어해 속성을 없앤다.

  65:         CW_USEDEFAULT, CW_USEDEFAULT, 30 * 25 + 6, 30 * 15 + 25,        // 6과 25 더하는 이유는 타이틀고 윈도우 경계    

CreateWindow()의 네 번째부터 일곱 번째까지 인자들은 각 윈도우의 초기 위치(x, y)와 윈도우의 가로크기, 세로크기이다.
가로크기에 6을 더하는 이유는 윈도우테두리가 좌우로 각 3Pixel이라 6Pixel을 더한 것이고,
세로크기에 25를 더하는 이유는 윈도우테두리 합 6Pixel과 타이틀바19Pixel을 더한 것이다. 다른 사람은 32~34까지 다양했다 –_-;
#define문으로 정의해야 하나 발로 짠 프로그램이라 ㅎㅎ

  95:     static unsigned char ucStage[][15][25 + 1] =    // +1은 문자열이니 NULL추가

ucStage[][][]은 3차원배열로 첫 번째 인덱스는 스테이지이고,
                                 두 번째 인덱스는 맵의 세로축 데이터이고,
                                 세 번째 인덱스는 맵의 가로축 데이터이다.
문자열을 저장하므로 마지막에 NULL이 포함된다. 그러니 원소의 갯수를 +1해준다.

 115:     switch(iMessage) {
 116:     case WM_CREATE:    
 117:         hWall = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_WALL));    // 벽 비트맵 읽어옴.
 118:         hSpace = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_SPACE));    // 빈공간 비트맵 읽어옴.
 119:         hHuman = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_HUMAN));    // 졸라맨 비트맵 읽어옴.
 120:         hBox = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BOX));        // 박스 비트맵 읽어옴.
 121:         hStorage = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_STORAGE));    // 저장공간 비트맵 읽어옴.
 123:         ucStage[0][iY][iX] = '@';

115행부터 시작되는 switch-case문은 운영체제가 CALLBACK함수인 WndProc()를 호출하면서 함께 인자로 넘겨 주는 메시지의 분기문으로 모든 일은 여기서 시작된다고 해도 과언이 아니다.

116행은 WM_CREATE메시지 분기로 윈도우생성 전에 발생되는 메시지다.
(실험해보니 윈도우가 보이기 전에 실행되었다. 즉, WinMain()이 처음 실행되는 함수가 먼저 실행되지 않거나,
                                                            멀티쓰레드로 WinMain()과 WndProc()가 같이 실행될 수도 있다는 것?)

117행부터 121행까지는 비트맵리소스(파일)를 불러와 메모리에 적재하는 과정이다.
GetModuleHandle()은 인자로 받은 모듈명(DLL또는 EXE파일명)의 인스턴스 핸들을 반환하는 함수이다.
여기서는 GetModuleHandle()대신 g_hInst를 사용하는게 더 현명하다.

  23: HINSTANCE g_hlnst;    //핸들 인스턴스
  43:     g_hlnst = hInstance;

g_hInst는 상기와 같이 WinMain()의 첫 번째 인자인 HINSTANCE형의 hInstance(프로그램의 인스턴스 핸들)을 대입받아 저장하고 있다.
전역변수이니 어느 함수에서나 자유롭게 쓸 수 있다.

123행은 맵 배열에 초기 인간의 위치를 지정하는 명령이다.  ‘@’는 인간을 뜻한다.

 126:     case WM_KEYDOWN:

126행의 WM_KEYDOWN메시지는 수업시간에 하지 않았으니 패스...쉽게 구성되어 있어 초보자도 쉽게 분석할 수 있다.



 170:     case WM_PAINT:
 175:         for(iRow = 0 ; 15 > iRow ; ++iRow)
 177:             for(iCol = 0 ; 25 > iCol ; ++iCol)
 179:                 switch(ucStage[0][iRow][iCol]) {
 180:                 case '#':    // 벽
 185:                 case '@':    // 졸라맨
 190:                 case 'O':    // 박스
 195:                 case '.':    // 저장공간
 200:                 case ' ':    // 통로

170행은 WM_PAINT메시지 분기로,
          이 윈도우가 다른 윈도우에 가려지거나 최소화 최대화 등등 윈도우를 다시 그려야 할 때 발생하는 메시지다.
175행은 행의 반복문으로 15번 반복,
177행은 열의 반복문으로 25번 반복,
179행의 switch-case문은 2차원 반복문으로 정해진 행과 열의 위치에 있는 데이터에 따른 분기문이다.
180행은 절대 뚫을 수 없는 변태 프로그래머가 만든 졸라맨 감금을 위한 벽,
185행은 인간캐릭인 졸라맨,
190행은 옮겨야할 박스,
195행은 저장공간으로 여기에 박스를 쌓아야 다음 스테이지로 넘어간다.
200행은 통로로 졸라맨이 자유롭게 돌아다닐 수 있는 공간이다.

5개의 비트맵 중 하나만 설명한다. 귀차나…

 181:                     hOldBitmap=(HBITMAP)SelectObject(MemDC, hWall);
 182:                     BitBlt(hdc, iCol * BW, iRow * BH, BW, BH, MemDC, 0, 0, SRCCOPY);    //비트맵복사 MemDC -> DC
 183:                     SelectObject(MemDC,hOldBitmap);

181행: 우선 SelectObject()로 메모리DC에 hWall이 가리키는 메모리영역의 비트맵을 선택한다.
        메모리DC도 DC와 마찬가지로 DC안에 GDI오브젝트가 있고 메모리DC를 가리키는 핸들인 MemDC를 이용한다.
        반환값은 예전 비트맵의 주소로 hOldBitmap이 받아 임시로 가리키고 있게 한다.  (백업)
        (이 부분에 대해서 확실히 이해한 것은 아니니 잘 못 된 부분이 있으면 언제든지 댓글 달아주세요.)

182행: BiBlt()는 메모리DC에서 DC로 고속복사를 함수로 자세한 설명은 아래의 링크를 참조하세요.
http://sumanaki.tistory.com/207  <-이게 불만이면   -> http://winapi.co.kr

183행: 비트맵을 선택해 메모리DC에서 DC로 복사를 끝내 화면에 비트맵을 출력하였으니 더 이상 볼일은 없다.
       예전의 비트맵을 가리키는 핸들로 복귀시키자..이렇게 하는 이유는 비트맵리소스를 메모리에서 제거하기 전에 핸들을 교체하는것.
       (현재 GDI오브젝트의 비트맵리소스의 핸들(?)은 제거할 수 없다(?) 이 부분도 마찬가지로 이해가 확실히 안 되었음.)

 172:         MemDC=CreateCompatibleDC(hdc);
 226:         DeleteDC(MemDC);

CreateCopatibleDC()를 사용해 메모리DC핸들을 얻었으면 GetDC()나 BeginPaint()와 마찬가지로 해제해 줘야 한다.
malloc()로 얻은 메모리영역을 free()로 반납하는 것과 같다.

 230:     case WM_DESTROY:
 231:         DeleteObject(hWall);    //게임 끝나면 지워야 함.
 232:         DeleteObject(hSpace);    
 233:         DeleteObject(hHuman);    
 234:         PostQuitMessage(0);

230행: WM_DESTROY메시지는 윈도우 파괴시 (종료시) 발생하는 메시지이다.
231행부터 233행까지 메모리에 불러온 비트맵리소스를 지워주고 있는데..
                         여기서 문제가 벽, 공간, 사람은 해제하고 있으나 박스, 저장공간은 해제하고 있지 않다. (실수)
234행: PostQuitMessage()은 스레드 메시지 큐에 WM_QUIT 메시지를 붙이고 즉시 리턴한다. WM_QUIT 메시지를 큐에 붙임으로써 시스템에게 이 스레드가 종료될 것이라는 것을 미리 알려준다. 인자로 전달받은 0은 WinMain()의 리턴값으로 재사용된다.
참조URL: http://winapi.co.kr


● 실행결과

image

초기화면으로 winAPI정복 p.256과 똑같게 하였다.

 

image 

왼쪽 화살표를 눌러 캐릭터를 이동시킨 후 스크린샷

image

한 번 더 누르니 상자를 먹었다. ㅋㅋ

전체소스코드 다운로드: 1010_Game_Basic.zip