贪吃蛇(使用QT)

news2024/12/23 18:34:11

贪吃蛇小游戏

  • 一.项目介绍
  • **[贪吃蛇项目地址](https://gitee.com/strandingzy/QT/tree/zyy/snake)**
    • 界面一:游戏大厅
    • 界面二:关卡选择界面
    • 界面三:游戏界面
  • 二.项目实现
    • 2.1 游戏大厅
    • 2.2关卡选择界面
    • 2.3 游戏房间
      • 2.3.1 封装贪吃蛇数据结构
      • 2.3.2 初始化游戏房间界面
      • 2.3.3 蛇的移动
      • 2.3.4 初始化贪吃蛇生体和食物节点
      • 2.3.5 实现定时器的超时槽函数
      • 2.3.6 实现各个方向的移动
      • 2.3.7 重写绘图事件函数进行渲染
      • 2.3.8 检查是否自己会撞到自己
      • 2.3.9 设置游戏开始和游戏暂停按钮
      • 2.3.10 设置游戏开始和游戏暂停按钮
      • 2.3.11 获取历史战绩
  • 代码的总体文件
  • gamehall.h
  • gameroom.h
  • gameselect.h
  • gamehall.cpp
  • gameroom.cpp
  • gameselect.cpp

一.项目介绍

贪吃蛇游戏是⼀款休闲益智类游戏。它通过控制蛇头方向吃食物,从而使得蛇变得越来越长。在本游戏中设置了上下左右四个⽅向键来控制蛇的移动方向。⻝物的产⽣是随机⽣成的,当蛇每吃⼀次⻝物就会增加⼀节身体,同时游戏积分也会相应的加⼀。在本游戏的设计中,蛇的⾝体会越吃越长,身体越长对应的难度就越⼤,因为⼀旦蛇头和⾝体相交游戏就会结束。

贪吃蛇项目地址

界面一:游戏大厅

在这里插入图片描述

当用户点击 “开始游戏” 按钮之后,就会进⼊到关卡选择界面。

界面二:关卡选择界面

在这里插入图片描述
在关卡选择界面上设置了三个游戏模式按钮,分别是:简单模式、正常模式、困难模式;⼀个 “历史战绩” 按钮;⼀个返回游戏大厅界⾯的按钮。

界面三:游戏界面

在这里插入图片描述
在游戏界⾯,如果想要开始游戏,⾸先点击 “开始” 按钮,此时蛇就会移动并且还有背景⾳效。如果想要暂停游戏,那么点击 “暂停” 按钮即可。当我们在游戏时,可以通过右边控制区域的上下左右⽅向键来控制蛇的移动。当蛇每吃⼀次⻝物时,伴随有吃⻝物的⾳效,蛇⾝会增加⼀节⻓度,并且分数
积分也会相应的加⼀。最后在控制区域的右下⻆布局了⼀个 “退出” 按钮,当点击 “退出” 按钮时,就会有⼀个弹窗提⽰,如下在这里插入图片描述
如果我们点击 “Ok” ,则会退出到游戏关卡选择界⾯,此时我们可以重新选择游戏模式。如果点击“Cancel” ,则不会退出游戏,我们还是处在当前游戏房间界⾯。

二.项目实现

2.1 游戏大厅

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

  • 背景图片的渲染,通过QT的绘图事件完成
void GameHall::paintEvent(QPaintEvent *event)
{
    //实例化画家对象
     QPainter painter(this);

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

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

}
  • 按钮的响应通过QT的信号槽机制完成
GameHall::GameHall(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::GameHall)
{
    ui->setupUi(this);
    this->setFixedSize(1500,1000);//设置窗口大小
    this->setWindowTitle(QString("贪吃蛇游戏"));
    this->setWindowIcon(QIcon(":res/ico.png"));//设置窗口图标
    //
    QFont font("华文行楷",20);
    QPushButton * strbutton = new QPushButton(this);
    strbutton->setText("开始游戏");
    strbutton->setFont(font);
    strbutton->move(650,700);
    strbutton->setShortcut(QKeySequence(Qt::Key_Space));
    strbutton->setStyleSheet("QPushButton{border:0px}");//给按钮设置样式  去除边框


    GameSelect *  gameSelect = new GameSelect;
    connect(strbutton,&QPushButton::clicked,[=](){
        this->close();//当前窗口关闭
        gameSelect->setGeometry(this->geometry());//设置第二个窗口于当前窗口一致
        gameSelect->show();//新的窗口打开
        QSound::play(":res/clicked.wav");
    });
}

  • 这里的Qfound类,使用需要在.pro 的文件中加上 QT += core gui multimedia
  • 这里使用strbutton->setShortcut(QKeySequence(Qt::Key_Space));给按钮添加了快捷键
  • 使用strbutton->setStyleSheet("QPushButton{border:0px}");去除按钮边框样式
  • 通过槽函数跳转到新的窗口 ,在槽函数中使用了Lambda 表达式
    通过QSound创建音频对象,使点击按钮有声音

在这里插入图片描述

2.2关卡选择界面

关卡选择界⾯包含⼀个背景图和五个按钮,背景图的绘制和游戏⼤厅背景图绘制⼀样,同样使用的是Qt中的绘图事件,下面依次介绍。

  • 背景图片的渲染,通过QT的绘图事件完成
void GameHall::paintEvent(QPaintEvent *event)
{
    //实例化画家对象
     QPainter painter(this);

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

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

}
  • 按钮的响应通过QT的信号和槽机制完成
#include "gameselect.h"
#include"gameroom.h"
#include<QIcon>
#include<QPushButton>
#include"gamehall.h"
#include<QSound>
#include<QPainter>


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

    QPushButton* backBtn = new QPushButton(this);//返回按钮
    backBtn->move(1400,900);
    backBtn->setIcon(QIcon(":res/back.png"));
    backBtn->setShortcut(QKeySequence(Qt::Key_Escape));

    connect(backBtn,&QPushButton::clicked,[=](){
         this->close();
        GameHall * gameHall = new GameHall;
        gameHall->show();

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

    QFont font("华文行楷",20);

    gameroom*gameRoom = new gameroom;

    QPushButton* simpleBtn = new QPushButton(this);
    simpleBtn->move(650,300);
    simpleBtn->setFont(font);
    simpleBtn->setText("简单模式");
    connect(simpleBtn,&QPushButton::clicked,[=](){
        this->close();
        gameRoom->setGeometry(this->geometry());
        gameRoom->show();
    });

    QPushButton*normalBtn = new QPushButton(this);
    normalBtn->move(650,400);
    normalBtn->setFont(font);
    normalBtn->setText("正常模式");
    connect(normalBtn,&QPushButton::clicked,[=](){
        this->close();
        gameRoom->setGeometry(this->geometry());
        gameRoom->show();
    });


    QPushButton*hardBtn = new QPushButton(this);
    hardBtn->move(650,500);
    hardBtn->setFont(font);
    hardBtn->setText("困难模式");
    connect(hardBtn,&QPushButton::clicked,[=]{
        this->close();
        gameRoom->setGeometry(this->geometry());
        gameRoom->show();
    });


    QPushButton*hisBtn = new QPushButton(this);
    hisBtn->move(650,600);
    hisBtn->setFont(font);
    hisBtn->setText("历史战绩");
}

void GameSelect::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);//实例化画家对象
    QPixmap pix(":res/game_select.png");

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

connect(hardBtn,&QPushButton::clicked,[=]{ this->close(); gameRoom->setGeometry(this->geometry()); gameRoom->show(); });
通过关联信号槽,使得可以选择游戏模式,首先把当前窗口关闭,然后显示新的窗口,在设置与当前窗口的大小一样

2.3 游戏房间

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

  • 背景的绘制
  • 蛇的绘制,蛇的移动,判断蛇是否会撞到自己
  • 积分的累加和绘制

怎么让蛇动起来?

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

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

  • 判断蛇头和食物的坐标是否相交,

怎么控制蛇的移动?

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

2.3.1 封装贪吃蛇数据结构

#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 moveUp();
    void moveDown();
    void moveLeft();
    void moveRight();

    bool checkFail();

    void createNewFood();

    void  setTimeout(int timeout){moveTime=timeout;}

private:
    const int KSnakeNodeWidth = 20;
    const int KSnakeNodeHeight = 20;

    const int KDefaultTimeout = 200;//移动速度

    QList<QRectF> snakeList;//表示链表
    QRectF foodRect;//食物
    SnakeDirect  moveDirect = SnakeDirect::UP;//默认移动方向
    QTimer *timer;//定时器

    bool  isGameStart = false;//游戏的开始


    QSound *sound;

    int moveTime = KDefaultTimeout;
};

