贪吃蛇(Qt版)

news2024/11/27 14:47:22

目录

一、项目介绍

界面一:游戏大厅界面

界面二:关卡选择界面

界面三:游戏界面

最终游戏效果:

二、项目创建与资源配置

1. 创建项目

2. 添加项目资源文件

三、项目实现

1. 游戏大厅界面

2. 关卡选择界面

3. 游戏房间界面

3.1 封装贪吃蛇数据结构

3.2 初始化游戏房间界面

3.3 蛇的移动

3.4 初始化贪吃蛇本体和食物节点

3.5 实现定时器的超时槽函数

3.6 实现各个方向的移动

3.7 重写绘图事件函数进行渲染

3.8 检查是否自己会撞到自己

3.9 设置游戏开始和游戏暂停按钮

3.10 设置退出游戏按钮

3.11 获取历史战绩

四、项目源码


一、项目介绍

        贪吃蛇游戏是一款休闲益智类游戏。它通过控制蛇头方向吃食物,从而使得蛇变得越来越长。

        在本游戏中设置了上下左右四个方向键来控制蛇的移动方向。食物的产生是随机生成的,当蛇每吃一次食物就会增加一节身体,同时游戏积分也会相应的加一。

        在本游戏的设计中,蛇的身体会越吃越长,身体越长对应的难度就越大,因为一旦蛇头和身体相交游戏就会结束。


本项目使用 Qt 实现一款简单的贪吃蛇游戏。

主要界面如下:

界面一:游戏大厅界面

界面二:关卡选择界面

界面三:游戏界面


最终游戏效果:


二、项目创建与资源配置

1. 创建项目

🌴游戏大厅界面:

  • 打开Qt-Creator 创建项目。 注意项目名中不能包含空格、回车、中文等特殊字符

  • 选择基类为 QWidget, 类名为 GameHall 代表游戏大厅界面

 

  • 选择 MinGW 64-bit 编译套件


🌴关卡选择界面: 

  • 添加新文件 

  •  继承自 QWidget, 类名为 GameSelect 代表游戏关卡界面

  • 点击完成 


🌴游戏房间界面: 

  • 添加新文件 

  • 继承自 QWidget, 类名为 GameRoom 代表游戏房间界面

  • 点击完成 


2. 添加项目资源文件

  • 拷贝资源文件到项目文件中

  • 在项目中添加资源文件

  • 给资源文件起名为:res, 点击下一步

  • 点击完成 

  • 添加前缀 

  • 添加资源文件 

三、项目实现

1. 游戏大厅界面

游戏大厅界面比较简单, 只有一个背景图和一个按钮。

  • 背景图的渲染,我们通过QT的绘图事件完成。
#include <QPainter>

void GameHall::paintEvent(QPaintEvent *event)
{
    (void)event;// 去掉警告小技巧

    QPainter painter(this);// 实例化画家对象

    QPixmap pix(":res/game_hall.png");// 实例化绘画设备

    painter.drawPixmap(0, 0, this->width(), this->height(), pix);// 绘画
}
  • 按钮的响应我们通过QT的信号和槽机制完成。
#include "gameselect.h"
#include <QIcon>
#include <QPushButton>
#include <QFont>
#include <QSound>

GameHall::GameHall(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::GameHall)
{
    ui->setupUi(this);

    // 设置窗口信息
    this->setFixedSize(1000, 800);
    this->setWindowTitle("贪吃蛇游戏");
    this->setWindowIcon(QIcon(":res/ico.png"));

    // 设置开始游戏按钮
    QPushButton* startBtn = new QPushButton(this);
    startBtn->setText("开始游戏");
    startBtn->move(this->width() * 0.4, this->height() * 0.7);
    startBtn->setStyleSheet("QPushButton { border : 0px; }");
    QFont font("华文行楷", 23, QFont::ExtraLight, false);
    startBtn->setFont(font);

    // 点击按钮就会进入游戏关卡界面
    GameSelect* select = new GameSelect;
    connect(startBtn, &QPushButton::clicked, [=](){
        select->setGeometry(this->geometry());// 设置窗口固定
        this->close();// 关闭游戏大厅界面
        select->show();// 显示游戏关卡界面

        // 注意:使用 QSound 类时, 需要添加模块:multimedia
        QSound::play(":res/clicked.wav");// 添加按钮点击音效
    });
}

 效果示例:

2. 关卡选择界面

        关卡选择界面包含一个背景图和五个按钮,背景图的绘制和游戏大厅背景图绘制⼀样,同样使用的是Qt中的绘图事件。

  • 背景图的渲染,我们通过QT的绘图事件完成。
#include <QPainter>

void GameSelect::paintEvent(QPaintEvent *event)
{
    (void)event;

    QPainter painter(this);// 实例化画家对象

    QPixmap pix(":res/game_select.png");// 实例化绘画设备

    painter.drawPixmap(0, 0, this->width(), this->height(), pix);// 绘画
}
  •  按钮的响应我们通过QT的信号和槽机制完成。
#include "gameselect.h"
#include "gamehall.h"
#include "gameroom.h"
#include <QIcon>
#include <QFont>
#include <QPushButton>
#include <QSound>
#include <QFile>
#include <QTextStream>
#include <QTextEdit>
#include <QDateTime>

GameSelect::GameSelect(QWidget *parent) : QWidget(parent)
{
    // 设置游戏关卡界面
    this->setFixedSize(1000, 800);
    this->setWindowTitle("关卡选择");
    this->setWindowIcon(QIcon(":res/ico.png"));

    // 设置字体格式
    QFont font("华文行楷", 20, QFont::ExtraLight, false);

    // 创建返回按钮
    QPushButton* backBtn = new QPushButton(this);
    QPixmap pix(":res/back.png");
    backBtn->setIcon(pix);
    backBtn->move(this->width() * 0.9, this->height() * 0.9);

    connect(backBtn, &QPushButton::clicked, [=](){
        emit this->backGameHall();// emit 关键字触发信号
        QSound::play(":res/clicked.wav");
    });

    // 返回游戏大厅界面
    connect(this, &GameSelect::backGameHall, [=](){
        this->hide();
        GameHall* gameHall = new GameHall;
        gameHall->show();
    });

    GameRoom* gameRoom = new GameRoom();

    // 设置简单模式按钮
    QPushButton* simpleBtn = new QPushButton(this);
    simpleBtn->setStyleSheet("QPushButton { background-color:#0072C6; color:white; "
                             "border:2px groove gray; border-radius:10px; padding:6px; }");
    simpleBtn->setFont(font);
    simpleBtn->setText("简单模式");
    simpleBtn->move(395, 140);

    connect(simpleBtn, &QPushButton::clicked, [=](){
        gameRoom->setTimeout(300);
        gameRoom->setGeometry(this->geometry());// 设置窗口固定
        this->hide();
        gameRoom->show();
        QSound::play(":res/clicked.wav");
    });


    // 设置正常按钮模式
    QPushButton* normalBtn = new QPushButton(this);
    normalBtn->setStyleSheet("QPushButton { background-color:#0072C6; color:white; "
                             "border:2px groove gray; border-radius:10px; padding:6px; }");
    normalBtn->setFont(font);
    normalBtn->setText("正常模式");
    normalBtn->move(395, 260);

    connect(normalBtn, &QPushButton::clicked, [=](){
        gameRoom->setTimeout(200);
        gameRoom->setGeometry(this->geometry());// 设置窗口固定
        this->hide();
        gameRoom->show();
        QSound::play(":res/clicked.wav");
    });


    // 设置困难模式按钮
    QPushButton* hardBtn = new QPushButton(this);
    hardBtn->setStyleSheet("QPushButton { background-color:#0072C6; color:white; "
                             "border:2px groove gray; border-radius:10px; padding:6px; }");
    hardBtn->setFont(font);
    hardBtn->setText("困难模式");
    hardBtn->move(395, 380);

    connect(hardBtn, &QPushButton::clicked, [=](){
        gameRoom->setTimeout(100);
        gameRoom->setGeometry(this->geometry());// 设置窗口固定
        this->hide();
        gameRoom->show();
        QSound::play(":res/clicked.wav");
    });


    // 获取历史战绩
    QPushButton* hisBtn = new QPushButton(this);
    hisBtn->setStyleSheet("QPushButton { background-color:#0072C6; color:white; "
                             "border:2px groove gray; border-radius:10px; padding:6px; }");
    hisBtn->setFont(font);
    hisBtn->setText("历史战绩");
    hisBtn->move(395, 500);

    connect(hisBtn, &QPushButton::clicked, [=](){
        QSound::play(":res/clicked.wav");

        QFile file("D:/code/qt/Snake/1.txt");
        file.open(QIODevice::ReadOnly);

        QTextStream in(&file);// 创建文本流对象
        int data = in.readLine().toInt();// 读取第一行作为整形数据

        QWidget* newWidget = new QWidget;// 一定不能添加到对象书上
        newWidget->setWindowTitle("历史战绩");
        newWidget->setFixedSize(500, 300);

        QTextEdit* edit = new QTextEdit(newWidget);// 将编辑框置于新窗口上
        edit->setFont(font);
        edit->setFixedSize(500, 300);

        // 获取系统时间
        QDateTime currentTime = QDateTime::currentDateTime();
        QString time = currentTime.toString("yyyy-MM-dd hh:mm:ss");

        edit->setText("时间:");
        edit->append(time);

        edit->setText("得分为:");
        edit->append(QString::number(data));

        newWidget->show();
    });

}

