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

打印 上一主题 下一主题

主题 552|帖子 552|积分 1656

1 项目简介

本项目灵感来自经典的俄罗斯方块游戏(Tetris),该游戏由Alexey Pajitnov于1984年开辟。俄罗斯方块以其简单而富有挑衅性的游戏机制广受欢迎,成为了许多平台上的经典游戏。随着现代开辟工具的进步,利用Qt框架重新实现这一经典游戏不仅是对经典的致敬,也是对个人编程技能的一次提升。
《俄罗斯方块》的根本规则是移动、旋转和摆放游戏主动输出的各种方块,使之排列成完备的一行或多行并且消除得分。
a.按方向键的左右键可实现方块的左右移动;
b. 按方向键的下键可实现方块的加速下落;
c.按方向键的上键可实现方块的变形。
2 效果展示


3 代码实现

3.1 框架


3.2 UI界面


3.3 核心代码

3.3.1 TetrisGameWindow.h

  1. #ifndef TETRISGAMEWINDOW_H
  2. #define TETRISGAMEWINDOW_H
  3. #include <QWidget>
  4. #include <QPoint>
  5. #include "core/Subject.h"
  6. namespace Ui {
  7. class TetrisGameWindow;
  8. }
  9. class QTimer;
  10. namespace restonce {
  11. class TetrisGame;
  12. }
  13. class TetrisGameWindow
  14.         : public QWidget, public restonce::Observer
  15. {
  16.     Q_OBJECT
  17. public:
  18.     explicit TetrisGameWindow(QWidget *parent = 0);
  19.     ~TetrisGameWindow();
  20. protected:
  21.     void paintEvent(QPaintEvent *) override final;
  22.     void keyPressEvent(QKeyEvent *) override final;
  23.     virtual void onSubjectChanged() override final;
  24. private slots:
  25.     void on_pushButton_clicked();
  26.     void slot_timeout();
  27. private:
  28.     Ui::TetrisGameWindow *ui;
  29.     restonce::TetrisGame *m_game;
  30.     int m_boxSize = 24;
  31.     QPoint m_basePosition = QPoint(10, 10);
  32.     QPoint m_baseNextPosition = QPoint(200, 240);
  33.     QTimer *m_timer;
  34. };
  35. #endif // TETRISGAMEWINDOW_H
