Qt超简单实现贪吃蛇

为了能够最简单地完成程序,所以没有用类的继承等知识。感兴趣的朋友可以改写一下。

常量

const int FILE_SIZE = 30; //地图方格大小
const int FPS = 5000 / 33; //游戏运行帧率
enum Item{empty, wall, food, snake}; //方格的类型
//地图大小
const int mapWidth = 20;
const int mapHeight = 20;

Snake类

为了存储一条蛇, 我们需要的变量有:

  • 蛇的头坐标
  • 蛇的尾巴, 用一个链表存储
  • 蛇的移动方向

下面是Snake类的定义:

class Snake
{
public:
    Snake(QObject* parent, iItem ** _map, int x, int y);
    
    void setDirection(const int x, const int y);
    void move();
    void grow();
    bool eatSelf();
    int length()const{return tail.size();}
    QPoint getPos()const{return head;}

private:
    bool isMoving();
    QPoint head;
    QList<QPoint> tail;

    int moveX = 0;
    int moveY = 0;

    iItem **map;
};

贪吃蛇中的唯一难点, 就是蛇移动的逻辑了. 蛇移动不必更新身体每一个点的位置. 相反, 只要移动头, 并把头原来的位置加入身子, 并且去掉身体的最后就可以了.

函数的实现:

Snake::Snake(QObject *parent, iItem **_map, int x, int y) : Item(parent), map(_map)
{
    head = QPoint(x, y);
    map[x][y] = snake;
}

void Snake::setDirection(const int x, const int y)
{
    moveX = x;
    moveY = y;
}

void Snake::move()
{
    if(!isMoving()) return;
    QPoint newHead = head;
    //更新head的位置
    newHead.setX((newHead.x() + moveX) % mapWidth);
    newHead.setY((newHead.y() + moveY) % mapHeight);
    if(!tail.empty()){
	    //如果有尾巴的话, 将原来head作为tail的头
        tail.push_front(head);
        //剁掉尾巴的最后
        QPoint t = tail.back();
        map[t.x()][t.y()] = empty;
        tail.pop_back();
    }
    else{
	    //没有尾巴, 则把原来的地图位置置空
        map[head.x()][head.y()] = empty;
    }

    head = newHead; //更新head
    map[head.x()][head.y()] = snake; //更新地图
}

bool Snake::isMoving()
{
    return moveX != 0 || moveY != 0;
}

bool Snake::eatSelf()
{
	// 如果tail链表中包含与head坐标一样的点, 说明蛇头撞到身子了
	// contains是QList类内置的函数
    return tail.contains(head);
}

void Snake::grow() //变长
{
    QPoint t = head;
    //尾巴变长的方向和移动方向相反
    t.setX((t.x() - moveX) % mapWidth);
    t.setY((t.y() - moveY) % mapHeight);
    tail.append(t);
    map[t.x()][t.y()] = snake;
}

GameController类

class GameController : public QObject
{
    Q_OBJECT
public:
    Item** map; 
    GameController(QObject *parent);
    ~GameController();

protected:
	//这个函数由于把QMainWindow收到的键盘事件在handlerKeyPressed里处理
    virtual bool eventFilter(QObject *watched, QEvent *event) override;
    void handleKeyPressed(QKeyEvent* event);

private:
    QTimer timer;
    bool paused = false; //游戏暂停
    Snake *snake;
    QPoint foodPos;
    
    void checkCollision();
    bool snakeEatFood();
    void snakeEatSelf();
    void snakeHitWall();

    void addFood();

    void initMap(); //初始化地图

signals:
    void updateView(); //通知更新显示
    void over(); //通知游戏终止

public slots:
    void updateGame(); //更新游戏状态
    void gameover(); //游戏结束
    void stop(); //游戏暂停
    void resume(); //游戏恢复进行
    void newGame(); //再开一局
};
void GameController::initMap()
{
    for(int i = 0; i < mapWidth; ++i){
        for(int j = 0; j < mapHeight; ++j){
            map[i][j] = empty;
        }
    }
}

GameController::GameController(QObject *parent) : QObject(parent)
{
	//创建动态数组
    map = new Item*[mapWidth];
    for(int i = 0; i < mapWidth; ++i){
        map[i] = new iItem[mapHeight];
    }

    initMap(); //初始化地图

	//连接信号
    connect(&timer, &QTimer::timeout, this, &GameController::updateGame);
    timer.start(FPS); //开始计时

	//蛇初始化到地图中央
    snake = new Snake(this, map, mapWidth/2, mapHeight/2);
    addFood(); //生成食物
}

