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