1 /***********************************************************************
2 *
3 * Copyright (C) 2007, 2008, 2010, 2012, 2013, 2014, 2015 Graeme Gott <graeme@gottcode.org>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 ***********************************************************************/
19
20 #include "board.h"
21
22 #include "piece.h"
23
24 #include <QKeyEvent>
25 #include <QMessageBox>
26 #include <QPainter>
27 #include <QPixmap>
28 #include <QTimer>
29
30 #include <algorithm>
31 #include <ctime>
32
33 /*****************************************************************************/
34
35 static QColor colors[] = {
36 QColor("#0057ae"),
37 QColor("#006e29"),
38 QColor("#9c0f0f"),
39 QColor("#539ae3"),
40 QColor("#8f6b32"),
41 QColor("#ec7331"),
42 QColor("#644a9b")
43 };
44
45 /*****************************************************************************/
46
Board(QWidget * parent)47 Board::Board(QWidget* parent)
48 : QWidget(parent),
49 m_removed_lines(0),
50 m_level(1),
51 m_score(0),
52 m_piece(0),
53 m_next_piece(1),
54 m_flash_frame(-1),
55 m_piece_size(0),
56 m_started(false),
57 m_done(false),
58 m_paused(false),
59 m_random_distribution(1, 7)
60 {
61 setMinimumSize(201, 401);
62 setFocusPolicy(Qt::StrongFocus);
63 setFocus();
64
65 m_shift_timer = new QTimer(this);
66 m_shift_timer->setInterval(500);
67 m_shift_timer->setSingleShot(true);
68 connect(m_shift_timer, &QTimer::timeout, this, &Board::shiftPiece);
69
70 m_flash_timer = new QTimer(this);
71 m_flash_timer->setInterval(80);
72 connect(m_flash_timer, &QTimer::timeout, this, &Board::flashLines);
73
74 for (int i = 0; i < 4; ++i) {
75 m_full_lines[i] = -1;
76 }
77
78 for (int col = 0; col < 10; ++col) {
79 for (int row = 0; row < 20; ++row) {
80 m_cells[col][row] = 0;
81 }
82 }
83
84 #ifndef Q_OS_WIN
85 std::random_device rd;
86 m_random_generator.seed(rd());
87 #else
88 std::mt19937 gen(time(0));
89 std::uniform_int_distribution<unsigned int> dist;
90 m_random_generator.seed(dist(gen));
91 #endif
92 m_next_piece = nextPiece();
93 }
94
95 /*****************************************************************************/
96
endGame()97 bool Board::endGame()
98 {
99 if (m_done || !m_started) {
100 return true;
101 }
102
103 if (QMessageBox::question(this, tr("Question"), tr("End the current game?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
104 gameOver();
105 return true;
106 } else {
107 return false;
108 }
109 }
110
111 /*****************************************************************************/
112
findFullLines()113 void Board::findFullLines()
114 {
115 // Empty list of full lines
116 for (int i = 0; i < 4; ++i)
117 m_full_lines[i] = -1;
118 int pos = 0;
119
120 // Find full lines
121 bool full = false;
122 for (int row = 0; row < 20; ++row) {
123 full = true;
124 for (int col = 0; col < 10; ++col) {
125 if (m_cells[col][row] == 0)
126 full = false;
127 }
128 if (full) {
129 m_full_lines[pos] = row;
130 ++pos;
131 }
132 }
133 }
134
135 /*****************************************************************************/
136
newGame()137 void Board::newGame()
138 {
139 if (!endGame()) {
140 return;
141 }
142
143 m_flash_timer->stop();
144 m_shift_timer->stop();
145 delete m_piece;
146 m_piece = 0;
147
148 emit hideMessage();
149 m_started = true;
150 m_done = false;
151 m_paused = false;
152 m_removed_lines = 0;
153 m_level = 1;
154 m_score = 0;
155 m_shift_timer->setInterval(500);
156 m_next_piece = nextPiece();
157
158 for (int i = 0; i < 4; ++i)
159 m_full_lines[i] = -1;
160
161 for (int col = 0; col < 10; ++col) {
162 for (int row = 0; row < 20; ++row) {
163 m_cells[col][row] = 0;
164 }
165 }
166
167 emit pauseAvailable(true);
168 emit levelUpdated(m_level);
169 emit linesRemovedUpdated(m_removed_lines);
170 emit scoreUpdated(m_score);
171 emit gameStarted();
172
173 setCursor(Qt::BlankCursor);
174 createPiece();
175 }
176
177 /*****************************************************************************/
178
pauseGame()179 void Board::pauseGame()
180 {
181 m_paused = true;
182 if (m_flash_frame > -1) {
183 m_flash_timer->stop();
184 } else {
185 m_shift_timer->stop();
186 }
187
188 update();
189 emit showMessage(tr("<big><b>Paused</b></big><br>Click to resume playing."));
190
191 unsetCursor();
192 emit pauseAvailable(false);
193 }
194
195 /*****************************************************************************/
196
resumeGame()197 void Board::resumeGame()
198 {
199 m_paused = false;
200 if (m_flash_frame > -1) {
201 m_flash_timer->start();
202 } else {
203 m_shift_timer->start();
204 }
205
206 emit hideMessage();
207 update();
208
209 setCursor(Qt::BlankCursor);
210 emit pauseAvailable(true);
211 }
212
213 /*****************************************************************************/
214
keyPressEvent(QKeyEvent * event)215 void Board::keyPressEvent(QKeyEvent* event)
216 {
217 if (!m_piece || m_paused)
218 return;
219
220 switch (event->key()) {
221 case Qt::Key_Left:
222 m_piece->moveLeft();
223 break;
224 case Qt::Key_Right:
225 m_piece->moveRight();
226 break;
227 case Qt::Key_Up:
228 m_piece->rotate();
229 break;
230 case Qt::Key_Down:
231 m_piece->drop();
232 landPiece();
233 break;
234 default:
235 break;
236 }
237
238 update();
239 }
240
241 /*****************************************************************************/
242
mousePressEvent(QMouseEvent * event)243 void Board::mousePressEvent(QMouseEvent* event)
244 {
245 if (m_paused) {
246 resumeGame();
247 } else if (m_done || !m_started) {
248 newGame();
249 }
250 QWidget::mousePressEvent(event);
251 }
252
253 /*****************************************************************************/
254
paintEvent(QPaintEvent *)255 void Board::paintEvent(QPaintEvent*)
256 {
257 QPainter painter(this);
258 painter.setRenderHint(QPainter::Antialiasing, true);
259 painter.fillRect(m_background, Qt::black);
260
261 if (m_paused) {
262 return;
263 }
264
265 // Draw board
266 for (int col = 0; col < 10; ++col) {
267 for (int row = 0; row < 20; ++row) {
268 int cell = m_cells[col][row] - 1;
269 if (cell >= 0) {
270 painter.drawPixmap(col * m_piece_size + m_background.x(), row * m_piece_size + m_background.y(), m_images[cell]);
271 }
272 }
273 }
274
275 // Draw piece
276 if (m_piece) {
277 int type = m_piece->type() - 1;
278 const Cell* cells = m_piece->cells();
279 for (int i = 0; i < 4; ++i) {
280 painter.drawPixmap(cells[i].x * m_piece_size + m_background.x(), cells[i].y * m_piece_size + m_background.y(), m_images[type]);
281 }
282 }
283 }
284
285 /*****************************************************************************/
286
focusOutEvent(QFocusEvent *)287 void Board::focusOutEvent(QFocusEvent*)
288 {
289 if (m_piece && !m_done && !m_paused)
290 pauseGame();
291 }
292
293 /*****************************************************************************/
294
resizeEvent(QResizeEvent * event)295 void Board::resizeEvent(QResizeEvent* event)
296 {
297 m_piece_size = std::min(event->size().width() / 10, event->size().height() / 20);
298 int w = m_piece_size * 10 + 1;
299 int h = m_piece_size * 20 + 1;
300 m_background = QRect((width() - w) / 2, (height() - h) / 2, w, h);
301
302 QPainter painter;
303
304 int ratio = devicePixelRatio();
305 for (int i = 0; i < 7; ++i) {
306 QPixmap pixmap((m_piece_size + 1) * ratio, (m_piece_size + 1) * ratio);
307 pixmap.setDevicePixelRatio(ratio);
308 pixmap.fill(QColor(0, 0, 0, 0));
309
310 painter.begin(&pixmap);
311 painter.setRenderHint(QPainter::Antialiasing, true);
312 painter.setBrush(colors[i]);
313 painter.setPen(QPen(colors[i].lighter(), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
314 painter.drawRoundedRect(QRectF(1.5, 1.5, m_piece_size - 2, m_piece_size - 2), 25, 25, Qt::RelativeSize);
315 painter.end();
316
317 m_images[i] = pixmap;
318 }
319 }
320
321 /*****************************************************************************/
322
shiftPiece()323 void Board::shiftPiece()
324 {
325 Q_ASSERT(m_piece != 0);
326
327 if (m_piece->moveDown()) {
328 update();
329 m_shift_timer->start();
330 } else {
331 landPiece();
332 }
333 }
334
335 /*****************************************************************************/
336
flashLines()337 void Board::flashLines()
338 {
339 m_flash_frame++;
340 if (m_flash_frame < 6) {
341 int amount = (m_flash_frame % 2) ? 7 : -7;
342
343 for (int i = 0; i < 4; ++i) {
344 int row = m_full_lines[i];
345 if (row == -1) {
346 break;
347 }
348
349 for (int col = 0; col < 10; ++col) {
350 m_cells[col][row] += amount;
351 }
352 }
353
354 update();
355 } else {
356 m_flash_timer->stop();
357 m_flash_frame = -1;
358 removeLines();
359 }
360 }
361
362 /*****************************************************************************/
363
removeLines()364 void Board::removeLines()
365 {
366 int score = 14 * m_level;
367
368 // Loop through full lines
369 for (int i = 0; i < 4; ++i) {
370 int row = m_full_lines[i];
371 if (row == -1)
372 break;
373
374 // Remove line
375 for (int col = 0; col < 10; ++col) {
376 removeCell(col, row);
377 }
378 ++m_removed_lines;
379 score *= 3;
380
381 // Shift board down
382 for (; row > 0; --row) {
383 for (int col = 0; col < 10; ++col) {
384 m_cells[col][row] = m_cells[col][row - 1];
385 }
386 }
387 }
388
389 // Remove top line
390 if (m_full_lines[0] != -1) {
391 for (int col = 0; col < 10; ++col) {
392 removeCell(col, 0);
393 }
394 }
395
396 m_level = (m_removed_lines / 10) + 1;
397 m_shift_timer->setInterval(10000 / (m_removed_lines + 20));
398 m_score += score;
399 emit levelUpdated(m_level);
400 emit linesRemovedUpdated(m_removed_lines);
401 emit scoreUpdated(m_score);
402
403 // Empty list of full lines
404 for (int i = 0; i < 4; ++i)
405 m_full_lines[i] = -1;
406
407 // Add new piece
408 createPiece();
409 }
410
411 /*****************************************************************************/
412
gameOver()413 void Board::gameOver()
414 {
415 delete m_piece;
416 m_piece = 0;
417 m_done = true;
418 unsetCursor();
419 emit showMessage(tr("<big><b>Game Over!</b></big><br>Click to start a new game."));
420 emit gameOver(m_level, m_removed_lines, m_score);
421 }
422
423 /*****************************************************************************/
424
addCell(int x,int y,int type)425 void Board::addCell(int x, int y, int type)
426 {
427 Q_ASSERT(x >= 0 && x < 10);
428 Q_ASSERT(y >= 0 && y < 20);
429 Q_ASSERT(type > 0 && type < 8);
430 Q_ASSERT(m_cells[x][y] == 0);
431
432 m_cells[x][y] = type;
433 }
434
435 /*****************************************************************************/
436
removeCell(int x,int y)437 void Board::removeCell(int x, int y)
438 {
439 Q_ASSERT(x >= 0 && x < 10);
440 Q_ASSERT(y >= 0 && y < 20);
441
442 m_cells[x][y] = 0;
443 }
444
445 /*****************************************************************************/
446
createPiece()447 void Board::createPiece()
448 {
449 Q_ASSERT(m_piece == 0);
450
451 m_piece = new Piece(m_next_piece, this);
452 if (m_piece->isValid()) {
453 m_next_piece = nextPiece();
454 emit nextPieceAvailable(renderPiece(m_next_piece));
455 m_shift_timer->start();
456 } else {
457 gameOver();
458 }
459 update();
460 }
461
462 /*****************************************************************************/
463
landPiece()464 void Board::landPiece()
465 {
466 m_shift_timer->stop();
467
468 int type = m_piece->type();
469 const Cell* cells = m_piece->cells();
470 for (int i = 0; i < 4; ++i) {
471 addCell(cells[i].x, cells[i].y, type);
472 }
473 delete m_piece;
474 m_piece = 0;
475
476 findFullLines();
477 if (m_full_lines[0] != -1) {
478 m_flash_timer->start();
479 } else {
480 createPiece();
481 }
482 }
483
484 /*****************************************************************************/
485
renderPiece(int type) const486 QPixmap Board::renderPiece(int type) const
487 {
488 Q_ASSERT(type > 0 && type < 8);
489
490 Cell piece[4];
491 Piece::cells(piece, type);
492
493 int ratio = devicePixelRatio();
494 QPixmap result(80 * ratio, 100 * ratio);
495 result.setDevicePixelRatio(ratio);
496 result.fill(Qt::black);
497 {
498 QPainter painter(&result);
499 painter.setRenderHint(QPainter::Antialiasing, true);
500 if (type == 1) {
501 painter.translate(30, 10);
502 } else if (type < 7) {
503 painter.translate(20, 20);
504 } else {
505 painter.translate(20, 30);
506 }
507
508 for (int i = 0; i < 4; ++i) {
509 painter.setBrush(colors[type - 1]);
510 painter.setPen(QPen(colors[type - 1].lighter(), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
511 painter.drawRoundedRect(QRectF(piece[i].x * 20 + 0.5, piece[i].y * 20 + 0.5, 18, 18), 25, 25, Qt::RelativeSize);
512 }
513 }
514
515 return result;
516 }
517
518 /*****************************************************************************/
519