Игра Win32 Snake

Ниже я сделал тривиальную игру-змейку, чтобы попрактиковаться в программировании на C ++ и Win32.

/* g++ snake.cpp -o snake -lgdi32 -Wl,-subsystem,windows */

#include <deque>
#include <vector>

#include <cstdio>
#include <ctime>

#include <Windows.h>

#define ID_TIMER 1

constexpr int WND_WIDTH = 500;
constexpr int WND_HEIGHT = 500;

struct Cell
{
    int row, col;
    Cell(int row, int col) : row(row), col(col)
    {}
};

class Snake
{
public:
    Snake(int rows, int cols);
    ~Snake();
    void Update(HWND hWnd);
    void DrawBitmap(HDC hdc, RECT *rect, BITMAPINFO info);
    void HandleKey(WPARAM wParam);
    int GetScore();
private:
    enum Direction { UP = 0, DOWN, LEFT, RIGHT };
    enum CellType { EMPTY = 0, SNAKE, FOOD };
    
    std::vector<BYTE> pixels;
    std::deque<Cell> snake;
    
    int rows;
    int cols;
    int dir;
    
    void MakeFood();
    void SetCell(int row, int col, CellType type);
    CellType GetCellType(int row, int col);
    Cell Move(const Cell& cell);
};

Snake::Snake(int rows, int cols)
  : rows(rows), cols(cols)
{
    pixels.resize(rows * cols * 4);
    srand(static_cast<unsigned int>(time(NULL)));
    Snake::MakeFood();
    snake.push_back(Cell(rows / 2, cols / 2));
    dir = UP;
}

Snake::~Snake()
{}

void Snake::Update(HWND hWnd)
{
    snake.push_back(Snake::Move(snake.back()));
    
    Cell head = snake.back();
    bool eaten = false;
    
    if (Snake::GetCellType(head.row, head.col) == FOOD)
    {
        snake.push_back(Snake::Move(head));
        Cell newHead = snake.back();
        Snake::SetCell(newHead.row, newHead.col, SNAKE);
        eaten = true;
    }
    else if (Snake::GetCellType(head.row, head.col) == SNAKE)
    {
        char buff[200];
        snprintf(buff, sizeof(buff), "Score: %d", Snake::GetScore());
        KillTimer(hWnd, ID_TIMER);
        MessageBox(hWnd, TEXT(buff), "Game Over!", MB_OK | MB_ICONINFORMATION);
        DestroyWindow(hWnd);
    }
    
    Snake::SetCell(head.row, head.col, SNAKE);
    
    Cell tail = snake.front();
    snake.pop_front();
    Snake::SetCell(tail.row, tail.col, EMPTY);
    
    if (eaten) Snake::MakeFood();
}

void Snake::DrawBitmap(HDC hdc, RECT *rect, BITMAPINFO info)
{
    int width = rect->right - rect->left;
    int height = rect->bottom - rect->top;
    
    StretchDIBits(hdc,
                  0,
                  0,
                  width,
                  height,
                  0,
                  0,
                  cols,
                  rows,
                  &pixels[0],
                  &info,
                  DIB_RGB_COLORS,
                  SRCCOPY);
}

void Snake::HandleKey(WPARAM wParam)
{
    switch (wParam)
    {
    case VK_UP:
        dir = UP;
        break;
    case VK_DOWN:
        dir = DOWN;
        break;
    case VK_LEFT:
        dir = LEFT;
        break;
    case VK_RIGHT:
        dir = RIGHT;
        break;
    default:
        break;
    }
}

int Snake::GetScore()
{
    return snake.size();
}

void Snake::MakeFood()
{
    int row = rand() % rows;
    int col = rand() % cols;
    
    while (Snake::GetCellType(row, col) == SNAKE)
    {
        row = rand() % rows;
        col = rand() % cols;
    }
    
    Snake::SetCell(row, col, FOOD);
}

void Snake::SetCell(int row, int col, CellType type)
{
    int offset = row * cols * 4 + col * 4;
    
    switch (type)
    {
    case EMPTY:
        pixels[offset]     = 0;
        pixels[offset + 1] = 0;
        pixels[offset + 2] = 0;
        pixels[offset + 3] = 255;
        break;
    case SNAKE:
        pixels[offset]     = 0;
        pixels[offset + 1] = 255;
        pixels[offset + 2] = 0;
        pixels[offset + 3] = 255;
        break;
    case FOOD:
        pixels[offset]     = 0;
        pixels[offset + 1] = 0;
        pixels[offset + 2] = 255;
        pixels[offset + 3] = 255;
        break;
    default:
        break;
    }
}

