QT-贪吃蛇小游戏


一、演示效果

请添加图片描述

二、核心代码

#include "Food.h"
#include <QTime>
#include <time.h>
#include "Snake.h"


Food::Food(int foodSize):foodSize(foodSize)
{

    coordinate.x = -1;
    coordinate.y = -1;
    qsrand(time(NULL));
}

void Food::createFood(int map_row,int map_col,vector<Point> snakeCoords){

    int foodX = qrand()%map_col*foodSize;
    int foodY = qrand()%map_row*foodSize;
    // 防止將食物生成在上一個位置
    while(foodX == coordinate.x && foodY == coordinate.y){
        foodX =  qrand()%map_col*foodSize;
        foodY = qrand()%map_row*foodSize;
    }
    //防止食物生成在蛇身上
    while(1){
        bool flag = true;
        for(auto& e:snakeCoords){
            if(e.x == foodX && e.y == foodY){
                foodX =  qrand()%map_col*foodSize;
                foodY = qrand()%map_row*foodSize;
                flag = false;
                break;
            }
        }
        if(flag)break;
    }

    coordinate.x = foodX;
    coordinate.y = foodY;

}

//返回食物座標
Point Food::getCoor()const{
    return this->coordinate;
}

#include "MapScene.h"
#include <QDebug>
#include <QMessageBox>
#include <QPushButton>
#include <vector>
#include <QPainter>
#include "data.h"
#include <QJsonDocument>
#include <QJsonObject>
#include "User.h"
#include <QDateTime>
#include "NetworkManager.h"



MapScene::MapScene(QWidget *parent,int row,int col,Snake* snake,int speed) : BaseScene(parent),row(row),col(col),snake(snake),speed(speed)
{
   // srand((unsigned)time(NULL)); //亂數種

    //載入和設置CSS樣式表
    QFile cssFile;
    cssFile.setFileName("./css/mapScene.css");
    cssFile.open(QIODevice::ReadOnly);
    QString styleSheet = cssFile.readAll();
    cssFile.close();
    this->setStyleSheet(styleSheet);

    //對界面進行基本的設置(只需做一次)
    int mapWidth = col*snake->getSize(),mapHeight = row*snake->getSize();
    int controlBarHeight = 50;
    this->setFixedSize(mapWidth,mapHeight+controlBarHeight);
    this->setWindowTitle(u8"【SuperSnake】遊戲界面");

    //主要的初始化
    this->initControlBar(mapWidth,mapHeight,controlBarHeight); //初始化最底下的控制欄
    this->initMap();

    //介紹遊戲操作的對話框
    QMessageBox::information(this,u8"提示",u8"【操作說明】使用W、A、S、D改變蛇的運動方向(注:WASD對應上下左右)");


    connect(gameTimer,&QTimer::timeout,this,&MapScene::onGameRunning);


}

//更新排行榜( 注:不同速度有不同的記錄 )
/*
 * 排行榜.json的儲存格式:
 * {
 *   "speed1":{
 *          "userId": {
                "maxScore": XXX,
                "userName": "XXX"
                "date":"XXX"
            }
 *    },
 *
 *   "speed2":{},
 *   "speed3":{}
 *    //....
 * }
*/
bool MapScene::updateRankList(){

    /* 打開文件 */
    QFile rankListFile;
    rankListFile.setFileName(rankListPath);
    rankListFile.open(QIODevice::ReadOnly);
    QByteArray rankListData = rankListFile.readAll(); //讀取所有內容

    QJsonDocument jsonDoc = QJsonDocument::fromJson(rankListData); //將數據解析為Json格式
    QJsonObject jsonObj = jsonDoc.object(); //轉為QJsonObject類型

    /* 獲取各種信息 */
    QString userId = User::getCurrentUserId();
    QString userName = User::getCurrentUserName();
    QString speed = QString::number(this->speed);
    QDateTime current_date_time =QDateTime::currentDateTime();
    QString current_date =current_date_time.toString("yyyy-MM-dd");


    QJsonObject speedObj = jsonObj[speed].toObject();

    //當【指定速度的排行榜】不包含當前的userId時,代表第一次遊玩該速度,可直接記錄該速度的排行榜中
    if(!speedObj.contains(userId)){
        QJsonObject newRankRecord;
        newRankRecord.insert("userName",userName);
        newRankRecord.insert("maxScore",score);
        newRankRecord.insert("date",current_date);
        speedObj.insert(userId,newRankRecord);

    }else{
        int maxScore = speedObj[userId].toObject()["maxScore"].toInt();
        //當局分數<=最高分時,不作記錄,直接返回
        if(score<=maxScore){
            rankListFile.close();
            return false;
        }

        //更新最高分
        QJsonObject userIdObj = speedObj[userId].toObject();
        userIdObj["maxScore"] = score;
        userIdObj["date"] = current_date;
        speedObj[userId] = userIdObj;


    }
    rankListFile.close();
    rankListFile.open(QIODevice::WriteOnly);

    /* 更新排行榜內容 */
    jsonObj[speed] = speedObj;
    jsonDoc.setObject(jsonObj);
    rankListFile.write(jsonDoc.toJson());

    rankListFile.close();
    return true;

}

