使用C++的QT框架实现俄罗斯方块

news2024/11/28 16:05:21

今天实现一个简单的俄罗斯方块,网上别人写的都比较长还复杂,我就写了一个简单的,可以实现功能的俄罗斯方块,使用的是C++语言,框架都可以,主要是逻辑思路有都可以实现

我这边实现的逻辑为两个数组包含各个动态的点为下落进行绘画,通过判断实现这个游戏

1.绘画游戏框

// 构造函数,创建一个 Widget 对象,继承自 QWidget
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)  // 创建一个 Ui::Widget 对象
{
    // 在 Widget 上设置用户界面
    ui->setupUi(this);

    // 设置窗口标题为 "俄罗斯方块"
    this->setWindowTitle("俄罗斯方块");

    // 使用当前时间作为随机数生成器的种子
    qsrand((unsigned int)time(NULL));

    // 设置 Widget 的固定大小为 400x800 像素
    this->setFixedSize(QSize(400, 800));

    // 创建一个定时器对象 time1,间隔时间为 300 毫秒
    this->time1.setInterval(300);

    // 启动定时器
    time1.start();

    // 在开局时调用 addwan() 函数,随机生成四个方块形状
    addwan();

    // 连接定时器 time1 的 timeout 信号到 xialuo() 函数的槽
    connect(&time1, &QTimer::timeout, this, [=]() { xialuo(); });
}

在上述代码中我先设置了游戏的标题,大小等,还设置了一个定时器,没300毫秒绘画刷新一次,之后就是画线,代码如下

void Widget::paintEvent(QPaintEvent *event)
{
    // 创建一个 QPainter 对象,用于在当前 Widget 上绘制图形
    QPainter huajia(this);

    // 绘制垂直方向的网格线,总共10条,间隔40像素
    for (int a = 0; a < 10; a++) {
        huajia.drawLine(40 * a, 0, 40 * a, 800);
    }

    // 绘制水平方向的网格线,总共20条,间隔40像素
    for (int a = 0; a < 20; a++) {
        huajia.drawLine(0, 40 * a, 400, 40 * a);
    }

    // 调用基类 QWidget 的 paintEvent 函数,以便处理默认的绘制操作
    return QWidget::paintEvent(event);
}

0b5ca13a682740a3b22eecebd58ffd61.png

2.之后画方块,方块的我我这边设置了四种情况,如下

// widget.h 文件

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QVector>
#include <QPoint>

// Widget 类,继承自 QWidget
class Widget : public QWidget
{
    Q_OBJECT

public:
    // 存储坐标点的 QVector
    QVector<QPoint> quandian;

    // 构造函数,可指定父对象,默认为 nullptr
    Widget(QWidget *parent = nullptr);

    // 析构函数
    ~Widget();

    // 重写绘制事件的函数
    void paintEvent(QPaintEvent *event);

    // 重写按键事件的函数
    void keyPressEvent(QKeyEvent *event);

    // 函数用于向 quandian 中添加ling形状的坐标点
    void addling();

    // 函数用于向 quandian 中添加yi形状的坐标点
    void addyi();

    // 函数用于向 quandian 中添加er形状的坐标点
    void adder();

    // 函数用于向 quandian 中添加san形状的坐标点
    void addsan();

    // 函数用于向 quandian 中添加wan形状的坐标点
    void addwan();

    // 存储坐标点的另一个 QVector
    QVector<QPoint> quanku;

private:
    // 指向 UI 对象的指针
    Ui::Widget *ui;
};

#endif // WIDGET_H

 先不看quandian和quanku这两个点容器,add...是初始方块容器的点,我这边使用随机数实现,代码如下

