Qt超简单实现贪吃蛇

文章目录

  • 常量
  • Snake类
  • GameController类
  • GUI显示
  • 游戏简图

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

常量

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023 IoTDB Summit:天谋科技高级开发工程师谭新宇《优其效:如何用 IoTDB 监控工具进行深度系统调优》...

12 月 3 日&#xff0c;2023 IoTDB 用户大会在北京成功举行&#xff0c;收获强烈反响。本次峰会汇集了超 20 位大咖嘉宾带来工业互联网行业、技术、应用方向的精彩议题&#xff0c;多位学术泰斗、企业代表、开发者&#xff0c;深度分享了工业物联网时序数据库 IoTDB 的技术创新…

ARM day4 汇编及硬件编程

一、指令--数据从内存到cpu--ldr、str load -- 加载 读 store -- 存储 写 在ARM 架构下&#xff0c; 数据从内存到cpu 直接的移动只能通过 LDR/STR来完成 mov 只能在寄存器之间移动数据 &#xff0c;或把立即数移动到寄存器 &#xff0c;并且数据长度不能超过 8 位 str …

C++ 设计模式之策略模式

【声明】本题目来源于卡码网&#xff08;题目页面 (kamacoder.com)&#xff09; 【提示&#xff1a;如果不想看文字介绍&#xff0c;可以直接跳转到C编码部分】 【设计模式大纲】 【简介】什么是策略模式&#xff08;第14种模式&#xff09; 策略模式是⼀种⾏为型设计模式&…

解决BigDecimal序列化科学计数法前端展示问题(大坑)

解决BigDecimal序列化科学计数法前端展示问题(大坑) 前言&#xff1a;在生产中出现一个问题&#xff0c;就是BigDecimal类型的字段在前端页面展示变成科学计数法&#xff0c;通过排查&#xff0c;发现里面的坑还是挺多的&#xff0c;所以特意记录下处理过程。Json序列化&#x…

使用dbever连接 hsqldb

完整的url为 jdbc:hsqldb:hsql://ip:端口/别名 注意&#xff0c;hsqldb跟随应用启动和停止&#xff0c;所以当应用断点时&#xff0c;hsqldb也会连接不上导致查询数据失败&#xff0c;可以断点前进一步

Angular系列教程之路由守卫

文章目录 前言路由守卫的类型CanLoadCanActivateCanActivateChildCanDeactivateResolve总结 前言 在Angular中&#xff0c;路由守卫是一个非常有用的功能&#xff0c;可以帮助我们控制用户在导航过程中的权限和访问限制。通过使用路由守卫&#xff0c;我们可以拦截导航并根据需…

Mybatis 常用条件语句,大于小于、if、for、模糊搜索、case when、choose

大于小于 方法1&#xff1a; > 大于 &#xff0c; < 小于 <if test"startTime ! null ">and a.create_time > #{startTime} </if> <if test"endTime ! null ">and a.create_time < #{endTime} </if> 方法2(建议写这…

Macbook空间不足怎么解决?

随着使用时间的增长&#xff0c;我们会发现Mac电脑的存储空间越来越少&#xff0c;这时候我们就需要对Mac电脑进行清理&#xff0c;以释放更多的存储空间。那么&#xff0c;Mac空间不足怎么解决呢&#xff1f; 1.清理垃圾文件 Mac空间不足怎么解决&#xff1f;首先要做的就是清…

图像表示方法

RGB表示 RGB是使用三基色合成的原理&#xff0c;我们看到的彩色图片&#xff0c;都有三个通道&#xff0c;分别为红、绿、蓝通道&#xff0c;如果需要透明度则还有alpha分量. 通常每个通道用8bit表示&#xff0c;8bit能表示256种颜色&#xff0c;所以可以组成 256256256167772…

Vue 如何把computed里的逻辑提取出来

借用一下百度的ai 项目使用&#xff1a; vue 文件引入 <sidebar-itemv-for"route in routes":key"route.menuCode":item"route":base-path"route.path"click"onColor"/>import { handleroutes } from "./handle…

分布式搜索引擎ElasticSearch——基础

分布式搜索引擎ElasticSearch——基础 文章目录 分布式搜索引擎ElasticSearch——基础初识elasticsearch什么是elasticsearchelasticsearch的发展正向索引和倒排索引安装elasticsearch&#xff0c;kibana部署单点es创建网络加载镜像运行 部署kibana部署DevTools 安装IK分词器在…

力扣白嫖日记(sql)

前言 练习sql语句&#xff0c;所有题目来自于力扣&#xff08;https://leetcode.cn/problemset/database/&#xff09;的免费数据库练习题。 今日题目&#xff1a; 175.组合两个表 表&#xff1a;Courses 列名类型studentvarcharclassvarchar 在 SQL 中&#xff0c;(stude…

物流信息管理系统的设计与实现:从数据库到前端的全流程解析

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

CSS3中多列布局详解

多列布局 概念&#xff1a;在CSS3之前&#xff0c;想要设计类似报纸那样的多列布局&#xff0c;有两种方式可以实现&#xff1a;一种是"浮动布局"&#xff0c;另一种是“定位布局”。 这两种方式都有缺点&#xff1a;浮动布局比较灵活&#xff0c;但不容易控制&…

vue3__Provide / Inject (依赖注入)和mixins

一、 Provide提供和Inject 注入 Provide提供 <script setup> import { provide } from vueprovide(/* 注入名 */ message, /* 值 */ hello!) </script> 例如父组件中提供方法 <template> <div class"home">dfhualsf<div><button…

npm link 后怎么查看软连接和删除软连接的

一&#xff1a;在你的npm项目中&#xff0c;进行打包&#xff0c;形成一个dist文件 npm run build // 这是我的打包命令&#xff0c;具体可查看 package.json 文件 二&#xff1a; 打包完成后&#xff0c;运行pwd命令&#xff0c;可查看到你npm项目的路径。 pwd // 输出一…

FFMPEG解码实时流,支持cpu、gpu解码

官网下载的ffmpeg目前只能下载到X64版本的库&#xff0c;具体编译请参考windows编译ffmpeg源码&#xff08;32位库&#xff09;_windows 32位ffmpeg动态库-CSDN博客 直接上代码 int VideoDecodeModule::Open(std::string strUrl) {AVFormatContext *pFormatCtx nullptr;AVCo…

Next.js 开发指​南(GitHub 115k star​)

Next.js 是一个构建于 Node.js 之上的开源 Web 开发框架&#xff0c;它扩展了最新的 React 特性&#xff0c;集成了基于 Rust 的 JavaScript 工具&#xff0c;可以帮助你快速创建全栈 Web 应用 &#xff08;full-stack Web applications&#xff09; 。 对于有一定 React 基础…

虚拟机 以及 Centos 7的 安装全过程

目录 安装VMwere Workstion 虚拟机的操作过程 CentOS 7 安装过程 install CentOS 7 安装操作系统 安装VMwere Workstion 虚拟机的操作过程 更改安装位置 到下面图片中的这一个步骤&#xff0c;可以点击许可证&#xff0c;输入密钥就可以使用了&#xff0c; 密钥可以去某度或…

网络安全B模块(笔记详解)- 文件包含

文件包含的渗透与加固 1.使用渗透机场景kali中工具扫描服务器,将服务器上 File Inclusion首页概述页中的Flag提交; Flag:iloveu 2.使用渗透机场景windows7访问服务器场景网站中的File Inclusion(local)页面,找到根目录下Flag文件夹下的Flag.txt文件并将其内容提交; Flag:…
最新文章