OIG.puUcNNdAsG.jpeg

之前使用python里面的pygame可以很容易构建snake游戏,其实pygame后端也是封装了sdl库,所以pygame编写的snake,使用C++
SDL2编写应该很容易移植过来

不过我不想自己亲自编写,我想偷懒让人工智能帮我完成看看,结果表明要一次达成目标似乎也不是那么顺利
经过几次对话,好歹勉强实现了贪吃蛇的基础功能

第一次尝试
第一次对话
代码

#include <SDL.h>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>

using namespace std;

// 定义游戏窗口的宽度和高度
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;

// 定义Snake的单元格大小
const int CELL_SIZE = 20;

// 定义Snake的初始长度
const int INITIAL_LENGTH = 5;

// 定义Snake的方向
enum class Direction { UP, DOWN, LEFT, RIGHT };

// 定义Snake的单元格结构体
struct Cell {
    int x, y;
};

// Snake类
class Snake {
public:
    Snake();
    void HandleInput(SDL_Event &e);
    void Move();
    bool CheckCollision();
    void Grow();
    void Render(SDL_Renderer* renderer);
private:
    vector<Cell> body;
    Direction direction;
};

Snake::Snake() {
    // 初始化Snake的位置和方向
    for (int i = 0; i < INITIAL_LENGTH; ++i) {
        body.push_back({SCREEN_WIDTH / 2 - i * CELL_SIZE, SCREEN_HEIGHT / 2});
    }
    direction = Direction::RIGHT;
}

void Snake::HandleInput(SDL_Event &e) {
    // 处理用户输入,改变Snake的方向
    if (e.type == SDL_KEYDOWN) {
        switch (e.key.keysym.sym) {
            case SDLK_UP:
                if (direction != Direction::DOWN)
                    direction = Direction::UP;
                break;
            case SDLK_DOWN:
                if (direction != Direction::UP)
                    direction = Direction::DOWN;
                break;
            case SDLK_LEFT:
                if (direction != Direction::RIGHT)
                    direction = Direction::LEFT;
                break;
            case SDLK_RIGHT:
                if (direction != Direction::LEFT)
                    direction = Direction::RIGHT;
                break;
        }
    }
}

void Snake::Move() {
    // 移动Snake的头部
    int newHeadX = body.front().x;
    int newHeadY = body.front().y;

    switch (direction) {
        case Direction::UP:
            newHeadY -= CELL_SIZE;
            break;
        case Direction::DOWN:
            newHeadY += CELL_SIZE;
            break;
        case Direction::LEFT:
            newHeadX -= CELL_SIZE;
            break;
        case Direction::RIGHT:
            newHeadX += CELL_SIZE;
            break;
    }

    // 添加新的头部
    body.insert(body.begin(), {newHeadX, newHeadY});
}

bool Snake::CheckCollision() {
    // 检查是否与自身相撞
    for (size_t i = 1; i < body.size(); ++i) {
        if (body[i].x == body.front().x && body[i].y == body.front().y) {
            return true;
        }
    }

    // 检查是否超出屏幕边界
    if (body.front().x < 0 || body.front().x >= SCREEN_WIDTH || body.front().y < 0 || body.front().y >= SCREEN_HEIGHT) {
        return true;
    }

    return false;
}

void Snake::Grow() {
    // Snake吃到食物时增加长度
    int tailX = body.back().x;
    int tailY = body.back().y;

    switch (direction) {
        case Direction::UP:
            tailY += CELL_SIZE;
            break;
        case Direction::DOWN:
            tailY -= CELL_SIZE;
            break;
        case Direction::LEFT:
            tailX += CELL_SIZE;
            break;
        case Direction::RIGHT:
            tailX -= CELL_SIZE;
            break;
    }

    body.push_back({tailX, tailY});
}

void Snake::Render(SDL_Renderer* renderer) {
    // 渲染Snake
    for (const Cell& cell : body) {
        SDL_Rect rect = {cell.x, cell.y, CELL_SIZE, CELL_SIZE};
        SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); // Snake颜色
        SDL_RenderFillRect(renderer, &rect);
    }
}

