纯代码贪吃蛇Qt版本

3 篇文章 0 订阅

分享一个用Qt软件去写一个贪吃蛇小游戏,学习qt,在这里你可以学到

  • qt的自定义信号与槽
  • qt线程使用
  • qt中容器qlist,qvector的使用
  • qt样式简单应用
  • 自定义数据结构
  • 设计思路

快来复制粘贴代码运行一下吧!
一个简单的贪吃蛇游戏
键盘操作说明: ↑W,↓S,←A,→D按键来控制蛇的方向,按下U实现snake的加速,按下P实现snake暂停移动

废话不多说,直接动手分析写代码,代码我有比较详细的注释
头文件:qsnake.h

#ifndef QSNAKE_H
#define QSNAKE_H

#include <QLabel>
#include <QThread>
#include <QObject>


//定义snake一些样式
#define BOYD_STYLE "background-color: rgb(138, 226, 5);border-radius: 10px;"
#define HEAD_STYLE "background-color: rgb(234, 7, 5);border-radius: 10px;"
#define TAIL_STYLE "background-color: rgb(23, 27, 45);border-radius: 10px;"
#define FROG_STYLE1 "background-color: rgb(100, 255, 0);border-radius: 10px;"
#define FROG_STYLE2 "background-color: rgb(50, 255, 100);border-radius: 2px;"
#define AREA_STYLE  "background-color: rgba(220, 220, 220,125);"

//定义一些信号码
#define SNAKE_NULL -1
#define SNAKE_OVER 0   //结束
#define IS_WALL 1     //撞墙
#define IS_BODY 2     //撞身体
#define FULL_AREA 3   //满屏幕
#define EATEN 4       //吃掉食物
#define SNAKE_MOVE 5  //移到

//采用label形式显示节点
typedef QLabel showType;

typedef struct
{
    enum nodetype{head,body,tail};
    nodetype type;//节点类型
    showType *disp;//节点显示方式 disp属于显示窗口,不允许在PlaySanke中修改,可以通过信号把指针和需要的参数送回主窗口修改
    QString style;//节点显示样式
    int x;
    int y;
    int step; //节点移动步长或者边长或者半径

}snakeNode,*snakeNodePtr;

//采用链表形式存放节点,可以采用list vector或者自定义容器存放节点
//容器类需要有实现:frist(),last(),at(),size()等模板类容器具有的特性
typedef QList<snakeNodePtr> snakeType;

//定义一个管理蛇属性的对象类
class PlaySnake : public QThread
{
    Q_OBJECT

public:

    enum Direction{Up,Down,Left,Right};

    //构造函数,parent为snake的显示窗口
    PlaySnake(QWidget *parent = nullptr);
    ~PlaySnake(){}

    //设置蛇到活动区域,相对于主窗口parent,边界必须是step到整数倍
    void setRect(int w,int h, int x=0 ,int y=0);
    //设置移动速度值
    void setSpeed(int speed){g_speed = speed;}
    //设置加速标志
    void setSpeedFlag(bool flag){g_speedFlag = flag;}
    //设置移动方向
    void setDirection(Direction indirect = Right);
    //设置移动步长
    void setStep(int instep){step = instep;}
    //设置蛇操作对象
    void setSnake(snakeType *tsnake){snake = tsnake;}
    //设置青蛙操作对象
    void setFrog(snakeNodePtr frog){this->frog = frog;}
    //移动
    void move(int step_x ,int step_y);
    //设置节点样式
    void setStyle(QString style,snakeNode::nodetype nodetype);
    bool eating();
    //判断位置
    bool isBody();
    bool isWall();
    bool isBody(int x ,int y);
    bool isWall(int x ,int y);
    //新建一个青蛙位置
    bool creatFrog(int &x , int &y);