效果示例:

3. 游戏房间界面

游戏房间界面包含下面几个部分:

  1. 背景图的绘制。
  2. 蛇的绘制、蛇的移动、判断蛇是否会撞到自己。
  3. 积分的累加和绘制。

在这里我们要考虑几个比较核心的问题:

1. 怎么让蛇动起来?

  • 我们可以用一个链表表示贪吃蛇,一个小方块表示蛇的一个节点, 我们设置蛇的默认长度为3;
  • 向上移动的逻辑就是在蛇的上方加入一个小方块, 然后把最后一个小方块删除即可;
  • 需要用到定时器 Qtimer 每100 - 200ms 重新渲染。

2. 怎么判断蛇有没有吃到食物?

  • 判断蛇头和食物的坐标是否相交,Qt 有相关的接口调用。

3. 怎么控制蛇的移动?

  • 借助QT的实践机制实现, 重写keyPressEvent即可, 在函数中监控想要的键盘事件即可。
  • 我们通过绘制四个按钮,使用信号和槽的机制控制蛇的上、下、左、右移动方向。

3.1 封装贪吃蛇数据结构

#include <QSound>

// 枚举蛇的移动方向
enum class snakeDirect{
    UP = 0,
    DOWN,
    LEFT,
    RIGHT
};

class GameRoom : public QWidget
{
    Q_OBJECT
public:
    explicit GameRoom(QWidget *parent = nullptr);
    
    // 重写绘图事件,实现贪吃蛇游戏游戏背景效果
    void paintEvent(QPaintEvent *event);
    
    void startGame();
    
    void setTimeout(int count) {moveTimeout = count;}
    
    void createNewFood();// 生成食物
    
    void moveUp();// 蛇向上移动
    void moveDown();// 蛇向下移动
    void moveLeft();// 蛇向左移动
    void moveRight();// 蛇向右移动
    
    bool checkFail();// 判断游戏是否结束
    
private:
    snakeDirect moveDirect = snakeDirect::UP;// 定义蛇的移动方向,默认朝上
    bool isGameStart = false;// 表示是否开始游戏
    QTimer* timer;// 定时器
    
    QList<QRectF> snakeList;// 表示贪吃蛇链表
    QRectF foodRect;// 表示食物节点
    
    const int kDefaultTimeout = 200;// 表示贪吃蛇默认移动时间
    const int kSnakeNodeWidth = 20;// 表示蛇身体节点的宽度
    const int kSnakeNodeHeight = 20;// 表示蛇身体节点的高度
    
    int moveTimeout = kDefaultTimeout;
    
    QSound* sound;// 音频
};

3.2 初始化游戏房间界面

  • 设置窗口大小、标题、图标等

#include <QIcon>

GameRoom::GameRoom(QWidget *parent) : QWidget(parent)
{
    // 初始化窗口设置
    this->setFixedSize(1000, 800);
    this->setWindowTitle("贪吃蛇游戏");
    this->setWindowIcon(QIcon(":res/ico.png"));
}

3.3 蛇的移动

  • 蛇的移动方向为:上、下、左、右。通过在游戏房间中布局四个按钮来控制蛇的移动方向。
  • 注意: 这里贪吃蛇不允许直接掉头, 比如当前是向上的, 不能直接修改为向下。
    // 方向控制
    QPushButton* Up = new QPushButton(this);
    QPushButton* Down = new QPushButton(this);
    QPushButton* Left = new QPushButton(this);
    QPushButton* Right = new QPushButton(this);

    QString buttonStyle =
                    "QPushButton {"
                    "    border: 2px solid #8f8f8f;"
                    "    border-radius: 10px;"
                    "    background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #f0f0f0, stop: 1 #bbbbbb);"
                    "    color: #000000;"
                    "}"
                    "QPushButton:hover {"
                    "    background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffffff, stop: 1 #8f8f8f);"
                    "}"
                    "QPushButton:pressed {"
                    "    background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #dadada, stop: 1 #949494);"
                    "}";

    Up->move(880, 400);
    Down->move(880, 480);
    Left->move(840, 440);
    Right->move(920, 440);

    QFont font("楷体", 24, QFont::ExtraLight, false);
    Up->setStyleSheet(buttonStyle);

    Up->setFont(font);
    Up->setText("↑");

    Down->setStyleSheet(buttonStyle);
    Down->setFont(font);
    Down->setText("↓");

    Left->setStyleSheet(buttonStyle);
    Left->setFont(font);
    Left->setText("←");

    Right->setStyleSheet(buttonStyle);
    Right->setFont(font);
    Right->setText("→");
    
    connect(Up, &QPushButton::clicked, [=](){
        if (moveDirect != snakeDirect::DOWN){
            moveDirect = snakeDirect::UP;
        }
    });
    
    connect(Down, &QPushButton::clicked, [=](){
        if (moveDirect != snakeDirect::UP){
            moveDirect = snakeDirect::DOWN;
        }
    });
    
    connect(Left, &QPushButton::clicked, [=](){
        if (moveDirect != snakeDirect::RIGHT){
            moveDirect = snakeDirect::LEFT;
        }
    });
    
    connect(Right, &QPushButton::clicked, [=](){
        if (moveDirect != snakeDirect::LEFT){
            moveDirect = snakeDirect::RIGHT;
        }
    });

