diff --git a/TetrisTask.pro b/TetrisTask.pro index f4de819..ddf75f8 100644 --- a/TetrisTask.pro +++ b/TetrisTask.pro @@ -11,11 +11,13 @@ CONFIG += c++17 SOURCES += \ gamefield.cpp \ main.cpp \ - tetris.cpp + tetris.cpp \ + figure.cpp HEADERS += \ gamefield.h \ - tetris.h + tetris.h \ + figure.h FORMS += \ tetris.ui diff --git a/figure.cpp b/figure.cpp new file mode 100644 index 0000000..9ef9161 --- /dev/null +++ b/figure.cpp @@ -0,0 +1,62 @@ +#include "figure.h" +#include + +Figure::Figure() {} + +void Figure::generateFigure() { + QVector>> figures; + figures.append( { {0, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0} } ); + figures.append( { {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0} } ); + figures.append( { {0, 0, 1, 0}, {0, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0} } ); + figures.append( { {0, 1, 1, 0}, {0, 0, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0} } ); + figures.append( { {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0} } ); + figures.append( { {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0} } ); + srand(time(NULL)); + int index = rand() % figures.size(); + figure_ = figures[index]; +} + +int Figure::getXPoints() const { + return figure_.size(); +} + +int Figure::getYPoints() const { + return figure_[0].size(); +} + +int Figure::getFigureAt(QPoint point) const { + if (point.y() >= 0 && point.y() < getYPoints() && point.x() >= 0 && point.x() < getXPoints()) + return figure_[point.y()][point.x()]; + return 0; +} +QVector> Figure::getFigure() { + return figure_; +} +void Figure::rotate() { + int xPoints = getXPoints(); + int yPoints = getYPoints(); + QVector> rotated(xPoints, QVector (yPoints)); + + for (int i = 0; i < yPoints; i++) { + for (int j = 0; j < xPoints; j++) { + rotated[j][yPoints - 1 - i] = figure_[i][j]; + } + } + figure_ = rotated; +} +void Figure::generateColor() { + QVector colors; + colors.append( QColor(255, 135, 50)); + colors.append( QColor(168, 50, 168)); + colors.append( QColor(0, 255, 0)); + colors.append( QColor(0, 255, 255)); + colors.append( QColor(127, 0, 255)); + + srand(time(NULL)); + int index = rand() % colors.size(); + color_ = colors[index]; +} + +QColor Figure::getColor() { + return color_; +} diff --git a/figure.h b/figure.h new file mode 100644 index 0000000..ba72b2c --- /dev/null +++ b/figure.h @@ -0,0 +1,27 @@ +#ifndef FIGURE_H +#define FIGURE_H + +#include ; +#include ; +#include +#include + +class Figure { +public: + Figure(); + + void rotate(); + void generateFigure(); + int getXPoints() const; + int getYPoints() const; + int getFigureAt(QPoint point) const; + QVector> getFigure(); + void generateColor(); + QColor getColor(); + +private: + QVector> figure_; + QColor color_; +}; + +#endif // FIGURE_H diff --git a/gamefield.cpp b/gamefield.cpp index 6a5ce13..112cdfc 100644 --- a/gamefield.cpp +++ b/gamefield.cpp @@ -1,32 +1,188 @@ #include "gamefield.h" #include +#include +#include GameField::GameField(QWidget *parent) : QWidget{parent} { - connect(this, &GameField::InitialisationStarted, this, &GameField::SetCells, + setFocusPolicy(Qt::StrongFocus); + + connect(this, &GameField::initialisationStarted, this, &GameField::SetCells, Qt::QueuedConnection); + connect(this, &GameField::moveFigure, this, &GameField::refresh); - emit InitialisationStarted(); + timer_ = new QTimer(this); + connect(timer_, &QTimer::timeout, this, &GameField::moveDown); + timer_ -> start(500); + startNewGame(); + emit initialisationStarted(); } uint GameField::GetRowsNumber() const { return rowsNumber_; } void GameField::SetRowsNumber(uint rowsNumber) { if (rowsNumber_ == rowsNumber) return; - rowsNumber_ = rowsNumber; - emit RowsNumberChanged(); + emit rowsNumberChanged(); } uint GameField::GetColumnsNumber() const { return columnsNumber_; } void GameField::SetColumnNumber(uint columnsCount) { if (columnsNumber_ == columnsCount) return; - columnsNumber_ = columnsCount; - emit ColumnsNumberChanged(); + emit columnsNumberChanged(); +} + +void GameField::drawFigure(Figure figure, QPainter *painter, QPoint currentPosition) { + + for (int i = 0; i < figure.getFigure().size(); i++) { + for (int j = 0; j < figure.getFigure()[i].size(); j++) { + if (figure.getFigure()[j][i] == 1) { + painter->fillRect(( j + currentPosition.x()) * blockSize_, (i + currentPosition.y()) * blockSize_, blockSize_, blockSize_, figure.getColor()); + } + } + } +} + +void GameField::paintEvent(QPaintEvent *event) { + Q_UNUSED(event); + + int nextLayerPositionX = 11; + + QPainter *painter = new QPainter(this); + QVector> gameField = gameField_; + + painter->fillRect(0, 0, gameField[0].size()*blockSize_, gameField.size()*blockSize_, Qt::white); + for (int i = 0; i < gameField.size(); i++) { + for (int j = 0; j < gameField[i].size(); j++) { + if (gameField[i][j] == 1) { + painter->fillRect(j * blockSize_, i * blockSize_, blockSize_, blockSize_, Qt::yellow); + } else { + painter->drawRect(j * blockSize_, i * blockSize_, blockSize_, blockSize_); + } + } + } + + drawFigure(currentFigure_, painter, QPoint(currentXPosition_, currentYPosition_)); + drawFigure(nextFigure_, painter, QPoint(nextLayerPositionX, 0)); + painter->drawText(350, 125, QString("Score: %1").arg(score_)); + } void GameField::SetCells() { qDebug() << rowsNumber_ << " " << columnsNumber_ << "|"; } + +void GameField::keyPressEvent(QKeyEvent *event) { + switch( event->key()) { + case Qt::Key_Left: + moveFigureLeft(); + break; + case Qt::Key_Right: + moveFigureRight(); + break; + case Qt::Key_Up: + rotateFigure(); + break; + case Qt::Key_Down: + moveDown(); + break; + } +} + +void GameField::rotateFigure() { + Figure rotatedFigure = currentFigure_; + rotatedFigure.rotate(); + if (!isCollision(rotatedFigure, 0, 0)) { + currentFigure_ = rotatedFigure; + emit moveFigure(); + } + } + +void GameField::moveFigureRight() { + if(!isCollision(currentFigure_, 1, 0)) { + currentXPosition_++; + emit moveFigure(); + } +} + +void GameField::moveFigureLeft() { + if(!isCollision(currentFigure_, -1, 0)) { + currentXPosition_--; + emit moveFigure(); + } +} + +void GameField::moveDown() { + if(!isCollision(currentFigure_, 0, 1)) { + currentYPosition_++; + emit moveFigure(); + }else { + for (int i = 0; i < currentFigure_.getYPoints(); i++) + for (int j = 0; j < currentFigure_.getXPoints(); j++) + if (currentFigure_.getFigureAt(QPoint(i, j)) == 1) + gameField_[currentYPosition_ + i][currentXPosition_ + j] = 1; + setNewFigure(); + clearLines(); + } +} + +void GameField::refresh() { + repaint(); +} + +bool GameField::isCollision(const Figure &movedFigure, int xOffset, int yOffset) { + + for (int i = 0; i <= movedFigure.getYPoints(); i++) { + for (int j = 0; j < movedFigure.getXPoints(); j++) { + if (movedFigure.getFigureAt(QPoint(i, j)) == 1) { + if (currentXPosition_ + j + xOffset < 0 || currentXPosition_ + j + xOffset >= gameField_[0].size() || + currentYPosition_ + i + yOffset >= gameField_.size() || + gameField_[currentYPosition_ + i + yOffset][currentXPosition_ + j + xOffset] == 1) + return true; + } + } + } + return false; +} + +void GameField::setNewFigure() { + currentFigure_ = nextFigure_; + nextFigure_.generateFigure(); + nextFigure_.generateColor(); + currentYPosition_ = 0; + currentXPosition_ = 3; + emit moveFigure(); + if(isCollision(currentFigure_, 0, 0)) { + startNewGame(); + }; +} + +void GameField::startNewGame() { + int xPoints = 10; + int yPoints = 20; + gameField_ = QVector> (yPoints, QVector (xPoints, 0)); + currentFigure_.generateFigure(); + currentFigure_.generateColor(); + nextFigure_.generateFigure(); + nextFigure_.generateColor(); + currentYPosition_ = 0; + currentXPosition_ = 3; + score_ = 0; +} + +void GameField::clearLines() { + for (int i = 0; i <= gameField_.size() - 1; i++) { + if (std::all_of(gameField_[i].begin(), gameField_[i].end(), [](int val) { return val == 1; })) { + gameField_.remove(i); + gameField_.prepend(QVector (gameField_[0].size(), 0)); + changeScore(100); + i--; + } + } +} + + void GameField::changeScore(int points){ + score_=score_ + points; + } diff --git a/gamefield.h b/gamefield.h index e36baa8..80f5711 100644 --- a/gamefield.h +++ b/gamefield.h @@ -2,6 +2,11 @@ #define GAMEFIELD_H #include +#include +#include +#include + +#include "figure.h" class GameField : public QWidget { Q_OBJECT @@ -9,28 +14,56 @@ class GameField : public QWidget { explicit GameField(QWidget *parent = nullptr); Q_PROPERTY(uint rowsNumber READ GetRowsNumber WRITE SetRowsNumber NOTIFY - RowsNumberChanged FINAL) + rowsNumberChanged FINAL) Q_PROPERTY(uint columnsNumber READ GetColumnsNumber WRITE SetColumnNumber - NOTIFY ColumnsNumberChanged FINAL) + NOTIFY columnsNumberChanged FINAL) signals: - void RowsNumberChanged(); - void ColumnsNumberChanged(); - void InitialisationStarted(); + void rowsNumberChanged(); + void columnsNumberChanged(); + void initialisationStarted(); + void moveFigure(); private slots: void SetCells(); public: uint GetRowsNumber() const; - void SetRowsNumber(uint newRowsNumber); - uint GetColumnsNumber() const; void SetColumnNumber(uint newColumnsCount); + void SetRowsNumber(uint newRowsNumber); + + void moveDown(); + void rotateFigure(); + void moveFigureLeft(); + void moveFigureRight(); + void setNewFigure(); + void startNewGame(); + void clearLines(); + void changeScore(int points); + + QVector> getGameField(); + +protected slots: + void refresh(); private: - uint rowsNumber_ = 0; - uint columnsNumber_ = 0; + void paintEvent(QPaintEvent *event); + void keyPressEvent(QKeyEvent *event); + bool isCollision(const Figure &movedFigure, int xOffset, int yOffset); + void drawFigure(Figure figure, QPainter *painter, QPoint currentPosition); + + QVector> gameField_; + Figure nextFigure_; + Figure currentFigure_; + uint rowsNumber_ = 0; + uint columnsNumber_ = 0; + int currentXPosition_ = 3; + int currentYPosition_ = 0; + int score_ = 0; + int blockSize_ = 30; + QTimer* timer_; + QPainter painter_; }; #endif // GAMEFIELD_H diff --git a/tetris.cpp b/tetris.cpp index 50cf380..e7b0214 100644 --- a/tetris.cpp +++ b/tetris.cpp @@ -1,9 +1,64 @@ #include "tetris.h" - #include "ui_tetris.h" +#include "gamefield.h" + +#include +#include +#include +#include +#include Tetris::Tetris(QWidget *parent) : QMainWindow(parent), ui(new Ui::Tetris) { ui->setupUi(this); + + auto *gameField = new GameField(this); + setCentralWidget(gameField); + + QMenu *gameMenu = new QMenu(); + gameMenu->setTitle("Menu"); + gameMenu->setVisible(true); + + QAction *startNewGameAction = new QAction("New Game"); + QAction *exitGameAction = new QAction("Exit"); + QAction *rulesAction = new QAction("Rules"); + + connect(startNewGameAction, &QAction::triggered, gameField, &GameField::startNewGame); + connect(exitGameAction, &QAction::triggered, this, &Tetris::close); + connect(rulesAction, &QAction::triggered, this, &Tetris::showRules); + + gameMenu->addAction(startNewGameAction); + gameMenu->addAction(exitGameAction); + gameMenu->addAction(rulesAction); + + ui->menubar->addMenu(gameMenu); + + QLabel *label = new QLabel(this); + label->setGeometry(350, 0, 100, 75); + label->setText("Next Figure"); + label->show(); + + QPushButton *newGameButton = new QPushButton("New Game", this); + newGameButton->setGeometry(350, 200, 100, 25); + QPushButton *exitButton = new QPushButton("Exit", this); + exitButton->setGeometry(350, 250, 100, 25); + + connect(newGameButton, &QPushButton::clicked, gameField, &GameField::startNewGame); + connect(exitButton, &QPushButton::clicked, this, &Tetris::close); } +void Tetris::showRules() { + QMessageBox::information( + this, + "Rules", + "In Tetris, players complete lines by moving differently shaped pieces (tetrominoes), which descend onto the playing field.\n" + "The completed lines disappear and grant the player points, and the player can proceed to fill the vacated spaces.\n" + "The game ends when the uncleared lines reach the top of the playing field.\n" + "\n" + "Game Control\n" + " Up key- rotate figure\n" + " Left key - move the figure Left\n" + " Right key - move the figure Right\n" + " Down key - lower the figure down\n" + ); + } Tetris::~Tetris() { delete ui; } diff --git a/tetris.h b/tetris.h index 1245a26..ca7aea7 100644 --- a/tetris.h +++ b/tetris.h @@ -2,6 +2,8 @@ #define TETRIS_H #include +#include +#include QT_BEGIN_NAMESPACE namespace Ui { @@ -16,7 +18,13 @@ class Tetris : public QMainWindow { Tetris(QWidget *parent = nullptr); ~Tetris(); +private slots: + void showRules(); + private: Ui::Tetris *ui; + QMenu *mewGameMenu_; + QMenu *exitMenu_; + QMenu *rulesMenu_; }; #endif // TETRIS_H diff --git a/tetris.ui b/tetris.ui index ae63379..ee960e1 100644 --- a/tetris.ui +++ b/tetris.ui @@ -6,8 +6,8 @@ 0 0 - 800 - 600 + 611 + 723 @@ -21,17 +21,27 @@ 10 + 0 0 - 800 - 21 + 611 + 26 - + + + Exit + + + + + StartNewGame + +