#endif // GAMEROOM_H

2.3.2 初始化游戏房间界面

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

gameroom::gameroom(QWidget *parent) : QWidget(parent)
{
    this->setFixedSize(1500,1000);
    this->setWindowIcon(QIcon(":res/ico.png"));
    this->setWindowTitle("游戏房间");
}

2.3.3 蛇的移动

蛇的移动方向为:上、下、左、右。通过在游戏房间中布局四个按钮来控制蛇的移动方向。

· 注意: 这里贪吃蛇不允许直接掉头, 比如当前是向上的, 不能直接修改为向下。

 QPushButton*up= new QPushButton(this);
    QPushButton*down= new QPushButton(this);
    QPushButton*left= new QPushButton(this);
    QPushButton*right= new QPushButton(this);

    up->move(1320,750);
    down->move(1320,900);
    left->move(1220,825);
    right->move(1420,825);

    up->setText("↑");
//    up->setShortcut(QKeySequence(Qt::Key_W));
    down->setText("↓");
//    down->setShortcut(QKeySequence(Qt::Key_S));
    left->setText("←");
//    left->setShortcut(QKeySequence(Qt::Key_A));
    right->setText("→");
//    left->setShortcut(QKeySequence(Qt::Key_D));

    up->setStyleSheet("QPushButton{border:0px}");
    down->setStyleSheet("QPushButton{border:0px}");
    left->setStyleSheet("QPushButton{border:0px}");
    right->setStyleSheet("QPushButton{border:0px}");

    QFont ft("宋体",36);
    up->setFont(ft);
    down->setFont(ft);
    right->setFont(ft);
    left->setFont(ft);

    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::LEFT)
        {
            moveDirect = SnakeDirect::RIGHT;
        }
    });

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

