1//
2// KBlackBox
3//
4// A simple game inspired by an emacs module
5//
6/***************************************************************************
7 * Copyright (c) 2007, Nicolas Roffet *
8 * nicolas-kde@roffet.com *
9 * *
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 * GNU General Public License for more details. *
20 * *
21 * You should have received a copy of the GNU General Public License *
22 * along with this program; if not, write to the *
23 * Free Software Foundation, Inc., *
24 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA *
25 ***************************************************************************/
26
27#include "kbbtutorial.h"
28
29
30
31#include <QFrame>
32#include <QHBoxLayout>
33#include <QLabel>
34#include <QProgressBar>
35#include <QTimer>
36#include <QVBoxLayout>
37
38
39#include <kicon.h>
40#include <klocale.h>
41#include <kpushbutton.h>
42#include <ktextedit.h>
43
44
45#include "kbbgraphicsitemtutorialmarker.h"
46#include "kbbscalablegraphicwidget.h"
47
48
49
50//
51// Constructor / Destructor
52//
53
54KBBTutorial::KBBTutorial(QWidget* parent) : QGroupBox(i18n("Tutorial"), parent)
55{
56 m_marker = 0;
57 m_gameWidget = 0;
58
59 setMinimumSize(QSize(WIDTH, WIDTH));
60 setFixedWidth(WIDTH);
61
62 QVBoxLayout *tutorialLayout = new QVBoxLayout();
63 setLayout(tutorialLayout);
64 setFlat(true);
65
66 m_progression = new QProgressBar(this);
67 m_progression->setTextVisible(true);
68 m_progression->setFormat("%v / %m");
69 m_progression->setMinimum(FIRST_STEP-1);
70 m_progression->setMaximum(LAST_STEP);
71 m_progression->setWhatsThis(i18n("Displays the progress of the tutorial."));
72 tutorialLayout->addWidget(m_progression);
73
74 m_title = new QLabel(this);
75 tutorialLayout->addWidget(m_title, 0, Qt::AlignHCenter);
76
77 m_explanation = new KTextEdit(this);
78 m_explanation->setReadOnly(true);
79 m_explanation->setFrameStyle(QFrame::NoFrame);
80 m_explanation->setAlignment(Qt::AlignJustify);
81 tutorialLayout->addWidget(m_explanation);
82
83 tutorialLayout->addStretch();
84
85
86 QHBoxLayout *actionLayout = new QHBoxLayout();
87 tutorialLayout->addLayout(actionLayout);
88 QLabel* iconLabel = new QLabel(this);
89 iconLabel->setFixedSize(24, 24);
90 iconLabel->setPixmap(KIcon( QLatin1String( "go-next" )).pixmap(24, 24));
91 actionLayout->addWidget(iconLabel, 0, Qt::AlignVCenter);
92 m_playerAction = new QLabel(this);
93 m_playerAction->setWhatsThis(i18n("Describes what you should do to reach the next tutorial step."));
94 m_playerAction->setAlignment(Qt::AlignLeft);
95 m_playerAction->setWordWrap(true);
96 m_playerAction->setFrameStyle(QFrame::StyledPanel);
97 m_playerAction->setStyleSheet("border-style: none");
98 actionLayout->addWidget(m_playerAction, 0, Qt::AlignVCenter);
99
100 QHBoxLayout *buttonLayout = new QHBoxLayout();
101 tutorialLayout->addLayout(buttonLayout);
102 m_buttonPrevious = new KPushButton(KIcon( QLatin1String( "go-previous") ), i18nc("Previous tutorial step", "&Previous"), this);
103 m_buttonPrevious->setWhatsThis(i18n("Go back to the previous tutorial step."));
104 connect(m_buttonPrevious, SIGNAL(clicked()), this, SLOT(previousStep()));
105 buttonLayout->addWidget(m_buttonPrevious);
106 m_buttonNext = new KPushButton(KIcon( QLatin1String( "go-next")) , i18nc("Next tutorial step", "&Next"), this);
107 m_buttonNext->setWhatsThis(i18n("Go to the next tutorial step."));
108 connect(m_buttonNext, SIGNAL(clicked()), this, SLOT(nextStep()));
109 m_buttonNext->setDefault(true);
110 buttonLayout->addWidget(m_buttonNext);
111}
112
113
114
115//
116// Public
117//
118
119void KBBTutorial::hideEvent(QHideEvent*)
120{
121 showMarker(MAY_NOT_USE);
122}
123
124
125bool KBBTutorial::maySolve()
126{
127 return m_step==LAST_STEP;
128}
129
130
131bool KBBTutorial::mayShootRay(const int incomingPosition)
132{
133 if (m_step==LAST_STEP)
134 return true;
135 else
136 if (incomingPosition==m_laserToUse) {
137 nextStep();
138 return true;
139 } else {
140 // Highlight m_playerAction to show what the player has to do
141 m_playerAction->setStyleSheet("color: black; background-color: #de0000");
142 QTimer::singleShot(HIGHLIGHT_TIME, this, SLOT(restoreStyle()));
143 return false;
144 }
145}
146
147
148void KBBTutorial::setGameWidget(KBBScalableGraphicWidget* gameWidget, KBBGraphicsItemTutorialMarker* marker)
149{
150 m_gameWidget = gameWidget;
151 m_marker = marker;
152}
153
154
155void KBBTutorial::setStep(const int step)
156{
157 Q_ASSERT((step>=FIRST_STEP) && (step<=LAST_STEP));
158 Q_ASSERT(m_gameWidget!=0);
159
160 m_step = step;
161 setNewStepMaxAllowed(m_step);
162
163 if (m_step!=LAST_STEP)
164 m_gameWidget->removeAllBalls();
165
166 switch (m_step) {
167 case FIRST_STEP:
168 m_gameWidget->newGame(KBBTutorial::COLUMNS, KBBTutorial::ROWS, KBBTutorial::BALLS);
169 setTexts(i18n("Welcome!"), i18n("This tutorial will teach you how to play KBlackBox, using a simple example.<br /><br />We are playing with a square black box of 6 columns and 6 rows. It has 3 balls <b>hidden</b> inside it and 24 laser probes around it.<br /><br />The goal is to <b>find the positions of the balls.</b>"), i18n("Click on \"Next\""));
170 m_laserToUse = MAY_NOT_USE;
171 setNewStepMaxAllowed(FIRST_STEP+1);
172 break;
173 case FIRST_STEP+1:
174 setTexts(i18n("Black box principles"), i18n("The balls are not visible, but we can shoot laser beams into the box at different entry points and observe if the laser beams leave the box, and if they do, where they come out.<br /><br />The laser beams interact with the balls in various ways."), i18n("Please click on the marked laser to shoot a beam into the black box."));
175 m_laserToUse = 0;
176 m_gameWidget->drawRay(0);
177 break;
178 case FIRST_STEP+2:
179 setTexts(i18n("No interaction"), i18n("If a laser beam does not interact with any ball in the black box, it comes out at the point opposite the entry point.<br /><br />Example: Suppose we have 3 balls in the box as shown. They will not affect laser beam \"1\".<br /><br />As the game progresses, each pair of entry/exit points is marked with a different number."), i18n("Now shoot the marked laser to discover the first kind of interaction."));
180 m_laserToUse = 4;
181 m_gameWidget->addBallUnsure(8);
182 m_gameWidget->addBallUnsure(27);
183 m_gameWidget->addBallUnsure(34);
184 m_gameWidget->drawRay(0);
185 break;
186 case FIRST_STEP+3:
187 setTexts(i18n("Hit"), i18n("A direct impact on a ball is called a \"<b>hit</b>\". A beam that hits a ball does <b>not</b> emerge from the black box.<br /><br />Example: The beam might have hit a ball at the position shown, but the exact position of the hit is not certain: There are many other possibilities."), i18n("Shoot the marked laser to discover the second kind of interaction."));
188 m_laserToUse = 22;
189 m_gameWidget->addBallUnsure(28);
190 m_gameWidget->drawRay(4);
191 break;
192 case FIRST_STEP+4:
193 setTexts(i18n("Simple deflection"), i18n("The interaction of a beam that does not actually hit a ball, but aims to one side of it, is called a \"<b>deflection</b>\". The angle of deflection of the beam is always <b>90 degrees</b>.<br /><br />Example: The ball shown would deflect beam \"2\" upward, as shown, but this is <b>not</b> the only possibility."), i18n("Click on \"Next\" to see another combination of ball positions that deflects the laser beam as shown."));
194 m_laserToUse = MAY_NOT_USE;
195 setNewStepMaxAllowed(FIRST_STEP+5);
196 m_gameWidget->addBallUnsure(16);
197 m_gameWidget->drawRay(22);
198 break;
199 case FIRST_STEP+5:
200 setTexts(i18n("Several deflections"), i18n("As you can see, interactions in the black box can be quite complicated!<br />A laser beam entering and exiting at the positions \"2\" might have been deflected by this configuration of 3 balls."), i18n("Shoot the marked laser to discover another kind of result."));
201 m_laserToUse = 19;
202 m_gameWidget->addBallUnsure(5);
203 m_gameWidget->addBallUnsure(26);
204 m_gameWidget->addBallUnsure(29);
205 m_gameWidget->drawRay(22);
206 break;
207 case FIRST_STEP+6:
208 setTexts(i18n("Reflection"), i18n("If the laser beam leaves the black box <b>at the entry point</b>, it has been reflected backward inside the black box.<br /><br />Example: We have placed 2 balls for you in a configuration that would lead to such a reflection."), i18n("Shoot the marked laser to see another backward reflection case."));
209 m_laserToUse = 15;
210 m_gameWidget->addBallUnsure(22);
211 m_gameWidget->addBallUnsure(34);
212 m_gameWidget->drawRay(19);
213 break;
214 case FIRST_STEP+7:
215 setTexts(i18n("Special reflection"), i18n("If a ball is <b>at the edge of the box</b> (with no other ball nearby), a beam which is aimed into the black box directly beside it causes a backward reflection.<br /><br />Example: The configuration shown can cause a backward reflection."), i18n("Nearly done. Click on \"Next\"."));
216 m_laserToUse = MAY_NOT_USE;
217 setNewStepMaxAllowed(FIRST_STEP+8);
218 m_gameWidget->addBallUnsure(33);
219 m_gameWidget->drawRay(15);
220 break;
221 case FIRST_STEP+8:
222 setTexts(i18n("Marker for \"free position\""), i18n("We are sure there are no balls in the first 2 columns. If there were any, the beam entering at position \"1\" would hit a ball or be deflected by a ball in column 2. You can mark a \"free position\" with a right mouse click (see also keyboard shortcuts).<br /><br />Example: There are 12 markers in the first 2 columns."), i18n("Click on \"Next\"."));
223 m_laserToUse = MAY_NOT_USE;
224 setNewStepMaxAllowed(FIRST_STEP+9);
225 for (int i=0;i<ROWS;i++) {
226 m_gameWidget->addMarkerNothing(i*COLUMNS);
227 m_gameWidget->addMarkerNothing(i*COLUMNS+1);
228 }
229 break;
230 case FIRST_STEP+9:
231 setTexts(i18n("Marking balls"), i18n("When you have worked out where a ball is, please use the left mouse button to mark it. To remove a ball mark, use the left mouse button again. Last tip: If you are not sure about a position, you can use a right click on a ball to mark it as \"unsure\". (See also keyboard shortcuts.)<br /><br />Example: We marked one position as sure, the other one as unsure."), i18n("Click on \"Next\"."));
232 m_laserToUse = MAY_NOT_USE;
233 setNewStepMaxAllowed(FIRST_STEP+10);
234 m_gameWidget->addBall(33);
235 m_gameWidget->addBallUnsure(35);
236 break;
237 case FIRST_STEP+10:
238 setTexts(i18n("Let us play!"), i18n("<b>Congratulations!</b> You now know <b>all the rules</b> for KBlackBox.<br /><br /><b>You can start to play.</b> Try to finish this tutorial game by yourself!<br /><br />Tip: We have sent in enough beams to deduce the positions of the 3 balls with certainty. Of course, you can use some more shots if needed."), i18n("Finish placing the balls and click on \"Done!\" when you are done!"));
239 m_laserToUse = MAY_NOT_USE;
240 setNewStepMaxAllowed(FIRST_STEP+9);
241 break;
242 }
243
244 m_buttonPrevious->setEnabled(m_step!=FIRST_STEP);
245 m_buttonNext->setEnabled(m_step<m_stepMaxAllowed);
246 m_progression->setValue(m_step);
247 showMarker(m_laserToUse);
248}
249
250
251void KBBTutorial::start()
252{
253 m_stepMaxAllowed = 0;
254 show();
255}
256
257
258
259//
260// Private slots
261//
262
263void KBBTutorial::nextStep()
264{
265 setStep(m_step+1);
266}
267
268
269void KBBTutorial::previousStep()
270{
271 setStep(m_step-1);
272}
273
274
275void KBBTutorial::restoreStyle()
276{
277 m_playerAction->setStyleSheet("color: palette(text); background-color: palette(window)");
278}
279
280
281
282//
283// Private
284//
285
286void KBBTutorial::setNewStepMaxAllowed(const int newStepMax)
287{
288 if (m_step>m_stepMaxAllowed)
289 m_stepMaxAllowed = m_step;
290 if (newStepMax>m_stepMaxAllowed)
291 m_stepMaxAllowed = newStepMax;
292}
293
294
295void KBBTutorial::setTexts(const QString &title, const QString &text, const QString &action)
296{
297 m_title->setText("<qt><strong>" + title + "</strong></qt>");
298 m_explanation->setText("<qt>" + text + "</qt>");
299 m_playerAction->setText("<qt><b>" + action + "</b></qt>");
300}
301
302
303void KBBTutorial::showMarker(const int laserPosition) const
304{
305 Q_ASSERT(m_marker!=0);
306
307 if (laserPosition==MAY_NOT_USE)
308 m_marker->hide();
309 else {
310 m_marker->setBorderPosition(laserPosition);
311 m_marker->show();
312 }
313}
314
315#include "kbbtutorial.moc"
316