// 主程序
int main(int argc, char* args[]) {
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window* window = SDL_CreateWindow("Snake Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    srand(time(nullptr));

    Snake snake;

    // 游戏主循环
    bool quit = false;
    SDL_Event e;
    while (!quit) {
        while (SDL_PollEvent(&e) != 0) {
            if (e.type == SDL_QUIT) {
                quit = true;
            }
            snake.HandleInput(e);
        }

        // 移动Snake
        snake.Move();

        // 检查碰撞
        if (snake.CheckCollision()) {
            quit = true;
        }

        // 渲染
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); // 清空屏幕
        SDL_RenderClear(renderer);
        snake.Render(renderer);
        SDL_RenderPresent(renderer);

        // 控制游戏速度
        SDL_Delay(100);
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

运行结果,看起来不符合预期
第一次运行结果

第二次尝试
第二次尝试
代码

#include <SDL.h>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>

using namespace std;

const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int CELL_SIZE = 20;

enum class Direction { UP, DOWN, LEFT, RIGHT };

struct Cell {
    int x, y;
};

class Snake {
public:
    Snake();
    void HandleInput(SDL_Event &e);
    void Move();
    bool CheckCollision();
    void Grow();
    void Render(SDL_Renderer* renderer);
private:
    vector<Cell> body;
    Direction direction;
};

Snake::Snake() {
    body.push_back({SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2});
    direction = Direction::RIGHT;
}

void Snake::HandleInput(SDL_Event &e) {
    if (e.type == SDL_KEYDOWN) {
        switch (e.key.keysym.sym) {
            case SDLK_UP:
                if (direction != Direction::DOWN)
                    direction = Direction::UP;
                break;
            case SDLK_DOWN:
                if (direction != Direction::UP)
                    direction = Direction::DOWN;
                break;
            case SDLK_LEFT:
                if (direction != Direction::RIGHT)
                    direction = Direction::LEFT;
                break;
            case SDLK_RIGHT:
                if (direction != Direction::LEFT)
                    direction = Direction::RIGHT;
                break;
        }
    }
}

void Snake::Move() {
    int newHeadX = body.front().x;
    int newHeadY = body.front().y;

    switch (direction) {
        case Direction::UP:
            newHeadY -= CELL_SIZE;
            break;
        case Direction::DOWN:
            newHeadY += CELL_SIZE;
            break;
        case Direction::LEFT:
            newHeadX -= CELL_SIZE;
            break;
        case Direction::RIGHT:
            newHeadX += CELL_SIZE;
            break;
    }

    body.insert(body.begin(), {newHeadX, newHeadY});
}

bool Snake::CheckCollision() {
    for (size_t i = 1; i < body.size(); ++i) {
        if (body[i].x == body.front().x && body[i].y == body.front().y) {
            return true;
        }
    }

    if (body.front().x < 0 || body.front().x >= SCREEN_WIDTH || body.front().y < 0 || body.front().y >= SCREEN_HEIGHT) {
        return true;
    }

    return false;
}

void Snake::Grow() {
    int tailX = body.back().x;
    int tailY = body.back().y;

    switch (direction) {
        case Direction::UP:
            tailY += CELL_SIZE;
            break;
        case Direction::DOWN:
            tailY -= CELL_SIZE;
            break;
        case Direction::LEFT:
            tailX += CELL_SIZE;
            break;
        case Direction::RIGHT:
            tailX -= CELL_SIZE;
            break;
    }

    body.push_back({tailX, tailY});
}

void Snake::Render(SDL_Renderer* renderer) {
    for (size_t i = 0; i < body.size(); ++i) {
        SDL_Rect rect = {body[i].x, body[i].y, CELL_SIZE, CELL_SIZE};
        if (i == 0) {
            SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255); // Snake头部为黄色
        } else {
            SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); // Snake身体为绿色
        }
        SDL_RenderFillRect(renderer, &rect);
    }
}