Snake::CellType Snake::GetCellType(int row, int col)
{
    int offset = row * rows * 4 + col * 4;
    
    int red = pixels[offset + 2];
    int green = pixels[offset + 1];
    int blue  = pixels[offset];
    
    if (red == 0 && green == 0 && blue == 0) return EMPTY;
    if (red == 0 && green == 255 && blue == 0) return SNAKE;
    if (red == 255 && green == 0 && blue == 0) return FOOD;
    
    return EMPTY; // should never reach here
}

Cell Snake::Move(const Cell& cell)
{
    Cell c(cell.row, cell.col);
    
    switch (dir)
    {
    case UP: c.row += 1; break;
    case DOWN: c.row -= 1; break;
    case LEFT: c.col -= 1; break;
    case RIGHT: c.col += 1; break;
    }
    
    // teleportation
    if (c.row == -1)     c.row = rows - 1;
    if (c.row == rows)   c.row = 0;
    if (c.col == -1)     c.col = cols - 1;
    if (c.col == cols)   c.col = 0;
    
    return c;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    static BITMAPINFO info;
    static Snake *snake;
    
    switch (msg)
    {
    case WM_CREATE:
    {
        int rows = 40;
        int cols = 40;
        
        info.bmiHeader.biSize = sizeof(info.bmiHeader);
        info.bmiHeader.biWidth = cols;
        info.bmiHeader.biHeight = rows;
        info.bmiHeader.biPlanes = 1;
        info.bmiHeader.biBitCount = 32;
        info.bmiHeader.biCompression = BI_RGB;
        info.bmiHeader.biSizeImage = 0;
        info.bmiHeader.biXPelsPerMeter = 0;
        info.bmiHeader.biYPelsPerMeter = 0;
        info.bmiHeader.biClrUsed = 0;
        info.bmiHeader.biClrImportant = 0;
        
        snake = new Snake(rows, cols);
        
        if(!SetTimer(hWnd, ID_TIMER, 80, NULL))
        {
            MessageBox(hWnd, "Could not set timer!", "Error", MB_OK | MB_ICONEXCLAMATION);
            PostQuitMessage(1);
        }
        
        break;
    }
    case WM_PAINT:
    {
        RECT rcClient;
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        
        GetClientRect(hWnd, &rcClient);
        snake->DrawBitmap(hdc, &rcClient, info);
        
        EndPaint(hWnd, &ps);
        break;
    }
    case WM_TIMER:
    {
        RECT rcClient;
        HDC hdc = GetDC(hWnd);
        
        GetClientRect(hWnd, &rcClient);
        snake->Update(hWnd);
        snake->DrawBitmap(hdc, &rcClient, info);
        
        ReleaseDC(hWnd, hdc);
        break;
    }
    case WM_KEYDOWN:
        snake->HandleKey(wParam);
        break;
    case WM_CLOSE:
        DestroyWindow(hWnd);
        break;
    case WM_DESTROY:
        delete snake;
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }
    
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    const TCHAR szClassName[] = TEXT("MyClass");
    
    WNDCLASS wc;
    HWND hWnd;
    MSG msg;
    
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szClassName;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    
    if (!RegisterClass(&wc))
    {
        MessageBox(NULL, TEXT("Window Registration Failed!"), TEXT("Error!"),
            MB_ICONEXCLAMATION | MB_OK);
        return 1;
    }
    
    hWnd = CreateWindow(szClassName,
                        TEXT("Snake"),
                        WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, /* this window style prevents window resizing */
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        WND_WIDTH,
                        WND_HEIGHT,
                        NULL,
                        NULL,
                        hInstance,
                        NULL);
    
    if (hWnd == NULL)
    {
        MessageBox(NULL, TEXT("Window Creation Failed!"), TEXT("Error!"),
            MB_ICONEXCLAMATION | MB_OK);
        return 1;
    }
    
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    
    return (int) msg.wParam;
}