GameController::~GameController()
{
    for(int i = 0; i < mapWidth; ++i){
        delete []map[i];
    }

    delete []map;
    delete snake;
}

bool GameController::eventFilter(QObject *watched, QEvent *event)
{
    if (event->type() == QEvent::KeyPress) {
        handleKeyPressed((QKeyEvent *)event); //自定义的按键处理函数
        return true; //返回已处理
    } else {
        return QObject::eventFilter(watched, event); //不处理
        }
}

void GameController::handleKeyPressed(QKeyEvent *event)
{
    if(paused){ 
        if(event->key() == Qt::Key_Space){
            resume();
        }
        return;
    }

	//设置方向
    if(event->key() == Qt::Key_Up){
        snake->setDirection(0, -1);
    }
    else if(event->key() == Qt::Key_Down){
        snake->setDirection(0, 1);
    }
    else if(event->key() == Qt::Key_Left){
        snake->setDirection(-1, 0);
    }
    else if(event->key() == Qt::Key_Right){
        snake->setDirection(1, 0);
    }
    else if(event->key() == Qt::Key_Space){
        stop();
    }
}

void GameController::updateGame()
{
    snake->move(); //蛇移动
    checkCollision(); //检查碰撞
    emit updateView(); //更新显示
}

void GameController::checkCollision()
{
    if(snake->eatSelf()){ // head撞到身体
        gameover();
    }

    if(snakeEatFood()){ //吃食物
        addFood(); //生成下一个食物
        snake->grow(); //蛇生长
    }
}

void GameController::addFood()
{
    int n, x, y;
    do{ //随机在不是蛇的地方生成食物
        n = rand() % (mapWidth * mapHeight);
        y = n / mapWidth;
        x = n % mapWidth;
    }
    while(map[x][y] == snake);

    map[x][y] = food; //更新地图
    foodPos = QPoint(x, y); //存储食物的位置
}

void GameController::gameover()
{
    stop(); //游戏暂停
    emit over(); //更新UI
}

void GameController::stop()
{
    timer.stop();
    paused = true;
}

void GameController::resume()
{
    timer.start(FPS);
    paused = false;
}

void GameController::newGame()
{
    delete snake;
    initMap();
    snake = new Snake(this, map, mapWidth/2, mapHeight/2);
    addFood();
    resume();
}

bool GameController::snakeEatFood()
{
    return snake->getPos() == foodPos;
}

GUI显示

class MainWindow : public QMainWindow
{
    Q_OBJECT
  
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

protected:
    virtual void paintEvent(QPaintEvent* event); //绘制地图

private:
    GameController *game;

public slots:
    void showMessageBox(); //死亡时显示的提示框
    void updateView(); //更新游戏显示
};
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    setFixedSize(FILE_SIZE * mapWidth, FILE_SIZE * mapHeight); //设置窗口大小
    game = new GameController(this); //新开始游戏
    this->installEventFilter(game); //对收到的事件不接收,让game接收
	//信号连接
    connect(game, &GameController::updateView, this, &MainWindow::updateView);
    connect(game, &GameController::over, this, &MainWindow::showMessageBox);
}

MainWindow::~MainWindow()
{
    delete game;
}

void MainWindow::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);

	//画背景
    painter.fillRect(0, 0, mapWidth * FILE_SIZE, mapHeight * FILE_SIZE, Qt::gray);

	//画方格内的物体
	//蛇,食物,墙分别用不同颜色表示
    for(int i = 0; i < mapWidth; ++i){
        for(int j = 0; j < mapHeight; ++j){
            switch (game->map[i][j]) {
            case empty:
                break;
            case food:
                painter.fillRect(i * FILE_SIZE, j * FILE_SIZE, FILE_SIZE, FILE_SIZE, Qt::red);
                break;
            case snake:
                painter.fillRect(i * FILE_SIZE, j * FILE_SIZE, FILE_SIZE, FILE_SIZE, Qt::yellow);
                break;
            case wall:
                break;
            }
        }
    }
}

void MainWindow::showMessageBox()
{
    if (QMessageBox::Yes == QMessageBox::information(NULL,
                            tr("Game Over"), tr("Again?"),
                            QMessageBox::Yes | QMessageBox::No,
                            QMessageBox::Yes)) {
        game->newGame(); //如果选了Yes则重新开始
    }
    else{
        exit(0); //否则推出
    }
}

void MainWindow::updateView()
{
    update(); //更新, 会自动调用paintEvent函数
}

游戏简图

屏幕截图 2024-01-16 142451.png
屏幕截图 2024-01-16 142513.png

  • 25
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值