int main(int argc, char* args[]) {
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window* window = SDL_CreateWindow("Snake Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    srand(time(nullptr));

    Snake snake;

    bool quit = false;
    SDL_Event e;
    while (!quit) {
        while (SDL_PollEvent(&e) != 0) {
            if (e.type == SDL_QUIT) {
                quit = true;
            }
            snake.HandleInput(e);
        }

        snake.Move();

        if (snake.CheckCollision()) {
            quit = true;
        }

        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);
        snake.Render(renderer);
        SDL_RenderPresent(renderer);

        SDL_Delay(100);
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

结果貌似还是不符合预期
截图 2023-09-23 12-38-08.png

第三次尝试
截图 2023-09-23 12-40-39.png

代码

#include <SDL.h>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>

using namespace std;

const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int CELL_SIZE = 20;

enum class Direction { UP, DOWN, LEFT, RIGHT };

struct Cell {
    int x, y;
};

class Snake {
public:
    Snake();
    void HandleInput(SDL_Event &e);
    void Move();
    bool CheckCollision();
    void Grow();
    void Render(SDL_Renderer* renderer);
    Cell GetHeadPosition();
private:
    vector<Cell> body;
    Direction direction;
};

Snake::Snake() {
    body.push_back({SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2});
    direction = Direction::RIGHT;
}

void Snake::HandleInput(SDL_Event &e) {
    if (e.type == SDL_KEYDOWN) {
        switch (e.key.keysym.sym) {
            case SDLK_UP:
                if (direction != Direction::DOWN)
                    direction = Direction::UP;
                break;
            case SDLK_DOWN:
                if (direction != Direction::UP)
                    direction = Direction::DOWN;
                break;
            case SDLK_LEFT:
                if (direction != Direction::RIGHT)
                    direction = Direction::LEFT;
                break;
            case SDLK_RIGHT:
                if (direction != Direction::LEFT)
                    direction = Direction::RIGHT;
                break;
        }
    }
}

void Snake::Move() {
    int newHeadX = body.front().x;
    int newHeadY = body.front().y;

    switch (direction) {
        case Direction::UP:
            newHeadY -= CELL_SIZE;
            break;
        case Direction::DOWN:
            newHeadY += CELL_SIZE;
            break;
        case Direction::LEFT:
            newHeadX -= CELL_SIZE;
            break;
        case Direction::RIGHT:
            newHeadX += CELL_SIZE;
            break;
    }

    body.insert(body.begin(), {newHeadX, newHeadY});
}

bool Snake::CheckCollision() {
    for (size_t i = 1; i < body.size(); ++i) {
        if (body[i].x == body.front().x && body[i].y == body.front().y) {
            return true;
        }
    }

    if (body.front().x < 0 || body.front().x >= SCREEN_WIDTH || body.front().y < 0 || body.front().y >= SCREEN_HEIGHT) {
        return true;
    }

    return false;
}

void Snake::Grow() {
    // 在Snake的尾部增加一个单元格
    int tailX = body.back().x;
    int tailY = body.back().y;

    switch (direction) {
        case Direction::UP:
            tailY += CELL_SIZE;
            break;
        case Direction::DOWN:
            tailY -= CELL_SIZE;
            break;
        case Direction::LEFT:
            tailX += CELL_SIZE;
            break;
        case Direction::RIGHT:
            tailX -= CELL_SIZE;
            break;
    }

    body.push_back({tailX, tailY});
}

void Snake::Render(SDL_Renderer* renderer) {
    for (size_t i = 0; i < body.size(); ++i) {
        SDL_Rect rect = {body[i].x, body[i].y, CELL_SIZE, CELL_SIZE};
        if (i == 0) {
            SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255); // Snake头部为黄色
        } else {
            SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); // Snake身体为绿色
        }
        SDL_RenderFillRect(renderer, &rect);
    }
}

Cell Snake::GetHeadPosition() {
    return body.front();
}