void Widget::addwan()
{
    // 调用 jiance 函数,可能是检查函数
    jiance(quanku);

    // 随机生成一个 0 到 3 之间的整数
    this->zhu = qrand() % 4;

    // 根据生成的随机数执行不同的操作
    switch (this->zhu) {
    case 0:
        // 调用 addling 函数
        addling();
        break;
    case 1:
        // 调用 addyi 函数
        addyi();
        break;
    case 2:
        // 调用 adder 函数
        adder();
        break;
    case 3:
        // 调用 addsan 函数
        addsan();
        break;
    }
}

 函数实现如下

void Widget::addling()
{ 
    // 生成一个 0 到 5 之间的随机整数
    this->suijishu = qrand() % 6;

    // 将四个坐标点添加到 'quandian' 集合中,X 坐标逐次递增
    this->quandian.push_back(QPoint(suijishu, 0));
    this->quandian.push_back(QPoint(suijishu + 1, 0));
    this->quandian.push_back(QPoint(suijishu + 2, 0));
    this->quandian.push_back(QPoint(suijishu + 3, 0));
}

void Widget::addyi()
{ 
    // 生成一个 0 到 5 之间的随机整数
    this->suijishu = qrand() % 6;

    // 将四个坐标点添加到 'quandian' 集合中,前三个的 X 坐标逐次递增,第四个的 X 坐标与第一个相同,Y 坐标加 1
    this->quandian.push_back(QPoint(suijishu, 0));
    this->quandian.push_back(QPoint(suijishu + 1, 0));
    this->quandian.push_back(QPoint(suijishu + 2, 0));
    this->quandian.push_back(QPoint(suijishu, 1));
}

void Widget::adder()
{ 
    // 生成一个 0 到 5 之间的随机整数
    this->suijishu = qrand() % 6;

    // 将四个坐标点添加到 'quandian' 集合中,前三个的 X 坐标逐次递增,第四个的 X 坐标与第二个相同,Y 坐标加 1
    this->quandian.push_back(QPoint(suijishu, 0));
    this->quandian.push_back(QPoint(suijishu + 1, 0));
    this->quandian.push_back(QPoint(suijishu + 2, 0));
    this->quandian.push_back(QPoint(suijishu + 1, 1));
}

void Widget::addsan()
{
    // 生成一个 0 到 5 之间的随机整数
    this->suijishu = qrand() % 6;

    // 将四个坐标点添加到 'quandian' 集合中,前两个的 X 坐标逐次递增,后两个的 X 坐标相同,Y 坐标递增
    this->quandian.push_back(QPoint(suijishu, 0));
    this->quandian.push_back(QPoint(suijishu + 1, 0));
    this->quandian.push_back(QPoint(suijishu, 1));
    this->quandian.push_back(QPoint(suijishu + 1, 1));
}

随机数是俄罗斯方块上部的任意点,y为0,比如addsan()

eb87e29f20c943a090355c4d07dca484.png

这边最快只能截到这里了,本来应该在最上方

之后使用绘画技术实现quandian这个点容器,如上图所示

void Widget::paintEvent(QPaintEvent *event)
{
    // 遍历 'quandian' 集合中的坐标点
    for (int a = 0; a < quandian.size(); a++) {
        // 在画布上绘制矩形,每个矩形的左上角坐标为 (x * 40, y * 40),宽度和高度均为 40 像素
        huajia.drawRect(quandian[a].x() * 40, quandian[a].y() * 40, 40, 40);
    }
    
    // 设置画刷的颜色为绿色
    huashua.setColor(Qt::green);
    // 设置画刷的样式为实心填充
    huashua.setStyle(Qt::SolidPattern);
    // 为绘制的矩形设置画刷
    huajia.setBrush(huashua);
    
    // 调用基类的 paintEvent 函数,完成绘制操作
    return QWidget::paintEvent(event);
}

或者addyi()这个函数进行绘画

7d3655f23a0540428f8f49bd261cd93c.png

3.方块下落,这个只需要遍历点的每一个y都加一就行了

void Widget::xialuo()
{
    // 遍历 'quandian' 集合中的坐标点
    for (int a = 0; a < quandian.size(); a++){
        // 将每个坐标点的 Y 坐标增加 1,实现向下移动
        quandian[a].setY(quandian[a].y() + 1);
    }
    
    // 调用 update 函数触发重绘事件,以便在界面上更新坐标点的位置
    this->update();
}

