Skip to main content

[C/C++] 소코반 게임

처음 접해보는 윈도우프로그램이라서 그런지 시간이 많이 걸렸다..

정말 많이 부족함을 느끼는구나 ㅠ_ㅠ…     하지만 좋은 경험이 된것 같다..

내 멋대로 주석과 많이 부족한 소스지만 기록을 남겨본다..

[#M_소스보기|접기|

/* Program Sokoban_Win ver.00    */
/* Romantic Programmer Teddy    */

#include ”stdafx.h”
#include <Windows.h>
#include ”resource.h”    // 비트맵 리소스를 가지고 있음.
#include <string.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass=”Sokoban_Win”;

#define P_BLANK        0    // 빈공간
#define P_WALL          1    // 벽
#define P_BLOCK        2    // 상자
#define P_PLAYER       5    // 플레이어
#define P_HOLE           8    // 상자구멍
#define P_FILL             9    // 상자구멍속 상자

// 한 스테이지 정보를 담는 구조체
typedef struct {
int        width;        // 스테이지 넓이
int        height;        // 스테이지 높이
int        player_x;    // 플레이어 x위치
int        player_y;    // 플레이어 y위치
int*      array;        // 현재 스테이지
int*      array_ori;    // 최초 스테이지
int        num_goal;    // 스테이지 내의 상자구멍의 수
} stage;

void    initStage(stage*);                    // 스테이지 정보 초기화
void    freeStage(stage*);                    // 스테이지에 할당된 메모리 해제
void    printStage(stage*, HDC);            // 스테이지 맵 그려줌
bool    isFinish(stage*);                    // 게임이 완료했는지 체크
bool    movement(stage*, char);                // 키 값에 따른 플레이어, 상자 이동
void    setPoint(stage*, int, int, int);    // 지정된 좌표에 지정된 식별번호 지정
int        getPoint(stage*, int, int);            // 지정된 좌표에 무엇이 존재하는지 반환
int        BlankOrFill(stage*, int, int);        // 최초 스테이지의 지정된 좌표에 무엇이 존재하는지 반환

int total_move = 0;        // 플레이어가 움직인 횟수를 담는 변수
int cur_x, cur_y;        // 현제 플레이어의 위치를 담는 변수
int new_x, new_y;        // 플레이어가 움직일 위치를 담는 변수
stage ground;            // 스테이지 정보를 담는 구조체 변수
char key_value;            // 플레이어가 누른 키 정보를 담는 변수
char msgString[50] = ”You solve the problem in ”;

// 메인이 되는 함수.
int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )
{
HWND hWnd;
MSG Message;
WNDCLASS WndClass;
g_hInst = hInstance;

WndClass.style = NULL;    // 윈도우의 스타일 보통 CS_HREDRAW | CS_VREDRAW 많이 사용(창크기조절가능).
WndClass.cbClsExtra = 0;    // 일종의 예약 영역으로 윈도우즈가 내부적으로 사용하며
WndClass.cbWndExtra = 0;    // 아주 특수한 목적으로 사용되는 여분의 공간.
WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);    // 윈도우의 배경색상을 지정. GetStockObject함수를 사용해서 윈도우가
// 기본적으로 제공하는 브러시를 지정. 보통 WHITE_BRUSH 많이 사용.
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);        // 윈도우가 사용할 마우스커서와 최소화되었을 경우 출력될 아이콘을 지정.
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);    // LoadCursor, LoadIcon 함수 사용. 이 경우는 윈도우즈가 기본으로 제공하는것 사용.
WndClass.hInstance = hInstance;            // 윈도우 클래스를 사용하는 프로그램 번호로 WinMain의 인수인 hInstance 그래도 대입.
WndClass.lpfnWndProc = (WNDPROC)WndProc;    // 윈도우의 메시지 처리 함수 지정 보통 WndProc로 지정.
WndClass.lpszClassName = lpszClass;    // 윈도우 클래스의 이름을 정의. 여기서 지정한 이름은 CreateWindow함수에 전달되어지며
// CreateWindow함수는 윈도우 클래스에서 정의한 특성값을 참조하여 윈도우를 만든다.
// 보통 실행파일 이름과 일치시킴.
WndClass.lpszMenuName = NULL;    // 프로그램이 사용할 메뉴를 지정. 메뉴는 프로그램 코드에서 만드는 것이 아니라 리소스 에디터에 의해
// 별도로 만들어진 후 링크시에 같이 합져짐. 사용하지 않으면 NULL.
RegisterClass(&WndClass);    // 윈도우 클래스를 등록. 위같은 특성을 가진 윈도우를 앞으로 사용하겠다는 등록 과정.