复制代码
3.3.2 TetrisGameWindow.cpp

  1. #include "TetrisGameWindow.h"
  2. #include "ui_TetrisGameWindow.h"
  3. #include "core/TetrisGame.h"
  4. #include "core/RandomBox.h"
  5. #include <QPainter>
  6. #include <QTimer>
  7. #include <QKeyEvent>
  8. TetrisGameWindow::TetrisGameWindow(QWidget *parent) :
  9.     QWidget(parent),
  10.     ui(new Ui::TetrisGameWindow)
  11. {
  12.     ui->setupUi(this);
  13.     m_game = new restonce::TetrisGame;
  14.     m_timer = new QTimer(this);
  15.     connect(m_timer, SIGNAL(timeout()),
  16.              this, SLOT(slot_timeout()));
  17.     this->setFixedSize(this->size());
  18.     m_game->attachObserver(this);
  19. }
  20. void TetrisGameWindow::slot_timeout()
  21. {
  22.     m_game->timeout();
  23. }
  24. TetrisGameWindow::~TetrisGameWindow()
  25. {
  26.     delete ui;
  27.     delete m_game;
  28. }
  29. void TetrisGameWindow::on_pushButton_clicked()
  30. {
  31.     m_timer->start(1000);
  32.     m_game->start();
  33. }
  34. void TetrisGameWindow::paintEvent(QPaintEvent *)
  35. {
  36.     switch(m_game->getGameStatus())
  37.     {
  38.     case restonce::TetrisGame::GameStatus::runing:
  39.         ui->label->setText("正在游戏");
  40.         ui->pushButton->setEnabled(false);
  41.         break;
  42.     case restonce::TetrisGame::GameStatus::stop:
  43.         ui->label->setText("游戏结束");
  44.         ui->pushButton->setEnabled(true);
  45.         break;
  46.     case restonce::TetrisGame::GameStatus::undo:
  47.         ui->label->clear();
  48.         ui->pushButton->setEnabled(true);
  49.         break;
  50.     }
  51.     QPainter painter(this);
  52.     QPoint p2(m_basePosition.x()+ m_boxSize*restonce::TetrisGame::ROW+1,
  53.                m_basePosition.y() -1);
  54.     QPoint p3(m_basePosition.x()-1,
  55.                m_basePosition.y() +m_boxSize*restonce::TetrisGame::LINE+1);
  56.     QPoint p4(m_basePosition.x() + m_boxSize*restonce::TetrisGame::ROW+1,
  57.                 m_basePosition.y() + m_boxSize*restonce::TetrisGame::LINE+1);
  58.     QPoint p1(m_basePosition.x()-1, m_basePosition.y()-1);
  59.     painter.drawLine(p1, p2);
  60.     painter.drawLine(p2, p4);
  61.     painter.drawLine(p4, p3);
  62.     painter.drawLine(p1, p3);
  63.     for(int l=0; l<restonce::TetrisGame::LINE; ++l) {
  64.         for(int r=0; r<restonce::TetrisGame::ROW; ++r) {
  65.             QPoint p(m_basePosition.x() + r*m_boxSize,
  66.                      m_basePosition.y() + l*m_boxSize);
  67.             int color = 0;
  68.             if(m_game->exists(l, r))
  69.             {
  70.                 color = m_game->color(l, r);
  71.             }
  72.             else if(m_game->getActiveBox() && m_game->getActiveBox()->inBody(l, r))
  73.             {
  74.                 color = m_game->getActiveBox()->color();
  75.             }
  76.             if(color <= 0)
  77.                 continue;
  78.             QString imgpath = QString::asprintf(":/boxes/images/box%d.jpg", color);
  79.             painter.drawImage(p, QImage(imgpath));
  80.         }
  81.     }
  82.     std::shared_ptr<restonce::RandomBox> nextBox = m_game->getNextBox();
  83.     if(nextBox) {
  84.         QString imgpath = QString::asprintf(":/boxes/images/box%d.jpg", nextBox->color());
  85.         for(restonce::Point const& p : nextBox->getMyBoxes()) {
  86.             painter.drawImage(QPoint(m_baseNextPosition.x() +m_boxSize*p.row(),
  87.                                       m_baseNextPosition.y() + m_boxSize*p.line()),
  88.                                QImage(imgpath));
  89.         }
  90.     }
  91. }
  92. void TetrisGameWindow::keyPressEvent(QKeyEvent *e)
  93. {
  94.     switch(e->key())
  95.     {
  96.     case Qt::Key_Down:
  97.         m_game->down();
  98.         break;
  99.     case Qt::Key_Left:
  100.         m_game->left();
  101.         break;
  102.     case Qt::Key_Right:
  103.         m_game->right();
  104.         break;
  105.     case Qt::Key_Up:
  106.         m_game->transform();
  107.         break;
  108.     }
  109. }
  110. void TetrisGameWindow::onSubjectChanged()
  111. {
  112.     repaint();
  113. }
复制代码
3.3.3 Subject.h

  1. #ifndef RESTONCE_SUBJECT_H
  2. #define RESTONCE_SUBJECT_H
  3. #include <set>
  4. namespace restonce {
  5. class Observer
  6. {
  7. protected:
  8.     Observer() = default;
  9.     virtual ~Observer() = default;
  10. public:
  11.     virtual void onSubjectChanged() = 0;
  12. };
  13. class Subject
  14. {
  15. public :
  16.     void attachObserver(Observer *o);
  17. protected:
  18.     Subject() = default;
  19.     virtual ~Subject() = default;
  20.     void notifyObservers() ;
  21. private:
  22.     std::set<Observer *> m_observers;
  23. };
  24. } // namespace restonce
  25. #endif // RESTONCE_SUBJECT_H
