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