ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스네이크 게임, 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
Designed by Tistory.