2.3.4 初始化贪吃蛇生体和食物节点

GameRoom::GameRoom(QWidget *parent) :
 QWidget(parent),
 ui(new Ui::Widget)
 { snakeList.push_back(QRectF(this->width()*0.5,this->height()*0.5,KSnakeNodeWidth,KSnakeNodeHeight));

    moveUp();
    moveUp();
    moveUp();


    //创建食物
    createNewFood();
    }

moveUp() 的功能是将蛇向上移动⼀次, 即在上⽅新增⼀个节点, 但不删除尾部节点。
createNewFood() ⽅法的功能是随机创建⼀个⻝物节点:

void gameroom::createNewFood()
{
    foodRect = QRectF(qrand()%(1500/KSnakeNodeWidth)*KSnakeNodeWidth,
                      qrand()%(this->height()/KSnakeNodeHeight)*KSnakeNodeHeight,
                      KSnakeNodeWidth,KSnakeNodeHeight);
}

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

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

  • ⾸先, 需要判断蛇头和⻝物节点坐标是否相交
    • 如果相交, 需要创建新的⻝物节点, 并且需要更新蛇的⻓度, 所以 cnt 需要 +1 ;
    • 如果不相交, 那么直接处理蛇的移动即可。
  • 根据蛇移动方向 moveDirect 来处理蛇的移动, 处理方法是在前方加⼀个, 并且删除后方节点;
  • 重新触发绘图事件, 更新渲染
timer = new QTimer(this);

    connect(timer,&QTimer::timeout,[=]{
        int cnt =1 ;

        if(snakeList.front().intersects(foodRect))
        {
            //intersects判断会不会相交
            createNewFood();
            cnt++;
            QSound::play(":res/eatfood.wav");
        }

        while (cnt--) {
            switch (moveDirect) {
                case SnakeDirect::UP:
                    moveUp();
                    break;
                case SnakeDirect::DOWN:
                    moveDown();
                    break;
                case SnakeDirect::LEFT:
                    moveLeft();
                    break;
                case SnakeDirect::RIGHT:
                    moveRight();
                    break;
            }
        }

        snakeList.pop_back();//删除最后一个节点
        update();
    });

2.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(1500 - 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(heady + KSnakeNodeWidth> 1200)
    {
        leftTop = QPointF(0,heady);

    }
    else {
        leftTop = snakeNode.bottomRight();
    }

    rightBottom = leftTop + QPointF(KSnakeNodeWidth,KSnakeNodeHeight);


    snakeList.push_front(QRectF(leftTop,rightBottom));
}

