| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the documentation of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:FDL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Free Documentation License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Free |
| ** Documentation License version 1.3 as published by the Free Software |
| ** Foundation and appearing in the file included in the packaging of |
| ** this file. Please review the following information to ensure |
| ** the GNU Free Documentation License version 1.3 requirements |
| ** will be met: https://www.gnu.org/licenses/fdl-1.3.html. |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| /*! |
| \example widgets/tetrix |
| \title Tetrix Example |
| \ingroup examples-widgets |
| \brief The Tetrix example is a Qt version of the classic Tetrix game. |
| |
| \borderedimage tetrix-example.png |
| |
| The object of the game is to stack pieces dropped from the top of the |
| playing area so that they fill entire rows at the bottom of the playing area. |
| |
| When a row is filled, all the blocks on that row are removed, the player earns |
| a number of points, and the pieces above are moved down to occupy that row. |
| If more than one row is filled, the blocks on each row are removed, and the |
| player earns extra points. |
| |
| The \uicontrol{Left} cursor key moves the current piece one space to the left, the |
| \uicontrol{Right} cursor key moves it one space to the right, the \uicontrol{Up} cursor |
| key rotates the piece counter-clockwise by 90 degrees, and the \uicontrol{Down} |
| cursor key rotates the piece clockwise by 90 degrees. |
| |
| To avoid waiting for a piece to fall to the bottom of the board, press \uicontrol{D} |
| to immediately move the piece down by one row, or press the \uicontrol{Space} key to |
| drop it as close to the bottom of the board as possible. |
| |
| This example shows how a simple game can be created using only three classes: |
| |
| \list |
| \li The \c TetrixWindow class is used to display the player's score, number of |
| lives, and information about the next piece to appear. |
| \li The \c TetrixBoard class contains the game logic, handles keyboard input, and |
| displays the pieces on the playing area. |
| \li The \c TetrixPiece class contains information about each piece. |
| \endlist |
| |
| In this approach, the \c TetrixBoard class is the most complex class, since it |
| handles the game logic and rendering. One benefit of this is that the |
| \c TetrixWindow and \c TetrixPiece classes are very simple and contain only a |
| minimum of code. |
| |
| \section1 TetrixWindow Class Definition |
| |
| The \c TetrixWindow class is used to display the game information and contains |
| the playing area: |
| |
| \snippet widgets/tetrix/tetrixwindow.h 0 |
| |
| We use private member variables for the board, various display widgets, and |
| buttons to allow the user to start a new game, pause the current game, and quit. |
| |
| Although the window inherits QWidget, the constructor does not provide an |
| argument to allow a parent widget to be specified. This is because the window |
| will always be used as a top-level widget. |
| |
| \section1 TetrixWindow Class Implementation |
| |
| The constructor sets up the user interface elements for the game: |
| |
| \snippet widgets/tetrix/tetrixwindow.cpp 0 |
| |
| We begin by constructing a \c TetrixBoard instance for the playing area and a |
| label that shows the next piece to be dropped into the playing area; the label |
| is initially empty. |
| |
| Three QLCDNumber objects are used to display the score, number of lives, and |
| lines removed. These initially show default values, and will be filled in |
| when a game begins: |
| |
| \snippet widgets/tetrix/tetrixwindow.cpp 1 |
| |
| Three buttons with shortcuts are constructed so that the user can start a |
| new game, pause the current game, and quit the application: |
| |
| \snippet widgets/tetrix/tetrixwindow.cpp 2 |
| \snippet widgets/tetrix/tetrixwindow.cpp 3 |
| |
| These buttons are configured so that they never receive the keyboard focus; |
| we want the keyboard focus to remain with the \c TetrixBoard instance so that |
| it receives all the keyboard events. Nonetheless, the buttons will still respond |
| to \uicontrol{Alt} key shortcuts. |
| |
| We connect \l{QAbstractButton::}{clicked()} signals from the \uicontrol{Start} |
| and \uicontrol{Pause} buttons to the board, and from the \uicontrol{Quit} button to the |
| application's \l{QCoreApplication::quit()} slot. |
| |
| \snippet widgets/tetrix/tetrixwindow.cpp 4 |
| \snippet widgets/tetrix/tetrixwindow.cpp 5 |
| |
| Signals from the board are also connected to the LCD widgets for the purpose of |
| updating the score, number of lives, and lines removed from the playing area. |
| |
| We place the label, LCD widgets, and the board into a QGridLayout |
| along with some labels that we create with the \c createLabel() convenience |
| function: |
| |
| \snippet widgets/tetrix/tetrixwindow.cpp 6 |
| |
| Finally, we set the grid layout on the widget, give the window a title, and |
| resize it to an appropriate size. |
| |
| The \c createLabel() convenience function simply creates a new label on the |
| heap, gives it an appropriate alignment, and returns it to the caller: |
| |
| \snippet widgets/tetrix/tetrixwindow.cpp 7 |
| |
| Since each label will be used in the widget's layout, it will become a child |
| of the \c TetrixWindow widget and, as a result, it will be deleted when the |
| window is deleted. |
| |
| \section1 TetrixPiece Class Definition |
| |
| The \c TetrixPiece class holds information about a piece in the game's |
| playing area, including its shape, position, and the range of positions it can |
| occupy on the board: |
| |
| \snippet widgets/tetrix/tetrixpiece.h 0 |
| |
| Each shape contains four blocks, and these are defined by the \c coords private |
| member variable. Additionally, each piece has a high-level description that is |
| stored internally in the \c pieceShape variable. |
| |
| The constructor is written inline in the definition, and simply ensures that |
| each piece is initially created with no shape. The \c shape() function simply |
| returns the contents of the \c pieceShape variable, and the \c x() and \c y() |
| functions return the x and y-coordinates of any given block in the shape. |
| |
| \section1 TetrixPiece Class Implementation |
| |
| The \c setRandomShape() function is used to select a random shape for a piece: |
| |
| \snippet widgets/tetrix/tetrixpiece.cpp 0 |
| |
| For convenience, it simply chooses a random shape from the \c TetrixShape enum |
| and calls the \c setShape() function to perform the task of positioning the |
| blocks. |
| |
| The \c setShape() function uses a look-up table of pieces to associate each |
| shape with an array of block positions: |
| |
| \snippet widgets/tetrix/tetrixpiece.cpp 1 |
| \snippet widgets/tetrix/tetrixpiece.cpp 2 |
| |
| These positions are read from the table into the piece's own array of positions, |
| and the piece's internal shape information is updated to use the new shape. |
| |
| The \c x() and \c y() functions are implemented inline in the class definition, |
| returning positions defined on a grid that extends horizontally and vertically |
| with coordinates from -2 to 2. Although the predefined coordinates for each |
| piece only vary horizontally from -1 to 1 and vertically from -1 to 2, each |
| piece can be rotated by 90, 180, and 270 degrees. |
| |
| The \c minX() and \c maxX() functions return the minimum and maximum horizontal |
| coordinates occupied by the blocks that make up the piece: |
| |
| \snippet widgets/tetrix/tetrixpiece.cpp 3 |
| \snippet widgets/tetrix/tetrixpiece.cpp 4 |
| |
| Similarly, the \c minY() and \c maxY() functions return the minimum and maximum |
| vertical coordinates occupied by the blocks: |
| |
| \snippet widgets/tetrix/tetrixpiece.cpp 5 |
| \snippet widgets/tetrix/tetrixpiece.cpp 6 |
| |
| The \c rotatedLeft() function returns a new piece with the same shape as an |
| existing piece, but rotated counter-clockwise by 90 degrees: |
| |
| \snippet widgets/tetrix/tetrixpiece.cpp 7 |
| |
| Similarly, the \c rotatedRight() function returns a new piece with the same |
| shape as an existing piece, but rotated clockwise by 90 degrees: |
| |
| \snippet widgets/tetrix/tetrixpiece.cpp 9 |
| |
| These last two functions enable each piece to create rotated copies of itself. |
| |
| \section1 TetrixBoard Class Definition |
| |
| The \c TetrixBoard class inherits from QFrame and contains the game logic and display features: |
| |
| \snippet widgets/tetrix/tetrixboard.h 0 |
| |
| Apart from the \c setNextPieceLabel() function and the \c start() and \c pause() |
| public slots, we only provide public functions to reimplement QWidget::sizeHint() |
| and QWidget::minimumSizeHint(). The signals are used to communicate changes to |
| the player's information to the \c TetrixWindow instance. |
| |
| The rest of the functionality is provided by reimplementations of protected event |
| handlers and private functions: |
| |
| \snippet widgets/tetrix/tetrixboard.h 1 |
| |
| The board is composed of a fixed-size array whose elements correspond to |
| spaces for individual blocks. Each element in the array contains a \c TetrixShape |
| value corresponding to the type of shape that occupies that element. |
| |
| Each shape on the board will occupy four elements in the array, and these will |
| all contain the enum value that corresponds to the type of the shape. |
| |
| We use a QBasicTimer to control the rate at which pieces fall toward the bottom |
| of the playing area. This allows us to provide an implementation of |
| \l{QObject::}{timerEvent()} that we can use to update the widget. |
| |
| \section1 TetrixBoard Class Implementation |
| |
| In the constructor, we customize the frame style of the widget, ensure that |
| keyboard input will be received by the widget by using Qt::StrongFocus for the |
| focus policy, and initialize the game state: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 0 |
| |
| The first (next) piece is also set up with a random shape. |
| |
| The \c setNextPieceLabel() function is used to pass in an externally-constructed |
| label to the board, so that it can be shown alongside the playing area: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 1 |
| |
| We provide a reasonable size hint and minimum size hint for the board, based on |
| the size of the space for each block in the playing area: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 2 |
| \snippet widgets/tetrix/tetrixboard.cpp 3 |
| |
| By using a minimum size hint, we indicate to the layout in the parent widget |
| that the board should not shrink below a minimum size. |
| |
| A new game is started when the \c start() slot is called. This resets the |
| game's state, the player's score and level, and the contents of the board: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 4 |
| |
| We also emit signals to inform other components of these changes before creating |
| a new piece that is ready to be dropped into the playing area. We start the |
| timer that determines how often the piece drops down one row on the board. |
| |
| The \c pause() slot is used to temporarily stop the current game by stopping the |
| internal timer: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 5 |
| \snippet widgets/tetrix/tetrixboard.cpp 6 |
| |
| We perform checks to ensure that the game can only be paused if it is already |
| running and not already paused. |
| |
| The \c paintEvent() function is straightforward to implement. We begin by |
| calling the base class's implementation of \l{QWidget::}{paintEvent()} before |
| constructing a QPainter for use on the board: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 7 |
| |
| Since the board is a subclass of QFrame, we obtain a QRect that covers the area |
| \e inside the frame decoration before drawing our own content. |
| |
| If the game is paused, we want to hide the existing state of the board and |
| show some text. We achieve this by painting text onto the widget and returning |
| early from the function. The rest of the painting is performed after this point. |
| |
| The position of the top of the board is found by subtracting the total height |
| of each space on the board from the bottom of the frame's internal rectangle. |
| For each space on the board that is occupied by a piece, we call the |
| \c drawSquare() function to draw a block at that position. |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 8 |
| \snippet widgets/tetrix/tetrixboard.cpp 9 |
| |
| Spaces that are not occupied by blocks are left blank. |
| |
| Unlike the existing pieces on the board, the current piece is drawn |
| block-by-block at its current position: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 10 |
| \snippet widgets/tetrix/tetrixboard.cpp 11 |
| \snippet widgets/tetrix/tetrixboard.cpp 12 |
| |
| The \c keyPressEvent() handler is called whenever the player presses a key while |
| the \c TetrixBoard widget has the keyboard focus. |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 13 |
| |
| If there is no current game, the game is running but paused, or if there is no |
| current shape to control, we simply pass on the event to the base class. |
| |
| We check whether the event is about any of the keys that the player uses to |
| control the current piece and, if so, we call the relevant function to handle |
| the input: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 14 |
| |
| In the case where the player presses a key that we are not interested in, we |
| again pass on the event to the base class's implementation of |
| \l{QWidget::}{keyPressEvent()}. |
| |
| The \c timerEvent() handler is called every time the class's QBasicTimer |
| instance times out. We need to check that the event we receive corresponds to |
| our timer. If it does, we can update the board: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 15 |
| \snippet widgets/tetrix/tetrixboard.cpp 16 |
| \snippet widgets/tetrix/tetrixboard.cpp 17 |
| |
| If a row (or line) has just been filled, we create a new piece and reset the |
| timer; otherwise we move the current piece down by one row. We let the base |
| class handle other timer events that we receive. |
| |
| The \c clearBoard() function simply fills the board with the |
| \c TetrixShape::NoShape value: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 18 |
| |
| The \c dropDown() function moves the current piece down as far as possible on |
| the board, either until it is touching the bottom of the playing area or it is |
| stacked on top of another piece: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 19 |
| \snippet widgets/tetrix/tetrixboard.cpp 20 |
| |
| The number of rows the piece has dropped is recorded and passed to the |
| \c pieceDropped() function so that the player's score can be updated. |
| |
| The \c oneLineDown() function is used to move the current piece down by one row |
| (line), either when the user presses the \uicontrol{D} key or when the piece is |
| scheduled to move: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 21 |
| |
| If the piece cannot drop down by one line, we call the \c pieceDropped() function |
| with zero as the argument to indicate that it cannot fall any further, and that |
| the player should receive no extra points for the fall. |
| |
| The \c pieceDropped() function itself is responsible for awarding points to the |
| player for positioning the current piece, checking for full rows on the board |
| and, if no lines have been removed, creating a new piece to replace the current |
| one: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 22 |
| \snippet widgets/tetrix/tetrixboard.cpp 23 |
| |
| We call \c removeFullLines() each time a piece has been dropped. This scans |
| the board from bottom to top, looking for blank spaces on each row. |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 24 |
| \snippet widgets/tetrix/tetrixboard.cpp 25 |
| \snippet widgets/tetrix/tetrixboard.cpp 26 |
| \snippet widgets/tetrix/tetrixboard.cpp 27 |
| |
| If a row contains no blank spaces, the rows above it are copied down by one row |
| to compress the stack of pieces, the top row on the board is cleared, and the |
| number of full lines found is incremented. |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 28 |
| \snippet widgets/tetrix/tetrixboard.cpp 29 |
| |
| If some lines have been removed, the player's score and the total number of lines |
| removed are updated. The \c linesRemoved() and \c scoreChanged() signals are |
| emitted to send these new values to other widgets in the window. |
| |
| Additionally, we set the timer to elapse after half a second, set the |
| \c isWaitingAfterLine flag to indicate that lines have been removed, unset |
| the piece's shape to ensure that it is not drawn, and update the widget. |
| The next time that the \c timerEvent() handler is called, a new piece will be |
| created and the game will continue. |
| |
| The \c newPiece() function places the next available piece at the top of the |
| board, and creates a new piece with a random shape: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 30 |
| \snippet widgets/tetrix/tetrixboard.cpp 31 |
| |
| We place a new piece in the middle of the board at the top. The game is over if |
| the piece can't move, so we unset its shape to prevent it from being drawn, stop |
| the timer, and unset the \c isStarted flag. |
| |
| The \c showNextPiece() function updates the label that shows the next piece to |
| be dropped: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 32 |
| \snippet widgets/tetrix/tetrixboard.cpp 33 |
| |
| We draw the piece's component blocks onto a pixmap that is then set on the label. |
| |
| The \c tryMove() function is used to determine whether a piece can be positioned |
| at the specified coordinates: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 34 |
| |
| We examine the spaces on the board that the piece needs to occupy and, if they |
| are already occupied by other pieces, we return \c false to indicate that the |
| move has failed. |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 35 |
| |
| If the piece could be placed on the board at the desired location, we update the |
| current piece and its position, update the widget, and return \c true to indicate |
| success. |
| |
| The \c drawSquare() function draws the blocks (normally squares) that make up |
| each piece using different colors for pieces with different shapes: |
| |
| \snippet widgets/tetrix/tetrixboard.cpp 36 |
| |
| We obtain the color to use from a look-up table that relates each shape to an |
| RGB value, and use the painter provided to draw the block at the specified |
| coordinates. |
| */ |