bool MapScene::updateRankList(QString url){
    QByteArray rankListData = NetworkManager::get(url);

    QJsonDocument jsonDoc = QJsonDocument::fromJson(rankListData); //將數據解析為Json格式
    QJsonObject jsonObj = jsonDoc.object(); //轉為QJsonObject類型

    /* 獲取各種信息 */
    QString userId = User::getCurrentUserId();
    QString userName = User::getCurrentUserName();
    QString speed = QString::number(this->speed);
    QDateTime current_date_time = QDateTime::currentDateTime();
    QString current_date =current_date_time.toString("yyyy-MM-dd");


    QJsonObject speedObj = jsonObj[speed].toObject();

    //當【指定速度的排行榜】不包含當前的userId時,代表第一次遊玩該速度,可直接記錄該速度的排行榜中
    if(!speedObj.contains(userId)){
        QJsonObject newRankRecord;
        newRankRecord.insert("userName",userName);
        newRankRecord.insert("maxScore",score);
        newRankRecord.insert("date",current_date);
        speedObj.insert(userId,newRankRecord);

    }else{
        int maxScore = speedObj[userId].toObject()["maxScore"].toInt();
        //當局分數<=最高分時,不作記錄,直接返回
        if(score<=maxScore){
            return false;
        }

        //更新最高分
        QJsonObject userIdObj = speedObj[userId].toObject();
        userIdObj["maxScore"] = score;
        userIdObj["date"] = current_date;
        speedObj[userId] = userIdObj;


    }


    /* 更新排行榜內容 */
    jsonObj[speed] = speedObj;
    jsonDoc.setObject(jsonObj);
    NetworkManager::put(url,jsonDoc);
    return true;
}

void MapScene::onGameRunning(){
    snake->move();
    moveFlag = true; //表示上一次的【方向指令】已執行完成,可以接收下一個【方向指令】

    /*取得蛇的各項信息*/
    int snakeSize = snake->getSize();
    std::vector<Point> snakeCoords = snake->getCoords();
    Point foodCoord = food->getCoor();
    //獲取食物座標
    int snakeNum = snake->getNum();


    //判斷蛇有無吃東西
    if(isSnakeEat(snakeCoords,foodCoord)){
        snake->addNum();
        food->createFood(this->row,this->col,snakeCoords);

        score+=100; //每食一個食物+100分
        scoreLabel->setText(QString(u8"分數:%1").arg(score));
        scoreLabel->adjustSize(); //防止分數顯示不完全

        snake->setSnakeColor(QColor(rand()%256,rand()%256,rand()%256)); //改變蛇的顏色
    }
    //判斷蛇是否死亡
    if(isSnakeDead(snakeCoords,snakeSize,snakeNum)){
        gameTimer->stop();
        QString server = User::getCurrentServer();

        NetworkManager nw;

        bool ret;
        if(server == "local"){
            ret = updateRankList();
        }
        else{
            nw.createLoadDialog();
            ret = updateRankList(webJsonUrl_RL);
        }

        QString resultStr = QString(u8"你的分數為:%1").arg(score);
        if(!ret){
            resultStr+="  >>排行榜沒有任何改變@@<<";
        }else{
            resultStr+="  >>排行榜已更新^.^<<";
        }
        /* 注:QMessageBox要放在initMap()的之後,因為它是模態對話框,會阻塞進程,從而導致一些bug */
        this->initMap();

        if(server == u8"web")nw.closeLoadDialog();

        QMessageBox::information(this,u8"遊戲結束",resultStr);

    }


    update(); //手動調用paintEvent
}

void MapScene::initControlBar(int mapWidth,int mapHeight,int controlBarHeight){
    //開始遊戲的按鈕
    QPushButton* startGameBtn = new QPushButton(u8"開始遊戲",this);
    startGameBtn->setFont(QFont(u8"Adobe 楷体 Std R",14));
    startGameBtn->adjustSize();
    startGameBtn->move(mapWidth*0.03,mapHeight+controlBarHeight/2-startGameBtn->height()/2);
    connect(startGameBtn,&QPushButton::clicked,[this](){
        gameTimer->start();

    });

    //暫停遊戲的按鈕
    QPushButton* pauseGameBtn = new QPushButton(u8"暫停遊戲",this);
    pauseGameBtn->setFont(QFont(u8"Adobe 楷体 Std R",14));
    pauseGameBtn->adjustSize();
    pauseGameBtn->move(mapWidth*0.05+startGameBtn->width()+10,mapHeight+controlBarHeight/2-pauseGameBtn->height()/2);
    connect(pauseGameBtn,&QPushButton::clicked,[this](){
        gameTimer->stop();

    });

    //返回的按鈕
    QPushButton* backBtn = new QPushButton(u8"返回",this);
    backBtn->setFont(QFont(u8"Adobe 楷体 Std R",14));
    backBtn->adjustSize();
    backBtn->move(mapWidth*0.95-backBtn->width(),mapHeight+controlBarHeight/2-backBtn->height()/2);
    connect(backBtn,&QPushButton::clicked,[this](){
        gameTimer->stop();
        this->close();
        //發送返回【設定界面】的信號
        emit backToSettingScene();


    });

    scoreLabel = new QLabel(u8"分數:0",this);
    scoreLabel->setFont(QFont(u8"Adobe 楷体 Std R",14));
    scoreLabel->adjustSize();
    scoreLabel->move(mapWidth*0.05+startGameBtn->width()+pauseGameBtn->width()+20,mapHeight+controlBarHeight/2-scoreLabel->height()/2);
}

