1 //////////////////////////////////////////////////////////////////////
2 //
3 // This file is part of BeeBEEP.
4 //
5 // BeeBEEP is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published
7 // by the Free Software Foundation, either version 3 of the License,
8 // or (at your option) any later version.
9 //
10 // BeeBEEP is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with BeeBEEP. If not, see <http://www.gnu.org/licenses/>.
17 //
18 // Author: Marco Mastroddi <marco.mastroddi(AT)gmail.com>
19 //
20 // $Id: TetrisBoard.cpp 346 2015-04-05 16:12:37Z mastroddi $
21 //
22 //////////////////////////////////////////////////////////////////////
23
24 #include <QKeyEvent>
25 #include <QPainter>
26 #include "TetrisBoard.h"
27
28
TetrisBoard(QWidget * parent)29 TetrisBoard::TetrisBoard( QWidget *parent )
30 : QFrame( parent )
31 {
32 setFrameStyle( QFrame::StyledPanel );
33 setFocusPolicy( Qt::StrongFocus );
34
35 setStyleSheet( "background-image: url(:/plugins/tetris-background.png);"
36 "background-repeat: repeat-y;" );
37
38 m_isStarted = false;
39 m_isPaused = false;
40 m_isGameOver = false;
41
42 clearBoard();
43
44 m_nextPiece.setRandomShape();
45 }
46
start()47 void TetrisBoard::start()
48 {
49 if( m_isPaused )
50 return;
51
52 m_isStarted = true;
53 m_isGameOver = false;
54 m_isWaitingAfterLine = false;
55 m_numLinesRemoved = 0;
56 m_numPiecesDropped = 0;
57 m_score = 0;
58 m_level = 1;
59
60 clearBoard();
61
62 emit linesRemovedChanged( m_numLinesRemoved );
63 emit scoreChanged( m_score );
64 emit levelChanged( m_level );
65
66 newPiece();
67
68 m_timer.start( timeoutTime(), this );
69
70 emit( started() );
71 }
72
pause()73 void TetrisBoard::pause()
74 {
75 if( !m_isStarted )
76 return;
77
78 m_isPaused = !m_isPaused;
79
80 if( m_isPaused )
81 m_timer.stop();
82 else
83 m_timer.start( timeoutTime(), this );
84
85 update();
86
87 emit( paused() );
88 }
89
setGameOver()90 void TetrisBoard::setGameOver()
91 {
92 m_isGameOver = true;
93 mp_nextPieceLabel->setPixmap( QPixmap() );
94 update();
95 emit( gameOver() );
96 }
97
paintEvent(QPaintEvent * event)98 void TetrisBoard::paintEvent( QPaintEvent *event )
99 {
100 QFrame::paintEvent( event );
101
102 QPainter painter(this);
103 QRect rect = contentsRect();
104
105 if( m_isPaused || m_isGameOver )
106 {
107 QFont font = painter.font();
108 font.setBold( true );
109 font.setPointSize( font.pointSize() + 6 );
110 painter.setFont( font );
111 painter.drawText( rect, Qt::AlignCenter, m_isPaused ? tr( "Pause" ) : tr( "Game Over" ) );
112 return;
113 }
114
115 int board_top = rect.bottom() - BoardHeight * squareHeight();
116
117 for( int i = 0; i < BoardHeight; i++ )
118 {
119 for( int j = 0; j < BoardWidth; j++ )
120 {
121 TetrisPiece::Shape shape = shapeAt( j, BoardHeight - i - 1 );
122 if( shape != TetrisPiece::NoShape )
123 drawSquare( painter, rect.left() + j * squareWidth(), board_top + i * squareHeight(), shape );
124 }
125 }
126
127 if( m_curPiece.shape() != TetrisPiece::NoShape )
128 {
129 for( int i = 0; i < 4; ++i )
130 {
131 int x = m_curX + m_curPiece.x( i );
132 int y = m_curY - m_curPiece.y( i );
133
134 drawSquare( painter, rect.left() + x * squareWidth(), board_top + (BoardHeight - y - 1) * squareHeight(), m_curPiece.shape() );
135 }
136 }
137 }
138
keyPressEvent(QKeyEvent * event)139 void TetrisBoard::keyPressEvent( QKeyEvent* event )
140 {
141 if( !m_isStarted || m_isGameOver || m_curPiece.shape() == TetrisPiece::NoShape )
142 {
143 QFrame::keyPressEvent( event );
144 return;
145 }
146
147 if( m_isPaused )
148 {
149 if( event->key() == Qt::Key_P )
150 pause();
151 else
152 QFrame::keyPressEvent( event );
153 return;
154 }
155
156 switch( event->key() )
157 {
158 case Qt::Key_Left:
159 tryMove( m_curPiece, m_curX - 1, m_curY );
160 break;
161 case Qt::Key_Right:
162 tryMove( m_curPiece, m_curX + 1, m_curY );
163 break;
164 case Qt::Key_Down:
165 tryMove( m_curPiece.rotatedRight(), m_curX, m_curY );
166 break;
167 case Qt::Key_Up:
168 tryMove( m_curPiece.rotatedLeft(), m_curX, m_curY );
169 break;
170 case Qt::Key_Space:
171 dropDown();
172 break;
173 case Qt::Key_D:
174 oneLineDown();
175 break;
176 case Qt::Key_P:
177 pause();
178 break;
179 default:
180 QFrame::keyPressEvent( event );
181 }
182 }
183
timerEvent(QTimerEvent * event)184 void TetrisBoard::timerEvent( QTimerEvent* event )
185 {
186 if( event->timerId() == m_timer.timerId() )
187 {
188 if( m_isWaitingAfterLine )
189 {
190 m_isWaitingAfterLine = false;
191 newPiece();
192 m_timer.start( timeoutTime(), this );
193 }
194 else
195 oneLineDown();
196 }
197 else
198 QFrame::timerEvent(event);
199
200 }
201
focusOutEvent(QFocusEvent * event)202 void TetrisBoard::focusOutEvent( QFocusEvent* event )
203 {
204 if( !isPaused() )
205 pause();
206 QWidget::focusOutEvent( event );
207 }
208
clearBoard()209 void TetrisBoard::clearBoard()
210 {
211 for( int i = 0; i < BoardHeight * BoardWidth; i++ )
212 m_board[ i ] = TetrisPiece::NoShape;
213 }
214
dropDown()215 void TetrisBoard::dropDown()
216 {
217 int drop_height = 0;
218 int newY = m_curY;
219
220 while( newY > 0 )
221 {
222 if( !tryMove( m_curPiece, m_curX, newY - 1 ) )
223 break;
224 --newY;
225 ++drop_height;
226 }
227
228 pieceDropped( drop_height );
229 }
230
oneLineDown()231 void TetrisBoard::oneLineDown()
232 {
233 if( !tryMove( m_curPiece, m_curX, m_curY - 1 ) )
234 pieceDropped( 0 );
235 }
236
pieceDropped(int drop_height)237 void TetrisBoard::pieceDropped( int drop_height )
238 {
239 for( int i = 0; i < 4; i++ )
240 {
241 int x = m_curX + m_curPiece.x(i);
242 int y = m_curY - m_curPiece.y(i);
243 shapeAt( x, y ) = m_curPiece.shape();
244 }
245
246 ++m_numPiecesDropped;
247
248 if( m_numPiecesDropped % 25 == 0 )
249 {
250 ++m_level;
251 m_timer.start( timeoutTime(), this );
252 emit levelChanged( m_level );
253 }
254
255 m_score += drop_height + 7;
256 emit scoreChanged( m_score );
257
258 removeFullLines();
259
260 if( !m_isWaitingAfterLine )
261 newPiece();
262 }
263
removeFullLines()264 void TetrisBoard::removeFullLines()
265 {
266 int num_full_lines = 0;
267
268 for( int i = BoardHeight - 1; i >= 0; i-- )
269 {
270 bool line_is_full = true;
271
272 for( int j = 0; j < BoardWidth; j++ )
273 {
274 if( shapeAt( j, i ) == TetrisPiece::NoShape )
275 {
276 line_is_full = false;
277 break;
278 }
279 }
280
281 if( line_is_full )
282 {
283 ++num_full_lines;
284
285 for( int k = i; k < BoardHeight - 1; k++ )
286 {
287 for( int j = 0; j < BoardWidth; j++ )
288 shapeAt( j, k ) = shapeAt( j, k + 1 );
289 }
290
291 for( int j = 0; j < BoardWidth; j++ )
292 shapeAt( j, BoardHeight - 1 ) = TetrisPiece::NoShape;
293 }
294
295 }
296
297 if( num_full_lines > 0 )
298 {
299 m_numLinesRemoved += num_full_lines;
300 m_score += 10 * num_full_lines;
301
302 emit linesRemovedChanged( m_numLinesRemoved );
303 emit scoreChanged( m_score );
304
305 m_timer.start(500, this);
306 m_isWaitingAfterLine = true;
307 m_curPiece.setShape( TetrisPiece::NoShape );
308 update();
309 }
310 }
311
newPiece()312 void TetrisBoard::newPiece()
313 {
314 m_curPiece = m_nextPiece;
315 m_nextPiece.setRandomShape();
316 showNextPiece();
317 m_curX = BoardWidth / 2 + 1;
318 m_curY = BoardHeight - 1 + m_curPiece.minY();
319
320 if( !tryMove( m_curPiece, m_curX, m_curY ) )
321 {
322 m_curPiece.setShape( TetrisPiece::NoShape );
323 m_timer.stop();
324 m_isStarted = false;
325 setGameOver();
326 }
327 }
328
showNextPiece()329 void TetrisBoard::showNextPiece()
330 {
331 if( !mp_nextPieceLabel )
332 return;
333
334 int dx = m_nextPiece.maxX() - m_nextPiece.minX() + 1;
335 int dy = m_nextPiece.maxY() - m_nextPiece.minY() + 1;
336
337 QPixmap pix( dx * squareWidth(), dy * squareHeight() );
338 QPainter painter( &pix );
339 painter.fillRect( pix.rect(), mp_nextPieceLabel->palette().background() );
340
341 for( int i = 0; i < 4; i++ )
342 {
343 int x = m_nextPiece.x( i ) - m_nextPiece.minX();
344 int y = m_nextPiece.y( i ) - m_nextPiece.minY();
345
346 drawSquare( painter, x * squareWidth(), y * squareHeight(), m_nextPiece.shape() );
347 }
348
349 mp_nextPieceLabel->setPixmap( pix );
350 }
351
tryMove(const TetrisPiece & new_piece,int newX,int newY)352 bool TetrisBoard::tryMove( const TetrisPiece& new_piece, int newX, int newY )
353 {
354 for( int i = 0; i < 4; i++ )
355 {
356 int x = newX + new_piece.x( i );
357 int y = newY - new_piece.y( i );
358
359 if( x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight )
360 return false;
361
362 if( shapeAt( x, y ) != TetrisPiece::NoShape )
363 return false;
364 }
365
366 m_curPiece = new_piece;
367 m_curX = newX;
368 m_curY = newY;
369 update();
370 return true;
371 }
372
drawSquare(QPainter & painter,int x,int y,TetrisPiece::Shape shape)373 void TetrisBoard::drawSquare( QPainter& painter, int x, int y, TetrisPiece::Shape shape )
374 {
375 static const QRgb color_table[ TetrisPiece::NumShapes ] = {
376 0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
377 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00
378 };
379
380 QColor color = color_table[ (int)shape ];
381 painter.fillRect( x + 1, y + 1, squareWidth() - 2, squareHeight() - 2, color );
382
383 painter.setPen( color.light() );
384 painter.drawLine( x, y + squareHeight() - 1, x, y );
385 painter.drawLine( x, y, x + squareWidth() - 1, y );
386
387 painter.setPen( color.dark() );
388 painter.drawLine( x + 1, y + squareHeight() - 1, x + squareWidth() - 1, y + squareHeight() - 1 );
389 painter.drawLine( x + squareWidth() - 1, y + squareHeight() - 1, x + squareWidth() - 1, y + 1 );
390 }
391
392