본문 바로가기
Development/WinAPI

[WinAPI, 윈도우 시스템 프로그래밍] 뱀 게임 구현하기

by 최호희 2023. 4. 16.

Visual Studio 2010를 사용한 Win32 프로젝트이며 간단한 뱀 게임을 구현해보겠습니다.

주요 변수는 다음과 같습니다.

  • BSIZE: 음식과 뱀 머리의 충돌 판정을 위한 범위
  • BS: 뱀의 몸 사이즈
  • fColor: 음식의 색상
  • food_x, food_y: 음식의 위치
  • snake_x, snake_y: 뱀의 몸통 위치
  • MOVE: 뱀의 이동 방향
  • start: 게임 시작 여부
  • length: 뱀의 길이
  • eat: 먹은 음식 개수

주요 함수는 다음과 같습니다.

  • Length(): 두 점 사이의 거리 계산
  • correct(): 음식과 뱀 머리 충돌 판정
  • Food_Random(): 음식의 위치 랜덤 생성
  • Snake(): 뱀 몸통 출력

 

WndProc 함수에서는 게임의 기능들이 구현됩니다. WM_CREATE 메시지에서는 초기화 작업이 이루어지며, WM_KEYDOWN 메시지에서는 키보드 입력을 처리합니다. WM_TIMER 메시지에서는 뱀의 움직임을 제어하고, 먹이를 먹은 경우 점수를 증가시킵니다. 게임은 윈도우가 생성될 때 타이머가 두 개 생성됩니다. 타이머 1은 뱀을 움직이는 역할을 하며, 타이머 2는 뱀이 몸을 길게 만드는 역할을 합니다. 뱀의 이동은 WM_KEYDOWN 이벤트를 통해 방향키로 조작합니다. 뱀의 머리와 음식이 충돌하면 뱀의 길이를 늘리고 새로운 음식을 생성합니다. 뱀의 머리가 벽에 닿거나 몸통과 충돌하면 게임이 종료됩니다.

"Press the 's' KEY"는 게임 시작 시 사용자가 게임을 시작하려면 's' 키를 눌러야 한다는 지시사항입니다. 게임을 시작할 때 이러한 지시사항을 표시하는 것은 게임 디자인의 일부입니다. 이렇게하면 사용자가 게임을 시작하기 전에 게임 규칙과 조작 방법을 이해하고 시작할 수 있습니다.

게임이 시작되면, 윈도우 창이 열리고, 뱀과 음식이 랜덤으로 생성됩니다. 타이머가 생성되어 뱀이 이동하고, 키보드 입력에 따라 뱀의 방향이 바뀝니다. 뱀이 음식을 먹으면 뱀의 길이가 늘어나고, 음식이 다시 랜덤으로 생성됩니다.

 

 

게임 오버 조건은 뱀의 머리가 벽 또는 자기 자신의 몸과 충돌할 때입니다. 이러한 조건이 충족되면 게임은 종료되며 "게임 오버" 메시지가 나타납니다. 게임 오버를 처리하는 방법은 WM_TIMER 메시지 처리 루프에서 확인할 수 있습니다. 각각의 움직임 타이머마다 먼저 충돌 검사를 수행하여 뱀의 머리가 벽 또는 자신의 몸과 충돌했는지 확인합니다. 이를 위해 뱀의 머리와 몸통의 좌표를 비교합니다.만약 충돌이 발생하면 KillTimer 함수를 사용하여 모든 타이머를 중지하고 MessageBox 함수를 사용하여 "게임 오버" 메시지를 출력합니다. 게임이 종료되면 PostQuitMessage 함수를 사용하여 메시지 루프를 종료합니다.

 

아래는 뱀게임을 코드로 구현한 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#include <windows.h>
#define BSIZE 15 //
#define BS 14    //몸 사이즈
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg,
    WPARAM wParam, LPARAM lParam);
 
 
LPCTSTR lpszClass = TEXT("SNAKE GAME");
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    HWND    hwnd;
    MSG        msg;
    WNDCLASS WndClass;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    WndClass.lpfnWndProc = WndProc;
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hInstance = hInstance;
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    WndClass.lpszMenuName = NULL;
    WndClass.lpszClassName = lpszClass;
    RegisterClass(&WndClass);
 
    hwnd = CreateWindow(lpszClass,
        lpszClass,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        600,
        600,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
 
    while (GetMessage(&msg, NULL, 00))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}
 
int Length(int food, int snake) {
    if (food >= snake)
        return (food - snake);
    else
        return (food - snake) * (-1);
}
bool correct(int food_x, int food_y, int snake_x, int snake_y) {
 
    if (Length(food_x, snake_x) < BSIZE && Length(food_y, snake_y) < BSIZE) {
 
        return TRUE;
    }
    else {
        return FALSE;
    }
}
 
