File indexing completed on 2024-03-24 04:05:17
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"