2.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,1200,1400,pix);

    pix.load(":res/bg1.png");
    painter.drawPixmap(1200,0,300,1100,pix);


    //绘制蛇

    //头
    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 if(moveDirect == SnakeDirect::RIGHT){
        pix.load(":res/right.png");
    }

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

    //身体
    pix.load(":res/Bd.png");
    for (int  i= 1;i < snakeList.size()-1;i++) {
        auto node = snakeList.at(i);   //at 获取每一个节点
        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.png");
    painter.drawPixmap(foodRect.x(),foodRect.y(),foodRect.width(),foodRect.height(),pix);



    //分数
    pix.load(":res/sorce_bg.png");
    painter.drawPixmap(this->width()*0.85,this->height()*0.1,90,40,pix);

    QPen pen;
    pen.setColor(Qt::black);
    QFont font("方正舒体",22);
    painter.setFont(font);
    painter.setPen(pen);
    painter.drawText(this->width()*0.9,this->height()*0.06,QString("%1").arg(snakeList.size()));


    //往文件中写分数
    int c = snakeList.size();
    QFile file("C:/Users/zyy/Desktop/1.txt");
    if(file.open(QIODevice::WriteOnly| QIODevice::Text))
    {
        QTextStream out(&file);
        int num = c;
        out<<num ;
        file.close();
    }

    //绘制有戏失败的效果
    if(checkFail())
    {
        pen.setColor(Qt::red);
        painter.setPen(pen);
        QFont font("方正舒体",22);
        painter.setFont(font);
        painter.drawText(this->width()*0.45,this->height()*0.5,QString("GAME OVER!"));
        timer->stop();
        QSound::play(":res/gameover.wav");

        sound->stop();
    }
}

2.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;
}

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

     QPushButton * strBtn = new QPushButton(this);
    QPushButton * stopBtn = new QPushButton(this);

    strBtn->move(1270,100);
    stopBtn->move(1270,170);
    strBtn->setText("开始");
    strBtn->setShortcut(QKeySequence(Qt::Key_C+Qt::CTRL));
    stopBtn->setText("暂停");
    stopBtn->setShortcut(QKeySequence(Qt::Key_V+Qt::CTRL));

    QFont font("楷体",20);
    strBtn->setFont(font);
    stopBtn->setFont(font);


    connect(strBtn,&QPushButton::clicked,[=]{
       
        sound = new QSound(":res/Trepak.wav");
        sound->play();
        sound->setLoops(-1);//一直循环播放
        
        isGameStart = true;
        timer->start(moveTime);
    });

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

    });

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

当我们点击退出游戏按钮时,当前游戏房间窗⼝不会⽴即退出,而是会弹窗提示,提示我们是否要退出游戏,效果如下图示:
在这里插入图片描述
这个弹窗提示我们是通过 Qt 中的消息盒子来实现的,具体实现过程如下:

 //退出游戏
    QPushButton*ExitBtn = new QPushButton(this);
    ExitBtn->setText("退出游戏");
    ExitBtn->move(1240,400);
    ExitBtn->setFont(font);
    QMessageBox *msg = new QMessageBox(this);
    QPushButton *ok = new QPushButton("ok");
    QPushButton *canel = new QPushButton("canel");

    msg->addButton(ok,QMessageBox::AcceptRole);
    msg->addButton(canel,QMessageBox::RejectRole);


    msg->setWindowTitle("退出游戏");
    msg->setText("确认退出游戏吗");

    connect(ExitBtn,&QPushButton::clicked,[=](){
        msg->show();
        msg->exec();//时间轮询
        QSound::play("res/clicked.wav");

        GameSelect *select = new GameSelect;


        if(msg->clickedButton()==ok)
        {
            this->close();
            select->show();
        }
        else {
            msg->close();
        }
    });
}

2.3.11 获取历史战绩

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

  • 写文件: 向文件中写入蛇的长度
    int c = snakeList.size();
    QFile file("C:/Users/zyy/Desktop/1.txt");
    if(file.open(QIODevice::WriteOnly| QIODevice::Text))
    {
        QTextStream out(&file);
        int num = c;
        out<<num ;
        file.close();
    }
  • 读文件:读取写入文件中蛇的长度
   int c = snakeList.size();
    QFile file("C:/Users/zyy/Desktop/1.txt");
    if(file.open(QIODevice::WriteOnly| QIODevice::Text))
    {
        QTextStream out(&file);
        int num = c;
        out<<num ;
        file.close();
    }

代码的总体文件

在这里插入图片描述

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

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 moveUp();
    void moveDown();
    void moveLeft();
    void moveRight();

    bool checkFail();

    void createNewFood();

    void  setTimeout(int timeout){moveTime=timeout;}

