俄罗斯方块
1 项目简介
本项目灵感来自经典的俄罗斯方块游戏(Tetris),该游戏由Alexey Pajitnov于1984年开发。俄罗斯方块以其简单而富有挑战性的游戏机制广受欢迎,成为了许多平台上的经典游戏。随着现代开发工具的进步,使用Qt框架重新实现这一经典游戏不仅是对经典的致敬,也是对个人编程技能的一次提升。
《俄罗斯方块》的基本规则是移动、旋转和摆放游戏自动输出的各种方块,使之排列成完整的一行或多行并且消除得分。
a.按方向键的左右键可实现方块的左右移动;
b. 按方向键的下键可实现方块的加速下落;
c.按方向键的上键可实现方块的变形。
2 效果展示
3 代码实现
3.1 框架
3.2 UI界面
3.3 核心代码
3.3.1 TetrisGameWindow.h
#ifndef TETRISGAMEWINDOW_H
#define TETRISGAMEWINDOW_H
#include <QWidget>
#include <QPoint>
#include "core/Subject.h"
namespace Ui {
class TetrisGameWindow;
}
class QTimer;
namespace restonce {
class TetrisGame;
}
class TetrisGameWindow
: public QWidget, public restonce::Observer
{
Q_OBJECT
public:
explicit TetrisGameWindow(QWidget *parent = 0);
~TetrisGameWindow();
protected:
void paintEvent(QPaintEvent *) override final;
void keyPressEvent(QKeyEvent *) override final;
virtual void onSubjectChanged() override final;
private slots:
void on_pushButton_clicked();
void slot_timeout();
private:
Ui::TetrisGameWindow *ui;
restonce::TetrisGame *m_game;
int m_boxSize = 24;
QPoint m_basePosition = QPoint(10, 10);
QPoint m_baseNextPosition = QPoint(200, 240);
QTimer *m_timer;
};
#endif // TETRISGAMEWINDOW_H
3.3.2 TetrisGameWindow.cpp
#include "TetrisGameWindow.h"
#include "ui_TetrisGameWindow.h"
#include "core/TetrisGame.h"
#include "core/RandomBox.h"
#include <QPainter>
#include <QTimer>
#include <QKeyEvent>
TetrisGameWindow::TetrisGameWindow(QWidget *parent) :
QWidget(parent),
ui(new Ui::TetrisGameWindow)
{
ui->setupUi(this);
m_game = new restonce::TetrisGame;
m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()),
this, SLOT(slot_timeout()));
this->setFixedSize(this->size());
m_game->attachObserver(this);
}
void TetrisGameWindow::slot_timeout()
{
m_game->timeout();
}
TetrisGameWindow::~TetrisGameWindow()
{
delete ui;
delete m_game;
}
void TetrisGameWindow::on_pushButton_clicked()
{
m_timer->start(1000);
m_game->start();
}
void TetrisGameWindow::paintEvent(QPaintEvent *)
{
switch(m_game->getGameStatus())
{
case restonce::TetrisGame::GameStatus::runing:
ui->label->setText("正在游戏");
ui->pushButton->setEnabled(false);
break;
case restonce::TetrisGame::GameStatus::stop:
ui->label->setText("游戏结束");
ui->pushButton->setEnabled(true);
break;
case restonce::TetrisGame::GameStatus::undo:
ui->label->clear();
ui->pushButton->setEnabled(true);
break;
}
QPainter painter(this);
QPoint p2(m_basePosition.x()+ m_boxSize*restonce::TetrisGame::ROW+1,
m_basePosition.y() -1);
QPoint p3(m_basePosition.x()-1,
m_basePosition.y() +m_boxSize*restonce::TetrisGame::LINE+1);
QPoint p4(m_basePosition.x() + m_boxSize*restonce::TetrisGame::ROW+1,
m_basePosition.y() + m_boxSize*restonce::TetrisGame::LINE+1);
QPoint p1(m_basePosition.x()-1, m_basePosition.y()-1);
painter.drawLine(p1, p2);
painter.drawLine(p2, p4);
painter.drawLine(p4, p3);
painter.drawLine(p1, p3);
for(int l=0; l<restonce::TetrisGame::LINE; ++l) {
for(int r=0; r<restonce::TetrisGame::ROW; ++r) {
QPoint p(m_basePosition.x() + r*m_boxSize,
m_basePosition.y() + l*m_boxSize);
int color = 0;
if(m_game->exists(l, r))
{
color = m_game->color(l, r);
}
else if(m_game->getActiveBox() && m_game->getActiveBox()->inBody(l, r))
{
color = m_game->getActiveBox()->color();
}
if(color <= 0)
continue;
QString imgpath = QString::asprintf(":/boxes/images/box%d.jpg", color);
painter.drawImage(p, QImage(imgpath));
}
}
std::shared_ptr<restonce::RandomBox> nextBox = m_game->getNextBox();
if(nextBox) {
QString imgpath = QString::asprintf(":/boxes/images/box%d.jpg", nextBox->color());
for(restonce::Point const& p : nextBox->getMyBoxes()) {
painter.drawImage(QPoint(m_baseNextPosition.x() +m_boxSize*p.row(),
m_baseNextPosition.y() + m_boxSize*p.line()),
QImage(imgpath));
}
}
}
void TetrisGameWindow::keyPressEvent(QKeyEvent *e)
{
switch(e->key())
{
case Qt::Key_Down:
m_game->down();
break;
case Qt::Key_Left:
m_game->left();
break;
case Qt::Key_Right:
m_game->right();
break;
case Qt::Key_Up:
m_game->transform();
break;
}
}
void TetrisGameWindow::onSubjectChanged()
{
repaint();
}
3.3.3 Subject.h
#ifndef RESTONCE_SUBJECT_H
#define RESTONCE_SUBJECT_H
#include <set>
namespace restonce {
class Observer
{
protected:
Observer() = default;
virtual ~Observer() = default;
public:
virtual void onSubjectChanged() = 0;
};
class Subject
{
public :
void attachObserver(Observer *o);
protected:
Subject() = default;
virtual ~Subject() = default;
void notifyObservers() ;
private:
std::set<Observer *> m_observers;
};
} // namespace restonce
#endif // RESTONCE_SUBJECT_H
3.3.4 Subject.cpp
#include "core/Subject.h"
namespace restonce {
void Subject::attachObserver(Observer *o)
{
m_observers.insert(o);
}
void Subject::notifyObservers()
{
for(Observer *o : m_observers)
{
o->onSubjectChanged();
}
}
} // namespace restonce
3.3.5 TetrisGame.h
#ifndef RESTONCE_TETRISGAME_H
#define RESTONCE_TETRISGAME_H
#include <memory>
#include <random>
#include "Subject.h"
namespace restonce {
class RandomBox;
class TetrisGame
: public Subject
{
public:
enum class GameStatus {
undo, runing, stop
};
enum class WinStatus {
win, lose
};
enum {
ROW = 10,
LINE = 18
};
TetrisGame();
// 用户通过gui可以对游戏进行的操作
void start();
void timeout();
void transform();
void down();
void left();
void right();
void stop();
// gui更新时会用以下函数读取游戏状态
GameStatus getGameStatus() const;
WinStatus getWinStatus() const;
// 是否存在方块,如果越界会抛出异常
bool exists(int line, int row) const;
int color(int line, int row) const;
std::shared_ptr<RandomBox> getActiveBox() const;
// 下一个Box
std::shared_ptr<RandomBox> getNextBox() const;
// 某位置是否越界
bool valid(int line, int row) const;
// 填充某个位置
void set(int line, int row, int color);
private:
void init();
private:
GameStatus m_gameStatus;
WinStatus m_winStatus;
bool m_map[LINE][ROW] ;
int m_colorMap[LINE][ROW] ;
std::shared_ptr<RandomBox> m_activebox, m_nextBox;
std::mt19937 m_rd;
};
} // namespace restonce
#endif // RESTONCE_TETRISGAME_H
3.3.6 TetrisGame.cpp
#include "TetrisGame.h"
#include "RandomBox.h"
#include <time.h>
namespace restonce {
TetrisGame::TetrisGame()
{
m_gameStatus = GameStatus::undo;
m_winStatus = WinStatus::lose;
m_rd.seed(time(NULL));
for(int l=0; l<LINE; ++l) {
for(int r=0; r<ROW; ++r) {
m_map[l][r] = false;
}
}
}
void TetrisGame::init()
{
for(int l=0; l<LINE; ++l) {
for(int r=0; r<ROW; ++r) {
m_map[l][r] = false;
m_colorMap[l][r] = 0;
}
}
m_gameStatus = GameStatus::undo;
m_winStatus = WinStatus::lose;
m_activebox = std::make_shared<RandomBox>(*this, m_rd);
m_nextBox = std::make_shared<RandomBox>(*this, m_rd);
}
void TetrisGame::start()
{
if(m_gameStatus == GameStatus::runing) {
throw std::logic_error("Game is runing !");
}
init();
m_gameStatus = GameStatus::runing;
notifyObservers();
}
void TetrisGame::timeout()
{
if(m_gameStatus != GameStatus::runing) {
return ;
}
if(!m_activebox->down()) {
// 此处准备消行
for(int line=LINE-1; line>=0; --line) {
bool isFull = true;
for(int row=0; row<ROW; ++row) {
if(!this->exists(line, row)) {
isFull = false;
break;
}
}
if(isFull) {
for(int l=line; l>=0; --l) {
for(int r=0; r<ROW; ++r) {
if(l ==0) {
m_map[l][r] = false;
} else {
m_map[l][r] = m_map[l-1][r];
}
}
}
++ line;
}
}
//
m_activebox =m_nextBox;
m_nextBox=std::make_shared<RandomBox>(*this, m_rd);
if(!m_activebox->valid()) {
// 新产生的方块不合法,说明你已经输了
m_gameStatus = GameStatus::stop;
m_winStatus = WinStatus::lose;
}
}
notifyObservers();
}
void TetrisGame::transform()
{
if(m_activebox->transform()) {
notifyObservers();
}
}
void TetrisGame::down()
{
timeout();
}
void TetrisGame::left()
{
if(m_activebox->left()) {
notifyObservers();
}
}
void TetrisGame::right()
{
if(m_activebox->right()) {
notifyObservers();
}
}
void TetrisGame::stop()
{
if(m_gameStatus == GameStatus::runing) {
m_gameStatus = GameStatus::stop;
m_winStatus = WinStatus::lose;
notifyObservers();
}
}
TetrisGame::GameStatus TetrisGame::getGameStatus() const
{
return m_gameStatus;
}
TetrisGame::WinStatus TetrisGame::getWinStatus() const
{
if(m_gameStatus != GameStatus::stop) {
throw std::logic_error("Game is not stop !");
}
return m_winStatus;
}
bool TetrisGame::valid(int line, int row) const
{
return line >=0 && line < LINE &&
row >=0 && row < ROW;
}
bool TetrisGame::exists(int line, int row) const
{
if(!valid(line, row)) {
throw std::out_of_range("Game position not exists !");
}
return m_map[line][row];
}
int TetrisGame::color(int line, int row) const
{
return m_colorMap[line][row];
}
std::shared_ptr<RandomBox> TetrisGame::getActiveBox() const
{
return m_activebox;
}
void TetrisGame::set(int line, int row, int color)
{
m_map[line][row] = true;
m_colorMap[line][row] = color;
}
std::shared_ptr<RandomBox> TetrisGame::getNextBox() const
{
return m_nextBox;
}
} // namespace restonce