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"