private:
    const int KSnakeNodeWidth = 20;
    const int KSnakeNodeHeight = 20;

    const int KDefaultTimeout = 200;//移动速度

    QList<QRectF> snakeList;//表示链表
    QRectF foodRect;//食物
    SnakeDirect  moveDirect = SnakeDirect::UP;//默认移动方向
    QTimer *timer;//定时器

    bool  isGameStart = false;//游戏的开始


    QSound *sound;

    int moveTime = KDefaultTimeout;
};

#endif // GAMEROOM_H

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:

};

#endif // GAMESELECT_H

gamehall.cpp

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


GameHall::GameHall(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::GameHall)
{
    ui->setupUi(this);
    this->setFixedSize(1500,1000);//设置窗口大小
    this->setWindowTitle(QString("贪吃蛇游戏"));
    this->setWindowIcon(QIcon(":res/ico.png"));//设置窗口图标
    //
    QFont font("华文行楷",20);
    QPushButton * strbutton = new QPushButton(this);
    strbutton->setText("开始游戏");
    strbutton->setFont(font);
    strbutton->move(650,700);
    strbutton->setShortcut(QKeySequence(Qt::Key_Space));
    strbutton->setStyleSheet("QPushButton{border:0px}");//给按钮设置样式  去除边框


    GameSelect *  gameSelect = new GameSelect;
    connect(strbutton,&QPushButton::clicked,[=](){
        this->close();//当前窗口关闭
        gameSelect->setGeometry(this->geometry());//设置第二个窗口于当前窗口一致
        gameSelect->show();//新的窗口打开
        QSound::play(":res/clicked.wav");
    });
}


GameHall::~GameHall()
{
    delete ui;

}

void GameHall::paintEvent(QPaintEvent *event)
{
    //实例化画家对象
     QPainter painter(this);

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

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

}


gameroom.cpp

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


gameroom::gameroom(QWidget *parent) : QWidget(parent)
{
    this->setFixedSize(1500,1000);
    this->setWindowIcon(QIcon(":res/ico.png"));
    this->setWindowTitle("游戏房间");


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

    moveUp();
    moveUp();
    moveUp();


    //创建食物
    createNewFood();

    timer = new QTimer(this);

    connect(timer,&QTimer::timeout,[=]{
        int cnt =1 ;

        if(snakeList.front().intersects(foodRect))
        {
            //intersects判断会不会相交
            createNewFood();
            cnt++;
            QSound::play(":res/eatfood.wav");
        }

        while (cnt--) {
            switch (moveDirect) {
                case SnakeDirect::UP:
                    moveUp();
                    break;
                case SnakeDirect::DOWN:
                    moveDown();
                    break;
                case SnakeDirect::LEFT:
                    moveLeft();
                    break;
                case SnakeDirect::RIGHT:
                    moveRight();
                    break;
            }
        }

        snakeList.pop_back();//删除最后一个节点
        update();
    });

    //开始暂停
    QPushButton * strBtn = new QPushButton(this);
    QPushButton * stopBtn = new QPushButton(this);

    strBtn->move(1270,100);
    stopBtn->move(1270,170);
    strBtn->setText("开始");
    strBtn->setShortcut(QKeySequence(Qt::Key_C+Qt::CTRL));
    stopBtn->setText("暂停");
    stopBtn->setShortcut(QKeySequence(Qt::Key_V+Qt::CTRL));

    QFont font("楷体",20);
    strBtn->setFont(font);
    stopBtn->setFont(font);


    connect(strBtn,&QPushButton::clicked,[=]{

        sound = new QSound(":res/Trepak.wav");
        sound->play();
        sound->setLoops(-1);//一直循环播放

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

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

    });

    //方向控制
    QPushButton*up= new QPushButton(this);
    QPushButton*down= new QPushButton(this);
    QPushButton*left= new QPushButton(this);
    QPushButton*right= new QPushButton(this);

    up->move(1320,750);
    down->move(1320,900);
    left->move(1220,825);
    right->move(1420,825);

    up->setText("↑");
    up->setShortcut(QKeySequence(Qt::Key_W));
    down->setText("↓");
    down->setShortcut(QKeySequence(Qt::Key_S));
    left->setText("←");
    left->setShortcut(QKeySequence(Qt::Key_A));
    right->setText("→");
    left->setShortcut(QKeySequence(Qt::Key_D));

    up->setStyleSheet("QPushButton{border:0px}");
    down->setStyleSheet("QPushButton{border:0px}");
    left->setStyleSheet("QPushButton{border:0px}");
    right->setStyleSheet("QPushButton{border:0px}");

    QFont ft("宋体",36);
    up->setFont(ft);
    down->setFont(ft);
    right->setFont(ft);
    left->setFont(ft);

    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*ExitBtn = new QPushButton(this);
    ExitBtn->setText("退出游戏");
    ExitBtn->move(1240,400);
    ExitBtn->setFont(font);
    QMessageBox *msg = new QMessageBox(this);
    QPushButton *ok = new QPushButton("ok");
    QPushButton *canel = new QPushButton("canel");

    msg->addButton(ok,QMessageBox::AcceptRole);
    msg->addButton(canel,QMessageBox::RejectRole);


    msg->setWindowTitle("退出游戏");
    msg->setText("确认退出游戏吗");

    connect(ExitBtn,&QPushButton::clicked,[=](){
        msg->show();
        msg->exec();//时间轮询
        QSound::play("res/clicked.wav");

        GameSelect *select = new GameSelect;


        if(msg->clickedButton()==ok)
        {
            this->close();
            select->show();
        }
        else {
            msg->close();
        }
    });
}