如下

4788eaa71edf40f0b40af43a2c4930e2.png

会一直定时器下落进行绘画 

6848ba0a4d014a34999ccae246423534.png

由于这个截图不会停止,就只能到这里了

4.方块可以根据按键进行移动,这个只需要写一个按键事件,之后根据速度调换xy坐标进行绘画就行

void Widget::keyPressEvent(QKeyEvent *event)
{
    switch (event->key()) {
    case Qt::Key_W:
        // 处理按下"W"键的逻辑
        // 这里可以添加相关的代码
        break;
    case Qt::Key_A:
        // 处理按下"A"键的逻辑
        for (int a = 0; a < quandian.size(); a++){
            for (int b = 0; b < quandian.size(); b++){
                // 检查如果任何坐标点的 X 坐标为 0,就不执行移动
                if (quandian[b].x() == 0){
                    return;  // 返回,不执行移动操作
                }
            }
            // 移动所有坐标点的 X 坐标减 1,实现向左移动
            quandian[a].setX(quandian[a].x() - 1);
        }
        break;
    case Qt::Key_S:
        // 处理按下"S"键的逻辑
        for (int a = 0; a < quandian.size(); a++){
            // 将所有坐标点的 Y 坐标加 1,实现向下移动
            quandian[a].setY(quandian[a].y() + 1);
        }
        break;
    case Qt::Key_D:
        // 处理按下"D"键的逻辑
        for (int a = 0; a < quandian.size(); a++){
            for (int b = 0; b < quandian.size(); b++){
                // 检查如果任何坐标点的 X 坐标为 9,就不执行移动
                if (quandian[b].x() == 9){
                    return;  // 返回,不执行移动操作
                }
            }
            // 移动所有坐标点的 X 坐标加 1,实现向右移动
            quandian[a].setX(quandian[a].x() + 1);
        }
        break;
    case Qt::Key_F:
        // 处理按下"F"键的逻辑
        if (qw == false){
            // 如果 'qw' 为假,启动计时器
            time1.start();
            qw = true;
        }
        else{
            // 如果 'qw' 为真,停止计时器
            time1.stop();
            qw = false;
        }
        break;
    }
}

如下

f0a8bb7be898450e9ebaab0c7f553ed9.png

5.下落到地上时的变色绘画

我主要是使用两点,quandian和quanku这两个点容器的数据传输实现,先绘画遍历这两个数组的点,代码如下

void Widget::paintEvent(QPaintEvent *event)
{
    // 绘制游戏中的坐标点
    for (int a = 0; a < quandian.size(); a++) {
        // 在指定位置绘制方块,每个方块的大小为40x40
        huajia.drawRect(quandian[a].x() * 40, quandian[a].y() * 40, 40, 40);
    }

    // 设置笔刷颜色为绿色
    huashua.setColor(Qt::green);
    // 设置笔刷风格为实色填充
    huashua.setStyle(Qt::SolidPattern);
    huajia.setBrush(huashua);

    // 绘制游戏中的块(或方块)
    for (int c = 0; c < quanku.size(); c++) {
        // 在指定位置绘制方块,每个方块的大小为40x40
        huajia.drawRect(quanku[c].x() * 40, quanku[c].y() * 40, 40, 40);
    }

    // 调用父类的绘制函数,确保事件被正确处理
    return QWidget::paintEvent(event);
}

如何判断呢,我这边使用的是遍历quandian的所有点,如果quanjian的元素有y达到了19,那么为最低点,如下

28112ccba1134bc382fcab3681855c69.png

最下面的点会执行这个函数