3.4 初始化贪吃蛇本体和食物节点

GameRoom::GameRoom(QWidget *parent) : QWidget(parent)
{
    // 初始化贪吃蛇
    snakeList.push_back(QRectF(this->width() * 0.5, this->height() * 0.5, kSnakeNodeWidth, kSnakeNodeHeight));
    moveUp();
    moveUp();

    createNewFood();// 初始化食物
}
  • moveUp() 的功能是将蛇向上移动一次, 即在上方新增一个节点, 但不删除尾部节点。
  • createNewFood() 方法的功能是随机创建一个食物节点:
void GameRoom::createNewFood()
{
    foodRect = QRectF(
                qrand() % (this->width() / kSnakeNodeWidth) * kSnakeNodeWidth,
                qrand() % (this->height() / kSnakeNodeHeight) *kSnakeNodeHeight,
                kSnakeNodeWidth,
                kSnakeNodeHeight);                
}

3.5 实现定时器的超时槽函数

定时器是为了实现每隔一段时间能处理移动的逻辑并且更新绘图事件。

1. 首先, 需要判断蛇头和食物节点坐标是否相交

  • 如果相交, 需要创建新的食物节点, 并且需要更新蛇的长度, 所以 count 需要 +1 ;
  • 如果不相交, 那么直接处理蛇的移动即可。

2. 根据蛇移动方向 moveDirect 来处理蛇的移动, 处理方法是在前方加一个, 并且删除后方节点;

3. 重新触发绘图事件, 更新渲染。

GameRoom::GameRoom(QWidget *parent) : QWidget(parent)
{    
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, [=](){
        int count = 1;
        
        if (snakeList.front().intersects(foodRect)){
            createNewFood();
            ++count;
            
            QSound::play(":res/eatfood.wav");
        }
        
        while (count--) {
            // 处理蛇的移动
            switch (moveDirect) {
            case snakeDirect::UP:
                moveUp();
                break;
            case snakeDirect::DOWN:
                moveDown();
                break;
            case snakeDirect::LEFT:
                moveLeft();
                break;
            case snakeDirect::RIGHT:
                moveRight();
                break;
            default:
                qDebug() << "非法移动方向";
                break;
            }
        }
        // 删除最后一个节点
        snakeList.pop_back();
        update();
    });
}

3.6 实现各个方向的移动

        各个方向的移动主要在于更新矩形节点的坐标, 要注意的是一定要处理边界的情况, 当边界不够存储一个新的节点时, 我们需要处理穿墙逻辑。

  • 实现向上移动
void GameRoom::moveUp()
{
    QPointF leftTop;// 左上角坐标
    QPointF rightBottom;// 右下角坐标
    
    auto snakeNode = snakeList.front();
    
    int headX = snakeNode.x();
    int headY = snakeNode.y();
    
    // 如果上面剩余的空间不够放入⼀个新的节点, 相当于到墙边了, 要处理穿墙逻辑
    if (headY < 0){
        leftTop = QPointF(headX, this->height() - kSnakeNodeHeight);
    }else {
        leftTop = QPointF(headX, headY - kSnakeNodeHeight);
    }
    
    rightBottom = leftTop + QPointF(kSnakeNodeWidth, kSnakeNodeHeight);
    snakeList.push_front(QRectF(leftTop, rightBottom));
}
  • 实现向下移动
void GameRoom::moveDown()
{
    QPointF leftTop;// 左上角坐标
    QPointF rightBottom;// 右下角坐标
    
    auto snakeNode = snakeList.front();
    
    int headX = snakeNode.x();
    int headY = snakeNode.y();
    
    // 如果下面剩余的空间不够放入⼀个新的节点, 相当于到墙边了, 要处理穿墙逻辑
    if (headY > this->height()){
        leftTop = QPointF(headX, 0);
    }else {
        leftTop = snakeNode.bottomLeft();// 返回矩形左下角的坐标
    }
    
    rightBottom = leftTop + QPointF(kSnakeNodeWidth, kSnakeNodeHeight);
    snakeList.push_front(QRectF(leftTop, rightBottom));
}
  • 实现向左移动
void GameRoom::moveLeft()
{
    QPointF leftTop;// 左上角坐标
    QPointF rightBottom;// 右下角坐标
    
    auto snakeNode = snakeList.front();
    
    int headX = snakeNode.x();
    int headY = snakeNode.y();
    
    // 如果左面剩余的空间不够放入⼀个新的节点, 相当于到墙边了, 要处理穿墙逻辑
    if (headX < 0){
        leftTop = QPointF(800 - kSnakeNodeWidth, headY);
    }else {
        leftTop = QPointF(headX - kSnakeNodeWidth, headY);
    }
    
    rightBottom = leftTop + QPointF(kSnakeNodeWidth, kSnakeNodeHeight);
    snakeList.push_front(QRectF(leftTop, rightBottom));
}
  • 实现向右移动
void GameRoom::moveRight()
{
    QPointF leftTop;// 左上角坐标
    QPointF rightBottom;// 右下角坐标
    
    auto snakeNode = snakeList.front();
    
    int headX = snakeNode.x();
    int headY = snakeNode.y();
    
    // 如果右面剩余的空间不够放入⼀个新的节点, 相当于到墙边了, 要处理穿墙逻辑
    if (headX > 760){
        leftTop = QPointF(0, headY);
    }else {
        leftTop = snakeNode.topRight();// 返回矩形右上角的坐标
    }
    
    rightBottom = leftTop + QPointF(kSnakeNodeWidth, kSnakeNodeHeight);
    snakeList.push_front(QRectF(leftTop, rightBottom));
}

3.7 重写绘图事件函数进行渲染

重写基类的 paintEvent() 方法进行渲染:

  • 渲染背景图
  • 渲染蛇头
  • 渲染蛇身体
  • 渲染蛇尾巴
  • 渲染右边游戏控制区域
  • 渲染食物节点
  • 渲染当前分数
  • 游戏结束渲染 game over!
void GameRoom::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QPixmap pix;
    pix.load(":res/game_room.png");
    painter.drawPixmap(0, 0, 800, 800, pix);
    painter.setRenderHint(QPainter::Antialiasing);// 设置抗锯齿

    // 渲染蛇头
    if (moveDirect == snakeDirect::UP){
        pix.load(":res/up.png");
    }else if (moveDirect == snakeDirect::DOWN){
        pix.load(":res/down.png");
    }else if (moveDirect == snakeDirect::LEFT){
        pix.load(":res/left.png");
    }else {
        pix.load(":res/right.png");
    }

    auto snakeHead = snakeList.front();
    painter.drawPixmap(snakeHead.x(), snakeHead.y(), snakeHead.width(), snakeHead.height(), pix);

    // 渲染蛇身体
    pix.load(":res/Bd.png");
    for (int i = 1; i < snakeList.size() - 1; i++){
        auto node = snakeList.at(i);
        painter.drawPixmap(node.x(), node.y(), node.width(), node.height(), pix);
    }

    // 渲染蛇尾巴
    auto snakeTail = snakeList.back();
    painter.drawPixmap(snakeTail.x(), snakeTail.y(), snakeTail.width(), snakeTail.height(), pix);

    // 渲染食物
    pix.load(":res/food.bmp");
    painter.drawPixmap(foodRect.x(), foodRect.y(), foodRect.width(), foodRect.height(), pix);

    // 渲染右边区域
    pix.load(":res/bg1.png");
    painter.drawPixmap(800, 0, 200, 1000, pix);

    // 渲染分数
    pix.load(":res/sorce_bg.png");
    painter.drawPixmap(this->width() * 0.85, this->height() * 0.02, 90, 40, pix);
    QPen pen;
    pen.setColor(Qt::black);
    painter.setPen(pen);
    QFont font("楷体", 22, QFont::ExtraLight, false);
    painter.setFont(font);
    painter.drawText(this->width() * 0.9, this->height() * 0.06, QString("%1").arg(snakeList.size()));
    
    // 如果失败,渲染 GAME OVER
    if (checkFail()){
        pen.setColor(Qt::red);
        painter.setPen(pen);
        QFont font("楷体", 50, QFont::ExtraLight, false);
        painter.setFont(font);
        painter.drawText(this->width() * 0.5 - 250, this->height() * 0.5, QString("GAME OVER"));
        timer->stop();
        
        QSound::play(":res/gameover.wav");
        sound->stop();
    }
}