void gameroom::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);//实例画家对象

    QPixmap pix;
    pix.load(":res/game_room.png");
    painter.drawPixmap(0,0,1200,1400,pix);

    pix.load(":res/bg1.png");
    painter.drawPixmap(1200,0,300,1100,pix);


    //绘制蛇

    //头
    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 if(moveDirect == SnakeDirect::RIGHT){
        pix.load(":res/right.png");
    }

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

    //身体
    pix.load(":res/Bd.png");
    for (int  i= 1;i < snakeList.size()-1;i++) {
        auto node = snakeList.at(i);   //at 获取每一个节点
        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.png");
    painter.drawPixmap(foodRect.x(),foodRect.y(),foodRect.width(),foodRect.height(),pix);



    //分数
    pix.load(":res/sorce_bg.png");
    painter.drawPixmap(this->width()*0.85,this->height()*0.1,90,40,pix);

    QPen pen;
    pen.setColor(Qt::black);
    QFont font("方正舒体",22);
    painter.setFont(font);
    painter.setPen(pen);
    painter.drawText(this->width()*0.9,this->height()*0.06,QString("%1").arg(snakeList.size()));


    //往文件中写分数
    int c = snakeList.size();
    QFile file("C:/Users/zyy/Desktop/1.txt");
    if(file.open(QIODevice::WriteOnly| QIODevice::Text))
    {
        QTextStream out(&file);
        int num = c;
        out<<num ;
        file.close();
    }

    //绘制有戏失败的效果
    if(checkFail())
    {
        pen.setColor(Qt::red);
        painter.setPen(pen);
        QFont font("方正舒体",22);
        painter.setFont(font);
        painter.drawText(this->width()*0.45,this->height()*0.5,QString("GAME OVER!"));
        timer->stop();
        QSound::play(":res/gameover.wav");

        sound->stop();
    }
}

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(1500 - 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(heady + KSnakeNodeWidth> 1200)
    {
        leftTop = QPointF(0,heady);

    }
    else {
        leftTop = snakeNode.bottomRight();
    }

    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;
}

void gameroom::createNewFood()
{
    foodRect = QRectF(qrand()%(1500/KSnakeNodeWidth)*KSnakeNodeWidth,
                      qrand()%(this->height()/KSnakeNodeHeight)*KSnakeNodeHeight,
                      KSnakeNodeWidth,KSnakeNodeHeight);
}

