虽然界面做的不美观,但是总算是实现了基本的功能。
首先写了一个俄罗斯方块的类Tetris,通过这个类来进行这个游戏的数据的处理;然后游戏窗口是继承的QWidget类,用来显示游戏的方块;“下一个方块”窗口也是继承的QWidget类,用来显示下一个方块;控制提示和分数的显示用的QLabel。然后将将这些控件整合到继承自QMainWindow的mainWindow类。
Tetris类:一共有7中不同形状的方块(如下图),每个方块由四个方格组成,Block结构体用来存储方块的方格坐、中心方格坐标、ID等数据,移动中的方块和下移个方块都是通过这个结构体来操作的;已经落下的方块的方格存储在二维数组box[][]中,x坐标从左到右为正方向,y坐标从上到下为正方向
-
项目源文件
- tetris.h
- tetris.cpp
- tetrisbox.h
- tetrisbox.cpp
- nexttetris.h
- nexttetris.cpp
- mainwindow.h
- mainwindow.cpp
form.h
#ifndef FORM_H
#define FORM_H
#include <QWidget>
#include <QPushButton>
namespace Ui {
class Form;
}
class Form : public QWidget
{
Q_OBJECT
public:
explicit Form(QWidget *parent = nullptr);
~Form();
signals:
void myReturn();
private slots:
void Return();
private:
Ui::Form *ui;
QPushButton *button;
};
#endif // FORM_H
form.cpp
#include "form.h"
#include "ui_form.h"
#include <QDebug>
Form::Form(QWidget *parent) :
QWidget(parent),
ui(new Ui::Form)
{
ui->setupUi(this);
this->button = new QPushButton();
this->button->setGeometry(10,10,100,100);
this->button->setText("RETURN");
this->button->setParent(this);
this->button->show();
connect(this->button,SIGNAL(clicked(bool)), this, SLOT(Return()));
}
void Form::Return()
{
emit myReturn();
this->hide();
//qDebug() << "return" << endl;
}
Form::~Form()
{
delete ui;
}
nexttetris.h
#include "nexttetrisbox.h"
NextTetrisBox::NextTetrisBox(QWidget *parent) : QWidget(parent)
{
//初始化nextBlock
for (int i = 0; i < COUNT; i++)
{
nextBlock.x[i] = -1;
nextBlock.y[i] = -1;
}
nextBlock.centerX = -1;
nextBlock.centerY = -1;
nextBlock.ID = 0;
//设置本“下一个”窗口的宽度和高度
//并设置背景为黑色
int w = Tetris::getNextWidth();
int h = Tetris::getNextHeight();
setFixedSize(w, h);
setPalette(QPalette(Qt::black));
setAutoFillBackground(true);
}
void NextTetrisBox::updateNextTetris(Tetris tetris)
{
nextBlock = tetris.getNextBlock();
for (int i = 0; i < COUNT; i++)
{
nextBlock.x[i] -= RESTX;
nextBlock.y[i] += RESTY;
}
//重新绘制
repaint();
}
void NextTetrisBox::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QPen pen;
QBrush brush;
pen.setStyle(Qt::SolidLine);
pen.setColor(QColor(255, 255, 255));
brush.setStyle(Qt::SolidPattern);
brush.setColor(QColor(255, 255, 255));
painter.setPen(pen);
painter.setBrush(brush);
//绘制nextBlock中的内容
for (int i = 0; i < COUNT; i++)
{
int x = nextBlock.x[i];
int y = nextBlock.y[i];
int x1 = x * WIDTH + x * INTERVAL;
int y1 = y * HEIGHT + y * INTERVAL;
painter.drawRect(x1, y1, WIDTH, HEIGHT);
}
}
nexttetris.cpp
#include "nexttetrisbox.h"
NextTetrisBox::NextTetrisBox(QWidget *parent) : QWidget(parent)
{
//初始化nextBlock
for (int i = 0; i < COUNT; i++)
{
nextBlock.x[i] = -1;
nextBlock.y[i] = -1;
}
nextBlock.centerX = -1;
nextBlock.centerY = -1;
nextBlock.ID = 0;
//设置本“下一个”窗口的宽度和高度
//并设置背景为黑色
int w = Tetris::getNextWidth();
int h = Tetris::getNextHeight();
setFixedSize(w, h);
setPalette(QPalette(Qt::black));
setAutoFillBackground(true);
}
void NextTetrisBox::updateNextTetris(Tetris tetris)
{
nextBlock = tetris.getNextBlock();
for (int i = 0; i < COUNT; i++)
{
nextBlock.x[i] -= RESTX;
nextBlock.y[i] += RESTY;
}
//重新绘制
repaint();
}
void NextTetrisBox::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QPen pen;
QBrush brush;
pen.setStyle(Qt::SolidLine);
pen.setColor(QColor(255, 255, 255));
brush.setStyle(Qt::SolidPattern);
brush.setColor(QColor(255, 255, 255));
painter.setPen(pen);
painter.setBrush(brush);
//绘制nextBlock中的内容
for (int i = 0; i < COUNT; i++)
{
int x = nextBlock.x[i];
int y = nextBlock.y[i];
int x1 = x * WIDTH + x * INTERVAL;
int y1 = y * HEIGHT + y * INTERVAL;
painter.drawRect(x1, y1, WIDTH, HEIGHT);
}
}
nexttetrisbox.h
#ifndef NEXTTETRISBOX_H
#define NEXTTETRISBOX_H
#include <QWidget>
#include <QWidget>
#include <QPaintEvent>
#include <QPen>
#include <QBrush>
#include <QPainter>
#include <QColor>
#include "tetris.h"
#define RESTX (MAXX - NEXTMAXX) / 2 //方块x坐标的转换常数
#define RESTY 4 //方块y坐标的转换常数
class NextTetrisBox : public QWidget
{
Q_OBJECT
public:
explicit NextTetrisBox(QWidget *parent = nullptr);
void updateNextTetris(Tetris tetris); //更新“下一个”的数据和视图
void paintEvent(QPaintEvent *event); //绘制视图
signals:
public slots:
private:
Block nextBlock; //“下一个”方块
};
#endif // NEXTTETRISBOX_H
tetri.h
#ifndef TETRIS_H
#define TETRIS_H
//为了获得随机数
#include <cstdlib>
#include <ctime>
#define MAXX 10 //显示窗口的横向格数
#define MAXY 20 //显示窗口的竖向格数
#define NEXTMAXX 6 //“下一个”显示窗口的横向格数
#define NEXTMAXY 6 //“下一个”显示窗口的竖向格数
#define WIDTH 30 //单格的宽度
#define HEIGHT 30 //单格的高度
#define INTERVAL 4 //单格之间的间隔
#define COUNT 4 //每个方块的格数
//Block结构体:一个方块
struct Block
{
int x[COUNT]; //方块单格的x坐标
int y[COUNT]; //方块单格的y坐标
int centerX; //方块的中心x坐标
int centerY; //方块的中心y坐标
int ID; //方块的ID
};
class Tetris
{
public:
Tetris();
void createBlock(); //创建当前方块
Block getNextBlock(); //获得下一个方块
Block getBlock(); //获得当前方块
int getScore(); //获得分数
int getBox(int x, int y); //获得相应坐标的状态
bool rotate(); //旋转
bool moveToLeft(); //向左移动
bool moveToRight(); //向右移动
bool moveToBottom(); //向下移动
bool isEnd(); //判断是否结束
void killLines(); //消去整行
void clear(); //重新初始化
static int getWidth(); //获得窗口的宽度
static int getHeight(); //获得窗口的高度
static int getNextWidth(); //获得“下一个”窗口的宽度
static int getNextHeight(); //获得“下一个”窗口的高度
private:
void createNextBlock(); //创建下一个方块
bool move(int dx, int dy); //是否可以移动
void blockToBox(); //将block中的数据转移到box中
bool isRotatable(); //是否可以旋转
int getFirstFullLine(); //获得第一个整行
private:
int score; //分数
Block block; //当前方块
Block nextBlock; //下一个方块
int box[MAXX][MAXY];//方格的坐标系 1表示右方格,0表示没有方格
};
#endif // TETRIS_H
tetri.cpp
#include "tetris.h"
Tetris::Tetris()
{
//初始化随机数发生器
srand(unsigned(time(NULL)));
//初始化成员变量
score = 0;
for (int i = 0; i < MAXX; i++)
{
for (int j = 0; j < MAXY; j++)
{
box[i][j] = 0;
}
}
for (int i = 0; i < COUNT; i++)
{
block.x[i] = -1;
block.y[i] = -1;
}
block.centerX = -1;
block.centerY = -1;
block.ID = 0;
//创建下一个方块
createNextBlock();
}
//创建当前方块
//将上一次生成的下一个方块nextBlock复制给block
//并创建下一个nextBlock
void Tetris::createBlock()
{
//nextBlock复制给block
for (int i = 0; i < COUNT; i++)
{
block.x[i] = nextBlock.x[i];
block.y[i] = nextBlock.y[i];
}
block.centerX = nextBlock.centerX;
block.centerY = nextBlock.centerY;
block.ID = nextBlock.ID;
//创建下一个nextblock
createNextBlock();
}
//返回下一个方块
Block Tetris::getNextBlock()
{
return nextBlock;
}
//返回当前方块
Block Tetris::getBlock()
{
return block;
}
//返回当前分数
int Tetris::getScore()
{
return score;
}
//返回坐标(x,y)的值,以判断是否右方格
int Tetris::getBox(int x, int y)
{
return box[x][y];
}
//旋转当前方块
//旋转成功返回true,否则返回false
bool Tetris::rotate()
{
if (isRotatable())
{
return true;
}
else
{
return false;
}
}
//将当前方块向左移动一格
//成功返回true,否则返回false
bool Tetris::moveToLeft()
{
if (move(-1, 0))
{
return true;
}
else
{
return false;
}
}
//将当前方块向右移动一格
//成功返回true,否则返回false
bool Tetris::moveToRight()
{
if (move(1, 0))
{
return true;
}
else
{
return false;
}
}
//将方块向下移动一格
//成功返回true, 游戏结束返回false
bool Tetris::moveToBottom()
{
if (!move(0, 1))
{
//移动不成功
blockToBox(); //将当前方块复制到box中
killLines(); //消行
//判断是否结束
//否则创建新的方块
if(isEnd())
{
return false;
}
else
{
createBlock();
}
}
return true;
}
//判断游戏是否结束
//结束条件为第一行有方格
bool Tetris::isEnd()
{
int j = 0;
for (int i = 0; i < MAXX; i++)
{
if (box[i][j] == 1)
{
return true;
}
}
return false;
}
//消掉整行并进行分数奖励
void Tetris::killLines()
{
int count = 0; //一次消掉的行数
//通过getFirstFullLine()函数获得从上到下第一个整行
//并将其上的行向下平移一行,达到消行的效果
int temp = 0;
while ((temp = getFirstFullLine()) != -1)
{
for (int j = temp; j >0; j--)
{
for (int i = 0; i < MAXX; i++)
{
box[i][j] = box[i][j-1];
}
}
count++;
}
//消行的分数奖励
score += count * count * 10;
}
//对成员变量进行初始化,重新开始游戏
void Tetris::clear()
{
//初始化
score = 0;
srand(unsigned(time(NULL)));
for (int i = 0; i < MAXX; i++)
{
for (int j = 0; j < MAXY; j++)
{
box[i][j] = 0;
}
}
for (int i = 0; i < COUNT; i++)
{
block.x[i] = -1;
block.y[i] = -1;
}
block.centerX = -1;
block.centerY = -1;
block.ID = 0;
//创建下一个方块
createNextBlock();
}
//获得游戏窗口的宽度
int Tetris::getWidth()
{
return MAXX * WIDTH + (MAXX - 1) * INTERVAL;
}
//获得游戏窗口的高度
int Tetris::getHeight()
{
return MAXY * HEIGHT + (MAXY - 1) * INTERVAL;
}
//获得“下一个”窗口的宽度
int Tetris::getNextWidth()
{
return NEXTMAXX * WIDTH + (NEXTMAXX - 1) * INTERVAL;
}
//获得“下一个”窗口的高度
int Tetris::getNextHeight()
{
return NEXTMAXY * WIDTH + (NEXTMAXY - 1) * INTERVAL;
}
//创建“下一个”方块
void Tetris::createNextBlock()
{
int centerX = (MAXX - 1) / 2; //中心x坐标
int ID = rand() % 7; //获得0 - 6的随机数
//根据不同的随机数创建方块
switch (ID)
{
case 0:
//##
//##
nextBlock.x[0] = centerX;
nextBlock.x[1] = centerX;
nextBlock.x[2] = centerX + 1;
nextBlock.x[3] = centerX + 1;
nextBlock.y[0] = -2;
nextBlock.y[1] = -1;
nextBlock.y[2] = -2;
nextBlock.y[3] = -1;
nextBlock.centerX = 0;
nextBlock.centerY = 0;
nextBlock.ID = 0;
break;
case 1:
//####
//
nextBlock.x[0] = centerX - 1;
nextBlock.x[1] = centerX;
nextBlock.x[2] = centerX + 1;
nextBlock.x[3] = centerX + 2;
nextBlock.y[0] = -1;
nextBlock.y[1] = -1;
nextBlock.y[2] = -1;
nextBlock.y[3] = -1;
nextBlock.centerX = centerX;
nextBlock.centerY = -1;
nextBlock.ID = 1;
break;
case 2:
//##
// ##
nextBlock.x[0] = centerX - 1;
nextBlock.x[1] = centerX;
nextBlock.x[2] = centerX;
nextBlock.x[3] = centerX + 1;
nextBlock.y[0] = -2;
nextBlock.y[1] = -2;
nextBlock.y[2] = -1;
nextBlock.y[3] = -1;
nextBlock.centerX = centerX;
nextBlock.centerY = -2;
nextBlock.ID = 2;
break;
case 3:
// ##
//##
nextBlock.x[0] = centerX;
nextBlock.x[1] = centerX + 1;
nextBlock.x[2] = centerX - 1;
nextBlock.x[3] = centerX;
nextBlock.y[0] = -2;
nextBlock.y[1] = -2;
nextBlock.y[2] = -1;
nextBlock.y[3] = -1;
nextBlock.centerX = centerX;
nextBlock.centerY = -2;
nextBlock.ID = 3;
break;
case 4:
//#
//###
nextBlock.x[0] = centerX - 1;
nextBlock.x[1] = centerX - 1;
nextBlock.x[2] = centerX;
nextBlock.x[3] = centerX + 1;
nextBlock.y[0] = -2;
nextBlock.y[1] = -1;
nextBlock.y[2] = -1;
nextBlock.y[3] = -1;
nextBlock.centerX = centerX;
nextBlock.centerY = -1;
nextBlock.ID = 4;
break;
case 5:
// #
//###
nextBlock.x[0] = centerX + 1;
nextBlock.x[1] = centerX - 1;
nextBlock.x[2] = centerX;
nextBlock.x[3] = centerX + 1;
nextBlock.y[0] = -2;
nextBlock.y[1] = -1;
nextBlock.y[2] = -1;
nextBlock.y[3] = -1;
nextBlock.centerX = centerX;
nextBlock.centerY = -1;
nextBlock.ID = 5;
break;
case 6:
// #
//###
nextBlock.x[0] = centerX;
nextBlock.x[1] = centerX - 1;
nextBlock.x[2] = centerX;
nextBlock.x[3] = centerX + 1;
nextBlock.y[0] = -2;
nextBlock.y[1] = -1;
nextBlock.y[2] = -1;
nextBlock.y[3] = -1;
nextBlock.centerX = centerX;
nextBlock.centerY = -1;
nextBlock.ID = 6;
break;
default:
break;
}
}
//可以移动就对block进行变换,返回true
//否则返回false
bool Tetris::move(int dx, int dy)
{
int newX[COUNT];
int newY[COUNT];
int newCenterX;
int newCenterY;
for (int i = 0; i < COUNT; i++)
{
newX[i] = block.x[i] + dx;
newY[i] = block.y[i] + dy;
//对变换后的坐标进行判定
//x坐标超出范围返回false
if (newX[i] < 0 || newX[i] >= MAXX)
{
return false;
}
//y坐标在0 - MAXY之间就对box中的状态进行判定
//box中为1则返回false
if (newY[i] >=0 && newY[i] < MAXY)
{
if (box[newX[i]][newY[i]] == 1)
{
return false;
}
}//y坐标超出最大值返回false
else if (newY[i] >= MAXY)
{
return false;
}
}
newCenterX = block.centerX + dx;
newCenterY = block.centerY + dy;
//满足条件就将新的x和y坐标赋值给block
for (int i = 0; i < COUNT; i++)
{
block.x[i] = newX[i];
block.y[i] = newY[i];
}
block.centerX = newCenterX;
block.centerY = newCenterY;
return true;
}
//可以旋转就对block进行变换,返回true
//否则返回false
bool Tetris::isRotatable()
{
int newX[COUNT];
int newY[COUNT];
int newCenterX;
int newCenterY;
if (block.ID == 0)
{
return false;
}
for (int i = 0; i < COUNT; i++)
{
int nx = block.x[i] - block.centerX;
int ny = block.y[i] - block.centerY;
newX[i] = nx * 0 + ny * (-1) + block.centerX;
newY[i] = nx * 1 + ny * 0 + block.centerY;
//对变换后的坐标进行判定
//x坐标超出范围返回false
if (newX[i] < 0 || newX[i] >= MAXX)
{
return false;
}
//y坐标在0 - MAXY 之间就对box中的状态进行判定
//box中为1则返回false
if (newY[i] >=0 && newY[i] < MAXY)
{
if (box[newX[i]][newY[i]] == 1)
{
return false;
}
}//y坐标超过最大值返回false
else if (newY[i] >= MAXY)
{
return false;
}
}
newCenterX = block.centerX;
newCenterY = block.centerY;
//满足条件后进行block的赋值
for (int i = 0; i < COUNT; i++)
{
block.x[i] = newX[i];
block.y[i] = newY[i];
}
block.centerX = newCenterX;
block.centerY = newCenterY;
return true;
}
//将block中数据复制到box中
void Tetris::blockToBox()
{
for (int i = 0; i < COUNT; i++)
{
int x = block.x[i];
int y = block.y[i];
if (y >= 0)
{
box[x][y] = 1;
}
}
}
//获得第一个整行的行数,并返回
int Tetris::getFirstFullLine()
{
//这里j从1开始就好
for (int j = 0; j < MAXY; j++)
{
bool judgement = true;
for (int i = 0; i < MAXX; i++)
{
if (box[i][j] == 0)
{
judgement = false;
break;
}
}
if (judgement)
{
return j;
}
}
return -1;
}
tetrisbox.h
#ifndef TETRISBOX_H
#define TETRISBOX_H
#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <QPalette>
#include <QPen>
#include <QBrush>
#include <QColor>
//为了使用Block
#include "tetris.h"
class TetrisBox : public QWidget
{
Q_OBJECT
public:
explicit TetrisBox(QWidget *parent = nullptr);
void updateTetris(Tetris tetris); //更新数据和视图
void paintEvent(QPaintEvent *event); //绘制视图
signals:
public slots:
private:
Block block; //用来储存Tetris中block的数据
int box[MAXX][MAXY]; //用来存储Tetris中box的数据
};
#endif // TETRISBOX_H
tetrisbox.cpp
#include "tetrisbox.h"
TetrisBox::TetrisBox(QWidget *parent) : QWidget(parent)
{
//对block初始化
for (int i = 0; i < COUNT; i++)
{
block.x[i] = -1;
block.y[i] = -1;
}
block.centerX = -1;
block.centerY = -1;
block.ID = -1;
//对box初始化
for (int i = 0; i < MAXX; i++)
{
for (int j = 0; j < MAXY; j++)
{
box[i][j] = 0;
}
}
//设置本游戏窗口的宽度和高度
//并设置背景为黑色
int w = Tetris::getWidth();
int h = Tetris::getHeight();
setFixedSize(w, h);
setPalette(QPalette(Qt::black));
setAutoFillBackground(true);
}
void TetrisBox::updateTetris(Tetris tetris)
{
//更新block
block = tetris.getBlock();
//更新box
for (int i = 0; i < MAXX; i++)
{
for (int j = 0; j < MAXY; j++)
{
box[i][j] = tetris.getBox(i, j);
}
}
repaint();
}
void TetrisBox::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QPen pen;
QBrush brush;
pen.setStyle(Qt::SolidLine);
pen.setColor(QColor(255, 255, 255));
brush.setStyle(Qt::SolidPattern);
brush.setColor(QColor(255, 255, 255));
painter.setPen(pen);
painter.setBrush(brush);
//绘制box中的内容
for (int i = 0; i < MAXX; i++)
{
for (int j = 0; j < MAXY; j++)
{
if (box[i][j] == 1)
{
int x = i * WIDTH + i * INTERVAL;
int y = j * HEIGHT + j * INTERVAL;
painter.drawRect(x, y, WIDTH, HEIGHT);
}
}
}
//绘制block中的内容
for (int i = 0; i < COUNT; i++)
{
int x = block.x[i];
int y = block.y[i];
int x1 = x * WIDTH + x * INTERVAL;
int y1 = y * HEIGHT + y * INTERVAL;
painter.drawRect(x1, y1, WIDTH, HEIGHT);
}
}