今天实现一个简单的俄罗斯方块,网上别人写的都比较长还复杂,我就写了一个简单的,可以实现功能的俄罗斯方块,使用的是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);
}
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()
这边最快只能截到这里了,本来应该在最上方
之后使用绘画技术实现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()这个函数进行绘画
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();
}
如下
会一直定时器下落进行绘画
由于这个截图不会停止,就只能到这里了
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;
}
}
如下
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,那么为最低点,如下
最下面的点会执行这个函数
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,则执行这里的逻辑
// 可以在此处添加相应的操作
}
}
还有就是碰撞也要变色,如下
下落后碰撞
需要再上述代码中加上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;
}
}
}
点击ok之后清空
这样的简单的俄罗斯方块就实现好了