-
스네이크 게임, Win32일상/ssafy 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); }
'일상 > ssafy' 카테고리의 다른 글
특화 0806 (2) 2024.08.08 Jira (2) 2024.07.03 Faiss 활용 유사어 검색 (0) 2024.06.27 프로시저, Procedure (0) 2024.06.27 Wireshark 실습 (0) 2024.06.27