// 畫蛇的函數
void MapScene::drawSnake(QPainter& painter,std::vector<Point>& snakeCoords,int snakeNum,int snakeSize){
    QColor snakeColor = snake->getSnakeColor();
    //設置畫家各項屬性
    painter.setPen(QPen(snakeColor));

    painter.setBrush(QBrush(snakeColor));

    //畫蛇
    for(int i = 0;i<snakeNum;i++){
        painter.drawRect(snakeCoords[i].x,snakeCoords[i].y,snakeSize,snakeSize);
    }
}

//畫食物的函數
void MapScene::drawFood(QPainter& painter,int snakeSize){
    //設置畫家各項屬性
    painter.setPen(QColor());
    //創建畫刷
    QBrush brush(QColor(255,255,0));;
    painter.setBrush(brush);

    Point foodCoor = food->getCoor();
    painter.drawEllipse(foodCoor.x,foodCoor.y,snakeSize,snakeSize);

}

//判斷蛇是否死亡
bool MapScene::isSnakeDead(std::vector<Point>& snakeCoords,int& snakeSize,int& snakeNum){
    //檢查蛇有無超出邊界
    if(snakeCoords[0].x<0 || snakeCoords[0].x>=this->col*snakeSize || snakeCoords[0].y<0 || snakeCoords[0].y>=this->row*snakeSize )return true;
    //檢查有沒有碰到自己
    for(int i = 1;i < snakeNum;i++){
        if(snakeCoords[0].x == snakeCoords[i].x && snakeCoords[0].y == snakeCoords[i].y )return true;
    }
    return false;
}

//判斷蛇有無吃東西
bool MapScene::isSnakeEat(std::vector<Point>& snakeCoords,Point& foodCoord){
        //檢查蛇頭有沒有吃到食物
        if(snakeCoords[0].x == foodCoord.x && snakeCoords[0].y == foodCoord.y)return true;
        return false;
}

// 繪圖事件
void MapScene::paintEvent(QPaintEvent* event){

    QPainter painter(this);
    int snakeSize = snake->getSize();
    std::vector<Point> snakeCoords = snake->getCoords();
    int snakeNum = snake->getNum();

    //分隔線:分開遊戲區域和控制區域
    painter.drawLine(0,row*snakeSize,col*snakeSize,row*snakeSize);


    drawFood(painter,snakeSize);
    drawSnake(painter,snakeCoords,snakeNum,snakeSize);

}

// 通過 wasd 改變蛇的方向
void MapScene::changeSnakeDir(QKeyEvent* event){
    // wasd->上下左右
    //通過wasd改變蛇的運動方向
    int snakeDir = snake->getDir();
    switch(event->key()){
        case Qt::Key_W:
            if(snakeDir!=DOWN){
                snake->setDir(UP);
                moveFlag = false;
            }
            break;
        case Qt::Key_A:
            if(snakeDir!=RIGHT){
                snake->setDir(LEFT);
                moveFlag = false;
            }
            break;
        case Qt::Key_S:
            if(snakeDir!=UP){
                snake->setDir(DOWN);
                moveFlag = false;
            }
            break;
        case Qt::Key_D:
            if(snakeDir!=LEFT){
                snake->setDir(RIGHT);
                moveFlag = false;
            }
            break;

    }
}

//鍵盤點擊事件
void MapScene::keyPressEvent(QKeyEvent* event){
    //當moveFlag為false時,代表上一次發出的【方向指令】還在使用中,故直接return,防止有bug
    if(!moveFlag)return;
    changeSnakeDir(event);

}

void MapScene::initMap(){

    //設置定時器
    if(!gameTimer)gameTimer = new QTimer(this);
    gameTimer->setInterval(100/speed);

    //初始化食物對象
    if(!food)food = new Food(snake->getSize());
    food->createFood(this->row,this->col,snake->getCoords());


    //初始化蛇
    snake->init();



    //重置分數
    score = 0;
    scoreLabel->setText(QString(u8"分數:%1").arg(score));
    scoreLabel->adjustSize();



}

MapScene::~MapScene(){
    //因為food沒有加入到【對象樹】中,所以要手動釋放
    if(food!=nullptr){
        delete food;
        food = nullptr;
    }


}

三、下载链接

https://download.csdn.net/download/u013083044/89656909

  • 13
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

进击的大海贼

联系博主,为您提供有价值的资源

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

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

打赏作者

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

抵扣说明:

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

余额充值