hWnd=CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 370, 330,
NULL, (HMENU)NULL, hInstance, NULL);
// 등록된 윈도우 클래스를 기본으로 윈도우를 실제 생성함.
// 첫번째 인수 : 생성하고자 하는 윈도우의 클래스를 지정하는 문자열. 앞에서 정의한 WndClass구조체의 lpszClassName을 여기에 기입해주면 됨.
// 두번째 인수 : 윈도우의 타이틀바에 나타날 문자열. 프로그래머가 마음대로 지정. 위는 클래스명이랑 동일하게 지정.
// 세번째 인수 : 만들고자 하는 윈도우의 형태를 지정. WS_OVERLAPPEDWINDOW는 가장 무난한 윈도우 설정(시스템메뉴,최대최소버튼,타이틀바등).
// 네번째~일곱번째 인수 : 순서대로 X,Y, Width, Height. 즉, 윈도우의 크기와 위치를 지정하며 픽셀단위를 사용. CW_USEDEFAULT를 사용하면
//                        윈도우즈가 알아서 적당한 크기와 위치를 설정해 줌.
// 여덟번째 인수 : 부모 윈도우가 있을 경우 부모 윈도우의 핸들을 지정해 줌. 부모 윈도우가 없을 경우 NULL.
// 아홉번째 인수 : 윈도우에서 사용할 메뉴의 핸들을 지정. NULL 지정시 WndClass에서 지정해준 메뉴 사용.
// 열번째 인수 : 윈도우를 만드는 주체, 즉 프로그램의 핸들을 지정. WinMain의 인수로 전달된 hInstance를 대입해 주면 됨.
// 열한번재 인수 : CREATESTRUCT라는 구조체의 번지이며 특수한 목적에 사용됨. 보통은 NULL값을 사용.
ShowWindow(hWnd, nShowCmd);    // 생성된 윈도우를 화면에 출력해 줌. hwnd는 생성된 윈도우 핸들, nCmdShow는 윈도우를 화면에 출력하는 방법.

while(GetMessage(&Message,0,0,0)) {    // 시스템을 유지하는 메시지 큐에서 메시지를 읽음. 읽은 메시지는 첫번째 인수가 지정하는 MSG구조체에
// 저장. 이 메시지가 프로그램을 종료하라는 WM_QUIT일 경우 False를 리턴. 그 외의 메시지는 True.
// 즉 프로그램이 종료될때가지 실행. 나머지 인수들은 읽어들일 메시지의 범위를 지정하는데 잘 사용안함.
TranslateMessage(&Message);        // 키보드 입력 메시지를 가공하여 프로그램에서 쉽게 쓸 수 있도록 해 줌. WM_CHAR이라는 메시지 작성.
DispatchMessage(&Message);        // 시스템 메시지 큐에서 꺼낸 메시지를 프로그램의 메시지 처리함수(WndProc)로 전달함. 이 함수에 의해
// 메시지가 프로그램으로 전달되며 프로그램에서는 전달된 메시지를 점검하여 다음 동작을 결정함.
}    // 실제 메시지 처리는 별도의 메시지 처리함수 WndProc에서 수행함.

//freeStage(&ground);

return Message.wParam;
}