//음식 랜덤 생성
void Food_Random(int* x, int* y) {
    for (int i = 0; i < 10; i++) {
        x[i] = (rand() % 500);
        y[i] = (rand() % 500);
    }
}
 
//뱀의 몸 출력
void Snake(HDC hdc, int lengthint* x, int* y) {
    for (int i = 0; i < length; i++)
        Rectangle(hdc, x[i] - BS / 2, y[i] - BS / 2, x[i] + BS / 2, y[i] + BS / 2);
}
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg,
    WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static COLORREF fColor;
    static int food_x[10];
    static int food_y[10];
    static int snake_x[50];
    static int snake_y[50];
    static int MOVE;
    static int start, length, eat;
    static char ch;
 
    switch (iMsg) {
    case WM_CREATE:
        fColor = RGB(000);
        // 타이머 생성 및 시작
        SetTimer(hwnd, 1300, NULL);    //1 블럭 이동
        SetTimer(hwnd, 2500, NULL);    //0.5초마다 꼬리가 1개씩 증가
        snake_x[0= snake_y[0= 100;
 
        eat = 0;
        start = -1;
        length = 1;
        Food_Random(food_x, food_y);
        MOVE = 2;
        break;
 
    case WM_KEYDOWN:
        if (wParam == VK_LEFT)
            MOVE = 1;
        else if (wParam == VK_RIGHT)
            MOVE = 2;
        else if (wParam == VK_UP)
            MOVE = 3;
        else if (wParam == VK_DOWN)
            MOVE = 4;
        break;
 
    case WM_TIMER:
        int i;
        // 게임이 시작했을 때 타이머 1번(뱀 움직임용 타이머)
        if (wParam == 1 && start == 1) {
            if (MOVE == 1) {
                for (i = 0; i < length; i++) {
                    snake_x[length - i] = snake_x[length - i - 1];
                    snake_y[length - i] = snake_y[length - i - 1];
                }
                snake_x[0-= BS;    //뱀의 머리 x좌표를 BS만큼 -
            }
            if (MOVE == 2) {
                for (i = 0; i < length; i++) {
                    snake_x[length - i] = snake_x[length - i - 1];
                    snake_y[length - i] = snake_y[length - i - 1];
                }
                snake_x[0+= BS;    //뱀의 머리 x좌표를 BS만큼 +
            }
            if (MOVE == 3) {
                for (i = 0; i < length; i++) {
                    snake_x[length - i] = snake_x[length - i - 1];
                    snake_y[length - i] = snake_y[length - i - 1];
                }
                snake_y[0-= BS;    //뱀의 머리 y좌표를 BS만큼 -
            }
            if (MOVE == 4) {
                for (i = 0; i < length; i++) {
                    snake_x[length - i] = snake_x[length - i - 1];
                    snake_y[length - i] = snake_y[length - i - 1];
                }
                snake_y[0+= BS;    //뱀의 머리 y좌표를 BS만큼 +
            }
 
            if (snake_x[0< 7// 벽에 부딫혔을 때 게임 종료
                start = 0;
            else if (snake_x[0> 593// 벽에 부딫혔을 때 게임 종료
                start = 0;
            else if (snake_y[0< 7// 벽에 부딫혔을 때 게임 종료
                start = 0;
            else if (snake_y[0> 555// 벽에 부딫혔을 때 게임 종료
                start = 0;
        }
        if (wParam == 2 && start == 1) {
            length++;
        }
 
        for (int i = 0; i < 10; i++) {
            if (correct(food_x[i], food_y[i], snake_x[0], snake_y[0])) {
                food_x[i] = food_y[i] = 1000;
                eat++;
            }
        }
        
        if (eat == 9)
            start = 0;
        if (length == 50)
            start = 2;
 
        InvalidateRect(hwnd, NULL, TRUE);
        break;
 
    case WM_CHAR:    //s누르면 시작 e누르면 종료
        if (wParam == 115) {
            start = 1;
        }
        else if (wParam == 101) {
            start = 0;
        }
        break;
 
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        TCHAR tmp[20];
        SetTextColor(hdc, fColor);
        if (start == -1)
            TextOut(hdc, 200250"----- Press the 's' KEY -----"28);
 
        else if (start == 1) {    //s누르고 시작할 때 먹이 랜덤 출력
            for (int i = 0; i < 9; i++) {
                TextOut(hdc, food_x[i], food_y[i], "T"1);
            }
            Snake(hdc, length, snake_x, snake_y);
            wsprintf(tmp,"SCORE: %d", eat);
            TextOut(hdc,49010, tmp,lstrlen(tmp));
        }
        else if (start == 0) {
            TextOut(hdc, 250250"GAME OVER"9);
        }
        else if (start == 2) {
            TextOut(hdc, 250250"TIME OVER"9);
        }
 
        EndPaint(hwnd, &ps);
        ReleaseDC(hwnd, hdc);
    
        break;
 
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    }
    return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
cs