3.8 检查是否自己会撞到自己

bool GameRoom::checkFail()
{
    // 判断头尾是否出现相交
    for (int i = 0; i < snakeList.size(); i++){
        for (int j = i + 1; j < snakeList.size(); j++){
            if (snakeList.at(i) == snakeList.at(j)){
                return true;
            }
        }
    }
    return false;
}

3.9 设置游戏开始和游戏暂停按钮

    // 开始游戏 & 暂停游戏
    QPushButton* startBtn = new QPushButton(this);
    QPushButton* stopBtn = new QPushButton(this);

    QFont ft("楷体", 20, QFont::ExtraLight, false);

    // 设置按钮的位置
    startBtn->move(860, 150);
    stopBtn->move(860, 200);

    // 设置按钮文本
    startBtn->setText("开始");
    stopBtn->setText("暂停");

    // 设置按钮样式
    startBtn->setStyleSheet("QPushButton {"
                                "background-color: white;"// 按钮背景颜色
                                "color: #4CAF50;" // 文字颜色
                                "border: 2px solid #4CAF50;" // 边框
                                "border-radius: 8px;" // 边框圆角
                                "padding: 10px 20px;" // 内边距
                            "}");

    stopBtn->setStyleSheet("QPushButton {"
                                "background-color: white;"// 按钮背景颜色
                                "color: #4CAF50;" // 文字颜色
                                "border: 2px solid #4CAF50;" // 边框
                                "border-radius: 8px;" // 边框圆角
                                "padding: 10px 20px;" // 内边距
                            "}");

    // 设置按钮字体格式
    startBtn->setFont(ft);
    stopBtn->setFont(ft);

    connect(startBtn, &QPushButton::clicked, [=](){
        sound = new QSound(":res/Trepak.wav");
        sound->play();
        sound->setLoops(-1);// 循环播放

        isGameStart = true;
        timer->start(moveTimeout);
    });

    connect(stopBtn, &QPushButton::clicked, [=](){
        sound->stop();

        isGameStart = false;
        timer->stop();
    });

3.10 设置退出游戏按钮

当我们点击退出游戏按钮时,当前游戏房间窗口不会立即退出,而是会弹窗提示,提示我们是否要退出游戏,效果如下图示:

这个弹窗提示我们是通过 Qt 中的消息盒子来实现的,具体实现过程如下:

  // 退出游戏按钮
    QPushButton* exitGame = new QPushButton(this);
    exitGame->setStyleSheet("QPushButton {"
                            "background-color: white;" // 按钮背景颜色
                            "color: #4CAF50;" // 文字颜色
                            "border: 2px solid #4CAF50;" // 边框
                            "border-radius: 8px;" // 边框圆角
                            "padding: 10px 20px;" // 内边距
                            "}");

    exitGame->move(825, 730);
    exitGame->setText("退出");
    exitGame->setFont(font);

  // 消息提示
    QMessageBox* messageBox = new QMessageBox(this);
    QPushButton* okbtn = new QPushButton("ok");
    QPushButton* cancelbtn = new QPushButton("cancel");

    messageBox->setWindowTitle("退出游戏");// 设置消息对话框的标题
    messageBox->setText("确认退出游戏吗?");// 设置消息对话框内容
    messageBox->setIcon(QMessageBox::Question); //设置消息对话框类型

    messageBox->addButton(okbtn, QMessageBox::AcceptRole);//Accept Role:接受的角色
    messageBox->addButton(cancelbtn, QMessageBox::RejectRole);//Reject Role:排斥作用


    connect(exitGame, &QPushButton::clicked, [=](){
        messageBox->show();// 消息提示
        QSound::play(":res/clicked.wav");
        messageBox->exec();// 阻塞等待用户输入

        GameSelect* s = new GameSelect;

        if (messageBox->clickedButton() == okbtn){
            this->close();
            //如果点击了 Ok 按钮,那么就会跳转到游戏关卡界⾯
            s->show();
            s->setGeometry(this->geometry());
            QSound::play(":res/clicked.wav");
        }else{
            messageBox->close();
            QSound::play(":res/clicked.wav");
        }
    });

3.11 获取历史战绩

对于历史战绩的获取我们是通过 Qt 中的读写文件操作来实现的。具体实现过程如下:

  • 写文件:往文件中写入蛇的长度
 int c = snakeList.size();
    QFile file("D:/code/qt/Snake/1.txt");
    if (file.open(QIODevice::WriteOnly | QIODevice::Text)){
        QTextStream out(&file);
        int num = c;
        out << num;// 将int类型数据写入文件
        file.close();
    }
  • 读文件:读取写入文件中蛇的长度
QFile file("D:/code/qt/Snake/1.txt");
file.open(QIODevice::ReadOnly);

QTextStream in(&file);// 创建文本流对象
int data = in.readLine().toInt();// 读取第一行作为整形数据

 效果示例:

四、项目源码

🌵gamehall.h

#ifndef GAMEHALL_H
#define GAMEHALL_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class GameHall; }
QT_END_NAMESPACE

class GameHall : public QWidget
{
    Q_OBJECT

public:
    GameHall(QWidget *parent = nullptr);
    ~GameHall();

    //重写绘图事件,绘制游戏大厅界面
    void paintEvent(QPaintEvent *event);

private:
    Ui::GameHall *ui;
};
#endif // GAMEHALL_H

🌵gamehall.cpp

#include "gamehall.h"
#include "ui_gamehall.h"
#include "gameselect.h"
#include <QPainter>
#include <QIcon>
#include <QPushButton>
#include <QFont>
#include <QSound>

GameHall::GameHall(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::GameHall)
{
    ui->setupUi(this);

    // 设置窗口信息
    this->setFixedSize(1000, 800);
    this->setWindowTitle("贪吃蛇游戏");
    this->setWindowIcon(QIcon(":res/ico.png"));

    // 设置开始游戏按钮
    QPushButton* startBtn = new QPushButton(this);
    startBtn->setText("开始游戏");
    startBtn->move(this->width() * 0.4, this->height() * 0.7);
    startBtn->setStyleSheet("QPushButton { border : 0px; }");
    QFont font("华文行楷", 23, QFont::ExtraLight, false);
    startBtn->setFont(font);

    // 点击按钮就会进入游戏关卡界面
    GameSelect* select = new GameSelect;
    connect(startBtn, &QPushButton::clicked, [=](){
        select->setGeometry(this->geometry());// 设置窗口固定
        this->close();// 关闭游戏大厅界面
        select->show();// 显示游戏关卡界面

        // 注意:使用 QSound 类时, 需要添加模块:multimedia
        QSound::play(":res/clicked.wav");// 添加按钮点击音效
    });
}