Я ищу способы улучшить качество и эффективность моего кода на C ++. Меня беспокоит, если я неправильно использую некоторые структуры данных. Растровое изображение Win32 — хороший выбор для представления игрового поля змейки?

Правильно ли я структурировал свой код? Есть ли способ улучшить организацию кода?

Разные отзывы приветствуются!

Я впервые прошу обзор кода на C ++. Надеюсь, я получу достойные отзывы.

1 ответ
1

#define ID_TIMER 1

Тот факт, что вы используете его с вызовами Win32 API, не меняет того факта, что MACROS == BAD, constexpr == good.

struct Cell
{
    int row, col;
    Cell(int row, int col) : row(row), col(col)
    {}
};

Ваш конструктор ничего не добавляет к тому, что может предложить встроенная инициализация агрегата, и его наличие предотвращает «тривиальность» класса. В этом случае лучше не использовать его. Вам просто нужно использовать унифицированную инициализацию в стиле C ++ 11 (фигурные скобки); например snake.push_back(Cell{rows / 2, cols / 2});. Но на самом деле имя класса здесь даже не нужно, и вы можете написать snake.push_back({rows / 2, cols / 2});.

В общем, вы нигде не используете единую инициализацию.

Snake::~Snake()
{}

Напишите в теле класса, ~Snake() = default; вместо. Так лучше по ряду причин.

Ваш Snake Класс использует автоматически сгенерированный конструктор копирования и оператор присваивания. Так что мне интересно, почему вы потрудились упомянуть деструктор, когда ему не нужно ничего, кроме того, что предоставляется автоматически. Вы имели в виду, чтобы класс можно было дублировать и назначать?

static Snake *snake;
 ...
 snake = new Snake(rows, cols);

Почему вы используете new вместо использования переменной этого типа?
rows а также cols объявлены локально, но не const или же constexpr, хотя они кажутся константами.

В классе нет ничего, что требовало бы задержки его создания до сообщения WM_CREATE. Просто объявите его с инициализатором как глобальную переменную.


Необработанное использование функций и типов Win32 API в других программах на C ++ жестоко. Я всегда добавлял обертки и еще много чего, чтобы улучшить ситуацию. Например, используйте RAII / RFID для различных типов РУЧКИ.


Cell Snake::Move(const Cell& cell)
{
    Cell c(cell.row, cell.col);
    
    switch (dir)
    {
    case UP: c.row += 1; break;
    case DOWN: c.row -= 1; break;
    case LEFT: c.col -= 1; break;
    case RIGHT: c.col += 1; break;
    }
    
    // teleportation
    if (c.row == -1)     c.row = rows - 1;
    if (c.row == rows)   c.row = 0;
    if (c.col == -1)     c.col = cols - 1;
    if (c.col == cols)   c.col = 0;
    
    return c;
}

Вы передаете ссылку на исходную ячейку, но затем сразу же дублируете ее, чтобы сделать локальную копию, а затем игнорируете переданную вами. Прежде всего, вы копируете ее жестким способом. Просто пиши Cell c = cell; для вызова конструктора копирования. Во-вторых, какой смысл передавать по ссылке (избегать создания копии) просто для того, чтобы в любом случае сделать локальную копию? Вы получите тот же эффект, но более эффективно, если просто передадите значение.

Cell Snake::Move(Cell c)


if (Snake::GetCellType(head.row, head.col) == FOOD)
    {...}
else if (Snake::GetCellType(head.row, head.col) == SNAKE)

Вы звоните, чтобы посмотреть GetCellType повторно для одной и той же ячейки! Вы уже искали это однажды, зачем делать это снова?

Прикованный if заявления действительно должны быть switch оператор, который автоматически предотвратит повторение кода поиска.

Это функция-член для Snake, так зачем вам квалифицировать звонки на GetCellType с тем же классом, в котором вы уже находитесь?


char buff[200];
snprintf(buff, sizeof(buff), "Score: %d", Snake::GetScore());

Разве нет формы snprintf который автоматически увеличивает размер массива при передаче в качестве буфера? Я знаю, что многие из добавленных Microsoft функций «безопасного переполнения буфера» имеют это. Лучше пусть он подбирает размер автоматически.


   int width = rect->right - rect->left;
    int height = rect->bottom - rect->top;

В общем, используйте const более.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *