File indexing completed on 2024-04-21 04:02:37

0001 /*
0002     SPDX-FileCopyrightText: 2012 Christian Krippendorf <Coding@Christian-Krippendorf.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // own
0008 #include "gameview.h"
0009 
0010 // Qt
0011 #include <QMouseEvent>
0012 #include <QRandomGenerator>
0013 #include <QResizeEvent>
0014 
0015 // KF
0016 #include <KLocalizedString>
0017 
0018 // LibKMahjongg
0019 #include <KMahjonggTileset>
0020 #include <KMahjonggBackground>
0021 
0022 // KMahjongg
0023 #include "demoanimation.h"
0024 #include "gamedata.h"
0025 #include "gameitem.h"
0026 #include "gamescene.h"
0027 #include "kmahjongg_debug.h"
0028 #include "kmahjongglayout.h"
0029 #include "movelistanimation.h"
0030 #include "prefs.h"
0031 #include "selectionanimation.h"
0032 #include "gamebackground.h"
0033 #include "gameremovedtiles.h"
0034 
0035 
0036 GameView::GameView(GameScene * gameScene, GameData * gameData, QWidget * parent)
0037     : QGraphicsView(gameScene, parent)
0038     , m_cheatsUsed(0)
0039     , m_gameNumber(0)
0040     , m_gamePaused(false)
0041     , m_match(false)
0042     , m_gameGenerated(false)
0043     , m_showRemovedTiles(true)
0044     , m_remTilesWidthFactor(0.3)
0045     , m_gameData(gameData)
0046     , m_selectedItem(nullptr)
0047     , m_gameBackground(new GameBackground())
0048     , m_gameRemovedTiles(new GameRemovedTiles())
0049     , m_tilesetPath(new QString())
0050     , m_backgroundPath(new QString())
0051     , m_helpAnimation(new SelectionAnimation(this))
0052     , m_moveListAnimation(new MoveListAnimation(this))
0053     , m_demoAnimation(new DemoAnimation(this))
0054     , m_tiles(new KMahjonggTileset())
0055     , m_background(new KMahjonggBackground())
0056 {
0057     // Some settings to the QGraphicsView.
0058     setFocusPolicy(Qt::NoFocus);
0059     setStyleSheet(QStringLiteral("QGraphicsView { border-style: none; }"));
0060     setAutoFillBackground(true);
0061 
0062     // Read in some settings.
0063     m_angle = static_cast<TileViewAngle>(Prefs::angle());
0064 
0065     // Show removed tiles.
0066     m_showRemovedTiles = Prefs::removedTiles();
0067 
0068     // Init HelpAnimation
0069     m_helpAnimation->setAnimationSpeed(ANIMATION_SPEED);
0070     m_helpAnimation->setRepetitions(3);
0071 
0072     // Init DemoAnimation
0073     m_demoAnimation->setAnimationSpeed(ANIMATION_SPEED);
0074 
0075     // Init MoveListAnimation
0076     m_moveListAnimation->setAnimationSpeed(ANIMATION_SPEED);
0077 
0078     // Set the tileset to the game removed tiles object.
0079     m_gameRemovedTiles->setTileset(m_tiles);
0080     m_gameRemovedTiles->setGameData(m_gameData);
0081     m_gameRemovedTiles->setVisible(m_showRemovedTiles);
0082 
0083     // Add the fix background item to the scene
0084     scene()->setBackgroundItem(m_gameBackground);
0085 
0086     // Add the fix removedtiles item to the scene
0087     scene()->setRemovedTilesItem(m_gameRemovedTiles);
0088 
0089     m_selectionChangedConnect = connect(scene(), &GameScene::selectionChanged, this, &GameView::selectionChanged);
0090 
0091     connect(m_demoAnimation, &DemoAnimation::changeItemSelectedState, this, &GameView::changeItemSelectedState);
0092     connect(m_demoAnimation, &DemoAnimation::removeItem, this, &GameView::removeItem);
0093     connect(m_demoAnimation, &DemoAnimation::gameOver, this, &GameView::demoGameOver);
0094 
0095     connect(m_moveListAnimation, &MoveListAnimation::removeItem, this, &GameView::removeItem);
0096     connect(m_moveListAnimation, &MoveListAnimation::addItem, this, &GameView::addItemAndUpdate);
0097     connect(scene(), &GameScene::clearSelectedTile, this, &GameView::clearSelectedTile);
0098 }
0099 
0100 GameView::~GameView()
0101 {
0102     delete m_helpAnimation;
0103     delete m_demoAnimation;
0104     delete m_moveListAnimation;
0105     delete m_gameBackground;
0106     delete m_background;
0107     delete m_backgroundPath;
0108     delete m_tilesetPath;
0109     delete m_tiles;
0110 }
0111 
0112 GameScene * GameView::scene() const
0113 {
0114     return dynamic_cast<GameScene *>(QGraphicsView::scene());
0115 }
0116 
0117 bool GameView::checkUndoAllowed()
0118 {
0119     return (m_gameData->m_allowUndo && !checkDemoAnimationActive() && !checkMoveListAnimationActive());
0120 }
0121 
0122 bool GameView::checkRedoAllowed()
0123 {
0124     return (m_gameData->m_allowRedo && !checkDemoAnimationActive() && !checkMoveListAnimationActive());
0125 }
0126 
0127 long GameView::getGameNumber() const
0128 {
0129     return m_gameNumber;
0130 }
0131 
0132 void GameView::setGameNumber(long gameNumber)
0133 {
0134     m_gameNumber = gameNumber;
0135     setStatusText(i18n("Ready. Now it is your turn."));
0136 }
0137 
0138 bool GameView::undo()
0139 {
0140     // Clear user selections.
0141     clearSelectedTile();
0142 
0143     if (m_gameData->m_tileNum < m_gameData->m_maxTileNum) {
0144         m_gameData->clearRemovedTilePair(m_gameData->MoveListData(m_gameData->m_tileNum + 1),
0145                                          m_gameData->MoveListData(m_gameData->m_tileNum + 2));
0146 
0147         ++m_gameData->m_tileNum;
0148         addItemAndUpdate(m_gameData->MoveListData(m_gameData->m_tileNum));
0149         ++m_gameData->m_tileNum;
0150         addItemAndUpdate(m_gameData->MoveListData(m_gameData->m_tileNum));
0151 
0152         ++m_gameData->m_allowRedo;
0153 
0154         // Undo removed tile object.
0155         m_gameRemovedTiles->undo();
0156 
0157         setStatusText(i18n("Undo operation done successfully."));
0158 
0159         return true;
0160     }
0161 
0162     setStatusText(i18n("What do you want to undo? You have done nothing!"));
0163 
0164     return false;
0165 }
0166 
0167 bool GameView::redo()
0168 {
0169     if (m_gameData->m_allowRedo > 0) {
0170         m_gameData->setRemovedTilePair(m_gameData->MoveListData(m_gameData->m_tileNum),
0171                                        m_gameData->MoveListData(m_gameData->m_tileNum - 1));
0172 
0173         removeItem(m_gameData->MoveListData(m_gameData->m_tileNum));
0174         removeItem(m_gameData->MoveListData(m_gameData->m_tileNum));
0175 
0176         --m_gameData->m_allowRedo;
0177 
0178         // Test whether the game is over or not.
0179         if (m_gameData->m_tileNum == 0) {
0180             Q_EMIT gameOver(m_gameData->m_maxTileNum, m_cheatsUsed);
0181         } else {
0182             // The game is not over, so test if there are any valid moves.
0183             validMovesAvailable();
0184         }
0185 
0186         return true;
0187     }
0188     return false;
0189 }
0190 
0191 void GameView::demoGameOver(bool won)
0192 {
0193     if (won) {
0194         startMoveListAnimation();
0195     } else {
0196         setStatusText(i18n("Your computer has lost the game."));
0197         Q_EMIT demoOrMoveListAnimationOver(true);
0198     }
0199 }
0200 
0201 void GameView::createNewGame(long gameNumber)
0202 {
0203     setStatusText(i18n("Calculating new game..."));
0204 
0205     // Check any animations are running and stop them.
0206     checkHelpAnimationActive(true);
0207     checkDemoAnimationActive(true);
0208     checkMoveListAnimationActive(true);
0209 
0210     // Reset the removed tile object.
0211     m_gameRemovedTiles->reset();
0212 
0213     // Create a random game number, if no one was given.
0214     if (gameNumber == -1) {
0215         m_gameNumber = QRandomGenerator::global()->generate() % INT_MAX;
0216     } else {
0217         m_gameNumber = gameNumber;
0218     }
0219 
0220     m_gameData->m_allowUndo = 0;
0221     m_gameData->m_allowRedo = 0;
0222     m_gameData->random.seed(m_gameNumber);
0223 
0224     // Translate m_pGameData->Map to an array of POSITION data.  We only need to
0225     // do this once for each new game.
0226     m_gameData->generateTilePositions();
0227 
0228     // Now use the tile position data to generate tile dependency data.
0229     // We only need to do this once for each new game.
0230     m_gameData->generatePositionDepends();
0231 
0232     // TODO: This is really bad... the generatedStartPosition2() function should never fail!!!!
0233     // Now try to position tiles on the board, 64 tries max.
0234     for (short sNr = 0; sNr < 64; ++sNr) {
0235         if (m_gameData->generateStartPosition2()) {
0236             m_gameGenerated = true;
0237 
0238             // No cheats are used until now.
0239             m_cheatsUsed = 0;
0240             addItemsFromBoardLayout();
0241             populateItemNumber();
0242 
0243             QResizeEvent event(size(), size());
0244             resizeEvent(&event);
0245 
0246             setStatusText(i18n("Ready. Now it is your turn."));
0247 
0248             return;
0249         }
0250     }
0251 
0252     // Couldn't generate the game.
0253     m_gameGenerated = false;
0254 
0255     // Hide all generated tiles.
0256     const QList<GameItem *> gameItems = getGameItems();
0257     for (GameItem * item : gameItems) {
0258         item->hide();
0259     }
0260 
0261     setStatusText(i18n("Error generating new game!"));
0262 }
0263 
0264 void GameView::selectionChanged()
0265 {
0266     QList<GameItem *> selectedGameItems = scene()->selectedItems();
0267 
0268     // When no item is selected or help animation is running, there is nothing to do.
0269     if (selectedGameItems.size() < 1 || checkHelpAnimationActive() || checkDemoAnimationActive()) {
0270         return;
0271     }
0272 
0273     // If no item was already selected...
0274     if (m_selectedItem == nullptr) {
0275         // ...set the selected item.
0276         m_selectedItem = selectedGameItems.at(0);
0277 
0278         // Display the matching ones if wanted.
0279         if (m_match) {
0280             helpMatch(m_selectedItem);
0281         }
0282     } else {
0283         // The selected item is already there, so this is the second selected item.
0284 
0285         // If the same item was clicked, clear the selection and return.
0286         if (m_selectedItem == selectedGameItems.at(0)) {
0287             clearSelectedTile();
0288             return;
0289         }
0290 
0291         // Get both items and their positions.
0292         POSITION stFirstPos = m_selectedItem->getGridPos();
0293         POSITION stSecondPos = selectedGameItems.at(0)->getGridPos();
0294 
0295         // Test if the items are the same...
0296         if (m_gameData->isMatchingTile(stFirstPos, stSecondPos)) {
0297             // Update the removed tiles in GameData.
0298             m_gameData->setRemovedTilePair(stFirstPos, stSecondPos);
0299 
0300             // One tile pair is removed, so we are not allowed to redo anymore.
0301             m_gameData->m_allowRedo = 0;
0302 
0303             // Remove the items.
0304             removeItem(stFirstPos);
0305             removeItem(stSecondPos);
0306 
0307             // Reset the selected item variable.
0308             m_selectedItem = nullptr;
0309 
0310             // Test whether the game is over or not.
0311             if (m_gameData->m_tileNum == 0) {
0312                 Q_EMIT gameOver(m_gameData->m_maxTileNum, m_cheatsUsed);
0313             } else {
0314                 // The game is not over, so test if there are any valid moves.
0315                 validMovesAvailable();
0316             }
0317         } else {
0318             // The second tile keeps selected and becomes the first one.
0319             m_selectedItem = selectedGameItems.at(0);
0320 
0321             // Display the matching ones if wanted.
0322             if (m_match) {
0323                 helpMatch(m_selectedItem);
0324             }
0325         }
0326     }
0327 }
0328 
0329 void GameView::removeItem(POSITION & stItemPos)
0330 {
0331     // Adding the data to the protocol.
0332     m_gameData->setMoveListData(m_gameData->m_tileNum, stItemPos);
0333 
0334     // Put an empty item in the data object. (data part)
0335     m_gameData->putTile(stItemPos.z, stItemPos.y, stItemPos.x, 0);
0336 
0337     // Add the tile to the removedtiles object.
0338     m_gameRemovedTiles->addItem(stItemPos);
0339     m_gameRemovedTiles->update();
0340 
0341     // Remove the item from the scene object. (graphic part)
0342     scene()->removeItem(stItemPos);
0343 
0344     // Decrement the tilenum variable from GameData.
0345     m_gameData->m_tileNum = m_gameData->m_tileNum - 1;
0346 
0347     // If TileNum is % 2 then update the number in the status bar.
0348     if (!(m_gameData->m_tileNum % 2)) {
0349         // The item numbers changed, so we need to populate the new information.
0350         populateItemNumber();
0351     }
0352 }
0353 
0354 void GameView::startDemo()
0355 {
0356     qCDebug(KMAHJONGG_LOG) << "Starting demo mode";
0357 
0358     // Create a new game with the actual game number.
0359     createNewGame(m_gameNumber);
0360 
0361     if (m_gameGenerated) {
0362         // Start the demo mode.
0363         m_demoAnimation->start(m_gameData);
0364 
0365         // Set the status text.
0366         setStatusText(i18n("Demo mode. Click mousebutton to stop."));
0367     }
0368 }
0369 
0370 void GameView::startMoveListAnimation()
0371 {
0372     qCDebug(KMAHJONGG_LOG) << "Starting move list animation";
0373 
0374     // Stop any helping animation.
0375     checkHelpAnimationActive(true);
0376 
0377     // Stop demo animation, if anyone is running.
0378     checkDemoAnimationActive(true);
0379 
0380     m_moveListAnimation->start(m_gameData);
0381 }
0382 
0383 void GameView::clearSelectedTile()
0384 {
0385     scene()->clearSelection();
0386     m_selectedItem = nullptr;
0387 }
0388 
0389 void GameView::changeItemSelectedState(POSITION & stItemPos, bool selected)
0390 {
0391     GameItem * gameItem = scene()->getItemOnGridPos(stItemPos);
0392 
0393     if (gameItem != nullptr) {
0394         gameItem->setSelected(selected);
0395     }
0396 }
0397 
0398 void GameView::helpMove()
0399 {
0400     POSITION stItem1;
0401     POSITION stItem2;
0402 
0403     // Stop a running help animation.
0404     checkHelpAnimationActive(true);
0405 
0406     if (m_gameData->findMove(stItem1, stItem2)) {
0407         clearSelectedTile();
0408         m_helpAnimation->addGameItem(scene()->getItemOnGridPos(stItem1));
0409         m_helpAnimation->addGameItem(scene()->getItemOnGridPos(stItem2));
0410 
0411         // Increase the cheat variable.
0412         ++m_cheatsUsed;
0413 
0414         m_helpAnimation->start();
0415     }
0416 }
0417 
0418 void GameView::helpMatch(GameItem const * const gameItem)
0419 {
0420     int matchCount = 0;
0421     POSITION stGameItemPos = gameItem->getGridPos();
0422 
0423     // Stop a running help animation.
0424     checkHelpAnimationActive(true);
0425 
0426     // Find matching items...
0427     if ((matchCount = m_gameData->findAllMatchingTiles(stGameItemPos))) {
0428         // ...add them to the animation object...
0429         for (int i = 0; i < matchCount; ++i) {
0430             if (scene()->getItemOnGridPos(m_gameData->getFromPosTable(i)) != gameItem) {
0431                 m_helpAnimation->addGameItem(scene()->getItemOnGridPos(
0432                     m_gameData->getFromPosTable(i)));
0433             }
0434         }
0435 
0436         // Increase the cheat variable.
0437         ++m_cheatsUsed;
0438 
0439         // ...and start the animation.
0440         m_helpAnimation->start();
0441     }
0442 }
0443 
0444 bool GameView::checkHelpAnimationActive(bool stop)
0445 {
0446     bool active = m_helpAnimation->isActive();
0447 
0448     // If animation is running and it should be closed, do so.
0449     if (active && stop) {
0450         m_helpAnimation->stop();
0451     }
0452 
0453     return active;
0454 }
0455 
0456 bool GameView::checkMoveListAnimationActive(bool stop)
0457 {
0458     bool active = m_moveListAnimation->isActive();
0459 
0460     // If animation is running and it should be closed, do so.
0461     if (active && stop) {
0462         m_moveListAnimation->stop();
0463     }
0464 
0465     return active;
0466 }
0467 
0468 bool GameView::checkDemoAnimationActive(bool stop)
0469 {
0470     bool active = m_demoAnimation->isActive();
0471 
0472     // If animation is running and it should be closed, do so.
0473     if (active && stop) {
0474         m_demoAnimation->stop();
0475     }
0476 
0477     return active;
0478 }
0479 
0480 bool GameView::validMovesAvailable(bool silent)
0481 {
0482     POSITION stItem1;
0483     POSITION stItem2;
0484 
0485     if (!m_gameData->findMove(stItem1, stItem2)) {
0486         if (!silent) {
0487             Q_EMIT noMovesAvailable();
0488         }
0489         return false;
0490     }
0491     return true;
0492 }
0493 
0494 void GameView::pause(bool isPaused)
0495 {
0496     const QList<GameItem *> gameItems = getGameItems();
0497     if (isPaused) {
0498         for (GameItem * item : gameItems) {
0499             item->hide();
0500         }
0501     } else {
0502         for (GameItem * item : gameItems) {
0503             item->show();
0504         }
0505     }
0506 }
0507 
0508 bool GameView::gameGenerated()
0509 {
0510     return m_gameGenerated;
0511 }
0512 
0513 void GameView::shuffle()
0514 {
0515     if (!gameGenerated()) {
0516         return;
0517     }
0518 
0519     if (checkDemoAnimationActive() || checkMoveListAnimationActive()) {
0520         return;
0521     }
0522 
0523     // Fix bug 156022 comment 5: Redo after shuffle can cause invalid states.
0524     m_gameData->m_allowRedo = 0;
0525 
0526     m_gameData->shuffle();
0527 
0528     // Update the item images.
0529     updateItemsImages(getGameItems());
0530 
0531     // Cause of using the shuffle function... increase the cheat used variable.
0532     m_cheatsUsed += 15;
0533 
0534     // Populate the new item numbers.
0535     populateItemNumber();
0536 
0537     // Test if any moves are available
0538     validMovesAvailable();
0539 
0540     // Clear any tile selection done prior to the shuffle.
0541     clearSelectedTile();
0542 }
0543 
0544 void GameView::populateItemNumber()
0545 {
0546     // Update the allow_undo variable, cause the item number changes.
0547     m_gameData->m_allowUndo = (m_gameData->m_maxTileNum != m_gameData->m_tileNum);
0548 
0549     Q_EMIT itemNumberChanged(m_gameData->m_maxTileNum, m_gameData->m_tileNum, m_gameData->moveCount());
0550 }
0551 
0552 void GameView::addItemsFromBoardLayout()
0553 {
0554     // The QGraphicsScene::selectionChanged() signal can be emitted when deleting or removing
0555     // items, so disconnect from this signal to prevent our selectionChanged() slot being
0556     // triggered and trying to access those items when we clear the scene.
0557     // The signal is reconnected at the end of the function.
0558     disconnect(m_selectionChangedConnect);
0559 
0560     // Remove all GameItem objects
0561     scene()->clearGameItems();
0562 
0563     // Create the items and add them to the scene.
0564     for (int iZ = 0; iZ < m_gameData->m_depth; ++iZ) {
0565         for (int iY = m_gameData->m_height - 1; iY >= 0; --iY) {
0566             for (int iX = m_gameData->m_width - 1; iX >= 0; --iX) {
0567                 // Skip if no tile should be displayed on this position.
0568                 if (!m_gameData->tilePresent(iZ, iY, iX)) {
0569                     continue;
0570                 }
0571 
0572                 POSITION stItemPos;
0573                 stItemPos.x = iX;
0574                 stItemPos.y = iY;
0575                 stItemPos.z = iZ;
0576                 stItemPos.f = (m_gameData->BoardData(iZ, iY, iX) - TILE_OFFSET);
0577 
0578                 addItem(stItemPos, false, false, false);
0579             }
0580         }
0581     }
0582 
0583     updateItemsImages(getGameItems());
0584     updateItemsOrder();
0585 
0586     // Reconnect our selectionChanged() slot.
0587     m_selectionChangedConnect = connect(scene(), &GameScene::selectionChanged, this, &GameView::selectionChanged);
0588     m_selectedItem = nullptr;
0589 }
0590 
0591 void GameView::addItem(GameItem * gameItem, bool updateImage, bool updateOrder, bool updatePosition)
0592 {
0593     // Add the item to the scene.
0594     scene()->addItem(gameItem);
0595 
0596     // If TileNum is % 2 then update the number in the status bar.
0597     if (!(m_gameData->m_tileNum % 2)) {
0598         // The item numbers changed, so we need to populate the new information.
0599         populateItemNumber();
0600     }
0601 
0602     QList<GameItem *> gameItems;
0603     gameItems.append(gameItem);
0604 
0605     if (updateImage) {
0606         updateItemsImages(gameItems);
0607     }
0608 
0609     if (updatePosition) {
0610         // When updating the order... the position will automatically be updated after.
0611         if (updateOrder) {
0612             updateItemsOrder();
0613         } else {
0614             updateItemsPosition(gameItems);
0615         }
0616     }
0617 }
0618 
0619 void GameView::addItem(POSITION & stItemPos, bool updateImage, bool updateOrder, bool updatePosition)
0620 {
0621     GameItem * gameItem = new GameItem(m_gameData->HighlightData(stItemPos.z, stItemPos.y, stItemPos.x));
0622     gameItem->setGridPos(stItemPos);
0623     gameItem->setFlag(QGraphicsItem::ItemIsSelectable);
0624 
0625     m_gameData->putTile(stItemPos.z, stItemPos.y, stItemPos.x, stItemPos.f + TILE_OFFSET);
0626     addItem(gameItem, updateImage, updateOrder, updatePosition);
0627 }
0628 
0629 void GameView::addItemAndUpdate(POSITION & stItemPos)
0630 {
0631     addItem(stItemPos, true, true, true);
0632 }
0633 
0634 void GameView::showRemovedTiles(bool show)
0635 {
0636     if (m_showRemovedTiles != show) {
0637         m_showRemovedTiles = show;
0638         m_gameRemovedTiles->setVisible(show);
0639 
0640         QResizeEvent event(size(), size());
0641         resizeEvent(&event);
0642         updateItemsPosition();
0643     }
0644 }
0645 
0646 void GameView::updateItemsPosition()
0647 {
0648     updateItemsPosition(scene()->items());
0649 }
0650 
0651 void GameView::updateItemsPosition(const QList<GameItem *> &gameItems)
0652 {
0653     // The width and height need to be corrected, related to the removedtiles
0654     // view and whether it is shown or not. For now the removed tiles field can
0655     // only be placed to the right of the board.
0656     qreal boardWidth = (!m_showRemovedTiles) ? width() : width() * (1 - m_remTilesWidthFactor - 0.05);
0657     qreal boardHeight = height();
0658 
0659     // TODO: Change!!!
0660     // Make decision of painting the removed tiles.
0661 
0662     // These factor are needed for the different angles. So we simply can
0663     // calculate to move the items to the left or right and up or down.
0664     int angleXFactor = (m_angle == NE || m_angle == SE) ? -1 : 1;
0665     int angleYFactor = (m_angle == NW || m_angle == NE) ? -1 : 1;
0666 
0667     // Get half width and height of tile faces: minimum spacing = 1 pixel.
0668     // NOTE - qWidth is divided by 2 in kmahjonggtileset.cpp. The reason is 
0669     //        unknown for now. Please review this later.
0670     qreal tileFaceWidth = m_tiles->qWidth() * 2 + 1;
0671     qreal tileFaceHeight = m_tiles->qHeight() * 2 + 1;
0672 
0673     // Get half height and width of tile-layout: ((n - 1) faces + full tile)/2.
0674     
0675     // Because the positions of the tiles can be half-positioned, the width and
0676     // height in GameData is two times higher. To get the maximum number of
0677     // tiles in a row or column, the GameData-width and -height have to be
0678     // divided by two.
0679     qreal numTilesX = m_gameData->m_width / 2;
0680     qreal numTilesY = m_gameData->m_height / 2;
0681 
0682     // Calculate the total width and height of board layout with tiles.
0683     qreal tilesWidth = tileFaceWidth * numTilesX + m_tiles->levelOffsetX();
0684     qreal tilesHeight = tileFaceHeight * numTilesY + m_tiles->levelOffsetY();
0685 
0686     // Get the top-left offset required to center the items in the view.
0687     qreal xFrame = (boardWidth - tilesWidth) / 2;
0688     qreal yFrame = (boardHeight - tilesHeight) / 2;
0689 
0690     // TODO - The last /2 makes it HALF what it should be, but it gets doubled
0691     //        somehow before the view is painted. Why? Apparently it is because
0692     //        the background is painted independently by the VIEW, rather than
0693     //        being an item in the scene and filling the scene completely. So
0694     //        the whole scene is just the rectangle that contains the tiles.
0695     // NOTE - scene()->itemsBoundingRect() returns the correct doubled offset.
0696     // NOTE - insert game background object but problem persist
0697     // SOLVED - Problem was in coordinate system in QGraphicsItem/GameItem.
0698     //          Painting positions are relative, but were defined absolute to
0699     //          scene coordinate system. Same with boundingRect().
0700     for (int i = 0; i < gameItems.size(); i++) {
0701         GameItem * gameItem = gameItems.at(i);
0702 
0703         // Get rasterized positions of the item.
0704         int x = gameItem->getGridPosX();
0705         int y = gameItem->getGridPosY();
0706         int z = gameItem->getGridPosZ();
0707 
0708 
0709         // Set the position of the item on the scene.
0710         gameItem->setPos(
0711             xFrame + tileFaceWidth / 2 * x
0712                 + z * angleXFactor * (m_tiles->levelOffsetX()),
0713             yFrame + tileFaceHeight / 2 * y
0714                 + z * angleYFactor * (m_tiles->levelOffsetY()));
0715     }
0716 
0717     // Position the removedtiles object.
0718     qreal removedTilesBorder = height() * 0.1 / 2;
0719     m_gameRemovedTiles->setPos(
0720         width() * (1 - m_remTilesWidthFactor), removedTilesBorder
0721     );
0722 }
0723 
0724 void GameView::updateItemsOrder()
0725 {
0726     int zCount = 0;
0727     int xStart = 0;
0728     int xEnd = 0;
0729     int xCounter = 0;
0730     int yStart = 0;
0731     int yEnd = 0;
0732     int yCounter = 0;
0733 
0734     switch (m_angle) {
0735         case NW:
0736             xStart = m_gameData->m_width - 1;
0737             xEnd = -1;
0738             xCounter = -1;
0739 
0740             yStart = 0;
0741             yEnd = m_gameData->m_height;
0742             yCounter = 1;
0743             break;
0744         case NE:
0745             xStart = 0;
0746             xEnd = m_gameData->m_width;
0747             xCounter = 1;
0748 
0749             yStart = 0;
0750             yEnd = m_gameData->m_height;
0751             yCounter = 1;
0752             break;
0753         case SE:
0754             xStart = 0;
0755             xEnd = m_gameData->m_width;
0756             xCounter = 1;
0757 
0758             yStart = m_gameData->m_height - 1;
0759             yEnd = -1;
0760             yCounter = -1;
0761             break;
0762         case SW:
0763             xStart = m_gameData->m_width - 1;
0764             xEnd = -1;
0765             xCounter = -1;
0766 
0767             yStart = m_gameData->m_height - 1;
0768             yEnd = -1;
0769             yCounter = -1;
0770             break;
0771     }
0772 
0773     GameScene * gameScene = scene();
0774 
0775     for (int z = 0; z < m_gameData->m_depth; ++z) {
0776         for (int y = yStart; y != yEnd; y = y + yCounter) {
0777             orderLine(gameScene->getItemOnGridPos(xStart, y, z), xStart, xEnd, xCounter, y, yCounter, z, zCount);
0778         }
0779     }
0780 
0781     updateItemsPosition(getGameItems());
0782 }
0783 
0784 void GameView::orderLine(GameItem * startItem, int xStart, int xEnd, int xCounter, int y, int yCounter, int z, int & zCount)
0785 {
0786     GameScene * gameScene = scene();
0787     GameItem * gameItem = startItem;
0788 
0789     for (int i = xStart; i != xEnd; i = i + xCounter) {
0790         if (gameItem == nullptr) {
0791             if ((gameItem = gameScene->getItemOnGridPos(i, y, z)) == nullptr) {
0792                 continue;
0793             }
0794         }
0795 
0796         gameItem->setZValue(zCount);
0797         ++zCount;
0798 
0799         gameItem = gameScene->getItemOnGridPos(i + 2 * xCounter, y - 1 * yCounter, z);
0800         if (gameItem != nullptr) {
0801             orderLine(gameItem, i + 2 * xCounter, xEnd, xCounter, y - 1 * yCounter, yCounter, z, zCount);
0802             gameItem = nullptr;
0803         }
0804     }
0805 }
0806 
0807 bool GameView::setTilesetPath(QString const & tilesetPath)
0808 {
0809     *m_tilesetPath = tilesetPath;
0810 
0811     if (m_tiles->loadTileset(tilesetPath)) {
0812         if (m_tiles->loadGraphics()) {
0813             resizeTileset(size());
0814 
0815             return true;
0816         }
0817     }
0818 
0819     // Tileset or graphics could not be loaded, try default
0820     if (m_tiles->loadDefault()) {
0821         if (m_tiles->loadGraphics()) {
0822             resizeTileset(size());
0823 
0824             *m_tilesetPath = m_tiles->path();
0825         }
0826     }
0827 
0828     return false;
0829 }
0830 
0831 bool GameView::setBackgroundPath(QString const & backgroundPath)
0832 {
0833     qCDebug(KMAHJONGG_LOG) << "Set a new Background: " << backgroundPath;
0834 
0835     *m_backgroundPath = backgroundPath;
0836 
0837     if (m_background->load(backgroundPath, width(), height())) {
0838         if (m_background->loadGraphics()) {
0839             // Update the new background.
0840             updateBackground();
0841 
0842             return true;
0843         }
0844     }
0845 
0846     qCDebug(KMAHJONGG_LOG) << "Loading the background failed. Try to load the default background.";
0847 
0848     // Try default
0849     if (m_background->loadDefault()) {
0850         if (m_background->loadGraphics()) {
0851             // Update the new background.
0852             updateBackground();
0853 
0854             *m_backgroundPath = m_background->path();
0855         }
0856     }
0857 
0858     return false;
0859 }
0860 
0861 void GameView::setAngle(TileViewAngle angle)
0862 {
0863     m_angle = angle;
0864     updateItemsImages(getGameItems());
0865     updateItemsOrder();
0866 }
0867 
0868 TileViewAngle GameView::getAngle() const
0869 {
0870     return m_angle;
0871 }
0872 
0873 void GameView::angleSwitchCCW()
0874 {
0875     switch (m_angle) {
0876         case SW:
0877             m_angle = NW;
0878             break;
0879         case NW:
0880             m_angle = NE;
0881             break;
0882         case NE:
0883             m_angle = SE;
0884             break;
0885         case SE:
0886             m_angle = SW;
0887             break;
0888     }
0889 
0890     updateItemsImages(getGameItems());
0891     updateItemsOrder();
0892 }
0893 
0894 void GameView::angleSwitchCW()
0895 {
0896     switch (m_angle) {
0897         case SW:
0898             m_angle = SE;
0899             break;
0900         case SE:
0901             m_angle = NE;
0902             break;
0903         case NE:
0904             m_angle = NW;
0905             break;
0906         case NW:
0907             m_angle = SW;
0908             break;
0909     }
0910 
0911     updateItemsImages(getGameItems());
0912     updateItemsOrder();
0913 }
0914 
0915 QList<GameItem *> GameView::getGameItems() const
0916 {
0917     QList<QGraphicsItem *> items = QGraphicsView::items();
0918     QList<GameItem *> tmpList;
0919 
0920     tmpList.reserve(items.size());
0921     for (int i = 0; i < items.size(); ++i) {
0922         GameItem *gameItem = dynamic_cast<GameItem *>(items.at(i));
0923         if (nullptr != gameItem) {
0924             tmpList.append(gameItem);
0925         }
0926     }
0927 
0928     return tmpList;
0929 }
0930 
0931 void GameView::mousePressEvent(QMouseEvent * pMouseEvent)
0932 {
0933     // If a move list animation is running start a new game.
0934     if (checkMoveListAnimationActive(true)) {
0935         Q_EMIT demoOrMoveListAnimationOver(false);
0936         return;
0937     }
0938 
0939     // No mouse events when the demo mode is active.
0940     if (checkDemoAnimationActive(true)) {
0941         Q_EMIT demoOrMoveListAnimationOver(false);
0942         return;
0943     }
0944 
0945     // If any help mode is active, ... stop it.
0946     checkHelpAnimationActive(true);
0947 
0948     // Then go on with the press event.
0949     QGraphicsView::mousePressEvent(pMouseEvent);
0950 }
0951 
0952 void GameView::resizeEvent(QResizeEvent * event)
0953 {
0954     if (event->spontaneous() || m_gameData == nullptr) {
0955         return;
0956     }
0957 
0958     // The size need to be corrected, related to the removedtiles
0959     // view and whether it is shown or not. For now the removed tiles field can
0960     // only be placed to the right of the board.
0961     QSize size(event->size());
0962     if (m_showRemovedTiles) {
0963         resizeTileset(QSize(
0964             size.width() * (1 - m_remTilesWidthFactor - 0.05), size.height()
0965         ));
0966 
0967         // Update removed tiles
0968         qreal removedTilesBorder = size.height() * 0.1 / 2;
0969         m_gameRemovedTiles->setSize(
0970             size.width() * m_remTilesWidthFactor - removedTilesBorder, 
0971             size.height() * 0.9
0972         );
0973     } else {
0974         resizeTileset(QSize(
0975             size.width(), size.height()
0976         ));
0977     }
0978 
0979     // Update background
0980     m_background->sizeChanged(width(), height());
0981     updateBackground();
0982 
0983     setSceneRect(0, 0, width(), height());
0984 }
0985 
0986 void GameView::resizeTileset(const QSize & size)
0987 {
0988     if (m_gameData == nullptr) {
0989         return;
0990     }
0991 
0992     QSize newtiles = m_tiles->preferredTileSize(
0993         size, m_gameData->m_width / 2, m_gameData->m_height / 2
0994     );
0995 
0996     const QList<GameItem *> gameItems = getGameItems();
0997     for (GameItem * item : gameItems) {
0998         item->prepareForGeometryChange();
0999     }
1000 
1001     m_tiles->reloadTileset(newtiles);
1002 
1003     updateItemsImages(getGameItems());
1004     updateItemsPosition(getGameItems());
1005 }
1006 
1007 void GameView::updateItemsImages(const QList<GameItem *> &gameItems)
1008 {
1009     for (int i = 0; i < gameItems.size(); ++i) {
1010         GameItem * gameItem = gameItems.at(i);
1011 
1012         QPixmap selPix;
1013         QPixmap unselPix;
1014         QPixmap facePix;
1015 
1016         USHORT faceId = (m_gameData->BoardData(gameItem->getGridPosZ(), gameItem->getGridPosY(), gameItem->getGridPosX()) - TILE_OFFSET);
1017 
1018         gameItem->setFaceId(faceId);
1019 
1020         facePix = m_tiles->tileface(faceId);
1021         selPix = m_tiles->selectedTile(m_angle);
1022         unselPix = m_tiles->unselectedTile(m_angle);
1023 
1024         // Set the background pictures to the item.
1025         int shadowWidth = selPix.width() - m_tiles->levelOffsetX() - facePix.width();
1026         int shadowHeight = selPix.height() - m_tiles->levelOffsetY() - facePix.height();
1027 
1028         gameItem->setAngle(m_angle, &selPix, &unselPix, shadowWidth, shadowHeight);
1029         gameItem->setFace(&facePix);
1030     }
1031 
1032     // Repaint the view.
1033     update();
1034 }
1035 
1036 void GameView::setStatusText(QString const & text)
1037 {
1038     Q_EMIT statusTextChanged(text, m_gameNumber);
1039 }
1040 
1041 void GameView::updateBackground()
1042 {
1043     m_gameBackground->setBackground(m_background->getBackground());
1044     m_gameBackground->setSize(width(), height());
1045 }
1046 
1047 void GameView::setGameData(GameData * gameData)
1048 {
1049     m_gameData = gameData;
1050 
1051     addItemsFromBoardLayout();
1052     populateItemNumber();
1053 }
1054 
1055 GameData * GameView::getGameData() const
1056 {
1057     return m_gameData;
1058 }
1059 
1060 QString GameView::getTilesetPath() const
1061 {
1062     return *m_tilesetPath;
1063 }
1064 
1065 QString GameView::getBackgroundPath() const
1066 {
1067     return *m_backgroundPath;
1068 }
1069 
1070 void GameView::setMatch(bool match)
1071 {
1072     m_match = match;
1073 }
1074 
1075 bool GameView::getMatch() const
1076 {
1077     return m_match;
1078 }
1079 
1080 #include "moc_gameview.cpp"