GameHall::~GameHall()
{
    delete ui;
}

void GameHall::paintEvent(QPaintEvent *event)
{
    (void)event;// 去掉警告小技巧

    QPainter painter(this);// 实例化画家对象

    QPixmap pix(":res/game_hall.png");// 实例化绘画设备

    painter.drawPixmap(0, 0, this->width(), this->height(), pix);// 绘画
}

 🍇gameselect.h

#ifndef GAMESELECT_H
#define GAMESELECT_H

#include <QWidget>

class GameSelect : public QWidget
{
    Q_OBJECT
public:
    explicit GameSelect(QWidget *parent = nullptr);

    //重写绘图事件,绘制游戏关卡选择界面
    void paintEvent(QPaintEvent *event);

signals:
    void backGameHall();// 信号声明

};

#endif // GAMESELECT_H

 🍇gameselect.cpp 

#include "gameselect.h"
#include "gamehall.h"
#include "gameroom.h"
#include <QPainter>
#include <QIcon>
#include <QFont>
#include <QPushButton>
#include <QSound>
#include <QFile>
#include <QTextStream>
#include <QTextEdit>
#include <QDateTime>

GameSelect::GameSelect(QWidget *parent) : QWidget(parent)
{
    // 设置游戏关卡界面
    this->setFixedSize(1000, 800);
    this->setWindowTitle("关卡选择");
    this->setWindowIcon(QIcon(":res/ico.png"));

    // 设置字体格式
    QFont font("华文行楷", 20, QFont::ExtraLight, false);

    // 创建返回按钮
    QPushButton* backBtn = new QPushButton(this);
    QPixmap pix(":res/back.png");
    backBtn->setIcon(pix);
    backBtn->move(this->width() * 0.9, this->height() * 0.9);

    connect(backBtn, &QPushButton::clicked, [=](){
        emit this->backGameHall();// emit 关键字触发信号
        QSound::play(":res/clicked.wav");
    });

    // 返回游戏大厅界面
    connect(this, &GameSelect::backGameHall, [=](){
        this->hide();
        GameHall* gameHall = new GameHall;
        gameHall->show();
    });

    GameRoom* gameRoom = new GameRoom();

    // 设置简单模式按钮
    QPushButton* simpleBtn = new QPushButton(this);
    simpleBtn->setStyleSheet("QPushButton { background-color:#0072C6; color:white; "
                             "border:2px groove gray; border-radius:10px; padding:6px; }");
    simpleBtn->setFont(font);
    simpleBtn->setText("简单模式");
    simpleBtn->move(395, 140);

    connect(simpleBtn, &QPushButton::clicked, [=](){
        gameRoom->setTimeout(300);
        gameRoom->setGeometry(this->geometry());// 设置窗口固定
        this->hide();
        gameRoom->show();
        QSound::play(":res/clicked.wav");
    });


    // 设置正常按钮模式
    QPushButton* normalBtn = new QPushButton(this);
    normalBtn->setStyleSheet("QPushButton { background-color:#0072C6; color:white; "
                             "border:2px groove gray; border-radius:10px; padding:6px; }");
    normalBtn->setFont(font);
    normalBtn->setText("正常模式");
    normalBtn->move(395, 260);

    connect(normalBtn, &QPushButton::clicked, [=](){
        gameRoom->setTimeout(200);
        gameRoom->setGeometry(this->geometry());// 设置窗口固定
        this->hide();
        gameRoom->show();
        QSound::play(":res/clicked.wav");
    });


    // 设置困难模式
    QPushButton* hardBtn = new QPushButton(this);
    hardBtn->setStyleSheet("QPushButton { background-color:#0072C6; color:white; "
                             "border:2px groove gray; border-radius:10px; padding:6px; }");
    hardBtn->setFont(font);
    hardBtn->setText("困难模式");
    hardBtn->move(395, 380);

    connect(hardBtn, &QPushButton::clicked, [=](){
        gameRoom->setTimeout(100);
        gameRoom->setGeometry(this->geometry());// 设置窗口固定
        this->hide();
        gameRoom->show();
        QSound::play(":res/clicked.wav");
    });


    // 获取历史战绩
    QPushButton* hisBtn = new QPushButton(this);
    hisBtn->setStyleSheet("QPushButton { background-color:#0072C6; color:white; "
                             "border:2px groove gray; border-radius:10px; padding:6px; }");
    hisBtn->setFont(font);
    hisBtn->setWindowIcon(QIcon(":res/ico.png"));
    hisBtn->setText("历史战绩");
    hisBtn->move(395, 500);

    connect(hisBtn, &QPushButton::clicked, [=](){
        QSound::play(":res/clicked.wav");

        QFile file("D:/code/qt/Snake/1.txt");
        file.open(QIODevice::ReadOnly);

        QTextStream in(&file);// 创建文本流对象
        int data = in.readLine().toInt();// 读取第一行作为整形数据

        QWidget* newWidget = new QWidget;// 一定不能添加到对象书上
        newWidget->setWindowTitle("历史战绩");
        newWidget->setWindowIcon(QIcon(":res/ico.png"));
        newWidget->setFixedSize(500, 300);

        QTextEdit* edit = new QTextEdit(newWidget);// 将编辑框置于新窗口上
        edit->setFont(font);
        edit->setFixedSize(500, 300);


        // 获取系统时间
        QDateTime currentTime = QDateTime::currentDateTime();
        QString time = currentTime.toString("yyyy-MM-dd hh:mm:ss");

        edit->setText("时间:");
        edit->append(time);

        edit->setText("得分为:");
        edit->append(QString::number(data));

        newWidget->show();
    });

}


//重写绘图事件,绘制游戏关卡选择界面
void GameSelect::paintEvent(QPaintEvent *event)
{
    (void)event;

    QPainter painter(this);// 实例化画家对象

    QPixmap pix(":res/game_select.png");// 实例化绘画设备

    painter.drawPixmap(0, 0, this->width(), this->height(), pix);// 绘画
}

🌴gameroom.h

#ifndef GAMEROOM_H
#define GAMEROOM_H

#include <QWidget>
#include <QSound>

// 枚举蛇的移动方向
enum class snakeDirect{
    UP = 0,
    DOWN,
    LEFT,
    RIGHT
};

class GameRoom : public QWidget
{
    Q_OBJECT
public:
    explicit GameRoom(QWidget *parent = nullptr);

    // 重写绘图事件,实现贪吃蛇游戏游戏背景效果
    void paintEvent(QPaintEvent *event);

    void startGame();

    void setTimeout(int count) {moveTimeout = count;}

    void createNewFood();// 生成食物

    void moveUp();// 蛇向上移动
    void moveDown();// 蛇向下移动
    void moveLeft();// 蛇向左移动
    void moveRight();// 蛇向右移动

    bool checkFail();// 判断游戏是否结束

private:
    snakeDirect moveDirect = snakeDirect::UP;// 定义蛇的移动方向,默认朝上
    bool isGameStart = false;// 表示是否开始游戏
    QTimer* timer;// 定时器

    QList<QRectF> snakeList;// 表示贪吃蛇链表
    QRectF foodRect;// 表示食物节点

