1 /***********************************************************************
2 *
3 * Copyright (C) 2007, 2008, 2009, 2012, 2014, 2015, 2016, 2018, 2019 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 "maze.h"
23 #include "solver.h"
24 #include "theme.h"
25
26 #include <QKeyEvent>
27 #include <QLabel>
28 #include <QMainWindow>
29 #include <QMessageBox>
30 #include <QPainter>
31 #include <QSettings>
32 #include <QStatusBar>
33 #include <QTimeLine>
34 #include <QTime>
35 #include <QTimer>
36
37 #include <algorithm>
38 #include <ctime>
39 #include <random>
40
41 // ============================================================================
42
Board(QMainWindow * parent)43 Board::Board(QMainWindow* parent)
44 : QWidget(parent),
45 m_done(false),
46 m_paused(false),
47 m_total_targets(3),
48 m_maze(0),
49 m_solver(0),
50 m_show_path(true),
51 m_smooth_movement(true),
52 m_col_delta(0),
53 m_row_delta(0),
54 m_unit(32),
55 m_zoom(5),
56 m_max_zoom(5),
57 m_zoom_size(14),
58 m_player_angle(360),
59 m_player_steps(0),
60 m_player_total_time(0),
61 m_hint(-1, -1),
62 m_hint_angle(0)
63 {
64 setMinimumSize(448, 448);
65 setFocusPolicy(Qt::StrongFocus);
66
67 m_move_animation = new QTimeLine(60, this);
68 m_move_animation->setFrameRange(0, 3);
69 m_move_animation->setCurveShape(QTimeLine::LinearCurve);
70 m_move_animation->setUpdateInterval(15);
71 connect(m_move_animation, &QTimeLine::frameChanged, this, static_cast<void (Board::*)()>(&Board::repaint));
72
73 // Create status messages
74 m_status_time_message = new QLabel;
75 m_status_time_message->setContentsMargins(10, 0, 10, 0);
76 parent->statusBar()->addPermanentWidget(m_status_time_message);
77
78 m_status_steps_message = new QLabel;
79 m_status_steps_message->setContentsMargins(10, 0, 10, 0);
80 parent->statusBar()->addPermanentWidget(m_status_steps_message);
81
82 m_status_remain_message = new QLabel;
83 m_status_remain_message->setContentsMargins(10, 0, 10, 0);
84 parent->statusBar()->addPermanentWidget(m_status_remain_message);
85
86 m_status_timer = new QTimer(this);
87 m_status_timer->setInterval(1000);
88 connect(m_status_timer, &QTimer::timeout, this, &Board::updateStatusMessage);
89
90 // Setup theme support
91 m_theme = new Theme;
92 m_theme->setDevicePixelRatio(devicePixelRatio());
93
94 loadSettings();
95
96 // Start or load game
97 if (QSettings().contains("Current/Seed")) {
98 loadGame();
99 } else {
100 m_done = true;
101 newGame();
102 }
103 }
104
105 // ============================================================================
106
~Board()107 Board::~Board()
108 {
109 delete m_maze;
110 delete m_solver;
111 delete m_theme;
112 }
113
114 // ============================================================================
115
newGame()116 void Board::newGame()
117 {
118 // Stop tracking time
119 m_status_timer->stop();
120
121 // Fetch new seed
122 #ifndef Q_OS_WIN
123 std::random_device rd;
124 unsigned int seed = rd();
125 #else
126 std::mt19937 gen(time(0));
127 std::uniform_int_distribution<unsigned int> dist;
128 unsigned int seed = dist(gen);
129 #endif
130
131 // Set values for new game
132 QSettings settings;
133 settings.remove("Current");
134 settings.setValue("Current/Algorithm", settings.value("New/Algorithm", 4).toInt());
135 settings.setValue("Current/Seed", seed);
136 settings.setValue("Current/Size", settings.value("New/Size", 20).toInt());
137 settings.setValue("Current/Targets", settings.value("New/Targets", 3).toInt());
138 settings.setValue("Current/Version", 3);
139 m_player_angle = 360;
140 m_player_steps = 0;
141
142 // Create new game
143 m_done = false;
144 generate(seed);
145 saveGame();
146
147 // Begin tracking time
148 m_player_total_time = 0;
149 m_player_time.start();
150 m_status_timer->start();
151
152 // Show
153 update();
154 updateStatusMessage();
155 m_status_remain_message->setVisible(true);
156
157 m_paused = false;
158 emit pauseAvailable(true);
159 emit hintAvailable(true);
160 emit pauseChecked(false);
161 }
162
163 // ============================================================================
164
loadGame()165 void Board::loadGame()
166 {
167 m_status_time_message->clear();
168 m_status_steps_message->clear();
169 m_status_remain_message->clear();
170
171 QSettings settings;
172
173 // Load maze
174 bool success = false;
175 if (settings.value("Current/Version").toInt() == 3) {
176 generate(settings.value("Current/Seed").toUInt());
177 success = m_maze->load();
178 }
179 if (!success) {
180 QMessageBox::warning(this, tr("Sorry"), tr("Unable to load previous game. A new game will be started."));
181 m_done = true;
182 return newGame();
183 }
184
185 // Place player at last location
186 m_player = settings.value("Current/Player").toPoint();
187 m_player_angle = settings.value("Current/Rotation", 360).toInt();
188 if (m_player_angle % 90 != 0 || m_player_angle < 0 || m_player_angle > 360) {
189 m_player_angle = 360;
190 }
191 m_player_steps = settings.value("Current/Steps", 0).toInt();
192
193 // Resume tracking time
194 m_player_total_time = settings.value("Current/Time", 0).toInt();
195 m_player_time.start();
196 m_status_timer->start();
197
198 // Remove any targets with matching movement
199 for (int i = 0; i < m_targets.size(); ++i) {
200 if (m_maze->cell(m_targets[i].x(), m_targets[i].y()).pathMarker() || m_player == m_targets[i]) {
201 QPoint target = m_targets.takeAt(i);
202 m_solver->removeTarget(target);
203 --i;
204 }
205 }
206
207 // Show
208 update();
209 updateStatusMessage();
210 m_status_remain_message->setVisible(true);
211
212 // Should not happen, but handle a finished game
213 if (m_targets.isEmpty()) {
214 finish();
215 }
216 }
217
218 // ============================================================================
219
saveGame()220 void Board::saveGame()
221 {
222 if (!m_done) {
223 m_maze->save();
224 QSettings settings;
225 settings.setValue("Current/Player", m_player);
226 settings.setValue("Current/Rotation", m_player_angle);
227 settings.setValue("Current/Steps", m_player_steps);
228 int msecs = m_player_total_time;
229 if (!m_paused) {
230 msecs += m_player_time.elapsed();
231 }
232 settings.setValue("Current/Time", msecs);
233 }
234 }
235
236 // ============================================================================
237
pauseGame(bool paused)238 void Board::pauseGame(bool paused)
239 {
240 m_paused = paused;
241 if (paused) {
242 m_status_timer->stop();
243 m_player_total_time += m_player_time.elapsed();
244 } else {
245 m_player_time.start();
246 m_status_timer->start();
247 updateStatusMessage();
248 }
249 update();
250 emit hintAvailable(!m_paused);
251 }
252
253 // ============================================================================
254
hint()255 void Board::hint()
256 {
257 if (m_done || m_paused || (m_smooth_movement && m_move_animation->state() == QTimeLine::Running)) {
258 return;
259 }
260
261 m_hint = m_solver->hint(m_player);
262 if (m_hint.x() < m_player.x()) {
263 m_hint_angle = 270;
264 } else if (m_hint.x() > m_player.x()) {
265 m_hint_angle = 90;
266 } else if (m_hint.y() < m_player.y()) {
267 m_hint_angle = 360;
268 } else {
269 m_hint_angle = 180;
270 }
271 int pos = (m_zoom / 2) + 1;
272 m_hint = m_hint - m_player + QPoint(pos, pos);
273 update();
274 }
275
276 // ============================================================================
277
zoomIn()278 void Board::zoomIn()
279 {
280 if (m_zoom > 5) {
281 m_zoom -= 2;
282 scale();
283 update();
284 }
285 }
286
287 // ============================================================================
288
zoomOut()289 void Board::zoomOut()
290 {
291 if (m_zoom < m_max_zoom) {
292 m_zoom += 2;
293 scale();
294 update();
295 }
296 }
297
298 // ============================================================================
299
loadSettings()300 void Board::loadSettings()
301 {
302 QSettings settings;
303
304 // Load zoom
305 m_zoom = QSettings().value("Zoom", 5).toInt();
306 if ((m_zoom % 2) == 0) {
307 m_zoom--;
308 }
309 m_zoom = std::max(m_zoom, 5);
310
311 // Load gameplay settings
312 m_status_steps_message->setVisible(settings.value("Show Steps", true).toBool());
313 m_status_time_message->setVisible(settings.value("Show Time", true).toBool());
314 m_show_path = settings.value("Show Path", true).toBool();
315 m_smooth_movement = settings.value("Smooth Movement", true).toBool();
316
317 // Load player controls
318 m_controls_up = settings.value("Controls/Up", Qt::Key_Up).toUInt();
319 m_controls_down = settings.value("Controls/Down", Qt::Key_Down).toUInt();
320 m_controls_left = settings.value("Controls/Left", Qt::Key_Left).toUInt();
321 m_controls_right = settings.value("Controls/Right", Qt::Key_Right).toUInt();
322 m_controls_flag = settings.value("Controls/Flag", Qt::Key_Space).toUInt();
323 m_controls_hint = settings.value("Controls/Hint", Qt::Key_H).toUInt();
324
325 // Load theme
326 m_theme->load(settings.value("Theme", "Mouse").toString());
327 renderBackground();
328
329 // Show
330 update();
331 updateStatusMessage();
332 }
333
334 // ============================================================================
335
keyPressEvent(QKeyEvent * event)336 void Board::keyPressEvent(QKeyEvent* event)
337 {
338 // Prevent player from changing a paused or finished maze
339 if (m_done || m_paused) {
340 return;
341 }
342
343 // Prevent movement during animation
344 if (m_smooth_movement && m_move_animation->state() == QTimeLine::Running) {
345 return;
346 }
347
348 m_col_delta = m_row_delta = 0;
349 QPoint position = m_player;
350 const Cell& cell = m_maze->cell(m_player.x(), m_player.y());
351
352 unsigned int keypress = event->key();
353 if (keypress == m_controls_left) {
354 m_player_angle = 270;
355 if (!cell.leftWall()) {
356 Q_ASSERT(m_player.x() > 0);
357 m_player.rx()--;
358 }
359 } else if (keypress == m_controls_right) {
360 m_player_angle = 90;
361 if (!cell.rightWall()) {
362 Q_ASSERT(m_player.x() < m_maze->columns() - 1);
363 m_player.rx()++;
364 }
365 } else if (keypress == m_controls_up) {
366 m_player_angle = 360;
367 if (!cell.topWall()) {
368 Q_ASSERT(m_player.y() > 0);
369 m_player.ry()--;
370 }
371 } else if (keypress == m_controls_down) {
372 m_player_angle = 180;
373 if (!cell.bottomWall()) {
374 Q_ASSERT(m_player.y() < m_maze->rows() - 1);
375 m_player.ry()++;
376 }
377 } else if (keypress == m_controls_flag) {
378 m_maze->cellMutable(m_player.x(), m_player.y()).toggleFlag();
379 } else if (keypress == m_controls_hint) {
380 hint();
381 } else {
382 return;
383 }
384
385 // Handle player movement
386 if (position != m_player) {
387 m_player_steps++;
388 m_col_delta = m_player.x() - position.x();
389 m_row_delta = m_player.y() - position.y();
390 if (m_smooth_movement) {
391 m_move_animation->start();
392 }
393 m_hint = QPoint(-1, -1);
394
395 // Add path marker
396 if (m_maze->cell(position.x(), position.y()).pathMarker() == 0) {
397 int angle = 0;
398 if (m_col_delta) {
399 angle = 180 - (m_col_delta * 90);
400 } else {
401 angle = 360 - ((m_row_delta + 1) * 90);
402 }
403 m_maze->cellMutable(position.x(), position.y()).setPathMarker(angle);
404 }
405 }
406
407 // Check for collisions with targets
408 for (int i = 0; i < m_targets.size(); ++i) {
409 if (m_player == m_targets.at(i)) {
410 QPoint target = m_targets.takeAt(i);
411 m_solver->removeTarget(target);
412 --i;
413 }
414 }
415
416 // Show updated maze
417 update();
418 updateStatusMessage();
419
420 // Handle finishing a maze
421 if (m_targets.isEmpty()) {
422 m_maze->cellMutable(m_player.x(), m_player.y()).setPathMarker(m_player_angle);
423 finish();
424 }
425 }
426
427 // ============================================================================
428
paintEvent(QPaintEvent *)429 void Board::paintEvent(QPaintEvent*)
430 {
431 if (!m_paused) {
432 if (!m_done) {
433 renderMaze();
434 } else {
435 renderDone();
436 }
437 } else {
438 renderPause();
439 }
440 }
441
442 // ============================================================================
443
resizeEvent(QResizeEvent *)444 void Board::resizeEvent(QResizeEvent*)
445 {
446 scale();
447 }
448
449 // ============================================================================
450
updateStatusMessage()451 void Board::updateStatusMessage()
452 {
453 if (m_done || m_paused) {
454 return;
455 }
456
457 QTime t = QTime(0, 0, 0).addMSecs(m_player_time.elapsed() + m_player_total_time);
458 m_status_time_message->setText(tr("%1 elapsed") .arg(t.toString("hh:mm:ss")));
459 m_status_steps_message->setText(tr("%1 steps taken") .arg(m_player_steps));
460 m_status_remain_message->setText(tr("%1 of %2 targets remain") .arg(m_targets.size()) .arg(m_total_targets));
461 }
462
463 // ============================================================================
464
scale()465 void Board::scale()
466 {
467 m_zoom_size = (m_zoom * 3) - 1;
468 m_unit = std::min(width(), height()) / m_zoom_size;
469 m_theme->scale(m_unit);
470 renderBackground();
471 emit zoomOutAvailable(m_zoom < m_max_zoom);
472 emit zoomInAvailable(m_zoom > 5);
473 QSettings().setValue("Zoom", m_zoom);
474 }
475
476 // ============================================================================
477
generate(unsigned int seed)478 void Board::generate(unsigned int seed)
479 {
480 QSettings settings;
481 int size = qBound(10, settings.value("Current/Size").toInt(), 100);
482 m_total_targets = qBound(1, settings.value("Current/Targets").toInt(), 100);
483 m_max_zoom = size / 2;
484 if ((m_max_zoom % 2) == 0) {
485 m_max_zoom--;
486 }
487 m_zoom = std::min(m_zoom, m_max_zoom);
488 scale();
489
490 // Create new maze
491 m_targets.clear();
492 delete m_maze;
493 switch (QSettings().value("Current/Algorithm").toInt()) {
494 case 0:
495 m_maze = new HuntAndKillMaze;
496 break;
497 case 1:
498 m_maze = new KruskalMaze;
499 break;
500 case 2:
501 m_maze = new PrimMaze;
502 break;
503 case 3:
504 m_maze = new RecursiveBacktrackerMaze;
505 break;
506 case 5:
507 m_maze = new Stack2Maze;
508 break;
509 case 6:
510 m_maze = new Stack3Maze;
511 break;
512 case 7:
513 m_maze = new Stack4Maze;
514 break;
515 case 8:
516 m_maze = new Stack5Maze;
517 break;
518 case 4:
519 default:
520 m_maze = new StackMaze;
521 break;
522 }
523 std::mt19937 gen(seed);
524 m_maze->generate(size, size, gen);
525
526 // Add player and targets
527 QList<QPoint> locations;
528 for (int y = 0; y < size; ++y) {
529 for (int x = 0; x < size; ++x) {
530 locations.append(QPoint(x,y));
531 }
532 }
533 std::shuffle(locations.begin(), locations.end(), gen);
534 m_player = m_start = locations.first();
535 m_targets = locations.mid(1, m_total_targets);
536
537 // Find solutions
538 delete m_solver;
539 m_solver = new Solver(m_maze, m_start, m_targets);
540 m_hint = QPoint(-1, -1);
541 }
542
543 // ============================================================================
544
finish()545 void Board::finish()
546 {
547 emit hintAvailable(false);
548 emit pauseAvailable(false);
549 m_move_animation->stop();
550 m_move_animation->setCurrentTime(m_move_animation->duration());
551
552 QSettings settings;
553 settings.beginGroup("Current");
554
555 // Get score values
556 int seconds = (m_player_total_time + m_player_time.elapsed()) / 1000;
557 int algorithm = settings.value("Algorithm").toInt();
558 int size = settings.value("Size").toInt();
559
560 // Remove game from disk
561 m_done = true;
562 settings.remove("");
563
564 // Show congratulations
565 m_status_timer->stop();
566 update();
567
568 // Add high score
569 emit finished(m_player_steps, seconds, algorithm, size);
570 }
571
572 // ============================================================================
573
renderBackground()574 void Board::renderBackground()
575 {
576 int size = (m_zoom_size + 6) * m_unit;
577 int ratio = devicePixelRatio();
578 m_back = QPixmap(QSize(size, size) * ratio);
579 m_back.setDevicePixelRatio(ratio);
580 QPainter painter(&m_back);
581 m_theme->drawBackground(painter);
582 }
583
584 // ============================================================================
585
renderMaze()586 void Board::renderMaze()
587 {
588 int frame = m_smooth_movement ? m_move_animation->currentFrame() : 3;
589 Q_ASSERT(frame > -1);
590 Q_ASSERT(frame < 5);
591
592 int pos = (m_zoom / 2) + 1;
593 int column = m_player.x() - m_col_delta - pos;
594 int row = m_player.y() - m_row_delta - pos;
595 int columns = m_maze->columns();
596 int rows = m_maze->rows();
597 Q_ASSERT(m_player.x() > -1);
598 Q_ASSERT(m_player.x() < columns);
599 Q_ASSERT(m_player.y() > -1);
600 Q_ASSERT(m_player.y() < rows);
601
602 // Create painter
603 QPainter painter(this);
604 int size = m_unit * m_zoom_size;
605 painter.setClipRect((width() - size) >> 1, (height() - size) >> 1, size, size);
606 painter.translate((width() - size) >> 1, (height() - size) >> 1);
607 painter.translate(-3 * m_unit, -3 * m_unit);
608
609 // Shift by frame amount
610 painter.save();
611 int delta = frame * -m_unit;
612 painter.translate(delta * m_col_delta, delta * m_row_delta);
613
614 // Draw background
615 painter.drawPixmap(0, 0, m_back);
616
617 // Initialize corners
618 int full_view = m_zoom + 3;
619 unsigned char corners[full_view][full_view];
620 for (int r = 0; r < full_view; ++r) {
621 for (int c = 0; c < full_view; ++c) {
622 corners[c][r] = 0;
623 }
624 }
625
626 // Setup columns
627 int column_start = 0;
628 int column_count = m_zoom + 2;
629 if (column < 1) {
630 column_start = abs(column);
631 } else if ((column + m_zoom + 1) >= columns) {
632 column_count = columns - column;
633 }
634
635 // Setup rows
636 int row_start = 0;
637 int row_count = m_zoom + 2;
638 if (row < 1) {
639 row_start = abs(row);
640 } else if ((row + m_zoom + 1) >= rows) {
641 row_count = rows - row;
642 }
643
644 // Draw cells
645 int angle = 0;
646 for (int r = row_start; r < row_count; ++r) {
647 for (int c = column_start; c < column_count; ++c) {
648
649 const Cell& cell = m_maze->cell(column + c, row + r);
650
651 // Draw walls
652 if (cell.topWall()) {
653 m_theme->drawWall(painter, c, r);
654 }
655 if (cell.leftWall()) {
656 m_theme->drawWall(painter, c, r, true);
657 }
658 if (column + c + 1 == columns) {
659 m_theme->drawWall(painter, c + 1, r, true);
660 }
661 if (row + r + 1 == rows) {
662 m_theme->drawWall(painter, c, r + 1);
663 }
664
665 // Draw marker
666 if (m_show_path) {
667 angle = cell.pathMarker();
668 if (angle) {
669 m_theme->draw(painter, c, r, Theme::Marker, angle);
670 }
671 }
672
673 // Draw flag
674 if (cell.flag()) {
675 m_theme->draw(painter, c, r, Theme::Flag);
676 }
677
678 // Configure corners
679 unsigned char& corner1 = corners[c][r];
680 corner1 |= (cell.topWall() << 1);
681 corner1 |= (cell.leftWall() << 2);
682 unsigned char& corner2 = corners[c + 1][r];
683 corner2 |= (cell.topWall() << 3);
684 corner2 |= (cell.rightWall() << 2);
685 unsigned char& corner3 = corners[c + 1][r + 1];
686 corner3 |= (cell.rightWall() << 0);
687 corner3 |= (cell.bottomWall() << 3);
688 unsigned char& corner4 = corners[c][r + 1];
689 corner4 |= (cell.leftWall() << 0);
690 corner4 |= (cell.bottomWall() << 1);
691 }
692 }
693
694 // Draw corners
695 for (int r = 0; r < full_view; ++r) {
696 for (int c = 0; c < full_view; ++c) {
697 unsigned char walls = corners[c][r];
698 if (walls) {
699 m_theme->drawCorner(painter, c, r, walls);
700 }
701 }
702 }
703
704 // Draw start
705 QRect view(column, row, m_zoom + 2, m_zoom + 2);
706 if (view.contains(m_start)) {
707 m_theme->draw(painter, m_start.x() - column, m_start.y() - row, Theme::Start);
708 }
709
710 // Draw targets
711 for (const QPoint& target : m_targets) {
712 if (view.contains(target)) {
713 m_theme->draw(painter, target.x() - column, target.y() - row, Theme::Target);
714 }
715 }
716
717 painter.restore();
718
719 // Draw hint
720 if (m_hint.x() != -1) {
721 painter.save();
722 switch (m_hint_angle) {
723 case 90:
724 painter.translate(-m_unit, 0);
725 break;
726 case 180:
727 painter.translate(0, -m_unit);
728 break;
729 case 270:
730 painter.translate(m_unit, 0);
731 break;
732 case 360:
733 painter.translate(0, m_unit);
734 break;
735 default:
736 break;
737 };
738 m_theme->draw(painter, m_hint.x(), m_hint.y(), Theme::Hint, m_hint_angle);
739 painter.restore();
740 }
741
742 // Draw player
743 m_theme->draw(painter, pos, pos, Theme::Player, m_player_angle);
744 }
745
746 // ============================================================================
747
renderDone()748 void Board::renderDone()
749 {
750 int columns = m_maze->columns();
751 int rows = m_maze->rows();
752
753 // Determine sizes
754 int mcr = std::min(columns, rows);
755 int cell_width = std::min(width(), height());
756 cell_width -= (mcr + 1);
757 cell_width /= mcr;
758 cell_width += 1;
759 int w = columns * cell_width + 1;
760 int h = rows * cell_width + 1;
761
762 // Create painter
763 QPainter painter(this);
764 painter.save();
765 painter.translate((width() - w) >> 1, (height() - h) >> 1);
766 painter.fillRect(0, 0, w, h, Qt::white);
767
768 // Draw image
769 int x1, x2, y1, y2;
770 for (int r = 0; r < rows; ++r) {
771 for (int c = 0; c < columns; ++c) {
772 const Cell& cell = m_maze->cell(c, r);
773 x1 = c * cell_width;
774 x2 = x1 + cell_width;
775 y1 = r * cell_width;
776 y2 = y1 + cell_width;
777 if (cell.pathMarker()) {
778 painter.fillRect(x1, y1, cell_width, cell_width, Qt::lightGray);
779 }
780 if (cell.topWall()) {
781 painter.drawLine(x1, y1, x2, y1);
782 }
783 if (cell.leftWall()) {
784 painter.drawLine(x1, y1, x1, y2);
785 }
786 }
787 }
788 painter.drawLine(0, rows * cell_width, columns * cell_width, rows * cell_width);
789 painter.drawLine(columns * cell_width, 0, columns * cell_width, rows * cell_width);
790
791 // Draw congratulations
792 painter.restore();
793 renderText(&painter, tr("Success"));
794 }
795
796 // ============================================================================
797
renderPause()798 void Board::renderPause()
799 {
800 int size = m_unit * m_zoom_size;
801
802 // Create painter
803 QPainter painter(this);
804 painter.save();
805 painter.translate((width() - size) >> 1, (height() - size) >> 1);
806 painter.fillRect(0, 0, size, size, Qt::white);
807
808 // Draw message
809 painter.restore();
810 renderText(&painter, tr("Paused"));
811 }
812
813 // ============================================================================
814
renderText(QPainter * painter,const QString & message) const815 void Board::renderText(QPainter* painter, const QString& message) const
816 {
817 // Find message size
818 QFont f = font();
819 f.setPointSize(24);
820 QFontMetrics metrics(f);
821 int width = metrics.boundingRect(message).width();
822 int height = metrics.height();
823
824 painter->save();
825 painter->translate(rect().center() - QRect(0, 0, width + height, height * 2).center());
826
827 // Draw black background
828 painter->setPen(Qt::NoPen);
829 painter->setBrush(QColor(0, 0, 0, 200));
830 painter->setRenderHint(QPainter::Antialiasing, true);
831 painter->drawRoundedRect(0, 0, width + height, height * 2, 10, 10);
832
833 // Draw message
834 painter->setFont(f);
835 painter->setPen(Qt::white);
836 painter->setRenderHint(QPainter::TextAntialiasing, true);
837 painter->drawText(height / 2, height / 2 + metrics.ascent(), message);
838
839 painter->restore();
840 }
841
842 // ============================================================================
843