// 메시지 처리 함수. 메시지가 입력되면 윈도우즈에 의해 호추로디어 메시지를 처리. 이처럼 운영체제에 의해 호출되는 응용 프로그램내의 함수를
// 콜백(CallBack) 함수라 함. 인수는 MSG 구조체의 맴버와 동일하며 각각 윈도우 핸들, 메시지종류, iMessage의 메시지에 따른 부가적인 정보를 가짐.
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
PAINTSTRUCT ps;
char cTotal_move[5];
HDC hdc;

switch(iMessage) {
case WM_PAINT:            // 화면을 다시 그려야 할 필요가 있을 때 발생.
hdc = BeginPaint(hWnd, &ps);
printStage(&ground, hdc);
EndPaint(hWnd, &ps);
if(isFinish(&ground)) {        // 스테이지 완료했는지 검사
strcat(msgString, itoa(total_move, cTotal_move, 10));
strcat(msgString, ” movement(s)”);
MessageBox(NULL, msgString, ”Stage completed”, MB_OK);
PostQuitMessage(0);    // WM_QUIT 메시지를 전달. 프로그램 종료.
}
return 0;
case WM_KEYDOWN:        // 키보드가 눌려졌을 때 발생.
switch(wParam) {
case VK_LEFT:    key_value = ’L’;    break;
case VK_RIGHT:    key_value = ’R’;    break;
case VK_UP:        key_value = ’U’;    break;
case VK_DOWN:    key_value = ’D’;    break;
default:        key_value = ’ ’;
}                                    // 누른 키 값을 가져옴
total_move++;                        // 플레이어 이동수 누적
movement(&ground, key_value);        // 키 값에 따른 플레이어, 상자 이동
InvalidateRect(hWnd, NULL, TRUE);    // 화면을 다시 그리라고 명령함.
return 0;
case WM_DESTROY:        // 윈도우가 메모리에서 파괴될 때 발생.
PostQuitMessage(0);    // WM_QUIT 메시지를 전달.
return 0;
case WM_CREATE:            // 윈도우가 처음 만들어질 때 발생.
initStage(&ground);    // 스테이지 정보 초기화
cur_x = ground.player_x; cur_y = ground.player_y;    // 플레이어 위치 옴겨담음
MessageBox(NULL, ”Left : ←, Right : →, Up : ↑, Down :
↓\n\nYou click OK to start trying to please”, ”Information”, MB_OK);
return 0;
}

return(DefWindowProc(hWnd, iMessage, wParam, lParam));    // WndProc에서 처리하지 않은 나머지 메시지에 관한 처리를 해줌.
}

// 스테이지 초기화
void initStage(stage* new_stage)
{
int i, j;

// 스테이지 넓이, 높이 지정
new_stage->height = 9;
new_stage->width = 11;

// 스테이지 맵 세팅(0 = 빈공간, 1 = 벽, 2 = 상자, 5 = 유저, 8 = 상자구멍)
int stage_array[] = {
1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,8,1,1,1,1,1,1,
1,1,1,1,0,1,1,1,1,1,1,
1,1,1,0,2,0,0,2,0,8,1,
1,8,0,2,0,5,0,1,1,1,1,
1,1,1,1,0,0,0,1,1,1,1,
1,1,1,1,1,2,1,1,1,1,1,
1,1,1,1,1,8,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1};

// 현재 스테이지를 표시할 메모리 할당
new_stage->array = (int*)malloc(sizeof(int) * new_stage->height * new_stage->width);

// 스테이지 복사
memcpy(new_stage->array, stage_array, sizeof(int) * new_stage->height * new_stage->width);

// 최초 스테이지 상태를 표시할 메모리 할당
new_stage->array_ori = (int*)malloc(sizeof(int) * new_stage->height * new_stage->width);

// 스테이지 복사
memcpy(new_stage->array_ori, stage_array, sizeof(int) * new_stage->height * new_stage->width);

new_stage->num_goal = 0;    // 상자구멍의 수를 담아둘 변수 초기화
// 최초 스테이지에서 상자구멍이 아닌 곳은 다 빈공간으로 표시 수정
for(i=0;i<new_stage->width * new_stage->height;i++)
if(new_stage->array_ori[i] != P_HOLE)
new_stage->array_ori[i] = P_BLANK;
else
new_stage->num_goal++;    // 상자구멍 수를 계산

return;
}