for (int a = 0; a < quandian.size(); a++) {
    // 检查当前 'quandian' 中的坐标点的 Y 值是否为 19
    if (quandian[a].y() == 19) {
        // 如果条件满足,则执行以下操作

        // 将 'quandian' 中的所有坐标点添加到 'quanku' 集合中
        for (int b = 0; b < quandian.size(); b++) {
            quanku.push_back(quandian[b]);
        }

        // 清空 'quandian' 集合
        quandian.clear();

        // 调用 'addwan()' 函数
        addwan();

        // 跳出当前循环
        break;
    } else {
        // 如果 'quandian' 中的任何坐标点的 Y 值不为 19,则执行这里的逻辑
        // 可以在此处添加相应的操作
    }
}

 还有就是碰撞也要变色,如下

d4188290de0a46dcac0b61b2e3c29e81.png

下落后碰撞

7e07c074f21e49f5a64159c0303b79cd.png

需要再上述代码中加上else,遍历两个数组如果碰撞元素也会从quandian转移到quanku中,详细代码如下 

void Widget::paintEvent(QPaintEvent *event) {
    // 遍历名为 'quandian' 的集合中的所有坐标点
    for (int a = 0; a < quandian.size(); a++) {
        // 如果当前坐标点的 Y 坐标等于 19
        if (quandian[a].y() == 19) {
            // 如果条件满足,执行以下操作:

            // 将 'quandian' 中的所有坐标点添加到 'quanku' 集合中
            for (int b = 0; b < quandian.size(); b++) {
                quanku.push_back(quandian[b]);
            }

            // 清空 'quandian' 集合
            quandian.clear();

            // 调用 'addwan()' 函数
            addwan();

            // 跳出当前循环
            break;
        } else {
            // 如果条件不满足,执行以下操作:

            // 再次遍历 'quandian' 集合
            for (int a = 0; a < quandian.size(); a++) {
                // 遍历 'quanku' 集合
                for (int b1 = 0; b1 < quanku.size(); b1++) {
                    // 如果 'quandian' 中的某个坐标点的 X 和 Y 坐标与 'quanku' 中的某个坐标点的 X 和 Y 坐标相邻
                    if (quandian[a].x() == quanku[b1].x() && quandian[a].y() == quanku[b1].y() - 1) {
                        // 执行以下操作:

                        // 将 'quandian' 中的所有坐标点添加到 'quanku' 集合中
                        for (int b = 0; b < quandian.size(); b++) {
                            quanku.push_back(quandian[b]);
                        }

                        // 清空 'quandian' 集合
                        quandian.clear();

                        // 调用 'addwan()' 函数
                        addwan();

                        // 跳出内部循环
                        break;
                    }
                }
            }
        }
    }

    // 返回 QWidget::paintEvent(event) 的结果
    return QWidget::paintEvent(event);
}

6.最后就是失败提醒了,这个只需要判断quanku(绿色)的y是否为最上方,代码如下

void Widget::jiance(QVector<QPoint> &zxc) {
    // 遍历名为 'zxc' 的 QVector 集合中的所有坐标点
    for (int a = 0; a < zxc.size(); a++) {
        // 如果当前坐标点的 Y 坐标等于 0
        if (zxc[a].y() == 0) {
            // 如果条件满足,执行以下操作:

            // 显示一个消息框,提示用户失败了
            QMessageBox::information(this, "失败了", "你个猪");

            // 清空名为 'quanku' 的集合
            this->quanku.clear();

            // 跳出当前循环
            break;
        }
    }
}

ed58e3a1df4f4aa1af0d65ea0f9aefb2.png

点击ok之后清空

dce0b957ff894f409f2b5dcdcabd65c2.png

这样的简单的俄罗斯方块就实现好了

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

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

相关文章

“没有酒瓶”的新春礼酒,泸州老窖的颠覆性之作

执笔 | 萧 萧 编辑 | 扬 灵 没有想到&#xff0c;新春礼酒还能跳出生肖酒造型桎梏&#xff0c;开创出“没有酒瓶的白酒”。 没有想到&#xff0c;即将要发布的新品就“藏”在每一位参会者都触手可及的餐桌正中。 没有想到&#xff0c;首发定价如此“实诚”&#xff0c;加…