    void stop(){runing = false;}
    void setpause(bool pause){this->pause = pause;}

signals:
    //由主窗口更新显示,更新节点样式或者节点位置
    void updataStyle(showType *lab, QString style);
    void updataNode(showType *lab, QString style,int x,int y ,int step);
    //当青蛙被吃,重新更新
    bool eaten(int x,int y,int step);
    //信号触发码
    int snakeMsg(int code);


protected:
    virtual void run();
private:
    int g_speed;
    bool g_speedFlag;
    bool step_flag;
    bool runing;
    bool pause;
    Direction direct;
    int step;
    snakeType *snake;
    snakeNodePtr frog;
    int area_w;
    int area_h;
    int area_x;
    int area_y;
    QWidget *parent;
};
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
#endif // QSNAKE_H

在头文件中,我们定义了一个PlaySnake的类,这个类主要是用于操控snake列表的节点位置,snake的每一个节点包含位置,节点类型,显示方式,样式,还有节点的移到步伐,也是节点一个格子的大小。PlaySnake类提供蛇的移动,设置移到方向,判断自身状态,吃掉青蛙等等一些方法。PlaySnake类是继承QThread的,实现多线程运行。PlaySnake的parent主窗口用于显示snake.
源码文件: qsnake.cpp

#include <QDebug>
#include <QRandomGenerator>
#include "qsnake.h"


PlaySnake::PlaySnake(QWidget *parent)
{
    this->parent = parent;
    g_speed = 10;
    g_speedFlag=false;
    runing = true;
    pause = false;
    snake = nullptr;
    frog = nullptr;
    direct = Right;
    step = 10;
    area_x = 0;
    area_y = 0;

    if(parent != nullptr)
    {
        area_h = (parent->height()/step)*step;
        area_w = (parent->width()/step)*step;

    }
    else
    {
        area_h = (100/step)*step;
        area_w = (100/step)*step;
    }

}

void PlaySnake::setRect(int w, int h, int x, int y)
{
    area_w = (w/step)*step;
    area_h = (h/step)*step;
    area_x = (x/step)*step;
    area_y = (y/step)*step;
}

void PlaySnake::setDirection(PlaySnake::Direction indirect)
{
    //限定蛇不能后退
    bool flag = (direct == Up && indirect == Down) ||
            (direct == Left && indirect == Right)||
            (direct == Down && indirect == Up) ||
            (direct == Right && indirect == Left) ;
    if(flag)
        return;

    //最小步伐限定,每次改变方向前,要等待上一次移动完成
    //避免移动一步,方向改变两次,造成矛盾
    if(step_flag)
    {
        direct = indirect;
        step_flag = false;
    }
}

//采用蛇头变蛇体,蛇尾变蛇头,蛇尾前一个节点变蛇尾方式交替,实现蛇到移动
//实际上每次移动只操作了三个节点
void PlaySnake::move(int step_x, int step_y)
{
    if(snake->size() < 3)
        return;

    snakeNodePtr body = snake->at(snake->size()-2);
    snakeNodePtr head = snake->first();
    snakeNodePtr tail = snake->takeLast();


    QString tmph = head->style;
    QString tmpt = tail->style;
    QString tmpb = body->style;


    int x = head->x + step_x;
    int y = head->y + step_y;

    tail->x = x;
    tail->y = y;
    tail->type = snakeNode::head;
    tail->style = tmph;

    head->type = snakeNode::body;
    head->style = tmpb;

    body->type = snakeNode::tail;
    body->style = tmpt;


    emit updataNode(body->disp,tmpt,body->x,body->y,body->step);
    emit updataNode(tail->disp,tmph,tail->x,tail->y,tail->step);
    emit updataNode(head->disp,tmpb,head->x,head->y,head->step);

    snake->prepend(tail);

}
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
void PlaySnake::setStyle(QString style, snakeNode::nodetype nodetype)
{
    if(nodetype == snakeNode::nodetype::head)
    {
       emit updataStyle(snake->first()->disp,style);
       return;
    }

    if(nodetype == snakeNode::nodetype::tail)
    {
       emit updataStyle(snake->last()->disp,style);
       return;
    }

    foreach (snakeNodePtr var, *snake) {

        if(var->type == nodetype)
           emit updataStyle(var->disp,style);
    }

    return;
}