// 화면에 스테이지를 그려줌
void printStage(stage* new_stage, HDC hdc)
{
int i, j;
int positionX, positionY;
HDC MemDC;
static bool flag = true;

// 비트맵 선언
HBITMAP hBlank = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BLANK));
HBITMAP hBlock = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BLOCK));
HBITMAP hWall = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_WALL));
HBITMAP hPlayer = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_PLAYER));
HBITMAP hHole = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_HOLE));
HBITMAP hFill = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_FILL));
// LoadBitmap – 비트맵을 읽어옴. 첫번째 인수 : 비트맵리소스를 가진 인스턴스 핸들, 두번째 인수 : 비트맵 리소스

MemDC = CreateCompatibleDC(hdc);    // 인수로 받은 화면 DC핸들을 동일한 특성을 가진 메모리 DC를 만들어 핸들값을 리턴해 줌.

for(i=0;i<new_stage->height;i++) {
for(j=0;j<new_stage->width;j++) {
positionX = i * 33;
positionY = j * 33;

// 스테이지를 특수문자와 색을 지정해서 화면에 그려줌
switch(new_stage->array[i * new_stage->width + j]) {
case P_BLANK:    SelectObject(MemDC, hBlank);    break;
case P_WALL    :    SelectObject(MemDC, hWall);        break;
case P_BLOCK:    SelectObject(MemDC, hBlock);    break;
case P_PLAYER:    SelectObject(MemDC, hPlayer);
if(flag == true) {
new_stage->player_x = j;
new_stage->player_y = i;
new_stage->array[i * new_stage->width + j] = P_BLANK;    // 스테이지 맵에서 유저를 빈공간으로 수정.
flag = false;
}
break;
case P_HOLE:    SelectObject(MemDC, hHole);        break;
case P_FILL:    SelectObject(MemDC, hFill);        break;
}
BitBlt(hdc, positionY, positionX, 32, 32, MemDC, 0, 0, SRCCOPY);
// DC간의 영역끼리 고속복사를 수행. 첫번재 인수 : 복사 대상 DC, 두번째~다섯번째 인수 : XY좌표,넓이,높이,
// 여섯번째 인수 : 복사원 DC, 일곱번째~여덟번째 인수 : 복사원 XY좌표, 아홉번째 인수 : 레스터 연산방법(SRCCOPY:그대로복사).
}
}

DeleteDC(MemDC);

return;
}