python爬取Web of science论文信息

一、python爬取WOS总体思路 (一)拟实现功能描述 wos里面&#xff0c;爬取论文的名称&#xff0c;作者名称&#xff0c;作者单位&#xff0c;引用数量 要求&#xff1a;英文论文、期刊无论好坏 检索关键词&#xff1a;zhejiang academy of agricultural sciences、 xianghu lab…

【并查集】93 岛屿数量

岛屿数量 题解1 DFS&#xff08;图论经典方法&#xff09;题解2 BFS(遍历&#xff08;DFS展开【顺序不同】&#xff09;)题解3 并查集&#xff08;学习理解&#xff09; 给你一个由 ‘1’&#xff08;陆地&#xff09;和 ‘0’&#xff08;水&#xff09;组成的的二维网格&…

TikTok未来展望:数字创新的下一浪潮

随着全球数字时代的蓬勃发展&#xff0c;TikTok已经成为一个巨大的现象&#xff0c;吸引了数以亿计的用户&#xff0c;尤其是年轻一代。这个短视频分享平台已经深刻影响了社交媒体、娱乐和文化。然而&#xff0c;TikTok未来的发展前景如何&#xff1f;本文将探讨TikTok的未来展…

哈希的开放定址法的实现【C++】

哈希的开放定址法的实现【C】 1 概述2 线性探测2.1 插入2.2 查找2.3 删除2.6 完整代码2.5 线性探测的优缺点 3. 二次探测 1 概述 开放定址法也叫闭散列&#xff0c;是解决哈希冲突的一种方法&#xff0c;当发生哈希冲突之后&#xff0c;如果哈希表没有被装满(正常情况哈希表不会…

饥荒联机版 Don‘t Starve Together(WinMac)最新中文学习版

《饥荒联机版》是由Klei自主开发的开放世界冒险游戏。在这个游戏中&#xff0c;玩家将扮演各种各样的人物&#xff0c;这些人物不幸来到了一个神秘的异世界。在旅行中&#xff0c;玩家会邂逅性格各异、能力独特的同伴们&#xff0c;并和他们一起生存下去并征服异世界。游戏中的…

每日一题 117. 填充每个节点的下一个右侧节点指针 II (中等,树)

BFS&#xff0c;一层层去搜索整棵树&#xff0c;然后建立next关系即可&#xff0c;下面给出的代码的空间复杂度是O(n)的O(1) 的做法&#xff0c;当构建完上一层的next关系后&#xff0c;我们就可以像链表一样从左到右访问上一层的节点&#xff0c;显然在访问的过程中&#xff0…

N-132基于springboot,vue人事管理系统

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 系统分前后台&#xff0c;项目采用前后端分离 前端技术&#xff1a;vueelementUI 服务端技术&#xff1a;springbootmybatis-plus 本项…

自定义类型结构体(下)

目录 结构体传参结构体实现位段什么是位段位段的内存分配位段的跨平台问题总结&#xff1a; 位段的应用位段使用的注意事项** 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412; 个人主页 &#x1f978;&#x1f978;&a…

DataCastle企业风险算法赛实战(进阶难度)

目录 一、数据读取及分析 1、数据读取 2、数据分析 二、数据挖掘 三、模型构建及评估 四、划重点 推荐相关文章 去年在DataCastle上参加了华录杯算法赛&#xff0c;初赛前10、进复赛就没打了。相比于之前文章 kaggle风控建模实战&#xff08;文末附链接&#xff09;&…

智慧财务的未来

信息化时代&#xff0c;财务管理不再局限于传统的手工操作&#xff0c;而是借助RPA技术实现了自动化、智能化的转型。智慧财务作为财务管理的一种新模式&#xff0c;将为企业提供更加高效、便捷的服务&#xff0c;使企业能够更好地适应市场需求的变化&#xff0c;在瞬息万变的市…

