1//
2// KBlackBox
3//
4// A simple game inspired by an emacs module
5//
6/***************************************************************************
7 * Copyright (c) 1999-2000, Robert Cimrman *
8 * cimrman3@students.zcu.cz *
9 * *
10 * Copyright (c) 2007, Nicolas Roffet *
11 * nicolas-kde@roffet.com *
12 * *
13 * *
14 * This program is free software; you can redistribute it and/or modify *
15 * it under the terms of the GNU General Public License as published by *
16 * the Free Software Foundation; either version 2 of the License, or *
17 * (at your option) any later version. *
18 * *
19 * This program is distributed in the hope that it will be useful, *
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
22 * GNU General Public License for more details. *
23 * *
24 * You should have received a copy of the GNU General Public License *
25 * along with this program; if not, write to the *
26 * Free Software Foundation, Inc., *
27 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA *
28 ***************************************************************************/
29
30#include "kbbscalablegraphicwidget.h"
31
32
33
34#include <QAction>
35#include <QFont>
36#include <QGraphicsScene>
37#include <QGraphicsView>
38#include <QLCDNumber>
39#include <QResizeEvent>
40
41
42#include <kgamepopupitem.h>
43#include <kicon.h>
44#include <klocale.h>
45#include <kpushbutton.h>
46
47
48#include "kbbballsonboard.h"
49#include "kbbgamedoc.h"
50#include "kbbgraphicsitemball.h"
51#include "kbbgraphicsitemballrepository.h"
52#include "kbbgraphicsitemblackbox.h"
53#include "kbbgraphicsitemcursor.h"
54#include "kbbgraphicsitemlaser.h"
55#include "kbbgraphicsitemonbox.h"
56#include "kbbgraphicsitemray.h"
57#include "kbbgraphicsitemrayresult.h"
58#include "kbbgraphicsitemset.h"
59#include "kbbthememanager.h"
60
61
62
63//
64// Constructor / Destructor
65//
66
67KBBScalableGraphicWidget::KBBScalableGraphicWidget(KBBGameDoc* gameDoc, KBBThemeManager* themeManager, QAction* done)
68{
69 m_gameDoc = gameDoc;
70 m_themeManager = themeManager;
71 m_columns = -2;
72 m_rows = -2;
73 m_pause = false;
74 m_ballNumber = 0;
75 m_doneAction = done;
76
77
78 setFrameStyle(QFrame::NoFrame);
79 setCacheMode(QGraphicsView::CacheBackground);
80 setMinimumSize(QSize(MINIMUM_SIZE, MINIMUM_SIZE));
81
82
83 m_scene = new QGraphicsScene( 0, 0, 2*BORDER_SIZE, 2*BORDER_SIZE, this );
84
85 m_blackbox = new KBBGraphicsItemBlackBox(this, m_scene, m_themeManager);
86 m_blackbox->setKBBScalableGraphicWidget(this);
87 m_balls = new KBBGraphicsItemSet(m_scene);
88 m_cursor = new KBBGraphicsItemCursor(this, m_themeManager);
89 connect(m_cursor, SIGNAL(cursorAtNewPosition(int)), this, SLOT(cursorAtNewPosition(int)));
90 m_markersNothing = new KBBGraphicsItemSet(m_scene);
91 m_ballsSolution = new KBBGraphicsItemSet(m_scene);
92 m_ballsUnsure = new KBBGraphicsItemSet(m_scene);
93 m_lasers = new KBBGraphicsItemSet(m_scene);
94 m_rayResults = new KBBGraphicsItemSet(m_scene);
95
96 m_playerRay = new KBBGraphicsItemRay(playerRay, m_scene, m_themeManager);
97 m_solutionRay = new KBBGraphicsItemRay(solutionRay, m_scene, m_themeManager);
98
99 // Information message about the score
100 m_infoScore = new KGamePopupItem();
101 m_infoScore->setMessageIcon(QPixmap()); // No icon, because they are no scalable.
102 m_scene->addItem(m_infoScore); // it hides itself by default
103
104 m_ballRepository = new KBBGraphicsItemBallRepository(this, themeManager);
105
106 setScene(m_scene);
107
108 m_doneButton = new KPushButton(m_doneAction->text(), this);
109 m_doneButton->setIcon(KIcon(m_doneAction->icon()));
110 m_doneButton->setWhatsThis(m_doneAction->whatsThis());
111 connect(m_doneButton, SIGNAL(clicked(bool)), m_doneAction, SLOT(trigger()));
112 QFont font;
113 font.setPointSize(m_doneButton->font().pointSize()+2);
114 font.setBold(true);
115 m_doneButton->setFont(font);
116
117 m_score = new QLCDNumber(3, this);
118 m_score->setFrameStyle(QFrame::NoFrame);
119 m_score->setMaximumWidth(m_doneButton->width());
120 m_score->setFixedHeight((int)(1.5*m_doneButton->height()));
121 m_score->setToolTip(i18n("Score"));
122 m_score->setWhatsThis(i18n("<qt><p>This is <b>your score</b>. You should try to get the lowest possible.</p><p>The score increases:<ul><li>with time: <b>1 point</b> per second.</li><li>with the use of lasers:<ul><li><b>3 points</b> if the laser beam hits a ball or exits at the entry point,</li><li><b>9 points</b> if it exits at another entry point.</li></ul></li></ul></p><p>Your score is set to <b>999</b> at the end of the game if you make a mistake.</p></qt>"));
123
124 // TODO: not displayed... :(
125 setWhatsThis(i18n("<qt><p>This is the <b>main game area</b>.</p><ul><li>The <b>black box</b> is in the center.</li><li>On the left, there are the <b>balls</b> you have to place over the black box.</li><li>Around the black box, there are <b>lasers</b> that are replaced with <b>interaction information</b> if you use them.</li></ul></qt>"));
126
127}
128
129
130KBBScalableGraphicWidget::~KBBScalableGraphicWidget()
131{
132 delete m_balls;
133 delete m_markersNothing;
134 delete m_ballsSolution;
135 delete m_ballsUnsure;
136 delete m_lasers;
137 delete m_rayResults;
138
139 delete m_ballRepository;
140
141 delete m_scene;
142}
143
144
145
146//
147// Public
148//
149
150void KBBScalableGraphicWidget::addBall(int boxPosition)
151{
152 addBall(boxPosition, KBBGraphicsItemOnBox::NO_POSITION);
153}
154
155
156void KBBScalableGraphicWidget::addBall(int boxPosition, int outsidePosition)
157{
158 if (!m_pause && m_inputAccepted && (!m_balls->containsVisible(boxPosition))&& (!m_ballsUnsure->containsVisible(boxPosition))) {
159 m_boardBallsPlaced->add(boxPosition);
160 m_balls->insert(new KBBGraphicsItemBall(playerBall, this, m_themeManager, boxPosition, m_columns, m_rows));
161 m_markersNothing->remove(boxPosition);
162 if (outsidePosition==KBBGraphicsItemOnBox::NO_POSITION) {
163 outsidePosition = m_ballRepository->ballToTake();
164 }
165 if (outsidePosition!=KBBGraphicsItemSet::NO_INDEX)
166 m_ballRepository->removeBall(outsidePosition);
167
168 updateDoneButton();
169 }
170}
171
172
173void KBBScalableGraphicWidget::addBallUnsure(const int boxPosition)
174{
175 addBall(boxPosition);
176 setBallUnsure(boxPosition, true);
177}
178
179
180void KBBScalableGraphicWidget::addMarkerNothing(const int boxPosition)
181{
182 if (!m_pause && m_inputAccepted && (!m_markersNothing->containsVisible(boxPosition))) {
183 m_markersNothing->insert(new KBBGraphicsItemOnBox(markerNothing, this, m_themeManager, boxPosition, m_columns, m_rows));
184 m_balls->remove(boxPosition);
185 m_ballsUnsure->remove(boxPosition);
186 m_boardBallsPlaced->remove(boxPosition);
187 }
188}
189
190
191void KBBScalableGraphicWidget::drawRay(const int borderPosition)
192{
193 if (!m_pause) {
194 if (!m_inputAccepted) {
195 m_solutionRay->draw(m_boardBalls, borderPosition);
196 }
197 m_playerRay->draw(m_boardBallsPlaced, borderPosition);
198 }
199}
200
201
202void KBBScalableGraphicWidget::mouseBorderClick(const int borderPosition)
203{
204 useLaser(borderPosition);
205 m_cursor->setBorderPosition(borderPosition);
206 m_cursor->hide();
207}
208
209
210void KBBScalableGraphicWidget::mouseBoxClick(const Qt::MouseButton button, int boxPosition)
211{
212 m_cursor->setBoxPosition(boxPosition);
213 if (button==Qt::RightButton)
214 switchMarker();
215 else
216 switchBall();
217 m_cursor->hide();
218}
219
220
221int KBBScalableGraphicWidget::moveBall(const int boxPositionFrom, const int boxPositionTo)
222{
223 int newPos = positionAfterMovingBall(boxPositionFrom, boxPositionTo);
224
225 if (!m_pause && m_inputAccepted && (!m_balls->containsVisible(boxPositionTo)) && (!m_ballsUnsure->containsVisible(boxPositionTo))) {
226 m_markersNothing->remove(boxPositionTo);
227 if (boxPositionFrom>=m_columns*m_rows) {
228 // ball moved from outside of the board
229 addBall(boxPositionTo, boxPositionFrom);
230 } else {
231 // ball moved from a board position
232 m_boardBallsPlaced->remove(boxPositionFrom);
233 m_boardBallsPlaced->add(boxPositionTo);
234 }
235 }
236
237 return newPos;
238}
239
240
241int KBBScalableGraphicWidget::moveMarkerNothing(const int boxPositionFrom, const int boxPositionTo)
242{
243 if (!m_pause && m_inputAccepted && (!m_markersNothing->containsVisible(boxPositionTo))) {
244 removeBall(boxPositionTo);
245 return boxPositionTo;
246 } else
247 return boxPositionFrom;
248}
249
250
251void KBBScalableGraphicWidget::newGame(int columns, int rows, int ballNumber)
252{
253 m_rayNumber = 0;
254 m_boardBallsPlaced = m_gameDoc->m_ballsPlaced;
255 setPause(false);
256 m_ballNumber = ballNumber;
257
258 // remove old ray results, all placed balls, all markers "nothing" and all solutions
259 m_rayResults->clear();
260 m_balls->clear();
261 m_ballsUnsure->clear();
262 m_markersNothing->clear();
263 m_ballsSolution->clear();
264
265 // Reorganize lasers
266 if ((columns!=m_columns) || (rows!=m_rows)) {
267 // not the same amount of lasers: We can destroy them and create some new ones
268 m_lasers->clear();
269 for (int i=0; i<2*(columns + rows); i++)
270 m_lasers->insert(new KBBGraphicsItemLaser(this, m_themeManager, i, columns, rows));
271 } else {
272 // same amount of lasers: We "recycle" them. (Just destroying them and re-creating them is not working fine: some lasers remain hidden until the next resize... Strange bug with QGraphicsView...)
273 for (int i=0; i<2*(m_columns + m_rows); i++)
274 m_lasers->setVisible(i, true);
275 }
276
277 m_ballRepository->newGame(columns, rows, ballNumber);
278
279 // set the new size if needed
280 if (m_columns!=columns || m_rows!=rows) {
281 m_columns = columns;
282 m_rows = rows;
283 m_blackbox->setSize(m_columns, m_rows);
284 m_cursor->setBoardSize(m_columns, m_rows);
285 m_scene->setSceneRect(m_ballRepository->x() - RATIO, 0, m_columns*RATIO + 2*BORDER_SIZE - m_ballRepository->x() + RATIO, m_rows*RATIO + 2*BORDER_SIZE);
286 }
287 resizeEvent(0);
288 setInputAccepted(true);
289}
290
291
292void KBBScalableGraphicWidget::popupText(const QString& text, int time)
293{
294 if (text.isEmpty())
295 m_infoScore->forceHide();
296 else {
297 m_infoScore->setMessageTimeout(time);
298 m_infoScore->showMessage(text, KGamePopupItem::TopLeft, KGamePopupItem::ReplacePrevious);
299 }
300}
301
302
303int KBBScalableGraphicWidget::positionAfterMovingBall(const int boxPositionFrom, const int boxPositionTo) const
304{
305 if (!m_pause && m_inputAccepted && (!m_balls->containsVisible(boxPositionTo)) && (!m_ballsUnsure->containsVisible(boxPositionTo))) {
306 return boxPositionTo;
307 } else
308 return boxPositionFrom;
309}
310
311
312void KBBScalableGraphicWidget::setPause(bool state)
313{
314 m_pause = state;
315 for (int i=0;i<2*(m_rows+m_columns);i++) {
316 if (m_rayResults->containsVisible(i))
317 m_rayResults->item(i)->setPause(state);
318 }
319
320 updateDoneButton();
321}
322
323
324void KBBScalableGraphicWidget::resizeEvent( QResizeEvent* )
325{
326 // 1. Compute the size of m_rectBackground
327 const qreal sW = m_scene->width();
328 const qreal sH = m_scene->height();
329 const qreal wW = width();
330 const qreal wH = height();
331 const qreal offset = (sH+sW)/100 ;
332 if (sH*wW > sW*wH) {
333 // The widget is larger than the scene
334 qreal w = wW*sH/wH;
335 m_rectBackground.setRect((sW-w)/2-offset+m_ballRepository->x()-RATIO, -offset, w + 2*offset, sH + 2*offset);
336 } else {
337 // The scene is larger than the widget (or as large)
338 qreal h = wH*sW/wW;
339 m_rectBackground.setRect(-offset+m_ballRepository->x()-RATIO, (sH-h)/2-offset, sW + 2*offset, h + 2*offset);
340 }
341
342 // 2. Resize the scene to fit in the widget
343 fitInView(m_ballRepository->x()-RATIO, 0, m_columns*RATIO + 2*BORDER_SIZE - m_ballRepository->x() + RATIO, m_rows*RATIO + 2*BORDER_SIZE, Qt::KeepAspectRatio);
344
345
346 m_doneButton->move(OFFSET_DONE_BUTTON, height() - m_doneButton->height() - OFFSET_DONE_BUTTON);
347 m_score->move(OFFSET_DONE_BUTTON, height() - m_score->height() - m_doneButton->height() - 3*OFFSET_DONE_BUTTON);
348}
349
350
351void KBBScalableGraphicWidget::removeAllBalls()
352{
353 for (int i=0;i<m_columns*m_rows;i++) {
354 removeBall(i);
355 }
356}
357
358
359void KBBScalableGraphicWidget::removeBall(const int boxPosition)
360{
361 if (!m_pause && m_inputAccepted) {
362 m_balls->remove(boxPosition);
363 m_ballsUnsure->remove(boxPosition);
364 m_boardBallsPlaced->remove(boxPosition);
365 m_ballRepository->fillBallsOutside(m_gameDoc->m_ballsPlaced->count());
366
367 updateDoneButton();
368 }
369}
370
371
372void KBBScalableGraphicWidget::removeRay()
373{
374 m_playerRay->hide();
375 m_solutionRay->hide();
376}
377
378
379QGraphicsScene* KBBScalableGraphicWidget::scene()
380{
381 return m_scene;
382}
383
384
385void KBBScalableGraphicWidget::setScore(int score)
386{
387 m_score->display(score);
388}
389
390
391void KBBScalableGraphicWidget::solve(const bool continueGame)
392{
393 m_boardBalls = m_gameDoc->m_balls;
394
395 setInputAccepted(continueGame);
396 if (!continueGame)
397 setPause(false);
398
399 for (int i=0; i<(m_columns * m_rows); i++) {
400 if ((m_balls->containsVisible(i) || m_ballsUnsure->containsVisible(i)) && m_boardBalls->contains(i)) {
401 m_ballsSolution->remove(i); // For the sandbox mode: a solution ball could already be here.
402 m_ballsSolution->insert(new KBBGraphicsItemBall(rightPlayerBall, this, m_themeManager, i, m_columns, m_rows));
403 }
404 if ((m_balls->containsVisible(i) || m_ballsUnsure->containsVisible(i)) && !m_boardBalls->contains(i))
405 m_ballsSolution->insert(new KBBGraphicsItemOnBox(wrongPlayerBall, this, m_themeManager, i, m_columns, m_rows));
406 if (!m_balls->containsVisible(i) && !m_ballsUnsure->containsVisible(i) && m_boardBalls->contains(i))
407 m_ballsSolution->insert(new KBBGraphicsItemBall(solutionBall, this, m_themeManager, i, m_columns, m_rows));
408 }
409}
410
411
412
413//
414// Public slots
415//
416
417void KBBScalableGraphicWidget::cursorAtNewPosition(int borderPosition)
418{
419 removeRay();
420 if ((borderPosition!=KBBGraphicsItemCursor::NO_POSITION) && m_cursor->isVisible())
421 drawRay(borderPosition);
422
423 // highlight
424 for (int i=0;i<2*(m_rows+m_columns);i++) {
425 if (m_rayResults->containsVisible(i))
426 m_rayResults->item(i)->highlight(false);
427 }
428 if (m_rayResults->containsVisible(borderPosition))
429 m_rayResults->item(borderPosition)->highlightBoth(true);
430
431}
432
433
434void KBBScalableGraphicWidget::keyboardEnter()
435{
436 if (m_cursor->isVisible()) {
437 if (m_cursor->borderPosition() != KBBGraphicsItemCursor::NO_POSITION)
438 useLaser(m_cursor->borderPosition());
439 else
440 switchBall();
441 }
442 m_cursor->show();
443}
444
445
446void KBBScalableGraphicWidget::keyboardMoveDown()
447{
448 if (m_cursor->isVisible())
449 m_cursor->moveDown();
450 m_cursor->show();
451}
452
453
454void KBBScalableGraphicWidget::keyboardMoveLeft()
455{
456 if (m_cursor->isVisible())
457 m_cursor->moveLeft();
458 m_cursor->show();
459}
460
461
462void KBBScalableGraphicWidget::keyboardMoveRight()
463{
464 if (m_cursor->isVisible())
465 m_cursor->moveRight();
466 m_cursor->show();
467}
468
469
470void KBBScalableGraphicWidget::keyboardMoveUp()
471{
472 if (m_cursor->isVisible())
473 m_cursor->moveUp();
474 m_cursor->show();
475}
476
477
478void KBBScalableGraphicWidget::keyboardSpace()
479{
480 if (m_cursor->isVisible()) {
481 if (m_cursor->boxPosition() != KBBGraphicsItemCursor::NO_POSITION)
482 switchMarker();
483 }
484 m_cursor->show();
485}
486
487
488
489//
490// Protected
491//
492
493void KBBScalableGraphicWidget::drawBackground(QPainter* painter, const QRectF&)
494{
495 m_themeManager->svgRenderer()->render(painter, m_themeManager->elementId(background), m_rectBackground);
496}
497
498
499
500//
501// Private
502//
503
504void KBBScalableGraphicWidget::removeMarkerNothing(const int boxPosition)
505{
506 if (!m_pause && m_inputAccepted) {
507 m_markersNothing->remove(boxPosition);
508 }
509}
510
511
512void KBBScalableGraphicWidget::setBallUnsure(const int boxPosition, const bool unsure)
513{
514 if (!m_pause && m_inputAccepted) {
515 if (unsure) {
516 m_balls->remove(boxPosition);
517 m_ballsUnsure->insert(new KBBGraphicsItemBall(unsureBall, this, m_themeManager, boxPosition, m_columns, m_rows));
518 } else {
519 m_ballsUnsure->remove(boxPosition);
520 m_balls->insert(new KBBGraphicsItemBall(playerBall, this, m_themeManager, boxPosition, m_columns, m_rows));
521 }
522 }
523}
524
525
526void KBBScalableGraphicWidget::setInputAccepted(bool inputAccepted)
527{
528 m_inputAccepted = inputAccepted;
529 if (m_inputAccepted) {
530 setFocusPolicy( Qt::StrongFocus );
531 setFocus();
532 } else {
533 setFocusPolicy( Qt::NoFocus );
534 clearFocus();
535 }
536
537 updateDoneButton();
538}
539
540
541void KBBScalableGraphicWidget::switchBall()
542{
543 if ((m_balls->containsVisible(m_cursor->boxPosition())) || (m_ballsUnsure->containsVisible(m_cursor->boxPosition())))
544 removeBall(m_cursor->boxPosition());
545 else
546 addBall(m_cursor->boxPosition());
547}
548
549
550void KBBScalableGraphicWidget::switchMarker()
551{
552 if (m_balls->containsVisible(m_cursor->boxPosition()))
553 setBallUnsure(m_cursor->boxPosition(), true);
554 else if (m_markersNothing->containsVisible(m_cursor->boxPosition()))
555 removeMarkerNothing(m_cursor->boxPosition());
556 else{
557 removeBall(m_cursor->boxPosition());
558 addMarkerNothing(m_cursor->boxPosition());
559 }
560}
561
562
563void KBBScalableGraphicWidget::updateDoneButton()
564{
565 m_doneButton->setEnabled(m_doneAction->isEnabled());
566 m_doneButton->setToolTip(m_doneAction->toolTip());
567}
568
569
570void KBBScalableGraphicWidget::useLaser(const int incomingPosition)
571{
572 if (!m_pause && m_gameDoc->mayShootRay(incomingPosition) && m_inputAccepted && m_lasers->containsVisible(incomingPosition)) {
573 const int outgoingPosition = m_gameDoc->shootRay(incomingPosition);
574
575 KBBGraphicsItemRayResult* inRay;
576 KBBGraphicsItemRayResult* outRay;
577
578 int rayNumberOrReflection = 0;
579 if (outgoingPosition==KBBGameDoc::HIT_POSITION)
580 rayNumberOrReflection = KBBGameDoc::HIT_POSITION;
581 if ((outgoingPosition!=incomingPosition) && (outgoingPosition!=KBBGameDoc::HIT_POSITION)) {
582 m_rayNumber++;
583 m_lasers->setVisible(outgoingPosition, false);
584 m_rayResults->insert(outRay = new KBBGraphicsItemRayResult(this, m_themeManager, m_scene, outgoingPosition, m_columns, m_rows, m_rayNumber));
585 rayNumberOrReflection = m_rayNumber;
586 }
587 m_rayResults->insert(inRay = new KBBGraphicsItemRayResult(this, m_themeManager, m_scene, incomingPosition, m_columns, m_rows, rayNumberOrReflection));
588
589 if ((outgoingPosition!=incomingPosition) && (outgoingPosition!=KBBGameDoc::HIT_POSITION)) {
590 inRay->setOpposite(outRay);
591 outRay->setOpposite(inRay);
592 }
593
594 m_scene->update();
595 m_lasers->setVisible(incomingPosition, false);
596
597 popupText(""); // To Remove any displayed text quicker.
598
599 cursorAtNewPosition(incomingPosition);
600 }
601}
602
603
604#include "kbbscalablegraphicwidget.moc"
605