// 키 값에 따른 플레이어, 상자 이동
bool movement(stage* cur_stage, char direction)
{
int new_x, new_y, block_x, block_y;
int next_player_point, next_block_point;

// 입력된 키 값에 따라서 플레이어, 상자 움직일 예상 위치지정
switch(direction) {
case ’L’:
new_x = cur_stage->player_x – 1;
new_y = cur_stage->player_y;
block_x = cur_stage->player_x – 2;
block_y = cur_stage->player_y;
break;
case ’U’:
new_x = cur_stage->player_x;
new_y = cur_stage->player_y – 1;
block_x = cur_stage->player_x;
block_y = cur_stage->player_y – 2;
break;
case ’D’:
new_x = cur_stage->player_x;
new_y = cur_stage->player_y + 1;
block_x = cur_stage->player_x;
block_y = cur_stage->player_y + 2;
break;
case ’R’:
new_x = cur_stage->player_x + 1;
new_y = cur_stage->player_y;
block_x = cur_stage->player_x + 2;
block_y = cur_stage->player_y;
break;
default:
return false;
}

// 플레이어, 상자가 이동할 예상 위치에 무엇이 존재하는지 가져옴
next_player_point = getPoint(cur_stage, new_x, new_y);
next_block_point = getPoint(cur_stage, block_x, block_y);

// 다음 플레이어 예상 위치가 벽이라면 이동 실패
if(next_player_point == P_WALL) return false;
// 다음 플레이어 예상 위치가 상자 또는 상자구멍속의 상자라면 상자의 다음 예상 위치 체크
else if(next_player_point == P_BLOCK || next_player_point == P_FILL) {
// 상자의 다음 예상 위치가 벽, 상자, 상자구멍속의 상자라면 이동 실패
if(next_block_point == P_WALL || next_block_point == P_BLOCK || next_block_point == P_FILL) return false;
// 상자의 다음 예상 위치가 구멍이라면
else if(next_block_point == P_HOLE) {
setPoint(cur_stage, block_x, block_y, P_FILL);    // 상자의 다음 예상 위치를 상자구멍속 상자로 지정
setPoint(cur_stage, new_x, new_y, P_PLAYER);    // 플레이어 위치 이동
// 플레이어가 지나간 자리에 원래 존재하던 것으로 복구(빈공간 or 상자구멍)
setPoint(cur_stage, cur_stage->player_x, cur_stage->player_y,
BlankOrFill(cur_stage, cur_stage->player_x, cur_stage->player_y));
}
// 상자의 다음 예상 위치가 빈공간이라면
else if(next_block_point == P_BLANK) {
setPoint(cur_stage, block_x, block_y, P_BLOCK);    // 상자의 다음 예상 위치를 상자로 지정
setPoint(cur_stage, new_x, new_y, P_PLAYER);    // 플레이어의 다음 예상위치를 플레이어로 지정
// 플레이어가 지나간 자리에 원래 존재하던 것으로 복구(빈공간 or 상자구멍)
setPoint(cur_stage, cur_stage->player_x, cur_stage->player_y,
BlankOrFill(cur_stage, cur_stage->player_x, cur_stage->player_y));
}
}
// 플레이어의 다음 예상 위치가 빈공간 or 상자구멍이라면
else if(next_player_point == P_BLANK || next_player_point == P_HOLE) {
setPoint(cur_stage, new_x, new_y, P_PLAYER);    // 플레이어의 다음 예상 위치에 플레이어로 지정
// 플레이어가 지나간 자리에 원래 존재하던 것으로 복구(빈공간 or 상자구멍)
setPoint(cur_stage, cur_stage->player_x, cur_stage->player_y,
BlankOrFill(cur_stage, cur_stage->player_x, cur_stage->player_y));
}

// 스테이지 정보가 가지고 있는 플레이어 좌표 수정
cur_stage->player_x = new_x;
cur_stage->player_y = new_y;

return true;
}

// 전달받은 좌표에 뭐가 존재하는지 알려줌.
int getPoint(stage* cur_stage, int x, int y)
{
return cur_stage->array[y * cur_stage->width + x];
}

// 전달받은 좌표에 전달받은 식별번호를 스테이지 정보에 저장
void setPoint(stage* cur_stage, int x, int y, int block)
{
cur_stage->array[y * cur_stage->width + x] = block;
return;
}

// 최초 스테이지에서 전달받은 좌표에 위치한 것을 반환(빈공간 or 상자구멍)
int    BlankOrFill(stage* cur_stage, int x, int y)
{
return cur_stage->array_ori[y * cur_stage->width + x];
}

// 상자를 구멍에 다 채웠는지 체크
bool isFinish(stage* cur_stage)
{
int num_goal=0;        // 현제 채워진 구멍수 카운트

// 현제스테이지에서 상자가 채워진 구멍의 수 확인
for(int i=0;i<cur_stage->height * cur_stage->width;i++)
if(cur_stage->array[i] == 9) num_goal++;

// 초기 스테이지의 구멍수랑 같다면 true.. 즉, Game Finish..
if(num_goal == cur_stage->num_goal) return true;

return false;
}

_M#]

댓글 남기기