批量删除文件名中的某些文字

怎么批量删除文件名中的某些文字&#xff1f;在整理电脑文件的时候&#xff0c;我们经常需要处理大量文件的重命名工作。当你的文件名称包含不必要或重复的字符时&#xff0c;可以进行批量删除&#xff0c;以使文件名称更简洁、清晰&#xff0c;提高可读性和识别性。例如&#…

【蓝桥杯软件赛 零基础备赛20周】第2周——常考知识点+判题

文章目录 0. 第1周答疑1. 常考知识点2. 蓝桥杯怎么判题2.1 判题系统如何判题2.2 测试数据和得分的关系2.3 自己做测试数据 3. 备赛计划4. 本周刷题 0. 第1周答疑 问题1&#xff1a;蓝桥杯怎么报名&#xff0c;什么时候报名&#xff1f; 答&#xff1a;集体报名或个人报名。大…

【word技巧】ABCD选项如何对齐?

使用word文件制作试卷&#xff0c;如何将ABCD选项全部设置对齐&#xff1f;除了一直按空格或者Tab键以外&#xff0c;还有其他方法吗&#xff1f;今天分享如何将ABCD选项对齐。 首先&#xff0c;我们打开【替换和查找】&#xff0c;在查找内容输入空格&#xff0c;然后点击全部…

利用关键字批量整理文件:用关键字轻松移动多个文件到指定文件夹

在日常生活和工作中&#xff0c;我们经常需要处理大量的文件&#xff0c;随着时间的推移&#xff0c;电脑中的文件越来越多&#xff0c;而文件的命名可能并不规范&#xff0c;导致查找和整理变得非常困难。因此&#xff0c;我们需要一种高效的文件管理方法&#xff0c;以方便我…

一种用醋酸刻蚀氧化铜的新方法

引言 由于乙酸不会氧化铜表面&#xff0c;因此乙酸常用于去除氧化铜而不侵蚀铜膜。乙酸还具有低表面张力&#xff0c;易于从表面移除。因此&#xff0c;不需要去离子(DI)水冲洗来移除残留的乙酸&#xff0c;可以防止水冲洗导致的铜再氧化。 英思特研究了在低温下用乙酸去除氧…

SSE加速随笔

Intel Intrinsics Guide 搞懂SSE 寄存器与指令数据细节 SSE指令集推出时&#xff0c;Intel公司在Pentium III CPU中增加了8个128位的SSE指令专用寄存器&#xff0c;称作XMM0到XMM7。这些XMM寄存器用于4个单精度浮点数运算的SIMD执行&#xff0c;并可以与MMX整数运算或x87浮点运…

01、SpringBoot + MyBaits-Plus 集成微信支付 -->项目搭建

目录 SpringBoot MyBaits-Plus 集成微信支付 之 项目搭建1、创建boot项目2、引入Swagger作用&#xff1a;2-1、引入依赖2-2、写配置文件进行测试2-3、访问Swagger页面2-4、注解优化显示 3、定义统一结果作用&#xff1a;3-1、引入lombok依赖3-2、写个统一结果的类-->RR类的…

Windows、程序员必装的工具

一、Typora 啥也不说了直接上图 Markdown语法 Typora免费版 提取码&#xff1a;av01 二维码&#xff1a; 1&#xff09;页面展示 2&#xff09;主题 3&#xff09;偏好设置 4&#xff09;Markdown语法设置偏好 5&#xff09;编辑器 6&#xff09;系统 二、Snipaste Snipaste…

Java反射详解:入门+使用+原理+应用场景

反射非常强大和有用&#xff0c;现在市面上绝大部分框架(spring、mybatis、rocketmq等等)中都有反射的影子&#xff0c;反射机制在框架设计中占有举足轻重的作用。 所以&#xff0c;在你Java进阶的道路上&#xff0c;你需要掌握好反射。 怎么才能学好反射&#xff0c;我们需要…