复制代码
3.3.4 Subject.cpp

  1. #include "core/Subject.h"
  2. namespace restonce {
  3. void Subject::attachObserver(Observer *o)
  4. {
  5.     m_observers.insert(o);
  6. }
  7. void Subject::notifyObservers()
  8. {
  9.     for(Observer *o : m_observers)
  10.     {
  11.         o->onSubjectChanged();
  12.     }
  13. }
  14. } // namespace restonce
复制代码
3.3.5 TetrisGame.h

  1. #ifndef RESTONCE_TETRISGAME_H
  2. #define RESTONCE_TETRISGAME_H
  3. #include <memory>
  4. #include <random>
  5. #include "Subject.h"
  6. namespace restonce {
  7. class RandomBox;
  8. class TetrisGame
  9.         : public Subject
  10. {
  11. public:
  12.     enum class GameStatus {
  13.         undo, runing, stop
  14.     };
  15.     enum class WinStatus {
  16.         win, lose
  17.     };
  18.     enum {
  19.         ROW = 10,
  20.         LINE = 18
  21.     };
  22.     TetrisGame();
  23.     // 用户通过gui可以对游戏进行的操作
  24.     void start();
  25.     void timeout();
  26.     void transform();
  27.     void down();
  28.     void left();
  29.     void right();
  30.     void stop();
  31.     // gui更新时会用以下函数读取游戏状态
  32.     GameStatus getGameStatus() const;
  33.     WinStatus getWinStatus() const;
  34.     // 是否存在方块,如果越界会抛出异常
  35.     bool exists(int line, int row) const;
  36.     int color(int line, int row) const;
  37.     std::shared_ptr<RandomBox> getActiveBox() const;
  38.     // 下一个Box
  39.     std::shared_ptr<RandomBox> getNextBox() const;
  40.     // 某位置是否越界
  41.     bool valid(int line, int row) const;
  42.     // 填充某个位置
  43.     void set(int line, int row, int color);
  44. private:
  45.     void init();
  46. private:
  47.     GameStatus m_gameStatus;
  48.     WinStatus m_winStatus;
  49.     bool m_map[LINE][ROW] ;
  50.     int  m_colorMap[LINE][ROW] ;
  51.     std::shared_ptr<RandomBox> m_activebox, m_nextBox;
  52.     std::mt19937 m_rd;
  53. };
  54. } // namespace restonce
  55. #endif // RESTONCE_TETRISGAME_H