gameselect.cpp

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


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

    QPushButton* backBtn = new QPushButton(this);//返回按钮
    backBtn->move(1400,900);
    backBtn->setIcon(QIcon(":res/back.png"));
    backBtn->setShortcut(QKeySequence(Qt::Key_Escape));

    connect(backBtn,&QPushButton::clicked,[=](){
         this->close();
        GameHall * gameHall = new GameHall;
        gameHall->show();

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

    QFont font("华文行楷",20);

    gameroom*gameRoom = new gameroom;

    QPushButton* simpleBtn = new QPushButton(this);
    simpleBtn->move(650,300);
    simpleBtn->setFont(font);
    simpleBtn->setText("简单模式");
    connect(simpleBtn,&QPushButton::clicked,[=](){
        this->close();
        gameRoom->setGeometry(this->geometry());
        gameRoom->show();

        gameRoom->setTimeout(300);
    });

    QPushButton*normalBtn = new QPushButton(this);
    normalBtn->move(650,400);
    normalBtn->setFont(font);
    normalBtn->setText("正常模式");
    connect(normalBtn,&QPushButton::clicked,[=](){
        this->close();
        gameRoom->setGeometry(this->geometry());
        gameRoom->show();

        gameRoom->setTimeout(200);
    });


    QPushButton*hardBtn = new QPushButton(this);
    hardBtn->move(650,500);
    hardBtn->setFont(font);
    hardBtn->setText("困难模式");
    connect(hardBtn,&QPushButton::clicked,[=]{
        this->close();
        gameRoom->setGeometry(this->geometry());
        gameRoom->show();

        gameRoom->setTimeout(300);
    });


    QPushButton*hisBtn = new QPushButton(this);
    hisBtn->move(650,600);
    hisBtn->setFont(font);
    hisBtn->setText("历史战绩");


    connect(hisBtn,&QPushButton::clicked,[=](){
        QWidget *widget = new QWidget;
        widget->setWindowTitle("历史战绩");
        widget->setFixedSize(500,300);


        QTextEdit *edit = new QTextEdit(widget);
        edit->setFont(font);
        edit->setFixedSize(500,300);

        QFile file("C:/Users/zyy/Desktop/1.txt");
        file.open(QIODevice::ReadOnly);//读取内容

        QTextStream in(&file);
        int data =  in.readLine().toInt();//读取一行的数据

        edit->append("得分为:");
        edit->append(QString::number(data));//写入文本中

        widget->show();
    });

}

void GameSelect::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);//实例化画家对象
    QPixmap pix(":res/game_select.png");

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

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

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

相关文章

如何通过谷歌外链快速增加网站流量?

利用谷歌外链提升流量的方法非常直接&#xff0c;但实际上&#xff0c;外链影响的是关键词排名&#xff0c;关键词排名提升了&#xff0c;自然就会有流量&#xff0c;所以谷歌外链不是直接能提升网站流量&#xff0c;而是间接的&#xff0c;下面&#xff0c;我会详细介绍几种有…

验收测试:确保软件符合业务需求和合同要求

目录 前言1. 验收测试的概念1.1 用户验收测试&#xff08;UAT&#xff09;1.2 操作验收测试&#xff08;OAT&#xff09; 2. 验收测试的主要作用2.1 确认业务需求的满足2.2 验证合同要求的实现2.3 提升用户信心 3. 验收测试在整个测试中的地位3.1 测试的最后一道关卡3.2 用户与…

niushop逻辑漏洞

niushop逻辑漏洞 安装环境 这里控制不了 抓包在数据包中可以改数据

非线性系统稳定控制器设及案例仿真(s-function函数)

目录 前沿一、案例11. 系统模型 二、案例21. 系统模型2. 稳定性分析3. 仿真(包含代码)1. 仿真效果2. 仿真结果3. 仿真剖析4. 仿真图与代码 前沿 定义一个系统 x ˙ f ( x , u ) \dot{x} f(x,u) x˙f(x,u), 其中 x x x 为状态变量&#xff0c; u u u 为系统输入&#xff0c…

跨平台终端工具Tabby安装配置与远程ssh连接Linux_ubuntu详细教程

文章目录 前言1. Tabby下载安装2. Tabby相关配置3. Tabby简单操作4. ssh连接Linux4.1 ubuntu系统安装ssh4.2 Tabby远程ssh连接ubuntu 5. 安装内网穿透工具5.1 创建公网地址5.2 使用公网地址远程ssh连接 6. 配置固定公网地址 前言 今天和大家分享一下如何在Windows系统使用Tabb…

构建LVS负载均衡群集

构建LVS负载均衡群集 实验环境实验环境 201:客户端 101:调度器 (需要用到2个网卡) 102:web 103:web 104:NFS存储 101:(添加一个网卡) 2 [root@localhost ~]# cd /etc/sysconfig/network-scripts/ 3 4 //查看另一个网卡的名字ens36 5 [root@localhost network-scrip…

人工智能深度学习系列—深度学习中的相似性追求:Triplet Loss 全解析

文章目录 1. 背景介绍2. Loss计算公式3. 使用场景4. 代码样例5. 总结 1. 背景介绍 在机器学习和模式识别领域&#xff0c;相似性度量是核心问题之一。Triplet Loss&#xff0c;作为一种特殊的损失函数&#xff0c;被设计用来学习数据的相对距离&#xff0c;从而使得相似样本更…

5.C_Demo_排序