    const int kDefaultTimeout = 200;// 表示贪吃蛇默认移动时间
    const int kSnakeNodeWidth = 20;// 表示蛇身体节点的宽度
    const int kSnakeNodeHeight = 20;// 表示蛇身体节点的高度

    int moveTimeout = kDefaultTimeout;

    QSound* sound;// 音频
};

#endif // GAMEROOM_H

🌴gameroom.cpp 

#include "gameroom.h"
#include "gameselect.h"
#include <QIcon>
#include <QPixmap>
#include <QPainter>
#include <QTimer>
#include <QDebug>
#include <QFile>
#include <QPushButton>
#include <QMessageBox>
#include <QTextEdit>
#include <QSound>

GameRoom::GameRoom(QWidget *parent) : QWidget(parent)
{
    // 初始化窗口设置
    this->setFixedSize(1000, 800);
    this->setWindowTitle("贪吃蛇游戏");
    this->setWindowIcon(QIcon(":res/ico.png"));

    // 初始化贪吃蛇
    snakeList.push_back(QRectF(this->width() * 0.5, this->height() * 0.5, kSnakeNodeWidth, kSnakeNodeHeight));
    moveUp();
    moveUp();

    createNewFood();// 初始化食物

    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, [=](){
        int count = 1;

        if (snakeList.front().intersects(foodRect)){
            createNewFood();
            ++count;

            QSound::play(":res/eatfood.wav");
        }

        while (count--) {
            // 处理蛇的移动
            switch (moveDirect) {
            case snakeDirect::UP:
                moveUp();
                break;
            case snakeDirect::DOWN:
                moveDown();
                break;
            case snakeDirect::LEFT:
                moveLeft();
                break;
            case snakeDirect::RIGHT:
                moveRight();
                break;
            default:
                qDebug() << "非法移动方向";
                break;
            }
        }
        // 删除最后一个节点
        snakeList.pop_back();
        update();
    });


    // 开始游戏 & 暂停游戏
    QPushButton* startBtn = new QPushButton(this);
    QPushButton* stopBtn = new QPushButton(this);

    QFont ft("楷体", 15, QFont::ExtraLight, false);

    // 设置按钮的位置
    startBtn->move(840, 150);
    stopBtn->move(840, 220);

    // 设置按钮文本
    startBtn->setText("开始");
    stopBtn->setText("暂停");

    // 设置按钮样式
    startBtn->setStyleSheet("QPushButton {"
                                "background-color: white;"// 按钮背景颜色
                                "color: #4CAF50;" // 文字颜色
                                "border: 2px solid #4CAF50;" // 边框
                                "border-radius: 8px;" // 边框圆角
                                "padding: 10px 20px;" // 内边距
                            "}");

    stopBtn->setStyleSheet("QPushButton {"
                                "background-color: white;"// 按钮背景颜色
                                "color: #4CAF50;" // 文字颜色
                                "border: 2px solid #4CAF50;" // 边框
                                "border-radius: 8px;" // 边框圆角
                                "padding: 10px 20px;" // 内边距
                            "}");

    // 设置按钮字体格式
    startBtn->setFont(ft);
    stopBtn->setFont(ft);

    connect(startBtn, &QPushButton::clicked, [=](){
        sound = new QSound(":res/Trepak.wav");
        sound->play();
        sound->setLoops(-1);// 循环播放

        isGameStart = true;
        timer->start(moveTimeout);
    });

    connect(stopBtn, &QPushButton::clicked, [=](){
        sound->stop();

        isGameStart = false;
        timer->stop();
    });


    // 方向控制
    QPushButton* Up = new QPushButton(this);
    QPushButton* Down = new QPushButton(this);
    QPushButton* Left = new QPushButton(this);
    QPushButton* Right = new QPushButton(this);

    QString buttonStyle =
                    "QPushButton {"
                    "    border: 2px solid #8f8f8f;"
                    "    border-radius: 10px;"
                    "    background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #f0f0f0, stop: 1 #bbbbbb);"
                    "    color: #000000;"
                    "}"
                    "QPushButton:hover {"
                    "    background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffffff, stop: 1 #8f8f8f);"
                    "}"
                    "QPushButton:pressed {"
                    "    background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #dadada, stop: 1 #949494);"
                    "}";

    Up->move(880, 400);
    Down->move(880, 480);
    Left->move(840, 440);
    Right->move(920, 440);

    QFont font("楷体", 15, QFont::ExtraLight, false);
    Up->setStyleSheet(buttonStyle);

    Up->setFont(font);
    Up->setText("↑");

    Down->setStyleSheet(buttonStyle);
    Down->setFont(font);
    Down->setText("↓");

    Left->setStyleSheet(buttonStyle);
    Left->setFont(font);
    Left->setText("←");

    Right->setStyleSheet(buttonStyle);
    Right->setFont(font);
    Right->setText("→");

    connect(Up, &QPushButton::clicked, [=](){
        if (moveDirect != snakeDirect::DOWN){
            moveDirect = snakeDirect::UP;
        }
    });

    connect(Down, &QPushButton::clicked, [=](){
        if (moveDirect != snakeDirect::UP){
            moveDirect = snakeDirect::DOWN;
        }
    });

    connect(Left, &QPushButton::clicked, [=](){
        if (moveDirect != snakeDirect::RIGHT){
            moveDirect = snakeDirect::LEFT;
        }
    });

    connect(Right, &QPushButton::clicked, [=](){
        if (moveDirect != snakeDirect::LEFT){
            moveDirect = snakeDirect::RIGHT;
        }
    });


    // 退出游戏按钮
    QPushButton* exitGame = new QPushButton(this);
    exitGame->setStyleSheet("QPushButton {"
                            "background-color: white;" // 按钮背景颜色
                            "color: #4CAF50;" // 文字颜色
                            "border: 2px solid #4CAF50;" // 边框
                            "border-radius: 8px;" // 边框圆角
                            "padding: 10px 20px;" // 内边距
                            "}");

    exitGame->move(840, 725);
    exitGame->setText("退出");
    exitGame->setFont(font);


    // 消息提示
    QMessageBox* messageBox = new QMessageBox(this);
    QPushButton* okbtn = new QPushButton("ok");
    QPushButton* cancelbtn = new QPushButton("cancel");

    messageBox->setWindowIcon(QIcon(":res/ico.png"));// 设置消息对话框的图标
    messageBox->setWindowTitle("退出游戏");// 设置消息对话框的标题
    messageBox->setText("确认退出游戏吗?");// 设置消息对话框内容
    messageBox->setIcon(QMessageBox::Question); //设置消息对话框类型

    messageBox->addButton(okbtn, QMessageBox::AcceptRole);//Accept Role:接受的角色
    messageBox->addButton(cancelbtn, QMessageBox::RejectRole);//Reject Role:排斥作用


    connect(exitGame, &QPushButton::clicked, [=](){
        messageBox->show();// 消息提示
        QSound::play(":res/clicked.wav");
        messageBox->exec();// 阻塞等待用户输入

        GameSelect* s = new GameSelect;

        if (messageBox->clickedButton() == okbtn){
            this->close();
            //如果点击了 Ok 按钮,那么就会跳转到游戏关卡界⾯
            s->show();
            s->setGeometry(this->geometry());
            QSound::play(":res/clicked.wav");
        }else{
            messageBox->close();
            QSound::play(":res/clicked.wav");
        }
    });
}