int main(int argc, char* args[]) {
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window* window = SDL_CreateWindow("Snake Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    srand(time(nullptr));

    Snake snake;

    // 初始化食物位置
    Cell food = {rand() % (SCREEN_WIDTH / CELL_SIZE) * CELL_SIZE, rand() % (SCREEN_HEIGHT / CELL_SIZE) * CELL_SIZE};

    bool quit = false;
    SDL_Event e;
    while (!quit) {
        while (SDL_PollEvent(&e) != 0) {
            if (e.type == SDL_QUIT) {
                quit = true;
            }
            snake.HandleInput(e);
        }

        snake.Move();

        if (snake.GetHeadPosition().x == food.x && snake.GetHeadPosition().y == food.y) {
            // Snake吃到食物时,增加长度并生成新的食物位置
            snake.Grow();
            food = {rand() % (SCREEN_WIDTH / CELL_SIZE) * CELL_SIZE, rand() % (SCREEN_HEIGHT / CELL_SIZE) * CELL_SIZE};
        }

        if (snake.CheckCollision()) {
            quit = true;
        }

        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);
        snake.Render(renderer);

        // 渲染食物
        SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); // 食物为绿色
        SDL_Rect foodRect = {food.x, food.y, CELL_SIZE, CELL_SIZE};
        SDL_RenderFillRect(renderer, &foodRect);

        SDL_RenderPresent(renderer);

        SDL_Delay(100);
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

运行结果,依然不符合预期,真是糟糕
截图 2023-09-23 12-42-20.png

第四次尝试,看看
截图 2023-09-23 12-42-55.png

代码

#include <SDL.h>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>

using namespace std;

const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int CELL_SIZE = 20;

enum class Direction { UP, DOWN, LEFT, RIGHT };

struct Cell {
    int x, y;
};

class Snake {
public:
    Snake();
    void HandleInput(SDL_Event &e);
    void Move();
    bool CheckCollision();
    void Grow();
    void Render(SDL_Renderer* renderer);
    Cell GetHeadPosition();
private:
    vector<Cell> body;
    Direction direction;
    bool growing;  // 用于标记是否吃到食物后需要增长
};

Snake::Snake() {
    body.push_back({SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2});
    direction = Direction::RIGHT;
    growing = false;  // 初始化时不需要增长
}

void Snake::HandleInput(SDL_Event &e) {
    if (e.type == SDL_KEYDOWN) {
        switch (e.key.keysym.sym) {
            case SDLK_UP:
                if (direction != Direction::DOWN)
                    direction = Direction::UP;
                break;
            case SDLK_DOWN:
                if (direction != Direction::UP)
                    direction = Direction::DOWN;
                break;
            case SDLK_LEFT:
                if (direction != Direction::RIGHT)
                    direction = Direction::LEFT;
                break;
            case SDLK_RIGHT:
                if (direction != Direction::LEFT)
                    direction = Direction::RIGHT;
                break;
        }
    }
}

void Snake::Move() {
    int newHeadX = body.front().x;
    int newHeadY = body.front().y;

    switch (direction) {
        case Direction::UP:
            newHeadY -= CELL_SIZE;
            break;
        case Direction::DOWN:
            newHeadY += CELL_SIZE;
            break;
        case Direction::LEFT:
            newHeadX -= CELL_SIZE;
            break;
        case Direction::RIGHT:
            newHeadX += CELL_SIZE;
            break;
    }

    body.insert(body.begin(), {newHeadX, newHeadY});

    // 如果吃到食物,则不删除尾部,实现增长
    if (!growing) {
        body.pop_back();
    } else {
        growing = false;
    }
}

bool Snake::CheckCollision() {
    for (size_t i = 1; i < body.size(); ++i) {
        if (body[i].x == body.front().x && body[i].y == body.front().y) {
            return true;
        }
    }

    if (body.front().x < 0 || body.front().x >= SCREEN_WIDTH || body.front().y < 0 || body.front().y >= SCREEN_HEIGHT) {
        return true;
    }

    return false;
}

void Snake::Grow() {
    // 在Snake的尾部增加一个单元格
    int tailX = body.back().x;
    int tailY = body.back().y;

    switch (direction) {
        case Direction::UP:
            tailY += CELL_SIZE;
            break;
        case Direction::DOWN:
            tailY -= CELL_SIZE;
            break;
        case Direction::LEFT:
            tailX += CELL_SIZE;
            break;
        case Direction::RIGHT:
            tailX -= CELL_SIZE;
            break;
    }

    body.push_back({tailX, tailY});
    growing = true;  // 标记为需要增长
}

void Snake::Render(SDL_Renderer* renderer) {
    for (size_t i = 0; i < body.size(); ++i) {
        SDL_Rect rect = {body[i].x, body[i].y, CELL_SIZE, CELL_SIZE};
        if (i == 0) {
            SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255); // Snake头部为黄色
        } else {
            SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); // Snake身体为绿色
        }
        SDL_RenderFillRect(renderer, &rect);
    }
}

Cell Snake::GetHeadPosition() {
    return body.front();
}

int main(int argc, char* args[]) {
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window* window = SDL_CreateWindow("Snake Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    srand(time(nullptr));

    Snake snake;

    // 初始化食物位置
    Cell food = {rand() % (SCREEN_WIDTH / CELL_SIZE) * CELL_SIZE, rand() % (SCREEN_HEIGHT / CELL_SIZE) * CELL_SIZE};

    bool quit = false;
    SDL_Event e;
    while (!quit) {
        while (SDL_PollEvent(&e) != 0) {
            if (e.type == SDL_QUIT) {
                quit = true;
            }
            snake.HandleInput(e);
        }

        snake.Move();

        if (snake.GetHeadPosition().x == food.x && snake.GetHeadPosition().y == food.y) {
            // Snake吃到食物时,增加长度并生成新的食物位置
            snake.Grow();
            food = {rand() % (SCREEN_WIDTH / CELL_SIZE) * CELL_SIZE, rand() % (SCREEN_HEIGHT / CELL_SIZE) * CELL_SIZE};
        }

        if (snake.CheckCollision()) {
            quit = true;
        }

        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);
        snake.Render(renderer);

        // 渲染食物
        SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); // 食物为绿色
        SDL_Rect foodRect = {food.x, food.y, CELL_SIZE, CELL_SIZE};
        SDL_RenderFillRect(renderer, &foodRect);

        SDL_RenderPresent(renderer);

        SDL_Delay(100);
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

运行结果,看起来达到预期
截图 2023-09-23 12-44-48.png

透过这几次对话,我们可以看到要想快速达到预期的结果,提问非常关键,如果一开始就给出比较细致的说明,应该能够一次成功,需要试试!

透过比较几次代码的差异,也可以从中学习到程序bug产生的原因

标签: SDL2, Game, C++