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

今天实现一个简单的俄罗斯方块,网上别人写的都比较长还复杂,我就写了一个简单的,可以实现功能的俄罗斯方块,使用的是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

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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大白猫~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值