bool PlaySnake::eating()
{

    if(!frog)
        return false;

    snakeNodePtr head = snake->first();
    snakeNodePtr body = snake->at(snake->size()-2);
    snakeNodePtr tail = snake->last();

    int s_x = head->x;
    int s_y = head->y;

    int f_x = frog->x;
    int f_y = frog->y;

    //吃到青蛙把青蛙变成蛇尾
    if(s_x == f_x && s_y == f_y)
    {
        frog->style = tail->style;
        frog->type = tail->type;
        frog->x = tail->x;
        frog->y = tail->y;

        tail->type = body->type;
        tail->style = body->style;

        snake->append(frog);

        emit updataNode(tail->disp,tail->style,tail->x,tail->y,tail->step);
        emit updataNode(frog->disp,frog->style,frog->x,frog->y,frog->step);

        int x=0 , y = 0;
        if(creatFrog(x,y))
            emit eaten(x,y,step);
        else
            return false;

        return true;
    }
    else
    {
        return false;
    }


}

bool PlaySnake::isBody()
{
    snakeNodePtr head = snake->first();

    foreach (snakeNodePtr var, *snake) {

        if(var->type == snakeNode::head)
            continue;
        if(var->x == head->x && var->y == head->y)
            return true;
    }

    return false;
}

bool PlaySnake::isBody(int x , int y)
{
    foreach (snakeNodePtr var, *snake) {
        if(var->x == x && var->y == y)
            return true;
    }

    return false;
}
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
bool PlaySnake::isWall()
{
    if(snake->isEmpty())
        return false;
    snakeNodePtr first = snake->first();
	//snake右移和下移需要多+step,节点(正方形)左上角代表实际坐标x,y
    int x = direct == Direction::Right ? first->x+step : first->x;
    int y = direct == Direction::Down ? first->y+step : first->y;

    return x > area_w+area_x || y > area_h+area_y || x < area_x || y < area_y;
}

bool PlaySnake::isWall(int x , int y)
{
   return x > area_w+area_x || y > area_h+area_y || x < area_x || y < area_y;
}

bool PlaySnake::creatFrog(int &x, int &y)
{
    int a_w = (area_w/step);
    int a_h = (area_h/step);
    int count = 0;
    int area_size = a_w * a_h ;
    //用于标记区域内那些区域被sanke占用
    bool *area = new bool[area_size] ;

    //创建一个合法的frog坐标,无法创建返回false;
    do
    {
        //限定随机区域范围,主窗口区域大小,并且为step的整数倍
        x = (QRandomGenerator::global()->bounded(area_x,area_w+area_x)/step) * step;
        y = (QRandomGenerator::global()->bounded(area_y,area_h+area_y)/step) * step;
        if(!isBody(x,y)) return true;

        //蛇身体已经占满区域时无法创建返回false
        int i = x/step;
        int j = y/step;
        if(!area[i*a_w+j*a_h])
        {
            area[i*a_w+j*a_h] = true;
            count++;
        }

    }while(count < area_size - 1);

    runing = false;
    delete [] area;
    emit snakeMsg(FULL_AREA);
    return false;
}

void PlaySnake::run()
{
    int tmpspeed = 0;
    int speed = 0;
    int x = 0;
    int y = 0;
    int t_s = 0;
    bool change = true;


    //创建初始位置青蛙
    if(creatFrog(x,y))
       emit eaten(x,y,step);

    while (runing) {

        QThread::msleep(5ll);
        if(++t_s > 50)
        {
            t_s = 0;
            change = !change;
            //让frog闪烁起来
            if(frog != nullptr)
                emit updataStyle(frog->disp,change?FROG_STYLE2:FROG_STYLE1);
        }
		//暂停
        if(pause) continue;
		//加速标志
        if(!g_speedFlag){

            tmpspeed = g_speed;
        }
        else {
            tmpspeed = g_speed/20;
        }

        if(++speed < tmpspeed)
            continue;
        speed = 0;

        x = 0;
        y = 0;

        if(snake->isEmpty())
            continue;
		//方向选择
        switch (direct) {
        case Direction::Up:
            y -= step;
            break;
        case Direction::Down:
            y += step;
            break;
        case Direction::Left:
            x -= step;
            break;
        case Direction::Right:
            x += step;
            break;
        }
        //移到
        move(x,y);
        emit snakeMsg(SNAKE_MOVE);
        step_flag = true;
		//判断是否吃掉青蛙
        if(eating()){
            emit snakeMsg(EATEN);
            continue;
        }

		//判断是否撞墙
        if(isWall())
            emit snakeMsg(IS_WALL);

        if(isBody())
            emit snakeMsg(IS_BODY);

    }
}
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343