冒泡排序法 原理&#xff1a; 依次比较相邻的两个元素&#xff0c;如果顺序错误就交换。 思路&#xff1a; 这种方法&#xff0c;显然需要很多轮才能完成&#xff0c;每一轮只能排序一个最大值或最小值(第一层for)&#xff0c;将全部的数据排序完成&#xff0c;需要很多轮(第…

第三期书生大模型实战营之书生大模型全链路开源开放体系

一. Introduction 大模型是发展通用人工智能的重要途经 二. 开源历程以及InternLM2 2024年1月17日 InternLM2 2开源 三. 书生浦语2.0的主要亮点 3.1 超长上下文 3.2 综合性能全面提升 3.3 优秀的对话和创作体验 3.4 工具调用能力整体升级 3.5 突出的数理能力和实用的…

Among Us 私服的制作之路

文章目录 Among Us 私服的制作之路这游戏通常包括以下核心元素&#xff1a;角色设定&#xff1a;游戏机制&#xff1a;游戏界面&#xff1a; 第四步&#xff1a;添加社交特性第五&#xff1a;测试与优化方面 十分基础的框架(Web)注意事项 Among Us 私服的制作之路 作者正在准备…

嵌入式:简单的UI框架

1&#xff1a;UI框架简介 除了服务框架外&#xff0c;我们还需要对外显示UI&#xff0c;所以我们就需要一个UI的框架&#xff0c;跟服务框架一样&#xff0c;不用这个UI框架我们也是可以实现&#xff0c;但是这样每个人写的UI都会有差异&#xff0c;需要的事件&#xff0c;数据…

牛客JS题(二十)判断斐波那契数组

注释很详细&#xff0c;直接上代码 涉及知识点&#xff1a; 循环判断斐波那契数列组递归判断斐波那契数列组合法性判断 题干&#xff1a; 我的答案 <!DOCTYPE html> <html><head><meta charset"utf-8" /></head><body><scrip…

嵌入式数据库 sqlite3

数据库文件与普通文件区别: 1.普通文件对数据管理(增删改查)效率低 2.数据库对数据管理效率高,使用方便 常用数据库: 1.关系型数据库 将复杂的数据结构简化为二维表格形式 大型:Oracle、DB2 中型:MySql、SQLServer 小型:Sqlite …

c# .net core项目角色授权机制

前言 角色授权机制是确保应用程序安全性的重要组成部分,它允许开发者根据用户的角色来限制对应用程序中不同资源的访问。 基本概念: 角色授权基于用户角色的访问控制,即根据用户所属的角色来决定其能够访问的资源或执行的操作。在.NET Core中,这通常与身份认证(Authent…

怎么配置一个axios来拦截前后端请求

首先创建一个axios.js文件 导入我们所需要的依赖 import axios from "axios"; import Element from element-ui import router from "./router"; 设置请求头和它的类型和地址 注意先注释这个url,还没有解决跨域问题,不然会出现跨域 // axios.defaults.…

Python Sklearn库SVM算法应用

SVM 是一种有监督学习分类算法&#xff0c;输入值为样本特征值向量和其对应的类别标签&#xff0c;输出具有预测分类功能的模型&#xff0c;当给该模型喂入特征值时&#xff0c;该模型可以它对应的类别标签&#xff0c;从而实现分类。 Sklearn库SVM算法 下面我看一下 Python …

CSS学习 - 选择器

基础选择器 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 类型选择器&…

牛逼,两百行Python代码带你打造一款《天天酷跑》游戏!(附源码)

《天天酷跑》是一款广受欢迎的跑酷类手机游戏&#xff0c;玩家需要控制角色在赛道上奔跑&#xff0c;躲避障碍物&#xff0c;收集金币和道具&#xff0c;以获取高分。虽然完全复现这款游戏的复杂度和图形效果在简单的Python环境中难以实现&#xff08;特别是游戏图形和动画&…

市电220V

概念 市电 220V 是指在中国及许多其他国家使用的标准交流电压,该值是电压的有效值(RMS值,Root Mean Square)。有效值是交流电压或电流的一个测量方式,它表示在一个周期内,交流电的平方平均值等于直流电压(或电流)的值。 有效值在交流电中具有特殊意义,因为交流电的瞬…

华大基因守护新生健康,基因检测助力新生儿疾病筛查

此前&#xff0c;《中国出生缺陷防治报告》预估我国出生缺陷发生率在5.6%左右&#xff0c;无创产前基因检测技术&#xff08;NIPT&#xff09;等先进产前筛查手段&#xff0c;在我国历经十多年的发展历史&#xff0c;华大基因作为行业引领者&#xff0c;深耕基因检测领域&#…