【C++\Qt项目实战】俄罗斯方块

俄罗斯方块

  • [1 项目简介](#1 项目简介)
  • [2 效果展示](#2 效果展示)
  • [3 代码实现](#3 代码实现)
    • [3.1 框架](#3.1 框架)
    • [3.2 UI界面](#3.2 UI界面)
    • [3.3 核心代码](#3.3 核心代码)
      • [3.3.1 TetrisGameWindow.h](#3.3.1 TetrisGameWindow.h)
      • [3.3.2 TetrisGameWindow.cpp](#3.3.2 TetrisGameWindow.cpp)
      • [3.3.3 Subject.h](#3.3.3 Subject.h)
      • [3.3.4 Subject.cpp](#3.3.4 Subject.cpp)
      • [3.3.5 TetrisGame.h](#3.3.5 TetrisGame.h)
      • [3.3.6 TetrisGame.cpp](#3.3.6 TetrisGame.cpp)
  • [4 运行效果](#4 运行效果)

1 项目简介

本项目灵感来自经典的俄罗斯方块游戏(Tetris),该游戏由Alexey Pajitnov于1984年开发。俄罗斯方块以其简单而富有挑战性的游戏机制广受欢迎,成为了许多平台上的经典游戏。随着现代开发工具的进步,使用Qt框架重新实现这一经典游戏不仅是对经典的致敬,也是对个人编程技能的一次提升。

《俄罗斯方块》的基本规则是移动、旋转和摆放游戏自动输出的各种方块,使之排列成完整的一行或多行并且消除得分。

a.按方向键的左右键可实现方块的左右移动;

b. 按方向键的下键可实现方块的加速下落;

c.按方向键的上键可实现方块的变形。

2 效果展示

3 代码实现

3.1 框架

3.2 UI界面

3.3 核心代码

3.3.1 TetrisGameWindow.h

cpp 复制代码
#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

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

cpp 复制代码
#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

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

cpp 复制代码
#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

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

4 运行效果

相关推荐
诚丞成1 小时前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++
东风吹柳2 小时前
观察者模式(sigslot in C++)
c++·观察者模式·信号槽·sigslot
A懿轩A2 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
云空2 小时前
《QT 5.14.1 搭建 opencv 环境全攻略》
开发语言·qt·opencv
大胆飞猪3 小时前
C++9--前置++和后置++重载,const,日期类的实现(对前几篇知识点的应用)
c++
1 9 J3 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法
夕泠爱吃糖3 小时前
C++中如何实现序列化和反序列化?
服务器·数据库·c++
小老鼠不吃猫3 小时前
力学笃行(二)Qt 示例程序运行
开发语言·qt
长潇若雪3 小时前
《类和对象:基础原理全解析(上篇)》
开发语言·c++·经验分享·类和对象
晓纪同学5 小时前
QT创建一个模板槽和信号刷新UI
开发语言·qt·ui