从qsnake源码上看,snake对象是从外面传进来的,PlaySnake类只负责对其进行操作,判断自身状态,然后通过触发自定义信号,让主线程窗口去修改其显示位置和显示样式,实现了snake的移到和吃食物。这是我们需要创建一个主窗口,用来显示蛇
头文件:mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include <QMainWindow>
#include <QLabel>
#include <QThread>
#include <QObject>
#include "qsnake.h"


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

    //通过键盘按钮操作snake的方向和加速
    void keyPressEvent(QKeyEvent *event);
    void keyReleaseEvent(QKeyEvent *event);
public slots :
    //主窗口PlaySnake请求更新样式接收槽
    void updataStyle(showType *lab, QString style);
    //PlaySnake请求更新显示节点位置样式大小
    void updataNode(showType *lab, QString style,int x,int y ,int step);
    //更新frog
    void updataFrog(int x ,int y,int step);
    //信息码
    void dealSnakeMsg(int code);

private:
    showType *showtype;
    showType *area;
    PlaySnake *playsnake;
    snakeType *snake;
};
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
#endif // MAINWINDOW_H

主窗口的头文件我们准备了几个方法函数,void keyPressEvent(QKeyEvent *event)和keyReleaseEvent(QKeyEvent *event)用于读取键盘按下事件函数,这里用到↑W,↓S,←A,→D按键来控制蛇的方向,按下U实现snake的加速,按下P实现snake暂停移动,槽函数,updataStyle用来更新PlaySnake发送updataStyle更新节点的样式显示,updataNode更新节点显示状态,updataFrog更新设置frog,dealSankeMsg处理snake一些状态码,在qsnake.h中有定义。

源码文件:mainwindow.cpp

#include "mainwindow.h"
#include <QKeyEvent>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    this->setGeometry(100,50,800,600);

    //snake活动范围
    int area_x = 100;
    int area_y = 50;
    int area_h = 500;
    int area_w = 600;

    //步长与速度值
    int step = 20;
    int speed =100;


    //显示snake在主窗口的活动区域
    area = new showType(this);
    area->setStyleSheet(AREA_STYLE);
    area->setGeometry((area_x/step)*step,(area_y/step)*step,(area_w/step)*step,(area_h/step)*step);


    //初始化sanke
    snake = new snakeType;
    //初始长度
    int init_size = 3;
    for(int i = 0 ; i < init_size ; i++)
    {
        //snake 所有坐标必须是step的整数倍
        snakeNodePtr snakenode = new snakeNode;
        showtype = new showType(this);
        snakenode->x = (area_x/step)*step+i*step;
        snakenode->y = (area_y/step)*step;
        snakenode->step = step;
        snakenode->style = BOYD_STYLE;
        snakenode->type = snakeNode::body;
        snakenode->disp = showtype;

        if(i == 0)
        {
            snakenode->type = snakeNode::tail;
            snakenode->style = TAIL_STYLE;

        }
        if(i == (init_size-1))
        {
            snakenode->type = snakeNode::head;
            snakenode->style = HEAD_STYLE;
        }

        snakenode->disp->setStyleSheet(snakenode->style);
        snakenode->disp->setGeometry(snakenode->x,snakenode->y,snakenode->step,snakenode->step);

        snake->prepend(snakenode);
    }

    //创建sanke操作对象
    playsnake = new PlaySnake(this);
	//连接接受playsnake发出的信号和处理的槽函数
    connect(playsnake,&PlaySnake::updataStyle,this,&MainWindow::updataStyle);
    connect(playsnake,&PlaySnake::updataNode,this,&MainWindow::updataNode);
    connect(playsnake,&PlaySnake::eaten,this,&MainWindow::updataFrog);
    connect(playsnake,&PlaySnake::snakeMsg,this,&MainWindow::dealSnakeMsg);

    playsnake->setSpeed(speed);
    playsnake->setStep(step);
    playsnake->setRect(area_w,area_h,area_x,area_y);
    playsnake->setSnake(snake);
    playsnake->start();


}