// 重写绘图事件,实现贪吃蛇游戏游戏背景效果
void GameRoom::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QPixmap pix;
    pix.load(":res/game_room.png");
    painter.drawPixmap(0, 0, 800, 800, pix);
    painter.setRenderHint(QPainter::Antialiasing);// 设置抗锯齿

    // 蛇 = 蛇头 + 蛇身体 + 蛇尾巴
    // 渲染蛇头
    if (moveDirect == snakeDirect::UP){
        pix.load(":res/up.png");
    }else if (moveDirect == snakeDirect::DOWN){
        pix.load(":res/down.png");
    }else if (moveDirect == snakeDirect::LEFT){
        pix.load(":res/left.png");
    }else {
        pix.load(":res/right.png");
    }

    auto snakeHead = snakeList.front();
    painter.drawPixmap(snakeHead.x(), snakeHead.y(), snakeHead.width(), snakeHead.height(), pix);

    // 渲染蛇身体
    pix.load(":res/Bd.png");
    for (int i = 1; i < snakeList.size() - 1; i++){
        auto node = snakeList.at(i);
        painter.drawPixmap(node.x(), node.y(), node.width(), node.height(), pix);
    }

    // 渲染蛇尾巴
    auto snakeTail = snakeList.back();
    painter.drawPixmap(snakeTail.x(), snakeTail.y(), snakeTail.width(), snakeTail.height(), pix);

    // 渲染食物
    pix.load(":res/food.bmp");
    painter.drawPixmap(foodRect.x(), foodRect.y(), foodRect.width(), foodRect.height(), pix);

    // 渲染右边区域
    pix.load(":res/bg1.png");
    painter.drawPixmap(800, 0, 200, 1000, pix);

    // 渲染分数
    pix.load(":res/sorce_bg.png");
    painter.drawPixmap(this->width() * 0.85, this->height() * 0.02, 100, 50, pix);
    QPen pen;
    pen.setColor(Qt::black);
    painter.setPen(pen);
    QFont font("楷体", 15, QFont::ExtraLight, false);
    painter.setFont(font);
    painter.drawText(this->width() * 0.9, this->height() * 0.075, QString("%1").arg(snakeList.size()));

    // 往文件中写入得分
    int c = snakeList.size();
    QFile file("D:/code/qt/Snake/1.txt");
    if (file.open(QIODevice::WriteOnly | QIODevice::Text)){
        QTextStream out(&file);
        int num = c;
        out << num;// 将int类型数据写入文件
        file.close();
    }

    // 如果失败,渲染 GAME OVER
    if (checkFail()){
        pen.setColor(Qt::red);
        painter.setPen(pen);
        QFont font("微软雅黑", 40, QFont::ExtraLight, false);
        painter.setFont(font);
        painter.drawText(this->width() * 0.1, this->height() * 0.5, QString("GAME OVER"));
        timer->stop();

        QSound::play(":res/gameover.wav");
        sound->stop();
    }
}



// 创建食物
void GameRoom::createNewFood()
{
    foodRect = QRectF(
                qrand() % (this->width() / kSnakeNodeWidth) * kSnakeNodeWidth,
                qrand() % (this->height() / kSnakeNodeHeight) *kSnakeNodeHeight,
                kSnakeNodeWidth,
                kSnakeNodeHeight);
}



// 实现向上移动
void GameRoom::moveUp()
{
    QPointF leftTop;// 左上角坐标
    QPointF rightBottom;// 右下角坐标

    auto snakeNode = snakeList.front();

    int headX = snakeNode.x();
    int headY = snakeNode.y();

    // 如果上面剩余的空间不够放入⼀个新的节点, 相当于到墙边了, 要处理穿墙逻辑
    if (headY < 0){
        leftTop = QPointF(headX, this->height() - kSnakeNodeHeight);
    }else {
        leftTop = QPointF(headX, headY - kSnakeNodeHeight);
    }

    rightBottom = leftTop + QPointF(kSnakeNodeWidth, kSnakeNodeHeight);
    snakeList.push_front(QRectF(leftTop, rightBottom));
}



// 实现向下移动
void GameRoom::moveDown()
{
    QPointF leftTop;// 左上角坐标
    QPointF rightBottom;// 右下角坐标

    auto snakeNode = snakeList.front();

    int headX = snakeNode.x();
    int headY = snakeNode.y();

    // 如果下面剩余的空间不够放入⼀个新的节点, 相当于到墙边了, 要处理穿墙逻辑
    if (headY > this->height()){
        leftTop = QPointF(headX, 0);
    }else {
        leftTop = snakeNode.bottomLeft();// 返回矩形左下角的坐标
    }

    rightBottom = leftTop + QPointF(kSnakeNodeWidth, kSnakeNodeHeight);
    snakeList.push_front(QRectF(leftTop, rightBottom));
}



// 实现向左移动
void GameRoom::moveLeft()
{
    QPointF leftTop;// 左上角坐标
    QPointF rightBottom;// 右下角坐标

    auto snakeNode = snakeList.front();

    int headX = snakeNode.x();
    int headY = snakeNode.y();

    // 如果左面剩余的空间不够放入⼀个新的节点, 相当于到墙边了, 要处理穿墙逻辑
    if (headX < 0){
        leftTop = QPointF(800 - kSnakeNodeWidth, headY);
    }else {
        leftTop = QPointF(headX - kSnakeNodeWidth, headY);
    }

    rightBottom = leftTop + QPointF(kSnakeNodeWidth, kSnakeNodeHeight);
    snakeList.push_front(QRectF(leftTop, rightBottom));
}



// 实现向右移动
void GameRoom::moveRight()
{
    QPointF leftTop;// 左上角坐标
    QPointF rightBottom;// 右下角坐标

    auto snakeNode = snakeList.front();

    int headX = snakeNode.x();
    int headY = snakeNode.y();

    // 如果右面剩余的空间不够放入⼀个新的节点, 相当于到墙边了, 要处理穿墙逻辑
    if (headX > 760){
        leftTop = QPointF(0, headY);
    }else {
        leftTop = snakeNode.topRight();// 返回矩形右上角的坐标
    }

    rightBottom = leftTop + QPointF(kSnakeNodeWidth, kSnakeNodeHeight);
    snakeList.push_front(QRectF(leftTop, rightBottom));
}



