File indexing completed on 2024-04-21 04:02:03
0001 /* 0002 KBlackBox - A simple game inspired by an emacs module 0003 0004 SPDX-FileCopyrightText: 2007 Nicolas Roffet <nicolas-kde@roffet.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "kbbtutorial.h" 0010 0011 0012 0013 #include <QFrame> 0014 #include <QHBoxLayout> 0015 #include <QLabel> 0016 #include <QProgressBar> 0017 #include <QTimer> 0018 #include <QVBoxLayout> 0019 0020 0021 #include <QIcon> 0022 #include <KLocalizedString> 0023 #include <QPushButton> 0024 #include <KTextEdit> 0025 0026 0027 #include "kbbgraphicsitemtutorialmarker.h" 0028 #include "kbbscalablegraphicwidget.h" 0029 0030 0031 0032 // 0033 // Constructor / Destructor 0034 // 0035 0036 KBBTutorial::KBBTutorial(QWidget* parent) : QGroupBox(i18n("Tutorial"), parent) 0037 { 0038 m_marker = nullptr; 0039 m_gameWidget = nullptr; 0040 0041 setMinimumSize(QSize(WIDTH, WIDTH)); 0042 setFixedWidth(WIDTH); 0043 0044 QVBoxLayout *tutorialLayout = new QVBoxLayout(); 0045 setLayout(tutorialLayout); 0046 setFlat(true); 0047 0048 m_progression = new QProgressBar(this); 0049 m_progression->setTextVisible(true); 0050 m_progression->setFormat(QStringLiteral("%v / %m")); 0051 m_progression->setMinimum(FIRST_STEP-1); 0052 m_progression->setMaximum(LAST_STEP); 0053 m_progression->setWhatsThis(i18n("Displays the progress of the tutorial.")); 0054 tutorialLayout->addWidget(m_progression); 0055 0056 m_title = new QLabel(this); 0057 tutorialLayout->addWidget(m_title, 0, Qt::AlignHCenter); 0058 0059 m_explanation = new KTextEdit(this); 0060 m_explanation->setReadOnly(true); 0061 m_explanation->setFrameStyle(QFrame::NoFrame); 0062 m_explanation->setAlignment(Qt::AlignJustify); 0063 tutorialLayout->addWidget(m_explanation); 0064 0065 tutorialLayout->addStretch(); 0066 0067 0068 QHBoxLayout *actionLayout = new QHBoxLayout(); 0069 tutorialLayout->addLayout(actionLayout); 0070 QLabel* iconLabel = new QLabel(this); 0071 iconLabel->setFixedSize(24, 24); 0072 iconLabel->setPixmap(QIcon::fromTheme( QStringLiteral( "go-next" )).pixmap(24, 24)); 0073 actionLayout->addWidget(iconLabel, 0, Qt::AlignVCenter); 0074 m_playerAction = new QLabel(this); 0075 m_playerAction->setWhatsThis(i18n("Describes what you should do to reach the next tutorial step.")); 0076 m_playerAction->setAlignment(Qt::AlignLeft); 0077 m_playerAction->setWordWrap(true); 0078 m_playerAction->setFrameStyle(QFrame::StyledPanel); 0079 m_playerAction->setStyleSheet(QStringLiteral("border-style: none")); 0080 actionLayout->addWidget(m_playerAction, 0, Qt::AlignVCenter); 0081 0082 QHBoxLayout *buttonLayout = new QHBoxLayout(); 0083 tutorialLayout->addLayout(buttonLayout); 0084 m_buttonPrevious = new QPushButton(QIcon::fromTheme( QStringLiteral( "go-previous") ), i18nc("Previous tutorial step", "&Previous"), this); 0085 m_buttonPrevious->setWhatsThis(i18n("Go back to the previous tutorial step.")); 0086 connect(m_buttonPrevious, &QPushButton::clicked, this, &KBBTutorial::previousStep); 0087 buttonLayout->addWidget(m_buttonPrevious); 0088 m_buttonNext = new QPushButton(QIcon::fromTheme( QStringLiteral( "go-next")) , i18nc("Next tutorial step", "&Next"), this); 0089 m_buttonNext->setWhatsThis(i18n("Go to the next tutorial step.")); 0090 connect(m_buttonNext, &QPushButton::clicked, this, &KBBTutorial::nextStep); 0091 m_buttonNext->setDefault(true); 0092 buttonLayout->addWidget(m_buttonNext); 0093 } 0094 0095 0096 0097 // 0098 // Public 0099 // 0100 0101 void KBBTutorial::hideEvent(QHideEvent*) 0102 { 0103 showMarker(MAY_NOT_USE); 0104 } 0105 0106 0107 bool KBBTutorial::maySolve() 0108 { 0109 return m_step==LAST_STEP; 0110 } 0111 0112 0113 bool KBBTutorial::mayShootRay(const int incomingPosition) 0114 { 0115 if (m_step==LAST_STEP) 0116 return true; 0117 else 0118 if (incomingPosition==m_laserToUse) { 0119 nextStep(); 0120 return true; 0121 } else { 0122 // Highlight m_playerAction to show what the player has to do 0123 m_playerAction->setStyleSheet(QStringLiteral("color: black; background-color: #de0000")); 0124 QTimer::singleShot(HIGHLIGHT_TIME, this, &KBBTutorial::restoreStyle); 0125 return false; 0126 } 0127 } 0128 0129 0130 void KBBTutorial::setGameWidget(KBBScalableGraphicWidget* gameWidget, KBBGraphicsItemTutorialMarker* marker) 0131 { 0132 m_gameWidget = gameWidget; 0133 m_marker = marker; 0134 } 0135 0136 0137 void KBBTutorial::setStep(const int step) 0138 { 0139 Q_ASSERT((step>=FIRST_STEP) && (step<=LAST_STEP)); 0140 Q_ASSERT(m_gameWidget!=nullptr); 0141 0142 m_step = step; 0143 setNewStepMaxAllowed(m_step); 0144 0145 if (m_step!=LAST_STEP) 0146 m_gameWidget->removeAllBalls(); 0147 0148 switch (m_step) { 0149 case FIRST_STEP: 0150 m_gameWidget->newGame(KBBTutorial::COLUMNS, KBBTutorial::ROWS, KBBTutorial::BALLS); 0151 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\"")); 0152 m_laserToUse = MAY_NOT_USE; 0153 setNewStepMaxAllowed(FIRST_STEP+1); 0154 break; 0155 case FIRST_STEP+1: 0156 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.")); 0157 m_laserToUse = 0; 0158 m_gameWidget->drawRay(0); 0159 break; 0160 case FIRST_STEP+2: 0161 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.")); 0162 m_laserToUse = 4; 0163 m_gameWidget->addBallUnsure(8); 0164 m_gameWidget->addBallUnsure(27); 0165 m_gameWidget->addBallUnsure(34); 0166 m_gameWidget->drawRay(0); 0167 break; 0168 case FIRST_STEP+3: 0169 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.")); 0170 m_laserToUse = 22; 0171 m_gameWidget->addBallUnsure(28); 0172 m_gameWidget->drawRay(4); 0173 break; 0174 case FIRST_STEP+4: 0175 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.")); 0176 m_laserToUse = MAY_NOT_USE; 0177 setNewStepMaxAllowed(FIRST_STEP+5); 0178 m_gameWidget->addBallUnsure(16); 0179 m_gameWidget->drawRay(22); 0180 break; 0181 case FIRST_STEP+5: 0182 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.")); 0183 m_laserToUse = 19; 0184 m_gameWidget->addBallUnsure(5); 0185 m_gameWidget->addBallUnsure(26); 0186 m_gameWidget->addBallUnsure(29); 0187 m_gameWidget->drawRay(22); 0188 break; 0189 case FIRST_STEP+6: 0190 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.")); 0191 m_laserToUse = 15; 0192 m_gameWidget->addBallUnsure(22); 0193 m_gameWidget->addBallUnsure(34); 0194 m_gameWidget->drawRay(19); 0195 break; 0196 case FIRST_STEP+7: 0197 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\".")); 0198 m_laserToUse = MAY_NOT_USE; 0199 setNewStepMaxAllowed(FIRST_STEP+8); 0200 m_gameWidget->addBallUnsure(33); 0201 m_gameWidget->drawRay(15); 0202 break; 0203 case FIRST_STEP+8: 0204 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\".")); 0205 m_laserToUse = MAY_NOT_USE; 0206 setNewStepMaxAllowed(FIRST_STEP+9); 0207 for (int i=0;i<ROWS;i++) { 0208 m_gameWidget->addMarkerNothing(i*COLUMNS); 0209 m_gameWidget->addMarkerNothing(i*COLUMNS+1); 0210 } 0211 break; 0212 case FIRST_STEP+9: 0213 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\".")); 0214 m_laserToUse = MAY_NOT_USE; 0215 setNewStepMaxAllowed(FIRST_STEP+10); 0216 m_gameWidget->addBall(33); 0217 m_gameWidget->addBallUnsure(35); 0218 break; 0219 case FIRST_STEP+10: 0220 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!")); 0221 m_laserToUse = MAY_NOT_USE; 0222 setNewStepMaxAllowed(FIRST_STEP+9); 0223 break; 0224 } 0225 0226 m_buttonPrevious->setEnabled(m_step!=FIRST_STEP); 0227 m_buttonNext->setEnabled(m_step<m_stepMaxAllowed); 0228 m_progression->setValue(m_step); 0229 showMarker(m_laserToUse); 0230 } 0231 0232 0233 void KBBTutorial::start() 0234 { 0235 m_stepMaxAllowed = 0; 0236 show(); 0237 } 0238 0239 0240 0241 // 0242 // Private slots 0243 // 0244 0245 void KBBTutorial::nextStep() 0246 { 0247 setStep(m_step+1); 0248 } 0249 0250 0251 void KBBTutorial::previousStep() 0252 { 0253 setStep(m_step-1); 0254 } 0255 0256 0257 void KBBTutorial::restoreStyle() 0258 { 0259 m_playerAction->setStyleSheet(QStringLiteral("color: palette(text); background-color: palette(window)")); 0260 } 0261 0262 0263 0264 // 0265 // Private 0266 // 0267 0268 void KBBTutorial::setNewStepMaxAllowed(const int newStepMax) 0269 { 0270 if (m_step>m_stepMaxAllowed) 0271 m_stepMaxAllowed = m_step; 0272 if (newStepMax>m_stepMaxAllowed) 0273 m_stepMaxAllowed = newStepMax; 0274 } 0275 0276 0277 void KBBTutorial::setTexts(const QString &title, const QString &text, const QString &action) 0278 { 0279 m_title->setText(QLatin1String("<qt><strong>") + title + QLatin1String("</strong></qt>")); 0280 m_explanation->setText(QLatin1String("<qt>") + text + QLatin1String("</qt>")); 0281 m_playerAction->setText(QLatin1String("<qt><b>") + action + QLatin1String("</b></qt>")); 0282 } 0283 0284 0285 void KBBTutorial::showMarker(const int laserPosition) const 0286 { 0287 Q_ASSERT(m_marker!=nullptr); 0288 0289 if (laserPosition==MAY_NOT_USE) 0290 m_marker->hide(); 0291 else { 0292 m_marker->setBorderPosition(laserPosition); 0293 m_marker->show(); 0294 } 0295 } 0296 0297 #include "moc_kbbtutorial.cpp"