//释放内存
MainWindow::~MainWindow()
{
    playsnake->stop();
    playsnake->wait();

    if(snake)
    {
        foreach (snakeNodePtr var, *snake) {
            delete var->disp;
            var->disp = nullptr;
            delete var;
            var = nullptr;
        }

        delete snake;
        snake = nullptr;
    }

    delete playsnake;
    delete area;

    area = nullptr;
    playsnake = nullptr;
}
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343
void MainWindow::keyPressEvent(QKeyEvent *event)
{
       static bool pause = false;

      switch (event->key()) {
        case Qt::Key_U :
            playsnake->setSpeedFlag(true);
            break;
        case Qt::Key_W :
        case Qt::Key_Up :
             playsnake->setDirection(PlaySnake::Up);
             break;
        case Qt::Key_S :
        case Qt::Key_Down :
            playsnake->setDirection(PlaySnake::Down);
            break;
        case Qt::Key_D :
        case Qt::Key_Right :
            playsnake->setDirection(PlaySnake::Right);
            break;
        case Qt::Key_A :
        case Qt::Key_Left :
            playsnake->setDirection(PlaySnake::Left);
            break;
        case Qt::Key_P :
            pause = !pause;
            playsnake->setpause(pause);
            break;
      }
}

void MainWindow::keyReleaseEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_U)
    {
        playsnake->setSpeedFlag(false);
    }
}

void MainWindow::updataStyle(showType *lab, QString style)
{
    lab->setStyleSheet(style);
    lab->raise();
    this->update();
}

void MainWindow::updataNode(showType *lab, QString style,int x,int y ,int step)
{
    lab->setStyleSheet(style);
    lab->setGeometry(x,y,step,step);
    lab->raise();
    this->update();
}

void MainWindow::updataFrog(int x, int y,int step)
{
    QString style =FROG_STYLE1;
    snakeNodePtr frog = new snakeNode;
    frog->x = x;
    frog->y = y;
    frog->disp = new showType(this);
    frog->style = style;
    frog->step = step;

    frog->type = snakeNode::head;

    frog->disp->setGeometry(frog->x,frog->y,frog->step,frog->step);
    frog->disp->setStyleSheet(frog->style);
    frog->disp->show();

    playsnake->setFrog(frog);
}

void MainWindow::dealSnakeMsg(int code)
{
    switch (code) {
    case IS_WALL:
        qDebug()<<"IS_WALL";
        break;
    case IS_BODY:
        qDebug()<<"IS_BODY";
        break;
    case FULL_AREA:
        playsnake->stop();
        playsnake->wait();
        qDebug()<<"FULL_AREA";
        break;
    case EATEN:
        qDebug()<<"EATEN";
        break;
    default:
        break;
    }
}
//博客:booinon
//https://blog.csdn.net/boonion?spm=1011.2415.3001.5343

dealSnakeMsg(int code)可以根据code作出一些处理措施,比如分数增加,速度加快,撞墙的时候结束游戏等等,这里就不去实现了,留给大家发挥的余地。

pro文件: snake.pro

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = snake_t
TEMPLATE = app

DEFINES += QT_DEPRECATED_WARNINGS

CONFIG += c++11

SOURCES += \
        main.cpp \
        mainwindow.cpp \
    qsnake.cpp

HEADERS += \
        mainwindow.h \
    qsnake.h

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
# 博客:booinon
# https://blog.csdn.net/boonion?spm=1011.2415.3001.5343

运行指导:
把上面的源码和头文件内容分别复制下来,粘贴到对应名字的文件,然后用Qt,打开snake.pro文件运行即可。或者,自己创建一条qt工程,把源码贴进去也是可以的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

boonion

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值