File indexing completed on 2024-04-28 04:01:51

0001 /*
0002     SPDX-FileCopyrightText: 2007-2008 Thomas Gallinari <tg8187@yahoo.fr>
0003     SPDX-FileCopyrightText: 2007-2008 Alexandre Galinier <alex.galinier@hotmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "gamescene.h"
0009 #include "bonus.h"
0010 #include "cell.h"
0011 
0012 #include <KGameTheme>
0013 
0014 #include <KLocalizedString>
0015 
0016 GameScene::GameScene(Game *p_game, const KGameTheme *theme)
0017     : m_game(p_game)
0018     , m_kapmanItem(nullptr)
0019     , m_mazeItem(nullptr)
0020 {
0021     connect(p_game, &Game::levelStarted, this, &GameScene::intro);
0022     connect(p_game, &Game::gameStarted, this, &GameScene::start);
0023     connect(p_game, &Game::pauseChanged, this, &GameScene::setPaused);
0024     connect(p_game, &Game::elementEaten, this, &GameScene::hideElement);
0025     connect(p_game, &Game::bonusOn, this, &GameScene::displayBonus);
0026     connect(p_game, &Game::bonusOff, this, &GameScene::hideBonus);
0027 
0028     // Connection between Game and GameScene for the display of won points when a bonus or a ghost is eaten
0029     connect(p_game, &Game::pointsToDisplay, this, &GameScene::displayPoints);
0030 
0031     // Load the SVG file
0032     m_renderer = new QSvgRenderer();
0033     loadTheme(theme);
0034 
0035     // Create the MazeItem
0036     m_mazeItem = new MazeItem();
0037     // Give the maze the shared renderer to avoid loading the whole SVG file again
0038     m_mazeItem->setSharedRenderer(m_renderer);
0039     // Set the element Id to the right value
0040     m_mazeItem->setElementId(QStringLiteral("maze"));
0041     m_mazeItem->setZValue(-2);
0042 
0043     // Create the KapmanItem
0044     m_kapmanItem = new KapmanItem(p_game->getKapman());
0045     m_kapmanItem->setSharedRenderer(m_renderer);
0046     m_kapmanItem->setElementId(QStringLiteral("kapman_0"));
0047     // Corrects the position of the KapmanItem
0048     m_kapmanItem->update(p_game->getKapman()->getX(), p_game->getKapman()->getY());
0049     m_kapmanItem->setZValue(2);
0050     // Stops the Kapman animation
0051     m_kapmanItem->stopAnim();
0052 
0053     // Create the GhostItems
0054     for (int i = 0; i < p_game->getGhosts().size(); ++i) {
0055         auto ghost = new GhostItem(p_game->getGhosts()[i]);
0056         ghost->setSharedRenderer(m_renderer);
0057         ghost->setElementId(p_game->getGhosts()[i]->getImageId());
0058         ghost->update(p_game->getGhosts()[i]->getX(), p_game->getGhosts()[i]->getY());
0059         // At the beginning, the ghosts are above the kapman because they eat him
0060         ghost->setZValue(3);
0061         m_ghostItems.append(ghost);
0062     }
0063     // Create the Pill and Energizer items
0064     m_elementItems = new ElementItem **[m_game->getMaze()->getNbRows()];
0065     for (int i = 0; i < m_game->getMaze()->getNbRows(); ++i) {
0066         m_elementItems[i] = new ElementItem *[m_game->getMaze()->getNbColumns()];
0067         for (int j = 0; j < m_game->getMaze()->getNbColumns(); ++j) {
0068             if (m_game->getMaze()->getCell(i, j).getElement() != nullptr) {
0069                 // Create the element and set the image
0070                 auto element = new ElementItem(m_game->getMaze()->getCell(i, j).getElement());
0071                 element->setSharedRenderer(m_renderer);
0072                 element->setElementId(m_game->getMaze()->getCell(i, j).getElement()->getImageId());
0073                 element->update(m_game->getMaze()->getCell(i, j).getElement()->getX(), m_game->getMaze()->getCell(i, j).getElement()->getY());
0074                 m_elementItems[i][j] = element;
0075             } else {
0076                 m_elementItems[i][j] = nullptr;
0077             }
0078         }
0079     }
0080     // Create the Bonus item
0081     m_bonusItem = new ElementItem(m_game->getBonus());
0082     m_bonusItem->setSharedRenderer(m_renderer);
0083     m_bonusItem->setElementId(QStringLiteral("bonus1"));
0084 
0085     // All elements are created, update theme properties
0086     updateSvgIds();
0087     updateThemeProperties(theme);
0088 
0089     // Create the introduction labels
0090     m_introLabel = new QGraphicsTextItem(i18n("GET READY!!!"));
0091     m_introLabel->setFont(QFont(QStringLiteral("Helvetica"), 25, QFont::Bold, false));
0092     m_introLabel->setDefaultTextColor(QColor(QLatin1String("#FFFF00")));
0093     m_introLabel->setZValue(4);
0094     m_introLabel2 = new QGraphicsTextItem(i18n("Press any arrow key to start"));
0095     m_introLabel2->setFont(QFont(QStringLiteral("Helvetica"), 15, QFont::Bold, false));
0096     m_introLabel2->setDefaultTextColor(QColor(QLatin1String("#FFFF00")));
0097     m_introLabel2->setZValue(4);
0098     // Create the new level label
0099     m_newLevelLabel = new QGraphicsTextItem();
0100     m_newLevelLabel->setFont(QFont(QStringLiteral("Helvetica"), 35, QFont::Bold, false));
0101     m_newLevelLabel->setDefaultTextColor(QColor(QLatin1String("#FFFF00")));
0102     m_newLevelLabel->setZValue(4);
0103     // Create the pause label
0104     m_pauseLabel = new QGraphicsTextItem(i18n("PAUSED"));
0105     m_pauseLabel->setFont(QFont(QStringLiteral("Helvetica"), 35, QFont::Bold, false));
0106     m_pauseLabel->setDefaultTextColor(QColor(QLatin1String("#FFFF00")));
0107     m_pauseLabel->setZValue(4);
0108 
0109     // Display the MazeItem
0110     addItem(m_mazeItem);
0111     // Display the KapmanItem
0112     addItem(m_kapmanItem);
0113     // Display each GhostItem
0114     for (int i = 0; i < m_ghostItems.size(); ++i) {
0115         addItem(m_ghostItems[i]);
0116     }
0117     // Display each Pill and Energizer item and introduction labels
0118     intro(true);
0119 }
0120 
0121 GameScene::~GameScene()
0122 {
0123     delete m_mazeItem;
0124     delete m_kapmanItem;
0125     for (int i = 0; i < m_ghostItems.size(); ++i) {
0126         delete m_ghostItems[i];
0127     }
0128     for (int i = 0; i < m_game->getMaze()->getNbRows(); ++i) {
0129         for (int j = 0; j < m_game->getMaze()->getNbColumns(); ++j) {
0130             if (m_elementItems[i][j] != nullptr) {
0131                 delete m_elementItems[i][j];
0132             }
0133         }
0134         delete[] m_elementItems[i];
0135     }
0136     delete[] m_elementItems;
0137     delete m_bonusItem;
0138     delete m_introLabel;
0139     delete m_introLabel2;
0140     delete m_newLevelLabel;
0141     delete m_pauseLabel;
0142     delete m_renderer;
0143 }
0144 
0145 Game *GameScene::getGame() const
0146 {
0147     return m_game;
0148 }
0149 
0150 void GameScene::loadTheme(const KGameTheme *theme)
0151 {
0152     if (!m_renderer->load(theme->graphicsPath())) {
0153         return;
0154     }
0155 
0156     // Update elementIDs, theme properties
0157     updateSvgIds();
0158     updateThemeProperties(theme);
0159 
0160     update(0, 0, width(), height());
0161 }
0162 
0163 void GameScene::updateSvgIds()
0164 {
0165     // Needed so new boundingRects() are read for all SVG elements after a theme change
0166     //  Sanity check, see if game elements already exist
0167     if (!m_kapmanItem) {
0168         return;
0169     }
0170 
0171     // Set the element Id to the right value
0172     m_mazeItem->setElementId(QStringLiteral("maze"));
0173 
0174     // Create the KapmanItem
0175     m_kapmanItem->setElementId(QStringLiteral("kapman_0"));
0176     // Corrects the position of the KapmanItem
0177     m_kapmanItem->update(m_game->getKapman()->getX(), m_game->getKapman()->getY());
0178 
0179     for (int i = 0; i < m_ghostItems.size(); ++i) {
0180         GhostItem *ghost = m_ghostItems[i];
0181         ghost->setElementId(m_game->getGhosts()[i]->getImageId());
0182         ghost->update(m_game->getGhosts()[i]->getX(), m_game->getGhosts()[i]->getY());
0183     }
0184     for (int i = 0; i < m_game->getMaze()->getNbRows(); ++i) {
0185         for (int j = 0; j < m_game->getMaze()->getNbColumns(); ++j) {
0186             if (m_elementItems[i][j] != nullptr) {
0187                 ElementItem *element = m_elementItems[i][j];
0188                 element->setElementId(m_game->getMaze()->getCell(i, j).getElement()->getImageId());
0189                 element->update(m_game->getMaze()->getCell(i, j).getElement()->getX(), m_game->getMaze()->getCell(i, j).getElement()->getY());
0190             }
0191         }
0192     }
0193 }
0194 
0195 void GameScene::updateThemeProperties(const KGameTheme *theme)
0196 {
0197     // Sanity check, see if game elements already exist
0198     if (!m_kapmanItem) {
0199         return;
0200     }
0201 
0202     // Set the Rotation flag for KapmanItem
0203     if (theme->customData(QStringLiteral("RotateKapman")) == QLatin1String("0")) {
0204         m_kapmanItem->setRotationFlag(false);
0205     } else {
0206         m_kapmanItem->setRotationFlag(true);
0207     }
0208 }
0209 
0210 void GameScene::intro(const bool p_newLevel)
0211 {
0212     // If a new level has begun
0213     if (p_newLevel) {
0214         // Set each Pill and Energizer item to its original coordinates
0215         for (int i = 0; i < m_game->getMaze()->getNbRows(); ++i) {
0216             for (int j = 0; j < m_game->getMaze()->getNbColumns(); ++j) {
0217                 if (m_elementItems[i][j] != nullptr) {
0218                     if (!items().contains(m_elementItems[i][j])) {
0219                         addItem(m_elementItems[i][j]);
0220                     }
0221                 }
0222             }
0223         }
0224         if (items().contains(m_introLabel)) {
0225             removeItem(m_introLabel);
0226         }
0227         // Display the new level label
0228         m_newLevelLabel->setPlainText(i18nc("The number of the game level", "Level %1", m_game->getLevel()));
0229         if (!items().contains(m_newLevelLabel)) {
0230             addItem(m_newLevelLabel);
0231             m_newLevelLabel->setPos((width() - m_newLevelLabel->boundingRect().width()) / 2, (height() - m_newLevelLabel->boundingRect().height()) / 2);
0232         }
0233         // Display the introduction label
0234         if (!items().contains(m_introLabel2)) {
0235             addItem(m_introLabel2);
0236             m_introLabel2->setPos((width() - m_introLabel2->boundingRect().width()) / 2,
0237                                   (height() - m_introLabel2->boundingRect().height() + m_newLevelLabel->boundingRect().height()) / 2);
0238         }
0239     } else {
0240         // Display the introduction labels
0241         if (!items().contains(m_introLabel)) {
0242             addItem(m_introLabel);
0243             m_introLabel->setPos((width() - m_introLabel->boundingRect().width()) / 2, (height() - m_introLabel->boundingRect().height()) / 2);
0244         }
0245         if (!items().contains(m_introLabel2)) {
0246             addItem(m_introLabel2);
0247             m_introLabel2->setPos((width() - m_introLabel2->boundingRect().width()) / 2,
0248                                   (height() - m_introLabel2->boundingRect().height() + m_introLabel->boundingRect().height()) / 2);
0249         }
0250     }
0251 }
0252 
0253 void GameScene::start()
0254 {
0255     // If the introduction and new level labels were displayed then remove them
0256     if (items().contains(m_introLabel)) {
0257         removeItem(m_introLabel);
0258     }
0259     if (items().contains(m_introLabel2)) {
0260         removeItem(m_introLabel2);
0261     }
0262     if (items().contains(m_newLevelLabel)) {
0263         removeItem(m_newLevelLabel);
0264     }
0265 }
0266 
0267 void GameScene::setPaused(const bool p_pause, const bool p_fromUser)
0268 {
0269     // If the game has paused
0270     if (p_pause) {
0271         // If the pause is due to an action from the user
0272         if (p_fromUser) {
0273             // If the label was not displayed yet
0274             if (!items().contains(m_pauseLabel)) {
0275                 // FIXME: Hack to remove labels when pausing game while init labels are shown (icwiener)
0276                 //        This should be done cleaner
0277                 // FIXME #2: start() is a misleading method name ...
0278                 start();
0279                 // Display the pause label
0280                 addItem(m_pauseLabel);
0281                 m_pauseLabel->setPos((width() - m_pauseLabel->boundingRect().width()) / 2, (height() - m_pauseLabel->boundingRect().height()) / 2);
0282             }
0283         }
0284         // Stop kapman animation
0285         m_kapmanItem->pauseAnim();
0286     } else { // If the game has resumed
0287         // If the pause was due to an action from the user
0288         if (p_fromUser) {
0289             // If the label was displayed
0290             if (items().contains(m_pauseLabel)) {
0291                 removeItem(m_pauseLabel);
0292             }
0293         }
0294         // Resume kapman animation
0295         m_kapmanItem->resumeAnim();
0296     }
0297 }
0298 
0299 void GameScene::hideElement(const qreal p_x, const qreal p_y)
0300 {
0301     if (items().contains(m_elementItems[(int)((p_y - (Cell::SIZE * 0.5)) / Cell::SIZE)][(int)((p_x - (Cell::SIZE * 0.5)) / Cell::SIZE)])) {
0302         removeItem(m_elementItems[(int)((p_y - (Cell::SIZE * 0.5)) / Cell::SIZE)][(int)((p_x - (Cell::SIZE * 0.5)) / Cell::SIZE)]);
0303     }
0304 }
0305 
0306 void GameScene::displayBonus()
0307 {
0308     if (!items().contains(m_bonusItem)) {
0309         switch (m_game->getLevel()) {
0310         case 1:
0311             m_bonusItem->setElementId(QStringLiteral("bonus1"));
0312             break;
0313         case 2:
0314             m_bonusItem->setElementId(QStringLiteral("bonus2"));
0315             break;
0316         case 3:
0317             m_bonusItem->setElementId(QStringLiteral("bonus3"));
0318             break;
0319         case 4:
0320             m_bonusItem->setElementId(QStringLiteral("bonus4"));
0321             break;
0322         case 5:
0323             m_bonusItem->setElementId(QStringLiteral("bonus5"));
0324             break;
0325         case 6:
0326             m_bonusItem->setElementId(QStringLiteral("bonus6"));
0327             break;
0328         default:
0329             m_bonusItem->setElementId(QStringLiteral("bonus7"));
0330             break;
0331         }
0332         m_bonusItem->update(m_game->getBonus()->getX(), m_game->getBonus()->getY());
0333         addItem(m_bonusItem);
0334     }
0335 }
0336 
0337 void GameScene::hideBonus()
0338 {
0339     if (items().contains(m_bonusItem)) {
0340         removeItem(m_bonusItem);
0341     }
0342 }
0343 
0344 void GameScene::displayPoints(long p_wonPoints, qreal p_xPos, qreal p_yPos)
0345 {
0346     // Launch a singleShot timer
0347     QTimer::singleShot(1000, this, &GameScene::hidePoints);
0348 
0349     // Add a label in the list of won points Labels
0350     m_wonPointsLabels.prepend(new QGraphicsTextItem(QString::number(p_wonPoints)));
0351     addItem(m_wonPointsLabels.first());
0352 
0353     // Temporary reference to the first item in the list
0354     QGraphicsTextItem *tempRef = m_wonPointsLabels.first();
0355 
0356     // Positioning and customization of the point label
0357     tempRef->setDefaultTextColor(QColor(QLatin1String("#FFFF00")));
0358     tempRef->setFont(QFont(QStringLiteral("Helvetica"), 15, QFont::Normal, false));
0359     tempRef->setPos(p_xPos - (tempRef->boundingRect().width() / 2), p_yPos - (tempRef->boundingRect().height() / 2));
0360     tempRef->setZValue(-1);
0361 }
0362 
0363 void GameScene::hidePoints()
0364 {
0365     // Remove the oldest item of the list from the scene
0366     removeItem(m_wonPointsLabels.last());
0367     // Remove the oldest item from the list
0368     delete m_wonPointsLabels.takeLast();
0369 }
0370 
0371 #include "moc_gamescene.cpp"