复制代码
3.3.6 TetrisGame.cpp

  1. #include "TetrisGame.h"
  2. #include "RandomBox.h"
  3. #include <time.h>
  4. namespace restonce {
  5. TetrisGame::TetrisGame()
  6. {
  7.     m_gameStatus = GameStatus::undo;
  8.     m_winStatus = WinStatus::lose;
  9.     m_rd.seed(time(NULL));
  10.     for(int l=0; l<LINE; ++l) {
  11.         for(int r=0; r<ROW; ++r) {
  12.             m_map[l][r] = false;
  13.         }
  14.     }
  15. }
  16. void TetrisGame::init()
  17. {
  18.     for(int l=0; l<LINE; ++l) {
  19.         for(int r=0; r<ROW; ++r) {
  20.             m_map[l][r] = false;
  21.             m_colorMap[l][r] = 0;
  22.         }
  23.     }
  24.     m_gameStatus = GameStatus::undo;
  25.     m_winStatus = WinStatus::lose;
  26.     m_activebox = std::make_shared<RandomBox>(*this, m_rd);
  27.     m_nextBox = std::make_shared<RandomBox>(*this, m_rd);
  28. }
  29. void TetrisGame::start()
  30. {
  31.     if(m_gameStatus == GameStatus::runing) {
  32.         throw std::logic_error("Game is runing !");
  33.     }
  34.     init();
  35.     m_gameStatus = GameStatus::runing;
  36.     notifyObservers();
  37. }
  38. void TetrisGame::timeout()
  39. {
  40.     if(m_gameStatus != GameStatus::runing) {
  41.         return  ;
  42.     }
  43.     if(!m_activebox->down()) {
  44.         // 此处准备消行
  45.         for(int line=LINE-1; line>=0; --line) {
  46.             bool isFull = true;
  47.             for(int row=0; row<ROW; ++row) {
  48.                 if(!this->exists(line, row)) {
  49.                     isFull = false;
  50.                     break;
  51.                 }
  52.             }
  53.             if(isFull) {
  54.                 for(int l=line; l>=0; --l) {
  55.                     for(int r=0; r<ROW; ++r) {
  56.                         if(l ==0) {
  57.                             m_map[l][r] = false;
  58.                         } else {
  59.                             m_map[l][r] = m_map[l-1][r];
  60.                         }
  61.                     }
  62.                 }
  63.                 ++ line;
  64.             }
  65.         }
  66.         //
  67.         m_activebox =m_nextBox;
  68.         m_nextBox=std::make_shared<RandomBox>(*this, m_rd);
  69.         if(!m_activebox->valid()) {
  70.             // 新产生的方块不合法,说明你已经输了
  71.             m_gameStatus = GameStatus::stop;
  72.             m_winStatus = WinStatus::lose;
  73.         }
  74.     }
  75.     notifyObservers();
  76. }
  77. void TetrisGame::transform()
  78. {
  79.     if(m_activebox->transform()) {
  80.         notifyObservers();
  81.     }
  82. }
  83. void TetrisGame::down()
  84. {
  85.     timeout();
  86. }
  87. void TetrisGame::left()
  88. {
  89.     if(m_activebox->left()) {
  90.         notifyObservers();
  91.     }
  92. }
  93. void TetrisGame::right()
  94. {
  95.     if(m_activebox->right()) {
  96.         notifyObservers();
  97.     }
  98. }
  99. void TetrisGame::stop()
  100. {
  101.     if(m_gameStatus == GameStatus::runing) {
  102.         m_gameStatus = GameStatus::stop;
  103.         m_winStatus = WinStatus::lose;
  104.         notifyObservers();
  105.     }
  106. }
  107. TetrisGame::GameStatus TetrisGame::getGameStatus() const
  108. {
  109.     return m_gameStatus;
  110. }
  111. TetrisGame::WinStatus TetrisGame::getWinStatus() const
  112. {
  113.     if(m_gameStatus != GameStatus::stop) {
  114.         throw std::logic_error("Game is not stop !");
  115.     }
  116.     return m_winStatus;
  117. }
  118. bool TetrisGame::valid(int line, int row) const
  119. {
  120.     return line >=0 && line < LINE &&
  121.             row >=0 && row < ROW;
  122. }
  123. bool TetrisGame::exists(int line, int row) const
  124. {
  125.     if(!valid(line, row)) {
  126.         throw std::out_of_range("Game position not exists !");
  127.     }
  128.     return m_map[line][row];
  129. }
  130. int TetrisGame::color(int line, int row) const
  131. {
  132.     return m_colorMap[line][row];
  133. }
  134. std::shared_ptr<RandomBox> TetrisGame::getActiveBox() const
  135. {
  136.     return m_activebox;
  137. }
  138. void TetrisGame::set(int line, int row, int color)
  139. {
  140.     m_map[line][row] = true;
  141.     m_colorMap[line][row] = color;
  142. }
  143. std::shared_ptr<RandomBox> TetrisGame::getNextBox() const
  144. {
  145.     return m_nextBox;
  146. }
  147. } // namespace restonce
复制代码
4 运行效果



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用户云卷云舒

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表