일상/ssafy
스네이크 게임, Win32
래울
2024. 6. 28. 01:45
내용
기본적인 스네이크 게임 기능
게임시간에 비례해서 난이도 올라감
조작키
S: Start
P: Pause
방향키: 조작
실행결과
코드
#include <stdio.h>
#include <iostream>
#include <vector>
#include <deque>
#include <Windows.h>
#include "resource.h"
// 공간 가로 세로
#define WIDTH 20
#define HEIGHT 20
// 블록 크기 픽셀 크기
#define TILESIZE 24
#define RESOURCESIZE 4
#define LINF 999999999999999999
// debug flag
int DEBUGFLAG = 0;
// Window는 한번의 실행으로 끝나는 것이 아니라 윈도우가 종료될 때까지의 무한 루프의 메시지가 필요하기 때문에 callback 함수를 사용한다. 메시지 처리 함수.
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
HWND hWndMain;
// 포인트 구조체
struct Point {
int x, y;
};
int dy[4] = { -1, 0, 1, 0 };
int dx[4] = { 0, 1, 0, -1 };
int snakeDir;
int turnFlag = 1; // 회전이 한번에 여러번 일어나는 것을 배제
int score = 0;
time_t startTime = LINF;
// 보드판 크기
int board[WIDTH + 2][HEIGHT + 2];
std::deque<Point> snake;
enum tag_Status { GAMEOVER, RUNNING, PAUSE };
tag_Status GameStatus;
int Interval;
HBITMAP hBit[10];
void InitGame();
void TurnSnake(int _d);
int MoveSnake();
void AppearStar();
void DrawScreen(HDC hdc);
void PrintTile(HDC hdc, int x, int y, int c);
void DrawBitmap(HDC hdc, int x, int y, HBITMAP hBit);
int GetRunSenconds();
// 실행 함수
int main() {
// 프로그램 인스턴스 핸들 취득.
// Window 프로그램은 하나의 인스턴스 윈도우를 OS에 등록을 해야 한다.
HINSTANCE hInstance = GetModuleHandle(NULL);
g_hInst = hInstance;
// 윈도우 핸들
HWND hWnd;
// 윈도우 메시지
MSG Message;
// 윈도우를 정의하는 구조체
WNDCLASS WndClass;
// 윈도우의 초기 설정하는 값
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
// 윈도우의 메인 바탕색
WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
// 윈도우의 기본 마우스 커서 형태
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
// 윈도우의 아이콘
WndClass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
// 윈도우의 인스턴스
WndClass.hInstance = hInstance;
// 윈도우의 메시지 callbak 함수
WndClass.lpfnWndProc = WndProc;
// 윈도우를 인식하기 위한 클래스 명
WndClass.lpszClassName = L"Snake";
// 윈도우 메뉴 이름
WndClass.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
// 윈도우 스타일
WndClass.style = 0;
// OS에 윈도우를 등록한다.
// 윈도우라는 것은 결국 Window OS에서 실행하는 것인데 다른 프로그램들과 동시에 관리하기 위해서는 OS가 Window의 정보를 알고 있어야 한다.
// 그를 위해 등록한다.
RegisterClass(&WndClass);
// 윈도우 생성
hWnd = CreateWindow(WndClass.lpszClassName, WndClass.lpszClassName, WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, NULL, (HMENU)NULL, hInstance, NULL);
// 이제 윈도우를 띄운다.
ShowWindow(hWnd, SW_SHOWDEFAULT);
HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1));
// 메시지 루프 생성
while (GetMessage(&Message, NULL, 0, 0)) {
if (!TranslateAccelerator(hWnd, hAccel, &Message)) {
// 키보드 메시지를 받고 프로그램에서 사용할 수 있게 변환한다.
TranslateMessage(&Message);
// 메시지를 WndProc로 보내는 함수.
DispatchMessage(&Message);
}
}
return 0;
}
// 메시지 처리 함수, 메시지를 처리하는 함수로 메시지란 유저의 입력 또는 OS에서 요구하는 메시지 등을 처리하는 함수
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
int i;
RECT crt;
int ret;
HDC hdc;
PAINTSTRUCT ps;
// 윈도우를 생성할 때의 구조, main에서 CreateWindowEx로 넣었던 파라미터 값이 있다.
// 다른 윈도우를 생성하려면 인스턴스가 필요한데 여기서 취득한다.
CREATESTRUCT* cs = (CREATESTRUCT*)lParam;
switch (iMessage) {
// 최초 윈도우가 생성되면 호출된다.
case WM_CREATE:
hWndMain = hWnd;
// 윈도우 크기 설정
SetRect(&crt, 0, 0, (WIDTH + 10) * TILESIZE, (HEIGHT + 2) * TILESIZE);
AdjustWindowRect(&crt, WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, TRUE);
SetWindowPos(hWndMain, NULL, 0, 0, crt.right - crt.left, crt.bottom - crt.top, SWP_NOMOVE | SWP_NOZORDER);
// 최초 상태
GameStatus = GAMEOVER;
// 랜덤 틱 설정
srand(GetTickCount());
// 리소스 로드
for (i = 0; i < 4; i++) {
// 104 105 106
hBit[i] = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1 + i));
}
InitGame();
return 0;
// 윈도우에서 명령이 발생하면 여기로 호출
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_GAME_START:
// 게임 시작
if (GameStatus != GAMEOVER) {
break;
}
// 게임 상태
GameStatus = RUNNING;
startTime = time(NULL);
// 시간이 지남에 따라 점점 속도가 빨라짐
// 최대 0.1s
Interval = max(100, 300 - GetRunSenconds());
SetTimer(hWnd, 1, Interval, NULL);
break;
case ID_GAME_PAUSE:
// 일시 정지
if (GameStatus == RUNNING) {
GameStatus = PAUSE;
KillTimer(hWnd, 1);
}
else if (GameStatus == PAUSE) {
GameStatus = RUNNING;
SetTimer(hWnd, 1, Interval, NULL);
}
break;
case ID_GAME_EXIT:
// 종료
DestroyWindow(hWnd);
break;
}
return 0;
case WM_TIMER:
ret = MoveSnake();
if (ret == 0)
{
//common
InvalidateRect(hWndMain, NULL, FALSE);
}
else if (ret == 1)
{
//eat star
InvalidateRect(hWndMain, NULL, FALSE);
}
else if (ret == 2)
{
//game over
KillTimer(hWndMain, 1);
GameStatus = GAMEOVER;
MessageBox(hWndMain, TEXT("Game Over !!!"), TEXT("MSG"), MB_OK);
}
return 0;
case WM_KEYDOWN:
if (GameStatus != RUNNING) {
return 0;
}
switch (wParam) {
case VK_LEFT:
TurnSnake(3);
InvalidateRect(hWnd, NULL, FALSE);
break;
case VK_RIGHT:
TurnSnake(1);
InvalidateRect(hWnd, NULL, FALSE);
break;
case VK_UP:
TurnSnake(0);
InvalidateRect(hWnd, NULL, FALSE);
break;
case VK_DOWN:
TurnSnake(2);
InvalidateRect(hWnd, NULL, FALSE);
break;
}
return 0;
case WM_PAINT:
// 윈도우 draw 이벤트
hdc = BeginPaint(hWnd, &ps);
// 윈도우에 그리기
DrawScreen(hdc);
EndPaint(hWnd, &ps);
return 0;
case WM_DESTROY:
KillTimer(hWndMain, 1);
for (i = 0; i < RESOURCESIZE; i++) {
DeleteObject(hBit[i]);
}
PostQuitMessage(0);
return 0;
}
return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
void InitGame()
{
// 기본 보드판 설정.
for (int x = 0; x < WIDTH + 2; x++) {
for (int y = 0; y < HEIGHT + 2; y++) {
// wall : 1, empty : 0
board[x][y] = (y == 0 || y == HEIGHT + 1 || x == 0 || x == WIDTH + 1) ? 1 : 0;
}
}
snake.clear();
int range = 5;
int x = ((1 + WIDTH) / 2 - range) + rand() % (range * 2); //1~WIDHT
int y = ((1 + HEIGHT) / 2 - range) + rand() % (range * 2); //1~HEIGHT
snake.push_back({ x, y });
for (int i = 0; i < snake.size(); ++i)
{
Point curr = snake[i];
board[curr.x][curr.y] = 2;
}
snakeDir = rand() % 4;
turnFlag = 0;
AppearStar();
return;
}
void TurnSnake(int _d)
{
// 진행 방향으로는 ㄴㄴ
if (_d == 0 && snakeDir == 2) return;
if (_d == 1 && snakeDir == 3) return;
if (_d == 2 && snakeDir == 0) return;
if (_d == 3 && snakeDir == 1) return;
if (turnFlag) return;
snakeDir = _d;
turnFlag = 1;
return;
}
int MoveSnake()
{
int ret = 0;
Point snakeHead = snake[0];
int updateX = snakeHead.x + dx[snakeDir];
int updateY = snakeHead.y + dy[snakeDir];
int meetStar = 0;
if (updateX < 1 || updateY < 1 || updateX > WIDTH || updateY > HEIGHT)
{
// out of bound
return 2;
}
if (board[updateX][updateY] == 1 || board[updateX][updateY] == 2)
{
//1 : 벽
//2 : snake
return 2;
}
//can move
turnFlag = 0;
if (board[updateX][updateY] == 3)
{
meetStar = 1;
}
if (meetStar == 0)
{
if (DEBUGFLAG) std::cout << "get star !!!!\n";
board[snake.back().x][snake.back().y] = 0;
snake.pop_back();
}
else
{
score++;
AppearStar();
}
board[updateX][updateY] = 2;
snake.push_front({ updateX, updateY });
if(DEBUGFLAG) std::cout << "xy: " << updateX << ", " << updateY << "\n";
return ret;
}
void AppearStar()
{
int x = WIDTH / 2;
int y = HEIGHT / 2;
while (true)
{
x = 1 + rand() % WIDTH;
y = 1 + rand() % HEIGHT;
if (board[x][y] == 0) break;
}
board[x][y] = 3;
return;
}
void DrawScreen(HDC hdc) {
/*
PrintTile : x, y, i번째 타일로 그리기
*/
for (int i = 0; i < HEIGHT + 2; ++i)
{
for (int j = 0; j < WIDTH + 2; ++j)
{
PrintTile(hdc, j, i, board[j][i]);
}
}
// 점수판 그리기
TCHAR str[128];
int currTime = GetRunSenconds();
int currScore = currTime + score * 50;
lstrcpy(str, TEXT("Snake Game"));
TextOut(hdc, (WIDTH + 4) * TILESIZE, 30, str, lstrlen(str));
wsprintf(str, TEXT("Score : %d "), currScore);
TextOut(hdc, (WIDTH + 4) * TILESIZE, 60, str, lstrlen(str));
wsprintf(str, TEXT("Time : %d "), currTime);
TextOut(hdc, (WIDTH + 4) * TILESIZE, 80, str, lstrlen(str));
}
// 타일을 bitmap으로 그리기
void DrawBitmap(HDC hdc, int x, int y, HBITMAP hBit) {
HDC MemDC;
HBITMAP OldBitmap;
int bx, by;
BITMAP bit;
MemDC = CreateCompatibleDC(hdc);
OldBitmap = (HBITMAP)SelectObject(MemDC, hBit);
GetObject(hBit, sizeof(BITMAP), &bit);
bx = bit.bmWidth;
by = bit.bmHeight;
BitBlt(hdc, x, y, bx, by, MemDC, 0, 0, SRCCOPY);
SelectObject(MemDC, OldBitmap);
DeleteDC(MemDC);
}
void PrintTile(HDC hdc, int _x, int _y, int index) {
DrawBitmap(hdc, _x * TILESIZE, _y * TILESIZE, hBit[index]);
return;
}
int GetRunSenconds()
{
int ret = (time(NULL) - startTime);
return max(ret, 0);
}