// 判断游戏是否结束
bool GameRoom::checkFail()
{
    // 判断头尾是否出现相交
    for (int i = 0; i < snakeList.size(); i++){
        for (int j = i + 1; j < snakeList.size(); j++){
            if (snakeList.at(i) == snakeList.at(j)){
                return true;
            }
        }
    }
    return false;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2062829.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

重装后的电脑怎么分区?轻松优化存储空间

电脑重装系统是解决许多软件问题和提升性能的有效方法。然而&#xff0c;重装系统后&#xff0c;合理的硬盘分区不仅能提高数据管理效率&#xff0c;还有助于保护系统安全。本文将详细介绍如何在重装电脑后进行合理的分区&#xff0c;帮助您更好地管理和使用您的电脑。 一、了解…

傅里叶变换与拉普拉斯变换:联系、区别及其应用

1. 傅里叶变换和拉普拉斯变换的定义 1.1 傅里叶变换的定义 傅里叶变换是将时间域信号转换为频率域信号的数学工具&#xff0c;由正向和逆变换组成。它将信号分解为正弦波和余弦波的组合&#xff0c;适用于周期性和非周期性信号分析。 1.2 拉普拉斯变换的定义 拉普拉斯变换是…

武汉流星汇聚:跨境电商领航者,以自营经验赋能万企,共绘出海蓝图

在数字经济浪潮席卷全球的今天&#xff0c;跨境电商作为国际贸易的新引擎&#xff0c;正以前所未有的速度改变着全球商业格局。在这片充满机遇的蓝海中&#xff0c;武汉流星汇聚电子商务有限公司犹如一颗璀璨的流星&#xff0c;划破长空&#xff0c;以其独特的优势和卓越的成就…

数学强化| 李林880重点题速刷计划

快9月了&#xff0c;有的同学还没开始强化&#xff0c;进度确实有点慢了&#xff0c;有同学问&#xff1a; 刚开始强化&#xff0c;880题该如何快速刷完&#xff1f; 听我说&#xff0c;别急&#xff01;越是强化开始的晚&#xff0c;就越不能急&#xff0c;因为强化的作用有两…

易基因:泪腺RRBS+RNA-seq揭示Sjögren综合征相关干眼症的潜在基因|项目文章

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 Sjgren综合征&#xff08;Sjgren’s syndrome&#xff0c;SS&#xff09;相关干眼症是一种以泪腺&#xff08;lacrimal glands&#xff0c;LGs&#xff09;慢性炎症为特征的难治性自身免…

mysql InnoDB引擎各种隔离级别的加锁机制

文章目录 概要前置知识了解各种隔离锁的验证小结 概要 我们都知道&#xff0c;mysql的InnoDB引擎在各种隔离级别下的加锁机制都是有差异的&#xff0c;但是对于各种隔离级别下如何加锁大家可能不太了解&#xff0c;今天我就通过一篇文章去带领大家去分析一下各个隔离级别的加锁…

【HTML】使用Javascript制作网页

1、Javascript的语法规则 JavaScript程序按照在HTML文件中出现的顺序逐行执行。JavaScript严格区分字母大小写。在JavaScript中&#xff0c;每行结尾的分号可有可无。JavaScript中主要包括两种注释&#xff1a;单行注释和多行注释。单行注释使用双斜线“//”作为注释标签&…

AI绘画商业实战,深入剖析Stable Diffusion 服装模特精准换装脱Y,AI虚拟模特变现教程

大家好&#xff0c;我是灵魂画师向阳 在之前的文章中&#xff0c;我们已经深入讲解了SD与ControlNet基础知识和原理。接下来我们将结合这一堆基础工具法宝组合使用&#xff0c;完成一些有意义的AIGC商业实战案例分享。 本文是来自一位粉丝的现实需求案例&#xff1a;电商服装…

人在上海ip显示在安徽怎么回事?怎么办

在这个信息爆炸的时代&#xff0c;网络已成为我们生活中不可或缺的一部分。无论是工作、学习还是娱乐&#xff0c;网络都以其独特的魅力渗透进我们生活的每一个角落。然而&#xff0c;随着网络技术的不断发展&#xff0c;一些看似不可思议的现象也逐渐浮出水面。比如&#xff0…

海思SD3403/SS928V100开发(16)Tsensor驱动开发

1. 前言 由于需要检测SD3403芯片内部实时温度,需要开发Tsensor传感器驱动和应用 查看手册发现SD3403内部有三个Tsensor传感器 可以参考之前我写的35系列平台Tsensor驱动开发记录 海思35系列平台Tsensor驱动开发(1)驱动编写_t sensor-CSDN博客 海思35系列平台Tsensor驱动…

什么是埋点测试,app埋点测试怎么做?

前言 埋点测试是指在应用程序或网站中预设检查点&#xff0c;收集程序运行时的数据&#xff0c;以便于后续对程序进行性能分析或故障排查。埋点测试通常用于监控和追踪用户在软件产品中的行为&#xff0c;以收集有关用户体验、功能使用情况和潜在问题的数据。这些数据对于软件…

哪些ai取名网站免费?盘点4大好用的ai取名字自动生成器

在忙碌而喧嚣的都市生活中&#xff0c;越来越多人选择养宠物作为自己的精神寄托。当你决定迎接一只新生命回家时&#xff0c;除了准备好食物等必需品外&#xff0c;更重要的是给它起一个既好听又有意义的名字。 然而&#xff0c;有时候想出一个合适的名字并不容易&#xff0c;…

中电金信:稳定运行超百天!业内首个100%全栈国产化手机银行上线

日前&#xff0c;国内首个全栈国产化手机银行-华润银行新一代手机银行已经正式投产超百天&#xff0c;系统运行稳定&#xff0c;性能显著提升&#xff0c;客户体验明显改善&#xff0c;达成了“新理念、新体验、新技术、新底座”的建设目标&#xff0c;整体水平处于行业中上游。…

对射式光电开关应用

图 5-38a所示是自动启停扶梯的示意图。扶梯入口安装一个对射式光电开关&#xff0c;当有人走到扶梯口要上扶梯时&#xff0c;挡住了光电开关发光部分所发出的光&#xff0c;使得受光部分无法接收到光信号&#xff0c;由此控制扶梯启动&#xff0c;延时一段时间后&#xff0c;扶…

ubuntu上cmake3.30.2的安装

引言 安装下载安装包将安装包从windows拷贝到ubuntu解压进入解压后的文件夹执行boostrap编译CMake安装CMake查看是否安装成功 目前的ubuntu系统是20.04.4&#xff0c;用命令行安装了cmake的版本是3.16的&#xff0c;由于项目需要升级cmake到cmake3.22之上&#xff0c;使用命令行…

揭秘led台灯对眼睛好不好?护眼台灯真的护眼吗?台灯要这样选!

在当今数字化时代&#xff0c;长时间面对电脑屏幕和各种电子设备已成为日常生活的一部分&#xff0c;这对我们的视力构成了前所未见的挑战。目前中国的近视情况十分严峻&#xff0c;尤其在青少年群体中表现得更为突出。因此&#xff0c;科学用眼、合理安排用眼时间和增加户外活…

新手起步:探索AWS新账户的服务器部署能力与限制

对于刚刚注册Amazon Web Services (AWS)账户的用户来说&#xff0c;一个常见的疑问是&#xff1a;我能立即开始部署服务器吗&#xff1f;这个问题的答案是肯定的&#xff0c;但同时也需要注意一些重要的细节。我们九河云将深入探讨新注册的AWS账户在服务器部署方面的能力和注意…

AI大模型应用开发实战-Agent应用对话情感优化

1 使用prompt设计agent性格与行为 添加系统 prompt&#xff1a; 代码语言&#xff1a;python 代码运行次数&#xff1a;0 复制 Cloud Studio 代码运行 self.SYSTEMPL """你是一个非常厉害的算命先生&#xff0c;你叫JavaEdge人称Edge大师。以下是你的个人…

数据库MySQL多表设计、查询

目录 1.概述 2.一对多 3.一对一 4.多对多 5.多表查询 5.1内连接 5.2外连接 5.3子查询 1.概述 项目开发中,在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所以各个…

信息集成系统:打造智慧化的数字化平台

在现代社会中&#xff0c;信息集成系统已经成为不可或缺的一部分。它们不仅可以帮助企业管理各种数据和资源&#xff0c;还可以提供更高效的工作流程和更好的用户体验。本文将介绍信息集成系统的定义、优势、应用以及最新的技术趋势。 什么是信息集成系统&#xff1f;它是一种集…